@houseofwolvesllc/claude-scrum-skill 1.8.0 → 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 CHANGED
@@ -60,18 +60,24 @@ 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
66
68
  # Global install — available in all projects
67
69
  npm install -g @houseofwolvesllc/claude-scrum-skill
68
70
 
69
- # Local install — this project only
70
- npm install @houseofwolvesllc/claude-scrum-skill
71
+ # Local install — this project only (developer tooling → devDependencies)
72
+ npm install --save-dev @houseofwolvesllc/claude-scrum-skill
73
+ # or the shorthand:
74
+ npm install -D @houseofwolvesllc/claude-scrum-skill
71
75
  ```
72
76
 
73
77
  Global install copies skills to `~/.claude/skills/`. Local install copies them to `<project>/.claude/skills/` and adds `.claude-scrum-skill` to your `.gitignore`.
74
78
 
79
+ > **Why `--save-dev`?** This package is developer tooling — the skills are consumed by Claude Code at planning/build/iteration time, never by your application's runtime. Saving to `devDependencies` (instead of `dependencies`) keeps it out of production installs (`npm ci --production`, `NODE_ENV=production`, Docker production layers), avoids running the postinstall script in environments where `~/.claude/skills/` doesn't exist, and accurately reflects that the package isn't a runtime requirement. Same category as `eslint`, `prettier`, `vitest`, etc.
80
+
75
81
  Skills surface in Claude Code with their plain names: `/project-scaffold`, `/project-orchestrate`, `/sprint-plan`, etc.
76
82
 
77
83
  > **Heads-up on re-installs:** the postinstall script overwrites every file under `<install-dir>/skills/` with the package's shipped version. If you've customized `<install-dir>/skills/shared/config.json` (e.g., changed `paths.specs` or `paths.adr`), back it up before re-running `npm install` and restore your values afterward. Skill files outside the shipped set are left alone — only files that exist in the package are overwritten.
@@ -478,6 +484,59 @@ Run `/project-scaffold` with a new PRD — it detects the existing project and o
478
484
 
479
485
  ---
480
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
+
481
540
  ## Shared References
482
541
 
483
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
+ }