Skip to content

Commit 52d20b1

Browse files
authored
Merge pull request #12 from Iron-Ham/hs/update-diff
Improve Commit Generation
2 parents dd052c2 + afc1b22 commit 52d20b1

18 files changed

+1917
-49
lines changed

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,11 @@ scg generate [OPTIONS]
123123
| `--quiet` | `-q` | Suppress routine info lines | Off | Hides `[INFO]` but keeps `[NOTICE]`, warnings, errors. Ignored if `--verbose` is present. |
124124
| `--no-quiet` | | Ensure quiet mode is disabled, even if configured | - | Helpful when scripts need full output. |
125125
| `--single-file` | | Analyze each file independently and then combine per-file drafts | Off | Sends a larger diff slice per file, useful when you need high-fidelity summaries. |
126+
| `--function-context` | | Include entire functions containing changes in the diff | On | Provides better semantic context for the AI model. |
127+
| `--no-function-context` | | Disable function context in diffs | - | May reduce diff size for very large changesets. |
128+
| `--detect-renames` | | Detect renamed and copied files in diffs | On | Shows moves as renames rather than delete + add. |
129+
| `--no-detect-renames` | | Disable rename/copy detection | - | Use raw add/delete representation. |
130+
| `--context-lines <n>` | | Number of context lines around changes | `3` | Higher values give more surrounding code context. |
126131

127132
### Verbosity Levels
128133

@@ -174,6 +179,12 @@ Request high-fidelity per-file drafts before they are combined:
174179
scg --single-file
175180
```
176181

182+
Generate with more context lines and disabled function context (for very large diffs):
183+
184+
```sh
185+
scg --context-lines 5 --no-function-context
186+
```
187+
177188
Configuration Defaults
178189
----------------------
179190
Use the `config` subcommand to inspect or update stored defaults. Running it with no flags opens an interactive, colorized editor that walks through each preference:
@@ -193,6 +204,14 @@ Available options:
193204
| `--clear-verbose` | Remove the stored verbose preference. |
194205
| `--quiet <true\|false>` | Set the default quiet logging preference. |
195206
| `--clear-quiet` | Remove the stored quiet preference. |
207+
| `--mode <automatic\|per-file>` | Set the default generation mode. |
208+
| `--clear-mode` | Remove the stored generation mode preference. |
209+
| `--function-context <true\|false>` | Set whether to include entire functions in diffs. |
210+
| `--clear-function-context` | Remove the stored function-context preference. |
211+
| `--detect-renames <true\|false>` | Set whether to detect renamed/copied files in diffs. |
212+
| `--clear-detect-renames` | Remove the stored detect-renames preference. |
213+
| `--context-lines <n>` | Set the default number of context lines around changes. |
214+
| `--clear-context-lines` | Remove the stored context-lines preference. |
196215

197216
When no options are provided, the command detects whether the terminal is interactive and presents guided prompts with recommended defaults highlighted. Stored settings live in `~/Library/Application Support/scg/config.json`.
198217

Sources/SwiftCommitGen/CommitGenOptions.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
import FoundationModels
22

3+
/// Configuration for git diff command behavior.
4+
struct DiffOptions: Codable, Hashable {
5+
/// Include entire functions containing changes for better semantic context.
6+
var useFunctionContext: Bool = true
7+
/// Detect renamed and copied files rather than showing as delete + add.
8+
var detectRenamesCopies: Bool = true
9+
/// Number of context lines around changes. Uses git default (3) when nil.
10+
var contextLines: Int? = nil
11+
12+
static let `default` = DiffOptions()
13+
}
14+
315
/// Container for the resolved configuration driving a commit generation run.
416
struct CommitGenOptions {
517
/// Supported output formats for the rendered draft.
@@ -43,4 +55,6 @@ struct CommitGenOptions {
4355
var isQuiet: Bool
4456
/// Controls whether batching happens automatically or per file.
4557
var generationMode: GenerationMode
58+
/// Configuration for git diff command behavior.
59+
var diffOptions: DiffOptions
4660
}

Sources/SwiftCommitGen/CommitGenTool.swift

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,10 @@ struct CommitGenTool {
118118
}
119119

120120
let stagedStatus = GitStatus(staged: stagedChanges, unstaged: [], untracked: [])
121-
let summary = try await summarizer.summarize(status: stagedStatus)
121+
let summary = try await summarizer.summarize(
122+
status: stagedStatus,
123+
diffOptions: options.diffOptions
124+
)
122125
logger.notice(
123126
"Summary: \(summary.fileCount) file(s), +\(summary.totalAdditions) / -\(summary.totalDeletions)"
124127
)
@@ -149,8 +152,12 @@ struct CommitGenTool {
149152
fullSummary: summary
150153
)
151154
case .automatic:
155+
// Group semantically related files together (source + tests, same directory)
156+
let grouper = SemanticFileGrouper()
157+
let fileGroups = grouper.groupFiles(summary.files)
158+
152159
let planner = PromptBatchPlanner()
153-
let batches = planner.planBatches(for: summary)
160+
let batches = planner.planBatches(for: summary, groups: fileGroups)
154161
let useBatching = shouldUseBatching(for: batches)
155162

156163
if useBatching {
@@ -581,6 +588,44 @@ extension CommitGenTool {
581588
"Large change set spans \(fullSummary.fileCount) file(s); splitting into \(batches.count) prompt batch(es)."
582589
)
583590

591+
// Two-pass analysis: For large changesets, first get a high-level overview
592+
// to provide context to each batch. This helps maintain coherence across batches.
593+
let useTwoPass = fullSummary.fileCount > 15 || batches.count > 3
594+
var overviewContext: String?
595+
596+
if useTwoPass {
597+
logger.info("Using two-pass analysis for large changeset...")
598+
599+
let overviewBuilder = OverviewPromptBuilder()
600+
let overviewPrompt = overviewBuilder.makePrompt(summary: fullSummary, metadata: metadata)
601+
logPromptDiagnostics(overviewPrompt.diagnostics)
602+
603+
do {
604+
let overviewResult = try await llmClient.generateOverview(from: overviewPrompt)
605+
logPromptDiagnostics(overviewResult.diagnostics)
606+
607+
// Build minimal context from overview - only category and key files.
608+
// Explicitly avoid passing the summary to batches, as the on-device LLM
609+
// tends to copy it verbatim instead of analyzing the actual diffs.
610+
var contextParts: [String] = []
611+
let category = overviewResult.overview.category
612+
if !category.isEmpty {
613+
contextParts.append("Overall change type: \(category)")
614+
}
615+
if !overviewResult.overview.keyFiles.isEmpty {
616+
contextParts.append(
617+
"Key files in full changeset: \(overviewResult.overview.keyFiles.prefix(5).joined(separator: ", "))")
618+
}
619+
overviewContext = contextParts.isEmpty ? nil : contextParts.joined(separator: ". ")
620+
621+
logger.debug("Overview summary: \(overviewResult.overview.summary)")
622+
logger.debug("Overview category: \(overviewResult.overview.category)")
623+
} catch {
624+
// If overview generation fails, continue without it
625+
logger.warning("Overview generation failed, proceeding without high-level context: \(error)")
626+
}
627+
}
628+
584629
var partialDrafts: [BatchPartialDraft] = []
585630

586631
for (index, batch) in batches.enumerated() {
@@ -593,9 +638,16 @@ extension CommitGenTool {
593638
var batchPackage = promptBuilder.makePrompt(summary: batchSummary, metadata: metadata)
594639

595640
let fileList = batch.files.prefix(10).map { "- \($0.path)" }.joined(separator: "\n")
596-
var contextLines: [String] = [
597-
"Batch \(batchNumber) of \(batches.count). Focus exclusively on these files; other batches are handled separately."
598-
]
641+
var contextLines: [String] = []
642+
643+
contextLines.append(
644+
"Batch \(batchNumber) of \(batches.count). Analyze the diff content above and describe the specific code changes."
645+
)
646+
647+
// Include minimal overview context (category and key files only)
648+
if let overview = overviewContext {
649+
contextLines.append(overview)
650+
}
599651
if !fileList.isEmpty {
600652
contextLines.append("Files in this batch:")
601653
contextLines.append(fileList)

0 commit comments

Comments
 (0)