@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
|
@@ -6,9 +6,8 @@
|
|
|
6
6
|
import { Command } from 'commander';
|
|
7
7
|
import * as path from 'path';
|
|
8
8
|
import * as fs from 'fs';
|
|
9
|
-
import { execSync } from 'child_process';
|
|
10
9
|
import { parse as parseYaml } from 'yaml';
|
|
11
|
-
import { parseFeatureMetadata } from '../../exporters/feature-parser';
|
|
10
|
+
import { isHookScenario, parseFeatureMetadata } from '../../exporters/feature-parser';
|
|
12
11
|
import { parseSpecFile } from '../../exporters/spec-parser';
|
|
13
12
|
import { loadTestData } from '../../exporters/test-data-resolver';
|
|
14
13
|
import { loadSelectorKeyMap } from '../../exporters/selector-key-resolver';
|
|
@@ -21,12 +20,12 @@ import {
|
|
|
21
20
|
renderCsv,
|
|
22
21
|
writeCsv,
|
|
23
22
|
} from '../../exporters/csv-exporter';
|
|
24
|
-
import { renderXlsxMultiSheet, writeXlsx, buildApiDetailRows, addApiDetailSheet } from '../../exporters/xlsx-exporter';
|
|
23
|
+
import { renderXlsxMultiSheet, writeXlsx, buildApiDetailRows, addApiDetailSheet, deliverableXlsxPath } from '../../exporters/xlsx-exporter';
|
|
25
24
|
import { loadApiCatalog } from '../../exporters/api-catalog-loader';
|
|
26
25
|
import { resolveApiEnvironment } from '../../exporters/api-environment';
|
|
27
|
-
import {
|
|
26
|
+
import { getEnvironment, platformOfResultFile, RunEnvironment } from '../../exporters/run-environment';
|
|
28
27
|
import { resolvePlatformAppId } from '../../exporters/mobile-app-id';
|
|
29
|
-
import { scanTestDataSecrets } from '../../harness/secret-scan';
|
|
28
|
+
import { scanTestDataSecrets, formatSecretScan, type SecretScanResult } from '../../harness/secret-scan';
|
|
30
29
|
import { EnvironmentInfo, PreflightCheck, ScreenSummary, TestCaseRow, ApiCatalogEntry } from '../../exporters/types';
|
|
31
30
|
import { mapFilePath } from '../../exporters/matrix/map-loader';
|
|
32
31
|
import {
|
|
@@ -39,7 +38,9 @@ import {
|
|
|
39
38
|
hasReview,
|
|
40
39
|
} from '../../exporters/matrix/export';
|
|
41
40
|
import { MatrixFinding } from '../../exporters/matrix/types';
|
|
41
|
+
import { applyManualCarry, ManualCarryReport, PriorManualResults, readPriorManualResults } from '../../exporters/matrix/carry-manual-results';
|
|
42
42
|
import { formatTestOutcome, resultTotals } from '../../exporters/matrix/result-totals';
|
|
43
|
+
import { readCapabilities } from '../../harness/capability';
|
|
43
44
|
|
|
44
45
|
const COLOR = {
|
|
45
46
|
reset: '\x1b[0m',
|
|
@@ -84,6 +85,12 @@ export interface DeliveryTarget {
|
|
|
84
85
|
isFlow: boolean;
|
|
85
86
|
}
|
|
86
87
|
|
|
88
|
+
/** The command that regenerates a target's unit — flag by kind, the unit (not feature) name. */
|
|
89
|
+
function generateCommandFor(target: DeliveryTarget): string {
|
|
90
|
+
const flag = target.kind === 'flow' ? '--flow' : target.kind === 'api' ? '--api' : '--screen';
|
|
91
|
+
return `sungen generate ${flag} ${target.screen}`;
|
|
92
|
+
}
|
|
93
|
+
|
|
87
94
|
function makeTarget(screen: string, featureBaseName: string, kind: UnitKind): DeliveryTarget {
|
|
88
95
|
return { screen, featureBaseName, name: featureBaseName, kind, isFlow: kind === 'flow' };
|
|
89
96
|
}
|
|
@@ -340,34 +347,27 @@ export function hasBlockingMissing(p: PreflightCheck): boolean {
|
|
|
340
347
|
// Environment
|
|
341
348
|
// ----------------------------------------------------------------------------
|
|
342
349
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
if (fs.existsSync(configPath)) {
|
|
348
|
-
const c = fs.readFileSync(configPath, 'utf-8');
|
|
349
|
-
// Both config shapes: the legacy literal `baseURL: 'x'` and the env-aware
|
|
350
|
-
// `baseURL: resolveBaseUrl('x')` (the quoted value is the static fallback).
|
|
351
|
-
const baseMatch = c.match(/baseURL:\s*(?:resolveBaseUrl\(\s*)?['"]([^'"]+)['"]/);
|
|
352
|
-
if (baseMatch) baseURL = baseMatch[1];
|
|
353
|
-
const projMatch = c.match(/name:\s*['"]([^'"]+)['"]/);
|
|
354
|
-
if (projMatch) projectName = projMatch[1];
|
|
355
|
-
}
|
|
356
|
-
// SUNGEN_BASE_URL (shell > .env.qa.<SUNGEN_ENV> > .env.qa) overrides the static
|
|
357
|
-
// value — the Test Environment column must show the host the run actually hit.
|
|
358
|
-
baseURL = resolveWebBaseUrl(cwd, baseURL);
|
|
359
|
-
|
|
360
|
-
let executor = process.env.CI_USER || process.env.USER || '';
|
|
361
|
-
try {
|
|
362
|
-
const gitUser = execSync('git config user.name', { cwd, encoding: 'utf-8' }).trim();
|
|
363
|
-
if (gitUser) executor = gitUser;
|
|
364
|
-
} catch {
|
|
365
|
-
// ignore
|
|
366
|
-
}
|
|
350
|
+
/** The unit's path for the Environment column — a flow records it in its page selector. */
|
|
351
|
+
function targetFeaturePath(target: DeliveryTarget, declared: string | undefined, selectorsFile: string): string | undefined {
|
|
352
|
+
return declared || (target.isFlow ? readFlowPagePath(selectorsFile) : undefined);
|
|
353
|
+
}
|
|
367
354
|
|
|
368
|
-
|
|
355
|
+
/**
|
|
356
|
+
* Executor + environment for the matrix's run-result rows (#850) — resolved exactly as the
|
|
357
|
+
* legacy workbook resolves them: the API datasource host for an api unit, and on mobile the
|
|
358
|
+
* app id of the OS the result file came from (`<f>-test-result.<os>[.<env>].json`).
|
|
359
|
+
*/
|
|
360
|
+
function matrixRunEnv(cwd: string, target: DeliveryTarget, env: EnvironmentInfo, paths: MatrixTargetPaths): RunEnvironment {
|
|
361
|
+
const base = qaDir(cwd, target);
|
|
362
|
+
let declared: string | undefined;
|
|
363
|
+
try { declared = parseFeatureMetadata(paths.featureFile).featurePath; } catch { /* loadMatrixModel reports it */ }
|
|
364
|
+
const featurePath = targetFeaturePath(target, declared, path.join(base, 'selectors', `${target.featureBaseName}.yaml`));
|
|
365
|
+
const apiEnv = target.kind === 'api' ? resolveApiEnvironment(cwd, loadApiCatalog(cwd, target.kind, target.screen)) : null;
|
|
366
|
+
const os = platformOfResultFile(paths.resultsPath) ?? process.env.MOBILE_PLATFORM ?? process.env.SUNGEN_ENV;
|
|
367
|
+
return { ...env, ...(apiEnv ?? {}), featurePath: resolvePlatformAppId(featurePath, os) };
|
|
369
368
|
}
|
|
370
369
|
|
|
370
|
+
|
|
371
371
|
/**
|
|
372
372
|
* True when this is a mobile project (`platform: mobile` in qa/capabilities.yaml).
|
|
373
373
|
* Mobile results are written PER-OS (`<feature>-test-result.android|ios.json`) —
|
|
@@ -375,14 +375,11 @@ function getEnvironment(cwd: string): EnvironmentInfo {
|
|
|
375
375
|
* emits one csv+xlsx per OS and skips the (empty/misleading) base aggregate.
|
|
376
376
|
*/
|
|
377
377
|
function isMobileProject(cwd: string): boolean {
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
} catch {
|
|
384
|
-
return false;
|
|
385
|
-
}
|
|
378
|
+
// Reads through `readCapabilities` rather than re-parsing the file: the third copy of this parse
|
|
379
|
+
// was the third place a malformed capabilities.yaml quietly read as "web". The shared reader
|
|
380
|
+
// reports the breakage once per process; here it still degrades to web, which is the safe
|
|
381
|
+
// direction (per-OS delivery is skipped, nothing is deleted).
|
|
382
|
+
return readCapabilities(cwd).platform === 'mobile';
|
|
386
383
|
}
|
|
387
384
|
|
|
388
385
|
/**
|
|
@@ -406,6 +403,18 @@ function removeBaseDeliverables(cwd: string, featureBaseName: string): void {
|
|
|
406
403
|
// Per-screen export
|
|
407
404
|
// ----------------------------------------------------------------------------
|
|
408
405
|
|
|
406
|
+
/**
|
|
407
|
+
* #810 — a manual oracle that lives only in unlabelled `Tester verifies:` prose is exported
|
|
408
|
+
* verbatim into Expected (never dropped), and the author is told to structure it.
|
|
409
|
+
*/
|
|
410
|
+
function warnUnlabeledManualOracles(label: string, merged: ReturnType<typeof mergeFeatureAndSpec>): void {
|
|
411
|
+
const hits = merged.filter((m) => m.unlabeledManualOracle);
|
|
412
|
+
if (!hits.length) return;
|
|
413
|
+
log(`${COLOR.yellow}⚠ ${label}: manual oracle found only in an unlabeled comment (${hits.length})${COLOR.reset} — exported verbatim into Expected; write it as numbered "N. Observable: …" lines:`);
|
|
414
|
+
for (const m of hits.slice(0, 10)) log(` ${m.feature.name.slice(0, 90)}`);
|
|
415
|
+
if (hits.length > 10) log(` … and ${hits.length - 10} more`);
|
|
416
|
+
}
|
|
417
|
+
|
|
409
418
|
async function exportTarget(
|
|
410
419
|
cwd: string,
|
|
411
420
|
target: DeliveryTarget,
|
|
@@ -424,14 +433,12 @@ async function exportTarget(
|
|
|
424
433
|
const spec = parseSpecFile(specFile);
|
|
425
434
|
|
|
426
435
|
const merged = mergeFeatureAndSpec(feature, spec);
|
|
436
|
+
warnUnlabeledManualOracles(label, merged);
|
|
427
437
|
// Screens get their URL path from the .feature file directly. Flows record
|
|
428
438
|
// it in selectors/<feature>.yaml under the page-selector block — fall back
|
|
429
439
|
// to that so the Environment column shows a full URL for flows too.
|
|
430
|
-
let featurePath = feature.featurePath;
|
|
431
440
|
const selectorsFile = path.join(base, 'selectors', `${target.featureBaseName}.yaml`);
|
|
432
|
-
|
|
433
|
-
featurePath = readFlowPagePath(selectorsFile);
|
|
434
|
-
}
|
|
441
|
+
const featurePath = targetFeaturePath(target, feature.featurePath, selectorsFile);
|
|
435
442
|
const selectorKeyMap = loadSelectorKeyMap(selectorsFile);
|
|
436
443
|
|
|
437
444
|
const specLink = fs.existsSync(specMdFile) ? path.relative(cwd, specMdFile) : '';
|
|
@@ -463,6 +470,7 @@ async function exportTarget(
|
|
|
463
470
|
if (isMobileProject(cwd)) removeBaseDeliverables(cwd, target.featureBaseName);
|
|
464
471
|
const rows = buildTestCaseRows({
|
|
465
472
|
screen: label,
|
|
473
|
+
generateCommand: generateCommandFor(target),
|
|
466
474
|
featureName: feature.featureName,
|
|
467
475
|
// OS-precise env column: a mobile dual-id Path resolves to just the
|
|
468
476
|
// android package / ios bundle id for SUNGEN_ENV=android|ios.
|
|
@@ -521,6 +529,7 @@ async function exportTarget(
|
|
|
521
529
|
for (const variant of osVariants) {
|
|
522
530
|
const variantRows = buildTestCaseRows({
|
|
523
531
|
screen: label,
|
|
532
|
+
generateCommand: generateCommandFor(target),
|
|
524
533
|
featureName: feature.featureName,
|
|
525
534
|
featurePath: resolvePlatformAppId(featurePath, variant.locale), // OS-precise app id
|
|
526
535
|
merged,
|
|
@@ -573,6 +582,7 @@ async function exportTarget(
|
|
|
573
582
|
const variantResults = variant.resultsPath ? loadPlaywrightReport(variant.resultsPath) : null;
|
|
574
583
|
const variantRows = buildTestCaseRows({
|
|
575
584
|
screen: label,
|
|
585
|
+
generateCommand: generateCommandFor(target),
|
|
576
586
|
featureName: feature.featureName,
|
|
577
587
|
// No-op for web (URL Path); for a mobile fall-through (a no-suffix result
|
|
578
588
|
// json) this collapses the dual-id to the android package so the combined
|
|
@@ -674,17 +684,21 @@ async function exportMatrixTarget(
|
|
|
674
684
|
cwd: string,
|
|
675
685
|
target: DeliveryTarget,
|
|
676
686
|
opts: { check?: boolean; preview?: boolean; format?: MatrixFormat },
|
|
687
|
+
env: EnvironmentInfo,
|
|
677
688
|
): Promise<MatrixRunSummary | 'skipped' | null> {
|
|
678
689
|
const paths = matrixPathsFor(cwd, target);
|
|
690
|
+
paths.runEnv = matrixRunEnv(cwd, target, env, paths);
|
|
679
691
|
|
|
680
692
|
// A feature whose scenarios are ALL @exploration probes has nothing to deliver —
|
|
681
693
|
// skip it quietly (no map required) instead of demanding a reviewed grouping for
|
|
682
694
|
// probes that are, by contract, outside the official suite.
|
|
683
695
|
{
|
|
684
696
|
const featureMeta = parseFeatureMetadata(paths.featureFile);
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
697
|
+
// Hooks are setup/teardown, never test cases — they neither need nor make a deliverable (#880).
|
|
698
|
+
const scenarios = featureMeta.scenarios.filter((s) => !isHookScenario(s));
|
|
699
|
+
const deliverable = scenarios.filter((s) => !s.tags.some((t) => /^@exploration\b/i.test(t)));
|
|
700
|
+
if (scenarios.length > 0 && deliverable.length === 0) {
|
|
701
|
+
log(`${COLOR.gray}${paths.label}: nothing to deliver — all ${scenarios.length} scenario(s) are @exploration probes (outside the official suite).${COLOR.reset}`);
|
|
688
702
|
return 'skipped';
|
|
689
703
|
}
|
|
690
704
|
}
|
|
@@ -704,6 +718,17 @@ async function exportMatrixTarget(
|
|
|
704
718
|
return null;
|
|
705
719
|
}
|
|
706
720
|
|
|
721
|
+
// The tester's recorded Manual results are part of the outcome: read them BEFORE anything is
|
|
722
|
+
// counted, so this line, the workbook's totals and the summary table all agree (#891). A file
|
|
723
|
+
// that cannot be read is reported where it matters — when it would be overwritten.
|
|
724
|
+
const format = opts.format ?? 'xlsx';
|
|
725
|
+
let prior: PriorManualResults | null = null;
|
|
726
|
+
let priorError: Error | null = null;
|
|
727
|
+
try { prior = await readPriorManualResults(deliverableXlsxPath(cwd, paths.unit)); }
|
|
728
|
+
// A CSV-only export overwrites no workbook, so an unreadable one only means "nothing to carry".
|
|
729
|
+
catch (e) { if (format !== 'csv') priorError = e as Error; }
|
|
730
|
+
applyManualCarry(model, prior);
|
|
731
|
+
|
|
707
732
|
log(`${COLOR.bold}${paths.label}${COLOR.reset} — ${model.manifest.itemCount} delivery item(s), ${model.manifest.variantCount} coverage variant(s), review: ${model.manifest.approvalState} (design), test outcome: ${formatTestOutcome(resultTotals(model.items))}`);
|
|
708
733
|
{
|
|
709
734
|
const mocked = model.items.flatMap((it) => it.variants.filter((v) => v.mocked));
|
|
@@ -736,11 +761,21 @@ async function exportMatrixTarget(
|
|
|
736
761
|
return null;
|
|
737
762
|
}
|
|
738
763
|
|
|
739
|
-
|
|
764
|
+
let deliverables: Awaited<ReturnType<typeof writeMatrixDeliverables>>;
|
|
765
|
+
try {
|
|
766
|
+
if (priorError) throw priorError;
|
|
767
|
+
deliverables = await writeMatrixDeliverables(paths, model, format, prior);
|
|
768
|
+
} catch (e) {
|
|
769
|
+
// The existing workbook could not be read, so writing would destroy its Manual results.
|
|
770
|
+
log(` ${COLOR.red}not written:${COLOR.reset} ${(e as Error).message}`);
|
|
771
|
+
return null;
|
|
772
|
+
}
|
|
773
|
+
const { csvPath, xlsxPath, manualCarry } = deliverables;
|
|
740
774
|
const passed = model.items.reduce((a, i) => a + i.resultCounts.passed, 0);
|
|
741
775
|
const failed = model.items.reduce((a, i) => a + i.resultCounts.failed, 0);
|
|
742
776
|
const written = [xlsxPath, csvPath].filter((p): p is string => !!p).map((p) => path.relative(cwd, p));
|
|
743
777
|
for (const rel of written) log(` ${COLOR.green}→ ${rel}${COLOR.reset}`);
|
|
778
|
+
if (manualCarry) printManualCarry(manualCarry);
|
|
744
779
|
return {
|
|
745
780
|
label: paths.label,
|
|
746
781
|
items: model.manifest.itemCount,
|
|
@@ -751,6 +786,23 @@ async function exportMatrixTarget(
|
|
|
751
786
|
};
|
|
752
787
|
}
|
|
753
788
|
|
|
789
|
+
/** What happened to the tester's hand-entered Manual results on this write (#756). */
|
|
790
|
+
function printManualCarry(r: ManualCarryReport): void {
|
|
791
|
+
const kept = r.carried + r.unchecked;
|
|
792
|
+
if (kept + r.reset.length + r.notPlaced.length + r.duplicates.length === 0) return;
|
|
793
|
+
const list = (refs: string[]) => `${refs.slice(0, 4).join(', ')}${refs.length > 4 ? ', …' : ''}`;
|
|
794
|
+
log(` Manual results: ${kept} kept${r.unchecked > 0 ? ` (${r.unchecked} without a drift check — the file predates the fingerprint stamp)` : ''}`);
|
|
795
|
+
if (r.reset.length > 0) {
|
|
796
|
+
log(` ${COLOR.yellow}reset to Pending${COLOR.reset} — procedure changed since the result was recorded (${r.reset.length}): ${list(r.reset)}; the previous result is kept in Note`);
|
|
797
|
+
}
|
|
798
|
+
if (r.notPlaced.length > 0) {
|
|
799
|
+
log(` ${COLOR.yellow}not carried${COLOR.reset} — no matching manual case in the feature any more (${r.notPlaced.length}): ${list(r.notPlaced.map((n) => `${n.ref} was ${n.result}`))}`);
|
|
800
|
+
}
|
|
801
|
+
if (r.duplicates.length > 0) {
|
|
802
|
+
log(` ${COLOR.yellow}not carried${COLOR.reset} — a second row for the same case; only the first was kept (${r.duplicates.length}): ${list(r.duplicates.map((n) => `${n.ref} was ${n.result}`))}`);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
754
806
|
/**
|
|
755
807
|
* One place that accounts for every target the run did NOT write, so a sweep is
|
|
756
808
|
* never silently partial: scaffolds skipped by preflight and units whose gates /
|
|
@@ -934,12 +986,9 @@ export function registerDeliveryCommand(program: Command): void {
|
|
|
934
986
|
// Deliverables are COMMITTED files; delivery is the last stop before a
|
|
935
987
|
// hardcoded credential lands in one. Same scan `generate` runs.
|
|
936
988
|
const scanDirs = [...new Set(toExport.map((t) => qaDir(cwd, t)))];
|
|
937
|
-
const
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
for (const h of secretHits.slice(0, 10)) log(` ${h.file}:${h.line} — ${h.reason}`);
|
|
941
|
-
log(` Move real secrets to an env overlay / CI secret; keep test-data placeholders only.\n`);
|
|
942
|
-
}
|
|
989
|
+
const secretScan = scanDirs.map((d) => scanTestDataSecrets(d))
|
|
990
|
+
.reduce<SecretScanResult>((a, r) => ({ hits: [...a.hits, ...r.hits], unscanned: [...a.unscanned, ...r.unscanned] }), { hits: [], unscanned: [] });
|
|
991
|
+
for (const line of formatSecretScan(secretScan)) log(line);
|
|
943
992
|
|
|
944
993
|
// 3. Export
|
|
945
994
|
const legacy = options.legacy || options.full;
|
|
@@ -955,9 +1004,9 @@ export function registerDeliveryCommand(program: Command): void {
|
|
|
955
1004
|
// Same contract as the export path: an all-@exploration feature has no
|
|
956
1005
|
// deliverable variants, so there is no map to approve — skip, not an error.
|
|
957
1006
|
{
|
|
958
|
-
const
|
|
959
|
-
if (
|
|
960
|
-
&&
|
|
1007
|
+
const scenarios = parseFeatureMetadata(paths.featureFile).scenarios.filter((s) => !isHookScenario(s));
|
|
1008
|
+
if (scenarios.length > 0
|
|
1009
|
+
&& scenarios.every((s) => s.tags.some((t) => /^@exploration\b/i.test(t)))) {
|
|
961
1010
|
log(`${COLOR.gray}${paths.label}: nothing to approve — all scenario(s) are @exploration probes.${COLOR.reset}`);
|
|
962
1011
|
continue;
|
|
963
1012
|
}
|
|
@@ -982,10 +1031,11 @@ export function registerDeliveryCommand(program: Command): void {
|
|
|
982
1031
|
|
|
983
1032
|
const matrixSummaries: MatrixRunSummary[] = [];
|
|
984
1033
|
const notExported: string[] = [];
|
|
1034
|
+
const env = getEnvironment(cwd);
|
|
985
1035
|
for (const target of toExport) {
|
|
986
1036
|
const s = await exportMatrixTarget(cwd, target, {
|
|
987
1037
|
check: options.check, preview: options.preview, format: format as MatrixFormat,
|
|
988
|
-
});
|
|
1038
|
+
}, env);
|
|
989
1039
|
if (s === 'skipped') continue; // all-@exploration feature — neither exported nor a failure
|
|
990
1040
|
if (s) matrixSummaries.push(s);
|
|
991
1041
|
else notExported.push(target.isFlow ? `flow/${target.featureBaseName}` : target.featureBaseName);
|
|
@@ -3,7 +3,7 @@ import * as path from 'path';
|
|
|
3
3
|
import * as fs from 'fs';
|
|
4
4
|
import { CodeGenerator } from '../../generators/test-generator/code-generator';
|
|
5
5
|
import { adapterRegistry } from '../../generators/test-generator/adapters';
|
|
6
|
-
import { scanTestDataSecrets } from '../../harness/secret-scan';
|
|
6
|
+
import { scanTestDataSecrets, formatSecretScan, type SecretScanResult } from '../../harness/secret-scan';
|
|
7
7
|
import { capabilityRegistry } from '../../capabilities/registry';
|
|
8
8
|
import { discoverAndRegisterCapabilities } from '../../capabilities/discover';
|
|
9
9
|
import { readCapabilities, writeCapabilities, driverMeta, loadDriverCatalog } from '../../harness/capability';
|
|
@@ -160,6 +160,17 @@ export function registerGenerateCommand(program: Command): void {
|
|
|
160
160
|
const cap = readCapabilities(cwd);
|
|
161
161
|
if (cap.platform) {
|
|
162
162
|
platform = cap.platform;
|
|
163
|
+
} else if (cap.source === 'unreadable') {
|
|
164
|
+
// The file EXISTS and we failed to parse it. Scaffolding the web default here would
|
|
165
|
+
// overwrite whatever the user declared — a mobile project's platform, drivers and
|
|
166
|
+
// verification scope, gone, while the notice claimed the file was missing. Refuse:
|
|
167
|
+
// the one thing we know is that we do not know what this project is.
|
|
168
|
+
throw new Error(
|
|
169
|
+
`qa/capabilities.yaml exists but could not be parsed — fix the YAML and re-run.\n` +
|
|
170
|
+
` Refusing to scaffold a default over it: that would discard the platform, drivers ` +
|
|
171
|
+
`and verification scope it declares.\n` +
|
|
172
|
+
` To compile without reading it, pass --framework <web|mobile|…>.`,
|
|
173
|
+
);
|
|
163
174
|
} else {
|
|
164
175
|
platform = 'web';
|
|
165
176
|
writeCapabilities(cwd, { platform: 'web', enabled: ['web'] });
|
|
@@ -225,12 +236,9 @@ export function registerGenerateCommand(program: Command): void {
|
|
|
225
236
|
if (fs.existsSync(d)) for (const n of fs.readdirSync(d)) scanDirs.push(path.join(d, n));
|
|
226
237
|
}
|
|
227
238
|
}
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
for (const h of secretHits.slice(0, 10)) console.log(` ${h.file}:${h.line} — ${h.reason}`);
|
|
232
|
-
console.log(` Move real secrets to an env overlay / CI secret; keep test-data placeholders only.`);
|
|
233
|
-
}
|
|
239
|
+
const secretScan = scanDirs.map((d) => scanTestDataSecrets(d))
|
|
240
|
+
.reduce<SecretScanResult>((a, r) => ({ hits: [...a.hits, ...r.hits], unscanned: [...a.unscanned, ...r.unscanned] }), { hits: [], unscanned: [] });
|
|
241
|
+
for (const line of formatSecretScan(secretScan)) console.log(line);
|
|
234
242
|
|
|
235
243
|
// Advisory harness sensors (e.g. @cases/@query lint) — run via the Capability SPI,
|
|
236
244
|
// never block generation. Each capability registers its own; the kernel just runs them.
|
|
@@ -13,6 +13,7 @@ import { detectReportRoot, sanitizeReportPaths } from '../../exporters/report-pa
|
|
|
13
13
|
import { registerResultsSanitizeCommand } from './results-sanitize';
|
|
14
14
|
import { resolveResultsPath } from '../../exporters/playwright-report-parser';
|
|
15
15
|
import { parseSpecFile } from '../../exporters/spec-parser';
|
|
16
|
+
import { writeFileAtomic } from '../../harness/write-atomic';
|
|
16
17
|
|
|
17
18
|
interface MergeCliOptions {
|
|
18
19
|
out?: string;
|
|
@@ -38,7 +39,7 @@ function readReport(file: string, label: string): any {
|
|
|
38
39
|
* (flows → screens → api → api flows); the same basename found in more than one
|
|
39
40
|
* place is an ambiguity error, never a silent pick.
|
|
40
41
|
*/
|
|
41
|
-
function resolveFeatureDir(cwd: string, name: string): string {
|
|
42
|
+
export function resolveFeatureDir(cwd: string, name: string): string {
|
|
42
43
|
const roots = [
|
|
43
44
|
path.join(cwd, 'specs', 'generated', 'flows'),
|
|
44
45
|
path.join(cwd, 'specs', 'generated'),
|
|
@@ -88,6 +89,12 @@ export function runResultsMerge(baseArg: string | undefined, newArg: string | un
|
|
|
88
89
|
if (!baseFile || path.dirname(baseFile) !== dir) {
|
|
89
90
|
throw new Error(`results merge: no per-target result file for "${opts.screen}" in ${path.relative(cwd, dir)} — nothing to merge into (a first full run writes it directly).`);
|
|
90
91
|
}
|
|
92
|
+
// Under SUNGEN_ENV the candidate order falls back to the base-locale file when the env file
|
|
93
|
+
// does not exist yet; merging a `vi` subset there would overwrite `ja`/base results in silence.
|
|
94
|
+
const env = process.env.SUNGEN_ENV;
|
|
95
|
+
if (env && !path.basename(baseFile).endsWith(`.${env}.json`)) {
|
|
96
|
+
throw new Error(`results merge: SUNGEN_ENV=${env} but "${opts.screen}" has no ${env} result file yet (found ${path.basename(baseFile)}) — run the full ${env} suite first; it writes the file directly.`);
|
|
97
|
+
}
|
|
91
98
|
// Ghost/foreign-title checks need the CURRENT spec titles.
|
|
92
99
|
const specFile = path.join(dir, `${opts.screen}.spec.ts`);
|
|
93
100
|
try { specTitles = parseSpecFile(specFile).tests.map((t) => t.testTitle); } catch { specTitles = null; }
|
|
@@ -125,14 +132,7 @@ export function runResultsMerge(baseArg: string | undefined, newArg: string | un
|
|
|
125
132
|
// that file is the single source of execution truth for delivery/dashboard.
|
|
126
133
|
// (Same-dir rename = same device; concurrent merges are last-rename-wins.)
|
|
127
134
|
const outFile = opts.out ?? baseFile;
|
|
128
|
-
|
|
129
|
-
try {
|
|
130
|
-
fs.writeFileSync(tmp, JSON.stringify(merged, null, 2));
|
|
131
|
-
fs.renameSync(tmp, outFile);
|
|
132
|
-
} catch (err) {
|
|
133
|
-
try { fs.rmSync(tmp, { force: true }); } catch { /* best-effort cleanup */ }
|
|
134
|
-
throw err;
|
|
135
|
-
}
|
|
135
|
+
writeFileAtomic(outFile, JSON.stringify(merged, null, 2));
|
|
136
136
|
|
|
137
137
|
console.log(`✓ merged ${path.basename(newFile)} into ${path.relative(cwd, outFile)} — ${replaced.length} updated, ${added.length} added, ${kept.length} kept`);
|
|
138
138
|
return outFile;
|
|
@@ -16,6 +16,7 @@ import * as fs from 'fs';
|
|
|
16
16
|
import * as path from 'path';
|
|
17
17
|
import fg from 'fast-glob';
|
|
18
18
|
import { detectReportRoot, sanitizeReportPaths } from '../../exporters/report-path-sanitizer';
|
|
19
|
+
import { writeFileAtomic } from '../../harness/write-atomic';
|
|
19
20
|
|
|
20
21
|
/** Where a project keeps the JSON files that carry paths. */
|
|
21
22
|
export const DEFAULT_REPORT_GLOBS = [
|
|
@@ -50,15 +51,7 @@ export function runResultsSanitize(files: string[] | undefined, cwd: string = pr
|
|
|
50
51
|
const root = detectReportRoot(doc) ?? nearestProjectRoot(path.dirname(file)) ?? cwd;
|
|
51
52
|
const { doc: clean, rewritten } = sanitizeReportPaths(doc, { root });
|
|
52
53
|
if (rewritten > 0) {
|
|
53
|
-
|
|
54
|
-
const tmp = `${file}.tmp-${process.pid}`;
|
|
55
|
-
try {
|
|
56
|
-
fs.writeFileSync(tmp, JSON.stringify(clean, null, 2) + (raw.endsWith('\n') ? '\n' : ''));
|
|
57
|
-
fs.renameSync(tmp, file);
|
|
58
|
-
} catch (err) {
|
|
59
|
-
try { fs.rmSync(tmp, { force: true }); } catch { /* best-effort cleanup */ }
|
|
60
|
-
throw err;
|
|
61
|
-
}
|
|
54
|
+
writeFileAtomic(file, JSON.stringify(clean, null, 2) + (raw.endsWith('\n') ? '\n' : ''));
|
|
62
55
|
}
|
|
63
56
|
out.push({ file: path.relative(cwd, file), rewritten });
|
|
64
57
|
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `sungen select` — which tests does a list of test-case IDs name? (#835)
|
|
3
|
+
*
|
|
4
|
+
* Backs `run-test --ids`. Like `sungen changed` for `--change-only`, the selection is computed here
|
|
5
|
+
* so it is deterministic and testable; the workflow only runs it and merges the subset result into
|
|
6
|
+
* the existing per-feature result file.
|
|
7
|
+
*
|
|
8
|
+
* Fails loud: an ID that selects nothing runnable exits non-zero, because a 0-test run reports green.
|
|
9
|
+
*/
|
|
10
|
+
import { Command } from 'commander';
|
|
11
|
+
import * as path from 'path';
|
|
12
|
+
import { findUnitDir } from '../resolve-unit';
|
|
13
|
+
import { loadUnitScenarios } from '../../harness/parse';
|
|
14
|
+
import { parseIdList, selectByIds } from '../../harness/select-by-ids';
|
|
15
|
+
import { resolveFeatureDir } from './results-merge';
|
|
16
|
+
import { readCapabilities } from '../../harness/capability';
|
|
17
|
+
import { projectRootFromScreenDir } from '../../harness/intent';
|
|
18
|
+
|
|
19
|
+
export function registerSelectCommand(program: Command): void {
|
|
20
|
+
program
|
|
21
|
+
.command('select')
|
|
22
|
+
.description('List the tests a set of test-case IDs selects, grouped per feature (backs run-test --ids)')
|
|
23
|
+
.option('-s, --screen <name>', 'Screen or flow name')
|
|
24
|
+
.option('--api <name>', 'API-first area or api flow (qa/api/<name>)')
|
|
25
|
+
.option('--area <name>', 'Alias of --api')
|
|
26
|
+
.requiredOption('--ids <list>', 'Test-case IDs, comma- or space-separated (e.g. VP-UI-001,VP-UI-002)')
|
|
27
|
+
.option('--json', 'Output the selection as JSON (per feature: spec, ids, titles, grep)')
|
|
28
|
+
.option('--grep', 'Output ONLY the regex for -g / --mochaOpts.grep (selection must lie in one feature)')
|
|
29
|
+
.action((options) => {
|
|
30
|
+
try {
|
|
31
|
+
const name = options.screen || options.api || options.area;
|
|
32
|
+
if (!name) throw new Error('Provide --screen <name> (or --api <area>)');
|
|
33
|
+
const dir = findUnitDir(name);
|
|
34
|
+
if (!dir) throw new Error(`Not found: qa/screens|flows|api/${name}`);
|
|
35
|
+
const ids = parseIdList(options.ids);
|
|
36
|
+
if (ids.length === 0) throw new Error('--ids is empty — pass at least one test-case ID');
|
|
37
|
+
|
|
38
|
+
const cwd = process.cwd();
|
|
39
|
+
// Same capability set `generate` compiles against: a @requires:<cap> scenario is a skip without it.
|
|
40
|
+
const enabledCaps = new Set(readCapabilities(projectRootFromScreenDir(path.resolve(dir))).enabled.map((c) => c.toLowerCase()));
|
|
41
|
+
const selection = selectByIds(loadUnitScenarios(dir, name), ids, name, enabledCaps);
|
|
42
|
+
// Resolve each feature's compiled spec: a test cannot run before `sungen generate`.
|
|
43
|
+
const features = selection.features.map((g) => {
|
|
44
|
+
let spec: string | null = null;
|
|
45
|
+
try { spec = path.relative(cwd, path.join(resolveFeatureDir(cwd, g.feature), `${g.feature}.spec.ts`)); } catch { /* reported below */ }
|
|
46
|
+
return { ...g, spec };
|
|
47
|
+
});
|
|
48
|
+
const notCompiled = features.filter((f) => !f.spec).map((f) => f.feature);
|
|
49
|
+
const failed = selection.unknown.length > 0 || selection.notRunnable.length > 0 || notCompiled.length > 0 || features.length === 0;
|
|
50
|
+
|
|
51
|
+
if (options.json) {
|
|
52
|
+
console.log(JSON.stringify({ unit: name, features, unknown: selection.unknown, notRunnable: selection.notRunnable, notCompiled }, null, 2));
|
|
53
|
+
if (failed) process.exitCode = 1;
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (options.grep) {
|
|
57
|
+
// Only a clean single-feature selection prints a regex; anything else must not be run
|
|
58
|
+
// as if it were complete, so it prints nothing and exits non-zero.
|
|
59
|
+
if (failed || features.length !== 1) {
|
|
60
|
+
console.error(features.length > 1
|
|
61
|
+
? `select: the IDs span ${features.length} features (${features.map((f) => f.feature).join(', ')}) — use --json and run each feature on its own.`
|
|
62
|
+
: 'select: nothing runnable for these IDs — run without --grep to see why.');
|
|
63
|
+
process.exitCode = 1;
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
console.log(features[0].grep);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const selected = features.reduce((n, f) => n + f.titles.length, 0);
|
|
71
|
+
// A @cases scenario expands to one test per dataset row, so this counts scenarios.
|
|
72
|
+
console.log(`\n ${selected} scenario(s) selected for ${ids.length} ID(s) in ${name}:\n`);
|
|
73
|
+
for (const f of features) {
|
|
74
|
+
console.log(` ${f.feature} → ${f.spec ?? '(not compiled — run sungen generate first)'}`);
|
|
75
|
+
for (const t of f.titles) console.log(` ▶ ${t}`);
|
|
76
|
+
}
|
|
77
|
+
for (const u of selection.unknown) {
|
|
78
|
+
console.log(` ✗ ${u.id} — no scenario has this ID${u.didYouMean.length ? ` (did you mean ${u.didYouMean.join(', ')}?)` : ''}`);
|
|
79
|
+
}
|
|
80
|
+
for (const n of selection.notRunnable) console.log(` ○ ${n.id} — ${n.reason}`);
|
|
81
|
+
console.log('');
|
|
82
|
+
if (failed) process.exitCode = 1;
|
|
83
|
+
} catch (err) {
|
|
84
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
85
|
+
process.exitCode = 1;
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { resolveUnit } from '../resolve-unit';
|
|
4
|
+
import { triageUnit, writeTriageReport } from '../../harness/triage-unit';
|
|
5
|
+
import type { TriageClass } from '../../harness/triage';
|
|
6
|
+
|
|
7
|
+
/** Display order: what needs a decision first, green last. */
|
|
8
|
+
const ORDER: TriageClass[] = ['product-candidate', 'unclassified', 'test-defect', 'environment', 'blocked-capability', 'known-defect', 'flaky'];
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* `sungen triage` — classify every red or flaky scenario of the latest run (see harness/triage.ts).
|
|
12
|
+
* Reports, never gates: always exits 0 once the unit is found.
|
|
13
|
+
*/
|
|
14
|
+
export function registerTriageCommand(program: Command): void {
|
|
15
|
+
program
|
|
16
|
+
.command('triage')
|
|
17
|
+
.description('Classify each red or flaky scenario of the latest run: known-defect, blocked, environment, test-defect, product-candidate, unclassified')
|
|
18
|
+
.option('-s, --screen <name>', 'Screen or flow name')
|
|
19
|
+
.option('--api <name>', 'API-first area or api flow (qa/api/<name>)')
|
|
20
|
+
.option('--area <name>', 'Alias of --api')
|
|
21
|
+
.option('--json', 'Output the raw JSON report')
|
|
22
|
+
.action((o: { screen?: string; api?: string; area?: string; json?: boolean }) => {
|
|
23
|
+
try {
|
|
24
|
+
const name = o.screen || o.api || o.area;
|
|
25
|
+
if (!name) throw new Error('Provide --screen <name> (or --api <area>)');
|
|
26
|
+
const cwd = process.cwd();
|
|
27
|
+
const unit = resolveUnit(name, cwd);
|
|
28
|
+
if (!unit) throw new Error(`Not found: qa/screens/${name}, qa/flows/${name}, or qa/api/${name}`);
|
|
29
|
+
|
|
30
|
+
const report = triageUnit(cwd, unit);
|
|
31
|
+
if (report.resultFiles.length === 0) {
|
|
32
|
+
if (o.json) console.log(JSON.stringify(report, null, 2));
|
|
33
|
+
else console.log(`\n ${name}: no results — run the tests first.\n`);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const saved = writeTriageReport(cwd, report);
|
|
37
|
+
if (o.json) { console.log(JSON.stringify(report, null, 2)); return; }
|
|
38
|
+
|
|
39
|
+
const L = console.log;
|
|
40
|
+
L(`\n━━━ Triage: ${name} ━━━`);
|
|
41
|
+
if (report.rows.length === 0) L(' ✓ every scenario passed on its first attempt');
|
|
42
|
+
for (const cls of ORDER) {
|
|
43
|
+
const rows = report.rows.filter((r) => r.class === cls);
|
|
44
|
+
if (rows.length === 0) continue;
|
|
45
|
+
L(`\n ${cls} (${rows.length})`);
|
|
46
|
+
for (const r of rows) {
|
|
47
|
+
L(` • ${r.scenario}${r.variant ? ` [${r.variant}]` : ''}${r.defectId ? ` → ${r.defectId}` : ''}`);
|
|
48
|
+
L(` ${r.ruleId}: ${r.evidence}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
L(`\n Report: ${path.relative(cwd, saved)}\n`);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error('Error:', error instanceof Error ? error.message : error);
|
|
54
|
+
process.exitCode = 1;
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
@@ -103,6 +103,9 @@ export function registerUpdateCommand(program: Command): void {
|
|
|
103
103
|
// playwright.config.ts is written once at init and never re-synced — patch the reporter in.
|
|
104
104
|
const { installReportSanitizeReporter } = require('../../orchestrator/report-sanitize-reporter-install');
|
|
105
105
|
installReportSanitizeReporter(process.cwd(), options.dryRun ?? false);
|
|
106
|
+
// …and move the old `trace: 'on-first-retry'` default, which never records a trace off CI.
|
|
107
|
+
const { installTraceMode } = require('../../orchestrator/trace-mode-install');
|
|
108
|
+
installTraceMode(process.cwd(), options.dryRun ?? false);
|
|
106
109
|
} catch (error) {
|
|
107
110
|
console.error('❌ Update failed:', error);
|
|
108
111
|
process.exit(1);
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { resolveUnit } from '../resolve-unit';
|
|
3
|
+
import { scanViewpointUnit, ViewpointScan } from '../../harness/viewpoint-scan';
|
|
4
|
+
|
|
5
|
+
const SHOWN = 10;
|
|
6
|
+
|
|
7
|
+
/** `a, b, c … +N more` — a list long enough to scroll past is a count plus a sample. */
|
|
8
|
+
const sample = (xs: string[]): string => (xs.length <= SHOWN ? xs.join(', ') : `${xs.slice(0, SHOWN).join(', ')} … +${xs.length - SHOWN} more`);
|
|
9
|
+
|
|
10
|
+
function printScan(name: string, s: ViewpointScan): void {
|
|
11
|
+
const L = console.log;
|
|
12
|
+
L(`\n━━━ Viewpoint scan: ${name} ━━━`);
|
|
13
|
+
L(` mode ${s.mode} (${s.modeReason}) — ${s.mode === 2 ? 'the AI drafts / revises its own draft' : 'review only, the file is never edited'}`);
|
|
14
|
+
L(` ${s.vpRows.length} rows (${s.distinctIds} distinct ids) without Kỳ vọng: ${s.rowsWithoutExpected.length} manual: ${s.manualRows.length}`);
|
|
15
|
+
if (s.note) L(` ⚠ ${s.note}`);
|
|
16
|
+
if (s.rowsWithoutExpected.length) L(` no Kỳ vọng: ${sample(s.rowsWithoutExpected)}`);
|
|
17
|
+
if (s.manualRows.length) L(` manual: ${sample(s.manualRows)}`);
|
|
18
|
+
if (s.itemSource === 'none') L(' item source: none (the viewpoint cites no spec id, spec item or UI-NNN)');
|
|
19
|
+
else L(` item source: ${s.itemSource} uncited items: ${s.uncitedItems.length}`);
|
|
20
|
+
if (s.uncitedItems.length) L(` ${sample(s.uncitedItems.map((i) => (i.name ? `${i.ref} (${i.name})` : i.ref)))}`);
|
|
21
|
+
if (s.withoutScenario === null) L(' scenarios: no feature file yet');
|
|
22
|
+
else L(` VP ids with no scenario: ${s.withoutScenario.length}${s.withoutScenario.length ? `\n ${sample(s.withoutScenario)}` : ''}`);
|
|
23
|
+
L('');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* `sungen viewpoint scan` — ids and item refs only. Read-only: no audit finding, no score, no
|
|
28
|
+
* baseline effect; the file is never edited. What the rows MEAN is the review skill's job.
|
|
29
|
+
*/
|
|
30
|
+
export function registerViewpointCommand(program: Command): void {
|
|
31
|
+
const viewpoint = program
|
|
32
|
+
.command('viewpoint')
|
|
33
|
+
.description('Read a unit\'s requirements/test-viewpoint.md');
|
|
34
|
+
|
|
35
|
+
viewpoint
|
|
36
|
+
.command('scan')
|
|
37
|
+
.description('List VP rows (full-id first cell), the item numbers they cite, uncited spec items and VP ids no scenario carries — read-only')
|
|
38
|
+
.option('-s, --screen <name>', 'Screen (or flow) name')
|
|
39
|
+
.option('--flow <name>', 'Flow name (qa/flows/<name>)')
|
|
40
|
+
.option('--json', 'Output the scan as JSON')
|
|
41
|
+
.action((o: { screen?: string; flow?: string; json?: boolean }) => {
|
|
42
|
+
try {
|
|
43
|
+
if (o.screen && o.flow) throw new Error('Provide --screen <name> or --flow <name>, not both');
|
|
44
|
+
const name = o.screen || o.flow;
|
|
45
|
+
if (!name) throw new Error('Provide --screen <name> or --flow <name>');
|
|
46
|
+
const unit = resolveUnit(name);
|
|
47
|
+
if (!unit) throw new Error(`Not found: qa/screens/${name} or qa/flows/${name}`);
|
|
48
|
+
if (o.flow && unit.kind !== 'flow') throw new Error(`Not a flow: ${name} resolves to qa/${unit.unitId} (use --screen)`);
|
|
49
|
+
|
|
50
|
+
const scan = scanViewpointUnit(unit.dir, name);
|
|
51
|
+
if (o.json) console.log(JSON.stringify(scan, null, 2));
|
|
52
|
+
else printScan(name, scan);
|
|
53
|
+
} catch (e) {
|
|
54
|
+
console.error('Error:', e instanceof Error ? e.message : e);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|