@sun-asterisk/sungen 3.2.32 → 3.2.34-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands/audit.d.ts +5 -0
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/audit.js +64 -11
- 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 +40 -18
- package/dist/cli/commands/capability.js.map +1 -1
- package/dist/cli/commands/changed.d.ts.map +1 -1
- package/dist/cli/commands/changed.js +2 -3
- package/dist/cli/commands/changed.js.map +1 -1
- package/dist/cli/commands/dashboard.d.ts.map +1 -1
- package/dist/cli/commands/dashboard.js +2 -28
- package/dist/cli/commands/dashboard.js.map +1 -1
- 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 +113 -59
- 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 +14 -7
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/cli/commands/results-merge.d.ts +9 -0
- package/dist/cli/commands/results-merge.d.ts.map +1 -1
- package/dist/cli/commands/results-merge.js +9 -12
- package/dist/cli/commands/results-merge.js.map +1 -1
- package/dist/cli/commands/results-sanitize.d.ts.map +1 -1
- package/dist/cli/commands/results-sanitize.js +2 -13
- package/dist/cli/commands/results-sanitize.js.map +1 -1
- package/dist/cli/commands/select.d.ts +12 -0
- package/dist/cli/commands/select.d.ts.map +1 -0
- package/dist/cli/commands/select.js +122 -0
- package/dist/cli/commands/select.js.map +1 -0
- 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.map +1 -1
- package/dist/cli/commands/update.js +3 -0
- package/dist/cli/commands/update.js.map +1 -1
- package/dist/cli/commands/viewpoint.d.ts +7 -0
- package/dist/cli/commands/viewpoint.d.ts.map +1 -0
- package/dist/cli/commands/viewpoint.js +70 -0
- package/dist/cli/commands/viewpoint.js.map +1 -0
- package/dist/cli/index.js +13 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/dashboard/snapshot-builder.d.ts.map +1 -1
- package/dist/dashboard/snapshot-builder.js +6 -10
- package/dist/dashboard/snapshot-builder.js.map +1 -1
- package/dist/dashboard/templates/index.html +1 -1
- package/dist/dashboard/types.d.ts +1 -0
- package/dist/dashboard/types.d.ts.map +1 -1
- 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 +12 -11
- package/dist/exporters/csv-exporter.js.map +1 -1
- package/dist/exporters/feature-parser.d.ts +29 -0
- package/dist/exporters/feature-parser.d.ts.map +1 -1
- package/dist/exporters/feature-parser.js +44 -1
- package/dist/exporters/feature-parser.js.map +1 -1
- package/dist/exporters/json-exporter.d.ts.map +1 -1
- package/dist/exporters/json-exporter.js +7 -0
- package/dist/exporters/json-exporter.js.map +1 -1
- package/dist/exporters/locale-variants.d.ts +7 -1
- package/dist/exporters/locale-variants.d.ts.map +1 -1
- package/dist/exporters/locale-variants.js +23 -16
- package/dist/exporters/locale-variants.js.map +1 -1
- package/dist/exporters/manual-hybrid-oracle.d.ts +54 -0
- package/dist/exporters/manual-hybrid-oracle.d.ts.map +1 -0
- package/dist/exporters/manual-hybrid-oracle.js +114 -0
- package/dist/exporters/manual-hybrid-oracle.js.map +1 -0
- package/dist/exporters/matrix/build.d.ts.map +1 -1
- package/dist/exporters/matrix/build.js +68 -19
- package/dist/exporters/matrix/build.js.map +1 -1
- package/dist/exporters/matrix/carry-manual-results.d.ts +89 -0
- package/dist/exporters/matrix/carry-manual-results.d.ts.map +1 -0
- package/dist/exporters/matrix/carry-manual-results.js +302 -0
- package/dist/exporters/matrix/carry-manual-results.js.map +1 -0
- package/dist/exporters/matrix/export.d.ts +9 -1
- package/dist/exporters/matrix/export.d.ts.map +1 -1
- package/dist/exporters/matrix/export.js +21 -3
- package/dist/exporters/matrix/export.js.map +1 -1
- package/dist/exporters/matrix/gates.js +6 -0
- package/dist/exporters/matrix/gates.js.map +1 -1
- package/dist/exporters/matrix/render-csv.js +1 -1
- package/dist/exporters/matrix/render-csv.js.map +1 -1
- package/dist/exporters/matrix/render-testcase-sheet.d.ts.map +1 -1
- package/dist/exporters/matrix/render-testcase-sheet.js +10 -4
- package/dist/exporters/matrix/render-testcase-sheet.js.map +1 -1
- package/dist/exporters/matrix/render-xlsx.d.ts.map +1 -1
- package/dist/exporters/matrix/render-xlsx.js +3 -0
- package/dist/exporters/matrix/render-xlsx.js.map +1 -1
- package/dist/exporters/matrix/types.d.ts +13 -1
- package/dist/exporters/matrix/types.d.ts.map +1 -1
- package/dist/exporters/matrix/types.js.map +1 -1
- package/dist/exporters/matrix/wording.d.ts +8 -1
- package/dist/exporters/matrix/wording.d.ts.map +1 -1
- package/dist/exporters/matrix/wording.js +37 -2
- package/dist/exporters/matrix/wording.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/run-environment.d.ts +32 -0
- package/dist/exporters/run-environment.d.ts.map +1 -0
- package/dist/exporters/run-environment.js +125 -0
- package/dist/exporters/run-environment.js.map +1 -0
- package/dist/exporters/scenario-merger.d.ts +18 -0
- package/dist/exporters/scenario-merger.d.ts.map +1 -1
- package/dist/exporters/scenario-merger.js +49 -3
- package/dist/exporters/scenario-merger.js.map +1 -1
- package/dist/exporters/test-data-resolver.d.ts +0 -4
- package/dist/exporters/test-data-resolver.d.ts.map +1 -1
- package/dist/exporters/test-data-resolver.js +0 -7
- package/dist/exporters/test-data-resolver.js.map +1 -1
- package/dist/exporters/types.d.ts +5 -0
- package/dist/exporters/types.d.ts.map +1 -1
- package/dist/exporters/xlsx-exporter.d.ts +2 -0
- package/dist/exporters/xlsx-exporter.d.ts.map +1 -1
- package/dist/exporters/xlsx-exporter.js +7 -2
- package/dist/exporters/xlsx-exporter.js.map +1 -1
- package/dist/generators/gherkin-parser/index.d.ts +11 -4
- package/dist/generators/gherkin-parser/index.d.ts.map +1 -1
- package/dist/generators/gherkin-parser/index.js +49 -19
- package/dist/generators/gherkin-parser/index.js.map +1 -1
- package/dist/generators/test-generator/adapters/adapter-interface.d.ts +4 -0
- package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -1
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/reinstall-app-action.hbs +2 -2
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/label-value-assertion.hbs +8 -2
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/visible-with-role-variable-assertion.hbs +7 -3
- package/dist/generators/test-generator/adapters/appium/templates/test-file.hbs +68 -4
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/click-select-action.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/select-action.hbs +1 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/label-value-assertion.hbs +7 -1
- package/dist/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-role-variable-assertion.hbs +8 -4
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/pick-option.hbs +13 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/partials/url-assertion.hbs +5 -1
- package/dist/generators/test-generator/adapters/playwright/templates/test-file.hbs +16 -0
- package/dist/generators/test-generator/code-generator.d.ts +2 -1
- package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
- package/dist/generators/test-generator/code-generator.js +90 -5
- package/dist/generators/test-generator/code-generator.js.map +1 -1
- package/dist/generators/test-generator/diagnostics.d.ts +36 -5
- package/dist/generators/test-generator/diagnostics.d.ts.map +1 -1
- package/dist/generators/test-generator/diagnostics.js +35 -4
- package/dist/generators/test-generator/diagnostics.js.map +1 -1
- package/dist/generators/test-generator/patterns/index.d.ts +12 -2
- package/dist/generators/test-generator/patterns/index.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/index.js +15 -9
- package/dist/generators/test-generator/patterns/index.js.map +1 -1
- package/dist/generators/test-generator/patterns/types.d.ts +15 -0
- package/dist/generators/test-generator/patterns/types.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/with-parent-scope.d.ts +5 -0
- package/dist/generators/test-generator/patterns/with-parent-scope.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/with-parent-scope.js +6 -0
- package/dist/generators/test-generator/patterns/with-parent-scope.js.map +1 -1
- package/dist/generators/test-generator/step-mapper.d.ts +46 -0
- package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
- package/dist/generators/test-generator/step-mapper.js +84 -9
- package/dist/generators/test-generator/step-mapper.js.map +1 -1
- package/dist/generators/test-generator/utils/data-resolver.d.ts +25 -0
- package/dist/generators/test-generator/utils/data-resolver.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/data-resolver.js +39 -8
- package/dist/generators/test-generator/utils/data-resolver.js.map +1 -1
- package/dist/generators/test-generator/utils/selector-entry-fields.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/selector-entry-fields.js +1 -0
- package/dist/generators/test-generator/utils/selector-entry-fields.js.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.d.ts +37 -4
- package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.js +212 -79
- package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
- package/dist/harness/audit.d.ts +3 -0
- package/dist/harness/audit.d.ts.map +1 -1
- package/dist/harness/audit.js +60 -19
- package/dist/harness/audit.js.map +1 -1
- package/dist/harness/capability.d.ts +7 -1
- package/dist/harness/capability.d.ts.map +1 -1
- package/dist/harness/capability.js +15 -12
- package/dist/harness/capability.js.map +1 -1
- package/dist/harness/catalog/universal-viewpoints.yaml +5 -5
- package/dist/harness/config-file.d.ts +17 -0
- package/dist/harness/config-file.d.ts.map +1 -0
- package/dist/harness/config-file.js +100 -0
- package/dist/harness/config-file.js.map +1 -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/data-driven-lint.d.ts.map +1 -1
- package/dist/harness/data-driven-lint.js +69 -27
- package/dist/harness/data-driven-lint.js.map +1 -1
- 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/depth-lint.d.ts.map +1 -1
- package/dist/harness/depth-lint.js +2 -1
- package/dist/harness/depth-lint.js.map +1 -1
- package/dist/harness/flow-check.d.ts.map +1 -1
- package/dist/harness/flow-check.js +6 -2
- package/dist/harness/flow-check.js.map +1 -1
- package/dist/harness/keyword-match.d.ts +59 -0
- package/dist/harness/keyword-match.d.ts.map +1 -0
- package/dist/harness/keyword-match.js +86 -0
- package/dist/harness/keyword-match.js.map +1 -0
- package/dist/harness/manifest.d.ts.map +1 -1
- package/dist/harness/manifest.js +2 -2
- package/dist/harness/manifest.js.map +1 -1
- package/dist/harness/next-step.d.ts +2 -4
- package/dist/harness/next-step.d.ts.map +1 -1
- package/dist/harness/next-step.js +50 -5
- package/dist/harness/next-step.js.map +1 -1
- package/dist/harness/parse.d.ts +45 -0
- package/dist/harness/parse.d.ts.map +1 -1
- package/dist/harness/parse.js +142 -24
- package/dist/harness/parse.js.map +1 -1
- package/dist/harness/perf.d.ts +7 -1
- package/dist/harness/perf.d.ts.map +1 -1
- package/dist/harness/perf.js +13 -10
- package/dist/harness/perf.js.map +1 -1
- package/dist/harness/quality-gates.d.ts +9 -2
- package/dist/harness/quality-gates.d.ts.map +1 -1
- package/dist/harness/quality-gates.js +22 -8
- package/dist/harness/quality-gates.js.map +1 -1
- package/dist/harness/query-catalog.js +0 -0
- package/dist/harness/query-catalog.js.map +1 -1
- package/dist/harness/read-text.d.ts +21 -0
- package/dist/harness/read-text.d.ts.map +1 -1
- package/dist/harness/read-text.js +32 -1
- package/dist/harness/read-text.js.map +1 -1
- package/dist/harness/script-check.d.ts.map +1 -1
- package/dist/harness/script-check.js +7 -1
- package/dist/harness/script-check.js.map +1 -1
- package/dist/harness/secret-scan.d.ts +27 -1
- package/dist/harness/secret-scan.d.ts.map +1 -1
- package/dist/harness/secret-scan.js +57 -4
- package/dist/harness/secret-scan.js.map +1 -1
- package/dist/harness/select-by-ids.d.ts +31 -0
- package/dist/harness/select-by-ids.d.ts.map +1 -0
- package/dist/harness/select-by-ids.js +106 -0
- package/dist/harness/select-by-ids.js.map +1 -0
- package/dist/harness/sensors.d.ts +20 -1
- package/dist/harness/sensors.d.ts.map +1 -1
- package/dist/harness/sensors.js +133 -39
- package/dist/harness/sensors.js.map +1 -1
- package/dist/harness/spec-branches.d.ts +0 -18
- package/dist/harness/spec-branches.d.ts.map +1 -1
- package/dist/harness/spec-branches.js +24 -3
- package/dist/harness/spec-branches.js.map +1 -1
- package/dist/harness/spec-coverage.d.ts.map +1 -1
- package/dist/harness/spec-coverage.js +14 -4
- package/dist/harness/spec-coverage.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/ui-inventory.d.ts +40 -0
- package/dist/harness/ui-inventory.d.ts.map +1 -0
- package/dist/harness/ui-inventory.js +191 -0
- package/dist/harness/ui-inventory.js.map +1 -0
- package/dist/harness/viewpoint-baseline.d.ts.map +1 -1
- package/dist/harness/viewpoint-baseline.js +7 -1
- package/dist/harness/viewpoint-baseline.js.map +1 -1
- package/dist/harness/viewpoint-draft-header.d.ts +7 -0
- package/dist/harness/viewpoint-draft-header.d.ts.map +1 -0
- package/dist/harness/viewpoint-draft-header.js +100 -0
- package/dist/harness/viewpoint-draft-header.js.map +1 -0
- package/dist/harness/viewpoint-ledger.d.ts +13 -3
- package/dist/harness/viewpoint-ledger.d.ts.map +1 -1
- package/dist/harness/viewpoint-ledger.js +106 -19
- package/dist/harness/viewpoint-ledger.js.map +1 -1
- package/dist/harness/viewpoint-mode.d.ts +9 -0
- package/dist/harness/viewpoint-mode.d.ts.map +1 -0
- package/dist/harness/viewpoint-mode.js +52 -0
- package/dist/harness/viewpoint-mode.js.map +1 -0
- package/dist/harness/viewpoint-priority.d.ts +19 -0
- package/dist/harness/viewpoint-priority.d.ts.map +1 -0
- package/dist/harness/viewpoint-priority.js +36 -0
- package/dist/harness/viewpoint-priority.js.map +1 -0
- package/dist/harness/viewpoint-review-open.d.ts +6 -0
- package/dist/harness/viewpoint-review-open.d.ts.map +1 -0
- package/dist/harness/viewpoint-review-open.js +90 -0
- package/dist/harness/viewpoint-review-open.js.map +1 -0
- package/dist/harness/viewpoint-scan-refs.d.ts +50 -0
- package/dist/harness/viewpoint-scan-refs.d.ts.map +1 -0
- package/dist/harness/viewpoint-scan-refs.js +200 -0
- package/dist/harness/viewpoint-scan-refs.js.map +1 -0
- package/dist/harness/viewpoint-scan.d.ts +41 -0
- package/dist/harness/viewpoint-scan.d.ts.map +1 -0
- package/dist/harness/viewpoint-scan.js +118 -0
- package/dist/harness/viewpoint-scan.js.map +1 -0
- package/dist/harness/write-atomic.d.ts +2 -0
- package/dist/harness/write-atomic.d.ts.map +1 -0
- package/dist/harness/write-atomic.js +62 -0
- package/dist/harness/write-atomic.js.map +1 -0
- package/dist/orchestrator/ai-skills/writer.d.ts.map +1 -1
- package/dist/orchestrator/ai-skills/writer.js +15 -7
- package/dist/orchestrator/ai-skills/writer.js.map +1 -1
- package/dist/orchestrator/assets-drift.d.ts.map +1 -1
- package/dist/orchestrator/assets-drift.js +11 -6
- package/dist/orchestrator/assets-drift.js.map +1 -1
- package/dist/orchestrator/flow-manager.d.ts +0 -1
- package/dist/orchestrator/flow-manager.d.ts.map +1 -1
- package/dist/orchestrator/flow-manager.js +3 -52
- package/dist/orchestrator/flow-manager.js.map +1 -1
- package/dist/orchestrator/project-deps-sync.d.ts.map +1 -1
- package/dist/orchestrator/project-deps-sync.js +22 -1
- package/dist/orchestrator/project-deps-sync.js.map +1 -1
- package/dist/orchestrator/project-initializer.d.ts +8 -0
- package/dist/orchestrator/project-initializer.d.ts.map +1 -1
- package/dist/orchestrator/project-initializer.js +49 -7
- 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 +8 -62
- package/dist/orchestrator/screen-manager.js.map +1 -1
- package/dist/orchestrator/security-overrides-catalog.d.ts +39 -0
- package/dist/orchestrator/security-overrides-catalog.d.ts.map +1 -0
- package/dist/orchestrator/security-overrides-catalog.js +63 -0
- package/dist/orchestrator/security-overrides-catalog.js.map +1 -0
- package/dist/orchestrator/security-overrides.d.ts +51 -0
- package/dist/orchestrator/security-overrides.d.ts.map +1 -0
- package/dist/orchestrator/security-overrides.js +165 -0
- package/dist/orchestrator/security-overrides.js.map +1 -0
- package/dist/orchestrator/templates/ai-src/agents/discovery.md +11 -3
- package/dist/orchestrator/templates/ai-src/agents/reviewer.md +8 -0
- package/dist/orchestrator/templates/ai-src/commands/create-test.md +32 -24
- package/dist/orchestrator/templates/ai-src/commands/review.md +2 -2
- package/dist/orchestrator/templates/ai-src/commands/run-test.md +78 -15
- package/dist/orchestrator/templates/ai-src/skills/sungen-capture/mode-local.md +2 -2
- package/dist/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +14 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +25 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +26 -8
- package/dist/orchestrator/templates/ai-src/skills/sungen-harness-audit/SKILL.md +5 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +58 -14
- package/dist/orchestrator/templates/ai-src/skills/sungen-selector-keys/SKILL.md +13 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +42 -2
- package/dist/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +7 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-viewpoint/SKILL.md +18 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-viewpoint/review-checklist.md +63 -0
- package/dist/orchestrator/templates/ai-src/skills/sungen-viewpoint/viewpoint-template.md +158 -0
- package/dist/orchestrator/templates/playwright.config.ts +4 -2
- package/dist/orchestrator/templates/specs-locale-fixture.ts +21 -9
- package/dist/orchestrator/templates/specs-pw-shape-reporter.ts +0 -0
- package/dist/orchestrator/templates/specs-url-assert.d.ts +17 -1
- package/dist/orchestrator/templates/specs-url-assert.d.ts.map +1 -1
- package/dist/orchestrator/templates/specs-url-assert.js +38 -4
- package/dist/orchestrator/templates/specs-url-assert.js.map +1 -1
- package/dist/orchestrator/templates/specs-url-assert.ts +47 -4
- package/dist/orchestrator/templates/wdio.conf.ts +24 -1
- package/dist/orchestrator/trace-mode-install.d.ts +14 -0
- package/dist/orchestrator/trace-mode-install.d.ts.map +1 -0
- package/dist/orchestrator/trace-mode-install.js +132 -0
- package/dist/orchestrator/trace-mode-install.js.map +1 -0
- package/dist/orchestrator/viewpoint-pattern-catalog.d.ts +23 -0
- package/dist/orchestrator/viewpoint-pattern-catalog.d.ts.map +1 -0
- package/dist/orchestrator/viewpoint-pattern-catalog.js +48 -0
- package/dist/orchestrator/viewpoint-pattern-catalog.js.map +1 -0
- package/dist/orchestrator/viewpoint-scaffold.d.ts +3 -0
- package/dist/orchestrator/viewpoint-scaffold.d.ts.map +1 -0
- package/dist/orchestrator/viewpoint-scaffold.js +70 -0
- package/dist/orchestrator/viewpoint-scaffold.js.map +1 -0
- package/package.json +3 -3
- package/src/cli/commands/audit.ts +59 -12
- package/src/cli/commands/capability.ts +54 -19
- package/src/cli/commands/changed.ts +2 -4
- package/src/cli/commands/dashboard.ts +1 -26
- package/src/cli/commands/defect.ts +49 -0
- package/src/cli/commands/delivery.ts +106 -56
- package/src/cli/commands/generate.ts +15 -7
- package/src/cli/commands/results-merge.ts +9 -9
- package/src/cli/commands/results-sanitize.ts +2 -9
- package/src/cli/commands/select.ts +88 -0
- package/src/cli/commands/triage.ts +57 -0
- package/src/cli/commands/update.ts +3 -0
- package/src/cli/commands/viewpoint.ts +58 -0
- package/src/cli/index.ts +13 -2
- package/src/dashboard/snapshot-builder.ts +6 -8
- package/src/dashboard/templates/index.html +1 -1
- package/src/dashboard/types.ts +1 -0
- package/src/exporters/csv-exporter.ts +12 -11
- package/src/exporters/feature-parser.ts +44 -2
- package/src/exporters/json-exporter.ts +6 -1
- package/src/exporters/locale-variants.ts +23 -17
- package/src/exporters/manual-hybrid-oracle.ts +112 -0
- package/src/exporters/matrix/build.ts +71 -19
- package/src/exporters/matrix/carry-manual-results.ts +292 -0
- package/src/exporters/matrix/export.ts +24 -4
- package/src/exporters/matrix/gates.ts +6 -0
- package/src/exporters/matrix/render-csv.ts +1 -1
- package/src/exporters/matrix/render-testcase-sheet.ts +10 -4
- package/src/exporters/matrix/render-xlsx.ts +3 -0
- package/src/exporters/matrix/types.ts +13 -1
- package/src/exporters/matrix/wording.ts +36 -2
- package/src/exporters/playwright-report-parser.ts +1 -0
- package/src/exporters/run-environment.ts +89 -0
- package/src/exporters/scenario-merger.ts +51 -5
- package/src/exporters/test-data-resolver.ts +0 -6
- package/src/exporters/types.ts +5 -0
- package/src/exporters/xlsx-exporter.ts +7 -2
- package/src/generators/gherkin-parser/index.ts +50 -18
- package/src/generators/test-generator/adapters/adapter-interface.ts +6 -2
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/reinstall-app-action.hbs +2 -2
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/label-value-assertion.hbs +8 -2
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/visible-with-role-variable-assertion.hbs +7 -3
- package/src/generators/test-generator/adapters/appium/templates/test-file.hbs +68 -4
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/click-select-action.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/select-action.hbs +1 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/label-value-assertion.hbs +7 -1
- package/src/generators/test-generator/adapters/playwright/templates/steps/assertions/visible-with-role-variable-assertion.hbs +8 -4
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/pick-option.hbs +13 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/partials/url-assertion.hbs +5 -1
- package/src/generators/test-generator/adapters/playwright/templates/test-file.hbs +16 -0
- package/src/generators/test-generator/code-generator.ts +77 -5
- package/src/generators/test-generator/diagnostics.ts +36 -5
- package/src/generators/test-generator/patterns/index.ts +15 -8
- package/src/generators/test-generator/patterns/types.ts +15 -0
- package/src/generators/test-generator/patterns/with-parent-scope.ts +6 -0
- package/src/generators/test-generator/step-mapper.ts +90 -8
- package/src/generators/test-generator/utils/data-resolver.ts +41 -12
- package/src/generators/test-generator/utils/selector-entry-fields.ts +1 -0
- package/src/generators/test-generator/utils/selector-resolver.ts +238 -82
- package/src/harness/audit.ts +69 -24
- package/src/harness/capability.ts +23 -12
- package/src/harness/catalog/universal-viewpoints.yaml +5 -5
- package/src/harness/config-file.ts +66 -0
- package/src/harness/counted-assertions.ts +33 -0
- package/src/harness/data-driven-lint.ts +82 -24
- package/src/harness/defect-draft.ts +115 -0
- package/src/harness/defects.ts +94 -0
- package/src/harness/depth-lint.ts +2 -1
- package/src/harness/flow-check.ts +6 -2
- package/src/harness/keyword-match.ts +85 -0
- package/src/harness/manifest.ts +2 -2
- package/src/harness/next-step.ts +47 -5
- package/src/harness/parse.ts +151 -27
- package/src/harness/perf.ts +15 -7
- package/src/harness/quality-gates.ts +22 -8
- package/src/harness/query-catalog.ts +0 -0
- package/src/harness/read-text.ts +32 -1
- package/src/harness/script-check.ts +7 -1
- package/src/harness/secret-scan.ts +60 -4
- package/src/harness/select-by-ids.ts +83 -0
- package/src/harness/sensors.ts +141 -40
- package/src/harness/spec-branches.ts +6 -3
- package/src/harness/spec-coverage.ts +14 -4
- 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/ui-inventory.ts +164 -0
- package/src/harness/viewpoint-baseline.ts +7 -1
- package/src/harness/viewpoint-draft-header.ts +55 -0
- package/src/harness/viewpoint-ledger.ts +113 -25
- package/src/harness/viewpoint-mode.ts +49 -0
- package/src/harness/viewpoint-priority.ts +38 -0
- package/src/harness/viewpoint-review-open.ts +51 -0
- package/src/harness/viewpoint-scan-refs.ts +154 -0
- package/src/harness/viewpoint-scan.ts +111 -0
- package/src/harness/write-atomic.ts +22 -0
- package/src/orchestrator/ai-skills/writer.ts +18 -3
- package/src/orchestrator/assets-drift.ts +10 -5
- package/src/orchestrator/flow-manager.ts +3 -53
- package/src/orchestrator/project-deps-sync.ts +23 -1
- package/src/orchestrator/project-initializer.ts +54 -6
- package/src/orchestrator/screen-manager.ts +7 -61
- package/src/orchestrator/security-overrides-catalog.ts +69 -0
- package/src/orchestrator/security-overrides.ts +149 -0
- package/src/orchestrator/templates/ai-src/agents/discovery.md +11 -3
- package/src/orchestrator/templates/ai-src/agents/reviewer.md +8 -0
- package/src/orchestrator/templates/ai-src/commands/create-test.md +32 -24
- package/src/orchestrator/templates/ai-src/commands/review.md +2 -2
- package/src/orchestrator/templates/ai-src/commands/run-test.md +78 -15
- package/src/orchestrator/templates/ai-src/skills/sungen-capture/mode-local.md +2 -2
- package/src/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +14 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md +25 -1
- package/src/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +26 -8
- package/src/orchestrator/templates/ai-src/skills/sungen-harness-audit/SKILL.md +5 -1
- package/src/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md +58 -14
- package/src/orchestrator/templates/ai-src/skills/sungen-selector-keys/SKILL.md +13 -1
- package/src/orchestrator/templates/ai-src/skills/sungen-tc-generation/SKILL.md +42 -2
- package/src/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +7 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-viewpoint/SKILL.md +18 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-viewpoint/review-checklist.md +63 -0
- package/src/orchestrator/templates/ai-src/skills/sungen-viewpoint/viewpoint-template.md +158 -0
- package/src/orchestrator/templates/playwright.config.ts +4 -2
- package/src/orchestrator/templates/specs-locale-fixture.ts +21 -9
- package/src/orchestrator/templates/specs-pw-shape-reporter.ts +0 -0
- package/src/orchestrator/templates/specs-url-assert.ts +47 -4
- package/src/orchestrator/templates/wdio.conf.ts +24 -1
- package/src/orchestrator/trace-mode-install.ts +94 -0
- package/src/orchestrator/viewpoint-pattern-catalog.ts +61 -0
- package/src/orchestrator/viewpoint-scaffold.ts +36 -0
package/src/harness/audit.ts
CHANGED
|
@@ -32,7 +32,11 @@ const MULTI_SCREEN_RISK_COUNT = MULTI_SCREEN_RISKS.length;
|
|
|
32
32
|
import { capabilityRegistry } from '../capabilities/registry';
|
|
33
33
|
import { discoverAndRegisterCapabilities } from '../capabilities/discover';
|
|
34
34
|
import { contextRouter } from '../capabilities/context-router';
|
|
35
|
-
import { readTextFile } from './read-text';
|
|
35
|
+
import { readTextFile, readViewpointText } from './read-text';
|
|
36
|
+
import { isAiDraft } from './viewpoint-draft-header';
|
|
37
|
+
import { viewpointReviewOpenFindings } from './viewpoint-review-open';
|
|
38
|
+
import { uiInventoryCoverage, uiInventoryFindings, UiInventoryResult } from './ui-inventory';
|
|
39
|
+
import { defectLinkFindings } from './defects';
|
|
36
40
|
|
|
37
41
|
export interface AuditReport {
|
|
38
42
|
screen: string;
|
|
@@ -57,6 +61,8 @@ export interface AuditReport {
|
|
|
57
61
|
flowQuality?: FlowQualityResult; // #569 — flow contract verification (flows only)
|
|
58
62
|
/** #595 — coverage per DECLARED FLOW; absent when the contract declares no `flows:`. */
|
|
59
63
|
flowInventory?: InventoryResult;
|
|
64
|
+
/** #811 — design items vs the suite; present only when the unit holds design sources. */
|
|
65
|
+
uiInventory?: UiInventoryResult;
|
|
60
66
|
calibration: { // #8 — multi-axis score so a high overall can't hide a weak axis
|
|
61
67
|
axes: Record<string, number>;
|
|
62
68
|
weakest: { axis: string; value: number };
|
|
@@ -185,6 +191,7 @@ export function runAudit(screenDir: string, screenName: string): AuditReport {
|
|
|
185
191
|
const scoringCap = scoringCapId ? capabilityRegistry.get(scoringCapId) : undefined;
|
|
186
192
|
const catalog = (scoringCap?.viewpoints?.() as Catalog | undefined) || loadCatalog();
|
|
187
193
|
const spec = specCoverage(specPath, scenarios, featureText);
|
|
194
|
+
const uiInv = uiInventoryCoverage(path.join(screenDir, 'requirements'), featureText);
|
|
188
195
|
|
|
189
196
|
// P3 — intent profile from qa/context.md drives the depth threshold (focus).
|
|
190
197
|
const intent = readIntent(projectRootFromScreenDir(screenDir));
|
|
@@ -196,7 +203,10 @@ export function runAudit(screenDir: string, screenName: string): AuditReport {
|
|
|
196
203
|
// Is the declaration this suite is measured against still the one a human accepted?
|
|
197
204
|
const viewpointBaseline = checkViewpointBaseline(
|
|
198
205
|
projectRootFromScreenDir(screenDir), catalogScreenName, viewpointPath);
|
|
199
|
-
const viewpointText =
|
|
206
|
+
const viewpointText = readViewpointText(viewpointPath);
|
|
207
|
+
// `drafted-by: ai` — unreviewed until `--accept-viewpoint` removes it. Only a draft that declares ids
|
|
208
|
+
// has anything to accept; an id-less one is VIEWPOINT-MISSING's to report, not a dead-end finding.
|
|
209
|
+
const aiDraft = isAiDraft(viewpointText) && viewpointBaseline.status !== 'absent';
|
|
200
210
|
// #569 — a flow WITH a contract is measured against ITS declaration, not a screen's.
|
|
201
211
|
// Keyword page-type detection force-fit flows into screen checklists (a registration
|
|
202
212
|
// flow judged as a `form` screen scored coverage 0%), so for contract-holding flows the
|
|
@@ -211,9 +221,13 @@ export function runAudit(screenDir: string, screenName: string): AuditReport {
|
|
|
211
221
|
? { ...catalog, page_types: {} } as Catalog
|
|
212
222
|
: catalog;
|
|
213
223
|
const capGate = scoringCap?.gateProvider as
|
|
214
|
-
((i: { scenarios: ScenarioInfo[]; viewpoints: ViewpointEntry[]; catalog: Catalog; focus: typeof intent.focus; cwd: string; screenName: string; threshold: number; businessCriticalMethods?: string[] }) => { gate: GateResult; depth: DepthResult }) | undefined;
|
|
215
|
-
|
|
216
|
-
|
|
224
|
+
((i: { scenarios: ScenarioInfo[]; viewpoints: ViewpointEntry[]; catalog: Catalog; focus: typeof intent.focus; cwd: string; screenName: string; threshold: number; businessCriticalMethods?: string[]; isMobile: boolean; viewpointText: string }) => { gate: GateResult; depth: DepthResult }) | undefined;
|
|
225
|
+
// The capability gate gets the SAME isMobile + viewpoint text as the in-core fallback, or a declared
|
|
226
|
+
// `page-type:` is ignored wherever driver-ui scores — every web project, and a mobile project whose
|
|
227
|
+
// CLI cannot resolve driver-mobile (#864).
|
|
228
|
+
const isMobile = platform === 'mobile';
|
|
229
|
+
const provided = capGate?.({ scenarios, viewpoints, catalog: gateCatalog, focus: intent.focus, cwd: projectRootFromScreenDir(screenDir), screenName: catalogScreenName, threshold: depthThresholdFor(intent.focus), businessCriticalMethods: intent.businessCriticalMethods, isMobile, viewpointText });
|
|
230
|
+
const gate = provided?.gate ?? viewpointGate(scenarios, viewpoints, gateCatalog, isMobile, viewpointText);
|
|
217
231
|
const depth = provided?.depth ?? assertionDepth(scenarios, dataThemesFor(gateCatalog, gate.pageType), intent.focus);
|
|
218
232
|
|
|
219
233
|
// TQ-4 — deferral-aware coverage credit: an inherently cross-screen theme (cart / detail /
|
|
@@ -227,7 +241,12 @@ export function runAudit(screenDir: string, screenName: string): AuditReport {
|
|
|
227
241
|
if (flowScenarios.length) {
|
|
228
242
|
for (const c of flowCoveredThemes(gate.gaps, flowScenarios)) {
|
|
229
243
|
const i = gate.gaps.findIndex((g) => g.theme === c.theme);
|
|
230
|
-
if (i >= 0) {
|
|
244
|
+
if (i >= 0) {
|
|
245
|
+
gate.gaps.splice(i, 1); gate.themesCovered++; flowCredits.push(c);
|
|
246
|
+
// Keep the claim-proof list in step with the count (#776) — a theme credited to a flow
|
|
247
|
+
// must SAY it came from a flow, or the evidence lines silently under-report the total.
|
|
248
|
+
gate.coveredBy.push({ theme: c.theme, by: `(flow) ${c.flow}`, keyword: '—' });
|
|
249
|
+
}
|
|
231
250
|
}
|
|
232
251
|
gate.coverageRatio = gate.themesTotal ? gate.themesCovered / gate.themesTotal : 1;
|
|
233
252
|
}
|
|
@@ -292,12 +311,16 @@ export function runAudit(screenDir: string, screenName: string): AuditReport {
|
|
|
292
311
|
// #569 — handoff integrity: a flow's depth is also whether each cross-screen transition
|
|
293
312
|
// is followed by an assertion on the carried state (same floor discipline as the other caps).
|
|
294
313
|
const HANDOFF_FLOOR = 0.5;
|
|
295
|
-
|
|
314
|
+
// Each FLOOR is a floor on the CAP, not on the result — an out-of-range input ratio walks
|
|
315
|
+
// straight through it (a -1.5 oracle ratio yielded businessDepth -0.25 and crashed the CLI's
|
|
316
|
+
// bar renderer, #762). The axis is declared `0..1` above and weighted `critical`, so a negative
|
|
317
|
+
// value would also drag `score.overall` below zero. Clamp once, here, where the axis is formed.
|
|
318
|
+
const businessDepth = Math.max(0, Math.min(1, Math.min(
|
|
296
319
|
depth.bcDepthRatio,
|
|
297
320
|
flowDepth.stateful ? FLOW_DEPTH_FLOOR + (1 - FLOW_DEPTH_FLOOR) * flowDepth.ratio : 1,
|
|
298
|
-
oracle.
|
|
321
|
+
oracle.weakFacet ? ORACLE_FLOOR + (1 - ORACLE_FLOOR) * oracle.ratio : 1,
|
|
299
322
|
flowQ.hasContract && flowQ.handoffs.total > 0 ? HANDOFF_FLOOR + (1 - HANDOFF_FLOOR) * flowQ.handoffs.ratio : 1,
|
|
300
|
-
);
|
|
323
|
+
)));
|
|
301
324
|
// When the taxonomy drifted (most scenarios unclassified), the balance axis is unreliable — cap it
|
|
302
325
|
// at 0.5 instead of awarding a vacuous 1.0 so a stale taxonomy fails loudly, not silently (H1).
|
|
303
326
|
const balanceScore = balance.unclassifiedRatio > 0.4
|
|
@@ -333,7 +356,8 @@ export function runAudit(screenDir: string, screenName: string): AuditReport {
|
|
|
333
356
|
// so it can only measure what their author already thought of. One field run wrote the file
|
|
334
357
|
// after generating the suite and said so in its own header — and both axes still read 100%,
|
|
335
358
|
// because a first audit records the baseline silently (#622). Untrusted until a human accepts it.
|
|
336
|
-
|
|
359
|
+
// An AI draft rides the same hold: its line is only removed when a human accepts it.
|
|
360
|
+
const viewpointMoved = viewpointBaseline.status === 'changed' || adoptedPostHoc || aiDraft;
|
|
337
361
|
// #618 — is there a YARDSTICK at all? `atomicLedger` and `traceability` are both measured
|
|
338
362
|
// against test-viewpoint.md, and four more sensors read it (the ledger, the declaration-integrity
|
|
339
363
|
// checks, the browser-gesture check, the continuity check). When the file is absent every one of
|
|
@@ -364,7 +388,7 @@ export function runAudit(screenDir: string, screenName: string): AuditReport {
|
|
|
364
388
|
{ key: 'atomicLedger', value: ledger.ratio, weight: 0.13, applicable: ledger.hasViewpoint && ledger.total > 0 && !viewpointMoved, critical: true, withheld: viewpointMoved && ledger.hasViewpoint && ledger.total > 0 },
|
|
365
389
|
{ key: 'businessDepth', value: businessDepth, weight: 0.20, applicable: true, critical: true },
|
|
366
390
|
{ key: 'claimProof', value: claim.ratio, weight: 0.15, applicable: claim.withClaims > 0, critical: true },
|
|
367
|
-
{ key: 'traceability', value: traceScore, weight: 0.09, applicable: viewpoints.length > 0 && !viewpointMoved, critical: false, withheld: viewpointMoved && viewpoints.length > 0 },
|
|
391
|
+
{ key: 'traceability', value: traceScore, weight: 0.09, applicable: viewpoints.length > 0 && scenarios.length > 0 && !viewpointMoved, critical: false, withheld: viewpointMoved && viewpoints.length > 0 && scenarios.length > 0 },
|
|
368
392
|
// A use-case decomposition is EF-heavy by construction (1 basic flow, N alternates, N
|
|
369
393
|
// exceptions) — that is the shape of a well-decomposed use case, not neglected business
|
|
370
394
|
// core. `flowCoverage` already measures whether the journey's phases are covered, so
|
|
@@ -576,7 +600,7 @@ export function runAudit(screenDir: string, screenName: string): AuditReport {
|
|
|
576
600
|
findings.push(`COVERED-VIA-FLOW: critical theme "${c.theme}" is not covered on this screen but is deeply covered by flow "${c.flow}" — cross-screen depth correctly owned by the flow, so it is credited (not a screen gap). Verify with \`sungen flow-check\`.`);
|
|
577
601
|
}
|
|
578
602
|
// Verification-scope (advisory): API/DB verification the project's recorded test type never asked for.
|
|
579
|
-
for (const v of verificationScopeFindings(featureText,
|
|
603
|
+
for (const v of verificationScopeFindings(featureText, viewpointText, capProfile)) {
|
|
580
604
|
findings.push(`VERIFICATION-OUT-OF-SCOPE (${v.layer}): this unit ${v.message}`);
|
|
581
605
|
}
|
|
582
606
|
for (const g of gate.gaps) {
|
|
@@ -594,19 +618,26 @@ export function runAudit(screenDir: string, screenName: string): AuditReport {
|
|
|
594
618
|
`add data assertions (\`... with {{value}}\`, \`see all ... contain {{v}}\`) or, if cross-screen, defer to a flow with @manual + reason.`,
|
|
595
619
|
);
|
|
596
620
|
}
|
|
597
|
-
// H5 — state isolation: a
|
|
598
|
-
//
|
|
621
|
+
// H5 — state isolation: a stateful flow that mutates the cart needs per-scenario isolation, else
|
|
622
|
+
// count/quantity asserts go flaky. Warn (advisory) when no mitigation is present
|
|
599
623
|
// (@cleanup:cart / @isolate / a "Given … empty cart" background / fresh context).
|
|
624
|
+
// #759 — scoped by the ABSENCE of @serial, not the presence of @parallel: parallel is the default.
|
|
600
625
|
if (isoRisk) {
|
|
601
626
|
const what = flowQ.contract?.stateful ?? 'cart';
|
|
602
|
-
findings.push(`ISOLATION-RISK: this
|
|
627
|
+
findings.push(`ISOLATION-RISK: this flow mutates ${what} but has no per-scenario isolation → count/quantity/state asserts can go flaky when scenarios share it. Add \`@cleanup:${what}\` (or \`@isolate\`, a fresh context, or \`{{$timestamp}}\` data) so each scenario starts clean.`);
|
|
603
628
|
}
|
|
604
629
|
// #414 — serial-cascade masking: an @serial suite of independent (re-navigating) scenarios hides
|
|
605
630
|
// failures, because Playwright's describe.serial SKIPS everything after the first red. Isolation is
|
|
606
631
|
// the default, so this only fires when @serial was added to a suite that didn't need it.
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
632
|
+
// `@serial` is a property of ONE feature file — only that file compiles to `describe.serial` — so
|
|
633
|
+
// each file is judged on its own text and its own scenarios. The unit-wide text paired one
|
|
634
|
+
// feature's @serial with another's navigating Background and counted every scenario of the unit,
|
|
635
|
+
// telling QA to drop @serial from a genuinely ordered 2-scenario feature (#887).
|
|
636
|
+
for (const file of featureFiles.filter((f) => fs.existsSync(f))) {
|
|
637
|
+
const cascadeN = serialCascadeRisk(readTextFile(file), scenarios.filter((s) => s.sourceFile === file).length);
|
|
638
|
+
if (cascadeN) {
|
|
639
|
+
findings.push(`SERIAL-CASCADE-RISK: the \`@serial\` suite ${path.basename(file)} has ${cascadeN} INDEPENDENT scenarios (each re-navigates via Background) that don't need a shared-page sequence. \`describe.serial\` SKIPS every test after the first failure → one early red MASKS the rest as never-run. **Drop \`@serial\`** — the default per-scenario isolation runs every scenario and isolates failures. Keep \`@serial\` only for a genuinely stateful ordered sequence (where scenario N depends on N-1's state).`);
|
|
640
|
+
}
|
|
610
641
|
}
|
|
611
642
|
if (flowDepth.stateful && flowDepth.missing.length) {
|
|
612
643
|
// The advice names the collection the CONTRACT declared. It used to say "the cart" whatever
|
|
@@ -633,12 +664,19 @@ export function runAudit(screenDir: string, screenName: string): AuditReport {
|
|
|
633
664
|
for (const m of taxonomy.mislabeled) {
|
|
634
665
|
findings.push(`VP-MISLABEL: "${m.name}" is coded VP-${m.current} but reads as ${m.suggested} (signal: "${m.signal}") → re-tag VP-${m.suggested}-NNN so the coverage matrix isn't skewed.`);
|
|
635
666
|
}
|
|
667
|
+
// Scenario ↔ defect record links (qa/defects/) — advisory, never scored.
|
|
668
|
+
findings.push(...defectLinkFindings(projectRootFromScreenDir(screenDir), catalogScreenName, featureText, scenarios.map((sc) => sc.name)));
|
|
636
669
|
if (balance.unclassifiedRatio > 0.4) {
|
|
637
670
|
findings.push(`TAXONOMY-UNCLASSIFIED: ${balance.note} → align the VP-<CATEGORY> codes with the catalog (or extend the bucket keywords) so coverage-balance is meaningful.`);
|
|
638
671
|
} else if (balance.imbalanced && !flowScored) {
|
|
639
672
|
findings.push(`BALANCE: ${balance.note} Stop expanding secondary viewpoints until business-core gaps are filled.`);
|
|
640
673
|
}
|
|
641
|
-
if (
|
|
674
|
+
if (aiDraft) {
|
|
675
|
+
findings.push(`VIEWPOINT-DRAFT-UNREVIEWED: \`requirements/test-viewpoint.md\` is marked \`drafted-by: ai\` and no QA has accepted it, so it is not recorded as the baseline and \`atomicLedger\` + \`traceability\` are held as unverified — a checklist the AI wrote would otherwise score the suite it generated at 100% by construction. QA: review the claims (add what you care about, delete what you do not), then \`sungen audit --screen ${screenName} --accept-viewpoint\` — it records the baseline and removes the \`drafted-by: ai\` line. A QA decision: an agent presents it and does not run it.`);
|
|
676
|
+
}
|
|
677
|
+
// create-test's checklist review of the viewpoint, still waiting for QA — advisory, never scored.
|
|
678
|
+
findings.push(...viewpointReviewOpenFindings(path.dirname(viewpointPath)));
|
|
679
|
+
if (adoptedPostHoc && !aiDraft) { // a draft's own finding already says this and names the accept command
|
|
642
680
|
findings.push(`VIEWPOINT-ADOPTED-POST-HOC: this is the first audit of \`requirements/test-viewpoint.md\` and the suite ALREADY has ${scenarios.length} scenarios, so the yardstick is being introduced against tests that already exist. A declaration written after the suite can only measure what its author already thought of — and if it was derived FROM the suite, \`atomicLedger\` and \`traceability\` read 100% by construction while anything dropped along the way stops being missing from anything. Both axes are held as unverified until you review the claims (add what you care about, delete what you do not) and confirm: \`sungen audit --screen ${screenName} --accept-viewpoint\` — a QA decision: an agent presents it and does not run it.`);
|
|
643
681
|
}
|
|
644
682
|
if (!hasYardstick) {
|
|
@@ -647,7 +685,8 @@ export function runAudit(screenDir: string, screenName: string): AuditReport {
|
|
|
647
685
|
...(unsourced.length ? [`${unsourced.length} scenario(s) could not be traced to a source`] : []),
|
|
648
686
|
];
|
|
649
687
|
findings.push(`VIEWPOINT-MISSING: \`requirements/test-viewpoint.md\` ${ledger.hasViewpoint ? 'declares no viewpoints' : 'does not exist'}, so this unit has no yardstick. Two scored axes are excluded (atomicLedger, traceability) and four sensors cannot run: the atomic ledger, the declaration-integrity checks (reused ids, dangling refs, uniform priority), the browser-gesture check and the two-way continuity check. Nothing here is a pass — it is unmeasured [${lost.join(', ')}]. A flow contract does NOT substitute: the contract declares the use case, the viewpoint declares the CLAIMS the suite is scored against, and only the second is independent of what was generated. Author it (\`sungen add-flow\` scaffolds a template), then \`sungen audit --screen ${screenName} --accept-viewpoint\`.`);
|
|
650
|
-
} else if (trace.mappedRatio < 0.5) {
|
|
688
|
+
} else if (scenarios.length > 0 && trace.mappedRatio < 0.5) {
|
|
689
|
+
// No scenario yet is not a traceability gap — the axis is n/a, like coverage and claimProof (#852).
|
|
651
690
|
findings.push(`TRACE: ${trace.note}`);
|
|
652
691
|
}
|
|
653
692
|
// (UNIVERSAL viewpoint-gap finding now emitted by the `ui` gate sensor — see the gate block below.)
|
|
@@ -688,8 +727,10 @@ export function runAudit(screenDir: string, screenName: string): AuditReport {
|
|
|
688
727
|
for (const d of downstream.underCovered) {
|
|
689
728
|
findings.push(`DOWNSTREAM-SCOPE-MISSING: "${d.route}" is a navigation target but is covered only by a page-nav assertion — cover its content/guards, or scaffold it (\`sungen add --screen ${d.slug}\`).`);
|
|
690
729
|
}
|
|
730
|
+
// #811 — design inventory (advisory): never moves the score or the gate.
|
|
731
|
+
findings.push(...uiInventoryFindings(uiInv));
|
|
691
732
|
for (const m of manualOracleResult.insufficient.slice(0, 8)) {
|
|
692
|
-
findings.push(`MANUAL-STEPS-INSUFFICIENT: "${m}" — @manual is incomplete: either Gherkin steps without a Then (invalid Gherkin; comments can't substitute), or a documentation manual whose comments lack numbered action steps + a labeled Observable:/Oracle: line. Write \`# Tester verifies:\` as "1. <action>" … "N. Observable: <what>" / "Oracle: <tool>" — that is what the delivery parser extracts into Steps/Expected.`);
|
|
733
|
+
findings.push(`MANUAL-STEPS-INSUFFICIENT: "${m}" — @manual is incomplete: either Gherkin steps without a Then (invalid Gherkin; comments can't substitute), a Then that only asserts presence (\`see [X] table\`) while the real oracle sits in a \`# Tester verifies:\` comment (the deliverable's Expected would not say what is checked), or a documentation manual whose comments lack numbered action steps + a labeled Observable:/Oracle: line. Write \`# Tester verifies:\` as "1. <action>" … "N. Observable: <what>" / "Oracle: <tool>" — that is what the delivery parser extracts into Steps/Expected.`);
|
|
693
734
|
}
|
|
694
735
|
// TQ-2 — automatable @manual: deferred (usually cross-screen) but fully DSL-expressible.
|
|
695
736
|
for (const m of autoManual.scenarios.slice(0, 8)) {
|
|
@@ -787,7 +828,7 @@ export function runAudit(screenDir: string, screenName: string): AuditReport {
|
|
|
787
828
|
// #622 — the priority table is where release selection comes from; a tag that contradicts it
|
|
788
829
|
// means nobody can tell which of the two was the decision.
|
|
789
830
|
{
|
|
790
|
-
const pm = priorityMismatches(hasYardstick ?
|
|
831
|
+
const pm = priorityMismatches(hasYardstick ? viewpoints : [], scenarios);
|
|
791
832
|
const byId = new Map<string, { declared: string; tagged: string; n: number }>();
|
|
792
833
|
for (const m of pm) {
|
|
793
834
|
const cur = byId.get(m.id) ?? { declared: m.declared, tagged: m.tagged, n: 0 };
|
|
@@ -860,8 +901,11 @@ export function runAudit(screenDir: string, screenName: string): AuditReport {
|
|
|
860
901
|
axes.manualOracle = Math.round(manualCompleteness * 100) / 100;
|
|
861
902
|
// Only a real mismatch scores 0. Without a yardstick `trace.mappedRatio` is 0 because there is
|
|
862
903
|
// nothing to map to, and reporting that as the weakest axis sent authors to "raise taxonomy" —
|
|
863
|
-
// an axis that was never measured.
|
|
864
|
-
|
|
904
|
+
// an axis that was never measured. The same holds for a yardstick the ledger can read but that
|
|
905
|
+
// declares no viewpoint ID traceability can read (per-axis tables only, #832): the ledger has
|
|
906
|
+
// items, so `hasYardstick` is true, yet there is still nothing to map to. Same condition as the
|
|
907
|
+
// `traceability` score axis (`applicable: viewpoints.length > 0`).
|
|
908
|
+
if (hasYardstick && viewpoints.length > 0 && scenarios.length > 0) axes.taxonomy = taxonomyMismatch ? 0 : Math.round(trace.mappedRatio * 100) / 100;
|
|
865
909
|
const notApplicable = axisDefs.filter((a) => !a.applicable).map((a) => a.key);
|
|
866
910
|
const weakestEntry = Object.entries(axes).sort((a, b) => a[1] - b[1])[0];
|
|
867
911
|
const weakest = { axis: weakestEntry[0], value: weakestEntry[1] };
|
|
@@ -889,6 +933,7 @@ export function runAudit(screenDir: string, screenName: string): AuditReport {
|
|
|
889
933
|
gate, depth, claim, taxonomy, balance, duplicates, trace, spec,
|
|
890
934
|
taxonomyMismatch, reusedScenarioIds: reusedIds, downstream, manualOracle: manualOracleResult, automatableManual: autoManual, flowDepth, oracle, ledger, viewpointBaseline, calibration,
|
|
891
935
|
...(isUiFlow ? { flowQuality: flowQ, flowInventory: inventory ?? undefined } : {}),
|
|
936
|
+
...(uiInv.designSources.length ? { uiInventory: uiInv } : {}),
|
|
892
937
|
score: {
|
|
893
938
|
overall: Math.round(overall * 10) / 10,
|
|
894
939
|
coverage: Math.round(coverage * 100) / 100,
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import * as fs from 'fs';
|
|
12
12
|
import * as path from 'path';
|
|
13
13
|
import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
|
|
14
|
+
import { readYamlConfig, warnUnreadableConfig } from './config-file';
|
|
14
15
|
|
|
15
16
|
export interface CapabilityProfile {
|
|
16
17
|
platform?: string; // web | mobile | … (the runtime adapter)
|
|
@@ -23,7 +24,13 @@ export interface CapabilityProfile {
|
|
|
23
24
|
* never asked for.
|
|
24
25
|
*/
|
|
25
26
|
verification?: string[];
|
|
26
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Where the profile came from. `'unreadable'` is NOT `'absent'`: the file exists and says
|
|
29
|
+
* something, we just cannot read it. Callers that WRITE capabilities.yaml must branch on this —
|
|
30
|
+
* scaffolding a default over a file whose contents we failed to parse destroys the user's
|
|
31
|
+
* declaration (see `cli/commands/generate.ts`).
|
|
32
|
+
*/
|
|
33
|
+
source: 'capabilities.yaml' | 'absent' | 'unreadable';
|
|
27
34
|
}
|
|
28
35
|
|
|
29
36
|
export type VerifyLayer = 'ui' | 'api' | 'db';
|
|
@@ -119,18 +126,22 @@ export function capabilitiesPath(cwd: string): string {
|
|
|
119
126
|
|
|
120
127
|
export function readCapabilities(cwd: string): CapabilityProfile {
|
|
121
128
|
const p = capabilitiesPath(cwd);
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
};
|
|
131
|
-
} catch {
|
|
132
|
-
return { platform: undefined, enabled: [], source: 'absent' };
|
|
129
|
+
const read = readYamlConfig<Record<string, unknown> | null>(p);
|
|
130
|
+
if (read.state === 'absent') return { platform: undefined, enabled: [], source: 'absent' };
|
|
131
|
+
if (read.state === 'unreadable') {
|
|
132
|
+
// The profile decides the runtime adapter, the driver set and the verification scope. Reporting
|
|
133
|
+
// it once here covers all fifteen call sites; what each does about it is still its own choice.
|
|
134
|
+
warnUnreadableConfig(p, read.error,
|
|
135
|
+
'this run cannot tell which platform or drivers the project declares, and falls back to none');
|
|
136
|
+
return { platform: undefined, enabled: [], source: 'unreadable' };
|
|
133
137
|
}
|
|
138
|
+
const y = (read.value || {}) as Record<string, unknown>;
|
|
139
|
+
return {
|
|
140
|
+
platform: typeof y.platform === 'string' ? y.platform : undefined,
|
|
141
|
+
enabled: Array.isArray(y.enabled) ? y.enabled.map(String) : [],
|
|
142
|
+
verification: Array.isArray(y.verification) ? y.verification.map(String) : undefined,
|
|
143
|
+
source: 'capabilities.yaml',
|
|
144
|
+
};
|
|
134
145
|
}
|
|
135
146
|
|
|
136
147
|
export function writeCapabilities(cwd: string, profile: { platform?: string; enabled: string[]; verification?: string[] }): string {
|
|
@@ -24,7 +24,7 @@ page_types:
|
|
|
24
24
|
detect_keywords: [cart, product, checkout, catalog, brand, category, "add to cart"]
|
|
25
25
|
must_cover:
|
|
26
26
|
- theme: list-data
|
|
27
|
-
keywords: [list, displayed, card, "product card", grid]
|
|
27
|
+
keywords: ["product list", displayed, card, "product card", grid]
|
|
28
28
|
depth:
|
|
29
29
|
requires: data-assertion
|
|
30
30
|
cross_screen: false
|
|
@@ -38,7 +38,7 @@ page_types:
|
|
|
38
38
|
keywords: ["product detail", "detail page", "same product name", "matching name", "consistency", "same price"]
|
|
39
39
|
template: "User remember [Product Name] text as {{v}} … User see [Detail Product Name] header with {{v}}"
|
|
40
40
|
- theme: cart-correctness
|
|
41
|
-
keywords: ["cart contains", quantity, "price", subtotal, "in cart", "cart content", "cart product"]
|
|
41
|
+
keywords: ["cart contains", "cart quantity", "cart price", subtotal, "in cart", "cart content", "cart product"]
|
|
42
42
|
depth:
|
|
43
43
|
requires: data-assertion
|
|
44
44
|
cross_screen: true
|
|
@@ -77,7 +77,7 @@ page_types:
|
|
|
77
77
|
keywords: ["required", "must be", "error message", "validation message"]
|
|
78
78
|
template: "User see [Field Error] message with {{error_text}}"
|
|
79
79
|
- theme: format-boundary
|
|
80
|
-
keywords: [format, invalid, boundary, length, range]
|
|
80
|
+
keywords: [format, invalid, boundary, "max length", "min length", range]
|
|
81
81
|
depth:
|
|
82
82
|
requires: data-assertion
|
|
83
83
|
cross_screen: false
|
|
@@ -125,10 +125,10 @@ page_types:
|
|
|
125
125
|
# suite, which is a scoring decision for an owner, not a drive-by. `viewpoint-ledger` already
|
|
126
126
|
# covers the dangerous half by refusing `tap [Back]` as an answer to an OS-back viewpoint item.
|
|
127
127
|
mobile-list:
|
|
128
|
-
detect_keywords: [feed, "pull to refresh", "infinite scroll", "load more", "list item", "list row"]
|
|
128
|
+
detect_keywords: ["feed item", "pull to refresh", "infinite scroll", "load more", "list item", "list row"]
|
|
129
129
|
must_cover:
|
|
130
130
|
- theme: list-data
|
|
131
|
-
keywords: [
|
|
131
|
+
keywords: ["feed item", row, "list item", displayed]
|
|
132
132
|
depth:
|
|
133
133
|
requires: data-assertion
|
|
134
134
|
cross_screen: false
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading a config file has THREE outcomes, not two: absent, parsed, and present-but-unreadable.
|
|
3
|
+
*
|
|
4
|
+
* Collapsing the third into the first is the defect class #782 catalogues. The shape recurs because
|
|
5
|
+
* it looks harmless: an `fs.existsSync` guard already answers absence, so the `catch` below it only
|
|
6
|
+
* ever fires for CORRUPTION — and returning the absent value there is the one case that must not be
|
|
7
|
+
* silent. A file that is absent was never written; a file that is broken WAS written, by someone who
|
|
8
|
+
* believes it is in effect.
|
|
9
|
+
*
|
|
10
|
+
* `run-manifest.ts:hashTopLevel` already draws the distinction for its own reader (`__unparseable__`
|
|
11
|
+
* rather than `{}`); this is that idea as a seam every config reader can share, so the next one does
|
|
12
|
+
* not have to rediscover it.
|
|
13
|
+
*
|
|
14
|
+
* Nothing here throws: the caller owns the consequence, because only the caller knows it. What the
|
|
15
|
+
* type removes is the option of not deciding.
|
|
16
|
+
*/
|
|
17
|
+
import * as fs from 'fs';
|
|
18
|
+
import { parse as parseYaml } from 'yaml';
|
|
19
|
+
|
|
20
|
+
export type ConfigRead<T> =
|
|
21
|
+
| { state: 'absent' }
|
|
22
|
+
| { state: 'ok'; value: T }
|
|
23
|
+
| { state: 'unreadable'; error: string };
|
|
24
|
+
|
|
25
|
+
const message = (e: unknown): string => (e instanceof Error ? e.message.split('\n')[0] : String(e));
|
|
26
|
+
|
|
27
|
+
/** Read + parse a YAML config. An empty or comment-only file parses to null — that is `ok`, not broken. */
|
|
28
|
+
export function readYamlConfig<T>(file: string): ConfigRead<T> {
|
|
29
|
+
if (!fs.existsSync(file)) return { state: 'absent' };
|
|
30
|
+
try {
|
|
31
|
+
return { state: 'ok', value: (parseYaml(fs.readFileSync(file, 'utf-8')) ?? null) as T };
|
|
32
|
+
} catch (e) {
|
|
33
|
+
return { state: 'unreadable', error: message(e) };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Read + parse a JSON config. Unlike YAML, an empty file is NOT valid JSON and reads as unreadable. */
|
|
38
|
+
export function readJsonConfig<T>(file: string): ConfigRead<T> {
|
|
39
|
+
if (!fs.existsSync(file)) return { state: 'absent' };
|
|
40
|
+
try {
|
|
41
|
+
return { state: 'ok', value: JSON.parse(fs.readFileSync(file, 'utf-8')) as T };
|
|
42
|
+
} catch (e) {
|
|
43
|
+
return { state: 'unreadable', error: message(e) };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* One line to stderr naming the file, the parse error, and WHAT THE RUN WILL DO INSTEAD — the last
|
|
49
|
+
* part is the whole point: "capabilities.yaml is broken" leaves the reader guessing; "…so this run
|
|
50
|
+
* treats the project as web" tells them why their mobile suite just compiled wrong.
|
|
51
|
+
*
|
|
52
|
+
* At most once per file per process. `readCapabilities` alone has fifteen call sites, and a warning
|
|
53
|
+
* repeated fifteen times reads as noise — which is its own way of being ignored.
|
|
54
|
+
*/
|
|
55
|
+
const warned = new Set<string>();
|
|
56
|
+
|
|
57
|
+
export function warnUnreadableConfig(file: string, error: string, consequence: string): void {
|
|
58
|
+
if (warned.has(file)) return;
|
|
59
|
+
warned.add(file);
|
|
60
|
+
console.error(`⚠️ ${file}: unreadable (${error}) — ${consequence}`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Test seam: the dedup set is process-global, so a suite exercising several cases must clear it. */
|
|
64
|
+
export function resetUnreadableConfigWarnings(): void {
|
|
65
|
+
warned.clear();
|
|
66
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How many items a scenario's assertions demonstrate — the denominator of CLAIM-PARTIAL (#622).
|
|
3
|
+
*
|
|
4
|
+
* One reading for both places that check a counted claim (the viewpoint ledger and the title-level
|
|
5
|
+
* `numericClaimGaps`), which used to carry their own copy of the regex.
|
|
6
|
+
*
|
|
7
|
+
* Counted, once per distinct assertion:
|
|
8
|
+
* - a value: `see [X] … with|contains {{v}}`;
|
|
9
|
+
* - a positive state: `see [X] … is checked|selected|enabled|disabled`;
|
|
10
|
+
* - an indicator's presence: `see [X] icon|checkbox|toggle|radio|badge|image` (#866) — on mobile a
|
|
11
|
+
* ticked item is often observable ONLY as its tick icon, so one per item is the proof.
|
|
12
|
+
* Not counted: plain visibility of text/headers (it shows a label, not one of N values) and absence
|
|
13
|
+
* (`is hidden`, `not …`), which proves nothing is there.
|
|
14
|
+
*/
|
|
15
|
+
// The tail stops before the NEXT `see [`, or consecutive assertions would swallow each other.
|
|
16
|
+
const SEE_STEP = /\bsee \[([^\]]+)\]((?:(?!\bsee \[)[^[])*)/g;
|
|
17
|
+
const VALUE = /^[^|]*?\b(?:with|contains) (\{\{[^}]*\}\})/;
|
|
18
|
+
const ABSENT = /^\s*\S*\s*(?:is hidden|is not\b|not\b|does not\b)/;
|
|
19
|
+
const STATE = /^\s*\S*\s*is (checked|selected|enabled|disabled)\b/;
|
|
20
|
+
const INDICATOR = /^\s*(icon|checkbox|toggle|radio|badge|image)\b/;
|
|
21
|
+
|
|
22
|
+
export function countedAssertions(stepsText: string): number {
|
|
23
|
+
const seen = new Set<string>();
|
|
24
|
+
for (const m of stepsText.matchAll(SEE_STEP)) {
|
|
25
|
+
const [, target, rest] = m;
|
|
26
|
+
if (ABSENT.test(rest)) continue;
|
|
27
|
+
// Keyed on the target and WHAT it proves — never on the tail, which runs into the next step, so
|
|
28
|
+
// the same element asserted before and after an action is still one assertion.
|
|
29
|
+
const proof = VALUE.exec(rest)?.[1] ?? STATE.exec(rest)?.[1] ?? INDICATOR.exec(rest)?.[1];
|
|
30
|
+
if (proof) seen.add(`${target}|${proof}`);
|
|
31
|
+
}
|
|
32
|
+
return seen.size;
|
|
33
|
+
}
|
|
@@ -155,29 +155,100 @@ function overrideKeys(raw?: string): Set<string> {
|
|
|
155
155
|
return out;
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
+
interface FeatureUnderLint {
|
|
159
|
+
/** Feature file basename — names the test-data file the runtime loads for it. */
|
|
160
|
+
name: string;
|
|
161
|
+
scenarios: ParsedScenario[];
|
|
162
|
+
tags: string[];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** What every feature of a unit shares: unit-level refs and the @api catalog. */
|
|
166
|
+
interface UnitLintContext {
|
|
167
|
+
screenDir: string;
|
|
168
|
+
screenName: string;
|
|
169
|
+
cwd: string;
|
|
170
|
+
fileRefs: Set<string>;
|
|
171
|
+
apiParams: Map<string, string[]>;
|
|
172
|
+
}
|
|
173
|
+
|
|
158
174
|
/** Lint the @cases/@query usage of one screen/flow directory. Returns advisory warnings. */
|
|
159
175
|
export function lintDataDriven(screenDir: string, cwd: string = process.cwd()): DataDrivenWarning[] {
|
|
160
176
|
const base = path.basename(screenDir);
|
|
161
177
|
const screenName = unitIdOf(screenDir);
|
|
162
178
|
// Every `.feature` of the unit — `add --feature` sub-features lint too. A file that
|
|
163
179
|
// fails to parse contributes nothing rather than voiding the whole unit's lint.
|
|
164
|
-
const
|
|
165
|
-
const
|
|
166
|
-
for (const p of
|
|
180
|
+
const features: FeatureUnderLint[] = [];
|
|
181
|
+
const featureFiles = featureFilesFor(screenDir, base).filter((f) => fs.existsSync(f));
|
|
182
|
+
for (const p of featureFiles) {
|
|
167
183
|
try {
|
|
168
184
|
const feature = new GherkinParser().parseFeatureFile(p);
|
|
169
|
-
|
|
170
|
-
featureTags.push(...(feature.tags || []));
|
|
185
|
+
features.push({ name: path.basename(p, '.feature'), scenarios: feature.scenarios || [], tags: feature.tags || [] });
|
|
171
186
|
} catch {
|
|
172
187
|
/* a file that fails to parse contributes nothing */
|
|
173
188
|
}
|
|
174
189
|
}
|
|
175
|
-
if (scenarios.length
|
|
190
|
+
if (!features.some((f) => f.scenarios.length > 0)) return [];
|
|
191
|
+
|
|
192
|
+
const ctx: UnitLintContext = {
|
|
193
|
+
screenDir, screenName, cwd,
|
|
194
|
+
// `{{key}}` refs carried by the unit's selectors (i18n) and mock catalog are live too.
|
|
195
|
+
fileRefs: refsInUnitFiles(screenDir),
|
|
196
|
+
apiParams: apiParamsByName(screenName, cwd),
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
// #839 — each feature against ITS OWN test-data file. The runtime loads
|
|
200
|
+
// `test-data/<feature file basename>.yaml` (TestDataLoader.load(unit, featureFileName)), with no
|
|
201
|
+
// fallback and no merging; reading `<unit dir>.yaml` instead found nothing whenever the names
|
|
202
|
+
// differ — the default for `add --screen x --path /a/b` (→ b.feature) and for `--feature` — so
|
|
203
|
+
// every key read as missing and no orphan was ever reported. The tables are deliberately NOT
|
|
204
|
+
// merged: a key in another feature's file compiles to the literal `${key}` here.
|
|
205
|
+
const warns: DataDrivenWarning[] = [];
|
|
206
|
+
for (const f of features) if (f.scenarios.length > 0) warns.push(...lintFeature(f, ctx));
|
|
207
|
+
warns.push(...unloadedTestDataFiles(screenDir, featureFiles.map((f) => path.basename(f, '.feature'))));
|
|
208
|
+
|
|
209
|
+
// Catalog-level lint (SELECT-only, params declared/used, datasource present).
|
|
210
|
+
try {
|
|
211
|
+
for (const e of lintCatalog(screenName, null, cwd).errors) warns.push({ message: e });
|
|
212
|
+
} catch {
|
|
213
|
+
/* no catalog → nothing to lint */
|
|
214
|
+
}
|
|
215
|
+
return warns;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* A test-data file no feature loads is dead in full: the runtime reads only
|
|
220
|
+
* `<feature>.yaml` and its `<feature>.<env>.yaml` overlays. Typically a `<unit>.yaml` written by
|
|
221
|
+
* hand next to a feature `add --path` named after the URL — its keys never reach a test.
|
|
222
|
+
* Every existing feature file counts (even one that failed to parse), so a parse error never
|
|
223
|
+
* turns its data into a false "loaded by no feature".
|
|
224
|
+
*/
|
|
225
|
+
function unloadedTestDataFiles(screenDir: string, featureNames: string[]): DataDrivenWarning[] {
|
|
226
|
+
let files: string[];
|
|
227
|
+
try {
|
|
228
|
+
files = fs.readdirSync(path.join(screenDir, 'test-data')).filter((f) => /\.ya?ml$/.test(f) && !f.startsWith('_'));
|
|
229
|
+
} catch {
|
|
230
|
+
return [];
|
|
231
|
+
}
|
|
232
|
+
const loaded = (stem: string): boolean => featureNames.some((n) => stem === n || stem.startsWith(`${n}.`));
|
|
233
|
+
return files
|
|
234
|
+
.filter((f) => !loaded(f.replace(/\.ya?ml$/, '')) || !f.endsWith('.yaml'))
|
|
235
|
+
.map((f) => ({
|
|
236
|
+
message: `test-data/${f} is loaded by no feature — the runtime reads test-data/<feature>.yaml (+ <feature>.<env>.yaml) for ${featureNames.map((n) => `${n}.feature`).join(', ')}: move its keys there or remove it.`,
|
|
237
|
+
}));
|
|
238
|
+
}
|
|
176
239
|
|
|
177
|
-
|
|
178
|
-
const
|
|
240
|
+
function lintFeature(feature: FeatureUnderLint, ctx: UnitLintContext): DataDrivenWarning[] {
|
|
241
|
+
const { screenName, cwd, fileRefs, apiParams } = ctx;
|
|
242
|
+
const { scenarios, tags: featureTags } = feature;
|
|
243
|
+
const tdFile = `test-data/${feature.name}.yaml`;
|
|
244
|
+
const tdPath = path.join(ctx.screenDir, 'test-data', `${feature.name}.yaml`);
|
|
245
|
+
const td: Record<string, any> = fs.existsSync(tdPath) ? parseYaml(fs.readFileSync(tdPath, 'utf8')) || {} : {};
|
|
179
246
|
const topKeys = new Set(Object.keys(td));
|
|
180
247
|
const warns: DataDrivenWarning[] = [];
|
|
248
|
+
// H3: an `@extend:<base>` scenario runs the base's steps first — the base's captures bind for it.
|
|
249
|
+
// Per feature, like the compiler's steps registry: an `@extend` never reaches another file's base.
|
|
250
|
+
const stepsBase = new Map<string, ParsedScenario>();
|
|
251
|
+
for (const sc of scenarios) if (sc.stepsName) stepsBase.set(sc.stepsName, sc);
|
|
181
252
|
// Bindings a `@beforeAll` hook's `@api:`/`@query:` preconditions make are readable from EVERY
|
|
182
253
|
// scenario of the feature (the compiler registers them feature-wide). A `remember … as` inside the
|
|
183
254
|
// hook is NOT — it stays hook-local — so only the tag-bound names are exempted here.
|
|
@@ -186,18 +257,12 @@ export function lintDataDriven(screenDir: string, cwd: string = process.cwd()):
|
|
|
186
257
|
if (sc.hookType !== 'beforeAll') continue;
|
|
187
258
|
for (const t of sc.tags || []) { const m = t.match(/^@(?:api|query):([A-Za-z_]\w*)/); if (m) featureBound.add(m[1]); }
|
|
188
259
|
}
|
|
189
|
-
// H3: an `@extend:<base>` scenario runs the base's steps first — the base's captures bind for it.
|
|
190
|
-
const stepsBase = new Map<string, ParsedScenario>();
|
|
191
|
-
for (const sc of scenarios) if (sc.stepsName) stepsBase.set(sc.stepsName, sc);
|
|
192
|
-
// `{{key}}` refs carried by the unit's selectors (i18n) and mock catalog are live too.
|
|
193
|
-
const fileRefs = refsInUnitFiles(screenDir);
|
|
194
260
|
// A feature-level `@manual` makes every scenario manual (the whole file is skipped by the compiler).
|
|
195
261
|
const featureManual = featureTags.some((t) => t === '@manual' || t.startsWith('@manual:'));
|
|
196
262
|
|
|
197
263
|
// What the scenarios bind, collected up front — the @query pass below adds the params it binds by name.
|
|
198
264
|
const usedHeads = new Set<string>();
|
|
199
265
|
const usedDatasets = new Set<string>();
|
|
200
|
-
const apiParams = apiParamsByName(screenName, cwd);
|
|
201
266
|
for (const h of fileRefs) usedHeads.add(h);
|
|
202
267
|
for (const sc of scenarios) {
|
|
203
268
|
for (const r of collectRefs(sc, featureManual || isManualScenario(sc))) usedHeads.add(r.split(/[.[]/)[0]);
|
|
@@ -228,7 +293,7 @@ export function lintDataDriven(screenDir: string, cwd: string = process.cwd()):
|
|
|
228
293
|
const ds = casesTag.slice('@cases:'.length).trim();
|
|
229
294
|
const rows = td[ds];
|
|
230
295
|
if (!Array.isArray(rows)) {
|
|
231
|
-
warns.push({ scenario: sc.name, message: `@cases:${ds} → dataset "${ds}" is missing or not a list in
|
|
296
|
+
warns.push({ scenario: sc.name, message: `@cases:${ds} → dataset "${ds}" is missing or not a list in ${tdFile}.` });
|
|
232
297
|
} else {
|
|
233
298
|
const colSets = rows.map((r) => new Set(Object.keys(r || {}).filter((k) => !ROW_META.has(k))));
|
|
234
299
|
const allCols = new Set<string>();
|
|
@@ -256,7 +321,7 @@ export function lintDataDriven(screenDir: string, cwd: string = process.cwd()):
|
|
|
256
321
|
for (const r of refs) {
|
|
257
322
|
const head = r.split(/[.[]/)[0];
|
|
258
323
|
if (head.startsWith('$') || bound.has(head) || featureBound.has(head) || topKeys.has(head)) continue;
|
|
259
|
-
warns.push({ scenario: sc.name, message: `{{${r}}} is not a test-data key and nothing binds it at run time — it compiles to the literal text '\${${r}}' (add the key to
|
|
324
|
+
warns.push({ scenario: sc.name, message: `{{${r}}} is not a test-data key and nothing binds it at run time — it compiles to the literal text '\${${r}}' (add the key to ${tdFile}, or fix the ref).` });
|
|
260
325
|
}
|
|
261
326
|
}
|
|
262
327
|
|
|
@@ -289,15 +354,8 @@ export function lintDataDriven(screenDir: string, cwd: string = process.cwd()):
|
|
|
289
354
|
for (const k of topKeys) {
|
|
290
355
|
if (k.startsWith('_')) continue; // Data Factory metadata, not scenario data
|
|
291
356
|
if (!usedHeads.has(k) && !usedDatasets.has(k)) {
|
|
292
|
-
warns.push({ message: `test-data key "${k}" is defined but never referenced ({{${k}}}, a @cases dataset, or an override) — dead data: bind it into a scenario or remove it.` });
|
|
357
|
+
warns.push({ message: `test-data key "${k}" is defined but never referenced ({{${k}}}, a @cases dataset, or an override) by ${feature.name}.feature — dead data: bind it into a scenario or remove it from ${tdFile}.` });
|
|
293
358
|
}
|
|
294
359
|
}
|
|
295
|
-
|
|
296
|
-
// Catalog-level lint (SELECT-only, params declared/used, datasource present).
|
|
297
|
-
try {
|
|
298
|
-
for (const e of lintCatalog(screenName, null, cwd).errors) warns.push({ message: e });
|
|
299
|
-
} catch {
|
|
300
|
-
/* no catalog → nothing to lint */
|
|
301
|
-
}
|
|
302
360
|
return warns;
|
|
303
361
|
}
|