devrites 3.0.0 → 3.0.1

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 CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  All notable changes to DevRites are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and DevRites adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Releases are generated automatically by [semantic-release](https://semantic-release.gitbook.io/) from Conventional Commits on `main`.
4
4
 
5
+ ## [3.0.1](https://github.com/ViktorsBaikers/DevRites/compare/v3.0.0...v3.0.1) (2026-07-20)
6
+
7
+ ### Fixed
8
+
9
+ * **rite:** run analysis after task definition ([6373227](https://github.com/ViktorsBaikers/DevRites/commit/6373227521b5e1e6b22bfe48e98799e709f5dc7a))
10
+
5
11
  ## [3.0.0](https://github.com/ViktorsBaikers/DevRites/compare/v2.6.1...v3.0.0) (2026-07-17)
6
12
 
7
13
  ### ⚠ BREAKING CHANGES
package/README.md CHANGED
@@ -115,7 +115,7 @@ Full diagram set (lifecycle, polish orchestrator, review fan-out, debug loop,
115
115
  rules carrier, workspace state, namespace map) →
116
116
  [`docs/flow.md`](docs/flow.md).
117
117
 
118
- **Status:** [`v3.0.0`](https://github.com/ViktorsBaikers/DevRites/releases/tag/v3.0.0) — see [`CHANGELOG.md`](CHANGELOG.md) for release notes.
118
+ **Status:** [`v3.0.1`](https://github.com/ViktorsBaikers/DevRites/releases/tag/v3.0.1) — see [`CHANGELOG.md`](CHANGELOG.md) for release notes.
119
119
 
120
120
  ## Contents
121
121
 
@@ -159,7 +159,7 @@ Prohibitions tables — a blocking spec-gate check.
159
159
  any code is written, so a coverage gap surfaces as a one-line plan edit instead of a reslice
160
160
  mid-build. It emits a markdown report with four passes:
161
161
 
162
- - **Coverage** — a spec `[ACn]` that no slice `Satisfies:` (**CRITICAL**).
162
+ - **Coverage** — a spec `AC-###` that no slice `Satisfies:` (**CRITICAL**; legacy `[ACn]` remains supported).
163
163
  - **Consistency** — a slice that `Satisfies:` an AC the spec never defines (**CRITICAL**).
164
164
  - **Orphan slice** — a slice satisfying no acceptance criterion (warn).
165
165
  - **Ambiguity** — an unquantified vague adjective (`fast`, `robust`, `intuitive`, …) or an
@@ -15,7 +15,7 @@ import (
15
15
  // written, so a coverage gap surfaces as a one-line plan edit rather than a reslice
16
16
  // mid-build. It emits a markdown report and flags:
17
17
  //
18
- // Coverage — a spec [ACn] that no slice Satisfies (CRITICAL)
18
+ // Coverage — a spec AC id that no slice Satisfies (CRITICAL)
19
19
  // Consistency — a slice that Satisfies an AC the spec never defines (CRITICAL)
20
20
  // Orphan slice — a slice that satisfies no acceptance criterion (warn)
21
21
  // Ambiguity — an unquantified vague adjective or unresolved
@@ -27,9 +27,8 @@ import (
27
27
  // Exit codes: 0 clear · 1 at least one CRITICAL finding · 2 no workspace (no active
28
28
  // slug, or spec.md/tasks.md missing).
29
29
  var (
30
- specACRe = regexp.MustCompile(`\[AC[0-9]+\]`) // a bracketed [ACn] in the spec
31
- taskACRe = regexp.MustCompile(`\bAC[0-9]+\b`) // a bare ACn in the tasks
32
- sliceHeadRe = regexp.MustCompile(`^##[[:space:]]*Slice`)
30
+ taskACRe = regexp.MustCompile(`\b(?:AC-[0-9]{3}|AC[0-9]+)\b`)
31
+ sliceHeadRe = regexp.MustCompile(`(?i)^##[[:space:]]*(?:SLICE-[0-9]{3}\b|Slice\b)`)
33
32
  sliceNamePrefixRe = regexp.MustCompile(`^##[[:space:]]*`) // stripped to leave the slice name
34
33
  analyzeSatisfiesRe = regexp.MustCompile(`^[[:space:]]*Satisfies:`)
35
34
  // Ambiguity scan: a vague quality adjective with no number on the line is an
@@ -57,7 +56,7 @@ func Analyze(root string, args []string, stdout, stderr io.Writer) int {
57
56
  specData, _ := os.ReadFile(spec)
58
57
  tasksData, _ := os.ReadFile(tasks)
59
58
 
60
- specACs := sortedACIDs(specACRe, specData, true) // brackets stripped: "[AC1]" -> "AC1"
59
+ specACs := sortedACIDs(acIDRe, specData, true) // legacy brackets stripped: "[AC1]" -> "AC1"
61
60
  taskACs := sortedACIDs(taskACRe, tasksData, false)
62
61
  specSet := stringSet(specACs)
63
62
  taskSet := stringSet(taskACs)
@@ -70,7 +69,7 @@ func Analyze(root string, args []string, stdout, stderr io.Writer) int {
70
69
  covered := 0
71
70
  fmt.Fprintln(stdout, "## Coverage")
72
71
  if len(specACs) == 0 {
73
- fmt.Fprintln(stdout, "- [warn] spec.md — no [ACn] acceptance ids found; tag criteria as '[AC1] ...' for a machine-checkable gate.")
72
+ fmt.Fprintln(stdout, "- [warn] spec.md — no AC-### acceptance ids found; tag criteria for a machine-checkable gate.")
74
73
  } else {
75
74
  for _, ac := range specACs {
76
75
  if taskSet[ac] {
@@ -155,9 +154,9 @@ func ambiguityFindings(specData []byte) []string {
155
154
  out = append(out, fmt.Sprintf("- [warn] spec.md:L%d — unresolved placeholder %q", ln, m))
156
155
  continue
157
156
  }
158
- // Strip the AC id before the digit gate — else the id's own digit
159
- // ("[AC1]") masks a vague adjective on the very criterion line it tags.
160
- bare := specACRe.ReplaceAllString(line, "")
157
+ // Strip the AC id before the digit gate — else the id's own digit masks
158
+ // a vague adjective on the very criterion line it tags.
159
+ bare := acIDRe.ReplaceAllString(line, "")
161
160
  bare = taskACRe.ReplaceAllString(bare, "")
162
161
  if !analyzeDigitRe.MatchString(bare) {
163
162
  if m := vagueAdjRe.FindString(bare); m != "" {
@@ -170,7 +169,7 @@ func ambiguityFindings(specData []byte) []string {
170
169
 
171
170
  // sortedACIDs collects every acceptance id matched by re, deduplicated and sorted
172
171
  // lexicographically. When strip is set the surrounding brackets are removed, so
173
- // "[AC1]" becomes "AC1". Returns nil when there are none.
172
+ // Legacy "[AC1]" becomes "AC1". Returns nil when there are none.
174
173
  func sortedACIDs(re *regexp.Regexp, data []byte, strip bool) []string {
175
174
  seen := map[string]bool{}
176
175
  var out []string
@@ -10,17 +10,17 @@ import (
10
10
  )
11
11
 
12
12
  // Executable acceptance criteria — a port of check-acceptance.sh. Grades the
13
- // [ACn]-tagged acceptance criteria in spec.md against seal.md: each spec
13
+ // AC-### acceptance criteria in spec.md against seal.md: each spec
14
14
  // criterion must be checked ([x]) in seal.md. Without ids it falls back to a
15
15
  // count/unchecked heuristic. The spec-time mirror is SpecValidate (shape); this
16
- // grades proof.
16
+ // grades proof. Legacy [ACn] ids remain supported for existing workspaces.
17
17
  //
18
18
  // Exit codes: 0 every criterion proven · 1 gap (uncovered/unproven) · 2 usage ·
19
19
  // 5 missing spec.md or seal.md.
20
20
  var (
21
21
  acHeadingRe = regexp.MustCompile(`^##[[:space:]]+[Aa]cceptance [Cc]riteria`)
22
22
  h2Re = regexp.MustCompile(`^##[[:space:]]`)
23
- acIDRe = regexp.MustCompile(`\[AC[0-9]+\]`)
23
+ acIDRe = regexp.MustCompile(`\[AC[0-9]+\]|\bAC-[0-9]{3}\b`)
24
24
  checkedRe = regexp.MustCompile(`^[[:space:]]*-[[:space:]]*\[[xX]\]`)
25
25
  bulletRe = regexp.MustCompile(`^[[:space:]]*-[[:space:]]`)
26
26
  checkboxRe = regexp.MustCompile(`^[[:space:]]*-[[:space:]]*\[[ xX]\]`)
@@ -64,7 +64,7 @@ func CheckAcceptance(ws string, stdout, stderr io.Writer) int {
64
64
  specIDs := sortedUniqueIDs(specBody)
65
65
 
66
66
  if len(specIDs) > 0 {
67
- // ID mode — each spec [ACn] must be checked ([x]) in seal.md.
67
+ // ID mode — each spec AC id must be checked ([x]) in seal.md.
68
68
  checkedIDs := map[string]bool{}
69
69
  for _, line := range sealBody {
70
70
  if checkedRe.MatchString(line) {
@@ -96,15 +96,15 @@ func CheckAcceptance(ws string, stdout, stderr io.Writer) int {
96
96
  sealUnchecked := countMatching(sealBody, uncheckedRe)
97
97
 
98
98
  if sealTotal < specN {
99
- fmt.Fprintf(stderr, "check-acceptance: seal.md lists %d acceptance items but spec.md has %d criteria — %d dropped. (Tag criteria [AC1].. to grade by id.)\n",
99
+ fmt.Fprintf(stderr, "check-acceptance: seal.md lists %d acceptance items but spec.md has %d criteria — %d dropped. (Tag criteria AC-### to grade by id.)\n",
100
100
  sealTotal, specN, specN-sealTotal)
101
101
  return 1
102
102
  }
103
103
  if sealUnchecked > 0 {
104
- fmt.Fprintf(stderr, "check-acceptance: %d acceptance criterion(s) unchecked in seal.md (unproven). (Tag criteria [AC1].. to grade by id.)\n", sealUnchecked)
104
+ fmt.Fprintf(stderr, "check-acceptance: %d acceptance criterion(s) unchecked in seal.md (unproven). (Tag criteria AC-### to grade by id.)\n", sealUnchecked)
105
105
  return 1
106
106
  }
107
- fmt.Fprintf(stdout, "check-acceptance: OK — %d acceptance items all checked in seal.md (no ids; add [AC1].. for id-level grading)\n", sealTotal)
107
+ fmt.Fprintf(stdout, "check-acceptance: OK — %d acceptance items all checked in seal.md (no ids; add AC-### for id-level grading)\n", sealTotal)
108
108
  return 0
109
109
  }
110
110
 
@@ -145,8 +145,8 @@ func sectionEmpty(lines []string) bool {
145
145
  return strings.TrimRight(strings.Join(lines, "\n"), "\n") == ""
146
146
  }
147
147
 
148
- // sortedUniqueIDs extracts every [ACn] id from the lines and returns them sorted
149
- // and de-duplicated, matching `grep -oE '\[AC[0-9]+\]' | sort -u`.
148
+ // sortedUniqueIDs extracts canonical AC-### and legacy [ACn] ids from the lines,
149
+ // then returns them sorted and de-duplicated.
150
150
  func sortedUniqueIDs(lines []string) []string {
151
151
  set := map[string]bool{}
152
152
  for _, line := range lines {
@@ -18,9 +18,9 @@ import (
18
18
  // criterion is covered by a slice and proven in the seal. Read-only.
19
19
  //
20
20
  // The convention pairs with check-acceptance: a criterion carries a stable id,
21
- // spec.md declares `- [ ] [ACn] …`, a tasks.md slice claims it with a
22
- // `Satisfies: ACn` line, and seal.md proves it with `- [x] [ACn] …`. A criterion
23
- // is proven iff its `[ACn]` id is checked in seal.md.
21
+ // spec.md declares `- [ ] AC-###: …`, a tasks.md slice claims it with a
22
+ // `Satisfies: AC-###` line, and seal.md proves it with `- [x] AC-###: …`.
23
+ // Legacy [ACn] ids remain supported for existing workspaces.
24
24
  //
25
25
  // base is the project directory that contains the .devrites tree; slug defaults
26
26
  // to the active feature named in .devrites/ACTIVE. The matrix is the stdout
@@ -83,16 +83,14 @@ func Coverage(root string, args []string, stdout, stderr io.Writer) int {
83
83
  return 0
84
84
  }
85
85
 
86
- // acIDRe (`\[AC[0-9]+\]`) is shared with check-acceptance, which declares it.
86
+ // acIDRe is shared with check-acceptance, which declares it.
87
87
  var (
88
- sliceHeaderRe = regexp.MustCompile(`^##[[:space:]]*Slice`)
89
- sliceStripRe = regexp.MustCompile(`^##[[:space:]]*`)
90
- satisfiesRe = regexp.MustCompile(`^[[:space:]]*Satisfies:`)
88
+ sliceStripRe = regexp.MustCompile(`^##[[:space:]]*`)
89
+ satisfiesRe = regexp.MustCompile(`^[[:space:]]*Satisfies:`)
91
90
  )
92
91
 
93
- // acIDs returns the unique `[ACn]` ids declared in spec.md, bracket-stripped and
94
- // byte-order sorted — mirroring `grep -oE '\[AC[0-9]+\]' | tr -d '[]' | sort -u`
95
- // under LC_ALL=C.
92
+ // acIDs returns unique canonical AC-### and legacy [ACn] ids declared in
93
+ // spec.md, bracket-stripped and byte-order sorted.
96
94
  func acIDs(spec string) []string {
97
95
  seen := map[string]bool{}
98
96
  var ids []string
@@ -118,7 +116,7 @@ func slicesForAC(tasks, ac string) string {
118
116
  sc := newLineScanner(strings.NewReader(tasks))
119
117
  for sc.Scan() {
120
118
  line := sc.Text()
121
- if sliceHeaderRe.MatchString(line) {
119
+ if sliceHeadRe.MatchString(line) {
122
120
  cur = sliceStripRe.ReplaceAllString(line, "")
123
121
  }
124
122
  if satisfiesRe.MatchString(line) && strings.Contains(line, ac) {
@@ -128,11 +126,11 @@ func slicesForAC(tasks, ac string) string {
128
126
  return strings.Join(matched, ";")
129
127
  }
130
128
 
131
- // provenInSeal reports whether seal.md checks off ac's id — a line of the form
132
- // `- [x] [ACn]` (with optional surrounding whitespace), the same pattern the
133
- // script greps for.
129
+ // provenInSeal reports whether seal.md contains a checked canonical or legacy
130
+ // criterion line.
134
131
  func provenInSeal(seal, ac string) bool {
135
- re := regexp.MustCompile(`(?m)^[[:space:]]*-[[:space:]]*\[x\][[:space:]]*\[` + regexp.QuoteMeta(ac) + `\]`)
132
+ quoted := regexp.QuoteMeta(ac)
133
+ re := regexp.MustCompile(`(?m)^[[:space:]]*-[[:space:]]*\[x\][[:space:]]*(?:\[` + quoted + `\]|` + quoted + `\b)`)
136
134
  return re.MatchString(seal)
137
135
  }
138
136
 
@@ -83,7 +83,7 @@ func parseLaneSlices(md string) []laneSlice {
83
83
  }
84
84
  for _, line := range strings.Split(md, "\n") {
85
85
  trim := strings.TrimSpace(line)
86
- if strings.HasPrefix(trim, "## Slice") {
86
+ if sliceHeadRe.MatchString(trim) {
87
87
  flush()
88
88
  cur = &laneSlice{name: strings.TrimPrefix(trim, "## ")}
89
89
  continue
@@ -2,7 +2,7 @@ exit 0
2
2
  # Cross-artifact analysis: noac
3
3
 
4
4
  ## Coverage
5
- - [warn] spec.md — no [ACn] acceptance ids found; tag criteria as '[AC1] ...' for a machine-checkable gate.
5
+ - [warn] spec.md — no AC-### acceptance ids found; tag criteria for a machine-checkable gate.
6
6
 
7
7
  ## Consistency
8
8
 
@@ -1,2 +1,2 @@
1
1
  exit 0
2
- check-acceptance: OK — 2 acceptance items all checked in seal.md (no ids; add [AC1].. for id-level grading)
2
+ check-acceptance: OK — 2 acceptance items all checked in seal.md (no ids; add AC-### for id-level grading)
@@ -0,0 +1,44 @@
1
+ package main_test
2
+
3
+ import (
4
+ "strings"
5
+ "testing"
6
+ )
7
+
8
+ func TestCanonicalAcceptanceIDsAcrossGates(t *testing.T) {
9
+ work := t.TempDir()
10
+ writeFeatureFile(t, work, "canonical", "spec.md",
11
+ "## Acceptance criteria\n- [ ] AC-001: alpha\n- [ ] AC-002: beta\n")
12
+ writeFeatureFile(t, work, "canonical", "tasks.md",
13
+ "## SLICE-001 alpha\nSatisfies: AC-001\n## SLICE-002 beta\nSatisfies: AC-002\n## SLICE-003 orphan\n")
14
+ writeFeatureFile(t, work, "canonical", "seal.md",
15
+ "## Acceptance Criteria\n- [x] AC-001: alpha — evidence: t1\n- [x] AC-002: beta — evidence: t2\n")
16
+
17
+ t.Run("analyze", func(t *testing.T) {
18
+ stdout, stderr, code := runGo(t, work, "analyze", "canonical")
19
+ if code != 0 || !strings.Contains(stdout, "AC-001 — covered") || !strings.Contains(stdout, "slice 'SLICE-003 orphan'") {
20
+ t.Fatalf("exit=%d stdout=%q stderr=%q", code, stdout, stderr)
21
+ }
22
+ })
23
+
24
+ t.Run("coverage", func(t *testing.T) {
25
+ stdout, stderr, code := runGo(t, work, "coverage", "canonical")
26
+ if code != 0 || !strings.Contains(stdout, "| AC-001 | SLICE-001 alpha | yes |") {
27
+ t.Fatalf("exit=%d stdout=%q stderr=%q", code, stdout, stderr)
28
+ }
29
+ })
30
+
31
+ t.Run("check-acceptance", func(t *testing.T) {
32
+ stdout, stderr, code := runGo(t, work, "check-acceptance", ".devrites/features/canonical")
33
+ if code != 0 || !strings.Contains(stdout, "AC-001 AC-002") {
34
+ t.Fatalf("exit=%d stdout=%q stderr=%q", code, stdout, stderr)
35
+ }
36
+ })
37
+
38
+ t.Run("lanes", func(t *testing.T) {
39
+ stdout, stderr, code := runGo(t, work, "lanes", "plan", "canonical")
40
+ if code != 0 || !strings.Contains(stdout, "- SLICE-001 alpha:") {
41
+ t.Fatalf("exit=%d stdout=%q stderr=%q", code, stdout, stderr)
42
+ }
43
+ })
44
+ }
@@ -119,6 +119,12 @@ Pull these via `Read` when shaping the plan:
119
119
  as a blocking gate at `/rite-vet`; no file → none declared → nothing to check.)
120
120
  6. **Write** `architecture.md`, `plan.md`, `tasks.md`, and `traceability.md`; update
121
121
  `state.md` (phase: plan → next `/rite-vet`).
122
+ 6a. **Cross-artifact gate — now that `tasks.md` exists.** Run the deterministic
123
+ spec↔tasks coverage/consistency check; any non-zero result blocks plan readiness:
124
+ ```bash
125
+ S="$(cat .devrites/ACTIVE 2>/dev/null)"
126
+ devrites-engine analyze "$S"
127
+ ```
122
128
  7. **Readiness gate** (bottom of plan-template): every acceptance criterion covered by a
123
129
  slice, dependency order acyclic + risk-first, no unjustified deviation, rollback for
124
130
  every destructive/migration step. **Stop and confirm** before code. Render the review-before-code
@@ -185,6 +185,8 @@ stay flat `AC-###` bullets; the grammar is opt-in by rigor, never forced.
185
185
  devrites-engine spec-skeleton ".devrites/work/<slug>"
186
186
  devrites-engine spec-validate ".devrites/work/<slug>" --against .devrites/specs
187
187
  ```
188
+ **Do not run `devrites-engine analyze` in this phase:** `tasks.md` deliberately does not
189
+ exist yet. `/rite-define` owns the first analyze pass after it writes the slices.
188
190
  Treat edge/prohibition findings as blocking just like grammar findings. When it passes, write `Spec gate: passed <iso>` to `state.md`.
189
191
  6a. **Review-before-code digest.** Before handing off to planning, render the cheap human review:
190
192
  `Intent` (one sentence), `Done means` (top acceptance/scenario IDs), `Scope/risk` (what is in/out
@@ -140,7 +140,12 @@ Audit for a plan resting on unstated beliefs).
140
140
  (`/rite-build` and `/rite-prove` read it). Harden `plan.md` / `tasks.md` directly for
141
141
  behavior-preserving refinements; route every acceptance/behavior-changing delta through the
142
142
  **Spec Drift Guard** (`drift.md` + recorded decision + `/rite-plan repair`). Append
143
- `decisions.md` (one ADR per material call) and `assumptions.md`. Update `state.md`:
143
+ `decisions.md` (one ADR per material call) and `assumptions.md`. Re-run the gate after
144
+ every fold-back so a task edit cannot invalidate the earlier pass:
145
+ ```bash
146
+ devrites-engine analyze; echo "final analyze rc=$?"
147
+ ```
148
+ Any non-zero result blocks the handoff. Then update `state.md`:
144
149
  `Phase: vet`, `Next step: /rite-build`; on a blocking pause write the `Awaiting human` block +
145
150
  `Status: awaiting_human` before stopping.
146
151
  6. **Adversarial verification loop.** Apply the light/full branching contract in
@@ -119,6 +119,12 @@ Pull these via `Read` when shaping the plan:
119
119
  as a blocking gate at `/rite-vet`; no file → none declared → nothing to check.)
120
120
  6. **Write** `architecture.md`, `plan.md`, `tasks.md`, and `traceability.md`; update
121
121
  `state.md` (phase: plan → next `/rite-vet`).
122
+ 6a. **Cross-artifact gate — now that `tasks.md` exists.** Run the deterministic
123
+ spec↔tasks coverage/consistency check; any non-zero result blocks plan readiness:
124
+ ```bash
125
+ S="$(cat .devrites/ACTIVE 2>/dev/null)"
126
+ devrites-engine analyze "$S"
127
+ ```
122
128
  7. **Readiness gate** (bottom of plan-template): every acceptance criterion covered by a
123
129
  slice, dependency order acyclic + risk-first, no unjustified deviation, rollback for
124
130
  every destructive/migration step. **Stop and confirm** before code. Render the review-before-code
@@ -185,6 +185,8 @@ stay flat `AC-###` bullets; the grammar is opt-in by rigor, never forced.
185
185
  devrites-engine spec-skeleton ".devrites/work/<slug>"
186
186
  devrites-engine spec-validate ".devrites/work/<slug>" --against .devrites/specs
187
187
  ```
188
+ **Do not run `devrites-engine analyze` in this phase:** `tasks.md` deliberately does not
189
+ exist yet. `/rite-define` owns the first analyze pass after it writes the slices.
188
190
  Treat edge/prohibition findings as blocking just like grammar findings. When it passes, write `Spec gate: passed <iso>` to `state.md`.
189
191
  6a. **Review-before-code digest.** Before handing off to planning, render the cheap human review:
190
192
  `Intent` (one sentence), `Done means` (top acceptance/scenario IDs), `Scope/risk` (what is in/out
@@ -140,7 +140,12 @@ Audit for a plan resting on unstated beliefs).
140
140
  (`/rite-build` and `/rite-prove` read it). Harden `plan.md` / `tasks.md` directly for
141
141
  behavior-preserving refinements; route every acceptance/behavior-changing delta through the
142
142
  **Spec Drift Guard** (`drift.md` + recorded decision + `/rite-plan repair`). Append
143
- `decisions.md` (one ADR per material call) and `assumptions.md`. Update `state.md`:
143
+ `decisions.md` (one ADR per material call) and `assumptions.md`. Re-run the gate after
144
+ every fold-back so a task edit cannot invalidate the earlier pass:
145
+ ```bash
146
+ devrites-engine analyze; echo "final analyze rc=$?"
147
+ ```
148
+ Any non-zero result blocks the handoff. Then update `state.md`:
144
149
  `Phase: vet`, `Next step: /rite-build`; on a blocking pause write the `Awaiting human` block +
145
150
  `Status: awaiting_human` before stopping.
146
151
  6. **Adversarial verification loop.** Apply the light/full branching contract in
@@ -131,6 +131,12 @@ Pull these via `Read` when shaping the plan:
131
131
  as a blocking gate at `$rite-vet`; no file → none declared → nothing to check.)
132
132
  6. **Write** `architecture.md`, `plan.md`, `tasks.md`, and `traceability.md`; update
133
133
  `state.md` (phase: plan → next `$rite-vet`).
134
+ 6a. **Cross-artifact gate — now that `tasks.md` exists.** Run the deterministic
135
+ spec↔tasks coverage/consistency check; any non-zero result blocks plan readiness:
136
+ ```bash
137
+ S="$(cat .devrites/ACTIVE 2>/dev/null)"
138
+ devrites-engine analyze "$S"
139
+ ```
134
140
  7. **Readiness gate** (bottom of plan-template): every acceptance criterion covered by a
135
141
  slice, dependency order acyclic + risk-first, no unjustified deviation, rollback for
136
142
  every destructive/migration step. **Stop and confirm** before code. Render the review-before-code
@@ -197,6 +197,8 @@ stay flat `AC-###` bullets; the grammar is opt-in by rigor, never forced.
197
197
  devrites-engine spec-skeleton ".devrites/work/<slug>"
198
198
  devrites-engine spec-validate ".devrites/work/<slug>" --against .devrites/specs
199
199
  ```
200
+ **Do not run `devrites-engine analyze` in this phase:** `tasks.md` deliberately does not
201
+ exist yet. `$rite-define` owns the first analyze pass after it writes the slices.
200
202
  Treat edge/prohibition findings as blocking just like grammar findings. When it passes, write `Spec gate: passed <iso>` to `state.md`.
201
203
  6a. **Review-before-code digest.** Before handing off to planning, render the cheap human review:
202
204
  `Intent` (one sentence), `Done means` (top acceptance/scenario IDs), `Scope/risk` (what is in/out
@@ -152,7 +152,12 @@ Audit for a plan resting on unstated beliefs).
152
152
  (`$rite-build` and `$rite-prove` read it). Harden `plan.md` / `tasks.md` directly for
153
153
  behavior-preserving refinements; route every acceptance/behavior-changing delta through the
154
154
  **Spec Drift Guard** (`drift.md` + recorded decision + `$rite-plan repair`). Append
155
- `decisions.md` (one ADR per material call) and `assumptions.md`. Update `state.md`:
155
+ `decisions.md` (one ADR per material call) and `assumptions.md`. Re-run the gate after
156
+ every fold-back so a task edit cannot invalidate the earlier pass:
157
+ ```bash
158
+ devrites-engine analyze; echo "final analyze rc=$?"
159
+ ```
160
+ Any non-zero result blocks the handoff. Then update `state.md`:
156
161
  `Phase: vet`, `Next step: $rite-build`; on a blocking pause write the `Awaiting human` block +
157
162
  `Status: awaiting_human` before stopping.
158
163
  6. **Adversarial verification loop.** Apply the light/full branching contract in
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devrites",
3
- "version": "3.0.0",
3
+ "version": "3.0.1",
4
4
  "description": "DevRites — disciplined senior-engineer workflow skills pack for Claude Code and Codex",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "homepage": "https://github.com/ViktorsBaikers/DevRites#readme",