geeto 0.6.5 → 0.9.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/README.md +23 -9
- package/lib/api/copilot-adapter.d.ts +14 -5
- package/lib/api/copilot-adapter.d.ts.map +1 -1
- package/lib/api/copilot-adapter.js +15 -21
- package/lib/api/copilot-adapter.js.map +1 -1
- package/lib/api/copilot-sdk.d.ts +3 -16
- package/lib/api/copilot-sdk.d.ts.map +1 -1
- package/lib/api/copilot-sdk.js +166 -441
- package/lib/api/copilot-sdk.js.map +1 -1
- package/lib/api/copilot.d.ts +3 -4
- package/lib/api/copilot.d.ts.map +1 -1
- package/lib/api/copilot.js +34 -31
- package/lib/api/copilot.js.map +1 -1
- package/lib/api/gemini-sdk.d.ts.map +1 -1
- package/lib/api/gemini-sdk.js +11 -77
- package/lib/api/gemini-sdk.js.map +1 -1
- package/lib/api/gemini.d.ts +2 -2
- package/lib/api/gemini.d.ts.map +1 -1
- package/lib/api/gemini.js +30 -22
- package/lib/api/gemini.js.map +1 -1
- package/lib/api/github.d.ts.map +1 -1
- package/lib/api/github.js +12 -6
- package/lib/api/github.js.map +1 -1
- package/lib/api/gitlab.d.ts +80 -0
- package/lib/api/gitlab.d.ts.map +1 -0
- package/lib/api/gitlab.js +192 -0
- package/lib/api/gitlab.js.map +1 -0
- package/lib/api/openrouter-sdk.d.ts +7 -7
- package/lib/api/openrouter-sdk.d.ts.map +1 -1
- package/lib/api/openrouter-sdk.js +17 -79
- package/lib/api/openrouter-sdk.js.map +1 -1
- package/lib/api/openrouter.d.ts.map +1 -1
- package/lib/api/openrouter.js +10 -20
- package/lib/api/openrouter.js.map +1 -1
- package/lib/api/platform.d.ts +78 -0
- package/lib/api/platform.d.ts.map +1 -0
- package/lib/api/platform.js +218 -0
- package/lib/api/platform.js.map +1 -0
- package/lib/api/trello.d.ts.map +1 -1
- package/lib/api/trello.js +8 -4
- package/lib/api/trello.js.map +1 -1
- package/lib/cli/input.d.ts +12 -3
- package/lib/cli/input.d.ts.map +1 -1
- package/lib/cli/input.js +123 -9
- package/lib/cli/input.js.map +1 -1
- package/lib/cli/menu.d.ts +1 -1
- package/lib/cli/menu.d.ts.map +1 -1
- package/lib/cli/menu.js +153 -96
- package/lib/cli/menu.js.map +1 -1
- package/lib/core/copilot-setup.d.ts +8 -7
- package/lib/core/copilot-setup.d.ts.map +1 -1
- package/lib/core/copilot-setup.js +60 -232
- package/lib/core/copilot-setup.js.map +1 -1
- package/lib/core/gemini-setup.js +7 -7
- package/lib/core/gemini-setup.js.map +1 -1
- package/lib/core/gitlab-setup.d.ts +5 -0
- package/lib/core/gitlab-setup.d.ts.map +1 -0
- package/lib/core/gitlab-setup.js +85 -0
- package/lib/core/gitlab-setup.js.map +1 -0
- package/lib/core/openrouter-setup.d.ts.map +1 -1
- package/lib/core/openrouter-setup.js +17 -0
- package/lib/core/openrouter-setup.js.map +1 -1
- package/lib/index.js +501 -704
- package/lib/index.js.map +1 -1
- package/lib/types/index.d.ts +11 -5
- package/lib/types/index.d.ts.map +1 -1
- package/lib/utils/ai-provider-helpers.d.ts +5 -0
- package/lib/utils/ai-provider-helpers.d.ts.map +1 -0
- package/lib/utils/ai-provider-helpers.js +23 -0
- package/lib/utils/ai-provider-helpers.js.map +1 -0
- package/lib/utils/ai-text.d.ts +23 -0
- package/lib/utils/ai-text.d.ts.map +1 -0
- package/lib/utils/ai-text.js +57 -0
- package/lib/utils/ai-text.js.map +1 -0
- package/lib/utils/ai-workflow.d.ts +18 -0
- package/lib/utils/ai-workflow.d.ts.map +1 -0
- package/lib/utils/ai-workflow.js +66 -0
- package/lib/utils/ai-workflow.js.map +1 -0
- package/lib/utils/branch-naming.d.ts.map +1 -1
- package/lib/utils/branch-naming.js +2 -4
- package/lib/utils/branch-naming.js.map +1 -1
- package/lib/utils/config.d.ts +13 -1
- package/lib/utils/config.d.ts.map +1 -1
- package/lib/utils/config.js +38 -1
- package/lib/utils/config.js.map +1 -1
- package/lib/utils/display.d.ts +24 -0
- package/lib/utils/display.d.ts.map +1 -1
- package/lib/utils/display.js +55 -1
- package/lib/utils/display.js.map +1 -1
- package/lib/utils/git-ai.d.ts +2 -0
- package/lib/utils/git-ai.d.ts.map +1 -1
- package/lib/utils/git-ai.js +30 -13
- package/lib/utils/git-ai.js.map +1 -1
- package/lib/utils/git-errors.d.ts.map +1 -1
- package/lib/utils/git-errors.js +4 -10
- package/lib/utils/git-errors.js.map +1 -1
- package/lib/utils/git.d.ts.map +1 -1
- package/lib/utils/git.js +5 -0
- package/lib/utils/git.js.map +1 -1
- package/lib/utils/github-helpers.d.ts +33 -0
- package/lib/utils/github-helpers.d.ts.map +1 -0
- package/lib/utils/github-helpers.js +101 -0
- package/lib/utils/github-helpers.js.map +1 -0
- package/lib/utils/logging.d.ts +8 -0
- package/lib/utils/logging.d.ts.map +1 -1
- package/lib/utils/logging.js +12 -0
- package/lib/utils/logging.js.map +1 -1
- package/lib/utils/prompt-loader.d.ts +9 -0
- package/lib/utils/prompt-loader.d.ts.map +1 -0
- package/lib/utils/prompt-loader.js +42 -0
- package/lib/utils/prompt-loader.js.map +1 -0
- package/lib/utils/prompts-embedded.d.ts +2 -0
- package/lib/utils/prompts-embedded.d.ts.map +1 -0
- package/lib/utils/prompts-embedded.js +255 -0
- package/lib/utils/prompts-embedded.js.map +1 -0
- package/lib/utils/scramble.d.ts +9 -86
- package/lib/utils/scramble.d.ts.map +1 -1
- package/lib/utils/scramble.js +27 -279
- package/lib/utils/scramble.js.map +1 -1
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/lib/workflows/alias.d.ts.map +1 -1
- package/lib/workflows/alias.js +1 -0
- package/lib/workflows/alias.js.map +1 -1
- package/lib/workflows/amend.d.ts.map +1 -1
- package/lib/workflows/amend.js +3 -6
- package/lib/workflows/amend.js.map +1 -1
- package/lib/workflows/branch-helpers.d.ts.map +1 -1
- package/lib/workflows/branch-helpers.js +0 -1
- package/lib/workflows/branch-helpers.js.map +1 -1
- package/lib/workflows/branch-utils.js +1 -1
- package/lib/workflows/branch-utils.js.map +1 -1
- package/lib/workflows/cleanup.js +3 -3
- package/lib/workflows/cleanup.js.map +1 -1
- package/lib/workflows/commit.d.ts.map +1 -1
- package/lib/workflows/commit.js +163 -202
- package/lib/workflows/commit.js.map +1 -1
- package/lib/workflows/doctor.d.ts +7 -0
- package/lib/workflows/doctor.d.ts.map +1 -0
- package/lib/workflows/doctor.js +284 -0
- package/lib/workflows/doctor.js.map +1 -0
- package/lib/workflows/history.d.ts.map +1 -1
- package/lib/workflows/history.js +2 -1
- package/lib/workflows/history.js.map +1 -1
- package/lib/workflows/issue.d.ts +1 -1
- package/lib/workflows/issue.d.ts.map +1 -1
- package/lib/workflows/issue.js +31 -130
- package/lib/workflows/issue.js.map +1 -1
- package/lib/workflows/main-helpers.d.ts +34 -0
- package/lib/workflows/main-helpers.d.ts.map +1 -0
- package/lib/workflows/main-helpers.js +346 -0
- package/lib/workflows/main-helpers.js.map +1 -0
- package/lib/workflows/main-steps.d.ts.map +1 -1
- package/lib/workflows/main-steps.js +11 -139
- package/lib/workflows/main-steps.js.map +1 -1
- package/lib/workflows/main.d.ts +2 -6
- package/lib/workflows/main.d.ts.map +1 -1
- package/lib/workflows/main.js +43 -365
- package/lib/workflows/main.js.map +1 -1
- package/lib/workflows/pr.d.ts +2 -2
- package/lib/workflows/pr.d.ts.map +1 -1
- package/lib/workflows/pr.js +52 -152
- package/lib/workflows/pr.js.map +1 -1
- package/lib/workflows/prune.d.ts.map +1 -1
- package/lib/workflows/prune.js +2 -10
- package/lib/workflows/prune.js.map +1 -1
- package/lib/workflows/pull.d.ts.map +1 -1
- package/lib/workflows/pull.js +2 -24
- package/lib/workflows/pull.js.map +1 -1
- package/lib/workflows/release-merge.d.ts +12 -0
- package/lib/workflows/release-merge.d.ts.map +1 -0
- package/lib/workflows/release-merge.js +569 -0
- package/lib/workflows/release-merge.js.map +1 -0
- package/lib/workflows/release-notes.d.ts +13 -0
- package/lib/workflows/release-notes.d.ts.map +1 -0
- package/lib/workflows/release-notes.js +141 -0
- package/lib/workflows/release-notes.js.map +1 -0
- package/lib/workflows/release-recover.d.ts +5 -0
- package/lib/workflows/release-recover.d.ts.map +1 -0
- package/lib/workflows/release-recover.js +137 -0
- package/lib/workflows/release-recover.js.map +1 -0
- package/lib/workflows/release-sync.d.ts +7 -0
- package/lib/workflows/release-sync.d.ts.map +1 -0
- package/lib/workflows/release-sync.js +321 -0
- package/lib/workflows/release-sync.js.map +1 -0
- package/lib/workflows/release-utils.d.ts +36 -0
- package/lib/workflows/release-utils.d.ts.map +1 -0
- package/lib/workflows/release-utils.js +150 -0
- package/lib/workflows/release-utils.js.map +1 -0
- package/lib/workflows/release.d.ts.map +1 -1
- package/lib/workflows/release.js +97 -723
- package/lib/workflows/release.js.map +1 -1
- package/lib/workflows/repo-settings.d.ts +2 -2
- package/lib/workflows/repo-settings.d.ts.map +1 -1
- package/lib/workflows/repo-settings.js +35 -25
- package/lib/workflows/repo-settings.js.map +1 -1
- package/lib/workflows/reword.d.ts.map +1 -1
- package/lib/workflows/reword.js +159 -167
- package/lib/workflows/reword.js.map +1 -1
- package/lib/workflows/security-gate.d.ts.map +1 -1
- package/lib/workflows/security-gate.js +18 -94
- package/lib/workflows/security-gate.js.map +1 -1
- package/lib/workflows/settings.d.ts +2 -1
- package/lib/workflows/settings.d.ts.map +1 -1
- package/lib/workflows/settings.js +289 -19
- package/lib/workflows/settings.js.map +1 -1
- package/lib/workflows/stash.d.ts.map +1 -1
- package/lib/workflows/stash.js +2 -1
- package/lib/workflows/stash.js.map +1 -1
- package/lib/workflows/trello-menu.d.ts +2 -5
- package/lib/workflows/trello-menu.d.ts.map +1 -1
- package/lib/workflows/trello-menu.js +67 -228
- package/lib/workflows/trello-menu.js.map +1 -1
- package/lib/workflows/undo.d.ts.map +1 -1
- package/lib/workflows/undo.js +2 -1
- package/lib/workflows/undo.js.map +1 -1
- package/package.json +3 -6
- package/prompts/branch-name-prompt.md +4 -0
- package/prompts/commit-message-prompt.md +12 -0
- package/prompts/issue-prompt.md +19 -0
- package/prompts/issue-review.with-context.prompt.yml +77 -0
- package/prompts/pr-prompt.md +14 -0
- package/prompts/release-notes-prompt.md +35 -0
- package/prompts/repo-description-prompt.md +1 -0
- package/prompts/security-gate-prompt.md +80 -0
package/lib/workflows/reword.js
CHANGED
|
@@ -14,9 +14,10 @@ import { multiSelect, select } from '../cli/menu.js';
|
|
|
14
14
|
import { colors } from '../utils/colors.js';
|
|
15
15
|
import { extractCommitBody, extractCommitTitle, formatCommitBody, normalizeAIOutput, } from '../utils/commit-helpers.js';
|
|
16
16
|
import { DEFAULT_GEMINI_MODEL } from '../utils/config.js';
|
|
17
|
+
import { BOX_W } from '../utils/display.js';
|
|
17
18
|
import { isDryRun, logDryRun } from '../utils/dry-run.js';
|
|
18
19
|
import { execAsync, execSilent } from '../utils/exec.js';
|
|
19
|
-
import { chooseModelForProvider, getAIProviderShortName, getModelValue, interactiveAIFallback, isContextLimitFailure, isTransientAIFailure, } from '../utils/git-ai.js';
|
|
20
|
+
import { chooseModelForProvider, generateCommitMessageWithProvider, getAIProviderShortName, getModelValue, interactiveAIFallback, isContextLimitFailure, isTransientAIFailure, } from '../utils/git-ai.js';
|
|
20
21
|
import { getCurrentBranch } from '../utils/git.js';
|
|
21
22
|
import { log } from '../utils/logging.js';
|
|
22
23
|
import { ScrambleProgress } from '../utils/scramble.js';
|
|
@@ -255,28 +256,97 @@ const getRepoUrl = () => {
|
|
|
255
256
|
};
|
|
256
257
|
/** Wrap text in OSC 8 hyperlink (clickable in supported terminals). */
|
|
257
258
|
const hyperlink = (url, text) => `\u001B]8;;${url}\u0007${text}\u001B]8;;\u0007`;
|
|
258
|
-
// ──
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
259
|
+
// ── Small helpers ──────────────────────────────────────────────────
|
|
260
|
+
/** Resolve current AI provider and model from persisted state. */
|
|
261
|
+
const resolveAIProvider = (state) => {
|
|
262
|
+
const provider = (state.aiProvider === 'manual' ? undefined : state.aiProvider) ?? 'gemini';
|
|
263
|
+
if (provider === 'copilot')
|
|
264
|
+
return { provider, model: state.copilotModel };
|
|
265
|
+
if (provider === 'openrouter')
|
|
266
|
+
return { provider, model: state.openrouterModel };
|
|
267
|
+
return { provider, model: state.geminiModel ?? DEFAULT_GEMINI_MODEL };
|
|
268
|
+
};
|
|
269
|
+
/** Normalize AI output and extract title + body into final commit message. */
|
|
270
|
+
const buildFinalMessage = (raw) => {
|
|
271
|
+
const normalized = normalizeAIOutput(raw);
|
|
272
|
+
const extracted = extractCommitTitle(normalized);
|
|
273
|
+
let title;
|
|
274
|
+
let bodyText = null;
|
|
275
|
+
if (extracted) {
|
|
276
|
+
title = extracted;
|
|
277
|
+
bodyText = extractCommitBody(normalized, title);
|
|
278
|
+
if (bodyText)
|
|
279
|
+
bodyText = formatCommitBody(bodyText);
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
const first = normalized.split('\n').find((l) => l.trim());
|
|
283
|
+
title = first?.trim() ?? normalized;
|
|
284
|
+
}
|
|
285
|
+
return bodyText ? `${title}\n\n${bodyText}` : title;
|
|
286
|
+
};
|
|
287
|
+
/** Regenerate commit message directly via the current provider (with retry). */
|
|
288
|
+
const regenerateDirect = async (state, diff, correction) => {
|
|
289
|
+
const maxAttempts = 2;
|
|
290
|
+
let aiResult = null;
|
|
291
|
+
for (let attempt = 0; attempt < maxAttempts && !aiResult; attempt++) {
|
|
292
|
+
let modelName = '';
|
|
293
|
+
if (state.aiProvider === 'copilot' && state.copilotModel) {
|
|
294
|
+
modelName = state.copilotModel;
|
|
295
|
+
}
|
|
296
|
+
else if (state.aiProvider === 'openrouter' && state.openrouterModel) {
|
|
297
|
+
modelName = state.openrouterModel;
|
|
298
|
+
}
|
|
299
|
+
else if (state.aiProvider === 'gemini') {
|
|
300
|
+
modelName = state.geminiModel ?? DEFAULT_GEMINI_MODEL;
|
|
301
|
+
}
|
|
302
|
+
if (correction)
|
|
303
|
+
console.log('');
|
|
304
|
+
const sp = new ScrambleProgress();
|
|
305
|
+
sp.start([
|
|
306
|
+
`Regenerating with ${getAIProviderShortName(state.aiProvider ?? 'gemini')}${modelName ? ` (${modelName})` : ''}`,
|
|
307
|
+
]);
|
|
308
|
+
try {
|
|
309
|
+
switch (state.aiProvider) {
|
|
310
|
+
case 'copilot': {
|
|
311
|
+
const { generateCommitMessage } = await import('../api/copilot.js');
|
|
312
|
+
aiResult = await generateCommitMessage(diff, correction, state.copilotModel);
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
case 'openrouter': {
|
|
316
|
+
const { generateCommitMessage } = await import('../api/openrouter.js');
|
|
317
|
+
aiResult = await generateCommitMessage(diff, correction, state.openrouterModel);
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
case 'gemini': {
|
|
321
|
+
const { generateCommitMessage } = await import('../api/gemini.js');
|
|
322
|
+
aiResult = await generateCommitMessage(diff, correction, state.geminiModel);
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
default: {
|
|
326
|
+
aiResult = null;
|
|
327
|
+
break;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
sp.stop();
|
|
331
|
+
}
|
|
332
|
+
catch {
|
|
333
|
+
sp.stop();
|
|
334
|
+
aiResult = null;
|
|
335
|
+
}
|
|
336
|
+
if (!aiResult && attempt < maxAttempts - 1) {
|
|
337
|
+
log.ai('Regenerate returned no suggestion; retrying...');
|
|
338
|
+
}
|
|
279
339
|
}
|
|
340
|
+
return aiResult;
|
|
341
|
+
};
|
|
342
|
+
// ── Extracted workflow steps ───────────────────────────────────────
|
|
343
|
+
/**
|
|
344
|
+
* Show commit log with format consistency indicators and let user
|
|
345
|
+
* pick which commits to reword. Returns sorted (oldest-first) array
|
|
346
|
+
* or null when nothing was selected.
|
|
347
|
+
*/
|
|
348
|
+
const selectCommitsToReword = async (ctx) => {
|
|
349
|
+
const { commits, repoUrl } = ctx;
|
|
280
350
|
// Detect commit format consistency (skip merge commits)
|
|
281
351
|
const isMerge = (subject) => /^Merge\s/.test(subject);
|
|
282
352
|
const formats = commits.map((c) => (isMerge(c.subject) ? null : detectCommitFormat(c.subject)));
|
|
@@ -298,20 +368,16 @@ export const handleReword = async () => {
|
|
|
298
368
|
}
|
|
299
369
|
// Priority-based inconsistency detection:
|
|
300
370
|
// conventional (1st) > bracket-tag (2nd) > no-prefix / other (3rd)
|
|
301
|
-
// - conventional: NEVER gets ⚠ (gold standard)
|
|
302
|
-
// - bracket-tag: ⚠ only when conventional is the majority
|
|
303
|
-
// - no-prefix/other: ⚠ when ANY conventional or bracket-tag exists
|
|
304
371
|
const hasConventional = (formatCounts.get('conventional') ?? 0) > 0;
|
|
305
372
|
const hasBracketTag = (formatCounts.get('bracket-tag') ?? 0) > 0;
|
|
306
373
|
const hasStructured = hasConventional || hasBracketTag;
|
|
307
374
|
const isInconsistent = (fmt) => {
|
|
308
375
|
if (fmt === null)
|
|
309
|
-
return false;
|
|
376
|
+
return false;
|
|
310
377
|
if (fmt === 'conventional')
|
|
311
|
-
return false;
|
|
378
|
+
return false;
|
|
312
379
|
if (fmt === 'bracket-tag')
|
|
313
|
-
return majorityFormat === 'conventional';
|
|
314
|
-
// no-prefix / other: flag if any structured format exists
|
|
380
|
+
return majorityFormat === 'conventional';
|
|
315
381
|
return hasStructured;
|
|
316
382
|
};
|
|
317
383
|
const inconsistentCount = formats.filter((f) => isInconsistent(f)).length;
|
|
@@ -319,35 +385,30 @@ export const handleReword = async () => {
|
|
|
319
385
|
if (inconsistentCount > 0) {
|
|
320
386
|
console.log(` ${colors.yellow}⚠${colors.reset} ${inconsistentCount} inconsistent commit(s) detected`);
|
|
321
387
|
console.log(` ${colors.gray}Team pattern: ${formatLabel(majorityFormat)} (${majorityCount}/${totalNonMerge})${colors.reset}`);
|
|
322
|
-
// If bracket-tag is majority, gently suggest conventional commits
|
|
323
388
|
if (majorityFormat === 'bracket-tag' && !hasConventional) {
|
|
324
389
|
console.log(` ${colors.gray}💡 Tip: conventional commits is the recommended project standard${colors.reset}`);
|
|
325
390
|
}
|
|
326
391
|
console.log('');
|
|
327
392
|
}
|
|
328
393
|
else if (majorityFormat !== 'conventional' && totalNonMerge > 0) {
|
|
329
|
-
// No inconsistencies but not using conventional — soft tip
|
|
330
394
|
console.log(` ${colors.gray}💡 Tip: conventional commits is the recommended project standard${colors.reset}`);
|
|
331
395
|
console.log('');
|
|
332
396
|
}
|
|
333
397
|
// Compute column widths
|
|
334
398
|
const maxHash = Math.max(...commits.map((c) => c.shortHash.length));
|
|
335
|
-
const repoUrl = getRepoUrl();
|
|
336
399
|
// Build multi-select options
|
|
337
400
|
const options = commits.map((c, i) => {
|
|
338
401
|
const hashCol = c.shortHash.padEnd(maxHash);
|
|
339
402
|
const refs = formatRefs(c.refs);
|
|
340
403
|
const subj = c.subject.length > 60 ? c.subject.slice(0, 57) + '...' : c.subject;
|
|
341
|
-
// Show indicator only when inconsistencies exist
|
|
342
404
|
const fmt = formats[i] ?? null;
|
|
343
405
|
const indicator = inconsistentCount > 0
|
|
344
406
|
? isInconsistent(fmt)
|
|
345
407
|
? `${colors.red}⚠${colors.reset} `
|
|
346
408
|
: fmt === null
|
|
347
|
-
? ' '
|
|
409
|
+
? ' '
|
|
348
410
|
: `${colors.green}✓${colors.reset} `
|
|
349
411
|
: '';
|
|
350
|
-
// Wrap hash in OSC 8 hyperlink when repo URL is available
|
|
351
412
|
const hashDisplay = repoUrl
|
|
352
413
|
? hyperlink(`${repoUrl}/commit/${c.hash}`, `${colors.yellow}${hashCol}${colors.reset}`)
|
|
353
414
|
: `${colors.yellow}${hashCol}${colors.reset}`;
|
|
@@ -374,7 +435,7 @@ export const handleReword = async () => {
|
|
|
374
435
|
const selected = await multiSelect('Select commits to edit:', options);
|
|
375
436
|
if (selected.length === 0) {
|
|
376
437
|
log.info('No commits selected.');
|
|
377
|
-
return;
|
|
438
|
+
return null;
|
|
378
439
|
}
|
|
379
440
|
// Sort selected: oldest first (for rebase ordering)
|
|
380
441
|
const filtered = commits.filter((c) => selected.includes(c.hash));
|
|
@@ -384,11 +445,18 @@ export const handleReword = async () => {
|
|
|
384
445
|
if (item)
|
|
385
446
|
selectedCommits.push(item);
|
|
386
447
|
}
|
|
387
|
-
|
|
448
|
+
return selectedCommits;
|
|
449
|
+
};
|
|
450
|
+
/**
|
|
451
|
+
* For each selected commit, let the user choose AI / manual / skip
|
|
452
|
+
* and collect new commit messages. Returns a Map of hash → new message.
|
|
453
|
+
*/
|
|
454
|
+
const generateNewMessages = async (selectedCommits, ctx) => {
|
|
455
|
+
const { repoUrl, branch } = ctx;
|
|
456
|
+
const newMessages = new Map();
|
|
388
457
|
console.log('');
|
|
389
458
|
log.info(`Editing ${selectedCommits.length} commit message(s)...`);
|
|
390
459
|
console.log('');
|
|
391
|
-
const newMessages = new Map();
|
|
392
460
|
// Load provider/model state for AI generation
|
|
393
461
|
const state = loadState() ?? {
|
|
394
462
|
step: 0,
|
|
@@ -451,44 +519,20 @@ export const handleReword = async () => {
|
|
|
451
519
|
continue;
|
|
452
520
|
}
|
|
453
521
|
// Determine current provider/model
|
|
454
|
-
let
|
|
455
|
-
let currentModel;
|
|
456
|
-
if (currentProvider === 'copilot') {
|
|
457
|
-
currentModel = state.copilotModel;
|
|
458
|
-
}
|
|
459
|
-
else if (currentProvider === 'openrouter') {
|
|
460
|
-
currentModel = state.openrouterModel;
|
|
461
|
-
}
|
|
462
|
-
else {
|
|
463
|
-
currentModel = state.geminiModel ?? DEFAULT_GEMINI_MODEL;
|
|
464
|
-
}
|
|
522
|
+
let { provider: currentProvider, model: currentModel } = resolveAIProvider(state);
|
|
465
523
|
// Initial AI generation
|
|
466
524
|
let correction = editGuidance.trim();
|
|
467
525
|
let initialAiResult = null;
|
|
468
526
|
const spinner = new ScrambleProgress();
|
|
469
527
|
try {
|
|
470
528
|
spinner.start([
|
|
471
|
-
|
|
472
|
-
`generating commit message with ${getAIProviderShortName(currentProvider)}${currentModel ? ` (${currentModel})` : ''}...`,
|
|
473
|
-
'formatting conventional commit...',
|
|
529
|
+
`Generating commit message with ${getAIProviderShortName(currentProvider)}${currentModel ? ` (${currentModel})` : ''}`,
|
|
474
530
|
]);
|
|
475
|
-
|
|
476
|
-
const { generateCommitMessage } = await import('../api/copilot.js');
|
|
477
|
-
initialAiResult = await generateCommitMessage(diff, correction, state.copilotModel);
|
|
478
|
-
}
|
|
479
|
-
else if (currentProvider === 'openrouter') {
|
|
480
|
-
const { generateCommitMessage } = await import('../api/openrouter.js');
|
|
481
|
-
initialAiResult = await generateCommitMessage(diff, correction, state.openrouterModel);
|
|
482
|
-
}
|
|
483
|
-
else {
|
|
484
|
-
const { generateCommitMessage } = await import('../api/gemini.js');
|
|
485
|
-
initialAiResult = await generateCommitMessage(diff, correction, state.geminiModel);
|
|
486
|
-
}
|
|
531
|
+
initialAiResult = await generateCommitMessageWithProvider(currentProvider, diff, correction, state.copilotModel, state.openrouterModel, state.geminiModel);
|
|
487
532
|
spinner.stop();
|
|
488
533
|
}
|
|
489
534
|
catch {
|
|
490
|
-
spinner.
|
|
491
|
-
log.warn('AI generation failed, falling back to manual edit');
|
|
535
|
+
spinner.fail('AI generation failed, falling back to manual edit');
|
|
492
536
|
const edited = await editInline(currentMsg, `Edit: ${commit.shortHash} ${commit.subject}`);
|
|
493
537
|
if (edited && edited.trim() !== currentMsg.trim()) {
|
|
494
538
|
newMessages.set(commit.hash, edited.trim());
|
|
@@ -515,60 +559,7 @@ export const handleReword = async () => {
|
|
|
515
559
|
aiResult = initialAiResult;
|
|
516
560
|
}
|
|
517
561
|
else if (forceDirect) {
|
|
518
|
-
|
|
519
|
-
const maxDirectAttempts = 2;
|
|
520
|
-
while (directAttempt < maxDirectAttempts && !aiResult) {
|
|
521
|
-
let directModelName = '';
|
|
522
|
-
if (state.aiProvider === 'copilot' && state.copilotModel) {
|
|
523
|
-
directModelName = state.copilotModel;
|
|
524
|
-
}
|
|
525
|
-
else if (state.aiProvider === 'openrouter' && state.openrouterModel) {
|
|
526
|
-
directModelName = state.openrouterModel;
|
|
527
|
-
}
|
|
528
|
-
else if (state.aiProvider === 'gemini') {
|
|
529
|
-
directModelName = state.geminiModel ?? DEFAULT_GEMINI_MODEL;
|
|
530
|
-
}
|
|
531
|
-
if (correction)
|
|
532
|
-
console.log('');
|
|
533
|
-
const sp = new ScrambleProgress();
|
|
534
|
-
sp.start([
|
|
535
|
-
'reviewing feedback...',
|
|
536
|
-
`regenerating with ${getAIProviderShortName(state.aiProvider ?? 'gemini')}${directModelName ? ` (${directModelName})` : ''}...`,
|
|
537
|
-
'formatting conventional commit...',
|
|
538
|
-
]);
|
|
539
|
-
try {
|
|
540
|
-
switch (state.aiProvider) {
|
|
541
|
-
case 'copilot': {
|
|
542
|
-
const { generateCommitMessage } = await import('../api/copilot.js');
|
|
543
|
-
aiResult = await generateCommitMessage(diff, correction, state.copilotModel);
|
|
544
|
-
break;
|
|
545
|
-
}
|
|
546
|
-
case 'openrouter': {
|
|
547
|
-
const { generateCommitMessage } = await import('../api/openrouter.js');
|
|
548
|
-
aiResult = await generateCommitMessage(diff, correction, state.openrouterModel);
|
|
549
|
-
break;
|
|
550
|
-
}
|
|
551
|
-
case 'gemini': {
|
|
552
|
-
const { generateCommitMessage } = await import('../api/gemini.js');
|
|
553
|
-
aiResult = await generateCommitMessage(diff, correction, state.geminiModel);
|
|
554
|
-
break;
|
|
555
|
-
}
|
|
556
|
-
default: {
|
|
557
|
-
aiResult = null;
|
|
558
|
-
break;
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
sp.stop();
|
|
562
|
-
}
|
|
563
|
-
catch {
|
|
564
|
-
sp.stop();
|
|
565
|
-
aiResult = null;
|
|
566
|
-
}
|
|
567
|
-
directAttempt += 1;
|
|
568
|
-
if (!aiResult && directAttempt < maxDirectAttempts) {
|
|
569
|
-
log.ai('Regenerate returned no suggestion; retrying...');
|
|
570
|
-
}
|
|
571
|
-
}
|
|
562
|
+
aiResult = await regenerateDirect(state, diff, correction);
|
|
572
563
|
}
|
|
573
564
|
else {
|
|
574
565
|
const provForFallback = (state.aiProvider ?? 'gemini');
|
|
@@ -655,22 +646,7 @@ export const handleReword = async () => {
|
|
|
655
646
|
}
|
|
656
647
|
switch (acceptChoice) {
|
|
657
648
|
case 'accept': {
|
|
658
|
-
|
|
659
|
-
const extracted = extractCommitTitle(normalized);
|
|
660
|
-
let title;
|
|
661
|
-
let bodyText = null;
|
|
662
|
-
if (extracted) {
|
|
663
|
-
title = extracted;
|
|
664
|
-
bodyText = extractCommitBody(normalized, title);
|
|
665
|
-
if (bodyText)
|
|
666
|
-
bodyText = formatCommitBody(bodyText);
|
|
667
|
-
}
|
|
668
|
-
else {
|
|
669
|
-
const first = normalized.split('\n').find((l) => l.trim());
|
|
670
|
-
title = first?.trim() ?? normalized;
|
|
671
|
-
}
|
|
672
|
-
const finalMsg = bodyText ? `${title}\n\n${bodyText}` : title;
|
|
673
|
-
newMessages.set(commit.hash, finalMsg.trim());
|
|
649
|
+
newMessages.set(commit.hash, buildFinalMessage(commitMessage).trim());
|
|
674
650
|
log.success(`Message set for ${commit.shortHash}`);
|
|
675
651
|
commitDone = true;
|
|
676
652
|
break;
|
|
@@ -776,22 +752,7 @@ export const handleReword = async () => {
|
|
|
776
752
|
case 'edit': {
|
|
777
753
|
const edited = await editInline(commitMessage, `Edit: ${commit.shortHash}`);
|
|
778
754
|
if (edited?.trim()) {
|
|
779
|
-
|
|
780
|
-
const editedTitle = extractCommitTitle(editedNorm);
|
|
781
|
-
let title;
|
|
782
|
-
let bodyText = null;
|
|
783
|
-
if (editedTitle) {
|
|
784
|
-
title = editedTitle;
|
|
785
|
-
bodyText = extractCommitBody(editedNorm, title);
|
|
786
|
-
if (bodyText)
|
|
787
|
-
bodyText = formatCommitBody(bodyText);
|
|
788
|
-
}
|
|
789
|
-
else {
|
|
790
|
-
const first = editedNorm.split('\n').find((l) => l.trim());
|
|
791
|
-
title = first?.trim() ?? editedNorm;
|
|
792
|
-
}
|
|
793
|
-
const finalMsg = bodyText ? `${title}\n\n${bodyText}` : title;
|
|
794
|
-
newMessages.set(commit.hash, finalMsg.trim());
|
|
755
|
+
newMessages.set(commit.hash, buildFinalMessage(edited.trim()).trim());
|
|
795
756
|
log.success(`Message set for ${commit.shortHash}`);
|
|
796
757
|
commitDone = true;
|
|
797
758
|
}
|
|
@@ -804,12 +765,16 @@ export const handleReword = async () => {
|
|
|
804
765
|
}
|
|
805
766
|
console.log('');
|
|
806
767
|
}
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
768
|
+
return newMessages;
|
|
769
|
+
};
|
|
770
|
+
/**
|
|
771
|
+
* Preview proposed changes, confirm with the user, then execute
|
|
772
|
+
* git rebase -i with auto-generated editor scripts.
|
|
773
|
+
*/
|
|
774
|
+
const executeRebase = async (selectedCommits, newMessages, ctx) => {
|
|
775
|
+
const { commits, repoUrl, branch, isProtected } = ctx;
|
|
811
776
|
// Preview changes — complete before/after summary
|
|
812
|
-
const line = '─'.repeat(
|
|
777
|
+
const line = '─'.repeat(BOX_W);
|
|
813
778
|
console.log('');
|
|
814
779
|
console.log(` ${colors.cyan}┌${line}┐${colors.reset}`);
|
|
815
780
|
console.log(` ${colors.cyan}│${colors.reset} ${colors.bright}Changes to apply (${newMessages.size} commit${newMessages.size > 1 ? 's' : ''})${colors.reset}`);
|
|
@@ -900,7 +865,6 @@ export const handleReword = async () => {
|
|
|
900
865
|
parentRef = execSilent(`git rev-parse ${oldestHash}^`).trim();
|
|
901
866
|
}
|
|
902
867
|
catch {
|
|
903
|
-
// Oldest commit might be the root commit
|
|
904
868
|
parentRef = '--root';
|
|
905
869
|
}
|
|
906
870
|
// Execute rebase
|
|
@@ -915,7 +879,6 @@ export const handleReword = async () => {
|
|
|
915
879
|
const result = spawnSync('git', ['rebase', '-i', rebaseTarget], { stdio: 'inherit', env });
|
|
916
880
|
if (result.status === 0) {
|
|
917
881
|
log.success(`Reworded ${newMessages.size} commit(s)!`);
|
|
918
|
-
// Show updated commits
|
|
919
882
|
for (const [hash] of newMessages) {
|
|
920
883
|
const short = hash.slice(0, 7);
|
|
921
884
|
const newMsg = getCommitMessage(hash)?.split('\n')[0];
|
|
@@ -923,7 +886,7 @@ export const handleReword = async () => {
|
|
|
923
886
|
console.log(` ${colors.green}✓${colors.reset} ${colors.yellow}${short}${colors.reset} ${newMsg}`);
|
|
924
887
|
}
|
|
925
888
|
}
|
|
926
|
-
// Offer force push
|
|
889
|
+
// Offer force push
|
|
927
890
|
console.log('');
|
|
928
891
|
const shouldPush = confirm('Force push to update remote? (recommended)');
|
|
929
892
|
if (shouldPush) {
|
|
@@ -933,11 +896,7 @@ export const handleReword = async () => {
|
|
|
933
896
|
else {
|
|
934
897
|
console.log('');
|
|
935
898
|
const pushProgress = new ScrambleProgress();
|
|
936
|
-
pushProgress.start([
|
|
937
|
-
'preparing force push...',
|
|
938
|
-
'pushing to remote...',
|
|
939
|
-
'confirming remote state...',
|
|
940
|
-
]);
|
|
899
|
+
pushProgress.start([`Force pushing to origin/${branch}`]);
|
|
941
900
|
try {
|
|
942
901
|
await execAsync(`git push --force-with-lease origin "${branch}"`, true);
|
|
943
902
|
pushProgress.succeed(`Force pushed ${branch} to remote`);
|
|
@@ -950,7 +909,7 @@ export const handleReword = async () => {
|
|
|
950
909
|
}
|
|
951
910
|
}
|
|
952
911
|
}
|
|
953
|
-
// Team warning
|
|
912
|
+
// Team warning
|
|
954
913
|
console.log('');
|
|
955
914
|
console.log(` ${colors.yellow}⚠${colors.reset} ${colors.bright}History rewritten!${colors.reset} All commit hashes on ${colors.cyan}${branch}${colors.reset} have changed.`);
|
|
956
915
|
console.log(` ${colors.gray}If this branch is shared, inform your team to run:${colors.reset}`);
|
|
@@ -974,7 +933,6 @@ export const handleReword = async () => {
|
|
|
974
933
|
log.info('Run: git rebase --abort to undo');
|
|
975
934
|
}
|
|
976
935
|
finally {
|
|
977
|
-
// Cleanup temp files
|
|
978
936
|
try {
|
|
979
937
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
980
938
|
}
|
|
@@ -983,4 +941,38 @@ export const handleReword = async () => {
|
|
|
983
941
|
}
|
|
984
942
|
}
|
|
985
943
|
};
|
|
944
|
+
// ── Main handler ───────────────────────────────────────────────────
|
|
945
|
+
export const handleReword = async () => {
|
|
946
|
+
log.banner();
|
|
947
|
+
log.step(`${colors.cyan}Edit Commit Messages${colors.reset}\n`);
|
|
948
|
+
const branch = getCurrentBranch();
|
|
949
|
+
const isProtected = ['main', 'master', 'develop', 'production', 'staging'].includes(branch);
|
|
950
|
+
console.log(` ${colors.gray}Branch: ${branch}${colors.reset}`);
|
|
951
|
+
if (isProtected) {
|
|
952
|
+
console.log(` ${colors.red}⚠${colors.reset} ${colors.bright}Protected branch${colors.reset} — rewriting history here affects all collaborators.`);
|
|
953
|
+
}
|
|
954
|
+
// Check working tree
|
|
955
|
+
if (!isWorkingTreeClean()) {
|
|
956
|
+
log.error('Working tree has uncommitted changes.');
|
|
957
|
+
log.info('Please commit or stash changes before rewording.');
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
// Load commits
|
|
961
|
+
const commits = getRecentCommits(30);
|
|
962
|
+
if (commits.length === 0) {
|
|
963
|
+
log.warn('No commits found.');
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
const repoUrl = getRepoUrl();
|
|
967
|
+
const ctx = { branch, isProtected, commits, repoUrl };
|
|
968
|
+
const selectedCommits = await selectCommitsToReword(ctx);
|
|
969
|
+
if (!selectedCommits)
|
|
970
|
+
return;
|
|
971
|
+
const newMessages = await generateNewMessages(selectedCommits, ctx);
|
|
972
|
+
if (newMessages.size === 0) {
|
|
973
|
+
log.info('No messages changed.');
|
|
974
|
+
return;
|
|
975
|
+
}
|
|
976
|
+
await executeRebase(selectedCommits, newMessages, ctx);
|
|
977
|
+
};
|
|
986
978
|
//# sourceMappingURL=reword.js.map
|