@quikcommit/cli 11.1.0 → 12.0.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.js +154 -76
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -459,6 +459,13 @@ var init_api = __esm({
|
|
|
459
459
|
async generateBranchName(req) {
|
|
460
460
|
return this.request("/v1/branch", req);
|
|
461
461
|
}
|
|
462
|
+
async summarizeChunk(diff, changes, model) {
|
|
463
|
+
const data = await this.request(
|
|
464
|
+
"/v1/summarize",
|
|
465
|
+
{ diff, changes, ...model ? { model } : {} }
|
|
466
|
+
);
|
|
467
|
+
return data.summary ?? "";
|
|
468
|
+
}
|
|
462
469
|
async fetchJson(endpoint, options) {
|
|
463
470
|
if (!this.apiKey) {
|
|
464
471
|
throw new Error("Not authenticated. Run `qc login` first.");
|
|
@@ -7933,7 +7940,7 @@ function getStagedDiff(excludes = []) {
|
|
|
7933
7940
|
}
|
|
7934
7941
|
return (0, import_child_process2.execFileSync)("git", args, {
|
|
7935
7942
|
encoding: "utf-8",
|
|
7936
|
-
maxBuffer:
|
|
7943
|
+
maxBuffer: 50 * 1024 * 1024
|
|
7937
7944
|
});
|
|
7938
7945
|
}
|
|
7939
7946
|
function getStagedFiles() {
|
|
@@ -8794,7 +8801,8 @@ var smart_diff_exports = {};
|
|
|
8794
8801
|
__export(smart_diff_exports, {
|
|
8795
8802
|
classifyFile: () => classifyFile,
|
|
8796
8803
|
preprocessDiff: () => preprocessDiff,
|
|
8797
|
-
preprocessDiffWithSizeBudget: () => preprocessDiffWithSizeBudget
|
|
8804
|
+
preprocessDiffWithSizeBudget: () => preprocessDiffWithSizeBudget,
|
|
8805
|
+
splitDiffIntoChunks: () => splitDiffIntoChunks
|
|
8798
8806
|
});
|
|
8799
8807
|
function sanitizeFilepath(path) {
|
|
8800
8808
|
return path.replace(/[\x00-\x1F\x7F[\]`]/g, "_").slice(0, 200);
|
|
@@ -8835,7 +8843,7 @@ function isMinified(content) {
|
|
|
8835
8843
|
}
|
|
8836
8844
|
function preprocessDiff(diff) {
|
|
8837
8845
|
const files = parseDiffIntoFiles(diff);
|
|
8838
|
-
if (files.length === 0) return { processedDiff: diff, summarized: [],
|
|
8846
|
+
if (files.length === 0) return { processedDiff: diff, summarized: [], tokensSaved: 0, needsChunking: false };
|
|
8839
8847
|
const kept = [];
|
|
8840
8848
|
const summarized = [];
|
|
8841
8849
|
let tokensSaved = 0;
|
|
@@ -8880,19 +8888,50 @@ function preprocessDiff(diff) {
|
|
|
8880
8888
|
return {
|
|
8881
8889
|
processedDiff: kept.join(""),
|
|
8882
8890
|
summarized,
|
|
8883
|
-
|
|
8884
|
-
|
|
8891
|
+
tokensSaved,
|
|
8892
|
+
needsChunking: false
|
|
8885
8893
|
};
|
|
8886
8894
|
}
|
|
8887
|
-
function
|
|
8888
|
-
const
|
|
8889
|
-
|
|
8890
|
-
|
|
8895
|
+
function stripContext(fileContent, contextLines) {
|
|
8896
|
+
const lines = fileContent.split("\n");
|
|
8897
|
+
const result = [];
|
|
8898
|
+
let inHeader = true;
|
|
8899
|
+
const pendingContext = [];
|
|
8900
|
+
let afterChange = 0;
|
|
8901
|
+
for (const line of lines) {
|
|
8902
|
+
if (inHeader) {
|
|
8903
|
+
result.push(line);
|
|
8904
|
+
if (line.startsWith("@@")) inHeader = false;
|
|
8905
|
+
continue;
|
|
8906
|
+
}
|
|
8907
|
+
if (line.startsWith("@@")) {
|
|
8908
|
+
pendingContext.length = 0;
|
|
8909
|
+
afterChange = 0;
|
|
8910
|
+
result.push(line);
|
|
8911
|
+
continue;
|
|
8912
|
+
}
|
|
8913
|
+
if (line.startsWith("+") || line.startsWith("-")) {
|
|
8914
|
+
if (contextLines > 0) {
|
|
8915
|
+
result.push(...pendingContext.slice(-contextLines));
|
|
8916
|
+
}
|
|
8917
|
+
pendingContext.length = 0;
|
|
8918
|
+
result.push(line);
|
|
8919
|
+
afterChange = contextLines;
|
|
8920
|
+
continue;
|
|
8921
|
+
}
|
|
8922
|
+
if (afterChange > 0) {
|
|
8923
|
+
result.push(line);
|
|
8924
|
+
afterChange--;
|
|
8925
|
+
} else {
|
|
8926
|
+
pendingContext.push(line);
|
|
8927
|
+
}
|
|
8928
|
+
}
|
|
8929
|
+
return result.join("\n");
|
|
8891
8930
|
}
|
|
8892
|
-
function preprocessDiffWithSizeBudget(diff, maxBytes =
|
|
8931
|
+
function preprocessDiffWithSizeBudget(diff, maxBytes = 750 * 1024) {
|
|
8893
8932
|
const files = parseDiffIntoFiles(diff);
|
|
8894
8933
|
if (files.length === 0) {
|
|
8895
|
-
return { processedDiff: diff, summarized: [],
|
|
8934
|
+
return { processedDiff: diff, summarized: [], tokensSaved: 0, needsChunking: false };
|
|
8896
8935
|
}
|
|
8897
8936
|
const entries = [];
|
|
8898
8937
|
const summarized = [];
|
|
@@ -8903,7 +8942,7 @@ function preprocessDiffWithSizeBudget(diff, maxBytes = 5 * 1024 * 1024) {
|
|
|
8903
8942
|
case "sourcemap":
|
|
8904
8943
|
tokensSaved += estimateTokens(file.content);
|
|
8905
8944
|
summarized.push(file.filepath);
|
|
8906
|
-
entries.push({ file, isNoise: true, summaryLine: null });
|
|
8945
|
+
entries.push({ file, isNoise: true, summaryLine: null, strippedContent: null });
|
|
8907
8946
|
break;
|
|
8908
8947
|
case "lock":
|
|
8909
8948
|
tokensSaved += estimateTokens(file.content);
|
|
@@ -8911,6 +8950,7 @@ function preprocessDiffWithSizeBudget(diff, maxBytes = 5 * 1024 * 1024) {
|
|
|
8911
8950
|
entries.push({
|
|
8912
8951
|
file,
|
|
8913
8952
|
isNoise: true,
|
|
8953
|
+
strippedContent: null,
|
|
8914
8954
|
summaryLine: `[lock file updated: ${sanitizeFilepath(file.filepath)} (+${file.additions} \u2212${file.deletions} lines)]
|
|
8915
8955
|
`
|
|
8916
8956
|
});
|
|
@@ -8921,6 +8961,7 @@ function preprocessDiffWithSizeBudget(diff, maxBytes = 5 * 1024 * 1024) {
|
|
|
8921
8961
|
entries.push({
|
|
8922
8962
|
file,
|
|
8923
8963
|
isNoise: true,
|
|
8964
|
+
strippedContent: null,
|
|
8924
8965
|
summaryLine: `[generated: ${sanitizeFilepath(file.filepath)} (+${file.additions} \u2212${file.deletions})]
|
|
8925
8966
|
`
|
|
8926
8967
|
});
|
|
@@ -8931,6 +8972,7 @@ function preprocessDiffWithSizeBudget(diff, maxBytes = 5 * 1024 * 1024) {
|
|
|
8931
8972
|
entries.push({
|
|
8932
8973
|
file,
|
|
8933
8974
|
isNoise: true,
|
|
8975
|
+
strippedContent: null,
|
|
8934
8976
|
summaryLine: `[vendored: ${sanitizeFilepath(file.filepath)} updated]
|
|
8935
8977
|
`
|
|
8936
8978
|
});
|
|
@@ -8943,83 +8985,75 @@ function preprocessDiffWithSizeBudget(diff, maxBytes = 5 * 1024 * 1024) {
|
|
|
8943
8985
|
entries.push({
|
|
8944
8986
|
file,
|
|
8945
8987
|
isNoise: true,
|
|
8988
|
+
strippedContent: null,
|
|
8946
8989
|
summaryLine: `[minified asset: ${sanitizeFilepath(file.filepath)} (${sizeKB} KB)]
|
|
8947
8990
|
`
|
|
8948
8991
|
});
|
|
8949
8992
|
} else {
|
|
8950
|
-
entries.push({ file, isNoise: false, summaryLine: null });
|
|
8993
|
+
entries.push({ file, isNoise: false, summaryLine: null, strippedContent: null });
|
|
8951
8994
|
}
|
|
8952
8995
|
break;
|
|
8953
8996
|
}
|
|
8954
8997
|
}
|
|
8955
|
-
const
|
|
8998
|
+
const codeEntries = entries.filter((e) => !e.isNoise);
|
|
8956
8999
|
function buildOutput() {
|
|
8957
9000
|
const parts = [];
|
|
8958
9001
|
for (const entry of entries) {
|
|
8959
9002
|
if (entry.isNoise) {
|
|
8960
9003
|
if (entry.summaryLine !== null) parts.push(entry.summaryLine);
|
|
8961
|
-
} else if (aggressiveMap.has(entry.file.filepath)) {
|
|
8962
|
-
parts.push(aggressiveMap.get(entry.file.filepath));
|
|
8963
9004
|
} else {
|
|
8964
|
-
parts.push(entry.file.content);
|
|
9005
|
+
parts.push(entry.strippedContent ?? entry.file.content);
|
|
8965
9006
|
}
|
|
8966
9007
|
}
|
|
8967
9008
|
return parts.join("");
|
|
8968
9009
|
}
|
|
8969
|
-
const codeEntries = entries.filter((e) => !e.isNoise);
|
|
8970
9010
|
let output = buildOutput();
|
|
8971
9011
|
if (output.length <= maxBytes) {
|
|
8972
|
-
return {
|
|
8973
|
-
processedDiff: output,
|
|
8974
|
-
summarized,
|
|
8975
|
-
aggressivelySummarized: [],
|
|
8976
|
-
tokensSaved
|
|
8977
|
-
};
|
|
9012
|
+
return { processedDiff: output, summarized, tokensSaved, needsChunking: false };
|
|
8978
9013
|
}
|
|
8979
|
-
const TIER1_THRESHOLD = 5 * 1024;
|
|
8980
9014
|
for (const entry of codeEntries) {
|
|
8981
|
-
|
|
8982
|
-
|
|
8983
|
-
|
|
8984
|
-
}
|
|
9015
|
+
const stripped = stripContext(entry.file.content, 1);
|
|
9016
|
+
tokensSaved += estimateTokens(entry.file.content) - estimateTokens(stripped);
|
|
9017
|
+
entry.strippedContent = stripped;
|
|
8985
9018
|
}
|
|
8986
9019
|
output = buildOutput();
|
|
8987
9020
|
if (output.length <= maxBytes) {
|
|
8988
|
-
return {
|
|
8989
|
-
processedDiff: output,
|
|
8990
|
-
summarized,
|
|
8991
|
-
aggressivelySummarized: [...aggressiveMap.keys()],
|
|
8992
|
-
tokensSaved
|
|
8993
|
-
};
|
|
9021
|
+
return { processedDiff: output, summarized, tokensSaved, needsChunking: false };
|
|
8994
9022
|
}
|
|
8995
|
-
const TIER2_THRESHOLD = 2 * 1024;
|
|
8996
9023
|
for (const entry of codeEntries) {
|
|
8997
|
-
|
|
8998
|
-
|
|
8999
|
-
|
|
9000
|
-
}
|
|
9024
|
+
const stripped = stripContext(entry.file.content, 0);
|
|
9025
|
+
tokensSaved += estimateTokens(entry.strippedContent ?? entry.file.content) - estimateTokens(stripped);
|
|
9026
|
+
entry.strippedContent = stripped;
|
|
9001
9027
|
}
|
|
9002
9028
|
output = buildOutput();
|
|
9003
9029
|
if (output.length <= maxBytes) {
|
|
9004
|
-
return {
|
|
9005
|
-
processedDiff: output,
|
|
9006
|
-
summarized,
|
|
9007
|
-
aggressivelySummarized: [...aggressiveMap.keys()],
|
|
9008
|
-
tokensSaved
|
|
9009
|
-
};
|
|
9030
|
+
return { processedDiff: output, summarized, tokensSaved, needsChunking: false };
|
|
9010
9031
|
}
|
|
9011
|
-
|
|
9012
|
-
|
|
9013
|
-
|
|
9014
|
-
|
|
9032
|
+
return { processedDiff: output, summarized, tokensSaved, needsChunking: true };
|
|
9033
|
+
}
|
|
9034
|
+
function splitDiffIntoChunks(diff, maxChunkBytes = 600 * 1024) {
|
|
9035
|
+
const files = parseDiffIntoFiles(diff);
|
|
9036
|
+
if (files.length === 0) return [];
|
|
9037
|
+
const chunks = [];
|
|
9038
|
+
let currentDiff = "";
|
|
9039
|
+
let currentFiles = [];
|
|
9040
|
+
for (const file of files) {
|
|
9041
|
+
let content = file.content;
|
|
9042
|
+
if (content.length > maxChunkBytes) {
|
|
9043
|
+
content = stripContext(content, 0);
|
|
9044
|
+
}
|
|
9045
|
+
if (currentDiff.length > 0 && currentDiff.length + content.length > maxChunkBytes) {
|
|
9046
|
+
chunks.push({ diff: currentDiff, files: currentFiles });
|
|
9047
|
+
currentDiff = "";
|
|
9048
|
+
currentFiles = [];
|
|
9015
9049
|
}
|
|
9050
|
+
currentDiff += content;
|
|
9051
|
+
currentFiles.push(file.filepath);
|
|
9016
9052
|
}
|
|
9017
|
-
|
|
9018
|
-
|
|
9019
|
-
|
|
9020
|
-
|
|
9021
|
-
tokensSaved
|
|
9022
|
-
};
|
|
9053
|
+
if (currentDiff.length > 0) {
|
|
9054
|
+
chunks.push({ diff: currentDiff, files: currentFiles });
|
|
9055
|
+
}
|
|
9056
|
+
return chunks;
|
|
9023
9057
|
}
|
|
9024
9058
|
var LOCK_FILES, GENERATED_PATTERNS, VENDORED_PREFIXES;
|
|
9025
9059
|
var init_smart_diff = __esm({
|
|
@@ -10626,17 +10660,15 @@ async function runLocalCommit(args) {
|
|
|
10626
10660
|
let diff = getStagedDiff(excludes);
|
|
10627
10661
|
const changes = getStagedFiles();
|
|
10628
10662
|
if (!args.noSmartDiff) {
|
|
10629
|
-
const smartResult = preprocessDiffWithSizeBudget(diff
|
|
10663
|
+
const smartResult = preprocessDiffWithSizeBudget(diff);
|
|
10630
10664
|
diff = smartResult.processedDiff;
|
|
10631
10665
|
if (smartResult.summarized.length > 0 && !silent) {
|
|
10632
10666
|
log.step(
|
|
10633
10667
|
`smart-diff: ${smartResult.summarized.length} file(s) summarized (saved ~${Math.round(smartResult.tokensSaved / 1e3)}K tokens)`
|
|
10634
10668
|
);
|
|
10635
10669
|
}
|
|
10636
|
-
if (smartResult.
|
|
10637
|
-
log.step(
|
|
10638
|
-
`large-diff: ${smartResult.aggressivelySummarized.length} additional file(s) summarized to fit (commit message may be less specific)`
|
|
10639
|
-
);
|
|
10670
|
+
if (smartResult.needsChunking && !silent) {
|
|
10671
|
+
log.step("large diff detected \u2014 local providers receive context-stripped diff");
|
|
10640
10672
|
}
|
|
10641
10673
|
}
|
|
10642
10674
|
let rules = { ...await detectCommitlintRules(), ...config2.rules ?? {} };
|
|
@@ -11815,19 +11847,16 @@ async function runCommit(args) {
|
|
|
11815
11847
|
const diff = getStagedDiff(excludes);
|
|
11816
11848
|
const changes = getStagedFiles();
|
|
11817
11849
|
let processedDiff = diff;
|
|
11850
|
+
let needsChunking = false;
|
|
11818
11851
|
if (!args.noSmartDiff) {
|
|
11819
|
-
const smartResult = preprocessDiffWithSizeBudget(diff
|
|
11852
|
+
const smartResult = preprocessDiffWithSizeBudget(diff);
|
|
11820
11853
|
processedDiff = smartResult.processedDiff;
|
|
11854
|
+
needsChunking = smartResult.needsChunking;
|
|
11821
11855
|
if (smartResult.summarized.length > 0) {
|
|
11822
11856
|
log.step(
|
|
11823
11857
|
`smart-diff: ${smartResult.summarized.length} file(s) summarized (saved ~${Math.round(smartResult.tokensSaved / 1e3)}K tokens)`
|
|
11824
11858
|
);
|
|
11825
11859
|
}
|
|
11826
|
-
if (smartResult.aggressivelySummarized.length > 0) {
|
|
11827
|
-
log.step(
|
|
11828
|
-
`large-diff: ${smartResult.aggressivelySummarized.length} additional file(s) summarized to fit (commit message may be less specific \u2014 consider committing fewer files at a time)`
|
|
11829
|
-
);
|
|
11830
|
-
}
|
|
11831
11860
|
}
|
|
11832
11861
|
const commitlintRules = await detectCommitlintRules();
|
|
11833
11862
|
let rules = { ...commitlintRules, ...config2.rules ?? {} };
|
|
@@ -11865,7 +11894,7 @@ async function runCommit(args) {
|
|
|
11865
11894
|
const boxStyle = args.boxStyleOverride ?? config2.ui?.box?.style ?? "gradient";
|
|
11866
11895
|
const spinner = createStageSpinner({
|
|
11867
11896
|
stage: "aiGenerate",
|
|
11868
|
-
message: `generating commit (${modelDisplay})...`,
|
|
11897
|
+
message: needsChunking ? `analyzing ${changes.trim().split("\n").length} files in chunks (${modelDisplay})...` : `generating commit (${modelDisplay})...`,
|
|
11869
11898
|
...uiCtx
|
|
11870
11899
|
});
|
|
11871
11900
|
if (!silent) spinner.start();
|
|
@@ -11873,14 +11902,63 @@ async function runCommit(args) {
|
|
|
11873
11902
|
let generatedMessage;
|
|
11874
11903
|
let diagnostics;
|
|
11875
11904
|
try {
|
|
11876
|
-
({
|
|
11877
|
-
processedDiff
|
|
11878
|
-
|
|
11879
|
-
|
|
11880
|
-
|
|
11881
|
-
|
|
11882
|
-
|
|
11883
|
-
|
|
11905
|
+
if (needsChunking) {
|
|
11906
|
+
const chunks = splitDiffIntoChunks(processedDiff);
|
|
11907
|
+
if (chunks.length === 0) {
|
|
11908
|
+
spinner.stop();
|
|
11909
|
+
log.error("No parseable diff content to analyze.");
|
|
11910
|
+
process.exit(1);
|
|
11911
|
+
}
|
|
11912
|
+
spinner.stop();
|
|
11913
|
+
if (!silent) log.step(`large diff \u2014 analyzing ${chunks.length} chunk(s) in parallel...`);
|
|
11914
|
+
const results = await Promise.allSettled(
|
|
11915
|
+
chunks.map(
|
|
11916
|
+
(chunk) => client.summarizeChunk(chunk.diff, chunk.files.filter(Boolean).join("\n") || "unknown", model)
|
|
11917
|
+
)
|
|
11918
|
+
);
|
|
11919
|
+
const summaries = [];
|
|
11920
|
+
for (const r of results) {
|
|
11921
|
+
if (r.status === "fulfilled" && r.value) {
|
|
11922
|
+
summaries.push(r.value);
|
|
11923
|
+
}
|
|
11924
|
+
}
|
|
11925
|
+
if (summaries.length === 0) {
|
|
11926
|
+
log.error("All chunk summaries failed. Check your connection and try again.");
|
|
11927
|
+
process.exit(1);
|
|
11928
|
+
}
|
|
11929
|
+
if (results.some((r) => r.status === "rejected") && !silent) {
|
|
11930
|
+
const failed = results.filter((r) => r.status === "rejected").length;
|
|
11931
|
+
log.step(`${failed}/${results.length} chunk(s) failed \u2014 continuing with partial summaries`);
|
|
11932
|
+
}
|
|
11933
|
+
const combinedSummary = summaries.join("\n\n");
|
|
11934
|
+
const finalSpinner = createStageSpinner({
|
|
11935
|
+
stage: "aiGenerate",
|
|
11936
|
+
message: `generating commit from ${chunks.length} summaries (${modelDisplay})...`,
|
|
11937
|
+
...uiCtx
|
|
11938
|
+
});
|
|
11939
|
+
if (!silent) finalSpinner.start();
|
|
11940
|
+
try {
|
|
11941
|
+
({ message: generatedMessage, diagnostics } = await client.generateCommit(
|
|
11942
|
+
combinedSummary,
|
|
11943
|
+
changes,
|
|
11944
|
+
rules,
|
|
11945
|
+
model,
|
|
11946
|
+
recentCommits,
|
|
11947
|
+
generationHints
|
|
11948
|
+
));
|
|
11949
|
+
} finally {
|
|
11950
|
+
finalSpinner.stop();
|
|
11951
|
+
}
|
|
11952
|
+
} else {
|
|
11953
|
+
({ message: generatedMessage, diagnostics } = await client.generateCommit(
|
|
11954
|
+
processedDiff,
|
|
11955
|
+
changes,
|
|
11956
|
+
rules,
|
|
11957
|
+
model,
|
|
11958
|
+
recentCommits,
|
|
11959
|
+
generationHints
|
|
11960
|
+
));
|
|
11961
|
+
}
|
|
11884
11962
|
} finally {
|
|
11885
11963
|
spinner.stop();
|
|
11886
11964
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quikcommit/cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "12.0.0",
|
|
4
4
|
"description": "AI-powered conventional commit messages",
|
|
5
5
|
"bin": {
|
|
6
6
|
"qc": "./dist/index.js"
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"esbuild": "^0.28.0",
|
|
35
35
|
"typescript": "^5.9.3",
|
|
36
36
|
"vitest": "^4.1.5",
|
|
37
|
-
"@quikcommit/shared": "
|
|
37
|
+
"@quikcommit/shared": "9.0.0"
|
|
38
38
|
},
|
|
39
39
|
"scripts": {
|
|
40
40
|
"build": "node build.mjs",
|