dorfl 0.1.2 → 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 (364) hide show
  1. package/dist/advance-drivers.d.ts +1 -1
  2. package/dist/advance-drivers.d.ts.map +1 -1
  3. package/dist/advance-drivers.js +15 -3
  4. package/dist/advance-drivers.js.map +1 -1
  5. package/dist/advance-lifecycle-template.d.ts.map +1 -1
  6. package/dist/advance-lifecycle-template.js +76 -4
  7. package/dist/advance-lifecycle-template.js.map +1 -1
  8. package/dist/advance-treeless-publish.d.ts +23 -0
  9. package/dist/advance-treeless-publish.d.ts.map +1 -1
  10. package/dist/advance-treeless-publish.js +41 -0
  11. package/dist/advance-treeless-publish.js.map +1 -1
  12. package/dist/advance.d.ts +59 -7
  13. package/dist/advance.d.ts.map +1 -1
  14. package/dist/advance.js +389 -82
  15. package/dist/advance.js.map +1 -1
  16. package/dist/advancing-lock.d.ts +30 -2
  17. package/dist/advancing-lock.d.ts.map +1 -1
  18. package/dist/advancing-lock.js +50 -0
  19. package/dist/advancing-lock.js.map +1 -1
  20. package/dist/agent-launch.d.ts +12 -0
  21. package/dist/agent-launch.d.ts.map +1 -1
  22. package/dist/agent-launch.js +22 -12
  23. package/dist/agent-launch.js.map +1 -1
  24. package/dist/agent-stop.d.ts +40 -2
  25. package/dist/agent-stop.d.ts.map +1 -1
  26. package/dist/agent-stop.js +30 -2
  27. package/dist/agent-stop.js.map +1 -1
  28. package/dist/apply-decide.d.ts +17 -3
  29. package/dist/apply-decide.d.ts.map +1 -1
  30. package/dist/apply-decide.js +36 -7
  31. package/dist/apply-decide.js.map +1 -1
  32. package/dist/apply-merge-action.d.ts +17 -5
  33. package/dist/apply-merge-action.d.ts.map +1 -1
  34. package/dist/apply-merge-action.js +43 -8
  35. package/dist/apply-merge-action.js.map +1 -1
  36. package/dist/apply-persist.d.ts +57 -26
  37. package/dist/apply-persist.d.ts.map +1 -1
  38. package/dist/apply-persist.js +166 -37
  39. package/dist/apply-persist.js.map +1 -1
  40. package/dist/apply-stuck-action.d.ts +151 -0
  41. package/dist/apply-stuck-action.d.ts.map +1 -0
  42. package/dist/apply-stuck-action.js +125 -0
  43. package/dist/apply-stuck-action.js.map +1 -0
  44. package/dist/brand.d.ts +12 -1
  45. package/dist/brand.d.ts.map +1 -1
  46. package/dist/brand.js +2 -1
  47. package/dist/brand.js.map +1 -1
  48. package/dist/cli-spinner.d.ts +1 -1
  49. package/dist/cli-spinner.js +1 -1
  50. package/dist/cli.d.ts +10 -0
  51. package/dist/cli.d.ts.map +1 -1
  52. package/dist/cli.js +320 -63
  53. package/dist/cli.js.map +1 -1
  54. package/dist/close-job-template.js +1 -1
  55. package/dist/complete.d.ts +6 -3
  56. package/dist/complete.d.ts.map +1 -1
  57. package/dist/complete.js +91 -37
  58. package/dist/complete.js.map +1 -1
  59. package/dist/config.d.ts +55 -9
  60. package/dist/config.d.ts.map +1 -1
  61. package/dist/config.js +56 -4
  62. package/dist/config.js.map +1 -1
  63. package/dist/continue-branch.d.ts.map +1 -1
  64. package/dist/continue-branch.js +22 -0
  65. package/dist/continue-branch.js.map +1 -1
  66. package/dist/decision-engine.d.ts +38 -10
  67. package/dist/decision-engine.d.ts.map +1 -1
  68. package/dist/decision-engine.js +10 -6
  69. package/dist/decision-engine.js.map +1 -1
  70. package/dist/do-autopick.d.ts.map +1 -1
  71. package/dist/do-autopick.js +11 -1
  72. package/dist/do-autopick.js.map +1 -1
  73. package/dist/do-config.d.ts +1 -1
  74. package/dist/do.d.ts +49 -56
  75. package/dist/do.d.ts.map +1 -1
  76. package/dist/do.js +419 -30
  77. package/dist/do.js.map +1 -1
  78. package/dist/env-config.d.ts.map +1 -1
  79. package/dist/env-config.js +12 -4
  80. package/dist/env-config.js.map +1 -1
  81. package/dist/failure-cause.d.ts +3 -2
  82. package/dist/failure-cause.d.ts.map +1 -1
  83. package/dist/failure-cause.js +28 -1
  84. package/dist/failure-cause.js.map +1 -1
  85. package/dist/format.d.ts +6 -6
  86. package/dist/format.d.ts.map +1 -1
  87. package/dist/format.js +10 -27
  88. package/dist/format.js.map +1 -1
  89. package/dist/frontmatter.d.ts +16 -4
  90. package/dist/frontmatter.d.ts.map +1 -1
  91. package/dist/frontmatter.js +27 -1
  92. package/dist/frontmatter.js.map +1 -1
  93. package/dist/gc.d.ts +65 -6
  94. package/dist/gc.d.ts.map +1 -1
  95. package/dist/gc.js +126 -9
  96. package/dist/gc.js.map +1 -1
  97. package/dist/github.d.ts +14 -0
  98. package/dist/github.d.ts.map +1 -1
  99. package/dist/github.js +73 -0
  100. package/dist/github.js.map +1 -1
  101. package/dist/harness.d.ts +25 -0
  102. package/dist/harness.d.ts.map +1 -1
  103. package/dist/harness.js.map +1 -1
  104. package/dist/index.d.ts +4 -1
  105. package/dist/index.d.ts.map +1 -1
  106. package/dist/index.js +2 -1
  107. package/dist/index.js.map +1 -1
  108. package/dist/install-ci-branch-protection.d.ts +132 -38
  109. package/dist/install-ci-branch-protection.d.ts.map +1 -1
  110. package/dist/install-ci-branch-protection.js +190 -48
  111. package/dist/install-ci-branch-protection.js.map +1 -1
  112. package/dist/install-ci-core.d.ts +21 -1
  113. package/dist/install-ci-core.d.ts.map +1 -1
  114. package/dist/install-ci-core.js +10 -3
  115. package/dist/install-ci-core.js.map +1 -1
  116. package/dist/install-ci-github.d.ts +32 -0
  117. package/dist/install-ci-github.d.ts.map +1 -1
  118. package/dist/install-ci-github.js +73 -0
  119. package/dist/install-ci-github.js.map +1 -1
  120. package/dist/install-skills.d.ts +123 -0
  121. package/dist/install-skills.d.ts.map +1 -0
  122. package/dist/install-skills.js +100 -0
  123. package/dist/install-skills.js.map +1 -0
  124. package/dist/intake-trigger-template.js +1 -1
  125. package/dist/intake.d.ts +17 -4
  126. package/dist/intake.d.ts.map +1 -1
  127. package/dist/intake.js +23 -7
  128. package/dist/intake.js.map +1 -1
  129. package/dist/integration-core.d.ts +10 -7
  130. package/dist/integration-core.d.ts.map +1 -1
  131. package/dist/integration-core.js +174 -60
  132. package/dist/integration-core.js.map +1 -1
  133. package/dist/integrator.d.ts +1 -1
  134. package/dist/integrator.d.ts.map +1 -1
  135. package/dist/integrator.js +19 -3
  136. package/dist/integrator.js.map +1 -1
  137. package/dist/isolation.d.ts +2 -2
  138. package/dist/isolation.d.ts.map +1 -1
  139. package/dist/isolation.js +9 -0
  140. package/dist/isolation.js.map +1 -1
  141. package/dist/item-lock.d.ts +196 -129
  142. package/dist/item-lock.d.ts.map +1 -1
  143. package/dist/item-lock.js +329 -254
  144. package/dist/item-lock.js.map +1 -1
  145. package/dist/ledger-write.d.ts +31 -26
  146. package/dist/ledger-write.d.ts.map +1 -1
  147. package/dist/ledger-write.js +100 -122
  148. package/dist/ledger-write.js.map +1 -1
  149. package/dist/lifecycle-gather.d.ts +17 -0
  150. package/dist/lifecycle-gather.d.ts.map +1 -1
  151. package/dist/lifecycle-gather.js +7 -1
  152. package/dist/lifecycle-gather.js.map +1 -1
  153. package/dist/lifecycle-pools.d.ts +40 -3
  154. package/dist/lifecycle-pools.d.ts.map +1 -1
  155. package/dist/lifecycle-pools.js +26 -6
  156. package/dist/lifecycle-pools.js.map +1 -1
  157. package/dist/merge-question-surfacer.d.ts +15 -1
  158. package/dist/merge-question-surfacer.d.ts.map +1 -1
  159. package/dist/merge-question-surfacer.js +18 -3
  160. package/dist/merge-question-surfacer.js.map +1 -1
  161. package/dist/migrate-stuck-locks.d.ts +129 -0
  162. package/dist/migrate-stuck-locks.d.ts.map +1 -0
  163. package/dist/migrate-stuck-locks.js +355 -0
  164. package/dist/migrate-stuck-locks.js.map +1 -0
  165. package/dist/needs-attention.d.ts +293 -16
  166. package/dist/needs-attention.d.ts.map +1 -1
  167. package/dist/needs-attention.js +532 -57
  168. package/dist/needs-attention.js.map +1 -1
  169. package/dist/orphan-sidecar.d.ts +8 -4
  170. package/dist/orphan-sidecar.d.ts.map +1 -1
  171. package/dist/orphan-sidecar.js +35 -2
  172. package/dist/orphan-sidecar.js.map +1 -1
  173. package/dist/pi-harness.d.ts +16 -0
  174. package/dist/pi-harness.d.ts.map +1 -1
  175. package/dist/pi-harness.js +82 -2
  176. package/dist/pi-harness.js.map +1 -1
  177. package/dist/placement.d.ts +3 -3
  178. package/dist/placement.js +1 -1
  179. package/dist/prd-to-spec.d.ts.map +1 -1
  180. package/dist/prd-to-spec.js +9 -5
  181. package/dist/prd-to-spec.js.map +1 -1
  182. package/dist/protocol/CLAIM-PROTOCOL.md +17 -10
  183. package/dist/protocol/REVIEW-PROTOCOL.md +4 -1
  184. package/dist/protocol/SURFACE-PROTOCOL.md +16 -2
  185. package/dist/protocol/TASKING-PROTOCOL.md +3 -1
  186. package/dist/protocol/WORK-CONTRACT.md +22 -18
  187. package/dist/protocol/task-template.md +1 -1
  188. package/dist/reap-branches.d.ts +12 -9
  189. package/dist/reap-branches.d.ts.map +1 -1
  190. package/dist/reap-branches.js +25 -7
  191. package/dist/reap-branches.js.map +1 -1
  192. package/dist/recover-isolated.d.ts +12 -0
  193. package/dist/recover-isolated.d.ts.map +1 -1
  194. package/dist/recover-isolated.js +5 -0
  195. package/dist/recover-isolated.js.map +1 -1
  196. package/dist/repo-config.d.ts +23 -2
  197. package/dist/repo-config.d.ts.map +1 -1
  198. package/dist/repo-config.js +53 -7
  199. package/dist/repo-config.js.map +1 -1
  200. package/dist/repo-mirror.d.ts.map +1 -1
  201. package/dist/repo-mirror.js +18 -2
  202. package/dist/repo-mirror.js.map +1 -1
  203. package/dist/review-verdict.js +1 -1
  204. package/dist/review-verdict.js.map +1 -1
  205. package/dist/run.d.ts +1 -1
  206. package/dist/run.d.ts.map +1 -1
  207. package/dist/run.js +54 -19
  208. package/dist/run.js.map +1 -1
  209. package/dist/scan.d.ts +17 -8
  210. package/dist/scan.d.ts.map +1 -1
  211. package/dist/scan.js +22 -8
  212. package/dist/scan.js.map +1 -1
  213. package/dist/select-priority.d.ts +2 -2
  214. package/dist/select-priority.js +1 -1
  215. package/dist/select-priority.js.map +1 -1
  216. package/dist/sidecar-apply.js +1 -1
  217. package/dist/sidecar-apply.js.map +1 -1
  218. package/dist/sidecar.d.ts +45 -10
  219. package/dist/sidecar.d.ts.map +1 -1
  220. package/dist/sidecar.js +82 -2
  221. package/dist/sidecar.js.map +1 -1
  222. package/dist/skills/answer-questions/SKILL.md +89 -0
  223. package/dist/skills/capture-signal/SKILL.md +52 -0
  224. package/dist/skills/convert-from-prd-to-spec/SKILL.md +90 -0
  225. package/dist/skills/drive-tasks/SKILL.md +218 -0
  226. package/dist/skills/from-idea/SKILL.md +83 -0
  227. package/dist/skills/merge-prs/SKILL.md +70 -0
  228. package/dist/skills/orchestrate/SKILL.md +101 -0
  229. package/dist/skills/promote/SKILL.md +35 -0
  230. package/dist/skills/review/SKILL.md +16 -0
  231. package/dist/skills/setup/SKILL.md +258 -0
  232. package/dist/skills/setup/protocol/ADR-FORMAT.md +47 -0
  233. package/dist/skills/setup/protocol/CLAIM-PROTOCOL.md +224 -0
  234. package/dist/skills/setup/protocol/REVIEW-PROTOCOL.md +122 -0
  235. package/dist/skills/setup/protocol/SURFACE-PROTOCOL.md +135 -0
  236. package/dist/skills/setup/protocol/TASKING-PROTOCOL.md +124 -0
  237. package/dist/skills/setup/protocol/WORK-CONTRACT.md +280 -0
  238. package/dist/skills/setup/protocol/spec-template.md +71 -0
  239. package/dist/skills/setup/protocol/task-template.md +65 -0
  240. package/dist/skills/surface-questions/SKILL.md +16 -0
  241. package/dist/skills/to-spec/SKILL.md +34 -0
  242. package/dist/skills/to-task/SKILL.md +19 -0
  243. package/dist/skills/triage-observations/SKILL.md +78 -0
  244. package/dist/skills/work/SKILL.md +51 -0
  245. package/dist/slug-namespace.d.ts +5 -5
  246. package/dist/slug-namespace.js +5 -5
  247. package/dist/slug-namespace.js.map +1 -1
  248. package/dist/spec-complete.d.ts.map +1 -1
  249. package/dist/spec-complete.js +2 -3
  250. package/dist/spec-complete.js.map +1 -1
  251. package/dist/start.d.ts.map +1 -1
  252. package/dist/start.js +51 -57
  253. package/dist/start.js.map +1 -1
  254. package/dist/surface-gate.d.ts.map +1 -1
  255. package/dist/surface-gate.js +10 -3
  256. package/dist/surface-gate.js.map +1 -1
  257. package/dist/surface-persist.js +1 -1
  258. package/dist/surface-persist.js.map +1 -1
  259. package/dist/tasker-review-loop.d.ts +4 -3
  260. package/dist/tasker-review-loop.d.ts.map +1 -1
  261. package/dist/tasker-review-loop.js.map +1 -1
  262. package/dist/tasking-lock.d.ts +2 -2
  263. package/dist/tasking-lock.d.ts.map +1 -1
  264. package/dist/tasking-lock.js +53 -32
  265. package/dist/tasking-lock.js.map +1 -1
  266. package/dist/tasking.d.ts +40 -6
  267. package/dist/tasking.d.ts.map +1 -1
  268. package/dist/tasking.js +224 -12
  269. package/dist/tasking.js.map +1 -1
  270. package/dist/triage-persist.d.ts +10 -6
  271. package/dist/triage-persist.d.ts.map +1 -1
  272. package/dist/triage-persist.js +34 -4
  273. package/dist/triage-persist.js.map +1 -1
  274. package/dist/vendor/incur/agents.d.ts +58 -0
  275. package/dist/vendor/incur/agents.d.ts.map +1 -0
  276. package/dist/vendor/incur/agents.js +343 -0
  277. package/dist/vendor/incur/agents.js.map +1 -0
  278. package/dist/verify-workflow-template.js +1 -1
  279. package/dist/watch-session.d.ts +11 -3
  280. package/dist/watch-session.d.ts.map +1 -1
  281. package/dist/watch-session.js +94 -7
  282. package/dist/watch-session.js.map +1 -1
  283. package/dist/work-layout.d.ts +7 -3
  284. package/dist/work-layout.d.ts.map +1 -1
  285. package/dist/work-layout.js +6 -3
  286. package/dist/work-layout.js.map +1 -1
  287. package/dist/workspace.d.ts +1 -1
  288. package/package.json +2 -2
  289. package/src/advance-drivers.ts +16 -3
  290. package/src/advance-lifecycle-template.ts +89 -4
  291. package/src/advance-treeless-publish.ts +45 -0
  292. package/src/advance.ts +467 -87
  293. package/src/advancing-lock.ts +96 -1
  294. package/src/agent-launch.ts +37 -12
  295. package/src/agent-stop.ts +60 -2
  296. package/src/apply-decide.ts +36 -7
  297. package/src/apply-merge-action.ts +45 -7
  298. package/src/apply-persist.ts +228 -54
  299. package/src/apply-stuck-action.ts +260 -0
  300. package/src/brand.ts +14 -2
  301. package/src/cli-spinner.ts +1 -1
  302. package/src/cli.ts +401 -70
  303. package/src/close-job-template.ts +1 -1
  304. package/src/complete.ts +110 -52
  305. package/src/config.ts +107 -13
  306. package/src/continue-branch.ts +22 -0
  307. package/src/decision-engine.ts +55 -17
  308. package/src/do-autopick.ts +12 -1
  309. package/src/do-config.ts +1 -1
  310. package/src/do.ts +511 -38
  311. package/src/env-config.ts +12 -4
  312. package/src/failure-cause.ts +30 -1
  313. package/src/format.ts +10 -30
  314. package/src/frontmatter.ts +35 -5
  315. package/src/gc.ts +172 -9
  316. package/src/github.ts +78 -0
  317. package/src/harness.ts +25 -0
  318. package/src/index.ts +10 -0
  319. package/src/install-ci-branch-protection.ts +282 -57
  320. package/src/install-ci-core.ts +30 -3
  321. package/src/install-ci-github.ts +86 -0
  322. package/src/install-skills.ts +166 -0
  323. package/src/intake-trigger-template.ts +1 -1
  324. package/src/intake.ts +46 -11
  325. package/src/integration-core.ts +199 -65
  326. package/src/integrator.ts +22 -9
  327. package/src/isolation.ts +11 -2
  328. package/src/item-lock.ts +485 -341
  329. package/src/ledger-write.ts +135 -150
  330. package/src/lifecycle-gather.ts +29 -6
  331. package/src/lifecycle-pools.ts +67 -9
  332. package/src/merge-question-surfacer.ts +28 -4
  333. package/src/migrate-stuck-locks.ts +451 -0
  334. package/src/needs-attention.ts +857 -84
  335. package/src/orphan-sidecar.ts +47 -6
  336. package/src/pi-harness.ts +82 -2
  337. package/src/placement.ts +3 -3
  338. package/src/prd-to-spec.ts +13 -5
  339. package/src/reap-branches.ts +38 -14
  340. package/src/recover-isolated.ts +17 -0
  341. package/src/repo-config.ts +55 -6
  342. package/src/repo-mirror.ts +23 -1
  343. package/src/review-verdict.ts +1 -1
  344. package/src/run.ts +64 -18
  345. package/src/scan.ts +22 -8
  346. package/src/select-priority.ts +3 -3
  347. package/src/sidecar-apply.ts +1 -1
  348. package/src/sidecar.ts +138 -11
  349. package/src/slug-namespace.ts +7 -7
  350. package/src/spec-complete.ts +2 -3
  351. package/src/start.ts +53 -59
  352. package/src/surface-gate.ts +10 -3
  353. package/src/surface-persist.ts +1 -1
  354. package/src/tasker-review-loop.ts +4 -3
  355. package/src/tasking-lock.ts +52 -36
  356. package/src/tasking.ts +261 -17
  357. package/src/triage-persist.ts +56 -12
  358. package/src/vendor/incur/LICENSE +21 -0
  359. package/src/vendor/incur/README.md +19 -0
  360. package/src/vendor/incur/agents.ts +392 -0
  361. package/src/verify-workflow-template.ts +1 -1
  362. package/src/watch-session.ts +102 -7
  363. package/src/work-layout.ts +6 -3
  364. package/src/workspace.ts +1 -1
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;
@@ -392,7 +399,7 @@ function buildRegistrySetAdvanceTick(options: {
392
399
  // `taskingIntegration ?? integration`; the build path stays on `integration`.
393
400
  taskingIntegration: config.taskingIntegration,
394
401
  // The TASK-PLACEMENT configured default (`do spec:` tasking output:
395
- // `pre-backlog` staged vs `ready` pool). No operator flag on this
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,
@@ -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 spec:` 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;
@@ -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.
@@ -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
  }
@@ -982,9 +993,9 @@ interface CloseMergedIssuesFlags {
982
993
  * REJECT `spec:` with a clear "operates on tasks, not specs" error. On rejection
983
994
  * it prints the error to stderr and exits 1 (the task-only commands never act on
984
995
  * a spec). An OMITTED slug (`start`/`complete`/`prompt` infer it from the branch)
985
- * passes through untouched. (The legacy `prd:` prefix is a DEAD namespace token
996
+ * passes through untouched. (The legacy ''prd:'' prefix is a DEAD namespace token
986
997
  * after the hard cutover — it is neither special-cased nor rejected here; a
987
- * `prd:<slug>` arg resolves as a bare literal task slug.)
998
+ * ''prd:<slug>'' arg resolves as a bare literal task slug.)
988
999
  *
989
1000
  * `do` is the ONE command that spans both namespaces; it consumes the full
990
1001
  * `resolveSlug` (with the cross-namespace collision check) in the `do-in-place`
@@ -1164,6 +1175,44 @@ export function buildProgram(): Command {
1164
1175
  .name(brand.bin)
1165
1176
  .description('Autonomous parallel agents over file-based work/ queues.');
1166
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
+
1167
1216
  program
1168
1217
  .command('scan')
1169
1218
  .helpGroup(HEADLINE_GROUP)
@@ -1516,7 +1565,7 @@ export function buildProgram(): Command {
1516
1565
  .command('claim')
1517
1566
  .helpGroup(ADVANCED_GROUP)
1518
1567
  .description(
1519
- '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).`,
1520
1569
  )
1521
1570
  .argument('<slug>', 'the slug of the backlog item to claim')
1522
1571
  .option(
@@ -1789,7 +1838,7 @@ export function buildProgram(): Command {
1789
1838
  .command('prompt')
1790
1839
  .helpGroup(ADVANCED_GROUP)
1791
1840
  .description(
1792
- "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 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.`,
1793
1842
  )
1794
1843
  .argument(
1795
1844
  '[slug]',
@@ -1927,6 +1976,11 @@ export function buildProgram(): Command {
1927
1976
  override,
1928
1977
  flags: {
1929
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),
1930
1984
  ...noPRFlagOverrides(flags),
1931
1985
  },
1932
1986
  });
@@ -1941,6 +1995,9 @@ export function buildProgram(): Command {
1941
1995
  workspacesDir: flags.workspace ?? isoConfig.workspacesDir,
1942
1996
  integration: isoConfig.integration,
1943
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,
1944
2001
  note: (message) => console.error(`>> ${message}`),
1945
2002
  env: process.env,
1946
2003
  });
@@ -2040,7 +2097,7 @@ export function buildProgram(): Command {
2040
2097
  .command('do')
2041
2098
  .helpGroup(HEADLINE_GROUP)
2042
2099
  .description(
2043
- '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) | 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.",
2044
2101
  )
2045
2102
  // EXTENSIBLE argument grammar (the three do-* tasks grow this one block):
2046
2103
  // `do-autopick` widens the single optional positional into a VARIADIC one so
@@ -2070,7 +2127,11 @@ export function buildProgram(): Command {
2070
2127
  )
2071
2128
  .option(
2072
2129
  '--isolated',
2073
- "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.",
2074
2135
  )
2075
2136
  .option(
2076
2137
  '--merge',
@@ -2082,7 +2143,7 @@ export function buildProgram(): Command {
2082
2143
  )
2083
2144
  .option(
2084
2145
  '--tasks-land-in <where>',
2085
- 'where `do spec:<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.',
2086
2147
  )
2087
2148
  .option(
2088
2149
  '--no-pr',
@@ -2221,14 +2282,52 @@ export function buildProgram(): Command {
2221
2282
  // `remote-do-reads-per-repo-config-from-arbiter-main`). Only the whitelisted
2222
2283
  // `REPO_ALLOWED_KEYS` are layered (host-only keys stay global/flag/env-only,
2223
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.
2224
2313
  const isolatedNoRemote =
2225
- 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;
2226
2319
  if (flags.remote !== undefined || isolatedNoRemote) {
2227
2320
  // The form's user-facing name + canonical usage, for the shared error
2228
- // messages below (so `--isolated` errors read in its own terms).
2229
- 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';
2230
2327
  const usage = isolatedNoRemote
2231
- ? '`do --isolated <slug>`'
2328
+ ? defaultedToIsolated
2329
+ ? '`do <slug>`'
2330
+ : '`do --isolated <slug>`'
2232
2331
  : '`do --remote <r> <slug>`';
2233
2332
  // The no-checkout forms now support the SAME variadic grammar the in-place
2234
2333
  // form does: a single NAMED item, MULTIPLE named items (sequential), and
@@ -2258,12 +2357,33 @@ export function buildProgram(): Command {
2258
2357
  identityEnv(bootstrapIdentity, process.env),
2259
2358
  );
2260
2359
  if (resolvedUrl === undefined) {
2261
- console.error(
2262
- `error: --isolated builds in a worktree off this repo's arbiter ` +
2263
- `('${arbiterName}'), but no such arbiter remote is configured/found ` +
2264
- `here. Run inside a participating repo (a clone with an arbiter ` +
2265
- `remote), or use --remote <url> to target another repo.`,
2266
- );
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
+ }
2267
2387
  process.exit(1);
2268
2388
  }
2269
2389
  effectiveRemote = resolvedUrl;
@@ -2369,6 +2489,11 @@ export function buildProgram(): Command {
2369
2489
  })
2370
2490
  : undefined,
2371
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,
2372
2497
  color: shouldUseColor(process.stdout),
2373
2498
  note: (message) => console.error(`>> ${message}`),
2374
2499
  noteBlock: (message) => console.error(message),
@@ -2550,6 +2675,11 @@ export function buildProgram(): Command {
2550
2675
  // `--watch`: tail the pi session log live (pi harness only; the
2551
2676
  // performDo guard errors clearly on any other adapter). READ-ONLY.
2552
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,
2553
2683
  color: shouldUseColor(process.stdout),
2554
2684
  note: (message) => console.error(`>> ${message}`),
2555
2685
  noteBlock: (message) => console.error(message),
@@ -2685,7 +2815,7 @@ export function buildProgram(): Command {
2685
2815
  )
2686
2816
  .option(
2687
2817
  '--tasks-land-in <where>',
2688
- 'where `advance spec:<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.',
2689
2819
  )
2690
2820
  .option(
2691
2821
  '--watch',
@@ -2996,6 +3126,10 @@ export function buildProgram(): Command {
2996
3126
  taskReviewGate: config.review
2997
3127
  ? harnessTaskAcceptanceGate({harness, agentCmd: config.agentCmd})
2998
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,
2999
3133
  color: shouldUseColor(process.stdout),
3000
3134
  note: (message) => console.error(`>> ${message}`),
3001
3135
  noteBlock: (message) => console.error(message),
@@ -3115,7 +3249,7 @@ export function buildProgram(): Command {
3115
3249
  )
3116
3250
  .option(
3117
3251
  '--reap-stale-locks',
3118
- '(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).',
3119
3253
  )
3120
3254
  .option(
3121
3255
  '--remote-branches',
@@ -3160,13 +3294,16 @@ export function buildProgram(): Command {
3160
3294
  // clearing (no auto-sweep; a human asserts a lock is dead via
3161
3295
  // `release-lock`).
3162
3296
  // OPT-IN SWEEP (`--reap-stale-locks`): the WRITE twin of the report. A
3163
- // human asserting "clear the dead TERMINAL locks now": for EXACTLY the
3164
- // `cleared-stale` class (terminal-on-main + active = stranded) perform the
3165
- // SAME leased delete `release-lock` / the recovery use, so one command
3166
- // sweeps every orphaned terminal lock. A `kept-stuck` / `kept-in-flight`
3167
- // lock is NEVER reaped (scope fence); a concurrent change makes a clear
3168
- // REJECT (reported `lost`), never --force. WITHOUT the flag the surface
3169
- // 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).
3170
3307
  if (flags.reapStaleLocks) {
3171
3308
  const reap = await reapStaleItemLocks(
3172
3309
  flags.cwd ?? repoPath,
@@ -3190,10 +3327,11 @@ export function buildProgram(): Command {
3190
3327
  : formatLedgerSweep(result),
3191
3328
  );
3192
3329
  }
3193
- // Fail-loud AFTER the sweep: a `kept-stuck` (rightly left for a human) or
3194
- // a `lost`/`error` (a stale lock whose leased delete lost the race) still
3195
- // needs attention; a clean sweep that reaped every stale lock and left
3196
- // 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.
3197
3335
  process.exit(
3198
3336
  result.duplicates.length > 0 || reapReportNeedsAttention(reap)
3199
3337
  ? 1
@@ -3233,13 +3371,15 @@ export function buildProgram(): Command {
3233
3371
  // human clears a NAMED unified lock via `release-lock`).
3234
3372
  //
3235
3373
  // SCOPED to the ATTENTION verdicts only (spec US#14/#21, ADR
3236
- // `ledger-status-on-per-item-lock-refs`: this surface is the STUCK /
3237
- // crash-orphaned lock, NOT every held one): a `kept-stuck` (terminal +
3238
- // stuck) or a `cleared-stale`-eligible (terminal + stale active = orphaned)
3239
- // lock fails loud, but a `kept-in-flight` (active, non-terminal) lock is the
3240
- // NORMAL in-flight state of a healthy concurrent build (read by `status` as
3241
- // healthy) it is reported informationally and does NOT make a routine
3242
- // `gc --ledger` health check exit non-zero.
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.
3243
3383
  process.exit(
3244
3384
  result.duplicates.length > 0 ||
3245
3385
  itemLockReportNeedsAttention(lockReport)
@@ -3386,7 +3526,7 @@ export function buildProgram(): Command {
3386
3526
  }
3387
3527
 
3388
3528
  // Exit non-zero on a REFUSAL (quiescence gate) or a non-green leak scan
3389
- // (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
3390
3530
  // dry-run or a green migration exits 0.
3391
3531
  if (result.refused || result.leaks.length > 0) {
3392
3532
  process.exit(1);
@@ -3498,7 +3638,7 @@ export function buildProgram(): Command {
3498
3638
  .command('requeue <slug>')
3499
3639
  .helpGroup(HEADLINE_GROUP)
3500
3640
  .description(
3501
- '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).',
3502
3642
  )
3503
3643
  .option('-c, --config <path>', 'config file path', defaultConfigPath())
3504
3644
  .option(
@@ -3509,9 +3649,14 @@ export function buildProgram(): Command {
3509
3649
  '--arbiter <remote>',
3510
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.',
3511
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)')
3512
3657
  .option(
3513
3658
  '--reset',
3514
- '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.',
3515
3660
  )
3516
3661
  .option(
3517
3662
  '-m, --message <note>',
@@ -3533,6 +3678,14 @@ export function buildProgram(): Command {
3533
3678
  // call site, rather than relying on the seam's silent `?? process.env`
3534
3679
  // default by omission (the implicit fallback that made `requeue`'s human
3535
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
+ }
3536
3689
  const result = await ledgerWrite.applyReturnToBacklogTransition({
3537
3690
  cwd,
3538
3691
  slug,
@@ -3541,6 +3694,7 @@ export function buildProgram(): Command {
3541
3694
  // `--cwd` is purely the ORIGIN SOURCE the remote is resolved from.
3542
3695
  arbiter: flags.arbiter ?? 'origin',
3543
3696
  reset: flags.reset,
3697
+ reconcile,
3544
3698
  message: flags.message,
3545
3699
  env: process.env,
3546
3700
  note: (message) => console.error(`>> ${message}`),
@@ -3551,7 +3705,9 @@ export function buildProgram(): Command {
3551
3705
  }
3552
3706
  const how = result.deletedRemoteBranch
3553
3707
  ? ` (--reset: deleted the remote ${workBranchRef('task', slug)} branch; next claim starts fresh)`
3554
- : ' (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)';
3555
3711
  console.log(`Requeued '${slug}' to backlog for re-claiming.${how}`);
3556
3712
  });
3557
3713
 
@@ -3559,7 +3715,7 @@ export function buildProgram(): Command {
3559
3715
  // `pre-backlog-staging-folder-and-promote-step-a` /
3560
3716
  // `pre-prd-staging-pool-split-and-untrusted-prd-placement`): the HUMAN/runner-
3561
3717
  // owned verb that moves a STAGED item into its agent-eligible POOL — a task
3562
- // `work/pre-backlog/<slug>.md → work/backlog/<slug>.md`, a spec
3718
+ // `work/tasks/backlog/<slug>.md → work/tasks/ready/<slug>.md`, a spec
3563
3719
  // `work/specs/proposed/<slug>.md → work/specs/ready/<slug>.md` — as a tree-less CAS on the
3564
3720
  // arbiter, the SAME trust model + mechanism as `requeue`. The agent emits STAGED;
3565
3721
  // only this verb (a human, or the runner) admits it to the pool. With NO argument
@@ -3569,7 +3725,7 @@ export function buildProgram(): Command {
3569
3725
  .command('promote [item]')
3570
3726
  .helpGroup(HEADLINE_GROUP)
3571
3727
  .description(
3572
- `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 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 pre-backlog/ + the specs in 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.`,
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.`,
3573
3729
  )
3574
3730
  .option('-c, --config <path>', 'config file path', defaultConfigPath())
3575
3731
  .option(
@@ -3597,7 +3753,7 @@ export function buildProgram(): Command {
3597
3753
  }
3598
3754
  if (listed.items.length === 0) {
3599
3755
  console.log(
3600
- `Nothing staged to promote on ${arbiter}/main (work/pre-backlog/ and ${workFolderPrefix('specs-proposed')} are empty).`,
3756
+ `Nothing staged to promote on ${arbiter}/main (${workFolderPrefix('tasks-backlog')} and ${workFolderPrefix('specs-proposed')} are empty).`,
3601
3757
  );
3602
3758
  return;
3603
3759
  }
@@ -3610,7 +3766,7 @@ export function buildProgram(): Command {
3610
3766
 
3611
3767
  // AN ITEM → promote it. `task:`/`spec:` are explicit; a bare slug defaults to
3612
3768
  // a task (mirrors `requeue`). An `obs:`/`observation:` prefix is rejected
3613
- // (observations have no pool). A legacy `prd:` prefix is a DEAD namespace token
3769
+ // (observations have no pool). A legacy ''prd:'' prefix is a DEAD namespace token
3614
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') {
@@ -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>` / `spec:<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
 
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
+
3709
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
@@ -3723,7 +3967,6 @@ export function buildProgram(): Command {
3723
3967
  .description(
3724
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>',
@@ -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,
@@ -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