vibecodingmachine-cli 2026.3.14-1537 โ†’ 2026.6.17-1956

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 (162) hide show
  1. package/bin/auth/auth-compliance.js +7 -7
  2. package/bin/commands/agent-commands.js +15 -15
  3. package/bin/commands/auto-commands.js +3 -3
  4. package/bin/commands/command-aliases.js +13 -4
  5. package/bin/config/cli-config.js +15 -5
  6. package/bin/update/update-checker.js +5 -5
  7. package/bin/vibecodingmachine.js +2 -2
  8. package/package.json +2 -2
  9. package/src/commands/agents/add.js +5 -5
  10. package/src/commands/agents/check.js +19 -19
  11. package/src/commands/agents/list.js +24 -24
  12. package/src/commands/agents/remove.js +4 -4
  13. package/src/commands/agents-check.js +1 -1
  14. package/src/commands/analyze-file-sizes.js +43 -43
  15. package/src/commands/auto-direct/auto-provider-manager.js +19 -19
  16. package/src/commands/auto-direct/auto-start-phases.js +493 -0
  17. package/src/commands/auto-direct/auto-status-display.js +35 -35
  18. package/src/commands/auto-direct/auto-utils.js +50 -50
  19. package/src/commands/auto-direct/cline-installer.js +56 -0
  20. package/src/commands/auto-direct/code-processor.js +27 -27
  21. package/src/commands/auto-direct/file-scanner.js +19 -19
  22. package/src/commands/auto-direct/ide-completion-waiter.js +485 -0
  23. package/src/commands/auto-direct/ide-fallback-runner.js +226 -0
  24. package/src/commands/auto-direct/ide-provider-runner.js +103 -0
  25. package/src/commands/auto-direct/iteration-handlers.js +189 -0
  26. package/src/commands/auto-direct/iteration-runner.js +485 -0
  27. package/src/commands/auto-direct/provider-config.js +38 -7
  28. package/src/commands/auto-direct/provider-manager.js +132 -6
  29. package/src/commands/auto-direct/requirement-manager.js +169 -104
  30. package/src/commands/auto-direct/requirement-mover.js +350 -0
  31. package/src/commands/auto-direct/spec-handlers.js +155 -0
  32. package/src/commands/auto-direct/spec-ide-runner.js +318 -0
  33. package/src/commands/auto-direct/spec-processing.js +203 -0
  34. package/src/commands/auto-direct/status-display.js +9 -9
  35. package/src/commands/auto-direct/utils.js +83 -1
  36. package/src/commands/auto-direct-refactored.js +1 -413
  37. package/src/commands/auto-direct.js +127 -4119
  38. package/src/commands/auto-execution.js +21 -21
  39. package/src/commands/auto-status-helpers.js +0 -2
  40. package/src/commands/auto.js +22 -22
  41. package/src/commands/check-compliance.js +65 -65
  42. package/src/commands/computers.js +39 -39
  43. package/src/commands/continuous-scan.js +19 -19
  44. package/src/commands/ide.js +4 -4
  45. package/src/commands/locale.js +7 -7
  46. package/src/commands/refactor-file.js +59 -59
  47. package/src/commands/requirements/commands.js +17 -17
  48. package/src/commands/requirements/default-handlers.js +30 -30
  49. package/src/commands/requirements/disable.js +3 -3
  50. package/src/commands/requirements/enable.js +3 -3
  51. package/src/commands/requirements/utils.js +6 -6
  52. package/src/commands/requirements-refactored.js +3 -3
  53. package/src/commands/requirements-remote.js +38 -38
  54. package/src/commands/requirements.js +3 -3
  55. package/src/commands/settings.js +111 -0
  56. package/src/commands/specs/count.js +60 -0
  57. package/src/commands/specs/disable.js +3 -3
  58. package/src/commands/specs/enable.js +3 -3
  59. package/src/commands/status.js +10 -10
  60. package/src/commands/sync.js +25 -25
  61. package/src/commands/timeout.js +35 -35
  62. package/src/trui/TruiInterface.js +2 -2
  63. package/src/trui/agents/AgentInterface.js +4 -4
  64. package/src/trui/agents/handlers/CommandHandler.js +4 -4
  65. package/src/trui/agents/handlers/ContextManager.js +1 -1
  66. package/src/trui/agents/handlers/DisplayHandler.js +11 -11
  67. package/src/trui/agents/handlers/HelpHandler.js +1 -1
  68. package/src/utils/agent-selector.js +6 -6
  69. package/src/utils/antigravity-installer.js +4 -4
  70. package/src/utils/asset-cleanup.js +1 -1
  71. package/src/utils/auth.js +9 -12
  72. package/src/utils/clarification-actions.js +4 -4
  73. package/src/utils/cline-js-handler.js +5 -5
  74. package/src/utils/compliance-check.js +6 -6
  75. package/src/utils/config.js +12 -12
  76. package/src/utils/display-formatters-complete.js +2 -2
  77. package/src/utils/display-formatters-extracted.js +2 -2
  78. package/src/utils/display-formatters.js +2 -2
  79. package/src/utils/feedback-handler.js +2 -2
  80. package/src/utils/first-run.js +7 -7
  81. package/src/utils/ide-detection.js +1 -1
  82. package/src/utils/ide-handlers.js +6 -6
  83. package/src/utils/interactive/clarification-actions.js +3 -3
  84. package/src/utils/interactive/core-ui.js +7 -7
  85. package/src/utils/interactive/file-backup.js +6 -6
  86. package/src/utils/interactive/file-import-export.js +49 -49
  87. package/src/utils/interactive/file-operations.js +3 -3
  88. package/src/utils/interactive/file-validation.js +41 -41
  89. package/src/utils/interactive/interactive-prompts.js +41 -41
  90. package/src/utils/interactive/requirement-actions.js +5 -5
  91. package/src/utils/interactive/requirement-crud.js +4 -4
  92. package/src/utils/interactive/requirements-navigation.js +10 -10
  93. package/src/utils/interactive-broken.js +6 -6
  94. package/src/utils/interactive.js +37 -37
  95. package/src/utils/keyboard-handler.js +4 -4
  96. package/src/utils/prompt-helper.js +6 -6
  97. package/src/utils/provider-checker/agent-checker.js +1 -1
  98. package/src/utils/provider-checker/agent-runner.js +203 -314
  99. package/src/utils/provider-checker/agents-file-lock.js +134 -0
  100. package/src/utils/provider-checker/agents-manager.js +224 -36
  101. package/src/utils/provider-checker/cli-installer.js +28 -28
  102. package/src/utils/provider-checker/cli-utils.js +2 -2
  103. package/src/utils/provider-checker/cursor-approval-clicker.js +108 -0
  104. package/src/utils/provider-checker/format-utils.js +4 -4
  105. package/src/utils/provider-checker/ide-installer-helper.js +96 -0
  106. package/src/utils/provider-checker/ide-manager.js +19 -8
  107. package/src/utils/provider-checker/ide-quota-checker.js +120 -0
  108. package/src/utils/provider-checker/ide-utils.js +2 -2
  109. package/src/utils/provider-checker/node-detector.js +4 -4
  110. package/src/utils/provider-checker/node-utils.js +5 -5
  111. package/src/utils/provider-checker/opencode-checker.js +107 -73
  112. package/src/utils/provider-checker/process-utils.js +1 -1
  113. package/src/utils/provider-checker/provider-validator.js +11 -11
  114. package/src/utils/provider-checker/quota-checker.js +5 -5
  115. package/src/utils/provider-checker/quota-detector.js +5 -5
  116. package/src/utils/provider-checker/requirements-manager.js +6 -6
  117. package/src/utils/provider-checker/test-requirements.js +1 -1
  118. package/src/utils/provider-checker/vscode-approval-clicker.js +328 -0
  119. package/src/utils/provider-checker-new.js +6 -6
  120. package/src/utils/provider-checker.js +6 -6
  121. package/src/utils/provider-checkers/ide-manager.js +13 -13
  122. package/src/utils/provider-checkers/node-executable-finder.js +4 -4
  123. package/src/utils/provider-checkers/provider-checker-core.js +5 -5
  124. package/src/utils/provider-checkers/provider-checker-main.js +17 -17
  125. package/src/utils/provider-registry.js +5 -6
  126. package/src/utils/provider-utils.js +12 -12
  127. package/src/utils/quota-detectors.js +32 -32
  128. package/src/utils/requirement-action-handlers.js +12 -12
  129. package/src/utils/requirement-actions/requirement-operations.js +3 -3
  130. package/src/utils/requirement-actions.js +1 -1
  131. package/src/utils/requirement-file-operations.js +5 -5
  132. package/src/utils/requirement-helpers.js +1 -1
  133. package/src/utils/requirement-management.js +5 -5
  134. package/src/utils/requirement-navigation.js +2 -2
  135. package/src/utils/requirement-organization.js +3 -3
  136. package/src/utils/rui-trui-adapter.js +14 -14
  137. package/src/utils/simple-trui.js +3 -3
  138. package/src/utils/status-helpers-extracted.js +3 -3
  139. package/src/utils/trui-clarifications.js +11 -11
  140. package/src/utils/trui-debug.js +3 -2
  141. package/src/utils/trui-devin.js +217 -0
  142. package/src/utils/trui-feedback.js +7 -7
  143. package/src/utils/trui-kiro-integration.js +34 -34
  144. package/src/utils/trui-main-handlers.js +20 -21
  145. package/src/utils/trui-main-menu.js +19 -19
  146. package/src/utils/trui-nav-agents.js +59 -8
  147. package/src/utils/trui-nav-requirements.js +3 -3
  148. package/src/utils/trui-nav-settings.js +10 -10
  149. package/src/utils/trui-nav-specifications.js +1 -1
  150. package/src/utils/trui-navigation-backup.js +11 -11
  151. package/src/utils/trui-navigation.js +9 -9
  152. package/src/utils/trui-provider-health.js +25 -25
  153. package/src/utils/trui-provider-manager.js +28 -28
  154. package/src/utils/trui-quick-menu.js +2 -2
  155. package/src/utils/trui-req-actions-backup.js +21 -21
  156. package/src/utils/trui-req-actions.js +20 -20
  157. package/src/utils/trui-req-editor.js +10 -10
  158. package/src/utils/trui-req-file-ops.js +3 -3
  159. package/src/utils/trui-req-tree.js +7 -7
  160. package/src/utils/trui-windsurf.js +103 -103
  161. package/src/utils/user-tracking.js +15 -15
  162. package/src/utils/trui-req-tree-old.js +0 -719
@@ -0,0 +1,318 @@
1
+ /**
2
+ * Spec IDE Runner - Spec-based IDE Iteration Handler
3
+ *
4
+ * Extracted from iteration-handlers.js for constitutional compliance (<555 lines per file)
5
+ * Handles spec iterations using IDE providers (AppleScript) with task progress monitoring.
6
+ */
7
+
8
+ const chalk = require('chalk');
9
+ const fs = require('fs-extra');
10
+ const vibecodingmachineCore = require('vibecodingmachine-core');
11
+ const { AppleScriptManager } = vibecodingmachineCore;
12
+
13
+ // Import auto mode utilities
14
+ const { getAutoConfig } = require('../../utils/config');
15
+ const { stopAutoMode } = require('../../utils/auto-mode');
16
+
17
+ // Import modular functions
18
+ const { safeLog } = require('./utils');
19
+ const { printStatusCard } = require('./status-display');
20
+
21
+ // Shared variables set by parent module
22
+ let storedStatusTitle = null;
23
+ let storedStatus = null;
24
+
25
+ /**
26
+ * Initialize module with shared instances
27
+ */
28
+ function initialize(deps) {
29
+ storedStatusTitle = deps.statusVars.storedStatusTitle;
30
+ storedStatus = deps.statusVars.storedStatus;
31
+ }
32
+
33
+ /**
34
+ * Run a spec iteration using an IDE provider (AppleScript).
35
+ * Sends the full spec instruction to the IDE and polls tasks.md for
36
+ * progress (any new checkbox ticked), instead of spawning vcm auto:start.
37
+ *
38
+ * COORDINATION STRATEGY:
39
+ * - Wait for agent completion signals instead of just detecting any progress
40
+ * - Implement 2-minute cooldown after progress detection to prevent rapid spawning
41
+ * - Only consider iteration complete when all tasks are done AND agent signals completion
42
+ * - Use STATUS:WAITING and PLEASE RESPOND signals from agent to coordinate handoff
43
+ * - Prevent multiple agents from working on the same spec simultaneously
44
+ *
45
+ * @param {Object} spec - Spec object with .path, .directory, .hasTasks, .hasPlan, .hasPlanPrompt
46
+ * @param {string} taskText - Text of the NEXT unchecked task (for display)
47
+ * @param {string} taskLine - Full line from tasks.md (used if this is a continuation)
48
+ * @param {Object} providerConfig - Provider config with .provider/.ide and .displayName
49
+ * @param {Function} countSpecCheckboxes - Function to count checkboxes in spec tasks.md
50
+ * @returns {Promise<{success: boolean, error?: string, shouldRetry?: boolean}>}
51
+ */
52
+ async function runSpecIdeIteration(spec, taskText, taskLine, providerConfig, countSpecCheckboxes) {
53
+ const ideType = providerConfig.provider || providerConfig.ide;
54
+ const { done: doneBefore, total: totalBefore } = countSpecCheckboxes(spec.path);
55
+ const pctBefore = totalBefore > 0 ? Math.round((doneBefore / totalBefore) * 100) : 0;
56
+
57
+ // Get configurable timeouts from auto config
58
+ const autoConfig = await getAutoConfig();
59
+ const PROGRESS_TIMEOUT_MS = (autoConfig.specProgressTimeoutMinutes || 15) * 60 * 1000; // Configurable, default 15 minutes
60
+ const MAX_IDE_ATTEMPTS = autoConfig.maxIdeAttempts || 3; // Configurable, default 3 attempts
61
+
62
+ // Build the full spec instruction (mirrors buildSpecInstruction from Electron app)
63
+ // This lets the agent work through multiple tasks autonomously rather than one at a time.
64
+ let instruction;
65
+ if (spec.hasTasks) {
66
+ instruction = `Read AGENTS.md and INSTRUCTIONS.md in the repo root for workflow guidelines. Implement all remaining tasks in ${spec.path}/tasks.md. Current progress: ${doneBefore}/${totalBefore} tasks (${pctBefore}%) complete. Check off each task (change "[ ]" to "[x]") as you complete it. Report progress as "X/Y tasks (Z%) complete" after each task. When ALL tasks are checked off, create a status file at ${spec.path}/.vcm-status.json with content {"completion": 100, "status": "WAITING"} to signal completion to VCM.`;
67
+ } else if (spec.hasPlan) {
68
+ instruction = `Read AGENTS.md and INSTRUCTIONS.md in the repo root for workflow guidelines. Read ${spec.path}/plan.md, generate tasks.md for this spec, then implement all tasks. Check off each task as you complete it. Report progress as "X/Y tasks (Z%) complete" after each task. When ALL tasks are done, create a status file at ${spec.path}/.vcm-status.json with content {"completion": 100, "status": "WAITING"} to signal completion to VCM.`;
69
+ } else {
70
+ const planPromptNote = spec.hasPlanPrompt
71
+ ? `Use plan prompt from ${spec.path}/plan-prompt.md to guide planning. ` : '';
72
+ instruction = `Read AGENTS.md and INSTRUCTIONS.md in the repo root for workflow guidelines. Read ${spec.path}/spec.md. ${planPromptNote}Run speckit workflow: (1) read spec.md, (2) generate plan.md, (3) generate tasks.md, (4) implement all tasks. Check off each task as you complete it. Report progress as "X/Y tasks (Z%) complete" after each task. When ALL tasks are done, create a status file at ${spec.path}/.vcm-status.json with content {"completion": 100, "status": "WAITING"} to signal completion to VCM.`;
73
+ }
74
+
75
+ // Send the spec instruction to the IDE via platform-specific automation.
76
+ // Uses the appropriate automation manager for the current platform.
77
+ console.log(chalk.cyan(`โ”๐Ÿš€ Sending spec instruction to ${providerConfig.displayName}...\n`));
78
+
79
+ if (ideType === 'devin') {
80
+ try {
81
+ let sendResult;
82
+
83
+ // Use platform-specific automation for Devin
84
+ if (process.platform === 'win32') {
85
+ const { WindowsAutomationManager } = require('vibecodingmachine-core');
86
+ const windowsManager = new WindowsAutomationManager();
87
+ sendResult = await windowsManager.sendTextToDevin?.(instruction) || { success: false, error: 'Devin Windows automation not implemented' };
88
+ } else {
89
+ const appleScriptManager = new AppleScriptManager();
90
+ sendResult = await appleScriptManager.sendText(instruction, 'devin');
91
+ }
92
+
93
+ if (sendResult && sendResult.success) {
94
+ console.log(chalk.green(`โœ“ Spec instruction sent to ${providerConfig.displayName}`));
95
+ console.log(chalk.gray(`๐Ÿ‘ Polling tasks.md every 30s for checkbox progress...\n`));
96
+ console.log(chalk.gray(`โ” Coordination: Will wait for agent completion signals before sending new tasks\n`));
97
+ } else {
98
+ console.log(chalk.red(`โœ— Failed to send instruction to Devin: ${sendResult?.error || 'Unknown error'}`));
99
+ return { success: false, error: sendResult?.error || 'Unknown error' };
100
+ }
101
+ } catch (err) {
102
+ console.log(chalk.red(`โœ— Automation error: ${err.message}`));
103
+ return { success: false, error: err.message };
104
+ }
105
+ } else {
106
+ // Non-Devin IDEs: use AppleScriptManager
107
+ try {
108
+ const appleScriptManager = new AppleScriptManager();
109
+ let sendResult;
110
+ // Prefer sendTextWithThreadClosure when available (.js build), fall back to sendText (.cjs build)
111
+ if (typeof appleScriptManager.sendTextWithThreadClosure === 'function') {
112
+ sendResult = await appleScriptManager.sendTextWithThreadClosure(instruction, ideType);
113
+ } else {
114
+ sendResult = await appleScriptManager.sendText(instruction, ideType);
115
+ }
116
+ if (!sendResult || !sendResult.success) {
117
+ const errorMsg = (sendResult && sendResult.error) || `Failed to send text to ${providerConfig.displayName}`;
118
+ console.log(chalk.red(`โœ— ${errorMsg}`));
119
+ return { success: false, error: errorMsg };
120
+ }
121
+ } catch (err) {
122
+ console.log(chalk.red(`โœ— AppleScript error: ${err.message}`));
123
+ return { success: false, error: err.message };
124
+ }
125
+ }
126
+
127
+ console.log(chalk.green(`โœ“ Spec instruction sent to ${providerConfig.displayName}`));
128
+ console.log(chalk.gray(`๐Ÿ‘ Polling tasks.md every 30s for checkbox progress...\n`));
129
+ console.log(chalk.gray(`โ” Coordination: Will wait for agent completion signals before sending new tasks\n`));
130
+
131
+ const POLL_INTERVAL_MS = 30 * 1000; // 30 seconds
132
+ const CONTINUE_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
133
+ const TIMEOUT_MS = PROGRESS_TIMEOUT_MS; // Use configurable progress timeout
134
+ const PROGRESS_COOLDOWN_MS = 2 * 60 * 1000; // 2 minutes cooldown after progress
135
+
136
+ let startTime = Date.now();
137
+ let lastContinueSent = Date.now();
138
+ let lastProgressTime = Date.now(); // Track when we last saw progress
139
+ let lastProgressDetectionTime = 0; // Track when we last detected progress to implement cooldown
140
+ let totalAttempts = (spec.totalIdeAttempts || 0) + 1; // Track attempts across IDEs
141
+ let agentSignaledCompletion = false; // Track if agent signaled completion
142
+
143
+ return new Promise((resolve) => {
144
+ let interval = null;
145
+
146
+ const cleanup = (result) => {
147
+ if (interval) { clearInterval(interval); interval = null; }
148
+ process.off('SIGINT', onSigint);
149
+ resolve(result);
150
+ };
151
+
152
+ // Handle Ctrl+C gracefully: stop polling and exit cleanly
153
+ const onSigint = () => {
154
+ console.log(chalk.yellow('\nโš  Interrupted โ€” stopping spec task polling\n'));
155
+ cleanup({ success: false, error: 'interrupted' });
156
+ // Re-emit to trigger normal process exit after cleanup
157
+ process.exit(0);
158
+ };
159
+ process.once('SIGINT', onSigint);
160
+
161
+ interval = setInterval(async () => {
162
+ try {
163
+ const elapsed = Date.now() - startTime;
164
+
165
+ if (elapsed >= TIMEOUT_MS) {
166
+ console.log(chalk.yellow(`โฑ Overall timeout reached - but continuing to monitor existing Devin instance\n`));
167
+ // Don't create new instance - just reset timer and continue monitoring
168
+ startTime = Date.now(); // Reset the overall timer
169
+ return;
170
+ }
171
+
172
+ // Detect progress AND check if IDE agent is done working
173
+ const { done: doneNow, total: totalNow } = countSpecCheckboxes(spec.path);
174
+ const currentTime = Date.now();
175
+
176
+ // Check for completion signals from IDE agent via status file
177
+ const statusFilePath = spec.path.replace(/\/$/, '') + '/.vcm-status.json';
178
+ let agentSignaledCompletionViaFile = false;
179
+ try {
180
+ if (await fs.pathExists(statusFilePath)) {
181
+ const statusContent = await fs.readFile(statusFilePath, 'utf-8');
182
+ const statusData = JSON.parse(statusContent);
183
+
184
+ // Check for completion signals
185
+ if (statusData.completion === 100 && statusData.status === 'WAITING') {
186
+ console.log(chalk.green(`โ†๐Ÿ‘ Agent signaled completion via status file: COMPLETION: 100%, STATUS:WAITING\n`));
187
+ agentSignaledCompletionViaFile = true;
188
+
189
+ // Clean up status file
190
+ await fs.remove(statusFilePath);
191
+ }
192
+ }
193
+ } catch (error) {
194
+ // Ignore status file errors - file might not exist or be invalid JSON
195
+ }
196
+
197
+ if (doneNow > doneBefore) {
198
+ const pctNow = totalNow > 0 ? Math.round((doneNow / totalNow) * 100) : 0;
199
+ safeLog(chalk.green(`โœ“ Progress detected: ${doneNow}/${totalNow} tasks (${pctNow}%) complete\n`));
200
+
201
+ // Update progress tracking
202
+ lastProgressTime = currentTime;
203
+ lastProgressDetectionTime = currentTime;
204
+
205
+ // Check if all tasks are complete - if so, wait for agent completion signal
206
+ if (doneNow >= totalNow && totalNow > 0) {
207
+ safeLog(chalk.green(`โ†๐Ÿ‘ All tasks checked off! Waiting for agent completion signal...\n`));
208
+ // Don't immediately return - wait for STATUS:WAITING signal or timeout
209
+ agentSignaledCompletion = true; // Flag that we're waiting for completion signal
210
+ }
211
+
212
+ // If progress detected but not all tasks done, continue waiting for agent to finish current work
213
+ return; // Continue polling, don't send new task yet
214
+ }
215
+
216
+ // Check for immediate completion via status file signal
217
+ if (agentSignaledCompletionViaFile) {
218
+ safeLog(chalk.green(`โœ“ Agent completed all tasks via status signal! Finishing iteration.\n`));
219
+ cleanup({ success: true, changes: [] });
220
+ return;
221
+ }
222
+
223
+ // If agent signaled completion (all tasks done) and we've waited a reasonable time, consider it complete
224
+ if (agentSignaledCompletion && (currentTime - lastProgressDetectionTime) >= 30 * 1000) {
225
+ safeLog(chalk.green(`โœ“ Agent completed all tasks! Finishing iteration.\n`));
226
+ cleanup({ success: true, changes: [] });
227
+ return;
228
+ }
229
+
230
+ // Implement cooldown period after progress detection to prevent rapid spawning
231
+ if (lastProgressDetectionTime > 0 && (currentTime - lastProgressDetectionTime) < PROGRESS_COOLDOWN_MS) {
232
+ const cooldownRemaining = Math.round((PROGRESS_COOLDOWN_MS - (currentTime - lastProgressDetectionTime)) / 1000);
233
+ safeLog(chalk.gray(`โณ In cooldown period (${cooldownRemaining}s remaining) - allowing agent time to complete work\n`));
234
+ return;
235
+ }
236
+
237
+ // Check for no progress timeout (configurable, default 15 minutes)
238
+ const timeSinceLastProgress = Date.now() - lastProgressTime;
239
+ if (timeSinceLastProgress >= PROGRESS_TIMEOUT_MS) {
240
+ safeLog(chalk.yellow(`โฑ No progress detected for ${Math.round(timeSinceLastProgress / 60000)} minutes on ${providerConfig.displayName}\n`));
241
+
242
+ // Check if we've exceeded max IDE attempts
243
+ if (totalAttempts >= MAX_IDE_ATTEMPTS) {
244
+ safeLog(chalk.red(`โœ— Maximum IDE attempts (${MAX_IDE_ATTEMPTS}) reached. Stopping auto mode.\n`));
245
+ // Update status card to stopped mode
246
+ if (storedStatusTitle && storedStatus) {
247
+ printStatusCard(storedStatusTitle, storedStatus, 'stopped');
248
+ }
249
+ // Stop auto mode - call async function properly
250
+ stopAutoMode().then(() => {
251
+ cleanup({ success: false, error: 'max_ide_attempts_exceeded', shouldRetry: false });
252
+ }).catch((err) => {
253
+ console.error('Error stopping auto mode:', err);
254
+ cleanup({ success: false, error: 'max_ide_attempts_exceeded', shouldRetry: false });
255
+ });
256
+ return;
257
+ }
258
+
259
+ // Don't create new instance - just continue monitoring and send continuation messages
260
+ // Reset the progress timer to allow more time for the existing instance
261
+ lastProgressTime = Date.now();
262
+ totalAttempts++;
263
+ console.log(chalk.yellow(`โฑ Allowing more time for existing Devin instance (attempt ${totalAttempts}/${MAX_IDE_ATTEMPTS})\n`));
264
+ return;
265
+ }
266
+
267
+ // Send continuation prompt every 5 minutes if no progress OR if waiting for completion signal
268
+ const sinceLastContinue = Date.now() - lastContinueSent;
269
+ if (sinceLastContinue >= CONTINUE_INTERVAL_MS) {
270
+ lastContinueSent = Date.now();
271
+ const pctNow = totalNow > 0 ? Math.round((doneNow / totalNow) * 100) : 0;
272
+ const mins = Math.round(elapsed / 60000);
273
+
274
+ let continueMsg;
275
+ if (agentSignaledCompletion) {
276
+ // All tasks done, waiting for completion signal
277
+ continueMsg = `All tasks appear to be completed (${doneNow}/${totalNow} tasks). If you are truly finished, create a status file at ${spec.path}/.vcm-status.json with content {"completion": 100, "status": "WAITING"} to signal completion to VCM. If you need more time, continue working and report progress. (${mins}min elapsed) PLEASE RESPOND`;
278
+ } else {
279
+ // Normal continuation prompt
280
+ continueMsg = `Continue implementing spec "${spec.directory}". Current progress: ${doneNow}/${totalNow} tasks (${pctNow}%) complete. Keep working until ALL tasks in tasks.md are checked off. Next task: "${taskText}". (${mins}min elapsed) PLEASE RESPOND`;
281
+ }
282
+
283
+ try {
284
+ const appleScriptManager = new AppleScriptManager();
285
+ // Use plain sendText for continuations (no need to open new thread)
286
+ await appleScriptManager.sendText(continueMsg, ideType);
287
+ try {
288
+ // Check if stdout is still writable before attempting to log
289
+ if (process.stdout && !process.stdout.destroyed) {
290
+ const message = `โ”๐Ÿ” Sent continuation prompt to ${providerConfig.displayName} (${mins}min elapsed)\n`;
291
+ console.log(chalk.gray(message));
292
+ }
293
+ } catch (logError) {
294
+ // Ignore EPIPE and other stdout errors - process may be terminating
295
+ if (logError.code !== 'EPIPE') {
296
+ // Re-throw non-EPIPE errors
297
+ throw logError;
298
+ }
299
+ }
300
+ } catch (_) { /* ignore continuation errors */ }
301
+ }
302
+ } catch (error) {
303
+ // Handle any errors in the setInterval callback, especially EPIPE
304
+ if (error.code === 'EPIPE') {
305
+ // Silently ignore EPIPE errors - process is terminating
306
+ return;
307
+ }
308
+ // Log other errors but don't crash
309
+ console.error('Error in polling interval:', error.message);
310
+ }
311
+ }, POLL_INTERVAL_MS);
312
+ });
313
+ }
314
+
315
+ module.exports = {
316
+ initialize,
317
+ runSpecIdeIteration
318
+ };
@@ -0,0 +1,203 @@
1
+ /**
2
+ * Spec Processing Helpers
3
+ *
4
+ * Handles spec planning and tasks.md generation/regeneration
5
+ */
6
+
7
+ const chalk = require('chalk');
8
+ const path = require('path');
9
+ const fs = require('fs-extra');
10
+ const vibecodingmachineCore = require('vibecodingmachine-core');
11
+ const { AppleScriptManager } = vibecodingmachineCore;
12
+ const { runIteration } = require('./iteration-handlers');
13
+ const { countSpecCheckboxes } = require('./spec-handlers');
14
+
15
+ /**
16
+ * Handle spec planning - create tasks.md for specs without one
17
+ */
18
+ async function handleSpecPlanning(spec, providerConfig, repoPath) {
19
+ console.log(chalk.cyan('๐Ÿ“ No tasks.md yet โ€” planning spec first...\n'));
20
+ const planningText = [
21
+ `Plan and create tasks.md for spec "${spec.directory}".`,
22
+ `Read ${spec.path}/spec.md${spec.hasPlanPrompt ? ` and ${spec.path}/plan-prompt.md` : ''}.`,
23
+ `Then create ${spec.path}/tasks.md with implementation tasks as checkboxes (- [ ] task).`
24
+ ].join('\n');
25
+
26
+ let planResult;
27
+ if (providerConfig.type === 'ide') {
28
+ console.log(chalk.cyan(`๐Ÿ“ค Sending planning task to ${providerConfig.displayName}...\n`));
29
+ const ideTypeForPlan = providerConfig.provider || providerConfig.ide;
30
+
31
+ const sendPlanToIde = async () => {
32
+ if (ideTypeForPlan === 'devin') {
33
+ return await sendDevinPlanningTask(planningText);
34
+ } else {
35
+ const appleScriptManager = new AppleScriptManager();
36
+ const sendResult = typeof appleScriptManager.sendTextWithThreadClosure === 'function'
37
+ ? await appleScriptManager.sendTextWithThreadClosure(planningText, ideTypeForPlan)
38
+ : await appleScriptManager.sendText(planningText, ideTypeForPlan);
39
+ if (!sendResult || !sendResult.success) {
40
+ throw new Error((sendResult && sendResult.error) || 'send failed');
41
+ }
42
+ return { success: true };
43
+ }
44
+ };
45
+
46
+ try {
47
+ const sendPlanResult = await sendPlanToIde();
48
+ if (!sendPlanResult || !sendPlanResult.success) {
49
+ console.log(chalk.red(`โœ— Failed to send to ${providerConfig.displayName}`));
50
+ planResult = { success: false, error: 'send failed' };
51
+ } else {
52
+ console.log(chalk.green(`โœ“ Planning task sent. Waiting for tasks.md to be created...`));
53
+ const tasksFilePath = path.join(spec.path, 'tasks.md');
54
+ const POLL_MS = 30 * 1000;
55
+ const TIMEOUT_MS = 30 * 60 * 1000;
56
+ const planStart = Date.now();
57
+ planResult = await new Promise((resolve) => {
58
+ const iv = setInterval(() => {
59
+ if (fs.existsSync(tasksFilePath)) {
60
+ clearInterval(iv);
61
+ console.log(chalk.green('โœ“ tasks.md created!\n'));
62
+ resolve({ success: true });
63
+ } else if (Date.now() - planStart >= TIMEOUT_MS) {
64
+ clearInterval(iv);
65
+ console.log(chalk.yellow('โฐ Timeout waiting for tasks.md\n'));
66
+ resolve({ success: false, error: 'timeout' });
67
+ }
68
+ }, POLL_MS);
69
+ });
70
+ }
71
+ } catch (err) {
72
+ planResult = { success: false, error: err.message };
73
+ }
74
+ } else {
75
+ const planRequirement = { text: planningText, package: null, disabled: false };
76
+ planResult = await runIteration(planRequirement, providerConfig, repoPath);
77
+ }
78
+
79
+ if (planResult.success) {
80
+ console.log(chalk.green('โœ“ tasks.md created โ€” proceeding with implementation\n'));
81
+ } else {
82
+ console.log(chalk.red('โœ— Failed to create tasks.md โ€” skipping spec\n'));
83
+ }
84
+
85
+ return planResult;
86
+ }
87
+
88
+ /**
89
+ * Handle tasks.md regeneration for narrative format specs
90
+ */
91
+ async function handleTasksRegeneration(spec, providerConfig) {
92
+ console.log(chalk.yellow(`\nโš ๏ธ tasks.md exists but has no checkbox tasks (narrative format detected)`));
93
+ console.log(chalk.yellow(` Regenerating tasks.md with /speckit.tasks...\n`));
94
+
95
+ const tasksRegenerationText = `Run /speckit.tasks to regenerate tasks.md for spec "${spec.directory}" in checkbox format (- [ ] task).`;
96
+
97
+ let regenResult;
98
+ if (providerConfig.type === 'ide') {
99
+ console.log(chalk.cyan(`๐Ÿ“ค Sending tasks regeneration request to ${providerConfig.displayName}...\n`));
100
+ const ideType = providerConfig.provider || providerConfig.ide;
101
+ const appleScriptManager = new AppleScriptManager();
102
+ try {
103
+ const sendResult = typeof appleScriptManager.sendTextWithThreadClosure === 'function'
104
+ ? await appleScriptManager.sendTextWithThreadClosure(tasksRegenerationText, ideType)
105
+ : await appleScriptManager.sendText(tasksRegenerationText, ideType);
106
+
107
+ if (!sendResult || !sendResult.success) {
108
+ console.log(chalk.red(`โœ— Failed to send to ${providerConfig.displayName}`));
109
+ regenResult = { success: false };
110
+ } else {
111
+ console.log(chalk.green(`โœ“ Regeneration request sent. Waiting for tasks.md to be updated...`));
112
+ await new Promise(resolve => setTimeout(resolve, 30000));
113
+
114
+ const { total: totalAfterRegen } = countSpecCheckboxes(spec.path);
115
+ if (totalAfterRegen > 0) {
116
+ console.log(chalk.green(`โœ“ tasks.md regenerated with ${totalAfterRegen} checkbox tasks!\n`));
117
+ regenResult = { success: true };
118
+ } else {
119
+ console.log(chalk.yellow(`โš ๏ธ tasks.md still has no checkbox tasks after regeneration\n`));
120
+ regenResult = { success: false };
121
+ }
122
+ }
123
+ } catch (err) {
124
+ regenResult = { success: false, error: err.message };
125
+ }
126
+ } else {
127
+ const regenRequirement = { text: tasksRegenerationText, package: null, disabled: false };
128
+ regenResult = await runIteration(regenRequirement, providerConfig, spec.path);
129
+ }
130
+
131
+ if (!regenResult.success) {
132
+ console.log(chalk.red('โœ— Failed to regenerate tasks.md โ€” skipping spec\n'));
133
+ }
134
+
135
+ return regenResult;
136
+ }
137
+
138
+ /**
139
+ * Send planning task to Devin using AppleScript
140
+ */
141
+ async function sendDevinPlanningTask(planningText) {
142
+ const { execSync } = require('child_process');
143
+ const { writeFileSync, unlinkSync } = require('fs');
144
+ const { tmpdir } = require('os');
145
+ const ts = Date.now();
146
+ const tmpText = path.join(tmpdir(), `plan_instr_${ts}.txt`);
147
+ const tmpScpt = path.join(tmpdir(), `send_devin_plan_${ts}.scpt`);
148
+ writeFileSync(tmpText, planningText, 'utf8');
149
+ const combinedPlanScript = `
150
+ set planText to (do shell script "cat " & quoted form of "${tmpText}")
151
+ tell application "Devin"
152
+ activate
153
+ delay 1.5
154
+ end tell
155
+ set maxTries to 3
156
+ set tries to 0
157
+ repeat
158
+ set tries to tries + 1
159
+ tell application "System Events"
160
+ set frontBundleID to bundle identifier of first process whose frontmost is true
161
+ end tell
162
+ if frontBundleID is "ai.cognition.devin" then exit repeat
163
+ if tries >= maxTries then
164
+ error "Devin did not become frontmost (frontmost bundle ID: " & frontBundleID & ")"
165
+ end if
166
+ tell application "Devin"
167
+ activate
168
+ end tell
169
+ delay 1.0
170
+ end repeat
171
+ tell application "System Events"
172
+ set devinProc to first process whose bundle identifier is "ai.cognition.devin"
173
+ tell devinProc
174
+ set frontmost to true
175
+ delay 0.5
176
+ keystroke "l" using {command down, shift down}
177
+ delay 2.0
178
+ keystroke "a" using {command down}
179
+ delay 0.3
180
+ key code 51
181
+ delay 0.3
182
+ set the clipboard to planText
183
+ keystroke "v" using {command down}
184
+ delay 0.5
185
+ key code 36
186
+ delay 1.0
187
+ end tell
188
+ end tell
189
+ `;
190
+ writeFileSync(tmpScpt, combinedPlanScript, 'utf8');
191
+ try {
192
+ execSync(`osascript "${tmpScpt}"`, { stdio: 'pipe', timeout: 30000 });
193
+ return { success: true };
194
+ } finally {
195
+ try { unlinkSync(tmpScpt); } catch (_) { }
196
+ try { unlinkSync(tmpText); } catch (_) { }
197
+ }
198
+ }
199
+
200
+ module.exports = {
201
+ handleSpecPlanning,
202
+ handleTasksRegeneration
203
+ };
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  const chalk = require('chalk');
7
- const { getAutoConfig, getStages, DEFAULT_STAGES } = require('../../../utils/config');
7
+ const { getAutoConfig, getStages, DEFAULT_STAGES } = require('../../utils/config');
8
8
 
9
9
  // Configured stages (will be loaded from config)
10
10
  let configuredStages = DEFAULT_STAGES;
@@ -21,32 +21,32 @@ function printStatusCard(currentTitle, currentStatus) {
21
21
 
22
22
  // Get terminal width
23
23
  const terminalWidth = process.stdout.columns || 80;
24
-
24
+
25
25
  // Calculate widths
26
26
  const padding = 2;
27
27
  const maxTitleWidth = Math.floor((terminalWidth - padding * 2) * 0.4);
28
28
  const maxStatusWidth = Math.floor((terminalWidth - padding * 2) * 0.6);
29
-
29
+
30
30
  // Truncate title if too long
31
31
  let displayTitle = currentTitle;
32
32
  if (displayTitle.length > maxTitleWidth) {
33
33
  displayTitle = displayTitle.substring(0, maxTitleWidth - 3) + '...';
34
34
  }
35
-
35
+
36
36
  // Build status line
37
37
  let statusLine = '';
38
38
  stages.forEach((stage, index) => {
39
39
  const isCurrent = stage === currentStatus;
40
40
  const isCompleted = stageMap[currentStatus] > index;
41
41
  const isPending = stageMap[currentStatus] < index;
42
-
42
+
43
43
  let stageDisplay = stage;
44
44
  if (stage === 'PREPARE') stageDisplay = '๐Ÿ” PREPARE';
45
45
  else if (stage === 'ACT') stageDisplay = 'โšก ACT';
46
46
  else if (stage === 'CLEAN UP') stageDisplay = '๐Ÿงน CLEAN UP';
47
47
  else if (stage === 'VERIFY') stageDisplay = 'โœ… VERIFY';
48
48
  else if (stage === 'DONE') stageDisplay = '๐ŸŽ‰ DONE';
49
-
49
+
50
50
  if (isCurrent) {
51
51
  statusLine += chalk.bgBlue.white(` ${stageDisplay} `);
52
52
  } else if (isCompleted) {
@@ -54,17 +54,17 @@ function printStatusCard(currentTitle, currentStatus) {
54
54
  } else if (isPending) {
55
55
  statusLine += chalk.bgGray.white(` ${stageDisplay} `);
56
56
  }
57
-
57
+
58
58
  if (index < stages.length - 1) {
59
59
  statusLine += ' ';
60
60
  }
61
61
  });
62
-
62
+
63
63
  // Build the card
64
64
  const border = 'โ”€'.repeat(terminalWidth);
65
65
  const titleLine = displayTitle.padEnd(terminalWidth - padding * 2);
66
66
  const statusLinePadded = statusLine.padEnd(terminalWidth - padding * 2);
67
-
67
+
68
68
  console.log();
69
69
  console.log(chalk.gray(border));
70
70
  console.log(chalk.gray(`โ”‚ ${titleLine} โ”‚`));
@@ -100,6 +100,84 @@ function sleep(ms) {
100
100
  return new Promise(resolve => setTimeout(resolve, ms));
101
101
  }
102
102
 
103
+ /**
104
+ * Safely log to console with EPIPE protection
105
+ * @param {...any} args - Arguments to pass to console.log
106
+ */
107
+ function safeLog(...args) {
108
+ try {
109
+ // Check if stdout is still writable before attempting to log
110
+ if (process.stdout && !process.stdout.destroyed) {
111
+ console.log(...args);
112
+ }
113
+ } catch (logError) {
114
+ // Ignore EPIPE and other stdout errors - process may be terminating
115
+ if (logError.code !== 'EPIPE') {
116
+ // Re-throw non-EPIPE errors
117
+ throw logError;
118
+ }
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Normalize whitespace for comparison (convert all whitespace to single spaces)
124
+ */
125
+ function normalizeWhitespace(str) {
126
+ return str.replace(/\s+/g, ' ').trim();
127
+ }
128
+
129
+ /**
130
+ * Extract key identifiers from code (variable names, function names, strings)
131
+ */
132
+ function extractIdentifiers(code) {
133
+ const identifiers = new Set();
134
+
135
+ // Extract quoted strings
136
+ const stringMatches = code.match(/'([^']+)'|"([^"]+)"/g);
137
+ if (stringMatches) {
138
+ stringMatches.forEach(match => {
139
+ const str = match.slice(1, -1); // Remove quotes
140
+ if (str.length > 3) { // Only meaningful strings
141
+ identifiers.add(str);
142
+ }
143
+ });
144
+ }
145
+
146
+ // Extract variable/function names (words followed by : or =)
147
+ const nameMatches = code.match(/\b([a-zA-Z_$][a-zA-Z0-9_$]*)\s*[:=]/g);
148
+ if (nameMatches) {
149
+ nameMatches.forEach(match => {
150
+ const name = match.replace(/[:=].*$/, '').trim();
151
+ if (name.length > 2) {
152
+ identifiers.add(name);
153
+ }
154
+ });
155
+ }
156
+
157
+ // Extract common patterns like 'type:', 'name:', 'value:'
158
+ const patternMatches = code.match(/(type|name|value|file|path|function|const|let|var)\s*:/gi);
159
+ if (patternMatches) {
160
+ patternMatches.forEach(match => {
161
+ identifiers.add(match.toLowerCase().replace(/\s*:/, ''));
162
+ });
163
+ }
164
+
165
+ return Array.from(identifiers);
166
+ }
167
+
168
+ /**
169
+ * Extract structural pattern from code (ignoring values)
170
+ */
171
+ function extractPattern(code) {
172
+ // Replace strings with placeholders
173
+ let pattern = code.replace(/'[^']+'|"[^"]+"/g, '"..."');
174
+ // Replace numbers with placeholders
175
+ pattern = pattern.replace(/\b\d+\b/g, 'N');
176
+ // Normalize whitespace
177
+ pattern = normalizeWhitespace(pattern);
178
+ return pattern;
179
+ }
180
+
103
181
  module.exports = {
104
182
  getTimestamp,
105
183
  getLogTimestamp,
@@ -108,5 +186,9 @@ module.exports = {
108
186
  getVisualWidth,
109
187
  padToVisualWidth,
110
188
  isRateLimitMessage,
111
- sleep
189
+ sleep,
190
+ safeLog,
191
+ normalizeWhitespace,
192
+ extractIdentifiers,
193
+ extractPattern
112
194
  };