@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/parse.ts
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import * as fs from 'fs';
|
|
9
9
|
import { GherkinParser, ParsedScenario, ParsedStep } from '../generators/gherkin-parser';
|
|
10
|
-
import { readTextFile } from './read-text';
|
|
10
|
+
import { readTextFile, readViewpointText } from './read-text';
|
|
11
|
+
import { parsePriority, toEntryPriority } from './viewpoint-priority';
|
|
11
12
|
import { featureFilesFor } from './unit-paths';
|
|
12
13
|
|
|
13
14
|
export type Priority = 'high' | 'normal' | 'low' | 'unknown';
|
|
@@ -15,15 +16,57 @@ export type Priority = 'high' | 'normal' | 'low' | 'unknown';
|
|
|
15
16
|
export interface ViewpointEntry {
|
|
16
17
|
id: string; // e.g. VP-DATA-CONSISTENCY
|
|
17
18
|
priority: 'High' | 'Medium' | 'Low' | 'Unknown';
|
|
19
|
+
/** The declared priority word as written (`critical` · `high` · `medium` · `med` · `normal` · `low` ·
|
|
20
|
+
* `deferred` · `skip`, which also stands for skipped / n/a / none / not applicable), kept because `priority` folds several of them together and the
|
|
21
|
+
* PRIORITY-CONTRADICTED check needs them apart. Absent when no priority was declared. */
|
|
22
|
+
priorityWord?: string;
|
|
18
23
|
reason: string;
|
|
19
24
|
group?: 'Required' | 'Recommended' | 'Optional';
|
|
20
25
|
}
|
|
21
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Whole SECTIONS that declare something ABOUT the checklist rather than listing claims or
|
|
29
|
+
* viewpoints — known issues, spec gaps, traceability indexes, references and appendices, changelogs. Shared by the atomic
|
|
30
|
+
* ledger (their rows are not claims to prove, #630) and the viewpoint-overview parser (an id
|
|
31
|
+
* listed there is a reference, not a declaration, #833). `UI Patterns Identified` is the author's
|
|
32
|
+
* pick of WHICH sungen-viewpoint patterns apply — a `Skip` row there is not a claim to cover (#841).
|
|
33
|
+
* A `How to run` / `Procedure` section — that exact heading, optionally naming one id — is a manual
|
|
34
|
+
* check's steps, not claims of its own (#855); `How to test the login flow` stays a normal section.
|
|
35
|
+
*/
|
|
36
|
+
export const NON_DECLARATION_SECTION = /^(?:(?:ui patterns?(?: identified)?|(?:how to (?:run|test|reproduce|verify)|(?:manual )?procedures?)(?:\s+(?:vp|fl)-[^\s:]+)?)\s*:?$|(?:known issues?|open questions?|spec gaps?|appendi(?:x|ces)|phụ lục|deferred|out of scope|viewpoint grouping|traceability|metadata|scope|journey phases?|revision history|references?|sources?|changelog)\b)/i;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Every word a viewpoint row may declare as its priority — ONE list for the overview parser, the
|
|
40
|
+
* atomic ledger and the integrity checks, which each carried their own copy and all missed the
|
|
41
|
+
* `Skip` that the `sungen add` / `add-flow` scaffolds themselves suggest (#853). `deferred` and the
|
|
42
|
+
* not-applicable words are declarations as well: such a row is not a claim to cover.
|
|
43
|
+
*/
|
|
44
|
+
export const PRIORITY_WORDS = 'critical|high|medium|med|normal|low|deferred|skip(?:ped)?|n\\/a|none|not applicable';
|
|
45
|
+
/** "Declared not applicable" — folded to `skip` so every consumer checks one word. */
|
|
46
|
+
const NOT_APPLICABLE = /^(?:skip(?:ped)?|n\/a|none|not applicable)$/i;
|
|
47
|
+
|
|
48
|
+
// The whole cell must BE the word (emphasis aside), as the old `| VP-… | high |` scan required:
|
|
49
|
+
// "Low risk" or "Not high" declares no priority.
|
|
50
|
+
const PRIORITY_WORD = new RegExp(`^[\\s*_\`]*(${PRIORITY_WORDS})[\\s*_\`.]*$`, 'i');
|
|
51
|
+
/** Header names of a priority column, in the languages viewpoint files are written in. */
|
|
52
|
+
const PRIORITY_HEADER = /^(?:priority|pri|level|severity|優先度?|độ ưu tiên|ưu tiên)(?:\s|\(|$)/i;
|
|
53
|
+
|
|
54
|
+
/** A priority cell → the folded level plus the word as written. */
|
|
55
|
+
function priorityOf(cell: string): { priority: ViewpointEntry['priority']; priorityWord?: string } {
|
|
56
|
+
const raw = cell.match(PRIORITY_WORD)?.[1].toLowerCase();
|
|
57
|
+
const word = raw && NOT_APPLICABLE.test(raw) ? 'skip' : raw;
|
|
58
|
+
const priority = toEntryPriority(parsePriority(cell));
|
|
59
|
+
return word ? { priority, priorityWord: word } : { priority };
|
|
60
|
+
}
|
|
61
|
+
|
|
22
62
|
export interface ScenarioInfo {
|
|
23
63
|
name: string;
|
|
24
64
|
vpCode?: string; // VP-CART-001 style code in the scenario title
|
|
25
65
|
category?: string; // CART, LIST, VAL, ... (from the code)
|
|
26
66
|
priority: Priority;
|
|
67
|
+
/** The priority tag as written, lower-cased, without `@` — keeps `critical` / `medium` / `med`, which
|
|
68
|
+
* `priority` has no slot for, so PRIORITY-CONTRADICTED can fold it like the declared word (#861). */
|
|
69
|
+
priorityTag?: string;
|
|
27
70
|
manual: boolean;
|
|
28
71
|
thenCount: number;
|
|
29
72
|
hasDataAssertion: boolean; // a Then asserts {{data}} / contains / match data
|
|
@@ -39,8 +82,10 @@ export interface ScenarioInfo {
|
|
|
39
82
|
exploration?: boolean; // @exploration — a probe outside the official suite, exempt from source tracing
|
|
40
83
|
deferredToFlow?: boolean; // @deferred:flow — owned by a flow, not automated on this screen (H6)
|
|
41
84
|
ownedByFlow?: string; // @owned-by:<flow> — the flow that owns this deferred scenario (H6)
|
|
85
|
+
featureManual?: boolean; // its Feature is tagged @manual — codegen skips the whole file (#835)
|
|
42
86
|
/** Ordered steps with their resolved bucket (And/But inherit) — flow handoff analysis (#569). */
|
|
43
|
-
|
|
87
|
+
/** `arg` = the step's data table, normalised (#817) — absent when the step has none. */
|
|
88
|
+
steps?: Array<{ bucket: 'given' | 'when' | 'then'; text: string; arg?: string }>;
|
|
44
89
|
/**
|
|
45
90
|
* The `.feature` this scenario was read from. A unit may hold several (`add --feature`), and
|
|
46
91
|
* `scenarioIdReuse` scopes its uniqueness check to ONE file — so the sensor needs to know
|
|
@@ -106,7 +151,7 @@ function headingParts(line: string): { level: number; text: string } | null {
|
|
|
106
151
|
|
|
107
152
|
export function parseViewpointOverview(filePath: string): ViewpointEntry[] {
|
|
108
153
|
if (!fs.existsSync(filePath)) return [];
|
|
109
|
-
const text =
|
|
154
|
+
const text = readViewpointText(filePath);
|
|
110
155
|
const lines = text.split('\n');
|
|
111
156
|
|
|
112
157
|
const entries = new Map<string, ViewpointEntry>();
|
|
@@ -125,8 +170,7 @@ export function parseViewpointOverview(filePath: string): ViewpointEntry[] {
|
|
|
125
170
|
// and an id read with its emphasis markers still attached matches no scenario tag.
|
|
126
171
|
const id = cells[0].replace(/[*`]/g, '').trim();
|
|
127
172
|
if (isViewpointId(id) && !/^-+$/.test(cells[1])) {
|
|
128
|
-
|
|
129
|
-
entries.set(id.toUpperCase(), { id: id.toUpperCase(), priority: pr as any, reason: cells[2] });
|
|
173
|
+
entries.set(id.toUpperCase(), { id: id.toUpperCase(), ...priorityOf(cells[1]), reason: cells[2] });
|
|
130
174
|
}
|
|
131
175
|
}
|
|
132
176
|
}
|
|
@@ -179,6 +223,52 @@ export function parseViewpointOverview(filePath: string): ViewpointEntry[] {
|
|
|
179
223
|
}
|
|
180
224
|
}
|
|
181
225
|
|
|
226
|
+
// 3) ID-first tables (#833): per-axis tables such as `| ID | Priority | Input | Expected |`.
|
|
227
|
+
// A row whose FIRST cell is a viewpoint id declares that id — the same rows the
|
|
228
|
+
// PRIORITY-CONTRADICTED check always read, so the two readers now agree. Kept narrow on purpose,
|
|
229
|
+
// because a viewpoint file also holds tables that only MENTION ids:
|
|
230
|
+
// - the id must be a `VP-`/`FL-` id, or belong to a family this file already declared above
|
|
231
|
+
// (`MS-HP` in Priority Viewpoints → `MS-HP-001`); `TC-01`, `FR-001`, `ISO-27001` never are;
|
|
232
|
+
// - reference sections (Known Issues, Traceability…) are skipped WITH their subsections, the
|
|
233
|
+
// way the ledger inherits the exclusion (#657), and fenced blocks are skipped;
|
|
234
|
+
// - the header is the row directly above a `|---|` separator (structure, not guessed words);
|
|
235
|
+
// its Priority column (any position, any alias) gives the priority, else column 2 does, as
|
|
236
|
+
// the old scan read it;
|
|
237
|
+
// - `reason` stays empty: viewpoint reasons feed page-type detection, and these rows were never
|
|
238
|
+
// entries before — the page type of an untouched file must not move.
|
|
239
|
+
const declaredFamilies = [...entries.keys()];
|
|
240
|
+
const familyOk = (id: string): boolean =>
|
|
241
|
+
/^(?:VP|FL)-/i.test(id) || declaredFamilies.some((f) => id.startsWith(`${f}-`) || id === f);
|
|
242
|
+
const isSeparator = (l: string): boolean => /^\|[\s|:-]+\|?$/.test(l) && l.includes('-');
|
|
243
|
+
let excludedAt: number | null = null;
|
|
244
|
+
let inFence = false;
|
|
245
|
+
let priorityCol = 1;
|
|
246
|
+
for (let i = 0; i < lines.length; i++) {
|
|
247
|
+
const line = lines[i].trim();
|
|
248
|
+
if (/^(```|~~~)/.test(line)) { inFence = !inFence; continue; }
|
|
249
|
+
if (inFence) continue;
|
|
250
|
+
const head = headingParts(line);
|
|
251
|
+
if (head) {
|
|
252
|
+
if (excludedAt !== null && head.level > excludedAt) continue; // subsection inherits
|
|
253
|
+
const text = head.text.replace(/[*`]/g, '').trim();
|
|
254
|
+
excludedAt = /^Priority Viewpoints/i.test(text) || NON_DECLARATION_SECTION.test(text) ? head.level : null;
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
if (excludedAt !== null || !line.startsWith('|') || isSeparator(line)) continue;
|
|
258
|
+
const cells = line.split('|').map((c) => c.trim()).filter((_, k, a) => k > 0 && k < a.length - 1);
|
|
259
|
+
if (isSeparator((lines[i + 1] ?? '').trim())) { // header row
|
|
260
|
+
const col = cells.findIndex((c) => PRIORITY_HEADER.test(c.replace(/[*`]/g, '').trim()));
|
|
261
|
+
priorityCol = col >= 0 ? col : 1;
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
const id = (cells[0] ?? '').replace(/[*`]/g, '').trim().toUpperCase();
|
|
265
|
+
if (!isViewpointId(id) || !familyOk(id)) continue;
|
|
266
|
+
const declared = priorityOf(cells[priorityCol] ?? '');
|
|
267
|
+
const existing = entries.get(id);
|
|
268
|
+
if (!existing) entries.set(id, { id, ...declared, reason: '' });
|
|
269
|
+
else if (!existing.priorityWord && declared.priorityWord) existing.priorityWord = declared.priorityWord;
|
|
270
|
+
}
|
|
271
|
+
|
|
182
272
|
return [...entries.values()];
|
|
183
273
|
}
|
|
184
274
|
|
|
@@ -199,6 +289,25 @@ function withBackground(sc: ParsedScenario, background?: ParsedScenario): Parsed
|
|
|
199
289
|
return { ...sc, steps: [...bg, ...((sc.steps as ParsedStep[]) || [])] } as ParsedScenario;
|
|
200
290
|
}
|
|
201
291
|
|
|
292
|
+
/**
|
|
293
|
+
* The `VP-<CATEGORY>-<n>` / `FL-<PHASE>-<n>` code in a scenario title, and its category — the ONE
|
|
294
|
+
* reading of it; the manifest kept an older copy of this regex and recorded no code for
|
|
295
|
+
* VP-I18N-001, VP-LIST-DISPLAY-01, VP-AF02-01 or any FL- id (#889).
|
|
296
|
+
*
|
|
297
|
+
* Category is everything between `VP-` and the final `-<sequence>` — INCLUDING hyphens, so
|
|
298
|
+
* compound categories (VP-LIST-DISPLAY-01, VP-ADD-TO-CART-03, VP-PRODUCT-DISCOVERY-02) parse,
|
|
299
|
+
* not just single-word ones. A single-word category (VP-CART-001) still works. (H1)
|
|
300
|
+
* Flows use journey-phase ids (FL-HP-001 / FL-ER-002) — the VP- anchor rejected them, so every
|
|
301
|
+
* flow scenario had NO category and the whole suite bucketed `other` (taxonomy=0%, #569).
|
|
302
|
+
* Segments may carry digits: the use-case framework numbers its flows (VP-AF02-01,
|
|
303
|
+
* VP-EF11-01 — one id per Alternate/Exception Flow), and `[A-Z]+` also silently truncated
|
|
304
|
+
* VP-I18N-001 to no category at all (the `18` broke the class mid-segment).
|
|
305
|
+
*/
|
|
306
|
+
export function vpCodeOf(title: string): { vpCode?: string; category?: string } {
|
|
307
|
+
const m = title.match(/\b(?:VP|FL)-([A-Z][A-Z0-9]*(?:-[A-Z0-9]+)*)-\d+/i);
|
|
308
|
+
return m ? { vpCode: m[0].toUpperCase(), category: m[1].toUpperCase() } : {};
|
|
309
|
+
}
|
|
310
|
+
|
|
202
311
|
function classifyScenario(sc: ParsedScenario): ScenarioInfo {
|
|
203
312
|
const tags = sc.tags || [];
|
|
204
313
|
const deferredToFlow = tags.includes('@deferred:flow');
|
|
@@ -223,18 +332,11 @@ function classifyScenario(sc: ParsedScenario): ScenarioInfo {
|
|
|
223
332
|
const requiresCaps = tags.filter((t) => /^@requires:/i.test(t)).map((t) => t.slice('@requires:'.length).trim().toLowerCase()).filter(Boolean);
|
|
224
333
|
let priority: Priority = 'unknown';
|
|
225
334
|
for (const t of tags) if (PRIORITY_TAGS[t]) priority = PRIORITY_TAGS[t];
|
|
335
|
+
// Deliberately wider than `priority` (every priority word, any case): only PRIORITY-CONTRADICTED reads
|
|
336
|
+
// it, so `@critical` / `@High` are judged there without moving what select-by-ids or the score see.
|
|
337
|
+
const priorityTag = [...tags].reverse().find((t) => /^@(?:critical|high|medium|med|normal|low)$/i.test(t))?.slice(1).toLowerCase();
|
|
226
338
|
|
|
227
|
-
|
|
228
|
-
// compound categories (VP-LIST-DISPLAY-01, VP-ADD-TO-CART-03, VP-PRODUCT-DISCOVERY-02) parse,
|
|
229
|
-
// not just single-word ones. A single-word category (VP-CART-001) still works. (H1)
|
|
230
|
-
// Flows use journey-phase ids (FL-HP-001 / FL-ER-002) — the VP- anchor rejected them, so every
|
|
231
|
-
// flow scenario had NO category and the whole suite bucketed `other` (taxonomy=0%, #569).
|
|
232
|
-
// Segments may carry digits: the use-case framework numbers its flows (VP-AF02-01,
|
|
233
|
-
// VP-EF11-01 — one id per Alternate/Exception Flow), and `[A-Z]+` also silently truncated
|
|
234
|
-
// VP-I18N-001 to no category at all (the `18` broke the class mid-segment).
|
|
235
|
-
const codeMatch = sc.name.match(/\b(?:VP|FL)-([A-Z][A-Z0-9]*(?:-[A-Z0-9]+)*)-\d+/i);
|
|
236
|
-
const vpCode = codeMatch ? codeMatch[0].toUpperCase() : undefined;
|
|
237
|
-
const category = codeMatch ? codeMatch[1].toUpperCase() : undefined;
|
|
339
|
+
const { vpCode, category } = vpCodeOf(sc.name);
|
|
238
340
|
// Project-scheme ID: the leading token of the title (VP0-001 / MS-HP-001 / VP-LIST-001).
|
|
239
341
|
const leadMatch = sc.name.match(/^\s*([A-Za-z][A-Za-z0-9.-]*)/);
|
|
240
342
|
const vpId = leadMatch && isIdLike(leadMatch[1]) ? leadMatch[1].toUpperCase() : undefined;
|
|
@@ -246,22 +348,20 @@ function classifyScenario(sc: ParsedScenario): ScenarioInfo {
|
|
|
246
348
|
const skeletonParts: string[] = [];
|
|
247
349
|
const textParts: string[] = [sc.name];
|
|
248
350
|
const stepTextParts: string[] = [];
|
|
249
|
-
const orderedSteps: Array<{ bucket: 'given' | 'when' | 'then'; text: string }> = [];
|
|
351
|
+
const orderedSteps: Array<{ bucket: 'given' | 'when' | 'then'; text: string; arg?: string }> = [];
|
|
250
352
|
|
|
251
353
|
for (const step of sc.steps as ParsedStep[]) {
|
|
252
354
|
const kw = step.keyword.trim();
|
|
253
355
|
if (kw === 'Given' || kw === 'When' || kw === 'Then') last = kw;
|
|
254
356
|
textParts.push(step.text);
|
|
255
357
|
stepTextParts.push(step.text);
|
|
256
|
-
|
|
257
|
-
//
|
|
258
|
-
//
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
.trim()
|
|
264
|
-
.toLowerCase();
|
|
358
|
+
// The data table is part of what a step says (#817): `User see tab order:` with [FieldA]→[Cancel]
|
|
359
|
+
// and with [Save]→[Close] are different tests, and comparing step text alone called them exact
|
|
360
|
+
// duplicates and one oracle asserted N times. Kept OUT of `text`, which the claim/keyword sensors
|
|
361
|
+
// read as prose.
|
|
362
|
+
const arg = tableArg(step);
|
|
363
|
+
orderedSteps.push({ bucket: (kw === 'And' || kw === 'But' ? last : kw).toLowerCase() as 'given' | 'when' | 'then', text: step.text, ...(arg ? { arg } : {}) });
|
|
364
|
+
const skel = skeletonOf(step.text) + (arg ? ` ${skeletonOf(arg)}` : '');
|
|
265
365
|
skeletonParts.push(`${kw === 'And' || kw === 'But' ? last : kw}:${skel}`);
|
|
266
366
|
|
|
267
367
|
if (last === 'Then') {
|
|
@@ -286,6 +386,7 @@ function classifyScenario(sc: ParsedScenario): ScenarioInfo {
|
|
|
286
386
|
vpCode,
|
|
287
387
|
category,
|
|
288
388
|
priority,
|
|
389
|
+
priorityTag,
|
|
289
390
|
manual,
|
|
290
391
|
thenCount,
|
|
291
392
|
hasDataAssertion: hasData,
|
|
@@ -309,9 +410,11 @@ export function loadScenarios(featurePath: string): ScenarioInfo[] {
|
|
|
309
410
|
if (!fs.existsSync(featurePath)) return [];
|
|
310
411
|
const parser = new GherkinParser();
|
|
311
412
|
const feature = parser.parseFeatureFile(featurePath);
|
|
413
|
+
// Kept apart from `manual` so the audit's accounting is unchanged; only `select` reads it.
|
|
414
|
+
const featureManual = (feature.tags || []).some((t: string) => /^@manual\b/i.test(t)) || undefined;
|
|
312
415
|
return (feature.scenarios || [])
|
|
313
416
|
.filter((s) => !s.stepsName && !s.hookType) // skip @steps/@hook blocks
|
|
314
|
-
.map((s) => ({ ...classifyScenario(withBackground(s, feature.background)), sourceFile: featurePath }));
|
|
417
|
+
.map((s) => ({ ...classifyScenario(withBackground(s, feature.background)), sourceFile: featurePath, featureManual }));
|
|
315
418
|
}
|
|
316
419
|
|
|
317
420
|
/**
|
|
@@ -334,3 +437,24 @@ export function readUnitFeatureText(unitDir: string, unit: string): string {
|
|
|
334
437
|
.map((p) => readTextFile(p))
|
|
335
438
|
.join('\n');
|
|
336
439
|
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Normalised skeleton of step text: keep [refs] (distinct targets = distinct tests), but neutralise
|
|
443
|
+
* {{vars}} and quoted values so EP/data families collapse.
|
|
444
|
+
*/
|
|
445
|
+
function skeletonOf(text: string): string {
|
|
446
|
+
return text
|
|
447
|
+
.replace(/\{\{[^}]*\}\}/g, '{}')
|
|
448
|
+
.replace(/"[^"]*"/g, '""')
|
|
449
|
+
.replace(/\s+/g, ' ')
|
|
450
|
+
.trim()
|
|
451
|
+
.toLowerCase();
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/** A step's data table as one comparable string — `<h1|h2 / c1|c2 / …>` — or undefined. */
|
|
455
|
+
function tableArg(step: ParsedStep): string | undefined {
|
|
456
|
+
const t = step.dataTable;
|
|
457
|
+
if (!t || (!t.headers.length && !t.rows.length)) return undefined;
|
|
458
|
+
const rows = [t.headers, ...t.rows.map((r) => r.cells)].map((cells) => cells.map((c) => c.trim()).join('|'));
|
|
459
|
+
return `<${rows.join(' / ')}>`;
|
|
460
|
+
}
|
package/src/harness/perf.ts
CHANGED
|
@@ -23,10 +23,8 @@
|
|
|
23
23
|
* units:
|
|
24
24
|
* place-order: { scenario_ms: 20000 }
|
|
25
25
|
*/
|
|
26
|
-
import * as fs from 'fs';
|
|
27
26
|
import * as path from 'path';
|
|
28
|
-
import {
|
|
29
|
-
import { readTextFile } from './read-text';
|
|
27
|
+
import { readYamlConfig, warnUnreadableConfig } from './config-file';
|
|
30
28
|
|
|
31
29
|
export interface PerfConfig {
|
|
32
30
|
/** 0..100 — e.g. 75 for p75. */
|
|
@@ -51,12 +49,22 @@ export function perfConfigPath(projectRoot: string): string {
|
|
|
51
49
|
return path.join(projectRoot, 'qa', 'perf.yaml');
|
|
52
50
|
}
|
|
53
51
|
|
|
54
|
-
/**
|
|
52
|
+
/**
|
|
53
|
+
* Absent file → null (perf reporting is opt-in; nothing changes until configured).
|
|
54
|
+
*
|
|
55
|
+
* An UNREADABLE file is not that. Returning null for it drops the perf section out of the delivered
|
|
56
|
+
* matrix (`exporters/matrix/export.ts`) exactly as if no budgets had ever been declared — so a typo
|
|
57
|
+
* in qa/perf.yaml reads, in the deliverable, as "this project has no performance budgets". Say so.
|
|
58
|
+
*/
|
|
55
59
|
export function loadPerfConfig(projectRoot: string): PerfConfig | null {
|
|
56
60
|
const p = perfConfigPath(projectRoot);
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
61
|
+
const read = readYamlConfig<Record<string, unknown> | null>(p);
|
|
62
|
+
if (read.state === 'absent') return null;
|
|
63
|
+
if (read.state === 'unreadable') {
|
|
64
|
+
warnUnreadableConfig(p, read.error, 'perf budgets are not applied and the perf section is omitted from the delivered matrix');
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
const raw = read.value as Record<string, unknown>;
|
|
60
68
|
if (!raw || typeof raw !== 'object') return null;
|
|
61
69
|
const pctRaw = String(raw.percentile ?? 'p75').toLowerCase().replace(/^p/, '');
|
|
62
70
|
const percentile = Math.min(100, Math.max(1, Number(pctRaw) || 75));
|
|
@@ -5,8 +5,10 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import * as fs from 'fs';
|
|
7
7
|
import * as path from 'path';
|
|
8
|
+
import { keywordHit } from './keyword-match';
|
|
8
9
|
import { ScenarioInfo, loadScenarios, idPrefix } from './parse';
|
|
9
10
|
import { parseManualComments } from '../exporters/scenario-merger';
|
|
11
|
+
import { commentLinesOf, isPresencePlaceholderHybrid, keywordStepsOf } from '../exporters/manual-hybrid-oracle';
|
|
10
12
|
import { readTextFile } from './read-text';
|
|
11
13
|
|
|
12
14
|
// ---------- #2 Downstream-scope ----------
|
|
@@ -57,12 +59,19 @@ function downstreamRoutes(specText: string): string[] {
|
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
/**
|
|
60
|
-
* H5 — a
|
|
61
|
-
*
|
|
62
|
+
* H5 — a stateful (cart-mutating) flow with NO per-scenario isolation is flaky: scenarios share
|
|
63
|
+
* state, so cart count/quantity asserts race. Mitigations: @cleanup:cart, @isolate, a fresh
|
|
62
64
|
* browser context, or a "Given … empty cart" background. Returns true when the risk is unmitigated.
|
|
65
|
+
*
|
|
66
|
+
* #759 — this used to require a literal `@parallel`, which made the gate miss the common case.
|
|
67
|
+
* Parallel is the DEFAULT (code-generator.ts: `isParallelFeature = !isSerialFeature`); `@parallel`
|
|
68
|
+
* only NAMES that default and changes no codegen, so an untagged stateful flow is isolated exactly
|
|
69
|
+
* like a tagged one and carries exactly the same risk. The tag that actually changes the emitted
|
|
70
|
+
* suite is `@serial` — one shared ordered page, where state carry-over is the point — so that is
|
|
71
|
+
* what exempts a flow now. Same reading as serialCascadeRisk() below, which already said so.
|
|
63
72
|
*/
|
|
64
73
|
export function isolationRisk(featureText: string, stateful: boolean, collection = 'cart'): boolean {
|
|
65
|
-
if (!stateful ||
|
|
74
|
+
if (!stateful || /@serial\b/i.test(featureText)) return false;
|
|
66
75
|
// What discharges the risk is isolation of THE MUTATED COLLECTION — so the cleanup tag has
|
|
67
76
|
// to name it (`@cleanup:cart` for a cart; `@cleanup:registration` for a registration flow).
|
|
68
77
|
// A cleanup of something else (`@cleanup:overlay`) leaves the shared state shared.
|
|
@@ -149,8 +158,12 @@ export function downstreamScope(
|
|
|
149
158
|
// names the same screen under its human label (register ↔ [Registration]).
|
|
150
159
|
const labelsOf = (s: ScenarioInfo): string[] =>
|
|
151
160
|
[...s.haystack.matchAll(/\[([^\]]+)\]/g)].map((m) => m[1].split(':')[0]);
|
|
161
|
+
// The slug is the route's last segment — in practice an ordinary noun (`order`, `detail`,
|
|
162
|
+
// `login`), so it is matched as a WORD (#779). `dis·order` is not a reference to `/account/order`,
|
|
163
|
+
// and crediting it downgraded a route nothing mentions from `absent` to merely `underCovered`.
|
|
164
|
+
// The full route stays a literal: a path is an identifier, not prose.
|
|
152
165
|
const refs = scenarios.filter((s) =>
|
|
153
|
-
s.haystack
|
|
166
|
+
keywordHit(s.haystack, slug) || s.haystack.includes(route.toLowerCase()) ||
|
|
154
167
|
labelsOf(s).some((l) => sameScreenName(slug, l)));
|
|
155
168
|
if (!refs.length) {
|
|
156
169
|
// A scenario's haystack is its name + steps, so a route documented in a @manual scenario's
|
|
@@ -208,14 +221,15 @@ export function manualOracle(featureText: string): ManualOracleResult {
|
|
|
208
221
|
// Observable:/Oracle:/Expected: line (parseManualComments — the SAME parser the
|
|
209
222
|
// delivery exporter uses, so this gate can't drift from the exported cells).
|
|
210
223
|
// Prose paragraphs / rationale-only comments export EMPTY Steps + Expected.
|
|
224
|
+
// • HAS a Then whose every assertion is a bare presence placeholder, with the real oracle
|
|
225
|
+
// in the comments (#810) — the same hybrid wearing a Then (manual-hybrid-oracle.ts, the
|
|
226
|
+
// SAME predicate as compile-time SG-W008).
|
|
211
227
|
const hasGherkinSteps = /^\s*(Given|When|Then)\s/m.test(b);
|
|
228
|
+
const commentLines = commentLinesOf(b);
|
|
212
229
|
let bad: boolean;
|
|
213
230
|
if (hasGherkinSteps) {
|
|
214
|
-
bad = !/^\s*Then\s/m.test(b);
|
|
231
|
+
bad = !/^\s*Then\s/m.test(b) || isPresencePlaceholderHybrid(keywordStepsOf(b), commentLines);
|
|
215
232
|
} else {
|
|
216
|
-
const commentLines = b.split('\n')
|
|
217
|
-
.filter((l) => /^\s*#/.test(l))
|
|
218
|
-
.map((l) => l.replace(/^\s*#\s?/, ''));
|
|
219
233
|
const parsed = parseManualComments(commentLines);
|
|
220
234
|
bad = !parsed.steps.length || !parsed.expected.length;
|
|
221
235
|
}
|
|
Binary file
|
package/src/harness/read-text.ts
CHANGED
|
@@ -17,12 +17,43 @@ import * as fs from 'fs';
|
|
|
17
17
|
*
|
|
18
18
|
* A Windows-authored spec is completely normal, so every harness parser that
|
|
19
19
|
* reads a user file must go through here.
|
|
20
|
+
*
|
|
21
|
+
* A leading UTF-8 BOM is stripped for the same reason. It is what PowerShell's `Out-File` and
|
|
22
|
+
* several Windows editors write by default, and it is invisible in every editor — but it breaks a
|
|
23
|
+
* line-anchored `/^\s*Scenario:/` on line 1, and `JSON.parse` rejects it outright while npm and
|
|
24
|
+
* most other tools quietly ignore it. A file the rest of the ecosystem reads fine must not read as
|
|
25
|
+
* corrupt here.
|
|
20
26
|
*/
|
|
21
27
|
export function readTextFile(filePath: string): string {
|
|
22
|
-
return fs.readFileSync(filePath, 'utf-8').replace(/\r\n?/g, '\n');
|
|
28
|
+
return fs.readFileSync(filePath, 'utf-8').replace(/^\uFEFF/, '').replace(/\r\n?/g, '\n');
|
|
23
29
|
}
|
|
24
30
|
|
|
25
31
|
/** `readTextFile` split into lines — the common case for line-anchored parsers. */
|
|
26
32
|
export function readTextLines(filePath: string): string[] {
|
|
27
33
|
return readTextFile(filePath).split('\n');
|
|
28
34
|
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Drop HTML comments (`<!-- … -->`, multi-line too) — they are author notes and scaffold samples,
|
|
38
|
+
* never content. Newlines inside a comment are kept so every later line keeps its position (#841).
|
|
39
|
+
*
|
|
40
|
+
* Only a CLOSED comment is dropped. An unclosed `<!--` (a forgotten `-->`) stays text: dropping it
|
|
41
|
+
* to the end of the file would silently delete every declaration below it. A fenced code block is
|
|
42
|
+
* content, and so is a `<!--` written inside backticks: both are how a document SHOWS the syntax.
|
|
43
|
+
*/
|
|
44
|
+
export function stripHtmlComments(text: string): string {
|
|
45
|
+
// A comment body never holds another `<!--`: an opener with no `-->` before the next opener is
|
|
46
|
+
// the unclosed one, and must not reach a LATER comment's closer and eat everything between.
|
|
47
|
+
const blank = (s: string): string => s.replace(/(?<!`)<!--(?:(?!<!--)[\s\S])*?-->/g, (m) => m.replace(/[^\n]/g, ''));
|
|
48
|
+
// split() with a capture group keeps the fences at the odd indexes.
|
|
49
|
+
return text.split(/(^```[^\n]*\n[\s\S]*?^```[^\n]*$)/m).map((part, i) => (i % 2 ? part : blank(part))).join('');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* `test-viewpoint.md` as every harness sensor must read it: normalised, comments removed, and
|
|
54
|
+
* '' when the unit has none. `sungen add` scaffolds its samples inside `<!-- -->`; read raw, an
|
|
55
|
+
* untouched scaffold declared VP-UI/VP-VAL/VP-LOGIC/VP-SEC and carried 12 phantom claims (#841).
|
|
56
|
+
*/
|
|
57
|
+
export function readViewpointText(filePath: string): string {
|
|
58
|
+
return fs.existsSync(filePath) ? stripHtmlComments(readTextFile(filePath)) : '';
|
|
59
|
+
}
|
|
@@ -107,6 +107,12 @@ const ASSERTION_STEP_COMMENT = /^\s*\/\/\s*Assert\b/;
|
|
|
107
107
|
// but as a bare substring it would also exempt a genuinely hollow step whose test-data or step
|
|
108
108
|
// text happened to contain it.
|
|
109
109
|
const NOOP_STEP_MARKER = /^\s*\/\/\s*App launched via wdio capabilities/;
|
|
110
|
+
// The same kind of step on the web adapter, where the explanation IS the step comment: the
|
|
111
|
+
// step-mapper compiles `Given User is on [Edit] dialog` (and its exit) to `// Enter dialog scope:
|
|
112
|
+
// Edit` and no code, because it only re-roots the steps after it — the next assertion runs as
|
|
113
|
+
// `page.getByRole('dialog', …).getByRole(…)`. A field unit reported 13 BYPASS, every one of them
|
|
114
|
+
// this comment, and exited 2 on a correct suite (#881). Anchored to the whole marker line.
|
|
115
|
+
const SCOPE_STEP_MARKER = /^\s*\/\/\s*(?:Enter|Exit) dialog scope(?::\s*.*)?$/;
|
|
110
116
|
|
|
111
117
|
/** A spec comment line reduced to its text, for comparison against a compiler step comment. */
|
|
112
118
|
const commentText = (line: string): string => line.trim().replace(/^\/\/\s?/, '').trim();
|
|
@@ -241,7 +247,7 @@ export function analyzeFaithfulness(
|
|
|
241
247
|
const hasCode = region.some((l) => !isComment(l) && CODE_LINE.test(l));
|
|
242
248
|
// A documented no-op emits its explanation INTO the step's region, so read the region
|
|
243
249
|
// (not just the marker line) for it — see NOOP_STEP_MARKER.
|
|
244
|
-
if (hasCode || region.some((l) => NOOP_STEP_MARKER.test(l))) continue;
|
|
250
|
+
if (hasCode || region.some((l) => NOOP_STEP_MARKER.test(l)) || SCOPE_STEP_MARKER.test(body[markers[s]])) continue;
|
|
245
251
|
if (tailAmbiguous && s === markers.length - 1) continue;
|
|
246
252
|
hollowSteps.push({ test: title, step: body[markers[s]].trim().slice(0, 70) });
|
|
247
253
|
}
|
|
@@ -11,6 +11,19 @@ import * as path from 'path';
|
|
|
11
11
|
|
|
12
12
|
export interface SecretHit { file: string; line: number; reason: string }
|
|
13
13
|
|
|
14
|
+
/** A yaml sink the scanner could not read. Not a hit — the absence of one it cannot vouch for. */
|
|
15
|
+
export interface UnscannedFile { file: string; reason: string }
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The scan result, deliberately two lists rather than one.
|
|
19
|
+
*
|
|
20
|
+
* This is a security control, and its whole value is that "no hits" means "I looked". A file the
|
|
21
|
+
* scanner could not read used to be dropped by a bare `catch`, so an unread yaml and a yaml with no
|
|
22
|
+
* secrets produced the identical answer: clean. Returning `unscanned` alongside makes the caller
|
|
23
|
+
* hold both facts — what was found, and what the finding does not cover.
|
|
24
|
+
*/
|
|
25
|
+
export interface SecretScanResult { hits: SecretHit[]; unscanned: UnscannedFile[] }
|
|
26
|
+
|
|
14
27
|
// High-confidence vendor token prefixes (very low false-positive rate).
|
|
15
28
|
const VENDOR_PREFIXES = /\b(figd_[A-Za-z0-9_-]{12,}|ghp_[A-Za-z0-9]{20,}|gho_[A-Za-z0-9]{20,}|glpat-[A-Za-z0-9_-]{16,}|sk-[A-Za-z0-9]{16,}|AKIA[0-9A-Z]{12,}|xox[baprs]-[A-Za-z0-9-]{10,}|AIza[0-9A-Za-z_-]{30,})/;
|
|
16
29
|
// A secret-named key assigned a long, non-placeholder value.
|
|
@@ -51,16 +64,59 @@ function scanText(text: string, file: string): SecretHit[] {
|
|
|
51
64
|
/** Scan a unit dir's committed yaml sinks for likely real secrets: test-data/*.yaml,
|
|
52
65
|
* mock/*.yaml (mock response bodies are a credential magnet — pasted DevTools
|
|
53
66
|
* responses carry real tokens), and api/*.yaml (endpoint catalogs carry headers). */
|
|
54
|
-
export function scanTestDataSecrets(baseDir: string):
|
|
67
|
+
export function scanTestDataSecrets(baseDir: string): SecretScanResult {
|
|
55
68
|
const hits: SecretHit[] = [];
|
|
69
|
+
const unscanned: UnscannedFile[] = [];
|
|
70
|
+
const why = (e: unknown): string => (e instanceof Error ? e.message.split('\n')[0] : String(e));
|
|
71
|
+
|
|
56
72
|
for (const sub of ['test-data', 'mock', 'api']) {
|
|
57
73
|
const dir = path.join(baseDir, sub);
|
|
58
74
|
if (!fs.existsSync(dir)) continue;
|
|
59
|
-
|
|
75
|
+
|
|
76
|
+
// A directory that cannot be LISTED is a directory that cannot be scanned. This used to throw
|
|
77
|
+
// out of the scanner and abort the whole `generate`/`delivery` run; report it in the same place
|
|
78
|
+
// a hit would appear instead, so the operator learns what the clean verdict does not cover
|
|
79
|
+
// rather than losing the deliverable to an unrelated permission fault.
|
|
80
|
+
let entries: string[];
|
|
81
|
+
try { entries = fs.readdirSync(dir); } catch (e) { unscanned.push({ file: sub, reason: why(e) }); continue; }
|
|
82
|
+
|
|
83
|
+
for (const f of entries) {
|
|
60
84
|
if (!/\.ya?ml$/i.test(f)) continue;
|
|
61
85
|
const p = path.join(dir, f);
|
|
62
|
-
|
|
86
|
+
const rel = path.join(sub, f);
|
|
87
|
+
let text: string;
|
|
88
|
+
// `readdirSync` listed this file, so it exists as a name. A read that fails here means a
|
|
89
|
+
// restricted mount, a dangling symlink, or a file removed between the two calls — never
|
|
90
|
+
// "there was nothing to read". Silently skipping it let a committed credential ship under an
|
|
91
|
+
// explicit "clean" verdict.
|
|
92
|
+
try { text = fs.readFileSync(p, 'utf-8'); } catch (e) { unscanned.push({ file: rel, reason: why(e) }); continue; }
|
|
93
|
+
hits.push(...scanText(text, rel));
|
|
63
94
|
}
|
|
64
95
|
}
|
|
65
|
-
return hits;
|
|
96
|
+
return { hits, unscanned };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* The operator-facing lines for a scan result — one renderer, because `generate` and `delivery`
|
|
101
|
+
* had grown two copies of it and only one of them would have learned about `unscanned`.
|
|
102
|
+
*
|
|
103
|
+
* Empty array when there is nothing to say. The unscanned block is NOT folded into the hit block:
|
|
104
|
+
* "possible secret" and "not examined" are different claims, and printing the second under the
|
|
105
|
+
* first's heading invites it to be dismissed as another false positive.
|
|
106
|
+
*/
|
|
107
|
+
export function formatSecretScan(result: SecretScanResult, limit = 10): string[] {
|
|
108
|
+
const out: string[] = [];
|
|
109
|
+
if (result.hits.length) {
|
|
110
|
+
out.push('', '⚠️ Possible secret(s) in committed test-data (review — do NOT commit real credentials):');
|
|
111
|
+
for (const h of result.hits.slice(0, limit)) out.push(` ${h.file}:${h.line} — ${h.reason}`);
|
|
112
|
+
if (result.hits.length > limit) out.push(` … and ${result.hits.length - limit} more`);
|
|
113
|
+
out.push(' Move real secrets to an env overlay / CI secret; keep test-data placeholders only.');
|
|
114
|
+
}
|
|
115
|
+
if (result.unscanned.length) {
|
|
116
|
+
out.push('', `⚠️ Secret scan could NOT read ${result.unscanned.length} committed yaml file(s) — they are NOT covered by the result above:`);
|
|
117
|
+
for (const u of result.unscanned.slice(0, limit)) out.push(` ${u.file} — ${u.reason}`);
|
|
118
|
+
if (result.unscanned.length > limit) out.push(` … and ${result.unscanned.length - limit} more`);
|
|
119
|
+
out.push(' Make them readable and re-run before treating this unit as scanned.');
|
|
120
|
+
}
|
|
121
|
+
return out;
|
|
66
122
|
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Select scenarios by test-case ID — backs `sungen select` and `run-test --ids` (#835).
|
|
3
|
+
*
|
|
4
|
+
* An ID means what it means everywhere else in the harness: `vpId`, the leading ID token of the
|
|
5
|
+
* scenario title (`VP-UI-001`, `MS-HP-001`, `VP0-001`). Matching is exact and case-insensitive —
|
|
6
|
+
* `VP-UI-001` never selects `VP-UI-0010` or `VP-UI-001-B` — and every ID that selects nothing
|
|
7
|
+
* runnable is reported, because the alternative is a 0-test run that reports green.
|
|
8
|
+
*/
|
|
9
|
+
import * as path from 'path';
|
|
10
|
+
import { ScenarioInfo } from './parse';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Escape scenario titles into one `-g` / `--mochaOpts.grep` regex. Full titles, not bare IDs:
|
|
14
|
+
* a title starts with its own ID, so it can only appear inside its own test's title — and a
|
|
15
|
+
* `@cases` row test (`<title> — <row label>`) still contains it.
|
|
16
|
+
*/
|
|
17
|
+
export function titlesToGrep(titles: string[]): string {
|
|
18
|
+
return titles.map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface IdSelectionGroup {
|
|
22
|
+
/** Feature basename (`products`, `home-modal`) — names the spec and the result file. */
|
|
23
|
+
feature: string;
|
|
24
|
+
ids: string[];
|
|
25
|
+
titles: string[];
|
|
26
|
+
grep: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface IdSelection {
|
|
30
|
+
features: IdSelectionGroup[];
|
|
31
|
+
/** IDs no scenario carries, with near matches (`VP-VAL-001` → `VP-VAL-001-B`). */
|
|
32
|
+
unknown: Array<{ id: string; didYouMean: string[] }>;
|
|
33
|
+
/** IDs whose every scenario is manual, owned by a flow or waiting on a capability — nothing to run. */
|
|
34
|
+
notRunnable: Array<{ id: string; reason: string }>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** `"VP-UI-001, vp-ui-002 VP-UI-001"` → `['VP-UI-001', 'VP-UI-002']`. */
|
|
38
|
+
export function parseIdList(raw: string): string[] {
|
|
39
|
+
return [...new Set(raw.split(/[\s,]+/).map((s) => s.trim().toUpperCase()).filter(Boolean))];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** `VP-C-002.` → `VP-C-002`: the title token may carry trailing punctuation the user never types. */
|
|
43
|
+
const idOf = (s: ScenarioInfo): string | undefined => s.vpId?.replace(/[.-]+$/, '') || undefined;
|
|
44
|
+
|
|
45
|
+
function notRunnableReason(s: ScenarioInfo, enabledCaps: Set<string>): string | null {
|
|
46
|
+
if (s.deferredToFlow) return `owned by a flow${s.ownedByFlow ? ` (${s.ownedByFlow})` : ''} — run it there`;
|
|
47
|
+
if (s.featureManual) return 'its Feature is @manual — no spec is generated for it';
|
|
48
|
+
if (s.manual) return 'manual — no automated test exists';
|
|
49
|
+
// Compiled as `test.skip` while the capability is off: the run would report a skip, not a result.
|
|
50
|
+
const missing = (s.requiresCaps ?? []).filter((c) => !enabledCaps.has(c));
|
|
51
|
+
if (missing.length) return `needs capability ${missing.join(', ')} — compiled as a skip until \`sungen capability add ${missing.join(' ')}\``;
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function selectByIds(scenarios: ScenarioInfo[], ids: string[], unit: string, enabledCaps: Set<string> = new Set()): IdSelection {
|
|
56
|
+
const out: IdSelection = { features: [], unknown: [], notRunnable: [] };
|
|
57
|
+
const groups = new Map<string, IdSelectionGroup>();
|
|
58
|
+
const allIds = [...new Set(scenarios.map(idOf).filter((v): v is string => !!v))];
|
|
59
|
+
|
|
60
|
+
for (const id of ids) {
|
|
61
|
+
const hits = scenarios.filter((s) => idOf(s) === id);
|
|
62
|
+
if (hits.length === 0) {
|
|
63
|
+
const didYouMean = allIds.filter((v) => v.startsWith(`${id}-`) || id.startsWith(`${v}-`)).slice(0, 5);
|
|
64
|
+
out.unknown.push({ id, didYouMean });
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const runnable = hits.filter((s) => !notRunnableReason(s, enabledCaps));
|
|
68
|
+
if (runnable.length === 0) {
|
|
69
|
+
out.notRunnable.push({ id, reason: notRunnableReason(hits[0], enabledCaps)! });
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
for (const s of runnable) {
|
|
73
|
+
const feature = s.sourceFile ? path.basename(s.sourceFile, '.feature') : unit;
|
|
74
|
+
const g = groups.get(feature) ?? { feature, ids: [], titles: [], grep: '' };
|
|
75
|
+
if (!g.ids.includes(id)) g.ids.push(id);
|
|
76
|
+
if (!g.titles.includes(s.name)) g.titles.push(s.name);
|
|
77
|
+
groups.set(feature, g);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
for (const g of groups.values()) g.grep = titlesToGrep(g.titles);
|
|
81
|
+
out.features = [...groups.values()];
|
|
82
|
+
return out;
|
|
83
|
+
}
|