@haoyiyin/workflow 0.2.2 → 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 +9 -8
- 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,394 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Review Diff Skill - Reviews code changes via reviewer subagents
|
|
3
|
+
*
|
|
4
|
+
* Use case: Review PR, branch, commit, or diff-file
|
|
5
|
+
* Pattern: Get diff → Inspect surface → Check requirements → Check tests →
|
|
6
|
+
* Check quality → Return structured review report
|
|
7
|
+
*
|
|
8
|
+
* Spawns reviewer subagent(s) to review diffs.
|
|
9
|
+
* Returns structured review report with severity-classified issues.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { z } from 'zod'
|
|
13
|
+
import { Skill } from '../skill.js'
|
|
14
|
+
import type { SkillContext } from '../types.js'
|
|
15
|
+
import { createDispatcher } from '../../agents/dispatcher.js'
|
|
16
|
+
import { createMainAgentGuard } from '../../guard/main-agent.js'
|
|
17
|
+
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Schemas
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
const ReviewDiffInputSchema = z.object({
|
|
23
|
+
target: z.string().min(1),
|
|
24
|
+
targetType: z
|
|
25
|
+
.enum(['pr', 'branch', 'commit', 'diff-file'])
|
|
26
|
+
.default('branch'),
|
|
27
|
+
planPath: z.string().optional(),
|
|
28
|
+
focus: z
|
|
29
|
+
.array(z.enum(['security', 'performance', 'correctness', 'style', 'tests']))
|
|
30
|
+
.optional(),
|
|
31
|
+
model: z.string().optional(),
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const ReviewDiffOutputSchema = z.object({
|
|
35
|
+
decision: z.enum(['approve', 'changes', 'blocked', 'needs-evidence']),
|
|
36
|
+
issues: z.array(
|
|
37
|
+
z.object({
|
|
38
|
+
severity: z.enum(['blocker', 'major', 'minor', 'note']),
|
|
39
|
+
file: z.string().optional(),
|
|
40
|
+
line: z.number().optional(),
|
|
41
|
+
description: z.string(),
|
|
42
|
+
action: z.string(),
|
|
43
|
+
}),
|
|
44
|
+
),
|
|
45
|
+
specCompliance: z.enum(['pass', 'partial', 'fail']),
|
|
46
|
+
testAssessment: z.enum(['adequate', 'missing', 'failing']),
|
|
47
|
+
strengths: z.array(z.string()),
|
|
48
|
+
summary: z.string(),
|
|
49
|
+
tokensUsed: z.number(),
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
type ReviewDiffInput = z.infer<typeof ReviewDiffInputSchema>
|
|
53
|
+
type ReviewDiffOutput = z.infer<typeof ReviewDiffOutputSchema>
|
|
54
|
+
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// Prompt builders (pure functions)
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
function buildDiffPrompt(targetType: string, target: string): string {
|
|
60
|
+
const commands: Record<string, string> = {
|
|
61
|
+
pr: `gh pr diff ${target}`,
|
|
62
|
+
branch: `git diff origin/${target}...HEAD`,
|
|
63
|
+
commit: `git show ${target}`,
|
|
64
|
+
'diff-file': `cat ${target}`,
|
|
65
|
+
}
|
|
66
|
+
const cmd = commands[targetType] ?? `git diff ${target}`
|
|
67
|
+
return [
|
|
68
|
+
`Get the diff for ${targetType}: ${target}`,
|
|
69
|
+
'',
|
|
70
|
+
`Run: \`${cmd}\``,
|
|
71
|
+
'',
|
|
72
|
+
'Return the full diff content. Include changed file paths and line numbers.',
|
|
73
|
+
].join('\n')
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function buildSurfacePrompt(diff: string): string {
|
|
77
|
+
return [
|
|
78
|
+
'Analyze the changed surface from this diff:',
|
|
79
|
+
'',
|
|
80
|
+
diff.slice(0, 5000),
|
|
81
|
+
'',
|
|
82
|
+
'Report:',
|
|
83
|
+
'- Files changed (list each path)',
|
|
84
|
+
'- Lines added / removed per file',
|
|
85
|
+
'- Scope assessment (localized vs wide-ranging)',
|
|
86
|
+
].join('\n')
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function buildSpecCompliancePrompt(
|
|
90
|
+
diff: string,
|
|
91
|
+
_planPath: string,
|
|
92
|
+
): string {
|
|
93
|
+
return [
|
|
94
|
+
'Compare implementation to requirements.',
|
|
95
|
+
'',
|
|
96
|
+
'Read the plan file to understand what was requested.',
|
|
97
|
+
'',
|
|
98
|
+
'## Diff',
|
|
99
|
+
diff.slice(0, 4000),
|
|
100
|
+
'',
|
|
101
|
+
'Check: Does the implementation satisfy the plan requirements?',
|
|
102
|
+
'Return: pass, partial, or fail with specific violations listed.',
|
|
103
|
+
].join('\n')
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function buildQualityReviewPrompt(
|
|
107
|
+
diff: string,
|
|
108
|
+
focusAreas: string[],
|
|
109
|
+
): string {
|
|
110
|
+
const focusText =
|
|
111
|
+
focusAreas.length > 0 ? focusAreas.join(', ') : 'all aspects'
|
|
112
|
+
|
|
113
|
+
return [
|
|
114
|
+
`Review code quality focusing on: ${focusText}`,
|
|
115
|
+
'',
|
|
116
|
+
'## Diff',
|
|
117
|
+
diff.slice(0, 5000),
|
|
118
|
+
'',
|
|
119
|
+
'## Check Each Category',
|
|
120
|
+
'',
|
|
121
|
+
'- **Correctness**: Logic errors, edge cases, off-by-one',
|
|
122
|
+
'- **Security**: Injection risks, auth bypass, secrets in code, input validation',
|
|
123
|
+
'- **Performance**: N+1 queries, unnecessary allocations, blocking calls',
|
|
124
|
+
'- **Style**: Naming conventions, consistency with codebase, immutability',
|
|
125
|
+
'- **Error Handling**: Caught errors, graceful degradation, user-facing messages',
|
|
126
|
+
'',
|
|
127
|
+
'## Output Format',
|
|
128
|
+
'',
|
|
129
|
+
'For each issue found, output one line:',
|
|
130
|
+
'`SEVERITY|FILE|LINE|DESCRIPTION|SUGGESTED_ACTION`',
|
|
131
|
+
'',
|
|
132
|
+
'Severity values: BLOCKER, MAJOR, MINOR, NOTE',
|
|
133
|
+
'BLOCKER = must fix before merge (security hole, broken build, data loss)',
|
|
134
|
+
'MAJOR = should fix (bug, performance problem, missing error handling)',
|
|
135
|
+
'MINOR = nice to fix (style, naming, minor duplication)',
|
|
136
|
+
'NOTE = observation, not a problem',
|
|
137
|
+
].join('\n')
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// ---------------------------------------------------------------------------
|
|
141
|
+
// Parsing helpers (pure functions)
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
|
|
144
|
+
interface ParsedIssue {
|
|
145
|
+
severity: 'blocker' | 'major' | 'minor' | 'note'
|
|
146
|
+
file?: string
|
|
147
|
+
line?: number
|
|
148
|
+
description: string
|
|
149
|
+
action: string
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function parseIssues(output: string): ParsedIssue[] {
|
|
153
|
+
const issues: ParsedIssue[] = []
|
|
154
|
+
const lines = output.split('\n')
|
|
155
|
+
|
|
156
|
+
for (const line of lines) {
|
|
157
|
+
const match = line.match(
|
|
158
|
+
/^(BLOCKER|MAJOR|MINOR|NOTE)\s*\|\s*([^|]*)\s*\|\s*(\d+)\s*\|\s*([^|]+)\s*\|\s*(.+)$/i,
|
|
159
|
+
)
|
|
160
|
+
if (match) {
|
|
161
|
+
const file = match[2]?.trim()
|
|
162
|
+
issues.push({
|
|
163
|
+
severity: match[1].toLowerCase() as ParsedIssue['severity'],
|
|
164
|
+
file: file || undefined,
|
|
165
|
+
line: parseInt(match[3], 10),
|
|
166
|
+
description: match[4].trim(),
|
|
167
|
+
action: match[5].trim(),
|
|
168
|
+
})
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return issues
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function determineDecision(issues: ParsedIssue[]): ReviewDiffOutput['decision'] {
|
|
176
|
+
if (issues.some((i) => i.severity === 'blocker')) return 'blocked'
|
|
177
|
+
if (issues.some((i) => i.severity === 'major')) return 'changes'
|
|
178
|
+
return 'approve'
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function assessTests(output: string): ReviewDiffOutput['testAssessment'] {
|
|
182
|
+
const lower = output.toLowerCase()
|
|
183
|
+
if (lower.includes('missing') || lower.includes('no test')) return 'missing'
|
|
184
|
+
if (lower.includes('fail')) return 'failing'
|
|
185
|
+
return 'adequate'
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function assessSpecCompliance(output: string): ReviewDiffOutput['specCompliance'] {
|
|
189
|
+
const lower = output.toLowerCase()
|
|
190
|
+
if (lower.includes('fail')) return 'fail'
|
|
191
|
+
if (lower.includes('partial')) return 'partial'
|
|
192
|
+
return 'pass'
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ---------------------------------------------------------------------------
|
|
196
|
+
// Skill class
|
|
197
|
+
// ---------------------------------------------------------------------------
|
|
198
|
+
|
|
199
|
+
export class ReviewDiffSkill extends Skill<ReviewDiffInput, ReviewDiffOutput> {
|
|
200
|
+
constructor() {
|
|
201
|
+
super({
|
|
202
|
+
name: 'review-diff',
|
|
203
|
+
description:
|
|
204
|
+
'Review code changes (diff/branch/PR) with reviewer subagents. Returns structured report.',
|
|
205
|
+
requires: [],
|
|
206
|
+
inputSchema: ReviewDiffInputSchema as z.ZodType<ReviewDiffInput>,
|
|
207
|
+
outputSchema: ReviewDiffOutputSchema,
|
|
208
|
+
})
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async execute(
|
|
212
|
+
input: ReviewDiffInput,
|
|
213
|
+
context: SkillContext,
|
|
214
|
+
): Promise<ReviewDiffOutput> {
|
|
215
|
+
const { config, logger } = context
|
|
216
|
+
const dispatcher = createDispatcher(logger)
|
|
217
|
+
const guard = createMainAgentGuard({}, logger)
|
|
218
|
+
|
|
219
|
+
guard.activateEmbargo()
|
|
220
|
+
let totalTokens = 0
|
|
221
|
+
|
|
222
|
+
try {
|
|
223
|
+
// Step 1: Get diff
|
|
224
|
+
logger.info(
|
|
225
|
+
`[review-diff] Getting diff for ${input.targetType}: ${input.target}`,
|
|
226
|
+
)
|
|
227
|
+
const diffResult = await dispatcher.dispatch(
|
|
228
|
+
{
|
|
229
|
+
role: 'explorer',
|
|
230
|
+
model: input.model || config.defaultModel,
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
permissions: {
|
|
234
|
+
readFiles: true,
|
|
235
|
+
searchCode: false,
|
|
236
|
+
runCommands: true,
|
|
237
|
+
writeFiles: false,
|
|
238
|
+
gitOperations: true,
|
|
239
|
+
},
|
|
240
|
+
prompt: buildDiffPrompt(input.targetType, input.target),
|
|
241
|
+
owns: [],
|
|
242
|
+
reads: [],
|
|
243
|
+
},
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
totalTokens += diffResult.tokensUsed
|
|
247
|
+
|
|
248
|
+
if (diffResult.status !== 'success') {
|
|
249
|
+
return {
|
|
250
|
+
decision: 'needs-evidence',
|
|
251
|
+
issues: [
|
|
252
|
+
{
|
|
253
|
+
severity: 'blocker',
|
|
254
|
+
description: 'Cannot access diff',
|
|
255
|
+
action: 'Verify target exists and is accessible',
|
|
256
|
+
},
|
|
257
|
+
],
|
|
258
|
+
specCompliance: 'fail',
|
|
259
|
+
testAssessment: 'missing',
|
|
260
|
+
strengths: [],
|
|
261
|
+
summary: 'Cannot retrieve diff for review',
|
|
262
|
+
tokensUsed: totalTokens,
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const diffContent = diffResult.output
|
|
267
|
+
|
|
268
|
+
// Step 2: Inspect changed surface
|
|
269
|
+
logger.info('[review-diff] Inspecting changed surface')
|
|
270
|
+
const surfaceResult = await dispatcher.dispatch(
|
|
271
|
+
{
|
|
272
|
+
role: 'explorer',
|
|
273
|
+
model: input.model || config.defaultModel,
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
permissions: {
|
|
277
|
+
readFiles: true,
|
|
278
|
+
searchCode: false,
|
|
279
|
+
runCommands: false,
|
|
280
|
+
writeFiles: false,
|
|
281
|
+
gitOperations: false,
|
|
282
|
+
},
|
|
283
|
+
prompt: buildSurfacePrompt(diffContent),
|
|
284
|
+
owns: [],
|
|
285
|
+
reads: [],
|
|
286
|
+
},
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
totalTokens += surfaceResult.tokensUsed
|
|
290
|
+
|
|
291
|
+
// Step 3: Check requirement compliance (if plan provided)
|
|
292
|
+
let specOutput = 'pass'
|
|
293
|
+
if (input.planPath) {
|
|
294
|
+
logger.info('[review-diff] Checking spec compliance')
|
|
295
|
+
const specResult = await dispatcher.dispatch(
|
|
296
|
+
{
|
|
297
|
+
role: 'reviewer',
|
|
298
|
+
model: input.model || config.defaultModel,
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
permissions: {
|
|
302
|
+
readFiles: true,
|
|
303
|
+
searchCode: false,
|
|
304
|
+
runCommands: false,
|
|
305
|
+
writeFiles: false,
|
|
306
|
+
gitOperations: false,
|
|
307
|
+
},
|
|
308
|
+
prompt: buildSpecCompliancePrompt(diffContent, input.planPath),
|
|
309
|
+
owns: [],
|
|
310
|
+
reads: [input.planPath],
|
|
311
|
+
},
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
totalTokens += specResult.tokensUsed
|
|
315
|
+
specOutput = specResult.output
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Step 4: Check tests
|
|
319
|
+
logger.info('[review-diff] Assessing test coverage')
|
|
320
|
+
const testResult = await dispatcher.dispatch(
|
|
321
|
+
{
|
|
322
|
+
role: 'reviewer',
|
|
323
|
+
model: input.model || config.defaultModel,
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
permissions: {
|
|
327
|
+
readFiles: true,
|
|
328
|
+
searchCode: false,
|
|
329
|
+
runCommands: true,
|
|
330
|
+
writeFiles: false,
|
|
331
|
+
gitOperations: false,
|
|
332
|
+
},
|
|
333
|
+
prompt: [
|
|
334
|
+
'Check test coverage in the changed code:',
|
|
335
|
+
'',
|
|
336
|
+
diffContent.slice(0, 4000),
|
|
337
|
+
'',
|
|
338
|
+
'Do changed files have corresponding test changes?',
|
|
339
|
+
'Are new functions/classes covered by tests?',
|
|
340
|
+
'Return: adequate, missing, or failing with details.',
|
|
341
|
+
].join('\n'),
|
|
342
|
+
owns: [],
|
|
343
|
+
reads: [],
|
|
344
|
+
},
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
totalTokens += testResult.tokensUsed
|
|
348
|
+
|
|
349
|
+
// Step 5: Review code quality
|
|
350
|
+
logger.info('[review-diff] Reviewing code quality')
|
|
351
|
+
const qualityResult = await dispatcher.dispatch(
|
|
352
|
+
{
|
|
353
|
+
role: 'reviewer',
|
|
354
|
+
model: input.model || config.defaultModel,
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
permissions: {
|
|
358
|
+
readFiles: true,
|
|
359
|
+
searchCode: true,
|
|
360
|
+
runCommands: false,
|
|
361
|
+
writeFiles: false,
|
|
362
|
+
gitOperations: false,
|
|
363
|
+
},
|
|
364
|
+
prompt: buildQualityReviewPrompt(
|
|
365
|
+
diffContent,
|
|
366
|
+
input.focus ?? [],
|
|
367
|
+
),
|
|
368
|
+
owns: [],
|
|
369
|
+
reads: [],
|
|
370
|
+
},
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
totalTokens += qualityResult.tokensUsed
|
|
374
|
+
|
|
375
|
+
// Step 6: Compile final report
|
|
376
|
+
const issues = parseIssues(qualityResult.output)
|
|
377
|
+
|
|
378
|
+
return {
|
|
379
|
+
decision: determineDecision(issues),
|
|
380
|
+
issues,
|
|
381
|
+
specCompliance: assessSpecCompliance(specOutput),
|
|
382
|
+
testAssessment: assessTests(testResult.output),
|
|
383
|
+
strengths: ['Reviewed all changed files'],
|
|
384
|
+
summary: `Review complete: ${issues.length} issue(s) found (${issues.filter((i) => i.severity === 'blocker').length} blocker, ${issues.filter((i) => i.severity === 'major').length} major)`,
|
|
385
|
+
tokensUsed: totalTokens,
|
|
386
|
+
}
|
|
387
|
+
} finally {
|
|
388
|
+
guard.deactivateEmbargo()
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
export const reviewDiffSkill = new ReviewDiffSkill()
|
|
394
|
+
export default reviewDiffSkill
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base skill class with validation and error handling
|
|
3
|
+
*/
|
|
4
|
+
import { z } from 'zod'
|
|
5
|
+
import type {
|
|
6
|
+
SkillDefinition,
|
|
7
|
+
SkillContext,
|
|
8
|
+
} from './types.js'
|
|
9
|
+
|
|
10
|
+
export abstract class Skill<Input, Output> {
|
|
11
|
+
protected name: string
|
|
12
|
+
protected description: string
|
|
13
|
+
protected requires: string[]
|
|
14
|
+
protected inputSchema?: z.ZodSchema<Input>
|
|
15
|
+
protected outputSchema?: z.ZodSchema<Output>
|
|
16
|
+
|
|
17
|
+
constructor(options: {
|
|
18
|
+
name: string
|
|
19
|
+
description: string
|
|
20
|
+
requires?: string[]
|
|
21
|
+
inputSchema?: z.ZodSchema<Input>
|
|
22
|
+
outputSchema?: z.ZodSchema<Output>
|
|
23
|
+
}) {
|
|
24
|
+
this.name = options.name
|
|
25
|
+
this.description = options.description
|
|
26
|
+
this.requires = options.requires || []
|
|
27
|
+
this.inputSchema = options.inputSchema
|
|
28
|
+
this.outputSchema = options.outputSchema
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
abstract execute(input: Input, context: SkillContext): Promise<Output>
|
|
32
|
+
|
|
33
|
+
async validateInput(input: unknown): Promise<Input> {
|
|
34
|
+
if (this.inputSchema) {
|
|
35
|
+
return this.inputSchema.parse(input)
|
|
36
|
+
}
|
|
37
|
+
return input as Input
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async validateOutput(output: unknown): Promise<Output> {
|
|
41
|
+
if (this.outputSchema) {
|
|
42
|
+
return this.outputSchema.parse(output)
|
|
43
|
+
}
|
|
44
|
+
return output as Output
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
toDefinition(): SkillDefinition<any, any> {
|
|
48
|
+
return {
|
|
49
|
+
name: this.name,
|
|
50
|
+
description: this.description,
|
|
51
|
+
requires: this.requires,
|
|
52
|
+
execute: async (input: any, context: SkillContext) => {
|
|
53
|
+
const validatedInput = await this.validateInput(input)
|
|
54
|
+
const result = await this.execute(validatedInput, context)
|
|
55
|
+
return await this.validateOutput(result)
|
|
56
|
+
},
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: systematic-debugging
|
|
3
|
+
description: Find root cause before fixing. Spawn debugger subagents for systematic diagnosis. CRITICAL: no fix before root cause confirmed.
|
|
4
|
+
requires: [tdd]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## ⛔ MAIN AGENT CONSTRAINT
|
|
8
|
+
|
|
9
|
+
You are a THIN DISPATCHER. Your ONLY job is to dispatch subagents.
|
|
10
|
+
You MUST NOT: read source files, search code, write/edit files, run tests, run git commands.
|
|
11
|
+
You MAY only: read plan/state files, dispatch subagents via Agent tool, relay results.
|
|
12
|
+
For EVERY user request (including this skill), dispatch a subagent. Never execute yourself.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Use/Exclude Matrix
|
|
17
|
+
|
|
18
|
+
| Use When | Exclude When |
|
|
19
|
+
|---|---|
|
|
20
|
+
| Bug with unknown root cause | Root cause already known and confirmed |
|
|
21
|
+
| Test failure needing investigation | Typo or syntax error (use quick-task) |
|
|
22
|
+
| Intermittent/flaky behavior | Build/environment issue (not code) |
|
|
23
|
+
| Regression after recent changes | Fix is obvious and trivial |
|
|
24
|
+
|
|
25
|
+
## Workflow
|
|
26
|
+
|
|
27
|
+
1. **Reproduce** — Dispatch debugger subagent to reproduce the symptom. Confirm observed vs expected behavior.
|
|
28
|
+
2. **Gather Evidence** — Dispatch explorer subagent (read-only) to identify suspect files, key functions, failure points, recent changes.
|
|
29
|
+
3. **Diagnose** — Dispatch debugger subagent to form hypotheses, test top hypothesis, eliminate alternatives, confirm root cause. NO CODE CHANGES in this phase.
|
|
30
|
+
4. **Apply Fix** — Dispatch debugger subagent (worktree) to apply ONE minimal fix targeting the confirmed root cause.
|
|
31
|
+
5. **Verify** — Dispatch verifier subagent to re-run reproduction and full test suite.
|
|
32
|
+
|
|
33
|
+
## Output Spec
|
|
34
|
+
|
|
35
|
+
| Field | Type | Description |
|
|
36
|
+
|---|---|---|
|
|
37
|
+
| `rootCause` | string | Description of confirmed root cause |
|
|
38
|
+
| `confidence` | enum | `high`, `medium`, `low` |
|
|
39
|
+
| `location.file` | string? | File containing the root cause |
|
|
40
|
+
| `location.line` | number? | Line number of root cause |
|
|
41
|
+
| `location.function` | string? | Function containing root cause |
|
|
42
|
+
| `fixApplied` | boolean | Whether the fix was successfully applied |
|
|
43
|
+
| `fixDescription` | string? | Description of the applied fix |
|
|
44
|
+
| `verification` | string | Verification output (PASS/FAIL with details) |
|
|
45
|
+
| `risks` | string[] | Identified risks or follow-ups |
|
|
46
|
+
| `tokensUsed` | number | Total tokens consumed |
|
|
47
|
+
|
|
48
|
+
## Error Handling
|
|
49
|
+
|
|
50
|
+
| Error | Action |
|
|
51
|
+
|---|---|
|
|
52
|
+
| Cannot reproduce symptom | Return with confidence=low; describe reproduction gap |
|
|
53
|
+
| Diagnosis confidence is low | Apply safer, more conservative fix |
|
|
54
|
+
| Diagnose phase blocked (no fix yet) | Enforced by prompt design; subagent told not to write code |
|
|
55
|
+
| Fix subagent fails | Return fixApplied=false; preserve diagnosis |
|
|
56
|
+
| Verification fails after fix | Return verification FAIL; may need re-diagnosis |
|