@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
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/* eslint-disable max-lines -- done evidence gates remain co-located for one auditable fail-closed boundary */
|
|
2
|
+
// tools/update-status/dod-gates.ts — DoD gate checks and forceStatus bypass
|
|
3
|
+
// SPEC-729: Validate gate wrapped with withEscalation for bounded retries.
|
|
4
|
+
// SPEC-780: checkApprovedFormatGate honours forceApprove, mirroring SPEC-769 readiness gate.
|
|
5
|
+
import { createHash } from 'node:crypto';
|
|
6
|
+
import { constants } from 'node:fs';
|
|
7
|
+
import { lstat, open, realpath } from 'node:fs/promises';
|
|
8
|
+
import { isAbsolute, relative, resolve as resolvePath, sep } from 'node:path';
|
|
1
9
|
import { validateSpec } from '../../engine/validator.js';
|
|
2
10
|
import { generateDoD } from '../../engine/validator/dor-dod.js';
|
|
3
11
|
import { addLesson, getLessons } from '../../storage/lessons-store.js';
|
|
@@ -9,6 +17,8 @@ import { writeSpecReviewFeedback } from '../../engine/validator/spec-review-writ
|
|
|
9
17
|
import { formatKeyValue } from '../output-formatter.js';
|
|
10
18
|
import { reportClassifiedDegradation } from '../../errors/classified-degradation.js';
|
|
11
19
|
import { verifyCurrentDoneReceipt } from './done-receipt-verifier.js';
|
|
20
|
+
import { projectDataDir } from '../../storage/base-store.js';
|
|
21
|
+
import { ValidationReportV1Schema } from '../../engine/handoff-artifacts/schemas.js';
|
|
12
22
|
export { completedJobPublishesReceipt } from './done-receipt-verifier.js';
|
|
13
23
|
/**
|
|
14
24
|
* SPEC-721 / SPEC-222 Trigger 1: Run validate engine before marking done.
|
|
@@ -275,6 +285,176 @@ function validationReceiptGateError(specId, reason) {
|
|
|
275
285
|
},
|
|
276
286
|
};
|
|
277
287
|
}
|
|
288
|
+
function doneReviewDigestError(specId, code, message) {
|
|
289
|
+
const artifactPath = `external Planu project data: handoffs/${specId}/validation-report.json`;
|
|
290
|
+
const fixHint = 'Run validate, then recompute the sha256 digest from the exact report bytes.';
|
|
291
|
+
return {
|
|
292
|
+
content: [{ type: 'text', text: `${code}: ${message}\nFix: ${fixHint}` }],
|
|
293
|
+
isError: true,
|
|
294
|
+
structuredContent: {
|
|
295
|
+
error: code,
|
|
296
|
+
code: 422,
|
|
297
|
+
context: { specId, artifactPath },
|
|
298
|
+
fixHint,
|
|
299
|
+
},
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
const MAX_VALIDATION_REPORT_BYTES = 5 * 1024 * 1024;
|
|
303
|
+
function isContained(root, candidate) {
|
|
304
|
+
const pathFromRoot = relative(root, candidate);
|
|
305
|
+
return (pathFromRoot === '' ||
|
|
306
|
+
(!isAbsolute(pathFromRoot) && pathFromRoot !== '..' && !pathFromRoot.startsWith(`..${sep}`)));
|
|
307
|
+
}
|
|
308
|
+
function sameFileMetadata(left, right) {
|
|
309
|
+
return (left.dev === right.dev &&
|
|
310
|
+
left.ino === right.ino &&
|
|
311
|
+
left.size === right.size &&
|
|
312
|
+
left.mtimeNs === right.mtimeNs &&
|
|
313
|
+
left.ctimeNs === right.ctimeNs);
|
|
314
|
+
}
|
|
315
|
+
async function readStableDoneReviewReport(specId, projectId) {
|
|
316
|
+
const handoffsRoot = resolvePath(projectDataDir(projectId), 'handoffs');
|
|
317
|
+
const specRoot = resolvePath(handoffsRoot, specId);
|
|
318
|
+
const reportPath = resolvePath(specRoot, 'validation-report.json');
|
|
319
|
+
if (!isContained(handoffsRoot, specRoot) || !isContained(specRoot, reportPath)) {
|
|
320
|
+
return doneReviewDigestError(specId, 'DONE_REVIEW_REPORT_UNSAFE', 'The validation report path escapes its contained handoff directory.');
|
|
321
|
+
}
|
|
322
|
+
let canonicalHandoffs;
|
|
323
|
+
let canonicalSpecRoot;
|
|
324
|
+
let canonicalReport;
|
|
325
|
+
let pathStats;
|
|
326
|
+
try {
|
|
327
|
+
[canonicalHandoffs, canonicalSpecRoot, canonicalReport, pathStats] = await Promise.all([
|
|
328
|
+
realpath(handoffsRoot),
|
|
329
|
+
realpath(specRoot),
|
|
330
|
+
realpath(reportPath),
|
|
331
|
+
lstat(reportPath, { bigint: true }),
|
|
332
|
+
]);
|
|
333
|
+
}
|
|
334
|
+
catch {
|
|
335
|
+
return {
|
|
336
|
+
...doneReviewDigestError(specId, 'DONE_REVIEW_DIGEST_MISMATCH', 'No persisted validation report was found for this spec. Run validate first, then retry done.'),
|
|
337
|
+
isError: true,
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
if (!isContained(canonicalHandoffs, canonicalSpecRoot) ||
|
|
341
|
+
!isContained(canonicalSpecRoot, canonicalReport) ||
|
|
342
|
+
pathStats.isSymbolicLink() ||
|
|
343
|
+
!pathStats.isFile() ||
|
|
344
|
+
pathStats.size > BigInt(MAX_VALIDATION_REPORT_BYTES)) {
|
|
345
|
+
return doneReviewDigestError(specId, 'DONE_REVIEW_REPORT_UNSAFE', 'The validation report is not a contained bounded regular file.');
|
|
346
|
+
}
|
|
347
|
+
if (typeof constants.O_NOFOLLOW !== 'number') {
|
|
348
|
+
return doneReviewDigestError(specId, 'DONE_REVIEW_REPORT_UNSAFE', 'The platform cannot safely open the validation report without following links.');
|
|
349
|
+
}
|
|
350
|
+
let handle;
|
|
351
|
+
try {
|
|
352
|
+
handle = await open(reportPath, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
353
|
+
}
|
|
354
|
+
catch {
|
|
355
|
+
return {
|
|
356
|
+
...doneReviewDigestError(specId, 'DONE_REVIEW_REPORT_UNSAFE', 'The validation report could not be opened without following links.'),
|
|
357
|
+
isError: true,
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
try {
|
|
361
|
+
const openedStats = await handle.stat({ bigint: true });
|
|
362
|
+
if (!openedStats.isFile() || !sameFileMetadata(pathStats, openedStats)) {
|
|
363
|
+
return doneReviewDigestError(specId, 'DONE_REVIEW_REPORT_CHANGED', 'The validation report identity changed before it was opened.');
|
|
364
|
+
}
|
|
365
|
+
const reportBytes = Buffer.alloc(Number(openedStats.size));
|
|
366
|
+
let offset = 0;
|
|
367
|
+
while (offset < reportBytes.length) {
|
|
368
|
+
const { bytesRead } = await handle.read(reportBytes, offset, reportBytes.length - offset, offset);
|
|
369
|
+
if (bytesRead === 0) {
|
|
370
|
+
return doneReviewDigestError(specId, 'DONE_REVIEW_REPORT_CHANGED', 'The validation report changed while it was being read.');
|
|
371
|
+
}
|
|
372
|
+
offset += bytesRead;
|
|
373
|
+
}
|
|
374
|
+
const finalHandleStats = await handle.stat({ bigint: true });
|
|
375
|
+
if (!sameFileMetadata(openedStats, finalHandleStats)) {
|
|
376
|
+
return doneReviewDigestError(specId, 'DONE_REVIEW_REPORT_CHANGED', 'The opened validation report changed while it was being read.');
|
|
377
|
+
}
|
|
378
|
+
let finalPathStats;
|
|
379
|
+
let finalCanonicalReport;
|
|
380
|
+
try {
|
|
381
|
+
[finalPathStats, finalCanonicalReport] = await Promise.all([
|
|
382
|
+
lstat(reportPath, { bigint: true }),
|
|
383
|
+
realpath(reportPath),
|
|
384
|
+
]);
|
|
385
|
+
}
|
|
386
|
+
catch {
|
|
387
|
+
return {
|
|
388
|
+
...doneReviewDigestError(specId, 'DONE_REVIEW_REPORT_CHANGED', 'The validation report path changed while it was being read.'),
|
|
389
|
+
isError: true,
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
if (!isContained(canonicalSpecRoot, finalCanonicalReport)) {
|
|
393
|
+
return doneReviewDigestError(specId, 'DONE_REVIEW_REPORT_UNSAFE', 'The validation report path escaped containment while it was being read.');
|
|
394
|
+
}
|
|
395
|
+
if (finalPathStats.isSymbolicLink() ||
|
|
396
|
+
!finalPathStats.isFile() ||
|
|
397
|
+
!sameFileMetadata(openedStats, finalPathStats)) {
|
|
398
|
+
return doneReviewDigestError(specId, 'DONE_REVIEW_REPORT_CHANGED', 'The validation report path identity changed while it was being read.');
|
|
399
|
+
}
|
|
400
|
+
return reportBytes;
|
|
401
|
+
}
|
|
402
|
+
finally {
|
|
403
|
+
await handle.close();
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* SPEC-1356 (T-A1): auto-derive the sha256 digest of the persisted
|
|
408
|
+
* `validation-report.json` so callers no longer have to compute it by hand.
|
|
409
|
+
* Reads and hashes the exact same bytes `checkDoneReviewDigest` would validate
|
|
410
|
+
* against, using the same hardened path/identity checks. Returns the digest, or
|
|
411
|
+
* the exact blocking `ToolResult` when the report cannot be safely read (e.g.
|
|
412
|
+
* validate has not run yet). Exported for direct unit testing and for the
|
|
413
|
+
* orchestration layer (`update-status/index.ts`) that no longer requires the
|
|
414
|
+
* caller to supply `implementationReviewDigest`.
|
|
415
|
+
*/
|
|
416
|
+
export async function deriveDoneReviewDigest(specId, projectId) {
|
|
417
|
+
const reportResult = await readStableDoneReviewReport(specId, projectId);
|
|
418
|
+
if (!Buffer.isBuffer(reportResult)) {
|
|
419
|
+
return { error: reportResult };
|
|
420
|
+
}
|
|
421
|
+
return { digest: `sha256:${createHash('sha256').update(reportResult).digest('hex')}` };
|
|
422
|
+
}
|
|
423
|
+
async function checkDoneReviewDigest(specId, projectId, implementationReviewDigest) {
|
|
424
|
+
if (!/^sha256:[a-f0-9]{64}$/u.test(implementationReviewDigest)) {
|
|
425
|
+
return doneReviewDigestError(specId, 'DONE_REVIEW_DIGEST_INVALID', 'The implementation review digest is malformed.');
|
|
426
|
+
}
|
|
427
|
+
const reportResult = await readStableDoneReviewReport(specId, projectId);
|
|
428
|
+
if (!Buffer.isBuffer(reportResult)) {
|
|
429
|
+
return reportResult;
|
|
430
|
+
}
|
|
431
|
+
const observed = `sha256:${createHash('sha256').update(reportResult).digest('hex')}`;
|
|
432
|
+
if (observed !== implementationReviewDigest) {
|
|
433
|
+
return doneReviewDigestError(specId, 'DONE_REVIEW_DIGEST_MISMATCH', 'The implementation review digest does not match the persisted validation report.');
|
|
434
|
+
}
|
|
435
|
+
let decoded;
|
|
436
|
+
try {
|
|
437
|
+
decoded = JSON.parse(reportResult.toString('utf8'));
|
|
438
|
+
}
|
|
439
|
+
catch {
|
|
440
|
+
return {
|
|
441
|
+
...doneReviewDigestError(specId, 'DONE_REVIEW_REPORT_UNSAFE', 'The validation report bytes are not valid JSON.'),
|
|
442
|
+
isError: true,
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
const parsed = ValidationReportV1Schema.safeParse(decoded);
|
|
446
|
+
if (!parsed.success ||
|
|
447
|
+
parsed.data.specId !== specId ||
|
|
448
|
+
!parsed.data.passed ||
|
|
449
|
+
parsed.data.gates.some((gate) => !gate.passed) ||
|
|
450
|
+
parsed.data.minimalityReport?.blocked === true ||
|
|
451
|
+
parsed.data.reviewer.kind !== 'implementation-review-agent' ||
|
|
452
|
+
parsed.data.reviewer.agent !== 'planu-implementation-reviewer' ||
|
|
453
|
+
parsed.data.reviewer.verdict !== 'approved') {
|
|
454
|
+
return doneReviewDigestError(specId, 'DONE_REVIEW_REPORT_UNSAFE', 'The validation report schema or independent reviewer evidence is invalid.');
|
|
455
|
+
}
|
|
456
|
+
return null;
|
|
457
|
+
}
|
|
278
458
|
/**
|
|
279
459
|
* SPEC-725: Check the validation-report artifact gate before marking done.
|
|
280
460
|
* SPEC-1050: Fail closed. A done transition requires a fresh validation-report
|
|
@@ -493,8 +673,18 @@ function specReviewGateError(args) {
|
|
|
493
673
|
* When force=true, gates are not blocking but failing items are recorded in
|
|
494
674
|
* the project's force-bypass-log.json for traceability (guard: done ≠ implemented).
|
|
495
675
|
*/
|
|
496
|
-
export async function checkDoneGates(spec, specId, projectId, projectPath, _force, _forceReason = 'No force reason provided', verifyReceipt = verifyCurrentDoneReceipt) {
|
|
676
|
+
export async function checkDoneGates(spec, specId, projectId, projectPath, _force, _forceReason = 'No force reason provided', verifyReceipt = verifyCurrentDoneReceipt, implementationReviewDigest) {
|
|
497
677
|
try {
|
|
678
|
+
if (implementationReviewDigest !== undefined) {
|
|
679
|
+
const digestError = await checkDoneReviewDigest(specId, projectId, implementationReviewDigest);
|
|
680
|
+
if (digestError) {
|
|
681
|
+
return {
|
|
682
|
+
blocked: digestError,
|
|
683
|
+
forcedBypassWarning: null,
|
|
684
|
+
freshnessLease: null,
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
}
|
|
498
688
|
const result = await verifyReceipt({ projectId, specId, projectPath, specPath: spec.specPath });
|
|
499
689
|
return result.valid
|
|
500
690
|
? {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type DurableJobRecord } from '../../storage/runtime-db.js';
|
|
2
2
|
import { type ValidationFreshnessLease } from '../../engine/validation/validation-freshness.js';
|
|
3
|
+
import type { ValidationReceiptBindings } from '../../types/validation-receipt.js';
|
|
3
4
|
export type DoneReceiptVerifier = (identity: {
|
|
4
5
|
projectId: string;
|
|
5
6
|
specId: string;
|
|
@@ -11,5 +12,12 @@ export type DoneReceiptVerifier = (identity: {
|
|
|
11
12
|
freshnessLease?: ValidationFreshnessLease;
|
|
12
13
|
}>;
|
|
13
14
|
export declare function completedJobPublishesReceipt(job: DurableJobRecord | undefined, receiptId: string): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Evidence artifacts (discovery/task-plan/traceability/contract-validation) may legitimately
|
|
17
|
+
* be regenerated after a receipt was issued (e.g. autopilot skeleton fill). If ONLY the evidence
|
|
18
|
+
* digests changed while every code-facing binding stayed byte-identical, the receipt is rebound
|
|
19
|
+
* to the freshly observed evidence digests instead of being rejected as stale.
|
|
20
|
+
*/
|
|
21
|
+
export declare function doneReceiptBindingsDifferOnlyInEvidence(observed: ValidationReceiptBindings | undefined, stored: ValidationReceiptBindings | undefined): boolean;
|
|
14
22
|
export declare function verifyCurrentDoneReceipt(identity: Parameters<DoneReceiptVerifier>[0]): ReturnType<DoneReceiptVerifier>;
|
|
15
23
|
//# sourceMappingURL=done-receipt-verifier.d.ts.map
|
|
@@ -54,8 +54,9 @@ import { isAbsolute, join } from 'node:path';
|
|
|
54
54
|
import { RuntimeDatabase } from '../../storage/runtime-db.js';
|
|
55
55
|
import { resolveStorageLayout } from '../../storage/storage-layout.js';
|
|
56
56
|
import { projectDataDir } from '../../storage/base-store.js';
|
|
57
|
+
import { reportClassifiedDegradation } from '../../errors/classified-degradation.js';
|
|
57
58
|
import { ValidationReceiptStore, LOCAL_VALIDATION_RECEIPT_ISSUER, createValidationReceiptAuthority, readCurrentValidationReceipt, } from '../../engine/validation/validation-receipt.js';
|
|
58
|
-
import { beginValidationFreshnessBarrier, bindValidationFreshnessLease, captureValidationFreshnessLease, } from '../../engine/validation/validation-freshness.js';
|
|
59
|
+
import { beginValidationFreshnessBarrier, bindValidationFreshnessLease, captureValidationFreshnessLease, closeValidationFreshnessLease, } from '../../engine/validation/validation-freshness.js';
|
|
59
60
|
import { computeDurableValidationBindings, toValidationReceiptBindings, } from '../../engine/validation/durable-validation.js';
|
|
60
61
|
export function completedJobPublishesReceipt(job, receiptId) {
|
|
61
62
|
if (job?.state !== 'completed' || !job.result || typeof job.result !== 'object') {
|
|
@@ -69,6 +70,31 @@ export function completedJobPublishesReceipt(job, receiptId) {
|
|
|
69
70
|
return (published.receiptId === receiptId &&
|
|
70
71
|
published.receiptKey === `validation-receipt:${receiptId.slice('sha256:'.length)}`);
|
|
71
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* Evidence artifacts (discovery/task-plan/traceability/contract-validation) may legitimately
|
|
75
|
+
* be regenerated after a receipt was issued (e.g. autopilot skeleton fill). If ONLY the evidence
|
|
76
|
+
* digests changed while every code-facing binding stayed byte-identical, the receipt is rebound
|
|
77
|
+
* to the freshly observed evidence digests instead of being rejected as stale.
|
|
78
|
+
*/
|
|
79
|
+
export function doneReceiptBindingsDifferOnlyInEvidence(observed, stored) {
|
|
80
|
+
if (!observed?.artifactDigests || !stored?.artifactDigests) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
const codeBindingsMatch = observed.projectId === stored.projectId &&
|
|
84
|
+
observed.specId === stored.specId &&
|
|
85
|
+
observed.sourceDigest === stored.sourceDigest &&
|
|
86
|
+
observed.specDigest === stored.specDigest &&
|
|
87
|
+
observed.policyDigest === stored.policyDigest &&
|
|
88
|
+
observed.toolchainDigest === stored.toolchainDigest &&
|
|
89
|
+
observed.artifactDigests.approvedSpec === stored.artifactDigests.approvedSpec;
|
|
90
|
+
if (!codeBindingsMatch) {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
return (observed.artifactDigests.discovery !== stored.artifactDigests.discovery ||
|
|
94
|
+
observed.artifactDigests.taskPlan !== stored.artifactDigests.taskPlan ||
|
|
95
|
+
observed.artifactDigests.traceability !== stored.artifactDigests.traceability ||
|
|
96
|
+
observed.artifactDigests.contractValidation !== stored.artifactDigests.contractValidation);
|
|
97
|
+
}
|
|
72
98
|
export async function verifyCurrentDoneReceipt(identity) {
|
|
73
99
|
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
74
100
|
try {
|
|
@@ -105,34 +131,56 @@ export async function verifyCurrentDoneReceipt(identity) {
|
|
|
105
131
|
if (!receiptJobExists) {
|
|
106
132
|
return { valid: false, reason: 'missing-receipt' };
|
|
107
133
|
}
|
|
108
|
-
let
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
134
|
+
let ownedLease = await captureValidationFreshnessLease(identity, currentBindings);
|
|
135
|
+
try {
|
|
136
|
+
if (!ownedLease && identity.projectPath && identity.specPath) {
|
|
137
|
+
const projectPath = identity.projectPath;
|
|
138
|
+
const specPath = isAbsolute(identity.specPath)
|
|
139
|
+
? identity.specPath
|
|
140
|
+
: join(projectPath, identity.specPath);
|
|
141
|
+
ownedLease = await beginValidationFreshnessBarrier({
|
|
142
|
+
projectId: identity.projectId,
|
|
143
|
+
specId: identity.specId,
|
|
144
|
+
projectPath,
|
|
145
|
+
handoffPath: join(projectDataDir(identity.projectId), 'handoffs', identity.specId),
|
|
146
|
+
invalidateCurrentBindings: false,
|
|
147
|
+
});
|
|
148
|
+
const observed = await computeDurableValidationBindings({
|
|
149
|
+
canonicalProjectId: identity.projectId,
|
|
150
|
+
specId: identity.specId,
|
|
151
|
+
projectPath,
|
|
152
|
+
specPath,
|
|
153
|
+
});
|
|
154
|
+
const observedBindings = toValidationReceiptBindings(observed.bindings, observed.artifactDigests);
|
|
155
|
+
if (JSON.stringify(observedBindings) !== JSON.stringify(currentBindings)) {
|
|
156
|
+
if (!doneReceiptBindingsDifferOnlyInEvidence(observedBindings, currentBindings)) {
|
|
157
|
+
return { valid: false, reason: 'stale-bindings' };
|
|
158
|
+
}
|
|
159
|
+
await store.projectCurrentBindings(observedBindings);
|
|
160
|
+
console.warn('[planu:evidence-rebind]', {
|
|
161
|
+
projectId: identity.projectId,
|
|
162
|
+
specId: identity.specId,
|
|
163
|
+
rebindAt: new Date().toISOString(),
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
if (!(await bindValidationFreshnessLease(ownedLease, observedBindings))) {
|
|
167
|
+
return { valid: false, reason: 'stale-bindings' };
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (!ownedLease) {
|
|
171
|
+
return { valid: false, reason: 'stale-bindings' };
|
|
172
|
+
}
|
|
173
|
+
const transferred = ownedLease;
|
|
174
|
+
ownedLease = undefined;
|
|
175
|
+
return { ...verification, freshnessLease: transferred };
|
|
176
|
+
}
|
|
177
|
+
finally {
|
|
178
|
+
if (ownedLease) {
|
|
179
|
+
await closeValidationFreshnessLease(ownedLease).catch((error) => {
|
|
180
|
+
reportClassifiedDegradation('VALIDATION_FRESHNESS_CHECK_FAILED', error);
|
|
181
|
+
});
|
|
131
182
|
}
|
|
132
183
|
}
|
|
133
|
-
return freshnessLease
|
|
134
|
-
? { ...verification, freshnessLease }
|
|
135
|
-
: { valid: false, reason: 'stale-bindings' };
|
|
136
184
|
}
|
|
137
185
|
catch (e_1) {
|
|
138
186
|
env_1.error = e_1;
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { ToolResult } from '../../types/index.js';
|
|
2
2
|
import type { Spec } from '../../types/spec/core.js';
|
|
3
3
|
import type { EvidenceGateTransition } from '../../types/evidence-gates.js';
|
|
4
|
+
/** SPEC-1356: repo-relative paths mentioned in the spec's Problem/Technical prose also count
|
|
5
|
+
* as approved canonical scope, even when they are not in the file-ownership checklist. Paths
|
|
6
|
+
* do not need to exist yet (a spec may name files it has not created). */
|
|
7
|
+
export declare function extractCanonicalScopePathsFromSections(body: string): string[];
|
|
4
8
|
/** SPEC-1054: BDD/SDD lifecycle evidence gate. */
|
|
5
9
|
export declare function checkLifecycleEvidenceTransitionGate(args: {
|
|
6
10
|
spec: Spec;
|
|
@@ -66,9 +66,38 @@ import { ValidationReceiptStore, LOCAL_VALIDATION_RECEIPT_ISSUER, createValidati
|
|
|
66
66
|
import { reportClassifiedDegradation } from '../../errors/classified-degradation.js';
|
|
67
67
|
import { captureValidationFreshnessLease } from '../../engine/validation/validation-freshness.js';
|
|
68
68
|
import { computeDurableValidationBindings, toValidationReceiptBindings, } from '../../engine/validation/durable-validation.js';
|
|
69
|
+
import { checkReconciliationFreshness } from '../../engine/evidence-gates/reconciliation-freshness.js';
|
|
70
|
+
import { extractCanonicalFileOwnership } from '../../engine/handoff-packager.js';
|
|
71
|
+
import { generateDiscoverySkeleton, generateTaskPlanSkeleton, writeEvidenceSkeleton, } from '../../engine/evidence-gates/evidence-skeletons.js';
|
|
72
|
+
import { extractSection } from '../../engine/spec-format/markdown-sections.js';
|
|
73
|
+
const CANONICAL_SCOPE_PATH = /(?:src|tests|scripts|website)\/[\w/.@-]+\.\w+/g;
|
|
74
|
+
/** SPEC-1356: repo-relative paths mentioned in the spec's Problem/Technical prose also count
|
|
75
|
+
* as approved canonical scope, even when they are not in the file-ownership checklist. Paths
|
|
76
|
+
* do not need to exist yet (a spec may name files it has not created). */
|
|
77
|
+
export function extractCanonicalScopePathsFromSections(body) {
|
|
78
|
+
const problem = extractSection(body, 'problem') ?? '';
|
|
79
|
+
const technical = extractSection(body, 'technical') ?? '';
|
|
80
|
+
const matches = `${problem}\n${technical}`.match(CANONICAL_SCOPE_PATH) ?? [];
|
|
81
|
+
return [...new Set(matches)];
|
|
82
|
+
}
|
|
69
83
|
/** SPEC-1054: BDD/SDD lifecycle evidence gate. */
|
|
70
84
|
export async function checkLifecycleEvidenceTransitionGate(args) {
|
|
71
|
-
const
|
|
85
|
+
const staleReconciliationEvidence = await checkReconciliationFreshness({
|
|
86
|
+
projectId: args.projectId,
|
|
87
|
+
specId: args.specId,
|
|
88
|
+
});
|
|
89
|
+
if (staleReconciliationEvidence.length > 0) {
|
|
90
|
+
return evidenceGateError({
|
|
91
|
+
specId: args.specId,
|
|
92
|
+
transition: args.transition,
|
|
93
|
+
issues: staleReconciliationEvidence.map(({ artifactId }) => ({
|
|
94
|
+
code: 'SUPERSEDED_RECONCILIATION_EVIDENCE',
|
|
95
|
+
message: artifactId,
|
|
96
|
+
})),
|
|
97
|
+
requiredContractKinds: [],
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
const [criteriaResult, initialArtifacts] = await Promise.all([
|
|
72
101
|
extractLifecycleGateCriteriaAndBody(args.spec, args.projectPath),
|
|
73
102
|
readEvidenceArtifacts({
|
|
74
103
|
spec: args.spec,
|
|
@@ -77,6 +106,16 @@ export async function checkLifecycleEvidenceTransitionGate(args) {
|
|
|
77
106
|
projectPath: args.projectPath,
|
|
78
107
|
}),
|
|
79
108
|
]);
|
|
109
|
+
const { criteria, body } = criteriaResult;
|
|
110
|
+
const artifacts = await autofillMissingEvidenceSkeletons({
|
|
111
|
+
spec: args.spec,
|
|
112
|
+
projectId: args.projectId,
|
|
113
|
+
specId: args.specId,
|
|
114
|
+
transition: args.transition,
|
|
115
|
+
criteria,
|
|
116
|
+
body,
|
|
117
|
+
artifacts: initialArtifacts,
|
|
118
|
+
});
|
|
80
119
|
const receiptResult = args.transition === 'done'
|
|
81
120
|
? await readVerifiedCurrentReceipt({
|
|
82
121
|
projectId: args.projectId,
|
|
@@ -93,7 +132,6 @@ export async function checkLifecycleEvidenceTransitionGate(args) {
|
|
|
93
132
|
requiredContractKinds: [],
|
|
94
133
|
});
|
|
95
134
|
}
|
|
96
|
-
const { criteria, body } = criteriaResult;
|
|
97
135
|
const result = checkLifecycleEvidenceGate({
|
|
98
136
|
transition: args.transition,
|
|
99
137
|
spec: args.spec,
|
|
@@ -108,8 +146,28 @@ export async function checkLifecycleEvidenceTransitionGate(args) {
|
|
|
108
146
|
artifacts,
|
|
109
147
|
projectPath: args.projectPath,
|
|
110
148
|
});
|
|
149
|
+
const canonicalOwnership = body
|
|
150
|
+
? extractCanonicalFileOwnership(body)
|
|
151
|
+
: { present: false, toCreate: [], toModify: [], toTest: [], blockers: [] };
|
|
111
152
|
const groundedTechnicalPaths = body ? parseGroundedTechnicalPaths(body) : [];
|
|
112
|
-
|
|
153
|
+
const problemTechnicalScopePaths = body ? extractCanonicalScopePathsFromSections(body) : [];
|
|
154
|
+
const approvedScopePaths = [
|
|
155
|
+
...new Set([
|
|
156
|
+
...canonicalOwnership.toCreate,
|
|
157
|
+
...canonicalOwnership.toModify,
|
|
158
|
+
...canonicalOwnership.toTest,
|
|
159
|
+
...groundedTechnicalPaths,
|
|
160
|
+
...problemTechnicalScopePaths,
|
|
161
|
+
]),
|
|
162
|
+
];
|
|
163
|
+
if (canonicalOwnership.blockers.length > 0) {
|
|
164
|
+
const safeBlockers = canonicalOwnership.blockers.map((blocker) => blocker.startsWith('CONFLICTING_FILE_OWNERSHIP:') ? blocker : 'INVALID_FILE_OWNERSHIP');
|
|
165
|
+
result.issues.push({
|
|
166
|
+
code: 'done_drift_unapproved_scope',
|
|
167
|
+
message: `Approved canonical ownership is invalid: ${[...new Set(safeBlockers)].join(', ')}.`,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
result.issues.push(...checkDoneDriftContract({ index, approvedScopePaths }));
|
|
113
171
|
}
|
|
114
172
|
if (result.issues.length === 0) {
|
|
115
173
|
return null;
|
|
@@ -124,6 +182,48 @@ export async function checkLifecycleEvidenceTransitionGate(args) {
|
|
|
124
182
|
requiredContractKinds: result.requiredContractKinds,
|
|
125
183
|
});
|
|
126
184
|
}
|
|
185
|
+
/** True when the artifact truly has no file yet, as opposed to failing schema validation. */
|
|
186
|
+
function isArtifactAbsent(artifacts, label) {
|
|
187
|
+
return !artifacts.invalidArtifacts.some((entry) => entry.startsWith(label));
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* SPEC-1356: self-heal missing (not malformed) discovery/task-plan handoff evidence by
|
|
191
|
+
* generating a schema-valid skeleton and persisting it, instead of blocking the transition
|
|
192
|
+
* on a manual authoring step. Malformed existing artifacts still fail the gate as before.
|
|
193
|
+
*/
|
|
194
|
+
async function autofillMissingEvidenceSkeletons(args) {
|
|
195
|
+
if (args.spec.scope === 'trivial') {
|
|
196
|
+
return args.artifacts;
|
|
197
|
+
}
|
|
198
|
+
let artifacts = args.artifacts;
|
|
199
|
+
if (args.transition === 'approved' &&
|
|
200
|
+
!artifacts.discovery &&
|
|
201
|
+
isArtifactAbsent(artifacts, 'Discovery evidence')) {
|
|
202
|
+
const skeleton = generateDiscoverySkeleton({ spec: args.spec, body: args.body });
|
|
203
|
+
await writeEvidenceSkeleton({
|
|
204
|
+
projectId: args.projectId,
|
|
205
|
+
specId: args.specId,
|
|
206
|
+
filename: 'discovery.json',
|
|
207
|
+
artifact: skeleton,
|
|
208
|
+
});
|
|
209
|
+
console.warn('[planu:evidence-skeleton]', { specId: args.specId, filename: 'discovery.json' });
|
|
210
|
+
artifacts = { ...artifacts, discovery: skeleton };
|
|
211
|
+
}
|
|
212
|
+
if (args.transition === 'implementing' &&
|
|
213
|
+
!artifacts.taskPlan &&
|
|
214
|
+
isArtifactAbsent(artifacts, 'Task plan evidence')) {
|
|
215
|
+
const skeleton = generateTaskPlanSkeleton({ criteria: args.criteria });
|
|
216
|
+
await writeEvidenceSkeleton({
|
|
217
|
+
projectId: args.projectId,
|
|
218
|
+
specId: args.specId,
|
|
219
|
+
filename: 'task-plan.json',
|
|
220
|
+
artifact: skeleton,
|
|
221
|
+
});
|
|
222
|
+
console.warn('[planu:evidence-skeleton]', { specId: args.specId, filename: 'task-plan.json' });
|
|
223
|
+
artifacts = { ...artifacts, taskPlan: skeleton };
|
|
224
|
+
}
|
|
225
|
+
return artifacts;
|
|
226
|
+
}
|
|
127
227
|
async function readVerifiedCurrentReceipt(identity) {
|
|
128
228
|
try {
|
|
129
229
|
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { SpecStatus,
|
|
1
|
+
import type { SpecStatus, Actuals } from '../../types/index.js';
|
|
2
2
|
import type { Spec } from '../../types/spec/core.js';
|
|
3
|
-
export declare function recordDoneMetrics(projectId: string, specId: string, spec: Spec, actuals:
|
|
3
|
+
export declare function recordDoneMetrics(projectId: string, specId: string, spec: Spec, actuals: Actuals, projectPath?: string): Promise<void>;
|
|
4
4
|
/**
|
|
5
5
|
* SPEC-698: A frontmatter sync warning surfaced when `update_status` could not
|
|
6
6
|
* keep the on-disk spec.md status in sync with the data store.
|
|
@@ -10,7 +10,7 @@ export interface FrontmatterSyncWarning {
|
|
|
10
10
|
specPath: string | null;
|
|
11
11
|
reason: string;
|
|
12
12
|
}
|
|
13
|
-
export declare function syncSpecFiles(updatedSpec: Spec, _currentStatus: SpecStatus, newStatus: SpecStatus, projectPath?: string): Promise<{
|
|
13
|
+
export declare function syncSpecFiles(updatedSpec: Spec, _currentStatus: SpecStatus, newStatus: SpecStatus, projectPath?: string, qualityWarnings?: readonly string[]): Promise<{
|
|
14
14
|
warning?: FrontmatterSyncWarning;
|
|
15
15
|
}>;
|
|
16
16
|
export declare function tryReconcile(newStatus: string, specId: string, projectId: string): Promise<string | null>;
|
|
@@ -32,5 +32,6 @@ export declare function recordTerminalTransitionEvent(args: {
|
|
|
32
32
|
modelId?: string;
|
|
33
33
|
gateResults?: Record<string, 'pass' | 'fail' | 'skip' | 'forced'>;
|
|
34
34
|
meta?: Record<string, unknown>;
|
|
35
|
+
projectPath?: string;
|
|
35
36
|
}): Promise<void>;
|
|
36
37
|
//# sourceMappingURL=file-sync.d.ts.map
|
|
@@ -3,11 +3,12 @@ import { metricsStore } from '../../storage/index.js';
|
|
|
3
3
|
import { addLesson, getLessons } from '../../storage/lessons-store.js';
|
|
4
4
|
import { dispatchFeedbackEvent } from '../learn.js';
|
|
5
5
|
import { autoReconcileOnDone } from '../update-status-reconcile.js';
|
|
6
|
-
import { readFile } from 'node:fs/promises';
|
|
6
|
+
import { lstat, readFile, realpath } from 'node:fs/promises';
|
|
7
7
|
import { parseFrontmatter } from '../../engine/frontmatter-parser.js';
|
|
8
8
|
import { computeFrontmatterSha } from '../../engine/frontmatter-sha/index.js';
|
|
9
9
|
import { appendTransitionEvent } from '../../storage/transition-log.js';
|
|
10
10
|
import { atomicWriteFile } from '../../engine/safety/atomic-write-file.js';
|
|
11
|
+
import { isAbsolute, normalize, relative, resolve, sep } from 'node:path';
|
|
11
12
|
import { redactFailureMessage, reportClassifiedDegradation, } from '../../errors/classified-degradation.js';
|
|
12
13
|
export async function recordDoneMetrics(projectId, specId, spec, actuals, projectPath) {
|
|
13
14
|
if (actuals.devHours + actuals.reviewHours === 0) {
|
|
@@ -74,6 +75,46 @@ async function autoCaptureLessonEstimation(spec, actuals, accuracy, projectId, p
|
|
|
74
75
|
}
|
|
75
76
|
const SPEC_BODY_SHRINK_ABSOLUTE_THRESHOLD = 200;
|
|
76
77
|
const SPEC_BODY_SHRINK_RATIO_THRESHOLD = 0.05;
|
|
78
|
+
async function resolveSpecPathForTransition(spec, projectPath) {
|
|
79
|
+
if (!projectPath) {
|
|
80
|
+
return spec.specPath;
|
|
81
|
+
}
|
|
82
|
+
const normalizedPath = normalize(spec.specPath);
|
|
83
|
+
const segments = normalizedPath.split(sep).filter(Boolean);
|
|
84
|
+
const planuIndex = segments.lastIndexOf('planu');
|
|
85
|
+
const specDirectory = segments[planuIndex + 2];
|
|
86
|
+
const isPortableContract = planuIndex >= 0 &&
|
|
87
|
+
segments[planuIndex + 1] === 'specs' &&
|
|
88
|
+
segments[planuIndex + 3] === 'spec.md' &&
|
|
89
|
+
planuIndex + 4 === segments.length &&
|
|
90
|
+
specDirectory !== undefined &&
|
|
91
|
+
(specDirectory === spec.id || specDirectory.startsWith(`${spec.id}-`));
|
|
92
|
+
if (!isPortableContract) {
|
|
93
|
+
throw new Error('SPEC_PATH_RELOCALIZATION_FAILED: specPath is not a portable spec contract');
|
|
94
|
+
}
|
|
95
|
+
const selectedRoot = resolve(projectPath);
|
|
96
|
+
const candidate = resolve(selectedRoot, 'planu', 'specs', specDirectory, 'spec.md');
|
|
97
|
+
const relativeCandidate = relative(selectedRoot, candidate);
|
|
98
|
+
if (relativeCandidate.startsWith(`..${sep}`) ||
|
|
99
|
+
relativeCandidate === '..' ||
|
|
100
|
+
isAbsolute(relativeCandidate)) {
|
|
101
|
+
throw new Error('SPEC_PATH_RELOCALIZATION_FAILED: specPath escapes the selected root');
|
|
102
|
+
}
|
|
103
|
+
const [rootRealpath, candidateRealpath, candidateStat] = await Promise.all([
|
|
104
|
+
realpath(selectedRoot),
|
|
105
|
+
realpath(candidate),
|
|
106
|
+
lstat(candidate),
|
|
107
|
+
]);
|
|
108
|
+
const realRelative = relative(rootRealpath, candidateRealpath);
|
|
109
|
+
if (candidateStat.isSymbolicLink() ||
|
|
110
|
+
!candidateStat.isFile() ||
|
|
111
|
+
realRelative.startsWith(`..${sep}`) ||
|
|
112
|
+
realRelative === '..' ||
|
|
113
|
+
isAbsolute(realRelative)) {
|
|
114
|
+
throw new Error('SPEC_PATH_RELOCALIZATION_FAILED: specPath is not a contained regular file');
|
|
115
|
+
}
|
|
116
|
+
return candidateRealpath;
|
|
117
|
+
}
|
|
77
118
|
function bodyWithoutFrontmatter(content) {
|
|
78
119
|
return parseFrontmatter(content).body;
|
|
79
120
|
}
|
|
@@ -95,13 +136,23 @@ function assertSpecBodyIntegrity(args) {
|
|
|
95
136
|
throw new Error(`SPEC_INTEGRITY_WRITE_FAILED: refusing to write ${args.specId} because update_status(${args.newStatus}) would shrink spec.md body by ${String(lost)} characters without an explicit migration marker.`);
|
|
96
137
|
}
|
|
97
138
|
}
|
|
98
|
-
export async function syncSpecFiles(updatedSpec, _currentStatus, newStatus, projectPath) {
|
|
139
|
+
export async function syncSpecFiles(updatedSpec, _currentStatus, newStatus, projectPath, qualityWarnings = []) {
|
|
99
140
|
let warning;
|
|
100
141
|
if (updatedSpec.specPath) {
|
|
101
142
|
try {
|
|
102
|
-
const
|
|
143
|
+
const transitionSpecPath = await resolveSpecPathForTransition(updatedSpec, projectPath);
|
|
144
|
+
const specContent = await readFile(transitionSpecPath, 'utf-8');
|
|
145
|
+
if (projectPath) {
|
|
146
|
+
const parsedId = parseFrontmatter(specContent).metadata.id;
|
|
147
|
+
if (parsedId !== updatedSpec.id) {
|
|
148
|
+
throw new Error('SPEC_PATH_RELOCALIZATION_FAILED: spec frontmatter ID does not match');
|
|
149
|
+
}
|
|
150
|
+
}
|
|
103
151
|
const { updateFrontmatterField } = await import('../../engine/frontmatter-parser.js');
|
|
104
152
|
let updatedContent = updateFrontmatterField(specContent, 'status', newStatus);
|
|
153
|
+
if (qualityWarnings.length > 0) {
|
|
154
|
+
updatedContent = updateFrontmatterField(updatedContent, 'qualityWarnings', JSON.stringify(qualityWarnings));
|
|
155
|
+
}
|
|
105
156
|
if (newStatus === 'done') {
|
|
106
157
|
updatedContent = markAllCriteriaDone(updatedContent);
|
|
107
158
|
if (isLeanSpecContent(updatedContent)) {
|
|
@@ -114,7 +165,7 @@ export async function syncSpecFiles(updatedSpec, _currentStatus, newStatus, proj
|
|
|
114
165
|
newStatus,
|
|
115
166
|
specId: updatedSpec.id,
|
|
116
167
|
});
|
|
117
|
-
await atomicWriteFile(
|
|
168
|
+
await atomicWriteFile(transitionSpecPath, updatedContent);
|
|
118
169
|
// SPEC-544: Auto-stage spec.md after writing so it's included in the user's next commit
|
|
119
170
|
// SPEC-575: Auto-commit staged planu/ docs (idempotent, safe-fail)
|
|
120
171
|
// Commit message must reflect the actual transition — 'mark-done' is only
|
|
@@ -125,7 +176,7 @@ export async function syncSpecFiles(updatedSpec, _currentStatus, newStatus, proj
|
|
|
125
176
|
process.env.PLANU_SUPPRESS_AUTOCOMMIT !== 'true') {
|
|
126
177
|
try {
|
|
127
178
|
const { git: gitCmd } = await import('../git/git-helpers.js');
|
|
128
|
-
await gitCmd(projectPath, ['add', '--',
|
|
179
|
+
await gitCmd(projectPath, ['add', '--', transitionSpecPath]);
|
|
129
180
|
const { planuAutoCommit } = await import('../../engine/git/planu-autocommit.js');
|
|
130
181
|
void planuAutoCommit({
|
|
131
182
|
projectPath,
|
|
@@ -197,7 +248,7 @@ export async function recordTerminalTransitionEvent(args) {
|
|
|
197
248
|
let frontmatterSha;
|
|
198
249
|
if (args.specPath) {
|
|
199
250
|
try {
|
|
200
|
-
const content = await readFile(args.specPath, 'utf-8');
|
|
251
|
+
const content = await readFile(await resolveSpecPathForTransition({ id: args.specId, specPath: args.specPath }, args.projectPath), 'utf-8');
|
|
201
252
|
const { metadata } = parseFrontmatter(content);
|
|
202
253
|
frontmatterSha = computeFrontmatterSha(metadata);
|
|
203
254
|
}
|