@planu/cli 4.14.1 → 5.1.1
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 +82 -0
- 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 +12 -3
- 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 +33 -19
- 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 +12 -1
- package/dist/engine/evidence-gates/artifact-reader.js +174 -4
- package/dist/engine/evidence-gates/evidence-autofill.d.ts +10 -0
- package/dist/engine/evidence-gates/evidence-autofill.js +148 -0
- package/dist/engine/evidence-gates/evidence-skeletons.d.ts +19 -0
- package/dist/engine/evidence-gates/evidence-skeletons.js +69 -0
- 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/operation-journal.js +10 -4
- 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/minimality/policy-loader.js +247 -6
- 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 +20 -20
- package/dist/engine/planu-core.darwin-x64.node.manifest.json +7 -7
- package/dist/engine/planu-core.darwin-x64.node.sbom.json +20 -20
- package/dist/engine/planu-core.linux-arm64-gnu.node.manifest.json +7 -7
- package/dist/engine/planu-core.linux-arm64-gnu.node.sbom.json +20 -20
- package/dist/engine/planu-core.linux-arm64-musl.node.manifest.json +7 -7
- package/dist/engine/planu-core.linux-arm64-musl.node.sbom.json +20 -20
- package/dist/engine/planu-core.linux-x64-gnu.node.manifest.json +7 -7
- package/dist/engine/planu-core.linux-x64-gnu.node.sbom.json +20 -20
- package/dist/engine/planu-core.linux-x64-musl.node.manifest.json +7 -7
- package/dist/engine/planu-core.linux-x64-musl.node.sbom.json +20 -20
- package/dist/engine/planu-core.win32-arm64-msvc.node.manifest.json +7 -7
- package/dist/engine/planu-core.win32-arm64-msvc.node.sbom.json +20 -20
- package/dist/engine/planu-core.win32-x64-msvc.node.manifest.json +7 -7
- package/dist/engine/planu-core.win32-x64-msvc.node.sbom.json +20 -20
- package/dist/engine/readiness-checker.d.ts +1 -16
- package/dist/engine/readiness-checker.js +27 -88
- package/dist/engine/reverse-engineer/api-detector.js +2 -13
- package/dist/engine/reverse-engineer/complexity-analyzer.js +2 -13
- package/dist/engine/reverse-engineer/config-analyzer.js +2 -13
- package/dist/engine/reverse-engineer/dependency-graph.js +2 -13
- package/dist/engine/reverse-engineer/test-analyzer.js +2 -13
- package/dist/engine/reverse-engineer/walk-ignore.d.ts +3 -0
- package/dist/engine/reverse-engineer/walk-ignore.js +26 -0
- 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 +82 -22
- 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/text-fences.js +20 -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/spec-state-syncer.js +1 -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 +31 -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 +13 -1
- 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-helpers.d.ts +10 -1
- package/dist/tools/challenge-spec-helpers.js +63 -22
- package/dist/tools/challenge-spec.js +23 -8
- package/dist/tools/check-readiness.js +38 -14
- package/dist/tools/configure-telemetry.js +30 -2
- package/dist/tools/create-spec/spec-builder.d.ts +7 -0
- package/dist/tools/create-spec/spec-builder.js +19 -15
- package/dist/tools/create-spec.d.ts +1 -0
- package/dist/tools/create-spec.js +584 -309
- 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 +29 -19
- 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 +50 -2
- 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/batch.d.ts +6 -2
- package/dist/tools/update-status/batch.js +58 -1
- package/dist/tools/update-status/dod-gates.d.ts +16 -1
- package/dist/tools/update-status/dod-gates.js +191 -1
- package/dist/tools/update-status/done-receipt-verifier.d.ts +8 -0
- package/dist/tools/update-status/done-receipt-verifier.js +75 -27
- package/dist/tools/update-status/evidence-gate.d.ts +4 -0
- package/dist/tools/update-status/evidence-gate.js +103 -3
- package/dist/tools/update-status/file-sync.d.ts +4 -3
- package/dist/tools/update-status/file-sync.js +57 -6
- package/dist/tools/update-status/index.d.ts +25 -1
- package/dist/tools/update-status/index.js +292 -78
- 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 +45 -16
- 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/tools/workspace-dashboard-handler.js +38 -0
- 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-autofill.d.ts +34 -0
- package/dist/types/evidence-autofill.js +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 +3 -6
- package/dist/types/index.js +2 -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 +20 -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 +35 -1
- package/dist/types/spec-format.d.ts +25 -1
- package/dist/types/telemetry.d.ts +4 -1
- package/dist/types/transition-log.d.ts +1 -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 +10 -4
- package/package.json +17 -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,343 @@
|
|
|
1
|
+
import { lstat, readFile, readdir, unlink } from 'node:fs/promises';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { basename, dirname, isAbsolute, join } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { atomicWriteFile } from '../safety/atomic-write-file.js';
|
|
6
|
+
import { withUpgradeLock } from '../auto-updater/upgrade-lock.js';
|
|
7
|
+
import { deleteSecretField } from '../../security/secret-field.js';
|
|
8
|
+
import { isSecretReference, parseSecretReference } from '../../security/secret-provider.js';
|
|
9
|
+
import { globalDataDir } from '../../storage/base-store.js';
|
|
10
|
+
import { resolveStorageLayout } from '../../storage/storage-layout.js';
|
|
11
|
+
const TOP_LEVEL_USAGE_KEYS = [
|
|
12
|
+
'trialState',
|
|
13
|
+
'firstRunAt',
|
|
14
|
+
'dailyLimit',
|
|
15
|
+
'dailyCalls',
|
|
16
|
+
'remainingDailyCalls',
|
|
17
|
+
'currentTier',
|
|
18
|
+
'callsByTier',
|
|
19
|
+
'daysSinceFirstRun',
|
|
20
|
+
'isTrialActive',
|
|
21
|
+
'trialDaysLeft',
|
|
22
|
+
];
|
|
23
|
+
const EVENT_USAGE_KEYS = ['tier', 'trial', 'quota', 'dailyLimit', 'remainingDailyCalls'];
|
|
24
|
+
const COMMERCIAL_HOST_ENV_KEYS = [
|
|
25
|
+
'SDD_LICENSE_KEY',
|
|
26
|
+
'SDD_OWNER_TOKEN',
|
|
27
|
+
'PLANU_LICENSE_KEY',
|
|
28
|
+
'PLANU_SKIP_TRIAL',
|
|
29
|
+
];
|
|
30
|
+
const ARCHIVED_USAGE = /^usage-\d{4}-\d{2}-\d{2}\.json$/u;
|
|
31
|
+
function nodeError(error, code) {
|
|
32
|
+
return error instanceof Error && 'code' in error && error.code === code;
|
|
33
|
+
}
|
|
34
|
+
function cleanupCode(error) {
|
|
35
|
+
if (error instanceof Error && error.message.includes('symlink')) {
|
|
36
|
+
return 'SYMLINK_REJECTED';
|
|
37
|
+
}
|
|
38
|
+
if (error instanceof SyntaxError ||
|
|
39
|
+
(error instanceof Error && error.message.includes('malformed'))) {
|
|
40
|
+
return 'MALFORMED_JSON';
|
|
41
|
+
}
|
|
42
|
+
if (nodeError(error, 'EACCES') || nodeError(error, 'EPERM') || nodeError(error, 'EROFS')) {
|
|
43
|
+
return 'WRITE_DENIED';
|
|
44
|
+
}
|
|
45
|
+
if (error instanceof Error && error.message.includes('lock')) {
|
|
46
|
+
return 'LOCK_UNAVAILABLE';
|
|
47
|
+
}
|
|
48
|
+
return 'CLEANUP_FAILED';
|
|
49
|
+
}
|
|
50
|
+
async function regularFile(path) {
|
|
51
|
+
try {
|
|
52
|
+
const info = await lstat(path);
|
|
53
|
+
if (info.isSymbolicLink()) {
|
|
54
|
+
throw new Error('[Planu] cleanup symlink rejected');
|
|
55
|
+
}
|
|
56
|
+
if (!info.isFile()) {
|
|
57
|
+
throw new Error('[Planu] cleanup non-file rejected');
|
|
58
|
+
}
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
if (nodeError(error, 'ENOENT')) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async function parseJsonFile(path) {
|
|
69
|
+
if (!(await regularFile(path))) {
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
return JSON.parse(await readFile(path, 'utf8'));
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
throw new Error('[Planu] cleanup malformed JSON', { cause: error });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function omitKnownKeys(record, keys) {
|
|
80
|
+
const forbidden = new Set(keys);
|
|
81
|
+
const entries = Object.entries(record).filter(([key]) => !forbidden.has(key));
|
|
82
|
+
return {
|
|
83
|
+
value: Object.fromEntries(entries),
|
|
84
|
+
changed: entries.length !== Object.keys(record).length,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function stripCommercialUsage(value) {
|
|
88
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
89
|
+
return { value, changed: false };
|
|
90
|
+
}
|
|
91
|
+
const root = value;
|
|
92
|
+
const topLevel = omitKnownKeys(root, TOP_LEVEL_USAGE_KEYS);
|
|
93
|
+
let changed = topLevel.changed;
|
|
94
|
+
let events = topLevel.value.events;
|
|
95
|
+
if (Array.isArray(root.events)) {
|
|
96
|
+
const sourceEvents = root.events;
|
|
97
|
+
events = sourceEvents.map((event) => {
|
|
98
|
+
if (event === null || typeof event !== 'object' || Array.isArray(event)) {
|
|
99
|
+
return event;
|
|
100
|
+
}
|
|
101
|
+
const sanitized = omitKnownKeys(event, EVENT_USAGE_KEYS);
|
|
102
|
+
changed = sanitized.changed || changed;
|
|
103
|
+
return sanitized.value;
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
value: Array.isArray(root.events) ? { ...topLevel.value, events } : topLevel.value,
|
|
108
|
+
changed,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
async function rewriteJson(path, value, assertFence) {
|
|
112
|
+
await assertFence();
|
|
113
|
+
await atomicWriteFile(path, `${JSON.stringify(value, null, 2)}\n`);
|
|
114
|
+
}
|
|
115
|
+
async function cleanUsage(path, assertFence) {
|
|
116
|
+
const document = await parseJsonFile(path);
|
|
117
|
+
const sanitized = stripCommercialUsage(document);
|
|
118
|
+
if (document === undefined || !sanitized.changed) {
|
|
119
|
+
return 0;
|
|
120
|
+
}
|
|
121
|
+
await rewriteJson(path, sanitized.value, assertFence);
|
|
122
|
+
return 1;
|
|
123
|
+
}
|
|
124
|
+
async function cleanHost(path, assertFence) {
|
|
125
|
+
const document = await parseJsonFile(path);
|
|
126
|
+
if (document === undefined || document === null || typeof document !== 'object') {
|
|
127
|
+
return 0;
|
|
128
|
+
}
|
|
129
|
+
const root = document;
|
|
130
|
+
const sanitizeContainer = (value) => {
|
|
131
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
132
|
+
return { value, changed: false };
|
|
133
|
+
}
|
|
134
|
+
const container = value;
|
|
135
|
+
const planu = container.planu;
|
|
136
|
+
if (planu === null || typeof planu !== 'object' || Array.isArray(planu)) {
|
|
137
|
+
return { value, changed: false };
|
|
138
|
+
}
|
|
139
|
+
const entry = planu;
|
|
140
|
+
const env = entry.env;
|
|
141
|
+
if (env === null || typeof env !== 'object' || Array.isArray(env)) {
|
|
142
|
+
return { value, changed: false };
|
|
143
|
+
}
|
|
144
|
+
const sanitized = omitKnownKeys(env, COMMERCIAL_HOST_ENV_KEYS);
|
|
145
|
+
return {
|
|
146
|
+
value: sanitized.changed
|
|
147
|
+
? { ...container, planu: { ...entry, env: sanitized.value } }
|
|
148
|
+
: value,
|
|
149
|
+
changed: sanitized.changed,
|
|
150
|
+
};
|
|
151
|
+
};
|
|
152
|
+
const mcpServers = sanitizeContainer(root.mcpServers);
|
|
153
|
+
const contextServers = sanitizeContainer(root.context_servers);
|
|
154
|
+
const changed = mcpServers.changed || contextServers.changed;
|
|
155
|
+
const sanitizedRoot = {
|
|
156
|
+
...root,
|
|
157
|
+
...(Object.hasOwn(root, 'mcpServers') ? { mcpServers: mcpServers.value } : {}),
|
|
158
|
+
...(Object.hasOwn(root, 'context_servers') ? { context_servers: contextServers.value } : {}),
|
|
159
|
+
};
|
|
160
|
+
if (!changed) {
|
|
161
|
+
return 0;
|
|
162
|
+
}
|
|
163
|
+
await rewriteJson(path, sanitizedRoot, assertFence);
|
|
164
|
+
return 1;
|
|
165
|
+
}
|
|
166
|
+
async function removeFile(path, assertFence) {
|
|
167
|
+
if (!(await regularFile(path))) {
|
|
168
|
+
return 0;
|
|
169
|
+
}
|
|
170
|
+
await assertFence();
|
|
171
|
+
await unlink(path);
|
|
172
|
+
return 1;
|
|
173
|
+
}
|
|
174
|
+
async function matchingFiles(directory, predicate) {
|
|
175
|
+
try {
|
|
176
|
+
return (await readdir(directory)).filter(predicate).map((name) => join(directory, name));
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
if (nodeError(error, 'ENOENT')) {
|
|
180
|
+
return [];
|
|
181
|
+
}
|
|
182
|
+
throw error;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
async function licenseBackups(licensePath) {
|
|
186
|
+
const file = basename(licensePath);
|
|
187
|
+
const stem = file.endsWith('.json') ? file.slice(0, -5) : file;
|
|
188
|
+
return matchingFiles(dirname(licensePath), (entry) => entry === `${file}.bak` ||
|
|
189
|
+
(entry.startsWith(`${file}.bak.`) && /^\d+$/u.test(entry.slice(file.length + 5))) ||
|
|
190
|
+
(entry.startsWith(`${stem}.backup-`) && entry.endsWith('.json')));
|
|
191
|
+
}
|
|
192
|
+
async function hostPathsFromManifest(path) {
|
|
193
|
+
try {
|
|
194
|
+
const document = await parseJsonFile(path);
|
|
195
|
+
if (document === undefined || document === null || typeof document !== 'object') {
|
|
196
|
+
return [];
|
|
197
|
+
}
|
|
198
|
+
const records = document.installations;
|
|
199
|
+
if (!Array.isArray(records)) {
|
|
200
|
+
return [];
|
|
201
|
+
}
|
|
202
|
+
return records.flatMap((record) => {
|
|
203
|
+
if (record === null || typeof record !== 'object') {
|
|
204
|
+
return [];
|
|
205
|
+
}
|
|
206
|
+
const configPath = record.configPath;
|
|
207
|
+
return typeof configPath === 'string' && isAbsolute(configPath) ? [configPath] : [];
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
return [];
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
function defaultLegacyDataPaths() {
|
|
215
|
+
const packageRoot = fileURLToPath(new URL('../../../', import.meta.url));
|
|
216
|
+
return [
|
|
217
|
+
join(packageRoot, 'data', 'global'),
|
|
218
|
+
join(homedir(), '.planu', 'data', 'global'),
|
|
219
|
+
join(homedir(), '.planu'),
|
|
220
|
+
];
|
|
221
|
+
}
|
|
222
|
+
function defaultHostConfigPaths(home, project) {
|
|
223
|
+
return [
|
|
224
|
+
join(home, '.claude.json'),
|
|
225
|
+
join(home, '.cursor', 'mcp.json'),
|
|
226
|
+
join(home, '.codeium', 'windsurf', 'mcp_config.json'),
|
|
227
|
+
join(home, '.config', 'zed', 'settings.json'),
|
|
228
|
+
join(home, '.vscode', 'globalStorage', 'saoudrizwan.claude-dev', 'settings', 'cline_mcp_settings.json'),
|
|
229
|
+
join(home, '.continue', 'config.json'),
|
|
230
|
+
join(project, '.claude.json'),
|
|
231
|
+
join(project, '.cursor', 'mcp.json'),
|
|
232
|
+
join(project, '.windsurf', 'mcp.json'),
|
|
233
|
+
join(project, '.zed', 'settings.json'),
|
|
234
|
+
join(project, '.continue', 'config.json'),
|
|
235
|
+
];
|
|
236
|
+
}
|
|
237
|
+
async function cleanDataDirectory(dataPath, assertFence, strict) {
|
|
238
|
+
const errors = [];
|
|
239
|
+
const attempt = async (operation, kind) => {
|
|
240
|
+
try {
|
|
241
|
+
const count = await operation();
|
|
242
|
+
if (kind === 'remove') {
|
|
243
|
+
removedFiles += count;
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
updatedFiles += count;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
const code = cleanupCode(error);
|
|
251
|
+
errors.push(code);
|
|
252
|
+
if (strict) {
|
|
253
|
+
throw new Error(`[Planu] commercial cleanup failed: ${code}`, { cause: error });
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
let removedFiles = 0;
|
|
258
|
+
let updatedFiles = 0;
|
|
259
|
+
const licensePath = join(dataPath, 'license.json');
|
|
260
|
+
await attempt(async () => {
|
|
261
|
+
const licenseDocument = await parseJsonFile(licensePath);
|
|
262
|
+
if (licenseDocument && typeof licenseDocument === 'object') {
|
|
263
|
+
const reference = licenseDocument.licenseKey;
|
|
264
|
+
if (isSecretReference(reference) && parseSecretReference(reference).purpose === 'license') {
|
|
265
|
+
await assertFence();
|
|
266
|
+
await deleteSecretField(reference);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return removeFile(licensePath, assertFence);
|
|
270
|
+
}, 'remove');
|
|
271
|
+
await attempt(async () => {
|
|
272
|
+
let count = 0;
|
|
273
|
+
for (const backup of await licenseBackups(licensePath)) {
|
|
274
|
+
count += await removeFile(backup, assertFence);
|
|
275
|
+
}
|
|
276
|
+
return count;
|
|
277
|
+
}, 'remove');
|
|
278
|
+
await attempt(() => removeFile(join(dataPath, 'first-run.json'), assertFence), 'remove');
|
|
279
|
+
await attempt(() => cleanUsage(join(dataPath, 'usage.json'), assertFence), 'update');
|
|
280
|
+
let archives = [];
|
|
281
|
+
try {
|
|
282
|
+
archives = await matchingFiles(dataPath, (name) => ARCHIVED_USAGE.test(name));
|
|
283
|
+
}
|
|
284
|
+
catch (error) {
|
|
285
|
+
errors.push(cleanupCode(error));
|
|
286
|
+
}
|
|
287
|
+
for (const archive of archives) {
|
|
288
|
+
await attempt(() => cleanUsage(archive, assertFence), 'update');
|
|
289
|
+
}
|
|
290
|
+
return { removedFiles, updatedFiles, errors };
|
|
291
|
+
}
|
|
292
|
+
export async function removeCommercialState(options = {}) {
|
|
293
|
+
const layout = resolveStorageLayout();
|
|
294
|
+
const lockPath = options.lockPath ?? layout.installationManifest;
|
|
295
|
+
const errors = [];
|
|
296
|
+
const run = options.lockRunner ?? withUpgradeLock;
|
|
297
|
+
try {
|
|
298
|
+
return await run(lockPath, async (assertFence) => {
|
|
299
|
+
let removedFiles = 0;
|
|
300
|
+
let updatedFiles = 0;
|
|
301
|
+
const dataPaths = new Set([
|
|
302
|
+
options.globalDataPath ?? globalDataDir(),
|
|
303
|
+
...(options.legacyGlobalDataPaths ?? defaultLegacyDataPaths()),
|
|
304
|
+
]);
|
|
305
|
+
const hostPaths = new Set([
|
|
306
|
+
...(options.hostConfigPaths ?? []),
|
|
307
|
+
...defaultHostConfigPaths(options.homeDirectory ?? homedir(), options.projectDirectory ?? process.cwd()),
|
|
308
|
+
...(await hostPathsFromManifest(layout.installationManifest)),
|
|
309
|
+
]);
|
|
310
|
+
const attempt = async (operation) => {
|
|
311
|
+
try {
|
|
312
|
+
const result = await operation();
|
|
313
|
+
removedFiles += result.removedFiles ?? 0;
|
|
314
|
+
updatedFiles += result.updatedFiles ?? 0;
|
|
315
|
+
}
|
|
316
|
+
catch (error) {
|
|
317
|
+
const code = cleanupCode(error);
|
|
318
|
+
errors.push(code);
|
|
319
|
+
if (options.strict) {
|
|
320
|
+
throw new Error(`[Planu] commercial cleanup failed: ${code}`, { cause: error });
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
for (const path of dataPaths) {
|
|
325
|
+
const result = await cleanDataDirectory(path, assertFence, options.strict === true);
|
|
326
|
+
removedFiles += result.removedFiles;
|
|
327
|
+
updatedFiles += result.updatedFiles;
|
|
328
|
+
errors.push(...result.errors);
|
|
329
|
+
}
|
|
330
|
+
for (const path of hostPaths) {
|
|
331
|
+
await attempt(async () => ({ updatedFiles: await cleanHost(path, assertFence) }));
|
|
332
|
+
}
|
|
333
|
+
return { removedFiles, updatedFiles, errors };
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
catch (error) {
|
|
337
|
+
if (options.strict) {
|
|
338
|
+
throw error;
|
|
339
|
+
}
|
|
340
|
+
return { removedFiles: 0, updatedFiles: 0, errors: [cleanupCode(error)] };
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
//# sourceMappingURL=remove-commercial-state.js.map
|
|
@@ -1,9 +1,239 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { constants } from 'node:fs';
|
|
2
|
+
import { lstat, open, readFile, realpath } from 'node:fs/promises';
|
|
3
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
3
4
|
import { fileURLToPath } from 'node:url';
|
|
5
|
+
const BUNDLED_POLICY_ID = 'bundled:minimal-implementation-gate';
|
|
6
|
+
const PROJECT_CONFIG_ID = 'project:planu.json#minimalImplementationGate';
|
|
7
|
+
const REDACTED_POLICY_MESSAGE = 'Minimality policy source is unavailable (external:redacted).';
|
|
8
|
+
const REDACTED_BUNDLED_MESSAGE = 'Bundled minimality policy is unavailable (external:redacted).';
|
|
9
|
+
const MAX_POLICY_BYTES = 1024 * 1024;
|
|
10
|
+
const POLICY_READ_CHUNK_BYTES = 64 * 1024;
|
|
11
|
+
class PolicySourceError extends Error {
|
|
12
|
+
code;
|
|
13
|
+
constructor(code) {
|
|
14
|
+
super(code === 'MINIMALITY_BUNDLED_POLICY_UNAVAILABLE'
|
|
15
|
+
? REDACTED_BUNDLED_MESSAGE
|
|
16
|
+
: REDACTED_POLICY_MESSAGE);
|
|
17
|
+
this.name = 'PolicySourceError';
|
|
18
|
+
this.code = code;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
4
21
|
function configPath() {
|
|
5
22
|
return join(dirname(fileURLToPath(import.meta.url)), '../../config/minimal-implementation-gate.json');
|
|
6
23
|
}
|
|
24
|
+
function isOutside(root, candidate) {
|
|
25
|
+
const pathFromRoot = relative(root, candidate);
|
|
26
|
+
return pathFromRoot === '..' || pathFromRoot.startsWith(`..${sep}`) || isAbsolute(pathFromRoot);
|
|
27
|
+
}
|
|
28
|
+
function nodeErrorCode(error) {
|
|
29
|
+
return typeof error === 'object' && error !== null && 'code' in error
|
|
30
|
+
? String(error.code)
|
|
31
|
+
: undefined;
|
|
32
|
+
}
|
|
33
|
+
function sourceErrorFor(error) {
|
|
34
|
+
if (error instanceof PolicySourceError) {
|
|
35
|
+
return error;
|
|
36
|
+
}
|
|
37
|
+
switch (nodeErrorCode(error)) {
|
|
38
|
+
case 'ENOENT':
|
|
39
|
+
case 'ENOTDIR':
|
|
40
|
+
return new PolicySourceError('MINIMALITY_POLICY_SOURCE_NOT_FOUND');
|
|
41
|
+
case 'ELOOP':
|
|
42
|
+
return new PolicySourceError('MINIMALITY_POLICY_SOURCE_SYMLINK_REJECTED');
|
|
43
|
+
case 'EISDIR':
|
|
44
|
+
return new PolicySourceError('MINIMALITY_POLICY_SOURCE_NOT_REGULAR');
|
|
45
|
+
case undefined:
|
|
46
|
+
default:
|
|
47
|
+
return new PolicySourceError('MINIMALITY_POLICY_SOURCE_UNREADABLE');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function sameIdentity(left, right) {
|
|
51
|
+
return left.dev === right.dev && left.ino === right.ino;
|
|
52
|
+
}
|
|
53
|
+
function sameStableMetadata(left, right) {
|
|
54
|
+
return (sameIdentity(left, right) &&
|
|
55
|
+
left.size === right.size &&
|
|
56
|
+
left.mtimeNs === right.mtimeNs &&
|
|
57
|
+
left.ctimeNs === right.ctimeNs);
|
|
58
|
+
}
|
|
59
|
+
async function assertNoSymlinkComponents(projectRoot, candidate) {
|
|
60
|
+
const relativePath = relative(projectRoot, candidate);
|
|
61
|
+
let current = projectRoot;
|
|
62
|
+
for (const component of relativePath.split(sep).filter(Boolean)) {
|
|
63
|
+
current = join(current, component);
|
|
64
|
+
let status;
|
|
65
|
+
try {
|
|
66
|
+
status = await lstat(current);
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
throw sourceErrorFor(error);
|
|
70
|
+
}
|
|
71
|
+
if (status.isSymbolicLink()) {
|
|
72
|
+
throw new PolicySourceError('MINIMALITY_POLICY_SOURCE_SYMLINK_REJECTED');
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function validateExplicitPath(path) {
|
|
77
|
+
if (path.length === 0 ||
|
|
78
|
+
path.length > 4096 ||
|
|
79
|
+
/^project:/iu.test(path) ||
|
|
80
|
+
path.includes('\0') ||
|
|
81
|
+
path.includes('?') ||
|
|
82
|
+
path.includes('#') ||
|
|
83
|
+
(sep === '/' && path.includes('\\'))) {
|
|
84
|
+
throw new PolicySourceError('MINIMALITY_POLICY_SOURCE_INVALID_PATH');
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async function readBoundedPolicy(handle) {
|
|
88
|
+
const chunks = [];
|
|
89
|
+
let offset = 0;
|
|
90
|
+
while (offset <= MAX_POLICY_BYTES) {
|
|
91
|
+
const remaining = MAX_POLICY_BYTES + 1 - offset;
|
|
92
|
+
const chunk = Buffer.allocUnsafe(Math.min(POLICY_READ_CHUNK_BYTES, remaining));
|
|
93
|
+
const { bytesRead } = await handle.read(chunk, 0, chunk.length, offset);
|
|
94
|
+
if (bytesRead === 0) {
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
offset += bytesRead;
|
|
98
|
+
if (offset > MAX_POLICY_BYTES) {
|
|
99
|
+
throw new PolicySourceError('MINIMALITY_POLICY_SOURCE_UNREADABLE');
|
|
100
|
+
}
|
|
101
|
+
chunks.push(chunk.subarray(0, bytesRead));
|
|
102
|
+
}
|
|
103
|
+
return Buffer.concat(chunks, offset).toString('utf8');
|
|
104
|
+
}
|
|
105
|
+
async function resolvePolicySource(projectPath, explicitPath) {
|
|
106
|
+
if (explicitPath === undefined) {
|
|
107
|
+
return { path: configPath(), identifier: BUNDLED_POLICY_ID, bundled: true };
|
|
108
|
+
}
|
|
109
|
+
validateExplicitPath(explicitPath);
|
|
110
|
+
if (projectPath === undefined) {
|
|
111
|
+
throw new PolicySourceError('MINIMALITY_POLICY_SOURCE_OUTSIDE_ALLOWED_ROOT');
|
|
112
|
+
}
|
|
113
|
+
const lexicalRoot = resolve(projectPath);
|
|
114
|
+
const candidate = isAbsolute(explicitPath)
|
|
115
|
+
? resolve(explicitPath)
|
|
116
|
+
: resolve(lexicalRoot, explicitPath);
|
|
117
|
+
if (isOutside(lexicalRoot, candidate)) {
|
|
118
|
+
throw new PolicySourceError('MINIMALITY_POLICY_SOURCE_OUTSIDE_ALLOWED_ROOT');
|
|
119
|
+
}
|
|
120
|
+
let canonicalRoot;
|
|
121
|
+
try {
|
|
122
|
+
canonicalRoot = await realpath(lexicalRoot);
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
throw new PolicySourceError('MINIMALITY_POLICY_SOURCE_OUTSIDE_ALLOWED_ROOT');
|
|
126
|
+
}
|
|
127
|
+
await assertNoSymlinkComponents(lexicalRoot, candidate);
|
|
128
|
+
let candidateStatus;
|
|
129
|
+
let canonicalCandidate;
|
|
130
|
+
try {
|
|
131
|
+
[candidateStatus, canonicalCandidate] = await Promise.all([
|
|
132
|
+
lstat(candidate),
|
|
133
|
+
realpath(candidate),
|
|
134
|
+
]);
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
throw sourceErrorFor(error);
|
|
138
|
+
}
|
|
139
|
+
if (candidateStatus.isSymbolicLink()) {
|
|
140
|
+
throw new PolicySourceError('MINIMALITY_POLICY_SOURCE_SYMLINK_REJECTED');
|
|
141
|
+
}
|
|
142
|
+
if (!candidateStatus.isFile()) {
|
|
143
|
+
throw new PolicySourceError('MINIMALITY_POLICY_SOURCE_NOT_REGULAR');
|
|
144
|
+
}
|
|
145
|
+
if (isOutside(canonicalRoot, canonicalCandidate)) {
|
|
146
|
+
throw new PolicySourceError('MINIMALITY_POLICY_SOURCE_OUTSIDE_ALLOWED_ROOT');
|
|
147
|
+
}
|
|
148
|
+
const portablePath = relative(canonicalRoot, canonicalCandidate)
|
|
149
|
+
.replaceAll('\\', '/')
|
|
150
|
+
.normalize('NFC');
|
|
151
|
+
if (portablePath.length === 0 ||
|
|
152
|
+
portablePath.startsWith('../') ||
|
|
153
|
+
portablePath.startsWith('project:') ||
|
|
154
|
+
/^[a-z]:/iu.test(portablePath) ||
|
|
155
|
+
isAbsolute(portablePath)) {
|
|
156
|
+
throw new PolicySourceError('MINIMALITY_POLICY_SOURCE_INVALID_PATH');
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
path: candidate,
|
|
160
|
+
identifier: `project:${portablePath}`,
|
|
161
|
+
bundled: false,
|
|
162
|
+
projectRoot: lexicalRoot,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
async function readStableJson(source) {
|
|
166
|
+
try {
|
|
167
|
+
if (source.projectRoot !== undefined) {
|
|
168
|
+
await assertNoSymlinkComponents(source.projectRoot, source.path);
|
|
169
|
+
}
|
|
170
|
+
const canonicalBefore = await realpath(source.path);
|
|
171
|
+
const pathBefore = await lstat(source.path, { bigint: true });
|
|
172
|
+
if (pathBefore.isSymbolicLink()) {
|
|
173
|
+
throw new PolicySourceError('MINIMALITY_POLICY_SOURCE_SYMLINK_REJECTED');
|
|
174
|
+
}
|
|
175
|
+
if (!pathBefore.isFile()) {
|
|
176
|
+
throw new PolicySourceError('MINIMALITY_POLICY_SOURCE_NOT_REGULAR');
|
|
177
|
+
}
|
|
178
|
+
let handle;
|
|
179
|
+
try {
|
|
180
|
+
handle = await open(source.path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
if (nodeErrorCode(error) === 'ELOOP') {
|
|
184
|
+
throw new PolicySourceError('MINIMALITY_POLICY_SOURCE_CHANGED');
|
|
185
|
+
}
|
|
186
|
+
throw error;
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
const handleBefore = await handle.stat({ bigint: true });
|
|
190
|
+
if (!handleBefore.isFile() || !sameIdentity(pathBefore, handleBefore)) {
|
|
191
|
+
throw new PolicySourceError('MINIMALITY_POLICY_SOURCE_CHANGED');
|
|
192
|
+
}
|
|
193
|
+
if (handleBefore.size > BigInt(MAX_POLICY_BYTES)) {
|
|
194
|
+
throw new PolicySourceError('MINIMALITY_POLICY_SOURCE_UNREADABLE');
|
|
195
|
+
}
|
|
196
|
+
const content = await readBoundedPolicy(handle);
|
|
197
|
+
let handleAfter;
|
|
198
|
+
let pathAfter;
|
|
199
|
+
let canonicalAfter;
|
|
200
|
+
try {
|
|
201
|
+
[handleAfter, pathAfter, canonicalAfter] = await Promise.all([
|
|
202
|
+
handle.stat({ bigint: true }),
|
|
203
|
+
lstat(source.path, { bigint: true }),
|
|
204
|
+
realpath(source.path),
|
|
205
|
+
]);
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
throw new PolicySourceError('MINIMALITY_POLICY_SOURCE_CHANGED');
|
|
209
|
+
}
|
|
210
|
+
if (canonicalBefore !== canonicalAfter ||
|
|
211
|
+
!sameStableMetadata(pathBefore, pathAfter) ||
|
|
212
|
+
!sameStableMetadata(handleBefore, handleAfter) ||
|
|
213
|
+
!sameIdentity(pathAfter, handleAfter)) {
|
|
214
|
+
throw new PolicySourceError('MINIMALITY_POLICY_SOURCE_CHANGED');
|
|
215
|
+
}
|
|
216
|
+
if (source.projectRoot !== undefined) {
|
|
217
|
+
try {
|
|
218
|
+
await assertNoSymlinkComponents(source.projectRoot, source.path);
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
throw new PolicySourceError('MINIMALITY_POLICY_SOURCE_CHANGED');
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return JSON.parse(content);
|
|
225
|
+
}
|
|
226
|
+
finally {
|
|
227
|
+
await handle.close();
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
if (source.bundled) {
|
|
232
|
+
throw new PolicySourceError('MINIMALITY_BUNDLED_POLICY_UNAVAILABLE');
|
|
233
|
+
}
|
|
234
|
+
throw sourceErrorFor(error);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
7
237
|
function isRecord(value) {
|
|
8
238
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
9
239
|
}
|
|
@@ -109,10 +339,21 @@ async function readJson(path) {
|
|
|
109
339
|
return JSON.parse(await readFile(path, 'utf-8'));
|
|
110
340
|
}
|
|
111
341
|
export async function loadMinimalImplementationPolicy(projectPath, options = {}) {
|
|
112
|
-
const
|
|
113
|
-
|
|
342
|
+
const source = await resolvePolicySource(projectPath, options.defaultPolicyPath);
|
|
343
|
+
let defaultPolicy;
|
|
344
|
+
try {
|
|
345
|
+
defaultPolicy = validatePolicy(await readStableJson(source));
|
|
346
|
+
}
|
|
347
|
+
catch (error) {
|
|
348
|
+
if (error instanceof PolicySourceError) {
|
|
349
|
+
throw error;
|
|
350
|
+
}
|
|
351
|
+
throw source.bundled
|
|
352
|
+
? new PolicySourceError('MINIMALITY_BUNDLED_POLICY_UNAVAILABLE')
|
|
353
|
+
: new PolicySourceError('MINIMALITY_POLICY_SOURCE_UNREADABLE');
|
|
354
|
+
}
|
|
114
355
|
const evidence = [
|
|
115
|
-
{ source: 'policy', key:
|
|
356
|
+
{ source: 'policy', key: source.identifier },
|
|
116
357
|
];
|
|
117
358
|
let merged = defaultPolicy;
|
|
118
359
|
if (projectPath !== undefined) {
|
|
@@ -121,7 +362,7 @@ export async function loadMinimalImplementationPolicy(projectPath, options = {})
|
|
|
121
362
|
const config = await readJson(planuConfigPath);
|
|
122
363
|
if (isRecord(config) && isRecord(config.minimalImplementationGate)) {
|
|
123
364
|
merged = validatePolicy(mergeDeep(defaultPolicy, config.minimalImplementationGate));
|
|
124
|
-
evidence.push({ source: 'project-config', key:
|
|
365
|
+
evidence.push({ source: 'project-config', key: PROJECT_CONFIG_ID });
|
|
125
366
|
}
|
|
126
367
|
}
|
|
127
368
|
catch {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 2,
|
|
3
|
-
"cliVersion": "
|
|
4
|
-
"engineVersion": "
|
|
5
|
-
"buildId": "1-
|
|
3
|
+
"cliVersion": "5.1.1",
|
|
4
|
+
"engineVersion": "5.1.1",
|
|
5
|
+
"buildId": "1-5.1.1-aarch64-apple-darwin",
|
|
6
6
|
"protocolAbi": 1,
|
|
7
7
|
"capabilityAbi": 2,
|
|
8
8
|
"nodeApiAbi": 4,
|
|
@@ -17,15 +17,15 @@
|
|
|
17
17
|
"platform": "darwin",
|
|
18
18
|
"arch": "arm64",
|
|
19
19
|
"libc": null,
|
|
20
|
-
"artifactSha256": "
|
|
20
|
+
"artifactSha256": "3791a7635fda74d3c0c5a226115ac4ab78c98afc366d0451a107ed1419139873",
|
|
21
21
|
"sbom": {
|
|
22
22
|
"format": "CycloneDX-1.5",
|
|
23
23
|
"path": "planu-core.darwin-arm64.node.sbom.json",
|
|
24
|
-
"sha256": "
|
|
24
|
+
"sha256": "434d0491e2130ac49b7eb34989f5ae2135f3ad9f54f0042f5bb3ba660bd2c568"
|
|
25
25
|
},
|
|
26
26
|
"provenance": {
|
|
27
27
|
"builder": "scripts/build-rust-local.sh",
|
|
28
|
-
"sourceCommit": "
|
|
28
|
+
"sourceCommit": "5046eff43d23641fe60bd1c01e8c34277172db16",
|
|
29
29
|
"sourceTreeDirty": false,
|
|
30
30
|
"sourceDateEpoch": 1,
|
|
31
31
|
"rustToolchain": "rustc 1.95.0 (59807616e 2026-04-14)",
|
|
@@ -34,6 +34,6 @@
|
|
|
34
34
|
"signature": {
|
|
35
35
|
"algorithm": "Ed25519",
|
|
36
36
|
"keyId": "e2ee765cb5ad9fbdc433ece332bce26d746d5e350b3acde0721a4c8c6337ff7b",
|
|
37
|
-
"value": "
|
|
37
|
+
"value": "7kFQj3Sq5rFuMciulVbszYysMir4sSVuTrgLv0exAYSmkfwj16xag4ldwYRCJky/EXeCn1ZYZYJvfuCNEqZ5DQ=="
|
|
38
38
|
}
|
|
39
39
|
}
|