@haoyiyin/workflow 0.2.11 → 0.3.1

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