devrites 7.2.8 → 7.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (332) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +1 -1
  3. package/docs/candidate-integrity.md +10 -0
  4. package/docs/cli.md +7 -1
  5. package/docs/command-map.md +5 -2
  6. package/docs/engine/commands.md +34 -4
  7. package/docs/orchestration.md +26 -13
  8. package/docs/skills.md +5 -0
  9. package/engine/internal/acceptance/cli.go +27 -26
  10. package/engine/internal/acceptance/preconditions.go +1 -1
  11. package/engine/internal/acceptance/run.go +42 -16
  12. package/engine/internal/acceptance/transcript.go +236 -45
  13. package/engine/internal/devritespaths/paths.go +16 -3
  14. package/engine/internal/gate/gate.go +6 -0
  15. package/engine/internal/gate/readiness_binding.go +4 -3
  16. package/engine/internal/lib/candidate.go +20 -7
  17. package/engine/internal/lib/candidate_coverage.go +2 -19
  18. package/engine/internal/lib/closeout.go +16 -4
  19. package/engine/internal/lib/context.go +64 -45
  20. package/engine/internal/lib/context_io.go +171 -0
  21. package/engine/internal/lib/detect.go +8 -8
  22. package/engine/internal/lib/dispatch.go +2 -0
  23. package/engine/internal/lib/packet.go +37 -7
  24. package/engine/internal/lib/packet_freeze.go +60 -12
  25. package/engine/internal/lib/packet_integrity.go +179 -0
  26. package/engine/internal/lib/perturb.go +3 -3
  27. package/engine/internal/lib/slicecheck.go +7 -1
  28. package/engine/internal/lib/taskgraph.go +20 -36
  29. package/engine/internal/lib/triggers.go +28 -29
  30. package/engine/internal/lib/workspace_pool.go +330 -0
  31. package/engine/internal/parallel/cli.go +190 -49
  32. package/engine/internal/parallel/git.go +47 -14
  33. package/engine/internal/parallel/landing.go +232 -0
  34. package/engine/internal/parallel/lease.go +97 -18
  35. package/engine/internal/parallel/ops.go +312 -155
  36. package/engine/internal/parallel/pathdisjoint.go +48 -4
  37. package/engine/internal/parallel/prune.go +133 -0
  38. package/engine/internal/parallel/repository.go +239 -0
  39. package/engine/internal/parallel/select.go +64 -42
  40. package/engine/internal/state/observation.go +49 -2
  41. package/engine/main.go +2 -1
  42. package/pack/.claude/agents/devrites-proof-runner.md +16 -9
  43. package/pack/.claude/skills/devrites-lib/SKILL.md +2 -0
  44. package/pack/.claude/skills/devrites-lib/reference/candidate-integrity.md +7 -3
  45. package/pack/.claude/skills/devrites-lib/reference/parallel-dispatch.md +3 -0
  46. package/pack/.claude/skills/devrites-lib/reference/parallel-workspaces.md +236 -0
  47. package/pack/.claude/skills/devrites-lib/reference/standards/afk-hitl.md +9 -12
  48. package/pack/.claude/skills/devrites-lib/reference/standards/agents.md +1 -1
  49. package/pack/.claude/skills/devrites-lib/reference/standards/context-hygiene.md +1 -1
  50. package/pack/.claude/skills/devrites-lib/reference/standards/gates.md +18 -9
  51. package/pack/.claude/skills/devrites-lib/reference/workspace-artifact-schema.md +10 -0
  52. package/pack/.claude/skills/rite-autocomplete/SKILL.md +12 -10
  53. package/pack/.claude/skills/rite-autocomplete/reference/lookahead.md +4 -4
  54. package/pack/.claude/skills/rite-autocomplete/reference/loop.md +20 -9
  55. package/pack/.claude/skills/rite-build/SKILL.md +13 -10
  56. package/pack/.claude/skills/rite-build/reference/afk-discipline.md +1 -1
  57. package/pack/.claude/skills/rite-build/reference/checkpoint.md +1 -1
  58. package/pack/.claude/skills/rite-build/reference/parallel-batch.md +57 -36
  59. package/pack/.claude/skills/rite-build/reference/phase-contract.md +8 -5
  60. package/pack/.claude/skills/rite-build/reference/wright-dispatch.md +7 -7
  61. package/pack/.claude/skills/rite-clarify/SKILL.md +2 -2
  62. package/pack/.claude/skills/rite-converge/SKILL.md +4 -4
  63. package/pack/.claude/skills/rite-define/SKILL.md +7 -3
  64. package/pack/.claude/skills/rite-define/reference/plan-template.md +4 -2
  65. package/pack/.claude/skills/rite-plan/SKILL.md +11 -8
  66. package/pack/.claude/skills/rite-plan/reference/dependency-graph.md +9 -2
  67. package/pack/.claude/skills/rite-plan/reference/slicing.md +2 -0
  68. package/pack/.claude/skills/rite-polish/SKILL.md +3 -3
  69. package/pack/.claude/skills/rite-prove/SKILL.md +24 -12
  70. package/pack/.claude/skills/rite-resolve/SKILL.md +3 -3
  71. package/pack/.claude/skills/rite-resolve/reference/answer-protocol.md +2 -0
  72. package/pack/.claude/skills/rite-review/SKILL.md +11 -3
  73. package/pack/.claude/skills/rite-review/reference/feature-scoped-review.md +2 -2
  74. package/pack/.claude/skills/rite-seal/SKILL.md +1 -1
  75. package/pack/.claude/skills/rite-seal/reference/phase-contract.md +1 -1
  76. package/pack/.claude/skills/rite-ship/SKILL.md +2 -2
  77. package/pack/.claude/skills/rite-ship/reference/git-ship.md +2 -2
  78. package/pack/.claude/skills/rite-spec/SKILL.md +8 -5
  79. package/pack/.claude/skills/rite-spec/reference/state-workspace.md +5 -0
  80. package/pack/.claude/skills/rite-temper/SKILL.md +3 -3
  81. package/pack/.claude/skills/rite-vet/SKILL.md +19 -6
  82. package/pack/.claude/skills/rite-vet/reference/review-axes.md +12 -5
  83. package/pack/generated/claude/agents/devrites-proof-runner.md +16 -9
  84. package/pack/generated/claude/skills/devrites-lib/SKILL.md +2 -0
  85. package/pack/generated/claude/skills/devrites-lib/reference/candidate-integrity.md +7 -3
  86. package/pack/generated/claude/skills/devrites-lib/reference/parallel-dispatch.md +3 -0
  87. package/pack/generated/claude/skills/devrites-lib/reference/parallel-workspaces.md +236 -0
  88. package/pack/generated/claude/skills/devrites-lib/reference/standards/afk-hitl.md +9 -12
  89. package/pack/generated/claude/skills/devrites-lib/reference/standards/agents.md +1 -1
  90. package/pack/generated/claude/skills/devrites-lib/reference/standards/context-hygiene.md +1 -1
  91. package/pack/generated/claude/skills/devrites-lib/reference/standards/gates.md +18 -9
  92. package/pack/generated/claude/skills/devrites-lib/reference/workspace-artifact-schema.md +10 -0
  93. package/pack/generated/claude/skills/rite-autocomplete/SKILL.md +12 -10
  94. package/pack/generated/claude/skills/rite-autocomplete/reference/lookahead.md +4 -4
  95. package/pack/generated/claude/skills/rite-autocomplete/reference/loop.md +20 -9
  96. package/pack/generated/claude/skills/rite-build/SKILL.md +13 -10
  97. package/pack/generated/claude/skills/rite-build/reference/afk-discipline.md +1 -1
  98. package/pack/generated/claude/skills/rite-build/reference/checkpoint.md +1 -1
  99. package/pack/generated/claude/skills/rite-build/reference/parallel-batch.md +57 -36
  100. package/pack/generated/claude/skills/rite-build/reference/phase-contract.md +8 -5
  101. package/pack/generated/claude/skills/rite-build/reference/wright-dispatch.md +7 -7
  102. package/pack/generated/claude/skills/rite-clarify/SKILL.md +2 -2
  103. package/pack/generated/claude/skills/rite-converge/SKILL.md +4 -4
  104. package/pack/generated/claude/skills/rite-define/SKILL.md +7 -3
  105. package/pack/generated/claude/skills/rite-define/reference/plan-template.md +4 -2
  106. package/pack/generated/claude/skills/rite-plan/SKILL.md +11 -8
  107. package/pack/generated/claude/skills/rite-plan/reference/dependency-graph.md +9 -2
  108. package/pack/generated/claude/skills/rite-plan/reference/slicing.md +2 -0
  109. package/pack/generated/claude/skills/rite-polish/SKILL.md +3 -3
  110. package/pack/generated/claude/skills/rite-prove/SKILL.md +24 -12
  111. package/pack/generated/claude/skills/rite-resolve/SKILL.md +3 -3
  112. package/pack/generated/claude/skills/rite-resolve/reference/answer-protocol.md +2 -0
  113. package/pack/generated/claude/skills/rite-review/SKILL.md +11 -3
  114. package/pack/generated/claude/skills/rite-review/reference/feature-scoped-review.md +2 -2
  115. package/pack/generated/claude/skills/rite-seal/SKILL.md +1 -1
  116. package/pack/generated/claude/skills/rite-seal/reference/phase-contract.md +1 -1
  117. package/pack/generated/claude/skills/rite-ship/SKILL.md +2 -2
  118. package/pack/generated/claude/skills/rite-ship/reference/git-ship.md +2 -2
  119. package/pack/generated/claude/skills/rite-spec/SKILL.md +8 -5
  120. package/pack/generated/claude/skills/rite-spec/reference/state-workspace.md +5 -0
  121. package/pack/generated/claude/skills/rite-temper/SKILL.md +3 -3
  122. package/pack/generated/claude/skills/rite-vet/SKILL.md +19 -6
  123. package/pack/generated/claude/skills/rite-vet/reference/review-axes.md +12 -5
  124. package/pack/generated/codex/agents/devrites-proof-runner.toml +16 -9
  125. package/pack/generated/codex/skills/devrites-lib/SKILL.md +2 -0
  126. package/pack/generated/codex/skills/devrites-lib/reference/candidate-integrity.md +7 -3
  127. package/pack/generated/codex/skills/devrites-lib/reference/parallel-dispatch.md +3 -0
  128. package/pack/generated/codex/skills/devrites-lib/reference/parallel-workspaces.md +236 -0
  129. package/pack/generated/codex/skills/devrites-lib/reference/standards/afk-hitl.md +9 -12
  130. package/pack/generated/codex/skills/devrites-lib/reference/standards/agents.md +1 -1
  131. package/pack/generated/codex/skills/devrites-lib/reference/standards/context-hygiene.md +1 -1
  132. package/pack/generated/codex/skills/devrites-lib/reference/standards/gates.md +18 -9
  133. package/pack/generated/codex/skills/devrites-lib/reference/workspace-artifact-schema.md +10 -0
  134. package/pack/generated/codex/skills/rite-autocomplete/SKILL.md +12 -10
  135. package/pack/generated/codex/skills/rite-autocomplete/reference/lookahead.md +4 -4
  136. package/pack/generated/codex/skills/rite-autocomplete/reference/loop.md +20 -9
  137. package/pack/generated/codex/skills/rite-build/SKILL.md +13 -10
  138. package/pack/generated/codex/skills/rite-build/reference/afk-discipline.md +1 -1
  139. package/pack/generated/codex/skills/rite-build/reference/checkpoint.md +1 -1
  140. package/pack/generated/codex/skills/rite-build/reference/parallel-batch.md +57 -36
  141. package/pack/generated/codex/skills/rite-build/reference/phase-contract.md +8 -5
  142. package/pack/generated/codex/skills/rite-build/reference/wright-dispatch.md +7 -7
  143. package/pack/generated/codex/skills/rite-clarify/SKILL.md +2 -2
  144. package/pack/generated/codex/skills/rite-converge/SKILL.md +4 -4
  145. package/pack/generated/codex/skills/rite-define/SKILL.md +7 -3
  146. package/pack/generated/codex/skills/rite-define/reference/plan-template.md +4 -2
  147. package/pack/generated/codex/skills/rite-plan/SKILL.md +11 -8
  148. package/pack/generated/codex/skills/rite-plan/reference/dependency-graph.md +9 -2
  149. package/pack/generated/codex/skills/rite-plan/reference/slicing.md +2 -0
  150. package/pack/generated/codex/skills/rite-polish/SKILL.md +3 -3
  151. package/pack/generated/codex/skills/rite-prove/SKILL.md +24 -12
  152. package/pack/generated/codex/skills/rite-resolve/SKILL.md +3 -3
  153. package/pack/generated/codex/skills/rite-resolve/reference/answer-protocol.md +2 -0
  154. package/pack/generated/codex/skills/rite-review/SKILL.md +11 -3
  155. package/pack/generated/codex/skills/rite-review/reference/feature-scoped-review.md +2 -2
  156. package/pack/generated/codex/skills/rite-seal/SKILL.md +1 -1
  157. package/pack/generated/codex/skills/rite-seal/reference/phase-contract.md +1 -1
  158. package/pack/generated/codex/skills/rite-ship/SKILL.md +2 -2
  159. package/pack/generated/codex/skills/rite-ship/reference/git-ship.md +2 -2
  160. package/pack/generated/codex/skills/rite-spec/SKILL.md +8 -5
  161. package/pack/generated/codex/skills/rite-spec/reference/state-workspace.md +5 -0
  162. package/pack/generated/codex/skills/rite-temper/SKILL.md +3 -3
  163. package/pack/generated/codex/skills/rite-vet/SKILL.md +19 -6
  164. package/pack/generated/codex/skills/rite-vet/reference/review-axes.md +12 -5
  165. package/pack/generated/devin/agents/devrites-proof-runner.md +16 -9
  166. package/pack/generated/devin/skills/devrites-lib/SKILL.md +2 -0
  167. package/pack/generated/devin/skills/devrites-lib/reference/candidate-integrity.md +7 -3
  168. package/pack/generated/devin/skills/devrites-lib/reference/parallel-dispatch.md +3 -0
  169. package/pack/generated/devin/skills/devrites-lib/reference/parallel-workspaces.md +236 -0
  170. package/pack/generated/devin/skills/devrites-lib/reference/standards/afk-hitl.md +9 -12
  171. package/pack/generated/devin/skills/devrites-lib/reference/standards/agents.md +1 -1
  172. package/pack/generated/devin/skills/devrites-lib/reference/standards/context-hygiene.md +1 -1
  173. package/pack/generated/devin/skills/devrites-lib/reference/standards/gates.md +18 -9
  174. package/pack/generated/devin/skills/devrites-lib/reference/workspace-artifact-schema.md +10 -0
  175. package/pack/generated/devin/skills/rite-autocomplete/SKILL.md +12 -10
  176. package/pack/generated/devin/skills/rite-autocomplete/reference/lookahead.md +4 -4
  177. package/pack/generated/devin/skills/rite-autocomplete/reference/loop.md +20 -9
  178. package/pack/generated/devin/skills/rite-build/SKILL.md +13 -10
  179. package/pack/generated/devin/skills/rite-build/reference/afk-discipline.md +1 -1
  180. package/pack/generated/devin/skills/rite-build/reference/checkpoint.md +1 -1
  181. package/pack/generated/devin/skills/rite-build/reference/parallel-batch.md +57 -36
  182. package/pack/generated/devin/skills/rite-build/reference/phase-contract.md +8 -5
  183. package/pack/generated/devin/skills/rite-build/reference/wright-dispatch.md +7 -7
  184. package/pack/generated/devin/skills/rite-clarify/SKILL.md +2 -2
  185. package/pack/generated/devin/skills/rite-converge/SKILL.md +4 -4
  186. package/pack/generated/devin/skills/rite-define/SKILL.md +7 -3
  187. package/pack/generated/devin/skills/rite-define/reference/plan-template.md +4 -2
  188. package/pack/generated/devin/skills/rite-plan/SKILL.md +11 -8
  189. package/pack/generated/devin/skills/rite-plan/reference/dependency-graph.md +9 -2
  190. package/pack/generated/devin/skills/rite-plan/reference/slicing.md +2 -0
  191. package/pack/generated/devin/skills/rite-polish/SKILL.md +3 -3
  192. package/pack/generated/devin/skills/rite-prove/SKILL.md +24 -12
  193. package/pack/generated/devin/skills/rite-resolve/SKILL.md +3 -3
  194. package/pack/generated/devin/skills/rite-resolve/reference/answer-protocol.md +2 -0
  195. package/pack/generated/devin/skills/rite-review/SKILL.md +11 -3
  196. package/pack/generated/devin/skills/rite-review/reference/feature-scoped-review.md +2 -2
  197. package/pack/generated/devin/skills/rite-seal/SKILL.md +1 -1
  198. package/pack/generated/devin/skills/rite-seal/reference/phase-contract.md +1 -1
  199. package/pack/generated/devin/skills/rite-ship/SKILL.md +2 -2
  200. package/pack/generated/devin/skills/rite-ship/reference/git-ship.md +2 -2
  201. package/pack/generated/devin/skills/rite-spec/SKILL.md +8 -5
  202. package/pack/generated/devin/skills/rite-spec/reference/state-workspace.md +5 -0
  203. package/pack/generated/devin/skills/rite-temper/SKILL.md +3 -3
  204. package/pack/generated/devin/skills/rite-vet/SKILL.md +19 -6
  205. package/pack/generated/devin/skills/rite-vet/reference/review-axes.md +12 -5
  206. package/pack/generated/dsh/agents/devrites-proof-runner.md +16 -9
  207. package/pack/generated/dsh/runtime/cordis.patch.yml +1 -1
  208. package/pack/generated/dsh/skills/devrites-lib/SKILL.md +2 -0
  209. package/pack/generated/dsh/skills/devrites-lib/reference/candidate-integrity.md +7 -3
  210. package/pack/generated/dsh/skills/devrites-lib/reference/parallel-dispatch.md +3 -0
  211. package/pack/generated/dsh/skills/devrites-lib/reference/parallel-workspaces.md +236 -0
  212. package/pack/generated/dsh/skills/devrites-lib/reference/standards/afk-hitl.md +9 -12
  213. package/pack/generated/dsh/skills/devrites-lib/reference/standards/agents.md +1 -1
  214. package/pack/generated/dsh/skills/devrites-lib/reference/standards/context-hygiene.md +1 -1
  215. package/pack/generated/dsh/skills/devrites-lib/reference/standards/gates.md +18 -9
  216. package/pack/generated/dsh/skills/devrites-lib/reference/workspace-artifact-schema.md +10 -0
  217. package/pack/generated/dsh/skills/rite-autocomplete/SKILL.md +12 -10
  218. package/pack/generated/dsh/skills/rite-autocomplete/reference/lookahead.md +4 -4
  219. package/pack/generated/dsh/skills/rite-autocomplete/reference/loop.md +20 -9
  220. package/pack/generated/dsh/skills/rite-build/SKILL.md +13 -10
  221. package/pack/generated/dsh/skills/rite-build/reference/afk-discipline.md +1 -1
  222. package/pack/generated/dsh/skills/rite-build/reference/checkpoint.md +1 -1
  223. package/pack/generated/dsh/skills/rite-build/reference/parallel-batch.md +57 -36
  224. package/pack/generated/dsh/skills/rite-build/reference/phase-contract.md +8 -5
  225. package/pack/generated/dsh/skills/rite-build/reference/wright-dispatch.md +7 -7
  226. package/pack/generated/dsh/skills/rite-clarify/SKILL.md +2 -2
  227. package/pack/generated/dsh/skills/rite-converge/SKILL.md +4 -4
  228. package/pack/generated/dsh/skills/rite-define/SKILL.md +7 -3
  229. package/pack/generated/dsh/skills/rite-define/reference/plan-template.md +4 -2
  230. package/pack/generated/dsh/skills/rite-plan/SKILL.md +11 -8
  231. package/pack/generated/dsh/skills/rite-plan/reference/dependency-graph.md +9 -2
  232. package/pack/generated/dsh/skills/rite-plan/reference/slicing.md +2 -0
  233. package/pack/generated/dsh/skills/rite-polish/SKILL.md +3 -3
  234. package/pack/generated/dsh/skills/rite-prove/SKILL.md +24 -12
  235. package/pack/generated/dsh/skills/rite-resolve/SKILL.md +3 -3
  236. package/pack/generated/dsh/skills/rite-resolve/reference/answer-protocol.md +2 -0
  237. package/pack/generated/dsh/skills/rite-review/SKILL.md +11 -3
  238. package/pack/generated/dsh/skills/rite-review/reference/feature-scoped-review.md +2 -2
  239. package/pack/generated/dsh/skills/rite-seal/SKILL.md +1 -1
  240. package/pack/generated/dsh/skills/rite-seal/reference/phase-contract.md +1 -1
  241. package/pack/generated/dsh/skills/rite-ship/SKILL.md +2 -2
  242. package/pack/generated/dsh/skills/rite-ship/reference/git-ship.md +2 -2
  243. package/pack/generated/dsh/skills/rite-spec/SKILL.md +8 -5
  244. package/pack/generated/dsh/skills/rite-spec/reference/state-workspace.md +5 -0
  245. package/pack/generated/dsh/skills/rite-temper/SKILL.md +3 -3
  246. package/pack/generated/dsh/skills/rite-vet/SKILL.md +19 -6
  247. package/pack/generated/dsh/skills/rite-vet/reference/review-axes.md +12 -5
  248. package/pack/generated/omp/agents/devrites-proof-runner.md +16 -9
  249. package/pack/generated/omp/commands/rite-build.md +1 -1
  250. package/pack/generated/omp/skills/devrites-lib/SKILL.md +2 -0
  251. package/pack/generated/omp/skills/devrites-lib/reference/candidate-integrity.md +7 -3
  252. package/pack/generated/omp/skills/devrites-lib/reference/parallel-dispatch.md +3 -0
  253. package/pack/generated/omp/skills/devrites-lib/reference/parallel-workspaces.md +236 -0
  254. package/pack/generated/omp/skills/devrites-lib/reference/standards/afk-hitl.md +9 -12
  255. package/pack/generated/omp/skills/devrites-lib/reference/standards/agents.md +1 -1
  256. package/pack/generated/omp/skills/devrites-lib/reference/standards/context-hygiene.md +1 -1
  257. package/pack/generated/omp/skills/devrites-lib/reference/standards/gates.md +18 -9
  258. package/pack/generated/omp/skills/devrites-lib/reference/workspace-artifact-schema.md +10 -0
  259. package/pack/generated/omp/skills/rite-autocomplete/SKILL.md +12 -10
  260. package/pack/generated/omp/skills/rite-autocomplete/reference/lookahead.md +4 -4
  261. package/pack/generated/omp/skills/rite-autocomplete/reference/loop.md +20 -9
  262. package/pack/generated/omp/skills/rite-build/SKILL.md +13 -10
  263. package/pack/generated/omp/skills/rite-build/reference/afk-discipline.md +1 -1
  264. package/pack/generated/omp/skills/rite-build/reference/checkpoint.md +1 -1
  265. package/pack/generated/omp/skills/rite-build/reference/parallel-batch.md +57 -36
  266. package/pack/generated/omp/skills/rite-build/reference/phase-contract.md +8 -5
  267. package/pack/generated/omp/skills/rite-build/reference/wright-dispatch.md +7 -7
  268. package/pack/generated/omp/skills/rite-clarify/SKILL.md +2 -2
  269. package/pack/generated/omp/skills/rite-converge/SKILL.md +4 -4
  270. package/pack/generated/omp/skills/rite-define/SKILL.md +7 -3
  271. package/pack/generated/omp/skills/rite-define/reference/plan-template.md +4 -2
  272. package/pack/generated/omp/skills/rite-plan/SKILL.md +11 -8
  273. package/pack/generated/omp/skills/rite-plan/reference/dependency-graph.md +9 -2
  274. package/pack/generated/omp/skills/rite-plan/reference/slicing.md +2 -0
  275. package/pack/generated/omp/skills/rite-polish/SKILL.md +3 -3
  276. package/pack/generated/omp/skills/rite-prove/SKILL.md +24 -12
  277. package/pack/generated/omp/skills/rite-resolve/SKILL.md +3 -3
  278. package/pack/generated/omp/skills/rite-resolve/reference/answer-protocol.md +2 -0
  279. package/pack/generated/omp/skills/rite-review/SKILL.md +11 -3
  280. package/pack/generated/omp/skills/rite-review/reference/feature-scoped-review.md +2 -2
  281. package/pack/generated/omp/skills/rite-seal/SKILL.md +1 -1
  282. package/pack/generated/omp/skills/rite-seal/reference/phase-contract.md +1 -1
  283. package/pack/generated/omp/skills/rite-ship/SKILL.md +2 -2
  284. package/pack/generated/omp/skills/rite-ship/reference/git-ship.md +2 -2
  285. package/pack/generated/omp/skills/rite-spec/SKILL.md +8 -5
  286. package/pack/generated/omp/skills/rite-spec/reference/state-workspace.md +5 -0
  287. package/pack/generated/omp/skills/rite-temper/SKILL.md +3 -3
  288. package/pack/generated/omp/skills/rite-vet/SKILL.md +19 -6
  289. package/pack/generated/omp/skills/rite-vet/reference/review-axes.md +12 -5
  290. package/pack/generated/pi/agents/devrites-proof-runner.md +16 -9
  291. package/pack/generated/pi/prompts/rite-build.md +1 -1
  292. package/pack/generated/pi/skills/devrites-lib/SKILL.md +2 -0
  293. package/pack/generated/pi/skills/devrites-lib/reference/candidate-integrity.md +7 -3
  294. package/pack/generated/pi/skills/devrites-lib/reference/parallel-dispatch.md +3 -0
  295. package/pack/generated/pi/skills/devrites-lib/reference/parallel-workspaces.md +236 -0
  296. package/pack/generated/pi/skills/devrites-lib/reference/standards/afk-hitl.md +9 -12
  297. package/pack/generated/pi/skills/devrites-lib/reference/standards/agents.md +1 -1
  298. package/pack/generated/pi/skills/devrites-lib/reference/standards/context-hygiene.md +1 -1
  299. package/pack/generated/pi/skills/devrites-lib/reference/standards/gates.md +18 -9
  300. package/pack/generated/pi/skills/devrites-lib/reference/workspace-artifact-schema.md +10 -0
  301. package/pack/generated/pi/skills/rite-autocomplete/SKILL.md +12 -10
  302. package/pack/generated/pi/skills/rite-autocomplete/reference/lookahead.md +4 -4
  303. package/pack/generated/pi/skills/rite-autocomplete/reference/loop.md +20 -9
  304. package/pack/generated/pi/skills/rite-build/SKILL.md +13 -10
  305. package/pack/generated/pi/skills/rite-build/reference/afk-discipline.md +1 -1
  306. package/pack/generated/pi/skills/rite-build/reference/checkpoint.md +1 -1
  307. package/pack/generated/pi/skills/rite-build/reference/parallel-batch.md +57 -36
  308. package/pack/generated/pi/skills/rite-build/reference/phase-contract.md +8 -5
  309. package/pack/generated/pi/skills/rite-build/reference/wright-dispatch.md +7 -7
  310. package/pack/generated/pi/skills/rite-clarify/SKILL.md +2 -2
  311. package/pack/generated/pi/skills/rite-converge/SKILL.md +4 -4
  312. package/pack/generated/pi/skills/rite-define/SKILL.md +7 -3
  313. package/pack/generated/pi/skills/rite-define/reference/plan-template.md +4 -2
  314. package/pack/generated/pi/skills/rite-plan/SKILL.md +11 -8
  315. package/pack/generated/pi/skills/rite-plan/reference/dependency-graph.md +9 -2
  316. package/pack/generated/pi/skills/rite-plan/reference/slicing.md +2 -0
  317. package/pack/generated/pi/skills/rite-polish/SKILL.md +3 -3
  318. package/pack/generated/pi/skills/rite-prove/SKILL.md +24 -12
  319. package/pack/generated/pi/skills/rite-resolve/SKILL.md +3 -3
  320. package/pack/generated/pi/skills/rite-resolve/reference/answer-protocol.md +2 -0
  321. package/pack/generated/pi/skills/rite-review/SKILL.md +11 -3
  322. package/pack/generated/pi/skills/rite-review/reference/feature-scoped-review.md +2 -2
  323. package/pack/generated/pi/skills/rite-seal/SKILL.md +1 -1
  324. package/pack/generated/pi/skills/rite-seal/reference/phase-contract.md +1 -1
  325. package/pack/generated/pi/skills/rite-ship/SKILL.md +2 -2
  326. package/pack/generated/pi/skills/rite-ship/reference/git-ship.md +2 -2
  327. package/pack/generated/pi/skills/rite-spec/SKILL.md +8 -5
  328. package/pack/generated/pi/skills/rite-spec/reference/state-workspace.md +5 -0
  329. package/pack/generated/pi/skills/rite-temper/SKILL.md +3 -3
  330. package/pack/generated/pi/skills/rite-vet/SKILL.md +19 -6
  331. package/pack/generated/pi/skills/rite-vet/reference/review-axes.md +12 -5
  332. package/package.json +1 -1
@@ -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"
@@ -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
@@ -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
@@ -926,20 +937,28 @@ func roleAgentPath(manifest loadsManifest, role string) string {
926
937
 
927
938
  // activeSliceScope is state.md's active_slice when it names a slice block in
928
939
  // tasks.md, else "" (unset, "none", or stale cursors keep the whole bundle).
929
- func activeSliceScope(featureDir string) string {
930
- 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")
931
946
  // active_slice outlives Build; after it the root's autocomplete bundle stays whole.
932
947
  if phase, _ := state.CursorField(lines, state.CursorPhase); strings.TrimSpace(phase) != "build" {
933
- return ""
948
+ return "", nil
934
949
  }
935
950
  id, _ := state.CursorField(lines, state.CursorActiveSlice)
936
951
  if id = strings.TrimSpace(id); !state.ValidSliceID(id) {
937
- return ""
952
+ return "", nil
953
+ }
954
+ tasks, err := sources.read("tasks.md")
955
+ if err != nil && !errors.Is(err, os.ErrNotExist) {
956
+ return "", err
938
957
  }
939
- if _, ok := state.ExtractTaskSlice(readWorkspaceFile(featureDir, "tasks.md"), id); !ok {
940
- return ""
958
+ if _, ok := state.ExtractTaskSlice(tasks, id); !ok {
959
+ return "", nil
941
960
  }
942
- return id
961
+ return id, nil
943
962
  }
944
963
 
945
964
  func bundleName(opts contextOpts) string {
@@ -0,0 +1,171 @@
1
+ package lib
2
+
3
+ import (
4
+ "bytes"
5
+ "crypto/sha256"
6
+ "fmt"
7
+ "io"
8
+ "os"
9
+ "path/filepath"
10
+ "strings"
11
+
12
+ "github.com/devrites/devrites/internal/fsutil"
13
+ )
14
+
15
+ type contextSource struct {
16
+ data []byte
17
+ err error
18
+ }
19
+
20
+ // contextSources retains one invocation's bytes, including optional absence.
21
+ type contextSources struct {
22
+ dir string
23
+ files map[string]contextSource
24
+ }
25
+
26
+ func (s *contextSources) read(name string) ([]byte, error) {
27
+ if s.dir == "" {
28
+ return nil, os.ErrNotExist
29
+ }
30
+ name = filepath.Clean(filepath.FromSlash(name))
31
+ if !filepath.IsLocal(name) {
32
+ return nil, fmt.Errorf("workspace artifact %q escapes workspace", name)
33
+ }
34
+ if source, ok := s.files[name]; ok {
35
+ return source.data, source.err
36
+ }
37
+ var source contextSource
38
+ if source.err = contextParents(s.dir, name); source.err == nil {
39
+ source.data, source.err = readBoundedRegularFile(filepath.Join(s.dir, name), workspaceArtifactLimit)
40
+ }
41
+ if source.err != nil {
42
+ source.err = fmt.Errorf("workspace artifact %s: %w", name, source.err)
43
+ }
44
+ if s.files == nil {
45
+ s.files = make(map[string]contextSource)
46
+ }
47
+ s.files[name] = source
48
+ return source.data, source.err
49
+ }
50
+
51
+ // Reject even in-workspace parent links: managed paths name actual directories.
52
+ func contextParents(dir, rel string) error {
53
+ parent := filepath.Dir(rel)
54
+ if parent == "." {
55
+ return nil
56
+ }
57
+ for _, part := range strings.Split(parent, string(filepath.Separator)) {
58
+ dir = filepath.Join(dir, part)
59
+ info, err := os.Lstat(dir)
60
+ if os.IsNotExist(err) {
61
+ return nil
62
+ }
63
+ if err != nil {
64
+ return err
65
+ }
66
+ if !info.IsDir() {
67
+ return fmt.Errorf("context parent %s is not a directory (symlinks are not allowed)", dir)
68
+ }
69
+ }
70
+ return nil
71
+ }
72
+
73
+ func publishContext(featureDir string, opts contextOpts, body []byte) (out string, unchanged bool, err error) {
74
+ out = opts.out
75
+ managed := featureDir != "" && (out == "" || !filepath.IsAbs(out))
76
+ if out == "" {
77
+ out = filepath.Join("ctx", bundleName(opts))
78
+ }
79
+ base, rel := featureDir, out
80
+ if !managed && featureDir != "" {
81
+ absoluteFeature, absErr := filepath.Abs(featureDir)
82
+ if absErr != nil {
83
+ return out, false, absErr
84
+ }
85
+ if within, relErr := filepath.Rel(absoluteFeature, out); relErr == nil && filepath.IsLocal(within) {
86
+ managed, rel = true, within
87
+ }
88
+ }
89
+ if managed {
90
+ if !filepath.IsLocal(rel) {
91
+ return out, false, fmt.Errorf("context output %q escapes workspace", out)
92
+ }
93
+ if err := contextParents(base, rel); err != nil {
94
+ return out, false, err
95
+ }
96
+ out = filepath.Join(base, rel)
97
+ } else {
98
+ base, rel = filepath.Dir(out), filepath.Base(out)
99
+ if err := os.MkdirAll(base, 0o755); err != nil {
100
+ return out, false, err
101
+ }
102
+ }
103
+ ws, err := os.OpenRoot(base)
104
+ if err != nil {
105
+ return out, false, err
106
+ }
107
+ defer func() { _ = ws.Close() }()
108
+ if err := ws.MkdirAll(filepath.Dir(rel), 0o755); err != nil {
109
+ return out, false, err
110
+ }
111
+ digest := []byte(fmt.Sprintf("%x\n", sha256.Sum256(body)))
112
+ // Check both destinations before publishing either; never follow a leaf link.
113
+ matches := true
114
+ for _, entry := range []struct {
115
+ name string
116
+ data []byte
117
+ }{{rel, body}, {rel + ".sha256", digest}} {
118
+ info, err := ws.Lstat(entry.name)
119
+ if os.IsNotExist(err) {
120
+ matches = false
121
+ continue
122
+ }
123
+ if err != nil {
124
+ return out, false, err
125
+ }
126
+ if !info.Mode().IsRegular() {
127
+ return out, false, fmt.Errorf("context output %s is not a regular file", entry.name)
128
+ }
129
+ // shortcut: Windows FileMode cannot prove POSIX 0600; rebuild until ACL-aware reuse is needed.
130
+ if info.Mode() != 0o600 || info.Size() != int64(len(entry.data)) {
131
+ matches = false
132
+ continue
133
+ }
134
+ // Open through the confined root; replacement still uses an atomic rename.
135
+ file, err := ws.Open(entry.name)
136
+ if err != nil {
137
+ return out, false, err
138
+ }
139
+ opened, err := file.Stat()
140
+ if err == nil && (opened.Mode() != 0o600 || !os.SameFile(info, opened)) {
141
+ err = fmt.Errorf("context output %s changed while opening", entry.name)
142
+ }
143
+ var data []byte
144
+ if err == nil {
145
+ data, err = io.ReadAll(io.LimitReader(file, int64(len(entry.data))+1))
146
+ }
147
+ if err == nil {
148
+ current, statErr := ws.Lstat(entry.name)
149
+ if statErr != nil {
150
+ err = statErr
151
+ } else if current.Mode() != 0o600 || !os.SameFile(opened, current) {
152
+ err = fmt.Errorf("context output %s changed while reading", entry.name)
153
+ }
154
+ }
155
+ _ = file.Close()
156
+ if err != nil {
157
+ return out, false, err
158
+ }
159
+ matches = matches && bytes.Equal(data, entry.data)
160
+ }
161
+ if matches {
162
+ return out, true, nil
163
+ }
164
+ if err := fsutil.WriteFileAtomicIn(ws, rel, body, 0o600); err != nil {
165
+ return out, false, err
166
+ }
167
+ if err := fsutil.WriteFileAtomicIn(ws, rel+".sha256", digest, 0o600); err != nil {
168
+ return out, false, err
169
+ }
170
+ return out, false, nil
171
+ }
@@ -251,27 +251,27 @@ func manifestFallback(root string, slot string) (detectedCommand, bool) {
251
251
  }
252
252
  manifests := []manifest{
253
253
  {"go.mod", "go.mod", map[string]string{
254
- "unit": "go test ./...", "integration": "go test -tags=integration ./...",
254
+ "unit": "go test ./...",
255
255
  "lint": "golangci-lint run", "vet": "go vet ./...", "build": "go build ./...",
256
256
  }},
257
257
  {"Cargo.toml", "Cargo.toml", map[string]string{
258
- "unit": "cargo test", "integration": "cargo test -- --ignored",
258
+ "unit": "cargo test",
259
259
  "lint": "cargo clippy --all-targets", "vet": "cargo check", "build": "cargo build",
260
260
  }},
261
261
  {"pyproject.toml", "pyproject.toml", map[string]string{
262
- "unit": "python -m pytest", "integration": "python -m pytest -m integration",
262
+ "unit": "python -m pytest",
263
263
  "lint": "ruff check", "vet": "mypy .", "build": "python -m build",
264
264
  }},
265
265
  {"setup.py", "setup.py", map[string]string{
266
- "unit": "python -m pytest", "integration": "python -m pytest -m integration",
266
+ "unit": "python -m pytest",
267
267
  "lint": "flake8", "vet": "mypy .",
268
268
  }},
269
269
  {"pom.xml", "pom.xml", map[string]string{
270
- "unit": "mvn test", "integration": "mvn verify",
271
- "vet": "mvn -q compile", "build": "mvn package",
270
+ "unit": "mvn test",
271
+ "vet": "mvn -q compile", "build": "mvn package",
272
272
  }},
273
273
  {"build.gradle", "build.gradle", map[string]string{
274
- "unit": "gradle test", "integration": "gradle integrationTest",
274
+ "unit": "gradle test",
275
275
  "lint": "gradle check", "build": "gradle build",
276
276
  }},
277
277
  {"composer.json", "composer.json", map[string]string{
@@ -279,7 +279,7 @@ func manifestFallback(root string, slot string) (detectedCommand, bool) {
279
279
  "vet": "vendor/bin/phpstan analyse",
280
280
  }},
281
281
  {"Gemfile", "Gemfile", map[string]string{
282
- "unit": "bundle exec rspec", "integration": "bundle exec rspec --tag integration",
282
+ "unit": "bundle exec rspec",
283
283
  "lint": "bundle exec rubocop",
284
284
  }},
285
285
  {"mix.exs", "mix.exs", map[string]string{
@@ -325,6 +325,8 @@ func dispatchLocked(root, slug, sub, phase, wave, role, handle, reason string, r
325
325
  if err := lintReviewCandidate(root, slug, packet); err != nil {
326
326
  return fail("BLOCKED: %v", err)
327
327
  }
328
+ } else if err := lintPacketDiffs(root, slug, packet, false); err != nil {
329
+ return fail("BLOCKED: %v", err)
328
330
  }
329
331
  break
330
332
  }
@@ -91,6 +91,21 @@ func RunPacket(root string, args []string, stdout, stderr io.Writer) int {
91
91
  if err != nil {
92
92
  return fail(err)
93
93
  }
94
+ code := 0
95
+ if err := state.WithFeatureLock(root, slug, func() error {
96
+ code = renderPacketLocked(root, slug, workspace, wave, phase, role, slice, checks, focus, recheck, wright, doubt, stdout, stderr)
97
+ return nil
98
+ }); err != nil {
99
+ return fail(err)
100
+ }
101
+ return code
102
+ }
103
+
104
+ func renderPacketLocked(root, slug, workspace, wave, phase, role, slice, checks, focus, recheck string, wright, doubt bool, stdout, stderr io.Writer) int {
105
+ fail := func(err error) int {
106
+ fmt.Fprintf(stderr, "packet: %v\n", err)
107
+ return 3
108
+ }
94
109
  rel := workspaceRel(root, workspace)
95
110
  bundle := "ctx/" + bundleName(contextOpts{phase: phase, role: role, slice: slice, recheck: recheck})
96
111
  if info, err := os.Stat(filepath.Join(workspace, filepath.FromSlash(bundle))); err != nil || !info.Mode().IsRegular() {
@@ -188,16 +203,24 @@ func RunPacket(root string, args []string, stdout, stderr io.Writer) int {
188
203
  return fail(err)
189
204
  }
190
205
  // Lint a sibling draft so a refused re-render keeps the packet already on disk.
191
- draft := filepath.Join(workspace, "packets", "."+name+".draft")
192
- if err := state.AtomicWrite(draft, []byte(b.String()), 0o600); err != nil {
206
+ draftFile, err := os.CreateTemp(filepath.Dir(out), "."+name+".draft-*")
207
+ if err != nil {
208
+ return fail(err)
209
+ }
210
+ draft := draftFile.Name()
211
+ defer func() { _ = os.Remove(draft) }()
212
+ _, writeErr := io.WriteString(draftFile, b.String())
213
+ if writeErr == nil {
214
+ writeErr = draftFile.Sync()
215
+ }
216
+ closeErr := draftFile.Close()
217
+ if err := errors.Join(writeErr, closeErr); err != nil {
193
218
  return fail(err)
194
219
  }
195
220
  if err := lintRenderedPacket(root, slug, draft, role, wright, finalPhase); err != nil {
196
- _ = os.Remove(draft)
197
221
  return fail(fmt.Errorf("BLOCKED: %w", err))
198
222
  }
199
223
  if err := os.Rename(draft, out); err != nil {
200
- _ = os.Remove(draft)
201
224
  return fail(err)
202
225
  }
203
226
  fmt.Fprintf(stdout, "packet: %s/packets/%s (%s, %d bytes)\n", rel, name, role, b.Len())
@@ -216,7 +239,7 @@ func lintRenderedPacket(root, slug, path, role string, wright, finalPhase bool)
216
239
  if finalPhase {
217
240
  return lintReviewCandidate(root, slug, path)
218
241
  }
219
- return nil
242
+ return lintPacketDiffs(root, slug, path, false)
220
243
  }
221
244
 
222
245
  // frozenCandidate returns packets/<wave>.sha256 when candidate freeze wrote
@@ -231,8 +254,15 @@ func frozenCandidate(workspace, wave string) (sums string, partial bool, err err
231
254
  if err != nil {
232
255
  return "", false, fmt.Errorf("packets/%s.sha256: %w", wave, err)
233
256
  }
234
- if info, err := os.Stat(filepath.Join(workspace, "packets", wave+".diff")); err != nil || !info.Mode().IsRegular() || info.Size() == 0 {
235
- return "", false, fmt.Errorf("packets/%s.sha256 has no non-empty packets/%s.diff beside it; rerun candidate freeze under a new wave", wave, wave)
257
+ bindings, err := frozenDiffBindings(raw)
258
+ if err != nil || len(bindings) != 1 || bindings[0].name != "packets/"+wave+".diff" {
259
+ return "", false, fmt.Errorf("packets/%s.sha256 must bind exactly packets/%s.diff with a diff SHA256 checksum", wave, wave)
260
+ }
261
+ if _, _, err := candidatePathInfo(workspace, "packets/"+wave+".diff"); err != nil {
262
+ return "", false, err
263
+ }
264
+ if err := verifyFrozenDiff(filepath.Join(workspace, "packets", wave+".diff"), bindings[0].digest); err != nil {
265
+ return "", false, fmt.Errorf("packets/%s.diff: %w", wave, err)
236
266
  }
237
267
  if len(raw) <= maxContractBytes/2 {
238
268
  return string(raw), false, nil
@@ -1,6 +1,7 @@
1
1
  package lib
2
2
 
3
3
  import (
4
+ "bytes"
4
5
  "crypto/sha256"
5
6
  "errors"
6
7
  "fmt"
@@ -80,6 +81,22 @@ func RunCandidateFreeze(root string, args []string, stdout, stderr io.Writer) in
80
81
  if err != nil {
81
82
  return fail(err)
82
83
  }
84
+ code := 0
85
+ if err := state.WithFeatureLock(root, slug, func() error {
86
+ code = freezeCandidateLocked(root, slug, workspace, wave, slice, base, worktree, paths, stdout, stderr)
87
+ return nil
88
+ }); err != nil {
89
+ return fail(err)
90
+ }
91
+ return code
92
+ }
93
+
94
+ func freezeCandidateLocked(root, slug, workspace, wave, slice, base, worktree string, paths []string, stdout, stderr io.Writer) int {
95
+ fail := func(err error) int {
96
+ fmt.Fprintf(stderr, "candidate freeze: %v\n", err)
97
+ return 3
98
+ }
99
+ var err error
83
100
  if slice != "" {
84
101
  if paths, err = slicePaths(root, slug, slice); err != nil {
85
102
  return fail(err)
@@ -98,13 +115,10 @@ func RunCandidateFreeze(root string, args []string, stdout, stderr io.Writer) in
98
115
  if err := validateCandidateRows(project, rows); err != nil {
99
116
  return fail(err)
100
117
  }
101
- diff, baseCommit, err := freezeDiff(project, base, rows)
118
+ digest, err := hashCandidate(project, rows, true)
102
119
  if err != nil {
103
120
  return fail(err)
104
121
  }
105
- if len(diff) == 0 {
106
- return fail(errors.New("the path set has no change against the base; there is no candidate to freeze"))
107
- }
108
122
  head := ""
109
123
  if worktree != "" {
110
124
  // Review binds to the transfer commit, so the frozen tree must be exactly it.
@@ -115,24 +129,27 @@ func RunCandidateFreeze(root string, args []string, stdout, stderr io.Writer) in
115
129
  if len(dirty) != 0 {
116
130
  return fail(errors.New("the worktree has uncommitted changes on the path set; the frozen candidate must be its HEAD transfer commit"))
117
131
  }
118
- head = "head " + commit + "\n"
132
+ head = commit
119
133
  }
120
- digest, err := hashCandidate(project, rows)
134
+ diff, baseCommit, err := freezeDiff(project, base, rows)
121
135
  if err != nil {
122
136
  return fail(err)
123
137
  }
138
+ if len(diff) == 0 {
139
+ return fail(errors.New("the path set has no change against the base; there is no candidate to freeze"))
140
+ }
124
141
  var sums strings.Builder
125
- fmt.Fprintf(&sums, "%s %s\nbase %s\n%sdiff %x packets/%s.diff\n", candidateDigestDomain, candidateDigestVersion, baseCommit, head, sha256.Sum256(diff), wave)
142
+ fmt.Fprintf(&sums, "%s %s\nbase %s\n", candidateDigestDomain, candidateDigestVersion, baseCommit)
143
+ if head != "" {
144
+ fmt.Fprintf(&sums, "head %s\n", head)
145
+ }
146
+ fmt.Fprintf(&sums, "diff %x packets/%s.diff\n", sha256.Sum256(diff), wave)
126
147
  for _, row := range rows {
127
148
  if row.state != "present" {
128
149
  fmt.Fprintf(&sums, "file absent %s\n", row.path)
129
150
  continue
130
151
  }
131
- file := sha256.New()
132
- if err := hashCandidateFile(file, project, row); err != nil {
133
- return fail(fmt.Errorf("candidate %q: %w", row.path, err))
134
- }
135
- fmt.Fprintf(&sums, "file %x %s\n", file.Sum(nil), row.path)
152
+ fmt.Fprintf(&sums, "file %s %s\n", row.fileDigest, row.path)
136
153
  }
137
154
  fmt.Fprintf(&sums, "aggregate %s\n", digest)
138
155
 
@@ -143,6 +160,9 @@ func RunCandidateFreeze(root string, args []string, stdout, stderr io.Writer) in
143
160
  } else if err != nil && !errors.Is(err, os.ErrNotExist) {
144
161
  return fail(err)
145
162
  }
163
+ if err := revalidateFrozenCandidate(project, baseCommit, rows, digest, diff, head); err != nil {
164
+ return fail(err)
165
+ }
146
166
  if err := os.MkdirAll(dir, 0o755); err != nil {
147
167
  return fail(err)
148
168
  }
@@ -157,6 +177,34 @@ func RunCandidateFreeze(root string, args []string, stdout, stderr io.Writer) in
157
177
  return 0
158
178
  }
159
179
 
180
+ // Bracket Git's mutable worktree reads; refuse observed drift, not a filesystem snapshot guarantee.
181
+ func revalidateFrozenCandidate(project, base string, rows []candidateRow, digest string, diff []byte, head string) error {
182
+ currentDiff, commit, err := freezeDiff(project, base, rows)
183
+ if err != nil {
184
+ return err
185
+ }
186
+ if commit != base || !bytes.Equal(currentDiff, diff) {
187
+ return errors.New("candidate diff changed during freeze; retry under a new wave")
188
+ }
189
+ current, err := hashCandidate(project, rows, false)
190
+ if err != nil {
191
+ return fmt.Errorf("candidate changed during freeze: %w", err)
192
+ }
193
+ if current != digest {
194
+ return errors.New("candidate bytes changed during freeze; retry under a new wave")
195
+ }
196
+ if head != "" {
197
+ dirty, currentHead, err := freezeDiff(project, "HEAD", rows)
198
+ if err != nil {
199
+ return err
200
+ }
201
+ if currentHead != head || len(dirty) != 0 {
202
+ return errors.New("worktree HEAD or path set changed during freeze; candidate must remain its clean transfer commit")
203
+ }
204
+ }
205
+ return nil
206
+ }
207
+
160
208
  // slicePaths is the slice's tasks.md write set: Writer allowlist, else Files
161
209
  // likely touched.
162
210
  func slicePaths(root, slug, slice string) ([]string, error) {