@planu/cli 5.3.67 → 5.3.69
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 +92 -0
- package/dist/.planu-build.json +1 -1
- package/dist/cli/commands/install.d.ts +2 -15
- package/dist/config/release-policy.json +2 -2
- package/dist/core/shared/fs.d.ts +11 -0
- package/dist/core/shared/fs.js +90 -0
- package/dist/core/shared/strings.d.ts +3 -0
- package/dist/core/shared/strings.js +13 -0
- package/dist/engine/actuals/git-analyzer.js +1 -1
- package/dist/engine/actuals/progress-parser.js +1 -3
- package/dist/engine/agent-handoff-store.js +1 -0
- package/dist/engine/agent-squad/dispatcher.js +1 -0
- package/dist/engine/agent-telemetry.js +1 -0
- package/dist/engine/auth/config.js +1 -0
- package/dist/engine/auto-updater/atomic-upgrade.js +1 -0
- package/dist/engine/auto-updater/index.js +1 -0
- package/dist/engine/autopilot/action-registry.js +20 -0
- package/dist/engine/autopilot/audit-logger.js +1 -0
- package/dist/engine/autopilot/bootstrap.js +9 -2
- package/dist/engine/autopilot/complete-loop.js +4 -0
- package/dist/engine/autopilot/crash-shield-rate-limiter.js +1 -0
- package/dist/engine/autopilot/event-bus.js +1 -0
- package/dist/engine/autopilot/state-updater.js +6 -0
- package/dist/engine/browser-validator.js +1 -3
- package/dist/engine/build-tool-detector.js +1 -8
- package/dist/engine/bundle-installer.js +2 -10
- package/dist/engine/cascade-hooks/core/append-releases.js +1 -0
- package/dist/engine/cascade-hooks/core/write-session-json.js +2 -2
- package/dist/engine/cascade-hooks/hooks/dispatch-hook-event.hook.d.ts +3 -1
- package/dist/engine/cascade-hooks/hooks/dispatch-hook-event.hook.js +12 -3
- package/dist/engine/cascade-hooks/hooks/estimate-calibration.hook.js +1 -0
- package/dist/engine/cascade-hooks/hooks/housekeeping-on-done.hook.js +2 -0
- package/dist/engine/cascade-hooks/hooks/implementing-kickoff.hook.js +5 -1
- package/dist/engine/cascade-hooks/hooks/lesson-drift.hook.js +1 -0
- package/dist/engine/cascade-hooks/hooks/orchestration-plan.hook.js +1 -0
- package/dist/engine/cascade-hooks/hooks/session-context.hook.js +4 -2
- package/dist/engine/cascade-hooks/hooks/slack-notify.hook.js +1 -0
- package/dist/engine/cascade-hooks/hooks/status-json.hook.js +2 -0
- package/dist/engine/cascade-hooks/hooks/velocity-profile.hook.js +2 -0
- package/dist/engine/cascade-hooks/runner.js +2 -0
- package/dist/engine/cascade-hooks/state-drift-detector.js +2 -0
- package/dist/engine/checkpoint/checkpoint-manager.js +2 -0
- package/dist/engine/checkpoint/policy-engine.js +1 -0
- package/dist/engine/ci-generator/stack-detector.js +2 -10
- package/dist/engine/clarification-gate/token-store.js +1 -0
- package/dist/engine/claude-config-auditor/claudemd-auditor.js +2 -10
- package/dist/engine/claude-config-auditor/settings-auditor.js +2 -10
- package/dist/engine/client-detection.js +2 -10
- package/dist/engine/compliance/auto-remediator.js +1 -0
- package/dist/engine/compliance/evidence-builder.d.ts +2 -2
- package/dist/engine/compliance/framework-catalog.d.ts +6 -6
- package/dist/engine/compliance/gap-analyzer.d.ts +2 -2
- package/dist/engine/config-health/build-tool-checker.js +1 -9
- package/dist/engine/config-health/ci-commands-collector.d.ts +2 -1
- package/dist/engine/config-health/ci-commands-collector.js +2 -9
- package/dist/engine/config-health/go-checker.js +1 -9
- package/dist/engine/config-health/mcp-config-checker.js +2 -10
- package/dist/engine/config-health/python-checker.js +1 -9
- package/dist/engine/config-health/rust-checker.js +1 -9
- package/dist/engine/config-health/ts-checker-scripts.js +1 -9
- package/dist/engine/config-health/ts-checker.js +1 -9
- package/dist/engine/context-artifacts/store.js +1 -0
- package/dist/engine/context-md-generator.js +1 -0
- package/dist/engine/context-orchestrator/index.js +2 -0
- package/dist/engine/convention-scanner/convention-parser.js +2 -9
- package/dist/engine/cost-guardrails.js +1 -0
- package/dist/engine/cost-tracking/ledger.js +1 -0
- package/dist/engine/criterion-identity.js +1 -1
- package/dist/engine/dashboard/data-loader.js +1 -0
- package/dist/engine/dashboard/drift-score.js +2 -0
- package/dist/engine/dashboard/spec-updater.d.ts +3 -1
- package/dist/engine/dashboard/spec-updater.js +9 -2
- package/dist/engine/dashboard/templates-layout.d.ts +2 -2
- package/dist/engine/dashboard/templates-layout.js +2 -9
- package/dist/engine/data-projects-gc/gc-runner.js +2 -4
- package/dist/engine/deleter/audit-logger.js +1 -0
- package/dist/engine/deleter/core.js +6 -0
- package/dist/engine/deleter/trash-manager.js +1 -0
- package/dist/engine/desktop-detector.js +1 -9
- package/dist/engine/detection-utils.d.ts +2 -1
- package/dist/engine/detection-utils.js +3 -10
- package/dist/engine/detectors/advanced-framework-detector/helpers.d.ts +3 -2
- package/dist/engine/detectors/advanced-framework-detector/helpers.js +3 -17
- package/dist/engine/detectors/cicd-detector.js +2 -10
- package/dist/engine/detectors/css-framework-detector.js +2 -10
- package/dist/engine/detectors/deployment-detector.js +3 -12
- package/dist/engine/detectors/frontend-perf-detector.js +2 -10
- package/dist/engine/detectors/gitops-detector.js +3 -11
- package/dist/engine/detectors/idp-detector.js +1 -3
- package/dist/engine/detectors/k3s-detector.js +3 -11
- package/dist/engine/detectors/llm-detector/file-readers.d.ts +2 -1
- package/dist/engine/detectors/llm-detector/file-readers.js +2 -9
- package/dist/engine/detectors/mfe-detector.js +5 -13
- package/dist/engine/detectors/monitoring-detector.js +2 -11
- package/dist/engine/detectors/monorepo-detector.d.ts +1 -0
- package/dist/engine/detectors/monorepo-detector.js +1 -10
- package/dist/engine/detectors/nomad-detector.js +3 -11
- package/dist/engine/detectors/otel-ecosystem-detectors.d.ts +2 -1
- package/dist/engine/detectors/otel-ecosystem-detectors.js +2 -10
- package/dist/engine/doc-generator/executive-summary/html-template.js +1 -7
- package/dist/engine/doc-generator/executive-summary/section-builders.js +1 -7
- package/dist/engine/doc-generator/per-spec-report/mermaid-renderer.js +1 -7
- package/dist/engine/doc-generator/per-spec-report/spec-section-builders.js +1 -7
- package/dist/engine/doc-generator/per-spec-report/technical-report.js +1 -7
- package/dist/engine/doc-generator/portal/analytics-page.js +1 -7
- package/dist/engine/doc-generator/portal/architecture-page.js +1 -7
- package/dist/engine/doc-generator/portal/changelog-page.js +1 -7
- package/dist/engine/doc-generator/portal/decision-page.js +1 -7
- package/dist/engine/doc-generator/portal/decision-timeline-builder.js +1 -7
- package/dist/engine/doc-generator/portal/kanban-view-builder.js +1 -7
- package/dist/engine/doc-generator/portal/portal-breadcrumbs.js +1 -7
- package/dist/engine/doc-generator/portal/portal-landing-cards.js +1 -7
- package/dist/engine/doc-generator/portal/portal-navbar.js +1 -7
- package/dist/engine/doc-generator/portal/portal-page-detector.d.ts +3 -0
- package/dist/engine/doc-generator/portal/portal-page-detector.js +1 -10
- package/dist/engine/doc-generator/portal/portal-regenerator.js +2 -0
- package/dist/engine/doc-generator/portal/risk-matrix-svg.js +1 -7
- package/dist/engine/doc-generator/portal/risk-page.js +1 -7
- package/dist/engine/doc-generator/portal/roadmap-page.js +1 -7
- package/dist/engine/doc-generator/proposal/proposal-gantt-builder.js +1 -7
- package/dist/engine/doc-generator/proposal/proposal-kpi-builder.js +1 -7
- package/dist/engine/doc-generator/proposal/proposal-section-builders-advanced.js +2 -8
- package/dist/engine/doc-generator/proposal/proposal-section-builders.js +1 -7
- package/dist/engine/docs-site-generator/html-templates.js +1 -7
- package/dist/engine/docs-site-generator/markdown-renderer.js +1 -7
- package/dist/engine/drift/violation-resolver.js +2 -0
- package/dist/engine/dynamic-knowledge/knowledge-cache.js +1 -0
- package/dist/engine/ecosystem-absorber/knowledge-cache.js +1 -0
- package/dist/engine/edge-db-detector.js +1 -10
- package/dist/engine/escalator/retry-counter.js +1 -0
- package/dist/engine/evidence-gates/artifact-reader.js +1 -0
- package/dist/engine/evidence-gates/evidence-autofill.js +1 -0
- package/dist/engine/evidence-gates/evidence-skeletons.js +1 -0
- package/dist/engine/evidence-gates/reconciliation-freshness.js +1 -0
- package/dist/engine/execution/durable-job-heartbeat-runtime.js +1 -0
- package/dist/engine/execution/job-runtime.js +2 -0
- package/dist/engine/execution/validate-job-executor.d.ts +1 -1
- package/dist/engine/execution/validate-job-executor.js +3 -0
- package/dist/engine/execution/validate-job-payload.js +1 -0
- package/dist/engine/execution/validate-worker-thread.js +1 -0
- package/dist/engine/facilitate/context-loader.js +1 -0
- package/dist/engine/figma/change-detector.d.ts +1 -8
- package/dist/engine/figma/code-connect-fetcher.d.ts +1 -7
- package/dist/engine/figma/design-enricher.d.ts +1 -17
- package/dist/engine/figma/design-enricher.js +0 -1
- package/dist/engine/figma/e2e-generator.d.ts +1 -23
- package/dist/engine/figma/flow-analyzer.d.ts +1 -11
- package/dist/engine/figma/frame-extractor.d.ts +1 -11
- package/dist/engine/figma/frame-extractor.js +3 -0
- package/dist/engine/figma/multi-file-manager.d.ts +1 -6
- package/dist/engine/figma/spec-generator.js +2 -0
- package/dist/engine/figma/thumbnail-fetcher.d.ts +1 -1
- package/dist/engine/figma/token-extractor.d.ts +1 -6
- package/dist/engine/figma/token-extractor.js +0 -1
- package/dist/engine/figma/version-tracker.d.ts +1 -7
- package/dist/engine/figma/version-tracker.js +0 -1
- package/dist/engine/figma/visual-qa.d.ts +5 -19
- package/dist/engine/figma/visual-qa.js +1 -2
- package/dist/engine/figma/webhook-manager.d.ts +1 -7
- package/dist/engine/frontmatter-sha/reseal.js +1 -0
- package/dist/engine/frontmatter-sha/verify-terminal.js +1 -0
- package/dist/engine/git-hook-injector.js +2 -10
- package/dist/engine/github/quality-dimensions.js +1 -3
- package/dist/engine/global-rules-manager.js +2 -3
- package/dist/engine/handoff-artifacts/implementation-review-reader.js +1 -0
- package/dist/engine/handoff-artifacts/io.js +2 -0
- package/dist/engine/handoff-packager.js +2 -1
- package/dist/engine/health/auto-fixer.js +1 -0
- package/dist/engine/hooks/context-injector.js +1 -0
- package/dist/engine/hooks-reconciler.js +1 -8
- package/dist/engine/impact-detector/index.js +2 -9
- package/dist/engine/implementation-contract/common.js +1 -3
- package/dist/engine/implementation-contract/evaluator.js +1 -3
- package/dist/engine/keyword-matcher/boundary.d.ts +2 -7
- package/dist/engine/keyword-matcher/boundary.js +2 -14
- package/dist/engine/lifecycle-reconciliation-io.js +1 -0
- package/dist/engine/lifecycle-reconciliation.js +3 -0
- package/dist/engine/local-first/detector.js +1 -9
- package/dist/engine/marketplace-fetcher/cache.js +2 -0
- package/dist/engine/marketplace-fetcher/index.js +1 -0
- package/dist/engine/mcp-config/mcp-config-writer.js +2 -10
- package/dist/engine/migrations/remove-commercial-state.js +2 -0
- package/dist/engine/mobile-detector-shared.d.ts +1 -1
- package/dist/engine/mobile-detector-shared.js +2 -9
- package/dist/engine/multi-channel-notifier.js +1 -0
- package/dist/engine/multi-teammate-review/disagreement-counter.js +1 -0
- package/dist/engine/multi-teammate-review/panel-orchestrator.js +1 -0
- package/dist/engine/onboarding-engine.js +1 -9
- package/dist/engine/permissions-merger/stack-detectors.js +2 -10
- package/dist/engine/pii-detector/core.js +1 -3
- package/dist/engine/platform-engineering/dx-analyzer.js +1 -10
- package/dist/engine/platform-engineering/idp-detector.js +2 -10
- package/dist/engine/project-dna/ai-tool-detector.js +2 -10
- package/dist/engine/project-dna/stack-detector.js +2 -10
- package/dist/engine/project-graph/builder.js +11 -2
- package/dist/engine/project-graph/cache.d.ts +5 -2
- package/dist/engine/project-graph/cache.js +174 -34
- package/dist/engine/project-graph/extractors/decision-store-extractor.js +1 -0
- package/dist/engine/project-graph/freshness-projection.js +3 -0
- package/dist/engine/project-graph/query.js +14 -11
- package/dist/engine/project-health-checker.js +2 -6
- package/dist/engine/provider-adapters/shared/spec-loader.js +1 -0
- package/dist/engine/quality-gates/gate-evaluator.js +1 -3
- package/dist/engine/react-detector.js +6 -14
- package/dist/engine/reconcile/apply-changes.js +1 -3
- package/dist/engine/registry/core.js +1 -0
- package/dist/engine/registry/loader.js +2 -0
- package/dist/engine/release/postmortem-generator.js +1 -0
- package/dist/engine/release/rollback-runner.d.ts +2 -2
- package/dist/engine/release/stuck-spec-detector.js +7 -0
- package/dist/engine/release-pipeline/release-spec-closer.js +3 -0
- package/dist/engine/rules-reconciler.d.ts +1 -1
- package/dist/engine/rules-reconciler.js +2 -9
- package/dist/engine/safety/atomic-writer.js +1 -0
- package/dist/engine/safety/cross-process-lock.js +2 -0
- package/dist/engine/safety/file-mutex.js +1 -0
- package/dist/engine/safety/lock-lease-store.js +1 -0
- package/dist/engine/safety/lock-scavenger.js +2 -0
- package/dist/engine/saga/transition-cascade.js +3 -0
- package/dist/engine/scan-project/index.js +1 -0
- package/dist/engine/scan-project/module-discoverer.js +1 -9
- package/dist/engine/schema-parity-auditor/handler-checker.js +1 -3
- package/dist/engine/schema-parity-auditor/index.js +1 -3
- package/dist/engine/schema-parity-auditor/type-checker.js +1 -3
- package/dist/engine/self-healing/strategies/coverage-fix.js +2 -10
- package/dist/engine/session-context-generator.d.ts +8 -0
- package/dist/engine/session-context-generator.js +33 -23
- package/dist/engine/session-state/session-artifact-refresher.d.ts +7 -0
- package/dist/engine/session-state/session-artifact-refresher.js +140 -0
- package/dist/engine/session-state/writer.d.ts +3 -0
- package/dist/engine/session-state/writer.js +35 -17
- package/dist/engine/shared-agent-memory.js +1 -0
- package/dist/engine/skill-adapter/index.js +3 -0
- package/dist/engine/skill-bootstrap/registry-fetcher.js +1 -0
- package/dist/engine/skill-bootstrap/skill-writer.js +2 -9
- package/dist/engine/skill-bootstrap/stack-matcher.js +1 -9
- package/dist/engine/skill-discovery/multi-agent-writer.d.ts +1 -0
- package/dist/engine/skill-discovery/multi-agent-writer.js +1 -9
- package/dist/engine/skill-generator/workflow-skill-generator.js +1 -0
- package/dist/engine/skill-registry/ttl-refresh.js +2 -0
- package/dist/engine/spec-audit/index.js +2 -0
- package/dist/engine/spec-audit/report-writer.js +1 -0
- package/dist/engine/spec-coverage/core.js +1 -0
- package/dist/engine/spec-decomposer/analyzer.js +1 -11
- package/dist/engine/spec-dependency-graph/dot-exporter.d.ts +1 -1
- package/dist/engine/spec-dependency-graph/graph-builder.d.ts +1 -20
- package/dist/engine/spec-dependency-graph/superseded-set.d.ts +1 -1
- package/dist/engine/spec-diff-engine.js +1 -7
- package/dist/engine/spec-format/read-technical-section.js +1 -3
- package/dist/engine/spec-format/replace-section.js +1 -3
- package/dist/engine/spec-format/spec-id.d.ts +9 -1
- package/dist/engine/spec-format/spec-id.js +24 -3
- package/dist/engine/spec-format/unified-spec-builder.js +1 -3
- package/dist/engine/spec-migrator/canonical-deduplicator.js +2 -0
- package/dist/engine/spec-observability/tracker.js +1 -0
- package/dist/engine/spec-registry/adapter.js +2 -10
- package/dist/engine/spec-registry/validator.d.ts +1 -0
- package/dist/engine/spec-registry/validator.js +1 -9
- package/dist/engine/spec-state-machine/transition-spec.js +1 -0
- package/dist/engine/spec-templates/custom-loader.js +1 -0
- package/dist/engine/stack-auditor/build-tool-auditor.js +1 -9
- package/dist/engine/staleness/stale-implementing.js +5 -14
- package/dist/engine/status-reconciler/index.js +4 -1
- package/dist/engine/structural-memory/helpers.js +1 -0
- package/dist/engine/supply-chain/slsa-checker.js +1 -9
- package/dist/engine/tdd-enforcer/stub-artifact-path.js +1 -0
- package/dist/engine/telemetry/health-checker.js +1 -0
- package/dist/engine/telemetry/telemetry-store.js +1 -0
- package/dist/engine/token-optimizer/response-cache.js +1 -3
- package/dist/engine/tool-groups/group-manager.js +2 -0
- package/dist/engine/update-notifier.js +1 -0
- package/dist/engine/validation/approved-spec-portability.js +1 -0
- package/dist/engine/validation/durable-validation.js +1 -0
- package/dist/engine/validation/validation-freshness.js +2 -0
- package/dist/engine/validation/validation-receipt.js +1 -0
- package/dist/engine/validation/validation-submission.d.ts +1 -1
- package/dist/engine/validation-evidence-ledger.js +1 -0
- package/dist/engine/validator/deep-code-checker.js +1 -3
- package/dist/engine/validator/executable-evidence.d.ts +1 -14
- package/dist/engine/validator/scope-resolver.d.ts +2 -2
- package/dist/engine/validator/spec-compliance-runner.d.ts +1 -31
- package/dist/engine/validator/validation-report-writer.js +1 -1
- package/dist/engine/validator.d.ts +1 -1
- package/dist/engine/validator.js +1 -0
- package/dist/engine/velocity/measure-velocity.js +2 -0
- package/dist/engine/version-resolver.js +1 -0
- package/dist/engine/workers/handlers/auto-drift.js +3 -0
- package/dist/engine/workspace-manager.js +4 -0
- package/dist/hosts/gemini/config-scaffold.js +2 -10
- package/dist/storage/approval-operation-lock.js +1 -0
- package/dist/storage/auto-promoter-config-store.js +1 -0
- package/dist/storage/autopilot-log-store.js +1 -0
- package/dist/storage/base-store.js +1 -0
- package/dist/storage/checkpoint-store.js +1 -0
- package/dist/storage/feedback-remote.js +1 -0
- package/dist/storage/gaps-log.js +1 -0
- package/dist/storage/global-projects-store.d.ts +17 -0
- package/dist/storage/global-projects-store.js +47 -0
- package/dist/storage/global-store.js +1 -0
- package/dist/storage/knowledge-store/sessions.js +2 -0
- package/dist/storage/migrations/canonical-storage.js +42 -18
- package/dist/storage/project-drift-store.js +1 -0
- package/dist/storage/project-identity.d.ts +27 -0
- package/dist/storage/project-identity.js +132 -2
- package/dist/storage/project-resolver.js +1 -17
- package/dist/storage/runtime-db.d.ts +2 -20
- package/dist/storage/runtime-db.js +2 -9
- package/dist/storage/semantic-index-store.js +1 -0
- package/dist/storage/sentry-store.js +1 -0
- package/dist/storage/session-state-store.js +1 -0
- package/dist/storage/spec-store.d.ts +31 -3
- package/dist/storage/spec-store.js +146 -5
- package/dist/storage/status-store/atomic-status-write.js +1 -0
- package/dist/storage/status-store/file-lock.js +1 -0
- package/dist/storage/status-store/self-healing.js +1 -0
- package/dist/storage/storage-bundle.d.ts +12 -2
- package/dist/storage/supabase-store.js +1 -0
- package/dist/storage/technology-selection-store.js +1 -0
- package/dist/storage/trash-store.d.ts +2 -4
- package/dist/storage/trash-store.js +2 -15
- package/dist/storage/vector-store/json-fallback.js +1 -0
- package/dist/storage/vector-store/migrator.js +1 -0
- package/dist/storage/vector-store/sqlite-adapter.js +1 -0
- package/dist/tools/audit-claude-config.js +1 -10
- package/dist/tools/challenge-spec-helpers.d.ts +1 -1
- package/dist/tools/challenge-spec-helpers.js +7 -2
- package/dist/tools/challenge-spec.js +11 -6
- package/dist/tools/check-readiness.js +121 -24
- package/dist/tools/clarify-requirements/multiple-choice.d.ts +3 -3
- package/dist/tools/configure-oauth-handler.js +1 -0
- package/dist/tools/create-spec-helpers.d.ts +1 -3
- package/dist/tools/create-spec-helpers.js +1 -8
- package/dist/tools/create-spec.js +1 -13
- package/dist/tools/dashboard.js +5 -0
- package/dist/tools/generate-execution-plan.js +10 -6
- package/dist/tools/github-pr-handler.js +1 -0
- package/dist/tools/github-release-handler.d.ts +1 -0
- package/dist/tools/github-release-handler.js +4 -3
- package/dist/tools/graph-specs.d.ts +1 -1
- package/dist/tools/heal-spec-docs.js +1 -3
- package/dist/tools/init-project/git-setup.d.ts +2 -2
- package/dist/tools/init-project/host-assets-writer.js +2 -10
- package/dist/tools/init-project/lifecycle-helpers.d.ts +2 -2
- package/dist/tools/init-project/lint-scaffolder.js +2 -10
- package/dist/tools/init-project/stack-detector.d.ts +2 -2
- package/dist/tools/reconcile-interactive-question-hooks.d.ts +2 -2
- package/dist/tools/register-sdd-tools.js +1 -0
- package/dist/tools/safe-handler.js +1 -0
- package/dist/tools/schemas/spec-id.d.ts +1 -6
- package/dist/tools/schemas/spec-id.js +1 -16
- package/dist/tools/tool-entry.d.ts +2 -2
- package/dist/tools/tool-entry.js +1 -1
- package/dist/tools/tool-registry/core-tools.d.ts +2 -2
- package/dist/tools/tool-registry/deprecated-stubs.d.ts +3 -3
- package/dist/tools/tool-registry/deprecated-stubs.js +1 -1
- package/dist/tools/triage-request.d.ts +2 -2
- package/dist/tools/update-status/approval-gates.d.ts +2 -2
- package/dist/tools/update-status/approval-gates.js +1 -0
- package/dist/tools/update-status/dod-gates.d.ts +1 -1
- package/dist/tools/update-status/done-receipt-verifier.d.ts +1 -1
- package/dist/tools/update-status/evidence-gate.js +5 -5
- package/dist/tools/update-status/file-sync.js +11 -39
- package/dist/tools/update-status/index.js +1 -1
- package/dist/tools/update-status/side-effects.js +5 -0
- package/dist/tools/update-status/transition-guard.d.ts +1 -1
- package/dist/tools/update-status/transition-guard.js +40 -8
- package/dist/tools/update-status-actions.js +28 -40
- package/dist/tools/validate-graph-coverage.js +14 -2
- package/dist/tools/validate.js +2 -0
- package/dist/transports/middleware/types.d.ts +3 -3
- package/dist/types/atomic-upgrade.d.ts +1 -1
- package/dist/types/cascade-hooks.d.ts +3 -0
- package/dist/types/cross-repo-search.d.ts +2 -0
- package/dist/types/dashboard.d.ts +4 -1
- package/dist/types/durable-job.d.ts +20 -0
- package/dist/types/durable-job.js +9 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.js +2 -0
- package/dist/types/installation.d.ts +16 -0
- package/dist/types/installation.js +2 -0
- package/dist/types/project-knowledge-graph.d.ts +11 -0
- package/dist/types/session-checkpoint.d.ts +16 -0
- package/dist/types/spec-dependency-graph.d.ts +21 -0
- package/dist/types/spec-dependency-graph.js +2 -0
- package/dist/types/validation-evidence.d.ts +45 -0
- package/package.json +2 -2
- package/planu-plugin.json +1 -1
- package/dist/types/storage-bundle.d.ts +0 -13
- package/dist/types/storage-bundle.js +0 -2
|
@@ -6,6 +6,7 @@ import { readFile, writeFile } from 'node:fs/promises';
|
|
|
6
6
|
import { tmpdir } from 'node:os';
|
|
7
7
|
import { join } from 'node:path';
|
|
8
8
|
import { fetchLatestVersion } from './package-registry.js';
|
|
9
|
+
// eslint-disable-next-line no-restricted-imports -- grandfathered layer violation, remediation SPEC-1661 SPEC-1662 SPEC-1663
|
|
9
10
|
import { getCachedVersions, saveCachedVersions, isExpired as isCachedVersionExpired, } from '../storage/version-cache-store.js';
|
|
10
11
|
// -- Constants ---------------------------------------------------------------
|
|
11
12
|
const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
// engine/workers/handlers/auto-drift.ts — Auto drift detection on file changes (SPEC-225)
|
|
2
2
|
import { relative, extname } from 'node:path';
|
|
3
3
|
import { readdir, stat } from 'node:fs/promises';
|
|
4
|
+
// eslint-disable-next-line no-restricted-imports -- grandfathered layer violation, remediation SPEC-1661 SPEC-1662 SPEC-1663
|
|
4
5
|
import { specStore } from '../../../storage/index.js';
|
|
6
|
+
// eslint-disable-next-line no-restricted-imports -- grandfathered layer violation, remediation SPEC-1661 SPEC-1662 SPEC-1663
|
|
5
7
|
import { hashProjectPath } from '../../../storage/base-store.js';
|
|
8
|
+
// eslint-disable-next-line no-restricted-imports -- grandfathered layer violation, remediation SPEC-1661 SPEC-1662 SPEC-1663
|
|
6
9
|
import { DriftCacheStore } from '../../../storage/drift-cache-store.js';
|
|
7
10
|
import { emitAutopilotEvent } from '../../autopilot/event-bus.js';
|
|
8
11
|
// ---------------------------------------------------------------------------
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
// engine/workspace-manager.ts — SPEC-363: Multi-tenant Workspace Dashboard
|
|
2
|
+
// eslint-disable-next-line no-restricted-imports -- grandfathered layer violation, remediation SPEC-1661 SPEC-1662 SPEC-1663
|
|
2
3
|
import { getProjects, addProject } from '../storage/global-projects-store.js';
|
|
4
|
+
// eslint-disable-next-line no-restricted-imports -- grandfathered layer violation, remediation SPEC-1661 SPEC-1662 SPEC-1663
|
|
3
5
|
import { specStore } from '../storage/index.js';
|
|
6
|
+
// eslint-disable-next-line no-restricted-imports -- grandfathered layer violation, remediation SPEC-1661 SPEC-1662 SPEC-1663
|
|
4
7
|
import { hashProjectPath, readJson } from '../storage/base-store.js';
|
|
5
8
|
import { glob } from 'glob';
|
|
9
|
+
// eslint-disable-next-line no-restricted-imports -- grandfathered layer violation, remediation SPEC-1661 SPEC-1662 SPEC-1663
|
|
6
10
|
import { resolveStorageLayout } from '../storage/storage-layout.js';
|
|
7
11
|
// ---------------------------------------------------------------------------
|
|
8
12
|
// SPEC-789: Project discovery fallback
|
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
// Writes .gemini/conventions.md, .gemini/skills/planu-*.md, and .gemini/hooks/
|
|
3
3
|
// equivalents when init_project runs inside a Gemini workspace.
|
|
4
4
|
// NOTE: This function deliberately does NOT create .claude/ files.
|
|
5
|
-
import { writeFile, readFile, mkdir
|
|
5
|
+
import { writeFile, readFile, mkdir } from 'node:fs/promises';
|
|
6
6
|
import { join } from 'node:path';
|
|
7
7
|
import { buildRulesForHost } from '../../engine/host-rules-templates/index.js';
|
|
8
8
|
import { assertEnglishOnlyArtifactText } from '../../engine/spec-language/english-only.js';
|
|
9
9
|
import { PLANU_VERSION } from '../../config/version.js';
|
|
10
|
+
import { fileExists } from '../../core/shared/fs.js';
|
|
10
11
|
// ---------------------------------------------------------------------------
|
|
11
12
|
// File content generators
|
|
12
13
|
// ---------------------------------------------------------------------------
|
|
@@ -188,13 +189,4 @@ async function injectRulesBlock(filePath, version) {
|
|
|
188
189
|
// ---------------------------------------------------------------------------
|
|
189
190
|
// Internal helpers
|
|
190
191
|
// ---------------------------------------------------------------------------
|
|
191
|
-
async function fileExists(filePath) {
|
|
192
|
-
try {
|
|
193
|
-
await access(filePath);
|
|
194
|
-
return true;
|
|
195
|
-
}
|
|
196
|
-
catch {
|
|
197
|
-
return false;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
192
|
//# sourceMappingURL=config-scaffold.js.map
|
|
@@ -2,6 +2,7 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
3
3
|
import { isAbsolute, resolve } from 'node:path';
|
|
4
4
|
import { withFileLock } from './file-mutex.js';
|
|
5
|
+
// eslint-disable-next-line no-restricted-imports -- grandfathered layer violation, remediation SPEC-1661 SPEC-1662 SPEC-1663
|
|
5
6
|
import { LockBusyError, withSpecLock } from '../engine/safety/cross-process-lock.js';
|
|
6
7
|
const heldApprovalLocks = new AsyncLocalStorage();
|
|
7
8
|
function approvalLockId(specId) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// storage/auto-promoter-config-store.ts — Persists auto-promoter config (SPEC-391)
|
|
2
2
|
// Layout: data/projects/{hash}/config/auto-promoter.json
|
|
3
3
|
import { readJson, writeJson, projectDataDir, hashProjectPath } from './base-store.js';
|
|
4
|
+
// eslint-disable-next-line no-restricted-imports -- grandfathered layer violation, remediation SPEC-1661 SPEC-1662 SPEC-1663
|
|
4
5
|
import { getDefaultConfig } from '../engine/auto-promoter.js';
|
|
5
6
|
function configFile(projectId) {
|
|
6
7
|
return `${projectDataDir(projectId)}/config/auto-promoter.json`;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// storage/autopilot-log-store.ts — SPEC-772: Autopilot cascade log persistence
|
|
2
2
|
import { readFile, mkdir } from 'node:fs/promises';
|
|
3
3
|
import { dirname, join } from 'node:path';
|
|
4
|
+
// eslint-disable-next-line no-restricted-imports -- grandfathered layer violation, remediation SPEC-1661 SPEC-1662 SPEC-1663
|
|
4
5
|
import { atomicWriteFile } from '../engine/safety/atomic-write-file.js';
|
|
5
6
|
import { reportClassifiedDegradation } from '../errors/classified-degradation.js';
|
|
6
7
|
import { projectDataDir } from './base-store.js';
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { readFile, mkdir } from 'node:fs/promises';
|
|
4
4
|
import { readFileSync } from 'node:fs';
|
|
5
5
|
import { dirname, isAbsolute, join, resolve } from 'node:path';
|
|
6
|
+
// eslint-disable-next-line no-restricted-imports -- grandfathered layer violation, remediation SPEC-1661 SPEC-1662 SPEC-1663
|
|
6
7
|
import { writeJsonSafe } from '../engine/safety/atomic-writer.js';
|
|
7
8
|
import { resolveStorageLayout } from './storage-layout.js';
|
|
8
9
|
import { reportClassifiedDegradation } from '../errors/classified-degradation.js';
|
|
@@ -3,6 +3,7 @@ import { readdir, mkdir } from 'node:fs/promises';
|
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { readJson, writeJson, projectDataDir } from './base-store.js';
|
|
5
5
|
import { withFileLock } from './file-mutex.js';
|
|
6
|
+
// eslint-disable-next-line no-restricted-imports -- grandfathered layer violation, remediation SPEC-1661 SPEC-1662 SPEC-1663
|
|
6
7
|
import { writeJsonSafe } from '../engine/safety/atomic-writer.js';
|
|
7
8
|
// ---------------------------------------------------------------------------
|
|
8
9
|
// Helpers
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// storage/feedback-remote.ts — Configured remote feedback relay (fire-and-forget)
|
|
2
2
|
// No PII is ever sent: no project paths, no user names, no emails.
|
|
3
|
+
// eslint-disable-next-line no-restricted-imports -- grandfathered layer violation, remediation SPEC-1661 SPEC-1662 SPEC-1663
|
|
3
4
|
import { networkFetch, withNetworkConsent } from '../engine/network-policy.js';
|
|
4
5
|
function resolveConfiguredFeedbackEndpoint(endpoint) {
|
|
5
6
|
const configured = endpoint ?? process.env.PLANU_FEEDBACK_ENDPOINT;
|
package/dist/storage/gaps-log.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { createHash, randomUUID } from 'node:crypto';
|
|
8
8
|
import { appendFile, readFile, mkdir } from 'node:fs/promises';
|
|
9
9
|
import { dirname, join } from 'node:path';
|
|
10
|
+
// eslint-disable-next-line no-restricted-imports -- grandfathered layer violation, remediation SPEC-1661 SPEC-1662 SPEC-1663
|
|
10
11
|
import { fastAppendGapEntry, fastVerifyGapsChain } from '../engine/core-bridge.js';
|
|
11
12
|
import { projectDataDir } from './base-store.js';
|
|
12
13
|
import { reportClassifiedDegradation } from '../errors/classified-degradation.js';
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { RegisteredProject, GlobalProjectsRegistry } from '../types/index.js';
|
|
2
|
+
/** Best-effort, non-creating read of a checkout's logical project identity. */
|
|
3
|
+
export declare function readLogicalProjectId(projectPath: string): Promise<string | undefined>;
|
|
2
4
|
/**
|
|
3
5
|
* Read the full registry from disk.
|
|
4
6
|
* Returns an empty registry when the file does not exist.
|
|
@@ -15,6 +17,21 @@ export declare function saveRegistry(registry: GlobalProjectsRegistry): Promise<
|
|
|
15
17
|
* Returns the registered project entry.
|
|
16
18
|
*/
|
|
17
19
|
export declare function addProject(projectPath: string): Promise<RegisteredProject>;
|
|
20
|
+
/** Stable, classified failure for a logical project id with no unambiguous canonical root. */
|
|
21
|
+
export type CanonicalRootResult = {
|
|
22
|
+
readonly ok: true;
|
|
23
|
+
readonly root: string;
|
|
24
|
+
} | {
|
|
25
|
+
readonly ok: false;
|
|
26
|
+
readonly reason: 'zero' | 'ambiguous';
|
|
27
|
+
readonly roots: readonly string[];
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Resolve the single canonical root registered for a logical project identity.
|
|
31
|
+
* Fails closed — never guesses a first match — when zero or more than one
|
|
32
|
+
* distinct registered path claims the same `logicalProjectId`.
|
|
33
|
+
*/
|
|
34
|
+
export declare function getCanonicalRoot(logicalProjectId: string): Promise<CanonicalRootResult>;
|
|
18
35
|
/**
|
|
19
36
|
* Remove a project from the registry by path.
|
|
20
37
|
* Returns true if the project was found and removed, false otherwise.
|
|
@@ -2,8 +2,26 @@
|
|
|
2
2
|
// Persistence file: data/global/registered-projects.json
|
|
3
3
|
import { readJson, writeJson, globalDataDir, hashProjectPath } from './base-store.js';
|
|
4
4
|
import { withFileLock } from './file-mutex.js';
|
|
5
|
+
// eslint-disable-next-line no-restricted-imports -- grandfathered layer violation, remediation SPEC-1661 SPEC-1662 SPEC-1663
|
|
5
6
|
import { isEphemeralProject } from '../engine/data-projects-gc/pattern-matcher.js';
|
|
6
7
|
import { reportClassifiedDegradation } from '../errors/classified-degradation.js';
|
|
8
|
+
import { readFile } from 'node:fs/promises';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
11
|
+
/** Best-effort, non-creating read of a checkout's logical project identity. */
|
|
12
|
+
export async function readLogicalProjectId(projectPath) {
|
|
13
|
+
try {
|
|
14
|
+
const raw = JSON.parse(await readFile(join(projectPath, 'planu', 'project.json'), 'utf8'));
|
|
15
|
+
const id = raw?.logicalProjectId;
|
|
16
|
+
return typeof id === 'string' && UUID_PATTERN.test(id) ? id : undefined;
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
if (error.code !== 'ENOENT') {
|
|
20
|
+
reportClassifiedDegradation('LOGICAL_PROJECT_ID_READ_FAILED', error);
|
|
21
|
+
}
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
7
25
|
// ---------------------------------------------------------------------------
|
|
8
26
|
// File path
|
|
9
27
|
// ---------------------------------------------------------------------------
|
|
@@ -46,11 +64,15 @@ export async function addProject(projectPath) {
|
|
|
46
64
|
specCount: 0,
|
|
47
65
|
};
|
|
48
66
|
}
|
|
67
|
+
const logicalProjectId = await readLogicalProjectId(projectPath);
|
|
49
68
|
return withFileLock(registryFile(), async () => {
|
|
50
69
|
const registry = await getRegistry();
|
|
51
70
|
const existing = registry.projects.find((p) => p.path === projectPath);
|
|
52
71
|
if (existing !== undefined) {
|
|
53
72
|
existing.hash = hash;
|
|
73
|
+
if (logicalProjectId !== undefined) {
|
|
74
|
+
existing.logicalProjectId = logicalProjectId;
|
|
75
|
+
}
|
|
54
76
|
registry.updatedAt = new Date().toISOString();
|
|
55
77
|
await saveRegistry(registry);
|
|
56
78
|
return existing;
|
|
@@ -60,6 +82,7 @@ export async function addProject(projectPath) {
|
|
|
60
82
|
hash,
|
|
61
83
|
registeredAt: new Date().toISOString(),
|
|
62
84
|
specCount: 0,
|
|
85
|
+
...(logicalProjectId !== undefined && { logicalProjectId }),
|
|
63
86
|
};
|
|
64
87
|
registry.projects.push(entry);
|
|
65
88
|
registry.updatedAt = new Date().toISOString();
|
|
@@ -67,6 +90,30 @@ export async function addProject(projectPath) {
|
|
|
67
90
|
return entry;
|
|
68
91
|
});
|
|
69
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* Resolve the single canonical root registered for a logical project identity.
|
|
95
|
+
* Fails closed — never guesses a first match — when zero or more than one
|
|
96
|
+
* distinct registered path claims the same `logicalProjectId`.
|
|
97
|
+
*/
|
|
98
|
+
export async function getCanonicalRoot(logicalProjectId) {
|
|
99
|
+
const projects = await getProjects();
|
|
100
|
+
const roots = [
|
|
101
|
+
...new Set(projects
|
|
102
|
+
.filter((project) => project.logicalProjectId === logicalProjectId)
|
|
103
|
+
.map((project) => project.path)),
|
|
104
|
+
];
|
|
105
|
+
if (roots.length === 0) {
|
|
106
|
+
return { ok: false, reason: 'zero', roots };
|
|
107
|
+
}
|
|
108
|
+
if (roots.length > 1) {
|
|
109
|
+
return { ok: false, reason: 'ambiguous', roots };
|
|
110
|
+
}
|
|
111
|
+
const [root] = roots;
|
|
112
|
+
if (root === undefined) {
|
|
113
|
+
return { ok: false, reason: 'zero', roots };
|
|
114
|
+
}
|
|
115
|
+
return { ok: true, root };
|
|
116
|
+
}
|
|
70
117
|
/**
|
|
71
118
|
* Remove a project from the registry by path.
|
|
72
119
|
* Returns true if the project was found and removed, false otherwise.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readJson, writeJson, globalDataDir } from './base-store.js';
|
|
2
2
|
import { withFileLock } from './file-mutex.js';
|
|
3
|
+
// eslint-disable-next-line no-restricted-imports -- grandfathered layer violation, remediation SPEC-1661 SPEC-1662 SPEC-1663
|
|
3
4
|
import { applyLearnedLimit, markVerified } from '../engine/registry-extender.js';
|
|
4
5
|
import { readFileSync } from 'node:fs';
|
|
5
6
|
import { reportClassifiedDegradation } from '../errors/classified-degradation.js';
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
// eslint-disable-next-line no-restricted-imports -- grandfathered layer violation, remediation SPEC-1661 SPEC-1662 SPEC-1663
|
|
1
2
|
import { mergeContext } from '../../engine/context-merger.js';
|
|
2
3
|
import { readJson, writeJson, projectDataDir } from '../base-store.js';
|
|
3
4
|
import { withFileLock } from '../file-mutex.js';
|
|
5
|
+
// eslint-disable-next-line no-restricted-imports -- grandfathered layer violation, remediation SPEC-1661 SPEC-1662 SPEC-1663
|
|
4
6
|
import { addMetricsToState } from '../../engine/calibrator.js';
|
|
5
7
|
import { getKnowledge, knowledgeFile } from './knowledge.js';
|
|
6
8
|
// --- File paths ---
|
|
@@ -3,7 +3,9 @@ import { copyFile, lstat, mkdir, readFile, readdir, rename, rm, writeFile } from
|
|
|
3
3
|
import { createHash, randomUUID } from 'node:crypto';
|
|
4
4
|
import { homedir, tmpdir } from 'node:os';
|
|
5
5
|
import { basename, dirname, isAbsolute, join, parse, relative, resolve, sep } from 'node:path';
|
|
6
|
+
// eslint-disable-next-line no-restricted-imports -- grandfathered layer violation, remediation SPEC-1661 SPEC-1662 SPEC-1663
|
|
6
7
|
import { acquireLock as acquireFileMutex, releaseLock as releaseFileMutex, } from '../../engine/safety/file-mutex.js';
|
|
8
|
+
// eslint-disable-next-line no-restricted-imports -- grandfathered layer violation, remediation SPEC-1661 SPEC-1662 SPEC-1663
|
|
7
9
|
import { assertNoObsoleteFileLock } from '../../engine/safety/obsolete-lock-guard.js';
|
|
8
10
|
function sha256(value) {
|
|
9
11
|
return createHash('sha256').update(value).digest('hex');
|
|
@@ -174,10 +176,17 @@ async function readJournal(path, expected) {
|
|
|
174
176
|
throw error;
|
|
175
177
|
}
|
|
176
178
|
}
|
|
179
|
+
function isTerminalPhaseValue(value) {
|
|
180
|
+
return value === 'completed';
|
|
181
|
+
}
|
|
182
|
+
async function quarantineStaleJournal(journalPath) {
|
|
183
|
+
const quarantinePath = journalPath.replace(/\.journal\.json$/, `.journal.stale-${String(Date.now())}.json`);
|
|
184
|
+
await rename(journalPath, quarantinePath);
|
|
185
|
+
}
|
|
177
186
|
async function retireCompletedJournalForNewSources(journalPath, expected) {
|
|
178
|
-
let
|
|
187
|
+
let rawText;
|
|
179
188
|
try {
|
|
180
|
-
|
|
189
|
+
rawText = await readFile(journalPath, 'utf8');
|
|
181
190
|
}
|
|
182
191
|
catch (error) {
|
|
183
192
|
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
|
|
@@ -185,32 +194,47 @@ async function retireCompletedJournalForNewSources(journalPath, expected) {
|
|
|
185
194
|
}
|
|
186
195
|
throw error;
|
|
187
196
|
}
|
|
197
|
+
const parsed = JSON.parse(rawText);
|
|
188
198
|
if (!parsed || typeof parsed !== 'object' || !('legacyRoots' in parsed)) {
|
|
189
199
|
throw new Error('[Planu] Invalid or unsafe migration journal');
|
|
190
200
|
}
|
|
191
|
-
const
|
|
201
|
+
const record = parsed;
|
|
202
|
+
const rawRoots = record.legacyRoots;
|
|
192
203
|
if (!Array.isArray(rawRoots) || !rawRoots.every((root) => typeof root === 'string')) {
|
|
193
204
|
throw new Error('[Planu] Invalid or unsafe migration journal');
|
|
194
205
|
}
|
|
195
|
-
|
|
196
|
-
|
|
206
|
+
const rootsChanged = JSON.stringify(rawRoots) !== JSON.stringify(expected.legacyRoots);
|
|
207
|
+
const previousRoots = rootsChanged
|
|
208
|
+
? rawRoots.map((root) => assertAbsolute(root, 'journal legacyRoot'))
|
|
209
|
+
: [...expected.legacyRoots];
|
|
210
|
+
if (rootsChanged) {
|
|
211
|
+
for (const root of previousRoots) {
|
|
212
|
+
assertNarrowLegacyRoot(root);
|
|
213
|
+
if (isInside(root, expected.destinationRoot) || isInside(expected.destinationRoot, root)) {
|
|
214
|
+
throw new Error('[Planu] Invalid or unsafe migration journal');
|
|
215
|
+
}
|
|
216
|
+
}
|
|
197
217
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
218
|
+
try {
|
|
219
|
+
const previous = validateJournal(parsed, { ...expected, legacyRoots: previousRoots });
|
|
220
|
+
if (!rootsChanged) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (previous.phase !== 'completed') {
|
|
224
|
+
throw new Error('[Planu] Active migration journal does not match requested roots');
|
|
203
225
|
}
|
|
226
|
+
// A completed destination remains live and may legitimately receive newer
|
|
227
|
+
// canonical records. Re-inventory its real directory instead of requiring
|
|
228
|
+
// the historical generation digest to remain frozen forever.
|
|
229
|
+
await inventoryStorageRoot(expected.destinationRoot);
|
|
230
|
+
await rm(journalPath, { force: true });
|
|
204
231
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
232
|
+
catch (error) {
|
|
233
|
+
if (!isTerminalPhaseValue(record.phase)) {
|
|
234
|
+
throw error;
|
|
235
|
+
}
|
|
236
|
+
await quarantineStaleJournal(journalPath);
|
|
208
237
|
}
|
|
209
|
-
// A completed destination remains live and may legitimately receive newer
|
|
210
|
-
// canonical records. Re-inventory its real directory instead of requiring
|
|
211
|
-
// the historical generation digest to remain frozen forever.
|
|
212
|
-
await inventoryStorageRoot(expected.destinationRoot);
|
|
213
|
-
await rm(journalPath, { force: true });
|
|
214
238
|
}
|
|
215
239
|
function recordTimestamp(value) {
|
|
216
240
|
if (!value || typeof value !== 'object') {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// storage/project-drift-store.ts — SPEC-666: Read/write drift signals from .planu/drift-commits.ndjson
|
|
2
2
|
import { readFile, writeFile, unlink, mkdir } from 'node:fs/promises';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
+
// eslint-disable-next-line no-restricted-imports -- grandfathered layer violation, remediation SPEC-1661 SPEC-1662 SPEC-1663
|
|
4
5
|
import { analyzeCommitRecord } from '../engine/project-drift-detector.js';
|
|
5
6
|
import { hashProjectPath, projectDataDir } from './base-store.js';
|
|
6
7
|
import { reportClassifiedDegradation } from '../errors/classified-degradation.js';
|
|
@@ -27,6 +27,33 @@ export declare function hashWorkspaceKey(workspaceKey: string): string;
|
|
|
27
27
|
export declare function deriveWorkspaceKey(projectPath: string): Promise<string>;
|
|
28
28
|
/** Resolve or create stable project and machine-workspace UUIDs. */
|
|
29
29
|
export declare function ensureProjectIdentity(projectPath: string, options: ProjectIdentityOptions): Promise<ResolvedProjectIdentity>;
|
|
30
|
+
export type PortablePathErrorCode = 'INVALID_TYPE' | 'EMPTY' | 'NUL_BYTE' | 'NOT_PORTABLE_SHAPE' | 'WRONG_SPEC' | 'ESCAPES_ROOT' | 'ROOT_NOT_FOUND' | 'ROOT_UNVERIFIED' | 'NOT_FOUND' | 'SYMLINK' | 'NOT_FILE';
|
|
31
|
+
/** Stable, classified failure for a spec path that cannot be resolved as a portable identity. */
|
|
32
|
+
export declare class PortablePathError extends Error {
|
|
33
|
+
readonly code: PortablePathErrorCode;
|
|
34
|
+
constructor(code: PortablePathErrorCode, message: string);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Extract the portable `planu/specs/<specId[-slug]>/<filename>` suffix from a stored
|
|
38
|
+
* path value, rejecting anything that is not that exact shape for the given spec.
|
|
39
|
+
* Works for both legacy absolute values and already-portable relative ones — the
|
|
40
|
+
* incoming prefix is discarded, so this doubles as the legacy-path normalizer.
|
|
41
|
+
*/
|
|
42
|
+
export declare function toPortableSpecPath(specId: string, storedPath: unknown): string;
|
|
43
|
+
/**
|
|
44
|
+
* Resolve a stored `specPath`/`technicalPath` value against the verified canonical
|
|
45
|
+
* root, returning the realpath of the contained file. Fails closed — with a stable
|
|
46
|
+
* classified {@link PortablePathError} — for any absent, malformed, escaping,
|
|
47
|
+
* symlinked, or cross-spec value, before any content is read or written.
|
|
48
|
+
*/
|
|
49
|
+
export declare function resolvePortableSpecPath(specId: string, storedPath: unknown, canonicalRoot: string): Promise<string>;
|
|
50
|
+
/**
|
|
51
|
+
* Lifecycle-consumer entry point: verify the candidate root against the
|
|
52
|
+
* global project registry before resolving through it, so an unregistered,
|
|
53
|
+
* ambiguous, or stale checkout never stands in for the real canonical root.
|
|
54
|
+
* Delegates to {@link resolvePortableSpecPath} once verified.
|
|
55
|
+
*/
|
|
56
|
+
export declare function resolveVerifiedSpecPath(specId: string, storedPath: unknown, canonicalRoot: string): Promise<string>;
|
|
30
57
|
/** Explicitly turn a copied project into an independent logical project. */
|
|
31
58
|
export declare function forkProjectIdentity(projectPath: string, options: ProjectIdentityOptions): Promise<ResolvedProjectIdentity>;
|
|
32
59
|
//# sourceMappingURL=project-identity.d.ts.map
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
-
import { mkdir, open, readFile, realpath, rename, rm, stat } from 'node:fs/promises';
|
|
3
|
-
import { basename, dirname, join, normalize, resolve, sep } from 'node:path';
|
|
2
|
+
import { lstat, mkdir, open, readFile, realpath, rename, rm, stat } from 'node:fs/promises';
|
|
3
|
+
import { basename, dirname, isAbsolute, join, normalize, relative, resolve, sep } from 'node:path';
|
|
4
4
|
import { resolveStorageLayout } from './storage-layout.js';
|
|
5
|
+
import { getCanonicalRoot, readLogicalProjectId } from './global-projects-store.js';
|
|
6
|
+
// eslint-disable-next-line no-restricted-imports -- grandfathered layer violation, remediation SPEC-1661 SPEC-1662 SPEC-1663
|
|
7
|
+
import { isEphemeralProject } from '../engine/data-projects-gc/pattern-matcher.js';
|
|
5
8
|
const IDENTITY_VERSION = 1;
|
|
6
9
|
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
7
10
|
function isProjectIdentity(value) {
|
|
@@ -156,6 +159,133 @@ export async function ensureProjectIdentity(projectPath, options) {
|
|
|
156
159
|
const workspace = await ensureWorkspaceDocument(options.layout ?? resolveStorageLayout(), options.workspaceKey, project.logicalProjectId);
|
|
157
160
|
return { project, workspace, canonicalProjectPath };
|
|
158
161
|
}
|
|
162
|
+
/** Stable, classified failure for a spec path that cannot be resolved as a portable identity. */
|
|
163
|
+
export class PortablePathError extends Error {
|
|
164
|
+
code;
|
|
165
|
+
constructor(code, message) {
|
|
166
|
+
super(message);
|
|
167
|
+
this.name = 'PortablePathError';
|
|
168
|
+
this.code = code;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Extract the portable `planu/specs/<specId[-slug]>/<filename>` suffix from a stored
|
|
173
|
+
* path value, rejecting anything that is not that exact shape for the given spec.
|
|
174
|
+
* Works for both legacy absolute values and already-portable relative ones — the
|
|
175
|
+
* incoming prefix is discarded, so this doubles as the legacy-path normalizer.
|
|
176
|
+
*/
|
|
177
|
+
export function toPortableSpecPath(specId, storedPath) {
|
|
178
|
+
if (typeof storedPath !== 'string') {
|
|
179
|
+
throw new PortablePathError('INVALID_TYPE', '[Planu] specPath must be a string');
|
|
180
|
+
}
|
|
181
|
+
if (storedPath.includes('\0')) {
|
|
182
|
+
throw new PortablePathError('NUL_BYTE', '[Planu] specPath must not contain a NUL byte');
|
|
183
|
+
}
|
|
184
|
+
if (storedPath.trim().length === 0) {
|
|
185
|
+
throw new PortablePathError('EMPTY', '[Planu] specPath must not be empty or whitespace-only');
|
|
186
|
+
}
|
|
187
|
+
const segments = normalize(storedPath).split(sep).filter(Boolean);
|
|
188
|
+
const planuIndex = segments.lastIndexOf('planu');
|
|
189
|
+
const dir = segments[planuIndex + 2];
|
|
190
|
+
const filename = segments[planuIndex + 3];
|
|
191
|
+
const isPortableShape = planuIndex >= 0 &&
|
|
192
|
+
segments[planuIndex + 1] === 'specs' &&
|
|
193
|
+
dir !== undefined &&
|
|
194
|
+
filename !== undefined &&
|
|
195
|
+
planuIndex + 4 === segments.length;
|
|
196
|
+
if (!isPortableShape) {
|
|
197
|
+
throw new PortablePathError('NOT_PORTABLE_SHAPE', '[Planu] specPath is not a portable planu/specs/<dir>/<file> contract');
|
|
198
|
+
}
|
|
199
|
+
if (dir !== specId && !dir.startsWith(`${specId}-`)) {
|
|
200
|
+
throw new PortablePathError('WRONG_SPEC', `[Planu] specPath directory "${dir}" does not belong to ${specId}`);
|
|
201
|
+
}
|
|
202
|
+
return join('planu', 'specs', dir, filename);
|
|
203
|
+
}
|
|
204
|
+
function rejectsRootEscape(root, candidate) {
|
|
205
|
+
const rel = relative(root, candidate);
|
|
206
|
+
return rel.startsWith(`..${sep}`) || rel === '..' || isAbsolute(rel);
|
|
207
|
+
}
|
|
208
|
+
async function realpathClassified(path, notFoundCode) {
|
|
209
|
+
try {
|
|
210
|
+
return await realpath(path);
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
|
|
214
|
+
throw new PortablePathError(notFoundCode, `[Planu] path not found: ${notFoundCode}`);
|
|
215
|
+
}
|
|
216
|
+
throw error;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Reject a candidate root that carries a tracked logical project identity but
|
|
221
|
+
* is not the single registry-confirmed canonical root for it — a checkout
|
|
222
|
+
* left over from a rename/fork, or an ambiguous/unregistered project never
|
|
223
|
+
* resolves specs against an arbitrary directory. A candidate with no tracked
|
|
224
|
+
* identity (`planu/project.json` absent) is untracked, not unverified, and
|
|
225
|
+
* passes through unchanged — as does an ephemeral checkout (test fixture,
|
|
226
|
+
* scratch clone), since the global registry deliberately excludes those
|
|
227
|
+
* (SPEC-581) and can never confirm a root for them.
|
|
228
|
+
*/
|
|
229
|
+
async function rejectsUnverifiedRoot(root) {
|
|
230
|
+
if (isEphemeralProject(root)) {
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
const logicalProjectId = await readLogicalProjectId(root);
|
|
234
|
+
if (logicalProjectId === undefined) {
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
const canonical = await getCanonicalRoot(logicalProjectId);
|
|
238
|
+
return !canonical.ok || resolve(canonical.root) !== root;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Resolve a stored `specPath`/`technicalPath` value against the verified canonical
|
|
242
|
+
* root, returning the realpath of the contained file. Fails closed — with a stable
|
|
243
|
+
* classified {@link PortablePathError} — for any absent, malformed, escaping,
|
|
244
|
+
* symlinked, or cross-spec value, before any content is read or written.
|
|
245
|
+
*/
|
|
246
|
+
export async function resolvePortableSpecPath(specId, storedPath, canonicalRoot) {
|
|
247
|
+
const suffix = toPortableSpecPath(specId, storedPath);
|
|
248
|
+
const root = resolve(canonicalRoot);
|
|
249
|
+
const candidate = resolve(root, suffix);
|
|
250
|
+
if (rejectsRootEscape(root, candidate)) {
|
|
251
|
+
throw new PortablePathError('ESCAPES_ROOT', '[Planu] specPath escapes the canonical root');
|
|
252
|
+
}
|
|
253
|
+
const rootReal = await realpathClassified(root, 'ROOT_NOT_FOUND');
|
|
254
|
+
let candidateReal;
|
|
255
|
+
let candidateStat;
|
|
256
|
+
try {
|
|
257
|
+
candidateReal = await realpathClassified(candidate, 'NOT_FOUND');
|
|
258
|
+
candidateStat = await lstat(candidate);
|
|
259
|
+
}
|
|
260
|
+
catch (error) {
|
|
261
|
+
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
|
|
262
|
+
throw new PortablePathError('NOT_FOUND', '[Planu] spec file not found under canonical root');
|
|
263
|
+
}
|
|
264
|
+
throw error;
|
|
265
|
+
}
|
|
266
|
+
if (candidateStat.isSymbolicLink()) {
|
|
267
|
+
throw new PortablePathError('SYMLINK', '[Planu] specPath must not resolve through a symlink');
|
|
268
|
+
}
|
|
269
|
+
if (!candidateStat.isFile()) {
|
|
270
|
+
throw new PortablePathError('NOT_FILE', '[Planu] specPath must resolve to a regular file');
|
|
271
|
+
}
|
|
272
|
+
if (rejectsRootEscape(rootReal, candidateReal)) {
|
|
273
|
+
throw new PortablePathError('ESCAPES_ROOT', '[Planu] specPath escapes the canonical root');
|
|
274
|
+
}
|
|
275
|
+
return candidateReal;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Lifecycle-consumer entry point: verify the candidate root against the
|
|
279
|
+
* global project registry before resolving through it, so an unregistered,
|
|
280
|
+
* ambiguous, or stale checkout never stands in for the real canonical root.
|
|
281
|
+
* Delegates to {@link resolvePortableSpecPath} once verified.
|
|
282
|
+
*/
|
|
283
|
+
export async function resolveVerifiedSpecPath(specId, storedPath, canonicalRoot) {
|
|
284
|
+
if (await rejectsUnverifiedRoot(resolve(canonicalRoot))) {
|
|
285
|
+
throw new PortablePathError('ROOT_UNVERIFIED', '[Planu] canonical root is not the single registry-confirmed root for this project');
|
|
286
|
+
}
|
|
287
|
+
return resolvePortableSpecPath(specId, storedPath, canonicalRoot);
|
|
288
|
+
}
|
|
159
289
|
/** Explicitly turn a copied project into an independent logical project. */
|
|
160
290
|
export async function forkProjectIdentity(projectPath, options) {
|
|
161
291
|
const canonicalProjectPath = await canonicalizeProjectPath(projectPath);
|
|
@@ -2,27 +2,11 @@
|
|
|
2
2
|
// Resolves a project from its data directory OR from disk fingerprints (planu/ + specs/).
|
|
3
3
|
// Used by planu_status, challenge_spec, and snapshot_spec_hashes to avoid "Project not found"
|
|
4
4
|
// when the knowledge entry is missing but the project clearly exists on disk.
|
|
5
|
-
import { access } from 'node:fs/promises';
|
|
6
5
|
import { join } from 'node:path';
|
|
7
6
|
import { getKnowledge, saveKnowledge } from './knowledge-store/knowledge.js';
|
|
8
7
|
import { hashProjectPath, projectDataDir } from './base-store.js';
|
|
9
8
|
import { reportClassifiedDegradation } from '../errors/classified-degradation.js';
|
|
10
|
-
|
|
11
|
-
* Check whether a file/directory path exists on disk (no-throw helper).
|
|
12
|
-
*/
|
|
13
|
-
async function pathExists(p) {
|
|
14
|
-
try {
|
|
15
|
-
await access(p);
|
|
16
|
-
return true;
|
|
17
|
-
}
|
|
18
|
-
catch (error) {
|
|
19
|
-
if (error.code === 'ENOENT' ||
|
|
20
|
-
(error instanceof Error && error.message === 'ENOENT')) {
|
|
21
|
-
return false;
|
|
22
|
-
}
|
|
23
|
-
throw error;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
9
|
+
import { pathExistsStrict as pathExists } from '../core/shared/fs.js';
|
|
26
10
|
/**
|
|
27
11
|
* Resolve a project from its projectPath.
|
|
28
12
|
*
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { DURABLE_JOB_STATES, type DurableJobRecord, type DurableJobState } from '../types/durable-job.js';
|
|
1
2
|
import { type ValidationReceiptCheckpointLimits } from '../types/durable-validation.js';
|
|
3
|
+
export { DURABLE_JOB_STATES, type DurableJobRecord, type DurableJobState };
|
|
2
4
|
/** Count-only process diagnostic for deterministic resource lifecycle tests. */
|
|
3
5
|
export declare function getRuntimeDatabaseDiagnosticsForTests(): {
|
|
4
6
|
readonly openConnections: number;
|
|
@@ -50,26 +52,6 @@ export interface RuntimeProjectBundle {
|
|
|
50
52
|
readonly outbox: readonly Record<string, unknown>[];
|
|
51
53
|
readonly audit: readonly Record<string, unknown>[];
|
|
52
54
|
}
|
|
53
|
-
export declare const DURABLE_JOB_STATES: readonly ['accepted', 'running', 'checkpointed', 'cancelled', 'failed', 'dead-letter', 'completed'];
|
|
54
|
-
export type DurableJobState = (typeof DURABLE_JOB_STATES)[number];
|
|
55
|
-
export interface DurableJobRecord {
|
|
56
|
-
readonly id: string;
|
|
57
|
-
readonly projectId: string;
|
|
58
|
-
readonly workspaceId: string;
|
|
59
|
-
readonly state: DurableJobState;
|
|
60
|
-
readonly payload: unknown;
|
|
61
|
-
readonly checkpoint?: unknown;
|
|
62
|
-
readonly result?: unknown;
|
|
63
|
-
readonly error?: string;
|
|
64
|
-
readonly attempts: number;
|
|
65
|
-
readonly maxAttempts: number;
|
|
66
|
-
readonly cancelRequested: boolean;
|
|
67
|
-
readonly leaseOwner?: string;
|
|
68
|
-
readonly leaseExpiresAt?: string;
|
|
69
|
-
readonly fencingToken: number;
|
|
70
|
-
readonly createdAt: string;
|
|
71
|
-
readonly updatedAt: string;
|
|
72
|
-
}
|
|
73
55
|
export interface JobLeaseInput {
|
|
74
56
|
readonly id: string;
|
|
75
57
|
readonly projectId: string;
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
import { mkdirSync } from 'node:fs';
|
|
3
3
|
import { dirname, isAbsolute, resolve } from 'node:path';
|
|
4
4
|
import { DatabaseSync } from 'node:sqlite';
|
|
5
|
+
import { DURABLE_JOB_STATES, } from '../types/durable-job.js';
|
|
5
6
|
import { parseValidationReceiptPendingCheckpoint, } from '../types/durable-validation.js';
|
|
7
|
+
export { DURABLE_JOB_STATES };
|
|
6
8
|
const CURRENT_SCHEMA_VERSION = 4;
|
|
7
9
|
const DEFAULT_BUSY_TIMEOUT_MS = 5_000;
|
|
8
10
|
const DEFAULT_MAX_CONNECTIONS = 4;
|
|
@@ -47,15 +49,6 @@ export function getRuntimeDatabaseDiagnosticsForTests() {
|
|
|
47
49
|
}
|
|
48
50
|
return { openConnections: count, integrityCheckedOpens, integrityBypassedOpens };
|
|
49
51
|
}
|
|
50
|
-
export const DURABLE_JOB_STATES = [
|
|
51
|
-
'accepted',
|
|
52
|
-
'running',
|
|
53
|
-
'checkpointed',
|
|
54
|
-
'cancelled',
|
|
55
|
-
'failed',
|
|
56
|
-
'dead-letter',
|
|
57
|
-
'completed',
|
|
58
|
-
];
|
|
59
52
|
export class RuntimeDatabaseCorruptionError extends Error {
|
|
60
53
|
databasePath;
|
|
61
54
|
detail;
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { createHash } from 'node:crypto';
|
|
7
7
|
import { readJson, writeJson, projectDataDir } from './base-store.js';
|
|
8
8
|
import { withFileLock } from './file-mutex.js';
|
|
9
|
+
// eslint-disable-next-line no-restricted-imports -- grandfathered layer violation, remediation SPEC-1661 SPEC-1662 SPEC-1663
|
|
9
10
|
import { TFIDFEngine, TFIDF_SCHEMA_VERSION, tokenize } from '../engine/vector-store/tfidf.js';
|
|
10
11
|
function hashContent(content) {
|
|
11
12
|
return createHash('sha256').update(content).digest('hex');
|