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
@@ -27,12 +27,13 @@ var (
27
27
 
28
28
  // CreateOpts configures parallel worktree creation.
29
29
  type CreateOpts struct {
30
- Root string
31
- Slug string
32
- BatchID string
33
- BaseSHA string
34
- Session string
35
- Slices []SlicePaths
30
+ Root string
31
+ Slug string
32
+ BatchID string
33
+ BaseSHA string
34
+ Session string
35
+ Slices []SlicePaths
36
+ Capacity int
36
37
  }
37
38
 
38
39
  // withLeaseLock serializes lease read-modify-write across engine processes:
@@ -42,11 +43,13 @@ func withLeaseLock(repoRoot, slug string, fn func() error) error {
42
43
  if err := validateSlug(slug); err != nil {
43
44
  return err
44
45
  }
45
- return state.WithFeatureLock(
46
- filepath.Join(repoRoot, devritespaths.DevritesRootName), slug, fn)
46
+ return WithControlLock(filepath.Join(repoRoot, devritespaths.DevritesRootName), func() error {
47
+ return state.WithFeatureLock(
48
+ filepath.Join(repoRoot, devritespaths.DevritesRootName), slug, fn)
49
+ })
47
50
  }
48
51
 
49
- // Create path-disjoint-gates, creates 2-10 worktrees from base, and writes a running lease.
52
+ // Create validates and creates 1-10 isolated worktrees with a durable writer cap.
50
53
  func Create(opts CreateOpts) (lease *Lease, err error) {
51
54
  err = withLeaseLock(opts.Root, opts.Slug, func() error {
52
55
  lease, err = createLocked(opts)
@@ -70,7 +73,29 @@ func createLocked(opts CreateOpts) (*Lease, error) {
70
73
  if err := checkParallelSliceCount(len(opts.Slices)); err != nil {
71
74
  return nil, err
72
75
  }
73
- if _, err := CheckPathDisjoint(opts.Slices, opts.Root); err != nil {
76
+ if err := validateCapacity(opts.Capacity); err != nil {
77
+ return nil, err
78
+ }
79
+ if opts.Capacity == 0 {
80
+ opts.Capacity = MaxParallelSlices
81
+ }
82
+ if len(opts.Slices) > opts.Capacity {
83
+ return nil, fmt.Errorf("create would make %d slices active; parallel allows at most %d", len(opts.Slices), opts.Capacity)
84
+ }
85
+ normalized, err := normalizeSlices(opts.Slug, opts.Slices, opts.Root)
86
+ if err != nil {
87
+ return nil, err
88
+ }
89
+ opts.Slices = normalized
90
+ if err := validateSources(filepath.Join(opts.Root, devritespaths.DevritesRootName), opts.Slices); err != nil {
91
+ return nil, err
92
+ }
93
+ if len(opts.Slices) >= 2 {
94
+ if _, err := CheckPathDisjoint(opts.Slices, opts.Root); err != nil {
95
+ return nil, err
96
+ }
97
+ }
98
+ if _, err := controllerLease(opts.Root, opts.Slug); err != nil {
74
99
  return nil, err
75
100
  }
76
101
  base, err := revParse(opts.Root, opts.BaseSHA)
@@ -84,6 +109,10 @@ func createLocked(opts CreateOpts) (*Lease, error) {
84
109
  if _, err := os.Stat(leasePath); err == nil {
85
110
  return nil, fmt.Errorf("lease already exists: %s", leasePath)
86
111
  }
112
+ checkout, controlRef, err := controlIdentity(opts.Root)
113
+ if err != nil {
114
+ return nil, err
115
+ }
87
116
 
88
117
  session := opts.Session
89
118
  if session == "" {
@@ -99,7 +128,10 @@ func createLocked(opts CreateOpts) (*Lease, error) {
99
128
  BatchID: opts.BatchID,
100
129
  CreatedAt: NowUTC(),
101
130
  BaseSHA: base,
131
+ ControlCheckout: checkout,
132
+ ControlRef: controlRef,
102
133
  N: len(slices),
134
+ Capacity: opts.Capacity,
103
135
  Status: StatusRunning,
104
136
  ControlPIDOrSession: session,
105
137
  Slices: slices,
@@ -128,6 +160,13 @@ func createWorktrees(root, slug, batchID, base string, specs []SlicePaths) ([]Le
128
160
  if err := deleteBranch(root, sl.Branch); err != nil {
129
161
  warnf("branch cleanup %s: %v", sl.Branch, err)
130
162
  }
163
+ if ownedWorktreePath(root, batchID, sl) {
164
+ if sl.Workspace != "" {
165
+ _ = os.Remove(filepath.Dir(sl.WorktreePath))
166
+ _ = os.Remove(filepath.Join(ScratchRoot(root), batchID, "_workspaces"))
167
+ }
168
+ _ = os.Remove(filepath.Join(ScratchRoot(root), batchID))
169
+ }
131
170
  }
132
171
  }
133
172
  for _, sp := range specs {
@@ -142,43 +181,46 @@ func createWorktrees(root, slug, batchID, base string, specs []SlicePaths) ([]Le
142
181
  }
143
182
 
144
183
  func createWorktree(root, slug, batchID, base string, sp SlicePaths) (LeaseSlice, error) {
145
- if err := validateSliceID(sp.ID); err != nil {
146
- return LeaseSlice{}, err
147
- }
148
- paths, err := validateSlicePaths(sp.Paths, fmt.Sprintf("slice %q", sp.ID), root)
149
- if err != nil {
150
- return LeaseSlice{}, err
184
+ if sp.Workspace == "" && sp.ID == "integrate" {
185
+ return LeaseSlice{}, fmt.Errorf("slice id %q is reserved for staging integration", sp.ID)
151
186
  }
152
- branch := WorkerBranch(slug, batchID, sp.ID)
153
- wt := WorkerWorktreePath(root, batchID, sp.ID)
187
+ branch := workerBranch(slug, batchID, sp.Workspace, sp.ID)
188
+ wt := workerWorktreePath(root, batchID, sp.Workspace, sp.ID)
154
189
  if _, err := os.Stat(wt); err == nil {
155
190
  return LeaseSlice{}, fmt.Errorf("worktree path already exists: %s", wt)
156
191
  }
192
+ claim := LeaseSlice{ID: sp.ID, Workspace: sp.Workspace, WorktreePath: wt}
193
+ if !ownedWorktreePath(root, batchID, claim) {
194
+ return LeaseSlice{}, fmt.Errorf("worktree path is not owned: %s", wt)
195
+ }
157
196
  if err := os.MkdirAll(filepath.Dir(wt), 0o755); err != nil {
158
197
  return LeaseSlice{}, err
159
198
  }
160
199
  if err := worktreeAdd(root, wt, branch, base); err != nil {
161
- return LeaseSlice{}, fmt.Errorf("worktree add %s: %w", sp.ID, err)
200
+ return LeaseSlice{}, fmt.Errorf("worktree add %s: %w", SliceKey(sp.Workspace, sp.ID), err)
162
201
  }
163
202
  return LeaseSlice{
164
203
  ID: sp.ID,
165
- Paths: paths,
204
+ Workspace: sp.Workspace,
205
+ Paths: sp.Paths,
166
206
  WorktreePath: wt,
167
207
  Branch: branch,
168
208
  WrightStatus: WrightPending,
169
209
  }, nil
170
210
  }
171
211
 
172
- // AddOpts configures refilling a running lease with new slices.
212
+ // AddOpts configures worker refill or an explicit metadata-only capacity update.
173
213
  type AddOpts struct {
174
- Root string
175
- Slug string
176
- Slices []SlicePaths
214
+ Root string
215
+ Slug string
216
+ Slices []SlicePaths
217
+ Capacity int
177
218
  }
178
219
 
179
220
  // Add refills freed slots of a running lease: each new slice gets a worktree
180
221
  // from the current control tip, under the same path-disjoint and cap rules as
181
222
  // the slices still active. Landed (integrated) slices no longer claim paths.
223
+ // An explicit Capacity with no Slices changes only the bound, not lease status.
182
224
  func Add(opts AddOpts) (lease *Lease, err error) {
183
225
  err = withLeaseLock(opts.Root, opts.Slug, func() error {
184
226
  lease, err = addLocked(opts)
@@ -191,8 +233,8 @@ func addLocked(opts AddOpts) (*Lease, error) {
191
233
  if abs, err := filepath.Abs(opts.Root); err == nil {
192
234
  opts.Root = abs
193
235
  }
194
- if len(opts.Slices) == 0 {
195
- return nil, fmt.Errorf("add requires at least one slice")
236
+ if len(opts.Slices) == 0 && opts.Capacity == 0 {
237
+ return nil, fmt.Errorf("add requires at least one slice or an explicit cap")
196
238
  }
197
239
  leasePath, err := LeasePath(opts.Root, opts.Slug)
198
240
  if err != nil {
@@ -202,8 +244,26 @@ func addLocked(opts AddOpts) (*Lease, error) {
202
244
  if err != nil {
203
245
  return nil, err
204
246
  }
205
- if lease.Status != StatusRunning {
206
- return nil, fmt.Errorf("add requires status=running (have %s)", lease.Status)
247
+ if err := normalizeLease(lease, opts.Slug); err != nil {
248
+ return nil, err
249
+ }
250
+ if _, err := recoverLanding(opts.Root, opts.Slug, leasePath, lease, false, false); err != nil {
251
+ return nil, err
252
+ }
253
+ if _, err := controllerLease(opts.Root, opts.Slug); err != nil {
254
+ return nil, err
255
+ }
256
+ if err := validateLeaseSources(filepath.Join(opts.Root, devritespaths.DevritesRootName), lease); err != nil {
257
+ return nil, err
258
+ }
259
+ if err := validateCapacity(opts.Capacity); err != nil {
260
+ return nil, err
261
+ }
262
+ if opts.Capacity == 0 {
263
+ opts.Capacity = leaseCapacity(lease)
264
+ }
265
+ if lease.Status != StatusRunning && lease.Status != StatusComplete {
266
+ return nil, fmt.Errorf("add requires status=running|complete (have %s)", lease.Status)
207
267
  }
208
268
  head, err := revParse(opts.Root, expectedControl(lease))
209
269
  if err != nil {
@@ -215,34 +275,40 @@ func addLocked(opts AddOpts) (*Lease, error) {
215
275
 
216
276
  seen := make(map[string]bool, len(lease.Slices)+len(opts.Slices))
217
277
  for _, sl := range lease.Slices {
218
- seen[sl.ID] = true
278
+ seen[SliceKey(sl.Workspace, sl.ID)] = true
279
+ }
280
+ added, err := normalizeSlices(opts.Slug, opts.Slices, opts.Root)
281
+ if err != nil {
282
+ return nil, err
283
+ }
284
+ if err := validateSources(filepath.Join(opts.Root, devritespaths.DevritesRootName), added); err != nil {
285
+ return nil, err
219
286
  }
220
- added := make([]SlicePaths, 0, len(opts.Slices))
221
287
  var addedPaths []string
222
- for _, sp := range opts.Slices {
223
- if err := validateSliceID(sp.ID); err != nil {
224
- return nil, err
288
+ for _, sp := range added {
289
+ key := SliceKey(sp.Workspace, sp.ID)
290
+ if seen[key] {
291
+ return nil, fmt.Errorf("slice id %q already in lease", key)
225
292
  }
226
- if seen[sp.ID] {
227
- return nil, fmt.Errorf("slice id %q already in lease", sp.ID)
228
- }
229
- seen[sp.ID] = true
230
- paths, err := validateSlicePaths(sp.Paths, fmt.Sprintf("slice %q", sp.ID), opts.Root)
231
- if err != nil {
232
- return nil, err
233
- }
234
- added = append(added, SlicePaths{ID: sp.ID, Paths: paths})
235
- addedPaths = append(addedPaths, paths...)
293
+ seen[key] = true
294
+ addedPaths = append(addedPaths, sp.Paths...)
236
295
  }
237
296
  all := append(activeSlices(lease), added...)
238
- if len(all) > MaxParallelSlices {
239
- return nil, fmt.Errorf("add would make %d slices active; parallel allows at most %d", len(all), MaxParallelSlices)
297
+ if len(all) > opts.Capacity {
298
+ return nil, fmt.Errorf("add would make %d slices active; parallel allows at most %d", len(all), opts.Capacity)
240
299
  }
241
300
  if len(all) >= 2 {
242
301
  if _, err := CheckPathDisjoint(all, opts.Root); err != nil {
243
302
  return nil, err
244
303
  }
245
304
  }
305
+ if len(added) == 0 {
306
+ lease.Capacity = opts.Capacity
307
+ if err := WriteLease(leasePath, lease); err != nil {
308
+ return nil, err
309
+ }
310
+ return lease, nil
311
+ }
246
312
  // The new worktree starts from control HEAD, so uncommitted user work on
247
313
  // its paths would be neither in the slice nor safe from its landing.
248
314
  dirty, err := porcelainDirtyPaths(opts.Root, addedPaths)
@@ -262,6 +328,8 @@ func addLocked(opts AddOpts) (*Lease, error) {
262
328
  }
263
329
  lease.Slices = append(lease.Slices, made...)
264
330
  lease.N = len(lease.Slices)
331
+ lease.Capacity = opts.Capacity
332
+ lease.Status = StatusRunning
265
333
  if err := WriteLease(leasePath, lease); err != nil {
266
334
  rollback()
267
335
  return nil, err
@@ -287,6 +355,19 @@ func recordGreenLocked(repoRoot, slug, sliceID, commit string) (*Lease, error) {
287
355
  if err != nil {
288
356
  return nil, err
289
357
  }
358
+ if err := normalizeLease(lease, slug); err != nil {
359
+ return nil, err
360
+ }
361
+ if _, err := controllerLease(repoRoot, slug); err != nil {
362
+ return nil, err
363
+ }
364
+ if _, err := recoverLanding(repoRoot, slug, leasePath, lease, false, false); err != nil {
365
+ return nil, err
366
+ }
367
+ sliceID, err = normalizeSliceRef(slug, sliceID)
368
+ if err != nil {
369
+ return nil, err
370
+ }
290
371
  if lease.Status != StatusRunning && lease.Status != StatusIntegrateFailed {
291
372
  return nil, fmt.Errorf("record-green requires status=running|integrate-failed (have %s)", lease.Status)
292
373
  }
@@ -296,7 +377,7 @@ func recordGreenLocked(repoRoot, slug, sliceID, commit string) (*Lease, error) {
296
377
  }
297
378
  found := false
298
379
  for i := range lease.Slices {
299
- if lease.Slices[i].ID == sliceID {
380
+ if SliceKey(lease.Slices[i].Workspace, lease.Slices[i].ID) == sliceID {
300
381
  if lease.Slices[i].WrightStatus == WrightIntegrated {
301
382
  // Re-greening a landed slice would land its work a second time.
302
383
  return nil, fmt.Errorf("slice %s already integrated", sliceID)
@@ -335,6 +416,15 @@ func abortLocked(repoRoot, slug string) (*Lease, error) {
335
416
  if err != nil {
336
417
  return nil, err
337
418
  }
419
+ if err := normalizeLease(lease, slug); err != nil {
420
+ return nil, err
421
+ }
422
+ if _, err := controllerLease(repoRoot, slug); err != nil {
423
+ return nil, err
424
+ }
425
+ if _, err := recoverLanding(repoRoot, slug, leasePath, lease, false, true); err != nil {
426
+ return nil, err
427
+ }
338
428
  if lease.Status == StatusComplete {
339
429
  return nil, fmt.Errorf("abort refused: lease already complete — the batch landed in control; nothing to discard")
340
430
  }
@@ -399,6 +489,36 @@ func integrateLocked(opts IntegrateOpts) (tip string, lease *Lease, err error) {
399
489
  if err != nil {
400
490
  return "", nil, err
401
491
  }
492
+ if err := normalizeLease(lease, opts.Slug); err != nil {
493
+ return "", nil, err
494
+ }
495
+ if _, err := controllerLease(opts.Root, opts.Slug); err != nil {
496
+ return "", nil, err
497
+ }
498
+ if opts.Slice != "" {
499
+ opts.Slice, err = normalizeSliceRef(opts.Slug, opts.Slice)
500
+ if err != nil {
501
+ return "", nil, err
502
+ }
503
+ }
504
+ resume := opts.ApplyToControl && lease.PendingLanding != nil && lease.PendingLanding.Slice == opts.Slice
505
+ if _, err := recoverLanding(opts.Root, opts.Slug, leasePath, lease, resume, false); err != nil {
506
+ return "", nil, err
507
+ }
508
+ if opts.ApplyToControl {
509
+ if accepted, err := acceptedRetry(opts.Root, lease, opts.Slice); err != nil {
510
+ return "", nil, err
511
+ } else if accepted != "" {
512
+ return accepted, lease, nil
513
+ }
514
+ }
515
+ if lease.ControlCheckout == "" && lease.ControlRef == "" {
516
+ checkout, ref, err := controlIdentity(opts.Root)
517
+ if err != nil {
518
+ return "", nil, err
519
+ }
520
+ lease.ControlCheckout, lease.ControlRef = checkout, ref
521
+ }
402
522
  if lease.Status != StatusRunning && lease.Status != StatusIntegrateFailed {
403
523
  return "", nil, fmt.Errorf("integrate requires status=running|integrate-failed (have %s)", lease.Status)
404
524
  }
@@ -416,7 +536,7 @@ func integrateLocked(opts IntegrateOpts) (tip string, lease *Lease, err error) {
416
536
  for i, sl := range lease.Slices {
417
537
  switch {
418
538
  case opts.Slice == "" && sl.WrightStatus != WrightIntegrated,
419
- opts.Slice != "" && sl.ID == opts.Slice:
539
+ opts.Slice != "" && SliceKey(sl.Workspace, sl.ID) == opts.Slice:
420
540
  targets = append(targets, i)
421
541
  }
422
542
  }
@@ -449,23 +569,17 @@ func integrateLocked(opts IntegrateOpts) (tip string, lease *Lease, err error) {
449
569
  for _, i := range targets {
450
570
  sl := lease.Slices[i]
451
571
  if sl.WrightStatus != WrightGreen {
452
- return "", nil, fmt.Errorf("slice %s is not green", sl.ID)
572
+ return "", nil, fmt.Errorf("slice %s is not green", SliceKey(sl.Workspace, sl.ID))
453
573
  }
454
574
  if sl.TransferCommit == "" {
455
- return "", nil, fmt.Errorf("slice %s missing transfer_commit", sl.ID)
575
+ return "", nil, fmt.Errorf("slice %s missing transfer_commit", SliceKey(sl.Workspace, sl.ID))
456
576
  }
457
577
  }
458
578
 
459
579
  ibranch := IntegrateBranchName(opts.Slug, lease.BatchID)
460
580
  stageWT := filepath.Join(ScratchRoot(opts.Root), lease.BatchID, "integrate")
461
- _ = os.RemoveAll(stageWT)
462
- // A crashed prior attempt leaves the worktree registered; prune it so the
463
- // branch delete and worktree add below can succeed on retry.
464
- if _, err := git(opts.Root, "worktree", "prune"); err != nil {
465
- warnf("worktree prune: %v", err)
466
- }
467
- if exists, _ := branchExists(opts.Root, ibranch); exists {
468
- _ = deleteBranch(opts.Root, ibranch)
581
+ if err := prepareStaging(opts.Root, opts.Slug, lease, stageWT, ibranch); err != nil {
582
+ return "", nil, err
469
583
  }
470
584
  if err := os.MkdirAll(filepath.Dir(stageWT), 0o755); err != nil {
471
585
  return "", nil, err
@@ -481,9 +595,6 @@ func integrateLocked(opts IntegrateOpts) (tip string, lease *Lease, err error) {
481
595
  if err := deleteBranch(opts.Root, ibranch); err != nil {
482
596
  warnf("integrate branch cleanup: %v", err)
483
597
  }
484
- if _, err := git(opts.Root, "worktree", "prune"); err != nil {
485
- warnf("worktree prune: %v", err)
486
- }
487
598
  lease.Status = StatusIntegrateFailed
488
599
  if err := writeLease(leasePath, lease); err != nil {
489
600
  // The integrate-failed transition must survive a transient write
@@ -502,22 +613,24 @@ func integrateLocked(opts IntegrateOpts) (tip string, lease *Lease, err error) {
502
613
 
503
614
  for _, i := range targets {
504
615
  sl := lease.Slices[i]
616
+ key := SliceKey(sl.Workspace, sl.ID)
505
617
  // Each slice is proven against the commit its worktree was cut from,
506
618
  // which for a refill is a later control tip than the batch base.
507
619
  base, err := revParse(opts.Root, sliceBase(lease, sl))
508
620
  if err != nil {
509
- return fail(fmt.Errorf("slice %s base: %w", sl.ID, err))
621
+ return fail(fmt.Errorf("slice %s base: %w", key, err))
510
622
  }
511
623
  tc, err := revParse(opts.Root, sl.TransferCommit)
512
624
  if err != nil {
513
- return fail(fmt.Errorf("slice %s transfer: %w", sl.ID, err))
625
+ return fail(fmt.Errorf("slice %s transfer: %w", key, err))
514
626
  }
515
627
  ok, err := isAncestor(opts.Root, base, tc)
628
+ lease.Slices[i].TransferCommit = tc
516
629
  if err != nil {
517
630
  return fail(err)
518
631
  }
519
632
  if !ok {
520
- return fail(fmt.Errorf("transfer_commit for %s is not a descendant of base", sl.ID))
633
+ return fail(fmt.Errorf("transfer_commit for %s is not a descendant of base", key))
521
634
  }
522
635
  got, err := diffNames(opts.Root, base, tc)
523
636
  if err != nil {
@@ -527,7 +640,7 @@ func integrateLocked(opts IntegrateOpts) (tip string, lease *Lease, err error) {
527
640
  sort.Strings(want)
528
641
  sort.Strings(got)
529
642
  if !pathListsEqual(want, got) {
530
- return fail(fmt.Errorf("exact-path proof failed for slice %s: want=%v got=%v", sl.ID, want, got))
643
+ return fail(fmt.Errorf("exact-path proof failed for slice %s: want=%v got=%v", key, want, got))
531
644
  }
532
645
  for _, p := range got {
533
646
  if p == ".devrites" || strings.HasPrefix(p, ".devrites/") {
@@ -537,14 +650,18 @@ func integrateLocked(opts IntegrateOpts) (tip string, lease *Lease, err error) {
537
650
 
538
651
  if err := cherryPickNoCommit(stageWT, base, tc); err != nil {
539
652
  cherryPickAbort(stageWT)
540
- return fail(fmt.Errorf("apply failed for slice %s: %w", sl.ID, err))
653
+ return fail(fmt.Errorf("apply failed for slice %s: %w", key, err))
541
654
  }
542
655
  subj, err := commitSubject(opts.Root, tc)
543
656
  if err != nil {
544
- return fail(fmt.Errorf("transfer subject for %s: %w", sl.ID, err))
657
+ return fail(fmt.Errorf("transfer subject for %s: %w", key, err))
658
+ }
659
+ source := sl.Workspace
660
+ if source == "" {
661
+ source = opts.Slug
545
662
  }
546
- if _, err = stagePathsAndCommit(stageWT, controlCommitMessage(opts.Slug, subj), sl.Paths); err != nil {
547
- return fail(fmt.Errorf("control commit for %s: %w", sl.ID, err))
663
+ if _, err = stagePathsAndCommit(stageWT, controlCommitMessage(source, subj), sl.Paths); err != nil {
664
+ return fail(fmt.Errorf("control commit for %s: %w", key, err))
548
665
  }
549
666
  cherryPickQuit(stageWT)
550
667
  }
@@ -560,25 +677,28 @@ func integrateLocked(opts IntegrateOpts) (tip string, lease *Lease, err error) {
560
677
  }
561
678
  warnf("staging worktree cleanup: %v", err)
562
679
  }
563
- if _, err := git(opts.Root, "worktree", "prune"); err != nil {
564
- warnf("worktree prune: %v", err)
565
- }
566
680
 
567
681
  if opts.ApplyToControl {
568
- if err := mergeFFOnly(opts.Root, tip); err != nil {
569
- return fail(fmt.Errorf("control fast-forward to integrate tip failed: %w", err))
682
+ if err := ensureControllerIdentity(opts.Root, lease); err != nil {
683
+ return "", nil, err
684
+ }
685
+ checkout, ref, err := controlIdentity(opts.Root)
686
+ if err != nil {
687
+ return "", nil, err
688
+ }
689
+ lease.ControlCheckout, lease.ControlRef = checkout, ref
690
+ record := &landing{
691
+ BatchID: lease.BatchID, Controller: opts.Slug, Checkout: checkout,
692
+ Ref: ref, PriorTip: expected, NewTip: tip, Slice: opts.Slice,
570
693
  }
571
694
  for _, i := range targets {
572
- lease.Slices[i].WrightStatus = WrightIntegrated
695
+ record.Targets = append(record.Targets, lease.Slices[i])
573
696
  }
574
- lease.ControlTip = tip
575
- // A per-slice landing also clears an earlier integrate-failed: the
576
- // lease is healthy again while other slices are still in flight.
577
- lease.Status = StatusRunning
578
- if len(activeSlices(lease)) == 0 {
579
- lease.Status = StatusComplete
697
+ lease.PendingLanding = record
698
+ if err := writeLease(leasePath, lease); err != nil {
699
+ return "", nil, fmt.Errorf("prepare landing before control fast-forward: %w", err)
580
700
  }
581
- if err := WriteLease(leasePath, lease); err != nil {
701
+ if _, err := recoverLanding(opts.Root, opts.Slug, leasePath, lease, true, false); err != nil {
582
702
  return "", nil, err
583
703
  }
584
704
  } else if err := ensureControlAtBase(opts.Root, expected); err != nil {
@@ -648,7 +768,7 @@ type Salvage struct {
648
768
  // root down to the worktree directory may be a symlink.
649
769
  func ownedWorktreePath(repoRoot, batchID string, sl LeaseSlice) bool {
650
770
  got, err1 := filepath.Abs(sl.WorktreePath)
651
- want, err2 := filepath.Abs(WorkerWorktreePath(repoRoot, batchID, sl.ID))
771
+ want, err2 := filepath.Abs(workerWorktreePath(repoRoot, batchID, sl.Workspace, sl.ID))
652
772
  root, err3 := filepath.Abs(repoRoot)
653
773
  if err1 != nil || err2 != nil || err3 != nil || got != want {
654
774
  return false
@@ -689,12 +809,9 @@ func salvageSlice(sl LeaseSlice, batchID string) (string, error) {
689
809
  fmt.Sprintf("devrites: salvage WIP (%s)", batchID), sl.Paths)
690
810
  }
691
811
 
692
- // Cleanup removes worker worktrees and clears the lease. A complete batch, and
693
- // any slice already integrated, deletes the merged slice branches; a forced
694
- // non-complete cleanup first salvages each other sibling — uncommitted
695
- // allowlisted changes become a commit on the slice branch — and keeps every
696
- // branch whose tip moved past that slice's base as the durable rejected-work
697
- // ref.
812
+ // Cleanup removes unchanged integrated checkouts and refs, salvages allowlisted
813
+ // nonintegrated work when forced, then clears the lease. Late data or changed
814
+ // refs remain available through the returned recovery paths and branches.
698
815
  func Cleanup(repoRoot, slug string, force bool) (salvaged []Salvage, err error) {
699
816
  err = withLeaseLock(repoRoot, slug, func() error {
700
817
  salvaged, err = cleanupLocked(repoRoot, slug, force)
@@ -715,10 +832,18 @@ func cleanupLocked(repoRoot, slug string, force bool) ([]Salvage, error) {
715
832
  if err != nil {
716
833
  return nil, err
717
834
  }
835
+ if err := normalizeLease(lease, slug); err != nil {
836
+ return nil, err
837
+ }
838
+ if _, err := controllerLease(repoRoot, slug); err != nil {
839
+ return nil, err
840
+ }
841
+ if _, err := recoverLanding(repoRoot, slug, leasePath, lease, false, false); err != nil {
842
+ return nil, err
843
+ }
718
844
  if lease.Status != StatusComplete && !force {
719
845
  return nil, fmt.Errorf("cleanup refuses status=%s without --force (non-complete cleanup salvages work onto slice branches)", lease.Status)
720
846
  }
721
- complete := lease.Status == StatusComplete
722
847
  // Ownership comes from what git registered, read before anything is
723
848
  // removed, never from the lease's own claim.
724
849
  registered, err := registeredWorktrees(repoRoot)
@@ -727,66 +852,101 @@ func cleanupLocked(repoRoot, slug string, force bool) ([]Salvage, error) {
727
852
  }
728
853
  var salvaged []Salvage
729
854
  var failed, stranded, unconfirmed []string
730
- keptWorktrees := map[string]bool{}
731
855
  for _, sl := range lease.Slices {
732
- // A slice integrate already landed is done like a complete batch: its
733
- // work is on control, so there is nothing to salvage or keep.
734
- landed := complete || sl.WrightStatus == WrightIntegrated
856
+ key := SliceKey(sl.Workspace, sl.ID)
857
+ landed := sl.WrightStatus == WrightIntegrated
735
858
  salvageFailed := false
736
859
  // Only the deterministic scratch layout is ours to touch. A forged or
737
860
  // stale lease worktree_path is never salvaged into (git ops would run
738
861
  // in an arbitrary repo) and never removed (RemoveAll would delete an
739
862
  // arbitrary directory).
740
863
  inScratch := sl.WorktreePath != "" && ownedWorktreePath(repoRoot, lease.BatchID, sl)
741
- owned := inScratch && createdWorktree(registered, sl.WorktreePath)
864
+ owned := inScratch && sl.Branch == workerBranch(slug, lease.BatchID, sl.Workspace, sl.ID) && createdWorktree(registered, sl.WorktreePath)
865
+ if owned {
866
+ if _, err := os.Stat(sl.WorktreePath); err == nil {
867
+ branch, err := git(sl.WorktreePath, "symbolic-ref", "--quiet", "--short", "HEAD")
868
+ if err != nil || branch != sl.Branch {
869
+ owned = false
870
+ warnf("slice %s no longer has its registered worker branch; leaving %s untouched", key, sl.WorktreePath)
871
+ }
872
+ }
873
+ }
742
874
  if sl.WorktreePath != "" && !owned {
743
- // Never salvaged, so the batch sweep must not delete this slice's
744
- // scratch entry either (e.g. a --root spelled through a symlink).
745
- keptWorktrees[sl.ID] = true
746
875
  if inScratch {
747
- warnf("slice %s worktree_path %q is not a worktree git registered; leaving it untouched",
748
- sl.ID, sl.WorktreePath)
876
+ warnf("slice %s worktree_path %q is not a worktree this lease owns and git registered; leaving it untouched",
877
+ key, sl.WorktreePath)
749
878
  } else {
750
879
  warnf("slice %s worktree_path %q is outside %s; leaving it untouched",
751
- sl.ID, sl.WorktreePath, ScratchRoot(repoRoot))
880
+ key, sl.WorktreePath, ScratchRoot(repoRoot))
881
+ }
882
+ }
883
+ present := false
884
+ if _, err := os.Lstat(sl.WorktreePath); err == nil || !os.IsNotExist(err) {
885
+ present = true
886
+ }
887
+ missingRegistered := !present && registered[resolvedPath(sl.WorktreePath)]
888
+ if missingRegistered {
889
+ warnf("preserving missing slice %s registration, index and recovery metadata; recovery path=%s ref=%s", key, sl.WorktreePath, sl.Branch)
890
+ salvageFailed = true
891
+ }
892
+ if !owned && present {
893
+ salvageFailed = true
894
+ }
895
+ if landed && !missingRegistered {
896
+ exists, branchErr := branchExists(repoRoot, sl.Branch)
897
+ if branchErr == nil && !exists && !present {
898
+ continue
899
+ }
900
+ if err := acceptedWorker(repoRoot, slug, lease, sl, registered); err != nil {
901
+ warnf("preserving integrated slice %s: %v; recovery path=%s ref=%s", key, err, sl.WorktreePath, sl.Branch)
902
+ salvageFailed = true
752
903
  }
753
904
  }
754
905
  if !landed && owned {
755
906
  if _, err := salvage(sl, lease.BatchID); err != nil {
756
- warnf("salvage %s: %v", sl.ID, err)
907
+ warnf("salvage %s: %v", key, err)
757
908
  salvageFailed = true
758
- keptWorktrees[sl.ID] = true
759
909
  }
760
910
  }
761
- if owned && !salvageFailed {
762
- if err := removeWorktree(repoRoot, sl.WorktreePath); err != nil {
763
- warnf("worktree cleanup %s: %v", sl.WorktreePath, err)
911
+ if owned && present && !salvageFailed {
912
+ if err := fullyClean(sl.WorktreePath); err != nil {
913
+ warnf("preserving residual data for %s: %v; recovery ref=%s", key, err, sl.Branch)
914
+ salvageFailed = true
764
915
  }
765
- if err := os.RemoveAll(sl.WorktreePath); err != nil {
766
- warnf("worktree dir cleanup %s: %v", sl.WorktreePath, err)
767
- failed = append(failed, sl.ID)
916
+ }
917
+ if owned && present && !salvageFailed {
918
+ if err := removeWorktree(repoRoot, sl.WorktreePath); err != nil {
919
+ warnf("worktree cleanup %s (recovery ref=%s): %v", sl.WorktreePath, sl.Branch, err)
920
+ salvageFailed = true
921
+ failed = append(failed, key)
768
922
  // git can unregister a worktree and still exit non-zero, so the
769
923
  // exit code says nothing about registration; ask git again.
770
924
  if reg, rerr := registeredWorktrees(repoRoot); rerr != nil {
771
925
  unconfirmed = append(unconfirmed, sl.WorktreePath)
772
- } else if !createdWorktree(reg, sl.WorktreePath) {
926
+ } else if !reg[resolvedPath(sl.WorktreePath)] {
773
927
  // A rerun refuses an unregistered directory as not
774
928
  // created by us, so it will not delete it.
775
929
  stranded = append(stranded, sl.WorktreePath)
776
930
  }
777
931
  }
778
932
  }
779
- if sl.Branch != "" && !ownedBranchName(slug, lease.BatchID, sl.Branch) {
780
- warnf("slice %s branch %q is outside devrites/parallel/%s/%s/; leaving it untouched",
781
- sl.ID, sl.Branch, slug, lease.BatchID)
782
- if salvageFailed {
783
- salvaged = append(salvaged, Salvage{SliceID: sl.ID, Worktree: sl.WorktreePath})
933
+ if sl.Branch != "" && sl.Branch != workerBranch(slug, lease.BatchID, sl.Workspace, sl.ID) {
934
+ warnf("slice %s branch %q does not match its worker namespace; leaving it untouched",
935
+ key, sl.Branch)
936
+ tip, _ := revParse(repoRoot, "refs/heads/"+sl.Branch)
937
+ result := Salvage{SliceID: key, Branch: sl.Branch, Commit: tip}
938
+ if present || missingRegistered {
939
+ result.Worktree = sl.WorktreePath
784
940
  }
941
+ salvaged = append(salvaged, result)
785
942
  continue
786
943
  }
787
944
  if sl.Branch != "" {
788
945
  keep := salvageFailed
789
946
  tip := ""
947
+ if t, err := revParse(repoRoot, "refs/heads/"+sl.Branch); err == nil {
948
+ tip = t
949
+ }
790
950
  if !landed {
791
951
  base := ""
792
952
  if b, err := revParse(repoRoot, sliceBase(lease, sl)); err != nil {
@@ -803,22 +963,25 @@ func cleanupLocked(repoRoot, slug string, force bool) ([]Salvage, error) {
803
963
  }
804
964
  }
805
965
  if keep {
806
- s := Salvage{SliceID: sl.ID, Branch: sl.Branch, Commit: tip}
807
- if salvageFailed {
966
+ s := Salvage{SliceID: key, Branch: sl.Branch, Commit: tip}
967
+ if salvageFailed && (present || missingRegistered) {
808
968
  s.Worktree = sl.WorktreePath
809
969
  }
810
970
  salvaged = append(salvaged, s)
811
- } else if err := deleteBranch(repoRoot, sl.Branch); err != nil {
812
- warnf("branch cleanup %s: %v", sl.Branch, err)
813
- // A refused worktree is not removed and the lease is cleared once
814
- // nothing else failed, so the branch stays behind; the warning
815
- // above names it.
816
- if owned || sl.WorktreePath == "" {
817
- failed = append(failed, sl.ID)
971
+ } else {
972
+ deleteTip := tip
973
+ if landed {
974
+ deleteTip = sl.TransferCommit
975
+ }
976
+ if err := deleteBranchAt(repoRoot, sl.Branch, deleteTip); err != nil {
977
+ warnf("branch cleanup %s: %v", sl.Branch, err)
978
+ if owned || sl.WorktreePath == "" {
979
+ failed = append(failed, key)
980
+ }
818
981
  }
819
982
  }
820
983
  } else if salvageFailed {
821
- salvaged = append(salvaged, Salvage{SliceID: sl.ID, Worktree: sl.WorktreePath})
984
+ salvaged = append(salvaged, Salvage{SliceID: key, Worktree: sl.WorktreePath})
822
985
  }
823
986
  }
824
987
  ibranch := IntegrateBranchName(slug, lease.BatchID)
@@ -831,43 +994,26 @@ func cleanupLocked(repoRoot, slug string, force bool) ([]Salvage, error) {
831
994
  }
832
995
  if keep {
833
996
  salvaged = append(salvaged, Salvage{SliceID: "integrate", Branch: ibranch, Commit: itip})
834
- } else if err := deleteBranch(repoRoot, ibranch); err != nil {
997
+ } else if err := deleteBranchAt(repoRoot, ibranch, itip); err != nil {
835
998
  warnf("integrate branch cleanup %s: %v", ibranch, err)
836
999
  failed = append(failed, "integrate")
837
1000
  }
838
1001
  }
839
1002
  batchDir := filepath.Join(ScratchRoot(repoRoot), lease.BatchID)
840
- if fi, err := os.Lstat(batchDir); err == nil && fi.Mode()&os.ModeSymlink != 0 {
841
- // Drop the link itself; what it points at is not ours.
842
- if err := os.Remove(batchDir); err != nil {
843
- warnf("scratch cleanup: %v", err)
844
- failed = append(failed, "scratch")
845
- }
846
- } else if entries, err := os.ReadDir(batchDir); err == nil {
847
- // A failed salvage keeps its worktree as the only copy, and an entry git
848
- // never registered is not something Create made.
849
- for _, e := range entries {
850
- p := filepath.Join(batchDir, e.Name())
851
- if keptWorktrees[e.Name()] {
852
- continue
853
- }
854
- if !createdWorktree(registered, p) {
855
- warnf("scratch entry %q is not a worktree git registered; leaving it untouched", p)
856
- continue
857
- }
858
- if err := os.RemoveAll(p); err != nil {
859
- warnf("scratch entry cleanup %s: %v", e.Name(), err)
860
- failed = append(failed, e.Name())
1003
+ claim := LeaseSlice{ID: "integrate", WorktreePath: WorkerWorktreePath(repoRoot, lease.BatchID, "integrate")}
1004
+ if ownedWorktreePath(repoRoot, lease.BatchID, claim) {
1005
+ // Remove only empty source namespaces; unknown or unsalvaged files stay.
1006
+ for _, sl := range lease.Slices {
1007
+ if sl.Workspace != "" && ownedWorktreePath(repoRoot, lease.BatchID, sl) {
1008
+ _ = os.Remove(filepath.Join(batchDir, "_workspaces", sl.Workspace))
861
1009
  }
862
1010
  }
1011
+ namespace := filepath.Join(batchDir, "_workspaces")
1012
+ if info, err := os.Lstat(namespace); err == nil && info.IsDir() {
1013
+ _ = os.Remove(namespace)
1014
+ }
863
1015
  // Fails harmlessly while anything was left behind.
864
1016
  _ = os.Remove(batchDir)
865
- } else if !os.IsNotExist(err) {
866
- warnf("scratch cleanup %s: %v", batchDir, err)
867
- failed = append(failed, "scratch")
868
- }
869
- if _, err := git(repoRoot, "worktree", "prune"); err != nil {
870
- warnf("worktree prune: %v", err)
871
1017
  }
872
1018
  if len(failed) > 0 {
873
1019
  // The lease is the only index of what is left on disk; keep it so a
@@ -891,27 +1037,38 @@ func StatusReport(repoRoot, slug string) (string, error) {
891
1037
  if err != nil {
892
1038
  return "", err
893
1039
  }
894
- if _, err := os.Stat(leasePath); os.IsNotExist(err) {
1040
+ found, err := controllerLease(repoRoot, "")
1041
+ if err != nil {
1042
+ return "", err
1043
+ }
1044
+ if found == nil {
895
1045
  return fmt.Sprintf("status: no lease at %s\n", leasePath), nil
896
1046
  }
897
- lease, err := ReadLease(leasePath)
1047
+ lease := found.lease
1048
+ leasePath, err = LeasePath(found.root, found.controller)
898
1049
  if err != nil {
899
1050
  return "", err
900
1051
  }
901
1052
  var b strings.Builder
902
1053
  fmt.Fprintf(&b, "lease: %s\n", leasePath)
1054
+ fmt.Fprintf(&b, "controller: %s\n", found.controller)
1055
+ fmt.Fprintf(&b, "capacity: %d\n", leaseCapacity(lease))
903
1056
  fmt.Fprintf(&b, "status: %s\n", lease.Status)
904
1057
  fmt.Fprintf(&b, "base_sha: %s\n", lease.BaseSHA)
905
1058
  if lease.ControlTip != "" {
906
1059
  fmt.Fprintf(&b, "control_tip: %s\n", lease.ControlTip)
907
1060
  }
1061
+ if lease.PendingLanding != nil {
1062
+ fmt.Fprintf(&b, "pending_landing: %s -> %s; recover with integrate --apply-to-control or abort\n",
1063
+ lease.PendingLanding.PriorTip, lease.PendingLanding.NewTip)
1064
+ }
908
1065
  for _, sl := range lease.Slices {
909
1066
  tc := sl.TransferCommit
910
1067
  if tc == "" {
911
1068
  tc = "-"
912
1069
  }
913
1070
  fmt.Fprintf(&b, "slice %s: wright=%s wt=%s branch=%s transfer=%s",
914
- sl.ID, sl.WrightStatus, sl.WorktreePath, sl.Branch, tc)
1071
+ SliceKey(sl.Workspace, sl.ID), sl.WrightStatus, sl.WorktreePath, sl.Branch, tc)
915
1072
  if sl.BaseSHA != "" {
916
1073
  fmt.Fprintf(&b, " base=%s", sl.BaseSHA)
917
1074
  }