@haoyiyin/workflow 0.2.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 (223) hide show
  1. package/README.md +168 -0
  2. package/SETUP.md +71 -0
  3. package/SKILL.md +68 -0
  4. package/USAGE.md +152 -0
  5. package/dist/bin/yi-workflow.d.ts +3 -0
  6. package/dist/bin/yi-workflow.d.ts.map +1 -0
  7. package/dist/bin/yi-workflow.js +39 -0
  8. package/dist/bin/yi-workflow.js.map +1 -0
  9. package/dist/src/agents/contracts.d.ts +120 -0
  10. package/dist/src/agents/contracts.d.ts.map +1 -0
  11. package/dist/src/agents/contracts.js +431 -0
  12. package/dist/src/agents/contracts.js.map +1 -0
  13. package/dist/src/agents/dispatcher-enhanced.d.ts +68 -0
  14. package/dist/src/agents/dispatcher-enhanced.d.ts.map +1 -0
  15. package/dist/src/agents/dispatcher-enhanced.js +290 -0
  16. package/dist/src/agents/dispatcher-enhanced.js.map +1 -0
  17. package/dist/src/agents/dispatcher.d.ts +107 -0
  18. package/dist/src/agents/dispatcher.d.ts.map +1 -0
  19. package/dist/src/agents/dispatcher.js +454 -0
  20. package/dist/src/agents/dispatcher.js.map +1 -0
  21. package/dist/src/agents/index.d.ts +11 -0
  22. package/dist/src/agents/index.d.ts.map +1 -0
  23. package/dist/src/agents/index.js +10 -0
  24. package/dist/src/agents/index.js.map +1 -0
  25. package/dist/src/agents/resilience.d.ts +86 -0
  26. package/dist/src/agents/resilience.d.ts.map +1 -0
  27. package/dist/src/agents/resilience.js +183 -0
  28. package/dist/src/agents/resilience.js.map +1 -0
  29. package/dist/src/agents/token-budget.d.ts +47 -0
  30. package/dist/src/agents/token-budget.d.ts.map +1 -0
  31. package/dist/src/agents/token-budget.js +63 -0
  32. package/dist/src/agents/token-budget.js.map +1 -0
  33. package/dist/src/agents/types.d.ts +59 -0
  34. package/dist/src/agents/types.d.ts.map +1 -0
  35. package/dist/src/agents/types.js +5 -0
  36. package/dist/src/agents/types.js.map +1 -0
  37. package/dist/src/guard/main-agent.d.ts +72 -0
  38. package/dist/src/guard/main-agent.d.ts.map +1 -0
  39. package/dist/src/guard/main-agent.js +184 -0
  40. package/dist/src/guard/main-agent.js.map +1 -0
  41. package/dist/src/hooks/builtin/index.d.ts +9 -0
  42. package/dist/src/hooks/builtin/index.d.ts.map +1 -0
  43. package/dist/src/hooks/builtin/index.js +9 -0
  44. package/dist/src/hooks/builtin/index.js.map +1 -0
  45. package/dist/src/hooks/builtin/on-error.d.ts +7 -0
  46. package/dist/src/hooks/builtin/on-error.d.ts.map +1 -0
  47. package/dist/src/hooks/builtin/on-error.js +15 -0
  48. package/dist/src/hooks/builtin/on-error.js.map +1 -0
  49. package/dist/src/hooks/builtin/post-execute.d.ts +7 -0
  50. package/dist/src/hooks/builtin/post-execute.d.ts.map +1 -0
  51. package/dist/src/hooks/builtin/post-execute.js +30 -0
  52. package/dist/src/hooks/builtin/post-execute.js.map +1 -0
  53. package/dist/src/hooks/builtin/post-plan.d.ts +7 -0
  54. package/dist/src/hooks/builtin/post-plan.d.ts.map +1 -0
  55. package/dist/src/hooks/builtin/post-plan.js +15 -0
  56. package/dist/src/hooks/builtin/post-plan.js.map +1 -0
  57. package/dist/src/hooks/builtin/pre-execute.d.ts +7 -0
  58. package/dist/src/hooks/builtin/pre-execute.d.ts.map +1 -0
  59. package/dist/src/hooks/builtin/pre-execute.js +22 -0
  60. package/dist/src/hooks/builtin/pre-execute.js.map +1 -0
  61. package/dist/src/hooks/builtin/pre-plan.d.ts +7 -0
  62. package/dist/src/hooks/builtin/pre-plan.d.ts.map +1 -0
  63. package/dist/src/hooks/builtin/pre-plan.js +19 -0
  64. package/dist/src/hooks/builtin/pre-plan.js.map +1 -0
  65. package/dist/src/hooks/index.d.ts +8 -0
  66. package/dist/src/hooks/index.d.ts.map +1 -0
  67. package/dist/src/hooks/index.js +4 -0
  68. package/dist/src/hooks/index.js.map +1 -0
  69. package/dist/src/hooks/loader.d.ts +16 -0
  70. package/dist/src/hooks/loader.d.ts.map +1 -0
  71. package/dist/src/hooks/loader.js +77 -0
  72. package/dist/src/hooks/loader.js.map +1 -0
  73. package/dist/src/hooks/manager.d.ts +20 -0
  74. package/dist/src/hooks/manager.d.ts.map +1 -0
  75. package/dist/src/hooks/manager.js +76 -0
  76. package/dist/src/hooks/manager.js.map +1 -0
  77. package/dist/src/hooks/types-enhanced.d.ts +30 -0
  78. package/dist/src/hooks/types-enhanced.d.ts.map +1 -0
  79. package/dist/src/hooks/types-enhanced.js +22 -0
  80. package/dist/src/hooks/types-enhanced.js.map +1 -0
  81. package/dist/src/hooks/types.d.ts +27 -0
  82. package/dist/src/hooks/types.d.ts.map +1 -0
  83. package/dist/src/hooks/types.js +2 -0
  84. package/dist/src/hooks/types.js.map +1 -0
  85. package/dist/src/index.d.ts +43 -0
  86. package/dist/src/index.d.ts.map +1 -0
  87. package/dist/src/index.js +41 -0
  88. package/dist/src/index.js.map +1 -0
  89. package/dist/src/persistence/index.d.ts +7 -0
  90. package/dist/src/persistence/index.d.ts.map +1 -0
  91. package/dist/src/persistence/index.js +6 -0
  92. package/dist/src/persistence/index.js.map +1 -0
  93. package/dist/src/persistence/plan-md.d.ts +11 -0
  94. package/dist/src/persistence/plan-md.d.ts.map +1 -0
  95. package/dist/src/persistence/plan-md.js +125 -0
  96. package/dist/src/persistence/plan-md.js.map +1 -0
  97. package/dist/src/persistence/state-md.d.ts +17 -0
  98. package/dist/src/persistence/state-md.d.ts.map +1 -0
  99. package/dist/src/persistence/state-md.js +143 -0
  100. package/dist/src/persistence/state-md.js.map +1 -0
  101. package/dist/src/persistence/types.d.ts +85 -0
  102. package/dist/src/persistence/types.d.ts.map +1 -0
  103. package/dist/src/persistence/types.js +5 -0
  104. package/dist/src/persistence/types.js.map +1 -0
  105. package/dist/src/router/classifier.d.ts +108 -0
  106. package/dist/src/router/classifier.d.ts.map +1 -0
  107. package/dist/src/router/classifier.js +476 -0
  108. package/dist/src/router/classifier.js.map +1 -0
  109. package/dist/src/router/guard.d.ts +128 -0
  110. package/dist/src/router/guard.d.ts.map +1 -0
  111. package/dist/src/router/guard.js +370 -0
  112. package/dist/src/router/guard.js.map +1 -0
  113. package/dist/src/router/index.d.ts +10 -0
  114. package/dist/src/router/index.d.ts.map +1 -0
  115. package/dist/src/router/index.js +8 -0
  116. package/dist/src/router/index.js.map +1 -0
  117. package/dist/src/router/router.d.ts +58 -0
  118. package/dist/src/router/router.d.ts.map +1 -0
  119. package/dist/src/router/router.js +78 -0
  120. package/dist/src/router/router.js.map +1 -0
  121. package/dist/src/router/types.d.ts +100 -0
  122. package/dist/src/router/types.d.ts.map +1 -0
  123. package/dist/src/router/types.js +24 -0
  124. package/dist/src/router/types.js.map +1 -0
  125. package/dist/src/skills/agents-md/index.d.ts +9 -0
  126. package/dist/src/skills/agents-md/index.d.ts.map +1 -0
  127. package/dist/src/skills/agents-md/index.js +28 -0
  128. package/dist/src/skills/agents-md/index.js.map +1 -0
  129. package/dist/src/skills/execute-plan/index.d.ts +141 -0
  130. package/dist/src/skills/execute-plan/index.d.ts.map +1 -0
  131. package/dist/src/skills/execute-plan/index.js +784 -0
  132. package/dist/src/skills/execute-plan/index.js.map +1 -0
  133. package/dist/src/skills/index.d.ts +14 -0
  134. package/dist/src/skills/index.d.ts.map +1 -0
  135. package/dist/src/skills/index.js +10 -0
  136. package/dist/src/skills/index.js.map +1 -0
  137. package/dist/src/skills/quick-task/index.d.ts +75 -0
  138. package/dist/src/skills/quick-task/index.d.ts.map +1 -0
  139. package/dist/src/skills/quick-task/index.js +284 -0
  140. package/dist/src/skills/quick-task/index.js.map +1 -0
  141. package/dist/src/skills/registry.d.ts +15 -0
  142. package/dist/src/skills/registry.d.ts.map +1 -0
  143. package/dist/src/skills/registry.js +44 -0
  144. package/dist/src/skills/registry.js.map +1 -0
  145. package/dist/src/skills/review-diff/index.d.ts +96 -0
  146. package/dist/src/skills/review-diff/index.d.ts.map +1 -0
  147. package/dist/src/skills/review-diff/index.js +316 -0
  148. package/dist/src/skills/review-diff/index.js.map +1 -0
  149. package/dist/src/skills/skill.d.ts +24 -0
  150. package/dist/src/skills/skill.d.ts.map +1 -0
  151. package/dist/src/skills/skill.js +39 -0
  152. package/dist/src/skills/skill.js.map +1 -0
  153. package/dist/src/skills/systematic-debugging/index.d.ts +90 -0
  154. package/dist/src/skills/systematic-debugging/index.d.ts.map +1 -0
  155. package/dist/src/skills/systematic-debugging/index.js +305 -0
  156. package/dist/src/skills/systematic-debugging/index.js.map +1 -0
  157. package/dist/src/skills/tdd/index.d.ts +103 -0
  158. package/dist/src/skills/tdd/index.d.ts.map +1 -0
  159. package/dist/src/skills/tdd/index.js +338 -0
  160. package/dist/src/skills/tdd/index.js.map +1 -0
  161. package/dist/src/skills/to-plan/index-enhanced.d.ts +100 -0
  162. package/dist/src/skills/to-plan/index-enhanced.d.ts.map +1 -0
  163. package/dist/src/skills/to-plan/index-enhanced.js +452 -0
  164. package/dist/src/skills/to-plan/index-enhanced.js.map +1 -0
  165. package/dist/src/skills/to-plan/index.d.ts +131 -0
  166. package/dist/src/skills/to-plan/index.d.ts.map +1 -0
  167. package/dist/src/skills/to-plan/index.js +460 -0
  168. package/dist/src/skills/to-plan/index.js.map +1 -0
  169. package/dist/src/skills/types.d.ts +44 -0
  170. package/dist/src/skills/types.d.ts.map +1 -0
  171. package/dist/src/skills/types.js +2 -0
  172. package/dist/src/skills/types.js.map +1 -0
  173. package/dist/src/state/cleanup.d.ts +22 -0
  174. package/dist/src/state/cleanup.d.ts.map +1 -0
  175. package/dist/src/state/cleanup.js +87 -0
  176. package/dist/src/state/cleanup.js.map +1 -0
  177. package/dist/src/state/index.d.ts +9 -0
  178. package/dist/src/state/index.d.ts.map +1 -0
  179. package/dist/src/state/index.js +5 -0
  180. package/dist/src/state/index.js.map +1 -0
  181. package/dist/src/state/manager.d.ts +15 -0
  182. package/dist/src/state/manager.d.ts.map +1 -0
  183. package/dist/src/state/manager.js +73 -0
  184. package/dist/src/state/manager.js.map +1 -0
  185. package/dist/src/state/persistence.d.ts +13 -0
  186. package/dist/src/state/persistence.d.ts.map +1 -0
  187. package/dist/src/state/persistence.js +68 -0
  188. package/dist/src/state/persistence.js.map +1 -0
  189. package/dist/src/state/types.d.ts +26 -0
  190. package/dist/src/state/types.d.ts.map +1 -0
  191. package/dist/src/state/types.js +2 -0
  192. package/dist/src/state/types.js.map +1 -0
  193. package/dist/src/state/validator.d.ts +12 -0
  194. package/dist/src/state/validator.d.ts.map +1 -0
  195. package/dist/src/state/validator.js +56 -0
  196. package/dist/src/state/validator.js.map +1 -0
  197. package/dist/src/types.d.ts +83 -0
  198. package/dist/src/types.d.ts.map +1 -0
  199. package/dist/src/types.js +5 -0
  200. package/dist/src/types.js.map +1 -0
  201. package/dist/src/utils/compress.d.ts +37 -0
  202. package/dist/src/utils/compress.d.ts.map +1 -0
  203. package/dist/src/utils/compress.js +298 -0
  204. package/dist/src/utils/compress.js.map +1 -0
  205. package/dist/src/utils/git.d.ts +9 -0
  206. package/dist/src/utils/git.d.ts.map +1 -0
  207. package/dist/src/utils/git.js +81 -0
  208. package/dist/src/utils/git.js.map +1 -0
  209. package/dist/src/utils/index.d.ts +7 -0
  210. package/dist/src/utils/index.d.ts.map +1 -0
  211. package/dist/src/utils/index.js +7 -0
  212. package/dist/src/utils/index.js.map +1 -0
  213. package/dist/src/utils/logger.d.ts +6 -0
  214. package/dist/src/utils/logger.d.ts.map +1 -0
  215. package/dist/src/utils/logger.js +19 -0
  216. package/dist/src/utils/logger.js.map +1 -0
  217. package/dist/src/utils/paths.d.ts +12 -0
  218. package/dist/src/utils/paths.d.ts.map +1 -0
  219. package/dist/src/utils/paths.js +44 -0
  220. package/dist/src/utils/paths.js.map +1 -0
  221. package/package.json +76 -0
  222. package/scripts/postinstall.js +69 -0
  223. package/yi-workflow.js +17 -0
@@ -0,0 +1,784 @@
1
+ /**
2
+ * Enhanced Execute Plan Skill - Orchestrates TDD and Review skills
3
+ *
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
10
+ */
11
+ import { z } from 'zod';
12
+ import { Skill } from '../skill.js';
13
+ // HookType constants (avoiding enum import issues)
14
+ const HookType = {
15
+ PRE_EXECUTE: 'pre-execute',
16
+ POST_EXECUTE: 'post-execute',
17
+ PRE_WAVE: 'pre-wave',
18
+ POST_WAVE: 'post-wave',
19
+ PRE_TASK: 'pre-task',
20
+ POST_TASK: 'post-task',
21
+ ON_ERROR: 'on-error',
22
+ STATE_CHANGE: 'state-change',
23
+ TASK_COMPLETE: 'task-complete',
24
+ WAVE_START: 'wave-start',
25
+ WAVE_COMPLETE: 'wave-complete',
26
+ };
27
+ import { worktreeContract } from '../../agents/contracts.js';
28
+ const ExecuteInputSchema = z.object({
29
+ plan: z.any(),
30
+ waves: z.array(z.any()),
31
+ skipVerify: z.boolean().optional(),
32
+ model: z.string().optional()
33
+ });
34
+ const ExecuteOutputSchema = z.object({
35
+ success: z.boolean(),
36
+ results: z.array(z.any()),
37
+ verification: z.any(),
38
+ wavesCompleted: z.number(),
39
+ totalWaves: z.number()
40
+ });
41
+ export class ExecutePlanSkill extends Skill {
42
+ constructor() {
43
+ super({
44
+ 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']
47
+ });
48
+ }
49
+ async execute(input, context) {
50
+ const { plan, waves } = input;
51
+ const results = [];
52
+ let wavesCompleted = 0;
53
+ // 1. Update state
54
+ if (context.persistence) {
55
+ await context.persistence.state.updatePhase('executing');
56
+ }
57
+ // 2. Trigger pre-execute hooks
58
+ await this.triggerHook(context, HookType.PRE_EXECUTE, { plan, waves });
59
+ try {
60
+ // 3. Execute waves sequentially
61
+ for (const wave of waves) {
62
+ console.log(`\n[ExecutePlanSkill] Wave ${wave.id}: ${wave.tasks.length} tasks`);
63
+ // 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
+ }
83
+ if (mergeResult.manualResolution.length > 0) {
84
+ console.error(` [Merge] Needs manual resolution: ${mergeResult.manualResolution.join(', ')}`);
85
+ }
86
+ }
87
+ // Trigger wave complete hooks (with merge info)
88
+ await this.triggerHook(context, HookType.WAVE_COMPLETE, {
89
+ wave,
90
+ results: waveResults,
91
+ plan,
92
+ mergeResult
93
+ });
94
+ 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);
100
+ break;
101
+ }
102
+ console.log(`[ExecutePlanSkill] Wave ${wave.id} complete ✓`);
103
+ }
104
+ // 4. Run verification using review-diff skill
105
+ let verification;
106
+ if (!input.skipVerify && wavesCompleted === waves.length) {
107
+ verification = await this.runVerificationWithReviewDiff(plan, results, context);
108
+ }
109
+ else {
110
+ verification = {
111
+ success: false,
112
+ dimensions: {
113
+ goals: { success: false, gaps: ['Verification skipped or incomplete'] }
114
+ }
115
+ };
116
+ }
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
124
+ await this.triggerHook(context, HookType.POST_EXECUTE, {
125
+ results,
126
+ verification,
127
+ wavesCompleted,
128
+ totalWaves: waves.length
129
+ });
130
+ return {
131
+ success: results.every(r => r.success) && verification.success,
132
+ results,
133
+ verification,
134
+ wavesCompleted,
135
+ totalWaves: waves.length
136
+ };
137
+ }
138
+ catch (error) {
139
+ // Trigger error hooks
140
+ await this.triggerHook(context, HookType.ON_ERROR, {
141
+ phase: 'execute',
142
+ error: error.message,
143
+ results
144
+ });
145
+ throw error;
146
+ }
147
+ }
148
+ /**
149
+ * Execute a wave using TDD skill for each task
150
+ * Parallel execution with Promise.allSettled
151
+ */
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
+ };
183
+ }
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)
199
+ const settledResults = await Promise.allSettled(promises);
200
+ return settledResults.map((result, index) => {
201
+ if (result.status === 'fulfilled') {
202
+ return result.value;
203
+ }
204
+ else {
205
+ return {
206
+ taskId: taskConfigs[index].taskId,
207
+ success: false,
208
+ filesModified: [],
209
+ testsAdded: [],
210
+ duration: 0,
211
+ notes: '',
212
+ error: result.reason?.message || 'Unknown error'
213
+ };
214
+ }
215
+ });
216
+ }
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
221
+ */
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'}`);
233
+ }
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
+ }
259
+ /**
260
+ * Run verification using review-diff skill
261
+ * Returns structured verification result
262
+ */
263
+ async runVerificationWithReviewDiff(plan, results, context) {
264
+ console.log('\n[ExecutePlanSkill] Running verification...');
265
+ if (context.persistence) {
266
+ await context.persistence.state.updatePhase('verifying');
267
+ }
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: {} };
272
+ }
273
+ // Parallel verification dimensions
274
+ const [goalsResult, qualityResult, testsResult] = await Promise.all([
275
+ this.verifyGoalsWithReviewDiff(plan, context, reviewSkill),
276
+ this.verifyQualityWithReviewDiff(results, context, reviewSkill),
277
+ this.verifyTests(results, context)
278
+ ]);
279
+ const verification = {
280
+ success: goalsResult.success && qualityResult.success && testsResult.success,
281
+ dimensions: {
282
+ goals: goalsResult,
283
+ quality: qualityResult,
284
+ tests: testsResult
285
+ }
286
+ };
287
+ console.log(`[ExecutePlanSkill] Verification: ${verification.success ? 'PASSED' : 'FAILED'}`);
288
+ return verification;
289
+ }
290
+ /**
291
+ * Verify goals using review-diff skill
292
+ */
293
+ async verifyGoalsWithReviewDiff(plan, context, reviewSkill) {
294
+ 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';
303
+ 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)
308
+ };
309
+ }
310
+ catch (error) {
311
+ return {
312
+ success: false,
313
+ gaps: [`Goal verification failed: ${error.message}`]
314
+ };
315
+ }
316
+ }
317
+ /**
318
+ * Verify code quality using review-diff skill
319
+ */
320
+ async verifyQualityWithReviewDiff(results, context, reviewSkill) {
321
+ 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');
328
+ return {
329
+ success: !hasBlockers && review.decision !== 'blocked',
330
+ details: review,
331
+ evidence: review.strengths,
332
+ gaps: hasBlockers ? ['Critical issues found'] : []
333
+ };
334
+ }
335
+ catch (error) {
336
+ return {
337
+ success: false,
338
+ gaps: [`Quality verification failed: ${error.message}`]
339
+ };
340
+ }
341
+ }
342
+ /**
343
+ * Verify tests by checking TDD results
344
+ */
345
+ async verifyTests(results, context) {
346
+ // Check if all tasks have tests
347
+ const allHaveTests = results.every(r => r.testsAdded && r.testsAdded.length > 0);
348
+ const allPassed = results.every(r => r.success);
349
+ // Try to run full test suite
350
+ try {
351
+ const testResult = await context.dispatcher.dispatch({
352
+ role: 'verifier',
353
+ timeout: 120000
354
+ }, {
355
+ permissions: {
356
+ readFiles: true,
357
+ searchCode: true,
358
+ runCommands: true,
359
+ writeFiles: false,
360
+ gitOperations: false
361
+ },
362
+ prompt: `Run the full test suite and report coverage.\n\nReturn JSON: { "passed": boolean, "coverage": number }`,
363
+ owns: [],
364
+ reads: []
365
+ });
366
+ const coverageOk = testResult.coverage >= 80;
367
+ 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%`] : []
372
+ };
373
+ }
374
+ catch {
375
+ // Fallback to basic check
376
+ return {
377
+ success: allHaveTests && allPassed,
378
+ details: { allHaveTests, allPassed },
379
+ evidence: allHaveTests ? ['All tasks have tests'] : [],
380
+ gaps: !allHaveTests ? ['Some tasks missing tests'] : []
381
+ };
382
+ }
383
+ }
384
+ /**
385
+ * Handle wave failure
386
+ */
387
+ async handleWaveFailure(wave, failedTasks, context) {
388
+ console.error(`[ExecutePlanSkill] Wave failure handler`);
389
+ await this.triggerHook(context, HookType.ON_ERROR, {
390
+ phase: 'execute',
391
+ wave,
392
+ failedTasks
393
+ });
394
+ }
395
+ /**
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
550
+ */
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;
593
+ }
594
+ /**
595
+ * Parse conflict analysis from subagent output
596
+ */
597
+ parseConflictAnalysis(output) {
598
+ try {
599
+ const jsonMatch = output.match(/```json\s*([\s\S]*?)\s*```/);
600
+ 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
+ };
607
+ }
608
+ }
609
+ 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` };
652
+ }
653
+ }
654
+ /**
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
705
+ */
706
+ parseResolutionResult(output) {
707
+ try {
708
+ const jsonMatch = output.match(/```json\s*([\s\S]*?)\s*```/);
709
+ if (jsonMatch) {
710
+ const parsed = JSON.parse(jsonMatch[1]);
711
+ return {
712
+ success: parsed.success || false,
713
+ resolution: parsed.resolution
714
+ };
715
+ }
716
+ }
717
+ catch {
718
+ // Check for success indicators in text
719
+ if (output.includes('success') || output.includes('resolved')) {
720
+ return { success: true };
721
+ }
722
+ }
723
+ return { success: false };
724
+ }
725
+ /**
726
+ * Parse worktree output from subagent
727
+ */
728
+ parseWorktreeOutput(output) {
729
+ try {
730
+ const jsonMatch = output.match(/```json\s*([\s\S]*?)\s*```/);
731
+ 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
+ };
741
+ }
742
+ }
743
+ 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
+ }
757
+ }
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
+ }
768
+ /**
769
+ * Helper to trigger hooks with error handling
770
+ */
771
+ async triggerHook(context, type, data) {
772
+ if (context.hooks) {
773
+ try {
774
+ const hookContext = data;
775
+ await context.hooks.execute(type, hookContext);
776
+ }
777
+ catch (error) {
778
+ console.warn(`[ExecutePlanSkill] Hook ${type} failed:`, error);
779
+ }
780
+ }
781
+ }
782
+ }
783
+ export const executePlanSkill = new ExecutePlanSkill();
784
+ //# sourceMappingURL=index.js.map