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/tasking.ts ADDED
@@ -0,0 +1,1438 @@
1
+ import {
2
+ existsSync,
3
+ mkdirSync,
4
+ readdirSync,
5
+ readFileSync,
6
+ rmSync,
7
+ writeFileSync,
8
+ } from 'node:fs';
9
+ import {basename, dirname, join} from 'node:path';
10
+ import {parseFrontmatter} from './frontmatter.js';
11
+ import {runAsync, type RunResult} from './git.js';
12
+ import {
13
+ workFolderRel,
14
+ workFolderPath,
15
+ workItemPath,
16
+ workItemRel,
17
+ isWorkItemFile,
18
+ } from './work-layout.js';
19
+ import {
20
+ performIntegration,
21
+ type IntegrationCoreResult,
22
+ } from './integration-core.js';
23
+ import type {IntegrationMode} from './config.js';
24
+ import type {ReviewProvider} from './integrator.js';
25
+ import {
26
+ resolveTaskingEligibility,
27
+ type TaskingEligibilityResult,
28
+ } from './tasking-eligibility.js';
29
+ import {
30
+ acquireTaskingLock,
31
+ releaseTaskingLock,
32
+ type AcquireTaskingLockOptions,
33
+ type AcquireTaskingLockResult,
34
+ type ReleaseTaskingLockOptions,
35
+ type ReleaseTaskingLockResult,
36
+ } from './tasking-lock.js';
37
+ import {releaseItemLock} from './item-lock.js';
38
+ import {NullHarness, type Harness} from './harness.js';
39
+ import {launchWithOptionalWatch} from './agent-launch.js';
40
+ import {placementFolder, resolvePlacement} from './placement.js';
41
+ import {setNeedsAnswersMarker, propagateOrigin} from './frontmatter.js';
42
+ import {workBranchRef} from './slug-namespace.js';
43
+ import {
44
+ runTaskReviewLoop,
45
+ type TaskReviewGate,
46
+ type RunTaskReviewLoopResult,
47
+ } from './tasker-review-loop.js';
48
+ import type {ReviewGate} from './review-gate.js';
49
+
50
+ /**
51
+ * The **`do prd:<slug>` tasking path** (prd `auto-slice`, task
52
+ * `autoslice-command`) — the orchestration that ties the tasking GATE
53
+ * (`tasking-eligibility.ts`) and the tasking LOCK (`tasking-lock.ts`) together to
54
+ * task a prd into `work/tasks/backlog/` STAGED items (task
55
+ * `pre-backlog-staging-folder-and-promote-step-a` — the runner-owned promotion
56
+ * moves them `pre-backlog/ → backlog/` later), with the RUNNER owning every git-state
57
+ * transition. This is the prd branch of the `do` worker (ADR
58
+ * `command-surface-and-journeys.md` §3/§3a), NOT a standalone `task` command;
59
+ * `do.ts` dispatches `resolved.namespace === 'prd'` here.
60
+ *
61
+ * The end-to-end flow (mirroring the `do`/`run` runner-owns-git discipline — the
62
+ * agent only EDITS files, the runner does ALL git):
63
+ *
64
+ * 1. **Resolve the gate** (agent path): refuse to task a prd that is
65
+ * `humanOnly`/`needsAnswers`, or whose `taskedAfter` prds are not yet tasked.
66
+ * The repo's `autoTask` POLICY also refuses on the AUTO-PICK pool path, but
67
+ * NOT when the prd was named EXPLICITLY (`do prd:<slug>`, `explicit: true`):
68
+ * naming it IS the authorization, exactly as `do <task>` builds regardless of
69
+ * `autoBuild` (the pool, not the explicit claim, gates the policy). The HUMAN
70
+ * path is unbound by the gate entirely.
71
+ * 2. **Acquire the lock** (agent path) via the unified per-item lock CAS —
72
+ * serialising concurrent taskers on the `prd:<slug>` ref (the body STAYS in
73
+ * `work/prds/ready/`; the lock no longer moves it). The HUMAN path with no contention
74
+ * may task on `main` directly WITHOUT the lock.
75
+ * 3. **Invoke the agent harness** with the `to-task` prd — the agent runs the
76
+ * tasker methodology and produces `work/tasks/backlog/<slug>.md` FILES ONLY; it does
77
+ * NOT commit/push/move (the same in-band boundary as the build agent).
78
+ * 4. **The runner integrates the COMPLETING transition through the SHARED core**
79
+ * (`performIntegration`, task `slice-output-through-integration`): the agent's
80
+ * tasking runs on a `work/<slug>` branch cut from `<arbiter>/main` (whose base
81
+ * holds the prd in `work/prds/ready/`), and the produced backlog tasks + the durable
82
+ * prd lifecycle move (`work/prds/ready/ → work/prds/tasked/`)
83
+ * integrate via the band honoring `--propose` (push the branch + open a
84
+ * PR, NO `main` touch) / `--merge` (land on `main`). Because the integrate-time
85
+ * args resolve ONCE in the shared core, EVERY `do task:` integrate arg applies
86
+ * to `do prd:` by construction. A content-identity STALE CHECK (the lock's
87
+ * read-stability backstop) fires FIRST against the acquire-time `lockedBlob`,
88
+ * so a concurrent edit of the held prd still fails loud (`stale`).
89
+ *
90
+ * The tasking LOCK (`tasking-lock.ts`, `acquireTaskingLock`/`releaseTaskingLock`)
91
+ * is the UNIFIED per-item lock (`refs/dorfl/lock/<entry>`, `action: task`)
92
+ * — the transient `tasking/` folder marker is RETIRED (task
93
+ * `cutover-retire-slicing-advancing-markers-and-trim-folder-sets`). The lock
94
+ * RELEASE owns the needs-attention redirect for the loop's decomposition-unclear
95
+ * verdict (it amends the lock `active → stuck` — no folder write); only the SUCCESS
96
+ * output integrates through the shared core. This path
97
+ * does NOT build the no-human confidence routing — that is the review/edit loop
98
+ * owned by `slicer-review-edit-loop`; this path produces + integrates the tasks.
99
+ */
100
+
101
+ /** The terminal status of one `do prd:<slug>` tasking run. */
102
+ export type TaskOutcome =
103
+ | 'tasked' // gate passed (agent) / unbound (human) → lock → agent → committed
104
+ | 'gate-refused' // the agent gate refused (humanOnly/needsAnswers/autoTask/taskedAfter)
105
+ | 'lock-lost' // the lock was lost/contended (another tasker holds it)
106
+ | 'agent-failed' // the agent invocation itself errored
107
+ | 'stale' // the held prd was edited under the lock → the tasking is stale
108
+ | 'needs-attention' // the tasker edit loop found the decomposition unclear → prd routed to needs-attention (no tasks)
109
+ | 'usage-error'; // usage / environment problem (missing prd, bad release, …)
110
+
111
+ export interface TaskResult {
112
+ exitCode: 0 | 1 | 2 | 3 | 4;
113
+ outcome: TaskOutcome;
114
+ /** The prd slug acted on. */
115
+ slug: string;
116
+ /** Repo-relative paths of the backlog tasks the runner committed. */
117
+ emitted?: string[];
118
+ /**
119
+ * The tasker review→edit LOOP's disposition (`slicer-review-edit-loop`), when
120
+ * the loop ran. `converged` = the improved tasks landed; `uncertain-tasks` =
121
+ * the cap was hit and specific tasks landed `needsAnswers: true`; absent when
122
+ * no loop ran or the prd was routed to needs-attention (`outcome:
123
+ * 'needs-attention'`).
124
+ */
125
+ loop?: 'converged' | 'uncertain-tasks';
126
+ /** Human-readable summary of the terminal condition. */
127
+ message: string;
128
+ }
129
+
130
+ /**
131
+ * The agent invocation: runs the `to-task` prd in `cwd`, WRITING
132
+ * `work/tasks/backlog/<slug>.md` task files (and trimming the prd). It does NO git —
133
+ * the runner captures the produced files and commits them.
134
+ */
135
+ export type TaskDorfl = (input: {
136
+ cwd: string;
137
+ prompt: string;
138
+ slug: string;
139
+ env?: NodeJS.ProcessEnv;
140
+ }) => {ok: boolean; detail?: string};
141
+
142
+ /** Injectable lock seams (production: the real CAS; tests: stubs). */
143
+ export interface TaskingLockSeam {
144
+ acquire(
145
+ options: AcquireTaskingLockOptions,
146
+ ): Promise<AcquireTaskingLockResult>;
147
+ release(
148
+ options: ReleaseTaskingLockOptions,
149
+ ): Promise<ReleaseTaskingLockResult>;
150
+ }
151
+
152
+ const DEFAULT_LOCK_SEAM: TaskingLockSeam = {
153
+ acquire: acquireTaskingLock,
154
+ release: releaseTaskingLock,
155
+ };
156
+
157
+ export interface PerformTaskOptions {
158
+ /** The prd slug to task (`work/prds/ready/<slug>.md`). */
159
+ slug: string;
160
+ /** The working clone/checkout the tasking runs in. */
161
+ cwd: string;
162
+ /** Name of the arbiter git remote. Defaults to `origin`. */
163
+ arbiter?: string;
164
+ /**
165
+ * The DOER: `'agent'` (the default; bound by the gate, MUST take the lock) or
166
+ * `'human'` (unbound by the gate; with no contention tasks on `main` directly
167
+ * WITHOUT the lock). The human-vs-agent choice the command wires.
168
+ */
169
+ doer?: 'agent' | 'human';
170
+ /** Per-repo `autoTask` policy (resolved by `autoslice-gate`). Agent path only. */
171
+ autoTask?: boolean;
172
+ /**
173
+ * The prd was named EXPLICITLY by the operator (`do prd:<slug>`), so the
174
+ * `autoTask` POLICY is already satisfied — naming the prd IS the authorization,
175
+ * EXACTLY as `do <task>` builds a named task regardless of `autoBuild` (the
176
+ * build path's precedent: `autoBuild` gates the scan/selection POOL only, never
177
+ * `performDo`'s explicit claim). When `true`, the agent tasking gate drops the
178
+ * `autoTask` policy term and binds ONLY the prd's own readiness axes
179
+ * (`humanOnly`/`needsAnswers`) + `taskedAfter`. Defaults `false`. Both the
180
+ * explicit `do prd:` dispatch AND the auto-pick path pass `true` here: the
181
+ * auto-pick POOL (`do-autopick.ts`) is the single `autoTask`-enforcement point
182
+ * (a pool-ineligible prd is never selected), so once a prd is dispatched its
183
+ * policy is already settled. Agent path only.
184
+ */
185
+ explicit?: boolean;
186
+ /**
187
+ * The agent invocation. Tests inject this to write task files directly;
188
+ * production wires the harness seam. When omitted, {@link harness} is used.
189
+ */
190
+ dorfl?: TaskDorfl;
191
+ /** The harness seam used when `dorfl` is omitted; defaults to the null adapter. */
192
+ harness?: Harness;
193
+ /** The configured agent command the harness shells out to (null adapter). */
194
+ agentCmd?: string;
195
+ /** The model routing intent forwarded to the harness (ADR §13). */
196
+ model?: string;
197
+ /** The HOST-ONLY sessions root for the pi session file. */
198
+ sessionsDir?: string;
199
+ /**
200
+ * The integration mode the produced tasks integrate THROUGH the shared core
201
+ * with (task `slice-output-through-integration`): `propose` (default — push the
202
+ * `work/<slug>` branch + open a PR carrying the tasks, NO `main` touch) or
203
+ * `merge` (land them on `main`). Resolved ONCE in {@link performIntegration},
204
+ * so EVERY `do task:` integrate-time arg applies to `do prd:` by construction.
205
+ * The AGENT path only; the human path commits its own output. Defaults to the
206
+ * system default (`propose`).
207
+ */
208
+ integration?: IntegrationMode;
209
+ /**
210
+ * **The PR-INTENT axis** (config `noPR`, ADR §6): when `true`, propose pushes
211
+ * the produced task branch but skips the PR (the explicit suppress-PR intent).
212
+ * NOT a provider choice — the provider is purely arbiter-derived. Unset/false ⇒
213
+ * the PR opens normally.
214
+ */
215
+ noPR?: boolean;
216
+ /**
217
+ * Optional FULLY-FORMED review provider INSTANCE used VERBATIM (the SAME seam
218
+ * `run`/`do` expose; forwarded to `performIntegration` as `providerInstance`).
219
+ * Tests/embeddings inject a stubbed `GitHubProvider` (a custom `gh` path) to
220
+ * drive the propose pipeline OFFLINE. The resolved provider OBJECT, NOT a config
221
+ * override. Unset ⇒ the core selects from the arbiter URL.
222
+ */
223
+ providerInstance?: ReviewProvider;
224
+ /**
225
+ * **The task-SET ACCEPTANCE GATE** (task `slice-acceptance-gate`): the
226
+ * task-path mirror of the build Gate-2, riding {@link performIntegration}'s
227
+ * review-before-integrate block. When `review` resolves on, a FRESH-CONTEXT
228
+ * agent reviews the WHOLE produced task SET (coherence / dependency graph /
229
+ * gaps + overlap / prd-goal correct-if-implemented) BEFORE the tasks integrate;
230
+ * `approve` lands them, `block` routes the set to needs-attention. It is
231
+ * controlled by the BUILD `--review`/`--no-review`/`--review-model` family (ONE
232
+ * gate-configuration story shared with the build path) and is ONE-SHOT —
233
+ * terminal pass/fail, NO rounds (it does NOT inherit `--review-max-rounds`; the
234
+ * caller drives it with a single reviewer invocation). It is DISTINCT from and
235
+ * independently controllable from the tasker improver loop ({@link reviewLoop} /
236
+ * the `--tasker-loop*` family).
237
+ */
238
+ review?: boolean;
239
+ /** The task-SET acceptance-gate SEAM (injectable). Required when `review` is on. */
240
+ reviewGate?: ReviewGate;
241
+ /**
242
+ * The model the task-SET acceptance-gate reviewer runs on (the BUILD
243
+ * `--review-model`, de-correlated from the tasker). DISTINCT from the improver
244
+ * loop's {@link taskerLoopModel} — see the note there.
245
+ */
246
+ acceptanceReviewModel?: string;
247
+ /** Injectable lock seam (tests stub acquire/release). Defaults to the real CAS. */
248
+ lock?: TaskingLockSeam;
249
+ /**
250
+ * **The per-repo TASK-PLACEMENT default** (prd
251
+ * `staging-pool-position-gate-and-trust-model` US #5, task
252
+ * `runner-deterministic-slice-placement-policy-and-precedence`). The
253
+ * resolved per-repo default landing for the tasker's emitted tasks, fed as
254
+ * the CONFIGURED-DEFAULT rung into the runner-deterministic placement
255
+ * resolver (`src/placement.ts`). The resolver overlays an EXPLICIT operator
256
+ * flag ({@link explicitTasksLandIn}, top) and the UNTRUSTED-ORIGIN force
257
+ * (`originTrust: untrusted` ⇒ staging) on top. Unset ⇒ the resolver's
258
+ * built-in floor applies (`staging` = `pre-backlog/`, the conservative
259
+ * landing that preserves zero behaviour change for the normal path).
260
+ */
261
+ tasksLandIn?: 'pre-backlog' | 'ready';
262
+ /**
263
+ * **The OPERATOR's EXPLICIT task-placement override** (the TOP precedence
264
+ * rung). When set, the runner-deterministic resolver lands the tasks HERE
265
+ * regardless of `originTrust` or {@link tasksLandIn} — the positional
266
+ * analogue of `explicitMerge` overriding the untrusted-origin
267
+ * build-propose rule ("the operator is present; CLI always wins, no special
268
+ * force-key"). Set ONLY when the operator typed `--tasks-land-in <where>`;
269
+ * never when the value came from config.
270
+ */
271
+ explicitTasksLandIn?: 'pre-backlog' | 'ready';
272
+ /**
273
+ * **The tasker review→edit→converge LOOP** (`slicer-review-edit-loop`, GATES prd
274
+ * `work/prds/ready/review.md` RESOLVED DESIGN — Shape 2 / insertion point A). When
275
+ * provided, AFTER the agent produces candidate tasks (step 3) and BEFORE the
276
+ * runner finalises them (step 4), run the `review` SKILL as a review→edit→
277
+ * re-review loop that IMPROVES the candidate tasks in place, then routes the
278
+ * verdict through the three outcomes (converge→land / uncertain-task→
279
+ * needsAnswers / decomposition-unclear→prd-to-needs-attention). The seam is the
280
+ * review+edit gate (tests inject a canned verdict+edits; production:
281
+ * {@link harnessTaskReviewGate}). Omitted ⇒ NO loop (the candidate tasks land
282
+ * as-is — the pre-loop behaviour). The HUMAN path is unaffected (the loop runs
283
+ * on the auto-tasker's output only — see the gating in {@link performTask}).
284
+ */
285
+ reviewLoop?: TaskReviewGate;
286
+ /**
287
+ * The HARD CAP on the tasker improver loop's in-context review passes (N) —
288
+ * resolved per-repo (flag `--tasker-loop-max` > env > per-repo > global > cheap
289
+ * default). Only consulted when {@link reviewLoop} is set. Defaults to 3 (the
290
+ * cheap default) when omitted.
291
+ */
292
+ taskerLoopMax?: number;
293
+ /**
294
+ * How many fresh-context EXECUTIONS (M) of the loop to run — each a NEW launch in
295
+ * a fresh context. Default 1 (the cheap degenerate case). Only consulted when
296
+ * {@link reviewLoop} is set.
297
+ */
298
+ reviewExecutions?: number;
299
+ /**
300
+ * The model the IMPROVER loop's review agent runs on (de-correlated from the
301
+ * tasker; the `--tasker-loop-model` family). Loop only. DISTINCT from the
302
+ * acceptance gate's {@link acceptanceReviewModel} (build `--review-model`).
303
+ */
304
+ taskerLoopModel?: string;
305
+ /** Environment for child GIT/provider processes (the identity-scoped env). */
306
+ env?: NodeJS.ProcessEnv;
307
+ /**
308
+ * Environment for the AGENT launches (the tasker agent + the review/improver
309
+ * loop's review agent). Distinct from {@link env}: an AGENT must NOT carry the
310
+ * runner identity (only the runner's git transitions do). Unset ⇒ falls back to
311
+ * {@link env} (byte-for-byte unchanged for non-identity callers).
312
+ */
313
+ agentEnv?: NodeJS.ProcessEnv;
314
+ /** Sink for human-readable progress notes. */
315
+ note?: (message: string) => void;
316
+ }
317
+
318
+ const DEFAULT_ARBITER = 'origin';
319
+
320
+ /**
321
+ * **The STAGED-TASKS dir** (prd `staging-pool-position-gate-and-trust-model`,
322
+ * task `pre-backlog-staging-folder-and-promote-step-a`, governing ADR
323
+ * `placement-is-runner-deterministic-humanonly-is-agent-judgement`). The runner
324
+ * lands the tasker's emitted task files HERE, NOT in `work/tasks/ready/`: an item
325
+ * born in `pre-backlog/` is durable + readable but NOT in the agent-eligible
326
+ * pool (`work/tasks/ready/` STILL means the pool — every reader is byte-for-byte
327
+ * unchanged). A runner/human-owned promotion (`promoteFromPreBacklog` in
328
+ * `needs-attention.ts`) moves an approved item `pre-backlog/ → backlog/` to make
329
+ * it claimable. STEP A: ADDITIVE — no `work/tasks/ready/` reader changes here.
330
+ */
331
+ export const STAGED_TASKS_DIR = workFolderRel('tasks-backlog');
332
+
333
+ /**
334
+ * The POOL folder tasks land in when the runner-deterministic placement
335
+ * resolver chooses the pool side (`tasksLandIn: 'ready'` and a trusted
336
+ * origin, or an `--tasks-land-in ready` operator override). The agent NEVER
337
+ * writes here — it always writes to {@link STAGED_TASKS_DIR}; the runner
338
+ * redirects the emitted files to the resolved destination at integrate-stage
339
+ * time. prd US #4 / the governing ADR: the agent cannot self-place into the
340
+ * pool. Task `runner-deterministic-slice-placement-policy-and-precedence`.
341
+ */
342
+ const POOL_TASKS_DIR = workFolderRel('tasks-ready');
343
+
344
+ /** The placement slots for the TASK lifecycle (folder names). */
345
+ const TASK_PLACEMENT_SLOTS = {
346
+ staging: STAGED_TASKS_DIR,
347
+ pool: POOL_TASKS_DIR,
348
+ } as const;
349
+
350
+ /**
351
+ * Map the `tasksLandIn` value spelling (`pre-backlog` | `ready`) onto the
352
+ * resolver's lifecycle-generic side enum (`staging` | `pool`). Returns
353
+ * `undefined` when no value is set, so the resolver's next precedence rung
354
+ * applies (the built-in floor). The legacy `'backlog'`/`'todo'` pool spellings
355
+ * are NOT accepted (clean break — the value was renamed `'backlog'` → `'todo'`
356
+ * → `'ready'`, ADR `rename-task-pool-folder-todo-to-ready`).
357
+ */
358
+ function landingToSide(
359
+ landing: 'pre-backlog' | 'ready' | undefined,
360
+ ): 'staging' | 'pool' | undefined {
361
+ if (landing === 'pre-backlog') return 'staging';
362
+ if (landing === 'ready') return 'pool';
363
+ return undefined;
364
+ }
365
+
366
+ /** The repo-relative path of a staged task's `.md` (per {@link STAGED_TASKS_DIR}). */
367
+ function stagedTaskPath(name: string): string {
368
+ return `${STAGED_TASKS_DIR}/${name}`;
369
+ }
370
+
371
+ /**
372
+ * Run the `do prd:<slug>` tasking path end-to-end. Never throws for the expected
373
+ * gate-refused / lock-lost / agent-failed / stale cases — those are returned with
374
+ * the appropriate exit code and outcome. The runner owns all git; the agent only
375
+ * writes task files.
376
+ */
377
+ export async function performTask(
378
+ options: PerformTaskOptions,
379
+ ): Promise<TaskResult> {
380
+ const note = options.note ?? (() => {});
381
+ const arbiter = options.arbiter ?? DEFAULT_ARBITER;
382
+ const cwd = options.cwd;
383
+ // `env` is the runner's GIT/provider env (identity-scoped). `agentEnv` is the
384
+ // AMBIENT env for AGENT launches (tasker agent, review/improver agents) — an
385
+ // agent must not act as the bot. Falls back to `env` when no identity.
386
+ const env = options.env;
387
+ const agentEnv = options.agentEnv ?? options.env;
388
+ const slug = options.slug;
389
+ const doer = options.doer ?? 'agent';
390
+ const lock = options.lock ?? DEFAULT_LOCK_SEAM;
391
+
392
+ // 0. The prd must exist in the checkout (`work/prds/ready/<slug>.md`) — it is the
393
+ // source the agent tasks + the file the lock holds.
394
+ const specPath = workItemPath(cwd, 'specs-ready', slug);
395
+ if (!existsSync(specPath)) {
396
+ const message = `no prd '${slug}' found at ${workFolderRel('specs-ready')}/${slug}.md.`;
397
+ note(message);
398
+ return {exitCode: 1, outcome: 'usage-error', slug, message};
399
+ }
400
+ const specContent = readFileSync(specPath, 'utf8');
401
+ const specFm = parseFrontmatter(specContent);
402
+
403
+ // 1. RESOLVE THE GATE (agent path only). The human path is UNBOUND — a human
404
+ // decides for themselves whether a prd is taskable.
405
+ if (doer === 'agent') {
406
+ const eligibility = resolveAgentGate(
407
+ cwd,
408
+ slug,
409
+ specFm,
410
+ options.autoTask,
411
+ options.explicit ?? false,
412
+ );
413
+ if (!eligibility.taskable) {
414
+ const message = gateRefusalReason(slug, specFm, eligibility, options);
415
+ note(message);
416
+ return {exitCode: 1, outcome: 'gate-refused', slug, message};
417
+ }
418
+ }
419
+
420
+ // 2. ACQUIRE THE LOCK (agent path; concurrency serialisation). The human path
421
+ // with no contention may task on `main` directly WITHOUT the lock.
422
+ let lockedBlob: string | undefined;
423
+ const useLock = doer === 'agent';
424
+ if (useLock) {
425
+ const acquired = await lock.acquire({slug, cwd, arbiter, env, note});
426
+ if (acquired.outcome === 'lost') {
427
+ return {
428
+ exitCode: 2,
429
+ outcome: 'lock-lost',
430
+ slug,
431
+ message: acquired.message,
432
+ };
433
+ }
434
+ if (acquired.outcome === 'contended') {
435
+ return {
436
+ exitCode: 3,
437
+ outcome: 'lock-lost',
438
+ slug,
439
+ message: acquired.message,
440
+ };
441
+ }
442
+ if (acquired.exitCode !== 0) {
443
+ return {
444
+ exitCode: 1,
445
+ outcome: 'usage-error',
446
+ slug,
447
+ message: acquired.message,
448
+ };
449
+ }
450
+ lockedBlob = acquired.lockedBlob;
451
+ }
452
+
453
+ // 2b. ONBOARD the agent's tasking work onto a `work/<slug>` BRANCH cut from the
454
+ // freshly-fetched `<arbiter>/main` (task `slice-output-through-integration`).
455
+ // The prd body rests in `work/prds/ready/<slug>.md` on `<arbiter>/main` (the lock no
456
+ // longer moves it), so the branch's base HOLDS the held prd — the lifecycle
457
+ // stage below moves it `prd/ → prd-tasked/` ON THIS BRANCH and the shared integrate core (`--propose`
458
+ // PR / `--merge` main) lands the whole transition, WITHOUT the lock release
459
+ // committing tasks straight to `main`. The agent runs IN-PLACE on this branch
460
+ // (branch ≠ worktree; the isolation seam upgrades it). The HUMAN path stays on
461
+ // its own branch and commits its output itself (no integrate, no branch cut).
462
+ if (useLock) {
463
+ await switchToWorkBranch(cwd, arbiter, slug, env);
464
+ }
465
+
466
+ // 3. INVOKE THE AGENT with the to-task prd. It WRITES
467
+ // `work/tasks/backlog/*.md` task files (the STAGED area — NOT `work/tasks/ready/`,
468
+ // which is the agent-eligible pool the runner owns the promotion into; task
469
+ // `pre-backlog-staging-folder-and-promote-step-a`); it does NO git. We
470
+ // snapshot the staged-tasks folder before/after so the runner (not the
471
+ // agent) captures + commits exactly what was produced.
472
+ const before = snapshotStagedTasks(cwd);
473
+ // Also snapshot the POOL `work/tasks/ready/` BEFORE the agent runs: the runner's
474
+ // final commit must scrub any agent writes there (an attempt to self-place into
475
+ // the pool, prd US #4) before `git add -A` would sweep them in.
476
+ const poolBefore = snapshotPool(cwd);
477
+ // MIGRATE step (prd `prd-to-spec-vocabulary-cutover-and-migration-command`):
478
+ // read the parent-spec self-pointer off `prdFm.spec` (populated beside
479
+ // `prdFm.prd` by the expand task).
480
+ const prompt = buildTaskingSpec(slug, specFm.spec);
481
+ let agent: {ok: boolean; detail?: string};
482
+ try {
483
+ agent = await runTaskAgent(options, cwd, prompt, slug);
484
+ } catch (err) {
485
+ agent = {
486
+ ok: false,
487
+ detail: err instanceof Error ? err.message : String(err),
488
+ };
489
+ }
490
+ if (!agent.ok) {
491
+ const detail = agent.detail ?? `the agent failed to task '${slug}'.`;
492
+ const message = `Agent failed tasking '${slug}' (${detail}).`;
493
+ note(message);
494
+ // The lock stays held (the runner did not release it): a stuck tasking is
495
+ // recoverable / re-runnable. Surfacing it is the review/edit loop's job.
496
+ return {exitCode: 1, outcome: 'agent-failed', slug, message};
497
+ }
498
+
499
+ // 3.5 THE TASKER REVIEW→EDIT→CONVERGE LOOP (`slicer-review-edit-loop`, Shape 2 /
500
+ // insertion point A): when a loop seam is wired, run the `review` SKILL as a
501
+ // review→edit→re-review loop that IMPROVES the candidate tasks in place, then
502
+ // determines the disposition (the three outcomes). This plugs in AFTER the
503
+ // candidate tasks are produced and BEFORE they are finalised. The agent makes
504
+ // the review/edit JUDGEMENTS; the loop applies edits to the candidate files
505
+ // and routes the verdict; the runner (below) owns the git transition. Only the
506
+ // AGENT path runs the loop — the human tasking path is unaffected.
507
+ let loopDisposition: RunTaskReviewLoopResult | undefined;
508
+ if (options.reviewLoop && doer === 'agent') {
509
+ loopDisposition = await runTaskReviewLoop({
510
+ slug,
511
+ cwd,
512
+ gate: options.reviewLoop,
513
+ // SCOPING FENCE (the requeue fix): the loop reviews/edits/flags ONLY the
514
+ // tasks THIS run produced (new-or-changed vs `before`), never the
515
+ // pre-existing staged tasks that share `work/tasks/backlog/`.
516
+ before,
517
+ taskerLoopMax: options.taskerLoopMax ?? 3,
518
+ executions: options.reviewExecutions,
519
+ taskerLoopModel: options.taskerLoopModel,
520
+ sessionsDir: options.sessionsDir,
521
+ // The improver loop's review AGENT launches AMBIENT, never the identity.
522
+ env: agentEnv,
523
+ note,
524
+ });
525
+ // DECOMPOSITION UNCLEAR: emit NO guessed tasks — route the held prd to
526
+ // needs-attention with the questions as the reason. The lock release amends the
527
+ // `prd:<slug>` unified lock `active → stuck` (the tasking needs-attention surface
528
+ // is the stuck lock now — NO folder write; the prd body stays in `work/prds/ready/`).
529
+ if (loopDisposition.outcome === 'decomposition-unclear') {
530
+ const reason = decompositionUnclearReason(
531
+ slug,
532
+ loopDisposition.specQuestions,
533
+ );
534
+ if (useLock) {
535
+ const routed = await lock.release({
536
+ slug,
537
+ cwd,
538
+ arbiter,
539
+ lockedBlob,
540
+ routeToNeedsAttention: {reason},
541
+ env,
542
+ note,
543
+ });
544
+ if (routed.outcome !== 'released') {
545
+ return releaseFailureToResult(routed, slug);
546
+ }
547
+ }
548
+ note(loopDisposition.message);
549
+ return {
550
+ exitCode: 1,
551
+ outcome: 'needs-attention',
552
+ slug,
553
+ message: loopDisposition.message,
554
+ };
555
+ }
556
+ // UNCERTAIN TASKS: mark each named candidate `needsAnswers: true` + record
557
+ // its questions in the body, so it lands but is not agent-buildable. The
558
+ // runner writes the marker (the agent does no git/disk-escape).
559
+ if (loopDisposition.outcome === 'uncertain-tasks') {
560
+ for (const uncertain of loopDisposition.uncertainTasks) {
561
+ markTaskNeedsAnswers(cwd, uncertain.path, uncertain.questions, note);
562
+ }
563
+ }
564
+ }
565
+
566
+ // 4. The RUNNER commits the COMPLETING transition: drop the produced backlog
567
+ // tasks IN + move the prd tasking/ -> prd-tasked/ (residence = tasked-ness) — now
568
+ // through the SHARED integrate core (`--propose` PR / `--merge` main), NOT a
569
+ // direct commit to `main`. The agent never does git. (The backlog snapshot is
570
+ // taken AFTER any loop edits, so the runner integrates the IMPROVED tasks,
571
+ // not the pre-loop candidates.)
572
+ const stagedEmitted = newOrChangedStagedTasks(cwd, before);
573
+ const emitTasks = collectEmittedTasks(cwd, stagedEmitted);
574
+ // RUNNER-DETERMINISTIC PLACEMENT (task
575
+ // `runner-deterministic-slice-placement-policy-and-precedence`). Resolve which
576
+ // folder the runner lands the emitted tasks in BEFORE handing them to the
577
+ // shared integrate band: precedence `explicit > untrusted-origin ⇒ staging >
578
+ // tasksLandIn > built-in (staging)`, all from unforgeable inputs (the prd's
579
+ // stamped `originTrust:` + the resolved per-repo default + the operator's
580
+ // explicit flag). The agent NEVER influences this; it always writes to
581
+ // `work/tasks/backlog/`, and the runner redirects at `stage()` time.
582
+ const placementDecision = resolvePlacement({
583
+ explicit: landingToSide(options.explicitTasksLandIn),
584
+ originTrust: specFm.originTrust,
585
+ configuredDefault: landingToSide(options.tasksLandIn),
586
+ });
587
+ const placementDir = placementFolder(
588
+ TASK_PLACEMENT_SLOTS,
589
+ placementDecision.choice,
590
+ );
591
+ // REWRITE the emitted list to the RUNNER-RESOLVED destination so callers see
592
+ // where the runner actually placed the files (not where the agent wrote them).
593
+ const emitted = stagedEmitted.map(
594
+ (rel) => `${placementDir}/${basename(rel)}`,
595
+ );
596
+ const loopTag: 'converged' | 'uncertain-tasks' | undefined =
597
+ loopDisposition?.outcome === 'converged'
598
+ ? 'converged'
599
+ : loopDisposition?.outcome === 'uncertain-tasks'
600
+ ? 'uncertain-tasks'
601
+ : undefined;
602
+
603
+ if (useLock) {
604
+ // READ-STABILITY BACKSTOP (the lock's content-identity check, now owned at the
605
+ // integrate seam): the OUTPUT no longer rides the lock release, so the band
606
+ // below would otherwise rebase a concurrent edit of the held prd body CLEANLY
607
+ // into prd/ (a rename+edit merge) while the tasks were cut from the OLD body —
608
+ // the exact silent stale-task drift the lock forbids
609
+ // (`work/notes/observations/tasking-lock-does-not-stabilise-prd-content.md`). So we
610
+ // compare the CURRENTLY held `work/prds/ready/<slug>.md` blob on the arbiter against
611
+ // the snapshot the lock TOOK (`lockedBlob`); ANY change ⇒ STALE ⇒ fail loud,
612
+ // touch NOTHING (the lock stays held; a human re-tasks or routes to
613
+ // needs-attention). It is the SAME content-identity check `releaseTaskingLock`
614
+ // runs — relocated here because this transition, not the release, owns the
615
+ // completing commit now.
616
+ const stale = await heldSpecIsStale(cwd, arbiter, slug, lockedBlob, env);
617
+ if (stale) {
618
+ const specRel = workItemRel('specs-ready', `${slug}.md`);
619
+ const message =
620
+ `RELEASE CONFLICT for '${slug}': the prd was edited (${specRel} ` +
621
+ `changed on ${arbiter}/main) while the tasking lock was held. The tasking is ` +
622
+ `STALE — re-task from the edited prd or route it to needs-attention. ` +
623
+ `The arbiter was NOT modified (lock still held).`;
624
+ note(message);
625
+ return {exitCode: 4, outcome: 'stale', slug, message};
626
+ }
627
+
628
+ // Route the OUTPUT through the SHARED integrate back-half (task
629
+ // `slice-output-through-integration`): the produced backlog tasks + the prd
630
+ // lifecycle move (`work/prds/ready/ -> work/prds/tasked/`, residence = tasked-ness) integrate
631
+ // via `performIntegration` honoring `--propose` (push the work branch + open a
632
+ // PR, NO `main` touch) / `--merge` (land on `main`). Because the integrate-time
633
+ // args resolve ONCE in the shared core, every `do task:` arg applies here by
634
+ // construction. The agent did NO git; the runner (the band) owns the ONE commit.
635
+ const core = await performIntegration({
636
+ cwd,
637
+ arbiter,
638
+ slug,
639
+ // `source`/`recovering` are task-shaped and IGNORED when `lifecycle` is set
640
+ // (a tasking transition never recovers a surfaced needs-attention move).
641
+ source: 'in-progress',
642
+ recovering: false,
643
+ // Skip the build acceptance gate (Gate 1 / verify): a tasking transition has
644
+ // no `verify` floor (the tasker review loop above is its quality gate).
645
+ skipVerify: true,
646
+ // THE TASK-SET ACCEPTANCE GATE (task `slice-acceptance-gate`): the
647
+ // task-path mirror of the build Gate-2, riding THIS shared core's
648
+ // review-before-integrate block. When `review` resolves on, the wired
649
+ // `reviewGate` (production: `harnessTaskReviewGate` with the task-SET
650
+ // prompt) runs a FRESH-CONTEXT review of the produced task SET before it
651
+ // integrates: `approve` lands it, `block` routes the set to needs-attention
652
+ // via the SAME machinery the build block uses (mapped to the tasking
653
+ // `needs-attention` outcome below). It is ONE-SHOT: we pin
654
+ // `reviewMaxRounds: 1` so the gate is a SINGLE reviewer invocation → verdict
655
+ // (terminal pass/fail). The task path NEVER exposes/consults
656
+ // `--review-max-rounds` — a gate is terminal, the rounds bound is an orphan
657
+ // that belongs to a future revise↔review loop (see
658
+ // `work/notes/observations/reviewmaxrounds-on-wrong-concept.md`). This is
659
+ // independently controllable from the tasker improver loop (`reviewLoop` /
660
+ // the `--tasker-loop*` family); toggling one does not affect the other.
661
+ review: options.review,
662
+ reviewGate: options.reviewGate,
663
+ reviewModel: options.acceptanceReviewModel,
664
+ reviewMaxRounds: 1,
665
+ // The EXPLICITLY-chosen integrate mode proceeds AS-IS on an APPROVE — a
666
+ // `--merge` tasking run lands on main, `--propose` opens a PR. The tasking
667
+ // path's merge-vs-propose decision is the `integration` mode the user typed;
668
+ // `merge` IS the auto-land mode, so a resolved `merge` is never downgraded.
669
+ // The task gate family is `--review`/`--no-review`/`--review-model` only
670
+ // (prd US #6).
671
+ mode: options.integration ?? 'propose',
672
+ noPR: options.noPR,
673
+ providerInstance: options.providerInstance,
674
+ type: 'tasking',
675
+ lifecycle: {
676
+ // Read the PR title / commit summary from the held prd (before it moves).
677
+ titlePath: workItemPath(cwd, 'specs-ready', slug),
678
+ commitTag: 'tasked',
679
+ stage: () =>
680
+ stageTaskingLifecycle({
681
+ cwd,
682
+ slug,
683
+ emitTasks,
684
+ poolBefore,
685
+ placementDir,
686
+ placementReason: placementDecision.reason,
687
+ note,
688
+ env,
689
+ }),
690
+ },
691
+ env,
692
+ // The task-SET acceptance review AGENT launches AMBIENT, never the
693
+ // identity-scoped `env` (an agent must not act as the bot).
694
+ agentEnv,
695
+ note,
696
+ });
697
+
698
+ // THE TASK-SET ACCEPTANCE GATE BLOCKED (task `slice-acceptance-gate`): the
699
+ // fresh-context review of the produced SET returned `block`, so the core ran
700
+ // the review BEFORE the stage/integrate and did NOT integrate the tasks
701
+ // (correct). The CORRECT task-path destination is the SAME needs-attention
702
+ // route the lock release owns for the decomposition-unclear verdict: it amends
703
+ // the `prd:<slug>` unified lock `active -> stuck` with the block reason (the
704
+ // tasking needs-attention surface is the stuck lock now — NO folder write; the
705
+ // prd body stays in `work/prds/ready/`). So on a block we route the held prd to
706
+ // needs-attention THROUGH the lock release — the set never lands.
707
+ if (core.outcome === 'review-blocked') {
708
+ const reason = taskGateBlockedReason(slug, core.reviewBlockReason);
709
+ const routed = await lock.release({
710
+ slug,
711
+ cwd,
712
+ arbiter,
713
+ lockedBlob,
714
+ routeToNeedsAttention: {reason},
715
+ env,
716
+ note,
717
+ });
718
+ if (routed.outcome !== 'released') {
719
+ return releaseFailureToResult(routed, slug);
720
+ }
721
+ note(reason);
722
+ return {
723
+ exitCode: 1,
724
+ outcome: 'needs-attention',
725
+ slug,
726
+ message:
727
+ `The task acceptance gate blocked the set produced for '${slug}'; ` +
728
+ `marked the per-item lock stuck (needs attention; no tasks landed).`,
729
+ };
730
+ }
731
+ if (core.outcome === 'review-unparseable') {
732
+ // The task-set acceptance gate RAN but its verdict was UNPARSEABLE (malformed
733
+ // JSON). Route the held prd to needs-attention through the SAME lock-release
734
+ // seam the block path uses (the tasking needs-attention surface is the stuck
735
+ // `prd:<slug>` lock; no folder write). It is NOT a block (the gate output was
736
+ // unreadable) — record it as the transient-infra-class re-run signal so the
737
+ // stuck reason reads correctly; nothing landed.
738
+ const reason =
739
+ `The task acceptance gate for '${slug}' produced an UNPARSEABLE verdict ` +
740
+ `(re-run — transient): ${core.reason ?? ''}`;
741
+ const routed = await lock.release({
742
+ slug,
743
+ cwd,
744
+ arbiter,
745
+ lockedBlob,
746
+ routeToNeedsAttention: {reason},
747
+ env,
748
+ note,
749
+ });
750
+ if (routed.outcome !== 'released') {
751
+ return releaseFailureToResult(routed, slug);
752
+ }
753
+ note(reason);
754
+ return {
755
+ exitCode: 1,
756
+ outcome: 'needs-attention',
757
+ slug,
758
+ message:
759
+ `The task acceptance gate produced an unparseable verdict for '${slug}'; ` +
760
+ `marked the per-item lock stuck (needs attention; no tasks landed; re-run).`,
761
+ };
762
+ }
763
+ if (core.outcome === 'completed') {
764
+ // The durable `prd → prd-tasked` `main` move landed through the shared integrate
765
+ // core (the body moved straight from `work/prds/ready/` — no transient `tasking/`
766
+ // marker). The completing commit is owned by the integrate band, NOT
767
+ // `releaseTaskingLock`, so the unified per-item lock that `acquireTaskingLock`
768
+ // took is released HERE (delete the ref). A
769
+ // `propose` (`mode: 'propose'`) is ALSO `completed` (the PR opened, the lock's
770
+ // hold over the in-flight tasking is done); the eventual hold-across-the-PR
771
+ // crash-safe ordering is the capstone task #7's concern, not this interim
772
+ // half. Best-effort + idempotent (`not-held` is fine).
773
+ // MIGRATE step: release under the `spec:<slug>` identity (keyed to the
774
+ // `spec-<slug>` lock entry the acquire now takes). Idempotent.
775
+ if (useLock) {
776
+ await releaseItemLock({item: `spec:${slug}`, cwd, arbiter, env});
777
+ }
778
+ }
779
+ return integrationToTaskResult(core, {slug, emitted, loop: loopTag});
780
+ }
781
+
782
+ // HUMAN, no-lock path: the human commits on `main` directly (the runner does
783
+ // not own the human's git). We report the produced tasks; moving the prd into
784
+ // `work/prds/tasked/` (residence = tasked-ness) and committing is the human's to
785
+ // do, as with the human `complete`.
786
+ const message =
787
+ `Tasked '${slug}' -> ${emitted.length} backlog task` +
788
+ `${emitted.length === 1 ? '' : 's'} (human path, no lock). Inspect + commit ` +
789
+ `the produced files (and move the prd into work/prds/tasked/) yourself.`;
790
+ note(message);
791
+ return {
792
+ exitCode: 0,
793
+ outcome: 'tasked',
794
+ slug,
795
+ emitted,
796
+ loop: loopTag,
797
+ message,
798
+ };
799
+ }
800
+
801
+ /**
802
+ * Map a non-`released` lock-release result onto the {@link TaskResult} contract
803
+ * (the decomposition-unclear routing reuses the SAME release seam, so it can also
804
+ * be `stale`/`lost`/`contended`/usage-error). Mirrors the step-4 release mapping.
805
+ */
806
+ function releaseFailureToResult(
807
+ released: ReleaseTaskingLockResult,
808
+ slug: string,
809
+ ): TaskResult {
810
+ if (released.outcome === 'stale') {
811
+ return {exitCode: 4, outcome: 'stale', slug, message: released.message};
812
+ }
813
+ if (released.outcome === 'lost' || released.outcome === 'contended') {
814
+ const code = released.outcome === 'lost' ? 2 : 3;
815
+ return {
816
+ exitCode: code,
817
+ outcome: 'lock-lost',
818
+ slug,
819
+ message: released.message,
820
+ };
821
+ }
822
+ return {exitCode: 1, outcome: 'usage-error', slug, message: released.message};
823
+ }
824
+
825
+ /**
826
+ * Map the shared integrate band's {@link IntegrationCoreResult} onto the tasking
827
+ * {@link TaskResult} (task `slice-output-through-integration`). On `completed`
828
+ * (propose pushed the work branch + opened a PR / merge landed on `main`) the
829
+ * tasking is `tasked`. The band's FAILURE outcomes are reported on the tasking
830
+ * contract: a `rebase-conflict` against a concurrently-advanced `main` maps to
831
+ * `stale` (exit 4) — the tasking analogue of "the held prd moved under us"; a
832
+ * a `gate-failed` cannot occur (the tasking path passes `skipVerify`) but maps to
833
+ * a usage error defensively. A `review-blocked` (the task-SET ACCEPTANCE GATE
834
+ * blocked the set, task `slice-acceptance-gate`) is handled by `performTask`
835
+ * BEFORE this mapper — it routes the held prd to needs-attention via
836
+ * the lock release (the task-path needs-attention route) — so it never reaches
837
+ * here; it is mapped defensively to a usage error if it ever does.
838
+ */
839
+ function integrationToTaskResult(
840
+ core: IntegrationCoreResult,
841
+ ctx: {
842
+ slug: string;
843
+ emitted: string[];
844
+ loop: 'converged' | 'uncertain-tasks' | undefined;
845
+ },
846
+ ): TaskResult {
847
+ const {slug, emitted, loop} = ctx;
848
+ if (core.outcome === 'completed') {
849
+ const landed =
850
+ core.integration?.mode === 'merge'
851
+ ? 'landed them on the arbiter main'
852
+ : 'opened a PR carrying them (main untouched)';
853
+ const message =
854
+ `Tasked '${slug}' -> ${emitted.length} backlog task` +
855
+ `${emitted.length === 1 ? '' : 's'}; the runner integrated the transition ` +
856
+ `through the shared core (moved work/prds/ready/ -> work/prds/tasked/, the ` +
857
+ `tasked resting state) and ${landed}.`;
858
+ return {exitCode: 0, outcome: 'tasked', slug, emitted, loop, message};
859
+ }
860
+ if (core.outcome === 'rebase-conflict') {
861
+ return {
862
+ exitCode: 4,
863
+ outcome: 'stale',
864
+ slug,
865
+ message:
866
+ core.reason ??
867
+ `Integrating the tasking of '${slug}' conflicted against the latest ` +
868
+ `${slug} main — the tasking is stale; re-task from the current prd.`,
869
+ };
870
+ }
871
+ return {
872
+ exitCode: 1,
873
+ outcome: 'usage-error',
874
+ slug,
875
+ message:
876
+ core.reason ??
877
+ `Integrating the tasking of '${slug}' failed unexpectedly.`,
878
+ };
879
+ }
880
+
881
+ /**
882
+ * ONBOARD the tasking work onto a `work/<slug>` branch cut from the freshly-
883
+ * fetched `<arbiter>/main` (task `slice-output-through-integration`). Called
884
+ * AFTER the tasking lock is held, so the branch's base HOLDS the prd in
885
+ * `work/prds/ready/` (the lock no longer moves the body) — the lifecycle stage then moves
886
+ * it `prd/ -> prd-tasked/` ON THIS BRANCH and the shared integrate core lands it. A
887
+ * pre-existing local `work/<slug>` (a re-run) is force-recreated off fresh main.
888
+ * The agent runs in-place on this branch (branch ≠ worktree).
889
+ */
890
+ async function switchToWorkBranch(
891
+ cwd: string,
892
+ arbiter: string,
893
+ slug: string,
894
+ env: NodeJS.ProcessEnv | undefined,
895
+ ): Promise<void> {
896
+ // The tasking path is the parent-spec namespace (`do spec:<slug>`): the branch
897
+ // is `work/spec-<slug>`, distinct from a same-slug task-build's `work/task-<slug>`.
898
+ // MIGRATE step (prd `prd-to-spec-vocabulary-cutover-and-migration-command`):
899
+ // MINT the work-branch under the `spec` namespace token (`workBranchRef` still
900
+ // parses the legacy `work/prd-<slug>` form, so in-flight branches keep resolving).
901
+ const branch = workBranchRef('spec', slug);
902
+ await gitHard(['fetch', '--quiet', arbiter], cwd, env);
903
+ await gitHard(
904
+ ['switch', '--quiet', '-C', branch, `${arbiter}/main`],
905
+ cwd,
906
+ env,
907
+ );
908
+ }
909
+
910
+ /**
911
+ * The READ-STABILITY content-identity STALE CHECK (the lock's backstop, owned at
912
+ * the integrate seam now that the OUTPUT no longer rides the lock release): true
913
+ * iff the CURRENTLY held `work/prds/ready/<slug>.md` blob on `<arbiter>/main` DIFFERS
914
+ * from the snapshot the lock TOOK (`lockedBlob`, read from `work/prds/ready/<slug>.md` at
915
+ * acquire). ANY change = a concurrent edit under the lock = the tasking is STALE.
916
+ * Stronger than a textual rebase conflict (which a rename+edit merge can apply
917
+ * CLEANLY). When `lockedBlob` is absent (never, in production) it reads as
918
+ * not-stale (the lock acquire always returns it).
919
+ */
920
+ async function heldSpecIsStale(
921
+ cwd: string,
922
+ arbiter: string,
923
+ slug: string,
924
+ lockedBlob: string | undefined,
925
+ env: NodeJS.ProcessEnv | undefined,
926
+ ): Promise<boolean> {
927
+ if (lockedBlob === undefined) {
928
+ return false;
929
+ }
930
+ await gitHard(['fetch', '--quiet', arbiter], cwd, env);
931
+ const held = await gitSoft(
932
+ ['rev-parse', `${arbiter}/main:${workFolderRel('specs-ready')}/${slug}.md`],
933
+ cwd,
934
+ env,
935
+ );
936
+ // The prd being absent (already tasked/moved) is NOT this check's concern
937
+ // (the integrate's rebase/push surfaces that); only a CHANGED held blob is stale.
938
+ if (held.status !== 0) {
939
+ return false;
940
+ }
941
+ return held.stdout.trim() !== lockedBlob;
942
+ }
943
+
944
+ /**
945
+ * STAGE the tasking lifecycle into the index on the `work/<slug>` branch (the
946
+ * {@link performIntegration} lifecycle seam): move the held prd
947
+ * `git mv work/prds/ready/<slug>.md -> work/prds/tasked/<slug>.md` (the TASKED resting
948
+ * state — the build-machine `done/` analogue, the SOURCE OF TRUTH for tasked-ness),
949
+ * and write+`git add` the produced `work/tasks/backlog/*.md` files. The band's subsequent
950
+ * `git add -A` + atomic commit folds this AND the agent's uncommitted backlog writes
951
+ * into ONE runner-owned commit (the agent never does git).
952
+ *
953
+ * TASK `prd-sliced-folder-step-a` (prd `slicing-coherence` US #8): the lifecycle
954
+ * destination is `work/prds/tasked/` (NOT back to `work/prds/ready/`) — `prd-tasked/`
955
+ * residence IS tasked-ness (like `done/` for tasks, with no `done:` marker). The
956
+ * `tasked:` frontmatter marker was removed entirely in `remove-sliced-marker-step-b`
957
+ * (sequenced last): residence in `work/prds/tasked/` is now the sole signal.
958
+ */
959
+ async function stageTaskingLifecycle(params: {
960
+ cwd: string;
961
+ slug: string;
962
+ emitTasks: Record<string, string>;
963
+ poolBefore: Map<string, string>;
964
+ /**
965
+ * The runner-resolved destination folder (task
966
+ * `runner-deterministic-slice-placement-policy-and-precedence`). Computed
967
+ * ONCE in `performTask` via the shared {@link resolvePlacement} from the
968
+ * prd's `originTrust:` stamp + the configured `tasksLandIn` default + the
969
+ * operator's explicit override, then passed in here — so the call site sees
970
+ * exactly where the emitted tasks landed (the placement decision is not
971
+ * buried in the stage closure).
972
+ */
973
+ placementDir: string;
974
+ /** Which precedence rung the resolver took (for honest reporting). */
975
+ placementReason:
976
+ | 'explicit'
977
+ | 'untrusted-origin'
978
+ | 'configured-default'
979
+ | 'built-in';
980
+ note: (message: string) => void;
981
+ env: NodeJS.ProcessEnv | undefined;
982
+ }): Promise<void> {
983
+ const {
984
+ cwd,
985
+ slug,
986
+ emitTasks,
987
+ poolBefore,
988
+ placementDir,
989
+ placementReason,
990
+ note,
991
+ env,
992
+ } = params;
993
+ const spec = workItemRel('specs-ready', `${slug}.md`);
994
+ const specTasked = workItemRel('specs-tasked', `${slug}.md`);
995
+ // PROPAGATE the origin-trust PROVENANCE (task
996
+ // `untrusted-origin-forces-build-propose`): read the held prd's `origin`/
997
+ // `originTrust` stamp BEFORE the move, so each emitted task can carry it. A
998
+ // task's risk is its BUILD; the stamp must reach the task so the build
999
+ // transition can force `propose` for untrusted-origin work. An UNSTAMPED prd (a
1000
+ // human/local-authored one ⇒ trusted) propagates nothing — the normal path is
1001
+ // untouched.
1002
+ const specAbs = join(cwd, spec);
1003
+ const specProvenance = existsSync(specAbs)
1004
+ ? parseFrontmatter(readFileSync(specAbs, 'utf8'))
1005
+ : {origin: undefined, originTrust: undefined};
1006
+ if (placementReason === 'untrusted-origin') {
1007
+ note(
1008
+ `Untrusted-origin prd '${slug}': forcing the emitted tasks STAGED ` +
1009
+ `(${placementDir}/) regardless of tasksLandIn (a human promotes ` +
1010
+ 'them into work/tasks/ready/). Pass --tasks-land-in <where> to override.',
1011
+ );
1012
+ }
1013
+ // Move the held prd prd/ -> prd-tasked/ (the TASKED resting state — folder =
1014
+ // source of truth, like done/ for tasks). This is the DURABLE `prd → prd-tasked`
1015
+ // success move, owned by THIS transition's commit (the lock no longer moved the
1016
+ // body, so the source is `work/prds/ready/`, never `work/tasking/`).
1017
+ mkdirSync(dirname(join(cwd, specTasked)), {recursive: true});
1018
+ await gitHard(['mv', spec, specTasked], cwd, env);
1019
+ await gitHard(['add', '--', specTasked], cwd, env);
1020
+ // **POOL-PLACEMENT FENCE (prd US #4 / governing ADR
1021
+ // `placement-is-runner-deterministic-humanonly-is-agent-judgement`).** The
1022
+ // agent ALWAYS writes to the STAGING folder (`work/tasks/backlog/`); the POOL
1023
+ // (`work/tasks/ready/`) is the agent-eligible pool the runner owns the promotion
1024
+ // into. Anything the agent dropped under the pool would otherwise be swept in
1025
+ // by `performIntegration`'s subsequent `git add -A` — a self-placement into
1026
+ // the pool. Scrub it FIRST (before the runner writes its resolved destination
1027
+ // files below), so when the runner-deterministic placement resolves to the
1028
+ // pool the runner's writes are the ONLY legitimate pool entries in the commit.
1029
+ await scrubPoolDrift(cwd, poolBefore, env);
1030
+ // Drop the produced backlog tasks IN at the RUNNER-RESOLVED destination
1031
+ // (write + stage; the band's `git add -A` also catches them, but staging here
1032
+ // keeps the transition explicit + atomic). The runner STAMPS the propagated
1033
+ // provenance onto each task as it writes it (the agent does no git; the runner
1034
+ // owns the file write here). When the resolved destination DIFFERS from where
1035
+ // the agent wrote (the staging folder), remove the agent's source file too —
1036
+ // otherwise `git add -A` would commit BOTH (staging twin + pool destination).
1037
+ for (const [agentRel, content] of Object.entries(emitTasks)) {
1038
+ const filename = basename(agentRel);
1039
+ const destRel = `${placementDir}/${filename}`;
1040
+ const destAbs = join(cwd, destRel);
1041
+ mkdirSync(dirname(destAbs), {recursive: true});
1042
+ writeFileSync(destAbs, propagateOrigin(specProvenance, content));
1043
+ await gitHard(['add', '--', destRel], cwd, env);
1044
+ if (destRel !== agentRel) {
1045
+ const srcAbs = join(cwd, agentRel);
1046
+ rmSync(srcAbs, {force: true});
1047
+ // Also unstage if `git add --` from a previous run picked it up;
1048
+ // untracked-and-now-gone is fine.
1049
+ await gitSoft(['rm', '-f', '--quiet', '--', agentRel], cwd, env);
1050
+ }
1051
+ }
1052
+ }
1053
+
1054
+ /**
1055
+ * Revert any change/addition the agent made to the POOL `work/tasks/ready/` during a
1056
+ * tasking run. The agent's STAGING folder is `work/tasks/backlog/`; a write to the
1057
+ * pool is an attempt to self-place into the agent-eligible pool the runner owns
1058
+ * the promotion into (prd US #4 / governing ADR). Compared to the `poolBefore`
1059
+ * snapshot (the branch-base state of `work/tasks/ready/`, taken BEFORE the agent
1060
+ * ran), any new file is removed from the worktree and any changed file is
1061
+ * checked back out to HEAD — so the subsequent `git add -A` cannot land it. The
1062
+ * runner's commit then carries ONLY the explicit `pre-backlog/` placement.
1063
+ */
1064
+ async function scrubPoolDrift(
1065
+ cwd: string,
1066
+ poolBefore: Map<string, string>,
1067
+ env: NodeJS.ProcessEnv | undefined,
1068
+ ): Promise<void> {
1069
+ const dir = workFolderPath(cwd, 'tasks-ready');
1070
+ let entries: string[];
1071
+ try {
1072
+ entries = readdirSync(dir);
1073
+ } catch {
1074
+ return;
1075
+ }
1076
+ for (const name of entries) {
1077
+ if (!isWorkItemFile(name)) {
1078
+ continue;
1079
+ }
1080
+ const abs = join(dir, name);
1081
+ const content = readFileSync(abs, 'utf8');
1082
+ if (poolBefore.has(name)) {
1083
+ if (poolBefore.get(name) === content) {
1084
+ continue;
1085
+ }
1086
+ // The agent edited a pre-existing pool task — restore it from HEAD.
1087
+ await gitSoft(
1088
+ ['checkout', 'HEAD', '--', workItemRel('tasks-ready', name)],
1089
+ cwd,
1090
+ env,
1091
+ );
1092
+ continue;
1093
+ }
1094
+ // The agent introduced a NEW file in the pool: drop it.
1095
+ rmSync(abs, {force: true});
1096
+ }
1097
+ }
1098
+
1099
+ /** Run git, returning the raw result (no throw) — for soft checks. */
1100
+ function gitSoft(
1101
+ args: string[],
1102
+ cwd: string,
1103
+ env: NodeJS.ProcessEnv | undefined,
1104
+ ): Promise<RunResult> {
1105
+ return runAsync('git', args, cwd, {env});
1106
+ }
1107
+
1108
+ /** Run git; throw on non-zero (genuinely unexpected plumbing failures). */
1109
+ async function gitHard(
1110
+ args: string[],
1111
+ cwd: string,
1112
+ env: NodeJS.ProcessEnv | undefined,
1113
+ ): Promise<RunResult> {
1114
+ const result = await runAsync('git', args, cwd, {env});
1115
+ if (result.status !== 0) {
1116
+ throw new Error(
1117
+ `git ${args.join(' ')} failed (exit ${result.status}): ${result.stderr.trim()}`,
1118
+ );
1119
+ }
1120
+ return result;
1121
+ }
1122
+
1123
+ /**
1124
+ * Build the needs-attention REASON for a task-SET ACCEPTANCE GATE block (task
1125
+ * `slice-acceptance-gate`): the fresh-context review of the produced set returned
1126
+ * `block`, so the prd is routed to `work/needs-attention/` with the review's
1127
+ * blocking findings as the body prose and NO tasks landed. Takes the core's
1128
+ * structured `reviewBlockReason` (the gate's blocking findings); falls back to a
1129
+ * generic line when absent. DISTINCT from the improver loop's
1130
+ * {@link decompositionUnclearReason} (which carries the loop's open questions).
1131
+ */
1132
+ function taskGateBlockedReason(
1133
+ slug: string,
1134
+ findingsReason: string | undefined,
1135
+ ): string {
1136
+ const head =
1137
+ `The task acceptance gate (fresh-context review of the produced SET) blocked ` +
1138
+ `'${slug}'. The prd is routed to needs-attention with no tasks landed; a human ` +
1139
+ `must resolve the blocking findings, then re-task.`;
1140
+ return findingsReason ? `${head}\n\n${findingsReason}` : head;
1141
+ }
1142
+
1143
+ /**
1144
+ * Build the needs-attention REASON for a decomposition-unclear loop verdict (the
1145
+ * prd is routed to `work/needs-attention/` with these open questions, no guessed
1146
+ * tasks). Prose only — recorded as the prd's needs-attention body block.
1147
+ */
1148
+ function decompositionUnclearReason(slug: string, questions: string[]): string {
1149
+ const head =
1150
+ `The tasker review→edit loop could not converge on a sound decomposition of ` +
1151
+ `'${slug}' (--tasker-loop-max exhausted with unresolved blockers). The prd is routed ` +
1152
+ `to needs-attention with no guessed tasks; a human must resolve:`;
1153
+ const body =
1154
+ questions.length > 0
1155
+ ? questions.map((q) => `- ${q}`).join('\n')
1156
+ : '- (no specific questions surfaced; the decomposition is broadly unclear)';
1157
+ return `${head}\n${body}`;
1158
+ }
1159
+
1160
+ /**
1161
+ * Mark a candidate task file `needsAnswers: true` and record its open questions in
1162
+ * its body (the loop's uncertain-task routing outcome). The runner writes the
1163
+ * file; the agent does no git/disk-escape. A path outside `work/tasks/backlog/`
1164
+ * is skipped (defensive). A relative `work/tasks/backlog/<slug>.md` that does not
1165
+ * exist is skipped with a note (never crash the transition).
1166
+ */
1167
+ function markTaskNeedsAnswers(
1168
+ cwd: string,
1169
+ relPath: string,
1170
+ questions: string[],
1171
+ note: (message: string) => void,
1172
+ ): void {
1173
+ const normalized = relPath.replace(/\\/g, '/');
1174
+ if (
1175
+ !normalized.startsWith(`${STAGED_TASKS_DIR}/`) ||
1176
+ normalized.includes('..')
1177
+ ) {
1178
+ note(
1179
+ `Skipped a needsAnswers mark outside ${STAGED_TASKS_DIR}/ (${relPath}).`,
1180
+ );
1181
+ return;
1182
+ }
1183
+ const abs = join(cwd, normalized);
1184
+ if (!existsSync(abs)) {
1185
+ note(`Skipped a needsAnswers mark for missing candidate task ${relPath}.`);
1186
+ return;
1187
+ }
1188
+ const current = readFileSync(abs, 'utf8');
1189
+ const marked = setNeedsAnswersMarker(current, true);
1190
+ writeFileSync(abs, appendQuestionsBlock(marked, questions));
1191
+ }
1192
+
1193
+ /** The heading that opens the open-questions block in an uncertain task body. */
1194
+ const OPEN_QUESTIONS_HEADING = '## Open questions';
1195
+
1196
+ /**
1197
+ * Append an `## Open questions` block (prose, never a frontmatter field —
1198
+ * WORK-CONTRACT rule 3) listing the loop's surfaced questions to an uncertain
1199
+ * task's body. A human answers these before the task becomes agent-buildable.
1200
+ */
1201
+ function appendQuestionsBlock(content: string, questions: string[]): string {
1202
+ if (questions.length === 0) {
1203
+ return content;
1204
+ }
1205
+ const base = content.replace(/\s*$/, '');
1206
+ const items = questions.map((q) => `- ${q}`).join('\n');
1207
+ return [base, '', OPEN_QUESTIONS_HEADING, '', items, ''].join('\n');
1208
+ }
1209
+
1210
+ /**
1211
+ * Resolve the AGENT tasking gate for `slug`: the pure predicate
1212
+ * (`needsAnswers !== true && humanOnly !== true && autoTask`) plus the
1213
+ * cross-prd `taskedAfter` ordering, resolved against `work/prds/tasked/` residence of
1214
+ * the prds present in the checkout.
1215
+ */
1216
+ function resolveAgentGate(
1217
+ cwd: string,
1218
+ slug: string,
1219
+ specFm: {humanOnly?: boolean; needsAnswers?: boolean; taskedAfter: string[]},
1220
+ autoTask: boolean | undefined,
1221
+ explicit: boolean,
1222
+ ): TaskingEligibilityResult {
1223
+ return resolveTaskingEligibility({
1224
+ humanOnly: specFm.humanOnly,
1225
+ needsAnswers: specFm.needsAnswers,
1226
+ taskedAfter: specFm.taskedAfter,
1227
+ taskedSlugs: readTaskedSlugs(cwd),
1228
+ autoTask: autoTask ?? false,
1229
+ explicit,
1230
+ });
1231
+ }
1232
+
1233
+ /** Build an HONEST gate-refusal message naming WHY the agent skipped the prd. */
1234
+ function gateRefusalReason(
1235
+ slug: string,
1236
+ specFm: {humanOnly?: boolean; needsAnswers?: boolean},
1237
+ eligibility: TaskingEligibilityResult,
1238
+ options: PerformTaskOptions,
1239
+ ): string {
1240
+ const reasons: string[] = [];
1241
+ if (specFm.humanOnly === true) {
1242
+ reasons.push('the prd is humanOnly (a human must drive its tasking)');
1243
+ }
1244
+ if (specFm.needsAnswers === true) {
1245
+ reasons.push(
1246
+ 'the prd has needsAnswers (open questions block auto-tasking)',
1247
+ );
1248
+ }
1249
+ // The autoTask POLICY only refuses on the NON-explicit (auto-pick pool) path:
1250
+ // an explicitly-named `do prd:<slug>` is authorized by the naming itself (the
1251
+ // build path's autoBuild precedent), so the policy is never the reason there.
1252
+ if (
1253
+ options.explicit !== true &&
1254
+ specFm.humanOnly !== true &&
1255
+ specFm.needsAnswers !== true &&
1256
+ (options.autoTask ?? false) !== true
1257
+ ) {
1258
+ reasons.push("the repo's autoTask policy is off");
1259
+ }
1260
+ if (!eligibility.taskedAfter.satisfied) {
1261
+ reasons.push(
1262
+ `taskedAfter prd(s) not yet tasked: ${eligibility.taskedAfter.missing.join(', ')}`,
1263
+ );
1264
+ }
1265
+ const why =
1266
+ reasons.length > 0 ? reasons.join('; ') : 'the tasking gate refused';
1267
+ return `Skipped tasking '${slug}': ${why}.`;
1268
+ }
1269
+
1270
+ /**
1271
+ * Read the set of slugs whose prds are already TASKED in this checkout — RESIDENCE
1272
+ * in `work/prds/tasked/` (the tasked resting state, task `prd-sliced-folder-step-a`
1273
+ * / prd `slicing-coherence` US #9), the build-machine `done/` analogue. The FOLDER
1274
+ * is the source of truth; the `tasked:` frontmatter marker was removed entirely in
1275
+ * `remove-sliced-marker-step-b` and is NOT consulted. So `taskedAfter` resolves
1276
+ * against `prd-tasked/` residence
1277
+ * (mirroring `blockedBy` -> `done/`). A missing folder reads as empty. The slug is
1278
+ * read from each file's frontmatter `slug:`, falling back to the filename — the same
1279
+ * shape the task readers use.
1280
+ */
1281
+ function readTaskedSlugs(cwd: string): Set<string> {
1282
+ const slugs = new Set<string>();
1283
+ const dir = workFolderPath(cwd, 'specs-tasked');
1284
+ for (const file of listMarkdown(dir)) {
1285
+ const content = readFileSync(join(dir, file), 'utf8');
1286
+ const fm = parseFrontmatter(content);
1287
+ slugs.add(fm.slug ?? file.replace(/\.md$/i, ''));
1288
+ }
1289
+ return slugs;
1290
+ }
1291
+
1292
+ /**
1293
+ * Build the tasking PROMPT: instruct a fresh-context agent to apply the
1294
+ * **tasking discipline** (`work/protocol/TASKING-PROTOCOL.md`) to the held
1295
+ * prd at `work/prds/ready/<slug>.md` and to EMIT tracer-bullet vertical
1296
+ * tasks under `work/tasks/backlog/`. The discipline body (the tracer-bullet
1297
+ * rules, the two-axis gate guidance, the confidence check, file-orthogonality,
1298
+ * the emitted task shape) lives in `TASKING-PROTOCOL.md` — NOT inlined here
1299
+ * (task `slicing-protocol-doc-and-vocabulary-fix`). The shape's source of
1300
+ * truth is the frontmatter parser + the task template (`work/protocol/
1301
+ * task-template.md`); the doc DESCRIBES it (D2).
1302
+ *
1303
+ * This builder owns ONLY the PER-BUILDER framing: who you are (a fresh-context
1304
+ * tasker for ONE prd), where the held prd is, where the emitted task files
1305
+ * MUST land (the staging folder, never the pool), and the runner-owns-git
1306
+ * boundary on the agent path. The shared discipline body is NOT duplicated
1307
+ * here.
1308
+ */
1309
+ function buildTaskingSpec(slug: string, _spec: string | undefined): string {
1310
+ return [
1311
+ `You are a FRESH-CONTEXT tasker for the prd \`work/prds/ready/${slug}.md\`.`,
1312
+ `Apply the tasking discipline defined in \`work/protocol/TASKING-PROTOCOL.md\``,
1313
+ `(the in-band, protocol-native tasking protocol every set-up repo carries; the`,
1314
+ `human-facing pointer is \`skills/to-task/SKILL.md\`) to this ONE prd:`,
1315
+ `decompose it into independently-grabbable, tracer-bullet vertical tasks.`,
1316
+ `Read the prd fully first.`,
1317
+ ``,
1318
+ `The discipline rules — the tracer-bullet test, the two-axis gate guidance`,
1319
+ `(\`humanOnly\` is NARROW; \`needsAnswers\` flags genuine uncertainty), the`,
1320
+ `confidence check that REPLACES the human-quiz step when no human is present,`,
1321
+ `the file-orthogonality preference, the prd-vs-task gate disjointness, and`,
1322
+ `the emitted task shape — ALL live in that doc. Read them there.`,
1323
+ ``,
1324
+ `No human is present, so apply the CONFIDENCE CHECK (\`TASKING-PROTOCOL.md\``,
1325
+ `step 4): only emit tasks you would have gotten a human to approve. If`,
1326
+ `granularity, dependency order, a gate, or a seam is genuinely unresolved, set`,
1327
+ `\`needsAnswers: true\` on the specific uncertain task (questions in its body)`,
1328
+ `rather than guessing — or, if the whole decomposition is unclear, stop and`,
1329
+ `route the prd to needs-attention with the questions.`,
1330
+ ``,
1331
+ `WRITE EVERY emitted task file under \`${STAGED_TASKS_DIR}/\` (the STAGING folder)`,
1332
+ `— NEVER \`work/tasks/ready/\`. \`work/tasks/ready/\` is the agent-eligible POOL and`,
1333
+ `the runner owns the runner/human-only promotion into it; the tasker's staging`,
1334
+ `folder is \`work/tasks/backlog/\`. A write outside the staging folder is dropped`,
1335
+ `by the runner-deterministic placement resolver.`,
1336
+ ``,
1337
+ `Set each task's \`prd:\` field to the source prd slug (\`${slug}\`) so the`,
1338
+ `link back to the prd survives.`,
1339
+ ``,
1340
+ `Do NOT perform any git operations — do not stage, commit, push, or move any`,
1341
+ `files. The RUNNER owns every git-state transition (it commits the produced`,
1342
+ `tasks, releases the tasking lock, and moves the prd into \`work/prds/tasked/\`).`,
1343
+ ].join('\n');
1344
+ }
1345
+
1346
+ /** Run the task agent. Prefers the injected runner; else the harness seam. */
1347
+ async function runTaskAgent(
1348
+ options: PerformTaskOptions,
1349
+ cwd: string,
1350
+ prompt: string,
1351
+ slug: string,
1352
+ ): Promise<{ok: boolean; detail?: string}> {
1353
+ // The tasker AGENT launches with the AMBIENT env (`agentEnv`), never the
1354
+ // identity-scoped `env` (an agent must not act as the bot). Falls back to `env`
1355
+ // when no identity is configured.
1356
+ const agentEnv = options.agentEnv ?? options.env;
1357
+ if (options.dorfl) {
1358
+ return options.dorfl({cwd, prompt, slug, env: agentEnv});
1359
+ }
1360
+ const harness = options.harness ?? new NullHarness();
1361
+ const launched = await launchWithOptionalWatch({
1362
+ harness,
1363
+ dir: cwd,
1364
+ slug,
1365
+ command: options.agentCmd ?? '',
1366
+ prompt,
1367
+ model: options.model,
1368
+ sessionId: `task-${slug}`,
1369
+ sessionsDir: options.sessionsDir,
1370
+ env: agentEnv,
1371
+ });
1372
+ return {ok: launched.ok, detail: launched.detail};
1373
+ }
1374
+
1375
+ /** A snapshot of {@link STAGED_TASKS_DIR}: filename → file content. */
1376
+ function snapshotStagedTasks(cwd: string): Map<string, string> {
1377
+ const dir = join(cwd, STAGED_TASKS_DIR);
1378
+ const snap = new Map<string, string>();
1379
+ for (const file of listMarkdown(dir)) {
1380
+ snap.set(file, readFileSync(join(dir, file), 'utf8'));
1381
+ }
1382
+ return snap;
1383
+ }
1384
+
1385
+ /**
1386
+ * Repo-relative paths of the {@link STAGED_TASKS_DIR}`/*.md` files the agent
1387
+ * NEWLY created or CHANGED vs the pre-run snapshot — exactly what the runner
1388
+ * captures + commits. (An untouched pre-existing staged task is NOT
1389
+ * re-committed.) The agent's staging folder is `work/tasks/backlog/`; writes to
1390
+ * the pool `work/tasks/ready/` are scrubbed at stage time, never picked up here.
1391
+ */
1392
+ function newOrChangedStagedTasks(
1393
+ cwd: string,
1394
+ before: Map<string, string>,
1395
+ ): string[] {
1396
+ const dir = join(cwd, STAGED_TASKS_DIR);
1397
+ const changed: string[] = [];
1398
+ for (const file of listMarkdown(dir)) {
1399
+ const content = readFileSync(join(dir, file), 'utf8');
1400
+ if (before.get(file) !== content) {
1401
+ changed.push(stagedTaskPath(file));
1402
+ }
1403
+ }
1404
+ return changed.sort();
1405
+ }
1406
+
1407
+ /** Snapshot the POOL `work/tasks/ready/` (for the agent-write fence at stage time). */
1408
+ function snapshotPool(cwd: string): Map<string, string> {
1409
+ const dir = workFolderPath(cwd, 'tasks-ready');
1410
+ const snap = new Map<string, string>();
1411
+ for (const file of listMarkdown(dir)) {
1412
+ snap.set(file, readFileSync(join(dir, file), 'utf8'));
1413
+ }
1414
+ return snap;
1415
+ }
1416
+
1417
+ /** Read the produced backlog tasks' content keyed by repo-relative path. */
1418
+ function collectEmittedTasks(
1419
+ cwd: string,
1420
+ relPaths: string[],
1421
+ ): Record<string, string> {
1422
+ const out: Record<string, string> = {};
1423
+ for (const rel of relPaths) {
1424
+ out[rel] = readFileSync(join(cwd, rel), 'utf8');
1425
+ }
1426
+ return out;
1427
+ }
1428
+
1429
+ /** List `*.md` files in `dir`, sorted; an absent dir reads as empty. */
1430
+ function listMarkdown(dir: string): string[] {
1431
+ let entries: string[];
1432
+ try {
1433
+ entries = readdirSync(dir);
1434
+ } catch {
1435
+ return [];
1436
+ }
1437
+ return entries.filter((name) => isWorkItemFile(name)).sort();
1438
+ }