dorfl 0.1.2 → 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 (364) hide show
  1. package/dist/advance-drivers.d.ts +1 -1
  2. package/dist/advance-drivers.d.ts.map +1 -1
  3. package/dist/advance-drivers.js +15 -3
  4. package/dist/advance-drivers.js.map +1 -1
  5. package/dist/advance-lifecycle-template.d.ts.map +1 -1
  6. package/dist/advance-lifecycle-template.js +76 -4
  7. package/dist/advance-lifecycle-template.js.map +1 -1
  8. package/dist/advance-treeless-publish.d.ts +23 -0
  9. package/dist/advance-treeless-publish.d.ts.map +1 -1
  10. package/dist/advance-treeless-publish.js +41 -0
  11. package/dist/advance-treeless-publish.js.map +1 -1
  12. package/dist/advance.d.ts +59 -7
  13. package/dist/advance.d.ts.map +1 -1
  14. package/dist/advance.js +389 -82
  15. package/dist/advance.js.map +1 -1
  16. package/dist/advancing-lock.d.ts +30 -2
  17. package/dist/advancing-lock.d.ts.map +1 -1
  18. package/dist/advancing-lock.js +50 -0
  19. package/dist/advancing-lock.js.map +1 -1
  20. package/dist/agent-launch.d.ts +12 -0
  21. package/dist/agent-launch.d.ts.map +1 -1
  22. package/dist/agent-launch.js +22 -12
  23. package/dist/agent-launch.js.map +1 -1
  24. package/dist/agent-stop.d.ts +40 -2
  25. package/dist/agent-stop.d.ts.map +1 -1
  26. package/dist/agent-stop.js +30 -2
  27. package/dist/agent-stop.js.map +1 -1
  28. package/dist/apply-decide.d.ts +17 -3
  29. package/dist/apply-decide.d.ts.map +1 -1
  30. package/dist/apply-decide.js +36 -7
  31. package/dist/apply-decide.js.map +1 -1
  32. package/dist/apply-merge-action.d.ts +17 -5
  33. package/dist/apply-merge-action.d.ts.map +1 -1
  34. package/dist/apply-merge-action.js +43 -8
  35. package/dist/apply-merge-action.js.map +1 -1
  36. package/dist/apply-persist.d.ts +57 -26
  37. package/dist/apply-persist.d.ts.map +1 -1
  38. package/dist/apply-persist.js +166 -37
  39. package/dist/apply-persist.js.map +1 -1
  40. package/dist/apply-stuck-action.d.ts +151 -0
  41. package/dist/apply-stuck-action.d.ts.map +1 -0
  42. package/dist/apply-stuck-action.js +125 -0
  43. package/dist/apply-stuck-action.js.map +1 -0
  44. package/dist/brand.d.ts +12 -1
  45. package/dist/brand.d.ts.map +1 -1
  46. package/dist/brand.js +2 -1
  47. package/dist/brand.js.map +1 -1
  48. package/dist/cli-spinner.d.ts +1 -1
  49. package/dist/cli-spinner.js +1 -1
  50. package/dist/cli.d.ts +10 -0
  51. package/dist/cli.d.ts.map +1 -1
  52. package/dist/cli.js +320 -63
  53. package/dist/cli.js.map +1 -1
  54. package/dist/close-job-template.js +1 -1
  55. package/dist/complete.d.ts +6 -3
  56. package/dist/complete.d.ts.map +1 -1
  57. package/dist/complete.js +91 -37
  58. package/dist/complete.js.map +1 -1
  59. package/dist/config.d.ts +55 -9
  60. package/dist/config.d.ts.map +1 -1
  61. package/dist/config.js +56 -4
  62. package/dist/config.js.map +1 -1
  63. package/dist/continue-branch.d.ts.map +1 -1
  64. package/dist/continue-branch.js +22 -0
  65. package/dist/continue-branch.js.map +1 -1
  66. package/dist/decision-engine.d.ts +38 -10
  67. package/dist/decision-engine.d.ts.map +1 -1
  68. package/dist/decision-engine.js +10 -6
  69. package/dist/decision-engine.js.map +1 -1
  70. package/dist/do-autopick.d.ts.map +1 -1
  71. package/dist/do-autopick.js +11 -1
  72. package/dist/do-autopick.js.map +1 -1
  73. package/dist/do-config.d.ts +1 -1
  74. package/dist/do.d.ts +49 -56
  75. package/dist/do.d.ts.map +1 -1
  76. package/dist/do.js +419 -30
  77. package/dist/do.js.map +1 -1
  78. package/dist/env-config.d.ts.map +1 -1
  79. package/dist/env-config.js +12 -4
  80. package/dist/env-config.js.map +1 -1
  81. package/dist/failure-cause.d.ts +3 -2
  82. package/dist/failure-cause.d.ts.map +1 -1
  83. package/dist/failure-cause.js +28 -1
  84. package/dist/failure-cause.js.map +1 -1
  85. package/dist/format.d.ts +6 -6
  86. package/dist/format.d.ts.map +1 -1
  87. package/dist/format.js +10 -27
  88. package/dist/format.js.map +1 -1
  89. package/dist/frontmatter.d.ts +16 -4
  90. package/dist/frontmatter.d.ts.map +1 -1
  91. package/dist/frontmatter.js +27 -1
  92. package/dist/frontmatter.js.map +1 -1
  93. package/dist/gc.d.ts +65 -6
  94. package/dist/gc.d.ts.map +1 -1
  95. package/dist/gc.js +126 -9
  96. package/dist/gc.js.map +1 -1
  97. package/dist/github.d.ts +14 -0
  98. package/dist/github.d.ts.map +1 -1
  99. package/dist/github.js +73 -0
  100. package/dist/github.js.map +1 -1
  101. package/dist/harness.d.ts +25 -0
  102. package/dist/harness.d.ts.map +1 -1
  103. package/dist/harness.js.map +1 -1
  104. package/dist/index.d.ts +4 -1
  105. package/dist/index.d.ts.map +1 -1
  106. package/dist/index.js +2 -1
  107. package/dist/index.js.map +1 -1
  108. package/dist/install-ci-branch-protection.d.ts +132 -38
  109. package/dist/install-ci-branch-protection.d.ts.map +1 -1
  110. package/dist/install-ci-branch-protection.js +190 -48
  111. package/dist/install-ci-branch-protection.js.map +1 -1
  112. package/dist/install-ci-core.d.ts +21 -1
  113. package/dist/install-ci-core.d.ts.map +1 -1
  114. package/dist/install-ci-core.js +10 -3
  115. package/dist/install-ci-core.js.map +1 -1
  116. package/dist/install-ci-github.d.ts +32 -0
  117. package/dist/install-ci-github.d.ts.map +1 -1
  118. package/dist/install-ci-github.js +73 -0
  119. package/dist/install-ci-github.js.map +1 -1
  120. package/dist/install-skills.d.ts +123 -0
  121. package/dist/install-skills.d.ts.map +1 -0
  122. package/dist/install-skills.js +100 -0
  123. package/dist/install-skills.js.map +1 -0
  124. package/dist/intake-trigger-template.js +1 -1
  125. package/dist/intake.d.ts +17 -4
  126. package/dist/intake.d.ts.map +1 -1
  127. package/dist/intake.js +23 -7
  128. package/dist/intake.js.map +1 -1
  129. package/dist/integration-core.d.ts +10 -7
  130. package/dist/integration-core.d.ts.map +1 -1
  131. package/dist/integration-core.js +174 -60
  132. package/dist/integration-core.js.map +1 -1
  133. package/dist/integrator.d.ts +1 -1
  134. package/dist/integrator.d.ts.map +1 -1
  135. package/dist/integrator.js +19 -3
  136. package/dist/integrator.js.map +1 -1
  137. package/dist/isolation.d.ts +2 -2
  138. package/dist/isolation.d.ts.map +1 -1
  139. package/dist/isolation.js +9 -0
  140. package/dist/isolation.js.map +1 -1
  141. package/dist/item-lock.d.ts +196 -129
  142. package/dist/item-lock.d.ts.map +1 -1
  143. package/dist/item-lock.js +329 -254
  144. package/dist/item-lock.js.map +1 -1
  145. package/dist/ledger-write.d.ts +31 -26
  146. package/dist/ledger-write.d.ts.map +1 -1
  147. package/dist/ledger-write.js +100 -122
  148. package/dist/ledger-write.js.map +1 -1
  149. package/dist/lifecycle-gather.d.ts +17 -0
  150. package/dist/lifecycle-gather.d.ts.map +1 -1
  151. package/dist/lifecycle-gather.js +7 -1
  152. package/dist/lifecycle-gather.js.map +1 -1
  153. package/dist/lifecycle-pools.d.ts +40 -3
  154. package/dist/lifecycle-pools.d.ts.map +1 -1
  155. package/dist/lifecycle-pools.js +26 -6
  156. package/dist/lifecycle-pools.js.map +1 -1
  157. package/dist/merge-question-surfacer.d.ts +15 -1
  158. package/dist/merge-question-surfacer.d.ts.map +1 -1
  159. package/dist/merge-question-surfacer.js +18 -3
  160. package/dist/merge-question-surfacer.js.map +1 -1
  161. package/dist/migrate-stuck-locks.d.ts +129 -0
  162. package/dist/migrate-stuck-locks.d.ts.map +1 -0
  163. package/dist/migrate-stuck-locks.js +355 -0
  164. package/dist/migrate-stuck-locks.js.map +1 -0
  165. package/dist/needs-attention.d.ts +293 -16
  166. package/dist/needs-attention.d.ts.map +1 -1
  167. package/dist/needs-attention.js +532 -57
  168. package/dist/needs-attention.js.map +1 -1
  169. package/dist/orphan-sidecar.d.ts +8 -4
  170. package/dist/orphan-sidecar.d.ts.map +1 -1
  171. package/dist/orphan-sidecar.js +35 -2
  172. package/dist/orphan-sidecar.js.map +1 -1
  173. package/dist/pi-harness.d.ts +16 -0
  174. package/dist/pi-harness.d.ts.map +1 -1
  175. package/dist/pi-harness.js +82 -2
  176. package/dist/pi-harness.js.map +1 -1
  177. package/dist/placement.d.ts +3 -3
  178. package/dist/placement.js +1 -1
  179. package/dist/prd-to-spec.d.ts.map +1 -1
  180. package/dist/prd-to-spec.js +9 -5
  181. package/dist/prd-to-spec.js.map +1 -1
  182. package/dist/protocol/CLAIM-PROTOCOL.md +17 -10
  183. package/dist/protocol/REVIEW-PROTOCOL.md +4 -1
  184. package/dist/protocol/SURFACE-PROTOCOL.md +16 -2
  185. package/dist/protocol/TASKING-PROTOCOL.md +3 -1
  186. package/dist/protocol/WORK-CONTRACT.md +22 -18
  187. package/dist/protocol/task-template.md +1 -1
  188. package/dist/reap-branches.d.ts +12 -9
  189. package/dist/reap-branches.d.ts.map +1 -1
  190. package/dist/reap-branches.js +25 -7
  191. package/dist/reap-branches.js.map +1 -1
  192. package/dist/recover-isolated.d.ts +12 -0
  193. package/dist/recover-isolated.d.ts.map +1 -1
  194. package/dist/recover-isolated.js +5 -0
  195. package/dist/recover-isolated.js.map +1 -1
  196. package/dist/repo-config.d.ts +23 -2
  197. package/dist/repo-config.d.ts.map +1 -1
  198. package/dist/repo-config.js +53 -7
  199. package/dist/repo-config.js.map +1 -1
  200. package/dist/repo-mirror.d.ts.map +1 -1
  201. package/dist/repo-mirror.js +18 -2
  202. package/dist/repo-mirror.js.map +1 -1
  203. package/dist/review-verdict.js +1 -1
  204. package/dist/review-verdict.js.map +1 -1
  205. package/dist/run.d.ts +1 -1
  206. package/dist/run.d.ts.map +1 -1
  207. package/dist/run.js +54 -19
  208. package/dist/run.js.map +1 -1
  209. package/dist/scan.d.ts +17 -8
  210. package/dist/scan.d.ts.map +1 -1
  211. package/dist/scan.js +22 -8
  212. package/dist/scan.js.map +1 -1
  213. package/dist/select-priority.d.ts +2 -2
  214. package/dist/select-priority.js +1 -1
  215. package/dist/select-priority.js.map +1 -1
  216. package/dist/sidecar-apply.js +1 -1
  217. package/dist/sidecar-apply.js.map +1 -1
  218. package/dist/sidecar.d.ts +45 -10
  219. package/dist/sidecar.d.ts.map +1 -1
  220. package/dist/sidecar.js +82 -2
  221. package/dist/sidecar.js.map +1 -1
  222. package/dist/skills/answer-questions/SKILL.md +89 -0
  223. package/dist/skills/capture-signal/SKILL.md +52 -0
  224. package/dist/skills/convert-from-prd-to-spec/SKILL.md +90 -0
  225. package/dist/skills/drive-tasks/SKILL.md +218 -0
  226. package/dist/skills/from-idea/SKILL.md +83 -0
  227. package/dist/skills/merge-prs/SKILL.md +70 -0
  228. package/dist/skills/orchestrate/SKILL.md +101 -0
  229. package/dist/skills/promote/SKILL.md +35 -0
  230. package/dist/skills/review/SKILL.md +16 -0
  231. package/dist/skills/setup/SKILL.md +258 -0
  232. package/dist/skills/setup/protocol/ADR-FORMAT.md +47 -0
  233. package/dist/skills/setup/protocol/CLAIM-PROTOCOL.md +224 -0
  234. package/dist/skills/setup/protocol/REVIEW-PROTOCOL.md +122 -0
  235. package/dist/skills/setup/protocol/SURFACE-PROTOCOL.md +135 -0
  236. package/dist/skills/setup/protocol/TASKING-PROTOCOL.md +124 -0
  237. package/dist/skills/setup/protocol/WORK-CONTRACT.md +280 -0
  238. package/dist/skills/setup/protocol/spec-template.md +71 -0
  239. package/dist/skills/setup/protocol/task-template.md +65 -0
  240. package/dist/skills/surface-questions/SKILL.md +16 -0
  241. package/dist/skills/to-spec/SKILL.md +34 -0
  242. package/dist/skills/to-task/SKILL.md +19 -0
  243. package/dist/skills/triage-observations/SKILL.md +78 -0
  244. package/dist/skills/work/SKILL.md +51 -0
  245. package/dist/slug-namespace.d.ts +5 -5
  246. package/dist/slug-namespace.js +5 -5
  247. package/dist/slug-namespace.js.map +1 -1
  248. package/dist/spec-complete.d.ts.map +1 -1
  249. package/dist/spec-complete.js +2 -3
  250. package/dist/spec-complete.js.map +1 -1
  251. package/dist/start.d.ts.map +1 -1
  252. package/dist/start.js +51 -57
  253. package/dist/start.js.map +1 -1
  254. package/dist/surface-gate.d.ts.map +1 -1
  255. package/dist/surface-gate.js +10 -3
  256. package/dist/surface-gate.js.map +1 -1
  257. package/dist/surface-persist.js +1 -1
  258. package/dist/surface-persist.js.map +1 -1
  259. package/dist/tasker-review-loop.d.ts +4 -3
  260. package/dist/tasker-review-loop.d.ts.map +1 -1
  261. package/dist/tasker-review-loop.js.map +1 -1
  262. package/dist/tasking-lock.d.ts +2 -2
  263. package/dist/tasking-lock.d.ts.map +1 -1
  264. package/dist/tasking-lock.js +53 -32
  265. package/dist/tasking-lock.js.map +1 -1
  266. package/dist/tasking.d.ts +40 -6
  267. package/dist/tasking.d.ts.map +1 -1
  268. package/dist/tasking.js +224 -12
  269. package/dist/tasking.js.map +1 -1
  270. package/dist/triage-persist.d.ts +10 -6
  271. package/dist/triage-persist.d.ts.map +1 -1
  272. package/dist/triage-persist.js +34 -4
  273. package/dist/triage-persist.js.map +1 -1
  274. package/dist/vendor/incur/agents.d.ts +58 -0
  275. package/dist/vendor/incur/agents.d.ts.map +1 -0
  276. package/dist/vendor/incur/agents.js +343 -0
  277. package/dist/vendor/incur/agents.js.map +1 -0
  278. package/dist/verify-workflow-template.js +1 -1
  279. package/dist/watch-session.d.ts +11 -3
  280. package/dist/watch-session.d.ts.map +1 -1
  281. package/dist/watch-session.js +94 -7
  282. package/dist/watch-session.js.map +1 -1
  283. package/dist/work-layout.d.ts +7 -3
  284. package/dist/work-layout.d.ts.map +1 -1
  285. package/dist/work-layout.js +6 -3
  286. package/dist/work-layout.js.map +1 -1
  287. package/dist/workspace.d.ts +1 -1
  288. package/package.json +2 -2
  289. package/src/advance-drivers.ts +16 -3
  290. package/src/advance-lifecycle-template.ts +89 -4
  291. package/src/advance-treeless-publish.ts +45 -0
  292. package/src/advance.ts +467 -87
  293. package/src/advancing-lock.ts +96 -1
  294. package/src/agent-launch.ts +37 -12
  295. package/src/agent-stop.ts +60 -2
  296. package/src/apply-decide.ts +36 -7
  297. package/src/apply-merge-action.ts +45 -7
  298. package/src/apply-persist.ts +228 -54
  299. package/src/apply-stuck-action.ts +260 -0
  300. package/src/brand.ts +14 -2
  301. package/src/cli-spinner.ts +1 -1
  302. package/src/cli.ts +401 -70
  303. package/src/close-job-template.ts +1 -1
  304. package/src/complete.ts +110 -52
  305. package/src/config.ts +107 -13
  306. package/src/continue-branch.ts +22 -0
  307. package/src/decision-engine.ts +55 -17
  308. package/src/do-autopick.ts +12 -1
  309. package/src/do-config.ts +1 -1
  310. package/src/do.ts +511 -38
  311. package/src/env-config.ts +12 -4
  312. package/src/failure-cause.ts +30 -1
  313. package/src/format.ts +10 -30
  314. package/src/frontmatter.ts +35 -5
  315. package/src/gc.ts +172 -9
  316. package/src/github.ts +78 -0
  317. package/src/harness.ts +25 -0
  318. package/src/index.ts +10 -0
  319. package/src/install-ci-branch-protection.ts +282 -57
  320. package/src/install-ci-core.ts +30 -3
  321. package/src/install-ci-github.ts +86 -0
  322. package/src/install-skills.ts +166 -0
  323. package/src/intake-trigger-template.ts +1 -1
  324. package/src/intake.ts +46 -11
  325. package/src/integration-core.ts +199 -65
  326. package/src/integrator.ts +22 -9
  327. package/src/isolation.ts +11 -2
  328. package/src/item-lock.ts +485 -341
  329. package/src/ledger-write.ts +135 -150
  330. package/src/lifecycle-gather.ts +29 -6
  331. package/src/lifecycle-pools.ts +67 -9
  332. package/src/merge-question-surfacer.ts +28 -4
  333. package/src/migrate-stuck-locks.ts +451 -0
  334. package/src/needs-attention.ts +857 -84
  335. package/src/orphan-sidecar.ts +47 -6
  336. package/src/pi-harness.ts +82 -2
  337. package/src/placement.ts +3 -3
  338. package/src/prd-to-spec.ts +13 -5
  339. package/src/reap-branches.ts +38 -14
  340. package/src/recover-isolated.ts +17 -0
  341. package/src/repo-config.ts +55 -6
  342. package/src/repo-mirror.ts +23 -1
  343. package/src/review-verdict.ts +1 -1
  344. package/src/run.ts +64 -18
  345. package/src/scan.ts +22 -8
  346. package/src/select-priority.ts +3 -3
  347. package/src/sidecar-apply.ts +1 -1
  348. package/src/sidecar.ts +138 -11
  349. package/src/slug-namespace.ts +7 -7
  350. package/src/spec-complete.ts +2 -3
  351. package/src/start.ts +53 -59
  352. package/src/surface-gate.ts +10 -3
  353. package/src/surface-persist.ts +1 -1
  354. package/src/tasker-review-loop.ts +4 -3
  355. package/src/tasking-lock.ts +52 -36
  356. package/src/tasking.ts +261 -17
  357. package/src/triage-persist.ts +56 -12
  358. package/src/vendor/incur/LICENSE +21 -0
  359. package/src/vendor/incur/README.md +19 -0
  360. package/src/vendor/incur/agents.ts +392 -0
  361. package/src/verify-workflow-template.ts +1 -1
  362. package/src/watch-session.ts +102 -7
  363. package/src/work-layout.ts +6 -3
  364. package/src/workspace.ts +1 -1
package/src/start.ts CHANGED
@@ -122,7 +122,8 @@ class StartUsageError extends Error {}
122
122
  /**
123
123
  * Raised for a deliberate REFUSAL (exit 1, outcome 'refused') — distinct from a
124
124
  * usage/environment error. Used when the item exists but start declines to act
125
- * (an in-progress item without --resume). The decision is folder-based.
125
+ * (an in-progress item without --resume). The decision is LOCK-based (a held
126
+ * `active` per-item lock), not a `work/in-progress/` folder read.
126
127
  */
127
128
  class StartRefusal extends Error {}
128
129
 
@@ -262,11 +263,11 @@ async function dispatchFolder(
262
263
  if (!inPool) {
263
264
  return folder;
264
265
  }
265
- // In the pool/staging: claimed-ness + stuck-ness are the per-item lock, not the folder.
266
+ // In the pool/staging: claimed-ness is the per-item lock (not the folder).
267
+ // Post-`retire-stuck-lock-state` there is no `stuck` state — a parked item is
268
+ // a `needsAnswers:true` pool item on `main` (rendered by status/scan and
269
+ // drained by the apply rung), NEVER a lock re-key.
266
270
  const lock = await readItemLock({item: `task:${slug}`, cwd, arbiter, env});
267
- if (lock && lock.state === 'stuck') {
268
- return 'needs-attention';
269
- }
270
271
  if (lock && lock.state === 'active') {
271
272
  return 'in-progress';
272
273
  }
@@ -404,7 +405,7 @@ async function continueConflictResult(params: {
404
405
  }
405
406
  const message =
406
407
  `Could not continue '${params.slug}': the kept work branch did not rebase ` +
407
- `cleanly onto ${params.arbiter}/main; routed to work/needs-attention/ ` +
408
+ `cleanly onto ${params.arbiter}/main; marked stuck on its per-item lock ` +
408
409
  '(surfaced by status). Resolve against the latest main, or `requeue --reset` ' +
409
410
  'to discard and start fresh.';
410
411
  return {
@@ -438,8 +439,8 @@ async function continuePushFailureResult(params: {
438
439
  }
439
440
  const message =
440
441
  `Could not continue '${params.slug}': publishing the rebased work branch to ` +
441
- `${params.arbiter} failed terminally (${params.pushFailure}); routed to ` +
442
- 'work/needs-attention/ (surfaced by status), the kept branch left intact on ' +
442
+ `${params.arbiter} failed terminally (${params.pushFailure}); marked stuck ` +
443
+ 'on its per-item lock (surfaced by status), the kept branch left intact on ' +
443
444
  'the arbiter (recoverable). `requeue` to retry once the churn settles.';
444
445
  return {
445
446
  exitCode: 1,
@@ -465,42 +466,17 @@ async function startFromNeedsAttention(params: {
465
466
  }): Promise<StartResult> {
466
467
  const {slug, arbiter, cwd, env, note} = params;
467
468
 
468
- // Surface the recorded reason for the human, read from the STUCK LOCK ENTRY
469
- // (task `cutover-needs-attention-becomes-lock-stuck-recovery-surface`: the lock
470
- // is the sole stuck record no `needs-attention/` folder file). The full reason
471
- // prose + any surfaced questions ride on the lock entry body.
472
- const stuck = await readItemLock({item: `task:${slug}`, cwd, arbiter, env});
473
- if (stuck?.reason) {
474
- note(`'${slug}' is stuck (needs-attention): ${stuck.reason}`);
475
- } else {
476
- note(`'${slug}' is stuck (needs-attention)picking it up.`);
477
- }
478
- if (stuck?.questions && stuck.questions.length > 0) {
479
- note('Surfaced questions:');
480
- for (const q of stuck.questions) {
481
- note(` - ${q}`);
482
- }
483
- }
484
-
485
- // Resume the lock `stuck → active` THROUGH the write seam (a pure lock amend, NO
486
- // `main` write, NO temp checkout — the body already rests in `backlog/` and the
487
- // work stays on the kept `work/<slug>` branch), then onboard onto work/<slug>.
488
- const resolved = await ledgerWrite.applyResolveNeedsAttentionTransition({
489
- cwd,
490
- slug,
491
- arbiter,
492
- env,
493
- note,
494
- });
495
- if (!resolved.moved) {
496
- throw new StartUsageError(
497
- resolved.reasonNotMoved ??
498
- `could not resolve '${slug}' from needs-attention.`,
499
- );
500
- }
469
+ // Post-`retire-stuck-lock-state`: the `stuck` lock state is gone; a parked
470
+ // item is a `needsAnswers:true` pool item on `main` drained by the apply
471
+ // rung (answer the sidecar). This branch fires only when a LEGACY
472
+ // `needs-attention/` folder body still exists on `<arbiter>/main` (no
473
+ // live path lands one). Onboard the human onto the kept work branch — no
474
+ // lock amend, no reason/questions read from the lock (those live on the
475
+ // surfaced sidecar now).
476
+ note(
477
+ `'${slug}' is in the legacy needs-attention/ folder onboarding onto its work branch; answer any question sidecar on main to drain it.`,
478
+ );
501
479
 
502
- // The lock is back to active (the human owns it). Onboard onto the work branch:
503
- // CONTINUE from a kept arbiter work/<slug> when present, else fresh off main.
504
480
  const switched = await switchToWorkBranch({slug, arbiter, cwd, env, note});
505
481
  if (switched.rebaseConflict) {
506
482
  return continueConflictResult({slug, arbiter, cwd, env, note});
@@ -522,10 +498,14 @@ async function startFromNeedsAttention(params: {
522
498
  }
523
499
 
524
500
  /**
525
- * In-progress → already claimed (it is no longer in backlog, so the CAS cannot
526
- * apply). Refuse by default. With --resume the human explicitly asserts
501
+ * In-progress → already claimed (the per-item LOCK is held `active`, so the CAS
502
+ * cannot apply). Refuse by default. With --resume the human explicitly asserts
527
503
  * ownership, so we switch to the work branch WITHOUT claiming. The decision is
528
- * folder-based; we never read the advisory `claimed_by` to guess ownership.
504
+ * LOCK-based: `dispatchFolder` reaches here only when the lock is held `active`
505
+ * (the body rests in the pool/staging, NOT a `work/in-progress/` folder file —
506
+ * the folder cutover retired that path,
507
+ * `finish-in-progress-folder-cutover-remove-legacy-recovery-readers`). We never
508
+ * read the advisory `claimed_by` to guess ownership.
529
509
  */
530
510
  async function startFromInProgress(params: {
531
511
  slug: string;
@@ -538,10 +518,11 @@ async function startFromInProgress(params: {
538
518
  const {slug, arbiter, cwd, env, resume, note} = params;
539
519
 
540
520
  if (!resume) {
541
- // The folder drove the decision (WORK-CONTRACT rule 6: the folder + git
542
- // history are the source of truth). We do NOT name the claimer in the
543
- // message; whoever holds it is read from git history
544
- // (`git log work/in-progress/<slug>.md`).
521
+ // The held LOCK drove the decision (the per-item lock is the source of truth
522
+ // for liveness now the body stays in the pool, no `work/in-progress/` file).
523
+ // We do NOT name the claimer in the message; whoever holds it is read from git
524
+ // history (`git log` over the item's `work/tasks/ready/<slug>.md` + the lock
525
+ // ref history).
545
526
  throw new StartRefusal(
546
527
  `'${slug}' is already in-progress; see \`git log\` for who claimed it; ` +
547
528
  'if this is your own work, re-run with --resume.',
@@ -816,8 +797,9 @@ function surfaceUnmovedStartResult(params: {
816
797
  * TREE-LESS (`#89` mechanism), the SAME-PROFILE sibling of
817
798
  * {@link routeContinuePushFailure}: the rebase was ABORTED, so the REAL continued
818
799
  * `work/<slug>` is already on the arbiter from the prior requeue, untouched
819
- * (after-commit, durable, recoverable). The surface is PURELY the one-file
820
- * `in-progress/ needs-attention/` ledger `.md` move + reason we publish it via
800
+ * (after-commit, durable, recoverable). The surface is PURELY the lock amend to
801
+ * `state: stuck` with the reason (post lock-cutoverno `in-progress/
802
+ * needs-attention/` folder move) — we publish it via
821
803
  * the tree-less surface CAS (the SAME no-checkout primitive `requeue` uses for the
822
804
  * reverse direction), NO temp-branch switch/restore, NO `pushBranch`, NO worktree.
823
805
  */
@@ -831,8 +813,10 @@ async function routeContinueConflict(params: {
831
813
  const {slug, arbiter, cwd, env, note} = params;
832
814
  const reason =
833
815
  `continuing the kept ${arbiter}/${workBranchRef('task', slug)}: rebase onto ` +
834
- `${arbiter}/main conflicted (aborted, never auto-resolved) — resolve ` +
835
- 'against the latest main, or `requeue --reset` to discard and start fresh';
816
+ `${arbiter}/main conflicted (aborted, never auto-resolved) — run ` +
817
+ '`requeue --reconcile` to non-destructively re-sync the mirror and retry ' +
818
+ 'the rebase (keeps the work). Last resort: `requeue --reset` ' +
819
+ 'DESTRUCTIVELY discards the branch and starts fresh.';
836
820
  note(reason);
837
821
  // PROPAGATE the {moved, reasonNotMoved} result to the caller so a `moved:false`
838
822
  // (the surface did NOT reach main — lost the CAS race) is surfaced honestly
@@ -857,8 +841,9 @@ async function routeContinueConflict(params: {
857
841
  * TREE-LESS (`#89` mechanism): the recoverable artifact — the kept `work/<slug>`
858
842
  * — is ALREADY on the arbiter from the prior requeue (our local rebased tip is
859
843
  * what FAILED to push, so it is not the cross-machine truth), and the work is
860
- * already committed. So the surface is PURELY the one-file `in-progress/
861
- * needs-attention/` ledger `.md` move + reason: we publish it via the tree-less
844
+ * already committed. So the surface is PURELY the lock amend to `state: stuck`
845
+ * with the reason (post lock-cutover no `in-progress/ needs-attention/`
846
+ * folder move): we publish it via the tree-less
862
847
  * surface CAS (the SAME no-checkout primitive `requeue` uses for the reverse
863
848
  * direction) — NO temp-branch switch/restore, NO `pushBranch`, NO worktree. A
864
849
  * `requeue` then continues from the kept arbiter tip.
@@ -875,8 +860,10 @@ async function routeContinuePushFailure(params: {
875
860
  const reason =
876
861
  `continuing the kept ${arbiter}/${workBranchRef('task', slug)}: publishing ` +
877
862
  `the rebased work branch to ${arbiter} failed terminally (${pushFailure}) — ` +
878
- 'the kept branch is left intact on the arbiter (recoverable); `requeue` to ' +
879
- 'retry once the churn settles, or `requeue --reset` to discard and start fresh';
863
+ 'the kept branch is left intact on the arbiter (recoverable); `requeue ' +
864
+ '--reconcile` to non-destructively re-sync the mirror and retry once the ' +
865
+ 'churn settles (keeps the work). Last resort: `requeue --reset` ' +
866
+ 'DESTRUCTIVELY discards the branch and starts fresh.';
880
867
  note(reason);
881
868
  // PROPAGATE the {moved, reasonNotMoved} result to the caller so a `moved:false`
882
869
  // (the surface did NOT reach main — lost the CAS race) is surfaced honestly
@@ -936,7 +923,14 @@ async function folderOnArbiterMain(
936
923
  )[] = [
937
924
  'tasks-ready',
938
925
  ...(allowBacklog ? (['tasks-backlog'] as const) : []),
939
- 'in-progress',
926
+ // NOTE: `work/in-progress/` is NOT probed (task
927
+ // `finish-in-progress-folder-cutover-remove-legacy-recovery-readers`): the
928
+ // per-item-lock cutover moved claim OFF writing that folder, so nothing lands a
929
+ // body there. A claimed-and-in-flight item RESTS in the pool/staging on `main`
930
+ // with the held lock as its liveness — `dispatchFolder` re-keys THAT to the
931
+ // `in-progress` dispatch off the LOCK (`state: active`), never a folder read.
932
+ // (`needs-attention/` is likewise the lock `state: stuck`; its retained probe is
933
+ // a separate cutover's concern, left untouched here.)
940
934
  'needs-attention',
941
935
  'done',
942
936
  ];
@@ -252,9 +252,16 @@ export function buildSurfacePrompt(item: string): string {
252
252
  ` "note": "… (optional; your reasoning / findings prose lives HERE, not outside the object)"}`,
253
253
  '```',
254
254
  ``,
255
- `An EMPTY \`questions\` array is a VALID, honest result (no open judgement) —`,
256
- `emit the object with \`"questions": []\` (put WHY in \`note\`); do NOT replace`,
257
- `it with a prose explanation. Absence of the field is NOT valid.`,
255
+ `RULE empty is VALID, absence is NOT. When there is nothing to ask (no open`,
256
+ `judgement to surface), you MUST STILL emit the JSON object, with`,
257
+ `\`"questions": []\` (an empty array); put any explanation of WHY in the`,
258
+ `\`note\` field INSIDE the object. Do NOT replace the object with a prose`,
259
+ `sentence like "no open questions", do NOT skip the emit, and do NOT drop the`,
260
+ `\`questions\` field. The parser (\`parseSurfaceEmit\`) enforces this: an empty`,
261
+ `\`questions\` array is a valid, honest "no open judgement" result; ABSENCE of`,
262
+ `the \`questions\` field is rejected as an unparseable emit — that failure is`,
263
+ `what strands the surface rung. Emitting \`{"questions": [], "note": "…"}\` is`,
264
+ `ALWAYS the safe, honest close for a nothing-to-ask item.`,
258
265
  ``,
259
266
  // The SHARED defensive-JSON contract (the same hardening Gate-2's verdict
260
267
  // prompt carries) — the surface rung hit the IDENTICAL unparseable-emit
@@ -192,7 +192,7 @@ export function persistSurfacedQuestions(
192
192
 
193
193
  // Write the appended/created sidecar.
194
194
  mkdirSync(dirname(sidecarAbs), {recursive: true});
195
- writeFileSync(sidecarAbs, serialiseSidecar(model));
195
+ writeFileSync(sidecarAbs, serialiseSidecar(model, {repoRoot: cwd}));
196
196
 
197
197
  // Stage exactly the two paths and commit them TOGETHER (one atomic commit) —
198
198
  // the sidecar write + the `needsAnswers:true` flip land in the SAME commit, so
@@ -52,7 +52,7 @@ export {parseReviewVerdict as parseTaskReviewVerdict} from './review-verdict.js'
52
52
  * applyEdits(…to disk…) }` — ONE agent LAUNCH per pass, the runner writing the
53
53
  * agent's edits to the candidate task FILES (`work/tasks/backlog/`) between passes, the
54
54
  * next pass's agent re-reading the edited files. Accumulation is via DISK +
55
- * re-launch, NOT one agent retaining context. `prd/review.md` §Shape 2 is internally
55
+ * re-launch, NOT one agent retaining context. `spec/review.md` §Shape 2 is internally
56
56
  * contradictory on this (single-context headline vs "edit the files" operative spec);
57
57
  * this code implements the operative reading. See
58
58
  * `work/findings/review-edit-loop-single-context-is-unbuilt-aspiration-vs-per-pass-disk-impl.md`.
@@ -75,8 +75,9 @@ export {parseReviewVerdict as parseTaskReviewVerdict} from './review-verdict.js'
75
75
  * - **a specific uncertain task** → emit it `needsAnswers: true` with the
76
76
  * questions in its body (created, not agent-buildable until a human answers).
77
77
  * - **the whole decomposition unclear / `taskerLoopMax` exhausted with blockers** →
78
- * route the spec to `work/needs-attention/<slug>.md` with the questions as the
79
- * reason, emitting NO guessed tasks.
78
+ * mark the spec stuck on its per-item lock (post lock-cutover `state: stuck`
79
+ * with the questions as the reason, no `work/needs-attention/` folder write),
80
+ * emitting NO guessed tasks.
80
81
  *
81
82
  * The verdict sink itself (the git transitions for those three outcomes) is the
82
83
  * caller's (`tasking.ts`): this module decides WHICH outcome and prepares the
@@ -1,9 +1,6 @@
1
1
  import {runAsync, type RunResult} from './git.js';
2
- import {
3
- acquireItemLock,
4
- releaseItemLock,
5
- markStuckItemLock,
6
- } from './item-lock.js';
2
+ import {acquireItemLock, releaseItemLock, readItemLock} from './item-lock.js';
3
+ import {surfaceStuckToNeedsAttention} from './needs-attention.js';
7
4
  import {workItemRel} from './work-layout.js';
8
5
 
9
6
  /**
@@ -35,9 +32,10 @@ import {workItemRel} from './work-layout.js';
35
32
  * read-stability backstop). A dry-run takes no lock (it mutates nothing).
36
33
  *
37
34
  * - **Release** ({@link releaseTaskingLock}) DELETES the unified lock ref. It
38
- * moves NO lifecycle file: the durable `prdprd-tasked` success move is owned
39
- * by the integrate band (`tasking.ts`/`integration-core.ts`), and there is no
40
- * `tasking/ → prd/` abort bounce anymore (the body never left `prd/`). When
35
+ * moves NO lifecycle file: the durable `specs/readyspecs/tasked` success move
36
+ * is owned by the integrate band (`tasking.ts`/`integration-core.ts`), and there
37
+ * is no `tasking/ → specs/ready/` abort bounce anymore (the body never left
38
+ * `specs/ready/`). When
41
39
  * `routeToNeedsAttention` is set (the tasker review/edit loop's
42
40
  * decomposition-unclear verdict, or the task-SET acceptance gate's `block`),
43
41
  * release amends the lock `active → stuck` with the reason on the entry INSTEAD
@@ -57,7 +55,7 @@ import {workItemRel} from './work-layout.js';
57
55
  *
58
56
  * This module provides the lock PRIMITIVES only. The orchestrating `do spec:<slug>`
59
57
  * tasking command (`tasking.ts`) acquires, drives the agent's tasking, integrates
60
- * the emitted tasks + the durable `prdprd-tasked` move, and releases. The
58
+ * the emitted tasks + the durable `specs/readyspecs/tasked` move, and releases. The
61
59
  * human path (no contention) may task on `main` directly without the lock.
62
60
  */
63
61
 
@@ -188,9 +186,9 @@ async function runAcquire(
188
186
  // a lock is dead via `release-lock` + `gc --ledger`).
189
187
  // Spec `prd-to-spec-vocabulary-cutover-and-migration-command`: the parent-spec
190
188
  // item identity is `spec:<slug>`, so the unified per-item lock ref is
191
- // `refs/dorfl/lock/spec-<slug>`. HARD CUTOVER: the legacy `prd:<slug>` identity
192
- // is GONE — the resolver no longer treats `prd:` as the spec namespace (a
193
- // `prd:<slug>` arg is a bare literal task slug now).
189
+ // `refs/dorfl/lock/spec-<slug>`. HARD CUTOVER: the legacy ''prd:<slug>'' identity
190
+ // is GONE — the resolver no longer treats ''prd:'' as the spec namespace (a
191
+ // ''prd:<slug>'' arg is a bare literal task slug now).
194
192
  const lock = await acquireItemLock({
195
193
  item: `spec:${slug}`,
196
194
  action: 'task',
@@ -273,9 +271,9 @@ export interface ReleaseTaskingLockResult {
273
271
  /**
274
272
  * Release the tasking lock for `slug`: DELETE the unified `action: task` lock ref
275
273
  * (idempotent — an already-absent ref is a clean `released`). It moves NO lifecycle
276
- * file: the durable `prdprd-tasked` success move is owned by the integrate band,
274
+ * file: the durable `specs/readyspecs/tasked` success move is owned by the integrate band,
277
275
  * and the spec body never left `work/specs/ready/` under the lock, so there is no
278
- * `tasking/ → prd/` restore.
276
+ * `tasking/ → specs/ready/` restore.
279
277
  *
280
278
  * When `routeToNeedsAttention` is set (the tasker decomposition-unclear verdict /
281
279
  * the task-SET acceptance gate `block`), the lock is AMENDED `active → stuck`
@@ -325,33 +323,51 @@ async function runRelease(
325
323
  );
326
324
  }
327
325
 
328
- // DECOMPOSITION-UNCLEAR / TASK-GATE BLOCK: amend the lock `active stuck` with
329
- // the reason on the entry (the tasking needs-attention surface), NOT delete it.
330
- // The spec body stays in `work/specs/ready/`; the stuck lock keeps it out of the
331
- // taskable pool until a human resolves it (`release-lock`/`resume`).
326
+ // DECOMPOSITION-UNCLEAR / TASK-GATE BLOCK: surface the spec as a `stuck`-kind
327
+ // sidecar + `needsAnswers:true` on the spec body in ONE commit on
328
+ // `<arbiter>/main`, THEN release the tasking lock (PR-2b, spec
329
+ // `surface-stuck-as-questions-and-retire-stuck-lock-state`, decision #1). The
330
+ // spec body stays in `work/specs/ready/` (or `work/specs/proposed/` — the D1
331
+ // probe finds it); the surfaced question keeps the spec out of the taskable
332
+ // pool via `needsAnswers:true` (eligibility `false`) until a human answers.
332
333
  if (options.routeToNeedsAttention !== undefined) {
333
- // MIGRATE: EMIT `spec:<slug>` (keyed to the `spec-<slug>` lock entry).
334
- const stuck = await markStuckItemLock({
335
- item: `spec:${slug}`,
336
- reason: options.routeToNeedsAttention.reason,
337
- cwd,
338
- arbiter,
339
- env,
340
- });
341
- if (stuck.outcome === 'transitioned' || stuck.outcome === 'wrong-state') {
342
- // `wrong-state` (already stuck) is a tolerated idempotent re-surface.
343
- const message = `Routed the tasking of '${slug}' to needs-attention (per-item lock marked stuck).`;
334
+ const item = `spec:${slug}`;
335
+ // Detect a genuinely NOT-HELD lock BEFORE the surface (so a bounce of an
336
+ // unlocked spec still returns the exit-2 `lost` contract). The surface itself
337
+ // is idempotent, but without this pre-check a not-held spec would surface
338
+ // silently on main with no lock to release — losing the caller-visible
339
+ // "nothing to route" signal.
340
+ const held = await readItemLock({item, cwd, arbiter, env});
341
+ if (held === undefined) {
342
+ const message = `'${slug}' is not locked for tasking — nothing to route to needs-attention.`;
344
343
  note(message);
345
- return {exitCode: 0, outcome: 'released', message};
344
+ return {exitCode: 2, outcome: 'lost', message};
346
345
  }
347
- if (stuck.outcome === 'not-held') {
348
- const message = `'${slug}' is not locked for tasking — nothing to mark stuck.`;
346
+ try {
347
+ const surfaced = await surfaceStuckToNeedsAttention({
348
+ cwd,
349
+ slug,
350
+ item,
351
+ reason: options.routeToNeedsAttention.reason,
352
+ arbiter,
353
+ env,
354
+ note,
355
+ });
356
+ if (surfaced.surfaced || surfaced.bodyAbsent) {
357
+ const message = surfaced.surfaced
358
+ ? `Routed the tasking of '${slug}' to needs-attention (surfaced on ${arbiter}/main; tasking lock released).`
359
+ : `Routed the tasking of '${slug}' to needs-attention (no spec body on ${arbiter}/main — clean no-op surface; tasking lock released).`;
360
+ note(message);
361
+ return {exitCode: 0, outcome: 'released', message};
362
+ }
363
+ const message = `could not surface '${item}' on ${arbiter}/main (${surfaced.reasonNotSurfaced ?? 'unknown'}).`;
349
364
  note(message);
350
- return {exitCode: 2, outcome: 'lost', message};
365
+ return {exitCode: 1, outcome: 'usage-error', message};
366
+ } catch (err) {
367
+ const message = `could not surface '${item}' on ${arbiter}/main (${err instanceof Error ? err.message : String(err)}).`;
368
+ note(message);
369
+ return {exitCode: 1, outcome: 'usage-error', message};
351
370
  }
352
- const message = `could not mark the per-item lock for '${slug}' stuck (${stuck.outcome}: ${stuck.message}).`;
353
- note(message);
354
- return {exitCode: 1, outcome: 'usage-error', message};
355
371
  }
356
372
 
357
373
  // NORMAL release: delete the lock ref (idempotent).