erosolar-cli 2.1.176 → 2.1.177

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.
@@ -4,6 +4,7 @@ import { exec } from 'node:child_process';
4
4
  import { promisify } from 'node:util';
5
5
  import { existsSync, readFileSync, statSync, writeFileSync } from 'node:fs';
6
6
  import { join, resolve } from 'node:path';
7
+ import { AgentOrchestrator } from '../core/agentOrchestrator.js';
7
8
  import { display } from '../ui/display.js';
8
9
  import { theme } from '../ui/theme.js';
9
10
  import { getTerminalColumns } from '../ui/layout.js';
@@ -12,13 +13,12 @@ import { ensureSecretForProvider, getSecretDefinitionForProvider, getSecretValue
12
13
  import { saveActiveProfilePreference, saveModelPreference, loadToolSettings, saveToolSettings, clearToolSettings, clearActiveProfilePreference, loadSessionPreferences, saveSessionPreferences, loadFeatureFlags, saveFeatureFlags, toggleFeatureFlag, FEATURE_FLAG_INFO, } from '../core/preferences.js';
13
14
  import { getLearningSummary, getRecentLearning, commitLearning, exportAllLearning, getLearningDir, } from '../core/learningPersistence.js';
14
15
  import { buildEnabledToolSet, evaluateToolPermissions, getToolToggleOptions, } from '../capabilities/toolRegistry.js';
15
- import { FlowOrchestrator } from '../runtime/flowOrchestrator.js';
16
16
  import { detectApiKeyError } from '../core/errors/apiKeyErrors.js';
17
17
  import { detectPromptBlockError, } from '../core/errors/promptBlockErrors.js';
18
18
  import { detectNetworkError } from '../core/errors/networkErrors.js';
19
19
  import { buildWorkspaceContext } from '../workspace.js';
20
20
  import { buildInteractiveSystemPrompt } from './systemPrompt.js';
21
- import { getTaskCompletionDetector, resetTaskCompletionDetector, WRITE_TOOLS, } from './taskCompletionDetector.js';
21
+ import { WRITE_TOOLS } from './taskCompletionDetector.js';
22
22
  import { discoverAllModels, quickCheckProviders, getCachedDiscoveredModels, sortModelsByPriority } from '../core/modelDiscovery.js';
23
23
  import { getModels, getSlashCommands, getProviders } from '../core/agentSchemaLoader.js';
24
24
  import { loadMcpServers } from '../mcp/config.js';
@@ -124,7 +124,6 @@ export class InteractiveShell {
124
124
  uiUpdates;
125
125
  _fileChangeTracker = new FileChangeTracker(); // Reserved for future file tracking features
126
126
  alphaZeroMetrics; // Alpha Zero 2 performance tracking
127
- flowOrchestrator = new FlowOrchestrator();
128
127
  maxNetworkRetries = 2;
129
128
  statusSubscription = null;
130
129
  followUpQueue = [];
@@ -2276,14 +2275,17 @@ export class InteractiveShell {
2276
2275
  // Check for continuous/infinite loop commands or auto-escalation to completion mode
2277
2276
  const explicitContinuous = this.isContinuousCommand(trimmed);
2278
2277
  const autoContinuous = this.shouldAutoRunToCompletion(trimmed);
2279
- if (explicitContinuous || autoContinuous) {
2280
- if (autoContinuous && !explicitContinuous) {
2281
- display.showSystemMessage('⚡ Actionable request detected; running continuously until complete (Ctrl+C to stop).');
2282
- }
2278
+ if (explicitContinuous) {
2283
2279
  await this.processContinuousRequest(trimmed);
2284
2280
  this.syncRendererInput();
2285
2281
  return;
2286
2282
  }
2283
+ if (autoContinuous) {
2284
+ display.showSystemMessage('⚡ Actionable request detected; orchestrating until complete (Ctrl+C to stop).');
2285
+ await this.processRequest(trimmed, { orchestrate: true });
2286
+ this.syncRendererInput();
2287
+ return;
2288
+ }
2287
2289
  // Direct execution for all inputs, including multi-line pastes
2288
2290
  await this.processRequest(trimmed);
2289
2291
  this.syncRendererInput();
@@ -2358,7 +2360,7 @@ export class InteractiveShell {
2358
2360
  return false;
2359
2361
  }
2360
2362
  const mutatingToolUsed = toolsUsed.some((tool) => WRITE_TOOL_NAMES.has(tool.toLowerCase()));
2361
- const planOnly = this.flowOrchestrator.isPlanOnlyResponse(response);
2363
+ const planOnly = this.isPlanOnlyResponse(response);
2362
2364
  const lowActionDensity = response.split(/\s+/).length < 80;
2363
2365
  // If it's clearly a plan-only response, continue regardless of prior tool usage
2364
2366
  if (planOnly) {
@@ -5832,7 +5834,7 @@ Execute the plan you outlined. Use the available tools (bash, edits, git) to tak
5832
5834
  this.clearInlinePanel();
5833
5835
  this.syncRendererInput();
5834
5836
  }
5835
- async processRequest(request) {
5837
+ async processRequest(request, options) {
5836
5838
  if (this.isProcessing) {
5837
5839
  this.enqueueFollowUpAction({ type: 'request', text: request });
5838
5840
  return;
@@ -5884,10 +5886,23 @@ Execute the plan you outlined. Use the available tools (bash, edits, git) to tak
5884
5886
  this.beginAiRuntime();
5885
5887
  let responseText = '';
5886
5888
  let autoFollowThrough = null;
5889
+ let orchestratorResult = null;
5890
+ const orchestrate = options?.orchestrate ?? false;
5887
5891
  try {
5888
5892
  // Start streaming - no header needed, the input area already provides context
5889
5893
  this.startStreamingHeartbeat('Streaming response');
5890
- responseText = await agent.send(request, true);
5894
+ if (orchestrate) {
5895
+ const orchestrator = new AgentOrchestrator(agent);
5896
+ orchestratorResult = await orchestrator.runToCompletion(request, {
5897
+ streaming: true,
5898
+ maxPasses: options?.maxPasses ?? 4,
5899
+ enforceActions: true,
5900
+ });
5901
+ responseText = orchestratorResult.finalResponse;
5902
+ }
5903
+ else {
5904
+ responseText = await agent.send(request, true);
5905
+ }
5891
5906
  this.finishStreamingFormatter(undefined, { refreshPrompt: false, mode: 'complete' });
5892
5907
  await this.awaitPendingCleanup();
5893
5908
  this.captureHistorySnapshot();
@@ -5899,14 +5914,24 @@ Execute the plan you outlined. Use the available tools (bash, edits, git) to tak
5899
5914
  display.showWarning('The provider returned an empty response. Check your API key/provider selection or retry the prompt.');
5900
5915
  }
5901
5916
  // AlphaZero: Extract and track tool calls from response
5902
- const toolsUsed = this.getExecutedTools(responseText);
5917
+ const toolsUsed = orchestratorResult
5918
+ ? orchestratorResult.passes.flatMap(pass => pass.toolsUsed)
5919
+ : this.getExecutedTools(responseText);
5903
5920
  this.currentToolCalls = toolsUsed.map(name => ({
5904
5921
  name,
5905
5922
  arguments: {},
5906
5923
  success: true, // Assume success if we got here
5907
5924
  duration: 0,
5908
5925
  }));
5909
- autoFollowThrough = this.maybeAutoFollowThrough(request, responseText, toolsUsed);
5926
+ if (!orchestrate) {
5927
+ autoFollowThrough = this.maybeAutoFollowThrough(request, responseText, toolsUsed);
5928
+ }
5929
+ if (orchestratorResult && orchestratorResult.exitReason !== 'complete') {
5930
+ const exitDetail = orchestratorResult.exitReason === 'max-passes'
5931
+ ? 'Reached orchestrator pass limit; showing last response.'
5932
+ : 'Received empty replies while orchestrating; showing last response.';
5933
+ display.showSystemMessage(`⚠️ ${exitDetail}`);
5934
+ }
5910
5935
  // AlphaZero: Check for failure in response
5911
5936
  const failure = detectFailure(responseText, {
5912
5937
  toolCalls: this.currentToolCalls,
@@ -5938,7 +5963,7 @@ Execute the plan you outlined. Use the available tools (bash, edits, git) to tak
5938
5963
  }
5939
5964
  }
5940
5965
  catch (error) {
5941
- const handled = this.handleProviderError(error, () => this.processRequest(request));
5966
+ const handled = this.handleProviderError(error, () => this.processRequest(request, options));
5942
5967
  if (!handled) {
5943
5968
  // Pass full error object for enhanced formatting with stack trace
5944
5969
  display.showError(error instanceof Error ? error.message : String(error), error);
@@ -5992,7 +6017,7 @@ Execute the plan you outlined. Use the available tools (bash, edits, git) to tak
5992
6017
  * Context is automatically managed - overflow errors trigger auto-recovery.
5993
6018
  */
5994
6019
  async processContinuousRequest(initialRequest) {
5995
- const MAX_ITERATIONS = 100; // Safety limit to prevent truly infinite loops
6020
+ const MAX_PASSES = 100; // Safety limit to prevent truly infinite loops
5996
6021
  if (this.isProcessing) {
5997
6022
  this.enqueueFollowUpAction({ type: 'continuous', text: initialRequest });
5998
6023
  return;
@@ -6001,49 +6026,9 @@ Execute the plan you outlined. Use the available tools (bash, edits, git) to tak
6001
6026
  display.showWarning('Configure an API key via /secrets before sending requests.');
6002
6027
  return;
6003
6028
  }
6004
- this.inlinePanelScopeActive = false;
6005
- this.clearInlinePanel();
6006
- const agent = this.agent;
6007
- if (!agent) {
6008
- return;
6009
- }
6010
- this.toolsUsedThisRun = [];
6011
- this.currentToolCalls = [];
6012
- this.lastUserQuery = initialRequest;
6013
- this.clearToolUsageMeta();
6014
- this.isProcessing = true;
6015
- this.uiUpdates.setMode('processing');
6016
- this.streamingTokenCount = 0; // Reset token counter for new request
6017
- this.terminalInput.setStreaming(true);
6018
- if (this.suppressNextNetworkReset) {
6019
- this.suppressNextNetworkReset = false;
6020
- }
6021
- else {
6022
- this.resetNetworkRetryState();
6023
- }
6024
- const overallStartTime = Date.now();
6025
- // Clear previous parallel agents and start fresh for continuous mode
6026
- const parallelManager = getParallelAgentManager();
6027
- parallelManager.clear();
6028
- parallelManager.startBatch();
6029
- // Initialize the task completion detector
6030
- const completionDetector = getTaskCompletionDetector();
6031
- completionDetector.reset();
6032
- // Display user prompt in scrollback (Claude Code style)
6033
- this.logUserPrompt(initialRequest);
6034
6029
  display.showSystemMessage(`Continuous mode active. Ctrl+C to stop.`);
6035
- this.uiAdapter.startProcessing('Continuous execution mode');
6036
- this.setProcessingStatus();
6037
- this.beginAiRuntime();
6038
- // No streaming header - just start streaming directly
6039
- this.startStreamingHeartbeat('Streaming');
6040
- this.flowOrchestrator.start(initialRequest);
6041
- let iteration = 0;
6042
- try {
6043
- // Enhance initial prompt with git context for self-improvement tasks
6044
- let currentPrompt = initialRequest;
6045
- if (this.isSelfImprovementRequest(initialRequest)) {
6046
- currentPrompt = `${initialRequest}
6030
+ const preparedRequest = this.isSelfImprovementRequest(initialRequest)
6031
+ ? `${initialRequest}
6047
6032
 
6048
6033
  IMPORTANT: You have full git access. After making improvements:
6049
6034
  1. Use bash to run: git status (see changes)
@@ -6052,124 +6037,9 @@ IMPORTANT: You have full git access. After making improvements:
6052
6037
  4. Use bash to run: git push (when milestone reached)
6053
6038
 
6054
6039
  Commit frequently with descriptive messages. Push when ready.
6055
- When truly finished with ALL tasks, explicitly state "TASK_FULLY_COMPLETE".`;
6056
- }
6057
- while (iteration < MAX_ITERATIONS) {
6058
- iteration++;
6059
- this.toolsUsedThisRun = [];
6060
- display.showSystemMessage(`\n📍 Iteration ${iteration}/${MAX_ITERATIONS}`);
6061
- this.updateStatusMessage(`Working on iteration ${iteration}...`);
6062
- try {
6063
- // Send the request and capture the response (streaming disabled)
6064
- display.showThinking('Responding...');
6065
- this.refreshStatusLine(true);
6066
- const response = await agent.send(currentPrompt, true);
6067
- this.finishStreamingFormatter(undefined, { refreshPrompt: false, mode: 'complete' });
6068
- await this.awaitPendingCleanup();
6069
- this.captureHistorySnapshot();
6070
- this.autosaveIfEnabled();
6071
- // Track metrics
6072
- const elapsedMs = Date.now() - overallStartTime;
6073
- this.alphaZeroMetrics.recordMessage(elapsedMs);
6074
- if (!response?.trim()) {
6075
- display.showWarning('Model returned an empty response. Retrying this iteration...');
6076
- currentPrompt = `${initialRequest}
6077
-
6078
- The previous reply was empty. Resume the task now: take the next action, call the necessary tools, and report progress.`;
6079
- continue;
6080
- }
6081
- // Extract tools used from the response (look for tool call patterns)
6082
- const toolsUsed = this.getExecutedTools(response);
6083
- toolsUsed.forEach(tool => completionDetector.recordToolCall(tool, true, true));
6084
- // Use intelligent completion detection
6085
- const completionAnalysis = completionDetector.analyzeCompletion(response, toolsUsed);
6086
- display.showSystemMessage(`📈 Completion confidence: ${(completionAnalysis.confidence * 100).toFixed(0)}%`);
6087
- const decision = this.flowOrchestrator.decide({
6088
- iteration,
6089
- response,
6090
- toolsUsed,
6091
- completionAnalysis,
6092
- verificationConfirmed: this.flowOrchestrator.isVerificationPending()
6093
- ? completionDetector.isVerificationConfirmed(response)
6094
- : false,
6095
- });
6096
- if (decision.type === 'stop') {
6097
- display.showSystemMessage(decision.message);
6098
- break;
6099
- }
6100
- if (decision.type === 'stagnation-stop') {
6101
- display.showWarning(decision.message);
6102
- break;
6103
- }
6104
- if (decision.type === 'execute-plan') {
6105
- display.showSystemMessage(decision.message);
6106
- currentPrompt = decision.prompt;
6107
- await new Promise(resolve => setTimeout(resolve, 500));
6108
- continue;
6109
- }
6110
- if (decision.type === 'verify') {
6111
- display.showSystemMessage(decision.message);
6112
- currentPrompt = decision.prompt;
6113
- await new Promise(resolve => setTimeout(resolve, 500));
6114
- continue;
6115
- }
6116
- if (decision.type === 'continue') {
6117
- if (decision.message) {
6118
- display.showSystemMessage(decision.message);
6119
- }
6120
- currentPrompt = decision.prompt;
6121
- }
6122
- // Small delay between iterations to prevent rate limiting
6123
- await new Promise(resolve => setTimeout(resolve, 500));
6124
- }
6125
- catch (error) {
6126
- display.stopThinking(false);
6127
- // Handle context overflow specially - the agent should auto-recover
6128
- // but if it propagates here, we continue the loop
6129
- if (this.isContextOverflowError(error)) {
6130
- display.showSystemMessage(`⚡ Context overflow handled. Continuing with reduced context...`);
6131
- // The agent.ts should have already handled recovery
6132
- // Continue to next iteration
6133
- continue;
6134
- }
6135
- // For other errors, check if handled by provider error handler
6136
- const handled = this.handleProviderError(error, () => this.processContinuousRequest(initialRequest));
6137
- if (!handled) {
6138
- display.showError(error instanceof Error ? error.message : String(error), error);
6139
- break;
6140
- }
6141
- }
6142
- }
6143
- if (iteration >= MAX_ITERATIONS) {
6144
- display.showWarning(`\n⚠️ Reached maximum iterations (${MAX_ITERATIONS}). Stopping to prevent infinite loop.`);
6145
- }
6146
- }
6147
- finally {
6148
- this.finishStreamingFormatter(undefined, { refreshPrompt: false, mode: 'complete' });
6149
- const totalElapsed = Date.now() - overallStartTime;
6150
- const minutes = Math.floor(totalElapsed / 60000);
6151
- const seconds = Math.floor((totalElapsed % 60000) / 1000);
6152
- display.showSystemMessage(`\n🏁 Continuous execution completed: ${iteration} iterations, ${minutes}m ${seconds}s total`);
6153
- // Reset completion detector for next task
6154
- resetTaskCompletionDetector();
6155
- this.uiUpdates.setMode('processing');
6156
- this.stopStreamingHeartbeat('complete', { quiet: true });
6157
- this.endAiRuntime();
6158
- this.isProcessing = false;
6159
- this.terminalInput.setStreaming(false);
6160
- this.uiAdapter.endProcessing('Ready for prompts');
6161
- this.updateToolUsageMeta(this.uiAdapter.getToolUsageSummary({ plain: true }));
6162
- this.setIdleStatus();
6163
- this.updateStatusMessage(null);
6164
- this.toolsUsedThisRun = [];
6165
- queueMicrotask(() => this.uiUpdates.setMode('idle'));
6166
- // CRITICAL: Ensure readline prompt is active for user input
6167
- // Erosolar-CLI style: New prompt naturally appears at bottom
6168
- this.ensureReadlineReady();
6169
- this.scheduleQueueProcessing();
6170
- this.maybeProcessPromptInbox();
6171
- this.refreshQueueIndicators();
6172
- }
6040
+ When truly finished with ALL tasks, explicitly state "TASK_FULLY_COMPLETE".`
6041
+ : initialRequest;
6042
+ await this.processRequest(preparedRequest, { orchestrate: true, maxPasses: MAX_PASSES });
6173
6043
  }
6174
6044
  /**
6175
6045
  * Resolve executed tools for the current turn. Prefer the actual tool
@@ -6191,6 +6061,129 @@ The previous reply was empty. Resume the task now: take the next action, call th
6191
6061
  }
6192
6062
  return this.extractToolsFromResponse(responseText);
6193
6063
  }
6064
+ /**
6065
+ * Detect plan-only responses that narrate intent without executing actions.
6066
+ */
6067
+ isPlanOnlyResponse(response) {
6068
+ const normalized = response.trim().toLowerCase();
6069
+ if (!normalized) {
6070
+ return false;
6071
+ }
6072
+ // If the assistant is clearly declaring completion, don't treat it as plan-only
6073
+ const completionGuards = [
6074
+ /\bnothing\s+(left|else)\s+(to\s+do|pending)\b/i,
6075
+ /\b(already|now)\s+(clean|complete|done)\b/i,
6076
+ /\b(no\s+(junk|issues?|changes?)\s+found)\b/i,
6077
+ ];
6078
+ if (completionGuards.some((pattern) => pattern.test(response))) {
6079
+ return false;
6080
+ }
6081
+ const planIndicators = [
6082
+ /\bplan\b/i,
6083
+ /\bapproach\b/i,
6084
+ /\bsteps?:\b/i,
6085
+ /\bstep\s+1\b/i,
6086
+ /\bstart by\b/i,
6087
+ /\bfirst[, ]/i,
6088
+ /\bthen\b/i,
6089
+ /\bnext\b/i,
6090
+ /\bi['’]?\s*will\b/i,
6091
+ /\bi['’]?\s*ll\b/i,
6092
+ /\bi['’]?\s*can\b.{0,40}\bthen\b/i,
6093
+ /\bi['’]?\s*(?:will|ll)\s+begin\b/i,
6094
+ ];
6095
+ return planIndicators.some((pattern) => pattern.test(response));
6096
+ }
6097
+ /**
6098
+ * Check if a response contains indicators that work is actually incomplete,
6099
+ * even if it also contains TASK_FULLY_COMPLETE marker.
6100
+ * This catches contradictory responses where the AI says "done" but also "not integrated yet".
6101
+ */
6102
+ responseIndicatesIncompleteWork(response) {
6103
+ // Patterns that indicate work isn't actually complete
6104
+ // Organized by category for maintainability
6105
+ const incompletePatterns = [
6106
+ // === INTEGRATION/DEPLOYMENT STATE ===
6107
+ // "hasn't been integrated/implemented/connected yet"
6108
+ /hasn'?t\s+been\s+(integrated|implemented|connected|deployed|added|completed|tested|verified)\s*(yet|still)?/i,
6109
+ // "not yet integrated/implemented" or "not integrated"
6110
+ /not\s+(yet\s+)?(integrated|implemented|connected|deployed|functional|working|complete|tested|verified)/i,
6111
+ // "ready for integration" = NOT integrated
6112
+ /ready\s+(for|to\s+be)\s+(integration|integrated|connected|deployed|testing|review)/i,
6113
+ // "needs to be integrated"
6114
+ /needs?\s+to\s+be\s+(integrated|connected|deployed|added|hooked|wired|tested|reviewed|merged)/i,
6115
+ // Passive voice: "was not performed/completed"
6116
+ /was\s+not\s+(performed|completed|implemented|deployed|integrated|tested)/i,
6117
+ // "the [X] service hasn't been"
6118
+ /the\s+\w+\s+(service|module|component|feature)\s+hasn'?t\s+been/i,
6119
+ // === PARTIAL/INCOMPLETE STATE ===
6120
+ // "still stores/uses/has" (current bad state persists)
6121
+ /still\s+(stores?|uses?|has|contains?|needs?|requires?|missing|lacks?|broken)/i,
6122
+ // Partial completion: "partially", "mostly", "almost"
6123
+ /\b(partially|mostly|almost|nearly|not\s+fully)\s+(complete|done|finished|implemented|working)/i,
6124
+ // Explicit partial: "part of", "some of", "half of"
6125
+ /\b(only\s+)?(part|some|half|portion)\s+of\s+(the\s+)?(task|work|feature|implementation)/i,
6126
+ // === QUALIFIER WORDS (uncertain completion) ===
6127
+ // "should be complete", "appears complete", "theoretically"
6128
+ /\b(should|might|may|could|appears?\s+to)\s+be\s+(complete|done|working|functional)/i,
6129
+ /\btheoretically\s+(complete|done|working|functional)/i,
6130
+ // "assuming", "if everything works"
6131
+ /\b(assuming|provided|if)\s+(everything|it|this|that)\s+(works?|is\s+correct)/i,
6132
+ // === SELF-CONTRADICTION PHRASES ===
6133
+ // "done but...", "complete except...", "finished however..."
6134
+ /\b(done|complete|finished)\s+(but|except|however|although|though)/i,
6135
+ // "however" followed by incomplete indicator
6136
+ /however[,\s].{0,50}?(hasn'?t|not\s+yet|still\s+needs?|pending|remains?|missing|broken|failing)/i,
6137
+ // "but" followed by negative state
6138
+ /\bbut\s+.{0,30}?(not|hasn'?t|won'?t|can'?t|doesn'?t|isn'?t|wasn'?t)/i,
6139
+ // === FUTURE TENSE / DEFERRED WORK ===
6140
+ // "will need to", "will require"
6141
+ /will\s+(need\s+to|require|have\s+to)\s+(integrate|connect|deploy|complete|implement|test|fix)/i,
6142
+ // Deferred: "left as", "deferred", "postponed", "out of scope"
6143
+ /\b(left\s+as|deferred|postponed|out\s+of\s+scope|for\s+later|in\s+a\s+future)/i,
6144
+ // Time-dependent: "after restart", "takes effect after", "once you"
6145
+ /\b(after\s+(restart|reboot|redeploy)|takes?\s+effect\s+after|once\s+you)/i,
6146
+ // === REMAINING WORK INDICATORS ===
6147
+ // "remaining tasks", "outstanding items"
6148
+ /\b(remaining|outstanding|pending|leftover)\s+(tasks?|items?|work|issues?|steps?)/i,
6149
+ // "X more to do", "still have to"
6150
+ /\b(more\s+to\s+do|still\s+have\s+to|yet\s+to\s+be\s+done)/i,
6151
+ // Explicit blockers
6152
+ /\b(blocker|blocked\s+by|waiting\s+(for|on)|depends?\s+on)/i,
6153
+ // === ERROR/FAILURE STATE ===
6154
+ // "failing tests", "build errors"
6155
+ /\b(failing|broken|erroring)\s+(tests?|builds?|checks?|validations?)/i,
6156
+ // "tests? (are )?(still )?failing"
6157
+ /\btests?\s+(are\s+)?(still\s+)?failing/i,
6158
+ // "errors? to (address|fix)"
6159
+ /\b(errors?|warnings?|issues?)\s+to\s+(address|fix|resolve)/i,
6160
+ // "doesn't work", "isn't working", "not working"
6161
+ /\b(doesn'?t|isn'?t|not)\s+(work|working|functional|functioning)/i,
6162
+ // === MANUAL STEPS REQUIRED ===
6163
+ // "you'll need to", "manually run", "requires user"
6164
+ /\b(you('ll|\s+will)\s+need\s+to|manually\s+(run|configure|set|update)|requires?\s+user)/i,
6165
+ // "run this command", "execute the following"
6166
+ /\b(run\s+this|execute\s+the\s+following|apply\s+the\s+migration)/i,
6167
+ // === TODO/FIXME IN PROSE ===
6168
+ // TODO or FIXME mentioned as remaining work (not in code blocks)
6169
+ /\b(todo|fixme|hack|xxx):\s/i,
6170
+ // "need to add", "should implement"
6171
+ /\b(need\s+to|should|must)\s+(add|implement|create|write|build|fix)\b/i,
6172
+ // === SCOPE LIMITATIONS ===
6173
+ // "didn't have time", "ran out of time"
6174
+ /\b(didn'?t|did\s+not)\s+have\s+(time|chance|opportunity)/i,
6175
+ // "beyond scope", "outside scope"
6176
+ /\b(beyond|outside)\s+(the\s+)?scope/i,
6177
+ // "for now" (temporary state)
6178
+ /\b(for\s+now|at\s+this\s+point|currently)\s*.{0,20}?(not|without|lacks?|missing)/i,
6179
+ ];
6180
+ for (const pattern of incompletePatterns) {
6181
+ if (pattern.test(response)) {
6182
+ return true;
6183
+ }
6184
+ }
6185
+ return false;
6186
+ }
6194
6187
  /**
6195
6188
  * Extract tool names from a response by looking for tool call patterns
6196
6189
  */