devrites 7.2.7 → 7.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (340) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +6 -1
  3. package/docs/candidate-integrity.md +10 -0
  4. package/docs/cli.md +7 -1
  5. package/docs/command-map.md +9 -2
  6. package/docs/engine/commands.md +50 -4
  7. package/docs/orchestration.md +35 -13
  8. package/docs/skills.md +5 -0
  9. package/engine/internal/acceptance/cli.go +35 -30
  10. package/engine/internal/acceptance/preconditions.go +1 -1
  11. package/engine/internal/acceptance/run.go +138 -53
  12. package/engine/internal/acceptance/transcript.go +236 -45
  13. package/engine/internal/devritespaths/paths.go +23 -5
  14. package/engine/internal/gate/gate.go +6 -0
  15. package/engine/internal/install/apply.go +5 -0
  16. package/engine/internal/install/merge.go +48 -6
  17. package/engine/internal/install/preflight.go +21 -3
  18. package/engine/internal/install/uninstall.go +9 -10
  19. package/engine/internal/lib/apply_bundle.go +7 -1
  20. package/engine/internal/lib/candidate.go +20 -7
  21. package/engine/internal/lib/candidate_coverage.go +2 -19
  22. package/engine/internal/lib/closeout.go +22 -10
  23. package/engine/internal/lib/context.go +67 -47
  24. package/engine/internal/lib/context_io.go +171 -0
  25. package/engine/internal/lib/detect.go +8 -8
  26. package/engine/internal/lib/dispatch.go +6 -0
  27. package/engine/internal/lib/packet.go +37 -7
  28. package/engine/internal/lib/packet_freeze.go +60 -12
  29. package/engine/internal/lib/packet_integrity.go +179 -0
  30. package/engine/internal/lib/perturb.go +3 -3
  31. package/engine/internal/lib/resolve.go +10 -5
  32. package/engine/internal/lib/slicecheck.go +7 -1
  33. package/engine/internal/lib/state_edit.go +9 -2
  34. package/engine/internal/lib/taskgraph.go +20 -36
  35. package/engine/internal/lib/triggers.go +30 -31
  36. package/engine/internal/lib/workspace_pool.go +330 -0
  37. package/engine/internal/parallel/cli.go +190 -49
  38. package/engine/internal/parallel/git.go +47 -14
  39. package/engine/internal/parallel/landing.go +232 -0
  40. package/engine/internal/parallel/lease.go +97 -18
  41. package/engine/internal/parallel/ops.go +312 -155
  42. package/engine/internal/parallel/pathdisjoint.go +48 -4
  43. package/engine/internal/parallel/prune.go +133 -0
  44. package/engine/internal/parallel/repository.go +239 -0
  45. package/engine/internal/parallel/select.go +64 -42
  46. package/engine/internal/state/cursor.go +5 -5
  47. package/engine/main.go +2 -1
  48. package/pack/.claude/agents/devrites-proof-runner.md +16 -9
  49. package/pack/.claude/skills/devrites-lib/SKILL.md +2 -0
  50. package/pack/.claude/skills/devrites-lib/reference/candidate-integrity.md +7 -3
  51. package/pack/.claude/skills/devrites-lib/reference/parallel-dispatch.md +3 -0
  52. package/pack/.claude/skills/devrites-lib/reference/parallel-workspaces.md +202 -0
  53. package/pack/.claude/skills/devrites-lib/reference/standards/afk-hitl.md +9 -12
  54. package/pack/.claude/skills/devrites-lib/reference/standards/agents.md +7 -3
  55. package/pack/.claude/skills/devrites-lib/reference/standards/context-hygiene.md +1 -1
  56. package/pack/.claude/skills/devrites-lib/reference/standards/gates.md +18 -9
  57. package/pack/.claude/skills/devrites-lib/reference/workspace-artifact-schema.md +10 -0
  58. package/pack/.claude/skills/rite-autocomplete/SKILL.md +13 -10
  59. package/pack/.claude/skills/rite-autocomplete/reference/lookahead.md +4 -4
  60. package/pack/.claude/skills/rite-autocomplete/reference/loop.md +13 -9
  61. package/pack/.claude/skills/rite-build/SKILL.md +13 -10
  62. package/pack/.claude/skills/rite-build/reference/afk-discipline.md +1 -1
  63. package/pack/.claude/skills/rite-build/reference/checkpoint.md +1 -1
  64. package/pack/.claude/skills/rite-build/reference/parallel-batch.md +60 -39
  65. package/pack/.claude/skills/rite-build/reference/phase-contract.md +8 -5
  66. package/pack/.claude/skills/rite-build/reference/wright-dispatch.md +7 -7
  67. package/pack/.claude/skills/rite-clarify/SKILL.md +2 -2
  68. package/pack/.claude/skills/rite-converge/SKILL.md +4 -4
  69. package/pack/.claude/skills/rite-define/SKILL.md +7 -3
  70. package/pack/.claude/skills/rite-define/reference/plan-template.md +4 -2
  71. package/pack/.claude/skills/rite-plan/SKILL.md +11 -8
  72. package/pack/.claude/skills/rite-plan/reference/dependency-graph.md +9 -2
  73. package/pack/.claude/skills/rite-plan/reference/slicing.md +2 -0
  74. package/pack/.claude/skills/rite-polish/SKILL.md +3 -3
  75. package/pack/.claude/skills/rite-prove/SKILL.md +9 -5
  76. package/pack/.claude/skills/rite-resolve/SKILL.md +3 -3
  77. package/pack/.claude/skills/rite-resolve/reference/answer-protocol.md +2 -0
  78. package/pack/.claude/skills/rite-review/SKILL.md +4 -3
  79. package/pack/.claude/skills/rite-review/reference/feature-scoped-review.md +2 -2
  80. package/pack/.claude/skills/rite-seal/SKILL.md +1 -1
  81. package/pack/.claude/skills/rite-seal/reference/phase-contract.md +1 -1
  82. package/pack/.claude/skills/rite-ship/SKILL.md +2 -2
  83. package/pack/.claude/skills/rite-ship/reference/git-ship.md +2 -2
  84. package/pack/.claude/skills/rite-spec/SKILL.md +8 -5
  85. package/pack/.claude/skills/rite-spec/reference/state-workspace.md +5 -0
  86. package/pack/.claude/skills/rite-temper/SKILL.md +3 -3
  87. package/pack/.claude/skills/rite-vet/SKILL.md +9 -5
  88. package/pack/.claude/skills/rite-vet/reference/review-axes.md +12 -5
  89. package/pack/generated/claude/agents/devrites-proof-runner.md +16 -9
  90. package/pack/generated/claude/skills/devrites-lib/SKILL.md +2 -0
  91. package/pack/generated/claude/skills/devrites-lib/reference/candidate-integrity.md +7 -3
  92. package/pack/generated/claude/skills/devrites-lib/reference/parallel-dispatch.md +3 -0
  93. package/pack/generated/claude/skills/devrites-lib/reference/parallel-workspaces.md +202 -0
  94. package/pack/generated/claude/skills/devrites-lib/reference/standards/afk-hitl.md +9 -12
  95. package/pack/generated/claude/skills/devrites-lib/reference/standards/agents.md +7 -3
  96. package/pack/generated/claude/skills/devrites-lib/reference/standards/context-hygiene.md +1 -1
  97. package/pack/generated/claude/skills/devrites-lib/reference/standards/gates.md +18 -9
  98. package/pack/generated/claude/skills/devrites-lib/reference/workspace-artifact-schema.md +10 -0
  99. package/pack/generated/claude/skills/rite-autocomplete/SKILL.md +13 -10
  100. package/pack/generated/claude/skills/rite-autocomplete/reference/lookahead.md +4 -4
  101. package/pack/generated/claude/skills/rite-autocomplete/reference/loop.md +13 -9
  102. package/pack/generated/claude/skills/rite-build/SKILL.md +13 -10
  103. package/pack/generated/claude/skills/rite-build/reference/afk-discipline.md +1 -1
  104. package/pack/generated/claude/skills/rite-build/reference/checkpoint.md +1 -1
  105. package/pack/generated/claude/skills/rite-build/reference/parallel-batch.md +60 -39
  106. package/pack/generated/claude/skills/rite-build/reference/phase-contract.md +8 -5
  107. package/pack/generated/claude/skills/rite-build/reference/wright-dispatch.md +7 -7
  108. package/pack/generated/claude/skills/rite-clarify/SKILL.md +2 -2
  109. package/pack/generated/claude/skills/rite-converge/SKILL.md +4 -4
  110. package/pack/generated/claude/skills/rite-define/SKILL.md +7 -3
  111. package/pack/generated/claude/skills/rite-define/reference/plan-template.md +4 -2
  112. package/pack/generated/claude/skills/rite-plan/SKILL.md +11 -8
  113. package/pack/generated/claude/skills/rite-plan/reference/dependency-graph.md +9 -2
  114. package/pack/generated/claude/skills/rite-plan/reference/slicing.md +2 -0
  115. package/pack/generated/claude/skills/rite-polish/SKILL.md +3 -3
  116. package/pack/generated/claude/skills/rite-prove/SKILL.md +9 -5
  117. package/pack/generated/claude/skills/rite-resolve/SKILL.md +3 -3
  118. package/pack/generated/claude/skills/rite-resolve/reference/answer-protocol.md +2 -0
  119. package/pack/generated/claude/skills/rite-review/SKILL.md +4 -3
  120. package/pack/generated/claude/skills/rite-review/reference/feature-scoped-review.md +2 -2
  121. package/pack/generated/claude/skills/rite-seal/SKILL.md +1 -1
  122. package/pack/generated/claude/skills/rite-seal/reference/phase-contract.md +1 -1
  123. package/pack/generated/claude/skills/rite-ship/SKILL.md +2 -2
  124. package/pack/generated/claude/skills/rite-ship/reference/git-ship.md +2 -2
  125. package/pack/generated/claude/skills/rite-spec/SKILL.md +8 -5
  126. package/pack/generated/claude/skills/rite-spec/reference/state-workspace.md +5 -0
  127. package/pack/generated/claude/skills/rite-temper/SKILL.md +3 -3
  128. package/pack/generated/claude/skills/rite-vet/SKILL.md +9 -5
  129. package/pack/generated/claude/skills/rite-vet/reference/review-axes.md +12 -5
  130. package/pack/generated/codex/agents/devrites-proof-runner.toml +16 -9
  131. package/pack/generated/codex/skills/devrites-lib/SKILL.md +2 -0
  132. package/pack/generated/codex/skills/devrites-lib/reference/candidate-integrity.md +7 -3
  133. package/pack/generated/codex/skills/devrites-lib/reference/parallel-dispatch.md +3 -0
  134. package/pack/generated/codex/skills/devrites-lib/reference/parallel-workspaces.md +202 -0
  135. package/pack/generated/codex/skills/devrites-lib/reference/standards/afk-hitl.md +9 -12
  136. package/pack/generated/codex/skills/devrites-lib/reference/standards/agents.md +7 -3
  137. package/pack/generated/codex/skills/devrites-lib/reference/standards/context-hygiene.md +1 -1
  138. package/pack/generated/codex/skills/devrites-lib/reference/standards/gates.md +18 -9
  139. package/pack/generated/codex/skills/devrites-lib/reference/workspace-artifact-schema.md +10 -0
  140. package/pack/generated/codex/skills/rite-autocomplete/SKILL.md +13 -10
  141. package/pack/generated/codex/skills/rite-autocomplete/reference/lookahead.md +4 -4
  142. package/pack/generated/codex/skills/rite-autocomplete/reference/loop.md +13 -9
  143. package/pack/generated/codex/skills/rite-build/SKILL.md +13 -10
  144. package/pack/generated/codex/skills/rite-build/reference/afk-discipline.md +1 -1
  145. package/pack/generated/codex/skills/rite-build/reference/checkpoint.md +1 -1
  146. package/pack/generated/codex/skills/rite-build/reference/parallel-batch.md +60 -39
  147. package/pack/generated/codex/skills/rite-build/reference/phase-contract.md +8 -5
  148. package/pack/generated/codex/skills/rite-build/reference/wright-dispatch.md +7 -7
  149. package/pack/generated/codex/skills/rite-clarify/SKILL.md +2 -2
  150. package/pack/generated/codex/skills/rite-converge/SKILL.md +4 -4
  151. package/pack/generated/codex/skills/rite-define/SKILL.md +7 -3
  152. package/pack/generated/codex/skills/rite-define/reference/plan-template.md +4 -2
  153. package/pack/generated/codex/skills/rite-plan/SKILL.md +11 -8
  154. package/pack/generated/codex/skills/rite-plan/reference/dependency-graph.md +9 -2
  155. package/pack/generated/codex/skills/rite-plan/reference/slicing.md +2 -0
  156. package/pack/generated/codex/skills/rite-polish/SKILL.md +3 -3
  157. package/pack/generated/codex/skills/rite-prove/SKILL.md +9 -5
  158. package/pack/generated/codex/skills/rite-resolve/SKILL.md +3 -3
  159. package/pack/generated/codex/skills/rite-resolve/reference/answer-protocol.md +2 -0
  160. package/pack/generated/codex/skills/rite-review/SKILL.md +4 -3
  161. package/pack/generated/codex/skills/rite-review/reference/feature-scoped-review.md +2 -2
  162. package/pack/generated/codex/skills/rite-seal/SKILL.md +1 -1
  163. package/pack/generated/codex/skills/rite-seal/reference/phase-contract.md +1 -1
  164. package/pack/generated/codex/skills/rite-ship/SKILL.md +2 -2
  165. package/pack/generated/codex/skills/rite-ship/reference/git-ship.md +2 -2
  166. package/pack/generated/codex/skills/rite-spec/SKILL.md +8 -5
  167. package/pack/generated/codex/skills/rite-spec/reference/state-workspace.md +5 -0
  168. package/pack/generated/codex/skills/rite-temper/SKILL.md +3 -3
  169. package/pack/generated/codex/skills/rite-vet/SKILL.md +9 -5
  170. package/pack/generated/codex/skills/rite-vet/reference/review-axes.md +12 -5
  171. package/pack/generated/devin/agents/devrites-proof-runner.md +16 -9
  172. package/pack/generated/devin/skills/devrites-lib/SKILL.md +2 -0
  173. package/pack/generated/devin/skills/devrites-lib/reference/candidate-integrity.md +7 -3
  174. package/pack/generated/devin/skills/devrites-lib/reference/parallel-dispatch.md +3 -0
  175. package/pack/generated/devin/skills/devrites-lib/reference/parallel-workspaces.md +202 -0
  176. package/pack/generated/devin/skills/devrites-lib/reference/standards/afk-hitl.md +9 -12
  177. package/pack/generated/devin/skills/devrites-lib/reference/standards/agents.md +7 -3
  178. package/pack/generated/devin/skills/devrites-lib/reference/standards/context-hygiene.md +1 -1
  179. package/pack/generated/devin/skills/devrites-lib/reference/standards/gates.md +18 -9
  180. package/pack/generated/devin/skills/devrites-lib/reference/workspace-artifact-schema.md +10 -0
  181. package/pack/generated/devin/skills/rite-autocomplete/SKILL.md +13 -10
  182. package/pack/generated/devin/skills/rite-autocomplete/reference/lookahead.md +4 -4
  183. package/pack/generated/devin/skills/rite-autocomplete/reference/loop.md +13 -9
  184. package/pack/generated/devin/skills/rite-build/SKILL.md +13 -10
  185. package/pack/generated/devin/skills/rite-build/reference/afk-discipline.md +1 -1
  186. package/pack/generated/devin/skills/rite-build/reference/checkpoint.md +1 -1
  187. package/pack/generated/devin/skills/rite-build/reference/parallel-batch.md +60 -39
  188. package/pack/generated/devin/skills/rite-build/reference/phase-contract.md +8 -5
  189. package/pack/generated/devin/skills/rite-build/reference/wright-dispatch.md +7 -7
  190. package/pack/generated/devin/skills/rite-clarify/SKILL.md +2 -2
  191. package/pack/generated/devin/skills/rite-converge/SKILL.md +4 -4
  192. package/pack/generated/devin/skills/rite-define/SKILL.md +7 -3
  193. package/pack/generated/devin/skills/rite-define/reference/plan-template.md +4 -2
  194. package/pack/generated/devin/skills/rite-plan/SKILL.md +11 -8
  195. package/pack/generated/devin/skills/rite-plan/reference/dependency-graph.md +9 -2
  196. package/pack/generated/devin/skills/rite-plan/reference/slicing.md +2 -0
  197. package/pack/generated/devin/skills/rite-polish/SKILL.md +3 -3
  198. package/pack/generated/devin/skills/rite-prove/SKILL.md +9 -5
  199. package/pack/generated/devin/skills/rite-resolve/SKILL.md +3 -3
  200. package/pack/generated/devin/skills/rite-resolve/reference/answer-protocol.md +2 -0
  201. package/pack/generated/devin/skills/rite-review/SKILL.md +4 -3
  202. package/pack/generated/devin/skills/rite-review/reference/feature-scoped-review.md +2 -2
  203. package/pack/generated/devin/skills/rite-seal/SKILL.md +1 -1
  204. package/pack/generated/devin/skills/rite-seal/reference/phase-contract.md +1 -1
  205. package/pack/generated/devin/skills/rite-ship/SKILL.md +2 -2
  206. package/pack/generated/devin/skills/rite-ship/reference/git-ship.md +2 -2
  207. package/pack/generated/devin/skills/rite-spec/SKILL.md +8 -5
  208. package/pack/generated/devin/skills/rite-spec/reference/state-workspace.md +5 -0
  209. package/pack/generated/devin/skills/rite-temper/SKILL.md +3 -3
  210. package/pack/generated/devin/skills/rite-vet/SKILL.md +9 -5
  211. package/pack/generated/devin/skills/rite-vet/reference/review-axes.md +12 -5
  212. package/pack/generated/dsh/agents/devrites-proof-runner.md +16 -9
  213. package/pack/generated/dsh/runtime/cordis.patch.yml +1 -1
  214. package/pack/generated/dsh/skills/devrites-lib/SKILL.md +2 -0
  215. package/pack/generated/dsh/skills/devrites-lib/reference/candidate-integrity.md +7 -3
  216. package/pack/generated/dsh/skills/devrites-lib/reference/parallel-dispatch.md +3 -0
  217. package/pack/generated/dsh/skills/devrites-lib/reference/parallel-workspaces.md +202 -0
  218. package/pack/generated/dsh/skills/devrites-lib/reference/standards/afk-hitl.md +9 -12
  219. package/pack/generated/dsh/skills/devrites-lib/reference/standards/agents.md +7 -3
  220. package/pack/generated/dsh/skills/devrites-lib/reference/standards/context-hygiene.md +1 -1
  221. package/pack/generated/dsh/skills/devrites-lib/reference/standards/gates.md +18 -9
  222. package/pack/generated/dsh/skills/devrites-lib/reference/workspace-artifact-schema.md +10 -0
  223. package/pack/generated/dsh/skills/rite-autocomplete/SKILL.md +13 -10
  224. package/pack/generated/dsh/skills/rite-autocomplete/reference/lookahead.md +4 -4
  225. package/pack/generated/dsh/skills/rite-autocomplete/reference/loop.md +13 -9
  226. package/pack/generated/dsh/skills/rite-build/SKILL.md +13 -10
  227. package/pack/generated/dsh/skills/rite-build/reference/afk-discipline.md +1 -1
  228. package/pack/generated/dsh/skills/rite-build/reference/checkpoint.md +1 -1
  229. package/pack/generated/dsh/skills/rite-build/reference/parallel-batch.md +60 -39
  230. package/pack/generated/dsh/skills/rite-build/reference/phase-contract.md +8 -5
  231. package/pack/generated/dsh/skills/rite-build/reference/wright-dispatch.md +7 -7
  232. package/pack/generated/dsh/skills/rite-clarify/SKILL.md +2 -2
  233. package/pack/generated/dsh/skills/rite-converge/SKILL.md +4 -4
  234. package/pack/generated/dsh/skills/rite-define/SKILL.md +7 -3
  235. package/pack/generated/dsh/skills/rite-define/reference/plan-template.md +4 -2
  236. package/pack/generated/dsh/skills/rite-plan/SKILL.md +11 -8
  237. package/pack/generated/dsh/skills/rite-plan/reference/dependency-graph.md +9 -2
  238. package/pack/generated/dsh/skills/rite-plan/reference/slicing.md +2 -0
  239. package/pack/generated/dsh/skills/rite-polish/SKILL.md +3 -3
  240. package/pack/generated/dsh/skills/rite-prove/SKILL.md +9 -5
  241. package/pack/generated/dsh/skills/rite-resolve/SKILL.md +3 -3
  242. package/pack/generated/dsh/skills/rite-resolve/reference/answer-protocol.md +2 -0
  243. package/pack/generated/dsh/skills/rite-review/SKILL.md +4 -3
  244. package/pack/generated/dsh/skills/rite-review/reference/feature-scoped-review.md +2 -2
  245. package/pack/generated/dsh/skills/rite-seal/SKILL.md +1 -1
  246. package/pack/generated/dsh/skills/rite-seal/reference/phase-contract.md +1 -1
  247. package/pack/generated/dsh/skills/rite-ship/SKILL.md +2 -2
  248. package/pack/generated/dsh/skills/rite-ship/reference/git-ship.md +2 -2
  249. package/pack/generated/dsh/skills/rite-spec/SKILL.md +8 -5
  250. package/pack/generated/dsh/skills/rite-spec/reference/state-workspace.md +5 -0
  251. package/pack/generated/dsh/skills/rite-temper/SKILL.md +3 -3
  252. package/pack/generated/dsh/skills/rite-vet/SKILL.md +9 -5
  253. package/pack/generated/dsh/skills/rite-vet/reference/review-axes.md +12 -5
  254. package/pack/generated/omp/agents/devrites-proof-runner.md +16 -9
  255. package/pack/generated/omp/commands/rite-build.md +1 -1
  256. package/pack/generated/omp/skills/devrites-lib/SKILL.md +2 -0
  257. package/pack/generated/omp/skills/devrites-lib/reference/candidate-integrity.md +7 -3
  258. package/pack/generated/omp/skills/devrites-lib/reference/parallel-dispatch.md +3 -0
  259. package/pack/generated/omp/skills/devrites-lib/reference/parallel-workspaces.md +202 -0
  260. package/pack/generated/omp/skills/devrites-lib/reference/standards/afk-hitl.md +9 -12
  261. package/pack/generated/omp/skills/devrites-lib/reference/standards/agents.md +11 -3
  262. package/pack/generated/omp/skills/devrites-lib/reference/standards/context-hygiene.md +1 -1
  263. package/pack/generated/omp/skills/devrites-lib/reference/standards/gates.md +18 -9
  264. package/pack/generated/omp/skills/devrites-lib/reference/workspace-artifact-schema.md +10 -0
  265. package/pack/generated/omp/skills/rite-autocomplete/SKILL.md +13 -10
  266. package/pack/generated/omp/skills/rite-autocomplete/reference/lookahead.md +4 -4
  267. package/pack/generated/omp/skills/rite-autocomplete/reference/loop.md +13 -9
  268. package/pack/generated/omp/skills/rite-build/SKILL.md +13 -10
  269. package/pack/generated/omp/skills/rite-build/reference/afk-discipline.md +1 -1
  270. package/pack/generated/omp/skills/rite-build/reference/checkpoint.md +1 -1
  271. package/pack/generated/omp/skills/rite-build/reference/parallel-batch.md +60 -39
  272. package/pack/generated/omp/skills/rite-build/reference/phase-contract.md +8 -5
  273. package/pack/generated/omp/skills/rite-build/reference/wright-dispatch.md +7 -7
  274. package/pack/generated/omp/skills/rite-clarify/SKILL.md +2 -2
  275. package/pack/generated/omp/skills/rite-converge/SKILL.md +4 -4
  276. package/pack/generated/omp/skills/rite-define/SKILL.md +7 -3
  277. package/pack/generated/omp/skills/rite-define/reference/plan-template.md +4 -2
  278. package/pack/generated/omp/skills/rite-plan/SKILL.md +11 -8
  279. package/pack/generated/omp/skills/rite-plan/reference/dependency-graph.md +9 -2
  280. package/pack/generated/omp/skills/rite-plan/reference/slicing.md +2 -0
  281. package/pack/generated/omp/skills/rite-polish/SKILL.md +3 -3
  282. package/pack/generated/omp/skills/rite-prove/SKILL.md +9 -5
  283. package/pack/generated/omp/skills/rite-resolve/SKILL.md +3 -3
  284. package/pack/generated/omp/skills/rite-resolve/reference/answer-protocol.md +2 -0
  285. package/pack/generated/omp/skills/rite-review/SKILL.md +4 -3
  286. package/pack/generated/omp/skills/rite-review/reference/feature-scoped-review.md +2 -2
  287. package/pack/generated/omp/skills/rite-seal/SKILL.md +1 -1
  288. package/pack/generated/omp/skills/rite-seal/reference/phase-contract.md +1 -1
  289. package/pack/generated/omp/skills/rite-ship/SKILL.md +2 -2
  290. package/pack/generated/omp/skills/rite-ship/reference/git-ship.md +2 -2
  291. package/pack/generated/omp/skills/rite-spec/SKILL.md +8 -5
  292. package/pack/generated/omp/skills/rite-spec/reference/state-workspace.md +5 -0
  293. package/pack/generated/omp/skills/rite-temper/SKILL.md +3 -3
  294. package/pack/generated/omp/skills/rite-vet/SKILL.md +9 -5
  295. package/pack/generated/omp/skills/rite-vet/reference/review-axes.md +12 -5
  296. package/pack/generated/pi/agents/devrites-proof-runner.md +16 -9
  297. package/pack/generated/pi/prompts/rite-build.md +1 -1
  298. package/pack/generated/pi/skills/devrites-lib/SKILL.md +2 -0
  299. package/pack/generated/pi/skills/devrites-lib/reference/candidate-integrity.md +7 -3
  300. package/pack/generated/pi/skills/devrites-lib/reference/parallel-dispatch.md +3 -0
  301. package/pack/generated/pi/skills/devrites-lib/reference/parallel-workspaces.md +202 -0
  302. package/pack/generated/pi/skills/devrites-lib/reference/standards/afk-hitl.md +9 -12
  303. package/pack/generated/pi/skills/devrites-lib/reference/standards/agents.md +7 -3
  304. package/pack/generated/pi/skills/devrites-lib/reference/standards/context-hygiene.md +1 -1
  305. package/pack/generated/pi/skills/devrites-lib/reference/standards/gates.md +18 -9
  306. package/pack/generated/pi/skills/devrites-lib/reference/workspace-artifact-schema.md +10 -0
  307. package/pack/generated/pi/skills/rite-autocomplete/SKILL.md +13 -10
  308. package/pack/generated/pi/skills/rite-autocomplete/reference/lookahead.md +4 -4
  309. package/pack/generated/pi/skills/rite-autocomplete/reference/loop.md +13 -9
  310. package/pack/generated/pi/skills/rite-build/SKILL.md +13 -10
  311. package/pack/generated/pi/skills/rite-build/reference/afk-discipline.md +1 -1
  312. package/pack/generated/pi/skills/rite-build/reference/checkpoint.md +1 -1
  313. package/pack/generated/pi/skills/rite-build/reference/parallel-batch.md +60 -39
  314. package/pack/generated/pi/skills/rite-build/reference/phase-contract.md +8 -5
  315. package/pack/generated/pi/skills/rite-build/reference/wright-dispatch.md +7 -7
  316. package/pack/generated/pi/skills/rite-clarify/SKILL.md +2 -2
  317. package/pack/generated/pi/skills/rite-converge/SKILL.md +4 -4
  318. package/pack/generated/pi/skills/rite-define/SKILL.md +7 -3
  319. package/pack/generated/pi/skills/rite-define/reference/plan-template.md +4 -2
  320. package/pack/generated/pi/skills/rite-plan/SKILL.md +11 -8
  321. package/pack/generated/pi/skills/rite-plan/reference/dependency-graph.md +9 -2
  322. package/pack/generated/pi/skills/rite-plan/reference/slicing.md +2 -0
  323. package/pack/generated/pi/skills/rite-polish/SKILL.md +3 -3
  324. package/pack/generated/pi/skills/rite-prove/SKILL.md +9 -5
  325. package/pack/generated/pi/skills/rite-resolve/SKILL.md +3 -3
  326. package/pack/generated/pi/skills/rite-resolve/reference/answer-protocol.md +2 -0
  327. package/pack/generated/pi/skills/rite-review/SKILL.md +4 -3
  328. package/pack/generated/pi/skills/rite-review/reference/feature-scoped-review.md +2 -2
  329. package/pack/generated/pi/skills/rite-seal/SKILL.md +1 -1
  330. package/pack/generated/pi/skills/rite-seal/reference/phase-contract.md +1 -1
  331. package/pack/generated/pi/skills/rite-ship/SKILL.md +2 -2
  332. package/pack/generated/pi/skills/rite-ship/reference/git-ship.md +2 -2
  333. package/pack/generated/pi/skills/rite-spec/SKILL.md +8 -5
  334. package/pack/generated/pi/skills/rite-spec/reference/state-workspace.md +5 -0
  335. package/pack/generated/pi/skills/rite-temper/SKILL.md +3 -3
  336. package/pack/generated/pi/skills/rite-vet/SKILL.md +9 -5
  337. package/pack/generated/pi/skills/rite-vet/reference/review-axes.md +12 -5
  338. package/package.json +1 -1
  339. package/scripts/build-host-artifacts.sh +5 -6
  340. package/scripts/omp-generate.sh +24 -14
@@ -17,11 +17,18 @@ import (
17
17
  )
18
18
 
19
19
  func (r *runner) installClaudeSettingsMarker(state claudeSettingsMerge) error {
20
+ return r.installMarker(hostpack.ClaudeSettingsMerge.MarkerRel, claudeSettingsMarkerText(state))
21
+ }
22
+
23
+ func claudeSettingsMarkerText(state claudeSettingsMerge) string {
20
24
  var b strings.Builder
21
25
  b.WriteString(hostpack.ClaudeSettingsMerge.MarkerText + "\n" + claudeRulesAddedLine)
22
26
  if state.created {
23
27
  b.WriteString("\n" + claudeSettingsCreatedLine)
24
28
  }
29
+ if state.defaultModeOwned {
30
+ b.WriteString("\ndefault-mode=added")
31
+ }
25
32
  rules := make([]string, 0, len(state.added))
26
33
  for rule := range state.added {
27
34
  rules = append(rules, rule)
@@ -30,7 +37,7 @@ func (r *runner) installClaudeSettingsMarker(state claudeSettingsMerge) error {
30
37
  for _, rule := range rules {
31
38
  b.WriteString("\nrule:" + rule)
32
39
  }
33
- return r.installMarker(hostpack.ClaudeSettingsMerge.MarkerRel, b.String())
40
+ return b.String()
34
41
  }
35
42
 
36
43
  const (
@@ -364,16 +371,45 @@ func (r *runner) mergeClaudeSettings(merge hostpack.JSONMerge) error {
364
371
  return fmt.Errorf("encode merged Claude settings: %w", err)
365
372
  }
366
373
  data = append(data, '\n')
374
+ // Publish ownership before grants; keep retired ownership until settings land.
375
+ pending := r.pendingClaudeSettingsOwnership(state)
376
+ if err := r.installClaudeSettingsMarker(pending); err != nil {
377
+ return err
378
+ }
379
+ if r.records[merge.MarkerRel] == "" {
380
+ return fmt.Errorf("cannot merge %s without owning %s; use --force to replace the marker", merge.TargetRel, merge.MarkerRel)
381
+ }
382
+ if err := r.recheckPath(merge.TargetRel); err != nil {
383
+ return err
384
+ }
367
385
  if err := r.writeConfined(dest, data, 0o644); err != nil {
368
386
  return fmt.Errorf("cannot write %s: %w", merge.TargetRel, err)
369
387
  }
370
- return r.installClaudeSettingsMarker(state)
388
+ if pending.defaultModeOwned || len(pending.added) != len(state.added) {
389
+ if err := r.refreshPreflight(merge.MarkerRel); err != nil {
390
+ return err
391
+ }
392
+ return r.installClaudeSettingsMarker(state)
393
+ }
394
+ return nil
371
395
  }
372
396
 
373
397
  // claudeSettingsMerge is what the settings marker records about a merge.
374
398
  type claudeSettingsMerge struct {
375
- created bool
376
- added map[string]bool
399
+ created bool
400
+ added map[string]bool
401
+ defaultModeOwned bool
402
+ }
403
+
404
+ func (r *runner) pendingClaudeSettingsOwnership(next claudeSettingsMerge) claudeSettingsMerge {
405
+ pending := claudeSettingsMerge{
406
+ created: next.created, added: r.managedClaudePermissionRules(),
407
+ defaultModeOwned: r.claudeDefaultModeOwned(),
408
+ }
409
+ for rule := range next.added {
410
+ pending.added[rule] = true
411
+ }
412
+ return pending
377
413
  }
378
414
 
379
415
  // claudeSettingsNext returns the merged .claude/settings.json without
@@ -433,6 +469,12 @@ func stripHooksWith(path string, write func([]byte) error, remove func() error)
433
469
  }
434
470
 
435
471
  func (r *runner) stripClaudeSettings(path string) error {
472
+ return r.stripClaudeSettingsWith(path, func(data []byte) error {
473
+ return r.writeConfined(path, data, 0o644)
474
+ }, func() error { return r.removeConfined(path) })
475
+ }
476
+
477
+ func (r *runner) stripClaudeSettingsWith(path string, write func([]byte) error, remove func() error) error {
436
478
  current, err := readJSON(path)
437
479
  if err != nil {
438
480
  if errors.Is(err, fs.ErrNotExist) {
@@ -442,14 +484,14 @@ func (r *runner) stripClaudeSettings(path string) error {
442
484
  }
443
485
  next := stripDevritesSettings(current, r.claudeDefaultModeOwned(), r.managedClaudePermissionRules())
444
486
  if len(next) == 0 && r.claudeSettingsCreated() {
445
- return r.removeConfined(path)
487
+ return remove()
446
488
  }
447
489
  data, err := json.MarshalIndent(next, "", " ")
448
490
  if err != nil {
449
491
  return fmt.Errorf("encode Claude settings: %w", err)
450
492
  }
451
493
  data = append(data, '\n')
452
- return r.writeConfined(path, data, 0o644)
494
+ return write(data)
453
495
  }
454
496
 
455
497
  func (r *runner) stripMarkerConfined(path, begin, end string) error {
@@ -6,6 +6,7 @@ import (
6
6
  "fmt"
7
7
  "io"
8
8
  "io/fs"
9
+ "maps"
9
10
  "os"
10
11
  "path/filepath"
11
12
  "slices"
@@ -22,6 +23,7 @@ func (r *runner) preflightInstall() error {
22
23
  return fmt.Errorf("preflight install paths: %w", err)
23
24
  }
24
25
  var conflicts []string
26
+ dryTree := map[string][]byte{}
25
27
  for rel := range desired {
26
28
  snapshot, err := r.rememberPath(rel)
27
29
  if err != nil {
@@ -34,7 +36,8 @@ func (r *runner) preflightInstall() error {
34
36
  }
35
37
  }
36
38
  }
37
- for rel, record := range r.prev {
39
+ for _, rel := range slices.Sorted(maps.Keys(r.prev)) {
40
+ record := r.prev[rel]
38
41
  if desired[rel] || hostpack.PreserveOnPrune(rel) {
39
42
  continue
40
43
  }
@@ -58,7 +61,7 @@ func (r *runner) preflightInstall() error {
58
61
  if _, err := r.rememberPath(merge.TargetRel); err != nil {
59
62
  return err
60
63
  }
61
- if err := dryRunStripMarker(map[string][]byte{}, r.target, merge); err != nil {
64
+ if err := r.dryRunStripMarker(dryTree, merge); err != nil {
62
65
  return err
63
66
  }
64
67
  }
@@ -104,19 +107,34 @@ func (r *runner) preflightMerges() error {
104
107
  return fmt.Errorf("merge %s: %w", hostpack.CodexConfigMerge.TargetRel, err)
105
108
  }
106
109
  }
107
- if _, _, err := r.claudeSettingsNext(); err != nil {
110
+ _, settings, err := r.claudeSettingsNext()
111
+ if err != nil {
108
112
  return fmt.Errorf("seed claude settings: %w", err)
109
113
  }
114
+ marker := hostpack.ClaudeSettingsMerge.MarkerRel
115
+ if snapshot := r.preflight[marker]; !snapshot.missing && !r.opts.Force {
116
+ if _, managed := r.prev[marker]; !managed {
117
+ text := claudeSettingsMarkerText(r.pendingClaudeSettingsOwnership(settings)) + "\n"
118
+ sum := sha256.Sum256([]byte(text))
119
+ if snapshot.hash != fmt.Sprintf("sha256:%x", sum[:]) {
120
+ return fmt.Errorf("cannot merge %s without owning %s; use --force to replace the marker", hostpack.ClaudeSettingsMerge.TargetRel, marker)
121
+ }
122
+ }
123
+ }
110
124
  return nil
111
125
  }
112
126
 
113
127
  func (r *runner) preflightUninstall(entries []string) error {
114
128
  var conflicts []string
129
+ dryTree := map[string][]byte{}
115
130
  for _, rel := range entries {
116
131
  if merge, ok := hostpack.ManagedMergeForMarker(rel); ok {
117
132
  if _, err := r.rememberPath(merge.TargetRel); err != nil {
118
133
  return err
119
134
  }
135
+ if err := r.dryRunStripMarker(dryTree, merge); err != nil {
136
+ return err
137
+ }
120
138
  }
121
139
  if !hostpack.ShouldRemoveOnUninstall(rel, entries) {
122
140
  continue
@@ -35,18 +35,12 @@ func (r *runner) uninstall() error {
35
35
  return err
36
36
  }
37
37
 
38
- // Per-path would-be content for a dry run; a nil value means the file
39
- // would be removed.
40
- dryTree := map[string][]byte{}
41
38
  for _, rel := range entries {
42
39
  merge, ok := hostpack.ManagedMergeForMarker(rel)
43
40
  if !ok {
44
41
  continue
45
42
  }
46
43
  if r.opts.DryRun {
47
- if err := dryRunStripMarker(dryTree, r.target, merge); err != nil {
48
- return err
49
- }
50
44
  fmt.Fprintf(r.opts.Stdout, " [merge-remove] %s\n", merge.DryRun)
51
45
  continue
52
46
  }
@@ -142,11 +136,16 @@ func (r *runner) uninstall() error {
142
136
  // dryRunStripMarker runs the real marker strip against an in-memory view of
143
137
  // the target (tree), so a dry run follows the same sequence as the real
144
138
  // uninstall and reports the same errors without writing anything.
145
- func dryRunStripMarker(tree map[string][]byte, target string, merge hostpack.ManagedMerge) error {
146
- if merge.MarkerRel == hostpack.LegacyCodexHooksMerge.MarkerRel || merge.TargetRel == hostpack.ClaudeSettingsMerge.TargetRel {
147
- return nil
139
+ func (r *runner) dryRunStripMarker(tree map[string][]byte, merge hostpack.ManagedMerge) error {
140
+ path := filepath.Join(r.target, filepath.FromSlash(merge.TargetRel))
141
+ write := func(data []byte) error { tree[path] = data; return nil }
142
+ remove := func() error { tree[path] = nil; return nil }
143
+ if merge.MarkerRel == hostpack.LegacyCodexHooksMerge.MarkerRel {
144
+ return stripHooksWith(path, write, remove)
145
+ }
146
+ if merge.TargetRel == hostpack.ClaudeSettingsMerge.TargetRel {
147
+ return r.stripClaudeSettingsWith(path, write, remove)
148
148
  }
149
- path := filepath.Join(target, filepath.FromSlash(merge.TargetRel))
150
149
  data, seen := tree[path]
151
150
  if !seen {
152
151
  var err error
@@ -210,7 +210,12 @@ func collectBundleTargets(slug string, bundles []candidateBundle) ([]*bundleTarg
210
210
  }
211
211
  pins := map[string]string{}
212
212
  for path, hash := range bundle.BaseHashes {
213
- pins[strings.TrimPrefix(filepath.ToSlash(path), prefix)] = strings.ToLower(strings.TrimPrefix(strings.TrimSpace(hash), "sha256:"))
213
+ rel := filepath.ToSlash(filepath.Clean(strings.TrimPrefix(filepath.ToSlash(path), prefix)))
214
+ pin := strings.ToLower(strings.TrimPrefix(strings.TrimSpace(hash), "sha256:"))
215
+ if previous, ok := pins[rel]; ok && previous != pin {
216
+ problems = append(problems, fmt.Sprintf("bundle %d %s: conflicting base hashes for the same file", bi+1, path))
217
+ }
218
+ pins[rel] = pin
214
219
  }
215
220
  for _, file := range bundle.CandidateFiles {
216
221
  rel := strings.TrimPrefix(filepath.ToSlash(file.Path), prefix)
@@ -219,6 +224,7 @@ func collectBundleTargets(slug string, bundles []candidateBundle) ([]*bundleTarg
219
224
  problems = append(problems, where+": path must be a Markdown file inside the "+slug+" workspace")
220
225
  continue
221
226
  }
227
+ rel = filepath.ToSlash(filepath.Clean(rel))
222
228
  op := strings.ToLower(strings.TrimSpace(file.Operation))
223
229
  switch op {
224
230
  case "create", "replace":
@@ -38,6 +38,7 @@ type candidateRow struct {
38
38
  executable bool
39
39
  size int64
40
40
  info os.FileInfo
41
+ fileDigest string
41
42
  }
42
43
 
43
44
  type candidateIdentity struct {
@@ -72,7 +73,7 @@ func loadCandidateIdentity(root, slug string) (candidateIdentity, error) {
72
73
  if err := validateCandidateRows(project, rows); err != nil {
73
74
  return candidateIdentity{}, err
74
75
  }
75
- digest, err := hashCandidate(project, rows)
76
+ digest, err := hashCandidate(project, rows, false)
76
77
  if err != nil {
77
78
  return candidateIdentity{}, err
78
79
  }
@@ -407,15 +408,18 @@ func candidatePathInfo(project, name string) (os.FileInfo, bool, error) {
407
408
  return nil, false, errors.New("path is empty")
408
409
  }
409
410
 
410
- func hashCandidate(project string, rows []candidateRow) (string, error) {
411
+ func hashCandidate(project string, rows []candidateRow, perFile bool) (string, error) {
411
412
  digest := sha256.New()
412
413
  writeCandidateField(digest, candidateDigestDomain)
413
414
  writeCandidateField(digest, candidateDigestVersion)
414
415
  writeCandidateField(digest, fmt.Sprintf("%d", len(rows)))
415
- for _, row := range rows {
416
+ for i, row := range rows {
416
417
  writeCandidateField(digest, row.state)
417
418
  writeCandidateField(digest, row.path)
418
419
  if row.state == "deleted" {
420
+ if err := validateCandidateRow(project, &row); err != nil {
421
+ return "", fmt.Errorf("candidate %q: %w", row.path, err)
422
+ }
419
423
  writeCandidateField(digest, "absent")
420
424
  writeCandidateField(digest, "0")
421
425
  writeCandidateLength(digest, 0)
@@ -428,9 +432,18 @@ func hashCandidate(project string, rows []candidateRow) (string, error) {
428
432
  writeCandidateField(digest, "0")
429
433
  }
430
434
  writeCandidateLength(digest, uint64(row.size)) // #nosec G115 -- validateCandidateRow bounds size to 0..64 MiB
431
- if err := hashCandidateFile(digest, project, row); err != nil {
435
+ var writer io.Writer = digest
436
+ var fileDigest hash.Hash
437
+ if perFile {
438
+ fileDigest = sha256.New()
439
+ writer = io.MultiWriter(digest, fileDigest)
440
+ }
441
+ if err := hashCandidateFile(writer, project, row); err != nil {
432
442
  return "", fmt.Errorf("candidate %q: %w", row.path, err)
433
443
  }
444
+ if perFile {
445
+ rows[i].fileDigest = hex.EncodeToString(fileDigest.Sum(nil))
446
+ }
434
447
  }
435
448
  return hex.EncodeToString(digest.Sum(nil)), nil
436
449
  }
@@ -446,7 +459,7 @@ func writeCandidateLength(writer hash.Hash, size uint64) {
446
459
  _, _ = writer.Write(encoded[:])
447
460
  }
448
461
 
449
- func hashCandidateFile(digest hash.Hash, project string, row candidateRow) error {
462
+ func hashCandidateFile(digest io.Writer, project string, row candidateRow) error {
450
463
  if _, exists, err := candidatePathInfo(project, row.path); err != nil || !exists {
451
464
  if err != nil {
452
465
  return err
@@ -464,7 +477,7 @@ func hashCandidateFile(digest hash.Hash, project string, row candidateRow) error
464
477
  if err != nil {
465
478
  return err
466
479
  }
467
- if !opened.Mode().IsRegular() || opened.Size() != row.size || (opened.Mode().Perm()&0o111 != 0) != row.executable || !os.SameFile(row.info, opened) {
480
+ if !opened.Mode().IsRegular() || opened.Size() != row.size || (opened.Mode().Perm()&0o111 != 0) != row.executable || !os.SameFile(row.info, opened) || !row.info.ModTime().Equal(opened.ModTime()) {
468
481
  return errors.New("file changed size or type before reading")
469
482
  }
470
483
  written, err := io.CopyN(digest, file, row.size)
@@ -483,7 +496,7 @@ func hashCandidateFile(digest hash.Hash, project string, row candidateRow) error
483
496
  if err != nil {
484
497
  return err
485
498
  }
486
- if !exists || !after.Mode().IsRegular() || after.Size() != row.size || (after.Mode().Perm()&0o111 != 0) != row.executable || !os.SameFile(opened, after) || !os.SameFile(opened, current) {
499
+ if !exists || !after.Mode().IsRegular() || after.Size() != row.size || current.Size() != row.size || (after.Mode().Perm()&0o111 != 0) != row.executable || (current.Mode().Perm()&0o111 != 0) != row.executable || !os.SameFile(opened, after) || !os.SameFile(opened, current) || !opened.ModTime().Equal(after.ModTime()) || !opened.ModTime().Equal(current.ModTime()) {
487
500
  return errors.New("file changed size or type while reading")
488
501
  }
489
502
  return nil
@@ -1,9 +1,7 @@
1
1
  package lib
2
2
 
3
3
  import (
4
- "errors"
5
4
  "fmt"
6
- "os"
7
5
  "path/filepath"
8
6
  "regexp"
9
7
  "sort"
@@ -118,7 +116,7 @@ func omissionError(omitted []string) error {
118
116
  len(omitted), strings.Join(shown, ", "), more)
119
117
  }
120
118
 
121
- var diffPathRE = regexp.MustCompile(`[A-Za-z0-9_./@+-]+\.(?:diff|patch)\b`)
119
+ var diffPathRE = regexp.MustCompile(`(?:[A-Za-z]:/)?[A-Za-z0-9_./@+-]+\.(?:diff|patch)\b`)
122
120
 
123
121
  // lintReviewCandidate gates a Review or Seal reviewer wave: the candidate
124
122
  // manifest must cover every path the feature's checkpoints changed, and the
@@ -128,20 +126,5 @@ func lintReviewCandidate(root, slug, packet string) error {
128
126
  if err := CheckCandidateCoverage(root, slug); err != nil {
129
127
  return err
130
128
  }
131
- raw, err := readBoundedRegularFile(packet, maxContractBytes)
132
- if err != nil {
133
- return fmt.Errorf("reviewer packet %s: %w", packet, err)
134
- }
135
- for _, name := range diffPathRE.FindAllString(string(raw), -1) {
136
- for _, dir := range []string{projectDir(root), featureDir(root, slug), ""} {
137
- candidate := name
138
- if dir != "" {
139
- candidate = filepath.Join(dir, filepath.FromSlash(name))
140
- }
141
- if info, err := os.Stat(candidate); err == nil && info.Mode().IsRegular() && info.Size() > 0 {
142
- return nil
143
- }
144
- }
145
- }
146
- return errors.New("a Review or Seal reviewer packet must name the frozen candidate diff (an existing non-empty .diff or .patch file); reviewers cannot judge a digest alone")
129
+ return lintPacketDiffs(root, slug, packet, true)
147
130
  }
@@ -10,6 +10,7 @@ import (
10
10
  "runtime"
11
11
 
12
12
  "github.com/devrites/devrites/internal/devritespaths"
13
+ "github.com/devrites/devrites/internal/parallel"
13
14
  "github.com/devrites/devrites/internal/state"
14
15
  )
15
16
 
@@ -62,11 +63,6 @@ func CloseOut(root string, args []string, stdout, stderr io.Writer) int {
62
63
  archDisplay := filepath.ToSlash(filepath.Join("archive", slug))
63
64
  active := filepath.Join(dv, "ACTIVE")
64
65
 
65
- activeSlug, err := devritespaths.ActiveSlug(dv)
66
- if err != nil {
67
- fmt.Fprintf(stderr, "state close: invalid ACTIVE cursor: %v\n", err)
68
- return 1
69
- }
70
66
  // A broken symlink at the destination still counts as "already there", so use
71
67
  // Lstat rather than Stat to avoid silently overwriting it.
72
68
  if _, err := os.Lstat(arch); err == nil {
@@ -83,11 +79,22 @@ func CloseOut(root string, args []string, stdout, stderr io.Writer) int {
83
79
  // re-create work/<slug>. Windows cannot rename a directory that holds an
84
80
  // open file, so there the lock is released just before the rename.
85
81
  code := 1
86
- if err := state.WithFeatureLockRelease(dv, slug, func(release func()) error {
87
- code = closeOutLocked(release, work, arch, archiveDir, archDisplay, active, activeSlug, slug, stdout, stderr)
88
- return nil
82
+ if err := parallel.WithControlLock(dv, func() error {
83
+ leased, err := parallel.WorkspaceLeased(dv, slug)
84
+ if err != nil {
85
+ return err
86
+ }
87
+ if leased {
88
+ fmt.Fprintf(stderr, "state close: BLOCKED: %s is named by a retained parallel lease; finish and clean up the controller lease before archiving\n", slug)
89
+ code = 3
90
+ return nil
91
+ }
92
+ return state.WithFeatureLockRelease(dv, slug, func(release func()) error {
93
+ code = closeOutLocked(release, work, arch, archiveDir, archDisplay, active, slug, stdout, stderr)
94
+ return nil
95
+ })
89
96
  }); err != nil {
90
- fmt.Fprintf(stderr, "state close: cannot lock feature %s: %v\n", slug, err)
97
+ fmt.Fprintf(stderr, "state close: %v\n", err)
91
98
  return 1
92
99
  }
93
100
  return code
@@ -96,7 +103,12 @@ func CloseOut(root string, args []string, stdout, stderr io.Writer) int {
96
103
  // closeOutLocked performs the rename and the conditional ACTIVE clear. It runs
97
104
  // under the feature lock held by CloseOut and reports an exit code rather than
98
105
  // an error, preserving CloseOut's documented exit codes.
99
- func closeOutLocked(release func(), work, arch, archiveDir, archDisplay, active, activeSlug, slug string, stdout, stderr io.Writer) int {
106
+ func closeOutLocked(release func(), work, arch, archiveDir, archDisplay, active, slug string, stdout, stderr io.Writer) int {
107
+ activeSlug, err := devritespaths.PhysicalActiveSlug(filepath.Dir(active))
108
+ if err != nil {
109
+ fmt.Fprintf(stderr, "state close: invalid ACTIVE cursor: %v\n", err)
110
+ return 1
111
+ }
100
112
  // Fail closed: if the move does not happen, leave the cursor alone and do not
101
113
  // claim success: a lost audit trail must never look like a clean close-out.
102
114
  if err := os.MkdirAll(archiveDir, 0o755); err != nil {
@@ -4,6 +4,7 @@ import (
4
4
  "bytes"
5
5
  "crypto/sha256"
6
6
  "encoding/json"
7
+ "errors"
7
8
  "fmt"
8
9
  "io"
9
10
  "os"
@@ -80,7 +81,7 @@ func RunContext(root string, args []string, stdout, stderr io.Writer) int {
80
81
  }
81
82
  // A Build reviewer judges one slice; an unscoped bundle inlines whole
82
83
  // plan/tasks/architecture into every dispatch.
83
- if opts.phase == "build" && opts.role != "" && opts.role != "slice-wright" && opts.slice == "" {
84
+ if opts.phase == "build" && opts.role != "" && roleBase(opts.role) != "slice-wright" && opts.slice == "" {
84
85
  fmt.Fprintf(stderr, "context: BLOCKED: unscoped build reviewer bundle: --role %s in phase build needs --slice <SLICE-ID>\n", opts.role)
85
86
  return 3
86
87
  }
@@ -93,12 +94,18 @@ func RunContext(root string, args []string, stdout, stderr io.Writer) int {
93
94
  return 2
94
95
  }
95
96
  }
97
+ sources := contextSources{dir: featureDir}
96
98
  // One active slice per bundle, except a parallel batch: the root has to
97
99
  // see the other slices to start them. Whole tasks/plan/decisions ride
98
100
  // that batch's turns; serial runs stay scoped.
99
101
  autoScope := false
100
102
  if featureDir != "" && opts.role == "" && opts.slice == "" && !opts.whole && (opts.phase == "build" || opts.phase == "autocomplete") && !parallelPlanVisible(root, opts.slug) {
101
- if id := activeSliceScope(featureDir); id != "" {
103
+ id, err := activeSliceScope(&sources)
104
+ if err != nil {
105
+ fmt.Fprintf(stderr, "context: %v\n", err)
106
+ return 2
107
+ }
108
+ if id != "" {
102
109
  opts.slices, opts.slice, autoScope = []string{id}, id, true
103
110
  }
104
111
  }
@@ -110,10 +117,21 @@ func RunContext(root string, args []string, stdout, stderr io.Writer) int {
110
117
  var inventory []byte
111
118
  recheckPath := opts.recheck
112
119
  if recheckPath != "" {
113
- if !filepath.IsAbs(recheckPath) {
120
+ if filepath.IsAbs(recheckPath) {
121
+ absoluteFeature, absErr := filepath.Abs(featureDir)
122
+ if absErr != nil {
123
+ fmt.Fprintf(stderr, "context: --recheck: %v\n", absErr)
124
+ return 2
125
+ }
126
+ if rel, relErr := filepath.Rel(absoluteFeature, recheckPath); relErr == nil && filepath.IsLocal(rel) {
127
+ inventory, err = sources.read(rel)
128
+ } else {
129
+ inventory, err = readBoundedRegularFile(recheckPath, workspaceArtifactLimit)
130
+ }
131
+ } else {
132
+ inventory, err = sources.read(recheckPath)
114
133
  recheckPath = filepath.Join(featureDir, filepath.FromSlash(recheckPath))
115
134
  }
116
- inventory, err = os.ReadFile(recheckPath) // #nosec G304 -- the root names its own recheck inventory file
117
135
  if err == nil && len(bytes.TrimSpace(inventory)) == 0 {
118
136
  err = fmt.Errorf("%s is empty", opts.recheck)
119
137
  }
@@ -141,7 +159,7 @@ func RunContext(root string, args []string, stdout, stderr io.Writer) int {
141
159
  var sliceBlock string
142
160
  var sliceIDs []citedID
143
161
  if opts.slice != "" {
144
- raw, err := os.ReadFile(filepath.Join(featureDir, "tasks.md")) // #nosec G304 -- featureDir is the validated feature workspace
162
+ raw, err := sources.read("tasks.md")
145
163
  if err != nil {
146
164
  fmt.Fprintf(stderr, "context: %v\n", err)
147
165
  return 2
@@ -238,12 +256,17 @@ func RunContext(root string, args []string, stdout, stderr io.Writer) int {
238
256
  }
239
257
  // The open findings lead a recheck bundle, before any reference text.
240
258
  if inventory != nil {
241
- writeContextEntry(&bundle, recheckPath, "recheck inventory: "+opts.recheck, seen, false)
259
+ writeContextData(&bundle, recheckPath, "recheck inventory: "+opts.recheck, inventory, nil, seen, false)
242
260
  loaded++
243
261
  }
244
262
  var rootOnly []string
245
263
  citedSections, packByReference := 0, 0
246
- keepModel := opts.role == "" || keepModelSurface(opts.role, gatherTriggerFacts(root, featureDir, opts.role))
264
+ facts, err := gatherTriggerFacts(root, opts.role, &sources)
265
+ if err != nil {
266
+ fmt.Fprintf(stderr, "context: %v\n", err)
267
+ return 2
268
+ }
269
+ keepModel := opts.role == "" || keepModelSurface(opts.role, facts)
247
270
  for _, rel := range files {
248
271
  abs := filepath.Join(skillsRoot, filepath.FromSlash(rel))
249
272
  if seen[abs] {
@@ -297,16 +320,21 @@ func RunContext(root string, args []string, stdout, stderr io.Writer) int {
297
320
  }
298
321
  for _, rel := range workspaceList {
299
322
  abs := filepath.Join(featureDir, filepath.FromSlash(rel))
323
+ data, err := sources.read(rel)
324
+ if err != nil && !errors.Is(err, os.ErrNotExist) {
325
+ fmt.Fprintf(stderr, "context: %v\n", err)
326
+ return 2
327
+ }
300
328
  // Role bundles without --slice (full Vet, Review, Seal) read
301
329
  // everything; root and --slice bundles take over-budget
302
330
  // artifacts by anchor.
303
331
  if opts.slice == "" && opts.role != "" {
304
- if writeContextEntry(&bundle, abs, "workspace/"+rel, seen, false) {
332
+ if writeContextData(&bundle, abs, "workspace/"+rel, data, err, seen, false) {
305
333
  loaded++
306
334
  }
307
335
  continue
308
336
  }
309
- ok, byRef := writeSliceEntry(&bundle, filepath.Dir(root), abs, "workspace/"+rel, opts.slice, sliceBlock, sliceIDs, seen)
337
+ ok, byRef := writeSliceEntry(&bundle, filepath.Dir(root), abs, "workspace/"+rel, data, err, opts.slice, sliceBlock, sliceIDs, seen)
310
338
  if ok {
311
339
  loaded++
312
340
  }
@@ -322,34 +350,11 @@ func RunContext(root string, args []string, stdout, stderr io.Writer) int {
322
350
  withTOC := insertSectionTOC(bundle.String())
323
351
  bundle.Reset()
324
352
  bundle.WriteString(withTOC)
325
- out := opts.out
326
- if out == "" {
327
- out = filepath.Join(featureDir, "ctx", bundleName(opts))
328
- } else if !filepath.IsAbs(out) && featureDir != "" {
329
- out = filepath.Join(featureDir, filepath.FromSlash(out))
330
- }
331
- if err := os.MkdirAll(filepath.Dir(out), 0o755); err != nil {
353
+ out, unchanged, err := publishContext(featureDir, opts, []byte(bundle.String()))
354
+ if err != nil {
332
355
  fmt.Fprintf(stderr, "context: %v\n", err)
333
356
  return 2
334
357
  }
335
- digestPath := out + ".sha256"
336
- digest := fmt.Sprintf("%x", sha256.Sum256([]byte(bundle.String())))
337
- unchanged := false
338
- if prev, err := os.ReadFile(digestPath); err == nil && strings.TrimSpace(string(prev)) == digest { // #nosec G304 -- digestPath is generated under the feature workspace
339
- if _, err := os.Stat(out); err == nil {
340
- unchanged = true
341
- }
342
- }
343
- if !unchanged {
344
- if err := os.WriteFile(out, []byte(bundle.String()), 0o600); err != nil {
345
- fmt.Fprintf(stderr, "context: %v\n", err)
346
- return 2
347
- }
348
- if err := os.WriteFile(digestPath, []byte(digest+"\n"), 0o600); err != nil {
349
- fmt.Fprintf(stderr, "context: %v\n", err)
350
- return 2
351
- }
352
- }
353
358
  if opts.slug != "" {
354
359
  phaseLabel := opts.phase
355
360
  if phaseLabel == "" {
@@ -396,7 +401,7 @@ func RunContext(root string, args []string, stdout, stderr io.Writer) int {
396
401
  fmt.Fprintf(stdout, "triggers: auto=[%s] (fired by --role dispatch)\n",
397
402
  strings.Join(autoTriggers, ","))
398
403
  }
399
- if suggested := suggestTriggers(manifest, root, featureDir, opts); len(suggested) > 0 {
404
+ if suggested := suggestTriggers(manifest, opts, facts); len(suggested) > 0 {
400
405
  fmt.Fprintf(stdout, "triggers: suggested=[%s] (advisory — confirm each before use)\n",
401
406
  strings.Join(suggested, ","))
402
407
  // The advisory list is only actionable if the agent is told how to
@@ -455,7 +460,7 @@ func workspaceReadSet(manifest loadsManifest, role string) ([]string, bool) {
455
460
  if role == "" {
456
461
  return manifest.Workspace, false
457
462
  }
458
- if list, ok := manifest.WorkspaceByRole[role]; ok {
463
+ if list, ok := manifest.WorkspaceByRole[roleBase(role)]; ok {
459
464
  return list, true
460
465
  }
461
466
  return manifest.Workspace, false
@@ -558,11 +563,10 @@ const sliceInlineMaxBytes = 16 << 10
558
563
  // other artifact but spec.md over sliceInlineMaxBytes as a by-reference index;
559
564
  // any canonical artifact over its line/byte budget as a by-reference index;
560
565
  // everything else whole.
561
- func writeSliceEntry(bundle *strings.Builder, project, abs, label, slice, block string, ids []citedID, seen map[string]bool) (loaded, byRef bool) {
566
+ func writeSliceEntry(bundle *strings.Builder, project, abs, label string, data []byte, err error, slice, block string, ids []citedID, seen map[string]bool) (loaded, byRef bool) {
562
567
  if seen[abs] {
563
568
  return false, false
564
569
  }
565
- data, err := os.ReadFile(abs) // #nosec G304 -- abs is resolved from the validated feature workspace
566
570
  name := filepath.Base(abs)
567
571
  if err == nil && name == "tasks.md" && slice != "" {
568
572
  seen[abs] = true
@@ -570,7 +574,7 @@ func writeSliceEntry(bundle *strings.Builder, project, abs, label, slice, block
570
574
  } else if err == nil && slice != "" && name != "spec.md" && len(data) > sliceInlineMaxBytes {
571
575
  seen[abs] = true
572
576
  } else if status, ok := state.ArtifactBudget(name, data); err != nil || !ok || !status.Over {
573
- return writeContextEntry(bundle, abs, label, seen, false), false
577
+ return writeContextData(bundle, abs, label, data, err, seen, false), false
574
578
  } else {
575
579
  seen[abs] = true
576
580
  }
@@ -726,12 +730,19 @@ func clipRunes(s string, n int) string {
726
730
  }
727
731
 
728
732
  func writeContextEntry(bundle *strings.Builder, abs, label string, seen map[string]bool, expand bool) bool {
733
+ if seen[abs] {
734
+ return false
735
+ }
736
+ data, err := os.ReadFile(abs) // #nosec G304 -- abs is resolved from the validated context source set
737
+ return writeContextData(bundle, abs, label, data, err, seen, expand)
738
+ }
739
+
740
+ func writeContextData(bundle *strings.Builder, abs, label string, data []byte, err error, seen map[string]bool, expand bool) bool {
729
741
  if seen[abs] {
730
742
  return false
731
743
  }
732
744
  seen[abs] = true
733
745
  fmt.Fprintf(bundle, "===== %s =====\n", label)
734
- data, err := os.ReadFile(abs) // #nosec G304 -- abs is resolved from the validated context source set
735
746
  if err != nil {
736
747
  fmt.Fprintf(bundle, "MISSING: %s\n\n", label)
737
748
  return false
@@ -915,6 +926,7 @@ func parseLoadsManifest(text string) (loadsManifest, bool, error) {
915
926
  }
916
927
 
917
928
  func roleAgentPath(manifest loadsManifest, role string) string {
929
+ role = roleBase(role)
918
930
  if manifest.Agents != nil {
919
931
  if rel, ok := manifest.Agents[role]; ok {
920
932
  return rel
@@ -925,20 +937,28 @@ func roleAgentPath(manifest loadsManifest, role string) string {
925
937
 
926
938
  // activeSliceScope is state.md's active_slice when it names a slice block in
927
939
  // tasks.md, else "" (unset, "none", or stale cursors keep the whole bundle).
928
- func activeSliceScope(featureDir string) string {
929
- lines := strings.Split(string(readWorkspaceFile(featureDir, "state.md")), "\n")
940
+ func activeSliceScope(sources *contextSources) (string, error) {
941
+ raw, err := sources.read("state.md")
942
+ if err != nil && !errors.Is(err, os.ErrNotExist) {
943
+ return "", err
944
+ }
945
+ lines := strings.Split(string(raw), "\n")
930
946
  // active_slice outlives Build; after it the root's autocomplete bundle stays whole.
931
947
  if phase, _ := state.CursorField(lines, state.CursorPhase); strings.TrimSpace(phase) != "build" {
932
- return ""
948
+ return "", nil
933
949
  }
934
950
  id, _ := state.CursorField(lines, state.CursorActiveSlice)
935
951
  if id = strings.TrimSpace(id); !state.ValidSliceID(id) {
936
- return ""
952
+ return "", nil
953
+ }
954
+ tasks, err := sources.read("tasks.md")
955
+ if err != nil && !errors.Is(err, os.ErrNotExist) {
956
+ return "", err
937
957
  }
938
- if _, ok := state.ExtractTaskSlice(readWorkspaceFile(featureDir, "tasks.md"), id); !ok {
939
- return ""
958
+ if _, ok := state.ExtractTaskSlice(tasks, id); !ok {
959
+ return "", nil
940
960
  }
941
- return id
961
+ return id, nil
942
962
  }
943
963
 
944
964
  func bundleName(opts contextOpts) string {