devrites 4.5.0 → 4.7.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 (191) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/NOTICE.md +0 -13
  3. package/README.md +4 -3
  4. package/docs/adr/0029-v5-workspace-schema-and-native-migration.md +62 -0
  5. package/docs/adr/README.md +3 -2
  6. package/docs/architecture.md +1 -1
  7. package/docs/cli.md +2 -2
  8. package/docs/command-map.md +2 -2
  9. package/docs/engine/commands.md +9 -0
  10. package/docs/engine/state-schema.md +2 -2
  11. package/docs/engine/workspace-schema.md +9 -3
  12. package/docs/usage.md +1 -1
  13. package/engine/commands.go +23 -0
  14. package/engine/internal/fsutil/copy_test.go +193 -0
  15. package/engine/internal/gate/gate_test.go +7 -7
  16. package/engine/internal/gate/readiness_binding_test.go +8 -2
  17. package/engine/internal/install/apply.go +314 -0
  18. package/engine/internal/install/binary.go +255 -0
  19. package/engine/internal/install/install.go +0 -1596
  20. package/engine/internal/install/manifest.go +109 -0
  21. package/engine/internal/install/merge.go +468 -0
  22. package/engine/internal/install/preflight.go +234 -0
  23. package/engine/internal/install/uninstall.go +119 -0
  24. package/engine/internal/install/update.go +184 -0
  25. package/engine/internal/lib/candidate_test.go +3 -0
  26. package/engine/internal/lib/closeout.go +4 -0
  27. package/engine/internal/lib/closeout_test.go +3 -3
  28. package/engine/internal/lib/migrate.go +219 -0
  29. package/engine/internal/lib/migrate_test.go +154 -0
  30. package/engine/internal/lib/resolve.go +3 -0
  31. package/engine/internal/lib/resolve_remediation_test.go +1 -1
  32. package/engine/internal/lib/secretscan_test.go +1 -0
  33. package/engine/internal/lib/taskgraph_test.go +2 -2
  34. package/engine/internal/parallel/ops.go +67 -16
  35. package/engine/internal/parallel/ops_test.go +211 -0
  36. package/engine/internal/state/cmd/workflowmanifest/main.go +24 -13
  37. package/engine/internal/state/cmd/workflowmanifest/main_test.go +96 -0
  38. package/engine/internal/state/cursor.go +74 -0
  39. package/engine/internal/state/schema.go +3 -2
  40. package/engine/internal/state/state_test.go +2 -2
  41. package/engine/internal/state/workflow_manifest.json +1 -1
  42. package/engine/internal/state/workspaceschema.go +58 -0
  43. package/engine/internal/state/workspaceschema_test.go +67 -0
  44. package/engine/main.go +7 -31
  45. package/engine/root_routing_test.go +10 -2
  46. package/engine/testdata/fixtures/basic/devrites-root/work/auth-tokens/state.md +1 -0
  47. package/engine/testdata/fixtures/basic/devrites-root/work/search-ranking/state.md +1 -0
  48. package/engine/testdata/golden/TestParityResolve/answer/state.md.golden +1 -0
  49. package/engine/testdata/golden/TestParityResolve/drop/state.md.golden +1 -0
  50. package/engine/tests/parity_closeout_test.go +3 -0
  51. package/engine/tests/parity_resolve_test.go +1 -0
  52. package/pack/.claude/agents/devrites-code-reviewer.md +8 -0
  53. package/pack/.claude/agents/devrites-devex-reviewer.md +7 -0
  54. package/pack/.claude/agents/devrites-doubt-reviewer.md +7 -0
  55. package/pack/.claude/agents/devrites-evidence-scout.md +4 -0
  56. package/pack/.claude/agents/devrites-frontend-reviewer.md +8 -0
  57. package/pack/.claude/agents/devrites-performance-reviewer.md +7 -0
  58. package/pack/.claude/agents/devrites-plan-reviewer.md +8 -0
  59. package/pack/.claude/agents/devrites-retrospector.md +2 -0
  60. package/pack/.claude/agents/devrites-security-auditor.md +7 -0
  61. package/pack/.claude/agents/devrites-simplifier-reviewer.md +7 -0
  62. package/pack/.claude/agents/devrites-spec-reviewer.md +7 -0
  63. package/pack/.claude/agents/devrites-strategy-reviewer.md +7 -0
  64. package/pack/.claude/agents/devrites-test-analyst.md +7 -0
  65. package/pack/.claude/skills/devrites-frontend-craft/SKILL.md +4 -7
  66. package/pack/.claude/skills/devrites-frontend-craft/reference/quality-standards.md +9 -4
  67. package/pack/.claude/skills/devrites-lib/reference/intent-map.md +7 -0
  68. package/pack/.claude/skills/devrites-lib/reference/parallel-dispatch.md +1 -1
  69. package/pack/.claude/skills/devrites-lib/reference/reply-contract.md +9 -7
  70. package/pack/.claude/skills/devrites-lib/reference/standards/agents.md +1 -1
  71. package/pack/.claude/skills/devrites-lib/reference/standards/code-review.md +6 -1
  72. package/pack/.claude/skills/devrites-lib/reference/standards/coding-style.md +4 -3
  73. package/pack/.claude/skills/devrites-lib/reference/standards/context-hygiene.md +4 -0
  74. package/pack/.claude/skills/devrites-lib/reference/standards/core.md +3 -6
  75. package/pack/.claude/skills/devrites-lib/reference/standards/data-integrity.md +4 -0
  76. package/pack/.claude/skills/devrites-lib/reference/standards/development-workflow.md +8 -0
  77. package/pack/.claude/skills/devrites-lib/reference/standards/documentation.md +6 -0
  78. package/pack/.claude/skills/devrites-lib/reference/standards/edge-case-trace.md +3 -1
  79. package/pack/.claude/skills/devrites-lib/reference/standards/elicitation.md +5 -0
  80. package/pack/.claude/skills/devrites-lib/reference/standards/error-handling.md +14 -0
  81. package/pack/.claude/skills/devrites-lib/reference/standards/integration-reliability.md +18 -6
  82. package/pack/.claude/skills/devrites-lib/reference/standards/patterns.md +16 -0
  83. package/pack/.claude/skills/devrites-lib/reference/standards/performance.md +4 -0
  84. package/pack/.claude/skills/devrites-lib/reference/standards/repository-topology.md +6 -0
  85. package/pack/.claude/skills/devrites-lib/reference/standards/security-checklist.md +3 -0
  86. package/pack/.claude/skills/devrites-lib/reference/standards/security.md +10 -2
  87. package/pack/.claude/skills/devrites-lib/reference/standards/skill-authoring.md +8 -0
  88. package/pack/.claude/skills/devrites-lib/reference/standards/spec-grammar.md +5 -6
  89. package/pack/.claude/skills/devrites-lib/reference/standards/testing.md +2 -2
  90. package/pack/.claude/skills/devrites-lib/reference/standards/tooling.md +1 -1
  91. package/pack/.claude/skills/rite-define/reference/plan-template.md +5 -2
  92. package/pack/.claude/skills/rite-learn/SKILL.md +6 -0
  93. package/pack/.claude/skills/rite-polish/SKILL.md +4 -0
  94. package/pack/.claude/skills/rite-polish/reference/anti-ai-slop.md +12 -53
  95. package/pack/.claude/skills/rite-pr-feedback/SKILL.md +7 -2
  96. package/pack/.claude/skills/rite-spec/reference/spec-template.md +5 -4
  97. package/pack/.claude/skills/rite-status/SKILL.md +2 -0
  98. package/pack/generated/claude/agents/devrites-code-reviewer.md +8 -0
  99. package/pack/generated/claude/agents/devrites-devex-reviewer.md +7 -0
  100. package/pack/generated/claude/agents/devrites-doubt-reviewer.md +7 -0
  101. package/pack/generated/claude/agents/devrites-evidence-scout.md +4 -0
  102. package/pack/generated/claude/agents/devrites-frontend-reviewer.md +8 -0
  103. package/pack/generated/claude/agents/devrites-performance-reviewer.md +7 -0
  104. package/pack/generated/claude/agents/devrites-plan-reviewer.md +8 -0
  105. package/pack/generated/claude/agents/devrites-retrospector.md +2 -0
  106. package/pack/generated/claude/agents/devrites-security-auditor.md +7 -0
  107. package/pack/generated/claude/agents/devrites-simplifier-reviewer.md +7 -0
  108. package/pack/generated/claude/agents/devrites-spec-reviewer.md +7 -0
  109. package/pack/generated/claude/agents/devrites-strategy-reviewer.md +7 -0
  110. package/pack/generated/claude/agents/devrites-test-analyst.md +7 -0
  111. package/pack/generated/claude/skills/devrites-frontend-craft/SKILL.md +4 -7
  112. package/pack/generated/claude/skills/devrites-frontend-craft/reference/quality-standards.md +9 -4
  113. package/pack/generated/claude/skills/devrites-lib/reference/intent-map.md +7 -0
  114. package/pack/generated/claude/skills/devrites-lib/reference/parallel-dispatch.md +1 -1
  115. package/pack/generated/claude/skills/devrites-lib/reference/reply-contract.md +9 -7
  116. package/pack/generated/claude/skills/devrites-lib/reference/standards/agents.md +1 -1
  117. package/pack/generated/claude/skills/devrites-lib/reference/standards/code-review.md +6 -1
  118. package/pack/generated/claude/skills/devrites-lib/reference/standards/coding-style.md +4 -3
  119. package/pack/generated/claude/skills/devrites-lib/reference/standards/context-hygiene.md +4 -0
  120. package/pack/generated/claude/skills/devrites-lib/reference/standards/core.md +3 -6
  121. package/pack/generated/claude/skills/devrites-lib/reference/standards/data-integrity.md +4 -0
  122. package/pack/generated/claude/skills/devrites-lib/reference/standards/development-workflow.md +8 -0
  123. package/pack/generated/claude/skills/devrites-lib/reference/standards/documentation.md +6 -0
  124. package/pack/generated/claude/skills/devrites-lib/reference/standards/edge-case-trace.md +3 -1
  125. package/pack/generated/claude/skills/devrites-lib/reference/standards/elicitation.md +5 -0
  126. package/pack/generated/claude/skills/devrites-lib/reference/standards/error-handling.md +14 -0
  127. package/pack/generated/claude/skills/devrites-lib/reference/standards/integration-reliability.md +18 -6
  128. package/pack/generated/claude/skills/devrites-lib/reference/standards/patterns.md +16 -0
  129. package/pack/generated/claude/skills/devrites-lib/reference/standards/performance.md +4 -0
  130. package/pack/generated/claude/skills/devrites-lib/reference/standards/repository-topology.md +6 -0
  131. package/pack/generated/claude/skills/devrites-lib/reference/standards/security-checklist.md +3 -0
  132. package/pack/generated/claude/skills/devrites-lib/reference/standards/security.md +10 -2
  133. package/pack/generated/claude/skills/devrites-lib/reference/standards/skill-authoring.md +8 -0
  134. package/pack/generated/claude/skills/devrites-lib/reference/standards/spec-grammar.md +5 -6
  135. package/pack/generated/claude/skills/devrites-lib/reference/standards/testing.md +2 -2
  136. package/pack/generated/claude/skills/devrites-lib/reference/standards/tooling.md +1 -1
  137. package/pack/generated/claude/skills/rite-define/reference/plan-template.md +5 -2
  138. package/pack/generated/claude/skills/rite-learn/SKILL.md +6 -0
  139. package/pack/generated/claude/skills/rite-polish/SKILL.md +4 -0
  140. package/pack/generated/claude/skills/rite-polish/reference/anti-ai-slop.md +12 -53
  141. package/pack/generated/claude/skills/rite-pr-feedback/SKILL.md +7 -2
  142. package/pack/generated/claude/skills/rite-spec/reference/spec-template.md +5 -4
  143. package/pack/generated/claude/skills/rite-status/SKILL.md +2 -0
  144. package/pack/generated/codex/agents/devrites-code-reviewer.toml +8 -0
  145. package/pack/generated/codex/agents/devrites-devex-reviewer.toml +7 -0
  146. package/pack/generated/codex/agents/devrites-doubt-reviewer.toml +7 -0
  147. package/pack/generated/codex/agents/devrites-evidence-scout.toml +4 -0
  148. package/pack/generated/codex/agents/devrites-frontend-reviewer.toml +8 -0
  149. package/pack/generated/codex/agents/devrites-performance-reviewer.toml +7 -0
  150. package/pack/generated/codex/agents/devrites-plan-reviewer.toml +8 -0
  151. package/pack/generated/codex/agents/devrites-retrospector.toml +2 -0
  152. package/pack/generated/codex/agents/devrites-security-auditor.toml +7 -0
  153. package/pack/generated/codex/agents/devrites-simplifier-reviewer.toml +7 -0
  154. package/pack/generated/codex/agents/devrites-spec-reviewer.toml +7 -0
  155. package/pack/generated/codex/agents/devrites-strategy-reviewer.toml +7 -0
  156. package/pack/generated/codex/agents/devrites-test-analyst.toml +7 -0
  157. package/pack/generated/codex/skills/devrites-frontend-craft/SKILL.md +4 -7
  158. package/pack/generated/codex/skills/devrites-frontend-craft/reference/quality-standards.md +9 -4
  159. package/pack/generated/codex/skills/devrites-lib/reference/intent-map.md +7 -0
  160. package/pack/generated/codex/skills/devrites-lib/reference/parallel-dispatch.md +1 -1
  161. package/pack/generated/codex/skills/devrites-lib/reference/reply-contract.md +9 -7
  162. package/pack/generated/codex/skills/devrites-lib/reference/standards/agents.md +1 -1
  163. package/pack/generated/codex/skills/devrites-lib/reference/standards/code-review.md +6 -1
  164. package/pack/generated/codex/skills/devrites-lib/reference/standards/coding-style.md +4 -3
  165. package/pack/generated/codex/skills/devrites-lib/reference/standards/context-hygiene.md +4 -0
  166. package/pack/generated/codex/skills/devrites-lib/reference/standards/core.md +3 -6
  167. package/pack/generated/codex/skills/devrites-lib/reference/standards/data-integrity.md +4 -0
  168. package/pack/generated/codex/skills/devrites-lib/reference/standards/development-workflow.md +8 -0
  169. package/pack/generated/codex/skills/devrites-lib/reference/standards/documentation.md +6 -0
  170. package/pack/generated/codex/skills/devrites-lib/reference/standards/edge-case-trace.md +3 -1
  171. package/pack/generated/codex/skills/devrites-lib/reference/standards/elicitation.md +5 -0
  172. package/pack/generated/codex/skills/devrites-lib/reference/standards/error-handling.md +14 -0
  173. package/pack/generated/codex/skills/devrites-lib/reference/standards/integration-reliability.md +18 -6
  174. package/pack/generated/codex/skills/devrites-lib/reference/standards/patterns.md +16 -0
  175. package/pack/generated/codex/skills/devrites-lib/reference/standards/performance.md +4 -0
  176. package/pack/generated/codex/skills/devrites-lib/reference/standards/repository-topology.md +6 -0
  177. package/pack/generated/codex/skills/devrites-lib/reference/standards/security-checklist.md +3 -0
  178. package/pack/generated/codex/skills/devrites-lib/reference/standards/security.md +10 -2
  179. package/pack/generated/codex/skills/devrites-lib/reference/standards/skill-authoring.md +8 -0
  180. package/pack/generated/codex/skills/devrites-lib/reference/standards/spec-grammar.md +5 -6
  181. package/pack/generated/codex/skills/devrites-lib/reference/standards/testing.md +2 -2
  182. package/pack/generated/codex/skills/devrites-lib/reference/standards/tooling.md +1 -1
  183. package/pack/generated/codex/skills/rite-define/reference/plan-template.md +5 -2
  184. package/pack/generated/codex/skills/rite-learn/SKILL.md +6 -0
  185. package/pack/generated/codex/skills/rite-polish/SKILL.md +4 -0
  186. package/pack/generated/codex/skills/rite-polish/reference/anti-ai-slop.md +12 -53
  187. package/pack/generated/codex/skills/rite-pr-feedback/SKILL.md +7 -2
  188. package/pack/generated/codex/skills/rite-spec/reference/spec-template.md +5 -4
  189. package/pack/generated/codex/skills/rite-status/SKILL.md +2 -0
  190. package/package.json +1 -1
  191. package/docs/markdown-instruction-upgrade-2026-08-27.md +0 -127
package/CHANGELOG.md CHANGED
@@ -2,6 +2,18 @@
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
+ ## [4.7.0](https://github.com/ViktorsBaikers/DevRites/compare/v4.6.0...v4.7.0) (2026-08-30)
6
+
7
+ ### Added
8
+
9
+ * **devrites:** markdown knowledge-layer round 5 and engine v5 slate ([#49](https://github.com/ViktorsBaikers/DevRites/issues/49)) ([2ef8e9a](https://github.com/ViktorsBaikers/DevRites/commit/2ef8e9a20c750d37f506d6ed0fbcd6bce74fce55))
10
+
11
+ ## [4.6.0](https://github.com/ViktorsBaikers/DevRites/compare/v4.5.0...v4.6.0) (2026-08-28)
12
+
13
+ ### Added
14
+
15
+ * **skills:** markdown knowledge-layer round 4 ([#48](https://github.com/ViktorsBaikers/DevRites/issues/48)) ([9dab7af](https://github.com/ViktorsBaikers/DevRites/commit/9dab7af644c219ab3f3411b4a701947b9da9108b))
16
+
5
17
  ## [4.5.0](https://github.com/ViktorsBaikers/DevRites/compare/v4.4.2...v4.5.0) (2026-08-27)
6
18
 
7
19
  ### Added
package/NOTICE.md CHANGED
@@ -7,19 +7,6 @@ The Nielsen heuristics 0–4 scoring rubric in
7
7
  `pack/.claude/skills/rite-review/reference/nielsen-heuristics.md` references the heuristics themselves
8
8
  (Jakob Nielsen, 1994 / 2020 — public usability canon).
9
9
 
10
- ## Research attribution (2026-08-27 instruction upgrade)
11
-
12
- The 2026-08-27 Markdown methodology round adapted *concepts* (not text) from external,
13
- untrusted-at-research-time repositories. Adaptations below are rewritten in DevRites language;
14
- no upstream prose ships in this pack.
15
-
16
- - [rebelytics/one-skill-to-rule-them-all](https://github.com/rebelytics/one-skill-to-rule-them-all) — CC BY 4.0: its capture/promotion/self-improvement loop **structure** informed `rite-learn` and `devrites-retrospector` guardrails (qualification contract, consolidation duty, growth cap, rejection persistence). Attribution required by license and hereby given.
17
- - [mukul975/Anthropic-Cybersecurity-Skills](https://github.com/mukul975/Anthropic-Cybersecurity-Skills) — Apache 2.0: its change-type→domain routing shape and co-located framework-identifier binding method informed `standards/security.md`. Concept-level adaptation; catalog not imported.
18
- - [pbakaus/impeccable](https://github.com/pbakaus/impeccable) — Apache 2.0: earlier derivative material remains in-tree; this round's Verify-vs-Refuse framing influenced the browser-proof checklist rewrite. Concept-level adaptation.
19
- - [netresearch/file-search-skill](https://github.com/netresearch/file-search-skill) — MIT AND CC BY-SA 4.0: only the *idea* of a question-type→tool matrix was used; `standards/tooling.md`'s table was rebuilt natively from DevRites tools (no structural transplant, honoring share-alike).
20
- - [Nutlope/hallmark](https://github.com/Nutlope/hallmark), [Leonxlnx/taste-skill](https://github.com/Leonxlnx/taste-skill), [educlopez/ui-craft](https://github.com/educlopez/ui-craft), [Weizhena/Deep-Research-skills](https://github.com/Weizhena/Deep-Research-skills) — MIT: viewport/state-capture checks, mechanical slop counters, coverage markers, and research checkpoint ideas, adapted concept-level.
21
-
22
-
23
10
  ## License
24
11
  Free to use. Modification is permitted **for personal use**; any other use —
25
12
  distributing it, distributing modified versions, or commercial/organizational use —
package/README.md CHANGED
@@ -28,7 +28,7 @@ project-conventional push, tag, or PR action, and archive the workspace.
28
28
  Unattended runs may create local WIP checkpoint commits along the way, but they
29
29
  remain local unless Ship's disclosed plan includes an approved remote action.
30
30
 
31
- **Status:** [`v4.5.0`](https://github.com/ViktorsBaikers/DevRites/releases/tag/v4.5.0): see [`CHANGELOG.md`](CHANGELOG.md) for release notes.
31
+ **Status:** [`v4.7.0`](https://github.com/ViktorsBaikers/DevRites/releases/tag/v4.7.0): see [`CHANGELOG.md`](CHANGELOG.md) for release notes.
32
32
 
33
33
  This is the latest published release; `main` may contain unreleased work.
34
34
 
@@ -123,8 +123,9 @@ with the self-contained updater.
123
123
  `/rite-upgrade` is the separate native, preservation-first route for reconciling
124
124
  an unfinished workspace. It proves a current-contract defect before routing
125
125
  Clarify, Plan repair, Converge, Vet, Prove, Polish, Review, or Seal; it never
126
- migrates cursor format or invents historical proof. The engine has no workspace
127
- migration command. See the [CLI contract](docs/cli.md).
126
+ migrates cursor format or invents historical proof. `devrites-engine migrate`
127
+ owns deterministic v5 schema normalization; see the [CLI contract](docs/cli.md)
128
+ and [ADR-0029](docs/adr/0029-v5-workspace-schema-and-native-migration.md).
128
129
 
129
130
  The [command map](docs/command-map.md) covers every command, trigger, input, and
130
131
  output. The [worked examples](docs/usage.md) show normal features, plan drift,
@@ -0,0 +1,62 @@
1
+ # ADR-0029: v5 workspace schema and native migration
2
+
3
+ - **Status:** Accepted
4
+ - **Date:** 2026-08-28
5
+
6
+ ## Context
7
+
8
+ The v5 engine changes the workspace contract: `state.md` carries a `schema`
9
+ cursor row declaring the workspace schema version, and the current contract is
10
+ schema 3. Workspaces written before v5 carry no row and resolve to schema 2,
11
+ the last pre-v5 contract.
12
+
13
+ The repository's preservation-first stance (ADR-0022, as applied by ADR-0025)
14
+ forbade an engine migrator: judgment work belongs to skills, and
15
+ `/rite-upgrade` routes repairs through phase owners. That stance served
16
+ read-compatible releases where no normalization was needed. A schema bump is
17
+ different: normalization — cursor encoding, missing-artifact reconciliation —
18
+ is deterministic, content-preserving work that needs per-feature locking,
19
+ atomic writes, and fail-closed refusal on ambiguity. Those primitives are
20
+ engine-owned.
21
+
22
+ ## Decision
23
+
24
+ - The engine declares the current workspace schema (`state.SchemaVersion`), and
25
+ each workspace declares its own through a `schema` row in its `state.md`
26
+ cursor. An absent row means schema 2.
27
+ - `state resolve` and `state close` refuse workspaces whose declared schema
28
+ differs from the engine's, naming the recovery path:
29
+ `devrites-engine migrate <slug>` for pre-v5 workspaces, `upgrade devrites`
30
+ for newer ones. Diagnostic checks (readiness, candidate, task-graph, observe)
31
+ keep reading pre-v5 workspaces and reporting their established named gaps.
32
+ - `devrites-engine migrate` performs fail-closed normalization: cursor-form
33
+ conversion through the existing dual-form primitives, missing required
34
+ artifacts as empty stubs with no synthesized content, and byte-exact
35
+ preservation of bound proof files. The v2→v3 normalization cannot invalidate
36
+ a binding — it only creates missing files and rewrites the unbound ledger —
37
+ and any future schema delta that would modify a bound file requires explicit
38
+ per-finding confirmation before it applies.
39
+ - This supersedes the no-migrator invariant of ADR-0022 as applied by ADR-0025
40
+ for deterministic schema normalization only. `/rite-upgrade` keeps
41
+ preservation-first routing for judgment work and may hand mechanical
42
+ normalization to the engine command.
43
+
44
+ ## Alternatives considered
45
+
46
+ | Option | Why not |
47
+ |--------|---------|
48
+ | Keep the no-migrator stance | Every pre-v5 workspace would strand mid-feature or silently diverge from the declared contract; the manifest version alone cannot normalize. |
49
+ | Refuse all reads of pre-v5 workspaces | Diagnostics are the user's fastest evidence of what migration will change; refusing them removes actionable output without adding safety. |
50
+ | Migrate as a skill checklist | Cursor rewrites and artifact stubs need the feature lock, atomic writes, and deterministic refusal on ambiguity — engine-owned primitives. |
51
+ | New marker file per workspace | Duplicates the ledger; the cursor row travels inside the file every workspace already has. |
52
+
53
+ ## Consequences
54
+
55
+ Pre-v5 workspaces remain readable; mutating or closing them requires
56
+ migration. Migration is deterministic and byte-preserving by default, so
57
+ recorded proof stays valid, and invalidation is never silent. The schema row
58
+ travels inside `state.md`, so no new file format exists. New workspaces created
59
+ by the v5 pack declare the row at creation.
60
+
61
+ Guard tests: `engine/internal/state/workspaceschema_test.go`,
62
+ `engine/tests/parity_resolve_test.go`, `engine/tests/parity_closeout_test.go`.
@@ -67,10 +67,11 @@ architecture; per-feature `decisions.md` files stay scoped to that feature.
67
67
  | [0019](0019-native-boundary-with-deterministic-gates.md) | Native boundary with deterministic gates | Accepted in part; semantic gates superseded by 0022, native writer and fresh approval retained | `engine/internal/gate/gate_test.go` |
68
68
  | [0020](0020-thin-engine-native-orchestration-boundary.md) | Thin engine and native host orchestration | Accepted in part; engine-surface/migration scope superseded by 0022, native orchestration retained | `tests/native-orchestration-contract-test.sh`, `engine/root_routing_test.go` |
69
69
  | [0021](0021-observable-workspace-compatibility.md) | Observable and reversible workspace compatibility | Superseded by 0022 | historical |
70
- | [0022](0022-native-orchestration-thin-engine.md) | Native orchestration with a thin deterministic engine | Accepted in part; snapshot/JSON-output clauses superseded by 0023; retained policy-command/install clauses narrowed by 0024 | `engine/root_routing_test.go`, `engine/internal/gate/gate_test.go`, `engine/internal/lib/cursor_compat_test.go`, `tests/native-orchestration-contract-test.sh` |
70
+ | [0022](0022-native-orchestration-thin-engine.md) | Native orchestration with a thin deterministic engine | Accepted in part; snapshot/JSON-output clauses superseded by 0023; retained policy-command/install clauses narrowed by 0024; no-migrator normalization scope superseded by 0029 | `engine/root_routing_test.go`, `engine/internal/gate/gate_test.go`, `engine/internal/lib/cursor_compat_test.go`, `tests/native-orchestration-contract-test.sh` |
71
71
  | [0023](0023-native-workspace-reads-line-output.md) | Native workspace reads and line-oriented engine output | Accepted in part; AFK/recovery/doctor retention superseded by 0024 | `engine/root_routing_test.go`, `engine/internal/gate/gate_test.go`, `tests/native-orchestration-contract-test.sh`, `tests/npx-pack-smoke.sh` |
72
72
  | [0024](0024-native-policy-offline-installer-boundary.md) | Native policy and offline installer boundary | Accepted in part; offline engine-update clause superseded by 0028 | `tests/native-orchestration-contract-test.sh`, `tests/phase-gate-routing-test.sh`, `tests/install-smoke.sh`, `tests/host-artifacts-test.sh` |
73
- | [0025](0025-evidence-gated-workspace-upgrades.md) | Evidence-gated semantic workspace upgrades | Accepted | `tests/phase-gate-routing-test.sh`, `tests/host-artifacts-test.sh` |
73
+ | [0025](0025-evidence-gated-workspace-upgrades.md) | Evidence-gated semantic workspace upgrades | Accepted; no-migrator scope superseded by 0029 | `tests/phase-gate-routing-test.sh`, `tests/host-artifacts-test.sh` |
74
74
  | [0026](0026-content-bound-proof-and-bounded-inputs.md) | Content-bound proof and bounded inputs | Accepted | `engine/internal/lib/candidate_test.go`, `engine/internal/lib/evidencefresh_test.go`, `engine/internal/gate/gate_test.go`, `engine/root_routing_test.go`, `engine/tests/parity_githelpers_test.go`, `tests/native-orchestration-contract-test.sh`, `tests/phase-gate-routing-test.sh`, `tests/install-smoke.sh`, `tests/update-smoke.sh`, `tests/npx-pack-smoke.sh`, `tests/release-tarball-test.sh` |
75
75
  | [0027](0027-content-bound-build-readiness.md) | Content-bound build readiness | Accepted | `engine/tests/adr_0027_readiness_binding_test.go` |
76
76
  | [0028](0028-self-contained-engine-update.md) | Self-contained engine update | Accepted | `engine/internal/install/install_test.go`, `engine/internal/release/release_test.go`, `engine/tests/meta_test.go` |
77
+ | [0029](0029-v5-workspace-schema-and-native-migration.md) | v5 workspace schema and native migration | Accepted | `engine/internal/state/workspaceschema_test.go`, `engine/tests/parity_resolve_test.go`, `engine/tests/parity_closeout_test.go` |
@@ -212,7 +212,7 @@ delta; Clarify, Plan repair, Converge, Vet, Prove, Polish, Review, or Seal then
212
212
  performs it under normal gates. Candidate repair runs current real proof and
213
213
  never synthesizes a historical pass; ambiguous legacy candidate scope is a gap.
214
214
 
215
- The engine has no structural migration command or compatibility telemetry. It
215
+ The engine owns deterministic v5 schema normalization (`devrites-engine migrate`) or compatibility telemetry. It
216
216
  directly reads the official v1/v2 bullet and v3 table `state.md` cursors without
217
217
  rewriting them. Wider pre-release compatibility experiments are not runtime
218
218
  contracts. See [ADR-0025](adr/0025-evidence-gated-workspace-upgrades.md) and
package/docs/cli.md CHANGED
@@ -22,6 +22,7 @@ devrites-engine check seal <slug>
22
22
 
23
23
  devrites-engine state resolve <qid> "<answer>"
24
24
  devrites-engine state close <slug>
25
+ devrites-engine migrate <slug> [--dry-run] [--answer id=choice]
25
26
 
26
27
  devrites-engine secret-scan [--staged] [--stdin] [slug]
27
28
  devrites-engine open-visual <path-or-name> [--slug <slug>] [--no-open]
@@ -30,8 +31,7 @@ devrites-engine version
30
31
 
31
32
  Commands outside this operational list and the standard help/version forms are
32
33
  unsupported. There are no legacy engine aliases, tombstones, agent-protocol
33
- versions, semantic-readiness digests, compatibility telemetry, or workspace
34
- migration command.
34
+ versions, semantic-readiness digests, or compatibility telemetry.
35
35
  `check candidate` is additive; no existing engine command or public `/rite-*`
36
36
  workflow was removed or renamed.
37
37
 
@@ -41,7 +41,7 @@ The Go surface is intentionally closed and deterministic:
41
41
  | Lane | Commands | Owner |
42
42
  |---|---|---|
43
43
  | Candidate and deterministic checks | `check candidate`, `check readiness [--emit-binding]`, `check seal`, `check task-graph`, `check path-disjoint`, `check skill-trust` | Candidate validates/hashes the strict project manifest; readiness checks phase files, the required `tasks.md` slice graph, or emits the vetted Build-input binding; seal checks final files/open gates, that graph and binding, then exact candidate bindings. |
44
- | Atomic workspace state | `state resolve`, `state close` | Go owns answer/drop/batch resolution and transactional close. |
44
+ | Atomic workspace state | `state resolve`, `state close`, `migrate [--dry-run] [--answer id=choice]` | Go owns answer/drop/batch resolution, transactional close, and fail-closed v5 schema normalization. |
45
45
  | Security | `secret-scan` | `/rite-ship`, safe hooks, or an operator scans staged blobs, stdin, or touched files. |
46
46
  | Install/operator | `install`, `update`, `uninstall`, `version` | Direct update acquires latest; npm/bootstrap may supply local candidates; the engine performs manifest-owned local changes. |
47
47
  | Native policy | no engine command | Skills/root own spec grammar re-read, qid allocation, Clarify cursor edits, AFK/recovery accounting, and read-only `/rite-doctor`. |
@@ -53,7 +53,7 @@ prints exactly
53
53
  `candidate-sha256: <64 lowercase hex>` and `candidate-files: <row count>` on a
54
54
  pass; usage/root errors exit `2` and candidate blocks exit `3`. There are no
55
55
  semantic readiness commands, reviewer-prose parsers, capability-ledger engine,
56
- compatibility telemetry, or migration command.
56
+ or compatibility telemetry.
57
57
 
58
58
  Claude Code and Codex own native dispatch. Installed skills and exact agents
59
59
  own semantic readiness, traceability, acceptance/evidence quality, doubt,
@@ -21,6 +21,7 @@ workflow strategy.
21
21
  | `observe summary <slug>` | Emit sanitized JSON workspace summary from one retained observation. `task_graph.ok` is true iff `task_graph.problems` is empty; `problems` lists cycle, unknown-dep, malformed-token, duplicate-id, and missing-`Dependencies` blockers. |
22
22
  | `state resolve <qid> "<answer>"` | Resolve an open question and update `questions.md` plus `state.md` atomically. |
23
23
  | `state close <slug>` | Archive a shipped workspace and clear matching `ACTIVE`. |
24
+ | `migrate <slug> [--dry-run] [--answer id=choice]` | Normalize a pre-v5 workspace to the current schema; fail-closed, one-shot. |
24
25
  | `secret-scan [--staged] [--stdin] [slug]` | Scan exact staged blobs, stdin, or touched regular files for credential material. |
25
26
  | `open-visual <path-or-name> [--slug <slug>] [--no-open]` | Resolve a local visual HTML file, optionally open it in the OS browser, warn if the sibling outline is missing or inventory ids are absent from HTML, and print agent path tips. No network. |
26
27
  | `version` | Print the engine version. |
@@ -55,6 +56,14 @@ State mutations use the shared physical-root checks, feature lock, and atomic
55
56
  write path. `state resolve` additionally supports `--drop` and `--batch`;
56
57
  `state close` owns transactional archive plus `ACTIVE` clearing.
57
58
 
59
+ `migrate` normalizes a pre-v5 workspace: legacy bullet cursor fields become
60
+ canonical table rows, the `schema` row is recorded, and missing required
61
+ artifacts are created as empty stubs (content is never synthesized, and bound
62
+ proof files stay byte-exact). It is one-shot and fail-closed: on ambiguity it
63
+ writes nothing, prints its questions, and exits `3`; answers arrive on rerun
64
+ via `--answer id=choice`. `--dry-run` prints the plan and always writes
65
+ nothing. See [ADR-0029](../adr/0029-v5-workspace-schema-and-native-migration.md).
66
+
58
67
  Normative spec grammar checks, qid allocation, Clarify cursor transitions, AFK
59
68
  slice accounting, recovery attempt accounting, and installation diagnostics are
60
69
  explicit root-owned native procedures. The workflow owns reproduction,
@@ -1,11 +1,11 @@
1
- # `.devrites/` state schema (v2)
1
+ # `.devrites/` state schema (v3)
2
2
 
3
3
  The engine reads workflow state from plain files under `.devrites/`. Those
4
4
  human-editable files remain authoritative; the engine has no database or
5
5
  semantic cache.
6
6
 
7
7
  <!-- authority:schema-version:start -->
8
- `schemaVersion: 2`.
8
+ `schemaVersion: 3`.
9
9
  <!-- authority:schema-version:end -->
10
10
 
11
11
  For root selection, supported legacy cursors, and the full artifact layout, see
@@ -125,7 +125,12 @@ The runtime reads only official released workspace formats:
125
125
  The canonical workspace location remains `.devrites/work/<slug>/`; canonical
126
126
  map, cursor, and proof files are `README.md`, `state.md`, and `evidence.md`.
127
127
  Compatibility reads do not rewrite a workspace and do not emit local telemetry.
128
- There is no structural migration command. `/rite-upgrade` first audits an older
128
+ Since engine v5, workspaces declare their schema through a `schema` row in the
129
+ `state.md` cursor; an absent row means the pre-v5 schema 2 contract. `state
130
+ resolve` and `state close` refuse workspaces whose declared schema is not the
131
+ current one: pre-v5 workspaces run `devrites-engine migrate <slug>`, and newer
132
+ workspaces require upgrading devrites.
133
+ `/rite-upgrade` first audits an older
129
134
  active workspace against named current contracts; only a cited defect may route
130
135
  an edit through its normal phase owner. For released unfinished post-Build
131
136
  workspaces, candidate defects route through current Prove, Polish, Review, and
@@ -134,8 +139,9 @@ an old pass.
134
139
 
135
140
  Other pre-release layouts, filename substitutions, and phase encodings are
136
141
  not runtime authorities. See
137
- [ADR-0022](../adr/0022-native-orchestration-thin-engine.md) and
138
- [ADR-0025](../adr/0025-evidence-gated-workspace-upgrades.md).
142
+ [ADR-0022](../adr/0022-native-orchestration-thin-engine.md),
143
+ [ADR-0025](../adr/0025-evidence-gated-workspace-upgrades.md), and
144
+ [ADR-0029](../adr/0029-v5-workspace-schema-and-native-migration.md).
139
145
 
140
146
  ## Native policy state
141
147
 
package/docs/usage.md CHANGED
@@ -61,7 +61,7 @@ preservation-first audit: age/cursor encoding alone is not a defect, and only a
61
61
  cited current-contract failure routes repair through its phase owner. Completed
62
62
  work and evidence stay intact. Candidate defects may route current Prove,
63
63
  Polish, Review, and Seal in that order; old passes are never synthesized. The
64
- engine has no structural migration command.
64
+ engine owns deterministic v5 schema normalization.
65
65
 
66
66
  Project-root sentinel (outside the workspace):
67
67
 
@@ -17,6 +17,7 @@ const (
17
17
  rootUnused rootMode = iota
18
18
  rootLenient
19
19
  rootStrict
20
+ rootStrictUsage
20
21
  )
21
22
 
22
23
  // rootModeFor is the single policy boundary between diagnostic/read-only
@@ -34,6 +35,20 @@ func rootModeFor(command string, args []string) rootMode {
34
35
  return rootStrict
35
36
  }
36
37
  return rootUnused
38
+ case "migrate":
39
+ return rootStrictUsage
40
+ case "check":
41
+ switch subcommand {
42
+ case "candidate", "readiness", "seal", "task-graph":
43
+ return rootStrictUsage
44
+ }
45
+ return rootUnused
46
+ case "observe":
47
+ switch subcommand {
48
+ case "summary":
49
+ return rootStrictUsage
50
+ }
51
+ return rootUnused
37
52
  default:
38
53
  return rootUnused
39
54
  }
@@ -67,6 +82,14 @@ func resolveRootFor(command string, args []string) (string, int, error) {
67
82
  return fallbackRoot(), exitOK, nil
68
83
  }
69
84
  return "", exitUsage, err
85
+ case rootStrictUsage:
86
+ root, err := state.ResolveRoot(os.Getenv("DEVRITES_ROOT"))
87
+ if err != nil {
88
+ // Workspace-requiring read/check commands have always mapped every
89
+ // resolution failure to the usage exit code.
90
+ return "", exitUsage, err
91
+ }
92
+ return root, exitOK, nil
70
93
  default:
71
94
  panic("unknown root resolution mode")
72
95
  }
@@ -0,0 +1,193 @@
1
+ package fsutil
2
+
3
+ import (
4
+ "os"
5
+ "path/filepath"
6
+ "runtime"
7
+ "strings"
8
+ "testing"
9
+ "time"
10
+ )
11
+
12
+ func TestWriteFileAtomicWritesAndOverwrites(t *testing.T) {
13
+ dir := t.TempDir()
14
+ path := filepath.Join(dir, "nested", "file.txt")
15
+ if err := WriteFileAtomic(path, []byte("first\n"), 0o644); err != nil {
16
+ t.Fatal(err)
17
+ }
18
+ got, err := os.ReadFile(path)
19
+ if err != nil {
20
+ t.Fatal(err)
21
+ }
22
+ if string(got) != "first\n" {
23
+ t.Fatalf("content %q", got)
24
+ }
25
+ if !PermissionsMatch(mustMode(t, path), 0o644) {
26
+ t.Fatalf("perm %v", mustMode(t, path))
27
+ }
28
+ // Overwrite in place.
29
+ if err := WriteFileAtomic(path, []byte("second\n"), 0o644); err != nil {
30
+ t.Fatal(err)
31
+ }
32
+ if got, _ := os.ReadFile(path); string(got) != "second\n" {
33
+ t.Fatalf("content after overwrite %q", got)
34
+ }
35
+ // No temp files left behind.
36
+ entries, err := os.ReadDir(filepath.Dir(path))
37
+ if err != nil {
38
+ t.Fatal(err)
39
+ }
40
+ for _, e := range entries {
41
+ if name := e.Name(); name != "file.txt" && name != "nested" {
42
+ t.Fatalf("temp file left behind: %s", name)
43
+ }
44
+ }
45
+ }
46
+
47
+ func mustMode(t *testing.T, path string) os.FileMode {
48
+ t.Helper()
49
+ info, err := os.Stat(path)
50
+ if err != nil {
51
+ t.Fatal(err)
52
+ }
53
+ return info.Mode()
54
+ }
55
+
56
+ func TestFileModTime(t *testing.T) {
57
+ dir := t.TempDir()
58
+ path := filepath.Join(dir, "file.txt")
59
+ if err := os.WriteFile(path, []byte("x"), 0o644); err != nil {
60
+ t.Fatal(err)
61
+ }
62
+ modified, ok := FileModTime(path)
63
+ if !ok {
64
+ t.Fatal("regular file should report mod time")
65
+ }
66
+ if modified <= 0 {
67
+ t.Fatalf("mod time %d", modified)
68
+ }
69
+ if _, ok := FileModTime(filepath.Join(dir, "missing.txt")); ok {
70
+ t.Fatal("missing file should report !ok")
71
+ }
72
+ if _, ok := FileModTime(dir); ok {
73
+ t.Fatal("directory should report !ok")
74
+ }
75
+ }
76
+
77
+ func TestNewestModTime(t *testing.T) {
78
+ dir := t.TempDir()
79
+ old := filepath.Join(dir, "old.txt")
80
+ new := filepath.Join(dir, "new.txt")
81
+ if err := os.WriteFile(old, []byte("x"), 0o644); err != nil {
82
+ t.Fatal(err)
83
+ }
84
+ if err := os.WriteFile(new, []byte("x"), 0o644); err != nil {
85
+ t.Fatal(err)
86
+ }
87
+ future := time.Now().Add(2 * time.Hour)
88
+ if err := os.Chtimes(new, future, future); err != nil {
89
+ t.Skipf("cannot set file times on this platform: %v", err)
90
+ }
91
+ newest, ok := NewestModTime(old, new)
92
+ if !ok {
93
+ t.Fatal("expected ok")
94
+ }
95
+ want, _ := FileModTime(new)
96
+ if newest != want {
97
+ t.Fatalf("newest=%d want %d", newest, want)
98
+ }
99
+ if _, ok := NewestModTime(filepath.Join(dir, "missing.txt")); ok {
100
+ t.Fatal("missing-only inputs should report !ok")
101
+ }
102
+ }
103
+
104
+ func TestCopyTreeMissingSourceIsNoOp(t *testing.T) {
105
+ dir := t.TempDir()
106
+ dst := filepath.Join(dir, "dst")
107
+ if err := CopyTree(filepath.Join(dir, "missing"), dst); err != nil {
108
+ t.Fatalf("missing src must not error: %v", err)
109
+ }
110
+ if _, err := os.Stat(dst); !os.IsNotExist(err) {
111
+ t.Fatalf("dst must not be created, got %v", err)
112
+ }
113
+ }
114
+
115
+ func TestCopyTreeFileAndTree(t *testing.T) {
116
+ dir := t.TempDir()
117
+ src := filepath.Join(dir, "src")
118
+ if err := os.MkdirAll(filepath.Join(src, "sub"), 0o755); err != nil {
119
+ t.Fatal(err)
120
+ }
121
+ if err := os.WriteFile(filepath.Join(src, "root.txt"), []byte("root\n"), 0o644); err != nil {
122
+ t.Fatal(err)
123
+ }
124
+ if err := os.WriteFile(filepath.Join(src, "sub", "leaf.txt"), []byte("leaf\n"), 0o644); err != nil {
125
+ t.Fatal(err)
126
+ }
127
+ dst := filepath.Join(dir, "dst")
128
+ if err := CopyTree(src, dst); err != nil {
129
+ t.Fatal(err)
130
+ }
131
+ for rel, want := range map[string]string{
132
+ "root.txt": "root\n",
133
+ "sub/leaf.txt": "leaf\n",
134
+ } {
135
+ got, err := os.ReadFile(filepath.Join(dst, filepath.FromSlash(rel)))
136
+ if err != nil {
137
+ t.Fatalf("%s: %v", rel, err)
138
+ }
139
+ if string(got) != want {
140
+ t.Fatalf("%s: %q want %q", rel, got, want)
141
+ }
142
+ }
143
+
144
+ // Copying a single file copies its contents.
145
+ single := filepath.Join(dir, "single.txt")
146
+ if err := os.WriteFile(single, []byte("one\n"), 0o644); err != nil {
147
+ t.Fatal(err)
148
+ }
149
+ singleDst := filepath.Join(dir, "single-copy.txt")
150
+ if err := CopyTree(single, singleDst); err != nil {
151
+ t.Fatal(err)
152
+ }
153
+ if got, _ := os.ReadFile(singleDst); string(got) != "one\n" {
154
+ t.Fatalf("single copy %q", got)
155
+ }
156
+
157
+ // Recopying overwrites existing files.
158
+ if err := os.WriteFile(filepath.Join(src, "root.txt"), []byte("changed\n"), 0o644); err != nil {
159
+ t.Fatal(err)
160
+ }
161
+ if err := CopyTree(src, dst); err != nil {
162
+ t.Fatal(err)
163
+ }
164
+ if got, _ := os.ReadFile(filepath.Join(dst, "root.txt")); string(got) != "changed\n" {
165
+ t.Fatalf("overwrite %q", got)
166
+ }
167
+ }
168
+
169
+ func TestPermissionsMatch(t *testing.T) {
170
+ if !PermissionsMatch(0o644, 0o644) {
171
+ t.Fatal("matching perms must match")
172
+ }
173
+ if runtime.GOOS != "windows" && PermissionsMatch(0o600, 0o644) {
174
+ t.Fatal("differing POSIX perms must not match")
175
+ }
176
+ }
177
+
178
+ func TestWriteFileAtomicSurfacesFailure(t *testing.T) {
179
+ dir := t.TempDir()
180
+ blocker := filepath.Join(dir, "blocker")
181
+ if err := os.WriteFile(blocker, []byte("x"), 0o644); err != nil {
182
+ t.Fatal(err)
183
+ }
184
+ // The parent path is an existing file, so creating the destination
185
+ // directory must fail and the error must surface to the caller.
186
+ err := WriteFileAtomic(filepath.Join(blocker, "nested", "file.txt"), []byte("x"), 0o644)
187
+ if err == nil {
188
+ t.Fatal("expected the write failure to surface")
189
+ }
190
+ if !strings.Contains(err.Error(), "atomic write") {
191
+ t.Fatalf("error should identify the operation, got %v", err)
192
+ }
193
+ }
@@ -123,7 +123,7 @@ Goal: looks complete without an ordering field
123
123
  func TestCheckAndRenderReadiness(t *testing.T) {
124
124
  root := t.TempDir()
125
125
  writeFeature(t, root, "alpha", map[string]string{
126
- "state.md": "- Phase: build\n",
126
+ "state.md": "- Phase: build\n- Schema: 3\n",
127
127
  "brief.md": "brief\n",
128
128
  "spec.md": "real spec\n",
129
129
  "assumptions.md": "none\n",
@@ -164,7 +164,7 @@ func TestCheckAndRenderReadiness(t *testing.T) {
164
164
  func TestCheckUsesConcreteWorkspaceRequirements(t *testing.T) {
165
165
  root := t.TempDir()
166
166
  files := map[string]string{
167
- "state.md": "| phase | vet |\n",
167
+ "state.md": "| phase | vet |\n| schema | 3 |\n",
168
168
  "brief.md": "brief\n",
169
169
  "spec.md": "spec\n",
170
170
  "decisions.md": "decisions\n",
@@ -192,7 +192,7 @@ func TestCheckUsesConcreteWorkspaceRequirements(t *testing.T) {
192
192
  func TestSealRequiresDurableReviewAndSealArtifacts(t *testing.T) {
193
193
  root := t.TempDir()
194
194
  files := map[string]string{
195
- "state.md": "| phase | seal |\n",
195
+ "state.md": "| phase | seal |\n| schema | 3 |\n",
196
196
  "brief.md": "brief\n",
197
197
  "spec.md": "spec\n",
198
198
  "decisions.md": "decisions\n",
@@ -373,7 +373,7 @@ func TestCheckBlocksOpenHumanQuestions(t *testing.T) {
373
373
  t.Run(tc.name, func(t *testing.T) {
374
374
  root := t.TempDir()
375
375
  writeFeature(t, root, "alpha", map[string]string{
376
- "state.md": "- Phase: build\n- Status: " + tc.status + "\n",
376
+ "state.md": "- Phase: build\n- Status: " + tc.status + "\n- Schema: 3\n",
377
377
  "spec.md": "spec\n",
378
378
  "plan.md": "plan\n",
379
379
  "decisions.md": "decisions\n",
@@ -412,7 +412,7 @@ func TestCheckUsesWorkspaceOverrideForStateInvariants(t *testing.T) {
412
412
  override := filepath.Join(physicalRoot, "work", "alpha")
413
413
  t.Setenv("DEVRITES_WORKSPACE", override)
414
414
  testutil.WriteFile(t, filepath.Join(override, "README.md"), "---\nphase: spec\nschemaVersion: 1\n---\n")
415
- testutil.WriteFile(t, filepath.Join(override, "state.md"), "- Phase: build\n- Status: running\n")
415
+ testutil.WriteFile(t, filepath.Join(override, "state.md"), "- Phase: build\n- Status: running\n- Schema: 3\n")
416
416
  testutil.WriteFile(t, filepath.Join(override, "spec.md"), "spec\n")
417
417
  testutil.WriteFile(t, filepath.Join(override, "questions.md"), "## q-1\nstatus: open\ngate: blocking\n")
418
418
 
@@ -443,7 +443,7 @@ func TestCheckObservationUsesRetainedPhaseQuestionsReadinessAndReview(t *testing
443
443
  if err != nil {
444
444
  t.Fatal(err)
445
445
  }
446
- testutil.WriteFile(t, filepath.Join(workspace, "state.md"), "- Phase: spec\n- Status: running\n")
446
+ testutil.WriteFile(t, filepath.Join(workspace, "state.md"), "- Phase: spec\n- Status: running\n- Schema: 3\n")
447
447
  testutil.WriteFile(t, filepath.Join(workspace, "questions.md"), "## q-1\nstatus: open\ngate: blocking\n")
448
448
  testutil.WriteFile(t, filepath.Join(workspace, "plan.md"), "# Plan\n\nChanged after observation.\n")
449
449
  testutil.WriteFile(t, filepath.Join(workspace, "eng-review.md"), "# Engineering review\n\nNo binding.\n")
@@ -609,7 +609,7 @@ func writeCompleteGateFeature(t *testing.T, root, slug string, current, required
609
609
  content := "# " + name + "\n\nreal\n"
610
610
  switch name {
611
611
  case "state.md":
612
- content = "- Phase: " + string(current) + "\n- Status: running\n"
612
+ content = "- Phase: " + string(current) + "\n- Status: running\n- Schema: 3\n"
613
613
  case "questions.md":
614
614
  questionsRequired = true
615
615
  content = questions
@@ -77,7 +77,13 @@ func TestReadinessBindingBindsOnlyStableBuildInputs(t *testing.T) {
77
77
  }
78
78
 
79
79
  for _, name := range []string{"state.md", "questions.md", "decisions.md", "assumptions.md", "eng-review.md", "evidence.md", "review.md", "seal.md", "handoff.md", "ambient.md"} {
80
- testutil.WriteFile(t, filepath.Join(workspace, name), "# Mutable\n\nChanged outside the stable contract.\n")
80
+ // state.md stays a valid v5 workspace when mutated: the schema row is
81
+ // part of the contract, not part of the bound inputs.
82
+ body := "# Mutable\n\nChanged outside the stable contract.\n"
83
+ if name == "state.md" {
84
+ body = "| phase | build |\n| schema | 3 |\n" + body
85
+ }
86
+ testutil.WriteFile(t, filepath.Join(workspace, name), body)
81
87
  if got := mustReadinessBinding(t, root, "stable"); got != baseline {
82
88
  t.Fatalf("excluded %s altered binding", name)
83
89
  }
@@ -365,7 +371,7 @@ func writeReadinessFixture(t *testing.T, root, slug, phase string) string {
365
371
  t.Helper()
366
372
  workspace := filepath.Join(root, "work", slug)
367
373
  for name, body := range map[string]string{
368
- "state.md": "| phase | " + phase + " |\n",
374
+ "state.md": "| phase | " + phase + " |\n| schema | 3 |\n",
369
375
  "brief.md": "# Brief\n\nReady.\n",
370
376
  "spec.md": "# Spec\n\nReady.\n",
371
377
  "decisions.md": "# Decisions\n\nNone.\n",