@cleocode/core 2026.9.8 → 2026.9.10
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.
- package/dist/audit/reconstruct.d.ts +13 -43
- package/dist/audit/reconstruct.d.ts.map +1 -1
- package/dist/audit/reconstruct.js +295 -290
- package/dist/audit/reconstruct.js.map +1 -1
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +8 -2
- package/dist/bootstrap.js.map +1 -1
- package/dist/compliance/protocol-rules.d.ts.map +1 -1
- package/dist/compliance/protocol-rules.js +3 -1
- package/dist/compliance/protocol-rules.js.map +1 -1
- package/dist/dispatch/mutate-projection.d.ts.map +1 -1
- package/dist/dispatch/mutate-projection.js +12 -0
- package/dist/dispatch/mutate-projection.js.map +1 -1
- package/dist/dispatch/mvi-projection.d.ts +29 -6
- package/dist/dispatch/mvi-projection.d.ts.map +1 -1
- package/dist/dispatch/mvi-projection.js +109 -139
- package/dist/dispatch/mvi-projection.js.map +1 -1
- package/dist/docs/docs-read-model.d.ts +3 -2
- package/dist/docs/docs-read-model.d.ts.map +1 -1
- package/dist/docs/docs-read-model.js +4 -3
- package/dist/docs/docs-read-model.js.map +1 -1
- package/dist/docs/export-document.js +5129 -2297
- package/dist/docs/export-document.js.map +3 -3
- package/dist/docs/projection.d.ts +67 -0
- package/dist/docs/projection.d.ts.map +1 -0
- package/dist/docs/projection.js +370 -0
- package/dist/docs/projection.js.map +1 -0
- package/dist/doctor/acceptance-drift.d.ts +6 -2
- package/dist/doctor/acceptance-drift.d.ts.map +1 -1
- package/dist/doctor/acceptance-drift.js +43 -5
- package/dist/doctor/acceptance-drift.js.map +1 -1
- package/dist/doctor/knowledge-summary.d.ts +41 -0
- package/dist/doctor/knowledge-summary.d.ts.map +1 -0
- package/dist/doctor/knowledge-summary.js +81 -0
- package/dist/doctor/knowledge-summary.js.map +1 -0
- package/dist/doctor/knowledge.d.ts +192 -0
- package/dist/doctor/knowledge.d.ts.map +1 -0
- package/dist/doctor/knowledge.js +1864 -0
- package/dist/doctor/knowledge.js.map +1 -0
- package/dist/git/work-tree.d.ts +169 -0
- package/dist/git/work-tree.d.ts.map +1 -0
- package/dist/git/work-tree.js +341 -0
- package/dist/git/work-tree.js.map +1 -0
- package/dist/hooks/handlers/task-hooks.d.ts +8 -5
- package/dist/hooks/handlers/task-hooks.d.ts.map +1 -1
- package/dist/hooks/handlers/task-hooks.js +15 -60
- package/dist/hooks/handlers/task-hooks.js.map +1 -1
- package/dist/identity/cleo-identity.d.ts +13 -0
- package/dist/identity/cleo-identity.d.ts.map +1 -1
- package/dist/identity/cleo-identity.js +20 -6
- package/dist/identity/cleo-identity.js.map +1 -1
- package/dist/injection.d.ts.map +1 -1
- package/dist/injection.js +29 -13
- package/dist/injection.js.map +1 -1
- package/dist/internal.d.ts +3 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +4 -0
- package/dist/internal.js.map +1 -1
- package/dist/lifecycle/verification/satisfies-validator.d.ts.map +1 -1
- package/dist/lifecycle/verification/satisfies-validator.js +124 -120
- package/dist/lifecycle/verification/satisfies-validator.js.map +1 -1
- package/dist/llm/plugin-facade.js +4047 -1726
- package/dist/llm/plugin-facade.js.map +3 -3
- package/dist/memory/brain-backfill.d.ts +27 -12
- package/dist/memory/brain-backfill.d.ts.map +1 -1
- package/dist/memory/brain-backfill.js +325 -134
- package/dist/memory/brain-backfill.js.map +1 -1
- package/dist/memory/brain-doctor.d.ts +21 -0
- package/dist/memory/brain-doctor.d.ts.map +1 -1
- package/dist/memory/brain-doctor.js +111 -53
- package/dist/memory/brain-doctor.js.map +1 -1
- package/dist/memory/brain-links.d.ts.map +1 -1
- package/dist/memory/brain-links.js +31 -4
- package/dist/memory/brain-links.js.map +1 -1
- package/dist/memory/brain-reasoning.d.ts.map +1 -1
- package/dist/memory/brain-reasoning.js +28 -2
- package/dist/memory/brain-reasoning.js.map +1 -1
- package/dist/memory/brain-reconciler.d.ts +14 -19
- package/dist/memory/brain-reconciler.d.ts.map +1 -1
- package/dist/memory/brain-reconciler.js +70 -97
- package/dist/memory/brain-reconciler.js.map +1 -1
- package/dist/memory/brain-search.d.ts +2 -0
- package/dist/memory/brain-search.d.ts.map +1 -1
- package/dist/memory/brain-search.js +59 -29
- package/dist/memory/brain-search.js.map +1 -1
- package/dist/memory/brain-similarity.d.ts.map +1 -1
- package/dist/memory/brain-similarity.js +5 -4
- package/dist/memory/brain-similarity.js.map +1 -1
- package/dist/memory/brain-stub-prune.d.ts +31 -9
- package/dist/memory/brain-stub-prune.d.ts.map +1 -1
- package/dist/memory/brain-stub-prune.js +65 -18
- package/dist/memory/brain-stub-prune.js.map +1 -1
- package/dist/memory/brain-writer-handlers.d.ts +10 -1
- package/dist/memory/brain-writer-handlers.d.ts.map +1 -1
- package/dist/memory/brain-writer-handlers.js +23 -3
- package/dist/memory/brain-writer-handlers.js.map +1 -1
- package/dist/memory/brain-writer-thread.d.ts +11 -1
- package/dist/memory/brain-writer-thread.d.ts.map +1 -1
- package/dist/memory/brain-writer-thread.js +87 -20
- package/dist/memory/brain-writer-thread.js.map +1 -1
- package/dist/memory/brain-writer-worker.js +8 -1
- package/dist/memory/brain-writer-worker.js.map +1 -1
- package/dist/memory/decision-cross-link.d.ts +39 -0
- package/dist/memory/decision-cross-link.d.ts.map +1 -1
- package/dist/memory/decision-cross-link.js +316 -0
- package/dist/memory/decision-cross-link.js.map +1 -1
- package/dist/memory/decisions.d.ts +4 -1
- package/dist/memory/decisions.d.ts.map +1 -1
- package/dist/memory/decisions.js +7 -24
- package/dist/memory/decisions.js.map +1 -1
- package/dist/memory/dispatch-trace.d.ts +12 -5
- package/dist/memory/dispatch-trace.d.ts.map +1 -1
- package/dist/memory/dispatch-trace.js +17 -7
- package/dist/memory/dispatch-trace.js.map +1 -1
- package/dist/memory/eligibility.d.ts +51 -0
- package/dist/memory/eligibility.d.ts.map +1 -0
- package/dist/memory/eligibility.js +77 -0
- package/dist/memory/eligibility.js.map +1 -0
- package/dist/memory/engine-compat.d.ts +2 -0
- package/dist/memory/engine-compat.d.ts.map +1 -1
- package/dist/memory/engine-compat.js +10 -0
- package/dist/memory/engine-compat.js.map +1 -1
- package/dist/memory/extraction-gate.d.ts +21 -7
- package/dist/memory/extraction-gate.d.ts.map +1 -1
- package/dist/memory/extraction-gate.js +178 -216
- package/dist/memory/extraction-gate.js.map +1 -1
- package/dist/memory/graph-auto-populate.d.ts +38 -5
- package/dist/memory/graph-auto-populate.d.ts.map +1 -1
- package/dist/memory/graph-auto-populate.js +182 -60
- package/dist/memory/graph-auto-populate.js.map +1 -1
- package/dist/memory/graph-memory-bridge.d.ts +14 -1
- package/dist/memory/graph-memory-bridge.d.ts.map +1 -1
- package/dist/memory/graph-memory-bridge.js +66 -28
- package/dist/memory/graph-memory-bridge.js.map +1 -1
- package/dist/memory/index.d.ts +1 -19
- package/dist/memory/index.d.ts.map +1 -1
- package/dist/memory/index.js +1 -50
- package/dist/memory/index.js.map +1 -1
- package/dist/memory/learnings.d.ts.map +1 -1
- package/dist/memory/learnings.js +2 -23
- package/dist/memory/learnings.js.map +1 -1
- package/dist/memory/manifest-filter.d.ts +21 -0
- package/dist/memory/manifest-filter.d.ts.map +1 -0
- package/dist/memory/manifest-filter.js +51 -0
- package/dist/memory/manifest-filter.js.map +1 -0
- package/dist/memory/manifest-ingestion.d.ts +28 -30
- package/dist/memory/manifest-ingestion.d.ts.map +1 -1
- package/dist/memory/manifest-ingestion.js +104 -235
- package/dist/memory/manifest-ingestion.js.map +1 -1
- package/dist/memory/observer-reflector.d.ts +2 -0
- package/dist/memory/observer-reflector.d.ts.map +1 -1
- package/dist/memory/observer-reflector.js +6 -49
- package/dist/memory/observer-reflector.js.map +1 -1
- package/dist/memory/patterns.d.ts +10 -0
- package/dist/memory/patterns.d.ts.map +1 -1
- package/dist/memory/patterns.js +25 -33
- package/dist/memory/patterns.js.map +1 -1
- package/dist/memory/pipeline-manifest-sqlite.d.ts +72 -9
- package/dist/memory/pipeline-manifest-sqlite.d.ts.map +1 -1
- package/dist/memory/pipeline-manifest-sqlite.js +508 -412
- package/dist/memory/pipeline-manifest-sqlite.js.map +1 -1
- package/dist/memory/retrieval/build-retrieval-bundle.d.ts.map +1 -1
- package/dist/memory/retrieval/build-retrieval-bundle.js +16 -15
- package/dist/memory/retrieval/build-retrieval-bundle.js.map +1 -1
- package/dist/memory/retrieval/fetch.d.ts +7 -0
- package/dist/memory/retrieval/fetch.d.ts.map +1 -1
- package/dist/memory/retrieval/fetch.js +14 -11
- package/dist/memory/retrieval/fetch.js.map +1 -1
- package/dist/memory/retrieval/observe.d.ts +7 -1
- package/dist/memory/retrieval/observe.d.ts.map +1 -1
- package/dist/memory/retrieval/observe.js +58 -4
- package/dist/memory/retrieval/observe.js.map +1 -1
- package/dist/memory/retrieval/search.d.ts +14 -1
- package/dist/memory/retrieval/search.d.ts.map +1 -1
- package/dist/memory/retrieval/search.js +133 -69
- package/dist/memory/retrieval/search.js.map +1 -1
- package/dist/memory/retrieval/telemetry.d.ts +31 -0
- package/dist/memory/retrieval/telemetry.d.ts.map +1 -0
- package/dist/memory/retrieval/telemetry.js +53 -0
- package/dist/memory/retrieval/telemetry.js.map +1 -0
- package/dist/nexus/analyze-orchestrator.d.ts +41 -9
- package/dist/nexus/analyze-orchestrator.d.ts.map +1 -1
- package/dist/nexus/analyze-orchestrator.js +257 -58
- package/dist/nexus/analyze-orchestrator.js.map +1 -1
- package/dist/nexus/api-contracts.d.ts +2 -6
- package/dist/nexus/api-contracts.d.ts.map +1 -1
- package/dist/nexus/api-contracts.js +11 -2
- package/dist/nexus/api-contracts.js.map +1 -1
- package/dist/nexus/context.d.ts.map +1 -1
- package/dist/nexus/context.js +14 -37
- package/dist/nexus/context.js.map +1 -1
- package/dist/nexus/diff.d.ts.map +1 -1
- package/dist/nexus/diff.js +2 -1
- package/dist/nexus/diff.js.map +1 -1
- package/dist/nexus/identity.d.ts +29 -3
- package/dist/nexus/identity.d.ts.map +1 -1
- package/dist/nexus/identity.js +63 -12
- package/dist/nexus/identity.js.map +1 -1
- package/dist/nexus/impact.d.ts +21 -19
- package/dist/nexus/impact.d.ts.map +1 -1
- package/dist/nexus/impact.js +96 -192
- package/dist/nexus/impact.js.map +1 -1
- package/dist/nexus/knowledge.d.ts +80 -0
- package/dist/nexus/knowledge.d.ts.map +1 -0
- package/dist/nexus/knowledge.js +786 -0
- package/dist/nexus/knowledge.js.map +1 -0
- package/dist/nexus/living-brain.d.ts +8 -5
- package/dist/nexus/living-brain.d.ts.map +1 -1
- package/dist/nexus/living-brain.js +252 -188
- package/dist/nexus/living-brain.js.map +1 -1
- package/dist/nexus/registry.d.ts +11 -2
- package/dist/nexus/registry.d.ts.map +1 -1
- package/dist/nexus/registry.js +144 -128
- package/dist/nexus/registry.js.map +1 -1
- package/dist/nexus/source-roots.d.ts +16 -0
- package/dist/nexus/source-roots.d.ts.map +1 -0
- package/dist/nexus/source-roots.js +174 -0
- package/dist/nexus/source-roots.js.map +1 -0
- package/dist/nexus/task-evidence.d.ts +22 -0
- package/dist/nexus/task-evidence.d.ts.map +1 -0
- package/dist/nexus/task-evidence.js +210 -0
- package/dist/nexus/task-evidence.js.map +1 -0
- package/dist/nexus/tasks-bridge.d.ts +14 -7
- package/dist/nexus/tasks-bridge.d.ts.map +1 -1
- package/dist/nexus/tasks-bridge.js +81 -40
- package/dist/nexus/tasks-bridge.js.map +1 -1
- package/dist/orchestrate/lifecycle-ops.d.ts.map +1 -1
- package/dist/orchestrate/lifecycle-ops.js +170 -120
- package/dist/orchestrate/lifecycle-ops.js.map +1 -1
- package/dist/orchestrate/query-ops.d.ts +6 -7
- package/dist/orchestrate/query-ops.d.ts.map +1 -1
- package/dist/orchestrate/query-ops.js +509 -532
- package/dist/orchestrate/query-ops.js.map +1 -1
- package/dist/orchestration/bootstrap.d.ts +15 -1
- package/dist/orchestration/bootstrap.d.ts.map +1 -1
- package/dist/orchestration/bootstrap.js +21 -10
- package/dist/orchestration/bootstrap.js.map +1 -1
- package/dist/orchestration/index.d.ts +35 -3
- package/dist/orchestration/index.d.ts.map +1 -1
- package/dist/orchestration/index.js +64 -35
- package/dist/orchestration/index.js.map +1 -1
- package/dist/orchestration/lead-rollup.d.ts +24 -1
- package/dist/orchestration/lead-rollup.d.ts.map +1 -1
- package/dist/orchestration/lead-rollup.js +31 -11
- package/dist/orchestration/lead-rollup.js.map +1 -1
- package/dist/orchestration/spawn-prompt.d.ts.map +1 -1
- package/dist/orchestration/spawn-prompt.js +25 -45
- package/dist/orchestration/spawn-prompt.js.map +1 -1
- package/dist/orchestration/unblock.d.ts +15 -1
- package/dist/orchestration/unblock.d.ts.map +1 -1
- package/dist/orchestration/unblock.js +23 -12
- package/dist/orchestration/unblock.js.map +1 -1
- package/dist/orchestration/validate-spawn.d.ts.map +1 -1
- package/dist/orchestration/validate-spawn.js +3 -3
- package/dist/orchestration/validate-spawn.js.map +1 -1
- package/dist/orchestration/waves.d.ts +29 -13
- package/dist/orchestration/waves.d.ts.map +1 -1
- package/dist/orchestration/waves.js +43 -55
- package/dist/orchestration/waves.js.map +1 -1
- package/dist/paths.d.ts +17 -163
- package/dist/paths.d.ts.map +1 -1
- package/dist/paths.js +145 -496
- package/dist/paths.js.map +1 -1
- package/dist/project-info.d.ts +2 -11
- package/dist/project-info.d.ts.map +1 -1
- package/dist/project-info.js +3 -31
- package/dist/project-info.js.map +1 -1
- package/dist/project-scope.d.ts +239 -0
- package/dist/project-scope.d.ts.map +1 -0
- package/dist/project-scope.js +473 -0
- package/dist/project-scope.js.map +1 -0
- package/dist/release/pr-evidence.d.ts +23 -4
- package/dist/release/pr-evidence.d.ts.map +1 -1
- package/dist/release/pr-evidence.js +65 -9
- package/dist/release/pr-evidence.js.map +1 -1
- package/dist/render/nexus/graph/index.d.ts.map +1 -1
- package/dist/render/nexus/graph/index.js +4 -2
- package/dist/render/nexus/graph/index.js.map +1 -1
- package/dist/render/orchestration/audit-reconstruct.d.ts +11 -0
- package/dist/render/orchestration/audit-reconstruct.d.ts.map +1 -1
- package/dist/render/orchestration/audit-reconstruct.js +21 -6
- package/dist/render/orchestration/audit-reconstruct.js.map +1 -1
- package/dist/render/tasks/show.js +2 -1
- package/dist/render/tasks/show.js.map +1 -1
- package/dist/resources/spawn-wrapper.d.ts +82 -14
- package/dist/resources/spawn-wrapper.d.ts.map +1 -1
- package/dist/resources/spawn-wrapper.js +585 -27
- package/dist/resources/spawn-wrapper.js.map +1 -1
- package/dist/selfimprove/fix-gen.js +4055 -1734
- package/dist/selfimprove/fix-gen.js.map +3 -3
- package/dist/sequence/index.d.ts.map +1 -1
- package/dist/sequence/index.js +76 -68
- package/dist/sequence/index.js.map +1 -1
- package/dist/session/canon-lint.d.ts +24 -3
- package/dist/session/canon-lint.d.ts.map +1 -1
- package/dist/session/canon-lint.js +72 -31
- package/dist/session/canon-lint.js.map +1 -1
- package/dist/session/engine-ops.d.ts +1 -4
- package/dist/session/engine-ops.d.ts.map +1 -1
- package/dist/session/engine-ops.js +42 -6
- package/dist/session/engine-ops.js.map +1 -1
- package/dist/sessions/briefing.d.ts +11 -1
- package/dist/sessions/briefing.d.ts.map +1 -1
- package/dist/sessions/briefing.js +76 -8
- package/dist/sessions/briefing.js.map +1 -1
- package/dist/sessions/handoff.d.ts +3 -1
- package/dist/sessions/handoff.d.ts.map +1 -1
- package/dist/sessions/handoff.js +2 -0
- package/dist/sessions/handoff.js.map +1 -1
- package/dist/shutdown-deadline.d.ts +24 -33
- package/dist/shutdown-deadline.d.ts.map +1 -1
- package/dist/shutdown-deadline.js +71 -14
- package/dist/shutdown-deadline.js.map +1 -1
- package/dist/shutdown.d.ts +21 -10
- package/dist/shutdown.d.ts.map +1 -1
- package/dist/shutdown.js +89 -42
- package/dist/shutdown.js.map +1 -1
- package/dist/skills/index.d.ts +1 -1
- package/dist/skills/index.d.ts.map +1 -1
- package/dist/skills/index.js +1 -1
- package/dist/skills/index.js.map +1 -1
- package/dist/skills/orchestrator/validator.d.ts +87 -20
- package/dist/skills/orchestrator/validator.d.ts.map +1 -1
- package/dist/skills/orchestrator/validator.js +330 -241
- package/dist/skills/orchestrator/validator.js.map +1 -1
- package/dist/store/agent-resolver.d.ts.map +1 -1
- package/dist/store/agent-resolver.js +16 -9
- package/dist/store/agent-resolver.js.map +1 -1
- package/dist/store/attachment-store.d.ts +4 -1
- package/dist/store/attachment-store.d.ts.map +1 -1
- package/dist/store/attachment-store.js +20 -3
- package/dist/store/attachment-store.js.map +1 -1
- package/dist/store/background-jobs.d.ts +426 -105
- package/dist/store/background-jobs.d.ts.map +1 -1
- package/dist/store/background-jobs.js +1208 -252
- package/dist/store/background-jobs.js.map +1 -1
- package/dist/store/background-ops.d.ts +145 -8
- package/dist/store/background-ops.d.ts.map +1 -1
- package/dist/store/background-ops.js +459 -15
- package/dist/store/background-ops.js.map +1 -1
- package/dist/store/backup-inspect.d.ts +48 -4
- package/dist/store/backup-inspect.d.ts.map +1 -1
- package/dist/store/backup-inspect.js +384 -4
- package/dist/store/backup-inspect.js.map +1 -1
- package/dist/store/converters.d.ts.map +1 -1
- package/dist/store/converters.js +19 -2
- package/dist/store/converters.js.map +1 -1
- package/dist/store/data-accessor.d.ts +9 -1
- package/dist/store/data-accessor.d.ts.map +1 -1
- package/dist/store/data-accessor.js +22 -5
- package/dist/store/data-accessor.js.map +1 -1
- package/dist/store/data-safety-central.d.ts.map +1 -1
- package/dist/store/data-safety-central.js +272 -232
- package/dist/store/data-safety-central.js.map +1 -1
- package/dist/store/db-helpers.d.ts +19 -3
- package/dist/store/db-helpers.d.ts.map +1 -1
- package/dist/store/db-helpers.js +42 -58
- package/dist/store/db-helpers.js.map +1 -1
- package/dist/store/dual-scope-db.d.ts +63 -4
- package/dist/store/dual-scope-db.d.ts.map +1 -1
- package/dist/store/dual-scope-db.js +166 -19
- package/dist/store/dual-scope-db.js.map +1 -1
- package/dist/store/exodus/archive.d.ts +21 -7
- package/dist/store/exodus/archive.d.ts.map +1 -1
- package/dist/store/exodus/archive.js +60 -18
- package/dist/store/exodus/archive.js.map +1 -1
- package/dist/store/exodus/migrate.d.ts.map +1 -1
- package/dist/store/exodus/migrate.js +27 -23
- package/dist/store/exodus/migrate.js.map +1 -1
- package/dist/store/exodus/on-open.d.ts +16 -27
- package/dist/store/exodus/on-open.d.ts.map +1 -1
- package/dist/store/exodus/on-open.js +121 -169
- package/dist/store/exodus/on-open.js.map +1 -1
- package/dist/store/exodus/recovery.d.ts +90 -0
- package/dist/store/exodus/recovery.d.ts.map +1 -0
- package/dist/store/exodus/recovery.js +434 -0
- package/dist/store/exodus/recovery.js.map +1 -0
- package/dist/store/exodus/types.d.ts +15 -0
- package/dist/store/exodus/types.d.ts.map +1 -1
- package/dist/store/memory-accessor.d.ts +52 -3
- package/dist/store/memory-accessor.d.ts.map +1 -1
- package/dist/store/memory-accessor.js +122 -7
- package/dist/store/memory-accessor.js.map +1 -1
- package/dist/store/nexus-sqlite.d.ts +24 -6
- package/dist/store/nexus-sqlite.d.ts.map +1 -1
- package/dist/store/nexus-sqlite.js +39 -20
- package/dist/store/nexus-sqlite.js.map +1 -1
- package/dist/store/ports/domain-binding.d.ts.map +1 -1
- package/dist/store/ports/domain-binding.js +27 -5
- package/dist/store/ports/domain-binding.js.map +1 -1
- package/dist/store/schema/background-jobs.d.ts +181 -2
- package/dist/store/schema/background-jobs.d.ts.map +1 -1
- package/dist/store/schema/background-jobs.js +27 -3
- package/dist/store/schema/background-jobs.js.map +1 -1
- package/dist/store/schema/cleo-global/skills.d.ts +2 -2
- package/dist/store/schema/cleo-project/lifecycle.d.ts +2 -2
- package/dist/store/schema/cleo-project/runtime.d.ts +1 -1
- package/dist/store/schema/cleo-project/tasks-core.d.ts +1 -1
- package/dist/store/schema/lifecycle.d.ts +2 -2
- package/dist/store/schema/skills-schema.d.ts +2 -2
- package/dist/store/schema/tasks.d.ts +1 -1
- package/dist/store/session-store.d.ts +3 -1
- package/dist/store/session-store.d.ts.map +1 -1
- package/dist/store/session-store.js +252 -207
- package/dist/store/session-store.js.map +1 -1
- package/dist/store/sqlite-data-accessor.d.ts +39 -4
- package/dist/store/sqlite-data-accessor.d.ts.map +1 -1
- package/dist/store/sqlite-data-accessor.js +689 -527
- package/dist/store/sqlite-data-accessor.js.map +1 -1
- package/dist/store/tasks-sqlite.d.ts +3 -1
- package/dist/store/tasks-sqlite.d.ts.map +1 -1
- package/dist/store/tasks-sqlite.js +309 -279
- package/dist/store/tasks-sqlite.js.map +1 -1
- package/dist/store/validation-schemas.d.ts +8 -8
- package/dist/store/writer-lease.d.ts +80 -8
- package/dist/store/writer-lease.d.ts.map +1 -1
- package/dist/store/writer-lease.js +503 -134
- package/dist/store/writer-lease.js.map +1 -1
- package/dist/system/audit.d.ts +2 -1
- package/dist/system/audit.d.ts.map +1 -1
- package/dist/system/audit.js +118 -71
- package/dist/system/audit.js.map +1 -1
- package/dist/system/backup.d.ts.map +1 -1
- package/dist/system/backup.js +5 -5
- package/dist/system/backup.js.map +1 -1
- package/dist/system/inject-generate.js +1 -1
- package/dist/task-work/index.d.ts +3 -0
- package/dist/task-work/index.d.ts.map +1 -1
- package/dist/task-work/index.js +34 -37
- package/dist/task-work/index.js.map +1 -1
- package/dist/tasks/ac-coverage-gate.d.ts.map +1 -1
- package/dist/tasks/ac-coverage-gate.js +4 -0
- package/dist/tasks/ac-coverage-gate.js.map +1 -1
- package/dist/tasks/ac-immutability.d.ts +7 -4
- package/dist/tasks/ac-immutability.d.ts.map +1 -1
- package/dist/tasks/ac-immutability.js +19 -17
- package/dist/tasks/ac-immutability.js.map +1 -1
- package/dist/tasks/ac-table.d.ts +5 -5
- package/dist/tasks/ac-table.d.ts.map +1 -1
- package/dist/tasks/ac-table.js +26 -12
- package/dist/tasks/ac-table.js.map +1 -1
- package/dist/tasks/acceptance-input.d.ts +52 -0
- package/dist/tasks/acceptance-input.d.ts.map +1 -0
- package/dist/tasks/acceptance-input.js +259 -0
- package/dist/tasks/acceptance-input.js.map +1 -0
- package/dist/tasks/add.d.ts +67 -29
- package/dist/tasks/add.d.ts.map +1 -1
- package/dist/tasks/add.js +255 -97
- package/dist/tasks/add.js.map +1 -1
- package/dist/tasks/complete.d.ts +16 -1
- package/dist/tasks/complete.d.ts.map +1 -1
- package/dist/tasks/complete.js +152 -55
- package/dist/tasks/complete.js.map +1 -1
- package/dist/tasks/decompose.d.ts +103 -0
- package/dist/tasks/decompose.d.ts.map +1 -0
- package/dist/tasks/decompose.js +224 -0
- package/dist/tasks/decompose.js.map +1 -0
- package/dist/tasks/delete.d.ts +5 -8
- package/dist/tasks/delete.d.ts.map +1 -1
- package/dist/tasks/delete.js +4 -3
- package/dist/tasks/delete.js.map +1 -1
- package/dist/tasks/dependency-check.d.ts +59 -0
- package/dist/tasks/dependency-check.d.ts.map +1 -1
- package/dist/tasks/dependency-check.js +73 -0
- package/dist/tasks/dependency-check.js.map +1 -1
- package/dist/tasks/deps-ready.d.ts +13 -1
- package/dist/tasks/deps-ready.d.ts.map +1 -1
- package/dist/tasks/deps-ready.js +16 -5
- package/dist/tasks/deps-ready.js.map +1 -1
- package/dist/tasks/engine-converters.d.ts +7 -0
- package/dist/tasks/engine-converters.d.ts.map +1 -1
- package/dist/tasks/engine-converters.js +8 -1
- package/dist/tasks/engine-converters.js.map +1 -1
- package/dist/tasks/evidence.d.ts +88 -27
- package/dist/tasks/evidence.d.ts.map +1 -1
- package/dist/tasks/evidence.js +344 -43
- package/dist/tasks/evidence.js.map +1 -1
- package/dist/tasks/find.d.ts +16 -12
- package/dist/tasks/find.d.ts.map +1 -1
- package/dist/tasks/find.js +107 -93
- package/dist/tasks/find.js.map +1 -1
- package/dist/tasks/gate-preview.d.ts +73 -0
- package/dist/tasks/gate-preview.d.ts.map +1 -0
- package/dist/tasks/gate-preview.js +119 -0
- package/dist/tasks/gate-preview.js.map +1 -0
- package/dist/tasks/gate-runner.d.ts +88 -14
- package/dist/tasks/gate-runner.d.ts.map +1 -1
- package/dist/tasks/gate-runner.js +719 -250
- package/dist/tasks/gate-runner.js.map +1 -1
- package/dist/tasks/hierarchy-policy.d.ts +8 -0
- package/dist/tasks/hierarchy-policy.d.ts.map +1 -1
- package/dist/tasks/hierarchy-policy.js +9 -2
- package/dist/tasks/hierarchy-policy.js.map +1 -1
- package/dist/tasks/hierarchy.d.ts +67 -1
- package/dist/tasks/hierarchy.d.ts.map +1 -1
- package/dist/tasks/hierarchy.js +81 -1
- package/dist/tasks/hierarchy.js.map +1 -1
- package/dist/tasks/index.d.ts +3 -0
- package/dist/tasks/index.d.ts.map +1 -1
- package/dist/tasks/index.js +3 -0
- package/dist/tasks/index.js.map +1 -1
- package/dist/tasks/infer-add-params.d.ts +1 -20
- package/dist/tasks/infer-add-params.d.ts.map +1 -1
- package/dist/tasks/infer-add-params.js +2 -203
- package/dist/tasks/infer-add-params.js.map +1 -1
- package/dist/tasks/list.d.ts +7 -2
- package/dist/tasks/list.d.ts.map +1 -1
- package/dist/tasks/list.js +24 -36
- package/dist/tasks/list.js.map +1 -1
- package/dist/tasks/ops.d.ts +6 -61
- package/dist/tasks/ops.d.ts.map +1 -1
- package/dist/tasks/ops.js +5 -53
- package/dist/tasks/ops.js.map +1 -1
- package/dist/tasks/plan.d.ts +15 -1
- package/dist/tasks/plan.d.ts.map +1 -1
- package/dist/tasks/plan.js +23 -25
- package/dist/tasks/plan.js.map +1 -1
- package/dist/tasks/population.d.ts +46 -0
- package/dist/tasks/population.d.ts.map +1 -0
- package/dist/tasks/population.js +88 -0
- package/dist/tasks/population.js.map +1 -0
- package/dist/tasks/reconcile-scope.d.ts +157 -0
- package/dist/tasks/reconcile-scope.d.ts.map +1 -0
- package/dist/tasks/reconcile-scope.js +242 -0
- package/dist/tasks/reconcile-scope.js.map +1 -0
- package/dist/tasks/req.d.ts +51 -12
- package/dist/tasks/req.d.ts.map +1 -1
- package/dist/tasks/req.js +125 -69
- package/dist/tasks/req.js.map +1 -1
- package/dist/tasks/session-scope.d.ts +6 -31
- package/dist/tasks/session-scope.d.ts.map +1 -1
- package/dist/tasks/session-scope.js +3 -17
- package/dist/tasks/session-scope.js.map +1 -1
- package/dist/tasks/severity-attestation.d.ts +39 -10
- package/dist/tasks/severity-attestation.d.ts.map +1 -1
- package/dist/tasks/severity-attestation.js +75 -36
- package/dist/tasks/severity-attestation.js.map +1 -1
- package/dist/tasks/task-next.d.ts +5 -0
- package/dist/tasks/task-next.d.ts.map +1 -1
- package/dist/tasks/task-next.js +10 -9
- package/dist/tasks/task-next.js.map +1 -1
- package/dist/tasks/task-reparent.d.ts.map +1 -1
- package/dist/tasks/task-reparent.js +3 -13
- package/dist/tasks/task-reparent.js.map +1 -1
- package/dist/tasks/update.d.ts +20 -43
- package/dist/tasks/update.d.ts.map +1 -1
- package/dist/tasks/update.js +64 -49
- package/dist/tasks/update.js.map +1 -1
- package/dist/validation/engine-ops.d.ts +17 -1
- package/dist/validation/engine-ops.d.ts.map +1 -1
- package/dist/validation/engine-ops.js +149 -11
- package/dist/validation/engine-ops.js.map +1 -1
- package/dist/workgraph/reparent-preflight.d.ts +1 -1
- package/dist/workgraph/reparent-preflight.d.ts.map +1 -1
- package/dist/workgraph/reparent-preflight.js +6 -1
- package/dist/workgraph/reparent-preflight.js.map +1 -1
- package/dist/workgraph/scaffold-apply.d.ts.map +1 -1
- package/dist/workgraph/scaffold-apply.js +81 -76
- package/dist/workgraph/scaffold-apply.js.map +1 -1
- package/dist/workgraph/structure-validation.d.ts +1 -1
- package/dist/workgraph/structure-validation.d.ts.map +1 -1
- package/dist/workgraph/structure-validation.js +6 -1
- package/dist/workgraph/structure-validation.js.map +1 -1
- package/migrations/drizzle-tasks/20260919180000_t12263-job-ownership-fences/migration.sql +34 -0
- package/migrations/drizzle-tasks/20260919184500_t12265-durable-pending-proposals/migration.sql +4 -0
- package/package.json +18 -13
- package/schemas/config.schema.json +2 -2
- package/schemas/project-context.schema.json +12 -0
- package/templates/CLEO-INJECTION.md +114 -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 '../
|
|
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
|
|
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
|
|
91
|
-
|
|
92
|
-
const accessor = await getTaskAccessor(
|
|
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
|
|
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
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
const
|
|
116
|
-
if (
|
|
117
|
-
|
|
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
|
-
|
|
120
|
-
const status =
|
|
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
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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
|
-
|
|
261
|
-
|
|
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
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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
|
-
//
|
|
313
|
-
const
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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 (
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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 (
|
|
335
|
-
reason = 'all
|
|
377
|
+
else if (blockedCount === all.length) {
|
|
378
|
+
reason = 'all children have unmet dependencies';
|
|
336
379
|
}
|
|
337
380
|
else {
|
|
338
|
-
reason = 'no
|
|
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(
|
|
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:
|
|
349
|
-
total:
|
|
350
|
-
via: '
|
|
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
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
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:
|
|
427
|
-
|
|
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)`.
|
|
455
|
-
*
|
|
456
|
-
*
|
|
457
|
-
*
|
|
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
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
//
|
|
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
|
-
|
|
506
|
-
|
|
507
|
-
|
|
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
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
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
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
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
|
-
|
|
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 ?
|
|
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
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
taskCount = tasks.
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
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
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
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
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
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
|
-
|
|
717
|
-
const
|
|
718
|
-
const
|
|
719
|
-
|
|
720
|
-
|
|
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
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
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:
|
|
737
|
+
reason: 'ready',
|
|
752
738
|
});
|
|
753
|
-
continue;
|
|
754
739
|
}
|
|
755
|
-
//
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
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
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
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';
|