@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.
Files changed (197) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +222 -0
  3. package/bin/cli.js +247 -0
  4. package/obj/template/.claude/agents/swarm-worker.md +52 -0
  5. package/obj/template/.claude/bin/LICENSE +201 -0
  6. package/obj/template/.claude/bin/NOTICE +48 -0
  7. package/obj/template/.claude/commands/approve-spec.md +29 -0
  8. package/obj/template/.claude/commands/approve-swarm.md +27 -0
  9. package/obj/template/.claude/commands/grant-commit.md +19 -0
  10. package/obj/template/.claude/commands/init-project.md +191 -0
  11. package/obj/template/.claude/hooks/artifact_template_guard.sh +141 -0
  12. package/obj/template/.claude/hooks/consent_gate_grant.sh +89 -0
  13. package/obj/template/.claude/hooks/destructive_cmd_guard.sh +42 -0
  14. package/obj/template/.claude/hooks/env_guard.sh +36 -0
  15. package/obj/template/.claude/hooks/git_commit_guard.sh +93 -0
  16. package/obj/template/.claude/hooks/harness_continuation.sh +121 -0
  17. package/obj/template/.claude/hooks/lib/__pycache__/resume_writer.cpython-314.pyc +0 -0
  18. package/obj/template/.claude/hooks/lib/common.sh +328 -0
  19. package/obj/template/.claude/hooks/lib/resume_writer.py +341 -0
  20. package/obj/template/.claude/hooks/lint_runner.sh +55 -0
  21. package/obj/template/.claude/hooks/memory_pre_compact.sh +36 -0
  22. package/obj/template/.claude/hooks/memory_session_start.sh +244 -0
  23. package/obj/template/.claude/hooks/memory_stop.sh +173 -0
  24. package/obj/template/.claude/hooks/plantuml_syntax_guard.sh +161 -0
  25. package/obj/template/.claude/hooks/process_lifecycle_guard.sh +89 -0
  26. package/obj/template/.claude/hooks/setup_guard.sh +50 -0
  27. package/obj/template/.claude/hooks/spec_approval_guard.sh +81 -0
  28. package/obj/template/.claude/hooks/spec_design_calls_guard.sh +183 -0
  29. package/obj/template/.claude/hooks/spec_diagram_presence_guard.sh +141 -0
  30. package/obj/template/.claude/hooks/swarm_approval_guard.sh +39 -0
  31. package/obj/template/.claude/hooks/swarm_boundary_guard.sh +136 -0
  32. package/obj/template/.claude/hooks/tdd_order_guard.sh +176 -0
  33. package/obj/template/.claude/hooks/test_runner.sh +75 -0
  34. package/obj/template/.claude/hooks/tests/fixtures/ac008_byte_equal_reference.txt +12 -0
  35. package/obj/template/.claude/hooks/tests/memory_session_start_test.sh +285 -0
  36. package/obj/template/.claude/hooks/track_guard.sh +127 -0
  37. package/obj/template/.claude/hooks/verify_pass_guard.sh +88 -0
  38. package/obj/template/.claude/memory/README.md +108 -0
  39. package/obj/template/.claude/memory/_pending.md +15 -0
  40. package/obj/template/.claude/memory/_resume.md +12 -0
  41. package/obj/template/.claude/memory/conventions.md +26 -0
  42. package/obj/template/.claude/memory/decisions.md +29 -0
  43. package/obj/template/.claude/memory/landmarks.md +26 -0
  44. package/obj/template/.claude/memory/landmines.md +27 -0
  45. package/obj/template/.claude/memory/libraries.md +27 -0
  46. package/obj/template/.claude/memory/pending-questions.md +28 -0
  47. package/obj/template/.claude/project.json +221 -0
  48. package/obj/template/.claude/settings.json +110 -0
  49. package/obj/template/.claude/skills/archive/SKILL.md +48 -0
  50. package/obj/template/.claude/skills/archive/archive.sh +145 -0
  51. package/obj/template/.claude/skills/audit-baseline/SKILL.md +80 -0
  52. package/obj/template/.claude/skills/audit-baseline/audit.sh +919 -0
  53. package/obj/template/.claude/skills/brd/SKILL.md +44 -0
  54. package/obj/template/.claude/skills/brd/template.md +83 -0
  55. package/obj/template/.claude/skills/chore/SKILL.md +99 -0
  56. package/obj/template/.claude/skills/claude-automation-recommender/LICENSE +202 -0
  57. package/obj/template/.claude/skills/claude-automation-recommender/NOTICE +69 -0
  58. package/obj/template/.claude/skills/claude-automation-recommender/SKILL.md +358 -0
  59. package/obj/template/.claude/skills/claude-automation-recommender/references/hooks-patterns.md +226 -0
  60. package/obj/template/.claude/skills/claude-automation-recommender/references/mcp-servers.md +263 -0
  61. package/obj/template/.claude/skills/claude-automation-recommender/references/plugins-reference.md +98 -0
  62. package/obj/template/.claude/skills/claude-automation-recommender/references/skills-reference.md +408 -0
  63. package/obj/template/.claude/skills/claude-automation-recommender/references/subagent-templates.md +181 -0
  64. package/obj/template/.claude/skills/code-structure/SKILL.md +204 -0
  65. package/obj/template/.claude/skills/commit/SKILL.md +21 -0
  66. package/obj/template/.claude/skills/copywriting/SKILL.md +252 -0
  67. package/obj/template/.claude/skills/copywriting/evals/evals.json +111 -0
  68. package/obj/template/.claude/skills/copywriting/references/ai-writing-detection.md +200 -0
  69. package/obj/template/.claude/skills/copywriting/references/copy-frameworks.md +344 -0
  70. package/obj/template/.claude/skills/copywriting/references/natural-transitions.md +272 -0
  71. package/obj/template/.claude/skills/design-ui/SKILL.md +175 -0
  72. package/obj/template/.claude/skills/design-ui/references/design-vs-development.md +89 -0
  73. package/obj/template/.claude/skills/design-ui/references/intent-table.md +64 -0
  74. package/obj/template/.claude/skills/design-ui/references/orchestration.md +121 -0
  75. package/obj/template/.claude/skills/design-ui/references/state-machine.md +125 -0
  76. package/obj/template/.claude/skills/document/SKILL.md +66 -0
  77. package/obj/template/.claude/skills/documentation/SKILL.md +50 -0
  78. package/obj/template/.claude/skills/harness/SKILL.md +169 -0
  79. package/obj/template/.claude/skills/humanizer/SKILL.md +489 -0
  80. package/obj/template/.claude/skills/humanizer/references/ai-writing-detection.md +208 -0
  81. package/obj/template/.claude/skills/impeccable/PROJECT_NOTES.md +22 -0
  82. package/obj/template/.claude/skills/impeccable/SKILL.md +153 -0
  83. package/obj/template/.claude/skills/impeccable/agents/openai.yaml +4 -0
  84. package/obj/template/.claude/skills/impeccable/reference/adapt.md +190 -0
  85. package/obj/template/.claude/skills/impeccable/reference/animate.md +173 -0
  86. package/obj/template/.claude/skills/impeccable/reference/audit.md +134 -0
  87. package/obj/template/.claude/skills/impeccable/reference/bolder.md +113 -0
  88. package/obj/template/.claude/skills/impeccable/reference/brand.md +104 -0
  89. package/obj/template/.claude/skills/impeccable/reference/clarify.md +174 -0
  90. package/obj/template/.claude/skills/impeccable/reference/cognitive-load.md +106 -0
  91. package/obj/template/.claude/skills/impeccable/reference/color-and-contrast.md +105 -0
  92. package/obj/template/.claude/skills/impeccable/reference/colorize.md +154 -0
  93. package/obj/template/.claude/skills/impeccable/reference/craft.md +138 -0
  94. package/obj/template/.claude/skills/impeccable/reference/critique.md +213 -0
  95. package/obj/template/.claude/skills/impeccable/reference/delight.md +302 -0
  96. package/obj/template/.claude/skills/impeccable/reference/distill.md +111 -0
  97. package/obj/template/.claude/skills/impeccable/reference/document.md +427 -0
  98. package/obj/template/.claude/skills/impeccable/reference/extract.md +70 -0
  99. package/obj/template/.claude/skills/impeccable/reference/harden.md +347 -0
  100. package/obj/template/.claude/skills/impeccable/reference/heuristics-scoring.md +234 -0
  101. package/obj/template/.claude/skills/impeccable/reference/interaction-design.md +195 -0
  102. package/obj/template/.claude/skills/impeccable/reference/layout.md +141 -0
  103. package/obj/template/.claude/skills/impeccable/reference/live.md +513 -0
  104. package/obj/template/.claude/skills/impeccable/reference/motion-design.md +99 -0
  105. package/obj/template/.claude/skills/impeccable/reference/onboard.md +234 -0
  106. package/obj/template/.claude/skills/impeccable/reference/optimize.md +258 -0
  107. package/obj/template/.claude/skills/impeccable/reference/overdrive.md +130 -0
  108. package/obj/template/.claude/skills/impeccable/reference/personas.md +178 -0
  109. package/obj/template/.claude/skills/impeccable/reference/polish.md +232 -0
  110. package/obj/template/.claude/skills/impeccable/reference/product.md +62 -0
  111. package/obj/template/.claude/skills/impeccable/reference/quieter.md +99 -0
  112. package/obj/template/.claude/skills/impeccable/reference/responsive-design.md +114 -0
  113. package/obj/template/.claude/skills/impeccable/reference/shape.md +136 -0
  114. package/obj/template/.claude/skills/impeccable/reference/spatial-design.md +100 -0
  115. package/obj/template/.claude/skills/impeccable/reference/teach.md +137 -0
  116. package/obj/template/.claude/skills/impeccable/reference/typeset.md +124 -0
  117. package/obj/template/.claude/skills/impeccable/reference/typography.md +159 -0
  118. package/obj/template/.claude/skills/impeccable/reference/ux-writing.md +107 -0
  119. package/obj/template/.claude/skills/impeccable/scripts/cleanup-deprecated.mjs +284 -0
  120. package/obj/template/.claude/skills/impeccable/scripts/command-metadata.json +94 -0
  121. package/obj/template/.claude/skills/impeccable/scripts/design-parser.mjs +820 -0
  122. package/obj/template/.claude/skills/impeccable/scripts/detect-csp.mjs +198 -0
  123. package/obj/template/.claude/skills/impeccable/scripts/is-generated.mjs +69 -0
  124. package/obj/template/.claude/skills/impeccable/scripts/live-accept.mjs +465 -0
  125. package/obj/template/.claude/skills/impeccable/scripts/live-browser.js +4684 -0
  126. package/obj/template/.claude/skills/impeccable/scripts/live-inject.mjs +436 -0
  127. package/obj/template/.claude/skills/impeccable/scripts/live-poll.mjs +187 -0
  128. package/obj/template/.claude/skills/impeccable/scripts/live-server.mjs +679 -0
  129. package/obj/template/.claude/skills/impeccable/scripts/live-wrap.mjs +395 -0
  130. package/obj/template/.claude/skills/impeccable/scripts/live.mjs +247 -0
  131. package/obj/template/.claude/skills/impeccable/scripts/load-context.mjs +93 -0
  132. package/obj/template/.claude/skills/impeccable/scripts/modern-screenshot.umd.js +14 -0
  133. package/obj/template/.claude/skills/impeccable/scripts/pin.mjs +214 -0
  134. package/obj/template/.claude/skills/implement/SKILL.md +83 -0
  135. package/obj/template/.claude/skills/intake/SKILL.md +46 -0
  136. package/obj/template/.claude/skills/intake/template.md +61 -0
  137. package/obj/template/.claude/skills/integrate/SKILL.md +62 -0
  138. package/obj/template/.claude/skills/memory-flush/SKILL.md +172 -0
  139. package/obj/template/.claude/skills/memory-flush/sweep.py +286 -0
  140. package/obj/template/.claude/skills/memory-flush/tests/run.sh +327 -0
  141. package/obj/template/.claude/skills/prose/SKILL.md +119 -0
  142. package/obj/template/.claude/skills/rca/SKILL.md +42 -0
  143. package/obj/template/.claude/skills/rca/template.md +83 -0
  144. package/obj/template/.claude/skills/research/SKILL.md +75 -0
  145. package/obj/template/.claude/skills/scenario/SKILL.md +64 -0
  146. package/obj/template/.claude/skills/scout/SKILL.md +72 -0
  147. package/obj/template/.claude/skills/security/SKILL.md +75 -0
  148. package/obj/template/.claude/skills/simplify/SKILL.md +67 -0
  149. package/obj/template/.claude/skills/spec/SKILL.md +69 -0
  150. package/obj/template/.claude/skills/spec/template.md +274 -0
  151. package/obj/template/.claude/skills/spec-diagram-review/SKILL.md +81 -0
  152. package/obj/template/.claude/skills/spec-lint/SKILL.md +55 -0
  153. package/obj/template/.claude/skills/spec-lint/lint.sh +218 -0
  154. package/obj/template/.claude/skills/spec-render/SKILL.md +45 -0
  155. package/obj/template/.claude/skills/spec-render/render.sh +109 -0
  156. package/obj/template/.claude/skills/spec-traceability-review/SKILL.md +72 -0
  157. package/obj/template/.claude/skills/swarm-dispatch/SKILL.md +212 -0
  158. package/obj/template/.claude/skills/swarm-dispatch/swarm_merge.sh +154 -0
  159. package/obj/template/.claude/skills/swarm-plan/SKILL.md +90 -0
  160. package/obj/template/.claude/skills/swarm-plan/validate.sh +181 -0
  161. package/obj/template/.claude/skills/tdd/SKILL.md +100 -0
  162. package/obj/template/.claude/skills/technical-tutorials/SKILL.md +569 -0
  163. package/obj/template/.claude/skills/technical-tutorials/references/audience-context-README.md +53 -0
  164. package/obj/template/.claude/skills/technical-tutorials/references/audience-context.md +246 -0
  165. package/obj/template/.claude/skills/technical-tutorials/references/audience-example.md +175 -0
  166. package/obj/template/.claude/skills/technical-tutorials/references/audience-template.md +152 -0
  167. package/obj/template/.claude/skills/triage/SKILL.md +55 -0
  168. package/obj/template/.claude/skills/verify/SKILL.md +74 -0
  169. package/obj/template/.mcp.json +24 -0
  170. package/obj/template/CLAUDE.md +327 -0
  171. package/obj/template/docs/init/seed.md +585 -0
  172. package/obj/template/manifest.json +214 -0
  173. package/package.json +48 -0
  174. package/src/.mcp.template.json +24 -0
  175. package/src/.npmrc.template +2 -0
  176. package/src/CLAUDE.template.md +327 -0
  177. package/src/agents/swarm-worker.template.md +51 -0
  178. package/src/cli/conflict.js +31 -0
  179. package/src/cli/doctor.js +152 -0
  180. package/src/cli/install.js +93 -0
  181. package/src/cli/io.js +27 -0
  182. package/src/cli/manifest.js +38 -0
  183. package/src/cli/mcp.js +54 -0
  184. package/src/cli/merge.js +107 -0
  185. package/src/cli/plantuml.js +121 -0
  186. package/src/cli/util.js +10 -0
  187. package/src/memory/_pending.template.md +15 -0
  188. package/src/memory/_resume.template.md +12 -0
  189. package/src/memory/conventions.template.md +26 -0
  190. package/src/memory/decisions.template.md +29 -0
  191. package/src/memory/landmarks.template.md +26 -0
  192. package/src/memory/landmines.template.md +27 -0
  193. package/src/memory/libraries.template.md +27 -0
  194. package/src/memory/pending-questions.template.md +28 -0
  195. package/src/project.template.json +221 -0
  196. package/src/seed.template.md +585 -0
  197. 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"![{kind}]({stem}.svg)")
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.