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.
- package/CHANGELOG.md +29 -0
- package/README.md +31 -17
- package/docs/architecture.md +30 -24
- package/docs/command-map.md +15 -6
- package/docs/flow.md +20 -6
- package/docs/orchestration.md +81 -0
- package/docs/skills.md +32 -8
- package/pack/.claude/agents/devrites-code-reviewer.md +40 -4
- package/pack/.claude/agents/devrites-devex-reviewer.md +94 -0
- package/pack/.claude/agents/devrites-forge-judge.md +92 -0
- package/pack/.claude/agents/devrites-frontend-reviewer.md +8 -0
- package/pack/.claude/agents/devrites-retrospector.md +91 -0
- package/pack/.claude/agents/devrites-security-auditor.md +21 -1
- package/pack/.claude/agents/devrites-slice-wright.md +18 -5
- package/pack/.claude/rules/README.md +7 -2
- package/pack/.claude/rules/afk-hitl.md +6 -0
- package/pack/.claude/rules/agents.md +30 -2
- package/pack/.claude/rules/anti-patterns.md +11 -0
- package/pack/.claude/rules/code-review.md +16 -6
- package/pack/.claude/rules/core.md +13 -4
- package/pack/.claude/rules/deprecation.md +50 -0
- package/pack/.claude/rules/developer-experience.md +119 -0
- package/pack/.claude/rules/observability.md +60 -0
- package/pack/.claude/rules/patterns.md +3 -0
- package/pack/.claude/rules/principles.md +158 -0
- package/pack/.claude/rules/security.md +31 -0
- package/pack/.claude/rules/spec-grammar.md +106 -0
- package/pack/.claude/skills/devrites-browser-proof/SKILL.md +42 -3
- package/pack/.claude/skills/devrites-lib/SKILL.md +9 -2
- package/pack/.claude/skills/devrites-lib/scripts/devrites.sh +8 -0
- package/pack/.claude/skills/devrites-lib/scripts/spec-validate.sh +130 -0
- package/pack/.claude/skills/rite-adopt/SKILL.md +13 -1
- package/pack/.claude/skills/rite-autocomplete/SKILL.md +3 -1
- package/pack/.claude/skills/rite-build/SKILL.md +36 -1
- package/pack/.claude/skills/rite-build/reference/anti-patterns.md +12 -0
- package/pack/.claude/skills/rite-build/reference/forge.md +171 -0
- package/pack/.claude/skills/rite-build/reference/one-slice-cycle.md +2 -0
- package/pack/.claude/skills/rite-build/reference/wright-dispatch.md +22 -2
- package/pack/.claude/skills/rite-define/SKILL.md +14 -1
- package/pack/.claude/skills/rite-frame/SKILL.md +7 -2
- package/pack/.claude/skills/rite-learn/SKILL.md +17 -4
- package/pack/.claude/skills/rite-polish/SKILL.md +3 -1
- package/pack/.claude/skills/rite-prove/SKILL.md +40 -1
- package/pack/.claude/skills/rite-quick/SKILL.md +9 -4
- package/pack/.claude/skills/rite-review/SKILL.md +5 -2
- package/pack/.claude/skills/rite-review/reference/five-axis-review.md +15 -1
- package/pack/.claude/skills/rite-seal/SKILL.md +33 -4
- package/pack/.claude/skills/rite-ship/SKILL.md +21 -0
- package/pack/.claude/skills/rite-spec/SKILL.md +29 -5
- package/pack/.claude/skills/rite-spec/reference/spec-template.md +11 -1
- package/pack/.claude/skills/rite-status/SKILL.md +3 -1
- package/pack/.claude/skills/rite-vet/SKILL.md +46 -14
- package/package.json +1 -1
- package/scripts/check-cross-refs.py +4 -0
- package/scripts/run-behavioral-evals.sh +170 -0
- package/scripts/run-evals.sh +3 -0
- 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) · **
|
|
49
|
-
|
|
50
|
-
|
|
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` / `
|
|
74
|
-
`use`), so any agent or human can drive `.devrites/` without the skill
|
|
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`,
|
|
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.
|