daedalus-cli 1.83.6 → 1.83.8

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 (53) hide show
  1. package/AGENTS.md +31 -10
  2. package/CHANGELOG.md +14 -0
  3. package/README.md +3 -3
  4. package/dist/agents/ensemble.d.ts.map +1 -1
  5. package/dist/agents/ensemble.js +2 -2
  6. package/dist/agents/ensemble.js.map +1 -1
  7. package/dist/agents/orchestrator-types.d.ts +20 -0
  8. package/dist/agents/orchestrator-types.d.ts.map +1 -0
  9. package/dist/agents/orchestrator-types.js +3 -0
  10. package/dist/agents/orchestrator-types.js.map +1 -0
  11. package/dist/agents/orchestrator-validation.d.ts +19 -0
  12. package/dist/agents/orchestrator-validation.d.ts.map +1 -0
  13. package/dist/agents/orchestrator-validation.js +227 -0
  14. package/dist/agents/orchestrator-validation.js.map +1 -0
  15. package/dist/agents/orchestrator-verification.d.ts +28 -0
  16. package/dist/agents/orchestrator-verification.d.ts.map +1 -0
  17. package/dist/agents/orchestrator-verification.js +355 -0
  18. package/dist/agents/orchestrator-verification.js.map +1 -0
  19. package/dist/agents/orchestrator.d.ts +1 -49
  20. package/dist/agents/orchestrator.d.ts.map +1 -1
  21. package/dist/agents/orchestrator.js +40 -678
  22. package/dist/agents/orchestrator.js.map +1 -1
  23. package/dist/agents/orchestrator.test.js +33 -81
  24. package/dist/agents/orchestrator.test.js.map +1 -1
  25. package/dist/commands/agents.d.ts +3 -0
  26. package/dist/commands/agents.d.ts.map +1 -0
  27. package/dist/commands/agents.js +886 -0
  28. package/dist/commands/agents.js.map +1 -0
  29. package/dist/commands/context.d.ts +3 -0
  30. package/dist/commands/context.d.ts.map +1 -0
  31. package/dist/commands/context.js +875 -0
  32. package/dist/commands/context.js.map +1 -0
  33. package/dist/commands/dev.d.ts +3 -0
  34. package/dist/commands/dev.d.ts.map +1 -0
  35. package/dist/commands/dev.js +820 -0
  36. package/dist/commands/dev.js.map +1 -0
  37. package/dist/commands/index.d.ts +5 -0
  38. package/dist/commands/index.d.ts.map +1 -0
  39. package/dist/commands/index.js +88 -0
  40. package/dist/commands/index.js.map +1 -0
  41. package/dist/commands/types.d.ts +45 -0
  42. package/dist/commands/types.d.ts.map +1 -0
  43. package/dist/commands/types.js +2 -0
  44. package/dist/commands/types.js.map +1 -0
  45. package/dist/commands.d.ts +2 -46
  46. package/dist/commands.d.ts.map +1 -1
  47. package/dist/commands.js +1 -2639
  48. package/dist/commands.js.map +1 -1
  49. package/dist/config/index.d.ts +78 -78
  50. package/dist/model.d.ts.map +1 -1
  51. package/dist/model.js +11 -6
  52. package/dist/model.js.map +1 -1
  53. package/package.json +1 -1
@@ -1,7 +1,6 @@
1
1
  // Multi-agent orchestrator - coordinates delegation and synthesis
2
2
  import fs from 'fs';
3
3
  import path from 'path';
4
- import os from 'os';
5
4
  import readline from 'readline';
6
5
  import { BUILTIN_TOOLS } from '../tools/definitions.js';
7
6
  import { executeToolCalls } from '../tools/executor.js';
@@ -9,10 +8,10 @@ import { getAgentRole, filterToolsForRole } from './roles.js';
9
8
  import pc from 'picocolors';
10
9
  import { DaedalusSpinner } from '../tools/daedalus-spinner.js';
11
10
  import { parseTextToolCalls } from '../formatting.js';
11
+ import { filterValidTasks, validateTasks, cleanTaskText, cleanPlanOutput, truncateGoal, extractFilePaths, buildDependencyGraph, groupIndependent, isUnnecessaryConfigTask, extractRequirements, getFrameworkGuidance, } from './orchestrator-validation.js';
12
+ import { isDeclaredError, verifyArtifacts, verifyArtifactsThoroughly, checkPlaceholders, fillPlaceholders, buildCleanSummary, isBuildErrorRelated, generateBuildErrorHint, runBuildVerification, attemptRepair, rollbackTaskPatches, } from './orchestrator-verification.js';
12
13
  // Simplified placeholder regexes for common auto-fill tokens only
13
- const PLACEHOLDER_RE = /\[(?:YEAR|Year|year|YYYY|yyyy|DATE|Date|date|TODAY|Today|today|YOUR\s+NAME|Your\s+Name|your\s+name|FULLNAME|Fullname|fullname|AUTHOR|Author|author|USERNAME|Username|username|OWNER|Owner|owner)\]/i;
14
14
  // Simplified HTML placeholder regex for same tokens inside comments
15
- const HTML_PLACEHOLDER_RE = /<!--[^>]*?(?:YEAR|Year|year|DATE|Date|date|YOUR\s+NAME|Your\s+Name|your\s+name)\s*-->/i;
16
15
  export class Orchestrator {
17
16
  router;
18
17
  messages;
@@ -111,13 +110,13 @@ export class Orchestrator {
111
110
  if (tasks.length > this.MAX_INITIAL_TASKS) {
112
111
  console.log(pc.yellow(`\nPlan has ${tasks.length} steps (max ${this.MAX_INITIAL_TASKS}). Asking planner to simplify...`));
113
112
  plan = await this.createPlan(goal, projectContext, `Simplify to at most ${this.MAX_INITIAL_TASKS} focused steps. Merge related steps. Each step must produce real output.`);
114
- tasks = Orchestrator.filterValidTasks(this.parseDelegationTasks(plan, goal));
113
+ tasks = filterValidTasks(this.parseDelegationTasks(plan, goal));
115
114
  if (tasks.length > this.MAX_INITIAL_TASKS) {
116
115
  tasks = tasks.slice(0, this.MAX_INITIAL_TASKS);
117
116
  }
118
117
  }
119
118
  else {
120
- tasks = Orchestrator.filterValidTasks(tasks);
119
+ tasks = filterValidTasks(tasks);
121
120
  }
122
121
  if (this.sessionManager) {
123
122
  this.sessionManager.saveState('orchestrate_plan', tasks);
@@ -204,7 +203,7 @@ export class Orchestrator {
204
203
  const simplifyBlock = simplifyHint ? `\n\n${simplifyHint}` : '';
205
204
  const messages = [
206
205
  { role: 'system', content: systemPrompt + (attempts > 1 ? `\n\nIMPORTANT: You MUST create a valid plan. Each subtask needs an explicit file path and concrete wording.${retryHint}` : '') + simplifyBlock },
207
- { role: 'user', content: `Create a step-by-step plan with one subtask per file for: ${goal}\n\nProject context:\n${projectContext || '(none discovered)'}${Orchestrator.getFrameworkGuidance(projectContext, this.toolContext.projectRoot)}\n\n${this.toolContext.activeFiles.size > 0 ? 'Files in context: ' + Array.from(this.toolContext.activeFiles.values()).join(', ') : ''}\n\nRemember: one subtask per file, include the exact file path in each subtask, order by dependencies.` },
206
+ { role: 'user', content: `Create a step-by-step plan with one subtask per file for: ${goal}\n\nProject context:\n${projectContext || '(none discovered)'}${getFrameworkGuidance(projectContext, this.toolContext.projectRoot)}\n\n${this.toolContext.activeFiles.size > 0 ? 'Files in context: ' + Array.from(this.toolContext.activeFiles.values()).join(', ') : ''}\n\nRemember: one subtask per file, include the exact file path in each subtask, order by dependencies.` },
208
207
  ];
209
208
  const planSpinner = new DaedalusSpinner({ text: `planner generating plan`, color: (s) => pc.blue(s) });
210
209
  planSpinner.start();
@@ -268,7 +267,7 @@ export class Orchestrator {
268
267
  }
269
268
  // Validate the plan
270
269
  const testTasks = this.parseDelegationTasks(planText || `- delegate to coder: ${goal}`, goal);
271
- const validationError = Orchestrator.validateTasks(testTasks, goal, this.toolContext.projectRoot);
270
+ const validationError = validateTasks(testTasks, goal, this.toolContext.projectRoot);
272
271
  if (!validationError) {
273
272
  return planText || `- delegate to coder: ${goal}`;
274
273
  }
@@ -396,35 +395,6 @@ export class Orchestrator {
396
395
  }
397
396
  return batch;
398
397
  }
399
- static getTaskFilePaths(task) {
400
- return Orchestrator.extractFilePaths(task.goal);
401
- }
402
- static hasFileConflict(a, b) {
403
- const aPaths = Orchestrator.getTaskFilePaths(a);
404
- const bPaths = Orchestrator.getTaskFilePaths(b);
405
- // If either has no detected paths, assume conflict (conservative)
406
- if (aPaths.length === 0 || bPaths.length === 0)
407
- return true;
408
- return aPaths.some(ap => bPaths.includes(ap));
409
- }
410
- static groupIndependent(tasks) {
411
- const groups = [];
412
- for (const t of tasks) {
413
- let placed = false;
414
- for (const group of groups) {
415
- const noConflict = group.every(g => !Orchestrator.hasFileConflict(g, t));
416
- if (noConflict) {
417
- group.push(t);
418
- placed = true;
419
- break;
420
- }
421
- }
422
- if (!placed) {
423
- groups.push([t]);
424
- }
425
- }
426
- return groups;
427
- }
428
398
  async executeSingleTask(task, tasks, originalGoal, projectContext) {
429
399
  task.status = 'in_progress';
430
400
  this.printTaskList(tasks);
@@ -506,7 +476,7 @@ export class Orchestrator {
506
476
  async executePlan(plan, tasks, startIndex = 0, originalGoal, projectContext) {
507
477
  let lastReplanCount = 0;
508
478
  // Build dependency graph from file paths
509
- Orchestrator.buildDependencyGraph(tasks);
479
+ buildDependencyGraph(tasks);
510
480
  for (let i = startIndex; i < tasks.length; /* increment inside */) {
511
481
  if (this.toolContext.abortSignal.aborted) {
512
482
  break;
@@ -530,7 +500,7 @@ export class Orchestrator {
530
500
  continue;
531
501
  }
532
502
  // Skip unnecessary config tasks for file-based routing frameworks
533
- if (Orchestrator.isUnnecessaryConfigTask(task, projectContext)) {
503
+ if (isUnnecessaryConfigTask(task, projectContext)) {
534
504
  console.log(pc.yellow(`\nSkipping task ${i + 1}: Next.js uses file-based routing — no config changes needed`));
535
505
  task.status = 'skipped';
536
506
  task.error = 'Unnecessary config task for file-based routing framework';
@@ -546,7 +516,7 @@ export class Orchestrator {
546
516
  }
547
517
  // In auto-approve mode, run independent tasks concurrently
548
518
  if (process.env.DAEDALUS_AUTO_APPROVE === 'true') {
549
- const groups = Orchestrator.groupIndependent(batch);
519
+ const groups = groupIndependent(batch);
550
520
  for (const group of groups) {
551
521
  await Promise.all(group.map(t => this.executeSingleTask(t, tasks, originalGoal, projectContext)));
552
522
  }
@@ -574,7 +544,7 @@ export class Orchestrator {
574
544
  lastReplanCount = completedCount;
575
545
  await this.replanRemaining(tasks, originalGoal, projectContext);
576
546
  // Rebuild dependency graph after replan
577
- Orchestrator.buildDependencyGraph(tasks);
547
+ buildDependencyGraph(tasks);
578
548
  }
579
549
  }
580
550
  // Interactive checkpoint: ask user before next task
@@ -633,7 +603,7 @@ export class Orchestrator {
633
603
  const r = this.results.find(rr => rr.goal === t.goal && rr.role === t.role);
634
604
  if (!r)
635
605
  return [];
636
- const paths = Orchestrator.extractFilePaths(r.summary);
606
+ const paths = extractFilePaths(r.summary);
637
607
  return paths;
638
608
  });
639
609
  // Save original pending tasks as fallback in case replan fails
@@ -653,19 +623,19 @@ export class Orchestrator {
653
623
  newTasks = newTasks.filter(nt => {
654
624
  if (done.length === 0 || nt.role !== 'coder')
655
625
  return true;
656
- const newPaths = Orchestrator.extractFilePaths(nt.goal).map(p => p.toLowerCase());
626
+ const newPaths = extractFilePaths(nt.goal).map(p => p.toLowerCase());
657
627
  if (newPaths.length === 0)
658
628
  return true;
659
629
  const keep = !done.some(d => {
660
630
  if (d.role !== 'coder')
661
631
  return false;
662
- const donePaths = Orchestrator.extractFilePaths(d.goal).map(p => p.toLowerCase());
632
+ const donePaths = extractFilePaths(d.goal).map(p => p.toLowerCase());
663
633
  return donePaths.some(dp => newPaths.includes(dp));
664
634
  });
665
635
  return keep;
666
636
  });
667
637
  // Enforce task cap and filter out non-actionable tasks
668
- newTasks = Orchestrator.filterValidTasks(newTasks).slice(0, this.MAX_INITIAL_TASKS);
638
+ newTasks = filterValidTasks(newTasks).slice(0, this.MAX_INITIAL_TASKS);
669
639
  // Fallback: if replan produced no valid tasks, restore original pending tasks
670
640
  if (newTasks.length === 0 && originalPending.length > 0) {
671
641
  for (const t of originalPending) {
@@ -681,138 +651,14 @@ export class Orchestrator {
681
651
  }
682
652
  this.printTaskList(tasks);
683
653
  }
684
- static filterValidTasks(tasks) {
685
- const doneRe = /\b(open|launch|start|run|execute)\b.*\b(file|editor|IDE|app|application|browser|window)\b|\b(commit|push|pull|merge)\b|\b(researcher)\b.*\b(identify and install)\b/i;
686
- const filtered = tasks.filter(t => !doneRe.test(t.goal));
687
- const metaRe = /\b(save changes|save the file|ensure that|ensure the)\b/i;
688
- const metaSaveRe = /(?:save|write)\s+the\s+(?:changes|file)/i;
689
- const guiTestRe = /\b(cypress|playwright|puppeteer|selenium)\b/i;
690
- const fileSimpleRe = /\b(create|write|build|make|generate|add)\b.*\b(file|component|page|layout|module|function|class|route)\b/i;
691
- const pathRe = /([a-z0-9_\-./\\:]+\\.[a-z0-9]+)/i;
692
- const seen = new Map();
693
- const out = [];
694
- for (const t of filtered) {
695
- const rawGoal = t.goal;
696
- const cleanedGoal = Orchestrator.cleanTaskText(rawGoal) || rawGoal;
697
- if (doneRe.test(cleanedGoal))
698
- continue;
699
- if (metaRe.test(cleanedGoal))
700
- continue;
701
- if (metaSaveRe.test(cleanedGoal))
702
- continue;
703
- if (guiTestRe.test(cleanedGoal))
704
- continue;
705
- if (/\b(open|view|check)\b/i.test(cleanedGoal) && cleanedGoal.length < 25)
706
- continue;
707
- const lower = cleanedGoal.toLowerCase();
708
- if (t.role === 'coder') {
709
- const pathMatch = lower.match(pathRe);
710
- const key = pathMatch ? `${t.role}:${pathMatch[1]}` : `${t.role}:${lower}`;
711
- if (seen.has(key)) {
712
- const existing = seen.get(key);
713
- if (cleanedGoal.length > existing.goal.length)
714
- existing.goal = cleanedGoal;
715
- continue;
716
- }
717
- seen.set(key, t);
718
- const isSimpleFileTask = fileSimpleRe.test(lower);
719
- if (isSimpleFileTask) {
720
- const simpleFileCount = out.filter(o => {
721
- if (o.role !== 'coder')
722
- return false;
723
- const p = o.goal.toLowerCase().match(pathRe);
724
- return p && pathMatch && p[1] === pathMatch[1];
725
- }).length;
726
- if (simpleFileCount >= 2)
727
- continue;
728
- }
729
- }
730
- t.goal = cleanedGoal;
731
- out.push(t);
732
- }
733
- return out;
734
- }
735
- static stripCodeBlocks(text) {
736
- // Strip triple-backtick code fences (language identifier + code block)
737
- let result = text.replace(/```[\s\S]*?```/g, '').trim();
738
- // Unwrap inline backticks — keep the content (e.g. `src/pages/about.tsx` stays)
739
- result = result.replace(/`([^`]+)`/g, '$1').trim();
740
- return result;
741
- }
742
- static stripToolRequestArtifacts(text) {
743
- // Remove [TOOL_REQUEST]...[END_TOOL_REQUEST] blocks (including trailing JSON)
744
- let result = text.replace(/\[TOOL_REQUEST\][\s\S]*?\[END_TOOL_REQUEST\]/gi, '').trim();
745
- // Remove trailing whitespace / stray colons left behind
746
- result = result.replace(/[:\s]+$/g, '').trim();
747
- return result;
748
- }
749
- static VAGUE_GOAL_RE = /\b(add the necessary|add the required|install the necessary|install the required|appropriate packages|suitable packages)\b/i;
750
- static isComplexGoal(goal) {
751
- const lines = goal.split('\n').map(l => l.trim()).filter(l => l.length > 0);
752
- const hasBullets = lines.filter(l => /^[-*•\d+]/.test(l)).length >= 2;
753
- const isLong = goal.length > 400;
754
- return hasBullets || isLong;
755
- }
756
- static validateTasks(tasks, goal, projectRoot) {
757
- if (tasks.length === 0)
758
- return 'No tasks generated';
759
- const isSplit = goal.toLowerCase().includes('continue the remaining work');
760
- const coderTasks = tasks.filter(t => t.role === 'coder');
761
- if (!isSplit && coderTasks.length === 1 && Orchestrator.extractFilePaths(goal).length > 3) {
762
- return `Expected multiple coder tasks for goal with many file paths`;
763
- }
764
- if (!isSplit && tasks.length === 1 && Orchestrator.isComplexGoal(goal)) {
765
- return `Expected multiple tasks (one per file/component) to delegate a complex goal, but the plan only has 1 task. Please break it down into at least 2-3 focused subtasks.`;
766
- }
767
- // Whether this is a last-resort single-task fallback plan (goal === task goal, no file path in goal)
768
- const isFallbackSingleTask = tasks.length === 1 && tasks[0].goal.trim() === goal.trim();
769
- for (const t of tasks) {
770
- if (Orchestrator.VAGUE_GOAL_RE.test(t.goal)) {
771
- return `Task "${t.goal.slice(0, 80)}" contains vague wording — be concrete`;
772
- }
773
- const paths = Orchestrator.extractFilePaths(t.goal);
774
- const isNonFileOp = /\b(install|npm|yarn|pnpm|compile|build|setup|initialize|init|run|test|lint)\b/i.test(t.goal);
775
- if ((t.role === 'coder' || t.role === 'debugger') && paths.length === 0 && !isFallbackSingleTask && !isNonFileOp) {
776
- return `Task "${t.goal.slice(0, 80)}" has no file path — each task must target a specific file`;
777
- }
778
- if (projectRoot) {
779
- for (const p of paths) {
780
- const basename = path.basename(p);
781
- const normalizedP = p.replace(/\\/g, '/');
782
- if (normalizedP.startsWith('src/') || normalizedP.startsWith('lib/')) {
783
- const rootPath = path.join(projectRoot, basename);
784
- const srcPath = path.join(projectRoot, p);
785
- if (fs.existsSync(rootPath) && !fs.existsSync(srcPath)) {
786
- return `Task "${t.goal.slice(0, 80)}" targets "${p}" but the file actually exists at the root level ("${basename}"). Correct the path.`;
787
- }
788
- }
789
- }
790
- }
791
- }
792
- return null;
793
- }
794
- static cleanTaskText(text) {
795
- const withoutBlocks = Orchestrator.stripCodeBlocks(text);
796
- const withoutToolRequests = Orchestrator.stripToolRequestArtifacts(withoutBlocks);
797
- return withoutToolRequests || withoutBlocks || text;
798
- }
799
- static cleanPlanOutput(text) {
800
- // Clean the entire planner output before parsing to remove all tool request clutter
801
- return Orchestrator.stripToolRequestArtifacts(text);
802
- }
803
- truncateGoal(text) {
804
- if (text.length <= 200)
805
- return text;
806
- return text.slice(0, 197) + '...';
807
- }
808
654
  parseDelegationTasks(plan, goal) {
809
- const cleanedPlan = Orchestrator.cleanPlanOutput(plan);
655
+ const cleanedPlan = cleanPlanOutput(plan);
810
656
  const tasks = [];
811
657
  const seenGoals = new Set();
812
658
  const activeFilesText = this.toolContext.activeFiles.size > 0
813
659
  ? `Files in context: ${Array.from(this.toolContext.activeFiles.values()).join(', ')}`
814
660
  : '';
815
- const originalPaths = Orchestrator.extractFilePaths(goal);
661
+ const originalPaths = extractFilePaths(goal);
816
662
  const pathsBlock = originalPaths.length > 0
817
663
  ? `\nOriginal goal file paths (MUST preserve in subtask):\n${originalPaths.map(p => ` - ${p}`).join('\n')}\n`
818
664
  : '';
@@ -822,12 +668,12 @@ export class Orchestrator {
822
668
  let currentRole = '';
823
669
  let currentGoal = '';
824
670
  const pushTask = (role, goalText, ctx, depth) => {
825
- const clean = Orchestrator.cleanTaskText(goalText) || goalText;
671
+ const clean = cleanTaskText(goalText) || goalText;
826
672
  const goalKey = clean.trim().toLowerCase().replace(/\s+/g, ' ');
827
673
  if (seenGoals.has(goalKey))
828
674
  return;
829
675
  seenGoals.add(goalKey);
830
- tasks.push({ goal: this.truncateGoal(clean || goalText), context: ctx, role, status: 'pending', splitDepth: depth });
676
+ tasks.push({ goal: truncateGoal(clean || goalText), context: ctx, role, status: 'pending', splitDepth: depth });
831
677
  };
832
678
  const guessRole = (text) => {
833
679
  const lower = text.toLowerCase();
@@ -895,7 +741,7 @@ export class Orchestrator {
895
741
  }
896
742
  if (tasks.length === 0) {
897
743
  tasks.push({
898
- goal: this.truncateGoal(goal),
744
+ goal: truncateGoal(goal),
899
745
  context: baseCtx,
900
746
  role: 'coder',
901
747
  status: 'pending',
@@ -904,344 +750,6 @@ export class Orchestrator {
904
750
  }
905
751
  return tasks;
906
752
  }
907
- isDeclaredError(result) {
908
- const normalized = result.trim().toLowerCase();
909
- return /^(error|failed)/.test(normalized);
910
- }
911
- requiresRealArtifacts(role, goal) {
912
- if (role !== 'coder')
913
- return false;
914
- const keywords = ['implement', 'create', 'write', 'add', 'make', 'build', 'update', 'change', 'modify', 'generate', 'setup', 'fix'];
915
- const lower = goal.toLowerCase();
916
- return keywords.some(k => lower.includes(k));
917
- }
918
- extractPendingWrites(result) {
919
- const paths = [];
920
- const regex = /(?:created|wrote|added|updated|modified|in)\s+([A-Za-z0-9_\-./\\:]+\.[A-Za-z0-9]+)/gi;
921
- let match;
922
- while ((match = regex.exec(result)) !== null) {
923
- paths.push(match[1]);
924
- }
925
- const standaloneRegex = /\b([A-Za-z0-9_\-./\\:]+\.[a-zA-Z0-9]+)\b/g;
926
- let standaloneMatch;
927
- while ((standaloneMatch = standaloneRegex.exec(result)) !== null) {
928
- const p = standaloneMatch[1];
929
- if (!paths.includes(p) && (p.includes('/') || p.includes('\\') || p.includes('.'))) {
930
- paths.push(p);
931
- }
932
- }
933
- return paths;
934
- }
935
- async verifyArtifacts(role, goal, result, historyStartIndex = 0) {
936
- if (!this.requiresRealArtifacts(role, goal))
937
- return true;
938
- if (this.isDeclaredError(result))
939
- return false;
940
- // Terminal-only tasks (npm install, tsc, npx commands) produce no patchHistory entries — accept them
941
- // Only apply when the goal is a bare process invocation, not a creative task like "build web app"
942
- const isTerminalOnlyGoal = /^\s*(run|install|execute|compile)\b/i.test(goal)
943
- && !/\b(create|write|generate|add|make|implement|build|setup|configure)\b/i.test(goal);
944
- if (isTerminalOnlyGoal)
945
- return true;
946
- if (!this.toolContext.patchHistory || this.toolContext.patchHistory.length <= historyStartIndex) {
947
- return false;
948
- }
949
- const rawPaths = this.extractPendingWrites(result);
950
- const paths = rawPaths.map(p => p.replace(/\\/g, '/'));
951
- const currentPatches = this.toolContext.patchHistory.slice(historyStartIndex);
952
- const normalizedHistory = currentPatches.map(h => ({
953
- ...h,
954
- normalizedPath: h.filePath.replace(/\\/g, '/')
955
- }));
956
- const hasPatchedMentioned = normalizedHistory.some(h => paths.includes(h.normalizedPath) || paths.some(p => h.normalizedPath.endsWith('/' + p)));
957
- if (hasPatchedMentioned)
958
- return true;
959
- const hasRelevantPatch = normalizedHistory.some(h => {
960
- const base = h.normalizedPath.split('/').pop() || '';
961
- const goalLower = goal.toLowerCase();
962
- return goalLower.includes(base.split('.')[0].toLowerCase());
963
- });
964
- if (hasRelevantPatch)
965
- return true;
966
- return false;
967
- }
968
- async checkPlaceholders(historyStartIndex) {
969
- const history = this.toolContext.patchHistory || [];
970
- const placeholders = [];
971
- for (let i = historyStartIndex; i < history.length; i++) {
972
- const entry = history[i];
973
- if (!entry.filePath)
974
- continue;
975
- try {
976
- const content = fs.readFileSync(entry.filePath, 'utf8');
977
- const lines = content.split('\n');
978
- for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {
979
- const bracketMatch = lines[lineIdx].match(PLACEHOLDER_RE);
980
- if (bracketMatch) {
981
- placeholders.push(`${entry.filePath}:${lineIdx + 1} — ${bracketMatch[0].trim()}`);
982
- }
983
- const htmlMatch = lines[lineIdx].match(HTML_PLACEHOLDER_RE);
984
- if (htmlMatch) {
985
- placeholders.push(`${entry.filePath}:${lineIdx + 1} — HTML comment placeholder`);
986
- }
987
- }
988
- }
989
- catch {
990
- // file might have been deleted — skip
991
- }
992
- }
993
- return placeholders;
994
- }
995
- async fillPlaceholders(historyStartIndex) {
996
- const history = this.toolContext.patchHistory || [];
997
- let filled = 0;
998
- const year = new Date().getFullYear().toString();
999
- const today = new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' });
1000
- let userName;
1001
- try {
1002
- userName = os.userInfo().username;
1003
- }
1004
- catch {
1005
- userName = 'user';
1006
- }
1007
- for (let i = historyStartIndex; i < history.length; i++) {
1008
- const entry = history[i];
1009
- if (!entry.filePath)
1010
- continue;
1011
- try {
1012
- const content = fs.readFileSync(entry.filePath, 'utf8');
1013
- const newContent = content
1014
- // Year/date — universally guessable
1015
- .replace(/\[(?:YEAR|Year|year|YYYY|yyyy)\]/g, year)
1016
- .replace(/\[(?:DATE|Date|date|TODAY|Today|today)\]/g, today)
1017
- // Name/author/owner — use system account name
1018
- .replace(/\[(?:YOUR\s+NAME|Your\s+Name|your\s+name|FULLNAME|Fullname|fullname|AUTHOR|Author|author|USERNAME|Username|username|OWNER|Owner|owner)\]/g, userName);
1019
- if (newContent !== content) {
1020
- fs.writeFileSync(entry.filePath, newContent, 'utf8');
1021
- const bracketCount = (content.match(/\[/g) || []).length;
1022
- const newBracketCount = (newContent.match(/\[/g) || []).length;
1023
- filled += bracketCount - newBracketCount;
1024
- }
1025
- }
1026
- catch {
1027
- // skip
1028
- }
1029
- }
1030
- return filled;
1031
- }
1032
- async attemptRepair(task, previous, customContext) {
1033
- const role = getAgentRole(task.role);
1034
- const tools = filterToolsForRole(BUILTIN_TOOLS, task.role);
1035
- const maxRetries = 2;
1036
- let attempt = 0;
1037
- let currentSummary = previous.summary;
1038
- let currentCustomContext = customContext;
1039
- while (attempt < maxRetries) {
1040
- if (this.toolContext.abortSignal.aborted) {
1041
- break;
1042
- }
1043
- attempt++;
1044
- console.log(`\n[REPAIR] Attempt ${attempt}/${maxRetries} to repair task: ${task.goal}`);
1045
- const baseCtx = currentCustomContext || task.context;
1046
- const historyStartIndex = this.toolContext.patchHistory?.length || 0;
1047
- let repairHint = '';
1048
- const noRealWork = (this.toolContext.patchHistory?.length ?? 0) <= historyStartIndex;
1049
- if (noRealWork) {
1050
- repairHint = `\n\nCRITICAL: Your previous response did not produce any file writes. You MUST call write_file or patch — do not describe what you did, just do it now. Call the tool immediately.`;
1051
- }
1052
- else if (currentSummary && currentSummary.toLowerCase().includes('i will') && !currentSummary.includes('`write_file`') && !currentSummary.includes('`patch`') && !currentSummary.includes('`terminal`')) {
1053
- repairHint = `\n\nCRITICAL: Your previous response described the work as text but did not actually call any tools. Do not describe WHAT you will do — directly EXECUTE the write_file or patch tool now. Your response must contain a tool call, not a plan or explanation.`;
1054
- }
1055
- const repairContext = `${baseCtx}\n\nPrevious attempt failed verification. Output was:\n${currentSummary}\n\nPlease retry and ensure you actually write the required files/artifacts.${repairHint}`;
1056
- const result = await this.runAgent(role, task.goal, repairContext, tools);
1057
- if (this.toolContext.abortSignal.aborted) {
1058
- return { success: false, summary: 'Task aborted by user' };
1059
- }
1060
- let verified = await this.verifyArtifacts(task.role, task.goal, result, historyStartIndex);
1061
- let repairCheckLogs = '';
1062
- if (verified && (task.role === 'coder' || task.role === 'debugger') && (this.toolContext.patchHistory?.length ?? 0) > historyStartIndex) {
1063
- const checkResult = await this.runBuildVerification(historyStartIndex);
1064
- if (!checkResult.success) {
1065
- const modifiedFiles = this.toolContext.patchHistory.slice(historyStartIndex).map(p => p.filePath);
1066
- const isRelated = this.isBuildErrorRelated(checkResult.errorLogs || '', modifiedFiles);
1067
- if (isRelated) {
1068
- verified = false;
1069
- repairCheckLogs = (checkResult.errorLogs || 'Build check failed') + this.generateBuildErrorHint(checkResult.errorLogs || '');
1070
- }
1071
- else {
1072
- console.log(pc.yellow(`\n[VERIFY] Build check failed, but errors appear to be in unrelated files. Ignoring build failure for this task.`));
1073
- }
1074
- }
1075
- }
1076
- if (verified && !this.isDeclaredError(result)) {
1077
- return { success: true, summary: result };
1078
- }
1079
- if (repairCheckLogs) {
1080
- currentCustomContext = (customContext || task.context) + `\n\nAdditionally, the build/compilation check failed with error output:\n\`\`\`\n${repairCheckLogs}\n\`\`\``;
1081
- }
1082
- currentSummary = result;
1083
- }
1084
- return { success: false, summary: currentSummary, evidence: 'no artifacts' };
1085
- }
1086
- hasRealWrites(result) {
1087
- const claimed = this.extractPendingWrites(result);
1088
- if (claimed.length === 0)
1089
- return false;
1090
- const history = this.toolContext.patchHistory || [];
1091
- const historyPaths = new Set(history.map(h => (h.filePath || '').replace(/\\/g, '/')));
1092
- return claimed.some(p => historyPaths.has(p.replace(/\\/g, '/')));
1093
- }
1094
- verifyArtifactsThoroughly(role, goal, result, historyStartIndex = 0) {
1095
- if (!this.requiresRealArtifacts(role, goal))
1096
- return true;
1097
- if (this.hasRealWrites(result))
1098
- return true;
1099
- // Fallback: accept if patchHistory has new entries since task start (agent wrote files but result text didn't mention paths)
1100
- const history = this.toolContext.patchHistory || [];
1101
- if (history.length > historyStartIndex)
1102
- return true;
1103
- // No writes detected anywhere — agent only talked, did not act
1104
- return false;
1105
- }
1106
- buildCleanSummary(task, result, historyStartIndex) {
1107
- const history = this.toolContext.patchHistory || [];
1108
- const newPatches = [];
1109
- for (let i = historyStartIndex; i < history.length; i++) {
1110
- newPatches.push(history[i]);
1111
- }
1112
- if (newPatches.length === 0 || result.split(/\s+/).length < 30)
1113
- return null;
1114
- const files = [...new Set(newPatches.map(p => p.filePath).filter(Boolean))];
1115
- if (files.length === 0)
1116
- return null;
1117
- return `Completed: ${task.goal} — Files: ${files.join(', ')}`;
1118
- }
1119
- static isUnnecessaryConfigTask(task, projectContext) {
1120
- if (!projectContext)
1121
- return false;
1122
- const goal = task.goal.toLowerCase();
1123
- const isNextJs = /\bNext\.js\b/i.test(projectContext);
1124
- const isVue = /\b(Vue|Nuxt)\b/i.test(projectContext);
1125
- if (isNextJs && /\bnext\.config\b/i.test(goal))
1126
- return true;
1127
- if (isVue && /\b(vue|nuxt)\.config\b/i.test(goal))
1128
- return true;
1129
- return false;
1130
- }
1131
- static getFrameworkGuidance(projectContext, projectRoot) {
1132
- if (!projectContext)
1133
- return '';
1134
- const ctx = projectContext;
1135
- if (/\bNext\.js\b/i.test(ctx)) {
1136
- const majorMatch = ctx.match(/Next\.js\s+(\d+)/);
1137
- const major = majorMatch ? parseInt(majorMatch[1]) : 13;
1138
- const isModern = major >= 13;
1139
- const linkRule = isModern
1140
- ? 'Use <Link href="...">visible text</Link>. Do NOT nest an <a> tag inside <Link> — Next.js 13+ renders the anchor automatically.'
1141
- : 'Use <Link href="..."><a className="...">text</a></Link> for Next.js 12 and earlier.';
1142
- const isAppRouter = fs.existsSync(path.join(projectRoot || process.cwd(), 'app'));
1143
- const jsxRule = isAppRouter
1144
- ? 'Do NOT add `import React from \'react\'` at the top of .tsx files. Next.js App Router uses the automatic JSX runtime.'
1145
- : 'If other files in the project (e.g. index.tsx) import React, or if ESLint has react/react-in-jsx-scope enabled, you MUST add `import React from \'react\'` at the top of .tsx files.';
1146
- return `\n\nNEXT.JS ${major} PRODUCTION CODING RULES (MANDATORY — follow these before writing any code):
1147
- - ROUTING: Pages live in pages/ or src/pages/ (or app/ for App Router). No edits to next.config.js or router config needed for new pages.
1148
- - LINK: ${linkRule}
1149
- - JSX TRANSFORM: ${jsxRule}
1150
- - APOSTROPHES: Escape apostrophes in JSX text as &apos; or use a JS template literal. Writing raw ' inside JSX text (e.g., <p>don't</p>) is a lint error.
1151
- - COMPONENT STRUCTURE: Every .tsx page file must export a single default function component. Do NOT write raw JSX tags outside a function body.
1152
- - IMPORTS: Use next/link for navigation, next/image for images. Do not use react-router-dom.
1153
- - TAILWIND: If Tailwind CSS is detected, use Tailwind utility classes for all styling. Do not write inline styles or separate .css files for component styling.\n`;
1154
- }
1155
- if (/\b(React)\b/i.test(ctx) && !/\bNext\.js\b/i.test(ctx)) {
1156
- return `\n\nREACT SPA PRODUCTION CODING RULES (MANDATORY):
1157
- - ROUTING: Use react-router-dom v6+ with createBrowserRouter/RouterProvider. Lazy-load routes with React.lazy() and Suspense if possible.
1158
- - STATE: Use React hooks (useState, useReducer, useContext) for local state. For complex global state, use a lightweight manager like Zustand or Redux Toolkit.
1159
- - DATA FETCHING: Use custom hooks wrapping fetch/axios. Keep API calls out of UI component bodies.
1160
- - COMPONENT STRUCTURE: Export a single default component per file. Avoid mixing business logic directly with presentation components.\n`;
1161
- }
1162
- if (/\b(Vue|Nuxt)\b/i.test(ctx)) {
1163
- return `\n\nVUE/NUXT PRODUCTION CODING RULES (MANDATORY):
1164
- - ROUTING: Vue Router (or file-based pages/ in Nuxt). New pages typically need route entries added to the router config, not config files like vue.config.js.
1165
- - COMPONENTS: Use Single File Components (.vue). Match the existing \`<script setup>\` or Options API style exactly.
1166
- - STATE: Use Pinia or Vuex for global state. Use standard refs/computed for local reactive state.\n`;
1167
- }
1168
- if (/\bExpress\b/i.test(ctx)) {
1169
- return `\n\nEXPRESS PRODUCTION CODING RULES (MANDATORY):
1170
- - ROUTING: Express requires explicit route handlers. New endpoints must be added to the server/ or routes/ directory, and registered on the main express application.
1171
- - CONTROLLERS: Keep request/response handler functions in controller files, separate from route definitions.
1172
- - ERROR HANDLING: Always wrap route logic in try/catch or async-error-handler middleware and forward to next(err).\n`;
1173
- }
1174
- if (/\b(Python|Flask|Django|FastAPI)\b/i.test(ctx)) {
1175
- return `\n\nPYTHON PRODUCTION CODING RULES (MANDATORY):
1176
- - STRUCTURE: Follow the framework's conventional project layout.
1177
- - TYPING: Use type hints on all function signatures. Use Pydantic models for request/response schemas.
1178
- - ASYNC: Use async/await where the framework supports it (FastAPI, async Django views).\n`;
1179
- }
1180
- if (/\b(Vanilla JS|HTML)\b/i.test(ctx)) {
1181
- return `\n\nVANILLA JS/HTML PRODUCTION CODING RULES (MANDATORY):
1182
- - STRUCTURE: Keep JS in separate .js files, CSS in separate .css files. Use ES modules (type="module") for scripts.
1183
- - DOM: Use querySelector/querySelectorAll. Never use document.write or innerHTML for user-supplied content.
1184
- - EVENTS: Use addEventListener, never inline event handlers (onclick="...").\n`;
1185
- }
1186
- return '';
1187
- }
1188
- static extractRequirements(text) {
1189
- if (!text)
1190
- return [];
1191
- const reqs = [];
1192
- const m = text.match(/(?:with|including|containing|that\s+(?:has|includes|contains|features?)|featuring)\s+(.+)/i);
1193
- if (!m)
1194
- return reqs;
1195
- const items = m[1]
1196
- .split(/\s*(?:,\s*|\sand\s|\s*&)\s*/)
1197
- .map(s => s.replace(/^(?:an?\s+|the\s+)/i, '').replace(/\.$/, '').trim())
1198
- .filter(Boolean);
1199
- const filler = new Set(['the following content', 'the specified content', 'appropriate content', 'content', 'your code']);
1200
- for (const item of items) {
1201
- const lower = item.toLowerCase();
1202
- if (!filler.has(lower) && lower.length > 2) {
1203
- reqs.push(item);
1204
- }
1205
- }
1206
- return reqs;
1207
- }
1208
- static extractFilePaths(text) {
1209
- if (!text)
1210
- return [];
1211
- const paths = [];
1212
- const re = /(?:\(|\[|\s|^)((?:[A-Za-z0-9_\-./\\]+[\\/])?[A-Za-z0-9_\-]+\.(?:tsx?|jsx?|vue|svelte|css|scss|json|md|csv|txt|yaml|yml|toml|py|rs|go|java|sh|env|html|xml|sql|tf|lock|dart))(?:[)\s,;.]|$)/g;
1213
- let m;
1214
- const excludedNames = new Set(['next.js', 'node.js', 'react.js', 'vue.js', 'nest.js', 'nuxt.js', 'express.js', 'alpine.js', 'svelte.js', 'deno.js', 'three.js', 'chart.js', 'socket.io']);
1215
- while ((m = re.exec(text)) !== null) {
1216
- const p = m[1].replace(/\\/g, '/');
1217
- if (excludedNames.has(p.toLowerCase())) {
1218
- continue;
1219
- }
1220
- if (!p.startsWith('http') && !p.startsWith('node_modules') && p.length < 200) {
1221
- paths.push(p);
1222
- }
1223
- }
1224
- return [...new Set(paths)];
1225
- }
1226
- static buildDependencyGraph(tasks) {
1227
- // Auto-detect dependencies: if task B mentions a file path that is the
1228
- // primary target file of an earlier task A, then B depends on A.
1229
- for (let i = 0; i < tasks.length; i++) {
1230
- const laterPaths = Orchestrator.extractFilePaths(tasks[i].goal);
1231
- for (let j = 0; j < i; j++) {
1232
- const earlierPaths = Orchestrator.extractFilePaths(tasks[j].goal);
1233
- const shared = earlierPaths.some(ep => laterPaths.includes(ep));
1234
- if (shared) {
1235
- if (!tasks[i].dependencies)
1236
- tasks[i].dependencies = [];
1237
- // Only add if not already present
1238
- if (!tasks[i].dependencies.includes(tasks[j].goal)) {
1239
- tasks[i].dependencies.push(tasks[j].goal);
1240
- }
1241
- }
1242
- }
1243
- }
1244
- }
1245
753
  findStyleReference(taskGoal) {
1246
754
  // Extract target directory from goal
1247
755
  let dir = '';
@@ -1399,7 +907,7 @@ export class Orchestrator {
1399
907
  }
1400
908
  }
1401
909
  // Extract explicit requirements from the task goal only
1402
- const taskReqs = Orchestrator.extractRequirements(task.goal);
910
+ const taskReqs = extractRequirements(task.goal);
1403
911
  if (taskReqs.length > 0) {
1404
912
  enrichedContext += `\nRequirements:\n${taskReqs.slice(0, 4).map(r => ` - ${r}`).join('\n')}\n`;
1405
913
  }
@@ -1408,12 +916,12 @@ export class Orchestrator {
1408
916
  enrichedContext += `\nCRITICAL: You MUST implement every requirement above with real, specific content. Do NOT use generic filler like "Welcome to our platform", "We provide services", "Learn more about us", or placeholder text. Each requirement needs actual concrete content that a real business would publish.\n`;
1409
917
  }
1410
918
  // Extract explicit file paths from the goal and inject a concise scope boundary
1411
- const scopePaths = Orchestrator.extractFilePaths(task.goal);
919
+ const scopePaths = extractFilePaths(task.goal);
1412
920
  if (scopePaths.length > 0) {
1413
921
  enrichedContext += `\nSCOPE: only touch ${scopePaths.join(', ')}\n`;
1414
922
  }
1415
923
  enrichedContext += `\n${frameworkBlock}${task.context}`;
1416
- const frameworkRules = Orchestrator.getFrameworkGuidance(projectContext, this.toolContext.projectRoot);
924
+ const frameworkRules = getFrameworkGuidance(projectContext, this.toolContext.projectRoot);
1417
925
  const systemExtra = `Project context:\n${projectContext || '(none discovered)'}${frameworkRules}\n`;
1418
926
  // Prepend a terse override reminder so the rules land in the user message too,
1419
927
  // which some models weight more heavily than the system prompt extension.
@@ -1457,7 +965,7 @@ export class Orchestrator {
1457
965
  task.status = 'failed';
1458
966
  task.error = result.split('\n')[0];
1459
967
  if (task.role === 'coder' || task.role === 'debugger') {
1460
- await this.rollbackTaskPatches(historyStartIndex);
968
+ await rollbackTaskPatches(this.toolContext, historyStartIndex);
1461
969
  }
1462
970
  this.results.push({ role: task.role, goal: task.goal, summary: result, success: false });
1463
971
  console.log(`[${pc.red('FAILED')}] ${role.name}: ${task.error}`);
@@ -1485,13 +993,13 @@ export class Orchestrator {
1485
993
  const deduped = subTasks.filter(st => {
1486
994
  if (doneBeforeSplit.length === 0 || st.role !== 'coder')
1487
995
  return true;
1488
- const newPaths = Orchestrator.extractFilePaths(st.goal).map(p => p.toLowerCase());
996
+ const newPaths = extractFilePaths(st.goal).map(p => p.toLowerCase());
1489
997
  if (newPaths.length === 0)
1490
998
  return true;
1491
999
  return !doneBeforeSplit.some(d => {
1492
1000
  if (d.role !== 'coder')
1493
1001
  return false;
1494
- const donePaths = Orchestrator.extractFilePaths(d.goal).map(p => p.toLowerCase());
1002
+ const donePaths = extractFilePaths(d.goal).map(p => p.toLowerCase());
1495
1003
  return donePaths.some(dp => newPaths.includes(dp));
1496
1004
  });
1497
1005
  });
@@ -1512,7 +1020,7 @@ export class Orchestrator {
1512
1020
  ? `Task still too large after ${depth} splits — manual review needed`
1513
1021
  : 'Task too large and no work completed';
1514
1022
  if (task.role === 'coder' || task.role === 'debugger') {
1515
- await this.rollbackTaskPatches(historyStartIndex);
1023
+ await rollbackTaskPatches(this.toolContext, historyStartIndex);
1516
1024
  }
1517
1025
  this.results.push({
1518
1026
  role: task.role,
@@ -1522,21 +1030,21 @@ export class Orchestrator {
1522
1030
  });
1523
1031
  return;
1524
1032
  }
1525
- let verified = await this.verifyArtifacts(task.role, task.goal, result, historyStartIndex);
1033
+ let verified = await verifyArtifacts(this.toolContext, task.role, task.goal, result, historyStartIndex);
1526
1034
  let evidence = '';
1527
1035
  let placeholderSites = [];
1528
1036
  let checkLogs = '';
1529
1037
  if (verified) {
1530
- placeholderSites = await this.checkPlaceholders(historyStartIndex);
1038
+ placeholderSites = await checkPlaceholders(this.toolContext, historyStartIndex);
1531
1039
  if (placeholderSites.length > 0) {
1532
1040
  console.log(pc.yellow(`\nFound ${placeholderSites.length} placeholder(s) in written files`));
1533
1041
  // Auto-fill trivial placeholders like [Year], [Your Name]
1534
- const filled = await this.fillPlaceholders(historyStartIndex);
1042
+ const filled = await fillPlaceholders(this.toolContext, historyStartIndex);
1535
1043
  if (filled > 0) {
1536
1044
  console.log(pc.green(` Auto-filled ${filled} trivial placeholder(s) (year, name, etc.)`));
1537
1045
  }
1538
1046
  // Re-check for remaining (structural) placeholders
1539
- placeholderSites = await this.checkPlaceholders(historyStartIndex);
1047
+ placeholderSites = await checkPlaceholders(this.toolContext, historyStartIndex);
1540
1048
  if (placeholderSites.length === 0) {
1541
1049
  verified = true;
1542
1050
  }
@@ -1546,13 +1054,13 @@ export class Orchestrator {
1546
1054
  }
1547
1055
  }
1548
1056
  if (verified && (task.role === 'coder' || task.role === 'debugger') && (this.toolContext.patchHistory?.length ?? 0) > historyStartIndex) {
1549
- const checkResult = await this.runBuildVerification(historyStartIndex);
1057
+ const checkResult = await runBuildVerification(this.toolContext, historyStartIndex);
1550
1058
  if (!checkResult.success) {
1551
1059
  const modifiedFiles = this.toolContext.patchHistory.slice(historyStartIndex).map(p => p.filePath);
1552
- const isRelated = this.isBuildErrorRelated(checkResult.errorLogs || '', modifiedFiles);
1060
+ const isRelated = isBuildErrorRelated(checkResult.errorLogs || '', modifiedFiles, this.toolContext.projectRoot);
1553
1061
  if (isRelated) {
1554
1062
  verified = false;
1555
- checkLogs = (checkResult.errorLogs || 'Build check failed') + this.generateBuildErrorHint(checkResult.errorLogs || '');
1063
+ checkLogs = (checkResult.errorLogs || 'Build check failed') + generateBuildErrorHint(checkResult.errorLogs || '');
1556
1064
  }
1557
1065
  else {
1558
1066
  console.log(pc.yellow(`\n[VERIFY] Build check failed, but errors appear to be in unrelated files. Ignoring build failure for this task.`));
@@ -1568,7 +1076,7 @@ export class Orchestrator {
1568
1076
  if (checkLogs) {
1569
1077
  repairCtx += `\n\nPrevious attempt failed build/compilation verification. The check failed with the following error output:\n\`\`\`\n${checkLogs}\n\`\`\`\nPlease fix the build/compilation errors listed above.`;
1570
1078
  }
1571
- const repaired = await this.attemptRepair(task, {
1079
+ const repaired = await attemptRepair({ toolContext: this.toolContext, runAgent: (role, goal, context, tools) => this.runAgent(role, goal, context, tools) }, task, {
1572
1080
  role: task.role,
1573
1081
  goal: task.goal,
1574
1082
  summary: result,
@@ -1578,13 +1086,13 @@ export class Orchestrator {
1578
1086
  verified = repaired.success;
1579
1087
  evidence = repaired.evidence || '';
1580
1088
  if (verified) {
1581
- const stillPlaceholders = await this.checkPlaceholders(historyStartIndex);
1089
+ const stillPlaceholders = await checkPlaceholders(this.toolContext, historyStartIndex);
1582
1090
  if (stillPlaceholders.length > 0) {
1583
1091
  // Try auto-fill one more time after repair
1584
- const filled = await this.fillPlaceholders(historyStartIndex);
1092
+ const filled = await fillPlaceholders(this.toolContext, historyStartIndex);
1585
1093
  if (filled > 0)
1586
1094
  console.log(pc.green(` Auto-filled ${filled} remaining trivial placeholder(s)`));
1587
- const remain = await this.checkPlaceholders(historyStartIndex);
1095
+ const remain = await checkPlaceholders(this.toolContext, historyStartIndex);
1588
1096
  if (remain.length > 0) {
1589
1097
  verified = false;
1590
1098
  evidence = `Placeholders remain: ${remain.join('; ')}`;
@@ -1593,9 +1101,9 @@ export class Orchestrator {
1593
1101
  }
1594
1102
  }
1595
1103
  const resultForCheck = result.replace(/<think>[\s\S]*?<\/think>/gi, '').trim();
1596
- const success = verified && !this.isDeclaredError(resultForCheck) && this.verifyArtifactsThoroughly(task.role, task.goal, resultForCheck, historyStartIndex);
1104
+ const success = verified && !isDeclaredError(resultForCheck) && verifyArtifactsThoroughly(this.toolContext, task.role, task.goal, resultForCheck, historyStartIndex);
1597
1105
  if (success) {
1598
- const clean = this.buildCleanSummary(task, result, historyStartIndex);
1106
+ const clean = buildCleanSummary(this.toolContext, task, result, historyStartIndex);
1599
1107
  if (clean)
1600
1108
  result = clean;
1601
1109
  }
@@ -1604,7 +1112,7 @@ export class Orchestrator {
1604
1112
  task.error = resultForCheck.split('\n')[0] || result.split('\n')[0] || 'Unknown failure';
1605
1113
  // Rollback patches made during this task to keep codebase clean
1606
1114
  if (task.role === 'coder' || task.role === 'debugger') {
1607
- await this.rollbackTaskPatches(historyStartIndex);
1115
+ await rollbackTaskPatches(this.toolContext, historyStartIndex);
1608
1116
  }
1609
1117
  // Log failure as a lesson for self-improvement
1610
1118
  if (this.sessionManager) {
@@ -1869,151 +1377,5 @@ export class Orchestrator {
1869
1377
  console.log(pc.yellow(`\n[WARN] Failed to write walkthrough.md: ${err.message}`));
1870
1378
  }
1871
1379
  }
1872
- async rollbackTaskPatches(historyStartIndex) {
1873
- const history = this.toolContext.patchHistory;
1874
- if (!history || history.length <= historyStartIndex)
1875
- return;
1876
- console.log(pc.yellow(`\n[ROLLBACK] Task failed verification. Rolling back changes to preserve workspace health...`));
1877
- // Revert patches in reverse order
1878
- for (let i = history.length - 1; i >= historyStartIndex; i--) {
1879
- const patch = history[i];
1880
- try {
1881
- if (fs.existsSync(patch.filePath)) {
1882
- fs.writeFileSync(patch.filePath, patch.oldContent, 'utf8');
1883
- console.log(pc.gray(` Reverted changes to ${path.relative(this.toolContext.projectRoot || process.cwd(), patch.filePath)}`));
1884
- }
1885
- }
1886
- catch (err) {
1887
- console.log(pc.red(` Failed to revert changes to ${patch.filePath}: ${err.message}`));
1888
- }
1889
- }
1890
- // Truncate the patch history
1891
- history.length = historyStartIndex;
1892
- }
1893
- async runBuildVerification(historyStartIndex = 0) {
1894
- const cwd = this.toolContext.projectRoot || process.cwd();
1895
- const history = this.toolContext.patchHistory || [];
1896
- const touchedFiles = history.slice(historyStartIndex).map(p => p.filePath);
1897
- const hasSourceFiles = touchedFiles.some(f => {
1898
- const ext = path.extname(f).toLowerCase();
1899
- return ['.ts', '.tsx', '.js', '.jsx', '.go', '.rs', '.py', '.cpp', '.c', '.h', '.java'].includes(ext);
1900
- });
1901
- if (touchedFiles.length > 0 && !hasSourceFiles) {
1902
- console.log(pc.gray(` [VERIFY] Skipping build check (only config/docs files modified).`));
1903
- return { success: true };
1904
- }
1905
- let command = '';
1906
- let lintCommand = '';
1907
- // Auto-discover verification command
1908
- if (fs.existsSync(path.join(cwd, 'package.json'))) {
1909
- try {
1910
- const pkg = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'));
1911
- if (pkg.scripts) {
1912
- if (pkg.scripts['daedalus-check']) {
1913
- command = 'npm run daedalus-check';
1914
- }
1915
- else if (fs.existsSync(path.join(cwd, 'tsconfig.json'))) {
1916
- command = 'npx tsc --noEmit';
1917
- }
1918
- else if (pkg.scripts.build) {
1919
- command = 'npm run build';
1920
- }
1921
- if (pkg.scripts.lint) {
1922
- lintCommand = 'npm run lint';
1923
- }
1924
- }
1925
- }
1926
- catch { /* ignored */ }
1927
- }
1928
- else if (fs.existsSync(path.join(cwd, 'Cargo.toml'))) {
1929
- command = 'cargo check';
1930
- }
1931
- else if (fs.existsSync(path.join(cwd, 'go.mod'))) {
1932
- command = 'go build ./...';
1933
- }
1934
- if (!command && !lintCommand) {
1935
- return { success: true };
1936
- }
1937
- const { exec } = await import('child_process');
1938
- const runCmd = (cmd) => {
1939
- return new Promise((resolve) => {
1940
- exec(cmd, { cwd, timeout: 30000 }, (error, stdout, stderr) => {
1941
- if (error) {
1942
- resolve({ success: false, logs: (stdout + '\n' + stderr).trim() });
1943
- }
1944
- else {
1945
- resolve({ success: true });
1946
- }
1947
- });
1948
- });
1949
- };
1950
- if (command) {
1951
- console.log(pc.cyan(`\n[VERIFY] Running verification command: "${command}"...`));
1952
- const res = await runCmd(command);
1953
- if (!res.success) {
1954
- console.log(pc.red(`[VERIFY] Verification failed!`));
1955
- return { success: false, errorLogs: res.logs };
1956
- }
1957
- console.log(pc.green(`[VERIFY] Verification passed.`));
1958
- }
1959
- if (lintCommand) {
1960
- console.log(pc.cyan(`\n[VERIFY] Running linter command: "${lintCommand}"...`));
1961
- const res = await runCmd(lintCommand);
1962
- if (!res.success) {
1963
- console.log(pc.red(`[VERIFY] Linter failed!`));
1964
- return { success: false, errorLogs: res.logs };
1965
- }
1966
- console.log(pc.green(`[VERIFY] Linter passed.`));
1967
- }
1968
- if (fs.existsSync(path.join(cwd, 'package.json'))) {
1969
- try {
1970
- const pkg = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'));
1971
- if (pkg.scripts && pkg.scripts['sync-docs']) {
1972
- console.log(pc.cyan(`[VERIFY] Syncing documentation with latest command registry...`));
1973
- await runCmd('npm run sync-docs');
1974
- }
1975
- }
1976
- catch { /* ignored */ }
1977
- }
1978
- return { success: true };
1979
- }
1980
- isBuildErrorRelated(errorLogs, modifiedFiles) {
1981
- if (!errorLogs)
1982
- return false;
1983
- const lowerLogs = errorLogs.toLowerCase();
1984
- const configFiles = ['tsconfig.json', 'package.json', 'package-lock.json', 'cargo.toml', 'go.mod', 'requirements.txt'];
1985
- for (const file of modifiedFiles) {
1986
- const basename = path.basename(file).toLowerCase();
1987
- if (configFiles.includes(basename)) {
1988
- return true;
1989
- }
1990
- const relativePath = path.relative(this.toolContext.projectRoot || process.cwd(), file).replace(/\\/g, '/').toLowerCase();
1991
- if (lowerLogs.includes(basename) || lowerLogs.includes(relativePath)) {
1992
- return true;
1993
- }
1994
- }
1995
- return false;
1996
- }
1997
- generateBuildErrorHint(errorLogs) {
1998
- if (!errorLogs)
1999
- return '';
2000
- const hints = [];
2001
- const missingModuleMatch = errorLogs.match(/cannot find module ['"]([^'"]+)['"]/i) ||
2002
- errorLogs.match(/could not resolve ['"]([^'"]+)['"]/i);
2003
- if (missingModuleMatch) {
2004
- const pkg = missingModuleMatch[1];
2005
- hints.push(`Hint: A required package "${pkg}" is missing. Use the terminal tool to install it (e.g., "npm install ${pkg}").`);
2006
- }
2007
- if (errorLogs.toLowerCase().includes('duplicate page detected') ||
2008
- (errorLogs.includes('pages/') && errorLogs.includes('src/pages/'))) {
2009
- hints.push('Hint: Next.js detected duplicate pages in both pages/ and src/pages/. You must delete the duplicate files in the root pages/ directory to resolve the conflict.');
2010
- }
2011
- if (errorLogs.toLowerCase().includes('overload') || errorLogs.toLowerCase().includes('no overload matches')) {
2012
- hints.push('Hint: TypeScript has type overload resolution issues. Try casting the options/arguments as "any" (e.g., "options as any") to bypass strict type checking.');
2013
- }
2014
- if (hints.length === 0)
2015
- return '';
2016
- return '\n\n' + hints.join('\n');
2017
- }
2018
1380
  }
2019
1381
  //# sourceMappingURL=orchestrator.js.map