devrites 5.14.0 → 5.14.2

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 (218) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +6 -3
  3. package/docs/adr/0033-rite-fast-own-agents.md +45 -0
  4. package/docs/adr/README.md +2 -1
  5. package/docs/architecture.md +6 -4
  6. package/docs/command-map.md +2 -1
  7. package/docs/flow.md +3 -2
  8. package/docs/orchestration.md +3 -0
  9. package/docs/quick-reference.md +2 -1
  10. package/docs/skills.md +13 -8
  11. package/docs/usage.md +2 -2
  12. package/engine/internal/install/apply.go +5 -3
  13. package/engine/internal/install/binary.go +3 -1
  14. package/engine/internal/install/install.go +0 -25
  15. package/engine/internal/lib/claims.go +15 -0
  16. package/engine/internal/lib/claims_test.go +28 -0
  17. package/engine/internal/lib/context_next_test.go +53 -0
  18. package/engine/internal/lib/dispatch.go +28 -7
  19. package/engine/internal/lib/manifest_union.go +14 -0
  20. package/engine/internal/overhaul/admit/admit_test.go +9 -0
  21. package/engine/internal/overhaul/admit/run.go +9 -0
  22. package/engine/internal/overhaul/bench/run.go +327 -38
  23. package/engine/internal/overhaul/bench/run_test.go +131 -0
  24. package/engine/internal/overhaul/bench/stats.go +154 -0
  25. package/engine/internal/overhaul/ovio/ovio.go +4 -0
  26. package/engine/internal/overhaul/records/check.go +3 -0
  27. package/engine/internal/overhaul/records/consistency.go +217 -0
  28. package/engine/internal/overhaul/records/records_test.go +81 -0
  29. package/engine/internal/overhaul/records/run.go +1 -1
  30. package/engine/internal/overhaul/render/run.go +2 -0
  31. package/engine/internal/overhaul/render/run_test.go +5 -1
  32. package/engine/internal/overhaul/score/run.go +58 -9
  33. package/engine/internal/overhaul/score/run_test.go +84 -8
  34. package/engine/internal/parallel/ops.go +5 -0
  35. package/engine/internal/state/write.go +8 -2
  36. package/engine/internal/version/version_test.go +27 -0
  37. package/engine/main.go +2 -2
  38. package/pack/.claude/agents/fast-builder.md +90 -0
  39. package/pack/.claude/agents/fast-checker.md +60 -0
  40. package/pack/.claude/agents/fast-critic.md +70 -0
  41. package/pack/.claude/agents/fast-planner.md +95 -0
  42. package/pack/.claude/agents/overhaul-backend-engineer.md +5 -4
  43. package/pack/.claude/agents/overhaul-boundary.md +2 -2
  44. package/pack/.claude/agents/overhaul-craft.md +1 -1
  45. package/pack/.claude/agents/overhaul-frontend-engineer.md +5 -3
  46. package/pack/.claude/agents/overhaul-implementer.md +9 -5
  47. package/pack/.claude/agents/overhaul-specialist.md +6 -3
  48. package/pack/.claude/agents/overhaul-verifier.md +3 -3
  49. package/pack/.claude/settings.json +1 -1
  50. package/pack/.claude/skills/devrites-lib/reference/intent-map.md +2 -0
  51. package/pack/.claude/skills/overhaul/SKILL.md +2 -1
  52. package/pack/.claude/skills/overhaul/references/audit-domains.md +139 -12
  53. package/pack/.claude/skills/overhaul/references/lifecycle.md +2 -0
  54. package/pack/.claude/skills/overhaul/references/orchestration.md +13 -5
  55. package/pack/.claude/skills/overhaul/references/records.md +12 -3
  56. package/pack/.claude/skills/overhaul/references/repair-and-measurement.md +108 -5
  57. package/pack/.claude/skills/overhaul/references/scope-and-safety.md +17 -2
  58. package/pack/.claude/skills/overhaul/references/scoring.md +36 -7
  59. package/pack/.claude/skills/overhaul/references/stack-profiles.md +52 -0
  60. package/pack/.claude/skills/rite/SKILL.md +4 -1
  61. package/pack/.claude/skills/rite-doctor/SKILL.md +2 -0
  62. package/pack/.claude/skills/rite-fast/SKILL.md +183 -0
  63. package/pack/.claude/skills/rite-fast/reference/artifacts.md +107 -0
  64. package/pack/.claude/skills/rite-fast/reference/check-and-triage.md +51 -0
  65. package/pack/.claude/skills/rite-quick/SKILL.md +2 -0
  66. package/pack/.claude/skills/rite-spec/reference/state-workspace.md +1 -0
  67. package/pack/generated/claude/agents/fast-builder.md +90 -0
  68. package/pack/generated/claude/agents/fast-checker.md +60 -0
  69. package/pack/generated/claude/agents/fast-critic.md +70 -0
  70. package/pack/generated/claude/agents/fast-planner.md +95 -0
  71. package/pack/generated/claude/agents/overhaul-backend-engineer.md +5 -4
  72. package/pack/generated/claude/agents/overhaul-boundary.md +2 -2
  73. package/pack/generated/claude/agents/overhaul-craft.md +1 -1
  74. package/pack/generated/claude/agents/overhaul-frontend-engineer.md +5 -3
  75. package/pack/generated/claude/agents/overhaul-implementer.md +9 -5
  76. package/pack/generated/claude/agents/overhaul-specialist.md +6 -3
  77. package/pack/generated/claude/agents/overhaul-verifier.md +3 -3
  78. package/pack/generated/claude/settings.json +1 -1
  79. package/pack/generated/claude/skills/devrites-lib/reference/intent-map.md +2 -0
  80. package/pack/generated/claude/skills/overhaul/SKILL.md +2 -1
  81. package/pack/generated/claude/skills/overhaul/references/audit-domains.md +139 -12
  82. package/pack/generated/claude/skills/overhaul/references/lifecycle.md +2 -0
  83. package/pack/generated/claude/skills/overhaul/references/orchestration.md +13 -5
  84. package/pack/generated/claude/skills/overhaul/references/records.md +12 -3
  85. package/pack/generated/claude/skills/overhaul/references/repair-and-measurement.md +108 -5
  86. package/pack/generated/claude/skills/overhaul/references/scope-and-safety.md +17 -2
  87. package/pack/generated/claude/skills/overhaul/references/scoring.md +36 -7
  88. package/pack/generated/claude/skills/overhaul/references/stack-profiles.md +52 -0
  89. package/pack/generated/claude/skills/rite/SKILL.md +4 -1
  90. package/pack/generated/claude/skills/rite-doctor/SKILL.md +2 -0
  91. package/pack/generated/claude/skills/rite-fast/SKILL.md +183 -0
  92. package/pack/generated/claude/skills/rite-fast/reference/artifacts.md +107 -0
  93. package/pack/generated/claude/skills/rite-fast/reference/check-and-triage.md +51 -0
  94. package/pack/generated/claude/skills/rite-quick/SKILL.md +2 -0
  95. package/pack/generated/claude/skills/rite-spec/reference/state-workspace.md +1 -0
  96. package/pack/generated/codex/AGENTS.md +1 -0
  97. package/pack/generated/codex/agents/fast-builder.toml +90 -0
  98. package/pack/generated/codex/agents/fast-checker.toml +60 -0
  99. package/pack/generated/codex/agents/fast-critic.toml +70 -0
  100. package/pack/generated/codex/agents/fast-planner.toml +95 -0
  101. package/pack/generated/codex/agents/overhaul-backend-engineer.toml +5 -4
  102. package/pack/generated/codex/agents/overhaul-boundary.toml +2 -2
  103. package/pack/generated/codex/agents/overhaul-craft.toml +1 -1
  104. package/pack/generated/codex/agents/overhaul-frontend-engineer.toml +5 -3
  105. package/pack/generated/codex/agents/overhaul-implementer.toml +9 -5
  106. package/pack/generated/codex/agents/overhaul-specialist.toml +6 -3
  107. package/pack/generated/codex/agents/overhaul-verifier.toml +3 -3
  108. package/pack/generated/codex/skills/devrites-lib/reference/intent-map.md +2 -0
  109. package/pack/generated/codex/skills/overhaul/SKILL.md +2 -1
  110. package/pack/generated/codex/skills/overhaul/references/audit-domains.md +139 -12
  111. package/pack/generated/codex/skills/overhaul/references/lifecycle.md +2 -0
  112. package/pack/generated/codex/skills/overhaul/references/orchestration.md +13 -5
  113. package/pack/generated/codex/skills/overhaul/references/records.md +12 -3
  114. package/pack/generated/codex/skills/overhaul/references/repair-and-measurement.md +108 -5
  115. package/pack/generated/codex/skills/overhaul/references/scope-and-safety.md +17 -2
  116. package/pack/generated/codex/skills/overhaul/references/scoring.md +36 -7
  117. package/pack/generated/codex/skills/overhaul/references/stack-profiles.md +52 -0
  118. package/pack/generated/codex/skills/rite/SKILL.md +4 -1
  119. package/pack/generated/codex/skills/rite-doctor/SKILL.md +2 -0
  120. package/pack/generated/codex/skills/rite-fast/SKILL.md +183 -0
  121. package/pack/generated/codex/skills/rite-fast/agents/openai.yaml +2 -0
  122. package/pack/generated/codex/skills/rite-fast/reference/artifacts.md +107 -0
  123. package/pack/generated/codex/skills/rite-fast/reference/check-and-triage.md +51 -0
  124. package/pack/generated/codex/skills/rite-quick/SKILL.md +2 -0
  125. package/pack/generated/codex/skills/rite-spec/reference/state-workspace.md +1 -0
  126. package/pack/generated/devin/AGENTS.md +1 -0
  127. package/pack/generated/devin/agents/fast-builder.md +96 -0
  128. package/pack/generated/devin/agents/fast-checker.md +63 -0
  129. package/pack/generated/devin/agents/fast-critic.md +73 -0
  130. package/pack/generated/devin/agents/fast-planner.md +98 -0
  131. package/pack/generated/devin/agents/overhaul-backend-engineer.md +5 -4
  132. package/pack/generated/devin/agents/overhaul-boundary.md +2 -2
  133. package/pack/generated/devin/agents/overhaul-craft.md +1 -1
  134. package/pack/generated/devin/agents/overhaul-frontend-engineer.md +5 -3
  135. package/pack/generated/devin/agents/overhaul-implementer.md +9 -5
  136. package/pack/generated/devin/agents/overhaul-specialist.md +6 -3
  137. package/pack/generated/devin/agents/overhaul-verifier.md +3 -3
  138. package/pack/generated/devin/skills/devrites-lib/reference/intent-map.md +2 -0
  139. package/pack/generated/devin/skills/overhaul/SKILL.md +2 -1
  140. package/pack/generated/devin/skills/overhaul/references/audit-domains.md +139 -12
  141. package/pack/generated/devin/skills/overhaul/references/lifecycle.md +2 -0
  142. package/pack/generated/devin/skills/overhaul/references/orchestration.md +13 -5
  143. package/pack/generated/devin/skills/overhaul/references/records.md +12 -3
  144. package/pack/generated/devin/skills/overhaul/references/repair-and-measurement.md +108 -5
  145. package/pack/generated/devin/skills/overhaul/references/scope-and-safety.md +17 -2
  146. package/pack/generated/devin/skills/overhaul/references/scoring.md +36 -7
  147. package/pack/generated/devin/skills/overhaul/references/stack-profiles.md +52 -0
  148. package/pack/generated/devin/skills/rite/SKILL.md +4 -1
  149. package/pack/generated/devin/skills/rite-doctor/SKILL.md +2 -0
  150. package/pack/generated/devin/skills/rite-fast/SKILL.md +183 -0
  151. package/pack/generated/devin/skills/rite-fast/reference/artifacts.md +107 -0
  152. package/pack/generated/devin/skills/rite-fast/reference/check-and-triage.md +51 -0
  153. package/pack/generated/devin/skills/rite-quick/SKILL.md +2 -0
  154. package/pack/generated/devin/skills/rite-spec/reference/state-workspace.md +1 -0
  155. package/pack/generated/omp/agents/fast-builder.md +89 -0
  156. package/pack/generated/omp/agents/fast-checker.md +59 -0
  157. package/pack/generated/omp/agents/fast-critic.md +69 -0
  158. package/pack/generated/omp/agents/fast-planner.md +94 -0
  159. package/pack/generated/omp/agents/overhaul-backend-engineer.md +5 -4
  160. package/pack/generated/omp/agents/overhaul-boundary.md +2 -2
  161. package/pack/generated/omp/agents/overhaul-craft.md +1 -1
  162. package/pack/generated/omp/agents/overhaul-frontend-engineer.md +5 -3
  163. package/pack/generated/omp/agents/overhaul-implementer.md +9 -5
  164. package/pack/generated/omp/agents/overhaul-specialist.md +6 -3
  165. package/pack/generated/omp/agents/overhaul-verifier.md +3 -3
  166. package/pack/generated/omp/commands/rite-fast.md +5 -0
  167. package/pack/generated/omp/skills/devrites-lib/reference/intent-map.md +2 -0
  168. package/pack/generated/omp/skills/overhaul/SKILL.md +2 -1
  169. package/pack/generated/omp/skills/overhaul/references/audit-domains.md +139 -12
  170. package/pack/generated/omp/skills/overhaul/references/lifecycle.md +2 -0
  171. package/pack/generated/omp/skills/overhaul/references/orchestration.md +13 -5
  172. package/pack/generated/omp/skills/overhaul/references/records.md +12 -3
  173. package/pack/generated/omp/skills/overhaul/references/repair-and-measurement.md +108 -5
  174. package/pack/generated/omp/skills/overhaul/references/scope-and-safety.md +17 -2
  175. package/pack/generated/omp/skills/overhaul/references/scoring.md +36 -7
  176. package/pack/generated/omp/skills/overhaul/references/stack-profiles.md +52 -0
  177. package/pack/generated/omp/skills/rite/SKILL.md +4 -1
  178. package/pack/generated/omp/skills/rite-doctor/SKILL.md +2 -0
  179. package/pack/generated/omp/skills/rite-fast/SKILL.md +183 -0
  180. package/pack/generated/omp/skills/rite-fast/reference/artifacts.md +107 -0
  181. package/pack/generated/omp/skills/rite-fast/reference/check-and-triage.md +51 -0
  182. package/pack/generated/omp/skills/rite-quick/SKILL.md +2 -0
  183. package/pack/generated/omp/skills/rite-spec/reference/state-workspace.md +1 -0
  184. package/pack/generated/pi/AGENTS.md +1 -0
  185. package/pack/generated/pi/agents/fast-builder.md +90 -0
  186. package/pack/generated/pi/agents/fast-checker.md +60 -0
  187. package/pack/generated/pi/agents/fast-critic.md +70 -0
  188. package/pack/generated/pi/agents/fast-planner.md +95 -0
  189. package/pack/generated/pi/agents/overhaul-backend-engineer.md +5 -4
  190. package/pack/generated/pi/agents/overhaul-boundary.md +2 -2
  191. package/pack/generated/pi/agents/overhaul-craft.md +1 -1
  192. package/pack/generated/pi/agents/overhaul-frontend-engineer.md +5 -3
  193. package/pack/generated/pi/agents/overhaul-implementer.md +9 -5
  194. package/pack/generated/pi/agents/overhaul-specialist.md +6 -3
  195. package/pack/generated/pi/agents/overhaul-verifier.md +3 -3
  196. package/pack/generated/pi/prompts/rite-fast.md +5 -0
  197. package/pack/generated/pi/skills/devrites-lib/reference/intent-map.md +2 -0
  198. package/pack/generated/pi/skills/overhaul/SKILL.md +2 -1
  199. package/pack/generated/pi/skills/overhaul/references/audit-domains.md +139 -12
  200. package/pack/generated/pi/skills/overhaul/references/lifecycle.md +2 -0
  201. package/pack/generated/pi/skills/overhaul/references/orchestration.md +13 -5
  202. package/pack/generated/pi/skills/overhaul/references/records.md +12 -3
  203. package/pack/generated/pi/skills/overhaul/references/repair-and-measurement.md +108 -5
  204. package/pack/generated/pi/skills/overhaul/references/scope-and-safety.md +17 -2
  205. package/pack/generated/pi/skills/overhaul/references/scoring.md +36 -7
  206. package/pack/generated/pi/skills/overhaul/references/stack-profiles.md +52 -0
  207. package/pack/generated/pi/skills/rite/SKILL.md +4 -1
  208. package/pack/generated/pi/skills/rite-doctor/SKILL.md +2 -0
  209. package/pack/generated/pi/skills/rite-fast/SKILL.md +183 -0
  210. package/pack/generated/pi/skills/rite-fast/reference/artifacts.md +107 -0
  211. package/pack/generated/pi/skills/rite-fast/reference/check-and-triage.md +51 -0
  212. package/pack/generated/pi/skills/rite-quick/SKILL.md +2 -0
  213. package/pack/generated/pi/skills/rite-spec/reference/state-workspace.md +1 -0
  214. package/package.json +1 -1
  215. package/scripts/codex-generate.sh +2 -1
  216. package/scripts/devin-generate.sh +4 -3
  217. package/scripts/omp-generate.sh +3 -3
  218. package/scripts/pi-generate.sh +4 -3
package/CHANGELOG.md CHANGED
@@ -2,6 +2,23 @@
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
+ ## [5.14.2](https://github.com/ViktorsBaikers/DevRites/compare/v5.14.1...v5.14.2) (2026-09-30)
6
+
7
+ ### Fixed
8
+
9
+ * **skills:** stamp workspace schema row in state.md template ([#91](https://github.com/ViktorsBaikers/DevRites/issues/91)) ([d4701ad](https://github.com/ViktorsBaikers/DevRites/commit/d4701adc7ae2cc9a0225aff5da8e5e39b46dd39e))
10
+
11
+ ## [5.14.1](https://github.com/ViktorsBaikers/DevRites/compare/v5.14.0...v5.14.1) (2026-09-29)
12
+
13
+ ### Added
14
+
15
+ * **no-release:** add /rite-fast lane for mid-size work ([#89](https://github.com/ViktorsBaikers/DevRites/issues/89)) ([7b71593](https://github.com/ViktorsBaikers/DevRites/commit/7b7159377a88002d33b19277f3b365cd9be89520))
16
+ * **no-release:** close /overhaul audit and repair coverage gaps ([f7c4e6a](https://github.com/ViktorsBaikers/DevRites/commit/f7c4e6a0e27b83e7fa65befdee44df2efebbccca))
17
+
18
+ ### Fixed
19
+
20
+ * **repo:** close engine, script, and doc quality gaps ([#90](https://github.com/ViktorsBaikers/DevRites/issues/90)) ([61572e2](https://github.com/ViktorsBaikers/DevRites/commit/61572e2bccae418aa47c7852807d2551f7f0bda1))
21
+
5
22
  ## [5.14.0](https://github.com/ViktorsBaikers/DevRites/compare/v5.13.1...v5.14.0) (2026-09-25)
6
23
 
7
24
  ### Added
package/README.md CHANGED
@@ -31,7 +31,7 @@ history can't lose it. After you clear the conversation, switch hosts, or hand
31
31
  the feature to another engineer, the next session reads the same plan,
32
32
  decisions, and evidence before it continues.
33
33
 
34
- **Status:** [`v5.14.0`](https://github.com/ViktorsBaikers/DevRites/releases/tag/v5.14.0): see [`CHANGELOG.md`](CHANGELOG.md) for release notes.
34
+ **Status:** [`v5.14.2`](https://github.com/ViktorsBaikers/DevRites/releases/tag/v5.14.2): see [`CHANGELOG.md`](CHANGELOG.md) for release notes.
35
35
 
36
36
  This is the latest published release; `main` may contain unreleased work.
37
37
 
@@ -147,6 +147,7 @@ equivalent.
147
147
  | --- | --- |
148
148
  | [`/rite`](pack/.claude/skills/rite/SKILL.md) | Show the command menu, or route a verb. |
149
149
  | [`/rite-quick`](pack/.claude/skills/rite-quick/SKILL.md) | Ship a small, reversible change with a compact contract, focused proof, and scope review. Escalates to `/rite-spec` when the work stops being small. |
150
+ | [`/rite-fast`](pack/.claude/skills/rite-fast/SKILL.md) | Take mid-size work through one spec-and-plan step with your answers, up to ten slices, one completeness check, review, polish, and proof. No gate after each slice; explicit invocation only. |
150
151
  | [`/rite-autocomplete`](pack/.claude/skills/rite-autocomplete/SKILL.md) | Run the reversible lifecycle unattended and stop at Seal `GO`. With `--ship`, continue through Ship preflight and wait for your literal `GO`. |
151
152
  | [`/rite-adopt`](pack/.claude/skills/rite-adopt/SKILL.md) | Reverse-engineer current behavior of an existing codebase into a baseline workspace. |
152
153
  | [`/rite-upgrade [slug]`](pack/.claude/skills/rite-upgrade/SKILL.md) | Reconcile an older active workspace with current contracts. Routes only evidence-backed defects to their phase owners. |
@@ -423,8 +424,8 @@ through Claude Code or Codex plugin stores.
423
424
 
424
425
  ## Skills and agents
425
426
 
426
- The pack ships 45 skills: 34 public and 11 internal. The public surface
427
- contains the `rite` menu, 32 `rite-*` workflows and utilities, and the
427
+ The pack ships 46 skills: 35 public and 11 internal. The public surface
428
+ contains the `rite` menu, 33 `rite-*` workflows and utilities, and the
428
429
  standalone, explicit-only `/overhaul`. Ten `devrites-*` specialists load when a
429
430
  matching task needs them; `devrites-lib` carries the shared contracts and
430
431
  engineering standards.
@@ -434,6 +435,8 @@ roles plus `devrites-slice-wright`, the sole source and test writer. Codex,
434
435
  omp, pi, and Devin generate the same one-writer, sixteen-reader split.
435
436
  `/overhaul` adds eight `overhaul-*` agents that only it dispatches; they keep
436
437
  every tool ([ADR-0031](docs/adr/0031-overhaul-agents-keep-every-tool.md)).
438
+ `/rite-fast` adds four `fast-*` agents that only it dispatches: three read-only
439
+ roles and `fast-builder`, its own writer ([ADR-0033](docs/adr/0033-rite-fast-own-agents.md)).
437
440
 
438
441
  The [skills catalogue](docs/skills.md) lists every skill and agent. The
439
442
  [flow diagrams](docs/flow.md) show routing, reviewer fan-out, and namespace
@@ -0,0 +1,45 @@
1
+ # ADR-0033: `/rite-fast` ships its own agents and its own writer
2
+
3
+ - **Status:** Accepted
4
+ - **Date:** 2026-09-26
5
+
6
+ ## Context
7
+
8
+ Mid-size work had two routes. `/rite-quick` covers one small reversible change. The
9
+ full lifecycle adds strategy review, a vetted plan, and review plus proof after every
10
+ slice. Users asked for a lane in between: one spec-and-plan step with clarifying
11
+ questions, up to ten slices built with no gate between them, one completeness check
12
+ with gap fixes, then review, polish, and proof.
13
+
14
+ The lifecycle agents are shaped for per-slice gates and engine workspace artifacts
15
+ (`state.md`, `tasks.md`, `touched-files.md`, readiness bindings). `devrites-slice-wright`
16
+ runs writer-safe proof on every slice, which is the step the fast lane removes.
17
+ ADR-0010, ADR-0015, ADR-0017, ADR-0018 and ADR-0019 keep one writer among `devrites-*`
18
+ specialists; ADR-0031 already scoped that rule for `/overhaul`.
19
+
20
+ ## Decision
21
+
22
+ - `/rite-fast` is an explicit-only skill (`disable-model-invocation: true`). It stands
23
+ outside the engine lifecycle: its record is `spec.md`, `plan.md`, and `evidence.md`
24
+ under `.devrites/work/<slug>/`, with no `state.md`, no `.devrites/ACTIVE`, and no
25
+ seal. No engine change is needed.
26
+ - The pack ships four agents that only `/rite-fast` dispatches: `fast-planner`,
27
+ `fast-checker`, and `fast-critic` (read-only, `permissionMode: plan`), and
28
+ `fast-builder` (write-capable in the exact paths of its contract,
29
+ `permissionMode: acceptEdits`). `fast-builder` runs no test suite; the skill runs one
30
+ check after all slices and proof last.
31
+ - `validate-agent-composition.py` accepts `fast-builder` as write-capable when it states
32
+ that mode. Codex gives it `default_permissions = ":workspace"`; omp, pi, and Devin give
33
+ it the write tool set. The other `fast-*` agents stay read-only on every host.
34
+
35
+ ## Consequences
36
+
37
+ - The one-writer clauses of the lifecycle ADRs govern `devrites-*` specialists only;
38
+ `/overhaul` (ADR-0031) and `/rite-fast` each own one more writer.
39
+ - A `/rite-fast` run is not visible to `/rite-status`, `orient`, `next`, or `handoff`.
40
+ It resumes from its own `plan.md` ledger.
41
+ - Four more agents are installed, updated, and removed with the pack. `/rite-fast`
42
+ stops when they are missing and no fresh context is available.
43
+
44
+ Verified by `scripts/validate-agent-composition.py`, `tests/rite-fast-contract-test.sh`,
45
+ and the host generators' `fast-builder` branches.
@@ -47,7 +47,7 @@ architecture; per-feature `decisions.md` files stay scoped to that feature.
47
47
  | ADR | Title | Status | Guard test |
48
48
  |-----|-------|--------|-----------|
49
49
  | [0001](0001-go-engine-as-control-plane.md) | Go engine as deterministic control plane | Accepted in part; all-state-transition/derivation scope superseded by 0024 | none |
50
- | [0002](0002-dual-host-harness.md) | Dual-host harness (Claude + Codex) | Accepted | `tests/parity_*_test.go` |
50
+ | [0002](0002-dual-host-harness.md) | Dual-host harness (Claude + Codex) | Accepted | `engine/tests/parity_*_test.go` |
51
51
  | [0003](0003-gate-model-hitl-pause.md) | Gates block as HITL pause, never crash | Accepted | `engine/tests/adr_0003_gate_exit_code_test.go` |
52
52
  | [0004](0004-state-schema-phases-sections.md) | Phase-relative section completeness | Accepted | `engine/tests/adr_0004_required_by_phase_test.go`, `engine/internal/state/state_test.go` |
53
53
  | [0005](0005-hooks-as-engine-subcommands.md) | Hooks are engine subcommands, not shell scripts | Accepted; active hook clause superseded by 0018 | historical |
@@ -77,3 +77,4 @@ architecture; per-feature `decisions.md` files stay scoped to that feature.
77
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` |
78
78
  | [0031](0031-overhaul-agents-keep-every-tool.md) | `/overhaul` ships its own agents that keep every tool | Accepted; scopes the one-writer clauses of 0010/0015/0017/0018/0019 to `devrites-*` specialists | `scripts/validate-agent-composition.py`, `tests/codex-agent-generation-test.sh` |
79
79
  | [0032](0032-single-lint-gate-and-cached-pr-tests.md) | One engine lint gate, and PR tests that reuse main's results | Accepted | `tests/validation-governance-test.sh` |
80
+ | [0033](0033-rite-fast-own-agents.md) | `/rite-fast` ships its own agents and its own writer | Accepted; adds `fast-builder` as a writer outside the `devrites-*` one-writer rule | `scripts/validate-agent-composition.py`, `tests/rite-fast-contract-test.sh` |
@@ -30,7 +30,7 @@ cursor readers, traceability rules, and phase-relative completeness model, see
30
30
  irreversible git ladder and **closes** the task by archiving the workspace
31
31
  and clearing `ACTIVE`. Separate steps let users audit the release decision
32
32
  before any irreversible action.
33
- 2. **Public utility and on-ramp skills**: `rite-adopt`, `rite-quick`,
33
+ 2. **Public utility and on-ramp skills**: `rite-adopt`, `rite-quick`, `rite-fast`,
34
34
  `rite-frame`, `rite-status`, `rite-doctor`, `rite-upgrade`, `rite-learn`, `rite-explain`,
35
35
  `rite-customize`, `rite-zoom-out`, `rite-prototype`, `rite-handoff`,
36
36
  `rite-pressure-test`, `rite-pov`, `rite-dogfood`, `rite-pr-feedback`,
@@ -389,8 +389,8 @@ contract.
389
389
 
390
390
  ## Design choices at a glance
391
391
 
392
- - **Surface**: 32 public `rite-*` skills (45 total), plus the thin `/rite`
393
- menu and the standalone `overhaul` skill: 34 public and 11 internal. The lifecycle
392
+ - **Surface**: 33 public `rite-*` skills (46 total), plus the thin `/rite`
393
+ menu and the standalone `overhaul` skill: 35 public and 11 internal. The lifecycle
394
394
  includes mandatory adaptive Clarify, optional Temper, mandatory Vet, and
395
395
  Converge recovery; Seal **decides** and Ship **executes + closes**. Ten
396
396
  `devrites-*` specialist skills are model-invoked; `devrites-lib` is the
@@ -413,7 +413,9 @@ contract.
413
413
  - **Agents**: 17 fresh-context roles at flat depth one: 16 read-only leaves and
414
414
  one wright. Dispatch uses the exact named project role; if it cannot spawn,
415
415
  the workflow stops for HITL. `/overhaul` adds eight `overhaul-*` agents
416
- that only it dispatches and that keep every tool (ADR-0031).
416
+ that only it dispatches and that keep every tool (ADR-0031). `/rite-fast`
417
+ adds four `fast-*` agents: three read-only roles and `fast-builder`, its own
418
+ writer (ADR-0033).
417
419
  - **Review**: **feature-scoped** multi-axis review with severity labels and
418
420
  fresh-context agents at the seal.
419
421
  - **Scope**: clarify → seal (decide) → ship (read-only preflight → type-GO →
@@ -16,7 +16,7 @@ installer; DevRites is not distributed as a Claude or Codex plugin.
16
16
  The `devrites-` prefix avoids collisions with bundled Claude Code skill names
17
17
  such as `prototype`, `handoff`, `triage`, and `diagnose`. The prefix does not
18
18
  mean that a skill is internal; each `SKILL.md` uses `user-invocable:` to set
19
- visibility. Public utilities use the `rite-*` prefix: `rite-quick`,
19
+ visibility. Public utilities use the `rite-*` prefix: `rite-quick`, `rite-fast`,
20
20
  `rite-frame`, `rite-adopt`, `rite-learn`, `rite-doctor`, `rite-customize`,
21
21
  `rite-zoom-out`, `rite-prototype`, `rite-handoff`, `rite-pressure-test`,
22
22
  `rite-autocomplete`, `rite-explain`, `rite-pov`, `rite-dogfood`,
@@ -106,6 +106,7 @@ See [`cli.md`](cli.md).
106
106
  | [`/rite-ship`](../pack/.claude/skills/rite-ship/SKILL.md) | ship | `[slug]` | Starts with a read-only Seal/candidate check and exact plan disclosure, then requires fresh literal `GO`. Only then may it collapse checkpoints, stage exact candidate paths, validate staged scope/bytes/bindings/secrets, commit and reverify, perform optional separately approved push/tag/PR actions, and archive/close. | `seal.md` + candidate + Git index | `ship.md`, `state.md`, archive |
107
107
  | [`/rite-autocomplete`](../pack/.claude/skills/rite-autocomplete/SKILL.md) | (orchestrator) | `[idea] [--ship\|--yolo] [--max-slices N] [--full] [--cross-model]` | Full lifecycle (spec → clarify → … → seal → ship). Spec + clarify form the one interactive window; AFK/checkpoint mode arms only after decision coverage is CLEAR. Pauses for irreversible risk, escalating, unanswered blocking with no recommended option, human-only access, or remaining NO-GO — not recommended-option blocking questions, temper expand, validating gates, `--max-slices`, or default resource caps. Objective red checks use bounded technical recovery. Default stops at Seal GO; `--ship` (`--yolo`) reaches Ship preflight but still requires fresh literal `GO` and native approval; `--full` selects the Full profile and `--cross-model` arms Vet's second opinion. | idea + workspace | whole workspace (drives every phase) |
108
108
  | [`/rite-quick`](../pack/.claude/skills/rite-quick/SKILL.md) | (express) | `<change>` | Express lane for a **small, reversible, unambiguous** change: one-line contract → TDD build → scoped prove → review-lite → ship, no full artifact tree. **Significance gate first**: auth / migration / public-API / destructive / multi-slice / ambiguous → escalates to `/rite-spec`. Triggers: "quick fix", "small change", "tiny tweak", "just do X". | the change + codebase | code + commit (optional `brief.md` / `evidence.md`) |
109
+ | [`/rite-fast`](../pack/.claude/skills/rite-fast/SKILL.md) | (fast lane) | `<task \| slug>` | **Explicit-only** shortened lane for mid-size work: one SHAPE step (`fast-planner` drafts spec + ≤10 slices, root asks ≤5 ranked questions, 10 when risk is flagged, user approves) → BUILD with `fast-builder` and **no per-slice gate** → one CHECK (`fast-checker` completeness pass, ≤2 gap-fix rounds) → REFINE (`fast-critic` review + polish) → PROVE on the final tree. Outside the engine lifecycle: no `state.md`, no `ACTIVE`, no seal; commits only on confirmation. | the task + codebase | `spec.md`, `plan.md`, `evidence.md` under `.devrites/work/<slug>/` + code |
109
110
  | [`/rite-frame`](../pack/.claude/skills/rite-frame/SKILL.md) | lens | `[ask \| diff]` | Pre-flight + self-audit lens for ad-hoc work the lifecycle gates never see: **FRAME** turns an imperative ask into a falsifiable success criterion + verify command before code; **AUDIT** checks a raw diff against the four LLM coding failure modes (silent assumption / overcomplication / out-of-scope edit / unverifiable goal). Top of `/rite-quick` or before a plain "just do X". | the ask / a raw diff | success criterion + verify command (inline) |
110
111
  | [`/rite-adopt`](../pack/.claude/skills/rite-adopt/SKILL.md) | onboard | `[path or area to adopt] [+ what you want to build next]` | Reverse-derive the existing baseline and next objective; propose any durable project guidance in the nearest native instruction file. | codebase + project instructions | workspace spec/state artifacts + optional guidance proposal |
111
112
  | [`/rite-status`](../pack/.claude/skills/rite-status/SKILL.md) | status | `[slug]` | Active feature: phase, run mode, status, persisted next action, evidence, questions, drift, risks, and handoff readiness. | direct workspace ledger/artifacts | none |
package/docs/flow.md CHANGED
@@ -381,7 +381,7 @@ separately by `disable-model-invocation`.
381
381
 
382
382
  ```mermaid
383
383
  flowchart TB
384
- subgraph Public["Public (user-invocable: true): 34 skills"]
384
+ subgraph Public["Public (user-invocable: true): 35 skills"]
385
385
  direction TB
386
386
  R1[/rite/]
387
387
  R2[/rite-spec/]
@@ -401,6 +401,7 @@ flowchart TB
401
401
  R10[/rite-status/]
402
402
  R11[/rite-resolve/]
403
403
  RQ[/rite-quick/]
404
+ RFA[/rite-fast/]
404
405
  RF[/rite-frame/]
405
406
  RA[/rite-adopt/]
406
407
  RL[/rite-learn/]
@@ -435,7 +436,7 @@ flowchart TB
435
436
 
436
437
  classDef pub fill:#064e3b,stroke:#34d399,color:#ecfdf5
437
438
  classDef int fill:#1f2937,stroke:#9ca3af,color:#f9fafb
438
- class R1,R2,RCL,RT,R3,RV,R4,R5,RC,R6,R7,R8,R9,R12,R13,R10,R11,RQ,RF,RA,RL,RD,RU,RE,RCU,RDO,RPOV,RPF,IPT,D1,D2,D3 pub
439
+ class R1,R2,RCL,RT,R3,RV,R4,R5,RC,R6,R7,R8,R9,R12,R13,R10,R11,RQ,RFA,RF,RA,RL,RD,RU,RE,RCU,RDO,RPOV,RPF,IPT,D1,D2,D3 pub
439
440
  class I1,I2,I3,I4,I5,I6,I7,I8,I9,I10,I11 int
440
441
  ```
441
442
 
@@ -52,6 +52,9 @@ slice-wright uses `default_permissions = ":workspace"` and the other 16
52
52
  specialists use `default_permissions = ":read-only"`. The eight `overhaul-*` agents of the
53
53
  explicit `/overhaul` skill sit outside this split: Claude gives them every tool and
54
54
  Codex gives them `:workspace` ([ADR-0031](adr/0031-overhaul-agents-keep-every-tool.md)).
55
+ The four `fast-*` agents of `/rite-fast` also sit outside it: `fast-builder` uses
56
+ `acceptEdits` on Claude and `:workspace` on Codex, and the other three are
57
+ read-only ([ADR-0033](adr/0033-rite-fast-own-agents.md)).
55
58
 
56
59
  If the exact role is unavailable, the root stops instead of substituting a
57
60
  generic agent or doing specialist/writer work inline.
@@ -3,7 +3,8 @@
3
3
  Executive fast path: **Spec → Build → Prove**. `/rite-spec` writes the
4
4
  contract, `/rite-build` implements one slice, `/rite-prove` binds evidence.
5
5
  Adaptive Clarify/Vet still run; they ask nothing when the contract is already
6
- complete. Use `/rite-quick` for a small reversible change.
6
+ complete. Use `/rite-quick` for a small reversible change and `/rite-fast` for
7
+ mid-size work that does not need a gate after every slice.
7
8
 
8
9
  Install DevRites with `npx devrites ...`. Normal installs from package and
9
10
  release candidates consume pre-generated project-local Claude Code and Codex
package/docs/skills.md CHANGED
@@ -1,6 +1,6 @@
1
- # All 45 skills
1
+ # All 46 skills
2
2
 
3
- The pack contains 45 skills: the `rite` menu, 32 user-invocable `rite-*`
3
+ The pack contains 46 skills: the `rite` menu, 33 user-invocable `rite-*`
4
4
  workflow and utility skills, the standalone `overhaul` skill,
5
5
  10 model-invoked `devrites-*` specialists, and the internal `devrites-lib` library. `devrites-lib` is not a command. It holds shared
6
6
  references and the few explicit script exceptions.
@@ -19,8 +19,9 @@ guidance block in `AGENTS.md`.
19
19
  The root and reviewers never write source. Claude enforces the root boundary in
20
20
  plan mode. Codex uses a workspace-capable root so its sole write-capable
21
21
  slice-wright can execute, while all other specialists remain read-only. On
22
- Devin, `allowed-tools` gives only `devrites-slice-wright` `edit`, `write`, and
23
- `exec`; a missing required profile stops for HITL instead of substituting a
22
+ Devin, `allowed-tools` gives `edit`, `write`, and `exec` only to
23
+ `devrites-slice-wright` among lifecycle roles (plus `fast-builder` for
24
+ `/rite-fast`); a missing required profile stops for HITL instead of substituting a
24
25
  generic profile. The
25
26
  installed `devrites-engine` owns strict candidate identity, deterministic checks,
26
27
  exact proof/review/seal bindings,
@@ -42,7 +43,7 @@ contract when a rite names parallel reviewers.
42
43
  ## Naming convention
43
44
 
44
45
  `rite-*` is the namespace for lifecycle and utility commands. Its utilities are
45
- `rite-quick`, `rite-frame`, `rite-adopt`, `rite-learn`, `rite-customize`,
46
+ `rite-quick`, `rite-fast`, `rite-frame`, `rite-adopt`, `rite-learn`, `rite-customize`,
46
47
  `rite-explain`, `rite-pov`, `rite-dogfood`, `rite-pr-feedback`, `rite-watch-pr`, `rite-doctor`,
47
48
  `rite-upgrade`, `rite-prototype`, `rite-handoff`, `rite-zoom-out`, and
48
49
  `rite-pressure-test`.
@@ -145,6 +146,7 @@ and `Shipped`. Utility commands keep the same compact labels and one-next-action
145
146
  | Skill | What It Does | Use When |
146
147
  |---|---|---|
147
148
  | [`rite-quick`](../pack/.claude/skills/rite-quick/SKILL.md) | Express lane for a **small, reversible, unambiguous** change: one-line contract → TDD build → scoped prove → review-lite → ship, collapsing the full lifecycle into one pass. Significance gate first: auth / migration / public-API / destructive / multi-slice / ambiguous **escalates to `/rite-spec`**. | "quick fix", "small change", "tiny tweak", "just do X" on a low-risk change. |
149
+ | [`rite-fast`](../pack/.claude/skills/rite-fast/SKILL.md) | Shortened lane: one spec-and-plan step with ranked clarifying questions (more when risk is flagged), up to ten vertical slices built by `fast-builder` without per-slice gates, one `fast-checker` completeness pass with gap fixes, one `fast-critic` review and polish pass, then proof on the final tree. Own `fast-*` agents; outside the engine lifecycle. | Explicit-only: `/rite-fast <task>` for mid-size work that does not need a gate after every slice. |
148
150
  | [`rite-frame`](../pack/.claude/skills/rite-frame/SKILL.md) | Pre-flight + self-audit lens for ad-hoc work the lifecycle gates never see: FRAME converts an imperative ask into a falsifiable success criterion + verify command before code; AUDIT checks a raw diff against the four LLM coding failure modes (silent assumption / overcomplication / out-of-scope edit / unverifiable goal). | Top of `/rite-quick`, before a plain "just do X" edit, or to self-review a raw diff. |
149
151
  | [`rite-adopt`](../pack/.claude/skills/rite-adopt/SKILL.md) | Bring an EXISTING codebase under DevRites: reverse-derive a baseline `spec.md` of current behavior, placement, and architecture; optionally propose one reviewed scoped-instruction update; then hand off to the lifecycle. | "adopt this project", "onboard this codebase", "we already have code", "reverse-engineer a spec from the existing app". |
150
152
 
@@ -261,8 +263,10 @@ and `Shipped`. Utility commands keep the same compact labels and one-next-action
261
263
  DevRites ships 17 role profiles at depth one. Both hosts have 16 read-only
262
264
  leaves and one source/test writer role. The explicit `/overhaul` skill ships eight
263
265
  more agents, `overhaul-*`, that only it dispatches and that keep every tool
264
- ([ADR-0031](adr/0031-overhaul-agents-keep-every-tool.md)); the counts here cover the
265
- lifecycle roles. Four read-only work roles handle
266
+ ([ADR-0031](adr/0031-overhaul-agents-keep-every-tool.md)). `/rite-fast` adds four
267
+ `fast-*` agents that only it dispatches: three read-only roles and `fast-builder`,
268
+ its own writer ([ADR-0033](adr/0033-rite-fast-own-agents.md)). The counts here cover
269
+ the lifecycle roles. Four read-only work roles handle
266
270
  bounded evidence, planning, proof, and compatibility while the root keeps
267
271
  authority:
268
272
 
@@ -302,7 +306,8 @@ Seal fan-out diagram: [`flow.md` § /rite-seal fan-out](flow.md#4-rite-seal-fan-
302
306
 
303
307
  ## Executor agent: fresh-context writer
304
308
 
305
- The catalog has one writer role. Claude and Codex run it with one exact
309
+ The lifecycle catalog has one writer role (`/rite-fast` keeps its own
310
+ `fast-builder`, ADR-0033). Claude and Codex run it with one exact
306
311
  path-bounded slice or accepted correction; every other agent remains read-only.
307
312
 
308
313
  | Agent | Purpose |
package/docs/usage.md CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  These examples cover the common DevRites workflows. Start a new feature with
4
4
  `/rite-spec` and bring an existing codebase in with `/rite-adopt`.
5
- `/rite-quick` and `/rite-frame` handle bounded work outside the full feature
6
- lifecycle. Workspace phases first read the active workspace from
5
+ `/rite-quick`, `/rite-fast`, and `/rite-frame` handle bounded work outside the full
6
+ feature lifecycle. Workspace phases first read the active workspace from
7
7
  `.devrites/ACTIVE` and `.devrites/work/<slug>/`. If none exists, they report the
8
8
  command that can create or select one. `/rite-upgrade [slug]` is the
9
9
  compatibility audit for an older active workspace that cannot resume.
@@ -274,9 +274,11 @@ func (r *runner) pruneDropped() error {
274
274
  return fmt.Errorf("strip hooks from %s: %w", merge.TargetRel, err)
275
275
  }
276
276
  } else if merge.TargetRel == hostpack.ClaudeSettingsMerge.TargetRel {
277
- _ = r.stripClaudeSettings(filepath.Join(r.target, filepath.FromSlash(merge.TargetRel)), true)
278
- } else {
279
- _ = stripMarkerPath(filepath.Join(r.target, filepath.FromSlash(merge.TargetRel)), merge.Begin, merge.End)
277
+ if err := r.stripClaudeSettings(filepath.Join(r.target, filepath.FromSlash(merge.TargetRel)), true); err != nil {
278
+ return fmt.Errorf("strip DevRites settings from %s: %w", merge.TargetRel, err)
279
+ }
280
+ } else if err := stripMarkerPath(filepath.Join(r.target, filepath.FromSlash(merge.TargetRel)), merge.Begin, merge.End); err != nil {
281
+ return fmt.Errorf("strip marker block from %s: %w", merge.TargetRel, err)
280
282
  }
281
283
  }
282
284
  if err := os.Remove(dead); err != nil && !os.IsNotExist(err) {
@@ -13,6 +13,7 @@ import (
13
13
  "time"
14
14
 
15
15
  "github.com/devrites/devrites/internal/fsutil"
16
+ "github.com/devrites/devrites/internal/version"
16
17
  )
17
18
 
18
19
  func (r *runner) installBinary() error {
@@ -28,7 +29,8 @@ func (r *runner) installBinary() error {
28
29
  incoming := strings.TrimPrefix(tag, "v")
29
30
  dest := binaryDest()
30
31
  if exists(dest) {
31
- if ev := engineVersion(dest); semverLike(ev) && semverLike(incoming) && verGT(ev, incoming) {
32
+ ev := engineVersion(dest)
33
+ if c, ok := version.Compare(ev, incoming); ok && c > 0 {
32
34
  fmt.Fprintf(r.opts.Stderr, "warning: engine binary: installed %s is newer than %s - refusing to downgrade (kept).\n", ev, tag)
33
35
  return nil
34
36
  }
@@ -405,28 +405,3 @@ func semverLike(v string) bool {
405
405
  v = strings.TrimPrefix(v, "v")
406
406
  return len(v) > 0 && v[0] >= '0' && v[0] <= '9'
407
407
  }
408
-
409
- func verGT(a, b string) bool {
410
- a = strings.TrimPrefix(a, "v")
411
- b = strings.TrimPrefix(b, "v")
412
- as := strings.Split(strings.Split(a, "-")[0], ".")
413
- bs := strings.Split(strings.Split(b, "-")[0], ".")
414
- for len(as) < 3 {
415
- as = append(as, "0")
416
- }
417
- for len(bs) < 3 {
418
- bs = append(bs, "0")
419
- }
420
- for i := 0; i < 3; i++ {
421
- var ai, bi int
422
- _, _ = fmt.Sscanf(as[i], "%d", &ai)
423
- _, _ = fmt.Sscanf(bs[i], "%d", &bi)
424
- if ai > bi {
425
- return true
426
- }
427
- if ai < bi {
428
- return false
429
- }
430
- }
431
- return false
432
- }
@@ -15,6 +15,7 @@ import (
15
15
  "time"
16
16
 
17
17
  "github.com/devrites/devrites/internal/parallel"
18
+ "github.com/devrites/devrites/internal/state"
18
19
  )
19
20
 
20
21
  // ClaimsFile is the repo-level append-only claim ledger under .devrites.
@@ -249,6 +250,20 @@ func claimAdd(root string, args []string, stdout, stderr io.Writer) int {
249
250
  return 2
250
251
  }
251
252
  path := claimsPath(root)
253
+ // Hold the ledger lock across read, conflict check, and append so two
254
+ // sessions cannot both pass the check and claim the same path.
255
+ code := 0
256
+ if err := state.WithLock(path+".lock", func() error {
257
+ code = claimAddLocked(path, session, reason, ttl, paths, stdout, stderr)
258
+ return nil
259
+ }); err != nil {
260
+ fmt.Fprintf(stderr, "claim: %v\n", err)
261
+ return 2
262
+ }
263
+ return code
264
+ }
265
+
266
+ func claimAddLocked(path, session, reason string, ttl int, paths []string, stdout, stderr io.Writer) int {
252
267
  latest, order, _, err := readClaims(path)
253
268
  if err != nil {
254
269
  fmt.Fprintf(stderr, "claim: %v\n", err)
@@ -3,9 +3,11 @@ package lib
3
3
  import (
4
4
  "bytes"
5
5
  "encoding/json"
6
+ "fmt"
6
7
  "os"
7
8
  "path/filepath"
8
9
  "strings"
10
+ "sync"
9
11
  "testing"
10
12
  "time"
11
13
  )
@@ -180,3 +182,29 @@ func TestClaimLedgerToleratesMalformedLines(t *testing.T) {
180
182
  t.Fatalf("malformed ledger: %d %q", code, out)
181
183
  }
182
184
  }
185
+
186
+ func TestClaimAddConcurrentOverlapOneWins(t *testing.T) {
187
+ root := claimTestRoot(t)
188
+ const n = 8
189
+ codes := make(chan int, n)
190
+ var wg sync.WaitGroup
191
+ for i := range n {
192
+ wg.Add(1)
193
+ go func() {
194
+ defer wg.Done()
195
+ var out, errOut bytes.Buffer
196
+ codes <- RunClaim(root, []string{"add", "--session", fmt.Sprintf("s%d", i), "pkg/a.go"}, &out, &errOut)
197
+ }()
198
+ }
199
+ wg.Wait()
200
+ close(codes)
201
+ wins := 0
202
+ for c := range codes {
203
+ if c == 0 {
204
+ wins++
205
+ }
206
+ }
207
+ if wins != 1 {
208
+ t.Fatalf("overlapping concurrent claims: %d succeeded, want exactly 1", wins)
209
+ }
210
+ }
@@ -5,6 +5,7 @@ import (
5
5
  "os"
6
6
  "path/filepath"
7
7
  "strings"
8
+ "sync"
8
9
  "testing"
9
10
  )
10
11
 
@@ -519,3 +520,55 @@ func TestDiffScopeAllowlist(t *testing.T) {
519
520
  t.Fatalf("code=%d\n%s", code, stdout.String())
520
521
  }
521
522
  }
523
+
524
+ func TestDispatchConcurrentReturnsCompleteWave(t *testing.T) {
525
+ root, featureDir := newWorkspace(t, "build")
526
+ run := func(args ...string) int {
527
+ out, err := &bytes.Buffer{}, &bytes.Buffer{}
528
+ return RunDispatch(root, args, out, err)
529
+ }
530
+ roles := []string{"r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7"}
531
+ open := []string{"feat", "open", "--phase", "review", "--wave", "w"}
532
+ for _, r := range roles {
533
+ open = append(open, "--role", r)
534
+ }
535
+ if c := run(open...); c != 0 {
536
+ t.Fatal("open failed")
537
+ }
538
+ for _, r := range roles {
539
+ if c := run("feat", "start", "--wave", "w", "--role", r, "--handle", "h-"+r); c != 0 {
540
+ t.Fatalf("start %s failed", r)
541
+ }
542
+ }
543
+ if c := run("feat", "seal", "--wave", "w"); c != 0 {
544
+ t.Fatal("seal failed")
545
+ }
546
+ var wg sync.WaitGroup
547
+ for _, r := range roles {
548
+ wg.Add(1)
549
+ go func() {
550
+ defer wg.Done()
551
+ if c := run("feat", "return", "--wave", "w", "--role", r); c != 0 {
552
+ t.Errorf("return %s failed: %d", r, c)
553
+ }
554
+ }()
555
+ }
556
+ wg.Wait()
557
+ if c := run("feat", "status", "--wave", "w"); c != 0 {
558
+ data, _ := os.ReadFile(filepath.Join(featureDir, "dispatch.json"))
559
+ t.Fatalf("concurrent returns lost an update; wave not complete:\n%s", data)
560
+ }
561
+ }
562
+
563
+ func TestDispatchAbandonRecordsReason(t *testing.T) {
564
+ root, featureDir := newWorkspace(t, "build")
565
+ out, errOut := &bytes.Buffer{}, &bytes.Buffer{}
566
+ if c := RunDispatch(root, []string{"feat", "open", "--phase", "build", "--wave", "w", "--role", "x"}, out, errOut); c != 0 {
567
+ t.Fatal("open failed")
568
+ }
569
+ RunDispatch(root, []string{"feat", "abandon", "--wave", "w", "--reason", "host limit"}, out, errOut)
570
+ data, err := os.ReadFile(filepath.Join(featureDir, "dispatch.json"))
571
+ if err != nil || !strings.Contains(string(data), `"reason": "host limit"`) {
572
+ t.Fatalf("abandon reason not persisted: %v\n%s", err, data)
573
+ }
574
+ }
@@ -6,10 +6,12 @@ import (
6
6
  "io"
7
7
  "os"
8
8
  "path/filepath"
9
+ "sort"
9
10
  "strings"
10
11
  "time"
11
12
 
12
13
  "github.com/devrites/devrites/internal/devritespaths"
14
+ "github.com/devrites/devrites/internal/state"
13
15
  )
14
16
 
15
17
  // dispatch.go: launch-wave barrier for host-native agent dispatch. Hosts own
@@ -31,9 +33,10 @@ type dispatchFile struct {
31
33
  }
32
34
 
33
35
  type dispatchWave struct {
34
- Phase string `json:"phase"`
35
- State string `json:"state"` // open, sealed, abandoned, complete
36
- Roles map[string]*dispatchRole `json:"roles"`
36
+ Phase string `json:"phase"`
37
+ State string `json:"state"` // open, sealed, abandoned, complete
38
+ Reason string `json:"reason,omitempty"`
39
+ Roles map[string]*dispatchRole `json:"roles"`
37
40
  }
38
41
 
39
42
  type dispatchRole struct {
@@ -79,7 +82,7 @@ func saveDispatch(path string, f *dispatchFile) error {
79
82
  if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
80
83
  return err
81
84
  }
82
- return os.WriteFile(path, data, 0o600)
85
+ return state.AtomicWrite(path, data, 0o600)
83
86
  }
84
87
 
85
88
  func nowStamp() string { return time.Now().UTC().Format(time.RFC3339) }
@@ -122,6 +125,20 @@ func RunDispatch(root string, args []string, stdout, stderr io.Writer) int {
122
125
  fmt.Fprintf(stderr, "dispatch: %v\n", err)
123
126
  return 2
124
127
  }
128
+ // Serialize load-modify-save so concurrent start/return calls never lose
129
+ // an update and strand the wave short of complete.
130
+ code := 0
131
+ if err := state.WithFeatureLock(root, slug, func() error {
132
+ code = dispatchLocked(root, slug, sub, phase, wave, role, handle, reason, roles, stdout, stderr)
133
+ return nil
134
+ }); err != nil {
135
+ fmt.Fprintf(stderr, "dispatch: %v\n", err)
136
+ return 3
137
+ }
138
+ return code
139
+ }
140
+
141
+ func dispatchLocked(root, slug, sub, phase, wave, role, handle, reason string, roles []string, stdout, stderr io.Writer) int {
125
142
  f, path, err := loadDispatch(root, slug)
126
143
  if err != nil {
127
144
  fmt.Fprintf(stderr, "dispatch: %v\n", err)
@@ -204,6 +221,7 @@ func RunDispatch(root string, args []string, stdout, stderr io.Writer) int {
204
221
  }
205
222
  }
206
223
  if len(missing) > 0 {
224
+ sort.Strings(missing)
207
225
  return fail("wave %q cannot seal: no start handle for %s", wave, strings.Join(missing, ","))
208
226
  }
209
227
  w.State = "sealed"
@@ -244,7 +262,8 @@ func RunDispatch(root string, args []string, stdout, stderr io.Writer) int {
244
262
 
245
263
  case "status":
246
264
  if wave == "" {
247
- for name, w := range f.Waves {
265
+ for _, name := range sortedKeys(f.Waves) {
266
+ w := f.Waves[name]
248
267
  fmt.Fprintf(stdout, "dispatch: wave %s state=%s roles=%d\n", name, w.State, len(w.Roles))
249
268
  }
250
269
  if len(f.Waves) == 0 {
@@ -257,7 +276,8 @@ func RunDispatch(root string, args []string, stdout, stderr io.Writer) int {
257
276
  return fail("no wave %q", wave)
258
277
  }
259
278
  var pending []string
260
- for name, r := range w.Roles {
279
+ for _, name := range sortedKeys(w.Roles) {
280
+ r := w.Roles[name]
261
281
  mark := "declared"
262
282
  if r.Handle != "" {
263
283
  mark = "started"
@@ -281,9 +301,10 @@ func RunDispatch(root string, args []string, stdout, stderr io.Writer) int {
281
301
  return fail("no wave %q", wave)
282
302
  }
283
303
  if strings.TrimSpace(reason) == "" {
284
- return fail("--reason is required (non-empty, surfaced in handoff)")
304
+ return fail("--reason is required (non-empty, recorded in dispatch.json)")
285
305
  }
286
306
  w.State = "abandoned"
307
+ w.Reason = reason
287
308
  if err := saveDispatch(path, f); err != nil {
288
309
  return fail("%v", err)
289
310
  }
@@ -48,6 +48,20 @@ func RunMergeManifest(root string, args []string, stdout, stderr io.Writer) int
48
48
  fmt.Fprintf(stderr, "merge-manifest: BLOCKED: %v\n", err)
49
49
  return 3
50
50
  }
51
+ code := 0
52
+ if err := state.WithFeatureLock(root, slug, func() error {
53
+ code = mergeManifestLocked(root, slug, workspace, args, stdout, stderr)
54
+ return nil
55
+ }); err != nil {
56
+ fmt.Fprintf(stderr, "merge-manifest: %v\n", err)
57
+ return 1
58
+ }
59
+ return code
60
+ }
61
+
62
+ // mergeManifestLocked reads, unions, and rewrites touched-files.md under the
63
+ // feature lock so a concurrent writer cannot interleave with the rewrite.
64
+ func mergeManifestLocked(root, slug, workspace string, args []string, stdout, stderr io.Writer) int {
51
65
  manifestPath := filepath.Join(workspace, "touched-files.md")
52
66
  releaseRaw, err := readBoundedRegularFile(manifestPath, maxCandidateManifestBytes)
53
67
  if err != nil {
@@ -80,8 +80,16 @@ func TestReceipt(t *testing.T) {
80
80
  r["task_id"], r["role"], r["lane"], r["outcome"] = "R-1", "reviewer", "security", "no-findings"
81
81
  delete(r, "changed_paths")
82
82
  }, "-", 1, "no-findings without inspected ranges is malformed"},
83
+ {"reviewer no-findings without domains", func(r J) {
84
+ r["task_id"], r["role"], r["lane"], r["outcome"], r["inspected"] = "R-1", "reviewer", "security", "no-findings", L{"src/a.py:1-9"}
85
+ }, "-", 1, "no-findings must name its domains_checked"},
86
+ {"reviewer unknown domain", func(r J) {
87
+ r["task_id"], r["role"], r["lane"], r["outcome"], r["inspected"] = "R-1", "reviewer", "security", "no-findings", L{"src/a.py:1-9"}
88
+ r["domains_checked"] = L{"style"}
89
+ }, "-", 1, "domains_checked has unknown domain 'style'"},
83
90
  {"reviewer admissible", func(r J) {
84
91
  r["task_id"], r["role"], r["lane"], r["outcome"], r["inspected"] = "R-1", "reviewer", "security", "no-findings", L{"src/a.py:1-9"}
92
+ r["domains_checked"] = L{"security"}
85
93
  }, "-", 0, "ADMISSIBLE"},
86
94
  }
87
95
  for _, c := range cases {
@@ -114,6 +122,7 @@ func TestReceiptAgainstOpenStage(t *testing.T) {
114
122
  }})
115
123
  rc := writerReceipt()
116
124
  rc["task_id"], rc["role"], rc["lane"], rc["outcome"], rc["inspected"] = "R-2", "reviewer", "security", "no-findings", L{"src/a.py:1-9"}
125
+ rc["domains_checked"] = L{"security"}
117
126
  delete(rc, "changed_paths")
118
127
  p := write(t, filepath.Join(dir, "receipts/r2.json"), rc)
119
128
  if code, out, errs := run("receipt", dir, p); code != 0 || !strings.Contains(out, "ADMISSIBLE") {