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/advance.ts CHANGED
@@ -66,13 +66,19 @@ import {
66
66
  type MergeActionHandler,
67
67
  type MergeActionResult,
68
68
  } from './apply-merge-action.js';
69
+ import {
70
+ detectAnsweredStuckAction,
71
+ performStuckAction,
72
+ type StuckActionHandler,
73
+ type StuckActionResult,
74
+ } from './apply-stuck-action.js';
69
75
  import type {VerifyConfig} from './verify.js';
70
76
  import type {NewQuestion} from './sidecar.js';
71
77
 
72
78
  /**
73
- * The **`advance` verb SKELETON** (prd `advance-loop`, task
79
+ * The **`advance` verb SKELETON** (spec `advance-loop`, task
74
80
  * `advance-verb-resolver`, US #1/5/6/18). `advance` is the SIBLING top-level verb
75
- * (NOT a `do` subcommand — `do` subcommands are REJECTED in the prd) that drives
81
+ * (NOT a `do` subcommand — `do` subcommands are REJECTED in the spec) that drives
76
82
  * a `work/` item ONE lifecycle rung toward "ready/built", reusing the SAME shared
77
83
  * `prefix:arg` resolver `do` uses (extended with the `obs:` namespace, see
78
84
  * {@link resolveAdvanceArg}).
@@ -96,7 +102,7 @@ import type {NewQuestion} from './sidecar.js';
96
102
  * (`advance-rung-surface`), `apply` (`advance-rung-apply`), and
97
103
  * `triage-observation` (`advance-rung-triage`) dispatch through the clearly-named
98
104
  * executor SEAM ({@link RungExecutor}); the build/task rungs ORCHESTRATE
99
- * `do`/`do prd:`. What this verb does NOT do (LATER tasks):
105
+ * `do`/`do spec:`. What this verb does NOT do (LATER tasks):
100
106
  * - The two **DRIVERS** (one-shot sequential / loop) + `-n` + the gate-FAMILY
101
107
  * WIRING that resolves `autoBuild`/`autoTask`/`observationTriage` and threads
102
108
  * them into the build/task gate composition — task `advance-drivers-and-gates`.
@@ -131,11 +137,34 @@ export type AdvanceOutcome =
131
137
  | 'advanced'
132
138
  | 'no-op'
133
139
  | 'vanished'
140
+ /**
141
+ * The triage/apply promote leg found the task it would mint ALREADY EXISTS on
142
+ * the arbiter AND was PROVABLY minted from THIS observation in a prior run (its
143
+ * `promotedFrom:` back-reference matches) — an idempotency fact, so the source was
144
+ * already triaged. `exitCode: 0` (the matrix tolerates it, so it does NOT red CI),
145
+ * DISTINGUISHABLE from `vanished` (item file gone) and `no-op` (calm classify) so
146
+ * reviewers can grep it. The LOUD `lost` (exit 2) is reserved for a genuine
147
+ * concurrent-create race with an UNRELATED same-path item, where a retry helps.
148
+ * (task `observation-triage-already-triaged-benign-skip`; sibling of the
149
+ * stale-snapshot / held-lock CI-noise fixes.)
150
+ */
151
+ | 'already-triaged'
134
152
  | 'usage-error'
135
153
  | 'lost'
136
154
  | 'contended'
137
155
  | 'not-implemented'
138
- | 'invariant-violation';
156
+ | 'invariant-violation'
157
+ /**
158
+ * The answered-merge dispatcher REFUSED the land on the rebased tip (RED
159
+ * re-verify, rebase conflict, or a pre-checkout failure). Distinct from
160
+ * `usage-error` (which is reserved for genuine caller-usage errors — e.g.
161
+ * the workspacesDir-unset guard below). `performIntegration` has already
162
+ * routed the item to needs-attention via its shared seam, so `main` never
163
+ * received a failing tree; the sidecar is LEFT IN PLACE so the open answer
164
+ * stays surfaced for a human follow-up. `exitCode: 1`.
165
+ * (task `merge-action-nits-followup` nit 2.)
166
+ */
167
+ | 'merge-refused';
139
168
 
140
169
  /** Maps onto the claim-CAS exit codes (identical semantics). */
141
170
  export type AdvanceExitCode = 0 | 1 | 2 | 3;
@@ -144,7 +173,7 @@ export type AdvanceExitCode = 0 | 1 | 2 | 3;
144
173
  * The injectable rung-executor SEAM — WHAT happens once the tick has classified a
145
174
  * rung AND won the `advancing` lock. It is the boundary between the skeleton (this
146
175
  * task) and the rung bodies (later tasks): the surface/apply/triage rungs are
147
- * filled by their own tasks; the build/task rungs ORCHESTRATE `do`/`do prd:`.
176
+ * filled by their own tasks; the build/task rungs ORCHESTRATE `do`/`do spec:`.
148
177
  *
149
178
  * Production wires {@link defaultRungExecutor}; tests inject a spy to assert the
150
179
  * classify→lock→dispatch ORDER (and that a CAS loser never reaches the executor).
@@ -164,15 +193,15 @@ export interface RungExecutor {
164
193
 
165
194
  /** What a rung executor is handed: the resolved identity + the run context. */
166
195
  export interface RungExecInput {
167
- /** The canonical namespaced identity (`task:<slug>` / `prd:<slug>` / `observation:<slug>`). */
196
+ /** The canonical namespaced identity (`task:<slug>` / `spec:<slug>` / `observation:<slug>`). */
168
197
  item: string;
169
- /** The resolved namespace (`task` / `prd` / `observation`). */
198
+ /** The resolved namespace (`task` / `spec` / `observation`). */
170
199
  namespace: SlugNamespace;
171
200
  /** The bare slug. */
172
201
  slug: string;
173
202
  /** The classification that selected this rung (the two signals are visible). */
174
203
  classification: TickClassification;
175
- /** The tick's run context (cwd, arbiter, …) — threaded to `do`/`do prd:`. */
204
+ /** The tick's run context (cwd, arbiter, …) — threaded to `do`/`do spec:`. */
176
205
  context: AdvanceContext;
177
206
  }
178
207
 
@@ -250,14 +279,14 @@ export interface AdvanceContext {
250
279
  * The AGENTIC apply DECISION seam (task
251
280
  * `agentic-apply-retire-disposition-vocabulary`): the fresh-context decision
252
281
  * agent the apply rung runs on a fully-answered OBSERVATION to choose what to DO
253
- * with the signal (`mint-task | mint-prd | mint-adr | delete-source |
254
- * ask-follow-up`),
282
+ * with the signal (`mint-task | mint-spec | mint-adr | dispose-source |
283
+ * resolve-no-mint | ask-follow-up`),
255
284
  * grounded in the source's full context. It is the injected
256
285
  * {@link ApplyDecider} the shared `decide(input, allowedOutcomes)` engine runs;
257
286
  * tests inject a CANNED verdict (no model). `undefined` ⇒ the apply rung defaults
258
287
  * to {@link harnessApplyDecider} (a NullHarness, no real model) so the seam is
259
288
  * never a crash — but the CLI threads the real harness-backed decider. The
260
- * verdict's type SELECTION (task vs prd vs adr) replaces the retired `promote-*`
289
+ * verdict's type SELECTION (task vs spec vs adr) replaces the retired `promote-*`
261
290
  * disposition token; `adr` is now WIRED (task `agentic-apply-mint-adr-route`).
262
291
  */
263
292
  applyDecide?: ApplyDecider;
@@ -322,10 +351,14 @@ export interface AdvanceContext {
322
351
  * The execution working area (`workspacesDir`, default `~/.dorfl`) the
323
352
  * answered-merge LAND uses to cut a per-job worktree from the hub mirror
324
353
  * (via `workspace.ts` `createJob`). Unset ⇒ no `workspacesDir` is available
325
- * to the dispatcher, so an answered `kind: merge` entry is REFUSED (clean
326
- * surfacing); the answer stays for a follow-up. The registry-set advance
327
- * driver threads the resolved `workspacesDir` here.
328
- * (prd `land-time-reverify-and-parallel-merge-ceiling`, task
354
+ * to the dispatcher, so an answered `kind: merge` entry is REFUSED as a
355
+ * genuine caller-usage error (outcome `usage-error`, `exitCode: 1`) and the
356
+ * answer stays surfaced for a follow-up — threading `workspacesDir` is the
357
+ * caller's contract, and forgetting it gets this documented clean refusal
358
+ * rather than a mysterious downstream failure (task
359
+ * `merge-action-nits-followup` nit 3). The registry-set advance driver
360
+ * threads the resolved `workspacesDir` here.
361
+ * (spec `land-time-reverify-and-parallel-merge-ceiling`, task
329
362
  * `apply-rung-merge-disposition`)
330
363
  */
331
364
  workspacesDir?: string;
@@ -353,7 +386,7 @@ export interface AdvanceContext {
353
386
  /**
354
387
  * Resolved `strictMergeApproval` boolean (sibling task
355
388
  * `strict-merge-approval-gate`). Default OFF ⇒ honour the prior approval +
356
- * land on a green re-verify (cheap, the PRD-applied OQ6 default). ON ⇒
389
+ * land on a green re-verify (cheap, the SPEC-applied OQ6 default). ON ⇒
357
390
  * re-surface the merge-question when the merge-base moved between the
358
391
  * surfacer's question and this apply (the host-agnostic analogue of
359
392
  * GitHub's "dismiss stale approvals when the base changes").
@@ -370,6 +403,20 @@ export interface AdvanceContext {
370
403
  * WITHOUT spinning up a hub mirror or running real verify.
371
404
  */
372
405
  mergeAction?: MergeActionHandler;
406
+ /**
407
+ * The stuck-action dispatch SEAM (task
408
+ * `apply-resolve-reset-flag-discards-work-branch`, spec
409
+ * `surface-stuck-as-questions-and-retire-stuck-lock-state`): the
410
+ * deterministic answer-driven runner-action handler the apply rung invokes
411
+ * BEFORE the fall-through persist when an answered `kind: 'stuck'` entry
412
+ * (the shape the bounce-surface path stamps) is detected. Production wires
413
+ * {@link performStuckAction} (drives the shared
414
+ * `deleteRemoteWorkBranchIfPresent` primitive on `reset`); tests inject a
415
+ * stub so they assert on the apply-rung's `keep | reset | refused | cancel`
416
+ * routing WITHOUT touching a real arbiter. The deterministic sibling of
417
+ * {@link mergeAction} for the `kind: 'stuck'` axis.
418
+ */
419
+ stuckAction?: StuckActionHandler;
373
420
  /** Sink for human-readable progress notes. */
374
421
  note?: (message: string) => void;
375
422
  }
@@ -377,7 +424,7 @@ export interface AdvanceContext {
377
424
  /** The options one `advance` tick consumes. */
378
425
  export interface AdvanceOptions extends AdvanceContext {
379
426
  /**
380
- * The raw CLI slug argument: bare (= task), `task:<slug>`, `prd:<slug>`, or
427
+ * The raw CLI slug argument: bare (= task), `task:<slug>`, `spec:<slug>`, or
381
428
  * `obs:<slug>` / `observation:<slug>`. Omit/empty ⇒ the bare eligible-SET form,
382
429
  * which needs the driver task (a clear error here — see `## Decisions`).
383
430
  */
@@ -420,7 +467,7 @@ export interface ItemSignals {
420
467
  export interface ReadSignalsInput {
421
468
  /** The repo working-tree root. */
422
469
  repoPath: string;
423
- /** The item type (task / prd / observation). */
470
+ /** The item type (task / spec / observation). */
424
471
  type: SidecarType;
425
472
  /** The bare slug. */
426
473
  slug: string;
@@ -469,14 +516,14 @@ export function readItemSignals(input: ReadSignalsInput): ItemSignals {
469
516
  * The lifecycle folders each item type may rest in (frontmatter source). After
470
517
  * the capstone cut-over (task
471
518
  * `cutover-retire-slicing-advancing-markers-and-trim-folder-sets`) the transient
472
- * `tasking/` folder is GONE — a prd rests in `prds/ready` (source) or
473
- * `prds/tasked` (tasked); while it is being tasked the body STAYS in
474
- * `prds/ready` (the lock no longer moves it), so `tasking/` is never a
519
+ * `tasking/` folder is GONE — a spec rests in `specs/ready` (source) or
520
+ * `specs/tasked` (tasked); while it is being tasked the body STAYS in
521
+ * `specs/ready` (the lock no longer moves it), so `tasking/` is never a
475
522
  * frontmatter source.
476
523
  *
477
524
  * STAGING IS INCLUDED (`tasks-backlog` / `prds-proposed`): with `surfaceStaging`
478
525
  * on (the user-visible default) the lifecycle surface pool enumerates
479
- * `needsAnswers` items resting in STAGING as `task:`/`prd:` legs
526
+ * `needsAnswers` items resting in STAGING as `task:`/`spec:` legs
480
527
  * (`lifecycle-gather.ts`). The rung CLASSIFIER's signal read MUST see those
481
528
  * staged bodies, or `needsAnswers` reads back `undefined`, the classifier
482
529
  * mis-routes the item to the BUILD rung, and claim dies with "not found on
@@ -512,11 +559,11 @@ function readNeedsAnswers(
512
559
 
513
560
  /**
514
561
  * The PRODUCTION rung executor: build/task rungs ORCHESTRATE the existing
515
- * `do`/`do prd:` machinery ({@link performDo}); the `surface`/`apply`/
562
+ * `do`/`do spec:` machinery ({@link performDo}); the `surface`/`apply`/
516
563
  * `triage-observation` rung bodies are filled by their own tasks
517
564
  * ({@link surfaceRung} / {@link applyRung} / {@link triageRung}). It NEVER
518
565
  * re-implements the build/task path — it hands the resolved arg to `performDo`,
519
- * which spans both namespaces (the task path is the `do prd:` rung the prd's
566
+ * which spans both namespaces (the task path is the `do spec:` rung the spec's
520
567
  * 2026-06-09 UPDATE confirms routes through `performIntegration`).
521
568
  */
522
569
  export const defaultRungExecutor: RungExecutor = {
@@ -602,7 +649,10 @@ function mapDoOutcome(result: DoResult): AdvanceOutcome {
602
649
  * answered sidecar back to not-all-answered (the persist owns that). An EMPTY
603
650
  * emit (the skill's honest "no open judgement") writes nothing and reports it.
604
651
  */
605
- async function surfaceRung(input: RungExecInput): Promise<RungExecResult> {
652
+ async function surfaceRung(
653
+ input: RungExecInput,
654
+ surfaceOpts: {baseQuestions?: NewQuestion[]} = {},
655
+ ): Promise<RungExecResult> {
606
656
  const {item, context} = input;
607
657
  const note = context.note ?? (() => {});
608
658
  const cwd = context.cwd;
@@ -615,50 +665,84 @@ async function surfaceRung(input: RungExecInput): Promise<RungExecResult> {
615
665
  return vanishedSkip({rung: 'surface', item});
616
666
  }
617
667
 
668
+ // DETERMINISTIC BASE QUESTIONS (the triage rung's "always ask" contract): the
669
+ // caller may pass engine-built questions that MUST be surfaced regardless of the
670
+ // agent. They are added ONLY on the FIRST pass (no sidecar yet) — on a re-surface
671
+ // the sidecar already carries them, and `appendQuestions` does NOT dedup, so
672
+ // re-adding would duplicate. When base questions are present the agent is
673
+ // ADDITIVE ONLY and its flake/empty is NON-FATAL (the base question still lands).
674
+ const baseQuestions = surfaceOpts.baseQuestions ?? [];
675
+ const hasBase = baseQuestions.length > 0;
676
+ const sidecarExists = existsSync(join(cwd, sidecarPathFor(item)));
677
+ const baseToAdd = hasBase && !sidecarExists ? baseQuestions : [];
678
+
618
679
  // 1. SPAWN the fresh-context `surface-questions` agent (the skill JUDGES). The
619
680
  // expensive model work is POST-lock (the lock is held by `performAdvance`).
681
+ //
682
+ // SHORT-CIRCUIT (primary/load-bearing half of task
683
+ // `surface-short-circuit-already-triaged-observations-and-harden-skill-empty-emit`):
684
+ // an OBSERVATION with provably no open judgement (frontmatter `needsAnswers` NOT
685
+ // true, no non-empty `## Open questions` section, no pending sidecar) — the
686
+ // typical shape of a decision-record / already-triaged note — deterministically
687
+ // yields `{questions: []}` WITHOUT round-tripping the flaky surface-questions
688
+ // agent (source observation
689
+ // `surface-questions-agent-still-emits-no-parseable-questions-on-decision-record-obs-2026-07-10`).
690
+ // The loud-error contract from
691
+ // `advance-surface-limbo-observation-loudly-instead-of-silent-no-op` is
692
+ // PRESERVED for the observations that DO reach the agent — this only spares the
693
+ // ones that provably have nothing to ask. Conservative: fires only on
694
+ // observations; tasks/specs still always ask.
620
695
  const gate = context.surfaceGate ?? harnessSurfaceGate();
621
696
  let emit;
622
- try {
623
- emit = await gate({
624
- item,
625
- cwd,
626
- surfaceModel: context.surfaceModel,
627
- });
628
- } catch (err) {
629
- const detail = err instanceof Error ? err.message : String(err);
630
- return {
631
- exitCode: 1,
632
- outcome: 'usage-error',
633
- message: `surface ${item}: the surface-questions agent produced no usable emit (${detail}).`,
634
- };
697
+ if (isNothingToSurfaceObservation(cwd, input, itemPath, sidecarExists)) {
698
+ note(
699
+ `surface ${item}: auto-triaged (no open questions, no sidecar) — skipped agent.`,
700
+ );
701
+ emit = {questions: []};
702
+ } else {
703
+ try {
704
+ emit = await gate({
705
+ item,
706
+ cwd,
707
+ surfaceModel: context.surfaceModel,
708
+ });
709
+ } catch (err) {
710
+ const detail = err instanceof Error ? err.message : String(err);
711
+ if (!hasBase) {
712
+ return {
713
+ exitCode: 1,
714
+ outcome: 'usage-error',
715
+ message: `surface ${item}: the surface-questions agent produced no usable emit (${detail}).`,
716
+ };
717
+ }
718
+ // The agent flaked but we have a deterministic base question to surface: the
719
+ // flake is NON-FATAL. Treat the agent's extras as empty and carry on (the
720
+ // base triage question still lands, so the human can triage via the sidecar).
721
+ note(
722
+ `surface ${item}: the surface-questions agent produced no usable emit ` +
723
+ `(${detail}); surfacing the deterministic question(s) only.`,
724
+ );
725
+ emit = {questions: []};
726
+ }
635
727
  }
636
728
 
637
729
  // 2. The ENGINE persists (the skill wrote nothing): append-or-create the sidecar
638
730
  // + set `needsAnswers:true` in ONE commit (CAS-atomic under the held lock).
731
+ // Base questions FIRST (q1…), then the agent's additive extras.
639
732
  const persist = context.surfacePersist ?? persistSurfacedQuestions;
640
733
  const result = persist({
641
734
  cwd,
642
735
  item,
643
736
  itemPath,
644
- questions: toNewQuestions(emit),
737
+ questions: [...baseToAdd, ...toNewQuestions(emit)],
645
738
  note,
646
739
  });
647
740
  if (result.outcome === 'nothing') {
648
- // LIMBO DETECTION (task `advance-surface-limbo-observation-loudly-instead-of-
649
- // silent-no-op`): an untriaged observation with NO sidecar whose surfacer had
650
- // nothing to ask is a TRAP the human's triage answer may have been recorded
651
- // in an in-BODY "Applied answers" block, but the engine reads triage-vs-
652
- // settled only from the `triaged:` frontmatter marker (`ledger-read.ts`) and
653
- // the promote path only from an answered `disposition: promote` SIDECAR
654
- // (`triage-persist.ts`). Without either, the item is untriaged (re-enumerated
655
- // every tick), un-surfaceable (nothing to ask), un-promotable (no sidecar) —
656
- // a silent exit-0 no-op forever. Surface it LOUDLY instead.
657
- const limbo = detectObservationLimbo(input);
658
- if (limbo !== undefined) {
659
- note(limbo);
660
- return {exitCode: 1, outcome: 'usage-error', message: limbo};
661
- }
741
+ // The agent had nothing to ask AND there was no base question to add (a
742
+ // task/spec surface, or a re-surface whose base question is already present).
743
+ // This is the calm "no open judgement" no-op no sidecar written. (There is
744
+ // no "limbo" any more: the triage rung ALWAYS passes a base question on the
745
+ // first pass, so an untriaged observation can never fall here empty-handed.)
662
746
  return {
663
747
  exitCode: 0,
664
748
  outcome: 'no-op',
@@ -675,40 +759,113 @@ async function surfaceRung(input: RungExecInput): Promise<RungExecResult> {
675
759
  }
676
760
 
677
761
  /**
678
- * Detect the OBSERVATION LIMBO shape (task `advance-surface-limbo-observation-
679
- * loudly-instead-of-silent-no-op`): an observation whose triage answer was
680
- * (mis-)authored in an in-body "Applied answers" block instead of the sidecar/
681
- * frontmatter channels the engine reads. All four conditions must hold:
762
+ * Would this OBSERVATION provably surface `{questions: []}` — i.e. is there no
763
+ * open judgement anywhere on it? Cheap read-only predicate the surface rung uses
764
+ * to skip the flaky agent round-trip for already-triaged / decision-record notes
765
+ * (task
766
+ * `surface-short-circuit-already-triaged-observations-and-harden-skill-empty-emit`;
767
+ * source observation
768
+ * `surface-questions-agent-still-emits-no-parseable-questions-on-decision-record-obs-2026-07-10`).
682
769
  *
683
- * 1. the item is an OBSERVATION;
684
- * 2. its frontmatter has NO `triaged:` marker (untriaged per `ledger-read.ts`);
685
- * 3. there is NO active question sidecar at `work/questions/observation-<slug>.md`;
686
- * 4. the surfacer just returned empty (the caller {@link surfaceRung} — only
687
- * calls this on the persist's `nothing` branch).
770
+ * Fires ONLY when ALL of these hold (conservative — err on the side of STILL
771
+ * calling the agent if uncertain):
772
+ * - the item's namespace is `observation` (tasks/specs are out of scope here —
773
+ * they carry no engine-owned base question and a false-positive there would
774
+ * silently drop a real question the author asked for);
775
+ * - the item body's frontmatter does NOT set `needsAnswers: true` (an author
776
+ * who set the flag is explicitly asking for the agent's pass);
777
+ * - the body carries no non-empty `## Open questions` section (empty /
778
+ * whitespace-only sections don't count as judgement);
779
+ * - there is no pending open-question sidecar for the item (same signal the
780
+ * classifier's invariant-1 read uses — the sidecar path is identity-derived).
688
781
  *
689
- * Returns the loud diagnostic to emit (naming BOTH valid channels), or `undefined`
690
- * when the shape does not match (the surfacer's empty is then the normal calm no-op).
691
- * The engine does NOT (and will not) honour in-body disposition prose — one channel
692
- * (the sidecar + `triaged:` frontmatter) keeps the loop honest.
782
+ * The decision-record shape (a `Decision (…)` line and/or `## Alternatives
783
+ * considered` section) is a HINT, not required the four conditions above are
784
+ * load-bearing on their own.
693
785
  */
694
- function detectObservationLimbo(input: RungExecInput): string | undefined {
695
- if (input.namespace !== 'observation') return undefined;
696
- const {item, slug, context} = input;
697
- const cwd = context.cwd;
698
- const itemRel = findItemPath(cwd, input.namespace, slug);
699
- if (itemRel === undefined) return undefined;
700
- const fm = parseFrontmatter(readFileSync(join(cwd, itemRel), 'utf8'));
701
- if (fm.triaged !== undefined && fm.triaged !== '') return undefined;
702
- const sidecarRel = sidecarPathFor(item);
703
- if (existsSync(join(cwd, sidecarRel))) return undefined;
704
- return (
705
- `observation \`${slug}\` is in a limbo: no \`triaged:\` frontmatter marker ` +
706
- `AND no answered question sidecar at \`${sidecarRel}\`, but the surfacer has ` +
707
- `nothing to ask. If a human triage decision (promote-slice / keep / duplicate) ` +
708
- `has been recorded in the observation BODY, that channel is INVISIBLE to the ` +
709
- `runner author the sidecar, or set \`triaged:\` in frontmatter. The engine ` +
710
- `does not (and will not) honour in-body disposition prose.`
711
- );
786
+ function isNothingToSurfaceObservation(
787
+ cwd: string,
788
+ input: RungExecInput,
789
+ itemPath: string,
790
+ sidecarExists: boolean,
791
+ ): boolean {
792
+ if (input.namespace !== 'observation') {
793
+ return false;
794
+ }
795
+ if (sidecarExists) {
796
+ return false;
797
+ }
798
+ let content: string;
799
+ try {
800
+ content = readFileSync(join(cwd, itemPath), 'utf8');
801
+ } catch {
802
+ return false;
803
+ }
804
+ const fm = parseFrontmatter(content);
805
+ if (fm.needsAnswers === true) {
806
+ return false;
807
+ }
808
+ if (hasNonEmptyOpenQuestionsSection(content)) {
809
+ return false;
810
+ }
811
+ return true;
812
+ }
813
+
814
+ /**
815
+ * Does the body carry a non-empty `## Open questions` section? Tolerates the
816
+ * `## Open questions to NOT guess` variant the capture-signal skill writes (same
817
+ * pattern the triage split uses). Whitespace-only / "none" markers count as
818
+ * empty.
819
+ */
820
+ function hasNonEmptyOpenQuestionsSection(content: string): boolean {
821
+ const lines = content.replace(/\r\n/g, '\n').split('\n');
822
+ const startIdx = lines.findIndex((l) => /^##\s+Open questions\b/i.test(l));
823
+ if (startIdx === -1) {
824
+ return false;
825
+ }
826
+ let endIdx = lines.length;
827
+ for (let i = startIdx + 1; i < lines.length; i++) {
828
+ if (/^##\s+/.test(lines[i])) {
829
+ endIdx = i;
830
+ break;
831
+ }
832
+ }
833
+ const body = lines
834
+ .slice(startIdx + 1, endIdx)
835
+ .join('\n')
836
+ .trim();
837
+ if (body === '') {
838
+ return false;
839
+ }
840
+ // A single "none" / "n/a" marker line — treat as empty (author signalled "no
841
+ // open judgement" explicitly).
842
+ if (/^(?:none|n\/a|-|_+)\.?$/i.test(body)) {
843
+ return false;
844
+ }
845
+ return true;
846
+ }
847
+
848
+ /**
849
+ * The DETERMINISTIC triage question the triage rung ALWAYS surfaces for an
850
+ * untriaged observation (the "no limbo, ever" contract). It is engine-built (NOT
851
+ * LLM output), so it can never be zeroed out or flake: every untriaged observation
852
+ * gets exactly this question, and the human answers it via the sidecar — a record,
853
+ * a rationale note, and a fresh-bug signal are all treated identically (the human
854
+ * decides the disposition). The `surface-questions` agent runs ADDITIVELY on top,
855
+ * adding any extra pointed questions it extracts from the body.
856
+ */
857
+ export function buildTriageBaseQuestion(): NewQuestion {
858
+ return {
859
+ question:
860
+ 'What should become of this observation? Reply with a disposition and a ' +
861
+ 'reason: resolve (settle it, keep the note on record — say why), promote ' +
862
+ '(mint a task / spec / adr — say which and why), delete (redundant or ' +
863
+ 'obsolete — say why), or duplicate (maps onto an existing item — name it).',
864
+ context:
865
+ 'The engine records your disposition from the answer (no token needed); an ' +
866
+ 'answered promote mints the artifact, resolve keeps the note settled, ' +
867
+ 'delete/duplicate discharge it.',
868
+ };
712
869
  }
713
870
 
714
871
  /**
@@ -745,6 +902,28 @@ async function triageRung(input: RungExecInput): Promise<RungExecResult> {
745
902
  const note = context.note ?? (() => {});
746
903
  const cwd = context.cwd;
747
904
 
905
+ // SETTLED GUARD: a `triaged:` frontmatter marker means a human already
906
+ // dispositioned this observation, so it DROPS OUT of the triage pool. The pool
907
+ // enumeration already excludes it, but an EXPLICIT `obs:<slug>` bypasses the pool
908
+ // gate and still classifies as `triage-observation` — so re-check the marker here
909
+ // and no-op rather than surfacing a FRESH triage question on an already-settled
910
+ // note. (Replaces the old `detectObservationLimbo` special-case: there is no
911
+ // limbo any more — an UNtriaged observation always surfaces the deterministic
912
+ // question below; a SETTLED one is a calm no-op here.)
913
+ {
914
+ const itemRel = findItemPath(cwd, input.namespace, input.slug);
915
+ if (itemRel !== undefined) {
916
+ const fm = parseFrontmatter(readFileSync(join(cwd, itemRel), 'utf8'));
917
+ if (fm.triaged !== undefined && fm.triaged !== '') {
918
+ return {
919
+ exitCode: 0,
920
+ outcome: 'no-op',
921
+ message: `triage ${item}: already triaged (triaged:${fm.triaged}) — nothing to do.`,
922
+ };
923
+ }
924
+ }
925
+ }
926
+
748
927
  // The CONSERVATIVE auto-disposition EXCEPTION — ONLY under `observationTriage:
749
928
  // 'auto'`. Under `'ask'`/`'off'`/unset (including `off` + an EXPLICIT obs:<slug>
750
929
  // that bypassed the selection gate), EVERY untriaged observation surfaces the
@@ -795,7 +974,12 @@ async function triageRung(input: RungExecInput): Promise<RungExecResult> {
795
974
  // skill emits the triage question — NO disposition token any more, task
796
975
  // `agentic-apply-retire-disposition-vocabulary`); the AGENTIC apply decision
797
976
  // reads the human's answer + source and decides what to DO when it is answered.
798
- return surfaceRung(input);
977
+ //
978
+ // ALWAYS-ASK (the "no limbo" contract): pass the DETERMINISTIC triage question as
979
+ // the base question, so an untriaged observation ALWAYS surfaces it (q1) even if
980
+ // the `surface-questions` agent emits empty or flakes. The agent is ADDITIVE:
981
+ // any pointed questions it extracts from the body are appended after q1.
982
+ return surfaceRung(input, {baseQuestions: [buildTriageBaseQuestion()]});
799
983
  }
800
984
 
801
985
  /**
@@ -807,8 +991,9 @@ async function triageRung(input: RungExecInput): Promise<RungExecResult> {
807
991
  * apply rung is now AGENT-DRIVEN: it runs the shared `decide(input, allowedOutcomes)`
808
992
  * engine ({@link decide}) over `(the answered question(s) + the SOURCE item + its
809
993
  * type/context)` via the injected {@link ApplyDecider}, allowing the set
810
- * `{task | prd | adr | delete | ask}` (= `{mint-task | mint-prd | mint-adr |
811
- * delete-source | ask-follow-up}`; `adr` is now WIRED by task
994
+ * `{task | spec | adr | dispose | resolve | ask}` (= `{mint-task | mint-spec |
995
+ * mint-adr | dispose-source | resolve-no-mint | ask-follow-up}`; `adr` is now WIRED
996
+ * by task
812
997
  * `agentic-apply-mint-adr-route`, which added the {@link mintAdr} route). The
813
998
  * verdict ROUTES:
814
999
  * - `ask` → the EXISTING append/re-pause loop ({@link applyAnsweredQuestions}
@@ -817,15 +1002,26 @@ async function triageRung(input: RungExecInput): Promise<RungExecResult> {
817
1002
  * - `adr` → {@link mintAdr} (mint a SELF-CONTAINED ADR into `docs/adr/` + `git
818
1003
  * rm` the source + sidecar in the SAME atomic commit; the SIBLING route for the
819
1004
  * off-board target);
820
- * - `task` / `prd` → {@link promoteObservation} (mint a SELF-CONTAINED artifact +
1005
+ * - `task` / `spec` → {@link promoteObservation} (mint a SELF-CONTAINED artifact +
821
1006
  * `git rm` the source + sidecar in the SAME atomic commit); the artifact type
822
1007
  * comes from the agent's VERDICT, NOT a human `promote-*` field;
823
- * - `delete` → {@link applyAnsweredQuestions} discharge-by-deletion (`git rm`
824
- * source + sidecar in one revertible commit, the reason in the commit message).
1008
+ * - `resolve` → {@link applyAnsweredQuestions} resolve-fully (harvest answers
1009
+ * into the body, clear `needsAnswers`, delete the sidecar; the note is
1010
+ * RETAINED — the sibling of `dispose` that mints nothing but keeps the note);
1011
+ * - `dispose` → {@link applyAnsweredQuestions} regime-polymorphic disposal
1012
+ * (task `apply-disposition-delete-to-dispose-regime-polymorphic`, spec
1013
+ * `surface-stuck-as-questions-and-retire-stuck-lock-state` decision #5): an
1014
+ * OBSERVATION is `git rm`-ed in one revertible commit (notes leave by
1015
+ * deletion; reason in the message); a TASK is `git mv`-ed to
1016
+ * `tasks/cancelled/` (RETAINED, `reason:` written into the moved body); a
1017
+ * SPEC is `git mv`-ed to `specs/dropped/` (RETAINED). Making `dispose`
1018
+ * polymorphic (rather than a literal `delete`) makes "a task cannot be
1019
+ * hard-deleted by the apply rung, only disposed to its terminal" true BY
1020
+ * CONSTRUCTION.
825
1021
  *
826
- * For a TASK/PRD (answering its OWN open questions) or a caller-supplied follow-up
1022
+ * For a TASK/SPEC (answering its OWN open questions) or a caller-supplied follow-up
827
1023
  * batch, it delegates straight to {@link applyAnsweredQuestions} (resolve fully /
828
- * re-pause) — the lifecycle path is untouched (a task/prd is dropped by its own
1024
+ * re-pause) — the lifecycle path is untouched (a task/spec is dropped by its own
829
1025
  * lifecycle, not by a question answer).
830
1026
  *
831
1027
  * Under the `advancing` CAS lock (held by {@link performAdvance} BEFORE this runs
@@ -844,7 +1040,7 @@ async function applyRung(input: RungExecInput): Promise<RungExecResult> {
844
1040
  return vanishedSkip({rung: 'apply', item});
845
1041
  }
846
1042
 
847
- // RUNNER-ACTION KIND-CHECK (task `apply-rung-merge-disposition`, prd
1043
+ // RUNNER-ACTION KIND-CHECK (task `apply-rung-merge-disposition`, spec
848
1044
  // `land-time-reverify-and-parallel-merge-ceiling`): an answered `kind: merge`
849
1045
  // sidecar entry is a DETERMINISTIC land action, NOT a content decision. It
850
1046
  // dispatches HERE (a sibling of the agentic `decide()`), keyed off the
@@ -859,10 +1055,30 @@ async function applyRung(input: RungExecInput): Promise<RungExecResult> {
859
1055
  return mergeRoute;
860
1056
  }
861
1057
 
1058
+ // RUNNER-ACTION KIND-CHECK for `kind: 'stuck'` (task
1059
+ // `apply-resolve-reset-flag-discards-work-branch`, spec
1060
+ // `surface-stuck-as-questions-and-retire-stuck-lock-state` decision #6): a
1061
+ // bounced TASK surfaces as a `kind: 'stuck'` question; the human's plain
1062
+ // `keep | reset | cancel` answer drives one of the three deterministic
1063
+ // verbs. The DIRECT SIBLING of `maybeRunMergeAction` above (there is NO
1064
+ // agentic decider on the TASK apply path — `runAgenticDecision` fires only
1065
+ // for observations — so a bounced task's answer MUST be sourced from a
1066
+ // deterministic parse+dispatch, not a widened decider). `keep` and `reset`
1067
+ // fall through to the normal fall-through persist (with the branch
1068
+ // pre-deleted on `reset` via the SHARED
1069
+ // `deleteRemoteWorkBranchIfPresent`); `cancel` dispatches through
1070
+ // `applyAnsweredQuestions`' `dispose` option (task →
1071
+ // `tasks/cancelled/`); `refused` short-circuits with the sidecar left in
1072
+ // place for a re-answer.
1073
+ const stuckRoute = await maybeRunStuckAction(input, itemPath);
1074
+ if (stuckRoute !== undefined) {
1075
+ return stuckRoute;
1076
+ }
1077
+
862
1078
  // AGENTIC APPLY for an answered OBSERVATION (the subsumed triage rung): run the
863
1079
  // shared decision engine over the answer(s) + source, route the verdict. A
864
1080
  // caller-supplied follow-up batch (`applyFollowups`) bypasses the decision and
865
- // re-pauses directly (a test/driver hook). TASK/PRD items resolve in place
1081
+ // re-pauses directly (a test/driver hook). TASK/SPEC items resolve in place
866
1082
  // (their own lifecycle), so they skip the decision and fall through to the
867
1083
  // persist below.
868
1084
  const runAgenticDecision =
@@ -910,7 +1126,7 @@ async function applyRung(input: RungExecInput): Promise<RungExecResult> {
910
1126
  * the merge-question surfacer) through the EXISTING land primitive
911
1127
  * (`performIntegration` with `committedRecovery: true` + `freshWorktreeGate:
912
1128
  * true`) via the EXISTING per-job worktree seam (`workspace.ts` `createJob`).
913
- * The deterministic SIBLING of the agentic `decide()` content-decision (PRD
1129
+ * The deterministic SIBLING of the agentic `decide()` content-decision (SPEC
914
1130
  * `land-time-reverify-and-parallel-merge-ceiling`, task
915
1131
  * `apply-rung-merge-disposition`; Stories #15, #16): a merge-acceptance has no
916
1132
  * judgement content (the human's plain `merge | hold | drop` answer IS the
@@ -919,7 +1135,7 @@ async function applyRung(input: RungExecInput): Promise<RungExecResult> {
919
1135
  *
920
1136
  * Returns `undefined` when there is no answered `kind: merge` entry to dispatch
921
1137
  * (the apply rung then proceeds to the existing path — agentic for
922
- * observations, normal `applyAnsweredQuestions` for task/prd content questions).
1138
+ * observations, normal `applyAnsweredQuestions` for task/spec content questions).
923
1139
  * Returns a {@link RungExecResult} when the dispatcher handled the rung:
924
1140
  *
925
1141
  * - `landed` / `already-integrated` ⇒ the kept commit landed on `main` (or
@@ -995,8 +1211,13 @@ async function maybeRunMergeAction(
995
1211
  // bounce to needs-attention through its own shared seam). SHORT-CIRCUIT:
996
1212
  // leave the sidecar so the open answer stays surfaced — the apply rung
997
1213
  // MUST NOT also resolve it (the next surfacer / human will follow up).
1214
+ // Outcome tag `merge-refused` (task `merge-action-nits-followup` nit 2):
1215
+ // distinct from `usage-error` — which is reserved for genuine caller-usage
1216
+ // errors (e.g. the workspacesDir-unset guard above) — so reviewers can
1217
+ // grep the refusal-on-rebased-tip signal without confusing it with a
1218
+ // misuse. `exitCode: 1` preserved.
998
1219
  note(result.message);
999
- return {exitCode: 1, outcome: 'usage-error', message: result.message};
1220
+ return {exitCode: 1, outcome: 'merge-refused', message: result.message};
1000
1221
  }
1001
1222
 
1002
1223
  if (result.outcome === 'restale') {
@@ -1064,6 +1285,113 @@ async function maybeRunMergeAction(
1064
1285
  }
1065
1286
  }
1066
1287
 
1288
+ /**
1289
+ * Dispatch an answered STUCK-QUESTION (a sidecar entry stamped `kind: 'stuck'`
1290
+ * by the bounce-surface path) through the deterministic
1291
+ * {@link performStuckAction} sibling. The DIRECT SIBLING of
1292
+ * {@link maybeRunMergeAction} for the `kind: 'stuck'` axis (task
1293
+ * `apply-resolve-reset-flag-discards-work-branch`, spec
1294
+ * `surface-stuck-as-questions-and-retire-stuck-lock-state` decision #6):
1295
+ *
1296
+ * - `keep` -> fall through to today's normal `applyAnsweredQuestions`
1297
+ * resolve (branch untouched; continue-from-WIP);
1298
+ * - `reset` -> the SHARED `deleteRemoteWorkBranchIfPresent` primitive
1299
+ * discards the remote `work/task-<slug>` FIRST, then the
1300
+ * normal fall-through persist clears `needsAnswers`. Safely
1301
+ * IDEMPOTENT when no branch exists (an item never built) —
1302
+ * an `already-gone` push is tolerated as a no-op;
1303
+ * - `cancel` -> fall through to the normal persist with the `dispose`
1304
+ * option (`git mv -> tasks/cancelled/`), the answer text
1305
+ * recorded as the human's reason;
1306
+ * - `refused` -> a REAL push-delete failure aborted the discard;
1307
+ * SHORT-CIRCUIT with the sidecar left in place so the human
1308
+ * sees the failure and re-answers (matches the
1309
+ * `requeue --reset` abort-on-failed-delete contract).
1310
+ *
1311
+ * Returns `undefined` when no answered `kind: 'stuck'` entry is present (the
1312
+ * apply rung then proceeds to the existing path — an unrelated content
1313
+ * question falls through to the normal `applyAnsweredQuestions` /
1314
+ * agentic-decider path).
1315
+ */
1316
+ async function maybeRunStuckAction(
1317
+ input: RungExecInput,
1318
+ itemPath: string,
1319
+ ): Promise<RungExecResult | undefined> {
1320
+ const {item, context} = input;
1321
+ const cwd = context.cwd;
1322
+ const note = context.note ?? (() => {});
1323
+
1324
+ const detected = detectAnsweredStuckAction(cwd, item);
1325
+ if (detected === undefined) return undefined;
1326
+
1327
+ const handler = context.stuckAction ?? performStuckAction;
1328
+
1329
+ let result: StuckActionResult;
1330
+ try {
1331
+ result = await handler({
1332
+ action: detected,
1333
+ item,
1334
+ slug: input.slug,
1335
+ cwd,
1336
+ arbiter: context.arbiter ?? DEFAULT_ARBITER,
1337
+ note,
1338
+ });
1339
+ } catch (err) {
1340
+ const detail = err instanceof Error ? err.message : String(err);
1341
+ const message =
1342
+ `apply ${item}: answered stuck-question dispatch raised (${detail}); NOT ` +
1343
+ `clearing needsAnswers; the sidecar stays surfaced.`;
1344
+ note(message);
1345
+ return {exitCode: 1, outcome: 'usage-error', message};
1346
+ }
1347
+
1348
+ if (result.outcome === 'refused') {
1349
+ // The arbiter delete FAILED (not `already-gone`); we MUST NOT clear
1350
+ // needsAnswers, because that would leave the item claimable while still
1351
+ // carrying the WIP branch we meant to discard — the very stale-continue
1352
+ // trap the `requeue --reset` path guards against. Leave the sidecar in
1353
+ // place for a re-answer.
1354
+ note(result.message);
1355
+ return {exitCode: 1, outcome: 'usage-error', message: result.message};
1356
+ }
1357
+
1358
+ note(result.message);
1359
+ const apply = context.applyPersist ?? applyAnsweredQuestions;
1360
+ try {
1361
+ const applyOptions: ApplyAnsweredQuestionsOptions = {
1362
+ cwd,
1363
+ item,
1364
+ itemPath,
1365
+ note,
1366
+ };
1367
+ if (result.outcome === 'cancel') {
1368
+ // The human's answer text is the dispose reason (verbatim), so the
1369
+ // terminal `tasks/cancelled/` body records WHY — the same reason
1370
+ // contract the agentic `dispose` verdict uses.
1371
+ applyOptions.dispose = {reason: detected.entry.answer.trim()};
1372
+ }
1373
+ const applied = apply(applyOptions);
1374
+ const mapped: AdvanceOutcome =
1375
+ applied.outcome === 'repaused'
1376
+ ? 'no-op'
1377
+ : applied.outcome === 'vanished'
1378
+ ? 'vanished'
1379
+ : 'advanced';
1380
+ return {
1381
+ exitCode: 0,
1382
+ outcome: mapped,
1383
+ message: `${result.message} ${applied.message}`,
1384
+ };
1385
+ } catch (err) {
1386
+ const detail = err instanceof Error ? err.message : String(err);
1387
+ return {
1388
+ exitCode: 1,
1389
+ outcome: 'usage-error',
1390
+ message: `apply ${item}: ${detail}`,
1391
+ };
1392
+ }
1393
+ }
1394
+
1067
1395
  /**
1068
1396
  * The cross-tick-window BENIGN SKIP shared by all three rungs that need to
1069
1397
  * resolve an item file (surface / triage / apply). The lifecycle pool enumerated
@@ -1113,21 +1441,30 @@ function findItemPath(
1113
1441
  * The AGENTIC apply DECISION for a fully-answered OBSERVATION (task
1114
1442
  * `agentic-apply-retire-disposition-vocabulary`): run the shared
1115
1443
  * `decide(input, allowedOutcomes)` engine over the answer(s) + source, then ROUTE
1116
- * the verdict. The artifact-type selection (task vs prd) comes from the agent's
1444
+ * the verdict. The artifact-type selection (task vs spec) comes from the agent's
1117
1445
  * VERDICT, NOT a human `promote-*` field (which is retired). Replaces the old
1118
1446
  * `answeredPromoteArtifact` + disposition picker.
1119
1447
  *
1120
1448
  * - `ask` → append the follow-up question(s) + re-pause (the EXISTING loop, via
1121
1449
  * {@link applyAnsweredQuestions}'s `appendQuestions`);
1122
- * - `task` / `prd` → {@link promoteObservation} (mint self-contained + delete
1450
+ * - `task` / `spec` → {@link promoteObservation} (mint self-contained + delete
1123
1451
  * source in the same atomic commit);
1124
1452
  * - `adr` → {@link mintAdr} (mint a self-contained ADR into `docs/adr/` + delete
1125
1453
  * source in the same atomic commit; the SIBLING route for the off-board target,
1126
1454
  * task `agentic-apply-mint-adr-route`);
1127
- * - `delete` → {@link applyAnsweredQuestions}'s discharge-by-deletion (`git rm`
1128
- * source + sidecar in one revertible commit, the reason in the message).
1455
+ * - `resolve` → {@link applyAnsweredQuestions}'s resolve-fully path (harvest the
1456
+ * answers into `## Applied answers`, strip the open-questions block, clear
1457
+ * `needsAnswers`, DELETE the sidecar — the note is RETAINED). The sibling of
1458
+ * `dispose` that KEEPS the note instead of moving/rm-ing it (task
1459
+ * `apply-decide-resolve-verdict-mint-nothing`);
1460
+ * - `dispose` → {@link applyAnsweredQuestions}'s regime-polymorphic disposal
1461
+ * (task `apply-disposition-delete-to-dispose-regime-polymorphic`): an
1462
+ * observation is `git rm`-ed in one revertible commit (reason in the
1463
+ * message); a task is `git mv`-ed to `tasks/cancelled/` (reason written into
1464
+ * the body); a spec is `git mv`-ed to `specs/dropped/`.
1129
1465
  *
1130
- * The allowed set is `{task | prd | adr | delete | ask}`; a verdict outside it is
1466
+ * The allowed set is `{task | spec | adr | dispose | resolve | ask}`; a verdict
1467
+ * outside it is
1131
1468
  * rejected by the engine's allowed-outcome guard ({@link DisallowedOutcomeError})
1132
1469
  * and mapped onto a usage-error — never dispatched.
1133
1470
  */
@@ -1234,11 +1571,13 @@ async function applyAgenticDecision(
1234
1571
  outcome:
1235
1572
  result.outcome === 'promoted'
1236
1573
  ? 'advanced'
1237
- : result.outcome === 'lost'
1238
- ? 'lost'
1239
- : result.outcome === 'contended'
1240
- ? 'contended'
1241
- : 'usage-error',
1574
+ : result.outcome === 'already-triaged'
1575
+ ? 'already-triaged'
1576
+ : result.outcome === 'lost'
1577
+ ? 'lost'
1578
+ : result.outcome === 'contended'
1579
+ ? 'contended'
1580
+ : 'usage-error',
1242
1581
  message: result.message,
1243
1582
  };
1244
1583
  } catch (err) {
@@ -1303,16 +1642,53 @@ async function applyAgenticDecision(
1303
1642
  }
1304
1643
  }
1305
1644
 
1306
- // delete-source → discharge by deletion (DIRECT, no confirm — decision 12). The
1307
- // human's answer is the source of truth; the deletion is a single revertible
1308
- // commit with the reason in the message.
1645
+ if (verdict.outcome === 'resolve') {
1646
+ // resolve-no-mint the EXISTING resolve-fully path (task
1647
+ // `apply-decide-resolve-verdict-mint-nothing`). The answer SETTLES the item
1648
+ // with NOTHING to mint and the note RETAINED: call `applyAnsweredQuestions`
1649
+ // with NEITHER `appendQuestions` (re-pause) NOR `dispose` (drop/terminal), so it
1650
+ // takes the default resolve-fully branch — harvest the answers into `##
1651
+ // Applied answers`, strip the marker-fenced open-questions block, clear
1652
+ // `needsAnswers`, and DELETE the sidecar in ONE atomic commit. Invariant-clean
1653
+ // (`needsAnswers:false` ⟺ no active sidecar). The note file is KEPT (this is
1654
+ // the sibling of `dispose`, which git-rm's it or moves it to a terminal). `resolveReason` is advisory
1655
+ // context only; the durable disposition record is the harvested `## Applied
1656
+ // answers` block the resolve-fully path writes (NOT a separate convention).
1657
+ const apply = context.applyPersist ?? applyAnsweredQuestions;
1658
+ try {
1659
+ const result = apply({cwd, item, itemPath, note});
1660
+ return {
1661
+ exitCode: 0,
1662
+ outcome: result.outcome === 'vanished' ? 'vanished' : 'advanced',
1663
+ message: result.message,
1664
+ };
1665
+ } catch (err) {
1666
+ const detail = err instanceof Error ? err.message : String(err);
1667
+ return {
1668
+ exitCode: 1,
1669
+ outcome: 'usage-error',
1670
+ message: `apply ${item}: ${detail}`,
1671
+ };
1672
+ }
1673
+ }
1674
+
1675
+ // dispose-source → regime-polymorphic disposal (DIRECT, no confirm — decision
1676
+ // 12; task `apply-disposition-delete-to-dispose-regime-polymorphic`, spec
1677
+ // `surface-stuck-as-questions-and-retire-stuck-lock-state` decision #5). The
1678
+ // human's answer is the source of truth; the persist chooses the on-disk
1679
+ // effect by the source's regime: an OBSERVATION is `git rm`-ed in one
1680
+ // revertible commit (reason in the message, git history = archive); a TASK is
1681
+ // `git mv`-ed to `tasks/cancelled/` (RETAINED, `reason:` written into the
1682
+ // moved body); a SPEC is `git mv`-ed to `specs/dropped/` (RETAINED). A task
1683
+ // cannot be hard-deleted by the apply rung — dispose is the only path off the
1684
+ // board.
1309
1685
  const apply = context.applyPersist ?? applyAnsweredQuestions;
1310
1686
  try {
1311
1687
  const result = apply({
1312
1688
  cwd,
1313
1689
  item,
1314
1690
  itemPath,
1315
- discharge: {reason: verdict.deleteReason ?? ''},
1691
+ dispose: {reason: verdict.disposeReason ?? ''},
1316
1692
  note,
1317
1693
  });
1318
1694
  return {
@@ -1353,13 +1729,13 @@ export async function performAdvance(
1353
1729
  const message =
1354
1730
  '`advance` with no item is the eligible-SET form (the one-shot driver). ' +
1355
1731
  'The single-item tick needs a named item: ' +
1356
- '`advance <slug>` / `advance prd:<slug>` / `advance obs:<slug>`.';
1732
+ '`advance <slug>` / `advance spec:<slug>` / `advance obs:<slug>`.';
1357
1733
  note(message);
1358
1734
  return {exitCode: 1, outcome: 'usage-error', message};
1359
1735
  }
1360
1736
 
1361
1737
  // 1. RESOLVE the arg via the SHARED resolver (extended with `obs:`). `advance`
1362
- // spans task / prd / observation; a collision / bad arg is a loud usage error.
1738
+ // spans task / spec / observation; a collision / bad arg is a loud usage error.
1363
1739
  let resolved;
1364
1740
  try {
1365
1741
  resolved = resolveAdvanceArg({
@@ -1419,7 +1795,7 @@ export async function performAdvance(
1419
1795
  // 3. LOCK — take the `advancing` CAS borrow for the classified rung, keyed on
1420
1796
  // the item's `<type>-<slug>` identity. The expensive phase is POST-lock.
1421
1797
  //
1422
- // UNIFIED PER-ITEM LOCK, TREE-LESS RUNGS ONLY (prd
1798
+ // UNIFIED PER-ITEM LOCK, TREE-LESS RUNGS ONLY (spec
1423
1799
  // `ledger-status-per-item-lock-refs` US #1/#3/#18; ADR
1424
1800
  // `ledger-status-on-per-item-lock-refs`). The rung kind is KNOWN here
1425
1801
  // (`classification.kind`, classified pre-lock), so the tree-less-only policy
@@ -1428,7 +1804,7 @@ export async function performAdvance(
1428
1804
  // TREE-LESS rung (`surface`/`apply`/`triage-observation`) the advancing acquire
1429
1805
  // ALSO takes the item's unified lock (`action: advance`) — these rungs have NO
1430
1806
  // inner `do`, so the unified hold is what realises advance∥claim / advance∥task
1431
- // exclusion. For a BUILD-TASK / TASK-PRD rung we do NOT take the unified lock
1807
+ // exclusion. For a BUILD-TASK / TASK-SPEC rung we do NOT take the unified lock
1432
1808
  // at the advance layer: `performAdvance` ORCHESTRATES an inner `performDo` that
1433
1809
  // ITSELF acquires the SAME `task-<slug>`/`prd-<slug>` ref (the create-only CAS
1434
1810
  // with NO re-entrancy/auto-steal, per the ADR), so taking it here too would
@@ -1506,6 +1882,10 @@ export async function performAdvance(
1506
1882
  verify: options.verify,
1507
1883
  strictMergeApproval: options.strictMergeApproval,
1508
1884
  mergeAction: options.mergeAction,
1885
+ // The answered-stuck-question dispatch context (task
1886
+ // `apply-resolve-reset-flag-discards-work-branch`): threaded into the
1887
+ // apply rung's kind-check for a `kind: 'stuck'` runner-action.
1888
+ stuckAction: options.stuckAction,
1509
1889
  note,
1510
1890
  },
1511
1891
  });