From 6d30bf4923e7319ddee1083217c0d15ef23489f0 Mon Sep 17 00:00:00 2001
From: Patrick Ruddiman <86851465+PatrickRuddiman@users.noreply.github.com>
Date: Sun, 29 Jun 2025 11:41:12 -0400
Subject: [PATCH 1/3] feat: improve token counting and add brevity pattern
---
Constants/FabricPatterns.cs | 5 +++
Constants/PatternNames.cs | 5 +++
README.md | 3 ++
Services/OpenAIService.cs | 42 +++++++++++++++++++++++++-
Services/SemanticCoherenceAnalyzer.cs | 6 ++--
Services/TokenHelper.cs | 26 ++++++++++++++++
WriteCommit.csproj | 1 +
patterns/brief_chunk_summary/system.md | 11 +++++++
8 files changed, 95 insertions(+), 4 deletions(-)
create mode 100644 Services/TokenHelper.cs
create mode 100644 patterns/brief_chunk_summary/system.md
diff --git a/Constants/FabricPatterns.cs b/Constants/FabricPatterns.cs
index 2f9e923..7eb456f 100644
--- a/Constants/FabricPatterns.cs
+++ b/Constants/FabricPatterns.cs
@@ -11,5 +11,10 @@ public static class FabricPatterns
/// Default pattern used for generating commit messages
///
public const string CommitPattern = "write_commit_message";
+
+ ///
+ /// Pattern used when context overflow requires extra summarization
+ ///
+ public const string BrevityPattern = "brief_chunk_summary";
}
}
diff --git a/Constants/PatternNames.cs b/Constants/PatternNames.cs
index 41fb083..744fc1f 100644
--- a/Constants/PatternNames.cs
+++ b/Constants/PatternNames.cs
@@ -11,5 +11,10 @@ public static class PatternNames
/// Default pattern used for generating commit messages
///
public const string CommitPattern = "write_commit_message";
+
+ ///
+ /// Used when context overflow requires extra summarization
+ ///
+ public const string BrevityPattern = "brief_chunk_summary";
}
}
diff --git a/README.md b/README.md
index 0baca01..91acec8 100644
--- a/README.md
+++ b/README.md
@@ -85,6 +85,9 @@ write-commit --verbose
# Custom AI parameters
write-commit --temperature 0.7 --topp 0.9 --pattern custom_pattern
+# Built-in brevity pattern for overflowing contexts
+write-commit --pattern brief_chunk_summary
+
# Force reinstall all patterns
write-commit --reinstall-patterns
diff --git a/Services/OpenAIService.cs b/Services/OpenAIService.cs
index 56d4b2d..61a19b3 100644
--- a/Services/OpenAIService.cs
+++ b/Services/OpenAIService.cs
@@ -9,6 +9,7 @@ public class OpenAIService
{
private readonly string _apiKey;
private readonly string _patternsDirectory;
+ private const int MaxContextTokens = 128000;
public OpenAIService(string apiKey)
{
@@ -227,6 +228,44 @@ bool verbose
throw new InvalidOperationException($"Failed to load pattern: {pattern}");
}
+ var combinedContent = string.Join("\n\n", chunkMessages);
+ var estimatedTokens = TokenHelper.EstimateTokens(systemPrompt, model) + TokenHelper.EstimateTokens(combinedContent, model);
+
+ if (estimatedTokens > MaxContextTokens && chunkMessages.Count > 1)
+ {
+ if (verbose)
+ {
+ Console.WriteLine("Context length exceeded, re-chunking summaries...");
+ }
+
+ var groupedSummaries = new List();
+ var currentGroup = new List();
+ var currentTokens = TokenHelper.EstimateTokens(systemPrompt, model);
+
+ foreach (var msg in chunkMessages)
+ {
+ var msgTokens = TokenHelper.EstimateTokens(msg, model);
+ if (currentTokens + msgTokens > MaxContextTokens / 2 && currentGroup.Count > 0)
+ {
+ var summary = await CombineChunkMessagesAsync(currentGroup, PatternNames.BrevityPattern, temperature, topP, presence, frequency, model, verbose);
+ groupedSummaries.Add(summary);
+ currentGroup.Clear();
+ currentTokens = TokenHelper.EstimateTokens(systemPrompt, model);
+ }
+
+ currentGroup.Add(msg);
+ currentTokens += msgTokens;
+ }
+
+ if (currentGroup.Count > 0)
+ {
+ var summary = await CombineChunkMessagesAsync(currentGroup, PatternNames.BrevityPattern, temperature, topP, presence, frequency, model, verbose);
+ groupedSummaries.Add(summary);
+ }
+
+ return await CombineChunkMessagesAsync(groupedSummaries, PatternNames.BrevityPattern, temperature, topP, presence, frequency, model, verbose);
+ }
+
// Create a client for this specific model
var chatClient = new ChatClient(model, _apiKey);
@@ -234,7 +273,7 @@ bool verbose
var messages = new List
{
new SystemChatMessage(systemPrompt),
- new UserChatMessage(string.Join("\n\n", chunkMessages)),
+ new UserChatMessage(combinedContent),
};
// Create chat completion options
@@ -313,4 +352,5 @@ private float ConvertPenalty(int penalty)
// OpenAI uses -2 to 2 for penalties
return Math.Clamp((float)penalty, -2f, 2f);
}
+
}
diff --git a/Services/SemanticCoherenceAnalyzer.cs b/Services/SemanticCoherenceAnalyzer.cs
index 64b5044..6b8dae6 100644
--- a/Services/SemanticCoherenceAnalyzer.cs
+++ b/Services/SemanticCoherenceAnalyzer.cs
@@ -2,6 +2,7 @@
using System.Text.RegularExpressions;
using Microsoft.Extensions.Logging;
using WriteCommit.Models;
+using WriteCommit.Services;
namespace WriteCommit.Services;
@@ -311,8 +312,7 @@ private string DetermineChangeType(string content)
private int EstimateTokenCount(string text)
{
- // Rough estimation: ~4 characters per token for code
- // This is conservative to ensure we don't exceed LLM limits
- return Math.Max(1, text.Length / 4);
+ // Use token encoder for more accurate results
+ return TokenHelper.EstimateTokens(text, "gpt-4o-mini");
}
}
diff --git a/Services/TokenHelper.cs b/Services/TokenHelper.cs
new file mode 100644
index 0000000..f6776f0
--- /dev/null
+++ b/Services/TokenHelper.cs
@@ -0,0 +1,26 @@
+using TiktokenSharp;
+
+namespace WriteCommit.Services;
+
+public static class TokenHelper
+{
+ private static readonly Dictionary Encoders = new();
+
+ public static int EstimateTokens(string text, string model)
+ {
+ try
+ {
+ if (!Encoders.TryGetValue(model, out var encoder))
+ {
+ encoder = TikToken.EncodingForModel(model);
+ Encoders[model] = encoder;
+ }
+ return encoder.Encode(text).Count;
+ }
+ catch
+ {
+ // Fallback heuristic
+ return Math.Max(1, text.Length / 4);
+ }
+ }
+}
diff --git a/WriteCommit.csproj b/WriteCommit.csproj
index 2ee0b89..5eafe29 100644
--- a/WriteCommit.csproj
+++ b/WriteCommit.csproj
@@ -24,6 +24,7 @@
+
diff --git a/patterns/brief_chunk_summary/system.md b/patterns/brief_chunk_summary/system.md
new file mode 100644
index 0000000..607cdb8
--- /dev/null
+++ b/patterns/brief_chunk_summary/system.md
@@ -0,0 +1,11 @@
+# IDENTITY and PURPOSE
+You are called when earlier summaries exceed the model context limit.
+Condense the provided summaries into one very short commit summary.
+
+# STEPS
+- Read the given summaries carefully.
+- Produce an extremely concise summary focusing only on key changes.
+- Keep the text brief and under typical commit message length.
+
+# OUTPUT
+Return the single brief summary only.
From d9d3c571fe81fe41fa233eebf3ef9059a88cfc33 Mon Sep 17 00:00:00 2001
From: Patrick Ruddiman <86851465+PatrickRuddiman@users.noreply.github.com>
Date: Tue, 1 Jul 2025 13:31:25 +0000
Subject: [PATCH 2/3] refactor: enhance summarization instructions for clarity
and effectiveness
---
patterns/brief_chunk_summary/system.md | 26 +++++++++++++++++++-------
1 file changed, 19 insertions(+), 7 deletions(-)
diff --git a/patterns/brief_chunk_summary/system.md b/patterns/brief_chunk_summary/system.md
index 607cdb8..08d1b2b 100644
--- a/patterns/brief_chunk_summary/system.md
+++ b/patterns/brief_chunk_summary/system.md
@@ -1,11 +1,23 @@
# IDENTITY and PURPOSE
-You are called when earlier summaries exceed the model context limit.
-Condense the provided summaries into one very short commit summary.
+You are a summarizer machine. Your job is to take a list of chunk objects (from the chunk_git_diff pattern) and distill them into a minimal, high-level summary that preserves the intent and spirit of the changes, so that an AI can write a relevant, human-like git commit message.
-# STEPS
-- Read the given summaries carefully.
-- Produce an extremely concise summary focusing only on key changes.
-- Keep the text brief and under typical commit message length.
+Think step by step:
+1. Read all chunk objects.
+2. Identify the main themes, features, or fixes represented.
+3. Merge related or repetitive changes into a single, concise statement.
+4. Omit low-level details, but keep enough context for a meaningful commit message.
+
+# OUTPUT SECTIONS
+- TITLE: A short, imperative summary of the overall change (max 1 line)
+- DESCRIPTION: 1-3 sentences elaborating on the main changes, grouped by theme or feature
+- TAGS: comma-separated list of key topics, features, or subsystems touched
# OUTPUT
-Return the single brief summary only.
+- Output only the above sections, no extra commentary or formatting.
+- Do not include chunk IDs, file lists, or raw diffs.
+- Focus on clarity, intent, and relevance for a commit message.
+
+# INPUT:
+INPUT:
+
+
From fbdd16eeb4747c5aedaa28a545da46f56dd94c43 Mon Sep 17 00:00:00 2001
From: Patrick Ruddiman <86851465+PatrickRuddiman@users.noreply.github.com>
Date: Tue, 1 Jul 2025 13:33:50 +0000
Subject: [PATCH 3/3] fix: correct typos and improve clarity in commit message
guidelines
---
patterns/write_commit_message/system.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/patterns/write_commit_message/system.md b/patterns/write_commit_message/system.md
index c86296a..d6d89df 100644
--- a/patterns/write_commit_message/system.md
+++ b/patterns/write_commit_message/system.md
@@ -24,7 +24,7 @@ You are a component in an application. You are created to analyize git commits a
- Commit subject should be no more than 50 characters, and the body should be no more than 72 characters per line. (“50/72 formatting”)
-- Terse, consise, and succinct is the goal, dont repeat yourself in the body of the commit message. If there is a bullet point that already kind of explains what the change is, do not repeat it with a new bullet point.
+- Terse and succinct is the goal, don't repeat yourself in the body of the commit message. If there is a bullet point that even remotely explains what the change is, do not repeat it with a new bullet point.
- the commit message should be output in plain text, not in Markdown format. It will be passed directly to the `git commmit -m` command.