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/src/cli.ts CHANGED
@@ -121,6 +121,7 @@ import {status, formatStatus} from './status.js';
121
121
  import {ledgerWrite} from './ledger-write.js';
122
122
  import {
123
123
  releaseItemLock,
124
+ releaseLiteralLockEntry,
124
125
  reportItemLocks,
125
126
  formatItemLockReport,
126
127
  itemLockReportNeedsAttention,
@@ -128,6 +129,11 @@ import {
128
129
  formatReapReport,
129
130
  reapReportNeedsAttention,
130
131
  } from './item-lock.js';
132
+ import {
133
+ migrateStuckLocks,
134
+ formatMigrateStuckLocksReport,
135
+ migrateStuckLocksNeedsAttention,
136
+ } from './migrate-stuck-locks.js';
131
137
  import {
132
138
  promoteFromPreBacklog,
133
139
  promoteFromPreSpec,
@@ -146,6 +152,7 @@ import {installCI, type WizardPrompts} from './install-ci.js';
146
152
  import {GitHubCIContext} from './install-ci-github.js';
147
153
  import {loadCapabilityRegistry} from './install-ci-core.js';
148
154
  import {performCloseMergedIssues} from './close-job.js';
155
+ import {installSkills, type InstallSkillsResult} from './install-skills.js';
149
156
 
150
157
  interface ScanFlags {
151
158
  config?: string;
@@ -353,10 +360,10 @@ function buildRegistrySetAdvanceTick(options: {
353
360
  lifecycleGates: {
354
361
  triage: config.observationTriage !== 'off',
355
362
  surface: config.surfaceBlockers,
356
- // `surfaceStaging` widens the SURFACE candidate set into STAGING (prd
363
+ // `surfaceStaging` widens the SURFACE candidate set into STAGING (spec
357
364
  // `staging-surface-and-apply-promote-safety` F2). Default `true` — a
358
- // tasked `needsAnswers` task in `tasks/backlog/` (or prd in
359
- // `prds/proposed/`) surfaces its questions BEFORE promotion. BUILD/claim
365
+ // tasked `needsAnswers` task in `tasks/backlog/` (or spec in
366
+ // `specs/proposed/`) surfaces its questions BEFORE promotion. BUILD/claim
360
367
  // stays pool-only either way.
361
368
  surfaceStaging: config.surfaceStaging,
362
369
  },
@@ -388,11 +395,11 @@ function buildRegistrySetAdvanceTick(options: {
388
395
  identity: config.identity,
389
396
  autoTask: config.autoTask,
390
397
  integration: config.integration,
391
- // The per-TRANSITION TASKING override: the `do prd:` tasking path threads
398
+ // The per-TRANSITION TASKING override: the `do spec:` tasking path threads
392
399
  // `taskingIntegration ?? integration`; the build path stays on `integration`.
393
400
  taskingIntegration: config.taskingIntegration,
394
- // The TASK-PLACEMENT configured default (`do prd:` tasking output:
395
- // `pre-backlog` staged vs `ready` pool). No operator flag on this
401
+ // The TASK-PLACEMENT configured default (`do spec:` tasking output:
402
+ // `backlog` staged vs `ready` pool). No operator flag on this
396
403
  // registry-driven advance context, so only the configured default rung is
397
404
  // threaded (the resolver still layers untrusted-origin force + built-in floor).
398
405
  tasksLandIn: config.tasksLandIn,
@@ -400,7 +407,7 @@ function buildRegistrySetAdvanceTick(options: {
400
407
  verify: config.verify,
401
408
  // Single-job build path: gate the REBASED tip (the default) unconditionally.
402
409
  freshWorktreeGate: config.freshWorktreeGate,
403
- // Cross-job merge-serialiser CAS-retry cap (prd `land-time-reverify-and-
410
+ // Cross-job merge-serialiser CAS-retry cap (spec `land-time-reverify-and-
404
411
  // parallel-merge-ceiling` Story 5 / Applied Answer q1 (a)) — resolved per-repo
405
412
  // and threaded so the registry-driven advance path's `do` inherits it.
406
413
  mergeRetries: config.mergeRetries,
@@ -439,7 +446,7 @@ function buildRegistrySetAdvanceTick(options: {
439
446
  triageGate: harnessTriageGate({harness, agentCmd: config.agentCmd}),
440
447
  triageModel: config.model,
441
448
  // The ANSWERED-MERGE LAND DISPATCH context (task
442
- // `apply-rung-merge-disposition`, prd `land-time-reverify-and-parallel-
449
+ // `apply-rung-merge-disposition`, spec `land-time-reverify-and-parallel-
443
450
  // merge-ceiling`): the dispatcher cuts a per-job worktree via
444
451
  // `workspace.ts` `createJob` off the hub mirror (so we thread the resolved
445
452
  // `workspacesDir` + the real arbiter URL — the per-mirror tree-less
@@ -544,7 +551,7 @@ interface RunFlags extends ScanFlags {
544
551
  reviewMaxRounds?: string;
545
552
  /** `--fresh-worktree-gate` / `--no-fresh-worktree-gate` — gate the REBASED tip in a clean throwaway worktree (ON by default). */
546
553
  freshWorktreeGate?: boolean;
547
- /** `--merge-retries <n>` — the cross-job merge-serialiser CAS-retry cap (prd `land-time-reverify-and-parallel-merge-ceiling` Story 5 / Applied Answer q1 (a)). */
554
+ /** `--merge-retries <n>` — the cross-job merge-serialiser CAS-retry cap (spec `land-time-reverify-and-parallel-merge-ceiling` Story 5 / Applied Answer q1 (a)). */
548
555
  mergeRetries?: string;
549
556
  }
550
557
 
@@ -583,7 +590,7 @@ function runFlagOverrides(flags: RunFlags, command?: Commander): PartialConfig {
583
590
  // fleet conditional lives in `runOnce`, not in this flag mapping).
584
591
  Object.assign(overrides, freshWorktreeGateFlagOverrides(flags));
585
592
  // `--merge-retries <n>` rides the SAME chain: the cross-job merge-serialiser
586
- // CAS-retry cap (prd `land-time-reverify-and-parallel-merge-ceiling` Story 5 /
593
+ // CAS-retry cap (spec `land-time-reverify-and-parallel-merge-ceiling` Story 5 /
587
594
  // Applied Answer q1 (a)). The `run` fleet inherits the resolved cap through
588
595
  // the converged `performIntegration` core (config.mergeRetries threads into
589
596
  // the merge loop, replacing the bare `DEFAULT_MERGE_RETRIES` fallback).
@@ -661,7 +668,7 @@ interface CompleteFlags {
661
668
  reviewMaxRounds?: string;
662
669
  /** `--fresh-worktree-gate` / `--no-fresh-worktree-gate` — gate the REBASED tip in a clean throwaway worktree (ON by default). */
663
670
  freshWorktreeGate?: boolean;
664
- /** `--merge-retries <n>` — the cross-job merge-serialiser CAS-retry cap (prd `land-time-reverify-and-parallel-merge-ceiling` Story 5 / Applied Answer q1 (a)). */
671
+ /** `--merge-retries <n>` — the cross-job merge-serialiser CAS-retry cap (spec `land-time-reverify-and-parallel-merge-ceiling` Story 5 / Applied Answer q1 (a)). */
665
672
  mergeRetries?: string;
666
673
  /** `--isolated`: finish the slug's retained job worktree (the stranded-branch recover). */
667
674
  isolated?: boolean;
@@ -670,7 +677,7 @@ interface CompleteFlags {
670
677
 
671
678
  /**
672
679
  * Resolve the EXPLICIT operator placement override from `--tasks-land-in <where>`
673
- * (the top of the `do prd:` tasking-placement precedence — task
680
+ * (the top of the `do spec:` tasking-placement precedence — task
674
681
  * `runner-deterministic-slice-placement-policy-and-precedence`). Mirrors the
675
682
  * `flagMode === 'merge'` ⇒ `explicitMerge: true` shape: it contributes
676
683
  * `explicitTasksLandIn` ONLY when the operator actually typed the flag, so an
@@ -681,13 +688,13 @@ interface CompleteFlags {
681
688
  */
682
689
  function explicitTasksLandInFromFlag(
683
690
  raw: string | undefined,
684
- ): 'pre-backlog' | 'ready' | undefined {
691
+ ): 'backlog' | 'ready' | undefined {
685
692
  if (raw === undefined) {
686
693
  return undefined;
687
694
  }
688
- if (raw !== 'pre-backlog' && raw !== 'ready') {
695
+ if (raw !== 'backlog' && raw !== 'ready') {
689
696
  throw new Error(
690
- `--tasks-land-in must be 'pre-backlog' or 'ready' (got '${raw}').`,
697
+ `--tasks-land-in must be 'backlog' or 'ready' (got '${raw}').`,
691
698
  );
692
699
  }
693
700
  return raw;
@@ -726,8 +733,10 @@ interface DoFlags {
726
733
  config?: string;
727
734
  arbiter?: string;
728
735
  remote?: string;
729
- /** `--isolated`: build in a job worktree off THIS repo's arbiter (no checkout takeover). */
736
+ /** `--isolated`: build in a job worktree off THIS repo's arbiter (no checkout takeover). Since `make-isolated-default-build-mode` this is the DEFAULT; the flag remains accepted as a redundant explicit opt-IN alias (D3). */
730
737
  isolated?: boolean;
738
+ /** `--in-place`: opt OUT of the new default (isolated off the arbiter) and build in the CURRENT checkout, restoring today's dirty-tree-refusing in-place behaviour (D3). */
739
+ inPlace?: boolean;
731
740
  /** `-n <x>`: do x eligible items in sequence (auto-pick form). */
732
741
  number?: string;
733
742
  /** `--selection-order <order>`: a preset keyword (drain/groom) or comma-separated pool order. */
@@ -742,7 +751,7 @@ interface DoFlags {
742
751
  strictMergeApproval?: boolean;
743
752
  merge?: boolean;
744
753
  propose?: boolean;
745
- /** `--tasks-land-in <pre-backlog|ready>`: the explicit operator placement override for `do prd:` tasking output (top of the placement precedence). Resolves into the `tasksLandIn` config key. */
754
+ /** `--tasks-land-in <backlog|ready>`: the explicit operator placement override for `do spec:` tasking output (top of the placement precedence). Resolves into the `tasksLandIn` config key. */
746
755
  tasksLandIn?: string;
747
756
  /** `--no-pr` ⇒ commander stores `pr === false` (the suppress-PR intent). */
748
757
  pr?: boolean;
@@ -758,15 +767,15 @@ interface DoFlags {
758
767
  review?: boolean;
759
768
  reviewModel?: string;
760
769
  reviewMaxRounds?: string;
761
- /** `--tasker-loop` / `--no-tasker-loop` — the tasker improver loop on/off toggle (`do prd:` path). Resolves into the `taskerLoop` config key. */
770
+ /** `--tasker-loop` / `--no-tasker-loop` — the tasker improver loop on/off toggle (`do spec:` path). Resolves into the `taskerLoop` config key. */
762
771
  taskerLoop?: boolean;
763
- /** `--tasker-loop-max <n>` — the tasker improver loop's in-context convergence cap (`do prd:` path). Resolves into the `taskerLoopMax` config key. */
772
+ /** `--tasker-loop-max <n>` — the tasker improver loop's in-context convergence cap (`do spec:` path). Resolves into the `taskerLoopMax` config key. */
764
773
  taskerLoopMax?: string;
765
- /** `--tasker-loop-model <id>` — the tasker improver loop reviewer's de-correlated model (`do prd:` path). Resolves into the `taskerLoopModel` config key. */
774
+ /** `--tasker-loop-model <id>` — the tasker improver loop reviewer's de-correlated model (`do spec:` path). Resolves into the `taskerLoopModel` config key. */
766
775
  taskerLoopModel?: string;
767
776
  /** `--fresh-worktree-gate` / `--no-fresh-worktree-gate` — gate the REBASED tip in a clean throwaway worktree (ON by default). */
768
777
  freshWorktreeGate?: boolean;
769
- /** `--merge-retries <n>` — the cross-job merge-serialiser CAS-retry cap (prd `land-time-reverify-and-parallel-merge-ceiling` Story 5 / Applied Answer q1 (a)). */
778
+ /** `--merge-retries <n>` — the cross-job merge-serialiser CAS-retry cap (spec `land-time-reverify-and-parallel-merge-ceiling` Story 5 / Applied Answer q1 (a)). */
770
779
  mergeRetries?: string;
771
780
  }
772
781
 
@@ -783,7 +792,7 @@ interface IntakeFlags {
783
792
  proposeTask?: boolean;
784
793
  /**
785
794
  * `--origin-trust <trusted|untrusted>` — the author-trust verdict the CI shell
786
- * passes IN so `intake` STAMPS the emitted prd/task (task
795
+ * passes IN so `intake` STAMPS the emitted spec/task (task
787
796
  * `untrusted-origin-forces-build-propose`). `intake` does NOT resolve trust; the
788
797
  * shell derives it from the SAME `author_association` case as the integration
789
798
  * flags. UNSET (a local intake) ⇒ emit unstamped ⇒ human/trusted.
@@ -886,7 +895,7 @@ function printPrdToSpecReport(result: PrdToSpecResult): void {
886
895
  return;
887
896
  }
888
897
  if (result.leaks.length === 0) {
889
- console.log('Leak scan: GREEN (no surviving prd data ref).');
898
+ console.log('Leak scan: GREEN (no surviving spec data ref).');
890
899
  } else {
891
900
  console.error(`Leak scan: FAILED (${result.leaks.length} leak(s)):`);
892
901
  for (const leak of result.leaks) {
@@ -916,6 +925,8 @@ interface RequeueFlags {
916
925
  cwd?: string;
917
926
  arbiter?: string;
918
927
  reset?: boolean;
928
+ reconcile?: boolean;
929
+ rebase?: boolean;
919
930
  message?: string;
920
931
  }
921
932
 
@@ -929,10 +940,10 @@ interface ReleaseLockFlags {
929
940
  config?: string;
930
941
  cwd?: string;
931
942
  arbiter?: string;
943
+ entry?: string;
932
944
  }
933
945
 
934
946
  interface DropFlags {
935
- config?: string;
936
947
  cwd?: string;
937
948
  reason?: string;
938
949
  }
@@ -979,11 +990,12 @@ interface CloseMergedIssuesFlags {
979
990
  /**
980
991
  * Resolve a task-only command's slug argument through the §3a namespace guard
981
992
  * (`resolveTaskOnlyArg`): accept bare (= task) + `task:` (explicit alias),
982
- * REJECT `spec:` with a clear "operates on tasks, not specs" error (and the
983
- * legacy `prd:` with "operates on tasks, not prds", still accepted through the
984
- * cutover). On rejection it prints the error to stderr and exits 1 (the task-only
985
- * commands never act on a spec). An OMITTED slug (`start`/`complete`/`prompt`
986
- * infer it from the branch) passes through untouched.
993
+ * REJECT `spec:` with a clear "operates on tasks, not specs" error. On rejection
994
+ * it prints the error to stderr and exits 1 (the task-only commands never act on
995
+ * a spec). An OMITTED slug (`start`/`complete`/`prompt` infer it from the branch)
996
+ * passes through untouched. (The legacy ''prd:'' prefix is a DEAD namespace token
997
+ * after the hard cutover — it is neither special-cased nor rejected here; a
998
+ * ''prd:<slug>'' arg resolves as a bare literal task slug.)
987
999
  *
988
1000
  * `do` is the ONE command that spans both namespaces; it consumes the full
989
1001
  * `resolveSlug` (with the cross-namespace collision check) in the `do-in-place`
@@ -1067,14 +1079,14 @@ function buildInteractiveLauncher(
1067
1079
  * switching to its `work/<slug>` branch WITHOUT claiming). The runtime
1068
1080
  * difference is exactly the `resume` flag — `resume` forces it on (its only mode
1069
1081
  * is to re-engage), while `start` honours the (now hidden) `--resume` alias.
1070
- * Both are task-only (§3a: accept bare + `task:`, reject `prd:`).
1082
+ * Both are task-only (§3a: accept bare + `task:`, reject `spec:`).
1071
1083
  */
1072
1084
  async function runStartAction(
1073
1085
  rawSlug: string | undefined,
1074
1086
  flags: StartFlags,
1075
1087
  resume: boolean,
1076
1088
  ): Promise<void> {
1077
- // Task-only command (§3a): accept bare + `task:`, reject `prd:`.
1089
+ // Task-only command (§3a): accept bare + `task:`, reject `spec:`.
1078
1090
  const slug = resolveTaskOnlySlug(rawSlug);
1079
1091
  const cwd = process.cwd();
1080
1092
 
@@ -1163,6 +1175,44 @@ export function buildProgram(): Command {
1163
1175
  .name(brand.bin)
1164
1176
  .description('Autonomous parallel agents over file-based work/ queues.');
1165
1177
 
1178
+ // `dorfl config --json` (spec `graceful-pre-timeout-wip-checkpoint` — step 5):
1179
+ // print the RESOLVED per-repo config as JSON, so BOTH the advance-lifecycle
1180
+ // workflow's `enumerate` job (which reads it to emit the dynamic
1181
+ // `githubTimeout` output) AND a human debugging a config-precedence question
1182
+ // share ONE honest primitive. A focused honest primitive — NOT overloaded
1183
+ // onto `scan`. Runs in the cwd (in-place), so the resolution chain reads THIS
1184
+ // repo's committed `dorfl.json` exactly like `scan --here`.
1185
+ program
1186
+ .command('config')
1187
+ .helpGroup(HEADLINE_GROUP)
1188
+ .description(
1189
+ "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.",
1190
+ )
1191
+ .option('-c, --config <path>', 'config file path', defaultConfigPath())
1192
+ .option(
1193
+ '--arbiter <remote>',
1194
+ 'name of the arbiter git remote (default: per-repo/global defaultArbiter)',
1195
+ )
1196
+ .option('--json', 'output the resolved config as JSON')
1197
+ .action(
1198
+ async (flags: {config?: string; arbiter?: string; json?: boolean}) => {
1199
+ if (flags.json !== true) {
1200
+ console.error(
1201
+ 'error: `dorfl config` currently supports only --json output. Pass --json.',
1202
+ );
1203
+ process.exit(1);
1204
+ }
1205
+ const cwd = process.cwd();
1206
+ const {global, override} = loadGlobalAndOverride(flags.config);
1207
+ const resolved = resolveRepoConfig({
1208
+ repoPath: cwd,
1209
+ global,
1210
+ override,
1211
+ }).config;
1212
+ console.log(JSON.stringify(resolved, null, 2));
1213
+ },
1214
+ );
1215
+
1166
1216
  program
1167
1217
  .command('scan')
1168
1218
  .helpGroup(HEADLINE_GROUP)
@@ -1515,7 +1565,7 @@ export function buildProgram(): Command {
1515
1565
  .command('claim')
1516
1566
  .helpGroup(ADVANCED_GROUP)
1517
1567
  .description(
1518
- 'Atomically claim a work/backlog/<slug>.md item via a compare-and-swap push to the arbiter (in-process; mirrors scripts/claim.sh).',
1568
+ `Atomically claim a ${workFolderPrefix('tasks-ready')}<slug>.md item via a compare-and-swap push to the arbiter (in-process; mirrors scripts/claim.sh).`,
1519
1569
  )
1520
1570
  .argument('<slug>', 'the slug of the backlog item to claim')
1521
1571
  .option(
@@ -1530,7 +1580,7 @@ export function buildProgram(): Command {
1530
1580
  'override the readiness guard: claim despite an unmet blockedBy, and silence the needsAnswers warning (loud, never default)',
1531
1581
  )
1532
1582
  .action(async (rawSlug: string, flags: ClaimFlags) => {
1533
- // Task-only command (§3a): accept bare + `task:`, reject `prd:`.
1583
+ // Task-only command (§3a): accept bare + `task:`, reject `spec:`.
1534
1584
  const slug = resolveTaskOnlySlug(rawSlug) as string;
1535
1585
  // Wrap ONLY this CLI surface's `performClaim` call with the spinner
1536
1586
  // helper (task `claim-cas-spinner`): the push can take seconds, so the
@@ -1736,7 +1786,7 @@ export function buildProgram(): Command {
1736
1786
  flags.remote !== undefined && flags.remote.trim() !== ''
1737
1787
  ? flags.remote
1738
1788
  : undefined;
1739
- // Task-only command (§3a): accept bare + `task:`, reject `prd:`.
1789
+ // Task-only command (§3a): accept bare + `task:`, reject `spec:`.
1740
1790
  const theSlug = resolveTaskOnlySlug(rawSlug) as string;
1741
1791
 
1742
1792
  const configPath = flags.config ?? defaultConfigPath();
@@ -1788,14 +1838,14 @@ export function buildProgram(): Command {
1788
1838
  .command('prompt')
1789
1839
  .helpGroup(ADVANCED_GROUP)
1790
1840
  .description(
1791
- "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.",
1841
+ `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.`,
1792
1842
  )
1793
1843
  .argument(
1794
1844
  '[slug]',
1795
1845
  'the slug to render (inferred from a work/<slug> branch if omitted)',
1796
1846
  )
1797
1847
  .action((rawSlug: string | undefined) => {
1798
- // Task-only command (§3a): accept bare + `task:`, reject `prd:`.
1848
+ // Task-only command (§3a): accept bare + `task:`, reject `spec:`.
1799
1849
  const slug = resolveTaskOnlySlug(rawSlug);
1800
1850
  // Resolve the `promptGuidance` NUDGE namespace through the SAME chain the
1801
1851
  // gate family uses (env > per-repo > global > default), so e.g. a
@@ -1902,7 +1952,7 @@ export function buildProgram(): Command {
1902
1952
  'cross-job merge-serialiser CAS-retry cap (see `run --help`); resolved flag > env > per-repo > global > default 1000.',
1903
1953
  )
1904
1954
  .action(async (rawSlug: string | undefined, flags: CompleteFlags) => {
1905
- // Task-only command (§3a): accept bare + `task:`, reject `prd:`.
1955
+ // Task-only command (§3a): accept bare + `task:`, reject `spec:`.
1906
1956
  const slug = resolveTaskOnlySlug(rawSlug);
1907
1957
  const cwd = process.cwd();
1908
1958
  const {global, override} = loadGlobalAndOverride(flags.config);
@@ -1926,6 +1976,11 @@ export function buildProgram(): Command {
1926
1976
  override,
1927
1977
  flags: {
1928
1978
  ...(flagMode ? {integration: flagMode} : {}),
1979
+ // `--merge-retries <n>` rides the SAME gate-family precedence chain on
1980
+ // the isolated-recovery path so a wide-matrix CI's raised cap actually
1981
+ // reaches the land tail here too (task
1982
+ // `thread-merge-retries-cross-task-and-ratify-default`).
1983
+ ...mergeRetriesFlagOverrides(flags),
1929
1984
  ...noPRFlagOverrides(flags),
1930
1985
  },
1931
1986
  });
@@ -1940,6 +1995,9 @@ export function buildProgram(): Command {
1940
1995
  workspacesDir: flags.workspace ?? isoConfig.workspacesDir,
1941
1996
  integration: isoConfig.integration,
1942
1997
  noPR: isoConfig.noPR,
1998
+ // The resolved cross-job CAS-retry cap threaded through so the isolated-
1999
+ // recovery land tail respects the per-repo cap (same chain as `complete`).
2000
+ mergeRetries: isoConfig.mergeRetries,
1943
2001
  note: (message) => console.error(`>> ${message}`),
1944
2002
  env: process.env,
1945
2003
  });
@@ -1966,7 +2024,7 @@ export function buildProgram(): Command {
1966
2024
  // `--fresh-worktree-gate`/`--no-fresh-worktree-gate` rides the SAME chain.
1967
2025
  ...freshWorktreeGateFlagOverrides(flags),
1968
2026
  // `--merge-retries <n>` rides the SAME chain: the cross-job merge-serialiser
1969
- // CAS-retry cap (prd `land-time-reverify-and-parallel-merge-ceiling` Story 5
2027
+ // CAS-retry cap (spec `land-time-reverify-and-parallel-merge-ceiling` Story 5
1970
2028
  // / Applied Answer q1 (a)).
1971
2029
  ...mergeRetriesFlagOverrides(flags),
1972
2030
  // `--no-pr` (the PR-INTENT axis) rides the SAME chain.
@@ -2006,7 +2064,7 @@ export function buildProgram(): Command {
2006
2064
  // a single-job path, so the resolved flag is passed UNCONDITIONALLY (no
2007
2065
  // fleet downgrade).
2008
2066
  freshWorktreeGate: config.freshWorktreeGate,
2009
- // Cross-job merge-serialiser CAS-retry cap (prd `land-time-reverify-and-
2067
+ // Cross-job merge-serialiser CAS-retry cap (spec `land-time-reverify-and-
2010
2068
  // parallel-merge-ceiling` Story 5 / Applied Answer q1 (a)) — the resolved
2011
2069
  // per-repo value reaches the merge loop via `performComplete`→
2012
2070
  // `performIntegration`.
@@ -2039,7 +2097,7 @@ export function buildProgram(): Command {
2039
2097
  .command('do')
2040
2098
  .helpGroup(HEADLINE_GROUP)
2041
2099
  .description(
2042
- '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.',
2100
+ "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.",
2043
2101
  )
2044
2102
  // EXTENSIBLE argument grammar (the three do-* tasks grow this one block):
2045
2103
  // `do-autopick` widens the single optional positional into a VARIADIC one so
@@ -2048,7 +2106,7 @@ export function buildProgram(): Command {
2048
2106
  // for the auto-pick form. `do` stays SEQUENTIAL (parallelism is `run`).
2049
2107
  .argument(
2050
2108
  '[slugs...]',
2051
- '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.',
2109
+ '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.',
2052
2110
  )
2053
2111
  .option('-c, --config <path>', 'config file path', defaultConfigPath())
2054
2112
  .option(
@@ -2069,7 +2127,11 @@ export function buildProgram(): Command {
2069
2127
  )
2070
2128
  .option(
2071
2129
  '--isolated',
2072
- "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).",
2130
+ "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).",
2131
+ )
2132
+ .option(
2133
+ '--in-place',
2134
+ "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.",
2073
2135
  )
2074
2136
  .option(
2075
2137
  '--merge',
@@ -2081,7 +2143,7 @@ export function buildProgram(): Command {
2081
2143
  )
2082
2144
  .option(
2083
2145
  '--tasks-land-in <where>',
2084
- '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.',
2146
+ '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.',
2085
2147
  )
2086
2148
  .option(
2087
2149
  '--no-pr',
@@ -2134,15 +2196,15 @@ export function buildProgram(): Command {
2134
2196
  )
2135
2197
  .option(
2136
2198
  '--tasker-loop',
2137
- '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).',
2199
+ '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).',
2138
2200
  )
2139
2201
  .option(
2140
2202
  '--no-tasker-loop',
2141
- 'skip the tasker improver loop on `do prd:<slug>`',
2203
+ 'skip the tasker improver loop on `do spec:<slug>`',
2142
2204
  )
2143
2205
  .option(
2144
2206
  '--tasker-loop-max <n>',
2145
- '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)',
2207
+ '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)',
2146
2208
  )
2147
2209
  .option(
2148
2210
  '--tasker-loop-model <id>',
@@ -2220,14 +2282,52 @@ export function buildProgram(): Command {
2220
2282
  // `remote-do-reads-per-repo-config-from-arbiter-main`). Only the whitelisted
2221
2283
  // `REPO_ALLOWED_KEYS` are layered (host-only keys stay global/flag/env-only,
2222
2284
  // rejected by the SAME `repo-config.ts` split).
2285
+ // Mutually-exclusive intent guards for the three FORM flags (task
2286
+ // `make-isolated-default-build-mode`, D3): `--in-place` opts OUT of the new
2287
+ // isolated default, `--isolated` is the redundant explicit opt-IN alias,
2288
+ // `--remote <r>` targets a foreign repo. `--in-place` + `--isolated` is
2289
+ // contradictory (build here vs build off the arbiter); `--in-place` + `--remote`
2290
+ // is nonsensical (there is no local checkout to take over). Reject loudly
2291
+ // rather than silently prefer one.
2292
+ if (flags.inPlace === true && flags.isolated === true) {
2293
+ console.error(
2294
+ 'error: --in-place and --isolated are contradictory (build in the current ' +
2295
+ "checkout vs build in a worktree off this repo's arbiter). Pick one.",
2296
+ );
2297
+ process.exit(1);
2298
+ }
2299
+ if (flags.inPlace === true && flags.remote !== undefined) {
2300
+ console.error(
2301
+ 'error: --in-place builds in the CURRENT checkout; --remote <r> targets a ' +
2302
+ 'REGISTERED repo with NO checkout. Pick one.',
2303
+ );
2304
+ process.exit(1);
2305
+ }
2306
+ // The DEFAULT flip (task `make-isolated-default-build-mode`): with no
2307
+ // `--remote` and no `--in-place`, `do <slug>` builds in an ISOLATED job
2308
+ // worktree off THIS repo's arbiter — the SAME no-checkout path `--isolated`
2309
+ // already used. The cwd is the origin SOURCE only (arbiter-remote resolution
2310
+ // + per-repo config), never written. `--isolated` remains accepted as a
2311
+ // redundant explicit opt-IN alias (D3). `--in-place` opts out to today's
2312
+ // in-checkout path (the fall-through below). See ADR §3.
2223
2313
  const isolatedNoRemote =
2224
- flags.isolated === true && flags.remote === undefined;
2314
+ flags.remote === undefined && flags.inPlace !== true;
2315
+ // Whether the user DEFAULTED into isolated (no form flag typed) vs typed
2316
+ // `--isolated` explicitly. Only used to tune the no-arbiter error message so
2317
+ // the default path names `--in-place` as its natural escape (D2).
2318
+ const defaultedToIsolated = isolatedNoRemote && flags.isolated !== true;
2225
2319
  if (flags.remote !== undefined || isolatedNoRemote) {
2226
2320
  // The form's user-facing name + canonical usage, for the shared error
2227
- // messages below (so `--isolated` errors read in its own terms).
2228
- const form = isolatedNoRemote ? '--isolated' : '--remote';
2321
+ // messages below (so isolated errors read in the form's own terms).
2322
+ const form = isolatedNoRemote
2323
+ ? defaultedToIsolated
2324
+ ? 'do'
2325
+ : '--isolated'
2326
+ : '--remote';
2229
2327
  const usage = isolatedNoRemote
2230
- ? '`do --isolated <slug>`'
2328
+ ? defaultedToIsolated
2329
+ ? '`do <slug>`'
2330
+ : '`do --isolated <slug>`'
2231
2331
  : '`do --remote <r> <slug>`';
2232
2332
  // The no-checkout forms now support the SAME variadic grammar the in-place
2233
2333
  // form does: a single NAMED item, MULTIPLE named items (sequential), and
@@ -2257,12 +2357,33 @@ export function buildProgram(): Command {
2257
2357
  identityEnv(bootstrapIdentity, process.env),
2258
2358
  );
2259
2359
  if (resolvedUrl === undefined) {
2260
- console.error(
2261
- `error: --isolated builds in a worktree off this repo's arbiter ` +
2262
- `('${arbiterName}'), but no such arbiter remote is configured/found ` +
2263
- `here. Run inside a participating repo (a clone with an arbiter ` +
2264
- `remote), or use --remote <url> to target another repo.`,
2265
- );
2360
+ // Isolated needs an arbiter to build off. Two message variants:
2361
+ // - defaulted-to-isolated (no form flag typed) leads with the DEFAULT
2362
+ // and names `--in-place` as the natural local escape (D2). This is
2363
+ // the ERROR-not-silent-degrade behaviour the flip requires: a repo
2364
+ // with no arbiter must NOT be quietly built in-place invisibly.
2365
+ // - explicit `--isolated` keeps its own terms (the user asked for
2366
+ // isolation deliberately) but ALSO surfaces `--in-place` alongside
2367
+ // `--remote <url>` as the two escapes.
2368
+ if (defaultedToIsolated) {
2369
+ console.error(
2370
+ `error: no arbiter is configured for this repo ` +
2371
+ `(no '${arbiterName}' remote), and \`do\` now defaults to ` +
2372
+ `building in an ISOLATED worktree off THIS repo's arbiter ` +
2373
+ `(task \`make-isolated-default-build-mode\`). Configure an ` +
2374
+ `arbiter (e.g. \`dorfl remote add <url>\`), pass --in-place ` +
2375
+ `to build in the current checkout, or use --remote <url> to ` +
2376
+ `target another repo.`,
2377
+ );
2378
+ } else {
2379
+ console.error(
2380
+ `error: --isolated builds in a worktree off this repo's arbiter ` +
2381
+ `('${arbiterName}'), but no such arbiter remote is configured/found ` +
2382
+ `here. Run inside a participating repo (a clone with an arbiter ` +
2383
+ `remote), pass --in-place to build in the current checkout, or ` +
2384
+ `use --remote <url> to target another repo.`,
2385
+ );
2386
+ }
2266
2387
  process.exit(1);
2267
2388
  }
2268
2389
  effectiveRemote = resolvedUrl;
@@ -2310,7 +2431,7 @@ export function buildProgram(): Command {
2310
2431
  // Host-only runner IDENTITY — scopes git/provider ops only (not the
2311
2432
  // agent launch); absent ⇒ ambient.
2312
2433
  identity: remoteConfig.identity,
2313
- // `do --remote prd:<slug>` tasking-gate policy (task-build path ignores it).
2434
+ // `do --remote spec:<slug>` tasking-gate policy (task-build path ignores it).
2314
2435
  autoTask: remoteConfig.autoTask,
2315
2436
  // The resolved `promptGuidance` nudge — threaded into the remote worker
2316
2437
  // prompt (runRemotePipeline → buildAgentPrompt), mirroring in-place `do`.
@@ -2318,7 +2439,7 @@ export function buildProgram(): Command {
2318
2439
  integration: remoteConfig.integration,
2319
2440
  // EXPLICIT `--merge` override for the untrusted-origin build-propose rule.
2320
2441
  explicitMerge: flagMode === 'merge',
2321
- // Per-TRANSITION TASKING override (the `do --remote prd:` tasking path).
2442
+ // Per-TRANSITION TASKING override (the `do --remote spec:` tasking path).
2322
2443
  taskingIntegration: remoteConfig.taskingIntegration,
2323
2444
  // TASK-PLACEMENT: the configured default + the EXPLICIT operator override
2324
2445
  // (`--tasks-land-in`), the top of the placement precedence — mirrors
@@ -2330,7 +2451,7 @@ export function buildProgram(): Command {
2330
2451
  // Single-job build path: gate the REBASED tip (the default) unconditionally.
2331
2452
  freshWorktreeGate: remoteConfig.freshWorktreeGate,
2332
2453
  // Cross-job merge-serialiser CAS-retry cap (resolved through the per-repo
2333
- // chain on the arbiter-side `.dorfl.json` too) — prd
2454
+ // chain on the arbiter-side `.dorfl.json` too) — spec
2334
2455
  // `land-time-reverify-and-parallel-merge-ceiling` Story 5.
2335
2456
  mergeRetries: remoteConfig.mergeRetries,
2336
2457
  noPR: remoteConfig.noPR,
@@ -2347,7 +2468,7 @@ export function buildProgram(): Command {
2347
2468
  agentCmd: remoteConfig.agentCmd,
2348
2469
  })
2349
2470
  : undefined,
2350
- // The tasker IMPROVER loop on the `do --remote prd:` path is ON by default
2471
+ // The tasker IMPROVER loop on the `do --remote spec:` path is ON by default
2351
2472
  // (auto-tasking has no `verify` floor, so the loop is the task path's
2352
2473
  // quality engine). `--tasker-loop`/`--no-tasker-loop` gates wiring the seam;
2353
2474
  // `taskerLoopMax`/`taskerLoopModel` resolve per-repo (flag > env > per-repo
@@ -2360,7 +2481,7 @@ export function buildProgram(): Command {
2360
2481
  : undefined,
2361
2482
  taskerLoopMax: remoteConfig.taskerLoopMax,
2362
2483
  taskerLoopModel: remoteConfig.taskerLoopModel,
2363
- // The task-SET ACCEPTANCE GATE on the `do --remote prd:` path too.
2484
+ // The task-SET ACCEPTANCE GATE on the `do --remote spec:` path too.
2364
2485
  taskReviewGate: remoteConfig.review
2365
2486
  ? harnessTaskAcceptanceGate({
2366
2487
  harness: remoteHarness,
@@ -2368,6 +2489,11 @@ export function buildProgram(): Command {
2368
2489
  })
2369
2490
  : undefined,
2370
2491
  watch: flags.watch === true,
2492
+ // The dorfl-internal agent deadline + anti-loop ceiling (spec
2493
+ // `graceful-pre-timeout-wip-checkpoint`). Threaded from the RESOLVED
2494
+ // remote-repo config (its committed `dorfl.json` on <arbiter>/main).
2495
+ agentDeadlineMinutes: remoteConfig.agentDeadlineMinutes,
2496
+ maxAutoCheckpoints: remoteConfig.maxAutoCheckpoints,
2371
2497
  color: shouldUseColor(process.stdout),
2372
2498
  note: (message) => console.error(`>> ${message}`),
2373
2499
  noteBlock: (message) => console.error(message),
@@ -2472,7 +2598,7 @@ export function buildProgram(): Command {
2472
2598
  // ops (claim, push, integrate, `gh`) — NEVER the agent launch. Absent ⇒
2473
2599
  // ambient (today's behaviour). Mapped Config → DoOptions like model/agentCmd.
2474
2600
  identity: config.identity,
2475
- // `do prd:<slug>` tasking-gate policy (the task-build path ignores it).
2601
+ // `do spec:<slug>` tasking-gate policy (the task-build path ignores it).
2476
2602
  autoTask: config.autoTask,
2477
2603
  // The resolved `promptGuidance` NUDGE namespace (e.g. `testFirst`),
2478
2604
  // threaded into the worker prompt by performDo → buildAgentPrompt so a
@@ -2485,11 +2611,11 @@ export function buildProgram(): Command {
2485
2611
  // typed `--merge` (`flagMode`), never when `merge` came from config — so an
2486
2612
  // untrusted-origin task still forces propose under a config `merge`.
2487
2613
  explicitMerge: flagMode === 'merge',
2488
- // Per-TRANSITION TASKING override: the `do prd:` tasking path threads
2614
+ // Per-TRANSITION TASKING override: the `do spec:` tasking path threads
2489
2615
  // `taskingIntegration ?? integration`; the task-build path stays on
2490
2616
  // `integration`. Unset ⇒ tasking falls back to `integration` (today's behaviour).
2491
2617
  taskingIntegration: config.taskingIntegration,
2492
- // TASK-PLACEMENT (`do prd:` tasking output): the configured default rung +
2618
+ // TASK-PLACEMENT (`do spec:` tasking output): the configured default rung +
2493
2619
  // the EXPLICIT operator override `--tasks-land-in` (top of the precedence).
2494
2620
  // `explicitTasksLandIn` is set ONLY when the flag was typed (mirrors
2495
2621
  // `explicitMerge`), so an untrusted-origin staging force still wins under a
@@ -2502,7 +2628,7 @@ export function buildProgram(): Command {
2502
2628
  verify: config.verify,
2503
2629
  // Single-job build path: gate the REBASED tip (the default) unconditionally.
2504
2630
  freshWorktreeGate: config.freshWorktreeGate,
2505
- // Cross-job merge-serialiser CAS-retry cap (prd `land-time-reverify-and-
2631
+ // Cross-job merge-serialiser CAS-retry cap (spec `land-time-reverify-and-
2506
2632
  // parallel-merge-ceiling` Story 5 / Applied Answer q1 (a)) — resolved per-repo
2507
2633
  // and threaded to `performComplete`→`performIntegration`.
2508
2634
  mergeRetries: config.mergeRetries,
@@ -2526,7 +2652,7 @@ export function buildProgram(): Command {
2526
2652
  reviewGate: config.review
2527
2653
  ? harnessReviewGate({harness, agentCmd: config.agentCmd})
2528
2654
  : undefined,
2529
- // The tasker IMPROVER loop on the `do prd:` tasking path is ON by default
2655
+ // The tasker IMPROVER loop on the `do spec:` tasking path is ON by default
2530
2656
  // (auto-tasking has no `verify` floor — the loop is the task path's quality
2531
2657
  // engine). `--tasker-loop`/`--no-tasker-loop` gates wiring the seam;
2532
2658
  // `taskerLoopMax`/`taskerLoopModel` resolve per-repo (flag > env > per-repo
@@ -2549,12 +2675,17 @@ export function buildProgram(): Command {
2549
2675
  // `--watch`: tail the pi session log live (pi harness only; the
2550
2676
  // performDo guard errors clearly on any other adapter). READ-ONLY.
2551
2677
  watch: flags.watch === true,
2678
+ // The dorfl-internal agent deadline + anti-loop ceiling (spec
2679
+ // `graceful-pre-timeout-wip-checkpoint`). Threaded from the resolved
2680
+ // per-repo config so an edit to dorfl.json reflects on the NEXT tick.
2681
+ agentDeadlineMinutes: config.agentDeadlineMinutes,
2682
+ maxAutoCheckpoints: config.maxAutoCheckpoints,
2552
2683
  color: shouldUseColor(process.stdout),
2553
2684
  note: (message) => console.error(`>> ${message}`),
2554
2685
  noteBlock: (message) => console.error(message),
2555
2686
  };
2556
2687
 
2557
- // `--allow-backlog` is EXPLICIT-SINGLE-TASK-ONLY (prd
2688
+ // `--allow-backlog` is EXPLICIT-SINGLE-TASK-ONLY (spec
2558
2689
  // `do-allow-backlog-drive-staged-tasks-without-promotion`, decision 4): it
2559
2690
  // drives ONE named staged task in place. It must NOT combine with the
2560
2691
  // AUTO-PICK (zero-args / -n) or MULTI-ITEM forms — those select FROM the
@@ -2615,11 +2746,11 @@ export function buildProgram(): Command {
2615
2746
  });
2616
2747
 
2617
2748
  // `advance` — the SIBLING top-level verb (NOT a `do` subcommand; `do`
2618
- // subcommands + a standalone `task` verb are REJECTED in prd `advance-loop`).
2749
+ // subcommands + a standalone `task` verb are REJECTED in spec `advance-loop`).
2619
2750
  // It reuses the SAME shared `prefix:arg` resolver `do` uses, EXTENDED with the
2620
2751
  // `obs:` namespace, and wires the classify → lock → execute SKELETON: classify
2621
2752
  // the rung (read-only, no model, no lock), take the `advancing` CAS borrow, then
2622
- // dispatch winner-only — build/task rungs ORCHESTRATE `do`/`do prd:` (never a
2753
+ // dispatch winner-only — build/task rungs ORCHESTRATE `do`/`do spec:` (never a
2623
2754
  // duplicate), surface/apply/triage dispatch to a named executor seam later
2624
2755
  // tasks fill. The DRIVERS (one-shot/loop) + `-n` + per-action gates and the
2625
2756
  // rung BODIES are LATER tasks; the bare eligible-SET form errors clearly here.
@@ -2627,11 +2758,11 @@ export function buildProgram(): Command {
2627
2758
  .command('advance')
2628
2759
  .helpGroup(HEADLINE_GROUP)
2629
2760
  .description(
2630
- '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).',
2761
+ '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).',
2631
2762
  )
2632
2763
  .argument(
2633
2764
  '[slugs...]',
2634
- '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.',
2765
+ '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.',
2635
2766
  )
2636
2767
  .option('-c, --config <path>', 'config file path', defaultConfigPath())
2637
2768
  .option(
@@ -2656,15 +2787,15 @@ export function buildProgram(): Command {
2656
2787
  )
2657
2788
  .option(
2658
2789
  '--surface-blockers',
2659
- '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).',
2790
+ '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).',
2660
2791
  )
2661
2792
  .option(
2662
2793
  '--no-surface-blockers',
2663
- 'leave a needsAnswers:true task/prd silently blocked (default; the blocked pool is dropped from auto-pick)',
2794
+ 'leave a needsAnswers:true task/spec silently blocked (default; the blocked pool is dropped from auto-pick)',
2664
2795
  )
2665
2796
  .option(
2666
2797
  '--strict-merge-approval',
2667
- '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.',
2798
+ '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.',
2668
2799
  )
2669
2800
  .option(
2670
2801
  '--no-strict-merge-approval',
@@ -2684,7 +2815,7 @@ export function buildProgram(): Command {
2684
2815
  )
2685
2816
  .option(
2686
2817
  '--tasks-land-in <where>',
2687
- '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.',
2818
+ '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.',
2688
2819
  )
2689
2820
  .option(
2690
2821
  '--watch',
@@ -2820,7 +2951,7 @@ export function buildProgram(): Command {
2820
2951
  integration: remoteConfig.integration,
2821
2952
  // EXPLICIT `--merge` override for the untrusted-origin build-propose rule.
2822
2953
  explicitMerge: flagMode === 'merge',
2823
- // Per-TRANSITION TASKING override (the isolated `do --remote prd:` path).
2954
+ // Per-TRANSITION TASKING override (the isolated `do --remote spec:` path).
2824
2955
  taskingIntegration: remoteConfig.taskingIntegration,
2825
2956
  // TASK-PLACEMENT: configured default + EXPLICIT `--tasks-land-in` override
2826
2957
  // (set only when typed, mirroring `explicitMerge`).
@@ -2830,7 +2961,7 @@ export function buildProgram(): Command {
2830
2961
  verify: remoteConfig.verify,
2831
2962
  // Single-job build path: gate the REBASED tip (the default) unconditionally.
2832
2963
  freshWorktreeGate: remoteConfig.freshWorktreeGate,
2833
- // Cross-job merge-serialiser CAS-retry cap — prd
2964
+ // Cross-job merge-serialiser CAS-retry cap — spec
2834
2965
  // `land-time-reverify-and-parallel-merge-ceiling` Story 5.
2835
2966
  mergeRetries: remoteConfig.mergeRetries,
2836
2967
  noPR: remoteConfig.noPR,
@@ -2962,7 +3093,7 @@ export function buildProgram(): Command {
2962
3093
  // EXPLICIT `--merge` override for the untrusted-origin build-propose rule (a
2963
3094
  // bare `advance` auto-pick passes no flag ⇒ unset ⇒ untrusted forces propose).
2964
3095
  explicitMerge: flagMode === 'merge',
2965
- // Per-TRANSITION TASKING override (the `do prd:` tasking path threads
3096
+ // Per-TRANSITION TASKING override (the `do spec:` tasking path threads
2966
3097
  // `taskingIntegration ?? integration`; the build path stays on `integration`).
2967
3098
  taskingIntegration: config.taskingIntegration,
2968
3099
  // TASK-PLACEMENT: configured default + EXPLICIT `--tasks-land-in` override
@@ -2973,7 +3104,7 @@ export function buildProgram(): Command {
2973
3104
  verify: config.verify,
2974
3105
  // Single-job build path: gate the REBASED tip (the default) unconditionally.
2975
3106
  freshWorktreeGate: config.freshWorktreeGate,
2976
- // Cross-job merge-serialiser CAS-retry cap (prd `land-time-reverify-and-
3107
+ // Cross-job merge-serialiser CAS-retry cap (spec `land-time-reverify-and-
2977
3108
  // parallel-merge-ceiling` Story 5 / Applied Answer q1 (a)).
2978
3109
  mergeRetries: config.mergeRetries,
2979
3110
  noPR: config.noPR,
@@ -2995,6 +3126,10 @@ export function buildProgram(): Command {
2995
3126
  taskReviewGate: config.review
2996
3127
  ? harnessTaskAcceptanceGate({harness, agentCmd: config.agentCmd})
2997
3128
  : undefined,
3129
+ // The dorfl-internal agent deadline + anti-loop ceiling (spec
3130
+ // `graceful-pre-timeout-wip-checkpoint`). Threaded from resolved config.
3131
+ agentDeadlineMinutes: config.agentDeadlineMinutes,
3132
+ maxAutoCheckpoints: config.maxAutoCheckpoints,
2998
3133
  color: shouldUseColor(process.stdout),
2999
3134
  note: (message) => console.error(`>> ${message}`),
3000
3135
  noteBlock: (message) => console.error(message),
@@ -3114,7 +3249,7 @@ export function buildProgram(): Command {
3114
3249
  )
3115
3250
  .option(
3116
3251
  '--reap-stale-locks',
3117
- '(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).',
3252
+ '(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).',
3118
3253
  )
3119
3254
  .option(
3120
3255
  '--remote-branches',
@@ -3137,7 +3272,7 @@ export function buildProgram(): Command {
3137
3272
  const config = resolveGlobalConfig(loadConfig(flags.config), {});
3138
3273
  const workspacesDir = flags.workspace ?? config.workspacesDir;
3139
3274
 
3140
- // The `gc`-STYLE ledger SWEEP (prd `ledger-integrity` story 3): a SEPARATE
3275
+ // The `gc`-STYLE ledger SWEEP (spec `ledger-integrity` story 3): a SEPARATE
3141
3276
  // surface from the worktree reaper below — it REPORTS one-slug-one-folder
3142
3277
  // violations in a repo's `work/` lifecycle ledger and NEVER deletes (a human
3143
3278
  // resolves each). Distinct `work/`: the ledger, not the execution substrate.
@@ -3146,7 +3281,7 @@ export function buildProgram(): Command {
3146
3281
  typeof flags.ledger === 'string' ? flags.ledger : process.cwd();
3147
3282
  const result = sweepLedgerDuplicates(repoPath);
3148
3283
  // The UNIFIED-LOCK stuck/orphaned-lock REPORT (task
3149
- // `release-lock-verb-and-gc-stuck-report`, prd
3284
+ // `release-lock-verb-and-gc-stuck-report`, spec
3150
3285
  // `ledger-status-per-item-lock-refs` US #12/#13/#14): generalises the
3151
3286
  // advancing-marker report from advancing-only to the unified per-item
3152
3287
  // lock. The locks live on the ARBITER ref (`refs/dorfl/lock/*`),
@@ -3159,13 +3294,16 @@ export function buildProgram(): Command {
3159
3294
  // clearing (no auto-sweep; a human asserts a lock is dead via
3160
3295
  // `release-lock`).
3161
3296
  // OPT-IN SWEEP (`--reap-stale-locks`): the WRITE twin of the report. A
3162
- // human asserting "clear the dead TERMINAL locks now": for EXACTLY the
3163
- // `cleared-stale` class (terminal-on-main + active = stranded) perform the
3164
- // SAME leased delete `release-lock` / the recovery use, so one command
3165
- // sweeps every orphaned terminal lock. A `kept-stuck` / `kept-in-flight`
3166
- // lock is NEVER reaped (scope fence); a concurrent change makes a clear
3167
- // REJECT (reported `lost`), never --force. WITHOUT the flag the surface
3168
- // below stays report-only (fail-loud, deletes nothing).
3297
+ // human asserting "clear the dead TERMINAL locks now": for BOTH
3298
+ // terminal-on-main orphan classes (task `reaper-reap-terminal-stuck-lock-orphans`;
3299
+ // ADR `ledger-status-on-per-item-lock-refs` § Addendum 2026-07-10) the
3300
+ // stranded `cleared-stale` AND the stuck-terminal `cleared-stuck-terminal`
3301
+ // crash-orphan perform the SAME leased delete `release-lock` / the
3302
+ // recovery use, so one command sweeps every orphaned terminal lock. A
3303
+ // `kept-stuck` (STUCK + NON-terminal) / `kept-in-flight` lock is NEVER
3304
+ // reaped (scope fence); a concurrent change makes a clear REJECT (reported
3305
+ // `lost`), never --force. WITHOUT the flag the surface below stays
3306
+ // report-only (fail-loud, deletes nothing).
3169
3307
  if (flags.reapStaleLocks) {
3170
3308
  const reap = await reapStaleItemLocks(
3171
3309
  flags.cwd ?? repoPath,
@@ -3189,10 +3327,11 @@ export function buildProgram(): Command {
3189
3327
  : formatLedgerSweep(result),
3190
3328
  );
3191
3329
  }
3192
- // Fail-loud AFTER the sweep: a `kept-stuck` (rightly left for a human) or
3193
- // a `lost`/`error` (a stale lock whose leased delete lost the race) still
3194
- // needs attention; a clean sweep that reaped every stale lock and left
3195
- // only healthy in-flight holds exits 0.
3330
+ // Fail-loud AFTER the sweep: a `kept-stuck` (STUCK + non-terminal
3331
+ // rightly left for a human) or a `lost`/`error` (an orphan lock whose
3332
+ // leased delete lost the race) still needs attention; a clean sweep
3333
+ // that reaped every orphan lock (stale OR stuck-terminal) and left only
3334
+ // healthy in-flight holds exits 0.
3196
3335
  process.exit(
3197
3336
  result.duplicates.length > 0 || reapReportNeedsAttention(reap)
3198
3337
  ? 1
@@ -3231,14 +3370,16 @@ export function buildProgram(): Command {
3231
3370
  // ALL are REPORTED here (never auto-deleted — no automatic sweep exists; a
3232
3371
  // human clears a NAMED unified lock via `release-lock`).
3233
3372
  //
3234
- // SCOPED to the ATTENTION verdicts only (prd US#14/#21, ADR
3235
- // `ledger-status-on-per-item-lock-refs`: this surface is the STUCK /
3236
- // crash-orphaned lock, NOT every held one): a `kept-stuck` (terminal +
3237
- // stuck) or a `cleared-stale`-eligible (terminal + stale active = orphaned)
3238
- // lock fails loud, but a `kept-in-flight` (active, non-terminal) lock is the
3239
- // NORMAL in-flight state of a healthy concurrent build (read by `status` as
3240
- // healthy) it is reported informationally and does NOT make a routine
3241
- // `gc --ledger` health check exit non-zero.
3373
+ // SCOPED to the ATTENTION verdicts only (spec US#14/#21, ADR
3374
+ // `ledger-status-on-per-item-lock-refs` § Addendum 2026-07-10: this surface
3375
+ // is the STUCK / crash-orphaned lock, NOT every held one): a `kept-stuck`
3376
+ // (STUCK + NON-terminal the genuine human-attention case), a
3377
+ // `cleared-stale`-eligible (terminal + stale active = stranded orphan), or
3378
+ // a `cleared-stuck-terminal`-eligible (terminal + stuck = crash-orphan the
3379
+ // reaper can now auto-clear) fails loud, but a `kept-in-flight` (active,
3380
+ // non-terminal) lock is the NORMAL in-flight state of a healthy concurrent
3381
+ // build (read by `status` as healthy) — it is reported informationally
3382
+ // and does NOT make a routine `gc --ledger` health check exit non-zero.
3242
3383
  process.exit(
3243
3384
  result.duplicates.length > 0 ||
3244
3385
  itemLockReportNeedsAttention(lockReport)
@@ -3261,7 +3402,7 @@ export function buildProgram(): Command {
3261
3402
  dryRun: flags.dryRun === true,
3262
3403
  note: (message) => console.error(`>> ${message}`),
3263
3404
  });
3264
- // The ORPHAN-SIDECAR sweep (prd
3405
+ // The ORPHAN-SIDECAR sweep (spec
3265
3406
  // `agentic-question-resolution-retire-disposition-vocabulary`, US #10) rides
3266
3407
  // the SAME `--remote-branches` invocation the SCHEDULED CI lifecycle workflow
3267
3408
  // runs (`dorfl gc --remote-branches --arbiter origin`) — so the reap of a
@@ -3385,7 +3526,7 @@ export function buildProgram(): Command {
3385
3526
  }
3386
3527
 
3387
3528
  // Exit non-zero on a REFUSAL (quiescence gate) or a non-green leak scan
3388
- // (a converted tree that still carries a dangling `prd` ref). A clean
3529
+ // (a converted tree that still carries a dangling ''prd'' ref). A clean
3389
3530
  // dry-run or a green migration exits 0.
3390
3531
  if (result.refused || result.leaks.length > 0) {
3391
3532
  process.exit(1);
@@ -3497,7 +3638,7 @@ export function buildProgram(): Command {
3497
3638
  .command('requeue <slug>')
3498
3639
  .helpGroup(HEADLINE_GROUP)
3499
3640
  .description(
3500
- '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).',
3641
+ '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).',
3501
3642
  )
3502
3643
  .option('-c, --config <path>', 'config file path', defaultConfigPath())
3503
3644
  .option(
@@ -3508,16 +3649,21 @@ export function buildProgram(): Command {
3508
3649
  '--arbiter <remote>',
3509
3650
  '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.',
3510
3651
  )
3652
+ .option(
3653
+ '--reconcile',
3654
+ '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.',
3655
+ )
3656
+ .option('--rebase', 'alias of --reconcile (canonical name)')
3511
3657
  .option(
3512
3658
  '--reset',
3513
- '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.',
3659
+ '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.',
3514
3660
  )
3515
3661
  .option(
3516
3662
  '-m, --message <note>',
3517
3663
  'append a dated handoff note to the item body for the next agent (append-only; applies to both default and --reset)',
3518
3664
  )
3519
3665
  .action(async (rawSlug: string, flags: RequeueFlags) => {
3520
- // Task-only command (§3a): accept bare + `task:`, reject `prd:`.
3666
+ // Task-only command (§3a): accept bare + `task:`, reject `spec:`.
3521
3667
  const slug = resolveTaskOnlySlug(rawSlug) as string;
3522
3668
  const cwd = flags.cwd ?? process.cwd();
3523
3669
  // Route the requeue (default keep+continue / --reset discard / -m handoff)
@@ -3532,6 +3678,14 @@ export function buildProgram(): Command {
3532
3678
  // call site, rather than relying on the seam's silent `?? process.env`
3533
3679
  // default by omission (the implicit fallback that made `requeue`'s human
3534
3680
  // attribution accidental rather than declared).
3681
+ const reconcile = flags.reconcile === true || flags.rebase === true;
3682
+ if (reconcile && flags.reset) {
3683
+ console.error(
3684
+ 'error: --reconcile and --reset are mutually exclusive ' +
3685
+ '(non-destructive recovery vs destructive last resort). Pick one.',
3686
+ );
3687
+ process.exit(1);
3688
+ }
3535
3689
  const result = await ledgerWrite.applyReturnToBacklogTransition({
3536
3690
  cwd,
3537
3691
  slug,
@@ -3540,6 +3694,7 @@ export function buildProgram(): Command {
3540
3694
  // `--cwd` is purely the ORIGIN SOURCE the remote is resolved from.
3541
3695
  arbiter: flags.arbiter ?? 'origin',
3542
3696
  reset: flags.reset,
3697
+ reconcile,
3543
3698
  message: flags.message,
3544
3699
  env: process.env,
3545
3700
  note: (message) => console.error(`>> ${message}`),
@@ -3550,16 +3705,18 @@ export function buildProgram(): Command {
3550
3705
  }
3551
3706
  const how = result.deletedRemoteBranch
3552
3707
  ? ` (--reset: deleted the remote ${workBranchRef('task', slug)} branch; next claim starts fresh)`
3553
- : ' (kept the work branch; next claim continues from its tip)';
3708
+ : result.reconciled
3709
+ ? ` (--reconcile: re-synced mirror + rebased ${workBranchRef('task', slug)} onto latest main and pushed the reconciled tip back; next claim continues from it)`
3710
+ : ' (kept the work branch; next claim continues from its tip)';
3554
3711
  console.log(`Requeued '${slug}' to backlog for re-claiming.${how}`);
3555
3712
  });
3556
3713
 
3557
- // `promote [item]` (prd `staging-pool-position-gate-and-trust-model`, tasks
3714
+ // `promote [item]` (spec `staging-pool-position-gate-and-trust-model`, tasks
3558
3715
  // `pre-backlog-staging-folder-and-promote-step-a` /
3559
3716
  // `pre-prd-staging-pool-split-and-untrusted-prd-placement`): the HUMAN/runner-
3560
3717
  // owned verb that moves a STAGED item into its agent-eligible POOL — a task
3561
- // `work/pre-backlog/<slug>.md → work/backlog/<slug>.md`, a prd
3562
- // `work/prds/proposed/<slug>.md → work/prds/ready/<slug>.md` — as a tree-less CAS on the
3718
+ // `work/tasks/backlog/<slug>.md → work/tasks/ready/<slug>.md`, a spec
3719
+ // `work/specs/proposed/<slug>.md → work/specs/ready/<slug>.md` — as a tree-less CAS on the
3563
3720
  // arbiter, the SAME trust model + mechanism as `requeue`. The agent emits STAGED;
3564
3721
  // only this verb (a human, or the runner) admits it to the pool. With NO argument
3565
3722
  // it LISTS what is promotable (the "what is staged waiting for me?" discovery), so
@@ -3568,7 +3725,7 @@ export function buildProgram(): Command {
3568
3725
  .command('promote [item]')
3569
3726
  .helpGroup(HEADLINE_GROUP)
3570
3727
  .description(
3571
- '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.',
3728
+ `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.`,
3572
3729
  )
3573
3730
  .option('-c, --config <path>', 'config file path', defaultConfigPath())
3574
3731
  .option(
@@ -3596,7 +3753,7 @@ export function buildProgram(): Command {
3596
3753
  }
3597
3754
  if (listed.items.length === 0) {
3598
3755
  console.log(
3599
- `Nothing staged to promote on ${arbiter}/main (work/pre-backlog/ and work/prds/proposed/ are empty).`,
3756
+ `Nothing staged to promote on ${arbiter}/main (${workFolderPrefix('tasks-backlog')} and ${workFolderPrefix('specs-proposed')} are empty).`,
3600
3757
  );
3601
3758
  return;
3602
3759
  }
@@ -3607,11 +3764,10 @@ export function buildProgram(): Command {
3607
3764
  return;
3608
3765
  }
3609
3766
 
3610
- // AN ITEM → promote it. `task:`/`spec:` are explicit (the legacy `prd:`
3611
- // prefix is still ACCEPTED as an input alias through the cutover — the
3612
- // contract task drops it); a bare slug defaults to a task (mirrors
3613
- // `requeue`). An `obs:`/`observation:` prefix is rejected (observations have
3614
- // no pool).
3767
+ // AN ITEM → promote it. `task:`/`spec:` are explicit; a bare slug defaults to
3768
+ // a task (mirrors `requeue`). An `obs:`/`observation:` prefix is rejected
3769
+ // (observations have no pool). A legacy ''prd:'' prefix is a DEAD namespace token
3770
+ // after the hard cutover it resolves as a bare literal task slug, not a spec.
3615
3771
  const parsed = parseSlugArg(rawItem);
3616
3772
  if (parsed.explicit === 'observation') {
3617
3773
  console.error(
@@ -3648,7 +3804,7 @@ export function buildProgram(): Command {
3648
3804
  // `action: advance` on the UNIFIED per-item lock, so `release-lock <item>` (below)
3649
3805
  // is the SOLE named human release for ALL holds (implement/task/advance).
3650
3806
 
3651
- // `release-lock <item>` (task `release-lock-verb-and-gc-stuck-report`, prd
3807
+ // `release-lock <item>` (task `release-lock-verb-and-gc-stuck-report`, spec
3652
3808
  // `ledger-status-per-item-lock-refs` US #14): the HUMAN-invoked named release of
3653
3809
  // a stuck/orphaned UNIFIED per-item lock (`refs/dorfl/lock/<entry>`) —
3654
3810
  // the GENERALISATION of `release-advancing` from the advancing-only marker to
@@ -3661,13 +3817,29 @@ export function buildProgram(): Command {
3661
3817
  // Idempotent: deleting an absent ref is a clean exit-0 "nothing to clear"
3662
3818
  // (`not-held`), NOT a failure — deleting the lock ref(s) is "all locks released"
3663
3819
  // and recoverable (the work is safe on the `work/<slug>` branches + `main`).
3820
+ //
3821
+ // ESCAPE HATCH (`--entry <literal>`, task
3822
+ // `release-lock-entry-escape-hatch-and-literal-entry-reporting`): the item
3823
+ // positional is OPTIONAL because a lock whose entry name is NOT derivable from
3824
+ // any current item-form (a PRE-CUTOVER `slice-<slug>` / `prd-<slug>` entry minted
3825
+ // before the slice→task / `prd-to-spec` vocabulary cutover) is UN-NAMEABLE through
3826
+ // the item-form path. `--entry` takes the literal `<entry>` VERBATIM (bypassing
3827
+ // the namespace mapping) so a human can still NAME + clear such an orphan without
3828
+ // the raw `git push origin --delete refs/dorfl/lock/…` plumbing. It reuses the
3829
+ // SAME leased-delete path (`releaseLiteralLockEntry` → the shared entry-keyed
3830
+ // core), so the trust model is unchanged: the human still asserts liveness.
3831
+ // EXACTLY ONE of {item positional, --entry} must be given.
3664
3832
  program
3665
- .command('release-lock <item>')
3833
+ .command('release-lock [item]')
3666
3834
  .helpGroup(HEADLINE_GROUP)
3667
3835
  .description(
3668
- '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).',
3836
+ '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).',
3669
3837
  )
3670
3838
  .option('-c, --config <path>', 'config file path', defaultConfigPath())
3839
+ .option(
3840
+ '--entry <literal>',
3841
+ '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.',
3842
+ )
3671
3843
  .option(
3672
3844
  '--cwd <dir>',
3673
3845
  'the repo/working clone whose arbiter remote the lock ref is DELETED on (default: cwd)',
@@ -3676,26 +3848,54 @@ export function buildProgram(): Command {
3676
3848
  '--arbiter <remote>',
3677
3849
  'the arbiter git remote the lock ref is deleted on (default: origin)',
3678
3850
  )
3679
- .action(async (item: string, flags: ReleaseLockFlags) => {
3851
+ .action(async (item: string | undefined, flags: ReleaseLockFlags) => {
3680
3852
  const cwd = flags.cwd ?? process.cwd();
3681
3853
  const arbiter = flags.arbiter ?? 'origin';
3682
- const result = await releaseItemLock({
3683
- item,
3684
- cwd,
3685
- arbiter,
3686
- env: process.env,
3687
- });
3854
+ // MUTUAL EXCLUSION: exactly one of {item positional, --entry}. BOTH or
3855
+ // NEITHER is a usage error (non-zero exit, actionable message) — the
3856
+ // item-form and the literal escape hatch are two ways to name ONE lock, not
3857
+ // combinable.
3858
+ if (item !== undefined && flags.entry !== undefined) {
3859
+ console.error(
3860
+ 'error: give EITHER an item (task:<slug> / spec:<slug> / obs:<slug> / <slug>) OR --entry <literal>, not both.',
3861
+ );
3862
+ process.exit(1);
3863
+ }
3864
+ if (item === undefined && flags.entry === undefined) {
3865
+ console.error(
3866
+ '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.',
3867
+ );
3868
+ process.exit(1);
3869
+ }
3870
+ // The LITERAL escape-hatch path (`--entry`): take the entry name verbatim,
3871
+ // bypassing the namespace mapping, and reuse the SAME leased-delete core.
3872
+ const result =
3873
+ flags.entry !== undefined
3874
+ ? await releaseLiteralLockEntry({
3875
+ entry: flags.entry,
3876
+ cwd,
3877
+ arbiter,
3878
+ env: process.env,
3879
+ })
3880
+ : await releaseItemLock({
3881
+ item: item as string,
3882
+ cwd,
3883
+ arbiter,
3884
+ env: process.env,
3885
+ });
3688
3886
  if (result.outcome === 'released') {
3689
3887
  console.log(
3690
3888
  `Released lock '${result.entry}' (${result.ref} deleted on ${arbiter}; the item itself was untouched — it rests on main / its work/<slug> branch).`,
3691
3889
  );
3692
3890
  return;
3693
3891
  }
3694
- // IDEMPOTENT exit semantics: `releaseItemLock` returns `not-held` when the
3892
+ // IDEMPOTENT exit semantics: the release returns `not-held` when the
3695
3893
  // ref is ALREADY absent. For a HUMAN re-running the verb on an
3696
3894
  // already-cleared lock that is the CORRECT "nothing to clear" outcome —
3697
3895
  // deleting the lock ref(s) is "all locks released" and recoverable — so map
3698
- // it to a clean exit-0 with an honest message (NOT a failure).
3896
+ // it to a clean exit-0 with an honest message (NOT a failure). The message
3897
+ // names the LITERAL entry (`refs/dorfl/lock/<entry>`) so an operator can
3898
+ // copy-paste it straight back into a `--entry` invocation.
3699
3899
  if (result.outcome === 'not-held') {
3700
3900
  console.log(
3701
3901
  `No lock to release for '${result.entry}' (${result.ref} is already absent on ${arbiter} — “all locks released”, recoverable).`,
@@ -3706,12 +3906,56 @@ export function buildProgram(): Command {
3706
3906
  process.exit(1);
3707
3907
  });
3708
3908
 
3709
- // `drop <slug>` (prd `agentic-question-resolution-retire-disposition-vocabulary`,
3909
+ // `migrate-stuck-locks` (spec `surface-stuck-as-questions-and-retire-stuck-lock-state`,
3910
+ // resolved decision #3, user story 5; task `migrate-existing-stuck-locks-one-shot`):
3911
+ // the ONE-SHOT ROLLOUT migration that converts every pre-existing `stuck` lock
3912
+ // ref (an item bounced under the OLD model, before `retire-stuck-lock-state`)
3913
+ // into the new resting shape (`needsAnswers:true` + `stuck`-kind sidecar on
3914
+ // `<arbiter>/main` + lock released) via the SAME surface-first-release-second
3915
+ // transition the new bounce uses. Idempotent (a re-run finds no legacy stuck
3916
+ // ref → clean no-op) and bare-arbiter-safe (a ref is a ref). Its own verb (not
3917
+ // folded into `gc --ledger`) because it is a one-shot rollout event with WRITE
3918
+ // semantics on `main`, distinct from `gc`'s report-oriented + terminal-orphan
3919
+ // reap surface — the two are different natures and their exit-code contracts
3920
+ // stay separate.
3921
+ program
3922
+ .command('migrate-stuck-locks')
3923
+ .helpGroup(ADVANCED_GROUP)
3924
+ .description(
3925
+ '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.',
3926
+ )
3927
+ .option(
3928
+ '--cwd <dir>',
3929
+ '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)',
3930
+ )
3931
+ .option(
3932
+ '--arbiter <remote>',
3933
+ 'the arbiter git remote the migration reads + mutates (default: origin)',
3934
+ )
3935
+ .action(async (flags: {cwd?: string; arbiter?: string}) => {
3936
+ const cwd = flags.cwd ?? process.cwd();
3937
+ const arbiter = flags.arbiter ?? 'origin';
3938
+ const note = (message: string) => console.error(`>> ${message}`);
3939
+ const report = await migrateStuckLocks({
3940
+ cwd,
3941
+ arbiter,
3942
+ env: process.env,
3943
+ note,
3944
+ });
3945
+ for (const line of formatMigrateStuckLocksReport(report)) {
3946
+ console.log(line);
3947
+ }
3948
+ if (migrateStuckLocksNeedsAttention(report)) {
3949
+ process.exit(1);
3950
+ }
3951
+ });
3952
+
3953
+ // `drop <slug>` (spec `agentic-question-resolution-retire-disposition-vocabulary`,
3710
3954
  // US #5/#11; task `direct-delete-question-cli-helper`): the DIRECT "throw it
3711
3955
  // away" verb — `git rm` a source item AND its question sidecar (when present) in
3712
3956
  // ONE revertible commit, the reason in the commit MESSAGE (git history is the
3713
3957
  // archive). It does NOT round-trip through the decision engine or spawn an agent
3714
- // (that is the SEPARATE agentic `delete-source` verdict in apply-persist.ts);
3958
+ // (that is the SEPARATE agentic `dispose` verdict in apply-persist.ts);
3715
3959
  // this is the human/skill/CLI no-ceremony delete of decision 7. DISTINCT from
3716
3960
  // the existing `remote rm` (the hub-MIRROR deleter) — different concern, no
3717
3961
  // collision. A LOCAL one-commit primitive over the working tree (like apply): it
@@ -3721,9 +3965,8 @@ export function buildProgram(): Command {
3721
3965
  .command('drop <slug>')
3722
3966
  .helpGroup(HEADLINE_GROUP)
3723
3967
  .description(
3724
- '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).',
3968
+ '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).',
3725
3969
  )
3726
- .option('-c, --config <path>', 'config file path', defaultConfigPath())
3727
3970
  .option(
3728
3971
  '--cwd <dir>',
3729
3972
  'the working clone the revertible delete commit is made in (default: cwd)',
@@ -3767,7 +4010,7 @@ export function buildProgram(): Command {
3767
4010
  .command('intake')
3768
4011
  .helpGroup(HEADLINE_GROUP)
3769
4012
  .description(
3770
- '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.',
4013
+ `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.`,
3771
4014
  )
3772
4015
  .argument(
3773
4016
  '<number>',
@@ -3808,7 +4051,7 @@ export function buildProgram(): Command {
3808
4051
  )
3809
4052
  .option(
3810
4053
  '--origin-trust <trusted|untrusted>',
3811
- "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.",
4054
+ "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.",
3812
4055
  )
3813
4056
  .option(
3814
4057
  '--specs-land-in <where>',
@@ -3859,7 +4102,7 @@ export function buildProgram(): Command {
3859
4102
  console.error(`>> ${resolved.message}`);
3860
4103
  }
3861
4104
  const config = resolved.config;
3862
- // Resolve the PER-OUTCOME integration modes (prd US #9): `intake` decides
4105
+ // Resolve the PER-OUTCOME integration modes (spec US #9): `intake` decides
3863
4106
  // the artifact TYPE at runtime, so a single --merge/--propose can't express
3864
4107
  // a type-conditional policy. The granular flags override the aggregate; an
3865
4108
  // UNSET type falls back to the per-repo/global `integration` (the SAME chain
@@ -3916,6 +4159,13 @@ export function buildProgram(): Command {
3916
4159
  cwd,
3917
4160
  arbiter: flags.arbiter ?? config.defaultArbiter,
3918
4161
  integration: modes,
4162
+ // The resolved cross-job CAS-retry cap (config `mergeRetries`). `intake`
4163
+ // has no `--merge-retries` flag today (task
4164
+ // `thread-merge-retries-cross-task-and-ratify-default` deliberately does
4165
+ // NOT add one — intake is unattended-CI-first, env + per-repo cover the
4166
+ // need); env + per-repo + global fold into `config.mergeRetries` via the
4167
+ // SAME chain, and unset falls through to the engine default.
4168
+ mergeRetries: config.mergeRetries,
3919
4169
  // The origin-trust stamp the CI shell passes IN (unset ⇒ unstamped).
3920
4170
  originTrust,
3921
4171
  noPR: config.noPR,
@@ -3941,10 +4191,10 @@ export function buildProgram(): Command {
3941
4191
  process.exit(result.exitCode);
3942
4192
  });
3943
4193
 
3944
- // The CI CLOSE-JOB driver (prd `runner-in-ci`, capability E; task
4194
+ // The CI CLOSE-JOB driver (spec `runner-in-ci`, capability E; task
3945
4195
  // `install-ci-close-job-workflow`). The thin JOB the emitted close-job workflow
3946
4196
  // invokes on a merge to main: resolve which source issue(s) the landed work
3947
- // closes (resolveClosingIssue), run the "prd complete?" query for the prd case
4197
+ // closes (resolveClosingIssue), run the "spec complete?" query for the spec case
3948
4198
  // (prd-complete-query, done), and close via the IssueProvider seam — all
3949
4199
  // UNCHANGED engine pieces, CONSUMED not re-built (the Out-of-Scope fence). CI
3950
4200
  // owns ONLY the job + trigger. Local-runnable too (a manual catch-up close).
@@ -3952,7 +4202,7 @@ export function buildProgram(): Command {
3952
4202
  .command('close-merged-issues')
3953
4203
  .helpGroup(ADVANCED_GROUP)
3954
4204
  .description(
3955
- '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.',
4205
+ '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.',
3956
4206
  )
3957
4207
  .option(
3958
4208
  '--cwd <dir>',
@@ -4108,7 +4358,7 @@ export function buildProgram(): Command {
4108
4358
  .command('find <folder>')
4109
4359
  .helpGroup(HEADLINE_GROUP)
4110
4360
  .description(
4111
- '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.',
4361
+ `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.`,
4112
4362
  )
4113
4363
  .option('-c, --config <path>', 'config file path', defaultConfigPath())
4114
4364
  .option('--yes', 'add all discovered participating repos (no prompt)')
@@ -4184,7 +4434,7 @@ export function buildProgram(): Command {
4184
4434
  )
4185
4435
  .option(
4186
4436
  '--max-parallel <n>',
4187
- '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.',
4437
+ '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.',
4188
4438
  )
4189
4439
  .action(async (flags: InstallCiFlags) => {
4190
4440
  const workDir = flags.cwd ?? process.cwd();
@@ -4211,6 +4461,7 @@ export function buildProgram(): Command {
4211
4461
  }
4212
4462
  maxParallel = n;
4213
4463
  }
4464
+
4214
4465
  const ctx = new GitHubCIContext({
4215
4466
  workDir,
4216
4467
  repo: flags.repo,
@@ -4241,9 +4492,89 @@ export function buildProgram(): Command {
4241
4492
  });
4242
4493
  });
4243
4494
 
4495
+ // The `skills` group (ADR `skill-install-vendors-incur-agents-map`): install the
4496
+ // packaged dorfl skills (`from-idea`, `setup`, ...) into the OPERATOR's OWN
4497
+ // harness dirs. Distinct from any target-repo propagation (ADR
4498
+ // `methodology-and-skills` §6 — skills don't travel into target repos; only
4499
+ // `work/protocol/` does). Only `add` is exposed in this pass — `list`/`remove`
4500
+ // are deferred until we have a real use case (the vendored `install()` is
4501
+ // idempotent, so `add` alone covers upgrade + drift-repair; a future `remove`
4502
+ // would map onto the vendored `remove()` and is intentionally out of scope
4503
+ // here to keep the CLI surface minimal).
4504
+ const skills = program
4505
+ .command('skills')
4506
+ .helpGroup(HEADLINE_GROUP)
4507
+ .description(
4508
+ "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).",
4509
+ );
4510
+
4511
+ skills
4512
+ .command('add')
4513
+ .helpGroup(HEADLINE_GROUP)
4514
+ .description(
4515
+ '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.',
4516
+ )
4517
+ .option(
4518
+ '--local',
4519
+ 'install project-locally into <cwd>/.agents/skills/ (and each detected harness\u2019s PROJECT dir) instead of globally into ~/.agents/skills/. Default: global.',
4520
+ )
4521
+ .action((flags: SkillsAddFlags) => {
4522
+ const result = installSkills({global: flags.local !== true});
4523
+ console.log(formatSkillsAddReport(result, flags.local === true));
4524
+ });
4525
+
4244
4526
  return program;
4245
4527
  }
4246
4528
 
4529
+ interface SkillsAddFlags {
4530
+ /**
4531
+ * `--local`: project-local install (canonical base `<cwd>/.agents/skills/`)
4532
+ * instead of the global `~/.agents/skills/` default. Named to mirror the
4533
+ * `remote add --local` sibling ('local' = "scope to here") and the vendored
4534
+ * `install()`'s `global: false` semantic. Chosen over `--project` /
4535
+ * `--here` / `--global` inversion for consistency with the existing verb.
4536
+ */
4537
+ local?: boolean;
4538
+ }
4539
+
4540
+ /**
4541
+ * Human-readable report for `dorfl skills add`. Prints the SOURCE the skills
4542
+ * were read from, each canonical install path (one per skill), and each
4543
+ * non-universal harness placement (symlink | copy) with its absolute path.
4544
+ * Universal harnesses read from the canonical dir directly and need no line.
4545
+ * Output goes to stdout; the shape is stable enough for a human to audit "what
4546
+ * landed where" but is NOT a machine contract (no JSON mode in this pass).
4547
+ */
4548
+ export function formatSkillsAddReport(
4549
+ result: InstallSkillsResult,
4550
+ local: boolean,
4551
+ ): string {
4552
+ const lines: string[] = [];
4553
+ const scope = local ? 'project-local' : 'global';
4554
+ lines.push(`Installed dorfl skills (${scope}) from ${result.sourceDir}:`);
4555
+ if (result.paths.length === 0) {
4556
+ lines.push(' (no skills found in source)');
4557
+ } else {
4558
+ for (const p of result.paths.slice().sort()) {
4559
+ lines.push(` ${p}`);
4560
+ }
4561
+ }
4562
+ if (result.agents.length === 0) {
4563
+ lines.push(
4564
+ 'Harnesses: no non-universal harness detected (universal harnesses read from the canonical dir directly).',
4565
+ );
4566
+ } else {
4567
+ lines.push('Harnesses:');
4568
+ for (const a of result.agents.slice().sort((x, y) => {
4569
+ const byAgent = x.agent.localeCompare(y.agent);
4570
+ return byAgent !== 0 ? byAgent : x.path.localeCompare(y.path);
4571
+ })) {
4572
+ lines.push(` ${a.agent}: ${a.mode} -> ${a.path}`);
4573
+ }
4574
+ }
4575
+ return lines.join('\n');
4576
+ }
4577
+
4247
4578
  /**
4248
4579
  * A readline-backed {@link WizardPrompts} for the interactive `install-ci`
4249
4580
  * wizard. Prompts go to stderr (stdout is reserved for any machine output); a