@cleocode/core 2026.9.7 → 2026.9.9

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 (565) hide show
  1. package/dist/audit/reconstruct.d.ts +13 -43
  2. package/dist/audit/reconstruct.d.ts.map +1 -1
  3. package/dist/audit/reconstruct.js +295 -290
  4. package/dist/audit/reconstruct.js.map +1 -1
  5. package/dist/bootstrap.d.ts.map +1 -1
  6. package/dist/bootstrap.js +8 -2
  7. package/dist/bootstrap.js.map +1 -1
  8. package/dist/dispatch/mutate-projection.d.ts.map +1 -1
  9. package/dist/dispatch/mutate-projection.js +12 -0
  10. package/dist/dispatch/mutate-projection.js.map +1 -1
  11. package/dist/dispatch/mvi-projection.d.ts +29 -6
  12. package/dist/dispatch/mvi-projection.d.ts.map +1 -1
  13. package/dist/dispatch/mvi-projection.js +109 -139
  14. package/dist/dispatch/mvi-projection.js.map +1 -1
  15. package/dist/docs/docs-read-model.d.ts +3 -2
  16. package/dist/docs/docs-read-model.d.ts.map +1 -1
  17. package/dist/docs/docs-read-model.js +4 -3
  18. package/dist/docs/docs-read-model.js.map +1 -1
  19. package/dist/docs/export-document.js +5100 -2297
  20. package/dist/docs/export-document.js.map +3 -3
  21. package/dist/docs/projection.d.ts +67 -0
  22. package/dist/docs/projection.d.ts.map +1 -0
  23. package/dist/docs/projection.js +370 -0
  24. package/dist/docs/projection.js.map +1 -0
  25. package/dist/doctor/acceptance-drift.d.ts +6 -2
  26. package/dist/doctor/acceptance-drift.d.ts.map +1 -1
  27. package/dist/doctor/acceptance-drift.js +43 -5
  28. package/dist/doctor/acceptance-drift.js.map +1 -1
  29. package/dist/doctor/knowledge-summary.d.ts +41 -0
  30. package/dist/doctor/knowledge-summary.d.ts.map +1 -0
  31. package/dist/doctor/knowledge-summary.js +81 -0
  32. package/dist/doctor/knowledge-summary.js.map +1 -0
  33. package/dist/doctor/knowledge.d.ts +192 -0
  34. package/dist/doctor/knowledge.d.ts.map +1 -0
  35. package/dist/doctor/knowledge.js +1864 -0
  36. package/dist/doctor/knowledge.js.map +1 -0
  37. package/dist/git/work-tree.d.ts +64 -0
  38. package/dist/git/work-tree.d.ts.map +1 -0
  39. package/dist/git/work-tree.js +111 -0
  40. package/dist/git/work-tree.js.map +1 -0
  41. package/dist/hooks/handlers/task-hooks.d.ts +8 -5
  42. package/dist/hooks/handlers/task-hooks.d.ts.map +1 -1
  43. package/dist/hooks/handlers/task-hooks.js +15 -60
  44. package/dist/hooks/handlers/task-hooks.js.map +1 -1
  45. package/dist/identity/cleo-identity.d.ts +13 -0
  46. package/dist/identity/cleo-identity.d.ts.map +1 -1
  47. package/dist/identity/cleo-identity.js +20 -6
  48. package/dist/identity/cleo-identity.js.map +1 -1
  49. package/dist/injection.d.ts.map +1 -1
  50. package/dist/injection.js +29 -13
  51. package/dist/injection.js.map +1 -1
  52. package/dist/internal.d.ts +2 -0
  53. package/dist/internal.d.ts.map +1 -1
  54. package/dist/internal.js +2 -0
  55. package/dist/internal.js.map +1 -1
  56. package/dist/lifecycle/verification/satisfies-validator.d.ts.map +1 -1
  57. package/dist/lifecycle/verification/satisfies-validator.js +124 -120
  58. package/dist/lifecycle/verification/satisfies-validator.js.map +1 -1
  59. package/dist/llm/plugin-facade.js +4018 -1726
  60. package/dist/llm/plugin-facade.js.map +3 -3
  61. package/dist/memory/brain-backfill.d.ts +27 -12
  62. package/dist/memory/brain-backfill.d.ts.map +1 -1
  63. package/dist/memory/brain-backfill.js +325 -134
  64. package/dist/memory/brain-backfill.js.map +1 -1
  65. package/dist/memory/brain-doctor.d.ts +21 -0
  66. package/dist/memory/brain-doctor.d.ts.map +1 -1
  67. package/dist/memory/brain-doctor.js +111 -53
  68. package/dist/memory/brain-doctor.js.map +1 -1
  69. package/dist/memory/brain-links.d.ts.map +1 -1
  70. package/dist/memory/brain-links.js +31 -4
  71. package/dist/memory/brain-links.js.map +1 -1
  72. package/dist/memory/brain-reasoning.d.ts.map +1 -1
  73. package/dist/memory/brain-reasoning.js +28 -2
  74. package/dist/memory/brain-reasoning.js.map +1 -1
  75. package/dist/memory/brain-reconciler.d.ts +14 -19
  76. package/dist/memory/brain-reconciler.d.ts.map +1 -1
  77. package/dist/memory/brain-reconciler.js +70 -97
  78. package/dist/memory/brain-reconciler.js.map +1 -1
  79. package/dist/memory/brain-search.d.ts +2 -0
  80. package/dist/memory/brain-search.d.ts.map +1 -1
  81. package/dist/memory/brain-search.js +59 -29
  82. package/dist/memory/brain-search.js.map +1 -1
  83. package/dist/memory/brain-similarity.d.ts.map +1 -1
  84. package/dist/memory/brain-similarity.js +5 -4
  85. package/dist/memory/brain-similarity.js.map +1 -1
  86. package/dist/memory/brain-stub-prune.d.ts +31 -9
  87. package/dist/memory/brain-stub-prune.d.ts.map +1 -1
  88. package/dist/memory/brain-stub-prune.js +65 -18
  89. package/dist/memory/brain-stub-prune.js.map +1 -1
  90. package/dist/memory/brain-writer-handlers.d.ts +10 -1
  91. package/dist/memory/brain-writer-handlers.d.ts.map +1 -1
  92. package/dist/memory/brain-writer-handlers.js +23 -3
  93. package/dist/memory/brain-writer-handlers.js.map +1 -1
  94. package/dist/memory/brain-writer-thread.d.ts +31 -1
  95. package/dist/memory/brain-writer-thread.d.ts.map +1 -1
  96. package/dist/memory/brain-writer-thread.js +166 -24
  97. package/dist/memory/brain-writer-thread.js.map +1 -1
  98. package/dist/memory/brain-writer-worker.js +8 -1
  99. package/dist/memory/brain-writer-worker.js.map +1 -1
  100. package/dist/memory/decision-cross-link.d.ts +39 -0
  101. package/dist/memory/decision-cross-link.d.ts.map +1 -1
  102. package/dist/memory/decision-cross-link.js +316 -0
  103. package/dist/memory/decision-cross-link.js.map +1 -1
  104. package/dist/memory/decisions.d.ts +4 -1
  105. package/dist/memory/decisions.d.ts.map +1 -1
  106. package/dist/memory/decisions.js +7 -24
  107. package/dist/memory/decisions.js.map +1 -1
  108. package/dist/memory/dispatch-trace.d.ts +12 -5
  109. package/dist/memory/dispatch-trace.d.ts.map +1 -1
  110. package/dist/memory/dispatch-trace.js +17 -7
  111. package/dist/memory/dispatch-trace.js.map +1 -1
  112. package/dist/memory/eligibility.d.ts +51 -0
  113. package/dist/memory/eligibility.d.ts.map +1 -0
  114. package/dist/memory/eligibility.js +77 -0
  115. package/dist/memory/eligibility.js.map +1 -0
  116. package/dist/memory/engine-compat.d.ts +2 -0
  117. package/dist/memory/engine-compat.d.ts.map +1 -1
  118. package/dist/memory/engine-compat.js +10 -0
  119. package/dist/memory/engine-compat.js.map +1 -1
  120. package/dist/memory/extraction-gate.d.ts +21 -7
  121. package/dist/memory/extraction-gate.d.ts.map +1 -1
  122. package/dist/memory/extraction-gate.js +178 -216
  123. package/dist/memory/extraction-gate.js.map +1 -1
  124. package/dist/memory/graph-auto-populate.d.ts +38 -5
  125. package/dist/memory/graph-auto-populate.d.ts.map +1 -1
  126. package/dist/memory/graph-auto-populate.js +182 -60
  127. package/dist/memory/graph-auto-populate.js.map +1 -1
  128. package/dist/memory/graph-memory-bridge.d.ts +14 -1
  129. package/dist/memory/graph-memory-bridge.d.ts.map +1 -1
  130. package/dist/memory/graph-memory-bridge.js +66 -28
  131. package/dist/memory/graph-memory-bridge.js.map +1 -1
  132. package/dist/memory/index.d.ts +1 -19
  133. package/dist/memory/index.d.ts.map +1 -1
  134. package/dist/memory/index.js +1 -50
  135. package/dist/memory/index.js.map +1 -1
  136. package/dist/memory/learnings.d.ts.map +1 -1
  137. package/dist/memory/learnings.js +2 -23
  138. package/dist/memory/learnings.js.map +1 -1
  139. package/dist/memory/manifest-filter.d.ts +21 -0
  140. package/dist/memory/manifest-filter.d.ts.map +1 -0
  141. package/dist/memory/manifest-filter.js +51 -0
  142. package/dist/memory/manifest-filter.js.map +1 -0
  143. package/dist/memory/manifest-ingestion.d.ts +28 -30
  144. package/dist/memory/manifest-ingestion.d.ts.map +1 -1
  145. package/dist/memory/manifest-ingestion.js +104 -235
  146. package/dist/memory/manifest-ingestion.js.map +1 -1
  147. package/dist/memory/observer-reflector.d.ts +2 -0
  148. package/dist/memory/observer-reflector.d.ts.map +1 -1
  149. package/dist/memory/observer-reflector.js +6 -49
  150. package/dist/memory/observer-reflector.js.map +1 -1
  151. package/dist/memory/patterns.d.ts +10 -0
  152. package/dist/memory/patterns.d.ts.map +1 -1
  153. package/dist/memory/patterns.js +25 -33
  154. package/dist/memory/patterns.js.map +1 -1
  155. package/dist/memory/pipeline-manifest-sqlite.d.ts +72 -9
  156. package/dist/memory/pipeline-manifest-sqlite.d.ts.map +1 -1
  157. package/dist/memory/pipeline-manifest-sqlite.js +508 -412
  158. package/dist/memory/pipeline-manifest-sqlite.js.map +1 -1
  159. package/dist/memory/retrieval/build-retrieval-bundle.d.ts.map +1 -1
  160. package/dist/memory/retrieval/build-retrieval-bundle.js +16 -15
  161. package/dist/memory/retrieval/build-retrieval-bundle.js.map +1 -1
  162. package/dist/memory/retrieval/fetch.d.ts +7 -0
  163. package/dist/memory/retrieval/fetch.d.ts.map +1 -1
  164. package/dist/memory/retrieval/fetch.js +14 -11
  165. package/dist/memory/retrieval/fetch.js.map +1 -1
  166. package/dist/memory/retrieval/observe.d.ts +7 -1
  167. package/dist/memory/retrieval/observe.d.ts.map +1 -1
  168. package/dist/memory/retrieval/observe.js +58 -4
  169. package/dist/memory/retrieval/observe.js.map +1 -1
  170. package/dist/memory/retrieval/search.d.ts +14 -1
  171. package/dist/memory/retrieval/search.d.ts.map +1 -1
  172. package/dist/memory/retrieval/search.js +133 -69
  173. package/dist/memory/retrieval/search.js.map +1 -1
  174. package/dist/memory/retrieval/telemetry.d.ts +31 -0
  175. package/dist/memory/retrieval/telemetry.d.ts.map +1 -0
  176. package/dist/memory/retrieval/telemetry.js +53 -0
  177. package/dist/memory/retrieval/telemetry.js.map +1 -0
  178. package/dist/nexus/analyze-orchestrator.d.ts +41 -9
  179. package/dist/nexus/analyze-orchestrator.d.ts.map +1 -1
  180. package/dist/nexus/analyze-orchestrator.js +257 -58
  181. package/dist/nexus/analyze-orchestrator.js.map +1 -1
  182. package/dist/nexus/api-contracts.d.ts +2 -6
  183. package/dist/nexus/api-contracts.d.ts.map +1 -1
  184. package/dist/nexus/api-contracts.js +11 -2
  185. package/dist/nexus/api-contracts.js.map +1 -1
  186. package/dist/nexus/context.d.ts.map +1 -1
  187. package/dist/nexus/context.js +14 -37
  188. package/dist/nexus/context.js.map +1 -1
  189. package/dist/nexus/diff.d.ts.map +1 -1
  190. package/dist/nexus/diff.js +2 -1
  191. package/dist/nexus/diff.js.map +1 -1
  192. package/dist/nexus/identity.d.ts +29 -3
  193. package/dist/nexus/identity.d.ts.map +1 -1
  194. package/dist/nexus/identity.js +63 -12
  195. package/dist/nexus/identity.js.map +1 -1
  196. package/dist/nexus/impact.d.ts +21 -19
  197. package/dist/nexus/impact.d.ts.map +1 -1
  198. package/dist/nexus/impact.js +96 -192
  199. package/dist/nexus/impact.js.map +1 -1
  200. package/dist/nexus/knowledge.d.ts +80 -0
  201. package/dist/nexus/knowledge.d.ts.map +1 -0
  202. package/dist/nexus/knowledge.js +786 -0
  203. package/dist/nexus/knowledge.js.map +1 -0
  204. package/dist/nexus/living-brain.d.ts +8 -5
  205. package/dist/nexus/living-brain.d.ts.map +1 -1
  206. package/dist/nexus/living-brain.js +252 -188
  207. package/dist/nexus/living-brain.js.map +1 -1
  208. package/dist/nexus/registry.d.ts +11 -2
  209. package/dist/nexus/registry.d.ts.map +1 -1
  210. package/dist/nexus/registry.js +144 -128
  211. package/dist/nexus/registry.js.map +1 -1
  212. package/dist/nexus/source-roots.d.ts +16 -0
  213. package/dist/nexus/source-roots.d.ts.map +1 -0
  214. package/dist/nexus/source-roots.js +174 -0
  215. package/dist/nexus/source-roots.js.map +1 -0
  216. package/dist/nexus/task-evidence.d.ts +22 -0
  217. package/dist/nexus/task-evidence.d.ts.map +1 -0
  218. package/dist/nexus/task-evidence.js +210 -0
  219. package/dist/nexus/task-evidence.js.map +1 -0
  220. package/dist/nexus/tasks-bridge.d.ts +14 -7
  221. package/dist/nexus/tasks-bridge.d.ts.map +1 -1
  222. package/dist/nexus/tasks-bridge.js +81 -40
  223. package/dist/nexus/tasks-bridge.js.map +1 -1
  224. package/dist/orchestrate/lifecycle-ops.d.ts.map +1 -1
  225. package/dist/orchestrate/lifecycle-ops.js +170 -120
  226. package/dist/orchestrate/lifecycle-ops.js.map +1 -1
  227. package/dist/orchestrate/query-ops.d.ts +6 -7
  228. package/dist/orchestrate/query-ops.d.ts.map +1 -1
  229. package/dist/orchestrate/query-ops.js +509 -532
  230. package/dist/orchestrate/query-ops.js.map +1 -1
  231. package/dist/orchestration/bootstrap.d.ts +15 -1
  232. package/dist/orchestration/bootstrap.d.ts.map +1 -1
  233. package/dist/orchestration/bootstrap.js +21 -10
  234. package/dist/orchestration/bootstrap.js.map +1 -1
  235. package/dist/orchestration/index.d.ts +35 -3
  236. package/dist/orchestration/index.d.ts.map +1 -1
  237. package/dist/orchestration/index.js +64 -35
  238. package/dist/orchestration/index.js.map +1 -1
  239. package/dist/orchestration/lead-rollup.d.ts +24 -1
  240. package/dist/orchestration/lead-rollup.d.ts.map +1 -1
  241. package/dist/orchestration/lead-rollup.js +31 -11
  242. package/dist/orchestration/lead-rollup.js.map +1 -1
  243. package/dist/orchestration/spawn-prompt.d.ts.map +1 -1
  244. package/dist/orchestration/spawn-prompt.js +16 -41
  245. package/dist/orchestration/spawn-prompt.js.map +1 -1
  246. package/dist/orchestration/unblock.d.ts +15 -1
  247. package/dist/orchestration/unblock.d.ts.map +1 -1
  248. package/dist/orchestration/unblock.js +23 -12
  249. package/dist/orchestration/unblock.js.map +1 -1
  250. package/dist/orchestration/validate-spawn.d.ts.map +1 -1
  251. package/dist/orchestration/validate-spawn.js +3 -3
  252. package/dist/orchestration/validate-spawn.js.map +1 -1
  253. package/dist/orchestration/waves.d.ts +29 -13
  254. package/dist/orchestration/waves.d.ts.map +1 -1
  255. package/dist/orchestration/waves.js +43 -55
  256. package/dist/orchestration/waves.js.map +1 -1
  257. package/dist/paths.d.ts +17 -163
  258. package/dist/paths.d.ts.map +1 -1
  259. package/dist/paths.js +145 -496
  260. package/dist/paths.js.map +1 -1
  261. package/dist/project-info.d.ts +2 -11
  262. package/dist/project-info.d.ts.map +1 -1
  263. package/dist/project-info.js +3 -31
  264. package/dist/project-info.js.map +1 -1
  265. package/dist/project-scope.d.ts +239 -0
  266. package/dist/project-scope.d.ts.map +1 -0
  267. package/dist/project-scope.js +473 -0
  268. package/dist/project-scope.js.map +1 -0
  269. package/dist/release/pr-evidence.d.ts +16 -3
  270. package/dist/release/pr-evidence.d.ts.map +1 -1
  271. package/dist/release/pr-evidence.js +53 -7
  272. package/dist/release/pr-evidence.js.map +1 -1
  273. package/dist/render/nexus/graph/index.d.ts.map +1 -1
  274. package/dist/render/nexus/graph/index.js +4 -2
  275. package/dist/render/nexus/graph/index.js.map +1 -1
  276. package/dist/render/orchestration/audit-reconstruct.d.ts +11 -0
  277. package/dist/render/orchestration/audit-reconstruct.d.ts.map +1 -1
  278. package/dist/render/orchestration/audit-reconstruct.js +21 -6
  279. package/dist/render/orchestration/audit-reconstruct.js.map +1 -1
  280. package/dist/render/tasks/show.js +2 -1
  281. package/dist/render/tasks/show.js.map +1 -1
  282. package/dist/resources/spawn-wrapper.d.ts +82 -14
  283. package/dist/resources/spawn-wrapper.d.ts.map +1 -1
  284. package/dist/resources/spawn-wrapper.js +585 -27
  285. package/dist/resources/spawn-wrapper.js.map +1 -1
  286. package/dist/selfimprove/fix-gen.js +4026 -1734
  287. package/dist/selfimprove/fix-gen.js.map +3 -3
  288. package/dist/sequence/index.d.ts.map +1 -1
  289. package/dist/sequence/index.js +76 -68
  290. package/dist/sequence/index.js.map +1 -1
  291. package/dist/session/canon-lint.d.ts +24 -3
  292. package/dist/session/canon-lint.d.ts.map +1 -1
  293. package/dist/session/canon-lint.js +72 -31
  294. package/dist/session/canon-lint.js.map +1 -1
  295. package/dist/session/engine-ops.d.ts +1 -4
  296. package/dist/session/engine-ops.d.ts.map +1 -1
  297. package/dist/session/engine-ops.js +6 -2
  298. package/dist/session/engine-ops.js.map +1 -1
  299. package/dist/sessions/briefing.d.ts +11 -1
  300. package/dist/sessions/briefing.d.ts.map +1 -1
  301. package/dist/sessions/briefing.js +76 -8
  302. package/dist/sessions/briefing.js.map +1 -1
  303. package/dist/sessions/handoff.d.ts +3 -1
  304. package/dist/sessions/handoff.d.ts.map +1 -1
  305. package/dist/sessions/handoff.js +2 -0
  306. package/dist/sessions/handoff.js.map +1 -1
  307. package/dist/shutdown-deadline.d.ts +24 -33
  308. package/dist/shutdown-deadline.d.ts.map +1 -1
  309. package/dist/shutdown-deadline.js +71 -14
  310. package/dist/shutdown-deadline.js.map +1 -1
  311. package/dist/shutdown.d.ts +21 -10
  312. package/dist/shutdown.d.ts.map +1 -1
  313. package/dist/shutdown.js +91 -35
  314. package/dist/shutdown.js.map +1 -1
  315. package/dist/skills/index.d.ts +1 -1
  316. package/dist/skills/index.d.ts.map +1 -1
  317. package/dist/skills/index.js +1 -1
  318. package/dist/skills/index.js.map +1 -1
  319. package/dist/skills/orchestrator/validator.d.ts +87 -20
  320. package/dist/skills/orchestrator/validator.d.ts.map +1 -1
  321. package/dist/skills/orchestrator/validator.js +330 -241
  322. package/dist/skills/orchestrator/validator.js.map +1 -1
  323. package/dist/store/agent-resolver.d.ts.map +1 -1
  324. package/dist/store/agent-resolver.js +16 -9
  325. package/dist/store/agent-resolver.js.map +1 -1
  326. package/dist/store/attachment-store.d.ts +4 -1
  327. package/dist/store/attachment-store.d.ts.map +1 -1
  328. package/dist/store/attachment-store.js +20 -3
  329. package/dist/store/attachment-store.js.map +1 -1
  330. package/dist/store/background-jobs.d.ts +426 -105
  331. package/dist/store/background-jobs.d.ts.map +1 -1
  332. package/dist/store/background-jobs.js +1208 -252
  333. package/dist/store/background-jobs.js.map +1 -1
  334. package/dist/store/background-ops.d.ts +145 -8
  335. package/dist/store/background-ops.d.ts.map +1 -1
  336. package/dist/store/background-ops.js +459 -15
  337. package/dist/store/background-ops.js.map +1 -1
  338. package/dist/store/backup-inspect.d.ts +48 -4
  339. package/dist/store/backup-inspect.d.ts.map +1 -1
  340. package/dist/store/backup-inspect.js +384 -4
  341. package/dist/store/backup-inspect.js.map +1 -1
  342. package/dist/store/converters.d.ts.map +1 -1
  343. package/dist/store/converters.js +19 -2
  344. package/dist/store/converters.js.map +1 -1
  345. package/dist/store/data-accessor.d.ts +9 -1
  346. package/dist/store/data-accessor.d.ts.map +1 -1
  347. package/dist/store/data-accessor.js +22 -5
  348. package/dist/store/data-accessor.js.map +1 -1
  349. package/dist/store/data-safety-central.d.ts.map +1 -1
  350. package/dist/store/data-safety-central.js +272 -232
  351. package/dist/store/data-safety-central.js.map +1 -1
  352. package/dist/store/db-helpers.d.ts +19 -3
  353. package/dist/store/db-helpers.d.ts.map +1 -1
  354. package/dist/store/db-helpers.js +42 -58
  355. package/dist/store/db-helpers.js.map +1 -1
  356. package/dist/store/dual-scope-db.d.ts +63 -4
  357. package/dist/store/dual-scope-db.d.ts.map +1 -1
  358. package/dist/store/dual-scope-db.js +166 -19
  359. package/dist/store/dual-scope-db.js.map +1 -1
  360. package/dist/store/exodus/archive.d.ts +21 -7
  361. package/dist/store/exodus/archive.d.ts.map +1 -1
  362. package/dist/store/exodus/archive.js +60 -18
  363. package/dist/store/exodus/archive.js.map +1 -1
  364. package/dist/store/exodus/migrate.d.ts.map +1 -1
  365. package/dist/store/exodus/migrate.js +27 -23
  366. package/dist/store/exodus/migrate.js.map +1 -1
  367. package/dist/store/exodus/on-open.d.ts +16 -27
  368. package/dist/store/exodus/on-open.d.ts.map +1 -1
  369. package/dist/store/exodus/on-open.js +121 -169
  370. package/dist/store/exodus/on-open.js.map +1 -1
  371. package/dist/store/exodus/recovery.d.ts +90 -0
  372. package/dist/store/exodus/recovery.d.ts.map +1 -0
  373. package/dist/store/exodus/recovery.js +434 -0
  374. package/dist/store/exodus/recovery.js.map +1 -0
  375. package/dist/store/exodus/types.d.ts +15 -0
  376. package/dist/store/exodus/types.d.ts.map +1 -1
  377. package/dist/store/memory-accessor.d.ts +52 -3
  378. package/dist/store/memory-accessor.d.ts.map +1 -1
  379. package/dist/store/memory-accessor.js +122 -7
  380. package/dist/store/memory-accessor.js.map +1 -1
  381. package/dist/store/nexus-sqlite.d.ts +24 -6
  382. package/dist/store/nexus-sqlite.d.ts.map +1 -1
  383. package/dist/store/nexus-sqlite.js +39 -20
  384. package/dist/store/nexus-sqlite.js.map +1 -1
  385. package/dist/store/ports/domain-binding.d.ts.map +1 -1
  386. package/dist/store/ports/domain-binding.js +27 -5
  387. package/dist/store/ports/domain-binding.js.map +1 -1
  388. package/dist/store/schema/background-jobs.d.ts +181 -2
  389. package/dist/store/schema/background-jobs.d.ts.map +1 -1
  390. package/dist/store/schema/background-jobs.js +27 -3
  391. package/dist/store/schema/background-jobs.js.map +1 -1
  392. package/dist/store/schema/cleo-global/skills.d.ts +2 -2
  393. package/dist/store/schema/cleo-project/lifecycle.d.ts +2 -2
  394. package/dist/store/schema/cleo-project/runtime.d.ts +1 -1
  395. package/dist/store/schema/cleo-project/tasks-core.d.ts +1 -1
  396. package/dist/store/schema/lifecycle.d.ts +2 -2
  397. package/dist/store/schema/skills-schema.d.ts +2 -2
  398. package/dist/store/schema/tasks.d.ts +1 -1
  399. package/dist/store/session-store.d.ts +3 -1
  400. package/dist/store/session-store.d.ts.map +1 -1
  401. package/dist/store/session-store.js +252 -207
  402. package/dist/store/session-store.js.map +1 -1
  403. package/dist/store/sqlite-data-accessor.d.ts +39 -4
  404. package/dist/store/sqlite-data-accessor.d.ts.map +1 -1
  405. package/dist/store/sqlite-data-accessor.js +689 -527
  406. package/dist/store/sqlite-data-accessor.js.map +1 -1
  407. package/dist/store/tasks-sqlite.d.ts +3 -1
  408. package/dist/store/tasks-sqlite.d.ts.map +1 -1
  409. package/dist/store/tasks-sqlite.js +309 -279
  410. package/dist/store/tasks-sqlite.js.map +1 -1
  411. package/dist/store/validation-schemas.d.ts +8 -8
  412. package/dist/store/writer-lease.d.ts +80 -8
  413. package/dist/store/writer-lease.d.ts.map +1 -1
  414. package/dist/store/writer-lease.js +503 -134
  415. package/dist/store/writer-lease.js.map +1 -1
  416. package/dist/system/audit.d.ts +2 -1
  417. package/dist/system/audit.d.ts.map +1 -1
  418. package/dist/system/audit.js +118 -71
  419. package/dist/system/audit.js.map +1 -1
  420. package/dist/system/backup.d.ts.map +1 -1
  421. package/dist/system/backup.js +5 -5
  422. package/dist/system/backup.js.map +1 -1
  423. package/dist/system/inject-generate.js +1 -1
  424. package/dist/task-work/index.d.ts +3 -0
  425. package/dist/task-work/index.d.ts.map +1 -1
  426. package/dist/task-work/index.js +34 -37
  427. package/dist/task-work/index.js.map +1 -1
  428. package/dist/tasks/ac-coverage-gate.d.ts.map +1 -1
  429. package/dist/tasks/ac-coverage-gate.js +4 -0
  430. package/dist/tasks/ac-coverage-gate.js.map +1 -1
  431. package/dist/tasks/ac-immutability.d.ts +7 -4
  432. package/dist/tasks/ac-immutability.d.ts.map +1 -1
  433. package/dist/tasks/ac-immutability.js +19 -17
  434. package/dist/tasks/ac-immutability.js.map +1 -1
  435. package/dist/tasks/ac-table.d.ts +5 -5
  436. package/dist/tasks/ac-table.d.ts.map +1 -1
  437. package/dist/tasks/ac-table.js +26 -12
  438. package/dist/tasks/ac-table.js.map +1 -1
  439. package/dist/tasks/acceptance-input.d.ts +52 -0
  440. package/dist/tasks/acceptance-input.d.ts.map +1 -0
  441. package/dist/tasks/acceptance-input.js +259 -0
  442. package/dist/tasks/acceptance-input.js.map +1 -0
  443. package/dist/tasks/add.d.ts +67 -29
  444. package/dist/tasks/add.d.ts.map +1 -1
  445. package/dist/tasks/add.js +255 -97
  446. package/dist/tasks/add.js.map +1 -1
  447. package/dist/tasks/complete.d.ts +16 -1
  448. package/dist/tasks/complete.d.ts.map +1 -1
  449. package/dist/tasks/complete.js +152 -55
  450. package/dist/tasks/complete.js.map +1 -1
  451. package/dist/tasks/decompose.d.ts +103 -0
  452. package/dist/tasks/decompose.d.ts.map +1 -0
  453. package/dist/tasks/decompose.js +224 -0
  454. package/dist/tasks/decompose.js.map +1 -0
  455. package/dist/tasks/delete.d.ts +5 -8
  456. package/dist/tasks/delete.d.ts.map +1 -1
  457. package/dist/tasks/delete.js +4 -3
  458. package/dist/tasks/delete.js.map +1 -1
  459. package/dist/tasks/dependency-check.d.ts +59 -0
  460. package/dist/tasks/dependency-check.d.ts.map +1 -1
  461. package/dist/tasks/dependency-check.js +73 -0
  462. package/dist/tasks/dependency-check.js.map +1 -1
  463. package/dist/tasks/deps-ready.d.ts +13 -1
  464. package/dist/tasks/deps-ready.d.ts.map +1 -1
  465. package/dist/tasks/deps-ready.js +16 -5
  466. package/dist/tasks/deps-ready.js.map +1 -1
  467. package/dist/tasks/engine-converters.d.ts +7 -0
  468. package/dist/tasks/engine-converters.d.ts.map +1 -1
  469. package/dist/tasks/engine-converters.js +8 -1
  470. package/dist/tasks/engine-converters.js.map +1 -1
  471. package/dist/tasks/evidence.d.ts +54 -26
  472. package/dist/tasks/evidence.d.ts.map +1 -1
  473. package/dist/tasks/evidence.js +292 -40
  474. package/dist/tasks/evidence.js.map +1 -1
  475. package/dist/tasks/find.d.ts +16 -12
  476. package/dist/tasks/find.d.ts.map +1 -1
  477. package/dist/tasks/find.js +107 -93
  478. package/dist/tasks/find.js.map +1 -1
  479. package/dist/tasks/gate-runner.d.ts +88 -14
  480. package/dist/tasks/gate-runner.d.ts.map +1 -1
  481. package/dist/tasks/gate-runner.js +596 -253
  482. package/dist/tasks/gate-runner.js.map +1 -1
  483. package/dist/tasks/hierarchy-policy.d.ts +8 -0
  484. package/dist/tasks/hierarchy-policy.d.ts.map +1 -1
  485. package/dist/tasks/hierarchy-policy.js +9 -2
  486. package/dist/tasks/hierarchy-policy.js.map +1 -1
  487. package/dist/tasks/hierarchy.d.ts +67 -1
  488. package/dist/tasks/hierarchy.d.ts.map +1 -1
  489. package/dist/tasks/hierarchy.js +81 -1
  490. package/dist/tasks/hierarchy.js.map +1 -1
  491. package/dist/tasks/index.d.ts +3 -0
  492. package/dist/tasks/index.d.ts.map +1 -1
  493. package/dist/tasks/index.js +3 -0
  494. package/dist/tasks/index.js.map +1 -1
  495. package/dist/tasks/infer-add-params.d.ts +1 -20
  496. package/dist/tasks/infer-add-params.d.ts.map +1 -1
  497. package/dist/tasks/infer-add-params.js +2 -203
  498. package/dist/tasks/infer-add-params.js.map +1 -1
  499. package/dist/tasks/list.d.ts +7 -2
  500. package/dist/tasks/list.d.ts.map +1 -1
  501. package/dist/tasks/list.js +24 -36
  502. package/dist/tasks/list.js.map +1 -1
  503. package/dist/tasks/ops.d.ts +6 -61
  504. package/dist/tasks/ops.d.ts.map +1 -1
  505. package/dist/tasks/ops.js +5 -53
  506. package/dist/tasks/ops.js.map +1 -1
  507. package/dist/tasks/plan.d.ts +15 -1
  508. package/dist/tasks/plan.d.ts.map +1 -1
  509. package/dist/tasks/plan.js +23 -25
  510. package/dist/tasks/plan.js.map +1 -1
  511. package/dist/tasks/population.d.ts +46 -0
  512. package/dist/tasks/population.d.ts.map +1 -0
  513. package/dist/tasks/population.js +88 -0
  514. package/dist/tasks/population.js.map +1 -0
  515. package/dist/tasks/reconcile-scope.d.ts +157 -0
  516. package/dist/tasks/reconcile-scope.d.ts.map +1 -0
  517. package/dist/tasks/reconcile-scope.js +242 -0
  518. package/dist/tasks/reconcile-scope.js.map +1 -0
  519. package/dist/tasks/req.d.ts +51 -12
  520. package/dist/tasks/req.d.ts.map +1 -1
  521. package/dist/tasks/req.js +125 -69
  522. package/dist/tasks/req.js.map +1 -1
  523. package/dist/tasks/session-scope.d.ts +6 -31
  524. package/dist/tasks/session-scope.d.ts.map +1 -1
  525. package/dist/tasks/session-scope.js +3 -17
  526. package/dist/tasks/session-scope.js.map +1 -1
  527. package/dist/tasks/severity-attestation.d.ts +39 -10
  528. package/dist/tasks/severity-attestation.d.ts.map +1 -1
  529. package/dist/tasks/severity-attestation.js +75 -36
  530. package/dist/tasks/severity-attestation.js.map +1 -1
  531. package/dist/tasks/task-next.d.ts +5 -0
  532. package/dist/tasks/task-next.d.ts.map +1 -1
  533. package/dist/tasks/task-next.js +10 -9
  534. package/dist/tasks/task-next.js.map +1 -1
  535. package/dist/tasks/task-reparent.d.ts.map +1 -1
  536. package/dist/tasks/task-reparent.js +3 -13
  537. package/dist/tasks/task-reparent.js.map +1 -1
  538. package/dist/tasks/update.d.ts +20 -43
  539. package/dist/tasks/update.d.ts.map +1 -1
  540. package/dist/tasks/update.js +64 -49
  541. package/dist/tasks/update.js.map +1 -1
  542. package/dist/teardown-signal.d.ts +72 -0
  543. package/dist/teardown-signal.d.ts.map +1 -0
  544. package/dist/teardown-signal.js +108 -0
  545. package/dist/teardown-signal.js.map +1 -0
  546. package/dist/validation/engine-ops.d.ts +17 -1
  547. package/dist/validation/engine-ops.d.ts.map +1 -1
  548. package/dist/validation/engine-ops.js +149 -11
  549. package/dist/validation/engine-ops.js.map +1 -1
  550. package/dist/workgraph/reparent-preflight.d.ts +1 -1
  551. package/dist/workgraph/reparent-preflight.d.ts.map +1 -1
  552. package/dist/workgraph/reparent-preflight.js +6 -1
  553. package/dist/workgraph/reparent-preflight.js.map +1 -1
  554. package/dist/workgraph/scaffold-apply.d.ts.map +1 -1
  555. package/dist/workgraph/scaffold-apply.js +81 -76
  556. package/dist/workgraph/scaffold-apply.js.map +1 -1
  557. package/dist/workgraph/structure-validation.d.ts +1 -1
  558. package/dist/workgraph/structure-validation.d.ts.map +1 -1
  559. package/dist/workgraph/structure-validation.js +6 -1
  560. package/dist/workgraph/structure-validation.js.map +1 -1
  561. package/migrations/drizzle-tasks/20260919180000_t12263-job-ownership-fences/migration.sql +34 -0
  562. package/migrations/drizzle-tasks/20260919184500_t12265-durable-pending-proposals/migration.sql +4 -0
  563. package/package.json +18 -13
  564. package/schemas/config.schema.json +2 -2
  565. package/templates/CLEO-INJECTION.md +103 -101
@@ -19,7 +19,7 @@ import { analyzeEpic, getNextTask, getReadyTasks } from '../orchestration/index.
19
19
  import { computeEpicStatus, computeOverallStatus } from '../orchestration/status.js';
20
20
  import { validateSpawnReadiness } from '../orchestration/validate-spawn.js';
21
21
  import { getEnrichedWaves } from '../orchestration/waves.js';
22
- import { getProjectRoot } from '../paths.js';
22
+ import { captureProjectScope, getProjectRoot, worktreeScope } from '../project-scope.js';
23
23
  import { isSagaShape } from '../sagas/enforcement.js';
24
24
  import { resolveSagaMemberIds } from '../sagas/storage.js';
25
25
  import { getTaskAccessor } from '../store/data-accessor.js';
@@ -84,20 +84,18 @@ export async function resolveSagaMembers(accessor, sagaId) {
84
84
  * Load all tasks from task data.
85
85
  *
86
86
  * @param projectRoot - Optional project root path. Defaults to resolved root.
87
- * @returns Array of all tasks, empty on error.
87
+ * @returns All matched tasks, including an empty array for an empty project.
88
+ * @throws Error when project identity or task storage cannot be read.
88
89
  */
89
90
  export async function loadTasks(projectRoot) {
90
- const root = getProjectRoot(projectRoot);
91
- try {
92
- const accessor = await getTaskAccessor(root);
91
+ const scope = captureProjectScope(projectRoot ?? getProjectRoot(), worktreeScope.getStore());
92
+ return worktreeScope.run(scope, async () => {
93
+ const accessor = await getTaskAccessor(scope.worktreeRoot);
93
94
  const result = await accessor.queryTasks({
94
95
  status: [...TASK_STATUSES],
95
96
  });
96
- return result?.tasks ?? [];
97
- }
98
- catch {
99
- return [];
100
- }
97
+ return result.tasks;
98
+ });
101
99
  }
102
100
  /**
103
101
  * orchestrate.status - Get orchestrator status
@@ -109,20 +107,23 @@ export async function loadTasks(projectRoot) {
109
107
  */
110
108
  export async function orchestrateStatus(epicId, projectRoot) {
111
109
  try {
112
- const root = getProjectRoot(projectRoot);
113
- const tasks = await loadTasks(root);
114
- if (epicId) {
115
- const epic = tasks.find((t) => t.id === epicId);
116
- if (!epic) {
117
- return engineError('E_NOT_FOUND', `Epic ${epicId} not found`);
110
+ const scope = captureProjectScope(projectRoot ?? getProjectRoot(), worktreeScope.getStore());
111
+ return await worktreeScope.run(scope, async () => {
112
+ const root = scope.worktreeRoot;
113
+ const tasks = await loadTasks(root);
114
+ if (epicId) {
115
+ const epic = tasks.find((t) => t.id === epicId);
116
+ if (!epic) {
117
+ return engineError('E_NOT_FOUND', `Epic ${epicId} not found`);
118
+ }
119
+ const children = tasks.filter((t) => t.parentId === epicId);
120
+ const status = computeEpicStatus(epicId, epic.title, children);
121
+ return { success: true, data: status };
118
122
  }
119
- const children = tasks.filter((t) => t.parentId === epicId);
120
- const status = computeEpicStatus(epicId, epic.title, children);
123
+ // No epicId - return overall status
124
+ const status = computeOverallStatus(tasks);
121
125
  return { success: true, data: status };
122
- }
123
- // No epicId - return overall status
124
- const status = computeOverallStatus(tasks);
125
- return { success: true, data: status };
126
+ });
126
127
  }
127
128
  catch (err) {
128
129
  return engineError('E_GENERAL', err.message);
@@ -138,35 +139,38 @@ export async function orchestrateStatus(epicId, projectRoot) {
138
139
  * @task T4478
139
140
  */
140
141
  export async function orchestrateAnalyze(epicId, projectRoot, mode) {
141
- // Mode: critical-path (delegates to critical path engine)
142
- if (mode === 'critical-path') {
143
- const { orchestrateCriticalPath } = await import('./lifecycle-ops.js');
144
- return orchestrateCriticalPath(projectRoot);
145
- }
146
- // Default mode: analysis (requires epicId)
147
- if (!epicId) {
148
- return engineError('E_INVALID_INPUT', 'epicId is required for standard analysis');
149
- }
150
142
  try {
151
- const root = getProjectRoot(projectRoot);
152
- const accessor = await getTaskAccessor(root);
153
- const result = await analyzeEpic(epicId, root, accessor);
154
- // Add dependency graph and circular dep detection via core analyze module
155
- const tasks = await loadTasks(root);
156
- const children = tasks.filter((t) => t.parentId === epicId);
157
- const depAnalysis = analyzeDependencies(children, tasks);
158
- return {
159
- success: true,
160
- data: {
161
- epicId: result.epicId,
162
- epicTitle: tasks.find((t) => t.id === epicId)?.title || epicId,
163
- totalTasks: result.totalTasks,
164
- waves: result.waves,
165
- circularDependencies: depAnalysis.circularDependencies,
166
- missingDependencies: depAnalysis.missingDependencies,
167
- dependencyGraph: depAnalysis.dependencyGraph,
168
- },
169
- };
143
+ const scope = captureProjectScope(projectRoot ?? getProjectRoot(), worktreeScope.getStore());
144
+ return await worktreeScope.run(scope, async () => {
145
+ // Mode: critical-path (delegates to critical path engine)
146
+ if (mode === 'critical-path') {
147
+ const { orchestrateCriticalPath } = await import('./lifecycle-ops.js');
148
+ return orchestrateCriticalPath(scope.worktreeRoot);
149
+ }
150
+ // Default mode: analysis (requires epicId)
151
+ if (!epicId) {
152
+ return engineError('E_INVALID_INPUT', 'epicId is required for standard analysis');
153
+ }
154
+ const root = scope.worktreeRoot;
155
+ const accessor = await getTaskAccessor(root);
156
+ const result = await analyzeEpic(epicId, root, accessor);
157
+ // Add dependency graph and circular dep detection via core analyze module
158
+ const tasks = await loadTasks(root);
159
+ const children = tasks.filter((t) => t.parentId === epicId);
160
+ const depAnalysis = analyzeDependencies(children, tasks);
161
+ return {
162
+ success: true,
163
+ data: {
164
+ epicId: result.epicId,
165
+ epicTitle: tasks.find((t) => t.id === epicId)?.title || epicId,
166
+ totalTasks: result.totalTasks,
167
+ waves: result.waves,
168
+ circularDependencies: depAnalysis.circularDependencies,
169
+ missingDependencies: depAnalysis.missingDependencies,
170
+ dependencyGraph: depAnalysis.dependencyGraph,
171
+ },
172
+ };
173
+ });
170
174
  }
171
175
  catch (err) {
172
176
  const code = err.code ?? 'E_GENERAL';
@@ -194,208 +198,211 @@ export async function orchestrateAnalyze(epicId, projectRoot, mode) {
194
198
  * @task T1858
195
199
  */
196
200
  export async function orchestrateReady(epicId, projectRoot, opts) {
197
- if (!epicId) {
198
- return engineError('E_INVALID_INPUT', 'epicId is required');
199
- }
200
201
  try {
201
- const root = getProjectRoot(projectRoot);
202
- // T929: verify the epic exists before computing the ready-set so that a
203
- // nonexistent epicId returns E_NOT_FOUND (exit 4) instead of success:{total:0}.
204
- const tasks = await loadTasks(root);
205
- const epic = tasks.find((t) => t.id === epicId);
206
- if (!epic) {
207
- return engineError('E_NOT_FOUND', `Epic ${epicId} not found`);
208
- }
209
- // ---------------------------------------------------------------------------
210
- // T1858: dep-graph validation pre-step (shared between parent + saga modes)
211
- // ---------------------------------------------------------------------------
212
- const config = await loadConfig(root);
213
- const lifecycleMode = config.lifecycle?.mode ?? 'strict';
214
- let depsWarning;
215
- // Helper: filter issues to only those that are actionable blockers for
216
- // the epic ready-set check. Excludes:
217
- // - E_ORPHAN: project-level concern, not specific to the epic ready-check.
218
- // - E_MISSING_REF where the referenced task EXISTS project-wide (cross-epic
219
- // deps that are outside the scoped set are not truly missing).
220
- const projectTaskIds = new Set(tasks.map((t) => t.id));
221
- const toBlockerIssues = (issues) => issues.filter((i) => {
222
- if (i.code === 'E_ORPHAN')
223
- return false;
224
- if (i.code === 'E_MISSING_REF') {
225
- // relatedIds contains the dep ID that was reported missing in the scope.
226
- // If it actually exists project-wide, it's just a cross-epic dep — not a
227
- // true missing reference.
228
- return (i.relatedIds ?? []).some((depId) => !projectTaskIds.has(depId));
202
+ const scope = captureProjectScope(projectRoot ?? getProjectRoot(), worktreeScope.getStore());
203
+ return await worktreeScope.run(scope, async () => {
204
+ if (!epicId) {
205
+ return engineError('E_INVALID_INPUT', 'epicId is required');
229
206
  }
230
- return true;
231
- });
232
- if (opts?.ignoreDepsValidate) {
233
- // Bypass requested — audit-log it regardless of mode, then skip check.
234
- const validation = runValidation(tasks, { epicId });
235
- const blockerIssues = toBlockerIssues(validation.issues);
236
- appendDepsValidateBypassAudit(root, {
237
- ts: new Date().toISOString(),
238
- epicId,
239
- source: 'cli',
240
- issueCount: blockerIssues.length,
241
- issues: blockerIssues.map(({ code, taskId, epicA, epicB }) => ({
242
- code,
243
- taskId,
244
- epicA,
245
- epicB,
246
- })),
247
- });
248
- }
249
- else if (lifecycleMode !== 'off') {
250
- const validation = runValidation(tasks, { epicId });
251
- const blockerIssues = toBlockerIssues(validation.issues);
252
- const isValid = blockerIssues.length === 0;
253
- if (!isValid) {
254
- const summary = `Dep graph has ${blockerIssues.length} issue(s): ${[...new Set(blockerIssues.map((i) => i.code))].join(', ')}`;
255
- if (lifecycleMode === 'strict') {
256
- return engineError('E_DEP_GRAPH_INVALID', summary, {
257
- details: { issueCount: blockerIssues.length, issues: blockerIssues },
258
- });
207
+ const root = scope.worktreeRoot;
208
+ // T929: verify the epic exists before computing the ready-set so that a
209
+ // nonexistent epicId returns E_NOT_FOUND (exit 4) instead of success:{total:0}.
210
+ const tasks = await loadTasks(root);
211
+ const epic = tasks.find((t) => t.id === epicId);
212
+ if (!epic) {
213
+ return engineError('E_NOT_FOUND', `Epic ${epicId} not found`);
214
+ }
215
+ // ---------------------------------------------------------------------------
216
+ // T1858: dep-graph validation pre-step (shared between parent + saga modes)
217
+ // ---------------------------------------------------------------------------
218
+ const config = await loadConfig(root);
219
+ const lifecycleMode = config.lifecycle?.mode ?? 'strict';
220
+ let depsWarning;
221
+ // Helper: filter issues to only those that are actionable blockers for
222
+ // the epic ready-set check. Excludes:
223
+ // - E_ORPHAN: project-level concern, not specific to the epic ready-check.
224
+ // - E_MISSING_REF where the referenced task EXISTS project-wide (cross-epic
225
+ // deps that are outside the scoped set are not truly missing).
226
+ const projectTaskIds = new Set(tasks.map((t) => t.id));
227
+ const toBlockerIssues = (issues) => issues.filter((i) => {
228
+ if (i.code === 'E_ORPHAN')
229
+ return false;
230
+ if (i.code === 'E_MISSING_REF') {
231
+ // relatedIds contains the dep ID that was reported missing in the scope.
232
+ // If it actually exists project-wide, it's just a cross-epic dep — not a
233
+ // true missing reference.
234
+ return (i.relatedIds ?? []).some((depId) => !projectTaskIds.has(depId));
259
235
  }
260
- // advisory: warn + proceed
261
- depsWarning = summary;
236
+ return true;
237
+ });
238
+ if (opts?.ignoreDepsValidate) {
239
+ // Bypass requested — audit-log it regardless of mode, then skip check.
240
+ const validation = runValidation(tasks, { epicId });
241
+ const blockerIssues = toBlockerIssues(validation.issues);
242
+ appendDepsValidateBypassAudit(root, {
243
+ ts: new Date().toISOString(),
244
+ epicId,
245
+ source: 'cli',
246
+ issueCount: blockerIssues.length,
247
+ issues: blockerIssues.map(({ code, taskId, epicA, epicB }) => ({
248
+ code,
249
+ taskId,
250
+ epicA,
251
+ epicB,
252
+ })),
253
+ });
262
254
  }
263
- }
264
- // mode === 'off': skip validation entirely
265
- // ---------------------------------------------------------------------------
266
- // -------------------------------------------------------------------------
267
- // T10966: Saga-aware traversal — always auto-detect saga shape.
268
- //
269
- // After T10638, sagas use `type='saga'` with `parent_id` containment.
270
- // The `via` parameter is deprecated (T10968); all callers get the same
271
- // auto-detect behavior. Saga members are recursed into per-member; the
272
- // result is the deduplicated union of ready tasks across all members.
273
- // -------------------------------------------------------------------------
274
- // T10331 (Saga T10326 W2.B): dual-shape saga detection via isSagaShape.
275
- const sagaShaped = isSagaShape(epic);
276
- const accessor = await getTaskAccessor(root);
277
- if (sagaShaped) {
278
- // T10966: use canonical resolveSagaMemberIds (with type-checking).
279
- const memberIds = await resolveSagaMemberIds(accessor, epicId);
280
- const members = memberIds ?? [];
281
- const seenIds = new Set();
282
- const aggregated = [];
283
- let aggregatedAllCount = 0;
284
- let aggregatedBlockedCount = 0;
285
- const skippedNested = [];
286
- for (const memberId of members) {
287
- // Recursion safety: sagas SHOULD NOT nest (ADR-073). If a member is
288
- // itself saga-shaped, skip it and surface the anomaly in meta rather
289
- // than recursing — preserves O(N) aggregation.
290
- const memberTask = tasks.find((t) => t.id === memberId);
291
- if (memberTask && isSagaShape(memberTask)) {
292
- skippedNested.push(memberId);
293
- continue;
255
+ else if (lifecycleMode !== 'off') {
256
+ const validation = runValidation(tasks, { epicId });
257
+ const blockerIssues = toBlockerIssues(validation.issues);
258
+ const isValid = blockerIssues.length === 0;
259
+ if (!isValid) {
260
+ const summary = `Dep graph has ${blockerIssues.length} issue(s): ${[...new Set(blockerIssues.map((i) => i.code))].join(', ')}`;
261
+ if (lifecycleMode === 'strict') {
262
+ return engineError('E_DEP_GRAPH_INVALID', summary, {
263
+ details: { issueCount: blockerIssues.length, issues: blockerIssues },
264
+ });
265
+ }
266
+ // advisory: warn + proceed
267
+ depsWarning = summary;
294
268
  }
295
- const memberReady = await getReadyTasks(memberId, root, accessor);
296
- aggregatedAllCount += memberReady.length;
297
- aggregatedBlockedCount += memberReady.filter((t) => !t.ready && t.blockers.length > 0).length;
298
- for (const t of memberReady) {
299
- if (!t.ready)
300
- continue;
301
- if (seenIds.has(t.taskId))
269
+ }
270
+ // mode === 'off': skip validation entirely
271
+ // ---------------------------------------------------------------------------
272
+ // -------------------------------------------------------------------------
273
+ // T10966: Saga-aware traversal — always auto-detect saga shape.
274
+ //
275
+ // After T10638, sagas use `type='saga'` with `parent_id` containment.
276
+ // The `via` parameter is deprecated (T10968); all callers get the same
277
+ // auto-detect behavior. Saga members are recursed into per-member; the
278
+ // result is the deduplicated union of ready tasks across all members.
279
+ // -------------------------------------------------------------------------
280
+ // T10331 (Saga T10326 W2.B): dual-shape saga detection via isSagaShape.
281
+ const sagaShaped = isSagaShape(epic);
282
+ const accessor = await getTaskAccessor(root);
283
+ if (sagaShaped) {
284
+ // T10966: use canonical resolveSagaMemberIds (with type-checking).
285
+ const memberIds = await resolveSagaMemberIds(accessor, epicId);
286
+ const members = memberIds ?? [];
287
+ const seenIds = new Set();
288
+ const aggregated = [];
289
+ let aggregatedAllCount = 0;
290
+ let aggregatedBlockedCount = 0;
291
+ const skippedNested = [];
292
+ for (const memberId of members) {
293
+ // Recursion safety: sagas SHOULD NOT nest (ADR-073). If a member is
294
+ // itself saga-shaped, skip it and surface the anomaly in meta rather
295
+ // than recursing — preserves O(N) aggregation.
296
+ const memberTask = tasks.find((t) => t.id === memberId);
297
+ if (memberTask && isSagaShape(memberTask)) {
298
+ skippedNested.push(memberId);
302
299
  continue;
303
- seenIds.add(t.taskId);
304
- aggregated.push({
305
- id: t.taskId,
306
- title: t.title,
307
- priority: t.priority,
308
- depends: t.depends,
309
- });
300
+ }
301
+ const memberReady = await getReadyTasks(memberId, root, accessor);
302
+ aggregatedAllCount += memberReady.length;
303
+ aggregatedBlockedCount += memberReady.filter((t) => !t.ready && t.blockers.length > 0).length;
304
+ for (const t of memberReady) {
305
+ if (!t.ready)
306
+ continue;
307
+ if (seenIds.has(t.taskId))
308
+ continue;
309
+ seenIds.add(t.taskId);
310
+ aggregated.push({
311
+ id: t.taskId,
312
+ title: t.title,
313
+ priority: t.priority,
314
+ depends: t.depends,
315
+ });
316
+ }
317
+ }
318
+ // Preserve priority ordering (critical → high → medium → low) then ID.
319
+ const priorityWeight = {
320
+ critical: 4,
321
+ high: 3,
322
+ medium: 2,
323
+ low: 1,
324
+ };
325
+ aggregated.sort((a, b) => {
326
+ const wa = priorityWeight[a.priority] ?? 0;
327
+ const wb = priorityWeight[b.priority] ?? 0;
328
+ if (wa !== wb)
329
+ return wb - wa;
330
+ return a.id.localeCompare(b.id);
331
+ });
332
+ let reason;
333
+ if (aggregated.length === 0) {
334
+ if (members.length === 0) {
335
+ reason = 'saga has no member epics';
336
+ }
337
+ else if (aggregatedAllCount === 0) {
338
+ reason = 'saga members have no children';
339
+ }
340
+ else if (aggregatedBlockedCount === aggregatedAllCount) {
341
+ reason = 'all saga-member tasks have unmet dependencies';
342
+ }
343
+ else {
344
+ reason = 'no saga-member tasks with unmet dependencies found';
345
+ }
310
346
  }
347
+ // T12000: annotate which ready tasks are admittable now vs deferred so
348
+ // orchestrators size their fan-out to host capacity (Never-OOM).
349
+ const admission = await computeAgentAdmission(aggregated.map((t) => t.id));
350
+ return {
351
+ success: true,
352
+ data: {
353
+ epicId,
354
+ readyTasks: aggregated,
355
+ total: aggregated.length,
356
+ via: 'saga',
357
+ sagaMembers: members,
358
+ admission,
359
+ ...(skippedNested.length > 0 && { sagaNestedSkipped: skippedNested }),
360
+ ...(reason !== undefined && { reason }),
361
+ ...(depsWarning !== undefined && { depsWarning }),
362
+ },
363
+ };
311
364
  }
312
- // Preserve priority ordering (critical → high → medium → low) then ID.
313
- const priorityWeight = {
314
- critical: 4,
315
- high: 3,
316
- medium: 2,
317
- low: 1,
318
- };
319
- aggregated.sort((a, b) => {
320
- const wa = priorityWeight[a.priority] ?? 0;
321
- const wb = priorityWeight[b.priority] ?? 0;
322
- if (wa !== wb)
323
- return wb - wa;
324
- return a.id.localeCompare(b.id);
325
- });
365
+ // Regular epic: walk parentId.
366
+ const readyTasks = await getReadyTasks(epicId, root, accessor);
367
+ const ready = readyTasks.filter((t) => t.ready);
368
+ // T929: when no tasks are ready, include a diagnostic reason so callers
369
+ // can distinguish "all done" from "all blocked" without a second query.
326
370
  let reason;
327
- if (aggregated.length === 0) {
328
- if (members.length === 0) {
329
- reason = 'saga has no member epics';
330
- }
331
- else if (aggregatedAllCount === 0) {
332
- reason = 'saga members have no children';
371
+ if (ready.length === 0) {
372
+ const all = readyTasks;
373
+ const blockedCount = all.filter((t) => !t.ready && t.blockers.length > 0).length;
374
+ if (all.length === 0) {
375
+ reason = 'epic has no children';
333
376
  }
334
- else if (aggregatedBlockedCount === aggregatedAllCount) {
335
- reason = 'all saga-member tasks have unmet dependencies';
377
+ else if (blockedCount === all.length) {
378
+ reason = 'all children have unmet dependencies';
336
379
  }
337
380
  else {
338
- reason = 'no saga-member tasks with unmet dependencies found';
381
+ reason = 'no tasks with unmet dependencies found; check child task statuses';
339
382
  }
340
383
  }
384
+ const readyOut = ready.map((t) => ({
385
+ id: t.taskId,
386
+ title: t.title,
387
+ priority: t.priority,
388
+ depends: t.depends,
389
+ }));
341
390
  // T12000: annotate which ready tasks are admittable now vs deferred so
342
391
  // orchestrators size their fan-out to host capacity (Never-OOM).
343
- const admission = await computeAgentAdmission(aggregated.map((t) => t.id));
392
+ const admission = await computeAgentAdmission(readyOut.map((t) => t.id));
344
393
  return {
345
394
  success: true,
346
395
  data: {
347
396
  epicId,
348
- readyTasks: aggregated,
349
- total: aggregated.length,
350
- via: 'saga',
351
- sagaMembers: members,
397
+ readyTasks: readyOut,
398
+ total: readyOut.length,
399
+ via: 'parent',
352
400
  admission,
353
- ...(skippedNested.length > 0 && { sagaNestedSkipped: skippedNested }),
354
401
  ...(reason !== undefined && { reason }),
355
402
  ...(depsWarning !== undefined && { depsWarning }),
356
403
  },
357
404
  };
358
- }
359
- // Regular epic: walk parentId.
360
- const readyTasks = await getReadyTasks(epicId, root, accessor);
361
- const ready = readyTasks.filter((t) => t.ready);
362
- // T929: when no tasks are ready, include a diagnostic reason so callers
363
- // can distinguish "all done" from "all blocked" without a second query.
364
- let reason;
365
- if (ready.length === 0) {
366
- const all = readyTasks;
367
- const blockedCount = all.filter((t) => !t.ready && t.blockers.length > 0).length;
368
- if (all.length === 0) {
369
- reason = 'epic has no children';
370
- }
371
- else if (blockedCount === all.length) {
372
- reason = 'all children have unmet dependencies';
373
- }
374
- else {
375
- reason = 'no tasks with unmet dependencies found; check child task statuses';
376
- }
377
- }
378
- const readyOut = ready.map((t) => ({
379
- id: t.taskId,
380
- title: t.title,
381
- priority: t.priority,
382
- depends: t.depends,
383
- }));
384
- // T12000: annotate which ready tasks are admittable now vs deferred so
385
- // orchestrators size their fan-out to host capacity (Never-OOM).
386
- const admission = await computeAgentAdmission(readyOut.map((t) => t.id));
387
- return {
388
- success: true,
389
- data: {
390
- epicId,
391
- readyTasks: readyOut,
392
- total: readyOut.length,
393
- via: 'parent',
394
- admission,
395
- ...(reason !== undefined && { reason }),
396
- ...(depsWarning !== undefined && { depsWarning }),
397
- },
398
- };
405
+ });
399
406
  }
400
407
  catch (err) {
401
408
  const code = err.code ?? 'E_GENERAL';
@@ -411,37 +418,40 @@ export async function orchestrateReady(epicId, projectRoot, opts) {
411
418
  * @task T4478
412
419
  */
413
420
  export async function orchestrateNext(epicId, projectRoot) {
414
- if (!epicId) {
415
- return engineError('E_INVALID_INPUT', 'epicId is required');
416
- }
417
421
  try {
418
- const root = getProjectRoot(projectRoot);
419
- const accessor = await getTaskAccessor(root);
420
- const nextTask = await getNextTask(epicId, root, accessor);
421
- if (!nextTask) {
422
+ const scope = captureProjectScope(projectRoot ?? getProjectRoot(), worktreeScope.getStore());
423
+ return await worktreeScope.run(scope, async () => {
424
+ if (!epicId) {
425
+ return engineError('E_INVALID_INPUT', 'epicId is required');
426
+ }
427
+ const root = scope.worktreeRoot;
428
+ const accessor = await getTaskAccessor(root);
429
+ const nextTask = await getNextTask(epicId, root, accessor);
430
+ if (!nextTask) {
431
+ return {
432
+ success: true,
433
+ data: {
434
+ epicId,
435
+ nextTask: null,
436
+ message: 'No tasks ready to spawn. All pending tasks may have unmet dependencies.',
437
+ },
438
+ };
439
+ }
440
+ // Get all ready tasks for alternatives
441
+ const readyTasks = await getReadyTasks(epicId, root, accessor);
442
+ const ready = readyTasks.filter((t) => t.ready);
422
443
  return {
423
444
  success: true,
424
445
  data: {
425
446
  epicId,
426
- nextTask: null,
427
- message: 'No tasks ready to spawn. All pending tasks may have unmet dependencies.',
447
+ nextTask: { id: nextTask.taskId, title: nextTask.title, priority: nextTask.priority },
448
+ alternatives: ready
449
+ .slice(1, 4)
450
+ .map((t) => ({ id: t.taskId, title: t.title, priority: t.priority })),
451
+ totalReady: ready.length,
428
452
  },
429
453
  };
430
- }
431
- // Get all ready tasks for alternatives
432
- const readyTasks = await getReadyTasks(epicId, root, accessor);
433
- const ready = readyTasks.filter((t) => t.ready);
434
- return {
435
- success: true,
436
- data: {
437
- epicId,
438
- nextTask: { id: nextTask.taskId, title: nextTask.title, priority: nextTask.priority },
439
- alternatives: ready
440
- .slice(1, 4)
441
- .map((t) => ({ id: t.taskId, title: t.title, priority: t.priority })),
442
- totalReady: ready.length,
443
- },
444
- };
454
+ });
445
455
  }
446
456
  catch (err) {
447
457
  const code = err.code ?? 'E_GENERAL';
@@ -451,12 +461,10 @@ export async function orchestrateNext(epicId, projectRoot) {
451
461
  /**
452
462
  * orchestrate.waves - Compute dependency waves
453
463
  *
454
- * For regular epics the wave plan is `getEnrichedWaves(epicId)`. For sagas
455
- * (Epics labeled `'saga'`, ADR-073), per-member wave plans are computed and
456
- * merged by wave index: wave N across all members becomes one unified wave N.
457
- * Members of unequal depth contribute to the trailing waves (longest tail
458
- * wins). Task IDs are deduplicated; per-wave order preserves the per-member
459
- * sort.
464
+ * For regular epics the wave plan is `getEnrichedWaves(epicId)`. Sagas select
465
+ * their member epics' children and compute one dependency graph across them.
466
+ * Containment controls selection; explicit hard dependencies control ordering
467
+ * and current readiness, including dependencies outside the selected population.
460
468
  *
461
469
  * @param epicId - Epic to compute waves for.
462
470
  * @param projectRoot - Optional project root path.
@@ -467,106 +475,64 @@ export async function orchestrateNext(epicId, projectRoot) {
467
475
  * @adr ADR-073
468
476
  */
469
477
  export async function orchestrateWaves(epicId, projectRoot, _opts) {
470
- if (!epicId) {
471
- return engineError('E_INVALID_INPUT', 'epicId is required');
472
- }
473
478
  try {
474
- const root = getProjectRoot(projectRoot);
475
- const accessor = await getTaskAccessor(root);
476
- // gh-390 / ADR-073: detect saga shape so we walk the groups relation.
477
- const epic = await accessor.loadSingleTask(epicId);
478
- if (!epic) {
479
- return engineError('E_NOT_FOUND', `Epic ${epicId} not found`);
480
- }
481
- // T10966: Saga-aware traversal — always auto-detect saga shape.
482
- // After T10638, sagas use `type='saga'` with `parent_id` containment.
483
- // T10331 (Saga T10326 W2.B): dual-shape saga detection via isSagaShape.
484
- const sagaShaped = isSagaShape(epic);
485
- if (!sagaShaped) {
486
- const result = await getEnrichedWaves(epicId, root, accessor);
487
- // T12000: admission over the first actionable (non-completed) wave —
488
- // the tasks an orchestrator would spawn next — so the fan-out is sized
489
- // to host capacity (Never-OOM).
490
- const admission = await computeAgentAdmission(firstActionableWaveTaskIds(result.waves));
491
- return {
492
- success: true,
493
- data: { ...result, via: 'parent', admission },
494
- };
495
- }
496
- // Saga walk: compute per-member waves and merge by index.
497
- const memberIds = await resolveSagaMemberIds(accessor, epicId);
498
- const members = memberIds ?? [];
499
- const skippedNested = [];
500
- const perMemberWaves = [];
501
- let totalChildren = 0;
502
- for (const memberId of members) {
503
- const memberTask = await accessor.loadSingleTask(memberId);
479
+ const scope = captureProjectScope(projectRoot ?? getProjectRoot(), worktreeScope.getStore());
480
+ return await worktreeScope.run(scope, async () => {
481
+ if (!epicId) {
482
+ return engineError('E_INVALID_INPUT', 'epicId is required');
483
+ }
484
+ const root = scope.worktreeRoot;
485
+ const accessor = await getTaskAccessor(root);
486
+ // gh-390 / ADR-073: detect saga shape so we walk the groups relation.
487
+ const epic = await accessor.loadSingleTask(epicId);
488
+ if (!epic) {
489
+ return engineError('E_NOT_FOUND', `Epic ${epicId} not found`);
490
+ }
491
+ // T10966: Saga-aware traversal — always auto-detect saga shape.
492
+ // After T10638, sagas use `type='saga'` with `parent_id` containment.
504
493
  // T10331 (Saga T10326 W2.B): dual-shape saga detection via isSagaShape.
505
- if (memberTask && isSagaShape(memberTask)) {
506
- skippedNested.push(memberId);
507
- continue;
494
+ const sagaShaped = isSagaShape(epic);
495
+ if (!sagaShaped) {
496
+ const result = await getEnrichedWaves(epicId, root, accessor);
497
+ // T12000: admission over the first actionable (non-completed) wave —
498
+ // the tasks an orchestrator would spawn next — so the fan-out is sized
499
+ // to host capacity (Never-OOM).
500
+ const admission = await computeAgentAdmission(firstActionableWaveTaskIds(result.waves));
501
+ return {
502
+ success: true,
503
+ data: { ...result, via: 'parent', admission },
504
+ };
508
505
  }
509
- const memberResult = await getEnrichedWaves(memberId, root, accessor);
510
- perMemberWaves.push(memberResult.waves);
511
- totalChildren += memberResult.totalTasks;
512
- }
513
- const maxWaves = perMemberWaves.reduce((m, w) => Math.max(m, w.length), 0);
514
- const mergedWaves = [];
515
- for (let i = 0; i < maxWaves; i++) {
516
- const seen = new Set();
517
- const mergedTasks = [];
518
- let anyInProgress = false;
519
- let allCompleted = true;
520
- let latestCompletedAt;
521
- for (const memberWaves of perMemberWaves) {
522
- const wave = memberWaves[i];
523
- if (!wave)
506
+ // Saga containment selects tasks; one graph preserves cross-member dependencies.
507
+ const memberIds = await resolveSagaMemberIds(accessor, epicId);
508
+ const members = memberIds ?? [];
509
+ const skippedNested = [];
510
+ const selectedMembers = [];
511
+ for (const memberId of members) {
512
+ const memberTask = await accessor.loadSingleTask(memberId);
513
+ // T10331 (Saga T10326 W2.B): dual-shape saga detection via isSagaShape.
514
+ if (memberTask && isSagaShape(memberTask)) {
515
+ skippedNested.push(memberId);
524
516
  continue;
525
- if (wave.status === 'in_progress')
526
- anyInProgress = true;
527
- if (wave.status !== 'completed')
528
- allCompleted = false;
529
- if (wave.completedAt && (!latestCompletedAt || wave.completedAt > latestCompletedAt)) {
530
- latestCompletedAt = wave.completedAt;
531
- }
532
- for (const t of wave.tasks) {
533
- if (seen.has(t.id))
534
- continue;
535
- seen.add(t.id);
536
- mergedTasks.push(t);
537
517
  }
518
+ selectedMembers.push(memberId);
538
519
  }
539
- const mergedStatus = allCompleted
540
- ? 'completed'
541
- : anyInProgress
542
- ? 'in_progress'
543
- : 'pending';
544
- const merged = {
545
- waveNumber: i + 1,
546
- status: mergedStatus,
547
- tasks: mergedTasks,
548
- taskIds: mergedTasks.map((t) => t.id),
520
+ const result = await getEnrichedWaves(epicId, root, accessor, selectedMembers);
521
+ const admission = await computeAgentAdmission(firstActionableWaveTaskIds(result.waves));
522
+ return {
523
+ success: true,
524
+ data: {
525
+ epicId,
526
+ waves: result.waves,
527
+ totalWaves: result.totalWaves,
528
+ totalTasks: result.totalTasks,
529
+ via: 'saga',
530
+ sagaMembers: members,
531
+ admission,
532
+ ...(skippedNested.length > 0 && { sagaNestedSkipped: skippedNested }),
533
+ },
549
534
  };
550
- if (mergedStatus === 'completed' && latestCompletedAt) {
551
- merged.completedAt = latestCompletedAt;
552
- }
553
- mergedWaves.push(merged);
554
- }
555
- // T12000: admission over the first actionable merged wave (Never-OOM).
556
- const admission = await computeAgentAdmission(firstActionableWaveTaskIds(mergedWaves));
557
- return {
558
- success: true,
559
- data: {
560
- epicId,
561
- waves: mergedWaves,
562
- totalWaves: mergedWaves.length,
563
- totalTasks: totalChildren,
564
- via: 'saga',
565
- sagaMembers: members,
566
- admission,
567
- ...(skippedNested.length > 0 && { sagaNestedSkipped: skippedNested }),
568
- },
569
- };
535
+ });
570
536
  }
571
537
  catch (err) {
572
538
  const code = err.code ?? 'E_GENERAL';
@@ -583,7 +549,7 @@ export async function orchestrateWaves(epicId, projectRoot, _opts) {
583
549
  */
584
550
  function firstActionableWaveTaskIds(waves) {
585
551
  const actionable = waves.find((w) => w.status !== 'completed');
586
- return actionable ? [...actionable.taskIds] : [];
552
+ return actionable ? actionable.tasks.filter((task) => task.ready).map((task) => task.id) : [];
587
553
  }
588
554
  /**
589
555
  * orchestrate.context - Context usage check
@@ -595,14 +561,17 @@ function firstActionableWaveTaskIds(waves) {
595
561
  */
596
562
  export async function orchestrateContext(epicId, projectRoot) {
597
563
  try {
598
- const root = getProjectRoot(projectRoot);
599
- const tasks = await loadTasks(root);
600
- let taskCount = tasks.length;
601
- if (epicId) {
602
- taskCount = tasks.filter((t) => t.parentId === epicId).length;
603
- }
604
- const contextData = estimateContext(taskCount, root, epicId);
605
- return { success: true, data: contextData };
564
+ const scope = captureProjectScope(projectRoot ?? getProjectRoot(), worktreeScope.getStore());
565
+ return await worktreeScope.run(scope, async () => {
566
+ const root = scope.worktreeRoot;
567
+ const tasks = await loadTasks(root);
568
+ let taskCount = tasks.length;
569
+ if (epicId) {
570
+ taskCount = tasks.filter((t) => t.parentId === epicId).length;
571
+ }
572
+ const contextData = estimateContext(taskCount, root, epicId);
573
+ return { success: true, data: contextData };
574
+ });
606
575
  }
607
576
  catch (err) {
608
577
  return engineError('E_GENERAL', err.message);
@@ -617,14 +586,17 @@ export async function orchestrateContext(epicId, projectRoot) {
617
586
  * @task T4478
618
587
  */
619
588
  export async function orchestrateValidate(taskId, projectRoot) {
620
- if (!taskId) {
621
- return engineError('E_INVALID_INPUT', 'taskId is required');
622
- }
623
589
  try {
624
- const root = getProjectRoot(projectRoot);
625
- const accessor = await getTaskAccessor(root);
626
- const result = await validateSpawnReadiness(taskId, root, accessor);
627
- return { success: true, data: result };
590
+ const scope = captureProjectScope(projectRoot ?? getProjectRoot(), worktreeScope.getStore());
591
+ return await worktreeScope.run(scope, async () => {
592
+ if (!taskId) {
593
+ return engineError('E_INVALID_INPUT', 'taskId is required');
594
+ }
595
+ const root = scope.worktreeRoot;
596
+ const accessor = await getTaskAccessor(root);
597
+ const result = await validateSpawnReadiness(taskId, root, accessor);
598
+ return { success: true, data: result };
599
+ });
628
600
  }
629
601
  catch (err) {
630
602
  return engineError('E_VALIDATION', err.message);
@@ -656,184 +628,189 @@ const MAX_REPORT_PAGE_SIZE = 200;
656
628
  * @task T10631
657
629
  */
658
630
  export async function orchestrateReport(epicId, projectRoot, params) {
659
- if (!epicId) {
660
- return engineError('E_INVALID_INPUT', 'epicId is required');
661
- }
662
631
  try {
663
- const root = getProjectRoot(projectRoot);
664
- const tasks = await loadTasks(root);
665
- const epic = tasks.find((t) => t.id === epicId);
666
- if (!epic) {
667
- return engineError('E_NOT_FOUND', `Epic ${epicId} not found`);
668
- }
669
- const children = tasks.filter((t) => t.parentId === epicId);
670
- const completedIds = new Set(tasks.filter((t) => t.status === 'done').map((t) => t.id));
671
- // Classification buckets
672
- const ready = [];
673
- const blocked = [];
674
- const blockedBy = [];
675
- const gateBlocked = [];
676
- const invalid = [];
677
- // Dependency analysis for invalid detection — swallow errors gracefully
678
- let depAnalysis = { missingDependencies: [], circularDependencies: [] };
679
- try {
680
- depAnalysis = analyzeDependencies(children, tasks);
681
- }
682
- catch {
683
- // Non-fatal: dependency analysis failed, skip invalid classification
684
- }
685
- const missingDepIds = new Set(depAnalysis.missingDependencies.map((m) => m.missingDep));
686
- const circularInvolved = new Set();
687
- for (const chain of depAnalysis.circularDependencies) {
688
- for (const id of chain)
689
- circularInvolved.add(id);
690
- }
691
- for (const task of children) {
692
- // Skip terminal states
693
- if (task.status === 'done' || task.status === 'cancelled')
694
- continue;
695
- const deps = task.depends ?? [];
696
- const unmetDeps = deps.filter((d) => !completedIds.has(d));
697
- const gates = task.gates ?? {};
698
- // --- Invalid classification ---
699
- const hasMissingDeps = deps.some((d) => missingDepIds.has(d));
700
- const isCircular = circularInvolved.has(task.id);
701
- if (hasMissingDeps || isCircular) {
702
- const reasons = [];
703
- if (hasMissingDeps)
704
- reasons.push('missing-dep');
705
- if (isCircular)
706
- reasons.push('circular-dep');
707
- invalid.push({
708
- id: task.id,
709
- title: task.title,
710
- priority: task.priority ?? 'medium',
711
- status: task.status,
712
- reason: reasons.join(', '),
713
- });
714
- continue;
632
+ const scope = captureProjectScope(projectRoot ?? getProjectRoot(), worktreeScope.getStore());
633
+ return await worktreeScope.run(scope, async () => {
634
+ if (!epicId) {
635
+ return engineError('E_INVALID_INPUT', 'epicId is required');
715
636
  }
716
- // --- Gate-blocked classification ---
717
- const requiredGates = ['implemented', 'testsPassed', 'qaPassed'];
718
- const failedGates = requiredGates.filter((g) => gates[g] === false);
719
- // A task is gate-blocked only if at least one required gate is explicitly false.
720
- // Missing/undefined gates (common for pending tasks) do NOT classify as gate-blocked.
721
- if (failedGates.length > 0) {
722
- const gateSummary = {};
723
- for (const g of requiredGates) {
724
- gateSummary[g] = gates[g] === true;
725
- }
726
- gateBlocked.push({
727
- id: task.id,
728
- title: task.title,
729
- priority: task.priority ?? 'medium',
730
- status: task.status,
731
- reason: failedGates.length > 0 ? `gates-failed: ${failedGates.join(',')}` : 'gates-incomplete',
732
- gates: gateSummary,
733
- });
734
- continue;
637
+ const root = scope.worktreeRoot;
638
+ const tasks = await loadTasks(root);
639
+ const epic = tasks.find((t) => t.id === epicId);
640
+ if (!epic) {
641
+ return engineError('E_NOT_FOUND', `Epic ${epicId} not found`);
735
642
  }
736
- // --- Blocked-by classification ---
737
- if (unmetDeps.length > 0) {
738
- blockedBy.push({
739
- id: task.id,
740
- title: task.title,
741
- priority: task.priority ?? 'medium',
742
- status: task.status,
743
- reason: unmetDeps.join(', '),
744
- });
745
- // Also add to plain "blocked" for the broader blocked group
746
- blocked.push({
643
+ const children = tasks.filter((t) => t.parentId === epicId);
644
+ const completedIds = new Set(tasks.filter((t) => t.status === 'done').map((t) => t.id));
645
+ // Classification buckets
646
+ const ready = [];
647
+ const blocked = [];
648
+ const blockedBy = [];
649
+ const gateBlocked = [];
650
+ const invalid = [];
651
+ // Dependency analysis for invalid detection — swallow errors gracefully
652
+ let depAnalysis = { missingDependencies: [], circularDependencies: [] };
653
+ try {
654
+ depAnalysis = analyzeDependencies(children, tasks);
655
+ }
656
+ catch {
657
+ // Non-fatal: dependency analysis failed, skip invalid classification
658
+ }
659
+ const missingDepIds = new Set(depAnalysis.missingDependencies.map((m) => m.missingDep));
660
+ const circularInvolved = new Set();
661
+ for (const chain of depAnalysis.circularDependencies) {
662
+ for (const id of chain)
663
+ circularInvolved.add(id);
664
+ }
665
+ for (const task of children) {
666
+ // Skip terminal states
667
+ if (task.status === 'done' || task.status === 'cancelled')
668
+ continue;
669
+ const deps = task.depends ?? [];
670
+ const unmetDeps = deps.filter((d) => !completedIds.has(d));
671
+ const gates = task.gates ?? {};
672
+ // --- Invalid classification ---
673
+ const hasMissingDeps = deps.some((d) => missingDepIds.has(d));
674
+ const isCircular = circularInvolved.has(task.id);
675
+ if (hasMissingDeps || isCircular) {
676
+ const reasons = [];
677
+ if (hasMissingDeps)
678
+ reasons.push('missing-dep');
679
+ if (isCircular)
680
+ reasons.push('circular-dep');
681
+ invalid.push({
682
+ id: task.id,
683
+ title: task.title,
684
+ priority: task.priority ?? 'medium',
685
+ status: task.status,
686
+ reason: reasons.join(', '),
687
+ });
688
+ continue;
689
+ }
690
+ // --- Gate-blocked classification ---
691
+ const requiredGates = ['implemented', 'testsPassed', 'qaPassed'];
692
+ const failedGates = requiredGates.filter((g) => gates[g] === false);
693
+ // A task is gate-blocked only if at least one required gate is explicitly false.
694
+ // Missing/undefined gates (common for pending tasks) do NOT classify as gate-blocked.
695
+ if (failedGates.length > 0) {
696
+ const gateSummary = {};
697
+ for (const g of requiredGates) {
698
+ gateSummary[g] = gates[g] === true;
699
+ }
700
+ gateBlocked.push({
701
+ id: task.id,
702
+ title: task.title,
703
+ priority: task.priority ?? 'medium',
704
+ status: task.status,
705
+ reason: failedGates.length > 0
706
+ ? `gates-failed: ${failedGates.join(',')}`
707
+ : 'gates-incomplete',
708
+ gates: gateSummary,
709
+ });
710
+ continue;
711
+ }
712
+ // --- Blocked-by classification ---
713
+ if (unmetDeps.length > 0) {
714
+ blockedBy.push({
715
+ id: task.id,
716
+ title: task.title,
717
+ priority: task.priority ?? 'medium',
718
+ status: task.status,
719
+ reason: unmetDeps.join(', '),
720
+ });
721
+ // Also add to plain "blocked" for the broader blocked group
722
+ blocked.push({
723
+ id: task.id,
724
+ title: task.title,
725
+ priority: task.priority ?? 'medium',
726
+ status: task.status,
727
+ reason: `${unmetDeps.length} unmet dep(s)`,
728
+ });
729
+ continue;
730
+ }
731
+ // --- Ready classification ---
732
+ ready.push({
747
733
  id: task.id,
748
734
  title: task.title,
749
735
  priority: task.priority ?? 'medium',
750
736
  status: task.status,
751
- reason: `${unmetDeps.length} unmet dep(s)`,
737
+ reason: 'ready',
752
738
  });
753
- continue;
754
739
  }
755
- // --- Ready classification ---
756
- ready.push({
757
- id: task.id,
758
- title: task.title,
759
- priority: task.priority ?? 'medium',
760
- status: task.status,
761
- reason: 'ready',
762
- });
763
- }
764
- // Build groups
765
- const groups = [
766
- {
767
- group: 'ready',
768
- label: 'Ready — parallel-safe, actionable now',
769
- count: ready.length,
770
- tasks: ready,
771
- },
772
- {
773
- group: 'blocked',
774
- label: 'Blocked — has unmet dependency counts',
775
- count: blocked.length,
776
- tasks: blocked,
777
- },
778
- {
779
- group: 'blockedBy',
780
- label: 'Blocked-by — lists specific blocker task IDs',
781
- count: blockedBy.length,
782
- tasks: blockedBy,
783
- },
784
- {
785
- group: 'gateBlocked',
786
- label: 'Gate-blocked — gates (implemented/testsPassed/qaPassed) not satisfied',
787
- count: gateBlocked.length,
788
- tasks: gateBlocked,
789
- },
790
- {
791
- group: 'invalid',
792
- label: 'Invalid — missing or circular dependencies',
793
- count: invalid.length,
794
- tasks: invalid,
795
- },
796
- ];
797
- // Pagination
798
- const pageSize = Math.min(params?.pageSize ?? DEFAULT_REPORT_PAGE_SIZE, MAX_REPORT_PAGE_SIZE);
799
- const page = Math.max(params?.page ?? 1, 1);
800
- const totalEntries = groups.reduce((sum, g) => sum + g.count, 0);
801
- const totalPages = Math.max(1, Math.ceil(totalEntries / pageSize));
802
- // Apply pagination — slice tasks across all groups
803
- if (totalEntries > pageSize) {
804
- const startIdx = (page - 1) * pageSize;
805
- const endIdx = startIdx + pageSize;
806
- let globalIdx = 0;
807
- for (const group of groups) {
808
- const groupStart = globalIdx;
809
- const groupEnd = globalIdx + group.tasks.length;
810
- if (groupEnd <= startIdx || groupStart >= endIdx) {
811
- // Group entirely outside the current page
812
- group.tasks = [];
813
- }
814
- else {
815
- const sliceStart = Math.max(0, startIdx - groupStart);
816
- const sliceEnd = Math.min(group.tasks.length, endIdx - groupStart);
817
- group.tasks = group.tasks.slice(sliceStart, sliceEnd);
740
+ // Build groups
741
+ const groups = [
742
+ {
743
+ group: 'ready',
744
+ label: 'Ready — parallel-safe, actionable now',
745
+ count: ready.length,
746
+ tasks: ready,
747
+ },
748
+ {
749
+ group: 'blocked',
750
+ label: 'Blocked — has unmet dependency counts',
751
+ count: blocked.length,
752
+ tasks: blocked,
753
+ },
754
+ {
755
+ group: 'blockedBy',
756
+ label: 'Blocked-by — lists specific blocker task IDs',
757
+ count: blockedBy.length,
758
+ tasks: blockedBy,
759
+ },
760
+ {
761
+ group: 'gateBlocked',
762
+ label: 'Gate-blocked — gates (implemented/testsPassed/qaPassed) not satisfied',
763
+ count: gateBlocked.length,
764
+ tasks: gateBlocked,
765
+ },
766
+ {
767
+ group: 'invalid',
768
+ label: 'Invalid — missing or circular dependencies',
769
+ count: invalid.length,
770
+ tasks: invalid,
771
+ },
772
+ ];
773
+ // Pagination
774
+ const pageSize = Math.min(params?.pageSize ?? DEFAULT_REPORT_PAGE_SIZE, MAX_REPORT_PAGE_SIZE);
775
+ const page = Math.max(params?.page ?? 1, 1);
776
+ const totalEntries = groups.reduce((sum, g) => sum + g.count, 0);
777
+ const totalPages = Math.max(1, Math.ceil(totalEntries / pageSize));
778
+ // Apply pagination — slice tasks across all groups
779
+ if (totalEntries > pageSize) {
780
+ const startIdx = (page - 1) * pageSize;
781
+ const endIdx = startIdx + pageSize;
782
+ let globalIdx = 0;
783
+ for (const group of groups) {
784
+ const groupStart = globalIdx;
785
+ const groupEnd = globalIdx + group.tasks.length;
786
+ if (groupEnd <= startIdx || groupStart >= endIdx) {
787
+ // Group entirely outside the current page
788
+ group.tasks = [];
789
+ }
790
+ else {
791
+ const sliceStart = Math.max(0, startIdx - groupStart);
792
+ const sliceEnd = Math.min(group.tasks.length, endIdx - groupStart);
793
+ group.tasks = group.tasks.slice(sliceStart, sliceEnd);
794
+ }
795
+ globalIdx = groupEnd;
818
796
  }
819
- globalIdx = groupEnd;
820
797
  }
821
- }
822
- return {
823
- success: true,
824
- data: {
825
- epicId,
826
- epicTitle: epic.title,
827
- totalTasks: children.length,
828
- groups,
829
- pagination: {
830
- page,
831
- pageSize,
832
- totalPages,
833
- totalEntries,
798
+ return {
799
+ success: true,
800
+ data: {
801
+ epicId,
802
+ epicTitle: epic.title,
803
+ totalTasks: children.length,
804
+ groups,
805
+ pagination: {
806
+ page,
807
+ pageSize,
808
+ totalPages,
809
+ totalEntries,
810
+ },
834
811
  },
835
- },
836
- };
812
+ };
813
+ });
837
814
  }
838
815
  catch (err) {
839
816
  const code = err.code ?? 'E_GENERAL';