@houseofwolvesllc/claude-scrum-skill 1.8.1 → 2.1.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/.claude-plugin/plugin.json +1 -1
- package/README.md +85 -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 +78 -0
- package/lib/workflows/schemas/SprintStoryReturnSchema.json +42 -0
- package/lib/workflows/schemas/StorySchema.json +56 -0
- package/lib/workflows/sprint_pipeline.js +264 -0
- package/package.json +3 -2
- package/skills/design-patterns/SKILL.md +133 -0
- package/skills/domain-modeling/SKILL.md +121 -0
- package/skills/project-cleanup/SKILL.md +27 -0
- package/skills/project-emulate/SKILL.md +29 -0
- package/skills/project-orchestrate/SKILL.md +49 -79
- package/skills/project-scaffold/SKILL.md +27 -19
- package/skills/project-spec/SKILL.md +67 -0
- package/skills/shared/references/ENGINEERING_BASELINE.md +154 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-scrum-skill",
|
|
3
3
|
"description": "Complete scrum pipeline — PRD to production release with Claude as scrum master. Includes project scaffolding, sprint planning, status tracking, sprint releases, and full-project emulation testing.",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "2.1.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "House of Wolves LLC"
|
|
7
7
|
},
|
package/README.md
CHANGED
|
@@ -4,6 +4,8 @@ An open-source npm package of Claude Code skills that give you a complete scrum
|
|
|
4
4
|
|
|
5
5
|
Includes project scaffolding, sprint planning, status tracking, sprint releases, full-project emulation testing, autonomous orchestration, and project cleanup.
|
|
6
6
|
|
|
7
|
+
It is also an **opinionated spec-and-implementation process**: its engineering guidance is grounded in the principles of four foundational software texts — Robert C. Martin's *Clean Code*, Kent Beck's *Test-Driven Development: By Example*, Eric Evans's *Domain-Driven Design*, and the Gang of Four's *Design Patterns* — distilled into an always-on engineering baseline plus on-demand design-pattern and domain-modeling guidance. See [Acknowledgments](#acknowledgments).
|
|
8
|
+
|
|
7
9
|
```
|
|
8
10
|
Manual mode — you invoke each skill:
|
|
9
11
|
|
|
@@ -54,12 +56,15 @@ PRD (optional) --> /project-orchestrate
|
|
|
54
56
|
- [Autonomous Orchestration](#autonomous-orchestration)
|
|
55
57
|
- [Customization](#customization)
|
|
56
58
|
- [Tips](#tips)
|
|
59
|
+
- [Acknowledgments](#acknowledgments)
|
|
57
60
|
- [License](#license)
|
|
58
61
|
|
|
59
62
|
---
|
|
60
63
|
|
|
61
64
|
## Installation
|
|
62
65
|
|
|
66
|
+
> **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`.
|
|
67
|
+
|
|
63
68
|
### npm (recommended)
|
|
64
69
|
|
|
65
70
|
```bash
|
|
@@ -346,9 +351,13 @@ Create a fine-grained Personal Access Token:
|
|
|
346
351
|
| **project-emulate** | `/project-emulate` | Integration seams, layer contracts, cross-service payloads, full lifecycle walkthrough |
|
|
347
352
|
| **project-orchestrate** | `/project-orchestrate [prd] [repo]` | Autonomous lifecycle driver |
|
|
348
353
|
| **project-cleanup** | `/project-cleanup [path] [--fix]` | Build, lint, dead code, and test coverage |
|
|
354
|
+
| **design-patterns** | `/design-patterns` | Gang of Four pattern guidance, counterweight-first; composed by `spec` (design) and `project-orchestrate` (core-domain implementation) |
|
|
355
|
+
| **domain-modeling** | `/domain-modeling` | Tactical Domain-Driven Design guidance; composed by `spec` and `project-orchestrate` for core-domain epics |
|
|
349
356
|
|
|
350
357
|
The `[owner/repo]` argument is only needed in GitHub mode. Jira, Trello, and local modes read from config.
|
|
351
358
|
|
|
359
|
+
`design-patterns` and `domain-modeling` are **situational guidance** layered on top of the always-on engineering baseline (`skills/shared/references/ENGINEERING_BASELINE.md`). They are pulled in automatically by `spec` and `project-orchestrate` for `core`-classified subdomains, and are also invocable directly. See [Acknowledgments](#acknowledgments) for their sources.
|
|
360
|
+
|
|
352
361
|
---
|
|
353
362
|
|
|
354
363
|
## Branch Strategy
|
|
@@ -482,6 +491,59 @@ Run `/project-scaffold` with a new PRD — it detects the existing project and o
|
|
|
482
491
|
|
|
483
492
|
---
|
|
484
493
|
|
|
494
|
+
## Architecture (v2.0.0+)
|
|
495
|
+
|
|
496
|
+
claudescrumskill ships in two cooperating layers. The split is documented in detail in [ADR-0003](docs/adrs/0003-workflow-backed-re-plumbing.md).
|
|
497
|
+
|
|
498
|
+
```
|
|
499
|
+
┌────────────────────────────────────────────────────────────┐
|
|
500
|
+
│ Skills — markdown SKILL.md (the opinion + the surface) │
|
|
501
|
+
│ - Slash commands users type │
|
|
502
|
+
│ - Phase / step structure │
|
|
503
|
+
│ - Durable repo artifacts (state files, ADRs, CONTEXT.md) │
|
|
504
|
+
│ - Installed at ~/.claude/skills/<skill>/SKILL.md │
|
|
505
|
+
└──────────────────────┬─────────────────────────────────────┘
|
|
506
|
+
│ invokes via Workflow tool
|
|
507
|
+
▼
|
|
508
|
+
┌────────────────────────────────────────────────────────────┐
|
|
509
|
+
│ Workflows — JavaScript at lib/workflows/ (the substrate) │
|
|
510
|
+
│ - Fan-out (parallel, pipeline) up to 16 concurrent agents │
|
|
511
|
+
│ - Schema-validated structured returns │
|
|
512
|
+
│ - Journal-based in-session resume │
|
|
513
|
+
│ - Installed at ~/.claude/skills/_workflows/ │
|
|
514
|
+
└──────────────────────┬─────────────────────────────────────┘
|
|
515
|
+
│ spawns
|
|
516
|
+
▼
|
|
517
|
+
┌────────────────────────────────────────────────────────────┐
|
|
518
|
+
│ Agents — the workers that implement stories, review │
|
|
519
|
+
│ diffs, verify findings, etc. │
|
|
520
|
+
└────────────────────────────────────────────────────────────┘
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
### Workflow scripts shipped in v2.0.0
|
|
524
|
+
|
|
525
|
+
| Script | Purpose | Used by |
|
|
526
|
+
|--------|---------|---------|
|
|
527
|
+
| `sprint_pipeline.js` | per-story pipeline: implement → review → verify → openPR | `/project-orchestrate` Phase 1 Step 3 |
|
|
528
|
+
| `elaborate_epics.js` | Pass 2 of two-pass scaffolding, in parallel | `/project-scaffold` |
|
|
529
|
+
| `adversarial_verify.js` | claimant/skeptic/judge per emulation finding | `/project-emulate` |
|
|
530
|
+
| `review_panel.js` | multi-lens (correctness/security/style/tests) review | `/project-cleanup`, `/code-review` |
|
|
531
|
+
|
|
532
|
+
### Schemas at `lib/workflows/schemas/`
|
|
533
|
+
|
|
534
|
+
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.
|
|
535
|
+
|
|
536
|
+
### Adding a workflow
|
|
537
|
+
|
|
538
|
+
Contributors adding fan-out heavy skills (large parallel work, multi-stage pipelines, judge panels) should:
|
|
539
|
+
|
|
540
|
+
1. Author the workflow at `lib/workflows/<name>.js` per the patterns in existing scripts.
|
|
541
|
+
2. Add any new schemas to `lib/workflows/schemas/`.
|
|
542
|
+
3. Reference the workflow from the skill markdown via the Path Resolution Algorithm documented in each affected SKILL.md (`<skills-root>/_workflows/<name>.js`).
|
|
543
|
+
4. Bump the package version per SemVer (new workflow + new schema → minor; breaking schema change → major).
|
|
544
|
+
|
|
545
|
+
---
|
|
546
|
+
|
|
485
547
|
## Shared References
|
|
486
548
|
|
|
487
549
|
All skills reference shared configuration and standards from `skills/shared/`:
|
|
@@ -510,6 +572,29 @@ skills/shared/
|
|
|
510
572
|
|
|
511
573
|
---
|
|
512
574
|
|
|
575
|
+
## Acknowledgments
|
|
576
|
+
|
|
577
|
+
The engineering guidance in this package — the always-on baseline plus the
|
|
578
|
+
`design-patterns` and `domain-modeling` skills — is grounded in the principles of
|
|
579
|
+
four foundational software texts. The guidance is expressed in this project's own
|
|
580
|
+
words; these works are credited as its intellectual sources. Their authors are not
|
|
581
|
+
affiliated with and do not endorse this project.
|
|
582
|
+
|
|
583
|
+
- **Robert C. Martin**, *Clean Code: A Handbook of Agile Software Craftsmanship*
|
|
584
|
+
(Prentice Hall, 2008) — the engineering baseline's naming, function, comment, and
|
|
585
|
+
code-quality principles.
|
|
586
|
+
- **Kent Beck**, *Test-Driven Development: By Example* (Addison-Wesley, 2002) — the
|
|
587
|
+
baseline's red/green/refactor discipline and simple-design rules.
|
|
588
|
+
- **Eric Evans**, *Domain-Driven Design: Tackling Complexity in the Heart of
|
|
589
|
+
Software* (Addison-Wesley, 2003) — the `domain-modeling` skill's tactical
|
|
590
|
+
building blocks and ubiquitous-language guidance.
|
|
591
|
+
- **Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides** ("the Gang of
|
|
592
|
+
Four"), *Design Patterns: Elements of Reusable Object-Oriented Software*
|
|
593
|
+
(Addison-Wesley, 1994) — the `design-patterns` skill's pattern catalog.
|
|
594
|
+
|
|
595
|
+
If you find this guidance useful, read the originals — they go far deeper than any
|
|
596
|
+
distillation can.
|
|
597
|
+
|
|
513
598
|
## License
|
|
514
599
|
|
|
515
600
|
Apache 2.0 — See [LICENSE](LICENSE) for details.
|
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
|
+
}
|