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/dist/intake.js ADDED
@@ -0,0 +1,1628 @@
1
+ import { mkdirSync, writeFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { runAsync } from './git.js';
4
+ import { workFolderRel, workItemRel } from './work-layout.js';
5
+ import { paramCase } from './brand.js';
6
+ import { performIntegration, } from './integration-core.js';
7
+ import { integrationFromFlags } from './complete.js';
8
+ import { placementFolder, resolvePlacement, } from './placement.js';
9
+ import { identityEnv, assertTransportAllowed, } from './identity.js';
10
+ import { NullHarness } from './harness.js';
11
+ import { workBranchRef } from './slug-namespace.js';
12
+ import { launchWithOptionalWatch } from './agent-launch.js';
13
+ import { GitHubIssueProvider, PROCESSING_LOCK_LABEL, } from './issue-provider.js';
14
+ import { extractJsonObjectSpan } from './verdict-json.js';
15
+ import { parseReviewVerdict, reviewDisciplinePrompt, verdictContractPrompt, } from './review-verdict.js';
16
+ import { stampIntakeMarker, computeSeenDelta, } from './intake-marker.js';
17
+ import { triageIntake } from './intake-triage.js';
18
+ import { renderTaskBody, renderSpecBody } from './buildable-body.js';
19
+ const DEFAULT_ARBITER = 'origin';
20
+ /**
21
+ * **The STAGED-prds dir** (prd `staging-pool-position-gate-and-trust-model`,
22
+ * task `pre-prd-staging-pool-split-and-untrusted-prd-placement`, governing
23
+ * ADR `placement-is-runner-deterministic-humanonly-is-agent-judgement`). When
24
+ * the runner-deterministic placement resolver picks the staging side for an
25
+ * `intake`-authored prd, the runner writes the prd file HERE instead of in
26
+ * `work/prds/ready/`. An item born in `prds/proposed/` is durable + readable but NOT in
27
+ * the tasking candidate POOL (`work/prds/ready/` is the pool). A runner/human-owned promotion
28
+ * ({@link promoteFromPreSpec} in `needs-attention.ts`) moves an approved prd
29
+ * `prds/proposed/ → prds/ready/` to make it taskable.
30
+ */
31
+ export const STAGED_SPECS_DIR = workFolderRel('specs-proposed');
32
+ /**
33
+ * The POOL folder specs land in when the runner-deterministic placement
34
+ * resolver chooses the pool side (`specsLandIn: 'ready'` + a trusted origin, or
35
+ * an `--specs-land-in ready` operator override). This is `work/specs/ready/`,
36
+ * the tasking candidate pool.
37
+ */
38
+ const POOL_SPECS_DIR = workFolderRel('specs-ready');
39
+ /** The placement slots for the spec lifecycle (folder names). */
40
+ const SPEC_PLACEMENT_SLOTS = {
41
+ staging: STAGED_SPECS_DIR,
42
+ pool: POOL_SPECS_DIR,
43
+ };
44
+ /**
45
+ * Map the `specsLandIn` value spelling (`pre-proposed` | `ready`) onto the
46
+ * resolver's lifecycle-generic side enum (`staging` | `pool`). Returns
47
+ * `undefined` when no value is set, so the resolver's next precedence rung
48
+ * applies (the built-in floor). The spec twin of `landingToSide` on the
49
+ * tasker path — same shape, different slots.
50
+ */
51
+ function specLandingToSide(landing) {
52
+ if (landing === 'pre-proposed')
53
+ return 'staging';
54
+ if (landing === 'ready')
55
+ return 'pool';
56
+ return undefined;
57
+ }
58
+ /** Default per-outcome integration mode when no flag selects one — propose (matches `do`). */
59
+ const DEFAULT_INTEGRATION = 'propose';
60
+ /**
61
+ * Resolve the GRANULAR per-type axis (`--merge-<t>` / `--propose-<t>`) for ONE
62
+ * artifact type, REUSING {@link integrationFromFlags} for its mutual-exclusion +
63
+ * "mutually exclusive" error message (the same-type-both usage error) — so the
64
+ * granular axis is NOT a forked second resolver, just `integrationFromFlags`
65
+ * applied to the per-type pair. Returns the granular mode, or `undefined` when
66
+ * neither granular flag for this type was given (the aggregate/default then
67
+ * decides). The error message is reworded to name the granular flag pair.
68
+ */
69
+ function granularFromFlags(type, merge, propose) {
70
+ try {
71
+ return integrationFromFlags({ merge, propose });
72
+ }
73
+ catch {
74
+ throw new Error(`--merge-${type} and --propose-${type} are mutually exclusive; pass at most one.`);
75
+ }
76
+ }
77
+ /**
78
+ * The PURE per-outcome integration mode resolution (prd `issue-intake` US #9 —
79
+ * the canonical table). Given ONLY the flag set, resolve BOTH per-type modes in
80
+ * one eager pass (so a usage error is caught before the runtime verdict is even
81
+ * known). The rules, all decided in the prd:
82
+ *
83
+ * - **unset ⇒ propose for BOTH** (conservative default; matches `do`).
84
+ * - **aggregates:** `--merge` ⇒ merge both; `--propose` ⇒ propose both (this axis
85
+ * COMPOSES the existing {@link integrationFromFlags}, reusing its mutual
86
+ * exclusion + error message).
87
+ * - **granular routes per type:** `--merge-spec` merges a spec (and leaves a task at
88
+ * the aggregate/default), etc.
89
+ * - **GRANULAR OVERRIDES AGGREGATE:** `--merge --propose-task` ⇒ merge a spec,
90
+ * propose a task.
91
+ * - **same type + both modes is a usage ERROR:** `--merge-spec --propose-spec` (and
92
+ * `--merge-task --propose-task`), and the aggregate `--merge --propose`.
93
+ *
94
+ * Throws (a usage error) on any mutually-exclusive pair. The dispatcher picks the
95
+ * field matching the runtime verdict's type; ask/bounce never integrate, so the
96
+ * modes are no-ops for them.
97
+ *
98
+ * `defaultMode` is the FALLBACK when NEITHER a granular nor the aggregate flag
99
+ * selects a mode for a type — it defaults to `propose` (so the pure table reads
100
+ * "unset ⇒ propose for both"), but the CLI passes the per-repo/global
101
+ * config-resolved mode so the established precedence chain (flag > per-repo >
102
+ * global > default) is preserved, exactly as `do`/`complete` resolve it.
103
+ */
104
+ export function resolveIntakeIntegrationModes(flags, defaultMode = DEFAULT_INTEGRATION) {
105
+ // AGGREGATE axis — reuse the existing resolver (its mutual exclusion + the
106
+ // "--merge and --propose are mutually exclusive" message). `undefined` ⇒ unset.
107
+ const aggregate = integrationFromFlags({
108
+ merge: flags.merge,
109
+ propose: flags.propose,
110
+ });
111
+ // GRANULAR axes — `integrationFromFlags` per type (the same-type-both error).
112
+ const specGranular = granularFromFlags('spec', flags.mergeSpec, flags.proposeSpec);
113
+ const taskGranular = granularFromFlags('task', flags.mergeTask, flags.proposeTask);
114
+ // GRANULAR OVERRIDES AGGREGATE; aggregate over the (config/propose) default.
115
+ // The result KEYS carry the `spec` vocabulary (canonical); the per-type flag axes
116
+ // (`--merge-spec`/`--merge-task`) carry the same `spec` spelling (the cli-flag
117
+ // rename landed in batch 4f).
118
+ return {
119
+ spec: specGranular ?? aggregate ?? defaultMode,
120
+ task: taskGranular ?? aggregate ?? defaultMode,
121
+ };
122
+ }
123
+ /**
124
+ * Run `intake <N>` end-to-end (the LOCAL one-shot). Never throws for the expected
125
+ * agent-failed / stale / usage cases — those are returned with the corresponding
126
+ * exit code and outcome. The runner owns all git/seam side-effects; the agent only
127
+ * DRAFTS the verdict.
128
+ */
129
+ export async function performIntake(options) {
130
+ const note = options.note ?? (() => { });
131
+ const arbiter = options.arbiter ?? DEFAULT_ARBITER;
132
+ const cwd = options.cwd;
133
+ // `env` is intake's GIT + provider env, scoped to the configured identity (the
134
+ // `gh` issue ops, the push, the PR). The intake AGENT launches (decision agent
135
+ // + lone-task review agent) read `options.env` directly — they stay AMBIENT
136
+ // (an agent must not act as the bot). Absent identity ⇒ `options.env` unchanged.
137
+ // A configured identity that cannot be resolved (e.g. `tokenEnv` names an unset
138
+ // env var) is a clean usage error, never a crash or a silent ambient fallback.
139
+ const issueNumber = options.issueNumber;
140
+ let env;
141
+ try {
142
+ env = identityEnv(options.identity, options.env ?? process.env);
143
+ }
144
+ catch (err) {
145
+ const message = err instanceof Error ? err.message : String(err);
146
+ note(message);
147
+ return { exitCode: 1, outcome: 'usage-error', issueNumber, message };
148
+ }
149
+ const issueProvider = options.issueProvider ?? new GitHubIssueProvider();
150
+ // Push-time transport-coherence guard (identity): if a configured identity
151
+ // forbids the arbiter's transport, fail with a clear message rather than
152
+ // silently pushing under an ambient credential. Resolve the arbiter URL softly
153
+ // (a non-zero/unknown URL is skipped — the guard is a no-op without an identity
154
+ // or a resolvable URL).
155
+ if (options.identity !== undefined) {
156
+ const urlRes = await runAsync('git', ['remote', 'get-url', arbiter], cwd, {
157
+ env,
158
+ });
159
+ if (urlRes.status === 0) {
160
+ try {
161
+ assertTransportAllowed(options.identity, urlRes.stdout.trim());
162
+ }
163
+ catch (err) {
164
+ const message = err instanceof Error ? err.message : String(err);
165
+ note(message);
166
+ return { exitCode: 1, outcome: 'usage-error', issueNumber, message };
167
+ }
168
+ }
169
+ }
170
+ // 1. READ the issue + thread via the seam (the core never imports `gh`; only the
171
+ // adapter shells out). A read failure surfaces as a usage error — `intake`
172
+ // cannot decide without the issue.
173
+ let issue;
174
+ let comments;
175
+ try {
176
+ issue = await issueProvider.getIssue({ cwd, issueNumber, env });
177
+ comments = await issueProvider.listComments({ cwd, issueNumber, env });
178
+ }
179
+ catch (err) {
180
+ const detail = err instanceof Error ? err.message : String(err);
181
+ const message = `Could not read issue #${issueNumber}: ${detail}`;
182
+ note(message);
183
+ return { exitCode: 1, outcome: 'usage-error', issueNumber, message };
184
+ }
185
+ // 2. ACQUIRE the `processing` LOCK (prd `issue-intake` US #10): a TRANSIENT concurrency mutex
186
+ // that serialises two concurrent runs on the SAME issue. Read the labels; if
187
+ // the lock is ALREADY present, BACK OFF (do nothing — another run owns it). The
188
+ // winner ADDS the label and proceeds; the label is REMOVED on finish (success
189
+ // OR handled failure, in the `finally` below). It is NOT a `work/` CAS and NOT a
190
+ // label state-machine (ADR §12) — ONE transient lock label.
191
+ //
192
+ // Fail-vs-degrade (maintainer decision): a lock that is MEANINGFUL but cannot
193
+ // be taken must NOT silently proceed lock-less. Only a genuinely-UNSUPPORTED
194
+ // provider (no label concept at all) legitimately degrades to best-effort (the
195
+ // spec's provider-pluggability; CI's per-issue concurrency group is then the
196
+ // only serialiser — out of scope here). A real FAILURE on a label-supporting
197
+ // provider (e.g. `gh` unauthenticated) FAILS the run with the REAL cause
198
+ // surfaced, rather than misattributing it or proceeding without serialisation.
199
+ const labels = await issueProvider.getLabels({ cwd, issueNumber, env });
200
+ if (labels.outcome === 'failed') {
201
+ // The provider HAS labels but we could not READ the lock state — we cannot tell
202
+ // whether another run holds it, so guessing "free" could let two runs proceed.
203
+ // FAIL with the real cause (the actual `gh` stderr), not a hard-coded guess.
204
+ const message = `Intake of issue #${issueNumber} could not acquire the ` +
205
+ `\`${PROCESSING_LOCK_LABEL}\` lock: ${labels.instruction}`;
206
+ note(message);
207
+ return { exitCode: 1, outcome: 'lock-failed', issueNumber, message };
208
+ }
209
+ if (labels.outcome === 'ok' &&
210
+ labels.labels.includes(PROCESSING_LOCK_LABEL)) {
211
+ const message = `Intake of issue #${issueNumber} backed off: the \`${PROCESSING_LOCK_LABEL}\` ` +
212
+ `lock is already held by a concurrent run; doing nothing.`;
213
+ note(message);
214
+ return { exitCode: 0, outcome: 'locked', issueNumber, message };
215
+ }
216
+ let locked = false;
217
+ if (labels.outcome === 'ok') {
218
+ const acquired = await issueProvider.addLabel({
219
+ cwd,
220
+ issueNumber,
221
+ label: PROCESSING_LOCK_LABEL,
222
+ env,
223
+ });
224
+ if (acquired.outcome === 'failed') {
225
+ // The provider HAS labels but the ACQUIRE failed for a real reason (e.g. `gh`
226
+ // lost auth, or the label could not be created on a fresh repo). The lock is
227
+ // meaningful but unacquirable → FAIL with the real cause, do NOT proceed
228
+ // lock-less (which would let a concurrent run race us).
229
+ const message = `Intake of issue #${issueNumber} could not acquire the ` +
230
+ `\`${PROCESSING_LOCK_LABEL}\` lock: ${acquired.instruction}`;
231
+ note(message);
232
+ return { exitCode: 1, outcome: 'lock-failed', issueNumber, message };
233
+ }
234
+ locked = acquired.applied;
235
+ }
236
+ else {
237
+ // Non-label provider (genuinely UNSUPPORTED) → the ONLY legitimate degrade:
238
+ // proceed without the lock, surfaced honestly.
239
+ note(`Processing lock degraded: ${labels.instruction}`);
240
+ }
241
+ // INTERRUPTION-SAFETY (maintainer point 3): the `finally` below releases on every
242
+ // EXCEPTION path, but a SIGINT/SIGTERM (Ctrl-C, kill) unwinds the process WITHOUT
243
+ // running `finally` — which would LEAK the lock label and block all future intake
244
+ // runs on this issue. While the lock is held we install signal handlers that
245
+ // release it best-effort before the process exits. A leaked lock must ALSO be
246
+ // recoverable by hand and that recovery must be DISCOVERABLE, so we surface the
247
+ // exact manual command (`gh issue edit <N> --remove-label <label>`) whenever the
248
+ // best-effort release does not confirm.
249
+ const manualRecovery = `If the \`${PROCESSING_LOCK_LABEL}\` lock is left behind, release it with: ` +
250
+ `gh issue edit ${issueNumber} --remove-label '${PROCESSING_LOCK_LABEL}'`;
251
+ const releaseLock = createLockReleaser({
252
+ locked,
253
+ issueProvider,
254
+ cwd,
255
+ issueNumber,
256
+ env,
257
+ note,
258
+ manualRecovery,
259
+ });
260
+ const onSignal = (signal) => {
261
+ // Synchronous best-effort release on interruption, then re-raise the default
262
+ // disposition so the process still exits with the conventional signal code.
263
+ if (locked) {
264
+ note(`Received ${signal}; releasing the \`${PROCESSING_LOCK_LABEL}\` lock on issue #${issueNumber} before exit.`);
265
+ }
266
+ releaseLock.releaseSync();
267
+ process.removeListener('SIGINT', onSignal);
268
+ process.removeListener('SIGTERM', onSignal);
269
+ process.kill(process.pid, signal);
270
+ };
271
+ if (locked) {
272
+ process.once('SIGINT', onSignal);
273
+ process.once('SIGTERM', onSignal);
274
+ }
275
+ try {
276
+ return await decideAndDispatch(options, cwd, issue, comments, {
277
+ arbiter,
278
+ issueProvider,
279
+ note,
280
+ // The identity-scoped GIT/provider env (the `gh` ops, push, PR). The AGENT
281
+ // launches inside dispatch read `options.env` (ambient) — not this.
282
+ gitEnv: env,
283
+ });
284
+ }
285
+ finally {
286
+ // RELEASE the lock on FINISH (success OR handled failure). Only the winner that
287
+ // actually acquired it releases it — a degraded/best-effort run holds nothing.
288
+ process.removeListener('SIGINT', onSignal);
289
+ process.removeListener('SIGTERM', onSignal);
290
+ await releaseLock.release();
291
+ }
292
+ }
293
+ /**
294
+ * Build the lock RELEASER for {@link performIntake}: one `release()` (the normal
295
+ * async finish path) and one `releaseSync()` (the signal-handler path — a
296
+ * best-effort synchronous release that must run inside a signal handler). Both are
297
+ * no-ops when the run never held the lock (a degraded/unsupported run holds
298
+ * nothing). When a release does not CONFIRM, the manual-recovery hint is surfaced
299
+ * so a leaked lock stays recoverable AND discoverable (maintainer point 3).
300
+ */
301
+ function createLockReleaser(params) {
302
+ const { locked, issueProvider, cwd, issueNumber, env, note, manualRecovery } = params;
303
+ let released = false;
304
+ const surfaceFailure = (instruction) => {
305
+ note(`Processing lock release degraded: ${instruction}`);
306
+ note(manualRecovery);
307
+ };
308
+ return {
309
+ async release() {
310
+ if (!locked || released) {
311
+ return;
312
+ }
313
+ released = true;
314
+ const result = await issueProvider.removeLabel({
315
+ cwd,
316
+ issueNumber,
317
+ label: PROCESSING_LOCK_LABEL,
318
+ env,
319
+ });
320
+ if (!result.applied) {
321
+ surfaceFailure(result.instruction);
322
+ }
323
+ },
324
+ releaseSync() {
325
+ if (!locked || released) {
326
+ return;
327
+ }
328
+ released = true;
329
+ // A signal handler cannot await. The GitHub adapter's `removeLabel` shells out
330
+ // SYNCHRONOUSLY (spawnSync) inside its async wrapper, so firing it here still
331
+ // runs the `gh` call before the process exits — but we cannot READ the result
332
+ // synchronously through the async seam, so we ALWAYS surface the manual-recovery
333
+ // hint too. That keeps a leaked lock both recoverable AND discoverable even if
334
+ // the in-handler release did not complete (maintainer point 3).
335
+ void issueProvider.removeLabel({
336
+ cwd,
337
+ issueNumber,
338
+ label: PROCESSING_LOCK_LABEL,
339
+ env,
340
+ });
341
+ note(manualRecovery);
342
+ },
343
+ };
344
+ }
345
+ /**
346
+ * The DECIDE (prompt → verdict) + DISPATCH (the four-outcome table) band, run
347
+ * INSIDE the `processing` lock {@link performIntake} acquires/releases around it.
348
+ * Split out so the lock release is a clean `try`/`finally` in the caller (the lock
349
+ * MUST release on every terminal path — success or handled failure). The agent
350
+ * DRAFTS only; the runner owns every git/seam side-effect here.
351
+ */
352
+ async function decideAndDispatch(options, cwd, issue, comments, ctx) {
353
+ const { arbiter, issueProvider, note } = ctx;
354
+ const issueNumber = issue.number;
355
+ // `env` here is the identity-scoped GIT/provider env (the runner's `gh`/git
356
+ // ops). The AGENT launches (decision agent, lone-task review) use the AMBIENT
357
+ // `options.env` — an agent must not act as the bot.
358
+ const env = ctx.gitEnv;
359
+ // TRIAGE (deterministic, under the lock, BEFORE the prompt): decide whether to run
360
+ // the decision at all, built ENTIRELY on intake's own MARKER on the thread (no
361
+ // sidecar/cursor/bot-identity). It SKIPS when intake has the last word
362
+ // (`no-new-input`) or the issue is already terminal (`already-terminal`), and runs
363
+ // the prompt ONLY on genuine new human input. This is also the COMPLETE fix for the
364
+ // self-trigger hazard: intake's own freshly-posted comment carries a marker, so it
365
+ // is excluded from the human-comment check by construction.
366
+ const triage = triageIntake(comments);
367
+ if (triage.action === 'skip') {
368
+ const message = triage.outcome === 'no-new-input'
369
+ ? `Intake of issue #${issueNumber} found nothing new: it has the last word ` +
370
+ `on the thread and has already seen every human comment up to it; doing ` +
371
+ `nothing (the decision prompt did not run).`
372
+ : `Intake of issue #${issueNumber} skipped: the issue was already ` +
373
+ `transformed (a terminal intake marker is on the thread); a later human ` +
374
+ `comment does not re-open it (the decision prompt did not run).`;
375
+ note(message);
376
+ return { exitCode: 0, outcome: triage.outcome, issueNumber, message };
377
+ }
378
+ // DECIDE: prompt → VERDICT. The agent DRAFTS only (no git, no seam ops). Tests
379
+ // inject a canned verdict (the dispatcher's testable seam); production wires the
380
+ // harness. The prompt's judgement is not unit-tested — only the dispatch.
381
+ const prompt = buildIntakeDecisionSpec(issue, comments, triage);
382
+ let verdict;
383
+ try {
384
+ verdict = await runDecision(options, cwd, issue, comments, prompt);
385
+ }
386
+ catch (err) {
387
+ const detail = err instanceof Error ? err.message : String(err);
388
+ const message = `Intake decision failed for issue #${issueNumber}: ${detail}`;
389
+ note(message);
390
+ return { exitCode: 1, outcome: 'agent-failed', issueNumber, message };
391
+ }
392
+ // DISPATCH on the verdict — the FULL four-outcome decision table (prd
393
+ // `issue-intake`). The agent only DRAFTED the verdict; the runner owns every
394
+ // git/seam side-effect below (the in-band boundary): the write + integrate
395
+ // (task/prd) and the `postIssueComment` (ask/bounce).
396
+ //
397
+ // PER-OUTCOME integration (prd `issue-intake` US #9): the resolved mode is keyed on the runtime
398
+ // artifact TYPE — a `task` verdict integrates with the task mode, a `spec`
399
+ // verdict with the spec mode. Unset ⇒ propose for both. ask/bounce never
400
+ // integrate, so the modes are no-ops for them.
401
+ const modes = options.integration ?? { task: 'propose', spec: 'propose' };
402
+ // The per-run `seen=` DELTA (the HUMAN comment ids intake READ this run, excluding
403
+ // its own marker-comments + already-seen ids) the marker records on every comment
404
+ // intake posts — the chain-model primitive the TRIAGE unions into `seenSet`.
405
+ const seenDelta = computeSeenDelta(comments);
406
+ switch (verdict.outcome) {
407
+ case 'task':
408
+ return dispatchTask({
409
+ verdict,
410
+ issueNumber,
411
+ cwd,
412
+ arbiter,
413
+ integration: modes.task,
414
+ // The origin-trust STAMP, passed IN (not resolved here): the emitted task
415
+ // carries `origin: issue` + this verdict so the becomes-code checkpoint is
416
+ // not laundered. Unset ⇒ unstamped (a local intake ⇒ human/trusted).
417
+ originTrust: options.originTrust,
418
+ noPR: options.noPR,
419
+ providerInstance: options.providerInstance,
420
+ issueProvider,
421
+ // The bounded lone-task review seam (tests inject a canned verdict;
422
+ // production wires the harness via the default below).
423
+ reviewTask: resolveLoneTaskReviewGate(options),
424
+ seen: seenDelta,
425
+ env,
426
+ // The lone-task review AGENT launches AMBIENT (an agent must not act as
427
+ // the bot); `env` above is the identity-scoped git/provider env.
428
+ agentEnv: options.env,
429
+ note,
430
+ });
431
+ // The `spec` outcome is the parent-spec verdict; it dispatches through
432
+ // `modes.spec`. HARD CUTOVER: the legacy `prd` outcome case is GONE.
433
+ case 'spec':
434
+ return dispatchSpec({
435
+ verdict,
436
+ issueNumber,
437
+ cwd,
438
+ arbiter,
439
+ integration: modes.spec,
440
+ // Same origin-trust stamp on the prd outcome (propagated onto its tasks
441
+ // later by the tasker). Passed IN; not resolved here.
442
+ originTrust: options.originTrust,
443
+ noPR: options.noPR,
444
+ // RUNNER-DETERMINISTIC PLACEMENT (task
445
+ // `pre-prd-staging-pool-split-and-untrusted-prd-placement`): the
446
+ // configured-default + explicit-flag rungs, fed into the SHARED placement
447
+ // resolver alongside the `originTrust` stamp above. The resolver decides
448
+ // `prds/proposed/` (staging) vs `prds/ready/` (the tasking pool); `intake` never
449
+ // places itself.
450
+ specsLandIn: options.specsLandIn,
451
+ explicitSpecsLandIn: options.explicitSpecsLandIn,
452
+ providerInstance: options.providerInstance,
453
+ issueProvider,
454
+ seen: seenDelta,
455
+ env,
456
+ note,
457
+ });
458
+ case 'ask':
459
+ return dispatchComment({
460
+ outcome: 'asked',
461
+ cwd,
462
+ issueNumber,
463
+ issueProvider,
464
+ // The drafted clarifying question; a thin fallback keeps the comment
465
+ // non-empty if the agent left it blank.
466
+ body: verdict.question && verdict.question.trim() !== ''
467
+ ? verdict.question
468
+ : `Could you clarify issue #${issueNumber} so it can be acted on?`,
469
+ // STAMP the MARKER recording `kind=ask` (non-terminal — the TRIAGE owns
470
+ // that) + the `seen=` delta, so a re-run recognises this as intake's own
471
+ // turn and resumes only on genuine new human input.
472
+ markerKind: 'ask',
473
+ seen: seenDelta,
474
+ env,
475
+ note,
476
+ });
477
+ case 'bounce':
478
+ return dispatchComment({
479
+ outcome: 'bounced',
480
+ cwd,
481
+ issueNumber,
482
+ issueProvider,
483
+ // The drafted bounce message; a thin fallback restates the "file separate
484
+ // issues" ask. A bounce is TERMINAL: the issue is CLOSED atomically (this
485
+ // text as the closing comment + reason not planned).
486
+ body: verdict.bounceMessage && verdict.bounceMessage.trim() !== ''
487
+ ? verdict.bounceMessage
488
+ : `This issue looks like multiple unrelated concerns — please file ` +
489
+ `separate issues so each can be intaken on its own.`,
490
+ // STAMP `kind=bounced` (TERMINAL — the TRIAGE then SKIPS `already-terminal`
491
+ // on a later human comment) + the `seen=` delta.
492
+ markerKind: 'bounced',
493
+ seen: seenDelta,
494
+ env,
495
+ note,
496
+ });
497
+ }
498
+ }
499
+ /**
500
+ * DISPATCH the `ask` / `bounce` outcomes — the SHARED comment band, which now
501
+ * BRANCHES on the outcome:
502
+ *
503
+ * - **ask** (non-terminal): `postIssueComment` the drafted question, emit NOTHING,
504
+ * and LEAVE THE ISSUE OPEN — it waits for the thread to be answered (a later run
505
+ * resumes from it). The task/prd path also never closes (CI's close-job does,
506
+ * via the `issue:` field). Intake closes ONLY on BOUNCE.
507
+ * - **bounce** (TERMINAL): the asks are unrelated and must be re-filed, so an OPEN
508
+ * issue is a dishonest "still in play" signal. Intake CLOSES the issue
509
+ * ATOMICALLY via a single `closeIssue` carrying the bounce text as the closing
510
+ * comment + `reason: not planned` (one call — no post-then-close partial-failure
511
+ * window). The result's `closed` reflects it.
512
+ *
513
+ * Both the comment poster and the atomic close are advisory and DEGRADE (a
514
+ * missing/unauthenticated `gh` never throws — the text/real cause is surfaced via
515
+ * `ghFailureReason`, never a hard-coded guess), so the terminal outcome is
516
+ * unchanged (`asked`/`bounced`, exit 0) and the run still terminates cleanly.
517
+ */
518
+ async function dispatchComment(params) {
519
+ const { outcome, cwd, issueNumber, issueProvider, body, markerKind, seen, env, note, } = params;
520
+ // STAMP the intake MARKER onto the body so a re-run recognises this as intake's
521
+ // own comment (the SOLE self-recognition signal — no author identity). Hidden HTML
522
+ // comment; renders as nothing, present in the raw markdown the TRIAGE parses.
523
+ const stamped = stampIntakeMarker(body, { kind: markerKind, seen });
524
+ if (outcome === 'bounced') {
525
+ // BOUNCE is TERMINAL: CLOSE the issue ATOMICALLY (bounce text as the closing
526
+ // comment + reason not planned) in ONE call — no separate postIssueComment, no
527
+ // post-then-close window. The close DEGRADES (never throws) on a missing/
528
+ // unauthenticated `gh`, surfacing the REAL cause; the terminal outcome stays
529
+ // `bounced`/exit 0 regardless.
530
+ const close = await issueProvider.closeIssue({
531
+ cwd,
532
+ issueNumber,
533
+ comment: stamped,
534
+ reason: 'not planned',
535
+ env,
536
+ });
537
+ const tail = close.closed
538
+ ? 'the issue was closed (as not planned) with the bounce comment'
539
+ : `the issue could NOT be closed (${close.instruction})`;
540
+ const message = `Intake bounced issue #${issueNumber}; emitted no artifact and closed the ` +
541
+ `issue as not planned — ${tail}.`;
542
+ note(message);
543
+ return {
544
+ exitCode: 0,
545
+ outcome,
546
+ issueNumber,
547
+ commented: close.closed,
548
+ closed: close.closed,
549
+ message,
550
+ };
551
+ }
552
+ // ASK (non-terminal): post the clarifying question and LEAVE THE ISSUE OPEN.
553
+ const posted = await issueProvider.postIssueComment({
554
+ cwd,
555
+ issueNumber,
556
+ body: stamped,
557
+ env,
558
+ });
559
+ const tail = posted.posted
560
+ ? 'the comment was posted'
561
+ : `the comment could NOT be posted (${posted.instruction})`;
562
+ const message = `Intake asked a clarifying question on issue #${issueNumber}; emitted no ` +
563
+ `artifact and left the issue open — ${tail}.`;
564
+ note(message);
565
+ return {
566
+ exitCode: 0,
567
+ outcome,
568
+ issueNumber,
569
+ commented: posted.posted,
570
+ message,
571
+ };
572
+ }
573
+ /**
574
+ * DISPATCH the `task` outcome: derive a content-derived slug, write
575
+ * `work/backlog/<slug>.md` (`covers: []`, NO `prd:`) carrying `issue: N` (the
576
+ * lone-task closure link, NOT `Fixes #N`), and integrate via {@link
577
+ * performIntegration}. The runner owns the git: it onboards a
578
+ * `work/<slug>` branch off fresh `<arbiter>/main`, then the lifecycle `stage`
579
+ * writes + stages the task and the band commits + rebases + integrates it. The
580
+ * agent did NO git/seam ops.
581
+ */
582
+ async function dispatchTask(params) {
583
+ const { verdict, issueNumber, cwd, arbiter, integration, originTrust, noPR, providerInstance, issueProvider, reviewTask, seen, env, agentEnv, note, } = params;
584
+ // A content-derived slug — NEVER a counter (prd `issue-intake` US #8). Prefer the drafted
585
+ // `taskSlug`, else derive from the drafted title; sanitise either through
586
+ // `paramCase` so the filename + frontmatter slug are well-formed.
587
+ const slug = resolveSlug(verdict);
588
+ if (slug === '') {
589
+ const message = `Intake produced a 'task' verdict for issue #${issueNumber} with no usable ` +
590
+ `slug/title to derive a content-derived slug from (never a counter).`;
591
+ note(message);
592
+ return { exitCode: 1, outcome: 'usage-error', issueNumber, message };
593
+ }
594
+ const relPath = workItemRel('tasks-ready', `${slug}.md`);
595
+ // BOUNDED INTERNAL REVIEW (observation
596
+ // `intake-lone-task-skips-adversarial-review-the-prd-path-gets`, rulings A/B/C):
597
+ // the `do prd:` path gets `runTaskReviewLoop`; the lone-TASK path got NOTHING.
598
+ // AFTER the `task` verdict and BEFORE the write/integrate, run a bounded (3-round,
599
+ // HARD-CAPPED) adversarial self-review on the SINGLE drafted task. It mutates the
600
+ // candidate body IN MEMORY (no `work/backlog/` write pre-convergence). A launch/
601
+ // parse failure THROWS — `decideAndDispatch`'s try/catch maps it onto `agent-failed`
602
+ // (never a silent emit of the un-reviewed task).
603
+ let review;
604
+ try {
605
+ review = await runLoneTaskReview({
606
+ slug,
607
+ issueNumber,
608
+ draftTitle: verdict.taskTitle ?? slug,
609
+ draftBody: verdict.taskBody,
610
+ gate: reviewTask,
611
+ cwd,
612
+ // The review AGENT launches AMBIENT (never the identity-scoped env).
613
+ env: agentEnv,
614
+ note,
615
+ });
616
+ }
617
+ catch (err) {
618
+ // A review-agent launch/parse FAILURE DEGRADES honestly onto the EXISTING
619
+ // `agent-failed` outcome (exit 1) — NEVER a silent emit of the un-reviewed
620
+ // task. The SAME try/catch discipline the decision step uses.
621
+ const detail = err instanceof Error ? err.message : String(err);
622
+ const message = `Intake lone-task review failed for issue #${issueNumber}: ${detail}`;
623
+ note(message);
624
+ return { exitCode: 1, outcome: 'agent-failed', issueNumber, message };
625
+ }
626
+ if (review.outcome === 'non-converge') {
627
+ // NON-CONVERGE (ruling C): FLIP the verdict TASK→ASK, reusing the EXISTING
628
+ // `asked` outcome + `kind=ask` marker. The ASK comment carries BOTH the proposed
629
+ // task DRAFT and the open question(s) in its BODY (NOT a new marker kind) — the
630
+ // human reacts to a concrete draft, strictly richer than a blank-question ask.
631
+ // NEVER write `work/backlog/<slug>.md`; NEVER silently emit the under-refined
632
+ // task. The next intake run resumes via the already-built triage gate.
633
+ note(`Intake's lone-task review did not converge for issue #${issueNumber} ` +
634
+ `(${review.passes} round(s)); flipping TASK→ASK with the draft + open ` +
635
+ `question(s) in the comment body.`);
636
+ return dispatchComment({
637
+ outcome: 'asked',
638
+ cwd,
639
+ issueNumber,
640
+ issueProvider,
641
+ body: composeLoneTaskAskComment({
642
+ issueNumber,
643
+ slug,
644
+ draftTitle: review.title,
645
+ draftBody: review.body,
646
+ questions: review.questions,
647
+ }),
648
+ markerKind: 'ask',
649
+ seen,
650
+ env,
651
+ note,
652
+ });
653
+ }
654
+ // CONVERGED: the (possibly edited) task is emitted via the EXISTING write/integrate
655
+ // path below + the existing `task created` completion comment. The refined body
656
+ // replaces the agent's first draft.
657
+ const reviewedBody = review.body;
658
+ // ONBOARD the task write onto a `work/intake-task-<slug>` branch cut from the
659
+ // freshly-fetched `<arbiter>/main` (the SAME runner-owns-git discipline the
660
+ // tasking path uses): the lifecycle `stage` writes the file ON THIS BRANCH and
661
+ // the shared integrate core (`--propose` PR / `--merge` main) lands it. The
662
+ // intake- producer prefix keeps it distinct from a later `do task:<slug>`
663
+ // build branch for the same slug. The agent ran no git.
664
+ await switchToWorkBranch(cwd, arbiter, 'task', slug, env);
665
+ const taskContent = renderBacklogTask({
666
+ slug,
667
+ title: review.title,
668
+ body: reviewedBody,
669
+ issueNumber,
670
+ originTrust,
671
+ });
672
+ const core = await performIntegration({
673
+ cwd,
674
+ arbiter,
675
+ slug,
676
+ // `source`/`recovering` are task-shaped and IGNORED when `lifecycle` is set.
677
+ source: 'in-progress',
678
+ recovering: false,
679
+ // An intake-emitted task has no `verify` floor of its own (it is a new
680
+ // backlog item, not a build); skip the acceptance gate, exactly as the
681
+ // tasking transition does.
682
+ skipVerify: true,
683
+ // Default `propose` (the per-outcome KNOBS are a later task). The
684
+ // EXPLICITLY-chosen mode proceeds as-is: a future `--merge-task` lands on main
685
+ // (`merge` IS the auto-land mode, never downgraded).
686
+ mode: integration,
687
+ noPR,
688
+ providerInstance,
689
+ type: 'feat',
690
+ lifecycle: {
691
+ // The emitted task IS the title source. Pass the DRAFTED title EXPLICITLY
692
+ // (not a read-from-path): `stage()` WRITES `work/backlog/<slug>.md` AFTER the
693
+ // core reads the title, so a `titlePath` read would race the write and degrade
694
+ // the commit subject / PR title to the generic fallback. `titlePath` stays set
695
+ // (the lifecycle contract requires it) but is IGNORED while `title` is present.
696
+ titlePath: join(cwd, relPath),
697
+ title: review.title,
698
+ commitTag: 'intake',
699
+ stage: () => stageIntakeContent({ cwd, relPath, content: taskContent, env }),
700
+ },
701
+ env,
702
+ note,
703
+ });
704
+ return integrationToIntakeResult(core, {
705
+ issueNumber,
706
+ slug,
707
+ relPath,
708
+ cwd,
709
+ issueProvider,
710
+ seen,
711
+ env,
712
+ note,
713
+ });
714
+ }
715
+ /**
716
+ * DISPATCH the `spec` outcome (canonical; the legacy `prd` outcome routes here too
717
+ * through the cutover): derive a content-derived slug, write the spec file
718
+ * (`work/specs/ready/<slug>.md`) carrying `issue: N` (the loop-closure linkage the
719
+ * close JOB reaches via `task.spec: → spec issue:`; on a fanned spec the number
720
+ * lives ONLY on the spec — a fanned task uses `spec:`, NOT its own `issue:`, which
721
+ * is the lone-task outcome's link) + the gate axes the prompt JUDGED, integrate it
722
+ * via {@link performIntegration}, then STOP. Tasking the emitted spec is the
723
+ * SEPARATE `do spec:` step (NOT done here). A coupled-but-SMALL pair lands here too
724
+ * (the spec vs BOUNCE line is SHARED VISION, not size — the over-bounce guard). The
725
+ * runner owns the git exactly as the task branch does; the agent did NO git/seam ops.
726
+ */
727
+ async function dispatchSpec(params) {
728
+ const { verdict, issueNumber, cwd, arbiter, integration, originTrust, noPR, specsLandIn, explicitSpecsLandIn, providerInstance, issueProvider, seen, env, note, } = params;
729
+ // A content-derived slug — NEVER a counter (prd `issue-intake` US #8). Prefer the drafted
730
+ // `specSlug`, else derive from the drafted title.
731
+ const slug = resolveSpecSlug(verdict);
732
+ if (slug === '') {
733
+ const message = `Intake produced a 'spec' verdict for issue #${issueNumber} with no usable ` +
734
+ `slug/title to derive a content-derived slug from (never a counter).`;
735
+ note(message);
736
+ return { exitCode: 1, outcome: 'usage-error', issueNumber, message };
737
+ }
738
+ // RUNNER-DETERMINISTIC PLACEMENT (task
739
+ // `pre-prd-staging-pool-split-and-untrusted-prd-placement`, governing ADR
740
+ // `placement-is-runner-deterministic-humanonly-is-agent-judgement`). Resolve
741
+ // which folder the runner writes the intake-authored prd into BEFORE handing
742
+ // it to the shared integrate band: the SAME precedence chain the tasker uses
743
+ // (`explicit > untrusted-origin ⇒ staging > specsLandIn > built-in (staging)`),
744
+ // the SAME shared resolver — only the lifecycle SLOTS differ. The agent
745
+ // (the intake decider) never influences placement; it returns the verdict and
746
+ // the runner computes the destination from unforgeable inputs.
747
+ const placementDecision = resolvePlacement({
748
+ explicit: specLandingToSide(explicitSpecsLandIn),
749
+ originTrust,
750
+ configuredDefault: specLandingToSide(specsLandIn),
751
+ });
752
+ const placementDir = placementFolder(SPEC_PLACEMENT_SLOTS, placementDecision.choice);
753
+ const relPath = `${placementDir}/${slug}.md`;
754
+ // ONBOARD onto a `work/intake-spec-<slug>` branch off fresh `<arbiter>/main` —
755
+ // the SAME runner-owns-git discipline the task branch uses; the intake-
756
+ // producer prefix keeps it distinct from a `do spec:<slug>` tasking branch.
757
+ await switchToWorkBranch(cwd, arbiter, 'spec', slug, env);
758
+ const specContent = renderSpec({
759
+ slug,
760
+ title: verdict.specTitle ?? slug,
761
+ body: verdict.specBody,
762
+ issueNumber,
763
+ humanOnly: verdict.specHumanOnly,
764
+ needsAnswers: verdict.specNeedsAnswers,
765
+ originTrust,
766
+ });
767
+ const core = await performIntegration({
768
+ cwd,
769
+ arbiter,
770
+ slug,
771
+ source: 'in-progress',
772
+ recovering: false,
773
+ // An intake-emitted prd has no `verify` floor of its own (it is a new spec,
774
+ // not a build), exactly as the task branch + the tasking transition skip it.
775
+ skipVerify: true,
776
+ mode: integration,
777
+ noPR,
778
+ providerInstance,
779
+ type: 'feat',
780
+ lifecycle: {
781
+ // The emitted prd IS the title source. Pass the DRAFTED title EXPLICITLY (same
782
+ // race as the task path: `stage()` writes the prd file AFTER the title
783
+ // read). `titlePath` stays set but is IGNORED while `title` is present.
784
+ titlePath: join(cwd, relPath),
785
+ title: verdict.specTitle ?? slug,
786
+ commitTag: 'intake',
787
+ stage: () => stageIntakeContent({ cwd, relPath, content: specContent, env }),
788
+ },
789
+ env,
790
+ note,
791
+ });
792
+ return integrationToIntakeResult(core, {
793
+ issueNumber,
794
+ slug,
795
+ relPath,
796
+ kind: 'spec',
797
+ cwd,
798
+ issueProvider,
799
+ seen,
800
+ env,
801
+ note,
802
+ });
803
+ }
804
+ /**
805
+ * Map the shared integrate band's {@link IntegrationCoreResult} onto the intake
806
+ * {@link IntakeResult}. On `completed` the artifact was written + integrated; a
807
+ * `rebase-conflict` against an advanced `main` maps to `stale` (the analogue of
808
+ * "the backlog moved under us"); everything else maps defensively to a usage error
809
+ * (the intake task path passes `skipVerify` + has no review gate, so neither
810
+ * `gate-failed` nor `review-blocked` can occur).
811
+ */
812
+ async function integrationToIntakeResult(core, ctx) {
813
+ const { issueNumber, slug, relPath, cwd, issueProvider, seen, env, note } = ctx;
814
+ const kind = ctx.kind ?? 'task';
815
+ const artifact = kind === 'spec' ? 'spec' : 'task';
816
+ if (core.outcome === 'completed') {
817
+ const landed = core.integration?.mode === 'merge'
818
+ ? 'landed it on the arbiter main'
819
+ : 'opened a PR carrying it (main untouched)';
820
+ // Both a lone task and a prd carry `issue: N` as their closure link (the task
821
+ // closes its own issue; a prd is reached via `task.prd: → prd issue:`). On the
822
+ // task/prd path `intake` never closes the issue (CI's close-job does, via the
823
+ // `issue:` field; intake closes ONLY on BOUNCE) and emits no `Fixes #N` (a
824
+ // deferred GitHub-only optimisation).
825
+ const link = `issue: ${issueNumber}`;
826
+ const message = `Intake of issue #${issueNumber} → wrote ${relPath} (${link}); ` +
827
+ `the runner integrated it through the shared core and ${landed}.`;
828
+ // CLOSE THE LOOP (this task): post ONE INFORMATIONAL completion comment back on
829
+ // the issue for the SUCCESSFUL outcome — the confirmation the ASK/BOUNCE comments
830
+ // already give the author. It reports `task created` / `spec created` (NEVER
831
+ // "issue resolved"; intake never closes on task/spec — CI's close-job does, via
832
+ // the `issue:` field) and links the artifact by integration mode: the PR `url` in
833
+ // propose, the landed `commit` in merge. The marker carries `kind=created` (the
834
+ // TRIAGE treats it as TERMINAL → `already-terminal`), so the comment cannot
835
+ // re-trigger intake. ADVISORY — it DEGRADES (a missing/unauthenticated `gh` never
836
+ // throws), so a degrade leaves the run's success outcome unchanged.
837
+ const posted = await postCompletionComment({
838
+ issueProvider,
839
+ issueNumber,
840
+ kind,
841
+ slug,
842
+ integration: core.integration,
843
+ seen,
844
+ cwd,
845
+ env,
846
+ note,
847
+ });
848
+ return {
849
+ exitCode: 0,
850
+ outcome: kind === 'spec' ? 'spec-written' : 'tasked',
851
+ issueNumber,
852
+ emittedSlug: slug,
853
+ emitted: relPath,
854
+ commented: posted,
855
+ message,
856
+ };
857
+ }
858
+ if (core.outcome === 'rebase-conflict') {
859
+ return {
860
+ exitCode: 4,
861
+ outcome: 'stale',
862
+ issueNumber,
863
+ message: core.reason ??
864
+ `Integrating the intake ${artifact} for issue #${issueNumber} conflicted ` +
865
+ `against the latest main; re-run intake.`,
866
+ };
867
+ }
868
+ return {
869
+ exitCode: 1,
870
+ outcome: 'usage-error',
871
+ issueNumber,
872
+ message: core.reason ??
873
+ `Integrating the intake ${artifact} for issue #${issueNumber} failed unexpectedly.`,
874
+ };
875
+ }
876
+ /**
877
+ * Build the INFORMATIONAL completion-comment BODY (with its FULL `created` marker)
878
+ * for a SUCCESSFUL `task` / `spec` outcome — the PURE, seam-free core of
879
+ * {@link postCompletionComment}, exported so both link variants are unit-testable
880
+ * without a live seam. The comment:
881
+ *
882
+ * - reports `task created` / `spec created` framed as CREATED — NEVER "issue
883
+ * resolved/closed" (intake never closes on the task/spec path).
884
+ * - LINKS the artifact by INTEGRATION MODE: the PR `url` in propose, the landed
885
+ * `commit` (the additive {@link IntegrateResult.commit}) in merge. A degraded
886
+ * propose (no `url`) or a failed merge-tip read (no `commit`) simply OMITS the
887
+ * link — the comment still confirms what was created (the artifact is safe on the
888
+ * branch/main regardless). No prd link beyond the slug.
889
+ * - carries the FULL intake MARKER via the SHARED {@link stampIntakeMarker} helper
890
+ * (`kind=created slug=<slug> seen=<id>,…`) so the triage's `already-terminal`
891
+ * branch consumes it — the comment cannot re-trigger intake.
892
+ */
893
+ export function composeIntakeCompletionComment(params) {
894
+ const { kind, slug, integration, seen } = params;
895
+ const artifact = kind === 'spec' ? 'spec' : 'task';
896
+ const link = integration?.mode === 'merge'
897
+ ? integration.commit !== undefined
898
+ ? `\n\nIt landed on \`main\` in commit ${integration.commit}.`
899
+ : ''
900
+ : integration?.url !== undefined
901
+ ? `\n\nIt is carried by the PR: ${integration.url}`
902
+ : '';
903
+ const body = `Created ${artifact} \`${slug}\` from this issue.${link}\n\n` +
904
+ `This is an informational update — the issue stays open (it remains in play ` +
905
+ `until the ${artifact} lands; intake does not change the issue's state).`;
906
+ // STAMP the FULL marker (incl. `seen=`) via the SHARED helper, so the triage's
907
+ // `already-terminal` branch recognises this terminal `created` comment.
908
+ return stampIntakeMarker(body, { kind: 'created', seen, slug });
909
+ }
910
+ /**
911
+ * Post the INFORMATIONAL completion comment for a SUCCESSFUL `task` / `spec`
912
+ * outcome (this task). It closes the loop the ASK/BOUNCE comments already close
913
+ * for the other outcomes: the issue author gets a confirmation when intake did the
914
+ * useful thing. The comment:
915
+ *
916
+ * - reports `task created` / `spec created` — NEVER "issue resolved/closed".
917
+ * Intake never closes the issue on the task/spec path (CI's future close-job
918
+ * does, via the `issue:` field); this comment changes NO issue state.
919
+ * - LINKS the artifact by INTEGRATION MODE: the PR `url` in propose, the landed
920
+ * `commit` (the additive {@link IntegrateResult.commit} this task surfaces) in
921
+ * merge. No prd link beyond the slug.
922
+ * - carries the FULL intake MARKER via the SHARED {@link stampIntakeMarker} helper
923
+ * (`kind=created slug=<slug> seen=<id>,…`). `kind=created` is TERMINAL, so the
924
+ * triage's `already-terminal` branch then treats the issue as already-transformed
925
+ * — the completion comment cannot re-trigger intake.
926
+ *
927
+ * ADVISORY — it DEGRADES (a missing/unauthenticated `gh` surfaces the text, never
928
+ * throws), so a degrade does NOT change the run's success outcome. Returns whether
929
+ * a comment was actually posted (for {@link IntakeResult.commented}).
930
+ */
931
+ async function postCompletionComment(params) {
932
+ const { issueProvider, issueNumber, kind, slug, integration, seen, cwd, env, note, } = params;
933
+ const artifact = kind === 'spec' ? 'spec' : 'task';
934
+ // Build the full stamped body (CREATED wording + mode-keyed link + the FULL
935
+ // `created` marker) via the exported pure builder — unit-tested directly for both
936
+ // link variants (propose `url` / merge `commit`).
937
+ const stamped = composeIntakeCompletionComment({
938
+ kind,
939
+ slug,
940
+ integration,
941
+ seen,
942
+ });
943
+ const posted = await issueProvider.postIssueComment({
944
+ cwd,
945
+ issueNumber,
946
+ body: stamped,
947
+ env,
948
+ });
949
+ note(posted.posted
950
+ ? `Posted a '${artifact} created' completion comment on issue #${issueNumber}.`
951
+ : `Could not post the completion comment on issue #${issueNumber} ` +
952
+ `(${posted.instruction}); the ${artifact} was still created.`);
953
+ return posted.posted;
954
+ }
955
+ /**
956
+ * Resolve a content-derived slug from the verdict — NEVER a counter (prd `issue-intake` US #8).
957
+ * Prefer the drafted `taskSlug`, else derive from the drafted title; both go
958
+ * through `paramCase` (the brand case-transform) so the result is a clean
959
+ * lowercase-`-`-joined slug. An empty result (no slug AND no title) signals the
960
+ * caller to refuse (a counter fallback is forbidden).
961
+ */
962
+ function resolveSlug(verdict) {
963
+ const candidate = verdict.taskSlug && verdict.taskSlug.trim() !== ''
964
+ ? verdict.taskSlug
965
+ : (verdict.taskTitle ?? '');
966
+ return paramCase(candidate);
967
+ }
968
+ /**
969
+ * Resolve a content-derived slug for the spec outcome — NEVER a counter (prd `issue-intake` US #8).
970
+ * Prefer the drafted `specSlug`, else derive from the drafted spec title; both go
971
+ * through `paramCase`. An empty result signals the caller to refuse.
972
+ */
973
+ function resolveSpecSlug(verdict) {
974
+ const candidate = verdict.specSlug && verdict.specSlug.trim() !== ''
975
+ ? verdict.specSlug
976
+ : (verdict.specTitle ?? '');
977
+ return paramCase(candidate);
978
+ }
979
+ /**
980
+ * Render the backlog task file: the frontmatter (`title`/`slug`/`covers: []`, NO
981
+ * `prd:` — its own source of truth, prd `issue-intake` decision table) carrying the lone-task
982
+ * `issue: N` closure link + the drafted body. The task closes its source issue
983
+ * via its `issue:` field (the provider-agnostic link a FUTURE CI close-job reads
984
+ * from folder + field state); it carries NO `Fixes #N` (a deferred GitHub-only
985
+ * optimisation, structurally unplaceable on the `--merge` path). The number is
986
+ * the task's own closure path — `issue:` XOR `prd:`; a lone task never carries a
987
+ * `prd:` (prd `issue-intake` decision table). When the agent drafted no body, a thin default
988
+ * scaffold keeps the file a valid task.
989
+ */
990
+ export function renderBacklogTask(params) {
991
+ const { slug, title, body, issueNumber, originTrust } = params;
992
+ const lines = [
993
+ '---',
994
+ `title: ${title}`,
995
+ `slug: ${slug}`,
996
+ `issue: ${issueNumber}`,
997
+ ];
998
+ if (originTrust !== undefined) {
999
+ lines.push('origin: issue', `originTrust: ${originTrust}`);
1000
+ }
1001
+ lines.push('covers: []', 'blockedBy: []', '---');
1002
+ const frontmatter = lines.join('\n');
1003
+ // The drafted body (agent-authored, headings and all) is wrapped VERBATIM.
1004
+ // Only the empty-body DEFAULT SCAFFOLD is sourced from the shared section
1005
+ // skeleton owner (`renderTaskBody`, prd
1006
+ // `centralize-buildable-task-renderer-shared-by-intake-and-promotion` US #2),
1007
+ // so intake's fallback and promotion's body cannot drift on section
1008
+ // names/order. The shared renderer ends its body with a trailing newline (its
1009
+ // last line is a blank); intake owns the single trailing `\n` in the join
1010
+ // below, so we `trimEnd()` the renderer output to stay byte-for-byte identical
1011
+ // to the pre-rewire literal.
1012
+ const drafted = body && body.trim() !== ''
1013
+ ? body.trim()
1014
+ : renderTaskBody({
1015
+ whatToBuild: title,
1016
+ acceptanceCriteria: '- [ ] the issue is resolved',
1017
+ prompt: `Resolve issue #${issueNumber}: ${title}`,
1018
+ }).trimEnd();
1019
+ return `${frontmatter}\n\n${drafted}\n`;
1020
+ }
1021
+ /**
1022
+ * Render the emitted prd file: the frontmatter (`title`/`slug` + the loop-closure
1023
+ * `issue: N` + the gate axes the prompt JUDGED) followed by the drafted prd body.
1024
+ * For a FANNED prd the `issue: N` lives ONLY on the prd — never duplicated across
1025
+ * the N fanned tasks, which reach it via `task.prd: → prd issue:` (a fanned
1026
+ * task carries `prd:`, NOT its own `issue:`; the lone-task outcome is the only
1027
+ * one that puts `issue:` on a task). The close JOB reaches the prd's number via
1028
+ * `task.prd: → prd issue:`. The gate axes (`humanOnly`/`needsAnswers`) are emitted ONLY when the
1029
+ * verdict declared them `true` — an omitted axis is `undefined` (undeclared), the
1030
+ * same convention `frontmatter.ts` parses. When the agent drafted no body, a thin
1031
+ * default scaffold keeps the file a valid prd that `do prd:` can later task.
1032
+ */
1033
+ export function renderSpec(params) {
1034
+ const { slug, title, body, issueNumber, humanOnly, needsAnswers, originTrust } = params;
1035
+ const lines = [
1036
+ '---',
1037
+ `title: ${title}`,
1038
+ `slug: ${slug}`,
1039
+ `issue: ${issueNumber}`,
1040
+ ];
1041
+ // The origin-trust stamp (only when passed IN from the CI shell): the
1042
+ // becomes-code checkpoint that survives the merge boundary. A local intake
1043
+ // leaves it unset ⇒ no stamp ⇒ human/trusted.
1044
+ if (originTrust !== undefined) {
1045
+ lines.push('origin: issue', `originTrust: ${originTrust}`);
1046
+ }
1047
+ // Surface the gate axes AS THE PROMPT JUDGED THEM (prd `issue-intake` US #8). Only emit a `true`
1048
+ // axis — an undeclared axis stays absent (parsed as `undefined`).
1049
+ if (humanOnly === true) {
1050
+ lines.push('humanOnly: true');
1051
+ }
1052
+ if (needsAnswers === true) {
1053
+ lines.push('needsAnswers: true');
1054
+ }
1055
+ lines.push('---');
1056
+ const frontmatter = lines.join('\n');
1057
+ // As with `renderBacklogTask`: the drafted PRD body is wrapped VERBATIM; only
1058
+ // the empty-body DEFAULT SCAFFOLD is sourced from the shared section skeleton
1059
+ // owner (`renderSpecBody` with `solution` + `userStories`, prd
1060
+ // `centralize-buildable-task-renderer-shared-by-intake-and-promotion` US #2),
1061
+ // so intake's PRD fallback and promotion's PRD body cannot drift. `trimEnd()`
1062
+ // drops the renderer's trailing blank line so intake's single trailing `\n`
1063
+ // (owned by the join below) keeps the bytes identical to the pre-rewire literal.
1064
+ const drafted = body && body.trim() !== ''
1065
+ ? body.trim()
1066
+ : renderSpecBody({
1067
+ problemStatement: `Transformed from issue #${issueNumber}: ${title}`,
1068
+ solution: '(to be detailed; this prd needs tasking via `do prd:`).',
1069
+ userStories: `1. As a user, I want issue #${issueNumber} addressed.`,
1070
+ }).trimEnd();
1071
+ return `${frontmatter}\n\n${drafted}\n`;
1072
+ }
1073
+ /**
1074
+ * STAGE the intake artifact content into the index on the `work/<slug>` branch (the
1075
+ * {@link performIntegration} lifecycle seam): write the `work/backlog/<slug>.md`
1076
+ * file (runner-owned; the agent never writes git-visible files) and `git add` it.
1077
+ * The band's subsequent `git add -A` + atomic commit folds it into ONE runner-owned
1078
+ * commit.
1079
+ */
1080
+ async function stageIntakeContent(params) {
1081
+ const { cwd, relPath, content, env } = params;
1082
+ const abs = join(cwd, relPath);
1083
+ mkdirSync(dirname(abs), { recursive: true });
1084
+ writeFileSync(abs, content);
1085
+ await gitHard(['add', '--', relPath], cwd, env);
1086
+ }
1087
+ /**
1088
+ * ONBOARD the intake write onto a NAMESPACED, INTAKE-PRODUCED branch
1089
+ * (`work/intake-task-<slug>` / `work/intake-prd-<slug>`) cut from the freshly-
1090
+ * fetched `<arbiter>/main` (the SAME discipline `tasking.ts` uses). The
1091
+ * `intake-` PRODUCER prefix keeps this short-lived "create the item" branch
1092
+ * DISTINCT from the later build branch (`work/task-<slug>`) for the same slug
1093
+ * — the firing `intake` × `do task:` collision the observation traced. The
1094
+ * task-emit path passes `'task'`, the prd-emit path `'prd'`. A pre-existing
1095
+ * local branch (a re-run) is force-recreated off fresh main.
1096
+ */
1097
+ async function switchToWorkBranch(cwd, arbiter, type, slug, env) {
1098
+ const branch = workBranchRef(type, slug, { producer: 'intake' });
1099
+ await gitHard(['fetch', '--quiet', arbiter], cwd, env);
1100
+ await gitHard(['switch', '--quiet', '-C', branch, `${arbiter}/main`], cwd, env);
1101
+ }
1102
+ /** Run git; throw on non-zero (genuinely unexpected plumbing failures). */
1103
+ async function gitHard(args, cwd, env) {
1104
+ const result = await runAsync('git', args, cwd, { env });
1105
+ if (result.status !== 0) {
1106
+ throw new Error(`git ${args.join(' ')} failed (exit ${result.status}): ${result.stderr.trim()}`);
1107
+ }
1108
+ return result;
1109
+ }
1110
+ /** Run the decision step. Prefers the injected decider; else the harness seam. */
1111
+ async function runDecision(options, cwd, issue, comments, prompt) {
1112
+ if (options.decide) {
1113
+ return options.decide({ cwd, issue, comments, prompt, env: options.env });
1114
+ }
1115
+ // PRODUCTION: launch the harness with the decision prd, then PARSE the verdict
1116
+ // the agent emitted out of its ANSWER channel (`launched.output`) — the SAME wire
1117
+ // the review gate runs (launch → `parseReviewVerdict(readOutput(launched.output))`;
1118
+ // `harnessReviewGate`). The agent emits a single fenced ```json block (the OUTPUT
1119
+ // CONTRACT {@link buildIntakeDecisionSpec} appends); {@link parseIntakeVerdict}
1120
+ // extracts + validates it. The model's JUDGEMENT is not unit-tested — only the
1121
+ // parse + dispatch — exactly as the review prompt's judgement is not.
1122
+ const harness = options.harness ?? new NullHarness();
1123
+ const launched = await launchWithOptionalWatch({
1124
+ harness,
1125
+ dir: cwd,
1126
+ slug: `intake-${issue.number}`,
1127
+ command: options.agentCmd ?? '',
1128
+ prompt,
1129
+ model: options.model,
1130
+ sessionId: `intake-${issue.number}`,
1131
+ sessionsDir: options.sessionsDir,
1132
+ env: options.env,
1133
+ });
1134
+ if (!launched.ok) {
1135
+ throw new Error(launched.detail ?? 'the intake decision agent failed.');
1136
+ }
1137
+ // Read the verdict from the agent's ANSWER channel (`output`), NOT `detail` (the
1138
+ // failure channel, empty on success) — the SAME `output ?? ''` normalisation the
1139
+ // review gate's `readOutput` default applies. A malformed/absent verdict throws,
1140
+ // which `decideAndDispatch`'s try/catch already maps onto `agent-failed` (exit 1).
1141
+ return parseIntakeVerdict(launched.output ?? '');
1142
+ }
1143
+ /**
1144
+ * Parse the decision agent's emitted VERDICT out of its (possibly prose-wrapped /
1145
+ * fenced) textual output into an {@link IntakeVerdict} — the PRODUCTION wire
1146
+ * between the launched agent and the already-built dispatcher, modeled 1:1 on the
1147
+ * review gate's `parseReviewVerdict` twin (`review-gate.ts`). It pulls the first
1148
+ * JSON object carrying an `"outcome"` field via the SHARED
1149
+ * {@link extractJsonObjectSpan} (NOT a forked second "first JSON object in agent
1150
+ * prose" extractor — the review gates anchor on `"verdict"`, intake on
1151
+ * `"outcome"`; same need, one implementation — coherence), `JSON.parse`s it, and
1152
+ * validates the shape: `outcome ∈ {ask,task,spec,bounce}`.
1153
+ *
1154
+ * The per-outcome fields map 1:1 onto {@link IntakeVerdict} (`task` →
1155
+ * taskSlug?/taskTitle/taskBody, `spec` →
1156
+ * specSlug?/specTitle/specBody/specHumanOnly?/specNeedsAnswers?, `ask` → question,
1157
+ * `bounce` → bounceMessage). Missing OPTIONALS are tolerated — the dispatcher
1158
+ * already has fallbacks (slug-from-title, the thin comment/scaffold defaults).
1159
+ *
1160
+ * THROWS a clear error on: no JSON object present, invalid JSON, or an `outcome`
1161
+ * not in the set. The caller (`decideAndDispatch`) maps any throw onto the
1162
+ * `agent-failed` outcome (exit 1) — a malformed verdict degrades honestly, never
1163
+ * a crash and never a silent dispatch.
1164
+ */
1165
+ export function parseIntakeVerdict(output) {
1166
+ const span = extractJsonObjectSpan(output, 'outcome');
1167
+ if (span === undefined) {
1168
+ throw new Error('intake decision agent produced no parseable {outcome, …} verdict.');
1169
+ }
1170
+ let parsed;
1171
+ try {
1172
+ parsed = JSON.parse(output.slice(span.start, span.end));
1173
+ }
1174
+ catch (err) {
1175
+ throw new Error(`intake verdict was not valid JSON: ${err.message}`);
1176
+ }
1177
+ if (typeof parsed !== 'object' || parsed === null) {
1178
+ throw new Error('intake verdict was not an object.');
1179
+ }
1180
+ const obj = parsed;
1181
+ const outcome = obj.outcome;
1182
+ if (outcome !== 'ask' &&
1183
+ outcome !== 'task' &&
1184
+ outcome !== 'spec' &&
1185
+ outcome !== 'bounce') {
1186
+ // The prompt teaches the LLM to emit `spec`, so the accepted outcome set is
1187
+ // `ask|task|spec|bounce`. HARD CUTOVER: the legacy `prd` outcome token is
1188
+ // fully gone (rejected here + removed from the `IntakeOutcome` type + the
1189
+ // dispatch `case`).
1190
+ throw new Error(`intake verdict 'outcome' was not one of ask|task|spec|bounce (got ` +
1191
+ `${JSON.stringify(outcome)}).`);
1192
+ }
1193
+ // Map the per-outcome fields onto the verdict shape, keeping ONLY the strings/
1194
+ // booleans the dispatcher consumes (a missing optional stays absent — the
1195
+ // dispatcher's fallbacks cover it). Every field is optional on the type, so the
1196
+ // `task`/`spec` content + the `ask`/`bounce` text are carried verbatim when present.
1197
+ const str = (v) => typeof v === 'string' ? v : undefined;
1198
+ const bool = (v) => typeof v === 'boolean' ? v : undefined;
1199
+ return {
1200
+ outcome,
1201
+ ...(str(obj.taskSlug) !== undefined ? { taskSlug: str(obj.taskSlug) } : {}),
1202
+ ...(str(obj.taskTitle) !== undefined
1203
+ ? { taskTitle: str(obj.taskTitle) }
1204
+ : {}),
1205
+ ...(str(obj.taskBody) !== undefined ? { taskBody: str(obj.taskBody) } : {}),
1206
+ ...(str(obj.question) !== undefined ? { question: str(obj.question) } : {}),
1207
+ ...(str(obj.specSlug) !== undefined ? { specSlug: str(obj.specSlug) } : {}),
1208
+ ...(str(obj.specTitle) !== undefined
1209
+ ? { specTitle: str(obj.specTitle) }
1210
+ : {}),
1211
+ ...(str(obj.specBody) !== undefined ? { specBody: str(obj.specBody) } : {}),
1212
+ ...(bool(obj.specHumanOnly) !== undefined
1213
+ ? { specHumanOnly: bool(obj.specHumanOnly) }
1214
+ : {}),
1215
+ ...(bool(obj.specNeedsAnswers) !== undefined
1216
+ ? { specNeedsAnswers: bool(obj.specNeedsAnswers) }
1217
+ : {}),
1218
+ ...(str(obj.bounceMessage) !== undefined
1219
+ ? { bounceMessage: str(obj.bounceMessage) }
1220
+ : {}),
1221
+ };
1222
+ }
1223
+ // ---------------------------------------------------------------------------
1224
+ // The LONE-TASK bounded internal review (observation
1225
+ // `intake-lone-task-skips-adversarial-review-the-prd-path-gets`, rulings A/B/C).
1226
+ //
1227
+ // Give intake's lone-TASK outcome the adversarial refinement the `do prd:` path
1228
+ // already gets — but as a small intake-NATIVE bounded review, NOT by integrating
1229
+ // the tasker loop. This is a NEW prompt + a small loop + an injectable gate seam,
1230
+ // MIRRORING the tasker loop's verdict/output CONVENTIONS (fenced JSON
1231
+ // `{verdict, findings, edit}` parsed via the shared `extractJsonObjectSpan`) WITHOUT
1232
+ // importing or calling `runTaskReviewLoop`. The differences are load-bearing: this
1233
+ // reviews ONE drafted task (N=1 — the SET/graph/overlap lenses are OFF), it never
1234
+ // touches disk pre-convergence (the task has not been emitted yet), and its only
1235
+ // non-converge sink is the EXISTING `asked` outcome (verdict flips TASK→ASK with
1236
+ // the draft + question(s) in the comment body — ruling C).
1237
+ // ---------------------------------------------------------------------------
1238
+ /** The HARD-CODED round cap for the lone-task review (ruling A — a literal, no config/flag). */
1239
+ const LONE_TASK_REVIEW_MAX_ROUNDS = 3;
1240
+ /**
1241
+ * Run the BOUNDED lone-task adversarial self-review over the SINGLE drafted task.
1242
+ * Each round runs the gate (the `review` skill's per-task + destination lenses on
1243
+ * the ONE task); a round may propose an EDIT (full replacement body) applied IN
1244
+ * MEMORY and re-reviewed. The cap is the HARD-CODED literal
1245
+ * {@link LONE_TASK_REVIEW_MAX_ROUNDS} = 3 (ruling A — no config/flag).
1246
+ *
1247
+ * - CONVERGE — a round returns `approve` with no NEW blocking issue → emit the
1248
+ * improved task (the caller's existing write/integrate + completion comment).
1249
+ * - NON-CONVERGE — a round `block`s with an open question (no clear thread answer)
1250
+ * OR the cap is hit with an unresolved blocker → flip TASK→ASK carrying the
1251
+ * draft + the open question(s) (ruling C).
1252
+ *
1253
+ * A gate launch/parse failure THROWS (the caller's try/catch maps it onto
1254
+ * `agent-failed`) — never a silent emit of the un-reviewed task.
1255
+ */
1256
+ async function runLoneTaskReview(params) {
1257
+ const { slug, issueNumber, draftTitle, draftBody, gate, cwd, env, note } = params;
1258
+ let body = draftBody;
1259
+ let lastVerdict = { verdict: 'block', findings: [] };
1260
+ let passes = 0;
1261
+ for (let round = 1; round <= LONE_TASK_REVIEW_MAX_ROUNDS; round++) {
1262
+ const verdict = await gate({
1263
+ slug,
1264
+ issueNumber,
1265
+ title: draftTitle,
1266
+ // The body the reviewer sees this round (after any prior in-memory edit);
1267
+ // fall back to the rendered scaffold-input the emit path also tolerates.
1268
+ body: body ?? '',
1269
+ round,
1270
+ cwd,
1271
+ env,
1272
+ });
1273
+ passes = round;
1274
+ lastVerdict = verdict;
1275
+ // APPLY the proposed EDIT IN MEMORY (no `work/backlog/` write pre-convergence).
1276
+ if (verdict.edit !== undefined && verdict.edit.trim() !== '') {
1277
+ body = verdict.edit;
1278
+ }
1279
+ if (verdict.verdict === 'approve') {
1280
+ return {
1281
+ outcome: 'converge',
1282
+ title: draftTitle,
1283
+ body,
1284
+ questions: [],
1285
+ passes,
1286
+ };
1287
+ }
1288
+ // EARLY FLIP → ASK (the non-converge trigger the source observation names
1289
+ // FIRST: "a blocking question with NO clear answer in the issue thread"). When a
1290
+ // round BLOCKS, carries open `questions`, and proposes NO `edit`, the agent is
1291
+ // saying "this needs the HUMAN, I have nothing left to tighten" — so flip to ASK
1292
+ // NOW rather than burning the remaining rounds (which cannot resolve a question
1293
+ // only the human can answer). Symmetric with the early CONVERGE return above; it
1294
+ // retires the "flips only at the cap" behaviour (PR #62 review nit #1). A `block`
1295
+ // that DID propose an `edit` is still iterated — the edit may converge it; only a
1296
+ // no-edit blocking question short-circuits.
1297
+ const hasEdit = verdict.edit !== undefined && verdict.edit.trim() !== '';
1298
+ const earlyQuestions = loneTaskBlockingQuestions(verdict);
1299
+ if (!hasEdit && earlyQuestions.length > 0) {
1300
+ note(`Intake lone-task review round ${round}/${LONE_TASK_REVIEW_MAX_ROUNDS} ` +
1301
+ `surfaced a blocking question with no edit to apply; flipping TASK→ASK ` +
1302
+ `early (no clear thread answer — the human must decide).`);
1303
+ return {
1304
+ outcome: 'non-converge',
1305
+ title: draftTitle,
1306
+ body,
1307
+ questions: earlyQuestions,
1308
+ passes,
1309
+ };
1310
+ }
1311
+ note(`Intake lone-task review round ${round}/${LONE_TASK_REVIEW_MAX_ROUNDS} ` +
1312
+ `found ${loneTaskBlockingCount(verdict)} blocking issue(s)` +
1313
+ `${hasEdit ? ' (an edit was applied)' : ''}.`);
1314
+ }
1315
+ // The cap was hit with a still-`block` verdict → NON-CONVERGE (flip TASK→ASK).
1316
+ return {
1317
+ outcome: 'non-converge',
1318
+ title: draftTitle,
1319
+ body,
1320
+ questions: loneTaskBlockingQuestions(lastVerdict),
1321
+ passes,
1322
+ };
1323
+ }
1324
+ /** Count blocking findings in a lone-task review verdict. */
1325
+ function loneTaskBlockingCount(verdict) {
1326
+ return verdict.findings.filter((f) => f.severity === 'blocking').length;
1327
+ }
1328
+ /**
1329
+ * The open question(s) for the ASK comment body on a non-converge: prefer the
1330
+ * verdict's explicit `questions`, else fall back to the blocking findings'
1331
+ * questions (so the human always gets a concrete question, never a blank ask).
1332
+ */
1333
+ function loneTaskBlockingQuestions(verdict) {
1334
+ if (verdict.questions && verdict.questions.length > 0) {
1335
+ return verdict.questions;
1336
+ }
1337
+ const blocking = verdict.findings.filter((f) => f.severity === 'blocking');
1338
+ const source = blocking.length > 0 ? blocking : verdict.findings;
1339
+ return source.map((f) => f.context ? `${f.question} (${f.context})` : f.question);
1340
+ }
1341
+ /**
1342
+ * Compose the NON-CONVERGE ASK comment BODY (ruling C): it carries BOTH the proposed
1343
+ * task DRAFT and the open question(s) that arose, so the human reacts to a concrete
1344
+ * draft ("yes, yes, but…"), strictly richer than a blank-question ask. The draft
1345
+ * rides in the comment BODY — NOT a new marker kind; {@link dispatchComment} stamps
1346
+ * the EXISTING `kind=ask` marker around it.
1347
+ */
1348
+ function composeLoneTaskAskComment(params) {
1349
+ const { issueNumber, slug, draftTitle, draftBody, questions } = params;
1350
+ const draft = renderBacklogTask({
1351
+ slug,
1352
+ title: draftTitle,
1353
+ body: draftBody,
1354
+ issueNumber,
1355
+ });
1356
+ const questionLines = questions.length > 0
1357
+ ? questions.map((q) => `- ${q}`).join('\n')
1358
+ : '- (the draft below needs a clarification before it can be built)';
1359
+ return [
1360
+ `I drafted a task for issue #${issueNumber} but the internal review surfaced`,
1361
+ `open question(s) it could not resolve from the thread. Please weigh in on the`,
1362
+ `draft below — once the question(s) are answered, a later run can emit it.`,
1363
+ '',
1364
+ '## Open question(s)',
1365
+ '',
1366
+ questionLines,
1367
+ '',
1368
+ '## Proposed task draft',
1369
+ '',
1370
+ '```markdown',
1371
+ draft.trimEnd(),
1372
+ '```',
1373
+ ].join('\n');
1374
+ }
1375
+ /**
1376
+ * Resolve the lone-task review GATE from the intake options: the injected
1377
+ * {@link PerformIntakeOptions.reviewTask} (tests' canned seam) when present, else
1378
+ * the production harness-backed gate ({@link harnessLoneTaskReviewGate}) wired to
1379
+ * the same harness/agent the decision step uses. Mirrors how {@link runDecision}
1380
+ * prefers the injected `decide`.
1381
+ */
1382
+ function resolveLoneTaskReviewGate(options) {
1383
+ if (options.reviewTask) {
1384
+ return options.reviewTask;
1385
+ }
1386
+ return harnessLoneTaskReviewGate({
1387
+ harness: options.harness,
1388
+ agentCmd: options.agentCmd,
1389
+ model: options.model,
1390
+ sessionsDir: options.sessionsDir,
1391
+ });
1392
+ }
1393
+ /**
1394
+ * The PRODUCTION lone-task review gate: launch the `review` SKILL as an agent
1395
+ * through the EXISTING harness seam (the SAME wire {@link runDecision} uses), then
1396
+ * PARSE the emitted `{verdict, findings, edit, questions}` via
1397
+ * {@link parseLoneTaskReviewVerdict}. The agent makes the review JUDGEMENT (the
1398
+ * per-task + destination lenses on the ONE task); this gate launches it and parses
1399
+ * its verdict. A launch failure THROWS (the dispatcher's try/catch maps it onto
1400
+ * `agent-failed`). MIRRORS {@link harnessTaskReviewGate} WITHOUT importing it.
1401
+ */
1402
+ export function harnessLoneTaskReviewGate(options = {}) {
1403
+ const harness = options.harness ?? new NullHarness();
1404
+ return async (input) => {
1405
+ const launched = await launchWithOptionalWatch({
1406
+ harness,
1407
+ dir: input.cwd,
1408
+ slug: `intake-task-review-${input.slug}`,
1409
+ command: options.agentCmd ?? '',
1410
+ prompt: buildLoneTaskReviewPrompt(input),
1411
+ model: options.model,
1412
+ // A DISTINCT session id per round so launches never collide.
1413
+ sessionId: `intake-task-review-${input.slug}-r${input.round}`,
1414
+ sessionsDir: options.sessionsDir,
1415
+ env: input.env,
1416
+ });
1417
+ if (!launched.ok) {
1418
+ throw new Error(`intake lone-task review agent launch failed${launched.detail ? `: ${launched.detail}` : ''}`);
1419
+ }
1420
+ return parseReviewVerdict(launched.output ?? '');
1421
+ };
1422
+ }
1423
+ /**
1424
+ * Build the LONE-TASK review PROMPT: instruct a fresh-context agent to apply
1425
+ * the **review discipline** (`work/protocol/REVIEW-PROTOCOL.md`) to the SINGLE
1426
+ * drafted task — per-task well-formedness + the destination check ("if this
1427
+ * task is built exactly as written, do we end up with the behaviour issue #N
1428
+ * asks for?"). The SET / graph / overlap lenses are N=1 and EXPLICITLY OFF.
1429
+ * A round may propose an `edit` (the FULL replacement task body) the runner
1430
+ * applies IN MEMORY and re-reviews; converge when a round finds NO new blocking
1431
+ * issue, else carry the open `questions` into the ASK comment for the human.
1432
+ *
1433
+ * The discipline body and the JSON-emitted-shape contract are SHARED helpers
1434
+ * (task `review-protocol-doc-and-shared-machinery`); this builder owns ONLY
1435
+ * the lone-task-specific framing.
1436
+ */
1437
+ export function buildLoneTaskReviewPrompt(input) {
1438
+ return [
1439
+ `You are a FRESH-CONTEXT reviewer in intake's BOUNDED lone-task review. A`,
1440
+ `single task has just been drafted from GitHub issue #${input.issueNumber}.`,
1441
+ `Review THIS ONE task adversarially (round ${input.round} of at most ${LONE_TASK_REVIEW_MAX_ROUNDS}).`,
1442
+ '',
1443
+ reviewDisciplinePrompt(),
1444
+ '',
1445
+ `Drafted task slug: ${input.slug}`,
1446
+ `Drafted task title: ${input.title}`,
1447
+ '',
1448
+ 'Drafted task body (the markdown AFTER the frontmatter):',
1449
+ '```markdown',
1450
+ input.body.trim() === ''
1451
+ ? '(empty — only a scaffold was drafted)'
1452
+ : input.body,
1453
+ '```',
1454
+ '',
1455
+ '## Which lenses apply (N=1 — this is ONE task, not a SET)',
1456
+ '',
1457
+ 'Apply ONLY the per-task lenses, ENDING in the destination check:',
1458
+ '- **Per-task well-formedness** — is it a single tracer-bullet vertical task',
1459
+ ' (one thin end-to-end path)? Are the `## What to build`, `## Acceptance',
1460
+ ' criteria`, and `## Prompt` present, concrete, and self-contained (an AFK',
1461
+ ' agent could start from the file alone)? Are claims/paths/“reuse X” real?',
1462
+ '- **The DESTINATION check** — if this task is built EXACTLY as written, do we',
1463
+ ` end up with the behaviour issue #${input.issueNumber} asks for? A hole here is`,
1464
+ ' the highest-value thing to flag.',
1465
+ '',
1466
+ 'The SET / graph / overlap / goal-COMPOSITION lenses are OFF: there is only ONE',
1467
+ 'task (N=1), so there is no dependency graph, no set-level gap, and no',
1468
+ 'duplicate/overlap to assess. Do NOT invent a decomposition.',
1469
+ '',
1470
+ '## How to iterate',
1471
+ '',
1472
+ 'You do NOT edit files or run git — you EMIT a verdict and the runner applies it',
1473
+ 'in memory, then re-reviews. If a finding can be FIXED by tightening the draft,',
1474
+ 'propose an `edit` (the FULL replacement task body — the markdown AFTER the',
1475
+ 'frontmatter; the runner writes the frontmatter + the issue link). CONVERGE',
1476
+ '(`approve`, no blocking findings) when a round finds NO new blocking issue.',
1477
+ 'When a BLOCKING question has NO clear answer in the issue thread — it needs the',
1478
+ 'human, not another edit — `block` and put it in `questions`: the runner asks the',
1479
+ 'human, carrying this draft. Flag, do not guess.',
1480
+ '',
1481
+ verdictContractPrompt(),
1482
+ '',
1483
+ 'Fill the channels appropriate to THIS caller (the lone-task review):',
1484
+ ' - `edit` — a single full-replacement task body (NOT a path; the task is',
1485
+ ' not yet emitted) when tightening the draft fixes the finding.',
1486
+ ' - `questions` — the open question(s) for the human when a blocking issue',
1487
+ ' has no clear thread answer.',
1488
+ 'Do NOT fill `review` / `edits` / `uncertainTasks` / `decompositionUnclear`',
1489
+ "— those are other callers' channels.",
1490
+ ].join('\n');
1491
+ }
1492
+ /**
1493
+ * Backwards-compatible alias for the unified {@link parseReviewVerdict}
1494
+ * (task `review-protocol-doc-and-shared-machinery`). The lone-task review
1495
+ * verdict is now the unified {@link ReviewVerdict}; the alias keeps existing
1496
+ * tests/callers compiling.
1497
+ */
1498
+ export const parseLoneTaskReviewVerdict = parseReviewVerdict;
1499
+ /**
1500
+ * Build the intake decision PRD (an inline prompt builder, like `buildTaskingPrd`
1501
+ * in `tasking.ts` / the reviewer prompts in `review-gate.ts` — NOT a standalone
1502
+ * asset/`.md` file; no such convention exists in this package). It encodes the FULL
1503
+ * four-outcome decision table (prd `issue-intake` — the source of truth) and the
1504
+ * three DECISION AIDS stated once there:
1505
+ *
1506
+ * 1. the **"clear?" bar** = `to-task`/`needsAnswers`' "would I build the wrong
1507
+ * thing if I guessed?" — if a material requirement/scope/acceptance question is
1508
+ * unanswered, ASK (never guess a spec from a vague issue);
1509
+ * 2. the **"one task?" bar** = `to-task`' tracer-bullet test (one thin end-to-end
1510
+ * path, demoable on its own) — fits → TASK, needs splitting → PRD;
1511
+ * 3. **PRD vs BOUNCE** turns on a **SHARED VISION**: coupled (even if small) → PRD;
1512
+ * genuinely unrelated → BOUNCE. Size NEVER forces a bounce — only unrelatedness
1513
+ * (the over-bounce guard: a coupled-but-small pair gets a light PRD, never a
1514
+ * bounce).
1515
+ *
1516
+ * The prompt anchors to `to-task`/`to-spec` for the task/spec SHAPES it drafts. Its
1517
+ * JUDGEMENT is NOT unit-tested (exactly as the review prompt's is not) — only the
1518
+ * dispatch is. The agent only DRAFTS the verdict + its content; it does NO git/seam
1519
+ * ops (the runner owns every postComment / write / integrate — the in-band boundary).
1520
+ */
1521
+ export function buildIntakeDecisionSpec(issue, comments, triage) {
1522
+ const thread = comments.length === 0
1523
+ ? '(no comments yet)'
1524
+ : comments
1525
+ .map((c, i) => `#${i + 1} ${c.author ? `@${c.author}` : '(unknown)'}: ${c.body}`)
1526
+ .join('\n\n');
1527
+ // TRIAGE ENRICHMENT (prd `issue-intake`): on the raced PROCEED path the prompt is
1528
+ // told which comment(s) PRE-DATE intake's last turn (context for a prior state,
1529
+ // not necessarily a fresh answer) and — only then — how many previously-SEEN
1530
+ // comments were DELETED (a flag + count; the bodies are gone, so do not name them).
1531
+ const triageNotes = [];
1532
+ if (triage?.action === 'proceed' && triage.predatingIds.length > 0) {
1533
+ triageNotes.push('', '## Triage note — raced comment(s) that PRE-DATE intake’s last turn', '', `${triage.predatingIds.length} comment(s) landed AFTER intake last read the`, 'thread but BEFORE it posted its last turn, so they pre-date that turn', '(possibly concurrent). Treat them as possibly-already-addressed context for a', 'PRIOR state — NOT necessarily a direct answer to intake’s latest question.');
1534
+ if (triage.deletedSeenCount > 0) {
1535
+ triageNotes.push('', `ALSO: ${triage.deletedSeenCount} previously-seen comment(s) were DELETED since`, 'intake last read the thread. Their content is gone and not recoverable; do', 'NOT assume your prior reasoning’s premises still hold — reassess from the', 'current thread.');
1536
+ }
1537
+ }
1538
+ return [
1539
+ `You are the dorfl INTAKE agent. Decide what to do with GitHub issue`,
1540
+ `#${issue.number}: "${issue.title}". You read the issue + its full comment`,
1541
+ `thread and return ONE verdict (the runner DISPATCHES on it deterministically).`,
1542
+ '',
1543
+ 'Issue body:',
1544
+ issue.body.trim() === '' ? '(empty)' : issue.body,
1545
+ '',
1546
+ 'Comment thread (oldest first):',
1547
+ thread,
1548
+ ...triageNotes,
1549
+ '',
1550
+ '## The decision — classify the issue into exactly ONE of four verdicts',
1551
+ '',
1552
+ '- **ASK** — the issue is NOT clear enough to act on: a material requirement,',
1553
+ ' scope, or acceptance question is unanswered. Use the same bar `to-task`',
1554
+ ' uses for `needsAnswers`: "would I build the WRONG thing if I guessed now?"',
1555
+ ' If yes → ASK. Draft the SINGLE next clarifying question (do NOT guess a spec',
1556
+ ' from a vague issue). The runner posts it and stops; a later run resumes from',
1557
+ ' the updated thread.',
1558
+ '',
1559
+ '- **TASK** — the issue is CLEAR *and* it fits ONE tracer-bullet vertical task',
1560
+ ' (a single thin end-to-end path, demoable on its own — `to-task`’ criterion).',
1561
+ ' Draft that ONE task in the `to-task` shape (a `## What to build`,',
1562
+ ' `## Acceptance criteria`, and `## Prompt`). The runner writes',
1563
+ ' `work/backlog/<slug>.md` (`covers: []`, NO `prd:`) carrying `issue: N` (the',
1564
+ ' lone-task closure link, NOT `Fixes #N`) and integrates it.',
1565
+ '',
1566
+ '- **PRD** — the issue is CLEAR *and* coherent but needs MORE THAN ONE task (it',
1567
+ ' cannot be one tracer-bullet path — it splits for scope/architecture). >1 task',
1568
+ ' ⟺ a shared vision worth recording ⟺ a spec. Draft a spec in the `to-spec` shape',
1569
+ ' (`## Problem Statement`, `## Solution`, `## User Stories`, `## Out of Scope`).',
1570
+ ' The runner writes the prd file (`work/prds/ready/<slug>.md`) with `issue: N` and integrates it;',
1571
+ ' TASKING the prd is a SEPARATE later step (`do prd:`) — do not task it here.',
1572
+ ' **INCLUDES a coupled-but-SMALL pair: if two asks share a vision they get a',
1573
+ ' (light) prd — they are NEVER bounced.**',
1574
+ '',
1575
+ '- **BOUNCE** — the issue is really MULTIPLE UNRELATED concerns wearing one issue:',
1576
+ ' you cannot articulate a SINGLE shared vision tying them together. Draft a short',
1577
+ ' message asking the author to file separate issues. A bounce is TERMINAL, so the',
1578
+ ' runner CLOSES the issue ATOMICALLY — your message as the closing comment +',
1579
+ ' reason "not planned" (the honest signal that the asks must be re-filed). Intake',
1580
+ ' closes on BOUNCE only; never on task/prd (CI’s close-job) / ask.',
1581
+ '',
1582
+ '## The three decision aids (apply them in order)',
1583
+ '',
1584
+ '1. **"clear?"** (ASK vs the rest): the `needsAnswers` bar — would acting now risk',
1585
+ ' building the wrong thing? If yes → ASK. Otherwise it is clear; continue.',
1586
+ '2. **"one task?"** (TASK vs PRD): the `to-task` tracer-bullet test — one thin',
1587
+ ' end-to-end path, demoable alone? Fits → TASK; needs splitting → PRD.',
1588
+ '3. **"shared vision?"** (PRD vs BOUNCE): coupled (even if small) → PRD; genuinely',
1589
+ ' unrelated → BOUNCE. SIZE NEVER forces a bounce — only UNRELATEDNESS does. Do',
1590
+ ' not over-bounce a small coupled pair: it is a light prd.',
1591
+ '',
1592
+ '## Boundary',
1593
+ '',
1594
+ 'You only DRAFT the verdict + its content (the task/prd body, or the comment',
1595
+ 'text). You do NOT perform ANY git operation and you do NOT post any comment — the',
1596
+ 'runner owns every git/seam side-effect (write, integrate, postComment). For a prd',
1597
+ 'verdict, also judge its gate axes (humanOnly / needsAnswers) so the runner can',
1598
+ 'surface them on the emitted prd.',
1599
+ '',
1600
+ '## Output — hand the verdict back as ONE fenced JSON block',
1601
+ '',
1602
+ 'Emit your verdict as a SINGLE fenced ```json block (and nothing else that looks',
1603
+ 'like JSON). Its keys map 1:1 onto the verdict the runner dispatches on — always an',
1604
+ '`"outcome"` plus ONLY the fields for that outcome:',
1605
+ '',
1606
+ '```json',
1607
+ '{"outcome": "task", "taskSlug": "<content-derived-slug>", "taskTitle": "<title>", "taskBody": "<the markdown AFTER the frontmatter>"}',
1608
+ '```',
1609
+ '',
1610
+ '- **task** → `taskTitle` + `taskBody` (the `## What to build` / `## Acceptance',
1611
+ ' criteria` / `## Prompt` markdown — NOT the frontmatter; the runner writes the',
1612
+ ' frontmatter + the `issue: N` link) and an optional `taskSlug` (the runner',
1613
+ ' derives one from the title if you omit it — never a counter).',
1614
+ '- **spec** → `specTitle` + `specBody` (the `## Problem Statement` / `## Solution` / …',
1615
+ ' markdown AFTER the frontmatter; the runner writes the frontmatter + `issue: N`),',
1616
+ ' an optional `specSlug`, and the gate axes `specHumanOnly` / `specNeedsAnswers`',
1617
+ ' (booleans — set `true` when a human should drive the TASKING and/or open',
1618
+ ' questions remain; omit otherwise).',
1619
+ '- **ask** → `question` (the single next clarifying question).',
1620
+ '- **bounce** → `bounceMessage` (the “file separate issues” message).',
1621
+ '',
1622
+ '`outcome` MUST be exactly one of `ask` | `task` | `spec` | `bounce`. Strings are',
1623
+ 'plain text inside the JSON (escape newlines as \\n). Do not wrap the JSON in any',
1624
+ 'other structure — the runner pulls the first `{"outcome": …}` object out and',
1625
+ 'dispatches on it.',
1626
+ ].join('\n');
1627
+ }
1628
+ //# sourceMappingURL=intake.js.map