@soleri/core 9.0.4 → 9.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/data/flows/build.flow.yaml +8 -9
- package/data/flows/deliver.flow.yaml +9 -10
- package/data/flows/design.flow.yaml +3 -4
- package/data/flows/enhance.flow.yaml +5 -6
- package/data/flows/explore.flow.yaml +3 -4
- package/data/flows/fix.flow.yaml +5 -6
- package/data/flows/plan.flow.yaml +4 -5
- package/data/flows/review.flow.yaml +3 -4
- package/dist/brain/intelligence.d.ts +27 -0
- package/dist/brain/intelligence.d.ts.map +1 -1
- package/dist/brain/intelligence.js +160 -14
- package/dist/brain/intelligence.js.map +1 -1
- package/dist/brain/learning-radar.d.ts +4 -0
- package/dist/brain/learning-radar.d.ts.map +1 -1
- package/dist/brain/learning-radar.js +20 -1
- package/dist/brain/learning-radar.js.map +1 -1
- package/dist/brain/strength-scorer.d.ts +31 -0
- package/dist/brain/strength-scorer.d.ts.map +1 -0
- package/dist/brain/strength-scorer.js +264 -0
- package/dist/brain/strength-scorer.js.map +1 -0
- package/dist/chat/agent-loop.d.ts.map +1 -1
- package/dist/chat/agent-loop.js +2 -0
- package/dist/chat/agent-loop.js.map +1 -1
- package/dist/chat/notifications.d.ts.map +1 -1
- package/dist/chat/notifications.js +2 -0
- package/dist/chat/notifications.js.map +1 -1
- package/dist/claudemd/compose.js +1 -1
- package/dist/claudemd/compose.js.map +1 -1
- package/dist/control/intent-router.d.ts.map +1 -1
- package/dist/control/intent-router.js +12 -4
- package/dist/control/intent-router.js.map +1 -1
- package/dist/curator/contradiction-detector.d.ts +27 -0
- package/dist/curator/contradiction-detector.d.ts.map +1 -0
- package/dist/curator/contradiction-detector.js +62 -0
- package/dist/curator/contradiction-detector.js.map +1 -0
- package/dist/curator/curator.d.ts +3 -4
- package/dist/curator/curator.d.ts.map +1 -1
- package/dist/curator/curator.js +94 -453
- package/dist/curator/curator.js.map +1 -1
- package/dist/curator/duplicate-detector.d.ts +14 -0
- package/dist/curator/duplicate-detector.d.ts.map +1 -0
- package/dist/curator/duplicate-detector.js +77 -0
- package/dist/curator/duplicate-detector.js.map +1 -0
- package/dist/curator/health-audit.d.ts +15 -0
- package/dist/curator/health-audit.d.ts.map +1 -0
- package/dist/curator/health-audit.js +97 -0
- package/dist/curator/health-audit.js.map +1 -0
- package/dist/curator/metadata-enricher.d.ts +17 -0
- package/dist/curator/metadata-enricher.d.ts.map +1 -0
- package/dist/curator/metadata-enricher.js +60 -0
- package/dist/curator/metadata-enricher.js.map +1 -0
- package/dist/curator/schema.d.ts +7 -0
- package/dist/curator/schema.d.ts.map +1 -0
- package/dist/curator/schema.js +62 -0
- package/dist/curator/schema.js.map +1 -0
- package/dist/curator/tag-manager.d.ts +36 -0
- package/dist/curator/tag-manager.d.ts.map +1 -0
- package/dist/curator/tag-manager.js +78 -0
- package/dist/curator/tag-manager.js.map +1 -0
- package/dist/engine/bin/soleri-engine.js +24 -3
- package/dist/engine/bin/soleri-engine.js.map +1 -1
- package/dist/engine/core-ops.d.ts.map +1 -1
- package/dist/engine/core-ops.js +23 -8
- package/dist/engine/core-ops.js.map +1 -1
- package/dist/engine/module-manifest.d.ts.map +1 -1
- package/dist/engine/module-manifest.js +42 -2
- package/dist/engine/module-manifest.js.map +1 -1
- package/dist/engine/register-engine.d.ts.map +1 -1
- package/dist/engine/register-engine.js +50 -2
- package/dist/engine/register-engine.js.map +1 -1
- package/dist/errors/retry.d.ts.map +1 -1
- package/dist/errors/retry.js +2 -0
- package/dist/errors/retry.js.map +1 -1
- package/dist/facades/types.d.ts +1 -1
- package/dist/flows/chain-types.d.ts +18 -18
- package/dist/flows/gate-evaluator.d.ts.map +1 -1
- package/dist/flows/gate-evaluator.js +22 -0
- package/dist/flows/gate-evaluator.js.map +1 -1
- package/dist/flows/types.d.ts +157 -28
- package/dist/flows/types.d.ts.map +1 -1
- package/dist/flows/types.js +4 -0
- package/dist/flows/types.js.map +1 -1
- package/dist/index.d.ts +10 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/dist/intake/intake-pipeline.d.ts.map +1 -1
- package/dist/intake/intake-pipeline.js +1 -0
- package/dist/intake/intake-pipeline.js.map +1 -1
- package/dist/intake/text-ingester.d.ts.map +1 -1
- package/dist/intake/text-ingester.js +2 -0
- package/dist/intake/text-ingester.js.map +1 -1
- package/dist/llm/key-pool.d.ts +1 -1
- package/dist/llm/key-pool.d.ts.map +1 -1
- package/dist/llm/key-pool.js +3 -4
- package/dist/llm/key-pool.js.map +1 -1
- package/dist/llm/utils.d.ts.map +1 -1
- package/dist/llm/utils.js +2 -0
- package/dist/llm/utils.js.map +1 -1
- package/dist/migrations/migration-runner.test-helpers.d.ts +13 -0
- package/dist/migrations/migration-runner.test-helpers.d.ts.map +1 -0
- package/dist/migrations/migration-runner.test-helpers.js +47 -0
- package/dist/migrations/migration-runner.test-helpers.js.map +1 -0
- package/dist/operator/operator-profile.d.ts +44 -0
- package/dist/operator/operator-profile.d.ts.map +1 -0
- package/dist/operator/operator-profile.js +383 -0
- package/dist/operator/operator-profile.js.map +1 -0
- package/dist/operator/operator-signals.d.ts +45 -0
- package/dist/operator/operator-signals.d.ts.map +1 -0
- package/dist/operator/operator-signals.js +228 -0
- package/dist/operator/operator-signals.js.map +1 -0
- package/dist/operator/operator-types.d.ts +360 -0
- package/dist/operator/operator-types.d.ts.map +1 -0
- package/dist/operator/operator-types.js +24 -0
- package/dist/operator/operator-types.js.map +1 -0
- package/dist/packs/types.d.ts +27 -27
- package/dist/paths.d.ts +40 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +98 -0
- package/dist/paths.js.map +1 -0
- package/dist/persistence/index.d.ts +1 -1
- package/dist/persistence/index.d.ts.map +1 -1
- package/dist/persistence/index.js +1 -1
- package/dist/persistence/index.js.map +1 -1
- package/dist/persistence/sqlite-provider.d.ts +2 -0
- package/dist/persistence/sqlite-provider.d.ts.map +1 -1
- package/dist/persistence/sqlite-provider.js +8 -5
- package/dist/persistence/sqlite-provider.js.map +1 -1
- package/dist/planning/evidence-collector.d.ts +13 -1
- package/dist/planning/evidence-collector.d.ts.map +1 -1
- package/dist/planning/evidence-collector.js +33 -0
- package/dist/planning/evidence-collector.js.map +1 -1
- package/dist/planning/gap-analysis.d.ts +5 -4
- package/dist/planning/gap-analysis.d.ts.map +1 -1
- package/dist/planning/gap-analysis.js +7 -341
- package/dist/planning/gap-analysis.js.map +1 -1
- package/dist/planning/gap-passes.d.ts +19 -0
- package/dist/planning/gap-passes.d.ts.map +1 -0
- package/dist/planning/gap-passes.js +174 -0
- package/dist/planning/gap-passes.js.map +1 -0
- package/dist/planning/gap-patterns.d.ts +29 -0
- package/dist/planning/gap-patterns.d.ts.map +1 -0
- package/dist/planning/gap-patterns.js +175 -0
- package/dist/planning/gap-patterns.js.map +1 -0
- package/dist/planning/gap-types.d.ts +1 -1
- package/dist/planning/gap-types.d.ts.map +1 -1
- package/dist/planning/gap-types.js +1 -0
- package/dist/planning/gap-types.js.map +1 -1
- package/dist/planning/github-projection.d.ts +122 -0
- package/dist/planning/github-projection.d.ts.map +1 -0
- package/dist/planning/github-projection.js +313 -0
- package/dist/planning/github-projection.js.map +1 -0
- package/dist/planning/impact-analyzer.d.ts +26 -0
- package/dist/planning/impact-analyzer.d.ts.map +1 -0
- package/dist/planning/impact-analyzer.js +201 -0
- package/dist/planning/impact-analyzer.js.map +1 -0
- package/dist/planning/plan-lifecycle.d.ts +136 -0
- package/dist/planning/plan-lifecycle.d.ts.map +1 -0
- package/dist/planning/plan-lifecycle.js +309 -0
- package/dist/planning/plan-lifecycle.js.map +1 -0
- package/dist/planning/planner-types.d.ts +202 -0
- package/dist/planning/planner-types.d.ts.map +1 -0
- package/dist/planning/planner-types.js +6 -0
- package/dist/planning/planner-types.js.map +1 -0
- package/dist/planning/planner.d.ts +31 -383
- package/dist/planning/planner.d.ts.map +1 -1
- package/dist/planning/planner.js +151 -832
- package/dist/planning/planner.js.map +1 -1
- package/dist/planning/rationalization-detector.d.ts +32 -0
- package/dist/planning/rationalization-detector.d.ts.map +1 -0
- package/dist/planning/rationalization-detector.js +89 -0
- package/dist/planning/rationalization-detector.js.map +1 -0
- package/dist/planning/reconciliation-engine.d.ts +47 -0
- package/dist/planning/reconciliation-engine.d.ts.map +1 -0
- package/dist/planning/reconciliation-engine.js +128 -0
- package/dist/planning/reconciliation-engine.js.map +1 -0
- package/dist/planning/task-verifier.d.ts +85 -0
- package/dist/planning/task-verifier.d.ts.map +1 -0
- package/dist/planning/task-verifier.js +235 -0
- package/dist/planning/task-verifier.js.map +1 -0
- package/dist/plugins/types.d.ts +4 -4
- package/dist/runtime/admin-ops.d.ts +2 -2
- package/dist/runtime/admin-ops.d.ts.map +1 -1
- package/dist/runtime/admin-ops.js +44 -17
- package/dist/runtime/admin-ops.js.map +1 -1
- package/dist/runtime/admin-setup-ops.d.ts.map +1 -1
- package/dist/runtime/admin-setup-ops.js +22 -46
- package/dist/runtime/admin-setup-ops.js.map +1 -1
- package/dist/runtime/archive-ops.d.ts +10 -0
- package/dist/runtime/archive-ops.d.ts.map +1 -0
- package/dist/runtime/archive-ops.js +310 -0
- package/dist/runtime/archive-ops.js.map +1 -0
- package/dist/runtime/branching-ops.d.ts +12 -0
- package/dist/runtime/branching-ops.d.ts.map +1 -0
- package/dist/runtime/branching-ops.js +100 -0
- package/dist/runtime/branching-ops.js.map +1 -0
- package/dist/runtime/capture-ops.d.ts.map +1 -1
- package/dist/runtime/capture-ops.js +42 -7
- package/dist/runtime/capture-ops.js.map +1 -1
- package/dist/runtime/claude-md-helpers.js +1 -1
- package/dist/runtime/claude-md-helpers.js.map +1 -1
- package/dist/runtime/context-health.d.ts +31 -0
- package/dist/runtime/context-health.d.ts.map +1 -0
- package/dist/runtime/context-health.js +57 -0
- package/dist/runtime/context-health.js.map +1 -0
- package/dist/runtime/facades/archive-facade.d.ts +10 -0
- package/dist/runtime/facades/archive-facade.d.ts.map +1 -0
- package/dist/runtime/facades/archive-facade.js +11 -0
- package/dist/runtime/facades/archive-facade.js.map +1 -0
- package/dist/runtime/facades/brain-facade.d.ts.map +1 -1
- package/dist/runtime/facades/brain-facade.js +2 -0
- package/dist/runtime/facades/brain-facade.js.map +1 -1
- package/dist/runtime/facades/branching-facade.d.ts +7 -0
- package/dist/runtime/facades/branching-facade.d.ts.map +1 -0
- package/dist/runtime/facades/branching-facade.js +8 -0
- package/dist/runtime/facades/branching-facade.js.map +1 -0
- package/dist/runtime/facades/chat-facade.d.ts +7 -0
- package/dist/runtime/facades/chat-facade.d.ts.map +1 -1
- package/dist/runtime/facades/chat-facade.js +15 -800
- package/dist/runtime/facades/chat-facade.js.map +1 -1
- package/dist/runtime/facades/chat-service-ops.d.ts +9 -0
- package/dist/runtime/facades/chat-service-ops.d.ts.map +1 -0
- package/dist/runtime/facades/chat-service-ops.js +332 -0
- package/dist/runtime/facades/chat-service-ops.js.map +1 -0
- package/dist/runtime/facades/chat-session-ops.d.ts +8 -0
- package/dist/runtime/facades/chat-session-ops.d.ts.map +1 -0
- package/dist/runtime/facades/chat-session-ops.js +136 -0
- package/dist/runtime/facades/chat-session-ops.js.map +1 -0
- package/dist/runtime/facades/chat-state.d.ts +31 -0
- package/dist/runtime/facades/chat-state.d.ts.map +1 -0
- package/dist/runtime/facades/chat-state.js +32 -0
- package/dist/runtime/facades/chat-state.js.map +1 -0
- package/dist/runtime/facades/chat-transport-ops.d.ts +9 -0
- package/dist/runtime/facades/chat-transport-ops.d.ts.map +1 -0
- package/dist/runtime/facades/chat-transport-ops.js +337 -0
- package/dist/runtime/facades/chat-transport-ops.js.map +1 -0
- package/dist/runtime/facades/control-facade.d.ts.map +1 -1
- package/dist/runtime/facades/control-facade.js +4 -1
- package/dist/runtime/facades/control-facade.js.map +1 -1
- package/dist/runtime/facades/index.d.ts.map +1 -1
- package/dist/runtime/facades/index.js +48 -0
- package/dist/runtime/facades/index.js.map +1 -1
- package/dist/runtime/facades/intake-facade.d.ts +9 -0
- package/dist/runtime/facades/intake-facade.d.ts.map +1 -0
- package/dist/runtime/facades/intake-facade.js +11 -0
- package/dist/runtime/facades/intake-facade.js.map +1 -0
- package/dist/runtime/facades/links-facade.d.ts +9 -0
- package/dist/runtime/facades/links-facade.d.ts.map +1 -0
- package/dist/runtime/facades/links-facade.js +10 -0
- package/dist/runtime/facades/links-facade.js.map +1 -0
- package/dist/runtime/facades/memory-facade.d.ts.map +1 -1
- package/dist/runtime/facades/memory-facade.js +75 -6
- package/dist/runtime/facades/memory-facade.js.map +1 -1
- package/dist/runtime/facades/operator-facade.d.ts +8 -0
- package/dist/runtime/facades/operator-facade.d.ts.map +1 -0
- package/dist/runtime/facades/operator-facade.js +220 -0
- package/dist/runtime/facades/operator-facade.js.map +1 -0
- package/dist/runtime/facades/orchestrate-facade.js +3 -3
- package/dist/runtime/facades/orchestrate-facade.js.map +1 -1
- package/dist/runtime/facades/plan-facade.d.ts.map +1 -1
- package/dist/runtime/facades/plan-facade.js +42 -6
- package/dist/runtime/facades/plan-facade.js.map +1 -1
- package/dist/runtime/facades/review-facade.d.ts +7 -0
- package/dist/runtime/facades/review-facade.d.ts.map +1 -0
- package/dist/runtime/facades/review-facade.js +8 -0
- package/dist/runtime/facades/review-facade.js.map +1 -0
- package/dist/runtime/facades/sync-facade.d.ts +7 -0
- package/dist/runtime/facades/sync-facade.d.ts.map +1 -0
- package/dist/runtime/facades/sync-facade.js +8 -0
- package/dist/runtime/facades/sync-facade.js.map +1 -0
- package/dist/runtime/facades/tier-facade.d.ts +7 -0
- package/dist/runtime/facades/tier-facade.d.ts.map +1 -0
- package/dist/runtime/facades/tier-facade.js +8 -0
- package/dist/runtime/facades/tier-facade.js.map +1 -0
- package/dist/runtime/facades/vault-facade.d.ts +12 -1
- package/dist/runtime/facades/vault-facade.d.ts.map +1 -1
- package/dist/runtime/facades/vault-facade.js +55 -251
- package/dist/runtime/facades/vault-facade.js.map +1 -1
- package/dist/runtime/github-integration.d.ts +49 -0
- package/dist/runtime/github-integration.d.ts.map +1 -0
- package/dist/runtime/github-integration.js +120 -0
- package/dist/runtime/github-integration.js.map +1 -0
- package/dist/runtime/grading-ops.js +1 -1
- package/dist/runtime/grading-ops.js.map +1 -1
- package/dist/runtime/memory-extra-ops.d.ts.map +1 -1
- package/dist/runtime/memory-extra-ops.js +6 -2
- package/dist/runtime/memory-extra-ops.js.map +1 -1
- package/dist/runtime/orchestrate-ops.d.ts.map +1 -1
- package/dist/runtime/orchestrate-ops.js +386 -37
- package/dist/runtime/orchestrate-ops.js.map +1 -1
- package/dist/runtime/planning-extra-ops.d.ts.map +1 -1
- package/dist/runtime/planning-extra-ops.js +69 -4
- package/dist/runtime/planning-extra-ops.js.map +1 -1
- package/dist/runtime/review-ops.d.ts +10 -0
- package/dist/runtime/review-ops.d.ts.map +1 -0
- package/dist/runtime/review-ops.js +97 -0
- package/dist/runtime/review-ops.js.map +1 -0
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +29 -12
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/runtime/session-briefing.d.ts +3 -0
- package/dist/runtime/session-briefing.d.ts.map +1 -1
- package/dist/runtime/session-briefing.js +72 -1
- package/dist/runtime/session-briefing.js.map +1 -1
- package/dist/runtime/sync-ops.d.ts +12 -0
- package/dist/runtime/sync-ops.d.ts.map +1 -0
- package/dist/runtime/sync-ops.js +288 -0
- package/dist/runtime/sync-ops.js.map +1 -0
- package/dist/runtime/tier-ops.d.ts +13 -0
- package/dist/runtime/tier-ops.d.ts.map +1 -0
- package/dist/runtime/tier-ops.js +110 -0
- package/dist/runtime/tier-ops.js.map +1 -0
- package/dist/runtime/types.d.ts +10 -4
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/runtime/vault-extra-ops.d.ts +5 -4
- package/dist/runtime/vault-extra-ops.d.ts.map +1 -1
- package/dist/runtime/vault-extra-ops.js +5 -300
- package/dist/runtime/vault-extra-ops.js.map +1 -1
- package/dist/runtime/vault-sharing-ops.d.ts +4 -4
- package/dist/runtime/vault-sharing-ops.d.ts.map +1 -1
- package/dist/runtime/vault-sharing-ops.js +5 -300
- package/dist/runtime/vault-sharing-ops.js.map +1 -1
- package/dist/skills/sync-skills.d.ts +27 -0
- package/dist/skills/sync-skills.d.ts.map +1 -0
- package/dist/skills/sync-skills.js +81 -0
- package/dist/skills/sync-skills.js.map +1 -0
- package/dist/update-check.d.ts +14 -0
- package/dist/update-check.d.ts.map +1 -0
- package/dist/update-check.js +96 -0
- package/dist/update-check.js.map +1 -0
- package/dist/vault/linking.d.ts +10 -12
- package/dist/vault/linking.d.ts.map +1 -1
- package/dist/vault/linking.js +140 -161
- package/dist/vault/linking.js.map +1 -1
- package/dist/vault/vault-entries.d.ts +69 -0
- package/dist/vault/vault-entries.d.ts.map +1 -0
- package/dist/vault/vault-entries.js +299 -0
- package/dist/vault/vault-entries.js.map +1 -0
- package/dist/vault/vault-interfaces.d.ts +153 -0
- package/dist/vault/vault-interfaces.d.ts.map +1 -0
- package/dist/vault/vault-interfaces.js +2 -0
- package/dist/vault/vault-interfaces.js.map +1 -0
- package/dist/vault/vault-maintenance.d.ts +40 -0
- package/dist/vault/vault-maintenance.d.ts.map +1 -0
- package/dist/vault/vault-maintenance.js +146 -0
- package/dist/vault/vault-maintenance.js.map +1 -0
- package/dist/vault/vault-markdown-sync.d.ts +22 -0
- package/dist/vault/vault-markdown-sync.d.ts.map +1 -0
- package/dist/vault/vault-markdown-sync.js +143 -0
- package/dist/vault/vault-markdown-sync.js.map +1 -0
- package/dist/vault/vault-memories.d.ts +61 -0
- package/dist/vault/vault-memories.d.ts.map +1 -0
- package/dist/vault/vault-memories.js +242 -0
- package/dist/vault/vault-memories.js.map +1 -0
- package/dist/vault/vault-schema.d.ts +9 -0
- package/dist/vault/vault-schema.d.ts.map +1 -0
- package/dist/vault/vault-schema.js +205 -0
- package/dist/vault/vault-schema.js.map +1 -0
- package/dist/vault/vault.d.ts +29 -81
- package/dist/vault/vault.d.ts.map +1 -1
- package/dist/vault/vault.js +82 -931
- package/dist/vault/vault.js.map +1 -1
- package/package.json +7 -7
- package/src/agency/agency-manager.test.ts +620 -0
- package/src/agency/default-rules.test.ts +236 -0
- package/src/{__tests__ → brain}/brain-intelligence.test.ts +37 -14
- package/src/{__tests__ → brain}/brain.test.ts +1 -1
- package/src/brain/intelligence.ts +196 -15
- package/src/brain/learning-radar.ts +22 -1
- package/src/{__tests__ → brain}/second-brain-features.test.ts +4 -4
- package/src/{__tests__ → brain}/session-lifecycle.test.ts +2 -2
- package/src/capabilities/chain-mapping.test.ts +66 -0
- package/src/capabilities/registry.test.ts +359 -0
- package/src/chat/agent-loop.test.ts +384 -0
- package/src/chat/agent-loop.ts +2 -0
- package/src/{__tests__ → chat}/chat-differentiators.test.ts +3 -3
- package/src/{__tests__ → chat}/chat-enhanced.test.ts +4 -4
- package/src/{__tests__ → chat}/chat-transport.test.ts +6 -6
- package/src/chat/mcp-bridge.test.ts +178 -0
- package/src/chat/notifications.ts +2 -0
- package/src/chat/output-compressor.test.ts +164 -0
- package/src/claudemd/compose.test.ts +178 -0
- package/src/claudemd/compose.ts +1 -1
- package/src/claudemd/inject.test.ts +201 -0
- package/src/context/context-engine.test.ts +506 -0
- package/src/control/identity-manager.test.ts +305 -0
- package/src/control/intent-router.test.ts +360 -0
- package/src/control/intent-router.ts +13 -4
- package/src/curator/classifier.test.ts +110 -0
- package/src/curator/contradiction-detector.test.ts +205 -0
- package/src/curator/contradiction-detector.ts +87 -0
- package/src/{__tests__ → curator}/curator-pipeline-e2e.test.ts +10 -10
- package/src/{__tests__ → curator}/curator.test.ts +77 -1
- package/src/curator/curator.ts +160 -600
- package/src/curator/duplicate-detector.test.ts +245 -0
- package/src/curator/duplicate-detector.ts +103 -0
- package/src/curator/health-audit.ts +126 -0
- package/src/curator/metadata-enricher.ts +84 -0
- package/src/curator/quality-gate.test.ts +175 -0
- package/src/curator/schema.ts +65 -0
- package/src/curator/tag-manager.test.ts +173 -0
- package/src/curator/tag-manager.ts +109 -0
- package/src/domain-packs/inject-rules.test.ts +117 -0
- package/src/domain-packs/knowledge-installer.test.ts +163 -0
- package/src/domain-packs/loader.test.ts +86 -0
- package/src/domain-packs/pack-runtime.test.ts +140 -0
- package/src/domain-packs/skills-installer.test.ts +135 -0
- package/src/domain-packs/token-resolver.test.ts +148 -0
- package/src/domain-packs/types.test.ts +144 -0
- package/src/enforcement/adapters/claude-code.test.ts +216 -0
- package/src/enforcement/registry.test.ts +258 -0
- package/src/engine/bin/soleri-engine.ts +30 -4
- package/src/engine/core-ops.test.ts +254 -0
- package/src/engine/core-ops.ts +25 -8
- package/src/engine/module-manifest.test.ts +125 -0
- package/src/engine/module-manifest.ts +42 -2
- package/src/engine/register-engine.test.ts +235 -0
- package/src/engine/register-engine.ts +50 -3
- package/src/errors/classify.test.ts +203 -0
- package/src/errors/retry.test.ts +153 -0
- package/src/errors/retry.ts +2 -0
- package/src/errors/types.test.ts +108 -0
- package/src/events/event-bus.test.ts +149 -0
- package/src/extensions/middleware.test.ts +234 -0
- package/src/facades/facade-factory.test.ts +470 -0
- package/src/flows/chain-runner.test.ts +273 -0
- package/src/flows/context-router.test.ts +52 -0
- package/src/flows/dispatch-registry.test.ts +128 -0
- package/src/flows/epilogue.test.ts +113 -0
- package/src/flows/executor.test.ts +263 -0
- package/src/flows/gate-evaluator.test.ts +200 -0
- package/src/flows/gate-evaluator.ts +23 -0
- package/src/flows/types.ts +4 -0
- package/src/governance/governance.test.ts +842 -0
- package/src/{__tests__ → health}/health-registry.test.ts +75 -55
- package/src/health/vault-integrity.test.ts +110 -0
- package/src/index.ts +92 -0
- package/src/intake/content-classifier.test.ts +279 -0
- package/src/intake/dedup-gate.test.ts +147 -0
- package/src/intake/intake-pipeline.test.ts +508 -0
- package/src/intake/intake-pipeline.ts +1 -0
- package/src/intake/text-ingester.test.ts +200 -0
- package/src/intake/text-ingester.ts +2 -0
- package/src/llm/key-pool.test.ts +234 -0
- package/src/llm/key-pool.ts +3 -4
- package/src/llm/llm-client.test.ts +342 -0
- package/src/llm/oauth-discovery.test.ts +180 -0
- package/src/llm/utils.test.ts +371 -0
- package/src/llm/utils.ts +2 -0
- package/src/{__tests__ → logging}/logger.test.ts +44 -62
- package/src/loop/loop-manager.test.ts +515 -0
- package/src/migrations/migration-runner.edge-cases.test.ts +314 -0
- package/src/migrations/migration-runner.test-helpers.ts +64 -0
- package/src/migrations/migration-runner.test.ts +385 -0
- package/src/operator/auto-signal-pipeline.test.ts +207 -0
- package/src/operator/operator-profile-extended.test.ts +330 -0
- package/src/operator/operator-profile.test.ts +332 -0
- package/src/operator/operator-profile.ts +485 -0
- package/src/operator/operator-signals-extended.test.ts +257 -0
- package/src/operator/operator-signals.test.ts +277 -0
- package/src/operator/operator-signals.ts +262 -0
- package/src/operator/operator-types.ts +444 -0
- package/src/operator/prompts/hook-precompact-operator-dispatch.md +98 -0
- package/src/operator/prompts/subagent-soft-signal-extractor.md +130 -0
- package/src/operator/prompts/subagent-synthesis-cognition.md +190 -0
- package/src/operator/prompts/subagent-synthesis-communication.md +146 -0
- package/src/operator/prompts/subagent-synthesis-technical.md +170 -0
- package/src/operator/prompts/subagent-synthesis-trust.md +149 -0
- package/src/{__tests__ → packs}/pack-lockfile.test.ts +3 -3
- package/src/{__tests__ → packs}/pack-system.test.ts +2 -2
- package/src/paths.ts +115 -0
- package/src/persistence/index.ts +1 -1
- package/src/persistence/sqlite-provider.test.ts +540 -0
- package/src/persistence/sqlite-provider.ts +8 -5
- package/src/persona/defaults.test.ts +55 -0
- package/src/persona/loader.test.ts +67 -0
- package/src/persona/prompt-generator.test.ts +127 -0
- package/src/planning/evidence-collector.test.ts +515 -0
- package/src/planning/evidence-collector.ts +47 -0
- package/src/planning/gap-analysis-alternatives.test.ts +199 -0
- package/src/planning/gap-analysis.ts +21 -636
- package/src/planning/gap-passes.test.ts +554 -0
- package/src/planning/gap-passes.ts +367 -0
- package/src/planning/gap-patterns.test.ts +394 -0
- package/src/planning/gap-patterns.ts +317 -0
- package/src/planning/gap-types.ts +4 -1
- package/src/planning/github-projection.test.ts +182 -0
- package/src/planning/github-projection.ts +446 -0
- package/src/planning/impact-analyzer.test.ts +167 -0
- package/src/planning/impact-analyzer.ts +251 -0
- package/src/planning/plan-lifecycle.test.ts +379 -0
- package/src/planning/plan-lifecycle.ts +377 -0
- package/src/planning/planner-types.ts +215 -0
- package/src/{__tests__ → planning}/planner.test.ts +179 -15
- package/src/planning/planner.ts +221 -1112
- package/src/planning/rationalization-detector.test.ts +156 -0
- package/src/planning/rationalization-detector.ts +136 -0
- package/src/planning/reconciliation-engine.test.ts +158 -0
- package/src/planning/reconciliation-engine.ts +161 -0
- package/src/planning/task-verifier.test.ts +267 -0
- package/src/planning/task-verifier.ts +309 -0
- package/src/planning/verification-protocol.test.ts +201 -0
- package/src/playbooks/generic/generic-playbooks.test.ts +438 -0
- package/src/playbooks/index.test.ts +77 -0
- package/src/playbooks/playbook-executor.test.ts +253 -0
- package/src/playbooks/playbook-registry.test.ts +232 -0
- package/src/playbooks/playbook-seeder.test.ts +153 -0
- package/src/plugins/plugin-loader.test.ts +217 -0
- package/src/plugins/plugin-registry.test.ts +284 -0
- package/src/project/project-registry.test.ts +439 -0
- package/src/prompts/parser.test.ts +100 -0
- package/src/prompts/template-manager.test.ts +112 -0
- package/src/{__tests__ → queue}/async-infrastructure.test.ts +3 -3
- package/src/queue/job-queue.test.ts +327 -0
- package/src/queue/pipeline-runner.test.ts +209 -0
- package/src/runtime/admin-extra-ops.test.ts +513 -0
- package/src/runtime/admin-ops.test.ts +255 -0
- package/src/runtime/admin-ops.ts +45 -17
- package/src/runtime/admin-setup-ops.test.ts +327 -0
- package/src/runtime/admin-setup-ops.ts +26 -42
- package/src/runtime/archive-ops.test.ts +272 -0
- package/src/runtime/archive-ops.ts +347 -0
- package/src/runtime/branching-ops.test.ts +144 -0
- package/src/runtime/branching-ops.ts +107 -0
- package/src/runtime/capture-ops.test.ts +419 -0
- package/src/runtime/capture-ops.ts +50 -8
- package/src/runtime/chain-ops.test.ts +159 -0
- package/src/runtime/claude-md-helpers.test.ts +189 -0
- package/src/runtime/claude-md-helpers.ts +1 -1
- package/src/runtime/context-health.test.ts +76 -0
- package/src/runtime/context-health.ts +83 -0
- package/src/runtime/curator-extra-ops.test.ts +204 -0
- package/src/runtime/deprecation.test.ts +98 -0
- package/src/runtime/domain-ops.test.ts +278 -0
- package/src/runtime/facades/admin-facade.test.ts +330 -0
- package/src/runtime/facades/agency-facade.test.ts +278 -0
- package/src/runtime/facades/archive-facade.test.ts +308 -0
- package/src/runtime/facades/archive-facade.ts +14 -0
- package/src/runtime/facades/brain-facade.test.ts +818 -0
- package/src/runtime/facades/brain-facade.ts +2 -0
- package/src/runtime/facades/branching-facade.test.ts +43 -0
- package/src/runtime/facades/branching-facade.ts +11 -0
- package/src/runtime/facades/chat-facade.test.ts +219 -0
- package/src/runtime/facades/chat-facade.ts +15 -906
- package/src/runtime/facades/chat-service-ops.test.ts +381 -0
- package/src/runtime/facades/chat-service-ops.ts +376 -0
- package/src/runtime/facades/chat-session-ops.test.ts +212 -0
- package/src/runtime/facades/chat-session-ops.ts +146 -0
- package/src/runtime/facades/chat-state.ts +60 -0
- package/src/runtime/facades/chat-transport-ops.test.ts +336 -0
- package/src/runtime/facades/chat-transport-ops.ts +379 -0
- package/src/runtime/facades/context-facade.test.ts +123 -0
- package/src/runtime/facades/control-facade.test.ts +436 -0
- package/src/runtime/facades/control-facade.ts +6 -1
- package/src/runtime/facades/curator-facade.test.ts +303 -0
- package/src/runtime/facades/index.ts +48 -0
- package/src/runtime/facades/intake-facade.test.ts +215 -0
- package/src/runtime/facades/intake-facade.ts +14 -0
- package/src/runtime/facades/links-facade.test.ts +203 -0
- package/src/runtime/facades/links-facade.ts +13 -0
- package/src/runtime/facades/loop-facade.test.ts +262 -0
- package/src/runtime/facades/memory-facade.test.ts +283 -0
- package/src/runtime/facades/memory-facade.ts +78 -6
- package/src/runtime/facades/operator-facade.test.ts +221 -0
- package/src/runtime/facades/operator-facade.ts +244 -0
- package/src/runtime/facades/orchestrate-facade.test.ts +191 -0
- package/src/runtime/facades/orchestrate-facade.ts +3 -3
- package/src/runtime/facades/plan-facade.test.ts +283 -0
- package/src/runtime/facades/plan-facade.ts +47 -6
- package/src/runtime/facades/review-facade.test.ts +82 -0
- package/src/runtime/facades/review-facade.ts +11 -0
- package/src/runtime/facades/sync-facade.test.ts +113 -0
- package/src/runtime/facades/sync-facade.ts +11 -0
- package/src/runtime/facades/tier-facade.test.ts +47 -0
- package/src/runtime/facades/tier-facade.ts +11 -0
- package/src/runtime/facades/vault-facade.test.ts +563 -0
- package/src/runtime/facades/vault-facade.ts +66 -265
- package/src/runtime/feature-flags.test.ts +140 -0
- package/src/runtime/github-integration.test.ts +89 -0
- package/src/runtime/github-integration.ts +162 -0
- package/src/runtime/grading-ops.test.ts +172 -0
- package/src/runtime/grading-ops.ts +1 -1
- package/src/runtime/intake-ops.test.ts +261 -0
- package/src/runtime/loop-ops.test.ts +248 -0
- package/src/runtime/memory-cross-project-ops.test.ts +188 -0
- package/src/runtime/memory-extra-ops.test.ts +453 -0
- package/src/runtime/memory-extra-ops.ts +6 -2
- package/src/runtime/orchestrate-ops.test.ts +302 -0
- package/src/runtime/orchestrate-ops.ts +461 -45
- package/src/runtime/pack-ops.test.ts +175 -0
- package/src/runtime/planning-extra-ops.test.ts +593 -0
- package/src/runtime/planning-extra-ops.ts +74 -4
- package/src/{__tests__ → runtime}/playbook-ops-execution.test.ts +3 -3
- package/src/runtime/playbook-ops.test.ts +285 -0
- package/src/runtime/plugin-ops.test.ts +259 -0
- package/src/runtime/project-ops.test.ts +255 -0
- package/src/runtime/review-ops.test.ts +142 -0
- package/src/runtime/review-ops.ts +99 -0
- package/src/runtime/runtime.test.ts +363 -0
- package/src/runtime/runtime.ts +41 -12
- package/src/runtime/session-briefing.test.ts +431 -0
- package/src/runtime/session-briefing.ts +86 -1
- package/src/runtime/sync-ops.test.ts +212 -0
- package/src/runtime/sync-ops.ts +325 -0
- package/src/runtime/telemetry-ops.test.ts +157 -0
- package/src/runtime/tier-ops.test.ts +159 -0
- package/src/runtime/tier-ops.ts +119 -0
- package/src/runtime/types.ts +10 -4
- package/src/runtime/vault-extra-ops.test.ts +270 -0
- package/src/runtime/vault-extra-ops.ts +5 -332
- package/src/runtime/vault-linking-ops.test.ts +237 -0
- package/src/runtime/vault-sharing-ops.test.ts +127 -0
- package/src/runtime/vault-sharing-ops.ts +5 -329
- package/src/skills/sync-skills.ts +98 -0
- package/src/streams/normalize.test.ts +95 -0
- package/src/streams/replayable-stream.test.ts +166 -0
- package/src/telemetry/telemetry.test.ts +143 -0
- package/src/transport/http-server.test.ts +394 -0
- package/src/transport/lsp-server.test.ts +458 -0
- package/src/transport/rate-limiter.test.ts +126 -0
- package/src/transport/session-manager.test.ts +133 -0
- package/src/transport/token-auth.test.ts +136 -0
- package/src/transport/ws-server.test.ts +297 -0
- package/src/update-check.ts +111 -0
- package/src/vault/__tests__/vault-characterization.test.ts +579 -0
- package/src/vault/content-hash.test.ts +78 -0
- package/src/vault/git-vault-sync.test.ts +234 -0
- package/src/vault/knowledge-review.test.ts +269 -0
- package/src/vault/linking.test.ts +391 -0
- package/src/vault/linking.ts +188 -181
- package/src/vault/obsidian-sync.test.ts +345 -0
- package/src/vault/playbook.test.ts +152 -0
- package/src/vault/scope-detector.test.ts +185 -0
- package/src/vault/vault-branching.test.ts +252 -0
- package/src/{__tests__ → vault}/vault-connect.test.ts +1 -1
- package/src/vault/vault-entries.ts +426 -0
- package/src/vault/vault-maintenance.ts +200 -0
- package/src/vault/vault-manager.test.ts +206 -0
- package/src/vault/vault-markdown-sync.test.ts +203 -0
- package/src/vault/vault-markdown-sync.ts +163 -0
- package/src/vault/vault-memories.ts +339 -0
- package/src/{__tests__ → vault}/vault-scaling.test.ts +1 -1
- package/src/vault/vault-schema.ts +238 -0
- package/src/{__tests__ → vault}/vault-sharing.test.ts +4 -4
- package/src/{__tests__ → vault}/vault.test.ts +2 -2
- package/src/vault/vault.ts +87 -1123
- package/dist/cognee/client.d.ts +0 -43
- package/dist/cognee/client.d.ts.map +0 -1
- package/dist/cognee/client.js +0 -375
- package/dist/cognee/client.js.map +0 -1
- package/dist/cognee/sync-manager.d.ts +0 -153
- package/dist/cognee/sync-manager.d.ts.map +0 -1
- package/dist/cognee/sync-manager.js +0 -390
- package/dist/cognee/sync-manager.js.map +0 -1
- package/dist/cognee/types.d.ts +0 -62
- package/dist/cognee/types.d.ts.map +0 -1
- package/dist/cognee/types.js +0 -3
- package/dist/cognee/types.js.map +0 -1
- package/dist/governance/index.d.ts +0 -3
- package/dist/governance/index.d.ts.map +0 -1
- package/dist/governance/index.js +0 -2
- package/dist/governance/index.js.map +0 -1
- package/dist/health/doctor-checks.d.ts +0 -15
- package/dist/health/doctor-checks.d.ts.map +0 -1
- package/dist/health/doctor-checks.js +0 -98
- package/dist/health/doctor-checks.js.map +0 -1
- package/dist/persistence/postgres-provider.d.ts +0 -81
- package/dist/persistence/postgres-provider.d.ts.map +0 -1
- package/dist/persistence/postgres-provider.js +0 -256
- package/dist/persistence/postgres-provider.js.map +0 -1
- package/dist/runtime/cognee-sync-ops.d.ts +0 -12
- package/dist/runtime/cognee-sync-ops.d.ts.map +0 -1
- package/dist/runtime/cognee-sync-ops.js +0 -93
- package/dist/runtime/cognee-sync-ops.js.map +0 -1
- package/dist/runtime/core-ops.d.ts +0 -23
- package/dist/runtime/core-ops.d.ts.map +0 -1
- package/dist/runtime/core-ops.js +0 -1296
- package/dist/runtime/core-ops.js.map +0 -1
- package/dist/runtime/facades/cognee-facade.d.ts +0 -8
- package/dist/runtime/facades/cognee-facade.d.ts.map +0 -1
- package/dist/runtime/facades/cognee-facade.js +0 -156
- package/dist/runtime/facades/cognee-facade.js.map +0 -1
- package/src/__tests__/admin-extra-ops.test.ts +0 -484
- package/src/__tests__/admin-ops.test.ts +0 -268
- package/src/__tests__/admin-setup-ops.test.ts +0 -355
- package/src/__tests__/agency-manager.test.ts +0 -374
- package/src/__tests__/agent-loop.test.ts +0 -256
- package/src/__tests__/capture-ops.test.ts +0 -784
- package/src/__tests__/claudemd.test.ts +0 -282
- package/src/__tests__/content-hash.test.ts +0 -60
- package/src/__tests__/context-engine.test.ts +0 -251
- package/src/__tests__/core-ops.test.ts +0 -550
- package/src/__tests__/curator-extra-ops.test.ts +0 -383
- package/src/__tests__/deprecation.test.ts +0 -78
- package/src/__tests__/domain-ops.test.ts +0 -226
- package/src/__tests__/domain-packs.test.ts +0 -421
- package/src/__tests__/enforcement.test.ts +0 -153
- package/src/__tests__/errors.test.ts +0 -388
- package/src/__tests__/extensions.test.ts +0 -233
- package/src/__tests__/facade-factory.test.ts +0 -271
- package/src/__tests__/feature-flags.test.ts +0 -137
- package/src/__tests__/flows.test.ts +0 -604
- package/src/__tests__/git-vault-sync.test.ts +0 -230
- package/src/__tests__/governance.test.ts +0 -522
- package/src/__tests__/grading-ops.test.ts +0 -361
- package/src/__tests__/identity-manager.test.ts +0 -243
- package/src/__tests__/intake-pipeline.test.ts +0 -162
- package/src/__tests__/intent-router.test.ts +0 -222
- package/src/__tests__/knowledge-review.test.ts +0 -104
- package/src/__tests__/llm-client.test.ts +0 -69
- package/src/__tests__/llm.test.ts +0 -556
- package/src/__tests__/loader.test.ts +0 -176
- package/src/__tests__/loop-ops.test.ts +0 -469
- package/src/__tests__/lsp-transport.test.ts +0 -442
- package/src/__tests__/memory-cross-project-ops.test.ts +0 -248
- package/src/__tests__/memory-extra-ops.test.ts +0 -352
- package/src/__tests__/migration-runner.test.ts +0 -170
- package/src/__tests__/module-manifest-drift.test.ts +0 -59
- package/src/__tests__/normalize.test.ts +0 -85
- package/src/__tests__/obsidian-sync.test.ts +0 -354
- package/src/__tests__/orchestrate-ops.test.ts +0 -289
- package/src/__tests__/pack-ops.test.ts +0 -146
- package/src/__tests__/persistence.test.ts +0 -291
- package/src/__tests__/planning-extra-ops.test.ts +0 -706
- package/src/__tests__/playbook-executor.test.ts +0 -249
- package/src/__tests__/playbook-registry.test.ts +0 -326
- package/src/__tests__/playbook-seeder.test.ts +0 -163
- package/src/__tests__/playbook.test.ts +0 -389
- package/src/__tests__/plugin-ops.test.ts +0 -411
- package/src/__tests__/plugin-system.test.ts +0 -509
- package/src/__tests__/project-ops.test.ts +0 -381
- package/src/__tests__/replayable-stream.test.ts +0 -177
- package/src/__tests__/runtime.test.ts +0 -95
- package/src/__tests__/scope-detector.test.ts +0 -121
- package/src/__tests__/template-manager.test.ts +0 -222
- package/src/__tests__/token-resolver.test.ts +0 -79
- package/src/__tests__/transport.test.ts +0 -758
- package/src/__tests__/vault-branching.test.ts +0 -274
- package/src/__tests__/vault-extra-ops.test.ts +0 -482
- package/src/__tests__/vault-integrity.test.ts +0 -71
- package/src/__tests__/vault-manager.test.ts +0 -238
- package/src/__tests__/ws-transport.test.ts +0 -479
- package/src/engine/index.ts +0 -21
- package/src/persona/index.ts +0 -9
package/src/planning/planner.ts
CHANGED
|
@@ -1,328 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Planner facade — delegates to extracted modules, owns persistence.
|
|
3
|
+
* Re-exports all public types for backward compatibility.
|
|
4
|
+
*/
|
|
1
5
|
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
2
|
-
import { createHash } from 'node:crypto';
|
|
3
6
|
import { dirname } from 'node:path';
|
|
4
|
-
import type { PlanGap } from './gap-types.js';
|
|
5
|
-
import { SEVERITY_WEIGHTS, CATEGORY_PENALTY_CAPS, CATEGORY_BONUS_CAPS } from './gap-types.js';
|
|
6
7
|
import { runGapAnalysis } from './gap-analysis.js';
|
|
7
8
|
import type { GapAnalysisOptions } from './gap-analysis.js';
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
'validating',
|
|
49
|
-
'reconciling',
|
|
50
|
-
];
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Validate a lifecycle status transition.
|
|
54
|
-
* Returns true if the transition is valid, false otherwise.
|
|
55
|
-
*/
|
|
56
|
-
export function isValidTransition(from: PlanStatus, to: PlanStatus): boolean {
|
|
57
|
-
return LIFECYCLE_TRANSITIONS[from].includes(to);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Get the valid next statuses for a given status.
|
|
62
|
-
*/
|
|
63
|
-
export function getValidNextStatuses(status: PlanStatus): PlanStatus[] {
|
|
64
|
-
return LIFECYCLE_TRANSITIONS[status];
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Check if a status should have TTL expiration.
|
|
69
|
-
* Plans in executing/reconciling states persist indefinitely.
|
|
70
|
-
*/
|
|
71
|
-
export function shouldExpire(status: PlanStatus): boolean {
|
|
72
|
-
return !NON_EXPIRING_STATUSES.includes(status);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export type TaskStatus = 'pending' | 'in_progress' | 'completed' | 'skipped' | 'failed';
|
|
76
|
-
|
|
77
|
-
export interface TaskEvidence {
|
|
78
|
-
/** What the evidence proves (maps to an acceptance criterion). */
|
|
79
|
-
criterion: string;
|
|
80
|
-
/** Evidence content — command output, URL, file path, description. */
|
|
81
|
-
content: string;
|
|
82
|
-
/** Evidence type. */
|
|
83
|
-
type: 'command_output' | 'url' | 'file' | 'description';
|
|
84
|
-
submittedAt: number;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export interface TaskMetrics {
|
|
88
|
-
durationMs?: number;
|
|
89
|
-
iterations?: number;
|
|
90
|
-
toolCalls?: number;
|
|
91
|
-
modelTier?: string;
|
|
92
|
-
estimatedCostUsd?: number;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export interface TaskDeliverable {
|
|
96
|
-
type: 'file' | 'vault_entry' | 'url';
|
|
97
|
-
path: string;
|
|
98
|
-
hash?: string;
|
|
99
|
-
verifiedAt?: number;
|
|
100
|
-
stale?: boolean;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export interface ExecutionSummary {
|
|
104
|
-
totalDurationMs: number;
|
|
105
|
-
tasksCompleted: number;
|
|
106
|
-
tasksSkipped: number;
|
|
107
|
-
tasksFailed: number;
|
|
108
|
-
avgTaskDurationMs: number;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export interface PlanTask {
|
|
112
|
-
id: string;
|
|
113
|
-
title: string;
|
|
114
|
-
description: string;
|
|
115
|
-
status: TaskStatus;
|
|
116
|
-
/** Optional dependency IDs — tasks that must complete before this one. */
|
|
117
|
-
dependsOn?: string[];
|
|
118
|
-
/** Evidence submitted for task acceptance criteria. */
|
|
119
|
-
evidence?: TaskEvidence[];
|
|
120
|
-
/** Whether this task has been verified (all evidence checked + reviews passed). */
|
|
121
|
-
verified?: boolean;
|
|
122
|
-
/** Task-level acceptance criteria (for verification checking). */
|
|
123
|
-
acceptanceCriteria?: string[];
|
|
124
|
-
/** Timestamp when task was first moved to in_progress. */
|
|
125
|
-
startedAt?: number;
|
|
126
|
-
/** Timestamp when task reached a terminal state (completed/skipped/failed). */
|
|
127
|
-
completedAt?: number;
|
|
128
|
-
/** Per-task execution metrics. */
|
|
129
|
-
metrics?: TaskMetrics;
|
|
130
|
-
/** Deliverables produced by this task. */
|
|
131
|
-
deliverables?: TaskDeliverable[];
|
|
132
|
-
updatedAt: number;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export interface DriftItem {
|
|
136
|
-
/** Type of drift */
|
|
137
|
-
type: 'skipped' | 'added' | 'modified' | 'reordered';
|
|
138
|
-
/** What drifted */
|
|
139
|
-
description: string;
|
|
140
|
-
/** How much this affected the plan */
|
|
141
|
-
impact: 'low' | 'medium' | 'high';
|
|
142
|
-
/** Why the drift occurred */
|
|
143
|
-
rationale: string;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Severity weights for drift accuracy score calculation.
|
|
148
|
-
* Score = 100 - sum(drift_items * weight_per_impact)
|
|
149
|
-
* Ported from Salvador's plan-lifecycle-types.ts.
|
|
150
|
-
*/
|
|
151
|
-
export const DRIFT_WEIGHTS: Record<DriftItem['impact'], number> = {
|
|
152
|
-
high: 20,
|
|
153
|
-
medium: 10,
|
|
154
|
-
low: 5,
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Calculate drift accuracy score from drift items.
|
|
159
|
-
* Score = max(0, 100 - sum(weight_per_impact))
|
|
160
|
-
* Ported from Salvador's calculateDriftScore.
|
|
161
|
-
*/
|
|
162
|
-
export function calculateDriftScore(items: DriftItem[]): number {
|
|
163
|
-
let deductions = 0;
|
|
164
|
-
for (const item of items) {
|
|
165
|
-
deductions += DRIFT_WEIGHTS[item.impact];
|
|
166
|
-
}
|
|
167
|
-
return Math.max(0, 100 - deductions);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
export interface ReconciliationReport {
|
|
171
|
-
planId: string;
|
|
172
|
-
/** Accuracy score: 100 = perfect execution, 0 = total drift. Impact-weighted. */
|
|
173
|
-
accuracy: number;
|
|
174
|
-
driftItems: DriftItem[];
|
|
175
|
-
/** Human-readable summary of the drift */
|
|
176
|
-
summary: string;
|
|
177
|
-
reconciledAt: number;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
export interface ReviewEvidence {
|
|
181
|
-
planId: string;
|
|
182
|
-
taskId?: string;
|
|
183
|
-
reviewer: string;
|
|
184
|
-
outcome: 'approved' | 'rejected' | 'needs_changes';
|
|
185
|
-
comments: string;
|
|
186
|
-
reviewedAt: number;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
export type PlanGrade = 'A+' | 'A' | 'B' | 'C' | 'D' | 'F';
|
|
190
|
-
|
|
191
|
-
export interface PlanCheck {
|
|
192
|
-
checkId: string;
|
|
193
|
-
planId: string;
|
|
194
|
-
grade: PlanGrade;
|
|
195
|
-
score: number; // 0-100
|
|
196
|
-
gaps: PlanGap[];
|
|
197
|
-
iteration: number;
|
|
198
|
-
checkedAt: number;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Calculate score from gaps with severity-weighted deductions and iteration leniency.
|
|
203
|
-
* Ported from Salvador MCP's plan-grading.ts.
|
|
204
|
-
*
|
|
205
|
-
* - Minor gaps: weight=0 on iteration 1 (free sketching), weight=1 on iteration 2, full weight on 3+
|
|
206
|
-
* - Per-category deductions are capped before summing (prevents one category from tanking the score)
|
|
207
|
-
* - Score = max(0, 100 - totalDeductions)
|
|
208
|
-
*/
|
|
209
|
-
export function calculateScore(gaps: PlanGap[], iteration: number = 1): number {
|
|
210
|
-
const categoryDeductions = new Map<string, number>();
|
|
211
|
-
const categoryBonuses = new Map<string, number>();
|
|
212
|
-
|
|
213
|
-
for (const gap of gaps) {
|
|
214
|
-
let weight: number = SEVERITY_WEIGHTS[gap.severity];
|
|
215
|
-
|
|
216
|
-
// Iteration leniency for minor gaps
|
|
217
|
-
if (gap.severity === 'minor') {
|
|
218
|
-
if (iteration === 1) weight = 0;
|
|
219
|
-
else if (iteration === 2) weight = 1;
|
|
220
|
-
// iteration 3+: full weight (2)
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const category = gap.category;
|
|
224
|
-
if (weight < 0) {
|
|
225
|
-
// Bonus — accumulate as positive value for capping, apply as negative deduction
|
|
226
|
-
categoryBonuses.set(category, (categoryBonuses.get(category) ?? 0) + Math.abs(weight));
|
|
227
|
-
} else {
|
|
228
|
-
categoryDeductions.set(category, (categoryDeductions.get(category) ?? 0) + weight);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
let deductions = 0;
|
|
233
|
-
for (const [category, total] of categoryDeductions) {
|
|
234
|
-
const cap = CATEGORY_PENALTY_CAPS[category];
|
|
235
|
-
deductions += cap !== undefined ? Math.min(total, cap) : total;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
let bonuses = 0;
|
|
239
|
-
for (const [category, total] of categoryBonuses) {
|
|
240
|
-
const cap = CATEGORY_BONUS_CAPS[category];
|
|
241
|
-
bonuses += cap !== undefined ? Math.min(total, cap) : total;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
return Math.max(0, Math.min(100, 100 - deductions + bonuses));
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* A structured decision with rationale.
|
|
249
|
-
* Ported from Salvador's PlanContent.decisions.
|
|
250
|
-
*/
|
|
251
|
-
export interface PlanDecision {
|
|
252
|
-
decision: string;
|
|
253
|
-
rationale: string;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
export interface Plan {
|
|
257
|
-
id: string;
|
|
258
|
-
objective: string;
|
|
259
|
-
scope: string;
|
|
260
|
-
status: PlanStatus;
|
|
261
|
-
/**
|
|
262
|
-
* Decisions can be flat strings (backward compat) or structured {decision, rationale}.
|
|
263
|
-
* New plans should prefer PlanDecision[].
|
|
264
|
-
*/
|
|
265
|
-
decisions: (string | PlanDecision)[];
|
|
266
|
-
tasks: PlanTask[];
|
|
267
|
-
/** High-level approach description. Ported from Salvador's PlanContent. */
|
|
268
|
-
approach?: string;
|
|
269
|
-
/** Additional context for the plan. */
|
|
270
|
-
context?: string;
|
|
271
|
-
/** Measurable success criteria. */
|
|
272
|
-
success_criteria?: string[];
|
|
273
|
-
/** Tools to use in execution order. */
|
|
274
|
-
tool_chain?: string[];
|
|
275
|
-
/** Flow definition to follow (e.g., 'developer', 'reviewer', 'designer'). */
|
|
276
|
-
flow?: string;
|
|
277
|
-
/** Target operational mode (e.g., 'build', 'review', 'fix'). */
|
|
278
|
-
target_mode?: string;
|
|
279
|
-
/** Reconciliation report — populated by reconcile(). */
|
|
280
|
-
reconciliation?: ReconciliationReport;
|
|
281
|
-
/** Review evidence — populated by addReview(). */
|
|
282
|
-
reviews?: ReviewEvidence[];
|
|
283
|
-
/** Latest grading check. */
|
|
284
|
-
latestCheck?: PlanCheck;
|
|
285
|
-
/** All check history. */
|
|
286
|
-
checks: PlanCheck[];
|
|
287
|
-
/** Matched playbook info (set by orchestration layer via playbook_match). */
|
|
288
|
-
playbookMatch?: {
|
|
289
|
-
label: string;
|
|
290
|
-
genericId?: string;
|
|
291
|
-
domainId?: string;
|
|
292
|
-
};
|
|
293
|
-
/** Aggregate execution metrics — populated by reconcile() and complete(). */
|
|
294
|
-
executionSummary?: ExecutionSummary;
|
|
295
|
-
createdAt: number;
|
|
296
|
-
updatedAt: number;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
export interface PlanStore {
|
|
300
|
-
version: string;
|
|
301
|
-
plans: Plan[];
|
|
302
|
-
}
|
|
9
|
+
export * from './plan-lifecycle.js';
|
|
10
|
+
export * from './reconciliation-engine.js';
|
|
11
|
+
export * from './task-verifier.js';
|
|
12
|
+
export * from './planner-types.js';
|
|
13
|
+
import type {
|
|
14
|
+
TaskStatus,
|
|
15
|
+
PlanTask,
|
|
16
|
+
Plan,
|
|
17
|
+
PlanStore,
|
|
18
|
+
PlanCheck,
|
|
19
|
+
PlannerOptions,
|
|
20
|
+
} from './planner-types.js';
|
|
21
|
+
import {
|
|
22
|
+
applyTransition,
|
|
23
|
+
scoreToGrade,
|
|
24
|
+
gradeToMinScore,
|
|
25
|
+
PlanGradeRejectionError,
|
|
26
|
+
hasCircularDependencies,
|
|
27
|
+
applyIteration,
|
|
28
|
+
applySplitTasks,
|
|
29
|
+
calculateScore,
|
|
30
|
+
applyTaskStatusUpdate,
|
|
31
|
+
createPlanObject,
|
|
32
|
+
} from './plan-lifecycle.js';
|
|
33
|
+
import type { PlanStatus, PlanGrade, IterateChanges } from './plan-lifecycle.js';
|
|
34
|
+
import {
|
|
35
|
+
buildReconciliationReport,
|
|
36
|
+
buildAutoReconcileInput,
|
|
37
|
+
computeExecutionSummary,
|
|
38
|
+
} from './reconciliation-engine.js';
|
|
39
|
+
import type { ReconcileInput } from './reconciliation-engine.js';
|
|
40
|
+
import {
|
|
41
|
+
createEvidence,
|
|
42
|
+
verifyTaskLogic,
|
|
43
|
+
verifyPlanLogic,
|
|
44
|
+
verifyDeliverablesLogic,
|
|
45
|
+
createDeliverable,
|
|
46
|
+
buildSpecReviewPrompt,
|
|
47
|
+
buildQualityReviewPrompt,
|
|
48
|
+
} from './task-verifier.js';
|
|
303
49
|
|
|
304
50
|
export class Planner {
|
|
305
51
|
private filePath: string;
|
|
306
52
|
private store: PlanStore;
|
|
307
53
|
private gapOptions?: GapAnalysisOptions;
|
|
54
|
+
private minGradeForApproval: PlanGrade;
|
|
308
55
|
|
|
309
|
-
constructor(filePath: string,
|
|
56
|
+
constructor(filePath: string, options?: GapAnalysisOptions | PlannerOptions) {
|
|
310
57
|
this.filePath = filePath;
|
|
311
|
-
|
|
58
|
+
if (options && 'minGradeForApproval' in options) {
|
|
59
|
+
this.gapOptions = options.gapOptions;
|
|
60
|
+
this.minGradeForApproval = options.minGradeForApproval ?? 'A';
|
|
61
|
+
} else {
|
|
62
|
+
this.gapOptions = options as GapAnalysisOptions | undefined;
|
|
63
|
+
this.minGradeForApproval = 'A';
|
|
64
|
+
}
|
|
312
65
|
this.store = this.load();
|
|
313
66
|
}
|
|
314
67
|
|
|
315
68
|
private load(): PlanStore {
|
|
316
|
-
if (!existsSync(this.filePath)) {
|
|
317
|
-
return { version: '1.0', plans: [] };
|
|
318
|
-
}
|
|
69
|
+
if (!existsSync(this.filePath)) return { version: '1.0', plans: [] };
|
|
319
70
|
try {
|
|
320
71
|
const data = readFileSync(this.filePath, 'utf-8');
|
|
321
72
|
const store = JSON.parse(data) as PlanStore;
|
|
322
|
-
|
|
323
|
-
for (const plan of store.plans) {
|
|
324
|
-
plan.checks = plan.checks ?? [];
|
|
325
|
-
}
|
|
73
|
+
for (const plan of store.plans) plan.checks = plan.checks ?? [];
|
|
326
74
|
return store;
|
|
327
75
|
} catch {
|
|
328
76
|
return { version: '1.0', plans: [] };
|
|
@@ -334,44 +82,26 @@ export class Planner {
|
|
|
334
82
|
writeFileSync(this.filePath, JSON.stringify(this.store, null, 2), 'utf-8');
|
|
335
83
|
}
|
|
336
84
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
decisions: params.decisions ?? [],
|
|
358
|
-
tasks: (params.tasks ?? []).map((t, i) => ({
|
|
359
|
-
id: `task-${i + 1}`,
|
|
360
|
-
title: t.title,
|
|
361
|
-
description: t.description,
|
|
362
|
-
status: 'pending' as TaskStatus,
|
|
363
|
-
updatedAt: now,
|
|
364
|
-
})),
|
|
365
|
-
...(params.approach !== undefined && { approach: params.approach }),
|
|
366
|
-
...(params.context !== undefined && { context: params.context }),
|
|
367
|
-
...(params.success_criteria !== undefined && { success_criteria: params.success_criteria }),
|
|
368
|
-
...(params.tool_chain !== undefined && { tool_chain: params.tool_chain }),
|
|
369
|
-
...(params.flow !== undefined && { flow: params.flow }),
|
|
370
|
-
...(params.target_mode !== undefined && { target_mode: params.target_mode }),
|
|
371
|
-
checks: [],
|
|
372
|
-
createdAt: now,
|
|
373
|
-
updatedAt: now,
|
|
374
|
-
};
|
|
85
|
+
private transition(plan: Plan, to: PlanStatus): void {
|
|
86
|
+
const r = applyTransition(plan.status, to);
|
|
87
|
+
plan.status = r.status;
|
|
88
|
+
plan.updatedAt = r.updatedAt;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private requirePlan(planId: string): Plan {
|
|
92
|
+
const plan = this.get(planId);
|
|
93
|
+
if (!plan) throw new Error(`Plan not found: ${planId}`);
|
|
94
|
+
return plan;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private requireTask(plan: Plan, taskId: string): PlanTask {
|
|
98
|
+
const task = plan.tasks.find((t) => t.id === taskId);
|
|
99
|
+
if (!task) throw new Error(`Task not found: ${taskId}`);
|
|
100
|
+
return task;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
create(params: Parameters<typeof createPlanObject>[0]): Plan {
|
|
104
|
+
const plan = createPlanObject(params);
|
|
375
105
|
this.store.plans.push(plan);
|
|
376
106
|
this.save();
|
|
377
107
|
return plan;
|
|
@@ -385,9 +115,6 @@ export class Planner {
|
|
|
385
115
|
return [...this.store.plans];
|
|
386
116
|
}
|
|
387
117
|
|
|
388
|
-
/**
|
|
389
|
-
* Permanently remove a plan by ID. Returns true if found and removed.
|
|
390
|
-
*/
|
|
391
118
|
remove(planId: string): boolean {
|
|
392
119
|
const idx = this.store.plans.findIndex((p) => p.id === planId);
|
|
393
120
|
if (idx < 0) return false;
|
|
@@ -396,121 +123,102 @@ export class Planner {
|
|
|
396
123
|
return true;
|
|
397
124
|
}
|
|
398
125
|
|
|
399
|
-
/**
|
|
400
|
-
* Transition a plan to a new status using the typed FSM.
|
|
401
|
-
* Validates that the transition is allowed before applying it.
|
|
402
|
-
*/
|
|
403
|
-
private transition(plan: Plan, to: PlanStatus): void {
|
|
404
|
-
if (!isValidTransition(plan.status, to)) {
|
|
405
|
-
const valid = getValidNextStatuses(plan.status);
|
|
406
|
-
throw new Error(
|
|
407
|
-
`Invalid transition: '${plan.status}' → '${to}'. ` +
|
|
408
|
-
`Valid transitions from '${plan.status}': ${valid.length > 0 ? valid.join(', ') : 'none'}`,
|
|
409
|
-
);
|
|
410
|
-
}
|
|
411
|
-
plan.status = to;
|
|
412
|
-
plan.updatedAt = Date.now();
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
/**
|
|
416
|
-
* Promote a brainstorming plan to draft status.
|
|
417
|
-
* Only allowed from 'brainstorming'.
|
|
418
|
-
*/
|
|
419
126
|
promoteToDraft(planId: string): Plan {
|
|
420
|
-
const plan = this.
|
|
421
|
-
if (!plan) throw new Error(`Plan not found: ${planId}`);
|
|
127
|
+
const plan = this.requirePlan(planId);
|
|
422
128
|
this.transition(plan, 'draft');
|
|
423
129
|
this.save();
|
|
424
130
|
return plan;
|
|
425
131
|
}
|
|
426
132
|
|
|
427
133
|
approve(planId: string): Plan {
|
|
428
|
-
const plan = this.
|
|
429
|
-
|
|
134
|
+
const plan = this.requirePlan(planId);
|
|
135
|
+
const check = plan.latestCheck;
|
|
136
|
+
if (check && check.score < gradeToMinScore(this.minGradeForApproval)) {
|
|
137
|
+
throw new PlanGradeRejectionError(
|
|
138
|
+
check.grade,
|
|
139
|
+
check.score,
|
|
140
|
+
this.minGradeForApproval,
|
|
141
|
+
check.gaps,
|
|
142
|
+
);
|
|
143
|
+
}
|
|
430
144
|
this.transition(plan, 'approved');
|
|
431
145
|
this.save();
|
|
432
146
|
return plan;
|
|
433
147
|
}
|
|
434
148
|
|
|
435
149
|
startExecution(planId: string): Plan {
|
|
436
|
-
const plan = this.
|
|
437
|
-
if (!plan) throw new Error(`Plan not found: ${planId}`);
|
|
150
|
+
const plan = this.requirePlan(planId);
|
|
438
151
|
this.transition(plan, 'executing');
|
|
439
152
|
this.save();
|
|
440
153
|
return plan;
|
|
441
154
|
}
|
|
442
155
|
|
|
443
|
-
|
|
444
|
-
const plan = this.
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
);
|
|
450
|
-
const task = plan.tasks.find((t) => t.id === taskId);
|
|
451
|
-
if (!task) throw new Error(`Task not found: ${taskId}`);
|
|
452
|
-
|
|
453
|
-
const now = Date.now();
|
|
454
|
-
|
|
455
|
-
// Auto-set startedAt on first in_progress transition
|
|
456
|
-
if (status === 'in_progress' && !task.startedAt) {
|
|
457
|
-
task.startedAt = now;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
// Auto-set completedAt and compute durationMs on terminal transitions
|
|
461
|
-
if (status === 'completed' || status === 'skipped' || status === 'failed') {
|
|
462
|
-
task.completedAt = now;
|
|
463
|
-
if (task.startedAt) {
|
|
464
|
-
if (!task.metrics) task.metrics = {};
|
|
465
|
-
task.metrics.durationMs = now - task.startedAt;
|
|
466
|
-
}
|
|
467
|
-
}
|
|
156
|
+
startValidation(planId: string): Plan {
|
|
157
|
+
const plan = this.requirePlan(planId);
|
|
158
|
+
this.transition(plan, 'validating');
|
|
159
|
+
this.save();
|
|
160
|
+
return plan;
|
|
161
|
+
}
|
|
468
162
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
plan
|
|
163
|
+
startReconciliation(planId: string): Plan {
|
|
164
|
+
const plan = this.requirePlan(planId);
|
|
165
|
+
this.transition(plan, 'reconciling');
|
|
472
166
|
this.save();
|
|
473
167
|
return plan;
|
|
474
168
|
}
|
|
475
169
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
170
|
+
updateTask(planId: string, taskId: string, status: TaskStatus): Plan {
|
|
171
|
+
const plan = this.requirePlan(planId);
|
|
172
|
+
if (plan.status !== 'executing' && plan.status !== 'validating')
|
|
173
|
+
throw new Error(
|
|
174
|
+
`Cannot update tasks on plan in '${plan.status}' status — must be 'executing' or 'validating'`,
|
|
175
|
+
);
|
|
176
|
+
applyTaskStatusUpdate(this.requireTask(plan, taskId), status);
|
|
177
|
+
plan.updatedAt = Date.now();
|
|
484
178
|
this.save();
|
|
485
179
|
return plan;
|
|
486
180
|
}
|
|
487
181
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
182
|
+
reconcile(planId: string, report: ReconcileInput): Plan {
|
|
183
|
+
const plan = this.requirePlan(planId);
|
|
184
|
+
if (
|
|
185
|
+
plan.status !== 'executing' &&
|
|
186
|
+
plan.status !== 'validating' &&
|
|
187
|
+
plan.status !== 'reconciling'
|
|
188
|
+
)
|
|
189
|
+
throw new Error(
|
|
190
|
+
`Cannot reconcile plan in '${plan.status}' status — must be 'executing', 'validating', or 'reconciling'`,
|
|
191
|
+
);
|
|
192
|
+
plan.reconciliation = buildReconciliationReport(planId, report);
|
|
193
|
+
plan.executionSummary = computeExecutionSummary(plan.tasks);
|
|
194
|
+
if (plan.status === 'executing' || plan.status === 'validating') plan.status = 'reconciling';
|
|
195
|
+
plan.status = 'completed';
|
|
196
|
+
plan.updatedAt = Date.now();
|
|
496
197
|
this.save();
|
|
497
198
|
return plan;
|
|
498
199
|
}
|
|
499
200
|
|
|
500
|
-
/**
|
|
501
|
-
* Complete a plan. Only allowed from 'reconciling'.
|
|
502
|
-
* Use startReconciliation() + reconcile() + complete() for the full lifecycle,
|
|
503
|
-
* or reconcile() which auto-transitions through reconciling → completed.
|
|
504
|
-
*/
|
|
505
201
|
complete(planId: string): Plan {
|
|
506
|
-
const plan = this.
|
|
507
|
-
if (
|
|
508
|
-
|
|
202
|
+
const plan = this.requirePlan(planId);
|
|
203
|
+
if (plan.status === 'executing' || plan.status === 'validating')
|
|
204
|
+
return this.reconcile(planId, { actualOutcome: 'All tasks completed', reconciledBy: 'auto' });
|
|
205
|
+
plan.executionSummary = computeExecutionSummary(plan.tasks);
|
|
509
206
|
this.transition(plan, 'completed');
|
|
510
207
|
this.save();
|
|
511
208
|
return plan;
|
|
512
209
|
}
|
|
513
210
|
|
|
211
|
+
autoReconcile(planId: string): Plan | null {
|
|
212
|
+
const plan = this.requirePlan(planId);
|
|
213
|
+
if (plan.status !== 'executing' && plan.status !== 'validating')
|
|
214
|
+
throw new Error(
|
|
215
|
+
`Cannot auto-reconcile plan in '${plan.status}' status — must be 'executing' or 'validating'`,
|
|
216
|
+
);
|
|
217
|
+
const result = buildAutoReconcileInput(plan.tasks);
|
|
218
|
+
if (!result.canAutoReconcile || !result.input) return null;
|
|
219
|
+
return this.reconcile(planId, result.input);
|
|
220
|
+
}
|
|
221
|
+
|
|
514
222
|
getExecuting(): Plan[] {
|
|
515
223
|
return this.store.plans.filter((p) => p.status === 'executing' || p.status === 'validating');
|
|
516
224
|
}
|
|
@@ -527,77 +235,17 @@ export class Planner {
|
|
|
527
235
|
);
|
|
528
236
|
}
|
|
529
237
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
* Only allowed on plans in 'draft' status.
|
|
533
|
-
*/
|
|
534
|
-
iterate(
|
|
535
|
-
planId: string,
|
|
536
|
-
changes: {
|
|
537
|
-
objective?: string;
|
|
538
|
-
scope?: string;
|
|
539
|
-
decisions?: (string | PlanDecision)[];
|
|
540
|
-
addTasks?: Array<{ title: string; description: string }>;
|
|
541
|
-
removeTasks?: string[];
|
|
542
|
-
approach?: string;
|
|
543
|
-
context?: string;
|
|
544
|
-
success_criteria?: string[];
|
|
545
|
-
tool_chain?: string[];
|
|
546
|
-
flow?: string;
|
|
547
|
-
target_mode?: string;
|
|
548
|
-
},
|
|
549
|
-
): Plan {
|
|
550
|
-
const plan = this.get(planId);
|
|
551
|
-
if (!plan) throw new Error(`Plan not found: ${planId}`);
|
|
238
|
+
iterate(planId: string, changes: IterateChanges): Plan {
|
|
239
|
+
const plan = this.requirePlan(planId);
|
|
552
240
|
if (plan.status !== 'draft' && plan.status !== 'brainstorming')
|
|
553
241
|
throw new Error(
|
|
554
242
|
`Cannot iterate plan in '${plan.status}' status — must be 'draft' or 'brainstorming'`,
|
|
555
243
|
);
|
|
556
|
-
|
|
557
|
-
const now = Date.now();
|
|
558
|
-
if (changes.objective !== undefined) plan.objective = changes.objective;
|
|
559
|
-
if (changes.scope !== undefined) plan.scope = changes.scope;
|
|
560
|
-
if (changes.decisions !== undefined) plan.decisions = changes.decisions;
|
|
561
|
-
if (changes.approach !== undefined) plan.approach = changes.approach;
|
|
562
|
-
if (changes.context !== undefined) plan.context = changes.context;
|
|
563
|
-
if (changes.success_criteria !== undefined) plan.success_criteria = changes.success_criteria;
|
|
564
|
-
if (changes.tool_chain !== undefined) plan.tool_chain = changes.tool_chain;
|
|
565
|
-
if (changes.flow !== undefined) plan.flow = changes.flow;
|
|
566
|
-
if (changes.target_mode !== undefined) plan.target_mode = changes.target_mode;
|
|
567
|
-
|
|
568
|
-
// Remove tasks by ID
|
|
569
|
-
if (changes.removeTasks && changes.removeTasks.length > 0) {
|
|
570
|
-
const removeSet = new Set(changes.removeTasks);
|
|
571
|
-
plan.tasks = plan.tasks.filter((t) => !removeSet.has(t.id));
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
// Add new tasks
|
|
575
|
-
if (changes.addTasks && changes.addTasks.length > 0) {
|
|
576
|
-
const maxIndex = plan.tasks.reduce((max, t) => {
|
|
577
|
-
const num = parseInt(t.id.replace('task-', ''), 10);
|
|
578
|
-
return isNaN(num) ? max : Math.max(max, num);
|
|
579
|
-
}, 0);
|
|
580
|
-
for (let i = 0; i < changes.addTasks.length; i++) {
|
|
581
|
-
plan.tasks.push({
|
|
582
|
-
id: `task-${maxIndex + i + 1}`,
|
|
583
|
-
title: changes.addTasks[i].title,
|
|
584
|
-
description: changes.addTasks[i].description,
|
|
585
|
-
status: 'pending',
|
|
586
|
-
updatedAt: now,
|
|
587
|
-
});
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
plan.updatedAt = now;
|
|
244
|
+
applyIteration(plan, changes);
|
|
592
245
|
this.save();
|
|
593
246
|
return plan;
|
|
594
247
|
}
|
|
595
248
|
|
|
596
|
-
/**
|
|
597
|
-
* Split a plan's tasks into sub-tasks with dependency tracking.
|
|
598
|
-
* Replaces existing tasks with a new set that includes dependency references.
|
|
599
|
-
* Only allowed on 'draft' or 'approved' plans.
|
|
600
|
-
*/
|
|
601
249
|
splitTasks(
|
|
602
250
|
planId: string,
|
|
603
251
|
tasks: Array<{
|
|
@@ -607,98 +255,16 @@ export class Planner {
|
|
|
607
255
|
acceptanceCriteria?: string[];
|
|
608
256
|
}>,
|
|
609
257
|
): Plan {
|
|
610
|
-
const plan = this.
|
|
611
|
-
if (!plan) throw new Error(`Plan not found: ${planId}`);
|
|
258
|
+
const plan = this.requirePlan(planId);
|
|
612
259
|
if (plan.status !== 'brainstorming' && plan.status !== 'draft' && plan.status !== 'approved')
|
|
613
260
|
throw new Error(
|
|
614
261
|
`Cannot split tasks on plan in '${plan.status}' status — must be 'brainstorming', 'draft', or 'approved'`,
|
|
615
262
|
);
|
|
616
|
-
|
|
617
|
-
const now = Date.now();
|
|
618
|
-
plan.tasks = tasks.map((t, i) => ({
|
|
619
|
-
id: `task-${i + 1}`,
|
|
620
|
-
title: t.title,
|
|
621
|
-
description: t.description,
|
|
622
|
-
status: 'pending' as TaskStatus,
|
|
623
|
-
dependsOn: t.dependsOn,
|
|
624
|
-
...(t.acceptanceCriteria && { acceptanceCriteria: t.acceptanceCriteria }),
|
|
625
|
-
updatedAt: now,
|
|
626
|
-
}));
|
|
627
|
-
|
|
628
|
-
// Validate dependency references
|
|
629
|
-
const taskIds = new Set(plan.tasks.map((t) => t.id));
|
|
630
|
-
for (const task of plan.tasks) {
|
|
631
|
-
if (task.dependsOn) {
|
|
632
|
-
for (const dep of task.dependsOn) {
|
|
633
|
-
if (!taskIds.has(dep)) {
|
|
634
|
-
throw new Error(`Task '${task.id}' depends on unknown task '${dep}'`);
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
plan.updatedAt = now;
|
|
263
|
+
applySplitTasks(plan, tasks);
|
|
641
264
|
this.save();
|
|
642
265
|
return plan;
|
|
643
266
|
}
|
|
644
267
|
|
|
645
|
-
/**
|
|
646
|
-
* Reconcile a plan — compare what was planned vs what actually happened.
|
|
647
|
-
* Uses impact-weighted drift scoring (ported from Salvador's calculateDriftScore).
|
|
648
|
-
*
|
|
649
|
-
* Transitions: executing → reconciling → completed (automatic).
|
|
650
|
-
* Also allowed from 'validating' and 'reconciling' states.
|
|
651
|
-
*/
|
|
652
|
-
reconcile(
|
|
653
|
-
planId: string,
|
|
654
|
-
report: {
|
|
655
|
-
actualOutcome: string;
|
|
656
|
-
driftItems?: DriftItem[];
|
|
657
|
-
/** Who initiated the reconciliation. */
|
|
658
|
-
reconciledBy?: 'human' | 'auto';
|
|
659
|
-
},
|
|
660
|
-
): Plan {
|
|
661
|
-
const plan = this.get(planId);
|
|
662
|
-
if (!plan) throw new Error(`Plan not found: ${planId}`);
|
|
663
|
-
if (
|
|
664
|
-
plan.status !== 'executing' &&
|
|
665
|
-
plan.status !== 'validating' &&
|
|
666
|
-
plan.status !== 'reconciling'
|
|
667
|
-
)
|
|
668
|
-
throw new Error(
|
|
669
|
-
`Cannot reconcile plan in '${plan.status}' status — must be 'executing', 'validating', or 'reconciling'`,
|
|
670
|
-
);
|
|
671
|
-
|
|
672
|
-
const driftItems = report.driftItems ?? [];
|
|
673
|
-
|
|
674
|
-
// Impact-weighted drift scoring (ported from Salvador)
|
|
675
|
-
const accuracy = calculateDriftScore(driftItems);
|
|
676
|
-
|
|
677
|
-
plan.reconciliation = {
|
|
678
|
-
planId,
|
|
679
|
-
accuracy,
|
|
680
|
-
driftItems,
|
|
681
|
-
summary: report.actualOutcome,
|
|
682
|
-
reconciledAt: Date.now(),
|
|
683
|
-
};
|
|
684
|
-
|
|
685
|
-
// Compute execution summary from per-task metrics
|
|
686
|
-
plan.executionSummary = this.computeExecutionSummary(plan);
|
|
687
|
-
|
|
688
|
-
// Transition through reconciling → completed via FSM
|
|
689
|
-
if (plan.status === 'executing' || plan.status === 'validating') {
|
|
690
|
-
plan.status = 'reconciling';
|
|
691
|
-
}
|
|
692
|
-
// Auto-complete after reconciliation
|
|
693
|
-
plan.status = 'completed';
|
|
694
|
-
plan.updatedAt = Date.now();
|
|
695
|
-
this.save();
|
|
696
|
-
return plan;
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
/**
|
|
700
|
-
* Add review evidence to a plan or specific task.
|
|
701
|
-
*/
|
|
702
268
|
addReview(
|
|
703
269
|
planId: string,
|
|
704
270
|
review: {
|
|
@@ -708,14 +274,8 @@ export class Planner {
|
|
|
708
274
|
comments: string;
|
|
709
275
|
},
|
|
710
276
|
): Plan {
|
|
711
|
-
const plan = this.
|
|
712
|
-
if (
|
|
713
|
-
|
|
714
|
-
if (review.taskId) {
|
|
715
|
-
const task = plan.tasks.find((t) => t.id === review.taskId);
|
|
716
|
-
if (!task) throw new Error(`Task not found: ${review.taskId}`);
|
|
717
|
-
}
|
|
718
|
-
|
|
277
|
+
const plan = this.requirePlan(planId);
|
|
278
|
+
if (review.taskId) this.requireTask(plan, review.taskId);
|
|
719
279
|
if (!plan.reviews) plan.reviews = [];
|
|
720
280
|
plan.reviews.push({
|
|
721
281
|
planId,
|
|
@@ -725,16 +285,27 @@ export class Planner {
|
|
|
725
285
|
comments: review.comments,
|
|
726
286
|
reviewedAt: Date.now(),
|
|
727
287
|
});
|
|
288
|
+
plan.updatedAt = Date.now();
|
|
289
|
+
this.save();
|
|
290
|
+
return plan;
|
|
291
|
+
}
|
|
728
292
|
|
|
293
|
+
setGitHubProjection(
|
|
294
|
+
planId: string,
|
|
295
|
+
projection: {
|
|
296
|
+
repo: string;
|
|
297
|
+
milestone?: number;
|
|
298
|
+
issues: Array<{ taskId: string; issueNumber: number }>;
|
|
299
|
+
projectedAt: number;
|
|
300
|
+
},
|
|
301
|
+
): Plan {
|
|
302
|
+
const plan = this.requirePlan(planId);
|
|
303
|
+
plan.githubProjection = projection;
|
|
729
304
|
plan.updatedAt = Date.now();
|
|
730
305
|
this.save();
|
|
731
306
|
return plan;
|
|
732
307
|
}
|
|
733
308
|
|
|
734
|
-
/**
|
|
735
|
-
* Get dispatch instructions for a specific task — returns the task and its
|
|
736
|
-
* unmet dependencies so a subagent knows what to work on and what to wait for.
|
|
737
|
-
*/
|
|
738
309
|
getDispatch(
|
|
739
310
|
planId: string,
|
|
740
311
|
taskId: string,
|
|
@@ -744,210 +315,77 @@ export class Planner {
|
|
|
744
315
|
ready: boolean;
|
|
745
316
|
deliverableStatus?: { count: number; staleCount: number };
|
|
746
317
|
} {
|
|
747
|
-
const plan = this.
|
|
748
|
-
|
|
749
|
-
const
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
if (task.dependsOn) {
|
|
754
|
-
for (const depId of task.dependsOn) {
|
|
755
|
-
const dep = plan.tasks.find((t) => t.id === depId);
|
|
756
|
-
if (dep && dep.status !== 'completed') {
|
|
757
|
-
unmetDependencies.push(dep);
|
|
758
|
-
}
|
|
759
|
-
}
|
|
318
|
+
const plan = this.requirePlan(planId);
|
|
319
|
+
const task = this.requireTask(plan, taskId);
|
|
320
|
+
const unmetDeps: PlanTask[] = [];
|
|
321
|
+
for (const depId of task.dependsOn ?? []) {
|
|
322
|
+
const dep = plan.tasks.find((t) => t.id === depId);
|
|
323
|
+
if (dep && dep.status !== 'completed') unmetDeps.push(dep);
|
|
760
324
|
}
|
|
761
|
-
|
|
762
|
-
const result: {
|
|
763
|
-
task: PlanTask;
|
|
764
|
-
unmetDependencies: PlanTask[];
|
|
765
|
-
ready: boolean;
|
|
766
|
-
deliverableStatus?: { count: number; staleCount: number };
|
|
767
|
-
} = {
|
|
768
|
-
task,
|
|
769
|
-
unmetDependencies,
|
|
770
|
-
ready: unmetDependencies.length === 0,
|
|
771
|
-
};
|
|
772
|
-
|
|
773
|
-
// Include deliverable status if deliverables exist
|
|
774
|
-
if (task.deliverables && task.deliverables.length > 0) {
|
|
775
|
-
result.deliverableStatus = {
|
|
776
|
-
count: task.deliverables.length,
|
|
777
|
-
staleCount: task.deliverables.filter((d) => d.stale).length,
|
|
778
|
-
};
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
return result;
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
// ─── Execution Metrics & Deliverables ──────────────────────────
|
|
785
|
-
|
|
786
|
-
/**
|
|
787
|
-
* Compute aggregate execution summary from per-task metrics.
|
|
788
|
-
* Called from reconcile() and complete() to populate plan.executionSummary.
|
|
789
|
-
*/
|
|
790
|
-
private computeExecutionSummary(plan: Plan): ExecutionSummary {
|
|
791
|
-
let totalDurationMs = 0;
|
|
792
|
-
let tasksCompleted = 0;
|
|
793
|
-
let tasksSkipped = 0;
|
|
794
|
-
let tasksFailed = 0;
|
|
795
|
-
let tasksWithDuration = 0;
|
|
796
|
-
|
|
797
|
-
for (const task of plan.tasks) {
|
|
798
|
-
if (task.status === 'completed') tasksCompleted++;
|
|
799
|
-
else if (task.status === 'skipped') tasksSkipped++;
|
|
800
|
-
else if (task.status === 'failed') tasksFailed++;
|
|
801
|
-
|
|
802
|
-
if (task.metrics?.durationMs) {
|
|
803
|
-
totalDurationMs += task.metrics.durationMs;
|
|
804
|
-
tasksWithDuration++;
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
|
|
808
325
|
return {
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
326
|
+
task,
|
|
327
|
+
unmetDependencies: unmetDeps,
|
|
328
|
+
ready: unmetDeps.length === 0,
|
|
329
|
+
...(task.deliverables?.length && {
|
|
330
|
+
deliverableStatus: {
|
|
331
|
+
count: task.deliverables.length,
|
|
332
|
+
staleCount: task.deliverables.filter((d) => d.stale).length,
|
|
333
|
+
},
|
|
334
|
+
}),
|
|
815
335
|
};
|
|
816
336
|
}
|
|
817
337
|
|
|
818
|
-
/**
|
|
819
|
-
* Submit a deliverable for a task. Auto-computes SHA-256 hash for file deliverables.
|
|
820
|
-
*/
|
|
821
338
|
submitDeliverable(
|
|
822
339
|
planId: string,
|
|
823
340
|
taskId: string,
|
|
824
|
-
deliverable: { type:
|
|
341
|
+
deliverable: { type: 'file' | 'vault_entry' | 'url'; path: string; hash?: string },
|
|
825
342
|
): PlanTask {
|
|
826
|
-
const plan = this.
|
|
827
|
-
|
|
828
|
-
const task = plan.tasks.find((t) => t.id === taskId);
|
|
829
|
-
if (!task) throw new Error(`Task not found: ${taskId}`);
|
|
830
|
-
|
|
831
|
-
const entry: TaskDeliverable = {
|
|
832
|
-
type: deliverable.type,
|
|
833
|
-
path: deliverable.path,
|
|
834
|
-
};
|
|
835
|
-
|
|
836
|
-
// Auto-compute hash for file deliverables
|
|
837
|
-
if (deliverable.type === 'file' && !deliverable.hash) {
|
|
838
|
-
try {
|
|
839
|
-
if (existsSync(deliverable.path)) {
|
|
840
|
-
const content = readFileSync(deliverable.path);
|
|
841
|
-
entry.hash = createHash('sha256').update(content).digest('hex');
|
|
842
|
-
}
|
|
843
|
-
} catch {
|
|
844
|
-
// Graceful degradation — skip hash if file can't be read
|
|
845
|
-
}
|
|
846
|
-
} else if (deliverable.hash) {
|
|
847
|
-
entry.hash = deliverable.hash;
|
|
848
|
-
}
|
|
849
|
-
|
|
343
|
+
const plan = this.requirePlan(planId);
|
|
344
|
+
const task = this.requireTask(plan, taskId);
|
|
850
345
|
if (!task.deliverables) task.deliverables = [];
|
|
851
|
-
task.deliverables.push(
|
|
346
|
+
task.deliverables.push(createDeliverable(deliverable));
|
|
852
347
|
task.updatedAt = Date.now();
|
|
853
348
|
plan.updatedAt = Date.now();
|
|
854
349
|
this.save();
|
|
855
350
|
return task;
|
|
856
351
|
}
|
|
857
352
|
|
|
858
|
-
/**
|
|
859
|
-
* Verify all deliverables for a task.
|
|
860
|
-
* - file: checks existsSync + SHA-256 hash match
|
|
861
|
-
* - vault_entry: checks vault.get(path) non-null (requires vault instance)
|
|
862
|
-
* - url: skips (just records, no fetch)
|
|
863
|
-
*/
|
|
864
353
|
verifyDeliverables(
|
|
865
354
|
planId: string,
|
|
866
355
|
taskId: string,
|
|
867
356
|
vault?: { get(id: string): unknown | null },
|
|
868
|
-
): {
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
const
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
for (const d of deliverables) {
|
|
879
|
-
d.stale = false;
|
|
880
|
-
|
|
881
|
-
if (d.type === 'file') {
|
|
882
|
-
if (!existsSync(d.path)) {
|
|
883
|
-
d.stale = true;
|
|
884
|
-
staleCount++;
|
|
885
|
-
} else if (d.hash) {
|
|
886
|
-
try {
|
|
887
|
-
const content = readFileSync(d.path);
|
|
888
|
-
const currentHash = createHash('sha256').update(content).digest('hex');
|
|
889
|
-
if (currentHash !== d.hash) {
|
|
890
|
-
d.stale = true;
|
|
891
|
-
staleCount++;
|
|
892
|
-
}
|
|
893
|
-
} catch {
|
|
894
|
-
d.stale = true;
|
|
895
|
-
staleCount++;
|
|
896
|
-
}
|
|
897
|
-
}
|
|
898
|
-
d.verifiedAt = now;
|
|
899
|
-
} else if (d.type === 'vault_entry') {
|
|
900
|
-
if (vault) {
|
|
901
|
-
const entry = vault.get(d.path);
|
|
902
|
-
if (!entry) {
|
|
903
|
-
d.stale = true;
|
|
904
|
-
staleCount++;
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
d.verifiedAt = now;
|
|
908
|
-
}
|
|
909
|
-
// url: skip — just record
|
|
910
|
-
}
|
|
911
|
-
|
|
357
|
+
): {
|
|
358
|
+
verified: boolean;
|
|
359
|
+
deliverables: import('./planner-types.js').TaskDeliverable[];
|
|
360
|
+
staleCount: number;
|
|
361
|
+
} {
|
|
362
|
+
const plan = this.requirePlan(planId);
|
|
363
|
+
const task = this.requireTask(plan, taskId);
|
|
364
|
+
const result = verifyDeliverablesLogic(task.deliverables ?? [], vault);
|
|
365
|
+
task.deliverables = result.deliverables;
|
|
912
366
|
plan.updatedAt = Date.now();
|
|
913
367
|
this.save();
|
|
914
|
-
|
|
915
|
-
return { verified: staleCount === 0, deliverables, staleCount };
|
|
368
|
+
return result;
|
|
916
369
|
}
|
|
917
370
|
|
|
918
|
-
// ─── Evidence & Verification ────────────────────────────────────
|
|
919
|
-
|
|
920
|
-
/**
|
|
921
|
-
* Submit evidence for a task acceptance criterion.
|
|
922
|
-
* Evidence is stored on the task and used by verifyTask() to check completeness.
|
|
923
|
-
*/
|
|
924
371
|
submitEvidence(
|
|
925
372
|
planId: string,
|
|
926
373
|
taskId: string,
|
|
927
|
-
evidence: {
|
|
374
|
+
evidence: {
|
|
375
|
+
criterion: string;
|
|
376
|
+
content: string;
|
|
377
|
+
type: import('./planner-types.js').TaskEvidence['type'];
|
|
378
|
+
},
|
|
928
379
|
): PlanTask {
|
|
929
|
-
const plan = this.
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
if (!task) throw new Error(`Task not found: ${taskId}`);
|
|
933
|
-
if (!task.evidence) task.evidence = [];
|
|
934
|
-
task.evidence.push({
|
|
935
|
-
criterion: evidence.criterion,
|
|
936
|
-
content: evidence.content,
|
|
937
|
-
type: evidence.type,
|
|
938
|
-
submittedAt: Date.now(),
|
|
939
|
-
});
|
|
380
|
+
const plan = this.requirePlan(planId);
|
|
381
|
+
const task = this.requireTask(plan, taskId);
|
|
382
|
+
task.evidence = createEvidence(task.evidence ?? [], evidence);
|
|
940
383
|
task.updatedAt = Date.now();
|
|
941
384
|
plan.updatedAt = Date.now();
|
|
942
385
|
this.save();
|
|
943
386
|
return task;
|
|
944
387
|
}
|
|
945
388
|
|
|
946
|
-
/**
|
|
947
|
-
* Verify a task — check that evidence exists for all acceptance criteria
|
|
948
|
-
* and any reviews have passed.
|
|
949
|
-
* Returns verification status with details.
|
|
950
|
-
*/
|
|
951
389
|
verifyTask(
|
|
952
390
|
planId: string,
|
|
953
391
|
taskId: string,
|
|
@@ -957,270 +395,45 @@ export class Planner {
|
|
|
957
395
|
missingCriteria: string[];
|
|
958
396
|
reviewStatus: 'approved' | 'rejected' | 'needs_changes' | 'no_reviews';
|
|
959
397
|
} {
|
|
960
|
-
const plan = this.
|
|
961
|
-
|
|
962
|
-
const
|
|
963
|
-
if (
|
|
964
|
-
|
|
965
|
-
// Check evidence coverage
|
|
966
|
-
const criteria = task.acceptanceCriteria ?? [];
|
|
967
|
-
const evidencedCriteria = new Set((task.evidence ?? []).map((e) => e.criterion));
|
|
968
|
-
const missingCriteria = criteria.filter((c) => !evidencedCriteria.has(c));
|
|
969
|
-
|
|
970
|
-
// Check task-level reviews
|
|
971
|
-
const taskReviews = (plan.reviews ?? []).filter((r) => r.taskId === taskId);
|
|
972
|
-
let reviewStatus: 'approved' | 'rejected' | 'needs_changes' | 'no_reviews' = 'no_reviews';
|
|
973
|
-
if (taskReviews.length > 0) {
|
|
974
|
-
const latest = taskReviews[taskReviews.length - 1];
|
|
975
|
-
reviewStatus = latest.outcome;
|
|
976
|
-
}
|
|
977
|
-
|
|
978
|
-
const verified =
|
|
979
|
-
task.status === 'completed' &&
|
|
980
|
-
missingCriteria.length === 0 &&
|
|
981
|
-
(reviewStatus === 'approved' || reviewStatus === 'no_reviews');
|
|
982
|
-
|
|
983
|
-
if (verified !== task.verified) {
|
|
984
|
-
task.verified = verified;
|
|
398
|
+
const plan = this.requirePlan(planId);
|
|
399
|
+
const task = this.requireTask(plan, taskId);
|
|
400
|
+
const result = verifyTaskLogic(task, plan.reviews ?? []);
|
|
401
|
+
if (result.verified !== task.verified) {
|
|
402
|
+
task.verified = result.verified;
|
|
985
403
|
task.updatedAt = Date.now();
|
|
986
404
|
plan.updatedAt = Date.now();
|
|
987
405
|
this.save();
|
|
988
406
|
}
|
|
989
|
-
|
|
990
|
-
return { verified, task, missingCriteria, reviewStatus };
|
|
991
|
-
}
|
|
992
|
-
|
|
993
|
-
/**
|
|
994
|
-
* Verify an entire plan — check all tasks are in a final state,
|
|
995
|
-
* all verification-required tasks have evidence, no tasks stuck in_progress.
|
|
996
|
-
* Returns a validation report.
|
|
997
|
-
*/
|
|
998
|
-
verifyPlan(planId: string): {
|
|
999
|
-
valid: boolean;
|
|
1000
|
-
planId: string;
|
|
1001
|
-
issues: Array<{ taskId: string; issue: string }>;
|
|
1002
|
-
summary: {
|
|
1003
|
-
total: number;
|
|
1004
|
-
completed: number;
|
|
1005
|
-
skipped: number;
|
|
1006
|
-
failed: number;
|
|
1007
|
-
pending: number;
|
|
1008
|
-
inProgress: number;
|
|
1009
|
-
verified: number;
|
|
1010
|
-
};
|
|
1011
|
-
} {
|
|
1012
|
-
const plan = this.get(planId);
|
|
1013
|
-
if (!plan) throw new Error(`Plan not found: ${planId}`);
|
|
1014
|
-
|
|
1015
|
-
const issues: Array<{ taskId: string; issue: string }> = [];
|
|
1016
|
-
let verified = 0;
|
|
1017
|
-
let completed = 0;
|
|
1018
|
-
let skipped = 0;
|
|
1019
|
-
let failed = 0;
|
|
1020
|
-
let pending = 0;
|
|
1021
|
-
let inProgress = 0;
|
|
1022
|
-
|
|
1023
|
-
for (const task of plan.tasks) {
|
|
1024
|
-
switch (task.status) {
|
|
1025
|
-
case 'completed':
|
|
1026
|
-
completed++;
|
|
1027
|
-
break;
|
|
1028
|
-
case 'skipped':
|
|
1029
|
-
skipped++;
|
|
1030
|
-
break;
|
|
1031
|
-
case 'failed':
|
|
1032
|
-
failed++;
|
|
1033
|
-
break;
|
|
1034
|
-
case 'pending':
|
|
1035
|
-
pending++;
|
|
1036
|
-
break;
|
|
1037
|
-
case 'in_progress':
|
|
1038
|
-
inProgress++;
|
|
1039
|
-
break;
|
|
1040
|
-
}
|
|
1041
|
-
|
|
1042
|
-
if (task.verified) verified++;
|
|
1043
|
-
|
|
1044
|
-
// Check for stuck tasks
|
|
1045
|
-
if (task.status === 'in_progress') {
|
|
1046
|
-
issues.push({ taskId: task.id, issue: 'Task stuck in in_progress state' });
|
|
1047
|
-
}
|
|
1048
|
-
if (task.status === 'pending') {
|
|
1049
|
-
issues.push({ taskId: task.id, issue: 'Task still pending — not started' });
|
|
1050
|
-
}
|
|
1051
|
-
|
|
1052
|
-
// Check evidence for completed tasks with acceptance criteria
|
|
1053
|
-
if (
|
|
1054
|
-
task.status === 'completed' &&
|
|
1055
|
-
task.acceptanceCriteria &&
|
|
1056
|
-
task.acceptanceCriteria.length > 0
|
|
1057
|
-
) {
|
|
1058
|
-
const evidencedCriteria = new Set((task.evidence ?? []).map((e) => e.criterion));
|
|
1059
|
-
const missing = task.acceptanceCriteria.filter((c) => !evidencedCriteria.has(c));
|
|
1060
|
-
if (missing.length > 0) {
|
|
1061
|
-
issues.push({
|
|
1062
|
-
taskId: task.id,
|
|
1063
|
-
issue: `Missing evidence for ${missing.length} criteria: ${missing.join(', ')}`,
|
|
1064
|
-
});
|
|
1065
|
-
}
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
const valid = issues.length === 0 && pending === 0 && inProgress === 0;
|
|
1070
|
-
|
|
1071
|
-
return {
|
|
1072
|
-
valid,
|
|
1073
|
-
planId,
|
|
1074
|
-
issues,
|
|
1075
|
-
summary: {
|
|
1076
|
-
total: plan.tasks.length,
|
|
1077
|
-
completed,
|
|
1078
|
-
skipped,
|
|
1079
|
-
failed,
|
|
1080
|
-
pending,
|
|
1081
|
-
inProgress,
|
|
1082
|
-
verified,
|
|
1083
|
-
},
|
|
1084
|
-
};
|
|
407
|
+
return { ...result, task };
|
|
1085
408
|
}
|
|
1086
409
|
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
* Checks all tasks are in final state, generates reconciliation report automatically.
|
|
1090
|
-
* Returns null if drift is too significant for auto-reconciliation (>2 non-completed tasks).
|
|
1091
|
-
*/
|
|
1092
|
-
autoReconcile(planId: string): Plan | null {
|
|
1093
|
-
const plan = this.get(planId);
|
|
1094
|
-
if (!plan) throw new Error(`Plan not found: ${planId}`);
|
|
1095
|
-
if (plan.status !== 'executing' && plan.status !== 'validating')
|
|
1096
|
-
throw new Error(
|
|
1097
|
-
`Cannot auto-reconcile plan in '${plan.status}' status — must be 'executing' or 'validating'`,
|
|
1098
|
-
);
|
|
1099
|
-
|
|
1100
|
-
const completed = plan.tasks.filter((t) => t.status === 'completed').length;
|
|
1101
|
-
const skipped = plan.tasks.filter((t) => t.status === 'skipped').length;
|
|
1102
|
-
const failed = plan.tasks.filter((t) => t.status === 'failed').length;
|
|
1103
|
-
const pending = plan.tasks.filter((t) => t.status === 'pending').length;
|
|
1104
|
-
const inProgress = plan.tasks.filter((t) => t.status === 'in_progress').length;
|
|
1105
|
-
|
|
1106
|
-
// Can't auto-reconcile if tasks are still in progress
|
|
1107
|
-
if (inProgress > 0) return null;
|
|
1108
|
-
// Can't auto-reconcile if too many non-completed tasks
|
|
1109
|
-
if (pending + failed > 2) return null;
|
|
1110
|
-
|
|
1111
|
-
const driftItems: DriftItem[] = [];
|
|
1112
|
-
|
|
1113
|
-
for (const task of plan.tasks) {
|
|
1114
|
-
if (task.status === 'skipped') {
|
|
1115
|
-
driftItems.push({
|
|
1116
|
-
type: 'skipped',
|
|
1117
|
-
description: `Task '${task.title}' was skipped`,
|
|
1118
|
-
impact: 'medium',
|
|
1119
|
-
rationale: 'Task not executed during plan implementation',
|
|
1120
|
-
});
|
|
1121
|
-
} else if (task.status === 'failed') {
|
|
1122
|
-
driftItems.push({
|
|
1123
|
-
type: 'modified',
|
|
1124
|
-
description: `Task '${task.title}' failed`,
|
|
1125
|
-
impact: 'high',
|
|
1126
|
-
rationale: 'Task execution failed',
|
|
1127
|
-
});
|
|
1128
|
-
} else if (task.status === 'pending') {
|
|
1129
|
-
driftItems.push({
|
|
1130
|
-
type: 'skipped',
|
|
1131
|
-
description: `Task '${task.title}' was never started`,
|
|
1132
|
-
impact: 'low',
|
|
1133
|
-
rationale: 'Task left in pending state',
|
|
1134
|
-
});
|
|
1135
|
-
}
|
|
1136
|
-
}
|
|
1137
|
-
|
|
1138
|
-
return this.reconcile(planId, {
|
|
1139
|
-
actualOutcome: `Auto-reconciled: ${completed}/${plan.tasks.length} tasks completed, ${skipped} skipped, ${failed} failed`,
|
|
1140
|
-
driftItems,
|
|
1141
|
-
reconciledBy: 'auto',
|
|
1142
|
-
});
|
|
410
|
+
verifyPlan(planId: string) {
|
|
411
|
+
return verifyPlanLogic(planId, this.requirePlan(planId).tasks);
|
|
1143
412
|
}
|
|
1144
413
|
|
|
1145
|
-
/**
|
|
1146
|
-
* Generate a review prompt for spec compliance checking.
|
|
1147
|
-
* Used by subagent dispatch — the controller generates the prompt, a subagent executes it.
|
|
1148
|
-
*/
|
|
1149
414
|
generateReviewSpec(
|
|
1150
415
|
planId: string,
|
|
1151
416
|
taskId: string,
|
|
1152
417
|
): { prompt: string; task: PlanTask; plan: Plan } {
|
|
1153
|
-
const plan = this.
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
if (!task) throw new Error(`Task not found: ${taskId}`);
|
|
1157
|
-
|
|
1158
|
-
const criteria = task.acceptanceCriteria?.length
|
|
1159
|
-
? `\n\nAcceptance Criteria:\n${task.acceptanceCriteria.map((c, i) => `${i + 1}. ${c}`).join('\n')}`
|
|
1160
|
-
: '';
|
|
1161
|
-
|
|
1162
|
-
const prompt = [
|
|
1163
|
-
`# Spec Compliance Review`,
|
|
1164
|
-
``,
|
|
1165
|
-
`## Task: ${task.title}`,
|
|
1166
|
-
`**Description:** ${task.description}`,
|
|
1167
|
-
`**Plan Objective:** ${plan.objective}${criteria}`,
|
|
1168
|
-
``,
|
|
1169
|
-
`## Review Checklist`,
|
|
1170
|
-
`1. Does the implementation match the task description?`,
|
|
1171
|
-
`2. Are all acceptance criteria satisfied?`,
|
|
1172
|
-
`3. Does it align with the plan's overall objective?`,
|
|
1173
|
-
`4. Are there any spec deviations?`,
|
|
1174
|
-
``,
|
|
1175
|
-
`Provide: outcome (approved/rejected/needs_changes) and detailed comments.`,
|
|
1176
|
-
].join('\n');
|
|
1177
|
-
|
|
1178
|
-
return { prompt, task, plan };
|
|
418
|
+
const plan = this.requirePlan(planId);
|
|
419
|
+
const task = this.requireTask(plan, taskId);
|
|
420
|
+
return { prompt: buildSpecReviewPrompt(task, plan.objective), task, plan };
|
|
1179
421
|
}
|
|
1180
422
|
|
|
1181
|
-
/**
|
|
1182
|
-
* Generate a review prompt for code quality checking.
|
|
1183
|
-
*/
|
|
1184
423
|
generateReviewQuality(
|
|
1185
424
|
planId: string,
|
|
1186
425
|
taskId: string,
|
|
1187
426
|
): { prompt: string; task: PlanTask; plan: Plan } {
|
|
1188
|
-
const plan = this.
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
if (!task) throw new Error(`Task not found: ${taskId}`);
|
|
1192
|
-
|
|
1193
|
-
const prompt = [
|
|
1194
|
-
`# Code Quality Review`,
|
|
1195
|
-
``,
|
|
1196
|
-
`## Task: ${task.title}`,
|
|
1197
|
-
`**Description:** ${task.description}`,
|
|
1198
|
-
``,
|
|
1199
|
-
`## Quality Checklist`,
|
|
1200
|
-
`1. **Correctness** — Does it work as intended?`,
|
|
1201
|
-
`2. **Security** — No injection, XSS, or OWASP top 10 vulnerabilities?`,
|
|
1202
|
-
`3. **Performance** — No unnecessary allocations, N+1 queries, or blocking calls?`,
|
|
1203
|
-
`4. **Maintainability** — Clear naming, appropriate abstractions, documented intent?`,
|
|
1204
|
-
`5. **Testing** — Adequate test coverage for the changes?`,
|
|
1205
|
-
`6. **Error Handling** — Graceful degradation, no swallowed errors?`,
|
|
1206
|
-
`7. **Conventions** — Follows project coding standards?`,
|
|
1207
|
-
``,
|
|
1208
|
-
`Provide: outcome (approved/rejected/needs_changes) and detailed comments.`,
|
|
1209
|
-
].join('\n');
|
|
1210
|
-
|
|
1211
|
-
return { prompt, task, plan };
|
|
427
|
+
const plan = this.requirePlan(planId);
|
|
428
|
+
const task = this.requireTask(plan, taskId);
|
|
429
|
+
return { prompt: buildQualityReviewPrompt(task), task, plan };
|
|
1212
430
|
}
|
|
1213
431
|
|
|
1214
|
-
/**
|
|
1215
|
-
* Archive completed plans — transitions them to 'archived' status.
|
|
1216
|
-
* If olderThanDays is provided, only archives plans older than that.
|
|
1217
|
-
* Returns the archived plans.
|
|
1218
|
-
*/
|
|
1219
432
|
archive(olderThanDays?: number): Plan[] {
|
|
1220
433
|
const cutoff =
|
|
1221
434
|
olderThanDays !== undefined
|
|
1222
435
|
? Date.now() - olderThanDays * 24 * 60 * 60 * 1000
|
|
1223
|
-
: Date.now() + 1;
|
|
436
|
+
: Date.now() + 1;
|
|
1224
437
|
const toArchive = this.store.plans.filter(
|
|
1225
438
|
(p) => p.status === 'completed' && p.updatedAt < cutoff,
|
|
1226
439
|
);
|
|
@@ -1228,15 +441,10 @@ export class Planner {
|
|
|
1228
441
|
plan.status = 'archived';
|
|
1229
442
|
plan.updatedAt = Date.now();
|
|
1230
443
|
}
|
|
1231
|
-
if (toArchive.length > 0)
|
|
1232
|
-
this.save();
|
|
1233
|
-
}
|
|
444
|
+
if (toArchive.length > 0) this.save();
|
|
1234
445
|
return toArchive;
|
|
1235
446
|
}
|
|
1236
447
|
|
|
1237
|
-
/**
|
|
1238
|
-
* Get statistics about all plans.
|
|
1239
|
-
*/
|
|
1240
448
|
stats(): {
|
|
1241
449
|
total: number;
|
|
1242
450
|
byStatus: Record<PlanStatus, number>;
|
|
@@ -1245,7 +453,7 @@ export class Planner {
|
|
|
1245
453
|
tasksByStatus: Record<TaskStatus, number>;
|
|
1246
454
|
} {
|
|
1247
455
|
const plans = this.store.plans;
|
|
1248
|
-
const byStatus
|
|
456
|
+
const byStatus = {
|
|
1249
457
|
brainstorming: 0,
|
|
1250
458
|
draft: 0,
|
|
1251
459
|
approved: 0,
|
|
@@ -1254,58 +462,33 @@ export class Planner {
|
|
|
1254
462
|
reconciling: 0,
|
|
1255
463
|
completed: 0,
|
|
1256
464
|
archived: 0,
|
|
1257
|
-
}
|
|
1258
|
-
const tasksByStatus
|
|
465
|
+
} as Record<PlanStatus, number>;
|
|
466
|
+
const tasksByStatus = {
|
|
1259
467
|
pending: 0,
|
|
1260
468
|
in_progress: 0,
|
|
1261
469
|
completed: 0,
|
|
1262
470
|
skipped: 0,
|
|
1263
471
|
failed: 0,
|
|
1264
|
-
}
|
|
472
|
+
} as Record<TaskStatus, number>;
|
|
1265
473
|
let totalTasks = 0;
|
|
1266
|
-
|
|
1267
474
|
for (const p of plans) {
|
|
1268
475
|
byStatus[p.status]++;
|
|
1269
476
|
totalTasks += p.tasks.length;
|
|
1270
|
-
for (const t of p.tasks)
|
|
1271
|
-
tasksByStatus[t.status]++;
|
|
1272
|
-
}
|
|
477
|
+
for (const t of p.tasks) tasksByStatus[t.status]++;
|
|
1273
478
|
}
|
|
1274
|
-
|
|
1275
479
|
return {
|
|
1276
480
|
total: plans.length,
|
|
1277
481
|
byStatus,
|
|
1278
|
-
avgTasksPerPlan: plans.length > 0 ? Math.round((totalTasks / plans.length) * 100) / 100 : 0,
|
|
1279
482
|
totalTasks,
|
|
1280
483
|
tasksByStatus,
|
|
484
|
+
avgTasksPerPlan: plans.length > 0 ? Math.round((totalTasks / plans.length) * 100) / 100 : 0,
|
|
1281
485
|
};
|
|
1282
486
|
}
|
|
1283
487
|
|
|
1284
|
-
// ─── Grading ──────────────────────────────────────────────────────
|
|
1285
|
-
|
|
1286
|
-
/**
|
|
1287
|
-
* Grade a plan using gap analysis with severity-weighted scoring.
|
|
1288
|
-
* Ported from Salvador MCP's multi-pass grading engine.
|
|
1289
|
-
*
|
|
1290
|
-
* 6 built-in passes + optional custom passes (domain-specific checks).
|
|
1291
|
-
*
|
|
1292
|
-
* Scoring:
|
|
1293
|
-
* - Each gap has a severity (critical=30, major=15, minor=2, info=0)
|
|
1294
|
-
* - Deductions are per-category with optional caps
|
|
1295
|
-
* - Iteration leniency: minor gaps free on iter 1, half on iter 2, full on 3+
|
|
1296
|
-
* - Score = max(0, 100 - deductions)
|
|
1297
|
-
*
|
|
1298
|
-
* Grade thresholds: A+=95, A=90, B=80, C=70, D=60, F=<60
|
|
1299
|
-
*/
|
|
1300
488
|
grade(planId: string): PlanCheck {
|
|
1301
|
-
const plan = this.
|
|
1302
|
-
if (!plan) throw new Error(`Plan not found: ${planId}`);
|
|
1303
|
-
|
|
1304
|
-
// Run 6-pass gap analysis
|
|
489
|
+
const plan = this.requirePlan(planId);
|
|
1305
490
|
const gaps = runGapAnalysis(plan, this.gapOptions);
|
|
1306
|
-
|
|
1307
|
-
// Add circular dependency check (structural, not covered by gap-analysis passes)
|
|
1308
|
-
if (this.hasCircularDependencies(plan)) {
|
|
491
|
+
if (hasCircularDependencies(plan.tasks)) {
|
|
1309
492
|
gaps.push({
|
|
1310
493
|
id: `gap_${Date.now()}_circ`,
|
|
1311
494
|
severity: 'critical',
|
|
@@ -1316,108 +499,34 @@ export class Planner {
|
|
|
1316
499
|
_trigger: 'circular_dependencies',
|
|
1317
500
|
});
|
|
1318
501
|
}
|
|
1319
|
-
|
|
1320
|
-
// Iteration = number of previous checks + 1
|
|
1321
502
|
const iteration = plan.checks.length + 1;
|
|
1322
503
|
const score = calculateScore(gaps, iteration);
|
|
1323
|
-
const grade = this.scoreToGrade(score);
|
|
1324
|
-
|
|
1325
504
|
const check: PlanCheck = {
|
|
1326
505
|
checkId: `chk-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
1327
506
|
planId,
|
|
1328
|
-
grade,
|
|
507
|
+
grade: scoreToGrade(score),
|
|
1329
508
|
score,
|
|
1330
509
|
gaps,
|
|
1331
510
|
iteration,
|
|
1332
511
|
checkedAt: Date.now(),
|
|
1333
512
|
};
|
|
1334
|
-
|
|
1335
513
|
plan.checks.push(check);
|
|
1336
514
|
plan.latestCheck = check;
|
|
1337
515
|
plan.updatedAt = Date.now();
|
|
1338
516
|
this.save();
|
|
1339
|
-
|
|
1340
517
|
return check;
|
|
1341
518
|
}
|
|
1342
519
|
|
|
1343
|
-
/**
|
|
1344
|
-
* Get the latest check for a plan.
|
|
1345
|
-
*/
|
|
1346
520
|
getLatestCheck(planId: string): PlanCheck | null {
|
|
1347
|
-
|
|
1348
|
-
if (!plan) throw new Error(`Plan not found: ${planId}`);
|
|
1349
|
-
return plan.latestCheck ?? null;
|
|
521
|
+
return this.requirePlan(planId).latestCheck ?? null;
|
|
1350
522
|
}
|
|
1351
523
|
|
|
1352
|
-
/**
|
|
1353
|
-
* Get all checks for a plan (history).
|
|
1354
|
-
*/
|
|
1355
524
|
getCheckHistory(planId: string): PlanCheck[] {
|
|
1356
|
-
|
|
1357
|
-
if (!plan) throw new Error(`Plan not found: ${planId}`);
|
|
1358
|
-
return [...plan.checks];
|
|
525
|
+
return [...this.requirePlan(planId).checks];
|
|
1359
526
|
}
|
|
1360
527
|
|
|
1361
|
-
/**
|
|
1362
|
-
* Auto-grade: grade the plan and return whether it meets a target grade.
|
|
1363
|
-
*/
|
|
1364
528
|
meetsGrade(planId: string, targetGrade: PlanGrade): { meets: boolean; check: PlanCheck } {
|
|
1365
529
|
const check = this.grade(planId);
|
|
1366
|
-
|
|
1367
|
-
return { meets: check.score >= targetScore, check };
|
|
1368
|
-
}
|
|
1369
|
-
|
|
1370
|
-
// ─── Grading Helpers ──────────────────────────────────────────────
|
|
1371
|
-
|
|
1372
|
-
private scoreToGrade(score: number): PlanGrade {
|
|
1373
|
-
if (score >= 95) return 'A+';
|
|
1374
|
-
if (score >= 90) return 'A';
|
|
1375
|
-
if (score >= 80) return 'B';
|
|
1376
|
-
if (score >= 70) return 'C';
|
|
1377
|
-
if (score >= 60) return 'D';
|
|
1378
|
-
return 'F';
|
|
1379
|
-
}
|
|
1380
|
-
|
|
1381
|
-
private gradeToMinScore(grade: PlanGrade): number {
|
|
1382
|
-
switch (grade) {
|
|
1383
|
-
case 'A+':
|
|
1384
|
-
return 95;
|
|
1385
|
-
case 'A':
|
|
1386
|
-
return 90;
|
|
1387
|
-
case 'B':
|
|
1388
|
-
return 80;
|
|
1389
|
-
case 'C':
|
|
1390
|
-
return 70;
|
|
1391
|
-
case 'D':
|
|
1392
|
-
return 60;
|
|
1393
|
-
case 'F':
|
|
1394
|
-
return 0;
|
|
1395
|
-
}
|
|
1396
|
-
}
|
|
1397
|
-
|
|
1398
|
-
private hasCircularDependencies(plan: Plan): boolean {
|
|
1399
|
-
const visited = new Set<string>();
|
|
1400
|
-
const inStack = new Set<string>();
|
|
1401
|
-
const taskMap = new Map(plan.tasks.map((t) => [t.id, t]));
|
|
1402
|
-
|
|
1403
|
-
const dfs = (taskId: string): boolean => {
|
|
1404
|
-
if (inStack.has(taskId)) return true;
|
|
1405
|
-
if (visited.has(taskId)) return false;
|
|
1406
|
-
visited.add(taskId);
|
|
1407
|
-
inStack.add(taskId);
|
|
1408
|
-
const task = taskMap.get(taskId);
|
|
1409
|
-
if (task?.dependsOn) {
|
|
1410
|
-
for (const dep of task.dependsOn) {
|
|
1411
|
-
if (dfs(dep)) return true;
|
|
1412
|
-
}
|
|
1413
|
-
}
|
|
1414
|
-
inStack.delete(taskId);
|
|
1415
|
-
return false;
|
|
1416
|
-
};
|
|
1417
|
-
|
|
1418
|
-
for (const task of plan.tasks) {
|
|
1419
|
-
if (dfs(task.id)) return true;
|
|
1420
|
-
}
|
|
1421
|
-
return false;
|
|
530
|
+
return { meets: check.score >= gradeToMinScore(targetGrade), check };
|
|
1422
531
|
}
|
|
1423
532
|
}
|