@quikcommit/cli 7.0.0 → 9.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 +3004 -923
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -32,6 +32,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
32
32
|
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
33
33
|
mod
|
|
34
34
|
));
|
|
35
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
35
36
|
|
|
36
37
|
// ../shared/dist/types.js
|
|
37
38
|
var init_types = __esm({
|
|
@@ -61,6 +62,80 @@ var init_rules = __esm({
|
|
|
61
62
|
}
|
|
62
63
|
});
|
|
63
64
|
|
|
65
|
+
// ../shared/dist/tokens.js
|
|
66
|
+
function estimateTokens(text) {
|
|
67
|
+
return Math.ceil(text.length / 2.5);
|
|
68
|
+
}
|
|
69
|
+
var init_tokens = __esm({
|
|
70
|
+
"../shared/dist/tokens.js"() {
|
|
71
|
+
"use strict";
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// ../shared/dist/branch.js
|
|
76
|
+
function validateBranchName(name) {
|
|
77
|
+
if (typeof name !== "string")
|
|
78
|
+
return false;
|
|
79
|
+
if (name.length > MAX_BRANCH_NAME_LENGTH)
|
|
80
|
+
return false;
|
|
81
|
+
if (!BRANCH_NAME_RX.test(name))
|
|
82
|
+
return false;
|
|
83
|
+
if (PROTECTED_BRANCH_RX.test(name))
|
|
84
|
+
return false;
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
function slugifyFilename(path) {
|
|
88
|
+
const basename = path.split("/").pop() ?? path;
|
|
89
|
+
const noExt = basename.replace(/\.[^.]+$/, "");
|
|
90
|
+
return noExt.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
|
|
91
|
+
}
|
|
92
|
+
function deterministicBranchName(opts) {
|
|
93
|
+
const files = opts.files ?? [];
|
|
94
|
+
const haystack = `${opts.description ?? ""} ${files.join(" ")}`;
|
|
95
|
+
let type = "chore";
|
|
96
|
+
for (const [rx, t] of TYPE_HINTS) {
|
|
97
|
+
if (rx.test(haystack)) {
|
|
98
|
+
type = t;
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
let slug;
|
|
103
|
+
if (opts.description) {
|
|
104
|
+
slug = opts.description.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").split("-").slice(0, 5).join("-").slice(0, 40);
|
|
105
|
+
} else if (files.length > 0) {
|
|
106
|
+
slug = slugifyFilename(files[0] ?? "");
|
|
107
|
+
} else {
|
|
108
|
+
slug = "changes";
|
|
109
|
+
}
|
|
110
|
+
if (!slug)
|
|
111
|
+
slug = "changes";
|
|
112
|
+
if (slug.length === 1)
|
|
113
|
+
slug = `${slug}-changes`;
|
|
114
|
+
const name = `${type}/${slug}`;
|
|
115
|
+
if (!validateBranchName(name)) {
|
|
116
|
+
return { type: "chore", slug: "updates", name: "chore/updates" };
|
|
117
|
+
}
|
|
118
|
+
return { name, type, slug };
|
|
119
|
+
}
|
|
120
|
+
var BRANCH_NAME_RX, PROTECTED_BRANCH_RX, MAX_BRANCH_NAME_LENGTH, TYPE_HINTS;
|
|
121
|
+
var init_branch = __esm({
|
|
122
|
+
"../shared/dist/branch.js"() {
|
|
123
|
+
"use strict";
|
|
124
|
+
BRANCH_NAME_RX = /^(feat|fix|refactor|perf|docs|test|chore|ci)\/[a-z0-9][a-z0-9-]{0,51}$/;
|
|
125
|
+
PROTECTED_BRANCH_RX = /(^|[/-])(main|master|develop|trunk|release)([/-]|$)/i;
|
|
126
|
+
MAX_BRANCH_NAME_LENGTH = 60;
|
|
127
|
+
TYPE_HINTS = [
|
|
128
|
+
[/\btest|spec\b/i, "test"],
|
|
129
|
+
[/\bdocs?\b|readme|\.md$/i, "docs"],
|
|
130
|
+
[/\bperf|benchmark/i, "perf"],
|
|
131
|
+
[/\brefactor\b/i, "refactor"],
|
|
132
|
+
[/\bci|workflow|github\/actions/i, "ci"],
|
|
133
|
+
[/\bfix|bug|issue/i, "fix"],
|
|
134
|
+
[/\bfeat|add|new\b/i, "feat"]
|
|
135
|
+
];
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
64
139
|
// ../shared/dist/index.js
|
|
65
140
|
var init_dist = __esm({
|
|
66
141
|
"../shared/dist/index.js"() {
|
|
@@ -68,10 +143,20 @@ var init_dist = __esm({
|
|
|
68
143
|
init_types();
|
|
69
144
|
init_constants();
|
|
70
145
|
init_rules();
|
|
146
|
+
init_tokens();
|
|
147
|
+
init_branch();
|
|
71
148
|
}
|
|
72
149
|
});
|
|
73
150
|
|
|
74
151
|
// src/config.ts
|
|
152
|
+
var config_exports = {};
|
|
153
|
+
__export(config_exports, {
|
|
154
|
+
clearApiKey: () => clearApiKey,
|
|
155
|
+
getApiKey: () => getApiKey,
|
|
156
|
+
getConfig: () => getConfig,
|
|
157
|
+
saveApiKey: () => saveApiKey,
|
|
158
|
+
saveConfig: () => saveConfig
|
|
159
|
+
});
|
|
75
160
|
function getApiKey() {
|
|
76
161
|
const envKey = process.env.QC_API_KEY;
|
|
77
162
|
if (envKey?.trim()) return envKey.trim();
|
|
@@ -125,6 +210,149 @@ var init_config = __esm({
|
|
|
125
210
|
}
|
|
126
211
|
});
|
|
127
212
|
|
|
213
|
+
// src/commands/login.ts
|
|
214
|
+
var login_exports = {};
|
|
215
|
+
__export(login_exports, {
|
|
216
|
+
runLogin: () => runLogin
|
|
217
|
+
});
|
|
218
|
+
function openBrowser(url) {
|
|
219
|
+
try {
|
|
220
|
+
if ((0, import_os2.platform)() === "darwin") {
|
|
221
|
+
(0, import_child_process.execFileSync)("open", [url], { stdio: "pipe" });
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
if ((0, import_os2.platform)() === "linux") {
|
|
225
|
+
(0, import_child_process.execFileSync)("xdg-open", [url], { stdio: "pipe" });
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
if ((0, import_os2.platform)() === "win32") {
|
|
229
|
+
(0, import_child_process.execFileSync)("cmd", ["/c", "start", "", url], { stdio: "pipe" });
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
} catch {
|
|
233
|
+
}
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
async function runLogin() {
|
|
237
|
+
const codeRes = await fetch(`${API_URL}/api/auth/device/code`, {
|
|
238
|
+
method: "POST",
|
|
239
|
+
headers: { "Content-Type": "application/json" },
|
|
240
|
+
body: JSON.stringify({ client_id: CLIENT_ID })
|
|
241
|
+
});
|
|
242
|
+
if (!codeRes.ok) {
|
|
243
|
+
const err = await codeRes.json().catch(() => ({ error: codeRes.statusText }));
|
|
244
|
+
throw new Error(err.error ?? "Failed to start device flow");
|
|
245
|
+
}
|
|
246
|
+
const codeData = await codeRes.json();
|
|
247
|
+
const { device_code, user_code, verification_uri_complete, interval = 5 } = codeData;
|
|
248
|
+
if (!device_code || !user_code) {
|
|
249
|
+
throw new Error("Server did not return device codes");
|
|
250
|
+
}
|
|
251
|
+
console.log("Opening browser to sign in...");
|
|
252
|
+
console.log("");
|
|
253
|
+
console.log(` Your code: ${user_code}`);
|
|
254
|
+
console.log("");
|
|
255
|
+
const authUrl = verification_uri_complete ?? `${DASHBOARD_URL}/device?user_code=${encodeURIComponent(user_code)}`;
|
|
256
|
+
const opened = openBrowser(authUrl);
|
|
257
|
+
if (!opened) {
|
|
258
|
+
console.log("Could not open browser. Please visit:");
|
|
259
|
+
console.log(authUrl);
|
|
260
|
+
console.log("");
|
|
261
|
+
}
|
|
262
|
+
let frame = 0;
|
|
263
|
+
const spinner = setInterval(() => {
|
|
264
|
+
const elapsed = Math.floor((Date.now() - startTime) / 1e3);
|
|
265
|
+
process.stderr.write(
|
|
266
|
+
`\r${SPINNER_FRAMES[frame++ % SPINNER_FRAMES.length]} Waiting for authorization... (${elapsed}s)`
|
|
267
|
+
);
|
|
268
|
+
}, 80);
|
|
269
|
+
let pollingInterval = interval * 1e3;
|
|
270
|
+
const startTime = Date.now();
|
|
271
|
+
try {
|
|
272
|
+
while (Date.now() - startTime < DEVICE_FLOW_TIMEOUT) {
|
|
273
|
+
await new Promise((r) => setTimeout(r, pollingInterval));
|
|
274
|
+
try {
|
|
275
|
+
const tokenRes = await fetch(`${API_URL}/api/auth/device/token`, {
|
|
276
|
+
method: "POST",
|
|
277
|
+
headers: { "Content-Type": "application/json" },
|
|
278
|
+
body: JSON.stringify({
|
|
279
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
280
|
+
device_code,
|
|
281
|
+
client_id: CLIENT_ID
|
|
282
|
+
})
|
|
283
|
+
});
|
|
284
|
+
const tokenData = await tokenRes.json();
|
|
285
|
+
if (tokenData.access_token) {
|
|
286
|
+
saveApiKey(tokenData.access_token);
|
|
287
|
+
process.stderr.write("\r\x1B[2K");
|
|
288
|
+
console.log("Successfully logged in!");
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
if (tokenData.error) {
|
|
292
|
+
switch (tokenData.error) {
|
|
293
|
+
case "authorization_pending":
|
|
294
|
+
break;
|
|
295
|
+
// continue polling
|
|
296
|
+
case "slow_down":
|
|
297
|
+
pollingInterval += 5e3;
|
|
298
|
+
break;
|
|
299
|
+
case "access_denied":
|
|
300
|
+
process.stderr.write("\r\x1B[2K");
|
|
301
|
+
console.error("Authorization was denied.");
|
|
302
|
+
process.exit(1);
|
|
303
|
+
break;
|
|
304
|
+
case "expired_token":
|
|
305
|
+
process.stderr.write("\r\x1B[2K");
|
|
306
|
+
console.error("Device code expired. Please try again.");
|
|
307
|
+
process.exit(1);
|
|
308
|
+
break;
|
|
309
|
+
default:
|
|
310
|
+
process.stderr.write("\r\x1B[2K");
|
|
311
|
+
console.error(`Error: ${tokenData.error_description ?? tokenData.error}`);
|
|
312
|
+
process.exit(1);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
} catch {
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
process.stderr.write("\r\x1B[2K");
|
|
319
|
+
console.error("Login timed out. Please try again.");
|
|
320
|
+
process.exit(1);
|
|
321
|
+
} finally {
|
|
322
|
+
clearInterval(spinner);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
var import_child_process, import_os2, API_URL, DASHBOARD_URL, CLIENT_ID, SPINNER_FRAMES;
|
|
326
|
+
var init_login = __esm({
|
|
327
|
+
"src/commands/login.ts"() {
|
|
328
|
+
"use strict";
|
|
329
|
+
import_child_process = require("child_process");
|
|
330
|
+
import_os2 = require("os");
|
|
331
|
+
init_config();
|
|
332
|
+
init_dist();
|
|
333
|
+
API_URL = process.env.QC_API_URL ?? DEFAULT_API_URL;
|
|
334
|
+
DASHBOARD_URL = "https://app.quikcommit.dev";
|
|
335
|
+
CLIENT_ID = "qc-cli";
|
|
336
|
+
SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
// src/commands/logout.ts
|
|
341
|
+
var logout_exports = {};
|
|
342
|
+
__export(logout_exports, {
|
|
343
|
+
runLogout: () => runLogout
|
|
344
|
+
});
|
|
345
|
+
function runLogout() {
|
|
346
|
+
clearApiKey();
|
|
347
|
+
console.log("Logged out. Credentials cleared.");
|
|
348
|
+
}
|
|
349
|
+
var init_logout = __esm({
|
|
350
|
+
"src/commands/logout.ts"() {
|
|
351
|
+
"use strict";
|
|
352
|
+
init_config();
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
|
|
128
356
|
// src/api.ts
|
|
129
357
|
var ApiClient;
|
|
130
358
|
var init_api = __esm({
|
|
@@ -155,6 +383,13 @@ var init_api = __esm({
|
|
|
155
383
|
body: JSON.stringify(body)
|
|
156
384
|
});
|
|
157
385
|
if (!res.ok) {
|
|
386
|
+
if (res.status === 413) {
|
|
387
|
+
const errBody = await res.json().catch(() => ({}));
|
|
388
|
+
const sizeHint = errBody.received_bytes ? ` (${Math.round(errBody.received_bytes / 1024)}KB > ${Math.round((errBody.limit_bytes ?? 0) / 1024)}KB limit)` : "";
|
|
389
|
+
throw new Error(
|
|
390
|
+
`Diff too large to send${sizeHint}. Try: qc --exclude '*.lock' --exclude 'dist/**' (or commit fewer files at a time).`
|
|
391
|
+
);
|
|
392
|
+
}
|
|
158
393
|
const err = await res.json().catch(() => ({ error: res.statusText }));
|
|
159
394
|
if (planRequiredMsg && err.code === "PLAN_REQUIRED") {
|
|
160
395
|
throw new Error(planRequiredMsg);
|
|
@@ -163,8 +398,15 @@ var init_api = __esm({
|
|
|
163
398
|
}
|
|
164
399
|
return res.json();
|
|
165
400
|
}
|
|
166
|
-
async generateCommit(diff, changes, rules, model) {
|
|
167
|
-
const body = {
|
|
401
|
+
async generateCommit(diff, changes, rules, model, recentCommits, generationHints) {
|
|
402
|
+
const body = {
|
|
403
|
+
diff,
|
|
404
|
+
changes,
|
|
405
|
+
rules,
|
|
406
|
+
model,
|
|
407
|
+
recent_commits: recentCommits,
|
|
408
|
+
...generationHints && Object.keys(generationHints).length > 0 ? { generation_hints: generationHints } : {}
|
|
409
|
+
};
|
|
168
410
|
const data = await this.request(
|
|
169
411
|
"/v1/commit",
|
|
170
412
|
body
|
|
@@ -194,6 +436,9 @@ var init_api = __esm({
|
|
|
194
436
|
summary: data.summary ?? ""
|
|
195
437
|
};
|
|
196
438
|
}
|
|
439
|
+
async generateBranchName(req) {
|
|
440
|
+
return this.request("/v1/branch", req);
|
|
441
|
+
}
|
|
197
442
|
async fetchJson(endpoint, options) {
|
|
198
443
|
if (!this.apiKey) {
|
|
199
444
|
throw new Error("Not authenticated. Run `qc login` first.");
|
|
@@ -248,6 +493,37 @@ var init_api = __esm({
|
|
|
248
493
|
}
|
|
249
494
|
});
|
|
250
495
|
|
|
496
|
+
// src/commands/status.ts
|
|
497
|
+
var status_exports = {};
|
|
498
|
+
__export(status_exports, {
|
|
499
|
+
runStatus: () => runStatus
|
|
500
|
+
});
|
|
501
|
+
async function runStatus(apiKeyFlag) {
|
|
502
|
+
const apiKey = apiKeyFlag ?? getApiKey();
|
|
503
|
+
if (!apiKey) {
|
|
504
|
+
console.log("Not logged in. Run `qc login` to authenticate.");
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
console.log("Logged in: yes");
|
|
508
|
+
console.log(` API key: ...${apiKey.slice(-4)}`);
|
|
509
|
+
const client = new ApiClient({ apiKey });
|
|
510
|
+
const usage = await client.getUsage();
|
|
511
|
+
if (usage) {
|
|
512
|
+
console.log(`Plan: ${usage.plan}`);
|
|
513
|
+
console.log(`Usage: ${usage.commit_count}/${usage.limit} commits this period`);
|
|
514
|
+
console.log(`Remaining: ${usage.remaining}`);
|
|
515
|
+
} else {
|
|
516
|
+
console.log("Usage: (unable to fetch)");
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
var init_status = __esm({
|
|
520
|
+
"src/commands/status.ts"() {
|
|
521
|
+
"use strict";
|
|
522
|
+
init_config();
|
|
523
|
+
init_api();
|
|
524
|
+
}
|
|
525
|
+
});
|
|
526
|
+
|
|
251
527
|
// ../../node_modules/.pnpm/yaml@2.8.4/node_modules/yaml/dist/nodes/identity.js
|
|
252
528
|
var require_identity = __commonJS({
|
|
253
529
|
"../../node_modules/.pnpm/yaml@2.8.4/node_modules/yaml/dist/nodes/identity.js"(exports2) {
|
|
@@ -7558,13 +7834,16 @@ var require_dist = __commonJS({
|
|
|
7558
7834
|
|
|
7559
7835
|
// src/git.ts
|
|
7560
7836
|
function validateRef(ref, name = "ref") {
|
|
7837
|
+
if (ref.startsWith("-")) {
|
|
7838
|
+
throw new Error(`Invalid git ref ${name}: starts with hyphen: "${ref}"`);
|
|
7839
|
+
}
|
|
7561
7840
|
if (!ref || !SAFE_GIT_REF.test(ref)) {
|
|
7562
7841
|
throw new Error(`Invalid git ref ${name}: "${ref}"`);
|
|
7563
7842
|
}
|
|
7564
7843
|
}
|
|
7565
7844
|
function isGitRepo() {
|
|
7566
7845
|
try {
|
|
7567
|
-
(0,
|
|
7846
|
+
(0, import_child_process2.execFileSync)("git", ["rev-parse", "--is-inside-work-tree"], {
|
|
7568
7847
|
stdio: "pipe"
|
|
7569
7848
|
});
|
|
7570
7849
|
return true;
|
|
@@ -7574,7 +7853,7 @@ function isGitRepo() {
|
|
|
7574
7853
|
}
|
|
7575
7854
|
function getGitRoot() {
|
|
7576
7855
|
try {
|
|
7577
|
-
return (0,
|
|
7856
|
+
return (0, import_child_process2.execFileSync)("git", ["rev-parse", "--show-toplevel"], {
|
|
7578
7857
|
encoding: "utf-8"
|
|
7579
7858
|
}).trim();
|
|
7580
7859
|
} catch {
|
|
@@ -7590,37 +7869,37 @@ function getStagedDiff(excludes = []) {
|
|
|
7590
7869
|
args.push(`:(exclude)${pattern}`);
|
|
7591
7870
|
}
|
|
7592
7871
|
}
|
|
7593
|
-
return (0,
|
|
7872
|
+
return (0, import_child_process2.execFileSync)("git", args, {
|
|
7594
7873
|
encoding: "utf-8",
|
|
7595
7874
|
maxBuffer: 10 * 1024 * 1024
|
|
7596
7875
|
});
|
|
7597
7876
|
}
|
|
7598
7877
|
function getStagedFiles() {
|
|
7599
|
-
return (0,
|
|
7878
|
+
return (0, import_child_process2.execFileSync)("git", ["diff", "--cached", "--name-only"], {
|
|
7600
7879
|
encoding: "utf-8"
|
|
7601
7880
|
});
|
|
7602
7881
|
}
|
|
7603
7882
|
function hasStagedChanges() {
|
|
7604
|
-
const output = (0,
|
|
7883
|
+
const output = (0, import_child_process2.execFileSync)("git", ["diff", "--cached", "--name-only"], {
|
|
7605
7884
|
encoding: "utf-8"
|
|
7606
7885
|
});
|
|
7607
7886
|
return output.trim().length > 0;
|
|
7608
7887
|
}
|
|
7609
7888
|
function getUnstagedFiles() {
|
|
7610
|
-
const output = (0,
|
|
7889
|
+
const output = (0, import_child_process2.execFileSync)("git", ["status", "--porcelain"], {
|
|
7611
7890
|
encoding: "utf-8"
|
|
7612
7891
|
});
|
|
7613
7892
|
return output.trim().split("\n").filter(Boolean).filter((line) => !line.startsWith("??"));
|
|
7614
7893
|
}
|
|
7615
7894
|
function stageAll() {
|
|
7616
|
-
(0,
|
|
7895
|
+
(0, import_child_process2.execFileSync)("git", ["add", "-u"], { stdio: "pipe" });
|
|
7617
7896
|
}
|
|
7618
7897
|
function gitCommit(message) {
|
|
7619
|
-
const tmpDir = (0, import_fs2.mkdtempSync)((0, import_path2.join)((0,
|
|
7898
|
+
const tmpDir = (0, import_fs2.mkdtempSync)((0, import_path2.join)((0, import_os3.tmpdir)(), "qc-"));
|
|
7620
7899
|
const tmpFile = (0, import_path2.join)(tmpDir, "commit.txt");
|
|
7621
7900
|
(0, import_fs2.writeFileSync)(tmpFile, message, { mode: 384 });
|
|
7622
7901
|
try {
|
|
7623
|
-
(0,
|
|
7902
|
+
(0, import_child_process2.execFileSync)("git", ["commit", "-F", tmpFile], { stdio: "inherit" });
|
|
7624
7903
|
} finally {
|
|
7625
7904
|
try {
|
|
7626
7905
|
(0, import_fs2.unlinkSync)(tmpFile);
|
|
@@ -7630,11 +7909,11 @@ function gitCommit(message) {
|
|
|
7630
7909
|
}
|
|
7631
7910
|
}
|
|
7632
7911
|
function gitPush() {
|
|
7633
|
-
(0,
|
|
7912
|
+
(0, import_child_process2.execFileSync)("git", ["push"], { stdio: "inherit" });
|
|
7634
7913
|
}
|
|
7635
7914
|
function getBranchCommits(base = "main") {
|
|
7636
7915
|
validateRef(base, "base");
|
|
7637
|
-
const output = (0,
|
|
7916
|
+
const output = (0, import_child_process2.execFileSync)("git", ["log", `${base}..HEAD`, "--format=%s", "--max-count=1000"], {
|
|
7638
7917
|
encoding: "utf-8",
|
|
7639
7918
|
maxBuffer: 10 * 1024 * 1024
|
|
7640
7919
|
});
|
|
@@ -7642,19 +7921,19 @@ function getBranchCommits(base = "main") {
|
|
|
7642
7921
|
}
|
|
7643
7922
|
function getDiffStat(base = "main") {
|
|
7644
7923
|
validateRef(base, "base");
|
|
7645
|
-
return (0,
|
|
7924
|
+
return (0, import_child_process2.execFileSync)("git", ["diff", `${base}..HEAD`, "--stat"], {
|
|
7646
7925
|
encoding: "utf-8",
|
|
7647
7926
|
maxBuffer: 10 * 1024 * 1024
|
|
7648
7927
|
});
|
|
7649
7928
|
}
|
|
7650
7929
|
function getCurrentBranch() {
|
|
7651
|
-
return (0,
|
|
7930
|
+
return (0, import_child_process2.execFileSync)("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
7652
7931
|
encoding: "utf-8"
|
|
7653
7932
|
}).trim();
|
|
7654
7933
|
}
|
|
7655
7934
|
function getLatestTag() {
|
|
7656
7935
|
try {
|
|
7657
|
-
return (0,
|
|
7936
|
+
return (0, import_child_process2.execFileSync)("git", ["describe", "--tags", "--abbrev=0"], {
|
|
7658
7937
|
encoding: "utf-8"
|
|
7659
7938
|
}).trim();
|
|
7660
7939
|
} catch {
|
|
@@ -7664,7 +7943,7 @@ function getLatestTag() {
|
|
|
7664
7943
|
function getCommitsSince(ref, to = "HEAD") {
|
|
7665
7944
|
validateRef(ref, "from ref");
|
|
7666
7945
|
validateRef(to, "to ref");
|
|
7667
|
-
const output = (0,
|
|
7946
|
+
const output = (0, import_child_process2.execFileSync)(
|
|
7668
7947
|
"git",
|
|
7669
7948
|
["log", `${ref}..${to}`, "--format=%H %s", "--max-count=1000"],
|
|
7670
7949
|
{ encoding: "utf-8", maxBuffer: 10 * 1024 * 1024 }
|
|
@@ -7676,7 +7955,7 @@ function getCommitsSince(ref, to = "HEAD") {
|
|
|
7676
7955
|
}
|
|
7677
7956
|
function getChangedFilesSince(base = "main") {
|
|
7678
7957
|
validateRef(base, "base");
|
|
7679
|
-
const output = (0,
|
|
7958
|
+
const output = (0, import_child_process2.execFileSync)("git", ["diff", `${base}..HEAD`, "--name-only"], {
|
|
7680
7959
|
encoding: "utf-8",
|
|
7681
7960
|
maxBuffer: 10 * 1024 * 1024
|
|
7682
7961
|
});
|
|
@@ -7684,7 +7963,7 @@ function getChangedFilesSince(base = "main") {
|
|
|
7684
7963
|
}
|
|
7685
7964
|
function getOnlineLog(base = "main") {
|
|
7686
7965
|
validateRef(base, "base");
|
|
7687
|
-
return (0,
|
|
7966
|
+
return (0, import_child_process2.execFileSync)(
|
|
7688
7967
|
"git",
|
|
7689
7968
|
["log", `${base}..HEAD`, "--oneline", "--max-count=200"],
|
|
7690
7969
|
{
|
|
@@ -7695,55 +7974,217 @@ function getOnlineLog(base = "main") {
|
|
|
7695
7974
|
}
|
|
7696
7975
|
function getFullDiff(base = "main") {
|
|
7697
7976
|
validateRef(base, "base");
|
|
7698
|
-
return (0,
|
|
7977
|
+
return (0, import_child_process2.execFileSync)("git", ["diff", `${base}..HEAD`], {
|
|
7699
7978
|
encoding: "utf-8",
|
|
7700
7979
|
maxBuffer: 10 * 1024 * 1024
|
|
7701
7980
|
});
|
|
7702
7981
|
}
|
|
7703
|
-
|
|
7704
|
-
|
|
7705
|
-
|
|
7706
|
-
|
|
7707
|
-
|
|
7708
|
-
|
|
7709
|
-
|
|
7710
|
-
|
|
7711
|
-
|
|
7712
|
-
|
|
7713
|
-
|
|
7714
|
-
|
|
7715
|
-
|
|
7716
|
-
|
|
7717
|
-
|
|
7718
|
-
const full = (0, import_path3.join)(root, file);
|
|
7719
|
-
if ((0, import_fs3.existsSync)(full)) return full;
|
|
7982
|
+
function getStagedDiffShortstat() {
|
|
7983
|
+
try {
|
|
7984
|
+
const out = (0, import_child_process2.execFileSync)("git", ["diff", "--cached", "--shortstat"], {
|
|
7985
|
+
encoding: "utf-8"
|
|
7986
|
+
}).trim();
|
|
7987
|
+
if (!out) return { additions: 0, deletions: 0 };
|
|
7988
|
+
let additions = 0;
|
|
7989
|
+
let deletions = 0;
|
|
7990
|
+
const ins = /(\d+) insertion/.exec(out);
|
|
7991
|
+
const del = /(\d+) deletion/.exec(out);
|
|
7992
|
+
if (ins?.[1]) additions = parseInt(ins[1], 10);
|
|
7993
|
+
if (del?.[1]) deletions = parseInt(del[1], 10);
|
|
7994
|
+
return { additions, deletions };
|
|
7995
|
+
} catch {
|
|
7996
|
+
return { additions: 0, deletions: 0 };
|
|
7720
7997
|
}
|
|
7721
|
-
return null;
|
|
7722
7998
|
}
|
|
7723
|
-
function
|
|
7724
|
-
const
|
|
7725
|
-
|
|
7726
|
-
|
|
7727
|
-
|
|
7728
|
-
return
|
|
7999
|
+
function getShortStagedFiles(max = 3) {
|
|
8000
|
+
const output = (0, import_child_process2.execFileSync)("git", ["diff", "--cached", "--name-only"], {
|
|
8001
|
+
encoding: "utf-8"
|
|
8002
|
+
});
|
|
8003
|
+
const all = output.trim().split("\n").filter(Boolean);
|
|
8004
|
+
return { files: all.slice(0, max), total: all.length };
|
|
7729
8005
|
}
|
|
7730
|
-
function
|
|
7731
|
-
|
|
7732
|
-
|
|
7733
|
-
|
|
7734
|
-
|
|
7735
|
-
|
|
7736
|
-
|
|
7737
|
-
|
|
7738
|
-
|
|
8006
|
+
function getCommitHash() {
|
|
8007
|
+
return (0, import_child_process2.execFileSync)("git", ["rev-parse", "--short", "HEAD"], {
|
|
8008
|
+
encoding: "utf-8"
|
|
8009
|
+
}).trim();
|
|
8010
|
+
}
|
|
8011
|
+
function getPushStats() {
|
|
8012
|
+
try {
|
|
8013
|
+
const branch = (0, import_child_process2.execFileSync)("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
8014
|
+
encoding: "utf-8"
|
|
8015
|
+
}).trim();
|
|
8016
|
+
const countOutput = (0, import_child_process2.execFileSync)(
|
|
8017
|
+
"git",
|
|
8018
|
+
["rev-list", "--count", `origin/${branch}..HEAD`],
|
|
8019
|
+
{ encoding: "utf-8" }
|
|
8020
|
+
).trim();
|
|
8021
|
+
const parsedCount = parseInt(countOutput, 10);
|
|
8022
|
+
const commits = Number.isFinite(parsedCount) ? parsedCount : 0;
|
|
8023
|
+
const stat = (0, import_child_process2.execFileSync)(
|
|
8024
|
+
"git",
|
|
8025
|
+
["diff", "--shortstat", `origin/${branch}..HEAD`],
|
|
8026
|
+
{ encoding: "utf-8" }
|
|
8027
|
+
).trim();
|
|
8028
|
+
return { commits, stat };
|
|
8029
|
+
} catch {
|
|
8030
|
+
return null;
|
|
7739
8031
|
}
|
|
7740
|
-
|
|
7741
|
-
|
|
7742
|
-
|
|
8032
|
+
}
|
|
8033
|
+
function getRecentBranchCommits(count = 5) {
|
|
8034
|
+
try {
|
|
8035
|
+
const output = (0, import_child_process2.execFileSync)(
|
|
8036
|
+
"git",
|
|
8037
|
+
["log", "--format=%s%n%b%n---", `--max-count=${count}`, "HEAD"],
|
|
8038
|
+
{ encoding: "utf-8", maxBuffer: 1024 * 1024 }
|
|
8039
|
+
);
|
|
8040
|
+
return output.split("---\n").map((entry) => entry.trim()).filter(Boolean).slice(0, count);
|
|
8041
|
+
} catch {
|
|
8042
|
+
return [];
|
|
7743
8043
|
}
|
|
7744
|
-
|
|
7745
|
-
|
|
7746
|
-
|
|
8044
|
+
}
|
|
8045
|
+
function getCommitsAheadOfUpstream(branch, upstream) {
|
|
8046
|
+
validateRef(branch, "branch");
|
|
8047
|
+
const target = upstream ?? `origin/${branch}`;
|
|
8048
|
+
validateRef(target, "upstream");
|
|
8049
|
+
try {
|
|
8050
|
+
const out = (0, import_child_process2.execFileSync)(
|
|
8051
|
+
"git",
|
|
8052
|
+
["rev-list", "--count", `${target}..HEAD`],
|
|
8053
|
+
{ encoding: "utf-8" }
|
|
8054
|
+
).trim();
|
|
8055
|
+
const n = parseInt(out, 10);
|
|
8056
|
+
return Number.isFinite(n) ? n : 0;
|
|
8057
|
+
} catch {
|
|
8058
|
+
return 0;
|
|
8059
|
+
}
|
|
8060
|
+
}
|
|
8061
|
+
function getUpstreamRef(branch) {
|
|
8062
|
+
validateRef(branch, "branch");
|
|
8063
|
+
try {
|
|
8064
|
+
return (0, import_child_process2.execFileSync)(
|
|
8065
|
+
"git",
|
|
8066
|
+
["rev-parse", "--abbrev-ref", `${branch}@{upstream}`],
|
|
8067
|
+
{ encoding: "utf-8" }
|
|
8068
|
+
).trim() || null;
|
|
8069
|
+
} catch {
|
|
8070
|
+
return null;
|
|
8071
|
+
}
|
|
8072
|
+
}
|
|
8073
|
+
function getDefaultBranch() {
|
|
8074
|
+
try {
|
|
8075
|
+
const out = (0, import_child_process2.execFileSync)(
|
|
8076
|
+
"git",
|
|
8077
|
+
["symbolic-ref", "refs/remotes/origin/HEAD"],
|
|
8078
|
+
{ encoding: "utf-8" }
|
|
8079
|
+
).trim();
|
|
8080
|
+
const segments = out.split("/");
|
|
8081
|
+
return segments[segments.length - 1] || null;
|
|
8082
|
+
} catch {
|
|
8083
|
+
return null;
|
|
8084
|
+
}
|
|
8085
|
+
}
|
|
8086
|
+
function branchExists(name) {
|
|
8087
|
+
validateRef(name, "branch");
|
|
8088
|
+
try {
|
|
8089
|
+
(0, import_child_process2.execFileSync)("git", ["show-ref", "--verify", "--quiet", `refs/heads/${name}`], {
|
|
8090
|
+
stdio: "pipe"
|
|
8091
|
+
});
|
|
8092
|
+
return true;
|
|
8093
|
+
} catch {
|
|
8094
|
+
}
|
|
8095
|
+
try {
|
|
8096
|
+
(0, import_child_process2.execFileSync)("git", ["show-ref", "--verify", "--quiet", `refs/remotes/origin/${name}`], {
|
|
8097
|
+
stdio: "pipe"
|
|
8098
|
+
});
|
|
8099
|
+
return true;
|
|
8100
|
+
} catch {
|
|
8101
|
+
return false;
|
|
8102
|
+
}
|
|
8103
|
+
}
|
|
8104
|
+
function stashPushIfDirty(message) {
|
|
8105
|
+
const status = (0, import_child_process2.execFileSync)("git", ["status", "--porcelain"], { encoding: "utf-8" }).trim();
|
|
8106
|
+
if (!status) return false;
|
|
8107
|
+
(0, import_child_process2.execFileSync)("git", ["stash", "push", "--include-untracked", "--message", message], {
|
|
8108
|
+
stdio: "pipe"
|
|
8109
|
+
});
|
|
8110
|
+
return true;
|
|
8111
|
+
}
|
|
8112
|
+
function stashPop() {
|
|
8113
|
+
(0, import_child_process2.execFileSync)("git", ["stash", "pop"], { stdio: "pipe" });
|
|
8114
|
+
}
|
|
8115
|
+
function resetHard(ref) {
|
|
8116
|
+
validateRef(ref, "ref");
|
|
8117
|
+
(0, import_child_process2.execFileSync)("git", ["reset", "--hard", ref], { stdio: "pipe" });
|
|
8118
|
+
}
|
|
8119
|
+
function createBranch(name, base = "HEAD") {
|
|
8120
|
+
validateRef(name, "name");
|
|
8121
|
+
validateRef(base, "base");
|
|
8122
|
+
(0, import_child_process2.execFileSync)("git", ["branch", name, base], { stdio: "pipe" });
|
|
8123
|
+
}
|
|
8124
|
+
function checkoutBranch(name) {
|
|
8125
|
+
validateRef(name, "name");
|
|
8126
|
+
(0, import_child_process2.execFileSync)("git", ["checkout", name], { stdio: "pipe" });
|
|
8127
|
+
}
|
|
8128
|
+
function createAndCheckoutBranch(name, base = "HEAD") {
|
|
8129
|
+
validateRef(name, "name");
|
|
8130
|
+
validateRef(base, "base");
|
|
8131
|
+
(0, import_child_process2.execFileSync)("git", ["checkout", "-b", name, base], { stdio: "pipe" });
|
|
8132
|
+
}
|
|
8133
|
+
function getHeadSha() {
|
|
8134
|
+
return (0, import_child_process2.execFileSync)("git", ["rev-parse", "HEAD"], { encoding: "utf-8" }).trim();
|
|
8135
|
+
}
|
|
8136
|
+
function gitPushSetUpstream(branch) {
|
|
8137
|
+
validateRef(branch, "branch");
|
|
8138
|
+
(0, import_child_process2.execFileSync)("git", ["push", "-u", "origin", branch], { stdio: "inherit" });
|
|
8139
|
+
}
|
|
8140
|
+
function deleteBranch(name) {
|
|
8141
|
+
validateRef(name, "name");
|
|
8142
|
+
(0, import_child_process2.execFileSync)("git", ["branch", "-D", name], { stdio: "pipe" });
|
|
8143
|
+
}
|
|
8144
|
+
var import_child_process2, import_fs2, import_path2, import_os3, SAFE_GIT_REF;
|
|
8145
|
+
var init_git = __esm({
|
|
8146
|
+
"src/git.ts"() {
|
|
8147
|
+
"use strict";
|
|
8148
|
+
import_child_process2 = require("child_process");
|
|
8149
|
+
import_fs2 = require("fs");
|
|
8150
|
+
import_path2 = require("path");
|
|
8151
|
+
import_os3 = require("os");
|
|
8152
|
+
SAFE_GIT_REF = /^[a-zA-Z0-9][a-zA-Z0-9._\-/~:^@]*$/;
|
|
8153
|
+
}
|
|
8154
|
+
});
|
|
8155
|
+
|
|
8156
|
+
// src/commitlint.ts
|
|
8157
|
+
function findConfigFile(root) {
|
|
8158
|
+
for (const file of CONFIG_FILES) {
|
|
8159
|
+
const full = (0, import_path3.join)(root, file);
|
|
8160
|
+
if ((0, import_fs3.existsSync)(full)) return full;
|
|
8161
|
+
}
|
|
8162
|
+
return null;
|
|
8163
|
+
}
|
|
8164
|
+
function getRule(rules, name) {
|
|
8165
|
+
const r = rules[name];
|
|
8166
|
+
if (!Array.isArray(r) || r.length < 3) return null;
|
|
8167
|
+
const [severity, applicability, value] = r;
|
|
8168
|
+
if (typeof severity !== "number" || severity < 1) return null;
|
|
8169
|
+
return [severity, applicability, value];
|
|
8170
|
+
}
|
|
8171
|
+
function mapRules(rules) {
|
|
8172
|
+
const result = {};
|
|
8173
|
+
const typeEnum = getRule(rules, "type-enum");
|
|
8174
|
+
if (typeEnum && typeEnum[1] === "always" && Array.isArray(typeEnum[2])) {
|
|
8175
|
+
result.types = typeEnum[2].filter((t) => typeof t === "string");
|
|
8176
|
+
}
|
|
8177
|
+
const scopeEnum = getRule(rules, "scope-enum");
|
|
8178
|
+
if (scopeEnum && scopeEnum[1] === "always" && Array.isArray(scopeEnum[2])) {
|
|
8179
|
+
result.scopes = scopeEnum[2].filter((s) => typeof s === "string");
|
|
8180
|
+
}
|
|
8181
|
+
const headerMax = getRule(rules, "header-max-length");
|
|
8182
|
+
if (headerMax && headerMax[1] === "always" && typeof headerMax[2] === "number") {
|
|
8183
|
+
result.headerMaxLength = headerMax[2];
|
|
8184
|
+
}
|
|
8185
|
+
const subjectMax = getRule(rules, "subject-max-length");
|
|
8186
|
+
if (subjectMax && subjectMax[1] === "always" && typeof subjectMax[2] === "number") {
|
|
8187
|
+
result.subjectMaxLength = subjectMax[2];
|
|
7747
8188
|
}
|
|
7748
8189
|
const bodyMaxLine = getRule(rules, "body-max-line-length");
|
|
7749
8190
|
if (bodyMaxLine && bodyMaxLine[1] === "always" && typeof bodyMaxLine[2] === "number") {
|
|
@@ -7774,7 +8215,7 @@ function mapRulesToCommitRules(rules) {
|
|
|
7774
8215
|
}
|
|
7775
8216
|
function tryNpxPrintConfig(root) {
|
|
7776
8217
|
try {
|
|
7777
|
-
const output = (0,
|
|
8218
|
+
const output = (0, import_child_process3.execFileSync)("npx", ["--no", "commitlint", "--print-config"], {
|
|
7778
8219
|
encoding: "utf-8",
|
|
7779
8220
|
cwd: root,
|
|
7780
8221
|
timeout: 1e4,
|
|
@@ -7791,7 +8232,7 @@ function tryNodeEval(configPath) {
|
|
|
7791
8232
|
const fileUrl = (0, import_node_url.pathToFileURL)(configPath).href;
|
|
7792
8233
|
const script = `import cfg from ${JSON.stringify(fileUrl)}; process.stdout.write(JSON.stringify(cfg.default ?? cfg));`;
|
|
7793
8234
|
try {
|
|
7794
|
-
const output = (0,
|
|
8235
|
+
const output = (0, import_child_process3.execFileSync)("node", ["--input-type=module"], {
|
|
7795
8236
|
input: script,
|
|
7796
8237
|
encoding: "utf-8",
|
|
7797
8238
|
timeout: 1e4,
|
|
@@ -7808,7 +8249,7 @@ function tryNodeEvalTs(configPath, root) {
|
|
|
7808
8249
|
const fileUrl = (0, import_node_url.pathToFileURL)(configPath).href;
|
|
7809
8250
|
const script = `import cfg from ${JSON.stringify(fileUrl)}; process.stdout.write(JSON.stringify(cfg.default ?? cfg));`;
|
|
7810
8251
|
try {
|
|
7811
|
-
const output = (0,
|
|
8252
|
+
const output = (0, import_child_process3.execFileSync)("node", ["--experimental-strip-types", "--input-type=module"], {
|
|
7812
8253
|
input: script,
|
|
7813
8254
|
encoding: "utf-8",
|
|
7814
8255
|
cwd: root,
|
|
@@ -7822,7 +8263,7 @@ function tryNodeEvalTs(configPath, root) {
|
|
|
7822
8263
|
}
|
|
7823
8264
|
try {
|
|
7824
8265
|
const tsxScript = `import cfg from ${JSON.stringify(fileUrl)}; console.log(JSON.stringify(cfg.default ?? cfg));`;
|
|
7825
|
-
const output = (0,
|
|
8266
|
+
const output = (0, import_child_process3.execFileSync)("npx", ["--no", "tsx", "-e", tsxScript], {
|
|
7826
8267
|
encoding: "utf-8",
|
|
7827
8268
|
cwd: root,
|
|
7828
8269
|
timeout: 15e3,
|
|
@@ -7875,11 +8316,11 @@ async function detectCommitlintRules() {
|
|
|
7875
8316
|
return void 0;
|
|
7876
8317
|
}
|
|
7877
8318
|
}
|
|
7878
|
-
var
|
|
8319
|
+
var import_child_process3, import_fs3, import_path3, import_node_url, import_yaml, CONFIG_FILES;
|
|
7879
8320
|
var init_commitlint = __esm({
|
|
7880
8321
|
"src/commitlint.ts"() {
|
|
7881
8322
|
"use strict";
|
|
7882
|
-
|
|
8323
|
+
import_child_process3 = require("child_process");
|
|
7883
8324
|
import_fs3 = require("fs");
|
|
7884
8325
|
import_path3 = require("path");
|
|
7885
8326
|
import_node_url = require("node:url");
|
|
@@ -7900,6 +8341,183 @@ var init_commitlint = __esm({
|
|
|
7900
8341
|
}
|
|
7901
8342
|
});
|
|
7902
8343
|
|
|
8344
|
+
// src/commands/pr.ts
|
|
8345
|
+
var pr_exports = {};
|
|
8346
|
+
__export(pr_exports, {
|
|
8347
|
+
pr: () => pr
|
|
8348
|
+
});
|
|
8349
|
+
function findPullRequestTemplate(gitRoot) {
|
|
8350
|
+
const fileCandidates = [
|
|
8351
|
+
(0, import_path4.join)(gitRoot, ".github", "pull_request_template.md"),
|
|
8352
|
+
(0, import_path4.join)(gitRoot, ".github", "PULL_REQUEST_TEMPLATE.md"),
|
|
8353
|
+
(0, import_path4.join)(gitRoot, "pull_request_template.md")
|
|
8354
|
+
];
|
|
8355
|
+
for (const p of fileCandidates) {
|
|
8356
|
+
try {
|
|
8357
|
+
if ((0, import_fs4.existsSync)(p) && (0, import_fs4.statSync)(p).isFile()) {
|
|
8358
|
+
return { path: p, content: (0, import_fs4.readFileSync)(p, "utf-8") };
|
|
8359
|
+
}
|
|
8360
|
+
} catch {
|
|
8361
|
+
}
|
|
8362
|
+
}
|
|
8363
|
+
const multiDir = (0, import_path4.join)(gitRoot, ".github", "PULL_REQUEST_TEMPLATE");
|
|
8364
|
+
try {
|
|
8365
|
+
if ((0, import_fs4.existsSync)(multiDir) && (0, import_fs4.statSync)(multiDir).isDirectory()) {
|
|
8366
|
+
const names = (0, import_fs4.readdirSync)(multiDir).filter((f) => f.toLowerCase().endsWith(".md")).sort();
|
|
8367
|
+
if (names.length > 0) {
|
|
8368
|
+
const p = (0, import_path4.join)(multiDir, names[0]);
|
|
8369
|
+
if ((0, import_fs4.statSync)(p).isFile()) {
|
|
8370
|
+
return { path: p, content: (0, import_fs4.readFileSync)(p, "utf-8") };
|
|
8371
|
+
}
|
|
8372
|
+
}
|
|
8373
|
+
}
|
|
8374
|
+
} catch {
|
|
8375
|
+
}
|
|
8376
|
+
return void 0;
|
|
8377
|
+
}
|
|
8378
|
+
async function pr(options) {
|
|
8379
|
+
const base = options.base ?? "main";
|
|
8380
|
+
const commits = getBranchCommits(base);
|
|
8381
|
+
const diffStat = getDiffStat(base);
|
|
8382
|
+
const gitRoot = getGitRoot();
|
|
8383
|
+
const templateHit = findPullRequestTemplate(gitRoot);
|
|
8384
|
+
let prTemplate;
|
|
8385
|
+
if (templateHit) {
|
|
8386
|
+
prTemplate = templateHit.content.substring(0, 16 * 1024);
|
|
8387
|
+
console.error(`[qc] Using PR template from ${(0, import_path4.relative)(gitRoot, templateHit.path)}`);
|
|
8388
|
+
}
|
|
8389
|
+
const currentBranch = getCurrentBranch().slice(0, MAX_PR_CURRENT_BRANCH_CHARS);
|
|
8390
|
+
if (commits.length === 0) {
|
|
8391
|
+
console.error(`No commits found on this branch vs ${base}`);
|
|
8392
|
+
process.exit(1);
|
|
8393
|
+
}
|
|
8394
|
+
const commitlintRules = await detectCommitlintRules();
|
|
8395
|
+
console.error(`Generating PR description from ${commits.length} commits...`);
|
|
8396
|
+
const apiKey = getApiKey();
|
|
8397
|
+
if (!apiKey) {
|
|
8398
|
+
console.error("Error: Not authenticated. Run `qc login` first.");
|
|
8399
|
+
process.exit(1);
|
|
8400
|
+
}
|
|
8401
|
+
const client = new ApiClient({ apiKey });
|
|
8402
|
+
const result = await client.generatePR(
|
|
8403
|
+
{
|
|
8404
|
+
commits,
|
|
8405
|
+
diff_stat: diffStat,
|
|
8406
|
+
base_branch: base,
|
|
8407
|
+
current_branch: currentBranch,
|
|
8408
|
+
pr_template: prTemplate,
|
|
8409
|
+
rules: commitlintRules
|
|
8410
|
+
},
|
|
8411
|
+
options.model
|
|
8412
|
+
);
|
|
8413
|
+
const trimmedTitle = result.title.trim();
|
|
8414
|
+
if (trimmedTitle) {
|
|
8415
|
+
console.log(`
|
|
8416
|
+
Title: ${trimmedTitle}
|
|
8417
|
+
`);
|
|
8418
|
+
}
|
|
8419
|
+
console.log(result.message + "\n");
|
|
8420
|
+
if (options.create) {
|
|
8421
|
+
try {
|
|
8422
|
+
const prTitle = trimmedTitle || result.message.split("\n").find((l) => l.trim()) || result.message.substring(0, 72).trim();
|
|
8423
|
+
(0, import_child_process4.execFileSync)("gh", ["pr", "create", "--title", prTitle, "--body", result.message], {
|
|
8424
|
+
stdio: "inherit"
|
|
8425
|
+
});
|
|
8426
|
+
} catch {
|
|
8427
|
+
console.error("Error: `gh` CLI not found or failed. Install from https://cli.github.com/");
|
|
8428
|
+
process.exit(1);
|
|
8429
|
+
}
|
|
8430
|
+
}
|
|
8431
|
+
}
|
|
8432
|
+
var import_child_process4, import_fs4, import_path4;
|
|
8433
|
+
var init_pr = __esm({
|
|
8434
|
+
"src/commands/pr.ts"() {
|
|
8435
|
+
"use strict";
|
|
8436
|
+
import_child_process4 = require("child_process");
|
|
8437
|
+
import_fs4 = require("fs");
|
|
8438
|
+
import_path4 = require("path");
|
|
8439
|
+
init_dist();
|
|
8440
|
+
init_config();
|
|
8441
|
+
init_api();
|
|
8442
|
+
init_commitlint();
|
|
8443
|
+
init_git();
|
|
8444
|
+
}
|
|
8445
|
+
});
|
|
8446
|
+
|
|
8447
|
+
// src/commands/changelog.ts
|
|
8448
|
+
var changelog_exports = {};
|
|
8449
|
+
__export(changelog_exports, {
|
|
8450
|
+
changelog: () => changelog
|
|
8451
|
+
});
|
|
8452
|
+
function parseCommitType(subject) {
|
|
8453
|
+
const match = subject.match(CONVENTIONAL_TYPE_RE);
|
|
8454
|
+
return match ? match[1].toLowerCase() : "chore";
|
|
8455
|
+
}
|
|
8456
|
+
function groupCommitsByType(commits) {
|
|
8457
|
+
const byType = {};
|
|
8458
|
+
for (const { subject } of commits) {
|
|
8459
|
+
const type = parseCommitType(subject);
|
|
8460
|
+
if (!byType[type]) byType[type] = [];
|
|
8461
|
+
byType[type].push(subject);
|
|
8462
|
+
}
|
|
8463
|
+
return byType;
|
|
8464
|
+
}
|
|
8465
|
+
async function changelog(options) {
|
|
8466
|
+
const fromRef = options.from ?? getLatestTag();
|
|
8467
|
+
const toRef = options.to ?? "HEAD";
|
|
8468
|
+
if (!fromRef) {
|
|
8469
|
+
console.error("Error: No git tag found. Use --from <ref> to specify a starting point.");
|
|
8470
|
+
process.exit(1);
|
|
8471
|
+
}
|
|
8472
|
+
const commits = getCommitsSince(fromRef, toRef);
|
|
8473
|
+
if (commits.length === 0) {
|
|
8474
|
+
console.error(`No commits found between ${fromRef} and ${toRef}`);
|
|
8475
|
+
process.exit(1);
|
|
8476
|
+
}
|
|
8477
|
+
const commitsByType = groupCommitsByType(commits);
|
|
8478
|
+
const apiKey = getApiKey();
|
|
8479
|
+
if (!apiKey) {
|
|
8480
|
+
console.error("Error: Not authenticated. Run `qc login` first.");
|
|
8481
|
+
process.exit(1);
|
|
8482
|
+
}
|
|
8483
|
+
const client = new ApiClient({ apiKey });
|
|
8484
|
+
const result = await client.generateChangelog(
|
|
8485
|
+
{
|
|
8486
|
+
commits_by_type: commitsByType,
|
|
8487
|
+
from_tag: fromRef,
|
|
8488
|
+
to_ref: toRef
|
|
8489
|
+
},
|
|
8490
|
+
options.model
|
|
8491
|
+
);
|
|
8492
|
+
const version = options.version ?? (/^v?\d/.test(toRef) && toRef !== "HEAD" ? toRef.replace(/^v/, "") : null) ?? `${fromRef.replace(/^v/, "")}-next`;
|
|
8493
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
8494
|
+
const header = `## [${version}] - ${date}
|
|
8495
|
+
|
|
8496
|
+
`;
|
|
8497
|
+
const changelogEntry = header + result.message;
|
|
8498
|
+
if (options.write) {
|
|
8499
|
+
const path = (0, import_path5.join)(getGitRoot(), "CHANGELOG.md");
|
|
8500
|
+
const existing = (0, import_fs5.existsSync)(path) ? (0, import_fs5.readFileSync)(path, "utf-8") : "";
|
|
8501
|
+
const newContent = changelogEntry + (existing ? "\n\n" + existing : "");
|
|
8502
|
+
(0, import_fs5.writeFileSync)(path, newContent);
|
|
8503
|
+
console.error(`Wrote to ${path}`);
|
|
8504
|
+
} else {
|
|
8505
|
+
console.log(changelogEntry);
|
|
8506
|
+
}
|
|
8507
|
+
}
|
|
8508
|
+
var import_fs5, import_path5, CONVENTIONAL_TYPE_RE;
|
|
8509
|
+
var init_changelog = __esm({
|
|
8510
|
+
"src/commands/changelog.ts"() {
|
|
8511
|
+
"use strict";
|
|
8512
|
+
import_fs5 = require("fs");
|
|
8513
|
+
import_path5 = require("path");
|
|
8514
|
+
init_config();
|
|
8515
|
+
init_api();
|
|
8516
|
+
init_git();
|
|
8517
|
+
CONVENTIONAL_TYPE_RE = /^(feat|fix|docs|style|refactor|perf|test|chore)(\([^)]+\))?!?:\s+/i;
|
|
8518
|
+
}
|
|
8519
|
+
});
|
|
8520
|
+
|
|
7903
8521
|
// src/monorepo.ts
|
|
7904
8522
|
var monorepo_exports = {};
|
|
7905
8523
|
__export(monorepo_exports, {
|
|
@@ -7909,7 +8527,7 @@ __export(monorepo_exports, {
|
|
|
7909
8527
|
});
|
|
7910
8528
|
function findGitRoot(start) {
|
|
7911
8529
|
try {
|
|
7912
|
-
return (0,
|
|
8530
|
+
return (0, import_child_process5.execFileSync)("git", ["rev-parse", "--show-toplevel"], {
|
|
7913
8531
|
encoding: "utf-8",
|
|
7914
8532
|
cwd: start,
|
|
7915
8533
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -7919,19 +8537,19 @@ function findGitRoot(start) {
|
|
|
7919
8537
|
}
|
|
7920
8538
|
}
|
|
7921
8539
|
function detectWorkspace(cwd = findGitRoot(process.cwd())) {
|
|
7922
|
-
const pnpmWs = (0,
|
|
7923
|
-
if ((0,
|
|
7924
|
-
const content = (0,
|
|
8540
|
+
const pnpmWs = (0, import_path6.join)(cwd, "pnpm-workspace.yaml");
|
|
8541
|
+
if ((0, import_fs6.existsSync)(pnpmWs)) {
|
|
8542
|
+
const content = (0, import_fs6.readFileSync)(pnpmWs, "utf-8");
|
|
7925
8543
|
const match = content.match(/packages:\s*\n((?:\s+-\s+.+\n?)*)/);
|
|
7926
8544
|
if (match) {
|
|
7927
8545
|
const packages = match[1].split("\n").map((l) => l.replace(/^\s+-\s+/, "").replace(/["']/g, "").trim()).filter(Boolean);
|
|
7928
8546
|
return { type: "pnpm", packages, root: cwd };
|
|
7929
8547
|
}
|
|
7930
8548
|
}
|
|
7931
|
-
const lerna = (0,
|
|
7932
|
-
if ((0,
|
|
8549
|
+
const lerna = (0, import_path6.join)(cwd, "lerna.json");
|
|
8550
|
+
if ((0, import_fs6.existsSync)(lerna)) {
|
|
7933
8551
|
try {
|
|
7934
|
-
const config2 = JSON.parse((0,
|
|
8552
|
+
const config2 = JSON.parse((0, import_fs6.readFileSync)(lerna, "utf-8"));
|
|
7935
8553
|
return {
|
|
7936
8554
|
type: "lerna",
|
|
7937
8555
|
packages: config2.packages ?? ["packages/*"],
|
|
@@ -7940,18 +8558,18 @@ function detectWorkspace(cwd = findGitRoot(process.cwd())) {
|
|
|
7940
8558
|
} catch {
|
|
7941
8559
|
}
|
|
7942
8560
|
}
|
|
7943
|
-
if ((0,
|
|
8561
|
+
if ((0, import_fs6.existsSync)((0, import_path6.join)(cwd, "nx.json"))) {
|
|
7944
8562
|
return {
|
|
7945
8563
|
type: "nx",
|
|
7946
8564
|
packages: ["packages/*", "apps/*", "libs/*"],
|
|
7947
8565
|
root: cwd
|
|
7948
8566
|
};
|
|
7949
8567
|
}
|
|
7950
|
-
if ((0,
|
|
7951
|
-
const pkgPath2 = (0,
|
|
7952
|
-
if ((0,
|
|
8568
|
+
if ((0, import_fs6.existsSync)((0, import_path6.join)(cwd, "turbo.json"))) {
|
|
8569
|
+
const pkgPath2 = (0, import_path6.join)(cwd, "package.json");
|
|
8570
|
+
if ((0, import_fs6.existsSync)(pkgPath2)) {
|
|
7953
8571
|
try {
|
|
7954
|
-
const config2 = JSON.parse((0,
|
|
8572
|
+
const config2 = JSON.parse((0, import_fs6.readFileSync)(pkgPath2, "utf-8"));
|
|
7955
8573
|
if (config2.workspaces) {
|
|
7956
8574
|
const ws = Array.isArray(config2.workspaces) ? config2.workspaces : config2.workspaces.packages ?? [];
|
|
7957
8575
|
return { type: "turbo", packages: ws, root: cwd };
|
|
@@ -7960,10 +8578,10 @@ function detectWorkspace(cwd = findGitRoot(process.cwd())) {
|
|
|
7960
8578
|
}
|
|
7961
8579
|
}
|
|
7962
8580
|
}
|
|
7963
|
-
const pkgPath = (0,
|
|
7964
|
-
if ((0,
|
|
8581
|
+
const pkgPath = (0, import_path6.join)(cwd, "package.json");
|
|
8582
|
+
if ((0, import_fs6.existsSync)(pkgPath)) {
|
|
7965
8583
|
try {
|
|
7966
|
-
const config2 = JSON.parse((0,
|
|
8584
|
+
const config2 = JSON.parse((0, import_fs6.readFileSync)(pkgPath, "utf-8"));
|
|
7967
8585
|
if (config2.workspaces) {
|
|
7968
8586
|
const ws = Array.isArray(config2.workspaces) ? config2.workspaces : config2.workspaces.packages ?? [];
|
|
7969
8587
|
return { type: "npm", packages: ws, root: cwd };
|
|
@@ -8006,8 +8624,8 @@ function matchGlobPattern(rel, pattern) {
|
|
|
8006
8624
|
return null;
|
|
8007
8625
|
}
|
|
8008
8626
|
function getPackageForFile(filePath, workspace) {
|
|
8009
|
-
const absPath = filePath.startsWith("/") ? filePath : (0,
|
|
8010
|
-
const rel = (0,
|
|
8627
|
+
const absPath = filePath.startsWith("/") ? filePath : (0, import_path6.join)(workspace.root, filePath);
|
|
8628
|
+
const rel = (0, import_path6.relative)(workspace.root, absPath);
|
|
8011
8629
|
for (const pattern of workspace.packages) {
|
|
8012
8630
|
const packageName = matchGlobPattern(rel, pattern);
|
|
8013
8631
|
if (packageName) return packageName;
|
|
@@ -8017,7 +8635,7 @@ function getPackageForFile(filePath, workspace) {
|
|
|
8017
8635
|
function autoDetectScope(stagedFiles, workspace) {
|
|
8018
8636
|
const packages = /* @__PURE__ */ new Set();
|
|
8019
8637
|
for (const file of stagedFiles) {
|
|
8020
|
-
const filePath = file.startsWith("/") ? file : (0,
|
|
8638
|
+
const filePath = file.startsWith("/") ? file : (0, import_path6.join)(workspace.root, file);
|
|
8021
8639
|
const pkg = getPackageForFile(filePath, workspace);
|
|
8022
8640
|
if (pkg) packages.add(pkg);
|
|
8023
8641
|
}
|
|
@@ -8030,477 +8648,126 @@ function autoDetectScope(stagedFiles, workspace) {
|
|
|
8030
8648
|
}
|
|
8031
8649
|
return null;
|
|
8032
8650
|
}
|
|
8033
|
-
var
|
|
8651
|
+
var import_child_process5, import_fs6, import_path6;
|
|
8034
8652
|
var init_monorepo = __esm({
|
|
8035
8653
|
"src/monorepo.ts"() {
|
|
8036
8654
|
"use strict";
|
|
8037
|
-
|
|
8038
|
-
|
|
8039
|
-
|
|
8655
|
+
import_child_process5 = require("child_process");
|
|
8656
|
+
import_fs6 = require("fs");
|
|
8657
|
+
import_path6 = require("path");
|
|
8040
8658
|
}
|
|
8041
8659
|
});
|
|
8042
8660
|
|
|
8043
|
-
// src/commands/
|
|
8044
|
-
var
|
|
8045
|
-
__export(
|
|
8046
|
-
|
|
8047
|
-
|
|
8048
|
-
|
|
8049
|
-
|
|
8050
|
-
|
|
8051
|
-
|
|
8052
|
-
|
|
8053
|
-
|
|
8054
|
-
if ((0, import_os3.platform)() === "linux") {
|
|
8055
|
-
(0, import_child_process4.execFileSync)("xdg-open", [url], { stdio: "pipe" });
|
|
8056
|
-
return true;
|
|
8057
|
-
}
|
|
8058
|
-
if ((0, import_os3.platform)() === "win32") {
|
|
8059
|
-
(0, import_child_process4.execFileSync)("cmd", ["/c", "start", "", url], { stdio: "pipe" });
|
|
8060
|
-
return true;
|
|
8061
|
-
}
|
|
8062
|
-
} catch {
|
|
8063
|
-
}
|
|
8064
|
-
return false;
|
|
8661
|
+
// src/commands/changeset.ts
|
|
8662
|
+
var changeset_exports = {};
|
|
8663
|
+
__export(changeset_exports, {
|
|
8664
|
+
changeset: () => changeset,
|
|
8665
|
+
formatChangesetFile: () => formatChangesetFile,
|
|
8666
|
+
generateSlug: () => generateSlug,
|
|
8667
|
+
mapFilesToPackages: () => mapFilesToPackages
|
|
8668
|
+
});
|
|
8669
|
+
function generateSlug() {
|
|
8670
|
+
const pick = (arr) => arr[Math.floor(Math.random() * arr.length)];
|
|
8671
|
+
return `${pick(ADJECTIVES)}-${pick(ANIMALS)}-${pick(VERBS)}`;
|
|
8065
8672
|
}
|
|
8066
|
-
|
|
8067
|
-
const
|
|
8068
|
-
|
|
8069
|
-
|
|
8070
|
-
|
|
8071
|
-
|
|
8072
|
-
|
|
8073
|
-
|
|
8074
|
-
|
|
8075
|
-
}
|
|
8076
|
-
const codeData = await codeRes.json();
|
|
8077
|
-
const { device_code, user_code, verification_uri_complete, interval = 5 } = codeData;
|
|
8078
|
-
if (!device_code || !user_code) {
|
|
8079
|
-
throw new Error("Server did not return device codes");
|
|
8080
|
-
}
|
|
8081
|
-
console.log("Opening browser to sign in...");
|
|
8082
|
-
console.log("");
|
|
8083
|
-
console.log(` Your code: ${user_code}`);
|
|
8084
|
-
console.log("");
|
|
8085
|
-
const authUrl = verification_uri_complete ?? `${DASHBOARD_URL}/device?user_code=${encodeURIComponent(user_code)}`;
|
|
8086
|
-
const opened = openBrowser(authUrl);
|
|
8087
|
-
if (!opened) {
|
|
8088
|
-
console.log("Could not open browser. Please visit:");
|
|
8089
|
-
console.log(authUrl);
|
|
8090
|
-
console.log("");
|
|
8091
|
-
}
|
|
8092
|
-
let frame = 0;
|
|
8093
|
-
const spinner = setInterval(() => {
|
|
8094
|
-
const elapsed = Math.floor((Date.now() - startTime) / 1e3);
|
|
8095
|
-
process.stderr.write(
|
|
8096
|
-
`\r${SPINNER_FRAMES[frame++ % SPINNER_FRAMES.length]} Waiting for authorization... (${elapsed}s)`
|
|
8097
|
-
);
|
|
8098
|
-
}, 80);
|
|
8099
|
-
let pollingInterval = interval * 1e3;
|
|
8100
|
-
const startTime = Date.now();
|
|
8101
|
-
try {
|
|
8102
|
-
while (Date.now() - startTime < DEVICE_FLOW_TIMEOUT) {
|
|
8103
|
-
await new Promise((r) => setTimeout(r, pollingInterval));
|
|
8673
|
+
function mapFilesToPackages(files, workspace) {
|
|
8674
|
+
const dirToName = /* @__PURE__ */ new Map();
|
|
8675
|
+
for (const file of files) {
|
|
8676
|
+
const dirName = getPackageForFile(file, workspace);
|
|
8677
|
+
if (!dirName || dirToName.has(dirName)) continue;
|
|
8678
|
+
for (const pattern of workspace.packages) {
|
|
8679
|
+
const hasGlob = /\*/.test(pattern);
|
|
8680
|
+
const dir = pattern.replace(/\/?\*\*?$/, "").replace(/\/$/, "");
|
|
8681
|
+
const pkgJsonPath = hasGlob ? (0, import_path7.join)(workspace.root, dir, dirName, "package.json") : (0, import_path7.join)(workspace.root, pattern.replace(/\/$/, ""), "package.json");
|
|
8104
8682
|
try {
|
|
8105
|
-
const
|
|
8106
|
-
|
|
8107
|
-
|
|
8108
|
-
body: JSON.stringify({
|
|
8109
|
-
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
8110
|
-
device_code,
|
|
8111
|
-
client_id: CLIENT_ID
|
|
8112
|
-
})
|
|
8113
|
-
});
|
|
8114
|
-
const tokenData = await tokenRes.json();
|
|
8115
|
-
if (tokenData.access_token) {
|
|
8116
|
-
saveApiKey(tokenData.access_token);
|
|
8117
|
-
process.stderr.write("\r\x1B[2K");
|
|
8118
|
-
console.log("Successfully logged in!");
|
|
8119
|
-
return;
|
|
8120
|
-
}
|
|
8121
|
-
if (tokenData.error) {
|
|
8122
|
-
switch (tokenData.error) {
|
|
8123
|
-
case "authorization_pending":
|
|
8124
|
-
break;
|
|
8125
|
-
// continue polling
|
|
8126
|
-
case "slow_down":
|
|
8127
|
-
pollingInterval += 5e3;
|
|
8128
|
-
break;
|
|
8129
|
-
case "access_denied":
|
|
8130
|
-
process.stderr.write("\r\x1B[2K");
|
|
8131
|
-
console.error("Authorization was denied.");
|
|
8132
|
-
process.exit(1);
|
|
8133
|
-
break;
|
|
8134
|
-
case "expired_token":
|
|
8135
|
-
process.stderr.write("\r\x1B[2K");
|
|
8136
|
-
console.error("Device code expired. Please try again.");
|
|
8137
|
-
process.exit(1);
|
|
8138
|
-
break;
|
|
8139
|
-
default:
|
|
8140
|
-
process.stderr.write("\r\x1B[2K");
|
|
8141
|
-
console.error(`Error: ${tokenData.error_description ?? tokenData.error}`);
|
|
8142
|
-
process.exit(1);
|
|
8143
|
-
}
|
|
8144
|
-
}
|
|
8683
|
+
const pkg = JSON.parse((0, import_fs7.readFileSync)(pkgJsonPath, "utf-8"));
|
|
8684
|
+
dirToName.set(dirName, pkg.name ?? dirName);
|
|
8685
|
+
break;
|
|
8145
8686
|
} catch {
|
|
8146
8687
|
}
|
|
8147
8688
|
}
|
|
8148
|
-
|
|
8149
|
-
|
|
8150
|
-
|
|
8151
|
-
} finally {
|
|
8152
|
-
clearInterval(spinner);
|
|
8689
|
+
if (!dirToName.has(dirName)) {
|
|
8690
|
+
dirToName.set(dirName, dirName);
|
|
8691
|
+
}
|
|
8153
8692
|
}
|
|
8693
|
+
return dirToName;
|
|
8154
8694
|
}
|
|
8155
|
-
|
|
8156
|
-
|
|
8157
|
-
|
|
8158
|
-
|
|
8159
|
-
|
|
8160
|
-
import_os3 = require("os");
|
|
8161
|
-
init_config();
|
|
8162
|
-
init_dist();
|
|
8163
|
-
API_URL = process.env.QC_API_URL ?? DEFAULT_API_URL;
|
|
8164
|
-
DASHBOARD_URL = "https://app.quikcommit.dev";
|
|
8165
|
-
CLIENT_ID = "qc-cli";
|
|
8166
|
-
SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
8167
|
-
}
|
|
8168
|
-
});
|
|
8695
|
+
function formatChangesetFile(packages, summary) {
|
|
8696
|
+
const frontmatter = packages.map((p) => `"${p.name}": ${p.bump}`).join("\n");
|
|
8697
|
+
return `---
|
|
8698
|
+
${frontmatter}
|
|
8699
|
+
---
|
|
8169
8700
|
|
|
8170
|
-
|
|
8171
|
-
|
|
8172
|
-
__export(logout_exports, {
|
|
8173
|
-
runLogout: () => runLogout
|
|
8174
|
-
});
|
|
8175
|
-
function runLogout() {
|
|
8176
|
-
clearApiKey();
|
|
8177
|
-
console.log("Logged out. Credentials cleared.");
|
|
8701
|
+
${summary}
|
|
8702
|
+
`;
|
|
8178
8703
|
}
|
|
8179
|
-
|
|
8180
|
-
|
|
8181
|
-
|
|
8182
|
-
|
|
8183
|
-
|
|
8184
|
-
|
|
8185
|
-
|
|
8186
|
-
// src/commands/status.ts
|
|
8187
|
-
var status_exports = {};
|
|
8188
|
-
__export(status_exports, {
|
|
8189
|
-
runStatus: () => runStatus
|
|
8190
|
-
});
|
|
8191
|
-
async function runStatus(apiKeyFlag) {
|
|
8192
|
-
const apiKey = apiKeyFlag ?? getApiKey();
|
|
8704
|
+
async function prompt(rl, question) {
|
|
8705
|
+
return new Promise((resolve) => rl.question(question, resolve));
|
|
8706
|
+
}
|
|
8707
|
+
async function changeset(options) {
|
|
8708
|
+
const base = options.base ?? "main";
|
|
8709
|
+
const apiKey = getApiKey();
|
|
8193
8710
|
if (!apiKey) {
|
|
8194
|
-
console.
|
|
8195
|
-
|
|
8711
|
+
console.error("Error: Not authenticated. Run `qc login` first.");
|
|
8712
|
+
process.exit(1);
|
|
8196
8713
|
}
|
|
8197
|
-
|
|
8198
|
-
|
|
8199
|
-
|
|
8200
|
-
|
|
8201
|
-
|
|
8202
|
-
|
|
8203
|
-
|
|
8204
|
-
console.log(`Remaining: ${usage.remaining}`);
|
|
8205
|
-
} else {
|
|
8206
|
-
console.log("Usage: (unable to fetch)");
|
|
8714
|
+
const { detectWorkspace: detectWorkspace2 } = await Promise.resolve().then(() => (init_monorepo(), monorepo_exports));
|
|
8715
|
+
const workspace = detectWorkspace2();
|
|
8716
|
+
if (!workspace) {
|
|
8717
|
+
console.error(
|
|
8718
|
+
"No workspace packages found. Is this a pnpm monorepo?"
|
|
8719
|
+
);
|
|
8720
|
+
process.exit(1);
|
|
8207
8721
|
}
|
|
8208
|
-
|
|
8209
|
-
|
|
8210
|
-
|
|
8211
|
-
|
|
8212
|
-
init_config();
|
|
8213
|
-
init_api();
|
|
8722
|
+
const changedFiles = getChangedFilesSince(base);
|
|
8723
|
+
if (changedFiles.length === 0) {
|
|
8724
|
+
console.error(`No changes detected vs ${base}.`);
|
|
8725
|
+
process.exit(1);
|
|
8214
8726
|
}
|
|
8215
|
-
|
|
8216
|
-
|
|
8217
|
-
|
|
8218
|
-
|
|
8219
|
-
|
|
8220
|
-
|
|
8221
|
-
|
|
8222
|
-
|
|
8223
|
-
const
|
|
8224
|
-
|
|
8225
|
-
(
|
|
8226
|
-
|
|
8227
|
-
|
|
8228
|
-
|
|
8727
|
+
const packageMap = mapFilesToPackages(changedFiles, workspace);
|
|
8728
|
+
const packageNames = Array.from(packageMap.values());
|
|
8729
|
+
if (packageNames.length === 0) {
|
|
8730
|
+
console.error("No workspace packages detected in changed files.");
|
|
8731
|
+
process.exit(1);
|
|
8732
|
+
}
|
|
8733
|
+
const commits = getOnlineLog(base);
|
|
8734
|
+
const diff = getFullDiff(base);
|
|
8735
|
+
const commitCount = commits.split("\n").filter(Boolean).length;
|
|
8736
|
+
console.error(
|
|
8737
|
+
`Analyzing changes vs ${base}... ${commitCount} commit(s), ${packageNames.length} package(s) changed`
|
|
8738
|
+
);
|
|
8739
|
+
const client = new ApiClient({ apiKey });
|
|
8740
|
+
let result;
|
|
8741
|
+
const msg = (e) => e instanceof Error ? e.message : String(e);
|
|
8742
|
+
const isTransient = (m) => /invalid json|no changeset|unexpected response|ai worker|timeout|502|503|504/i.test(m);
|
|
8743
|
+
let attempts = 0;
|
|
8744
|
+
while (true) {
|
|
8229
8745
|
try {
|
|
8230
|
-
|
|
8231
|
-
|
|
8746
|
+
result = await client.generateChangeset({
|
|
8747
|
+
diff,
|
|
8748
|
+
packages: packageNames,
|
|
8749
|
+
commits,
|
|
8750
|
+
model: options.model
|
|
8751
|
+
});
|
|
8752
|
+
break;
|
|
8753
|
+
} catch (err) {
|
|
8754
|
+
const m = msg(err);
|
|
8755
|
+
if (!isTransient(m)) {
|
|
8756
|
+
console.error(m);
|
|
8757
|
+
process.exit(1);
|
|
8232
8758
|
}
|
|
8233
|
-
|
|
8759
|
+
if (attempts === 0) {
|
|
8760
|
+
attempts++;
|
|
8761
|
+
continue;
|
|
8762
|
+
}
|
|
8763
|
+
console.error(m);
|
|
8764
|
+
process.exit(1);
|
|
8234
8765
|
}
|
|
8235
8766
|
}
|
|
8236
|
-
const
|
|
8237
|
-
|
|
8238
|
-
if (
|
|
8239
|
-
|
|
8240
|
-
if (names.length > 0) {
|
|
8241
|
-
const p = (0, import_path5.join)(multiDir, names[0]);
|
|
8242
|
-
if ((0, import_fs5.statSync)(p).isFile()) {
|
|
8243
|
-
return { path: p, content: (0, import_fs5.readFileSync)(p, "utf-8") };
|
|
8244
|
-
}
|
|
8245
|
-
}
|
|
8246
|
-
}
|
|
8247
|
-
} catch {
|
|
8248
|
-
}
|
|
8249
|
-
return void 0;
|
|
8250
|
-
}
|
|
8251
|
-
async function pr(options) {
|
|
8252
|
-
const base = options.base ?? "main";
|
|
8253
|
-
const commits = getBranchCommits(base);
|
|
8254
|
-
const diffStat = getDiffStat(base);
|
|
8255
|
-
const gitRoot = getGitRoot();
|
|
8256
|
-
const templateHit = findPullRequestTemplate(gitRoot);
|
|
8257
|
-
let prTemplate;
|
|
8258
|
-
if (templateHit) {
|
|
8259
|
-
prTemplate = templateHit.content.substring(0, 16 * 1024);
|
|
8260
|
-
console.error(`[qc] Using PR template from ${(0, import_path5.relative)(gitRoot, templateHit.path)}`);
|
|
8261
|
-
}
|
|
8262
|
-
const currentBranch = getCurrentBranch().slice(0, MAX_PR_CURRENT_BRANCH_CHARS);
|
|
8263
|
-
if (commits.length === 0) {
|
|
8264
|
-
console.error(`No commits found on this branch vs ${base}`);
|
|
8265
|
-
process.exit(1);
|
|
8266
|
-
}
|
|
8267
|
-
const commitlintRules = await detectCommitlintRules();
|
|
8268
|
-
console.error(`Generating PR description from ${commits.length} commits...`);
|
|
8269
|
-
const apiKey = getApiKey();
|
|
8270
|
-
if (!apiKey) {
|
|
8271
|
-
console.error("Error: Not authenticated. Run `qc login` first.");
|
|
8272
|
-
process.exit(1);
|
|
8273
|
-
}
|
|
8274
|
-
const client = new ApiClient({ apiKey });
|
|
8275
|
-
const result = await client.generatePR(
|
|
8276
|
-
{
|
|
8277
|
-
commits,
|
|
8278
|
-
diff_stat: diffStat,
|
|
8279
|
-
base_branch: base,
|
|
8280
|
-
current_branch: currentBranch,
|
|
8281
|
-
pr_template: prTemplate,
|
|
8282
|
-
rules: commitlintRules
|
|
8283
|
-
},
|
|
8284
|
-
options.model
|
|
8285
|
-
);
|
|
8286
|
-
const trimmedTitle = result.title.trim();
|
|
8287
|
-
if (trimmedTitle) {
|
|
8288
|
-
console.log(`
|
|
8289
|
-
Title: ${trimmedTitle}
|
|
8290
|
-
`);
|
|
8291
|
-
}
|
|
8292
|
-
console.log(result.message + "\n");
|
|
8293
|
-
if (options.create) {
|
|
8294
|
-
try {
|
|
8295
|
-
const prTitle = trimmedTitle || result.message.split("\n").find((l) => l.trim()) || result.message.substring(0, 72).trim();
|
|
8296
|
-
(0, import_child_process5.execFileSync)("gh", ["pr", "create", "--title", prTitle, "--body", result.message], {
|
|
8297
|
-
stdio: "inherit"
|
|
8298
|
-
});
|
|
8299
|
-
} catch {
|
|
8300
|
-
console.error("Error: `gh` CLI not found or failed. Install from https://cli.github.com/");
|
|
8301
|
-
process.exit(1);
|
|
8302
|
-
}
|
|
8303
|
-
}
|
|
8304
|
-
}
|
|
8305
|
-
var import_child_process5, import_fs5, import_path5;
|
|
8306
|
-
var init_pr = __esm({
|
|
8307
|
-
"src/commands/pr.ts"() {
|
|
8308
|
-
"use strict";
|
|
8309
|
-
import_child_process5 = require("child_process");
|
|
8310
|
-
import_fs5 = require("fs");
|
|
8311
|
-
import_path5 = require("path");
|
|
8312
|
-
init_dist();
|
|
8313
|
-
init_config();
|
|
8314
|
-
init_api();
|
|
8315
|
-
init_commitlint();
|
|
8316
|
-
init_git();
|
|
8317
|
-
}
|
|
8318
|
-
});
|
|
8319
|
-
|
|
8320
|
-
// src/commands/changelog.ts
|
|
8321
|
-
var changelog_exports = {};
|
|
8322
|
-
__export(changelog_exports, {
|
|
8323
|
-
changelog: () => changelog
|
|
8324
|
-
});
|
|
8325
|
-
function parseCommitType(subject) {
|
|
8326
|
-
const match = subject.match(CONVENTIONAL_TYPE_RE);
|
|
8327
|
-
return match ? match[1].toLowerCase() : "chore";
|
|
8328
|
-
}
|
|
8329
|
-
function groupCommitsByType(commits) {
|
|
8330
|
-
const byType = {};
|
|
8331
|
-
for (const { subject } of commits) {
|
|
8332
|
-
const type = parseCommitType(subject);
|
|
8333
|
-
if (!byType[type]) byType[type] = [];
|
|
8334
|
-
byType[type].push(subject);
|
|
8335
|
-
}
|
|
8336
|
-
return byType;
|
|
8337
|
-
}
|
|
8338
|
-
async function changelog(options) {
|
|
8339
|
-
const fromRef = options.from ?? getLatestTag();
|
|
8340
|
-
const toRef = options.to ?? "HEAD";
|
|
8341
|
-
if (!fromRef) {
|
|
8342
|
-
console.error("Error: No git tag found. Use --from <ref> to specify a starting point.");
|
|
8343
|
-
process.exit(1);
|
|
8344
|
-
}
|
|
8345
|
-
const commits = getCommitsSince(fromRef, toRef);
|
|
8346
|
-
if (commits.length === 0) {
|
|
8347
|
-
console.error(`No commits found between ${fromRef} and ${toRef}`);
|
|
8348
|
-
process.exit(1);
|
|
8349
|
-
}
|
|
8350
|
-
const commitsByType = groupCommitsByType(commits);
|
|
8351
|
-
const apiKey = getApiKey();
|
|
8352
|
-
if (!apiKey) {
|
|
8353
|
-
console.error("Error: Not authenticated. Run `qc login` first.");
|
|
8354
|
-
process.exit(1);
|
|
8355
|
-
}
|
|
8356
|
-
const client = new ApiClient({ apiKey });
|
|
8357
|
-
const result = await client.generateChangelog(
|
|
8358
|
-
{
|
|
8359
|
-
commits_by_type: commitsByType,
|
|
8360
|
-
from_tag: fromRef,
|
|
8361
|
-
to_ref: toRef
|
|
8362
|
-
},
|
|
8363
|
-
options.model
|
|
8364
|
-
);
|
|
8365
|
-
const version = options.version ?? (/^v?\d/.test(toRef) && toRef !== "HEAD" ? toRef.replace(/^v/, "") : null) ?? `${fromRef.replace(/^v/, "")}-next`;
|
|
8366
|
-
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
8367
|
-
const header = `## [${version}] - ${date}
|
|
8368
|
-
|
|
8369
|
-
`;
|
|
8370
|
-
const changelogEntry = header + result.message;
|
|
8371
|
-
if (options.write) {
|
|
8372
|
-
const path = (0, import_path6.join)(getGitRoot(), "CHANGELOG.md");
|
|
8373
|
-
const existing = (0, import_fs6.existsSync)(path) ? (0, import_fs6.readFileSync)(path, "utf-8") : "";
|
|
8374
|
-
const newContent = changelogEntry + (existing ? "\n\n" + existing : "");
|
|
8375
|
-
(0, import_fs6.writeFileSync)(path, newContent);
|
|
8376
|
-
console.error(`Wrote to ${path}`);
|
|
8377
|
-
} else {
|
|
8378
|
-
console.log(changelogEntry);
|
|
8379
|
-
}
|
|
8380
|
-
}
|
|
8381
|
-
var import_fs6, import_path6, CONVENTIONAL_TYPE_RE;
|
|
8382
|
-
var init_changelog = __esm({
|
|
8383
|
-
"src/commands/changelog.ts"() {
|
|
8384
|
-
"use strict";
|
|
8385
|
-
import_fs6 = require("fs");
|
|
8386
|
-
import_path6 = require("path");
|
|
8387
|
-
init_config();
|
|
8388
|
-
init_api();
|
|
8389
|
-
init_git();
|
|
8390
|
-
CONVENTIONAL_TYPE_RE = /^(feat|fix|docs|style|refactor|perf|test|chore)(\([^)]+\))?!?:\s+/i;
|
|
8391
|
-
}
|
|
8392
|
-
});
|
|
8393
|
-
|
|
8394
|
-
// src/commands/changeset.ts
|
|
8395
|
-
var changeset_exports = {};
|
|
8396
|
-
__export(changeset_exports, {
|
|
8397
|
-
changeset: () => changeset,
|
|
8398
|
-
formatChangesetFile: () => formatChangesetFile,
|
|
8399
|
-
generateSlug: () => generateSlug,
|
|
8400
|
-
mapFilesToPackages: () => mapFilesToPackages
|
|
8401
|
-
});
|
|
8402
|
-
function generateSlug() {
|
|
8403
|
-
const pick = (arr) => arr[Math.floor(Math.random() * arr.length)];
|
|
8404
|
-
return `${pick(ADJECTIVES)}-${pick(ANIMALS)}-${pick(VERBS)}`;
|
|
8405
|
-
}
|
|
8406
|
-
function mapFilesToPackages(files, workspace) {
|
|
8407
|
-
const dirToName = /* @__PURE__ */ new Map();
|
|
8408
|
-
for (const file of files) {
|
|
8409
|
-
const dirName = getPackageForFile(file, workspace);
|
|
8410
|
-
if (!dirName || dirToName.has(dirName)) continue;
|
|
8411
|
-
for (const pattern of workspace.packages) {
|
|
8412
|
-
const hasGlob = /\*/.test(pattern);
|
|
8413
|
-
const dir = pattern.replace(/\/?\*\*?$/, "").replace(/\/$/, "");
|
|
8414
|
-
const pkgJsonPath = hasGlob ? (0, import_path7.join)(workspace.root, dir, dirName, "package.json") : (0, import_path7.join)(workspace.root, pattern.replace(/\/$/, ""), "package.json");
|
|
8415
|
-
try {
|
|
8416
|
-
const pkg = JSON.parse((0, import_fs7.readFileSync)(pkgJsonPath, "utf-8"));
|
|
8417
|
-
dirToName.set(dirName, pkg.name ?? dirName);
|
|
8418
|
-
break;
|
|
8419
|
-
} catch {
|
|
8420
|
-
}
|
|
8421
|
-
}
|
|
8422
|
-
if (!dirToName.has(dirName)) {
|
|
8423
|
-
dirToName.set(dirName, dirName);
|
|
8424
|
-
}
|
|
8425
|
-
}
|
|
8426
|
-
return dirToName;
|
|
8427
|
-
}
|
|
8428
|
-
function formatChangesetFile(packages, summary) {
|
|
8429
|
-
const frontmatter = packages.map((p) => `"${p.name}": ${p.bump}`).join("\n");
|
|
8430
|
-
return `---
|
|
8431
|
-
${frontmatter}
|
|
8432
|
-
---
|
|
8433
|
-
|
|
8434
|
-
${summary}
|
|
8435
|
-
`;
|
|
8436
|
-
}
|
|
8437
|
-
async function prompt(rl, question) {
|
|
8438
|
-
return new Promise((resolve) => rl.question(question, resolve));
|
|
8439
|
-
}
|
|
8440
|
-
async function changeset(options) {
|
|
8441
|
-
const base = options.base ?? "main";
|
|
8442
|
-
const apiKey = getApiKey();
|
|
8443
|
-
if (!apiKey) {
|
|
8444
|
-
console.error("Error: Not authenticated. Run `qc login` first.");
|
|
8445
|
-
process.exit(1);
|
|
8446
|
-
}
|
|
8447
|
-
const { detectWorkspace: detectWorkspace2 } = await Promise.resolve().then(() => (init_monorepo(), monorepo_exports));
|
|
8448
|
-
const workspace = detectWorkspace2();
|
|
8449
|
-
if (!workspace) {
|
|
8450
|
-
console.error(
|
|
8451
|
-
"No workspace packages found. Is this a pnpm monorepo?"
|
|
8452
|
-
);
|
|
8453
|
-
process.exit(1);
|
|
8454
|
-
}
|
|
8455
|
-
const changedFiles = getChangedFilesSince(base);
|
|
8456
|
-
if (changedFiles.length === 0) {
|
|
8457
|
-
console.error(`No changes detected vs ${base}.`);
|
|
8458
|
-
process.exit(1);
|
|
8459
|
-
}
|
|
8460
|
-
const packageMap = mapFilesToPackages(changedFiles, workspace);
|
|
8461
|
-
const packageNames = Array.from(packageMap.values());
|
|
8462
|
-
if (packageNames.length === 0) {
|
|
8463
|
-
console.error("No workspace packages detected in changed files.");
|
|
8464
|
-
process.exit(1);
|
|
8465
|
-
}
|
|
8466
|
-
const commits = getOnlineLog(base);
|
|
8467
|
-
const diff = getFullDiff(base);
|
|
8468
|
-
const commitCount = commits.split("\n").filter(Boolean).length;
|
|
8469
|
-
console.error(
|
|
8470
|
-
`Analyzing changes vs ${base}... ${commitCount} commit(s), ${packageNames.length} package(s) changed`
|
|
8471
|
-
);
|
|
8472
|
-
const client = new ApiClient({ apiKey });
|
|
8473
|
-
let result;
|
|
8474
|
-
const msg = (e) => e instanceof Error ? e.message : String(e);
|
|
8475
|
-
const isTransient = (m) => /invalid json|no changeset|unexpected response|ai worker|timeout|502|503|504/i.test(m);
|
|
8476
|
-
let attempts = 0;
|
|
8477
|
-
while (true) {
|
|
8478
|
-
try {
|
|
8479
|
-
result = await client.generateChangeset({
|
|
8480
|
-
diff,
|
|
8481
|
-
packages: packageNames,
|
|
8482
|
-
commits,
|
|
8483
|
-
model: options.model
|
|
8484
|
-
});
|
|
8485
|
-
break;
|
|
8486
|
-
} catch (err) {
|
|
8487
|
-
const m = msg(err);
|
|
8488
|
-
if (!isTransient(m)) {
|
|
8489
|
-
console.error(m);
|
|
8490
|
-
process.exit(1);
|
|
8491
|
-
}
|
|
8492
|
-
if (attempts === 0) {
|
|
8493
|
-
attempts++;
|
|
8494
|
-
continue;
|
|
8495
|
-
}
|
|
8496
|
-
console.error(m);
|
|
8497
|
-
process.exit(1);
|
|
8498
|
-
}
|
|
8499
|
-
}
|
|
8500
|
-
const resultNames = new Set(result.packages.map((p) => p.name));
|
|
8501
|
-
for (const name of packageNames) {
|
|
8502
|
-
if (!resultNames.has(name)) {
|
|
8503
|
-
result.packages.push({ name, bump: "patch", reason: "included in changeset" });
|
|
8767
|
+
const resultNames = new Set(result.packages.map((p) => p.name));
|
|
8768
|
+
for (const name of packageNames) {
|
|
8769
|
+
if (!resultNames.has(name)) {
|
|
8770
|
+
result.packages.push({ name, bump: "patch", reason: "included in changeset" });
|
|
8504
8771
|
}
|
|
8505
8772
|
}
|
|
8506
8773
|
console.log("");
|
|
@@ -8697,6 +8964,1561 @@ var init_changeset = __esm({
|
|
|
8697
8964
|
}
|
|
8698
8965
|
});
|
|
8699
8966
|
|
|
8967
|
+
// src/branch-rescue.ts
|
|
8968
|
+
function rescueCommits(opts) {
|
|
8969
|
+
const upstream = getUpstreamRef(opts.currentBranch);
|
|
8970
|
+
if (!upstream) {
|
|
8971
|
+
throw new Error(
|
|
8972
|
+
`No upstream tracking branch for '${opts.currentBranch}'. Push it first or use \`qc branch\` manually.`
|
|
8973
|
+
);
|
|
8974
|
+
}
|
|
8975
|
+
const headSha = getHeadSha();
|
|
8976
|
+
const stashed = stashPushIfDirty(`qc-rescue-${opts.newBranch}`);
|
|
8977
|
+
try {
|
|
8978
|
+
createBranch(opts.newBranch, headSha);
|
|
8979
|
+
} catch (err) {
|
|
8980
|
+
if (stashed) {
|
|
8981
|
+
try {
|
|
8982
|
+
stashPop();
|
|
8983
|
+
} catch {
|
|
8984
|
+
}
|
|
8985
|
+
}
|
|
8986
|
+
throw err;
|
|
8987
|
+
}
|
|
8988
|
+
try {
|
|
8989
|
+
resetHard(upstream);
|
|
8990
|
+
} catch (err) {
|
|
8991
|
+
try {
|
|
8992
|
+
deleteBranch(opts.newBranch);
|
|
8993
|
+
} catch {
|
|
8994
|
+
}
|
|
8995
|
+
if (stashed) {
|
|
8996
|
+
try {
|
|
8997
|
+
stashPop();
|
|
8998
|
+
} catch {
|
|
8999
|
+
}
|
|
9000
|
+
}
|
|
9001
|
+
throw new Error(
|
|
9002
|
+
`Rescue aborted: failed to reset ${opts.currentBranch} to upstream. Your repo state has been restored. Original error: ${err?.message ?? String(err)}`
|
|
9003
|
+
);
|
|
9004
|
+
}
|
|
9005
|
+
try {
|
|
9006
|
+
checkoutBranch(opts.newBranch);
|
|
9007
|
+
} catch (err) {
|
|
9008
|
+
try {
|
|
9009
|
+
resetHard(headSha);
|
|
9010
|
+
} catch {
|
|
9011
|
+
}
|
|
9012
|
+
if (stashed) {
|
|
9013
|
+
try {
|
|
9014
|
+
stashPop();
|
|
9015
|
+
} catch {
|
|
9016
|
+
}
|
|
9017
|
+
}
|
|
9018
|
+
throw err;
|
|
9019
|
+
}
|
|
9020
|
+
if (stashed) {
|
|
9021
|
+
try {
|
|
9022
|
+
stashPop();
|
|
9023
|
+
} catch (err) {
|
|
9024
|
+
throw new Error(
|
|
9025
|
+
`Stash conflict during recovery. Your changes are preserved in the stash entry. Resolve manually with \`git stash pop\` then \`git stash drop\` after resolving conflicts.
|
|
9026
|
+
Original error: ${err?.message ?? err}`
|
|
9027
|
+
);
|
|
9028
|
+
}
|
|
9029
|
+
}
|
|
9030
|
+
return {
|
|
9031
|
+
newBranch: opts.newBranch,
|
|
9032
|
+
stashed,
|
|
9033
|
+
upstreamRef: upstream,
|
|
9034
|
+
movedFromSha: headSha
|
|
9035
|
+
};
|
|
9036
|
+
}
|
|
9037
|
+
var init_branch_rescue = __esm({
|
|
9038
|
+
"src/branch-rescue.ts"() {
|
|
9039
|
+
"use strict";
|
|
9040
|
+
init_git();
|
|
9041
|
+
}
|
|
9042
|
+
});
|
|
9043
|
+
|
|
9044
|
+
// src/branch-detect.ts
|
|
9045
|
+
function matchGlob(name, pattern) {
|
|
9046
|
+
const n = name.toLowerCase();
|
|
9047
|
+
const p = pattern.toLowerCase();
|
|
9048
|
+
if (!p.includes("*")) return n === p;
|
|
9049
|
+
const escaped = p.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
9050
|
+
const withDoubleStar = escaped.replace(/\*\*/g, "\0DOUBLE_STAR\0");
|
|
9051
|
+
const withSingleStar = withDoubleStar.replace(/\*/g, "[^/]*");
|
|
9052
|
+
const final = withSingleStar.replace(/\x00DOUBLE_STAR\x00/g, ".*");
|
|
9053
|
+
const rx = new RegExp("^" + final + "$");
|
|
9054
|
+
return rx.test(n);
|
|
9055
|
+
}
|
|
9056
|
+
function isProtectedBranch(branch, protectedList) {
|
|
9057
|
+
if (!protectedList || protectedList.length === 0) return false;
|
|
9058
|
+
return protectedList.some((p) => matchGlob(branch, p));
|
|
9059
|
+
}
|
|
9060
|
+
function resolveProtectedBranches(opts) {
|
|
9061
|
+
const set = /* @__PURE__ */ new Set();
|
|
9062
|
+
if (opts.configList && opts.configList.length > 0) {
|
|
9063
|
+
for (const b of opts.configList) set.add(b);
|
|
9064
|
+
} else {
|
|
9065
|
+
for (const b of HARDCODED_PROTECTED) set.add(b);
|
|
9066
|
+
}
|
|
9067
|
+
if (opts.detectDefault && opts.defaultBranch) {
|
|
9068
|
+
set.add(opts.defaultBranch);
|
|
9069
|
+
}
|
|
9070
|
+
return Array.from(set);
|
|
9071
|
+
}
|
|
9072
|
+
var HARDCODED_PROTECTED;
|
|
9073
|
+
var init_branch_detect = __esm({
|
|
9074
|
+
"src/branch-detect.ts"() {
|
|
9075
|
+
"use strict";
|
|
9076
|
+
HARDCODED_PROTECTED = ["main", "master", "develop", "trunk"];
|
|
9077
|
+
}
|
|
9078
|
+
});
|
|
9079
|
+
|
|
9080
|
+
// src/protected-branch-guard.ts
|
|
9081
|
+
function shouldRunGuard(opts) {
|
|
9082
|
+
if (opts.allowProtected) return false;
|
|
9083
|
+
if (opts.hookMode) return false;
|
|
9084
|
+
if (!opts.isTTY) return false;
|
|
9085
|
+
return true;
|
|
9086
|
+
}
|
|
9087
|
+
function detectProtectedBranchState(opts) {
|
|
9088
|
+
const branch = getCurrentBranch();
|
|
9089
|
+
const protectedList = resolveProtectedBranches({
|
|
9090
|
+
configList: opts.protectedBranches,
|
|
9091
|
+
detectDefault: opts.detectDefault !== false,
|
|
9092
|
+
defaultBranch: getDefaultBranch()
|
|
9093
|
+
});
|
|
9094
|
+
const protectedBranch = isProtectedBranch(branch, protectedList);
|
|
9095
|
+
if (!protectedBranch) {
|
|
9096
|
+
return { isProtected: false, branch, commitsAhead: 0, mode: "none" };
|
|
9097
|
+
}
|
|
9098
|
+
const commitsAhead = getCommitsAheadOfUpstream(branch);
|
|
9099
|
+
const mode = commitsAhead > 0 ? "rescue" : "uncommitted";
|
|
9100
|
+
return { isProtected: true, branch, commitsAhead, mode };
|
|
9101
|
+
}
|
|
9102
|
+
var init_protected_branch_guard = __esm({
|
|
9103
|
+
"src/protected-branch-guard.ts"() {
|
|
9104
|
+
"use strict";
|
|
9105
|
+
init_branch_detect();
|
|
9106
|
+
init_git();
|
|
9107
|
+
}
|
|
9108
|
+
});
|
|
9109
|
+
|
|
9110
|
+
// src/branch-name.ts
|
|
9111
|
+
function sanitizeBranchName(input) {
|
|
9112
|
+
if (!input) return null;
|
|
9113
|
+
let s = input.toLowerCase().trim();
|
|
9114
|
+
s = s.replace(/[\s_]+/g, "-");
|
|
9115
|
+
s = s.replace(/[^a-z0-9/-]/g, "");
|
|
9116
|
+
s = s.replace(/-+/g, "-").replace(/\/+/g, "/");
|
|
9117
|
+
s = s.replace(/^[-/]+|[-/]+$/g, "");
|
|
9118
|
+
if (!s.includes("/")) return null;
|
|
9119
|
+
if (s.length > MAX_BRANCH_NAME_LENGTH) {
|
|
9120
|
+
const parts = s.split("/");
|
|
9121
|
+
const type = parts[0] ?? "";
|
|
9122
|
+
const slugBudget = Math.min(MAX_BRANCH_NAME_LENGTH - type.length - 1, 52);
|
|
9123
|
+
if (slugBudget < 2) return null;
|
|
9124
|
+
s = `${type}/${parts.slice(1).join("/").slice(0, slugBudget).replace(/-+$/g, "")}`;
|
|
9125
|
+
}
|
|
9126
|
+
return validateBranchName(s) ? s : null;
|
|
9127
|
+
}
|
|
9128
|
+
function ensureUniqueName(name, exists) {
|
|
9129
|
+
if (!exists(name)) return name;
|
|
9130
|
+
for (let i = 2; i <= 100; i++) {
|
|
9131
|
+
const candidate = `${name}-${i}`;
|
|
9132
|
+
if (!exists(candidate)) return candidate;
|
|
9133
|
+
}
|
|
9134
|
+
throw new Error(`Could not find a unique name for ${name} after 100 attempts`);
|
|
9135
|
+
}
|
|
9136
|
+
function finalizeBranchName(raw, exists, options = {}) {
|
|
9137
|
+
let candidate = raw;
|
|
9138
|
+
if (!validateBranchName(candidate)) {
|
|
9139
|
+
const s = sanitizeBranchName(candidate);
|
|
9140
|
+
if (!s) {
|
|
9141
|
+
throw new Error(`Generated invalid branch name and could not sanitize: ${raw}`);
|
|
9142
|
+
}
|
|
9143
|
+
candidate = s;
|
|
9144
|
+
}
|
|
9145
|
+
if (options.skipUniqueness) {
|
|
9146
|
+
return candidate;
|
|
9147
|
+
}
|
|
9148
|
+
return ensureUniqueName(candidate, exists);
|
|
9149
|
+
}
|
|
9150
|
+
var init_branch_name = __esm({
|
|
9151
|
+
"src/branch-name.ts"() {
|
|
9152
|
+
"use strict";
|
|
9153
|
+
init_dist();
|
|
9154
|
+
}
|
|
9155
|
+
});
|
|
9156
|
+
|
|
9157
|
+
// ../../node_modules/.pnpm/picocolors@1.1.1/node_modules/picocolors/picocolors.js
|
|
9158
|
+
var require_picocolors = __commonJS({
|
|
9159
|
+
"../../node_modules/.pnpm/picocolors@1.1.1/node_modules/picocolors/picocolors.js"(exports2, module2) {
|
|
9160
|
+
var p = process || {};
|
|
9161
|
+
var argv = p.argv || [];
|
|
9162
|
+
var env = p.env || {};
|
|
9163
|
+
var isColorSupported = !(!!env.NO_COLOR || argv.includes("--no-color")) && (!!env.FORCE_COLOR || argv.includes("--color") || p.platform === "win32" || (p.stdout || {}).isTTY && env.TERM !== "dumb" || !!env.CI);
|
|
9164
|
+
var formatter = (open, close, replace = open) => (input) => {
|
|
9165
|
+
let string = "" + input, index = string.indexOf(close, open.length);
|
|
9166
|
+
return ~index ? open + replaceClose(string, close, replace, index) + close : open + string + close;
|
|
9167
|
+
};
|
|
9168
|
+
var replaceClose = (string, close, replace, index) => {
|
|
9169
|
+
let result = "", cursor = 0;
|
|
9170
|
+
do {
|
|
9171
|
+
result += string.substring(cursor, index) + replace;
|
|
9172
|
+
cursor = index + close.length;
|
|
9173
|
+
index = string.indexOf(close, cursor);
|
|
9174
|
+
} while (~index);
|
|
9175
|
+
return result + string.substring(cursor);
|
|
9176
|
+
};
|
|
9177
|
+
var createColors = (enabled = isColorSupported) => {
|
|
9178
|
+
let f = enabled ? formatter : () => String;
|
|
9179
|
+
return {
|
|
9180
|
+
isColorSupported: enabled,
|
|
9181
|
+
reset: f("\x1B[0m", "\x1B[0m"),
|
|
9182
|
+
bold: f("\x1B[1m", "\x1B[22m", "\x1B[22m\x1B[1m"),
|
|
9183
|
+
dim: f("\x1B[2m", "\x1B[22m", "\x1B[22m\x1B[2m"),
|
|
9184
|
+
italic: f("\x1B[3m", "\x1B[23m"),
|
|
9185
|
+
underline: f("\x1B[4m", "\x1B[24m"),
|
|
9186
|
+
inverse: f("\x1B[7m", "\x1B[27m"),
|
|
9187
|
+
hidden: f("\x1B[8m", "\x1B[28m"),
|
|
9188
|
+
strikethrough: f("\x1B[9m", "\x1B[29m"),
|
|
9189
|
+
black: f("\x1B[30m", "\x1B[39m"),
|
|
9190
|
+
red: f("\x1B[31m", "\x1B[39m"),
|
|
9191
|
+
green: f("\x1B[32m", "\x1B[39m"),
|
|
9192
|
+
yellow: f("\x1B[33m", "\x1B[39m"),
|
|
9193
|
+
blue: f("\x1B[34m", "\x1B[39m"),
|
|
9194
|
+
magenta: f("\x1B[35m", "\x1B[39m"),
|
|
9195
|
+
cyan: f("\x1B[36m", "\x1B[39m"),
|
|
9196
|
+
white: f("\x1B[37m", "\x1B[39m"),
|
|
9197
|
+
gray: f("\x1B[90m", "\x1B[39m"),
|
|
9198
|
+
bgBlack: f("\x1B[40m", "\x1B[49m"),
|
|
9199
|
+
bgRed: f("\x1B[41m", "\x1B[49m"),
|
|
9200
|
+
bgGreen: f("\x1B[42m", "\x1B[49m"),
|
|
9201
|
+
bgYellow: f("\x1B[43m", "\x1B[49m"),
|
|
9202
|
+
bgBlue: f("\x1B[44m", "\x1B[49m"),
|
|
9203
|
+
bgMagenta: f("\x1B[45m", "\x1B[49m"),
|
|
9204
|
+
bgCyan: f("\x1B[46m", "\x1B[49m"),
|
|
9205
|
+
bgWhite: f("\x1B[47m", "\x1B[49m"),
|
|
9206
|
+
blackBright: f("\x1B[90m", "\x1B[39m"),
|
|
9207
|
+
redBright: f("\x1B[91m", "\x1B[39m"),
|
|
9208
|
+
greenBright: f("\x1B[92m", "\x1B[39m"),
|
|
9209
|
+
yellowBright: f("\x1B[93m", "\x1B[39m"),
|
|
9210
|
+
blueBright: f("\x1B[94m", "\x1B[39m"),
|
|
9211
|
+
magentaBright: f("\x1B[95m", "\x1B[39m"),
|
|
9212
|
+
cyanBright: f("\x1B[96m", "\x1B[39m"),
|
|
9213
|
+
whiteBright: f("\x1B[97m", "\x1B[39m"),
|
|
9214
|
+
bgBlackBright: f("\x1B[100m", "\x1B[49m"),
|
|
9215
|
+
bgRedBright: f("\x1B[101m", "\x1B[49m"),
|
|
9216
|
+
bgGreenBright: f("\x1B[102m", "\x1B[49m"),
|
|
9217
|
+
bgYellowBright: f("\x1B[103m", "\x1B[49m"),
|
|
9218
|
+
bgBlueBright: f("\x1B[104m", "\x1B[49m"),
|
|
9219
|
+
bgMagentaBright: f("\x1B[105m", "\x1B[49m"),
|
|
9220
|
+
bgCyanBright: f("\x1B[106m", "\x1B[49m"),
|
|
9221
|
+
bgWhiteBright: f("\x1B[107m", "\x1B[49m")
|
|
9222
|
+
};
|
|
9223
|
+
};
|
|
9224
|
+
module2.exports = createColors();
|
|
9225
|
+
module2.exports.createColors = createColors;
|
|
9226
|
+
}
|
|
9227
|
+
});
|
|
9228
|
+
|
|
9229
|
+
// src/ui.ts
|
|
9230
|
+
function hasCliNoColor() {
|
|
9231
|
+
try {
|
|
9232
|
+
return process.argv.slice(2).includes("--no-color");
|
|
9233
|
+
} catch {
|
|
9234
|
+
return false;
|
|
9235
|
+
}
|
|
9236
|
+
}
|
|
9237
|
+
function getTerminalWidth() {
|
|
9238
|
+
return process.stderr.columns ?? process.stdout.columns ?? 80;
|
|
9239
|
+
}
|
|
9240
|
+
function createUI(options) {
|
|
9241
|
+
const isColor = options.isTTY && !options.noColor;
|
|
9242
|
+
const wrap = (fn) => (s) => isColor ? fn(s) : s;
|
|
9243
|
+
const format = {
|
|
9244
|
+
step: (msg) => `${isColor ? import_picocolors.default.dim("\u203A") : "\u203A"} ${isColor ? import_picocolors.default.dim(msg) : msg}`,
|
|
9245
|
+
success: (msg) => `${isColor ? import_picocolors.default.green("\u2713") : "\u2713"} ${msg}`,
|
|
9246
|
+
error: (msg) => `${isColor ? import_picocolors.default.red("\u2717") : "\u2717"} ${msg}`,
|
|
9247
|
+
dim: wrap(import_picocolors.default.dim),
|
|
9248
|
+
bold: wrap(import_picocolors.default.bold),
|
|
9249
|
+
commitType: wrap(import_picocolors.default.cyan),
|
|
9250
|
+
commitScope: wrap(import_picocolors.default.yellow),
|
|
9251
|
+
accent: wrap(import_picocolors.default.magenta)
|
|
9252
|
+
};
|
|
9253
|
+
function createSpinner(message, write = (s) => process.stderr.write(s)) {
|
|
9254
|
+
let frame = 0;
|
|
9255
|
+
let interval = null;
|
|
9256
|
+
return {
|
|
9257
|
+
start() {
|
|
9258
|
+
if (interval) return;
|
|
9259
|
+
if (!options.isTTY) return;
|
|
9260
|
+
interval = setInterval(() => {
|
|
9261
|
+
const f = SPINNER_FRAMES2[frame++ % SPINNER_FRAMES2.length];
|
|
9262
|
+
write(`\r${format.step(message)} ${isColor ? import_picocolors.default.cyan(f) : f}`);
|
|
9263
|
+
}, 80);
|
|
9264
|
+
},
|
|
9265
|
+
stop(finalMessage) {
|
|
9266
|
+
if (interval) {
|
|
9267
|
+
clearInterval(interval);
|
|
9268
|
+
interval = null;
|
|
9269
|
+
}
|
|
9270
|
+
if (options.isTTY) {
|
|
9271
|
+
write("\r\x1B[2K");
|
|
9272
|
+
}
|
|
9273
|
+
if (finalMessage) {
|
|
9274
|
+
write(finalMessage + "\n");
|
|
9275
|
+
}
|
|
9276
|
+
}
|
|
9277
|
+
};
|
|
9278
|
+
}
|
|
9279
|
+
const log = {
|
|
9280
|
+
step: (msg) => process.stderr.write(format.step(msg) + "\n"),
|
|
9281
|
+
success: (msg) => process.stderr.write(format.success(msg) + "\n"),
|
|
9282
|
+
error: (msg) => process.stderr.write(format.error(msg) + "\n"),
|
|
9283
|
+
dim: (msg) => process.stderr.write(format.dim(msg) + "\n")
|
|
9284
|
+
};
|
|
9285
|
+
return { isColor, format, spinner: createSpinner, log };
|
|
9286
|
+
}
|
|
9287
|
+
function getUI() {
|
|
9288
|
+
if (!_defaultUI) {
|
|
9289
|
+
_defaultUI = createUI({
|
|
9290
|
+
isTTY: !!process.stderr.isTTY,
|
|
9291
|
+
noColor: !!process.env.NO_COLOR || hasCliNoColor()
|
|
9292
|
+
});
|
|
9293
|
+
}
|
|
9294
|
+
return _defaultUI;
|
|
9295
|
+
}
|
|
9296
|
+
var import_picocolors, SPINNER_FRAMES2, _defaultUI, ui;
|
|
9297
|
+
var init_ui = __esm({
|
|
9298
|
+
"src/ui.ts"() {
|
|
9299
|
+
"use strict";
|
|
9300
|
+
import_picocolors = __toESM(require_picocolors());
|
|
9301
|
+
SPINNER_FRAMES2 = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
9302
|
+
ui = new Proxy({}, {
|
|
9303
|
+
get(_target, prop) {
|
|
9304
|
+
return getUI()[prop];
|
|
9305
|
+
}
|
|
9306
|
+
});
|
|
9307
|
+
}
|
|
9308
|
+
});
|
|
9309
|
+
|
|
9310
|
+
// src/ui-rich.ts
|
|
9311
|
+
function splitCommitForBox(message) {
|
|
9312
|
+
const firstLine = message.split("\n")[0] ?? "";
|
|
9313
|
+
const m = firstLine.match(HEADER_RX);
|
|
9314
|
+
if (!m) return { type: null, scope: null, subject: firstLine, breaking: false };
|
|
9315
|
+
return {
|
|
9316
|
+
type: m[1] ?? null,
|
|
9317
|
+
scope: m[2] ?? null,
|
|
9318
|
+
breaking: m[3] === "!",
|
|
9319
|
+
subject: m[4] ?? ""
|
|
9320
|
+
};
|
|
9321
|
+
}
|
|
9322
|
+
function renderFileTree(files, maxFiles) {
|
|
9323
|
+
if (files.length === 0) return "";
|
|
9324
|
+
const lines = [];
|
|
9325
|
+
const display = files.slice(0, maxFiles);
|
|
9326
|
+
const overflow = Math.max(0, files.length - maxFiles);
|
|
9327
|
+
for (let i = 0; i < display.length; i++) {
|
|
9328
|
+
const isLast = i === display.length - 1 && overflow === 0;
|
|
9329
|
+
const connector = isLast ? "\u2514\u2500" : "\u251C\u2500";
|
|
9330
|
+
lines.push(` ${connector} ${display[i]}`);
|
|
9331
|
+
}
|
|
9332
|
+
if (overflow > 0) {
|
|
9333
|
+
lines.push(` \u2514\u2500 +${overflow} more files`);
|
|
9334
|
+
}
|
|
9335
|
+
return lines.join("\n");
|
|
9336
|
+
}
|
|
9337
|
+
function wrapLine(text, width) {
|
|
9338
|
+
if (text.length <= width) return [text];
|
|
9339
|
+
const result = [];
|
|
9340
|
+
let remaining = text;
|
|
9341
|
+
while (remaining.length > width) {
|
|
9342
|
+
let breakAt = remaining.lastIndexOf(" ", width);
|
|
9343
|
+
if (breakAt < width / 2) breakAt = width;
|
|
9344
|
+
result.push(remaining.slice(0, breakAt));
|
|
9345
|
+
remaining = remaining.slice(breakAt).trimStart();
|
|
9346
|
+
}
|
|
9347
|
+
if (remaining) result.push(remaining);
|
|
9348
|
+
return result;
|
|
9349
|
+
}
|
|
9350
|
+
function stripAnsi(s) {
|
|
9351
|
+
return s.replace(/\x1b\[[0-9;]*m/g, "");
|
|
9352
|
+
}
|
|
9353
|
+
function boxedLine(content, innerWidth, isColor) {
|
|
9354
|
+
const visibleLen = stripAnsi(content).length;
|
|
9355
|
+
const padding = " ".repeat(Math.max(0, innerWidth - visibleLen));
|
|
9356
|
+
const border = isColor ? import_picocolors2.default.dim("\u2502") : "\u2502";
|
|
9357
|
+
return ` ${border} ${content}${padding} ${border}`;
|
|
9358
|
+
}
|
|
9359
|
+
function renderBoxedCommit(header, body, opts) {
|
|
9360
|
+
if (opts.width < MIN_BOX_WIDTH) {
|
|
9361
|
+
const lines2 = [header.split("\n")[0] ?? header];
|
|
9362
|
+
if (body) lines2.push("", body);
|
|
9363
|
+
return lines2.join("\n");
|
|
9364
|
+
}
|
|
9365
|
+
const innerWidth = opts.width - 8;
|
|
9366
|
+
const horiz = opts.width - 2;
|
|
9367
|
+
const top = " " + (opts.isColor ? import_picocolors2.default.dim("\u256D" + "\u2500".repeat(horiz) + "\u256E") : "\u256D" + "\u2500".repeat(horiz) + "\u256E");
|
|
9368
|
+
const bottom = " " + (opts.isColor ? import_picocolors2.default.dim("\u2570" + "\u2500".repeat(horiz) + "\u256F") : "\u2570" + "\u2500".repeat(horiz) + "\u256F");
|
|
9369
|
+
const parsed = splitCommitForBox(header);
|
|
9370
|
+
let firstLineStyled;
|
|
9371
|
+
if (parsed.type && parsed.scope) {
|
|
9372
|
+
const bare = `${parsed.type}(${parsed.scope})${parsed.breaking ? "!" : ""}: ${parsed.subject}`;
|
|
9373
|
+
firstLineStyled = opts.isColor ? `${import_picocolors2.default.bold(import_picocolors2.default.cyan(parsed.type))}(${import_picocolors2.default.bold(import_picocolors2.default.yellow(parsed.scope))})${parsed.breaking ? import_picocolors2.default.bold(import_picocolors2.default.red("!")) : ""}: ${parsed.subject}` : bare;
|
|
9374
|
+
} else if (parsed.type) {
|
|
9375
|
+
const bare = `${parsed.type}${parsed.breaking ? "!" : ""}: ${parsed.subject}`;
|
|
9376
|
+
firstLineStyled = opts.isColor ? `${import_picocolors2.default.bold(import_picocolors2.default.cyan(parsed.type))}${parsed.breaking ? import_picocolors2.default.bold(import_picocolors2.default.red("!")) : ""}: ${parsed.subject}` : bare;
|
|
9377
|
+
} else {
|
|
9378
|
+
firstLineStyled = header.split("\n")[0] ?? header;
|
|
9379
|
+
}
|
|
9380
|
+
const lines = [];
|
|
9381
|
+
const headerParts = wrapLine(firstLineStyled, innerWidth);
|
|
9382
|
+
for (let i = 0; i < headerParts.length; i++) {
|
|
9383
|
+
const indent = i === 0 ? "" : " ";
|
|
9384
|
+
lines.push(boxedLine(indent + (headerParts[i] ?? ""), innerWidth, opts.isColor));
|
|
9385
|
+
}
|
|
9386
|
+
if (body) {
|
|
9387
|
+
lines.push(boxedLine("", innerWidth, opts.isColor));
|
|
9388
|
+
for (const bline of body.split("\n")) {
|
|
9389
|
+
const trimmed = bline.trim();
|
|
9390
|
+
if (!trimmed) continue;
|
|
9391
|
+
const rendered = trimmed.replace(/^[-*]\s+/, opts.isColor ? `${import_picocolors2.default.green("\u2022")} ` : "\u2022 ");
|
|
9392
|
+
const wrapped = wrapLine(rendered, innerWidth);
|
|
9393
|
+
for (let i = 0; i < wrapped.length; i++) {
|
|
9394
|
+
lines.push(boxedLine((i === 0 ? "" : " ") + (wrapped[i] ?? ""), innerWidth, opts.isColor));
|
|
9395
|
+
}
|
|
9396
|
+
}
|
|
9397
|
+
}
|
|
9398
|
+
return [top, ...lines, bottom].join("\n");
|
|
9399
|
+
}
|
|
9400
|
+
function renderStatsLine(stats, isColor) {
|
|
9401
|
+
const parts = [];
|
|
9402
|
+
parts.push(`${stats.files} files`);
|
|
9403
|
+
parts.push(`+${stats.additions} \u2212${stats.deletions}`);
|
|
9404
|
+
if (stats.tokens !== void 0) parts.push(`${stats.tokens} tokens`);
|
|
9405
|
+
const text = parts.join(" \xB7 ");
|
|
9406
|
+
return isColor ? ` ${import_picocolors2.default.dim(text)}` : ` ${text}`;
|
|
9407
|
+
}
|
|
9408
|
+
function shouldUseRichOutput(opts) {
|
|
9409
|
+
if (!opts.isTTY) return false;
|
|
9410
|
+
if (opts.noColor) return false;
|
|
9411
|
+
if (opts.style !== "rich") return false;
|
|
9412
|
+
if (opts.width < MIN_BOX_WIDTH) return false;
|
|
9413
|
+
return true;
|
|
9414
|
+
}
|
|
9415
|
+
var import_picocolors2, HEADER_RX, MIN_BOX_WIDTH;
|
|
9416
|
+
var init_ui_rich = __esm({
|
|
9417
|
+
"src/ui-rich.ts"() {
|
|
9418
|
+
"use strict";
|
|
9419
|
+
import_picocolors2 = __toESM(require_picocolors());
|
|
9420
|
+
init_ui();
|
|
9421
|
+
HEADER_RX = /^([a-z]+)(?:\(([^)]+)\))?(!)?:\s*(.*)$/;
|
|
9422
|
+
MIN_BOX_WIDTH = 60;
|
|
9423
|
+
}
|
|
9424
|
+
});
|
|
9425
|
+
|
|
9426
|
+
// src/commit-helpers.ts
|
|
9427
|
+
function applyCliTypeScopeToRules(rules, type, scope) {
|
|
9428
|
+
let next = { ...rules };
|
|
9429
|
+
if (type) {
|
|
9430
|
+
next = { ...next, types: [type] };
|
|
9431
|
+
}
|
|
9432
|
+
if (scope) {
|
|
9433
|
+
next = { ...next, scopes: [scope] };
|
|
9434
|
+
}
|
|
9435
|
+
return next;
|
|
9436
|
+
}
|
|
9437
|
+
function generationHintsFromArgs(split, forceBody) {
|
|
9438
|
+
const h = {};
|
|
9439
|
+
if (split) h.split = true;
|
|
9440
|
+
if (forceBody) h.force_body = true;
|
|
9441
|
+
return Object.keys(h).length > 0 ? h : void 0;
|
|
9442
|
+
}
|
|
9443
|
+
function splitCommitMessageForDisplay(message) {
|
|
9444
|
+
const t = message.replace(/\r\n/g, "\n").trimEnd();
|
|
9445
|
+
const doubleNl = t.indexOf("\n\n");
|
|
9446
|
+
if (doubleNl !== -1) {
|
|
9447
|
+
const head = t.slice(0, doubleNl);
|
|
9448
|
+
const subject = head.split("\n")[0]?.trim() ?? "";
|
|
9449
|
+
return { subject, body: t.slice(doubleNl + 2).trimEnd() };
|
|
9450
|
+
}
|
|
9451
|
+
const firstNl = t.indexOf("\n");
|
|
9452
|
+
if (firstNl === -1) {
|
|
9453
|
+
return { subject: t.trim(), body: "" };
|
|
9454
|
+
}
|
|
9455
|
+
return {
|
|
9456
|
+
subject: t.slice(0, firstNl).trim(),
|
|
9457
|
+
body: t.slice(firstNl + 1).trimEnd()
|
|
9458
|
+
};
|
|
9459
|
+
}
|
|
9460
|
+
function formatVerboseCommitDiagnostics(diagnostics, roundTripMs) {
|
|
9461
|
+
const lines = [`api_round_trip_ms: ${roundTripMs}`];
|
|
9462
|
+
if (diagnostics !== void 0) {
|
|
9463
|
+
lines.push(JSON.stringify(diagnostics, null, 2));
|
|
9464
|
+
}
|
|
9465
|
+
return lines.join("\n");
|
|
9466
|
+
}
|
|
9467
|
+
async function interactiveRefineMessage(initial, opts) {
|
|
9468
|
+
if (opts.skip) return { action: "accept", message: initial };
|
|
9469
|
+
const rl = import_promises.default.createInterface({ input: process.stdin, output: process.stderr });
|
|
9470
|
+
try {
|
|
9471
|
+
process.stderr.write(`
|
|
9472
|
+
${initial}
|
|
9473
|
+
|
|
9474
|
+
`);
|
|
9475
|
+
const choice = (await rl.question("Keep? [Y/n/e]: ")).trim().toLowerCase();
|
|
9476
|
+
if (choice === "n") {
|
|
9477
|
+
return { action: "abort" };
|
|
9478
|
+
}
|
|
9479
|
+
if (choice === "e") {
|
|
9480
|
+
process.stderr.write("Enter new message (end with a line containing only .):\n");
|
|
9481
|
+
const lines = [];
|
|
9482
|
+
while (true) {
|
|
9483
|
+
const line = await rl.question("");
|
|
9484
|
+
if (line === ".") break;
|
|
9485
|
+
lines.push(line);
|
|
9486
|
+
}
|
|
9487
|
+
const edited = lines.join("\n").trim();
|
|
9488
|
+
return { action: "edit", message: edited.length > 0 ? edited : initial };
|
|
9489
|
+
}
|
|
9490
|
+
return { action: "accept", message: initial };
|
|
9491
|
+
} finally {
|
|
9492
|
+
rl.close();
|
|
9493
|
+
}
|
|
9494
|
+
}
|
|
9495
|
+
async function promptYesNo(question, defaultYes = true) {
|
|
9496
|
+
const rl = import_promises.default.createInterface({ input: process.stdin, output: process.stderr });
|
|
9497
|
+
const suffix = defaultYes ? "[Y/n]" : "[y/N]";
|
|
9498
|
+
try {
|
|
9499
|
+
const answer = (await rl.question(`${question} ${suffix} `)).trim().toLowerCase();
|
|
9500
|
+
if (answer === "n" || answer === "no") return false;
|
|
9501
|
+
if (answer === "y" || answer === "yes") return true;
|
|
9502
|
+
return defaultYes;
|
|
9503
|
+
} finally {
|
|
9504
|
+
rl.close();
|
|
9505
|
+
}
|
|
9506
|
+
}
|
|
9507
|
+
async function confirmCommit(prompt2, opts) {
|
|
9508
|
+
if (opts.skip) return { action: "commit" };
|
|
9509
|
+
const rl = import_promises.default.createInterface({ input: process.stdin, output: process.stderr });
|
|
9510
|
+
try {
|
|
9511
|
+
const ans = (await rl.question(prompt2)).trim().toLowerCase();
|
|
9512
|
+
if (ans !== "y" && ans !== "yes") {
|
|
9513
|
+
return { action: "abort" };
|
|
9514
|
+
}
|
|
9515
|
+
return { action: "commit" };
|
|
9516
|
+
} finally {
|
|
9517
|
+
rl.close();
|
|
9518
|
+
}
|
|
9519
|
+
}
|
|
9520
|
+
function shouldSkipTTYInteraction(hookMode) {
|
|
9521
|
+
return hookMode === true || process.stdin.isTTY !== true;
|
|
9522
|
+
}
|
|
9523
|
+
function logVerboseDiagnostics(dim, verbose, quiet, diagnostics, roundTripMs) {
|
|
9524
|
+
if (!verbose || quiet) return;
|
|
9525
|
+
process.stderr.write(
|
|
9526
|
+
`
|
|
9527
|
+
${formatVerboseCommitDiagnostics(diagnostics, roundTripMs)}
|
|
9528
|
+
`
|
|
9529
|
+
);
|
|
9530
|
+
dim("(verbose diagnostics on stderr)");
|
|
9531
|
+
}
|
|
9532
|
+
function isDisplayOpts(opts) {
|
|
9533
|
+
return typeof opts === "object" && opts !== null && "log" in opts;
|
|
9534
|
+
}
|
|
9535
|
+
function createSilentLog() {
|
|
9536
|
+
return {
|
|
9537
|
+
step: () => {
|
|
9538
|
+
},
|
|
9539
|
+
success: () => {
|
|
9540
|
+
},
|
|
9541
|
+
error: (msg) => console.error(msg),
|
|
9542
|
+
dim: () => {
|
|
9543
|
+
}
|
|
9544
|
+
};
|
|
9545
|
+
}
|
|
9546
|
+
function displayCommitMessage(message, opts) {
|
|
9547
|
+
const display = isDisplayOpts(opts) ? opts : { log: opts };
|
|
9548
|
+
const log = display.log;
|
|
9549
|
+
const { subject, body } = splitCommitMessageForDisplay(message);
|
|
9550
|
+
const tw = getTerminalWidth();
|
|
9551
|
+
const useRich = shouldUseRichOutput({
|
|
9552
|
+
isTTY: display.isTTY ?? !!process.stderr.isTTY,
|
|
9553
|
+
noColor: display.isColor === false,
|
|
9554
|
+
width: tw,
|
|
9555
|
+
style: display.style ?? "rich"
|
|
9556
|
+
});
|
|
9557
|
+
if (useRich) {
|
|
9558
|
+
const tree = display.stagedFiles && display.stagedFiles.length > 0 ? renderFileTree(display.stagedFiles, 8) : "";
|
|
9559
|
+
if (tree) {
|
|
9560
|
+
process.stderr.write(tree + "\n");
|
|
9561
|
+
}
|
|
9562
|
+
const boxed = renderBoxedCommit(subject, body, {
|
|
9563
|
+
width: Math.min(Math.max(tw - 4, 60), 80),
|
|
9564
|
+
isColor: !!display.isColor
|
|
9565
|
+
});
|
|
9566
|
+
process.stderr.write(boxed + "\n");
|
|
9567
|
+
if (display.stats) {
|
|
9568
|
+
process.stderr.write(renderStatsLine(display.stats, !!display.isColor) + "\n");
|
|
9569
|
+
}
|
|
9570
|
+
return;
|
|
9571
|
+
}
|
|
9572
|
+
log.success(subject);
|
|
9573
|
+
if (body) {
|
|
9574
|
+
for (const line of body.split("\n")) {
|
|
9575
|
+
log.dim(` ${line}`);
|
|
9576
|
+
}
|
|
9577
|
+
process.stderr.write("\n");
|
|
9578
|
+
}
|
|
9579
|
+
}
|
|
9580
|
+
var import_promises;
|
|
9581
|
+
var init_commit_helpers = __esm({
|
|
9582
|
+
"src/commit-helpers.ts"() {
|
|
9583
|
+
"use strict";
|
|
9584
|
+
import_promises = __toESM(require("node:readline/promises"));
|
|
9585
|
+
init_ui_rich();
|
|
9586
|
+
}
|
|
9587
|
+
});
|
|
9588
|
+
|
|
9589
|
+
// src/smart-diff.ts
|
|
9590
|
+
function sanitizeFilepath(path) {
|
|
9591
|
+
return path.replace(/[\x00-\x1F\x7F[\]`]/g, "_").slice(0, 200);
|
|
9592
|
+
}
|
|
9593
|
+
function classifyFile(filepath) {
|
|
9594
|
+
const basename = filepath.split("/").pop() ?? filepath;
|
|
9595
|
+
if (LOCK_FILES.has(basename)) return "lock";
|
|
9596
|
+
if (filepath.endsWith(".map")) return "sourcemap";
|
|
9597
|
+
if (VENDORED_PREFIXES.some((p) => filepath.startsWith(p))) return "vendored";
|
|
9598
|
+
if (GENERATED_PATTERNS.some((p) => p.test(filepath))) return "generated";
|
|
9599
|
+
return "code";
|
|
9600
|
+
}
|
|
9601
|
+
function parseDiffIntoFiles(diff) {
|
|
9602
|
+
const files = [];
|
|
9603
|
+
const parts = diff.split(/^(diff --git .+)$/m);
|
|
9604
|
+
for (let i = 1; i < parts.length; i += 2) {
|
|
9605
|
+
const header = parts[i];
|
|
9606
|
+
const content = parts[i + 1] ?? "";
|
|
9607
|
+
const match = header.match(/diff --git a\/(.+?) b\/(.+)/);
|
|
9608
|
+
const filepath = match?.[2] ?? "unknown";
|
|
9609
|
+
const lines = content.split("\n");
|
|
9610
|
+
let additions = 0;
|
|
9611
|
+
let deletions = 0;
|
|
9612
|
+
for (const line of lines) {
|
|
9613
|
+
if (line.startsWith("+") && !line.startsWith("+++")) additions++;
|
|
9614
|
+
else if (line.startsWith("-") && !line.startsWith("---")) deletions++;
|
|
9615
|
+
}
|
|
9616
|
+
files.push({ filepath, content: header + content, additions, deletions });
|
|
9617
|
+
}
|
|
9618
|
+
return files;
|
|
9619
|
+
}
|
|
9620
|
+
function isMinified(content) {
|
|
9621
|
+
const lines = content.split("\n").filter(
|
|
9622
|
+
(l) => (l.startsWith("+") || l.startsWith("-")) && !l.startsWith("+++") && !l.startsWith("---")
|
|
9623
|
+
);
|
|
9624
|
+
if (lines.length === 0) return false;
|
|
9625
|
+
return lines.some((l) => l.length > 500);
|
|
9626
|
+
}
|
|
9627
|
+
function buildFileSummary(file) {
|
|
9628
|
+
const sizeKB = Math.round(file.content.length / 1024);
|
|
9629
|
+
return `[modified: ${sanitizeFilepath(file.filepath)} \u2014 +${file.additions} \u2212${file.deletions} lines, ~${sizeKB}KB]
|
|
9630
|
+
`;
|
|
9631
|
+
}
|
|
9632
|
+
function preprocessDiffWithSizeBudget(diff, maxBytes = 5 * 1024 * 1024) {
|
|
9633
|
+
const files = parseDiffIntoFiles(diff);
|
|
9634
|
+
if (files.length === 0) {
|
|
9635
|
+
return { processedDiff: diff, summarized: [], aggressivelySummarized: [], tokensSaved: 0 };
|
|
9636
|
+
}
|
|
9637
|
+
const entries = [];
|
|
9638
|
+
const summarized = [];
|
|
9639
|
+
let tokensSaved = 0;
|
|
9640
|
+
for (const file of files) {
|
|
9641
|
+
const classification = classifyFile(file.filepath);
|
|
9642
|
+
switch (classification) {
|
|
9643
|
+
case "sourcemap":
|
|
9644
|
+
tokensSaved += estimateTokens(file.content);
|
|
9645
|
+
summarized.push(file.filepath);
|
|
9646
|
+
entries.push({ file, isNoise: true, summaryLine: null });
|
|
9647
|
+
break;
|
|
9648
|
+
case "lock":
|
|
9649
|
+
tokensSaved += estimateTokens(file.content);
|
|
9650
|
+
summarized.push(file.filepath);
|
|
9651
|
+
entries.push({
|
|
9652
|
+
file,
|
|
9653
|
+
isNoise: true,
|
|
9654
|
+
summaryLine: `[lock file updated: ${sanitizeFilepath(file.filepath)} (+${file.additions} \u2212${file.deletions} lines)]
|
|
9655
|
+
`
|
|
9656
|
+
});
|
|
9657
|
+
break;
|
|
9658
|
+
case "generated":
|
|
9659
|
+
tokensSaved += estimateTokens(file.content);
|
|
9660
|
+
summarized.push(file.filepath);
|
|
9661
|
+
entries.push({
|
|
9662
|
+
file,
|
|
9663
|
+
isNoise: true,
|
|
9664
|
+
summaryLine: `[generated: ${sanitizeFilepath(file.filepath)} (+${file.additions} \u2212${file.deletions})]
|
|
9665
|
+
`
|
|
9666
|
+
});
|
|
9667
|
+
break;
|
|
9668
|
+
case "vendored":
|
|
9669
|
+
tokensSaved += estimateTokens(file.content);
|
|
9670
|
+
summarized.push(file.filepath);
|
|
9671
|
+
entries.push({
|
|
9672
|
+
file,
|
|
9673
|
+
isNoise: true,
|
|
9674
|
+
summaryLine: `[vendored: ${sanitizeFilepath(file.filepath)} updated]
|
|
9675
|
+
`
|
|
9676
|
+
});
|
|
9677
|
+
break;
|
|
9678
|
+
case "code":
|
|
9679
|
+
if (isMinified(file.content)) {
|
|
9680
|
+
tokensSaved += estimateTokens(file.content);
|
|
9681
|
+
const sizeKB = Math.round(file.content.length / 1024);
|
|
9682
|
+
summarized.push(file.filepath);
|
|
9683
|
+
entries.push({
|
|
9684
|
+
file,
|
|
9685
|
+
isNoise: true,
|
|
9686
|
+
summaryLine: `[minified asset: ${sanitizeFilepath(file.filepath)} (${sizeKB} KB)]
|
|
9687
|
+
`
|
|
9688
|
+
});
|
|
9689
|
+
} else {
|
|
9690
|
+
entries.push({ file, isNoise: false, summaryLine: null });
|
|
9691
|
+
}
|
|
9692
|
+
break;
|
|
9693
|
+
}
|
|
9694
|
+
}
|
|
9695
|
+
const aggressiveMap = /* @__PURE__ */ new Map();
|
|
9696
|
+
function buildOutput() {
|
|
9697
|
+
const parts = [];
|
|
9698
|
+
for (const entry of entries) {
|
|
9699
|
+
if (entry.isNoise) {
|
|
9700
|
+
if (entry.summaryLine !== null) parts.push(entry.summaryLine);
|
|
9701
|
+
} else if (aggressiveMap.has(entry.file.filepath)) {
|
|
9702
|
+
parts.push(aggressiveMap.get(entry.file.filepath));
|
|
9703
|
+
} else {
|
|
9704
|
+
parts.push(entry.file.content);
|
|
9705
|
+
}
|
|
9706
|
+
}
|
|
9707
|
+
return parts.join("");
|
|
9708
|
+
}
|
|
9709
|
+
const codeEntries = entries.filter((e) => !e.isNoise);
|
|
9710
|
+
let output = buildOutput();
|
|
9711
|
+
if (output.length <= maxBytes) {
|
|
9712
|
+
return {
|
|
9713
|
+
processedDiff: output,
|
|
9714
|
+
summarized,
|
|
9715
|
+
aggressivelySummarized: [],
|
|
9716
|
+
tokensSaved
|
|
9717
|
+
};
|
|
9718
|
+
}
|
|
9719
|
+
const TIER1_THRESHOLD = 5 * 1024;
|
|
9720
|
+
for (const entry of codeEntries) {
|
|
9721
|
+
if (entry.file.content.length > TIER1_THRESHOLD && !aggressiveMap.has(entry.file.filepath)) {
|
|
9722
|
+
tokensSaved += estimateTokens(entry.file.content);
|
|
9723
|
+
aggressiveMap.set(entry.file.filepath, buildFileSummary(entry.file));
|
|
9724
|
+
}
|
|
9725
|
+
}
|
|
9726
|
+
output = buildOutput();
|
|
9727
|
+
if (output.length <= maxBytes) {
|
|
9728
|
+
return {
|
|
9729
|
+
processedDiff: output,
|
|
9730
|
+
summarized,
|
|
9731
|
+
aggressivelySummarized: [...aggressiveMap.keys()],
|
|
9732
|
+
tokensSaved
|
|
9733
|
+
};
|
|
9734
|
+
}
|
|
9735
|
+
const TIER2_THRESHOLD = 2 * 1024;
|
|
9736
|
+
for (const entry of codeEntries) {
|
|
9737
|
+
if (entry.file.content.length > TIER2_THRESHOLD && !aggressiveMap.has(entry.file.filepath)) {
|
|
9738
|
+
tokensSaved += estimateTokens(entry.file.content);
|
|
9739
|
+
aggressiveMap.set(entry.file.filepath, buildFileSummary(entry.file));
|
|
9740
|
+
}
|
|
9741
|
+
}
|
|
9742
|
+
output = buildOutput();
|
|
9743
|
+
if (output.length <= maxBytes) {
|
|
9744
|
+
return {
|
|
9745
|
+
processedDiff: output,
|
|
9746
|
+
summarized,
|
|
9747
|
+
aggressivelySummarized: [...aggressiveMap.keys()],
|
|
9748
|
+
tokensSaved
|
|
9749
|
+
};
|
|
9750
|
+
}
|
|
9751
|
+
for (const entry of codeEntries) {
|
|
9752
|
+
if (!aggressiveMap.has(entry.file.filepath)) {
|
|
9753
|
+
tokensSaved += estimateTokens(entry.file.content);
|
|
9754
|
+
aggressiveMap.set(entry.file.filepath, buildFileSummary(entry.file));
|
|
9755
|
+
}
|
|
9756
|
+
}
|
|
9757
|
+
return {
|
|
9758
|
+
processedDiff: buildOutput(),
|
|
9759
|
+
summarized,
|
|
9760
|
+
aggressivelySummarized: [...aggressiveMap.keys()],
|
|
9761
|
+
tokensSaved
|
|
9762
|
+
};
|
|
9763
|
+
}
|
|
9764
|
+
var LOCK_FILES, GENERATED_PATTERNS, VENDORED_PREFIXES;
|
|
9765
|
+
var init_smart_diff = __esm({
|
|
9766
|
+
"src/smart-diff.ts"() {
|
|
9767
|
+
"use strict";
|
|
9768
|
+
init_dist();
|
|
9769
|
+
LOCK_FILES = /* @__PURE__ */ new Set([
|
|
9770
|
+
"pnpm-lock.yaml",
|
|
9771
|
+
"package-lock.json",
|
|
9772
|
+
"yarn.lock",
|
|
9773
|
+
"Cargo.lock",
|
|
9774
|
+
"Gemfile.lock",
|
|
9775
|
+
"poetry.lock",
|
|
9776
|
+
"composer.lock",
|
|
9777
|
+
"bun.lockb",
|
|
9778
|
+
"shrinkwrap.json"
|
|
9779
|
+
]);
|
|
9780
|
+
GENERATED_PATTERNS = [
|
|
9781
|
+
/\.generated\.\w+$/,
|
|
9782
|
+
/\.g\.dart$/,
|
|
9783
|
+
/\.pb\.go$/,
|
|
9784
|
+
/\.pb\.ts$/,
|
|
9785
|
+
/(^|\/)\.prisma\/client\//,
|
|
9786
|
+
/\/generated\//
|
|
9787
|
+
];
|
|
9788
|
+
VENDORED_PREFIXES = ["vendor/", "third_party/", "node_modules/"];
|
|
9789
|
+
}
|
|
9790
|
+
});
|
|
9791
|
+
|
|
9792
|
+
// src/local.ts
|
|
9793
|
+
var local_exports = {};
|
|
9794
|
+
__export(local_exports, {
|
|
9795
|
+
generateLocalBranchName: () => generateLocalBranchName,
|
|
9796
|
+
getLocalProviderConfig: () => getLocalProviderConfig,
|
|
9797
|
+
runLocalBranch: () => runLocalBranch,
|
|
9798
|
+
runLocalCommit: () => runLocalCommit
|
|
9799
|
+
});
|
|
9800
|
+
function getLegacyProvider() {
|
|
9801
|
+
try {
|
|
9802
|
+
const p = (0, import_path8.join)(CONFIG_PATH2, "provider");
|
|
9803
|
+
if ((0, import_fs8.existsSync)(p)) {
|
|
9804
|
+
const v = (0, import_fs8.readFileSync)(p, "utf-8").trim().toLowerCase();
|
|
9805
|
+
if (["ollama", "lmstudio", "openrouter", "custom", "cloudflare"].includes(v)) {
|
|
9806
|
+
return v;
|
|
9807
|
+
}
|
|
9808
|
+
}
|
|
9809
|
+
} catch {
|
|
9810
|
+
}
|
|
9811
|
+
return null;
|
|
9812
|
+
}
|
|
9813
|
+
function getLegacyBaseUrl(provider) {
|
|
9814
|
+
try {
|
|
9815
|
+
const p = (0, import_path8.join)(CONFIG_PATH2, "base_url");
|
|
9816
|
+
if ((0, import_fs8.existsSync)(p)) {
|
|
9817
|
+
return (0, import_fs8.readFileSync)(p, "utf-8").trim();
|
|
9818
|
+
}
|
|
9819
|
+
} catch {
|
|
9820
|
+
}
|
|
9821
|
+
return PROVIDER_URLS[provider] ?? "";
|
|
9822
|
+
}
|
|
9823
|
+
function getLegacyModel(provider) {
|
|
9824
|
+
try {
|
|
9825
|
+
const p = (0, import_path8.join)(CONFIG_PATH2, "model");
|
|
9826
|
+
if ((0, import_fs8.existsSync)(p)) {
|
|
9827
|
+
const v = (0, import_fs8.readFileSync)(p, "utf-8").trim();
|
|
9828
|
+
if (v) return v;
|
|
9829
|
+
}
|
|
9830
|
+
} catch {
|
|
9831
|
+
}
|
|
9832
|
+
return DEFAULT_MODELS[provider] ?? "";
|
|
9833
|
+
}
|
|
9834
|
+
function getLocalProviderConfig() {
|
|
9835
|
+
const config2 = getConfig();
|
|
9836
|
+
const provider = config2.provider ?? getLegacyProvider();
|
|
9837
|
+
if (!provider) return null;
|
|
9838
|
+
const baseUrl = config2.apiUrl ?? getLegacyBaseUrl(provider) ?? PROVIDER_URLS[provider] ?? "";
|
|
9839
|
+
if (!baseUrl) return null;
|
|
9840
|
+
const model = config2.model ?? getLegacyModel(provider) ?? DEFAULT_MODELS[provider];
|
|
9841
|
+
const apiKey = provider === "openrouter" || provider === "custom" ? getApiKey() : null;
|
|
9842
|
+
if (provider === "openrouter" && !apiKey) return null;
|
|
9843
|
+
return { provider, baseUrl, model, apiKey };
|
|
9844
|
+
}
|
|
9845
|
+
function buildUserPrompt(changes, diff, rules, recentCommits, hints) {
|
|
9846
|
+
let prompt2 = `Generate a commit message for these changes:
|
|
9847
|
+
|
|
9848
|
+
## File changes:
|
|
9849
|
+
<file_changes>
|
|
9850
|
+
${changes}
|
|
9851
|
+
</file_changes>
|
|
9852
|
+
|
|
9853
|
+
## Diff:
|
|
9854
|
+
<diff>
|
|
9855
|
+
${diff}
|
|
9856
|
+
</diff>
|
|
9857
|
+
|
|
9858
|
+
`;
|
|
9859
|
+
if (recentCommits && recentCommits.length > 0) {
|
|
9860
|
+
const history = recentCommits.slice(0, 10).join("\n");
|
|
9861
|
+
prompt2 += `Recent commits on this branch (match style when appropriate):
|
|
9862
|
+
${history}
|
|
9863
|
+
|
|
9864
|
+
`;
|
|
9865
|
+
}
|
|
9866
|
+
if (hints?.split) {
|
|
9867
|
+
prompt2 += `MULTI-COMMIT MODE: If changes span multiple logical commits, focus the message on the primary change and mention other slices in the body.
|
|
9868
|
+
|
|
9869
|
+
`;
|
|
9870
|
+
}
|
|
9871
|
+
if (hints?.force_body) {
|
|
9872
|
+
prompt2 += `The user requires a BODY section after the subject line, even for small changes.
|
|
9873
|
+
|
|
9874
|
+
`;
|
|
9875
|
+
}
|
|
9876
|
+
if (rules && Object.keys(rules).length > 0) {
|
|
9877
|
+
prompt2 += `Rules: ${JSON.stringify(rules)}
|
|
9878
|
+
|
|
9879
|
+
`;
|
|
9880
|
+
}
|
|
9881
|
+
prompt2 += `Important:
|
|
9882
|
+
- Follow conventional commit format: <type>(<scope>): <subject>
|
|
9883
|
+
- Response should be the commit message only, no explanations`;
|
|
9884
|
+
return prompt2;
|
|
9885
|
+
}
|
|
9886
|
+
function buildRequest(provider, baseUrl, userContent, diff, changes, model, apiKey, rules, recentCommits, hints) {
|
|
9887
|
+
const headers = {
|
|
9888
|
+
"Content-Type": "application/json"
|
|
9889
|
+
};
|
|
9890
|
+
if (apiKey) {
|
|
9891
|
+
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
9892
|
+
}
|
|
9893
|
+
if (provider === "openrouter") {
|
|
9894
|
+
headers["HTTP-Referer"] = "https://github.com/Quikcommit-Internal/public";
|
|
9895
|
+
headers["X-Title"] = "qc - AI Commit Message Generator";
|
|
9896
|
+
}
|
|
9897
|
+
let url;
|
|
9898
|
+
let body;
|
|
9899
|
+
switch (provider) {
|
|
9900
|
+
case "ollama":
|
|
9901
|
+
url = `${baseUrl}/api/generate`;
|
|
9902
|
+
body = {
|
|
9903
|
+
model,
|
|
9904
|
+
prompt: userContent,
|
|
9905
|
+
stream: false,
|
|
9906
|
+
options: {}
|
|
9907
|
+
};
|
|
9908
|
+
return { url, body, headers: { "Content-Type": "application/json" } };
|
|
9909
|
+
case "lmstudio":
|
|
9910
|
+
url = `${baseUrl}/chat/completions`;
|
|
9911
|
+
body = {
|
|
9912
|
+
model,
|
|
9913
|
+
stream: false,
|
|
9914
|
+
messages: [
|
|
9915
|
+
{
|
|
9916
|
+
role: "system",
|
|
9917
|
+
content: "You are a git commit message generator. Create conventional commit messages."
|
|
9918
|
+
},
|
|
9919
|
+
{ role: "user", content: userContent }
|
|
9920
|
+
]
|
|
9921
|
+
};
|
|
9922
|
+
return { url, body, headers: { "Content-Type": "application/json" } };
|
|
9923
|
+
case "openrouter":
|
|
9924
|
+
case "custom":
|
|
9925
|
+
url = `${baseUrl}/chat/completions`;
|
|
9926
|
+
body = {
|
|
9927
|
+
model,
|
|
9928
|
+
stream: false,
|
|
9929
|
+
messages: [
|
|
9930
|
+
{
|
|
9931
|
+
role: "system",
|
|
9932
|
+
content: "You are a git commit message generator. Create conventional commit messages."
|
|
9933
|
+
},
|
|
9934
|
+
{ role: "user", content: userContent }
|
|
9935
|
+
]
|
|
9936
|
+
};
|
|
9937
|
+
return { url, body, headers };
|
|
9938
|
+
case "cloudflare": {
|
|
9939
|
+
url = `${baseUrl.replace(/\/$/, "")}/commit`;
|
|
9940
|
+
const payload = { diff, changes, rules };
|
|
9941
|
+
if (recentCommits && recentCommits.length > 0) {
|
|
9942
|
+
payload.recent_commits = recentCommits.slice(0, 10);
|
|
9943
|
+
}
|
|
9944
|
+
if (hints && Object.keys(hints).length > 0) {
|
|
9945
|
+
payload.generation_hints = hints;
|
|
9946
|
+
}
|
|
9947
|
+
body = payload;
|
|
9948
|
+
return { url, body, headers: { "Content-Type": "application/json" } };
|
|
9949
|
+
}
|
|
9950
|
+
default:
|
|
9951
|
+
throw new Error(`Unknown provider: ${provider}`);
|
|
9952
|
+
}
|
|
9953
|
+
}
|
|
9954
|
+
function parseResponse(provider, data) {
|
|
9955
|
+
const r = data;
|
|
9956
|
+
switch (provider) {
|
|
9957
|
+
case "ollama":
|
|
9958
|
+
return r.response ?? "";
|
|
9959
|
+
case "lmstudio":
|
|
9960
|
+
case "openrouter":
|
|
9961
|
+
case "custom": {
|
|
9962
|
+
const choices = r.choices;
|
|
9963
|
+
return choices?.[0]?.message?.content ?? "";
|
|
9964
|
+
}
|
|
9965
|
+
case "cloudflare":
|
|
9966
|
+
return r.commit?.response ?? "";
|
|
9967
|
+
default:
|
|
9968
|
+
return "";
|
|
9969
|
+
}
|
|
9970
|
+
}
|
|
9971
|
+
async function runLocalCommit(args) {
|
|
9972
|
+
const silent = !!(args.hookMode || args.quiet);
|
|
9973
|
+
const ui2 = getUI();
|
|
9974
|
+
const log = silent ? createSilentLog() : ui2.log;
|
|
9975
|
+
if (!isGitRepo()) {
|
|
9976
|
+
throw new Error("Not a git repository.");
|
|
9977
|
+
}
|
|
9978
|
+
if (!hasStagedChanges()) {
|
|
9979
|
+
throw new Error("No staged changes. Stage files with `git add` first.");
|
|
9980
|
+
}
|
|
9981
|
+
const local = getLocalProviderConfig();
|
|
9982
|
+
if (!local) {
|
|
9983
|
+
throw new Error(
|
|
9984
|
+
"No local provider configured. Set provider in ~/.config/qc/config.json or run with SaaS (qc login)."
|
|
9985
|
+
);
|
|
9986
|
+
}
|
|
9987
|
+
const config2 = getConfig();
|
|
9988
|
+
const excludes = [...config2.excludes ?? [], ...args.exclude];
|
|
9989
|
+
let diff = getStagedDiff(excludes);
|
|
9990
|
+
const changes = getStagedFiles();
|
|
9991
|
+
if (!args.noSmartDiff) {
|
|
9992
|
+
const smartResult = preprocessDiffWithSizeBudget(diff, 5 * 1024 * 1024);
|
|
9993
|
+
diff = smartResult.processedDiff;
|
|
9994
|
+
if (smartResult.summarized.length > 0 && !silent) {
|
|
9995
|
+
log.step(
|
|
9996
|
+
`smart-diff: ${smartResult.summarized.length} file(s) summarized (saved ~${Math.round(smartResult.tokensSaved / 1e3)}K tokens)`
|
|
9997
|
+
);
|
|
9998
|
+
}
|
|
9999
|
+
if (smartResult.aggressivelySummarized.length > 0 && !silent) {
|
|
10000
|
+
log.step(
|
|
10001
|
+
`large-diff: ${smartResult.aggressivelySummarized.length} additional file(s) summarized to fit (commit message may be less specific)`
|
|
10002
|
+
);
|
|
10003
|
+
}
|
|
10004
|
+
}
|
|
10005
|
+
let rules = { ...await detectCommitlintRules(), ...config2.rules ?? {} };
|
|
10006
|
+
const workspace = detectWorkspace();
|
|
10007
|
+
if (workspace) {
|
|
10008
|
+
const stagedFiles = changes.trim().split("\n").filter(Boolean);
|
|
10009
|
+
const scope = autoDetectScope(stagedFiles, workspace);
|
|
10010
|
+
if (scope) {
|
|
10011
|
+
const scopes = scope.split(",").map((s) => s.trim());
|
|
10012
|
+
rules = { ...rules, scopes };
|
|
10013
|
+
}
|
|
10014
|
+
}
|
|
10015
|
+
rules = applyCliTypeScopeToRules(rules, args.type, args.scope);
|
|
10016
|
+
const recentCommits = args.noContext ? void 0 : getRecentBranchCommits(5);
|
|
10017
|
+
const generationHints = generationHintsFromArgs(args.split, args.forceBody);
|
|
10018
|
+
const skipInteractive = silent || args.quiet || shouldSkipTTYInteraction(args.hookMode);
|
|
10019
|
+
const skipConfirm = args.dryRun || args.messageOnly || silent || args.quiet || shouldSkipTTYInteraction(args.hookMode);
|
|
10020
|
+
const model = args.model ?? local.model;
|
|
10021
|
+
const modelDisplay = model ?? local.model ?? "default";
|
|
10022
|
+
const userContent = buildUserPrompt(
|
|
10023
|
+
changes,
|
|
10024
|
+
diff,
|
|
10025
|
+
Object.keys(rules).length > 0 ? rules : void 0,
|
|
10026
|
+
recentCommits,
|
|
10027
|
+
generationHints
|
|
10028
|
+
);
|
|
10029
|
+
const { url, body, headers } = buildRequest(
|
|
10030
|
+
local.provider,
|
|
10031
|
+
local.baseUrl,
|
|
10032
|
+
userContent,
|
|
10033
|
+
diff,
|
|
10034
|
+
changes,
|
|
10035
|
+
model,
|
|
10036
|
+
local.apiKey,
|
|
10037
|
+
rules,
|
|
10038
|
+
recentCommits,
|
|
10039
|
+
generationHints
|
|
10040
|
+
);
|
|
10041
|
+
if (!url || url.includes("YOUR-WORKER")) {
|
|
10042
|
+
throw new Error(
|
|
10043
|
+
"Cloudflare provider requires api_url. Run: qc config set api_url https://your-worker.workers.dev"
|
|
10044
|
+
);
|
|
10045
|
+
}
|
|
10046
|
+
const spinner = ui2.spinner(`generating commit (${modelDisplay} via ${local.provider})...`);
|
|
10047
|
+
if (!silent) spinner.start();
|
|
10048
|
+
const t0 = Date.now();
|
|
10049
|
+
let res;
|
|
10050
|
+
try {
|
|
10051
|
+
res = await fetch(url, {
|
|
10052
|
+
method: "POST",
|
|
10053
|
+
headers,
|
|
10054
|
+
body: JSON.stringify(body)
|
|
10055
|
+
});
|
|
10056
|
+
} finally {
|
|
10057
|
+
spinner.stop();
|
|
10058
|
+
}
|
|
10059
|
+
const roundTripMs = Date.now() - t0;
|
|
10060
|
+
if (!res.ok) {
|
|
10061
|
+
const text = await res.text();
|
|
10062
|
+
throw new Error(`Provider error (${res.status}): ${text}`);
|
|
10063
|
+
}
|
|
10064
|
+
const data = await res.json();
|
|
10065
|
+
let message = parseResponse(local.provider, data);
|
|
10066
|
+
message = message.replace(/\\n/g, "\n").replace(/\\r/g, "").trim();
|
|
10067
|
+
if (!message) {
|
|
10068
|
+
throw new Error("Failed to generate commit message.");
|
|
10069
|
+
}
|
|
10070
|
+
const diagnostics = local.provider === "cloudflare" && typeof data === "object" && data !== null ? data.diagnostics : void 0;
|
|
10071
|
+
logVerboseDiagnostics((msg) => log.dim(msg), args.verbose, args.quiet, diagnostics, roundTripMs);
|
|
10072
|
+
if (args.interactive) {
|
|
10073
|
+
if (shouldSkipTTYInteraction(args.hookMode)) {
|
|
10074
|
+
if (!silent) log.dim("(--interactive ignored: not running in a TTY)");
|
|
10075
|
+
} else {
|
|
10076
|
+
const refineResult = await interactiveRefineMessage(message, { skip: skipInteractive });
|
|
10077
|
+
if (refineResult.action === "abort") {
|
|
10078
|
+
return;
|
|
10079
|
+
}
|
|
10080
|
+
message = refineResult.message;
|
|
10081
|
+
}
|
|
10082
|
+
}
|
|
10083
|
+
if (args.messageOnly) {
|
|
10084
|
+
console.log(message);
|
|
10085
|
+
return;
|
|
10086
|
+
}
|
|
10087
|
+
if (!silent) {
|
|
10088
|
+
const stagedPaths = changes.trim().split("\n").filter(Boolean);
|
|
10089
|
+
const short = getStagedDiffShortstat();
|
|
10090
|
+
const tokenEst = diagnostics && typeof diagnostics === "object" && diagnostics !== null && "tokenUsage" in diagnostics ? diagnostics.tokenUsage?.totalEstimated : void 0;
|
|
10091
|
+
displayCommitMessage(message, {
|
|
10092
|
+
log,
|
|
10093
|
+
isColor: ui2.isColor,
|
|
10094
|
+
isTTY: !!process.stderr.isTTY,
|
|
10095
|
+
style: "rich",
|
|
10096
|
+
stagedFiles: stagedPaths,
|
|
10097
|
+
stats: {
|
|
10098
|
+
files: stagedPaths.length,
|
|
10099
|
+
additions: short.additions,
|
|
10100
|
+
deletions: short.deletions,
|
|
10101
|
+
...tokenEst !== void 0 ? { tokens: tokenEst } : {}
|
|
10102
|
+
}
|
|
10103
|
+
});
|
|
10104
|
+
}
|
|
10105
|
+
if (args.dryRun) {
|
|
10106
|
+
return;
|
|
10107
|
+
}
|
|
10108
|
+
if (args.confirm) {
|
|
10109
|
+
const confirmResult = await confirmCommit("Proceed with commit? [y/N]: ", { skip: skipConfirm });
|
|
10110
|
+
if (confirmResult.action === "abort") {
|
|
10111
|
+
return;
|
|
10112
|
+
}
|
|
10113
|
+
}
|
|
10114
|
+
gitCommit(message);
|
|
10115
|
+
if (args.push) {
|
|
10116
|
+
gitPush();
|
|
10117
|
+
}
|
|
10118
|
+
}
|
|
10119
|
+
async function generateLocalBranchName(opts) {
|
|
10120
|
+
const local = getLocalProviderConfig();
|
|
10121
|
+
if (!local) {
|
|
10122
|
+
throw new Error("No local provider configured. Set provider with `qc --use-ollama` etc.");
|
|
10123
|
+
}
|
|
10124
|
+
const sections = [];
|
|
10125
|
+
sections.push("Generate a git branch name in the format <type>/<kebab-case-slug>.");
|
|
10126
|
+
sections.push("Type must be one of: feat, fix, refactor, perf, docs, test, chore, ci.");
|
|
10127
|
+
sections.push("Slug: 2-5 words, lowercase, hyphen-separated, max 55 chars.");
|
|
10128
|
+
sections.push("Output ONLY the branch name on a single line. No explanation.");
|
|
10129
|
+
sections.push("");
|
|
10130
|
+
if (opts.description) {
|
|
10131
|
+
sections.push("DESCRIPTION:");
|
|
10132
|
+
sections.push(opts.description);
|
|
10133
|
+
} else if (opts.recentCommits && opts.recentCommits.length > 0) {
|
|
10134
|
+
sections.push("RECENT COMMITS:");
|
|
10135
|
+
for (const c of opts.recentCommits) sections.push(`- ${c}`);
|
|
10136
|
+
} else if (opts.diff) {
|
|
10137
|
+
sections.push("DIFF:");
|
|
10138
|
+
sections.push(opts.diff.slice(0, 3e4));
|
|
10139
|
+
}
|
|
10140
|
+
const userContent = sections.join("\n");
|
|
10141
|
+
const model = opts.model ?? local.model;
|
|
10142
|
+
const headers = { "Content-Type": "application/json" };
|
|
10143
|
+
if (local.apiKey) headers.Authorization = `Bearer ${local.apiKey}`;
|
|
10144
|
+
if (local.provider === "openrouter") {
|
|
10145
|
+
headers["HTTP-Referer"] = "https://github.com/Quikcommit-Internal/public";
|
|
10146
|
+
headers["X-Title"] = "qc - AI Commit Message Generator";
|
|
10147
|
+
}
|
|
10148
|
+
let url;
|
|
10149
|
+
let body;
|
|
10150
|
+
switch (local.provider) {
|
|
10151
|
+
case "ollama":
|
|
10152
|
+
url = `${local.baseUrl}/api/generate`;
|
|
10153
|
+
body = { model, prompt: userContent, stream: false, options: {} };
|
|
10154
|
+
break;
|
|
10155
|
+
case "lmstudio":
|
|
10156
|
+
case "openrouter":
|
|
10157
|
+
case "custom":
|
|
10158
|
+
url = `${local.baseUrl}/chat/completions`;
|
|
10159
|
+
body = {
|
|
10160
|
+
model,
|
|
10161
|
+
stream: false,
|
|
10162
|
+
messages: [
|
|
10163
|
+
{
|
|
10164
|
+
role: "system",
|
|
10165
|
+
content: "You suggest concise git branch names. Reply with the branch name only."
|
|
10166
|
+
},
|
|
10167
|
+
{ role: "user", content: userContent }
|
|
10168
|
+
]
|
|
10169
|
+
};
|
|
10170
|
+
break;
|
|
10171
|
+
case "cloudflare":
|
|
10172
|
+
url = `${local.baseUrl.replace(/\/$/, "")}/branch`;
|
|
10173
|
+
body = {
|
|
10174
|
+
diff: opts.diff,
|
|
10175
|
+
changes: opts.changes,
|
|
10176
|
+
recent_commits: opts.recentCommits,
|
|
10177
|
+
description: opts.description,
|
|
10178
|
+
model,
|
|
10179
|
+
cf_model: model,
|
|
10180
|
+
...opts.rules ? { rules: opts.rules } : {}
|
|
10181
|
+
};
|
|
10182
|
+
break;
|
|
10183
|
+
}
|
|
10184
|
+
if (!url || url.includes("YOUR-WORKER")) {
|
|
10185
|
+
throw new Error(
|
|
10186
|
+
"Cloudflare provider requires api_url. Run: qc config set api_url https://your-worker.workers.dev"
|
|
10187
|
+
);
|
|
10188
|
+
}
|
|
10189
|
+
let res;
|
|
10190
|
+
try {
|
|
10191
|
+
res = await fetch(url, {
|
|
10192
|
+
method: "POST",
|
|
10193
|
+
headers,
|
|
10194
|
+
body: JSON.stringify(body)
|
|
10195
|
+
});
|
|
10196
|
+
} catch {
|
|
10197
|
+
const fallback = deterministicBranchName({ files: opts.changes?.split("\n").filter(Boolean), description: opts.description });
|
|
10198
|
+
return ensureUniqueName(fallback.name, branchExists);
|
|
10199
|
+
}
|
|
10200
|
+
if (!res.ok) {
|
|
10201
|
+
const fallback = deterministicBranchName({ files: opts.changes?.split("\n").filter(Boolean), description: opts.description });
|
|
10202
|
+
return ensureUniqueName(fallback.name, branchExists);
|
|
10203
|
+
}
|
|
10204
|
+
const data = await res.json();
|
|
10205
|
+
let raw;
|
|
10206
|
+
if (local.provider === "cloudflare") {
|
|
10207
|
+
const r = data;
|
|
10208
|
+
const br = r.branch;
|
|
10209
|
+
raw = typeof br?.name === "string" ? br.name : "";
|
|
10210
|
+
} else if (local.provider === "ollama") {
|
|
10211
|
+
raw = data.response ?? "";
|
|
10212
|
+
} else {
|
|
10213
|
+
const choices = data.choices;
|
|
10214
|
+
raw = choices?.[0]?.message?.content ?? "";
|
|
10215
|
+
}
|
|
10216
|
+
raw = raw.replace(/[\r\n].*$/s, "").trim();
|
|
10217
|
+
const sanitized = sanitizeBranchName(raw);
|
|
10218
|
+
if (!sanitized) {
|
|
10219
|
+
const fallback = deterministicBranchName({ files: opts.changes?.split("\n").filter(Boolean), description: opts.description });
|
|
10220
|
+
return ensureUniqueName(fallback.name, branchExists);
|
|
10221
|
+
}
|
|
10222
|
+
return ensureUniqueName(sanitized, branchExists);
|
|
10223
|
+
}
|
|
10224
|
+
async function runLocalBranch(opts) {
|
|
10225
|
+
const local = getLocalProviderConfig();
|
|
10226
|
+
if (!local) {
|
|
10227
|
+
throw new Error("No local provider configured. Set provider with `qc --use-ollama` etc.");
|
|
10228
|
+
}
|
|
10229
|
+
const ui2 = getUI();
|
|
10230
|
+
const log = ui2.log;
|
|
10231
|
+
const spinner = ui2.spinner(`generating branch name (${opts.model ?? local.model} via ${local.provider})...`);
|
|
10232
|
+
if (process.stderr.isTTY) spinner.start();
|
|
10233
|
+
let final;
|
|
10234
|
+
try {
|
|
10235
|
+
final = await generateLocalBranchName({
|
|
10236
|
+
description: opts.description,
|
|
10237
|
+
diff: opts.diff,
|
|
10238
|
+
changes: opts.changes,
|
|
10239
|
+
recentCommits: opts.recentCommits,
|
|
10240
|
+
model: opts.model,
|
|
10241
|
+
rules: opts.rules
|
|
10242
|
+
});
|
|
10243
|
+
} catch {
|
|
10244
|
+
const filesArr = opts.changes?.split("\n").filter(Boolean) ?? [];
|
|
10245
|
+
const fallback = deterministicBranchName({ files: filesArr, description: opts.description });
|
|
10246
|
+
final = ensureUniqueName(fallback.name, branchExists);
|
|
10247
|
+
log.dim("(used local fallback name; AI generation failed)");
|
|
10248
|
+
} finally {
|
|
10249
|
+
spinner.stop();
|
|
10250
|
+
}
|
|
10251
|
+
log.success(`branch name: ${final}`);
|
|
10252
|
+
const baseRef = opts.baseRef ?? "HEAD";
|
|
10253
|
+
if (opts.noSwitch) {
|
|
10254
|
+
createBranch(final, baseRef);
|
|
10255
|
+
log.success(`created ${final} (not switched)`);
|
|
10256
|
+
} else {
|
|
10257
|
+
createAndCheckoutBranch(final, baseRef);
|
|
10258
|
+
log.success(`switched to ${final}`);
|
|
10259
|
+
}
|
|
10260
|
+
if (opts.push) {
|
|
10261
|
+
gitPushSetUpstream(final);
|
|
10262
|
+
log.success(`pushed origin/${final}`);
|
|
10263
|
+
}
|
|
10264
|
+
}
|
|
10265
|
+
var import_fs8, import_path8, import_os4, CONFIG_PATH2, PROVIDER_URLS, DEFAULT_MODELS;
|
|
10266
|
+
var init_local = __esm({
|
|
10267
|
+
"src/local.ts"() {
|
|
10268
|
+
"use strict";
|
|
10269
|
+
import_fs8 = require("fs");
|
|
10270
|
+
import_path8 = require("path");
|
|
10271
|
+
import_os4 = require("os");
|
|
10272
|
+
init_config();
|
|
10273
|
+
init_dist();
|
|
10274
|
+
init_git();
|
|
10275
|
+
init_monorepo();
|
|
10276
|
+
init_commitlint();
|
|
10277
|
+
init_smart_diff();
|
|
10278
|
+
init_ui();
|
|
10279
|
+
init_commit_helpers();
|
|
10280
|
+
init_branch_name();
|
|
10281
|
+
CONFIG_PATH2 = (0, import_path8.join)((0, import_os4.homedir)(), CONFIG_DIR);
|
|
10282
|
+
PROVIDER_URLS = {
|
|
10283
|
+
ollama: "http://localhost:11434",
|
|
10284
|
+
lmstudio: "http://localhost:1234/v1",
|
|
10285
|
+
openrouter: "https://openrouter.ai/api/v1",
|
|
10286
|
+
custom: "",
|
|
10287
|
+
cloudflare: ""
|
|
10288
|
+
};
|
|
10289
|
+
DEFAULT_MODELS = {
|
|
10290
|
+
ollama: "codellama",
|
|
10291
|
+
lmstudio: "default",
|
|
10292
|
+
openrouter: "google/gemini-flash-1.5-8b",
|
|
10293
|
+
custom: "",
|
|
10294
|
+
cloudflare: "@cf/qwen/qwen2.5-coder-32b-instruct"
|
|
10295
|
+
};
|
|
10296
|
+
}
|
|
10297
|
+
});
|
|
10298
|
+
|
|
10299
|
+
// src/commands/branch.ts
|
|
10300
|
+
var branch_exports = {};
|
|
10301
|
+
__export(branch_exports, {
|
|
10302
|
+
runBranch: () => runBranch
|
|
10303
|
+
});
|
|
10304
|
+
function branchGenerationRules(cfg) {
|
|
10305
|
+
const types = cfg.branch?.generation?.types;
|
|
10306
|
+
if (types && types.length > 0) return { types: [...types] };
|
|
10307
|
+
return void 0;
|
|
10308
|
+
}
|
|
10309
|
+
function finalizeGeneratedBranchName(raw) {
|
|
10310
|
+
return finalizeBranchName(raw, branchExists);
|
|
10311
|
+
}
|
|
10312
|
+
async function runBranch(opts) {
|
|
10313
|
+
const ui2 = getUI();
|
|
10314
|
+
const log = ui2.log;
|
|
10315
|
+
if (!isGitRepo()) {
|
|
10316
|
+
log.error("Not a git repository.");
|
|
10317
|
+
process.exit(1);
|
|
10318
|
+
}
|
|
10319
|
+
const baseRef = opts.from ?? "HEAD";
|
|
10320
|
+
const config2 = getConfig();
|
|
10321
|
+
const model = opts.model ?? config2.model;
|
|
10322
|
+
const genRules = branchGenerationRules(config2);
|
|
10323
|
+
if (opts.rescue) {
|
|
10324
|
+
const state = detectProtectedBranchState({
|
|
10325
|
+
protectedBranches: config2.branch?.protectedBranches,
|
|
10326
|
+
detectDefault: config2.branch?.detectDefault
|
|
10327
|
+
});
|
|
10328
|
+
if (!state.isProtected) {
|
|
10329
|
+
throw new Error(
|
|
10330
|
+
"`--rescue` only applies on a protected branch (e.g. main). The current branch is not protected."
|
|
10331
|
+
);
|
|
10332
|
+
}
|
|
10333
|
+
if (state.commitsAhead === 0) {
|
|
10334
|
+
throw new Error(
|
|
10335
|
+
"No commits ahead of upstream to rescue. Push your branch or use `qc branch` without `--rescue`."
|
|
10336
|
+
);
|
|
10337
|
+
}
|
|
10338
|
+
let final2;
|
|
10339
|
+
if (opts.explicitName) {
|
|
10340
|
+
const sanitized = sanitizeBranchName(opts.explicitName);
|
|
10341
|
+
if (!sanitized) {
|
|
10342
|
+
throw new Error(`invalid branch name: ${opts.explicitName}`);
|
|
10343
|
+
}
|
|
10344
|
+
final2 = finalizeBranchName(sanitized, branchExists);
|
|
10345
|
+
} else {
|
|
10346
|
+
const recent = getRecentBranchCommits(state.commitsAhead);
|
|
10347
|
+
const apiKey2 = opts.apiKey ?? getApiKey();
|
|
10348
|
+
if (apiKey2) {
|
|
10349
|
+
const spinner2 = ui2.spinner(`generating branch name (${model ?? "default"})...`);
|
|
10350
|
+
if (process.stderr.isTTY) spinner2.start();
|
|
10351
|
+
try {
|
|
10352
|
+
const client = new ApiClient({ apiKey: apiKey2 });
|
|
10353
|
+
try {
|
|
10354
|
+
const result2 = await client.generateBranchName({
|
|
10355
|
+
recent_commits: recent,
|
|
10356
|
+
model: opts.model,
|
|
10357
|
+
description: opts.message,
|
|
10358
|
+
rules: genRules
|
|
10359
|
+
});
|
|
10360
|
+
final2 = finalizeGeneratedBranchName(result2.name);
|
|
10361
|
+
} catch {
|
|
10362
|
+
const fallback = deterministicBranchName({
|
|
10363
|
+
description: recent.join(" ") || opts.message
|
|
10364
|
+
});
|
|
10365
|
+
final2 = finalizeBranchName(fallback.name, branchExists);
|
|
10366
|
+
log.dim("(used deterministic fallback name; API generation failed)");
|
|
10367
|
+
}
|
|
10368
|
+
} finally {
|
|
10369
|
+
spinner2.stop();
|
|
10370
|
+
}
|
|
10371
|
+
} else {
|
|
10372
|
+
const { getLocalProviderConfig: getLocalProviderConfig2, generateLocalBranchName: generateLocalBranchName2 } = await Promise.resolve().then(() => (init_local(), local_exports));
|
|
10373
|
+
if (!getLocalProviderConfig2()) {
|
|
10374
|
+
throw new Error(
|
|
10375
|
+
"Not authenticated. Run `qc login` first, or configure a local provider for `--rescue`."
|
|
10376
|
+
);
|
|
10377
|
+
}
|
|
10378
|
+
const spinner2 = ui2.spinner(`generating branch name (${model ?? "default"} via local)...`);
|
|
10379
|
+
if (process.stderr.isTTY) spinner2.start();
|
|
10380
|
+
try {
|
|
10381
|
+
try {
|
|
10382
|
+
const name = await generateLocalBranchName2({
|
|
10383
|
+
recentCommits: recent,
|
|
10384
|
+
model: opts.model,
|
|
10385
|
+
description: opts.message,
|
|
10386
|
+
rules: genRules
|
|
10387
|
+
});
|
|
10388
|
+
final2 = finalizeBranchName(name, branchExists, { skipUniqueness: true });
|
|
10389
|
+
} catch {
|
|
10390
|
+
const fallback = deterministicBranchName({
|
|
10391
|
+
description: recent.join(" ") || opts.message
|
|
10392
|
+
});
|
|
10393
|
+
final2 = finalizeBranchName(fallback.name, branchExists);
|
|
10394
|
+
log.dim("(used deterministic fallback name; local provider failed)");
|
|
10395
|
+
}
|
|
10396
|
+
} finally {
|
|
10397
|
+
spinner2.stop();
|
|
10398
|
+
}
|
|
10399
|
+
}
|
|
10400
|
+
}
|
|
10401
|
+
log.success(`branch name: ${final2}`);
|
|
10402
|
+
if (opts.dryRun) {
|
|
10403
|
+
log.dim("(dry-run; not running rescue)");
|
|
10404
|
+
return;
|
|
10405
|
+
}
|
|
10406
|
+
if (!process.stdin.isTTY) {
|
|
10407
|
+
throw new Error("`--rescue` requires an interactive terminal to confirm (or use `qc branch <name>` after arranging commits manually).");
|
|
10408
|
+
}
|
|
10409
|
+
log.dim(
|
|
10410
|
+
`About to: 1) create ${final2} at HEAD, 2) reset ${state.branch} to upstream, 3) switch to ${final2}`
|
|
10411
|
+
);
|
|
10412
|
+
if (!await promptYesNo("Continue with rescue?")) {
|
|
10413
|
+
log.dim("aborted.");
|
|
10414
|
+
return;
|
|
10415
|
+
}
|
|
10416
|
+
rescueCommits({ currentBranch: state.branch, newBranch: final2 });
|
|
10417
|
+
log.success(`moved ${state.commitsAhead} commit(s) to ${final2}`);
|
|
10418
|
+
log.success(`${state.branch} reset to upstream`);
|
|
10419
|
+
if (opts.push) {
|
|
10420
|
+
gitPushSetUpstream(final2);
|
|
10421
|
+
log.success(`pushed origin/${final2}`);
|
|
10422
|
+
}
|
|
10423
|
+
return;
|
|
10424
|
+
}
|
|
10425
|
+
if (opts.explicitName) {
|
|
10426
|
+
const sanitized = sanitizeBranchName(opts.explicitName);
|
|
10427
|
+
if (!sanitized) {
|
|
10428
|
+
throw new Error(`invalid branch name: ${opts.explicitName}`);
|
|
10429
|
+
}
|
|
10430
|
+
const final2 = finalizeBranchName(sanitized, branchExists);
|
|
10431
|
+
if (opts.dryRun) {
|
|
10432
|
+
log.success(`would create branch: ${final2}`);
|
|
10433
|
+
return;
|
|
10434
|
+
}
|
|
10435
|
+
if (opts.noSwitch) {
|
|
10436
|
+
createBranch(final2, baseRef);
|
|
10437
|
+
log.success(`created branch ${final2} (not switched)`);
|
|
10438
|
+
} else {
|
|
10439
|
+
createAndCheckoutBranch(final2, baseRef);
|
|
10440
|
+
log.success(`switched to ${final2}`);
|
|
10441
|
+
}
|
|
10442
|
+
if (opts.push) {
|
|
10443
|
+
gitPushSetUpstream(final2);
|
|
10444
|
+
log.success(`pushed origin/${final2}`);
|
|
10445
|
+
}
|
|
10446
|
+
return;
|
|
10447
|
+
}
|
|
10448
|
+
const payload = { model, rules: genRules };
|
|
10449
|
+
if (opts.message) {
|
|
10450
|
+
payload.description = opts.message;
|
|
10451
|
+
} else if (opts.fromCommits) {
|
|
10452
|
+
payload.recent_commits = getRecentBranchCommits(10);
|
|
10453
|
+
} else {
|
|
10454
|
+
if (!hasStagedChanges()) {
|
|
10455
|
+
throw new Error(
|
|
10456
|
+
"No staged changes detected. Stage with `git add`, or provide -m '<description>'."
|
|
10457
|
+
);
|
|
10458
|
+
}
|
|
10459
|
+
payload.diff = getStagedDiff(config2.excludes ?? []);
|
|
10460
|
+
payload.changes = getStagedFiles();
|
|
10461
|
+
}
|
|
10462
|
+
const apiKey = opts.apiKey ?? getApiKey();
|
|
10463
|
+
if (!apiKey) {
|
|
10464
|
+
const { getLocalProviderConfig: getLocalProviderConfig2, runLocalBranch: runLocalBranch2 } = await Promise.resolve().then(() => (init_local(), local_exports));
|
|
10465
|
+
if (getLocalProviderConfig2()) {
|
|
10466
|
+
await runLocalBranch2({
|
|
10467
|
+
description: opts.message,
|
|
10468
|
+
diff: opts.message ? void 0 : payload.diff,
|
|
10469
|
+
changes: opts.message ? void 0 : payload.changes,
|
|
10470
|
+
recentCommits: payload.recent_commits,
|
|
10471
|
+
model: opts.model,
|
|
10472
|
+
noSwitch: opts.noSwitch,
|
|
10473
|
+
push: opts.push,
|
|
10474
|
+
baseRef,
|
|
10475
|
+
rules: genRules
|
|
10476
|
+
});
|
|
10477
|
+
return;
|
|
10478
|
+
}
|
|
10479
|
+
throw new Error("Not authenticated. Run `qc login` first, or provide --message.");
|
|
10480
|
+
}
|
|
10481
|
+
const spinner = ui2.spinner(`generating branch name (${model ?? "default"})...`);
|
|
10482
|
+
if (process.stderr.isTTY) spinner.start();
|
|
10483
|
+
let result;
|
|
10484
|
+
try {
|
|
10485
|
+
const client = new ApiClient({ apiKey });
|
|
10486
|
+
result = await client.generateBranchName(payload);
|
|
10487
|
+
} finally {
|
|
10488
|
+
spinner.stop();
|
|
10489
|
+
}
|
|
10490
|
+
const final = finalizeGeneratedBranchName(result.name);
|
|
10491
|
+
log.success(`branch name: ${final}`);
|
|
10492
|
+
if (opts.dryRun) {
|
|
10493
|
+
log.dim(`(dry-run; not creating)`);
|
|
10494
|
+
return;
|
|
10495
|
+
}
|
|
10496
|
+
if (opts.noSwitch) {
|
|
10497
|
+
createBranch(final, baseRef);
|
|
10498
|
+
log.success(`created ${final} (not switched)`);
|
|
10499
|
+
} else {
|
|
10500
|
+
createAndCheckoutBranch(final, baseRef);
|
|
10501
|
+
log.success(`switched to ${final}`);
|
|
10502
|
+
}
|
|
10503
|
+
if (opts.push) {
|
|
10504
|
+
gitPushSetUpstream(final);
|
|
10505
|
+
log.success(`pushed origin/${final}`);
|
|
10506
|
+
}
|
|
10507
|
+
}
|
|
10508
|
+
var init_branch2 = __esm({
|
|
10509
|
+
"src/commands/branch.ts"() {
|
|
10510
|
+
"use strict";
|
|
10511
|
+
init_api();
|
|
10512
|
+
init_config();
|
|
10513
|
+
init_branch_rescue();
|
|
10514
|
+
init_protected_branch_guard();
|
|
10515
|
+
init_git();
|
|
10516
|
+
init_branch_name();
|
|
10517
|
+
init_commit_helpers();
|
|
10518
|
+
init_ui();
|
|
10519
|
+
}
|
|
10520
|
+
});
|
|
10521
|
+
|
|
8700
10522
|
// src/commands/init.ts
|
|
8701
10523
|
var init_exports = {};
|
|
8702
10524
|
__export(init_exports, {
|
|
@@ -8712,12 +10534,12 @@ function init(options) {
|
|
|
8712
10534
|
console.error("Error: Not a git repository");
|
|
8713
10535
|
process.exit(1);
|
|
8714
10536
|
}
|
|
8715
|
-
const hookPath = (0,
|
|
10537
|
+
const hookPath = (0, import_path9.join)(hooksDir, "prepare-commit-msg");
|
|
8716
10538
|
if (options.uninstall) {
|
|
8717
|
-
if ((0,
|
|
8718
|
-
const content = (0,
|
|
10539
|
+
if ((0, import_fs9.existsSync)(hookPath)) {
|
|
10540
|
+
const content = (0, import_fs9.readFileSync)(hookPath, "utf-8");
|
|
8719
10541
|
if (content.includes("Quikcommit")) {
|
|
8720
|
-
(0,
|
|
10542
|
+
(0, import_fs9.unlinkSync)(hookPath);
|
|
8721
10543
|
console.log("Quikcommit hook removed.");
|
|
8722
10544
|
} else {
|
|
8723
10545
|
console.log("Hook exists but was not installed by Quikcommit. Skipping.");
|
|
@@ -8727,8 +10549,8 @@ function init(options) {
|
|
|
8727
10549
|
}
|
|
8728
10550
|
return;
|
|
8729
10551
|
}
|
|
8730
|
-
if ((0,
|
|
8731
|
-
const content = (0,
|
|
10552
|
+
if ((0, import_fs9.existsSync)(hookPath)) {
|
|
10553
|
+
const content = (0, import_fs9.readFileSync)(hookPath, "utf-8");
|
|
8732
10554
|
if (content.includes("Quikcommit")) {
|
|
8733
10555
|
console.log("Quikcommit hook is already installed.");
|
|
8734
10556
|
return;
|
|
@@ -8738,17 +10560,17 @@ function init(options) {
|
|
|
8738
10560
|
);
|
|
8739
10561
|
process.exit(1);
|
|
8740
10562
|
}
|
|
8741
|
-
(0,
|
|
8742
|
-
(0,
|
|
10563
|
+
(0, import_fs9.writeFileSync)(hookPath, HOOK_CONTENT);
|
|
10564
|
+
(0, import_fs9.chmodSync)(hookPath, 493);
|
|
8743
10565
|
console.log("Quikcommit hook installed.");
|
|
8744
10566
|
console.log("Now just run `git commit` and a message will be generated automatically.");
|
|
8745
10567
|
}
|
|
8746
|
-
var
|
|
10568
|
+
var import_fs9, import_path9, import_child_process6, HOOK_CONTENT;
|
|
8747
10569
|
var init_init = __esm({
|
|
8748
10570
|
"src/commands/init.ts"() {
|
|
8749
10571
|
"use strict";
|
|
8750
|
-
|
|
8751
|
-
|
|
10572
|
+
import_fs9 = require("fs");
|
|
10573
|
+
import_path9 = require("path");
|
|
8752
10574
|
import_child_process6 = require("child_process");
|
|
8753
10575
|
HOOK_CONTENT = `#!/bin/sh
|
|
8754
10576
|
# Quikcommit - auto-generate commit messages
|
|
@@ -8823,10 +10645,10 @@ function detectLocalCommitlintRules() {
|
|
|
8823
10645
|
"commitlint.config.mjs"
|
|
8824
10646
|
];
|
|
8825
10647
|
for (const file of files) {
|
|
8826
|
-
const path = (0,
|
|
8827
|
-
if (!(0,
|
|
10648
|
+
const path = (0, import_path10.join)(cwd, file);
|
|
10649
|
+
if (!(0, import_fs10.existsSync)(path)) continue;
|
|
8828
10650
|
try {
|
|
8829
|
-
const content = (0,
|
|
10651
|
+
const content = (0, import_fs10.readFileSync)(path, "utf-8");
|
|
8830
10652
|
let parsed;
|
|
8831
10653
|
if (file.endsWith(".json") || file === ".commitlintrc") {
|
|
8832
10654
|
parsed = JSON.parse(content);
|
|
@@ -8838,10 +10660,10 @@ function detectLocalCommitlintRules() {
|
|
|
8838
10660
|
} catch {
|
|
8839
10661
|
}
|
|
8840
10662
|
}
|
|
8841
|
-
const pkgPath = (0,
|
|
8842
|
-
if ((0,
|
|
10663
|
+
const pkgPath = (0, import_path10.join)(cwd, "package.json");
|
|
10664
|
+
if ((0, import_fs10.existsSync)(pkgPath)) {
|
|
8843
10665
|
try {
|
|
8844
|
-
const content = (0,
|
|
10666
|
+
const content = (0, import_fs10.readFileSync)(pkgPath, "utf-8");
|
|
8845
10667
|
const pkg = JSON.parse(content);
|
|
8846
10668
|
if (pkg.commitlint) {
|
|
8847
10669
|
const rules = mapCommitlintToRules(pkg.commitlint);
|
|
@@ -8904,20 +10726,20 @@ async function team(subcommand, args) {
|
|
|
8904
10726
|
process.exit(1);
|
|
8905
10727
|
}
|
|
8906
10728
|
}
|
|
8907
|
-
var
|
|
10729
|
+
var import_fs10, import_path10;
|
|
8908
10730
|
var init_team = __esm({
|
|
8909
10731
|
"src/commands/team.ts"() {
|
|
8910
10732
|
"use strict";
|
|
8911
|
-
|
|
8912
|
-
|
|
10733
|
+
import_fs10 = require("fs");
|
|
10734
|
+
import_path10 = require("path");
|
|
8913
10735
|
init_api();
|
|
8914
10736
|
init_config();
|
|
8915
10737
|
}
|
|
8916
10738
|
});
|
|
8917
10739
|
|
|
8918
10740
|
// src/commands/config.ts
|
|
8919
|
-
var
|
|
8920
|
-
__export(
|
|
10741
|
+
var config_exports2 = {};
|
|
10742
|
+
__export(config_exports2, {
|
|
8921
10743
|
config: () => config
|
|
8922
10744
|
});
|
|
8923
10745
|
function config(args) {
|
|
@@ -9030,459 +10852,698 @@ var init_upgrade = __esm({
|
|
|
9030
10852
|
}
|
|
9031
10853
|
});
|
|
9032
10854
|
|
|
9033
|
-
// src/
|
|
9034
|
-
|
|
9035
|
-
|
|
9036
|
-
|
|
9037
|
-
|
|
9038
|
-
|
|
9039
|
-
|
|
9040
|
-
|
|
9041
|
-
|
|
9042
|
-
|
|
9043
|
-
|
|
9044
|
-
|
|
9045
|
-
|
|
9046
|
-
|
|
9047
|
-
|
|
9048
|
-
|
|
10855
|
+
// src/branch-guard.ts
|
|
10856
|
+
async function runBranchGuard(args, log) {
|
|
10857
|
+
if (!shouldRunGuard({
|
|
10858
|
+
allowProtected: !!args.allowProtected,
|
|
10859
|
+
hookMode: !!args.hookMode,
|
|
10860
|
+
isTTY: !!process.stdin.isTTY
|
|
10861
|
+
})) {
|
|
10862
|
+
return { action: "continue" };
|
|
10863
|
+
}
|
|
10864
|
+
const { getConfig: getConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
10865
|
+
const config2 = getConfig2();
|
|
10866
|
+
const state = detectProtectedBranchState({
|
|
10867
|
+
protectedBranches: config2.branch?.protectedBranches,
|
|
10868
|
+
detectDefault: config2.branch?.detectDefault
|
|
10869
|
+
});
|
|
10870
|
+
if (!state.isProtected) {
|
|
10871
|
+
return { action: "continue" };
|
|
9049
10872
|
}
|
|
9050
|
-
|
|
9051
|
-
}
|
|
9052
|
-
|
|
9053
|
-
|
|
9054
|
-
|
|
9055
|
-
|
|
9056
|
-
|
|
9057
|
-
|
|
9058
|
-
|
|
10873
|
+
log.error(
|
|
10874
|
+
`You're on ${state.branch} (a protected branch).` + (state.commitsAhead > 0 ? ` ${state.commitsAhead} commit(s) ahead of upstream.` : "")
|
|
10875
|
+
);
|
|
10876
|
+
let action;
|
|
10877
|
+
let usedConfigDefault = false;
|
|
10878
|
+
if (args.autoBranch) {
|
|
10879
|
+
action = "branch";
|
|
10880
|
+
} else if (config2.branch?.defaultAction === "branch") {
|
|
10881
|
+
action = "branch";
|
|
10882
|
+
usedConfigDefault = true;
|
|
10883
|
+
} else if (config2.branch?.defaultAction === "continue") {
|
|
10884
|
+
action = "continue";
|
|
10885
|
+
usedConfigDefault = true;
|
|
10886
|
+
} else {
|
|
10887
|
+
action = await promptProtectedAction(state.mode);
|
|
9059
10888
|
}
|
|
9060
|
-
|
|
9061
|
-
|
|
9062
|
-
function getLegacyModel(provider) {
|
|
9063
|
-
try {
|
|
9064
|
-
const p = (0, import_path10.join)(CONFIG_PATH2, "model");
|
|
9065
|
-
if ((0, import_fs10.existsSync)(p)) {
|
|
9066
|
-
const v = (0, import_fs10.readFileSync)(p, "utf-8").trim();
|
|
9067
|
-
if (v) return v;
|
|
9068
|
-
}
|
|
9069
|
-
} catch {
|
|
10889
|
+
if (action === "continue" && usedConfigDefault) {
|
|
10890
|
+
log.dim("(continuing on protected branch per config `branch.defaultAction`)");
|
|
9070
10891
|
}
|
|
9071
|
-
|
|
9072
|
-
|
|
9073
|
-
|
|
9074
|
-
const config2 = getConfig();
|
|
9075
|
-
const provider = config2.provider ?? getLegacyProvider();
|
|
9076
|
-
if (!provider) return null;
|
|
9077
|
-
const baseUrl = config2.apiUrl ?? getLegacyBaseUrl(provider) ?? PROVIDER_URLS[provider] ?? "";
|
|
9078
|
-
if (!baseUrl) return null;
|
|
9079
|
-
const model = config2.model ?? getLegacyModel(provider) ?? DEFAULT_MODELS[provider];
|
|
9080
|
-
const apiKey = provider === "openrouter" || provider === "custom" ? getApiKey() : null;
|
|
9081
|
-
if (provider === "openrouter" && !apiKey) return null;
|
|
9082
|
-
return { provider, baseUrl, model, apiKey };
|
|
9083
|
-
}
|
|
9084
|
-
function buildUserPrompt(changes, diff, rules) {
|
|
9085
|
-
let prompt2 = `Generate a commit message for these changes:
|
|
9086
|
-
|
|
9087
|
-
## File changes:
|
|
9088
|
-
<file_changes>
|
|
9089
|
-
${changes}
|
|
9090
|
-
</file_changes>
|
|
9091
|
-
|
|
9092
|
-
## Diff:
|
|
9093
|
-
<diff>
|
|
9094
|
-
${diff}
|
|
9095
|
-
</diff>
|
|
9096
|
-
|
|
9097
|
-
`;
|
|
9098
|
-
if (rules && Object.keys(rules).length > 0) {
|
|
9099
|
-
prompt2 += `Rules: ${JSON.stringify(rules)}
|
|
9100
|
-
|
|
9101
|
-
`;
|
|
10892
|
+
if (action === "abort") {
|
|
10893
|
+
log.dim("aborted.");
|
|
10894
|
+
return { action: "abort" };
|
|
9102
10895
|
}
|
|
9103
|
-
|
|
9104
|
-
|
|
9105
|
-
- Response should be the commit message only, no explanations`;
|
|
9106
|
-
return prompt2;
|
|
9107
|
-
}
|
|
9108
|
-
function buildRequest(provider, baseUrl, userContent, diff, changes, model, apiKey, rules) {
|
|
9109
|
-
const headers = {
|
|
9110
|
-
"Content-Type": "application/json"
|
|
9111
|
-
};
|
|
9112
|
-
if (apiKey) {
|
|
9113
|
-
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
10896
|
+
if (action === "continue") {
|
|
10897
|
+
return { action: "continue" };
|
|
9114
10898
|
}
|
|
9115
|
-
|
|
9116
|
-
|
|
9117
|
-
|
|
10899
|
+
const stagedDiff = state.mode === "uncommitted" ? getStagedDiff(args.excludes ?? []) : "";
|
|
10900
|
+
const stagedChanges = state.mode === "uncommitted" ? getStagedFiles() : "";
|
|
10901
|
+
const recentCommits = state.mode === "rescue" ? getRecentBranchCommits(state.commitsAhead) : void 0;
|
|
10902
|
+
const branchRules = args.branchRules ?? (config2.branch?.generation?.types && config2.branch.generation.types.length > 0 ? { types: [...config2.branch.generation.types] } : void 0);
|
|
10903
|
+
const apiKey = args.apiKey;
|
|
10904
|
+
const ui2 = getUI();
|
|
10905
|
+
let generateLocalBranchNameFn;
|
|
10906
|
+
if (!apiKey) {
|
|
10907
|
+
const { getLocalProviderConfig: getLocalProviderConfig2, generateLocalBranchName: generateLocalBranchName2 } = await Promise.resolve().then(() => (init_local(), local_exports));
|
|
10908
|
+
if (!getLocalProviderConfig2()) {
|
|
10909
|
+
log.error(
|
|
10910
|
+
"Cannot generate branch name: not authenticated and no local provider configured. Run `qc login` or configure a local provider."
|
|
10911
|
+
);
|
|
10912
|
+
return { action: "abort" };
|
|
10913
|
+
}
|
|
10914
|
+
generateLocalBranchNameFn = generateLocalBranchName2;
|
|
10915
|
+
}
|
|
10916
|
+
const spinner = ui2.spinner(`generating branch name...`);
|
|
10917
|
+
if (process.stderr.isTTY) spinner.start();
|
|
10918
|
+
let rawName;
|
|
10919
|
+
let usedFallback = false;
|
|
10920
|
+
try {
|
|
10921
|
+
if (apiKey) {
|
|
10922
|
+
const client = new ApiClient({ apiKey });
|
|
10923
|
+
try {
|
|
10924
|
+
const branchResult = await client.generateBranchName({
|
|
10925
|
+
diff: stagedDiff || void 0,
|
|
10926
|
+
changes: stagedChanges || void 0,
|
|
10927
|
+
recent_commits: recentCommits,
|
|
10928
|
+
model: args.model,
|
|
10929
|
+
rules: branchRules
|
|
10930
|
+
});
|
|
10931
|
+
rawName = branchResult.name;
|
|
10932
|
+
} catch {
|
|
10933
|
+
const fallbackInput = state.mode === "rescue" ? { files: [], description: recentCommits?.join(" ") ?? "" } : { files: stagedChanges ? stagedChanges.split("\n").filter(Boolean) : [] };
|
|
10934
|
+
rawName = deterministicBranchName(fallbackInput).name;
|
|
10935
|
+
usedFallback = true;
|
|
10936
|
+
}
|
|
10937
|
+
} else {
|
|
10938
|
+
try {
|
|
10939
|
+
rawName = await generateLocalBranchNameFn({
|
|
10940
|
+
diff: stagedDiff || void 0,
|
|
10941
|
+
changes: stagedChanges || void 0,
|
|
10942
|
+
recentCommits,
|
|
10943
|
+
model: args.model,
|
|
10944
|
+
rules: branchRules
|
|
10945
|
+
});
|
|
10946
|
+
} catch {
|
|
10947
|
+
const fallbackInput = state.mode === "rescue" ? { files: [], description: recentCommits?.join(" ") ?? "" } : { files: stagedChanges ? stagedChanges.split("\n").filter(Boolean) : [] };
|
|
10948
|
+
rawName = deterministicBranchName(fallbackInput).name;
|
|
10949
|
+
usedFallback = true;
|
|
10950
|
+
}
|
|
10951
|
+
}
|
|
10952
|
+
} finally {
|
|
10953
|
+
spinner.stop();
|
|
9118
10954
|
}
|
|
9119
|
-
|
|
9120
|
-
|
|
9121
|
-
switch (provider) {
|
|
9122
|
-
case "ollama":
|
|
9123
|
-
url = `${baseUrl}/api/generate`;
|
|
9124
|
-
body = {
|
|
9125
|
-
model,
|
|
9126
|
-
prompt: userContent,
|
|
9127
|
-
stream: false,
|
|
9128
|
-
options: {}
|
|
9129
|
-
};
|
|
9130
|
-
return { url, body, headers: { "Content-Type": "application/json" } };
|
|
9131
|
-
case "lmstudio":
|
|
9132
|
-
url = `${baseUrl}/chat/completions`;
|
|
9133
|
-
body = {
|
|
9134
|
-
model,
|
|
9135
|
-
stream: false,
|
|
9136
|
-
messages: [
|
|
9137
|
-
{
|
|
9138
|
-
role: "system",
|
|
9139
|
-
content: "You are a git commit message generator. Create conventional commit messages."
|
|
9140
|
-
},
|
|
9141
|
-
{ role: "user", content: userContent }
|
|
9142
|
-
]
|
|
9143
|
-
};
|
|
9144
|
-
return { url, body, headers: { "Content-Type": "application/json" } };
|
|
9145
|
-
case "openrouter":
|
|
9146
|
-
case "custom":
|
|
9147
|
-
url = `${baseUrl}/chat/completions`;
|
|
9148
|
-
body = {
|
|
9149
|
-
model,
|
|
9150
|
-
stream: false,
|
|
9151
|
-
messages: [
|
|
9152
|
-
{
|
|
9153
|
-
role: "system",
|
|
9154
|
-
content: "You are a git commit message generator. Create conventional commit messages."
|
|
9155
|
-
},
|
|
9156
|
-
{ role: "user", content: userContent }
|
|
9157
|
-
]
|
|
9158
|
-
};
|
|
9159
|
-
return { url, body, headers };
|
|
9160
|
-
case "cloudflare":
|
|
9161
|
-
url = `${baseUrl.replace(/\/$/, "")}/commit`;
|
|
9162
|
-
body = { diff, changes, rules };
|
|
9163
|
-
return { url, body, headers: { "Content-Type": "application/json" } };
|
|
9164
|
-
default:
|
|
9165
|
-
throw new Error(`Unknown provider: ${provider}`);
|
|
10955
|
+
if (usedFallback) {
|
|
10956
|
+
log.dim("(used local fallback name; AI generation failed)");
|
|
9166
10957
|
}
|
|
9167
|
-
|
|
9168
|
-
|
|
9169
|
-
|
|
9170
|
-
|
|
9171
|
-
|
|
9172
|
-
|
|
9173
|
-
|
|
9174
|
-
|
|
9175
|
-
|
|
9176
|
-
|
|
9177
|
-
|
|
10958
|
+
let final;
|
|
10959
|
+
try {
|
|
10960
|
+
final = finalizeBranchName(rawName, branchExists);
|
|
10961
|
+
} catch {
|
|
10962
|
+
const generatorName = usedFallback ? "deterministic fallback" : apiKey ? "API generator" : "local provider";
|
|
10963
|
+
log.error(`Invalid branch name from ${generatorName}: ${rawName}`);
|
|
10964
|
+
return { action: "abort" };
|
|
10965
|
+
}
|
|
10966
|
+
log.success(`branch name: ${final}`);
|
|
10967
|
+
if (state.mode === "rescue") {
|
|
10968
|
+
log.dim(
|
|
10969
|
+
`About to: 1) create ${final} at HEAD, 2) reset ${state.branch} to upstream, 3) switch to ${final}`
|
|
10970
|
+
);
|
|
10971
|
+
const confirmed = await promptYesNo("Continue with rescue?");
|
|
10972
|
+
if (!confirmed) {
|
|
10973
|
+
log.dim("aborted.");
|
|
10974
|
+
return { action: "abort" };
|
|
9178
10975
|
}
|
|
9179
|
-
|
|
9180
|
-
|
|
9181
|
-
|
|
9182
|
-
|
|
10976
|
+
try {
|
|
10977
|
+
rescueCommits({ currentBranch: state.branch, newBranch: final });
|
|
10978
|
+
log.success(`moved ${state.commitsAhead} commit(s) to ${final}`);
|
|
10979
|
+
log.success(`${state.branch} reset to upstream`);
|
|
10980
|
+
} catch (err) {
|
|
10981
|
+
log.error(`Rescue failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
10982
|
+
return { action: "abort" };
|
|
10983
|
+
}
|
|
10984
|
+
return { action: "done" };
|
|
10985
|
+
}
|
|
10986
|
+
createAndCheckoutBranch(final);
|
|
10987
|
+
log.success(`switched to ${final}`);
|
|
10988
|
+
return { action: "continue" };
|
|
10989
|
+
}
|
|
10990
|
+
async function promptProtectedAction(mode) {
|
|
10991
|
+
const rl = import_promises2.default.createInterface({ input: process.stdin, output: process.stderr });
|
|
10992
|
+
try {
|
|
10993
|
+
const question = mode === "rescue" ? "Move commits to a new branch? [B/c/a] " : "Create a new branch first? [B/c/a] ";
|
|
10994
|
+
const answer = (await rl.question(question)).trim().toLowerCase();
|
|
10995
|
+
if (answer === "" || answer === "b" || answer === "y") return "branch";
|
|
10996
|
+
if (answer === "c") return "continue";
|
|
10997
|
+
return "abort";
|
|
10998
|
+
} finally {
|
|
10999
|
+
rl.close();
|
|
9183
11000
|
}
|
|
9184
11001
|
}
|
|
9185
|
-
|
|
11002
|
+
var import_promises2;
|
|
11003
|
+
var init_branch_guard = __esm({
|
|
11004
|
+
"src/branch-guard.ts"() {
|
|
11005
|
+
"use strict";
|
|
11006
|
+
import_promises2 = __toESM(require("node:readline/promises"));
|
|
11007
|
+
init_api();
|
|
11008
|
+
init_git();
|
|
11009
|
+
init_protected_branch_guard();
|
|
11010
|
+
init_branch_rescue();
|
|
11011
|
+
init_branch_name();
|
|
11012
|
+
init_ui();
|
|
11013
|
+
init_commit_helpers();
|
|
11014
|
+
}
|
|
11015
|
+
});
|
|
11016
|
+
|
|
11017
|
+
// src/commands/commit.ts
|
|
11018
|
+
var commit_exports = {};
|
|
11019
|
+
__export(commit_exports, {
|
|
11020
|
+
runCommit: () => runCommit
|
|
11021
|
+
});
|
|
11022
|
+
async function runCommit(args) {
|
|
11023
|
+
const { messageOnly, push, apiKey: apiKeyFlag, hookMode, model: modelFlag, all } = args;
|
|
11024
|
+
const silent = !!(hookMode || args.quiet);
|
|
11025
|
+
const ui2 = getUI();
|
|
11026
|
+
const log = silent ? createSilentLog() : ui2.log;
|
|
9186
11027
|
if (!isGitRepo()) {
|
|
9187
|
-
|
|
11028
|
+
log.error("Not a git repository.");
|
|
11029
|
+
process.exit(1);
|
|
11030
|
+
}
|
|
11031
|
+
const config2 = getConfig();
|
|
11032
|
+
const excludes = [...config2.excludes ?? [], ...args.exclude];
|
|
11033
|
+
const guardResult = await runBranchGuard(
|
|
11034
|
+
{
|
|
11035
|
+
allowProtected: !!(args.allowProtected || config2.branch?.allowProtected),
|
|
11036
|
+
autoBranch: !!args.autoBranch,
|
|
11037
|
+
hookMode: !!args.hookMode,
|
|
11038
|
+
apiKey: apiKeyFlag ?? getApiKey() ?? void 0,
|
|
11039
|
+
model: args.model,
|
|
11040
|
+
excludes
|
|
11041
|
+
},
|
|
11042
|
+
log
|
|
11043
|
+
);
|
|
11044
|
+
if (guardResult.action === "abort") {
|
|
11045
|
+
return;
|
|
11046
|
+
}
|
|
11047
|
+
if (guardResult.action === "done") {
|
|
11048
|
+
return;
|
|
11049
|
+
}
|
|
11050
|
+
const _exhaustive = guardResult.action;
|
|
11051
|
+
void _exhaustive;
|
|
11052
|
+
if (all || config2.autoStage) {
|
|
11053
|
+
stageAll();
|
|
11054
|
+
const { files, total } = getShortStagedFiles();
|
|
11055
|
+
const fileList = total > 3 ? `${files.join(", ")}, +${total - 3} more` : files.join(", ");
|
|
11056
|
+
log.step(`staging working tree (${total} file(s))...`);
|
|
11057
|
+
if (fileList) log.dim(` ${fileList}`);
|
|
9188
11058
|
}
|
|
9189
11059
|
if (!hasStagedChanges()) {
|
|
9190
|
-
|
|
11060
|
+
const unstaged = getUnstagedFiles();
|
|
11061
|
+
if (unstaged.length > 0) {
|
|
11062
|
+
log.error("No staged changes. Use `qc -a` to stage tracked files, or `git add` manually.");
|
|
11063
|
+
} else {
|
|
11064
|
+
log.error("No changes to commit.");
|
|
11065
|
+
}
|
|
11066
|
+
process.exit(1);
|
|
9191
11067
|
}
|
|
9192
|
-
const
|
|
9193
|
-
if (!
|
|
9194
|
-
|
|
9195
|
-
|
|
9196
|
-
);
|
|
11068
|
+
const apiKey = apiKeyFlag ?? getApiKey();
|
|
11069
|
+
if (!apiKey) {
|
|
11070
|
+
log.error("Not authenticated. Run `qc login` first.");
|
|
11071
|
+
process.exit(1);
|
|
9197
11072
|
}
|
|
9198
|
-
const
|
|
9199
|
-
const excludes = config2.excludes ?? [];
|
|
11073
|
+
const model = modelFlag ?? config2.model;
|
|
9200
11074
|
const diff = getStagedDiff(excludes);
|
|
9201
11075
|
const changes = getStagedFiles();
|
|
9202
|
-
|
|
9203
|
-
|
|
11076
|
+
let processedDiff = diff;
|
|
11077
|
+
if (!args.noSmartDiff) {
|
|
11078
|
+
const smartResult = preprocessDiffWithSizeBudget(diff, 5 * 1024 * 1024);
|
|
11079
|
+
processedDiff = smartResult.processedDiff;
|
|
11080
|
+
if (smartResult.summarized.length > 0) {
|
|
11081
|
+
log.step(
|
|
11082
|
+
`smart-diff: ${smartResult.summarized.length} file(s) summarized (saved ~${Math.round(smartResult.tokensSaved / 1e3)}K tokens)`
|
|
11083
|
+
);
|
|
11084
|
+
}
|
|
11085
|
+
if (smartResult.aggressivelySummarized.length > 0) {
|
|
11086
|
+
log.step(
|
|
11087
|
+
`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)`
|
|
11088
|
+
);
|
|
11089
|
+
}
|
|
11090
|
+
}
|
|
11091
|
+
const commitlintRules = await detectCommitlintRules();
|
|
11092
|
+
let rules = { ...commitlintRules, ...config2.rules ?? {} };
|
|
9204
11093
|
const workspace = detectWorkspace();
|
|
11094
|
+
let monorepoScopes;
|
|
9205
11095
|
if (workspace) {
|
|
9206
11096
|
const stagedFiles = changes.trim().split("\n").filter(Boolean);
|
|
9207
11097
|
const scope = autoDetectScope(stagedFiles, workspace);
|
|
9208
11098
|
if (scope) {
|
|
9209
|
-
|
|
9210
|
-
rules = { ...rules, scopes };
|
|
11099
|
+
monorepoScopes = scope.split(",").map((s) => s.trim());
|
|
11100
|
+
rules = { ...rules, scopes: monorepoScopes };
|
|
9211
11101
|
}
|
|
9212
11102
|
}
|
|
9213
|
-
const
|
|
9214
|
-
|
|
9215
|
-
|
|
9216
|
-
|
|
9217
|
-
|
|
9218
|
-
|
|
9219
|
-
|
|
9220
|
-
|
|
9221
|
-
|
|
9222
|
-
|
|
9223
|
-
|
|
9224
|
-
|
|
9225
|
-
|
|
9226
|
-
"Cloudflare provider requires api_url. Run: qc config set api_url https://your-worker.workers.dev"
|
|
9227
|
-
);
|
|
9228
|
-
}
|
|
9229
|
-
const res = await fetch(url, {
|
|
9230
|
-
method: "POST",
|
|
9231
|
-
headers,
|
|
9232
|
-
body: JSON.stringify(body)
|
|
9233
|
-
});
|
|
9234
|
-
if (!res.ok) {
|
|
9235
|
-
const text = await res.text();
|
|
9236
|
-
throw new Error(`Provider error (${res.status}): ${text}`);
|
|
11103
|
+
const client = new ApiClient({ apiKey });
|
|
11104
|
+
try {
|
|
11105
|
+
const teamRules = await client.getTeamRules();
|
|
11106
|
+
if (teamRules && Object.keys(teamRules).length > 0) {
|
|
11107
|
+
log.step("using team rules from org");
|
|
11108
|
+
rules = { ...rules, ...teamRules };
|
|
11109
|
+
if (monorepoScopes && teamRules.scopes && teamRules.scopes.length > 0) {
|
|
11110
|
+
const allowed = new Set(teamRules.scopes);
|
|
11111
|
+
const intersected = monorepoScopes.filter((s) => allowed.has(s));
|
|
11112
|
+
if (intersected.length > 0) rules = { ...rules, scopes: intersected };
|
|
11113
|
+
}
|
|
11114
|
+
}
|
|
11115
|
+
} catch {
|
|
9237
11116
|
}
|
|
9238
|
-
|
|
9239
|
-
|
|
9240
|
-
|
|
9241
|
-
|
|
9242
|
-
|
|
11117
|
+
rules = applyCliTypeScopeToRules(rules, args.type, args.scope);
|
|
11118
|
+
const recentCommits = args.noContext ? void 0 : getRecentBranchCommits(5);
|
|
11119
|
+
const generationHints = generationHintsFromArgs(args.split, args.forceBody);
|
|
11120
|
+
const skipInteractive = silent || args.quiet || shouldSkipTTYInteraction(args.hookMode);
|
|
11121
|
+
const skipConfirm = args.dryRun || messageOnly || silent || args.quiet || shouldSkipTTYInteraction(args.hookMode);
|
|
11122
|
+
const modelDisplay = model ?? "default";
|
|
11123
|
+
const spinner = ui2.spinner(`generating commit (${modelDisplay})...`);
|
|
11124
|
+
if (!silent) spinner.start();
|
|
11125
|
+
const t0 = Date.now();
|
|
11126
|
+
let generatedMessage;
|
|
11127
|
+
let diagnostics;
|
|
11128
|
+
try {
|
|
11129
|
+
({ message: generatedMessage, diagnostics } = await client.generateCommit(
|
|
11130
|
+
processedDiff,
|
|
11131
|
+
changes,
|
|
11132
|
+
rules,
|
|
11133
|
+
model,
|
|
11134
|
+
recentCommits,
|
|
11135
|
+
generationHints
|
|
11136
|
+
));
|
|
11137
|
+
} finally {
|
|
11138
|
+
spinner.stop();
|
|
11139
|
+
}
|
|
11140
|
+
const roundTripMs = Date.now() - t0;
|
|
11141
|
+
logVerboseDiagnostics((msg) => log.dim(msg), args.verbose, args.quiet, diagnostics, roundTripMs);
|
|
11142
|
+
let message = generatedMessage;
|
|
11143
|
+
if (args.interactive) {
|
|
11144
|
+
if (shouldSkipTTYInteraction(args.hookMode)) {
|
|
11145
|
+
if (!silent) log.dim("(--interactive ignored: not running in a TTY)");
|
|
11146
|
+
} else {
|
|
11147
|
+
const refineResult = await interactiveRefineMessage(message, { skip: skipInteractive });
|
|
11148
|
+
if (refineResult.action === "abort") {
|
|
11149
|
+
return;
|
|
11150
|
+
}
|
|
11151
|
+
message = refineResult.message;
|
|
11152
|
+
}
|
|
9243
11153
|
}
|
|
9244
11154
|
if (messageOnly) {
|
|
9245
11155
|
console.log(message);
|
|
9246
11156
|
return;
|
|
9247
11157
|
}
|
|
11158
|
+
const stagedPaths = changes.trim().split("\n").filter(Boolean);
|
|
11159
|
+
const short = getStagedDiffShortstat();
|
|
11160
|
+
const tokenEst = diagnostics && typeof diagnostics === "object" && diagnostics !== null && "tokenUsage" in diagnostics ? diagnostics.tokenUsage?.totalEstimated : void 0;
|
|
11161
|
+
displayCommitMessage(message, {
|
|
11162
|
+
log,
|
|
11163
|
+
isColor: ui2.isColor,
|
|
11164
|
+
isTTY: !!process.stderr.isTTY,
|
|
11165
|
+
style: "rich",
|
|
11166
|
+
stagedFiles: stagedPaths,
|
|
11167
|
+
stats: {
|
|
11168
|
+
files: stagedPaths.length,
|
|
11169
|
+
additions: short.additions,
|
|
11170
|
+
deletions: short.deletions,
|
|
11171
|
+
...tokenEst !== void 0 ? { tokens: tokenEst } : {}
|
|
11172
|
+
}
|
|
11173
|
+
});
|
|
11174
|
+
if (args.dryRun) {
|
|
11175
|
+
return;
|
|
11176
|
+
}
|
|
11177
|
+
if (args.confirm) {
|
|
11178
|
+
const confirmResult = await confirmCommit("Proceed with commit? [y/N]: ", { skip: skipConfirm });
|
|
11179
|
+
if (confirmResult.action === "abort") {
|
|
11180
|
+
return;
|
|
11181
|
+
}
|
|
11182
|
+
}
|
|
9248
11183
|
gitCommit(message);
|
|
11184
|
+
const hash = getCommitHash();
|
|
11185
|
+
const branch = getCurrentBranch();
|
|
11186
|
+
log.step(`[${branch} ${hash}] committed`);
|
|
9249
11187
|
if (push) {
|
|
11188
|
+
log.step(`pushing to origin/${branch}...`);
|
|
9250
11189
|
gitPush();
|
|
11190
|
+
const stats = getPushStats();
|
|
11191
|
+
if (stats) {
|
|
11192
|
+
log.success(`pushed ${stats.commits} commit(s) \xB7 ${stats.stat}`);
|
|
11193
|
+
} else {
|
|
11194
|
+
log.success("pushed");
|
|
11195
|
+
}
|
|
9251
11196
|
}
|
|
9252
11197
|
}
|
|
9253
|
-
var
|
|
9254
|
-
|
|
9255
|
-
"src/local.ts"() {
|
|
11198
|
+
var init_commit = __esm({
|
|
11199
|
+
"src/commands/commit.ts"() {
|
|
9256
11200
|
"use strict";
|
|
9257
|
-
import_fs10 = require("fs");
|
|
9258
|
-
import_path10 = require("path");
|
|
9259
|
-
import_os4 = require("os");
|
|
9260
11201
|
init_config();
|
|
9261
|
-
|
|
11202
|
+
init_api();
|
|
11203
|
+
init_commitlint();
|
|
9262
11204
|
init_git();
|
|
9263
11205
|
init_monorepo();
|
|
9264
|
-
|
|
9265
|
-
|
|
9266
|
-
|
|
9267
|
-
|
|
9268
|
-
openrouter: "https://openrouter.ai/api/v1",
|
|
9269
|
-
custom: "",
|
|
9270
|
-
cloudflare: ""
|
|
9271
|
-
};
|
|
9272
|
-
DEFAULT_MODELS = {
|
|
9273
|
-
ollama: "codellama",
|
|
9274
|
-
lmstudio: "default",
|
|
9275
|
-
openrouter: "google/gemini-flash-1.5-8b",
|
|
9276
|
-
custom: "",
|
|
9277
|
-
cloudflare: "@cf/qwen/qwen2.5-coder-32b-instruct"
|
|
9278
|
-
};
|
|
11206
|
+
init_ui();
|
|
11207
|
+
init_smart_diff();
|
|
11208
|
+
init_commit_helpers();
|
|
11209
|
+
init_branch_guard();
|
|
9279
11210
|
}
|
|
9280
11211
|
});
|
|
9281
11212
|
|
|
9282
11213
|
// src/index.ts
|
|
11214
|
+
var index_exports = {};
|
|
11215
|
+
__export(index_exports, {
|
|
11216
|
+
parseArgs: () => parseArgs
|
|
11217
|
+
});
|
|
11218
|
+
module.exports = __toCommonJS(index_exports);
|
|
9283
11219
|
init_config();
|
|
9284
|
-
init_api();
|
|
9285
|
-
init_commitlint();
|
|
9286
|
-
init_git();
|
|
9287
|
-
init_monorepo();
|
|
9288
11220
|
var HELP = `Quikcommit - AI-powered conventional commit messages
|
|
9289
11221
|
|
|
9290
11222
|
Usage:
|
|
9291
11223
|
qc Generate commit message and commit (default)
|
|
9292
|
-
qc --message-only Generate message only, print to stdout
|
|
9293
|
-
qc --push Commit and push to origin
|
|
9294
11224
|
qc pr Generate PR description from branch commits
|
|
9295
11225
|
qc changelog Generate changelog from commits since last tag
|
|
9296
11226
|
qc changeset Automate pnpm changeset with AI
|
|
9297
|
-
qc
|
|
11227
|
+
qc branch Generate branch name + create branch (use --message for description)
|
|
11228
|
+
qc init Install prepare-commit-msg hook
|
|
9298
11229
|
qc login Sign in via browser
|
|
9299
11230
|
qc logout Clear local credentials
|
|
9300
11231
|
qc status Show auth, plan, usage
|
|
9301
11232
|
qc team Team management (info, rules, invite)
|
|
11233
|
+
qc config Show/set config
|
|
11234
|
+
|
|
11235
|
+
Flags:
|
|
11236
|
+
-p, --push Commit and push
|
|
11237
|
+
-a, --all Stage all tracked changes first
|
|
11238
|
+
-m, --message-only Print message only (stdout, no commit)
|
|
11239
|
+
-v, --verbose Show diagnostics (model, token estimates, rules) + API round-trip ms on stderr
|
|
11240
|
+
-q, --quiet Minimal output
|
|
11241
|
+
-n, --dry-run Show message without committing
|
|
11242
|
+
-i, --interactive Interactive refinement mode
|
|
11243
|
+
-s, --split Multi-commit split mode
|
|
11244
|
+
-b, --body Force include body
|
|
11245
|
+
-l, --local Use local provider
|
|
11246
|
+
-c, --confirm Ask before committing
|
|
11247
|
+
-t, --type <type> Force commit type
|
|
11248
|
+
-S, --scope <scope> Force scope
|
|
11249
|
+
-e, --exclude <pat> Exclude files from diff (repeatable)
|
|
11250
|
+
|
|
11251
|
+
--no-context Skip commit history context
|
|
11252
|
+
--no-smart-diff Skip smart diff preprocessing
|
|
11253
|
+
--no-color Disable colors
|
|
11254
|
+
--model <id> Use specific model
|
|
11255
|
+
--base <branch> Base branch for pr/changeset (default: main)
|
|
11256
|
+
--create Create PR with gh CLI (qc pr --create)
|
|
11257
|
+
--from <ref> Start ref for changelog / base ref for qc branch
|
|
11258
|
+
--to <ref> End ref for changelog
|
|
11259
|
+
--write Write changelog to CHANGELOG.md
|
|
11260
|
+
--hook-mode Silent mode for git hooks
|
|
11261
|
+
|
|
11262
|
+
Branch flags (qc branch):
|
|
11263
|
+
--message <text> Generate from a description (no diff needed)
|
|
11264
|
+
--from-commits Generate from recent commits instead of diff
|
|
11265
|
+
--rescue Move commits off current protected branch (see docs)
|
|
11266
|
+
--no-switch Create branch but don't checkout
|
|
11267
|
+
--from <ref> Create branch from this ref (default: HEAD)
|
|
11268
|
+
|
|
11269
|
+
Commit guard flags:
|
|
11270
|
+
--allow-protected Bypass protected-branch guard for this run
|
|
11271
|
+
--auto-branch Auto-create branch with generated name (no prompt)
|
|
11272
|
+
|
|
11273
|
+
Compose short flags: qc -ap (stage all + push), qc -apv (+ verbose)
|
|
9302
11274
|
|
|
9303
|
-
|
|
9304
|
-
|
|
9305
|
-
-
|
|
9306
|
-
-
|
|
9307
|
-
-
|
|
9308
|
-
|
|
9309
|
-
|
|
9310
|
-
|
|
9311
|
-
--from <ref> Start ref for qc changelog (default: latest tag)
|
|
9312
|
-
--to <ref> End ref for qc changelog (default: HEAD)
|
|
9313
|
-
--write Prepend changelog to CHANGELOG.md
|
|
9314
|
-
--version <ver> Version label for changelog header (default: derived from --to or "<from>-next")
|
|
9315
|
-
--uninstall Remove Quikcommit hook (qc init --uninstall)
|
|
9316
|
-
--model <id> Use specific model (e.g. qwen25-coder-32b, llama-3.3-70b)
|
|
9317
|
-
|
|
9318
|
-
Commands:
|
|
9319
|
-
qc config Show current config
|
|
9320
|
-
qc config set <k> <v> Set config (model, api_url)
|
|
9321
|
-
qc config reset Reset to defaults
|
|
9322
|
-
qc upgrade Open billing page in browser
|
|
11275
|
+
Examples:
|
|
11276
|
+
qc # generate and commit
|
|
11277
|
+
qc -p # commit and push
|
|
11278
|
+
qc -ap # stage all, commit, push
|
|
11279
|
+
qc -m | pbcopy # copy message to clipboard
|
|
11280
|
+
qc -n # preview without committing
|
|
11281
|
+
qc -e "*.lock" # exclude lock files
|
|
11282
|
+
qc -t fix -S auth # force type and scope
|
|
9323
11283
|
`;
|
|
11284
|
+
var SHORT_FLAGS = {
|
|
11285
|
+
p: "push",
|
|
11286
|
+
a: "all",
|
|
11287
|
+
m: "messageOnly",
|
|
11288
|
+
v: "verbose",
|
|
11289
|
+
q: "quiet",
|
|
11290
|
+
n: "dryRun",
|
|
11291
|
+
i: "interactive",
|
|
11292
|
+
s: "split",
|
|
11293
|
+
b: "forceBody",
|
|
11294
|
+
l: "local",
|
|
11295
|
+
c: "confirm"
|
|
11296
|
+
};
|
|
11297
|
+
var SHORT_FLAGS_WITH_VALUE = {
|
|
11298
|
+
t: "type",
|
|
11299
|
+
S: "scope",
|
|
11300
|
+
e: "exclude"
|
|
11301
|
+
};
|
|
9324
11302
|
function parseArgs(args) {
|
|
9325
|
-
|
|
9326
|
-
|
|
9327
|
-
|
|
9328
|
-
|
|
9329
|
-
|
|
9330
|
-
|
|
9331
|
-
|
|
9332
|
-
|
|
9333
|
-
|
|
9334
|
-
|
|
9335
|
-
|
|
9336
|
-
|
|
9337
|
-
|
|
9338
|
-
|
|
9339
|
-
|
|
11303
|
+
const result = {
|
|
11304
|
+
command: "commit",
|
|
11305
|
+
all: false,
|
|
11306
|
+
messageOnly: false,
|
|
11307
|
+
push: false,
|
|
11308
|
+
verbose: false,
|
|
11309
|
+
quiet: false,
|
|
11310
|
+
dryRun: false,
|
|
11311
|
+
interactive: false,
|
|
11312
|
+
split: false,
|
|
11313
|
+
forceBody: false,
|
|
11314
|
+
confirm: false,
|
|
11315
|
+
noContext: false,
|
|
11316
|
+
noSmartDiff: false,
|
|
11317
|
+
local: false,
|
|
11318
|
+
exclude: [],
|
|
11319
|
+
positionals: []
|
|
11320
|
+
};
|
|
11321
|
+
let subcommandSeen = false;
|
|
9340
11322
|
for (let i = 0; i < args.length; i++) {
|
|
9341
11323
|
const arg = args[i];
|
|
9342
|
-
if (arg ===
|
|
9343
|
-
|
|
9344
|
-
|
|
9345
|
-
|
|
9346
|
-
|
|
9347
|
-
|
|
9348
|
-
|
|
9349
|
-
|
|
11324
|
+
if (arg === void 0) continue;
|
|
11325
|
+
if (arg.startsWith("-") && !arg.startsWith("--") && arg.length > 2) {
|
|
11326
|
+
const chars = [...arg.slice(1)];
|
|
11327
|
+
for (let j = 0; j < chars.length; j++) {
|
|
11328
|
+
const ch = chars[j];
|
|
11329
|
+
if (!ch) continue;
|
|
11330
|
+
if (SHORT_FLAGS[ch]) {
|
|
11331
|
+
const key = SHORT_FLAGS[ch];
|
|
11332
|
+
result[key] = true;
|
|
11333
|
+
} else if (SHORT_FLAGS_WITH_VALUE[ch]) {
|
|
11334
|
+
if (j < chars.length - 1) {
|
|
11335
|
+
throw new Error(`Flag -${ch} requires a value and must be last in a composed group`);
|
|
11336
|
+
}
|
|
11337
|
+
const val = args[++i];
|
|
11338
|
+
if (!val || val.startsWith("-") && val.length > 1) throw new Error(`Flag -${ch} requires a value`);
|
|
11339
|
+
const key = SHORT_FLAGS_WITH_VALUE[ch];
|
|
11340
|
+
if (key === "exclude") {
|
|
11341
|
+
result.exclude.push(val);
|
|
11342
|
+
} else {
|
|
11343
|
+
result[key] = val;
|
|
11344
|
+
}
|
|
11345
|
+
} else if (ch === "h") {
|
|
11346
|
+
result.command = "help";
|
|
11347
|
+
} else {
|
|
11348
|
+
throw new Error(`Unknown flag: -${ch}`);
|
|
11349
|
+
}
|
|
11350
|
+
}
|
|
11351
|
+
continue;
|
|
11352
|
+
}
|
|
11353
|
+
if (arg.length === 2 && arg.startsWith("-") && !arg.startsWith("--")) {
|
|
11354
|
+
const ch = arg[1];
|
|
11355
|
+
if (!ch) continue;
|
|
11356
|
+
if (SHORT_FLAGS[ch]) {
|
|
11357
|
+
result[SHORT_FLAGS[ch]] = true;
|
|
11358
|
+
continue;
|
|
11359
|
+
}
|
|
11360
|
+
if (SHORT_FLAGS_WITH_VALUE[ch]) {
|
|
11361
|
+
const val = args[++i];
|
|
11362
|
+
if (!val || val.startsWith("-") && val.length > 1) {
|
|
11363
|
+
throw new Error(`Flag -${ch} requires a value`);
|
|
11364
|
+
}
|
|
11365
|
+
const key = SHORT_FLAGS_WITH_VALUE[ch];
|
|
11366
|
+
if (key === "exclude") {
|
|
11367
|
+
result.exclude.push(val);
|
|
11368
|
+
} else {
|
|
11369
|
+
result[key] = val;
|
|
11370
|
+
}
|
|
11371
|
+
continue;
|
|
11372
|
+
}
|
|
11373
|
+
if (ch === "h") {
|
|
11374
|
+
result.command = "help";
|
|
11375
|
+
continue;
|
|
11376
|
+
}
|
|
11377
|
+
throw new Error(`Unknown flag: -${ch}`);
|
|
11378
|
+
}
|
|
11379
|
+
if (arg === "--help") {
|
|
11380
|
+
result.command = "help";
|
|
11381
|
+
} else if (arg === "--all") {
|
|
11382
|
+
result.all = true;
|
|
11383
|
+
} else if (arg === "--allow-protected") {
|
|
11384
|
+
result.allowProtected = true;
|
|
11385
|
+
} else if (arg === "--auto-branch") {
|
|
11386
|
+
result.autoBranch = true;
|
|
11387
|
+
} else if (arg === "--message-only") {
|
|
11388
|
+
result.messageOnly = true;
|
|
11389
|
+
} else if (arg === "--message" && i + 1 < args.length) {
|
|
11390
|
+
result.message = args[++i];
|
|
11391
|
+
} else if (arg === "--push") {
|
|
11392
|
+
result.push = true;
|
|
11393
|
+
} else if (arg === "--rescue") {
|
|
11394
|
+
result.rescue = true;
|
|
11395
|
+
} else if (arg === "--verbose") {
|
|
11396
|
+
result.verbose = true;
|
|
11397
|
+
} else if (arg === "--quiet") {
|
|
11398
|
+
result.quiet = true;
|
|
11399
|
+
} else if (arg === "--dry-run") {
|
|
11400
|
+
result.dryRun = true;
|
|
11401
|
+
} else if (arg === "--interactive") {
|
|
11402
|
+
result.interactive = true;
|
|
11403
|
+
} else if (arg === "--split") {
|
|
11404
|
+
result.split = true;
|
|
11405
|
+
} else if (arg === "--body") {
|
|
11406
|
+
result.forceBody = true;
|
|
11407
|
+
} else if (arg === "--confirm") {
|
|
11408
|
+
result.confirm = true;
|
|
11409
|
+
} else if (arg === "--no-confirm") {
|
|
11410
|
+
result.confirm = false;
|
|
11411
|
+
} else if (arg === "--no-context") {
|
|
11412
|
+
result.noContext = true;
|
|
11413
|
+
} else if (arg === "--no-smart-diff") {
|
|
11414
|
+
result.noSmartDiff = true;
|
|
11415
|
+
} else if (arg === "--no-switch") {
|
|
11416
|
+
result.noSwitch = true;
|
|
11417
|
+
} else if (arg === "--local" || arg === "--use-ollama" || arg === "--use-lmstudio" || arg === "--use-openrouter" || arg === "--use-cloudflare") {
|
|
11418
|
+
result.local = true;
|
|
11419
|
+
if (arg === "--use-ollama") {
|
|
11420
|
+
result.setProvider = "ollama";
|
|
11421
|
+
} else if (arg === "--use-lmstudio") {
|
|
11422
|
+
result.setProvider = "lmstudio";
|
|
11423
|
+
} else if (arg === "--use-openrouter") {
|
|
11424
|
+
result.setProvider = "openrouter";
|
|
11425
|
+
} else if (arg === "--use-cloudflare") {
|
|
11426
|
+
result.setProvider = "cloudflare";
|
|
11427
|
+
}
|
|
9350
11428
|
} else if (arg === "--api-key" && i + 1 < args.length) {
|
|
9351
|
-
apiKey = args[++i];
|
|
11429
|
+
result.apiKey = args[++i];
|
|
9352
11430
|
} else if (arg === "--base" && i + 1 < args.length) {
|
|
9353
|
-
base = args[++i];
|
|
11431
|
+
result.base = args[++i];
|
|
9354
11432
|
} else if (arg === "--create") {
|
|
9355
|
-
create = true;
|
|
11433
|
+
result.create = true;
|
|
9356
11434
|
} else if (arg === "--from" && i + 1 < args.length) {
|
|
9357
|
-
from = args[++i];
|
|
11435
|
+
result.from = args[++i];
|
|
11436
|
+
} else if (arg === "--from-commits") {
|
|
11437
|
+
result.fromCommits = true;
|
|
9358
11438
|
} else if (arg === "--to" && i + 1 < args.length) {
|
|
9359
|
-
to = args[++i];
|
|
11439
|
+
result.to = args[++i];
|
|
9360
11440
|
} else if (arg === "--write") {
|
|
9361
|
-
write = true;
|
|
11441
|
+
result.write = true;
|
|
9362
11442
|
} else if (arg === "--version" && i + 1 < args.length) {
|
|
9363
|
-
version = args[++i];
|
|
11443
|
+
result.version = args[++i];
|
|
9364
11444
|
} else if (arg === "--uninstall") {
|
|
9365
|
-
uninstall = true;
|
|
11445
|
+
result.uninstall = true;
|
|
9366
11446
|
} else if (arg === "--hook-mode") {
|
|
9367
|
-
hookMode = true;
|
|
11447
|
+
result.hookMode = true;
|
|
11448
|
+
} else if (arg === "--model" && i + 1 < args.length) {
|
|
11449
|
+
result.model = args[++i];
|
|
11450
|
+
} else if (arg === "--type" && i + 1 < args.length) {
|
|
11451
|
+
result.type = args[++i];
|
|
11452
|
+
} else if (arg === "--scope" && i + 1 < args.length) {
|
|
11453
|
+
result.scope = args[++i];
|
|
11454
|
+
} else if (arg === "--exclude" && i + 1 < args.length) {
|
|
11455
|
+
const ex = args[++i];
|
|
11456
|
+
if (ex) result.exclude.push(ex);
|
|
11457
|
+
} else if (arg === "--no-color") {
|
|
9368
11458
|
} else if (arg === "login") {
|
|
9369
|
-
command = "login";
|
|
11459
|
+
result.command = "login";
|
|
11460
|
+
subcommandSeen = true;
|
|
9370
11461
|
} else if (arg === "logout") {
|
|
9371
|
-
command = "logout";
|
|
11462
|
+
result.command = "logout";
|
|
11463
|
+
subcommandSeen = true;
|
|
9372
11464
|
} else if (arg === "status") {
|
|
9373
|
-
command = "status";
|
|
11465
|
+
result.command = "status";
|
|
11466
|
+
subcommandSeen = true;
|
|
9374
11467
|
} else if (arg === "pr") {
|
|
9375
|
-
command = "pr";
|
|
11468
|
+
result.command = "pr";
|
|
11469
|
+
subcommandSeen = true;
|
|
9376
11470
|
} else if (arg === "changelog") {
|
|
9377
|
-
command = "changelog";
|
|
11471
|
+
result.command = "changelog";
|
|
11472
|
+
subcommandSeen = true;
|
|
11473
|
+
} else if (arg === "branch") {
|
|
11474
|
+
result.command = "branch";
|
|
11475
|
+
subcommandSeen = true;
|
|
9378
11476
|
} else if (arg === "init") {
|
|
9379
|
-
command = "init";
|
|
11477
|
+
result.command = "init";
|
|
11478
|
+
subcommandSeen = true;
|
|
9380
11479
|
} else if (arg === "team") {
|
|
9381
|
-
command = "team";
|
|
11480
|
+
result.command = "team";
|
|
11481
|
+
subcommandSeen = true;
|
|
9382
11482
|
} else if (arg === "config") {
|
|
9383
|
-
command = "config";
|
|
11483
|
+
result.command = "config";
|
|
11484
|
+
subcommandSeen = true;
|
|
9384
11485
|
} else if (arg === "upgrade") {
|
|
9385
|
-
command = "upgrade";
|
|
11486
|
+
result.command = "upgrade";
|
|
11487
|
+
subcommandSeen = true;
|
|
9386
11488
|
} else if (arg === "changeset") {
|
|
9387
|
-
command = "changeset";
|
|
9388
|
-
|
|
9389
|
-
|
|
9390
|
-
|
|
9391
|
-
local = true;
|
|
9392
|
-
if (arg === "--use-ollama") {
|
|
9393
|
-
saveConfig({ ...getConfig(), provider: "ollama", apiUrl: "http://localhost:11434", model: "codellama" });
|
|
9394
|
-
} else if (arg === "--use-lmstudio") {
|
|
9395
|
-
saveConfig({ ...getConfig(), provider: "lmstudio", apiUrl: "http://localhost:1234/v1", model: "default" });
|
|
9396
|
-
} else if (arg === "--use-openrouter") {
|
|
9397
|
-
saveConfig({ ...getConfig(), provider: "openrouter", apiUrl: "https://openrouter.ai/api/v1", model: "google/gemini-flash-1.5-8b" });
|
|
9398
|
-
} else if (arg === "--use-cloudflare") {
|
|
9399
|
-
saveConfig({
|
|
9400
|
-
...getConfig(),
|
|
9401
|
-
provider: "cloudflare",
|
|
9402
|
-
apiUrl: "https://YOUR-WORKER.workers.dev",
|
|
9403
|
-
model: "@cf/qwen/qwen2.5-coder-32b-instruct"
|
|
9404
|
-
});
|
|
9405
|
-
console.error(
|
|
9406
|
-
"[qc] Cloudflare provider set. Run: qc config set api_url https://your-worker.workers.dev"
|
|
9407
|
-
);
|
|
9408
|
-
}
|
|
9409
|
-
}
|
|
9410
|
-
}
|
|
9411
|
-
return { command, all, messageOnly, push, apiKey, base, create, from, to, write, version, uninstall, hookMode, model, local };
|
|
9412
|
-
}
|
|
9413
|
-
async function runCommit(messageOnly, push, apiKeyFlag, hookMode = false, modelFlag, stageAll_) {
|
|
9414
|
-
const log = hookMode ? () => {
|
|
9415
|
-
} : (msg) => console.error(msg);
|
|
9416
|
-
if (!isGitRepo()) {
|
|
9417
|
-
log("Error: Not a git repository.");
|
|
9418
|
-
process.exit(1);
|
|
9419
|
-
}
|
|
9420
|
-
const config2 = getConfig();
|
|
9421
|
-
if (stageAll_ || config2.autoStage) {
|
|
9422
|
-
stageAll();
|
|
9423
|
-
}
|
|
9424
|
-
if (!hasStagedChanges()) {
|
|
9425
|
-
const unstaged = getUnstagedFiles();
|
|
9426
|
-
if (unstaged.length > 0) {
|
|
9427
|
-
log("Error: No staged changes. Use `qc -a` to stage tracked files, or `git add` manually.");
|
|
9428
|
-
} else {
|
|
9429
|
-
log("Error: No changes to commit.");
|
|
9430
|
-
}
|
|
9431
|
-
process.exit(1);
|
|
9432
|
-
}
|
|
9433
|
-
const apiKey = apiKeyFlag ?? getApiKey();
|
|
9434
|
-
if (!apiKey) {
|
|
9435
|
-
log("Error: Not authenticated. Run `qc login` first.");
|
|
9436
|
-
process.exit(1);
|
|
9437
|
-
}
|
|
9438
|
-
const model = modelFlag ?? config2.model;
|
|
9439
|
-
const excludes = config2.excludes ?? [];
|
|
9440
|
-
const diff = getStagedDiff(excludes);
|
|
9441
|
-
const changes = getStagedFiles();
|
|
9442
|
-
const commitlintRules = await detectCommitlintRules();
|
|
9443
|
-
let rules = { ...commitlintRules, ...config2.rules ?? {} };
|
|
9444
|
-
const workspace = detectWorkspace();
|
|
9445
|
-
let monorepoScopes;
|
|
9446
|
-
if (workspace) {
|
|
9447
|
-
const stagedFiles = changes.trim().split("\n").filter(Boolean);
|
|
9448
|
-
const scope = autoDetectScope(stagedFiles, workspace);
|
|
9449
|
-
if (scope) {
|
|
9450
|
-
monorepoScopes = scope.split(",").map((s) => s.trim());
|
|
9451
|
-
rules = { ...rules, scopes: monorepoScopes };
|
|
11489
|
+
result.command = "changeset";
|
|
11490
|
+
subcommandSeen = true;
|
|
11491
|
+
} else if (subcommandSeen && !arg.startsWith("-")) {
|
|
11492
|
+
result.positionals.push(arg);
|
|
9452
11493
|
}
|
|
9453
11494
|
}
|
|
9454
|
-
|
|
9455
|
-
|
|
9456
|
-
const teamRules = await client.getTeamRules();
|
|
9457
|
-
if (teamRules && Object.keys(teamRules).length > 0) {
|
|
9458
|
-
log("[qc] Using team rules from org");
|
|
9459
|
-
rules = { ...rules, ...teamRules };
|
|
9460
|
-
if (monorepoScopes && teamRules.scopes && teamRules.scopes.length > 0) {
|
|
9461
|
-
const allowed = new Set(teamRules.scopes);
|
|
9462
|
-
const intersected = monorepoScopes.filter((s) => allowed.has(s));
|
|
9463
|
-
if (intersected.length > 0) rules = { ...rules, scopes: intersected };
|
|
9464
|
-
}
|
|
9465
|
-
}
|
|
9466
|
-
} catch {
|
|
11495
|
+
if (result.messageOnly && result.push) {
|
|
11496
|
+
throw new Error("Cannot combine --message-only (-m) with --push (-p)");
|
|
9467
11497
|
}
|
|
9468
|
-
|
|
9469
|
-
|
|
9470
|
-
console.log(message);
|
|
9471
|
-
return;
|
|
11498
|
+
if (result.quiet && result.verbose) {
|
|
11499
|
+
throw new Error("Cannot combine --quiet (-q) with --verbose (-v)");
|
|
9472
11500
|
}
|
|
9473
|
-
|
|
9474
|
-
|
|
9475
|
-
gitPush();
|
|
11501
|
+
if (result.dryRun && result.push) {
|
|
11502
|
+
throw new Error("Cannot combine --dry-run (-n) with --push (-p). Pick one.");
|
|
9476
11503
|
}
|
|
11504
|
+
return result;
|
|
9477
11505
|
}
|
|
9478
11506
|
async function main() {
|
|
9479
11507
|
const argv = process.argv.slice(2);
|
|
9480
|
-
|
|
9481
|
-
|
|
11508
|
+
let values;
|
|
11509
|
+
try {
|
|
11510
|
+
values = parseArgs(argv);
|
|
11511
|
+
} catch (err) {
|
|
11512
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
11513
|
+
process.exit(1);
|
|
11514
|
+
}
|
|
11515
|
+
const { command, apiKey } = values;
|
|
9482
11516
|
if (command === "help") {
|
|
9483
11517
|
console.log(HELP);
|
|
9484
11518
|
return;
|
|
9485
11519
|
}
|
|
11520
|
+
if (values.setProvider) {
|
|
11521
|
+
switch (values.setProvider) {
|
|
11522
|
+
case "ollama":
|
|
11523
|
+
saveConfig({ ...getConfig(), provider: "ollama", apiUrl: "http://localhost:11434", model: "codellama" });
|
|
11524
|
+
break;
|
|
11525
|
+
case "lmstudio":
|
|
11526
|
+
saveConfig({ ...getConfig(), provider: "lmstudio", apiUrl: "http://localhost:1234/v1", model: "default" });
|
|
11527
|
+
break;
|
|
11528
|
+
case "openrouter":
|
|
11529
|
+
saveConfig({
|
|
11530
|
+
...getConfig(),
|
|
11531
|
+
provider: "openrouter",
|
|
11532
|
+
apiUrl: "https://openrouter.ai/api/v1",
|
|
11533
|
+
model: "google/gemini-flash-1.5-8b"
|
|
11534
|
+
});
|
|
11535
|
+
break;
|
|
11536
|
+
case "cloudflare":
|
|
11537
|
+
saveConfig({
|
|
11538
|
+
...getConfig(),
|
|
11539
|
+
provider: "cloudflare",
|
|
11540
|
+
apiUrl: "https://YOUR-WORKER.workers.dev",
|
|
11541
|
+
model: "@cf/qwen/qwen2.5-coder-32b-instruct"
|
|
11542
|
+
});
|
|
11543
|
+
console.error("[qc] Cloudflare provider set. Run: qc config set api_url https://your-worker.workers.dev");
|
|
11544
|
+
break;
|
|
11545
|
+
}
|
|
11546
|
+
}
|
|
9486
11547
|
if (command === "login") {
|
|
9487
11548
|
const { runLogin: runLogin2 } = await Promise.resolve().then(() => (init_login(), login_exports));
|
|
9488
11549
|
await runLogin2();
|
|
@@ -9526,6 +11587,23 @@ async function main() {
|
|
|
9526
11587
|
});
|
|
9527
11588
|
return;
|
|
9528
11589
|
}
|
|
11590
|
+
if (command === "branch") {
|
|
11591
|
+
const { runBranch: runBranch2 } = await Promise.resolve().then(() => (init_branch2(), branch_exports));
|
|
11592
|
+
const explicitName = values.positionals[0];
|
|
11593
|
+
await runBranch2({
|
|
11594
|
+
explicitName,
|
|
11595
|
+
message: values.message,
|
|
11596
|
+
fromCommits: values.fromCommits,
|
|
11597
|
+
rescue: values.rescue,
|
|
11598
|
+
dryRun: values.dryRun,
|
|
11599
|
+
noSwitch: values.noSwitch,
|
|
11600
|
+
push: values.push,
|
|
11601
|
+
from: values.from,
|
|
11602
|
+
model: values.model,
|
|
11603
|
+
apiKey: values.apiKey
|
|
11604
|
+
});
|
|
11605
|
+
return;
|
|
11606
|
+
}
|
|
9529
11607
|
if (command === "init") {
|
|
9530
11608
|
const { init: init2 } = await Promise.resolve().then(() => (init_init(), init_exports));
|
|
9531
11609
|
init2({ uninstall: values.uninstall });
|
|
@@ -9533,14 +11611,12 @@ async function main() {
|
|
|
9533
11611
|
}
|
|
9534
11612
|
if (command === "team") {
|
|
9535
11613
|
const { team: team2 } = await Promise.resolve().then(() => (init_team(), team_exports));
|
|
9536
|
-
|
|
9537
|
-
await team2(positionals[0], positionals.slice(1));
|
|
11614
|
+
await team2(values.positionals[0], values.positionals.slice(1));
|
|
9538
11615
|
return;
|
|
9539
11616
|
}
|
|
9540
11617
|
if (command === "config") {
|
|
9541
|
-
const { config: config2 } = await Promise.resolve().then(() => (init_config2(),
|
|
9542
|
-
|
|
9543
|
-
config2(positionals);
|
|
11618
|
+
const { config: config2 } = await Promise.resolve().then(() => (init_config2(), config_exports2));
|
|
11619
|
+
config2(values.positionals);
|
|
9544
11620
|
return;
|
|
9545
11621
|
}
|
|
9546
11622
|
if (command === "upgrade") {
|
|
@@ -9550,7 +11626,7 @@ async function main() {
|
|
|
9550
11626
|
}
|
|
9551
11627
|
if (values.local) {
|
|
9552
11628
|
const { runLocalCommit: runLocalCommit2 } = await Promise.resolve().then(() => (init_local(), local_exports));
|
|
9553
|
-
await runLocalCommit2(
|
|
11629
|
+
await runLocalCommit2(values);
|
|
9554
11630
|
return;
|
|
9555
11631
|
}
|
|
9556
11632
|
const apiKeyToUse = apiKey ?? getApiKey();
|
|
@@ -9558,11 +11634,12 @@ async function main() {
|
|
|
9558
11634
|
const { getLocalProviderConfig: getLocalProviderConfig2 } = await Promise.resolve().then(() => (init_local(), local_exports));
|
|
9559
11635
|
if (getLocalProviderConfig2()) {
|
|
9560
11636
|
const { runLocalCommit: runLocalCommit2 } = await Promise.resolve().then(() => (init_local(), local_exports));
|
|
9561
|
-
await runLocalCommit2(
|
|
11637
|
+
await runLocalCommit2(values);
|
|
9562
11638
|
return;
|
|
9563
11639
|
}
|
|
9564
11640
|
}
|
|
9565
|
-
|
|
11641
|
+
const { runCommit: runCommit2 } = await Promise.resolve().then(() => (init_commit(), commit_exports));
|
|
11642
|
+
await runCommit2(values);
|
|
9566
11643
|
}
|
|
9567
11644
|
main().catch((err) => {
|
|
9568
11645
|
const args = process.argv.slice(2);
|
|
@@ -9572,3 +11649,7 @@ main().catch((err) => {
|
|
|
9572
11649
|
}
|
|
9573
11650
|
process.exit(1);
|
|
9574
11651
|
});
|
|
11652
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
11653
|
+
0 && (module.exports = {
|
|
11654
|
+
parseArgs
|
|
11655
|
+
});
|