@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,12 +1,12 @@
|
|
|
1
1
|
import { parseFrontmatterScenarios } from '../validator/spec-compliance-runner.js';
|
|
2
2
|
import { stripFrontmatter } from '../frontmatter-parser.js';
|
|
3
|
-
import { extractListItems, extractSection } from '
|
|
3
|
+
import { extractListItems, extractSection } from './markdown-sections.js';
|
|
4
4
|
import { createCriterionIdentity, normalizeCriterionText } from '../criterion-identity.js';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
const BDD_STEP = /^(GIVEN|WHEN|THEN|AND|DADO|CUANDO|ENTONCES|Y)\b/i;
|
|
6
|
+
const BDD_START = /^(GIVEN|DADO)\b/i;
|
|
7
|
+
const TECHNICAL_MARKER = /^(FILES|FUNCTIONS|TEST|OWNER):/i;
|
|
8
|
+
const EXPLICIT_CRITERION_HEADING = /^#{3,6}\s+((?:AC|AB)\s*-?\s*\d+)\b/i;
|
|
9
|
+
function extractBddBlocks(section) {
|
|
10
10
|
const lines = section.split('\n').map((line) => line.trim());
|
|
11
11
|
if (lines.length === 0) {
|
|
12
12
|
return [];
|
|
@@ -21,16 +21,21 @@ function extractSectionCriteria(section) {
|
|
|
21
21
|
}
|
|
22
22
|
continue;
|
|
23
23
|
}
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
const withoutListMarker = line.replace(/^(?:[-*+]\s+(?:\[[ xX]\]\s*)?|\d+[.)]\s+)/, '');
|
|
25
|
+
if (TECHNICAL_MARKER.test(withoutListMarker) || /^#{1,6}\s+/.test(withoutListMarker)) {
|
|
26
|
+
if (current.length > 0) {
|
|
26
27
|
bddBlocks.push(current.join(' '));
|
|
27
28
|
current = [];
|
|
28
29
|
}
|
|
29
|
-
current.push(line.replace(/^[-*+]\s*/, ''));
|
|
30
30
|
continue;
|
|
31
31
|
}
|
|
32
|
-
if (
|
|
33
|
-
|
|
32
|
+
if (BDD_STEP.test(withoutListMarker)) {
|
|
33
|
+
if (BDD_START.test(withoutListMarker) && current.length > 0) {
|
|
34
|
+
bddBlocks.push(current.join(' '));
|
|
35
|
+
current = [];
|
|
36
|
+
}
|
|
37
|
+
current.push(withoutListMarker);
|
|
38
|
+
continue;
|
|
34
39
|
}
|
|
35
40
|
}
|
|
36
41
|
if (current.length > 0) {
|
|
@@ -38,7 +43,54 @@ function extractSectionCriteria(section) {
|
|
|
38
43
|
}
|
|
39
44
|
return bddBlocks;
|
|
40
45
|
}
|
|
41
|
-
function
|
|
46
|
+
function extractExplicitCriterionBodies(section) {
|
|
47
|
+
const lines = section.split('\n');
|
|
48
|
+
const bodies = [];
|
|
49
|
+
let current = null;
|
|
50
|
+
for (const line of lines) {
|
|
51
|
+
const heading = EXPLICIT_CRITERION_HEADING.exec(line.trim());
|
|
52
|
+
if (heading?.[1]) {
|
|
53
|
+
if (current !== null) {
|
|
54
|
+
bodies.push(current);
|
|
55
|
+
}
|
|
56
|
+
current = { id: heading[1].replace(/[\s-]/g, ''), body: '' };
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (current !== null) {
|
|
60
|
+
current.body += `${current.body.length > 0 ? '\n' : ''}${line}`;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (current !== null) {
|
|
64
|
+
bodies.push(current);
|
|
65
|
+
}
|
|
66
|
+
return bodies;
|
|
67
|
+
}
|
|
68
|
+
function extractOneCriterion(body) {
|
|
69
|
+
const bddBlocks = extractBddBlocks(body);
|
|
70
|
+
if (bddBlocks.length > 0) {
|
|
71
|
+
return bddBlocks.reduce((selected, candidate) => candidate.length > selected.length ? candidate : selected);
|
|
72
|
+
}
|
|
73
|
+
const listItem = extractListItems(body).find((item) => !TECHNICAL_MARKER.test(item));
|
|
74
|
+
return listItem ?? null;
|
|
75
|
+
}
|
|
76
|
+
function extractSectionCriteria(section) {
|
|
77
|
+
const explicitBodies = extractExplicitCriterionBodies(section);
|
|
78
|
+
if (explicitBodies.length > 0) {
|
|
79
|
+
return explicitBodies
|
|
80
|
+
.map(({ id, body }) => {
|
|
81
|
+
const criterion = extractOneCriterion(body);
|
|
82
|
+
return criterion === null ? null : `${id}: ${criterion}`;
|
|
83
|
+
})
|
|
84
|
+
.filter((criterion) => criterion !== null);
|
|
85
|
+
}
|
|
86
|
+
const listItems = extractListItems(section).filter((item) => !TECHNICAL_MARKER.test(item));
|
|
87
|
+
if (listItems.length > 0) {
|
|
88
|
+
const nonStepItems = listItems.filter((item) => !BDD_STEP.test(item));
|
|
89
|
+
return nonStepItems.length > 0 ? nonStepItems : extractBddBlocks(section);
|
|
90
|
+
}
|
|
91
|
+
return extractBddBlocks(section);
|
|
92
|
+
}
|
|
93
|
+
function extractBodyCriteria(raw, options) {
|
|
42
94
|
const body = stripFrontmatter(raw);
|
|
43
95
|
const acceptanceCriteria = extractSection(body, 'acceptance criteria', 'criterios de aceptaci');
|
|
44
96
|
if (acceptanceCriteria) {
|
|
@@ -47,11 +99,18 @@ function extractBodyCriteria(raw) {
|
|
|
47
99
|
return sectionCriteria;
|
|
48
100
|
}
|
|
49
101
|
}
|
|
50
|
-
|
|
102
|
+
if (options.allowLegacyBodyFallback === false) {
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
const checkboxCriteria = body
|
|
51
106
|
.split('\n')
|
|
52
107
|
.map((line) => line.trim())
|
|
53
|
-
.filter((line) => /^-\s*\[[ x]\]/i.test(line)
|
|
108
|
+
.filter((line) => /^-\s*\[[ x]\]/i.test(line))
|
|
54
109
|
.map((line) => line.replace(/^-\s*\[[ x]\]\s*/i, '').trim());
|
|
110
|
+
if (checkboxCriteria.length > 0) {
|
|
111
|
+
return checkboxCriteria;
|
|
112
|
+
}
|
|
113
|
+
return extractBddBlocks(body);
|
|
55
114
|
}
|
|
56
115
|
function buildCriterion(text, source, index) {
|
|
57
116
|
const trimmed = text.trim();
|
|
@@ -68,8 +127,8 @@ function buildCriterion(text, source, index) {
|
|
|
68
127
|
aliases: identity.aliases,
|
|
69
128
|
};
|
|
70
129
|
}
|
|
71
|
-
export function extractNormalizedAcceptanceCriteria(raw) {
|
|
72
|
-
const bodyCriteria = extractBodyCriteria(raw)
|
|
130
|
+
export function extractNormalizedAcceptanceCriteria(raw, options = {}) {
|
|
131
|
+
const bodyCriteria = extractBodyCriteria(raw, options)
|
|
73
132
|
.map((criterion, index) => buildCriterion(criterion, 'body', index))
|
|
74
133
|
.filter((criterion) => criterion !== null);
|
|
75
134
|
if (bodyCriteria.length > 0) {
|
|
@@ -79,8 +138,8 @@ export function extractNormalizedAcceptanceCriteria(raw) {
|
|
|
79
138
|
.map((scenario, index) => buildCriterion(scenario.title, 'frontmatter', index))
|
|
80
139
|
.filter((criterion) => criterion !== null);
|
|
81
140
|
}
|
|
82
|
-
export function extractAcceptanceCriteriaTexts(raw) {
|
|
83
|
-
return extractNormalizedAcceptanceCriteria(raw).map((criterion) => criterion.text);
|
|
141
|
+
export function extractAcceptanceCriteriaTexts(raw, options = {}) {
|
|
142
|
+
return extractNormalizedAcceptanceCriteria(raw, options).map((criterion) => criterion.text);
|
|
84
143
|
}
|
|
85
144
|
export function normalizeAcceptanceCriterionToken(value) {
|
|
86
145
|
return normalizeCriterionText(value);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Locate one exact markdown section without normalizing or copying source bytes. */
|
|
2
|
+
export declare function findMarkdownSectionRange(content: string, headingName: string): {
|
|
3
|
+
headingStart: number;
|
|
4
|
+
contentStart: number;
|
|
5
|
+
end: number;
|
|
6
|
+
level: number;
|
|
7
|
+
} | null;
|
|
8
|
+
/**
|
|
9
|
+
* Extract the text content under a markdown heading that matches any keyword.
|
|
10
|
+
*/
|
|
11
|
+
export declare function extractSection(content: string, ...headingKeywords: string[]): string | null;
|
|
12
|
+
/** Extract bullet and numbered-list item text from a markdown block. */
|
|
13
|
+
export declare function extractListItems(content: string): string[];
|
|
14
|
+
//# sourceMappingURL=markdown-sections.d.ts.map
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
function markdownLines(content) {
|
|
2
|
+
const lines = [];
|
|
3
|
+
let start = 0;
|
|
4
|
+
while (start < content.length) {
|
|
5
|
+
const newline = content.indexOf('\n', start);
|
|
6
|
+
const end = newline === -1 ? content.length : newline + 1;
|
|
7
|
+
const contentEnd = newline === -1
|
|
8
|
+
? end
|
|
9
|
+
: newline > start && content[newline - 1] === '\r'
|
|
10
|
+
? newline - 1
|
|
11
|
+
: newline;
|
|
12
|
+
lines.push({ start, contentEnd, end });
|
|
13
|
+
start = end;
|
|
14
|
+
}
|
|
15
|
+
return lines;
|
|
16
|
+
}
|
|
17
|
+
/** Locate one exact markdown section without normalizing or copying source bytes. */
|
|
18
|
+
export function findMarkdownSectionRange(content, headingName) {
|
|
19
|
+
let selected = null;
|
|
20
|
+
let fence = null;
|
|
21
|
+
for (const line of markdownLines(content)) {
|
|
22
|
+
const text = content.slice(line.start, line.contentEnd);
|
|
23
|
+
const fenceMatch = /^[ \t]{0,3}(`{3,}|~{3,})/.exec(text);
|
|
24
|
+
if (fenceMatch?.[1]) {
|
|
25
|
+
const marker = fenceMatch[1][0];
|
|
26
|
+
if (fence === null) {
|
|
27
|
+
fence = { marker, length: fenceMatch[1].length };
|
|
28
|
+
}
|
|
29
|
+
else if (fence.marker === marker && fenceMatch[1].length >= fence.length) {
|
|
30
|
+
fence = null;
|
|
31
|
+
}
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (fence !== null) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
const heading = /^(#{1,6})[ \t]+(.+?)[ \t]*#*[ \t]*$/.exec(text);
|
|
38
|
+
if (!heading?.[1] || heading[2] === undefined) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const level = heading[1].length;
|
|
42
|
+
if (selected !== null && level <= selected.level) {
|
|
43
|
+
return { ...selected, end: line.start };
|
|
44
|
+
}
|
|
45
|
+
if (selected === null && heading[2].trim().toLowerCase() === headingName.trim().toLowerCase()) {
|
|
46
|
+
selected = {
|
|
47
|
+
headingStart: line.start,
|
|
48
|
+
contentStart: line.end,
|
|
49
|
+
end: content.length,
|
|
50
|
+
level,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return selected;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Extract the text content under a markdown heading that matches any keyword.
|
|
58
|
+
*/
|
|
59
|
+
export function extractSection(content, ...headingKeywords) {
|
|
60
|
+
const lines = content.split('\n');
|
|
61
|
+
let capturing = false;
|
|
62
|
+
let capturedLevel = 0;
|
|
63
|
+
const captured = [];
|
|
64
|
+
for (const line of lines) {
|
|
65
|
+
const headingMatch = /^(#{1,6})\s+(.+)/.exec(line);
|
|
66
|
+
if (headingMatch?.[1] !== undefined && headingMatch[2] !== undefined) {
|
|
67
|
+
const level = headingMatch[1].length;
|
|
68
|
+
const title = headingMatch[2].toLowerCase();
|
|
69
|
+
if (capturing && level <= capturedLevel) {
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
if (!capturing && headingKeywords.some((keyword) => title.includes(keyword))) {
|
|
73
|
+
capturing = true;
|
|
74
|
+
capturedLevel = level;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (capturing) {
|
|
79
|
+
captured.push(line);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return captured.length > 0 ? captured.join('\n') : null;
|
|
83
|
+
}
|
|
84
|
+
/** Extract bullet and numbered-list item text from a markdown block. */
|
|
85
|
+
export function extractListItems(content) {
|
|
86
|
+
const items = [];
|
|
87
|
+
for (const line of content.split('\n')) {
|
|
88
|
+
const listMatch = /^\s*[-*+]\s+\[?\s*[xX ]?\s*\]?\s*(.+)/.exec(line);
|
|
89
|
+
if (listMatch?.[1]) {
|
|
90
|
+
const item = listMatch[1].trim();
|
|
91
|
+
if (item.length > 5) {
|
|
92
|
+
items.push(item);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const numberedMatch = /^\s*\d+[.)]\s+(.+)/.exec(line);
|
|
96
|
+
if (numberedMatch?.[1]) {
|
|
97
|
+
const item = numberedMatch[1].trim();
|
|
98
|
+
if (item.length > 5) {
|
|
99
|
+
items.push(item);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return items;
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=markdown-sections.js.map
|
|
@@ -60,11 +60,39 @@ export function extractSectionBody(body, sectionName) {
|
|
|
60
60
|
* against the masked variant.
|
|
61
61
|
*/
|
|
62
62
|
function maskFencedBlocks(body) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
63
|
+
const masked = body.split('');
|
|
64
|
+
let activeMarker = null;
|
|
65
|
+
let minimumClosingLength = 0;
|
|
66
|
+
let lineStart = 0;
|
|
67
|
+
while (lineStart < body.length) {
|
|
68
|
+
const newlineIndex = body.indexOf('\n', lineStart);
|
|
69
|
+
const lineEnd = newlineIndex === -1 ? body.length : newlineIndex;
|
|
70
|
+
const line = body.slice(lineStart, lineEnd);
|
|
71
|
+
if (activeMarker === null) {
|
|
72
|
+
const opening = /^ {0,3}(`{3,}|~{3,})/.exec(line)?.[1];
|
|
73
|
+
if (opening !== undefined) {
|
|
74
|
+
activeMarker = opening[0];
|
|
75
|
+
minimumClosingLength = opening.length;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
const closing = /^ {0,3}(`+|~+)[ \t]*$/.exec(line)?.[1];
|
|
80
|
+
if (closing?.startsWith(activeMarker) === true && closing.length >= minimumClosingLength) {
|
|
81
|
+
activeMarker = null;
|
|
82
|
+
minimumClosingLength = 0;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (activeMarker !== null || minimumClosingLength === 0) {
|
|
86
|
+
const isFenceBoundary = /^ {0,3}(`{3,}|~{3,})/.test(line) || /^ {0,3}(`+|~+)[ \t]*$/.test(line);
|
|
87
|
+
if (activeMarker !== null || isFenceBoundary) {
|
|
88
|
+
for (let index = lineStart; index < lineEnd; index += 1) {
|
|
89
|
+
masked[index] = ' ';
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
lineStart = newlineIndex === -1 ? body.length : newlineIndex + 1;
|
|
94
|
+
}
|
|
95
|
+
return masked.join('');
|
|
68
96
|
}
|
|
69
97
|
function escapeRegex(input) {
|
|
70
98
|
return input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
@@ -18,7 +18,7 @@ function extractPathsFromSection(sectionText) {
|
|
|
18
18
|
return paths;
|
|
19
19
|
}
|
|
20
20
|
function parseSectionedFileLists(body) {
|
|
21
|
-
const filesSection = /##\s+Files
|
|
21
|
+
const filesSection = /##\s+Files(?:\s+likely\s+to\s+change)?\s*[\s\S]*?(?=\n##\s|$)/.exec(body);
|
|
22
22
|
const scopedBody = filesSection?.[0] ?? body;
|
|
23
23
|
const createSection = /###\s+Create[\s\S]*?(?=\n###|\n##\s|$)/.exec(scopedBody);
|
|
24
24
|
const modifySection = /###\s+Modify[\s\S]*?(?=\n###|\n##\s|$)/.exec(scopedBody);
|
|
@@ -67,7 +67,8 @@ async function categorizeByExistence(paths, projectPath) {
|
|
|
67
67
|
*/
|
|
68
68
|
export async function extractFilesFromSpecBody(specBody, projectPath) {
|
|
69
69
|
const structured = parseSectionedFileLists(specBody);
|
|
70
|
-
if (structured !== null &&
|
|
70
|
+
if (structured !== null &&
|
|
71
|
+
(structured.create.length > 0 || structured.modify.length > 0 || structured.test.length > 0)) {
|
|
71
72
|
return structured;
|
|
72
73
|
}
|
|
73
74
|
const allPaths = [...new Set(Array.from(specBody.matchAll(FILE_PATH_RE), (m) => m[0]))];
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import type { CanonicalUnifiedSpecInput, UnifiedSpecCandidateValidation } from '../../types/index.js';
|
|
2
|
+
/** Compose the one canonical persisted spec document from already-grounded inputs. */
|
|
3
|
+
export declare function buildCanonicalUnifiedSpecContent(input: CanonicalUnifiedSpecInput): string;
|
|
1
4
|
/**
|
|
2
5
|
* Combine a generated lean spec.md body and a generated lean technical.md body
|
|
3
6
|
* into a single unified spec.md content. The technical body's YAML frontmatter
|
|
@@ -10,4 +13,8 @@
|
|
|
10
13
|
* inject sections the user did not author themselves.
|
|
11
14
|
*/
|
|
12
15
|
export declare function buildUnifiedSpecContent(leanSpecBody: string, leanTechnicalBody: string): string;
|
|
16
|
+
/** Pure pre-persistence validation for the complete unified spec candidate. */
|
|
17
|
+
export declare function validateUnifiedSpecCandidate(candidate: string, options?: {
|
|
18
|
+
groundedFilePaths?: Iterable<string>;
|
|
19
|
+
}): UnifiedSpecCandidateValidation;
|
|
13
20
|
//# sourceMappingURL=unified-spec-builder.d.ts.map
|
|
@@ -1,6 +1,44 @@
|
|
|
1
1
|
// engine/spec-format/unified-spec-builder.ts — SPEC-709/SPEC-1010 Bug C: Build unified spec.md.
|
|
2
2
|
// Closes the SPEC-630 residue: every new spec is born unified, never written as a
|
|
3
3
|
// two-file spec.md + technical.md pair only to be merged on the next list_specs run.
|
|
4
|
+
import { stripFrontmatter } from '../frontmatter-parser.js';
|
|
5
|
+
const REQUIRED_SECTIONS = [
|
|
6
|
+
'Problem',
|
|
7
|
+
'Goal',
|
|
8
|
+
'Technical',
|
|
9
|
+
'Implementation Contract',
|
|
10
|
+
'Acceptance Criteria',
|
|
11
|
+
'Files',
|
|
12
|
+
'Out of scope',
|
|
13
|
+
];
|
|
14
|
+
/** Compose the one canonical persisted spec document from already-grounded inputs. */
|
|
15
|
+
export function buildCanonicalUnifiedSpecContent(input) {
|
|
16
|
+
const frontmatter = /^---\n[\s\S]*?\n---\n?/.exec(input.leanSpecBody)?.[0]?.trimEnd() ?? '';
|
|
17
|
+
const source = input.sourceDescription.replace(/\r\n/g, '\n').trim();
|
|
18
|
+
const explicitProblem = extractTopLevelSectionBody(source, 'Problem');
|
|
19
|
+
const proseBeforeHeading = source.split(/^##[ \t]+\S.*$/m, 1)[0]?.trim() ?? '';
|
|
20
|
+
const problem = firstNonEmpty(explicitProblem, proseBeforeHeading, source);
|
|
21
|
+
const explicitGoal = extractTopLevelSectionBody(source, 'Goal');
|
|
22
|
+
const goal = firstNonEmpty(explicitGoal, firstSentence(problem), problem);
|
|
23
|
+
const explicitTechnical = extractTopLevelSectionBody(source, 'Technical');
|
|
24
|
+
const generatedNotes = extractTopLevelSectionBody(input.technicalBody, 'Implementation Notes');
|
|
25
|
+
const technical = firstNonEmpty(explicitTechnical, generatedNotes, 'Implement only the grounded ownership and observable behavior declared by this contract.');
|
|
26
|
+
const acceptance = extractExecutableCriteria(source, input.criteria);
|
|
27
|
+
const explicitOutOfScope = extractTopLevelSectionBody(source, 'Out of scope');
|
|
28
|
+
const outOfScope = input.outOfScope.length > 0
|
|
29
|
+
? input.outOfScope.map((item) => `- ${item}`).join('\n')
|
|
30
|
+
: (explicitOutOfScope ?? '');
|
|
31
|
+
const sections = [
|
|
32
|
+
renderSection('Problem', problem),
|
|
33
|
+
renderSection('Goal', goal),
|
|
34
|
+
renderSection('Technical', technical),
|
|
35
|
+
input.implementationContract.trim(),
|
|
36
|
+
renderSection('Acceptance Criteria', acceptance),
|
|
37
|
+
renderSection('Files', renderOwnedFiles(input.files)),
|
|
38
|
+
renderSection('Out of scope', outOfScope),
|
|
39
|
+
];
|
|
40
|
+
return `${frontmatter ? `${frontmatter}\n\n` : ''}${sections.join('\n\n')}\n`;
|
|
41
|
+
}
|
|
4
42
|
/**
|
|
5
43
|
* Combine a generated lean spec.md body and a generated lean technical.md body
|
|
6
44
|
* into a single unified spec.md content. The technical body's YAML frontmatter
|
|
@@ -78,4 +116,168 @@ function extractSection(body, sectionName) {
|
|
|
78
116
|
const end = next ? next.index : body.length;
|
|
79
117
|
return body.slice(start, end).trimEnd();
|
|
80
118
|
}
|
|
119
|
+
/** Pure pre-persistence validation for the complete unified spec candidate. */
|
|
120
|
+
// eslint-disable-next-line max-lines-per-function -- validation order mirrors the public issue contract
|
|
121
|
+
export function validateUnifiedSpecCandidate(candidate, options = {}) {
|
|
122
|
+
const body = stripFrontmatter(candidate.replace(/\r\n/g, '\n'));
|
|
123
|
+
const scannable = maskFencedAndQuotedText(body);
|
|
124
|
+
const issues = [];
|
|
125
|
+
const positions = new Map();
|
|
126
|
+
for (const section of REQUIRED_SECTIONS) {
|
|
127
|
+
const pattern = new RegExp(`^##[ \\t]+${escapeRegex(section)}[ \\t]*$`, 'gim');
|
|
128
|
+
positions.set(section, Array.from(scannable.matchAll(pattern), (match) => match.index));
|
|
129
|
+
}
|
|
130
|
+
for (const section of REQUIRED_SECTIONS) {
|
|
131
|
+
const matches = positions.get(section) ?? [];
|
|
132
|
+
if (matches.length === 0) {
|
|
133
|
+
issues.push({
|
|
134
|
+
code: 'MISSING_REQUIRED_SECTION',
|
|
135
|
+
section,
|
|
136
|
+
message: `Missing required top-level section: ${section}`,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
else if (matches.length > 1) {
|
|
140
|
+
issues.push({
|
|
141
|
+
code: 'DUPLICATE_REQUIRED_SECTION',
|
|
142
|
+
section,
|
|
143
|
+
message: `Required top-level section appears more than once: ${section}`,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
const observedOrder = REQUIRED_SECTIONS.flatMap((section) => (positions.get(section) ?? []).slice(0, 1).map((position) => ({ section, position }))).sort((left, right) => left.position - right.position);
|
|
148
|
+
if (observedOrder.length === REQUIRED_SECTIONS.length &&
|
|
149
|
+
observedOrder.some((entry, index) => entry.section !== REQUIRED_SECTIONS[index])) {
|
|
150
|
+
issues.push({
|
|
151
|
+
code: 'MISORDERED_REQUIRED_SECTION',
|
|
152
|
+
message: `Required sections must appear in canonical order: ${REQUIRED_SECTIONS.join(' -> ')}`,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
const embedded = findEmbeddedRequiredSections(body);
|
|
156
|
+
for (const section of embedded) {
|
|
157
|
+
issues.push({
|
|
158
|
+
code: 'EMBEDDED_REQUIRED_SECTION',
|
|
159
|
+
section,
|
|
160
|
+
message: `Required section heading is embedded in quoted, fenced, or nested content: ${section}`,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
if (/\b(?:Needs decision|TBD|TODO)\b/i.test(scannable)) {
|
|
164
|
+
issues.push({
|
|
165
|
+
code: 'UNRESOLVED_CONTRACT_PLACEHOLDER',
|
|
166
|
+
message: 'Unified candidate contains an unresolved contract placeholder.',
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
const acceptanceSection = extractTopLevelSectionBody(scannable, 'Acceptance Criteria');
|
|
170
|
+
if (acceptanceSection === null ||
|
|
171
|
+
!/\bGIVEN\b[\s\S]*\bWHEN\b[\s\S]*\bTHEN\b/i.test(acceptanceSection)) {
|
|
172
|
+
issues.push({
|
|
173
|
+
code: 'MISSING_EXECUTABLE_CRITERIA',
|
|
174
|
+
section: 'Acceptance Criteria',
|
|
175
|
+
message: 'Acceptance Criteria must contain executable GIVEN/WHEN/THEN behavior.',
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
if (options.groundedFilePaths !== undefined) {
|
|
179
|
+
const grounded = new Set(options.groundedFilePaths);
|
|
180
|
+
const filesSection = extractTopLevelSectionBody(scannable, 'Files') ?? '';
|
|
181
|
+
const ownedPaths = Array.from(filesSection.matchAll(/^[ \t]*-[ \t]+`?([^`\r\n]+?)`?[ \t]*$/gm), (match) => match[1]?.trim() ?? '').filter((path) => path.length > 0 && path !== 'None.');
|
|
182
|
+
for (const path of [...new Set(ownedPaths)]) {
|
|
183
|
+
if (!grounded.has(path)) {
|
|
184
|
+
issues.push({
|
|
185
|
+
code: 'UNGROUNDED_FILE_OWNERSHIP',
|
|
186
|
+
path,
|
|
187
|
+
message: `File ownership is not grounded in explicit input or project evidence: ${path}`,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return { valid: issues.length === 0, issues };
|
|
193
|
+
}
|
|
194
|
+
function maskFencedAndQuotedText(body) {
|
|
195
|
+
const lines = body.split('\n');
|
|
196
|
+
let fence = null;
|
|
197
|
+
return lines
|
|
198
|
+
.map((line) => {
|
|
199
|
+
const marker = /^ {0,3}(`{3,}|~{3,})/.exec(line)?.[1];
|
|
200
|
+
if (marker) {
|
|
201
|
+
const kind = marker[0];
|
|
202
|
+
fence = fence === null ? kind : fence === kind ? null : fence;
|
|
203
|
+
return ' '.repeat(line.length);
|
|
204
|
+
}
|
|
205
|
+
if (fence !== null || /^\s*>/.test(line)) {
|
|
206
|
+
return ' '.repeat(line.length);
|
|
207
|
+
}
|
|
208
|
+
return line;
|
|
209
|
+
})
|
|
210
|
+
.join('\n');
|
|
211
|
+
}
|
|
212
|
+
function findEmbeddedRequiredSections(body) {
|
|
213
|
+
const embedded = new Set();
|
|
214
|
+
let fence = null;
|
|
215
|
+
for (const line of body.split('\n')) {
|
|
216
|
+
const marker = /^ {0,3}(`{3,}|~{3,})/.exec(line)?.[1];
|
|
217
|
+
if (marker) {
|
|
218
|
+
const kind = marker[0];
|
|
219
|
+
fence = fence === null ? kind : fence === kind ? null : fence;
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
const candidate = (fence !== null ? line : line.replace(/^\s*>\s?/, '')).trim();
|
|
223
|
+
if (fence === null && !/^\s*>/.test(line) && !/^#{3,6}\s+/.test(line)) {
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
const title = /^#{2,6}\s+(.+?)\s*$/.exec(candidate)?.[1];
|
|
227
|
+
const canonical = REQUIRED_SECTIONS.find((section) => title?.toLowerCase() === section.toLowerCase());
|
|
228
|
+
if (canonical) {
|
|
229
|
+
embedded.add(canonical);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return [...embedded];
|
|
233
|
+
}
|
|
234
|
+
function extractTopLevelSectionBody(body, section) {
|
|
235
|
+
const heading = new RegExp(`^##[ \\t]+${escapeRegex(section)}[ \\t]*$`, 'mi').exec(body);
|
|
236
|
+
if (!heading) {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
const start = heading.index + heading[0].length;
|
|
240
|
+
const rest = body.slice(start);
|
|
241
|
+
const next = /^##[ \t]+\S.*$/m.exec(rest);
|
|
242
|
+
return rest.slice(0, next?.index ?? rest.length).trim();
|
|
243
|
+
}
|
|
244
|
+
function renderSection(title, body) {
|
|
245
|
+
return `## ${title}\n\n${body.trim()}`;
|
|
246
|
+
}
|
|
247
|
+
function firstNonEmpty(...values) {
|
|
248
|
+
return (values.find((value) => value !== undefined && value !== null && value.length > 0) ?? '');
|
|
249
|
+
}
|
|
250
|
+
function firstSentence(text) {
|
|
251
|
+
const sentence = text
|
|
252
|
+
.split(/(?<=[.!?])\s+/)
|
|
253
|
+
.map((part) => part.trim())
|
|
254
|
+
.find((part) => part.length > 0 && !part.startsWith('#'));
|
|
255
|
+
return sentence ?? null;
|
|
256
|
+
}
|
|
257
|
+
function extractExecutableCriteria(source, criteria) {
|
|
258
|
+
const explicit = /\bGIVEN\b[\s\S]*?(?=(?:\n##|\bFILES:|\bFUNCTIONS:|\bTEST:|$))/i
|
|
259
|
+
.exec(source)?.[0]
|
|
260
|
+
?.trim()
|
|
261
|
+
.replace(/[.\s]+$/, '.');
|
|
262
|
+
if (explicit) {
|
|
263
|
+
return `- ${explicit}`;
|
|
264
|
+
}
|
|
265
|
+
return criteria
|
|
266
|
+
.map((criterion) => `- GIVEN the requested change is implemented WHEN the behavior is exercised THEN ${criterion.text}`)
|
|
267
|
+
.join('\n');
|
|
268
|
+
}
|
|
269
|
+
function renderOwnedFiles(files) {
|
|
270
|
+
const sections = [];
|
|
271
|
+
for (const [title, entries] of [
|
|
272
|
+
['Create', files.create],
|
|
273
|
+
['Modify', files.modify],
|
|
274
|
+
['Test', files.test],
|
|
275
|
+
]) {
|
|
276
|
+
sections.push(`### ${title}`);
|
|
277
|
+
sections.push('');
|
|
278
|
+
sections.push(...(entries.length > 0 ? entries.map((entry) => `- \`${entry.path}\``) : ['- None.']));
|
|
279
|
+
sections.push('');
|
|
280
|
+
}
|
|
281
|
+
return sections.join('\n').trim();
|
|
282
|
+
}
|
|
81
283
|
//# sourceMappingURL=unified-spec-builder.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { CanonicalRepairManifest, CanonicalRepairPlan, CanonicalRepairRawStore, RepairCanonicalStoreInput } from '../../types/index.js';
|
|
2
|
+
declare function digest(value: Buffer | string): string;
|
|
3
|
+
declare function isContained(parent: string, child: string): boolean;
|
|
4
|
+
declare function artifactPath(projectId: string, artifactId: string): string;
|
|
5
|
+
declare function parseRawStore(raw: Buffer): CanonicalRepairRawStore;
|
|
6
|
+
/** Pure planner. It does not read or write the canonical store. */
|
|
7
|
+
export declare function buildCanonicalRepairPlan(raw: Buffer, manifest: CanonicalRepairManifest, manifestDigest?: string, requestedProjectId?: string): CanonicalRepairPlan;
|
|
8
|
+
/** Runs dry-run or the explicitly authorized, lock-protected repair. */
|
|
9
|
+
export declare function repairDuplicateSpecIds(input: RepairCanonicalStoreInput): Promise<CanonicalRepairPlan>;
|
|
10
|
+
export declare function rollbackCanonicalRepair(projectId: string, receiptPath: string): Promise<void>;
|
|
11
|
+
export declare const CANONICAL_DEDUPLICATOR_INTERNALS: {
|
|
12
|
+
digest: typeof digest;
|
|
13
|
+
parseRawStore: typeof parseRawStore;
|
|
14
|
+
artifactPath: typeof artifactPath;
|
|
15
|
+
isContained: typeof isContained;
|
|
16
|
+
};
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=canonical-deduplicator.d.ts.map
|