dorfl 0.1.2 → 0.2.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 (364) hide show
  1. package/dist/advance-drivers.d.ts +1 -1
  2. package/dist/advance-drivers.d.ts.map +1 -1
  3. package/dist/advance-drivers.js +15 -3
  4. package/dist/advance-drivers.js.map +1 -1
  5. package/dist/advance-lifecycle-template.d.ts.map +1 -1
  6. package/dist/advance-lifecycle-template.js +76 -4
  7. package/dist/advance-lifecycle-template.js.map +1 -1
  8. package/dist/advance-treeless-publish.d.ts +23 -0
  9. package/dist/advance-treeless-publish.d.ts.map +1 -1
  10. package/dist/advance-treeless-publish.js +41 -0
  11. package/dist/advance-treeless-publish.js.map +1 -1
  12. package/dist/advance.d.ts +59 -7
  13. package/dist/advance.d.ts.map +1 -1
  14. package/dist/advance.js +389 -82
  15. package/dist/advance.js.map +1 -1
  16. package/dist/advancing-lock.d.ts +30 -2
  17. package/dist/advancing-lock.d.ts.map +1 -1
  18. package/dist/advancing-lock.js +50 -0
  19. package/dist/advancing-lock.js.map +1 -1
  20. package/dist/agent-launch.d.ts +12 -0
  21. package/dist/agent-launch.d.ts.map +1 -1
  22. package/dist/agent-launch.js +22 -12
  23. package/dist/agent-launch.js.map +1 -1
  24. package/dist/agent-stop.d.ts +40 -2
  25. package/dist/agent-stop.d.ts.map +1 -1
  26. package/dist/agent-stop.js +30 -2
  27. package/dist/agent-stop.js.map +1 -1
  28. package/dist/apply-decide.d.ts +17 -3
  29. package/dist/apply-decide.d.ts.map +1 -1
  30. package/dist/apply-decide.js +36 -7
  31. package/dist/apply-decide.js.map +1 -1
  32. package/dist/apply-merge-action.d.ts +17 -5
  33. package/dist/apply-merge-action.d.ts.map +1 -1
  34. package/dist/apply-merge-action.js +43 -8
  35. package/dist/apply-merge-action.js.map +1 -1
  36. package/dist/apply-persist.d.ts +57 -26
  37. package/dist/apply-persist.d.ts.map +1 -1
  38. package/dist/apply-persist.js +166 -37
  39. package/dist/apply-persist.js.map +1 -1
  40. package/dist/apply-stuck-action.d.ts +151 -0
  41. package/dist/apply-stuck-action.d.ts.map +1 -0
  42. package/dist/apply-stuck-action.js +125 -0
  43. package/dist/apply-stuck-action.js.map +1 -0
  44. package/dist/brand.d.ts +12 -1
  45. package/dist/brand.d.ts.map +1 -1
  46. package/dist/brand.js +2 -1
  47. package/dist/brand.js.map +1 -1
  48. package/dist/cli-spinner.d.ts +1 -1
  49. package/dist/cli-spinner.js +1 -1
  50. package/dist/cli.d.ts +10 -0
  51. package/dist/cli.d.ts.map +1 -1
  52. package/dist/cli.js +320 -63
  53. package/dist/cli.js.map +1 -1
  54. package/dist/close-job-template.js +1 -1
  55. package/dist/complete.d.ts +6 -3
  56. package/dist/complete.d.ts.map +1 -1
  57. package/dist/complete.js +91 -37
  58. package/dist/complete.js.map +1 -1
  59. package/dist/config.d.ts +55 -9
  60. package/dist/config.d.ts.map +1 -1
  61. package/dist/config.js +56 -4
  62. package/dist/config.js.map +1 -1
  63. package/dist/continue-branch.d.ts.map +1 -1
  64. package/dist/continue-branch.js +22 -0
  65. package/dist/continue-branch.js.map +1 -1
  66. package/dist/decision-engine.d.ts +38 -10
  67. package/dist/decision-engine.d.ts.map +1 -1
  68. package/dist/decision-engine.js +10 -6
  69. package/dist/decision-engine.js.map +1 -1
  70. package/dist/do-autopick.d.ts.map +1 -1
  71. package/dist/do-autopick.js +11 -1
  72. package/dist/do-autopick.js.map +1 -1
  73. package/dist/do-config.d.ts +1 -1
  74. package/dist/do.d.ts +49 -56
  75. package/dist/do.d.ts.map +1 -1
  76. package/dist/do.js +419 -30
  77. package/dist/do.js.map +1 -1
  78. package/dist/env-config.d.ts.map +1 -1
  79. package/dist/env-config.js +12 -4
  80. package/dist/env-config.js.map +1 -1
  81. package/dist/failure-cause.d.ts +3 -2
  82. package/dist/failure-cause.d.ts.map +1 -1
  83. package/dist/failure-cause.js +28 -1
  84. package/dist/failure-cause.js.map +1 -1
  85. package/dist/format.d.ts +6 -6
  86. package/dist/format.d.ts.map +1 -1
  87. package/dist/format.js +10 -27
  88. package/dist/format.js.map +1 -1
  89. package/dist/frontmatter.d.ts +16 -4
  90. package/dist/frontmatter.d.ts.map +1 -1
  91. package/dist/frontmatter.js +27 -1
  92. package/dist/frontmatter.js.map +1 -1
  93. package/dist/gc.d.ts +65 -6
  94. package/dist/gc.d.ts.map +1 -1
  95. package/dist/gc.js +126 -9
  96. package/dist/gc.js.map +1 -1
  97. package/dist/github.d.ts +14 -0
  98. package/dist/github.d.ts.map +1 -1
  99. package/dist/github.js +73 -0
  100. package/dist/github.js.map +1 -1
  101. package/dist/harness.d.ts +25 -0
  102. package/dist/harness.d.ts.map +1 -1
  103. package/dist/harness.js.map +1 -1
  104. package/dist/index.d.ts +4 -1
  105. package/dist/index.d.ts.map +1 -1
  106. package/dist/index.js +2 -1
  107. package/dist/index.js.map +1 -1
  108. package/dist/install-ci-branch-protection.d.ts +132 -38
  109. package/dist/install-ci-branch-protection.d.ts.map +1 -1
  110. package/dist/install-ci-branch-protection.js +190 -48
  111. package/dist/install-ci-branch-protection.js.map +1 -1
  112. package/dist/install-ci-core.d.ts +21 -1
  113. package/dist/install-ci-core.d.ts.map +1 -1
  114. package/dist/install-ci-core.js +10 -3
  115. package/dist/install-ci-core.js.map +1 -1
  116. package/dist/install-ci-github.d.ts +32 -0
  117. package/dist/install-ci-github.d.ts.map +1 -1
  118. package/dist/install-ci-github.js +73 -0
  119. package/dist/install-ci-github.js.map +1 -1
  120. package/dist/install-skills.d.ts +123 -0
  121. package/dist/install-skills.d.ts.map +1 -0
  122. package/dist/install-skills.js +100 -0
  123. package/dist/install-skills.js.map +1 -0
  124. package/dist/intake-trigger-template.js +1 -1
  125. package/dist/intake.d.ts +17 -4
  126. package/dist/intake.d.ts.map +1 -1
  127. package/dist/intake.js +23 -7
  128. package/dist/intake.js.map +1 -1
  129. package/dist/integration-core.d.ts +10 -7
  130. package/dist/integration-core.d.ts.map +1 -1
  131. package/dist/integration-core.js +174 -60
  132. package/dist/integration-core.js.map +1 -1
  133. package/dist/integrator.d.ts +1 -1
  134. package/dist/integrator.d.ts.map +1 -1
  135. package/dist/integrator.js +19 -3
  136. package/dist/integrator.js.map +1 -1
  137. package/dist/isolation.d.ts +2 -2
  138. package/dist/isolation.d.ts.map +1 -1
  139. package/dist/isolation.js +9 -0
  140. package/dist/isolation.js.map +1 -1
  141. package/dist/item-lock.d.ts +196 -129
  142. package/dist/item-lock.d.ts.map +1 -1
  143. package/dist/item-lock.js +329 -254
  144. package/dist/item-lock.js.map +1 -1
  145. package/dist/ledger-write.d.ts +31 -26
  146. package/dist/ledger-write.d.ts.map +1 -1
  147. package/dist/ledger-write.js +100 -122
  148. package/dist/ledger-write.js.map +1 -1
  149. package/dist/lifecycle-gather.d.ts +17 -0
  150. package/dist/lifecycle-gather.d.ts.map +1 -1
  151. package/dist/lifecycle-gather.js +7 -1
  152. package/dist/lifecycle-gather.js.map +1 -1
  153. package/dist/lifecycle-pools.d.ts +40 -3
  154. package/dist/lifecycle-pools.d.ts.map +1 -1
  155. package/dist/lifecycle-pools.js +26 -6
  156. package/dist/lifecycle-pools.js.map +1 -1
  157. package/dist/merge-question-surfacer.d.ts +15 -1
  158. package/dist/merge-question-surfacer.d.ts.map +1 -1
  159. package/dist/merge-question-surfacer.js +18 -3
  160. package/dist/merge-question-surfacer.js.map +1 -1
  161. package/dist/migrate-stuck-locks.d.ts +129 -0
  162. package/dist/migrate-stuck-locks.d.ts.map +1 -0
  163. package/dist/migrate-stuck-locks.js +355 -0
  164. package/dist/migrate-stuck-locks.js.map +1 -0
  165. package/dist/needs-attention.d.ts +293 -16
  166. package/dist/needs-attention.d.ts.map +1 -1
  167. package/dist/needs-attention.js +532 -57
  168. package/dist/needs-attention.js.map +1 -1
  169. package/dist/orphan-sidecar.d.ts +8 -4
  170. package/dist/orphan-sidecar.d.ts.map +1 -1
  171. package/dist/orphan-sidecar.js +35 -2
  172. package/dist/orphan-sidecar.js.map +1 -1
  173. package/dist/pi-harness.d.ts +16 -0
  174. package/dist/pi-harness.d.ts.map +1 -1
  175. package/dist/pi-harness.js +82 -2
  176. package/dist/pi-harness.js.map +1 -1
  177. package/dist/placement.d.ts +3 -3
  178. package/dist/placement.js +1 -1
  179. package/dist/prd-to-spec.d.ts.map +1 -1
  180. package/dist/prd-to-spec.js +9 -5
  181. package/dist/prd-to-spec.js.map +1 -1
  182. package/dist/protocol/CLAIM-PROTOCOL.md +17 -10
  183. package/dist/protocol/REVIEW-PROTOCOL.md +4 -1
  184. package/dist/protocol/SURFACE-PROTOCOL.md +16 -2
  185. package/dist/protocol/TASKING-PROTOCOL.md +3 -1
  186. package/dist/protocol/WORK-CONTRACT.md +22 -18
  187. package/dist/protocol/task-template.md +1 -1
  188. package/dist/reap-branches.d.ts +12 -9
  189. package/dist/reap-branches.d.ts.map +1 -1
  190. package/dist/reap-branches.js +25 -7
  191. package/dist/reap-branches.js.map +1 -1
  192. package/dist/recover-isolated.d.ts +12 -0
  193. package/dist/recover-isolated.d.ts.map +1 -1
  194. package/dist/recover-isolated.js +5 -0
  195. package/dist/recover-isolated.js.map +1 -1
  196. package/dist/repo-config.d.ts +23 -2
  197. package/dist/repo-config.d.ts.map +1 -1
  198. package/dist/repo-config.js +53 -7
  199. package/dist/repo-config.js.map +1 -1
  200. package/dist/repo-mirror.d.ts.map +1 -1
  201. package/dist/repo-mirror.js +18 -2
  202. package/dist/repo-mirror.js.map +1 -1
  203. package/dist/review-verdict.js +1 -1
  204. package/dist/review-verdict.js.map +1 -1
  205. package/dist/run.d.ts +1 -1
  206. package/dist/run.d.ts.map +1 -1
  207. package/dist/run.js +54 -19
  208. package/dist/run.js.map +1 -1
  209. package/dist/scan.d.ts +17 -8
  210. package/dist/scan.d.ts.map +1 -1
  211. package/dist/scan.js +22 -8
  212. package/dist/scan.js.map +1 -1
  213. package/dist/select-priority.d.ts +2 -2
  214. package/dist/select-priority.js +1 -1
  215. package/dist/select-priority.js.map +1 -1
  216. package/dist/sidecar-apply.js +1 -1
  217. package/dist/sidecar-apply.js.map +1 -1
  218. package/dist/sidecar.d.ts +45 -10
  219. package/dist/sidecar.d.ts.map +1 -1
  220. package/dist/sidecar.js +82 -2
  221. package/dist/sidecar.js.map +1 -1
  222. package/dist/skills/answer-questions/SKILL.md +89 -0
  223. package/dist/skills/capture-signal/SKILL.md +52 -0
  224. package/dist/skills/convert-from-prd-to-spec/SKILL.md +90 -0
  225. package/dist/skills/drive-tasks/SKILL.md +218 -0
  226. package/dist/skills/from-idea/SKILL.md +83 -0
  227. package/dist/skills/merge-prs/SKILL.md +70 -0
  228. package/dist/skills/orchestrate/SKILL.md +101 -0
  229. package/dist/skills/promote/SKILL.md +35 -0
  230. package/dist/skills/review/SKILL.md +16 -0
  231. package/dist/skills/setup/SKILL.md +258 -0
  232. package/dist/skills/setup/protocol/ADR-FORMAT.md +47 -0
  233. package/dist/skills/setup/protocol/CLAIM-PROTOCOL.md +224 -0
  234. package/dist/skills/setup/protocol/REVIEW-PROTOCOL.md +122 -0
  235. package/dist/skills/setup/protocol/SURFACE-PROTOCOL.md +135 -0
  236. package/dist/skills/setup/protocol/TASKING-PROTOCOL.md +124 -0
  237. package/dist/skills/setup/protocol/WORK-CONTRACT.md +280 -0
  238. package/dist/skills/setup/protocol/spec-template.md +71 -0
  239. package/dist/skills/setup/protocol/task-template.md +65 -0
  240. package/dist/skills/surface-questions/SKILL.md +16 -0
  241. package/dist/skills/to-spec/SKILL.md +34 -0
  242. package/dist/skills/to-task/SKILL.md +19 -0
  243. package/dist/skills/triage-observations/SKILL.md +78 -0
  244. package/dist/skills/work/SKILL.md +51 -0
  245. package/dist/slug-namespace.d.ts +5 -5
  246. package/dist/slug-namespace.js +5 -5
  247. package/dist/slug-namespace.js.map +1 -1
  248. package/dist/spec-complete.d.ts.map +1 -1
  249. package/dist/spec-complete.js +2 -3
  250. package/dist/spec-complete.js.map +1 -1
  251. package/dist/start.d.ts.map +1 -1
  252. package/dist/start.js +51 -57
  253. package/dist/start.js.map +1 -1
  254. package/dist/surface-gate.d.ts.map +1 -1
  255. package/dist/surface-gate.js +10 -3
  256. package/dist/surface-gate.js.map +1 -1
  257. package/dist/surface-persist.js +1 -1
  258. package/dist/surface-persist.js.map +1 -1
  259. package/dist/tasker-review-loop.d.ts +4 -3
  260. package/dist/tasker-review-loop.d.ts.map +1 -1
  261. package/dist/tasker-review-loop.js.map +1 -1
  262. package/dist/tasking-lock.d.ts +2 -2
  263. package/dist/tasking-lock.d.ts.map +1 -1
  264. package/dist/tasking-lock.js +53 -32
  265. package/dist/tasking-lock.js.map +1 -1
  266. package/dist/tasking.d.ts +40 -6
  267. package/dist/tasking.d.ts.map +1 -1
  268. package/dist/tasking.js +224 -12
  269. package/dist/tasking.js.map +1 -1
  270. package/dist/triage-persist.d.ts +10 -6
  271. package/dist/triage-persist.d.ts.map +1 -1
  272. package/dist/triage-persist.js +34 -4
  273. package/dist/triage-persist.js.map +1 -1
  274. package/dist/vendor/incur/agents.d.ts +58 -0
  275. package/dist/vendor/incur/agents.d.ts.map +1 -0
  276. package/dist/vendor/incur/agents.js +343 -0
  277. package/dist/vendor/incur/agents.js.map +1 -0
  278. package/dist/verify-workflow-template.js +1 -1
  279. package/dist/watch-session.d.ts +11 -3
  280. package/dist/watch-session.d.ts.map +1 -1
  281. package/dist/watch-session.js +94 -7
  282. package/dist/watch-session.js.map +1 -1
  283. package/dist/work-layout.d.ts +7 -3
  284. package/dist/work-layout.d.ts.map +1 -1
  285. package/dist/work-layout.js +6 -3
  286. package/dist/work-layout.js.map +1 -1
  287. package/dist/workspace.d.ts +1 -1
  288. package/package.json +2 -2
  289. package/src/advance-drivers.ts +16 -3
  290. package/src/advance-lifecycle-template.ts +89 -4
  291. package/src/advance-treeless-publish.ts +45 -0
  292. package/src/advance.ts +467 -87
  293. package/src/advancing-lock.ts +96 -1
  294. package/src/agent-launch.ts +37 -12
  295. package/src/agent-stop.ts +60 -2
  296. package/src/apply-decide.ts +36 -7
  297. package/src/apply-merge-action.ts +45 -7
  298. package/src/apply-persist.ts +228 -54
  299. package/src/apply-stuck-action.ts +260 -0
  300. package/src/brand.ts +14 -2
  301. package/src/cli-spinner.ts +1 -1
  302. package/src/cli.ts +401 -70
  303. package/src/close-job-template.ts +1 -1
  304. package/src/complete.ts +110 -52
  305. package/src/config.ts +107 -13
  306. package/src/continue-branch.ts +22 -0
  307. package/src/decision-engine.ts +55 -17
  308. package/src/do-autopick.ts +12 -1
  309. package/src/do-config.ts +1 -1
  310. package/src/do.ts +511 -38
  311. package/src/env-config.ts +12 -4
  312. package/src/failure-cause.ts +30 -1
  313. package/src/format.ts +10 -30
  314. package/src/frontmatter.ts +35 -5
  315. package/src/gc.ts +172 -9
  316. package/src/github.ts +78 -0
  317. package/src/harness.ts +25 -0
  318. package/src/index.ts +10 -0
  319. package/src/install-ci-branch-protection.ts +282 -57
  320. package/src/install-ci-core.ts +30 -3
  321. package/src/install-ci-github.ts +86 -0
  322. package/src/install-skills.ts +166 -0
  323. package/src/intake-trigger-template.ts +1 -1
  324. package/src/intake.ts +46 -11
  325. package/src/integration-core.ts +199 -65
  326. package/src/integrator.ts +22 -9
  327. package/src/isolation.ts +11 -2
  328. package/src/item-lock.ts +485 -341
  329. package/src/ledger-write.ts +135 -150
  330. package/src/lifecycle-gather.ts +29 -6
  331. package/src/lifecycle-pools.ts +67 -9
  332. package/src/merge-question-surfacer.ts +28 -4
  333. package/src/migrate-stuck-locks.ts +451 -0
  334. package/src/needs-attention.ts +857 -84
  335. package/src/orphan-sidecar.ts +47 -6
  336. package/src/pi-harness.ts +82 -2
  337. package/src/placement.ts +3 -3
  338. package/src/prd-to-spec.ts +13 -5
  339. package/src/reap-branches.ts +38 -14
  340. package/src/recover-isolated.ts +17 -0
  341. package/src/repo-config.ts +55 -6
  342. package/src/repo-mirror.ts +23 -1
  343. package/src/review-verdict.ts +1 -1
  344. package/src/run.ts +64 -18
  345. package/src/scan.ts +22 -8
  346. package/src/select-priority.ts +3 -3
  347. package/src/sidecar-apply.ts +1 -1
  348. package/src/sidecar.ts +138 -11
  349. package/src/slug-namespace.ts +7 -7
  350. package/src/spec-complete.ts +2 -3
  351. package/src/start.ts +53 -59
  352. package/src/surface-gate.ts +10 -3
  353. package/src/surface-persist.ts +1 -1
  354. package/src/tasker-review-loop.ts +4 -3
  355. package/src/tasking-lock.ts +52 -36
  356. package/src/tasking.ts +261 -17
  357. package/src/triage-persist.ts +56 -12
  358. package/src/vendor/incur/LICENSE +21 -0
  359. package/src/vendor/incur/README.md +19 -0
  360. package/src/vendor/incur/agents.ts +392 -0
  361. package/src/verify-workflow-template.ts +1 -1
  362. package/src/watch-session.ts +102 -7
  363. package/src/work-layout.ts +6 -3
  364. package/src/workspace.ts +1 -1
@@ -171,6 +171,19 @@ export async function performIntegration(input) {
171
171
  recoveryRebaseJitterMs: input.recoveryRebaseJitterMs,
172
172
  recoveryRebaseSleep: input.recoveryRebaseSleep,
173
173
  recoveryRebaseRandom: input.recoveryRebaseRandom,
174
+ // GATE-2 REVIEW on the recovery path (task
175
+ // `committed-recovery-always-reviews`): a stranded already-complete
176
+ // branch is NOT "already reviewed" — its earlier attempt's PR never
177
+ // merged and Gate 2 may never have run on it. So when `review` is on we
178
+ // MUST run Gate 2 on the rebased tip here too (the SAME reasoning that
179
+ // forces the re-verify: `<arbiter>/main` moved, the merged tree is new).
180
+ // Pass the whole `input` so the recovery tail can call `runGate2Review`
181
+ // with the identical gate/model/watch/round wiring the build path uses.
182
+ input,
183
+ // PR BODY on the recovery path: the recovery has NO fresh agent output
184
+ // (no rebuild), so carry whatever `body` the caller supplied so the PR
185
+ // gets a description instead of degrading to a blank `gh --fill`.
186
+ body: input.body,
174
187
  // Answered-merge land (task `committed-recovery-honours-fresh-worktree-gate`,
175
188
  // spec `land-time-reverify-and-parallel-merge-ceiling`): the apply-rung
176
189
  // dispatches an answered `merge` through this committed-recovery tail (the
@@ -222,7 +235,7 @@ export async function performIntegration(input) {
222
235
  // passes no flag, so there an untrusted-origin task RELIABLY forces propose.
223
236
  //
224
237
  // SCOPE: the task BUILD transition ONLY. It NEVER fires for a `lifecycle`
225
- // transition (tasking / intake-emit — a prd/task FILE landing on main is inert;
238
+ // transition (tasking / intake-emit — a spec/task FILE landing on main is inert;
226
239
  // intake's OWN per-emit resolver already decided that mode), and the source file
227
240
  // here is the task being built. A `trusted`/unset task ⇒ untouched (zero
228
241
  // behaviour change for the normal human path).
@@ -270,10 +283,11 @@ export async function performIntegration(input) {
270
283
  }
271
284
  if (!prep.passed) {
272
285
  const reason = `prepare (env-prep) failed (exit ${prep.exitCode})`;
273
- // Bounce from in-progress/ straight to needs-attention/ (the SAME seam a red
274
- // gate uses) — recording the prepare-failed reason + committing the move
275
- // (with the agent's uncommitted work) as ONE atomic transition. We NEVER
276
- // run `verify` on an env that could not be made ready.
286
+ // Mark the item stuck on its per-item lock (the SAME seam a red gate uses)
287
+ // — recording the prepare-failed reason on the lock entry + saving the
288
+ // agent's uncommitted work as a wip commit (post lock-cutover: a lock amend,
289
+ // no `in-progress/ needs-attention/` folder move). We NEVER run `verify`
290
+ // on an env that could not be made ready.
277
291
  const routed = await ledgerWrite.applyNeedsAttentionTransition({
278
292
  cwd,
279
293
  slug,
@@ -287,10 +301,10 @@ export async function performIntegration(input) {
287
301
  routedToNeedsAttention: routed.moved,
288
302
  branch,
289
303
  reason: routed.moved
290
- ? `Env-prep (prepare) failed (exit ${prep.exitCode}); routed '${slug}' ` +
291
- 'to work/needs-attention/ (the environment could not be made ready, ' +
304
+ ? `Env-prep (prepare) failed (exit ${prep.exitCode}); marked '${slug}' ` +
305
+ 'stuck on its per-item lock (the environment could not be made ready, ' +
292
306
  'so the acceptance gate was NOT run). Fix the prepare command, then ' +
293
- 'return it to backlog/.'
307
+ '`requeue` to release the stuck lock.'
294
308
  : `Env-prep (prepare) failed (exit ${prep.exitCode}); not completing ` +
295
309
  `'${slug}' (the environment could not be made ready, so the ` +
296
310
  'acceptance gate was NOT run). Fix the prepare command, then retry.',
@@ -309,12 +323,12 @@ export async function performIntegration(input) {
309
323
  note('Running the acceptance gate (verify)…');
310
324
  const gate = await runVerify({ cwd, verify: input.verify, env });
311
325
  if (!gate.passed) {
312
- // Don't leave the item dangling in in-progress/: route it to
313
- // needs-attention/ with the reason (ADR §12) THROUGH the ledger write
314
- // seam's needs-attention transition. The item has NOT been committed/moved
315
- // yet, so the move bounces it straight from in-progress/ recording the
316
- // reason + committing the move (with the agent's uncommitted work) as ONE
317
- // atomic transition. No partial state.
326
+ // Don't leave the item dangling: mark it stuck on its per-item lock with
327
+ // the reason (ADR §12) THROUGH the ledger write seam's needs-attention
328
+ // transition. Post lock-cutover this is a lock amend (`state: stuck` + the
329
+ // reason on the lock entry, no `in-progress/ needs-attention/` folder
330
+ // move) plus saving the agent's uncommitted work as a wip commit. No
331
+ // partial state.
318
332
  const reason = `acceptance gate failed (exit ${gate.exitCode})`;
319
333
  const routed = await ledgerWrite.applyNeedsAttentionTransition({
320
334
  cwd,
@@ -337,9 +351,9 @@ export async function performIntegration(input) {
337
351
  routedToNeedsAttention: routed.moved,
338
352
  branch,
339
353
  reason: routed.moved
340
- ? `Acceptance gate failed (exit ${gate.exitCode}); routed '${slug}' ` +
341
- 'to work/needs-attention/ (surfaced by status; return to backlog/ ' +
342
- 'once resolved). Fix the work, or use --skip-verify to override.'
354
+ ? `Acceptance gate failed (exit ${gate.exitCode}); marked '${slug}' ` +
355
+ 'stuck on its per-item lock (surfaced by status; `requeue` once ' +
356
+ 'resolved). Fix the work, or use --skip-verify to override.'
343
357
  : `Acceptance gate failed (exit ${gate.exitCode}); not completing ` +
344
358
  `'${slug}'. Fix the work, or use --skip-verify to override.`,
345
359
  };
@@ -501,16 +515,14 @@ export async function performIntegration(input) {
501
515
  // 4. Rebase-before-integrate (ADR §10): rebase the work branch onto the
502
516
  // latest <arbiter>/main. Clean → continue. Conflict → abort + stop.
503
517
  //
504
- // RECOVERY reconciliation: when completing FROM needs-attention/, the work
505
- // branch's history still carries the original `in-progress needs-attention`
506
- // MOVE-ONLY commit, and `<arbiter>/main` was SURFACED with that same move
507
- // (the item is in needs-attention/ on main). Replaying that historical move
508
- // onto main conflicts (main has no in-progress/<slug>.md) exactly the
509
- // rebase conflict the human hit doing this by hand. So we DROP that move-only
510
- // commit during the rebase: the replay becomes `wip + (needs-attention →
511
- // done)`, which applies cleanly onto the surfaced main (it HAS the item in
512
- // needs-attention/). The done-move thus SUPERSEDES the surfaced state — no
513
- // leftover/conflicting on-`main` surface for the human to resolve.
518
+ // RECOVERY reconciliation: post the per-item-lock cut-over, a stuck item's
519
+ // body never moved into a `needs-attention/` folder (stuck is the lock
520
+ // `state: stuck`; the body rests in `backlog/`), so a recovery `complete`'s
521
+ // kept branch carries NO historical `in-progress needs-attention`
522
+ // move-only commit to drop and `<arbiter>/main` holds no surface move to
523
+ // conflict with. The old recovery drop (`rebaseDroppingNeedsAttentionSurface`,
524
+ // drop-bookkeeping-rebase) is deleted; recovery is now the SAME plain replay
525
+ // onto `<arbiter>/main` as any other build (see the plain-rebase note below).
514
526
  // Fetch the arbiter's `main` into the `<arbiter>/main` remote-tracking ref
515
527
  // EXPLICITLY. A `run` JOB WORKTREE is cut from a bare hub mirror whose remote
516
528
  // has no fetch refspec (so `<arbiter>/main` would not otherwise resolve / would
@@ -656,12 +668,12 @@ export async function performIntegration(input) {
656
668
  // recovery above can fall through to integrate while a genuine code conflict
657
669
  // still routes here.
658
670
  async function rebaseConflictRoute() {
659
- // Then route the item to needs-attention/ with the conflict reason (ADR
660
- // §12) THROUGH the ledger write seam's needs-attention transition, rather
661
- // than leaving it dangling in done/. The done-move was already committed
662
- // above, so the item sits in work/done/; the move bounces it from there and
663
- // commits the in-progress→needs-attention move (here done→needs-attention)
664
- // as ONE transition. No partial state.
671
+ // Then mark the item stuck on its per-item lock with the conflict reason
672
+ // (ADR §12) THROUGH the ledger write seam's needs-attention transition,
673
+ // rather than leaving it dangling. Post lock-cutover this is a lock amend
674
+ // (`state: stuck` + the reason on the lock entry, no `done/
675
+ // needs-attention/` folder move); the done-move already committed above
676
+ // stands. No partial state.
665
677
  const reason = `rebase onto ${arbiter}/main conflicted (aborted, never auto-resolved)`;
666
678
  const routed = await ledgerWrite.applyNeedsAttentionTransition({
667
679
  cwd,
@@ -683,9 +695,9 @@ export async function performIntegration(input) {
683
695
  commitMessage,
684
696
  reason: routed.moved
685
697
  ? `Rebasing ${branch} onto ${arbiter}/main conflicted; the rebase was ` +
686
- `aborted (never auto-resolved) and '${slug}' was routed to ` +
687
- 'work/needs-attention/ (surfaced by status). Resolve against the ' +
688
- 'latest main, then return it to backlog/ and re-run.'
698
+ `aborted (never auto-resolved) and '${slug}' was marked stuck on ` +
699
+ 'its per-item lock (surfaced by status). Resolve against the latest ' +
700
+ 'main, then `requeue` to release the stuck lock and re-run.'
689
701
  : `Rebasing ${branch} onto ${arbiter}/main conflicted; the rebase was ` +
690
702
  'aborted (never auto-resolved). Resolve against the latest main, ' +
691
703
  'then re-run complete.',
@@ -758,9 +770,9 @@ export async function performIntegration(input) {
758
770
  branch,
759
771
  commitMessage,
760
772
  reason: routed.moved
761
- ? `${what} on the rebased tip; routed '${slug}' to ` +
762
- 'work/needs-attention/ (surfaced by status; return to backlog/ ' +
763
- 'once resolved). Fix the work, or use --skip-verify to override.'
773
+ ? `${what} on the rebased tip; marked '${slug}' stuck on its ` +
774
+ 'per-item lock (surfaced by status; `requeue` once resolved). ' +
775
+ 'Fix the work, or use --skip-verify to override.'
764
776
  : `${what} on the rebased tip; not completing '${slug}'. Fix the ` +
765
777
  'work, or use --skip-verify to override.',
766
778
  };
@@ -775,7 +787,8 @@ export async function performIntegration(input) {
775
787
  if (gated.review) {
776
788
  if (gated.review.kind === 'blocked') {
777
789
  // The done-move was already committed (steps 2–3), so the slug sits in
778
- // work/done/; the routing bounced it from there to needs-attention/.
790
+ // work/done/; the routing marked it stuck on its per-item lock (post
791
+ // lock-cutover — a lock amend, no folder move).
779
792
  return gated.review.result;
780
793
  }
781
794
  approvedVerdict = gated.review.verdict;
@@ -936,9 +949,10 @@ export async function performIntegration(input) {
936
949
  commitMessage,
937
950
  reason: routed.moved
938
951
  ? `Integrating ${branch} onto ${arbiter}/main kept hitting a ` +
939
- `non-fast-forward push (a sibling advanced main); '${slug}' was routed ` +
940
- `to work/needs-attention/ (surfaced by status). Resolve against the ` +
941
- `latest main, then return it to backlog/ and re-run.`
952
+ `non-fast-forward push (a sibling advanced main); '${slug}' was ` +
953
+ `marked stuck on its per-item lock (surfaced by status). Resolve ` +
954
+ `against the latest main, then \`requeue\` to release the stuck ` +
955
+ `lock and re-run.`
942
956
  : `Integrating ${branch} onto ${arbiter}/main kept hitting a ` +
943
957
  `non-fast-forward push (a sibling advanced main). Resolve against the ` +
944
958
  `latest main, then re-run complete.`,
@@ -1017,7 +1031,7 @@ export async function performIntegration(input) {
1017
1031
  * primitive the build path uses.
1018
1032
  *
1019
1033
  * RE-GATE on the REBASED TIP (task `committed-recovery-honours-fresh-worktree-
1020
- * gate`, prd `land-time-reverify-and-parallel-merge-ceiling`): when the caller
1034
+ * gate`, spec `land-time-reverify-and-parallel-merge-ceiling`): when the caller
1021
1035
  * sets `freshWorktreeGate` (and not `skipVerify`), the EXISTING
1022
1036
  * `runFreshWorktreeGate` runs on the rebased tip AFTER the rebase loop and
1023
1037
  * BEFORE `applyCompleteTransition`, mirroring the build path's
@@ -1071,6 +1085,14 @@ async function recoverAlreadyCommitted(params) {
1071
1085
  // (`isAncestor` is the SAME reachability predicate `gc.ts` uses; do not fork it.)
1072
1086
  // KEPT before the retry loop: a no-op MUST short-circuit before we burn any
1073
1087
  // re-fetch/re-rebase budget.
1088
+ //
1089
+ // DELIBERATELY the RAW ancestry predicate, NOT `isProvablyMergedForReap`
1090
+ // (task `reap-squash-merged-remote-work-branches`, ratified): this is an
1091
+ // integration-IDEMPOTENCY check, not reap-safety. A squash-landed LOOKALIKE
1092
+ // whose tip is NOT genuinely reachable would falsely read "already
1093
+ // integrated" here and skip a needed rebase/re-push. The squash-aware helper
1094
+ // is scoped to reap-safety ONLY; see gc.ts `isProvablyMergedForReap` and
1095
+ // work/notes/observations/reap-squash-helper-scope-2026-07-10.md.
1074
1096
  if (isAncestor(cwd, tip, `refs/remotes/${arbiter}/main`, env)) {
1075
1097
  const message = `Nothing to recover for '${slug}': its work branch tip is already on ` +
1076
1098
  `${arbiter}/main (already integrated). No re-push, no double-integrate.`;
@@ -1162,7 +1184,7 @@ async function recoverAlreadyCommitted(params) {
1162
1184
  attempt++;
1163
1185
  }
1164
1186
  // FRESH-WORKTREE GATE on the REBASED TIP (task `committed-recovery-honours-
1165
- // fresh-worktree-gate`, prd `land-time-reverify-and-parallel-merge-ceiling`):
1187
+ // fresh-worktree-gate`, spec `land-time-reverify-and-parallel-merge-ceiling`):
1166
1188
  // when `freshWorktreeGate` is set (the answered-merge land caller) and not
1167
1189
  // `--skip-verify`, re-run the acceptance gate on the rebased tip BEFORE we
1168
1190
  // integrate, mirroring the build path's `freshWorktreeGate && !skipVerify &&
@@ -1173,11 +1195,21 @@ async function recoverAlreadyCommitted(params) {
1173
1195
  // clean-rebase-but-broken merge. With `freshWorktreeGate` UNSET (the original
1174
1196
  // stranded-recovery caller, whose pre-strand build already gated) this whole
1175
1197
  // block is skipped and behaviour is byte-identical to before.
1198
+ // Carry an approved Gate-2 verdict (when review ran) so its authored `review`
1199
+ // prose is posted as a PR comment AFTER the integrate below — the SAME audit
1200
+ // trail the build path keeps (task `committed-recovery-always-reviews`).
1201
+ let approvedVerdict;
1176
1202
  if (params.freshWorktreeGate && !params.skipVerify) {
1177
1203
  const tip = (await gitSoft(['rev-parse', '--verify', '--quiet', 'HEAD'], cwd, env)).stdout.trim();
1178
- // No `review:` callback here: the recovery tail re-verifies an already-
1179
- // reviewed, already-committed result, so Gate-2 review semantics do not
1180
- // apply on this path.
1204
+ // GATE-2 REVIEW callback (task `committed-recovery-always-reviews`): when
1205
+ // `input.review` is on, run Gate 2 on the rebased tip AFTER the green verify
1206
+ // EXACTLY the build path's fresh-gate `review:` callback. A stranded
1207
+ // already-complete branch is NOT trusted as already-reviewed (its earlier
1208
+ // attempt's PR never merged, and Gate 2 may never have run on it); the same
1209
+ // reasoning that forces the re-verify here (`<arbiter>/main` moved, so the
1210
+ // merged tree is new) forces the re-review. The needs-attention ROUTING
1211
+ // targets `cwd` (the work branch + ledger), identical to the build path.
1212
+ const reviewInput = params.input;
1181
1213
  const gated = await runFreshWorktreeGate({
1182
1214
  cwd,
1183
1215
  commit: tip,
@@ -1185,6 +1217,17 @@ async function recoverAlreadyCommitted(params) {
1185
1217
  verify: params.verify,
1186
1218
  env,
1187
1219
  note,
1220
+ review: reviewInput?.review === true
1221
+ ? (reviewCwd) => runGate2Review({
1222
+ reviewCwd,
1223
+ input: reviewInput,
1224
+ slug,
1225
+ branch,
1226
+ cwd,
1227
+ env,
1228
+ note,
1229
+ })
1230
+ : undefined,
1188
1231
  });
1189
1232
  if (!gated.passed) {
1190
1233
  const outcome = gated.kind === 'prepare' ? 'prepare-failed' : 'gate-failed';
@@ -1207,15 +1250,43 @@ async function recoverAlreadyCommitted(params) {
1207
1250
  routedToNeedsAttention: routed.moved,
1208
1251
  branch,
1209
1252
  reason: routed.moved
1210
- ? `${what} on the rebased tip during committed-recovery; routed ` +
1211
- `'${slug}' to work/needs-attention/ (surfaced by status; return ` +
1212
- 'to backlog/ once resolved). Fix the work, or use --skip-verify ' +
1253
+ ? `${what} on the rebased tip during committed-recovery; marked ` +
1254
+ `'${slug}' stuck on its per-item lock (surfaced by status; ` +
1255
+ '`requeue` once resolved). Fix the work, or use --skip-verify ' +
1213
1256
  'to override.'
1214
1257
  : `${what} on the rebased tip during committed-recovery; not ` +
1215
1258
  `completing '${slug}'. Fix the work, or use --skip-verify to ` +
1216
1259
  'override.',
1217
1260
  };
1218
1261
  }
1262
+ // GATE-2 REVIEW outcome on the rebased tip: a BLOCK routes to needs-attention
1263
+ // exactly as the build path (only the reviewed tree moved, not the routing).
1264
+ // On APPROVE: carry the verdict for the post-integrate PR comment and write
1265
+ // the per-run non-blocking-nits observation. UNLIKE the build path this branch
1266
+ // integrates the ALREADY-committed kept tip (no upcoming done-move/commit to
1267
+ // sweep the nits file into), so we FOLD the observation into the kept commit
1268
+ // via `commit --amend` — mirroring the build path's fresh-gate amend so the
1269
+ // nits still land in the SAME commit that integrates (no separate commit).
1270
+ if (gated.review) {
1271
+ if (gated.review.kind === 'blocked') {
1272
+ return gated.review.result;
1273
+ }
1274
+ approvedVerdict = gated.review.verdict;
1275
+ const nitsBefore = await stagedCaptureNotes(cwd, env);
1276
+ writeReviewNitsObservation({
1277
+ cwd,
1278
+ slug,
1279
+ findings: gated.review.verdict?.findings ?? [],
1280
+ note,
1281
+ });
1282
+ await gitHard(['add', '-A'], cwd, env);
1283
+ if (!(await nothingStaged(cwd, env))) {
1284
+ const nitsAfter = await stagedCaptureNotes(cwd, env);
1285
+ if (nitsAfter.length > nitsBefore.length) {
1286
+ await gitHard(['commit', '-q', '--amend', '--no-edit'], cwd, env);
1287
+ }
1288
+ }
1289
+ }
1219
1290
  }
1220
1291
  // Integrate the rebased kept commit through the SAME complete-transition
1221
1292
  // primitive the build path uses (no duplication of the integrate mechanism; the
@@ -1226,12 +1297,27 @@ async function recoverAlreadyCommitted(params) {
1226
1297
  (params.openPr
1227
1298
  ? bridgeProvider(params.openPr)
1228
1299
  : selectProvider({ arbiterUrl: await arbiterUrl(cwd, arbiter, env) }));
1300
+ // PR TITLE + BODY on the recovery path (task `committed-recovery-always-
1301
+ // reviews`): the stranded branch already holds the task in `work/done/`, so
1302
+ // synthesise the SAME single-line `<type>(<slug>): <title>` PR title the build
1303
+ // path does (from the done-resident task file, never agent text) and scaffold
1304
+ // the task-pointer body around the caller-supplied advisory `body`. Undefined
1305
+ // body ⇒ `composeProposeBody` returns undefined ⇒ the provider degrades to
1306
+ // `gh --fill` (no regression); a supplied body gives the PR a real description
1307
+ // instead of the blank one the recovery path used to open.
1308
+ const prTitle = synthesiseProposeTitle({
1309
+ type: (params.input?.type ?? DEFAULT_TYPE).trim() || DEFAULT_TYPE,
1310
+ slug,
1311
+ title: readTaskTitle(workItemPath(cwd, 'done', slug)),
1312
+ });
1229
1313
  const integration = await ledgerWrite.applyCompleteTransition({
1230
1314
  arbiter,
1231
1315
  branch,
1232
1316
  mode,
1233
1317
  provider,
1234
1318
  noPR: params.noPR,
1319
+ title: prTitle,
1320
+ body: composeProposeBody({ slug, body: params.body }),
1235
1321
  deleteMergedHead: true,
1236
1322
  cwd,
1237
1323
  env,
@@ -1241,6 +1327,33 @@ async function recoverAlreadyCommitted(params) {
1241
1327
  : `Recovered '${slug}': integrated the kept commit from ${branch} ` +
1242
1328
  `(absorbed a moving ${arbiter}/main across ${attempt} re-fetch+re-` +
1243
1329
  `rebase attempt${attempt === 1 ? '' : 's'}).`);
1330
+ // Post the Gate-2 review as a PR comment (task `committed-recovery-always-
1331
+ // reviews`): the SAME post-integrate audit trail the build path keeps — the
1332
+ // approved verdict's authored `review` prose, on the opened PR. Advisory only
1333
+ // (a block never reaches here — it returned above); the same URL/branch
1334
+ // precedence the build path uses (parsed url wins; else a branch-resolved
1335
+ // fallback when a PR opened but its url was unparseable). `postPRComment*`
1336
+ // never throws. A clean no-op in merge mode / push-only propose (no PR).
1337
+ if (approvedVerdict?.review !== undefined) {
1338
+ if (integration.url !== undefined) {
1339
+ const posted = provider.postPRComment({
1340
+ cwd,
1341
+ url: integration.url,
1342
+ body: approvedVerdict.review,
1343
+ env,
1344
+ });
1345
+ note(posted.instruction);
1346
+ }
1347
+ else if (integration.requestOpened) {
1348
+ const posted = provider.postPRCommentOnBranch({
1349
+ cwd,
1350
+ branch,
1351
+ body: approvedVerdict.review,
1352
+ env,
1353
+ });
1354
+ note(posted.instruction);
1355
+ }
1356
+ }
1244
1357
  return {
1245
1358
  outcome: 'completed',
1246
1359
  routedToNeedsAttention: false,
@@ -1278,8 +1391,9 @@ function resolveWorkBranch(cwd, slug, env) {
1278
1391
  *
1279
1392
  * Carries the {@link slug} so the autonomous-strand surface in `complete.ts`
1280
1393
  * (which catches this error in `performComplete`'s outer try/catch, OUTSIDE the
1281
- * `runComplete` slug scope) can publish the `in-progress/ needs-attention/`
1282
- * tree-less move without re-deriving the slug from the error message.
1394
+ * `runComplete` slug scope) can mark the item stuck on its per-item lock (post
1395
+ * lock-cutover the tree-less lock amend, no `in-progress/ needs-attention/`
1396
+ * folder move) without re-deriving the slug from the error message.
1283
1397
  */
1284
1398
  export class IntegrationNothingStaged extends Error {
1285
1399
  slug;
@@ -1432,8 +1546,8 @@ export function composeProposeBody(input) {
1432
1546
  * On a review APPROVE that carries ≥1 NON-BLOCKING finding, write ONE per-run
1433
1547
  * observation `work/notes/observations/review-nits-<slug>-<YYYY-MM-DD>.md` capturing all
1434
1548
  * of this run's non-blocking nits, so they get a durable, contract-native home
1435
- * instead of evaporating (the block path already routes BLOCKING findings to
1436
- * needs-attention/; the approve path dropped non-blocking ones — see
1549
+ * instead of evaporating (the block path already records BLOCKING findings as the
1550
+ * stuck reason on the item's per-item lock; the approve path dropped non-blocking ones — see
1437
1551
  * `work/findings/review-nonblocking-findings-disposition.md`).
1438
1552
  *
1439
1553
  * The RUNNER writes it (the review agent stays write-free). It is a PLAIN
@@ -2018,8 +2132,8 @@ async function runGate2Review(params) {
2018
2132
  });
2019
2133
  const message = routed.moved
2020
2134
  ? `PR/code review (Gate 2) produced an UNPARSEABLE verdict for '${slug}'; ` +
2021
- 'routed it to work/needs-attention/ (work branch pushed + surfaced; ' +
2022
- 'transient-infra — re-run). NOT integrated.'
2135
+ 'marked it stuck on its per-item lock (work branch pushed + lock ' +
2136
+ 'surfaced; transient-infra — re-run). NOT integrated.'
2023
2137
  : `PR/code review (Gate 2) produced an UNPARSEABLE verdict for '${slug}'; ` +
2024
2138
  'NOT integrating.';
2025
2139
  note(message);
@@ -2064,9 +2178,9 @@ async function runGate2Review(params) {
2064
2178
  note,
2065
2179
  });
2066
2180
  const message = routed.moved
2067
- ? `PR/code review (Gate 2) blocked '${slug}'; routed it to ` +
2068
- 'work/needs-attention/ (surfaced by status; the blocking findings are ' +
2069
- 'recorded in the item body). NOT integrated.'
2181
+ ? `PR/code review (Gate 2) blocked '${slug}'; marked it stuck on its ` +
2182
+ 'per-item lock (surfaced by status; the blocking findings are ' +
2183
+ 'recorded on the lock entry). NOT integrated.'
2070
2184
  : `PR/code review (Gate 2) blocked '${slug}'; NOT integrating.`;
2071
2185
  note(message);
2072
2186
  return {