@haoyiyin/workflow 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/package.json +9 -8
  2. package/src/agents/contracts.ts +559 -0
  3. package/src/agents/dispatcher-enhanced.ts +350 -0
  4. package/src/agents/dispatcher.ts +680 -0
  5. package/src/agents/index.ts +48 -0
  6. package/src/agents/resilience.ts +255 -0
  7. package/src/agents/token-budget.ts +83 -0
  8. package/src/agents/types.ts +73 -0
  9. package/src/guard/main-agent.ts +245 -0
  10. package/src/hooks/builtin/index.ts +8 -0
  11. package/src/hooks/builtin/on-error.ts +23 -0
  12. package/src/hooks/builtin/post-execute.ts +40 -0
  13. package/src/hooks/builtin/post-plan.ts +23 -0
  14. package/src/hooks/builtin/pre-execute.ts +30 -0
  15. package/src/hooks/builtin/pre-plan.ts +26 -0
  16. package/src/hooks/index.ts +7 -0
  17. package/src/hooks/loader.ts +98 -0
  18. package/src/hooks/manager.ts +99 -0
  19. package/src/hooks/types-enhanced.ts +38 -0
  20. package/src/hooks/types.ts +35 -0
  21. package/src/index.ts +127 -0
  22. package/src/persistence/index.ts +17 -0
  23. package/src/persistence/plan-md.ts +141 -0
  24. package/src/persistence/state-md.ts +167 -0
  25. package/src/persistence/types.ts +89 -0
  26. package/src/router/classifier.ts +610 -0
  27. package/src/router/guard.ts +483 -0
  28. package/src/router/index.ts +22 -0
  29. package/src/router/router.ts +108 -0
  30. package/src/router/types.ts +127 -0
  31. package/src/skills/agents-md/SKILL.md +45 -0
  32. package/src/skills/agents-md/index.ts +33 -0
  33. package/src/skills/execute-plan/SKILL.md +60 -0
  34. package/src/skills/execute-plan/index.ts +970 -0
  35. package/src/skills/index.ts +13 -0
  36. package/src/skills/quick-task/SKILL.md +54 -0
  37. package/src/skills/quick-task/index.ts +346 -0
  38. package/src/skills/registry.ts +59 -0
  39. package/src/skills/review-diff/SKILL.md +53 -0
  40. package/src/skills/review-diff/index.ts +394 -0
  41. package/src/skills/skill.ts +59 -0
  42. package/src/skills/systematic-debugging/SKILL.md +56 -0
  43. package/src/skills/systematic-debugging/index.ts +404 -0
  44. package/src/skills/tdd/SKILL.md +52 -0
  45. package/src/skills/tdd/index.ts +409 -0
  46. package/src/skills/to-plan/SKILL.md +56 -0
  47. package/src/skills/to-plan/index-enhanced.ts +551 -0
  48. package/src/skills/to-plan/index.ts +586 -0
  49. package/src/skills/types.ts +47 -0
  50. package/src/state/cleanup.ts +118 -0
  51. package/src/state/index.ts +8 -0
  52. package/src/state/manager.ts +96 -0
  53. package/src/state/persistence.ts +77 -0
  54. package/src/state/types.ts +30 -0
  55. package/src/state/validator.ts +78 -0
  56. package/src/types.ts +102 -0
  57. package/src/utils/compress.ts +347 -0
  58. package/src/utils/git.ts +82 -0
  59. package/src/utils/index.ts +6 -0
  60. package/src/utils/logger.ts +23 -0
  61. package/src/utils/paths.ts +55 -0
@@ -0,0 +1,141 @@
1
+ /**
2
+ * PLAN.md manager for execution plan persistence
3
+ */
4
+ import { promises as fs } from 'fs'
5
+ import { dirname } from 'path'
6
+ import type { Plan, Task, PlanMdManager as IPlanMdManager } from './types.js'
7
+
8
+ export class PlanMdManager implements IPlanMdManager {
9
+ private planPath: string
10
+
11
+ constructor(planPath: string = '.pi/state/PLAN.md') {
12
+ this.planPath = planPath
13
+ }
14
+
15
+ async writePlan(plan: Plan): Promise<void> {
16
+ // Ensure directory exists
17
+ await fs.mkdir(dirname(this.planPath), { recursive: true })
18
+
19
+ const content = this.generatePlanMarkdown(plan)
20
+ await fs.writeFile(this.planPath, content, 'utf-8')
21
+ }
22
+
23
+ async readPlan(): Promise<Plan> {
24
+ const content = await fs.readFile(this.planPath, 'utf-8')
25
+ return this.parsePlan(content)
26
+ }
27
+
28
+ async updateTaskStatus(taskId: string, status: 'pending' | 'in_progress' | 'complete'): Promise<void> {
29
+ const content = await fs.readFile(this.planPath, 'utf-8')
30
+ const statusChar = status === 'complete' ? 'x' : status === 'in_progress' ? '~' : ' '
31
+ const updated = content.replace(
32
+ new RegExp(`- \\[.] ${taskId}:`),
33
+ `- [${statusChar}] ${taskId}:`
34
+ )
35
+ await fs.writeFile(this.planPath, updated, 'utf-8')
36
+ }
37
+
38
+ private generatePlanMarkdown(plan: Plan): string {
39
+ return `# Execution Plan
40
+
41
+ ## Goal
42
+ ${plan.goal}
43
+
44
+ ## Tasks
45
+ ${plan.tasks.map(t => `- [ ] ${t.id}: ${t.description} (${t.estimatedComplexity})`).join('\n')}
46
+
47
+ ## Dependencies
48
+ ${Object.entries(plan.dependencies).length > 0
49
+ ? Object.entries(plan.dependencies).map(([task, deps]) => `- ${task}: ${deps.join(', ')}`).join('\n')
50
+ : '- No dependencies'
51
+ }
52
+
53
+ ## Waves
54
+ ${plan.waves.map(w => `- ${w.id}: ${w.tasks.join(', ')}`).join('\n')}
55
+
56
+ ## Verification Criteria
57
+ ${plan.verificationCriteria.map(c => `- ${c}`).join('\n')}
58
+
59
+ ## Estimated Effort
60
+ - Total Tasks: ${plan.tasks.length}
61
+ - Parallel Waves: ${plan.waves.length}
62
+ - Estimated Duration: ${plan.estimatedDuration}ms
63
+
64
+ ## Rationale
65
+ ${plan.rationale}
66
+ `
67
+ }
68
+
69
+ private parsePlan(content: string): Plan {
70
+ const goalMatch = content.match(/## Goal\n(.+)/)
71
+ const tasksMatch = content.match(/## Tasks\n([\s\S]*?)(?=\n## |$)/)
72
+ const depsMatch = content.match(/## Dependencies\n([\s\S]*?)(?=\n## |$)/)
73
+ const wavesMatch = content.match(/## Waves\n([\s\S]*?)(?=\n## |$)/)
74
+ const criteriaMatch = content.match(/## Verification Criteria\n([\s\S]*?)(?=\n## |$)/)
75
+ const durationMatch = content.match(/- Estimated Duration: (\d+)/)
76
+ const rationaleMatch = content.match(/## Rationale\n([\s\S]*?)(?=\n## |$)/)
77
+
78
+ // Parse tasks
79
+ const tasks: Task[] = []
80
+ if (tasksMatch) {
81
+ const taskLines = tasksMatch[1].split('\n').filter(l => l.trim().startsWith('- ['))
82
+ for (const line of taskLines) {
83
+ const match = line.match(/- \[[x\s~]]\s+(\w+):\s+(.+)\s+\((\w+)\)/)
84
+ if (match) {
85
+ tasks.push({
86
+ id: match[1],
87
+ description: match[2],
88
+ estimatedComplexity: match[3] as 'low' | 'medium' | 'high'
89
+ })
90
+ }
91
+ }
92
+ }
93
+
94
+ // Parse dependencies
95
+ const dependencies: Record<string, string[]> = {}
96
+ if (depsMatch && depsMatch[1].trim() !== '- No dependencies') {
97
+ const depLines = depsMatch[1].split('\n').filter(l => l.trim().startsWith('- '))
98
+ for (const line of depLines) {
99
+ const match = line.match(/- (\w+):\s*(.+)/)
100
+ if (match) {
101
+ dependencies[match[1]] = match[2].split(',').map(s => s.trim()).filter(s => s)
102
+ }
103
+ }
104
+ }
105
+
106
+ // Parse waves
107
+ const waves = []
108
+ if (wavesMatch) {
109
+ const waveLines = wavesMatch[1].split('\n').filter(l => l.trim().startsWith('- '))
110
+ for (const line of waveLines) {
111
+ const match = line.match(/- (\w+):\s*(.+)/)
112
+ if (match) {
113
+ waves.push({
114
+ id: match[1],
115
+ tasks: match[2].split(',').map(s => s.trim()).filter(s => s)
116
+ })
117
+ }
118
+ }
119
+ }
120
+
121
+ // Parse verification criteria
122
+ const verificationCriteria: string[] = []
123
+ if (criteriaMatch) {
124
+ const criteriaLines = criteriaMatch[1].split('\n').filter(l => l.trim().startsWith('- '))
125
+ for (const line of criteriaLines) {
126
+ const criterion = line.replace(/^- /, '').trim()
127
+ if (criterion) verificationCriteria.push(criterion)
128
+ }
129
+ }
130
+
131
+ return {
132
+ goal: goalMatch?.[1] || '',
133
+ tasks,
134
+ dependencies,
135
+ waves,
136
+ verificationCriteria,
137
+ estimatedDuration: durationMatch ? parseInt(durationMatch[1], 10) : 0,
138
+ rationale: rationaleMatch?.[1]?.trim() || ''
139
+ }
140
+ }
141
+ }
@@ -0,0 +1,167 @@
1
+ /**
2
+ * STATE.md manager for workflow state persistence
3
+ */
4
+ import { promises as fs } from 'fs'
5
+ import { dirname } from 'path'
6
+ import type { WorkflowState, Plan, TaskResult, VerificationResult, StateMdManager as IStateMdManager } from './types.js'
7
+
8
+ export class StateMdManager implements IStateMdManager {
9
+ private statePath: string
10
+
11
+ constructor(statePath: string = '.pi/state/STATE.md') {
12
+ this.statePath = statePath
13
+ }
14
+
15
+ async initialize(goal: string): Promise<void> {
16
+ // Ensure directory exists
17
+ await fs.mkdir(dirname(this.statePath), { recursive: true })
18
+
19
+ const content = `# Workflow State
20
+
21
+ ## Goal
22
+ ${goal}
23
+
24
+ ## Status
25
+ - Phase: planning
26
+ - Started: ${new Date().toISOString()}
27
+ - Last Updated: ${new Date().toISOString()}
28
+
29
+ ## Plan
30
+ <!-- Populated by plan skill -->
31
+
32
+ ## Completed Tasks
33
+ <!-- Updated during execution -->
34
+
35
+ ## Verification Results
36
+ <!-- Populated by verify phase -->
37
+
38
+ ## Token Usage
39
+ - Plan Phase: 0
40
+ - Execute Phase: 0
41
+ - Verify Phase: 0
42
+ - Total: 0 / 1000000
43
+ `
44
+ await fs.writeFile(this.statePath, content, 'utf-8')
45
+ }
46
+
47
+ async updatePhase(phase: WorkflowState['phase']): Promise<void> {
48
+ const content = await fs.readFile(this.statePath, 'utf-8')
49
+ const updated = content.replace(
50
+ /- Phase: .*/,
51
+ `- Phase: ${phase}`
52
+ ).replace(
53
+ /- Last Updated: .*/,
54
+ `- Last Updated: ${new Date().toISOString()}`
55
+ )
56
+ await fs.writeFile(this.statePath, updated, 'utf-8')
57
+ }
58
+
59
+ async recordPlan(plan: Plan): Promise<void> {
60
+ const content = await fs.readFile(this.statePath, 'utf-8')
61
+ const planSection = this.formatPlanSection(plan)
62
+ const updated = this.replaceSection(content, 'Plan', planSection)
63
+ await fs.writeFile(this.statePath, updated, 'utf-8')
64
+ }
65
+
66
+ async recordTaskComplete(taskId: string, result: TaskResult): Promise<void> {
67
+ const content = await fs.readFile(this.statePath, 'utf-8')
68
+ const entry = `- [x] ${taskId}: ${result.success ? '✓' : '✗'} (${result.duration}ms) - ${result.notes || ''}\n`
69
+ const updated = this.appendToSection(content, 'Completed Tasks', entry)
70
+ await fs.writeFile(this.statePath, updated, 'utf-8')
71
+ }
72
+
73
+ async recordVerification(verification: VerificationResult): Promise<void> {
74
+ const content = await fs.readFile(this.statePath, 'utf-8')
75
+ const verificationSection = this.formatVerificationSection(verification)
76
+ const updated = this.replaceSection(content, 'Verification Results', verificationSection)
77
+ await fs.writeFile(this.statePath, updated, 'utf-8')
78
+ }
79
+
80
+ async readCurrentState(): Promise<WorkflowState> {
81
+ const content = await fs.readFile(this.statePath, 'utf-8')
82
+ return this.parseState(content)
83
+ }
84
+
85
+ private formatPlanSection(plan: Plan): string {
86
+ return `
87
+ ### Goal
88
+ ${plan.goal}
89
+
90
+ ### Tasks (${plan.tasks.length})
91
+ ${plan.tasks.map(t => `- ${t.id}: ${t.description} (${t.estimatedComplexity})`).join('\n')}
92
+
93
+ ### Waves (${plan.waves.length})
94
+ ${plan.waves.map(w => `- ${w.id}: ${w.tasks.join(', ')}`).join('\n')}
95
+
96
+ ### Verification Criteria
97
+ ${plan.verificationCriteria.map(c => `- ${c}`).join('\n')}
98
+
99
+ ### Estimated Duration
100
+ ${plan.estimatedDuration}ms
101
+
102
+ ### Rationale
103
+ ${plan.rationale}
104
+ `
105
+ }
106
+
107
+ private formatVerificationSection(verification: VerificationResult): string {
108
+ const results: string[] = []
109
+ if (verification.dimensions.goals) {
110
+ results.push(`- Goals: ${verification.dimensions.goals.success ? '✓ PASS' : '✗ FAIL'}`)
111
+ }
112
+ if (verification.dimensions.quality) {
113
+ results.push(`- Quality: ${verification.dimensions.quality.success ? '✓ PASS' : '✗ FAIL'}`)
114
+ }
115
+ if (verification.dimensions.tests) {
116
+ results.push(`- Tests: ${verification.dimensions.tests.success ? '✓ PASS' : '✗ FAIL'}`)
117
+ }
118
+ results.push(`- Overall: ${verification.success ? '✓ PASSED' : '✗ FAILED'}`)
119
+
120
+ return `
121
+ ${results.join('\n')}
122
+ `
123
+ }
124
+
125
+ private replaceSection(content: string, sectionName: string, newContent: string): string {
126
+ const pattern = new RegExp(`(## ${sectionName}\\n)([\\s\\S]*?)(?=\\n## |$)`, 'g')
127
+ return content.replace(pattern, `## ${sectionName}\n${newContent}\n`)
128
+ }
129
+
130
+ private appendToSection(content: string, sectionName: string, entry: string): string {
131
+ const pattern = new RegExp(`(## ${sectionName}\\n)([\\s\\S]*?)(?=\\n## |$)`, 'g')
132
+ const match = pattern.exec(content)
133
+ if (match) {
134
+ const current = match[2] || ''
135
+ return content.replace(pattern, `## ${sectionName}\n${current}${entry}`)
136
+ }
137
+ return content
138
+ }
139
+
140
+ private parseState(content: string): WorkflowState {
141
+ const phaseMatch = content.match(/- Phase: (\w+)/)
142
+ const goalMatch = content.match(/## Goal\n(.+)/)
143
+ const startedMatch = content.match(/- Started: (.+)/)
144
+ const totalMatch = content.match(/### Tasks \((\d+)\)/)
145
+ const completedMatch = content.match(/## Completed Tasks\n([\s\S]*?)(?=\n## |$)/)
146
+
147
+ const completedTasks = completedMatch
148
+ ? completedMatch[1].split('\n').filter(l => l.trim().startsWith('- [x]')).length
149
+ : 0
150
+
151
+ const totalTasks = totalMatch ? parseInt(totalMatch[1], 10) : 0
152
+
153
+ return {
154
+ goal: goalMatch?.[1] || '',
155
+ phase: (phaseMatch?.[1] as WorkflowState['phase']) || 'planning',
156
+ started: startedMatch?.[1] || new Date().toISOString(),
157
+ lastUpdated: new Date().toISOString(),
158
+ totalTasks,
159
+ completedTasks: [],
160
+ tokenUsage: {
161
+ used: 0,
162
+ limit: 1000000,
163
+ percentage: 0
164
+ }
165
+ }
166
+ }
167
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Persistence layer types for STATE.md and PLAN.md
3
+ */
4
+
5
+ export interface Task {
6
+ id: string
7
+ description: string
8
+ estimatedComplexity: 'low' | 'medium' | 'high'
9
+ relatedFiles?: string[]
10
+ verificationCriteria?: string[]
11
+ }
12
+
13
+ export interface Wave {
14
+ id: string
15
+ tasks: string[]
16
+ }
17
+
18
+ export interface Plan {
19
+ goal: string
20
+ tasks: Task[]
21
+ dependencies: Record<string, string[]>
22
+ waves: Wave[]
23
+ verificationCriteria: string[]
24
+ estimatedDuration: number
25
+ rationale: string
26
+ }
27
+
28
+ export interface TaskResult {
29
+ taskId: string
30
+ success: boolean
31
+ filesModified: string[]
32
+ testsAdded: string[]
33
+ duration: number
34
+ notes: string
35
+ error?: string
36
+ phases?: { phase: string; status: string; command?: string; evidence?: string }[]
37
+ worktreePath?: string
38
+ branchName?: string
39
+ }
40
+
41
+ export interface VerificationResult {
42
+ success: boolean
43
+ dimensions: {
44
+ goals?: DimensionResult
45
+ quality?: DimensionResult
46
+ tests?: DimensionResult
47
+ }
48
+ }
49
+
50
+ export interface DimensionResult {
51
+ success: boolean
52
+ details?: unknown
53
+ evidence?: string[]
54
+ gaps?: string[]
55
+ }
56
+
57
+ export interface WorkflowState {
58
+ goal: string
59
+ phase: 'planning' | 'executing' | 'verifying' | 'complete'
60
+ started: string
61
+ lastUpdated: string
62
+ totalTasks: number
63
+ completedTasks: TaskResult[]
64
+ tokenUsage: {
65
+ used: number
66
+ limit: number
67
+ percentage: number
68
+ }
69
+ }
70
+
71
+ export interface StateMdManager {
72
+ initialize(goal: string): Promise<void>
73
+ updatePhase(phase: WorkflowState['phase']): Promise<void>
74
+ recordPlan(plan: Plan): Promise<void>
75
+ recordTaskComplete(taskId: string, result: TaskResult): Promise<void>
76
+ recordVerification(verification: VerificationResult): Promise<void>
77
+ readCurrentState(): Promise<WorkflowState>
78
+ }
79
+
80
+ export interface PlanMdManager {
81
+ writePlan(plan: Plan): Promise<void>
82
+ readPlan(): Promise<Plan>
83
+ updateTaskStatus(taskId: string, status: 'pending' | 'in_progress' | 'complete'): Promise<void>
84
+ }
85
+
86
+ export interface PersistenceManager {
87
+ state: StateMdManager
88
+ plan: PlanMdManager
89
+ }