@planu/cli 4.14.1 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +35 -1
- package/LICENSE +55 -12
- package/README.md +10 -2
- package/dist/cli/commands/install.d.ts +1 -12
- package/dist/cli/commands/install.js +9 -65
- package/dist/cli/commands/job.js +4 -3
- package/dist/cli/commands/spec.js +2 -2
- package/dist/cli/commands/validate.js +2 -2
- package/dist/cli/router.js +1 -3
- package/dist/config/environment-schema.json +1 -3
- package/dist/config/official-sdd-tools.d.ts +4 -0
- package/dist/config/official-sdd-tools.js +35 -0
- package/dist/config/product-public.d.ts +139 -3
- package/dist/config/product-public.js +8 -1
- package/dist/config/product-public.json +7 -36
- package/dist/config/registries/hosts/codex.json +20 -363
- package/dist/config/release-policy.json +84 -2
- package/dist/config/runtime-policy.json +12 -1
- package/dist/config/skill-templates/planu-resume-work.md +2 -2
- package/dist/core/index.d.ts +1 -1
- package/dist/core/spec-validator.d.ts +4 -4
- package/dist/core/spec-validator.js +1 -1
- package/dist/engine/abortable-process-runner.d.ts +20 -2
- package/dist/engine/abortable-process-runner.js +243 -74
- package/dist/engine/ambiguity-scorer.js +4 -26
- package/dist/engine/auth/scope-mapper.js +1 -7
- package/dist/engine/autopilot/state-updater.js +4 -4
- package/dist/engine/cascade-hooks/hooks/review-enricher.hook.js +9 -6
- package/dist/engine/cascade-hooks/state-drift-detector.d.ts +3 -3
- package/dist/engine/cascade-hooks/state-drift-detector.js +1 -1
- package/dist/engine/ci-generator/planu-config.js +1 -7
- package/dist/engine/clarification-gate/gate.d.ts +3 -3
- package/dist/engine/code-scanner/layer-scanner.js +6 -22
- package/dist/engine/core-bridge.d.ts +3 -3
- package/dist/engine/cost-guardrails.d.ts +1 -1
- package/dist/engine/cost-guardrails.js +1 -1
- package/dist/engine/criterion-identity.d.ts +1 -0
- package/dist/engine/criterion-identity.js +7 -0
- package/dist/engine/dashboard/data-loader.js +2 -7
- package/dist/engine/dashboard/templates-layout.js +0 -3
- package/dist/engine/dashboard/templates-project.js +1 -13
- package/dist/engine/docs-site-generator/data-collector.d.ts +1 -1
- package/dist/engine/docs-site-generator/data-collector.js +7 -28
- package/dist/engine/docs-site-generator/tools-renderer.js +3 -10
- package/dist/engine/evidence-gates/artifact-reader.d.ts +10 -1
- package/dist/engine/evidence-gates/artifact-reader.js +115 -2
- package/dist/engine/evidence-gates/lifecycle-gate.js +4 -3
- package/dist/engine/evidence-gates/reconciliation-freshness.d.ts +11 -0
- package/dist/engine/evidence-gates/reconciliation-freshness.js +72 -0
- package/dist/engine/evidence-index/done-drift.d.ts +1 -1
- package/dist/engine/evidence-index/done-drift.js +40 -18
- package/dist/engine/execution/context.js +3 -0
- package/dist/engine/execution/durable-job-heartbeat-runtime.d.ts +4 -0
- package/dist/engine/execution/durable-job-heartbeat-runtime.js +400 -0
- package/dist/engine/execution/job-runtime.d.ts +30 -0
- package/dist/engine/execution/job-runtime.js +114 -6
- package/dist/engine/execution/public-job-sanitizer.d.ts +4 -0
- package/dist/engine/execution/public-job-sanitizer.js +31 -0
- package/dist/engine/execution/validate-job-executor.d.ts +12 -1
- package/dist/engine/execution/validate-job-executor.js +549 -86
- package/dist/engine/execution/validate-job-payload.js +107 -50
- package/dist/engine/execution/validate-job-recovery.js +1 -1
- package/dist/engine/execution/validate-worker-runtime.d.ts +5 -0
- package/dist/engine/execution/validate-worker-runtime.js +90 -2
- package/dist/engine/execution/validate-worker-thread.js +17 -24
- package/dist/engine/handoff-artifacts/schemas.d.ts +2 -1
- package/dist/engine/handoff-artifacts/schemas.js +33 -0
- package/dist/engine/handoff-packager.d.ts +19 -0
- package/dist/engine/handoff-packager.js +415 -13
- package/dist/engine/hooks/effort-configurator.js +1 -1
- package/dist/engine/impact-detector/test-break-predictor.js +3 -3
- package/dist/engine/impact-detector/tool-registration.js +1 -1
- package/dist/engine/implementation-contract/renderer.js +23 -8
- package/dist/engine/lifecycle-reconciliation-io.d.ts +11 -0
- package/dist/engine/lifecycle-reconciliation-io.js +180 -0
- package/dist/engine/lifecycle-reconciliation.d.ts +16 -0
- package/dist/engine/lifecycle-reconciliation.js +406 -0
- package/dist/engine/local-first/tool-classification.js +2 -0
- package/dist/engine/macos-keychain-native-port.d.ts +5 -0
- package/dist/engine/macos-keychain-native-port.js +19 -7
- package/dist/engine/migrations/remove-commercial-state.d.ts +3 -0
- package/dist/engine/migrations/remove-commercial-state.js +343 -0
- package/dist/engine/network-policy.js +1 -0
- package/dist/engine/planu-core.darwin-arm64.node.manifest.json +7 -7
- package/dist/engine/planu-core.darwin-arm64.node.sbom.json +10 -10
- package/dist/engine/planu-core.darwin-x64.node.manifest.json +7 -7
- package/dist/engine/planu-core.darwin-x64.node.sbom.json +10 -10
- package/dist/engine/planu-core.linux-arm64-gnu.node.manifest.json +7 -7
- package/dist/engine/planu-core.linux-arm64-gnu.node.sbom.json +10 -10
- package/dist/engine/planu-core.linux-arm64-musl.node.manifest.json +7 -7
- package/dist/engine/planu-core.linux-arm64-musl.node.sbom.json +10 -10
- package/dist/engine/planu-core.linux-x64-gnu.node.manifest.json +7 -7
- package/dist/engine/planu-core.linux-x64-gnu.node.sbom.json +10 -10
- package/dist/engine/planu-core.linux-x64-musl.node.manifest.json +7 -7
- package/dist/engine/planu-core.linux-x64-musl.node.sbom.json +10 -10
- package/dist/engine/planu-core.win32-arm64-msvc.node.manifest.json +7 -7
- package/dist/engine/planu-core.win32-arm64-msvc.node.sbom.json +10 -10
- package/dist/engine/planu-core.win32-x64-msvc.node.manifest.json +7 -7
- package/dist/engine/planu-core.win32-x64-msvc.node.sbom.json +10 -10
- package/dist/engine/readiness-checker.d.ts +1 -16
- package/dist/engine/readiness-checker.js +27 -88
- package/dist/engine/runtime-policy.d.ts +2 -0
- package/dist/engine/runtime-policy.js +42 -5
- package/dist/engine/runtime-security/rate-limiter.js +0 -1
- package/dist/engine/safety/contained-project-file.d.ts +3 -0
- package/dist/engine/safety/contained-project-file.js +23 -0
- package/dist/engine/scope-boundaries/contradiction-checker.js +63 -1
- package/dist/engine/scope-boundaries/scope-validator.js +45 -7
- package/dist/engine/session/checkpoint-writer.js +0 -9
- package/dist/engine/spec-format/acceptance-criteria.d.ts +6 -2
- package/dist/engine/spec-format/acceptance-criteria.js +77 -18
- package/dist/engine/spec-format/markdown-sections.d.ts +14 -0
- package/dist/engine/spec-format/markdown-sections.js +105 -0
- package/dist/engine/spec-format/read-technical-section.js +33 -5
- package/dist/engine/spec-format/technical-md-populator.js +3 -2
- package/dist/engine/spec-format/unified-spec-builder.d.ts +7 -0
- package/dist/engine/spec-format/unified-spec-builder.js +202 -0
- package/dist/engine/spec-migrator/canonical-deduplicator.d.ts +18 -0
- package/dist/engine/spec-migrator/canonical-deduplicator.js +479 -0
- package/dist/engine/spec-migrator/filesystem-import.d.ts +4 -8
- package/dist/engine/spec-migrator/filesystem-import.js +121 -14
- package/dist/engine/spec-migrator/frontmatter-parser.js +32 -15
- package/dist/engine/spec-migrator/index.d.ts +1 -0
- package/dist/engine/spec-migrator/index.js +1 -0
- package/dist/engine/spec-state-machine/transition-spec.d.ts +6 -1
- package/dist/engine/spec-state-machine/transition-spec.js +10 -1
- package/dist/engine/technical-enricher/index.js +16 -8
- package/dist/engine/technical-enricher/render-enriched.d.ts +1 -0
- package/dist/engine/technical-enricher/render-enriched.js +42 -14
- package/dist/engine/telemetry/health-checker.js +53 -17
- package/dist/engine/telemetry/telemetry-client.js +26 -8
- package/dist/engine/text-signal-boundaries.d.ts +2 -0
- package/dist/engine/text-signal-boundaries.js +25 -2
- package/dist/engine/time/relative-date.d.ts +3 -0
- package/dist/engine/time/relative-date.js +16 -0
- package/dist/engine/timing/budget.d.ts +11 -0
- package/dist/engine/timing/budget.js +27 -9
- package/dist/engine/token-optimizer/analytics.d.ts +1 -1
- package/dist/engine/token-optimizer/analytics.js +5 -6
- package/dist/engine/usage-tracker/core.d.ts +2 -2
- package/dist/engine/usage-tracker/core.js +1 -2
- package/dist/engine/usage-tracker/index.d.ts +0 -3
- package/dist/engine/usage-tracker/index.js +0 -3
- package/dist/engine/usage-tracker/stats.d.ts +2 -2
- package/dist/engine/usage-tracker/stats.js +1 -28
- package/dist/engine/validation/durable-validation.d.ts +1 -1
- package/dist/engine/validation/durable-validation.js +3 -82
- package/dist/engine/validation/validation-freshness.d.ts +11 -1
- package/dist/engine/validation/validation-freshness.js +231 -91
- package/dist/engine/validation/validation-receipt.d.ts +9 -6
- package/dist/engine/validation/validation-receipt.js +84 -24
- package/dist/engine/validation/validation-source-digest.d.ts +2 -0
- package/dist/engine/validation/validation-source-digest.js +86 -0
- package/dist/engine/validation/validation-submission.d.ts +5 -4
- package/dist/engine/validation/validation-submission.js +25 -11
- package/dist/engine/validation/validation-worktree.d.ts +21 -0
- package/dist/engine/validation/validation-worktree.js +260 -0
- package/dist/engine/validation-impact-planner.d.ts +14 -2
- package/dist/engine/validation-impact-planner.js +0 -3
- package/dist/engine/validator/dor-dod.d.ts +1 -0
- package/dist/engine/validator/dor-dod.js +5 -3
- package/dist/engine/validator/extractors.d.ts +1 -5
- package/dist/engine/validator/extractors.js +24 -69
- package/dist/engine/validator/spec-compliance-runner.d.ts +12 -24
- package/dist/engine/validator/spec-compliance-runner.js +429 -211
- package/dist/engine/validator.js +35 -22
- package/dist/index.js +4 -16
- package/dist/resources/usage-stats-resource.js +2 -18
- package/dist/security/adapters/linux-secret-service.js +7 -3
- package/dist/security/adapters/windows-credential-manager.js +6 -4
- package/dist/server/routes/specs.js +10 -0
- package/dist/storage/base-store.d.ts +1 -1
- package/dist/storage/base-store.js +1 -1
- package/dist/storage/feedback-remote.d.ts +2 -3
- package/dist/storage/feedback-remote.js +45 -31
- package/dist/storage/global-store.d.ts +1 -17
- package/dist/storage/global-store.js +1 -42
- package/dist/storage/index.d.ts +0 -2
- package/dist/storage/index.js +0 -2
- package/dist/storage/migrations/canonical-storage.js +124 -11
- package/dist/storage/runtime-db.d.ts +9 -0
- package/dist/storage/runtime-db.js +64 -8
- package/dist/storage/spec-store.d.ts +14 -2
- package/dist/storage/spec-store.js +104 -14
- package/dist/storage/status-store/atomic-status-write.d.ts +5 -0
- package/dist/storage/status-store/atomic-status-write.js +57 -0
- package/dist/storage/status-store/file-lock.js +30 -11
- package/dist/storage/status-store/self-healing.js +3 -3
- package/dist/storage/status-store/version-sync.d.ts +2 -1
- package/dist/storage/status-store/version-sync.js +35 -40
- package/dist/storage/usage-store.d.ts +1 -5
- package/dist/storage/usage-store.js +0 -35
- package/dist/tools/auto-update-handler.d.ts +3 -3
- package/dist/tools/auto-update-handler.js +3 -3
- package/dist/tools/challenge-spec/resilience-challenge-scenarios.d.ts +2 -1
- package/dist/tools/challenge-spec/resilience-challenge-scenarios.js +10 -7
- package/dist/tools/challenge-spec/scenario-collector.js +1 -1
- package/dist/tools/challenge-spec/scenarios-data.js +1 -1
- package/dist/tools/challenge-spec/scenarios-utils.d.ts +1 -1
- package/dist/tools/challenge-spec/scenarios-utils.js +35 -11
- package/dist/tools/challenge-spec.js +5 -5
- package/dist/tools/check-readiness.js +1 -1
- package/dist/tools/configure-telemetry.js +30 -2
- package/dist/tools/create-spec/spec-builder.js +0 -11
- package/dist/tools/create-spec.d.ts +1 -0
- package/dist/tools/create-spec.js +444 -239
- package/dist/tools/git/branch-ops.d.ts +1 -1
- package/dist/tools/git/branch-ops.js +3 -2
- package/dist/tools/init-project/handler.js +10 -12
- package/dist/tools/init-project/host-assets-writer.js +26 -1
- package/dist/tools/init-project/lint-scaffolder.d.ts +3 -3
- package/dist/tools/init-project/migration-runner.d.ts +2 -4
- package/dist/tools/init-project/migration-runner.js +3 -2
- package/dist/tools/init-project/portable-index-reconciler.d.ts +4 -0
- package/dist/tools/init-project/portable-index-reconciler.js +14 -0
- package/dist/tools/init-project/result-builder.js +1 -3
- package/dist/tools/jobs/handlers.js +38 -6
- package/dist/tools/list-specs.js +5 -26
- package/dist/tools/merge-risk-handler.js +2 -2
- package/dist/tools/package-handoff.js +2 -0
- package/dist/tools/reconcile-spec.js +0 -27
- package/dist/tools/register-agent-squad-tools.js +2 -2
- package/dist/tools/register-platform-tools/design-stack-tools.js +11 -11
- package/dist/tools/register-platform-tools/lifecycle-infra-tools.js +7 -7
- package/dist/tools/register-sdd-tools.d.ts +1 -1
- package/dist/tools/register-sdd-tools.js +2 -29
- package/dist/tools/register-spec-tools/analysis-tools.js +8 -8
- package/dist/tools/register-spec-tools/core-spec-tools.js +17 -8
- package/dist/tools/safe-handler.d.ts +5 -13
- package/dist/tools/safe-handler.js +7 -23
- package/dist/tools/schemas/index.d.ts +1 -1
- package/dist/tools/schemas/index.js +1 -1
- package/dist/tools/schemas/output-schemas.d.ts +0 -23
- package/dist/tools/schemas/output-schemas.js +0 -28
- package/dist/tools/schemas/spec.d.ts +4 -0
- package/dist/tools/schemas/spec.js +3 -0
- package/dist/tools/schemas/validate-output-schema.d.ts +11 -0
- package/dist/tools/schemas/validate-output-schema.js +7 -0
- package/dist/tools/spec-prompt-handler.js +3 -3
- package/dist/tools/sync-spec-state-handler.d.ts +1 -1
- package/dist/tools/sync-spec-state-handler.js +1 -1
- package/dist/tools/tool-entry.d.ts +4 -4
- package/dist/tools/tool-entry.js +3 -3
- package/dist/tools/tool-registry/core-tools.js +31 -15
- package/dist/tools/tool-registry/group-infra.js +27 -110
- package/dist/tools/tool-registry/group-quality-compliance.js +26 -26
- package/dist/tools/tool-registry/inventory.d.ts +2 -0
- package/dist/tools/tool-registry/inventory.js +2 -0
- package/dist/tools/tool-registry-helpers.d.ts +3 -3
- package/dist/tools/tool-registry-helpers.js +3 -3
- package/dist/tools/tool-usage-report/registered-tools.d.ts +1 -1
- package/dist/tools/tool-usage-report/registered-tools.js +3 -15
- package/dist/tools/update-status/approval-gates.js +3 -0
- package/dist/tools/update-status/done-receipt-verifier.js +40 -27
- package/dist/tools/update-status/evidence-gate.js +36 -1
- package/dist/tools/update-status/file-sync.d.ts +2 -1
- package/dist/tools/update-status/file-sync.js +57 -6
- package/dist/tools/update-status/index.d.ts +2 -0
- package/dist/tools/update-status/index.js +95 -58
- package/dist/tools/update-status/response-builder.js +3 -3
- package/dist/tools/update-status/side-effects.d.ts +2 -0
- package/dist/tools/update-status/side-effects.js +2 -2
- package/dist/tools/update-status/transition-guard.js +32 -15
- package/dist/tools/update-status-actions.d.ts +4 -2
- package/dist/tools/update-status-actions.js +16 -14
- package/dist/tools/usage-report.js +1 -16
- package/dist/tools/usage-stats.js +1 -18
- package/dist/tools/usage-tracking.d.ts +4 -0
- package/dist/tools/usage-tracking.js +20 -0
- package/dist/tools/validate-lint.d.ts +1 -0
- package/dist/tools/validate-lint.js +1 -1
- package/dist/tools/validate.d.ts +2 -1
- package/dist/tools/validate.js +114 -34
- package/dist/transports/http-transport.js +6 -8
- package/dist/transports/mcp-types.d.ts +0 -2
- package/dist/transports/session-context.js +1 -1
- package/dist/types/cascade-hooks.d.ts +1 -1
- package/dist/types/ci.d.ts +0 -4
- package/dist/types/clarification-token.d.ts +1 -1
- package/dist/types/claude-code-runtime.d.ts +0 -27
- package/dist/types/commercial-migration.d.ts +17 -0
- package/dist/types/commercial-migration.js +2 -0
- package/dist/types/common/primitives.d.ts +0 -2
- package/dist/types/core-bridge.d.ts +4 -10
- package/dist/types/dashboard.d.ts +3 -7
- package/dist/types/docs.d.ts +0 -1
- package/dist/types/durable-job.d.ts +4 -4
- package/dist/types/durable-validation.d.ts +100 -2
- package/dist/types/durable-validation.js +165 -1
- package/dist/types/errors/structured-error.d.ts +3 -3
- package/dist/types/estimation.d.ts +2 -0
- package/dist/types/evidence-gates.d.ts +9 -1
- package/dist/types/execution.d.ts +2 -0
- package/dist/types/gemini-integration.d.ts +0 -11
- package/dist/types/handoff-artifacts.d.ts +41 -0
- package/dist/types/hooks-advanced.d.ts +0 -13
- package/dist/types/impact-detection.d.ts +2 -2
- package/dist/types/index.d.ts +2 -6
- package/dist/types/index.js +1 -5
- package/dist/types/kiro-integration.d.ts +0 -6
- package/dist/types/network-policy.d.ts +1 -1
- package/dist/types/project/lint-scaffolder.d.ts +1 -1
- package/dist/types/runtime-policy.d.ts +11 -0
- package/dist/types/session-safeguard.d.ts +0 -31
- package/dist/types/spec/canonical-repair.d.ts +81 -0
- package/dist/types/spec/canonical-repair.js +2 -0
- package/dist/types/spec/core.d.ts +14 -0
- package/dist/types/spec/index.d.ts +1 -0
- package/dist/types/spec/index.js +1 -0
- package/dist/types/spec/inputs.d.ts +29 -0
- package/dist/types/spec-format.d.ts +24 -0
- package/dist/types/telemetry.d.ts +4 -1
- package/dist/types/transport.d.ts +0 -2
- package/dist/types/usage.d.ts +1 -36
- package/dist/types/usage.js +1 -1
- package/dist/types/validate-worker.d.ts +6 -0
- package/dist/types/validation-receipt.d.ts +1 -1
- package/dist/types/validation.d.ts +2 -2
- package/package.json +16 -12
- package/planu-native.json +1 -1
- package/planu-plugin.json +23 -40
- package/scripts/lib/pending-release-file.mjs +93 -0
- package/src/i18n/messages/en.json +0 -41
- package/src/i18n/messages/es.json +0 -41
- package/src/i18n/messages/pt.json +0 -41
- package/dist/cli/commands/activate.d.ts +0 -14
- package/dist/cli/commands/activate.js +0 -174
- package/dist/config/license-plans.json +0 -251
- package/dist/config/product-public.ts +0 -148
- package/dist/config/server-instructions.ts +0 -121
- package/dist/config/version-defaults.ts +0 -76
- package/dist/config/version.ts +0 -21
- package/dist/engine/ai-integration/gemini/settings-generator.d.ts +0 -4
- package/dist/engine/ai-integration/gemini/settings-generator.js +0 -195
- package/dist/engine/ai-integration/kiro/hooks-generator.d.ts +0 -6
- package/dist/engine/ai-integration/kiro/hooks-generator.js +0 -75
- package/dist/engine/hooks/full-spectrum-generator.d.ts +0 -32
- package/dist/engine/hooks/full-spectrum-generator.js +0 -117
- package/dist/engine/license-validator/config-loader.d.ts +0 -5
- package/dist/engine/license-validator/config-loader.js +0 -16
- package/dist/engine/license-validator/core.d.ts +0 -15
- package/dist/engine/license-validator/core.js +0 -151
- package/dist/engine/license-validator/fingerprint.d.ts +0 -3
- package/dist/engine/license-validator/fingerprint.js +0 -17
- package/dist/engine/license-validator/lemon-squeezy.d.ts +0 -5
- package/dist/engine/license-validator/lemon-squeezy.js +0 -55
- package/dist/engine/license-validator.d.ts +0 -5
- package/dist/engine/license-validator.js +0 -6
- package/dist/engine/product-intelligence/index.d.ts +0 -7
- package/dist/engine/product-intelligence/index.js +0 -211
- package/dist/engine/session/auto-save.d.ts +0 -18
- package/dist/engine/session/auto-save.js +0 -98
- package/dist/engine/session/context-snapshot.d.ts +0 -8
- package/dist/engine/session/context-snapshot.js +0 -49
- package/dist/engine/session/memory-sync.d.ts +0 -12
- package/dist/engine/session/memory-sync.js +0 -34
- package/dist/engine/session/session-diff.d.ts +0 -11
- package/dist/engine/session/session-diff.js +0 -42
- package/dist/engine/session/session-merge.d.ts +0 -12
- package/dist/engine/session/session-merge.js +0 -55
- package/dist/engine/session-safeguard/checkpoint-runner.d.ts +0 -4
- package/dist/engine/session-safeguard/checkpoint-runner.js +0 -70
- package/dist/engine/session-safeguard/learnings-buffer.d.ts +0 -15
- package/dist/engine/session-safeguard/learnings-buffer.js +0 -91
- package/dist/engine/session-safeguard/precompaction-drain.d.ts +0 -10
- package/dist/engine/session-safeguard/precompaction-drain.js +0 -82
- package/dist/engine/session-safeguard/unpushed-detector.d.ts +0 -7
- package/dist/engine/session-safeguard/unpushed-detector.js +0 -59
- package/dist/engine/spec-changelog/core.d.ts +0 -16
- package/dist/engine/spec-changelog/core.js +0 -176
- package/dist/engine/spec-changelog/diff.d.ts +0 -18
- package/dist/engine/spec-changelog/diff.js +0 -121
- package/dist/engine/spec-changelog/index.d.ts +0 -3
- package/dist/engine/spec-changelog/index.js +0 -4
- package/dist/engine/trial-engine.d.ts +0 -5
- package/dist/engine/trial-engine.js +0 -14
- package/dist/engine/usage-tracker/rate-check.d.ts +0 -7
- package/dist/engine/usage-tracker/rate-check.js +0 -18
- package/dist/engine/usage-tracker/rate-limiter.d.ts +0 -14
- package/dist/engine/usage-tracker/rate-limiter.js +0 -73
- package/dist/engine/usage-tracker/trial.d.ts +0 -27
- package/dist/engine/usage-tracker/trial.js +0 -49
- package/dist/engine/webhook/event-handlers.d.ts +0 -25
- package/dist/engine/webhook/event-handlers.js +0 -89
- package/dist/engine/webhook/index.d.ts +0 -4
- package/dist/engine/webhook/index.js +0 -5
- package/dist/engine/webhook/server.d.ts +0 -12
- package/dist/engine/webhook/server.js +0 -249
- package/dist/engine/webhook/signature.d.ts +0 -16
- package/dist/engine/webhook/signature.js +0 -49
- package/dist/storage/license-store.d.ts +0 -6
- package/dist/storage/license-store.js +0 -63
- package/dist/storage/session-store.d.ts +0 -20
- package/dist/storage/session-store.js +0 -136
- package/dist/tools/activate-license.d.ts +0 -6
- package/dist/tools/activate-license.js +0 -132
- package/dist/tools/checkpoint-handler.d.ts +0 -3
- package/dist/tools/checkpoint-handler.js +0 -130
- package/dist/tools/export-session.d.ts +0 -3
- package/dist/tools/export-session.js +0 -90
- package/dist/tools/license-gate.d.ts +0 -18
- package/dist/tools/license-gate.js +0 -222
- package/dist/tools/license-status.d.ts +0 -3
- package/dist/tools/license-status.js +0 -93
- package/dist/tools/list-sessions.d.ts +0 -3
- package/dist/tools/list-sessions.js +0 -46
- package/dist/tools/planu-session-checkpoint.d.ts +0 -6
- package/dist/tools/planu-session-checkpoint.js +0 -42
- package/dist/tools/restore-session.d.ts +0 -3
- package/dist/tools/restore-session.js +0 -71
- package/dist/tools/spec-history.d.ts +0 -3
- package/dist/tools/spec-history.js +0 -59
- package/dist/tools/webhook.d.ts +0 -6
- package/dist/tools/webhook.js +0 -150
- package/dist/types/changelog.d.ts +0 -49
- package/dist/types/changelog.js +0 -3
- package/dist/types/licensing.d.ts +0 -109
- package/dist/types/licensing.js +0 -4
- package/dist/types/product-intelligence.d.ts +0 -60
- package/dist/types/product-intelligence.js +0 -3
- package/dist/types/session.d.ts +0 -84
- package/dist/types/session.js +0 -3
- package/dist/types/webhook.d.ts +0 -62
- package/dist/types/webhook.js +0 -4
|
@@ -1,65 +1,55 @@
|
|
|
1
|
+
/* eslint-disable max-lines -- runner adapters and exact command evidence stay co-located for auditability */
|
|
2
|
+
import { isAbsolute, join, normalize, relative, sep } from 'node:path';
|
|
3
|
+
import { PROCESS_TERMINATION_GRACE_MS, runAbortableProcess } from '../abortable-process-runner.js';
|
|
4
|
+
import { remainingExecutionMs } from '../execution/context.js';
|
|
5
|
+
import { getRuntimePolicy } from '../runtime-policy.js';
|
|
6
|
+
import { resolveContainedProjectFile } from '../safety/contained-project-file.js';
|
|
1
7
|
import { technologyValue } from '../technology-registry.js';
|
|
2
|
-
// engine/validator/spec-compliance-runner.ts — SPEC-732: vitest-backed spec-compliance runner
|
|
3
|
-
//
|
|
4
|
-
// Replaces the text-matching approach in validateSpec with actual test execution.
|
|
5
|
-
// Parses the `tests` array from each frontmatter scenario, spawns vitest, and
|
|
6
|
-
// produces a per-scenario verdict based on real test outcomes.
|
|
7
|
-
import { access } from 'node:fs/promises';
|
|
8
|
-
import { join } from 'node:path';
|
|
9
8
|
import { readMatchingValidationEvidence } from '../validation-evidence-ledger.js';
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
* done: false
|
|
23
|
-
* tests:
|
|
24
|
-
* - path: tests/foo.test.ts
|
|
25
|
-
* line: 42
|
|
26
|
-
* ```
|
|
27
|
-
*/
|
|
28
|
-
// eslint-disable-next-line complexity, max-lines-per-function -- YAML frontmatter has many structural variants; each branch handles a distinct schema shape (object + string test forms — SPEC-777)
|
|
9
|
+
import { readEvidenceArtifacts, traceabilityRowHasCurrentCommandEvidence, } from '../evidence-gates/artifact-reader.js';
|
|
10
|
+
import { normalizeCriterionText } from '../criterion-identity.js';
|
|
11
|
+
/** A sanitized terminal adapter failure that cannot be mistaken for a failed test verdict. */
|
|
12
|
+
export class ComplianceCommandTerminalError extends Error {
|
|
13
|
+
code;
|
|
14
|
+
constructor(code) {
|
|
15
|
+
super(`[Planu] ${code}`);
|
|
16
|
+
this.code = code;
|
|
17
|
+
this.name = 'ComplianceCommandTerminalError';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
// eslint-disable-next-line complexity, max-lines-per-function -- parses the supported scenario YAML subset without executing it
|
|
29
21
|
export function parseFrontmatterScenarios(raw) {
|
|
30
|
-
const
|
|
31
|
-
const match = FRONTMATTER_BLOCK_RE.exec(raw);
|
|
22
|
+
const match = /^---\n([\s\S]*?)\n---\n/.exec(raw);
|
|
32
23
|
if (!match?.[1]) {
|
|
33
24
|
return [];
|
|
34
25
|
}
|
|
35
|
-
const block = match[1];
|
|
36
|
-
const lines = block.split('\n');
|
|
37
26
|
const scenarios = [];
|
|
38
27
|
let inScenarios = false;
|
|
39
28
|
let currentScenario = null;
|
|
40
29
|
let inTests = false;
|
|
41
|
-
|
|
30
|
+
let inSteps = false;
|
|
31
|
+
let currentStep;
|
|
32
|
+
for (const line of match[1].split('\n')) {
|
|
42
33
|
const trimmed = line.trimEnd();
|
|
43
|
-
// Top-level `scenarios:` key
|
|
44
34
|
if (/^scenarios:\s*$/.test(trimmed)) {
|
|
45
35
|
inScenarios = true;
|
|
46
36
|
inTests = false;
|
|
37
|
+
inSteps = false;
|
|
47
38
|
continue;
|
|
48
39
|
}
|
|
49
|
-
// Another top-level key — exit scenarios block
|
|
50
40
|
if (inScenarios && /^\S/.test(trimmed) && !trimmed.startsWith('scenarios:')) {
|
|
51
41
|
if (currentScenario) {
|
|
52
42
|
scenarios.push(currentScenario);
|
|
53
|
-
currentScenario = null;
|
|
54
43
|
}
|
|
44
|
+
currentScenario = null;
|
|
55
45
|
inScenarios = false;
|
|
56
46
|
inTests = false;
|
|
47
|
+
inSteps = false;
|
|
57
48
|
continue;
|
|
58
49
|
}
|
|
59
50
|
if (!inScenarios) {
|
|
60
51
|
continue;
|
|
61
52
|
}
|
|
62
|
-
// New scenario entry ` - title: ...`
|
|
63
53
|
const titleMatch = /^\s{2}-\s+title:\s*["']?(.+?)["']?\s*$/.exec(trimmed);
|
|
64
54
|
if (titleMatch?.[1]) {
|
|
65
55
|
if (currentScenario) {
|
|
@@ -67,53 +57,75 @@ export function parseFrontmatterScenarios(raw) {
|
|
|
67
57
|
}
|
|
68
58
|
currentScenario = { title: titleMatch[1], done: false };
|
|
69
59
|
inTests = false;
|
|
60
|
+
inSteps = false;
|
|
70
61
|
continue;
|
|
71
62
|
}
|
|
72
63
|
if (!currentScenario) {
|
|
73
64
|
continue;
|
|
74
65
|
}
|
|
75
|
-
// ` done: true/false`
|
|
76
66
|
const doneMatch = /^\s+done:\s*(true|false)\s*$/.exec(trimmed);
|
|
77
67
|
if (doneMatch?.[1]) {
|
|
78
68
|
currentScenario.done = doneMatch[1] === 'true';
|
|
79
69
|
continue;
|
|
80
70
|
}
|
|
81
|
-
// ` tests:` sub-key
|
|
82
71
|
if (/^\s+tests:\s*$/.test(trimmed)) {
|
|
83
|
-
currentScenario.tests
|
|
72
|
+
currentScenario.tests ??= [];
|
|
84
73
|
inTests = true;
|
|
74
|
+
inSteps = false;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (/^\s+steps:\s*$/.test(trimmed)) {
|
|
78
|
+
currentScenario.steps ??= [];
|
|
79
|
+
inSteps = true;
|
|
80
|
+
inTests = false;
|
|
81
|
+
currentStep = undefined;
|
|
85
82
|
continue;
|
|
86
83
|
}
|
|
87
|
-
if (
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
currentScenario.
|
|
93
|
-
currentScenario.tests.push(testLink);
|
|
84
|
+
if (inSteps) {
|
|
85
|
+
const keywordMatch = /^\s+-\s+keyword:\s*["']?(.+?)["']?\s*$/.exec(trimmed);
|
|
86
|
+
if (keywordMatch?.[1]) {
|
|
87
|
+
currentStep = { keyword: keywordMatch[1], text: '' };
|
|
88
|
+
currentScenario.steps ??= [];
|
|
89
|
+
currentScenario.steps.push(currentStep);
|
|
94
90
|
continue;
|
|
95
91
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const testLink = { path: stringMatch[1] };
|
|
100
|
-
currentScenario.tests ??= [];
|
|
101
|
-
currentScenario.tests.push(testLink);
|
|
92
|
+
const textMatch = /^\s+text:\s*["']?(.+?)["']?\s*$/.exec(trimmed);
|
|
93
|
+
if (textMatch?.[1] && currentStep) {
|
|
94
|
+
currentStep.text = textMatch[1];
|
|
102
95
|
continue;
|
|
103
96
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
97
|
+
}
|
|
98
|
+
if (!inTests) {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
const pathMatch = /^\s+-\s+path:\s*["']?(.+?)["']?\s*$/.exec(trimmed);
|
|
102
|
+
if (pathMatch?.[1]) {
|
|
103
|
+
currentScenario.tests ??= [];
|
|
104
|
+
currentScenario.tests.push({ path: pathMatch[1] });
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const stringMatch = /^\s+-\s+["']?([^'":\s][^'"]*?)["']?\s*$/.exec(trimmed);
|
|
108
|
+
if (stringMatch?.[1] && !stringMatch[1].includes(':')) {
|
|
109
|
+
currentScenario.tests ??= [];
|
|
110
|
+
currentScenario.tests.push({ path: stringMatch[1] });
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
const metadataMatch = /^\s+(line|runner|config):\s*["']?(.+?)["']?\s*$/.exec(trimmed);
|
|
114
|
+
const last = currentScenario.tests?.at(-1);
|
|
115
|
+
if (metadataMatch?.[1] && metadataMatch[2] && last) {
|
|
116
|
+
if (metadataMatch[1] === 'line' && /^\d+$/.test(metadataMatch[2])) {
|
|
117
|
+
last.line = Number.parseInt(metadataMatch[2], 10);
|
|
112
118
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
inTests = false;
|
|
119
|
+
else if (metadataMatch[1] === 'runner') {
|
|
120
|
+
last.runner = metadataMatch[2];
|
|
116
121
|
}
|
|
122
|
+
else if (metadataMatch[1] === 'config') {
|
|
123
|
+
last.config = metadataMatch[2];
|
|
124
|
+
}
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (/^\s{4}\S/.test(line)) {
|
|
128
|
+
inTests = false;
|
|
117
129
|
}
|
|
118
130
|
}
|
|
119
131
|
if (currentScenario) {
|
|
@@ -121,63 +133,291 @@ export function parseFrontmatterScenarios(raw) {
|
|
|
121
133
|
}
|
|
122
134
|
return scenarios;
|
|
123
135
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
136
|
+
function validRelativePath(candidate) {
|
|
137
|
+
if (candidate.trim() !== candidate ||
|
|
138
|
+
candidate.length === 0 ||
|
|
139
|
+
isAbsolute(candidate) ||
|
|
140
|
+
candidate.includes('\\') ||
|
|
141
|
+
normalize(candidate) !== candidate) {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
return !candidate.split('/').some((segment) => segment === '..' || segment.startsWith('-'));
|
|
145
|
+
}
|
|
146
|
+
async function resolveSafeFile(projectPath, candidate) {
|
|
147
|
+
if (!validRelativePath(candidate)) {
|
|
148
|
+
throw new Error('unsafe project-relative path');
|
|
149
|
+
}
|
|
150
|
+
const physical = await resolveContainedProjectFile(projectPath, join(projectPath, candidate));
|
|
151
|
+
const relativePath = relative(projectPath, physical);
|
|
152
|
+
if (relativePath === '..' || relativePath.startsWith(`..${sep}`) || isAbsolute(relativePath)) {
|
|
153
|
+
throw new Error('path escapes project');
|
|
154
|
+
}
|
|
155
|
+
return physical;
|
|
156
|
+
}
|
|
157
|
+
async function resolveReportedFile(projectPath, candidate) {
|
|
158
|
+
if (candidate.trim() !== candidate || candidate.length === 0 || candidate.includes('\\')) {
|
|
159
|
+
throw new Error('unsafe reported path');
|
|
160
|
+
}
|
|
161
|
+
const physical = await resolveContainedProjectFile(projectPath, isAbsolute(candidate) ? candidate : join(projectPath, candidate));
|
|
162
|
+
const pathFromRoot = relative(projectPath, physical);
|
|
163
|
+
if (pathFromRoot === '..' || pathFromRoot.startsWith(`..${sep}`) || isAbsolute(pathFromRoot)) {
|
|
164
|
+
throw new Error('reported path escapes project');
|
|
165
|
+
}
|
|
166
|
+
return physical;
|
|
167
|
+
}
|
|
168
|
+
async function matchPlaywrightLink(reportedPath, links, projectPath) {
|
|
169
|
+
if (isAbsolute(reportedPath)) {
|
|
170
|
+
const physical = await resolveReportedFile(projectPath, reportedPath);
|
|
171
|
+
const candidates = links.filter((link) => link.physicalPath === physical);
|
|
172
|
+
return candidates.length === 1 ? (candidates[0] ?? null) : null;
|
|
173
|
+
}
|
|
174
|
+
if (!validRelativePath(reportedPath)) {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
const candidates = links.filter((link) => link.path === reportedPath || link.path.endsWith(`/${reportedPath}`));
|
|
178
|
+
return candidates.length === 1 ? (candidates[0] ?? null) : null;
|
|
179
|
+
}
|
|
180
|
+
function parseJsonOutput(stdout) {
|
|
181
|
+
const jsonStart = stdout.indexOf('{');
|
|
182
|
+
if (jsonStart === -1) {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
try {
|
|
186
|
+
return JSON.parse(stdout.slice(jsonStart));
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function vitestFilePath(result) {
|
|
193
|
+
for (const candidate of [result.testFilePath, result.name, result.filepath, result.filePath]) {
|
|
194
|
+
if (typeof candidate === 'string' && candidate.trim().length > 0) {
|
|
195
|
+
return candidate;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
function effectiveComplianceTimeoutMs() {
|
|
201
|
+
const policy = getRuntimePolicy().validation;
|
|
202
|
+
const availableMs = remainingExecutionMs() - policy.receiptPublicationDeadlineMs - PROCESS_TERMINATION_GRACE_MS;
|
|
203
|
+
if (availableMs <= 0) {
|
|
204
|
+
throw new ComplianceCommandTerminalError('COMPLIANCE_COMMAND_DEADLINE_EXHAUSTED');
|
|
205
|
+
}
|
|
206
|
+
return Math.min(policy.complianceCommandTimeoutMs, availableMs);
|
|
207
|
+
}
|
|
208
|
+
function throwForTerminalProcessFailure(result) {
|
|
209
|
+
if (result.aborted) {
|
|
210
|
+
throw new ComplianceCommandTerminalError('COMPLIANCE_COMMAND_ABORTED');
|
|
211
|
+
}
|
|
212
|
+
if (result.timedOut) {
|
|
213
|
+
throw new ComplianceCommandTerminalError('COMPLIANCE_COMMAND_TIMED_OUT');
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
async function runVitestAdapter(links, projectPath, signal) {
|
|
217
|
+
const statuses = new Map();
|
|
218
|
+
const result = await runAbortableProcess('pnpm', [
|
|
130
219
|
'exec',
|
|
131
220
|
technologyValue('technology-vitest-a9127f'),
|
|
132
221
|
'--reporter=json',
|
|
133
222
|
'--run',
|
|
134
|
-
...
|
|
135
|
-
]
|
|
136
|
-
return runAbortableProcess('pnpm', args, {
|
|
223
|
+
...links.map((link) => link.path),
|
|
224
|
+
], {
|
|
137
225
|
cwd: projectPath,
|
|
138
|
-
timeoutMs,
|
|
226
|
+
timeoutMs: effectiveComplianceTimeoutMs(),
|
|
139
227
|
maxBufferBytes: 64 * 1024 * 1024,
|
|
140
228
|
signal,
|
|
141
|
-
})
|
|
142
|
-
|
|
143
|
-
|
|
229
|
+
});
|
|
230
|
+
throwForTerminalProcessFailure(result);
|
|
231
|
+
if (result.error) {
|
|
232
|
+
return { statuses, detail: 'runner failed' };
|
|
233
|
+
}
|
|
234
|
+
const report = parseJsonOutput(result.stdout);
|
|
235
|
+
if (!report || !Array.isArray(report.testResults)) {
|
|
236
|
+
return { statuses, detail: 'unparseable runner output' };
|
|
237
|
+
}
|
|
238
|
+
for (const testResult of report.testResults) {
|
|
239
|
+
const reportedPath = vitestFilePath(testResult);
|
|
240
|
+
if (!reportedPath || !Array.isArray(testResult.assertionResults)) {
|
|
241
|
+
continue;
|
|
144
242
|
}
|
|
243
|
+
const relativePath = isAbsolute(reportedPath)
|
|
244
|
+
? relative(projectPath, reportedPath)
|
|
245
|
+
: reportedPath;
|
|
246
|
+
if (!links.some((link) => link.path === relativePath)) {
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
const assertions = testResult.assertionResults;
|
|
250
|
+
statuses.set(relativePath, assertions.length > 0 && assertions.every((item) => item.status === 'passed')
|
|
251
|
+
? 'pass'
|
|
252
|
+
: 'fail');
|
|
253
|
+
}
|
|
254
|
+
return { statuses, detail: 'vitest JSON' };
|
|
255
|
+
}
|
|
256
|
+
function collectPlaywrightFiles(suites, output = new Map()) {
|
|
257
|
+
for (const suite of suites) {
|
|
258
|
+
if (suite.file) {
|
|
259
|
+
const statuses = output.get(suite.file) ?? [];
|
|
260
|
+
for (const spec of suite.specs ?? []) {
|
|
261
|
+
for (const test of spec.tests ?? []) {
|
|
262
|
+
for (const result of test.results ?? []) {
|
|
263
|
+
if (typeof result.status === 'string') {
|
|
264
|
+
statuses.push(result.status);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
output.set(suite.file, statuses);
|
|
270
|
+
}
|
|
271
|
+
collectPlaywrightFiles(suite.suites ?? [], output);
|
|
272
|
+
}
|
|
273
|
+
return output;
|
|
274
|
+
}
|
|
275
|
+
async function runPlaywrightAdapter(links, projectPath, signal) {
|
|
276
|
+
const statuses = new Map();
|
|
277
|
+
const config = links[0]?.config;
|
|
278
|
+
if (!config) {
|
|
279
|
+
return { statuses, detail: 'missing config' };
|
|
280
|
+
}
|
|
281
|
+
const result = await runAbortableProcess('pnpm', [
|
|
282
|
+
'exec',
|
|
283
|
+
technologyValue('technology-playwright-7827e8'),
|
|
284
|
+
'test',
|
|
285
|
+
'--config',
|
|
286
|
+
config,
|
|
287
|
+
'--reporter=json',
|
|
288
|
+
...links.map((link) => link.path),
|
|
289
|
+
], {
|
|
290
|
+
cwd: projectPath,
|
|
291
|
+
timeoutMs: effectiveComplianceTimeoutMs(),
|
|
292
|
+
maxBufferBytes: 64 * 1024 * 1024,
|
|
293
|
+
signal,
|
|
294
|
+
});
|
|
295
|
+
throwForTerminalProcessFailure(result);
|
|
296
|
+
if (result.error) {
|
|
297
|
+
return { statuses, detail: 'runner failed' };
|
|
298
|
+
}
|
|
299
|
+
const report = parseJsonOutput(result.stdout);
|
|
300
|
+
if (!report || !Array.isArray(report.suites)) {
|
|
301
|
+
return { statuses, detail: 'unparseable runner output' };
|
|
302
|
+
}
|
|
303
|
+
const reportedFiles = collectPlaywrightFiles(report.suites);
|
|
304
|
+
for (const [reportedPath, testStatuses] of reportedFiles) {
|
|
145
305
|
try {
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
306
|
+
const link = await matchPlaywrightLink(reportedPath, links, projectPath);
|
|
307
|
+
if (!link) {
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
statuses.set(link.path, testStatuses.length === 0
|
|
311
|
+
? 'missing'
|
|
312
|
+
: testStatuses.every((status) => status === 'passed')
|
|
313
|
+
? 'pass'
|
|
314
|
+
: 'fail');
|
|
150
315
|
}
|
|
151
316
|
catch {
|
|
152
|
-
|
|
317
|
+
// Untrusted reporter paths never become evidence.
|
|
153
318
|
}
|
|
154
|
-
}
|
|
319
|
+
}
|
|
320
|
+
return { statuses, detail: 'playwright JSON' };
|
|
155
321
|
}
|
|
156
|
-
function
|
|
157
|
-
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
322
|
+
async function resolveLink(link, projectPath) {
|
|
323
|
+
const runner = link.runner ?? technologyValue('technology-vitest-a9127f');
|
|
324
|
+
const base = {
|
|
325
|
+
path: link.path,
|
|
326
|
+
physicalPath: '',
|
|
327
|
+
runner,
|
|
328
|
+
...(link.config ? { config: link.config } : {}),
|
|
329
|
+
};
|
|
330
|
+
if (runner !== technologyValue('technology-vitest-a9127f') &&
|
|
331
|
+
runner !== technologyValue('technology-playwright-7827e8')) {
|
|
332
|
+
return { ...base, error: `unsupported runner: ${runner}` };
|
|
333
|
+
}
|
|
334
|
+
if (runner === technologyValue('technology-vitest-a9127f') && link.config) {
|
|
335
|
+
return { ...base, error: `${runner} links do not accept config metadata` };
|
|
336
|
+
}
|
|
337
|
+
if (runner === technologyValue('technology-playwright-7827e8') && !link.config) {
|
|
338
|
+
return { ...base, error: `${runner} links require config metadata` };
|
|
339
|
+
}
|
|
340
|
+
try {
|
|
341
|
+
const physicalPath = await resolveSafeFile(projectPath, link.path);
|
|
342
|
+
if (link.config) {
|
|
343
|
+
await resolveSafeFile(projectPath, link.config);
|
|
162
344
|
}
|
|
345
|
+
return { ...base, physicalPath };
|
|
163
346
|
}
|
|
164
|
-
|
|
347
|
+
catch (error) {
|
|
348
|
+
const message = error.code === 'ENOENT'
|
|
349
|
+
? 'file not found'
|
|
350
|
+
: error instanceof Error
|
|
351
|
+
? error.message
|
|
352
|
+
: 'unsafe or missing path';
|
|
353
|
+
return { ...base, error: message };
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
function groupKey(link) {
|
|
357
|
+
return `${link.runner}\0${link.config ?? ''}`;
|
|
358
|
+
}
|
|
359
|
+
const EXECUTABLE_TEST_FILE = /(?:^|\/)[^/]+\.(?:test|spec)\.(?:[cm]?[jt]sx?)$/u;
|
|
360
|
+
function isExecutableTestLink(link) {
|
|
361
|
+
return link.runner !== undefined || EXECUTABLE_TEST_FILE.test(link.path);
|
|
362
|
+
}
|
|
363
|
+
function normalizedEvidencePath(path) {
|
|
364
|
+
return path.trim().replaceAll('\\', '/').replace(/^\.\//u, '');
|
|
365
|
+
}
|
|
366
|
+
function commandEvidenceCoversPath(row, path) {
|
|
367
|
+
return (row?.commandEvidence?.relevantPaths
|
|
368
|
+
?.map(normalizedEvidencePath)
|
|
369
|
+
.includes(normalizedEvidencePath(path)) === true);
|
|
165
370
|
}
|
|
166
|
-
function
|
|
167
|
-
const
|
|
168
|
-
return
|
|
371
|
+
function canonicalScenarioCriterion(scenario) {
|
|
372
|
+
const steps = scenario.steps ?? [];
|
|
373
|
+
return steps.length > 0
|
|
374
|
+
? steps.map((step) => `${step.keyword.toUpperCase()} ${step.text}`).join(' ')
|
|
375
|
+
: undefined;
|
|
169
376
|
}
|
|
170
|
-
function
|
|
171
|
-
|
|
377
|
+
function resolveExactCommandRow(exactRows, scenario) {
|
|
378
|
+
const identities = new Set([scenario.title, canonicalScenarioCriterion(scenario)]
|
|
379
|
+
.filter((value) => typeof value === 'string')
|
|
380
|
+
.map(normalizeCriterionText)
|
|
381
|
+
.filter(Boolean));
|
|
382
|
+
let resolved;
|
|
383
|
+
for (const identity of identities) {
|
|
384
|
+
if (!exactRows.has(identity)) {
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
const candidate = exactRows.get(identity);
|
|
388
|
+
if (candidate === undefined || (resolved !== undefined && resolved !== candidate)) {
|
|
389
|
+
return undefined;
|
|
390
|
+
}
|
|
391
|
+
resolved = candidate;
|
|
392
|
+
}
|
|
393
|
+
return resolved;
|
|
394
|
+
}
|
|
395
|
+
async function loadExactCommandRows(spec, projectPath, canonicalProjectId) {
|
|
396
|
+
const exactRows = new Map();
|
|
397
|
+
if (!canonicalProjectId) {
|
|
398
|
+
return exactRows;
|
|
399
|
+
}
|
|
400
|
+
const artifacts = await readEvidenceArtifacts({
|
|
401
|
+
spec,
|
|
402
|
+
projectId: canonicalProjectId,
|
|
403
|
+
specId: spec.id,
|
|
404
|
+
projectPath,
|
|
405
|
+
}).catch(() => undefined);
|
|
406
|
+
for (const row of artifacts?.traceabilityMatrix?.rows ?? []) {
|
|
407
|
+
if (!traceabilityRowHasCurrentCommandEvidence(row, { projectId: canonicalProjectId, projectPath })) {
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
const identities = [row.acceptanceCriterion, row.scenario]
|
|
411
|
+
.filter((value) => typeof value === 'string')
|
|
412
|
+
.map(normalizeCriterionText)
|
|
413
|
+
.filter(Boolean);
|
|
414
|
+
for (const identity of new Set(identities)) {
|
|
415
|
+
exactRows.set(identity, exactRows.has(identity) && exactRows.get(identity) !== row ? undefined : row);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return exactRows;
|
|
172
419
|
}
|
|
173
|
-
//
|
|
174
|
-
// Public API
|
|
175
|
-
// ---------------------------------------------------------------------------
|
|
176
|
-
/**
|
|
177
|
-
* Run vitest-backed spec-compliance for the given spec.
|
|
178
|
-
* Each frontmatter scenario must have a `tests` array; missing links count as `fail`.
|
|
179
|
-
*/
|
|
180
|
-
// eslint-disable-next-line max-lines-per-function, complexity -- spec compliance runner: each section handles a discrete verification phase (file access, scenario parsing, test execution, verdict aggregation)
|
|
420
|
+
// eslint-disable-next-line max-lines-per-function -- coordinates validation, adapters, and isolated per-scenario evidence
|
|
181
421
|
export async function runSpecCompliance(spec, projectPath, signal, canonicalProjectId) {
|
|
182
422
|
const { readFile } = await import('node:fs/promises');
|
|
183
423
|
let raw;
|
|
@@ -185,41 +425,41 @@ export async function runSpecCompliance(spec, projectPath, signal, canonicalProj
|
|
|
185
425
|
raw = await readFile(spec.specPath, 'utf-8');
|
|
186
426
|
}
|
|
187
427
|
catch {
|
|
188
|
-
// No spec file — return empty compliance
|
|
189
428
|
return { dimensionScore: 0, perScenario: [], command: '' };
|
|
190
429
|
}
|
|
191
430
|
const scenarios = parseFrontmatterScenarios(raw);
|
|
192
431
|
if (scenarios.length === 0) {
|
|
193
432
|
return { dimensionScore: 0, perScenario: [], command: '' };
|
|
194
433
|
}
|
|
195
|
-
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
const command = allTestPaths.size > 0
|
|
206
|
-
? `vitest --reporter=json --run ${[...allTestPaths].join(' ')}`
|
|
207
|
-
: 'vitest --reporter=json --run (no test files linked)';
|
|
208
|
-
// ── Phase 2: check file existence ────────────────────────────────────────
|
|
209
|
-
const existenceMap = new Map();
|
|
210
|
-
for (const p of allTestPaths) {
|
|
211
|
-
const fullPath = join(projectPath, p);
|
|
434
|
+
const allLinks = scenarios.flatMap((scenario) => scenario.tests ?? []);
|
|
435
|
+
const executableLinks = allLinks.filter(isExecutableTestLink);
|
|
436
|
+
const commandLinks = allLinks.filter((link) => !isExecutableTestLink(link));
|
|
437
|
+
const resolvedLinks = await Promise.all(executableLinks.map((link) => resolveLink(link, projectPath)));
|
|
438
|
+
const resolvedBySource = new Map(executableLinks.map((link, index) => [link, resolvedLinks[index]]));
|
|
439
|
+
const commandLinkErrors = new Map();
|
|
440
|
+
await Promise.all(commandLinks.map(async (link) => {
|
|
212
441
|
try {
|
|
213
|
-
await
|
|
214
|
-
existenceMap.set(p, true);
|
|
442
|
+
await resolveSafeFile(projectPath, link.path);
|
|
215
443
|
}
|
|
216
|
-
catch {
|
|
217
|
-
|
|
444
|
+
catch (error) {
|
|
445
|
+
commandLinkErrors.set(link, error.code === 'ENOENT'
|
|
446
|
+
? 'file not found'
|
|
447
|
+
: error instanceof Error
|
|
448
|
+
? error.message
|
|
449
|
+
: 'unsafe or missing path');
|
|
218
450
|
}
|
|
451
|
+
}));
|
|
452
|
+
const exactCommandRows = await loadExactCommandRows(spec, projectPath, canonicalProjectId);
|
|
453
|
+
const validLinks = resolvedLinks.filter((link) => !link.error);
|
|
454
|
+
const canonicalLinks = new Map();
|
|
455
|
+
for (const link of validLinks) {
|
|
456
|
+
canonicalLinks.set(`${groupKey(link)}\0${link.path}\0${link.physicalPath}`, link);
|
|
457
|
+
}
|
|
458
|
+
const groups = new Map();
|
|
459
|
+
for (const link of canonicalLinks.values()) {
|
|
460
|
+
groups.set(groupKey(link), [...(groups.get(groupKey(link)) ?? []), link]);
|
|
219
461
|
}
|
|
220
|
-
|
|
221
|
-
const existingPaths = [...allTestPaths].filter((p) => existenceMap.get(p) === true);
|
|
222
|
-
let vitestResult = null;
|
|
462
|
+
const statusByGroup = new Map();
|
|
223
463
|
const fullTestReceipt = canonicalProjectId
|
|
224
464
|
? readMatchingValidationEvidence(canonicalProjectId, {
|
|
225
465
|
projectPath,
|
|
@@ -228,98 +468,76 @@ export async function runSpecCompliance(spec, projectPath, signal, canonicalProj
|
|
|
228
468
|
scope: ['src', 'tests'],
|
|
229
469
|
})
|
|
230
470
|
: null;
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
for (const path of existingPaths) {
|
|
238
|
-
testFileStatus.set(path, 'pass');
|
|
471
|
+
for (const [key, links] of groups) {
|
|
472
|
+
if (links[0]?.runner === technologyValue('technology-vitest-a9127f') && fullTestReceipt) {
|
|
473
|
+
statusByGroup.set(key, {
|
|
474
|
+
statuses: new Map(links.map((link) => [link.path, 'pass'])),
|
|
475
|
+
detail: 'matching full-test receipt',
|
|
476
|
+
});
|
|
239
477
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
for (const tr of vitestResult.testResults) {
|
|
243
|
-
const testFilePath = resolveVitestTestFilePath(tr);
|
|
244
|
-
if (!testFilePath) {
|
|
245
|
-
continue;
|
|
246
|
-
}
|
|
247
|
-
const allPassed = tr.assertionResults.every((r) => r.status === 'passed');
|
|
248
|
-
testFileStatus.set(testFilePath, allPassed ? 'pass' : 'fail');
|
|
249
|
-
// Also index by relative path
|
|
250
|
-
const relPath = toProjectRelativeTestPath(testFilePath, projectPath);
|
|
251
|
-
testFileStatus.set(relPath, allPassed ? 'pass' : 'fail');
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
// ── Phase 4: build per-scenario verdicts ─────────────────────────────────
|
|
255
|
-
const perScenario = [];
|
|
256
|
-
let passCount = 0;
|
|
257
|
-
for (const scenario of scenarios) {
|
|
258
|
-
const links = scenario.tests ?? [];
|
|
259
|
-
const evidence = [];
|
|
260
|
-
let scenarioVerdict;
|
|
261
|
-
if (links.length === 0) {
|
|
262
|
-
evidence.push('No test links — scenario has no tests array');
|
|
263
|
-
scenarioVerdict = 'missing';
|
|
478
|
+
else if (links[0]?.runner === technologyValue('technology-vitest-a9127f')) {
|
|
479
|
+
statusByGroup.set(key, await runVitestAdapter(links, projectPath, signal));
|
|
264
480
|
}
|
|
265
481
|
else {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
verdicts.push('fail');
|
|
482
|
+
statusByGroup.set(key, await runPlaywrightAdapter(links, projectPath, signal));
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
const commands = [...groups.values()].map((links) => {
|
|
486
|
+
const first = links[0];
|
|
487
|
+
if (first?.runner === technologyValue('technology-playwright-7827e8')) {
|
|
488
|
+
return `${first.runner} test --config ${first.config ?? ''} --reporter=json ${links.map((link) => link.path).join(' ')}`;
|
|
489
|
+
}
|
|
490
|
+
return `${technologyValue('technology-vitest-a9127f')} --reporter=json --run ${links.map((link) => link.path).join(' ')}`;
|
|
491
|
+
});
|
|
492
|
+
const perScenario = scenarios.map((scenario) => {
|
|
493
|
+
if (!scenario.tests?.length) {
|
|
494
|
+
return {
|
|
495
|
+
title: scenario.title,
|
|
496
|
+
verdict: 'missing',
|
|
497
|
+
evidence: ['No test links — scenario has no tests array'],
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
const evidence = [];
|
|
501
|
+
const commandRow = resolveExactCommandRow(exactCommandRows, scenario);
|
|
502
|
+
const verdicts = scenario.tests.map((source) => {
|
|
503
|
+
if (!isExecutableTestLink(source)) {
|
|
504
|
+
const error = commandLinkErrors.get(source);
|
|
505
|
+
if (error) {
|
|
506
|
+
evidence.push(`${source.path || '(missing path)'}: ${error}`);
|
|
507
|
+
return 'missing';
|
|
293
508
|
}
|
|
509
|
+
const verdict = commandEvidenceCoversPath(commandRow, source.path) ? 'pass' : 'missing';
|
|
510
|
+
evidence.push(`${source.path}: ${verdict === 'pass' ? 'passed by current command evidence' : 'missing current command evidence'}`);
|
|
511
|
+
return verdict;
|
|
294
512
|
}
|
|
295
|
-
|
|
296
|
-
if (
|
|
297
|
-
|
|
513
|
+
const link = resolvedBySource.get(source);
|
|
514
|
+
if (!link || link.error) {
|
|
515
|
+
evidence.push(`${source.path || '(missing path)'}: ${link?.error ?? 'malformed test link'}`);
|
|
516
|
+
return 'missing';
|
|
298
517
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
}
|
|
311
|
-
const
|
|
312
|
-
return {
|
|
518
|
+
const adapter = statusByGroup.get(groupKey(link));
|
|
519
|
+
const verdict = adapter?.statuses.get(link.path) ?? 'missing';
|
|
520
|
+
evidence.push(`${link.path}: ${verdict === 'pass' ? 'passed' : verdict === 'fail' ? 'failed' : `not found in ${adapter?.detail ?? 'runner output'}`}`);
|
|
521
|
+
return verdict;
|
|
522
|
+
});
|
|
523
|
+
const verdict = verdicts.every((item) => item === 'pass')
|
|
524
|
+
? 'pass'
|
|
525
|
+
: verdicts.some((item) => item === 'missing')
|
|
526
|
+
? 'missing'
|
|
527
|
+
: 'fail';
|
|
528
|
+
return { title: scenario.title, verdict, evidence };
|
|
529
|
+
});
|
|
530
|
+
const passCount = perScenario.filter((scenario) => scenario.verdict === 'pass').length;
|
|
531
|
+
return {
|
|
532
|
+
dimensionScore: Math.round((passCount / scenarios.length) * 100),
|
|
533
|
+
perScenario,
|
|
534
|
+
command: commands.join(' && ') ||
|
|
535
|
+
`${technologyValue('technology-vitest-a9127f')} --reporter=json --run (no test files linked)`,
|
|
536
|
+
};
|
|
313
537
|
}
|
|
314
|
-
// ---------------------------------------------------------------------------
|
|
315
|
-
// check_readiness gate: scenarios_missing_tests
|
|
316
|
-
// ---------------------------------------------------------------------------
|
|
317
|
-
/**
|
|
318
|
-
* Returns a list of scenario titles that lack a `tests` array.
|
|
319
|
-
* Used by check-readiness to block `approved` when scenarios are untested.
|
|
320
|
-
*/
|
|
321
538
|
export function findScenariosWithoutTests(raw) {
|
|
322
|
-
|
|
323
|
-
|
|
539
|
+
return parseFrontmatterScenarios(raw)
|
|
540
|
+
.filter((scenario) => !scenario.tests?.length)
|
|
541
|
+
.map((scenario) => scenario.title);
|
|
324
542
|
}
|
|
325
543
|
//# sourceMappingURL=spec-compliance-runner.js.map
|