@soleri/core 9.0.3 → 9.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +90 -525
- 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 +55 -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 +33 -10
- 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 +22 -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 +26 -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 +377 -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 +157 -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 +129 -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 +294 -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 +199 -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 +296 -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 +154 -878
- 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 +227 -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 +21 -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/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/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 +330 -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 +6 -0
- package/dist/runtime/facades/index.js.map +1 -1
- 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 +39 -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/vault-facade.d.ts +4 -1
- package/dist/runtime/facades/vault-facade.d.ts.map +1 -1
- package/dist/runtime/facades/vault-facade.js +13 -66
- 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 +113 -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 +367 -40
- 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 +27 -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 +68 -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/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 +104 -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 +257 -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 +142 -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 +240 -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 +179 -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 +78 -931
- package/dist/vault/vault.js.map +1 -1
- package/package.json +1 -1
- package/src/agency/agency-manager.test.ts +600 -0
- package/src/agency/default-rules.test.ts +228 -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/brain/strength-scorer.ts +404 -0
- package/src/capabilities/chain-mapping.test.ts +66 -0
- package/src/capabilities/registry.test.ts +369 -0
- package/src/chat/agent-loop.test.ts +394 -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 +173 -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 +211 -0
- package/src/context/context-engine.test.ts +461 -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 +104 -0
- package/src/curator/contradiction-detector.test.ts +180 -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 +115 -777
- package/src/curator/duplicate-detector.test.ts +183 -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 +135 -0
- package/src/curator/schema.ts +65 -0
- package/src/curator/tag-manager.test.ts +165 -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 +171 -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 +150 -0
- package/src/domain-packs/types.test.ts +130 -0
- package/src/enforcement/adapters/claude-code.test.ts +216 -0
- package/src/enforcement/registry.test.ts +264 -0
- package/src/engine/bin/soleri-engine.ts +59 -3
- package/src/engine/core-ops.test.ts +254 -0
- package/src/engine/core-ops.ts +35 -10
- package/src/engine/module-manifest.test.ts +124 -0
- package/src/engine/module-manifest.ts +22 -2
- package/src/engine/register-engine.test.ts +230 -0
- package/src/engine/register-engine.ts +26 -2
- package/src/errors/classify.test.ts +199 -0
- package/src/errors/retry.test.ts +156 -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 +424 -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 +107 -0
- package/src/flows/executor.test.ts +263 -0
- package/src/flows/gate-evaluator.test.ts +194 -0
- package/src/flows/gate-evaluator.ts +25 -0
- package/src/flows/types.ts +4 -0
- package/src/governance/governance.test.ts +726 -0
- package/src/health/health-registry.test.ts +186 -0
- package/src/health/vault-integrity.test.ts +110 -0
- package/src/index.ts +92 -0
- package/src/intake/content-classifier.test.ts +209 -0
- package/src/intake/dedup-gate.test.ts +131 -0
- package/src/intake/intake-pipeline.test.ts +506 -0
- package/src/intake/intake-pipeline.ts +1 -0
- package/src/intake/text-ingester.test.ts +194 -0
- package/src/intake/text-ingester.ts +2 -0
- package/src/llm/key-pool.test.ts +236 -0
- package/src/llm/key-pool.ts +3 -4
- package/src/llm/llm-client.test.ts +345 -0
- package/src/llm/oauth-discovery.test.ts +180 -0
- package/src/llm/utils.test.ts +327 -0
- package/src/llm/utils.ts +2 -0
- package/src/{__tests__ → logging}/logger.test.ts +41 -62
- package/src/loop/loop-manager.test.ts +519 -0
- package/src/migrations/migration-runner.edge-cases.test.ts +319 -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 +320 -0
- package/src/operator/operator-profile.test.ts +314 -0
- package/src/operator/operator-profile.ts +469 -0
- package/src/operator/operator-signals-extended.test.ts +245 -0
- package/src/operator/operator-signals.test.ts +281 -0
- package/src/operator/operator-signals.ts +261 -0
- package/src/operator/operator-types.ts +444 -0
- package/src/operator/prompts/hook-precompact-operator-dispatch.md +94 -0
- package/src/operator/prompts/subagent-soft-signal-extractor.md +125 -0
- package/src/operator/prompts/subagent-synthesis-cognition.md +181 -0
- package/src/operator/prompts/subagent-synthesis-communication.md +140 -0
- package/src/operator/prompts/subagent-synthesis-technical.md +160 -0
- package/src/operator/prompts/subagent-synthesis-trust.md +143 -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 +59 -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 +406 -0
- package/src/planning/evidence-collector.ts +50 -0
- package/src/planning/gap-analysis-alternatives.test.ts +169 -0
- package/src/planning/gap-analysis.ts +21 -636
- package/src/planning/gap-passes.test.ts +372 -0
- package/src/planning/gap-passes.ts +298 -0
- package/src/planning/gap-patterns.test.ts +320 -0
- package/src/planning/gap-patterns.ts +234 -0
- package/src/planning/gap-types.ts +4 -1
- package/src/planning/github-projection.test.ts +177 -0
- package/src/planning/github-projection.ts +425 -0
- package/src/planning/impact-analyzer.test.ts +180 -0
- package/src/planning/impact-analyzer.ts +264 -0
- package/src/planning/plan-lifecycle.test.ts +312 -0
- package/src/planning/plan-lifecycle.ts +346 -0
- package/src/planning/planner-types.ts +215 -0
- package/src/{__tests__ → planning}/planner.test.ts +169 -15
- package/src/planning/planner.ts +197 -1228
- package/src/planning/rationalization-detector.test.ts +171 -0
- package/src/planning/rationalization-detector.ts +138 -0
- package/src/planning/reconciliation-engine.test.ts +141 -0
- package/src/planning/reconciliation-engine.ts +162 -0
- package/src/planning/task-verifier.test.ts +235 -0
- package/src/planning/task-verifier.ts +303 -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 +255 -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 +212 -0
- package/src/plugins/plugin-registry.test.ts +272 -0
- package/src/project/project-registry.test.ts +428 -0
- package/src/prompts/parser.test.ts +100 -0
- package/src/prompts/template-manager.test.ts +109 -0
- package/src/{__tests__ → queue}/async-infrastructure.test.ts +3 -3
- package/src/queue/job-queue.test.ts +331 -0
- package/src/queue/pipeline-runner.test.ts +209 -0
- package/src/runtime/admin-extra-ops.test.ts +527 -0
- package/src/runtime/admin-ops.test.ts +257 -0
- package/src/runtime/admin-ops.ts +45 -17
- package/src/runtime/admin-setup-ops.test.ts +328 -0
- package/src/runtime/admin-setup-ops.ts +20 -43
- package/src/runtime/archive-ops.test.ts +269 -0
- package/src/runtime/archive-ops.ts +347 -0
- package/src/runtime/capture-ops.test.ts +433 -0
- package/src/runtime/capture-ops.ts +50 -8
- package/src/runtime/chain-ops.test.ts +149 -0
- package/src/runtime/claude-md-helpers.test.ts +191 -0
- package/src/runtime/claude-md-helpers.ts +1 -1
- package/src/runtime/context-health.test.ts +78 -0
- package/src/runtime/context-health.ts +85 -0
- package/src/runtime/curator-extra-ops.test.ts +202 -0
- package/src/runtime/deprecation.test.ts +98 -0
- package/src/runtime/domain-ops.test.ts +268 -0
- package/src/runtime/facades/admin-facade.test.ts +333 -0
- package/src/runtime/facades/agency-facade.test.ts +278 -0
- package/src/runtime/facades/archive-facade.test.ts +294 -0
- package/src/runtime/facades/archive-facade.ts +14 -0
- package/src/runtime/facades/brain-facade.test.ts +714 -0
- package/src/runtime/facades/brain-facade.ts +2 -0
- package/src/runtime/facades/chat-facade.test.ts +166 -0
- package/src/runtime/facades/chat-facade.ts +15 -906
- package/src/runtime/facades/chat-service-ops.test.ts +276 -0
- package/src/runtime/facades/chat-service-ops.ts +374 -0
- package/src/runtime/facades/chat-session-ops.test.ts +197 -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 +269 -0
- package/src/runtime/facades/chat-transport-ops.ts +380 -0
- package/src/runtime/facades/context-facade.test.ts +108 -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 +6 -0
- package/src/runtime/facades/loop-facade.test.ts +245 -0
- package/src/runtime/facades/memory-facade.test.ts +269 -0
- package/src/runtime/facades/memory-facade.ts +78 -6
- package/src/runtime/facades/operator-facade.test.ts +208 -0
- package/src/runtime/facades/operator-facade.ts +236 -0
- package/src/runtime/facades/orchestrate-facade.test.ts +185 -0
- package/src/runtime/facades/orchestrate-facade.ts +3 -3
- package/src/runtime/facades/plan-facade.test.ts +266 -0
- package/src/runtime/facades/plan-facade.ts +42 -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/vault-facade.test.ts +631 -0
- package/src/runtime/facades/vault-facade.ts +15 -70
- package/src/runtime/feature-flags.test.ts +140 -0
- package/src/runtime/github-integration.test.ts +89 -0
- package/src/runtime/github-integration.ts +159 -0
- package/src/runtime/grading-ops.test.ts +141 -0
- package/src/runtime/grading-ops.ts +1 -1
- package/src/runtime/intake-ops.test.ts +208 -0
- package/src/runtime/loop-ops.test.ts +238 -0
- package/src/runtime/memory-cross-project-ops.test.ts +177 -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 +435 -46
- package/src/runtime/pack-ops.test.ts +158 -0
- package/src/runtime/planning-extra-ops.test.ts +583 -0
- package/src/runtime/planning-extra-ops.ts +72 -4
- package/src/{__tests__ → runtime}/playbook-ops-execution.test.ts +3 -3
- package/src/runtime/playbook-ops.test.ts +262 -0
- package/src/runtime/plugin-ops.test.ts +201 -0
- package/src/runtime/project-ops.test.ts +235 -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 +39 -12
- package/src/runtime/session-briefing.test.ts +302 -0
- package/src/runtime/session-briefing.ts +80 -1
- package/src/runtime/sync-ops.test.ts +221 -0
- package/src/runtime/sync-ops.ts +325 -0
- package/src/runtime/telemetry-ops.test.ts +132 -0
- package/src/runtime/types.ts +10 -4
- package/src/runtime/vault-extra-ops.test.ts +246 -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 +130 -0
- package/src/runtime/vault-sharing-ops.ts +5 -329
- package/src/skills/sync-skills.ts +108 -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 +294 -0
- package/src/update-check.ts +111 -0
- package/src/vault/__tests__/vault-characterization.test.ts +168 -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 +358 -0
- package/src/vault/linking.ts +149 -183
- package/src/vault/obsidian-sync.test.ts +342 -0
- package/src/vault/playbook.test.ts +152 -0
- package/src/vault/scope-detector.test.ts +187 -0
- package/src/vault/vault-branching.test.ts +250 -0
- package/src/{__tests__ → vault}/vault-connect.test.ts +1 -1
- package/src/vault/vault-entries.ts +282 -0
- package/src/vault/vault-interfaces.ts +56 -0
- package/src/vault/vault-maintenance.ts +205 -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 +160 -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 +181 -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 +89 -1171
- 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__/health-registry.test.ts +0 -173
- 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/planning/planner.ts
CHANGED
|
@@ -1,332 +1,57 @@
|
|
|
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
|
-
* Each key maps to the set of statuses it can transition to.
|
|
28
|
-
* Ported from Salvador's LIFECYCLE_TRANSITIONS.
|
|
29
|
-
*/
|
|
30
|
-
export const LIFECYCLE_TRANSITIONS: Record<PlanStatus, PlanStatus[]> = {
|
|
31
|
-
brainstorming: ['draft'],
|
|
32
|
-
draft: ['approved'],
|
|
33
|
-
approved: ['executing'],
|
|
34
|
-
executing: ['validating', 'reconciling'],
|
|
35
|
-
validating: ['reconciling', 'executing'],
|
|
36
|
-
reconciling: ['completed'],
|
|
37
|
-
completed: ['archived'],
|
|
38
|
-
archived: [],
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Statuses where the 30-minute TTL should NOT apply.
|
|
43
|
-
* Plans in these states may span multiple sessions.
|
|
44
|
-
*/
|
|
45
|
-
export const NON_EXPIRING_STATUSES: PlanStatus[] = [
|
|
46
|
-
'brainstorming',
|
|
47
|
-
'executing',
|
|
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, PlanTask, Plan, PlanStore, PlanCheck, PlannerOptions,
|
|
15
|
+
} from './planner-types.js';
|
|
16
|
+
import {
|
|
17
|
+
applyTransition, scoreToGrade, gradeToMinScore, PlanGradeRejectionError,
|
|
18
|
+
hasCircularDependencies, applyIteration, applySplitTasks, calculateScore,
|
|
19
|
+
applyTaskStatusUpdate, createPlanObject,
|
|
20
|
+
} from './plan-lifecycle.js';
|
|
21
|
+
import type { PlanStatus, PlanGrade, IterateChanges } from './plan-lifecycle.js';
|
|
22
|
+
import { buildReconciliationReport, buildAutoReconcileInput, computeExecutionSummary } from './reconciliation-engine.js';
|
|
23
|
+
import type { ReconcileInput } from './reconciliation-engine.js';
|
|
24
|
+
import {
|
|
25
|
+
createEvidence, verifyTaskLogic, verifyPlanLogic, verifyDeliverablesLogic,
|
|
26
|
+
createDeliverable, buildSpecReviewPrompt, buildQualityReviewPrompt,
|
|
27
|
+
} from './task-verifier.js';
|
|
303
28
|
|
|
304
29
|
export class Planner {
|
|
305
30
|
private filePath: string;
|
|
306
31
|
private store: PlanStore;
|
|
307
32
|
private gapOptions?: GapAnalysisOptions;
|
|
33
|
+
private minGradeForApproval: PlanGrade;
|
|
308
34
|
|
|
309
|
-
constructor(filePath: string,
|
|
35
|
+
constructor(filePath: string, options?: GapAnalysisOptions | PlannerOptions) {
|
|
310
36
|
this.filePath = filePath;
|
|
311
|
-
|
|
37
|
+
if (options && 'minGradeForApproval' in options) {
|
|
38
|
+
this.gapOptions = options.gapOptions;
|
|
39
|
+
this.minGradeForApproval = options.minGradeForApproval ?? 'A';
|
|
40
|
+
} else {
|
|
41
|
+
this.gapOptions = options as GapAnalysisOptions | undefined;
|
|
42
|
+
this.minGradeForApproval = 'A';
|
|
43
|
+
}
|
|
312
44
|
this.store = this.load();
|
|
313
45
|
}
|
|
314
46
|
|
|
315
47
|
private load(): PlanStore {
|
|
316
|
-
if (!existsSync(this.filePath)) {
|
|
317
|
-
return { version: '1.0', plans: [] };
|
|
318
|
-
}
|
|
48
|
+
if (!existsSync(this.filePath)) return { version: '1.0', plans: [] };
|
|
319
49
|
try {
|
|
320
50
|
const data = readFileSync(this.filePath, 'utf-8');
|
|
321
51
|
const store = JSON.parse(data) as PlanStore;
|
|
322
|
-
|
|
323
|
-
for (const plan of store.plans) {
|
|
324
|
-
plan.checks = plan.checks ?? [];
|
|
325
|
-
}
|
|
52
|
+
for (const plan of store.plans) plan.checks = plan.checks ?? [];
|
|
326
53
|
return store;
|
|
327
|
-
} catch {
|
|
328
|
-
return { version: '1.0', plans: [] };
|
|
329
|
-
}
|
|
54
|
+
} catch { return { version: '1.0', plans: [] }; }
|
|
330
55
|
}
|
|
331
56
|
|
|
332
57
|
private save(): void {
|
|
@@ -334,44 +59,26 @@ export class Planner {
|
|
|
334
59
|
writeFileSync(this.filePath, JSON.stringify(this.store, null, 2), 'utf-8');
|
|
335
60
|
}
|
|
336
61
|
|
|
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
|
-
};
|
|
62
|
+
private transition(plan: Plan, to: PlanStatus): void {
|
|
63
|
+
const r = applyTransition(plan.status, to);
|
|
64
|
+
plan.status = r.status;
|
|
65
|
+
plan.updatedAt = r.updatedAt;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private requirePlan(planId: string): Plan {
|
|
69
|
+
const plan = this.get(planId);
|
|
70
|
+
if (!plan) throw new Error(`Plan not found: ${planId}`);
|
|
71
|
+
return plan;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private requireTask(plan: Plan, taskId: string): PlanTask {
|
|
75
|
+
const task = plan.tasks.find((t) => t.id === taskId);
|
|
76
|
+
if (!task) throw new Error(`Task not found: ${taskId}`);
|
|
77
|
+
return task;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
create(params: Parameters<typeof createPlanObject>[0]): Plan {
|
|
81
|
+
const plan = createPlanObject(params);
|
|
375
82
|
this.store.plans.push(plan);
|
|
376
83
|
this.save();
|
|
377
84
|
return plan;
|
|
@@ -381,13 +88,8 @@ export class Planner {
|
|
|
381
88
|
return this.store.plans.find((p) => p.id === planId) ?? null;
|
|
382
89
|
}
|
|
383
90
|
|
|
384
|
-
list(): Plan[] {
|
|
385
|
-
return [...this.store.plans];
|
|
386
|
-
}
|
|
91
|
+
list(): Plan[] { return [...this.store.plans]; }
|
|
387
92
|
|
|
388
|
-
/**
|
|
389
|
-
* Permanently remove a plan by ID. Returns true if found and removed.
|
|
390
|
-
*/
|
|
391
93
|
remove(planId: string): boolean {
|
|
392
94
|
const idx = this.store.plans.findIndex((p) => p.id === planId);
|
|
393
95
|
if (idx < 0) return false;
|
|
@@ -396,1028 +98,295 @@ export class Planner {
|
|
|
396
98
|
return true;
|
|
397
99
|
}
|
|
398
100
|
|
|
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
101
|
promoteToDraft(planId: string): Plan {
|
|
420
|
-
const plan = this.
|
|
421
|
-
if (!plan) throw new Error(`Plan not found: ${planId}`);
|
|
102
|
+
const plan = this.requirePlan(planId);
|
|
422
103
|
this.transition(plan, 'draft');
|
|
423
104
|
this.save();
|
|
424
105
|
return plan;
|
|
425
106
|
}
|
|
426
107
|
|
|
427
108
|
approve(planId: string): Plan {
|
|
428
|
-
const plan = this.
|
|
429
|
-
|
|
109
|
+
const plan = this.requirePlan(planId);
|
|
110
|
+
const check = plan.latestCheck;
|
|
111
|
+
if (check && check.score < gradeToMinScore(this.minGradeForApproval)) {
|
|
112
|
+
throw new PlanGradeRejectionError(check.grade, check.score, this.minGradeForApproval, check.gaps);
|
|
113
|
+
}
|
|
430
114
|
this.transition(plan, 'approved');
|
|
431
115
|
this.save();
|
|
432
116
|
return plan;
|
|
433
117
|
}
|
|
434
118
|
|
|
435
119
|
startExecution(planId: string): Plan {
|
|
436
|
-
const plan = this.
|
|
437
|
-
if (!plan) throw new Error(`Plan not found: ${planId}`);
|
|
120
|
+
const plan = this.requirePlan(planId);
|
|
438
121
|
this.transition(plan, 'executing');
|
|
439
122
|
this.save();
|
|
440
123
|
return plan;
|
|
441
124
|
}
|
|
442
125
|
|
|
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
|
-
}
|
|
126
|
+
startValidation(planId: string): Plan {
|
|
127
|
+
const plan = this.requirePlan(planId);
|
|
128
|
+
this.transition(plan, 'validating');
|
|
129
|
+
this.save();
|
|
130
|
+
return plan;
|
|
131
|
+
}
|
|
468
132
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
plan
|
|
133
|
+
startReconciliation(planId: string): Plan {
|
|
134
|
+
const plan = this.requirePlan(planId);
|
|
135
|
+
this.transition(plan, 'reconciling');
|
|
472
136
|
this.save();
|
|
473
137
|
return plan;
|
|
474
138
|
}
|
|
475
139
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
if (!plan) throw new Error(`Plan not found: ${planId}`);
|
|
483
|
-
this.transition(plan, 'validating');
|
|
140
|
+
updateTask(planId: string, taskId: string, status: TaskStatus): Plan {
|
|
141
|
+
const plan = this.requirePlan(planId);
|
|
142
|
+
if (plan.status !== 'executing' && plan.status !== 'validating')
|
|
143
|
+
throw new Error(`Cannot update tasks on plan in '${plan.status}' status — must be 'executing' or 'validating'`);
|
|
144
|
+
applyTaskStatusUpdate(this.requireTask(plan, taskId), status);
|
|
145
|
+
plan.updatedAt = Date.now();
|
|
484
146
|
this.save();
|
|
485
147
|
return plan;
|
|
486
148
|
}
|
|
487
149
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
if (
|
|
495
|
-
|
|
150
|
+
reconcile(planId: string, report: ReconcileInput): Plan {
|
|
151
|
+
const plan = this.requirePlan(planId);
|
|
152
|
+
if (plan.status !== 'executing' && plan.status !== 'validating' && plan.status !== 'reconciling')
|
|
153
|
+
throw new Error(`Cannot reconcile plan in '${plan.status}' status — must be 'executing', 'validating', or 'reconciling'`);
|
|
154
|
+
plan.reconciliation = buildReconciliationReport(planId, report);
|
|
155
|
+
plan.executionSummary = computeExecutionSummary(plan.tasks);
|
|
156
|
+
if (plan.status === 'executing' || plan.status === 'validating') plan.status = 'reconciling';
|
|
157
|
+
plan.status = 'completed';
|
|
158
|
+
plan.updatedAt = Date.now();
|
|
496
159
|
this.save();
|
|
497
160
|
return plan;
|
|
498
161
|
}
|
|
499
162
|
|
|
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
163
|
complete(planId: string): Plan {
|
|
506
|
-
const plan = this.
|
|
507
|
-
if (
|
|
508
|
-
|
|
164
|
+
const plan = this.requirePlan(planId);
|
|
165
|
+
if (plan.status === 'executing' || plan.status === 'validating')
|
|
166
|
+
return this.reconcile(planId, { actualOutcome: 'All tasks completed', reconciledBy: 'auto' });
|
|
167
|
+
plan.executionSummary = computeExecutionSummary(plan.tasks);
|
|
509
168
|
this.transition(plan, 'completed');
|
|
510
169
|
this.save();
|
|
511
170
|
return plan;
|
|
512
171
|
}
|
|
513
172
|
|
|
173
|
+
autoReconcile(planId: string): Plan | null {
|
|
174
|
+
const plan = this.requirePlan(planId);
|
|
175
|
+
if (plan.status !== 'executing' && plan.status !== 'validating')
|
|
176
|
+
throw new Error(`Cannot auto-reconcile plan in '${plan.status}' status — must be 'executing' or 'validating'`);
|
|
177
|
+
const result = buildAutoReconcileInput(plan.tasks);
|
|
178
|
+
if (!result.canAutoReconcile || !result.input) return null;
|
|
179
|
+
return this.reconcile(planId, result.input);
|
|
180
|
+
}
|
|
181
|
+
|
|
514
182
|
getExecuting(): Plan[] {
|
|
515
183
|
return this.store.plans.filter((p) => p.status === 'executing' || p.status === 'validating');
|
|
516
184
|
}
|
|
517
185
|
|
|
518
186
|
getActive(): Plan[] {
|
|
519
|
-
return this.store.plans.filter(
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
p.status === 'draft' ||
|
|
523
|
-
p.status === 'approved' ||
|
|
524
|
-
p.status === 'executing' ||
|
|
525
|
-
p.status === 'validating' ||
|
|
526
|
-
p.status === 'reconciling',
|
|
527
|
-
);
|
|
187
|
+
return this.store.plans.filter((p) =>
|
|
188
|
+
p.status === 'brainstorming' || p.status === 'draft' || p.status === 'approved' ||
|
|
189
|
+
p.status === 'executing' || p.status === 'validating' || p.status === 'reconciling');
|
|
528
190
|
}
|
|
529
191
|
|
|
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}`);
|
|
192
|
+
iterate(planId: string, changes: IterateChanges): Plan {
|
|
193
|
+
const plan = this.requirePlan(planId);
|
|
552
194
|
if (plan.status !== 'draft' && plan.status !== 'brainstorming')
|
|
553
|
-
throw new Error(
|
|
554
|
-
|
|
555
|
-
);
|
|
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;
|
|
195
|
+
throw new Error(`Cannot iterate plan in '${plan.status}' status — must be 'draft' or 'brainstorming'`);
|
|
196
|
+
applyIteration(plan, changes);
|
|
592
197
|
this.save();
|
|
593
198
|
return plan;
|
|
594
199
|
}
|
|
595
200
|
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
*/
|
|
601
|
-
splitTasks(
|
|
602
|
-
planId: string,
|
|
603
|
-
tasks: Array<{
|
|
604
|
-
title: string;
|
|
605
|
-
description: string;
|
|
606
|
-
dependsOn?: string[];
|
|
607
|
-
acceptanceCriteria?: string[];
|
|
608
|
-
}>,
|
|
609
|
-
): Plan {
|
|
610
|
-
const plan = this.get(planId);
|
|
611
|
-
if (!plan) throw new Error(`Plan not found: ${planId}`);
|
|
201
|
+
splitTasks(planId: string, tasks: Array<{
|
|
202
|
+
title: string; description: string; dependsOn?: string[]; acceptanceCriteria?: string[];
|
|
203
|
+
}>): Plan {
|
|
204
|
+
const plan = this.requirePlan(planId);
|
|
612
205
|
if (plan.status !== 'brainstorming' && plan.status !== 'draft' && plan.status !== 'approved')
|
|
613
|
-
throw new Error(
|
|
614
|
-
|
|
615
|
-
);
|
|
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;
|
|
206
|
+
throw new Error(`Cannot split tasks on plan in '${plan.status}' status — must be 'brainstorming', 'draft', or 'approved'`);
|
|
207
|
+
applySplitTasks(plan, tasks);
|
|
641
208
|
this.save();
|
|
642
209
|
return plan;
|
|
643
210
|
}
|
|
644
211
|
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
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';
|
|
212
|
+
addReview(planId: string, review: {
|
|
213
|
+
taskId?: string; reviewer: string;
|
|
214
|
+
outcome: 'approved' | 'rejected' | 'needs_changes'; comments: string;
|
|
215
|
+
}): Plan {
|
|
216
|
+
const plan = this.requirePlan(planId);
|
|
217
|
+
if (review.taskId) this.requireTask(plan, review.taskId);
|
|
218
|
+
if (!plan.reviews) plan.reviews = [];
|
|
219
|
+
plan.reviews.push({
|
|
220
|
+
planId, taskId: review.taskId, reviewer: review.reviewer,
|
|
221
|
+
outcome: review.outcome, comments: review.comments, reviewedAt: Date.now(),
|
|
222
|
+
});
|
|
694
223
|
plan.updatedAt = Date.now();
|
|
695
224
|
this.save();
|
|
696
225
|
return plan;
|
|
697
226
|
}
|
|
698
227
|
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
planId
|
|
704
|
-
|
|
705
|
-
taskId?: string;
|
|
706
|
-
reviewer: string;
|
|
707
|
-
outcome: 'approved' | 'rejected' | 'needs_changes';
|
|
708
|
-
comments: string;
|
|
709
|
-
},
|
|
710
|
-
): Plan {
|
|
711
|
-
const plan = this.get(planId);
|
|
712
|
-
if (!plan) throw new Error(`Plan not found: ${planId}`);
|
|
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
|
-
|
|
719
|
-
if (!plan.reviews) plan.reviews = [];
|
|
720
|
-
plan.reviews.push({
|
|
721
|
-
planId,
|
|
722
|
-
taskId: review.taskId,
|
|
723
|
-
reviewer: review.reviewer,
|
|
724
|
-
outcome: review.outcome,
|
|
725
|
-
comments: review.comments,
|
|
726
|
-
reviewedAt: Date.now(),
|
|
727
|
-
});
|
|
728
|
-
|
|
228
|
+
setGitHubProjection(planId: string, projection: {
|
|
229
|
+
repo: string; milestone?: number;
|
|
230
|
+
issues: Array<{ taskId: string; issueNumber: number }>; projectedAt: number;
|
|
231
|
+
}): Plan {
|
|
232
|
+
const plan = this.requirePlan(planId);
|
|
233
|
+
plan.githubProjection = projection;
|
|
729
234
|
plan.updatedAt = Date.now();
|
|
730
235
|
this.save();
|
|
731
236
|
return plan;
|
|
732
237
|
}
|
|
733
238
|
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
* unmet dependencies so a subagent knows what to work on and what to wait for.
|
|
737
|
-
*/
|
|
738
|
-
getDispatch(
|
|
739
|
-
planId: string,
|
|
740
|
-
taskId: string,
|
|
741
|
-
): {
|
|
742
|
-
task: PlanTask;
|
|
743
|
-
unmetDependencies: PlanTask[];
|
|
744
|
-
ready: boolean;
|
|
239
|
+
getDispatch(planId: string, taskId: string): {
|
|
240
|
+
task: PlanTask; unmetDependencies: PlanTask[]; ready: boolean;
|
|
745
241
|
deliverableStatus?: { count: number; staleCount: number };
|
|
746
242
|
} {
|
|
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
|
-
}
|
|
243
|
+
const plan = this.requirePlan(planId);
|
|
244
|
+
const task = this.requireTask(plan, taskId);
|
|
245
|
+
const unmetDeps: PlanTask[] = [];
|
|
246
|
+
for (const depId of task.dependsOn ?? []) {
|
|
247
|
+
const dep = plan.tasks.find((t) => t.id === depId);
|
|
248
|
+
if (dep && dep.status !== 'completed') unmetDeps.push(dep);
|
|
760
249
|
}
|
|
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
250
|
return {
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
avgTaskDurationMs:
|
|
814
|
-
tasksWithDuration > 0 ? Math.round(totalDurationMs / tasksWithDuration) : 0,
|
|
251
|
+
task, unmetDependencies: unmetDeps, ready: unmetDeps.length === 0,
|
|
252
|
+
...(task.deliverables?.length && {
|
|
253
|
+
deliverableStatus: { count: task.deliverables.length, staleCount: task.deliverables.filter((d) => d.stale).length },
|
|
254
|
+
}),
|
|
815
255
|
};
|
|
816
256
|
}
|
|
817
257
|
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
*/
|
|
821
|
-
submitDeliverable(
|
|
822
|
-
planId: string,
|
|
823
|
-
taskId: string,
|
|
824
|
-
deliverable: { type: TaskDeliverable['type']; path: string; hash?: string },
|
|
258
|
+
submitDeliverable(planId: string, taskId: string,
|
|
259
|
+
deliverable: { type: 'file' | 'vault_entry' | 'url'; path: string; hash?: string },
|
|
825
260
|
): 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
|
-
|
|
261
|
+
const plan = this.requirePlan(planId);
|
|
262
|
+
const task = this.requireTask(plan, taskId);
|
|
850
263
|
if (!task.deliverables) task.deliverables = [];
|
|
851
|
-
task.deliverables.push(
|
|
264
|
+
task.deliverables.push(createDeliverable(deliverable));
|
|
852
265
|
task.updatedAt = Date.now();
|
|
853
266
|
plan.updatedAt = Date.now();
|
|
854
267
|
this.save();
|
|
855
268
|
return task;
|
|
856
269
|
}
|
|
857
270
|
|
|
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
|
-
verifyDeliverables(
|
|
865
|
-
planId: string,
|
|
866
|
-
taskId: string,
|
|
271
|
+
verifyDeliverables(planId: string, taskId: string,
|
|
867
272
|
vault?: { get(id: string): unknown | null },
|
|
868
|
-
): { verified: boolean; deliverables: TaskDeliverable[]; staleCount: number } {
|
|
869
|
-
const plan = this.
|
|
870
|
-
|
|
871
|
-
const
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
const deliverables = task.deliverables ?? [];
|
|
875
|
-
let staleCount = 0;
|
|
876
|
-
const now = Date.now();
|
|
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
|
-
|
|
273
|
+
): { verified: boolean; deliverables: import('./planner-types.js').TaskDeliverable[]; staleCount: number } {
|
|
274
|
+
const plan = this.requirePlan(planId);
|
|
275
|
+
const task = this.requireTask(plan, taskId);
|
|
276
|
+
const result = verifyDeliverablesLogic(task.deliverables ?? [], vault);
|
|
277
|
+
task.deliverables = result.deliverables;
|
|
912
278
|
plan.updatedAt = Date.now();
|
|
913
279
|
this.save();
|
|
914
|
-
|
|
915
|
-
return { verified: staleCount === 0, deliverables, staleCount };
|
|
280
|
+
return result;
|
|
916
281
|
}
|
|
917
282
|
|
|
918
|
-
|
|
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
|
-
submitEvidence(
|
|
925
|
-
planId: string,
|
|
926
|
-
taskId: string,
|
|
927
|
-
evidence: { criterion: string; content: string; type: TaskEvidence['type'] },
|
|
283
|
+
submitEvidence(planId: string, taskId: string,
|
|
284
|
+
evidence: { criterion: string; content: string; type: import('./planner-types.js').TaskEvidence['type'] },
|
|
928
285
|
): 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
|
-
});
|
|
286
|
+
const plan = this.requirePlan(planId);
|
|
287
|
+
const task = this.requireTask(plan, taskId);
|
|
288
|
+
task.evidence = createEvidence(task.evidence ?? [], evidence);
|
|
940
289
|
task.updatedAt = Date.now();
|
|
941
290
|
plan.updatedAt = Date.now();
|
|
942
291
|
this.save();
|
|
943
292
|
return task;
|
|
944
293
|
}
|
|
945
294
|
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
* and any reviews have passed.
|
|
949
|
-
* Returns verification status with details.
|
|
950
|
-
*/
|
|
951
|
-
verifyTask(
|
|
952
|
-
planId: string,
|
|
953
|
-
taskId: string,
|
|
954
|
-
): {
|
|
955
|
-
verified: boolean;
|
|
956
|
-
task: PlanTask;
|
|
957
|
-
missingCriteria: string[];
|
|
295
|
+
verifyTask(planId: string, taskId: string): {
|
|
296
|
+
verified: boolean; task: PlanTask; missingCriteria: string[];
|
|
958
297
|
reviewStatus: 'approved' | 'rejected' | 'needs_changes' | 'no_reviews';
|
|
959
298
|
} {
|
|
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;
|
|
299
|
+
const plan = this.requirePlan(planId);
|
|
300
|
+
const task = this.requireTask(plan, taskId);
|
|
301
|
+
const result = verifyTaskLogic(task, plan.reviews ?? []);
|
|
302
|
+
if (result.verified !== task.verified) {
|
|
303
|
+
task.verified = result.verified;
|
|
985
304
|
task.updatedAt = Date.now();
|
|
986
305
|
plan.updatedAt = Date.now();
|
|
987
306
|
this.save();
|
|
988
307
|
}
|
|
989
|
-
|
|
990
|
-
return { verified, task, missingCriteria, reviewStatus };
|
|
308
|
+
return { ...result, task };
|
|
991
309
|
}
|
|
992
310
|
|
|
993
|
-
|
|
994
|
-
|
|
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
|
-
};
|
|
311
|
+
verifyPlan(planId: string) {
|
|
312
|
+
return verifyPlanLogic(planId, this.requirePlan(planId).tasks);
|
|
1085
313
|
}
|
|
1086
314
|
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
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
|
-
});
|
|
315
|
+
generateReviewSpec(planId: string, taskId: string): { prompt: string; task: PlanTask; plan: Plan } {
|
|
316
|
+
const plan = this.requirePlan(planId);
|
|
317
|
+
const task = this.requireTask(plan, taskId);
|
|
318
|
+
return { prompt: buildSpecReviewPrompt(task, plan.objective), task, plan };
|
|
1143
319
|
}
|
|
1144
320
|
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
generateReviewSpec(
|
|
1150
|
-
planId: string,
|
|
1151
|
-
taskId: string,
|
|
1152
|
-
): { prompt: string; task: PlanTask; plan: Plan } {
|
|
1153
|
-
const plan = this.get(planId);
|
|
1154
|
-
if (!plan) throw new Error(`Plan not found: ${planId}`);
|
|
1155
|
-
const task = plan.tasks.find((t) => t.id === taskId);
|
|
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 };
|
|
321
|
+
generateReviewQuality(planId: string, taskId: string): { prompt: string; task: PlanTask; plan: Plan } {
|
|
322
|
+
const plan = this.requirePlan(planId);
|
|
323
|
+
const task = this.requireTask(plan, taskId);
|
|
324
|
+
return { prompt: buildQualityReviewPrompt(task), task, plan };
|
|
1179
325
|
}
|
|
1180
326
|
|
|
1181
|
-
/**
|
|
1182
|
-
* Generate a review prompt for code quality checking.
|
|
1183
|
-
*/
|
|
1184
|
-
generateReviewQuality(
|
|
1185
|
-
planId: string,
|
|
1186
|
-
taskId: string,
|
|
1187
|
-
): { prompt: string; task: PlanTask; plan: Plan } {
|
|
1188
|
-
const plan = this.get(planId);
|
|
1189
|
-
if (!plan) throw new Error(`Plan not found: ${planId}`);
|
|
1190
|
-
const task = plan.tasks.find((t) => t.id === taskId);
|
|
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 };
|
|
1212
|
-
}
|
|
1213
|
-
|
|
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
327
|
archive(olderThanDays?: number): Plan[] {
|
|
1220
|
-
const cutoff =
|
|
1221
|
-
olderThanDays
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
(p) => p.status === 'completed' && p.updatedAt < cutoff,
|
|
1226
|
-
);
|
|
1227
|
-
for (const plan of toArchive) {
|
|
1228
|
-
plan.status = 'archived';
|
|
1229
|
-
plan.updatedAt = Date.now();
|
|
1230
|
-
}
|
|
1231
|
-
if (toArchive.length > 0) {
|
|
1232
|
-
this.save();
|
|
1233
|
-
}
|
|
328
|
+
const cutoff = olderThanDays !== undefined
|
|
329
|
+
? Date.now() - olderThanDays * 24 * 60 * 60 * 1000 : Date.now() + 1;
|
|
330
|
+
const toArchive = this.store.plans.filter((p) => p.status === 'completed' && p.updatedAt < cutoff);
|
|
331
|
+
for (const plan of toArchive) { plan.status = 'archived'; plan.updatedAt = Date.now(); }
|
|
332
|
+
if (toArchive.length > 0) this.save();
|
|
1234
333
|
return toArchive;
|
|
1235
334
|
}
|
|
1236
335
|
|
|
1237
|
-
/**
|
|
1238
|
-
* Get statistics about all plans.
|
|
1239
|
-
*/
|
|
1240
336
|
stats(): {
|
|
1241
|
-
total: number;
|
|
1242
|
-
|
|
1243
|
-
avgTasksPerPlan: number;
|
|
1244
|
-
totalTasks: number;
|
|
1245
|
-
tasksByStatus: Record<TaskStatus, number>;
|
|
337
|
+
total: number; byStatus: Record<PlanStatus, number>;
|
|
338
|
+
avgTasksPerPlan: number; totalTasks: number; tasksByStatus: Record<TaskStatus, number>;
|
|
1246
339
|
} {
|
|
1247
340
|
const plans = this.store.plans;
|
|
1248
|
-
const byStatus:
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
approved: 0,
|
|
1252
|
-
executing: 0,
|
|
1253
|
-
validating: 0,
|
|
1254
|
-
reconciling: 0,
|
|
1255
|
-
completed: 0,
|
|
1256
|
-
archived: 0,
|
|
1257
|
-
};
|
|
1258
|
-
const tasksByStatus: Record<TaskStatus, number> = {
|
|
1259
|
-
pending: 0,
|
|
1260
|
-
in_progress: 0,
|
|
1261
|
-
completed: 0,
|
|
1262
|
-
skipped: 0,
|
|
1263
|
-
failed: 0,
|
|
1264
|
-
};
|
|
341
|
+
const byStatus = { brainstorming: 0, draft: 0, approved: 0, executing: 0,
|
|
342
|
+
validating: 0, reconciling: 0, completed: 0, archived: 0 } as Record<PlanStatus, number>;
|
|
343
|
+
const tasksByStatus = { pending: 0, in_progress: 0, completed: 0, skipped: 0, failed: 0 } as Record<TaskStatus, number>;
|
|
1265
344
|
let totalTasks = 0;
|
|
1266
|
-
|
|
1267
345
|
for (const p of plans) {
|
|
1268
346
|
byStatus[p.status]++;
|
|
1269
347
|
totalTasks += p.tasks.length;
|
|
1270
|
-
for (const t of p.tasks)
|
|
1271
|
-
tasksByStatus[t.status]++;
|
|
1272
|
-
}
|
|
348
|
+
for (const t of p.tasks) tasksByStatus[t.status]++;
|
|
1273
349
|
}
|
|
1274
|
-
|
|
1275
350
|
return {
|
|
1276
|
-
total: plans.length,
|
|
1277
|
-
byStatus,
|
|
351
|
+
total: plans.length, byStatus, totalTasks, tasksByStatus,
|
|
1278
352
|
avgTasksPerPlan: plans.length > 0 ? Math.round((totalTasks / plans.length) * 100) / 100 : 0,
|
|
1279
|
-
totalTasks,
|
|
1280
|
-
tasksByStatus,
|
|
1281
353
|
};
|
|
1282
354
|
}
|
|
1283
355
|
|
|
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
356
|
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
|
|
357
|
+
const plan = this.requirePlan(planId);
|
|
1305
358
|
const gaps = runGapAnalysis(plan, this.gapOptions);
|
|
1306
|
-
|
|
1307
|
-
// Add circular dependency check (structural, not covered by gap-analysis passes)
|
|
1308
|
-
if (this.hasCircularDependencies(plan)) {
|
|
359
|
+
if (hasCircularDependencies(plan.tasks)) {
|
|
1309
360
|
gaps.push({
|
|
1310
|
-
id: `gap_${Date.now()}_circ`,
|
|
1311
|
-
severity: 'critical',
|
|
1312
|
-
category: 'structure',
|
|
361
|
+
id: `gap_${Date.now()}_circ`, severity: 'critical', category: 'structure',
|
|
1313
362
|
description: 'Circular dependencies detected among tasks.',
|
|
1314
363
|
recommendation: 'Remove circular dependency chains so tasks can be executed in order.',
|
|
1315
|
-
location: 'tasks',
|
|
1316
|
-
_trigger: 'circular_dependencies',
|
|
364
|
+
location: 'tasks', _trigger: 'circular_dependencies',
|
|
1317
365
|
});
|
|
1318
366
|
}
|
|
1319
|
-
|
|
1320
|
-
// Iteration = number of previous checks + 1
|
|
1321
367
|
const iteration = plan.checks.length + 1;
|
|
1322
368
|
const score = calculateScore(gaps, iteration);
|
|
1323
|
-
const grade = this.scoreToGrade(score);
|
|
1324
|
-
|
|
1325
369
|
const check: PlanCheck = {
|
|
1326
370
|
checkId: `chk-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
1327
|
-
planId,
|
|
1328
|
-
grade,
|
|
1329
|
-
score,
|
|
1330
|
-
gaps,
|
|
1331
|
-
iteration,
|
|
1332
|
-
checkedAt: Date.now(),
|
|
371
|
+
planId, grade: scoreToGrade(score), score, gaps, iteration, checkedAt: Date.now(),
|
|
1333
372
|
};
|
|
1334
|
-
|
|
1335
373
|
plan.checks.push(check);
|
|
1336
374
|
plan.latestCheck = check;
|
|
1337
375
|
plan.updatedAt = Date.now();
|
|
1338
376
|
this.save();
|
|
1339
|
-
|
|
1340
377
|
return check;
|
|
1341
378
|
}
|
|
1342
379
|
|
|
1343
|
-
/**
|
|
1344
|
-
* Get the latest check for a plan.
|
|
1345
|
-
*/
|
|
1346
380
|
getLatestCheck(planId: string): PlanCheck | null {
|
|
1347
|
-
|
|
1348
|
-
if (!plan) throw new Error(`Plan not found: ${planId}`);
|
|
1349
|
-
return plan.latestCheck ?? null;
|
|
381
|
+
return this.requirePlan(planId).latestCheck ?? null;
|
|
1350
382
|
}
|
|
1351
383
|
|
|
1352
|
-
/**
|
|
1353
|
-
* Get all checks for a plan (history).
|
|
1354
|
-
*/
|
|
1355
384
|
getCheckHistory(planId: string): PlanCheck[] {
|
|
1356
|
-
|
|
1357
|
-
if (!plan) throw new Error(`Plan not found: ${planId}`);
|
|
1358
|
-
return [...plan.checks];
|
|
385
|
+
return [...this.requirePlan(planId).checks];
|
|
1359
386
|
}
|
|
1360
387
|
|
|
1361
|
-
/**
|
|
1362
|
-
* Auto-grade: grade the plan and return whether it meets a target grade.
|
|
1363
|
-
*/
|
|
1364
388
|
meetsGrade(planId: string, targetGrade: PlanGrade): { meets: boolean; check: PlanCheck } {
|
|
1365
389
|
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;
|
|
390
|
+
return { meets: check.score >= gradeToMinScore(targetGrade), check };
|
|
1422
391
|
}
|
|
1423
392
|
}
|