@haoyiyin/workflow 0.2.11 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/agents/contracts/implementer.d.ts +29 -0
- package/dist/src/agents/contracts/implementer.d.ts.map +1 -0
- package/dist/src/agents/contracts/implementer.js +94 -0
- package/dist/src/agents/contracts/implementer.js.map +1 -0
- package/dist/src/agents/contracts/index.d.ts +11 -0
- package/dist/src/agents/contracts/index.d.ts.map +1 -0
- package/dist/src/agents/contracts/index.js +11 -0
- package/dist/src/agents/contracts/index.js.map +1 -0
- package/dist/src/agents/contracts/planner.d.ts +25 -0
- package/dist/src/agents/contracts/planner.d.ts.map +1 -0
- package/dist/src/agents/contracts/planner.js +107 -0
- package/dist/src/agents/contracts/planner.js.map +1 -0
- package/dist/src/agents/contracts/router.d.ts +24 -0
- package/dist/src/agents/contracts/router.d.ts.map +1 -0
- package/dist/src/agents/contracts/router.js +137 -0
- package/dist/src/agents/contracts/router.js.map +1 -0
- package/dist/src/agents/contracts/verifier.d.ts +27 -0
- package/dist/src/agents/contracts/verifier.d.ts.map +1 -0
- package/dist/src/agents/contracts/verifier.js +115 -0
- package/dist/src/agents/contracts/verifier.js.map +1 -0
- package/dist/src/agents/dispatcher.d.ts +94 -51
- package/dist/src/agents/dispatcher.d.ts.map +1 -1
- package/dist/src/agents/dispatcher.js +207 -164
- package/dist/src/agents/dispatcher.js.map +1 -1
- package/dist/src/persistence/index.d.ts +4 -2
- package/dist/src/persistence/index.d.ts.map +1 -1
- package/dist/src/persistence/index.js +4 -1
- package/dist/src/persistence/index.js.map +1 -1
- package/dist/src/persistence/plan-md.d.ts +3 -2
- package/dist/src/persistence/plan-md.d.ts.map +1 -1
- package/dist/src/persistence/plan-md.js +47 -15
- package/dist/src/persistence/plan-md.js.map +1 -1
- package/dist/src/persistence/state-md.d.ts +2 -0
- package/dist/src/persistence/state-md.d.ts.map +1 -1
- package/dist/src/persistence/state-md.js +40 -22
- package/dist/src/persistence/state-md.js.map +1 -1
- package/dist/src/persistence/types.d.ts +35 -39
- package/dist/src/persistence/types.d.ts.map +1 -1
- package/dist/src/router/namespace/core/intent-router.d.ts +24 -0
- package/dist/src/router/namespace/core/intent-router.d.ts.map +1 -0
- package/dist/src/router/namespace/core/intent-router.js +190 -0
- package/dist/src/router/namespace/core/intent-router.js.map +1 -0
- package/dist/src/router/namespace/core/lifecycle-router.d.ts +28 -0
- package/dist/src/router/namespace/core/lifecycle-router.d.ts.map +1 -0
- package/dist/src/router/namespace/core/lifecycle-router.js +132 -0
- package/dist/src/router/namespace/core/lifecycle-router.js.map +1 -0
- package/dist/src/router/namespace/core/state-router.d.ts +32 -0
- package/dist/src/router/namespace/core/state-router.d.ts.map +1 -0
- package/dist/src/router/namespace/core/state-router.js +157 -0
- package/dist/src/router/namespace/core/state-router.js.map +1 -0
- package/dist/src/router/namespace/domain/code-router.d.ts +26 -0
- package/dist/src/router/namespace/domain/code-router.d.ts.map +1 -0
- package/dist/src/router/namespace/domain/code-router.js +171 -0
- package/dist/src/router/namespace/domain/code-router.js.map +1 -0
- package/dist/src/router/namespace/domain/debug-router.d.ts +25 -0
- package/dist/src/router/namespace/domain/debug-router.d.ts.map +1 -0
- package/dist/src/router/namespace/domain/debug-router.js +139 -0
- package/dist/src/router/namespace/domain/debug-router.js.map +1 -0
- package/dist/src/router/namespace/domain/plan-router.d.ts +29 -0
- package/dist/src/router/namespace/domain/plan-router.d.ts.map +1 -0
- package/dist/src/router/namespace/domain/plan-router.js +160 -0
- package/dist/src/router/namespace/domain/plan-router.js.map +1 -0
- package/dist/src/router/namespace/domain/review-router.d.ts +24 -0
- package/dist/src/router/namespace/domain/review-router.d.ts.map +1 -0
- package/dist/src/router/namespace/domain/review-router.js +116 -0
- package/dist/src/router/namespace/domain/review-router.js.map +1 -0
- package/dist/src/router/namespace/index.d.ts +19 -0
- package/dist/src/router/namespace/index.d.ts.map +1 -0
- package/dist/src/router/namespace/index.js +22 -0
- package/dist/src/router/namespace/index.js.map +1 -0
- package/dist/src/router/namespace/registry.d.ts +67 -0
- package/dist/src/router/namespace/registry.d.ts.map +1 -0
- package/dist/src/router/namespace/registry.js +197 -0
- package/dist/src/router/namespace/registry.js.map +1 -0
- package/dist/src/router/namespace/types.d.ts +124 -0
- package/dist/src/router/namespace/types.d.ts.map +1 -0
- package/dist/src/router/namespace/types.js +20 -0
- package/dist/src/router/namespace/types.js.map +1 -0
- package/dist/src/router/namespace/utility/fallback-router.d.ts +28 -0
- package/dist/src/router/namespace/utility/fallback-router.d.ts.map +1 -0
- package/dist/src/router/namespace/utility/fallback-router.js +88 -0
- package/dist/src/router/namespace/utility/fallback-router.js.map +1 -0
- package/dist/src/router/namespace/utility/quick-task-router.d.ts +28 -0
- package/dist/src/router/namespace/utility/quick-task-router.d.ts.map +1 -0
- package/dist/src/router/namespace/utility/quick-task-router.js +99 -0
- package/dist/src/router/namespace/utility/quick-task-router.js.map +1 -0
- package/dist/src/router/namespace/utility/research-router.d.ts +24 -0
- package/dist/src/router/namespace/utility/research-router.d.ts.map +1 -0
- package/dist/src/router/namespace/utility/research-router.js +84 -0
- package/dist/src/router/namespace/utility/research-router.js.map +1 -0
- package/dist/src/skills/agents-md/index.js +2 -2
- package/dist/src/skills/agents-md/index.js.map +1 -1
- package/dist/src/skills/execute-plan/index.d.ts +45 -65
- package/dist/src/skills/execute-plan/index.d.ts.map +1 -1
- package/dist/src/skills/execute-plan/index.js +325 -551
- package/dist/src/skills/execute-plan/index.js.map +1 -1
- package/dist/src/skills/index.d.ts +1 -0
- package/dist/src/skills/index.d.ts.map +1 -1
- package/dist/src/skills/index.js +1 -0
- package/dist/src/skills/index.js.map +1 -1
- package/dist/src/skills/quick-task/index.d.ts +4 -4
- package/dist/src/skills/quick-task/index.js +1 -1
- package/dist/src/skills/quick-task/index.js.map +1 -1
- package/dist/src/skills/review-diff/index.d.ts +6 -6
- package/dist/src/skills/review-diff/index.js +1 -1
- package/dist/src/skills/review-diff/index.js.map +1 -1
- package/dist/src/skills/router/index.d.ts +101 -0
- package/dist/src/skills/router/index.d.ts.map +1 -0
- package/dist/src/skills/router/index.js +450 -0
- package/dist/src/skills/router/index.js.map +1 -0
- package/dist/src/skills/router/types.d.ts +79 -0
- package/dist/src/skills/router/types.d.ts.map +1 -0
- package/dist/src/skills/router/types.js +8 -0
- package/dist/src/skills/router/types.js.map +1 -0
- package/dist/src/skills/systematic-debugging/index.js +1 -1
- package/dist/src/skills/systematic-debugging/index.js.map +1 -1
- package/dist/src/skills/tdd/index.d.ts +14 -14
- package/dist/src/skills/tdd/index.js +1 -1
- package/dist/src/skills/tdd/index.js.map +1 -1
- package/dist/src/skills/to-plan/index-enhanced.d.ts +4 -4
- package/dist/src/skills/to-plan/index-enhanced.d.ts.map +1 -1
- package/dist/src/skills/to-plan/index-enhanced.js +3 -5
- package/dist/src/skills/to-plan/index-enhanced.js.map +1 -1
- package/dist/src/skills/to-plan/index.d.ts +24 -91
- package/dist/src/skills/to-plan/index.d.ts.map +1 -1
- package/dist/src/skills/to-plan/index.js +214 -409
- package/dist/src/skills/to-plan/index.js.map +1 -1
- package/package.json +3 -5
- package/src/agents/contracts/implementer.ts +122 -0
- package/src/agents/contracts/index.ts +27 -0
- package/src/agents/contracts/planner.ts +129 -0
- package/src/agents/contracts/router.ts +168 -0
- package/src/agents/contracts/verifier.ts +137 -0
- package/src/agents/dispatcher.ts +387 -362
- package/src/persistence/index.ts +10 -4
- package/src/persistence/plan-md.ts +52 -18
- package/src/persistence/state-md.ts +45 -23
- package/src/persistence/types.ts +37 -40
- package/src/router/namespace/README.md +127 -0
- package/src/router/namespace/core/intent-router.ts +221 -0
- package/src/router/namespace/core/lifecycle-router.ts +156 -0
- package/src/router/namespace/core/state-router.ts +192 -0
- package/src/router/namespace/domain/code-router.ts +202 -0
- package/src/router/namespace/domain/debug-router.ts +167 -0
- package/src/router/namespace/domain/plan-router.ts +196 -0
- package/src/router/namespace/domain/review-router.ts +142 -0
- package/src/router/namespace/index.ts +84 -0
- package/src/router/namespace/registry.ts +242 -0
- package/src/router/namespace/types.ts +182 -0
- package/src/router/namespace/utility/fallback-router.ts +107 -0
- package/src/router/namespace/utility/quick-task-router.ts +121 -0
- package/src/router/namespace/utility/research-router.ts +105 -0
- package/src/skills/agents-md/index.ts +2 -2
- package/src/skills/execute-plan/index.ts +419 -673
- package/src/skills/index.ts +1 -0
- package/src/skills/quick-task/index.ts +1 -1
- package/src/skills/review-diff/index.ts +1 -1
- package/src/skills/router/SKILL.md +181 -0
- package/src/skills/router/index.ts +577 -0
- package/src/skills/router/types.ts +90 -0
- package/src/skills/systematic-debugging/index.ts +1 -1
- package/src/skills/tdd/index.ts +1 -1
- package/src/skills/to-plan/index-enhanced.ts +3 -5
- package/src/skills/to-plan/index.ts +231 -502
|
@@ -1,25 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* To Plan Skill -
|
|
2
|
+
* To Plan Skill - Thin orchestrator that delegates all planning to subagent
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* Architecture:
|
|
5
|
+
* 1. Initialize STATE.md with goal
|
|
6
|
+
* 2. Dispatch planner subagent with FRESH context (no main agent state)
|
|
7
|
+
* 3. Parse planner output into Plan structure
|
|
8
|
+
* 4. Group tasks into waves via topological sort
|
|
9
|
+
* 5. Write PLAN.md
|
|
10
|
+
* 6. Update STATE.md with plan
|
|
10
11
|
*
|
|
11
|
-
*
|
|
12
|
+
* The skill is THIN - all heavy lifting done by planner subagent.
|
|
12
13
|
*/
|
|
13
14
|
|
|
14
15
|
import { z } from 'zod'
|
|
15
16
|
import { Skill } from '../skill.js'
|
|
16
17
|
import type { SkillContext } from '../types.js'
|
|
17
|
-
import {
|
|
18
|
-
import
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import { mkdir, writeFile } from 'fs/promises'
|
|
22
|
-
import { join } from 'path'
|
|
18
|
+
import { StateMdManager } from '../../persistence/state-md.js'
|
|
19
|
+
import { PlanMdManager } from '../../persistence/plan-md.js'
|
|
20
|
+
import type { Plan, Task, Wave } from '../../persistence/types.js'
|
|
21
|
+
import type { SubagentConfig, SubagentContract } from '../../agents/types.js'
|
|
23
22
|
|
|
24
23
|
// ---------------------------------------------------------------------------
|
|
25
24
|
// Schemas
|
|
@@ -28,335 +27,188 @@ import { join } from 'path'
|
|
|
28
27
|
const ToPlanInputSchema = z.object({
|
|
29
28
|
goal: z.string().min(1, 'Goal is required'),
|
|
30
29
|
context: z.string().optional(),
|
|
31
|
-
|
|
30
|
+
constraints: z.array(z.string()).optional(),
|
|
32
31
|
model: z.string().optional(),
|
|
33
|
-
|
|
34
|
-
workType: z.enum(['feature', 'bugfix', 'migration', 'cleanup', 'refactor']).optional(),
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
const TaskSchema = z.object({
|
|
38
|
-
id: z.string(),
|
|
39
|
-
title: z.string(),
|
|
40
|
-
description: z.string(),
|
|
41
|
-
owns: z.array(z.string()),
|
|
42
|
-
reads: z.array(z.string()),
|
|
43
|
-
dependencies: z.array(z.string()),
|
|
44
|
-
verification: z.string().optional(),
|
|
32
|
+
outputPath: z.string().optional(),
|
|
45
33
|
})
|
|
46
34
|
|
|
47
35
|
const ToPlanOutputSchema = z.object({
|
|
48
36
|
planPath: z.string(),
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
type: z.string(),
|
|
52
|
-
complexity: z.enum(['low', 'medium', 'high']),
|
|
53
|
-
risk: z.enum(['low', 'medium', 'high']),
|
|
54
|
-
}),
|
|
55
|
-
planCheckerPassed: z.boolean(),
|
|
56
|
-
tasks: z.array(TaskSchema),
|
|
37
|
+
waves: z.array(z.custom<Wave>()),
|
|
38
|
+
taskCount: z.number(),
|
|
57
39
|
summary: z.string(),
|
|
58
40
|
tokensUsed: z.number(),
|
|
59
41
|
})
|
|
60
42
|
|
|
61
43
|
type ToPlanInput = z.infer<typeof ToPlanInputSchema>
|
|
62
44
|
type ToPlanOutput = z.infer<typeof ToPlanOutputSchema>
|
|
63
|
-
type TaskDef = z.infer<typeof TaskSchema>
|
|
64
45
|
|
|
65
46
|
// ---------------------------------------------------------------------------
|
|
66
|
-
//
|
|
47
|
+
// Planner Subagent Prompt
|
|
67
48
|
// ---------------------------------------------------------------------------
|
|
68
49
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
complexity: 'low' | 'medium' | 'high'
|
|
72
|
-
risk: 'low' | 'medium' | 'high'
|
|
73
|
-
explorationAreas: string[]
|
|
74
|
-
}
|
|
50
|
+
function buildPlannerPrompt(input: ToPlanInput): string {
|
|
51
|
+
return `# Planning Task
|
|
75
52
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
tasks: TaskDef[]
|
|
79
|
-
summary: string
|
|
80
|
-
}
|
|
53
|
+
## Goal
|
|
54
|
+
${input.goal}
|
|
81
55
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
// ---------------------------------------------------------------------------
|
|
56
|
+
${input.context ? `## Context\n${input.context}\n` : ''}
|
|
57
|
+
${input.constraints ? `## Constraints\n${input.constraints.map(c => `- ${c}`).join('\n')}\n` : ''}
|
|
85
58
|
|
|
86
|
-
|
|
87
|
-
return [
|
|
88
|
-
'## Classify This Request',
|
|
89
|
-
'',
|
|
90
|
-
`**Goal**: ${goal}`,
|
|
91
|
-
context ? `**Context**: ${context}` : '',
|
|
92
|
-
workType ? `**Hinted Work Type**: ${workType}` : '',
|
|
93
|
-
researchFindings ? `## Research Findings\n\n${researchFindings.slice(0, 1000)}` : '',
|
|
94
|
-
'',
|
|
95
|
-
'Determine:',
|
|
96
|
-
'- **Type**: feature, bugfix, migration, cleanup, refactor, or other',
|
|
97
|
-
'- **Complexity**: low (1-3 files), medium (3-10 files), high (10+ files / architectural)',
|
|
98
|
-
'- **Risk**: low (isolated, well-tested), medium (shared code), high (core systems, low coverage)',
|
|
99
|
-
'- **Exploration Areas**: 1-3 key directories or file patterns to explore first',
|
|
100
|
-
'',
|
|
101
|
-
'Output as JSON:',
|
|
102
|
-
'```json',
|
|
103
|
-
'{ "type": "...", "complexity": "low|medium|high", "risk": "low|medium|high", "explorationAreas": ["path/area1", "path/area2"] }',
|
|
104
|
-
'```',
|
|
105
|
-
].join('\n')
|
|
106
|
-
}
|
|
59
|
+
## Instructions
|
|
107
60
|
|
|
108
|
-
|
|
109
|
-
// Step 2: Exploration prompt builder (one per area)
|
|
110
|
-
// ---------------------------------------------------------------------------
|
|
61
|
+
Create a detailed implementation plan. Break the work into small, concrete tasks.
|
|
111
62
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
context ? `**Context**: ${context}` : '',
|
|
118
|
-
`**Focus Area**: ${area}`,
|
|
119
|
-
'',
|
|
120
|
-
'## Instructions',
|
|
121
|
-
'',
|
|
122
|
-
'1. Search and read files within this area',
|
|
123
|
-
'2. Identify: existing patterns, relevant abstractions, dependencies, test coverage',
|
|
124
|
-
'3. Note any existing implementation that relates to the goal',
|
|
125
|
-
'4. Flag surprises, risks, or undocumented behavior',
|
|
126
|
-
'',
|
|
127
|
-
'## Constraints',
|
|
128
|
-
'- READ ONLY — do not modify any files',
|
|
129
|
-
'- Focus only on the assigned area',
|
|
130
|
-
'- Be thorough but concise',
|
|
131
|
-
].join('\n')
|
|
132
|
-
}
|
|
63
|
+
### Task Requirements
|
|
64
|
+
- Each task should be completable in 1-5 minutes by a subagent
|
|
65
|
+
- Tasks should be independent where possible
|
|
66
|
+
- Each task must have clear owns[] (files to modify) and reads[] (files to read)
|
|
67
|
+
- Dependencies must be explicitly declared
|
|
133
68
|
|
|
134
|
-
|
|
135
|
-
// Step 3: Synthesize prompt builder
|
|
136
|
-
// ---------------------------------------------------------------------------
|
|
69
|
+
### Output Format
|
|
137
70
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
'- Each task: id, title, description, owns[], reads[], dependencies[], verification',
|
|
172
|
-
'- **owns[]**: files the task CREATES or MODIFIES',
|
|
173
|
-
'- **reads[]**: files the task reads for context',
|
|
174
|
-
'- **dependencies[]**: task IDs that must finish first',
|
|
175
|
-
'- **verification**: how to confirm the task is done correctly',
|
|
176
|
-
'- Tasks sized for 1-5 minutes of subagent work',
|
|
177
|
-
'',
|
|
178
|
-
'Output as JSON:',
|
|
179
|
-
'```json',
|
|
180
|
-
'{ "executionModel": "...", "tasks": [...], "summary": "..." }',
|
|
181
|
-
'```',
|
|
182
|
-
].join('\n')
|
|
71
|
+
Return ONLY a JSON object with this exact structure:
|
|
72
|
+
|
|
73
|
+
\`\`\`json
|
|
74
|
+
{
|
|
75
|
+
"goal": "restated goal",
|
|
76
|
+
"tasks": [
|
|
77
|
+
{
|
|
78
|
+
"id": "task-1",
|
|
79
|
+
"description": "Clear description of what to do",
|
|
80
|
+
"estimatedComplexity": "low|medium|high",
|
|
81
|
+
"owns": ["files/to/create/or/modify"],
|
|
82
|
+
"reads": ["files/to/read/for/context"],
|
|
83
|
+
"dependencies": ["task-ids-that-must-complete-first"],
|
|
84
|
+
"verificationCriteria": ["how to verify this task is done"]
|
|
85
|
+
}
|
|
86
|
+
],
|
|
87
|
+
"dependencies": {
|
|
88
|
+
"task-2": ["task-1"]
|
|
89
|
+
},
|
|
90
|
+
"verificationCriteria": [
|
|
91
|
+
"Overall acceptance criteria for the plan"
|
|
92
|
+
],
|
|
93
|
+
"estimatedDuration": 300000,
|
|
94
|
+
"rationale": "Brief explanation of the plan structure"
|
|
95
|
+
}
|
|
96
|
+
\`\`\`
|
|
97
|
+
|
|
98
|
+
## Rules
|
|
99
|
+
1. Use descriptive task IDs like "setup-config", "implement-core", "add-tests"
|
|
100
|
+
2. Keep task descriptions under 100 characters
|
|
101
|
+
3. owns[] and reads[] should be relative file paths
|
|
102
|
+
4. No circular dependencies
|
|
103
|
+
5. Include at least one verification criterion per task`
|
|
183
104
|
}
|
|
184
105
|
|
|
185
106
|
// ---------------------------------------------------------------------------
|
|
186
|
-
//
|
|
107
|
+
// Parsing Helpers
|
|
187
108
|
// ---------------------------------------------------------------------------
|
|
188
109
|
|
|
189
|
-
function
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
.map(
|
|
198
|
-
(t)
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
110
|
+
function parsePlanFromResponse(response: string): Plan {
|
|
111
|
+
try {
|
|
112
|
+
// Extract JSON from markdown code block or use raw response
|
|
113
|
+
const jsonMatch = response.match(/```json\s*([\s\S]*?)\s*```/)
|
|
114
|
+
const jsonStr = jsonMatch ? jsonMatch[1].trim() : response.trim()
|
|
115
|
+
const parsed = JSON.parse(jsonStr)
|
|
116
|
+
|
|
117
|
+
// Transform to Plan structure
|
|
118
|
+
const tasks: Task[] = (parsed.tasks || []).map((t: Record<string, unknown>) => ({
|
|
119
|
+
id: String(t.id || ''),
|
|
120
|
+
description: String(t.description || ''),
|
|
121
|
+
status: 'pending',
|
|
122
|
+
dependencies: Array.isArray(t.dependencies) ? t.dependencies.map(String) : [],
|
|
123
|
+
estimatedComplexity: (t.estimatedComplexity as 'low' | 'medium' | 'high') || 'medium',
|
|
124
|
+
}))
|
|
125
|
+
|
|
126
|
+
const dependencies: Record<string, string[]> = parsed.dependencies || {}
|
|
127
|
+
|
|
128
|
+
// Ensure all tasks have entry in dependencies
|
|
129
|
+
for (const task of tasks) {
|
|
130
|
+
if (!dependencies[task.id]) {
|
|
131
|
+
dependencies[task.id] = task.dependencies
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
goal: String(parsed.goal || ''),
|
|
137
|
+
tasks,
|
|
138
|
+
dependencies,
|
|
139
|
+
waves: [], // Will be computed via topological sort
|
|
140
|
+
verificationCriteria: Array.isArray(parsed.verificationCriteria)
|
|
141
|
+
? parsed.verificationCriteria.map(String)
|
|
142
|
+
: [],
|
|
143
|
+
estimatedDuration: Number(parsed.estimatedDuration) || 0,
|
|
144
|
+
}
|
|
145
|
+
} catch (error) {
|
|
146
|
+
throw new Error(`Failed to parse planner output: ${(error as Error).message}`)
|
|
147
|
+
}
|
|
223
148
|
}
|
|
224
149
|
|
|
225
150
|
// ---------------------------------------------------------------------------
|
|
226
|
-
//
|
|
151
|
+
// Topological Sort for Wave Grouping
|
|
227
152
|
// ---------------------------------------------------------------------------
|
|
228
153
|
|
|
229
|
-
function
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
'## Checklist',
|
|
236
|
-
'',
|
|
237
|
-
'1. Do tasks cover all requirements?',
|
|
238
|
-
'2. Does every task have clear owns[] and reads[]?',
|
|
239
|
-
'3. Are dependencies acyclic and valid (referenced IDs exist)?',
|
|
240
|
-
'4. Are tasks small enough for subagent execution (<5 min)?',
|
|
241
|
-
'5. Does each task have a verification step?',
|
|
242
|
-
'6. Are identified risks addressed?',
|
|
243
|
-
'7. Could tasks conflict by owning the same file?',
|
|
244
|
-
'',
|
|
245
|
-
'Return: PASS or BLOCKED with specific issues.',
|
|
246
|
-
].join('\n')
|
|
247
|
-
}
|
|
154
|
+
function groupTasksIntoWaves(plan: Plan): Wave[] {
|
|
155
|
+
const waves: Wave[] = []
|
|
156
|
+
const completed = new Set<string>()
|
|
157
|
+
const remaining = new Set(plan.tasks.map(t => t.id))
|
|
158
|
+
const taskMap = new Map(plan.tasks.map(t => [t.id, t]))
|
|
159
|
+
let waveIndex = 0
|
|
248
160
|
|
|
249
|
-
//
|
|
250
|
-
|
|
251
|
-
//
|
|
161
|
+
// Kahn's algorithm for topological sort with wave grouping
|
|
162
|
+
while (remaining.size > 0) {
|
|
163
|
+
// Find all tasks whose dependencies are satisfied
|
|
164
|
+
const waveTaskIds: string[] = []
|
|
165
|
+
|
|
166
|
+
for (const taskId of remaining) {
|
|
167
|
+
const deps = plan.dependencies[taskId] || []
|
|
168
|
+
const allDepsCompleted = deps.every(dep => completed.has(dep))
|
|
252
169
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
if (jsonMatch) {
|
|
256
|
-
try {
|
|
257
|
-
const parsed = JSON.parse(jsonMatch[1].trim())
|
|
258
|
-
return {
|
|
259
|
-
type: parsed.type || 'feature',
|
|
260
|
-
complexity: parsed.complexity || 'medium',
|
|
261
|
-
risk: parsed.risk || 'medium',
|
|
262
|
-
explorationAreas: Array.isArray(parsed.explorationAreas)
|
|
263
|
-
? parsed.explorationAreas.slice(0, 3)
|
|
264
|
-
: ['src/'],
|
|
170
|
+
if (allDepsCompleted) {
|
|
171
|
+
waveTaskIds.push(taskId)
|
|
265
172
|
}
|
|
266
|
-
} catch {
|
|
267
|
-
// fall through to defaults
|
|
268
173
|
}
|
|
269
|
-
}
|
|
270
|
-
return {
|
|
271
|
-
type: 'feature',
|
|
272
|
-
complexity: 'medium',
|
|
273
|
-
risk: 'medium',
|
|
274
|
-
explorationAreas: ['src/'],
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
174
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
const parsed = JSON.parse(jsonMatch[1].trim())
|
|
283
|
-
return {
|
|
284
|
-
executionModel: parsed.executionModel || 'ordered-non-parallel',
|
|
285
|
-
tasks: Array.isArray(parsed.tasks) ? parsed.tasks : [],
|
|
286
|
-
summary: parsed.summary || 'Plan synthesized',
|
|
287
|
-
}
|
|
288
|
-
} catch {
|
|
289
|
-
// fall through to defaults
|
|
175
|
+
if (waveTaskIds.length === 0) {
|
|
176
|
+
// Circular dependency detected
|
|
177
|
+
const remainingIds = Array.from(remaining).join(', ')
|
|
178
|
+
throw new Error(`Circular dependency detected among tasks: ${remainingIds}`)
|
|
290
179
|
}
|
|
291
|
-
}
|
|
292
|
-
return {
|
|
293
|
-
executionModel: 'ordered-non-parallel',
|
|
294
|
-
tasks: [
|
|
295
|
-
{
|
|
296
|
-
id: '1',
|
|
297
|
-
title: 'Implementation',
|
|
298
|
-
description: 'Core implementation work',
|
|
299
|
-
owns: [],
|
|
300
|
-
reads: [],
|
|
301
|
-
dependencies: [],
|
|
302
|
-
verification: 'Run tests',
|
|
303
|
-
},
|
|
304
|
-
],
|
|
305
|
-
summary: output.slice(0, 200),
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
180
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
181
|
+
// Create wave with full task objects
|
|
182
|
+
const waveTasks = waveTaskIds
|
|
183
|
+
.map(id => taskMap.get(id))
|
|
184
|
+
.filter((t): t is Task => t !== undefined)
|
|
312
185
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
186
|
+
waves.push({
|
|
187
|
+
id: `wave-${waveIndex}`,
|
|
188
|
+
tasks: waveTasks,
|
|
189
|
+
})
|
|
316
190
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
'',
|
|
328
|
-
t.description,
|
|
329
|
-
'',
|
|
330
|
-
`- **Owns**: ${t.owns.join(', ') || 'none'}`,
|
|
331
|
-
`- **Reads**: ${t.reads.join(', ') || 'none'}`,
|
|
332
|
-
`- **Depends on**: ${t.dependencies.join(', ') || 'none'}`,
|
|
333
|
-
`- **Verification**: ${t.verification || 'Run tests'}`,
|
|
334
|
-
].join('\n'),
|
|
335
|
-
)
|
|
336
|
-
.join('\n\n')
|
|
337
|
-
|
|
338
|
-
return [
|
|
339
|
-
`# ${goal}`,
|
|
340
|
-
'',
|
|
341
|
-
context ? `## Context\n\n${context}\n` : '',
|
|
342
|
-
`## Execution Model\n\n${synthesis.executionModel}`,
|
|
343
|
-
'',
|
|
344
|
-
'## Tasks',
|
|
345
|
-
'',
|
|
346
|
-
tasksBlock,
|
|
347
|
-
].join('\n')
|
|
191
|
+
// Mark tasks as completed and remove from remaining
|
|
192
|
+
for (const taskId of waveTaskIds) {
|
|
193
|
+
completed.add(taskId)
|
|
194
|
+
remaining.delete(taskId)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
waveIndex++
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return waves
|
|
348
201
|
}
|
|
349
202
|
|
|
350
203
|
// ---------------------------------------------------------------------------
|
|
351
|
-
// Skill
|
|
204
|
+
// Skill Class
|
|
352
205
|
// ---------------------------------------------------------------------------
|
|
353
206
|
|
|
354
207
|
export class ToPlanSkill extends Skill<ToPlanInput, ToPlanOutput> {
|
|
355
208
|
constructor() {
|
|
356
209
|
super({
|
|
357
210
|
name: 'to-plan',
|
|
358
|
-
description:
|
|
359
|
-
'Create implementation plans via 5-step subagent dispatch: classify, explore, synthesize, write, check',
|
|
211
|
+
description: 'Thin orchestrator that delegates planning to subagent with fresh context',
|
|
360
212
|
requires: [],
|
|
361
213
|
inputSchema: ToPlanInputSchema,
|
|
362
214
|
outputSchema: ToPlanOutputSchema,
|
|
@@ -364,220 +216,97 @@ export class ToPlanSkill extends Skill<ToPlanInput, ToPlanOutput> {
|
|
|
364
216
|
}
|
|
365
217
|
|
|
366
218
|
async execute(input: ToPlanInput, context: SkillContext): Promise<ToPlanOutput> {
|
|
367
|
-
const { config, logger } = context
|
|
368
|
-
const
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
219
|
+
const { config, logger, dispatcher } = context
|
|
220
|
+
const model = input.model || config.defaultModel || 'claude-sonnet-4.5'
|
|
221
|
+
let tokensUsed = 0
|
|
222
|
+
|
|
223
|
+
// Determine paths
|
|
224
|
+
const statePath = input.outputPath
|
|
225
|
+
? input.outputPath.replace(/\.md$/, '-STATE.md')
|
|
226
|
+
: '.pi/state/STATE.md'
|
|
227
|
+
const planPath = input.outputPath || '.pi/state/PLAN.md'
|
|
228
|
+
|
|
229
|
+
// Initialize managers
|
|
230
|
+
const stateManager = new StateMdManager(statePath)
|
|
231
|
+
const planManager = new PlanMdManager(planPath)
|
|
232
|
+
|
|
233
|
+
// Step 1: Initialize STATE.md with goal
|
|
234
|
+
logger.info('[to-plan] Initializing STATE.md')
|
|
235
|
+
await stateManager.initialize(input.goal)
|
|
236
|
+
|
|
237
|
+
// Step 2: Dispatch planner subagent with FRESH context
|
|
238
|
+
logger.info('[to-plan] Dispatching planner subagent with fresh context')
|
|
239
|
+
|
|
240
|
+
const plannerConfig: SubagentConfig = {
|
|
241
|
+
role: 'planner',
|
|
242
|
+
model,
|
|
243
|
+
timeout: 120000,
|
|
375
244
|
}
|
|
376
245
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
scope: 'technical',
|
|
390
|
-
questions: [
|
|
391
|
-
'What are the latest best practices?',
|
|
392
|
-
'Are there known issues or breaking changes?',
|
|
393
|
-
'What are recommended approaches?'
|
|
394
|
-
],
|
|
395
|
-
timeRange: '1y'
|
|
396
|
-
})
|
|
397
|
-
)
|
|
398
|
-
totalTokens += researchResult.tokensUsed
|
|
399
|
-
researchSummary = researchResult.output.slice(0, 1500)
|
|
400
|
-
logger.info(`[to-plan] Research complete: ${researchSummary.slice(0, 100)}...`)
|
|
401
|
-
}
|
|
246
|
+
const plannerContract: SubagentContract = {
|
|
247
|
+
permissions: {
|
|
248
|
+
readFiles: true,
|
|
249
|
+
searchCode: true,
|
|
250
|
+
runCommands: false,
|
|
251
|
+
writeFiles: false,
|
|
252
|
+
gitOperations: false,
|
|
253
|
+
},
|
|
254
|
+
prompt: buildPlannerPrompt(input),
|
|
255
|
+
owns: [],
|
|
256
|
+
reads: [],
|
|
257
|
+
}
|
|
402
258
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
{
|
|
408
|
-
permissions: {
|
|
409
|
-
readFiles: true,
|
|
410
|
-
searchCode: false,
|
|
411
|
-
runCommands: false,
|
|
412
|
-
writeFiles: false,
|
|
413
|
-
gitOperations: false,
|
|
414
|
-
},
|
|
415
|
-
prompt: buildClassifyPrompt(input.goal, input.context, input.workType, researchSummary),
|
|
416
|
-
owns: [],
|
|
417
|
-
reads: [],
|
|
418
|
-
},
|
|
419
|
-
)
|
|
420
|
-
|
|
421
|
-
totalTokens += classifyResult.tokensUsed
|
|
422
|
-
const classification = parseClassification(classifyResult.output)
|
|
423
|
-
logger.info(
|
|
424
|
-
`[to-plan] Classified: type=${classification.type}, complexity=${classification.complexity}, risk=${classification.risk}`,
|
|
425
|
-
)
|
|
426
|
-
|
|
427
|
-
// ---- Step 2: Dispatch 1-3 read-only exploration subagents (parallel) ----
|
|
428
|
-
const areas = classification.explorationAreas.slice(0, 3)
|
|
429
|
-
logger.info(
|
|
430
|
-
`[to-plan] Step 2: Dispatching ${areas.length} exploration subagent(s) in parallel`,
|
|
431
|
-
)
|
|
432
|
-
|
|
433
|
-
const exploreConfigs: SubagentConfig[] = areas.map(() => ({
|
|
434
|
-
role: 'explorer' as const,
|
|
435
|
-
model,
|
|
436
|
-
tokenBudget: 16000,
|
|
437
|
-
}))
|
|
438
|
-
|
|
439
|
-
const exploreContracts: SubagentContract[] = areas.map((area) => ({
|
|
440
|
-
permissions: {
|
|
441
|
-
readFiles: true,
|
|
442
|
-
searchCode: true,
|
|
443
|
-
runCommands: false,
|
|
444
|
-
writeFiles: false,
|
|
445
|
-
gitOperations: false,
|
|
446
|
-
},
|
|
447
|
-
prompt: buildExplorePrompt(area, input.goal, input.context),
|
|
448
|
-
owns: [],
|
|
449
|
-
reads: input.filesHint ?? [],
|
|
450
|
-
}))
|
|
451
|
-
|
|
452
|
-
const exploreResults =
|
|
453
|
-
areas.length === 1
|
|
454
|
-
? [await dispatcher.dispatch(exploreConfigs[0], exploreContracts[0])]
|
|
455
|
-
: await dispatcher.dispatchParallel(exploreConfigs, exploreContracts)
|
|
456
|
-
|
|
457
|
-
for (const result of exploreResults) {
|
|
458
|
-
totalTokens += result.tokensUsed
|
|
459
|
-
}
|
|
259
|
+
const plannerResult = await dispatcher.dispatch<{ output: string; tokensUsed: number }>(
|
|
260
|
+
plannerConfig,
|
|
261
|
+
plannerContract
|
|
262
|
+
)
|
|
460
263
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
`[to-plan] ${successfulExplorations.length}/${areas.length} explorations succeeded`,
|
|
464
|
-
)
|
|
465
|
-
|
|
466
|
-
// ---- Step 3: Synthesize findings ----
|
|
467
|
-
logger.info('[to-plan] Step 3: Synthesizing findings')
|
|
468
|
-
const synthesizeResult = await dispatcher.dispatch(
|
|
469
|
-
{ role: 'planner', model },
|
|
470
|
-
{
|
|
471
|
-
permissions: {
|
|
472
|
-
readFiles: true,
|
|
473
|
-
searchCode: false,
|
|
474
|
-
runCommands: false,
|
|
475
|
-
writeFiles: false,
|
|
476
|
-
gitOperations: false,
|
|
477
|
-
},
|
|
478
|
-
prompt: buildSynthesizePrompt(
|
|
479
|
-
input.goal,
|
|
480
|
-
input.context,
|
|
481
|
-
classification,
|
|
482
|
-
successfulExplorations.map((r) => r.output),
|
|
483
|
-
),
|
|
484
|
-
owns: [],
|
|
485
|
-
reads: [],
|
|
486
|
-
},
|
|
487
|
-
)
|
|
488
|
-
|
|
489
|
-
totalTokens += synthesizeResult.tokensUsed
|
|
490
|
-
const synthesis = parseSynthesis(synthesizeResult.output)
|
|
491
|
-
logger.info(
|
|
492
|
-
`[to-plan] Synthesized: ${synthesis.tasks.length} tasks, model=${synthesis.executionModel}`,
|
|
493
|
-
)
|
|
494
|
-
|
|
495
|
-
// ---- Step 4: Write plan to disk ----
|
|
496
|
-
const date = new Date().toISOString().split('T')[0]
|
|
497
|
-
const sanitized = input.goal
|
|
498
|
-
.toLowerCase()
|
|
499
|
-
.replace(/[^a-z0-9]+/g, '-')
|
|
500
|
-
.slice(0, 50)
|
|
501
|
-
const planPath =
|
|
502
|
-
input.outputPath ||
|
|
503
|
-
join(config.planPath, `${date}-${sanitized}-implementation-plan.md`)
|
|
504
|
-
|
|
505
|
-
await mkdir(config.planPath, { recursive: true })
|
|
506
|
-
|
|
507
|
-
logger.info(`[to-plan] Step 4: Writing plan to ${planPath}`)
|
|
508
|
-
const writeResult = await dispatcher.dispatch(
|
|
509
|
-
{ role: 'implementer', model },
|
|
510
|
-
{
|
|
511
|
-
permissions: {
|
|
512
|
-
readFiles: false,
|
|
513
|
-
searchCode: false,
|
|
514
|
-
runCommands: false,
|
|
515
|
-
writeFiles: true,
|
|
516
|
-
gitOperations: false,
|
|
517
|
-
},
|
|
518
|
-
prompt: buildWritePlanPrompt(
|
|
519
|
-
input.goal,
|
|
520
|
-
input.context,
|
|
521
|
-
classification,
|
|
522
|
-
synthesis,
|
|
523
|
-
planPath,
|
|
524
|
-
),
|
|
525
|
-
owns: [planPath],
|
|
526
|
-
reads: [],
|
|
527
|
-
},
|
|
528
|
-
)
|
|
529
|
-
|
|
530
|
-
totalTokens += writeResult.tokensUsed
|
|
531
|
-
|
|
532
|
-
// If the write subagent failed, write a fallback plan
|
|
533
|
-
if (writeResult.status !== 'success') {
|
|
534
|
-
const fallback = buildFallbackPlan(input.goal, input.context, synthesis)
|
|
535
|
-
await writeFile(planPath, fallback, 'utf-8')
|
|
536
|
-
logger.warn('[to-plan] Write subagent failed; wrote fallback plan')
|
|
537
|
-
}
|
|
264
|
+
tokensUsed += plannerResult.tokensUsed || 0
|
|
265
|
+
logger.info(`[to-plan] Planner subagent completed, used ${plannerResult.tokensUsed} tokens`)
|
|
538
266
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
267
|
+
// Step 3: Parse plan from subagent output
|
|
268
|
+
logger.info('[to-plan] Parsing plan from subagent output')
|
|
269
|
+
const plan = parsePlanFromResponse(plannerResult.output)
|
|
270
|
+
logger.info(`[to-plan] Parsed ${plan.tasks.length} tasks`)
|
|
271
|
+
|
|
272
|
+
// Step 4: Group tasks into waves via topological sort
|
|
273
|
+
logger.info('[to-plan] Grouping tasks into waves')
|
|
274
|
+
const waves = groupTasksIntoWaves(plan)
|
|
275
|
+
plan.waves = waves
|
|
276
|
+
logger.info(`[to-plan] Created ${waves.length} waves for parallel execution`)
|
|
277
|
+
|
|
278
|
+
// Log wave details
|
|
279
|
+
for (const wave of waves) {
|
|
280
|
+
const taskIds = wave.tasks.map(t => t.id).join(', ')
|
|
281
|
+
logger.info(`[to-plan] ${wave.id}: ${taskIds}`)
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Step 5: Write PLAN.md
|
|
285
|
+
logger.info(`[to-plan] Writing plan to ${planPath}`)
|
|
286
|
+
await planManager.writePlan(plan)
|
|
287
|
+
|
|
288
|
+
// Step 6: Update STATE.md with plan
|
|
289
|
+
logger.info('[to-plan] Recording plan in STATE.md')
|
|
290
|
+
await stateManager.recordPlan(plan)
|
|
291
|
+
await stateManager.updatePhase('planning')
|
|
292
|
+
await stateManager.updateTokenUsage(tokensUsed)
|
|
293
|
+
|
|
294
|
+
// Build summary
|
|
295
|
+
const summary = [
|
|
296
|
+
`Plan created with ${plan.tasks.length} tasks`,
|
|
297
|
+
`Organized into ${waves.length} parallel waves`,
|
|
298
|
+
`Estimated duration: ${plan.estimatedDuration}ms`,
|
|
299
|
+
`Used ${tokensUsed} tokens`,
|
|
300
|
+
].join('. ')
|
|
301
|
+
|
|
302
|
+
logger.info(`[to-plan] ${summary}`)
|
|
303
|
+
|
|
304
|
+
return {
|
|
305
|
+
planPath,
|
|
306
|
+
waves,
|
|
307
|
+
taskCount: plan.tasks.length,
|
|
308
|
+
summary,
|
|
309
|
+
tokensUsed,
|
|
581
310
|
}
|
|
582
311
|
}
|
|
583
312
|
}
|