dorfl 0.0.0 → 0.1.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 (619) hide show
  1. package/dist/advance-ci-template.d.ts +73 -0
  2. package/dist/advance-ci-template.d.ts.map +1 -0
  3. package/dist/advance-ci-template.js +104 -0
  4. package/dist/advance-ci-template.js.map +1 -0
  5. package/dist/advance-classify.d.ts +132 -0
  6. package/dist/advance-classify.d.ts.map +1 -0
  7. package/dist/advance-classify.js +120 -0
  8. package/dist/advance-classify.js.map +1 -0
  9. package/dist/advance-drivers.d.ts +182 -0
  10. package/dist/advance-drivers.d.ts.map +1 -0
  11. package/dist/advance-drivers.js +231 -0
  12. package/dist/advance-drivers.js.map +1 -0
  13. package/dist/advance-isolated.d.ts +156 -0
  14. package/dist/advance-isolated.d.ts.map +1 -0
  15. package/dist/advance-isolated.js +256 -0
  16. package/dist/advance-isolated.js.map +1 -0
  17. package/dist/advance-lifecycle-template.d.ts +107 -0
  18. package/dist/advance-lifecycle-template.d.ts.map +1 -0
  19. package/dist/advance-lifecycle-template.js +668 -0
  20. package/dist/advance-lifecycle-template.js.map +1 -0
  21. package/dist/advance-loop-driver.d.ts +325 -0
  22. package/dist/advance-loop-driver.d.ts.map +1 -0
  23. package/dist/advance-loop-driver.js +437 -0
  24. package/dist/advance-loop-driver.js.map +1 -0
  25. package/dist/advance-treeless-publish.d.ts +108 -0
  26. package/dist/advance-treeless-publish.d.ts.map +1 -0
  27. package/dist/advance-treeless-publish.js +71 -0
  28. package/dist/advance-treeless-publish.js.map +1 -0
  29. package/dist/advance.d.ts +340 -0
  30. package/dist/advance.d.ts.map +1 -0
  31. package/dist/advance.js +1122 -0
  32. package/dist/advance.js.map +1 -0
  33. package/dist/advancing-lock.d.ts +294 -0
  34. package/dist/advancing-lock.d.ts.map +1 -0
  35. package/dist/advancing-lock.js +594 -0
  36. package/dist/advancing-lock.js.map +1 -0
  37. package/dist/agent-launch.d.ts +79 -0
  38. package/dist/agent-launch.d.ts.map +1 -0
  39. package/dist/agent-launch.js +61 -0
  40. package/dist/agent-launch.js.map +1 -0
  41. package/dist/agent-stop.d.ts +149 -0
  42. package/dist/agent-stop.d.ts.map +1 -0
  43. package/dist/agent-stop.js +307 -0
  44. package/dist/agent-stop.js.map +1 -0
  45. package/dist/apply-decide.d.ts +127 -0
  46. package/dist/apply-decide.d.ts.map +1 -0
  47. package/dist/apply-decide.js +176 -0
  48. package/dist/apply-decide.js.map +1 -0
  49. package/dist/apply-merge-action.d.ts +206 -0
  50. package/dist/apply-merge-action.d.ts.map +1 -0
  51. package/dist/apply-merge-action.js +307 -0
  52. package/dist/apply-merge-action.js.map +1 -0
  53. package/dist/apply-persist.d.ts +174 -0
  54. package/dist/apply-persist.d.ts.map +1 -0
  55. package/dist/apply-persist.js +359 -0
  56. package/dist/apply-persist.js.map +1 -0
  57. package/dist/arbiter.d.ts +120 -0
  58. package/dist/arbiter.d.ts.map +1 -0
  59. package/dist/arbiter.js +255 -0
  60. package/dist/arbiter.js.map +1 -0
  61. package/dist/brand.d.ts +70 -0
  62. package/dist/brand.d.ts.map +1 -0
  63. package/dist/brand.js +84 -0
  64. package/dist/brand.js.map +1 -0
  65. package/dist/buildable-body.d.ts +132 -0
  66. package/dist/buildable-body.d.ts.map +1 -0
  67. package/dist/buildable-body.js +131 -0
  68. package/dist/buildable-body.js.map +1 -0
  69. package/dist/categorise.d.ts +66 -0
  70. package/dist/categorise.d.ts.map +1 -0
  71. package/dist/categorise.js +106 -0
  72. package/dist/categorise.js.map +1 -0
  73. package/dist/claim-cas.d.ts +117 -0
  74. package/dist/claim-cas.d.ts.map +1 -0
  75. package/dist/claim-cas.js +312 -0
  76. package/dist/claim-cas.js.map +1 -0
  77. package/dist/cli-spinner.d.ts +112 -0
  78. package/dist/cli-spinner.d.ts.map +1 -0
  79. package/dist/cli-spinner.js +157 -0
  80. package/dist/cli-spinner.js.map +1 -0
  81. package/dist/cli.d.ts +11 -0
  82. package/dist/cli.d.ts.map +1 -0
  83. package/dist/cli.js +3094 -0
  84. package/dist/cli.js.map +1 -0
  85. package/dist/close-job-template.d.ts +70 -0
  86. package/dist/close-job-template.d.ts.map +1 -0
  87. package/dist/close-job-template.js +180 -0
  88. package/dist/close-job-template.js.map +1 -0
  89. package/dist/close-job.d.ts +95 -0
  90. package/dist/close-job.d.ts.map +1 -0
  91. package/dist/close-job.js +226 -0
  92. package/dist/close-job.js.map +1 -0
  93. package/dist/complete.d.ts +361 -0
  94. package/dist/complete.d.ts.map +1 -0
  95. package/dist/complete.js +885 -0
  96. package/dist/complete.js.map +1 -0
  97. package/dist/concurrency.d.ts +68 -0
  98. package/dist/concurrency.d.ts.map +1 -0
  99. package/dist/concurrency.js +112 -0
  100. package/dist/concurrency.js.map +1 -0
  101. package/dist/config-override.d.ts +76 -0
  102. package/dist/config-override.d.ts.map +1 -0
  103. package/dist/config-override.js +50 -0
  104. package/dist/config-override.js.map +1 -0
  105. package/dist/config.d.ts +668 -0
  106. package/dist/config.d.ts.map +1 -0
  107. package/dist/config.js +241 -0
  108. package/dist/config.js.map +1 -0
  109. package/dist/continue-branch.d.ts +249 -0
  110. package/dist/continue-branch.d.ts.map +1 -0
  111. package/dist/continue-branch.js +389 -0
  112. package/dist/continue-branch.js.map +1 -0
  113. package/dist/cwd-section.d.ts +186 -0
  114. package/dist/cwd-section.d.ts.map +1 -0
  115. package/dist/cwd-section.js +209 -0
  116. package/dist/cwd-section.js.map +1 -0
  117. package/dist/decision-engine.d.ts +170 -0
  118. package/dist/decision-engine.d.ts.map +1 -0
  119. package/dist/decision-engine.js +136 -0
  120. package/dist/decision-engine.js.map +1 -0
  121. package/dist/detect.d.ts +17 -0
  122. package/dist/detect.d.ts.map +1 -0
  123. package/dist/detect.js +118 -0
  124. package/dist/detect.js.map +1 -0
  125. package/dist/do-autopick.d.ts +85 -0
  126. package/dist/do-autopick.d.ts.map +1 -0
  127. package/dist/do-autopick.js +112 -0
  128. package/dist/do-autopick.js.map +1 -0
  129. package/dist/do-config.d.ts +312 -0
  130. package/dist/do-config.d.ts.map +1 -0
  131. package/dist/do-config.js +358 -0
  132. package/dist/do-config.js.map +1 -0
  133. package/dist/do-remote-auto.d.ts +75 -0
  134. package/dist/do-remote-auto.d.ts.map +1 -0
  135. package/dist/do-remote-auto.js +111 -0
  136. package/dist/do-remote-auto.js.map +1 -0
  137. package/dist/do.d.ts +621 -0
  138. package/dist/do.d.ts.map +1 -0
  139. package/dist/do.js +1882 -0
  140. package/dist/do.js.map +1 -0
  141. package/dist/drop-source.d.ts +96 -0
  142. package/dist/drop-source.d.ts.map +1 -0
  143. package/dist/drop-source.js +91 -0
  144. package/dist/drop-source.js.map +1 -0
  145. package/dist/eligibility.d.ts +46 -0
  146. package/dist/eligibility.d.ts.map +1 -0
  147. package/dist/eligibility.js +34 -0
  148. package/dist/eligibility.js.map +1 -0
  149. package/dist/env-config.d.ts +51 -0
  150. package/dist/env-config.d.ts.map +1 -0
  151. package/dist/env-config.js +272 -0
  152. package/dist/env-config.js.map +1 -0
  153. package/dist/failure-cause.d.ts +70 -0
  154. package/dist/failure-cause.d.ts.map +1 -0
  155. package/dist/failure-cause.js +126 -0
  156. package/dist/failure-cause.js.map +1 -0
  157. package/dist/format.d.ts +43 -0
  158. package/dist/format.d.ts.map +1 -0
  159. package/dist/format.js +256 -0
  160. package/dist/format.js.map +1 -0
  161. package/dist/frontmatter.d.ts +215 -0
  162. package/dist/frontmatter.d.ts.map +1 -0
  163. package/dist/frontmatter.js +345 -0
  164. package/dist/frontmatter.js.map +1 -0
  165. package/dist/gate-readiness.d.ts +84 -0
  166. package/dist/gate-readiness.d.ts.map +1 -0
  167. package/dist/gate-readiness.js +103 -0
  168. package/dist/gate-readiness.js.map +1 -0
  169. package/dist/gc.d.ts +165 -0
  170. package/dist/gc.d.ts.map +1 -0
  171. package/dist/gc.js +313 -0
  172. package/dist/gc.js.map +1 -0
  173. package/dist/gh-failure.d.ts +42 -0
  174. package/dist/gh-failure.d.ts.map +1 -0
  175. package/dist/gh-failure.js +49 -0
  176. package/dist/gh-failure.js.map +1 -0
  177. package/dist/git.d.ts +75 -0
  178. package/dist/git.d.ts.map +1 -0
  179. package/dist/git.js +130 -0
  180. package/dist/git.js.map +1 -0
  181. package/dist/github.d.ts +187 -0
  182. package/dist/github.d.ts.map +1 -0
  183. package/dist/github.js +343 -0
  184. package/dist/github.js.map +1 -0
  185. package/dist/harness.d.ts +242 -0
  186. package/dist/harness.d.ts.map +1 -0
  187. package/dist/harness.js +157 -0
  188. package/dist/harness.js.map +1 -0
  189. package/dist/identity.d.ts +167 -0
  190. package/dist/identity.d.ts.map +1 -0
  191. package/dist/identity.js +231 -0
  192. package/dist/identity.js.map +1 -0
  193. package/dist/index.d.ts +147 -0
  194. package/dist/index.d.ts.map +1 -0
  195. package/dist/index.js +76 -0
  196. package/dist/index.js.map +1 -0
  197. package/dist/install-ci-branch-protection.d.ts +147 -0
  198. package/dist/install-ci-branch-protection.d.ts.map +1 -0
  199. package/dist/install-ci-branch-protection.js +166 -0
  200. package/dist/install-ci-branch-protection.js.map +1 -0
  201. package/dist/install-ci-capabilities/advance-lifecycle.d.ts +15 -0
  202. package/dist/install-ci-capabilities/advance-lifecycle.d.ts.map +1 -0
  203. package/dist/install-ci-capabilities/advance-lifecycle.js +28 -0
  204. package/dist/install-ci-capabilities/advance-lifecycle.js.map +1 -0
  205. package/dist/install-ci-capabilities/close-job.d.ts +13 -0
  206. package/dist/install-ci-capabilities/close-job.d.ts.map +1 -0
  207. package/dist/install-ci-capabilities/close-job.js +26 -0
  208. package/dist/install-ci-capabilities/close-job.js.map +1 -0
  209. package/dist/install-ci-capabilities/example-noop.d.ts +16 -0
  210. package/dist/install-ci-capabilities/example-noop.d.ts.map +1 -0
  211. package/dist/install-ci-capabilities/example-noop.js +23 -0
  212. package/dist/install-ci-capabilities/example-noop.js.map +1 -0
  213. package/dist/install-ci-capabilities/intake.d.ts +15 -0
  214. package/dist/install-ci-capabilities/intake.d.ts.map +1 -0
  215. package/dist/install-ci-capabilities/intake.js +28 -0
  216. package/dist/install-ci-capabilities/intake.js.map +1 -0
  217. package/dist/install-ci-capabilities/verify.d.ts +14 -0
  218. package/dist/install-ci-capabilities/verify.d.ts.map +1 -0
  219. package/dist/install-ci-capabilities/verify.js +27 -0
  220. package/dist/install-ci-capabilities/verify.js.map +1 -0
  221. package/dist/install-ci-core.d.ts +446 -0
  222. package/dist/install-ci-core.d.ts.map +1 -0
  223. package/dist/install-ci-core.js +760 -0
  224. package/dist/install-ci-core.js.map +1 -0
  225. package/dist/install-ci-github.d.ts +167 -0
  226. package/dist/install-ci-github.d.ts.map +1 -0
  227. package/dist/install-ci-github.js +315 -0
  228. package/dist/install-ci-github.js.map +1 -0
  229. package/dist/install-ci.d.ts +105 -0
  230. package/dist/install-ci.d.ts.map +1 -0
  231. package/dist/install-ci.js +363 -0
  232. package/dist/install-ci.js.map +1 -0
  233. package/dist/intake-event.d.ts +88 -0
  234. package/dist/intake-event.d.ts.map +1 -0
  235. package/dist/intake-event.js +66 -0
  236. package/dist/intake-event.js.map +1 -0
  237. package/dist/intake-marker.d.ts +95 -0
  238. package/dist/intake-marker.d.ts.map +1 -0
  239. package/dist/intake-marker.js +127 -0
  240. package/dist/intake-marker.js.map +1 -0
  241. package/dist/intake-triage.d.ts +48 -0
  242. package/dist/intake-triage.d.ts.map +1 -0
  243. package/dist/intake-triage.js +95 -0
  244. package/dist/intake-triage.js.map +1 -0
  245. package/dist/intake-trigger-template.d.ts +185 -0
  246. package/dist/intake-trigger-template.d.ts.map +1 -0
  247. package/dist/intake-trigger-template.js +449 -0
  248. package/dist/intake-trigger-template.js.map +1 -0
  249. package/dist/intake.d.ts +569 -0
  250. package/dist/intake.d.ts.map +1 -0
  251. package/dist/intake.js +1628 -0
  252. package/dist/intake.js.map +1 -0
  253. package/dist/integration-core.d.ts +539 -0
  254. package/dist/integration-core.d.ts.map +1 -0
  255. package/dist/integration-core.js +2195 -0
  256. package/dist/integration-core.js.map +1 -0
  257. package/dist/integrator.d.ts +343 -0
  258. package/dist/integrator.d.ts.map +1 -0
  259. package/dist/integrator.js +400 -0
  260. package/dist/integrator.js.map +1 -0
  261. package/dist/isolation.d.ts +219 -0
  262. package/dist/isolation.d.ts.map +1 -0
  263. package/dist/isolation.js +261 -0
  264. package/dist/isolation.js.map +1 -0
  265. package/dist/issue-provider.d.ts +349 -0
  266. package/dist/issue-provider.d.ts.map +1 -0
  267. package/dist/issue-provider.js +360 -0
  268. package/dist/issue-provider.js.map +1 -0
  269. package/dist/item-lock.d.ts +626 -0
  270. package/dist/item-lock.d.ts.map +1 -0
  271. package/dist/item-lock.js +1381 -0
  272. package/dist/item-lock.js.map +1 -0
  273. package/dist/item-path.d.ts +49 -0
  274. package/dist/item-path.d.ts.map +1 -0
  275. package/dist/item-path.js +66 -0
  276. package/dist/item-path.js.map +1 -0
  277. package/dist/ledger-lint.d.ts +129 -0
  278. package/dist/ledger-lint.d.ts.map +1 -0
  279. package/dist/ledger-lint.js +249 -0
  280. package/dist/ledger-lint.js.map +1 -0
  281. package/dist/ledger-read.d.ts +357 -0
  282. package/dist/ledger-read.d.ts.map +1 -0
  283. package/dist/ledger-read.js +442 -0
  284. package/dist/ledger-read.js.map +1 -0
  285. package/dist/ledger-write.d.ts +330 -0
  286. package/dist/ledger-write.d.ts.map +1 -0
  287. package/dist/ledger-write.js +411 -0
  288. package/dist/ledger-write.js.map +1 -0
  289. package/dist/lifecycle-gather.d.ts +30 -0
  290. package/dist/lifecycle-gather.d.ts.map +1 -0
  291. package/dist/lifecycle-gather.js +205 -0
  292. package/dist/lifecycle-gather.js.map +1 -0
  293. package/dist/lifecycle-pools.d.ts +180 -0
  294. package/dist/lifecycle-pools.d.ts.map +1 -0
  295. package/dist/lifecycle-pools.js +78 -0
  296. package/dist/lifecycle-pools.js.map +1 -0
  297. package/dist/merge-question-surfacer.d.ts +166 -0
  298. package/dist/merge-question-surfacer.d.ts.map +1 -0
  299. package/dist/merge-question-surfacer.js +297 -0
  300. package/dist/merge-question-surfacer.js.map +1 -0
  301. package/dist/mint-adr.d.ts +126 -0
  302. package/dist/mint-adr.d.ts.map +1 -0
  303. package/dist/mint-adr.js +257 -0
  304. package/dist/mint-adr.js.map +1 -0
  305. package/dist/mirror-pool-scan.d.ts +125 -0
  306. package/dist/mirror-pool-scan.d.ts.map +1 -0
  307. package/dist/mirror-pool-scan.js +104 -0
  308. package/dist/mirror-pool-scan.js.map +1 -0
  309. package/dist/needs-attention.d.ts +341 -0
  310. package/dist/needs-attention.d.ts.map +1 -0
  311. package/dist/needs-attention.js +900 -0
  312. package/dist/needs-attention.js.map +1 -0
  313. package/dist/orphan-sidecar.d.ts +79 -0
  314. package/dist/orphan-sidecar.d.ts.map +1 -0
  315. package/dist/orphan-sidecar.js +71 -0
  316. package/dist/orphan-sidecar.js.map +1 -0
  317. package/dist/output.d.ts +48 -0
  318. package/dist/output.d.ts.map +1 -0
  319. package/dist/output.js +66 -0
  320. package/dist/output.js.map +1 -0
  321. package/dist/pi-harness.d.ts +179 -0
  322. package/dist/pi-harness.d.ts.map +1 -0
  323. package/dist/pi-harness.js +342 -0
  324. package/dist/pi-harness.js.map +1 -0
  325. package/dist/placement.d.ts +99 -0
  326. package/dist/placement.d.ts.map +1 -0
  327. package/dist/placement.js +67 -0
  328. package/dist/placement.js.map +1 -0
  329. package/dist/prd-to-spec.d.ts +315 -0
  330. package/dist/prd-to-spec.d.ts.map +1 -0
  331. package/dist/prd-to-spec.js +684 -0
  332. package/dist/prd-to-spec.js.map +1 -0
  333. package/dist/prepare.d.ts +121 -0
  334. package/dist/prepare.d.ts.map +1 -0
  335. package/dist/prepare.js +140 -0
  336. package/dist/prepare.js.map +1 -0
  337. package/dist/prompt.d.ts +360 -0
  338. package/dist/prompt.d.ts.map +1 -0
  339. package/dist/prompt.js +499 -0
  340. package/dist/prompt.js.map +1 -0
  341. package/dist/protocol/ADR-FORMAT.md +47 -0
  342. package/dist/protocol/CLAIM-PROTOCOL.md +217 -0
  343. package/dist/protocol/REVIEW-PROTOCOL.md +119 -0
  344. package/dist/protocol/SURFACE-PROTOCOL.md +121 -0
  345. package/dist/protocol/TASKING-PROTOCOL.md +122 -0
  346. package/dist/protocol/WORK-CONTRACT.md +276 -0
  347. package/dist/protocol/spec-template.md +71 -0
  348. package/dist/protocol/task-template.md +65 -0
  349. package/dist/readiness.d.ts +66 -0
  350. package/dist/readiness.d.ts.map +1 -0
  351. package/dist/readiness.js +36 -0
  352. package/dist/readiness.js.map +1 -0
  353. package/dist/reap-branches.d.ts +102 -0
  354. package/dist/reap-branches.d.ts.map +1 -0
  355. package/dist/reap-branches.js +149 -0
  356. package/dist/reap-branches.js.map +1 -0
  357. package/dist/recover-isolated.d.ts +72 -0
  358. package/dist/recover-isolated.d.ts.map +1 -0
  359. package/dist/recover-isolated.js +188 -0
  360. package/dist/recover-isolated.js.map +1 -0
  361. package/dist/registry.d.ts +172 -0
  362. package/dist/registry.d.ts.map +1 -0
  363. package/dist/registry.js +296 -0
  364. package/dist/registry.js.map +1 -0
  365. package/dist/repo-config.d.ts +201 -0
  366. package/dist/repo-config.d.ts.map +1 -0
  367. package/dist/repo-config.js +414 -0
  368. package/dist/repo-config.js.map +1 -0
  369. package/dist/repo-key.d.ts +20 -0
  370. package/dist/repo-key.d.ts.map +1 -0
  371. package/dist/repo-key.js +68 -0
  372. package/dist/repo-key.js.map +1 -0
  373. package/dist/repo-mirror.d.ts +177 -0
  374. package/dist/repo-mirror.d.ts.map +1 -0
  375. package/dist/repo-mirror.js +271 -0
  376. package/dist/repo-mirror.js.map +1 -0
  377. package/dist/retry-backoff.d.ts +90 -0
  378. package/dist/retry-backoff.d.ts.map +1 -0
  379. package/dist/retry-backoff.js +98 -0
  380. package/dist/retry-backoff.js.map +1 -0
  381. package/dist/review-gate.d.ts +173 -0
  382. package/dist/review-gate.d.ts.map +1 -0
  383. package/dist/review-gate.js +261 -0
  384. package/dist/review-gate.js.map +1 -0
  385. package/dist/review-verdict.d.ts +149 -0
  386. package/dist/review-verdict.d.ts.map +1 -0
  387. package/dist/review-verdict.js +332 -0
  388. package/dist/review-verdict.js.map +1 -0
  389. package/dist/run.d.ts +221 -0
  390. package/dist/run.d.ts.map +1 -0
  391. package/dist/run.js +963 -0
  392. package/dist/run.js.map +1 -0
  393. package/dist/scan.d.ts +308 -0
  394. package/dist/scan.d.ts.map +1 -0
  395. package/dist/scan.js +374 -0
  396. package/dist/scan.js.map +1 -0
  397. package/dist/select-order.d.ts +75 -0
  398. package/dist/select-order.d.ts.map +1 -0
  399. package/dist/select-order.js +108 -0
  400. package/dist/select-order.js.map +1 -0
  401. package/dist/select-priority.d.ts +188 -0
  402. package/dist/select-priority.d.ts.map +1 -0
  403. package/dist/select-priority.js +80 -0
  404. package/dist/select-priority.js.map +1 -0
  405. package/dist/select.d.ts +25 -0
  406. package/dist/select.d.ts.map +1 -0
  407. package/dist/select.js +43 -0
  408. package/dist/select.js.map +1 -0
  409. package/dist/session-path.d.ts +36 -0
  410. package/dist/session-path.d.ts.map +1 -0
  411. package/dist/session-path.js +129 -0
  412. package/dist/session-path.js.map +1 -0
  413. package/dist/sidecar-apply.d.ts +83 -0
  414. package/dist/sidecar-apply.d.ts.map +1 -0
  415. package/dist/sidecar-apply.js +111 -0
  416. package/dist/sidecar-apply.js.map +1 -0
  417. package/dist/sidecar.d.ts +245 -0
  418. package/dist/sidecar.d.ts.map +1 -0
  419. package/dist/sidecar.js +481 -0
  420. package/dist/sidecar.js.map +1 -0
  421. package/dist/slug-namespace.d.ts +204 -0
  422. package/dist/slug-namespace.d.ts.map +1 -0
  423. package/dist/slug-namespace.js +229 -0
  424. package/dist/slug-namespace.js.map +1 -0
  425. package/dist/spec-complete.d.ts +44 -0
  426. package/dist/spec-complete.d.ts.map +1 -0
  427. package/dist/spec-complete.js +69 -0
  428. package/dist/spec-complete.js.map +1 -0
  429. package/dist/start.d.ts +97 -0
  430. package/dist/start.d.ts.map +1 -0
  431. package/dist/start.js +633 -0
  432. package/dist/start.js.map +1 -0
  433. package/dist/status.d.ts +199 -0
  434. package/dist/status.d.ts.map +1 -0
  435. package/dist/status.js +228 -0
  436. package/dist/status.js.map +1 -0
  437. package/dist/surface-gate.d.ts +162 -0
  438. package/dist/surface-gate.d.ts.map +1 -0
  439. package/dist/surface-gate.js +206 -0
  440. package/dist/surface-gate.js.map +1 -0
  441. package/dist/surface-persist.d.ts +86 -0
  442. package/dist/surface-persist.d.ts.map +1 -0
  443. package/dist/surface-persist.js +129 -0
  444. package/dist/surface-persist.js.map +1 -0
  445. package/dist/tasker-review-loop.d.ts +249 -0
  446. package/dist/tasker-review-loop.d.ts.map +1 -0
  447. package/dist/tasker-review-loop.js +369 -0
  448. package/dist/tasker-review-loop.js.map +1 -0
  449. package/dist/tasking-eligibility.d.ts +74 -0
  450. package/dist/tasking-eligibility.d.ts.map +1 -0
  451. package/dist/tasking-eligibility.js +52 -0
  452. package/dist/tasking-eligibility.js.map +1 -0
  453. package/dist/tasking-lock.d.ts +111 -0
  454. package/dist/tasking-lock.d.ts.map +1 -0
  455. package/dist/tasking-lock.js +256 -0
  456. package/dist/tasking-lock.js.map +1 -0
  457. package/dist/tasking.d.ts +275 -0
  458. package/dist/tasking.d.ts.map +1 -0
  459. package/dist/tasking.js +952 -0
  460. package/dist/tasking.js.map +1 -0
  461. package/dist/triage-gate.d.ts +127 -0
  462. package/dist/triage-gate.d.ts.map +1 -0
  463. package/dist/triage-gate.js +139 -0
  464. package/dist/triage-gate.js.map +1 -0
  465. package/dist/triage-persist.d.ts +163 -0
  466. package/dist/triage-persist.d.ts.map +1 -0
  467. package/dist/triage-persist.js +387 -0
  468. package/dist/triage-persist.js.map +1 -0
  469. package/dist/verdict-json.d.ts +32 -0
  470. package/dist/verdict-json.d.ts.map +1 -0
  471. package/dist/verdict-json.js +74 -0
  472. package/dist/verdict-json.js.map +1 -0
  473. package/dist/verify-workflow-template.d.ts +60 -0
  474. package/dist/verify-workflow-template.d.ts.map +1 -0
  475. package/dist/verify-workflow-template.js +126 -0
  476. package/dist/verify-workflow-template.js.map +1 -0
  477. package/dist/verify.d.ts +60 -0
  478. package/dist/verify.d.ts.map +1 -0
  479. package/dist/verify.js +62 -0
  480. package/dist/verify.js.map +1 -0
  481. package/dist/watch-session.d.ts +112 -0
  482. package/dist/watch-session.d.ts.map +1 -0
  483. package/dist/watch-session.js +347 -0
  484. package/dist/watch-session.js.map +1 -0
  485. package/dist/work-layout.d.ts +198 -0
  486. package/dist/work-layout.d.ts.map +1 -0
  487. package/dist/work-layout.js +217 -0
  488. package/dist/work-layout.js.map +1 -0
  489. package/dist/work-on.d.ts +154 -0
  490. package/dist/work-on.d.ts.map +1 -0
  491. package/dist/work-on.js +387 -0
  492. package/dist/work-on.js.map +1 -0
  493. package/dist/workspace.d.ts +224 -0
  494. package/dist/workspace.d.ts.map +1 -0
  495. package/dist/workspace.js +325 -0
  496. package/dist/workspace.js.map +1 -0
  497. package/package.json +46 -2
  498. package/src/advance-ci-template.ts +203 -0
  499. package/src/advance-classify.ts +197 -0
  500. package/src/advance-drivers.ts +414 -0
  501. package/src/advance-isolated.ts +432 -0
  502. package/src/advance-lifecycle-template.ts +791 -0
  503. package/src/advance-loop-driver.ts +745 -0
  504. package/src/advance-treeless-publish.ts +177 -0
  505. package/src/advance.ts +1564 -0
  506. package/src/advancing-lock.ts +988 -0
  507. package/src/agent-launch.ts +137 -0
  508. package/src/agent-stop.ts +361 -0
  509. package/src/apply-decide.ts +242 -0
  510. package/src/apply-merge-action.ts +502 -0
  511. package/src/apply-persist.ts +518 -0
  512. package/src/arbiter.ts +372 -0
  513. package/src/brand.ts +111 -0
  514. package/src/buildable-body.ts +196 -0
  515. package/src/categorise.ts +158 -0
  516. package/src/claim-cas.ts +513 -0
  517. package/src/cli-spinner.ts +225 -0
  518. package/src/cli.ts +4369 -0
  519. package/src/close-job-template.ts +236 -0
  520. package/src/close-job.ts +319 -0
  521. package/src/complete.ts +1379 -0
  522. package/src/concurrency.ts +151 -0
  523. package/src/config-override.ts +116 -0
  524. package/src/config.ts +883 -0
  525. package/src/continue-branch.ts +542 -0
  526. package/src/cwd-section.ts +392 -0
  527. package/src/decision-engine.ts +272 -0
  528. package/src/detect.ts +124 -0
  529. package/src/do-autopick.ts +223 -0
  530. package/src/do-config.ts +589 -0
  531. package/src/do-remote-auto.ts +197 -0
  532. package/src/do.ts +2623 -0
  533. package/src/drop-source.ts +194 -0
  534. package/src/eligibility.ts +79 -0
  535. package/src/env-config.ts +305 -0
  536. package/src/failure-cause.ts +142 -0
  537. package/src/format.ts +313 -0
  538. package/src/frontmatter.ts +485 -0
  539. package/src/gate-readiness.ts +147 -0
  540. package/src/gc.ts +510 -0
  541. package/src/gh-failure.ts +53 -0
  542. package/src/git.ts +186 -0
  543. package/src/github.ts +468 -0
  544. package/src/harness.ts +355 -0
  545. package/src/identity.ts +322 -0
  546. package/src/index.ts +785 -0
  547. package/src/install-ci-branch-protection.ts +255 -0
  548. package/src/install-ci-capabilities/advance-lifecycle.ts +34 -0
  549. package/src/install-ci-capabilities/close-job.ts +32 -0
  550. package/src/install-ci-capabilities/example-noop.ts +24 -0
  551. package/src/install-ci-capabilities/intake.ts +34 -0
  552. package/src/install-ci-capabilities/verify.ts +33 -0
  553. package/src/install-ci-core.ts +1088 -0
  554. package/src/install-ci-github.ts +376 -0
  555. package/src/install-ci.ts +552 -0
  556. package/src/intake-event.ts +102 -0
  557. package/src/intake-marker.ts +195 -0
  558. package/src/intake-triage.ts +138 -0
  559. package/src/intake-trigger-template.ts +591 -0
  560. package/src/intake.ts +2445 -0
  561. package/src/integration-core.ts +3065 -0
  562. package/src/integrator.ts +771 -0
  563. package/src/isolation.ts +484 -0
  564. package/src/issue-provider.ts +733 -0
  565. package/src/item-lock.ts +1858 -0
  566. package/src/item-path.ts +75 -0
  567. package/src/ledger-lint.ts +332 -0
  568. package/src/ledger-read.ts +924 -0
  569. package/src/ledger-write.ts +865 -0
  570. package/src/lifecycle-gather.ts +298 -0
  571. package/src/lifecycle-pools.ts +250 -0
  572. package/src/merge-question-surfacer.ts +496 -0
  573. package/src/mint-adr.ts +362 -0
  574. package/src/mirror-pool-scan.ts +240 -0
  575. package/src/needs-attention.ts +1506 -0
  576. package/src/orphan-sidecar.ts +150 -0
  577. package/src/output.ts +89 -0
  578. package/src/pi-harness.ts +403 -0
  579. package/src/placement.ts +131 -0
  580. package/src/prd-to-spec.ts +1023 -0
  581. package/src/prepare.ts +230 -0
  582. package/src/prompt.ts +760 -0
  583. package/src/readiness.ts +98 -0
  584. package/src/reap-branches.ts +278 -0
  585. package/src/recover-isolated.ts +276 -0
  586. package/src/registry.ts +475 -0
  587. package/src/repo-config.ts +550 -0
  588. package/src/repo-key.ts +74 -0
  589. package/src/repo-mirror.ts +367 -0
  590. package/src/retry-backoff.ts +130 -0
  591. package/src/review-gate.ts +389 -0
  592. package/src/review-verdict.ts +422 -0
  593. package/src/run.ts +1430 -0
  594. package/src/scan.ts +611 -0
  595. package/src/select-order.ts +143 -0
  596. package/src/select-priority.ts +266 -0
  597. package/src/select.ts +62 -0
  598. package/src/session-path.ts +153 -0
  599. package/src/sidecar-apply.ts +216 -0
  600. package/src/sidecar.ts +700 -0
  601. package/src/slug-namespace.ts +367 -0
  602. package/src/spec-complete.ts +118 -0
  603. package/src/start.ts +974 -0
  604. package/src/status.ts +441 -0
  605. package/src/surface-gate.ts +337 -0
  606. package/src/surface-persist.ts +241 -0
  607. package/src/tasker-review-loop.ts +671 -0
  608. package/src/tasking-eligibility.ts +114 -0
  609. package/src/tasking-lock.ts +416 -0
  610. package/src/tasking.ts +1438 -0
  611. package/src/triage-gate.ts +248 -0
  612. package/src/triage-persist.ts +570 -0
  613. package/src/verdict-json.ts +73 -0
  614. package/src/verify-workflow-template.ts +159 -0
  615. package/src/verify.ts +123 -0
  616. package/src/watch-session.ts +397 -0
  617. package/src/work-layout.ts +262 -0
  618. package/src/work-on.ts +660 -0
  619. package/src/workspace.ts +502 -0
package/src/advance.ts ADDED
@@ -0,0 +1,1564 @@
1
+ import {existsSync, readFileSync} from 'node:fs';
2
+ import {join} from 'node:path';
3
+ import {type WorkFolderKey, workItemPath, workItemRel} from './work-layout.js';
4
+ import {
5
+ resolveAdvanceArg,
6
+ SlugResolutionError,
7
+ type SlugNamespace,
8
+ } from './slug-namespace.js';
9
+ import {ledgerRead, type LedgerReadStrategy} from './ledger-read.js';
10
+ import {parseFrontmatter} from './frontmatter.js';
11
+ import {
12
+ parseSidecar,
13
+ sidecarPathFor,
14
+ type SidecarModel,
15
+ type SidecarType,
16
+ } from './sidecar.js';
17
+ import {classifyTick, type TickClassification} from './advance-classify.js';
18
+ import {
19
+ acquireAdvancingLock,
20
+ releaseAdvancingLock,
21
+ type AcquireAdvancingLockResult,
22
+ type ReleaseAdvancingLockResult,
23
+ } from './advancing-lock.js';
24
+ import {performDo, type DoOptions, type DoResult} from './do.js';
25
+ import {
26
+ harnessSurfaceGate,
27
+ toNewQuestions,
28
+ type SurfaceGate,
29
+ } from './surface-gate.js';
30
+ import {harnessTriageGate, type TriageGate} from './triage-gate.js';
31
+ import type {ObservationTriage} from './config.js';
32
+ import {
33
+ autoDispositionObservation,
34
+ promoteObservation,
35
+ type AutoDispositionOptions,
36
+ type AutoDispositionResult,
37
+ type PromoteObservationOptions,
38
+ type PromoteObservationResult,
39
+ } from './triage-persist.js';
40
+ import {mintAdr, type MintAdrOptions, type MintAdrResult} from './mint-adr.js';
41
+ import {LIFECYCLE_CAS_CONTENTION} from './advancing-lock.js';
42
+ import {
43
+ decide,
44
+ DisallowedOutcomeError,
45
+ type DecisionVerdict,
46
+ } from './decision-engine.js';
47
+ import {
48
+ APPLY_ALLOWED_OUTCOMES,
49
+ buildApplyDecisionInput,
50
+ harnessApplyDecider,
51
+ type ApplyDecider,
52
+ } from './apply-decide.js';
53
+ import {
54
+ persistSurfacedQuestions,
55
+ type SurfacePersistOptions,
56
+ type SurfacePersistResult,
57
+ } from './surface-persist.js';
58
+ import {
59
+ applyAnsweredQuestions,
60
+ type ApplyAnsweredQuestionsOptions,
61
+ type ApplyAnsweredQuestionsResult,
62
+ } from './apply-persist.js';
63
+ import {
64
+ detectAnsweredMergeAction,
65
+ performMergeAction,
66
+ type MergeActionHandler,
67
+ type MergeActionResult,
68
+ } from './apply-merge-action.js';
69
+ import type {VerifyConfig} from './verify.js';
70
+ import type {NewQuestion} from './sidecar.js';
71
+
72
+ /**
73
+ * The **`advance` verb SKELETON** (prd `advance-loop`, task
74
+ * `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
76
+ * a `work/` item ONE lifecycle rung toward "ready/built", reusing the SAME shared
77
+ * `prefix:arg` resolver `do` uses (extended with the `obs:` namespace, see
78
+ * {@link resolveAdvanceArg}).
79
+ *
80
+ * This module delivers the **classify → lock → execute SKELETON** — the contract
81
+ * both drivers (the later one-shot/loop tasks) wrap:
82
+ *
83
+ * 1. **classify** — read-only, NO model, NO lock: read the item's two signals
84
+ * (`needsAnswers` + the sidecar's answered-state) and call the pure
85
+ * {@link classifyTick} to get the rung kind. A CAS loser will have spent ONLY
86
+ * this free classification.
87
+ * 2. **lock** — take the `advancing` CAS borrow ({@link acquireAdvancingLock})
88
+ * for the classified rung, keyed on the item's `<type>-<slug>` identity. The
89
+ * expensive (agent/model) phase is ALWAYS post-lock, so a loser backs off
90
+ * having done ~nothing (a TOCTOU between classify and CAS is harmless — only
91
+ * the free classification is wasted, and the loser never starts model work).
92
+ * 3. **execute** — WINNER ONLY: dispatch the classified rung to the
93
+ * {@link RungExecutor} seam, then release the borrow.
94
+ *
95
+ * The **rung BODIES** are now ALL filled (their own tasks): `surface`
96
+ * (`advance-rung-surface`), `apply` (`advance-rung-apply`), and
97
+ * `triage-observation` (`advance-rung-triage`) dispatch through the clearly-named
98
+ * executor SEAM ({@link RungExecutor}); the build/task rungs ORCHESTRATE
99
+ * `do`/`do prd:`. What this verb does NOT do (LATER tasks):
100
+ * - The two **DRIVERS** (one-shot sequential / loop) + `-n` + the gate-FAMILY
101
+ * WIRING that resolves `autoBuild`/`autoTask`/`observationTriage` and threads
102
+ * them into the build/task gate composition — task `advance-drivers-and-gates`.
103
+ * (This verb already RESPECTS `observationTriage` in the triage rung — the gate's
104
+ * resolution chain + the build/task gate composition is the drivers task.)
105
+ * - The bare `advance` (eligible-SET) form — it needs the pool scan / driver, so
106
+ * the verb here is a SINGLE named-item tick; the bare form errors clearly
107
+ * ("needs the driver task"). See the `## Decisions` block in the task.
108
+ *
109
+ * The build-task / task-spec rungs ORCHESTRATE the existing `do` / `do spec:`
110
+ * machinery ({@link performDo}) — `advance` is a driver layered ON TOP, NEVER a
111
+ * peer that duplicates the build/task path (ONE build path, ONE task path —
112
+ * US #6).
113
+ */
114
+
115
+ const DEFAULT_ARBITER = 'origin';
116
+
117
+ /**
118
+ * The terminal condition of one `advance` tick (mirrors `DoOutcome`'s shape).
119
+ *
120
+ * `vanished` (task `observation-identity-is-its-filename-not-a-foreign-slug`):
121
+ * a BENIGN SKIP when the item's file was enumerated into the lifecycle pool but
122
+ * has since been moved/triaged/deleted by a sibling leg (the cross-tick window
123
+ * under parallel CI). It is `exitCode: 0` so the matrix tolerates it, but it is
124
+ * DISTINGUISHABLE from `no-op` (which is a calm classify result, e.g. a pending
125
+ * sidecar) so reviewers can grep these out. NOT used for a human-typed bare slug
126
+ * that names nothing — that is a malformed invocation; today the two are not
127
+ * distinguished at this seam (a human typo also skips benignly) and the matrix
128
+ * scale of the calm condition justifies the trade.
129
+ */
130
+ export type AdvanceOutcome =
131
+ | 'advanced'
132
+ | 'no-op'
133
+ | 'vanished'
134
+ | 'usage-error'
135
+ | 'lost'
136
+ | 'contended'
137
+ | 'not-implemented'
138
+ | 'invariant-violation';
139
+
140
+ /** Maps onto the claim-CAS exit codes (identical semantics). */
141
+ export type AdvanceExitCode = 0 | 1 | 2 | 3;
142
+
143
+ /**
144
+ * The injectable rung-executor SEAM — WHAT happens once the tick has classified a
145
+ * rung AND won the `advancing` lock. It is the boundary between the skeleton (this
146
+ * 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:`.
148
+ *
149
+ * Production wires {@link defaultRungExecutor}; tests inject a spy to assert the
150
+ * classify→lock→dispatch ORDER (and that a CAS loser never reaches the executor).
151
+ */
152
+ export interface RungExecutor {
153
+ /** A ready task → build it by ORCHESTRATING `do <slug>` (NOT a re-implementation). */
154
+ buildTask(input: RungExecInput): Promise<RungExecResult>;
155
+ /** A ready spec → task it by ORCHESTRATING `do spec:<slug>` (NOT a re-implementation). */
156
+ taskSpec(input: RungExecInput): Promise<RungExecResult>;
157
+ /** An untriaged observation → triage it (LATER task fills this body). */
158
+ triageObservation(input: RungExecInput): Promise<RungExecResult>;
159
+ /** `needsAnswers` but no sidecar → surface the questions (LATER task fills this). */
160
+ surface(input: RungExecInput): Promise<RungExecResult>;
161
+ /** Every entry answered → apply the answers + advance (LATER task fills this). */
162
+ apply(input: RungExecInput): Promise<RungExecResult>;
163
+ }
164
+
165
+ /** What a rung executor is handed: the resolved identity + the run context. */
166
+ export interface RungExecInput {
167
+ /** The canonical namespaced identity (`task:<slug>` / `prd:<slug>` / `observation:<slug>`). */
168
+ item: string;
169
+ /** The resolved namespace (`task` / `prd` / `observation`). */
170
+ namespace: SlugNamespace;
171
+ /** The bare slug. */
172
+ slug: string;
173
+ /** The classification that selected this rung (the two signals are visible). */
174
+ classification: TickClassification;
175
+ /** The tick's run context (cwd, arbiter, …) — threaded to `do`/`do prd:`. */
176
+ context: AdvanceContext;
177
+ }
178
+
179
+ /** A rung executor's result (the outcome the tick reports). */
180
+ export interface RungExecResult {
181
+ exitCode: AdvanceExitCode;
182
+ outcome: AdvanceOutcome;
183
+ message: string;
184
+ }
185
+
186
+ /** The run context threaded from the CLI into the tick + the rung executor. */
187
+ export interface AdvanceContext {
188
+ /** The working clone/checkout to run in-place in. */
189
+ cwd: string;
190
+ /** Name of the arbiter git remote. Defaults to `origin`. */
191
+ arbiter?: string;
192
+ /** The base `do` options the build/task rungs orchestrate `performDo` with. */
193
+ doOptions?: Omit<DoOptions, 'arg'>;
194
+ /**
195
+ * The build/task ORCHESTRATION DRIVER seam (task
196
+ * `advance-loop-driver-registry-set-job-worktrees`). The build-task / task-spec
197
+ * rungs ORCHESTRATE `do` by handing the resolved arg + the threaded
198
+ * {@link doOptions} to THIS driver. `undefined` ⇒ {@link performDo} (the IN-PLACE
199
+ * substrate — the human-local one-shot `advance` command + today's
200
+ * single-mirror `run --advance`, which build in the cwd checkout). The
201
+ * registry-set advance driver injects a PER-MIRROR JOB-WORKTREE driver
202
+ * ({@link jobWorktreeDoDriver}) so the daemon/CI path builds isolated off each
203
+ * mirror's arbiter (the SAME isolation `run`'s build tick gives `runOneItem`),
204
+ * NOT in `process.cwd()`. This is the parameterised isolation strategy the
205
+ * task's `## Decisions` records: in-place and worktree COEXIST behind one seam,
206
+ * reusing the EXISTING `selectIsolationStrategy`/`jobWorktreeStrategy` (no second
207
+ * isolation mechanism). The DEFAULT keeps in-place behaviour byte-for-byte.
208
+ */
209
+ doDriver?: (options: DoOptions) => Promise<DoResult>;
210
+ /**
211
+ * The SURFACE gate seam — the fresh-context `surface-questions` spawn the
212
+ * surface rung uses (task `advance-rung-surface`). The skill JUDGES (emits
213
+ * questions); the engine PERSISTS. Production wires {@link harnessSurfaceGate};
214
+ * tests inject a stub emit. `undefined` ⇒ the surface rung defaults to
215
+ * {@link harnessSurfaceGate} (a NullHarness, no real model) so the seam is never
216
+ * a crash — but the CLI threads the real harness-backed gate.
217
+ */
218
+ surfaceGate?: SurfaceGate;
219
+ /**
220
+ * The model the SURFACE agent runs on (de-correlated from the builder, like
221
+ * `reviewModel`). Flows to the gate's launch through `LaunchInput.model`.
222
+ */
223
+ surfaceModel?: string;
224
+ /**
225
+ * Persist the surfaced questions ATOMICALLY (append-or-create the sidecar + set
226
+ * `needsAnswers:true` in ONE commit). Tests inject a spy; production uses
227
+ * {@link persistSurfacedQuestions}. The ENGINE owns ALL persistence — the skill
228
+ * writes nothing.
229
+ */
230
+ surfacePersist?: (options: SurfacePersistOptions) => SurfacePersistResult;
231
+ /**
232
+ * Apply the HUMAN's answered sidecar ATOMICALLY (item body + sidecar in ONE
233
+ * commit, via the sidecar contract's atomic-apply), then resolve / re-pause /
234
+ * disposition to a terminal. Tests inject a spy; production uses
235
+ * {@link applyAnsweredQuestions}. The engine applies ONLY human-authored answers
236
+ * — it NEVER invents one.
237
+ */
238
+ applyPersist?: (
239
+ options: ApplyAnsweredQuestionsOptions,
240
+ ) => ApplyAnsweredQuestionsResult;
241
+ /**
242
+ * Supply the NEW follow-up questions an apply discovered (so it APPENDS them and
243
+ * re-pauses rather than resolving). `undefined`/empty ⇒ the apply resolves (or
244
+ * dispositions) the item. The follow-up GENERATION is the surface skill's job;
245
+ * this seam lets the apply rung append already-formulated follow-ups (and lets
246
+ * tests drive the append-re-pause path) WITHOUT inventing an ANSWER.
247
+ */
248
+ applyFollowups?: NewQuestion[];
249
+ /**
250
+ * The AGENTIC apply DECISION seam (task
251
+ * `agentic-apply-retire-disposition-vocabulary`): the fresh-context decision
252
+ * 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`),
255
+ * grounded in the source's full context. It is the injected
256
+ * {@link ApplyDecider} the shared `decide(input, allowedOutcomes)` engine runs;
257
+ * tests inject a CANNED verdict (no model). `undefined` ⇒ the apply rung defaults
258
+ * to {@link harnessApplyDecider} (a NullHarness, no real model) so the seam is
259
+ * 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-*`
261
+ * disposition token; `adr` is now WIRED (task `agentic-apply-mint-adr-route`).
262
+ */
263
+ applyDecide?: ApplyDecider;
264
+ /** The model the apply-DECISION agent runs on (de-correlated, like `surfaceModel`). */
265
+ applyModel?: string;
266
+ /**
267
+ * The 3-state `observationTriage` policy (ADR `ci-config-policy-and-gate-
268
+ * family` §2) read at the triage rung. It governs the rung-internal
269
+ * ask-vs-auto distinction (the SELECTION-layer `off` gate is applied EARLIER, in
270
+ * the driver, by dropping the observation pool — so a rung that runs was either
271
+ * `ask`/`auto`-selected OR explicitly named, which BYPASSES the selection gate):
272
+ * - `'auto'` ⇒ the conservative auto-disposition EXCEPTION is live (ask the
273
+ * {@link TriageGate}; auto-dispose ONLY the no-question cases);
274
+ * - `'ask'` / `'off'` / `undefined` ⇒ surface the promote/keep/delete question
275
+ * and WAIT (the question-gated path). Under `off` + an EXPLICIT `obs:<slug>`
276
+ * (which bypasses the selection gate) the rung runs in `ask`-mode — the
277
+ * conservative, question-surfacing default (task `## Decisions`). SURFACE +
278
+ * APPLY stay ALWAYS allowed; this gate ONLY governs the auto-disposition
279
+ * exception, never the always-allowed question loop.
280
+ */
281
+ observationTriage?: ObservationTriage;
282
+ /**
283
+ * The TRIAGE auto-disposition gate seam — the fresh-context spawn the triage
284
+ * rung asks (ONLY when `observationTriage` is `'auto'`) whether an observation
285
+ * is a no-question case. The skill JUDGES; the engine ACTS. Production wires
286
+ * {@link harnessTriageGate}; tests inject a stub decision. `undefined` ⇒ the
287
+ * triage rung defaults to {@link harnessTriageGate} (a NullHarness, no real
288
+ * model) so the seam is never a crash.
289
+ */
290
+ triageGate?: TriageGate;
291
+ /** The model the TRIAGE agent runs on (de-correlated, like `surfaceModel`). */
292
+ triageModel?: string;
293
+ /**
294
+ * Execute the conservative auto-disposition ATOMICALLY (record + marker, one
295
+ * commit). Tests inject a spy; production uses {@link autoDispositionObservation}.
296
+ */
297
+ autoDisposition?: (options: AutoDispositionOptions) => AutoDispositionResult;
298
+ /**
299
+ * Promote an answered observation: CAS-create a new backlog stub keyed on the
300
+ * NEW item's identity, then record + resolve the observation. Tests inject a
301
+ * spy; production uses {@link promoteObservation}.
302
+ */
303
+ promote?: (
304
+ options: PromoteObservationOptions,
305
+ ) => Promise<PromoteObservationResult>;
306
+ /**
307
+ * Mint an ADR from an answered observation (the agentic `mint-adr` verdict, task
308
+ * `agentic-apply-mint-adr-route`): CAS-create `docs/adr/<slug>.md` keyed on the
309
+ * NEW ADR's identity, with the source + sidecar deleted in the SAME commit. The
310
+ * SIBLING of {@link promote} for the `docs/adr/` target (an ADR lands OUTSIDE the
311
+ * work board, so it is a distinct route, not a `promoteObservation` artifact
312
+ * type). Tests inject a spy; production uses {@link mintAdr}.
313
+ */
314
+ mintAdr?: (options: MintAdrOptions) => Promise<MintAdrResult>;
315
+ /**
316
+ * The NEW backlog slug an answered promote drafts. `undefined` ⇒ the promote
317
+ * defaults to the observation's own slug. Lets a test (or a future driver) steer
318
+ * the promoted item's identity WITHOUT inventing the answer.
319
+ */
320
+ promoteSlug?: string;
321
+ /**
322
+ * The execution working area (`workspacesDir`, default `~/.dorfl`) the
323
+ * answered-merge LAND uses to cut a per-job worktree from the hub mirror
324
+ * (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
329
+ * `apply-rung-merge-disposition`)
330
+ */
331
+ workspacesDir?: string;
332
+ /**
333
+ * The arbiter URL the answered-merge LAND mirrors from. Optional: when
334
+ * unset the dispatcher resolves it via `git remote get-url <arbiter>` in the
335
+ * apply rung's `cwd` (the in-place / one-shot caller). The registry-set
336
+ * advance driver threads the per-mirror origin URL here directly so the
337
+ * per-mirror tree-less clone (whose `origin` points at the LOCAL mirror
338
+ * path, not the real arbiter URL) is bypassed.
339
+ */
340
+ arbiterUrl?: string;
341
+ /**
342
+ * Per-repo env-prep config (`prepare`) the answered-merge LAND threads into
343
+ * `performIntegration` so the fresh-worktree gate runs `prepare` then
344
+ * `verify` on the rebased tip — the SAME config the build path uses.
345
+ */
346
+ prepare?: VerifyConfig;
347
+ /**
348
+ * Per-repo acceptance gate (`verify`) the answered-merge LAND threads into
349
+ * `performIntegration` so the fresh-worktree gate re-verifies the rebased
350
+ * tip before integrating.
351
+ */
352
+ verify?: VerifyConfig;
353
+ /**
354
+ * Resolved `strictMergeApproval` boolean (sibling task
355
+ * `strict-merge-approval-gate`). Default OFF ⇒ honour the prior approval +
356
+ * land on a green re-verify (cheap, the PRD-applied OQ6 default). ON ⇒
357
+ * re-surface the merge-question when the merge-base moved between the
358
+ * surfacer's question and this apply (the host-agnostic analogue of
359
+ * GitHub's "dismiss stale approvals when the base changes").
360
+ */
361
+ strictMergeApproval?: boolean;
362
+ /**
363
+ * The merge-action dispatch SEAM (task `apply-rung-merge-disposition`):
364
+ * the deterministic answer-driven runner-action handler the apply rung
365
+ * invokes BEFORE the agentic decider when an answered `kind: merge` entry
366
+ * is detected. Production wires {@link performMergeAction} (checks out via
367
+ * `createJob` + lands via `performIntegration` with `committedRecovery:
368
+ * true` + `freshWorktreeGate: true`); tests inject a stub so they assert on
369
+ * the apply-rung's `landed | refused | restale | hold | drop` routing
370
+ * WITHOUT spinning up a hub mirror or running real verify.
371
+ */
372
+ mergeAction?: MergeActionHandler;
373
+ /** Sink for human-readable progress notes. */
374
+ note?: (message: string) => void;
375
+ }
376
+
377
+ /** The options one `advance` tick consumes. */
378
+ export interface AdvanceOptions extends AdvanceContext {
379
+ /**
380
+ * The raw CLI slug argument: bare (= task), `task:<slug>`, `prd:<slug>`, or
381
+ * `obs:<slug>` / `observation:<slug>`. Omit/empty ⇒ the bare eligible-SET form,
382
+ * which needs the driver task (a clear error here — see `## Decisions`).
383
+ */
384
+ arg?: string;
385
+ /** The repo working-tree root whose `work/` to read (defaults to `cwd`). */
386
+ repoPath?: string;
387
+ /** The read seam for the resolver's cross-namespace existence check. */
388
+ read?: LedgerReadStrategy;
389
+ /** The rung executor seam (defaults to {@link defaultRungExecutor}). */
390
+ executor?: RungExecutor;
391
+ /** Inject the classify signals (tests); production reads them from disk. */
392
+ readSignals?: (input: ReadSignalsInput) => ItemSignals;
393
+ /** Inject the lock acquire (tests); production uses {@link acquireAdvancingLock}. */
394
+ acquireLock?: (item: string) => Promise<AcquireAdvancingLockResult>;
395
+ /** Inject the lock release (tests); production uses {@link releaseAdvancingLock}. */
396
+ releaseLock?: (item: string) => Promise<ReleaseAdvancingLockResult>;
397
+ }
398
+
399
+ /** The tick's terminal result. */
400
+ export interface AdvanceResult {
401
+ exitCode: AdvanceExitCode;
402
+ outcome: AdvanceOutcome;
403
+ /** The classified rung kind, when the tick got far enough to classify. */
404
+ rung?: TickClassification['kind'];
405
+ /** The resolved bare slug acted on, when one was resolved. */
406
+ slug?: string;
407
+ /** Human-readable summary of the terminal condition. */
408
+ message: string;
409
+ }
410
+
411
+ /** The two classify SIGNALS read off the item (the only state the classifier needs). */
412
+ export interface ItemSignals {
413
+ /** The item-body `needsAnswers` flag. */
414
+ needsAnswers: boolean | undefined;
415
+ /** The parsed ACTIVE sidecar, or `undefined` when none exists. */
416
+ sidecar: SidecarModel | undefined;
417
+ }
418
+
419
+ /** What {@link readItemSignals} needs to read an item's two signals off disk. */
420
+ export interface ReadSignalsInput {
421
+ /** The repo working-tree root. */
422
+ repoPath: string;
423
+ /** The item type (task / prd / observation). */
424
+ type: SidecarType;
425
+ /** The bare slug. */
426
+ slug: string;
427
+ /** The canonical namespaced identity (`<namespace>:<slug>`). */
428
+ item: string;
429
+ }
430
+
431
+ /**
432
+ * Map the resolver's namespace onto the sidecar type. The `spec` namespace maps
433
+ * to the `spec` sidecar type, so `advance spec:<slug>` resolves its
434
+ * signals/folders (`FOLDERS_FOR_TYPE['spec']`) and orchestrates `do spec:<slug>`.
435
+ */
436
+ function sidecarTypeFor(namespace: SlugNamespace): SidecarType {
437
+ return namespace === 'observation'
438
+ ? 'observation'
439
+ : namespace === 'spec'
440
+ ? 'spec'
441
+ : 'task';
442
+ }
443
+
444
+ /**
445
+ * Read an item's two CLASSIFY signals off disk (read-only, NO model, NO lock):
446
+ * - `needsAnswers` from the item-body frontmatter (searching the lifecycle
447
+ * folders the type may rest in), and
448
+ * - the ACTIVE sidecar (`work/questions/<type>-<slug>.md`) parsed when present.
449
+ *
450
+ * Identity-keyed: the sidecar path derives PURELY from `<type>-<slug>` (it
451
+ * survives the item's `git mv`s with no lock-step move). Returns
452
+ * `needsAnswers:undefined` when no item file is found (the classifier treats it
453
+ * as "not gated", an ANALYSE rung) — the verb's job here is to wire the two
454
+ * signals, not to assert the item exists (that is the rung's concern).
455
+ */
456
+ export function readItemSignals(input: ReadSignalsInput): ItemSignals {
457
+ const {repoPath, type, slug, item} = input;
458
+ const needsAnswers = readNeedsAnswers(repoPath, type, slug);
459
+ const sidecarRel = sidecarPathFor(item);
460
+ const sidecarAbs = join(repoPath, sidecarRel);
461
+ let sidecar: SidecarModel | undefined;
462
+ if (existsSync(sidecarAbs)) {
463
+ sidecar = parseSidecar(readFileSync(sidecarAbs, 'utf8'));
464
+ }
465
+ return {needsAnswers, sidecar};
466
+ }
467
+
468
+ /**
469
+ * The lifecycle folders each item type may rest in (frontmatter source). After
470
+ * the capstone cut-over (task
471
+ * `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
475
+ * frontmatter source.
476
+ *
477
+ * STAGING IS INCLUDED (`tasks-backlog` / `prds-proposed`): with `surfaceStaging`
478
+ * on (the user-visible default) the lifecycle surface pool enumerates
479
+ * `needsAnswers` items resting in STAGING as `task:`/`prd:` legs
480
+ * (`lifecycle-gather.ts`). The rung CLASSIFIER's signal read MUST see those
481
+ * staged bodies, or `needsAnswers` reads back `undefined`, the classifier
482
+ * mis-routes the item to the BUILD rung, and claim dies with "not found on
483
+ * origin/main" (observation
484
+ * `advance-task-folder-set-omits-tasks-backlog-staged-surface-items-misroute-to-build`).
485
+ * This is the staging-inclusive set its sibling `apply-persist.ts`
486
+ * (`APPLY_LIFECYCLE_FOLDERS`) already uses — kept in step here. BUILD/claim
487
+ * eligibility is UNCHANGED (still pool-only; staging items stay non-claimable):
488
+ * only the rung-classifier's frontmatter-source folders widen.
489
+ */
490
+ const FOLDERS_FOR_TYPE: Record<SidecarType, readonly WorkFolderKey[]> = {
491
+ task: ['tasks-backlog', 'tasks-ready', 'in-progress', 'done'],
492
+ // `spec` rests in the parent-spec regime folders (a `spec:<slug>` legs
493
+ // frontmatter-source read resolves against these).
494
+ spec: ['specs-proposed', 'specs-ready', 'specs-tasked'],
495
+ observation: ['observations'],
496
+ };
497
+
498
+ /** Read `needsAnswers` off the FIRST `work/<folder>/<slug>.md` that exists. */
499
+ function readNeedsAnswers(
500
+ repoPath: string,
501
+ type: SidecarType,
502
+ slug: string,
503
+ ): boolean | undefined {
504
+ for (const folder of FOLDERS_FOR_TYPE[type]) {
505
+ const abs = workItemPath(repoPath, folder, slug);
506
+ if (existsSync(abs)) {
507
+ return parseFrontmatter(readFileSync(abs, 'utf8')).needsAnswers;
508
+ }
509
+ }
510
+ return undefined;
511
+ }
512
+
513
+ /**
514
+ * The PRODUCTION rung executor: build/task rungs ORCHESTRATE the existing
515
+ * `do`/`do prd:` machinery ({@link performDo}); the `surface`/`apply`/
516
+ * `triage-observation` rung bodies are filled by their own tasks
517
+ * ({@link surfaceRung} / {@link applyRung} / {@link triageRung}). It NEVER
518
+ * 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
520
+ * 2026-06-09 UPDATE confirms routes through `performIntegration`).
521
+ */
522
+ export const defaultRungExecutor: RungExecutor = {
523
+ async buildTask(input) {
524
+ return orchestrateDo(input);
525
+ },
526
+ async taskSpec(input) {
527
+ return orchestrateDo(input);
528
+ },
529
+ async triageObservation(input) {
530
+ return triageRung(input);
531
+ },
532
+ async surface(input) {
533
+ return surfaceRung(input);
534
+ },
535
+ async apply(input) {
536
+ return applyRung(input);
537
+ },
538
+ };
539
+
540
+ /**
541
+ * ORCHESTRATE `do`/`do spec:` for the build-task / task-spec rungs: hand the
542
+ * resolved namespaced identity to {@link performDo} (the ONE build path / ONE
543
+ * task path). `advance` is a driver ON TOP — it does NOT duplicate `do`. The
544
+ * `do` outcome is mapped back onto the tick's outcome surface.
545
+ */
546
+ async function orchestrateDo(input: RungExecInput): Promise<RungExecResult> {
547
+ const {item, context} = input;
548
+ const base = context.doOptions;
549
+ if (base === undefined) {
550
+ // The skeleton can classify + lock + DISPATCH without `do` options wired
551
+ // (the driver task threads them). Report it honestly rather than crash —
552
+ // the orchestration TARGET is `performDo`, named here, not re-implemented.
553
+ return {
554
+ exitCode: 1,
555
+ outcome: 'usage-error',
556
+ message:
557
+ `advance would ORCHESTRATE \`do ${item}\` for this rung, but no \`do\` ` +
558
+ `options were threaded into the tick (the driver task wires them).`,
559
+ };
560
+ }
561
+ // The ORCHESTRATION TARGET is `performDo` by DEFAULT (in-place, the cwd checkout
562
+ // IS the isolation), or the injected {@link AdvanceContext.doDriver} — the
563
+ // registry-set advance driver threads a PER-MIRROR JOB-WORKTREE driver so the
564
+ // daemon/CI build runs isolated off the mirror's arbiter. Either way `advance`
565
+ // ORCHESTRATES `do` (the ONE build path / ONE task path) — it does NOT duplicate it.
566
+ const driver = context.doDriver ?? performDo;
567
+ const result: DoResult = await driver({...base, arg: item});
568
+ return {
569
+ exitCode: result.exitCode,
570
+ outcome: result.exitCode === 0 ? 'advanced' : mapDoOutcome(result),
571
+ message: result.message,
572
+ };
573
+ }
574
+
575
+ /** Map a non-zero `do` result onto the tick's outcome vocabulary. */
576
+ function mapDoOutcome(result: DoResult): AdvanceOutcome {
577
+ switch (result.outcome) {
578
+ case 'lost':
579
+ return 'lost';
580
+ case 'contended':
581
+ return 'contended';
582
+ default:
583
+ return 'usage-error';
584
+ }
585
+ }
586
+
587
+ /**
588
+ * The SURFACE rung BODY (task `advance-rung-surface`, US #32/33): the FIRST rung
589
+ * filling the executor seam, establishing the spawn→emit→persist pattern the
590
+ * other rung bodies reuse. Under the `advancing` CAS lock (held by
591
+ * {@link performAdvance} BEFORE this runs — so the expensive spawn is POST-lock,
592
+ * winner-only), it:
593
+ *
594
+ * 1. spawns a FRESH-CONTEXT agent with `surface-questions` loaded (the
595
+ * {@link SurfaceGate} seam, mirroring the review gate's `review` spawn) and
596
+ * collects the EMITTED questions — the skill JUDGES, writes nothing; and
597
+ * 2. has the ENGINE ITSELF write/append them to the sidecar CAS-atomically AND
598
+ * set `needsAnswers:true` in the SAME commit
599
+ * ({@link persistSurfacedQuestions}) — the engine PERSISTS.
600
+ *
601
+ * Append-never-overwrite: a re-surface ADDS `qN+1` and flips a previously-all-
602
+ * answered sidecar back to not-all-answered (the persist owns that). An EMPTY
603
+ * emit (the skill's honest "no open judgement") writes nothing and reports it.
604
+ */
605
+ async function surfaceRung(input: RungExecInput): Promise<RungExecResult> {
606
+ const {item, context} = input;
607
+ const note = context.note ?? (() => {});
608
+ const cwd = context.cwd;
609
+
610
+ // Locate the item file (the only thing the persist needs beyond the questions:
611
+ // the file to set `needsAnswers:true` on). The sidecar path is identity-derived,
612
+ // not folder-derived, so only the ITEM file's folder must be found.
613
+ const itemPath = findItemPath(cwd, input.namespace, input.slug);
614
+ if (itemPath === undefined) {
615
+ return vanishedSkip({rung: 'surface', item});
616
+ }
617
+
618
+ // 1. SPAWN the fresh-context `surface-questions` agent (the skill JUDGES). The
619
+ // expensive model work is POST-lock (the lock is held by `performAdvance`).
620
+ const gate = context.surfaceGate ?? harnessSurfaceGate();
621
+ 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
+ };
635
+ }
636
+
637
+ // 2. The ENGINE persists (the skill wrote nothing): append-or-create the sidecar
638
+ // + set `needsAnswers:true` in ONE commit (CAS-atomic under the held lock).
639
+ const persist = context.surfacePersist ?? persistSurfacedQuestions;
640
+ const result = persist({
641
+ cwd,
642
+ item,
643
+ itemPath,
644
+ questions: toNewQuestions(emit),
645
+ note,
646
+ });
647
+ 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
+ }
662
+ return {
663
+ exitCode: 0,
664
+ outcome: 'no-op',
665
+ message: `surface ${item}: no open judgement — nothing surfaced.`,
666
+ };
667
+ }
668
+ return {
669
+ exitCode: 0,
670
+ outcome: 'advanced',
671
+ message:
672
+ `surfaced ${result.entryCount} question(s) for ${item} → ${result.sidecarPath} ` +
673
+ `(needsAnswers:true, CAS-atomic).`,
674
+ };
675
+ }
676
+
677
+ /**
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:
682
+ *
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).
688
+ *
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.
693
+ */
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
+ );
712
+ }
713
+
714
+ /**
715
+ * The observation TRIAGE rung BODY (task `advance-rung-triage`, US #16/17/23):
716
+ * the rung the classifier picks for an UNTRIAGED observation (`needsAnswers` not
717
+ * set, no sidecar). It is QUESTION-GATED BY DEFAULT: it surfaces a promote/keep/
718
+ * delete question and WAITS — so "is this worth building?" is NEVER decided
719
+ * autonomously. A CONSERVATIVE `observationTriage: 'auto'`-gated EXCEPTION (US #17,
720
+ * high bar) may auto-disposition ONLY the no-question cases:
721
+ *
722
+ * - **default (question-gated):** delegate to {@link surfaceRung} — spawn the
723
+ * `surface-questions` agent (it emits a PLAIN "what becomes of this signal?"
724
+ * question — NO disposition token any more, task
725
+ * `agentic-apply-retire-disposition-vocabulary`) and the ENGINE persists the
726
+ * sidecar + `needsAnswers`. When the human answers, the AGENTIC apply decision
727
+ * (not a stamped token) reads the answer + source and chooses what to DO.
728
+ * Surface stays ALWAYS allowed (US #23) — this path runs under `ask`/`off`
729
+ * (and `off` + an explicit `obs:` runs in this `ask`-mode).
730
+ * - **`auto` exception:** ONLY under `observationTriage: 'auto'`, ask the
731
+ * {@link TriageGate} whether the observation is a no-question case. If it emits
732
+ * `auto: true` (`duplicate` → DELETE the redundant note; `map` → unambiguous
733
+ * map onto an existing item), the engine auto-dispositions it WITHOUT a
734
+ * question ({@link autoDispositionObservation}). It NEVER auto-deletes a
735
+ * NON-duplicate (a `duplicate` discharges by deletion because it is a
736
+ * redundant copy of an already-captured signal — nothing is lost) and NEVER
737
+ * auto-promotes a judgement call (`auto: false` ⇒ fall back to the surface
738
+ * question). Promotion is ALWAYS a human answer (the apply path).
739
+ *
740
+ * Under the `advancing` CAS lock (held by {@link performAdvance} BEFORE this runs),
741
+ * so the expensive spawn is POST-lock, winner-only.
742
+ */
743
+ async function triageRung(input: RungExecInput): Promise<RungExecResult> {
744
+ const {item, context} = input;
745
+ const note = context.note ?? (() => {});
746
+ const cwd = context.cwd;
747
+
748
+ // The CONSERVATIVE auto-disposition EXCEPTION — ONLY under `observationTriage:
749
+ // 'auto'`. Under `'ask'`/`'off'`/unset (including `off` + an EXPLICIT obs:<slug>
750
+ // that bypassed the selection gate), EVERY untriaged observation surfaces the
751
+ // question (the always-allowed path), so "worth building?" is never decided
752
+ // autonomously — `off` + explicit runs in the conservative `ask`-mode.
753
+ if (context.observationTriage === 'auto') {
754
+ const itemPath = findItemPath(cwd, input.namespace, input.slug);
755
+ if (itemPath === undefined) {
756
+ return vanishedSkip({rung: 'triage', item});
757
+ }
758
+ const gate = context.triageGate ?? harnessTriageGate();
759
+ let decision;
760
+ try {
761
+ decision = await gate({item, cwd, triageModel: context.triageModel});
762
+ } catch (err) {
763
+ // A gate failure is NOT a reason to auto-dispose — fall back to the SAFE
764
+ // question-gated path (surface the question), never the reverse.
765
+ const detail = err instanceof Error ? err.message : String(err);
766
+ note(
767
+ `triage ${item}: the auto-triage gate produced no usable emit (${detail}); ` +
768
+ 'falling back to the question-gated surface path.',
769
+ );
770
+ decision = {auto: false as const};
771
+ }
772
+ if (decision.auto === true) {
773
+ // A no-question case (duplicate / map) — auto-disposition WITHOUT a
774
+ // question. BOTH discharge the redundant note BY DELETION (a duplicate is a
775
+ // redundant copy; a map is already covered by the item it maps onto). There
776
+ // is no resting `triaged:keep` state any more. NEVER auto-deletes a
777
+ // NON-redundant signal; NEVER auto-promotes.
778
+ const dispose = context.autoDisposition ?? autoDispositionObservation;
779
+ const result = dispose({
780
+ cwd,
781
+ item,
782
+ itemPath,
783
+ kind: decision.kind,
784
+ existing: decision.existing,
785
+ reason: decision.reason,
786
+ note,
787
+ });
788
+ return {exitCode: 0, outcome: 'advanced', message: result.message};
789
+ }
790
+ // `auto: false` ⇒ a judgement call. Fall through to the surface question.
791
+ }
792
+
793
+ // DEFAULT (question-gated): surface a PLAIN "what becomes of this signal?"
794
+ // question + WAIT. This REUSES the surface rung verbatim (the `surface-questions`
795
+ // skill emits the triage question — NO disposition token any more, task
796
+ // `agentic-apply-retire-disposition-vocabulary`); the AGENTIC apply decision
797
+ // reads the human's answer + source and decides what to DO when it is answered.
798
+ return surfaceRung(input);
799
+ }
800
+
801
+ /**
802
+ * The APPLY rung BODY (task `advance-rung-apply`; AGENTIC apply, task
803
+ * `agentic-apply-retire-disposition-vocabulary`): when the classifier says `apply`
804
+ * (ALL sidecar entries answered), apply the HUMAN's answers.
805
+ *
806
+ * For a fully-answered OBSERVATION (and no caller-supplied follow-up batch), the
807
+ * apply rung is now AGENT-DRIVEN: it runs the shared `decide(input, allowedOutcomes)`
808
+ * engine ({@link decide}) over `(the answered question(s) + the SOURCE item + its
809
+ * 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
812
+ * `agentic-apply-mint-adr-route`, which added the {@link mintAdr} route). The
813
+ * verdict ROUTES:
814
+ * - `ask` → the EXISTING append/re-pause loop ({@link applyAnsweredQuestions}
815
+ * with the follow-up appended; `needsAnswers:true` stays, re-pause in one
816
+ * commit);
817
+ * - `adr` → {@link mintAdr} (mint a SELF-CONTAINED ADR into `docs/adr/` + `git
818
+ * rm` the source + sidecar in the SAME atomic commit; the SIBLING route for the
819
+ * off-board target);
820
+ * - `task` / `prd` → {@link promoteObservation} (mint a SELF-CONTAINED artifact +
821
+ * `git rm` the source + sidecar in the SAME atomic commit); the artifact type
822
+ * 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).
825
+ *
826
+ * For a TASK/PRD (answering its OWN open questions) or a caller-supplied follow-up
827
+ * 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
829
+ * lifecycle, not by a question answer).
830
+ *
831
+ * Under the `advancing` CAS lock (held by {@link performAdvance} BEFORE this runs
832
+ * — so the work is POST-lock, winner-only). ALWAYS allowed (no gate). NEVER
833
+ * invents an answer — it applies ONLY the human-authored `answer:` text; a
834
+ * subset-answered sidecar is not even classified `apply` (the classifier NO-OPs),
835
+ * asserted in the persist.
836
+ */
837
+ async function applyRung(input: RungExecInput): Promise<RungExecResult> {
838
+ const {item, context} = input;
839
+ const note = context.note ?? (() => {});
840
+ const cwd = context.cwd;
841
+
842
+ const itemPath = findItemPath(cwd, input.namespace, input.slug);
843
+ if (itemPath === undefined) {
844
+ return vanishedSkip({rung: 'apply', item});
845
+ }
846
+
847
+ // RUNNER-ACTION KIND-CHECK (task `apply-rung-merge-disposition`, prd
848
+ // `land-time-reverify-and-parallel-merge-ceiling`): an answered `kind: merge`
849
+ // sidecar entry is a DETERMINISTIC land action, NOT a content decision. It
850
+ // dispatches HERE (a sibling of the agentic `decide()`), keyed off the
851
+ // question kind + the human's plain `merge | hold | drop` answer, BEFORE the
852
+ // agentic decider runs. The dispatcher invokes the EXISTING land primitive
853
+ // (`performIntegration` with `committedRecovery: true` + `freshWorktreeGate:
854
+ // true`) through the EXISTING per-job worktree seam (`workspace.ts`
855
+ // `createJob` off the hub mirror) — it does NOT re-implement rebase / verify
856
+ // / advance and does NOT improvise a worktree or clone.
857
+ const mergeRoute = await maybeRunMergeAction(input, itemPath);
858
+ if (mergeRoute !== undefined) {
859
+ return mergeRoute;
860
+ }
861
+
862
+ // AGENTIC APPLY for an answered OBSERVATION (the subsumed triage rung): run the
863
+ // shared decision engine over the answer(s) + source, route the verdict. A
864
+ // caller-supplied follow-up batch (`applyFollowups`) bypasses the decision and
865
+ // re-pauses directly (a test/driver hook). TASK/PRD items resolve in place
866
+ // (their own lifecycle), so they skip the decision and fall through to the
867
+ // persist below.
868
+ const runAgenticDecision =
869
+ input.namespace === 'observation' &&
870
+ !(context.applyFollowups && context.applyFollowups.length > 0);
871
+ if (runAgenticDecision) {
872
+ return applyAgenticDecision(input, itemPath);
873
+ }
874
+
875
+ const apply = context.applyPersist ?? applyAnsweredQuestions;
876
+ try {
877
+ const result = apply({
878
+ cwd,
879
+ item,
880
+ itemPath,
881
+ appendQuestions: context.applyFollowups,
882
+ note,
883
+ });
884
+ // Map the apply persist's outcome to the rung's outcome. `vanished` is the
885
+ // F3a clean-exit case (item was gone between capture and write, e.g. a
886
+ // concurrent promote); benign skip, exitCode 0, distinct from `no-op`.
887
+ const mapped: AdvanceOutcome =
888
+ result.outcome === 'repaused'
889
+ ? 'no-op'
890
+ : result.outcome === 'vanished'
891
+ ? 'vanished'
892
+ : 'advanced';
893
+ return {
894
+ exitCode: 0,
895
+ outcome: mapped,
896
+ message: result.message,
897
+ };
898
+ } catch (err) {
899
+ const detail = err instanceof Error ? err.message : String(err);
900
+ return {
901
+ exitCode: 1,
902
+ outcome: 'usage-error',
903
+ message: `apply ${item}: ${detail}`,
904
+ };
905
+ }
906
+ }
907
+
908
+ /**
909
+ * Dispatch an answered MERGE-QUESTION (a sidecar entry stamped `kind: merge` by
910
+ * the merge-question surfacer) through the EXISTING land primitive
911
+ * (`performIntegration` with `committedRecovery: true` + `freshWorktreeGate:
912
+ * true`) via the EXISTING per-job worktree seam (`workspace.ts` `createJob`).
913
+ * The deterministic SIBLING of the agentic `decide()` content-decision (PRD
914
+ * `land-time-reverify-and-parallel-merge-ceiling`, task
915
+ * `apply-rung-merge-disposition`; Stories #15, #16): a merge-acceptance has no
916
+ * judgement content (the human's plain `merge | hold | drop` answer IS the
917
+ * decision; the apply-time re-verify on the rebased tip is the real correctness
918
+ * gate), so the apply rung KIND-CHECKS the sidecar BEFORE the agentic decider.
919
+ *
920
+ * Returns `undefined` when there is no answered `kind: merge` entry to dispatch
921
+ * (the apply rung then proceeds to the existing path — agentic for
922
+ * observations, normal `applyAnsweredQuestions` for task/prd content questions).
923
+ * Returns a {@link RungExecResult} when the dispatcher handled the rung:
924
+ *
925
+ * - `landed` / `already-integrated` ⇒ the kept commit landed on `main` (or
926
+ * was already there); the dispatcher FALLS THROUGH to the normal apply path
927
+ * so the answer is recorded in the item body + the sidecar is resolved.
928
+ * - `refused` ⇒ the LAND was refused (RED re-verify on the rebased tip,
929
+ * rebase conflict, or pre-checkout failure); `performIntegration` routed
930
+ * the item to needs-attention through its own shared seam, so `main` never
931
+ * received a failing tree. The apply rung SHORT-CIRCUITS — the sidecar is
932
+ * LEFT IN PLACE so the open answer stays surfaced for a human follow-up.
933
+ * - `restale` ⇒ `strictMergeApproval` was ON and the merge-base moved
934
+ * between the surfacer's question and this apply; the apply rung appends
935
+ * a follow-up question + re-pauses (the human re-confirms against the new
936
+ * base).
937
+ * - `hold` / `drop` ⇒ no land; fall through to the normal apply path so the
938
+ * answer is recorded in body. The branch stays unmerged; a future
939
+ * surfacer pass may re-emit a merge-question.
940
+ */
941
+ async function maybeRunMergeAction(
942
+ input: RungExecInput,
943
+ itemPath: string,
944
+ ): Promise<RungExecResult | undefined> {
945
+ const {item, context} = input;
946
+ const cwd = context.cwd;
947
+ const note = context.note ?? (() => {});
948
+
949
+ const detected = detectAnsweredMergeAction(cwd, item);
950
+ if (detected === undefined) return undefined;
951
+
952
+ const handler = context.mergeAction ?? performMergeAction;
953
+
954
+ // The dispatcher needs a `workspacesDir` to cut a per-job worktree (the
955
+ // `createJob` seam). When unset (a caller that has not threaded it) we
956
+ // REFUSE cleanly rather than guess: a land without isolation is not the
957
+ // shape this dispatcher promises.
958
+ const workspacesDir = context.workspacesDir;
959
+ if (workspacesDir === undefined && context.mergeAction === undefined) {
960
+ const message =
961
+ `apply ${item}: answered merge-question detected (kind=merge, answer=` +
962
+ `${detected.verb}) but no \`workspacesDir\` is threaded into the apply ` +
963
+ `rung — the dispatcher needs one to cut the per-job worktree via ` +
964
+ `\`workspace.ts\` \`createJob\`. NOT landing; the answer stays surfaced.`;
965
+ note(message);
966
+ return {exitCode: 1, outcome: 'usage-error', message};
967
+ }
968
+
969
+ let result: MergeActionResult;
970
+ try {
971
+ result = await handler({
972
+ action: detected,
973
+ item,
974
+ slug: input.slug,
975
+ cwd,
976
+ arbiter: context.arbiter ?? DEFAULT_ARBITER,
977
+ arbiterUrl: context.arbiterUrl,
978
+ workspacesDir: workspacesDir ?? '',
979
+ prepare: context.prepare,
980
+ verify: context.verify,
981
+ strictMergeApproval: context.strictMergeApproval,
982
+ note,
983
+ });
984
+ } catch (err) {
985
+ const detail = err instanceof Error ? err.message : String(err);
986
+ const message =
987
+ `apply ${item}: answered merge-question dispatch raised (${detail}); NOT ` +
988
+ `landing; the answer stays surfaced.`;
989
+ note(message);
990
+ return {exitCode: 1, outcome: 'usage-error', message};
991
+ }
992
+
993
+ if (result.outcome === 'refused') {
994
+ // `main` never received a failing tree (performIntegration routed the
995
+ // bounce to needs-attention through its own shared seam). SHORT-CIRCUIT:
996
+ // leave the sidecar so the open answer stays surfaced — the apply rung
997
+ // MUST NOT also resolve it (the next surfacer / human will follow up).
998
+ note(result.message);
999
+ return {exitCode: 1, outcome: 'usage-error', message: result.message};
1000
+ }
1001
+
1002
+ if (result.outcome === 'restale') {
1003
+ // `strictMergeApproval` re-surface: append a follow-up question and
1004
+ // re-pause. The previous answer stays recorded in the entry; the human
1005
+ // re-confirms against the new merge-base in the appended question.
1006
+ const apply = context.applyPersist ?? applyAnsweredQuestions;
1007
+ try {
1008
+ const applied = apply({
1009
+ cwd,
1010
+ item,
1011
+ itemPath,
1012
+ appendQuestions: [
1013
+ {
1014
+ question:
1015
+ `Merge-base for \`work/${input.slug}\` moved since your last ` +
1016
+ `answer (strictMergeApproval is ON). Re-confirm: still land?`,
1017
+ context: result.message,
1018
+ default: 'merge | hold | drop',
1019
+ kind: 'merge',
1020
+ },
1021
+ ],
1022
+ note,
1023
+ });
1024
+ return {
1025
+ exitCode: 0,
1026
+ outcome: 'no-op',
1027
+ message: applied.message,
1028
+ };
1029
+ } catch (err) {
1030
+ const detail = err instanceof Error ? err.message : String(err);
1031
+ return {
1032
+ exitCode: 1,
1033
+ outcome: 'usage-error',
1034
+ message: `apply ${item}: re-surfacing the merge-question failed (${detail}).`,
1035
+ };
1036
+ }
1037
+ }
1038
+
1039
+ // landed | already-integrated | hold | drop: the dispatcher's action is
1040
+ // done; FALL THROUGH to the normal apply path so the answer is recorded in
1041
+ // the item body + the sidecar is resolved.
1042
+ note(result.message);
1043
+ const apply = context.applyPersist ?? applyAnsweredQuestions;
1044
+ try {
1045
+ const applied = apply({cwd, item, itemPath, note});
1046
+ const mapped: AdvanceOutcome =
1047
+ applied.outcome === 'repaused'
1048
+ ? 'no-op'
1049
+ : applied.outcome === 'vanished'
1050
+ ? 'vanished'
1051
+ : 'advanced';
1052
+ return {
1053
+ exitCode: 0,
1054
+ outcome: mapped,
1055
+ message: `${result.message} ${applied.message}`,
1056
+ };
1057
+ } catch (err) {
1058
+ const detail = err instanceof Error ? err.message : String(err);
1059
+ return {
1060
+ exitCode: 1,
1061
+ outcome: 'usage-error',
1062
+ message: `apply ${item}: ${detail}`,
1063
+ };
1064
+ }
1065
+ }
1066
+
1067
+ /**
1068
+ * The cross-tick-window BENIGN SKIP shared by all three rungs that need to
1069
+ * resolve an item file (surface / triage / apply). The lifecycle pool enumerated
1070
+ * the item at scan-time, but by the time this leg ran a sibling parallel leg had
1071
+ * already triaged/settled/deleted it. At the ~33-way CI matrix scale this is a
1072
+ * CALM, EXPECTED condition — making it an exit-1 "a human must reconcile" turned
1073
+ * the matrix into a wall of red. It is now a `vanished` outcome (`exitCode: 0`,
1074
+ * distinguishable from `no-op`) carrying a clear message naming the rung + item.
1075
+ * (See task `observation-identity-is-its-filename-not-a-foreign-slug`.)
1076
+ */
1077
+ function vanishedSkip(input: {
1078
+ rung: 'surface' | 'triage' | 'apply';
1079
+ item: string;
1080
+ }): RungExecResult {
1081
+ return {
1082
+ exitCode: 0,
1083
+ outcome: 'vanished',
1084
+ message:
1085
+ `advance classified the '${input.rung}' rung for ${input.item} but its ` +
1086
+ `item file was gone from work/ by the time the leg ran (a sibling leg ` +
1087
+ `likely triaged/settled/deleted it between enumerate and run) — benign ` +
1088
+ `skip.`,
1089
+ };
1090
+ }
1091
+
1092
+ /**
1093
+ * Find the item file `work/<folder>/<slug>.md` for a type, across the lifecycle
1094
+ * folders it may rest in (the SAME folder set {@link readNeedsAnswers} searches).
1095
+ * Returns the path RELATIVE to `cwd`, or `undefined` when no file exists.
1096
+ */
1097
+ function findItemPath(
1098
+ cwd: string,
1099
+ namespace: SlugNamespace,
1100
+ slug: string,
1101
+ ): string | undefined {
1102
+ const type = sidecarTypeFor(namespace);
1103
+ for (const folder of FOLDERS_FOR_TYPE[type]) {
1104
+ const rel = workItemRel(folder, `${slug}.md`);
1105
+ if (existsSync(join(cwd, rel))) {
1106
+ return rel;
1107
+ }
1108
+ }
1109
+ return undefined;
1110
+ }
1111
+
1112
+ /**
1113
+ * The AGENTIC apply DECISION for a fully-answered OBSERVATION (task
1114
+ * `agentic-apply-retire-disposition-vocabulary`): run the shared
1115
+ * `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
1117
+ * VERDICT, NOT a human `promote-*` field (which is retired). Replaces the old
1118
+ * `answeredPromoteArtifact` + disposition picker.
1119
+ *
1120
+ * - `ask` → append the follow-up question(s) + re-pause (the EXISTING loop, via
1121
+ * {@link applyAnsweredQuestions}'s `appendQuestions`);
1122
+ * - `task` / `prd` → {@link promoteObservation} (mint self-contained + delete
1123
+ * source in the same atomic commit);
1124
+ * - `adr` → {@link mintAdr} (mint a self-contained ADR into `docs/adr/` + delete
1125
+ * source in the same atomic commit; the SIBLING route for the off-board target,
1126
+ * 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).
1129
+ *
1130
+ * The allowed set is `{task | prd | adr | delete | ask}`; a verdict outside it is
1131
+ * rejected by the engine's allowed-outcome guard ({@link DisallowedOutcomeError})
1132
+ * and mapped onto a usage-error — never dispatched.
1133
+ */
1134
+ async function applyAgenticDecision(
1135
+ input: RungExecInput,
1136
+ itemPath: string,
1137
+ ): Promise<RungExecResult> {
1138
+ const {item, context} = input;
1139
+ const note = context.note ?? (() => {});
1140
+ const cwd = context.cwd;
1141
+
1142
+ const decisionInput = buildApplyDecisionInput({
1143
+ item,
1144
+ type: sidecarTypeFor(input.namespace),
1145
+ itemPath,
1146
+ cwd,
1147
+ model: context.applyModel,
1148
+ });
1149
+ if (decisionInput === undefined) {
1150
+ // No sidecar / item to decide over (a sibling leg removed it between classify
1151
+ // and run) — the same benign clean-exit the persist's vanished branch gives.
1152
+ return vanishedSkip({rung: 'apply', item});
1153
+ }
1154
+
1155
+ const decider = context.applyDecide ?? harnessApplyDecider();
1156
+ let verdict: DecisionVerdict;
1157
+ try {
1158
+ verdict = await decide(decisionInput, decider, APPLY_ALLOWED_OUTCOMES);
1159
+ } catch (err) {
1160
+ const detail = err instanceof Error ? err.message : String(err);
1161
+ // A DisallowedOutcomeError (e.g. a stubbed `adr` verdict) and an agent-failed
1162
+ // parse both degrade HONESTLY onto a usage-error — never a silent dispatch.
1163
+ const label =
1164
+ err instanceof DisallowedOutcomeError
1165
+ ? `apply ${item}: the decision verdict is not an allowed outcome (${detail})`
1166
+ : `apply ${item}: the decision agent produced no usable verdict (${detail})`;
1167
+ return {exitCode: 1, outcome: 'usage-error', message: label};
1168
+ }
1169
+
1170
+ if (verdict.outcome === 'ask') {
1171
+ // ask-follow-up → the EXISTING append/re-pause loop. One BATCH of follow-ups.
1172
+ const apply = context.applyPersist ?? applyAnsweredQuestions;
1173
+ const question = (verdict.question ?? '').trim();
1174
+ if (question === '') {
1175
+ return {
1176
+ exitCode: 1,
1177
+ outcome: 'usage-error',
1178
+ message: `apply ${item}: the decision agent chose 'ask' but emitted no follow-up question.`,
1179
+ };
1180
+ }
1181
+ try {
1182
+ const result = apply({
1183
+ cwd,
1184
+ item,
1185
+ itemPath,
1186
+ appendQuestions: [{question}],
1187
+ note,
1188
+ });
1189
+ return {
1190
+ exitCode: 0,
1191
+ outcome: result.outcome === 'vanished' ? 'vanished' : 'no-op',
1192
+ message: result.message,
1193
+ };
1194
+ } catch (err) {
1195
+ const detail = err instanceof Error ? err.message : String(err);
1196
+ return {
1197
+ exitCode: 1,
1198
+ outcome: 'usage-error',
1199
+ message: `apply ${item}: ${detail}`,
1200
+ };
1201
+ }
1202
+ }
1203
+
1204
+ if (verdict.outcome === 'task' || verdict.outcome === 'spec') {
1205
+ // mint-task / mint-spec → CAS-create a SELF-CONTAINED artifact + delete the
1206
+ // source + sidecar in the SAME commit (delete-on-promote, preserved). The
1207
+ // verdict's drafted body (when present) seeds the new item; else the writer
1208
+ // builds a self-contained body FROM the observation (carrying the answers +
1209
+ // open-question scoping). The artifact type is the agent's VERDICT.
1210
+ const promote = context.promote ?? promoteObservation;
1211
+ const draftedBody =
1212
+ verdict.outcome === 'task' ? verdict.taskBody : verdict.specBody;
1213
+ const draftedSlug =
1214
+ verdict.outcome === 'task' ? verdict.taskSlug : verdict.specSlug;
1215
+ try {
1216
+ const result = await promote({
1217
+ cwd,
1218
+ item,
1219
+ itemPath,
1220
+ artifact: verdict.outcome,
1221
+ newSlug: context.promoteSlug ?? draftedSlug,
1222
+ ...(draftedBody !== undefined && draftedBody.trim() !== ''
1223
+ ? {stubContent: draftedBody}
1224
+ : {}),
1225
+ arbiter: context.arbiter,
1226
+ // Lifecycle FAN-OUT: widen the CAS contention budget + jitter the retries
1227
+ // so N parallel promote legs desync (task
1228
+ // `jitter-and-widen-cas-contention-retry-for-lifecycle-fanout`).
1229
+ contention: LIFECYCLE_CAS_CONTENTION,
1230
+ note,
1231
+ });
1232
+ return {
1233
+ exitCode: result.exitCode,
1234
+ outcome:
1235
+ result.outcome === 'promoted'
1236
+ ? 'advanced'
1237
+ : result.outcome === 'lost'
1238
+ ? 'lost'
1239
+ : result.outcome === 'contended'
1240
+ ? 'contended'
1241
+ : 'usage-error',
1242
+ message: result.message,
1243
+ };
1244
+ } catch (err) {
1245
+ const detail = err instanceof Error ? err.message : String(err);
1246
+ return {
1247
+ exitCode: 1,
1248
+ outcome: 'usage-error',
1249
+ message: `apply ${item}: ${detail}`,
1250
+ };
1251
+ }
1252
+ }
1253
+
1254
+ if (verdict.outcome === 'adr') {
1255
+ // mint-adr → CAS-create a SELF-CONTAINED ADR into `docs/adr/` + delete the
1256
+ // source + sidecar in the SAME commit (delete-on-promote, preserved via the
1257
+ // shared create-CAS). An ADR lives OUTSIDE the work board, so this is the
1258
+ // SIBLING route (NOT a `promoteObservation` artifact type). The verdict's
1259
+ // drafted body (when present) seeds the ADR; else `mintAdr` builds a
1260
+ // self-contained body FROM the observation + the answered question(s).
1261
+ const mint = context.mintAdr ?? mintAdr;
1262
+ const answers = decisionInput.sidecar.entries.map((e) => ({
1263
+ question: e.question,
1264
+ answer: e.answer,
1265
+ }));
1266
+ try {
1267
+ const result = await mint({
1268
+ cwd,
1269
+ item,
1270
+ itemPath,
1271
+ adrSlug: context.promoteSlug ?? verdict.adrSlug,
1272
+ ...(verdict.adrTitle !== undefined ? {adrTitle: verdict.adrTitle} : {}),
1273
+ ...(verdict.adrBody !== undefined && verdict.adrBody.trim() !== ''
1274
+ ? {adrBody: verdict.adrBody}
1275
+ : {}),
1276
+ answers,
1277
+ arbiter: context.arbiter,
1278
+ // Lifecycle FAN-OUT: widen the CAS contention budget + jitter the retries
1279
+ // so N parallel mint-adr legs desync (task
1280
+ // `jitter-and-widen-cas-contention-retry-for-lifecycle-fanout`).
1281
+ contention: LIFECYCLE_CAS_CONTENTION,
1282
+ note,
1283
+ });
1284
+ return {
1285
+ exitCode: result.exitCode,
1286
+ outcome:
1287
+ result.outcome === 'minted'
1288
+ ? 'advanced'
1289
+ : result.outcome === 'lost'
1290
+ ? 'lost'
1291
+ : result.outcome === 'contended'
1292
+ ? 'contended'
1293
+ : 'usage-error',
1294
+ message: result.message,
1295
+ };
1296
+ } catch (err) {
1297
+ const detail = err instanceof Error ? err.message : String(err);
1298
+ return {
1299
+ exitCode: 1,
1300
+ outcome: 'usage-error',
1301
+ message: `apply ${item}: ${detail}`,
1302
+ };
1303
+ }
1304
+ }
1305
+
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.
1309
+ const apply = context.applyPersist ?? applyAnsweredQuestions;
1310
+ try {
1311
+ const result = apply({
1312
+ cwd,
1313
+ item,
1314
+ itemPath,
1315
+ discharge: {reason: verdict.deleteReason ?? ''},
1316
+ note,
1317
+ });
1318
+ return {
1319
+ exitCode: 0,
1320
+ outcome: result.outcome === 'vanished' ? 'vanished' : 'advanced',
1321
+ message: result.message,
1322
+ };
1323
+ } catch (err) {
1324
+ const detail = err instanceof Error ? err.message : String(err);
1325
+ return {
1326
+ exitCode: 1,
1327
+ outcome: 'usage-error',
1328
+ message: `apply ${item}: ${detail}`,
1329
+ };
1330
+ }
1331
+ }
1332
+
1333
+ /**
1334
+ * Run ONE `advance` tick over a SINGLE named item: classify → lock → dispatch →
1335
+ * release. The pure tick the drivers (later tasks) wrap. The expensive phase is
1336
+ * ALWAYS post-lock — a CAS loser backs off having done ONLY the free
1337
+ * classification (it never reaches the executor).
1338
+ */
1339
+ export async function performAdvance(
1340
+ options: AdvanceOptions,
1341
+ ): Promise<AdvanceResult> {
1342
+ const note = options.note ?? (() => {});
1343
+ const cwd = options.cwd;
1344
+ const repoPath = options.repoPath ?? cwd;
1345
+ const arbiter = options.arbiter ?? DEFAULT_ARBITER;
1346
+
1347
+ // {@link performAdvance} is the SINGLE-item tick. The bare `advance`
1348
+ // (eligible-SET) form is the DRIVER's job ({@link performAdvanceAuto} in
1349
+ // `advance-drivers.ts`, which selects over the pool + runs THIS tick per item) —
1350
+ // the tick itself REQUIRES a named item, so an empty arg here is a usage error
1351
+ // (the CLI dispatches the bare form to the driver before reaching here).
1352
+ if (options.arg === undefined || options.arg.trim() === '') {
1353
+ const message =
1354
+ '`advance` with no item is the eligible-SET form (the one-shot driver). ' +
1355
+ 'The single-item tick needs a named item: ' +
1356
+ '`advance <slug>` / `advance prd:<slug>` / `advance obs:<slug>`.';
1357
+ note(message);
1358
+ return {exitCode: 1, outcome: 'usage-error', message};
1359
+ }
1360
+
1361
+ // 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.
1363
+ let resolved;
1364
+ try {
1365
+ resolved = resolveAdvanceArg({
1366
+ arg: options.arg,
1367
+ repoPath,
1368
+ read: options.read ?? ledgerRead,
1369
+ });
1370
+ } catch (err) {
1371
+ if (err instanceof SlugResolutionError) {
1372
+ return {exitCode: 1, outcome: 'usage-error', message: err.message};
1373
+ }
1374
+ const message = err instanceof Error ? err.message : String(err);
1375
+ return {exitCode: 1, outcome: 'usage-error', message};
1376
+ }
1377
+
1378
+ const type = sidecarTypeFor(resolved.namespace);
1379
+ const item = `${resolved.namespace}:${resolved.slug}`;
1380
+
1381
+ // 2. CLASSIFY — read-only, NO model, NO lock. Read the two signals + run the
1382
+ // pure classifier. This is the ONLY work a CAS loser will have spent.
1383
+ const readSignals = options.readSignals ?? readItemSignals;
1384
+ const signals = readSignals({repoPath, type, slug: resolved.slug, item});
1385
+ const classification = classifyTick({
1386
+ type,
1387
+ needsAnswers: signals.needsAnswers,
1388
+ sidecar: signals.sidecar,
1389
+ });
1390
+
1391
+ // A NO-OP (pending sidecar / nothing eligible) or an invariant violation never
1392
+ // takes the lock — there is nothing to execute, so do NOT pay the CAS.
1393
+ if (classification.kind === 'no-op') {
1394
+ const message = `no-op for ${item} (${classification.reason ?? 'nothing to advance'}).`;
1395
+ note(message);
1396
+ return {
1397
+ exitCode: 0,
1398
+ outcome: 'no-op',
1399
+ rung: 'no-op',
1400
+ slug: resolved.slug,
1401
+ message,
1402
+ };
1403
+ }
1404
+ if (classification.kind === 'invariant-violation') {
1405
+ const message =
1406
+ `refusing to advance ${item}: the \`needsAnswers\` flag and the sidecar ` +
1407
+ `disagree (${classification.reason ?? 'invariant violation'}). ` +
1408
+ `A human must reconcile them.`;
1409
+ note(message);
1410
+ return {
1411
+ exitCode: 1,
1412
+ outcome: 'invariant-violation',
1413
+ rung: 'invariant-violation',
1414
+ slug: resolved.slug,
1415
+ message,
1416
+ };
1417
+ }
1418
+
1419
+ // 3. LOCK — take the `advancing` CAS borrow for the classified rung, keyed on
1420
+ // the item's `<type>-<slug>` identity. The expensive phase is POST-lock.
1421
+ //
1422
+ // UNIFIED PER-ITEM LOCK, TREE-LESS RUNGS ONLY (prd
1423
+ // `ledger-status-per-item-lock-refs` US #1/#3/#18; ADR
1424
+ // `ledger-status-on-per-item-lock-refs`). The rung kind is KNOWN here
1425
+ // (`classification.kind`, classified pre-lock), so the tree-less-only policy
1426
+ // lives HERE — where the rung is known — and `advancing-lock.ts` stays
1427
+ // rung-agnostic (it only learns "unified or not" via `acquireUnified`). For a
1428
+ // TREE-LESS rung (`surface`/`apply`/`triage-observation`) the advancing acquire
1429
+ // ALSO takes the item's unified lock (`action: advance`) — these rungs have NO
1430
+ // 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
1432
+ // at the advance layer: `performAdvance` ORCHESTRATES an inner `performDo` that
1433
+ // ITSELF acquires the SAME `task-<slug>`/`prd-<slug>` ref (the create-only CAS
1434
+ // with NO re-entrancy/auto-steal, per the ADR), so taking it here too would
1435
+ // DEADLOCK the tick against itself. The inner `do`'s claim/task lock IS the
1436
+ // single exclusion point for those rungs. The `work/advancing/<entry>.md` marker
1437
+ // CAS is KEPT for ALL rungs (its removal is the capstone task #9).
1438
+ const unifiedForRung = isTreeLessRung(classification.kind);
1439
+ const acquire =
1440
+ options.acquireLock ??
1441
+ ((lockItem: string) =>
1442
+ acquireAdvancingLock({
1443
+ item: lockItem,
1444
+ cwd,
1445
+ arbiter,
1446
+ acquireUnified: unifiedForRung,
1447
+ note,
1448
+ }));
1449
+ const lock = await acquire(item);
1450
+ if (lock.exitCode !== 0) {
1451
+ // A CAS LOSER (exit 2) or contended (exit 3) backs off having spent ONLY the
1452
+ // free classification above — it never reaches the executor.
1453
+ return {
1454
+ exitCode: lock.exitCode,
1455
+ outcome: lock.outcome === 'lost' ? 'lost' : 'contended',
1456
+ rung: classification.kind,
1457
+ slug: resolved.slug,
1458
+ message: lock.message,
1459
+ };
1460
+ }
1461
+
1462
+ // 4. EXECUTE — WINNER ONLY: dispatch the classified rung to the executor seam,
1463
+ // then ALWAYS release the borrow (the item never moved; release is clean).
1464
+ const executor = options.executor ?? defaultRungExecutor;
1465
+ const release =
1466
+ options.releaseLock ??
1467
+ ((lockItem: string) =>
1468
+ releaseAdvancingLock({
1469
+ item: lockItem,
1470
+ cwd,
1471
+ arbiter,
1472
+ releaseUnified: unifiedForRung,
1473
+ note,
1474
+ }));
1475
+ try {
1476
+ const exec = await dispatchRung(executor, {
1477
+ item,
1478
+ namespace: resolved.namespace,
1479
+ slug: resolved.slug,
1480
+ classification,
1481
+ context: {
1482
+ cwd,
1483
+ arbiter,
1484
+ doOptions: options.doOptions,
1485
+ doDriver: options.doDriver,
1486
+ surfaceGate: options.surfaceGate,
1487
+ surfaceModel: options.surfaceModel,
1488
+ surfacePersist: options.surfacePersist,
1489
+ applyPersist: options.applyPersist,
1490
+ applyFollowups: options.applyFollowups,
1491
+ applyDecide: options.applyDecide,
1492
+ applyModel: options.applyModel,
1493
+ observationTriage: options.observationTriage,
1494
+ triageGate: options.triageGate,
1495
+ triageModel: options.triageModel,
1496
+ autoDisposition: options.autoDisposition,
1497
+ promote: options.promote,
1498
+ mintAdr: options.mintAdr,
1499
+ promoteSlug: options.promoteSlug,
1500
+ // The answered-merge LAND dispatch context (task
1501
+ // `apply-rung-merge-disposition`): threaded into the apply rung's
1502
+ // kind-check for a `kind: merge` runner-action.
1503
+ workspacesDir: options.workspacesDir,
1504
+ arbiterUrl: options.arbiterUrl,
1505
+ prepare: options.prepare,
1506
+ verify: options.verify,
1507
+ strictMergeApproval: options.strictMergeApproval,
1508
+ mergeAction: options.mergeAction,
1509
+ note,
1510
+ },
1511
+ });
1512
+ return {
1513
+ exitCode: exec.exitCode,
1514
+ outcome: exec.outcome,
1515
+ rung: classification.kind,
1516
+ slug: resolved.slug,
1517
+ message: exec.message,
1518
+ };
1519
+ } finally {
1520
+ await release(item);
1521
+ }
1522
+ }
1523
+
1524
+ /**
1525
+ * Is this a TREE-LESS rung (`surface`/`apply`/`triage-observation`) — the rungs
1526
+ * that have NO inner `performDo`, so the advancing acquire must ALSO take the
1527
+ * unified per-item lock (`action: advance`) to realise advance∥claim / advance∥task
1528
+ * exclusion? The build/task rungs (`build-task`/`task-spec`) are the inverse:
1529
+ * their inner `do` holds the SAME unified ref, so the advance layer must NOT take
1530
+ * it (it would deadlock the tick against itself). `no-op`/`invariant-violation`
1531
+ * never reach the lock step. This is the single place the tree-less-only policy
1532
+ * is expressed (the rung is known here); `advancing-lock.ts` stays rung-agnostic.
1533
+ */
1534
+ function isTreeLessRung(kind: TickClassification['kind']): boolean {
1535
+ return (
1536
+ kind === 'surface' || kind === 'apply' || kind === 'triage-observation'
1537
+ );
1538
+ }
1539
+
1540
+ /** Dispatch the classified rung to the executor seam (winner-only). */
1541
+ function dispatchRung(
1542
+ executor: RungExecutor,
1543
+ input: RungExecInput,
1544
+ ): Promise<RungExecResult> {
1545
+ switch (input.classification.kind) {
1546
+ case 'build-task':
1547
+ return executor.buildTask(input);
1548
+ case 'task-spec':
1549
+ return executor.taskSpec(input);
1550
+ case 'triage-observation':
1551
+ return executor.triageObservation(input);
1552
+ case 'surface':
1553
+ return executor.surface(input);
1554
+ case 'apply':
1555
+ return executor.apply(input);
1556
+ default:
1557
+ // `no-op` / `invariant-violation` never reach here (handled pre-lock).
1558
+ return Promise.resolve({
1559
+ exitCode: 1,
1560
+ outcome: 'usage-error',
1561
+ message: `unexpected rung kind '${input.classification.kind}' at dispatch.`,
1562
+ });
1563
+ }
1564
+ }