@sun-asterisk/sungen 3.2.34 → 3.3.0-alpha.2
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/dist/basis/anchor-grammar.d.ts +32 -0
- package/dist/basis/anchor-grammar.d.ts.map +1 -0
- package/dist/basis/anchor-grammar.js +82 -0
- package/dist/basis/anchor-grammar.js.map +1 -0
- package/dist/basis/anchor-hash.d.ts +6 -0
- package/dist/basis/anchor-hash.d.ts.map +1 -0
- package/dist/basis/anchor-hash.js +28 -0
- package/dist/basis/anchor-hash.js.map +1 -0
- package/dist/basis/bm51-tables.d.ts +20 -0
- package/dist/basis/bm51-tables.d.ts.map +1 -0
- package/dist/basis/bm51-tables.js +71 -0
- package/dist/basis/bm51-tables.js.map +1 -0
- package/dist/basis/extract-spec-anchors.d.ts +39 -0
- package/dist/basis/extract-spec-anchors.d.ts.map +1 -0
- package/dist/basis/extract-spec-anchors.js +81 -0
- package/dist/basis/extract-spec-anchors.js.map +1 -0
- package/dist/basis/migration-sentinel.d.ts +47 -0
- package/dist/basis/migration-sentinel.d.ts.map +1 -0
- package/dist/basis/migration-sentinel.js +219 -0
- package/dist/basis/migration-sentinel.js.map +1 -0
- package/dist/basis/spec-frontmatter.d.ts +18 -0
- package/dist/basis/spec-frontmatter.d.ts.map +1 -0
- package/dist/basis/spec-frontmatter.js +55 -0
- package/dist/basis/spec-frontmatter.js.map +1 -0
- package/dist/basis/spec-sections.d.ts +18 -0
- package/dist/basis/spec-sections.d.ts.map +1 -0
- package/dist/basis/spec-sections.js +27 -0
- package/dist/basis/spec-sections.js.map +1 -0
- package/dist/basis/test-basis-schema.d.ts +28 -0
- package/dist/basis/test-basis-schema.d.ts.map +1 -0
- package/dist/basis/test-basis-schema.js +141 -0
- package/dist/basis/test-basis-schema.js.map +1 -0
- package/dist/capabilities/discover.d.ts +4 -0
- package/dist/capabilities/discover.d.ts.map +1 -1
- package/dist/capabilities/discover.js +16 -2
- package/dist/capabilities/discover.js.map +1 -1
- package/dist/capabilities/registry.d.ts +15 -0
- package/dist/capabilities/registry.d.ts.map +1 -1
- package/dist/capabilities/registry.js.map +1 -1
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/audit.js +10 -5
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/cli/commands/capability.d.ts.map +1 -1
- package/dist/cli/commands/capability.js +6 -2
- package/dist/cli/commands/capability.js.map +1 -1
- package/dist/cli/commands/contract-derive-command.d.ts +11 -0
- package/dist/cli/commands/contract-derive-command.d.ts.map +1 -0
- package/dist/cli/commands/contract-derive-command.js +99 -0
- package/dist/cli/commands/contract-derive-command.js.map +1 -0
- package/dist/cli/commands/contract.d.ts +17 -0
- package/dist/cli/commands/contract.d.ts.map +1 -0
- package/dist/cli/commands/contract.js +162 -0
- package/dist/cli/commands/contract.js.map +1 -0
- package/dist/cli/commands/defect.d.ts +7 -0
- package/dist/cli/commands/defect.d.ts.map +1 -0
- package/dist/cli/commands/defect.js +68 -0
- package/dist/cli/commands/defect.js.map +1 -0
- package/dist/cli/commands/delivery.d.ts.map +1 -1
- package/dist/cli/commands/delivery.js +96 -12
- package/dist/cli/commands/delivery.js.map +1 -1
- package/dist/cli/commands/generate.d.ts.map +1 -1
- package/dist/cli/commands/generate.js +3 -1
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/cli/commands/journey.d.ts.map +1 -1
- package/dist/cli/commands/journey.js +43 -15
- package/dist/cli/commands/journey.js.map +1 -1
- package/dist/cli/commands/next.d.ts +8 -0
- package/dist/cli/commands/next.d.ts.map +1 -1
- package/dist/cli/commands/next.js +68 -8
- package/dist/cli/commands/next.js.map +1 -1
- package/dist/cli/commands/triage.d.ts +7 -0
- package/dist/cli/commands/triage.d.ts.map +1 -0
- package/dist/cli/commands/triage.js +98 -0
- package/dist/cli/commands/triage.js.map +1 -0
- package/dist/cli/commands/update.d.ts +11 -0
- package/dist/cli/commands/update.d.ts.map +1 -1
- package/dist/cli/commands/update.js +31 -6
- package/dist/cli/commands/update.js.map +1 -1
- package/dist/cli/index.js +24 -33
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/project-sungen-invocation.d.ts +7 -0
- package/dist/cli/project-sungen-invocation.d.ts.map +1 -0
- package/dist/cli/project-sungen-invocation.js +76 -0
- package/dist/cli/project-sungen-invocation.js.map +1 -0
- package/dist/contract/approve-contract.d.ts +31 -0
- package/dist/contract/approve-contract.d.ts.map +1 -0
- package/dist/contract/approve-contract.js +87 -0
- package/dist/contract/approve-contract.js.map +1 -0
- package/dist/contract/assertion-matcher-vocabulary.d.ts +24 -0
- package/dist/contract/assertion-matcher-vocabulary.d.ts.map +1 -0
- package/dist/contract/assertion-matcher-vocabulary.js +100 -0
- package/dist/contract/assertion-matcher-vocabulary.js.map +1 -0
- package/dist/contract/build-contract-verdict.d.ts +32 -0
- package/dist/contract/build-contract-verdict.d.ts.map +1 -0
- package/dist/contract/build-contract-verdict.js +123 -0
- package/dist/contract/build-contract-verdict.js.map +1 -0
- package/dist/contract/build-trace-conformance.d.ts +18 -0
- package/dist/contract/build-trace-conformance.d.ts.map +1 -0
- package/dist/contract/build-trace-conformance.js +153 -0
- package/dist/contract/build-trace-conformance.js.map +1 -0
- package/dist/contract/classify-contract-change.d.ts +23 -0
- package/dist/contract/classify-contract-change.d.ts.map +1 -0
- package/dist/contract/classify-contract-change.js +98 -0
- package/dist/contract/classify-contract-change.js.map +1 -0
- package/dist/contract/classify-contract-unit.d.ts +18 -0
- package/dist/contract/classify-contract-unit.d.ts.map +1 -0
- package/dist/contract/classify-contract-unit.js +49 -0
- package/dist/contract/classify-contract-unit.js.map +1 -0
- package/dist/contract/classify-trace-conformance.d.ts +32 -0
- package/dist/contract/classify-trace-conformance.d.ts.map +1 -0
- package/dist/contract/classify-trace-conformance.js +117 -0
- package/dist/contract/classify-trace-conformance.js.map +1 -0
- package/dist/contract/contract-git.d.ts +24 -0
- package/dist/contract/contract-git.d.ts.map +1 -0
- package/dist/contract/contract-git.js +144 -0
- package/dist/contract/contract-git.js.map +1 -0
- package/dist/contract/contract-hash.d.ts +7 -0
- package/dist/contract/contract-hash.d.ts.map +1 -0
- package/dist/contract/contract-hash.js +38 -0
- package/dist/contract/contract-hash.js.map +1 -0
- package/dist/contract/contract-report-file.d.ts +4 -0
- package/dist/contract/contract-report-file.d.ts.map +1 -0
- package/dist/contract/contract-report-file.js +60 -0
- package/dist/contract/contract-report-file.js.map +1 -0
- package/dist/contract/contract-schema.d.ts +71 -0
- package/dist/contract/contract-schema.d.ts.map +1 -0
- package/dist/contract/contract-schema.js +102 -0
- package/dist/contract/contract-schema.js.map +1 -0
- package/dist/contract/contract-units.d.ts +12 -0
- package/dist/contract/contract-units.d.ts.map +1 -0
- package/dist/contract/contract-units.js +96 -0
- package/dist/contract/contract-units.js.map +1 -0
- package/dist/contract/contracts-lock-file.d.ts +24 -0
- package/dist/contract/contracts-lock-file.d.ts.map +1 -0
- package/dist/contract/contracts-lock-file.js +140 -0
- package/dist/contract/contracts-lock-file.js.map +1 -0
- package/dist/contract/data-normalize.d.ts +10 -0
- package/dist/contract/data-normalize.d.ts.map +1 -0
- package/dist/contract/data-normalize.js +110 -0
- package/dist/contract/data-normalize.js.map +1 -0
- package/dist/contract/derive-free-feature.d.ts +38 -0
- package/dist/contract/derive-free-feature.d.ts.map +1 -0
- package/dist/contract/derive-free-feature.js +154 -0
- package/dist/contract/derive-free-feature.js.map +1 -0
- package/dist/contract/field-semantic-redaction.d.ts +21 -0
- package/dist/contract/field-semantic-redaction.d.ts.map +1 -0
- package/dist/contract/field-semantic-redaction.js +118 -0
- package/dist/contract/field-semantic-redaction.js.map +1 -0
- package/dist/contract/free-feature-blocks.d.ts +10 -0
- package/dist/contract/free-feature-blocks.d.ts.map +1 -0
- package/dist/contract/free-feature-blocks.js +94 -0
- package/dist/contract/free-feature-blocks.js.map +1 -0
- package/dist/contract/lock-secret-guard.d.ts +22 -0
- package/dist/contract/lock-secret-guard.d.ts.map +1 -0
- package/dist/contract/lock-secret-guard.js +134 -0
- package/dist/contract/lock-secret-guard.js.map +1 -0
- package/dist/contract/lock-vs-git-head.d.ts +26 -0
- package/dist/contract/lock-vs-git-head.d.ts.map +1 -0
- package/dist/contract/lock-vs-git-head.js +28 -0
- package/dist/contract/lock-vs-git-head.js.map +1 -0
- package/dist/contract/normalize-contract-step.d.ts +21 -0
- package/dist/contract/normalize-contract-step.d.ts.map +1 -0
- package/dist/contract/normalize-contract-step.js +70 -0
- package/dist/contract/normalize-contract-step.js.map +1 -0
- package/dist/contract/normalize-feature-steps.d.ts +7 -0
- package/dist/contract/normalize-feature-steps.d.ts.map +1 -0
- package/dist/contract/normalize-feature-steps.js +127 -0
- package/dist/contract/normalize-feature-steps.js.map +1 -0
- package/dist/contract/oracle-strength.d.ts +12 -0
- package/dist/contract/oracle-strength.d.ts.map +1 -0
- package/dist/contract/oracle-strength.js +88 -0
- package/dist/contract/oracle-strength.js.map +1 -0
- package/dist/contract/project-contract-blockers.d.ts +10 -0
- package/dist/contract/project-contract-blockers.d.ts.map +1 -0
- package/dist/contract/project-contract-blockers.js +73 -0
- package/dist/contract/project-contract-blockers.js.map +1 -0
- package/dist/contract/record-contract-approval.d.ts +21 -0
- package/dist/contract/record-contract-approval.d.ts.map +1 -0
- package/dist/contract/record-contract-approval.js +98 -0
- package/dist/contract/record-contract-approval.js.map +1 -0
- package/dist/contract/render-free-gherkin.d.ts +40 -0
- package/dist/contract/render-free-gherkin.d.ts.map +1 -0
- package/dist/contract/render-free-gherkin.js +141 -0
- package/dist/contract/render-free-gherkin.js.map +1 -0
- package/dist/contract/render-trace-sentence.d.ts +20 -0
- package/dist/contract/render-trace-sentence.d.ts.map +1 -0
- package/dist/contract/render-trace-sentence.js +109 -0
- package/dist/contract/render-trace-sentence.js.map +1 -0
- package/dist/contract/render-trace-target.d.ts +14 -0
- package/dist/contract/render-trace-target.d.ts.map +1 -0
- package/dist/contract/render-trace-target.js +70 -0
- package/dist/contract/render-trace-target.js.map +1 -0
- package/dist/contract/resolve-unit-trace-identities.d.ts +8 -0
- package/dist/contract/resolve-unit-trace-identities.d.ts.map +1 -0
- package/dist/contract/resolve-unit-trace-identities.js +78 -0
- package/dist/contract/resolve-unit-trace-identities.js.map +1 -0
- package/dist/contract/trace/collect-trace-evidence.d.ts +33 -0
- package/dist/contract/trace/collect-trace-evidence.d.ts.map +1 -0
- package/dist/contract/trace/collect-trace-evidence.js +161 -0
- package/dist/contract/trace/collect-trace-evidence.js.map +1 -0
- package/dist/contract/trace/identity-cache.d.ts +19 -0
- package/dist/contract/trace/identity-cache.d.ts.map +1 -0
- package/dist/contract/trace/identity-cache.js +104 -0
- package/dist/contract/trace/identity-cache.js.map +1 -0
- package/dist/contract/trace/select-final-attempt.d.ts +27 -0
- package/dist/contract/trace/select-final-attempt.d.ts.map +1 -0
- package/dist/contract/trace/select-final-attempt.js +116 -0
- package/dist/contract/trace/select-final-attempt.js.map +1 -0
- package/dist/contract/trace/snapshot-html-renderer.d.ts +31 -0
- package/dist/contract/trace/snapshot-html-renderer.d.ts.map +1 -0
- package/dist/contract/trace/snapshot-html-renderer.js +172 -0
- package/dist/contract/trace/snapshot-html-renderer.js.map +1 -0
- package/dist/contract/trace/snapshot-resolver.d.ts +21 -0
- package/dist/contract/trace/snapshot-resolver.d.ts.map +1 -0
- package/dist/contract/trace/snapshot-resolver.js +65 -0
- package/dist/contract/trace/snapshot-resolver.js.map +1 -0
- package/dist/contract/trace/snapshot-store.d.ts +66 -0
- package/dist/contract/trace/snapshot-store.d.ts.map +1 -0
- package/dist/contract/trace/snapshot-store.js +173 -0
- package/dist/contract/trace/snapshot-store.js.map +1 -0
- package/dist/contract/trace/target-identity.d.ts +74 -0
- package/dist/contract/trace/target-identity.d.ts.map +1 -0
- package/dist/contract/trace/target-identity.js +68 -0
- package/dist/contract/trace/target-identity.js.map +1 -0
- package/dist/contract/trace/test-step-join.d.ts +65 -0
- package/dist/contract/trace/test-step-join.d.ts.map +1 -0
- package/dist/contract/trace/test-step-join.js +80 -0
- package/dist/contract/trace/test-step-join.js.map +1 -0
- package/dist/contract/trace/trace-binding.d.ts +21 -0
- package/dist/contract/trace/trace-binding.d.ts.map +1 -0
- package/dist/contract/trace/trace-binding.js +88 -0
- package/dist/contract/trace/trace-binding.js.map +1 -0
- package/dist/contract/trace/trace-file-reader.d.ts +41 -0
- package/dist/contract/trace/trace-file-reader.d.ts.map +1 -0
- package/dist/contract/trace/trace-file-reader.js +161 -0
- package/dist/contract/trace/trace-file-reader.js.map +1 -0
- package/dist/contract/trace/trace-fingerprint.d.ts +22 -0
- package/dist/contract/trace/trace-fingerprint.d.ts.map +1 -0
- package/dist/contract/trace/trace-fingerprint.js +96 -0
- package/dist/contract/trace/trace-fingerprint.js.map +1 -0
- package/dist/contract/trace/trace-identity-resolution.d.ts +26 -0
- package/dist/contract/trace/trace-identity-resolution.d.ts.map +1 -0
- package/dist/contract/trace/trace-identity-resolution.js +84 -0
- package/dist/contract/trace/trace-identity-resolution.js.map +1 -0
- package/dist/contract/trace/trace-run-record.d.ts +30 -0
- package/dist/contract/trace/trace-run-record.d.ts.map +1 -0
- package/dist/contract/trace/trace-run-record.js +121 -0
- package/dist/contract/trace/trace-run-record.js.map +1 -0
- package/dist/contract/trace/trace-steps.d.ts +19 -0
- package/dist/contract/trace/trace-steps.d.ts.map +1 -0
- package/dist/contract/trace/trace-steps.js +79 -0
- package/dist/contract/trace/trace-steps.js.map +1 -0
- package/dist/contract/trace/trace-zip-limits.d.ts +45 -0
- package/dist/contract/trace/trace-zip-limits.d.ts.map +1 -0
- package/dist/contract/trace/trace-zip-limits.js +52 -0
- package/dist/contract/trace/trace-zip-limits.js.map +1 -0
- package/dist/contract/trace/trace-zip-reader.d.ts +10 -0
- package/dist/contract/trace/trace-zip-reader.d.ts.map +1 -0
- package/dist/contract/trace/trace-zip-reader.js +186 -0
- package/dist/contract/trace/trace-zip-reader.js.map +1 -0
- package/dist/contract/verify-approval-decisions.d.ts +5 -0
- package/dist/contract/verify-approval-decisions.d.ts.map +1 -0
- package/dist/contract/verify-approval-decisions.js +104 -0
- package/dist/contract/verify-approval-decisions.js.map +1 -0
- package/dist/decisions/append-decision.d.ts +12 -0
- package/dist/decisions/append-decision.d.ts.map +1 -0
- package/dist/decisions/append-decision.js +73 -0
- package/dist/decisions/append-decision.js.map +1 -0
- package/dist/decisions/confirmed-decision-commands.d.ts +41 -0
- package/dist/decisions/confirmed-decision-commands.d.ts.map +1 -0
- package/dist/decisions/confirmed-decision-commands.js +190 -0
- package/dist/decisions/confirmed-decision-commands.js.map +1 -0
- package/dist/decisions/decision-file.d.ts +31 -0
- package/dist/decisions/decision-file.d.ts.map +1 -0
- package/dist/decisions/decision-file.js +139 -0
- package/dist/decisions/decision-file.js.map +1 -0
- package/dist/decisions/decision-identity.d.ts +12 -0
- package/dist/decisions/decision-identity.d.ts.map +1 -0
- package/dist/decisions/decision-identity.js +78 -0
- package/dist/decisions/decision-identity.js.map +1 -0
- package/dist/decisions/decision-integrity-check.d.ts +12 -0
- package/dist/decisions/decision-integrity-check.d.ts.map +1 -0
- package/dist/decisions/decision-integrity-check.js +102 -0
- package/dist/decisions/decision-integrity-check.js.map +1 -0
- package/dist/decisions/decision-schema.d.ts +42 -0
- package/dist/decisions/decision-schema.d.ts.map +1 -0
- package/dist/decisions/decision-schema.js +75 -0
- package/dist/decisions/decision-schema.js.map +1 -0
- package/dist/decisions/decision-source.d.ts +2 -0
- package/dist/decisions/decision-source.d.ts.map +1 -0
- package/dist/decisions/decision-source.js +18 -0
- package/dist/decisions/decision-source.js.map +1 -0
- package/dist/decisions/exclusive-file-create.d.ts +3 -0
- package/dist/decisions/exclusive-file-create.d.ts.map +1 -0
- package/dist/decisions/exclusive-file-create.js +84 -0
- package/dist/decisions/exclusive-file-create.js.map +1 -0
- package/dist/decisions/git-head-trust.d.ts +7 -0
- package/dist/decisions/git-head-trust.d.ts.map +1 -0
- package/dist/decisions/git-head-trust.js +76 -0
- package/dist/decisions/git-head-trust.js.map +1 -0
- package/dist/decisions/human-confirmation.d.ts +35 -0
- package/dist/decisions/human-confirmation.d.ts.map +1 -0
- package/dist/decisions/human-confirmation.js +161 -0
- package/dist/decisions/human-confirmation.js.map +1 -0
- package/dist/decisions/legacy-waiver-migration.d.ts +31 -0
- package/dist/decisions/legacy-waiver-migration.d.ts.map +1 -0
- package/dist/decisions/legacy-waiver-migration.js +170 -0
- package/dist/decisions/legacy-waiver-migration.js.map +1 -0
- package/dist/decisions/read-decisions.d.ts +19 -0
- package/dist/decisions/read-decisions.d.ts.map +1 -0
- package/dist/decisions/read-decisions.js +90 -0
- package/dist/decisions/read-decisions.js.map +1 -0
- package/dist/decisions/viewpoint-baseline-adapter.d.ts +7 -0
- package/dist/decisions/viewpoint-baseline-adapter.d.ts.map +1 -0
- package/dist/decisions/viewpoint-baseline-adapter.js +109 -0
- package/dist/decisions/viewpoint-baseline-adapter.js.map +1 -0
- package/dist/decisions/waiver-decisions.d.ts +24 -0
- package/dist/decisions/waiver-decisions.d.ts.map +1 -0
- package/dist/decisions/waiver-decisions.js +46 -0
- package/dist/decisions/waiver-decisions.js.map +1 -0
- package/dist/exporters/csv-exporter.d.ts +4 -0
- package/dist/exporters/csv-exporter.d.ts.map +1 -1
- package/dist/exporters/csv-exporter.js +1 -1
- package/dist/exporters/csv-exporter.js.map +1 -1
- package/dist/exporters/feature-parser.d.ts +8 -0
- package/dist/exporters/feature-parser.d.ts.map +1 -1
- package/dist/exporters/feature-parser.js +12 -0
- package/dist/exporters/feature-parser.js.map +1 -1
- package/dist/exporters/matrix/build.d.ts +3 -0
- package/dist/exporters/matrix/build.d.ts.map +1 -1
- package/dist/exporters/matrix/build.js +59 -19
- package/dist/exporters/matrix/build.js.map +1 -1
- package/dist/exporters/matrix/carry-manual-results.d.ts +19 -0
- package/dist/exporters/matrix/carry-manual-results.d.ts.map +1 -1
- package/dist/exporters/matrix/carry-manual-results.js +76 -6
- package/dist/exporters/matrix/carry-manual-results.js.map +1 -1
- package/dist/exporters/matrix/export.d.ts +8 -2
- package/dist/exporters/matrix/export.d.ts.map +1 -1
- package/dist/exporters/matrix/export.js +16 -3
- package/dist/exporters/matrix/export.js.map +1 -1
- package/dist/exporters/matrix/gates.d.ts +6 -1
- package/dist/exporters/matrix/gates.d.ts.map +1 -1
- package/dist/exporters/matrix/gates.js +57 -128
- package/dist/exporters/matrix/gates.js.map +1 -1
- package/dist/exporters/matrix/render-csv.d.ts.map +1 -1
- package/dist/exporters/matrix/render-csv.js +4 -1
- package/dist/exporters/matrix/render-csv.js.map +1 -1
- package/dist/exporters/matrix/render-gate-overrides-sheet.d.ts +13 -0
- package/dist/exporters/matrix/render-gate-overrides-sheet.d.ts.map +1 -0
- package/dist/exporters/matrix/render-gate-overrides-sheet.js +44 -0
- package/dist/exporters/matrix/render-gate-overrides-sheet.js.map +1 -0
- package/dist/exporters/matrix/render-xlsx.d.ts +1 -0
- package/dist/exporters/matrix/render-xlsx.d.ts.map +1 -1
- package/dist/exporters/matrix/render-xlsx.js +6 -0
- package/dist/exporters/matrix/render-xlsx.js.map +1 -1
- package/dist/exporters/matrix/types.d.ts +9 -1
- package/dist/exporters/matrix/types.d.ts.map +1 -1
- package/dist/exporters/matrix/types.js.map +1 -1
- package/dist/exporters/matrix/xlsx-cells.js +1 -1
- package/dist/exporters/matrix/xlsx-cells.js.map +1 -1
- package/dist/exporters/playwright-report-parser.d.ts.map +1 -1
- package/dist/exporters/playwright-report-parser.js +1 -0
- package/dist/exporters/playwright-report-parser.js.map +1 -1
- package/dist/exporters/scenario-merger.d.ts.map +1 -1
- package/dist/exporters/scenario-merger.js +2 -0
- package/dist/exporters/scenario-merger.js.map +1 -1
- package/dist/exporters/types.d.ts +5 -0
- package/dist/exporters/types.d.ts.map +1 -1
- package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
- package/dist/generators/test-generator/code-generator.js +13 -0
- package/dist/generators/test-generator/code-generator.js.map +1 -1
- package/dist/graph/evaluate-graph.d.ts +23 -0
- package/dist/graph/evaluate-graph.d.ts.map +1 -0
- package/dist/graph/evaluate-graph.js +52 -0
- package/dist/graph/evaluate-graph.js.map +1 -0
- package/dist/graph/locate-finding.d.ts +20 -0
- package/dist/graph/locate-finding.d.ts.map +1 -0
- package/dist/graph/locate-finding.js +137 -0
- package/dist/graph/locate-finding.js.map +1 -0
- package/dist/graph/node-decl.d.ts +66 -0
- package/dist/graph/node-decl.d.ts.map +1 -0
- package/dist/graph/node-decl.js +8 -0
- package/dist/graph/node-decl.js.map +1 -0
- package/dist/graph/nodes/core-node-catalog.d.ts +7 -0
- package/dist/graph/nodes/core-node-catalog.d.ts.map +1 -0
- package/dist/graph/nodes/core-node-catalog.js +13 -0
- package/dist/graph/nodes/core-node-catalog.js.map +1 -0
- package/dist/graph/nodes/test-case-contract-nodes.d.ts +21 -0
- package/dist/graph/nodes/test-case-contract-nodes.d.ts.map +1 -0
- package/dist/graph/nodes/test-case-contract-nodes.js +59 -0
- package/dist/graph/nodes/test-case-contract-nodes.js.map +1 -0
- package/dist/graph/nodes/test-case-nodes.d.ts +26 -0
- package/dist/graph/nodes/test-case-nodes.d.ts.map +1 -0
- package/dist/graph/nodes/test-case-nodes.js +66 -0
- package/dist/graph/nodes/test-case-nodes.js.map +1 -0
- package/dist/graph/nodes/unit-audit-nodes.d.ts +18 -0
- package/dist/graph/nodes/unit-audit-nodes.d.ts.map +1 -0
- package/dist/graph/nodes/unit-audit-nodes.js +95 -0
- package/dist/graph/nodes/unit-audit-nodes.js.map +1 -0
- package/dist/graph/nodes/unit-contract-node.d.ts +21 -0
- package/dist/graph/nodes/unit-contract-node.d.ts.map +1 -0
- package/dist/graph/nodes/unit-contract-node.js +88 -0
- package/dist/graph/nodes/unit-contract-node.js.map +1 -0
- package/dist/graph/nodes/unit-design-nodes.d.ts +14 -0
- package/dist/graph/nodes/unit-design-nodes.d.ts.map +1 -0
- package/dist/graph/nodes/unit-design-nodes.js +91 -0
- package/dist/graph/nodes/unit-design-nodes.js.map +1 -0
- package/dist/graph/nodes/unit-execution-nodes.d.ts +16 -0
- package/dist/graph/nodes/unit-execution-nodes.d.ts.map +1 -0
- package/dist/graph/nodes/unit-execution-nodes.js +79 -0
- package/dist/graph/nodes/unit-execution-nodes.js.map +1 -0
- package/dist/graph/test-case-identity.d.ts +13 -0
- package/dist/graph/test-case-identity.d.ts.map +1 -0
- package/dist/graph/test-case-identity.js +106 -0
- package/dist/graph/test-case-identity.js.map +1 -0
- package/dist/graph/test-case-results.d.ts +11 -0
- package/dist/graph/test-case-results.d.ts.map +1 -0
- package/dist/graph/test-case-results.js +137 -0
- package/dist/graph/test-case-results.js.map +1 -0
- package/dist/graph/test-case-satisfied.d.ts +25 -0
- package/dist/graph/test-case-satisfied.d.ts.map +1 -0
- package/dist/graph/test-case-satisfied.js +51 -0
- package/dist/graph/test-case-satisfied.js.map +1 -0
- package/dist/graph/test-case-state-shape.d.ts +67 -0
- package/dist/graph/test-case-state-shape.d.ts.map +1 -0
- package/dist/graph/test-case-state-shape.js +3 -0
- package/dist/graph/test-case-state-shape.js.map +1 -0
- package/dist/graph/test-case-states.d.ts +9 -0
- package/dist/graph/test-case-states.d.ts.map +1 -0
- package/dist/graph/test-case-states.js +116 -0
- package/dist/graph/test-case-states.js.map +1 -0
- package/dist/graph/unit-artifact-probes.d.ts +19 -0
- package/dist/graph/unit-artifact-probes.d.ts.map +1 -0
- package/dist/graph/unit-artifact-probes.js +133 -0
- package/dist/graph/unit-artifact-probes.js.map +1 -0
- package/dist/graph/unit-state-facts.d.ts +43 -0
- package/dist/graph/unit-state-facts.d.ts.map +1 -0
- package/dist/graph/unit-state-facts.js +87 -0
- package/dist/graph/unit-state-facts.js.map +1 -0
- package/dist/harness/audit.d.ts +11 -0
- package/dist/harness/audit.d.ts.map +1 -1
- package/dist/harness/audit.js +34 -9
- package/dist/harness/audit.js.map +1 -1
- package/dist/harness/catalog/gate-rules.yaml +647 -0
- package/dist/harness/counted-assertions.d.ts +2 -0
- package/dist/harness/counted-assertions.d.ts.map +1 -0
- package/dist/harness/counted-assertions.js +38 -0
- package/dist/harness/counted-assertions.js.map +1 -0
- package/dist/harness/defect-draft.d.ts +14 -0
- package/dist/harness/defect-draft.d.ts.map +1 -0
- package/dist/harness/defect-draft.js +143 -0
- package/dist/harness/defect-draft.js.map +1 -0
- package/dist/harness/defects.d.ts +24 -0
- package/dist/harness/defects.d.ts.map +1 -0
- package/dist/harness/defects.js +118 -0
- package/dist/harness/defects.js.map +1 -0
- package/dist/harness/gates/audit-gate-terms.d.ts +41 -0
- package/dist/harness/gates/audit-gate-terms.d.ts.map +1 -0
- package/dist/harness/gates/audit-gate-terms.js +72 -0
- package/dist/harness/gates/audit-gate-terms.js.map +1 -0
- package/dist/harness/gates/audit-gate-verdict.d.ts +31 -0
- package/dist/harness/gates/audit-gate-verdict.d.ts.map +1 -0
- package/dist/harness/gates/audit-gate-verdict.js +101 -0
- package/dist/harness/gates/audit-gate-verdict.js.map +1 -0
- package/dist/harness/gates/audit-input-hash.d.ts +4 -0
- package/dist/harness/gates/audit-input-hash.d.ts.map +1 -0
- package/dist/harness/gates/audit-input-hash.js +96 -0
- package/dist/harness/gates/audit-input-hash.js.map +1 -0
- package/dist/harness/gates/audit-report-verification.d.ts +41 -0
- package/dist/harness/gates/audit-report-verification.d.ts.map +1 -0
- package/dist/harness/gates/audit-report-verification.js +236 -0
- package/dist/harness/gates/audit-report-verification.js.map +1 -0
- package/dist/harness/gates/decisions-hash.d.ts +13 -0
- package/dist/harness/gates/decisions-hash.d.ts.map +1 -0
- package/dist/harness/gates/decisions-hash.js +72 -0
- package/dist/harness/gates/decisions-hash.js.map +1 -0
- package/dist/harness/gates/delivery-checklist.d.ts +21 -0
- package/dist/harness/gates/delivery-checklist.d.ts.map +1 -0
- package/dist/harness/gates/delivery-checklist.js +128 -0
- package/dist/harness/gates/delivery-checklist.js.map +1 -0
- package/dist/harness/gates/delivery-decision-gate.d.ts +33 -0
- package/dist/harness/gates/delivery-decision-gate.d.ts.map +1 -0
- package/dist/harness/gates/delivery-decision-gate.js +116 -0
- package/dist/harness/gates/delivery-decision-gate.js.map +1 -0
- package/dist/harness/gates/delivery-decision-step.d.ts +19 -0
- package/dist/harness/gates/delivery-decision-step.d.ts.map +1 -0
- package/dist/harness/gates/delivery-decision-step.js +86 -0
- package/dist/harness/gates/delivery-decision-step.js.map +1 -0
- package/dist/harness/gates/gate-override-report.d.ts +59 -0
- package/dist/harness/gates/gate-override-report.d.ts.map +1 -0
- package/dist/harness/gates/gate-override-report.js +86 -0
- package/dist/harness/gates/gate-override-report.js.map +1 -0
- package/dist/harness/gates/gate-override-scope.d.ts +18 -0
- package/dist/harness/gates/gate-override-scope.d.ts.map +1 -0
- package/dist/harness/gates/gate-override-scope.js +98 -0
- package/dist/harness/gates/gate-override-scope.js.map +1 -0
- package/dist/harness/gates/gate-override-validation.d.ts +25 -0
- package/dist/harness/gates/gate-override-validation.d.ts.map +1 -0
- package/dist/harness/gates/gate-override-validation.js +81 -0
- package/dist/harness/gates/gate-override-validation.js.map +1 -0
- package/dist/harness/gates/gate-rule-catalog.d.ts +24 -0
- package/dist/harness/gates/gate-rule-catalog.d.ts.map +1 -0
- package/dist/harness/gates/gate-rule-catalog.js +112 -0
- package/dist/harness/gates/gate-rule-catalog.js.map +1 -0
- package/dist/harness/gates/gate-terms.d.ts +33 -0
- package/dist/harness/gates/gate-terms.d.ts.map +1 -0
- package/dist/harness/gates/gate-terms.js +41 -0
- package/dist/harness/gates/gate-terms.js.map +1 -0
- package/dist/harness/gates/in-run-decision-check.d.ts +21 -0
- package/dist/harness/gates/in-run-decision-check.d.ts.map +1 -0
- package/dist/harness/gates/in-run-decision-check.js +138 -0
- package/dist/harness/gates/in-run-decision-check.js.map +1 -0
- package/dist/harness/gates/journey-gate-inputs.d.ts +37 -0
- package/dist/harness/gates/journey-gate-inputs.d.ts.map +1 -0
- package/dist/harness/gates/journey-gate-inputs.js +81 -0
- package/dist/harness/gates/journey-gate-inputs.js.map +1 -0
- package/dist/harness/gates/migrated-waiver-review.d.ts +6 -0
- package/dist/harness/gates/migrated-waiver-review.d.ts.map +1 -0
- package/dist/harness/gates/migrated-waiver-review.js +116 -0
- package/dist/harness/gates/migrated-waiver-review.js.map +1 -0
- package/dist/harness/gates/resolve-rule.d.ts +59 -0
- package/dist/harness/gates/resolve-rule.d.ts.map +1 -0
- package/dist/harness/gates/resolve-rule.js +41 -0
- package/dist/harness/gates/resolve-rule.js.map +1 -0
- package/dist/harness/gates/unit-gate-overrides.d.ts +25 -0
- package/dist/harness/gates/unit-gate-overrides.d.ts.map +1 -0
- package/dist/harness/gates/unit-gate-overrides.js +122 -0
- package/dist/harness/gates/unit-gate-overrides.js.map +1 -0
- package/dist/harness/journey.d.ts +43 -1
- package/dist/harness/journey.d.ts.map +1 -1
- package/dist/harness/journey.js +207 -33
- package/dist/harness/journey.js.map +1 -1
- package/dist/harness/manifest.d.ts +12 -0
- package/dist/harness/manifest.d.ts.map +1 -1
- package/dist/harness/manifest.js +73 -45
- package/dist/harness/manifest.js.map +1 -1
- package/dist/harness/next-step.d.ts +37 -6
- package/dist/harness/next-step.d.ts.map +1 -1
- package/dist/harness/next-step.js +34 -174
- package/dist/harness/next-step.js.map +1 -1
- package/dist/harness/parse.d.ts +25 -0
- package/dist/harness/parse.d.ts.map +1 -1
- package/dist/harness/parse.js +38 -13
- package/dist/harness/parse.js.map +1 -1
- package/dist/harness/provenance.d.ts +29 -0
- package/dist/harness/provenance.d.ts.map +1 -1
- package/dist/harness/provenance.js +109 -9
- package/dist/harness/provenance.js.map +1 -1
- package/dist/harness/script-check.d.ts.map +1 -1
- package/dist/harness/script-check.js +29 -6
- package/dist/harness/script-check.js.map +1 -1
- package/dist/harness/sensors.d.ts +14 -3
- package/dist/harness/sensors.d.ts.map +1 -1
- package/dist/harness/sensors.js +64 -44
- package/dist/harness/sensors.js.map +1 -1
- package/dist/harness/triage-signals.d.ts +20 -0
- package/dist/harness/triage-signals.d.ts.map +1 -0
- package/dist/harness/triage-signals.js +30 -0
- package/dist/harness/triage-signals.js.map +1 -0
- package/dist/harness/triage-unit.d.ts +23 -0
- package/dist/harness/triage-unit.d.ts.map +1 -0
- package/dist/harness/triage-unit.js +142 -0
- package/dist/harness/triage-unit.js.map +1 -0
- package/dist/harness/triage.d.ts +28 -0
- package/dist/harness/triage.d.ts.map +1 -0
- package/dist/harness/triage.js +99 -0
- package/dist/harness/triage.js.map +1 -0
- package/dist/harness/viewpoint-ledger.d.ts.map +1 -1
- package/dist/harness/viewpoint-ledger.js +2 -1
- package/dist/harness/viewpoint-ledger.js.map +1 -1
- package/dist/hooks/bash-write-targets.d.ts +15 -0
- package/dist/hooks/bash-write-targets.d.ts.map +1 -0
- package/dist/hooks/bash-write-targets.js +173 -0
- package/dist/hooks/bash-write-targets.js.map +1 -0
- package/dist/hooks/claude-hook-entry.d.ts +11 -0
- package/dist/hooks/claude-hook-entry.d.ts.map +1 -0
- package/dist/hooks/claude-hook-entry.js +137 -0
- package/dist/hooks/claude-hook-entry.js.map +1 -0
- package/dist/hooks/claude-transcript-turn.d.ts +11 -0
- package/dist/hooks/claude-transcript-turn.d.ts.map +1 -0
- package/dist/hooks/claude-transcript-turn.js +143 -0
- package/dist/hooks/claude-transcript-turn.js.map +1 -0
- package/dist/hooks/guard-delivery-contract-rule.d.ts +10 -0
- package/dist/hooks/guard-delivery-contract-rule.d.ts.map +1 -0
- package/dist/hooks/guard-delivery-contract-rule.js +152 -0
- package/dist/hooks/guard-delivery-contract-rule.js.map +1 -0
- package/dist/hooks/guard-protected-paths.d.ts +9 -0
- package/dist/hooks/guard-protected-paths.d.ts.map +1 -0
- package/dist/hooks/guard-protected-paths.js +107 -0
- package/dist/hooks/guard-protected-paths.js.map +1 -0
- package/dist/hooks/guard-rules.d.ts +37 -0
- package/dist/hooks/guard-rules.d.ts.map +1 -0
- package/dist/hooks/guard-rules.js +61 -0
- package/dist/hooks/guard-rules.js.map +1 -0
- package/dist/hooks/guard-tty-spoof-rule.d.ts +15 -0
- package/dist/hooks/guard-tty-spoof-rule.d.ts.map +1 -0
- package/dist/hooks/guard-tty-spoof-rule.js +21 -0
- package/dist/hooks/guard-tty-spoof-rule.js.map +1 -0
- package/dist/hooks/hand-back-core.d.ts +39 -0
- package/dist/hooks/hand-back-core.d.ts.map +1 -0
- package/dist/hooks/hand-back-core.js +122 -0
- package/dist/hooks/hand-back-core.js.map +1 -0
- package/dist/hooks/hook-cli-command.d.ts +9 -0
- package/dist/hooks/hook-cli-command.d.ts.map +1 -0
- package/dist/hooks/hook-cli-command.js +46 -0
- package/dist/hooks/hook-cli-command.js.map +1 -0
- package/dist/hooks/hook-registry.d.ts +10 -0
- package/dist/hooks/hook-registry.d.ts.map +1 -0
- package/dist/hooks/hook-registry.js +24 -0
- package/dist/hooks/hook-registry.js.map +1 -0
- package/dist/hooks/hook-result-contract.d.ts +50 -0
- package/dist/hooks/hook-result-contract.d.ts.map +1 -0
- package/dist/hooks/hook-result-contract.js +80 -0
- package/dist/hooks/hook-result-contract.js.map +1 -0
- package/dist/hooks/hook-toggles.d.ts +10 -0
- package/dist/hooks/hook-toggles.d.ts.map +1 -0
- package/dist/hooks/hook-toggles.js +107 -0
- package/dist/hooks/hook-toggles.js.map +1 -0
- package/dist/hooks/pre-tool-guard-hook.d.ts +10 -0
- package/dist/hooks/pre-tool-guard-hook.d.ts.map +1 -0
- package/dist/hooks/pre-tool-guard-hook.js +105 -0
- package/dist/hooks/pre-tool-guard-hook.js.map +1 -0
- package/dist/hooks/session-start-unit-state-hook.d.ts +13 -0
- package/dist/hooks/session-start-unit-state-hook.d.ts.map +1 -0
- package/dist/hooks/session-start-unit-state-hook.js +57 -0
- package/dist/hooks/session-start-unit-state-hook.js.map +1 -0
- package/dist/hooks/stop-next-step-hook.d.ts +16 -0
- package/dist/hooks/stop-next-step-hook.d.ts.map +1 -0
- package/dist/hooks/stop-next-step-hook.js +42 -0
- package/dist/hooks/stop-next-step-hook.js.map +1 -0
- package/dist/hooks/sungen-project-root.d.ts +19 -0
- package/dist/hooks/sungen-project-root.d.ts.map +1 -0
- package/dist/hooks/sungen-project-root.js +130 -0
- package/dist/hooks/sungen-project-root.js.map +1 -0
- package/dist/orchestrator/ai-skills/command-partial-includes.d.ts +15 -0
- package/dist/orchestrator/ai-skills/command-partial-includes.d.ts.map +1 -0
- package/dist/orchestrator/ai-skills/command-partial-includes.js +106 -0
- package/dist/orchestrator/ai-skills/command-partial-includes.js.map +1 -0
- package/dist/orchestrator/ai-skills/commands.d.ts +5 -1
- package/dist/orchestrator/ai-skills/commands.d.ts.map +1 -1
- package/dist/orchestrator/ai-skills/commands.js +16 -6
- package/dist/orchestrator/ai-skills/commands.js.map +1 -1
- package/dist/orchestrator/ai-skills/golden-skills.test.js +29 -0
- package/dist/orchestrator/ai-skills/golden-skills.test.js.map +1 -1
- package/dist/orchestrator/assets-drift.d.ts +5 -0
- package/dist/orchestrator/assets-drift.d.ts.map +1 -1
- package/dist/orchestrator/assets-drift.js +12 -3
- package/dist/orchestrator/assets-drift.js.map +1 -1
- package/dist/orchestrator/claude-hooks-install.d.ts +27 -0
- package/dist/orchestrator/claude-hooks-install.d.ts.map +1 -0
- package/dist/orchestrator/claude-hooks-install.js +203 -0
- package/dist/orchestrator/claude-hooks-install.js.map +1 -0
- package/dist/orchestrator/lockstep-version.d.ts +15 -5
- package/dist/orchestrator/lockstep-version.d.ts.map +1 -1
- package/dist/orchestrator/lockstep-version.js +30 -17
- package/dist/orchestrator/lockstep-version.js.map +1 -1
- package/dist/orchestrator/project-initializer.d.ts +6 -0
- package/dist/orchestrator/project-initializer.d.ts.map +1 -1
- package/dist/orchestrator/project-initializer.js +24 -0
- package/dist/orchestrator/project-initializer.js.map +1 -1
- package/dist/orchestrator/screen-manager.d.ts.map +1 -1
- package/dist/orchestrator/screen-manager.js +4 -2
- package/dist/orchestrator/screen-manager.js.map +1 -1
- package/dist/orchestrator/templates/ai-src/commands/add-api.md +2 -0
- package/dist/orchestrator/templates/ai-src/commands/add-flow.md +2 -0
- package/dist/orchestrator/templates/ai-src/commands/add-screen.md +2 -0
- package/dist/orchestrator/templates/ai-src/commands/create-data-test.md +1 -0
- package/dist/orchestrator/templates/ai-src/commands/create-test.md +14 -16
- package/dist/orchestrator/templates/ai-src/commands/dashboard.md +2 -0
- package/dist/orchestrator/templates/ai-src/commands/delivery.md +2 -0
- package/dist/orchestrator/templates/ai-src/commands/design.md +2 -0
- package/dist/orchestrator/templates/ai-src/commands/feedback.md +2 -0
- package/dist/orchestrator/templates/ai-src/commands/locale.md +2 -0
- package/dist/orchestrator/templates/ai-src/commands/review.md +2 -0
- package/dist/orchestrator/templates/ai-src/commands/run-test.md +14 -10
- package/dist/orchestrator/templates/ai-src/config/claude.md +20 -7
- package/dist/orchestrator/templates/ai-src/config/copilot.md +20 -7
- package/dist/orchestrator/templates/ai-src/partials/hand-back-rule.claude.md +20 -0
- package/dist/orchestrator/templates/ai-src/partials/hand-back-rule.codex.md +20 -0
- package/dist/orchestrator/templates/ai-src/partials/hand-back-rule.copilot.md +20 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-api-coverage-model/SKILL.md +1 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-capture-mobile/SKILL.md +3 -3
- package/dist/orchestrator/templates/ai-src/skills/sungen-data-factory/SKILL.md +9 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +2 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +1 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-harness-audit/SKILL.md +2 -0
- package/dist/orchestrator/templates/claude-hooks/sungen-hook.cjs +64 -0
- package/dist/utils/safe-yaml-parse.d.ts +8 -0
- package/dist/utils/safe-yaml-parse.d.ts.map +1 -0
- package/dist/utils/safe-yaml-parse.js +51 -0
- package/dist/utils/safe-yaml-parse.js.map +1 -0
- package/package.json +3 -3
- package/src/basis/anchor-grammar.ts +83 -0
- package/src/basis/anchor-hash.ts +27 -0
- package/src/basis/bm51-tables.ts +65 -0
- package/src/basis/extract-spec-anchors.ts +95 -0
- package/src/basis/migration-sentinel.ts +175 -0
- package/src/basis/spec-frontmatter.ts +68 -0
- package/src/basis/spec-sections.ts +35 -0
- package/src/basis/test-basis-schema.ts +81 -0
- package/src/capabilities/discover.ts +15 -2
- package/src/capabilities/registry.ts +16 -0
- package/src/cli/commands/audit.ts +10 -5
- package/src/cli/commands/capability.ts +4 -2
- package/src/cli/commands/contract-derive-command.ts +61 -0
- package/src/cli/commands/contract.ts +130 -0
- package/src/cli/commands/defect.ts +49 -0
- package/src/cli/commands/delivery.ts +92 -15
- package/src/cli/commands/generate.ts +3 -1
- package/src/cli/commands/journey.ts +41 -16
- package/src/cli/commands/next.ts +64 -10
- package/src/cli/commands/triage.ts +57 -0
- package/src/cli/commands/update.ts +35 -8
- package/src/cli/index.ts +24 -30
- package/src/cli/project-sungen-invocation.ts +39 -0
- package/src/contract/approve-contract.ts +94 -0
- package/src/contract/assertion-matcher-vocabulary.ts +93 -0
- package/src/contract/build-contract-verdict.ts +108 -0
- package/src/contract/build-trace-conformance.ts +100 -0
- package/src/contract/classify-contract-change.ts +92 -0
- package/src/contract/classify-contract-unit.ts +47 -0
- package/src/contract/classify-trace-conformance.ts +116 -0
- package/src/contract/contract-git.ts +106 -0
- package/src/contract/contract-hash.ts +34 -0
- package/src/contract/contract-report-file.ts +25 -0
- package/src/contract/contract-schema.ts +137 -0
- package/src/contract/contract-units.ts +52 -0
- package/src/contract/contracts-lock-file.ts +97 -0
- package/src/contract/data-normalize.ts +66 -0
- package/src/contract/derive-free-feature.ts +120 -0
- package/src/contract/field-semantic-redaction.ts +80 -0
- package/src/contract/free-feature-blocks.ts +56 -0
- package/src/contract/lock-secret-guard.ts +95 -0
- package/src/contract/lock-vs-git-head.ts +38 -0
- package/src/contract/normalize-contract-step.ts +74 -0
- package/src/contract/normalize-feature-steps.ts +93 -0
- package/src/contract/oracle-strength.ts +80 -0
- package/src/contract/project-contract-blockers.ts +36 -0
- package/src/contract/record-contract-approval.ts +70 -0
- package/src/contract/render-free-gherkin.ts +113 -0
- package/src/contract/render-trace-sentence.ts +98 -0
- package/src/contract/render-trace-target.ts +68 -0
- package/src/contract/resolve-unit-trace-identities.ts +35 -0
- package/src/contract/trace/collect-trace-evidence.ts +109 -0
- package/src/contract/trace/identity-cache.ts +61 -0
- package/src/contract/trace/select-final-attempt.ts +70 -0
- package/src/contract/trace/snapshot-html-renderer.ts +136 -0
- package/src/contract/trace/snapshot-resolver.ts +66 -0
- package/src/contract/trace/snapshot-store.ts +151 -0
- package/src/contract/trace/target-identity.ts +104 -0
- package/src/contract/trace/test-step-join.ts +104 -0
- package/src/contract/trace/trace-binding.ts +56 -0
- package/src/contract/trace/trace-file-reader.ts +133 -0
- package/src/contract/trace/trace-fingerprint.ts +68 -0
- package/src/contract/trace/trace-identity-resolution.ts +74 -0
- package/src/contract/trace/trace-run-record.ts +80 -0
- package/src/contract/trace/trace-steps.ts +88 -0
- package/src/contract/trace/trace-zip-limits.ts +77 -0
- package/src/contract/trace/trace-zip-reader.ts +127 -0
- package/src/contract/verify-approval-decisions.ts +60 -0
- package/src/decisions/append-decision.ts +35 -0
- package/src/decisions/confirmed-decision-commands.ts +149 -0
- package/src/decisions/decision-file.ts +93 -0
- package/src/decisions/decision-identity.ts +73 -0
- package/src/decisions/decision-integrity-check.ts +61 -0
- package/src/decisions/decision-schema.ts +76 -0
- package/src/decisions/decision-source.ts +14 -0
- package/src/decisions/exclusive-file-create.ts +42 -0
- package/src/decisions/git-head-trust.ts +32 -0
- package/src/decisions/human-confirmation.ts +119 -0
- package/src/decisions/legacy-waiver-migration.ts +135 -0
- package/src/decisions/read-decisions.ts +61 -0
- package/src/decisions/viewpoint-baseline-adapter.ts +74 -0
- package/src/decisions/waiver-decisions.ts +56 -0
- package/src/exporters/csv-exporter.ts +5 -1
- package/src/exporters/feature-parser.ts +12 -0
- package/src/exporters/matrix/build.ts +62 -18
- package/src/exporters/matrix/carry-manual-results.ts +70 -7
- package/src/exporters/matrix/export.ts +17 -2
- package/src/exporters/matrix/gates.ts +62 -129
- package/src/exporters/matrix/render-csv.ts +4 -1
- package/src/exporters/matrix/render-gate-overrides-sheet.ts +50 -0
- package/src/exporters/matrix/render-xlsx.ts +6 -0
- package/src/exporters/matrix/types.ts +9 -1
- package/src/exporters/matrix/xlsx-cells.ts +1 -1
- package/src/exporters/playwright-report-parser.ts +1 -0
- package/src/exporters/scenario-merger.ts +2 -1
- package/src/exporters/types.ts +5 -0
- package/src/generators/test-generator/code-generator.ts +13 -0
- package/src/graph/evaluate-graph.ts +66 -0
- package/src/graph/locate-finding.ts +93 -0
- package/src/graph/node-decl.ts +72 -0
- package/src/graph/nodes/core-node-catalog.ts +15 -0
- package/src/graph/nodes/test-case-contract-nodes.ts +74 -0
- package/src/graph/nodes/test-case-nodes.ts +68 -0
- package/src/graph/nodes/unit-audit-nodes.ts +95 -0
- package/src/graph/nodes/unit-contract-node.ts +89 -0
- package/src/graph/nodes/unit-design-nodes.ts +89 -0
- package/src/graph/nodes/unit-execution-nodes.ts +77 -0
- package/src/graph/test-case-identity.ts +75 -0
- package/src/graph/test-case-results.ts +90 -0
- package/src/graph/test-case-satisfied.ts +55 -0
- package/src/graph/test-case-state-shape.ts +62 -0
- package/src/graph/test-case-states.ts +84 -0
- package/src/graph/unit-artifact-probes.ts +77 -0
- package/src/graph/unit-state-facts.ts +112 -0
- package/src/harness/audit.ts +46 -11
- package/src/harness/catalog/gate-rules.yaml +647 -0
- package/src/harness/counted-assertions.ts +33 -0
- package/src/harness/defect-draft.ts +115 -0
- package/src/harness/defects.ts +94 -0
- package/src/harness/gates/audit-gate-terms.ts +80 -0
- package/src/harness/gates/audit-gate-verdict.ts +78 -0
- package/src/harness/gates/audit-input-hash.ts +55 -0
- package/src/harness/gates/audit-report-verification.ts +201 -0
- package/src/harness/gates/decisions-hash.ts +42 -0
- package/src/harness/gates/delivery-checklist.ts +120 -0
- package/src/harness/gates/delivery-decision-gate.ts +139 -0
- package/src/harness/gates/delivery-decision-step.ts +89 -0
- package/src/harness/gates/gate-override-report.ts +114 -0
- package/src/harness/gates/gate-override-scope.ts +66 -0
- package/src/harness/gates/gate-override-validation.ts +75 -0
- package/src/harness/gates/gate-rule-catalog.ts +82 -0
- package/src/harness/gates/gate-terms.ts +59 -0
- package/src/harness/gates/in-run-decision-check.ts +99 -0
- package/src/harness/gates/journey-gate-inputs.ts +89 -0
- package/src/harness/gates/migrated-waiver-review.ts +68 -0
- package/src/harness/gates/resolve-rule.ts +94 -0
- package/src/harness/gates/unit-gate-overrides.ts +99 -0
- package/src/harness/journey.ts +215 -38
- package/src/harness/manifest.ts +69 -45
- package/src/harness/next-step.ts +60 -170
- package/src/harness/parse.ts +40 -13
- package/src/harness/provenance.ts +99 -11
- package/src/harness/script-check.ts +27 -6
- package/src/harness/sensors.ts +73 -47
- package/src/harness/triage-signals.ts +46 -0
- package/src/harness/triage-unit.ts +113 -0
- package/src/harness/triage.ts +127 -0
- package/src/harness/viewpoint-ledger.ts +2 -2
- package/src/hooks/bash-write-targets.ts +109 -0
- package/src/hooks/claude-hook-entry.ts +85 -0
- package/src/hooks/claude-transcript-turn.ts +96 -0
- package/src/hooks/guard-delivery-contract-rule.ts +110 -0
- package/src/hooks/guard-protected-paths.ts +71 -0
- package/src/hooks/guard-rules.ts +98 -0
- package/src/hooks/guard-tty-spoof-rule.ts +31 -0
- package/src/hooks/hand-back-core.ts +112 -0
- package/src/hooks/hook-cli-command.ts +17 -0
- package/src/hooks/hook-registry.ts +24 -0
- package/src/hooks/hook-result-contract.ts +114 -0
- package/src/hooks/hook-toggles.ts +66 -0
- package/src/hooks/pre-tool-guard-hook.ts +67 -0
- package/src/hooks/session-start-unit-state-hook.ts +56 -0
- package/src/hooks/stop-next-step-hook.ts +37 -0
- package/src/hooks/sungen-project-root.ts +80 -0
- package/src/orchestrator/ai-skills/command-partial-includes.ts +72 -0
- package/src/orchestrator/ai-skills/commands.ts +22 -8
- package/src/orchestrator/ai-skills/golden-skills.test.ts +23 -0
- package/src/orchestrator/assets-drift.ts +12 -5
- package/src/orchestrator/claude-hooks-install.ts +168 -0
- package/src/orchestrator/lockstep-version.ts +28 -15
- package/src/orchestrator/project-initializer.ts +22 -0
- package/src/orchestrator/screen-manager.ts +3 -1
- package/src/orchestrator/templates/ai-src/commands/add-api.md +2 -0
- package/src/orchestrator/templates/ai-src/commands/add-flow.md +2 -0
- package/src/orchestrator/templates/ai-src/commands/add-screen.md +2 -0
- package/src/orchestrator/templates/ai-src/commands/create-data-test.md +1 -0
- package/src/orchestrator/templates/ai-src/commands/create-test.md +14 -16
- package/src/orchestrator/templates/ai-src/commands/dashboard.md +2 -0
- package/src/orchestrator/templates/ai-src/commands/delivery.md +2 -0
- package/src/orchestrator/templates/ai-src/commands/design.md +2 -0
- package/src/orchestrator/templates/ai-src/commands/feedback.md +2 -0
- package/src/orchestrator/templates/ai-src/commands/locale.md +2 -0
- package/src/orchestrator/templates/ai-src/commands/review.md +2 -0
- package/src/orchestrator/templates/ai-src/commands/run-test.md +14 -10
- package/src/orchestrator/templates/ai-src/config/claude.md +20 -7
- package/src/orchestrator/templates/ai-src/config/copilot.md +20 -7
- package/src/orchestrator/templates/ai-src/partials/hand-back-rule.claude.md +20 -0
- package/src/orchestrator/templates/ai-src/partials/hand-back-rule.codex.md +20 -0
- package/src/orchestrator/templates/ai-src/partials/hand-back-rule.copilot.md +20 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-api-coverage-model/SKILL.md +1 -1
- package/src/orchestrator/templates/ai-src/skills/sungen-capture-mobile/SKILL.md +3 -3
- package/src/orchestrator/templates/ai-src/skills/sungen-data-factory/SKILL.md +9 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +2 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +1 -1
- package/src/orchestrator/templates/ai-src/skills/sungen-harness-audit/SKILL.md +2 -0
- package/src/orchestrator/templates/claude-hooks/sungen-hook.cjs +64 -0
- package/src/utils/safe-yaml-parse.ts +44 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Test Contract's data model (DOS-14.1 – 14.4) and the strict check of a stored lock entry.
|
|
3
|
+
*
|
|
4
|
+
* step {kind: action|assert, verb, target?:{ref, also?, parent?, nth?}, value?, matcher?, expected?, negated?, table?}
|
|
5
|
+
* lock qa/<kind>/<unit>/contracts.lock.yaml → {schema: 1, scenarios: {<scenario id>: LockEntry}}
|
|
6
|
+
* entry {hash, steps, implementation, approvedAt, decisionId, humanApproved?}
|
|
7
|
+
*
|
|
8
|
+
* A stored entry is checked the way the decision layer checks its records: a wrong type or an
|
|
9
|
+
* UNKNOWN key is an error, because a field outside the hash could carry meaning the hash does not
|
|
10
|
+
* vouch for. `humanApproved` counts only when it is literally `true` (takumi T5).
|
|
11
|
+
*/
|
|
12
|
+
import { DECISION_ID_RE } from '../decisions/decision-schema';
|
|
13
|
+
|
|
14
|
+
export const IMPLEMENTATIONS = ['compiled', 'impl:code', 'free'] as const;
|
|
15
|
+
export type Implementation = typeof IMPLEMENTATIONS[number];
|
|
16
|
+
|
|
17
|
+
export interface ContractTarget {
|
|
18
|
+
/** The first `[Reference]` of the step, or the data path of an `expect {{x}} …` step. */
|
|
19
|
+
ref: string;
|
|
20
|
+
/** Further `[References]` in the order written (`drag [A] to [B]`, `all [Card] contain [Child]`). */
|
|
21
|
+
also?: string[];
|
|
22
|
+
/** The `in [Parent] <container>` scope, as `<container> [<Parent>]` plus its `with` tail. */
|
|
23
|
+
parent?: string;
|
|
24
|
+
nth?: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ContractStep {
|
|
28
|
+
kind: 'action' | 'assert';
|
|
29
|
+
/** The sentence with the actor dropped, refs as `[]`, values as `{}`, lowercased — the step's exact form. */
|
|
30
|
+
verb: string;
|
|
31
|
+
target?: ContractTarget;
|
|
32
|
+
/** An action's input: `{{var}}` kept as written, a literal without its quotes; several joined by ` | `. */
|
|
33
|
+
value?: string;
|
|
34
|
+
/** An assertion's check, from the closed vocabulary in oracle-strength.ts (`unknown` when it has none). */
|
|
35
|
+
matcher?: string;
|
|
36
|
+
/** An assertion's expected value(s), same form as `value`. */
|
|
37
|
+
expected?: string;
|
|
38
|
+
negated?: true;
|
|
39
|
+
/** The step's data table, header row first. */
|
|
40
|
+
table?: string[][];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface ContractScenario {
|
|
44
|
+
id: string;
|
|
45
|
+
implementation: Implementation;
|
|
46
|
+
steps: ContractStep[];
|
|
47
|
+
hash: string;
|
|
48
|
+
/** Where it was read — never hashed. `outline`: a Scenario Outline, which the compiler does not support. */
|
|
49
|
+
source: { file: string; name: string; hasId: boolean; lines: number[]; outline: boolean };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface LockEntry {
|
|
53
|
+
hash: string;
|
|
54
|
+
steps: ContractStep[];
|
|
55
|
+
implementation: Implementation;
|
|
56
|
+
approvedAt: string;
|
|
57
|
+
decisionId: string;
|
|
58
|
+
humanApproved?: true;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const LOCK_SCHEMA = 1;
|
|
62
|
+
export interface ContractLock { schema: typeof LOCK_SCHEMA; scenarios: Record<string, LockEntry> }
|
|
63
|
+
|
|
64
|
+
export type LadderClass = 'match' | 'strengthened' | 'hash-changed' | 'oracle-weakened' | 'target-changed' | 'assertion-lost';
|
|
65
|
+
/** Classes only a trace can produce (DOS-14.6) — free / impl:code scenarios. */
|
|
66
|
+
export type TraceClass = 'opaque' | 'nondeterministic' | 'inconclusive' | 'unproven';
|
|
67
|
+
export type ScenarioClass = LadderClass | 'proposed' | TraceClass;
|
|
68
|
+
export type FindingClass = Exclude<LadderClass, 'match'> | 'lock-uncommitted' | TraceClass;
|
|
69
|
+
|
|
70
|
+
/** Exit ≠ 0 (DOS-14.4, DOS-14.6). `unproven` applies to free / impl:code only. */
|
|
71
|
+
export const HARD_CLASSES: ReadonlySet<string> = new Set(['oracle-weakened', 'target-changed', 'assertion-lost', 'lock-uncommitted', 'unproven']);
|
|
72
|
+
/** Exit 0, but delivery waits for QA (DOS-14.4): changed-not-weaker, checks no code can compare, and an
|
|
73
|
+
* approved free scenario whose final attempt did not pass (inconclusive — review decision M6). */
|
|
74
|
+
export const REVIEW_CLASSES: ReadonlySet<string> = new Set(['hash-changed', 'opaque', 'inconclusive']);
|
|
75
|
+
|
|
76
|
+
/** The catalog rule each class is reported under (gate-rules.yaml); `strengthened` has none. */
|
|
77
|
+
export const RULE_OF: Readonly<Partial<Record<FindingClass, string>>> = {
|
|
78
|
+
'hash-changed': 'contract.hash-changed',
|
|
79
|
+
'oracle-weakened': 'contract.oracle-weakened',
|
|
80
|
+
'target-changed': 'contract.target-changed',
|
|
81
|
+
'assertion-lost': 'contract.assertion-lost',
|
|
82
|
+
'lock-uncommitted': 'contract.lock-uncommitted',
|
|
83
|
+
opaque: 'contract.opaque-step',
|
|
84
|
+
nondeterministic: 'contract.nondeterministic',
|
|
85
|
+
inconclusive: 'contract.inconclusive',
|
|
86
|
+
unproven: 'contract.unproven',
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const STEP_KEYS = new Set(['kind', 'verb', 'target', 'value', 'matcher', 'expected', 'negated', 'table']);
|
|
90
|
+
const TARGET_KEYS = new Set(['ref', 'also', 'parent', 'nth']);
|
|
91
|
+
const ENTRY_KEYS = new Set(['hash', 'steps', 'implementation', 'approvedAt', 'decisionId', 'humanApproved']);
|
|
92
|
+
const HASH_RE = /^[0-9a-f]{12}$/;
|
|
93
|
+
|
|
94
|
+
const isObject = (v: unknown): v is Record<string, unknown> => typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
95
|
+
const isStringList = (v: unknown): v is string[] => Array.isArray(v) && v.every((x) => typeof x === 'string');
|
|
96
|
+
const unknownKeys = (o: Record<string, unknown>, allowed: Set<string>, at: string): string[] =>
|
|
97
|
+
Object.keys(o).filter((k) => !allowed.has(k)).map((k) => `${at}: unknown key "${k}"`);
|
|
98
|
+
|
|
99
|
+
function targetErrors(t: unknown, at: string): string[] {
|
|
100
|
+
if (!isObject(t)) return [`${at}: target must be a mapping`];
|
|
101
|
+
const e = unknownKeys(t, TARGET_KEYS, at);
|
|
102
|
+
if (typeof t.ref !== 'string') e.push(`${at}: target.ref must be a string`);
|
|
103
|
+
if (t.also !== undefined && !isStringList(t.also)) e.push(`${at}: target.also must be a list of strings`);
|
|
104
|
+
if (t.parent !== undefined && typeof t.parent !== 'string') e.push(`${at}: target.parent must be a string`);
|
|
105
|
+
if (t.nth !== undefined && !(Number.isInteger(t.nth) && (t.nth as number) > 0)) e.push(`${at}: target.nth must be a positive integer`);
|
|
106
|
+
return e;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function stepErrors(s: unknown, at: string): string[] {
|
|
110
|
+
if (!isObject(s)) return [`${at}: not a mapping`];
|
|
111
|
+
const e = unknownKeys(s, STEP_KEYS, at);
|
|
112
|
+
if (s.kind !== 'action' && s.kind !== 'assert') e.push(`${at}: kind must be action|assert`);
|
|
113
|
+
if (typeof s.verb !== 'string' || !s.verb) e.push(`${at}: verb is required`);
|
|
114
|
+
if (s.target !== undefined) e.push(...targetErrors(s.target, at));
|
|
115
|
+
for (const k of ['value', 'matcher', 'expected'] as const) if (s[k] !== undefined && typeof s[k] !== 'string') e.push(`${at}: ${k} must be a string`);
|
|
116
|
+
if (s.negated !== undefined && s.negated !== true) e.push(`${at}: negated is either true or absent`);
|
|
117
|
+
if (s.table !== undefined && !(Array.isArray(s.table) && s.table.every(isStringList))) e.push(`${at}: table must be rows of strings`);
|
|
118
|
+
return e;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Errors in one stored lock entry (empty = well-formed). The hash itself is checked by the reader. */
|
|
122
|
+
export function lockEntryErrors(raw: unknown, id: string): string[] {
|
|
123
|
+
const at = `scenario ${id}`;
|
|
124
|
+
if (!isObject(raw)) return [`${at}: not a mapping`];
|
|
125
|
+
const e = unknownKeys(raw, ENTRY_KEYS, at);
|
|
126
|
+
if (typeof raw.hash !== 'string' || !HASH_RE.test(raw.hash)) e.push(`${at}: hash must be 12 hex characters`);
|
|
127
|
+
if (!IMPLEMENTATIONS.includes(raw.implementation as Implementation)) e.push(`${at}: implementation must be ${IMPLEMENTATIONS.join('|')}`);
|
|
128
|
+
if (typeof raw.approvedAt !== 'string' || Number.isNaN(Date.parse(raw.approvedAt))) e.push(`${at}: approvedAt must be an ISO-8601 timestamp`);
|
|
129
|
+
if (typeof raw.decisionId !== 'string' || !DECISION_ID_RE.test(raw.decisionId)) e.push(`${at}: decisionId must be D-<16 hex>`);
|
|
130
|
+
if (raw.humanApproved !== undefined && raw.humanApproved !== true) e.push(`${at}: humanApproved is either true or absent`);
|
|
131
|
+
if (!Array.isArray(raw.steps)) e.push(`${at}: steps must be a list`);
|
|
132
|
+
else raw.steps.forEach((s, i) => e.push(...stepErrors(s, `${at} step ${i + 1}`)));
|
|
133
|
+
return e;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Approved by a human on a TTY — and nothing else counts (an absent field is `proposed`, T5). */
|
|
137
|
+
export const isHumanApproved = (entry: LockEntry | undefined): entry is LockEntry => entry?.humanApproved === true;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which units a contract command or a `sungen delivery` call touches — the same units `delivery`
|
|
3
|
+
* enumerates (cli/commands/delivery.ts `listAllTargets` / `resolveTargetsFromArg`), restated here
|
|
4
|
+
* so the guard does not load the whole delivery command; a test pins the two enumerations together.
|
|
5
|
+
*
|
|
6
|
+
* every unit qa/screens/*, qa/flows/*, qa/api/* (not `flows`), and qa/api/flows/* as `flows/<name>`
|
|
7
|
+
* a name EVERY directory it can mean (a screen and a flow of one name are both checked);
|
|
8
|
+
* for delivery only, a name that is no unit falls back to the units hosting a
|
|
9
|
+
* feature file of that basename
|
|
10
|
+
*
|
|
11
|
+
* Names come from untrusted input (a Bash command): they are only compared with enumerated directory
|
|
12
|
+
* names, never joined into a path.
|
|
13
|
+
*/
|
|
14
|
+
import * as fs from 'fs';
|
|
15
|
+
import * as path from 'path';
|
|
16
|
+
import { featureFilesFor } from '../harness/unit-paths';
|
|
17
|
+
|
|
18
|
+
export interface ContractUnitRef { name: string; dir: string }
|
|
19
|
+
|
|
20
|
+
function subdirs(dir: string): string[] {
|
|
21
|
+
try { return fs.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).sort(); } catch { return []; }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function allContractUnits(root: string): ContractUnitRef[] {
|
|
25
|
+
const qa = path.join(root, 'qa');
|
|
26
|
+
const out: ContractUnitRef[] = [];
|
|
27
|
+
for (const kind of ['screens', 'flows', 'api']) {
|
|
28
|
+
for (const name of subdirs(path.join(qa, kind))) if (!(kind === 'api' && name === 'flows')) out.push({ name, dir: path.join(qa, kind, name) });
|
|
29
|
+
}
|
|
30
|
+
for (const name of subdirs(path.join(qa, 'api', 'flows'))) out.push({ name: `flows/${name}`, dir: path.join(qa, 'api', 'flows', name) });
|
|
31
|
+
return out;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Every unit directory `name` can mean (`pay` matches a flow `pay` and an api flow `flows/pay`). */
|
|
35
|
+
export function contractUnitDirsNamed(root: string, name: string): ContractUnitRef[] {
|
|
36
|
+
return allContractUnits(root).filter((u) => u.name === name || u.name === `flows/${name}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** The units `sungen delivery <name>` delivers. */
|
|
40
|
+
export function deliveryUnitsNamed(root: string, name: string): ContractUnitRef[] {
|
|
41
|
+
const direct = contractUnitDirsNamed(root, name);
|
|
42
|
+
if (direct.length) return direct;
|
|
43
|
+
return allContractUnits(root).filter((u) => featureFilesFor(u.dir, u.name).some((f) => path.basename(f, '.feature') === name && fs.existsSync(f)));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** The one unit a contract command names; an unknown or ambiguous name is an error, never a guess. */
|
|
47
|
+
export function resolveContractUnit(root: string, name: string): ContractUnitRef {
|
|
48
|
+
const found = contractUnitDirsNamed(root, name);
|
|
49
|
+
if (found.length === 1) return found[0];
|
|
50
|
+
if (!found.length) throw new Error(`Not found: qa/screens/${name}, qa/flows/${name}, qa/api/${name} or qa/api/flows/${name}`);
|
|
51
|
+
throw new Error(`"${name}" names ${found.length} units (${found.map((u) => path.relative(root, u.dir)).join(', ')}) — rename one so the contract knows which`);
|
|
52
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `qa/<kind>/<unit>/contracts.lock.yaml` — read, check, write (DOS-14.3, DOS-13.8).
|
|
3
|
+
*
|
|
4
|
+
* read safe YAML (core schema, few aliases, no duplicate keys, byte cap); every entry checked
|
|
5
|
+
* strictly. A structural problem throws ContractLockError — a lock that cannot be read is
|
|
6
|
+
* not "nothing locked" (CLI exit 1, guard `ask`). An entry whose steps no longer hash to its
|
|
7
|
+
* `hash` was edited by hand: it is returned in `tampered` and judged as a hard finding.
|
|
8
|
+
* write scenario ids sorted, one block per scenario, written atomically.
|
|
9
|
+
*/
|
|
10
|
+
import * as fs from 'fs';
|
|
11
|
+
import * as path from 'path';
|
|
12
|
+
import { stringify } from 'yaml';
|
|
13
|
+
import { writeFileAtomic } from '../harness/write-atomic';
|
|
14
|
+
import { safeYamlParse } from '../utils/safe-yaml-parse';
|
|
15
|
+
import { contractHash } from './contract-hash';
|
|
16
|
+
import { ContractLock, ContractScenario, ContractStep, LOCK_SCHEMA, LockEntry, lockEntryErrors } from './contract-schema';
|
|
17
|
+
|
|
18
|
+
export const LOCK_FILE_NAME = 'contracts.lock.yaml';
|
|
19
|
+
export const lockPathFor = (unitDir: string): string => path.join(unitDir, LOCK_FILE_NAME);
|
|
20
|
+
|
|
21
|
+
const HEADER = '# Sungen Test Contract lock (DOS-14.3) — written only by `sungen contract approve` on a terminal.\n'
|
|
22
|
+
+ '# One block per scenario id. Commit it through review: `sungen contract check` compares it with git HEAD.\n';
|
|
23
|
+
|
|
24
|
+
export class ContractLockError extends Error {
|
|
25
|
+
constructor(public readonly source: string, public readonly problems: string[]) {
|
|
26
|
+
super(`${source} cannot be trusted: ${problems.slice(0, 5).join('; ')}${problems.length > 5 ? ` (+${problems.length - 5} more)` : ''}. Restore it from git, or delete it and approve again on a terminal.`);
|
|
27
|
+
this.name = 'ContractLockError';
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const isObject = (v: unknown): v is Record<string, unknown> => typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* A lock as read. `tampered`: well-formed entries whose steps no longer hash to their `hash` — edited
|
|
35
|
+
* by hand. They are not a read error: they are a hard finding (`contract.lock-uncommitted`), so the
|
|
36
|
+
* delivery guard denies instead of asking.
|
|
37
|
+
*/
|
|
38
|
+
export interface ReadLock extends ContractLock { tampered: string[] }
|
|
39
|
+
|
|
40
|
+
export function parseLockText(text: string, source: string): ReadLock {
|
|
41
|
+
let raw: unknown;
|
|
42
|
+
try { raw = safeYamlParse(text, source); } catch (e) { throw new ContractLockError(source, [e instanceof Error ? e.message : String(e)]); }
|
|
43
|
+
if (!isObject(raw)) throw new ContractLockError(source, ['not a mapping']);
|
|
44
|
+
const problems = Object.keys(raw).filter((k) => k !== 'schema' && k !== 'scenarios').map((k) => `unknown key "${k}"`);
|
|
45
|
+
if (raw.schema !== LOCK_SCHEMA) problems.push(`schema must be ${LOCK_SCHEMA}`);
|
|
46
|
+
if (!isObject(raw.scenarios)) problems.push('scenarios must be a mapping');
|
|
47
|
+
const scenarios = isObject(raw.scenarios) ? raw.scenarios : {};
|
|
48
|
+
const tampered: string[] = [];
|
|
49
|
+
for (const [id, entry] of Object.entries(scenarios)) {
|
|
50
|
+
const errors = lockEntryErrors(entry, id);
|
|
51
|
+
problems.push(...errors);
|
|
52
|
+
const e = entry as unknown as LockEntry;
|
|
53
|
+
if (!errors.length && contractHash(e.implementation, e.steps) !== e.hash) tampered.push(id);
|
|
54
|
+
}
|
|
55
|
+
if (problems.length) throw new ContractLockError(source, problems);
|
|
56
|
+
return { schema: LOCK_SCHEMA, scenarios: scenarios as Record<string, LockEntry>, tampered };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** The lock at `file`, or null when there is none. Throws ContractLockError when it cannot be read as a lock. */
|
|
60
|
+
export function readLockFile(file: string): ReadLock | null {
|
|
61
|
+
let text: string;
|
|
62
|
+
try { text = fs.readFileSync(file, 'utf8'); } catch (e) {
|
|
63
|
+
if ((e as NodeJS.ErrnoException).code === 'ENOENT') return null;
|
|
64
|
+
throw new ContractLockError(file, [e instanceof Error ? e.message : String(e)]);
|
|
65
|
+
}
|
|
66
|
+
return parseLockText(text, file);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** The steps in their written key order, so the YAML reads the way DOS-14.1 lists the fields. */
|
|
70
|
+
function orderedStep(s: ContractStep): ContractStep {
|
|
71
|
+
const t = s.target && { ref: s.target.ref, also: s.target.also, parent: s.target.parent, nth: s.target.nth };
|
|
72
|
+
const o = { kind: s.kind, verb: s.verb, target: t, value: s.value, matcher: s.matcher, expected: s.expected, negated: s.negated, table: s.table };
|
|
73
|
+
return JSON.parse(JSON.stringify(o)) as ContractStep; // drops the undefined fields
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function serializeLock(lock: ContractLock): string {
|
|
77
|
+
const scenarios: Record<string, LockEntry> = {};
|
|
78
|
+
for (const id of Object.keys(lock.scenarios).sort((a, b) => a.localeCompare(b, 'en'))) {
|
|
79
|
+
const e = lock.scenarios[id];
|
|
80
|
+
scenarios[id] = {
|
|
81
|
+
hash: e.hash, implementation: e.implementation, approvedAt: e.approvedAt, decisionId: e.decisionId,
|
|
82
|
+
...(e.humanApproved === true ? { humanApproved: true as const } : {}),
|
|
83
|
+
steps: e.steps.map(orderedStep),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
return HEADER + stringify({ schema: LOCK_SCHEMA, scenarios }, { schema: 'core', lineWidth: 0 });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function writeLockFile(file: string, lock: ContractLock): void {
|
|
90
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
91
|
+
writeFileAtomic(file, serializeLock(lock));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** The entry a TTY approval writes for one scenario. Only the approve path sets `humanApproved`. */
|
|
95
|
+
export function lockEntryFor(sc: ContractScenario, decisionId: string, approvedAt: string): LockEntry {
|
|
96
|
+
return { hash: sc.hash, steps: sc.steps, implementation: sc.implementation, approvedAt, decisionId, humanApproved: true };
|
|
97
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dynamic data in a trace → stable contract values (DOS-14.6), so two runs of the same test give
|
|
3
|
+
* the same fingerprint:
|
|
4
|
+
*
|
|
5
|
+
* a value equal to a test-data value → `{{key}}` (the unit's `test-data/<unit>.yaml`, flattened)
|
|
6
|
+
* uuid → `{{$dynamic:uuid}}`
|
|
7
|
+
* ISO date / date-time → `{{$dynamic:iso-date}}`
|
|
8
|
+
* epoch ms (13 digits) / s (10 digits) → `{{$dynamic:timestamp}}`
|
|
9
|
+
* any other run of 6+ digits → `{{$dynamic:number}}`
|
|
10
|
+
*
|
|
11
|
+
* Dynamic parts are replaced INSIDE a value (`qa+1792638683664@x.dev` → `qa+{{$dynamic:timestamp}}@x.dev`).
|
|
12
|
+
* Test-data matches only on the WHOLE value, and only for values of 3+ characters — `1` or `ok` in
|
|
13
|
+
* test-data must not swallow every `1` a page shows. Several keys with one value → the first by name.
|
|
14
|
+
*/
|
|
15
|
+
import * as fs from 'fs';
|
|
16
|
+
import * as path from 'path';
|
|
17
|
+
import { safeYamlParse } from '../utils/safe-yaml-parse';
|
|
18
|
+
|
|
19
|
+
export interface TestDataIndex { byValue: Map<string, string> }
|
|
20
|
+
|
|
21
|
+
const DYNAMIC: Array<[RegExp, string]> = [
|
|
22
|
+
[/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, 'uuid'],
|
|
23
|
+
[/\d{4}-\d{2}-\d{2}(?:[T ]\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:?\d{2})?)?/g, 'iso-date'],
|
|
24
|
+
[/(?<!\d)(?:1\d{12}|1\d{9})(?!\d)/g, 'timestamp'],
|
|
25
|
+
[/(?<!\d)\d{6,}(?!\d)/g, 'number'],
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
function flatten(v: unknown, prefix: string, out: Map<string, string>): void {
|
|
29
|
+
if (v === null || v === undefined) return;
|
|
30
|
+
if (typeof v === 'string' || typeof v === 'number') {
|
|
31
|
+
const s = String(v);
|
|
32
|
+
if (prefix && s.length >= 3 && !out.has(s)) out.set(s, prefix);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (Array.isArray(v)) { v.forEach((x, i) => flatten(x, `${prefix}.${i}`, out)); return; }
|
|
36
|
+
if (typeof v === 'object') {
|
|
37
|
+
for (const k of Object.keys(v as Record<string, unknown>).sort()) flatten((v as Record<string, unknown>)[k], prefix ? `${prefix}.${k}` : k, out);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** The unit's base test-data file (environment overlays are not the contract's business). */
|
|
42
|
+
export function loadTestDataIndex(unitDir: string, unit: string): TestDataIndex {
|
|
43
|
+
const file = path.join(unitDir, 'test-data', `${path.basename(unit)}.yaml`);
|
|
44
|
+
const byValue = new Map<string, string>();
|
|
45
|
+
if (!fs.existsSync(file)) return { byValue };
|
|
46
|
+
let doc: unknown;
|
|
47
|
+
try { doc = safeYamlParse(fs.readFileSync(file, 'utf8')); } catch (e) {
|
|
48
|
+
throw new Error(`cannot read ${path.basename(file)} to normalize trace values: ${(e as Error).message.split('\n')[0]}`);
|
|
49
|
+
}
|
|
50
|
+
flatten(doc, '', byValue);
|
|
51
|
+
return { byValue };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Replace the dynamic parts of a value; whole-value test-data matches win. */
|
|
55
|
+
export function normalizeValue(value: string, index: TestDataIndex): string {
|
|
56
|
+
const key = index.byValue.get(value);
|
|
57
|
+
if (key) return `{{${key}}}`;
|
|
58
|
+
let out = value;
|
|
59
|
+
for (const [re, kind] of DYNAMIC) out = out.replace(re, `\u0000${kind}\u0001`);
|
|
60
|
+
return out.replace(/\u0000([a-z-]+)\u0001/g, '{{$dynamic:$1}}');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Names and texts the page shows: only the dynamic parts move (a name is never a test-data key). */
|
|
64
|
+
export function normalizeShownText(text: string): string {
|
|
65
|
+
return normalizeValue(text, { byValue: new Map() });
|
|
66
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `sungen contract derive --screen <u>` (DOS-14.8): free tests' traces → `features/<u>.free.feature`,
|
|
3
|
+
* every scenario tagged `@free @proposed`, built DETERMINISTICALLY from the trace — no LLM.
|
|
4
|
+
*
|
|
5
|
+
* which tests `--grep <re>` over "<id> <title> <file>", else those whose test file is named
|
|
6
|
+
* after the unit (a path segment `<u>`, or a file `<u>.*` / `<u>-*`)
|
|
7
|
+
* which trace the final attempt, passed, bound to its run (a failed one is skipped, never derived)
|
|
8
|
+
* identities asked of the registered `traceIdentity` resolver (driver-ui) when one is given,
|
|
9
|
+
* otherwise the cache, otherwise core's heuristic — `# identity low` marks those steps
|
|
10
|
+
* approved a scenario QA already approved (no `@proposed`) is never rewritten
|
|
11
|
+
* safety redaction by field meaning, machine paths scrubbed, then the secret scanner and the
|
|
12
|
+
* semantic credential check over the exact text: any hit → NOTHING is written
|
|
13
|
+
*/
|
|
14
|
+
import * as fs from 'fs';
|
|
15
|
+
import * as os from 'os';
|
|
16
|
+
import * as path from 'path';
|
|
17
|
+
import { readCapabilities } from '../harness/capability';
|
|
18
|
+
import { isIdLike } from '../harness/parse';
|
|
19
|
+
import { MOBILE_NOT_SUPPORTED } from './build-trace-conformance';
|
|
20
|
+
import { loadTestDataIndex } from './data-normalize';
|
|
21
|
+
import { scrubKnownSecrets, scrubMachinePaths, secretScanHits, SecretHit } from './field-semantic-redaction';
|
|
22
|
+
import { idOfTitle, splitFeatureBlocks } from './free-feature-blocks';
|
|
23
|
+
import { sensitiveLiteralHits } from './lock-secret-guard';
|
|
24
|
+
import { scenariosOfFeatureFile } from './normalize-feature-steps';
|
|
25
|
+
import { CONTROL_CHARS, freeFeatureText, renderTraceSteps, scenarioBlockText } from './render-free-gherkin';
|
|
26
|
+
import { collectRuns, TraceRun } from './trace/collect-trace-evidence';
|
|
27
|
+
import { readIdentityCache } from './trace/identity-cache';
|
|
28
|
+
import type { TraceIdentityResolver } from './trace/target-identity';
|
|
29
|
+
import { identitiesFor, resolveWithDriver } from './trace/trace-identity-resolution';
|
|
30
|
+
import { fingerprintOf } from './trace/trace-fingerprint';
|
|
31
|
+
import { traceIntentSteps } from './trace/trace-steps';
|
|
32
|
+
|
|
33
|
+
export interface DeriveInput { projectRoot: string; unit: { name: string; dir: string }; grep?: string; resolver?: TraceIdentityResolver | null }
|
|
34
|
+
export interface DeriveResult {
|
|
35
|
+
file: string;
|
|
36
|
+
written: boolean;
|
|
37
|
+
scenarios: Array<{ id: string; title: string; steps: number; low: number; opaque: number }>;
|
|
38
|
+
/** `approved: true` — skipped because QA already approved it (derive never rewrites one), not for want of a trace. */
|
|
39
|
+
skipped: Array<{ id: string; reason: string; approved?: true }>;
|
|
40
|
+
refused?: SecretHit[];
|
|
41
|
+
note?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const MACHINE_PATH = /(?:\/Users|\/home)\/[^/\s"'`]+\/|[A-Za-z]:\\Users\\/;
|
|
45
|
+
|
|
46
|
+
function belongsToUnit(testFile: string, unit: string): boolean {
|
|
47
|
+
const segs = testFile.toLowerCase().split(/[\\/]/);
|
|
48
|
+
const base = segs[segs.length - 1] ?? '';
|
|
49
|
+
return segs.slice(0, -1).includes(unit) || base.startsWith(`${unit}.`) || base.startsWith(`${unit}-`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** The semantic credential check over the text as it would be written. */
|
|
53
|
+
function semanticHits(text: string, label: string): SecretHit[] {
|
|
54
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'sungen-derive-'));
|
|
55
|
+
try {
|
|
56
|
+
const file = path.join(dir, label);
|
|
57
|
+
fs.writeFileSync(file, text);
|
|
58
|
+
return sensitiveLiteralHits(scenariosOfFeatureFile(file));
|
|
59
|
+
} finally { fs.rmSync(dir, { recursive: true, force: true }); }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Nothing was derived and nothing explains it away: no test of the unit left an admissible trace
|
|
64
|
+
* (#932). Not when the scenarios are approved (derive leaves those alone by design), up to date,
|
|
65
|
+
* refused, or the project is mobile (`note`).
|
|
66
|
+
*/
|
|
67
|
+
export function nothingDerived(r: DeriveResult): boolean {
|
|
68
|
+
return !r.note && !r.refused?.length && r.scenarios.length === 0 && !r.skipped.some((s) => s.approved);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export async function deriveFreeFeature(i: DeriveInput): Promise<DeriveResult> {
|
|
72
|
+
const unitBase = path.basename(i.unit.name);
|
|
73
|
+
const file = path.join(i.unit.dir, 'features', `${unitBase}.free.feature`);
|
|
74
|
+
const result: DeriveResult = { file, written: false, scenarios: [], skipped: [] };
|
|
75
|
+
if ((readCapabilities(i.projectRoot).platform ?? '').toLowerCase() === 'mobile') return { ...result, note: MOBILE_NOT_SUPPORTED };
|
|
76
|
+
const grep = i.grep ? new RegExp(i.grep, 'i') : null;
|
|
77
|
+
const found = collectRuns(i.projectRoot, (id, t) => (grep ? grep.test(`${id} ${t.title} ${t.file}`) : belongsToUnit(t.file, unitBase.toLowerCase())), 0);
|
|
78
|
+
const byId = new Map<string, TraceRun[]>();
|
|
79
|
+
for (const r of found.runs) byId.set(r.scenarioId, [...(byId.get(r.scenarioId) ?? []), r]);
|
|
80
|
+
const admissible = (runs: TraceRun[]): TraceRun[] => runs.filter((r) => r.status === 'admissible' && r.trace)
|
|
81
|
+
.sort((a, b) => `${a.record?.file}|${a.test?.testId}`.localeCompare(`${b.record?.file}|${b.test?.testId}`));
|
|
82
|
+
if (i.resolver) await resolveWithDriver(i.projectRoot, [...byId.values()].flatMap(admissible).map((r) => r.trace!), i.resolver);
|
|
83
|
+
|
|
84
|
+
const { cache } = readIdentityCache(i.projectRoot);
|
|
85
|
+
const index = loadTestDataIndex(i.unit.dir, i.unit.name);
|
|
86
|
+
const before = fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : '';
|
|
87
|
+
const kept = splitFeatureBlocks(before);
|
|
88
|
+
const blocks = new Map(kept.map((b) => [b.id, b.text]));
|
|
89
|
+
for (const id of [...byId.keys()].sort((a, b) => a.localeCompare(b, 'en'))) {
|
|
90
|
+
const runs = admissible(byId.get(id)!);
|
|
91
|
+
if (!runs.length) { result.skipped.push({ id, reason: byId.get(id)![0].reason ?? 'no admissible trace' }); continue; }
|
|
92
|
+
if (kept.some((b) => b.id === id && !b.proposed)) { result.skipped.push({ id, reason: 'approved — derive never rewrites an approved scenario; use `sungen contract check --trace`', approved: true }); continue; }
|
|
93
|
+
const secrets = new Set<string>();
|
|
94
|
+
const rendered = runs.map((r) => {
|
|
95
|
+
const steps = traceIntentSteps(r.trace!);
|
|
96
|
+
return renderTraceSteps(steps, identitiesFor(r.trace!, steps, cache), { index, projectRoot: i.projectRoot, specFile: r.specFile, secrets });
|
|
97
|
+
});
|
|
98
|
+
const run = runs[0];
|
|
99
|
+
const rawTitle = run.test!.title;
|
|
100
|
+
const title = scrubMachinePaths(scrubKnownSecrets(idOfTitle(rawTitle) === id ? rawTitle : `${id} ${rawTitle}`, secrets), i.projectRoot).replace(CONTROL_CHARS, '');
|
|
101
|
+
const variants = new Set(rendered.map((r) => fingerprintOf(r, 'free').hash)).size;
|
|
102
|
+
const comments = [
|
|
103
|
+
`source: ${run.specFile}:${run.test!.line} · run ${run.record!.runId}${run.result!.retry ? ` (retry ${run.result!.retry})` : ''}`,
|
|
104
|
+
...(variants > 1 ? [`nondeterministic: ${variants} runs fingerprint differently — derived from the first`] : []),
|
|
105
|
+
...(isIdLike(id) ? [] : ['test outside the spec: no VP-id (spec missing, or the test is surplus)']),
|
|
106
|
+
];
|
|
107
|
+
blocks.set(id, scenarioBlockText({ id, title, tags: ['@free', '@proposed'], comments, steps: rendered[0] }));
|
|
108
|
+
result.scenarios.push({ id, title, steps: rendered[0].length, low: rendered[0].filter((s) => s.low).length, opaque: rendered[0].filter((s) => s.opaque).length });
|
|
109
|
+
}
|
|
110
|
+
if (!result.scenarios.length) return result;
|
|
111
|
+
|
|
112
|
+
const text = freeFeatureText(unitBase, [...blocks.values()]);
|
|
113
|
+
const label = path.basename(file);
|
|
114
|
+
const hits = [...(MACHINE_PATH.test(text) ? [{ where: label, reason: 'a machine path survived scrubbing' }] : []), ...secretScanHits(text, label), ...semanticHits(text, label)];
|
|
115
|
+
if (hits.length) return { ...result, refused: hits };
|
|
116
|
+
if (text === before) return result;
|
|
117
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
118
|
+
fs.writeFileSync(file, text);
|
|
119
|
+
return { ...result, written: true };
|
|
120
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nothing secret, nothing machine-specific leaves a trace into a file sungen writes (DOS-14.7, G4).
|
|
3
|
+
* A login trace holds the password 13–14 times (step title, call params, snapshot value, request
|
|
4
|
+
* body) and up to 19 `/Users/…` paths (phase 07, R7).
|
|
5
|
+
*
|
|
6
|
+
* by field meaning a value typed into a field that IS a credential — `type=password`, or a name /
|
|
7
|
+
* id / autocomplete / label / accessible name that reads as one (password, token,
|
|
8
|
+
* secret, otp, api key…, whole words) — becomes `{{$redacted}}` (a test-data key
|
|
9
|
+
* wins when the value is in test-data: `{{login.password}}` is a reference)
|
|
10
|
+
* everywhere else every value redacted that way is also scrubbed from ALL other text written
|
|
11
|
+
* (expected texts, opaque verbatims, titles)
|
|
12
|
+
* machine paths the project root becomes relative; any other home directory becomes `~`
|
|
13
|
+
* last line the existing secret scanner runs over the exact text about to be written; a
|
|
14
|
+
* hit refuses the write — nothing is written, the place is named
|
|
15
|
+
*/
|
|
16
|
+
import * as fs from 'fs';
|
|
17
|
+
import * as os from 'os';
|
|
18
|
+
import * as path from 'path';
|
|
19
|
+
import { scanTestDataSecrets } from '../harness/secret-scan';
|
|
20
|
+
import { isSensitiveFieldName } from './lock-secret-guard';
|
|
21
|
+
import type { TargetFacts } from './trace/target-identity';
|
|
22
|
+
|
|
23
|
+
export const REDACTED = '{{$redacted}}';
|
|
24
|
+
|
|
25
|
+
const CREDENTIAL_AUTOCOMPLETE = /(?:^|\s)(?:current-password|new-password|one-time-code|cc-number|cc-csc)(?:\s|$)/i;
|
|
26
|
+
|
|
27
|
+
/** Is the element a value was typed into a credential field? */
|
|
28
|
+
export function isSensitiveTarget(facts: TargetFacts | null | undefined, accessibleName?: string): boolean {
|
|
29
|
+
const a = facts?.attrs ?? {};
|
|
30
|
+
if ((a.type ?? '').toLowerCase() === 'password') return true;
|
|
31
|
+
if (CREDENTIAL_AUTOCOMPLETE.test(a.autocomplete ?? '')) return true;
|
|
32
|
+
return [a.name, a.id, a.autocomplete, a['aria-label'], a.placeholder, facts?.labelText, accessibleName].some((n) => isSensitiveFieldName(n));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Replace every occurrence of a known secret value in `text`. Values shorter than 4 chars are left (noise). */
|
|
36
|
+
export function scrubKnownSecrets(text: string, secrets: Iterable<string>): string {
|
|
37
|
+
let out = text;
|
|
38
|
+
for (const s of [...new Set(secrets)].filter((x) => x.length >= 4).sort((a, b) => b.length - a.length)) out = out.split(s).join(REDACTED);
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Project-relative paths; other home directories → `~` (`/Users/<name>`, `/home/<name>`, `C:\Users\<name>`). */
|
|
43
|
+
export function scrubMachinePaths(text: string, projectRoot: string): string {
|
|
44
|
+
const root = path.resolve(projectRoot);
|
|
45
|
+
return text
|
|
46
|
+
.split(root + path.sep).join('')
|
|
47
|
+
.split(root).join('.')
|
|
48
|
+
.replace(/(?:\/Users|\/home)\/[^/\s"'`]+/g, '~')
|
|
49
|
+
.replace(/[A-Za-z]:\\Users\\[^\\\s"'`]+/g, '~');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** A path for display: project-relative when inside the project, the basename otherwise. */
|
|
53
|
+
export function displayPath(file: string, projectRoot: string): string {
|
|
54
|
+
const rel = path.relative(path.resolve(projectRoot), path.resolve(projectRoot, file));
|
|
55
|
+
return rel.startsWith('..') || path.isAbsolute(rel) ? path.basename(file) : rel.split(path.sep).join('/');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface SecretHit { where: string; reason: string }
|
|
59
|
+
|
|
60
|
+
/** The secret scanner over the exact text about to be written to `label`. */
|
|
61
|
+
export function secretScanHits(text: string, label: string): SecretHit[] {
|
|
62
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'sungen-trace-scan-'));
|
|
63
|
+
try {
|
|
64
|
+
fs.mkdirSync(path.join(dir, 'test-data'));
|
|
65
|
+
fs.writeFileSync(path.join(dir, 'test-data', 'scan.yaml'), text);
|
|
66
|
+
const result = scanTestDataSecrets(dir);
|
|
67
|
+
if (result.unscanned.length) throw new Error(`the secret scan could not read the text it was given (${result.unscanned[0].reason})`);
|
|
68
|
+
return result.hits.map((h) => ({ where: `${label}:${h.line}`, reason: h.reason }));
|
|
69
|
+
} finally {
|
|
70
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export class TraceSecretError extends Error {
|
|
75
|
+
constructor(public readonly hits: SecretHit[]) {
|
|
76
|
+
super(`Refused: ${hits.length} likely secret(s) would be written. Nothing was written.\n${hits.map((h) => ` ${h.where} — ${h.reason}`).join('\n')}`
|
|
77
|
+
+ '\n Move the value into test-data (or an env overlay) and use it from there, then derive again.');
|
|
78
|
+
this.name = 'TraceSecretError';
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scenario blocks of a derived `features/<unit>.free.feature` (DOS-14.8) — so `contract derive`
|
|
3
|
+
* can replace what is still `@proposed` while leaving every APPROVED scenario exactly as QA approved
|
|
4
|
+
* it, and `contract approve` can drop the `@proposed` tag of what it just locked.
|
|
5
|
+
*
|
|
6
|
+
* A block is a scenario's tag/comment lines directly above its `Scenario:` line, through the line
|
|
7
|
+
* before the next block. Its id is the title's leading id (`VP-LOGIN-001`), else the title — the
|
|
8
|
+
* same rule the contract uses.
|
|
9
|
+
*/
|
|
10
|
+
import * as fs from 'fs';
|
|
11
|
+
import { isIdLike } from '../harness/parse';
|
|
12
|
+
|
|
13
|
+
export interface FeatureBlock { id: string; proposed: boolean; text: string }
|
|
14
|
+
|
|
15
|
+
const SCENARIO = /^\s*Scenario(?: Outline| Template)?:\s*(.*)$/;
|
|
16
|
+
const TAG_OR_COMMENT = /^\s*(?:@|#)/;
|
|
17
|
+
|
|
18
|
+
export function idOfTitle(title: string): string {
|
|
19
|
+
const lead = title.match(/^\s*([A-Za-z][A-Za-z0-9.-]*)/)?.[1];
|
|
20
|
+
return lead && isIdLike(lead) ? lead.toUpperCase() : title.replace(/\s+/g, ' ').trim();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function splitFeatureBlocks(text: string): FeatureBlock[] {
|
|
24
|
+
const lines = text.split(/\r?\n/);
|
|
25
|
+
const featureAt = lines.findIndex((l) => /^\s*Feature:/.test(l));
|
|
26
|
+
const heads = lines.map((l, i) => (SCENARIO.test(l) && i > featureAt ? i : -1)).filter((i) => i >= 0);
|
|
27
|
+
const starts = heads.map((h, k) => {
|
|
28
|
+
let s = h;
|
|
29
|
+
const floor = k > 0 ? heads[k - 1] + 1 : featureAt + 1;
|
|
30
|
+
while (s - 1 >= floor && TAG_OR_COMMENT.test(lines[s - 1])) s--;
|
|
31
|
+
return s;
|
|
32
|
+
});
|
|
33
|
+
return heads.map((h, k) => {
|
|
34
|
+
const end = k + 1 < starts.length ? starts[k + 1] : lines.length;
|
|
35
|
+
const body = lines.slice(starts[k], end);
|
|
36
|
+
while (body.length && !body[body.length - 1].trim()) body.pop();
|
|
37
|
+
const tags = lines.slice(starts[k], h).filter((l) => /^\s*@/.test(l)).join(' ');
|
|
38
|
+
return { id: idOfTitle(SCENARIO.exec(lines[h])![1]), proposed: /(^|\s)@proposed(\s|$)/.test(tags), text: body.join('\n') };
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Approve dropped the tag: remove `@proposed` from the given scenarios. Returns true when the file changed. */
|
|
43
|
+
export function dropProposedTags(file: string, ids: ReadonlySet<string>): boolean {
|
|
44
|
+
if (!fs.existsSync(file)) return false;
|
|
45
|
+
const text = fs.readFileSync(file, 'utf8');
|
|
46
|
+
let changed = false;
|
|
47
|
+
let out = text;
|
|
48
|
+
for (const b of splitFeatureBlocks(text)) {
|
|
49
|
+
if (!b.proposed || !ids.has(b.id)) continue;
|
|
50
|
+
const next = b.text.split('\n').map((l) => (/^\s*@/.test(l) ? l.replace(/\s*@proposed(?=\s|$)/g, '') : l)).filter((l) => l.trim() !== '').join('\n');
|
|
51
|
+
out = out.replace(b.text, () => next);
|
|
52
|
+
changed = true;
|
|
53
|
+
}
|
|
54
|
+
if (changed) fs.writeFileSync(file, out);
|
|
55
|
+
return changed;
|
|
56
|
+
}
|