@friedbotstudio/create-baseline 0.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/LICENSE +202 -0
- package/README.md +222 -0
- package/bin/cli.js +247 -0
- package/obj/template/.claude/agents/swarm-worker.md +52 -0
- package/obj/template/.claude/bin/LICENSE +201 -0
- package/obj/template/.claude/bin/NOTICE +48 -0
- package/obj/template/.claude/commands/approve-spec.md +29 -0
- package/obj/template/.claude/commands/approve-swarm.md +27 -0
- package/obj/template/.claude/commands/grant-commit.md +19 -0
- package/obj/template/.claude/commands/init-project.md +191 -0
- package/obj/template/.claude/hooks/artifact_template_guard.sh +141 -0
- package/obj/template/.claude/hooks/consent_gate_grant.sh +89 -0
- package/obj/template/.claude/hooks/destructive_cmd_guard.sh +42 -0
- package/obj/template/.claude/hooks/env_guard.sh +36 -0
- package/obj/template/.claude/hooks/git_commit_guard.sh +93 -0
- package/obj/template/.claude/hooks/harness_continuation.sh +121 -0
- package/obj/template/.claude/hooks/lib/__pycache__/resume_writer.cpython-314.pyc +0 -0
- package/obj/template/.claude/hooks/lib/common.sh +328 -0
- package/obj/template/.claude/hooks/lib/resume_writer.py +341 -0
- package/obj/template/.claude/hooks/lint_runner.sh +55 -0
- package/obj/template/.claude/hooks/memory_pre_compact.sh +36 -0
- package/obj/template/.claude/hooks/memory_session_start.sh +244 -0
- package/obj/template/.claude/hooks/memory_stop.sh +173 -0
- package/obj/template/.claude/hooks/plantuml_syntax_guard.sh +161 -0
- package/obj/template/.claude/hooks/process_lifecycle_guard.sh +89 -0
- package/obj/template/.claude/hooks/setup_guard.sh +50 -0
- package/obj/template/.claude/hooks/spec_approval_guard.sh +81 -0
- package/obj/template/.claude/hooks/spec_design_calls_guard.sh +183 -0
- package/obj/template/.claude/hooks/spec_diagram_presence_guard.sh +141 -0
- package/obj/template/.claude/hooks/swarm_approval_guard.sh +39 -0
- package/obj/template/.claude/hooks/swarm_boundary_guard.sh +136 -0
- package/obj/template/.claude/hooks/tdd_order_guard.sh +176 -0
- package/obj/template/.claude/hooks/test_runner.sh +75 -0
- package/obj/template/.claude/hooks/tests/fixtures/ac008_byte_equal_reference.txt +12 -0
- package/obj/template/.claude/hooks/tests/memory_session_start_test.sh +285 -0
- package/obj/template/.claude/hooks/track_guard.sh +127 -0
- package/obj/template/.claude/hooks/verify_pass_guard.sh +88 -0
- package/obj/template/.claude/memory/README.md +108 -0
- package/obj/template/.claude/memory/_pending.md +15 -0
- package/obj/template/.claude/memory/_resume.md +12 -0
- package/obj/template/.claude/memory/conventions.md +26 -0
- package/obj/template/.claude/memory/decisions.md +29 -0
- package/obj/template/.claude/memory/landmarks.md +26 -0
- package/obj/template/.claude/memory/landmines.md +27 -0
- package/obj/template/.claude/memory/libraries.md +27 -0
- package/obj/template/.claude/memory/pending-questions.md +28 -0
- package/obj/template/.claude/project.json +221 -0
- package/obj/template/.claude/settings.json +110 -0
- package/obj/template/.claude/skills/archive/SKILL.md +48 -0
- package/obj/template/.claude/skills/archive/archive.sh +145 -0
- package/obj/template/.claude/skills/audit-baseline/SKILL.md +80 -0
- package/obj/template/.claude/skills/audit-baseline/audit.sh +919 -0
- package/obj/template/.claude/skills/brd/SKILL.md +44 -0
- package/obj/template/.claude/skills/brd/template.md +83 -0
- package/obj/template/.claude/skills/chore/SKILL.md +99 -0
- package/obj/template/.claude/skills/claude-automation-recommender/LICENSE +202 -0
- package/obj/template/.claude/skills/claude-automation-recommender/NOTICE +69 -0
- package/obj/template/.claude/skills/claude-automation-recommender/SKILL.md +358 -0
- package/obj/template/.claude/skills/claude-automation-recommender/references/hooks-patterns.md +226 -0
- package/obj/template/.claude/skills/claude-automation-recommender/references/mcp-servers.md +263 -0
- package/obj/template/.claude/skills/claude-automation-recommender/references/plugins-reference.md +98 -0
- package/obj/template/.claude/skills/claude-automation-recommender/references/skills-reference.md +408 -0
- package/obj/template/.claude/skills/claude-automation-recommender/references/subagent-templates.md +181 -0
- package/obj/template/.claude/skills/code-structure/SKILL.md +204 -0
- package/obj/template/.claude/skills/commit/SKILL.md +21 -0
- package/obj/template/.claude/skills/copywriting/SKILL.md +252 -0
- package/obj/template/.claude/skills/copywriting/evals/evals.json +111 -0
- package/obj/template/.claude/skills/copywriting/references/ai-writing-detection.md +200 -0
- package/obj/template/.claude/skills/copywriting/references/copy-frameworks.md +344 -0
- package/obj/template/.claude/skills/copywriting/references/natural-transitions.md +272 -0
- package/obj/template/.claude/skills/design-ui/SKILL.md +175 -0
- package/obj/template/.claude/skills/design-ui/references/design-vs-development.md +89 -0
- package/obj/template/.claude/skills/design-ui/references/intent-table.md +64 -0
- package/obj/template/.claude/skills/design-ui/references/orchestration.md +121 -0
- package/obj/template/.claude/skills/design-ui/references/state-machine.md +125 -0
- package/obj/template/.claude/skills/document/SKILL.md +66 -0
- package/obj/template/.claude/skills/documentation/SKILL.md +50 -0
- package/obj/template/.claude/skills/harness/SKILL.md +169 -0
- package/obj/template/.claude/skills/humanizer/SKILL.md +489 -0
- package/obj/template/.claude/skills/humanizer/references/ai-writing-detection.md +208 -0
- package/obj/template/.claude/skills/impeccable/PROJECT_NOTES.md +22 -0
- package/obj/template/.claude/skills/impeccable/SKILL.md +153 -0
- package/obj/template/.claude/skills/impeccable/agents/openai.yaml +4 -0
- package/obj/template/.claude/skills/impeccable/reference/adapt.md +190 -0
- package/obj/template/.claude/skills/impeccable/reference/animate.md +173 -0
- package/obj/template/.claude/skills/impeccable/reference/audit.md +134 -0
- package/obj/template/.claude/skills/impeccable/reference/bolder.md +113 -0
- package/obj/template/.claude/skills/impeccable/reference/brand.md +104 -0
- package/obj/template/.claude/skills/impeccable/reference/clarify.md +174 -0
- package/obj/template/.claude/skills/impeccable/reference/cognitive-load.md +106 -0
- package/obj/template/.claude/skills/impeccable/reference/color-and-contrast.md +105 -0
- package/obj/template/.claude/skills/impeccable/reference/colorize.md +154 -0
- package/obj/template/.claude/skills/impeccable/reference/craft.md +138 -0
- package/obj/template/.claude/skills/impeccable/reference/critique.md +213 -0
- package/obj/template/.claude/skills/impeccable/reference/delight.md +302 -0
- package/obj/template/.claude/skills/impeccable/reference/distill.md +111 -0
- package/obj/template/.claude/skills/impeccable/reference/document.md +427 -0
- package/obj/template/.claude/skills/impeccable/reference/extract.md +70 -0
- package/obj/template/.claude/skills/impeccable/reference/harden.md +347 -0
- package/obj/template/.claude/skills/impeccable/reference/heuristics-scoring.md +234 -0
- package/obj/template/.claude/skills/impeccable/reference/interaction-design.md +195 -0
- package/obj/template/.claude/skills/impeccable/reference/layout.md +141 -0
- package/obj/template/.claude/skills/impeccable/reference/live.md +513 -0
- package/obj/template/.claude/skills/impeccable/reference/motion-design.md +99 -0
- package/obj/template/.claude/skills/impeccable/reference/onboard.md +234 -0
- package/obj/template/.claude/skills/impeccable/reference/optimize.md +258 -0
- package/obj/template/.claude/skills/impeccable/reference/overdrive.md +130 -0
- package/obj/template/.claude/skills/impeccable/reference/personas.md +178 -0
- package/obj/template/.claude/skills/impeccable/reference/polish.md +232 -0
- package/obj/template/.claude/skills/impeccable/reference/product.md +62 -0
- package/obj/template/.claude/skills/impeccable/reference/quieter.md +99 -0
- package/obj/template/.claude/skills/impeccable/reference/responsive-design.md +114 -0
- package/obj/template/.claude/skills/impeccable/reference/shape.md +136 -0
- package/obj/template/.claude/skills/impeccable/reference/spatial-design.md +100 -0
- package/obj/template/.claude/skills/impeccable/reference/teach.md +137 -0
- package/obj/template/.claude/skills/impeccable/reference/typeset.md +124 -0
- package/obj/template/.claude/skills/impeccable/reference/typography.md +159 -0
- package/obj/template/.claude/skills/impeccable/reference/ux-writing.md +107 -0
- package/obj/template/.claude/skills/impeccable/scripts/cleanup-deprecated.mjs +284 -0
- package/obj/template/.claude/skills/impeccable/scripts/command-metadata.json +94 -0
- package/obj/template/.claude/skills/impeccable/scripts/design-parser.mjs +820 -0
- package/obj/template/.claude/skills/impeccable/scripts/detect-csp.mjs +198 -0
- package/obj/template/.claude/skills/impeccable/scripts/is-generated.mjs +69 -0
- package/obj/template/.claude/skills/impeccable/scripts/live-accept.mjs +465 -0
- package/obj/template/.claude/skills/impeccable/scripts/live-browser.js +4684 -0
- package/obj/template/.claude/skills/impeccable/scripts/live-inject.mjs +436 -0
- package/obj/template/.claude/skills/impeccable/scripts/live-poll.mjs +187 -0
- package/obj/template/.claude/skills/impeccable/scripts/live-server.mjs +679 -0
- package/obj/template/.claude/skills/impeccable/scripts/live-wrap.mjs +395 -0
- package/obj/template/.claude/skills/impeccable/scripts/live.mjs +247 -0
- package/obj/template/.claude/skills/impeccable/scripts/load-context.mjs +93 -0
- package/obj/template/.claude/skills/impeccable/scripts/modern-screenshot.umd.js +14 -0
- package/obj/template/.claude/skills/impeccable/scripts/pin.mjs +214 -0
- package/obj/template/.claude/skills/implement/SKILL.md +83 -0
- package/obj/template/.claude/skills/intake/SKILL.md +46 -0
- package/obj/template/.claude/skills/intake/template.md +61 -0
- package/obj/template/.claude/skills/integrate/SKILL.md +62 -0
- package/obj/template/.claude/skills/memory-flush/SKILL.md +172 -0
- package/obj/template/.claude/skills/memory-flush/sweep.py +286 -0
- package/obj/template/.claude/skills/memory-flush/tests/run.sh +327 -0
- package/obj/template/.claude/skills/prose/SKILL.md +119 -0
- package/obj/template/.claude/skills/rca/SKILL.md +42 -0
- package/obj/template/.claude/skills/rca/template.md +83 -0
- package/obj/template/.claude/skills/research/SKILL.md +75 -0
- package/obj/template/.claude/skills/scenario/SKILL.md +64 -0
- package/obj/template/.claude/skills/scout/SKILL.md +72 -0
- package/obj/template/.claude/skills/security/SKILL.md +75 -0
- package/obj/template/.claude/skills/simplify/SKILL.md +67 -0
- package/obj/template/.claude/skills/spec/SKILL.md +69 -0
- package/obj/template/.claude/skills/spec/template.md +274 -0
- package/obj/template/.claude/skills/spec-diagram-review/SKILL.md +81 -0
- package/obj/template/.claude/skills/spec-lint/SKILL.md +55 -0
- package/obj/template/.claude/skills/spec-lint/lint.sh +218 -0
- package/obj/template/.claude/skills/spec-render/SKILL.md +45 -0
- package/obj/template/.claude/skills/spec-render/render.sh +109 -0
- package/obj/template/.claude/skills/spec-traceability-review/SKILL.md +72 -0
- package/obj/template/.claude/skills/swarm-dispatch/SKILL.md +212 -0
- package/obj/template/.claude/skills/swarm-dispatch/swarm_merge.sh +154 -0
- package/obj/template/.claude/skills/swarm-plan/SKILL.md +90 -0
- package/obj/template/.claude/skills/swarm-plan/validate.sh +181 -0
- package/obj/template/.claude/skills/tdd/SKILL.md +100 -0
- package/obj/template/.claude/skills/technical-tutorials/SKILL.md +569 -0
- package/obj/template/.claude/skills/technical-tutorials/references/audience-context-README.md +53 -0
- package/obj/template/.claude/skills/technical-tutorials/references/audience-context.md +246 -0
- package/obj/template/.claude/skills/technical-tutorials/references/audience-example.md +175 -0
- package/obj/template/.claude/skills/technical-tutorials/references/audience-template.md +152 -0
- package/obj/template/.claude/skills/triage/SKILL.md +55 -0
- package/obj/template/.claude/skills/verify/SKILL.md +74 -0
- package/obj/template/.mcp.json +24 -0
- package/obj/template/CLAUDE.md +327 -0
- package/obj/template/docs/init/seed.md +585 -0
- package/obj/template/manifest.json +214 -0
- package/package.json +48 -0
- package/src/.mcp.template.json +24 -0
- package/src/.npmrc.template +2 -0
- package/src/CLAUDE.template.md +327 -0
- package/src/agents/swarm-worker.template.md +51 -0
- package/src/cli/conflict.js +31 -0
- package/src/cli/doctor.js +152 -0
- package/src/cli/install.js +93 -0
- package/src/cli/io.js +27 -0
- package/src/cli/manifest.js +38 -0
- package/src/cli/mcp.js +54 -0
- package/src/cli/merge.js +107 -0
- package/src/cli/plantuml.js +121 -0
- package/src/cli/util.js +10 -0
- package/src/memory/_pending.template.md +15 -0
- package/src/memory/_resume.template.md +12 -0
- package/src/memory/conventions.template.md +26 -0
- package/src/memory/decisions.template.md +29 -0
- package/src/memory/landmarks.template.md +26 -0
- package/src/memory/landmines.template.md +27 -0
- package/src/memory/libraries.template.md +27 -0
- package/src/memory/pending-questions.template.md +28 -0
- package/src/project.template.json +221 -0
- package/src/seed.template.md +585 -0
- package/src/settings.template.json +110 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: spec-diagram-review
|
|
3
|
+
owner: baseline
|
|
4
|
+
description: Cross-consistency review of a drafted spec's diagrams. Verifies that C4 components appear in the dependency graph, class-diagram changes have matching DDL, every AC resolves to a concrete sequence, and the dependency graph is acyclic. Read-only. Run after `/spec-lint` passes and before `/approve-spec`.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You are auditing whether the diagrams inside `docs/specs/<slug>.md` tell a **consistent** story. The hooks and `/spec-lint` already guarantee each diagram parses and required kinds are present — your job is to catch *semantic* drift between diagrams.
|
|
8
|
+
|
|
9
|
+
# Inputs
|
|
10
|
+
|
|
11
|
+
- The spec: `docs/specs/<slug>.md` (caller passes the slug or path).
|
|
12
|
+
- Optional: `docs/scout/<slug>.md` — reveals whether component names match actual code paths.
|
|
13
|
+
|
|
14
|
+
You do not write files. Your output is an advisory report.
|
|
15
|
+
|
|
16
|
+
# Method
|
|
17
|
+
|
|
18
|
+
Walk the spec end-to-end, then run the five checks. Report every finding with a precise pointer (`§<section> line <n>` or `block #<N>`).
|
|
19
|
+
|
|
20
|
+
## Check 1 — Container ↔ Component consistency
|
|
21
|
+
|
|
22
|
+
- Every `Container(id, "Name", ...)` in the C4 Container diagram either:
|
|
23
|
+
(a) has a matching `Container_Boundary(id, ...)` with a Component diagram, or
|
|
24
|
+
(b) is annotated as "unchanged" in prose.
|
|
25
|
+
- Every `Component(id, ...)` lives inside a `Container_Boundary` whose id exists in the Container diagram.
|
|
26
|
+
|
|
27
|
+
## Check 2 — Components ↔ Dependency graph
|
|
28
|
+
|
|
29
|
+
- Every component/container id referenced in a Component diagram's `Rel(...)` appears as a node in the dependency graph (`[id]`).
|
|
30
|
+
- Every node in the dependency graph corresponds to a component/container in the C4 diagrams or is labelled in Contracts as an external dependency.
|
|
31
|
+
|
|
32
|
+
## Check 3 — Dependency graph is acyclic
|
|
33
|
+
|
|
34
|
+
- Parse the `' @kind dependency-graph` block. Build a directed graph from `[a] --> [b]` edges.
|
|
35
|
+
- If any cycle exists, surface the cycle path as **Critical**. A cycle means the design has a deadlock — it must be resolved or explicitly justified under Open questions.
|
|
36
|
+
|
|
37
|
+
## Check 4 — Class diagram ↔ Migration DDL
|
|
38
|
+
|
|
39
|
+
- For each field marked `<<new>>` on a class, there must be a matching `ALTER TABLE ... ADD COLUMN` in the migration DDL block.
|
|
40
|
+
- For each field marked `<<changed>>`, there must be a matching `ALTER ... ALTER COLUMN` or equivalent.
|
|
41
|
+
- For each `ALTER TABLE ... ADD COLUMN`, the corresponding class must declare the field with a `<<new>>` stereotype.
|
|
42
|
+
- Every forward DDL must have a paired reverse DDL in the same block.
|
|
43
|
+
|
|
44
|
+
## Check 5 — ACs ↔ Sequences
|
|
45
|
+
|
|
46
|
+
- Every row in the Acceptance criteria table (`AC-NNN`) must reference a sequence via `§Behavior #N`.
|
|
47
|
+
- The referenced sequence block must exist and contain the promised interaction (method names in the AC should appear as arrow labels in the sequence).
|
|
48
|
+
- No orphan sequences: every `title Behavior #N` block should be referenced by at least one AC row.
|
|
49
|
+
|
|
50
|
+
# Output
|
|
51
|
+
|
|
52
|
+
Plain markdown, no code-fence wrapper. Severity: **Critical** (blocks approval), **Major** (should fix), **Minor** (advisory).
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
# Spec Diagram Review — <slug>
|
|
56
|
+
|
|
57
|
+
## Critical
|
|
58
|
+
- <finding with pointer>
|
|
59
|
+
|
|
60
|
+
## Major
|
|
61
|
+
- <finding with pointer>
|
|
62
|
+
|
|
63
|
+
## Minor
|
|
64
|
+
- <finding with pointer>
|
|
65
|
+
|
|
66
|
+
## Summary
|
|
67
|
+
- Container ↔ Component: PASS | FAIL (<count>)
|
|
68
|
+
- Components ↔ Dependency graph: PASS | FAIL (<count>)
|
|
69
|
+
- Dependency graph acyclic: PASS | FAIL
|
|
70
|
+
- Class ↔ Migration DDL: PASS | FAIL (<count>)
|
|
71
|
+
- ACs ↔ Sequences: PASS | FAIL (<count>)
|
|
72
|
+
|
|
73
|
+
Verdict: READY FOR APPROVAL | REVISIONS REQUIRED
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
# Constraints
|
|
77
|
+
|
|
78
|
+
- Read-only. Do not call Edit, Write, or Bash beyond reads.
|
|
79
|
+
- Do not rewrite the spec or propose new diagrams. Name the inconsistency; the author fixes it.
|
|
80
|
+
- If a check cannot run (e.g., no class diagram block), say so — do not fail silently and do not guess.
|
|
81
|
+
- Keep the report under ~150 lines. Long reports get skimmed; tight ones get acted on.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: spec-lint
|
|
3
|
+
owner: baseline
|
|
4
|
+
description: Preflight a spec draft without saving. Runs the same three checks as the write-boundary hooks — PlantUML syntax, required diagram presence, and AC-to-sequence traceability — and prints a compact pass/fail table. Use while iterating so the hooks don't bite on save.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# spec-lint — preflight a spec draft
|
|
8
|
+
|
|
9
|
+
Invocable by both user (`/spec-lint <slug>`) and Claude (when iterating on a spec and wanting to check status before writing).
|
|
10
|
+
|
|
11
|
+
## What it checks
|
|
12
|
+
|
|
13
|
+
Three checks, same logic as the hooks, but advisory (no writes are blocked):
|
|
14
|
+
|
|
15
|
+
| # | Check | Hook it mirrors |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| 1 | Every ```plantuml``` fence parses under `plantuml -checkonly` | `plantuml_syntax_guard` |
|
|
18
|
+
| 2 | Required diagram kinds present (config: `project.json → artifacts.required_diagrams.spec`) | `spec_diagram_presence_guard` |
|
|
19
|
+
| 3 | Every `AC-NNN` row in the Acceptance criteria table references a `§Behavior #N` section that exists | (no hook — unique to the lint) |
|
|
20
|
+
|
|
21
|
+
## Invocation
|
|
22
|
+
|
|
23
|
+
`/spec-lint <slug>` — where `<slug>` corresponds to `docs/specs/<slug>.md`.
|
|
24
|
+
|
|
25
|
+
## Steps
|
|
26
|
+
|
|
27
|
+
1. Validate the slug: `docs/specs/<slug>.md` must exist.
|
|
28
|
+
2. Run:
|
|
29
|
+
```
|
|
30
|
+
.claude/skills/spec-lint/lint.sh <slug>
|
|
31
|
+
```
|
|
32
|
+
3. Print the script's output verbatim to the user. It is a table with one row per check and a final summary line.
|
|
33
|
+
|
|
34
|
+
## Output format
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
check status
|
|
38
|
+
---------------------------------- ------
|
|
39
|
+
plantuml_syntax PASS
|
|
40
|
+
diagram_presence FAIL (missing: c4_component, dependency_graph)
|
|
41
|
+
ac_traceability FAIL (AC-002 → §Behavior #2 not found)
|
|
42
|
+
---------------------------------- ------
|
|
43
|
+
overall FAIL
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Exit 0 on overall PASS, 1 on overall FAIL. Intended for use in CI or a pre-commit loop as well as interactively.
|
|
47
|
+
|
|
48
|
+
## Prerequisites
|
|
49
|
+
|
|
50
|
+
- `plantuml` CLI on PATH for check #1 (if absent, #1 is reported as `SKIP (no plantuml)`; #2 and #3 still run).
|
|
51
|
+
|
|
52
|
+
## Notes
|
|
53
|
+
|
|
54
|
+
- Unlike the hooks, `spec-lint` runs against the on-disk file, not proposed content. Save or use the hooks to validate an unsaved draft.
|
|
55
|
+
- `spec-lint` does not render. Use `/spec-render <slug>` for that.
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# spec-lint — run the three diagram-spec checks against a saved spec.
|
|
3
|
+
# Usage: .claude/skills/spec-lint/lint.sh <slug>
|
|
4
|
+
|
|
5
|
+
set -u
|
|
6
|
+
|
|
7
|
+
if [ "${1:-}" = "" ]; then
|
|
8
|
+
echo "usage: lint.sh <slug>" >&2
|
|
9
|
+
exit 2
|
|
10
|
+
fi
|
|
11
|
+
SLUG="$1"
|
|
12
|
+
|
|
13
|
+
ROOT="${CLAUDE_PROJECT_DIR:-$(pwd)}"
|
|
14
|
+
SPEC="$ROOT/docs/specs/$SLUG.md"
|
|
15
|
+
PROJECT_JSON="$ROOT/.claude/project.json"
|
|
16
|
+
|
|
17
|
+
if [ ! -f "$SPEC" ]; then
|
|
18
|
+
echo "spec-lint: spec not found at $SPEC" >&2
|
|
19
|
+
exit 2
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
HAS_PLANTUML=0
|
|
23
|
+
command -v plantuml >/dev/null 2>&1 && HAS_PLANTUML=1
|
|
24
|
+
|
|
25
|
+
SPEC="$SPEC" PROJECT_JSON="$PROJECT_JSON" HAS_PLANTUML="$HAS_PLANTUML" SLUG="$SLUG" python3 <<'PY'
|
|
26
|
+
import json, os, re, subprocess, sys
|
|
27
|
+
|
|
28
|
+
spec_path = os.environ["SPEC"]
|
|
29
|
+
pj_path = os.environ["PROJECT_JSON"]
|
|
30
|
+
has_puml = os.environ.get("HAS_PLANTUML") == "1"
|
|
31
|
+
spec = open(spec_path, encoding="utf-8").read()
|
|
32
|
+
|
|
33
|
+
fence_re = re.compile(r'^[ \t]*```[ \t]*plantuml[ \t]*$(.*?)^[ \t]*```[ \t]*$',
|
|
34
|
+
re.DOTALL | re.IGNORECASE | re.MULTILINE)
|
|
35
|
+
blocks = [m.group(1) for m in fence_re.finditer(spec)]
|
|
36
|
+
|
|
37
|
+
def check_syntax():
|
|
38
|
+
if not has_puml:
|
|
39
|
+
return "SKIP", "plantuml CLI not on PATH"
|
|
40
|
+
if not blocks:
|
|
41
|
+
return "PASS", "no blocks"
|
|
42
|
+
bad = []
|
|
43
|
+
for i, body in enumerate(blocks, start=1):
|
|
44
|
+
src = body.strip("\n")
|
|
45
|
+
if "@startuml" not in src:
|
|
46
|
+
src = "@startuml\n" + src + "\n@enduml\n"
|
|
47
|
+
try:
|
|
48
|
+
r = subprocess.run(["plantuml", "-checkonly", "-pipe"],
|
|
49
|
+
input=src.encode(), capture_output=True, timeout=15)
|
|
50
|
+
except Exception as e:
|
|
51
|
+
bad.append(f"block #{i}: {e}")
|
|
52
|
+
continue
|
|
53
|
+
if r.returncode != 0:
|
|
54
|
+
err = (r.stderr or r.stdout or b"").decode(errors="replace").strip().splitlines()
|
|
55
|
+
bad.append(f"block #{i}: {' | '.join(err[-2:]) if err else 'exit ' + str(r.returncode)}")
|
|
56
|
+
return ("PASS", "all blocks parse") if not bad else ("FAIL", "; ".join(bad))
|
|
57
|
+
|
|
58
|
+
def check_presence():
|
|
59
|
+
try:
|
|
60
|
+
pj = json.load(open(pj_path))
|
|
61
|
+
required = pj["artifacts"]["required_diagrams"]["spec"]
|
|
62
|
+
except Exception:
|
|
63
|
+
return "SKIP", "required_diagrams.spec not configured"
|
|
64
|
+
missing = []
|
|
65
|
+
for kind, rule in required.items():
|
|
66
|
+
need = int(rule.get("min", 1))
|
|
67
|
+
marker = rule.get("marker")
|
|
68
|
+
any_of = rule.get("any_of") or []
|
|
69
|
+
found = 0
|
|
70
|
+
for b in blocks:
|
|
71
|
+
if marker and marker in b:
|
|
72
|
+
found += 1; continue
|
|
73
|
+
for pat in any_of:
|
|
74
|
+
try:
|
|
75
|
+
if re.search(pat, b, re.MULTILINE):
|
|
76
|
+
found += 1; break
|
|
77
|
+
except re.error:
|
|
78
|
+
continue
|
|
79
|
+
if found < need:
|
|
80
|
+
missing.append(f"{kind} (need {need}, found {found})")
|
|
81
|
+
return ("PASS", "all kinds present") if not missing else ("FAIL", "missing: " + ", ".join(missing))
|
|
82
|
+
|
|
83
|
+
def check_traceability():
|
|
84
|
+
# Find AC rows: table cells starting with AC-NNN in the Acceptance criteria section.
|
|
85
|
+
ac_section_re = re.compile(r'##\s+Acceptance criteria(.*?)(?=^##\s|\Z)', re.DOTALL | re.MULTILINE)
|
|
86
|
+
m = ac_section_re.search(spec)
|
|
87
|
+
if not m:
|
|
88
|
+
return "FAIL", "no '## Acceptance criteria' section"
|
|
89
|
+
section = m.group(1)
|
|
90
|
+
# Rows like: | AC-001 | ... | ... | §Behavior #1 |
|
|
91
|
+
row_re = re.compile(r'\|\s*(AC-\d+)\s*\|.*?\|\s*(§?Behavior\s*#?\s*\d+|§Behavior\s*#\d+|—|-)\s*\|', re.IGNORECASE)
|
|
92
|
+
rows = row_re.findall(section)
|
|
93
|
+
if not rows:
|
|
94
|
+
return "FAIL", "no AC-NNN rows with a sequence reference"
|
|
95
|
+
problems = []
|
|
96
|
+
# Extract which Behavior #N sequences actually exist: look for '### Behavior ...' or 'Behavior #N' titles and fenced sequence blocks.
|
|
97
|
+
behavior_titles = set()
|
|
98
|
+
# Accept anchors stamped inside sequence titles like `title Behavior #1 — ...`
|
|
99
|
+
for i, b in enumerate(blocks, start=1):
|
|
100
|
+
tm = re.search(r'(?im)^\s*title\s+Behavior\s*#(\d+)\b', b)
|
|
101
|
+
if tm:
|
|
102
|
+
behavior_titles.add(int(tm.group(1)))
|
|
103
|
+
# Also consider explicit ### headings like "### Behavior #N" (optional extra convention).
|
|
104
|
+
for hm in re.finditer(r'(?im)^###\s+Behavior\s*#(\d+)\b', spec):
|
|
105
|
+
behavior_titles.add(int(hm.group(1)))
|
|
106
|
+
|
|
107
|
+
for ac_id, ref in rows:
|
|
108
|
+
if ref.strip() in ("—", "-"):
|
|
109
|
+
problems.append(f"{ac_id}: no sequence reference")
|
|
110
|
+
continue
|
|
111
|
+
num_m = re.search(r'#\s*(\d+)', ref)
|
|
112
|
+
if not num_m:
|
|
113
|
+
problems.append(f"{ac_id}: unparsable ref '{ref.strip()}'")
|
|
114
|
+
continue
|
|
115
|
+
n = int(num_m.group(1))
|
|
116
|
+
if n not in behavior_titles:
|
|
117
|
+
problems.append(f"{ac_id}: §Behavior #{n} not found")
|
|
118
|
+
return ("PASS", f"{len(rows)} AC rows all traced") if not problems else ("FAIL", "; ".join(problems))
|
|
119
|
+
|
|
120
|
+
def _expand_brace_globs(globs):
|
|
121
|
+
# Expand {a,b,c} alternations into multiple flat globs so fnmatch can handle them.
|
|
122
|
+
out = []
|
|
123
|
+
for g in globs:
|
|
124
|
+
if "{" not in g:
|
|
125
|
+
out.append(g); continue
|
|
126
|
+
# one level of brace expansion is enough for our patterns
|
|
127
|
+
i = g.index("{"); j = g.index("}", i)
|
|
128
|
+
prefix, alts, suffix = g[:i], g[i+1:j].split(","), g[j+1:]
|
|
129
|
+
for a in alts:
|
|
130
|
+
out.append(prefix + a.strip() + suffix)
|
|
131
|
+
return out
|
|
132
|
+
|
|
133
|
+
def _glob_to_regex(g):
|
|
134
|
+
# Convert a shell-style glob to a regex anchored at full-string match.
|
|
135
|
+
# Handles `**` (any path segments incl. /), `*` (any chars except /),
|
|
136
|
+
# and `?` (one char). Everything else is escaped.
|
|
137
|
+
out = []
|
|
138
|
+
i = 0
|
|
139
|
+
while i < len(g):
|
|
140
|
+
c = g[i]
|
|
141
|
+
if c == "*":
|
|
142
|
+
if i + 1 < len(g) and g[i+1] == "*":
|
|
143
|
+
out.append(".*"); i += 2
|
|
144
|
+
else:
|
|
145
|
+
out.append("[^/]*"); i += 1
|
|
146
|
+
elif c == "?":
|
|
147
|
+
out.append("[^/]"); i += 1
|
|
148
|
+
elif c in ".+()|^$\\[]{}":
|
|
149
|
+
out.append(re.escape(c)); i += 1
|
|
150
|
+
else:
|
|
151
|
+
out.append(c); i += 1
|
|
152
|
+
return "^" + "".join(out) + "$"
|
|
153
|
+
|
|
154
|
+
def _matches_any_glob(path, globs):
|
|
155
|
+
for g in _expand_brace_globs(globs):
|
|
156
|
+
if re.fullmatch(_glob_to_regex(g), path):
|
|
157
|
+
return True
|
|
158
|
+
return False
|
|
159
|
+
|
|
160
|
+
def check_design_calls():
|
|
161
|
+
try:
|
|
162
|
+
pj = json.load(open(pj_path))
|
|
163
|
+
ui_globs = pj.get("tdd", {}).get("ui_globs", []) or []
|
|
164
|
+
except Exception:
|
|
165
|
+
return "SKIP", "tdd.ui_globs not configured"
|
|
166
|
+
if not ui_globs:
|
|
167
|
+
return "SKIP", "tdd.ui_globs is empty"
|
|
168
|
+
|
|
169
|
+
# Extract write_set paths from the spec body. Accept either a leading
|
|
170
|
+
# `write_set:` line (markdown body) or paths inside a Design calls table.
|
|
171
|
+
write_set_paths = set()
|
|
172
|
+
for line in spec.splitlines():
|
|
173
|
+
m = re.search(r'write[_\s]set\s*:\s*(.+)$', line, re.IGNORECASE)
|
|
174
|
+
if m:
|
|
175
|
+
for tok in re.split(r'[`,\s|]+', m.group(1)):
|
|
176
|
+
tok = tok.strip().strip("*").strip()
|
|
177
|
+
if tok and "/" in tok and not tok.startswith("#"):
|
|
178
|
+
write_set_paths.add(tok)
|
|
179
|
+
|
|
180
|
+
# Compute intersection of write_set with ui_globs.
|
|
181
|
+
ui_hits = [p for p in write_set_paths if _matches_any_glob(p, ui_globs)]
|
|
182
|
+
if not ui_hits:
|
|
183
|
+
return "SKIP", f"no UI files in write_set ({len(write_set_paths)} paths checked)"
|
|
184
|
+
|
|
185
|
+
# Conditional fires — design_calls section must be present AND non-empty.
|
|
186
|
+
dc_section = re.search(
|
|
187
|
+
r'^##\s+Design\s+calls\s*$([\s\S]*?)(?=^##\s|\Z)',
|
|
188
|
+
spec, re.MULTILINE | re.IGNORECASE,
|
|
189
|
+
)
|
|
190
|
+
if not dc_section:
|
|
191
|
+
return "FAIL", f"write_set has UI files ({', '.join(sorted(ui_hits))}) but no `## Design calls` section"
|
|
192
|
+
body = dc_section.group(1).strip()
|
|
193
|
+
# Empty conventions: `*(none)*`, dash placeholders, or no table rows.
|
|
194
|
+
has_table_row = bool(re.search(r'^\|[^|\n]+\|[^|\n]+\|', body, re.MULTILINE))
|
|
195
|
+
is_none_marker = bool(re.search(r'^\s*-?\s*\*?\(?none\)?\*?\s*$', body, re.MULTILINE | re.IGNORECASE))
|
|
196
|
+
if not has_table_row or is_none_marker:
|
|
197
|
+
return "FAIL", f"write_set has UI files ({', '.join(sorted(ui_hits))}) but Design calls section is empty / `*(none)*`"
|
|
198
|
+
return "PASS", f"{len(ui_hits)} UI path(s) match design_calls rows"
|
|
199
|
+
|
|
200
|
+
results = [
|
|
201
|
+
("plantuml_syntax", *check_syntax()),
|
|
202
|
+
("diagram_presence", *check_presence()),
|
|
203
|
+
("ac_traceability", *check_traceability()),
|
|
204
|
+
("design_calls", *check_design_calls()),
|
|
205
|
+
]
|
|
206
|
+
|
|
207
|
+
name_w = max(len(n) for n, _, _ in results)
|
|
208
|
+
print(f"{'check'.ljust(name_w)} {'status':<6} detail")
|
|
209
|
+
print(f"{'-'*name_w} {'-'*6} {'-'*50}")
|
|
210
|
+
overall_fail = False
|
|
211
|
+
for name, status, detail in results:
|
|
212
|
+
if status == "FAIL":
|
|
213
|
+
overall_fail = True
|
|
214
|
+
print(f"{name.ljust(name_w)} {status:<6} {detail}")
|
|
215
|
+
print(f"{'-'*name_w} {'-'*6}")
|
|
216
|
+
print(f"{'overall'.ljust(name_w)} {'FAIL' if overall_fail else 'PASS'}")
|
|
217
|
+
sys.exit(1 if overall_fail else 0)
|
|
218
|
+
PY
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: spec-render
|
|
3
|
+
owner: baseline
|
|
4
|
+
description: Extract every PlantUML block from docs/specs/<slug>.md and render each to SVG under docs/specs/_rendered/<slug>/, with an index.md listing them in order. Run this before /approve-spec so the reviewer sees pictures instead of raw PlantUML.
|
|
5
|
+
disable-model-invocation: true
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# spec-render — render a spec's diagrams for review
|
|
9
|
+
|
|
10
|
+
User-invokable only. This skill has side effects (writes SVGs and an index). Claude does not invoke it autonomously.
|
|
11
|
+
|
|
12
|
+
## Invocation
|
|
13
|
+
|
|
14
|
+
`/spec-render <slug>` — where `<slug>` corresponds to `docs/specs/<slug>.md`.
|
|
15
|
+
|
|
16
|
+
## Prerequisites
|
|
17
|
+
|
|
18
|
+
- `plantuml` CLI on PATH (`brew install plantuml` / `apt-get install plantuml`).
|
|
19
|
+
- The spec at `docs/specs/<slug>.md` exists and passes `spec_diagram_presence_guard` + `plantuml_syntax_guard` — otherwise rendering will surface the same errors.
|
|
20
|
+
|
|
21
|
+
## Steps
|
|
22
|
+
|
|
23
|
+
1. Validate the slug: `docs/specs/<slug>.md` must exist.
|
|
24
|
+
2. Run the render script:
|
|
25
|
+
```
|
|
26
|
+
.claude/skills/spec-render/render.sh <slug>
|
|
27
|
+
```
|
|
28
|
+
The script:
|
|
29
|
+
- Reads `docs/specs/<slug>.md`.
|
|
30
|
+
- Extracts every ```plantuml``` fenced block in source order.
|
|
31
|
+
- For each block: classifies it by marker (c4_context / c4_container / c4_component / sequence / class / dependency_graph / state / other), writes `docs/specs/_rendered/<slug>/<NN>_<kind>.puml`, then renders to `<NN>_<kind>.svg`.
|
|
32
|
+
- Writes `docs/specs/_rendered/<slug>/index.md` with section titles and image links in order.
|
|
33
|
+
3. Report the output path and a short per-kind count to the user.
|
|
34
|
+
4. If any block fails to render, surface the offending index + first line + stderr tail, and exit non-zero. Do **not** silently skip broken blocks.
|
|
35
|
+
|
|
36
|
+
## Output
|
|
37
|
+
|
|
38
|
+
- `docs/specs/_rendered/<slug>/<NN>_<kind>.svg` — one per diagram block.
|
|
39
|
+
- `docs/specs/_rendered/<slug>/<NN>_<kind>.puml` — source kept next to the SVG for easier diffs.
|
|
40
|
+
- `docs/specs/_rendered/<slug>/index.md` — markdown index with embedded images.
|
|
41
|
+
|
|
42
|
+
## Notes
|
|
43
|
+
|
|
44
|
+
- The render directory is a build output. Add `docs/specs/_rendered/` to `.gitignore` if you don't want the SVGs in the repo; the PlantUML sources in the spec are the source of truth.
|
|
45
|
+
- For offline review without installing PlantUML, use the `plantuml` MCP server from `.mcp.json` — it talks to a PlantUML renderer over HTTP. This skill prefers the local CLI because it's faster and keeps renders self-contained.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# spec-render — extract every ```plantuml``` block from docs/specs/<slug>.md,
|
|
3
|
+
# classify it, render to SVG, and write an index.md.
|
|
4
|
+
#
|
|
5
|
+
# Usage: .claude/skills/spec-render/render.sh <slug>
|
|
6
|
+
|
|
7
|
+
set -eu
|
|
8
|
+
|
|
9
|
+
if [ "${1:-}" = "" ]; then
|
|
10
|
+
echo "usage: render.sh <slug>" >&2
|
|
11
|
+
exit 2
|
|
12
|
+
fi
|
|
13
|
+
SLUG="$1"
|
|
14
|
+
|
|
15
|
+
ROOT="${CLAUDE_PROJECT_DIR:-$(pwd)}"
|
|
16
|
+
SPEC="$ROOT/docs/specs/$SLUG.md"
|
|
17
|
+
OUT="$ROOT/docs/specs/_rendered/$SLUG"
|
|
18
|
+
|
|
19
|
+
if [ ! -f "$SPEC" ]; then
|
|
20
|
+
echo "spec-render: spec not found at $SPEC" >&2
|
|
21
|
+
exit 2
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
if ! command -v plantuml >/dev/null 2>&1; then
|
|
25
|
+
echo "spec-render: \`plantuml\` CLI not on PATH. Install: brew install plantuml (macOS) / apt-get install plantuml (Debian/Ubuntu)." >&2
|
|
26
|
+
exit 2
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
mkdir -p "$OUT"
|
|
30
|
+
rm -f "$OUT"/*.puml "$OUT"/*.svg "$OUT"/index.md 2>/dev/null || true
|
|
31
|
+
|
|
32
|
+
# Extract + classify + write .puml files.
|
|
33
|
+
SPEC="$SPEC" OUT="$OUT" SLUG="$SLUG" python3 <<'PY'
|
|
34
|
+
import os, re, sys
|
|
35
|
+
|
|
36
|
+
spec = open(os.environ["SPEC"], encoding="utf-8").read()
|
|
37
|
+
out = os.environ["OUT"]
|
|
38
|
+
slug = os.environ["SLUG"]
|
|
39
|
+
|
|
40
|
+
fence_re = re.compile(r'^[ \t]*```[ \t]*plantuml[ \t]*$(.*?)^[ \t]*```[ \t]*$',
|
|
41
|
+
re.DOTALL | re.IGNORECASE | re.MULTILINE)
|
|
42
|
+
|
|
43
|
+
def classify(body):
|
|
44
|
+
lowered = body.lower()
|
|
45
|
+
if "!include <c4/c4_context>" in lowered: return "c4_context"
|
|
46
|
+
if "!include <c4/c4_container>" in lowered: return "c4_container"
|
|
47
|
+
if "!include <c4/c4_component>" in lowered: return "c4_component"
|
|
48
|
+
if re.search(r"'\s*@kind\s+dependency-graph", body): return "dependency_graph"
|
|
49
|
+
if re.search(r"^\s*(participant|actor)\b", body, re.MULTILINE): return "sequence"
|
|
50
|
+
if re.search(r"^\s*\[\*\]\s*-->", body, re.MULTILINE): return "state"
|
|
51
|
+
if re.search(r"^\s*class\s+\w", body, re.MULTILINE): return "class"
|
|
52
|
+
return "other"
|
|
53
|
+
|
|
54
|
+
# Section title from the last preceding ### heading (for the index).
|
|
55
|
+
heading_re = re.compile(r'^\s{0,3}#{2,4}\s+(.+?)\s*$', re.MULTILINE)
|
|
56
|
+
|
|
57
|
+
blocks = []
|
|
58
|
+
for m in fence_re.finditer(spec):
|
|
59
|
+
before = spec[:m.start()]
|
|
60
|
+
headings = heading_re.findall(before)
|
|
61
|
+
section = headings[-1].strip() if headings else "(untitled)"
|
|
62
|
+
body = m.group(1).strip("\n")
|
|
63
|
+
if "@startuml" not in body:
|
|
64
|
+
body = "@startuml\n" + body + "\n@enduml\n"
|
|
65
|
+
kind = classify(body)
|
|
66
|
+
blocks.append((section, kind, body))
|
|
67
|
+
|
|
68
|
+
if not blocks:
|
|
69
|
+
print("spec-render: no ```plantuml``` blocks found", file=sys.stderr)
|
|
70
|
+
sys.exit(1)
|
|
71
|
+
|
|
72
|
+
index_lines = [f"# Rendered diagrams — {slug}", ""]
|
|
73
|
+
for i, (section, kind, body) in enumerate(blocks, start=1):
|
|
74
|
+
stem = f"{i:02d}_{kind}"
|
|
75
|
+
puml_path = os.path.join(out, stem + ".puml")
|
|
76
|
+
with open(puml_path, "w", encoding="utf-8") as f:
|
|
77
|
+
f.write(body if body.endswith("\n") else body + "\n")
|
|
78
|
+
index_lines.append(f"## {i:02d}. {section} — `{kind}`")
|
|
79
|
+
index_lines.append("")
|
|
80
|
+
index_lines.append(f"")
|
|
81
|
+
index_lines.append("")
|
|
82
|
+
index_lines.append(f"Source: [`{stem}.puml`]({stem}.puml)")
|
|
83
|
+
index_lines.append("")
|
|
84
|
+
|
|
85
|
+
with open(os.path.join(out, "index.md"), "w", encoding="utf-8") as f:
|
|
86
|
+
f.write("\n".join(index_lines))
|
|
87
|
+
print(f"spec-render: extracted {len(blocks)} block(s)")
|
|
88
|
+
PY
|
|
89
|
+
|
|
90
|
+
# Render each .puml to SVG. Fail loud on any error.
|
|
91
|
+
fail=0
|
|
92
|
+
for puml in "$OUT"/*.puml; do
|
|
93
|
+
[ -f "$puml" ] || continue
|
|
94
|
+
if ! plantuml -tsvg -o "$OUT" "$puml" 2>"$OUT/.render.err"; then
|
|
95
|
+
echo "spec-render: FAILED to render ${puml##*/}" >&2
|
|
96
|
+
sed -n '1,10p' "$OUT/.render.err" >&2
|
|
97
|
+
fail=1
|
|
98
|
+
fi
|
|
99
|
+
done
|
|
100
|
+
rm -f "$OUT/.render.err"
|
|
101
|
+
|
|
102
|
+
if [ "$fail" -ne 0 ]; then
|
|
103
|
+
echo "spec-render: one or more blocks failed to render. See errors above." >&2
|
|
104
|
+
exit 1
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
# Count per kind for the user summary.
|
|
108
|
+
echo "spec-render: wrote $OUT/index.md"
|
|
109
|
+
ls "$OUT" | awk -F_ '/\.svg$/ { sub(/\.svg$/, "", $0); sub(/^[0-9]+_/, "", $0); kinds[$0]++ } END { for (k in kinds) printf " %s: %d\n", k, kinds[k] }'
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: spec-traceability-review
|
|
3
|
+
owner: baseline
|
|
4
|
+
description: Traceability review — every spec AC must trace to a resolvable upstream AC in the intake (and BRD if present), and no upstream AC is silently dropped. Read-only. Run alongside `spec-diagram-review` before `/approve-spec`.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You answer one question: **can every acceptance criterion in the spec be traced to an upstream requirement, and is every upstream requirement accounted for?**
|
|
8
|
+
|
|
9
|
+
# Inputs
|
|
10
|
+
|
|
11
|
+
- Spec: `docs/specs/<slug>.md`
|
|
12
|
+
- Intake: `docs/intake/<slug>.md` (required)
|
|
13
|
+
- BRD: `docs/brd/<slug>.md` (optional — include if present)
|
|
14
|
+
|
|
15
|
+
If the intake is missing, stop and report: "Cannot trace: intake not found at docs/intake/<slug>.md". Do not infer.
|
|
16
|
+
|
|
17
|
+
# Method
|
|
18
|
+
|
|
19
|
+
1. Extract AC IDs from the intake's Acceptance criteria section. IDs may be numbered (1, 2, 3) or prefixed (AC-001, AC1). Record both forms.
|
|
20
|
+
2. Extract business requirements from the BRD if present. IDs are typically `BR-NNN`.
|
|
21
|
+
3. Extract AC rows from the spec's Acceptance criteria table. Record each row's `AC-NNN` id and its `Upstream AC` reference.
|
|
22
|
+
4. Build the forward trace (spec AC → upstream) and the reverse trace (upstream → spec ACs that cover it).
|
|
23
|
+
|
|
24
|
+
# Severity matrix
|
|
25
|
+
|
|
26
|
+
| Severity | Condition |
|
|
27
|
+
|---|---|
|
|
28
|
+
| Critical | A spec `AC-NNN` row has no `Upstream AC` cell, or the cell does not resolve to a real intake/BRD AC. |
|
|
29
|
+
| Critical | An intake AC has no corresponding spec AC (silent drop). |
|
|
30
|
+
| Major | An intake AC is split across multiple spec ACs but the split is not explained in a note below the table. |
|
|
31
|
+
| Major | A BRD business requirement is listed as in-scope but no spec AC references it. |
|
|
32
|
+
| Minor | A spec AC traces to both intake and BRD; the primary reference should be the more specific source. |
|
|
33
|
+
|
|
34
|
+
# Output
|
|
35
|
+
|
|
36
|
+
Plain markdown. One section per severity. End with a two-table summary.
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
# Spec Traceability Review — <slug>
|
|
40
|
+
|
|
41
|
+
## Critical
|
|
42
|
+
- <finding>
|
|
43
|
+
|
|
44
|
+
## Major
|
|
45
|
+
- <finding>
|
|
46
|
+
|
|
47
|
+
## Minor
|
|
48
|
+
- <finding>
|
|
49
|
+
|
|
50
|
+
## Forward trace (spec → upstream)
|
|
51
|
+
| Spec AC | Upstream | Resolves? |
|
|
52
|
+
|---|---|---|
|
|
53
|
+
| AC-001 | intake AC 1 | YES |
|
|
54
|
+
| AC-002 | BR-001 | YES |
|
|
55
|
+
| AC-003 | (missing) | NO |
|
|
56
|
+
|
|
57
|
+
## Reverse trace (upstream → spec)
|
|
58
|
+
| Upstream | Covered by | Complete? |
|
|
59
|
+
|---|---|---|
|
|
60
|
+
| intake AC 1 | AC-001 | YES |
|
|
61
|
+
| intake AC 2 | — | NO (silent drop) |
|
|
62
|
+
| BR-001 | AC-002 | YES |
|
|
63
|
+
|
|
64
|
+
Verdict: READY FOR APPROVAL | REVISIONS REQUIRED
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
# Constraints
|
|
68
|
+
|
|
69
|
+
- Read-only. Do not call Edit, Write, or Bash beyond reads.
|
|
70
|
+
- Do not judge the *quality* of ACs themselves (that's the diagram-review's and human reviewer's concern). Only check that the linkage is intact.
|
|
71
|
+
- If the intake's AC format is ambiguous (mixed ID styles, un-numbered bullets), flag under **Minor** and proceed with your best mapping — do not fail the review on formatting alone.
|
|
72
|
+
- Keep the report under ~120 lines.
|