@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,458 +1,263 @@
|
|
|
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
|
import { z } from 'zod';
|
|
14
15
|
import { Skill } from '../skill.js';
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import { createMainAgentGuard } from '../../guard/main-agent.js';
|
|
18
|
-
import { mkdir, writeFile } from 'fs/promises';
|
|
19
|
-
import { join } from 'path';
|
|
16
|
+
import { StateMdManager } from '../../persistence/state-md.js';
|
|
17
|
+
import { PlanMdManager } from '../../persistence/plan-md.js';
|
|
20
18
|
// ---------------------------------------------------------------------------
|
|
21
19
|
// Schemas
|
|
22
20
|
// ---------------------------------------------------------------------------
|
|
23
21
|
const ToPlanInputSchema = z.object({
|
|
24
22
|
goal: z.string().min(1, 'Goal is required'),
|
|
25
23
|
context: z.string().optional(),
|
|
26
|
-
|
|
24
|
+
constraints: z.array(z.string()).optional(),
|
|
27
25
|
model: z.string().optional(),
|
|
28
|
-
|
|
29
|
-
workType: z.enum(['feature', 'bugfix', 'migration', 'cleanup', 'refactor']).optional(),
|
|
30
|
-
});
|
|
31
|
-
const TaskSchema = z.object({
|
|
32
|
-
id: z.string(),
|
|
33
|
-
title: z.string(),
|
|
34
|
-
description: z.string(),
|
|
35
|
-
owns: z.array(z.string()),
|
|
36
|
-
reads: z.array(z.string()),
|
|
37
|
-
dependencies: z.array(z.string()),
|
|
38
|
-
verification: z.string().optional(),
|
|
26
|
+
outputPath: z.string().optional(),
|
|
39
27
|
});
|
|
40
28
|
const ToPlanOutputSchema = z.object({
|
|
41
29
|
planPath: z.string(),
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
type: z.string(),
|
|
45
|
-
complexity: z.enum(['low', 'medium', 'high']),
|
|
46
|
-
risk: z.enum(['low', 'medium', 'high']),
|
|
47
|
-
}),
|
|
48
|
-
planCheckerPassed: z.boolean(),
|
|
49
|
-
tasks: z.array(TaskSchema),
|
|
30
|
+
waves: z.array(z.custom()),
|
|
31
|
+
taskCount: z.number(),
|
|
50
32
|
summary: z.string(),
|
|
51
33
|
tokensUsed: z.number(),
|
|
52
34
|
});
|
|
53
35
|
// ---------------------------------------------------------------------------
|
|
54
|
-
//
|
|
55
|
-
// ---------------------------------------------------------------------------
|
|
56
|
-
function buildClassifyPrompt(goal, context, workType, researchFindings) {
|
|
57
|
-
return [
|
|
58
|
-
'## Classify This Request',
|
|
59
|
-
'',
|
|
60
|
-
`**Goal**: ${goal}`,
|
|
61
|
-
context ? `**Context**: ${context}` : '',
|
|
62
|
-
workType ? `**Hinted Work Type**: ${workType}` : '',
|
|
63
|
-
researchFindings ? `## Research Findings\n\n${researchFindings.slice(0, 1000)}` : '',
|
|
64
|
-
'',
|
|
65
|
-
'Determine:',
|
|
66
|
-
'- **Type**: feature, bugfix, migration, cleanup, refactor, or other',
|
|
67
|
-
'- **Complexity**: low (1-3 files), medium (3-10 files), high (10+ files / architectural)',
|
|
68
|
-
'- **Risk**: low (isolated, well-tested), medium (shared code), high (core systems, low coverage)',
|
|
69
|
-
'- **Exploration Areas**: 1-3 key directories or file patterns to explore first',
|
|
70
|
-
'',
|
|
71
|
-
'Output as JSON:',
|
|
72
|
-
'```json',
|
|
73
|
-
'{ "type": "...", "complexity": "low|medium|high", "risk": "low|medium|high", "explorationAreas": ["path/area1", "path/area2"] }',
|
|
74
|
-
'```',
|
|
75
|
-
].join('\n');
|
|
76
|
-
}
|
|
77
|
-
// ---------------------------------------------------------------------------
|
|
78
|
-
// Step 2: Exploration prompt builder (one per area)
|
|
79
|
-
// ---------------------------------------------------------------------------
|
|
80
|
-
function buildExplorePrompt(area, goal, context) {
|
|
81
|
-
return [
|
|
82
|
-
'## Exploration Task',
|
|
83
|
-
'',
|
|
84
|
-
`**Goal**: ${goal}`,
|
|
85
|
-
context ? `**Context**: ${context}` : '',
|
|
86
|
-
`**Focus Area**: ${area}`,
|
|
87
|
-
'',
|
|
88
|
-
'## Instructions',
|
|
89
|
-
'',
|
|
90
|
-
'1. Search and read files within this area',
|
|
91
|
-
'2. Identify: existing patterns, relevant abstractions, dependencies, test coverage',
|
|
92
|
-
'3. Note any existing implementation that relates to the goal',
|
|
93
|
-
'4. Flag surprises, risks, or undocumented behavior',
|
|
94
|
-
'',
|
|
95
|
-
'## Constraints',
|
|
96
|
-
'- READ ONLY — do not modify any files',
|
|
97
|
-
'- Focus only on the assigned area',
|
|
98
|
-
'- Be thorough but concise',
|
|
99
|
-
].join('\n');
|
|
100
|
-
}
|
|
101
|
-
// ---------------------------------------------------------------------------
|
|
102
|
-
// Step 3: Synthesize prompt builder
|
|
36
|
+
// Planner Subagent Prompt
|
|
103
37
|
// ---------------------------------------------------------------------------
|
|
104
|
-
function
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
]
|
|
38
|
+
function buildPlannerPrompt(input) {
|
|
39
|
+
return `# Planning Task
|
|
40
|
+
|
|
41
|
+
## Goal
|
|
42
|
+
${input.goal}
|
|
43
|
+
|
|
44
|
+
${input.context ? `## Context\n${input.context}\n` : ''}
|
|
45
|
+
${input.constraints ? `## Constraints\n${input.constraints.map(c => `- ${c}`).join('\n')}\n` : ''}
|
|
46
|
+
|
|
47
|
+
## Instructions
|
|
48
|
+
|
|
49
|
+
Create a detailed implementation plan. Break the work into small, concrete tasks.
|
|
50
|
+
|
|
51
|
+
### Task Requirements
|
|
52
|
+
- Each task should be completable in 1-5 minutes by a subagent
|
|
53
|
+
- Tasks should be independent where possible
|
|
54
|
+
- Each task must have clear owns[] (files to modify) and reads[] (files to read)
|
|
55
|
+
- Dependencies must be explicitly declared
|
|
56
|
+
|
|
57
|
+
### Output Format
|
|
58
|
+
|
|
59
|
+
Return ONLY a JSON object with this exact structure:
|
|
60
|
+
|
|
61
|
+
\`\`\`json
|
|
62
|
+
{
|
|
63
|
+
"goal": "restated goal",
|
|
64
|
+
"tasks": [
|
|
65
|
+
{
|
|
66
|
+
"id": "task-1",
|
|
67
|
+
"description": "Clear description of what to do",
|
|
68
|
+
"estimatedComplexity": "low|medium|high",
|
|
69
|
+
"owns": ["files/to/create/or/modify"],
|
|
70
|
+
"reads": ["files/to/read/for/context"],
|
|
71
|
+
"dependencies": ["task-ids-that-must-complete-first"],
|
|
72
|
+
"verificationCriteria": ["how to verify this task is done"]
|
|
73
|
+
}
|
|
74
|
+
],
|
|
75
|
+
"dependencies": {
|
|
76
|
+
"task-2": ["task-1"]
|
|
77
|
+
},
|
|
78
|
+
"verificationCriteria": [
|
|
79
|
+
"Overall acceptance criteria for the plan"
|
|
80
|
+
],
|
|
81
|
+
"estimatedDuration": 300000,
|
|
82
|
+
"rationale": "Brief explanation of the plan structure"
|
|
143
83
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
`Write the implementation plan to: ${planPath}`,
|
|
153
|
-
'',
|
|
154
|
-
`**Goal**: ${goal}`,
|
|
155
|
-
context ? `**Context**: ${context}` : '',
|
|
156
|
-
`**Type**: ${classification.type}, **Complexity**: ${classification.complexity}, **Risk**: ${classification.risk}`,
|
|
157
|
-
`**Execution Model**: ${synthesis.executionModel}`,
|
|
158
|
-
'',
|
|
159
|
-
'## Tasks to Document',
|
|
160
|
-
tasksBlock,
|
|
161
|
-
'',
|
|
162
|
-
'## Plan Format',
|
|
163
|
-
'',
|
|
164
|
-
'Write a markdown file with these sections:',
|
|
165
|
-
'1. `# Goal` — restate the goal',
|
|
166
|
-
'2. `## Context` — background information',
|
|
167
|
-
'3. `## Execution Model` — how tasks should be executed',
|
|
168
|
-
'4. `## Tasks` — numbered sections per task with title, description, owns, reads, dependencies, verification',
|
|
169
|
-
'5. `## Risks` — identified risks and mitigation strategies',
|
|
170
|
-
].join('\n');
|
|
84
|
+
\`\`\`
|
|
85
|
+
|
|
86
|
+
## Rules
|
|
87
|
+
1. Use descriptive task IDs like "setup-config", "implement-core", "add-tests"
|
|
88
|
+
2. Keep task descriptions under 100 characters
|
|
89
|
+
3. owns[] and reads[] should be relative file paths
|
|
90
|
+
4. No circular dependencies
|
|
91
|
+
5. Include at least one verification criterion per task`;
|
|
171
92
|
}
|
|
172
93
|
// ---------------------------------------------------------------------------
|
|
173
|
-
//
|
|
94
|
+
// Parsing Helpers
|
|
174
95
|
// ---------------------------------------------------------------------------
|
|
175
|
-
function
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
96
|
+
function parsePlanFromResponse(response) {
|
|
97
|
+
try {
|
|
98
|
+
// Extract JSON from markdown code block or use raw response
|
|
99
|
+
const jsonMatch = response.match(/```json\s*([\s\S]*?)\s*```/);
|
|
100
|
+
const jsonStr = jsonMatch ? jsonMatch[1].trim() : response.trim();
|
|
101
|
+
const parsed = JSON.parse(jsonStr);
|
|
102
|
+
// Transform to Plan structure
|
|
103
|
+
const tasks = (parsed.tasks || []).map((t) => ({
|
|
104
|
+
id: String(t.id || ''),
|
|
105
|
+
description: String(t.description || ''),
|
|
106
|
+
status: 'pending',
|
|
107
|
+
dependencies: Array.isArray(t.dependencies) ? t.dependencies.map(String) : [],
|
|
108
|
+
estimatedComplexity: t.estimatedComplexity || 'medium',
|
|
109
|
+
}));
|
|
110
|
+
const dependencies = parsed.dependencies || {};
|
|
111
|
+
// Ensure all tasks have entry in dependencies
|
|
112
|
+
for (const task of tasks) {
|
|
113
|
+
if (!dependencies[task.id]) {
|
|
114
|
+
dependencies[task.id] = task.dependencies;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
goal: String(parsed.goal || ''),
|
|
119
|
+
tasks,
|
|
120
|
+
dependencies,
|
|
121
|
+
waves: [], // Will be computed via topological sort
|
|
122
|
+
verificationCriteria: Array.isArray(parsed.verificationCriteria)
|
|
123
|
+
? parsed.verificationCriteria.map(String)
|
|
124
|
+
: [],
|
|
125
|
+
estimatedDuration: Number(parsed.estimatedDuration) || 0,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
throw new Error(`Failed to parse planner output: ${error.message}`);
|
|
130
|
+
}
|
|
193
131
|
}
|
|
194
132
|
// ---------------------------------------------------------------------------
|
|
195
|
-
//
|
|
133
|
+
// Topological Sort for Wave Grouping
|
|
196
134
|
// ---------------------------------------------------------------------------
|
|
197
|
-
function
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
135
|
+
function groupTasksIntoWaves(plan) {
|
|
136
|
+
const waves = [];
|
|
137
|
+
const completed = new Set();
|
|
138
|
+
const remaining = new Set(plan.tasks.map(t => t.id));
|
|
139
|
+
const taskMap = new Map(plan.tasks.map(t => [t.id, t]));
|
|
140
|
+
let waveIndex = 0;
|
|
141
|
+
// Kahn's algorithm for topological sort with wave grouping
|
|
142
|
+
while (remaining.size > 0) {
|
|
143
|
+
// Find all tasks whose dependencies are satisfied
|
|
144
|
+
const waveTaskIds = [];
|
|
145
|
+
for (const taskId of remaining) {
|
|
146
|
+
const deps = plan.dependencies[taskId] || [];
|
|
147
|
+
const allDepsCompleted = deps.every(dep => completed.has(dep));
|
|
148
|
+
if (allDepsCompleted) {
|
|
149
|
+
waveTaskIds.push(taskId);
|
|
150
|
+
}
|
|
213
151
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
risk: 'medium',
|
|
219
|
-
explorationAreas: ['src/'],
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
function parseSynthesis(output) {
|
|
223
|
-
const jsonMatch = output.match(/```json\s*([\s\S]*?)\s*```/);
|
|
224
|
-
if (jsonMatch) {
|
|
225
|
-
try {
|
|
226
|
-
const parsed = JSON.parse(jsonMatch[1].trim());
|
|
227
|
-
return {
|
|
228
|
-
executionModel: parsed.executionModel || 'ordered-non-parallel',
|
|
229
|
-
tasks: Array.isArray(parsed.tasks) ? parsed.tasks : [],
|
|
230
|
-
summary: parsed.summary || 'Plan synthesized',
|
|
231
|
-
};
|
|
152
|
+
if (waveTaskIds.length === 0) {
|
|
153
|
+
// Circular dependency detected
|
|
154
|
+
const remainingIds = Array.from(remaining).join(', ');
|
|
155
|
+
throw new Error(`Circular dependency detected among tasks: ${remainingIds}`);
|
|
232
156
|
}
|
|
233
|
-
|
|
234
|
-
|
|
157
|
+
// Create wave with full task objects
|
|
158
|
+
const waveTasks = waveTaskIds
|
|
159
|
+
.map(id => taskMap.get(id))
|
|
160
|
+
.filter((t) => t !== undefined);
|
|
161
|
+
waves.push({
|
|
162
|
+
id: `wave-${waveIndex}`,
|
|
163
|
+
tasks: waveTasks,
|
|
164
|
+
});
|
|
165
|
+
// Mark tasks as completed and remove from remaining
|
|
166
|
+
for (const taskId of waveTaskIds) {
|
|
167
|
+
completed.add(taskId);
|
|
168
|
+
remaining.delete(taskId);
|
|
235
169
|
}
|
|
170
|
+
waveIndex++;
|
|
236
171
|
}
|
|
237
|
-
return
|
|
238
|
-
executionModel: 'ordered-non-parallel',
|
|
239
|
-
tasks: [
|
|
240
|
-
{
|
|
241
|
-
id: '1',
|
|
242
|
-
title: 'Implementation',
|
|
243
|
-
description: 'Core implementation work',
|
|
244
|
-
owns: [],
|
|
245
|
-
reads: [],
|
|
246
|
-
dependencies: [],
|
|
247
|
-
verification: 'Run tests',
|
|
248
|
-
},
|
|
249
|
-
],
|
|
250
|
-
summary: output.slice(0, 200),
|
|
251
|
-
};
|
|
252
|
-
}
|
|
253
|
-
function checkPlanPassed(output) {
|
|
254
|
-
return !output.toLowerCase().includes('blocked');
|
|
255
|
-
}
|
|
256
|
-
// ---------------------------------------------------------------------------
|
|
257
|
-
// Fallback plan writer (used when write subagent fails)
|
|
258
|
-
// ---------------------------------------------------------------------------
|
|
259
|
-
function buildFallbackPlan(goal, context, synthesis) {
|
|
260
|
-
const tasksBlock = synthesis.tasks
|
|
261
|
-
.map((t) => [
|
|
262
|
-
`### Task ${t.id}: ${t.title}`,
|
|
263
|
-
'',
|
|
264
|
-
t.description,
|
|
265
|
-
'',
|
|
266
|
-
`- **Owns**: ${t.owns.join(', ') || 'none'}`,
|
|
267
|
-
`- **Reads**: ${t.reads.join(', ') || 'none'}`,
|
|
268
|
-
`- **Depends on**: ${t.dependencies.join(', ') || 'none'}`,
|
|
269
|
-
`- **Verification**: ${t.verification || 'Run tests'}`,
|
|
270
|
-
].join('\n'))
|
|
271
|
-
.join('\n\n');
|
|
272
|
-
return [
|
|
273
|
-
`# ${goal}`,
|
|
274
|
-
'',
|
|
275
|
-
context ? `## Context\n\n${context}\n` : '',
|
|
276
|
-
`## Execution Model\n\n${synthesis.executionModel}`,
|
|
277
|
-
'',
|
|
278
|
-
'## Tasks',
|
|
279
|
-
'',
|
|
280
|
-
tasksBlock,
|
|
281
|
-
].join('\n');
|
|
172
|
+
return waves;
|
|
282
173
|
}
|
|
283
174
|
// ---------------------------------------------------------------------------
|
|
284
|
-
// Skill
|
|
175
|
+
// Skill Class
|
|
285
176
|
// ---------------------------------------------------------------------------
|
|
286
177
|
export class ToPlanSkill extends Skill {
|
|
287
178
|
constructor() {
|
|
288
179
|
super({
|
|
289
180
|
name: 'to-plan',
|
|
290
|
-
description: '
|
|
181
|
+
description: 'Thin orchestrator that delegates planning to subagent with fresh context',
|
|
291
182
|
requires: [],
|
|
292
183
|
inputSchema: ToPlanInputSchema,
|
|
293
184
|
outputSchema: ToPlanOutputSchema,
|
|
294
185
|
});
|
|
295
186
|
}
|
|
296
187
|
async execute(input, context) {
|
|
297
|
-
const { config, logger } = context;
|
|
298
|
-
const
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
const
|
|
345
|
-
logger.info(`[to-plan]
|
|
346
|
-
const exploreConfigs = areas.map(() => ({
|
|
347
|
-
role: 'explorer',
|
|
348
|
-
model,
|
|
349
|
-
tokenBudget: 16000,
|
|
350
|
-
}));
|
|
351
|
-
const exploreContracts = areas.map((area) => ({
|
|
352
|
-
permissions: {
|
|
353
|
-
readFiles: true,
|
|
354
|
-
searchCode: true,
|
|
355
|
-
runCommands: false,
|
|
356
|
-
writeFiles: false,
|
|
357
|
-
gitOperations: false,
|
|
358
|
-
},
|
|
359
|
-
prompt: buildExplorePrompt(area, input.goal, input.context),
|
|
360
|
-
owns: [],
|
|
361
|
-
reads: input.filesHint ?? [],
|
|
362
|
-
}));
|
|
363
|
-
const exploreResults = areas.length === 1
|
|
364
|
-
? [await dispatcher.dispatch(exploreConfigs[0], exploreContracts[0])]
|
|
365
|
-
: await dispatcher.dispatchParallel(exploreConfigs, exploreContracts);
|
|
366
|
-
for (const result of exploreResults) {
|
|
367
|
-
totalTokens += result.tokensUsed;
|
|
368
|
-
}
|
|
369
|
-
const successfulExplorations = exploreResults.filter((r) => r.status === 'success');
|
|
370
|
-
logger.info(`[to-plan] ${successfulExplorations.length}/${areas.length} explorations succeeded`);
|
|
371
|
-
// ---- Step 3: Synthesize findings ----
|
|
372
|
-
logger.info('[to-plan] Step 3: Synthesizing findings');
|
|
373
|
-
const synthesizeResult = await dispatcher.dispatch({ role: 'planner', model }, {
|
|
374
|
-
permissions: {
|
|
375
|
-
readFiles: true,
|
|
376
|
-
searchCode: false,
|
|
377
|
-
runCommands: false,
|
|
378
|
-
writeFiles: false,
|
|
379
|
-
gitOperations: false,
|
|
380
|
-
},
|
|
381
|
-
prompt: buildSynthesizePrompt(input.goal, input.context, classification, successfulExplorations.map((r) => r.output)),
|
|
382
|
-
owns: [],
|
|
383
|
-
reads: [],
|
|
384
|
-
});
|
|
385
|
-
totalTokens += synthesizeResult.tokensUsed;
|
|
386
|
-
const synthesis = parseSynthesis(synthesizeResult.output);
|
|
387
|
-
logger.info(`[to-plan] Synthesized: ${synthesis.tasks.length} tasks, model=${synthesis.executionModel}`);
|
|
388
|
-
// ---- Step 4: Write plan to disk ----
|
|
389
|
-
const date = new Date().toISOString().split('T')[0];
|
|
390
|
-
const sanitized = input.goal
|
|
391
|
-
.toLowerCase()
|
|
392
|
-
.replace(/[^a-z0-9]+/g, '-')
|
|
393
|
-
.slice(0, 50);
|
|
394
|
-
const planPath = input.outputPath ||
|
|
395
|
-
join(config.planPath, `${date}-${sanitized}-implementation-plan.md`);
|
|
396
|
-
await mkdir(config.planPath, { recursive: true });
|
|
397
|
-
logger.info(`[to-plan] Step 4: Writing plan to ${planPath}`);
|
|
398
|
-
const writeResult = await dispatcher.dispatch({ role: 'implementer', model }, {
|
|
399
|
-
permissions: {
|
|
400
|
-
readFiles: false,
|
|
401
|
-
searchCode: false,
|
|
402
|
-
runCommands: false,
|
|
403
|
-
writeFiles: true,
|
|
404
|
-
gitOperations: false,
|
|
405
|
-
},
|
|
406
|
-
prompt: buildWritePlanPrompt(input.goal, input.context, classification, synthesis, planPath),
|
|
407
|
-
owns: [planPath],
|
|
408
|
-
reads: [],
|
|
409
|
-
});
|
|
410
|
-
totalTokens += writeResult.tokensUsed;
|
|
411
|
-
// If the write subagent failed, write a fallback plan
|
|
412
|
-
if (writeResult.status !== 'success') {
|
|
413
|
-
const fallback = buildFallbackPlan(input.goal, input.context, synthesis);
|
|
414
|
-
await writeFile(planPath, fallback, 'utf-8');
|
|
415
|
-
logger.warn('[to-plan] Write subagent failed; wrote fallback plan');
|
|
416
|
-
}
|
|
417
|
-
// ---- Step 5: Dispatch plan-checker subagent ----
|
|
418
|
-
logger.info('[to-plan] Step 5: Validating plan');
|
|
419
|
-
const checkerResult = await dispatcher.dispatch({ role: 'reviewer', model }, {
|
|
420
|
-
permissions: {
|
|
421
|
-
readFiles: true,
|
|
422
|
-
searchCode: false,
|
|
423
|
-
runCommands: false,
|
|
424
|
-
writeFiles: false,
|
|
425
|
-
gitOperations: false,
|
|
426
|
-
},
|
|
427
|
-
prompt: buildPlanCheckPrompt(planPath),
|
|
428
|
-
owns: [],
|
|
429
|
-
reads: [planPath],
|
|
430
|
-
});
|
|
431
|
-
totalTokens += checkerResult.tokensUsed;
|
|
432
|
-
const planCheckerPassed = checkPlanPassed(checkerResult.output);
|
|
433
|
-
logger.info(`[to-plan] Plan checker: ${planCheckerPassed ? 'PASS' : 'BLOCKED'}`);
|
|
434
|
-
return {
|
|
435
|
-
planPath,
|
|
436
|
-
executionModel: synthesis.executionModel,
|
|
437
|
-
classification: {
|
|
438
|
-
type: classification.type,
|
|
439
|
-
complexity: classification.complexity,
|
|
440
|
-
risk: classification.risk,
|
|
441
|
-
},
|
|
442
|
-
planCheckerPassed,
|
|
443
|
-
tasks: synthesis.tasks,
|
|
444
|
-
summary: [
|
|
445
|
-
`Plan created with ${synthesis.tasks.length} tasks.`,
|
|
446
|
-
`Execution model: ${synthesis.executionModel}.`,
|
|
447
|
-
`Checker: ${planCheckerPassed ? 'PASS' : 'BLOCKED'}.`,
|
|
448
|
-
`Used ${totalTokens} tokens.`,
|
|
449
|
-
].join(' '),
|
|
450
|
-
tokensUsed: totalTokens,
|
|
451
|
-
};
|
|
452
|
-
}
|
|
453
|
-
finally {
|
|
454
|
-
guard.deactivateEmbargo();
|
|
188
|
+
const { config, logger, dispatcher } = context;
|
|
189
|
+
const model = input.model || config.defaultModel || 'claude-sonnet-4.5';
|
|
190
|
+
let tokensUsed = 0;
|
|
191
|
+
// Determine paths
|
|
192
|
+
const statePath = input.outputPath
|
|
193
|
+
? input.outputPath.replace(/\.md$/, '-STATE.md')
|
|
194
|
+
: '.pi/state/STATE.md';
|
|
195
|
+
const planPath = input.outputPath || '.pi/state/PLAN.md';
|
|
196
|
+
// Initialize managers
|
|
197
|
+
const stateManager = new StateMdManager(statePath);
|
|
198
|
+
const planManager = new PlanMdManager(planPath);
|
|
199
|
+
// Step 1: Initialize STATE.md with goal
|
|
200
|
+
logger.info('[to-plan] Initializing STATE.md');
|
|
201
|
+
await stateManager.initialize(input.goal);
|
|
202
|
+
// Step 2: Dispatch planner subagent with FRESH context
|
|
203
|
+
logger.info('[to-plan] Dispatching planner subagent with fresh context');
|
|
204
|
+
const plannerConfig = {
|
|
205
|
+
role: 'planner',
|
|
206
|
+
model,
|
|
207
|
+
timeout: 120000,
|
|
208
|
+
};
|
|
209
|
+
const plannerContract = {
|
|
210
|
+
permissions: {
|
|
211
|
+
readFiles: true,
|
|
212
|
+
searchCode: true,
|
|
213
|
+
runCommands: false,
|
|
214
|
+
writeFiles: false,
|
|
215
|
+
gitOperations: false,
|
|
216
|
+
},
|
|
217
|
+
prompt: buildPlannerPrompt(input),
|
|
218
|
+
owns: [],
|
|
219
|
+
reads: [],
|
|
220
|
+
};
|
|
221
|
+
const plannerResult = await dispatcher.dispatch(plannerConfig, plannerContract);
|
|
222
|
+
tokensUsed += plannerResult.tokensUsed || 0;
|
|
223
|
+
logger.info(`[to-plan] Planner subagent completed, used ${plannerResult.tokensUsed} tokens`);
|
|
224
|
+
// Step 3: Parse plan from subagent output
|
|
225
|
+
logger.info('[to-plan] Parsing plan from subagent output');
|
|
226
|
+
const plan = parsePlanFromResponse(plannerResult.output);
|
|
227
|
+
logger.info(`[to-plan] Parsed ${plan.tasks.length} tasks`);
|
|
228
|
+
// Step 4: Group tasks into waves via topological sort
|
|
229
|
+
logger.info('[to-plan] Grouping tasks into waves');
|
|
230
|
+
const waves = groupTasksIntoWaves(plan);
|
|
231
|
+
plan.waves = waves;
|
|
232
|
+
logger.info(`[to-plan] Created ${waves.length} waves for parallel execution`);
|
|
233
|
+
// Log wave details
|
|
234
|
+
for (const wave of waves) {
|
|
235
|
+
const taskIds = wave.tasks.map(t => t.id).join(', ');
|
|
236
|
+
logger.info(`[to-plan] ${wave.id}: ${taskIds}`);
|
|
455
237
|
}
|
|
238
|
+
// Step 5: Write PLAN.md
|
|
239
|
+
logger.info(`[to-plan] Writing plan to ${planPath}`);
|
|
240
|
+
await planManager.writePlan(plan);
|
|
241
|
+
// Step 6: Update STATE.md with plan
|
|
242
|
+
logger.info('[to-plan] Recording plan in STATE.md');
|
|
243
|
+
await stateManager.recordPlan(plan);
|
|
244
|
+
await stateManager.updatePhase('planning');
|
|
245
|
+
await stateManager.updateTokenUsage(tokensUsed);
|
|
246
|
+
// Build summary
|
|
247
|
+
const summary = [
|
|
248
|
+
`Plan created with ${plan.tasks.length} tasks`,
|
|
249
|
+
`Organized into ${waves.length} parallel waves`,
|
|
250
|
+
`Estimated duration: ${plan.estimatedDuration}ms`,
|
|
251
|
+
`Used ${tokensUsed} tokens`,
|
|
252
|
+
].join('. ');
|
|
253
|
+
logger.info(`[to-plan] ${summary}`);
|
|
254
|
+
return {
|
|
255
|
+
planPath,
|
|
256
|
+
waves,
|
|
257
|
+
taskCount: plan.tasks.length,
|
|
258
|
+
summary,
|
|
259
|
+
tokensUsed,
|
|
260
|
+
};
|
|
456
261
|
}
|
|
457
262
|
}
|
|
458
263
|
export const toPlanSkill = new ToPlanSkill();
|