@houseofwolvesllc/claude-scrum-skill 1.8.1 → 2.0.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.
- package/README.md +55 -0
- package/bin/install.js +9 -0
- package/lib/workflows/adversarial_verify.js +148 -0
- package/lib/workflows/elaborate_epics.js +136 -0
- package/lib/workflows/review_panel.js +166 -0
- package/lib/workflows/schemas/EmulationFindingSchema.json +36 -0
- package/lib/workflows/schemas/EpicSchema.json +47 -0
- package/lib/workflows/schemas/PRDFrontmatterSchema.json +21 -0
- package/lib/workflows/schemas/ReviewVerdictSchema.json +61 -0
- package/lib/workflows/schemas/ScaffoldOutputSchema.json +34 -0
- package/lib/workflows/schemas/SpecSchema.json +77 -0
- package/lib/workflows/schemas/SprintStoryReturnSchema.json +42 -0
- package/lib/workflows/schemas/StorySchema.json +56 -0
- package/lib/workflows/sprint_pipeline.js +252 -0
- package/package.json +3 -2
- package/skills/project-cleanup/SKILL.md +27 -0
- package/skills/project-emulate/SKILL.md +29 -0
- package/skills/project-orchestrate/SKILL.md +45 -79
- package/skills/project-scaffold/SKILL.md +21 -23
- package/skills/project-spec/SKILL.md +29 -0
package/README.md
CHANGED
|
@@ -60,6 +60,8 @@ PRD (optional) --> /project-orchestrate
|
|
|
60
60
|
|
|
61
61
|
## Installation
|
|
62
62
|
|
|
63
|
+
> **v2.0.0 runtime requirement:** v2.0.0 invokes Claude Code's [Workflow tool](https://docs.claude.com/claude-code) for internal fan-out. Your Claude Code must include the Workflow tool (latest CLI, desktop app, web app, and IDE extensions all do). If you're on a stale CLI install without auto-update, run `npm update -g @anthropic-ai/claude-code` first — or install the v1.8.x fallback: `npm install --save-dev @houseofwolvesllc/claude-scrum-skill@1.8.1`.
|
|
64
|
+
|
|
63
65
|
### npm (recommended)
|
|
64
66
|
|
|
65
67
|
```bash
|
|
@@ -482,6 +484,59 @@ Run `/project-scaffold` with a new PRD — it detects the existing project and o
|
|
|
482
484
|
|
|
483
485
|
---
|
|
484
486
|
|
|
487
|
+
## Architecture (v2.0.0+)
|
|
488
|
+
|
|
489
|
+
claudescrumskill ships in two cooperating layers. The split is documented in detail in [ADR-0003](docs/adrs/0003-workflow-backed-re-plumbing.md).
|
|
490
|
+
|
|
491
|
+
```
|
|
492
|
+
┌────────────────────────────────────────────────────────────┐
|
|
493
|
+
│ Skills — markdown SKILL.md (the opinion + the surface) │
|
|
494
|
+
│ - Slash commands users type │
|
|
495
|
+
│ - Phase / step structure │
|
|
496
|
+
│ - Durable repo artifacts (state files, ADRs, CONTEXT.md) │
|
|
497
|
+
│ - Installed at ~/.claude/skills/<skill>/SKILL.md │
|
|
498
|
+
└──────────────────────┬─────────────────────────────────────┘
|
|
499
|
+
│ invokes via Workflow tool
|
|
500
|
+
▼
|
|
501
|
+
┌────────────────────────────────────────────────────────────┐
|
|
502
|
+
│ Workflows — JavaScript at lib/workflows/ (the substrate) │
|
|
503
|
+
│ - Fan-out (parallel, pipeline) up to 16 concurrent agents │
|
|
504
|
+
│ - Schema-validated structured returns │
|
|
505
|
+
│ - Journal-based in-session resume │
|
|
506
|
+
│ - Installed at ~/.claude/skills/_workflows/ │
|
|
507
|
+
└──────────────────────┬─────────────────────────────────────┘
|
|
508
|
+
│ spawns
|
|
509
|
+
▼
|
|
510
|
+
┌────────────────────────────────────────────────────────────┐
|
|
511
|
+
│ Agents — the workers that implement stories, review │
|
|
512
|
+
│ diffs, verify findings, etc. │
|
|
513
|
+
└────────────────────────────────────────────────────────────┘
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
### Workflow scripts shipped in v2.0.0
|
|
517
|
+
|
|
518
|
+
| Script | Purpose | Used by |
|
|
519
|
+
|--------|---------|---------|
|
|
520
|
+
| `sprint_pipeline.js` | per-story pipeline: implement → review → verify → openPR | `/project-orchestrate` Phase 1 Step 3 |
|
|
521
|
+
| `elaborate_epics.js` | Pass 2 of two-pass scaffolding, in parallel | `/project-scaffold` |
|
|
522
|
+
| `adversarial_verify.js` | claimant/skeptic/judge per emulation finding | `/project-emulate` |
|
|
523
|
+
| `review_panel.js` | multi-lens (correctness/security/style/tests) review | `/project-cleanup`, `/code-review` |
|
|
524
|
+
|
|
525
|
+
### Schemas at `lib/workflows/schemas/`
|
|
526
|
+
|
|
527
|
+
JSON Schema Draft 2020-12. The cross-skill type system: `SpecSchema`, `EpicSchema`, `StorySchema`, `EmulationFindingSchema`, `ReviewVerdictSchema`, `SprintStoryReturnSchema`, `ScaffoldOutputSchema`, `PRDFrontmatterSchema`. Schemas live alongside workflows and are referenced from `agent({ schema: ... })` calls.
|
|
528
|
+
|
|
529
|
+
### Adding a workflow
|
|
530
|
+
|
|
531
|
+
Contributors adding fan-out heavy skills (large parallel work, multi-stage pipelines, judge panels) should:
|
|
532
|
+
|
|
533
|
+
1. Author the workflow at `lib/workflows/<name>.js` per the patterns in existing scripts.
|
|
534
|
+
2. Add any new schemas to `lib/workflows/schemas/`.
|
|
535
|
+
3. Reference the workflow from the skill markdown via the Path Resolution Algorithm documented in each affected SKILL.md (`<skills-root>/_workflows/<name>.js`).
|
|
536
|
+
4. Bump the package version per SemVer (new workflow + new schema → minor; breaking schema change → major).
|
|
537
|
+
|
|
538
|
+
---
|
|
539
|
+
|
|
485
540
|
## Shared References
|
|
486
541
|
|
|
487
542
|
All skills reference shared configuration and standards from `skills/shared/`:
|
package/bin/install.js
CHANGED
|
@@ -5,6 +5,7 @@ const path = require('path');
|
|
|
5
5
|
|
|
6
6
|
const HOME = process.env.HOME || process.env.USERPROFILE;
|
|
7
7
|
const SOURCE_DIR = path.join(__dirname, '..', 'skills');
|
|
8
|
+
const WORKFLOWS_SOURCE_DIR = path.join(__dirname, '..', 'lib', 'workflows');
|
|
8
9
|
const IS_GLOBAL = process.env.npm_config_global === 'true';
|
|
9
10
|
|
|
10
11
|
// Global install → ~/.claude/skills/
|
|
@@ -76,6 +77,14 @@ for (const skill of skills) {
|
|
|
76
77
|
}
|
|
77
78
|
}
|
|
78
79
|
|
|
80
|
+
// Copy lib/workflows/ → <skillsDir>/_workflows/ (v2.0.0+).
|
|
81
|
+
// Underscore prefix prevents Claude Code from registering as a skill.
|
|
82
|
+
if (fs.existsSync(WORKFLOWS_SOURCE_DIR)) {
|
|
83
|
+
const workflowsDest = path.join(skillsDir, '_workflows');
|
|
84
|
+
copyRecursive(WORKFLOWS_SOURCE_DIR, workflowsDest);
|
|
85
|
+
console.log(' ⚙️ _workflows (lib/workflows + schemas)');
|
|
86
|
+
}
|
|
87
|
+
|
|
79
88
|
// Add .claude-scrum-skill to .gitignore if not already present (local install only)
|
|
80
89
|
if (!IS_GLOBAL) {
|
|
81
90
|
const projectRoot = path.resolve(skillsDir, '..', '..');
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// adversarial_verify.js — Claimant / skeptic / judge verification of
|
|
2
|
+
// emulation findings. Replaces "trust the emulator" with structured verdicts.
|
|
3
|
+
//
|
|
4
|
+
// Invoked by /project-emulate after raw findings are produced.
|
|
5
|
+
//
|
|
6
|
+
// args: {
|
|
7
|
+
// findings: EmulationFinding[], // EmulationFindingSchema-shaped
|
|
8
|
+
// codebaseContext?: { projectRoot: string, languages: string[] }
|
|
9
|
+
// }
|
|
10
|
+
//
|
|
11
|
+
// returns: Array<{ finding, claim, skeptic, verdict }>
|
|
12
|
+
|
|
13
|
+
export const meta = {
|
|
14
|
+
name: 'adversarial-verify',
|
|
15
|
+
description: 'Claimant / skeptic / judge verification of emulation findings.',
|
|
16
|
+
phases: [
|
|
17
|
+
{ title: 'Argue' },
|
|
18
|
+
{ title: 'Judge' },
|
|
19
|
+
],
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const EVIDENCE_SCHEMA = {
|
|
23
|
+
type: 'object',
|
|
24
|
+
required: ['summary', 'evidence'],
|
|
25
|
+
properties: {
|
|
26
|
+
summary: { type: 'string' },
|
|
27
|
+
evidence: { type: 'array', items: { type: 'string' }, minItems: 1 },
|
|
28
|
+
confidence: { type: 'string', enum: ['low', 'medium', 'high'] },
|
|
29
|
+
},
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const VERDICT_SCHEMA = {
|
|
33
|
+
type: 'object',
|
|
34
|
+
required: ['isReal', 'rationale'],
|
|
35
|
+
properties: {
|
|
36
|
+
isReal: { type: 'boolean' },
|
|
37
|
+
rationale: { type: 'string' },
|
|
38
|
+
confidence: { type: 'string', enum: ['low', 'medium', 'high'] },
|
|
39
|
+
severity_adjustment: {
|
|
40
|
+
type: 'string',
|
|
41
|
+
enum: ['raise', 'lower', 'unchanged'],
|
|
42
|
+
description: 'Whether to raise or lower severity vs original finding.',
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const { findings, codebaseContext = {} } = args
|
|
48
|
+
|
|
49
|
+
if (!findings || findings.length === 0) {
|
|
50
|
+
log('No findings to verify — exiting.')
|
|
51
|
+
return []
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
log(`Verifying ${findings.length} findings with claimant/skeptic/judge.`)
|
|
55
|
+
|
|
56
|
+
phase('Argue')
|
|
57
|
+
phase('Judge')
|
|
58
|
+
|
|
59
|
+
function claimantPrompt(finding) {
|
|
60
|
+
return `You are arguing that the following emulation finding IS real and accurate.
|
|
61
|
+
|
|
62
|
+
Finding (severity: ${finding.severity}):
|
|
63
|
+
Title: ${finding.title}
|
|
64
|
+
Category: ${finding.category}
|
|
65
|
+
Body: ${finding.body}
|
|
66
|
+
Affected files: ${(finding.affected_files || []).join(', ') || '(none)'}
|
|
67
|
+
|
|
68
|
+
Argue the case. Read the affected files. Produce evidence supporting the finding. Cite specific lines / patterns. Default toward "real" unless evidence clearly contradicts.
|
|
69
|
+
|
|
70
|
+
Return: summary, evidence (array of citations), confidence.`
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function skepticPrompt(finding) {
|
|
74
|
+
return `You are arguing that the following emulation finding is a FALSE POSITIVE or overstated.
|
|
75
|
+
|
|
76
|
+
Finding (severity: ${finding.severity}):
|
|
77
|
+
Title: ${finding.title}
|
|
78
|
+
Category: ${finding.category}
|
|
79
|
+
Body: ${finding.body}
|
|
80
|
+
Affected files: ${(finding.affected_files || []).join(', ') || '(none)'}
|
|
81
|
+
|
|
82
|
+
Argue the case. Read the affected files. Look for: missing context the emulator didn't see, project-specific conventions that make this fine, downstream code that handles the concern, scope-narrowing facts that reduce severity, alternative interpretations.
|
|
83
|
+
|
|
84
|
+
Return: summary, evidence (array of citations), confidence.`
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function judgePrompt(finding, claim, skeptic) {
|
|
88
|
+
return `You are the third agent. Two prior agents argued opposing positions on this emulation finding.
|
|
89
|
+
|
|
90
|
+
Finding:
|
|
91
|
+
Title: ${finding.title}
|
|
92
|
+
Body: ${finding.body}
|
|
93
|
+
|
|
94
|
+
Claimant argued REAL:
|
|
95
|
+
Summary: ${claim.summary}
|
|
96
|
+
Evidence: ${(claim.evidence || []).join('\n ')}
|
|
97
|
+
Confidence: ${claim.confidence || 'unspecified'}
|
|
98
|
+
|
|
99
|
+
Skeptic argued FALSE POSITIVE:
|
|
100
|
+
Summary: ${skeptic.summary}
|
|
101
|
+
Evidence: ${(skeptic.evidence || []).join('\n ')}
|
|
102
|
+
Confidence: ${skeptic.confidence || 'unspecified'}
|
|
103
|
+
|
|
104
|
+
Judge: is this finding real or false-positive? Provide rationale. Optionally suggest a severity adjustment (raise / lower / unchanged).
|
|
105
|
+
|
|
106
|
+
Return: isReal (bool), rationale, confidence, severity_adjustment.`
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function verifyOne(finding) {
|
|
110
|
+
const [claim, skeptic] = await parallel([
|
|
111
|
+
() =>
|
|
112
|
+
agent(claimantPrompt(finding), {
|
|
113
|
+
label: `claim:${finding.id}`,
|
|
114
|
+
phase: 'Argue',
|
|
115
|
+
schema: EVIDENCE_SCHEMA,
|
|
116
|
+
}),
|
|
117
|
+
() =>
|
|
118
|
+
agent(skepticPrompt(finding), {
|
|
119
|
+
label: `skeptic:${finding.id}`,
|
|
120
|
+
phase: 'Argue',
|
|
121
|
+
schema: EVIDENCE_SCHEMA,
|
|
122
|
+
}),
|
|
123
|
+
])
|
|
124
|
+
|
|
125
|
+
if (!claim || !skeptic) {
|
|
126
|
+
log(`Skipping judge for finding ${finding.id} — claimant or skeptic agent failed.`)
|
|
127
|
+
return null
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const verdict = await agent(judgePrompt(finding, claim, skeptic), {
|
|
131
|
+
label: `judge:${finding.id}`,
|
|
132
|
+
phase: 'Judge',
|
|
133
|
+
schema: VERDICT_SCHEMA,
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
if (!verdict) return null
|
|
137
|
+
|
|
138
|
+
return { finding, claim, skeptic, verdict }
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const verified = await parallel(findings.map(f => () => verifyOne(f)))
|
|
142
|
+
const successful = verified.filter(Boolean)
|
|
143
|
+
|
|
144
|
+
const realCount = successful.filter(v => v.verdict.isReal).length
|
|
145
|
+
const falsePositiveCount = successful.length - realCount
|
|
146
|
+
log(`Verified ${successful.length}/${findings.length} findings: ${realCount} real, ${falsePositiveCount} false-positive.`)
|
|
147
|
+
|
|
148
|
+
return verified
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
// elaborate_epics.js — Pass 2 of two-pass scaffolding.
|
|
2
|
+
//
|
|
3
|
+
// Invoked by /project-scaffold when two-pass mode is selected.
|
|
4
|
+
// Replaces the v1.x Task-spawning prose with one parallel wave.
|
|
5
|
+
//
|
|
6
|
+
// args: {
|
|
7
|
+
// skeleton: {
|
|
8
|
+
// project: { name, description, global_preamble, non_functional_requirements: string[] },
|
|
9
|
+
// epics: Array<{
|
|
10
|
+
// name, slug, description,
|
|
11
|
+
// slice: { start_line: int, end_line: int },
|
|
12
|
+
// depends_on: string[],
|
|
13
|
+
// shared_design_concerns: string[]
|
|
14
|
+
// }>
|
|
15
|
+
// },
|
|
16
|
+
// prdPath: string, // for slicing
|
|
17
|
+
// conventionsPath?: string // shared/references/CONVENTIONS.md
|
|
18
|
+
// }
|
|
19
|
+
//
|
|
20
|
+
// returns: Epic[] with stories[] populated. Failed epics return null;
|
|
21
|
+
// the calling skill marks their stories needs-context.
|
|
22
|
+
|
|
23
|
+
export const meta = {
|
|
24
|
+
name: 'elaborate-epics',
|
|
25
|
+
description: 'Pass 2 of two-pass scaffolding: per-epic elaboration in parallel.',
|
|
26
|
+
phases: [{ title: 'Elaborate Epics' }],
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const EPIC_SCHEMA = {
|
|
30
|
+
type: 'object',
|
|
31
|
+
required: ['name', 'slug', 'description', 'stories'],
|
|
32
|
+
properties: {
|
|
33
|
+
name: { type: 'string' },
|
|
34
|
+
slug: { type: 'string', pattern: '^[a-z0-9]+(-[a-z0-9]+)*$' },
|
|
35
|
+
description: { type: 'string' },
|
|
36
|
+
depends_on: { type: 'array', items: { type: 'string' } },
|
|
37
|
+
shared_design_concerns: { type: 'array', items: { type: 'string' } },
|
|
38
|
+
stories: {
|
|
39
|
+
type: 'array',
|
|
40
|
+
items: {
|
|
41
|
+
type: 'object',
|
|
42
|
+
required: ['title', 'slug', 'acceptance_criteria', 'points', 'executor'],
|
|
43
|
+
properties: {
|
|
44
|
+
title: { type: 'string' },
|
|
45
|
+
slug: { type: 'string', pattern: '^[a-z0-9]+(-[a-z0-9]+)*$' },
|
|
46
|
+
acceptance_criteria: { type: 'array', items: { type: 'string' }, minItems: 1 },
|
|
47
|
+
technical_context: { type: 'string' },
|
|
48
|
+
points: { type: 'integer', enum: [1, 2, 3, 5, 8, 13] },
|
|
49
|
+
executor: { type: 'string', enum: ['claude', 'human', 'cowork'] },
|
|
50
|
+
priority: { type: 'string', enum: ['P0-critical', 'P1-high', 'P2-medium', 'P3-low'] },
|
|
51
|
+
persona: { type: 'string', enum: ['impl', 'ops', 'research'] },
|
|
52
|
+
blocked_by: { type: 'array', items: { type: 'string' } },
|
|
53
|
+
blocks: { type: 'array', items: { type: 'string' } },
|
|
54
|
+
labels: { type: 'array', items: { type: 'string' } },
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
minItems: 1,
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const { skeleton, prdPath, conventionsPath } = args
|
|
63
|
+
|
|
64
|
+
if (!skeleton || !skeleton.epics || skeleton.epics.length === 0) {
|
|
65
|
+
log('Empty skeleton — exiting.')
|
|
66
|
+
return []
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
log(`Pass 2: elaborating ${skeleton.epics.length} epics from ${prdPath}.`)
|
|
70
|
+
|
|
71
|
+
phase('Elaborate Epics')
|
|
72
|
+
|
|
73
|
+
function buildElaboratePrompt(epic) {
|
|
74
|
+
const siblings = skeleton.epics
|
|
75
|
+
.filter(e => e.slug !== epic.slug)
|
|
76
|
+
.map(e => ` - ${e.slug}: ${e.description}`)
|
|
77
|
+
.join('\n')
|
|
78
|
+
|
|
79
|
+
const sharedConcerns = (epic.shared_design_concerns || []).map(c => ` - ${c}`).join('\n')
|
|
80
|
+
|
|
81
|
+
return `You are elaborating epic "${epic.name}" (slug: ${epic.slug}) for project "${skeleton.project.name}".
|
|
82
|
+
|
|
83
|
+
== Project global preamble ==
|
|
84
|
+
${skeleton.project.global_preamble || ''}
|
|
85
|
+
|
|
86
|
+
== Project NFRs ==
|
|
87
|
+
${(skeleton.project.non_functional_requirements || []).map(n => ` - ${n}`).join('\n')}
|
|
88
|
+
|
|
89
|
+
== This epic ==
|
|
90
|
+
Description: ${epic.description}
|
|
91
|
+
PRD slice: lines ${epic.slice.start_line}–${epic.slice.end_line} of ${prdPath}
|
|
92
|
+
Depends on: ${(epic.depends_on || []).join(', ') || 'none'}
|
|
93
|
+
Shared design concerns this epic introduces:
|
|
94
|
+
${sharedConcerns || ' (none)'}
|
|
95
|
+
|
|
96
|
+
== Sibling epics (for dependency awareness, NOT for elaboration) ==
|
|
97
|
+
${siblings || ' (none)'}
|
|
98
|
+
|
|
99
|
+
== Your task ==
|
|
100
|
+
Read the PRD slice (lines ${epic.slice.start_line}–${epic.slice.end_line}). Produce the complete story list for THIS epic. Each story needs:
|
|
101
|
+
- title (concise imperative)
|
|
102
|
+
- slug (kebab-case)
|
|
103
|
+
- acceptance_criteria (at least one specific, testable item)
|
|
104
|
+
- technical_context (architecture notes, relevant files, approach)
|
|
105
|
+
- points (Fibonacci: 1, 2, 3, 5, 8, 13 per CONVENTIONS.md)
|
|
106
|
+
- executor (claude | human | cowork; per CONVENTIONS.md guidelines)
|
|
107
|
+
- persona (impl | ops | research; default impl)
|
|
108
|
+
- priority (P0/P1/P2/P3)
|
|
109
|
+
- blocked_by, blocks (within this epic or referencing other epics' slugs)
|
|
110
|
+
- labels (per CONVENTIONS.md taxonomy)
|
|
111
|
+
|
|
112
|
+
${conventionsPath ? `Reference ${conventionsPath} for the label taxonomy and story point guidelines.` : ''}
|
|
113
|
+
|
|
114
|
+
Return an Epic shape with name, slug, description, depends_on, shared_design_concerns, and stories[] populated.`
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const elaborated = await parallel(
|
|
118
|
+
skeleton.epics.map(epic => () =>
|
|
119
|
+
agent(buildElaboratePrompt(epic), {
|
|
120
|
+
label: `elaborate:${epic.slug}`,
|
|
121
|
+
phase: 'Elaborate Epics',
|
|
122
|
+
schema: EPIC_SCHEMA,
|
|
123
|
+
})
|
|
124
|
+
)
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
const successes = elaborated.filter(Boolean)
|
|
128
|
+
log(`Pass 2 complete: ${successes.length}/${skeleton.epics.length} epics elaborated successfully.`)
|
|
129
|
+
if (successes.length < skeleton.epics.length) {
|
|
130
|
+
const failed = skeleton.epics
|
|
131
|
+
.filter((_, i) => !elaborated[i])
|
|
132
|
+
.map(e => e.slug)
|
|
133
|
+
log(`Failed epics (calling skill should mark stories needs-context): ${failed.join(', ')}`)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return elaborated
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
// review_panel.js — Multi-lens parallel review with aggregated verdict.
|
|
2
|
+
//
|
|
3
|
+
// Invoked by /project-cleanup and /code-review for the review gate.
|
|
4
|
+
//
|
|
5
|
+
// args: {
|
|
6
|
+
// diff: string,
|
|
7
|
+
// files: Array<{ path: string, contents: string }>,
|
|
8
|
+
// lenses?: Array<"correctness" | "security" | "style" | "tests">,
|
|
9
|
+
// projectConventionsPath?: string // CLAUDE.md
|
|
10
|
+
// }
|
|
11
|
+
//
|
|
12
|
+
// returns: {
|
|
13
|
+
// panelVerdict: ReviewVerdict (aggregated),
|
|
14
|
+
// perLensVerdicts: Record<lens, ReviewVerdict>
|
|
15
|
+
// }
|
|
16
|
+
|
|
17
|
+
export const meta = {
|
|
18
|
+
name: 'review-panel',
|
|
19
|
+
description: 'Multi-lens parallel review with aggregated verdict.',
|
|
20
|
+
phases: [
|
|
21
|
+
{ title: 'Per-Lens Review' },
|
|
22
|
+
{ title: 'Aggregate' },
|
|
23
|
+
],
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const REVIEW_VERDICT_SCHEMA = {
|
|
27
|
+
type: 'object',
|
|
28
|
+
required: ['recommendation', 'findings', 'summary'],
|
|
29
|
+
properties: {
|
|
30
|
+
recommendation: { type: 'string', enum: ['accept', 'accept-with-followups', 'block'] },
|
|
31
|
+
findings: {
|
|
32
|
+
type: 'object',
|
|
33
|
+
required: ['critical', 'warning', 'info'],
|
|
34
|
+
properties: {
|
|
35
|
+
critical: { type: 'array' },
|
|
36
|
+
warning: { type: 'array' },
|
|
37
|
+
info: { type: 'array' },
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
summary: { type: 'string' },
|
|
41
|
+
lens: { type: 'string' },
|
|
42
|
+
},
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const DEFAULT_LENSES = ['correctness', 'security', 'style', 'tests']
|
|
46
|
+
|
|
47
|
+
const { diff, files, lenses = DEFAULT_LENSES, projectConventionsPath } = args
|
|
48
|
+
|
|
49
|
+
if (!diff && (!files || files.length === 0)) {
|
|
50
|
+
log('No diff and no files — nothing to review.')
|
|
51
|
+
return {
|
|
52
|
+
panelVerdict: {
|
|
53
|
+
recommendation: 'accept',
|
|
54
|
+
findings: { critical: [], warning: [], info: [] },
|
|
55
|
+
summary: 'Nothing to review.',
|
|
56
|
+
},
|
|
57
|
+
perLensVerdicts: {},
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
log(`Review panel: ${lenses.length} lenses (${lenses.join(', ')}).`)
|
|
62
|
+
|
|
63
|
+
phase('Per-Lens Review')
|
|
64
|
+
phase('Aggregate')
|
|
65
|
+
|
|
66
|
+
const LENS_INSTRUCTIONS = {
|
|
67
|
+
correctness: `Focus on: does the code do what the acceptance criteria / change description requires? Are there logic errors, off-by-ones, missed branches, broken invariants? Are edge cases handled?`,
|
|
68
|
+
security: `Focus on: injection sinks (SQL, shell, HTML, path), missing authentication checks, broken authorization, secret exposure, unvalidated user input reaching dangerous APIs, permission expansion without justification.`,
|
|
69
|
+
style: `Focus on: project convention adherence (per CLAUDE.md), naming consistency, file organization, import ordering. Do NOT bikeshed style the project doesn't enforce.`,
|
|
70
|
+
tests: `Focus on: are new behaviors covered by tests? Are existing tests still meaningful? Are tests testing behavior or implementation? Coverage at integration seams?`,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function buildLensPrompt(lens) {
|
|
74
|
+
const conventions = projectConventionsPath
|
|
75
|
+
? `Read ${projectConventionsPath} first — review against the project's actual conventions, not generic standards.`
|
|
76
|
+
: `Read the project CLAUDE.md if it exists — review against the project's actual conventions, not generic standards.`
|
|
77
|
+
return `You are reviewing a code change through the **${lens}** lens.
|
|
78
|
+
|
|
79
|
+
${conventions}
|
|
80
|
+
|
|
81
|
+
Lens focus: ${LENS_INSTRUCTIONS[lens] || '(no specific instructions for this lens)'}
|
|
82
|
+
|
|
83
|
+
== Diff ==
|
|
84
|
+
${diff || '(no diff provided; review the files below directly)'}
|
|
85
|
+
|
|
86
|
+
== Changed files (full contents) ==
|
|
87
|
+
${(files || []).map(f => `--- ${f.path} ---\n${f.contents}`).join('\n\n')}
|
|
88
|
+
|
|
89
|
+
Return a ReviewVerdict for THIS LENS ONLY:
|
|
90
|
+
- recommendation (accept | accept-with-followups | block)
|
|
91
|
+
- findings grouped by severity (critical | warning | info)
|
|
92
|
+
- summary (one-paragraph)
|
|
93
|
+
- lens: "${lens}"
|
|
94
|
+
|
|
95
|
+
Be specific. "This could be better" is not a finding. Cite file:line where applicable.`
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const perLensResults = await parallel(
|
|
99
|
+
lenses.map(lens => () =>
|
|
100
|
+
agent(buildLensPrompt(lens), {
|
|
101
|
+
label: `review:${lens}`,
|
|
102
|
+
phase: 'Per-Lens Review',
|
|
103
|
+
schema: REVIEW_VERDICT_SCHEMA,
|
|
104
|
+
})
|
|
105
|
+
)
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
const perLensVerdicts = {}
|
|
109
|
+
lenses.forEach((lens, i) => {
|
|
110
|
+
if (perLensResults[i]) {
|
|
111
|
+
perLensVerdicts[lens] = perLensResults[i]
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
const validVerdicts = Object.values(perLensVerdicts)
|
|
116
|
+
|
|
117
|
+
if (validVerdicts.length === 0) {
|
|
118
|
+
log('All lens reviewers failed — returning a default block verdict.')
|
|
119
|
+
return {
|
|
120
|
+
panelVerdict: {
|
|
121
|
+
recommendation: 'block',
|
|
122
|
+
findings: {
|
|
123
|
+
critical: [{
|
|
124
|
+
title: 'Review panel failed to produce any verdict',
|
|
125
|
+
severity: 'critical',
|
|
126
|
+
body: 'All lens reviewers returned null.',
|
|
127
|
+
}],
|
|
128
|
+
warning: [],
|
|
129
|
+
info: [],
|
|
130
|
+
},
|
|
131
|
+
summary: 'Review panel non-functional.',
|
|
132
|
+
},
|
|
133
|
+
perLensVerdicts: {},
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Aggregation rule: any block → panel block; any accept-with-followups → panel accept-with-followups; else accept.
|
|
138
|
+
let panelRecommendation = 'accept'
|
|
139
|
+
if (validVerdicts.some(v => v.recommendation === 'block')) {
|
|
140
|
+
panelRecommendation = 'block'
|
|
141
|
+
} else if (validVerdicts.some(v => v.recommendation === 'accept-with-followups')) {
|
|
142
|
+
panelRecommendation = 'accept-with-followups'
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Union findings across lenses, preserving lens attribution.
|
|
146
|
+
const findings = { critical: [], warning: [], info: [] }
|
|
147
|
+
for (const [lens, verdict] of Object.entries(perLensVerdicts)) {
|
|
148
|
+
for (const sev of ['critical', 'warning', 'info']) {
|
|
149
|
+
for (const f of verdict.findings[sev] || []) {
|
|
150
|
+
findings[sev].push({ ...f, lens })
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const summary = `Panel verdict: ${panelRecommendation}. ${validVerdicts.length}/${lenses.length} lenses reported. ${findings.critical.length} critical, ${findings.warning.length} warning, ${findings.info.length} info findings (union across lenses).`
|
|
156
|
+
|
|
157
|
+
log(summary)
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
panelVerdict: {
|
|
161
|
+
recommendation: panelRecommendation,
|
|
162
|
+
findings,
|
|
163
|
+
summary,
|
|
164
|
+
},
|
|
165
|
+
perLensVerdicts,
|
|
166
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://github.com/houseofwolvesllc/claudescrumskill/schemas/EmulationFindingSchema.json",
|
|
4
|
+
"title": "EmulationFindingSchema",
|
|
5
|
+
"description": "Single finding produced by /project-emulate; consumed by adversarial_verify.js.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["id", "severity", "category", "title", "body"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"id": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"description": "Unique finding identifier (e.g., C1, W3, I7)."
|
|
12
|
+
},
|
|
13
|
+
"severity": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"enum": ["critical", "warning", "info"]
|
|
16
|
+
},
|
|
17
|
+
"category": {
|
|
18
|
+
"type": "string",
|
|
19
|
+
"description": "Free-form classification (e.g., 'Integration/Dockerfile/COPY', 'Layer/ResponseFormat', 'Documentation/Consistency')."
|
|
20
|
+
},
|
|
21
|
+
"title": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"description": "One-line finding summary."
|
|
24
|
+
},
|
|
25
|
+
"body": {
|
|
26
|
+
"type": "string",
|
|
27
|
+
"description": "Detailed finding description including reproduction or evidence."
|
|
28
|
+
},
|
|
29
|
+
"affected_files": {
|
|
30
|
+
"type": "array",
|
|
31
|
+
"items": { "type": "string" },
|
|
32
|
+
"description": "Repo-relative paths of files implicated by the finding."
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"additionalProperties": false
|
|
36
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://github.com/houseofwolvesllc/claudescrumskill/schemas/EpicSchema.json",
|
|
4
|
+
"title": "EpicSchema",
|
|
5
|
+
"description": "Single epic shape with stories inlined to avoid cross-file $ref resolution ambiguity.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["name", "slug", "description"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"name": { "type": "string" },
|
|
10
|
+
"slug": { "type": "string", "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$" },
|
|
11
|
+
"description": { "type": "string" },
|
|
12
|
+
"depends_on": { "type": "array", "items": { "type": "string" } },
|
|
13
|
+
"shared_design_concerns": { "type": "array", "items": { "type": "string" } },
|
|
14
|
+
"slice": {
|
|
15
|
+
"type": "object",
|
|
16
|
+
"properties": {
|
|
17
|
+
"start_line": { "type": "integer", "minimum": 1 },
|
|
18
|
+
"end_line": { "type": "integer", "minimum": 1 }
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"epic_type": { "type": "string", "enum": ["design-spike"] },
|
|
22
|
+
"stories": {
|
|
23
|
+
"type": "array",
|
|
24
|
+
"items": { "$ref": "#/$defs/Story" }
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"additionalProperties": false,
|
|
28
|
+
"$defs": {
|
|
29
|
+
"Story": {
|
|
30
|
+
"type": "object",
|
|
31
|
+
"required": ["title", "slug", "acceptance_criteria", "points", "executor"],
|
|
32
|
+
"properties": {
|
|
33
|
+
"title": { "type": "string" },
|
|
34
|
+
"slug": { "type": "string", "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$" },
|
|
35
|
+
"acceptance_criteria": { "type": "array", "items": { "type": "string" }, "minItems": 1 },
|
|
36
|
+
"technical_context": { "type": "string" },
|
|
37
|
+
"points": { "type": "integer", "enum": [1, 2, 3, 5, 8, 13] },
|
|
38
|
+
"executor": { "type": "string", "enum": ["claude", "human", "cowork"] },
|
|
39
|
+
"priority": { "type": "string", "enum": ["P0-critical", "P1-high", "P2-medium", "P3-low"] },
|
|
40
|
+
"persona": { "type": "string", "enum": ["impl", "ops", "research"] },
|
|
41
|
+
"blocked_by": { "type": "array", "items": { "type": "string" } },
|
|
42
|
+
"blocks": { "type": "array", "items": { "type": "string" } },
|
|
43
|
+
"labels": { "type": "array", "items": { "type": "string" } }
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://github.com/houseofwolvesllc/claudescrumskill/schemas/PRDFrontmatterSchema.json",
|
|
4
|
+
"title": "PRDFrontmatterSchema",
|
|
5
|
+
"description": "YAML frontmatter parsed from PRD documents. All fields optional.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"properties": {
|
|
8
|
+
"title": { "type": "string" },
|
|
9
|
+
"scaffold_mode": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"enum": ["single-pass", "two-pass"]
|
|
12
|
+
},
|
|
13
|
+
"design_spike": { "type": "boolean" },
|
|
14
|
+
"depends_on": {
|
|
15
|
+
"type": "array",
|
|
16
|
+
"items": { "type": "string" },
|
|
17
|
+
"description": "Other PRD specs (path or basename) that must complete before this one in sequential multi-path mode."
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"additionalProperties": false
|
|
21
|
+
}
|