@haoyiyin/workflow 0.2.0 → 0.2.3
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/package.json +15 -10
- package/scripts/postinstall.js +2 -2
- package/src/agents/contracts.ts +559 -0
- package/src/agents/dispatcher-enhanced.ts +350 -0
- package/src/agents/dispatcher.ts +680 -0
- package/src/agents/index.ts +48 -0
- package/src/agents/resilience.ts +255 -0
- package/src/agents/token-budget.ts +83 -0
- package/src/agents/types.ts +73 -0
- package/src/guard/main-agent.ts +245 -0
- package/src/hooks/builtin/index.ts +8 -0
- package/src/hooks/builtin/on-error.ts +23 -0
- package/src/hooks/builtin/post-execute.ts +40 -0
- package/src/hooks/builtin/post-plan.ts +23 -0
- package/src/hooks/builtin/pre-execute.ts +30 -0
- package/src/hooks/builtin/pre-plan.ts +26 -0
- package/src/hooks/index.ts +7 -0
- package/src/hooks/loader.ts +98 -0
- package/src/hooks/manager.ts +99 -0
- package/src/hooks/types-enhanced.ts +38 -0
- package/src/hooks/types.ts +35 -0
- package/src/index.ts +127 -0
- package/src/persistence/index.ts +17 -0
- package/src/persistence/plan-md.ts +141 -0
- package/src/persistence/state-md.ts +167 -0
- package/src/persistence/types.ts +89 -0
- package/src/router/classifier.ts +610 -0
- package/src/router/guard.ts +483 -0
- package/src/router/index.ts +22 -0
- package/src/router/router.ts +108 -0
- package/src/router/types.ts +127 -0
- package/src/skills/agents-md/SKILL.md +45 -0
- package/src/skills/agents-md/index.ts +33 -0
- package/src/skills/execute-plan/SKILL.md +60 -0
- package/src/skills/execute-plan/index.ts +970 -0
- package/src/skills/index.ts +13 -0
- package/src/skills/quick-task/SKILL.md +54 -0
- package/src/skills/quick-task/index.ts +346 -0
- package/src/skills/registry.ts +59 -0
- package/src/skills/review-diff/SKILL.md +53 -0
- package/src/skills/review-diff/index.ts +394 -0
- package/src/skills/skill.ts +59 -0
- package/src/skills/systematic-debugging/SKILL.md +56 -0
- package/src/skills/systematic-debugging/index.ts +404 -0
- package/src/skills/tdd/SKILL.md +52 -0
- package/src/skills/tdd/index.ts +409 -0
- package/src/skills/to-plan/SKILL.md +56 -0
- package/src/skills/to-plan/index-enhanced.ts +551 -0
- package/src/skills/to-plan/index.ts +586 -0
- package/src/skills/types.ts +47 -0
- package/src/state/cleanup.ts +118 -0
- package/src/state/index.ts +8 -0
- package/src/state/manager.ts +96 -0
- package/src/state/persistence.ts +77 -0
- package/src/state/types.ts +30 -0
- package/src/state/validator.ts +78 -0
- package/src/types.ts +102 -0
- package/src/utils/compress.ts +347 -0
- package/src/utils/git.ts +82 -0
- package/src/utils/index.ts +6 -0
- package/src/utils/logger.ts +23 -0
- package/src/utils/paths.ts +55 -0
|
@@ -0,0 +1,551 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enhanced Plan Skill - Orchestrates analysis, debugging, and planning
|
|
3
|
+
*
|
|
4
|
+
* Architecture: Orchestrator pattern
|
|
5
|
+
* - Analyzes input to determine what preprocessing is needed
|
|
6
|
+
* - May spawn debugging subagent for bug investigation
|
|
7
|
+
* - Delegates actual planning to planner subagent
|
|
8
|
+
* - Wave grouping and validation
|
|
9
|
+
*
|
|
10
|
+
* Examples:
|
|
11
|
+
* "/to-plan 实现登录功能" → Direct planning
|
|
12
|
+
* "/to-plan 出现xxbug,查明原因并制定计划" → Debug → Plan
|
|
13
|
+
* "/to-plan 性能问题,先分析再计划" → Explore → Plan
|
|
14
|
+
*/
|
|
15
|
+
import { z } from 'zod'
|
|
16
|
+
import { Skill } from '../skill.js'
|
|
17
|
+
import type { SkillContext } from '../types.js'
|
|
18
|
+
import type { Plan, Task, Wave } from '../../persistence/types.js'
|
|
19
|
+
// HookType constants
|
|
20
|
+
const HookType = {
|
|
21
|
+
PRE_PLAN: 'pre-plan',
|
|
22
|
+
POST_PLAN: 'post-plan',
|
|
23
|
+
ON_ERROR: 'on-error',
|
|
24
|
+
} as const
|
|
25
|
+
|
|
26
|
+
const PlanInputSchema = z.object({
|
|
27
|
+
goal: z.string(),
|
|
28
|
+
constraints: z.array(z.string()).optional(),
|
|
29
|
+
model: z.string().optional(),
|
|
30
|
+
previousIssues: z.array(z.string()).optional(),
|
|
31
|
+
// Analysis hints
|
|
32
|
+
symptom: z.string().optional(), // Bug symptom
|
|
33
|
+
reproduceCommand: z.string().optional(), // How to reproduce
|
|
34
|
+
logs: z.string().optional(), // Error logs
|
|
35
|
+
recentChanges: z.string().optional(), // Recent git changes
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
const PlanOutputSchema = z.object({
|
|
39
|
+
plan: z.custom<Plan>(),
|
|
40
|
+
waves: z.array(z.custom<Wave>()),
|
|
41
|
+
// If debugging was performed
|
|
42
|
+
rootCause: z.string().optional(),
|
|
43
|
+
confidence: z.enum(['high', 'medium', 'low']).optional(),
|
|
44
|
+
// Metadata
|
|
45
|
+
analysisPerformed: z.boolean(),
|
|
46
|
+
tokensUsed: z.number()
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
type PlanInput = z.infer<typeof PlanInputSchema>
|
|
50
|
+
type PlanOutput = z.infer<typeof PlanOutputSchema>
|
|
51
|
+
|
|
52
|
+
export class ToPlanSkill extends Skill<PlanInput, PlanOutput> {
|
|
53
|
+
protected name = 'to-plan'
|
|
54
|
+
protected description = 'Orchestrates analysis, debugging, and planning with fresh-context subagents'
|
|
55
|
+
protected requires = ['systematic-debugging', 'quick-task']
|
|
56
|
+
|
|
57
|
+
async execute(input: PlanInput, context: SkillContext): Promise<PlanOutput> {
|
|
58
|
+
let analysisPerformed = false
|
|
59
|
+
let rootCause: string | undefined
|
|
60
|
+
let confidence: 'high' | 'medium' | 'low' | undefined
|
|
61
|
+
let tokensUsed = 0
|
|
62
|
+
|
|
63
|
+
// 1. Initialize persistence
|
|
64
|
+
if (context.persistence) {
|
|
65
|
+
await context.persistence.state.initialize(input.goal)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 2. Trigger pre-plan hooks
|
|
69
|
+
await this.triggerHook(context, HookType.PRE_PLAN, { goal: input.goal })
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
// 3. Analyze input and determine workflow
|
|
73
|
+
const analysis = this.analyzeInput(input)
|
|
74
|
+
console.log(`[ToPlanSkill] Analysis: ${analysis.type}`)
|
|
75
|
+
|
|
76
|
+
// 4. Perform preprocessing if needed
|
|
77
|
+
let enrichedGoal = input.goal
|
|
78
|
+
|
|
79
|
+
if (analysis.needsDebugging) {
|
|
80
|
+
console.log('[ToPlanSkill] Bug detected, running systematic debugging...')
|
|
81
|
+
|
|
82
|
+
// Use systematic-debugging skill
|
|
83
|
+
const debugResult = await this.runDebugging(input, context)
|
|
84
|
+
|
|
85
|
+
analysisPerformed = true
|
|
86
|
+
rootCause = debugResult.rootCause
|
|
87
|
+
confidence = debugResult.confidence
|
|
88
|
+
tokensUsed += debugResult.tokensUsed
|
|
89
|
+
|
|
90
|
+
// Enrich goal with root cause
|
|
91
|
+
enrichedGoal = `${input.goal}\n\nRoot Cause (confidence: ${confidence}): ${rootCause}`
|
|
92
|
+
|
|
93
|
+
console.log(`[ToPlanSkill] Debug complete: ${rootCause.slice(0, 100)}...`)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (analysis.needsExploration) {
|
|
97
|
+
console.log('[ToPlanSkill] Exploration needed, gathering context...')
|
|
98
|
+
|
|
99
|
+
// Run exploration
|
|
100
|
+
const exploreResult = await this.runExploration(input, context)
|
|
101
|
+
tokensUsed += exploreResult.tokensUsed
|
|
102
|
+
|
|
103
|
+
// Enrich goal with findings
|
|
104
|
+
enrichedGoal = `${enrichedGoal}\n\nContext: ${exploreResult.findings}`
|
|
105
|
+
|
|
106
|
+
analysisPerformed = true
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// 5. Generate plan with planner subagent
|
|
110
|
+
console.log('[ToPlanSkill] Generating plan...')
|
|
111
|
+
const planResult = await this.spawnPlannerSubagent(
|
|
112
|
+
{ ...input, goal: enrichedGoal },
|
|
113
|
+
context
|
|
114
|
+
)
|
|
115
|
+
tokensUsed += planResult.tokensUsed
|
|
116
|
+
|
|
117
|
+
// 6. Parse and validate plan
|
|
118
|
+
let plan = this.parsePlanResponse(planResult.output)
|
|
119
|
+
let validation = this.validatePlan(plan)
|
|
120
|
+
|
|
121
|
+
// 7. Retry once if validation fails
|
|
122
|
+
if (!validation.valid && !input.previousIssues) {
|
|
123
|
+
console.log('[ToPlanSkill] Validation failed, retrying with feedback...')
|
|
124
|
+
const retryResult = await this.spawnPlannerSubagent(
|
|
125
|
+
{
|
|
126
|
+
...input,
|
|
127
|
+
goal: enrichedGoal,
|
|
128
|
+
previousIssues: validation.issues
|
|
129
|
+
},
|
|
130
|
+
context
|
|
131
|
+
)
|
|
132
|
+
tokensUsed += retryResult.tokensUsed
|
|
133
|
+
plan = this.parsePlanResponse(retryResult.output)
|
|
134
|
+
validation = this.validatePlan(plan)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// 8. Group tasks into waves
|
|
138
|
+
const waves = this.groupIntoWaves(plan)
|
|
139
|
+
plan.waves = waves
|
|
140
|
+
|
|
141
|
+
// 9. Write to persistence
|
|
142
|
+
if (context.persistence) {
|
|
143
|
+
await context.persistence.plan.writePlan(plan)
|
|
144
|
+
await context.persistence.state.recordPlan(plan)
|
|
145
|
+
await context.persistence.state.updatePhase('planning')
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// 10. Trigger post-plan hooks
|
|
149
|
+
await this.triggerHook(context, HookType.POST_PLAN, {
|
|
150
|
+
plan,
|
|
151
|
+
waves,
|
|
152
|
+
analysisPerformed,
|
|
153
|
+
rootCause
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
plan,
|
|
158
|
+
waves,
|
|
159
|
+
rootCause,
|
|
160
|
+
confidence,
|
|
161
|
+
analysisPerformed,
|
|
162
|
+
tokensUsed
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
} catch (error) {
|
|
166
|
+
await this.triggerHook(context, HookType.ON_ERROR, {
|
|
167
|
+
phase: 'plan',
|
|
168
|
+
error: (error as Error).message
|
|
169
|
+
})
|
|
170
|
+
throw error
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Analyze input to determine what preprocessing is needed
|
|
176
|
+
*/
|
|
177
|
+
private analyzeInput(input: PlanInput): {
|
|
178
|
+
type: 'direct' | 'bug' | 'exploration' | 'combined'
|
|
179
|
+
needsDebugging: boolean
|
|
180
|
+
needsExploration: boolean
|
|
181
|
+
} {
|
|
182
|
+
const goal = input.goal.toLowerCase()
|
|
183
|
+
|
|
184
|
+
// Bug indicators
|
|
185
|
+
const bugPatterns = [
|
|
186
|
+
/bug|错误|故障|崩溃|crash|exception|error/i,
|
|
187
|
+
/查明原因|调查|定位|诊断|debug|fix/i,
|
|
188
|
+
/出现.*问题|发生.*错误|不工作|失败|fail/i
|
|
189
|
+
]
|
|
190
|
+
|
|
191
|
+
// Exploration indicators
|
|
192
|
+
const explorePatterns = [
|
|
193
|
+
/分析.*代码|了解.*架构|探索|调研/i,
|
|
194
|
+
/refactor|重构.*前/i,
|
|
195
|
+
/不熟悉|不清楚|unknown/i
|
|
196
|
+
]
|
|
197
|
+
|
|
198
|
+
const hasBugKeywords = bugPatterns.some(p => p.test(goal))
|
|
199
|
+
const hasExploreKeywords = explorePatterns.some(p => p.test(goal))
|
|
200
|
+
|
|
201
|
+
// Explicit symptom provided
|
|
202
|
+
const hasSymptom = !!input.symptom || !!input.logs || !!input.reproduceCommand
|
|
203
|
+
|
|
204
|
+
const needsDebugging = hasBugKeywords || hasSymptom
|
|
205
|
+
const needsExploration = hasExploreKeywords
|
|
206
|
+
|
|
207
|
+
if (needsDebugging && needsExploration) {
|
|
208
|
+
return { type: 'combined', needsDebugging: true, needsExploration: true }
|
|
209
|
+
} else if (needsDebugging) {
|
|
210
|
+
return { type: 'bug', needsDebugging: true, needsExploration: false }
|
|
211
|
+
} else if (needsExploration) {
|
|
212
|
+
return { type: 'exploration', needsDebugging: false, needsExploration: true }
|
|
213
|
+
} else {
|
|
214
|
+
return { type: 'direct', needsDebugging: false, needsExploration: false }
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Run systematic debugging using debugging skill
|
|
220
|
+
*/
|
|
221
|
+
private async runDebugging(
|
|
222
|
+
input: PlanInput,
|
|
223
|
+
context: SkillContext
|
|
224
|
+
): Promise<{
|
|
225
|
+
rootCause: string
|
|
226
|
+
confidence: 'high' | 'medium' | 'low'
|
|
227
|
+
tokensUsed: number
|
|
228
|
+
}> {
|
|
229
|
+
// Try to use systematic-debugging skill if available
|
|
230
|
+
const debugSkill = context.skills.get('systematic-debugging')
|
|
231
|
+
|
|
232
|
+
if (debugSkill) {
|
|
233
|
+
console.log('[ToPlanSkill] Using systematic-debugging skill')
|
|
234
|
+
const result = await debugSkill.execute({
|
|
235
|
+
symptom: input.symptom || input.goal,
|
|
236
|
+
reproduceCommand: input.reproduceCommand,
|
|
237
|
+
logs: input.logs,
|
|
238
|
+
recentChanges: input.recentChanges,
|
|
239
|
+
model: input.model
|
|
240
|
+
}, context)
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
rootCause: result.rootCause,
|
|
244
|
+
confidence: result.confidence,
|
|
245
|
+
tokensUsed: result.tokensUsed
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Fallback: direct dispatcher
|
|
250
|
+
console.log('[ToPlanSkill] Fallback: direct debugging subagent')
|
|
251
|
+
const result = await context.dispatcher.dispatch<{
|
|
252
|
+
rootCause: string
|
|
253
|
+
confidence: 'high' | 'medium' | 'low'
|
|
254
|
+
tokensUsed: number
|
|
255
|
+
}>({
|
|
256
|
+
role: 'debugger',
|
|
257
|
+
model: input.model,
|
|
258
|
+
timeout: 180000
|
|
259
|
+
}, {
|
|
260
|
+
permissions: {
|
|
261
|
+
readFiles: true,
|
|
262
|
+
searchCode: true,
|
|
263
|
+
runCommands: true,
|
|
264
|
+
writeFiles: false,
|
|
265
|
+
gitOperations: false
|
|
266
|
+
},
|
|
267
|
+
prompt: this.buildDebuggingPrompt(input),
|
|
268
|
+
owns: [],
|
|
269
|
+
reads: []
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
return result
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Run exploration to gather context
|
|
277
|
+
*/
|
|
278
|
+
private async runExploration(
|
|
279
|
+
input: PlanInput,
|
|
280
|
+
context: SkillContext
|
|
281
|
+
): Promise<{
|
|
282
|
+
findings: string
|
|
283
|
+
tokensUsed: number
|
|
284
|
+
}> {
|
|
285
|
+
const result = await context.dispatcher.dispatch<{
|
|
286
|
+
findings: string
|
|
287
|
+
tokensUsed: number
|
|
288
|
+
}>({
|
|
289
|
+
role: 'explorer',
|
|
290
|
+
model: input.model,
|
|
291
|
+
timeout: 120000
|
|
292
|
+
}, {
|
|
293
|
+
permissions: {
|
|
294
|
+
readFiles: true,
|
|
295
|
+
searchCode: true,
|
|
296
|
+
runCommands: false,
|
|
297
|
+
writeFiles: false,
|
|
298
|
+
gitOperations: false
|
|
299
|
+
},
|
|
300
|
+
prompt: `# Exploration Task
|
|
301
|
+
|
|
302
|
+
Goal: ${input.goal}
|
|
303
|
+
|
|
304
|
+
Explore the codebase to understand:
|
|
305
|
+
1. Overall architecture and structure
|
|
306
|
+
2. Relevant files and patterns
|
|
307
|
+
3. Dependencies and relationships
|
|
308
|
+
4. Potential challenges or risks
|
|
309
|
+
|
|
310
|
+
Return a concise summary (under 800 tokens) of key findings that will help with planning.`,
|
|
311
|
+
owns: [],
|
|
312
|
+
reads: []
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
return result
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Spawn planner subagent with fresh context
|
|
320
|
+
*/
|
|
321
|
+
private async spawnPlannerSubagent(
|
|
322
|
+
input: PlanInput,
|
|
323
|
+
context: SkillContext
|
|
324
|
+
): Promise<{ output: string; tokensUsed: number }> {
|
|
325
|
+
const result = await context.dispatcher.dispatch<{ plan: string }>(
|
|
326
|
+
{
|
|
327
|
+
role: 'planner',
|
|
328
|
+
model: input.model,
|
|
329
|
+
timeout: 120000
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
permissions: {
|
|
333
|
+
readFiles: true,
|
|
334
|
+
searchCode: true,
|
|
335
|
+
runCommands: false,
|
|
336
|
+
writeFiles: false,
|
|
337
|
+
gitOperations: false
|
|
338
|
+
},
|
|
339
|
+
prompt: this.buildPlannerPrompt(input),
|
|
340
|
+
owns: [],
|
|
341
|
+
reads: []
|
|
342
|
+
}
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
return { output: result.plan, tokensUsed: 0 }
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
private buildDebuggingPrompt(input: PlanInput): string {
|
|
349
|
+
return `# Systematic Debugging
|
|
350
|
+
|
|
351
|
+
## Symptom
|
|
352
|
+
${input.symptom || input.goal}
|
|
353
|
+
|
|
354
|
+
## Reproduction
|
|
355
|
+
${input.reproduceCommand ? `Command: ${input.reproduceCommand}` : 'Reproduce the issue'}
|
|
356
|
+
|
|
357
|
+
${input.logs ? `## Logs\n${input.logs.slice(0, 2000)}` : ''}
|
|
358
|
+
|
|
359
|
+
${input.recentChanges ? `## Recent Changes\n${input.recentChanges}` : ''}
|
|
360
|
+
|
|
361
|
+
## Instructions
|
|
362
|
+
1. Reproduce the issue
|
|
363
|
+
2. Gather evidence systematically
|
|
364
|
+
3. Form and test hypotheses
|
|
365
|
+
4. Identify root cause with confidence level
|
|
366
|
+
5. Propose fix
|
|
367
|
+
|
|
368
|
+
## Output Format
|
|
369
|
+
{
|
|
370
|
+
"rootCause": "detailed description",
|
|
371
|
+
"confidence": "high|medium|low",
|
|
372
|
+
"location": { "file": "path", "line": 123, "function": "name" },
|
|
373
|
+
"proposedFix": "description",
|
|
374
|
+
"verification": "how to verify the fix"
|
|
375
|
+
}`
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
private buildPlannerPrompt(input: PlanInput): string {
|
|
379
|
+
const issueSection = input.previousIssues
|
|
380
|
+
? `\n\n## Previous Attempt Issues\n${input.previousIssues.map(i => `- ${i}`).join('\n')}\nPlease address these issues in your new plan.`
|
|
381
|
+
: ''
|
|
382
|
+
|
|
383
|
+
return `# Planning Task
|
|
384
|
+
|
|
385
|
+
## Goal
|
|
386
|
+
${input.goal}
|
|
387
|
+
|
|
388
|
+
## Constraints
|
|
389
|
+
${input.constraints?.map(c => `- ${c}`).join('\n') || '- No specific constraints'}
|
|
390
|
+
${issueSection}
|
|
391
|
+
|
|
392
|
+
## Instructions
|
|
393
|
+
1. Break down into small, independent tasks (<50 lines of change each)
|
|
394
|
+
2. Identify dependencies between tasks explicitly
|
|
395
|
+
3. Group into parallel execution waves
|
|
396
|
+
4. Define clear verification criteria
|
|
397
|
+
5. If this is a bug fix, include regression tests
|
|
398
|
+
|
|
399
|
+
## Output Format
|
|
400
|
+
Return ONLY a JSON object:
|
|
401
|
+
{
|
|
402
|
+
"goal": "original goal",
|
|
403
|
+
"tasks": [
|
|
404
|
+
{
|
|
405
|
+
"id": "task-1",
|
|
406
|
+
"description": "What to do",
|
|
407
|
+
"estimatedComplexity": "low|medium|high",
|
|
408
|
+
"relatedFiles": ["optional/path"],
|
|
409
|
+
"verificationCriteria": ["How to verify"]
|
|
410
|
+
}
|
|
411
|
+
],
|
|
412
|
+
"dependencies": {"task-2": ["task-1"]},
|
|
413
|
+
"verificationCriteria": ["Overall acceptance criteria"],
|
|
414
|
+
"estimatedDuration": 300000,
|
|
415
|
+
"rationale": "Why this plan"
|
|
416
|
+
}`
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
private parsePlanResponse(response: string): Plan {
|
|
420
|
+
try {
|
|
421
|
+
const jsonMatch = response.match(/\{[\s\S]*\}/)
|
|
422
|
+
const jsonStr = jsonMatch ? jsonMatch[0] : response
|
|
423
|
+
const parsed = JSON.parse(jsonStr)
|
|
424
|
+
|
|
425
|
+
return {
|
|
426
|
+
goal: parsed.goal || '',
|
|
427
|
+
tasks: parsed.tasks || [],
|
|
428
|
+
dependencies: parsed.dependencies || {},
|
|
429
|
+
waves: parsed.waves || [],
|
|
430
|
+
verificationCriteria: parsed.verificationCriteria || [],
|
|
431
|
+
estimatedDuration: parsed.estimatedDuration || 0,
|
|
432
|
+
rationale: parsed.rationale || ''
|
|
433
|
+
}
|
|
434
|
+
} catch (error) {
|
|
435
|
+
console.error('[ToPlanSkill] Failed to parse plan:', error)
|
|
436
|
+
return {
|
|
437
|
+
goal: 'Failed to parse',
|
|
438
|
+
tasks: [],
|
|
439
|
+
dependencies: {},
|
|
440
|
+
waves: [],
|
|
441
|
+
verificationCriteria: [],
|
|
442
|
+
estimatedDuration: 0,
|
|
443
|
+
rationale: 'Parse error'
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
private validatePlan(plan: Plan): { valid: boolean; issues: string[] } {
|
|
449
|
+
const issues: string[] = []
|
|
450
|
+
|
|
451
|
+
if (!plan.goal || plan.goal.trim() === '') {
|
|
452
|
+
issues.push('Plan must have a goal')
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if (plan.tasks.length === 0) {
|
|
456
|
+
issues.push('Plan must have at least one task')
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Check for circular dependencies
|
|
460
|
+
const visited = new Set<string>()
|
|
461
|
+
const recursionStack = new Set<string>()
|
|
462
|
+
|
|
463
|
+
const hasCycle = (taskId: string): boolean => {
|
|
464
|
+
visited.add(taskId)
|
|
465
|
+
recursionStack.add(taskId)
|
|
466
|
+
|
|
467
|
+
const deps = plan.dependencies[taskId] || []
|
|
468
|
+
for (const dep of deps) {
|
|
469
|
+
if (!visited.has(dep)) {
|
|
470
|
+
if (hasCycle(dep)) return true
|
|
471
|
+
} else if (recursionStack.has(dep)) {
|
|
472
|
+
issues.push(`Circular dependency: ${taskId} -> ${dep}`)
|
|
473
|
+
return true
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
recursionStack.delete(taskId)
|
|
478
|
+
return false
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
for (const task of plan.tasks) {
|
|
482
|
+
if (!visited.has(task.id)) {
|
|
483
|
+
hasCycle(task.id)
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Validate dependencies exist
|
|
488
|
+
for (const [taskId, deps] of Object.entries(plan.dependencies)) {
|
|
489
|
+
if (!plan.tasks.some(t => t.id === taskId)) {
|
|
490
|
+
issues.push(`Dependency references non-existent task: ${taskId}`)
|
|
491
|
+
}
|
|
492
|
+
for (const dep of deps) {
|
|
493
|
+
if (!plan.tasks.some(t => t.id === dep)) {
|
|
494
|
+
issues.push(`Task ${taskId} depends on non-existent task: ${dep}`)
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
return { valid: issues.length === 0, issues }
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
private groupIntoWaves(plan: Plan): Wave[] {
|
|
503
|
+
const waves: Wave[] = []
|
|
504
|
+
const completed = new Set<string>()
|
|
505
|
+
const remaining = new Set(plan.tasks.map(t => t.id))
|
|
506
|
+
let waveIndex = 0
|
|
507
|
+
|
|
508
|
+
while (remaining.size > 0) {
|
|
509
|
+
const waveTasks = plan.tasks.filter(t => {
|
|
510
|
+
if (!remaining.has(t.id)) return false
|
|
511
|
+
const deps = plan.dependencies[t.id] || []
|
|
512
|
+
return deps.every(dep => completed.has(dep))
|
|
513
|
+
})
|
|
514
|
+
|
|
515
|
+
if (waveTasks.length === 0) {
|
|
516
|
+
console.error('[ToPlanSkill] Circular dependency detected')
|
|
517
|
+
break
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
waves.push({
|
|
521
|
+
id: `wave-${waveIndex}`,
|
|
522
|
+
tasks: waveTasks.map(t => t.id)
|
|
523
|
+
})
|
|
524
|
+
|
|
525
|
+
waveTasks.forEach(t => {
|
|
526
|
+
completed.add(t.id)
|
|
527
|
+
remaining.delete(t.id)
|
|
528
|
+
})
|
|
529
|
+
|
|
530
|
+
waveIndex++
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
return waves
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
private async triggerHook(
|
|
537
|
+
context: SkillContext,
|
|
538
|
+
type: typeof HookType[keyof typeof HookType],
|
|
539
|
+
data: Record<string, unknown>
|
|
540
|
+
): Promise<void> {
|
|
541
|
+
if (context.hooks) {
|
|
542
|
+
try {
|
|
543
|
+
// Import HookContext from types for proper typing
|
|
544
|
+
const hookContext = data as unknown as import('../../types.js').HookContext
|
|
545
|
+
await context.hooks.execute(type as import('../../types.js').HookType, hookContext)
|
|
546
|
+
} catch (error) {
|
|
547
|
+
console.warn(`[ToPlanSkill] Hook ${type} failed:`, error)
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|