devrites 2.1.0 → 2.3.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 (57) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +31 -17
  3. package/docs/architecture.md +30 -24
  4. package/docs/command-map.md +15 -6
  5. package/docs/flow.md +20 -6
  6. package/docs/orchestration.md +81 -0
  7. package/docs/skills.md +32 -8
  8. package/pack/.claude/agents/devrites-code-reviewer.md +40 -4
  9. package/pack/.claude/agents/devrites-devex-reviewer.md +94 -0
  10. package/pack/.claude/agents/devrites-forge-judge.md +92 -0
  11. package/pack/.claude/agents/devrites-frontend-reviewer.md +8 -0
  12. package/pack/.claude/agents/devrites-retrospector.md +91 -0
  13. package/pack/.claude/agents/devrites-security-auditor.md +21 -1
  14. package/pack/.claude/agents/devrites-slice-wright.md +18 -5
  15. package/pack/.claude/rules/README.md +7 -2
  16. package/pack/.claude/rules/afk-hitl.md +6 -0
  17. package/pack/.claude/rules/agents.md +30 -2
  18. package/pack/.claude/rules/anti-patterns.md +11 -0
  19. package/pack/.claude/rules/code-review.md +16 -6
  20. package/pack/.claude/rules/core.md +13 -4
  21. package/pack/.claude/rules/deprecation.md +50 -0
  22. package/pack/.claude/rules/developer-experience.md +119 -0
  23. package/pack/.claude/rules/observability.md +60 -0
  24. package/pack/.claude/rules/patterns.md +3 -0
  25. package/pack/.claude/rules/principles.md +158 -0
  26. package/pack/.claude/rules/security.md +31 -0
  27. package/pack/.claude/rules/spec-grammar.md +106 -0
  28. package/pack/.claude/skills/devrites-browser-proof/SKILL.md +42 -3
  29. package/pack/.claude/skills/devrites-lib/SKILL.md +9 -2
  30. package/pack/.claude/skills/devrites-lib/scripts/devrites.sh +8 -0
  31. package/pack/.claude/skills/devrites-lib/scripts/spec-validate.sh +130 -0
  32. package/pack/.claude/skills/rite-adopt/SKILL.md +13 -1
  33. package/pack/.claude/skills/rite-autocomplete/SKILL.md +3 -1
  34. package/pack/.claude/skills/rite-build/SKILL.md +36 -1
  35. package/pack/.claude/skills/rite-build/reference/anti-patterns.md +12 -0
  36. package/pack/.claude/skills/rite-build/reference/forge.md +171 -0
  37. package/pack/.claude/skills/rite-build/reference/one-slice-cycle.md +2 -0
  38. package/pack/.claude/skills/rite-build/reference/wright-dispatch.md +22 -2
  39. package/pack/.claude/skills/rite-define/SKILL.md +14 -1
  40. package/pack/.claude/skills/rite-frame/SKILL.md +7 -2
  41. package/pack/.claude/skills/rite-learn/SKILL.md +17 -4
  42. package/pack/.claude/skills/rite-polish/SKILL.md +3 -1
  43. package/pack/.claude/skills/rite-prove/SKILL.md +40 -1
  44. package/pack/.claude/skills/rite-quick/SKILL.md +9 -4
  45. package/pack/.claude/skills/rite-review/SKILL.md +5 -2
  46. package/pack/.claude/skills/rite-review/reference/five-axis-review.md +15 -1
  47. package/pack/.claude/skills/rite-seal/SKILL.md +33 -4
  48. package/pack/.claude/skills/rite-ship/SKILL.md +21 -0
  49. package/pack/.claude/skills/rite-spec/SKILL.md +29 -5
  50. package/pack/.claude/skills/rite-spec/reference/spec-template.md +11 -1
  51. package/pack/.claude/skills/rite-status/SKILL.md +3 -1
  52. package/pack/.claude/skills/rite-vet/SKILL.md +46 -14
  53. package/package.json +1 -1
  54. package/scripts/check-cross-refs.py +4 -0
  55. package/scripts/run-behavioral-evals.sh +170 -0
  56. package/scripts/run-evals.sh +3 -0
  57. package/scripts/validate.sh +16 -7
@@ -0,0 +1,106 @@
1
+ # Spec grammar — testable requirements, parseable by tooling
2
+
3
+ Acceptance criteria are the contract the seal checks ([`testing.md`](testing.md),
4
+ [`code-review.md`](code-review.md)). Prose criteria work, but a requirement written as free
5
+ text is graded by a human reading carefully — and an ambiguous one ("handle errors
6
+ gracefully") slips past every gate because nothing can falsify it. This rule adds an
7
+ **optional, recommended structure** that makes a behavioral requirement testable by
8
+ construction and parseable by a deterministic linter, so the spec phase catches a malformed
9
+ requirement before `/rite-define` plans against it.
10
+
11
+ It is the grammar counterpart to [`testing.md`](testing.md): testing says *prove every
12
+ behavior*; this says *write each behavior so it can be proven*.
13
+
14
+ ## Progressive rigor — when to use the structured form
15
+
16
+ Match the rigor to the stakes; don't pay grammar ceremony for a one-liner.
17
+
18
+ - **Simple / routine change** — the flat checklist form is correct and stays. One bullet per
19
+ criterion, each tagged with an `[ACn]` id:
20
+ ```markdown
21
+ ## Acceptance criteria
22
+ - [ ] [AC1] export returns a CSV with a header row
23
+ - [ ] [AC2] an empty dataset returns 204, not an empty 200
24
+ ```
25
+ - **Behavioral / high-risk / cross-boundary requirement** — auth, data model, state machine,
26
+ public API, money, a migration, anything with non-obvious edge cases — use the structured
27
+ **Requirement / Scenario** grammar below. The act of writing the WHEN/THEN forces the edge
28
+ cases into the open at spec time, where they're cheapest to resolve.
29
+
30
+ A spec mixes both: most criteria stay flat bullets; the two or three that carry real risk get
31
+ the structured treatment. **Absence of structured requirements is never a failure** — the
32
+ validator no-ops on a flat-bullet spec, the same discipline as the principles gate
33
+ ([`principles.md`](principles.md)).
34
+
35
+ ## The structured form
36
+
37
+ ```markdown
38
+ ### Requirement: Session tokens expire after inactivity
39
+ The system SHALL reject any session token older than 15 minutes of inactivity.
40
+
41
+ #### Scenario: token past the inactivity window
42
+ - [ ] [AC4] **WHEN** a request presents a token whose last use was > 15m ago
43
+ **THEN** the request is rejected with 401 and the token is revoked
44
+
45
+ #### Scenario: token within the window
46
+ - [ ] [AC5] **WHEN** a request presents a token last used < 15m ago
47
+ **THEN** the request is served and the last-use timestamp advances
48
+ ```
49
+
50
+ The rules the validator enforces (`spec-validate.sh`):
51
+
52
+ - **`### Requirement: <name>`** — a level-3 heading. Its block MUST carry a **SHALL** or
53
+ **MUST** statement (in the header or the body) describing the core behavior. Keep the name
54
+ descriptive and under ~50 characters.
55
+ - **Header identity is the key.** Requirement names are **unique** within a spec — tooling (and
56
+ any future spec sync) matches a requirement by its header text, so two requirements can't
57
+ share a name. Renaming a requirement is a remove + add, not an in-place edit, so the change
58
+ is visible.
59
+ - **`#### Scenario: <name>`** — every requirement owns **at least one**. A requirement with no
60
+ scenario is an assertion no test can target.
61
+ - **WHEN / THEN** — every scenario states a trigger (**WHEN**) and an observable outcome
62
+ (**THEN**); chain extra conditions with **AND**. Keywords are uppercase so they parse
63
+ unambiguously. A scenario missing either half isn't falsifiable.
64
+
65
+ ## Behavior first — WHAT, not HOW
66
+
67
+ A spec requirement describes observable behavior, not the implementation that delivers it
68
+ (the spec stays technology-agnostic — see `rite-spec/reference/spec-template.md`). "**THEN**
69
+ respond 401" is behavior; "**THEN** call `AuthGuard.reject()`" leaks the design and locks the
70
+ plan before `/rite-define` has chosen it. Name the input, the observable output, and the state
71
+ change — not the function, class, or library. Implementation belongs in `plan.md`, not the
72
+ requirement.
73
+
74
+ ## How it composes (no new gate, no duplication)
75
+
76
+ - **`[ACn]` ids nest inside scenarios.** Tag each scenario's checkable line with an `[ACn]` id
77
+ exactly as the flat form does. The same `check-acceptance.sh` grades it against `seal.md` at
78
+ the end of the lifecycle — this grammar checks the requirement's **shape**, that script
79
+ checks its **proof**. The two compose; neither replaces the other.
80
+ - **`/rite-prove` and `/rite-review` get concrete hooks.** Each scenario is one behavior to
81
+ prove and the test analyst's unit of "is this actually covered" — the WHEN/THEN maps
82
+ straight to an arrange/assert ([`testing.md`](testing.md)).
83
+ - **The spec-quality checklists still apply.** The grammar makes a requirement *parseable*; the
84
+ `checklists/<domain>.md` "unit tests for English" still score whether it's *complete and
85
+ measurable* ("is 'prominent' quantified?"). Structure doesn't excuse vagueness inside a THEN.
86
+
87
+ ## The validator — `spec-validate.sh`
88
+
89
+ Deterministic, zero-token. Run it on the workspace (or a spec path) at the spec readiness gate:
90
+
91
+ ```bash
92
+ bash .claude/skills/devrites-lib/scripts/spec-validate.sh .devrites/work/<slug>
93
+ ```
94
+
95
+ Exit codes: `0` valid (or no structured requirements — nothing to lint), `1` grammar
96
+ violation(s) with `file:line` locations, `2` usage, `5` missing `spec.md`. A non-zero exit is a
97
+ blocking spec-readiness failure: fix the requirement, never soften the check. It runs as part
98
+ of `/rite-spec`'s readiness gate and is safe to re-run any time the spec changes.
99
+
100
+ ## Forward note
101
+
102
+ The Requirement / Scenario block is deliberately the unit a persistent capability ledger would
103
+ store — so if DevRites later grows a living "what the system currently does" layer, today's
104
+ structured specs sync into it without a reformat. That layer is **not** shipped here; for now
105
+ the grammar earns its place purely as testable, lint-checkable acceptance criteria at the spec
106
+ phase.
@@ -9,6 +9,10 @@ user-invocable: false
9
9
  Screenshots and runtime observations beat "it should render fine." Use the highest rung
10
10
  of the ladder that's available; record which one.
11
11
 
12
+ The same ladder captures a **developer-facing docs / getting-started page** for the DX measure step
13
+ (`/rite-prove` 5c, `developer-experience.md`): screenshot the quickstart, confirm the documented
14
+ commands match what actually runs, and note the result in `browser-evidence.md` / `devex.md`.
15
+
12
16
  ## Ladder (top-down)
13
17
  1. **browser-harness** — detect `command -v browser-harness`. Connects to the user's
14
18
  Chrome over CDP. Pattern: `new_tab(url)` → `wait_for_load()` → `capture_screenshot()`
@@ -45,9 +49,44 @@ a lab value as a field value or vice versa.
45
49
  Tooling used · route(s) · viewports (375/768/1280) · screenshot paths **opened and
46
50
  described** · console errors/warnings · network failures · interaction path tested ·
47
51
  accessibility basics · responsive checks · **CWV capture** (tool + route + each
48
- source-labeled value, or `pending (manual)` + the command) · **design-reference match** (if
49
- the spec saved references in `references/`, compare the built UI to them and note
50
- match/diffs) · limitations.
52
+ source-labeled value, or `pending (manual)` + the command) · **Visual Verdict** (the
53
+ structured design-brief / design-reference scorecard below) · limitations.
54
+
55
+ ## Visual Verdict — structured pass/fail (auto-emit for UI with a design brief)
56
+ The screenshots you already opened and described become a verdict, not a vibe. **Whenever the
57
+ feature is UI and a `design-brief.md` exists** (or the spec saved design refs in `references/`),
58
+ emit a `## Visual Verdict` table to `browser-evidence.md`, scored from the opened screenshots.
59
+ This is the prose "design-reference match" formalized so `/rite-seal` and the
60
+ `devrites-frontend-reviewer` can gate on it instead of re-reading prose. No `design-brief.md` and
61
+ no saved references → no Visual Verdict (greenfield no-op — never block UI for the absence of a
62
+ brief; record the limitation and move on).
63
+
64
+ One row per **declared `design-brief.md` state** the slice delivers (default / loading / empty /
65
+ error / success / disabled / long-content), plus key **design-reference** diffs and the
66
+ **anti-slop** checks (`rite-polish/reference/anti-ai-slop.md`). Score each from a real screenshot,
67
+ not the markup:
68
+
69
+ ```markdown
70
+ ## Visual Verdict — <route / component> (viewport: 375 / 1280)
71
+ | Criterion (source) | Expected | Observed (screenshot) | Verdict | Severity |
72
+ |---|---|---|---|---|
73
+ | empty state (brief) | welcoming copy + primary action | renders, copy present | PASS | — |
74
+ | error state (brief) | inline recoverable message | no error UI — silent | FAIL | Important |
75
+ | primary CTA (ref) | brand indigo, 44px target | indigo, 32px target | PARTIAL | Suggestion |
76
+ | anti-slop | no gradient-on-card cliché | clean | PASS | — |
77
+
78
+ Overall: PARTIAL — 1 FAIL (error state), 1 PARTIAL (CTA size). Screenshots: <paths>.
79
+ ```
80
+
81
+ - **Verdict per row** — `PASS` (matches), `PARTIAL` (present but off), `FAIL` (missing / wrong /
82
+ broken). **Severity** scales by who-pays and acceptance mapping: a FAIL on a criterion that maps
83
+ to a **spec acceptance criterion** is **Critical** (it's an unmet criterion, not a polish nit); a
84
+ declared-state FAIL is **Important**; a cosmetic drift is **Suggestion**.
85
+ - **Overall line** — `PASS` | `PARTIAL (n)` | `FAIL (n)`. This is what the consumers read: a
86
+ FAIL on an acceptance-mapped row is a NO-GO at `/rite-seal`, and the FAIL/PARTIAL rows are the
87
+ `/rite-polish` normalize worklist.
88
+ - **Honesty.** A row scored without an opened screenshot is `pending (manual)` with the command —
89
+ never a green you didn't observe (the same standing as `pending (manual)` proof below).
51
90
 
52
91
  ## Hard rules
53
92
  - A screenshot **path is not proof** — open it and describe what's visible.
@@ -48,6 +48,12 @@ first, then `${CLAUDE_SKILL_DIR}`, then the repo `pack/` source).
48
48
  - `scripts/check-acceptance.sh` — executable acceptance gate. Compiles `spec.md`'s
49
49
  `[ACn]`-tagged criteria and exits `1` unless every one is checked (proven) in `seal.md`;
50
50
  used by `/rite-seal` and by the outcome grader.
51
+ - `scripts/spec-validate.sh` — spec-grammar gate (the spec-side mirror of
52
+ `check-acceptance.sh`). Lints `spec.md`'s structured `### Requirement:` / `#### Scenario:`
53
+ blocks (SHALL/MUST present, ≥1 scenario each, every scenario has WHEN + THEN, headers
54
+ unique). Exits `1` on a grammar violation, `0` when valid **or** when the spec uses the flat
55
+ `[ACn]`-bullet form (no structured blocks — nothing to lint, never a failure). Used by
56
+ `/rite-spec`'s readiness gate; see [`rules/spec-grammar.md`](../../rules/spec-grammar.md).
51
57
 
52
58
  **State mutators — write `state.md` / `questions.md` under one contract:**
53
59
 
@@ -70,7 +76,8 @@ PR=.claude/skills/devrites-lib/scripts/progress.sh
70
76
  **Unified entrypoint (tool-agnostic):**
71
77
 
72
78
  - `scripts/devrites.sh` — one CLI dispatching to all of the above (`orient` / `ready` /
73
- `evidence-fresh` / `acceptance` / `tick-afk` / `resolve` / `close` / `active` / `list` /
74
- `use`), so any agent or human can drive `.devrites/` without the skill prose. The MCP
79
+ `evidence-fresh` / `acceptance` / `spec-validate` / `tick-afk` / `resolve` / `close` /
80
+ `active` / `list` / `use`), so any agent or human can drive `.devrites/` without the skill
81
+ prose. The MCP
75
82
  wrapper `mcp/devrites-mcp.mjs` exposes the read/gate ops as MCP tools. See
76
83
  [`docs/cli-mcp.md`](../../../../docs/cli-mcp.md).
@@ -26,6 +26,9 @@ devrites — drive the .devrites/ workflow from any tool (run at the project roo
26
26
  ready [slug] build-readiness gate (0 ready · 2 no-plan · 3 awaiting · 4 blocked · 5 none)
27
27
  evidence-fresh [slug] evidence-freshness gate (0 fresh · 3 stale)
28
28
  acceptance [slug|dir] acceptance-criteria gate (0 all proven · 1 gap)
29
+ spec-validate [slug|dir|file]
30
+ spec-grammar gate — lint structured Requirement/Scenario blocks
31
+ (0 valid or flat form · 1 grammar violation · 5 no spec.md)
29
32
  tick-afk [slug] decrement the AFK slice budget (3 = exhausted)
30
33
  resolve <args...> answer / --drop / --batch a questions.md entry
31
34
  close [slug] archive the workspace + clear ACTIVE
@@ -51,6 +54,11 @@ case "$cmd" in
51
54
  if [ -n "$a" ] && [ -d "$a" ]; then d="$a"
52
55
  else s="$(resolve_slug "${a:-}")"; [ -n "$s" ] || { echo "devrites: no active feature" >&2; exit 5; }; d=".devrites/work/$s"; fi
53
56
  exec bash "$HERE/check-acceptance.sh" "$d" ;;
57
+ spec-validate)
58
+ a="${1:-}"
59
+ if [ -n "$a" ] && { [ -d "$a" ] || [ -f "$a" ]; }; then t="$a"
60
+ else s="$(resolve_slug "${a:-}")"; [ -n "$s" ] || { echo "devrites: no active feature" >&2; exit 5; }; t=".devrites/work/$s"; fi
61
+ exec bash "$HERE/spec-validate.sh" "$t" ;;
54
62
  tick-afk)
55
63
  s="$(resolve_slug "${1:-}")"; [ -n "$s" ] || { echo "devrites: no active feature" >&2; exit 5; }
56
64
  exec bash "$HERE/tick-afk.sh" ".devrites/work/$s/state.md" ;;
@@ -0,0 +1,130 @@
1
+ #!/usr/bin/env bash
2
+ # Spec-grammar validator — deterministic, zero-token lint of the structured
3
+ # Requirement/Scenario acceptance grammar (rules/spec-grammar.md) in a spec.md.
4
+ # Turns "are the behavioral requirements well-formed?" into an exit code instead
5
+ # of prose the model walks by hand — the spec-side mirror of check-acceptance.sh
6
+ # (which grades the SAME [ACn] ids against seal.md at the end of the lifecycle).
7
+ #
8
+ # Grammar checked (only when the spec opts into the structured form):
9
+ # ### Requirement: <name> — must carry a SHALL/MUST statement (header or body)
10
+ # — must own >=1 #### Scenario; header names unique
11
+ # #### Scenario: <name> — must carry both a WHEN and a THEN line
12
+ # A spec with ZERO `### Requirement:` headers uses the simple flat-bullet form
13
+ # (- [ ] [AC1] ...) — valid, and this validator no-ops (exit 0). Absence is never
14
+ # a failure, the same discipline as the principles gate. The grammar is opt-in and
15
+ # recommended for behavioral / high-risk requirements (progressive rigor).
16
+ #
17
+ # Usage: spec-validate.sh <workspace-dir | spec.md path>
18
+ # Exit: 0 valid (or no structured requirements — nothing to lint) ·
19
+ # 1 grammar violation(s) · 2 usage · 5 missing spec.md
20
+ #
21
+ # [ACn] compatibility: tag each scenario's checkable line with an [ACn] id so the
22
+ # same criterion is graded at seal by check-acceptance.sh. This validator checks
23
+ # SHAPE, not proof; the two compose, they don't overlap.
24
+
25
+ set -euo pipefail
26
+
27
+ arg="${1:-}"
28
+ if [ -z "$arg" ]; then
29
+ printf 'usage: spec-validate.sh <workspace-dir | spec.md path>\n' >&2
30
+ exit 2
31
+ fi
32
+
33
+ if [ -d "$arg" ]; then
34
+ spec="$arg/spec.md"
35
+ elif [ -f "$arg" ]; then
36
+ spec="$arg"
37
+ else
38
+ printf 'spec-validate: no such workspace or file: %s\n' "$arg" >&2
39
+ exit 2
40
+ fi
41
+ [ -f "$spec" ] || { printf 'spec-validate: no spec.md at %s\n' "$spec" >&2; exit 5; }
42
+
43
+ # Parse the structured grammar in one awk pass. Emits ERROR:/WARN: finding lines
44
+ # and a trailing "__SUMMARY__ <requirements> <scenarios>" line. BSD/GNU awk safe:
45
+ # tolower() is POSIX; no IGNORECASE, no gensub. Keyword matches are case-sensitive
46
+ # on purpose — the grammar mandates uppercase SHALL / MUST / WHEN / THEN.
47
+ findings="$(awk -v FILE="$spec" '
48
+ function close_scenario() {
49
+ if (inScenario) {
50
+ if (!scenWhen) { print "ERROR:" FILE ": Requirement \"" curReq "\" > Scenario \"" scenName "\" (line " scenLine ") has no WHEN line"; }
51
+ if (!scenThen) { print "ERROR:" FILE ": Requirement \"" curReq "\" > Scenario \"" scenName "\" (line " scenLine ") has no THEN line"; }
52
+ inScenario = 0
53
+ }
54
+ }
55
+ function close_requirement() {
56
+ close_scenario()
57
+ if (curReq != "") {
58
+ if (!sawShall) { print "ERROR:" FILE ": Requirement \"" curReq "\" (line " reqLine ") has no SHALL/MUST statement"; }
59
+ if (scenInReq == 0) {
60
+ if (looseWhenThen) { print "ERROR:" FILE ": Requirement \"" curReq "\" (line " reqLine ") has WHEN/THEN lines but no \"#### Scenario:\" header — wrap them in a scenario"; }
61
+ else { print "ERROR:" FILE ": Requirement \"" curReq "\" (line " reqLine ") has no \"#### Scenario:\" block"; }
62
+ }
63
+ }
64
+ }
65
+ /^[[:space:]]*###[[:space:]]+[Rr]equirement:/ {
66
+ close_requirement()
67
+ reqCount++
68
+ name = $0
69
+ sub(/^[[:space:]]*###[[:space:]]+[Rr]equirement:[[:space:]]*/, "", name)
70
+ sub(/[[:space:]]+$/, "", name)
71
+ curReq = name; reqLine = NR
72
+ sawShall = ($0 ~ /(^|[^A-Za-z])(SHALL|MUST)([^A-Za-z]|$)/) ? 1 : 0
73
+ scenInReq = 0; looseWhenThen = 0
74
+ key = tolower(name)
75
+ if (key in seen) { print "ERROR:" FILE ": duplicate Requirement header \"" name "\" (lines " seen[key] " and " NR ") — headers must be unique"; }
76
+ else { seen[key] = NR }
77
+ next
78
+ }
79
+ /^[[:space:]]*####[[:space:]]+[Ss]cenario:/ {
80
+ close_scenario()
81
+ if (curReq == "") { print "ERROR:" FILE ": Scenario at line " NR " is not under any \"### Requirement:\""; }
82
+ sName = $0
83
+ sub(/^[[:space:]]*####[[:space:]]+[Ss]cenario:[[:space:]]*/, "", sName)
84
+ sub(/[[:space:]]+$/, "", sName)
85
+ inScenario = 1; scenCount++; scenInReq++
86
+ scenName = sName; scenLine = NR; scenWhen = 0; scenThen = 0
87
+ next
88
+ }
89
+ {
90
+ if (curReq != "") {
91
+ if ($0 ~ /(^|[^A-Za-z])(SHALL|MUST)([^A-Za-z]|$)/) { sawShall = 1 }
92
+ hasWhen = ($0 ~ /(^|[^A-Za-z])WHEN([^A-Za-z]|$)/)
93
+ hasThen = ($0 ~ /(^|[^A-Za-z])THEN([^A-Za-z]|$)/)
94
+ if (inScenario) {
95
+ if (hasWhen) { scenWhen = 1 }
96
+ if (hasThen) { scenThen = 1 }
97
+ } else if (hasWhen || hasThen) {
98
+ looseWhenThen = 1
99
+ }
100
+ }
101
+ }
102
+ END { close_requirement(); print "__SUMMARY__ " reqCount+0 " " scenCount+0 }
103
+ ' "$spec")"
104
+
105
+ summary="$(printf '%s\n' "$findings" | grep '^__SUMMARY__' || true)"
106
+ reqs="$(printf '%s' "$summary" | awk '{print $2+0}')"
107
+ scens="$(printf '%s' "$summary" | awk '{print $3+0}')"
108
+ problems="$(printf '%s\n' "$findings" | grep -E '^(ERROR|WARN):' || true)"
109
+ errs="$(printf '%s\n' "$findings" | grep -c '^ERROR:' || true)"
110
+
111
+ rel="${spec#"$PWD"/}"
112
+
113
+ if [ "$reqs" -eq 0 ]; then
114
+ printf 'spec-validate: %s uses the simple acceptance form (no "### Requirement:" blocks) — nothing to lint\n' "$rel"
115
+ exit 0
116
+ fi
117
+
118
+ if [ -n "$problems" ]; then
119
+ printf '%s\n' "$problems" >&2
120
+ fi
121
+
122
+ if [ "$errs" -gt 0 ]; then
123
+ printf 'spec-validate: %s — %d requirement(s) / %d scenario(s); %d grammar error(s) (see rules/spec-grammar.md)\n' \
124
+ "$rel" "$reqs" "$scens" "$errs" >&2
125
+ exit 1
126
+ fi
127
+
128
+ printf 'spec-validate: OK — %s: %d requirement(s) / %d scenario(s) well-formed (SHALL + WHEN/THEN, unique headers)\n' \
129
+ "$rel" "$reqs" "$scens"
130
+ exit 0
@@ -22,7 +22,8 @@ lifecycle (`/rite-temper` → `/rite-define` → `/rite-build` …) takes over.
22
22
 
23
23
  ## Rules consulted (read on demand from `.claude/rules/`)
24
24
  **Step 0:** Read `.claude/rules/core.md` first. Pull `documentation.md` when recording
25
- the adoption decisions (why-not-what) in `decisions.md`.
25
+ the adoption decisions (why-not-what) in `decisions.md`; pull `principles.md` when the code
26
+ upholds invariants worth proposing as project principles (step 4a).
26
27
 
27
28
  ## Operating rules (DevRites core)
28
29
  - No silent assumptions · prefer the project's existing conventions (you are *documenting*
@@ -61,6 +62,16 @@ the adoption decisions (why-not-what) in `decisions.md`.
61
62
  evidence-gated promotion: the seeds start at the base band and are provenance-tagged as
62
63
  onboarding observations, not sealed-slice proofs, so real slices later corroborate or
63
64
  (fresh-wins) contradict them.
65
+ 4a. **Propose candidate principles** (human-ratified; optional). Where the investigation found an
66
+ invariant the code *consistently and deliberately* upholds — money always in integer cents, PII
67
+ always redacted from logs, every v1 endpoint preserved — surface it as a **candidate
68
+ principle**, not a seeded convention. Principles are prescriptive and gating, so they are
69
+ **ratified by the human, never auto-seeded** the way conventions are: present the candidates via
70
+ `AskUserQuestion` with the evidence (where the code upholds it), and write the ones the human
71
+ ratifies to `.devrites/principles.md` with a dated Governance entry
72
+ ([`principles.md`](../../rules/principles.md)). Propose, don't impose — an unratified candidate
73
+ stays a convention, not a gate. Skip cleanly when nothing rises to an invariant (common — a
74
+ fresh adopt may declare zero principles, and that's valid).
64
75
  5. **Hand off.** The project is now in the lifecycle with a spec and a head-start ledger.
65
76
  Point the user at `/rite-temper` (big/risky) or `/rite-define` (straightforward) — do not
66
77
  plan or build here.
@@ -78,6 +89,7 @@ Adopted: <slug>
78
89
  Baseline: <one-line summary of current behavior> Placement: <where it lives>
79
90
  Next objective: <what we'll build on top>
80
91
  Conventions seeded: <n> (commands · idioms · placement · gotchas)
92
+ Principles proposed: <n ratified → .devrites/principles.md | none rose to an invariant>
81
93
  Next: big / risky? → /rite-temper · straightforward? → /rite-define
82
94
  ↻ Hygiene: /clear before the next phase (spec.md + decisions.md + the seeded ledger captured). See rules/context-hygiene.md.
83
95
  ```
@@ -22,7 +22,9 @@ blocking / escalating gates, and any NO-GO still pause.
22
22
  - **Safety gates are not bypassable.** AFK never auto-passes destructive migration /
23
23
  auth-authz change / public-API break / external-contract change / red tests; blocking
24
24
  and escalating gates and any open `gate: validating` always pause. `--ship` auto-confirms
25
- the **final** type-GO only — nothing else.
25
+ the **final** type-GO only — nothing else. A change that violates a declared project
26
+ principle (`.devrites/principles.md`) with no recorded exception pauses too — autocomplete
27
+ never grants a principle exception on its own (`principles.md`: that's a human decision).
26
28
  - **Loop budget = the plan's own slice count, not a fixed number.** After `/rite-vet`
27
29
  (not `/rite-define` — vet may split or add slices, so the count isn't final until then),
28
30
  set the AFK budget to however many slices the plan has, so the loop builds exactly the
@@ -24,6 +24,7 @@ writes; read them yourself for the doubt/record gates or in the inline fallback:
24
24
  - `error-handling.md` — fail fast, no silent catches, fail closed.
25
25
  - `testing.md` — pyramid, behaviour over implementation, see-it-fail-first.
26
26
  - `patterns.md` — composition over inheritance, avoid premature abstraction.
27
+ - `principles.md` — the project invariants (`.devrites/principles.md`) the slice must honor; the wright reads them as **binding**, not priors.
27
28
  - `security.md` — when the slice touches user input, auth, data, or external integrations.
28
29
 
29
30
  ## Operating rules
@@ -40,11 +41,23 @@ writes; read them yourself for the doubt/record gates or in the inline fallback:
40
41
  The **prose you write yourself** — `evidence.md`, `decisions.md`, the slice report — follows
41
42
  the human-voice charter (`.claude/rules/prose-style.md`; depth in `devrites-prose-craft`): no
42
43
  filler openers, no marketing adjectives, exact commands and identifiers kept verbatim.
44
+ - **Honor declared project principles.** The wright reads `.devrites/principles.md` and treats
45
+ each invariant as **binding** (not a prior to weigh like a convention) — a slice it cannot build
46
+ without breaking one is an **Escalation**, not a silent violation. On return **you verify no
47
+ principle was broken**; a fresh violation is handled like any irreversible-risk item — a
48
+ human-approved, scoped exception in the register or a stop, never folded into the slice. No
49
+ `.devrites/principles.md` → none declared → nothing to honor.
43
50
  - **You never edit source — the wright is the only writer of code + tests.** You write only
44
51
  `.devrites/` bookkeeping. On any red gate, doubt finding, or coverage gap your only remedies
45
52
  are **continue the same wright once** (it fixes in its own context) or **stop + escalate** —
46
53
  never patch the code yourself. The `reconcile.sh` gate (step 6) enforces this by exit code:
47
54
  any source file changed outside the wright's claimed set is a hard STOP.
55
+ - **A `Forge: yes` slice competes candidates — one author still lands.** When `/rite-vet`
56
+ flagged the slice a genuine architecture fork, step 3 runs K=2–3 candidate wrights in
57
+ **isolated worktrees** and lands exactly one winner's diff; the single-writer invariant holds
58
+ because no tree ever has two authors and only the winner reaches the working tree. You still
59
+ never edit source, and reconcile runs against the winner's claimed set. The default slice is
60
+ single-path — forge is the rare exception ([`reference/forge.md`](reference/forge.md)).
48
61
 
49
62
  ## Workflow ([one-slice-cycle](reference/one-slice-cycle.md))
50
63
  0. **Rules + AFK + readiness check.** Read `.claude/rules/core.md` first. Then **run the
@@ -111,10 +124,25 @@ writes; read them yourself for the doubt/record gates or in the inline fallback:
111
124
  [ -f "$ST" ] || ST=pack/.claude/skills/devrites-lib/scripts/stuck.sh
112
125
  [ -f "$ST" ] && bash "$ST" log "$(cat .devrites/ACTIVE 2>/dev/null)" dispatch "<slice id>" || true
113
126
  ```
127
+ **Forge branch — only if the selected slice is `Forge: yes`.** Instead of the single dispatch
128
+ below, run the competitive build per [`reference/forge.md`](reference/forge.md): K=2–3 candidate
129
+ wrights, each in an **isolated git worktree** on the distinct strategy `/rite-vet` named, then a
130
+ fresh-context [`devrites-forge-judge`](../../agents/devrites-forge-judge.md) scores them against
131
+ acceptance + `test-plan.md` + `.devrites/principles.md` + the anti-slop charter, and you land
132
+ exactly **one** winner's diff in the working tree, graft any cheap runner-up improvement by
133
+ continuing the winning wright once, and write `forge-report.md`. Forge returns the **same shape**
134
+ a single wright does (one structured artifact, for the winner), so steps 4–7 (doubt, fail-on-red,
135
+ reconcile against the winner's claimed set, record, stop) run **unchanged**. If you cannot give
136
+ the judge an objective scorecard (the slice lacks acceptance / `test-plan.md` coverage) or cannot
137
+ name two genuinely different strategies, the flag is stale — clear it and build single-path. The
138
+ single-writer invariant is intact: each candidate owns its own tree, exactly one author's diff
139
+ lands. Then **stop** here for this slice; the steps below are the default single-path dispatch.
140
+
114
141
  Then assemble the slice contract and send it per
115
142
  [`reference/wright-dispatch.md`](reference/wright-dispatch.md): the slice goal, acceptance
116
143
  criteria, and **scope boundary**; the paths it may touch (`touched-files.md`); the context
117
- paths to read (`spec.md`, `plan.md`, `decisions.md`, `assumptions.md`, plus `test-plan.md`
144
+ paths to read (`spec.md`, `plan.md`, `decisions.md`, `assumptions.md`, `.devrites/principles.md`
145
+ when present — the binding invariants the slice must honor — plus `test-plan.md`
118
146
  when present — its per-gap test requirements + regression-criticals for this slice are the
119
147
  coverage the wright must write — and `design-brief.md`
120
148
  when the slice touches UI per [frontend-trigger](reference/frontend-trigger.md)); and the
@@ -146,6 +174,10 @@ writes; read them yourself for the doubt/record gates or in the inline fallback:
146
174
  blocking protocol violation — pause and re-dispatch with the item flagged out-of-bounds, do
147
175
  **not** doubt-and-accept it. (The wright's return is the not-yet-load-bearing moment — the
148
176
  slice isn't `built` or merged yet — so this post-return doubt is still pre-commit.)
177
+ **Principle check (same standing):** a wright return that breaks a declared principle
178
+ (reported in its `Principles` field, or that you detect against `.devrites/principles.md`) is
179
+ handled here like an irreversible-risk item — block, route to a human-approved scoped
180
+ exception in the register or stop; never doubt-and-accept a principle violation into the slice.
149
181
  5. **Fail-on-red.** If the wright's `Gates` were red (targeted tests / types / lint) or it
150
182
  couldn't verify: do **not** mark the slice `built`, and **do not fix the code yourself**.
151
183
  First remedy — **continue the same wright once** (`SendMessage` to it, carrying the failing
@@ -258,4 +290,7 @@ Next ▸ /rite-prove (prove the completed feature)
258
290
  Keep fact lines terse — one `key value` per fact, `·` between, no prose. The meter, the
259
291
  `✅ ALL BUILT` marker, and the ribbon carry the progress; don't restate them in words.
260
292
 
293
+ For a **forged slice**, the `Built` line names the competition and points at the record, e.g.
294
+ `Built slice 3 — csv-streaming (forged: 3 candidates → winner B; forge-report.md)`.
295
+
261
296
  **DO NOT continue to the next slice automatically** — even at `✅ ALL BUILT`, `/rite-prove` is the user's call.
@@ -15,6 +15,16 @@ generic AI naming, drive-by refactors): see
15
15
  | "Slices 1 and 2 are related; let me do both." | One slice, then stop. Full stop. The user asks for the next. |
16
16
  | "It's faster to skip `devrites-doubt` for this small change." | Doubt is cheapest *before* the decision is standing. After is debugging. |
17
17
 
18
+ ## Forge ([forge.md](forge.md))
19
+
20
+ | Excuse | Rebuttal |
21
+ |---|---|
22
+ | "This slice is hard — I'll forge it." | Hard ≠ forkable. Forge competes ≥2 *genuinely-different* approaches with no clear winner. One hard approach is single-path; building it K times wastes K× for one answer. |
23
+ | "I'm not sure which approach is right, so let me compete them and decide." | Forge is for a real fork the plan couldn't settle, not for dodging a decision you can make now. Decide from the code/docs first; forge only when the alternatives are genuinely undecidable on paper. |
24
+ | "Candidate A's data layer plus B's UI would be the best of both." | Don't hand-merge candidates — two authors in one tree is the incoherence the single-writer rule exists to prevent. Land one winner whole; graft a *specific* runner-up idea only by continuing the winning wright once. |
25
+ | "The judge already scored these, so I can skip the post-return doubt." | The judge picks *between* candidates; it doesn't replace doubting the winner's standing decisions. Steps 4–7 run on the winner unchanged. |
26
+ | "A fourth candidate might be even better." | K is capped at 3. A fourth rarely moves the winner and multiplies cost; if none of three is right, the plan is wrong — `/rite-plan repair`, don't add candidates. |
27
+
18
28
  ## Red Flags
19
29
 
20
30
  - About to start slice N+1 without the user asking.
@@ -23,3 +33,5 @@ generic AI naming, drive-by refactors): see
23
33
  - Writing a `try/catch` block wider than what you actually handle.
24
34
  - A comment that restates what the next line does.
25
35
  - Touching files that aren't in `touched-files.md` for "tidiness".
36
+ - Forging a slice with no acceptance / `test-plan.md` coverage for the judge to score against.
37
+ - Landing more than one candidate's code, or keeping a losing worktree/branch around after F7.