@haoyiyin/workflow 0.2.10 → 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.
Files changed (164) hide show
  1. package/dist/src/agents/contracts/implementer.d.ts +29 -0
  2. package/dist/src/agents/contracts/implementer.d.ts.map +1 -0
  3. package/dist/src/agents/contracts/implementer.js +94 -0
  4. package/dist/src/agents/contracts/implementer.js.map +1 -0
  5. package/dist/src/agents/contracts/index.d.ts +11 -0
  6. package/dist/src/agents/contracts/index.d.ts.map +1 -0
  7. package/dist/src/agents/contracts/index.js +11 -0
  8. package/dist/src/agents/contracts/index.js.map +1 -0
  9. package/dist/src/agents/contracts/planner.d.ts +25 -0
  10. package/dist/src/agents/contracts/planner.d.ts.map +1 -0
  11. package/dist/src/agents/contracts/planner.js +107 -0
  12. package/dist/src/agents/contracts/planner.js.map +1 -0
  13. package/dist/src/agents/contracts/router.d.ts +24 -0
  14. package/dist/src/agents/contracts/router.d.ts.map +1 -0
  15. package/dist/src/agents/contracts/router.js +137 -0
  16. package/dist/src/agents/contracts/router.js.map +1 -0
  17. package/dist/src/agents/contracts/verifier.d.ts +27 -0
  18. package/dist/src/agents/contracts/verifier.d.ts.map +1 -0
  19. package/dist/src/agents/contracts/verifier.js +115 -0
  20. package/dist/src/agents/contracts/verifier.js.map +1 -0
  21. package/dist/src/agents/dispatcher.d.ts +94 -51
  22. package/dist/src/agents/dispatcher.d.ts.map +1 -1
  23. package/dist/src/agents/dispatcher.js +207 -164
  24. package/dist/src/agents/dispatcher.js.map +1 -1
  25. package/dist/src/persistence/index.d.ts +4 -2
  26. package/dist/src/persistence/index.d.ts.map +1 -1
  27. package/dist/src/persistence/index.js +4 -1
  28. package/dist/src/persistence/index.js.map +1 -1
  29. package/dist/src/persistence/plan-md.d.ts +3 -2
  30. package/dist/src/persistence/plan-md.d.ts.map +1 -1
  31. package/dist/src/persistence/plan-md.js +47 -15
  32. package/dist/src/persistence/plan-md.js.map +1 -1
  33. package/dist/src/persistence/state-md.d.ts +2 -0
  34. package/dist/src/persistence/state-md.d.ts.map +1 -1
  35. package/dist/src/persistence/state-md.js +40 -22
  36. package/dist/src/persistence/state-md.js.map +1 -1
  37. package/dist/src/persistence/types.d.ts +35 -39
  38. package/dist/src/persistence/types.d.ts.map +1 -1
  39. package/dist/src/router/namespace/core/intent-router.d.ts +24 -0
  40. package/dist/src/router/namespace/core/intent-router.d.ts.map +1 -0
  41. package/dist/src/router/namespace/core/intent-router.js +190 -0
  42. package/dist/src/router/namespace/core/intent-router.js.map +1 -0
  43. package/dist/src/router/namespace/core/lifecycle-router.d.ts +28 -0
  44. package/dist/src/router/namespace/core/lifecycle-router.d.ts.map +1 -0
  45. package/dist/src/router/namespace/core/lifecycle-router.js +132 -0
  46. package/dist/src/router/namespace/core/lifecycle-router.js.map +1 -0
  47. package/dist/src/router/namespace/core/state-router.d.ts +32 -0
  48. package/dist/src/router/namespace/core/state-router.d.ts.map +1 -0
  49. package/dist/src/router/namespace/core/state-router.js +157 -0
  50. package/dist/src/router/namespace/core/state-router.js.map +1 -0
  51. package/dist/src/router/namespace/domain/code-router.d.ts +26 -0
  52. package/dist/src/router/namespace/domain/code-router.d.ts.map +1 -0
  53. package/dist/src/router/namespace/domain/code-router.js +171 -0
  54. package/dist/src/router/namespace/domain/code-router.js.map +1 -0
  55. package/dist/src/router/namespace/domain/debug-router.d.ts +25 -0
  56. package/dist/src/router/namespace/domain/debug-router.d.ts.map +1 -0
  57. package/dist/src/router/namespace/domain/debug-router.js +139 -0
  58. package/dist/src/router/namespace/domain/debug-router.js.map +1 -0
  59. package/dist/src/router/namespace/domain/plan-router.d.ts +29 -0
  60. package/dist/src/router/namespace/domain/plan-router.d.ts.map +1 -0
  61. package/dist/src/router/namespace/domain/plan-router.js +160 -0
  62. package/dist/src/router/namespace/domain/plan-router.js.map +1 -0
  63. package/dist/src/router/namespace/domain/review-router.d.ts +24 -0
  64. package/dist/src/router/namespace/domain/review-router.d.ts.map +1 -0
  65. package/dist/src/router/namespace/domain/review-router.js +116 -0
  66. package/dist/src/router/namespace/domain/review-router.js.map +1 -0
  67. package/dist/src/router/namespace/index.d.ts +19 -0
  68. package/dist/src/router/namespace/index.d.ts.map +1 -0
  69. package/dist/src/router/namespace/index.js +22 -0
  70. package/dist/src/router/namespace/index.js.map +1 -0
  71. package/dist/src/router/namespace/registry.d.ts +67 -0
  72. package/dist/src/router/namespace/registry.d.ts.map +1 -0
  73. package/dist/src/router/namespace/registry.js +197 -0
  74. package/dist/src/router/namespace/registry.js.map +1 -0
  75. package/dist/src/router/namespace/types.d.ts +124 -0
  76. package/dist/src/router/namespace/types.d.ts.map +1 -0
  77. package/dist/src/router/namespace/types.js +20 -0
  78. package/dist/src/router/namespace/types.js.map +1 -0
  79. package/dist/src/router/namespace/utility/fallback-router.d.ts +28 -0
  80. package/dist/src/router/namespace/utility/fallback-router.d.ts.map +1 -0
  81. package/dist/src/router/namespace/utility/fallback-router.js +88 -0
  82. package/dist/src/router/namespace/utility/fallback-router.js.map +1 -0
  83. package/dist/src/router/namespace/utility/quick-task-router.d.ts +28 -0
  84. package/dist/src/router/namespace/utility/quick-task-router.d.ts.map +1 -0
  85. package/dist/src/router/namespace/utility/quick-task-router.js +99 -0
  86. package/dist/src/router/namespace/utility/quick-task-router.js.map +1 -0
  87. package/dist/src/router/namespace/utility/research-router.d.ts +24 -0
  88. package/dist/src/router/namespace/utility/research-router.d.ts.map +1 -0
  89. package/dist/src/router/namespace/utility/research-router.js +84 -0
  90. package/dist/src/router/namespace/utility/research-router.js.map +1 -0
  91. package/dist/src/skills/agents-md/index.js +2 -2
  92. package/dist/src/skills/agents-md/index.js.map +1 -1
  93. package/dist/src/skills/execute-plan/index.d.ts +45 -65
  94. package/dist/src/skills/execute-plan/index.d.ts.map +1 -1
  95. package/dist/src/skills/execute-plan/index.js +325 -551
  96. package/dist/src/skills/execute-plan/index.js.map +1 -1
  97. package/dist/src/skills/index.d.ts +1 -0
  98. package/dist/src/skills/index.d.ts.map +1 -1
  99. package/dist/src/skills/index.js +1 -0
  100. package/dist/src/skills/index.js.map +1 -1
  101. package/dist/src/skills/quick-task/index.d.ts +4 -4
  102. package/dist/src/skills/quick-task/index.js +1 -1
  103. package/dist/src/skills/quick-task/index.js.map +1 -1
  104. package/dist/src/skills/review-diff/index.d.ts +6 -6
  105. package/dist/src/skills/review-diff/index.js +1 -1
  106. package/dist/src/skills/review-diff/index.js.map +1 -1
  107. package/dist/src/skills/router/index.d.ts +101 -0
  108. package/dist/src/skills/router/index.d.ts.map +1 -0
  109. package/dist/src/skills/router/index.js +450 -0
  110. package/dist/src/skills/router/index.js.map +1 -0
  111. package/dist/src/skills/router/types.d.ts +79 -0
  112. package/dist/src/skills/router/types.d.ts.map +1 -0
  113. package/dist/src/skills/router/types.js +8 -0
  114. package/dist/src/skills/router/types.js.map +1 -0
  115. package/dist/src/skills/systematic-debugging/index.js +1 -1
  116. package/dist/src/skills/systematic-debugging/index.js.map +1 -1
  117. package/dist/src/skills/tdd/index.d.ts +14 -14
  118. package/dist/src/skills/tdd/index.js +1 -1
  119. package/dist/src/skills/tdd/index.js.map +1 -1
  120. package/dist/src/skills/to-plan/index-enhanced.d.ts +4 -4
  121. package/dist/src/skills/to-plan/index-enhanced.d.ts.map +1 -1
  122. package/dist/src/skills/to-plan/index-enhanced.js +3 -5
  123. package/dist/src/skills/to-plan/index-enhanced.js.map +1 -1
  124. package/dist/src/skills/to-plan/index.d.ts +24 -91
  125. package/dist/src/skills/to-plan/index.d.ts.map +1 -1
  126. package/dist/src/skills/to-plan/index.js +214 -409
  127. package/dist/src/skills/to-plan/index.js.map +1 -1
  128. package/package.json +3 -5
  129. package/src/agents/contracts/implementer.ts +122 -0
  130. package/src/agents/contracts/index.ts +27 -0
  131. package/src/agents/contracts/planner.ts +129 -0
  132. package/src/agents/contracts/router.ts +168 -0
  133. package/src/agents/contracts/verifier.ts +137 -0
  134. package/src/agents/dispatcher.ts +387 -362
  135. package/src/persistence/index.ts +10 -4
  136. package/src/persistence/plan-md.ts +52 -18
  137. package/src/persistence/state-md.ts +45 -23
  138. package/src/persistence/types.ts +37 -40
  139. package/src/router/namespace/README.md +127 -0
  140. package/src/router/namespace/core/intent-router.ts +221 -0
  141. package/src/router/namespace/core/lifecycle-router.ts +156 -0
  142. package/src/router/namespace/core/state-router.ts +192 -0
  143. package/src/router/namespace/domain/code-router.ts +202 -0
  144. package/src/router/namespace/domain/debug-router.ts +167 -0
  145. package/src/router/namespace/domain/plan-router.ts +196 -0
  146. package/src/router/namespace/domain/review-router.ts +142 -0
  147. package/src/router/namespace/index.ts +84 -0
  148. package/src/router/namespace/registry.ts +242 -0
  149. package/src/router/namespace/types.ts +182 -0
  150. package/src/router/namespace/utility/fallback-router.ts +107 -0
  151. package/src/router/namespace/utility/quick-task-router.ts +121 -0
  152. package/src/router/namespace/utility/research-router.ts +105 -0
  153. package/src/skills/agents-md/index.ts +2 -2
  154. package/src/skills/execute-plan/index.ts +419 -673
  155. package/src/skills/index.ts +1 -0
  156. package/src/skills/quick-task/index.ts +1 -1
  157. package/src/skills/review-diff/index.ts +1 -1
  158. package/src/skills/router/SKILL.md +181 -0
  159. package/src/skills/router/index.ts +577 -0
  160. package/src/skills/router/types.ts +90 -0
  161. package/src/skills/systematic-debugging/index.ts +1 -1
  162. package/src/skills/tdd/index.ts +1 -1
  163. package/src/skills/to-plan/index-enhanced.ts +3 -5
  164. package/src/skills/to-plan/index.ts +231 -502
@@ -0,0 +1,577 @@
1
+ /**
2
+ * Router Skill - 5-Layer Routing Logic
3
+ *
4
+ * Layer 1: Parse & Classify - Extract intent and entities from user request
5
+ * Layer 2: State-Aware Scoring - Factor in project phase and execution state
6
+ * Layer 3: Skill Matching - Score each skill against request and state
7
+ * Layer 4: Confidence Calibration - Adjust scores based on history and context
8
+ * Layer 5: Selection & Output - Return structured routing decision
9
+ *
10
+ * This skill reads from STATE.md and PLAN.md to make context-aware decisions.
11
+ */
12
+
13
+ import { z } from 'zod'
14
+ import { Skill } from '../skill.js'
15
+ import type { SkillContext } from '../types.js'
16
+ import { StateMdManager } from '../../persistence/state-md.js'
17
+ import { PlanMdManager } from '../../persistence/plan-md.js'
18
+ import { createDispatcher } from '../../agents/dispatcher.js'
19
+ import type { SubagentConfig, SubagentContract, SubagentResult } from '../../agents/types.js'
20
+ import type { WorkflowState } from '../../persistence/types.js'
21
+ import type { RouterInput, RouterOutput, ProjectState, ProjectPhase, RoutingAlternative } from './types.js'
22
+
23
+ // ---------------------------------------------------------------------------
24
+ // Schemas
25
+ // ---------------------------------------------------------------------------
26
+
27
+ const ConversationHistoryItemSchema = z.object({
28
+ role: z.enum(['user', 'assistant']),
29
+ content: z.string(),
30
+ })
31
+
32
+ const RouterInputSchema = z.object({
33
+ userRequest: z.string().min(1, 'User request is required'),
34
+ conversationHistory: z.array(ConversationHistoryItemSchema).optional(),
35
+ })
36
+
37
+ const RouterOutputSchema = z.object({
38
+ selectedSkill: z.string(),
39
+ confidence: z.number().min(0).max(1),
40
+ reasoning: z.string(),
41
+ projectState: z.object({
42
+ phase: z.enum(['discovery', 'planning', 'execution', 'verification', 'completed', 'idle']),
43
+ hasPlan: z.boolean(),
44
+ isExecuting: z.boolean(),
45
+ completedTasks: z.number(),
46
+ totalTasks: z.number(),
47
+ }),
48
+ })
49
+
50
+ type RouterInputType = z.infer<typeof RouterInputSchema>
51
+ type RouterOutputType = z.infer<typeof RouterOutputSchema>
52
+
53
+ // ---------------------------------------------------------------------------
54
+ // 5-Layer Router Types
55
+ // ---------------------------------------------------------------------------
56
+
57
+ interface Layer1Classification {
58
+ intent: string
59
+ entities: string[]
60
+ complexity: 'low' | 'medium' | 'high'
61
+ urgency: 'low' | 'medium' | 'high'
62
+ }
63
+
64
+ interface Layer2StateFactors {
65
+ phaseWeight: number
66
+ executionWeight: number
67
+ completionWeight: number
68
+ }
69
+
70
+ interface Layer3SkillScore {
71
+ skillName: string
72
+ baseScore: number
73
+ stateAdjustedScore: number
74
+ matches: string[]
75
+ }
76
+
77
+ interface Layer4Calibration {
78
+ confidenceAdjustment: number
79
+ historyBonus: number
80
+ contextPenalty: number
81
+ }
82
+
83
+ interface Layer5Decision {
84
+ selectedSkill: string
85
+ finalConfidence: number
86
+ reasoning: string
87
+ alternatives: RoutingAlternative[]
88
+ }
89
+
90
+ // ---------------------------------------------------------------------------
91
+ // Available Skills by Phase
92
+ // ---------------------------------------------------------------------------
93
+
94
+ const SKILLS_BY_PHASE: Record<ProjectPhase, string[]> = {
95
+ discovery: ['to-plan', 'research', 'agents-md'],
96
+ planning: ['to-plan', 'research', 'review-diff'],
97
+ execution: ['execute-plan', 'quick-task', 'tdd', 'systematic-debugging'],
98
+ verification: ['review-diff', 'systematic-debugging', 'agents-md'],
99
+ completed: ['review-diff', 'agents-md', 'research'],
100
+ idle: ['to-plan', 'research', 'quick-task', 'agents-md'],
101
+ }
102
+
103
+ // ---------------------------------------------------------------------------
104
+ // Prompt Builders for Subagent
105
+ // ---------------------------------------------------------------------------
106
+
107
+ function buildLayer1Prompt(userRequest: string, history: Array<{ role: 'user' | 'assistant'; content: string }> | undefined): string {
108
+ const historyBlock = history && history.length > 0
109
+ ? `## Conversation History\n${history.map(h => `${h.role}: ${h.content}`).join('\n')}\n`
110
+ : ''
111
+
112
+ return [
113
+ '## Layer 1: Parse & Classify',
114
+ '',
115
+ 'Analyze the user request and extract structured information.',
116
+ '',
117
+ '**User Request**:',
118
+ userRequest,
119
+ '',
120
+ historyBlock,
121
+ '## Output Format (JSON)',
122
+ '',
123
+ '```json',
124
+ '{',
125
+ ' "intent": "primary intent category (plan, execute, debug, review, research, quick-task)",',
126
+ ' "entities": ["file paths mentioned", "concepts", "technologies"],',
127
+ ' "complexity": "low|medium|high",',
128
+ ' "urgency": "low|medium|high"',
129
+ '}',
130
+ '```',
131
+ ].join('\n')
132
+ }
133
+
134
+ function buildLayer3Prompt(
135
+ classification: Layer1Classification,
136
+ projectState: ProjectState,
137
+ availableSkills: string[],
138
+ ): string {
139
+ return [
140
+ '## Layer 3: Skill Matching',
141
+ '',
142
+ 'Score each available skill based on how well it matches the request and project state.',
143
+ '',
144
+ '**Request Classification**:',
145
+ `- Intent: ${classification.intent}`,
146
+ `- Entities: ${classification.entities.join(', ') || 'none'}`,
147
+ `- Complexity: ${classification.complexity}`,
148
+ `- Urgency: ${classification.urgency}`,
149
+ '',
150
+ '**Project State**:',
151
+ `- Phase: ${projectState.phase}`,
152
+ `- Has Plan: ${projectState.hasPlan}`,
153
+ `- Is Executing: ${projectState.isExecuting}`,
154
+ `- Progress: ${projectState.completedTasks}/${projectState.totalTasks} tasks`,
155
+ '',
156
+ '**Available Skills**:',
157
+ availableSkills.map(s => `- ${s}`).join('\n'),
158
+ '',
159
+ '## Output Format (JSON)',
160
+ '',
161
+ '```json',
162
+ '{',
163
+ ' "scores": [',
164
+ ' { "skillName": "skill-name", "baseScore": 0.85, "matches": ["reason1", "reason2"] }',
165
+ ' ]',
166
+ '}',
167
+ '```',
168
+ ].join('\n')
169
+ }
170
+
171
+ function buildLayer5Prompt(
172
+ calibratedScores: Layer3SkillScore[],
173
+ projectState: ProjectState,
174
+ ): string {
175
+ return [
176
+ '## Layer 5: Selection & Output',
177
+ '',
178
+ 'Make the final routing decision based on calibrated scores and project state.',
179
+ '',
180
+ '**Calibrated Scores**:',
181
+ calibratedScores.map(s => `- ${s.skillName}: ${s.stateAdjustedScore.toFixed(2)}`).join('\n'),
182
+ '',
183
+ '**Project State**:',
184
+ `- Phase: ${projectState.phase}`,
185
+ `- Has Plan: ${projectState.hasPlan}`,
186
+ `- Is Executing: ${projectState.isExecuting}`,
187
+ '',
188
+ '## Output Format (JSON)',
189
+ '',
190
+ '```json',
191
+ '{',
192
+ ' "selectedSkill": "skill-name",',
193
+ ' "finalConfidence": 0.92,',
194
+ ' "reasoning": "Detailed explanation of why this skill was selected",',
195
+ ' "alternatives": [',
196
+ ' { "skill": "other-skill", "confidence": 0.65, "reasoning": "Why it was considered" }',
197
+ ' ]',
198
+ '}',
199
+ '```',
200
+ ].join('\n')
201
+ }
202
+
203
+ // ---------------------------------------------------------------------------
204
+ // Parsing Helpers
205
+ // ---------------------------------------------------------------------------
206
+
207
+ function parseLayer1Output(output: string): Layer1Classification {
208
+ const jsonMatch = output.match(/```json\s*([\s\S]*?)\s*```/)
209
+ if (jsonMatch) {
210
+ try {
211
+ const parsed = JSON.parse(jsonMatch[1].trim())
212
+ return {
213
+ intent: parsed.intent || 'general',
214
+ entities: Array.isArray(parsed.entities) ? parsed.entities : [],
215
+ complexity: ['low', 'medium', 'high'].includes(parsed.complexity) ? parsed.complexity : 'medium',
216
+ urgency: ['low', 'medium', 'high'].includes(parsed.urgency) ? parsed.urgency : 'medium',
217
+ }
218
+ } catch {
219
+ // Fall through to defaults
220
+ }
221
+ }
222
+ return {
223
+ intent: 'general',
224
+ entities: [],
225
+ complexity: 'medium',
226
+ urgency: 'medium',
227
+ }
228
+ }
229
+
230
+ function parseLayer3Output(output: string): Layer3SkillScore[] {
231
+ const jsonMatch = output.match(/```json\s*([\s\S]*?)\s*```/)
232
+ if (jsonMatch) {
233
+ try {
234
+ const parsed = JSON.parse(jsonMatch[1].trim())
235
+ if (Array.isArray(parsed.scores)) {
236
+ return parsed.scores.map((s: { skillName?: string; baseScore?: number; matches?: string[] }) => ({
237
+ skillName: s.skillName || 'unknown',
238
+ baseScore: s.baseScore || 0,
239
+ stateAdjustedScore: s.baseScore || 0,
240
+ matches: Array.isArray(s.matches) ? s.matches : [],
241
+ }))
242
+ }
243
+ } catch {
244
+ // Fall through
245
+ }
246
+ }
247
+ return []
248
+ }
249
+
250
+ function parseLayer5Output(output: string): Layer5Decision {
251
+ const jsonMatch = output.match(/```json\s*([\s\S]*?)\s*```/)
252
+ if (jsonMatch) {
253
+ try {
254
+ const parsed = JSON.parse(jsonMatch[1].trim())
255
+ return {
256
+ selectedSkill: parsed.selectedSkill || 'quick-task',
257
+ finalConfidence: parsed.finalConfidence || 0.5,
258
+ reasoning: parsed.reasoning || 'No reasoning provided',
259
+ alternatives: Array.isArray(parsed.alternatives) ? parsed.alternatives : [],
260
+ }
261
+ } catch {
262
+ // Fall through to defaults
263
+ }
264
+ }
265
+ return {
266
+ selectedSkill: 'quick-task',
267
+ finalConfidence: 0.5,
268
+ reasoning: 'Failed to parse decision, defaulting to quick-task',
269
+ alternatives: [],
270
+ }
271
+ }
272
+
273
+ // ---------------------------------------------------------------------------
274
+ // State Mapping Helpers
275
+ // ---------------------------------------------------------------------------
276
+
277
+ function mapWorkflowPhaseToProjectPhase(phase: WorkflowState['phase']): ProjectPhase {
278
+ switch (phase) {
279
+ case 'planning':
280
+ return 'planning'
281
+ case 'executing':
282
+ return 'execution'
283
+ case 'verifying':
284
+ return 'verification'
285
+ case 'completed':
286
+ return 'completed'
287
+ default:
288
+ return 'idle'
289
+ }
290
+ }
291
+
292
+ function buildProjectState(
293
+ workflowState: WorkflowState,
294
+ hasPlan: boolean,
295
+ ): ProjectState {
296
+ return {
297
+ phase: mapWorkflowPhaseToProjectPhase(workflowState.phase),
298
+ hasPlan,
299
+ isExecuting: workflowState.phase === 'executing',
300
+ completedTasks: workflowState.completedTasks?.length || 0,
301
+ totalTasks: workflowState.totalTasks || 0,
302
+ }
303
+ }
304
+
305
+ // ---------------------------------------------------------------------------
306
+ // Layer 2: State-Aware Scoring (Pure Function)
307
+ // ---------------------------------------------------------------------------
308
+
309
+ function calculateStateFactors(
310
+ classification: Layer1Classification,
311
+ projectState: ProjectState,
312
+ ): Layer2StateFactors {
313
+ let phaseWeight = 1.0
314
+ let executionWeight = 1.0
315
+ let completionWeight = 1.0
316
+
317
+ // Adjust based on phase
318
+ switch (projectState.phase) {
319
+ case 'planning':
320
+ phaseWeight = classification.intent === 'plan' ? 1.5 : 0.8
321
+ break
322
+ case 'execution':
323
+ phaseWeight = classification.intent === 'execute' ? 1.5 : 0.9
324
+ break
325
+ case 'verification':
326
+ phaseWeight = classification.intent === 'review' ? 1.5 : 0.8
327
+ break
328
+ case 'completed':
329
+ phaseWeight = classification.intent === 'research' ? 1.2 : 1.0
330
+ break
331
+ }
332
+
333
+ // Adjust based on execution state
334
+ if (projectState.isExecuting && classification.intent === 'execute') {
335
+ executionWeight = 1.3
336
+ }
337
+
338
+ // Adjust based on completion progress
339
+ const progressRatio = projectState.totalTasks > 0
340
+ ? projectState.completedTasks / projectState.totalTasks
341
+ : 0
342
+ completionWeight = 0.8 + (0.4 * progressRatio)
343
+
344
+ return {
345
+ phaseWeight,
346
+ executionWeight,
347
+ completionWeight,
348
+ }
349
+ }
350
+
351
+ // ---------------------------------------------------------------------------
352
+ // Layer 4: Confidence Calibration (Pure Function)
353
+ // ---------------------------------------------------------------------------
354
+
355
+ function calibrateConfidence(
356
+ scores: Layer3SkillScore[],
357
+ stateFactors: Layer2StateFactors,
358
+ classification: Layer1Classification,
359
+ ): Layer3SkillScore[] {
360
+ const totalWeight = stateFactors.phaseWeight * stateFactors.executionWeight * stateFactors.completionWeight
361
+
362
+ return scores.map((score) => {
363
+ // Apply state-based adjustment
364
+ let adjustedScore = score.baseScore * totalWeight
365
+
366
+ // Boost for high urgency
367
+ if (classification.urgency === 'high' && score.skillName === 'quick-task') {
368
+ adjustedScore *= 1.2
369
+ }
370
+
371
+ // Cap at 1.0
372
+ adjustedScore = Math.min(adjustedScore, 1.0)
373
+
374
+ return {
375
+ ...score,
376
+ stateAdjustedScore: adjustedScore,
377
+ }
378
+ })
379
+ }
380
+
381
+ // ---------------------------------------------------------------------------
382
+ // Router Skill Class
383
+ // ---------------------------------------------------------------------------
384
+
385
+ export class RouterSkill extends Skill<RouterInputType, RouterOutputType> {
386
+ private stateManager: StateMdManager
387
+ private planManager: PlanMdManager
388
+
389
+ constructor(statePath = '.pi/state/STATE.md', planPath = '.pi/state/PLAN.md') {
390
+ super({
391
+ name: 'router',
392
+ description: '5-layer routing skill that selects the appropriate skill based on user request and project state',
393
+ requires: [],
394
+ inputSchema: RouterInputSchema,
395
+ outputSchema: RouterOutputSchema,
396
+ })
397
+ this.stateManager = new StateMdManager(statePath)
398
+ this.planManager = new PlanMdManager(planPath)
399
+ }
400
+
401
+ /**
402
+ * Get available skills for the current project phase
403
+ */
404
+ getAvailableSkillsForPhase(phase: ProjectPhase): string[] {
405
+ return [...(SKILLS_BY_PHASE[phase] || SKILLS_BY_PHASE.idle)]
406
+ }
407
+
408
+ async execute(input: RouterInputType, context: SkillContext): Promise<RouterOutputType> {
409
+ const { logger, config } = context
410
+ const dispatcher = createDispatcher({ logger })
411
+ const model = config.defaultModel || 'glm-5'
412
+
413
+ logger.info('[router] Starting 5-layer routing process')
414
+
415
+ // -----------------------------------------------------------------------
416
+ // Read Project State (STATE.md and PLAN.md)
417
+ // -----------------------------------------------------------------------
418
+
419
+ let workflowState: WorkflowState
420
+ let hasPlan = false
421
+
422
+ try {
423
+ workflowState = await this.stateManager.readCurrentState()
424
+ logger.info(`[router] Read workflow state: phase=${workflowState.phase}`)
425
+ } catch (error) {
426
+ logger.warn('[router] No existing workflow state, using defaults')
427
+ workflowState = {
428
+ goal: '',
429
+ phase: 'planning',
430
+ startedAt: new Date().toISOString(),
431
+ lastUpdated: new Date().toISOString(),
432
+ completedTasks: [],
433
+ failedTasks: [],
434
+ totalTasks: 0,
435
+ tokenUsage: { used: 0, limit: 1000000, percentage: 0 },
436
+ }
437
+ }
438
+
439
+ try {
440
+ await this.planManager.readPlan()
441
+ hasPlan = true
442
+ logger.info('[router] Plan file exists')
443
+ } catch {
444
+ logger.info('[router] No plan file found')
445
+ }
446
+
447
+ const projectState = buildProjectState(workflowState, hasPlan)
448
+ const availableSkills = this.getAvailableSkillsForPhase(projectState.phase)
449
+
450
+ logger.info(`[router] Available skills for ${projectState.phase}: ${availableSkills.join(', ')}`)
451
+
452
+ // -----------------------------------------------------------------------
453
+ // Layer 1: Parse & Classify (Dispatch subagent)
454
+ // -----------------------------------------------------------------------
455
+
456
+ logger.info('[router] Layer 1: Parsing and classifying request')
457
+
458
+ const layer1Config: SubagentConfig = {
459
+ role: 'explorer',
460
+ model,
461
+ tokenBudget: 8000,
462
+ }
463
+
464
+ const layer1Contract: SubagentContract = {
465
+ permissions: {
466
+ readFiles: false,
467
+ searchCode: false,
468
+ runCommands: false,
469
+ writeFiles: false,
470
+ gitOperations: false,
471
+ },
472
+ prompt: buildLayer1Prompt(input.userRequest, (input.conversationHistory ?? []) as Array<{ role: 'user' | 'assistant'; content: string }>),
473
+ owns: [],
474
+ reads: [],
475
+ }
476
+
477
+ const layer1Result = await dispatcher.dispatch(layer1Config, layer1Contract)
478
+ const classification = parseLayer1Output(layer1Result.output)
479
+
480
+ logger.info(`[router] Layer 1 complete: intent=${classification.intent}, complexity=${classification.complexity}`)
481
+
482
+ // -----------------------------------------------------------------------
483
+ // Layer 2: State-Aware Scoring (Pure function)
484
+ // -----------------------------------------------------------------------
485
+
486
+ logger.info('[router] Layer 2: Calculating state factors')
487
+
488
+ const stateFactors = calculateStateFactors(classification, projectState)
489
+
490
+ logger.info(`[router] State factors: phase=${stateFactors.phaseWeight.toFixed(2)}, execution=${stateFactors.executionWeight.toFixed(2)}`)
491
+
492
+ // -----------------------------------------------------------------------
493
+ // Layer 3: Skill Matching (Dispatch subagent)
494
+ // -----------------------------------------------------------------------
495
+
496
+ logger.info('[router] Layer 3: Matching skills')
497
+
498
+ const layer3Config: SubagentConfig = {
499
+ role: 'planner',
500
+ model,
501
+ tokenBudget: 10000,
502
+ }
503
+
504
+ const layer3Contract: SubagentContract = {
505
+ permissions: {
506
+ readFiles: false,
507
+ searchCode: false,
508
+ runCommands: false,
509
+ writeFiles: false,
510
+ gitOperations: false,
511
+ },
512
+ prompt: buildLayer3Prompt(classification, projectState, availableSkills),
513
+ owns: [],
514
+ reads: [],
515
+ }
516
+
517
+ const layer3Result = await dispatcher.dispatch(layer3Config, layer3Contract)
518
+ const skillScores = parseLayer3Output(layer3Result.output)
519
+
520
+ logger.info(`[router] Layer 3 complete: ${skillScores.length} skills scored`)
521
+
522
+ // -----------------------------------------------------------------------
523
+ // Layer 4: Confidence Calibration (Pure function)
524
+ // -----------------------------------------------------------------------
525
+
526
+ logger.info('[router] Layer 4: Calibrating confidence')
527
+
528
+ const calibratedScores = calibrateConfidence(skillScores, stateFactors, classification)
529
+
530
+ logger.info(`[router] Layer 4 complete: top score = ${Math.max(...calibratedScores.map(s => s.stateAdjustedScore)).toFixed(2)}`)
531
+
532
+ // -----------------------------------------------------------------------
533
+ // Layer 5: Selection & Output (Dispatch subagent)
534
+ // -----------------------------------------------------------------------
535
+
536
+ logger.info('[router] Layer 5: Making final selection')
537
+
538
+ const layer5Config: SubagentConfig = {
539
+ role: 'planner',
540
+ model,
541
+ tokenBudget: 8000,
542
+ }
543
+
544
+ const layer5Contract: SubagentContract = {
545
+ permissions: {
546
+ readFiles: false,
547
+ searchCode: false,
548
+ runCommands: false,
549
+ writeFiles: false,
550
+ gitOperations: false,
551
+ },
552
+ prompt: buildLayer5Prompt(calibratedScores, projectState),
553
+ owns: [],
554
+ reads: [],
555
+ }
556
+
557
+ const layer5Result = await dispatcher.dispatch(layer5Config, layer5Contract)
558
+ const decision = parseLayer5Output(layer5Result.output)
559
+
560
+ logger.info(`[router] Layer 5 complete: selected=${decision.selectedSkill}, confidence=${decision.finalConfidence.toFixed(2)}`)
561
+
562
+ // -----------------------------------------------------------------------
563
+ // Return Structured Output
564
+ // -----------------------------------------------------------------------
565
+
566
+ return {
567
+ selectedSkill: decision.selectedSkill,
568
+ confidence: decision.finalConfidence,
569
+ reasoning: decision.reasoning,
570
+ projectState,
571
+ }
572
+ }
573
+ }
574
+
575
+ // Singleton instance
576
+ export const routerSkill = new RouterSkill()
577
+ export default routerSkill
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Router Skill Types
3
+ *
4
+ * Type definitions for the router skill that determines which skill
5
+ * should handle a given user request based on conversation context.
6
+ */
7
+
8
+ /**
9
+ * Input to the router skill
10
+ */
11
+ export interface RouterInput {
12
+ /** The user's current request/message */
13
+ userRequest: string;
14
+ /** Optional conversation history for context */
15
+ conversationHistory?: Array<{
16
+ role: 'user' | 'assistant';
17
+ content: string;
18
+ }>;
19
+ }
20
+
21
+ /**
22
+ * Project lifecycle phases
23
+ */
24
+ export type ProjectPhase =
25
+ | 'discovery'
26
+ | 'planning'
27
+ | 'execution'
28
+ | 'verification'
29
+ | 'completed'
30
+ | 'idle';
31
+
32
+ /**
33
+ * Current state of the project being worked on
34
+ */
35
+ export interface ProjectState {
36
+ /** Current phase of the project lifecycle */
37
+ phase: ProjectPhase;
38
+ /** Whether a plan has been created */
39
+ hasPlan: boolean;
40
+ /** Whether execution is currently in progress */
41
+ isExecuting: boolean;
42
+ /** Number of completed tasks */
43
+ completedTasks: number;
44
+ /** Total number of tasks in the plan */
45
+ totalTasks: number;
46
+ }
47
+
48
+ /**
49
+ * Output from the router skill
50
+ */
51
+ export interface RouterOutput {
52
+ /** The skill selected to handle the request */
53
+ selectedSkill: string;
54
+ /** Confidence score (0-1) in the selection */
55
+ confidence: number;
56
+ /** Explanation of why this skill was selected */
57
+ reasoning: string;
58
+ /** Current project state at time of routing */
59
+ projectState: ProjectState;
60
+ }
61
+
62
+ /**
63
+ * Alternative skill that could have been selected
64
+ */
65
+ export interface RoutingAlternative {
66
+ /** Name of the alternative skill */
67
+ skill: string;
68
+ /** Confidence score for this alternative */
69
+ confidence: number;
70
+ /** Why this skill was considered */
71
+ reasoning: string;
72
+ }
73
+
74
+ /**
75
+ * Complete routing decision with full context
76
+ */
77
+ export interface RoutingDecision {
78
+ /** The selected target skill */
79
+ targetSkill: string;
80
+ /** Confidence score (0-1) in the decision */
81
+ confidence: number;
82
+ /** Detailed reasoning for the selection */
83
+ reasoning: string;
84
+ /** Whether project state was used in the decision */
85
+ stateUsed: boolean;
86
+ /** Current lifecycle phase when decision was made */
87
+ lifecyclePhase: ProjectPhase;
88
+ /** Alternative skills that were considered */
89
+ alternatives: RoutingAlternative[];
90
+ }
@@ -235,7 +235,7 @@ export class SystematicDebuggingSkill extends Skill<DebugInput, DebugOutput> {
235
235
  context: SkillContext,
236
236
  ): Promise<DebugOutput> {
237
237
  const { config, logger } = context
238
- const dispatcher = createDispatcher(logger)
238
+ const dispatcher = createDispatcher({ logger })
239
239
  const guard = createMainAgentGuard({}, logger)
240
240
  let totalTokens = 0
241
241
 
@@ -190,7 +190,7 @@ export class TDDSkill extends Skill<TDDInput, TDDOutput> {
190
190
  context: SkillContext,
191
191
  ): Promise<TDDOutput> {
192
192
  const { config, logger } = context
193
- const dispatcher = createDispatcher(logger)
193
+ const dispatcher = createDispatcher({ logger })
194
194
  const guard = createMainAgentGuard({}, logger)
195
195
 
196
196
  const phases: PhaseResult[] = []