git-coco 0.44.0 → 0.45.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +42 -0
- package/dist/index.esm.mjs +220 -13
- package/dist/index.js +220 -13
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -99,6 +99,35 @@ type BaseLLMService = {
|
|
|
99
99
|
* @default 'balanced'
|
|
100
100
|
*/
|
|
101
101
|
dynamicModelPreference?: DynamicModelPreference;
|
|
102
|
+
/**
|
|
103
|
+
* Opt-in fast paths that trade summary detail for speed. Each flag
|
|
104
|
+
* here replaces an LLM summary call with a deterministic templated
|
|
105
|
+
* extract for a specific file shape. Off by default — when enabled,
|
|
106
|
+
* you accept that final commit messages on those file shapes may be
|
|
107
|
+
* blander than LLM-generated summaries (the templated extract names
|
|
108
|
+
* structural changes only).
|
|
109
|
+
*
|
|
110
|
+
* Lossless optimizations (cache, trivial-shape skip on pure
|
|
111
|
+
* additions / deletions / renames / binary, sort discipline) ship
|
|
112
|
+
* default-on and are not configured here.
|
|
113
|
+
*/
|
|
114
|
+
fastPath?: {
|
|
115
|
+
/**
|
|
116
|
+
* Replace the LLM summary with a templated heading extract for
|
|
117
|
+
* `.md` / `.mdx` / `.markdown` modification diffs that have clear
|
|
118
|
+
* heading-level structural changes. Diffs without structural
|
|
119
|
+
* signals (paragraph-only edits) still go to the LLM regardless
|
|
120
|
+
* of this flag.
|
|
121
|
+
*
|
|
122
|
+
* Bench impact (synthetic): collapses docs-update-shaped commits
|
|
123
|
+
* from ~24s cold to ~3ms (no LLM calls fire for the markdown
|
|
124
|
+
* files). Real-world wall-clock savings depend on per-call LLM
|
|
125
|
+
* latency.
|
|
126
|
+
*
|
|
127
|
+
* @default false
|
|
128
|
+
*/
|
|
129
|
+
markdown?: boolean;
|
|
130
|
+
};
|
|
102
131
|
};
|
|
103
132
|
type Authentication = {
|
|
104
133
|
type: 'None';
|
|
@@ -534,6 +563,19 @@ interface BaseParserOptions {
|
|
|
534
563
|
* @default 6
|
|
535
564
|
*/
|
|
536
565
|
maxConcurrent?: number;
|
|
566
|
+
/**
|
|
567
|
+
* Opt-in fast paths that trade summary detail for speed. Mirrors the
|
|
568
|
+
* `service.fastPath` shape. Off by default; lossless optimizations
|
|
569
|
+
* are not configured here.
|
|
570
|
+
*/
|
|
571
|
+
fastPath?: {
|
|
572
|
+
/**
|
|
573
|
+
* Replace the LLM summary with a templated heading extract for
|
|
574
|
+
* markdown modification diffs with structural signals.
|
|
575
|
+
* @default false
|
|
576
|
+
*/
|
|
577
|
+
markdown?: boolean;
|
|
578
|
+
};
|
|
537
579
|
metadata?: Partial<LlmCallMetadata>;
|
|
538
580
|
}
|
|
539
581
|
interface BaseParserInput {
|
package/dist/index.esm.mjs
CHANGED
|
@@ -54,7 +54,7 @@ import { pathToFileURL } from 'url';
|
|
|
54
54
|
/**
|
|
55
55
|
* Current build version from package.json
|
|
56
56
|
*/
|
|
57
|
-
const BUILD_VERSION = "0.
|
|
57
|
+
const BUILD_VERSION = "0.45.0";
|
|
58
58
|
|
|
59
59
|
const isInteractive = (config) => {
|
|
60
60
|
return config?.mode === 'interactive' || !!config?.interactive;
|
|
@@ -1228,6 +1228,18 @@ const schema$1 = {
|
|
|
1228
1228
|
"$ref": "#/definitions/DynamicModelPreference",
|
|
1229
1229
|
"description": "Default dynamic routing preference when model is set to \"dynamic\".",
|
|
1230
1230
|
"default": "balanced"
|
|
1231
|
+
},
|
|
1232
|
+
"fastPath": {
|
|
1233
|
+
"type": "object",
|
|
1234
|
+
"properties": {
|
|
1235
|
+
"markdown": {
|
|
1236
|
+
"type": "boolean",
|
|
1237
|
+
"description": "Replace the LLM summary with a templated heading extract for `.md` / `.mdx` / `.markdown` modification diffs that have clear heading-level structural changes. Diffs without structural signals (paragraph-only edits) still go to the LLM regardless of this flag.\n\nBench impact (synthetic): collapses docs-update-shaped commits from ~24s cold to ~3ms (no LLM calls fire for the markdown files). Real-world wall-clock savings depend on per-call LLM latency.",
|
|
1238
|
+
"default": false
|
|
1239
|
+
}
|
|
1240
|
+
},
|
|
1241
|
+
"additionalProperties": false,
|
|
1242
|
+
"description": "Opt-in fast paths that trade summary detail for speed. Each flag here replaces an LLM summary call with a deterministic templated extract for a specific file shape. Off by default — when enabled, you accept that final commit messages on those file shapes may be blander than LLM-generated summaries (the templated extract names structural changes only).\n\nLossless optimizations (cache, trivial-shape skip on pure additions / deletions / renames / binary, sort discipline) ship default-on and are not configured here."
|
|
1231
1243
|
}
|
|
1232
1244
|
},
|
|
1233
1245
|
"required": [
|
|
@@ -1641,6 +1653,18 @@ const schema$1 = {
|
|
|
1641
1653
|
"$ref": "#/definitions/DynamicModelPreference",
|
|
1642
1654
|
"description": "Default dynamic routing preference when model is set to \"dynamic\".",
|
|
1643
1655
|
"default": "balanced"
|
|
1656
|
+
},
|
|
1657
|
+
"fastPath": {
|
|
1658
|
+
"type": "object",
|
|
1659
|
+
"properties": {
|
|
1660
|
+
"markdown": {
|
|
1661
|
+
"type": "boolean",
|
|
1662
|
+
"description": "Replace the LLM summary with a templated heading extract for `.md` / `.mdx` / `.markdown` modification diffs that have clear heading-level structural changes. Diffs without structural signals (paragraph-only edits) still go to the LLM regardless of this flag.\n\nBench impact (synthetic): collapses docs-update-shaped commits from ~24s cold to ~3ms (no LLM calls fire for the markdown files). Real-world wall-clock savings depend on per-call LLM latency.",
|
|
1663
|
+
"default": false
|
|
1664
|
+
}
|
|
1665
|
+
},
|
|
1666
|
+
"additionalProperties": false,
|
|
1667
|
+
"description": "Opt-in fast paths that trade summary detail for speed. Each flag here replaces an LLM summary call with a deterministic templated extract for a specific file shape. Off by default — when enabled, you accept that final commit messages on those file shapes may be blander than LLM-generated summaries (the templated extract names structural changes only).\n\nLossless optimizations (cache, trivial-shape skip on pure additions / deletions / renames / binary, sort discipline) ship default-on and are not configured here."
|
|
1644
1668
|
}
|
|
1645
1669
|
},
|
|
1646
1670
|
"required": [
|
|
@@ -1797,6 +1821,18 @@ const schema$1 = {
|
|
|
1797
1821
|
"$ref": "#/definitions/DynamicModelPreference",
|
|
1798
1822
|
"description": "Default dynamic routing preference when model is set to \"dynamic\".",
|
|
1799
1823
|
"default": "balanced"
|
|
1824
|
+
},
|
|
1825
|
+
"fastPath": {
|
|
1826
|
+
"type": "object",
|
|
1827
|
+
"properties": {
|
|
1828
|
+
"markdown": {
|
|
1829
|
+
"type": "boolean",
|
|
1830
|
+
"description": "Replace the LLM summary with a templated heading extract for `.md` / `.mdx` / `.markdown` modification diffs that have clear heading-level structural changes. Diffs without structural signals (paragraph-only edits) still go to the LLM regardless of this flag.\n\nBench impact (synthetic): collapses docs-update-shaped commits from ~24s cold to ~3ms (no LLM calls fire for the markdown files). Real-world wall-clock savings depend on per-call LLM latency.",
|
|
1831
|
+
"default": false
|
|
1832
|
+
}
|
|
1833
|
+
},
|
|
1834
|
+
"additionalProperties": false,
|
|
1835
|
+
"description": "Opt-in fast paths that trade summary detail for speed. Each flag here replaces an LLM summary call with a deterministic templated extract for a specific file shape. Off by default — when enabled, you accept that final commit messages on those file shapes may be blander than LLM-generated summaries (the templated extract names structural changes only).\n\nLossless optimizations (cache, trivial-shape skip on pure additions / deletions / renames / binary, sort discipline) ship default-on and are not configured here."
|
|
1800
1836
|
}
|
|
1801
1837
|
},
|
|
1802
1838
|
"required": [
|
|
@@ -7890,6 +7926,109 @@ async function summarize(documents, { chain, textSplitter, options, logger, toke
|
|
|
7890
7926
|
return res.text && res.text.trim();
|
|
7891
7927
|
}
|
|
7892
7928
|
|
|
7929
|
+
/**
|
|
7930
|
+
* Markdown-aware fast path (#861, angle 5). For modification diffs to
|
|
7931
|
+
* `.md` / `.mdx` / `.markdown` files, build a templated summary from
|
|
7932
|
+
* the changed structure (added / removed / updated headings) instead
|
|
7933
|
+
* of paying for an LLM call. Mirrors `trivialDiff` from #845: a deterministic
|
|
7934
|
+
* skip when the diff's meaning is captured by its shape.
|
|
7935
|
+
*
|
|
7936
|
+
* Quality / cost trade-off, on purpose: LLM summaries of markdown edits
|
|
7937
|
+
* are wordier ("expanded the configuration section with new examples,
|
|
7938
|
+
* fixed typos in troubleshooting") but most of that detail isn't load-
|
|
7939
|
+
* bearing for a commit message. The templated summary names the
|
|
7940
|
+
* structural changes (which sections moved) plus a +/- line count, and
|
|
7941
|
+
* defers to the LLM only when the diff has no clear structural signals
|
|
7942
|
+
* (paragraph-only edits, where a templated summary would actually drop
|
|
7943
|
+
* useful context).
|
|
7944
|
+
*/
|
|
7945
|
+
const MARKDOWN_EXTENSIONS = ['.md', '.markdown', '.mdx'];
|
|
7946
|
+
const MAX_HEADINGS_PER_BUCKET = 6;
|
|
7947
|
+
function isMarkdownFile(path) {
|
|
7948
|
+
const lower = path.toLowerCase();
|
|
7949
|
+
return MARKDOWN_EXTENSIONS.some((ext) => lower.endsWith(ext));
|
|
7950
|
+
}
|
|
7951
|
+
function summarizeMarkdownDiff(fileDiff) {
|
|
7952
|
+
if (!isMarkdownFile(fileDiff.file))
|
|
7953
|
+
return undefined;
|
|
7954
|
+
const addedHeadings = new Set();
|
|
7955
|
+
const removedHeadings = new Set();
|
|
7956
|
+
let addedLines = 0;
|
|
7957
|
+
let removedLines = 0;
|
|
7958
|
+
for (const line of fileDiff.diff.split('\n')) {
|
|
7959
|
+
if (isHeaderLine$1(line))
|
|
7960
|
+
continue;
|
|
7961
|
+
if (line.startsWith('+')) {
|
|
7962
|
+
addedLines++;
|
|
7963
|
+
const heading = parseHeading(line.slice(1));
|
|
7964
|
+
if (heading)
|
|
7965
|
+
addedHeadings.add(heading);
|
|
7966
|
+
}
|
|
7967
|
+
else if (line.startsWith('-')) {
|
|
7968
|
+
removedLines++;
|
|
7969
|
+
const heading = parseHeading(line.slice(1));
|
|
7970
|
+
if (heading)
|
|
7971
|
+
removedHeadings.add(heading);
|
|
7972
|
+
}
|
|
7973
|
+
}
|
|
7974
|
+
// No content change → nothing to summarize. Caller falls through.
|
|
7975
|
+
if (addedLines === 0 && removedLines === 0)
|
|
7976
|
+
return undefined;
|
|
7977
|
+
// No structural signal → fall through to LLM. We only fast-path
|
|
7978
|
+
// when the diff has heading-level changes; pure paragraph edits go
|
|
7979
|
+
// to the LLM so the summary keeps its detail.
|
|
7980
|
+
if (addedHeadings.size === 0 && removedHeadings.size === 0) {
|
|
7981
|
+
return undefined;
|
|
7982
|
+
}
|
|
7983
|
+
// A heading that appears in both buckets is likely an update (kept
|
|
7984
|
+
// around but its body changed) rather than two distinct events.
|
|
7985
|
+
// The naive split-by-bucket diff format used by git emits the old
|
|
7986
|
+
// text under `-` and the new text under `+`; an unchanged heading
|
|
7987
|
+
// line shouldn't show up in either bucket via the standard hunk
|
|
7988
|
+
// path, but defensively de-dupe in case the diff producer emits
|
|
7989
|
+
// surrounding context as +/-.
|
|
7990
|
+
const updated = new Set([...addedHeadings].filter((h) => removedHeadings.has(h)));
|
|
7991
|
+
const purelyAdded = [...addedHeadings].filter((h) => !updated.has(h));
|
|
7992
|
+
const purelyRemoved = [...removedHeadings].filter((h) => !updated.has(h));
|
|
7993
|
+
const parts = [`Updated markdown \`${fileDiff.file}\``];
|
|
7994
|
+
if (purelyAdded.length) {
|
|
7995
|
+
parts.push(`new sections: ${formatHeadingList(purelyAdded)}`);
|
|
7996
|
+
}
|
|
7997
|
+
if (purelyRemoved.length) {
|
|
7998
|
+
parts.push(`removed sections: ${formatHeadingList(purelyRemoved)}`);
|
|
7999
|
+
}
|
|
8000
|
+
if (updated.size) {
|
|
8001
|
+
parts.push(`updated sections: ${formatHeadingList([...updated])}`);
|
|
8002
|
+
}
|
|
8003
|
+
parts.push(`+${addedLines}/-${removedLines} lines`);
|
|
8004
|
+
return `${parts.join('. ')}.`;
|
|
8005
|
+
}
|
|
8006
|
+
function formatHeadingList(headings) {
|
|
8007
|
+
if (headings.length <= MAX_HEADINGS_PER_BUCKET) {
|
|
8008
|
+
return headings.join(', ');
|
|
8009
|
+
}
|
|
8010
|
+
const shown = headings.slice(0, MAX_HEADINGS_PER_BUCKET);
|
|
8011
|
+
const remainder = headings.length - shown.length;
|
|
8012
|
+
return `${shown.join(', ')} (+${remainder} more)`;
|
|
8013
|
+
}
|
|
8014
|
+
function isHeaderLine$1(line) {
|
|
8015
|
+
return (line.startsWith('diff --git') ||
|
|
8016
|
+
line.startsWith('index ') ||
|
|
8017
|
+
line.startsWith('--- ') ||
|
|
8018
|
+
line.startsWith('+++ ') ||
|
|
8019
|
+
line.startsWith('@@') ||
|
|
8020
|
+
line.startsWith('new file mode') ||
|
|
8021
|
+
line.startsWith('deleted file mode') ||
|
|
8022
|
+
line.startsWith('similarity index') ||
|
|
8023
|
+
line.startsWith('rename from ') ||
|
|
8024
|
+
line.startsWith('rename to ') ||
|
|
8025
|
+
line.startsWith('Binary files '));
|
|
8026
|
+
}
|
|
8027
|
+
function parseHeading(line) {
|
|
8028
|
+
const match = line.match(/^#{1,6}\s+(.+?)\s*$/);
|
|
8029
|
+
return match ? match[1].trim() : undefined;
|
|
8030
|
+
}
|
|
8031
|
+
|
|
7893
8032
|
/**
|
|
7894
8033
|
* Inspect a unified-diff string and report its shape, or undefined
|
|
7895
8034
|
* if the diff isn't trivial (mixed +/- lines, weird headers, etc.).
|
|
@@ -8027,7 +8166,7 @@ function isCacheEnabled$1() {
|
|
|
8027
8166
|
* synthetic summaries usually drop the directory token totals under
|
|
8028
8167
|
* budget so wave consolidation skips too.
|
|
8029
8168
|
*/
|
|
8030
|
-
async function summarizeFileDiff(fileDiff, { chain, textSplitter, tokenizer, logger, metadata, }) {
|
|
8169
|
+
async function summarizeFileDiff(fileDiff, { chain, textSplitter, tokenizer, logger, metadata, fastPath, }) {
|
|
8031
8170
|
const trivialSummary = summarizeTrivialDiff(fileDiff);
|
|
8032
8171
|
if (trivialSummary !== undefined) {
|
|
8033
8172
|
logger.verbose(` - ${fileDiff.file}: trivial-shape skip (no LLM call)`, { color: 'gray' });
|
|
@@ -8037,6 +8176,25 @@ async function summarizeFileDiff(fileDiff, { chain, textSplitter, tokenizer, log
|
|
|
8037
8176
|
tokenCount: tokenizer(trivialSummary),
|
|
8038
8177
|
};
|
|
8039
8178
|
}
|
|
8179
|
+
// Markdown fast path (#861, angle 5). Opt-in via `fastPath.markdown`
|
|
8180
|
+
// because it's a lossy optimization: the templated summary names
|
|
8181
|
+
// structural changes only and drops body-text detail that an LLM
|
|
8182
|
+
// summary would carry. Off by default; users who prefer summary
|
|
8183
|
+
// fidelity over speed (which is the safer default for commit-message
|
|
8184
|
+
// generation downstream) keep the LLM path. When the flag IS on, the
|
|
8185
|
+
// fast path still falls through to the LLM for paragraph-only edits
|
|
8186
|
+
// where a templated summary would lose useful context.
|
|
8187
|
+
if (fastPath?.markdown) {
|
|
8188
|
+
const markdownSummary = summarizeMarkdownDiff(fileDiff);
|
|
8189
|
+
if (markdownSummary !== undefined) {
|
|
8190
|
+
logger.verbose(` - ${fileDiff.file}: markdown fast-path skip (no LLM call)`, { color: 'gray' });
|
|
8191
|
+
return {
|
|
8192
|
+
...fileDiff,
|
|
8193
|
+
diff: markdownSummary,
|
|
8194
|
+
tokenCount: tokenizer(markdownSummary),
|
|
8195
|
+
};
|
|
8196
|
+
}
|
|
8197
|
+
}
|
|
8040
8198
|
// Cache lookup (#845, PR 5). Keyed on the file's literal diff
|
|
8041
8199
|
// content + the active model + the summarization prompt hash.
|
|
8042
8200
|
// A hit returns the prior summary instantly; on iterative
|
|
@@ -8148,7 +8306,7 @@ function createLimit$2(maxConcurrent) {
|
|
|
8148
8306
|
* @returns Array of file diffs with large files summarized
|
|
8149
8307
|
*/
|
|
8150
8308
|
async function summarizeLargeFiles(diffs, options) {
|
|
8151
|
-
const { maxFileTokens, minTokensForSummary, maxConcurrent, tokenizer, logger, chain, textSplitter, metadata } = options;
|
|
8309
|
+
const { maxFileTokens, minTokensForSummary, maxConcurrent, maxTokens, fastPath, tokenizer, logger, chain, textSplitter, metadata, } = options;
|
|
8152
8310
|
// Identify files that need summarization
|
|
8153
8311
|
const filesToSummarize = [];
|
|
8154
8312
|
const results = [...diffs];
|
|
@@ -8160,17 +8318,57 @@ async function summarizeLargeFiles(diffs, options) {
|
|
|
8160
8318
|
if (filesToSummarize.length === 0) {
|
|
8161
8319
|
return results;
|
|
8162
8320
|
}
|
|
8163
|
-
|
|
8164
|
-
//
|
|
8165
|
-
|
|
8166
|
-
//
|
|
8167
|
-
|
|
8321
|
+
// Incremental termination (#861, PR 1). When the caller supplies a
|
|
8322
|
+
// budget, dispatch biggest-first and re-check the running total per
|
|
8323
|
+
// dispatch — once earlier completions drop the total under maxTokens,
|
|
8324
|
+
// the remaining queued files skip the LLM and keep their raw diffs.
|
|
8325
|
+
// Mirrors the Phase 3 pattern in `summarizeDiffs.ts`. Without a
|
|
8326
|
+
// budget (undefined), behavior matches the prior path: every
|
|
8327
|
+
// eligible file is summarized regardless.
|
|
8328
|
+
filesToSummarize.sort((a, b) => b.diff.tokenCount - a.diff.tokenCount);
|
|
8329
|
+
const incrementalTermination = maxTokens !== undefined;
|
|
8330
|
+
let runningTotal = diffs.reduce((sum, diff) => sum + diff.tokenCount, 0);
|
|
8331
|
+
let summarizedCount = 0;
|
|
8332
|
+
let skippedCount = 0;
|
|
8333
|
+
logger.verbose(`Pre-summarizing up to ${filesToSummarize.length} large file(s)...`, { color: 'blue' });
|
|
8334
|
+
const processed = await processInWaves$1(filesToSummarize, async ({ diff }) => {
|
|
8335
|
+
// Re-check the budget at dispatch time when the caller supplied
|
|
8336
|
+
// one. Earlier completions may have already dropped the total
|
|
8337
|
+
// under the cap; in that case skip the LLM call entirely and
|
|
8338
|
+
// keep the raw diff. Without a budget, every eligible file is
|
|
8339
|
+
// summarized (preserves the prior behavior).
|
|
8340
|
+
if (incrementalTermination && runningTotal <= maxTokens) {
|
|
8341
|
+
return { diff, summarized: false };
|
|
8342
|
+
}
|
|
8343
|
+
const summarized = await summarizeFileDiff(diff, {
|
|
8344
|
+
chain,
|
|
8345
|
+
textSplitter,
|
|
8346
|
+
tokenizer,
|
|
8347
|
+
logger,
|
|
8348
|
+
metadata,
|
|
8349
|
+
fastPath,
|
|
8350
|
+
});
|
|
8351
|
+
const delta = diff.tokenCount - summarized.tokenCount;
|
|
8352
|
+
if (delta > 0) {
|
|
8353
|
+
runningTotal -= delta;
|
|
8354
|
+
}
|
|
8355
|
+
return { diff: summarized, summarized: true };
|
|
8356
|
+
}, maxConcurrent);
|
|
8357
|
+
processed.forEach((entry, i) => {
|
|
8168
8358
|
const originalIndex = filesToSummarize[i].index;
|
|
8359
|
+
if (!entry.summarized) {
|
|
8360
|
+
skippedCount++;
|
|
8361
|
+
return;
|
|
8362
|
+
}
|
|
8363
|
+
summarizedCount++;
|
|
8169
8364
|
const originalTokens = results[originalIndex].tokenCount;
|
|
8170
|
-
const newTokens =
|
|
8171
|
-
logger.verbose(` - ${
|
|
8172
|
-
results[originalIndex] =
|
|
8365
|
+
const newTokens = entry.diff.tokenCount;
|
|
8366
|
+
logger.verbose(` - ${entry.diff.file}: ${originalTokens} -> ${newTokens} tokens`, { color: 'magenta' });
|
|
8367
|
+
results[originalIndex] = entry.diff;
|
|
8173
8368
|
});
|
|
8369
|
+
if (skippedCount > 0) {
|
|
8370
|
+
logger.verbose(`Skipped ${skippedCount} pre-summary call(s) — token budget already met after ${summarizedCount} earlier file(s)`, { color: 'cyan' });
|
|
8371
|
+
}
|
|
8174
8372
|
return results;
|
|
8175
8373
|
}
|
|
8176
8374
|
/**
|
|
@@ -8436,7 +8634,7 @@ async function summarizeDiffs(rootDiffNode, { tokenizer, logger,
|
|
|
8436
8634
|
// with the service defaults means a caller that omits
|
|
8437
8635
|
// `maxTokens` doesn't accidentally fall into a tighter budget
|
|
8438
8636
|
// than the rest of the system assumes.
|
|
8439
|
-
maxTokens = 4096, minTokensForSummary = 400, maxFileTokens, maxConcurrent = 6, textSplitter, chain, metadata, handleOutput = defaultOutputCallback, }) {
|
|
8637
|
+
maxTokens = 4096, minTokensForSummary = 400, maxFileTokens, maxConcurrent = 6, fastPath, textSplitter, chain, metadata, handleOutput = defaultOutputCallback, }) {
|
|
8440
8638
|
// Calculate maxFileTokens as 25% of maxTokens if not specified
|
|
8441
8639
|
const effectiveMaxFileTokens = maxFileTokens ?? Math.floor(maxTokens * 0.25);
|
|
8442
8640
|
// PHASE 1: Directory grouping & assessment
|
|
@@ -8460,6 +8658,13 @@ maxTokens = 4096, minTokensForSummary = 400, maxFileTokens, maxConcurrent = 6, t
|
|
|
8460
8658
|
maxFileTokens: effectiveMaxFileTokens,
|
|
8461
8659
|
minTokensForSummary,
|
|
8462
8660
|
maxConcurrent,
|
|
8661
|
+
// #861, PR 1: pass the overall budget so Phase 2 can short-circuit
|
|
8662
|
+
// once earlier completions drop the running total under the cap.
|
|
8663
|
+
maxTokens,
|
|
8664
|
+
// #861, angle 5: opt-in markdown fast path. Off by default; when
|
|
8665
|
+
// enabled, markdown modification diffs with structural signals
|
|
8666
|
+
// resolve via a templated extract instead of an LLM call.
|
|
8667
|
+
fastPath,
|
|
8463
8668
|
tokenizer,
|
|
8464
8669
|
logger,
|
|
8465
8670
|
chain,
|
|
@@ -11437,7 +11642,7 @@ for (var i = 0; i < 256; i++) {
|
|
|
11437
11642
|
simpleEscapeMap[i] = simpleEscapeSequence(i);
|
|
11438
11643
|
}
|
|
11439
11644
|
|
|
11440
|
-
async function fileChangeParser({ changes, commit, options: { tokenizer, git, llm: model, logger, maxTokens, minTokensForSummary, maxFileTokens, maxConcurrent, metadata, }, }) {
|
|
11645
|
+
async function fileChangeParser({ changes, commit, options: { tokenizer, git, llm: model, logger, maxTokens, minTokensForSummary, maxFileTokens, maxConcurrent, fastPath, metadata, }, }) {
|
|
11441
11646
|
const textSplitter = new RecursiveCharacterTextSplitter({ chunkSize: 10000, chunkOverlap: 250 });
|
|
11442
11647
|
const summarizationChain = loadSummarizationChain(model, {
|
|
11443
11648
|
type: 'map_reduce',
|
|
@@ -11469,6 +11674,7 @@ async function fileChangeParser({ changes, commit, options: { tokenizer, git, ll
|
|
|
11469
11674
|
minTokensForSummary,
|
|
11470
11675
|
maxFileTokens,
|
|
11471
11676
|
maxConcurrent,
|
|
11677
|
+
fastPath,
|
|
11472
11678
|
textSplitter,
|
|
11473
11679
|
chain: summarizationChain,
|
|
11474
11680
|
logger,
|
|
@@ -11488,6 +11694,7 @@ function createFileChangeParserOptions({ command, git, llm, logger, model, provi
|
|
|
11488
11694
|
minTokensForSummary: service?.minTokensForSummary,
|
|
11489
11695
|
maxFileTokens: service?.maxFileTokens,
|
|
11490
11696
|
maxConcurrent: service?.maxConcurrent,
|
|
11697
|
+
fastPath: service?.fastPath,
|
|
11491
11698
|
metadata: {
|
|
11492
11699
|
command,
|
|
11493
11700
|
provider,
|
package/dist/index.js
CHANGED
|
@@ -78,7 +78,7 @@ var readline__namespace = /*#__PURE__*/_interopNamespaceDefault(readline);
|
|
|
78
78
|
/**
|
|
79
79
|
* Current build version from package.json
|
|
80
80
|
*/
|
|
81
|
-
const BUILD_VERSION = "0.
|
|
81
|
+
const BUILD_VERSION = "0.45.0";
|
|
82
82
|
|
|
83
83
|
const isInteractive = (config) => {
|
|
84
84
|
return config?.mode === 'interactive' || !!config?.interactive;
|
|
@@ -1252,6 +1252,18 @@ const schema$1 = {
|
|
|
1252
1252
|
"$ref": "#/definitions/DynamicModelPreference",
|
|
1253
1253
|
"description": "Default dynamic routing preference when model is set to \"dynamic\".",
|
|
1254
1254
|
"default": "balanced"
|
|
1255
|
+
},
|
|
1256
|
+
"fastPath": {
|
|
1257
|
+
"type": "object",
|
|
1258
|
+
"properties": {
|
|
1259
|
+
"markdown": {
|
|
1260
|
+
"type": "boolean",
|
|
1261
|
+
"description": "Replace the LLM summary with a templated heading extract for `.md` / `.mdx` / `.markdown` modification diffs that have clear heading-level structural changes. Diffs without structural signals (paragraph-only edits) still go to the LLM regardless of this flag.\n\nBench impact (synthetic): collapses docs-update-shaped commits from ~24s cold to ~3ms (no LLM calls fire for the markdown files). Real-world wall-clock savings depend on per-call LLM latency.",
|
|
1262
|
+
"default": false
|
|
1263
|
+
}
|
|
1264
|
+
},
|
|
1265
|
+
"additionalProperties": false,
|
|
1266
|
+
"description": "Opt-in fast paths that trade summary detail for speed. Each flag here replaces an LLM summary call with a deterministic templated extract for a specific file shape. Off by default — when enabled, you accept that final commit messages on those file shapes may be blander than LLM-generated summaries (the templated extract names structural changes only).\n\nLossless optimizations (cache, trivial-shape skip on pure additions / deletions / renames / binary, sort discipline) ship default-on and are not configured here."
|
|
1255
1267
|
}
|
|
1256
1268
|
},
|
|
1257
1269
|
"required": [
|
|
@@ -1665,6 +1677,18 @@ const schema$1 = {
|
|
|
1665
1677
|
"$ref": "#/definitions/DynamicModelPreference",
|
|
1666
1678
|
"description": "Default dynamic routing preference when model is set to \"dynamic\".",
|
|
1667
1679
|
"default": "balanced"
|
|
1680
|
+
},
|
|
1681
|
+
"fastPath": {
|
|
1682
|
+
"type": "object",
|
|
1683
|
+
"properties": {
|
|
1684
|
+
"markdown": {
|
|
1685
|
+
"type": "boolean",
|
|
1686
|
+
"description": "Replace the LLM summary with a templated heading extract for `.md` / `.mdx` / `.markdown` modification diffs that have clear heading-level structural changes. Diffs without structural signals (paragraph-only edits) still go to the LLM regardless of this flag.\n\nBench impact (synthetic): collapses docs-update-shaped commits from ~24s cold to ~3ms (no LLM calls fire for the markdown files). Real-world wall-clock savings depend on per-call LLM latency.",
|
|
1687
|
+
"default": false
|
|
1688
|
+
}
|
|
1689
|
+
},
|
|
1690
|
+
"additionalProperties": false,
|
|
1691
|
+
"description": "Opt-in fast paths that trade summary detail for speed. Each flag here replaces an LLM summary call with a deterministic templated extract for a specific file shape. Off by default — when enabled, you accept that final commit messages on those file shapes may be blander than LLM-generated summaries (the templated extract names structural changes only).\n\nLossless optimizations (cache, trivial-shape skip on pure additions / deletions / renames / binary, sort discipline) ship default-on and are not configured here."
|
|
1668
1692
|
}
|
|
1669
1693
|
},
|
|
1670
1694
|
"required": [
|
|
@@ -1821,6 +1845,18 @@ const schema$1 = {
|
|
|
1821
1845
|
"$ref": "#/definitions/DynamicModelPreference",
|
|
1822
1846
|
"description": "Default dynamic routing preference when model is set to \"dynamic\".",
|
|
1823
1847
|
"default": "balanced"
|
|
1848
|
+
},
|
|
1849
|
+
"fastPath": {
|
|
1850
|
+
"type": "object",
|
|
1851
|
+
"properties": {
|
|
1852
|
+
"markdown": {
|
|
1853
|
+
"type": "boolean",
|
|
1854
|
+
"description": "Replace the LLM summary with a templated heading extract for `.md` / `.mdx` / `.markdown` modification diffs that have clear heading-level structural changes. Diffs without structural signals (paragraph-only edits) still go to the LLM regardless of this flag.\n\nBench impact (synthetic): collapses docs-update-shaped commits from ~24s cold to ~3ms (no LLM calls fire for the markdown files). Real-world wall-clock savings depend on per-call LLM latency.",
|
|
1855
|
+
"default": false
|
|
1856
|
+
}
|
|
1857
|
+
},
|
|
1858
|
+
"additionalProperties": false,
|
|
1859
|
+
"description": "Opt-in fast paths that trade summary detail for speed. Each flag here replaces an LLM summary call with a deterministic templated extract for a specific file shape. Off by default — when enabled, you accept that final commit messages on those file shapes may be blander than LLM-generated summaries (the templated extract names structural changes only).\n\nLossless optimizations (cache, trivial-shape skip on pure additions / deletions / renames / binary, sort discipline) ship default-on and are not configured here."
|
|
1824
1860
|
}
|
|
1825
1861
|
},
|
|
1826
1862
|
"required": [
|
|
@@ -7914,6 +7950,109 @@ async function summarize(documents$1, { chain, textSplitter, options, logger, to
|
|
|
7914
7950
|
return res.text && res.text.trim();
|
|
7915
7951
|
}
|
|
7916
7952
|
|
|
7953
|
+
/**
|
|
7954
|
+
* Markdown-aware fast path (#861, angle 5). For modification diffs to
|
|
7955
|
+
* `.md` / `.mdx` / `.markdown` files, build a templated summary from
|
|
7956
|
+
* the changed structure (added / removed / updated headings) instead
|
|
7957
|
+
* of paying for an LLM call. Mirrors `trivialDiff` from #845: a deterministic
|
|
7958
|
+
* skip when the diff's meaning is captured by its shape.
|
|
7959
|
+
*
|
|
7960
|
+
* Quality / cost trade-off, on purpose: LLM summaries of markdown edits
|
|
7961
|
+
* are wordier ("expanded the configuration section with new examples,
|
|
7962
|
+
* fixed typos in troubleshooting") but most of that detail isn't load-
|
|
7963
|
+
* bearing for a commit message. The templated summary names the
|
|
7964
|
+
* structural changes (which sections moved) plus a +/- line count, and
|
|
7965
|
+
* defers to the LLM only when the diff has no clear structural signals
|
|
7966
|
+
* (paragraph-only edits, where a templated summary would actually drop
|
|
7967
|
+
* useful context).
|
|
7968
|
+
*/
|
|
7969
|
+
const MARKDOWN_EXTENSIONS = ['.md', '.markdown', '.mdx'];
|
|
7970
|
+
const MAX_HEADINGS_PER_BUCKET = 6;
|
|
7971
|
+
function isMarkdownFile(path) {
|
|
7972
|
+
const lower = path.toLowerCase();
|
|
7973
|
+
return MARKDOWN_EXTENSIONS.some((ext) => lower.endsWith(ext));
|
|
7974
|
+
}
|
|
7975
|
+
function summarizeMarkdownDiff(fileDiff) {
|
|
7976
|
+
if (!isMarkdownFile(fileDiff.file))
|
|
7977
|
+
return undefined;
|
|
7978
|
+
const addedHeadings = new Set();
|
|
7979
|
+
const removedHeadings = new Set();
|
|
7980
|
+
let addedLines = 0;
|
|
7981
|
+
let removedLines = 0;
|
|
7982
|
+
for (const line of fileDiff.diff.split('\n')) {
|
|
7983
|
+
if (isHeaderLine$1(line))
|
|
7984
|
+
continue;
|
|
7985
|
+
if (line.startsWith('+')) {
|
|
7986
|
+
addedLines++;
|
|
7987
|
+
const heading = parseHeading(line.slice(1));
|
|
7988
|
+
if (heading)
|
|
7989
|
+
addedHeadings.add(heading);
|
|
7990
|
+
}
|
|
7991
|
+
else if (line.startsWith('-')) {
|
|
7992
|
+
removedLines++;
|
|
7993
|
+
const heading = parseHeading(line.slice(1));
|
|
7994
|
+
if (heading)
|
|
7995
|
+
removedHeadings.add(heading);
|
|
7996
|
+
}
|
|
7997
|
+
}
|
|
7998
|
+
// No content change → nothing to summarize. Caller falls through.
|
|
7999
|
+
if (addedLines === 0 && removedLines === 0)
|
|
8000
|
+
return undefined;
|
|
8001
|
+
// No structural signal → fall through to LLM. We only fast-path
|
|
8002
|
+
// when the diff has heading-level changes; pure paragraph edits go
|
|
8003
|
+
// to the LLM so the summary keeps its detail.
|
|
8004
|
+
if (addedHeadings.size === 0 && removedHeadings.size === 0) {
|
|
8005
|
+
return undefined;
|
|
8006
|
+
}
|
|
8007
|
+
// A heading that appears in both buckets is likely an update (kept
|
|
8008
|
+
// around but its body changed) rather than two distinct events.
|
|
8009
|
+
// The naive split-by-bucket diff format used by git emits the old
|
|
8010
|
+
// text under `-` and the new text under `+`; an unchanged heading
|
|
8011
|
+
// line shouldn't show up in either bucket via the standard hunk
|
|
8012
|
+
// path, but defensively de-dupe in case the diff producer emits
|
|
8013
|
+
// surrounding context as +/-.
|
|
8014
|
+
const updated = new Set([...addedHeadings].filter((h) => removedHeadings.has(h)));
|
|
8015
|
+
const purelyAdded = [...addedHeadings].filter((h) => !updated.has(h));
|
|
8016
|
+
const purelyRemoved = [...removedHeadings].filter((h) => !updated.has(h));
|
|
8017
|
+
const parts = [`Updated markdown \`${fileDiff.file}\``];
|
|
8018
|
+
if (purelyAdded.length) {
|
|
8019
|
+
parts.push(`new sections: ${formatHeadingList(purelyAdded)}`);
|
|
8020
|
+
}
|
|
8021
|
+
if (purelyRemoved.length) {
|
|
8022
|
+
parts.push(`removed sections: ${formatHeadingList(purelyRemoved)}`);
|
|
8023
|
+
}
|
|
8024
|
+
if (updated.size) {
|
|
8025
|
+
parts.push(`updated sections: ${formatHeadingList([...updated])}`);
|
|
8026
|
+
}
|
|
8027
|
+
parts.push(`+${addedLines}/-${removedLines} lines`);
|
|
8028
|
+
return `${parts.join('. ')}.`;
|
|
8029
|
+
}
|
|
8030
|
+
function formatHeadingList(headings) {
|
|
8031
|
+
if (headings.length <= MAX_HEADINGS_PER_BUCKET) {
|
|
8032
|
+
return headings.join(', ');
|
|
8033
|
+
}
|
|
8034
|
+
const shown = headings.slice(0, MAX_HEADINGS_PER_BUCKET);
|
|
8035
|
+
const remainder = headings.length - shown.length;
|
|
8036
|
+
return `${shown.join(', ')} (+${remainder} more)`;
|
|
8037
|
+
}
|
|
8038
|
+
function isHeaderLine$1(line) {
|
|
8039
|
+
return (line.startsWith('diff --git') ||
|
|
8040
|
+
line.startsWith('index ') ||
|
|
8041
|
+
line.startsWith('--- ') ||
|
|
8042
|
+
line.startsWith('+++ ') ||
|
|
8043
|
+
line.startsWith('@@') ||
|
|
8044
|
+
line.startsWith('new file mode') ||
|
|
8045
|
+
line.startsWith('deleted file mode') ||
|
|
8046
|
+
line.startsWith('similarity index') ||
|
|
8047
|
+
line.startsWith('rename from ') ||
|
|
8048
|
+
line.startsWith('rename to ') ||
|
|
8049
|
+
line.startsWith('Binary files '));
|
|
8050
|
+
}
|
|
8051
|
+
function parseHeading(line) {
|
|
8052
|
+
const match = line.match(/^#{1,6}\s+(.+?)\s*$/);
|
|
8053
|
+
return match ? match[1].trim() : undefined;
|
|
8054
|
+
}
|
|
8055
|
+
|
|
7917
8056
|
/**
|
|
7918
8057
|
* Inspect a unified-diff string and report its shape, or undefined
|
|
7919
8058
|
* if the diff isn't trivial (mixed +/- lines, weird headers, etc.).
|
|
@@ -8051,7 +8190,7 @@ function isCacheEnabled$1() {
|
|
|
8051
8190
|
* synthetic summaries usually drop the directory token totals under
|
|
8052
8191
|
* budget so wave consolidation skips too.
|
|
8053
8192
|
*/
|
|
8054
|
-
async function summarizeFileDiff(fileDiff, { chain, textSplitter, tokenizer, logger, metadata, }) {
|
|
8193
|
+
async function summarizeFileDiff(fileDiff, { chain, textSplitter, tokenizer, logger, metadata, fastPath, }) {
|
|
8055
8194
|
const trivialSummary = summarizeTrivialDiff(fileDiff);
|
|
8056
8195
|
if (trivialSummary !== undefined) {
|
|
8057
8196
|
logger.verbose(` - ${fileDiff.file}: trivial-shape skip (no LLM call)`, { color: 'gray' });
|
|
@@ -8061,6 +8200,25 @@ async function summarizeFileDiff(fileDiff, { chain, textSplitter, tokenizer, log
|
|
|
8061
8200
|
tokenCount: tokenizer(trivialSummary),
|
|
8062
8201
|
};
|
|
8063
8202
|
}
|
|
8203
|
+
// Markdown fast path (#861, angle 5). Opt-in via `fastPath.markdown`
|
|
8204
|
+
// because it's a lossy optimization: the templated summary names
|
|
8205
|
+
// structural changes only and drops body-text detail that an LLM
|
|
8206
|
+
// summary would carry. Off by default; users who prefer summary
|
|
8207
|
+
// fidelity over speed (which is the safer default for commit-message
|
|
8208
|
+
// generation downstream) keep the LLM path. When the flag IS on, the
|
|
8209
|
+
// fast path still falls through to the LLM for paragraph-only edits
|
|
8210
|
+
// where a templated summary would lose useful context.
|
|
8211
|
+
if (fastPath?.markdown) {
|
|
8212
|
+
const markdownSummary = summarizeMarkdownDiff(fileDiff);
|
|
8213
|
+
if (markdownSummary !== undefined) {
|
|
8214
|
+
logger.verbose(` - ${fileDiff.file}: markdown fast-path skip (no LLM call)`, { color: 'gray' });
|
|
8215
|
+
return {
|
|
8216
|
+
...fileDiff,
|
|
8217
|
+
diff: markdownSummary,
|
|
8218
|
+
tokenCount: tokenizer(markdownSummary),
|
|
8219
|
+
};
|
|
8220
|
+
}
|
|
8221
|
+
}
|
|
8064
8222
|
// Cache lookup (#845, PR 5). Keyed on the file's literal diff
|
|
8065
8223
|
// content + the active model + the summarization prompt hash.
|
|
8066
8224
|
// A hit returns the prior summary instantly; on iterative
|
|
@@ -8172,7 +8330,7 @@ function createLimit$2(maxConcurrent) {
|
|
|
8172
8330
|
* @returns Array of file diffs with large files summarized
|
|
8173
8331
|
*/
|
|
8174
8332
|
async function summarizeLargeFiles(diffs, options) {
|
|
8175
|
-
const { maxFileTokens, minTokensForSummary, maxConcurrent, tokenizer, logger, chain, textSplitter, metadata } = options;
|
|
8333
|
+
const { maxFileTokens, minTokensForSummary, maxConcurrent, maxTokens, fastPath, tokenizer, logger, chain, textSplitter, metadata, } = options;
|
|
8176
8334
|
// Identify files that need summarization
|
|
8177
8335
|
const filesToSummarize = [];
|
|
8178
8336
|
const results = [...diffs];
|
|
@@ -8184,17 +8342,57 @@ async function summarizeLargeFiles(diffs, options) {
|
|
|
8184
8342
|
if (filesToSummarize.length === 0) {
|
|
8185
8343
|
return results;
|
|
8186
8344
|
}
|
|
8187
|
-
|
|
8188
|
-
//
|
|
8189
|
-
|
|
8190
|
-
//
|
|
8191
|
-
|
|
8345
|
+
// Incremental termination (#861, PR 1). When the caller supplies a
|
|
8346
|
+
// budget, dispatch biggest-first and re-check the running total per
|
|
8347
|
+
// dispatch — once earlier completions drop the total under maxTokens,
|
|
8348
|
+
// the remaining queued files skip the LLM and keep their raw diffs.
|
|
8349
|
+
// Mirrors the Phase 3 pattern in `summarizeDiffs.ts`. Without a
|
|
8350
|
+
// budget (undefined), behavior matches the prior path: every
|
|
8351
|
+
// eligible file is summarized regardless.
|
|
8352
|
+
filesToSummarize.sort((a, b) => b.diff.tokenCount - a.diff.tokenCount);
|
|
8353
|
+
const incrementalTermination = maxTokens !== undefined;
|
|
8354
|
+
let runningTotal = diffs.reduce((sum, diff) => sum + diff.tokenCount, 0);
|
|
8355
|
+
let summarizedCount = 0;
|
|
8356
|
+
let skippedCount = 0;
|
|
8357
|
+
logger.verbose(`Pre-summarizing up to ${filesToSummarize.length} large file(s)...`, { color: 'blue' });
|
|
8358
|
+
const processed = await processInWaves$1(filesToSummarize, async ({ diff }) => {
|
|
8359
|
+
// Re-check the budget at dispatch time when the caller supplied
|
|
8360
|
+
// one. Earlier completions may have already dropped the total
|
|
8361
|
+
// under the cap; in that case skip the LLM call entirely and
|
|
8362
|
+
// keep the raw diff. Without a budget, every eligible file is
|
|
8363
|
+
// summarized (preserves the prior behavior).
|
|
8364
|
+
if (incrementalTermination && runningTotal <= maxTokens) {
|
|
8365
|
+
return { diff, summarized: false };
|
|
8366
|
+
}
|
|
8367
|
+
const summarized = await summarizeFileDiff(diff, {
|
|
8368
|
+
chain,
|
|
8369
|
+
textSplitter,
|
|
8370
|
+
tokenizer,
|
|
8371
|
+
logger,
|
|
8372
|
+
metadata,
|
|
8373
|
+
fastPath,
|
|
8374
|
+
});
|
|
8375
|
+
const delta = diff.tokenCount - summarized.tokenCount;
|
|
8376
|
+
if (delta > 0) {
|
|
8377
|
+
runningTotal -= delta;
|
|
8378
|
+
}
|
|
8379
|
+
return { diff: summarized, summarized: true };
|
|
8380
|
+
}, maxConcurrent);
|
|
8381
|
+
processed.forEach((entry, i) => {
|
|
8192
8382
|
const originalIndex = filesToSummarize[i].index;
|
|
8383
|
+
if (!entry.summarized) {
|
|
8384
|
+
skippedCount++;
|
|
8385
|
+
return;
|
|
8386
|
+
}
|
|
8387
|
+
summarizedCount++;
|
|
8193
8388
|
const originalTokens = results[originalIndex].tokenCount;
|
|
8194
|
-
const newTokens =
|
|
8195
|
-
logger.verbose(` - ${
|
|
8196
|
-
results[originalIndex] =
|
|
8389
|
+
const newTokens = entry.diff.tokenCount;
|
|
8390
|
+
logger.verbose(` - ${entry.diff.file}: ${originalTokens} -> ${newTokens} tokens`, { color: 'magenta' });
|
|
8391
|
+
results[originalIndex] = entry.diff;
|
|
8197
8392
|
});
|
|
8393
|
+
if (skippedCount > 0) {
|
|
8394
|
+
logger.verbose(`Skipped ${skippedCount} pre-summary call(s) — token budget already met after ${summarizedCount} earlier file(s)`, { color: 'cyan' });
|
|
8395
|
+
}
|
|
8198
8396
|
return results;
|
|
8199
8397
|
}
|
|
8200
8398
|
/**
|
|
@@ -8460,7 +8658,7 @@ async function summarizeDiffs(rootDiffNode, { tokenizer, logger,
|
|
|
8460
8658
|
// with the service defaults means a caller that omits
|
|
8461
8659
|
// `maxTokens` doesn't accidentally fall into a tighter budget
|
|
8462
8660
|
// than the rest of the system assumes.
|
|
8463
|
-
maxTokens = 4096, minTokensForSummary = 400, maxFileTokens, maxConcurrent = 6, textSplitter, chain, metadata, handleOutput = defaultOutputCallback, }) {
|
|
8661
|
+
maxTokens = 4096, minTokensForSummary = 400, maxFileTokens, maxConcurrent = 6, fastPath, textSplitter, chain, metadata, handleOutput = defaultOutputCallback, }) {
|
|
8464
8662
|
// Calculate maxFileTokens as 25% of maxTokens if not specified
|
|
8465
8663
|
const effectiveMaxFileTokens = maxFileTokens ?? Math.floor(maxTokens * 0.25);
|
|
8466
8664
|
// PHASE 1: Directory grouping & assessment
|
|
@@ -8484,6 +8682,13 @@ maxTokens = 4096, minTokensForSummary = 400, maxFileTokens, maxConcurrent = 6, t
|
|
|
8484
8682
|
maxFileTokens: effectiveMaxFileTokens,
|
|
8485
8683
|
minTokensForSummary,
|
|
8486
8684
|
maxConcurrent,
|
|
8685
|
+
// #861, PR 1: pass the overall budget so Phase 2 can short-circuit
|
|
8686
|
+
// once earlier completions drop the running total under the cap.
|
|
8687
|
+
maxTokens,
|
|
8688
|
+
// #861, angle 5: opt-in markdown fast path. Off by default; when
|
|
8689
|
+
// enabled, markdown modification diffs with structural signals
|
|
8690
|
+
// resolve via a templated extract instead of an LLM call.
|
|
8691
|
+
fastPath,
|
|
8487
8692
|
tokenizer,
|
|
8488
8693
|
logger,
|
|
8489
8694
|
chain,
|
|
@@ -11461,7 +11666,7 @@ for (var i = 0; i < 256; i++) {
|
|
|
11461
11666
|
simpleEscapeMap[i] = simpleEscapeSequence(i);
|
|
11462
11667
|
}
|
|
11463
11668
|
|
|
11464
|
-
async function fileChangeParser({ changes, commit, options: { tokenizer, git, llm: model, logger, maxTokens, minTokensForSummary, maxFileTokens, maxConcurrent, metadata, }, }) {
|
|
11669
|
+
async function fileChangeParser({ changes, commit, options: { tokenizer, git, llm: model, logger, maxTokens, minTokensForSummary, maxFileTokens, maxConcurrent, fastPath, metadata, }, }) {
|
|
11465
11670
|
const textSplitter = new RecursiveCharacterTextSplitter({ chunkSize: 10000, chunkOverlap: 250 });
|
|
11466
11671
|
const summarizationChain = loadSummarizationChain(model, {
|
|
11467
11672
|
type: 'map_reduce',
|
|
@@ -11493,6 +11698,7 @@ async function fileChangeParser({ changes, commit, options: { tokenizer, git, ll
|
|
|
11493
11698
|
minTokensForSummary,
|
|
11494
11699
|
maxFileTokens,
|
|
11495
11700
|
maxConcurrent,
|
|
11701
|
+
fastPath,
|
|
11496
11702
|
textSplitter,
|
|
11497
11703
|
chain: summarizationChain,
|
|
11498
11704
|
logger,
|
|
@@ -11512,6 +11718,7 @@ function createFileChangeParserOptions({ command, git, llm, logger, model, provi
|
|
|
11512
11718
|
minTokensForSummary: service?.minTokensForSummary,
|
|
11513
11719
|
maxFileTokens: service?.maxFileTokens,
|
|
11514
11720
|
maxConcurrent: service?.maxConcurrent,
|
|
11721
|
+
fastPath: service?.fastPath,
|
|
11515
11722
|
metadata: {
|
|
11516
11723
|
command,
|
|
11517
11724
|
provider,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "git-coco",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.45.0",
|
|
4
4
|
"description": "zero-effort git commits with coco.",
|
|
5
5
|
"author": "gfargo <ghfargo@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
"ts-json-schema-generator": "^2.9.0",
|
|
86
86
|
"ts-node": "^10.9.1",
|
|
87
87
|
"tsx": "^4.16.5",
|
|
88
|
-
"typescript": "^
|
|
88
|
+
"typescript": "^6.0.3"
|
|
89
89
|
},
|
|
90
90
|
"dependencies": {
|
|
91
91
|
"@commitlint/core": "^20.5.0",
|