dorfl 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (492) hide show
  1. package/dist/advance-ci-template.d.ts +3 -3
  2. package/dist/advance-ci-template.js +1 -1
  3. package/dist/advance-ci-template.js.map +1 -1
  4. package/dist/advance-classify.d.ts +5 -5
  5. package/dist/advance-classify.d.ts.map +1 -1
  6. package/dist/advance-classify.js +4 -4
  7. package/dist/advance-drivers.d.ts +8 -8
  8. package/dist/advance-drivers.d.ts.map +1 -1
  9. package/dist/advance-drivers.js +20 -8
  10. package/dist/advance-drivers.js.map +1 -1
  11. package/dist/advance-isolated.d.ts +3 -3
  12. package/dist/advance-isolated.d.ts.map +1 -1
  13. package/dist/advance-isolated.js +1 -1
  14. package/dist/advance-lifecycle-template.d.ts +2 -2
  15. package/dist/advance-lifecycle-template.d.ts.map +1 -1
  16. package/dist/advance-lifecycle-template.js +85 -13
  17. package/dist/advance-lifecycle-template.js.map +1 -1
  18. package/dist/advance-loop-driver.d.ts +3 -3
  19. package/dist/advance-loop-driver.d.ts.map +1 -1
  20. package/dist/advance-loop-driver.js +1 -1
  21. package/dist/advance-treeless-publish.d.ts +24 -1
  22. package/dist/advance-treeless-publish.d.ts.map +1 -1
  23. package/dist/advance-treeless-publish.js +41 -0
  24. package/dist/advance-treeless-publish.js.map +1 -1
  25. package/dist/advance.d.ts +69 -17
  26. package/dist/advance.d.ts.map +1 -1
  27. package/dist/advance.js +409 -102
  28. package/dist/advance.js.map +1 -1
  29. package/dist/advancing-lock.d.ts +31 -3
  30. package/dist/advancing-lock.d.ts.map +1 -1
  31. package/dist/advancing-lock.js +55 -5
  32. package/dist/advancing-lock.js.map +1 -1
  33. package/dist/agent-launch.d.ts +12 -0
  34. package/dist/agent-launch.d.ts.map +1 -1
  35. package/dist/agent-launch.js +22 -12
  36. package/dist/agent-launch.js.map +1 -1
  37. package/dist/agent-stop.d.ts +40 -2
  38. package/dist/agent-stop.d.ts.map +1 -1
  39. package/dist/agent-stop.js +30 -2
  40. package/dist/agent-stop.js.map +1 -1
  41. package/dist/apply-decide.d.ts +19 -5
  42. package/dist/apply-decide.d.ts.map +1 -1
  43. package/dist/apply-decide.js +38 -9
  44. package/dist/apply-decide.js.map +1 -1
  45. package/dist/apply-merge-action.d.ts +20 -8
  46. package/dist/apply-merge-action.d.ts.map +1 -1
  47. package/dist/apply-merge-action.js +44 -9
  48. package/dist/apply-merge-action.js.map +1 -1
  49. package/dist/apply-persist.d.ts +62 -31
  50. package/dist/apply-persist.d.ts.map +1 -1
  51. package/dist/apply-persist.js +173 -44
  52. package/dist/apply-persist.js.map +1 -1
  53. package/dist/apply-stuck-action.d.ts +151 -0
  54. package/dist/apply-stuck-action.d.ts.map +1 -0
  55. package/dist/apply-stuck-action.js +125 -0
  56. package/dist/apply-stuck-action.js.map +1 -0
  57. package/dist/brand.d.ts +12 -1
  58. package/dist/brand.d.ts.map +1 -1
  59. package/dist/brand.js +2 -1
  60. package/dist/brand.js.map +1 -1
  61. package/dist/buildable-body.d.ts +14 -14
  62. package/dist/buildable-body.js +9 -9
  63. package/dist/claim-cas.d.ts +3 -3
  64. package/dist/claim-cas.js +1 -1
  65. package/dist/claim-cas.js.map +1 -1
  66. package/dist/cli-spinner.d.ts +1 -1
  67. package/dist/cli-spinner.js +1 -1
  68. package/dist/cli.d.ts +10 -0
  69. package/dist/cli.d.ts.map +1 -1
  70. package/dist/cli.js +385 -128
  71. package/dist/cli.js.map +1 -1
  72. package/dist/close-job-template.d.ts +3 -3
  73. package/dist/close-job-template.js +9 -9
  74. package/dist/close-job.d.ts +8 -8
  75. package/dist/close-job.js +25 -25
  76. package/dist/close-job.js.map +1 -1
  77. package/dist/complete.d.ts +9 -6
  78. package/dist/complete.d.ts.map +1 -1
  79. package/dist/complete.js +99 -45
  80. package/dist/complete.js.map +1 -1
  81. package/dist/concurrency.d.ts +1 -1
  82. package/dist/concurrency.js +1 -1
  83. package/dist/config.d.ts +86 -40
  84. package/dist/config.d.ts.map +1 -1
  85. package/dist/config.js +61 -9
  86. package/dist/config.js.map +1 -1
  87. package/dist/continue-branch.d.ts +1 -1
  88. package/dist/continue-branch.d.ts.map +1 -1
  89. package/dist/continue-branch.js +23 -1
  90. package/dist/continue-branch.js.map +1 -1
  91. package/dist/cwd-section.js +1 -1
  92. package/dist/cwd-section.js.map +1 -1
  93. package/dist/decision-engine.d.ts +39 -11
  94. package/dist/decision-engine.d.ts.map +1 -1
  95. package/dist/decision-engine.js +10 -6
  96. package/dist/decision-engine.js.map +1 -1
  97. package/dist/do-autopick.d.ts +6 -6
  98. package/dist/do-autopick.d.ts.map +1 -1
  99. package/dist/do-autopick.js +16 -6
  100. package/dist/do-autopick.js.map +1 -1
  101. package/dist/do-config.d.ts +4 -4
  102. package/dist/do-config.js +3 -3
  103. package/dist/do-config.js.map +1 -1
  104. package/dist/do-remote-auto.d.ts +2 -2
  105. package/dist/do-remote-auto.js +1 -1
  106. package/dist/do.d.ts +74 -81
  107. package/dist/do.d.ts.map +1 -1
  108. package/dist/do.js +439 -50
  109. package/dist/do.js.map +1 -1
  110. package/dist/drop-source.d.ts +2 -2
  111. package/dist/env-config.d.ts.map +1 -1
  112. package/dist/env-config.js +19 -11
  113. package/dist/env-config.js.map +1 -1
  114. package/dist/failure-cause.d.ts +3 -2
  115. package/dist/failure-cause.d.ts.map +1 -1
  116. package/dist/failure-cause.js +28 -1
  117. package/dist/failure-cause.js.map +1 -1
  118. package/dist/format.d.ts +6 -6
  119. package/dist/format.d.ts.map +1 -1
  120. package/dist/format.js +13 -30
  121. package/dist/format.js.map +1 -1
  122. package/dist/frontmatter.d.ts +28 -15
  123. package/dist/frontmatter.d.ts.map +1 -1
  124. package/dist/frontmatter.js +38 -24
  125. package/dist/frontmatter.js.map +1 -1
  126. package/dist/gc.d.ts +65 -6
  127. package/dist/gc.d.ts.map +1 -1
  128. package/dist/gc.js +126 -9
  129. package/dist/gc.js.map +1 -1
  130. package/dist/github.d.ts +14 -0
  131. package/dist/github.d.ts.map +1 -1
  132. package/dist/github.js +73 -0
  133. package/dist/github.js.map +1 -1
  134. package/dist/harness.d.ts +25 -0
  135. package/dist/harness.d.ts.map +1 -1
  136. package/dist/harness.js.map +1 -1
  137. package/dist/index.d.ts +4 -1
  138. package/dist/index.d.ts.map +1 -1
  139. package/dist/index.js +2 -1
  140. package/dist/index.js.map +1 -1
  141. package/dist/install-ci-branch-protection.d.ts +133 -39
  142. package/dist/install-ci-branch-protection.d.ts.map +1 -1
  143. package/dist/install-ci-branch-protection.js +191 -49
  144. package/dist/install-ci-branch-protection.js.map +1 -1
  145. package/dist/install-ci-capabilities/advance-lifecycle.d.ts +1 -1
  146. package/dist/install-ci-capabilities/advance-lifecycle.js +1 -1
  147. package/dist/install-ci-capabilities/close-job.d.ts +1 -1
  148. package/dist/install-ci-capabilities/close-job.js +1 -1
  149. package/dist/install-ci-capabilities/example-noop.d.ts +1 -1
  150. package/dist/install-ci-capabilities/example-noop.js +1 -1
  151. package/dist/install-ci-capabilities/intake.d.ts +2 -2
  152. package/dist/install-ci-capabilities/intake.js +2 -2
  153. package/dist/install-ci-capabilities/verify.d.ts +1 -1
  154. package/dist/install-ci-capabilities/verify.js +1 -1
  155. package/dist/install-ci-core.d.ts +24 -4
  156. package/dist/install-ci-core.d.ts.map +1 -1
  157. package/dist/install-ci-core.js +12 -5
  158. package/dist/install-ci-core.js.map +1 -1
  159. package/dist/install-ci-github.d.ts +33 -1
  160. package/dist/install-ci-github.d.ts.map +1 -1
  161. package/dist/install-ci-github.js +74 -1
  162. package/dist/install-ci-github.js.map +1 -1
  163. package/dist/install-ci.d.ts +1 -1
  164. package/dist/install-ci.js +4 -4
  165. package/dist/install-ci.js.map +1 -1
  166. package/dist/install-skills.d.ts +123 -0
  167. package/dist/install-skills.d.ts.map +1 -0
  168. package/dist/install-skills.js +100 -0
  169. package/dist/install-skills.js.map +1 -0
  170. package/dist/intake-event.d.ts +6 -6
  171. package/dist/intake-event.js +6 -6
  172. package/dist/intake-marker.d.ts +3 -3
  173. package/dist/intake-marker.d.ts.map +1 -1
  174. package/dist/intake-triage.d.ts +1 -1
  175. package/dist/intake-triage.js +2 -2
  176. package/dist/intake-trigger-template.d.ts +8 -8
  177. package/dist/intake-trigger-template.d.ts.map +1 -1
  178. package/dist/intake-trigger-template.js +17 -17
  179. package/dist/intake-trigger-template.js.map +1 -1
  180. package/dist/intake.d.ts +65 -52
  181. package/dist/intake.d.ts.map +1 -1
  182. package/dist/intake.js +92 -76
  183. package/dist/intake.js.map +1 -1
  184. package/dist/integration-core.d.ts +27 -24
  185. package/dist/integration-core.d.ts.map +1 -1
  186. package/dist/integration-core.js +191 -77
  187. package/dist/integration-core.js.map +1 -1
  188. package/dist/integrator.d.ts +1 -1
  189. package/dist/integrator.d.ts.map +1 -1
  190. package/dist/integrator.js +19 -3
  191. package/dist/integrator.js.map +1 -1
  192. package/dist/isolation.d.ts +4 -4
  193. package/dist/isolation.d.ts.map +1 -1
  194. package/dist/isolation.js +9 -0
  195. package/dist/isolation.js.map +1 -1
  196. package/dist/issue-provider.d.ts +4 -4
  197. package/dist/issue-provider.js +1 -1
  198. package/dist/item-lock.d.ts +216 -149
  199. package/dist/item-lock.d.ts.map +1 -1
  200. package/dist/item-lock.js +345 -270
  201. package/dist/item-lock.js.map +1 -1
  202. package/dist/item-path.d.ts +2 -2
  203. package/dist/item-path.js +2 -2
  204. package/dist/ledger-lint.d.ts +9 -9
  205. package/dist/ledger-lint.js +9 -9
  206. package/dist/ledger-read.d.ts +57 -57
  207. package/dist/ledger-read.d.ts.map +1 -1
  208. package/dist/ledger-read.js +24 -24
  209. package/dist/ledger-read.js.map +1 -1
  210. package/dist/ledger-write.d.ts +33 -28
  211. package/dist/ledger-write.d.ts.map +1 -1
  212. package/dist/ledger-write.js +100 -122
  213. package/dist/ledger-write.js.map +1 -1
  214. package/dist/lifecycle-gather.d.ts +18 -1
  215. package/dist/lifecycle-gather.d.ts.map +1 -1
  216. package/dist/lifecycle-gather.js +23 -17
  217. package/dist/lifecycle-gather.js.map +1 -1
  218. package/dist/lifecycle-pools.d.ts +44 -7
  219. package/dist/lifecycle-pools.d.ts.map +1 -1
  220. package/dist/lifecycle-pools.js +27 -7
  221. package/dist/lifecycle-pools.js.map +1 -1
  222. package/dist/merge-question-surfacer.d.ts +18 -4
  223. package/dist/merge-question-surfacer.d.ts.map +1 -1
  224. package/dist/merge-question-surfacer.js +20 -5
  225. package/dist/merge-question-surfacer.js.map +1 -1
  226. package/dist/migrate-stuck-locks.d.ts +129 -0
  227. package/dist/migrate-stuck-locks.d.ts.map +1 -0
  228. package/dist/migrate-stuck-locks.js +355 -0
  229. package/dist/migrate-stuck-locks.js.map +1 -0
  230. package/dist/mint-adr.js +4 -4
  231. package/dist/mirror-pool-scan.d.ts +2 -2
  232. package/dist/mirror-pool-scan.js +3 -3
  233. package/dist/mirror-pool-scan.js.map +1 -1
  234. package/dist/needs-attention.d.ts +304 -27
  235. package/dist/needs-attention.d.ts.map +1 -1
  236. package/dist/needs-attention.js +542 -67
  237. package/dist/needs-attention.js.map +1 -1
  238. package/dist/orphan-sidecar.d.ts +10 -6
  239. package/dist/orphan-sidecar.d.ts.map +1 -1
  240. package/dist/orphan-sidecar.js +35 -2
  241. package/dist/orphan-sidecar.js.map +1 -1
  242. package/dist/pi-harness.d.ts +16 -0
  243. package/dist/pi-harness.d.ts.map +1 -1
  244. package/dist/pi-harness.js +82 -2
  245. package/dist/pi-harness.js.map +1 -1
  246. package/dist/placement.d.ts +8 -8
  247. package/dist/placement.js +3 -3
  248. package/dist/prd-to-spec.d.ts.map +1 -1
  249. package/dist/prd-to-spec.js +9 -5
  250. package/dist/prd-to-spec.js.map +1 -1
  251. package/dist/prompt.d.ts +16 -19
  252. package/dist/prompt.d.ts.map +1 -1
  253. package/dist/prompt.js +17 -19
  254. package/dist/prompt.js.map +1 -1
  255. package/dist/protocol/CLAIM-PROTOCOL.md +17 -10
  256. package/dist/protocol/REVIEW-PROTOCOL.md +4 -1
  257. package/dist/protocol/SURFACE-PROTOCOL.md +16 -2
  258. package/dist/protocol/TASKING-PROTOCOL.md +3 -1
  259. package/dist/protocol/WORK-CONTRACT.md +22 -18
  260. package/dist/protocol/task-template.md +1 -1
  261. package/dist/readiness.d.ts +1 -1
  262. package/dist/reap-branches.d.ts +12 -9
  263. package/dist/reap-branches.d.ts.map +1 -1
  264. package/dist/reap-branches.js +25 -7
  265. package/dist/reap-branches.js.map +1 -1
  266. package/dist/recover-isolated.d.ts +12 -0
  267. package/dist/recover-isolated.d.ts.map +1 -1
  268. package/dist/recover-isolated.js +6 -1
  269. package/dist/recover-isolated.js.map +1 -1
  270. package/dist/repo-config.d.ts +23 -2
  271. package/dist/repo-config.d.ts.map +1 -1
  272. package/dist/repo-config.js +64 -18
  273. package/dist/repo-config.js.map +1 -1
  274. package/dist/repo-mirror.d.ts.map +1 -1
  275. package/dist/repo-mirror.js +18 -2
  276. package/dist/repo-mirror.js.map +1 -1
  277. package/dist/review-gate.d.ts +3 -3
  278. package/dist/review-gate.d.ts.map +1 -1
  279. package/dist/review-gate.js +11 -10
  280. package/dist/review-gate.js.map +1 -1
  281. package/dist/review-verdict.d.ts +2 -2
  282. package/dist/review-verdict.d.ts.map +1 -1
  283. package/dist/review-verdict.js +2 -2
  284. package/dist/review-verdict.js.map +1 -1
  285. package/dist/run.d.ts +2 -2
  286. package/dist/run.d.ts.map +1 -1
  287. package/dist/run.js +58 -23
  288. package/dist/run.js.map +1 -1
  289. package/dist/scan.d.ts +26 -17
  290. package/dist/scan.d.ts.map +1 -1
  291. package/dist/scan.js +29 -15
  292. package/dist/scan.js.map +1 -1
  293. package/dist/select-order.d.ts +1 -1
  294. package/dist/select-priority.d.ts +14 -14
  295. package/dist/select-priority.d.ts.map +1 -1
  296. package/dist/select-priority.js +5 -5
  297. package/dist/select-priority.js.map +1 -1
  298. package/dist/sidecar-apply.js +1 -1
  299. package/dist/sidecar-apply.js.map +1 -1
  300. package/dist/sidecar.d.ts +47 -12
  301. package/dist/sidecar.d.ts.map +1 -1
  302. package/dist/sidecar.js +84 -4
  303. package/dist/sidecar.js.map +1 -1
  304. package/dist/skills/answer-questions/SKILL.md +89 -0
  305. package/dist/skills/capture-signal/SKILL.md +52 -0
  306. package/dist/skills/convert-from-prd-to-spec/SKILL.md +90 -0
  307. package/dist/skills/drive-tasks/SKILL.md +218 -0
  308. package/dist/skills/from-idea/SKILL.md +83 -0
  309. package/dist/skills/merge-prs/SKILL.md +70 -0
  310. package/dist/skills/orchestrate/SKILL.md +101 -0
  311. package/dist/skills/promote/SKILL.md +35 -0
  312. package/dist/skills/review/SKILL.md +16 -0
  313. package/dist/skills/setup/SKILL.md +258 -0
  314. package/dist/skills/setup/protocol/ADR-FORMAT.md +47 -0
  315. package/dist/skills/setup/protocol/CLAIM-PROTOCOL.md +224 -0
  316. package/dist/skills/setup/protocol/REVIEW-PROTOCOL.md +122 -0
  317. package/dist/skills/setup/protocol/SURFACE-PROTOCOL.md +135 -0
  318. package/dist/skills/setup/protocol/TASKING-PROTOCOL.md +124 -0
  319. package/dist/skills/setup/protocol/WORK-CONTRACT.md +280 -0
  320. package/dist/skills/setup/protocol/spec-template.md +71 -0
  321. package/dist/skills/setup/protocol/task-template.md +65 -0
  322. package/dist/skills/surface-questions/SKILL.md +16 -0
  323. package/dist/skills/to-spec/SKILL.md +34 -0
  324. package/dist/skills/to-task/SKILL.md +19 -0
  325. package/dist/skills/triage-observations/SKILL.md +78 -0
  326. package/dist/skills/work/SKILL.md +51 -0
  327. package/dist/slug-namespace.d.ts +6 -6
  328. package/dist/slug-namespace.js +8 -8
  329. package/dist/slug-namespace.js.map +1 -1
  330. package/dist/spec-complete.d.ts.map +1 -1
  331. package/dist/spec-complete.js +4 -5
  332. package/dist/spec-complete.js.map +1 -1
  333. package/dist/start.d.ts +1 -1
  334. package/dist/start.d.ts.map +1 -1
  335. package/dist/start.js +54 -60
  336. package/dist/start.js.map +1 -1
  337. package/dist/status.d.ts +3 -3
  338. package/dist/status.js +4 -4
  339. package/dist/status.js.map +1 -1
  340. package/dist/surface-gate.d.ts +2 -2
  341. package/dist/surface-gate.d.ts.map +1 -1
  342. package/dist/surface-gate.js +10 -3
  343. package/dist/surface-gate.js.map +1 -1
  344. package/dist/surface-persist.d.ts +2 -2
  345. package/dist/surface-persist.d.ts.map +1 -1
  346. package/dist/surface-persist.js +1 -1
  347. package/dist/surface-persist.js.map +1 -1
  348. package/dist/tasker-review-loop.d.ts +11 -10
  349. package/dist/tasker-review-loop.d.ts.map +1 -1
  350. package/dist/tasker-review-loop.js +8 -8
  351. package/dist/tasker-review-loop.js.map +1 -1
  352. package/dist/tasking-eligibility.d.ts +21 -21
  353. package/dist/tasking-eligibility.d.ts.map +1 -1
  354. package/dist/tasking-eligibility.js +10 -10
  355. package/dist/tasking-lock.d.ts +10 -10
  356. package/dist/tasking-lock.d.ts.map +1 -1
  357. package/dist/tasking-lock.js +69 -49
  358. package/dist/tasking-lock.js.map +1 -1
  359. package/dist/tasking.d.ts +73 -39
  360. package/dist/tasking.d.ts.map +1 -1
  361. package/dist/tasking.js +309 -95
  362. package/dist/tasking.js.map +1 -1
  363. package/dist/triage-gate.d.ts +1 -1
  364. package/dist/triage-persist.d.ts +15 -11
  365. package/dist/triage-persist.d.ts.map +1 -1
  366. package/dist/triage-persist.js +48 -18
  367. package/dist/triage-persist.js.map +1 -1
  368. package/dist/vendor/incur/agents.d.ts +58 -0
  369. package/dist/vendor/incur/agents.d.ts.map +1 -0
  370. package/dist/vendor/incur/agents.js +343 -0
  371. package/dist/vendor/incur/agents.js.map +1 -0
  372. package/dist/verify-workflow-template.d.ts +1 -1
  373. package/dist/verify-workflow-template.js +3 -3
  374. package/dist/watch-session.d.ts +11 -3
  375. package/dist/watch-session.d.ts.map +1 -1
  376. package/dist/watch-session.js +94 -7
  377. package/dist/watch-session.js.map +1 -1
  378. package/dist/work-layout.d.ts +15 -9
  379. package/dist/work-layout.d.ts.map +1 -1
  380. package/dist/work-layout.js +14 -9
  381. package/dist/work-layout.js.map +1 -1
  382. package/dist/workspace.d.ts +2 -2
  383. package/package.json +2 -2
  384. package/src/advance-ci-template.ts +4 -4
  385. package/src/advance-classify.ts +5 -5
  386. package/src/advance-drivers.ts +26 -13
  387. package/src/advance-isolated.ts +3 -3
  388. package/src/advance-lifecycle-template.ts +98 -13
  389. package/src/advance-loop-driver.ts +3 -3
  390. package/src/advance-treeless-publish.ts +46 -1
  391. package/src/advance.ts +495 -115
  392. package/src/advancing-lock.ts +102 -7
  393. package/src/agent-launch.ts +37 -12
  394. package/src/agent-stop.ts +60 -2
  395. package/src/apply-decide.ts +38 -9
  396. package/src/apply-merge-action.ts +49 -11
  397. package/src/apply-persist.ts +236 -62
  398. package/src/apply-stuck-action.ts +260 -0
  399. package/src/brand.ts +14 -2
  400. package/src/buildable-body.ts +14 -14
  401. package/src/claim-cas.ts +4 -4
  402. package/src/cli-spinner.ts +1 -1
  403. package/src/cli.ts +472 -141
  404. package/src/close-job-template.ts +9 -9
  405. package/src/close-job.ts +26 -26
  406. package/src/complete.ts +121 -63
  407. package/src/concurrency.ts +1 -1
  408. package/src/config.ts +143 -49
  409. package/src/continue-branch.ts +23 -1
  410. package/src/cwd-section.ts +1 -1
  411. package/src/decision-engine.ts +57 -19
  412. package/src/do-autopick.ts +21 -10
  413. package/src/do-config.ts +7 -7
  414. package/src/do-remote-auto.ts +2 -2
  415. package/src/do.ts +558 -85
  416. package/src/drop-source.ts +2 -2
  417. package/src/env-config.ts +19 -11
  418. package/src/failure-cause.ts +30 -1
  419. package/src/format.ts +13 -33
  420. package/src/frontmatter.ts +55 -35
  421. package/src/gc.ts +172 -9
  422. package/src/github.ts +78 -0
  423. package/src/harness.ts +25 -0
  424. package/src/index.ts +10 -0
  425. package/src/install-ci-branch-protection.ts +283 -58
  426. package/src/install-ci-capabilities/advance-lifecycle.ts +1 -1
  427. package/src/install-ci-capabilities/close-job.ts +1 -1
  428. package/src/install-ci-capabilities/example-noop.ts +1 -1
  429. package/src/install-ci-capabilities/intake.ts +2 -2
  430. package/src/install-ci-capabilities/verify.ts +1 -1
  431. package/src/install-ci-core.ts +34 -7
  432. package/src/install-ci-github.ts +87 -1
  433. package/src/install-ci.ts +4 -4
  434. package/src/install-skills.ts +166 -0
  435. package/src/intake-event.ts +6 -6
  436. package/src/intake-marker.ts +3 -3
  437. package/src/intake-triage.ts +2 -2
  438. package/src/intake-trigger-template.ts +18 -18
  439. package/src/intake.ts +138 -103
  440. package/src/integration-core.ts +234 -100
  441. package/src/integrator.ts +22 -9
  442. package/src/isolation.ts +13 -4
  443. package/src/issue-provider.ts +4 -4
  444. package/src/item-lock.ts +505 -361
  445. package/src/item-path.ts +2 -2
  446. package/src/ledger-lint.ts +9 -9
  447. package/src/ledger-read.ts +81 -81
  448. package/src/ledger-write.ts +137 -152
  449. package/src/lifecycle-gather.ts +45 -22
  450. package/src/lifecycle-pools.ts +71 -13
  451. package/src/merge-question-surfacer.ts +32 -8
  452. package/src/migrate-stuck-locks.ts +451 -0
  453. package/src/mint-adr.ts +4 -4
  454. package/src/mirror-pool-scan.ts +5 -5
  455. package/src/needs-attention.ts +877 -104
  456. package/src/orphan-sidecar.ts +49 -8
  457. package/src/pi-harness.ts +82 -2
  458. package/src/placement.ts +8 -8
  459. package/src/prd-to-spec.ts +13 -5
  460. package/src/prompt.ts +20 -25
  461. package/src/readiness.ts +1 -1
  462. package/src/reap-branches.ts +38 -14
  463. package/src/recover-isolated.ts +18 -1
  464. package/src/repo-config.ts +66 -17
  465. package/src/repo-mirror.ts +23 -1
  466. package/src/review-gate.ts +12 -11
  467. package/src/review-verdict.ts +3 -3
  468. package/src/run.ts +69 -23
  469. package/src/scan.ts +36 -22
  470. package/src/select-order.ts +1 -1
  471. package/src/select-priority.ts +15 -15
  472. package/src/sidecar-apply.ts +1 -1
  473. package/src/sidecar.ts +141 -14
  474. package/src/slug-namespace.ts +10 -10
  475. package/src/spec-complete.ts +4 -5
  476. package/src/start.ts +57 -63
  477. package/src/status.ts +7 -7
  478. package/src/surface-gate.ts +12 -5
  479. package/src/surface-persist.ts +3 -3
  480. package/src/tasker-review-loop.ts +18 -17
  481. package/src/tasking-eligibility.ts +21 -21
  482. package/src/tasking-lock.ts +75 -60
  483. package/src/tasking.ts +381 -135
  484. package/src/triage-gate.ts +1 -1
  485. package/src/triage-persist.ts +71 -27
  486. package/src/vendor/incur/LICENSE +21 -0
  487. package/src/vendor/incur/README.md +19 -0
  488. package/src/vendor/incur/agents.ts +392 -0
  489. package/src/verify-workflow-template.ts +3 -3
  490. package/src/watch-session.ts +102 -7
  491. package/src/work-layout.ts +14 -9
  492. package/src/workspace.ts +2 -2
package/dist/tasking.js CHANGED
@@ -19,14 +19,14 @@ const DEFAULT_LOCK_SEAM = {
19
19
  };
20
20
  const DEFAULT_ARBITER = 'origin';
21
21
  /**
22
- * **The STAGED-TASKS dir** (prd `staging-pool-position-gate-and-trust-model`,
22
+ * **The STAGED-TASKS dir** (spec `staging-pool-position-gate-and-trust-model`,
23
23
  * task `pre-backlog-staging-folder-and-promote-step-a`, governing ADR
24
24
  * `placement-is-runner-deterministic-humanonly-is-agent-judgement`). The runner
25
25
  * lands the tasker's emitted task files HERE, NOT in `work/tasks/ready/`: an item
26
- * born in `pre-backlog/` is durable + readable but NOT in the agent-eligible
26
+ * born in `tasks/backlog/` is durable + readable but NOT in the agent-eligible
27
27
  * pool (`work/tasks/ready/` STILL means the pool — every reader is byte-for-byte
28
28
  * unchanged). A runner/human-owned promotion (`promoteFromPreBacklog` in
29
- * `needs-attention.ts`) moves an approved item `pre-backlog/ → backlog/` to make
29
+ * `needs-attention.ts`) moves an approved item `tasks/backlog/ → tasks/ready/` to make
30
30
  * it claimable. STEP A: ADDITIVE — no `work/tasks/ready/` reader changes here.
31
31
  */
32
32
  export const STAGED_TASKS_DIR = workFolderRel('tasks-backlog');
@@ -36,7 +36,7 @@ export const STAGED_TASKS_DIR = workFolderRel('tasks-backlog');
36
36
  * origin, or an `--tasks-land-in ready` operator override). The agent NEVER
37
37
  * writes here — it always writes to {@link STAGED_TASKS_DIR}; the runner
38
38
  * redirects the emitted files to the resolved destination at integrate-stage
39
- * time. prd US #4 / the governing ADR: the agent cannot self-place into the
39
+ * time. spec US #4 / the governing ADR: the agent cannot self-place into the
40
40
  * pool. Task `runner-deterministic-slice-placement-policy-and-precedence`.
41
41
  */
42
42
  const POOL_TASKS_DIR = workFolderRel('tasks-ready');
@@ -46,15 +46,17 @@ const TASK_PLACEMENT_SLOTS = {
46
46
  pool: POOL_TASKS_DIR,
47
47
  };
48
48
  /**
49
- * Map the `tasksLandIn` value spelling (`pre-backlog` | `ready`) onto the
49
+ * Map the `tasksLandIn` value spelling (`backlog` | `ready`) onto the
50
50
  * resolver's lifecycle-generic side enum (`staging` | `pool`). Returns
51
51
  * `undefined` when no value is set, so the resolver's next precedence rung
52
- * applies (the built-in floor). The legacy `'backlog'`/`'todo'` pool spellings
53
- * are NOT accepted (clean break — the value was renamed `'backlog'` → `'todo'`
54
- * → `'ready'`, ADR `rename-task-pool-folder-todo-to-ready`).
52
+ * applies (the built-in floor). The legacy `'pre-backlog'`/`'todo'` spellings
53
+ * are NOT accepted (clean break — the staging value was renamed
54
+ * `'pre-backlog'` → `'backlog'`, and the pool value was renamed
55
+ * `'backlog'` → `'todo'` → `'ready'`, ADR
56
+ * `rename-task-pool-folder-todo-to-ready`).
55
57
  */
56
58
  function landingToSide(landing) {
57
- if (landing === 'pre-backlog')
59
+ if (landing === 'backlog')
58
60
  return 'staging';
59
61
  if (landing === 'ready')
60
62
  return 'pool';
@@ -65,7 +67,7 @@ function stagedTaskPath(name) {
65
67
  return `${STAGED_TASKS_DIR}/${name}`;
66
68
  }
67
69
  /**
68
- * Run the `do prd:<slug>` tasking path end-to-end. Never throws for the expected
70
+ * Run the `do spec:<slug>` tasking path end-to-end. Never throws for the expected
69
71
  * gate-refused / lock-lost / agent-failed / stale cases — those are returned with
70
72
  * the appropriate exit code and outcome. The runner owns all git; the agent only
71
73
  * writes task files.
@@ -82,18 +84,18 @@ export async function performTask(options) {
82
84
  const slug = options.slug;
83
85
  const doer = options.doer ?? 'agent';
84
86
  const lock = options.lock ?? DEFAULT_LOCK_SEAM;
85
- // 0. The prd must exist in the checkout (`work/prds/ready/<slug>.md`) — it is the
87
+ // 0. The spec must exist in the checkout (`work/specs/ready/<slug>.md`) — it is the
86
88
  // source the agent tasks + the file the lock holds.
87
89
  const specPath = workItemPath(cwd, 'specs-ready', slug);
88
90
  if (!existsSync(specPath)) {
89
- const message = `no prd '${slug}' found at ${workFolderRel('specs-ready')}/${slug}.md.`;
91
+ const message = `no spec '${slug}' found at ${workFolderRel('specs-ready')}/${slug}.md.`;
90
92
  note(message);
91
93
  return { exitCode: 1, outcome: 'usage-error', slug, message };
92
94
  }
93
95
  const specContent = readFileSync(specPath, 'utf8');
94
96
  const specFm = parseFrontmatter(specContent);
95
97
  // 1. RESOLVE THE GATE (agent path only). The human path is UNBOUND — a human
96
- // decides for themselves whether a prd is taskable.
98
+ // decides for themselves whether a spec is taskable.
97
99
  if (doer === 'agent') {
98
100
  const eligibility = resolveAgentGate(cwd, slug, specFm, options.autoTask, options.explicit ?? false);
99
101
  if (!eligibility.taskable) {
@@ -136,9 +138,9 @@ export async function performTask(options) {
136
138
  }
137
139
  // 2b. ONBOARD the agent's tasking work onto a `work/<slug>` BRANCH cut from the
138
140
  // freshly-fetched `<arbiter>/main` (task `slice-output-through-integration`).
139
- // The prd body rests in `work/prds/ready/<slug>.md` on `<arbiter>/main` (the lock no
140
- // longer moves it), so the branch's base HOLDS the held prd — the lifecycle
141
- // stage below moves it `prd/ → prd-tasked/` ON THIS BRANCH and the shared integrate core (`--propose`
141
+ // The spec body rests in `work/specs/ready/<slug>.md` on `<arbiter>/main` (the lock no
142
+ // longer moves it), so the branch's base HOLDS the held spec — the lifecycle
143
+ // stage below moves it `specs/ready/specs/tasked/` ON THIS BRANCH and the shared integrate core (`--propose`
142
144
  // PR / `--merge` main) lands the whole transition, WITHOUT the lock release
143
145
  // committing tasks straight to `main`. The agent runs IN-PLACE on this branch
144
146
  // (branch ≠ worktree; the isolation seam upgrades it). The HUMAN path stays on
@@ -146,7 +148,7 @@ export async function performTask(options) {
146
148
  if (useLock) {
147
149
  await switchToWorkBranch(cwd, arbiter, slug, env);
148
150
  }
149
- // 3. INVOKE THE AGENT with the to-task prd. It WRITES
151
+ // 3. INVOKE THE AGENT with the to-task spec. It WRITES
150
152
  // `work/tasks/backlog/*.md` task files (the STAGED area — NOT `work/tasks/ready/`,
151
153
  // which is the agent-eligible pool the runner owns the promotion into; task
152
154
  // `pre-backlog-staging-folder-and-promote-step-a`); it does NO git. We
@@ -155,10 +157,10 @@ export async function performTask(options) {
155
157
  const before = snapshotStagedTasks(cwd);
156
158
  // Also snapshot the POOL `work/tasks/ready/` BEFORE the agent runs: the runner's
157
159
  // final commit must scrub any agent writes there (an attempt to self-place into
158
- // the pool, prd US #4) before `git add -A` would sweep them in.
160
+ // the pool, spec US #4) before `git add -A` would sweep them in.
159
161
  const poolBefore = snapshotPool(cwd);
160
162
  // Read the parent-spec self-pointer off `specFm.spec` (populated from the
161
- // `spec:` key or the legacy `prd:` back-compat alias).
163
+ // `spec:` key).
162
164
  const prompt = buildTaskingSpec(slug, specFm.spec);
163
165
  let agent;
164
166
  try {
@@ -204,10 +206,10 @@ export async function performTask(options) {
204
206
  env: agentEnv,
205
207
  note,
206
208
  });
207
- // DECOMPOSITION UNCLEAR: emit NO guessed tasks — route the held prd to
209
+ // DECOMPOSITION UNCLEAR: emit NO guessed tasks — route the held spec to
208
210
  // needs-attention with the questions as the reason. The lock release amends the
209
- // `prd:<slug>` unified lock `active → stuck` (the tasking needs-attention surface
210
- // is the stuck lock now — NO folder write; the prd body stays in `work/prds/ready/`).
211
+ // `spec:<slug>` unified lock `active → stuck` (the tasking needs-attention surface
212
+ // is the stuck lock now — NO folder write; the spec body stays in `work/specs/ready/`).
211
213
  if (loopDisposition.outcome === 'decomposition-unclear') {
212
214
  const reason = decompositionUnclearReason(slug, loopDisposition.specQuestions);
213
215
  if (useLock) {
@@ -242,7 +244,7 @@ export async function performTask(options) {
242
244
  }
243
245
  }
244
246
  // 4. The RUNNER commits the COMPLETING transition: drop the produced backlog
245
- // tasks IN + move the prd tasking/ -> prd-tasked/ (residence = tasked-ness) — now
247
+ // tasks IN + move the spec specs/ready/ -> specs/tasked/ (residence = tasked-ness) — now
246
248
  // through the SHARED integrate core (`--propose` PR / `--merge` main), NOT a
247
249
  // direct commit to `main`. The agent never does git. (The backlog snapshot is
248
250
  // taken AFTER any loop edits, so the runner integrates the IMPROVED tasks,
@@ -253,7 +255,7 @@ export async function performTask(options) {
253
255
  // `runner-deterministic-slice-placement-policy-and-precedence`). Resolve which
254
256
  // folder the runner lands the emitted tasks in BEFORE handing them to the
255
257
  // shared integrate band: precedence `explicit > untrusted-origin ⇒ staging >
256
- // tasksLandIn > built-in (staging)`, all from unforgeable inputs (the prd's
258
+ // tasksLandIn > built-in (staging)`, all from unforgeable inputs (the spec's
257
259
  // stamped `originTrust:` + the resolved per-repo default + the operator's
258
260
  // explicit flag). The agent NEVER influences this; it always writes to
259
261
  // `work/tasks/backlog/`, and the runner redirects at `stage()` time.
@@ -274,11 +276,11 @@ export async function performTask(options) {
274
276
  if (useLock) {
275
277
  // READ-STABILITY BACKSTOP (the lock's content-identity check, now owned at the
276
278
  // integrate seam): the OUTPUT no longer rides the lock release, so the band
277
- // below would otherwise rebase a concurrent edit of the held prd body CLEANLY
278
- // into prd/ (a rename+edit merge) while the tasks were cut from the OLD body —
279
+ // below would otherwise rebase a concurrent edit of the held spec body CLEANLY
280
+ // into spec/ (a rename+edit merge) while the tasks were cut from the OLD body —
279
281
  // the exact silent stale-task drift the lock forbids
280
- // (`work/notes/observations/tasking-lock-does-not-stabilise-prd-content.md`). So we
281
- // compare the CURRENTLY held `work/prds/ready/<slug>.md` blob on the arbiter against
282
+ // (`work/notes/observations/tasking-lock-does-not-stabilise-spec-content.md`). So we
283
+ // compare the CURRENTLY held `work/specs/ready/<slug>.md` blob on the arbiter against
282
284
  // the snapshot the lock TOOK (`lockedBlob`); ANY change ⇒ STALE ⇒ fail loud,
283
285
  // touch NOTHING (the lock stays held; a human re-tasks or routes to
284
286
  // needs-attention). It is the SAME content-identity check `releaseTaskingLock`
@@ -287,16 +289,16 @@ export async function performTask(options) {
287
289
  const stale = await heldSpecIsStale(cwd, arbiter, slug, lockedBlob, env);
288
290
  if (stale) {
289
291
  const specRel = workItemRel('specs-ready', `${slug}.md`);
290
- const message = `RELEASE CONFLICT for '${slug}': the prd was edited (${specRel} ` +
292
+ const message = `RELEASE CONFLICT for '${slug}': the spec was edited (${specRel} ` +
291
293
  `changed on ${arbiter}/main) while the tasking lock was held. The tasking is ` +
292
- `STALE — re-task from the edited prd or route it to needs-attention. ` +
294
+ `STALE — re-task from the edited spec or route it to needs-attention. ` +
293
295
  `The arbiter was NOT modified (lock still held).`;
294
296
  note(message);
295
297
  return { exitCode: 4, outcome: 'stale', slug, message };
296
298
  }
297
299
  // Route the OUTPUT through the SHARED integrate back-half (task
298
- // `slice-output-through-integration`): the produced backlog tasks + the prd
299
- // lifecycle move (`work/prds/ready/ -> work/prds/tasked/`, residence = tasked-ness) integrate
300
+ // `slice-output-through-integration`): the produced backlog tasks + the spec
301
+ // lifecycle move (`work/specs/ready/ -> work/specs/tasked/`, residence = tasked-ness) integrate
300
302
  // via `performIntegration` honoring `--propose` (push the work branch + open a
301
303
  // PR, NO `main` touch) / `--merge` (land on `main`). Because the integrate-time
302
304
  // args resolve ONCE in the shared core, every `do task:` arg applies here by
@@ -331,18 +333,35 @@ export async function performTask(options) {
331
333
  reviewGate: options.reviewGate,
332
334
  reviewModel: options.acceptanceReviewModel,
333
335
  reviewMaxRounds: 1,
336
+ // The cross-job merge-serialiser CAS-retry cap (config `mergeRetries`) —
337
+ // threaded so a wide-matrix CI's raised cap reaches the tasking-
338
+ // transition's land tail too (task
339
+ // `thread-merge-retries-cross-task-and-ratify-default`). Unset ⇒ falls
340
+ // through to the engine default (byte-for-byte unchanged).
341
+ mergeRetries: options.mergeRetries,
334
342
  // The EXPLICITLY-chosen integrate mode proceeds AS-IS on an APPROVE — a
335
343
  // `--merge` tasking run lands on main, `--propose` opens a PR. The tasking
336
344
  // path's merge-vs-propose decision is the `integration` mode the user typed;
337
345
  // `merge` IS the auto-land mode, so a resolved `merge` is never downgraded.
338
346
  // The task gate family is `--review`/`--no-review`/`--review-model` only
339
- // (prd US #6).
347
+ // (spec US #6).
340
348
  mode: options.integration ?? 'propose',
341
349
  noPR: options.noPR,
342
350
  providerInstance: options.providerInstance,
351
+ // PROPOSE-MODE PR BODY (task `slicing-pr-body-summary-threading`): mirror the
352
+ // BUILD path's body threading (`do.ts` — `body: agent.output`). The tasker
353
+ // agent produces no `LaunchResult.output` we can carry, but the slice-SET
354
+ // itself is a summary-worthy artifact: the emitted task slugs+titles, their
355
+ // coverage of the spec's user stories, the dependency graph (keystone +
356
+ // blockedBy edges), and any `needsAnswers` open questions the tasker review
357
+ // loop flagged. Compose it here and thread it into the shared core as `body`
358
+ // so the propose-mode PR carries the summary instead of degrading to
359
+ // `gh pr create --fill`. Empty-set (no emitted tasks) ⇒ undefined ⇒ the
360
+ // provider's `--fill` fallback (no regression on that edge).
361
+ body: composeTaskingProposeBody(slug, emitTasks),
343
362
  type: 'tasking',
344
363
  lifecycle: {
345
- // Read the PR title / commit summary from the held prd (before it moves).
364
+ // Read the PR title / commit summary from the held spec (before it moves).
346
365
  titlePath: workItemPath(cwd, 'specs-ready', slug),
347
366
  commitTag: 'tasked',
348
367
  stage: () => stageTaskingLifecycle({
@@ -367,9 +386,9 @@ export async function performTask(options) {
367
386
  // the review BEFORE the stage/integrate and did NOT integrate the tasks
368
387
  // (correct). The CORRECT task-path destination is the SAME needs-attention
369
388
  // route the lock release owns for the decomposition-unclear verdict: it amends
370
- // the `prd:<slug>` unified lock `active -> stuck` with the block reason (the
389
+ // the `spec:<slug>` unified lock `active -> stuck` with the block reason (the
371
390
  // tasking needs-attention surface is the stuck lock now — NO folder write; the
372
- // prd body stays in `work/prds/ready/`). So on a block we route the held prd to
391
+ // spec body stays in `work/specs/ready/`). So on a block we route the held spec to
373
392
  // needs-attention THROUGH the lock release — the set never lands.
374
393
  if (core.outcome === 'review-blocked') {
375
394
  const reason = taskGateBlockedReason(slug, core.reviewBlockReason);
@@ -396,9 +415,9 @@ export async function performTask(options) {
396
415
  }
397
416
  if (core.outcome === 'review-unparseable') {
398
417
  // The task-set acceptance gate RAN but its verdict was UNPARSEABLE (malformed
399
- // JSON). Route the held prd to needs-attention through the SAME lock-release
418
+ // JSON). Route the held spec to needs-attention through the SAME lock-release
400
419
  // seam the block path uses (the tasking needs-attention surface is the stuck
401
- // `prd:<slug>` lock; no folder write). It is NOT a block (the gate output was
420
+ // `spec:<slug>` lock; no folder write). It is NOT a block (the gate output was
402
421
  // unreadable) — record it as the transient-infra-class re-run signal so the
403
422
  // stuck reason reads correctly; nothing landed.
404
423
  const reason = `The task acceptance gate for '${slug}' produced an UNPARSEABLE verdict ` +
@@ -425,8 +444,8 @@ export async function performTask(options) {
425
444
  };
426
445
  }
427
446
  if (core.outcome === 'completed') {
428
- // The durable `prdprd-tasked` `main` move landed through the shared integrate
429
- // core (the body moved straight from `work/prds/ready/` — no transient `tasking/`
447
+ // The durable `specs/readyspecs/tasked` `main` move landed through the shared integrate
448
+ // core (the body moved straight from `work/specs/ready/` — no transient `tasking/`
430
449
  // marker). The completing commit is owned by the integrate band, NOT
431
450
  // `releaseTaskingLock`, so the unified per-item lock that `acquireTaskingLock`
432
451
  // took is released HERE (delete the ref). A
@@ -443,12 +462,12 @@ export async function performTask(options) {
443
462
  return integrationToTaskResult(core, { slug, emitted, loop: loopTag });
444
463
  }
445
464
  // HUMAN, no-lock path: the human commits on `main` directly (the runner does
446
- // not own the human's git). We report the produced tasks; moving the prd into
447
- // `work/prds/tasked/` (residence = tasked-ness) and committing is the human's to
465
+ // not own the human's git). We report the produced tasks; moving the spec into
466
+ // `work/specs/tasked/` (residence = tasked-ness) and committing is the human's to
448
467
  // do, as with the human `complete`.
449
468
  const message = `Tasked '${slug}' -> ${emitted.length} backlog task` +
450
469
  `${emitted.length === 1 ? '' : 's'} (human path, no lock). Inspect + commit ` +
451
- `the produced files (and move the prd into work/prds/tasked/) yourself.`;
470
+ `the produced files (and move the spec into work/specs/tasked/) yourself.`;
452
471
  note(message);
453
472
  return {
454
473
  exitCode: 0,
@@ -485,11 +504,11 @@ function releaseFailureToResult(released, slug) {
485
504
  * (propose pushed the work branch + opened a PR / merge landed on `main`) the
486
505
  * tasking is `tasked`. The band's FAILURE outcomes are reported on the tasking
487
506
  * contract: a `rebase-conflict` against a concurrently-advanced `main` maps to
488
- * `stale` (exit 4) — the tasking analogue of "the held prd moved under us"; a
507
+ * `stale` (exit 4) — the tasking analogue of "the held spec moved under us"; a
489
508
  * a `gate-failed` cannot occur (the tasking path passes `skipVerify`) but maps to
490
509
  * a usage error defensively. A `review-blocked` (the task-SET ACCEPTANCE GATE
491
510
  * blocked the set, task `slice-acceptance-gate`) is handled by `performTask`
492
- * BEFORE this mapper — it routes the held prd to needs-attention via
511
+ * BEFORE this mapper — it routes the held spec to needs-attention via
493
512
  * the lock release (the task-path needs-attention route) — so it never reaches
494
513
  * here; it is mapped defensively to a usage error if it ever does.
495
514
  */
@@ -501,7 +520,7 @@ function integrationToTaskResult(core, ctx) {
501
520
  : 'opened a PR carrying them (main untouched)';
502
521
  const message = `Tasked '${slug}' -> ${emitted.length} backlog task` +
503
522
  `${emitted.length === 1 ? '' : 's'}; the runner integrated the transition ` +
504
- `through the shared core (moved work/prds/ready/ -> work/prds/tasked/, the ` +
523
+ `through the shared core (moved work/specs/ready/ -> work/specs/tasked/, the ` +
505
524
  `tasked resting state) and ${landed}.`;
506
525
  return { exitCode: 0, outcome: 'tasked', slug, emitted, loop, message };
507
526
  }
@@ -512,7 +531,7 @@ function integrationToTaskResult(core, ctx) {
512
531
  slug,
513
532
  message: core.reason ??
514
533
  `Integrating the tasking of '${slug}' conflicted against the latest ` +
515
- `${slug} main — the tasking is stale; re-task from the current prd.`,
534
+ `${slug} main — the tasking is stale; re-task from the current spec.`,
516
535
  };
517
536
  }
518
537
  return {
@@ -526,16 +545,16 @@ function integrationToTaskResult(core, ctx) {
526
545
  /**
527
546
  * ONBOARD the tasking work onto a `work/<slug>` branch cut from the freshly-
528
547
  * fetched `<arbiter>/main` (task `slice-output-through-integration`). Called
529
- * AFTER the tasking lock is held, so the branch's base HOLDS the prd in
530
- * `work/prds/ready/` (the lock no longer moves the body) — the lifecycle stage then moves
531
- * it `prd/ -> prd-tasked/` ON THIS BRANCH and the shared integrate core lands it. A
548
+ * AFTER the tasking lock is held, so the branch's base HOLDS the spec in
549
+ * `work/specs/ready/` (the lock no longer moves the body) — the lifecycle stage then moves
550
+ * it `specs/ready/ -> specs/tasked/` ON THIS BRANCH and the shared integrate core lands it. A
532
551
  * pre-existing local `work/<slug>` (a re-run) is force-recreated off fresh main.
533
552
  * The agent runs in-place on this branch (branch ≠ worktree).
534
553
  */
535
554
  async function switchToWorkBranch(cwd, arbiter, slug, env) {
536
555
  // The tasking path is the parent-spec namespace (`do spec:<slug>`): the branch
537
556
  // is `work/spec-<slug>`, distinct from a same-slug task-build's `work/task-<slug>`.
538
- // MIGRATE step (prd `prd-to-spec-vocabulary-cutover-and-migration-command`):
557
+ // MIGRATE step (spec `prd-to-spec-vocabulary-cutover-and-migration-command`):
539
558
  // MINT the work-branch under the `spec` namespace token (`workBranchRef` still
540
559
  // parses the legacy `work/prd-<slug>` form, so in-flight branches keep resolving).
541
560
  const branch = workBranchRef('spec', slug);
@@ -545,8 +564,8 @@ async function switchToWorkBranch(cwd, arbiter, slug, env) {
545
564
  /**
546
565
  * The READ-STABILITY content-identity STALE CHECK (the lock's backstop, owned at
547
566
  * the integrate seam now that the OUTPUT no longer rides the lock release): true
548
- * iff the CURRENTLY held `work/prds/ready/<slug>.md` blob on `<arbiter>/main` DIFFERS
549
- * from the snapshot the lock TOOK (`lockedBlob`, read from `work/prds/ready/<slug>.md` at
567
+ * iff the CURRENTLY held `work/specs/ready/<slug>.md` blob on `<arbiter>/main` DIFFERS
568
+ * from the snapshot the lock TOOK (`lockedBlob`, read from `work/specs/ready/<slug>.md` at
550
569
  * acquire). ANY change = a concurrent edit under the lock = the tasking is STALE.
551
570
  * Stronger than a textual rebase conflict (which a rename+edit merge can apply
552
571
  * CLEANLY). When `lockedBlob` is absent (never, in production) it reads as
@@ -558,7 +577,7 @@ async function heldSpecIsStale(cwd, arbiter, slug, lockedBlob, env) {
558
577
  }
559
578
  await gitHard(['fetch', '--quiet', arbiter], cwd, env);
560
579
  const held = await gitSoft(['rev-parse', `${arbiter}/main:${workFolderRel('specs-ready')}/${slug}.md`], cwd, env);
561
- // The prd being absent (already tasked/moved) is NOT this check's concern
580
+ // The spec being absent (already tasked/moved) is NOT this check's concern
562
581
  // (the integrate's rebase/push surfaces that); only a CHANGED held blob is stale.
563
582
  if (held.status !== 0) {
564
583
  return false;
@@ -567,28 +586,28 @@ async function heldSpecIsStale(cwd, arbiter, slug, lockedBlob, env) {
567
586
  }
568
587
  /**
569
588
  * STAGE the tasking lifecycle into the index on the `work/<slug>` branch (the
570
- * {@link performIntegration} lifecycle seam): move the held prd
571
- * `git mv work/prds/ready/<slug>.md -> work/prds/tasked/<slug>.md` (the TASKED resting
589
+ * {@link performIntegration} lifecycle seam): move the held spec
590
+ * `git mv work/specs/ready/<slug>.md -> work/specs/tasked/<slug>.md` (the TASKED resting
572
591
  * state — the build-machine `done/` analogue, the SOURCE OF TRUTH for tasked-ness),
573
592
  * and write+`git add` the produced `work/tasks/backlog/*.md` files. The band's subsequent
574
593
  * `git add -A` + atomic commit folds this AND the agent's uncommitted backlog writes
575
594
  * into ONE runner-owned commit (the agent never does git).
576
595
  *
577
- * TASK `prd-sliced-folder-step-a` (prd `slicing-coherence` US #8): the lifecycle
578
- * destination is `work/prds/tasked/` (NOT back to `work/prds/ready/`) — `prd-tasked/`
596
+ * TASK `prd-sliced-folder-step-a` (spec `slicing-coherence` US #8): the lifecycle
597
+ * destination is `work/specs/tasked/` (NOT back to `work/specs/ready/`) — `specs/tasked/`
579
598
  * residence IS tasked-ness (like `done/` for tasks, with no `done:` marker). The
580
599
  * `tasked:` frontmatter marker was removed entirely in `remove-sliced-marker-step-b`
581
- * (sequenced last): residence in `work/prds/tasked/` is now the sole signal.
600
+ * (sequenced last): residence in `work/specs/tasked/` is now the sole signal.
582
601
  */
583
602
  async function stageTaskingLifecycle(params) {
584
603
  const { cwd, slug, emitTasks, poolBefore, placementDir, placementReason, note, env, } = params;
585
604
  const spec = workItemRel('specs-ready', `${slug}.md`);
586
605
  const specTasked = workItemRel('specs-tasked', `${slug}.md`);
587
606
  // PROPAGATE the origin-trust PROVENANCE (task
588
- // `untrusted-origin-forces-build-propose`): read the held prd's `origin`/
607
+ // `untrusted-origin-forces-build-propose`): read the held spec's `origin`/
589
608
  // `originTrust` stamp BEFORE the move, so each emitted task can carry it. A
590
609
  // task's risk is its BUILD; the stamp must reach the task so the build
591
- // transition can force `propose` for untrusted-origin work. An UNSTAMPED prd (a
610
+ // transition can force `propose` for untrusted-origin work. An UNSTAMPED spec (a
592
611
  // human/local-authored one ⇒ trusted) propagates nothing — the normal path is
593
612
  // untouched.
594
613
  const specAbs = join(cwd, spec);
@@ -596,18 +615,18 @@ async function stageTaskingLifecycle(params) {
596
615
  ? parseFrontmatter(readFileSync(specAbs, 'utf8'))
597
616
  : { origin: undefined, originTrust: undefined };
598
617
  if (placementReason === 'untrusted-origin') {
599
- note(`Untrusted-origin prd '${slug}': forcing the emitted tasks STAGED ` +
618
+ note(`Untrusted-origin spec '${slug}': forcing the emitted tasks STAGED ` +
600
619
  `(${placementDir}/) regardless of tasksLandIn (a human promotes ` +
601
620
  'them into work/tasks/ready/). Pass --tasks-land-in <where> to override.');
602
621
  }
603
- // Move the held prd prd/ -> prd-tasked/ (the TASKED resting state — folder =
604
- // source of truth, like done/ for tasks). This is the DURABLE `prdprd-tasked`
622
+ // Move the held spec specs/ready/ -> specs/tasked/ (the TASKED resting state — folder =
623
+ // source of truth, like done/ for tasks). This is the DURABLE `specs/readyspecs/tasked`
605
624
  // success move, owned by THIS transition's commit (the lock no longer moved the
606
- // body, so the source is `work/prds/ready/`, never `work/tasking/`).
625
+ // body, so the source is `work/specs/ready/`, never `work/tasking/`).
607
626
  mkdirSync(dirname(join(cwd, specTasked)), { recursive: true });
608
627
  await gitHard(['mv', spec, specTasked], cwd, env);
609
628
  await gitHard(['add', '--', specTasked], cwd, env);
610
- // **POOL-PLACEMENT FENCE (prd US #4 / governing ADR
629
+ // **POOL-PLACEMENT FENCE (spec US #4 / governing ADR
611
630
  // `placement-is-runner-deterministic-humanonly-is-agent-judgement`).** The
612
631
  // agent ALWAYS writes to the STAGING folder (`work/tasks/backlog/`); the POOL
613
632
  // (`work/tasks/ready/`) is the agent-eligible pool the runner owns the promotion
@@ -644,11 +663,11 @@ async function stageTaskingLifecycle(params) {
644
663
  * Revert any change/addition the agent made to the POOL `work/tasks/ready/` during a
645
664
  * tasking run. The agent's STAGING folder is `work/tasks/backlog/`; a write to the
646
665
  * pool is an attempt to self-place into the agent-eligible pool the runner owns
647
- * the promotion into (prd US #4 / governing ADR). Compared to the `poolBefore`
666
+ * the promotion into (spec US #4 / governing ADR). Compared to the `poolBefore`
648
667
  * snapshot (the branch-base state of `work/tasks/ready/`, taken BEFORE the agent
649
668
  * ran), any new file is removed from the worktree and any changed file is
650
669
  * checked back out to HEAD — so the subsequent `git add -A` cannot land it. The
651
- * runner's commit then carries ONLY the explicit `pre-backlog/` placement.
670
+ * runner's commit then carries ONLY the explicit `tasks/backlog/` placement.
652
671
  */
653
672
  async function scrubPoolDrift(cwd, poolBefore, env) {
654
673
  const dir = workFolderPath(cwd, 'tasks-ready');
@@ -692,26 +711,27 @@ async function gitHard(args, cwd, env) {
692
711
  /**
693
712
  * Build the needs-attention REASON for a task-SET ACCEPTANCE GATE block (task
694
713
  * `slice-acceptance-gate`): the fresh-context review of the produced set returned
695
- * `block`, so the prd is routed to `work/needs-attention/` with the review's
696
- * blocking findings as the body prose and NO tasks landed. Takes the core's
714
+ * `block`, so the spec is marked stuck on its per-item lock (post lock-cutover —
715
+ * `state: stuck` with this reason on the lock entry, no `work/needs-attention/`
716
+ * folder write) and NO tasks land. Takes the core's
697
717
  * structured `reviewBlockReason` (the gate's blocking findings); falls back to a
698
718
  * generic line when absent. DISTINCT from the improver loop's
699
719
  * {@link decompositionUnclearReason} (which carries the loop's open questions).
700
720
  */
701
721
  function taskGateBlockedReason(slug, findingsReason) {
702
722
  const head = `The task acceptance gate (fresh-context review of the produced SET) blocked ` +
703
- `'${slug}'. The prd is routed to needs-attention with no tasks landed; a human ` +
723
+ `'${slug}'. The spec is routed to needs-attention with no tasks landed; a human ` +
704
724
  `must resolve the blocking findings, then re-task.`;
705
725
  return findingsReason ? `${head}\n\n${findingsReason}` : head;
706
726
  }
707
727
  /**
708
728
  * Build the needs-attention REASON for a decomposition-unclear loop verdict (the
709
- * prd is routed to `work/needs-attention/` with these open questions, no guessed
710
- * tasks). Prose only — recorded as the prd's needs-attention body block.
729
+ * spec is marked stuck on its per-item lock with these open questions, no guessed
730
+ * tasks). Prose only — recorded as the spec's stuck-lock reason.
711
731
  */
712
732
  function decompositionUnclearReason(slug, questions) {
713
733
  const head = `The tasker review→edit loop could not converge on a sound decomposition of ` +
714
- `'${slug}' (--tasker-loop-max exhausted with unresolved blockers). The prd is routed ` +
734
+ `'${slug}' (--tasker-loop-max exhausted with unresolved blockers). The spec is routed ` +
715
735
  `to needs-attention with no guessed tasks; a human must resolve:`;
716
736
  const body = questions.length > 0
717
737
  ? questions.map((q) => `- ${q}`).join('\n')
@@ -759,8 +779,8 @@ function appendQuestionsBlock(content, questions) {
759
779
  /**
760
780
  * Resolve the AGENT tasking gate for `slug`: the pure predicate
761
781
  * (`needsAnswers !== true && humanOnly !== true && autoTask`) plus the
762
- * cross-prd `taskedAfter` ordering, resolved against `work/prds/tasked/` residence of
763
- * the prds present in the checkout.
782
+ * cross-spec `taskedAfter` ordering, resolved against `work/specs/tasked/` residence of
783
+ * the specs present in the checkout.
764
784
  */
765
785
  function resolveAgentGate(cwd, slug, specFm, autoTask, explicit) {
766
786
  return resolveTaskingEligibility({
@@ -772,17 +792,17 @@ function resolveAgentGate(cwd, slug, specFm, autoTask, explicit) {
772
792
  explicit,
773
793
  });
774
794
  }
775
- /** Build an HONEST gate-refusal message naming WHY the agent skipped the prd. */
795
+ /** Build an HONEST gate-refusal message naming WHY the agent skipped the spec. */
776
796
  function gateRefusalReason(slug, specFm, eligibility, options) {
777
797
  const reasons = [];
778
798
  if (specFm.humanOnly === true) {
779
- reasons.push('the prd is humanOnly (a human must drive its tasking)');
799
+ reasons.push('the spec is humanOnly (a human must drive its tasking)');
780
800
  }
781
801
  if (specFm.needsAnswers === true) {
782
- reasons.push('the prd has needsAnswers (open questions block auto-tasking)');
802
+ reasons.push('the spec has needsAnswers (open questions block auto-tasking)');
783
803
  }
784
804
  // The autoTask POLICY only refuses on the NON-explicit (auto-pick pool) path:
785
- // an explicitly-named `do prd:<slug>` is authorized by the naming itself (the
805
+ // an explicitly-named `do spec:<slug>` is authorized by the naming itself (the
786
806
  // build path's autoBuild precedent), so the policy is never the reason there.
787
807
  if (options.explicit !== true &&
788
808
  specFm.humanOnly !== true &&
@@ -791,18 +811,18 @@ function gateRefusalReason(slug, specFm, eligibility, options) {
791
811
  reasons.push("the repo's autoTask policy is off");
792
812
  }
793
813
  if (!eligibility.taskedAfter.satisfied) {
794
- reasons.push(`taskedAfter prd(s) not yet tasked: ${eligibility.taskedAfter.missing.join(', ')}`);
814
+ reasons.push(`taskedAfter spec(s) not yet tasked: ${eligibility.taskedAfter.missing.join(', ')}`);
795
815
  }
796
816
  const why = reasons.length > 0 ? reasons.join('; ') : 'the tasking gate refused';
797
817
  return `Skipped tasking '${slug}': ${why}.`;
798
818
  }
799
819
  /**
800
- * Read the set of slugs whose prds are already TASKED in this checkout — RESIDENCE
801
- * in `work/prds/tasked/` (the tasked resting state, task `prd-sliced-folder-step-a`
802
- * / prd `slicing-coherence` US #9), the build-machine `done/` analogue. The FOLDER
820
+ * Read the set of slugs whose specs are already TASKED in this checkout — RESIDENCE
821
+ * in `work/specs/tasked/` (the tasked resting state, task `prd-sliced-folder-step-a`
822
+ * / spec `slicing-coherence` US #9), the build-machine `done/` analogue. The FOLDER
803
823
  * is the source of truth; the `tasked:` frontmatter marker was removed entirely in
804
824
  * `remove-sliced-marker-step-b` and is NOT consulted. So `taskedAfter` resolves
805
- * against `prd-tasked/` residence
825
+ * against `specs/tasked/` residence
806
826
  * (mirroring `blockedBy` -> `done/`). A missing folder reads as empty. The slug is
807
827
  * read from each file's frontmatter `slug:`, falling back to the filename — the same
808
828
  * shape the task readers use.
@@ -820,7 +840,7 @@ function readTaskedSlugs(cwd) {
820
840
  /**
821
841
  * Build the tasking PROMPT: instruct a fresh-context agent to apply the
822
842
  * **tasking discipline** (`work/protocol/TASKING-PROTOCOL.md`) to the held
823
- * prd at `work/prds/ready/<slug>.md` and to EMIT tracer-bullet vertical
843
+ * spec at `work/specs/ready/<slug>.md` and to EMIT tracer-bullet vertical
824
844
  * tasks under `work/tasks/backlog/`. The discipline body (the tracer-bullet
825
845
  * rules, the two-axis gate guidance, the confidence check, file-orthogonality,
826
846
  * the emitted task shape) lives in `TASKING-PROTOCOL.md` — NOT inlined here
@@ -829,24 +849,26 @@ function readTaskedSlugs(cwd) {
829
849
  * task-template.md`); the doc DESCRIBES it (D2).
830
850
  *
831
851
  * This builder owns ONLY the PER-BUILDER framing: who you are (a fresh-context
832
- * tasker for ONE prd), where the held prd is, where the emitted task files
852
+ * tasker for ONE spec), where the held spec is, where the emitted task files
833
853
  * MUST land (the staging folder, never the pool), and the runner-owns-git
834
854
  * boundary on the agent path. The shared discipline body is NOT duplicated
835
855
  * here.
836
856
  */
837
857
  function buildTaskingSpec(slug, _spec) {
858
+ const specReady = workFolderRel('specs-ready');
859
+ const specTasked = workFolderRel('specs-tasked');
838
860
  return [
839
- `You are a FRESH-CONTEXT tasker for the prd \`work/prds/ready/${slug}.md\`.`,
861
+ `You are a FRESH-CONTEXT tasker for the spec \`${specReady}/${slug}.md\`.`,
840
862
  `Apply the tasking discipline defined in \`work/protocol/TASKING-PROTOCOL.md\``,
841
863
  `(the in-band, protocol-native tasking protocol every set-up repo carries; the`,
842
- `human-facing pointer is \`skills/to-task/SKILL.md\`) to this ONE prd:`,
864
+ `human-facing pointer is \`skills/to-task/SKILL.md\`) to this ONE spec:`,
843
865
  `decompose it into independently-grabbable, tracer-bullet vertical tasks.`,
844
- `Read the prd fully first.`,
866
+ `Read the spec fully first.`,
845
867
  ``,
846
868
  `The discipline rules — the tracer-bullet test, the two-axis gate guidance`,
847
869
  `(\`humanOnly\` is NARROW; \`needsAnswers\` flags genuine uncertainty), the`,
848
870
  `confidence check that REPLACES the human-quiz step when no human is present,`,
849
- `the file-orthogonality preference, the prd-vs-task gate disjointness, and`,
871
+ `the file-orthogonality preference, the spec-vs-task gate disjointness, and`,
850
872
  `the emitted task shape — ALL live in that doc. Read them there.`,
851
873
  ``,
852
874
  `No human is present, so apply the CONFIDENCE CHECK (\`TASKING-PROTOCOL.md\``,
@@ -854,7 +876,7 @@ function buildTaskingSpec(slug, _spec) {
854
876
  `granularity, dependency order, a gate, or a seam is genuinely unresolved, set`,
855
877
  `\`needsAnswers: true\` on the specific uncertain task (questions in its body)`,
856
878
  `rather than guessing — or, if the whole decomposition is unclear, stop and`,
857
- `route the prd to needs-attention with the questions.`,
879
+ `route the spec to needs-attention with the questions.`,
858
880
  ``,
859
881
  `WRITE EVERY emitted task file under \`${STAGED_TASKS_DIR}/\` (the STAGING folder)`,
860
882
  `— NEVER \`work/tasks/ready/\`. \`work/tasks/ready/\` is the agent-eligible POOL and`,
@@ -862,12 +884,12 @@ function buildTaskingSpec(slug, _spec) {
862
884
  `folder is \`work/tasks/backlog/\`. A write outside the staging folder is dropped`,
863
885
  `by the runner-deterministic placement resolver.`,
864
886
  ``,
865
- `Set each task's \`prd:\` field to the source prd slug (\`${slug}\`) so the`,
866
- `link back to the prd survives.`,
887
+ `Set each task's \`spec:\` field to the source spec slug (\`${slug}\`) so the`,
888
+ `link back to the spec survives.`,
867
889
  ``,
868
890
  `Do NOT perform any git operations — do not stage, commit, push, or move any`,
869
891
  `files. The RUNNER owns every git-state transition (it commits the produced`,
870
- `tasks, releases the tasking lock, and moves the prd into \`work/prds/tasked/\`).`,
892
+ `tasks, releases the tasking lock, and moves the spec into \`${specTasked}/\`).`,
871
893
  ].join('\n');
872
894
  }
873
895
  /** Run the task agent. Prefers the injected runner; else the harness seam. */
@@ -937,6 +959,198 @@ function collectEmittedTasks(cwd, relPaths) {
937
959
  }
938
960
  return out;
939
961
  }
962
+ /**
963
+ * Compose the propose-mode PR BODY for a slice-set (task
964
+ * `slicing-pr-body-summary-threading`): mirror the BUILD path's body threading
965
+ * (`do.ts` — `body: agent.output`) with a summary drawn from what the tasker
966
+ * ALREADY has in hand — the emitted task files. The shared
967
+ * {@link composeProposeBody} then wraps this prose with its runner-owned header.
968
+ *
969
+ * Sections (all suppressed when empty, so the body stays honest):
970
+ * 1. A lead line naming the source spec + emitted task count.
971
+ * 2. **Tasks** — one bullet per task: `slug — title`, plus `covers:` (which
972
+ * spec user stories it maps to), `blockedBy:`, and `needsAnswers` if set.
973
+ * 3. **Dependency graph** — the KEYSTONE(S) (tasks whose `blockedBy` names no
974
+ * other task in this set, so they land first) and the `blockedBy` edges.
975
+ * 4. **Needs answers** — any open questions the tasker review→edit loop
976
+ * appended under `## Open questions` on `needsAnswers: true` tasks, so the
977
+ * deferred seams are visible on the PR (not buried in task bodies).
978
+ *
979
+ * Returns `undefined` for the empty set (no emitted tasks) so the provider's
980
+ * `gh pr create --fill` fallback still applies on that edge (matches the build
981
+ * path's absent-body behaviour).
982
+ */
983
+ export function composeTaskingProposeBody(specSlug, emitTasks) {
984
+ const entries = Object.entries(emitTasks);
985
+ if (entries.length === 0) {
986
+ return undefined;
987
+ }
988
+ const tasks = entries
989
+ .map(([rel, content]) => parseTaskSummary(rel, content))
990
+ .sort((a, b) => a.slug.localeCompare(b.slug));
991
+ const setSlugs = new Set(tasks.map((t) => t.slug));
992
+ const lines = [];
993
+ const n = tasks.length;
994
+ lines.push(`Auto-tasked \`${specSlug}\` into ${n} backlog task${n === 1 ? '' : 's'}.`);
995
+ lines.push('');
996
+ lines.push('## Tasks');
997
+ lines.push('');
998
+ for (const t of tasks) {
999
+ lines.push(`- **${t.slug}** — ${t.title}`);
1000
+ if (t.covers.length > 0) {
1001
+ lines.push(` - covers: ${t.covers.map((c) => `US #${c}`).join(', ')}`);
1002
+ }
1003
+ if (t.blockedBy.length > 0) {
1004
+ lines.push(` - blockedBy: ${t.blockedBy.join(', ')}`);
1005
+ }
1006
+ if (t.needsAnswers) {
1007
+ lines.push(' - needsAnswers: true');
1008
+ }
1009
+ }
1010
+ // Dependency graph: keystone(s) are tasks whose blockedBy names no OTHER
1011
+ // member of this set (an external blocker — e.g. a task already in `done/` —
1012
+ // does not prevent this set-relative starting point); the edges list every
1013
+ // set-internal `blockedBy` link so a reviewer can trace the ordering.
1014
+ const keystones = tasks.filter((t) => t.blockedBy.every((b) => !setSlugs.has(b)));
1015
+ const edges = tasks.flatMap((t) => t.blockedBy.filter((b) => setSlugs.has(b)).map((b) => `${t.slug} ← ${b}`));
1016
+ if (keystones.length > 0 || edges.length > 0) {
1017
+ lines.push('');
1018
+ lines.push('## Dependency graph');
1019
+ lines.push('');
1020
+ if (keystones.length > 0) {
1021
+ const word = keystones.length === 1 ? 'Keystone' : 'Keystones';
1022
+ lines.push(`- ${word}: ${keystones.map((k) => k.slug).join(', ')}`);
1023
+ }
1024
+ for (const edge of edges) {
1025
+ lines.push(`- ${edge}`);
1026
+ }
1027
+ }
1028
+ const uncertain = tasks.filter((t) => t.needsAnswers);
1029
+ if (uncertain.length > 0) {
1030
+ lines.push('');
1031
+ lines.push('## Needs answers');
1032
+ lines.push('');
1033
+ for (const t of uncertain) {
1034
+ lines.push(`- **${t.slug}**:`);
1035
+ if (t.openQuestions.length === 0) {
1036
+ lines.push(' - (no specific questions surfaced)');
1037
+ continue;
1038
+ }
1039
+ for (const q of t.openQuestions) {
1040
+ lines.push(` - ${q}`);
1041
+ }
1042
+ }
1043
+ }
1044
+ return lines.join('\n');
1045
+ }
1046
+ /**
1047
+ * Parse one emitted task file into a {@link TaskSummary}. Uses
1048
+ * {@link parseFrontmatter} for the shared fields; scans the raw frontmatter
1049
+ * block for `covers:` (not currently modelled on the parser's typed shape) and
1050
+ * the body for a trailing `## Open questions` bullet list (the loop's
1051
+ * uncertain-task disposition sink, see {@link appendQuestionsBlock}).
1052
+ */
1053
+ function parseTaskSummary(rel, content) {
1054
+ const fm = parseFrontmatter(content);
1055
+ const slug = fm.slug ?? basename(rel).replace(/\.md$/i, '');
1056
+ const title = readTitleField(content) ?? slug;
1057
+ return {
1058
+ slug,
1059
+ title,
1060
+ covers: readCoversField(content),
1061
+ blockedBy: fm.blockedBy,
1062
+ needsAnswers: fm.needsAnswers === true,
1063
+ openQuestions: readOpenQuestions(content),
1064
+ };
1065
+ }
1066
+ /**
1067
+ * Extract the `title:` scalar from the raw frontmatter block (not on
1068
+ * {@link parseFrontmatter}'s typed shape). Missing / malformed ⇒ `undefined`.
1069
+ * Comment-tolerant: a trailing `# ...` after an UNQUOTED value is stripped.
1070
+ */
1071
+ function readTitleField(content) {
1072
+ const normalized = content.replace(/\r\n/g, '\n').replace(/^\uFEFF/, '');
1073
+ if (!normalized.startsWith('---\n')) {
1074
+ return undefined;
1075
+ }
1076
+ const lines = normalized.split('\n');
1077
+ const closing = lines.indexOf('---', 1);
1078
+ if (closing === -1) {
1079
+ return undefined;
1080
+ }
1081
+ for (let i = 1; i < closing; i++) {
1082
+ const m = lines[i].match(/^title:\s*(.*)$/);
1083
+ if (!m)
1084
+ continue;
1085
+ let value = m[1].trim();
1086
+ if (value === '')
1087
+ return undefined;
1088
+ // Quoted ⇒ return inner text verbatim; unquoted ⇒ strip trailing `# ...`.
1089
+ if ((value.startsWith('"') && value.endsWith('"')) ||
1090
+ (value.startsWith("'") && value.endsWith("'"))) {
1091
+ return value.slice(1, -1);
1092
+ }
1093
+ const hash = value.indexOf('#');
1094
+ if (hash !== -1)
1095
+ value = value.slice(0, hash).trimEnd();
1096
+ return value === '' ? undefined : value;
1097
+ }
1098
+ return undefined;
1099
+ }
1100
+ /**
1101
+ * Extract the `covers: [...]` inline-list values from the raw frontmatter block
1102
+ * of a task (not on {@link parseFrontmatter}'s typed shape, so we scan the raw
1103
+ * fence here). Missing / malformed / empty ⇒ `[]`. Comment-tolerant (a trailing
1104
+ * `# ...` after the `]` is ignored, matching the house style on `blockedBy: []
1105
+ * # startable now`).
1106
+ */
1107
+ function readCoversField(content) {
1108
+ const normalized = content.replace(/\r\n/g, '\n').replace(/^\uFEFF/, '');
1109
+ if (!normalized.startsWith('---\n')) {
1110
+ return [];
1111
+ }
1112
+ const lines = normalized.split('\n');
1113
+ const closing = lines.indexOf('---', 1);
1114
+ if (closing === -1) {
1115
+ return [];
1116
+ }
1117
+ for (let i = 1; i < closing; i++) {
1118
+ const line = lines[i];
1119
+ const m = line.match(/^covers:\s*\[([^\]]*)\]/);
1120
+ if (!m)
1121
+ continue;
1122
+ return m[1]
1123
+ .split(',')
1124
+ .map((s) => s.trim().replace(/^['"]|['"]$/g, ''))
1125
+ .filter((s) => s !== '');
1126
+ }
1127
+ return [];
1128
+ }
1129
+ /**
1130
+ * Extract the bullets under a trailing `## Open questions` heading in a task
1131
+ * body — the tasker review loop's uncertain-task sink (see
1132
+ * {@link appendQuestionsBlock}). Returns `[]` when no such block exists.
1133
+ */
1134
+ function readOpenQuestions(content) {
1135
+ const normalized = content.replace(/\r\n/g, '\n');
1136
+ const idx = normalized.lastIndexOf(`\n${OPEN_QUESTIONS_HEADING}`);
1137
+ if (idx === -1) {
1138
+ return [];
1139
+ }
1140
+ const tail = normalized.slice(idx + 1);
1141
+ const lines = tail.split('\n').slice(1);
1142
+ const questions = [];
1143
+ for (const raw of lines) {
1144
+ const line = raw.trimEnd();
1145
+ if (line === '')
1146
+ continue;
1147
+ const m = line.match(/^- (.*)$/);
1148
+ if (!m)
1149
+ break;
1150
+ questions.push(m[1]);
1151
+ }
1152
+ return questions;
1153
+ }
940
1154
  /** List `*.md` files in `dir`, sorted; an absent dir reads as empty. */
941
1155
  function listMarkdown(dir) {
942
1156
  let entries;