dorfl 0.14.2 → 0.15.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 (418) hide show
  1. package/dist/advance-ci-template.d.ts +17 -16
  2. package/dist/advance-ci-template.d.ts.map +1 -1
  3. package/dist/advance-ci-template.js +42 -53
  4. package/dist/advance-ci-template.js.map +1 -1
  5. package/dist/advance-drivers.d.ts +8 -0
  6. package/dist/advance-drivers.d.ts.map +1 -1
  7. package/dist/advance-drivers.js +28 -4
  8. package/dist/advance-drivers.js.map +1 -1
  9. package/dist/advance-isolated.d.ts.map +1 -1
  10. package/dist/advance-isolated.js +3 -2
  11. package/dist/advance-isolated.js.map +1 -1
  12. package/dist/advance-lifecycle-template.d.ts +41 -40
  13. package/dist/advance-lifecycle-template.d.ts.map +1 -1
  14. package/dist/advance-lifecycle-template.js +309 -489
  15. package/dist/advance-lifecycle-template.js.map +1 -1
  16. package/dist/advance-loop-driver.d.ts.map +1 -1
  17. package/dist/advance-loop-driver.js +3 -2
  18. package/dist/advance-loop-driver.js.map +1 -1
  19. package/dist/advance-treeless-publish.d.ts +31 -0
  20. package/dist/advance-treeless-publish.d.ts.map +1 -1
  21. package/dist/advance-treeless-publish.js +40 -0
  22. package/dist/advance-treeless-publish.js.map +1 -1
  23. package/dist/advance.d.ts +63 -3
  24. package/dist/advance.d.ts.map +1 -1
  25. package/dist/advance.js +80 -12
  26. package/dist/advance.js.map +1 -1
  27. package/dist/advancing-lock.d.ts +9 -0
  28. package/dist/advancing-lock.d.ts.map +1 -1
  29. package/dist/advancing-lock.js +21 -1
  30. package/dist/advancing-lock.js.map +1 -1
  31. package/dist/agent-env.d.ts +61 -0
  32. package/dist/agent-env.d.ts.map +1 -0
  33. package/dist/agent-env.js +144 -0
  34. package/dist/agent-env.js.map +1 -0
  35. package/dist/agent-stop.d.ts +35 -0
  36. package/dist/agent-stop.d.ts.map +1 -1
  37. package/dist/agent-stop.js +62 -0
  38. package/dist/agent-stop.js.map +1 -1
  39. package/dist/apply-merge-action.d.ts +76 -34
  40. package/dist/apply-merge-action.d.ts.map +1 -1
  41. package/dist/apply-merge-action.js +339 -129
  42. package/dist/apply-merge-action.js.map +1 -1
  43. package/dist/apply-stuck-action.js +2 -2
  44. package/dist/apply-stuck-action.js.map +1 -1
  45. package/dist/ci-agent-result.d.ts +151 -0
  46. package/dist/ci-agent-result.d.ts.map +1 -0
  47. package/dist/ci-agent-result.js +314 -0
  48. package/dist/ci-agent-result.js.map +1 -0
  49. package/dist/ci-handoff-apply.d.ts +162 -0
  50. package/dist/ci-handoff-apply.d.ts.map +1 -0
  51. package/dist/ci-handoff-apply.js +497 -0
  52. package/dist/ci-handoff-apply.js.map +1 -0
  53. package/dist/ci-handoff-format.d.ts +369 -0
  54. package/dist/ci-handoff-format.d.ts.map +1 -0
  55. package/dist/ci-handoff-format.js +646 -0
  56. package/dist/ci-handoff-format.js.map +1 -0
  57. package/dist/ci-handoff-lfs.d.ts +146 -0
  58. package/dist/ci-handoff-lfs.d.ts.map +1 -0
  59. package/dist/ci-handoff-lfs.js +397 -0
  60. package/dist/ci-handoff-lfs.js.map +1 -0
  61. package/dist/ci-handoff.d.ts +99 -0
  62. package/dist/ci-handoff.d.ts.map +1 -0
  63. package/dist/ci-handoff.js +277 -0
  64. package/dist/ci-handoff.js.map +1 -0
  65. package/dist/ci-lock-outputs.d.ts +93 -0
  66. package/dist/ci-lock-outputs.d.ts.map +1 -0
  67. package/dist/ci-lock-outputs.js +223 -0
  68. package/dist/ci-lock-outputs.js.map +1 -0
  69. package/dist/ci-phase-build.d.ts +178 -0
  70. package/dist/ci-phase-build.d.ts.map +1 -0
  71. package/dist/ci-phase-build.js +989 -0
  72. package/dist/ci-phase-build.js.map +1 -0
  73. package/dist/ci-phase-driver.d.ts +247 -0
  74. package/dist/ci-phase-driver.d.ts.map +1 -0
  75. package/dist/ci-phase-driver.js +370 -0
  76. package/dist/ci-phase-driver.js.map +1 -0
  77. package/dist/ci-phase-intake.d.ts +172 -0
  78. package/dist/ci-phase-intake.d.ts.map +1 -0
  79. package/dist/ci-phase-intake.js +555 -0
  80. package/dist/ci-phase-intake.js.map +1 -0
  81. package/dist/ci-phase-tasking.d.ts +205 -0
  82. package/dist/ci-phase-tasking.d.ts.map +1 -0
  83. package/dist/ci-phase-tasking.js +906 -0
  84. package/dist/ci-phase-tasking.js.map +1 -0
  85. package/dist/ci-phase-treeless.d.ts +224 -0
  86. package/dist/ci-phase-treeless.d.ts.map +1 -0
  87. package/dist/ci-phase-treeless.js +1482 -0
  88. package/dist/ci-phase-treeless.js.map +1 -0
  89. package/dist/ci-read-token.d.ts +47 -0
  90. package/dist/ci-read-token.d.ts.map +1 -0
  91. package/dist/ci-read-token.js +80 -0
  92. package/dist/ci-read-token.js.map +1 -0
  93. package/dist/cli.d.ts.map +1 -1
  94. package/dist/cli.js +447 -22
  95. package/dist/cli.js.map +1 -1
  96. package/dist/close-job-template.d.ts +10 -1
  97. package/dist/close-job-template.d.ts.map +1 -1
  98. package/dist/close-job-template.js +46 -14
  99. package/dist/close-job-template.js.map +1 -1
  100. package/dist/close-job.d.ts +20 -5
  101. package/dist/close-job.d.ts.map +1 -1
  102. package/dist/close-job.js +65 -12
  103. package/dist/close-job.js.map +1 -1
  104. package/dist/complete.d.ts.map +1 -1
  105. package/dist/complete.js +20 -1
  106. package/dist/complete.js.map +1 -1
  107. package/dist/config.d.ts +41 -44
  108. package/dist/config.d.ts.map +1 -1
  109. package/dist/config.js +2 -4
  110. package/dist/config.js.map +1 -1
  111. package/dist/continue-branch.d.ts +63 -15
  112. package/dist/continue-branch.d.ts.map +1 -1
  113. package/dist/continue-branch.js +75 -8
  114. package/dist/continue-branch.js.map +1 -1
  115. package/dist/crashed-run-save.d.ts +73 -0
  116. package/dist/crashed-run-save.d.ts.map +1 -0
  117. package/dist/crashed-run-save.js +86 -0
  118. package/dist/crashed-run-save.js.map +1 -0
  119. package/dist/cwd-section.d.ts.map +1 -1
  120. package/dist/cwd-section.js +10 -2
  121. package/dist/cwd-section.js.map +1 -1
  122. package/dist/decision-engine.d.ts +10 -0
  123. package/dist/decision-engine.d.ts.map +1 -1
  124. package/dist/decision-engine.js.map +1 -1
  125. package/dist/do-config.d.ts +2 -24
  126. package/dist/do-config.d.ts.map +1 -1
  127. package/dist/do-config.js +1 -33
  128. package/dist/do-config.js.map +1 -1
  129. package/dist/do.d.ts +149 -1
  130. package/dist/do.d.ts.map +1 -1
  131. package/dist/do.js +115 -25
  132. package/dist/do.js.map +1 -1
  133. package/dist/dorfl-item-template.d.ts +98 -0
  134. package/dist/dorfl-item-template.d.ts.map +1 -0
  135. package/dist/dorfl-item-template.js +504 -0
  136. package/dist/dorfl-item-template.js.map +1 -0
  137. package/dist/env-config.d.ts.map +1 -1
  138. package/dist/env-config.js +7 -5
  139. package/dist/env-config.js.map +1 -1
  140. package/dist/frontmatter.d.ts +52 -0
  141. package/dist/frontmatter.d.ts.map +1 -1
  142. package/dist/frontmatter.js +136 -2
  143. package/dist/frontmatter.js.map +1 -1
  144. package/dist/gc.d.ts +13 -0
  145. package/dist/gc.d.ts.map +1 -1
  146. package/dist/gc.js +16 -2
  147. package/dist/gc.js.map +1 -1
  148. package/dist/git.d.ts +18 -1
  149. package/dist/git.d.ts.map +1 -1
  150. package/dist/git.js +70 -5
  151. package/dist/git.js.map +1 -1
  152. package/dist/harness.d.ts +44 -5
  153. package/dist/harness.d.ts.map +1 -1
  154. package/dist/harness.js +24 -1
  155. package/dist/harness.js.map +1 -1
  156. package/dist/index.d.ts +11 -4
  157. package/dist/index.d.ts.map +1 -1
  158. package/dist/index.js +7 -3
  159. package/dist/index.js.map +1 -1
  160. package/dist/install-ci-action-pins.d.ts +123 -0
  161. package/dist/install-ci-action-pins.d.ts.map +1 -0
  162. package/dist/install-ci-action-pins.js +190 -0
  163. package/dist/install-ci-action-pins.js.map +1 -0
  164. package/dist/install-ci-actions-pr-creation.d.ts +70 -0
  165. package/dist/install-ci-actions-pr-creation.d.ts.map +1 -0
  166. package/dist/install-ci-actions-pr-creation.js +106 -0
  167. package/dist/install-ci-actions-pr-creation.js.map +1 -0
  168. package/dist/install-ci-capabilities/dorfl-item.d.ts +12 -0
  169. package/dist/install-ci-capabilities/dorfl-item.d.ts.map +1 -0
  170. package/dist/install-ci-capabilities/dorfl-item.js +26 -0
  171. package/dist/install-ci-capabilities/dorfl-item.js.map +1 -0
  172. package/dist/install-ci-core.d.ts +155 -37
  173. package/dist/install-ci-core.d.ts.map +1 -1
  174. package/dist/install-ci-core.js +304 -200
  175. package/dist/install-ci-core.js.map +1 -1
  176. package/dist/install-ci-github.d.ts +47 -2
  177. package/dist/install-ci-github.d.ts.map +1 -1
  178. package/dist/install-ci-github.js +113 -1
  179. package/dist/install-ci-github.js.map +1 -1
  180. package/dist/install-ci.d.ts +25 -6
  181. package/dist/install-ci.d.ts.map +1 -1
  182. package/dist/install-ci.js +72 -36
  183. package/dist/install-ci.js.map +1 -1
  184. package/dist/intake-marker.d.ts +6 -1
  185. package/dist/intake-marker.d.ts.map +1 -1
  186. package/dist/intake-marker.js.map +1 -1
  187. package/dist/intake-triage.d.ts.map +1 -1
  188. package/dist/intake-triage.js +6 -2
  189. package/dist/intake-triage.js.map +1 -1
  190. package/dist/intake-trigger-template.d.ts +31 -25
  191. package/dist/intake-trigger-template.d.ts.map +1 -1
  192. package/dist/intake-trigger-template.js +107 -292
  193. package/dist/intake-trigger-template.js.map +1 -1
  194. package/dist/intake.d.ts +186 -4
  195. package/dist/intake.d.ts.map +1 -1
  196. package/dist/intake.js +533 -416
  197. package/dist/intake.js.map +1 -1
  198. package/dist/integration-core.d.ts +154 -1
  199. package/dist/integration-core.d.ts.map +1 -1
  200. package/dist/integration-core.js +759 -486
  201. package/dist/integration-core.js.map +1 -1
  202. package/dist/integrator.d.ts +12 -0
  203. package/dist/integrator.d.ts.map +1 -1
  204. package/dist/integrator.js +21 -0
  205. package/dist/integrator.js.map +1 -1
  206. package/dist/isolation.d.ts +12 -2
  207. package/dist/isolation.d.ts.map +1 -1
  208. package/dist/isolation.js +19 -8
  209. package/dist/isolation.js.map +1 -1
  210. package/dist/item-lock.d.ts +93 -11
  211. package/dist/item-lock.d.ts.map +1 -1
  212. package/dist/item-lock.js +233 -65
  213. package/dist/item-lock.js.map +1 -1
  214. package/dist/land-question-residue.d.ts +58 -0
  215. package/dist/land-question-residue.d.ts.map +1 -0
  216. package/dist/land-question-residue.js +95 -0
  217. package/dist/land-question-residue.js.map +1 -0
  218. package/dist/lifecycle-gather.d.ts +16 -0
  219. package/dist/lifecycle-gather.d.ts.map +1 -1
  220. package/dist/lifecycle-gather.js +54 -6
  221. package/dist/lifecycle-gather.js.map +1 -1
  222. package/dist/merge-answer.d.ts +41 -0
  223. package/dist/merge-answer.d.ts.map +1 -0
  224. package/dist/merge-answer.js +60 -0
  225. package/dist/merge-answer.js.map +1 -0
  226. package/dist/merge-question-surfacer.d.ts +105 -30
  227. package/dist/merge-question-surfacer.d.ts.map +1 -1
  228. package/dist/merge-question-surfacer.js +202 -91
  229. package/dist/merge-question-surfacer.js.map +1 -1
  230. package/dist/merge-question-tick.d.ts +64 -0
  231. package/dist/merge-question-tick.d.ts.map +1 -0
  232. package/dist/merge-question-tick.js +96 -0
  233. package/dist/merge-question-tick.js.map +1 -0
  234. package/dist/mint-adr.d.ts.map +1 -1
  235. package/dist/mint-adr.js +20 -2
  236. package/dist/mint-adr.js.map +1 -1
  237. package/dist/needs-attention.d.ts +72 -2
  238. package/dist/needs-attention.d.ts.map +1 -1
  239. package/dist/needs-attention.js +275 -38
  240. package/dist/needs-attention.js.map +1 -1
  241. package/dist/phase-recorder.d.ts +168 -0
  242. package/dist/phase-recorder.d.ts.map +1 -0
  243. package/dist/phase-recorder.js +218 -0
  244. package/dist/phase-recorder.js.map +1 -0
  245. package/dist/phase.d.ts +94 -0
  246. package/dist/phase.d.ts.map +1 -0
  247. package/dist/phase.js +148 -0
  248. package/dist/phase.js.map +1 -0
  249. package/dist/pi-harness.d.ts.map +1 -1
  250. package/dist/pi-harness.js +19 -6
  251. package/dist/pi-harness.js.map +1 -1
  252. package/dist/prepare.d.ts.map +1 -1
  253. package/dist/prepare.js +4 -0
  254. package/dist/prepare.js.map +1 -1
  255. package/dist/prompt.d.ts +4 -0
  256. package/dist/prompt.d.ts.map +1 -1
  257. package/dist/prompt.js +48 -1
  258. package/dist/prompt.js.map +1 -1
  259. package/dist/protocol/CLAIM-PROTOCOL.md +3 -1
  260. package/dist/protocol/WORK-CONTRACT.md +1 -1
  261. package/dist/reap-branches.d.ts +48 -0
  262. package/dist/reap-branches.d.ts.map +1 -1
  263. package/dist/reap-branches.js +64 -0
  264. package/dist/reap-branches.js.map +1 -1
  265. package/dist/ref-write.d.ts +198 -0
  266. package/dist/ref-write.d.ts.map +1 -0
  267. package/dist/ref-write.js +79 -0
  268. package/dist/ref-write.js.map +1 -0
  269. package/dist/repo-config.js +8 -8
  270. package/dist/repo-config.js.map +1 -1
  271. package/dist/repo-mirror.d.ts +36 -0
  272. package/dist/repo-mirror.d.ts.map +1 -1
  273. package/dist/repo-mirror.js +91 -1
  274. package/dist/repo-mirror.js.map +1 -1
  275. package/dist/review-verdict.d.ts +1 -1
  276. package/dist/review-verdict.js +1 -1
  277. package/dist/run.d.ts +7 -1
  278. package/dist/run.d.ts.map +1 -1
  279. package/dist/run.js +28 -4
  280. package/dist/run.js.map +1 -1
  281. package/dist/scan.d.ts +19 -1
  282. package/dist/scan.d.ts.map +1 -1
  283. package/dist/scan.js +21 -8
  284. package/dist/scan.js.map +1 -1
  285. package/dist/sidecar.d.ts +14 -0
  286. package/dist/sidecar.d.ts.map +1 -1
  287. package/dist/sidecar.js +21 -0
  288. package/dist/sidecar.js.map +1 -1
  289. package/dist/single-job-warning.d.ts +63 -0
  290. package/dist/single-job-warning.d.ts.map +1 -0
  291. package/dist/single-job-warning.js +110 -0
  292. package/dist/single-job-warning.js.map +1 -0
  293. package/dist/skills/setup/protocol/CLAIM-PROTOCOL.md +3 -1
  294. package/dist/skills/setup/protocol/WORK-CONTRACT.md +1 -1
  295. package/dist/spec-complete.d.ts +13 -6
  296. package/dist/spec-complete.d.ts.map +1 -1
  297. package/dist/spec-complete.js +23 -11
  298. package/dist/spec-complete.js.map +1 -1
  299. package/dist/start.d.ts.map +1 -1
  300. package/dist/start.js +47 -14
  301. package/dist/start.js.map +1 -1
  302. package/dist/status.d.ts +12 -0
  303. package/dist/status.d.ts.map +1 -1
  304. package/dist/status.js +11 -3
  305. package/dist/status.js.map +1 -1
  306. package/dist/tasking.d.ts +246 -1
  307. package/dist/tasking.d.ts.map +1 -1
  308. package/dist/tasking.js +452 -358
  309. package/dist/tasking.js.map +1 -1
  310. package/dist/triage-persist.d.ts.map +1 -1
  311. package/dist/triage-persist.js +21 -2
  312. package/dist/triage-persist.js.map +1 -1
  313. package/dist/verify-workflow-template.d.ts.map +1 -1
  314. package/dist/verify-workflow-template.js +8 -4
  315. package/dist/verify-workflow-template.js.map +1 -1
  316. package/dist/verify.d.ts.map +1 -1
  317. package/dist/verify.js +4 -0
  318. package/dist/verify.js.map +1 -1
  319. package/dist/watch-session.d.ts +42 -13
  320. package/dist/watch-session.d.ts.map +1 -1
  321. package/dist/watch-session.js +52 -23
  322. package/dist/watch-session.js.map +1 -1
  323. package/dist/work-layout.d.ts +10 -4
  324. package/dist/work-layout.d.ts.map +1 -1
  325. package/dist/work-layout.js +11 -3
  326. package/dist/work-layout.js.map +1 -1
  327. package/dist/workspace.d.ts +65 -4
  328. package/dist/workspace.d.ts.map +1 -1
  329. package/dist/workspace.js +159 -11
  330. package/dist/workspace.js.map +1 -1
  331. package/package.json +1 -1
  332. package/src/advance-ci-template.ts +80 -81
  333. package/src/advance-drivers.ts +45 -6
  334. package/src/advance-isolated.ts +3 -5
  335. package/src/advance-lifecycle-template.ts +375 -538
  336. package/src/advance-loop-driver.ts +3 -5
  337. package/src/advance-treeless-publish.ts +61 -0
  338. package/src/advance.ts +145 -15
  339. package/src/advancing-lock.ts +36 -1
  340. package/src/agent-env.ts +174 -0
  341. package/src/agent-stop.ts +81 -0
  342. package/src/apply-merge-action.ts +401 -154
  343. package/src/apply-stuck-action.ts +2 -2
  344. package/src/ci-agent-result.ts +421 -0
  345. package/src/ci-handoff-apply.ts +737 -0
  346. package/src/ci-handoff-format.ts +926 -0
  347. package/src/ci-handoff-lfs.ts +519 -0
  348. package/src/ci-handoff.ts +392 -0
  349. package/src/ci-lock-outputs.ts +273 -0
  350. package/src/ci-phase-build.ts +1328 -0
  351. package/src/ci-phase-driver.ts +548 -0
  352. package/src/ci-phase-intake.ts +861 -0
  353. package/src/ci-phase-tasking.ts +1255 -0
  354. package/src/ci-phase-treeless.ts +2028 -0
  355. package/src/ci-read-token.ts +90 -0
  356. package/src/cli.ts +591 -29
  357. package/src/close-job-template.ts +52 -15
  358. package/src/close-job.ts +77 -12
  359. package/src/complete.ts +19 -1
  360. package/src/config.ts +43 -48
  361. package/src/continue-branch.ts +121 -16
  362. package/src/crashed-run-save.ts +162 -0
  363. package/src/cwd-section.ts +10 -0
  364. package/src/decision-engine.ts +10 -0
  365. package/src/do-config.ts +2 -57
  366. package/src/do.ts +170 -28
  367. package/src/dorfl-item-template.ts +556 -0
  368. package/src/env-config.ts +7 -5
  369. package/src/frontmatter.ts +155 -2
  370. package/src/gc.ts +27 -2
  371. package/src/git.ts +83 -5
  372. package/src/harness.ts +58 -6
  373. package/src/index.ts +50 -1
  374. package/src/install-ci-action-pins.ts +209 -0
  375. package/src/install-ci-actions-pr-creation.ts +186 -0
  376. package/src/install-ci-capabilities/dorfl-item.ts +33 -0
  377. package/src/install-ci-core.ts +388 -233
  378. package/src/install-ci-github.ts +149 -1
  379. package/src/install-ci.ts +83 -37
  380. package/src/intake-marker.ts +6 -1
  381. package/src/intake-triage.ts +7 -3
  382. package/src/intake-trigger-template.ts +126 -327
  383. package/src/intake.ts +725 -544
  384. package/src/integration-core.ts +975 -523
  385. package/src/integrator.ts +28 -0
  386. package/src/isolation.ts +29 -10
  387. package/src/item-lock.ts +308 -92
  388. package/src/land-question-residue.ts +122 -0
  389. package/src/lifecycle-gather.ts +82 -10
  390. package/src/merge-answer.ts +82 -0
  391. package/src/merge-question-surfacer.ts +320 -112
  392. package/src/merge-question-tick.ts +182 -0
  393. package/src/mint-adr.ts +28 -2
  394. package/src/needs-attention.ts +350 -42
  395. package/src/phase-recorder.ts +350 -0
  396. package/src/phase.ts +177 -0
  397. package/src/pi-harness.ts +25 -5
  398. package/src/prepare.ts +4 -0
  399. package/src/prompt.ts +68 -1
  400. package/src/reap-branches.ts +107 -0
  401. package/src/ref-write.ts +324 -0
  402. package/src/repo-config.ts +8 -8
  403. package/src/repo-mirror.ts +107 -1
  404. package/src/review-verdict.ts +1 -1
  405. package/src/run.ts +47 -6
  406. package/src/scan.ts +37 -7
  407. package/src/sidecar.ts +36 -0
  408. package/src/single-job-warning.ts +135 -0
  409. package/src/spec-complete.ts +36 -14
  410. package/src/start.ts +55 -16
  411. package/src/status.ts +29 -3
  412. package/src/tasking.ts +593 -378
  413. package/src/triage-persist.ts +24 -2
  414. package/src/verify-workflow-template.ts +8 -4
  415. package/src/verify.ts +4 -0
  416. package/src/watch-session.ts +86 -28
  417. package/src/work-layout.ts +11 -3
  418. package/src/workspace.ts +210 -14
@@ -0,0 +1,122 @@
1
+ import {existsSync, readFileSync, rmSync, writeFileSync} from 'node:fs';
2
+ import {join} from 'node:path';
3
+ import {parseFrontmatter, setNeedsAnswersMarker} from './frontmatter.js';
4
+ import {isEntryAnswered, parseSidecar, sidecarPathFor} from './sidecar.js';
5
+ import {workItemPath, workItemRel} from './work-layout.js';
6
+
7
+ /** What {@link clearOwnQuestionResidueOnLand} did to the working tree. */
8
+ export interface LandQuestionResidueResult {
9
+ /**
10
+ * Repo-relative paths this pass CHANGED in the working tree (the done record
11
+ * whose `needsAnswers` it cleared, the sidecar it deleted). Empty ⇒ the tree is
12
+ * byte-for-byte untouched (the normal land).
13
+ */
14
+ changed: string[];
15
+ /** True when the done record's `needsAnswers: true` was rewritten to `false`. */
16
+ unflagged: boolean;
17
+ /** True when the item's own unanswered question sidecar was deleted. */
18
+ drainedSidecar: boolean;
19
+ /**
20
+ * True when the item's sidecar carries a human ANSWER (or could not be parsed)
21
+ * and was therefore left alone, together with the flag.
22
+ */
23
+ held: boolean;
24
+ }
25
+
26
+ /**
27
+ * CLEAR a landing task's OWN stale question state in the WORKING TREE, so it
28
+ * rides the land's completion commit instead of surviving onto `main` (task
29
+ * `a-recovered-task-lands-without-stale-question-state`).
30
+ *
31
+ * THE DEFECT. A task surfaced to needs-attention (`surfaceStuckToNeedsAttention`
32
+ * writes `needsAnswers: true` on the body AND `work/questions/task-<slug>.md` in
33
+ * one commit on `main`) and then RECOVERED (a re-dispatch, a requeue continue, a
34
+ * stranded-branch finish) carried both halves straight through its done-move:
35
+ * the `git mv ... → tasks/done/` kept the flag, and nothing deleted the sidecar.
36
+ * The claim-time reconcile (`reconcileTerminalQuestionResidue`) drained them only
37
+ * on the NEXT claim, so the ledger was wrong in between, and stayed wrong when no
38
+ * claim followed.
39
+ *
40
+ * WHY THE LAND and not a later sweep: the done-move is the moment the residue
41
+ * becomes residue, and the land already owns a commit that touches exactly this
42
+ * item. Folding the clean-up into it leaves no window at all.
43
+ *
44
+ * SAME SEMANTICS as the claim-time drain for a `completed` terminal, so the two
45
+ * never disagree about what "stale" means:
46
+ * - the flag is set to `needsAnswers: false` via the SAME
47
+ * {@link setNeedsAnswersMarker} writer, and only when it parses back as
48
+ * `false` (a body the writer cannot annotate is left alone rather than
49
+ * written as something we cannot vouch for);
50
+ * - a sidecar carrying ANY answered entry is never deleted, and the item's flag
51
+ * is then left alone too (a human's unapplied answer is data the tool did not
52
+ * author; the answered-merge apply rung owns that sidecar). An unparseable
53
+ * sidecar is held the same way (every uncertainty resolves to leaving state
54
+ * alone).
55
+ *
56
+ * Only ever touches the landing task's own two paths, and only when its done
57
+ * record exists in the tree. A normal land (no flag, no sidecar) changes nothing.
58
+ * Best-effort: never throws; a fault leaves the tree as it was.
59
+ */
60
+ export function clearOwnQuestionResidueOnLand(params: {
61
+ cwd: string;
62
+ slug: string;
63
+ note?: (message: string) => void;
64
+ }): LandQuestionResidueResult {
65
+ const {cwd, slug} = params;
66
+ const note = params.note ?? (() => {});
67
+ const result: LandQuestionResidueResult = {
68
+ changed: [],
69
+ unflagged: false,
70
+ drainedSidecar: false,
71
+ held: false,
72
+ };
73
+ const donePath = workItemPath(cwd, 'done', slug);
74
+ if (!existsSync(donePath)) {
75
+ return result; // not a task done-move (a lifecycle / degenerate shape).
76
+ }
77
+ const sidecarRel = sidecarPathFor(`task:${slug}`);
78
+ const sidecarAbs = join(cwd, sidecarRel);
79
+ try {
80
+ if (existsSync(sidecarAbs)) {
81
+ let answered: boolean;
82
+ try {
83
+ answered = parseSidecar(readFileSync(sidecarAbs, 'utf8')).entries.some(
84
+ (e) => isEntryAnswered(e),
85
+ );
86
+ } catch {
87
+ answered = true; // unparseable: hold, never delete what we cannot read.
88
+ }
89
+ if (answered) {
90
+ result.held = true;
91
+ note(
92
+ `Left ${sidecarRel} and the needsAnswers flag of '${slug}' in place: ` +
93
+ 'the sidecar carries an answer (or could not be read), which the ' +
94
+ 'land never discards.',
95
+ );
96
+ return result;
97
+ }
98
+ rmSync(sidecarAbs, {force: true});
99
+ result.drainedSidecar = true;
100
+ result.changed.push(sidecarRel);
101
+ }
102
+ const body = readFileSync(donePath, 'utf8');
103
+ if (parseFrontmatter(body).needsAnswers === true) {
104
+ const cleared = setNeedsAnswersMarker(body, false);
105
+ if (parseFrontmatter(cleared).needsAnswers === false) {
106
+ writeFileSync(donePath, cleared);
107
+ result.unflagged = true;
108
+ result.changed.push(workItemRel('done', `${slug}.md`));
109
+ }
110
+ }
111
+ } catch {
112
+ // Best-effort: never fail a green land over question-state hygiene.
113
+ }
114
+ if (result.changed.length > 0) {
115
+ note(
116
+ `Cleared the stale question state of '${slug}' in its land ` +
117
+ `(${result.changed.join(', ')}): the task is done, so its ` +
118
+ 'needs-attention question is moot.',
119
+ );
120
+ }
121
+ return result;
122
+ }
@@ -7,6 +7,7 @@ import {
7
7
  type SidecarModel,
8
8
  } from './sidecar.js';
9
9
  import {runAsync} from './git.js';
10
+ import {answeredMergeActionIn} from './merge-answer.js';
10
11
  import {
11
12
  buildLifecyclePools,
12
13
  type LifecyclePoolGates,
@@ -153,10 +154,20 @@ export function gatherLifecycleInPlace(input: {
153
154
  * behaviour) so callers who cannot resolve a lock read still work.
154
155
  */
155
156
  heldSlugs?: Set<string>;
157
+ /**
158
+ * The held TASK slugs whose lock a finished propose build keeps (the
159
+ * `propose-pr` marker, `proposeKeptTaskSlugs`). Such a slug is NOT subtracted
160
+ * when its sidecar carries an answered `merge` (task
161
+ * `wire-merge-questions-into-the-advance-tick`, decision 6): the answered-merge
162
+ * apply takes that lock over. Every other held slug is still subtracted.
163
+ * Defaults empty.
164
+ */
165
+ proposeKeptSlugs?: Set<string>;
156
166
  }): SelectedLifecyclePools {
157
167
  const read = input.read ?? ledgerRead;
158
168
  const repoPath = input.repoPath;
159
169
  const heldSlugs = input.heldSlugs ?? new Set<string>();
170
+ const proposeKept = input.proposeKeptSlugs ?? new Set<string>();
160
171
 
161
172
  const rawObservations = read.resolveLocalState({repoPath}).observations;
162
173
  const observations: ObservationCandidate[] = rawObservations.map((obs) => ({
@@ -165,21 +176,40 @@ export function gatherLifecycleInPlace(input: {
165
176
  sidecar: readSidecarInPlace(repoPath, 'observation', obs.slug),
166
177
  }));
167
178
  const surfaceStaging = input.gates?.surfaceStaging === true;
168
- const needsAnswers: NeedsAnswersCandidate[] = blockedItemsInPlace(
169
- read,
170
- repoPath,
171
- surfaceStaging,
172
- )
173
- // Held-slug subtraction (task-scoped): a `needsAnswers` TASK whose per-item
174
- // lock is currently held is EXCLUDED from surface/apply enumeration. Spec /
175
- // observation entries are untouched — the lock ref set here is task-only.
176
- .filter((item) => !(item.namespace === 'task' && heldSlugs.has(item.slug)))
179
+ const blocked = blockedItemsInPlace(read, repoPath, surfaceStaging);
180
+ if (!surfaceStaging) {
181
+ // A merge question on a STAGED body (`tasks/backlog/`) is applied even with
182
+ // `surfaceStaging` off (task `wire-merge-questions-into-the-advance-tick`,
183
+ // decision 6): the surfacer asked it, the staging gate did not.
184
+ for (const item of read.resolveLocalTaskStaging({repoPath})) {
185
+ if (
186
+ item.needsAnswers === true &&
187
+ hasMergeQuestion(readSidecarInPlace(repoPath, 'task', item.slug))
188
+ ) {
189
+ blocked.push({namespace: 'task', slug: item.slug});
190
+ }
191
+ }
192
+ }
193
+ const needsAnswers: NeedsAnswersCandidate[] = blocked
177
194
  .map((item) => ({
178
195
  repoPath,
179
196
  namespace: item.namespace,
180
197
  slug: item.slug,
181
198
  sidecar: readSidecarInPlace(repoPath, item.namespace, item.slug),
182
- }));
199
+ }))
200
+ // Held-slug subtraction (task-scoped): a `needsAnswers` TASK whose per-item
201
+ // lock is currently held is EXCLUDED from surface/apply enumeration, unless
202
+ // the lock is kept for a propose PR and the item's merge question is
203
+ // answered `merge` (the answered-merge apply takes that lock over). Spec /
204
+ // observation entries are untouched: the lock ref set here is task-only.
205
+ .filter(
206
+ (item) =>
207
+ !(
208
+ item.namespace === 'task' &&
209
+ heldSlugs.has(item.slug) &&
210
+ !mayTakeOverProposeKept(proposeKept, item.slug, item.sidecar)
211
+ ),
212
+ );
183
213
 
184
214
  return buildLifecyclePools({
185
215
  repoPath,
@@ -189,6 +219,28 @@ export function gatherLifecycleInPlace(input: {
189
219
  });
190
220
  }
191
221
 
222
+ /** Does the sidecar carry a merge question (answered or not)? */
223
+ function hasMergeQuestion(sidecar: SidecarModel | undefined): boolean {
224
+ return sidecar?.entries.some((e) => e.kind === 'merge') === true;
225
+ }
226
+
227
+ /**
228
+ * May a HELD task still be selected? Only when its lock is kept for a propose PR
229
+ * (`proposeKept`) and its merge question is answered `merge`: that apply takes
230
+ * the lock over (task `wire-merge-questions-into-the-advance-tick`, decision 6).
231
+ */
232
+ export function mayTakeOverProposeKept(
233
+ proposeKept: Set<string>,
234
+ slug: string,
235
+ sidecar: SidecarModel | undefined,
236
+ ): boolean {
237
+ return (
238
+ proposeKept.has(slug) &&
239
+ sidecar !== undefined &&
240
+ answeredMergeActionIn(sidecar)?.verb === 'merge'
241
+ );
242
+ }
243
+
192
244
  /**
193
245
  * Read the ACTIVE sidecar for `<namespace>:<slug>` from a BARE hub mirror's
194
246
  * committed `<ref>:work/questions/<type>-<slug>.md` via `git show` (the SAME
@@ -243,6 +295,25 @@ export async function gatherLifecycleMirror(input: {
243
295
  read.resolveMirrorSpecStaging({mirrorPath, ref, env}),
244
296
  ])
245
297
  : [[], []];
298
+ // A merge question on a STAGED body is applied even with `surfaceStaging`
299
+ // off (decision 6), as in {@link gatherLifecycleInPlace}.
300
+ const stagedMergeQuestions: BlockedItem[] = [];
301
+ if (!surfaceStaging) {
302
+ for (const item of await read.resolveMirrorTaskStaging({
303
+ mirrorPath,
304
+ ref,
305
+ env,
306
+ })) {
307
+ if (
308
+ item.needsAnswers === true &&
309
+ hasMergeQuestion(
310
+ await readSidecarMirror(mirrorPath, ref, 'task', item.slug, env),
311
+ )
312
+ ) {
313
+ stagedMergeQuestions.push({namespace: 'task', slug: item.slug});
314
+ }
315
+ }
316
+ }
246
317
 
247
318
  const blocked: BlockedItem[] = [];
248
319
  for (const item of state.ready) {
@@ -282,6 +353,7 @@ export async function gatherLifecycleMirror(input: {
282
353
  blocked.push({namespace: 'spec', slug: spec.slug});
283
354
  }
284
355
  }
356
+ blocked.push(...stagedMergeQuestions);
285
357
 
286
358
  const needsAnswers: NeedsAnswersCandidate[] = await Promise.all(
287
359
  blocked.map(async (item) => ({
@@ -0,0 +1,82 @@
1
+ import {
2
+ isEntryAnswered,
3
+ type SidecarEntry,
4
+ type SidecarModel,
5
+ } from './sidecar.js';
6
+
7
+ /**
8
+ * The PARSE half of the answered merge-question (task
9
+ * `apply-rung-merge-disposition`): the human's plain `merge | hold | drop`
10
+ * answer to a `kind: merge` sidecar entry, read deterministically. Kept apart
11
+ * from the dispatcher (`apply-merge-action.ts`, which re-exports it) so the
12
+ * selection path can read an answered merge without importing the land
13
+ * machinery.
14
+ */
15
+
16
+ /** The three deterministic actions a merge-question answer encodes. */
17
+ export type MergeActionVerb = 'merge' | 'hold' | 'drop';
18
+
19
+ /** A detected, answer-driven merge-action keyed off ONE answered `kind: merge` entry. */
20
+ export interface DetectedMergeAction {
21
+ /** The deterministic verb parsed from the entry's answer text. */
22
+ verb: MergeActionVerb;
23
+ /** The answered `kind: merge` entry the verb came from. */
24
+ entry: SidecarEntry;
25
+ }
26
+
27
+ /**
28
+ * Parse the human's plain free-text answer into the deterministic
29
+ * {@link MergeActionVerb}. The merge-question surfacer renders
30
+ * `merge | hold | drop` in the entry's `default` as a human-readable hint, so
31
+ * the human typically types one of those words verbatim. We accept any text
32
+ * whose first whole word (case-insensitive) is one of `merge` / `hold` / `drop`
33
+ * — this is the same machine-parseable choice shape the surfacer's applied
34
+ * answer q1 documents ("a DETERMINISTIC CHOICE shape … the human picks and the
35
+ * system parses unambiguously").
36
+ *
37
+ * Returns `undefined` on ANYTHING else (an empty answer, a typo, a long
38
+ * narrative without one of the three words at the start) so the caller can
39
+ * route the ambiguity HONESTLY — never default-to-merge on a malformed answer,
40
+ * which would invent a land the human did not authorise.
41
+ */
42
+ export function parseMergeAnswer(text: string): MergeActionVerb | undefined {
43
+ const trimmed = text.trim().toLowerCase();
44
+ if (trimmed === '') return undefined;
45
+ // The first whole alphabetic word; trailing punctuation (a comma, em-dash,
46
+ // period) and any following commentary are tolerated. We deliberately do
47
+ // NOT consume hyphens / apostrophes — the three verbs are plain ASCII
48
+ // words, and over-tolerating typo-shapes would silently invent a verb the
49
+ // human did not pick.
50
+ const match = /^([a-z]+)/.exec(trimmed);
51
+ if (match === null) return undefined;
52
+ const word = match[1];
53
+ if (word === 'merge' || word === 'hold' || word === 'drop') {
54
+ return word;
55
+ }
56
+ return undefined;
57
+ }
58
+
59
+ /**
60
+ * `detectAnsweredMergeAction` (`apply-merge-action.ts`) over an already-parsed sidecar model (the
61
+ * selection path reads sidecars itself, in place and from a mirror ref). The
62
+ * same ordering rules apply.
63
+ */
64
+ export function answeredMergeActionIn(
65
+ model: SidecarModel,
66
+ ): DetectedMergeAction | undefined {
67
+ // (1) Unanswered `kind: merge` follow-up ⇒ re-paused; the apply must NOT fire.
68
+ for (const entry of model.entries) {
69
+ if (entry.kind === 'merge' && !isEntryAnswered(entry)) return undefined;
70
+ }
71
+ // (2) LATEST answered `kind: merge` entry — a fresh follow-up beats a stale one.
72
+ for (let i = model.entries.length - 1; i >= 0; i--) {
73
+ const entry = model.entries[i];
74
+ if (entry.kind !== 'merge') continue;
75
+ if (!isEntryAnswered(entry)) continue;
76
+ const verb = parseMergeAnswer(entry.answer);
77
+ if (verb !== undefined) {
78
+ return {verb, entry};
79
+ }
80
+ }
81
+ return undefined;
82
+ }