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.
Files changed (203) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +152 -0
  3. package/lib/api/copilot-adapter.d.ts +11 -0
  4. package/lib/api/copilot-adapter.d.ts.map +1 -0
  5. package/lib/api/copilot-adapter.js +41 -0
  6. package/lib/api/copilot-adapter.js.map +1 -0
  7. package/lib/api/copilot-sdk.d.ts +48 -0
  8. package/lib/api/copilot-sdk.d.ts.map +1 -0
  9. package/lib/api/copilot-sdk.js +451 -0
  10. package/lib/api/copilot-sdk.js.map +1 -0
  11. package/lib/api/copilot.d.ts +21 -0
  12. package/lib/api/copilot.d.ts.map +1 -0
  13. package/lib/api/copilot.js +87 -0
  14. package/lib/api/copilot.js.map +1 -0
  15. package/lib/api/gemini-sdk.d.ts +24 -0
  16. package/lib/api/gemini-sdk.d.ts.map +1 -0
  17. package/lib/api/gemini-sdk.js +245 -0
  18. package/lib/api/gemini-sdk.js.map +1 -0
  19. package/lib/api/gemini.d.ts +21 -0
  20. package/lib/api/gemini.d.ts.map +1 -0
  21. package/lib/api/gemini.js +87 -0
  22. package/lib/api/gemini.js.map +1 -0
  23. package/lib/api/openrouter-sdk.d.ts +58 -0
  24. package/lib/api/openrouter-sdk.d.ts.map +1 -0
  25. package/lib/api/openrouter-sdk.js +341 -0
  26. package/lib/api/openrouter-sdk.js.map +1 -0
  27. package/lib/api/openrouter.d.ts +17 -0
  28. package/lib/api/openrouter.d.ts.map +1 -0
  29. package/lib/api/openrouter.js +111 -0
  30. package/lib/api/openrouter.js.map +1 -0
  31. package/lib/api/trello.d.ts +17 -0
  32. package/lib/api/trello.d.ts.map +1 -0
  33. package/lib/api/trello.js +72 -0
  34. package/lib/api/trello.js.map +1 -0
  35. package/lib/cli/input.d.ts +39 -0
  36. package/lib/cli/input.d.ts.map +1 -0
  37. package/lib/cli/input.js +119 -0
  38. package/lib/cli/input.js.map +1 -0
  39. package/lib/cli/menu.d.ts +9 -0
  40. package/lib/cli/menu.d.ts.map +1 -0
  41. package/lib/cli/menu.js +201 -0
  42. package/lib/cli/menu.js.map +1 -0
  43. package/lib/core/constants.d.ts +22 -0
  44. package/lib/core/constants.d.ts.map +1 -0
  45. package/lib/core/constants.js +24 -0
  46. package/lib/core/constants.js.map +1 -0
  47. package/lib/core/copilot-setup.d.ts +13 -0
  48. package/lib/core/copilot-setup.d.ts.map +1 -0
  49. package/lib/core/copilot-setup.js +447 -0
  50. package/lib/core/copilot-setup.js.map +1 -0
  51. package/lib/core/gemini-setup.d.ts +8 -0
  52. package/lib/core/gemini-setup.d.ts.map +1 -0
  53. package/lib/core/gemini-setup.js +84 -0
  54. package/lib/core/gemini-setup.js.map +1 -0
  55. package/lib/core/menu-constants.d.ts +24 -0
  56. package/lib/core/menu-constants.d.ts.map +1 -0
  57. package/lib/core/menu-constants.js +22 -0
  58. package/lib/core/menu-constants.js.map +1 -0
  59. package/lib/core/openrouter-setup.d.ts +8 -0
  60. package/lib/core/openrouter-setup.d.ts.map +1 -0
  61. package/lib/core/openrouter-setup.js +73 -0
  62. package/lib/core/openrouter-setup.js.map +1 -0
  63. package/lib/core/setup.d.ts +21 -0
  64. package/lib/core/setup.d.ts.map +1 -0
  65. package/lib/core/setup.js +88 -0
  66. package/lib/core/setup.js.map +1 -0
  67. package/lib/core/trello-setup.d.ts +8 -0
  68. package/lib/core/trello-setup.d.ts.map +1 -0
  69. package/lib/core/trello-setup.js +107 -0
  70. package/lib/core/trello-setup.js.map +1 -0
  71. package/lib/index.d.ts +3 -0
  72. package/lib/index.d.ts.map +1 -0
  73. package/lib/index.js +250 -0
  74. package/lib/index.js.map +1 -0
  75. package/lib/types/index.d.ts +78 -0
  76. package/lib/types/index.d.ts.map +1 -0
  77. package/lib/types/index.js +2 -0
  78. package/lib/types/index.js.map +1 -0
  79. package/lib/utils/branch-naming.d.ts +11 -0
  80. package/lib/utils/branch-naming.d.ts.map +1 -0
  81. package/lib/utils/branch-naming.js +396 -0
  82. package/lib/utils/branch-naming.js.map +1 -0
  83. package/lib/utils/colors.d.ts +14 -0
  84. package/lib/utils/colors.d.ts.map +1 -0
  85. package/lib/utils/colors.js +14 -0
  86. package/lib/utils/colors.js.map +1 -0
  87. package/lib/utils/commit-helpers.d.ts +53 -0
  88. package/lib/utils/commit-helpers.d.ts.map +1 -0
  89. package/lib/utils/commit-helpers.js +177 -0
  90. package/lib/utils/commit-helpers.js.map +1 -0
  91. package/lib/utils/config.d.ts +87 -0
  92. package/lib/utils/config.d.ts.map +1 -0
  93. package/lib/utils/config.js +326 -0
  94. package/lib/utils/config.js.map +1 -0
  95. package/lib/utils/display.d.ts +27 -0
  96. package/lib/utils/display.d.ts.map +1 -0
  97. package/lib/utils/display.js +116 -0
  98. package/lib/utils/display.js.map +1 -0
  99. package/lib/utils/error-helpers.d.ts +27 -0
  100. package/lib/utils/error-helpers.d.ts.map +1 -0
  101. package/lib/utils/error-helpers.js +102 -0
  102. package/lib/utils/error-helpers.js.map +1 -0
  103. package/lib/utils/exec.d.ts +18 -0
  104. package/lib/utils/exec.d.ts.map +1 -0
  105. package/lib/utils/exec.js +96 -0
  106. package/lib/utils/exec.js.map +1 -0
  107. package/lib/utils/git-ai-errors.d.ts +10 -0
  108. package/lib/utils/git-ai-errors.d.ts.map +1 -0
  109. package/lib/utils/git-ai-errors.js +71 -0
  110. package/lib/utils/git-ai-errors.js.map +1 -0
  111. package/lib/utils/git-ai.d.ts +26 -0
  112. package/lib/utils/git-ai.d.ts.map +1 -0
  113. package/lib/utils/git-ai.js +603 -0
  114. package/lib/utils/git-ai.js.map +1 -0
  115. package/lib/utils/git-commands.d.ts +21 -0
  116. package/lib/utils/git-commands.d.ts.map +1 -0
  117. package/lib/utils/git-commands.js +58 -0
  118. package/lib/utils/git-commands.js.map +1 -0
  119. package/lib/utils/git-errors.d.ts +76 -0
  120. package/lib/utils/git-errors.d.ts.map +1 -0
  121. package/lib/utils/git-errors.js +565 -0
  122. package/lib/utils/git-errors.js.map +1 -0
  123. package/lib/utils/git.d.ts +61 -0
  124. package/lib/utils/git.d.ts.map +1 -0
  125. package/lib/utils/git.js +245 -0
  126. package/lib/utils/git.js.map +1 -0
  127. package/lib/utils/logging.d.ts +25 -0
  128. package/lib/utils/logging.d.ts.map +1 -0
  129. package/lib/utils/logging.js +71 -0
  130. package/lib/utils/logging.js.map +1 -0
  131. package/lib/utils/menu-builders.d.ts +47 -0
  132. package/lib/utils/menu-builders.d.ts.map +1 -0
  133. package/lib/utils/menu-builders.js +34 -0
  134. package/lib/utils/menu-builders.js.map +1 -0
  135. package/lib/utils/platform.d.ts +13 -0
  136. package/lib/utils/platform.d.ts.map +1 -0
  137. package/lib/utils/platform.js +32 -0
  138. package/lib/utils/platform.js.map +1 -0
  139. package/lib/utils/spinner-wrapper.d.ts +16 -0
  140. package/lib/utils/spinner-wrapper.d.ts.map +1 -0
  141. package/lib/utils/spinner-wrapper.js +56 -0
  142. package/lib/utils/spinner-wrapper.js.map +1 -0
  143. package/lib/utils/state.d.ts +22 -0
  144. package/lib/utils/state.d.ts.map +1 -0
  145. package/lib/utils/state.js +75 -0
  146. package/lib/utils/state.js.map +1 -0
  147. package/lib/utils/time.d.ts +4 -0
  148. package/lib/utils/time.d.ts.map +1 -0
  149. package/lib/utils/time.js +29 -0
  150. package/lib/utils/time.js.map +1 -0
  151. package/lib/utils/type-guards.d.ts +10 -0
  152. package/lib/utils/type-guards.d.ts.map +1 -0
  153. package/lib/utils/type-guards.js +29 -0
  154. package/lib/utils/type-guards.js.map +1 -0
  155. package/lib/workflows/ai-provider.d.ts +13 -0
  156. package/lib/workflows/ai-provider.d.ts.map +1 -0
  157. package/lib/workflows/ai-provider.js +55 -0
  158. package/lib/workflows/ai-provider.js.map +1 -0
  159. package/lib/workflows/author.d.ts +4 -0
  160. package/lib/workflows/author.d.ts.map +1 -0
  161. package/lib/workflows/author.js +80 -0
  162. package/lib/workflows/author.js.map +1 -0
  163. package/lib/workflows/branch-helpers.d.ts +9 -0
  164. package/lib/workflows/branch-helpers.d.ts.map +1 -0
  165. package/lib/workflows/branch-helpers.js +406 -0
  166. package/lib/workflows/branch-helpers.js.map +1 -0
  167. package/lib/workflows/branch-utils.d.ts +9 -0
  168. package/lib/workflows/branch-utils.d.ts.map +1 -0
  169. package/lib/workflows/branch-utils.js +61 -0
  170. package/lib/workflows/branch-utils.js.map +1 -0
  171. package/lib/workflows/branch.d.ts +12 -0
  172. package/lib/workflows/branch.d.ts.map +1 -0
  173. package/lib/workflows/branch.js +555 -0
  174. package/lib/workflows/branch.js.map +1 -0
  175. package/lib/workflows/cleanup.d.ts +10 -0
  176. package/lib/workflows/cleanup.d.ts.map +1 -0
  177. package/lib/workflows/cleanup.js +287 -0
  178. package/lib/workflows/cleanup.js.map +1 -0
  179. package/lib/workflows/commit.d.ts +10 -0
  180. package/lib/workflows/commit.d.ts.map +1 -0
  181. package/lib/workflows/commit.js +771 -0
  182. package/lib/workflows/commit.js.map +1 -0
  183. package/lib/workflows/main-steps.d.ts +12 -0
  184. package/lib/workflows/main-steps.d.ts.map +1 -0
  185. package/lib/workflows/main-steps.js +407 -0
  186. package/lib/workflows/main-steps.js.map +1 -0
  187. package/lib/workflows/main.d.ts +8 -0
  188. package/lib/workflows/main.d.ts.map +1 -0
  189. package/lib/workflows/main.js +720 -0
  190. package/lib/workflows/main.js.map +1 -0
  191. package/lib/workflows/security-gate.d.ts +8 -0
  192. package/lib/workflows/security-gate.d.ts.map +1 -0
  193. package/lib/workflows/security-gate.js +447 -0
  194. package/lib/workflows/security-gate.js.map +1 -0
  195. package/lib/workflows/settings.d.ts +15 -0
  196. package/lib/workflows/settings.d.ts.map +1 -0
  197. package/lib/workflows/settings.js +438 -0
  198. package/lib/workflows/settings.js.map +1 -0
  199. package/lib/workflows/trello-menu.d.ts +16 -0
  200. package/lib/workflows/trello-menu.d.ts.map +1 -0
  201. package/lib/workflows/trello-menu.js +361 -0
  202. package/lib/workflows/trello-menu.js.map +1 -0
  203. 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