geeto 0.1.0-beta.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/LICENSE +21 -0
- package/README.md +152 -0
- package/lib/api/copilot-adapter.d.ts +11 -0
- package/lib/api/copilot-adapter.d.ts.map +1 -0
- package/lib/api/copilot-adapter.js +41 -0
- package/lib/api/copilot-adapter.js.map +1 -0
- package/lib/api/copilot-sdk.d.ts +48 -0
- package/lib/api/copilot-sdk.d.ts.map +1 -0
- package/lib/api/copilot-sdk.js +451 -0
- package/lib/api/copilot-sdk.js.map +1 -0
- package/lib/api/copilot.d.ts +21 -0
- package/lib/api/copilot.d.ts.map +1 -0
- package/lib/api/copilot.js +87 -0
- package/lib/api/copilot.js.map +1 -0
- package/lib/api/gemini-sdk.d.ts +24 -0
- package/lib/api/gemini-sdk.d.ts.map +1 -0
- package/lib/api/gemini-sdk.js +245 -0
- package/lib/api/gemini-sdk.js.map +1 -0
- package/lib/api/gemini.d.ts +21 -0
- package/lib/api/gemini.d.ts.map +1 -0
- package/lib/api/gemini.js +87 -0
- package/lib/api/gemini.js.map +1 -0
- package/lib/api/openrouter-sdk.d.ts +58 -0
- package/lib/api/openrouter-sdk.d.ts.map +1 -0
- package/lib/api/openrouter-sdk.js +341 -0
- package/lib/api/openrouter-sdk.js.map +1 -0
- package/lib/api/openrouter.d.ts +17 -0
- package/lib/api/openrouter.d.ts.map +1 -0
- package/lib/api/openrouter.js +111 -0
- package/lib/api/openrouter.js.map +1 -0
- package/lib/api/trello.d.ts +17 -0
- package/lib/api/trello.d.ts.map +1 -0
- package/lib/api/trello.js +72 -0
- package/lib/api/trello.js.map +1 -0
- package/lib/cli/input.d.ts +39 -0
- package/lib/cli/input.d.ts.map +1 -0
- package/lib/cli/input.js +119 -0
- package/lib/cli/input.js.map +1 -0
- package/lib/cli/menu.d.ts +9 -0
- package/lib/cli/menu.d.ts.map +1 -0
- package/lib/cli/menu.js +201 -0
- package/lib/cli/menu.js.map +1 -0
- package/lib/core/constants.d.ts +22 -0
- package/lib/core/constants.d.ts.map +1 -0
- package/lib/core/constants.js +24 -0
- package/lib/core/constants.js.map +1 -0
- package/lib/core/copilot-setup.d.ts +13 -0
- package/lib/core/copilot-setup.d.ts.map +1 -0
- package/lib/core/copilot-setup.js +447 -0
- package/lib/core/copilot-setup.js.map +1 -0
- package/lib/core/gemini-setup.d.ts +8 -0
- package/lib/core/gemini-setup.d.ts.map +1 -0
- package/lib/core/gemini-setup.js +84 -0
- package/lib/core/gemini-setup.js.map +1 -0
- package/lib/core/menu-constants.d.ts +24 -0
- package/lib/core/menu-constants.d.ts.map +1 -0
- package/lib/core/menu-constants.js +22 -0
- package/lib/core/menu-constants.js.map +1 -0
- package/lib/core/openrouter-setup.d.ts +8 -0
- package/lib/core/openrouter-setup.d.ts.map +1 -0
- package/lib/core/openrouter-setup.js +73 -0
- package/lib/core/openrouter-setup.js.map +1 -0
- package/lib/core/setup.d.ts +21 -0
- package/lib/core/setup.d.ts.map +1 -0
- package/lib/core/setup.js +88 -0
- package/lib/core/setup.js.map +1 -0
- package/lib/core/trello-setup.d.ts +8 -0
- package/lib/core/trello-setup.d.ts.map +1 -0
- package/lib/core/trello-setup.js +107 -0
- package/lib/core/trello-setup.js.map +1 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +250 -0
- package/lib/index.js.map +1 -0
- package/lib/types/index.d.ts +78 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/index.js +2 -0
- package/lib/types/index.js.map +1 -0
- package/lib/utils/branch-naming.d.ts +11 -0
- package/lib/utils/branch-naming.d.ts.map +1 -0
- package/lib/utils/branch-naming.js +396 -0
- package/lib/utils/branch-naming.js.map +1 -0
- package/lib/utils/colors.d.ts +14 -0
- package/lib/utils/colors.d.ts.map +1 -0
- package/lib/utils/colors.js +14 -0
- package/lib/utils/colors.js.map +1 -0
- package/lib/utils/commit-helpers.d.ts +53 -0
- package/lib/utils/commit-helpers.d.ts.map +1 -0
- package/lib/utils/commit-helpers.js +177 -0
- package/lib/utils/commit-helpers.js.map +1 -0
- package/lib/utils/config.d.ts +87 -0
- package/lib/utils/config.d.ts.map +1 -0
- package/lib/utils/config.js +326 -0
- package/lib/utils/config.js.map +1 -0
- package/lib/utils/display.d.ts +27 -0
- package/lib/utils/display.d.ts.map +1 -0
- package/lib/utils/display.js +116 -0
- package/lib/utils/display.js.map +1 -0
- package/lib/utils/error-helpers.d.ts +27 -0
- package/lib/utils/error-helpers.d.ts.map +1 -0
- package/lib/utils/error-helpers.js +102 -0
- package/lib/utils/error-helpers.js.map +1 -0
- package/lib/utils/exec.d.ts +18 -0
- package/lib/utils/exec.d.ts.map +1 -0
- package/lib/utils/exec.js +96 -0
- package/lib/utils/exec.js.map +1 -0
- package/lib/utils/git-ai-errors.d.ts +10 -0
- package/lib/utils/git-ai-errors.d.ts.map +1 -0
- package/lib/utils/git-ai-errors.js +71 -0
- package/lib/utils/git-ai-errors.js.map +1 -0
- package/lib/utils/git-ai.d.ts +26 -0
- package/lib/utils/git-ai.d.ts.map +1 -0
- package/lib/utils/git-ai.js +603 -0
- package/lib/utils/git-ai.js.map +1 -0
- package/lib/utils/git-commands.d.ts +21 -0
- package/lib/utils/git-commands.d.ts.map +1 -0
- package/lib/utils/git-commands.js +58 -0
- package/lib/utils/git-commands.js.map +1 -0
- package/lib/utils/git-errors.d.ts +76 -0
- package/lib/utils/git-errors.d.ts.map +1 -0
- package/lib/utils/git-errors.js +565 -0
- package/lib/utils/git-errors.js.map +1 -0
- package/lib/utils/git.d.ts +61 -0
- package/lib/utils/git.d.ts.map +1 -0
- package/lib/utils/git.js +245 -0
- package/lib/utils/git.js.map +1 -0
- package/lib/utils/logging.d.ts +25 -0
- package/lib/utils/logging.d.ts.map +1 -0
- package/lib/utils/logging.js +71 -0
- package/lib/utils/logging.js.map +1 -0
- package/lib/utils/menu-builders.d.ts +47 -0
- package/lib/utils/menu-builders.d.ts.map +1 -0
- package/lib/utils/menu-builders.js +34 -0
- package/lib/utils/menu-builders.js.map +1 -0
- package/lib/utils/platform.d.ts +13 -0
- package/lib/utils/platform.d.ts.map +1 -0
- package/lib/utils/platform.js +32 -0
- package/lib/utils/platform.js.map +1 -0
- package/lib/utils/spinner-wrapper.d.ts +16 -0
- package/lib/utils/spinner-wrapper.d.ts.map +1 -0
- package/lib/utils/spinner-wrapper.js +56 -0
- package/lib/utils/spinner-wrapper.js.map +1 -0
- package/lib/utils/state.d.ts +22 -0
- package/lib/utils/state.d.ts.map +1 -0
- package/lib/utils/state.js +75 -0
- package/lib/utils/state.js.map +1 -0
- package/lib/utils/time.d.ts +4 -0
- package/lib/utils/time.d.ts.map +1 -0
- package/lib/utils/time.js +29 -0
- package/lib/utils/time.js.map +1 -0
- package/lib/utils/type-guards.d.ts +10 -0
- package/lib/utils/type-guards.d.ts.map +1 -0
- package/lib/utils/type-guards.js +29 -0
- package/lib/utils/type-guards.js.map +1 -0
- package/lib/workflows/ai-provider.d.ts +13 -0
- package/lib/workflows/ai-provider.d.ts.map +1 -0
- package/lib/workflows/ai-provider.js +55 -0
- package/lib/workflows/ai-provider.js.map +1 -0
- package/lib/workflows/author.d.ts +4 -0
- package/lib/workflows/author.d.ts.map +1 -0
- package/lib/workflows/author.js +80 -0
- package/lib/workflows/author.js.map +1 -0
- package/lib/workflows/branch-helpers.d.ts +9 -0
- package/lib/workflows/branch-helpers.d.ts.map +1 -0
- package/lib/workflows/branch-helpers.js +406 -0
- package/lib/workflows/branch-helpers.js.map +1 -0
- package/lib/workflows/branch-utils.d.ts +9 -0
- package/lib/workflows/branch-utils.d.ts.map +1 -0
- package/lib/workflows/branch-utils.js +61 -0
- package/lib/workflows/branch-utils.js.map +1 -0
- package/lib/workflows/branch.d.ts +12 -0
- package/lib/workflows/branch.d.ts.map +1 -0
- package/lib/workflows/branch.js +555 -0
- package/lib/workflows/branch.js.map +1 -0
- package/lib/workflows/cleanup.d.ts +10 -0
- package/lib/workflows/cleanup.d.ts.map +1 -0
- package/lib/workflows/cleanup.js +287 -0
- package/lib/workflows/cleanup.js.map +1 -0
- package/lib/workflows/commit.d.ts +10 -0
- package/lib/workflows/commit.d.ts.map +1 -0
- package/lib/workflows/commit.js +771 -0
- package/lib/workflows/commit.js.map +1 -0
- package/lib/workflows/main-steps.d.ts +12 -0
- package/lib/workflows/main-steps.d.ts.map +1 -0
- package/lib/workflows/main-steps.js +407 -0
- package/lib/workflows/main-steps.js.map +1 -0
- package/lib/workflows/main.d.ts +8 -0
- package/lib/workflows/main.d.ts.map +1 -0
- package/lib/workflows/main.js +720 -0
- package/lib/workflows/main.js.map +1 -0
- package/lib/workflows/security-gate.d.ts +8 -0
- package/lib/workflows/security-gate.d.ts.map +1 -0
- package/lib/workflows/security-gate.js +447 -0
- package/lib/workflows/security-gate.js.map +1 -0
- package/lib/workflows/settings.d.ts +15 -0
- package/lib/workflows/settings.d.ts.map +1 -0
- package/lib/workflows/settings.js +438 -0
- package/lib/workflows/settings.js.map +1 -0
- package/lib/workflows/trello-menu.d.ts +16 -0
- package/lib/workflows/trello-menu.d.ts.map +1 -0
- package/lib/workflows/trello-menu.js +361 -0
- package/lib/workflows/trello-menu.js.map +1 -0
- package/package.json +112 -0
|
@@ -0,0 +1,720 @@
|
|
|
1
|
+
/** Main workflow for the Geeto CLI. */
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { handleAIProviderSelection } from './ai-provider.js';
|
|
4
|
+
import { handleBranchCreationWorkflow } from './branch.js';
|
|
5
|
+
import { handleCommitWorkflow } from './commit.js';
|
|
6
|
+
import { handleCleanup, handleMerge, handlePush } from './main-steps.js';
|
|
7
|
+
import { showSettingsMenu } from './settings.js';
|
|
8
|
+
import { closeInput, confirm } from '../cli/input.js';
|
|
9
|
+
import { select } from '../cli/menu.js';
|
|
10
|
+
import { STEP, TASK_PLATFORMS } from '../core/constants.js';
|
|
11
|
+
import { colors } from '../utils/colors.js';
|
|
12
|
+
import { DEFAULT_GEMINI_MODEL, hasSkippedTrelloPrompt, hasTrelloConfig, setSkipTrelloPrompt, } from '../utils/config.js';
|
|
13
|
+
import { displayCompletionSummary, displayCurrentProviderStatus, getStepName, } from '../utils/display.js';
|
|
14
|
+
import { exec } from '../utils/exec.js';
|
|
15
|
+
import { getChangedFiles, getCurrentBranch, getStagedFiles } from '../utils/git.js';
|
|
16
|
+
import { log } from '../utils/logging.js';
|
|
17
|
+
import { loadState, preserveProviderState, saveState } from '../utils/state.js';
|
|
18
|
+
import { formatTimestampLocale } from '../utils/time.js';
|
|
19
|
+
export const main = async (opts) => {
|
|
20
|
+
try {
|
|
21
|
+
log.banner();
|
|
22
|
+
// Try to load saved state to show current provider status
|
|
23
|
+
const spinner = log.spinner();
|
|
24
|
+
spinner.start('Initializing...');
|
|
25
|
+
const initialSavedState = loadState();
|
|
26
|
+
spinner.stop();
|
|
27
|
+
if (initialSavedState?.aiProvider) {
|
|
28
|
+
// If there's a saved checkpoint, avoid duplicating the configured model
|
|
29
|
+
// in the 'Current AI Setup' box since the resume flow will show it.
|
|
30
|
+
displayCurrentProviderStatus();
|
|
31
|
+
}
|
|
32
|
+
const suppressLogs = !!opts?.startAt;
|
|
33
|
+
if (opts?.startAt) {
|
|
34
|
+
log.info(`Shortcut: starting at step -> ${opts.startAt}`);
|
|
35
|
+
}
|
|
36
|
+
// Settings menu
|
|
37
|
+
const initialChoice = opts?.startAt
|
|
38
|
+
? 'start'
|
|
39
|
+
: await select('Welcome to Geeto! What would you like to do?', [
|
|
40
|
+
{ label: 'Start new workflow', value: 'start' },
|
|
41
|
+
{ label: 'Trello tasks', value: 'trello' },
|
|
42
|
+
{ label: 'Security & Quality Gate', value: 'security' },
|
|
43
|
+
{ label: 'Settings', value: 'settings' },
|
|
44
|
+
{ label: 'Exit', value: 'exit' },
|
|
45
|
+
]);
|
|
46
|
+
if (!opts?.startAt && initialChoice === 'exit') {
|
|
47
|
+
log.info('Goodbye!');
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (!opts?.startAt && initialChoice === 'security') {
|
|
51
|
+
const { showSecurityGateMenu } = await import('./security-gate.js');
|
|
52
|
+
await showSecurityGateMenu();
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (!opts?.startAt && initialChoice === 'settings') {
|
|
56
|
+
await showSettingsMenu();
|
|
57
|
+
// After settings, go back to main menu
|
|
58
|
+
await main();
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (!opts?.startAt && initialChoice === 'trello') {
|
|
62
|
+
const { showTrelloMenu } = await import('./trello-menu.js');
|
|
63
|
+
await showTrelloMenu();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
// Check git repo first
|
|
67
|
+
try {
|
|
68
|
+
exec('git rev-parse --is-inside-work-tree', true);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
log.error('Not a git repository!');
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
// Load saved state early
|
|
75
|
+
const savedState = loadState();
|
|
76
|
+
let aiProvider;
|
|
77
|
+
let copilotModel;
|
|
78
|
+
let openrouterModel;
|
|
79
|
+
let geminiModel;
|
|
80
|
+
let shouldResume = false;
|
|
81
|
+
let suppressStagingDoneMessage = false;
|
|
82
|
+
if (savedState) {
|
|
83
|
+
// Only show saved checkpoint details when not running via shortcut flags
|
|
84
|
+
if (!suppressLogs) {
|
|
85
|
+
// Format saved checkpoint timestamp using system locale when available.
|
|
86
|
+
const formattedTimestamp = formatTimestampLocale(savedState.timestamp);
|
|
87
|
+
log.warn(`Found saved checkpoint from: ${formattedTimestamp}`);
|
|
88
|
+
log.info(`Last step: ${getStepName(savedState.step)}`);
|
|
89
|
+
}
|
|
90
|
+
// Use the real git branch at startup so manual `git checkout` is reflected
|
|
91
|
+
const actualCurrentBranch = getCurrentBranch();
|
|
92
|
+
const displayedWorking = actualCurrentBranch || savedState.workingBranch || savedState.currentBranch || 'unknown';
|
|
93
|
+
log.info(`Working branch: ${colors.cyan}${displayedWorking}${colors.reset}`);
|
|
94
|
+
let resumeChoice;
|
|
95
|
+
if (opts?.fresh) {
|
|
96
|
+
resumeChoice = 'fresh';
|
|
97
|
+
}
|
|
98
|
+
else if (opts?.resume === true || opts?.startAt) {
|
|
99
|
+
// Auto-resume without prompt
|
|
100
|
+
resumeChoice = 'resume';
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
// Avoid accidental immediate acceptance from previous Enter key press
|
|
104
|
+
await new Promise((resolve) => setTimeout(resolve, 80));
|
|
105
|
+
// If the saved checkpoint is already at or past cleanup, it's effectively finished;
|
|
106
|
+
// don't prompt the user — just start fresh immediately (no resume possible).
|
|
107
|
+
const isFinished = savedState.step >= STEP.CLEANUP;
|
|
108
|
+
if (isFinished) {
|
|
109
|
+
// Quietly proceed as 'fresh' to avoid showing a redundant prompt for a completed checkpoint
|
|
110
|
+
log.info('Checkpoint already complete — starting fresh...');
|
|
111
|
+
resumeChoice = 'fresh';
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
// Ask user whether to resume, start fresh, or cancel
|
|
115
|
+
const choices = [
|
|
116
|
+
{ label: 'Resume from checkpoint', value: 'resume' },
|
|
117
|
+
{ label: 'Start fresh (discard checkpoint)', value: 'fresh' },
|
|
118
|
+
{ label: 'Cancel', value: 'cancel' },
|
|
119
|
+
];
|
|
120
|
+
resumeChoice = await select('What would you like to do?', choices);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// If startAt provided, ensure the saved checkpoint step is compatible
|
|
124
|
+
switch (opts?.startAt) {
|
|
125
|
+
case 'commit': {
|
|
126
|
+
savedState.step = Math.max(savedState.step, STEP.BRANCH_CREATED);
|
|
127
|
+
if (!suppressLogs) {
|
|
128
|
+
log.info('Auto-start: commit');
|
|
129
|
+
}
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
case 'merge': {
|
|
133
|
+
savedState.step = Math.max(savedState.step, STEP.PUSHED);
|
|
134
|
+
if (!suppressLogs) {
|
|
135
|
+
log.info('Auto-start: merge');
|
|
136
|
+
}
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
case 'branch': {
|
|
140
|
+
savedState.step = Math.max(savedState.step, STEP.STAGED);
|
|
141
|
+
if (!suppressLogs) {
|
|
142
|
+
log.info('Auto-start: branch');
|
|
143
|
+
}
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
case 'stage': {
|
|
147
|
+
savedState.step = Math.max(savedState.step, STEP.INIT);
|
|
148
|
+
if (!suppressLogs) {
|
|
149
|
+
log.info('Auto-start: stage');
|
|
150
|
+
}
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
case 'push': {
|
|
154
|
+
savedState.step = Math.max(savedState.step, STEP.COMMITTED);
|
|
155
|
+
if (!suppressLogs) {
|
|
156
|
+
log.info('Auto-start: push');
|
|
157
|
+
}
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (resumeChoice === 'resume') {
|
|
162
|
+
shouldResume = true;
|
|
163
|
+
// When resuming from a checkpoint that already completed staging,
|
|
164
|
+
// avoid duplicating the staged-files summary message later.
|
|
165
|
+
suppressStagingDoneMessage = savedState.step >= STEP.STAGED;
|
|
166
|
+
// Use saved AI provider and model, or prompt if not set
|
|
167
|
+
if (savedState.aiProvider) {
|
|
168
|
+
aiProvider = savedState.aiProvider;
|
|
169
|
+
copilotModel = savedState.copilotModel;
|
|
170
|
+
openrouterModel = savedState.openrouterModel;
|
|
171
|
+
geminiModel = savedState.geminiModel;
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
// No provider saved, prompt user to select
|
|
175
|
+
const aiSelection = await handleAIProviderSelection();
|
|
176
|
+
aiProvider = aiSelection.aiProvider;
|
|
177
|
+
copilotModel = aiSelection.copilotModel;
|
|
178
|
+
openrouterModel = aiSelection.openrouterModel;
|
|
179
|
+
geminiModel = aiSelection.geminiModel;
|
|
180
|
+
}
|
|
181
|
+
// Show a compact resume status box separate from the full "Current AI Setup"
|
|
182
|
+
const gitUtils = await import('../utils/git-ai.js');
|
|
183
|
+
const providerShort = gitUtils.getAIProviderShortName(aiProvider);
|
|
184
|
+
let modelToShow;
|
|
185
|
+
if (aiProvider === 'copilot') {
|
|
186
|
+
modelToShow = copilotModel;
|
|
187
|
+
}
|
|
188
|
+
else if (aiProvider === 'openrouter') {
|
|
189
|
+
modelToShow = openrouterModel;
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
modelToShow = geminiModel ?? DEFAULT_GEMINI_MODEL;
|
|
193
|
+
}
|
|
194
|
+
if (!opts?.startAt) {
|
|
195
|
+
console.log(`${colors.cyan}┌─ Checkpoint Summary ────────────────────────────────────┐${colors.reset}`);
|
|
196
|
+
if (aiProvider === 'manual') {
|
|
197
|
+
console.log(`${colors.cyan}│${colors.reset} Provider: ${colors.cyan}Manual (manual mode)${colors.reset}`);
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
console.log(`${colors.cyan}│${colors.reset} Provider: ${colors.cyan}${providerShort}${colors.reset}`);
|
|
201
|
+
if (modelToShow) {
|
|
202
|
+
console.log(`${colors.cyan}│${colors.reset} Model: ${colors.cyan}${modelToShow}${colors.reset}`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
console.log(`${colors.cyan}│${colors.reset} Resuming from: ${colors.cyan}${getStepName(savedState.step)}${colors.reset}`);
|
|
206
|
+
console.log(`${colors.cyan}└─────────────────────────────────────────────────────────┘${colors.reset}`);
|
|
207
|
+
}
|
|
208
|
+
// Verify AI provider setup is still valid (skip for manual mode)
|
|
209
|
+
if (!opts?.startAt && aiProvider !== 'manual') {
|
|
210
|
+
const { ensureAIProvider } = await import('../core/setup.js');
|
|
211
|
+
const aiReady = await ensureAIProvider(aiProvider);
|
|
212
|
+
if (!aiReady) {
|
|
213
|
+
log.warn(`${aiProvider === 'gemini' ? 'Gemini' : 'GitHub Copilot'} setup is no longer valid.`);
|
|
214
|
+
const fixSetup = confirm(`Fix ${aiProvider === 'gemini' ? 'Gemini' : 'GitHub Copilot'} setup now?`);
|
|
215
|
+
if (fixSetup) {
|
|
216
|
+
const setupSuccess = await ensureAIProvider(aiProvider);
|
|
217
|
+
if (!setupSuccess) {
|
|
218
|
+
log.warn(`Could not fix ${aiProvider === 'gemini' ? 'Gemini' : 'GitHub Copilot'} setup. Switching to manual mode.`);
|
|
219
|
+
aiProvider = 'gemini'; // Keep gemini but will use manual fallback
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
log.warn(`${aiProvider === 'gemini' ? 'Gemini' : 'GitHub Copilot'} setup invalid. Will use manual mode for AI features.`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
else if (resumeChoice === 'fresh') {
|
|
229
|
+
preserveProviderState(savedState);
|
|
230
|
+
log.info('Starting fresh...');
|
|
231
|
+
// Keep the currently configured provider and model from saved checkpoint, or prompt if not set
|
|
232
|
+
if (savedState.aiProvider) {
|
|
233
|
+
aiProvider = savedState.aiProvider;
|
|
234
|
+
copilotModel = savedState.copilotModel;
|
|
235
|
+
openrouterModel = savedState.openrouterModel;
|
|
236
|
+
geminiModel = savedState.geminiModel;
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
// No provider saved, prompt user to select
|
|
240
|
+
const aiSelection = await handleAIProviderSelection();
|
|
241
|
+
aiProvider = aiSelection.aiProvider;
|
|
242
|
+
copilotModel = aiSelection.copilotModel;
|
|
243
|
+
openrouterModel = aiSelection.openrouterModel;
|
|
244
|
+
geminiModel = aiSelection.geminiModel;
|
|
245
|
+
}
|
|
246
|
+
// Display current provider status (Git info only)
|
|
247
|
+
displayCurrentProviderStatus();
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
process.exit(0);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
// No saved state, always prompt user to choose AI provider and model
|
|
255
|
+
const aiSelection = await handleAIProviderSelection();
|
|
256
|
+
aiProvider = aiSelection.aiProvider;
|
|
257
|
+
copilotModel = aiSelection.copilotModel;
|
|
258
|
+
openrouterModel = aiSelection.openrouterModel;
|
|
259
|
+
geminiModel = aiSelection.geminiModel;
|
|
260
|
+
// Display current provider status (Git info only)
|
|
261
|
+
displayCurrentProviderStatus();
|
|
262
|
+
}
|
|
263
|
+
// Determine actual current branch and staged files at startup
|
|
264
|
+
const actualBranch = getCurrentBranch();
|
|
265
|
+
let state = {
|
|
266
|
+
step: STEP.INIT,
|
|
267
|
+
workingBranch: '',
|
|
268
|
+
targetBranch: '',
|
|
269
|
+
currentBranch: actualBranch,
|
|
270
|
+
timestamp: new Date().toISOString(),
|
|
271
|
+
aiProvider,
|
|
272
|
+
copilotModel,
|
|
273
|
+
openrouterModel,
|
|
274
|
+
geminiModel,
|
|
275
|
+
};
|
|
276
|
+
if (shouldResume && savedState) {
|
|
277
|
+
// If actual branch equals saved working branch, resume from the saved step.
|
|
278
|
+
const savedBranch = savedState.currentBranch ?? '';
|
|
279
|
+
if (savedBranch && savedBranch !== actualBranch) {
|
|
280
|
+
log.warn(`Branch changed from '${savedBranch}' to '${actualBranch}', resetting workflow state`);
|
|
281
|
+
state = {
|
|
282
|
+
...savedState,
|
|
283
|
+
step: STEP.INIT,
|
|
284
|
+
workingBranch: actualBranch,
|
|
285
|
+
currentBranch: actualBranch,
|
|
286
|
+
// Preserve provider selection from new selection or savedState
|
|
287
|
+
aiProvider,
|
|
288
|
+
copilotModel,
|
|
289
|
+
openrouterModel,
|
|
290
|
+
geminiModel,
|
|
291
|
+
};
|
|
292
|
+
saveState(state);
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
// Resume but refresh current branch and staged files from git
|
|
296
|
+
state = {
|
|
297
|
+
...savedState,
|
|
298
|
+
currentBranch: actualBranch,
|
|
299
|
+
// Preserve provider selection from new selection or savedState
|
|
300
|
+
aiProvider,
|
|
301
|
+
copilotModel,
|
|
302
|
+
openrouterModel,
|
|
303
|
+
geminiModel,
|
|
304
|
+
};
|
|
305
|
+
// Save state if provider info was just selected
|
|
306
|
+
if (!savedState.aiProvider && aiProvider) {
|
|
307
|
+
saveState(state);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
// Save state immediately to persist provider selection for fresh starts and new state
|
|
312
|
+
if (!shouldResume) {
|
|
313
|
+
saveState(state);
|
|
314
|
+
}
|
|
315
|
+
// Use currentBranch as fallback if workingBranch is empty
|
|
316
|
+
let workingBranch = state.workingBranch ?? state.currentBranch ?? actualBranch;
|
|
317
|
+
let featureBranch = '';
|
|
318
|
+
// If startAt override was provided, adjust the initial step so the workflow
|
|
319
|
+
// begins at the requested stage (commit/merge/branch).
|
|
320
|
+
switch (opts?.startAt) {
|
|
321
|
+
case 'commit': {
|
|
322
|
+
// Start at branch-created so commit flow will run
|
|
323
|
+
state.step = STEP.BRANCH_CREATED;
|
|
324
|
+
if (!suppressLogs) {
|
|
325
|
+
log.info('Starting at commit step (skipping earlier steps)');
|
|
326
|
+
}
|
|
327
|
+
break;
|
|
328
|
+
}
|
|
329
|
+
case 'merge': {
|
|
330
|
+
// Mark as pushed so merge flow will run while push is skipped
|
|
331
|
+
state.step = STEP.PUSHED;
|
|
332
|
+
if (!suppressLogs) {
|
|
333
|
+
log.info('Starting at merge step (skipping earlier steps)');
|
|
334
|
+
}
|
|
335
|
+
break;
|
|
336
|
+
}
|
|
337
|
+
case 'branch': {
|
|
338
|
+
// Mark as staged so branch flow will run while staging is skipped
|
|
339
|
+
state.step = STEP.STAGED;
|
|
340
|
+
if (!suppressLogs) {
|
|
341
|
+
log.info('Starting at branch step (skipping earlier steps)');
|
|
342
|
+
}
|
|
343
|
+
break;
|
|
344
|
+
}
|
|
345
|
+
case 'stage': {
|
|
346
|
+
// Start from init so staging flow will run
|
|
347
|
+
state.step = STEP.INIT;
|
|
348
|
+
if (!suppressLogs) {
|
|
349
|
+
log.info('Starting at stage step (skipping earlier steps)');
|
|
350
|
+
}
|
|
351
|
+
break;
|
|
352
|
+
}
|
|
353
|
+
case 'push': {
|
|
354
|
+
// Mark as committed so push flow will run while earlier steps are skipped
|
|
355
|
+
state.step = STEP.COMMITTED;
|
|
356
|
+
if (!suppressLogs) {
|
|
357
|
+
log.info('Starting at push step (skipping earlier steps)');
|
|
358
|
+
}
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
// Validate working branch (skip if in detached HEAD)
|
|
363
|
+
if (!workingBranch || workingBranch.trim() === '') {
|
|
364
|
+
try {
|
|
365
|
+
// Try to get current branch again
|
|
366
|
+
const retryBranch = getCurrentBranch();
|
|
367
|
+
if (retryBranch && retryBranch.trim() !== '') {
|
|
368
|
+
workingBranch = retryBranch;
|
|
369
|
+
state.currentBranch = retryBranch;
|
|
370
|
+
}
|
|
371
|
+
else {
|
|
372
|
+
log.warn('Unable to determine current branch (possibly detached HEAD). Using fallback.');
|
|
373
|
+
workingBranch = 'detached-head';
|
|
374
|
+
state.currentBranch = workingBranch;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
catch {
|
|
378
|
+
log.warn('Unable to determine current branch. Using fallback.');
|
|
379
|
+
workingBranch = 'unknown-branch';
|
|
380
|
+
state.currentBranch = workingBranch;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
// Ask about task management platform integration
|
|
384
|
+
let selectedPlatform = 'none';
|
|
385
|
+
// Auto-detect configured platforms
|
|
386
|
+
if (hasTrelloConfig()) {
|
|
387
|
+
selectedPlatform = 'trello';
|
|
388
|
+
if (!opts?.startAt) {
|
|
389
|
+
log.success('Trello platform configured');
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
else {
|
|
393
|
+
// If user previously skipped Trello setup, don't prompt again here.
|
|
394
|
+
if (hasSkippedTrelloPrompt()) {
|
|
395
|
+
log.info('Trello integration not configured (previously skipped).');
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
const wantTaskIntegration = confirm('Integrate with task management platform?');
|
|
399
|
+
if (wantTaskIntegration) {
|
|
400
|
+
// Show available platforms
|
|
401
|
+
const enabledPlatforms = TASK_PLATFORMS.filter((p) => p.enabled);
|
|
402
|
+
if (enabledPlatforms.length > 0) {
|
|
403
|
+
// Show selection menu
|
|
404
|
+
const platformOptions = [
|
|
405
|
+
...enabledPlatforms.map((p) => ({ label: p.name, value: p.value })),
|
|
406
|
+
{ label: 'Skip integration', value: 'none' },
|
|
407
|
+
];
|
|
408
|
+
selectedPlatform = (await select('Select task management platform:', platformOptions));
|
|
409
|
+
}
|
|
410
|
+
// Setup selected platform if needed
|
|
411
|
+
if (selectedPlatform === 'trello' && !hasTrelloConfig()) {
|
|
412
|
+
const trelloSpinner = log.spinner();
|
|
413
|
+
trelloSpinner.start('Setting up Trello...');
|
|
414
|
+
const { setupTrelloConfigInteractive } = await import('../core/trello-setup.js');
|
|
415
|
+
const trelloSetupSuccess = setupTrelloConfigInteractive();
|
|
416
|
+
trelloSpinner.stop();
|
|
417
|
+
if (trelloSetupSuccess) {
|
|
418
|
+
log.success('Trello integration configured!');
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
421
|
+
log.warn('Trello setup failed or cancelled.');
|
|
422
|
+
}
|
|
423
|
+
console.log('');
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
else {
|
|
427
|
+
// User declined task integration, save skip flag
|
|
428
|
+
setSkipTrelloPrompt();
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
// STEP 1: Stage changes
|
|
433
|
+
if (state.step < STEP.STAGED) {
|
|
434
|
+
const changedFiles = getChangedFiles();
|
|
435
|
+
log.info(`Current branch: ${state.currentBranch}`);
|
|
436
|
+
log.info(`Changed files: ${changedFiles.length}`);
|
|
437
|
+
if (changedFiles.length === 0) {
|
|
438
|
+
// No file changes detected — if CLI requested auto-stage, just continue;
|
|
439
|
+
// otherwise prompt the user to continue or cancel.
|
|
440
|
+
if (!opts?.stageAll) {
|
|
441
|
+
const noChangesChoice = await select('No changes detected. How would you like to proceed?', [
|
|
442
|
+
{ label: 'Continue without staging', value: 'without' },
|
|
443
|
+
{ label: 'Cancel', value: 'cancel' },
|
|
444
|
+
]);
|
|
445
|
+
if (noChangesChoice === 'cancel') {
|
|
446
|
+
log.warn('Cancelled.');
|
|
447
|
+
process.exit(0);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
452
|
+
console.log('\nChanged files:');
|
|
453
|
+
for (const file of changedFiles) {
|
|
454
|
+
console.log(` ${file}`);
|
|
455
|
+
}
|
|
456
|
+
if (!suppressLogs) {
|
|
457
|
+
log.step('Step 1: Stage Changes');
|
|
458
|
+
}
|
|
459
|
+
let stageChoice;
|
|
460
|
+
if (opts?.stageAll) {
|
|
461
|
+
stageChoice = 'all';
|
|
462
|
+
if (!suppressLogs) {
|
|
463
|
+
log.info('Auto-staging all changes (from CLI flag)');
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
else {
|
|
467
|
+
stageChoice = (await select('What to stage?', [
|
|
468
|
+
{ label: 'Stage all changes', value: 'all' },
|
|
469
|
+
{ label: 'Already staged', value: 'skip' },
|
|
470
|
+
{ label: 'Continue without staging', value: 'without' },
|
|
471
|
+
{ label: 'Cancel', value: 'cancel' },
|
|
472
|
+
]));
|
|
473
|
+
}
|
|
474
|
+
switch (stageChoice) {
|
|
475
|
+
case 'all': {
|
|
476
|
+
exec('git add -A');
|
|
477
|
+
log.success('All changes staged');
|
|
478
|
+
break;
|
|
479
|
+
}
|
|
480
|
+
case 'without': {
|
|
481
|
+
log.info('Continuing without staging');
|
|
482
|
+
break;
|
|
483
|
+
}
|
|
484
|
+
case 'cancel': {
|
|
485
|
+
log.warn('Cancelled.');
|
|
486
|
+
process.exit(0);
|
|
487
|
+
}
|
|
488
|
+
// 'skip' intentionally falls through to stagedFiles check below
|
|
489
|
+
}
|
|
490
|
+
const stagedFiles = getStagedFiles();
|
|
491
|
+
// If the user explicitly chose to continue without staging, don't treat
|
|
492
|
+
// an empty staged set as a fatal error here — allow the workflow to
|
|
493
|
+
// continue and re-check staging status right before committing.
|
|
494
|
+
if (stagedFiles.length === 0 && stageChoice !== 'without') {
|
|
495
|
+
log.error('No staged files!');
|
|
496
|
+
process.exit(0);
|
|
497
|
+
}
|
|
498
|
+
// Only mark staging as completed when there are actually staged files.
|
|
499
|
+
if (stagedFiles.length > 0) {
|
|
500
|
+
state.step = STEP.STAGED;
|
|
501
|
+
saveState(state);
|
|
502
|
+
console.log('\nStaged files:');
|
|
503
|
+
for (const file of stagedFiles) {
|
|
504
|
+
console.log(` + ${file}`);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
else {
|
|
510
|
+
const stagedFiles = getStagedFiles();
|
|
511
|
+
if (suppressStagingDoneMessage) {
|
|
512
|
+
if (!suppressLogs) {
|
|
513
|
+
log.info('Staging previously completed');
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
else {
|
|
517
|
+
log.success(`Staging already done (${stagedFiles.length} files)`);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
// STEP 2: Create branch
|
|
521
|
+
if (state.step < STEP.BRANCH_CREATED) {
|
|
522
|
+
const suppressConfirm = !!opts?.startAt && opts.startAt !== 'stage';
|
|
523
|
+
// When running with CLI flags (except --stage), show a short staged-files preview before creating the branch
|
|
524
|
+
if (opts?.startAt && opts.startAt !== 'stage') {
|
|
525
|
+
const stagedPreview = getStagedFiles();
|
|
526
|
+
if (stagedPreview.length > 0) {
|
|
527
|
+
const moreCount = Math.max(0, stagedPreview.length - 2);
|
|
528
|
+
const more = moreCount > 0 ? ` (+${moreCount} more)` : '';
|
|
529
|
+
const shownCount = stagedPreview.length;
|
|
530
|
+
log.info(`${colors.cyan}${colors.reset}Staged: ${colors.cyan}${shownCount} files${colors.reset}`);
|
|
531
|
+
log.info(`${colors.cyan}${colors.reset}Files: ${colors.cyan}${stagedPreview
|
|
532
|
+
.slice(0, 2)
|
|
533
|
+
.map((f) => path.basename(f))
|
|
534
|
+
.join(', ')}${more}${colors.reset}`);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
const { branchName, created } = await handleBranchCreationWorkflow(state, {
|
|
538
|
+
suppressStep: !!opts?.startAt,
|
|
539
|
+
suppressConfirm,
|
|
540
|
+
});
|
|
541
|
+
// handleBranchCreationWorkflow returns { branchName, created }
|
|
542
|
+
state.workingBranch = branchName;
|
|
543
|
+
if (created) {
|
|
544
|
+
state.step = STEP.BRANCH_CREATED;
|
|
545
|
+
state.currentBranch = branchName;
|
|
546
|
+
}
|
|
547
|
+
saveState(state);
|
|
548
|
+
}
|
|
549
|
+
else {
|
|
550
|
+
workingBranch = state.workingBranch ?? state.currentBranch ?? actualBranch;
|
|
551
|
+
if (!state.workingBranch) {
|
|
552
|
+
state.workingBranch = workingBranch;
|
|
553
|
+
saveState(state);
|
|
554
|
+
}
|
|
555
|
+
if (!suppressLogs) {
|
|
556
|
+
log.success(`Branch already created: ${colors.cyan}${workingBranch}${colors.reset}`);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
// STEP 3: Commit
|
|
560
|
+
if (state.step < STEP.COMMITTED) {
|
|
561
|
+
// Refresh staged files from git in real-time. The staging step is
|
|
562
|
+
// optional and only helps the user; commits must always verify the
|
|
563
|
+
// current staged files from git so external changes are respected.
|
|
564
|
+
const liveStaged = getStagedFiles();
|
|
565
|
+
// When invoked via CLI flags (except --stage), show a short staged-files preview before committing
|
|
566
|
+
if (opts?.startAt && opts.startAt !== 'stage' && liveStaged.length > 0) {
|
|
567
|
+
const moreCount = Math.max(0, liveStaged.length - 2);
|
|
568
|
+
const more = moreCount > 0 ? ` (+${moreCount} more)` : '';
|
|
569
|
+
const shownCount = liveStaged.length;
|
|
570
|
+
log.info(`${colors.cyan}${colors.reset}Staged: ${colors.cyan}${shownCount} files${colors.reset}`);
|
|
571
|
+
log.info(`${colors.cyan}${colors.reset}Files: ${colors.cyan}${liveStaged
|
|
572
|
+
.slice(0, 2)
|
|
573
|
+
.map((f) => path.basename(f))
|
|
574
|
+
.join(', ')}${more}${colors.reset}`);
|
|
575
|
+
}
|
|
576
|
+
if (liveStaged.length === 0) {
|
|
577
|
+
console.log('');
|
|
578
|
+
log.warn('No staged files found at commit time. Aborting.');
|
|
579
|
+
process.exit(0);
|
|
580
|
+
}
|
|
581
|
+
if (!state.skippedCommit) {
|
|
582
|
+
await handleCommitWorkflow(state, {
|
|
583
|
+
suppressStep: !!opts?.startAt,
|
|
584
|
+
suppressConfirm: false,
|
|
585
|
+
});
|
|
586
|
+
state.step = STEP.COMMITTED;
|
|
587
|
+
saveState(state);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
else {
|
|
591
|
+
// If commit was explicitly skipped earlier, don't print "already done" messages
|
|
592
|
+
if (!state.skippedCommit && !suppressLogs) {
|
|
593
|
+
log.success('Commit already done');
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
// STEP 4: Push — ask user before pushing in interactive mode
|
|
597
|
+
if (opts?.startAt) {
|
|
598
|
+
// Non-interactive: only push automatically if starting at push
|
|
599
|
+
if (opts.startAt === 'push') {
|
|
600
|
+
await handlePush(state, { suppressStep: true, suppressLogs });
|
|
601
|
+
}
|
|
602
|
+
else if (opts.startAt === 'merge') {
|
|
603
|
+
// intentionally skip push prompt here; merge step will validate push status
|
|
604
|
+
}
|
|
605
|
+
else {
|
|
606
|
+
// Provide visible push progress by allowing git to print progress to terminal
|
|
607
|
+
console.log('');
|
|
608
|
+
// Get remote URL silently for a nicer message
|
|
609
|
+
let remoteUrl = '';
|
|
610
|
+
try {
|
|
611
|
+
remoteUrl = exec('git remote get-url origin', true);
|
|
612
|
+
}
|
|
613
|
+
catch {
|
|
614
|
+
/* ignore */
|
|
615
|
+
}
|
|
616
|
+
if (remoteUrl) {
|
|
617
|
+
log.info(`Pushing ${getCurrentBranch()} to: ${remoteUrl}`);
|
|
618
|
+
}
|
|
619
|
+
else {
|
|
620
|
+
log.info(`Pushing ${getCurrentBranch()} to remote`);
|
|
621
|
+
}
|
|
622
|
+
const currentBranch = state.workingBranch || getCurrentBranch();
|
|
623
|
+
console.log('');
|
|
624
|
+
const wantPush = confirm(`Push ${currentBranch} to origin now?`);
|
|
625
|
+
if (wantPush) {
|
|
626
|
+
await handlePush(state, { suppressStep: !!opts?.startAt, suppressLogs: true });
|
|
627
|
+
}
|
|
628
|
+
else {
|
|
629
|
+
log.info('Skipping push as per user request');
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
else {
|
|
634
|
+
// Interactive: ask before pushing
|
|
635
|
+
const currentBranch = state.workingBranch || getCurrentBranch();
|
|
636
|
+
console.log('');
|
|
637
|
+
// For safety, default NO to avoid accidental pushes on Enter
|
|
638
|
+
const wantPush = confirm(`Push ${currentBranch} to origin now?`);
|
|
639
|
+
if (wantPush) {
|
|
640
|
+
// We already confirmed, so suppress further confirm inside handlePush
|
|
641
|
+
await handlePush(state, { suppressStep: false, suppressLogs: true });
|
|
642
|
+
}
|
|
643
|
+
else {
|
|
644
|
+
log.info('Skipping push as per user request');
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
// STEP 5: Merge (simplified)
|
|
648
|
+
// If the current branch has commits that are not pushed, ask the user to push
|
|
649
|
+
const branchToCheck = state.workingBranch || getCurrentBranch();
|
|
650
|
+
try {
|
|
651
|
+
let hasCommitsToPush = false;
|
|
652
|
+
try {
|
|
653
|
+
const remoteRef = exec(`git ls-remote --heads origin "${branchToCheck}"`, true).trim();
|
|
654
|
+
if (remoteRef) {
|
|
655
|
+
const commitsAhead = exec(`git rev-list HEAD...origin/"${branchToCheck}" --count`, true).trim();
|
|
656
|
+
hasCommitsToPush = commitsAhead !== '0' && commitsAhead !== '';
|
|
657
|
+
}
|
|
658
|
+
else {
|
|
659
|
+
hasCommitsToPush = true;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
catch {
|
|
663
|
+
hasCommitsToPush = true;
|
|
664
|
+
}
|
|
665
|
+
if (hasCommitsToPush) {
|
|
666
|
+
// Default NO to avoid accidental push; if user declines, abort merge
|
|
667
|
+
console.log('');
|
|
668
|
+
log.info(`Branch ${branchToCheck} has commits not pushed to origin.`);
|
|
669
|
+
await handlePush(state, { suppressStep: false, suppressLogs: false, force: true });
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
catch {
|
|
673
|
+
// On any error checking remote status, be conservative and abort the merge
|
|
674
|
+
log.warn('Could not determine remote push status; aborting merge for safety.');
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
featureBranch = await handleMerge(state, { suppressStep: !!opts?.startAt, suppressLogs });
|
|
678
|
+
// Only proceed to cleanup if merge was successful
|
|
679
|
+
if (state.step !== STEP.MERGED) {
|
|
680
|
+
log.warn('Merge was not completed. Skipping cleanup.');
|
|
681
|
+
console.log('\n⚠️ Workflow incomplete. Please resolve any issues and retry.\n');
|
|
682
|
+
try {
|
|
683
|
+
closeInput();
|
|
684
|
+
}
|
|
685
|
+
catch {
|
|
686
|
+
/* ignore */
|
|
687
|
+
}
|
|
688
|
+
process.exit(1);
|
|
689
|
+
}
|
|
690
|
+
// STEP 6: Cleanup (simplified)
|
|
691
|
+
await handleCleanup(featureBranch, state);
|
|
692
|
+
// Reset state to initial but preserve AI provider settings
|
|
693
|
+
preserveProviderState(state);
|
|
694
|
+
// Display enhanced completion summary
|
|
695
|
+
const stagedFiles = getStagedFiles();
|
|
696
|
+
displayCompletionSummary({
|
|
697
|
+
stagedFiles: stagedFiles.length,
|
|
698
|
+
workingBranch: featureBranch || state.workingBranch,
|
|
699
|
+
targetBranch: state.targetBranch,
|
|
700
|
+
});
|
|
701
|
+
// Close any interactive input resources and exit to ensure the CLI terminates
|
|
702
|
+
try {
|
|
703
|
+
closeInput();
|
|
704
|
+
}
|
|
705
|
+
catch {
|
|
706
|
+
/* ignore */
|
|
707
|
+
}
|
|
708
|
+
process.exit(0);
|
|
709
|
+
}
|
|
710
|
+
catch (error) {
|
|
711
|
+
if (error instanceof Error) {
|
|
712
|
+
log.error(error.message);
|
|
713
|
+
}
|
|
714
|
+
else {
|
|
715
|
+
log.error('Unknown error occurred');
|
|
716
|
+
}
|
|
717
|
+
throw new Error('Application error');
|
|
718
|
+
}
|
|
719
|
+
};
|
|
720
|
+
//# sourceMappingURL=main.js.map
|