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/run.ts CHANGED
@@ -31,7 +31,11 @@ import {
31
31
  parseStopSentinel,
32
32
  isWorkBranchDiffEmpty,
33
33
  emptyDiffStopReason,
34
+ emptyDiffDisposeEnvelope,
35
+ type AgentStopKind,
34
36
  } from './agent-stop.js';
37
+ import {surfaceStuckToNeedsAttention} from './needs-attention.js';
38
+ import {resolveItemPathByIdentity} from './item-path.js';
35
39
  import {
36
40
  classifyFailureCause,
37
41
  failureCauseLabel,
@@ -190,6 +194,7 @@ export type ItemStatus =
190
194
  | 'surface-unmoved' // the tree-less surface to needs-attention did NOT land on the arbiter (lost the CAS race / no arbiter) — the item is STILL in-progress on the arbiter; retry/resolve
191
195
  | 'agent-failed' // the agent ran but produced bad/empty output (the conservative generic), OR the cause is unknown
192
196
  | 'transient-infra' // a harness-surfaced model/connection outage (post-retry) or a git/provider outage — RETRY the same work (FAILURE-CAUSE axis)
197
+ | 'needs-reauth' // a credential expired / was revoked (OAuth refresh) — retry cannot help; a human must RE-AUTH (FAILURE-CAUSE axis)
193
198
  | 'config-error' // a thrown CORE wiring/config error (e.g. review on, no reviewGate) — fix the WIRING, not the task (FAILURE-CAUSE axis)
194
199
  | 'agent-stopped'; // the agent DELIBERATELY stopped (task drifted) OR produced no change — gate + Gate-2 skipped
195
200
 
@@ -457,6 +462,7 @@ export async function runOnce(options: RunOnceOptions): Promise<RunOnceResult> {
457
462
  // are the same agent/run-failure routed to needs-attention, just labelled
458
463
  // by cause).
459
464
  i.status === 'transient-infra' ||
465
+ i.status === 'needs-reauth' ||
460
466
  i.status === 'config-error' ||
461
467
  // The surface to needs-attention did NOT land (lost the CAS race); the item
462
468
  // is still in-progress on the arbiter — a genuine FAILURE (not a clean
@@ -709,16 +715,19 @@ async function runOneItem(
709
715
  // `work/<slug>` tip == the arbiter tip (the kept branch, unchanged,
710
716
  // after-commit). The DURABLE artifact is that branch on the arbiter + the
711
717
  // main surface (ADR §14: the job worktree is a disposable cache; recovery
712
- // flows through the branch + folder-native surfaces, NOT by editing the
713
- // worktree). So the surface is purely the one-file `in-progress/
714
- // needs-attention/` ledger move + reason — no branch push, no worktree
715
- // mutation. Because the branch is provably on the arbiter, the §4 reap
718
+ // flows through the branch + lock surfaces, NOT by editing the
719
+ // worktree). So the surface is purely the lock amend to `state: stuck` +
720
+ // reason (post lock-cutover — no `in-progress/ needs-attention/` folder
721
+ // move, no branch push, no worktree
722
+ // mutation). Because the branch is provably on the arbiter, the §4 reap
716
723
  // predicate still HOLDS and this worktree is reaped — more §14-aligned.
717
724
  if (tree.continueRebaseConflict) {
718
725
  const reason =
719
726
  `continuing the kept ${tree.branch}: rebase onto the latest main ` +
720
- 'conflicted (aborted, never auto-resolved) — resolve against the latest ' +
721
- 'main, or `requeue --reset` to discard and start fresh';
727
+ 'conflicted (aborted, never auto-resolved) — run `requeue --reconcile` ' +
728
+ 'to non-destructively re-sync the mirror and retry the rebase (keeps ' +
729
+ 'the work). Last resort: `requeue --reset` DESTRUCTIVELY discards the ' +
730
+ 'branch and starts fresh.';
722
731
  updateJobRecord(tree.dir, {state: 'needs-attention', reason});
723
732
  const surfaced = await ledgerWrite.applyTreelessNeedsAttentionTransition({
724
733
  cwd: tree.dir,
@@ -747,9 +756,10 @@ async function runOneItem(
747
756
  const reason =
748
757
  `continuing the kept ${tree.branch}: publishing the rebased work branch ` +
749
758
  `to the arbiter failed terminally (${tree.continuePushFailure}) — the ` +
750
- 'kept branch is left intact on the arbiter (recoverable); `requeue` to ' +
751
- 'retry once the churn settles, or `requeue --reset` to discard and start ' +
752
- 'fresh';
759
+ 'kept branch is left intact on the arbiter (recoverable); ' +
760
+ '`requeue --reconcile` to non-destructively re-sync the mirror and ' +
761
+ 'retry once the churn settles (keeps the work). Last resort: ' +
762
+ '`requeue --reset` DESTRUCTIVELY discards the branch and starts fresh.';
753
763
  updateJobRecord(tree.dir, {state: 'needs-attention', reason});
754
764
  const surfaced = await ledgerWrite.applyTreelessNeedsAttentionTransition({
755
765
  cwd: tree.dir,
@@ -862,18 +872,25 @@ async function runOneItem(
862
872
  // the arbiter) and SKIPS the gate + Gate-2 (the whole `performIntegration`
863
873
  // band) — a clean STOP is NOT "a build that changed nothing".
864
874
  const sentinel = parseStopSentinel(agent.output);
865
- const stopReason =
875
+ const stopReason: {kind: AgentStopKind; reason: string} | undefined =
866
876
  sentinel !== undefined
867
- ? sentinel.reason
877
+ ? {kind: 'sentinel', reason: sentinel.reason}
868
878
  : (await isWorkBranchDiffEmpty({
869
879
  cwd: tree.dir,
870
880
  arbiter: tree.arbiterRemote,
871
881
  env: gitEnv,
872
882
  }))
873
- ? emptyDiffStopReason(slug)
883
+ ? {kind: 'empty-diff', reason: emptyDiffStopReason(slug)}
874
884
  : undefined;
875
885
  if (stopReason !== undefined) {
876
- return await saveAgentStop(base, tree, slug, stopReason, ctx);
886
+ return await saveAgentStop(
887
+ base,
888
+ tree,
889
+ slug,
890
+ stopReason.reason,
891
+ stopReason.kind,
892
+ ctx,
893
+ );
877
894
  }
878
895
 
879
896
  // 5–7 (CONVERGED). The whole gate → review → done-move → commit → rebase →
@@ -1138,9 +1155,10 @@ function runAgent(
1138
1155
  * This mirrors `do.ts`'s `saveAgentFailure`: route the failure through the SAME
1139
1156
  * ledger write seam's needs-attention transition the gate-fail / integrate-
1140
1157
  * conflict bounces use — which (with the arbiter) saves the agent's work as a wip
1141
- * commit, `git mv`s the item to needs-attention/ with the failure detail recorded
1142
- * as the reason, surfaces the move-only commit on the arbiter's `main`
1143
- * (OBSERVABLE), AND pushes the `work/<slug>` branch (RECOVERABLE the durable
1158
+ * commit, then post lock-cutover amends the item's per-item lock to
1159
+ * `state: stuck` with the failure detail as the reason on the lock entry
1160
+ * (OBSERVABLE; the `needs-attention/` folder move is retired), AND pushes the
1161
+ * `work/<slug>` branch (RECOVERABLE — the durable
1144
1162
  * artifact a requeue-continue lands the next agent on; continue-detection reads
1145
1163
  * <arbiter>/work/<slug> ahead of main). The push is best-effort — an unreachable
1146
1164
  * arbiter leaves the retained worktree + the local commits standing (the genuinely
@@ -1194,8 +1212,9 @@ function failureCauseToItemStatus(cause: FailureCause): ItemStatus {
1194
1212
 
1195
1213
  /**
1196
1214
  * Build the HONEST {@link ItemResult} for a CONTINUE-site surface that did NOT
1197
- * land on the arbiter (`{moved: false}`). The tree-less `in-progress/
1198
- * needs-attention/` move lost the CAS race against a busy arbiter (its
1215
+ * land on the arbiter (`{moved: false}`). The tree-less stuck-lock amend (post
1216
+ * lock-cutover `state: stuck` + reason, in place of the retired `in-progress/ →
1217
+ * needs-attention/` move) lost the CAS race against a busy arbiter (its
1199
1218
  * contention-retry cap exhausted) or had no arbiter to publish to, so the item is
1200
1219
  * STILL in-progress on the arbiter — reporting a clean `needs-attention` would
1201
1220
  * mislead (it claims the surface landed, when it did not). The DISTINCT
@@ -1236,9 +1255,36 @@ async function saveAgentStop(
1236
1255
  tree: IsolatedTree,
1237
1256
  slug: string,
1238
1257
  reason: string,
1258
+ kind: AgentStopKind,
1239
1259
  ctx: OneItemContext,
1240
1260
  ): Promise<ItemResult> {
1241
1261
  updateJobRecord(tree.dir, {state: 'needs-attention', reason});
1262
+ // EMPTY-DIFF branch (spec resolved decision #2, task
1263
+ // `empty-diff-bounce-surfaces-dispose-defaulted-question`): surface a sidecar
1264
+ // carrying an engine-authored DISPOSE-DEFAULTED envelope question on
1265
+ // `<arbiter>/main` (via the PR-1 tree-less surface primitive) and release the
1266
+ // lock — NOT the general lock-stuck bounce path. "Nothing to do" is a
1267
+ // non-deterministic LLM judgement, so a blind requeue would infinite-loop;
1268
+ // the surfaced sidecar + `needsAnswers:true` on `main` parks the item as a
1269
+ // human-visible question a `dispose` answer settles to `tasks/cancelled/`.
1270
+ if (kind === 'empty-diff') {
1271
+ const item = `task:${slug}`;
1272
+ const itemPath = resolveItemPathByIdentity(tree.dir, item);
1273
+ if (itemPath !== undefined) {
1274
+ await surfaceStuckToNeedsAttention({
1275
+ cwd: tree.dir,
1276
+ slug,
1277
+ item,
1278
+ itemPath,
1279
+ reason,
1280
+ envelope: emptyDiffDisposeEnvelope({item, reason}),
1281
+ arbiter: tree.arbiterRemote,
1282
+ env: ctx.gitEnv ?? ctx.env,
1283
+ });
1284
+ return {...base, status: 'agent-stopped', detail: reason};
1285
+ }
1286
+ // Fallthrough (body missing) — fall through to the lock-stuck bounce below.
1287
+ }
1242
1288
  await ledgerWrite.applyNeedsAttentionTransition({
1243
1289
  cwd: tree.dir,
1244
1290
  slug,
package/src/scan.ts CHANGED
@@ -280,7 +280,7 @@ export function scoreSpecs(
280
280
  * re-enumeration — a pure shape map over the already-gated pools.
281
281
  *
282
282
  * The pool items' `namespace` is the wider {@link SelectedNamespace}. SURFACE by
283
- * construction only carries `'task'`/`'prd'` (an observation with no sidecar is a
283
+ * construction only carries `'task'`/`'spec'` (an observation with no sidecar is a
284
284
  * `triage` candidate, never `surface`), so it narrows + drops any non-task/spec
285
285
  * defensively. APPLY additionally admits `'observation'` (an answered observation
286
286
  * sidecar → apply), and it MUST be kept so the matrix `jq` emits its
@@ -543,13 +543,22 @@ export function scanRepoPaths(
543
543
  repoPaths: string[],
544
544
  config: Config,
545
545
  /**
546
- * The HELD-SLUG set to SUBTRACT from each repo's pool (`tasks/ready/`) (spec
547
- * `ledger-status-per-item-lock-refs` US #15). This is a WORKING-TREE, OFFLINE
548
- * scan (it has no arbiter handle to fetch the lock refs from that is the
549
- * registry `scan`'s job), so the held set is supplied by the in-place CALLER
550
- * (which knows its arbiter) and DEFAULTS to empty: with the body still moving to
551
- * `in-progress/` on claim the subtraction is redundant-but-harmless, so omitting
552
- * it preserves the offline read while keeping the seam in place for task #9.
546
+ * The HELD-SLUG set to SUBTRACT from each repo's pool (`tasks/ready/`) AND from
547
+ * the in-place lifecycle triage/surface/apply pools (spec
548
+ * `ledger-status-per-item-lock-refs` US #15; task
549
+ * `in-place-scan-subtracts-held-locked-slugs-from-propose-matrix`). Passed
550
+ * BOTH to {@link scoreItems} AND to {@link gatherLifecycleInPlace}, so a slug
551
+ * whose per-item lock is currently held (state `active`/`stuck`) never leaks
552
+ * into the propose matrix via EITHER the eligible-task pool OR a lifecycle
553
+ * leg — CI would otherwise enumerate a `task:<slug>` surface/apply leg that
554
+ * always loses the claim CAS.
555
+ *
556
+ * This is a WORKING-TREE, OFFLINE scan (it has no arbiter handle to fetch the
557
+ * lock refs from — that is the registry `scan`'s job), so the held set is
558
+ * supplied by the in-place CALLER (which knows its arbiter, e.g.
559
+ * `resolveCwdSection` reads it fail-CLOSED via `heldTaskSlugsStrict`) and
560
+ * DEFAULTS to empty. An empty default preserves the offline read while keeping
561
+ * the seam in place.
553
562
  */
554
563
  heldSlugs: Set<string> = new Set(),
555
564
  /**
@@ -590,6 +599,11 @@ export function scanRepoPaths(
590
599
  surfaceBlockers: resolved.surfaceBlockers,
591
600
  surfaceStaging: resolved.surfaceStaging,
592
601
  }),
602
+ // Held-slug subtraction on the LIFECYCLE side (task
603
+ // `in-place-scan-subtracts-held-locked-slugs-from-propose-matrix`):
604
+ // symmetric to the `scoreItems` subtraction above, so a held task never
605
+ // leaks into a surface/apply leg either.
606
+ heldSlugs,
593
607
  }),
594
608
  );
595
609
  // The one-slug-one-folder LINT over THIS working tree's `work/` ledger.
@@ -49,7 +49,7 @@ import {
49
49
 
50
50
  /**
51
51
  * Which namespace a selected item names (mirrors the slug-namespace split). The
52
- * `do` selection only ever produces `task`/`prd`; the `advance` selection ALSO
52
+ * `do` selection only ever produces `task`/`spec`; the `advance` selection ALSO
53
53
  * produces `observation` (the lifecycle triage pool, task
54
54
  * `advance-autopick-lifecycle-pools`), so a selected lifecycle item carries which
55
55
  * rung the driver dispatches to. The widening is BACKWARD-COMPATIBLE: `do` never
@@ -76,7 +76,7 @@ export interface SelectedItem {
76
76
  /**
77
77
  * A lifecycle-pool selected item (task `advance-autopick-lifecycle-pools`). It is
78
78
  * a {@link SelectedItem} — the same shape — carrying the lifecycle namespace
79
- * (`observation` for triage; `task`/`prd` for a `needsAnswers`-blocked item the
79
+ * (`observation` for triage; `task`/`spec` for a `needsAnswers`-blocked item the
80
80
  * tick will surface/apply). A distinct alias names the lifecycle pools at the
81
81
  * call sites WITHOUT a structural difference (the discriminator is `namespace`).
82
82
  */
@@ -235,7 +235,7 @@ export function selectPrioritised(
235
235
 
236
236
  // The per-pool item lists, keyed by the orderable pool name. NOTE the
237
237
  // vocabulary bridge: `build` = the eligible-TASK pool (namespace `task`),
238
- // `task` = the taskable-spec pool (namespace `prd`) — the action names, not the
238
+ // `task` = the taskable-spec pool (namespace `spec`) — the action names, not the
239
239
  // item namespaces (task `advance-selection-order-config`).
240
240
  const byPool: Record<SelectionPool, SelectedItem[]> = {
241
241
  build: buildItems,
@@ -174,7 +174,7 @@ export function applyAtomic(options: ApplyAtomicOptions): ApplyAtomicResult {
174
174
  writeFileSync(itemAbs, setNeedsAnswersMarker(itemBody, true));
175
175
  }
176
176
  mkdirSync(dirname(sidecarAbs), {recursive: true});
177
- writeFileSync(sidecarAbs, serialiseSidecar(sidecar));
177
+ writeFileSync(sidecarAbs, serialiseSidecar(sidecar, {repoRoot: cwd}));
178
178
  touched.push(sidecarPath);
179
179
  }
180
180
 
package/src/sidecar.ts CHANGED
@@ -1,5 +1,7 @@
1
+ import {existsSync} from 'node:fs';
2
+ import {join} from 'node:path';
1
3
  import {parseSlugArg, type SlugNamespace} from './slug-namespace.js';
2
- import {workItemRel} from './work-layout.js';
4
+ import {workItemRel, type WorkFolderKey} from './work-layout.js';
3
5
 
4
6
  /**
5
7
  * The **question/answer SIDECAR contract** (spec `advance-loop`, task
@@ -72,7 +74,7 @@ import {workItemRel} from './work-layout.js';
72
74
  * The item-types a sidecar can key onto (the slug-namespace + obs).
73
75
  *
74
76
  * HARD CUTOVER (spec `prd-to-spec-vocabulary-cutover-and-migration-command`,
75
- * contract step): the legacy `'prd'` type member is GONE — the parent-spec type
77
+ * contract step): the legacy ''prd'' type member is GONE — the parent-spec type
76
78
  * is `'spec'` only. A `spec:<slug>` identity keys onto the `spec-<slug>`
77
79
  * lock/sidecar entry. The on-disk `prd-<slug>.md` sidecar FILE (dorfl's
78
80
  * not-yet-converted data) is still probed by {@link sidecarPathCandidates} as a
@@ -88,13 +90,19 @@ export type SidecarType = 'spec' | 'task' | 'observation';
88
90
  * sniffing the shape of another field. Absent ⇒ the existing binary content
89
91
  * entry (every pre-`kind` sidecar parses + renders byte-identically).
90
92
  *
91
- * INTERIM PRIMITIVE REMOVE when question sidecars move to KIND-BASED
92
- * SUBFOLDERS (`work/questions/merge/`, ), where the folder ENCODES the kind
93
- * and this per-entry field is redundant. See the observation
94
- * `questions-folder-rename-and-kind-axis-prefix-vs-subfolder-2026-06-21` + idea
95
- * `folder-taxonomy-and-prd-edit-handshake`. Built deliberately as a single
96
- * typed field read in exactly ONE place (the apply dispatch, a later task) so
97
- * the folder-cutover can DELETE it in one move.
93
+ * A TYPED FIELD is the PERMANENT home for the kind axis (not an interim step
94
+ * toward kind subfolders). Encoding kind in the PATH either a filename prefix
95
+ * (`merge-<type>-<slug>.md`) or a subfolder (`work/questions/merge/…`) was
96
+ * deliberately REJECTED: kind is a MUTABLE axis (one item can carry a `spec`
97
+ * question pre-build, then a `merge` question at land time), but `sidecarPathFor`
98
+ * is a pure function of item IDENTITY alone and does not know the kind, so a
99
+ * path that encodes kind would make identity-only lookups SILENTLY miss a
100
+ * sidecar filed under a different kind. So kind lives INSIDE the file (this
101
+ * field), the path stays identity-keyed, and per-kind queues are RENDERED by the
102
+ * tool (`status`/`scan`) rather than by the directory tree. See the observation
103
+ * `questions-folder-rename-and-kind-axis-prefix-vs-subfolder-2026-06-21` (its
104
+ * round-2 conclusion + the recorded answers) and `SURFACE-PROTOCOL.md`. The
105
+ * field is read in exactly ONE place (the apply dispatch).
98
106
  *
99
107
  * A mistyped/unknown `kind=` token parses to `undefined` (silent-on-malformed,
100
108
  * mirroring the retired `disposition` precedent), never a throw, never a
@@ -274,7 +282,7 @@ export function sidecarPathFor(identity: string): string {
274
282
  * `spec`-typed identity ALSO probes the legacy `prd-<slug>.md` as a fallback, and
275
283
  * a reader takes the FIRST candidate that exists. This is a FILE-PATH DATA alias
276
284
  * the migration command removes (it converts `prd-<slug>.md → spec-<slug>.md` on
277
- * disk); it is NOT the `SlugNamespace`/`SidecarType` `'prd'` type member. Every
285
+ * disk); it is NOT the `SlugNamespace`/`SidecarType` ''prd'' type member. Every
278
286
  * non-`spec` type has a single candidate (its canonical path), unchanged.
279
287
  */
280
288
  export function sidecarPathCandidates(identity: string): string[] {
@@ -291,6 +299,77 @@ export function sidecarPathCandidates(identity: string): string[] {
291
299
  /** The fixed answer marker the human types prose under. */
292
300
  const ANSWER_MARKER = '**Your answer** (write below this line):';
293
301
 
302
+ /**
303
+ * The lifecycle folders a given item type may currently reside in — the
304
+ * search set the {@link serialiseSidecar} human-visible link line scans to
305
+ * locate the item at write-time. Deliberately INCLUSIVE of the terminal
306
+ * folders (`done`, `cancelled`, `prds-dropped`) so a sidecar still-being-
307
+ * serialised for a finished item still emits a clickable link. Kept LOCAL to
308
+ * this module (rather than reused from `advance.ts`) because this set is the
309
+ * "where might the item CURRENTLY be on disk?" question, which is broader
310
+ * than advance's rung-classifier reach.
311
+ *
312
+ * DECISION (sidecar-visible-item-link): the task set is the four DURABLE
313
+ * folders `tasks-ready` / `done` / `cancelled` / `tasks-backlog` and
314
+ * deliberately EXCLUDES `in-progress` / `needs-attention`. Those two are NOT
315
+ * durable folders — they are retired transient lock-ref state (ADR
316
+ * `needs-attention-folder-cutover-followup-nits`, see the `TASK_LIFECYCLE_FOLDERS`
317
+ * JSDoc in `work-layout.ts`): a stuck task's BODY rests in `tasks/ready/`
318
+ * while the lock carries `state: stuck`, so no task body ever lives under
319
+ * `work/in-progress/` or `work/needs-attention/` for this scan to find. An
320
+ * earlier WIP of this task listed both; narrowing to the durable set matches
321
+ * the current on-disk reality and avoids scanning phantom folders.
322
+ * ALTERNATIVE considered: reuse `TASK_LIFECYCLE_FOLDERS` directly — rejected
323
+ * because it still carries the legacy `in-progress` entry and omits the
324
+ * `cancelled` / `tasks-backlog` folders a sidecar link may need to reach.
325
+ */
326
+ const LINK_LIFECYCLE_FOLDERS: Record<SidecarType, readonly WorkFolderKey[]> = {
327
+ task: ['tasks-ready', 'done', 'cancelled', 'tasks-backlog'],
328
+ spec: ['specs-ready', 'specs-tasked', 'specs-proposed', 'specs-dropped'],
329
+ observation: ['observations'],
330
+ };
331
+
332
+ /**
333
+ * Look up the item's CURRENT on-disk repo-relative path by scanning the
334
+ * lifecycle folders its type may reside in, in a documented precedence order.
335
+ * Returns `undefined` when the item is not found in ANY folder — the
336
+ * serialiser's "harmless fallback" (omit the link) branch.
337
+ *
338
+ * PURE lookup: no throws, no side effects. Every call is a fresh scan — the
339
+ * link SELF-HEALS on the next `serialise` after a `git mv` between folders
340
+ * (identity-keyed sidecar; NO lock-step move).
341
+ */
342
+ function findItemRelPath(
343
+ repoRoot: string,
344
+ type: SidecarType,
345
+ slug: string,
346
+ ): string | undefined {
347
+ for (const folder of LINK_LIFECYCLE_FOLDERS[type]) {
348
+ const rel = workItemRel(folder, `${slug}.md`);
349
+ if (existsSync(join(repoRoot, rel))) {
350
+ return rel;
351
+ }
352
+ }
353
+ return undefined;
354
+ }
355
+
356
+ /**
357
+ * Render the human-visible Markdown link line, from the sidecar's fixed path
358
+ * (`work/questions/<type>-<slug>.md`) to the item's current `work/<folder>/
359
+ * <slug>.md`. Since both live under `work/`, the sidecar-relative link is
360
+ * `../<folder-name>/<slug>.md` — the `..` climbs out of `questions/` and the
361
+ * `<folder-name>` (which may contain a `/`, e.g. `tasks/ready`) drops back in.
362
+ */
363
+ function renderItemLinkLine(item: string, itemRel: string): string {
364
+ // itemRel is `work/<folder-name>/<slug>.md`; strip the leading `work/` and
365
+ // prefix with `../` so the link is relative to `work/questions/<file>.md`.
366
+ const prefix = 'work/';
367
+ const target = itemRel.startsWith(prefix)
368
+ ? `../${itemRel.slice(prefix.length)}`
369
+ : itemRel;
370
+ return `Item: [\`${item}\`](${target})`;
371
+ }
372
+
294
373
  // --- Parse ----------------------------------------------------------------
295
374
 
296
375
  /** Pull the next monotonic id given the highest existing id number. */
@@ -582,7 +661,10 @@ function blockquote(value: string): string[] {
582
661
  * `parseSidecar(serialiseSidecar(m))` recovers an equal MODEL; re-serialising
583
662
  * canonicalises the text.
584
663
  */
585
- export function serialiseSidecar(model: SidecarModel): string {
664
+ export function serialiseSidecar(
665
+ model: SidecarModel,
666
+ options: SerialiseSidecarOptions = {},
667
+ ): string {
586
668
  const out: string[] = [];
587
669
  const identityParts = [
588
670
  `item=${model.item}`,
@@ -592,6 +674,21 @@ export function serialiseSidecar(model: SidecarModel): string {
592
674
  ];
593
675
  out.push(`<!-- dorfl-sidecar: ${identityParts.join(' ')} -->`);
594
676
 
677
+ // Human-visible Markdown link line — placed AFTER the identity comment and
678
+ // BEFORE the first `## ` heading, i.e. in the parser's ignored preamble
679
+ // region. Regenerated on every serialise from the item's CURRENT on-disk
680
+ // location; NEVER round-tripped through the parsed model (the link line is
681
+ // write-only cosmetic output, per the source observation). If the item is
682
+ // not resolvable on disk, we simply OMIT the line (harmless fallback) — a
683
+ // broken link would be more confusing than no link.
684
+ if (options.repoRoot !== undefined) {
685
+ const itemRel = findItemRelPath(options.repoRoot, model.type, model.slug);
686
+ if (itemRel !== undefined) {
687
+ out.push('');
688
+ out.push(renderItemLinkLine(model.item, itemRel));
689
+ }
690
+ }
691
+
595
692
  model.entries.forEach((entry) => {
596
693
  out.push('');
597
694
  const heading = entry.id.replace(/^q/, 'Q');
@@ -634,6 +731,36 @@ export function serialiseSidecar(model: SidecarModel): string {
634
731
  return out.join('\n');
635
732
  }
636
733
 
734
+ /**
735
+ * Options for {@link serialiseSidecar}. The optional `repoRoot` opts the
736
+ * caller into emitting the human-visible Markdown link line at the top of
737
+ * the sidecar (pointing at the item's current `work/<folder>/<slug>.md`).
738
+ * Callers that don't have a repo root (e.g. pure format tests) simply omit
739
+ * it and the serialiser emits no link — the parse is unaffected either way.
740
+ *
741
+ * DECISION (sidecar-visible-item-link): `repoRoot` is OPTIONAL and defaults
742
+ * to NO link, rather than being a required argument threaded through every
743
+ * caller. Only the two WRITING call sites (`applyAtomic` in
744
+ * `sidecar-apply.ts`, `persistSurfacedQuestions` in `surface-persist.ts`)
745
+ * pass `cwd`; any other current/future caller of `serialiseSidecar` silently
746
+ * emits no link line. This keeps the many pure-format / round-trip tests
747
+ * trivial (they need no on-disk repo) and keeps the link a write-only
748
+ * cosmetic concern of the persist paths. TRADE-OFF: a new writing caller must
749
+ * remember to pass `repoRoot` to get the link; ALTERNATIVE considered — make
750
+ * it required — rejected because it would force every format-only caller and
751
+ * test to fabricate a repo root purely to reach the same no-link output.
752
+ */
753
+ export interface SerialiseSidecarOptions {
754
+ /**
755
+ * Absolute or relative path to the repository root, so the serialiser can
756
+ * scan `work/<lifecycle-folder>/<slug>.md` for the item and render a
757
+ * clickable relative link line. When omitted (or when the item cannot be
758
+ * located), the link line is omitted — a harmless fallback, never a
759
+ * broken link.
760
+ */
761
+ repoRoot?: string;
762
+ }
763
+
637
764
  // --- Append ---------------------------------------------------------------
638
765
 
639
766
  /** A new question to append (no id — the appender mints the next monotonic id). */
@@ -43,8 +43,8 @@ import {ledgerRead, type LedgerReadStrategy} from './ledger-read.js';
43
43
  * {@link resolveAdvanceArg}).
44
44
  *
45
45
  * HARD CUTOVER (spec `prd-to-spec-vocabulary-cutover-and-migration-command`,
46
- * contract step): the legacy `'prd'` member is GONE — the parent-spec namespace
47
- * is `'spec'` only. A `prd:<slug>` arg no longer parses to a namespace (it falls
46
+ * contract step): the legacy ''prd'' member is GONE — the parent-spec namespace
47
+ * is `'spec'` only. A ''prd:<slug>'' arg no longer parses to a namespace (it falls
48
48
  * through to a bare literal slug), and a `work/prd-<slug>` branch ref no longer
49
49
  * parses. No back-compat alias (the clean-break stance).
50
50
  */
@@ -102,7 +102,7 @@ export function workBranchRef(
102
102
  * anchors the optional producer prefix BEFORE the type alternation, so
103
103
  * `work/intake-task-foo` resolves to
104
104
  * `{producer:'intake', namespace:'task', slug:'foo'}` (the `slug` never
105
- * swallows the `intake-`/`task-` prefixes). The old `slice`/`brief`/`prd` types
105
+ * swallows the `intake-`/`task-` prefixes). The old ''slice''/''brief''/''prd'' types
106
106
  * are NOT in the alternation, so a pre-rename `work/prd-foo` ref returns
107
107
  * `undefined` (the clean-break stance: no migration-window alias).
108
108
  */
@@ -112,7 +112,7 @@ export function parseWorkBranchRef(
112
112
  | {producer?: BranchProducer; namespace: SlugNamespace; slug: string}
113
113
  | undefined {
114
114
  // HARD CUTOVER (spec `prd-to-spec-vocabulary-cutover-and-migration-command`,
115
- // contract step): the type alternation is `task|spec` ONLY — the legacy `prd`
115
+ // contract step): the type alternation is `task|spec` ONLY — the legacy ''prd''
116
116
  // token is GONE, so a pre-rename `work/prd-<slug>` ref returns `undefined` (no
117
117
  // migration-window alias; the clean-break stance).
118
118
  const match = /^work\/(?:(intake)-)?(task|spec)-(.+)$/.exec(branch);
@@ -155,7 +155,7 @@ const TASK_PREFIX = 'task:';
155
155
  /**
156
156
  * The parent-spec namespace prefix: `spec:<slug>` → `{explicit: 'spec'}`. HARD
157
157
  * CUTOVER (spec `prd-to-spec-vocabulary-cutover-and-migration-command`): the
158
- * legacy `prd:` prefix is GONE — a `prd:<slug>` arg falls through to a bare
158
+ * legacy ''prd:'' prefix is GONE — a ''prd:<slug>'' arg falls through to a bare
159
159
  * literal slug (no namespace), the clean-break stance.
160
160
  */
161
161
  const SPEC_PREFIX = 'spec:';
@@ -184,8 +184,8 @@ export class SlugResolutionError extends Error {
184
184
  * slug. PURE string work, no existence check: `task:foo` → explicit task,
185
185
  * `spec:foo` → explicit spec, `foo` → bare (`explicit: undefined`). The prefix
186
186
  * match is case-sensitive and exact (`task:`/`spec:`); a slug like `tasked` is
187
- * NOT a prefix and stays bare. HARD CUTOVER: the legacy `prd:` prefix is not a
188
- * namespace prefix anymore — `prd:foo` stays a bare literal slug.
187
+ * NOT a prefix and stays bare. HARD CUTOVER: the legacy ''prd:'' prefix is not a
188
+ * namespace prefix anymore — ''prd:foo'' stays a bare literal slug.
189
189
  */
190
190
  export function parseSlugArg(arg: string): ParsedSlugArg {
191
191
  if (arg.startsWith(TASK_PREFIX)) {
@@ -27,9 +27,8 @@ import {
27
27
  * claim ledger (`ledger-read.ts` resolves claim-STATE, a different concern). It
28
28
  * reuses {@link parseFrontmatter} (the `spec:` field) rather than hand-rolling a YAML
29
29
  * parse, and scans the task lifecycle folders directly: `work/tasks/ready/`,
30
- * `work/in-progress/`, `work/needs-attention/`, and `work/done/`. A task that has
31
- * NOT yet landed in `work/done/` (still in backlog / in-progress / needs-attention)
32
- * means the spec is not yet complete.
30
+ * `work/in-progress/`, and `work/done/`. A task that has NOT yet landed in
31
+ * `work/done/` (still in backlog / in-progress) means the spec is not yet complete.
33
32
  */
34
33
 
35
34
  /** The task lifecycle folders a `spec:<slug>` task can reside in. */