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.
- package/bin/auth/auth-compliance.js +7 -7
- package/bin/commands/agent-commands.js +15 -15
- package/bin/commands/auto-commands.js +3 -3
- package/bin/commands/command-aliases.js +13 -4
- package/bin/config/cli-config.js +15 -5
- package/bin/update/update-checker.js +5 -5
- package/bin/vibecodingmachine.js +2 -2
- package/package.json +2 -2
- package/src/commands/agents/add.js +5 -5
- package/src/commands/agents/check.js +19 -19
- package/src/commands/agents/list.js +24 -24
- package/src/commands/agents/remove.js +4 -4
- package/src/commands/agents-check.js +1 -1
- package/src/commands/analyze-file-sizes.js +43 -43
- package/src/commands/auto-direct/auto-provider-manager.js +19 -19
- package/src/commands/auto-direct/auto-start-phases.js +493 -0
- package/src/commands/auto-direct/auto-status-display.js +35 -35
- package/src/commands/auto-direct/auto-utils.js +50 -50
- package/src/commands/auto-direct/cline-installer.js +56 -0
- package/src/commands/auto-direct/code-processor.js +27 -27
- package/src/commands/auto-direct/file-scanner.js +19 -19
- package/src/commands/auto-direct/ide-completion-waiter.js +485 -0
- package/src/commands/auto-direct/ide-fallback-runner.js +226 -0
- package/src/commands/auto-direct/ide-provider-runner.js +103 -0
- package/src/commands/auto-direct/iteration-handlers.js +189 -0
- package/src/commands/auto-direct/iteration-runner.js +485 -0
- package/src/commands/auto-direct/provider-config.js +38 -7
- package/src/commands/auto-direct/provider-manager.js +132 -6
- package/src/commands/auto-direct/requirement-manager.js +169 -104
- package/src/commands/auto-direct/requirement-mover.js +350 -0
- package/src/commands/auto-direct/spec-handlers.js +155 -0
- package/src/commands/auto-direct/spec-ide-runner.js +318 -0
- package/src/commands/auto-direct/spec-processing.js +203 -0
- package/src/commands/auto-direct/status-display.js +9 -9
- package/src/commands/auto-direct/utils.js +83 -1
- package/src/commands/auto-direct-refactored.js +1 -413
- package/src/commands/auto-direct.js +127 -4119
- package/src/commands/auto-execution.js +21 -21
- package/src/commands/auto-status-helpers.js +0 -2
- package/src/commands/auto.js +22 -22
- package/src/commands/check-compliance.js +65 -65
- package/src/commands/computers.js +39 -39
- package/src/commands/continuous-scan.js +19 -19
- package/src/commands/ide.js +4 -4
- package/src/commands/locale.js +7 -7
- package/src/commands/refactor-file.js +59 -59
- package/src/commands/requirements/commands.js +17 -17
- package/src/commands/requirements/default-handlers.js +30 -30
- package/src/commands/requirements/disable.js +3 -3
- package/src/commands/requirements/enable.js +3 -3
- package/src/commands/requirements/utils.js +6 -6
- package/src/commands/requirements-refactored.js +3 -3
- package/src/commands/requirements-remote.js +38 -38
- package/src/commands/requirements.js +3 -3
- package/src/commands/settings.js +111 -0
- package/src/commands/specs/count.js +60 -0
- package/src/commands/specs/disable.js +3 -3
- package/src/commands/specs/enable.js +3 -3
- package/src/commands/status.js +10 -10
- package/src/commands/sync.js +25 -25
- package/src/commands/timeout.js +35 -35
- package/src/trui/TruiInterface.js +2 -2
- package/src/trui/agents/AgentInterface.js +4 -4
- package/src/trui/agents/handlers/CommandHandler.js +4 -4
- package/src/trui/agents/handlers/ContextManager.js +1 -1
- package/src/trui/agents/handlers/DisplayHandler.js +11 -11
- package/src/trui/agents/handlers/HelpHandler.js +1 -1
- package/src/utils/agent-selector.js +6 -6
- package/src/utils/antigravity-installer.js +4 -4
- package/src/utils/asset-cleanup.js +1 -1
- package/src/utils/auth.js +9 -12
- package/src/utils/clarification-actions.js +4 -4
- package/src/utils/cline-js-handler.js +5 -5
- package/src/utils/compliance-check.js +6 -6
- package/src/utils/config.js +12 -12
- package/src/utils/display-formatters-complete.js +2 -2
- package/src/utils/display-formatters-extracted.js +2 -2
- package/src/utils/display-formatters.js +2 -2
- package/src/utils/feedback-handler.js +2 -2
- package/src/utils/first-run.js +7 -7
- package/src/utils/ide-detection.js +1 -1
- package/src/utils/ide-handlers.js +6 -6
- package/src/utils/interactive/clarification-actions.js +3 -3
- package/src/utils/interactive/core-ui.js +7 -7
- package/src/utils/interactive/file-backup.js +6 -6
- package/src/utils/interactive/file-import-export.js +49 -49
- package/src/utils/interactive/file-operations.js +3 -3
- package/src/utils/interactive/file-validation.js +41 -41
- package/src/utils/interactive/interactive-prompts.js +41 -41
- package/src/utils/interactive/requirement-actions.js +5 -5
- package/src/utils/interactive/requirement-crud.js +4 -4
- package/src/utils/interactive/requirements-navigation.js +10 -10
- package/src/utils/interactive-broken.js +6 -6
- package/src/utils/interactive.js +37 -37
- package/src/utils/keyboard-handler.js +4 -4
- package/src/utils/prompt-helper.js +6 -6
- package/src/utils/provider-checker/agent-checker.js +1 -1
- package/src/utils/provider-checker/agent-runner.js +203 -314
- package/src/utils/provider-checker/agents-file-lock.js +134 -0
- package/src/utils/provider-checker/agents-manager.js +224 -36
- package/src/utils/provider-checker/cli-installer.js +28 -28
- package/src/utils/provider-checker/cli-utils.js +2 -2
- package/src/utils/provider-checker/cursor-approval-clicker.js +108 -0
- package/src/utils/provider-checker/format-utils.js +4 -4
- package/src/utils/provider-checker/ide-installer-helper.js +96 -0
- package/src/utils/provider-checker/ide-manager.js +19 -8
- package/src/utils/provider-checker/ide-quota-checker.js +120 -0
- package/src/utils/provider-checker/ide-utils.js +2 -2
- package/src/utils/provider-checker/node-detector.js +4 -4
- package/src/utils/provider-checker/node-utils.js +5 -5
- package/src/utils/provider-checker/opencode-checker.js +107 -73
- package/src/utils/provider-checker/process-utils.js +1 -1
- package/src/utils/provider-checker/provider-validator.js +11 -11
- package/src/utils/provider-checker/quota-checker.js +5 -5
- package/src/utils/provider-checker/quota-detector.js +5 -5
- package/src/utils/provider-checker/requirements-manager.js +6 -6
- package/src/utils/provider-checker/test-requirements.js +1 -1
- package/src/utils/provider-checker/vscode-approval-clicker.js +328 -0
- package/src/utils/provider-checker-new.js +6 -6
- package/src/utils/provider-checker.js +6 -6
- package/src/utils/provider-checkers/ide-manager.js +13 -13
- package/src/utils/provider-checkers/node-executable-finder.js +4 -4
- package/src/utils/provider-checkers/provider-checker-core.js +5 -5
- package/src/utils/provider-checkers/provider-checker-main.js +17 -17
- package/src/utils/provider-registry.js +5 -6
- package/src/utils/provider-utils.js +12 -12
- package/src/utils/quota-detectors.js +32 -32
- package/src/utils/requirement-action-handlers.js +12 -12
- package/src/utils/requirement-actions/requirement-operations.js +3 -3
- package/src/utils/requirement-actions.js +1 -1
- package/src/utils/requirement-file-operations.js +5 -5
- package/src/utils/requirement-helpers.js +1 -1
- package/src/utils/requirement-management.js +5 -5
- package/src/utils/requirement-navigation.js +2 -2
- package/src/utils/requirement-organization.js +3 -3
- package/src/utils/rui-trui-adapter.js +14 -14
- package/src/utils/simple-trui.js +3 -3
- package/src/utils/status-helpers-extracted.js +3 -3
- package/src/utils/trui-clarifications.js +11 -11
- package/src/utils/trui-debug.js +3 -2
- package/src/utils/trui-devin.js +217 -0
- package/src/utils/trui-feedback.js +7 -7
- package/src/utils/trui-kiro-integration.js +34 -34
- package/src/utils/trui-main-handlers.js +20 -21
- package/src/utils/trui-main-menu.js +19 -19
- package/src/utils/trui-nav-agents.js +59 -8
- package/src/utils/trui-nav-requirements.js +3 -3
- package/src/utils/trui-nav-settings.js +10 -10
- package/src/utils/trui-nav-specifications.js +1 -1
- package/src/utils/trui-navigation-backup.js +11 -11
- package/src/utils/trui-navigation.js +9 -9
- package/src/utils/trui-provider-health.js +25 -25
- package/src/utils/trui-provider-manager.js +28 -28
- package/src/utils/trui-quick-menu.js +2 -2
- package/src/utils/trui-req-actions-backup.js +21 -21
- package/src/utils/trui-req-actions.js +20 -20
- package/src/utils/trui-req-editor.js +10 -10
- package/src/utils/trui-req-file-ops.js +3 -3
- package/src/utils/trui-req-tree.js +7 -7
- package/src/utils/trui-windsurf.js +103 -103
- package/src/utils/user-tracking.js +15 -15
- 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('
|
|
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
|
};
|