Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ Shareable, safe for dotfiles:
```yaml
language: English
num_suggestions: 10
# max_diff_chars: 40000 # size budget for the diff sent to the model
# system_message: ...
# commit_message_template: "... %s" # %s is replaced by the diff
# pr_title_template: "... %s"
Expand Down Expand Up @@ -171,6 +172,7 @@ Variant that lets you edit the message before committing:
- `No staged changes to commit.` — run `git add` first.
- `has no model configured` — run `lazycommit config set`.
- `environment variable X is not set` — your config references `$X`; export it or store the key directly.
- Large change sets: when the diff is longer than `max_diff_chars`, lazycommit sends a list of the changed files with line counts plus as many file diffs as fit; a very long file list is itself cut short with a count of the remaining files. Lock files, minified files, and binary files are listed without their diffs. Lower the value if your model still rejects the request as too large.

## License

Expand Down
1 change: 1 addition & 0 deletions internal/app/ports.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type PromptSettings struct {
PRTitleTemplate domain.PromptTemplate
Language domain.Language
SuggestionCount int
MaxDiffChars int
}

// ConfigRepository yields the effective settings the use cases need.
Expand Down
8 changes: 7 additions & 1 deletion internal/app/suggest.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func (uc *GeneratePRTitles) Execute(ctx context.Context, target string) (Suggest
}

// suggestionPipeline is the shared flow: read diff, short-circuit when
// empty, build prompt, generate, parse. Commit and PR generation differ
// empty, compact to the size budget, build prompt, generate, parse. Commit and PR generation differ
// only in diff source and template.
type suggestionPipeline struct {
gen Generator
Expand Down Expand Up @@ -86,6 +86,12 @@ func (p suggestionPipeline) run(
return SuggestionsResult{}, fmt.Errorf("loading configuration: %w", err)
}

maxDiff := settings.MaxDiffChars
if maxDiff <= 0 {
maxDiff = domain.DefaultMaxDiffChars
}
diff = diff.Compact(maxDiff)

prompt := domain.NewPromptBuilder().
WithSystemMessage(settings.SystemMessage).
WithTemplate(pickTemplate(settings)).
Expand Down
21 changes: 21 additions & 0 deletions internal/app/suggest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,27 @@ func TestCommitSuggestionsHappyPath(t *testing.T) {
}
}

func TestCommitSuggestionsCompactsLargeDiff(t *testing.T) {
raw := "diff --git a/big.txt b/big.txt\n--- a/big.txt\n+++ b/big.txt\n@@ -0,0 +1,500 @@\n" +
strings.Repeat("+some added content\n", 500)
settings := testSettings(t)
settings.MaxDiffChars = 1000
gen := &fakeGenerator{output: "feat: one"}
uc := NewGenerateCommitSuggestions(gen,
&fakeDiffSource{staged: raw},
&fakeConfig{settings: settings})

if _, err := uc.Execute(context.Background()); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if strings.Contains(gen.lastPrompt.User, raw) {
t.Fatal("full diff must not be sent when it exceeds the budget")
}
if !strings.Contains(gen.lastPrompt.User, "big.txt +500 -0") {
t.Fatalf("prompt misses the file summary: %q", gen.lastPrompt.User)
}
}

func TestCommitSuggestionsEmptyDiffShortCircuits(t *testing.T) {
gen := &fakeGenerator{output: "unused"}
uc := NewGenerateCommitSuggestions(gen,
Expand Down
9 changes: 9 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ type Prompts struct {
CommitMessageTemplate string `yaml:"commit_message_template,omitempty"`
PRTitleTemplate string `yaml:"pr_title_template,omitempty"`
NumSuggestions int `yaml:"num_suggestions,omitempty"`
MaxDiffChars int `yaml:"max_diff_chars,omitempty"`
}

// DefaultBackends is the effective configuration when no file exists; the
Expand Down Expand Up @@ -198,13 +199,18 @@ func (r *Repository) PromptSettings() (app.PromptSettings, error) {
if count <= 0 {
count = domain.DefaultSuggestionCount
}
maxDiff := p.MaxDiffChars
if maxDiff <= 0 {
maxDiff = domain.DefaultMaxDiffChars
}

return app.PromptSettings{
SystemMessage: system,
CommitTemplate: commit,
PRTitleTemplate: pr,
Language: domain.NewLanguage(p.Language),
SuggestionCount: count,
MaxDiffChars: maxDiff,
}, nil
}

Expand Down Expand Up @@ -238,6 +244,9 @@ func mergePrompts(top, bottom Prompts) Prompts {
if top.NumSuggestions > 0 {
out.NumSuggestions = top.NumSuggestions
}
if top.MaxDiffChars > 0 {
out.MaxDiffChars = top.MaxDiffChars
}
return out
}

Expand Down
8 changes: 8 additions & 0 deletions internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@ func TestPromptSettingsDefaultsWhenNoFiles(t *testing.T) {
if s.SystemMessage != domain.DefaultSystemMessage {
t.Fatalf("system = %q", s.SystemMessage)
}
if s.MaxDiffChars != domain.DefaultMaxDiffChars {
t.Fatalf("max diff chars = %d", s.MaxDiffChars)
}
}

func TestPromptSettingsLayering(t *testing.T) {
Expand All @@ -134,10 +137,12 @@ func TestPromptSettingsLayering(t *testing.T) {
language: Spanish
system_message: global system
num_suggestions: 7
max_diff_chars: 5000
`)
writeFile(t, filepath.Join(repoRoot, "lazycommit.prompts.yaml"), `
language: Korean
commit_message_template: "REPO %s"
max_diff_chars: 8000
`)

s, err := NewRepository(globalDir, repoRoot).PromptSettings()
Expand All @@ -153,6 +158,9 @@ commit_message_template: "REPO %s"
if s.SuggestionCount != 7 {
t.Fatalf("global count should fall through, got %d", s.SuggestionCount)
}
if s.MaxDiffChars != 8000 {
t.Fatalf("repo-local max diff chars should win, got %d", s.MaxDiffChars)
}
if s.CommitTemplate.String() != "REPO %s" {
t.Fatalf("repo-local template should win, got %q", s.CommitTemplate)
}
Expand Down
Loading
Loading