dorfl 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (492) hide show
  1. package/dist/advance-ci-template.d.ts +3 -3
  2. package/dist/advance-ci-template.js +1 -1
  3. package/dist/advance-ci-template.js.map +1 -1
  4. package/dist/advance-classify.d.ts +5 -5
  5. package/dist/advance-classify.d.ts.map +1 -1
  6. package/dist/advance-classify.js +4 -4
  7. package/dist/advance-drivers.d.ts +8 -8
  8. package/dist/advance-drivers.d.ts.map +1 -1
  9. package/dist/advance-drivers.js +20 -8
  10. package/dist/advance-drivers.js.map +1 -1
  11. package/dist/advance-isolated.d.ts +3 -3
  12. package/dist/advance-isolated.d.ts.map +1 -1
  13. package/dist/advance-isolated.js +1 -1
  14. package/dist/advance-lifecycle-template.d.ts +2 -2
  15. package/dist/advance-lifecycle-template.d.ts.map +1 -1
  16. package/dist/advance-lifecycle-template.js +85 -13
  17. package/dist/advance-lifecycle-template.js.map +1 -1
  18. package/dist/advance-loop-driver.d.ts +3 -3
  19. package/dist/advance-loop-driver.d.ts.map +1 -1
  20. package/dist/advance-loop-driver.js +1 -1
  21. package/dist/advance-treeless-publish.d.ts +24 -1
  22. package/dist/advance-treeless-publish.d.ts.map +1 -1
  23. package/dist/advance-treeless-publish.js +41 -0
  24. package/dist/advance-treeless-publish.js.map +1 -1
  25. package/dist/advance.d.ts +69 -17
  26. package/dist/advance.d.ts.map +1 -1
  27. package/dist/advance.js +409 -102
  28. package/dist/advance.js.map +1 -1
  29. package/dist/advancing-lock.d.ts +31 -3
  30. package/dist/advancing-lock.d.ts.map +1 -1
  31. package/dist/advancing-lock.js +55 -5
  32. package/dist/advancing-lock.js.map +1 -1
  33. package/dist/agent-launch.d.ts +12 -0
  34. package/dist/agent-launch.d.ts.map +1 -1
  35. package/dist/agent-launch.js +22 -12
  36. package/dist/agent-launch.js.map +1 -1
  37. package/dist/agent-stop.d.ts +40 -2
  38. package/dist/agent-stop.d.ts.map +1 -1
  39. package/dist/agent-stop.js +30 -2
  40. package/dist/agent-stop.js.map +1 -1
  41. package/dist/apply-decide.d.ts +19 -5
  42. package/dist/apply-decide.d.ts.map +1 -1
  43. package/dist/apply-decide.js +38 -9
  44. package/dist/apply-decide.js.map +1 -1
  45. package/dist/apply-merge-action.d.ts +20 -8
  46. package/dist/apply-merge-action.d.ts.map +1 -1
  47. package/dist/apply-merge-action.js +44 -9
  48. package/dist/apply-merge-action.js.map +1 -1
  49. package/dist/apply-persist.d.ts +62 -31
  50. package/dist/apply-persist.d.ts.map +1 -1
  51. package/dist/apply-persist.js +173 -44
  52. package/dist/apply-persist.js.map +1 -1
  53. package/dist/apply-stuck-action.d.ts +151 -0
  54. package/dist/apply-stuck-action.d.ts.map +1 -0
  55. package/dist/apply-stuck-action.js +125 -0
  56. package/dist/apply-stuck-action.js.map +1 -0
  57. package/dist/brand.d.ts +12 -1
  58. package/dist/brand.d.ts.map +1 -1
  59. package/dist/brand.js +2 -1
  60. package/dist/brand.js.map +1 -1
  61. package/dist/buildable-body.d.ts +14 -14
  62. package/dist/buildable-body.js +9 -9
  63. package/dist/claim-cas.d.ts +3 -3
  64. package/dist/claim-cas.js +1 -1
  65. package/dist/claim-cas.js.map +1 -1
  66. package/dist/cli-spinner.d.ts +1 -1
  67. package/dist/cli-spinner.js +1 -1
  68. package/dist/cli.d.ts +10 -0
  69. package/dist/cli.d.ts.map +1 -1
  70. package/dist/cli.js +385 -128
  71. package/dist/cli.js.map +1 -1
  72. package/dist/close-job-template.d.ts +3 -3
  73. package/dist/close-job-template.js +9 -9
  74. package/dist/close-job.d.ts +8 -8
  75. package/dist/close-job.js +25 -25
  76. package/dist/close-job.js.map +1 -1
  77. package/dist/complete.d.ts +9 -6
  78. package/dist/complete.d.ts.map +1 -1
  79. package/dist/complete.js +99 -45
  80. package/dist/complete.js.map +1 -1
  81. package/dist/concurrency.d.ts +1 -1
  82. package/dist/concurrency.js +1 -1
  83. package/dist/config.d.ts +86 -40
  84. package/dist/config.d.ts.map +1 -1
  85. package/dist/config.js +61 -9
  86. package/dist/config.js.map +1 -1
  87. package/dist/continue-branch.d.ts +1 -1
  88. package/dist/continue-branch.d.ts.map +1 -1
  89. package/dist/continue-branch.js +23 -1
  90. package/dist/continue-branch.js.map +1 -1
  91. package/dist/cwd-section.js +1 -1
  92. package/dist/cwd-section.js.map +1 -1
  93. package/dist/decision-engine.d.ts +39 -11
  94. package/dist/decision-engine.d.ts.map +1 -1
  95. package/dist/decision-engine.js +10 -6
  96. package/dist/decision-engine.js.map +1 -1
  97. package/dist/do-autopick.d.ts +6 -6
  98. package/dist/do-autopick.d.ts.map +1 -1
  99. package/dist/do-autopick.js +16 -6
  100. package/dist/do-autopick.js.map +1 -1
  101. package/dist/do-config.d.ts +4 -4
  102. package/dist/do-config.js +3 -3
  103. package/dist/do-config.js.map +1 -1
  104. package/dist/do-remote-auto.d.ts +2 -2
  105. package/dist/do-remote-auto.js +1 -1
  106. package/dist/do.d.ts +74 -81
  107. package/dist/do.d.ts.map +1 -1
  108. package/dist/do.js +439 -50
  109. package/dist/do.js.map +1 -1
  110. package/dist/drop-source.d.ts +2 -2
  111. package/dist/env-config.d.ts.map +1 -1
  112. package/dist/env-config.js +19 -11
  113. package/dist/env-config.js.map +1 -1
  114. package/dist/failure-cause.d.ts +3 -2
  115. package/dist/failure-cause.d.ts.map +1 -1
  116. package/dist/failure-cause.js +28 -1
  117. package/dist/failure-cause.js.map +1 -1
  118. package/dist/format.d.ts +6 -6
  119. package/dist/format.d.ts.map +1 -1
  120. package/dist/format.js +13 -30
  121. package/dist/format.js.map +1 -1
  122. package/dist/frontmatter.d.ts +28 -15
  123. package/dist/frontmatter.d.ts.map +1 -1
  124. package/dist/frontmatter.js +38 -24
  125. package/dist/frontmatter.js.map +1 -1
  126. package/dist/gc.d.ts +65 -6
  127. package/dist/gc.d.ts.map +1 -1
  128. package/dist/gc.js +126 -9
  129. package/dist/gc.js.map +1 -1
  130. package/dist/github.d.ts +14 -0
  131. package/dist/github.d.ts.map +1 -1
  132. package/dist/github.js +73 -0
  133. package/dist/github.js.map +1 -1
  134. package/dist/harness.d.ts +25 -0
  135. package/dist/harness.d.ts.map +1 -1
  136. package/dist/harness.js.map +1 -1
  137. package/dist/index.d.ts +4 -1
  138. package/dist/index.d.ts.map +1 -1
  139. package/dist/index.js +2 -1
  140. package/dist/index.js.map +1 -1
  141. package/dist/install-ci-branch-protection.d.ts +133 -39
  142. package/dist/install-ci-branch-protection.d.ts.map +1 -1
  143. package/dist/install-ci-branch-protection.js +191 -49
  144. package/dist/install-ci-branch-protection.js.map +1 -1
  145. package/dist/install-ci-capabilities/advance-lifecycle.d.ts +1 -1
  146. package/dist/install-ci-capabilities/advance-lifecycle.js +1 -1
  147. package/dist/install-ci-capabilities/close-job.d.ts +1 -1
  148. package/dist/install-ci-capabilities/close-job.js +1 -1
  149. package/dist/install-ci-capabilities/example-noop.d.ts +1 -1
  150. package/dist/install-ci-capabilities/example-noop.js +1 -1
  151. package/dist/install-ci-capabilities/intake.d.ts +2 -2
  152. package/dist/install-ci-capabilities/intake.js +2 -2
  153. package/dist/install-ci-capabilities/verify.d.ts +1 -1
  154. package/dist/install-ci-capabilities/verify.js +1 -1
  155. package/dist/install-ci-core.d.ts +24 -4
  156. package/dist/install-ci-core.d.ts.map +1 -1
  157. package/dist/install-ci-core.js +12 -5
  158. package/dist/install-ci-core.js.map +1 -1
  159. package/dist/install-ci-github.d.ts +33 -1
  160. package/dist/install-ci-github.d.ts.map +1 -1
  161. package/dist/install-ci-github.js +74 -1
  162. package/dist/install-ci-github.js.map +1 -1
  163. package/dist/install-ci.d.ts +1 -1
  164. package/dist/install-ci.js +4 -4
  165. package/dist/install-ci.js.map +1 -1
  166. package/dist/install-skills.d.ts +123 -0
  167. package/dist/install-skills.d.ts.map +1 -0
  168. package/dist/install-skills.js +100 -0
  169. package/dist/install-skills.js.map +1 -0
  170. package/dist/intake-event.d.ts +6 -6
  171. package/dist/intake-event.js +6 -6
  172. package/dist/intake-marker.d.ts +3 -3
  173. package/dist/intake-marker.d.ts.map +1 -1
  174. package/dist/intake-triage.d.ts +1 -1
  175. package/dist/intake-triage.js +2 -2
  176. package/dist/intake-trigger-template.d.ts +8 -8
  177. package/dist/intake-trigger-template.d.ts.map +1 -1
  178. package/dist/intake-trigger-template.js +17 -17
  179. package/dist/intake-trigger-template.js.map +1 -1
  180. package/dist/intake.d.ts +65 -52
  181. package/dist/intake.d.ts.map +1 -1
  182. package/dist/intake.js +92 -76
  183. package/dist/intake.js.map +1 -1
  184. package/dist/integration-core.d.ts +27 -24
  185. package/dist/integration-core.d.ts.map +1 -1
  186. package/dist/integration-core.js +191 -77
  187. package/dist/integration-core.js.map +1 -1
  188. package/dist/integrator.d.ts +1 -1
  189. package/dist/integrator.d.ts.map +1 -1
  190. package/dist/integrator.js +19 -3
  191. package/dist/integrator.js.map +1 -1
  192. package/dist/isolation.d.ts +4 -4
  193. package/dist/isolation.d.ts.map +1 -1
  194. package/dist/isolation.js +9 -0
  195. package/dist/isolation.js.map +1 -1
  196. package/dist/issue-provider.d.ts +4 -4
  197. package/dist/issue-provider.js +1 -1
  198. package/dist/item-lock.d.ts +216 -149
  199. package/dist/item-lock.d.ts.map +1 -1
  200. package/dist/item-lock.js +345 -270
  201. package/dist/item-lock.js.map +1 -1
  202. package/dist/item-path.d.ts +2 -2
  203. package/dist/item-path.js +2 -2
  204. package/dist/ledger-lint.d.ts +9 -9
  205. package/dist/ledger-lint.js +9 -9
  206. package/dist/ledger-read.d.ts +57 -57
  207. package/dist/ledger-read.d.ts.map +1 -1
  208. package/dist/ledger-read.js +24 -24
  209. package/dist/ledger-read.js.map +1 -1
  210. package/dist/ledger-write.d.ts +33 -28
  211. package/dist/ledger-write.d.ts.map +1 -1
  212. package/dist/ledger-write.js +100 -122
  213. package/dist/ledger-write.js.map +1 -1
  214. package/dist/lifecycle-gather.d.ts +18 -1
  215. package/dist/lifecycle-gather.d.ts.map +1 -1
  216. package/dist/lifecycle-gather.js +23 -17
  217. package/dist/lifecycle-gather.js.map +1 -1
  218. package/dist/lifecycle-pools.d.ts +44 -7
  219. package/dist/lifecycle-pools.d.ts.map +1 -1
  220. package/dist/lifecycle-pools.js +27 -7
  221. package/dist/lifecycle-pools.js.map +1 -1
  222. package/dist/merge-question-surfacer.d.ts +18 -4
  223. package/dist/merge-question-surfacer.d.ts.map +1 -1
  224. package/dist/merge-question-surfacer.js +20 -5
  225. package/dist/merge-question-surfacer.js.map +1 -1
  226. package/dist/migrate-stuck-locks.d.ts +129 -0
  227. package/dist/migrate-stuck-locks.d.ts.map +1 -0
  228. package/dist/migrate-stuck-locks.js +355 -0
  229. package/dist/migrate-stuck-locks.js.map +1 -0
  230. package/dist/mint-adr.js +4 -4
  231. package/dist/mirror-pool-scan.d.ts +2 -2
  232. package/dist/mirror-pool-scan.js +3 -3
  233. package/dist/mirror-pool-scan.js.map +1 -1
  234. package/dist/needs-attention.d.ts +304 -27
  235. package/dist/needs-attention.d.ts.map +1 -1
  236. package/dist/needs-attention.js +542 -67
  237. package/dist/needs-attention.js.map +1 -1
  238. package/dist/orphan-sidecar.d.ts +10 -6
  239. package/dist/orphan-sidecar.d.ts.map +1 -1
  240. package/dist/orphan-sidecar.js +35 -2
  241. package/dist/orphan-sidecar.js.map +1 -1
  242. package/dist/pi-harness.d.ts +16 -0
  243. package/dist/pi-harness.d.ts.map +1 -1
  244. package/dist/pi-harness.js +82 -2
  245. package/dist/pi-harness.js.map +1 -1
  246. package/dist/placement.d.ts +8 -8
  247. package/dist/placement.js +3 -3
  248. package/dist/prd-to-spec.d.ts.map +1 -1
  249. package/dist/prd-to-spec.js +9 -5
  250. package/dist/prd-to-spec.js.map +1 -1
  251. package/dist/prompt.d.ts +16 -19
  252. package/dist/prompt.d.ts.map +1 -1
  253. package/dist/prompt.js +17 -19
  254. package/dist/prompt.js.map +1 -1
  255. package/dist/protocol/CLAIM-PROTOCOL.md +17 -10
  256. package/dist/protocol/REVIEW-PROTOCOL.md +4 -1
  257. package/dist/protocol/SURFACE-PROTOCOL.md +16 -2
  258. package/dist/protocol/TASKING-PROTOCOL.md +3 -1
  259. package/dist/protocol/WORK-CONTRACT.md +22 -18
  260. package/dist/protocol/task-template.md +1 -1
  261. package/dist/readiness.d.ts +1 -1
  262. package/dist/reap-branches.d.ts +12 -9
  263. package/dist/reap-branches.d.ts.map +1 -1
  264. package/dist/reap-branches.js +25 -7
  265. package/dist/reap-branches.js.map +1 -1
  266. package/dist/recover-isolated.d.ts +12 -0
  267. package/dist/recover-isolated.d.ts.map +1 -1
  268. package/dist/recover-isolated.js +6 -1
  269. package/dist/recover-isolated.js.map +1 -1
  270. package/dist/repo-config.d.ts +23 -2
  271. package/dist/repo-config.d.ts.map +1 -1
  272. package/dist/repo-config.js +64 -18
  273. package/dist/repo-config.js.map +1 -1
  274. package/dist/repo-mirror.d.ts.map +1 -1
  275. package/dist/repo-mirror.js +18 -2
  276. package/dist/repo-mirror.js.map +1 -1
  277. package/dist/review-gate.d.ts +3 -3
  278. package/dist/review-gate.d.ts.map +1 -1
  279. package/dist/review-gate.js +11 -10
  280. package/dist/review-gate.js.map +1 -1
  281. package/dist/review-verdict.d.ts +2 -2
  282. package/dist/review-verdict.d.ts.map +1 -1
  283. package/dist/review-verdict.js +2 -2
  284. package/dist/review-verdict.js.map +1 -1
  285. package/dist/run.d.ts +2 -2
  286. package/dist/run.d.ts.map +1 -1
  287. package/dist/run.js +58 -23
  288. package/dist/run.js.map +1 -1
  289. package/dist/scan.d.ts +26 -17
  290. package/dist/scan.d.ts.map +1 -1
  291. package/dist/scan.js +29 -15
  292. package/dist/scan.js.map +1 -1
  293. package/dist/select-order.d.ts +1 -1
  294. package/dist/select-priority.d.ts +14 -14
  295. package/dist/select-priority.d.ts.map +1 -1
  296. package/dist/select-priority.js +5 -5
  297. package/dist/select-priority.js.map +1 -1
  298. package/dist/sidecar-apply.js +1 -1
  299. package/dist/sidecar-apply.js.map +1 -1
  300. package/dist/sidecar.d.ts +47 -12
  301. package/dist/sidecar.d.ts.map +1 -1
  302. package/dist/sidecar.js +84 -4
  303. package/dist/sidecar.js.map +1 -1
  304. package/dist/skills/answer-questions/SKILL.md +89 -0
  305. package/dist/skills/capture-signal/SKILL.md +52 -0
  306. package/dist/skills/convert-from-prd-to-spec/SKILL.md +90 -0
  307. package/dist/skills/drive-tasks/SKILL.md +218 -0
  308. package/dist/skills/from-idea/SKILL.md +83 -0
  309. package/dist/skills/merge-prs/SKILL.md +70 -0
  310. package/dist/skills/orchestrate/SKILL.md +101 -0
  311. package/dist/skills/promote/SKILL.md +35 -0
  312. package/dist/skills/review/SKILL.md +16 -0
  313. package/dist/skills/setup/SKILL.md +258 -0
  314. package/dist/skills/setup/protocol/ADR-FORMAT.md +47 -0
  315. package/dist/skills/setup/protocol/CLAIM-PROTOCOL.md +224 -0
  316. package/dist/skills/setup/protocol/REVIEW-PROTOCOL.md +122 -0
  317. package/dist/skills/setup/protocol/SURFACE-PROTOCOL.md +135 -0
  318. package/dist/skills/setup/protocol/TASKING-PROTOCOL.md +124 -0
  319. package/dist/skills/setup/protocol/WORK-CONTRACT.md +280 -0
  320. package/dist/skills/setup/protocol/spec-template.md +71 -0
  321. package/dist/skills/setup/protocol/task-template.md +65 -0
  322. package/dist/skills/surface-questions/SKILL.md +16 -0
  323. package/dist/skills/to-spec/SKILL.md +34 -0
  324. package/dist/skills/to-task/SKILL.md +19 -0
  325. package/dist/skills/triage-observations/SKILL.md +78 -0
  326. package/dist/skills/work/SKILL.md +51 -0
  327. package/dist/slug-namespace.d.ts +6 -6
  328. package/dist/slug-namespace.js +8 -8
  329. package/dist/slug-namespace.js.map +1 -1
  330. package/dist/spec-complete.d.ts.map +1 -1
  331. package/dist/spec-complete.js +4 -5
  332. package/dist/spec-complete.js.map +1 -1
  333. package/dist/start.d.ts +1 -1
  334. package/dist/start.d.ts.map +1 -1
  335. package/dist/start.js +54 -60
  336. package/dist/start.js.map +1 -1
  337. package/dist/status.d.ts +3 -3
  338. package/dist/status.js +4 -4
  339. package/dist/status.js.map +1 -1
  340. package/dist/surface-gate.d.ts +2 -2
  341. package/dist/surface-gate.d.ts.map +1 -1
  342. package/dist/surface-gate.js +10 -3
  343. package/dist/surface-gate.js.map +1 -1
  344. package/dist/surface-persist.d.ts +2 -2
  345. package/dist/surface-persist.d.ts.map +1 -1
  346. package/dist/surface-persist.js +1 -1
  347. package/dist/surface-persist.js.map +1 -1
  348. package/dist/tasker-review-loop.d.ts +11 -10
  349. package/dist/tasker-review-loop.d.ts.map +1 -1
  350. package/dist/tasker-review-loop.js +8 -8
  351. package/dist/tasker-review-loop.js.map +1 -1
  352. package/dist/tasking-eligibility.d.ts +21 -21
  353. package/dist/tasking-eligibility.d.ts.map +1 -1
  354. package/dist/tasking-eligibility.js +10 -10
  355. package/dist/tasking-lock.d.ts +10 -10
  356. package/dist/tasking-lock.d.ts.map +1 -1
  357. package/dist/tasking-lock.js +69 -49
  358. package/dist/tasking-lock.js.map +1 -1
  359. package/dist/tasking.d.ts +73 -39
  360. package/dist/tasking.d.ts.map +1 -1
  361. package/dist/tasking.js +309 -95
  362. package/dist/tasking.js.map +1 -1
  363. package/dist/triage-gate.d.ts +1 -1
  364. package/dist/triage-persist.d.ts +15 -11
  365. package/dist/triage-persist.d.ts.map +1 -1
  366. package/dist/triage-persist.js +48 -18
  367. package/dist/triage-persist.js.map +1 -1
  368. package/dist/vendor/incur/agents.d.ts +58 -0
  369. package/dist/vendor/incur/agents.d.ts.map +1 -0
  370. package/dist/vendor/incur/agents.js +343 -0
  371. package/dist/vendor/incur/agents.js.map +1 -0
  372. package/dist/verify-workflow-template.d.ts +1 -1
  373. package/dist/verify-workflow-template.js +3 -3
  374. package/dist/watch-session.d.ts +11 -3
  375. package/dist/watch-session.d.ts.map +1 -1
  376. package/dist/watch-session.js +94 -7
  377. package/dist/watch-session.js.map +1 -1
  378. package/dist/work-layout.d.ts +15 -9
  379. package/dist/work-layout.d.ts.map +1 -1
  380. package/dist/work-layout.js +14 -9
  381. package/dist/work-layout.js.map +1 -1
  382. package/dist/workspace.d.ts +2 -2
  383. package/package.json +2 -2
  384. package/src/advance-ci-template.ts +4 -4
  385. package/src/advance-classify.ts +5 -5
  386. package/src/advance-drivers.ts +26 -13
  387. package/src/advance-isolated.ts +3 -3
  388. package/src/advance-lifecycle-template.ts +98 -13
  389. package/src/advance-loop-driver.ts +3 -3
  390. package/src/advance-treeless-publish.ts +46 -1
  391. package/src/advance.ts +495 -115
  392. package/src/advancing-lock.ts +102 -7
  393. package/src/agent-launch.ts +37 -12
  394. package/src/agent-stop.ts +60 -2
  395. package/src/apply-decide.ts +38 -9
  396. package/src/apply-merge-action.ts +49 -11
  397. package/src/apply-persist.ts +236 -62
  398. package/src/apply-stuck-action.ts +260 -0
  399. package/src/brand.ts +14 -2
  400. package/src/buildable-body.ts +14 -14
  401. package/src/claim-cas.ts +4 -4
  402. package/src/cli-spinner.ts +1 -1
  403. package/src/cli.ts +472 -141
  404. package/src/close-job-template.ts +9 -9
  405. package/src/close-job.ts +26 -26
  406. package/src/complete.ts +121 -63
  407. package/src/concurrency.ts +1 -1
  408. package/src/config.ts +143 -49
  409. package/src/continue-branch.ts +23 -1
  410. package/src/cwd-section.ts +1 -1
  411. package/src/decision-engine.ts +57 -19
  412. package/src/do-autopick.ts +21 -10
  413. package/src/do-config.ts +7 -7
  414. package/src/do-remote-auto.ts +2 -2
  415. package/src/do.ts +558 -85
  416. package/src/drop-source.ts +2 -2
  417. package/src/env-config.ts +19 -11
  418. package/src/failure-cause.ts +30 -1
  419. package/src/format.ts +13 -33
  420. package/src/frontmatter.ts +55 -35
  421. package/src/gc.ts +172 -9
  422. package/src/github.ts +78 -0
  423. package/src/harness.ts +25 -0
  424. package/src/index.ts +10 -0
  425. package/src/install-ci-branch-protection.ts +283 -58
  426. package/src/install-ci-capabilities/advance-lifecycle.ts +1 -1
  427. package/src/install-ci-capabilities/close-job.ts +1 -1
  428. package/src/install-ci-capabilities/example-noop.ts +1 -1
  429. package/src/install-ci-capabilities/intake.ts +2 -2
  430. package/src/install-ci-capabilities/verify.ts +1 -1
  431. package/src/install-ci-core.ts +34 -7
  432. package/src/install-ci-github.ts +87 -1
  433. package/src/install-ci.ts +4 -4
  434. package/src/install-skills.ts +166 -0
  435. package/src/intake-event.ts +6 -6
  436. package/src/intake-marker.ts +3 -3
  437. package/src/intake-triage.ts +2 -2
  438. package/src/intake-trigger-template.ts +18 -18
  439. package/src/intake.ts +138 -103
  440. package/src/integration-core.ts +234 -100
  441. package/src/integrator.ts +22 -9
  442. package/src/isolation.ts +13 -4
  443. package/src/issue-provider.ts +4 -4
  444. package/src/item-lock.ts +505 -361
  445. package/src/item-path.ts +2 -2
  446. package/src/ledger-lint.ts +9 -9
  447. package/src/ledger-read.ts +81 -81
  448. package/src/ledger-write.ts +137 -152
  449. package/src/lifecycle-gather.ts +45 -22
  450. package/src/lifecycle-pools.ts +71 -13
  451. package/src/merge-question-surfacer.ts +32 -8
  452. package/src/migrate-stuck-locks.ts +451 -0
  453. package/src/mint-adr.ts +4 -4
  454. package/src/mirror-pool-scan.ts +5 -5
  455. package/src/needs-attention.ts +877 -104
  456. package/src/orphan-sidecar.ts +49 -8
  457. package/src/pi-harness.ts +82 -2
  458. package/src/placement.ts +8 -8
  459. package/src/prd-to-spec.ts +13 -5
  460. package/src/prompt.ts +20 -25
  461. package/src/readiness.ts +1 -1
  462. package/src/reap-branches.ts +38 -14
  463. package/src/recover-isolated.ts +18 -1
  464. package/src/repo-config.ts +66 -17
  465. package/src/repo-mirror.ts +23 -1
  466. package/src/review-gate.ts +12 -11
  467. package/src/review-verdict.ts +3 -3
  468. package/src/run.ts +69 -23
  469. package/src/scan.ts +36 -22
  470. package/src/select-order.ts +1 -1
  471. package/src/select-priority.ts +15 -15
  472. package/src/sidecar-apply.ts +1 -1
  473. package/src/sidecar.ts +141 -14
  474. package/src/slug-namespace.ts +10 -10
  475. package/src/spec-complete.ts +4 -5
  476. package/src/start.ts +57 -63
  477. package/src/status.ts +7 -7
  478. package/src/surface-gate.ts +12 -5
  479. package/src/surface-persist.ts +3 -3
  480. package/src/tasker-review-loop.ts +18 -17
  481. package/src/tasking-eligibility.ts +21 -21
  482. package/src/tasking-lock.ts +75 -60
  483. package/src/tasking.ts +381 -135
  484. package/src/triage-gate.ts +1 -1
  485. package/src/triage-persist.ts +71 -27
  486. package/src/vendor/incur/LICENSE +21 -0
  487. package/src/vendor/incur/README.md +19 -0
  488. package/src/vendor/incur/agents.ts +392 -0
  489. package/src/verify-workflow-template.ts +3 -3
  490. package/src/watch-session.ts +102 -7
  491. package/src/work-layout.ts +14 -9
  492. package/src/workspace.ts +2 -2
package/dist/cli.js CHANGED
@@ -51,7 +51,8 @@ import { sweepOrphanSidecars } from './orphan-sidecar.js';
51
51
  import { sweepLedgerDuplicates, formatLedgerSweep } from './ledger-lint.js';
52
52
  import { status, formatStatus } from './status.js';
53
53
  import { ledgerWrite } from './ledger-write.js';
54
- import { releaseItemLock, reportItemLocks, formatItemLockReport, itemLockReportNeedsAttention, reapStaleItemLocks, formatReapReport, reapReportNeedsAttention, } from './item-lock.js';
54
+ import { releaseItemLock, releaseLiteralLockEntry, reportItemLocks, formatItemLockReport, itemLockReportNeedsAttention, reapStaleItemLocks, formatReapReport, reapReportNeedsAttention, } from './item-lock.js';
55
+ import { migrateStuckLocks, formatMigrateStuckLocksReport, migrateStuckLocksNeedsAttention, } from './migrate-stuck-locks.js';
55
56
  import { promoteFromPreBacklog, promoteFromPreSpec, listPromotable, } from './needs-attention.js';
56
57
  import { parseSlugArg } from './slug-namespace.js';
57
58
  import { dropSource } from './drop-source.js';
@@ -62,6 +63,7 @@ import { installCI } from './install-ci.js';
62
63
  import { GitHubCIContext } from './install-ci-github.js';
63
64
  import { loadCapabilityRegistry } from './install-ci-core.js';
64
65
  import { performCloseMergedIssues } from './close-job.js';
66
+ import { installSkills } from './install-skills.js';
65
67
  /**
66
68
  * Whether `--auto-build` / `--no-auto-build` was explicitly passed on the command
67
69
  * line. Commander gives a negatable boolean option a default of `true`, so we
@@ -218,10 +220,10 @@ function buildRegistrySetAdvanceTick(options) {
218
220
  lifecycleGates: {
219
221
  triage: config.observationTriage !== 'off',
220
222
  surface: config.surfaceBlockers,
221
- // `surfaceStaging` widens the SURFACE candidate set into STAGING (prd
223
+ // `surfaceStaging` widens the SURFACE candidate set into STAGING (spec
222
224
  // `staging-surface-and-apply-promote-safety` F2). Default `true` — a
223
- // tasked `needsAnswers` task in `tasks/backlog/` (or prd in
224
- // `prds/proposed/`) surfaces its questions BEFORE promotion. BUILD/claim
225
+ // tasked `needsAnswers` task in `tasks/backlog/` (or spec in
226
+ // `specs/proposed/`) surfaces its questions BEFORE promotion. BUILD/claim
225
227
  // stays pool-only either way.
226
228
  surfaceStaging: config.surfaceStaging,
227
229
  },
@@ -249,11 +251,11 @@ function buildRegistrySetAdvanceTick(options) {
249
251
  identity: config.identity,
250
252
  autoTask: config.autoTask,
251
253
  integration: config.integration,
252
- // The per-TRANSITION TASKING override: the `do prd:` tasking path threads
254
+ // The per-TRANSITION TASKING override: the `do spec:` tasking path threads
253
255
  // `taskingIntegration ?? integration`; the build path stays on `integration`.
254
256
  taskingIntegration: config.taskingIntegration,
255
- // The TASK-PLACEMENT configured default (`do prd:` tasking output:
256
- // `pre-backlog` staged vs `ready` pool). No operator flag on this
257
+ // The TASK-PLACEMENT configured default (`do spec:` tasking output:
258
+ // `backlog` staged vs `ready` pool). No operator flag on this
257
259
  // registry-driven advance context, so only the configured default rung is
258
260
  // threaded (the resolver still layers untrusted-origin force + built-in floor).
259
261
  tasksLandIn: config.tasksLandIn,
@@ -261,7 +263,7 @@ function buildRegistrySetAdvanceTick(options) {
261
263
  verify: config.verify,
262
264
  // Single-job build path: gate the REBASED tip (the default) unconditionally.
263
265
  freshWorktreeGate: config.freshWorktreeGate,
264
- // Cross-job merge-serialiser CAS-retry cap (prd `land-time-reverify-and-
266
+ // Cross-job merge-serialiser CAS-retry cap (spec `land-time-reverify-and-
265
267
  // parallel-merge-ceiling` Story 5 / Applied Answer q1 (a)) — resolved per-repo
266
268
  // and threaded so the registry-driven advance path's `do` inherits it.
267
269
  mergeRetries: config.mergeRetries,
@@ -300,7 +302,7 @@ function buildRegistrySetAdvanceTick(options) {
300
302
  triageGate: harnessTriageGate({ harness, agentCmd: config.agentCmd }),
301
303
  triageModel: config.model,
302
304
  // The ANSWERED-MERGE LAND DISPATCH context (task
303
- // `apply-rung-merge-disposition`, prd `land-time-reverify-and-parallel-
305
+ // `apply-rung-merge-disposition`, spec `land-time-reverify-and-parallel-
304
306
  // merge-ceiling`): the dispatcher cuts a per-job worktree via
305
307
  // `workspace.ts` `createJob` off the hub mirror (so we thread the resolved
306
308
  // `workspacesDir` + the real arbiter URL — the per-mirror tree-less
@@ -396,7 +398,7 @@ function runFlagOverrides(flags, command) {
396
398
  // fleet conditional lives in `runOnce`, not in this flag mapping).
397
399
  Object.assign(overrides, freshWorktreeGateFlagOverrides(flags));
398
400
  // `--merge-retries <n>` rides the SAME chain: the cross-job merge-serialiser
399
- // CAS-retry cap (prd `land-time-reverify-and-parallel-merge-ceiling` Story 5 /
401
+ // CAS-retry cap (spec `land-time-reverify-and-parallel-merge-ceiling` Story 5 /
400
402
  // Applied Answer q1 (a)). The `run` fleet inherits the resolved cap through
401
403
  // the converged `performIntegration` core (config.mergeRetries threads into
402
404
  // the merge loop, replacing the bare `DEFAULT_MERGE_RETRIES` fallback).
@@ -409,7 +411,7 @@ function formatItemLine(item) {
409
411
  }
410
412
  /**
411
413
  * Resolve the EXPLICIT operator placement override from `--tasks-land-in <where>`
412
- * (the top of the `do prd:` tasking-placement precedence — task
414
+ * (the top of the `do spec:` tasking-placement precedence — task
413
415
  * `runner-deterministic-slice-placement-policy-and-precedence`). Mirrors the
414
416
  * `flagMode === 'merge'` ⇒ `explicitMerge: true` shape: it contributes
415
417
  * `explicitTasksLandIn` ONLY when the operator actually typed the flag, so an
@@ -422,8 +424,8 @@ function explicitTasksLandInFromFlag(raw) {
422
424
  if (raw === undefined) {
423
425
  return undefined;
424
426
  }
425
- if (raw !== 'pre-backlog' && raw !== 'ready') {
426
- throw new Error(`--tasks-land-in must be 'pre-backlog' or 'ready' (got '${raw}').`);
427
+ if (raw !== 'backlog' && raw !== 'ready') {
428
+ throw new Error(`--tasks-land-in must be 'backlog' or 'ready' (got '${raw}').`);
427
429
  }
428
430
  return raw;
429
431
  }
@@ -498,7 +500,7 @@ function printPrdToSpecReport(result) {
498
500
  return;
499
501
  }
500
502
  if (result.leaks.length === 0) {
501
- console.log('Leak scan: GREEN (no surviving prd data ref).');
503
+ console.log('Leak scan: GREEN (no surviving spec data ref).');
502
504
  }
503
505
  else {
504
506
  console.error(`Leak scan: FAILED (${result.leaks.length} leak(s)):`);
@@ -513,11 +515,12 @@ function printLeak(leak) {
513
515
  /**
514
516
  * Resolve a task-only command's slug argument through the §3a namespace guard
515
517
  * (`resolveTaskOnlyArg`): accept bare (= task) + `task:` (explicit alias),
516
- * REJECT `spec:` with a clear "operates on tasks, not specs" error (and the
517
- * legacy `prd:` with "operates on tasks, not prds", still accepted through the
518
- * cutover). On rejection it prints the error to stderr and exits 1 (the task-only
519
- * commands never act on a spec). An OMITTED slug (`start`/`complete`/`prompt`
520
- * infer it from the branch) passes through untouched.
518
+ * REJECT `spec:` with a clear "operates on tasks, not specs" error. On rejection
519
+ * it prints the error to stderr and exits 1 (the task-only commands never act on
520
+ * a spec). An OMITTED slug (`start`/`complete`/`prompt` infer it from the branch)
521
+ * passes through untouched. (The legacy ''prd:'' prefix is a DEAD namespace token
522
+ * after the hard cutover — it is neither special-cased nor rejected here; a
523
+ * ''prd:<slug>'' arg resolves as a bare literal task slug.)
521
524
  *
522
525
  * `do` is the ONE command that spans both namespaces; it consumes the full
523
526
  * `resolveSlug` (with the cross-namespace collision check) in the `do-in-place`
@@ -595,10 +598,10 @@ function buildInteractiveLauncher(flags, configPath, repoPath) {
595
598
  * switching to its `work/<slug>` branch WITHOUT claiming). The runtime
596
599
  * difference is exactly the `resume` flag — `resume` forces it on (its only mode
597
600
  * is to re-engage), while `start` honours the (now hidden) `--resume` alias.
598
- * Both are task-only (§3a: accept bare + `task:`, reject `prd:`).
601
+ * Both are task-only (§3a: accept bare + `task:`, reject `spec:`).
599
602
  */
600
603
  async function runStartAction(rawSlug, flags, resume) {
601
- // Task-only command (§3a): accept bare + `task:`, reject `prd:`.
604
+ // Task-only command (§3a): accept bare + `task:`, reject `spec:`.
602
605
  const slug = resolveTaskOnlySlug(rawSlug);
603
606
  const cwd = process.cwd();
604
607
  // `resume --isolated <slug>`: re-engage the slug's RETAINED job worktree (the
@@ -675,6 +678,34 @@ export function buildProgram() {
675
678
  program
676
679
  .name(brand.bin)
677
680
  .description('Autonomous parallel agents over file-based work/ queues.');
681
+ // `dorfl config --json` (spec `graceful-pre-timeout-wip-checkpoint` — step 5):
682
+ // print the RESOLVED per-repo config as JSON, so BOTH the advance-lifecycle
683
+ // workflow's `enumerate` job (which reads it to emit the dynamic
684
+ // `githubTimeout` output) AND a human debugging a config-precedence question
685
+ // share ONE honest primitive. A focused honest primitive — NOT overloaded
686
+ // onto `scan`. Runs in the cwd (in-place), so the resolution chain reads THIS
687
+ // repo's committed `dorfl.json` exactly like `scan --here`.
688
+ program
689
+ .command('config')
690
+ .helpGroup(HEADLINE_GROUP)
691
+ .description("Print the resolved config for the current repo (flag > env > per-repo > global > default). Consumed by the advance-lifecycle workflow's enumerate job to emit the dynamic GitHub timeout backstop (agentDeadlineMinutes + checkpointHeadroomMinutes) at run time from the committed dorfl.json — so editing dorfl.json reflects everywhere (internal deadline AND GitHub cap) on the next tick, no install-ci re-run.")
692
+ .option('-c, --config <path>', 'config file path', defaultConfigPath())
693
+ .option('--arbiter <remote>', 'name of the arbiter git remote (default: per-repo/global defaultArbiter)')
694
+ .option('--json', 'output the resolved config as JSON')
695
+ .action(async (flags) => {
696
+ if (flags.json !== true) {
697
+ console.error('error: `dorfl config` currently supports only --json output. Pass --json.');
698
+ process.exit(1);
699
+ }
700
+ const cwd = process.cwd();
701
+ const { global, override } = loadGlobalAndOverride(flags.config);
702
+ const resolved = resolveRepoConfig({
703
+ repoPath: cwd,
704
+ global,
705
+ override,
706
+ }).config;
707
+ console.log(JSON.stringify(resolved, null, 2));
708
+ });
678
709
  program
679
710
  .command('scan')
680
711
  .helpGroup(HEADLINE_GROUP)
@@ -907,14 +938,14 @@ export function buildProgram() {
907
938
  program
908
939
  .command('claim')
909
940
  .helpGroup(ADVANCED_GROUP)
910
- .description('Atomically claim a work/backlog/<slug>.md item via a compare-and-swap push to the arbiter (in-process; mirrors scripts/claim.sh).')
941
+ .description(`Atomically claim a ${workFolderPrefix('tasks-ready')}<slug>.md item via a compare-and-swap push to the arbiter (in-process; mirrors scripts/claim.sh).`)
911
942
  .argument('<slug>', 'the slug of the backlog item to claim')
912
943
  .option('--arbiter <remote>', 'name of the arbiter git remote (default: origin)', 'origin')
913
944
  .option('--retries <n>', 'cap on push retries when main advances', '3')
914
945
  .option('--dry-run', 'show the intended push without mutating the arbiter')
915
946
  .option('--ignore-not-ready', 'override the readiness guard: claim despite an unmet blockedBy, and silence the needsAnswers warning (loud, never default)')
916
947
  .action(async (rawSlug, flags) => {
917
- // Task-only command (§3a): accept bare + `task:`, reject `prd:`.
948
+ // Task-only command (§3a): accept bare + `task:`, reject `spec:`.
918
949
  const slug = resolveTaskOnlySlug(rawSlug);
919
950
  // Wrap ONLY this CLI surface's `performClaim` call with the spinner
920
951
  // helper (task `claim-cas-spinner`): the push can take seconds, so the
@@ -1022,7 +1053,7 @@ export function buildProgram() {
1022
1053
  const remote = flags.remote !== undefined && flags.remote.trim() !== ''
1023
1054
  ? flags.remote
1024
1055
  : undefined;
1025
- // Task-only command (§3a): accept bare + `task:`, reject `prd:`.
1056
+ // Task-only command (§3a): accept bare + `task:`, reject `spec:`.
1026
1057
  const theSlug = resolveTaskOnlySlug(rawSlug);
1027
1058
  const configPath = flags.config ?? defaultConfigPath();
1028
1059
  const { dir: configuredRoot, config } = loadHumanWorktreesDir(configPath);
@@ -1066,10 +1097,10 @@ export function buildProgram() {
1066
1097
  program
1067
1098
  .command('prompt')
1068
1099
  .helpGroup(ADVANCED_GROUP)
1069
- .description("Print to stdout the work-agent prompt for a task: the canonical CLAIM-PROTOCOL wrapper + the task's own ## Prompt (with <slug> and source prd substituted). Resolves work/in-progress/<slug>.md then work/backlog/<slug>.md; infers <slug> from a work/<slug> branch when omitted. Read-only, stdout only — the same assembly the autonomous runner feeds agentCmd.")
1100
+ .description(`Print to stdout the work-agent prompt for a task: the canonical CLAIM-PROTOCOL wrapper + the task's own ## Prompt (with <slug> and source spec substituted). Resolves work/in-progress/<slug>.md then ${workFolderPrefix('tasks-ready')}<slug>.md; infers <slug> from a work/<slug> branch when omitted. Read-only, stdout only — the same assembly the autonomous runner feeds agentCmd.`)
1070
1101
  .argument('[slug]', 'the slug to render (inferred from a work/<slug> branch if omitted)')
1071
1102
  .action((rawSlug) => {
1072
- // Task-only command (§3a): accept bare + `task:`, reject `prd:`.
1103
+ // Task-only command (§3a): accept bare + `task:`, reject `spec:`.
1073
1104
  const slug = resolveTaskOnlySlug(rawSlug);
1074
1105
  // Resolve the `promptGuidance` NUDGE namespace through the SAME chain the
1075
1106
  // gate family uses (env > per-repo > global > default), so e.g. a
@@ -1112,7 +1143,7 @@ export function buildProgram() {
1112
1143
  .option('--no-fresh-worktree-gate', 'run the acceptance gate in the current checkout (the pre-rebase tree) — the opt-out for when the per-gate install cost is too high')
1113
1144
  .option('--merge-retries <n>', 'cross-job merge-serialiser CAS-retry cap (see `run --help`); resolved flag > env > per-repo > global > default 1000.')
1114
1145
  .action(async (rawSlug, flags) => {
1115
- // Task-only command (§3a): accept bare + `task:`, reject `prd:`.
1146
+ // Task-only command (§3a): accept bare + `task:`, reject `spec:`.
1116
1147
  const slug = resolveTaskOnlySlug(rawSlug);
1117
1148
  const cwd = process.cwd();
1118
1149
  const { global, override } = loadGlobalAndOverride(flags.config);
@@ -1133,6 +1164,11 @@ export function buildProgram() {
1133
1164
  override,
1134
1165
  flags: {
1135
1166
  ...(flagMode ? { integration: flagMode } : {}),
1167
+ // `--merge-retries <n>` rides the SAME gate-family precedence chain on
1168
+ // the isolated-recovery path so a wide-matrix CI's raised cap actually
1169
+ // reaches the land tail here too (task
1170
+ // `thread-merge-retries-cross-task-and-ratify-default`).
1171
+ ...mergeRetriesFlagOverrides(flags),
1136
1172
  ...noPRFlagOverrides(flags),
1137
1173
  },
1138
1174
  });
@@ -1147,6 +1183,9 @@ export function buildProgram() {
1147
1183
  workspacesDir: flags.workspace ?? isoConfig.workspacesDir,
1148
1184
  integration: isoConfig.integration,
1149
1185
  noPR: isoConfig.noPR,
1186
+ // The resolved cross-job CAS-retry cap threaded through so the isolated-
1187
+ // recovery land tail respects the per-repo cap (same chain as `complete`).
1188
+ mergeRetries: isoConfig.mergeRetries,
1150
1189
  note: (message) => console.error(`>> ${message}`),
1151
1190
  env: process.env,
1152
1191
  });
@@ -1172,7 +1211,7 @@ export function buildProgram() {
1172
1211
  // `--fresh-worktree-gate`/`--no-fresh-worktree-gate` rides the SAME chain.
1173
1212
  ...freshWorktreeGateFlagOverrides(flags),
1174
1213
  // `--merge-retries <n>` rides the SAME chain: the cross-job merge-serialiser
1175
- // CAS-retry cap (prd `land-time-reverify-and-parallel-merge-ceiling` Story 5
1214
+ // CAS-retry cap (spec `land-time-reverify-and-parallel-merge-ceiling` Story 5
1176
1215
  // / Applied Answer q1 (a)).
1177
1216
  ...mergeRetriesFlagOverrides(flags),
1178
1217
  // `--no-pr` (the PR-INTENT axis) rides the SAME chain.
@@ -1212,7 +1251,7 @@ export function buildProgram() {
1212
1251
  // a single-job path, so the resolved flag is passed UNCONDITIONALLY (no
1213
1252
  // fleet downgrade).
1214
1253
  freshWorktreeGate: config.freshWorktreeGate,
1215
- // Cross-job merge-serialiser CAS-retry cap (prd `land-time-reverify-and-
1254
+ // Cross-job merge-serialiser CAS-retry cap (spec `land-time-reverify-and-
1216
1255
  // parallel-merge-ceiling` Story 5 / Applied Answer q1 (a)) — the resolved
1217
1256
  // per-repo value reaches the merge loop via `performComplete`→
1218
1257
  // `performIntegration`.
@@ -1243,22 +1282,23 @@ export function buildProgram() {
1243
1282
  program
1244
1283
  .command('do')
1245
1284
  .helpGroup(HEADLINE_GROUP)
1246
- .description('The per-repo WORKER (the CI command): claim + onboard onto work/<slug>, run the agent, gate, integrate, and exit. In the CURRENT checkout by default (refuses on a dirty tree, integrates in-place). With --remote <r>: against a REGISTERED repo with NO checkout — materialise a hub mirror + job worktree in the agents\u2019 area, run the same pipeline there, then reap. do <slug> | do task:<slug> | do spec:<slug> (the tasking path; the legacy prd:<slug> is still accepted) | do (auto-pick one) | do <a> <b> (those, in sequence) | do -n <x> (x eligible, in sequence). Auto-pick draws TASKS-FIRST then SPECS-to-task by default (per-repo selectionOrder reorders the pools). --propose (default) / --merge resolved at integrate-time. Supersedes ar-run.sh.')
1285
+ .description("The per-repo WORKER (the CI command): claim + onboard onto work/<slug>, run the agent, gate, integrate, and exit. Builds in an ISOLATED job worktree off THIS repo's arbiter by DEFAULT (task `make-isolated-default-build-mode` — the cwd is the origin SOURCE only; the human working tree is never written). --in-place opts OUT (build in the current checkout, refusing a dirty tree today's pre-flip behaviour). --isolated remains accepted as a redundant explicit opt-in alias. With --remote <r>: against a REGISTERED repo with NO checkout — materialise a hub mirror + job worktree in the agents\u2019 area, run the same pipeline there, then reap. do <slug> | do task:<slug> | do spec:<slug> (the tasking path) | do (auto-pick one) | do <a> <b> (those, in sequence) | do -n <x> (x eligible, in sequence). Auto-pick draws TASKS-FIRST then SPECS-to-task by default (per-repo selectionOrder reorders the pools). --propose (default) / --merge resolved at integrate-time. Supersedes ar-run.sh.")
1247
1286
  // EXTENSIBLE argument grammar (the three do-* tasks grow this one block):
1248
1287
  // `do-autopick` widens the single optional positional into a VARIADIC one so
1249
1288
  // `do` (zero args = auto-pick), `do <a> <b> …` (named, in sequence), and
1250
1289
  // `do <slug>` (exactly one) all share the one command. `-n <x>` is the count
1251
1290
  // for the auto-pick form. `do` stays SEQUENTIAL (parallelism is `run`).
1252
- .argument('[slugs...]', 'the item(s) to do: bare (= the task), task:<slug>, or spec:<slug> (task the spec; the legacy prd:<slug> is still accepted). Zero args = auto-pick; multiple = do them in sequence.')
1291
+ .argument('[slugs...]', 'the item(s) to do: bare (= the task), task:<slug>, or spec:<slug> (task the spec). Zero args = auto-pick; multiple = do them in sequence.')
1253
1292
  .option('-c, --config <path>', 'config file path', defaultConfigPath())
1254
1293
  .option('--arbiter <remote>', 'name of the arbiter git remote (default: per-repo/global defaultArbiter)')
1255
1294
  .option('-n, --number <x>', 'AUTO-PICK x eligible items and do them IN SEQUENCE (ordered by selectionOrder, default drain = tasks-first then prds-to-task). Sequential — never a parallelism knob (that is `run`). Mutually exclusive with naming items.')
1256
1295
  .option('--selection-order <order>', 'order the auto-pick pools (build/task/surface/triage; apply is always first): a preset keyword (drain (default) | groom) or an explicit comma-separated pool list (e.g. build,task,surface,triage). Resolved flag > env > per-repo > global > default.')
1257
1296
  .option('--remote <r>', 'run against a REGISTERED repo with NO checkout: materialise a hub mirror + job worktree in the agents\u2019 area (auto-registers an unknown remote), run the pipeline there, then reap (never touches the human area)')
1258
- .option('--isolated', "build in an ISOLATED job worktree off THIS repo's arbiter (inferred from cwd) instead of taking over the current checkout, then integrate + reap \u2014 the in-place-but-isolated form. Shares the same grammar as the no-checkout forms: a single named item, multiple named items (in sequence), AND -n/auto-pick over the mirror-side eligible-pool scan. Always SEQUENTIAL (parallelism is `run` / the CI matrix). Orthogonal to --remote (a foreign repo); with --remote, remote wins (isolation is already implied).")
1297
+ .option('--isolated', "build in an ISOLATED job worktree off THIS repo's arbiter (inferred from cwd) instead of taking over the current checkout, then integrate + reap \u2014 NOW THE DEFAULT since `make-isolated-default-build-mode`; the flag remains accepted as a redundant explicit opt-IN alias (some scripts/skills pin it, and it is harmless once implied). Shares the same grammar as the no-checkout forms: a single named item, multiple named items (in sequence), AND -n/auto-pick over the mirror-side eligible-pool scan. Always SEQUENTIAL (parallelism is `run` / the CI matrix). Orthogonal to --remote (a foreign repo); with --remote, remote wins (isolation is already implied). Mutually exclusive with --in-place (contradictory intents).")
1298
+ .option('--in-place', "OPT OUT of the new isolated default: build in the CURRENT checkout (integrates in place, refuses a dirty tree) \u2014 today's pre-flip behaviour, now explicit. Mutually exclusive with --isolated (contradictory intents) and with --remote (there is no local checkout to take over). Use this for the edit-locally-then-build loop; the default (isolated off the arbiter) never writes the cwd working tree.")
1259
1299
  .option('--merge', 'integrate in merge mode this invocation (mutually exclusive with --propose; overrides config)')
1260
1300
  .option('--propose', 'integrate in propose mode this invocation (default; mutually exclusive with --merge; overrides config)')
1261
- .option('--tasks-land-in <where>', 'where `do prd:<slug>` tasking output lands: `pre-backlog` (staged, not agent-eligible) or `ready` (the agent POOL). The EXPLICIT operator override at the top of the placement precedence (explicit flag > untrusted-origin forces staging > tasksLandIn default > built-in). Resolved flag > env (DORFL_TASKS_LAND_IN) > per-repo > global > built-in.')
1301
+ .option('--tasks-land-in <where>', 'where `do spec:<slug>` tasking output lands: `backlog` (staged, not agent-eligible) or `ready` (the agent POOL). The EXPLICIT operator override at the top of the placement precedence (explicit flag > untrusted-origin forces staging > tasksLandIn default > built-in). Resolved flag > env (DORFL_TASKS_LAND_IN) > per-repo > global > built-in.')
1262
1302
  .option('--no-pr', 'propose without opening a PR: push the branch but deliberately skip the review request, even on an authed GitHub arbiter (the explicit suppress-PR intent). Resolved flag > env > per-repo > global > default off.')
1263
1303
  .option('--ignore-diverged-main', 'override the in-place divergence guard: run even when local main is ahead of <arbiter>/main (unpushed). The work still lands on the arbiter; local main is left for you to `git rebase`. In-place only; loud, never default.')
1264
1304
  .option('--allow-backlog', 'do task:<slug> ONLY: also FIND, CLAIM, and COMPLETE a task that lives in tasks/backlog/ (staging), driving it in place WITHOUT promoting it to the pool (so no advance leg / run daemon can claim it out from under you). The done-move goes tasks/backlog/ -> tasks/done/ directly (your explicit drive IS the promotion). EXPLICIT-INVOCATION-ONLY: default off, never set by run/auto-pick/advance or config/env.')
@@ -1272,9 +1312,9 @@ export function buildProgram() {
1272
1312
  .option('--no-review', 'do NOT run Gate 2 this invocation (overrides config)')
1273
1313
  .option('--review-model <id>', 'model the Gate-2 review agent runs on (de-correlated from the builder; routing intent). Resolved flag > env > per-repo > global > default.')
1274
1314
  .option('--review-max-rounds <n>', 'bound the revise/review loop; on exhaustion force needs-attention (default 2)')
1275
- .option('--tasker-loop', 'run the tasker IMPROVER loop on `do prd:<slug>` (review→edit→converge over the produced task set). ON by default; --no-tasker-loop skips it. DISTINCT from the acceptance gate (--review).')
1276
- .option('--no-tasker-loop', 'skip the tasker improver loop on `do prd:<slug>`')
1277
- .option('--tasker-loop-max <n>', 'cap the tasker improver loop on `do prd:<slug>` (in-context review passes); on exhaustion with blockers, reject via needsAnswers / route the prd to needs-attention (default 3)')
1315
+ .option('--tasker-loop', 'run the tasker IMPROVER loop on `do spec:<slug>` (review→edit→converge over the produced task set). ON by default; --no-tasker-loop skips it. DISTINCT from the acceptance gate (--review).')
1316
+ .option('--no-tasker-loop', 'skip the tasker improver loop on `do spec:<slug>`')
1317
+ .option('--tasker-loop-max <n>', 'cap the tasker improver loop on `do spec:<slug>` (in-context review passes); on exhaustion with blockers, reject via needsAnswers / route the spec to needs-attention (default 3)')
1278
1318
  .option('--tasker-loop-model <id>', 'model the tasker improver loop review agent runs on (de-correlated from the tasker; routing intent). Resolved flag > env > per-repo > global > default. DISTINCT from --review-model.')
1279
1319
  .option('--fresh-worktree-gate', 'run the acceptance gate (prepare then verify) against the REBASED tip in a CLEAN throwaway worktree (the tree that actually integrates), so a green gate provably describes the merged artifact. ON by default. Resolved flag > env > per-repo > global > default on.')
1280
1320
  .option('--no-fresh-worktree-gate', "run the acceptance gate in the agent's build worktree (the pre-rebase tree) as before — the opt-out for when the per-gate install cost is too high")
@@ -1331,13 +1371,47 @@ export function buildProgram() {
1331
1371
  // `remote-do-reads-per-repo-config-from-arbiter-main`). Only the whitelisted
1332
1372
  // `REPO_ALLOWED_KEYS` are layered (host-only keys stay global/flag/env-only,
1333
1373
  // rejected by the SAME `repo-config.ts` split).
1334
- const isolatedNoRemote = flags.isolated === true && flags.remote === undefined;
1374
+ // Mutually-exclusive intent guards for the three FORM flags (task
1375
+ // `make-isolated-default-build-mode`, D3): `--in-place` opts OUT of the new
1376
+ // isolated default, `--isolated` is the redundant explicit opt-IN alias,
1377
+ // `--remote <r>` targets a foreign repo. `--in-place` + `--isolated` is
1378
+ // contradictory (build here vs build off the arbiter); `--in-place` + `--remote`
1379
+ // is nonsensical (there is no local checkout to take over). Reject loudly
1380
+ // rather than silently prefer one.
1381
+ if (flags.inPlace === true && flags.isolated === true) {
1382
+ console.error('error: --in-place and --isolated are contradictory (build in the current ' +
1383
+ "checkout vs build in a worktree off this repo's arbiter). Pick one.");
1384
+ process.exit(1);
1385
+ }
1386
+ if (flags.inPlace === true && flags.remote !== undefined) {
1387
+ console.error('error: --in-place builds in the CURRENT checkout; --remote <r> targets a ' +
1388
+ 'REGISTERED repo with NO checkout. Pick one.');
1389
+ process.exit(1);
1390
+ }
1391
+ // The DEFAULT flip (task `make-isolated-default-build-mode`): with no
1392
+ // `--remote` and no `--in-place`, `do <slug>` builds in an ISOLATED job
1393
+ // worktree off THIS repo's arbiter — the SAME no-checkout path `--isolated`
1394
+ // already used. The cwd is the origin SOURCE only (arbiter-remote resolution
1395
+ // + per-repo config), never written. `--isolated` remains accepted as a
1396
+ // redundant explicit opt-IN alias (D3). `--in-place` opts out to today's
1397
+ // in-checkout path (the fall-through below). See ADR §3.
1398
+ const isolatedNoRemote = flags.remote === undefined && flags.inPlace !== true;
1399
+ // Whether the user DEFAULTED into isolated (no form flag typed) vs typed
1400
+ // `--isolated` explicitly. Only used to tune the no-arbiter error message so
1401
+ // the default path names `--in-place` as its natural escape (D2).
1402
+ const defaultedToIsolated = isolatedNoRemote && flags.isolated !== true;
1335
1403
  if (flags.remote !== undefined || isolatedNoRemote) {
1336
1404
  // The form's user-facing name + canonical usage, for the shared error
1337
- // messages below (so `--isolated` errors read in its own terms).
1338
- const form = isolatedNoRemote ? '--isolated' : '--remote';
1405
+ // messages below (so isolated errors read in the form's own terms).
1406
+ const form = isolatedNoRemote
1407
+ ? defaultedToIsolated
1408
+ ? 'do'
1409
+ : '--isolated'
1410
+ : '--remote';
1339
1411
  const usage = isolatedNoRemote
1340
- ? '`do --isolated <slug>`'
1412
+ ? defaultedToIsolated
1413
+ ? '`do <slug>`'
1414
+ : '`do --isolated <slug>`'
1341
1415
  : '`do --remote <r> <slug>`';
1342
1416
  // The no-checkout forms now support the SAME variadic grammar the in-place
1343
1417
  // form does: a single NAMED item, MULTIPLE named items (sequential), and
@@ -1359,10 +1433,30 @@ export function buildProgram() {
1359
1433
  resolveDefaultArbiterForCwd(cwd, global, remoteFlags, override);
1360
1434
  const resolvedUrl = resolveArbiterUrlFromCheckout(cwd, arbiterName, identityEnv(bootstrapIdentity, process.env));
1361
1435
  if (resolvedUrl === undefined) {
1362
- console.error(`error: --isolated builds in a worktree off this repo's arbiter ` +
1363
- `('${arbiterName}'), but no such arbiter remote is configured/found ` +
1364
- `here. Run inside a participating repo (a clone with an arbiter ` +
1365
- `remote), or use --remote <url> to target another repo.`);
1436
+ // Isolated needs an arbiter to build off. Two message variants:
1437
+ // - defaulted-to-isolated (no form flag typed) leads with the DEFAULT
1438
+ // and names `--in-place` as the natural local escape (D2). This is
1439
+ // the ERROR-not-silent-degrade behaviour the flip requires: a repo
1440
+ // with no arbiter must NOT be quietly built in-place invisibly.
1441
+ // - explicit `--isolated` keeps its own terms (the user asked for
1442
+ // isolation deliberately) but ALSO surfaces `--in-place` alongside
1443
+ // `--remote <url>` as the two escapes.
1444
+ if (defaultedToIsolated) {
1445
+ console.error(`error: no arbiter is configured for this repo ` +
1446
+ `(no '${arbiterName}' remote), and \`do\` now defaults to ` +
1447
+ `building in an ISOLATED worktree off THIS repo's arbiter ` +
1448
+ `(task \`make-isolated-default-build-mode\`). Configure an ` +
1449
+ `arbiter (e.g. \`dorfl remote add <url>\`), pass --in-place ` +
1450
+ `to build in the current checkout, or use --remote <url> to ` +
1451
+ `target another repo.`);
1452
+ }
1453
+ else {
1454
+ console.error(`error: --isolated builds in a worktree off this repo's arbiter ` +
1455
+ `('${arbiterName}'), but no such arbiter remote is configured/found ` +
1456
+ `here. Run inside a participating repo (a clone with an arbiter ` +
1457
+ `remote), pass --in-place to build in the current checkout, or ` +
1458
+ `use --remote <url> to target another repo.`);
1459
+ }
1366
1460
  process.exit(1);
1367
1461
  }
1368
1462
  effectiveRemote = resolvedUrl;
@@ -1411,7 +1505,7 @@ export function buildProgram() {
1411
1505
  // Host-only runner IDENTITY — scopes git/provider ops only (not the
1412
1506
  // agent launch); absent ⇒ ambient.
1413
1507
  identity: remoteConfig.identity,
1414
- // `do --remote prd:<slug>` tasking-gate policy (task-build path ignores it).
1508
+ // `do --remote spec:<slug>` tasking-gate policy (task-build path ignores it).
1415
1509
  autoTask: remoteConfig.autoTask,
1416
1510
  // The resolved `promptGuidance` nudge — threaded into the remote worker
1417
1511
  // prompt (runRemotePipeline → buildAgentPrompt), mirroring in-place `do`.
@@ -1419,7 +1513,7 @@ export function buildProgram() {
1419
1513
  integration: remoteConfig.integration,
1420
1514
  // EXPLICIT `--merge` override for the untrusted-origin build-propose rule.
1421
1515
  explicitMerge: flagMode === 'merge',
1422
- // Per-TRANSITION TASKING override (the `do --remote prd:` tasking path).
1516
+ // Per-TRANSITION TASKING override (the `do --remote spec:` tasking path).
1423
1517
  taskingIntegration: remoteConfig.taskingIntegration,
1424
1518
  // TASK-PLACEMENT: the configured default + the EXPLICIT operator override
1425
1519
  // (`--tasks-land-in`), the top of the placement precedence — mirrors
@@ -1431,7 +1525,7 @@ export function buildProgram() {
1431
1525
  // Single-job build path: gate the REBASED tip (the default) unconditionally.
1432
1526
  freshWorktreeGate: remoteConfig.freshWorktreeGate,
1433
1527
  // Cross-job merge-serialiser CAS-retry cap (resolved through the per-repo
1434
- // chain on the arbiter-side `.dorfl.json` too) — prd
1528
+ // chain on the arbiter-side `.dorfl.json` too) — spec
1435
1529
  // `land-time-reverify-and-parallel-merge-ceiling` Story 5.
1436
1530
  mergeRetries: remoteConfig.mergeRetries,
1437
1531
  noPR: remoteConfig.noPR,
@@ -1448,7 +1542,7 @@ export function buildProgram() {
1448
1542
  agentCmd: remoteConfig.agentCmd,
1449
1543
  })
1450
1544
  : undefined,
1451
- // The tasker IMPROVER loop on the `do --remote prd:` path is ON by default
1545
+ // The tasker IMPROVER loop on the `do --remote spec:` path is ON by default
1452
1546
  // (auto-tasking has no `verify` floor, so the loop is the task path's
1453
1547
  // quality engine). `--tasker-loop`/`--no-tasker-loop` gates wiring the seam;
1454
1548
  // `taskerLoopMax`/`taskerLoopModel` resolve per-repo (flag > env > per-repo
@@ -1461,7 +1555,7 @@ export function buildProgram() {
1461
1555
  : undefined,
1462
1556
  taskerLoopMax: remoteConfig.taskerLoopMax,
1463
1557
  taskerLoopModel: remoteConfig.taskerLoopModel,
1464
- // The task-SET ACCEPTANCE GATE on the `do --remote prd:` path too.
1558
+ // The task-SET ACCEPTANCE GATE on the `do --remote spec:` path too.
1465
1559
  taskReviewGate: remoteConfig.review
1466
1560
  ? harnessTaskAcceptanceGate({
1467
1561
  harness: remoteHarness,
@@ -1469,6 +1563,11 @@ export function buildProgram() {
1469
1563
  })
1470
1564
  : undefined,
1471
1565
  watch: flags.watch === true,
1566
+ // The dorfl-internal agent deadline + anti-loop ceiling (spec
1567
+ // `graceful-pre-timeout-wip-checkpoint`). Threaded from the RESOLVED
1568
+ // remote-repo config (its committed `dorfl.json` on <arbiter>/main).
1569
+ agentDeadlineMinutes: remoteConfig.agentDeadlineMinutes,
1570
+ maxAutoCheckpoints: remoteConfig.maxAutoCheckpoints,
1472
1571
  color: shouldUseColor(process.stdout),
1473
1572
  note: (message) => console.error(`>> ${message}`),
1474
1573
  noteBlock: (message) => console.error(message),
@@ -1565,7 +1664,7 @@ export function buildProgram() {
1565
1664
  // ops (claim, push, integrate, `gh`) — NEVER the agent launch. Absent ⇒
1566
1665
  // ambient (today's behaviour). Mapped Config → DoOptions like model/agentCmd.
1567
1666
  identity: config.identity,
1568
- // `do prd:<slug>` tasking-gate policy (the task-build path ignores it).
1667
+ // `do spec:<slug>` tasking-gate policy (the task-build path ignores it).
1569
1668
  autoTask: config.autoTask,
1570
1669
  // The resolved `promptGuidance` NUDGE namespace (e.g. `testFirst`),
1571
1670
  // threaded into the worker prompt by performDo → buildAgentPrompt so a
@@ -1578,11 +1677,11 @@ export function buildProgram() {
1578
1677
  // typed `--merge` (`flagMode`), never when `merge` came from config — so an
1579
1678
  // untrusted-origin task still forces propose under a config `merge`.
1580
1679
  explicitMerge: flagMode === 'merge',
1581
- // Per-TRANSITION TASKING override: the `do prd:` tasking path threads
1680
+ // Per-TRANSITION TASKING override: the `do spec:` tasking path threads
1582
1681
  // `taskingIntegration ?? integration`; the task-build path stays on
1583
1682
  // `integration`. Unset ⇒ tasking falls back to `integration` (today's behaviour).
1584
1683
  taskingIntegration: config.taskingIntegration,
1585
- // TASK-PLACEMENT (`do prd:` tasking output): the configured default rung +
1684
+ // TASK-PLACEMENT (`do spec:` tasking output): the configured default rung +
1586
1685
  // the EXPLICIT operator override `--tasks-land-in` (top of the precedence).
1587
1686
  // `explicitTasksLandIn` is set ONLY when the flag was typed (mirrors
1588
1687
  // `explicitMerge`), so an untrusted-origin staging force still wins under a
@@ -1595,7 +1694,7 @@ export function buildProgram() {
1595
1694
  verify: config.verify,
1596
1695
  // Single-job build path: gate the REBASED tip (the default) unconditionally.
1597
1696
  freshWorktreeGate: config.freshWorktreeGate,
1598
- // Cross-job merge-serialiser CAS-retry cap (prd `land-time-reverify-and-
1697
+ // Cross-job merge-serialiser CAS-retry cap (spec `land-time-reverify-and-
1599
1698
  // parallel-merge-ceiling` Story 5 / Applied Answer q1 (a)) — resolved per-repo
1600
1699
  // and threaded to `performComplete`→`performIntegration`.
1601
1700
  mergeRetries: config.mergeRetries,
@@ -1619,7 +1718,7 @@ export function buildProgram() {
1619
1718
  reviewGate: config.review
1620
1719
  ? harnessReviewGate({ harness, agentCmd: config.agentCmd })
1621
1720
  : undefined,
1622
- // The tasker IMPROVER loop on the `do prd:` tasking path is ON by default
1721
+ // The tasker IMPROVER loop on the `do spec:` tasking path is ON by default
1623
1722
  // (auto-tasking has no `verify` floor — the loop is the task path's quality
1624
1723
  // engine). `--tasker-loop`/`--no-tasker-loop` gates wiring the seam;
1625
1724
  // `taskerLoopMax`/`taskerLoopModel` resolve per-repo (flag > env > per-repo
@@ -1642,11 +1741,16 @@ export function buildProgram() {
1642
1741
  // `--watch`: tail the pi session log live (pi harness only; the
1643
1742
  // performDo guard errors clearly on any other adapter). READ-ONLY.
1644
1743
  watch: flags.watch === true,
1744
+ // The dorfl-internal agent deadline + anti-loop ceiling (spec
1745
+ // `graceful-pre-timeout-wip-checkpoint`). Threaded from the resolved
1746
+ // per-repo config so an edit to dorfl.json reflects on the NEXT tick.
1747
+ agentDeadlineMinutes: config.agentDeadlineMinutes,
1748
+ maxAutoCheckpoints: config.maxAutoCheckpoints,
1645
1749
  color: shouldUseColor(process.stdout),
1646
1750
  note: (message) => console.error(`>> ${message}`),
1647
1751
  noteBlock: (message) => console.error(message),
1648
1752
  };
1649
- // `--allow-backlog` is EXPLICIT-SINGLE-TASK-ONLY (prd
1753
+ // `--allow-backlog` is EXPLICIT-SINGLE-TASK-ONLY (spec
1650
1754
  // `do-allow-backlog-drive-staged-tasks-without-promotion`, decision 4): it
1651
1755
  // drives ONE named staged task in place. It must NOT combine with the
1652
1756
  // AUTO-PICK (zero-args / -n) or MULTI-ITEM forms — those select FROM the
@@ -1700,33 +1804,33 @@ export function buildProgram() {
1700
1804
  process.exit(result.exitCode);
1701
1805
  });
1702
1806
  // `advance` — the SIBLING top-level verb (NOT a `do` subcommand; `do`
1703
- // subcommands + a standalone `task` verb are REJECTED in prd `advance-loop`).
1807
+ // subcommands + a standalone `task` verb are REJECTED in spec `advance-loop`).
1704
1808
  // It reuses the SAME shared `prefix:arg` resolver `do` uses, EXTENDED with the
1705
1809
  // `obs:` namespace, and wires the classify → lock → execute SKELETON: classify
1706
1810
  // the rung (read-only, no model, no lock), take the `advancing` CAS borrow, then
1707
- // dispatch winner-only — build/task rungs ORCHESTRATE `do`/`do prd:` (never a
1811
+ // dispatch winner-only — build/task rungs ORCHESTRATE `do`/`do spec:` (never a
1708
1812
  // duplicate), surface/apply/triage dispatch to a named executor seam later
1709
1813
  // tasks fill. The DRIVERS (one-shot/loop) + `-n` + per-action gates and the
1710
1814
  // rung BODIES are LATER tasks; the bare eligible-SET form errors clearly here.
1711
1815
  program
1712
1816
  .command('advance')
1713
1817
  .helpGroup(HEADLINE_GROUP)
1714
- .description('Advance work/ item(s) one lifecycle rung toward ready/built (PRD advance-loop), the SEQUENTIAL one-shot driver over the advance tick. advance <slug> (bare = the task) | advance spec:<slug> (the spec tasking rung; the legacy prd:<slug> is still accepted) | advance obs:<slug> (triage an observation) | advance (auto-pick one eligible) | advance <a> <b> (those, in sequence) | advance -n <x> (x eligible, in sequence). Each item: classify (read-only, no model, no lock) → take the `advancing` CAS lock → dispatch winner-only — build/task rungs ORCHESTRATE `do`/`do spec:`, surface/apply always run, triage respects observationTriage (off|ask|auto). The bare/`-n` selection respects the per-action gates (build→autoBuild, task→autoTask, triage→observationTriage); `-n` is ALWAYS sequential (parallelism is `run` / the CI matrix).')
1715
- .argument('[slugs...]', 'the item(s) to advance: bare (= the task), task:<slug>, spec:<slug> (the legacy prd:<slug> is still accepted), or obs:<slug> (an observation). Zero args = auto-pick one eligible; multiple = advance them in sequence.')
1818
+ .description('Advance work/ item(s) one lifecycle rung toward ready/built (SPEC advance-loop), the SEQUENTIAL one-shot driver over the advance tick. advance <slug> (bare = the task) | advance spec:<slug> (the spec tasking rung) | advance obs:<slug> (triage an observation) | advance (auto-pick one eligible) | advance <a> <b> (those, in sequence) | advance -n <x> (x eligible, in sequence). Each item: classify (read-only, no model, no lock) → take the `advancing` CAS lock → dispatch winner-only — build/task rungs ORCHESTRATE `do`/`do spec:`, surface/apply always run, triage respects observationTriage (off|ask|auto). The bare/`-n` selection respects the per-action gates (build→autoBuild, task→autoTask, triage→observationTriage); `-n` is ALWAYS sequential (parallelism is `run` / the CI matrix).')
1819
+ .argument('[slugs...]', 'the item(s) to advance: bare (= the task), task:<slug>, spec:<slug>, or obs:<slug> (an observation). Zero args = auto-pick one eligible; multiple = advance them in sequence.')
1716
1820
  .option('-c, --config <path>', 'config file path', defaultConfigPath())
1717
1821
  .option('--arbiter <remote>', 'name of the arbiter git remote (default: per-repo/global defaultArbiter)')
1718
1822
  .option('-n, --number <x>', 'AUTO-PICK x eligible items and advance them IN SEQUENCE (ordered by selectionOrder, default drain = tasks-first then prds-to-task). Sequential — never a parallelism knob (that is `run` / the CI matrix). Mutually exclusive with naming items.')
1719
1823
  .option('--isolated', "advance in an ISOLATED worktree off THIS repo's arbiter (inferred from cwd) instead of taking over the current checkout, then integrate + reap — the in-place-but-isolated form. Shares the same grammar: a single named item, multiple named items (in sequence), AND -n/auto-pick over the mirror-side eligible-pool scan. Always SEQUENTIAL (parallelism is `run` / the CI matrix). Lets you advance from a busy/dirty checkout or anywhere with a participating arbiter.")
1720
1824
  .option('--selection-order <order>', 'order the auto-pick pools (build/task/surface/triage; apply is always first): a preset keyword (drain (default) | groom) or an explicit comma-separated pool list. Resolved flag > env > per-repo > global > default.')
1721
1825
  .option('--observation-triage <mode>', 'the observation-inbox gate (off|ask|auto): off (default) leaves observations untouched (the triage pool is dropped from auto-pick); ask surfaces a promote/keep/delete question for each untriaged observation; auto auto-disposes the no-question cases (duplicate/map) and asks about the rest. Resolved flag > env > per-repo > global > default. An explicit `advance obs:<slug>` bypasses the selection gate and runs in ask-mode (auto-disposes only under `auto`).')
1722
- .option('--surface-blockers', 'the declared-blocked-work gate (the orthogonal peer of --observation-triage): render a task/prd carrying needsAnswers:true into an answerable question sidecar (the needsAnswers-blocked pool is enumerated into auto-pick). Resolved flag > env > per-repo > global > default off. An explicit `advance <slug>`/`advance prd:<slug>` bypasses this selection gate and surfaces regardless. Does NOT gate apply (an answered sidecar still applies) or needs-attention (always on).')
1723
- .option('--no-surface-blockers', 'leave a needsAnswers:true task/prd silently blocked (default; the blocked pool is dropped from auto-pick)')
1724
- .option('--strict-merge-approval', 'opt in to the host-agnostic "dismiss stale approvals on base change" discipline (prd `land-time-reverify-and-parallel-merge-ceiling` sidecar OQ6): when the merge-base CHANGED between the human’s merge-answer and the apply step, RE-SURFACE the merge-question (clear the answer back to no-answer; re-author the question on main/runner under the advancing lock) instead of auto-landing on a green re-verify. Default OFF (a green re-verify is trusted as sufficient; honour the prior answer). Story #16’s RED-re-verify refusal is UNCHANGED and independent of this flag. Resolved flag > env > per-repo > global > default off.')
1826
+ .option('--surface-blockers', 'the declared-blocked-work gate (the orthogonal peer of --observation-triage): render a task/spec carrying needsAnswers:true into an answerable question sidecar (the needsAnswers-blocked pool is enumerated into auto-pick). Resolved flag > env > per-repo > global > default off. An explicit `advance <slug>`/`advance spec:<slug>` bypasses this selection gate and surfaces regardless. Does NOT gate apply (an answered sidecar still applies) or needs-attention (always on).')
1827
+ .option('--no-surface-blockers', 'leave a needsAnswers:true task/spec silently blocked (default; the blocked pool is dropped from auto-pick)')
1828
+ .option('--strict-merge-approval', 'opt in to the host-agnostic "dismiss stale approvals on base change" discipline (spec `land-time-reverify-and-parallel-merge-ceiling` sidecar OQ6): when the merge-base CHANGED between the human’s merge-answer and the apply step, RE-SURFACE the merge-question (clear the answer back to no-answer; re-author the question on main/runner under the advancing lock) instead of auto-landing on a green re-verify. Default OFF (a green re-verify is trusted as sufficient; honour the prior answer). Story #16’s RED-re-verify refusal is UNCHANGED and independent of this flag. Resolved flag > env > per-repo > global > default off.')
1725
1829
  .option('--no-strict-merge-approval', 'honour the prior merge-answer and land when the rebased tip re-verifies GREEN even if the merge-base changed (default; the cheap green-re-verify-is-enough path)')
1726
1830
  .option('--merge-questions <mode>', 'the merge-question SURFACER gate (off|ask|auto): off drops the surfacer (only for a repo that lands by some other means); ask (default) enumerates unmerged `work/*` branches and surfaces a merge-question sidecar a human answers; auto self-supplies the `merge` answer and lands via the SAME deterministic apply-time re-verify (the merge-mode-like fast path). SEPARATE axis from --observation-triage with a HIGHER default (a dropped merge-question means pushed work never lands). Resolved flag > env > per-repo > global > default ask.')
1727
1831
  .option('--merge', 'integrate the advanced item(s) in merge mode this invocation (mutually exclusive with --propose; overrides config). The CI merge shape is a SINGLE SEQUENTIAL job, so this rides the `-n`/named-sequence path, never the matrix.')
1728
1832
  .option('--propose', 'integrate the advanced item(s) in propose mode this invocation (default; mutually exclusive with --merge; overrides config). The CI propose shape is the parallel matrix (one PR per item).')
1729
- .option('--tasks-land-in <where>', 'where `advance prd:<slug>` tasking output lands: `pre-backlog` (staged) or `ready` (the agent POOL). The EXPLICIT operator override at the top of the placement precedence. Resolved flag > env (DORFL_TASKS_LAND_IN) > per-repo > global > built-in.')
1833
+ .option('--tasks-land-in <where>', 'where `advance spec:<slug>` tasking output lands: `backlog` (staged) or `ready` (the agent POOL). The EXPLICIT operator override at the top of the placement precedence. Resolved flag > env (DORFL_TASKS_LAND_IN) > per-repo > global > built-in.')
1730
1834
  .option('--watch', "stream the build agent's high-signal events live by tailing the pi session log (requires harness: pi; READ-ONLY observer — does not change outcome/gate/git). The same view `do --watch` gives, threaded through the build rung; CI uses it so the job log shows the agent working instead of freezing.")
1731
1835
  .action(async (rawSlugs, flags) => {
1732
1836
  // Variadic grammar (mirrors `do`): zero args = AUTO-PICK; one = the single
@@ -1842,7 +1946,7 @@ export function buildProgram() {
1842
1946
  integration: remoteConfig.integration,
1843
1947
  // EXPLICIT `--merge` override for the untrusted-origin build-propose rule.
1844
1948
  explicitMerge: flagMode === 'merge',
1845
- // Per-TRANSITION TASKING override (the isolated `do --remote prd:` path).
1949
+ // Per-TRANSITION TASKING override (the isolated `do --remote spec:` path).
1846
1950
  taskingIntegration: remoteConfig.taskingIntegration,
1847
1951
  // TASK-PLACEMENT: configured default + EXPLICIT `--tasks-land-in` override
1848
1952
  // (set only when typed, mirroring `explicitMerge`).
@@ -1852,7 +1956,7 @@ export function buildProgram() {
1852
1956
  verify: remoteConfig.verify,
1853
1957
  // Single-job build path: gate the REBASED tip (the default) unconditionally.
1854
1958
  freshWorktreeGate: remoteConfig.freshWorktreeGate,
1855
- // Cross-job merge-serialiser CAS-retry cap — prd
1959
+ // Cross-job merge-serialiser CAS-retry cap — spec
1856
1960
  // `land-time-reverify-and-parallel-merge-ceiling` Story 5.
1857
1961
  mergeRetries: remoteConfig.mergeRetries,
1858
1962
  noPR: remoteConfig.noPR,
@@ -1980,7 +2084,7 @@ export function buildProgram() {
1980
2084
  // EXPLICIT `--merge` override for the untrusted-origin build-propose rule (a
1981
2085
  // bare `advance` auto-pick passes no flag ⇒ unset ⇒ untrusted forces propose).
1982
2086
  explicitMerge: flagMode === 'merge',
1983
- // Per-TRANSITION TASKING override (the `do prd:` tasking path threads
2087
+ // Per-TRANSITION TASKING override (the `do spec:` tasking path threads
1984
2088
  // `taskingIntegration ?? integration`; the build path stays on `integration`).
1985
2089
  taskingIntegration: config.taskingIntegration,
1986
2090
  // TASK-PLACEMENT: configured default + EXPLICIT `--tasks-land-in` override
@@ -1991,7 +2095,7 @@ export function buildProgram() {
1991
2095
  verify: config.verify,
1992
2096
  // Single-job build path: gate the REBASED tip (the default) unconditionally.
1993
2097
  freshWorktreeGate: config.freshWorktreeGate,
1994
- // Cross-job merge-serialiser CAS-retry cap (prd `land-time-reverify-and-
2098
+ // Cross-job merge-serialiser CAS-retry cap (spec `land-time-reverify-and-
1995
2099
  // parallel-merge-ceiling` Story 5 / Applied Answer q1 (a)).
1996
2100
  mergeRetries: config.mergeRetries,
1997
2101
  noPR: config.noPR,
@@ -2013,6 +2117,10 @@ export function buildProgram() {
2013
2117
  taskReviewGate: config.review
2014
2118
  ? harnessTaskAcceptanceGate({ harness, agentCmd: config.agentCmd })
2015
2119
  : undefined,
2120
+ // The dorfl-internal agent deadline + anti-loop ceiling (spec
2121
+ // `graceful-pre-timeout-wip-checkpoint`). Threaded from resolved config.
2122
+ agentDeadlineMinutes: config.agentDeadlineMinutes,
2123
+ maxAutoCheckpoints: config.maxAutoCheckpoints,
2016
2124
  color: shouldUseColor(process.stdout),
2017
2125
  note: (message) => console.error(`>> ${message}`),
2018
2126
  noteBlock: (message) => console.error(message),
@@ -2109,7 +2217,7 @@ export function buildProgram() {
2109
2217
  .option('--force', 'OVERRIDE the predicate: remove worktrees even with un-saved work (requires --yes; never the default)')
2110
2218
  .option('--yes', 'confirm a destructive --force sweep non-interactively')
2111
2219
  .option('--ledger [repoPath]', 'SWEEP the work/ lifecycle LEDGER instead of job worktrees: REPORT (never delete) every slug present in more than one work/ status folder (the one-slug-one-folder belt-and-suspenders), with its folders + candidate canonical folder, for a HUMAN to resolve. Defaults to the cwd repo.')
2112
- .option('--reap-stale-locks', '(with --ledger) OPT-IN: also CLEAR every STALE terminal lock the report finds (a held `active` per-item lock whose item is already TERMINAL on <arbiter>/main — the `cleared-stale` class) via the SAME leased delete `release-lock` uses, so one command sweeps all orphaned terminal locks instead of N hand-run release-locks. SCOPED to `cleared-stale` ONLY: a `kept-stuck` (terminal + stuck) or a `kept-in-flight` (active, non-terminal) lock is NEVER reaped, even with this flag. A concurrent change to a lock ref makes its leased delete REJECT (reported), never --force. WITHOUT this flag `gc --ledger` stays report-only (fail-loud, deletes nothing).')
2220
+ .option('--reap-stale-locks', '(with --ledger) OPT-IN: also CLEAR every ORPHAN lock the report finds via the SAME leased delete `release-lock` uses, so one command sweeps all orphaned locks instead of N hand-run release-locks. Reaps the `cleared-stale` class: a held `active` lock that is EITHER terminal on `<arbiter>/main` (stranded between the durable move and the release) OR non-terminal + SURFACED (`needsAnswers:true` + sidecar on `<arbiter>/main`) = the crash-window orphan the ordered bounce transition leaves when step 1 lands but step 2 never runs. A `kept-in-flight` (active + non-terminal + NOT surfaced — a healthy build) is NEVER reaped, even with this flag. A concurrent change to a lock ref makes its leased delete REJECT (reported), never --force. WITHOUT this flag `gc --ledger` stays report-only (fail-loud, deletes nothing).')
2113
2221
  .option('--remote-branches', 'SWEEP the arbiter’s remote work/* BRANCHES instead of job worktrees: delete (via git push --delete, NEVER --force) exactly those PROVABLY MERGED into <arbiter>/main (git merge-base --is-ancestor, the SAME predicate the worktree reaper uses), and RETAIN the rest with a reason. An in-flight/un-merged branch (the recovery point) is NEVER touched. Provider-agnostic plain git — works on a --bare arbiter. The merged-only complement of `requeue --reset`.')
2114
2222
  .option('--arbiter <remote>', '(with --remote-branches) the arbiter git remote whose work/* branches to sweep (default: origin); resolved from --cwd')
2115
2223
  .option('--cwd <dir>', '(with --remote-branches) the local repo/clone whose --arbiter remote points at the arbiter to sweep (default: cwd); only remote refs are read + deleted, never the working tree')
@@ -2118,7 +2226,7 @@ export function buildProgram() {
2118
2226
  .action(async (flags) => {
2119
2227
  const config = resolveGlobalConfig(loadConfig(flags.config), {});
2120
2228
  const workspacesDir = flags.workspace ?? config.workspacesDir;
2121
- // The `gc`-STYLE ledger SWEEP (prd `ledger-integrity` story 3): a SEPARATE
2229
+ // The `gc`-STYLE ledger SWEEP (spec `ledger-integrity` story 3): a SEPARATE
2122
2230
  // surface from the worktree reaper below — it REPORTS one-slug-one-folder
2123
2231
  // violations in a repo's `work/` lifecycle ledger and NEVER deletes (a human
2124
2232
  // resolves each). Distinct `work/`: the ledger, not the execution substrate.
@@ -2126,7 +2234,7 @@ export function buildProgram() {
2126
2234
  const repoPath = typeof flags.ledger === 'string' ? flags.ledger : process.cwd();
2127
2235
  const result = sweepLedgerDuplicates(repoPath);
2128
2236
  // The UNIFIED-LOCK stuck/orphaned-lock REPORT (task
2129
- // `release-lock-verb-and-gc-stuck-report`, prd
2237
+ // `release-lock-verb-and-gc-stuck-report`, spec
2130
2238
  // `ledger-status-per-item-lock-refs` US #12/#13/#14): generalises the
2131
2239
  // advancing-marker report from advancing-only to the unified per-item
2132
2240
  // lock. The locks live on the ARBITER ref (`refs/dorfl/lock/*`),
@@ -2139,13 +2247,16 @@ export function buildProgram() {
2139
2247
  // clearing (no auto-sweep; a human asserts a lock is dead via
2140
2248
  // `release-lock`).
2141
2249
  // OPT-IN SWEEP (`--reap-stale-locks`): the WRITE twin of the report. A
2142
- // human asserting "clear the dead TERMINAL locks now": for EXACTLY the
2143
- // `cleared-stale` class (terminal-on-main + active = stranded) perform the
2144
- // SAME leased delete `release-lock` / the recovery use, so one command
2145
- // sweeps every orphaned terminal lock. A `kept-stuck` / `kept-in-flight`
2146
- // lock is NEVER reaped (scope fence); a concurrent change makes a clear
2147
- // REJECT (reported `lost`), never --force. WITHOUT the flag the surface
2148
- // below stays report-only (fail-loud, deletes nothing).
2250
+ // human asserting "clear the dead TERMINAL locks now": for BOTH
2251
+ // terminal-on-main orphan classes (task `reaper-reap-terminal-stuck-lock-orphans`;
2252
+ // ADR `ledger-status-on-per-item-lock-refs` § Addendum 2026-07-10) the
2253
+ // stranded `cleared-stale` AND the stuck-terminal `cleared-stuck-terminal`
2254
+ // crash-orphan perform the SAME leased delete `release-lock` / the
2255
+ // recovery use, so one command sweeps every orphaned terminal lock. A
2256
+ // `kept-stuck` (STUCK + NON-terminal) / `kept-in-flight` lock is NEVER
2257
+ // reaped (scope fence); a concurrent change makes a clear REJECT (reported
2258
+ // `lost`), never --force. WITHOUT the flag the surface below stays
2259
+ // report-only (fail-loud, deletes nothing).
2149
2260
  if (flags.reapStaleLocks) {
2150
2261
  const reap = await reapStaleItemLocks(flags.cwd ?? repoPath, flags.arbiter ?? 'origin', process.env);
2151
2262
  if (flags.json) {
@@ -2164,10 +2275,11 @@ export function buildProgram() {
2164
2275
  ? blocks.join('\n\n')
2165
2276
  : formatLedgerSweep(result));
2166
2277
  }
2167
- // Fail-loud AFTER the sweep: a `kept-stuck` (rightly left for a human) or
2168
- // a `lost`/`error` (a stale lock whose leased delete lost the race) still
2169
- // needs attention; a clean sweep that reaped every stale lock and left
2170
- // only healthy in-flight holds exits 0.
2278
+ // Fail-loud AFTER the sweep: a `kept-stuck` (STUCK + non-terminal
2279
+ // rightly left for a human) or a `lost`/`error` (an orphan lock whose
2280
+ // leased delete lost the race) still needs attention; a clean sweep
2281
+ // that reaped every orphan lock (stale OR stuck-terminal) and left only
2282
+ // healthy in-flight holds exits 0.
2171
2283
  process.exit(result.duplicates.length > 0 || reapReportNeedsAttention(reap)
2172
2284
  ? 1
2173
2285
  : 0);
@@ -2202,14 +2314,16 @@ export function buildProgram() {
2202
2314
  // ALL are REPORTED here (never auto-deleted — no automatic sweep exists; a
2203
2315
  // human clears a NAMED unified lock via `release-lock`).
2204
2316
  //
2205
- // SCOPED to the ATTENTION verdicts only (prd US#14/#21, ADR
2206
- // `ledger-status-on-per-item-lock-refs`: this surface is the STUCK /
2207
- // crash-orphaned lock, NOT every held one): a `kept-stuck` (terminal +
2208
- // stuck) or a `cleared-stale`-eligible (terminal + stale active = orphaned)
2209
- // lock fails loud, but a `kept-in-flight` (active, non-terminal) lock is the
2210
- // NORMAL in-flight state of a healthy concurrent build (read by `status` as
2211
- // healthy) it is reported informationally and does NOT make a routine
2212
- // `gc --ledger` health check exit non-zero.
2317
+ // SCOPED to the ATTENTION verdicts only (spec US#14/#21, ADR
2318
+ // `ledger-status-on-per-item-lock-refs` § Addendum 2026-07-10: this surface
2319
+ // is the STUCK / crash-orphaned lock, NOT every held one): a `kept-stuck`
2320
+ // (STUCK + NON-terminal the genuine human-attention case), a
2321
+ // `cleared-stale`-eligible (terminal + stale active = stranded orphan), or
2322
+ // a `cleared-stuck-terminal`-eligible (terminal + stuck = crash-orphan the
2323
+ // reaper can now auto-clear) fails loud, but a `kept-in-flight` (active,
2324
+ // non-terminal) lock is the NORMAL in-flight state of a healthy concurrent
2325
+ // build (read by `status` as healthy) — it is reported informationally
2326
+ // and does NOT make a routine `gc --ledger` health check exit non-zero.
2213
2327
  process.exit(result.duplicates.length > 0 ||
2214
2328
  itemLockReportNeedsAttention(lockReport)
2215
2329
  ? 1
@@ -2229,7 +2343,7 @@ export function buildProgram() {
2229
2343
  dryRun: flags.dryRun === true,
2230
2344
  note: (message) => console.error(`>> ${message}`),
2231
2345
  });
2232
- // The ORPHAN-SIDECAR sweep (prd
2346
+ // The ORPHAN-SIDECAR sweep (spec
2233
2347
  // `agentic-question-resolution-retire-disposition-vocabulary`, US #10) rides
2234
2348
  // the SAME `--remote-branches` invocation the SCHEDULED CI lifecycle workflow
2235
2349
  // runs (`dorfl gc --remote-branches --arbiter origin`) — so the reap of a
@@ -2328,7 +2442,7 @@ export function buildProgram() {
2328
2442
  printPrdToSpecReport(result);
2329
2443
  }
2330
2444
  // Exit non-zero on a REFUSAL (quiescence gate) or a non-green leak scan
2331
- // (a converted tree that still carries a dangling `prd` ref). A clean
2445
+ // (a converted tree that still carries a dangling ''prd'' ref). A clean
2332
2446
  // dry-run or a green migration exits 0.
2333
2447
  if (result.refused || result.leaks.length > 0) {
2334
2448
  process.exit(1);
@@ -2420,14 +2534,16 @@ export function buildProgram() {
2420
2534
  program
2421
2535
  .command('requeue <slug>')
2422
2536
  .helpGroup(HEADLINE_GROUP)
2423
- .description('Requeue a STUCK task to the backlog for re-claiming (ADR §12/§14). Recovers a task whose per-item lock is held — stuck (the resolved-recovery path: a previously-routed needs-attention item, now lock `state: stuck`) OR active (a claim that never surfaced — an un-surfaced abort, a killed run, or an in-place requeue note). The body rests in work/tasks/backlog/<slug>.md (claim never moves it under the per-item-lock model); requeue releases the lock so the item is claimable again. The release is published as a TREE-LESS compare-and-swap to the arbiter ref, EXACTLY like claim — it NEVER stages or commits in the cwd working tree, so a requeue in a shared checkout can never sweep up a concurrent writer’s uncommitted files. DEFAULT = keep + continue: leave the work/<slug> branch UNTOUCHED so the next claim CONTINUES from its tip (rebased onto fresh main at onboard-time). --reset = discard + fresh: delete the remote work/<slug> branch FIRST (then release the lock) so the next claim starts fresh (guarded; never the default). -m/--message appends a dated handoff note to the item body (both modes; append-only).')
2537
+ .description('Requeue a STUCK task to the backlog for re-claiming (ADR §12/§14). Recovers a task whose per-item lock is held — stuck (the resolved-recovery path: a previously-routed needs-attention item, now lock `state: stuck`) OR active (a claim that never surfaced — an un-surfaced abort, a killed run, or an in-place requeue note). The body rests in work/tasks/backlog/<slug>.md (claim never moves it under the per-item-lock model); requeue releases the lock so the item is claimable again. The release is published as a TREE-LESS compare-and-swap to the arbiter ref, EXACTLY like claim — it NEVER stages or commits in the cwd working tree, so a requeue in a shared checkout can never sweep up a concurrent writer’s uncommitted files. Escalation ladder (least to most destructive): (1) DEFAULT = keep + continue: leave the work/<slug> branch UNTOUCHED so the next claim CONTINUES from its tip (rebased onto fresh main at onboard-time); if the work/<slug> branch is not on the arbiter (never pushed, or a prior --reset already deleted it), default requeue succeeds anyway with a fresh-claim note. (2) --reconcile (alias --rebase) = NON-DESTRUCTIVE recovery: re-sync the mirror to the arbiter (prune-fetch) and RETRY the rebase of the kept branch onto latest arbiter/main in a scratch worktree; on a clean rebase, push the reconciled tip back (--force-with-lease, work branch only) and release the lock; on a genuine content conflict AFTER the clean re-sync, leave the item stuck and the branch UNTOUCHED (nothing deleted) so the human can retry when the churn settles. NEVER deletes the remote branch. (3) --reset = DESTRUCTIVE last resort: delete the remote work/<slug> branch FIRST (then release the lock) so the next claim starts fresh (guarded; never the default). -m/--message appends a dated handoff note to the item body (all modes; append-only).')
2424
2538
  .option('-c, --config <path>', 'config file path', defaultConfigPath())
2425
2539
  .option('--cwd <dir>', 'the repo/working clone whose work/ tree the arbiter remote is resolved FROM (default: cwd) — an ORIGIN SOURCE only; the move is published to the arbiter, never to this tree')
2426
2540
  .option('--arbiter <remote>', 'the arbiter git remote the tree-less move is CAS-published to (default: origin). --cwd resolves this remote; the move is never written to the cwd tree.')
2427
- .option('--reset', 'DISCARD the kept work: delete the remote work/<slug> branch FIRST, then move to backlog so the next claim starts FRESH (guarded; a deliberate departure from the never-delete-the-remote-branch invariant). Never the default.')
2541
+ .option('--reconcile', 'NON-DESTRUCTIVE recovery (middle rung, between the default keep+continue and the destructive --reset): re-sync the mirror to the arbiter (prune-fetch) and RETRY the rebase of the kept work/<slug> onto latest arbiter/main in a scratch worktree; on success, push the reconciled tip back with --force-with-lease and release the lock; on a genuine content conflict after the clean re-sync, leave the item stuck and the branch UNTOUCHED (nothing deleted). NEVER deletes the remote branch. Incompatible with --reset.')
2542
+ .option('--rebase', 'alias of --reconcile (canonical name)')
2543
+ .option('--reset', 'DESTRUCTIVE last resort: DISCARD the kept work by deleting the remote work/<slug> branch FIRST, then move to backlog so the next claim starts FRESH (guarded; a deliberate departure from the never-delete-the-remote-branch invariant). Never the default; try --reconcile first.')
2428
2544
  .option('-m, --message <note>', 'append a dated handoff note to the item body for the next agent (append-only; applies to both default and --reset)')
2429
2545
  .action(async (rawSlug, flags) => {
2430
- // Task-only command (§3a): accept bare + `task:`, reject `prd:`.
2546
+ // Task-only command (§3a): accept bare + `task:`, reject `spec:`.
2431
2547
  const slug = resolveTaskOnlySlug(rawSlug);
2432
2548
  const cwd = flags.cwd ?? process.cwd();
2433
2549
  // Route the requeue (default keep+continue / --reset discard / -m handoff)
@@ -2442,6 +2558,12 @@ export function buildProgram() {
2442
2558
  // call site, rather than relying on the seam's silent `?? process.env`
2443
2559
  // default by omission (the implicit fallback that made `requeue`'s human
2444
2560
  // attribution accidental rather than declared).
2561
+ const reconcile = flags.reconcile === true || flags.rebase === true;
2562
+ if (reconcile && flags.reset) {
2563
+ console.error('error: --reconcile and --reset are mutually exclusive ' +
2564
+ '(non-destructive recovery vs destructive last resort). Pick one.');
2565
+ process.exit(1);
2566
+ }
2445
2567
  const result = await ledgerWrite.applyReturnToBacklogTransition({
2446
2568
  cwd,
2447
2569
  slug,
@@ -2450,6 +2572,7 @@ export function buildProgram() {
2450
2572
  // `--cwd` is purely the ORIGIN SOURCE the remote is resolved from.
2451
2573
  arbiter: flags.arbiter ?? 'origin',
2452
2574
  reset: flags.reset,
2575
+ reconcile,
2453
2576
  message: flags.message,
2454
2577
  env: process.env,
2455
2578
  note: (message) => console.error(`>> ${message}`),
@@ -2460,15 +2583,17 @@ export function buildProgram() {
2460
2583
  }
2461
2584
  const how = result.deletedRemoteBranch
2462
2585
  ? ` (--reset: deleted the remote ${workBranchRef('task', slug)} branch; next claim starts fresh)`
2463
- : ' (kept the work branch; next claim continues from its tip)';
2586
+ : result.reconciled
2587
+ ? ` (--reconcile: re-synced mirror + rebased ${workBranchRef('task', slug)} onto latest main and pushed the reconciled tip back; next claim continues from it)`
2588
+ : ' (kept the work branch; next claim continues from its tip)';
2464
2589
  console.log(`Requeued '${slug}' to backlog for re-claiming.${how}`);
2465
2590
  });
2466
- // `promote [item]` (prd `staging-pool-position-gate-and-trust-model`, tasks
2591
+ // `promote [item]` (spec `staging-pool-position-gate-and-trust-model`, tasks
2467
2592
  // `pre-backlog-staging-folder-and-promote-step-a` /
2468
2593
  // `pre-prd-staging-pool-split-and-untrusted-prd-placement`): the HUMAN/runner-
2469
2594
  // owned verb that moves a STAGED item into its agent-eligible POOL — a task
2470
- // `work/pre-backlog/<slug>.md → work/backlog/<slug>.md`, a prd
2471
- // `work/prds/proposed/<slug>.md → work/prds/ready/<slug>.md` — as a tree-less CAS on the
2595
+ // `work/tasks/backlog/<slug>.md → work/tasks/ready/<slug>.md`, a spec
2596
+ // `work/specs/proposed/<slug>.md → work/specs/ready/<slug>.md` — as a tree-less CAS on the
2472
2597
  // arbiter, the SAME trust model + mechanism as `requeue`. The agent emits STAGED;
2473
2598
  // only this verb (a human, or the runner) admits it to the pool. With NO argument
2474
2599
  // it LISTS what is promotable (the "what is staged waiting for me?" discovery), so
@@ -2476,7 +2601,7 @@ export function buildProgram() {
2476
2601
  program
2477
2602
  .command('promote [item]')
2478
2603
  .helpGroup(HEADLINE_GROUP)
2479
- .description('Admit a STAGED item into its agent-eligible POOL (the runner/human side of the staging gate): a task `work/pre-backlog/<slug>.md → work/backlog/<slug>.md`, a prd `work/prds/proposed/<slug>.md → work/prds/ready/<slug>.md`, published as a TREE-LESS compare-and-swap to the arbiter ref (EXACTLY like requeue/claim — it never stages/commits in the cwd tree). The agent only ever CREATES staged; this verb is the gate a human (or the runner) opens. Accepts `task:<slug>` / `prd:<slug>` / a bare `<slug>` (= task). With NO argument, LISTS every promotable item (the tasks in pre-backlog/ + the prds in prds/proposed/ on the arbiter) so you can see what is staged waiting for promotion. Idempotent: promoting an already-pooled slug is a clean no-op success.')
2604
+ .description(`Admit a STAGED item into its agent-eligible POOL (the runner/human side of the staging gate): a task \`${workFolderPrefix('tasks-backlog')}<slug>.md → ${workFolderPrefix('tasks-ready')}<slug>.md\`, a spec \`${workFolderPrefix('specs-proposed')}<slug>.md → ${workFolderPrefix('specs-ready')}<slug>.md\`, published as a TREE-LESS compare-and-swap to the arbiter ref (EXACTLY like requeue/claim — it never stages/commits in the cwd tree). The agent only ever CREATES staged; this verb is the gate a human (or the runner) opens. Accepts \`task:<slug>\` / \`spec:<slug>\` / a bare \`<slug>\` (= task). With NO argument, LISTS every promotable item (the tasks in ${workFolderPrefix('tasks-backlog')} + the specs in ${workFolderPrefix('specs-proposed')} on the arbiter) so you can see what is staged waiting for promotion. Idempotent: promoting an already-pooled slug is a clean no-op success.`)
2480
2605
  .option('-c, --config <path>', 'config file path', defaultConfigPath())
2481
2606
  .option('--cwd <dir>', 'the repo/working clone whose arbiter remote the tree-less move is resolved FROM (default: cwd) — an ORIGIN SOURCE only; the move is published to the arbiter, never to this tree')
2482
2607
  .option('--arbiter <remote>', 'the arbiter git remote the promotion is CAS-published to / the staging folders are listed from (default: origin)')
@@ -2495,7 +2620,7 @@ export function buildProgram() {
2495
2620
  process.exit(1);
2496
2621
  }
2497
2622
  if (listed.items.length === 0) {
2498
- console.log(`Nothing staged to promote on ${arbiter}/main (work/pre-backlog/ and work/prds/proposed/ are empty).`);
2623
+ console.log(`Nothing staged to promote on ${arbiter}/main (${workFolderPrefix('tasks-backlog')} and ${workFolderPrefix('specs-proposed')} are empty).`);
2499
2624
  return;
2500
2625
  }
2501
2626
  console.log('Staged, awaiting promotion (run `promote <item>`):');
@@ -2504,11 +2629,10 @@ export function buildProgram() {
2504
2629
  }
2505
2630
  return;
2506
2631
  }
2507
- // AN ITEM → promote it. `task:`/`spec:` are explicit (the legacy `prd:`
2508
- // prefix is still ACCEPTED as an input alias through the cutover — the
2509
- // contract task drops it); a bare slug defaults to a task (mirrors
2510
- // `requeue`). An `obs:`/`observation:` prefix is rejected (observations have
2511
- // no pool).
2632
+ // AN ITEM → promote it. `task:`/`spec:` are explicit; a bare slug defaults to
2633
+ // a task (mirrors `requeue`). An `obs:`/`observation:` prefix is rejected
2634
+ // (observations have no pool). A legacy ''prd:'' prefix is a DEAD namespace token
2635
+ // after the hard cutover it resolves as a bare literal task slug, not a spec.
2512
2636
  const parsed = parseSlugArg(rawItem);
2513
2637
  if (parsed.explicit === 'observation') {
2514
2638
  console.error(`error: promote takes a task or spec, not an observation ('${rawItem}'). Observations have no agent pool.`);
@@ -2535,7 +2659,7 @@ export function buildProgram() {
2535
2659
  // the `work/advancing/<entry>.md` marker is gone and an advance hold is now just
2536
2660
  // `action: advance` on the UNIFIED per-item lock, so `release-lock <item>` (below)
2537
2661
  // is the SOLE named human release for ALL holds (implement/task/advance).
2538
- // `release-lock <item>` (task `release-lock-verb-and-gc-stuck-report`, prd
2662
+ // `release-lock <item>` (task `release-lock-verb-and-gc-stuck-report`, spec
2539
2663
  // `ledger-status-per-item-lock-refs` US #14): the HUMAN-invoked named release of
2540
2664
  // a stuck/orphaned UNIFIED per-item lock (`refs/dorfl/lock/<entry>`) —
2541
2665
  // the GENERALISATION of `release-advancing` from the advancing-only marker to
@@ -2548,31 +2672,67 @@ export function buildProgram() {
2548
2672
  // Idempotent: deleting an absent ref is a clean exit-0 "nothing to clear"
2549
2673
  // (`not-held`), NOT a failure — deleting the lock ref(s) is "all locks released"
2550
2674
  // and recoverable (the work is safe on the `work/<slug>` branches + `main`).
2675
+ //
2676
+ // ESCAPE HATCH (`--entry <literal>`, task
2677
+ // `release-lock-entry-escape-hatch-and-literal-entry-reporting`): the item
2678
+ // positional is OPTIONAL because a lock whose entry name is NOT derivable from
2679
+ // any current item-form (a PRE-CUTOVER `slice-<slug>` / `prd-<slug>` entry minted
2680
+ // before the slice→task / `prd-to-spec` vocabulary cutover) is UN-NAMEABLE through
2681
+ // the item-form path. `--entry` takes the literal `<entry>` VERBATIM (bypassing
2682
+ // the namespace mapping) so a human can still NAME + clear such an orphan without
2683
+ // the raw `git push origin --delete refs/dorfl/lock/…` plumbing. It reuses the
2684
+ // SAME leased-delete path (`releaseLiteralLockEntry` → the shared entry-keyed
2685
+ // core), so the trust model is unchanged: the human still asserts liveness.
2686
+ // EXACTLY ONE of {item positional, --entry} must be given.
2551
2687
  program
2552
- .command('release-lock <item>')
2688
+ .command('release-lock [item]')
2553
2689
  .helpGroup(HEADLINE_GROUP)
2554
- .description('Clear a NAMED stuck/orphaned UNIFIED per-item lock (refs/dorfl/lock/<entry>) by DELETING the ref on the arbiter — the recovery verb for a lock the system orphaned (a crashed build/task/advance that left the hold behind). The generalisation of `release-advancing` from the advancing marker to the ONE lock per item. Same trust model as `requeue`: a HUMAN asserts the lock is dead by NAMING it; the tool never guesses liveness (the lock has NO heartbeat, so there is NO automatic sweep / age-based reaper anywhere). Accepts the same item forms as the lock API: `task:<slug>` / `prd:<slug>` / `obs:<slug>` / a bare `<slug>` (= task). Idempotent — re-running on an already-cleared lock is a clean exit-0 no-op (deleting the lock ref is “all locks released”, recoverable). NEVER `--force`. Discoverable via `gc --ledger` (it REPORTS every lingering lock, never deletes).')
2690
+ .description('Clear a NAMED stuck/orphaned UNIFIED per-item lock (refs/dorfl/lock/<entry>) by DELETING the ref on the arbiter — the recovery verb for a lock the system orphaned (a crashed build/task/advance that left the hold behind). The generalisation of `release-advancing` from the advancing marker to the ONE lock per item. Same trust model as `requeue`: a HUMAN asserts the lock is dead by NAMING it; the tool never guesses liveness (the lock has NO heartbeat, so there is NO automatic sweep / age-based reaper anywhere). Accepts the same item forms as the lock API: `task:<slug>` / `spec:<slug>` / `obs:<slug>` / a bare `<slug>` (= task). ESCAPE HATCH: `--entry <literal>` (no item positional) targets a LITERAL entry name directly, bypassing the namespace mapping — for a lock whose entry name is not derivable from any current item-form (a pre-vocabulary-cutover `slice-<slug>` / `prd-<slug>` prefix, or a future rename); this is the supported way to clear such orphans, so the raw `git push origin --delete refs/dorfl/lock/…` plumbing is no longer required. Idempotent — re-running on an already-cleared lock is a clean exit-0 no-op (deleting the lock ref is “all locks released”, recoverable). NEVER `--force`. Discoverable via `gc --ledger` (it REPORTS every lingering lock and, for a pre-cutover entry, the exact `release-lock --entry <literal>` invocation).')
2555
2691
  .option('-c, --config <path>', 'config file path', defaultConfigPath())
2692
+ .option('--entry <literal>', 'ESCAPE HATCH: the LITERAL lock-entry name (`refs/dorfl/lock/<literal>`) to release VERBATIM, bypassing the namespace mapping — for a lock whose entry name is not derivable from any current item-form (a pre-cutover `slice-<slug>` / `prd-<slug>` entry). Mutually exclusive with the item positional (give EXACTLY one). Validated to `[A-Za-z0-9._-]+` (no slashes/whitespace) so it cannot escape the lock namespace.')
2556
2693
  .option('--cwd <dir>', 'the repo/working clone whose arbiter remote the lock ref is DELETED on (default: cwd)')
2557
2694
  .option('--arbiter <remote>', 'the arbiter git remote the lock ref is deleted on (default: origin)')
2558
2695
  .action(async (item, flags) => {
2559
2696
  const cwd = flags.cwd ?? process.cwd();
2560
2697
  const arbiter = flags.arbiter ?? 'origin';
2561
- const result = await releaseItemLock({
2562
- item,
2563
- cwd,
2564
- arbiter,
2565
- env: process.env,
2566
- });
2698
+ // MUTUAL EXCLUSION: exactly one of {item positional, --entry}. BOTH or
2699
+ // NEITHER is a usage error (non-zero exit, actionable message) — the
2700
+ // item-form and the literal escape hatch are two ways to name ONE lock, not
2701
+ // combinable.
2702
+ if (item !== undefined && flags.entry !== undefined) {
2703
+ console.error('error: give EITHER an item (task:<slug> / spec:<slug> / obs:<slug> / <slug>) OR --entry <literal>, not both.');
2704
+ process.exit(1);
2705
+ }
2706
+ if (item === undefined && flags.entry === undefined) {
2707
+ console.error('error: name the lock to release — an item (task:<slug> / spec:<slug> / obs:<slug> / <slug>) OR --entry <literal> for a pre-cutover entry with no current item-form.');
2708
+ process.exit(1);
2709
+ }
2710
+ // The LITERAL escape-hatch path (`--entry`): take the entry name verbatim,
2711
+ // bypassing the namespace mapping, and reuse the SAME leased-delete core.
2712
+ const result = flags.entry !== undefined
2713
+ ? await releaseLiteralLockEntry({
2714
+ entry: flags.entry,
2715
+ cwd,
2716
+ arbiter,
2717
+ env: process.env,
2718
+ })
2719
+ : await releaseItemLock({
2720
+ item: item,
2721
+ cwd,
2722
+ arbiter,
2723
+ env: process.env,
2724
+ });
2567
2725
  if (result.outcome === 'released') {
2568
2726
  console.log(`Released lock '${result.entry}' (${result.ref} deleted on ${arbiter}; the item itself was untouched — it rests on main / its work/<slug> branch).`);
2569
2727
  return;
2570
2728
  }
2571
- // IDEMPOTENT exit semantics: `releaseItemLock` returns `not-held` when the
2729
+ // IDEMPOTENT exit semantics: the release returns `not-held` when the
2572
2730
  // ref is ALREADY absent. For a HUMAN re-running the verb on an
2573
2731
  // already-cleared lock that is the CORRECT "nothing to clear" outcome —
2574
2732
  // deleting the lock ref(s) is "all locks released" and recoverable — so map
2575
- // it to a clean exit-0 with an honest message (NOT a failure).
2733
+ // it to a clean exit-0 with an honest message (NOT a failure). The message
2734
+ // names the LITERAL entry (`refs/dorfl/lock/<entry>`) so an operator can
2735
+ // copy-paste it straight back into a `--entry` invocation.
2576
2736
  if (result.outcome === 'not-held') {
2577
2737
  console.log(`No lock to release for '${result.entry}' (${result.ref} is already absent on ${arbiter} — “all locks released”, recoverable).`);
2578
2738
  return;
@@ -2580,12 +2740,47 @@ export function buildProgram() {
2580
2740
  console.error(`error: ${result.message}`);
2581
2741
  process.exit(1);
2582
2742
  });
2583
- // `drop <slug>` (prd `agentic-question-resolution-retire-disposition-vocabulary`,
2743
+ // `migrate-stuck-locks` (spec `surface-stuck-as-questions-and-retire-stuck-lock-state`,
2744
+ // resolved decision #3, user story 5; task `migrate-existing-stuck-locks-one-shot`):
2745
+ // the ONE-SHOT ROLLOUT migration that converts every pre-existing `stuck` lock
2746
+ // ref (an item bounced under the OLD model, before `retire-stuck-lock-state`)
2747
+ // into the new resting shape (`needsAnswers:true` + `stuck`-kind sidecar on
2748
+ // `<arbiter>/main` + lock released) via the SAME surface-first-release-second
2749
+ // transition the new bounce uses. Idempotent (a re-run finds no legacy stuck
2750
+ // ref → clean no-op) and bare-arbiter-safe (a ref is a ref). Its own verb (not
2751
+ // folded into `gc --ledger`) because it is a one-shot rollout event with WRITE
2752
+ // semantics on `main`, distinct from `gc`'s report-oriented + terminal-orphan
2753
+ // reap surface — the two are different natures and their exit-code contracts
2754
+ // stay separate.
2755
+ program
2756
+ .command('migrate-stuck-locks')
2757
+ .helpGroup(ADVANCED_GROUP)
2758
+ .description('ONE-SHOT rollout migration: convert every pre-existing `stuck` per-item lock ref (`refs/dorfl/lock/<entry>` written under the retired `stuck` lock state) into the new resting shape — a `needsAnswers:true` item body + a `stuck`-kind `work/questions/<entry>.md` sidecar on `<arbiter>/main`, with the lock ref released — via the SAME surface-first-release-second transition a fresh bounce uses. Idempotent: re-running finds no legacy `stuck` ref (the current lock module never writes `state: stuck`) and is a clean no-op. Bare-arbiter safe (a ref is a ref). Skips a pre-cutover `slice-*` / `prd-*` entry with no current item-form (clear those via `release-lock --entry <literal>`) and NEVER --force-deletes a lock. Exits non-zero only when a surface CAS lost the race or a read/plumbing fault prevented migration.')
2759
+ .option('--cwd <dir>', 'the local repo/clone whose --arbiter remote the lock refs are read + mutated on (default: cwd); the working tree is never touched (the surface transition is tree-less)')
2760
+ .option('--arbiter <remote>', 'the arbiter git remote the migration reads + mutates (default: origin)')
2761
+ .action(async (flags) => {
2762
+ const cwd = flags.cwd ?? process.cwd();
2763
+ const arbiter = flags.arbiter ?? 'origin';
2764
+ const note = (message) => console.error(`>> ${message}`);
2765
+ const report = await migrateStuckLocks({
2766
+ cwd,
2767
+ arbiter,
2768
+ env: process.env,
2769
+ note,
2770
+ });
2771
+ for (const line of formatMigrateStuckLocksReport(report)) {
2772
+ console.log(line);
2773
+ }
2774
+ if (migrateStuckLocksNeedsAttention(report)) {
2775
+ process.exit(1);
2776
+ }
2777
+ });
2778
+ // `drop <slug>` (spec `agentic-question-resolution-retire-disposition-vocabulary`,
2584
2779
  // US #5/#11; task `direct-delete-question-cli-helper`): the DIRECT "throw it
2585
2780
  // away" verb — `git rm` a source item AND its question sidecar (when present) in
2586
2781
  // ONE revertible commit, the reason in the commit MESSAGE (git history is the
2587
2782
  // archive). It does NOT round-trip through the decision engine or spawn an agent
2588
- // (that is the SEPARATE agentic `delete-source` verdict in apply-persist.ts);
2783
+ // (that is the SEPARATE agentic `dispose` verdict in apply-persist.ts);
2589
2784
  // this is the human/skill/CLI no-ceremony delete of decision 7. DISTINCT from
2590
2785
  // the existing `remote rm` (the hub-MIRROR deleter) — different concern, no
2591
2786
  // collision. A LOCAL one-commit primitive over the working tree (like apply): it
@@ -2594,8 +2789,7 @@ export function buildProgram() {
2594
2789
  program
2595
2790
  .command('drop <slug>')
2596
2791
  .helpGroup(HEADLINE_GROUP)
2597
- .description('DIRECTLY delete a source item + its question sidecar (when present) in ONE revertible commit — the "I just want to throw this away" path that does NOT round-trip through the decision engine or any agent. Resolves the source by its namespaced identity (`task:<slug>` / `prd:<slug>` / `obs:<slug>` / a bare `<slug>` = task), `git rm`s the source AND its sidecar together, and records your --reason in the commit MESSAGE (git history is the archive). A single revertible commit, so a wrong delete is recoverable via `git revert`. DISTINCT from `remote rm` (the hub-mirror deleter). A LOCAL working-tree commit (like the apply rung); it does not touch the arbiter — push/integrate it as you normally would. If the named source is already gone it is a clean no-op (nothing to throw away).')
2598
- .option('-c, --config <path>', 'config file path', defaultConfigPath())
2792
+ .description('DIRECTLY delete a source item + its question sidecar (when present) in ONE revertible commit — the "I just want to throw this away" path that does NOT round-trip through the decision engine or any agent. Resolves the source by its namespaced identity (`task:<slug>` / `spec:<slug>` / `obs:<slug>` / a bare `<slug>` = task), `git rm`s the source AND its sidecar together, and records your --reason in the commit MESSAGE (git history is the archive). A single revertible commit, so a wrong delete is recoverable via `git revert`. DISTINCT from `remote rm` (the hub-mirror deleter). A LOCAL working-tree commit (like the apply rung); it does not touch the arbiter — push/integrate it as you normally would. If the named source is already gone it is a clean no-op (nothing to throw away).')
2599
2793
  .option('--cwd <dir>', 'the working clone the revertible delete commit is made in (default: cwd)')
2600
2794
  .option('--reason <text>', 'why you are throwing this away — recorded in the commit MESSAGE (git history is the archive). Optional; recorded as "(no reason given)" when omitted.')
2601
2795
  .action((slug, flags) => {
@@ -2622,7 +2816,7 @@ export function buildProgram() {
2622
2816
  program
2623
2817
  .command('intake')
2624
2818
  .helpGroup(HEADLINE_GROUP)
2625
- .description('Front-of-funnel: turn a GitHub issue into the right work/ artifact. Reads issue #N + its comment thread via the issue seam (gh), runs a prompt→verdict decision, and dispatches it: a clear, small issue → a proposed work/backlog/<slug>.md PR carrying an `issue: N` closure link (read by a future CI close-job; not `Fixes #N`). GATE-FREE — your explicit invocation IS the authorization (autoTask/autoBuild do NOT apply), exactly as `do`. A LOCAL one-shot AND the SAME command CI schedules. PER-OUTCOME integration modes (the artifact TYPE is decided at runtime): --merge/--propose set BOTH; --merge-spec/--propose-spec and --merge-task/--propose-task override per type; granular overrides the aggregate; unset ⇒ propose for both.')
2819
+ .description(`Front-of-funnel: turn a GitHub issue into the right work/ artifact. Reads issue #N + its comment thread via the issue seam (gh), runs a prompt→verdict decision, and dispatches it: a clear, small issue → a proposed ${workFolderPrefix('tasks-ready')}<slug>.md PR carrying an \`issue: N\` closure link (read by a future CI close-job; not \`Fixes #N\`). GATE-FREE — your explicit invocation IS the authorization (autoTask/autoBuild do NOT apply), exactly as \`do\`. A LOCAL one-shot AND the SAME command CI schedules. PER-OUTCOME integration modes (the artifact TYPE is decided at runtime): --merge/--propose set BOTH; --merge-spec/--propose-spec and --merge-task/--propose-task override per type; granular overrides the aggregate; unset ⇒ propose for both.`)
2626
2820
  .argument('<number>', 'the GitHub issue number to intake (e.g. `intake 42`)')
2627
2821
  .option('-c, --config <path>', 'config file path', defaultConfigPath())
2628
2822
  .option('--arbiter <remote>', 'name of the arbiter git remote (default: per-repo/global defaultArbiter)')
@@ -2633,7 +2827,7 @@ export function buildProgram() {
2633
2827
  .option('--propose-spec', 'integrate a spec outcome in propose mode (granular; overrides --merge/--propose for a spec; mutually exclusive with --merge-spec)')
2634
2828
  .option('--merge-task', 'integrate a task outcome in merge mode (granular; overrides --merge/--propose for a task; mutually exclusive with --propose-task)')
2635
2829
  .option('--propose-task', 'integrate a task outcome in propose mode (granular; overrides --merge/--propose for a task; mutually exclusive with --merge-task)')
2636
- .option('--origin-trust <trusted|untrusted>', "the author-trust verdict to STAMP onto the emitted prd/task (origin: issue + originTrust: <value>), so an untrusted origin survives the merge boundary and later forces the task's BUILD transition to propose. CI's intake.yml derives it from the SAME author_association case as the integration flags. UNSET (a local intake) ⇒ emitted unstamped (human/trusted) — the human running intake IS the checkpoint.")
2830
+ .option('--origin-trust <trusted|untrusted>', "the author-trust verdict to STAMP onto the emitted spec/task (origin: issue + originTrust: <value>), so an untrusted origin survives the merge boundary and later forces the task's BUILD transition to propose. CI's intake.yml derives it from the SAME author_association case as the integration flags. UNSET (a local intake) ⇒ emitted unstamped (human/trusted) — the human running intake IS the checkpoint.")
2637
2831
  .option('--specs-land-in <where>', 'where an intake-authored spec lands: `pre-proposed` (staged, not auto-taskable) or `ready` (the auto-tasking pool). The EXPLICIT operator override at the top of the placement precedence (explicit flag > untrusted-origin forces staging > specsLandIn default > built-in). Resolved flag > env (DORFL_SPECS_LAND_IN) > per-repo > global > built-in.')
2638
2832
  .option('--agent-cmd <cmd>', 'command to run the decision agent')
2639
2833
  .option('--model <id>', 'model the decision agent runs on (routing intent; resolved flag > env > per-repo > global > default)')
@@ -2664,7 +2858,7 @@ export function buildProgram() {
2664
2858
  console.error(`>> ${resolved.message}`);
2665
2859
  }
2666
2860
  const config = resolved.config;
2667
- // Resolve the PER-OUTCOME integration modes (prd US #9): `intake` decides
2861
+ // Resolve the PER-OUTCOME integration modes (spec US #9): `intake` decides
2668
2862
  // the artifact TYPE at runtime, so a single --merge/--propose can't express
2669
2863
  // a type-conditional policy. The granular flags override the aggregate; an
2670
2864
  // UNSET type falls back to the per-repo/global `integration` (the SAME chain
@@ -2715,6 +2909,13 @@ export function buildProgram() {
2715
2909
  cwd,
2716
2910
  arbiter: flags.arbiter ?? config.defaultArbiter,
2717
2911
  integration: modes,
2912
+ // The resolved cross-job CAS-retry cap (config `mergeRetries`). `intake`
2913
+ // has no `--merge-retries` flag today (task
2914
+ // `thread-merge-retries-cross-task-and-ratify-default` deliberately does
2915
+ // NOT add one — intake is unattended-CI-first, env + per-repo cover the
2916
+ // need); env + per-repo + global fold into `config.mergeRetries` via the
2917
+ // SAME chain, and unset falls through to the engine default.
2918
+ mergeRetries: config.mergeRetries,
2718
2919
  // The origin-trust stamp the CI shell passes IN (unset ⇒ unstamped).
2719
2920
  originTrust,
2720
2921
  noPR: config.noPR,
@@ -2740,17 +2941,17 @@ export function buildProgram() {
2740
2941
  }
2741
2942
  process.exit(result.exitCode);
2742
2943
  });
2743
- // The CI CLOSE-JOB driver (prd `runner-in-ci`, capability E; task
2944
+ // The CI CLOSE-JOB driver (spec `runner-in-ci`, capability E; task
2744
2945
  // `install-ci-close-job-workflow`). The thin JOB the emitted close-job workflow
2745
2946
  // invokes on a merge to main: resolve which source issue(s) the landed work
2746
- // closes (resolveClosingIssue), run the "prd complete?" query for the prd case
2947
+ // closes (resolveClosingIssue), run the "spec complete?" query for the spec case
2747
2948
  // (prd-complete-query, done), and close via the IssueProvider seam — all
2748
2949
  // UNCHANGED engine pieces, CONSUMED not re-built (the Out-of-Scope fence). CI
2749
2950
  // owns ONLY the job + trigger. Local-runnable too (a manual catch-up close).
2750
2951
  program
2751
2952
  .command('close-merged-issues')
2752
2953
  .helpGroup(ADVANCED_GROUP)
2753
- .description('Close source issues whose work has landed on main (CI capability E, prd runner-in-ci). Resolves each closing issue from the work/ tree (resolveClosingIssue: a lone task closes its own `issue:`; a fanned task reaches the number via `task.prd: → prd issue:`), runs the existing "prd complete?" query for the prd case (closes ONLY when ALL its prd:<slug> tasks are in work/done/), and closes via the IssueProvider seam (atomic comment+close; NO direct gh). Re-implements NONE of the resolution/query/close — it WIRES them. Invoked by the emitted close-job workflow on a merge to main; DEGRADES (never crashes) on a missing/unauthenticated gh.')
2954
+ .description('Close source issues whose work has landed on main (CI capability E, spec runner-in-ci). Resolves each closing issue from the work/ tree (resolveClosingIssue: a lone task closes its own `issue:`; a fanned task reaches the number via `task.spec: → spec issue:`), runs the existing "spec complete?" query for the spec case (closes ONLY when ALL its spec:<slug> tasks are in work/done/), and closes via the IssueProvider seam (atomic comment+close; NO direct gh). Re-implements NONE of the resolution/query/close — it WIRES them. Invoked by the emitted close-job workflow on a merge to main; DEGRADES (never crashes) on a missing/unauthenticated gh.')
2754
2955
  .option('--cwd <dir>', 'the repo working dir whose work/ tree to scan (default: cwd)')
2755
2956
  .option('--gh-bin <bin>', 'the gh CLI binary (default: gh on PATH)')
2756
2957
  .option('--json', 'output the raw result as JSON')
@@ -2867,7 +3068,7 @@ export function buildProgram() {
2867
3068
  remote
2868
3069
  .command('find <folder>')
2869
3070
  .helpGroup(HEADLINE_GROUP)
2870
- .description('Discover work/-participating repos under <folder> (a populated work/backlog/), then toggle-add the chosen ones via `remote add`. Interactive multi-select by default; --yes adds ALL discovered repos non-interactively.')
3071
+ .description(`Discover work/-participating repos under <folder> (a populated ${workFolderPrefix('tasks-ready')}), then toggle-add the chosen ones via \`remote add\`. Interactive multi-select by default; --yes adds ALL discovered repos non-interactively.`)
2871
3072
  .option('-c, --config <path>', 'config file path', defaultConfigPath())
2872
3073
  .option('--yes', 'add all discovered participating repos (no prompt)')
2873
3074
  .action(async (folder, flags) => {
@@ -2918,7 +3119,7 @@ export function buildProgram() {
2918
3119
  .option('--gh-bin <bin>', 'the gh CLI binary (default: gh on PATH)')
2919
3120
  .option('--cwd <dir>', 'the target repo working dir (default: cwd)')
2920
3121
  .option('--install-source <registry|workspace>', 'where the CI installs the CLI from: `registry` (npm install -g, the default) or `workspace` (build from the checked-out source + link onto PATH, for the self-hosting monorepo). Overrides auto-detection in both directions.')
2921
- .option('--max-parallel <n>', 'cap on CONCURRENT advance-lifecycle matrix legs (the propose/merge `max-parallel`). Each leg is a full agent session, so a large fan-out can exhaust the model provider rate limit + thrash the CAS. Default 4.')
3122
+ .option('--max-parallel <n>', 'cap on CONCURRENT advance-lifecycle matrix legs (the propose/merge `max-parallel`). Each leg is a full agent session, so a large fan-out can exhaust the model provider rate limit + thrash the CAS. Default 2.')
2922
3123
  .action(async (flags) => {
2923
3124
  const workDir = flags.cwd ?? process.cwd();
2924
3125
  if (flags.installSource !== undefined &&
@@ -2964,8 +3165,64 @@ export function buildProgram() {
2964
3165
  log: (line) => console.error(line),
2965
3166
  });
2966
3167
  });
3168
+ // The `skills` group (ADR `skill-install-vendors-incur-agents-map`): install the
3169
+ // packaged dorfl skills (`from-idea`, `setup`, ...) into the OPERATOR's OWN
3170
+ // harness dirs. Distinct from any target-repo propagation (ADR
3171
+ // `methodology-and-skills` §6 — skills don't travel into target repos; only
3172
+ // `work/protocol/` does). Only `add` is exposed in this pass — `list`/`remove`
3173
+ // are deferred until we have a real use case (the vendored `install()` is
3174
+ // idempotent, so `add` alone covers upgrade + drift-repair; a future `remove`
3175
+ // would map onto the vendored `remove()` and is intentionally out of scope
3176
+ // here to keep the CLI surface minimal).
3177
+ const skills = program
3178
+ .command('skills')
3179
+ .helpGroup(HEADLINE_GROUP)
3180
+ .description("Install the packaged dorfl skills (`from-idea`, `setup`, ...) into the operator's own agent harness(es). Installs into your OWN harness dirs (~/.agents/skills/ + per-harness symlinks) — this is operator tooling, NOT target-repo propagation (skills don't travel into `work/` repos; only `work/protocol/` does).");
3181
+ skills
3182
+ .command('add')
3183
+ .helpGroup(HEADLINE_GROUP)
3184
+ .description('Copy the packaged dorfl skills into the canonical `~/.agents/skills/` (global; `--local` scopes to <cwd>/.agents/skills/) and symlink each detected non-universal harness (Claude Code, Cursor, Windsurf, ...) to it. Idempotent; re-run to pick up new/updated skills.')
3185
+ .option('--local', 'install project-locally into <cwd>/.agents/skills/ (and each detected harness\u2019s PROJECT dir) instead of globally into ~/.agents/skills/. Default: global.')
3186
+ .action((flags) => {
3187
+ const result = installSkills({ global: flags.local !== true });
3188
+ console.log(formatSkillsAddReport(result, flags.local === true));
3189
+ });
2967
3190
  return program;
2968
3191
  }
3192
+ /**
3193
+ * Human-readable report for `dorfl skills add`. Prints the SOURCE the skills
3194
+ * were read from, each canonical install path (one per skill), and each
3195
+ * non-universal harness placement (symlink | copy) with its absolute path.
3196
+ * Universal harnesses read from the canonical dir directly and need no line.
3197
+ * Output goes to stdout; the shape is stable enough for a human to audit "what
3198
+ * landed where" but is NOT a machine contract (no JSON mode in this pass).
3199
+ */
3200
+ export function formatSkillsAddReport(result, local) {
3201
+ const lines = [];
3202
+ const scope = local ? 'project-local' : 'global';
3203
+ lines.push(`Installed dorfl skills (${scope}) from ${result.sourceDir}:`);
3204
+ if (result.paths.length === 0) {
3205
+ lines.push(' (no skills found in source)');
3206
+ }
3207
+ else {
3208
+ for (const p of result.paths.slice().sort()) {
3209
+ lines.push(` ${p}`);
3210
+ }
3211
+ }
3212
+ if (result.agents.length === 0) {
3213
+ lines.push('Harnesses: no non-universal harness detected (universal harnesses read from the canonical dir directly).');
3214
+ }
3215
+ else {
3216
+ lines.push('Harnesses:');
3217
+ for (const a of result.agents.slice().sort((x, y) => {
3218
+ const byAgent = x.agent.localeCompare(y.agent);
3219
+ return byAgent !== 0 ? byAgent : x.path.localeCompare(y.path);
3220
+ })) {
3221
+ lines.push(` ${a.agent}: ${a.mode} -> ${a.path}`);
3222
+ }
3223
+ }
3224
+ return lines.join('\n');
3225
+ }
2969
3226
  /**
2970
3227
  * A readline-backed {@link WizardPrompts} for the interactive `install-ci`
2971
3228
  * wizard. Prompts go to stderr (stdout is reserved for any machine output); a