@planu/cli 4.14.1 → 5.0.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/CHANGELOG.md +35 -1
- package/LICENSE +55 -12
- package/README.md +10 -2
- package/dist/cli/commands/install.d.ts +1 -12
- package/dist/cli/commands/install.js +9 -65
- package/dist/cli/commands/job.js +4 -3
- package/dist/cli/commands/spec.js +2 -2
- package/dist/cli/commands/validate.js +2 -2
- package/dist/cli/router.js +1 -3
- package/dist/config/environment-schema.json +1 -3
- package/dist/config/official-sdd-tools.d.ts +4 -0
- package/dist/config/official-sdd-tools.js +35 -0
- package/dist/config/product-public.d.ts +139 -3
- package/dist/config/product-public.js +8 -1
- package/dist/config/product-public.json +7 -36
- package/dist/config/registries/hosts/codex.json +20 -363
- package/dist/config/release-policy.json +84 -2
- package/dist/config/runtime-policy.json +12 -1
- package/dist/config/skill-templates/planu-resume-work.md +2 -2
- package/dist/core/index.d.ts +1 -1
- package/dist/core/spec-validator.d.ts +4 -4
- package/dist/core/spec-validator.js +1 -1
- package/dist/engine/abortable-process-runner.d.ts +20 -2
- package/dist/engine/abortable-process-runner.js +243 -74
- package/dist/engine/ambiguity-scorer.js +4 -26
- package/dist/engine/auth/scope-mapper.js +1 -7
- package/dist/engine/autopilot/state-updater.js +4 -4
- package/dist/engine/cascade-hooks/hooks/review-enricher.hook.js +9 -6
- package/dist/engine/cascade-hooks/state-drift-detector.d.ts +3 -3
- package/dist/engine/cascade-hooks/state-drift-detector.js +1 -1
- package/dist/engine/ci-generator/planu-config.js +1 -7
- package/dist/engine/clarification-gate/gate.d.ts +3 -3
- package/dist/engine/code-scanner/layer-scanner.js +6 -22
- package/dist/engine/core-bridge.d.ts +3 -3
- package/dist/engine/cost-guardrails.d.ts +1 -1
- package/dist/engine/cost-guardrails.js +1 -1
- package/dist/engine/criterion-identity.d.ts +1 -0
- package/dist/engine/criterion-identity.js +7 -0
- package/dist/engine/dashboard/data-loader.js +2 -7
- package/dist/engine/dashboard/templates-layout.js +0 -3
- package/dist/engine/dashboard/templates-project.js +1 -13
- package/dist/engine/docs-site-generator/data-collector.d.ts +1 -1
- package/dist/engine/docs-site-generator/data-collector.js +7 -28
- package/dist/engine/docs-site-generator/tools-renderer.js +3 -10
- package/dist/engine/evidence-gates/artifact-reader.d.ts +10 -1
- package/dist/engine/evidence-gates/artifact-reader.js +115 -2
- package/dist/engine/evidence-gates/lifecycle-gate.js +4 -3
- package/dist/engine/evidence-gates/reconciliation-freshness.d.ts +11 -0
- package/dist/engine/evidence-gates/reconciliation-freshness.js +72 -0
- package/dist/engine/evidence-index/done-drift.d.ts +1 -1
- package/dist/engine/evidence-index/done-drift.js +40 -18
- package/dist/engine/execution/context.js +3 -0
- package/dist/engine/execution/durable-job-heartbeat-runtime.d.ts +4 -0
- package/dist/engine/execution/durable-job-heartbeat-runtime.js +400 -0
- package/dist/engine/execution/job-runtime.d.ts +30 -0
- package/dist/engine/execution/job-runtime.js +114 -6
- package/dist/engine/execution/public-job-sanitizer.d.ts +4 -0
- package/dist/engine/execution/public-job-sanitizer.js +31 -0
- package/dist/engine/execution/validate-job-executor.d.ts +12 -1
- package/dist/engine/execution/validate-job-executor.js +549 -86
- package/dist/engine/execution/validate-job-payload.js +107 -50
- package/dist/engine/execution/validate-job-recovery.js +1 -1
- package/dist/engine/execution/validate-worker-runtime.d.ts +5 -0
- package/dist/engine/execution/validate-worker-runtime.js +90 -2
- package/dist/engine/execution/validate-worker-thread.js +17 -24
- package/dist/engine/handoff-artifacts/schemas.d.ts +2 -1
- package/dist/engine/handoff-artifacts/schemas.js +33 -0
- package/dist/engine/handoff-packager.d.ts +19 -0
- package/dist/engine/handoff-packager.js +415 -13
- package/dist/engine/hooks/effort-configurator.js +1 -1
- package/dist/engine/impact-detector/test-break-predictor.js +3 -3
- package/dist/engine/impact-detector/tool-registration.js +1 -1
- package/dist/engine/implementation-contract/renderer.js +23 -8
- package/dist/engine/lifecycle-reconciliation-io.d.ts +11 -0
- package/dist/engine/lifecycle-reconciliation-io.js +180 -0
- package/dist/engine/lifecycle-reconciliation.d.ts +16 -0
- package/dist/engine/lifecycle-reconciliation.js +406 -0
- package/dist/engine/local-first/tool-classification.js +2 -0
- package/dist/engine/macos-keychain-native-port.d.ts +5 -0
- package/dist/engine/macos-keychain-native-port.js +19 -7
- package/dist/engine/migrations/remove-commercial-state.d.ts +3 -0
- package/dist/engine/migrations/remove-commercial-state.js +343 -0
- package/dist/engine/network-policy.js +1 -0
- package/dist/engine/planu-core.darwin-arm64.node.manifest.json +7 -7
- package/dist/engine/planu-core.darwin-arm64.node.sbom.json +10 -10
- package/dist/engine/planu-core.darwin-x64.node.manifest.json +7 -7
- package/dist/engine/planu-core.darwin-x64.node.sbom.json +10 -10
- package/dist/engine/planu-core.linux-arm64-gnu.node.manifest.json +7 -7
- package/dist/engine/planu-core.linux-arm64-gnu.node.sbom.json +10 -10
- package/dist/engine/planu-core.linux-arm64-musl.node.manifest.json +7 -7
- package/dist/engine/planu-core.linux-arm64-musl.node.sbom.json +10 -10
- package/dist/engine/planu-core.linux-x64-gnu.node.manifest.json +7 -7
- package/dist/engine/planu-core.linux-x64-gnu.node.sbom.json +10 -10
- package/dist/engine/planu-core.linux-x64-musl.node.manifest.json +7 -7
- package/dist/engine/planu-core.linux-x64-musl.node.sbom.json +10 -10
- package/dist/engine/planu-core.win32-arm64-msvc.node.manifest.json +7 -7
- package/dist/engine/planu-core.win32-arm64-msvc.node.sbom.json +10 -10
- package/dist/engine/planu-core.win32-x64-msvc.node.manifest.json +7 -7
- package/dist/engine/planu-core.win32-x64-msvc.node.sbom.json +10 -10
- package/dist/engine/readiness-checker.d.ts +1 -16
- package/dist/engine/readiness-checker.js +27 -88
- package/dist/engine/runtime-policy.d.ts +2 -0
- package/dist/engine/runtime-policy.js +42 -5
- package/dist/engine/runtime-security/rate-limiter.js +0 -1
- package/dist/engine/safety/contained-project-file.d.ts +3 -0
- package/dist/engine/safety/contained-project-file.js +23 -0
- package/dist/engine/scope-boundaries/contradiction-checker.js +63 -1
- package/dist/engine/scope-boundaries/scope-validator.js +45 -7
- package/dist/engine/session/checkpoint-writer.js +0 -9
- package/dist/engine/spec-format/acceptance-criteria.d.ts +6 -2
- package/dist/engine/spec-format/acceptance-criteria.js +77 -18
- package/dist/engine/spec-format/markdown-sections.d.ts +14 -0
- package/dist/engine/spec-format/markdown-sections.js +105 -0
- package/dist/engine/spec-format/read-technical-section.js +33 -5
- package/dist/engine/spec-format/technical-md-populator.js +3 -2
- package/dist/engine/spec-format/unified-spec-builder.d.ts +7 -0
- package/dist/engine/spec-format/unified-spec-builder.js +202 -0
- package/dist/engine/spec-migrator/canonical-deduplicator.d.ts +18 -0
- package/dist/engine/spec-migrator/canonical-deduplicator.js +479 -0
- package/dist/engine/spec-migrator/filesystem-import.d.ts +4 -8
- package/dist/engine/spec-migrator/filesystem-import.js +121 -14
- package/dist/engine/spec-migrator/frontmatter-parser.js +32 -15
- package/dist/engine/spec-migrator/index.d.ts +1 -0
- package/dist/engine/spec-migrator/index.js +1 -0
- package/dist/engine/spec-state-machine/transition-spec.d.ts +6 -1
- package/dist/engine/spec-state-machine/transition-spec.js +10 -1
- package/dist/engine/technical-enricher/index.js +16 -8
- package/dist/engine/technical-enricher/render-enriched.d.ts +1 -0
- package/dist/engine/technical-enricher/render-enriched.js +42 -14
- package/dist/engine/telemetry/health-checker.js +53 -17
- package/dist/engine/telemetry/telemetry-client.js +26 -8
- package/dist/engine/text-signal-boundaries.d.ts +2 -0
- package/dist/engine/text-signal-boundaries.js +25 -2
- package/dist/engine/time/relative-date.d.ts +3 -0
- package/dist/engine/time/relative-date.js +16 -0
- package/dist/engine/timing/budget.d.ts +11 -0
- package/dist/engine/timing/budget.js +27 -9
- package/dist/engine/token-optimizer/analytics.d.ts +1 -1
- package/dist/engine/token-optimizer/analytics.js +5 -6
- package/dist/engine/usage-tracker/core.d.ts +2 -2
- package/dist/engine/usage-tracker/core.js +1 -2
- package/dist/engine/usage-tracker/index.d.ts +0 -3
- package/dist/engine/usage-tracker/index.js +0 -3
- package/dist/engine/usage-tracker/stats.d.ts +2 -2
- package/dist/engine/usage-tracker/stats.js +1 -28
- package/dist/engine/validation/durable-validation.d.ts +1 -1
- package/dist/engine/validation/durable-validation.js +3 -82
- package/dist/engine/validation/validation-freshness.d.ts +11 -1
- package/dist/engine/validation/validation-freshness.js +231 -91
- package/dist/engine/validation/validation-receipt.d.ts +9 -6
- package/dist/engine/validation/validation-receipt.js +84 -24
- package/dist/engine/validation/validation-source-digest.d.ts +2 -0
- package/dist/engine/validation/validation-source-digest.js +86 -0
- package/dist/engine/validation/validation-submission.d.ts +5 -4
- package/dist/engine/validation/validation-submission.js +25 -11
- package/dist/engine/validation/validation-worktree.d.ts +21 -0
- package/dist/engine/validation/validation-worktree.js +260 -0
- package/dist/engine/validation-impact-planner.d.ts +14 -2
- package/dist/engine/validation-impact-planner.js +0 -3
- package/dist/engine/validator/dor-dod.d.ts +1 -0
- package/dist/engine/validator/dor-dod.js +5 -3
- package/dist/engine/validator/extractors.d.ts +1 -5
- package/dist/engine/validator/extractors.js +24 -69
- package/dist/engine/validator/spec-compliance-runner.d.ts +12 -24
- package/dist/engine/validator/spec-compliance-runner.js +429 -211
- package/dist/engine/validator.js +35 -22
- package/dist/index.js +4 -16
- package/dist/resources/usage-stats-resource.js +2 -18
- package/dist/security/adapters/linux-secret-service.js +7 -3
- package/dist/security/adapters/windows-credential-manager.js +6 -4
- package/dist/server/routes/specs.js +10 -0
- package/dist/storage/base-store.d.ts +1 -1
- package/dist/storage/base-store.js +1 -1
- package/dist/storage/feedback-remote.d.ts +2 -3
- package/dist/storage/feedback-remote.js +45 -31
- package/dist/storage/global-store.d.ts +1 -17
- package/dist/storage/global-store.js +1 -42
- package/dist/storage/index.d.ts +0 -2
- package/dist/storage/index.js +0 -2
- package/dist/storage/migrations/canonical-storage.js +124 -11
- package/dist/storage/runtime-db.d.ts +9 -0
- package/dist/storage/runtime-db.js +64 -8
- package/dist/storage/spec-store.d.ts +14 -2
- package/dist/storage/spec-store.js +104 -14
- package/dist/storage/status-store/atomic-status-write.d.ts +5 -0
- package/dist/storage/status-store/atomic-status-write.js +57 -0
- package/dist/storage/status-store/file-lock.js +30 -11
- package/dist/storage/status-store/self-healing.js +3 -3
- package/dist/storage/status-store/version-sync.d.ts +2 -1
- package/dist/storage/status-store/version-sync.js +35 -40
- package/dist/storage/usage-store.d.ts +1 -5
- package/dist/storage/usage-store.js +0 -35
- package/dist/tools/auto-update-handler.d.ts +3 -3
- package/dist/tools/auto-update-handler.js +3 -3
- package/dist/tools/challenge-spec/resilience-challenge-scenarios.d.ts +2 -1
- package/dist/tools/challenge-spec/resilience-challenge-scenarios.js +10 -7
- package/dist/tools/challenge-spec/scenario-collector.js +1 -1
- package/dist/tools/challenge-spec/scenarios-data.js +1 -1
- package/dist/tools/challenge-spec/scenarios-utils.d.ts +1 -1
- package/dist/tools/challenge-spec/scenarios-utils.js +35 -11
- package/dist/tools/challenge-spec.js +5 -5
- package/dist/tools/check-readiness.js +1 -1
- package/dist/tools/configure-telemetry.js +30 -2
- package/dist/tools/create-spec/spec-builder.js +0 -11
- package/dist/tools/create-spec.d.ts +1 -0
- package/dist/tools/create-spec.js +444 -239
- package/dist/tools/git/branch-ops.d.ts +1 -1
- package/dist/tools/git/branch-ops.js +3 -2
- package/dist/tools/init-project/handler.js +10 -12
- package/dist/tools/init-project/host-assets-writer.js +26 -1
- package/dist/tools/init-project/lint-scaffolder.d.ts +3 -3
- package/dist/tools/init-project/migration-runner.d.ts +2 -4
- package/dist/tools/init-project/migration-runner.js +3 -2
- package/dist/tools/init-project/portable-index-reconciler.d.ts +4 -0
- package/dist/tools/init-project/portable-index-reconciler.js +14 -0
- package/dist/tools/init-project/result-builder.js +1 -3
- package/dist/tools/jobs/handlers.js +38 -6
- package/dist/tools/list-specs.js +5 -26
- package/dist/tools/merge-risk-handler.js +2 -2
- package/dist/tools/package-handoff.js +2 -0
- package/dist/tools/reconcile-spec.js +0 -27
- package/dist/tools/register-agent-squad-tools.js +2 -2
- package/dist/tools/register-platform-tools/design-stack-tools.js +11 -11
- package/dist/tools/register-platform-tools/lifecycle-infra-tools.js +7 -7
- package/dist/tools/register-sdd-tools.d.ts +1 -1
- package/dist/tools/register-sdd-tools.js +2 -29
- package/dist/tools/register-spec-tools/analysis-tools.js +8 -8
- package/dist/tools/register-spec-tools/core-spec-tools.js +17 -8
- package/dist/tools/safe-handler.d.ts +5 -13
- package/dist/tools/safe-handler.js +7 -23
- package/dist/tools/schemas/index.d.ts +1 -1
- package/dist/tools/schemas/index.js +1 -1
- package/dist/tools/schemas/output-schemas.d.ts +0 -23
- package/dist/tools/schemas/output-schemas.js +0 -28
- package/dist/tools/schemas/spec.d.ts +4 -0
- package/dist/tools/schemas/spec.js +3 -0
- package/dist/tools/schemas/validate-output-schema.d.ts +11 -0
- package/dist/tools/schemas/validate-output-schema.js +7 -0
- package/dist/tools/spec-prompt-handler.js +3 -3
- package/dist/tools/sync-spec-state-handler.d.ts +1 -1
- package/dist/tools/sync-spec-state-handler.js +1 -1
- package/dist/tools/tool-entry.d.ts +4 -4
- package/dist/tools/tool-entry.js +3 -3
- package/dist/tools/tool-registry/core-tools.js +31 -15
- package/dist/tools/tool-registry/group-infra.js +27 -110
- package/dist/tools/tool-registry/group-quality-compliance.js +26 -26
- package/dist/tools/tool-registry/inventory.d.ts +2 -0
- package/dist/tools/tool-registry/inventory.js +2 -0
- package/dist/tools/tool-registry-helpers.d.ts +3 -3
- package/dist/tools/tool-registry-helpers.js +3 -3
- package/dist/tools/tool-usage-report/registered-tools.d.ts +1 -1
- package/dist/tools/tool-usage-report/registered-tools.js +3 -15
- package/dist/tools/update-status/approval-gates.js +3 -0
- package/dist/tools/update-status/done-receipt-verifier.js +40 -27
- package/dist/tools/update-status/evidence-gate.js +36 -1
- package/dist/tools/update-status/file-sync.d.ts +2 -1
- package/dist/tools/update-status/file-sync.js +57 -6
- package/dist/tools/update-status/index.d.ts +2 -0
- package/dist/tools/update-status/index.js +95 -58
- package/dist/tools/update-status/response-builder.js +3 -3
- package/dist/tools/update-status/side-effects.d.ts +2 -0
- package/dist/tools/update-status/side-effects.js +2 -2
- package/dist/tools/update-status/transition-guard.js +32 -15
- package/dist/tools/update-status-actions.d.ts +4 -2
- package/dist/tools/update-status-actions.js +16 -14
- package/dist/tools/usage-report.js +1 -16
- package/dist/tools/usage-stats.js +1 -18
- package/dist/tools/usage-tracking.d.ts +4 -0
- package/dist/tools/usage-tracking.js +20 -0
- package/dist/tools/validate-lint.d.ts +1 -0
- package/dist/tools/validate-lint.js +1 -1
- package/dist/tools/validate.d.ts +2 -1
- package/dist/tools/validate.js +114 -34
- package/dist/transports/http-transport.js +6 -8
- package/dist/transports/mcp-types.d.ts +0 -2
- package/dist/transports/session-context.js +1 -1
- package/dist/types/cascade-hooks.d.ts +1 -1
- package/dist/types/ci.d.ts +0 -4
- package/dist/types/clarification-token.d.ts +1 -1
- package/dist/types/claude-code-runtime.d.ts +0 -27
- package/dist/types/commercial-migration.d.ts +17 -0
- package/dist/types/commercial-migration.js +2 -0
- package/dist/types/common/primitives.d.ts +0 -2
- package/dist/types/core-bridge.d.ts +4 -10
- package/dist/types/dashboard.d.ts +3 -7
- package/dist/types/docs.d.ts +0 -1
- package/dist/types/durable-job.d.ts +4 -4
- package/dist/types/durable-validation.d.ts +100 -2
- package/dist/types/durable-validation.js +165 -1
- package/dist/types/errors/structured-error.d.ts +3 -3
- package/dist/types/estimation.d.ts +2 -0
- package/dist/types/evidence-gates.d.ts +9 -1
- package/dist/types/execution.d.ts +2 -0
- package/dist/types/gemini-integration.d.ts +0 -11
- package/dist/types/handoff-artifacts.d.ts +41 -0
- package/dist/types/hooks-advanced.d.ts +0 -13
- package/dist/types/impact-detection.d.ts +2 -2
- package/dist/types/index.d.ts +2 -6
- package/dist/types/index.js +1 -5
- package/dist/types/kiro-integration.d.ts +0 -6
- package/dist/types/network-policy.d.ts +1 -1
- package/dist/types/project/lint-scaffolder.d.ts +1 -1
- package/dist/types/runtime-policy.d.ts +11 -0
- package/dist/types/session-safeguard.d.ts +0 -31
- package/dist/types/spec/canonical-repair.d.ts +81 -0
- package/dist/types/spec/canonical-repair.js +2 -0
- package/dist/types/spec/core.d.ts +14 -0
- package/dist/types/spec/index.d.ts +1 -0
- package/dist/types/spec/index.js +1 -0
- package/dist/types/spec/inputs.d.ts +29 -0
- package/dist/types/spec-format.d.ts +24 -0
- package/dist/types/telemetry.d.ts +4 -1
- package/dist/types/transport.d.ts +0 -2
- package/dist/types/usage.d.ts +1 -36
- package/dist/types/usage.js +1 -1
- package/dist/types/validate-worker.d.ts +6 -0
- package/dist/types/validation-receipt.d.ts +1 -1
- package/dist/types/validation.d.ts +2 -2
- package/package.json +16 -12
- package/planu-native.json +1 -1
- package/planu-plugin.json +23 -40
- package/scripts/lib/pending-release-file.mjs +93 -0
- package/src/i18n/messages/en.json +0 -41
- package/src/i18n/messages/es.json +0 -41
- package/src/i18n/messages/pt.json +0 -41
- package/dist/cli/commands/activate.d.ts +0 -14
- package/dist/cli/commands/activate.js +0 -174
- package/dist/config/license-plans.json +0 -251
- package/dist/config/product-public.ts +0 -148
- package/dist/config/server-instructions.ts +0 -121
- package/dist/config/version-defaults.ts +0 -76
- package/dist/config/version.ts +0 -21
- package/dist/engine/ai-integration/gemini/settings-generator.d.ts +0 -4
- package/dist/engine/ai-integration/gemini/settings-generator.js +0 -195
- package/dist/engine/ai-integration/kiro/hooks-generator.d.ts +0 -6
- package/dist/engine/ai-integration/kiro/hooks-generator.js +0 -75
- package/dist/engine/hooks/full-spectrum-generator.d.ts +0 -32
- package/dist/engine/hooks/full-spectrum-generator.js +0 -117
- package/dist/engine/license-validator/config-loader.d.ts +0 -5
- package/dist/engine/license-validator/config-loader.js +0 -16
- package/dist/engine/license-validator/core.d.ts +0 -15
- package/dist/engine/license-validator/core.js +0 -151
- package/dist/engine/license-validator/fingerprint.d.ts +0 -3
- package/dist/engine/license-validator/fingerprint.js +0 -17
- package/dist/engine/license-validator/lemon-squeezy.d.ts +0 -5
- package/dist/engine/license-validator/lemon-squeezy.js +0 -55
- package/dist/engine/license-validator.d.ts +0 -5
- package/dist/engine/license-validator.js +0 -6
- package/dist/engine/product-intelligence/index.d.ts +0 -7
- package/dist/engine/product-intelligence/index.js +0 -211
- package/dist/engine/session/auto-save.d.ts +0 -18
- package/dist/engine/session/auto-save.js +0 -98
- package/dist/engine/session/context-snapshot.d.ts +0 -8
- package/dist/engine/session/context-snapshot.js +0 -49
- package/dist/engine/session/memory-sync.d.ts +0 -12
- package/dist/engine/session/memory-sync.js +0 -34
- package/dist/engine/session/session-diff.d.ts +0 -11
- package/dist/engine/session/session-diff.js +0 -42
- package/dist/engine/session/session-merge.d.ts +0 -12
- package/dist/engine/session/session-merge.js +0 -55
- package/dist/engine/session-safeguard/checkpoint-runner.d.ts +0 -4
- package/dist/engine/session-safeguard/checkpoint-runner.js +0 -70
- package/dist/engine/session-safeguard/learnings-buffer.d.ts +0 -15
- package/dist/engine/session-safeguard/learnings-buffer.js +0 -91
- package/dist/engine/session-safeguard/precompaction-drain.d.ts +0 -10
- package/dist/engine/session-safeguard/precompaction-drain.js +0 -82
- package/dist/engine/session-safeguard/unpushed-detector.d.ts +0 -7
- package/dist/engine/session-safeguard/unpushed-detector.js +0 -59
- package/dist/engine/spec-changelog/core.d.ts +0 -16
- package/dist/engine/spec-changelog/core.js +0 -176
- package/dist/engine/spec-changelog/diff.d.ts +0 -18
- package/dist/engine/spec-changelog/diff.js +0 -121
- package/dist/engine/spec-changelog/index.d.ts +0 -3
- package/dist/engine/spec-changelog/index.js +0 -4
- package/dist/engine/trial-engine.d.ts +0 -5
- package/dist/engine/trial-engine.js +0 -14
- package/dist/engine/usage-tracker/rate-check.d.ts +0 -7
- package/dist/engine/usage-tracker/rate-check.js +0 -18
- package/dist/engine/usage-tracker/rate-limiter.d.ts +0 -14
- package/dist/engine/usage-tracker/rate-limiter.js +0 -73
- package/dist/engine/usage-tracker/trial.d.ts +0 -27
- package/dist/engine/usage-tracker/trial.js +0 -49
- package/dist/engine/webhook/event-handlers.d.ts +0 -25
- package/dist/engine/webhook/event-handlers.js +0 -89
- package/dist/engine/webhook/index.d.ts +0 -4
- package/dist/engine/webhook/index.js +0 -5
- package/dist/engine/webhook/server.d.ts +0 -12
- package/dist/engine/webhook/server.js +0 -249
- package/dist/engine/webhook/signature.d.ts +0 -16
- package/dist/engine/webhook/signature.js +0 -49
- package/dist/storage/license-store.d.ts +0 -6
- package/dist/storage/license-store.js +0 -63
- package/dist/storage/session-store.d.ts +0 -20
- package/dist/storage/session-store.js +0 -136
- package/dist/tools/activate-license.d.ts +0 -6
- package/dist/tools/activate-license.js +0 -132
- package/dist/tools/checkpoint-handler.d.ts +0 -3
- package/dist/tools/checkpoint-handler.js +0 -130
- package/dist/tools/export-session.d.ts +0 -3
- package/dist/tools/export-session.js +0 -90
- package/dist/tools/license-gate.d.ts +0 -18
- package/dist/tools/license-gate.js +0 -222
- package/dist/tools/license-status.d.ts +0 -3
- package/dist/tools/license-status.js +0 -93
- package/dist/tools/list-sessions.d.ts +0 -3
- package/dist/tools/list-sessions.js +0 -46
- package/dist/tools/planu-session-checkpoint.d.ts +0 -6
- package/dist/tools/planu-session-checkpoint.js +0 -42
- package/dist/tools/restore-session.d.ts +0 -3
- package/dist/tools/restore-session.js +0 -71
- package/dist/tools/spec-history.d.ts +0 -3
- package/dist/tools/spec-history.js +0 -59
- package/dist/tools/webhook.d.ts +0 -6
- package/dist/tools/webhook.js +0 -150
- package/dist/types/changelog.d.ts +0 -49
- package/dist/types/changelog.js +0 -3
- package/dist/types/licensing.d.ts +0 -109
- package/dist/types/licensing.js +0 -4
- package/dist/types/product-intelligence.d.ts +0 -60
- package/dist/types/product-intelligence.js +0 -3
- package/dist/types/session.d.ts +0 -84
- package/dist/types/session.js +0 -3
- package/dist/types/webhook.d.ts +0 -62
- package/dist/types/webhook.js +0 -4
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { lstat, mkdir, readFile, readdir, realpath } from 'node:fs/promises';
|
|
3
|
+
import { basename, join, relative, resolve, sep } from 'node:path';
|
|
4
|
+
import { projectDataDir, readJson, writeJson } from '../storage/base-store.js';
|
|
5
|
+
import { ReconciliationReceiptV1Schema } from './handoff-artifacts/schemas.js';
|
|
6
|
+
const REQUEST_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u;
|
|
7
|
+
const ARTIFACTS = [
|
|
8
|
+
'spec.lock.json',
|
|
9
|
+
'review_feedback.md',
|
|
10
|
+
'task-plan.json',
|
|
11
|
+
'implementation-report.json',
|
|
12
|
+
'validation-report.json',
|
|
13
|
+
'traceability-matrix.json',
|
|
14
|
+
'context-package.json',
|
|
15
|
+
];
|
|
16
|
+
export function reconciliationDigest(value) {
|
|
17
|
+
return `sha256:${createHash('sha256').update(value).digest('hex')}`;
|
|
18
|
+
}
|
|
19
|
+
function receiptDirectory(projectId, specId) {
|
|
20
|
+
return join(projectDataDir(projectId), 'handoffs', specId, 'reconciliations');
|
|
21
|
+
}
|
|
22
|
+
function isOutside(root, candidate) {
|
|
23
|
+
const pathFromRoot = relative(root, candidate);
|
|
24
|
+
return (pathFromRoot === '..' || pathFromRoot.startsWith(`..${sep}`) || pathFromRoot.startsWith('..'));
|
|
25
|
+
}
|
|
26
|
+
async function assertAncestors(projectId, specId, create) {
|
|
27
|
+
const dataRoot = projectDataDir(projectId);
|
|
28
|
+
const directory = receiptDirectory(projectId, specId);
|
|
29
|
+
const ancestors = [
|
|
30
|
+
dataRoot,
|
|
31
|
+
join(dataRoot, 'handoffs'),
|
|
32
|
+
join(dataRoot, 'handoffs', specId),
|
|
33
|
+
directory,
|
|
34
|
+
];
|
|
35
|
+
let parentRealPath;
|
|
36
|
+
for (const [index, ancestor] of ancestors.entries()) {
|
|
37
|
+
if (create) {
|
|
38
|
+
try {
|
|
39
|
+
await mkdir(ancestor, { recursive: index === 0, mode: 0o700 });
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
if (!(error instanceof Error && 'code' in error && error.code === 'EEXIST')) {
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const status = await lstat(ancestor);
|
|
48
|
+
if (status.isSymbolicLink() || !status.isDirectory()) {
|
|
49
|
+
throw new Error('reconciliation directory has an unsafe ancestor');
|
|
50
|
+
}
|
|
51
|
+
const ancestorRealPath = await realpath(ancestor);
|
|
52
|
+
if (parentRealPath && isOutside(parentRealPath, ancestorRealPath)) {
|
|
53
|
+
throw new Error('reconciliation directory escapes the canonical handoff root');
|
|
54
|
+
}
|
|
55
|
+
parentRealPath = ancestorRealPath;
|
|
56
|
+
}
|
|
57
|
+
return directory;
|
|
58
|
+
}
|
|
59
|
+
export async function safeReceiptPath(projectId, specId, requestId) {
|
|
60
|
+
if (!REQUEST_ID.test(requestId) || basename(requestId) !== requestId) {
|
|
61
|
+
throw new Error('invalid reconciliation request ID');
|
|
62
|
+
}
|
|
63
|
+
const directory = await assertAncestors(projectId, specId, true);
|
|
64
|
+
const root = await realpath(directory);
|
|
65
|
+
const candidate = resolve(root, `${requestId}.json`);
|
|
66
|
+
if (isOutside(root, candidate)) {
|
|
67
|
+
throw new Error('reconciliation receipt path escapes its directory');
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
const status = await lstat(candidate);
|
|
71
|
+
if (status.isSymbolicLink() || !status.isFile()) {
|
|
72
|
+
throw new Error('reconciliation receipt is not a regular file');
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
if (!(error instanceof Error && 'code' in error && error.code === 'ENOENT')) {
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return candidate;
|
|
81
|
+
}
|
|
82
|
+
export async function readReconciliationReceipt(path) {
|
|
83
|
+
try {
|
|
84
|
+
const candidate = await lstat(path);
|
|
85
|
+
if (candidate.isSymbolicLink() || !candidate.isFile()) {
|
|
86
|
+
throw new Error('reconciliation receipt is not a regular file');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
throw error;
|
|
94
|
+
}
|
|
95
|
+
const parsed = ReconciliationReceiptV1Schema.safeParse(await readJson(path, null));
|
|
96
|
+
if (!parsed.success) {
|
|
97
|
+
throw new Error('reconciliation receipt is invalid');
|
|
98
|
+
}
|
|
99
|
+
return parsed.data;
|
|
100
|
+
}
|
|
101
|
+
export async function writeReconciliationReceipt(path, receipt) {
|
|
102
|
+
try {
|
|
103
|
+
const status = await lstat(path);
|
|
104
|
+
if (status.isSymbolicLink() || !status.isFile()) {
|
|
105
|
+
throw new Error('reconciliation receipt is unsafe');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
if (!(error instanceof Error && 'code' in error && error.code === 'ENOENT')) {
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
await writeJson(path, receipt);
|
|
114
|
+
}
|
|
115
|
+
export async function collectSupersededReconciliationArtifacts(projectId, specId) {
|
|
116
|
+
const handoff = join(projectDataDir(projectId), 'handoffs', specId);
|
|
117
|
+
const candidates = [
|
|
118
|
+
...ARTIFACTS.map((name) => ({
|
|
119
|
+
artifactId: `handoffs/${specId}/${name}`,
|
|
120
|
+
path: join(handoff, name),
|
|
121
|
+
})),
|
|
122
|
+
{
|
|
123
|
+
artifactId: `handoffs/${specId}.md`,
|
|
124
|
+
path: join(projectDataDir(projectId), 'handoffs', `${specId}.md`),
|
|
125
|
+
},
|
|
126
|
+
];
|
|
127
|
+
try {
|
|
128
|
+
const names = await readdir(handoff);
|
|
129
|
+
for (const artifactName of names.filter((candidate) => /^contract-validation-[A-Za-z0-9_-]+\.json$/u.test(candidate))) {
|
|
130
|
+
candidates.push({
|
|
131
|
+
artifactId: `handoffs/${specId}/${artifactName}`,
|
|
132
|
+
path: join(handoff, artifactName),
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
if (!(error instanceof Error && 'code' in error && error.code === 'ENOENT')) {
|
|
138
|
+
throw error;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return Promise.all(candidates.map(async ({ artifactId, path }) => {
|
|
142
|
+
try {
|
|
143
|
+
const stat = await lstat(path);
|
|
144
|
+
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
145
|
+
throw new Error(`unsafe artifact ${artifactId}`);
|
|
146
|
+
}
|
|
147
|
+
return { artifactId, sha256: reconciliationDigest(await readFile(path)) };
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
|
|
151
|
+
return { artifactId };
|
|
152
|
+
}
|
|
153
|
+
throw error;
|
|
154
|
+
}
|
|
155
|
+
}));
|
|
156
|
+
}
|
|
157
|
+
export async function readCommittedReconciliationReceipts(projectId, specId) {
|
|
158
|
+
try {
|
|
159
|
+
const directory = await assertAncestors(projectId, specId, false);
|
|
160
|
+
const root = await realpath(directory);
|
|
161
|
+
const names = await readdir(root);
|
|
162
|
+
const receipts = await Promise.all(names
|
|
163
|
+
.filter((name) => REQUEST_ID.test(name.replace(/\.json$/u, '')) && name.endsWith('.json'))
|
|
164
|
+
.map((name) => {
|
|
165
|
+
const path = join(root, name);
|
|
166
|
+
if (isOutside(root, path)) {
|
|
167
|
+
throw new Error('reconciliation receipt path escapes its directory');
|
|
168
|
+
}
|
|
169
|
+
return readReconciliationReceipt(path);
|
|
170
|
+
}));
|
|
171
|
+
return receipts.filter((receipt) => receipt?.state === 'committed');
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
|
|
175
|
+
return [];
|
|
176
|
+
}
|
|
177
|
+
throw error;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
//# sourceMappingURL=lifecycle-reconciliation-io.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ReconciliationIo, ReconciliationReceiptV1, ReconciliationSpecSync } from '../types/handoff-artifacts.js';
|
|
2
|
+
import type { ReconciliationInvocationContext, ToolResult, UpdateStatusInput } from '../types/index.js';
|
|
3
|
+
/** The registrar, not caller-controlled tool input, establishes local authority. */
|
|
4
|
+
export declare function runWithTrustedLocalMcpContext<T>(callback: () => T): T;
|
|
5
|
+
export declare function currentReconciliationInvocationContext(): ReconciliationInvocationContext;
|
|
6
|
+
/** Read all committed receipts. Invalid or unsafe recovery state fails closed. */
|
|
7
|
+
export declare function readCommittedReconciliations(projectId: string, specId: string): Promise<ReconciliationReceiptV1[]>;
|
|
8
|
+
export declare function reconcileImplementingSpec(args: {
|
|
9
|
+
input: UpdateStatusInput;
|
|
10
|
+
projectId: string;
|
|
11
|
+
projectPath?: string;
|
|
12
|
+
context: ReconciliationInvocationContext;
|
|
13
|
+
syncSpecFiles: ReconciliationSpecSync;
|
|
14
|
+
io?: ReconciliationIo;
|
|
15
|
+
}): Promise<ToolResult>;
|
|
16
|
+
//# sourceMappingURL=lifecycle-reconciliation.d.ts.map
|
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
+
import { lstat, readFile } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { projectDataDir } from '../storage/base-store.js';
|
|
5
|
+
import { specStore } from '../storage/index.js';
|
|
6
|
+
import { ValidationReportV1Schema } from './handoff-artifacts/schemas.js';
|
|
7
|
+
import { rollbackTransitionSpec, transitionSpec, } from './spec-state-machine/transition-spec.js';
|
|
8
|
+
import { acquireLock, releaseLock } from './safety/cross-process-lock.js';
|
|
9
|
+
import { appendTransitionEvent, readTransitionLog } from '../storage/transition-log.js';
|
|
10
|
+
import { collectSupersededReconciliationArtifacts, readCommittedReconciliationReceipts, readReconciliationReceipt, reconciliationDigest as digest, safeReceiptPath, writeReconciliationReceipt, } from './lifecycle-reconciliation-io.js';
|
|
11
|
+
const REQUEST_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u;
|
|
12
|
+
const DIGEST = /^sha256:[a-f0-9]{64}$/u;
|
|
13
|
+
const invocationStore = new AsyncLocalStorage();
|
|
14
|
+
/** The registrar, not caller-controlled tool input, establishes local authority. */
|
|
15
|
+
export function runWithTrustedLocalMcpContext(callback) {
|
|
16
|
+
return invocationStore.run({ surface: 'local-mcp' }, callback);
|
|
17
|
+
}
|
|
18
|
+
export function currentReconciliationInvocationContext() {
|
|
19
|
+
return invocationStore.getStore() ?? { surface: 'direct' };
|
|
20
|
+
}
|
|
21
|
+
function failure(message, code = 'RECONCILIATION_REJECTED') {
|
|
22
|
+
return {
|
|
23
|
+
content: [{ type: 'text', text: `${code}: ${message}` }],
|
|
24
|
+
isError: true,
|
|
25
|
+
structuredContent: { error: code, code: 422 },
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function appendState(receipt, state, reason) {
|
|
29
|
+
const at = new Date().toISOString();
|
|
30
|
+
return {
|
|
31
|
+
...receipt,
|
|
32
|
+
state,
|
|
33
|
+
stateHistory: [...receipt.stateHistory, { state, at, ...(reason ? { reason } : {}) }],
|
|
34
|
+
...(state === 'committed' ? { committedAt: at } : {}),
|
|
35
|
+
...(state === 'aborted' ? { abortedAt: at } : {}),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
async function appendTransitionEventOnce(args) {
|
|
39
|
+
const existing = await readTransitionLog(args.projectId, args.specId);
|
|
40
|
+
const alreadyAppended = existing.some((entry) => {
|
|
41
|
+
const meta = entry.meta;
|
|
42
|
+
if (meta === undefined) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
return (meta.reconciliationRequestId === args.requestId &&
|
|
46
|
+
meta.reconciliationBindingDigest === args.bindingDigest);
|
|
47
|
+
});
|
|
48
|
+
if (alreadyAppended) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
await appendTransitionEvent({
|
|
52
|
+
projectId: args.projectId,
|
|
53
|
+
specId: args.specId,
|
|
54
|
+
eventType: 'transition',
|
|
55
|
+
from: 'implementing',
|
|
56
|
+
to: 'review',
|
|
57
|
+
actor: 'local-mcp',
|
|
58
|
+
reason: args.reason,
|
|
59
|
+
meta: {
|
|
60
|
+
trigger: 'reconciliation',
|
|
61
|
+
reconciliationRequestId: args.requestId,
|
|
62
|
+
reconciliationBindingDigest: args.bindingDigest,
|
|
63
|
+
transitionId: args.transitionId,
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
async function completeForwardReconciliation(args) {
|
|
68
|
+
let next = args.receipt;
|
|
69
|
+
if (next.phase !== 'spec-synced' && next.phase !== 'audit-appended') {
|
|
70
|
+
const syncResult = await args.syncSpecFiles(args.spec, 'implementing', 'review', args.projectPath);
|
|
71
|
+
if (syncResult.warning) {
|
|
72
|
+
throw new Error(`portable spec sync failed: ${syncResult.warning.reason}`);
|
|
73
|
+
}
|
|
74
|
+
next = { ...next, transitionId: args.transitionId, phase: 'spec-synced', auditPending: true };
|
|
75
|
+
await args.persistReceipt(args.receiptPath, next);
|
|
76
|
+
}
|
|
77
|
+
if (next.phase !== 'audit-appended') {
|
|
78
|
+
// The audit append may have reached disk even when the caller receives an error.
|
|
79
|
+
// From this point recovery is forward-only, never rollback.
|
|
80
|
+
args.markAppendOnly();
|
|
81
|
+
await appendTransitionEventOnce({
|
|
82
|
+
projectId: args.projectId,
|
|
83
|
+
specId: args.spec.id,
|
|
84
|
+
requestId: next.requestId,
|
|
85
|
+
bindingDigest: next.bindingDigest,
|
|
86
|
+
transitionId: args.transitionId,
|
|
87
|
+
reason: args.reason,
|
|
88
|
+
});
|
|
89
|
+
next = {
|
|
90
|
+
...next,
|
|
91
|
+
transitionId: args.transitionId,
|
|
92
|
+
phase: 'audit-appended',
|
|
93
|
+
auditPending: false,
|
|
94
|
+
};
|
|
95
|
+
await args.persistReceipt(args.receiptPath, next);
|
|
96
|
+
}
|
|
97
|
+
const committed = {
|
|
98
|
+
...appendState(next, 'committed'),
|
|
99
|
+
transitionId: args.transitionId,
|
|
100
|
+
committedTransitionId: args.transitionId,
|
|
101
|
+
auditPending: false,
|
|
102
|
+
};
|
|
103
|
+
await args.persistReceipt(args.receiptPath, committed);
|
|
104
|
+
return committed;
|
|
105
|
+
}
|
|
106
|
+
async function validateRequest(args) {
|
|
107
|
+
const { input, spec, context } = args;
|
|
108
|
+
if (context.surface !== 'local-mcp') {
|
|
109
|
+
return failure('reconciliation is available only through local MCP', 'RECONCILIATION_UNTRUSTED_SURFACE');
|
|
110
|
+
}
|
|
111
|
+
if (input.status !== 'review') {
|
|
112
|
+
return failure('reconciliation target must be review');
|
|
113
|
+
}
|
|
114
|
+
if (!input.reconciliationRequestId || !REQUEST_ID.test(input.reconciliationRequestId)) {
|
|
115
|
+
return failure('reconciliationRequestId must be a lowercase RFC 4122 UUID');
|
|
116
|
+
}
|
|
117
|
+
if (!input.expectedImplementingTransitionId ||
|
|
118
|
+
!input.implementationReviewDigest ||
|
|
119
|
+
!DIGEST.test(input.implementationReviewDigest)) {
|
|
120
|
+
return failure('expected transition and sha256 implementation review digest are required');
|
|
121
|
+
}
|
|
122
|
+
if (!input.reason || input.reason.trim().length < 100) {
|
|
123
|
+
return failure('reason must contain at least 100 characters');
|
|
124
|
+
}
|
|
125
|
+
if (spec.status !== 'implementing') {
|
|
126
|
+
return failure('spec is no longer implementing', 'RECONCILIATION_STALE');
|
|
127
|
+
}
|
|
128
|
+
const latest = spec.statusHistory?.at(-1);
|
|
129
|
+
if (!latest?.transitionId || latest.transitionId !== input.expectedImplementingTransitionId) {
|
|
130
|
+
return failure('expected implementing transition is stale', 'RECONCILIATION_STALE');
|
|
131
|
+
}
|
|
132
|
+
const reportPath = join(projectDataDir(args.projectId), 'handoffs', spec.id, 'validation-report.json');
|
|
133
|
+
let reportBytes;
|
|
134
|
+
try {
|
|
135
|
+
const status = await lstat(reportPath);
|
|
136
|
+
if (status.isSymbolicLink() || !status.isFile()) {
|
|
137
|
+
return failure('validation report is unsafe');
|
|
138
|
+
}
|
|
139
|
+
reportBytes = await readFile(reportPath);
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
return failure('validation report is missing');
|
|
143
|
+
}
|
|
144
|
+
const report = ValidationReportV1Schema.safeParse(JSON.parse(reportBytes.toString('utf8')));
|
|
145
|
+
if (!report.success ||
|
|
146
|
+
report.data.passed ||
|
|
147
|
+
report.data.reviewer.kind !== 'implementation-review-agent' ||
|
|
148
|
+
report.data.reviewer.agent !== 'planu-implementation-reviewer' ||
|
|
149
|
+
report.data.reviewer.verdict !== 'changes-requested') {
|
|
150
|
+
return failure('validation report is not an independent changes-requested finding');
|
|
151
|
+
}
|
|
152
|
+
const reviewDigest = digest(reportBytes);
|
|
153
|
+
if (reviewDigest !== input.implementationReviewDigest) {
|
|
154
|
+
return failure('implementation review digest does not match validation report', 'RECONCILIATION_STALE');
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
reviewDigest,
|
|
158
|
+
bindingDigest: digest([
|
|
159
|
+
args.projectId,
|
|
160
|
+
spec.id,
|
|
161
|
+
input.reconciliationRequestId,
|
|
162
|
+
latest.transitionId,
|
|
163
|
+
reviewDigest,
|
|
164
|
+
digest(input.reason.trim()),
|
|
165
|
+
].join('\0')),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
function validateDurableReceiptBinding(args) {
|
|
169
|
+
const { input, receipt } = args;
|
|
170
|
+
if (input.expectedImplementingTransitionId !== receipt.sourceImplementingTransitionId ||
|
|
171
|
+
input.implementationReviewDigest !== receipt.implementationReviewDigest ||
|
|
172
|
+
!input.reason ||
|
|
173
|
+
digest(input.reason.trim()) !== receipt.reasonDigest) {
|
|
174
|
+
return failure('reconciliation retry binding conflicts with the durable receipt', 'RECONCILIATION_BINDING_CONFLICT');
|
|
175
|
+
}
|
|
176
|
+
const bindingDigest = digest([
|
|
177
|
+
args.projectId,
|
|
178
|
+
args.specId,
|
|
179
|
+
receipt.requestId,
|
|
180
|
+
receipt.sourceImplementingTransitionId,
|
|
181
|
+
receipt.implementationReviewDigest,
|
|
182
|
+
receipt.reasonDigest,
|
|
183
|
+
].join('\0'));
|
|
184
|
+
if (bindingDigest !== receipt.bindingDigest) {
|
|
185
|
+
return failure('durable reconciliation binding is invalid', 'RECONCILIATION_BINDING_CONFLICT');
|
|
186
|
+
}
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
async function validateStoredBinding(args) {
|
|
190
|
+
const durableBindingError = validateDurableReceiptBinding(args);
|
|
191
|
+
if (durableBindingError) {
|
|
192
|
+
return durableBindingError;
|
|
193
|
+
}
|
|
194
|
+
const reportPath = join(projectDataDir(args.projectId), 'handoffs', args.specId, 'validation-report.json');
|
|
195
|
+
try {
|
|
196
|
+
const reportBytes = await readFile(reportPath);
|
|
197
|
+
const report = ValidationReportV1Schema.safeParse(JSON.parse(reportBytes.toString('utf8')));
|
|
198
|
+
if (!report.success ||
|
|
199
|
+
report.data.passed ||
|
|
200
|
+
report.data.reviewer.kind !== 'implementation-review-agent' ||
|
|
201
|
+
report.data.reviewer.agent !== 'planu-implementation-reviewer' ||
|
|
202
|
+
report.data.reviewer.verdict !== 'changes-requested' ||
|
|
203
|
+
digest(reportBytes) !== args.receipt.implementationReviewDigest) {
|
|
204
|
+
return failure('implementation review no longer matches the durable receipt', 'RECONCILIATION_BINDING_CONFLICT');
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
return failure('implementation review is unavailable for reconciliation recovery', 'RECONCILIATION_BINDING_CONFLICT');
|
|
209
|
+
}
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
/** Read all committed receipts. Invalid or unsafe recovery state fails closed. */
|
|
213
|
+
export async function readCommittedReconciliations(projectId, specId) {
|
|
214
|
+
return readCommittedReconciliationReceipts(projectId, specId);
|
|
215
|
+
}
|
|
216
|
+
function receiptMatchesRequest(receipt, projectId, specId, requestId) {
|
|
217
|
+
return (receipt.projectId === projectId && receipt.specId === specId && receipt.requestId === requestId);
|
|
218
|
+
}
|
|
219
|
+
// eslint-disable-next-line max-lines-per-function -- transaction recovery keeps rollback and forward-only phases in one auditable boundary
|
|
220
|
+
export async function reconcileImplementingSpec(args) {
|
|
221
|
+
const requestId = args.input.reconciliationRequestId;
|
|
222
|
+
if (!requestId || !REQUEST_ID.test(requestId)) {
|
|
223
|
+
return failure('reconciliationRequestId must be a lowercase RFC 4122 UUID');
|
|
224
|
+
}
|
|
225
|
+
if (args.context.surface !== 'local-mcp') {
|
|
226
|
+
return failure('reconciliation is available only through local MCP', 'RECONCILIATION_UNTRUSTED_SURFACE');
|
|
227
|
+
}
|
|
228
|
+
const lock = await acquireLock(args.projectPath ?? args.projectId, args.input.specId, {
|
|
229
|
+
reason: 'implementing to review reconciliation',
|
|
230
|
+
});
|
|
231
|
+
if (!lock) {
|
|
232
|
+
return failure('reconciliation is already running', 'RECONCILIATION_BUSY');
|
|
233
|
+
}
|
|
234
|
+
try {
|
|
235
|
+
const appendOnlyEffect = { value: false };
|
|
236
|
+
const persistReceipt = (path, receipt) => args.io?.writeReceipt?.(path, receipt) ?? writeReconciliationReceipt(path, receipt);
|
|
237
|
+
const receiptPath = await safeReceiptPath(args.projectId, args.input.specId, requestId);
|
|
238
|
+
const existing = await readReconciliationReceipt(receiptPath);
|
|
239
|
+
const spec = await specStore.getSpecFresh(args.projectId, args.input.specId);
|
|
240
|
+
if (!spec) {
|
|
241
|
+
return failure(`spec ${args.input.specId} not found`);
|
|
242
|
+
}
|
|
243
|
+
if (existing && !receiptMatchesRequest(existing, args.projectId, spec.id, requestId)) {
|
|
244
|
+
return failure('reconciliation receipt identity does not match the request');
|
|
245
|
+
}
|
|
246
|
+
if (existing?.state === 'committed') {
|
|
247
|
+
const bindingError = validateDurableReceiptBinding({
|
|
248
|
+
input: args.input,
|
|
249
|
+
receipt: existing,
|
|
250
|
+
projectId: args.projectId,
|
|
251
|
+
specId: spec.id,
|
|
252
|
+
});
|
|
253
|
+
if (bindingError) {
|
|
254
|
+
return bindingError;
|
|
255
|
+
}
|
|
256
|
+
return {
|
|
257
|
+
content: [{ type: 'text', text: `Reconciliation ${requestId} already committed.` }],
|
|
258
|
+
structuredContent: { receipt: existing },
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
if (existing) {
|
|
262
|
+
const bindingError = await validateStoredBinding({
|
|
263
|
+
input: args.input,
|
|
264
|
+
receipt: existing,
|
|
265
|
+
projectId: args.projectId,
|
|
266
|
+
specId: spec.id,
|
|
267
|
+
});
|
|
268
|
+
if (bindingError) {
|
|
269
|
+
return bindingError;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
if (existing?.state === 'aborted') {
|
|
273
|
+
return failure('an aborted reconciliation request cannot be reused', 'RECONCILIATION_ABORTED');
|
|
274
|
+
}
|
|
275
|
+
const recoveredBinding = existing
|
|
276
|
+
? spec.statusHistory?.find((entry) => entry.reconciliationRequestId === requestId &&
|
|
277
|
+
entry.reconciliationBindingDigest === existing.bindingDigest &&
|
|
278
|
+
entry.trigger === 'reconciliation')
|
|
279
|
+
: undefined;
|
|
280
|
+
if (existing && recoveredBinding) {
|
|
281
|
+
const committed = await completeForwardReconciliation({
|
|
282
|
+
receipt: existing,
|
|
283
|
+
spec,
|
|
284
|
+
transitionId: recoveredBinding.transitionId ?? '',
|
|
285
|
+
projectId: args.projectId,
|
|
286
|
+
projectPath: args.projectPath,
|
|
287
|
+
reason: args.input.reason,
|
|
288
|
+
receiptPath,
|
|
289
|
+
persistReceipt,
|
|
290
|
+
markAppendOnly: () => {
|
|
291
|
+
appendOnlyEffect.value = true;
|
|
292
|
+
},
|
|
293
|
+
syncSpecFiles: args.syncSpecFiles,
|
|
294
|
+
});
|
|
295
|
+
return {
|
|
296
|
+
content: [{ type: 'text', text: `Reconciliation ${requestId} recovered.` }],
|
|
297
|
+
structuredContent: { receipt: committed },
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
const validated = await validateRequest({
|
|
301
|
+
input: args.input,
|
|
302
|
+
spec,
|
|
303
|
+
projectId: args.projectId,
|
|
304
|
+
context: args.context,
|
|
305
|
+
});
|
|
306
|
+
if (!('bindingDigest' in validated) ||
|
|
307
|
+
typeof validated.bindingDigest !== 'string' ||
|
|
308
|
+
typeof validated.reviewDigest !== 'string') {
|
|
309
|
+
return validated;
|
|
310
|
+
}
|
|
311
|
+
const expectedTransitionId = args.input.expectedImplementingTransitionId;
|
|
312
|
+
const reason = args.input.reason;
|
|
313
|
+
if (!expectedTransitionId || !reason) {
|
|
314
|
+
return failure('validated reconciliation request is incomplete');
|
|
315
|
+
}
|
|
316
|
+
let receipt = existing ??
|
|
317
|
+
(() => {
|
|
318
|
+
const preparedAt = new Date().toISOString();
|
|
319
|
+
return {
|
|
320
|
+
schema_version: '1.0.0',
|
|
321
|
+
projectId: args.projectId,
|
|
322
|
+
specId: spec.id,
|
|
323
|
+
requestId,
|
|
324
|
+
sourceImplementingTransitionId: expectedTransitionId,
|
|
325
|
+
sourceUpdatedAt: spec.updatedAt,
|
|
326
|
+
bindingDigest: validated.bindingDigest,
|
|
327
|
+
reasonDigest: digest(reason.trim()),
|
|
328
|
+
implementationReviewDigest: validated.reviewDigest,
|
|
329
|
+
supersededArtifacts: [],
|
|
330
|
+
state: 'prepared',
|
|
331
|
+
stateHistory: [{ state: 'prepared', at: preparedAt }],
|
|
332
|
+
preparedAt,
|
|
333
|
+
phase: 'prepared',
|
|
334
|
+
};
|
|
335
|
+
})();
|
|
336
|
+
if (!existing) {
|
|
337
|
+
receipt.supersededArtifacts = await collectSupersededReconciliationArtifacts(args.projectId, spec.id);
|
|
338
|
+
await persistReceipt(receiptPath, receipt);
|
|
339
|
+
}
|
|
340
|
+
if (spec.status !== 'implementing' || spec.updatedAt !== receipt.sourceUpdatedAt) {
|
|
341
|
+
return failure('prepared receipt cannot be safely resumed after state drift', 'RECONCILIATION_RECOVERY_REQUIRED');
|
|
342
|
+
}
|
|
343
|
+
let transition;
|
|
344
|
+
try {
|
|
345
|
+
transition = await transitionSpec(args.projectId, spec.id, 'review', {
|
|
346
|
+
trigger: 'reconciliation',
|
|
347
|
+
actor: 'local-mcp',
|
|
348
|
+
reason: args.input.reason,
|
|
349
|
+
expectedSpec: spec,
|
|
350
|
+
reconciliationRequestId: requestId,
|
|
351
|
+
reconciliationBindingDigest: receipt.bindingDigest,
|
|
352
|
+
});
|
|
353
|
+
receipt = { ...receipt, transitionId: transition.transitionId, phase: 'status-committed' };
|
|
354
|
+
await persistReceipt(receiptPath, receipt);
|
|
355
|
+
const committed = await completeForwardReconciliation({
|
|
356
|
+
receipt,
|
|
357
|
+
spec: transition.spec,
|
|
358
|
+
transitionId: transition.transitionId,
|
|
359
|
+
projectId: args.projectId,
|
|
360
|
+
projectPath: args.projectPath,
|
|
361
|
+
reason: args.input.reason,
|
|
362
|
+
receiptPath,
|
|
363
|
+
persistReceipt,
|
|
364
|
+
markAppendOnly: () => {
|
|
365
|
+
appendOnlyEffect.value = true;
|
|
366
|
+
},
|
|
367
|
+
syncSpecFiles: args.syncSpecFiles,
|
|
368
|
+
});
|
|
369
|
+
return {
|
|
370
|
+
content: [{ type: 'text', text: `Reconciliation ${requestId} committed.` }],
|
|
371
|
+
structuredContent: { receipt: committed, transitionId: transition.transitionId },
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
catch (error) {
|
|
375
|
+
if (appendOnlyEffect.value || receipt.phase === 'audit-appended') {
|
|
376
|
+
return failure('reconciliation requires forward recovery after append-only audit', 'RECONCILIATION_RECOVERY_REQUIRED');
|
|
377
|
+
}
|
|
378
|
+
if (transition) {
|
|
379
|
+
try {
|
|
380
|
+
await rollbackTransitionSpec(transition);
|
|
381
|
+
const rollbackSync = await args.syncSpecFiles(transition.previousSpec, 'review', 'implementing', args.projectPath);
|
|
382
|
+
if (rollbackSync.warning) {
|
|
383
|
+
const recoverable = appendState(receipt, 'prepared', `portable rollback sync failed: ${rollbackSync.warning.reason}`);
|
|
384
|
+
await persistReceipt(receiptPath, recoverable);
|
|
385
|
+
return failure('reconciliation rollback sync failed; receipt remains prepared for operator recovery', 'RECONCILIATION_RECOVERY_REQUIRED');
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
catch (rollbackError) {
|
|
389
|
+
const recoverable = appendState(receipt, 'prepared', `rollback failed: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`);
|
|
390
|
+
await persistReceipt(receiptPath, recoverable);
|
|
391
|
+
return failure('reconciliation rollback failed; receipt remains prepared for operator recovery', 'RECONCILIATION_RECOVERY_REQUIRED');
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
const aborted = appendState(receipt, 'aborted', error instanceof Error ? error.message : 'transition failed');
|
|
395
|
+
await persistReceipt(receiptPath, aborted);
|
|
396
|
+
return failure('reconciliation failed and was compensated', 'RECONCILIATION_ABORTED');
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
catch (error) {
|
|
400
|
+
return failure(error instanceof Error ? error.message : 'reconciliation failed');
|
|
401
|
+
}
|
|
402
|
+
finally {
|
|
403
|
+
await releaseLock(lock);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
//# sourceMappingURL=lifecycle-reconciliation.js.map
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { OFFICIAL_SDD_TOOL_NAMES } from '../../config/official-sdd-tools.js';
|
|
1
2
|
const REGISTERED_TOOL_NAMES = [
|
|
2
3
|
'agent_run_history',
|
|
3
4
|
'analyze_spec_dependencies',
|
|
@@ -244,6 +245,7 @@ const NO_LLM_LOCAL_TOOL_NAMES = [
|
|
|
244
245
|
'worker_status',
|
|
245
246
|
];
|
|
246
247
|
const EXPLICIT_TOOL_NAMES = new Set([
|
|
248
|
+
...OFFICIAL_SDD_TOOL_NAMES,
|
|
247
249
|
...REGISTERED_TOOL_NAMES,
|
|
248
250
|
...LEGACY_OR_RUNTIME_TOOL_NAMES,
|
|
249
251
|
]);
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import type { PlanuCore } from '../types/core-bridge.js';
|
|
2
2
|
import type { MacOSKeychainNativePort } from '../types/security.js';
|
|
3
|
+
export declare class ValidationReceiptSignerUnavailableError extends Error {
|
|
4
|
+
readonly reason: 'PLATFORM_UNSUPPORTED' | 'NATIVE_DISABLED' | 'ARTIFACT_MISSING' | 'NONCANONICAL_ORIGIN' | 'CAPABILITY_MISSING' | 'PORT_INCOMPLETE' | 'OVERRIDE_REJECTED' | 'PROVIDER_NONCANONICAL' | 'PROVIDER_UNAVAILABLE';
|
|
5
|
+
readonly code: 'VALIDATION_RECEIPT_SIGNER_UNAVAILABLE';
|
|
6
|
+
constructor(reason: 'PLATFORM_UNSUPPORTED' | 'NATIVE_DISABLED' | 'ARTIFACT_MISSING' | 'NONCANONICAL_ORIGIN' | 'CAPABILITY_MISSING' | 'PORT_INCOMPLETE' | 'OVERRIDE_REJECTED' | 'PROVIDER_NONCANONICAL' | 'PROVIDER_UNAVAILABLE');
|
|
7
|
+
}
|
|
3
8
|
/** Build the privileged main-thread port only from a canonical, verified native module. */
|
|
4
9
|
export declare function createMacOSKeychainNativePort(platform: NodeJS.Platform, overrideDefined: boolean, disabled: boolean, nativeModule: PlanuCore | null, origin: 'override' | 'local' | 'package' | undefined, capabilities: readonly string[] | undefined): MacOSKeychainNativePort;
|
|
5
10
|
//# sourceMappingURL=macos-keychain-native-port.d.ts.map
|
|
@@ -1,25 +1,37 @@
|
|
|
1
|
+
export class ValidationReceiptSignerUnavailableError extends Error {
|
|
2
|
+
reason;
|
|
3
|
+
code = 'VALIDATION_RECEIPT_SIGNER_UNAVAILABLE';
|
|
4
|
+
constructor(reason) {
|
|
5
|
+
super(`[Planu] VALIDATION_RECEIPT_SIGNER_UNAVAILABLE:${reason} Restart the MCP host and retry.`);
|
|
6
|
+
this.reason = reason;
|
|
7
|
+
this.name = 'ValidationReceiptSignerUnavailableError';
|
|
8
|
+
}
|
|
9
|
+
}
|
|
1
10
|
/** Build the privileged main-thread port only from a canonical, verified native module. */
|
|
2
11
|
export function createMacOSKeychainNativePort(platform, overrideDefined, disabled, nativeModule, origin, capabilities) {
|
|
3
12
|
if (platform !== 'darwin') {
|
|
4
|
-
throw new
|
|
13
|
+
throw new ValidationReceiptSignerUnavailableError('PLATFORM_UNSUPPORTED');
|
|
5
14
|
}
|
|
6
15
|
if (overrideDefined || origin === 'override') {
|
|
7
|
-
throw new
|
|
16
|
+
throw new ValidationReceiptSignerUnavailableError('OVERRIDE_REJECTED');
|
|
17
|
+
}
|
|
18
|
+
if (disabled) {
|
|
19
|
+
throw new ValidationReceiptSignerUnavailableError('NATIVE_DISABLED');
|
|
8
20
|
}
|
|
9
|
-
if (
|
|
10
|
-
throw new
|
|
21
|
+
if (!nativeModule) {
|
|
22
|
+
throw new ValidationReceiptSignerUnavailableError('ARTIFACT_MISSING');
|
|
11
23
|
}
|
|
12
24
|
if (origin !== 'local' && origin !== 'package') {
|
|
13
|
-
throw new
|
|
25
|
+
throw new ValidationReceiptSignerUnavailableError('NONCANONICAL_ORIGIN');
|
|
14
26
|
}
|
|
15
27
|
if (!capabilities?.includes('macos-keychain-v1')) {
|
|
16
|
-
throw new
|
|
28
|
+
throw new ValidationReceiptSignerUnavailableError('CAPABILITY_MISSING');
|
|
17
29
|
}
|
|
18
30
|
const set = nativeModule.macosKeychainSet?.bind(nativeModule);
|
|
19
31
|
const get = nativeModule.macosKeychainGet?.bind(nativeModule);
|
|
20
32
|
const remove = nativeModule.macosKeychainDelete?.bind(nativeModule);
|
|
21
33
|
if (!set || !get || !remove) {
|
|
22
|
-
throw new
|
|
34
|
+
throw new ValidationReceiptSignerUnavailableError('PORT_INCOMPLETE');
|
|
23
35
|
}
|
|
24
36
|
return { set, get, delete: remove };
|
|
25
37
|
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { RemoveCommercialStateOptions, RemoveCommercialStateResult } from '../../types/commercial-migration.js';
|
|
2
|
+
export declare function removeCommercialState(options?: RemoveCommercialStateOptions): Promise<RemoveCommercialStateResult>;
|
|
3
|
+
//# sourceMappingURL=remove-commercial-state.d.ts.map
|