@haoyiyin/workflow 0.2.11 → 0.3.1

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 (175) 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/pi-extension.d.ts +4 -3
  40. package/dist/src/pi-extension.d.ts.map +1 -1
  41. package/dist/src/pi-extension.js +36 -67
  42. package/dist/src/pi-extension.js.map +1 -1
  43. package/dist/src/router/namespace/core/intent-router.d.ts +24 -0
  44. package/dist/src/router/namespace/core/intent-router.d.ts.map +1 -0
  45. package/dist/src/router/namespace/core/intent-router.js +190 -0
  46. package/dist/src/router/namespace/core/intent-router.js.map +1 -0
  47. package/dist/src/router/namespace/core/lifecycle-router.d.ts +28 -0
  48. package/dist/src/router/namespace/core/lifecycle-router.d.ts.map +1 -0
  49. package/dist/src/router/namespace/core/lifecycle-router.js +132 -0
  50. package/dist/src/router/namespace/core/lifecycle-router.js.map +1 -0
  51. package/dist/src/router/namespace/core/state-router.d.ts +32 -0
  52. package/dist/src/router/namespace/core/state-router.d.ts.map +1 -0
  53. package/dist/src/router/namespace/core/state-router.js +157 -0
  54. package/dist/src/router/namespace/core/state-router.js.map +1 -0
  55. package/dist/src/router/namespace/domain/code-router.d.ts +26 -0
  56. package/dist/src/router/namespace/domain/code-router.d.ts.map +1 -0
  57. package/dist/src/router/namespace/domain/code-router.js +171 -0
  58. package/dist/src/router/namespace/domain/code-router.js.map +1 -0
  59. package/dist/src/router/namespace/domain/debug-router.d.ts +25 -0
  60. package/dist/src/router/namespace/domain/debug-router.d.ts.map +1 -0
  61. package/dist/src/router/namespace/domain/debug-router.js +139 -0
  62. package/dist/src/router/namespace/domain/debug-router.js.map +1 -0
  63. package/dist/src/router/namespace/domain/plan-router.d.ts +29 -0
  64. package/dist/src/router/namespace/domain/plan-router.d.ts.map +1 -0
  65. package/dist/src/router/namespace/domain/plan-router.js +160 -0
  66. package/dist/src/router/namespace/domain/plan-router.js.map +1 -0
  67. package/dist/src/router/namespace/domain/review-router.d.ts +24 -0
  68. package/dist/src/router/namespace/domain/review-router.d.ts.map +1 -0
  69. package/dist/src/router/namespace/domain/review-router.js +116 -0
  70. package/dist/src/router/namespace/domain/review-router.js.map +1 -0
  71. package/dist/src/router/namespace/index.d.ts +19 -0
  72. package/dist/src/router/namespace/index.d.ts.map +1 -0
  73. package/dist/src/router/namespace/index.js +22 -0
  74. package/dist/src/router/namespace/index.js.map +1 -0
  75. package/dist/src/router/namespace/registry.d.ts +67 -0
  76. package/dist/src/router/namespace/registry.d.ts.map +1 -0
  77. package/dist/src/router/namespace/registry.js +197 -0
  78. package/dist/src/router/namespace/registry.js.map +1 -0
  79. package/dist/src/router/namespace/types.d.ts +124 -0
  80. package/dist/src/router/namespace/types.d.ts.map +1 -0
  81. package/dist/src/router/namespace/types.js +20 -0
  82. package/dist/src/router/namespace/types.js.map +1 -0
  83. package/dist/src/router/namespace/utility/fallback-router.d.ts +28 -0
  84. package/dist/src/router/namespace/utility/fallback-router.d.ts.map +1 -0
  85. package/dist/src/router/namespace/utility/fallback-router.js +88 -0
  86. package/dist/src/router/namespace/utility/fallback-router.js.map +1 -0
  87. package/dist/src/router/namespace/utility/quick-task-router.d.ts +28 -0
  88. package/dist/src/router/namespace/utility/quick-task-router.d.ts.map +1 -0
  89. package/dist/src/router/namespace/utility/quick-task-router.js +99 -0
  90. package/dist/src/router/namespace/utility/quick-task-router.js.map +1 -0
  91. package/dist/src/router/namespace/utility/research-router.d.ts +24 -0
  92. package/dist/src/router/namespace/utility/research-router.d.ts.map +1 -0
  93. package/dist/src/router/namespace/utility/research-router.js +84 -0
  94. package/dist/src/router/namespace/utility/research-router.js.map +1 -0
  95. package/dist/src/skills/agents-md/index.js +2 -2
  96. package/dist/src/skills/agents-md/index.js.map +1 -1
  97. package/dist/src/skills/execute-plan/index.d.ts +45 -65
  98. package/dist/src/skills/execute-plan/index.d.ts.map +1 -1
  99. package/dist/src/skills/execute-plan/index.js +325 -551
  100. package/dist/src/skills/execute-plan/index.js.map +1 -1
  101. package/dist/src/skills/index.d.ts +1 -0
  102. package/dist/src/skills/index.d.ts.map +1 -1
  103. package/dist/src/skills/index.js +1 -0
  104. package/dist/src/skills/index.js.map +1 -1
  105. package/dist/src/skills/quick-task/index.d.ts +4 -4
  106. package/dist/src/skills/quick-task/index.js +1 -1
  107. package/dist/src/skills/quick-task/index.js.map +1 -1
  108. package/dist/src/skills/review-diff/index.d.ts +6 -6
  109. package/dist/src/skills/review-diff/index.js +1 -1
  110. package/dist/src/skills/review-diff/index.js.map +1 -1
  111. package/dist/src/skills/router/index.d.ts +101 -0
  112. package/dist/src/skills/router/index.d.ts.map +1 -0
  113. package/dist/src/skills/router/index.js +450 -0
  114. package/dist/src/skills/router/index.js.map +1 -0
  115. package/dist/src/skills/router/types.d.ts +79 -0
  116. package/dist/src/skills/router/types.d.ts.map +1 -0
  117. package/dist/src/skills/router/types.js +8 -0
  118. package/dist/src/skills/router/types.js.map +1 -0
  119. package/dist/src/skills/systematic-debugging/index.js +1 -1
  120. package/dist/src/skills/systematic-debugging/index.js.map +1 -1
  121. package/dist/src/skills/tdd/index.d.ts +14 -14
  122. package/dist/src/skills/tdd/index.js +1 -1
  123. package/dist/src/skills/tdd/index.js.map +1 -1
  124. package/dist/src/skills/to-plan/index-enhanced.d.ts +4 -4
  125. package/dist/src/skills/to-plan/index-enhanced.d.ts.map +1 -1
  126. package/dist/src/skills/to-plan/index-enhanced.js +3 -5
  127. package/dist/src/skills/to-plan/index-enhanced.js.map +1 -1
  128. package/dist/src/skills/to-plan/index.d.ts +24 -91
  129. package/dist/src/skills/to-plan/index.d.ts.map +1 -1
  130. package/dist/src/skills/to-plan/index.js +214 -409
  131. package/dist/src/skills/to-plan/index.js.map +1 -1
  132. package/package.json +3 -5
  133. package/scripts/postinstall.js +42 -47
  134. package/src/agents/contracts/implementer.ts +122 -0
  135. package/src/agents/contracts/index.ts +27 -0
  136. package/src/agents/contracts/planner.ts +129 -0
  137. package/src/agents/contracts/router.ts +168 -0
  138. package/src/agents/contracts/verifier.ts +137 -0
  139. package/src/agents/dispatcher.ts +387 -362
  140. package/src/persistence/index.ts +10 -4
  141. package/src/persistence/plan-md.ts +52 -18
  142. package/src/persistence/state-md.ts +45 -23
  143. package/src/persistence/types.ts +37 -40
  144. package/src/pi-extension.ts +38 -76
  145. package/src/router/namespace/README.md +127 -0
  146. package/src/router/namespace/core/intent-router.ts +221 -0
  147. package/src/router/namespace/core/lifecycle-router.ts +156 -0
  148. package/src/router/namespace/core/state-router.ts +192 -0
  149. package/src/router/namespace/domain/code-router.ts +202 -0
  150. package/src/router/namespace/domain/debug-router.ts +167 -0
  151. package/src/router/namespace/domain/plan-router.ts +196 -0
  152. package/src/router/namespace/domain/review-router.ts +142 -0
  153. package/src/router/namespace/index.ts +84 -0
  154. package/src/router/namespace/registry.ts +242 -0
  155. package/src/router/namespace/types.ts +182 -0
  156. package/src/router/namespace/utility/fallback-router.ts +107 -0
  157. package/src/router/namespace/utility/quick-task-router.ts +121 -0
  158. package/src/router/namespace/utility/research-router.ts +105 -0
  159. package/src/skills/agents-md/index.ts +2 -2
  160. package/src/skills/execute-plan/index.ts +419 -673
  161. package/src/skills/index.ts +1 -0
  162. package/src/skills/quick-task/index.ts +1 -1
  163. package/src/skills/review-diff/index.ts +1 -1
  164. package/src/skills/router/SKILL.md +81 -0
  165. package/src/skills/router/index.ts +577 -0
  166. package/src/skills/router/types.ts +90 -0
  167. package/src/skills/systematic-debugging/index.ts +1 -1
  168. package/src/skills/tdd/index.ts +1 -1
  169. package/src/skills/to-plan/index-enhanced.ts +3 -5
  170. package/src/skills/to-plan/index.ts +231 -502
  171. package/dist/src/extension/classifier.d.ts +0 -18
  172. package/dist/src/extension/classifier.d.ts.map +0 -1
  173. package/dist/src/extension/classifier.js +0 -143
  174. package/dist/src/extension/classifier.js.map +0 -1
  175. package/src/extension/classifier.ts +0 -160
@@ -11,3 +11,4 @@ export { reviewDiffSkill, ReviewDiffSkill } from './review-diff/index.js'
11
11
  export { tddSkill, TDDSkill } from './tdd/index.js'
12
12
  export { systematicDebuggingSkill, SystematicDebuggingSkill } from './systematic-debugging/index.js'
13
13
  export { agentsMdSkill, AgentsMdSkill } from './agents-md/index.js'
14
+ export { routerSkill, RouterSkill } from './router/index.js'
@@ -134,7 +134,7 @@ export class QuickTaskSkill extends Skill<QuickTaskInput, QuickTaskOutput> {
134
134
  context: SkillContext,
135
135
  ): Promise<QuickTaskOutput> {
136
136
  const { config, logger } = context
137
- const dispatcher = createDispatcher(logger)
137
+ const dispatcher = createDispatcher({ logger })
138
138
  const guard = createMainAgentGuard({}, logger)
139
139
  let totalTokens = 0
140
140
 
@@ -213,7 +213,7 @@ export class ReviewDiffSkill extends Skill<ReviewDiffInput, ReviewDiffOutput> {
213
213
  context: SkillContext,
214
214
  ): Promise<ReviewDiffOutput> {
215
215
  const { config, logger } = context
216
- const dispatcher = createDispatcher(logger)
216
+ const dispatcher = createDispatcher({ logger })
217
217
  const guard = createMainAgentGuard({}, logger)
218
218
 
219
219
  guard.activateEmbargo()
@@ -0,0 +1,81 @@
1
+ ---
2
+ name: router
3
+ description: |
4
+ Universal entry point for ALL user messages. Routes questions, commands, requests, tasks — anything the user says.
5
+ Examples: "fix typo", "plan feature", "what's the weather", "debug error", "review PR", "implement auth", "rename file", "hello", "help".
6
+ Handles: planning, coding, debugging, reviewing, researching, quick tasks, TDD, documentation.
7
+ Always trigger this skill for any chat message, question, or command.
8
+ requires: []
9
+ ---
10
+
11
+ ## Purpose
12
+
13
+ Router is the **mandatory entry point** for ALL user requests. It dispatches to the correct target skill via 5-layer GSD routing.
14
+
15
+ ---
16
+
17
+ ## 5-Layer GSD Routing
18
+
19
+ ### Layer 1: State Check
20
+ - Read `.pi/state/STATE.md` for current workflow state
21
+ - Phase: `idle`, `planning`, `executing`, `verifying`, `completed`, `failed`
22
+
23
+ ### Layer 2: Lifecycle Gate
24
+ - Check PLAN.md existence and status
25
+ - If plan exists → execute-plan
26
+ - If no plan + complex request → to-plan
27
+
28
+ ### Layer 3: Intent Classification
29
+ | Signal | Target |
30
+ |--------|--------|
31
+ | plan, design, architecture | to-plan |
32
+ | implement, execute, build | execute-plan |
33
+ | fix typo, rename, quick | quick-task |
34
+ | bug, error, failing | systematic-debugging |
35
+ | review, audit | review-diff |
36
+ | test, TDD | tdd |
37
+ | weather, time, price, search, question | quick-task |
38
+
39
+ ### Layer 4: Semantic Analysis
40
+ - Trivial (<30 chars, single change) → quick-task
41
+ - Complex (multi-file, architectural) → to-plan
42
+
43
+ ### Layer 5: Confidence
44
+ - >0.8 → route directly
45
+ - <0.5 → to-plan (exploration)
46
+
47
+ ---
48
+
49
+ ## Target Skills
50
+
51
+ | Skill | When |
52
+ |-------|------|
53
+ | to-plan | Complex feature, unknown scope |
54
+ | execute-plan | Active plan exists |
55
+ | quick-task | Trivial change, questions, search |
56
+ | review-diff | Code review |
57
+ | systematic-debugging | Bug investigation |
58
+ | tdd | Test-first development |
59
+ | agents-md | Documentation |
60
+
61
+ ---
62
+
63
+ ## Workflow
64
+
65
+ ```
66
+ User Request → Router (this skill)
67
+ ↓ reads STATE.md + PLAN.md
68
+ ↓ 5-layer routing
69
+ ↓ returns { targetSkill, confidence, reasoning }
70
+ ```
71
+
72
+ ---
73
+
74
+ ## Error Handling
75
+
76
+ | Error | Action |
77
+ |-------|--------|
78
+ | STATE.md missing | Assume idle phase |
79
+ | PLAN.md missing | Assume planning needed |
80
+ | Router fails | Default to quick-task |
81
+ | Confidence <0.3 | Route to to-plan |
@@ -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