@haoyiyin/workflow 0.2.10 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/dist/src/agents/contracts/implementer.d.ts +29 -0
  2. package/dist/src/agents/contracts/implementer.d.ts.map +1 -0
  3. package/dist/src/agents/contracts/implementer.js +94 -0
  4. package/dist/src/agents/contracts/implementer.js.map +1 -0
  5. package/dist/src/agents/contracts/index.d.ts +11 -0
  6. package/dist/src/agents/contracts/index.d.ts.map +1 -0
  7. package/dist/src/agents/contracts/index.js +11 -0
  8. package/dist/src/agents/contracts/index.js.map +1 -0
  9. package/dist/src/agents/contracts/planner.d.ts +25 -0
  10. package/dist/src/agents/contracts/planner.d.ts.map +1 -0
  11. package/dist/src/agents/contracts/planner.js +107 -0
  12. package/dist/src/agents/contracts/planner.js.map +1 -0
  13. package/dist/src/agents/contracts/router.d.ts +24 -0
  14. package/dist/src/agents/contracts/router.d.ts.map +1 -0
  15. package/dist/src/agents/contracts/router.js +137 -0
  16. package/dist/src/agents/contracts/router.js.map +1 -0
  17. package/dist/src/agents/contracts/verifier.d.ts +27 -0
  18. package/dist/src/agents/contracts/verifier.d.ts.map +1 -0
  19. package/dist/src/agents/contracts/verifier.js +115 -0
  20. package/dist/src/agents/contracts/verifier.js.map +1 -0
  21. package/dist/src/agents/dispatcher.d.ts +94 -51
  22. package/dist/src/agents/dispatcher.d.ts.map +1 -1
  23. package/dist/src/agents/dispatcher.js +207 -164
  24. package/dist/src/agents/dispatcher.js.map +1 -1
  25. package/dist/src/persistence/index.d.ts +4 -2
  26. package/dist/src/persistence/index.d.ts.map +1 -1
  27. package/dist/src/persistence/index.js +4 -1
  28. package/dist/src/persistence/index.js.map +1 -1
  29. package/dist/src/persistence/plan-md.d.ts +3 -2
  30. package/dist/src/persistence/plan-md.d.ts.map +1 -1
  31. package/dist/src/persistence/plan-md.js +47 -15
  32. package/dist/src/persistence/plan-md.js.map +1 -1
  33. package/dist/src/persistence/state-md.d.ts +2 -0
  34. package/dist/src/persistence/state-md.d.ts.map +1 -1
  35. package/dist/src/persistence/state-md.js +40 -22
  36. package/dist/src/persistence/state-md.js.map +1 -1
  37. package/dist/src/persistence/types.d.ts +35 -39
  38. package/dist/src/persistence/types.d.ts.map +1 -1
  39. package/dist/src/router/namespace/core/intent-router.d.ts +24 -0
  40. package/dist/src/router/namespace/core/intent-router.d.ts.map +1 -0
  41. package/dist/src/router/namespace/core/intent-router.js +190 -0
  42. package/dist/src/router/namespace/core/intent-router.js.map +1 -0
  43. package/dist/src/router/namespace/core/lifecycle-router.d.ts +28 -0
  44. package/dist/src/router/namespace/core/lifecycle-router.d.ts.map +1 -0
  45. package/dist/src/router/namespace/core/lifecycle-router.js +132 -0
  46. package/dist/src/router/namespace/core/lifecycle-router.js.map +1 -0
  47. package/dist/src/router/namespace/core/state-router.d.ts +32 -0
  48. package/dist/src/router/namespace/core/state-router.d.ts.map +1 -0
  49. package/dist/src/router/namespace/core/state-router.js +157 -0
  50. package/dist/src/router/namespace/core/state-router.js.map +1 -0
  51. package/dist/src/router/namespace/domain/code-router.d.ts +26 -0
  52. package/dist/src/router/namespace/domain/code-router.d.ts.map +1 -0
  53. package/dist/src/router/namespace/domain/code-router.js +171 -0
  54. package/dist/src/router/namespace/domain/code-router.js.map +1 -0
  55. package/dist/src/router/namespace/domain/debug-router.d.ts +25 -0
  56. package/dist/src/router/namespace/domain/debug-router.d.ts.map +1 -0
  57. package/dist/src/router/namespace/domain/debug-router.js +139 -0
  58. package/dist/src/router/namespace/domain/debug-router.js.map +1 -0
  59. package/dist/src/router/namespace/domain/plan-router.d.ts +29 -0
  60. package/dist/src/router/namespace/domain/plan-router.d.ts.map +1 -0
  61. package/dist/src/router/namespace/domain/plan-router.js +160 -0
  62. package/dist/src/router/namespace/domain/plan-router.js.map +1 -0
  63. package/dist/src/router/namespace/domain/review-router.d.ts +24 -0
  64. package/dist/src/router/namespace/domain/review-router.d.ts.map +1 -0
  65. package/dist/src/router/namespace/domain/review-router.js +116 -0
  66. package/dist/src/router/namespace/domain/review-router.js.map +1 -0
  67. package/dist/src/router/namespace/index.d.ts +19 -0
  68. package/dist/src/router/namespace/index.d.ts.map +1 -0
  69. package/dist/src/router/namespace/index.js +22 -0
  70. package/dist/src/router/namespace/index.js.map +1 -0
  71. package/dist/src/router/namespace/registry.d.ts +67 -0
  72. package/dist/src/router/namespace/registry.d.ts.map +1 -0
  73. package/dist/src/router/namespace/registry.js +197 -0
  74. package/dist/src/router/namespace/registry.js.map +1 -0
  75. package/dist/src/router/namespace/types.d.ts +124 -0
  76. package/dist/src/router/namespace/types.d.ts.map +1 -0
  77. package/dist/src/router/namespace/types.js +20 -0
  78. package/dist/src/router/namespace/types.js.map +1 -0
  79. package/dist/src/router/namespace/utility/fallback-router.d.ts +28 -0
  80. package/dist/src/router/namespace/utility/fallback-router.d.ts.map +1 -0
  81. package/dist/src/router/namespace/utility/fallback-router.js +88 -0
  82. package/dist/src/router/namespace/utility/fallback-router.js.map +1 -0
  83. package/dist/src/router/namespace/utility/quick-task-router.d.ts +28 -0
  84. package/dist/src/router/namespace/utility/quick-task-router.d.ts.map +1 -0
  85. package/dist/src/router/namespace/utility/quick-task-router.js +99 -0
  86. package/dist/src/router/namespace/utility/quick-task-router.js.map +1 -0
  87. package/dist/src/router/namespace/utility/research-router.d.ts +24 -0
  88. package/dist/src/router/namespace/utility/research-router.d.ts.map +1 -0
  89. package/dist/src/router/namespace/utility/research-router.js +84 -0
  90. package/dist/src/router/namespace/utility/research-router.js.map +1 -0
  91. package/dist/src/skills/agents-md/index.js +2 -2
  92. package/dist/src/skills/agents-md/index.js.map +1 -1
  93. package/dist/src/skills/execute-plan/index.d.ts +45 -65
  94. package/dist/src/skills/execute-plan/index.d.ts.map +1 -1
  95. package/dist/src/skills/execute-plan/index.js +325 -551
  96. package/dist/src/skills/execute-plan/index.js.map +1 -1
  97. package/dist/src/skills/index.d.ts +1 -0
  98. package/dist/src/skills/index.d.ts.map +1 -1
  99. package/dist/src/skills/index.js +1 -0
  100. package/dist/src/skills/index.js.map +1 -1
  101. package/dist/src/skills/quick-task/index.d.ts +4 -4
  102. package/dist/src/skills/quick-task/index.js +1 -1
  103. package/dist/src/skills/quick-task/index.js.map +1 -1
  104. package/dist/src/skills/review-diff/index.d.ts +6 -6
  105. package/dist/src/skills/review-diff/index.js +1 -1
  106. package/dist/src/skills/review-diff/index.js.map +1 -1
  107. package/dist/src/skills/router/index.d.ts +101 -0
  108. package/dist/src/skills/router/index.d.ts.map +1 -0
  109. package/dist/src/skills/router/index.js +450 -0
  110. package/dist/src/skills/router/index.js.map +1 -0
  111. package/dist/src/skills/router/types.d.ts +79 -0
  112. package/dist/src/skills/router/types.d.ts.map +1 -0
  113. package/dist/src/skills/router/types.js +8 -0
  114. package/dist/src/skills/router/types.js.map +1 -0
  115. package/dist/src/skills/systematic-debugging/index.js +1 -1
  116. package/dist/src/skills/systematic-debugging/index.js.map +1 -1
  117. package/dist/src/skills/tdd/index.d.ts +14 -14
  118. package/dist/src/skills/tdd/index.js +1 -1
  119. package/dist/src/skills/tdd/index.js.map +1 -1
  120. package/dist/src/skills/to-plan/index-enhanced.d.ts +4 -4
  121. package/dist/src/skills/to-plan/index-enhanced.d.ts.map +1 -1
  122. package/dist/src/skills/to-plan/index-enhanced.js +3 -5
  123. package/dist/src/skills/to-plan/index-enhanced.js.map +1 -1
  124. package/dist/src/skills/to-plan/index.d.ts +24 -91
  125. package/dist/src/skills/to-plan/index.d.ts.map +1 -1
  126. package/dist/src/skills/to-plan/index.js +214 -409
  127. package/dist/src/skills/to-plan/index.js.map +1 -1
  128. package/package.json +3 -5
  129. package/src/agents/contracts/implementer.ts +122 -0
  130. package/src/agents/contracts/index.ts +27 -0
  131. package/src/agents/contracts/planner.ts +129 -0
  132. package/src/agents/contracts/router.ts +168 -0
  133. package/src/agents/contracts/verifier.ts +137 -0
  134. package/src/agents/dispatcher.ts +387 -362
  135. package/src/persistence/index.ts +10 -4
  136. package/src/persistence/plan-md.ts +52 -18
  137. package/src/persistence/state-md.ts +45 -23
  138. package/src/persistence/types.ts +37 -40
  139. package/src/router/namespace/README.md +127 -0
  140. package/src/router/namespace/core/intent-router.ts +221 -0
  141. package/src/router/namespace/core/lifecycle-router.ts +156 -0
  142. package/src/router/namespace/core/state-router.ts +192 -0
  143. package/src/router/namespace/domain/code-router.ts +202 -0
  144. package/src/router/namespace/domain/debug-router.ts +167 -0
  145. package/src/router/namespace/domain/plan-router.ts +196 -0
  146. package/src/router/namespace/domain/review-router.ts +142 -0
  147. package/src/router/namespace/index.ts +84 -0
  148. package/src/router/namespace/registry.ts +242 -0
  149. package/src/router/namespace/types.ts +182 -0
  150. package/src/router/namespace/utility/fallback-router.ts +107 -0
  151. package/src/router/namespace/utility/quick-task-router.ts +121 -0
  152. package/src/router/namespace/utility/research-router.ts +105 -0
  153. package/src/skills/agents-md/index.ts +2 -2
  154. package/src/skills/execute-plan/index.ts +419 -673
  155. package/src/skills/index.ts +1 -0
  156. package/src/skills/quick-task/index.ts +1 -1
  157. package/src/skills/review-diff/index.ts +1 -1
  158. package/src/skills/router/SKILL.md +181 -0
  159. package/src/skills/router/index.ts +577 -0
  160. package/src/skills/router/types.ts +90 -0
  161. package/src/skills/systematic-debugging/index.ts +1 -1
  162. package/src/skills/tdd/index.ts +1 -1
  163. package/src/skills/to-plan/index-enhanced.ts +3 -5
  164. package/src/skills/to-plan/index.ts +231 -502
@@ -1,15 +1,18 @@
1
1
  /**
2
- * Enhanced Execute Plan Skill - Orchestrates TDD and Review skills
2
+ * Execute Plan Skill - Orchestrates plan execution with persistence integration
3
3
  *
4
4
  * Architecture: Orchestrator pattern
5
- * - Executes waves sequentially
6
- * - Each task uses TDD skill (RED/GREEN/REFACTOR)
7
- * - Verification uses review-diff skill
8
- * - Lifecycle managed via hooks
9
- * - Task-level worktree isolation via worktreeContract subagent
5
+ * - Reads PLAN.md from PlanMdManager
6
+ * - Reads/updates STATE.md for progress tracking
7
+ * - Executes waves sequentially, tasks within wave in parallel
8
+ * - Each task dispatches implementer subagent with fresh context
9
+ * - After each wave: updates STATE.md with completed tasks
10
+ * - After all waves: runs verification subagents (goals, quality, tests)
11
+ * - Updates final state based on verification results
10
12
  */
11
13
  import { z } from 'zod';
12
14
  import { Skill } from '../skill.js';
15
+ import { implementerContract, verifierContract, reviewerContract } from '../../agents/contracts.js';
13
16
  // HookType constants (avoiding enum import issues)
14
17
  const HookType = {
15
18
  PRE_EXECUTE: 'pre-execute',
@@ -24,87 +27,93 @@ const HookType = {
24
27
  WAVE_START: 'wave-start',
25
28
  WAVE_COMPLETE: 'wave-complete',
26
29
  };
27
- import { worktreeContract } from '../../agents/contracts.js';
28
30
  const ExecuteInputSchema = z.object({
29
- plan: z.any(),
30
- waves: z.array(z.any()),
31
+ model: z.string().optional(),
31
32
  skipVerify: z.boolean().optional(),
32
- model: z.string().optional()
33
+ continueFromState: z.boolean().optional(),
33
34
  });
34
35
  const ExecuteOutputSchema = z.object({
35
36
  success: z.boolean(),
36
37
  results: z.array(z.any()),
37
38
  verification: z.any(),
38
39
  wavesCompleted: z.number(),
39
- totalWaves: z.number()
40
+ totalWaves: z.number(),
40
41
  });
41
42
  export class ExecutePlanSkill extends Skill {
42
43
  constructor() {
43
44
  super({
44
45
  name: 'execute-plan',
45
- description: 'Orchestrate plan execution with task-level worktree isolation using TDD skill and review-diff for verification',
46
- requires: ['tdd', 'review-diff']
46
+ description: 'Execute plan from PLAN.md with STATE.md tracking, parallel task execution, and verification',
47
+ requires: []
47
48
  });
48
49
  }
49
50
  async execute(input, context) {
50
- const { plan, waves } = input;
51
51
  const results = [];
52
52
  let wavesCompleted = 0;
53
- // 1. Update state
54
- if (context.persistence) {
55
- await context.persistence.state.updatePhase('executing');
53
+ // 1. Read PLAN.md from PlanMdManager
54
+ if (!context.persistence) {
55
+ throw new Error('Persistence manager required for execute-plan skill');
56
56
  }
57
- // 2. Trigger pre-execute hooks
58
- await this.triggerHook(context, HookType.PRE_EXECUTE, { plan, waves });
57
+ const plan = await context.persistence.plan.readPlan();
58
+ if (!plan) {
59
+ throw new Error('No PLAN.md found. Run plan skill first.');
60
+ }
61
+ // 2. Read STATE.md for current progress
62
+ const currentState = await context.persistence.state.readCurrentState();
63
+ console.log(`\n[ExecutePlanSkill] Goal: ${plan.goal}`);
64
+ console.log(`[ExecutePlanSkill] Current phase: ${currentState.phase}`);
65
+ console.log(`[ExecutePlanSkill] Total waves: ${plan.waves.length}, Tasks: ${plan.tasks.length}`);
66
+ // Update state to executing phase
67
+ await context.persistence.state.updatePhase('executing');
68
+ // 3. Trigger pre-execute hooks
69
+ await this.triggerHook(context, HookType.PRE_EXECUTE, { plan, state: currentState });
59
70
  try {
60
- // 3. Execute waves sequentially
61
- for (const wave of waves) {
71
+ // Determine starting point if continuing from state
72
+ let startWaveIndex = 0;
73
+ if (input.continueFromState && currentState.completedTasks.length > 0) {
74
+ startWaveIndex = this.findNextWaveIndex(plan, currentState.completedTasks);
75
+ console.log(`[ExecutePlanSkill] Resuming from wave ${startWaveIndex + 1}`);
76
+ }
77
+ // 4. Execute waves sequentially
78
+ for (let i = startWaveIndex; i < plan.waves.length; i++) {
79
+ const wave = plan.waves[i];
62
80
  console.log(`\n[ExecutePlanSkill] Wave ${wave.id}: ${wave.tasks.length} tasks`);
63
81
  // Trigger wave start hooks
64
- await this.triggerHook(context, HookType.WAVE_START, { wave, plan });
65
- // Execute wave using TDD skill for each task
66
- const waveResults = await this.executeWaveWithTDD(wave, plan, input.model, context);
67
- results.push(...waveResults);
68
- // Update state with completed tasks
69
- if (context.persistence) {
70
- for (const result of waveResults) {
71
- await context.persistence.state.recordTaskComplete(result.taskId, result);
72
- }
73
- }
74
- // Merge task worktrees back to main branch
75
- const successfulResults = waveResults.filter(r => r.success && r.branchName);
76
- let mergeResult = { merged: [], conflicts: [], manualResolution: [] };
77
- if (successfulResults.length > 0) {
78
- mergeResult = await this.mergeTaskWorktrees(successfulResults, context);
79
- // Report merge status
80
- if (mergeResult.conflicts.length > 0) {
81
- console.warn(` [Merge] Auto-resolved conflicts in: ${mergeResult.conflicts.join(', ')}`);
82
+ await this.triggerHook(context, HookType.WAVE_START, { wave, plan, waveIndex: i });
83
+ // Execute wave with parallel tasks
84
+ const waveResult = await this.executeWave(wave, plan, input.model, context);
85
+ results.push(...waveResult.results);
86
+ // 5. After each wave: update STATE.md with completed tasks
87
+ for (const taskResult of waveResult.results) {
88
+ if (taskResult.success) {
89
+ await context.persistence.state.recordTaskComplete(taskResult.taskId, taskResult);
90
+ await context.persistence.plan.updateTaskStatus(taskResult.taskId, 'complete');
82
91
  }
83
- if (mergeResult.manualResolution.length > 0) {
84
- console.error(` [Merge] Needs manual resolution: ${mergeResult.manualResolution.join(', ')}`);
92
+ else {
93
+ await context.persistence.state.recordTaskFailed(taskResult.taskId, taskResult.error);
94
+ await context.persistence.plan.updateTaskStatus(taskResult.taskId, 'pending');
85
95
  }
86
96
  }
87
- // Trigger wave complete hooks (with merge info)
97
+ // Trigger wave complete hooks
88
98
  await this.triggerHook(context, HookType.WAVE_COMPLETE, {
89
99
  wave,
90
- results: waveResults,
91
- plan,
92
- mergeResult
100
+ results: waveResult.results,
101
+ plan
93
102
  });
94
103
  wavesCompleted++;
95
- // Check for failures - fail-fast
96
- const failedTasks = waveResults.filter(r => !r.success);
97
- if (failedTasks.length > 0) {
98
- console.error(`[ExecutePlanSkill] Wave ${wave.id} failed:`, failedTasks.map(t => t.taskId));
99
- await this.handleWaveFailure(wave, failedTasks, context);
104
+ // Error handling for wave failures
105
+ if (!waveResult.success) {
106
+ await this.handleWaveFailure(wave, waveResult.results, context);
107
+ // Fail-fast: stop execution on wave failure
108
+ console.error(`[ExecutePlanSkill] Wave ${wave.id} failed, stopping execution`);
100
109
  break;
101
110
  }
102
111
  console.log(`[ExecutePlanSkill] Wave ${wave.id} complete ✓`);
103
112
  }
104
- // 4. Run verification using review-diff skill
113
+ // 6. After all waves: run verification subagents (goals, quality, tests)
105
114
  let verification;
106
- if (!input.skipVerify && wavesCompleted === waves.length) {
107
- verification = await this.runVerificationWithReviewDiff(plan, results, context);
115
+ if (!input.skipVerify && wavesCompleted === plan.waves.length) {
116
+ verification = await this.runVerification(plan, results, context);
108
117
  }
109
118
  else {
110
119
  verification = {
@@ -114,166 +123,215 @@ export class ExecutePlanSkill extends Skill {
114
123
  }
115
124
  };
116
125
  }
117
- // 5. Update final state
118
- const finalPhase = verification.success ? 'complete' : 'verifying';
119
- if (context.persistence) {
120
- await context.persistence.state.updatePhase(finalPhase);
121
- await context.persistence.state.recordVerification(verification);
122
- }
123
- // 6. Trigger post-execute hooks
126
+ // 7. Update final state based on verification results
127
+ const finalPhase = verification.success ? 'completed' : 'failed';
128
+ await context.persistence.state.updatePhase(finalPhase);
129
+ await context.persistence.state.recordVerification(verification);
130
+ // 8. Trigger post-execute hooks
124
131
  await this.triggerHook(context, HookType.POST_EXECUTE, {
125
132
  results,
126
133
  verification,
127
134
  wavesCompleted,
128
- totalWaves: waves.length
135
+ totalWaves: plan.waves.length
129
136
  });
130
137
  return {
131
138
  success: results.every(r => r.success) && verification.success,
132
139
  results,
133
140
  verification,
134
141
  wavesCompleted,
135
- totalWaves: waves.length
142
+ totalWaves: plan.waves.length
136
143
  };
137
144
  }
138
145
  catch (error) {
139
- // Trigger error hooks
146
+ // Trigger error hooks and update state
140
147
  await this.triggerHook(context, HookType.ON_ERROR, {
141
148
  phase: 'execute',
142
149
  error: error.message,
143
150
  results
144
151
  });
152
+ // Update state to failed
153
+ await context.persistence.state.updatePhase('failed');
145
154
  throw error;
146
155
  }
147
156
  }
148
157
  /**
149
- * Execute a wave using TDD skill for each task
150
- * Parallel execution with Promise.allSettled
158
+ * Find the next wave index based on completed tasks
151
159
  */
152
- async executeWaveWithTDD(wave, plan, model, context) {
153
- // Get TDD skill from registry
154
- const tddSkill = context.skills.get('tdd');
155
- if (!tddSkill) {
156
- throw new Error('TDD skill not found in registry');
157
- }
158
- // Prepare task contexts
159
- const taskConfigs = wave.tasks.map(taskId => {
160
- const task = plan.tasks.find(t => t.id === taskId);
161
- if (!task)
162
- throw new Error(`Task not found: ${taskId}`);
163
- return { taskId, task };
164
- });
165
- // Execute all tasks in parallel using TDD skill
166
- const promises = taskConfigs.map(async ({ taskId, task }) => {
167
- try {
168
- // Trigger task start hooks
169
- await this.triggerHook(context, HookType.PRE_EXECUTE, { task, wave });
170
- // Execute task with TDD
171
- const tddResult = await this.executeTaskWithTDD(task, model, context, tddSkill);
172
- // Trigger task complete hooks
173
- await this.triggerHook(context, HookType.TASK_COMPLETE, { task, result: tddResult });
174
- return {
175
- taskId,
176
- success: tddResult.status === 'success',
177
- filesModified: tddResult.filesModified || [],
178
- testsAdded: tddResult.testFile ? [tddResult.testFile] : [],
179
- duration: tddResult.duration || 0,
180
- notes: tddResult.summary || '',
181
- phases: tddResult.phases
182
- };
160
+ findNextWaveIndex(plan, completedTasks) {
161
+ const completedSet = new Set(completedTasks);
162
+ for (let i = 0; i < plan.waves.length; i++) {
163
+ const wave = plan.waves[i];
164
+ const waveTaskIds = wave.tasks.map(t => t.id);
165
+ const allCompleted = waveTaskIds.every(id => completedSet.has(id));
166
+ if (!allCompleted) {
167
+ return i;
183
168
  }
184
- catch (error) {
185
- // Task failed
186
- await this.triggerHook(context, HookType.ON_ERROR, { task, error });
187
- return {
188
- taskId,
189
- success: false,
190
- filesModified: [],
191
- testsAdded: [],
192
- duration: 0,
193
- notes: '',
194
- error: error.message
195
- };
196
- }
197
- });
198
- // Wait for all tasks to complete (fail-fast disabled for wave)
169
+ }
170
+ return plan.waves.length;
171
+ }
172
+ /**
173
+ * Execute a wave - tasks within wave run in parallel
174
+ * Each task dispatches an implementer subagent with fresh context
175
+ */
176
+ async executeWave(wave, plan, model, context) {
177
+ const taskResults = [];
178
+ // Filter out already completed tasks
179
+ const pendingTasks = wave.tasks.filter(t => t.status !== 'completed');
180
+ if (pendingTasks.length === 0) {
181
+ console.log(` [Wave ${wave.id}] All tasks already completed`);
182
+ return { waveId: wave.id, results: [], success: true };
183
+ }
184
+ // Update task statuses to in_progress
185
+ for (const task of pendingTasks) {
186
+ await context.persistence?.plan.updateTaskStatus(task.id, 'in_progress');
187
+ }
188
+ // Execute all tasks in parallel using Promise.allSettled
189
+ const promises = pendingTasks.map(task => this.executeTask(task, wave, plan, model, context));
199
190
  const settledResults = await Promise.allSettled(promises);
200
- return settledResults.map((result, index) => {
191
+ // Process results
192
+ let hasFailures = false;
193
+ for (const result of settledResults) {
201
194
  if (result.status === 'fulfilled') {
202
- return result.value;
195
+ taskResults.push(result.value);
196
+ if (!result.value.success) {
197
+ hasFailures = true;
198
+ }
203
199
  }
204
200
  else {
205
- return {
206
- taskId: taskConfigs[index].taskId,
201
+ hasFailures = true;
202
+ taskResults.push({
203
+ taskId: 'unknown',
207
204
  success: false,
208
- filesModified: [],
209
- testsAdded: [],
210
205
  duration: 0,
211
206
  notes: '',
212
207
  error: result.reason?.message || 'Unknown error'
213
- };
208
+ });
214
209
  }
215
- });
210
+ }
211
+ return {
212
+ waveId: wave.id,
213
+ results: taskResults,
214
+ success: !hasFailures
215
+ };
216
216
  }
217
217
  /**
218
- * Execute single task using TDD skill with task-level worktree isolation
219
- * Each task gets its own worktree for true isolation
220
- * Uses worktreeContract instead of worktree-start skill
218
+ * Execute a single task by dispatching implementer subagent
219
+ * Fresh context for each task with proper permissions
221
220
  */
222
- async executeTaskWithTDD(task, model, context, tddSkill) {
223
- // Step 1: Create isolated worktree using worktreeContract subagent
224
- const taskBranchName = `agent/task-${task.id}-${Date.now().toString(36)}`;
225
- console.log(` [Worktree] Creating for ${task.id}: ${taskBranchName}`);
226
- const worktreeResult = await context.dispatcher.dispatch({ role: 'general', model }, worktreeContract({
227
- branchName: taskBranchName,
228
- baseBranch: 'main',
229
- isolation: true
230
- }));
231
- if (worktreeResult.status !== 'success') {
232
- throw new Error(`Failed to create worktree for ${task.id}: ${worktreeResult.errors?.join(', ') || 'Unknown error'}`);
221
+ async executeTask(task, wave, plan, model, context) {
222
+ const startTime = Date.now();
223
+ try {
224
+ // Trigger pre-task hooks
225
+ await this.triggerHook(context, HookType.PRE_TASK, { task, wave, plan });
226
+ console.log(` [Task ${task.id}] ${task.description}`);
227
+ // Determine files to own and read
228
+ const ownedFiles = task.relatedFiles || [];
229
+ const readFiles = this.getRelatedFilesForTask(task, plan);
230
+ // Dispatch implementer subagent with fresh context
231
+ const result = await context.dispatcher.dispatch({ role: 'implementer', model }, implementerContract({
232
+ task: task.description,
233
+ ownedFiles,
234
+ readFiles,
235
+ constraints: [
236
+ 'Follow immutability patterns - create new objects, never mutate',
237
+ 'Handle errors comprehensively with try/catch',
238
+ 'Validate inputs using zod schemas',
239
+ 'Keep functions small (<50 lines)',
240
+ 'Files should be focused (<800 lines)'
241
+ ]
242
+ }));
243
+ const duration = Date.now() - startTime;
244
+ // Parse result
245
+ const output = result.output || '{}';
246
+ const parsed = this.parseImplementerOutput(output);
247
+ const taskResult = {
248
+ taskId: task.id,
249
+ success: parsed.success,
250
+ duration,
251
+ notes: parsed.summary || '',
252
+ filesModified: parsed.filesChanged || []
253
+ };
254
+ // Trigger post-task hooks
255
+ await this.triggerHook(context, HookType.POST_TASK, { task, result: taskResult });
256
+ await this.triggerHook(context, HookType.TASK_COMPLETE, { task, result: taskResult });
257
+ return taskResult;
258
+ }
259
+ catch (error) {
260
+ const duration = Date.now() - startTime;
261
+ // Trigger error hooks
262
+ await this.triggerHook(context, HookType.ON_ERROR, {
263
+ task,
264
+ error: error.message
265
+ });
266
+ return {
267
+ taskId: task.id,
268
+ success: false,
269
+ duration,
270
+ notes: '',
271
+ error: error.message
272
+ };
233
273
  }
234
- // Parse worktree output
235
- const worktreeData = this.parseWorktreeOutput(worktreeResult.output);
236
- const worktreePath = worktreeData.worktreePath;
237
- console.log(` [Worktree] Created at: ${worktreePath}`);
238
- // Step 2: Determine target file and test file from task
239
- const targetFile = task.relatedFiles?.[0] || 'src/index.ts';
240
- const testFile = targetFile.replace(/\.ts$/, '.test.ts');
241
- console.log(` [TDD] ${task.id}: ${task.description}`);
242
- // Step 3: Execute TDD skill - it will use isolation: 'worktree' internally
243
- // The TDD skill's implementer subagents will work within their own worktrees
244
- // but the task-level worktree is already isolated
245
- const tddResult = await tddSkill.execute({
246
- behavior: task.description,
247
- targetFile,
248
- testFile,
249
- testLevel: 'unit',
250
- model
251
- }, context);
252
- // Step 4: Record worktree info in result for potential merge later
253
- return {
254
- ...tddResult,
255
- worktreePath,
256
- branchName: taskBranchName
257
- };
258
274
  }
259
275
  /**
260
- * Run verification using review-diff skill
261
- * Returns structured verification result
276
+ * Get related files for a task based on dependencies
262
277
  */
263
- async runVerificationWithReviewDiff(plan, results, context) {
264
- console.log('\n[ExecutePlanSkill] Running verification...');
265
- if (context.persistence) {
266
- await context.persistence.state.updatePhase('verifying');
278
+ getRelatedFilesForTask(task, plan) {
279
+ const files = new Set();
280
+ // Add files from task's relatedFiles
281
+ if (task.relatedFiles) {
282
+ task.relatedFiles.forEach(f => files.add(f));
283
+ }
284
+ // Add files from dependency tasks
285
+ const depTaskIds = plan.dependencies[task.id] || [];
286
+ for (const depId of depTaskIds) {
287
+ const depTask = plan.tasks.find(t => t.id === depId);
288
+ if (depTask?.relatedFiles) {
289
+ depTask.relatedFiles.forEach(f => files.add(f));
290
+ }
291
+ }
292
+ return Array.from(files);
293
+ }
294
+ /**
295
+ * Parse implementer subagent output
296
+ */
297
+ parseImplementerOutput(output) {
298
+ try {
299
+ const jsonMatch = output.match(/```json\s*([\s\S]*?)\s*```/);
300
+ if (jsonMatch) {
301
+ const parsed = JSON.parse(jsonMatch[1]);
302
+ return {
303
+ success: parsed.success ?? true,
304
+ summary: parsed.summary,
305
+ filesChanged: parsed.filesChanged || []
306
+ };
307
+ }
308
+ // Fallback: check for success indicators
309
+ if (output.includes('success') || output.includes('completed')) {
310
+ return { success: true };
311
+ }
312
+ return { success: false, error: 'Could not parse output' };
267
313
  }
268
- const reviewSkill = context.skills.get('review-diff');
269
- if (!reviewSkill) {
270
- console.warn('[ExecutePlanSkill] review-diff skill not found, skipping verification');
271
- return { success: true, dimensions: {} };
314
+ catch {
315
+ return { success: false, error: 'Failed to parse JSON output' };
272
316
  }
273
- // Parallel verification dimensions
317
+ }
318
+ /**
319
+ * Run verification subagents after all waves complete
320
+ * - Goals verification: verifierContract
321
+ * - Quality verification: reviewerContract
322
+ * - Tests verification: dispatcher with test runner
323
+ */
324
+ async runVerification(plan, results, context) {
325
+ console.log('\n[ExecutePlanSkill] Running verification...');
326
+ await context.persistence?.state.updatePhase('executing');
327
+ // Get changed files from results
328
+ const changedFiles = results
329
+ .flatMap(r => r.filesModified || [])
330
+ .filter((f, i, arr) => arr.indexOf(f) === i); // unique
331
+ // Run verification dimensions in parallel
274
332
  const [goalsResult, qualityResult, testsResult] = await Promise.all([
275
- this.verifyGoalsWithReviewDiff(plan, context, reviewSkill),
276
- this.verifyQualityWithReviewDiff(results, context, reviewSkill),
333
+ this.verifyGoals(plan, changedFiles, context),
334
+ this.verifyQuality(changedFiles, context),
277
335
  this.verifyTests(results, context)
278
336
  ]);
279
337
  const verification = {
@@ -285,26 +343,31 @@ export class ExecutePlanSkill extends Skill {
285
343
  }
286
344
  };
287
345
  console.log(`[ExecutePlanSkill] Verification: ${verification.success ? 'PASSED' : 'FAILED'}`);
346
+ console.log(` - Goals: ${goalsResult.success ? '✓' : '✗'}`);
347
+ console.log(` - Quality: ${qualityResult.success ? '✓' : '✗'}`);
348
+ console.log(` - Tests: ${testsResult.success ? '✓' : '✗'}`);
288
349
  return verification;
289
350
  }
290
351
  /**
291
- * Verify goals using review-diff skill
352
+ * Verify goals using verifierContract subagent
292
353
  */
293
- async verifyGoalsWithReviewDiff(plan, context, reviewSkill) {
354
+ async verifyGoals(plan, changedFiles, context) {
294
355
  try {
295
- // Review current changes against plan
296
- const review = await reviewSkill.execute({
297
- target: '.',
298
- targetType: 'branch',
299
- focus: ['correctness'],
300
- planPath: '.pi/state/PLAN.md'
301
- }, context);
302
- const passed = review.specCompliance === 'pass' || review.specCompliance === 'partial';
356
+ const result = await context.dispatcher.dispatch({ role: 'verifier' }, verifierContract({
357
+ goal: plan.goal,
358
+ plan: this.formatPlanSummary(plan),
359
+ changedFiles,
360
+ acceptanceCriteria: plan.verificationCriteria
361
+ }));
362
+ const output = result.output || '{}';
363
+ const parsed = this.parseVerifierOutput(output);
303
364
  return {
304
- success: passed,
305
- details: review,
306
- evidence: review.strengths,
307
- gaps: passed ? [] : review.issues?.filter((i) => i.severity === 'blocker' || i.severity === 'major').map((i) => i.description)
365
+ success: parsed.verdict === 'pass',
366
+ details: parsed,
367
+ evidence: parsed.criteriaResults
368
+ ?.filter((c) => c.status === 'pass')
369
+ .map((c) => c.criterion),
370
+ gaps: parsed.gaps || []
308
371
  };
309
372
  }
310
373
  catch (error) {
@@ -315,21 +378,30 @@ export class ExecutePlanSkill extends Skill {
315
378
  }
316
379
  }
317
380
  /**
318
- * Verify code quality using review-diff skill
381
+ * Verify quality using reviewerContract subagent
319
382
  */
320
- async verifyQualityWithReviewDiff(results, context, reviewSkill) {
383
+ async verifyQuality(changedFiles, context) {
321
384
  try {
322
- const review = await reviewSkill.execute({
323
- target: '.',
324
- targetType: 'branch',
325
- focus: ['security', 'performance', 'style']
326
- }, context);
327
- const hasBlockers = review.issues?.some((i) => i.severity === 'blocker');
385
+ const result = await context.dispatcher.dispatch({ role: 'reviewer' }, reviewerContract({
386
+ target: 'Implementation quality review',
387
+ reviewFiles: changedFiles,
388
+ focusAreas: ['correctness', 'security', 'performance', 'style'],
389
+ criteria: [
390
+ 'No hardcoded secrets',
391
+ 'Proper error handling',
392
+ 'Input validation',
393
+ 'Immutability patterns',
394
+ 'Test coverage >= 80%'
395
+ ]
396
+ }));
397
+ const output = result.output || '{}';
398
+ const parsed = this.parseReviewerOutput(output);
399
+ const hasCritical = parsed.findings?.some((f) => f.severity === 'critical');
328
400
  return {
329
- success: !hasBlockers && review.decision !== 'blocked',
330
- details: review,
331
- evidence: review.strengths,
332
- gaps: hasBlockers ? ['Critical issues found'] : []
401
+ success: !hasCritical && parsed.verdict !== 'request_changes',
402
+ details: parsed,
403
+ evidence: parsed.strengths,
404
+ gaps: hasCritical ? ['Critical issues found'] : []
333
405
  };
334
406
  }
335
407
  catch (error) {
@@ -340,7 +412,7 @@ export class ExecutePlanSkill extends Skill {
340
412
  }
341
413
  }
342
414
  /**
343
- * Verify tests by checking TDD results
415
+ * Verify tests by running test suite
344
416
  */
345
417
  async verifyTests(results, context) {
346
418
  // Check if all tasks have tests
@@ -348,10 +420,7 @@ export class ExecutePlanSkill extends Skill {
348
420
  const allPassed = results.every(r => r.success);
349
421
  // Try to run full test suite
350
422
  try {
351
- const testResult = await context.dispatcher.dispatch({
352
- role: 'verifier',
353
- timeout: 120000
354
- }, {
423
+ const testResult = await context.dispatcher.dispatch({ role: 'verifier', timeout: 120000 }, {
355
424
  permissions: {
356
425
  readFiles: true,
357
426
  searchCode: true,
@@ -359,16 +428,30 @@ export class ExecutePlanSkill extends Skill {
359
428
  writeFiles: false,
360
429
  gitOperations: false
361
430
  },
362
- prompt: `Run the full test suite and report coverage.\n\nReturn JSON: { "passed": boolean, "coverage": number }`,
431
+ prompt: `Run the full test suite and report coverage.
432
+
433
+ Commands:
434
+ 1. npm test -- --coverage --watchAll=false 2>&1 || true
435
+ 2. cat coverage/lcov-report/index.html 2>/dev/null | grep -o '[^>]*%' | head -1 || echo "N/A"
436
+
437
+ Return JSON:
438
+ {
439
+ "passed": boolean,
440
+ "coverage": number,
441
+ "testCount": number,
442
+ "failedTests": number
443
+ }`,
363
444
  owns: [],
364
445
  reads: []
365
446
  });
366
- const coverageOk = testResult.coverage >= 80;
447
+ const output = testResult.output || '{}';
448
+ const parsed = this.parseTestOutput(output);
449
+ const coverageOk = parsed.coverage >= 80;
367
450
  return {
368
- success: allHaveTests && allPassed && coverageOk,
369
- details: { coverage: testResult.coverage, allHaveTests, allPassed },
370
- evidence: coverageOk ? [`Coverage: ${testResult.coverage}%`] : [],
371
- gaps: !coverageOk ? [`Coverage ${testResult.coverage}% < 80%`] : []
451
+ success: allHaveTests && allPassed && coverageOk && parsed.passed,
452
+ details: { coverage: parsed.coverage, allHaveTests, allPassed },
453
+ evidence: coverageOk ? [`Coverage: ${parsed.coverage}%`] : [],
454
+ gaps: !coverageOk ? [`Coverage ${parsed.coverage}% < 80%`] : []
372
455
  };
373
456
  }
374
457
  catch {
@@ -382,388 +465,79 @@ export class ExecutePlanSkill extends Skill {
382
465
  }
383
466
  }
384
467
  /**
385
- * Handle wave failure
468
+ * Handle wave failure with proper error handling
386
469
  */
387
- async handleWaveFailure(wave, failedTasks, context) {
388
- console.error(`[ExecutePlanSkill] Wave failure handler`);
470
+ async handleWaveFailure(wave, failedResults, context) {
471
+ const failedTasks = failedResults.filter(r => !r.success);
472
+ console.error(`\n[ExecutePlanSkill] Wave ${wave.id} failure handler:`);
473
+ console.error(` Failed tasks: ${failedTasks.map(t => t.taskId).join(', ')}`);
474
+ // Update state for failed tasks
475
+ for (const result of failedTasks) {
476
+ await context.persistence?.state.recordTaskFailed(result.taskId, result.error);
477
+ }
478
+ // Trigger error hooks
389
479
  await this.triggerHook(context, HookType.ON_ERROR, {
390
- phase: 'execute',
480
+ phase: 'wave',
391
481
  wave,
392
482
  failedTasks
393
483
  });
394
484
  }
395
485
  /**
396
- * Merge completed task worktrees back to main worktree with automatic conflict resolution
397
- * Called after each successful wave
398
- *
399
- * Conflict Resolution Strategy:
400
- * 1. Try merge --no-ff
401
- * 2. If conflict, analyze with debugger subagent
402
- * 3. Auto-resolve based on conflict type (imports, adjacent changes, etc.)
403
- * 4. If cannot auto-resolve, try rebase-merging or manual merge
404
- * 5. Last resort: mark for manual resolution
405
- */
406
- async mergeTaskWorktrees(waveResults, context) {
407
- console.log('[ExecutePlanSkill] Merging task worktrees...');
408
- const merged = [];
409
- const conflicts = [];
410
- const manualResolution = [];
411
- for (const result of waveResults) {
412
- if (!result.success || !result.branchName)
413
- continue;
414
- try {
415
- const mergeResult = await this.mergeSingleBranch(result.taskId, result.branchName, context);
416
- if (mergeResult.status === 'success') {
417
- merged.push(result.taskId);
418
- console.log(` [Merge] ${result.taskId}: ✓ ${result.branchName}`);
419
- }
420
- else if (mergeResult.status === 'auto-resolved') {
421
- merged.push(result.taskId);
422
- conflicts.push(`${result.taskId} (auto-resolved: ${mergeResult.conflicts.join(', ')})`);
423
- console.log(` [Merge] ${result.taskId}: ✓ (auto-resolved ${mergeResult.conflicts.length} conflicts)`);
424
- }
425
- else {
426
- manualResolution.push(result.taskId);
427
- console.error(` [Merge] ${result.taskId}: ✗ needs manual resolution`);
428
- }
429
- }
430
- catch (error) {
431
- manualResolution.push(result.taskId);
432
- console.error(` [Merge] ${result.taskId}: ✗ ${error.message}`);
433
- }
434
- }
435
- return { merged, conflicts, manualResolution };
436
- }
437
- /**
438
- * Merge a single branch with conflict detection and auto-resolution
439
- */
440
- async mergeSingleBranch(taskId, branchName, context) {
441
- // Step 1: Attempt merge
442
- const mergeAttempt = await context.dispatcher.dispatch({
443
- role: 'general',
444
- timeout: 60000
445
- }, {
446
- permissions: {
447
- readFiles: true,
448
- searchCode: false,
449
- runCommands: true,
450
- writeFiles: false,
451
- gitOperations: true
452
- },
453
- prompt: `Attempt to merge branch ${branchName} to current branch.
454
-
455
- Commands:
456
- 1. git merge ${branchName} --no-ff -m "Merge ${branchName}" 2>&1
457
- 2. Check exit code and output
458
-
459
- If successful: report status=success
460
- If conflicts: list conflicting files with git diff --name-only --diff-filter=U
461
-
462
- Return JSON:
463
- {
464
- "status": "success|conflict",
465
- "conflictingFiles": ["path/to/file"],
466
- "mergeOutput": "..."
467
- }`,
468
- owns: [],
469
- reads: []
470
- });
471
- const mergeResult = this.parseMergeResult(mergeAttempt.output);
472
- if (mergeResult.status === 'success') {
473
- return { status: 'success', conflicts: [] };
474
- }
475
- // Step 2: Auto-resolve conflicts
476
- console.log(` [Merge] ${taskId}: Detected conflicts in ${mergeResult.conflictingFiles.length} files`);
477
- const resolved = await this.autoResolveConflicts(mergeResult.conflictingFiles, branchName, context);
478
- if (resolved.success) {
479
- return { status: 'auto-resolved', conflicts: mergeResult.conflictingFiles };
480
- }
481
- // Step 3: Abort and mark for manual resolution
482
- await context.dispatcher.dispatch({
483
- role: 'general',
484
- timeout: 30000
485
- }, {
486
- permissions: {
487
- readFiles: false,
488
- searchCode: false,
489
- runCommands: true,
490
- writeFiles: false,
491
- gitOperations: true
492
- },
493
- prompt: `git merge --abort`,
494
- owns: [],
495
- reads: []
496
- });
497
- return { status: 'failed', conflicts: mergeResult.conflictingFiles };
498
- }
499
- /**
500
- * Parse merge result from subagent output
501
- */
502
- parseMergeResult(output) {
503
- try {
504
- const jsonMatch = output.match(/```json\s*([\s\S]*?)\s*```/);
505
- if (jsonMatch) {
506
- const parsed = JSON.parse(jsonMatch[1]);
507
- return {
508
- status: parsed.status || 'conflict',
509
- conflictingFiles: parsed.conflictingFiles || []
510
- };
511
- }
512
- }
513
- catch {
514
- // Fallback to text parsing
515
- }
516
- // Text-based detection
517
- if (output.includes('CONFLICT') || output.includes('Automatic merge failed')) {
518
- const fileMatches = output.matchAll(/CONFLICT\s*\([^)]+\):\s*(.+)/g);
519
- const files = [];
520
- for (const match of fileMatches) {
521
- files.push(match[1].trim());
522
- }
523
- return { status: 'conflict', conflictingFiles: files };
524
- }
525
- return { status: 'success', conflictingFiles: [] };
526
- }
527
- /**
528
- * Auto-resolve merge conflicts using debugger subagent
529
- */
530
- async autoResolveConflicts(conflictingFiles, branchName, context) {
531
- const resolved = [];
532
- const failed = [];
533
- for (const file of conflictingFiles) {
534
- const resolution = await this.resolveSingleFile(file, branchName, context);
535
- if (resolution.success) {
536
- resolved.push(file);
537
- }
538
- else {
539
- failed.push(file);
540
- }
541
- }
542
- return {
543
- success: resolved.length > 0 && failed.length === 0,
544
- resolved,
545
- failed
546
- };
547
- }
548
- /**
549
- * Resolve a single file conflict
486
+ * Format plan summary for verifier
550
487
  */
551
- async resolveSingleFile(file, branchName, context) {
552
- // Get both versions of the file
553
- const fileAnalysis = await context.dispatcher.dispatch({
554
- role: 'debugger',
555
- timeout: 60000
556
- }, {
557
- permissions: {
558
- readFiles: true,
559
- searchCode: false,
560
- runCommands: true,
561
- writeFiles: false,
562
- gitOperations: true
563
- },
564
- prompt: `Analyze conflict in file: ${file}
565
-
566
- Commands:
567
- 1. git show :1:${file} > /tmp/base.txt # common ancestor
568
- 2. git show :2:${file} > /tmp/ours.txt # current branch
569
- 3. git show :3:${file} > /tmp/theirs.txt # merging branch
570
- 4. cat ${file} # conflict markers
571
-
572
- Analyze:
573
- - Type of conflict (import additions, adjacent changes, overlapping edits)
574
- - Are changes logically compatible?
575
- - Can they be combined?
576
-
577
- Return JSON:
578
- {
579
- "conflictType": "imports|adjacent|overlapping|unrelated",
580
- "canAutoResolve": boolean,
581
- "strategy": "accept-ours|accept-theirs|combine|manual"
582
- }`,
583
- owns: [],
584
- reads: [file]
585
- });
586
- const analysis = this.parseConflictAnalysis(fileAnalysis.output);
587
- if (!analysis.canAutoResolve) {
588
- return { success: false, method: 'manual-required' };
589
- }
590
- // Attempt resolution based on strategy
591
- const resolution = await this.applyConflictStrategy(file, analysis.strategy, branchName, context);
592
- return resolution;
488
+ formatPlanSummary(plan) {
489
+ return plan.waves.map(w => `- ${w.id}: ${w.tasks.map(t => t.id).join(', ')}`).join('\n');
593
490
  }
594
491
  /**
595
- * Parse conflict analysis from subagent output
492
+ * Parse verifier output
596
493
  */
597
- parseConflictAnalysis(output) {
494
+ parseVerifierOutput(output) {
598
495
  try {
599
496
  const jsonMatch = output.match(/```json\s*([\s\S]*?)\s*```/);
600
497
  if (jsonMatch) {
601
- const parsed = JSON.parse(jsonMatch[1]);
602
- return {
603
- conflictType: parsed.conflictType || 'unknown',
604
- canAutoResolve: parsed.canAutoResolve || false,
605
- strategy: parsed.strategy || 'manual'
606
- };
498
+ return JSON.parse(jsonMatch[1]);
607
499
  }
608
500
  }
609
501
  catch {
610
- // Default fallback
611
- }
612
- return { conflictType: 'unknown', canAutoResolve: false, strategy: 'manual' };
613
- }
614
- /**
615
- * Apply conflict resolution strategy
616
- */
617
- async applyConflictStrategy(file, strategy, branchName, context) {
618
- let commands;
619
- switch (strategy) {
620
- case 'accept-ours':
621
- commands = `git checkout --ours "${file}" && git add "${file}"`;
622
- break;
623
- case 'accept-theirs':
624
- commands = `git checkout --theirs "${file}" && git add "${file}"`;
625
- break;
626
- case 'combine':
627
- // Use implementer to intelligently combine changes
628
- return this.combineFileChanges(file, branchName, context);
629
- default:
630
- return { success: false, method: 'manual-required' };
631
- }
632
- try {
633
- await context.dispatcher.dispatch({
634
- role: 'general',
635
- timeout: 30000
636
- }, {
637
- permissions: {
638
- readFiles: false,
639
- searchCode: false,
640
- runCommands: true,
641
- writeFiles: false,
642
- gitOperations: true
643
- },
644
- prompt: commands,
645
- owns: [],
646
- reads: []
647
- });
648
- return { success: true, method: strategy };
649
- }
650
- catch {
651
- return { success: false, method: `${strategy}-failed` };
502
+ // Fallback
652
503
  }
504
+ return { verdict: 'fail' };
653
505
  }
654
506
  /**
655
- * Intelligently combine changes from both branches
656
- */
657
- async combineFileChanges(file, branchName, context) {
658
- // Use implementer subagent to merge changes intelligently
659
- const combineResult = await context.dispatcher.dispatch({
660
- role: 'implementer',
661
- timeout: 60000
662
- }, {
663
- permissions: {
664
- readFiles: true,
665
- searchCode: false,
666
- runCommands: true,
667
- writeFiles: true,
668
- gitOperations: true
669
- },
670
- prompt: `Resolve merge conflict in ${file} by combining both changes.
671
-
672
- Steps:
673
- 1. git show :1:${file} > /tmp/base.ts
674
- 2. git show :2:${file} > /tmp/ours.ts
675
- 3. git show :3:${file} > /tmp/theirs.ts
676
- 4. Read all three versions
677
- 5. Create merged version that includes both sets of changes
678
- 6. Resolve conflict markers and write to ${file}
679
- 7. git add ${file}
680
- 8. Verify file is valid (syntax check if applicable)
681
-
682
- Rules:
683
- - Keep changes from both branches where possible
684
- - For imports: combine import lists
685
- - For functions: merge non-overlapping additions
686
- - Preserve code style and formatting
687
- - Add comment if resolution is complex
688
-
689
- Return JSON:
690
- {
691
- "success": boolean,
692
- "resolution": "description of changes made"
693
- }`,
694
- owns: [file],
695
- reads: [file]
696
- });
697
- const result = this.parseResolutionResult(combineResult.output);
698
- return {
699
- success: result.success,
700
- method: result.success ? 'combine-intelligent' : 'combine-failed'
701
- };
702
- }
703
- /**
704
- * Parse resolution result
507
+ * Parse reviewer output
705
508
  */
706
- parseResolutionResult(output) {
509
+ parseReviewerOutput(output) {
707
510
  try {
708
511
  const jsonMatch = output.match(/```json\s*([\s\S]*?)\s*```/);
709
512
  if (jsonMatch) {
710
- const parsed = JSON.parse(jsonMatch[1]);
711
- return {
712
- success: parsed.success || false,
713
- resolution: parsed.resolution
714
- };
513
+ return JSON.parse(jsonMatch[1]);
715
514
  }
716
515
  }
717
516
  catch {
718
- // Check for success indicators in text
719
- if (output.includes('success') || output.includes('resolved')) {
720
- return { success: true };
721
- }
517
+ // Fallback
722
518
  }
723
- return { success: false };
519
+ return { verdict: 'request_changes' };
724
520
  }
725
521
  /**
726
- * Parse worktree output from subagent
522
+ * Parse test output
727
523
  */
728
- parseWorktreeOutput(output) {
524
+ parseTestOutput(output) {
729
525
  try {
730
526
  const jsonMatch = output.match(/```json\s*([\s\S]*?)\s*```/);
731
527
  if (jsonMatch) {
732
- const parsed = JSON.parse(jsonMatch[1]);
733
- return {
734
- worktreePath: parsed.worktreePath || '',
735
- branchName: parsed.branchName || '',
736
- baseBranch: parsed.baseBranch || 'main',
737
- repoRoot: parsed.repoRoot || '',
738
- status: parsed.status || 'unknown',
739
- error: parsed.error
740
- };
528
+ return JSON.parse(jsonMatch[1]);
741
529
  }
530
+ // Fallback parsing
531
+ const coverageMatch = output.match(/(\d+(?:\.\d+)?)%/);
532
+ const passed = !output.includes('FAIL') && !output.includes('failed');
533
+ return {
534
+ passed,
535
+ coverage: coverageMatch ? parseFloat(coverageMatch[1]) : 0
536
+ };
742
537
  }
743
538
  catch {
744
- // Fallback: try to extract from text
745
- const pathMatch = output.match(/worktreePath["\']?\s*[:=]\s*["\']([^"\']+)["\']/i);
746
- const branchMatch = output.match(/branchName["\']?\s*[:=]\s*["\']([^"\']+)["\']/i);
747
- const statusMatch = output.match(/status["\']?\s*[:=]\s*["\'](created|reused|failed)["\']/i);
748
- if (pathMatch) {
749
- return {
750
- worktreePath: pathMatch[1],
751
- branchName: branchMatch?.[1] || '',
752
- baseBranch: 'main',
753
- repoRoot: '',
754
- status: statusMatch?.[1] || 'unknown'
755
- };
756
- }
539
+ return { passed: false, coverage: 0 };
757
540
  }
758
- // Default fallback
759
- return {
760
- worktreePath: '',
761
- branchName: '',
762
- baseBranch: 'main',
763
- repoRoot: '',
764
- status: 'failed',
765
- error: 'Could not parse worktree output'
766
- };
767
541
  }
768
542
  /**
769
543
  * Helper to trigger hooks with error handling