@quikcommit/cli 12.1.0 → 13.1.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 +236 -83
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -89,20 +89,59 @@ function slugifyFilename(path) {
|
|
|
89
89
|
const noExt = basename.replace(/\.[^.]+$/, "");
|
|
90
90
|
return noExt.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
|
|
91
91
|
}
|
|
92
|
+
function isTestPath(f) {
|
|
93
|
+
return /\.(test|spec)\.[^.]+$/i.test(f) || /^tests?\//i.test(f);
|
|
94
|
+
}
|
|
92
95
|
function pickBestFile(files) {
|
|
93
96
|
const codeFiles = files.filter((f) => !NON_CODE_PATTERNS.some((rx) => rx.test(f)));
|
|
94
|
-
|
|
97
|
+
const srcDirs = codeFiles.filter((f) => /^(src|lib|app|packages)\//.test(f));
|
|
98
|
+
const nonTestSrc = srcDirs.filter((f) => !isTestPath(f));
|
|
99
|
+
return nonTestSrc[0] ?? srcDirs[0] ?? codeFiles[0] ?? files[0] ?? "";
|
|
100
|
+
}
|
|
101
|
+
function inferTypeFromFiles(files) {
|
|
102
|
+
const isNonCode = (f) => NON_CODE_PATTERNS.some((rx) => rx.test(f));
|
|
103
|
+
let srcCount = 0, testCount = 0, docCount = 0, ciCount = 0;
|
|
104
|
+
for (const f of files) {
|
|
105
|
+
if (/^\.github\//i.test(f)) {
|
|
106
|
+
ciCount++;
|
|
107
|
+
} else if (isTestPath(f)) {
|
|
108
|
+
testCount++;
|
|
109
|
+
} else if (/^docs?\//i.test(f) || /\.md$/i.test(f) || /^readme/i.test(f)) {
|
|
110
|
+
docCount++;
|
|
111
|
+
} else if (!isNonCode(f)) {
|
|
112
|
+
srcCount++;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (srcCount === 0 && testCount > 0 && docCount === 0 && ciCount === 0)
|
|
116
|
+
return "test";
|
|
117
|
+
if (srcCount === 0 && docCount > 0 && testCount === 0 && ciCount === 0)
|
|
118
|
+
return "docs";
|
|
119
|
+
if (srcCount === 0 && ciCount > 0 && testCount === 0 && docCount === 0)
|
|
120
|
+
return "ci";
|
|
121
|
+
if (srcCount > 10)
|
|
122
|
+
return "refactor";
|
|
123
|
+
if (srcCount > 0)
|
|
124
|
+
return "feat";
|
|
125
|
+
if (testCount >= docCount && testCount >= ciCount)
|
|
126
|
+
return "test";
|
|
127
|
+
if (docCount >= ciCount)
|
|
128
|
+
return "docs";
|
|
129
|
+
if (ciCount > 0)
|
|
130
|
+
return "ci";
|
|
131
|
+
return "chore";
|
|
95
132
|
}
|
|
96
133
|
function deterministicBranchName(opts) {
|
|
97
134
|
const files = opts.files ?? [];
|
|
98
|
-
const codeFiles = files.filter((f) => !NON_CODE_PATTERNS.some((rx) => rx.test(f)));
|
|
99
|
-
const haystack = `${opts.description ?? ""} ${(codeFiles.length > 0 ? codeFiles : files).join(" ")}`;
|
|
100
135
|
let type = "chore";
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
136
|
+
if (opts.description) {
|
|
137
|
+
for (const [rx, t] of TYPE_HINTS) {
|
|
138
|
+
if (rx.test(opts.description)) {
|
|
139
|
+
type = t;
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
105
142
|
}
|
|
143
|
+
} else if (files.length > 0) {
|
|
144
|
+
type = inferTypeFromFiles(files);
|
|
106
145
|
}
|
|
107
146
|
let slug;
|
|
108
147
|
if (opts.description) {
|
|
@@ -127,16 +166,16 @@ var init_branch = __esm({
|
|
|
127
166
|
"../shared/dist/branch.js"() {
|
|
128
167
|
"use strict";
|
|
129
168
|
BRANCH_NAME_RX = /^(feat|fix|refactor|perf|docs|test|chore|ci)\/[a-z0-9][a-z0-9-]{0,51}$/;
|
|
130
|
-
PROTECTED_BRANCH_RX = /(
|
|
169
|
+
PROTECTED_BRANCH_RX = /(^|\/)(main|master|develop|trunk|release)(\/|$)/i;
|
|
131
170
|
MAX_BRANCH_NAME_LENGTH = 60;
|
|
132
171
|
TYPE_HINTS = [
|
|
133
|
-
[/\
|
|
134
|
-
[/\
|
|
135
|
-
[/\bperf|benchmark/i, "perf"],
|
|
172
|
+
[/\b(ci|workflow)\b|github\/actions/i, "ci"],
|
|
173
|
+
[/\b(perf|benchmark)\b/i, "perf"],
|
|
136
174
|
[/\brefactor\b/i, "refactor"],
|
|
137
|
-
[/\
|
|
138
|
-
[/\
|
|
139
|
-
[/\
|
|
175
|
+
[/\b(fix|bug|issue|patch)\b/i, "fix"],
|
|
176
|
+
[/\b(feat|add|new)\b/i, "feat"],
|
|
177
|
+
[/\b(docs?)\b|\breadme\b/i, "docs"],
|
|
178
|
+
[/\b(test|spec)\b/i, "test"]
|
|
140
179
|
];
|
|
141
180
|
NON_CODE_PATTERNS = [
|
|
142
181
|
/^docs?\//i,
|
|
@@ -150,6 +189,8 @@ var init_branch = __esm({
|
|
|
150
189
|
/\.json$/,
|
|
151
190
|
/\.toml$/,
|
|
152
191
|
/\/\.?config\b/i,
|
|
192
|
+
/\.config\.[a-z]+$/i,
|
|
193
|
+
// root-level config: eslint.config.mjs, vitest.config.ts
|
|
153
194
|
/^\.github\//,
|
|
154
195
|
/^\.vscode\//
|
|
155
196
|
];
|
|
@@ -400,7 +441,8 @@ var init_api = __esm({
|
|
|
400
441
|
"Content-Type": "application/json",
|
|
401
442
|
Authorization: `Bearer ${this.apiKey}`
|
|
402
443
|
},
|
|
403
|
-
body: JSON.stringify(body)
|
|
444
|
+
body: JSON.stringify(body),
|
|
445
|
+
signal: AbortSignal.timeout(3e4)
|
|
404
446
|
});
|
|
405
447
|
if (!res.ok) {
|
|
406
448
|
if (res.status === 413) {
|
|
@@ -476,7 +518,8 @@ var init_api = __esm({
|
|
|
476
518
|
"Content-Type": "application/json",
|
|
477
519
|
Authorization: `Bearer ${this.apiKey}`
|
|
478
520
|
},
|
|
479
|
-
body: options?.body
|
|
521
|
+
body: options?.body,
|
|
522
|
+
signal: AbortSignal.timeout(3e4)
|
|
480
523
|
});
|
|
481
524
|
if (!res.ok) {
|
|
482
525
|
const err = await res.json().catch(() => ({ error: res.statusText }));
|
|
@@ -7868,6 +7911,7 @@ __export(git_exports, {
|
|
|
7868
7911
|
createBranch: () => createBranch,
|
|
7869
7912
|
deleteBranch: () => deleteBranch,
|
|
7870
7913
|
getAllChangedFiles: () => getAllChangedFiles,
|
|
7914
|
+
getAllChangedFilesWithStatus: () => getAllChangedFilesWithStatus,
|
|
7871
7915
|
getBranchCommits: () => getBranchCommits,
|
|
7872
7916
|
getChangedFilesSince: () => getChangedFilesSince,
|
|
7873
7917
|
getCommitHash: () => getCommitHash,
|
|
@@ -7890,6 +7934,7 @@ __export(git_exports, {
|
|
|
7890
7934
|
getStagedFiles: () => getStagedFiles,
|
|
7891
7935
|
getUnstagedFiles: () => getUnstagedFiles,
|
|
7892
7936
|
getUpstreamRef: () => getUpstreamRef,
|
|
7937
|
+
getWorkingDiffStat: () => getWorkingDiffStat,
|
|
7893
7938
|
getWorkingTreeDiff: () => getWorkingTreeDiff,
|
|
7894
7939
|
gitCommit: () => gitCommit,
|
|
7895
7940
|
gitPush: () => gitPush,
|
|
@@ -7945,7 +7990,8 @@ function getStagedDiff(excludes = []) {
|
|
|
7945
7990
|
}
|
|
7946
7991
|
function getStagedFiles() {
|
|
7947
7992
|
return (0, import_child_process2.execFileSync)("git", ["diff", "--cached", "--name-only"], {
|
|
7948
|
-
encoding: "utf-8"
|
|
7993
|
+
encoding: "utf-8",
|
|
7994
|
+
maxBuffer: 10 * 1024 * 1024
|
|
7949
7995
|
});
|
|
7950
7996
|
}
|
|
7951
7997
|
function getWorkingTreeDiff(excludes = []) {
|
|
@@ -7968,9 +8014,36 @@ function getAllChangedFiles() {
|
|
|
7968
8014
|
});
|
|
7969
8015
|
return output.trim().split("\n").filter(Boolean).map((line) => line.slice(3)).join("\n");
|
|
7970
8016
|
}
|
|
8017
|
+
function getAllChangedFilesWithStatus() {
|
|
8018
|
+
const output = (0, import_child_process2.execFileSync)("git", ["status", "--porcelain"], {
|
|
8019
|
+
encoding: "utf-8"
|
|
8020
|
+
});
|
|
8021
|
+
return output.trim().split("\n").filter(Boolean).map((line) => {
|
|
8022
|
+
const xy = line.slice(0, 2).trim();
|
|
8023
|
+
const filepath = line.slice(3);
|
|
8024
|
+
let status = "M";
|
|
8025
|
+
if (xy === "??") status = "A";
|
|
8026
|
+
else if (xy.includes("D")) status = "D";
|
|
8027
|
+
else if (xy.includes("A")) status = "A";
|
|
8028
|
+
else if (xy.includes("R")) status = "R";
|
|
8029
|
+
return `${status} ${filepath}`;
|
|
8030
|
+
}).join("\n");
|
|
8031
|
+
}
|
|
8032
|
+
function getWorkingDiffStat(staged) {
|
|
8033
|
+
const args = staged ? ["diff", "--cached", "--stat=120"] : ["diff", "HEAD", "--stat=120"];
|
|
8034
|
+
try {
|
|
8035
|
+
return (0, import_child_process2.execFileSync)("git", args, {
|
|
8036
|
+
encoding: "utf-8",
|
|
8037
|
+
maxBuffer: 1024 * 1024
|
|
8038
|
+
}).trim();
|
|
8039
|
+
} catch {
|
|
8040
|
+
return "";
|
|
8041
|
+
}
|
|
8042
|
+
}
|
|
7971
8043
|
function hasStagedChanges() {
|
|
7972
8044
|
const output = (0, import_child_process2.execFileSync)("git", ["diff", "--cached", "--name-only"], {
|
|
7973
|
-
encoding: "utf-8"
|
|
8045
|
+
encoding: "utf-8",
|
|
8046
|
+
maxBuffer: 10 * 1024 * 1024
|
|
7974
8047
|
});
|
|
7975
8048
|
return output.trim().length > 0;
|
|
7976
8049
|
}
|
|
@@ -8839,7 +8912,8 @@ function isMinified(content) {
|
|
|
8839
8912
|
(l) => (l.startsWith("+") || l.startsWith("-")) && !l.startsWith("+++") && !l.startsWith("---")
|
|
8840
8913
|
);
|
|
8841
8914
|
if (lines.length === 0) return false;
|
|
8842
|
-
|
|
8915
|
+
const longLines = lines.filter((l) => l.length > 500).length;
|
|
8916
|
+
return longLines > lines.length / 2;
|
|
8843
8917
|
}
|
|
8844
8918
|
function preprocessDiff(diff) {
|
|
8845
8919
|
const files = parseDiffIntoFiles(diff);
|
|
@@ -9564,7 +9638,9 @@ function sanitizeBranchName(input) {
|
|
|
9564
9638
|
function ensureUniqueName(name, exists) {
|
|
9565
9639
|
if (!exists(name)) return name;
|
|
9566
9640
|
for (let i = 2; i <= 100; i++) {
|
|
9567
|
-
const
|
|
9641
|
+
const suffix = `-${i}`;
|
|
9642
|
+
const base = name.length + suffix.length > MAX_BRANCH_NAME_LENGTH ? name.slice(0, MAX_BRANCH_NAME_LENGTH - suffix.length) : name;
|
|
9643
|
+
const candidate = `${base}${suffix}`;
|
|
9568
9644
|
if (!exists(candidate)) return candidate;
|
|
9569
9645
|
}
|
|
9570
9646
|
throw new Error(`Could not find a unique name for ${name} after 100 attempts`);
|
|
@@ -10835,6 +10911,8 @@ async function generateLocalBranchName(opts) {
|
|
|
10835
10911
|
sections.push("Generate a git branch name in the format <type>/<kebab-case-slug>.");
|
|
10836
10912
|
sections.push("Type must be one of: feat, fix, refactor, perf, docs, test, chore, ci.");
|
|
10837
10913
|
sections.push("Slug: 2-5 words, lowercase, hyphen-separated, max 55 chars.");
|
|
10914
|
+
sections.push("For LARGE changesets: identify the dominant THEME (migration, refactor, feature). Name for the theme, not a single file.");
|
|
10915
|
+
sections.push("Look for DELETED + ADDED files as signals of migration.");
|
|
10838
10916
|
sections.push("Output ONLY the branch name on a single line. No explanation.");
|
|
10839
10917
|
sections.push("");
|
|
10840
10918
|
if (opts.description) {
|
|
@@ -10843,9 +10921,22 @@ async function generateLocalBranchName(opts) {
|
|
|
10843
10921
|
} else if (opts.recentCommits && opts.recentCommits.length > 0) {
|
|
10844
10922
|
sections.push("RECENT COMMITS:");
|
|
10845
10923
|
for (const c of opts.recentCommits) sections.push(`- ${c}`);
|
|
10846
|
-
} else
|
|
10847
|
-
|
|
10848
|
-
|
|
10924
|
+
} else {
|
|
10925
|
+
if (opts.changes) {
|
|
10926
|
+
sections.push("FILES CHANGED (PRIMARY signal \u2014 complete list):");
|
|
10927
|
+
sections.push(opts.changes.slice(0, 8e3));
|
|
10928
|
+
sections.push("");
|
|
10929
|
+
}
|
|
10930
|
+
if (opts.diffStat) {
|
|
10931
|
+
sections.push("CHANGE MAGNITUDE:");
|
|
10932
|
+
sections.push(opts.diffStat.slice(0, 6e3));
|
|
10933
|
+
sections.push("");
|
|
10934
|
+
}
|
|
10935
|
+
if (opts.diff) {
|
|
10936
|
+
const budget = opts.diffStat ? 8e3 : 3e4;
|
|
10937
|
+
sections.push("DIFF (supplementary, may be truncated):");
|
|
10938
|
+
sections.push(opts.diff.slice(0, budget));
|
|
10939
|
+
}
|
|
10849
10940
|
}
|
|
10850
10941
|
const userContent = sections.join("\n");
|
|
10851
10942
|
const model = opts.model ?? local.model;
|
|
@@ -10883,6 +10974,7 @@ async function generateLocalBranchName(opts) {
|
|
|
10883
10974
|
body = {
|
|
10884
10975
|
diff: opts.diff,
|
|
10885
10976
|
changes: opts.changes,
|
|
10977
|
+
diff_stat: opts.diffStat,
|
|
10886
10978
|
recent_commits: opts.recentCommits,
|
|
10887
10979
|
description: opts.description,
|
|
10888
10980
|
model,
|
|
@@ -10952,6 +11044,7 @@ async function runLocalBranch(opts) {
|
|
|
10952
11044
|
description: opts.description,
|
|
10953
11045
|
diff: opts.diff,
|
|
10954
11046
|
changes: opts.changes,
|
|
11047
|
+
diffStat: opts.diffStat,
|
|
10955
11048
|
recentCommits: opts.recentCommits,
|
|
10956
11049
|
model: opts.model,
|
|
10957
11050
|
rules: opts.rules
|
|
@@ -11194,8 +11287,10 @@ async function runBranch(opts) {
|
|
|
11194
11287
|
);
|
|
11195
11288
|
}
|
|
11196
11289
|
const rawDiff = getStagedDiff(config2.excludes ?? []);
|
|
11197
|
-
|
|
11290
|
+
const processed = preprocessDiff(rawDiff).processedDiff;
|
|
11291
|
+
payload.diff = processed.slice(0, 6e4) || void 0;
|
|
11198
11292
|
payload.changes = getStagedFiles();
|
|
11293
|
+
payload.diff_stat = getWorkingDiffStat(true) || void 0;
|
|
11199
11294
|
}
|
|
11200
11295
|
const apiKey = opts.apiKey ?? getApiKey();
|
|
11201
11296
|
if (!apiKey) {
|
|
@@ -11205,6 +11300,7 @@ async function runBranch(opts) {
|
|
|
11205
11300
|
description: opts.message,
|
|
11206
11301
|
diff: opts.message ? void 0 : payload.diff,
|
|
11207
11302
|
changes: opts.message ? void 0 : payload.changes,
|
|
11303
|
+
diffStat: opts.message ? void 0 : payload.diff_stat,
|
|
11208
11304
|
recentCommits: payload.recent_commits,
|
|
11209
11305
|
model: opts.model,
|
|
11210
11306
|
noSwitch: opts.noSwitch,
|
|
@@ -11232,6 +11328,10 @@ async function runBranch(opts) {
|
|
|
11232
11328
|
try {
|
|
11233
11329
|
const client = new ApiClient({ apiKey });
|
|
11234
11330
|
result = await client.generateBranchName(payload);
|
|
11331
|
+
} catch {
|
|
11332
|
+
const filesArr = payload.changes?.split("\n").filter(Boolean) ?? [];
|
|
11333
|
+
result = deterministicBranchName({ files: filesArr, description: payload.description });
|
|
11334
|
+
log.dim("(used deterministic fallback; API generation failed)");
|
|
11235
11335
|
} finally {
|
|
11236
11336
|
spinner.stop();
|
|
11237
11337
|
}
|
|
@@ -11647,15 +11747,21 @@ async function runBranchGuard(args, log) {
|
|
|
11647
11747
|
return { action: "continue" };
|
|
11648
11748
|
}
|
|
11649
11749
|
let stagedDiff = "";
|
|
11650
|
-
let
|
|
11750
|
+
let plainFiles = "";
|
|
11751
|
+
let changesForAI = "";
|
|
11752
|
+
let diffStat = "";
|
|
11651
11753
|
if (state.mode === "uncommitted") {
|
|
11652
11754
|
let rawDiff = getStagedDiff(args.excludes ?? []);
|
|
11653
|
-
|
|
11654
|
-
|
|
11755
|
+
plainFiles = getStagedFiles();
|
|
11756
|
+
changesForAI = plainFiles;
|
|
11757
|
+
const isStaged = !!rawDiff.trim();
|
|
11758
|
+
if (!isStaged) {
|
|
11655
11759
|
rawDiff = getWorkingTreeDiff(args.excludes ?? []);
|
|
11656
|
-
|
|
11760
|
+
plainFiles = getAllChangedFiles();
|
|
11761
|
+
changesForAI = getAllChangedFilesWithStatus();
|
|
11657
11762
|
}
|
|
11658
11763
|
stagedDiff = preprocessDiff(rawDiff).processedDiff;
|
|
11764
|
+
diffStat = getWorkingDiffStat(isStaged);
|
|
11659
11765
|
}
|
|
11660
11766
|
const recentCommits = state.mode === "rescue" ? getRecentBranchCommits(state.commitsAhead) : void 0;
|
|
11661
11767
|
const branchRules = args.branchRules ?? (config2.branch?.generation?.types && config2.branch.generation.types.length > 0 ? { types: [...config2.branch.generation.types] } : void 0);
|
|
@@ -11685,30 +11791,33 @@ async function runBranchGuard(args, log) {
|
|
|
11685
11791
|
if (apiKey) {
|
|
11686
11792
|
const client = new ApiClient({ apiKey });
|
|
11687
11793
|
try {
|
|
11794
|
+
const cappedDiff = stagedDiff ? stagedDiff.slice(0, 6e4) : void 0;
|
|
11688
11795
|
const branchResult = await client.generateBranchName({
|
|
11689
|
-
diff:
|
|
11690
|
-
changes:
|
|
11796
|
+
diff: cappedDiff || void 0,
|
|
11797
|
+
changes: changesForAI || void 0,
|
|
11798
|
+
diff_stat: diffStat || void 0,
|
|
11691
11799
|
recent_commits: recentCommits,
|
|
11692
11800
|
model: args.model,
|
|
11693
11801
|
rules: branchRules
|
|
11694
11802
|
});
|
|
11695
11803
|
rawName = branchResult.name;
|
|
11696
11804
|
} catch {
|
|
11697
|
-
const fallbackInput = state.mode === "rescue" ? { files: [], description: recentCommits?.join(" ") ?? "" } : { files:
|
|
11805
|
+
const fallbackInput = state.mode === "rescue" ? { files: [], description: recentCommits?.join(" ") ?? "" } : { files: plainFiles ? plainFiles.split("\n").filter(Boolean) : [] };
|
|
11698
11806
|
rawName = deterministicBranchName(fallbackInput).name;
|
|
11699
11807
|
usedFallback = true;
|
|
11700
11808
|
}
|
|
11701
11809
|
} else {
|
|
11702
11810
|
try {
|
|
11703
11811
|
rawName = await generateLocalBranchNameFn({
|
|
11704
|
-
diff: stagedDiff
|
|
11705
|
-
changes:
|
|
11812
|
+
diff: stagedDiff ? stagedDiff.slice(0, 6e4) : void 0,
|
|
11813
|
+
changes: changesForAI || void 0,
|
|
11814
|
+
diffStat: diffStat || void 0,
|
|
11706
11815
|
recentCommits,
|
|
11707
11816
|
model: args.model,
|
|
11708
11817
|
rules: branchRules
|
|
11709
11818
|
});
|
|
11710
11819
|
} catch {
|
|
11711
|
-
const fallbackInput = state.mode === "rescue" ? { files: [], description: recentCommits?.join(" ") ?? "" } : { files:
|
|
11820
|
+
const fallbackInput = state.mode === "rescue" ? { files: [], description: recentCommits?.join(" ") ?? "" } : { files: plainFiles ? plainFiles.split("\n").filter(Boolean) : [] };
|
|
11712
11821
|
rawName = deterministicBranchName(fallbackInput).name;
|
|
11713
11822
|
usedFallback = true;
|
|
11714
11823
|
}
|
|
@@ -11886,7 +11995,11 @@ async function runCommit(args) {
|
|
|
11886
11995
|
if (intersected.length > 0) rules = { ...rules, scopes: intersected };
|
|
11887
11996
|
}
|
|
11888
11997
|
}
|
|
11889
|
-
} catch {
|
|
11998
|
+
} catch (err) {
|
|
11999
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
12000
|
+
if (msg && !/not found|404|403/i.test(msg)) {
|
|
12001
|
+
log.dim("\u26A0 could not fetch team rules: " + msg.slice(0, 80));
|
|
12002
|
+
}
|
|
11890
12003
|
}
|
|
11891
12004
|
rules = applyCliTypeScopeToRules(rules, args.type, args.scope);
|
|
11892
12005
|
const recentCommits = args.noContext ? void 0 : getRecentBranchCommits(5);
|
|
@@ -11896,72 +12009,86 @@ async function runCommit(args) {
|
|
|
11896
12009
|
const modelDisplay = model ?? "default";
|
|
11897
12010
|
const uiCtx = buildUIContext(ui2, config2, args);
|
|
11898
12011
|
const boxStyle = args.boxStyleOverride ?? config2.ui?.box?.style ?? "gradient";
|
|
12012
|
+
const t0 = Date.now();
|
|
12013
|
+
let generatedMessage;
|
|
12014
|
+
let diagnostics;
|
|
12015
|
+
async function generateViaChunks() {
|
|
12016
|
+
const chunks = splitDiffIntoChunks(processedDiff);
|
|
12017
|
+
if (chunks.length === 0) {
|
|
12018
|
+
log.error("No parseable diff content to analyze.");
|
|
12019
|
+
process.exit(1);
|
|
12020
|
+
}
|
|
12021
|
+
if (!silent) log.step(`large diff \u2014 analyzing ${chunks.length} chunk(s) in parallel...`);
|
|
12022
|
+
const results = await Promise.allSettled(
|
|
12023
|
+
chunks.map(
|
|
12024
|
+
(chunk) => client.summarizeChunk(chunk.diff, chunk.files.filter(Boolean).join("\n") || "unknown", model)
|
|
12025
|
+
)
|
|
12026
|
+
);
|
|
12027
|
+
const summaries = [];
|
|
12028
|
+
for (const r of results) {
|
|
12029
|
+
if (r.status === "fulfilled" && r.value) {
|
|
12030
|
+
summaries.push(r.value);
|
|
12031
|
+
}
|
|
12032
|
+
}
|
|
12033
|
+
if (summaries.length === 0) {
|
|
12034
|
+
log.error("All chunk summaries failed. Check your connection and try again.");
|
|
12035
|
+
process.exit(1);
|
|
12036
|
+
}
|
|
12037
|
+
if (results.some((r) => r.status === "rejected") && !silent) {
|
|
12038
|
+
const failed = results.filter((r) => r.status === "rejected").length;
|
|
12039
|
+
log.step(`${failed}/${results.length} chunk(s) failed \u2014 continuing with partial summaries`);
|
|
12040
|
+
}
|
|
12041
|
+
const combinedSummary = summaries.join("\n\n");
|
|
12042
|
+
const finalSpinner = createStageSpinner({
|
|
12043
|
+
stage: "aiGenerate",
|
|
12044
|
+
message: `generating commit from ${chunks.length} summaries (${modelDisplay})...`,
|
|
12045
|
+
...uiCtx
|
|
12046
|
+
});
|
|
12047
|
+
if (!silent) finalSpinner.start();
|
|
12048
|
+
try {
|
|
12049
|
+
const result = await client.generateCommit(
|
|
12050
|
+
combinedSummary,
|
|
12051
|
+
changes,
|
|
12052
|
+
rules,
|
|
12053
|
+
model,
|
|
12054
|
+
recentCommits,
|
|
12055
|
+
generationHints
|
|
12056
|
+
);
|
|
12057
|
+
return result;
|
|
12058
|
+
} finally {
|
|
12059
|
+
finalSpinner.stop();
|
|
12060
|
+
}
|
|
12061
|
+
}
|
|
11899
12062
|
const spinner = createStageSpinner({
|
|
11900
12063
|
stage: "aiGenerate",
|
|
11901
12064
|
message: needsChunking ? `analyzing ${changes.trim().split("\n").length} files in chunks (${modelDisplay})...` : `generating commit (${modelDisplay})...`,
|
|
11902
12065
|
...uiCtx
|
|
11903
12066
|
});
|
|
11904
12067
|
if (!silent) spinner.start();
|
|
11905
|
-
const t0 = Date.now();
|
|
11906
|
-
let generatedMessage;
|
|
11907
|
-
let diagnostics;
|
|
11908
12068
|
try {
|
|
11909
12069
|
if (needsChunking) {
|
|
11910
|
-
const chunks = splitDiffIntoChunks(processedDiff);
|
|
11911
|
-
if (chunks.length === 0) {
|
|
11912
|
-
spinner.stop();
|
|
11913
|
-
log.error("No parseable diff content to analyze.");
|
|
11914
|
-
process.exit(1);
|
|
11915
|
-
}
|
|
11916
12070
|
spinner.stop();
|
|
11917
|
-
|
|
11918
|
-
|
|
11919
|
-
chunks.map(
|
|
11920
|
-
(chunk) => client.summarizeChunk(chunk.diff, chunk.files.filter(Boolean).join("\n") || "unknown", model)
|
|
11921
|
-
)
|
|
11922
|
-
);
|
|
11923
|
-
const summaries = [];
|
|
11924
|
-
for (const r of results) {
|
|
11925
|
-
if (r.status === "fulfilled" && r.value) {
|
|
11926
|
-
summaries.push(r.value);
|
|
11927
|
-
}
|
|
11928
|
-
}
|
|
11929
|
-
if (summaries.length === 0) {
|
|
11930
|
-
log.error("All chunk summaries failed. Check your connection and try again.");
|
|
11931
|
-
process.exit(1);
|
|
11932
|
-
}
|
|
11933
|
-
if (results.some((r) => r.status === "rejected") && !silent) {
|
|
11934
|
-
const failed = results.filter((r) => r.status === "rejected").length;
|
|
11935
|
-
log.step(`${failed}/${results.length} chunk(s) failed \u2014 continuing with partial summaries`);
|
|
11936
|
-
}
|
|
11937
|
-
const combinedSummary = summaries.join("\n\n");
|
|
11938
|
-
const finalSpinner = createStageSpinner({
|
|
11939
|
-
stage: "aiGenerate",
|
|
11940
|
-
message: `generating commit from ${chunks.length} summaries (${modelDisplay})...`,
|
|
11941
|
-
...uiCtx
|
|
11942
|
-
});
|
|
11943
|
-
if (!silent) finalSpinner.start();
|
|
12071
|
+
({ message: generatedMessage, diagnostics } = await generateViaChunks());
|
|
12072
|
+
} else {
|
|
11944
12073
|
try {
|
|
11945
12074
|
({ message: generatedMessage, diagnostics } = await client.generateCommit(
|
|
11946
|
-
|
|
12075
|
+
processedDiff,
|
|
11947
12076
|
changes,
|
|
11948
12077
|
rules,
|
|
11949
12078
|
model,
|
|
11950
12079
|
recentCommits,
|
|
11951
12080
|
generationHints
|
|
11952
12081
|
));
|
|
11953
|
-
}
|
|
11954
|
-
|
|
12082
|
+
} catch (err) {
|
|
12083
|
+
const is413 = err instanceof Error && /too large/i.test(err.message);
|
|
12084
|
+
if (is413) {
|
|
12085
|
+
spinner.stop();
|
|
12086
|
+
if (!silent) log.step("diff too large for single request \u2014 switching to chunk mode...");
|
|
12087
|
+
({ message: generatedMessage, diagnostics } = await generateViaChunks());
|
|
12088
|
+
} else {
|
|
12089
|
+
throw err;
|
|
12090
|
+
}
|
|
11955
12091
|
}
|
|
11956
|
-
} else {
|
|
11957
|
-
({ message: generatedMessage, diagnostics } = await client.generateCommit(
|
|
11958
|
-
processedDiff,
|
|
11959
|
-
changes,
|
|
11960
|
-
rules,
|
|
11961
|
-
model,
|
|
11962
|
-
recentCommits,
|
|
11963
|
-
generationHints
|
|
11964
|
-
));
|
|
11965
12092
|
}
|
|
11966
12093
|
} finally {
|
|
11967
12094
|
spinner.stop();
|
|
@@ -12312,6 +12439,8 @@ function parseArgs(args) {
|
|
|
12312
12439
|
result.messageOnly = true;
|
|
12313
12440
|
} else if (arg === "--message" && i + 1 < args.length) {
|
|
12314
12441
|
result.message = args[++i];
|
|
12442
|
+
} else if (arg === "--message") {
|
|
12443
|
+
throw new Error("Flag --message requires a value");
|
|
12315
12444
|
} else if (arg === "--push") {
|
|
12316
12445
|
result.push = true;
|
|
12317
12446
|
} else if (arg === "--rescue") {
|
|
@@ -12351,33 +12480,51 @@ function parseArgs(args) {
|
|
|
12351
12480
|
}
|
|
12352
12481
|
} else if (arg === "--api-key" && i + 1 < args.length) {
|
|
12353
12482
|
result.apiKey = args[++i];
|
|
12483
|
+
} else if (arg === "--api-key") {
|
|
12484
|
+
throw new Error("Flag --api-key requires a value");
|
|
12354
12485
|
} else if (arg === "--base" && i + 1 < args.length) {
|
|
12355
12486
|
result.base = args[++i];
|
|
12487
|
+
} else if (arg === "--base") {
|
|
12488
|
+
throw new Error("Flag --base requires a value");
|
|
12356
12489
|
} else if (arg === "--create") {
|
|
12357
12490
|
result.create = true;
|
|
12358
12491
|
} else if (arg === "--from" && i + 1 < args.length) {
|
|
12359
12492
|
result.from = args[++i];
|
|
12493
|
+
} else if (arg === "--from") {
|
|
12494
|
+
throw new Error("Flag --from requires a value");
|
|
12360
12495
|
} else if (arg === "--from-commits") {
|
|
12361
12496
|
result.fromCommits = true;
|
|
12362
12497
|
} else if (arg === "--to" && i + 1 < args.length) {
|
|
12363
12498
|
result.to = args[++i];
|
|
12499
|
+
} else if (arg === "--to") {
|
|
12500
|
+
throw new Error("Flag --to requires a value");
|
|
12364
12501
|
} else if (arg === "--write") {
|
|
12365
12502
|
result.write = true;
|
|
12366
12503
|
} else if (arg === "--version" && i + 1 < args.length) {
|
|
12367
12504
|
result.version = args[++i];
|
|
12505
|
+
} else if (arg === "--version") {
|
|
12506
|
+
throw new Error("Flag --version requires a value");
|
|
12368
12507
|
} else if (arg === "--uninstall") {
|
|
12369
12508
|
result.uninstall = true;
|
|
12370
12509
|
} else if (arg === "--hook-mode") {
|
|
12371
12510
|
result.hookMode = true;
|
|
12372
12511
|
} else if (arg === "--model" && i + 1 < args.length) {
|
|
12373
12512
|
result.model = args[++i];
|
|
12513
|
+
} else if (arg === "--model") {
|
|
12514
|
+
throw new Error("Flag --model requires a value");
|
|
12374
12515
|
} else if (arg === "--type" && i + 1 < args.length) {
|
|
12375
12516
|
result.type = args[++i];
|
|
12517
|
+
} else if (arg === "--type") {
|
|
12518
|
+
throw new Error("Flag --type requires a value");
|
|
12376
12519
|
} else if (arg === "--scope" && i + 1 < args.length) {
|
|
12377
12520
|
result.scope = args[++i];
|
|
12521
|
+
} else if (arg === "--scope") {
|
|
12522
|
+
throw new Error("Flag --scope requires a value");
|
|
12378
12523
|
} else if (arg === "--exclude" && i + 1 < args.length) {
|
|
12379
12524
|
const ex = args[++i];
|
|
12380
12525
|
if (ex) result.exclude.push(ex);
|
|
12526
|
+
} else if (arg === "--exclude") {
|
|
12527
|
+
throw new Error("Flag --exclude requires a value");
|
|
12381
12528
|
} else if (arg === "--no-color") {
|
|
12382
12529
|
} else if (arg === "--no-animate") {
|
|
12383
12530
|
result.noAnimate = true;
|
|
@@ -12389,6 +12536,8 @@ function parseArgs(args) {
|
|
|
12389
12536
|
);
|
|
12390
12537
|
}
|
|
12391
12538
|
result.boxStyleOverride = v;
|
|
12539
|
+
} else if (arg === "--style") {
|
|
12540
|
+
throw new Error("Flag --style requires a value");
|
|
12392
12541
|
} else if (arg === "login") {
|
|
12393
12542
|
result.command = "login";
|
|
12394
12543
|
subcommandSeen = true;
|
|
@@ -12424,6 +12573,10 @@ function parseArgs(args) {
|
|
|
12424
12573
|
subcommandSeen = true;
|
|
12425
12574
|
} else if (subcommandSeen && !arg.startsWith("-")) {
|
|
12426
12575
|
result.positionals.push(arg);
|
|
12576
|
+
} else if (arg.startsWith("--")) {
|
|
12577
|
+
throw new Error(`Unknown flag: ${arg}`);
|
|
12578
|
+
} else if (!subcommandSeen) {
|
|
12579
|
+
throw new Error(`Unknown command: ${arg}. Run 'qc --help' for usage.`);
|
|
12427
12580
|
}
|
|
12428
12581
|
}
|
|
12429
12582
|
if (result.messageOnly && result.push) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quikcommit/cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "13.1.0",
|
|
4
4
|
"description": "AI-powered conventional commit messages",
|
|
5
5
|
"bin": {
|
|
6
6
|
"qc": "./dist/index.js"
|
|
@@ -34,14 +34,14 @@
|
|
|
34
34
|
"esbuild": "^0.28.0",
|
|
35
35
|
"typescript": "^5.9.3",
|
|
36
36
|
"vitest": "^4.1.5",
|
|
37
|
-
"@quikcommit/shared": "
|
|
37
|
+
"@quikcommit/shared": "10.1.0"
|
|
38
38
|
},
|
|
39
39
|
"scripts": {
|
|
40
40
|
"build": "node build.mjs",
|
|
41
41
|
"typecheck": "tsc --noEmit",
|
|
42
42
|
"start": "node dist/index.js",
|
|
43
43
|
"test": "vitest run",
|
|
44
|
-
"lint": "
|
|
45
|
-
"lint:fix": "
|
|
44
|
+
"lint": "oxlint .",
|
|
45
|
+
"lint:fix": "oxlint --fix"
|
|
46
46
|
}
|
|
47
47
|
}
|