@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
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `sungen triage` — classify each red or flaky scenario of a run into exactly one class, with the
|
|
3
|
+
* rule that fired and one evidence line. Deterministic and read-only; it reports, it never gates.
|
|
4
|
+
*
|
|
5
|
+
* Order (first match wins):
|
|
6
|
+
* 1. known-defect `@known-defect` / `@known-defect:<id>` — an expected red.
|
|
7
|
+
* 2. blocked-capability `@manual:*` / `@requires:*`, or skipped by design.
|
|
8
|
+
* 3. environment the session or network never gave the test a usable app.
|
|
9
|
+
* 4. test-defect the test could not reach its element or data.
|
|
10
|
+
* 5. product-candidate an assertion whose Expected and Received both exist and differ.
|
|
11
|
+
* 6. unclassified anything else — left for the agent or QA, with the raw first message.
|
|
12
|
+
* A `passed` scenario that needed a retry is `flaky` (green, listed separately).
|
|
13
|
+
*
|
|
14
|
+
* 5 is tested before 3 and 4 in code: a parsed Expected/Received pair that differs means the app
|
|
15
|
+
* answered, which outranks any wrapper text (a test timeout around the assertion included).
|
|
16
|
+
*
|
|
17
|
+
* A bare test timeout (no locator, no assertion) is `unclassified` too: a slow environment, a page
|
|
18
|
+
* that hangs on a product bug and a test waiting on the wrong thing all print the same line.
|
|
19
|
+
*
|
|
20
|
+
* An assertion on an element that never appeared (`Received: <element(s) not found>`) is NOT a test
|
|
21
|
+
* defect: a wrong selector and a missing message look identical, so it is left for judgment rather
|
|
22
|
+
* than hiding a possible product bug behind "fix the selector".
|
|
23
|
+
*/
|
|
24
|
+
import { redactSecrets } from '../exporters/api-testcase-formatter';
|
|
25
|
+
import { parseExpectedReceived } from '../exporters/playwright-report-parser';
|
|
26
|
+
import type { PlaywrightResult } from '../exporters/types';
|
|
27
|
+
import { BARE_TIMEOUT, TRIAGE_SIGNALS } from './triage-signals';
|
|
28
|
+
|
|
29
|
+
export type TriageClass =
|
|
30
|
+
| 'known-defect' | 'blocked-capability' | 'environment' | 'test-defect' | 'product-candidate' | 'unclassified' | 'flaky';
|
|
31
|
+
|
|
32
|
+
export interface TriageInput {
|
|
33
|
+
unit: string;
|
|
34
|
+
title: string;
|
|
35
|
+
tags: string[];
|
|
36
|
+
result?: PlaywrightResult;
|
|
37
|
+
/** Result-file variant (`android`, `ios`, an env name) when the unit has several. */
|
|
38
|
+
variant?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface TriageRow {
|
|
42
|
+
unit: string;
|
|
43
|
+
scenario: string;
|
|
44
|
+
variant?: string;
|
|
45
|
+
class: TriageClass;
|
|
46
|
+
ruleId: string;
|
|
47
|
+
evidence: string;
|
|
48
|
+
ids: string[];
|
|
49
|
+
legacy: string[];
|
|
50
|
+
defectId?: string;
|
|
51
|
+
tracePath?: string;
|
|
52
|
+
/** product-candidate only: the two halves of the assertion, for a defect draft. */
|
|
53
|
+
expected?: string;
|
|
54
|
+
received?: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const EVIDENCE_MAX = 200;
|
|
58
|
+
const VIEWPOINT_ID = /\b(?:VP|FL)-[A-Z0-9]+(?:-[A-Z0-9]+)*\b/gi;
|
|
59
|
+
const ELEMENT_ABSENT = /Received:\s*<element\(s\) not found>/;
|
|
60
|
+
|
|
61
|
+
/** The part of a message the run itself wrote: before `Call log:`, without `Locator:` echo lines. */
|
|
62
|
+
const runHead = (m: string): string => m.split(/\n\s*Call log:/)[0].replace(/^\s*Locator:.*$/gm, '');
|
|
63
|
+
|
|
64
|
+
/** Every failure message, stack frames and a leading `Error:` dropped. */
|
|
65
|
+
function messagesOf(result: PlaywrightResult): string[] {
|
|
66
|
+
const raw = result.errorMessages?.length ? result.errorMessages : [result.error ?? ''];
|
|
67
|
+
return raw.map((m) => m.split(/\n\s*at\s/)[0].replace(/^Error:\s*/i, '').trim()).filter(Boolean);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function evidenceLine(text: string, secrets: string[]): string {
|
|
71
|
+
const flat = redactSecrets(text, secrets).replace(/\s+/g, ' ').trim();
|
|
72
|
+
return flat.length > EVIDENCE_MAX ? `${flat.slice(0, EVIDENCE_MAX - 1)}…` : flat;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Classify one scenario. Null when it needs no triage (clean pass, or never ran). */
|
|
76
|
+
export function classifyScenario(input: TriageInput, secrets: string[] = []): TriageRow | null {
|
|
77
|
+
const { result, tags } = input;
|
|
78
|
+
if (!result) return null;
|
|
79
|
+
const base = {
|
|
80
|
+
unit: input.unit,
|
|
81
|
+
scenario: input.title,
|
|
82
|
+
...(input.variant ? { variant: input.variant } : {}),
|
|
83
|
+
ids: [...new Set([input.title, ...tags].join(' ').match(VIEWPOINT_ID) ?? [])],
|
|
84
|
+
legacy: tags.filter((t) => t.startsWith('@legacy:')).map((t) => t.slice('@legacy:'.length)),
|
|
85
|
+
...(result.tracePath ? { tracePath: result.tracePath } : {}),
|
|
86
|
+
};
|
|
87
|
+
const row = (cls: TriageClass, ruleId: string, evidence: string): TriageRow =>
|
|
88
|
+
({ ...base, class: cls, ruleId, evidence: evidenceLine(evidence, secrets) });
|
|
89
|
+
|
|
90
|
+
if (result.status === 'passed') {
|
|
91
|
+
return (result.attempts ?? 1) > 1 ? row('flaky', 'passed-after-retry', `passed on attempt ${result.attempts}`) : null;
|
|
92
|
+
}
|
|
93
|
+
const messages = messagesOf(result);
|
|
94
|
+
const first = messages[0] ?? result.status;
|
|
95
|
+
|
|
96
|
+
const known = tags.find((t) => t === '@known-defect' || t.startsWith('@known-defect:'));
|
|
97
|
+
if (known) {
|
|
98
|
+
const r = row('known-defect', 'tag-known-defect', first);
|
|
99
|
+
const id = known.split(':')[1];
|
|
100
|
+
return id ? { ...r, defectId: id } : r;
|
|
101
|
+
}
|
|
102
|
+
if (tags.some((t) => t.startsWith('@manual'))) return row('blocked-capability', 'tag-manual', `${tags.find((t) => t.startsWith('@manual'))} — ${first}`);
|
|
103
|
+
if (tags.some((t) => t.startsWith('@requires:'))) return row('blocked-capability', 'tag-requires', `${tags.find((t) => t.startsWith('@requires:'))} — ${first}`);
|
|
104
|
+
if (result.status === 'skipped') return row('blocked-capability', 'status-skipped', 'skipped');
|
|
105
|
+
|
|
106
|
+
// An assertion with both halves decides the case even when a timeout wraps it.
|
|
107
|
+
for (const m of messages) {
|
|
108
|
+
const { expected, received } = parseExpectedReceived(m);
|
|
109
|
+
if (expected && received && !ELEMENT_ABSENT.test(m) && expected !== received) {
|
|
110
|
+
return { ...row('product-candidate', 'assertion-mismatch', `Expected ${expected} · Received ${received}`),
|
|
111
|
+
expected: evidenceLine(expected, secrets), received: evidenceLine(received, secrets) };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
for (const s of TRIAGE_SIGNALS) {
|
|
115
|
+
// Environment signals read only what the RUN said, never the test's own selector text that a
|
|
116
|
+
// message echoes in `Locator:` / `Call log:` — `getByText('Invalid URL')` is a missing message,
|
|
117
|
+
// not a bad base URL. Test-defect signals need the call log (`waiting for …`), so they read it all.
|
|
118
|
+
const hit = messages.find((m) => s.match.test(s.class === 'environment' ? runHead(m) : m));
|
|
119
|
+
if (hit) return row(s.class, s.id, hit);
|
|
120
|
+
}
|
|
121
|
+
const absent = messages.find((m) => ELEMENT_ABSENT.test(m));
|
|
122
|
+
if (absent) return row('unclassified', 'assertion-element-absent', absent);
|
|
123
|
+
if (messages.length > 0 && messages.every((m) => BARE_TIMEOUT.test(m))) {
|
|
124
|
+
return row('unclassified', 'bare-timeout', `${first} No locator or assertion detail: a slow environment, a hanging page, or a wait in the test. Re-run once, then read the trace's last action.`);
|
|
125
|
+
}
|
|
126
|
+
return row('unclassified', 'no-signal', first);
|
|
127
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #811 — the design inventory: is every item the DESIGN shows accounted for by the suite?
|
|
3
|
+
*
|
|
4
|
+
* Design sources in `requirements/ui/` (images, `figma-meta.md`) and `requirements/spec_figma.md`
|
|
5
|
+
* were read "for visual context" and never turned into a checkable list, so a column that exists
|
|
6
|
+
* only on the design — or contradicts the spec — was missed by the author AND the reviewer, and a
|
|
7
|
+
* deliberate deferral looked exactly like an oversight.
|
|
8
|
+
*
|
|
9
|
+
* create-test now writes `requirements/ui-inventory.md`: one `UI-NNN` row per component / column /
|
|
10
|
+
* button / state / annotation, with its source (`spec+design` | `design-only` | `conflict`). An
|
|
11
|
+
* item is accounted for by a scenario tag (`@ui:UI-014`) or a reasoned deferral comment
|
|
12
|
+
* (`# UI-DEFERRED: UI-002..013 — <reason>`) — no other mention counts; ranges and `/`
|
|
13
|
+
* enumerations read the same way as spec ids (spec-coverage `citedIds`). `spec+design` items are already measured
|
|
14
|
+
* by spec-coverage, so only `design-only` / `conflict` items are reported uncovered.
|
|
15
|
+
*
|
|
16
|
+
* Advisory: findings never move the score or the gate.
|
|
17
|
+
*/
|
|
18
|
+
import * as fs from 'fs';
|
|
19
|
+
import * as path from 'path';
|
|
20
|
+
import { citedIds } from './spec-coverage';
|
|
21
|
+
import { readTextFile } from './read-text';
|
|
22
|
+
|
|
23
|
+
export type UiItemSource = 'spec+design' | 'design-only' | 'conflict';
|
|
24
|
+
|
|
25
|
+
export interface UiItem {
|
|
26
|
+
id: string;
|
|
27
|
+
item: string;
|
|
28
|
+
kind: string;
|
|
29
|
+
source: UiItemSource;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface UiInventoryResult {
|
|
33
|
+
/** Design sources the unit holds: image files, figma-meta.md, spec_figma.md. */
|
|
34
|
+
designSources: string[];
|
|
35
|
+
/** `requirements/ui-inventory.md` exists. */
|
|
36
|
+
hasInventory: boolean;
|
|
37
|
+
items: UiItem[];
|
|
38
|
+
/** `images read: a/b` from the inventory header — null when the line is absent. */
|
|
39
|
+
imagesRead: { read: number; total: number } | null;
|
|
40
|
+
/** Images on disk, for comparing against `imagesRead.total`. */
|
|
41
|
+
imagesOnDisk: number;
|
|
42
|
+
covered: string[];
|
|
43
|
+
deferred: string[];
|
|
44
|
+
/** design-only / conflict items neither covered nor deferred. */
|
|
45
|
+
uncovered: UiItem[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const IMAGE = /\.(png|jpe?g|webp|gif)$/i;
|
|
49
|
+
|
|
50
|
+
/** Every design source in a unit's `requirements/` — what makes an inventory necessary. */
|
|
51
|
+
export function designSourcesOf(reqDir: string): string[] {
|
|
52
|
+
const out: string[] = [];
|
|
53
|
+
const uiDir = path.join(reqDir, 'ui');
|
|
54
|
+
try {
|
|
55
|
+
for (const f of fs.readdirSync(uiDir).sort()) {
|
|
56
|
+
if (IMAGE.test(f) || f === 'figma-meta.md') out.push(`ui/${f}`);
|
|
57
|
+
}
|
|
58
|
+
} catch { /* no ui/ dir */ }
|
|
59
|
+
if (fs.existsSync(path.join(reqDir, 'spec_figma.md'))) out.push('spec_figma.md');
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The Source cell, read leniently ("design only", "Spec + Design", "conflict — …"). Anything else
|
|
65
|
+
* is NOT assumed to be spec-backed: an unreadable source is reported like a design-only item, so a
|
|
66
|
+
* typo can never make an item disappear from the check.
|
|
67
|
+
*/
|
|
68
|
+
function sourceOf(cell: string): UiItemSource | null {
|
|
69
|
+
const c = cell.trim().toLowerCase();
|
|
70
|
+
if (/^design[\s_-]*only\b/.test(c)) return 'design-only';
|
|
71
|
+
if (/^conflict\b/.test(c)) return 'conflict';
|
|
72
|
+
if (/^spec\s*(?:\+|&|and)\s*design\b/.test(c)) return 'spec+design';
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Parse the inventory table: any markdown row whose first cell is a `UI-<n>` id. Leading/trailing
|
|
78
|
+
* pipes are optional (GFM), and the Source cell is FOUND rather than counted — a `|` written inside
|
|
79
|
+
* the Item cell shifts the columns, and a positional read then misfiled a design-only row.
|
|
80
|
+
*/
|
|
81
|
+
export function parseUiInventory(text: string): { items: UiItem[]; imagesRead: UiInventoryResult['imagesRead'] } {
|
|
82
|
+
const items: UiItem[] = [];
|
|
83
|
+
for (const line of text.split('\n')) {
|
|
84
|
+
if (!line.includes('|')) continue;
|
|
85
|
+
const cells = line.trim().replace(/^\|/, '').replace(/\|$/, '').split('|').map((c) => c.trim());
|
|
86
|
+
if (!/^UI-\d+$/i.test(cells[0] ?? '')) continue;
|
|
87
|
+
// Source = the last cell that reads as one (Seen-in follows it; prose in Item never starts so).
|
|
88
|
+
let srcIdx = -1;
|
|
89
|
+
for (let i = cells.length - 1; i >= 2; i--) if (sourceOf(cells[i])) { srcIdx = i; break; }
|
|
90
|
+
const source: UiItemSource = srcIdx >= 0 ? sourceOf(cells[srcIdx])! : 'design-only';
|
|
91
|
+
const kindIdx = srcIdx >= 0 ? srcIdx - 1 : Math.min(2, cells.length - 1);
|
|
92
|
+
items.push({
|
|
93
|
+
id: cells[0].toUpperCase(),
|
|
94
|
+
item: cells.slice(1, Math.max(2, kindIdx)).join(' | '),
|
|
95
|
+
kind: cells[kindIdx] ?? '',
|
|
96
|
+
source,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
const m = text.match(/images read:\s*(\d+)\s*\/\s*(\d+)/i);
|
|
100
|
+
return { items, imagesRead: m ? { read: Number(m[1]), total: Number(m[2]) } : null };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Ids cited on a `UI-DEFERRED` comment line — a deliberate, REASONED omission. A line that lists
|
|
105
|
+
* ids and nothing else is not a decision anyone can review, so it defers nothing.
|
|
106
|
+
*/
|
|
107
|
+
function deferredIds(featureText: string): Set<string> {
|
|
108
|
+
const lines = featureText.split('\n').filter((l) => {
|
|
109
|
+
if (!/^\s*#\s*UI-DEFERRED\b/i.test(l)) return false;
|
|
110
|
+
const reason = l.replace(/^\s*#\s*UI-DEFERRED\b\s*:?/i, '').replace(/\bUI-\d+(?:\s*(?:\.\.|–|—|~|\/)\s*(?:UI-)?\d+)*/gi, '');
|
|
111
|
+
return /[\p{L}]{3,}/u.test(reason);
|
|
112
|
+
});
|
|
113
|
+
return citedIds(lines.join('\n'));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function uiInventoryCoverage(reqDir: string, featureText: string): UiInventoryResult {
|
|
117
|
+
const designSources = designSourcesOf(reqDir);
|
|
118
|
+
const invPath = path.join(reqDir, 'ui-inventory.md');
|
|
119
|
+
const invText = fs.existsSync(invPath) ? readTextFile(invPath) : null;
|
|
120
|
+
const { items, imagesRead } = invText ? parseUiInventory(invText) : { items: [], imagesRead: null };
|
|
121
|
+
// Coverage is the `@ui:` TAG, nothing else: a `UI-001`-shaped string elsewhere (an `@spec:UI-001`
|
|
122
|
+
// requirement in a project that numbers its FRs that way, prose, an unreasoned deferral line)
|
|
123
|
+
// must not mark a design item as proven.
|
|
124
|
+
const cites = citedIds((featureText.match(/@ui:\S+/gi) ?? []).join(' '));
|
|
125
|
+
const deferSet = deferredIds(featureText);
|
|
126
|
+
const covered: string[] = [];
|
|
127
|
+
const deferred: string[] = [];
|
|
128
|
+
const uncovered: UiItem[] = [];
|
|
129
|
+
for (const it of items) {
|
|
130
|
+
if (deferSet.has(it.id)) deferred.push(it.id);
|
|
131
|
+
else if (cites.has(it.id)) covered.push(it.id);
|
|
132
|
+
else if (it.source !== 'spec+design') uncovered.push(it);
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
designSources,
|
|
136
|
+
hasInventory: invText !== null,
|
|
137
|
+
items,
|
|
138
|
+
imagesRead,
|
|
139
|
+
imagesOnDisk: designSources.filter((s) => IMAGE.test(s)).length,
|
|
140
|
+
covered,
|
|
141
|
+
deferred,
|
|
142
|
+
uncovered,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Advisory findings for the audit report. */
|
|
147
|
+
export function uiInventoryFindings(r: UiInventoryResult): string[] {
|
|
148
|
+
const out: string[] = [];
|
|
149
|
+
if (r.designSources.length === 0) return out;
|
|
150
|
+
if (!r.hasInventory) {
|
|
151
|
+
out.push(`UI-INVENTORY-MISSING: the unit has design sources (${r.designSources.slice(0, 4).join(', ')}${r.designSources.length > 4 ? ', …' : ''}) but no \`requirements/ui-inventory.md\` — nothing shows the design was read item by item, so an item that exists only on the design (or contradicts the spec) is invisible to the gate and to review. Re-run /sungen:create-test so it writes the inventory, or write it by hand: one \`| UI-NNN | item | kind | spec+design / design-only / conflict |\` row per component, column, button, state and annotation.`);
|
|
152
|
+
return out;
|
|
153
|
+
}
|
|
154
|
+
if (r.imagesRead && r.imagesRead.read < Math.max(r.imagesRead.total, r.imagesOnDisk)) {
|
|
155
|
+
out.push(`UI-INVENTORY-INCOMPLETE: the inventory records "images read: ${r.imagesRead.read}/${r.imagesRead.total}" and the unit holds ${r.imagesOnDisk} image(s) — the unread design is not inventoried. Read the remaining images and add their items.`);
|
|
156
|
+
} else if (!r.imagesRead && r.imagesOnDisk > 0) {
|
|
157
|
+
out.push(`UI-INVENTORY-INCOMPLETE: the unit holds ${r.imagesOnDisk} image(s) but the inventory does not record "images read: N/N" — nothing shows every image was opened. Add the line after reading them all.`);
|
|
158
|
+
}
|
|
159
|
+
for (const it of r.uncovered.slice(0, 10)) {
|
|
160
|
+
out.push(`UI-ITEM-UNCOVERED: ${it.id} "${it.item}" (${it.kind}, ${it.source}) has no scenario and no deferral — ${it.source === 'conflict' ? 'the design and spec.md disagree on it; resolve which is right, then cover it' : 'it exists only on the design, outside spec.md'}. Cover it (\`@ui:${it.id}\` on the scenario that proves it), or record the decision: \`# UI-DEFERRED: ${it.id} — <reason>\`.`);
|
|
161
|
+
}
|
|
162
|
+
if (r.uncovered.length > 10) out.push(`UI-ITEM-UNCOVERED: … and ${r.uncovered.length - 10} more design-only/conflict item(s).`);
|
|
163
|
+
return out;
|
|
164
|
+
}
|
|
@@ -2,6 +2,7 @@ import * as crypto from 'crypto';
|
|
|
2
2
|
import * as fs from 'fs';
|
|
3
3
|
import * as path from 'path';
|
|
4
4
|
import { parseViewpointOverview } from './parse';
|
|
5
|
+
import { isAiDraft, removeDraftLine } from './viewpoint-draft-header';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Keep the yardstick from moving with the thing it measures.
|
|
@@ -89,7 +90,12 @@ function fingerprint(ids: string[]): string {
|
|
|
89
90
|
*/
|
|
90
91
|
function fileFingerprint(viewpointPath: string): string {
|
|
91
92
|
try {
|
|
92
|
-
|
|
93
|
+
const bytes = fs.readFileSync(viewpointPath);
|
|
94
|
+
// `drafted-by: ai` is provenance that accept REMOVES, so it is not part of what was reviewed:
|
|
95
|
+
// counted, accepting a draft would read as the declaration changing. Every other byte stays raw.
|
|
96
|
+
const text = bytes.toString('utf-8');
|
|
97
|
+
const hashed = isAiDraft(text) ? Buffer.from(removeDraftLine(text), 'utf-8') : bytes;
|
|
98
|
+
return crypto.createHash('sha1').update(hashed).digest('hex').slice(0, 12);
|
|
93
99
|
} catch { return ''; }
|
|
94
100
|
}
|
|
95
101
|
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import { stripHtmlComments } from './read-text';
|
|
3
|
+
import { writeFileAtomic } from './write-atomic';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* `drafted-by: ai` — provenance, not acceptance. The AI writes it as the first header line of a
|
|
7
|
+
* viewpoint it drafted; while it is there the file is an unreviewed draft: the audit records no
|
|
8
|
+
* baseline for it and holds the two axes measured against it. `--accept-viewpoint` removes it.
|
|
9
|
+
*
|
|
10
|
+
* Read by a line scan, not by the viewpoint parser: the key must be found in a file the parser
|
|
11
|
+
* judges nothing about, and in a QA-written file it simply is not there.
|
|
12
|
+
*/
|
|
13
|
+
const DRAFT_KEY = /^\s*drafted-by:\s*ai\b/i;
|
|
14
|
+
/** The key is a header line: looking further down would read prose that merely mentions it. */
|
|
15
|
+
const HEADER_LINES = 15;
|
|
16
|
+
|
|
17
|
+
/** A line with its own terminator, so a removal keeps CRLF and a lone `\r` exactly as written. */
|
|
18
|
+
const withTerminator = (raw: string): string[] => raw.match(/[^\r\n]*(?:\r\n|\r|\n)|[^\r\n]+$/g) ?? [];
|
|
19
|
+
|
|
20
|
+
/** Index of the draft line in `raw`, or -1. Comments are blanked line-for-line, so indexes agree with the raw file. */
|
|
21
|
+
function draftLineIndex(raw: string): number {
|
|
22
|
+
const visible = stripHtmlComments(raw.replace(/\r\n?/g, '\n')).split('\n');
|
|
23
|
+
let seen = 0;
|
|
24
|
+
for (let i = 0; i < visible.length && seen < HEADER_LINES; i++) {
|
|
25
|
+
if (visible[i].trim() === '') continue;
|
|
26
|
+
if (DRAFT_KEY.test(visible[i])) return i;
|
|
27
|
+
seen++;
|
|
28
|
+
}
|
|
29
|
+
return -1;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function isAiDraft(text: string): boolean {
|
|
33
|
+
return draftLineIndex(text) >= 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** `raw` without the one draft line; every other byte is untouched. A file with no draft line comes back as is. */
|
|
37
|
+
export function removeDraftLine(raw: string): string {
|
|
38
|
+
const at = draftLineIndex(raw);
|
|
39
|
+
if (at < 0) return raw;
|
|
40
|
+
return withTerminator(raw).filter((_, i) => i !== at).join('');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function viewpointFileIsAiDraft(viewpointPath: string): boolean {
|
|
44
|
+
try { return isAiDraft(fs.readFileSync(viewpointPath, 'utf-8')); } catch { return false; }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Remove the draft line from the file, atomically. True when a line was removed; throws a clear error when the write fails. */
|
|
48
|
+
export function removeDraftLineFromFile(viewpointPath: string): boolean {
|
|
49
|
+
const raw = fs.readFileSync(viewpointPath, 'utf-8');
|
|
50
|
+
if (!isAiDraft(raw)) return false;
|
|
51
|
+
try { writeFileAtomic(viewpointPath, removeDraftLine(raw)); } catch (err) {
|
|
52
|
+
throw new Error(`could not remove the \`drafted-by: ai\` line from ${viewpointPath} (${err instanceof Error ? err.message : err}) — the file is unchanged; remove the line by hand`);
|
|
53
|
+
}
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
@@ -9,8 +9,10 @@
|
|
|
9
9
|
* gaps that inflate a "looks-covered" score; it does not fail the gate.
|
|
10
10
|
*/
|
|
11
11
|
import * as fs from 'fs';
|
|
12
|
-
import { ScenarioInfo } from './parse';
|
|
13
|
-
import {
|
|
12
|
+
import { NON_DECLARATION_SECTION, PRIORITY_WORDS, ScenarioInfo } from './parse';
|
|
13
|
+
import { readViewpointText } from './read-text';
|
|
14
|
+
import { keywordHit } from './keyword-match';
|
|
15
|
+
import { countedAssertions } from './counted-assertions';
|
|
14
16
|
|
|
15
17
|
export interface LedgerItem { id?: string; text: string; covered: boolean }
|
|
16
18
|
|
|
@@ -56,6 +58,13 @@ function claimedCount(text: string): number | null {
|
|
|
56
58
|
}
|
|
57
59
|
|
|
58
60
|
const ID_RE = /\b([A-Z]{1,5}\d{0,2}(?:[.\-][A-Za-z0-9]+)*-?\d{0,3})\b/; // VP0.Title, VP7-002, MS-HP-001, TV-01
|
|
61
|
+
/**
|
|
62
|
+
* A bullet declares only the id it STARTS with (after a `[ ]` checkbox); one further along is a
|
|
63
|
+
* citation (`… (SG-01, …)`). `- **High** VP-…` or `- Email shown — VP-…` therefore carry no id.
|
|
64
|
+
*/
|
|
65
|
+
const BULLET_ID = new RegExp(`^[\\s(#]*(?:\\[[ xX]\\]\\s*)?\\[?${ID_RE.source}`);
|
|
66
|
+
/** A table cell that is an id and nothing else. */
|
|
67
|
+
const WHOLE_ID = new RegExp(`^${ID_RE.source}$`);
|
|
59
68
|
|
|
60
69
|
/**
|
|
61
70
|
* Lines that DECLARE something about the checklist rather than being a checklist item —
|
|
@@ -68,7 +77,10 @@ const ID_RE = /\b([A-Z]{1,5}\d{0,2}(?:[.\-][A-Za-z0-9]+)*-?\d{0,3})\b/; // VP0.T
|
|
|
68
77
|
const PLACEHOLDER_ITEM = /^(none\b|n\/a\b|tbd\b|no known\b|nothing\b|do not invent\b|-+$)/i;
|
|
69
78
|
/** A priority-DECLARATION row: `| VP-LOGIC | High | <reason prose> |`. The category id and its
|
|
70
79
|
* priority are consumed by the traceability + balance axes; the reason is rationale. */
|
|
71
|
-
|
|
80
|
+
// The word must be the WHOLE second cell: `skip` / `none` / `n/a` also open real claims
|
|
81
|
+
// ("Skip link moves focus to main", "None of the optional fields block submit") — and so did
|
|
82
|
+
// `high`/`low` ("High contrast mode …"), which the old word-boundary match swallowed too.
|
|
83
|
+
const PRIORITY_ROW = new RegExp(`^(?:VP|FL)[A-Z0-9._-]*\\s+—\\s+[*_\`]*(?:${PRIORITY_WORDS})[*_\`.]*(?=\\s+—|\\s*$)`, 'i');
|
|
72
84
|
/**
|
|
73
85
|
* Whole SECTIONS that declare something ABOUT the checklist rather than listing claims to prove.
|
|
74
86
|
*
|
|
@@ -81,49 +93,121 @@ const PRIORITY_ROW = /^(?:VP|FL)[A-Z0-9._-]*\s+—\s+(?:critical|high|medium|nor
|
|
|
81
93
|
* `Design Decisions` deliberately stays IN: "values are buffered between step 3 and 4, a reload
|
|
82
94
|
* loses them" is a real claim, and it is what the continuity check reads.
|
|
83
95
|
*/
|
|
84
|
-
const DECLARATION_SECTION =
|
|
96
|
+
const DECLARATION_SECTION = NON_DECLARATION_SECTION; // shared with the overview parser (#833)
|
|
97
|
+
/**
|
|
98
|
+
* A lead-in that introduces a manual check's PROCEDURE — `How to run VP-SEC-1:`, `Procedures:`,
|
|
99
|
+
* `Steps to reproduce:`. The list under it is how to EXECUTE a claim already declared above;
|
|
100
|
+
* counting each step as its own claim made gaps no scenario can close (8 of 13 surplus items on a
|
|
101
|
+
* field unit, #855).
|
|
102
|
+
*
|
|
103
|
+
* Deliberately narrow, because a false match HIDES claims: the lead may name one id and nothing
|
|
104
|
+
* else (`How to verify the following claims hold:` introduces claims, not steps), and a bare
|
|
105
|
+
* `Steps:` (a flow lists real step claims under it) and `Tester verifies:` (expected outcomes —
|
|
106
|
+
* exactly what the ledger counts) never qualify.
|
|
107
|
+
*/
|
|
108
|
+
const PROCEDURE_LEAD = /^(?:how to (?:run|test|reproduce|verify)|(?:manual )?procedures?|manual steps|steps to reproduce)(?:\s+(?:VP|FL)-[\w.-]+)?\s*:$/i;
|
|
109
|
+
const LIST_ITEM = /^(?:[-*+]|\d+[.)])\s+/;
|
|
85
110
|
const GENERIC = new Set(['display', 'shown', 'value', 'field', 'input', 'page', 'screen', 'button', 'link', 'text', 'check', 'verify', 'should', 'with', 'when', 'then', 'user', 'this', 'that', 'each', 'item', 'items']);
|
|
86
111
|
|
|
112
|
+
/**
|
|
113
|
+
* `decision`: a `- D4: …` line under Design Decisions. It names the spec's intent so rows can cite
|
|
114
|
+
* it; its testable part is carried by a VP row (review check 8). No scenario ever carries `D4`, so
|
|
115
|
+
* as a ledger item it stayed MISSING for good — "not tested" decisions included (phase-7 D5). The
|
|
116
|
+
* ledger skips it; the continuity, source and integrity checks still read it. Only that section
|
|
117
|
+
* declares: a QA claim labelled `D8` elsewhere stays a claim, and an id-less decision bullet stays
|
|
118
|
+
* the prose claim it always was.
|
|
119
|
+
*/
|
|
120
|
+
export interface ViewpointItem { id?: string; text: string; decision?: true }
|
|
121
|
+
const DECISION_ID = /^D\d+$/;
|
|
122
|
+
const DECISIONS_SECTION = /^(?:design decisions?|quyết định(?: thiết kế)?)\b/i;
|
|
123
|
+
|
|
87
124
|
/** Extract atomic checklist items from a viewpoint file (format-tolerant). */
|
|
88
|
-
export function parseViewpointItems(viewpointPath: string):
|
|
125
|
+
export function parseViewpointItems(viewpointPath: string): ViewpointItem[] {
|
|
89
126
|
if (!fs.existsSync(viewpointPath)) return [];
|
|
90
|
-
const lines =
|
|
91
|
-
const items:
|
|
127
|
+
const lines = readViewpointText(viewpointPath).split('\n');
|
|
128
|
+
const items: ViewpointItem[] = [];
|
|
92
129
|
let inFence = false;
|
|
93
130
|
let inDeclarationSection = false;
|
|
94
131
|
// The level an excluded section was opened at. A SUBSECTION inherits it: `### Optional`
|
|
95
132
|
// nested under `## 8. Traceability Index` used to re-open the section and hand its rows
|
|
96
133
|
// back as claims, because exclusion was recomputed from scratch at every heading.
|
|
97
134
|
let excludedAt: number | null = null;
|
|
98
|
-
|
|
99
|
-
|
|
135
|
+
// The level the Design Decisions section was opened at, while inside it (see `decision`).
|
|
136
|
+
let decisionsAt: number | null = null;
|
|
137
|
+
// Inside the list a PROCEDURE_LEAD introduced: the indent of the lead (-1 for a paragraph lead),
|
|
138
|
+
// whose list items — and their wrapped continuation lines — are steps. A heading, a table, a
|
|
139
|
+
// new lead, or (after a paragraph lead) a blank line once a step was read ends it: when in
|
|
140
|
+
// doubt a step stays a claim, because a swallowed claim is a gap nobody ever sees — so a LOOSE
|
|
141
|
+
// procedure (steps separated by blank lines) leaks its later steps as claims, by design.
|
|
142
|
+
// Only an INDENTED line continues a step (Markdown's continuation); unindented prose ends it.
|
|
143
|
+
let procIndent: number | null = null;
|
|
144
|
+
let procSteps = 0;
|
|
145
|
+
let prevBlank = false;
|
|
146
|
+
for (let i = 0; i < lines.length; i++) {
|
|
147
|
+
const line = lines[i].trim();
|
|
100
148
|
if (line.startsWith('```')) { inFence = !inFence; continue; }
|
|
101
|
-
if (inFence
|
|
149
|
+
if (inFence) continue;
|
|
150
|
+
if (!line) { prevBlank = true; continue; }
|
|
151
|
+
const wasBlank = prevBlank;
|
|
152
|
+
prevBlank = false;
|
|
153
|
+
const isItem = LIST_ITEM.test(line);
|
|
154
|
+
const indent = lines[i].length - lines[i].trimStart().length;
|
|
155
|
+
if (PROCEDURE_LEAD.test(line.replace(LIST_ITEM, '').replace(/[*_`]/g, '').trim())) {
|
|
156
|
+
procIndent = isItem ? indent : -1;
|
|
157
|
+
procSteps = 0;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (procIndent !== null) {
|
|
161
|
+
const listEnded = wasBlank && procIndent < 0 && procSteps > 0;
|
|
162
|
+
if (!listEnded && isItem && indent > procIndent) { procSteps++; continue; } // a step
|
|
163
|
+
if (!listEnded && !isItem && !wasBlank && procSteps > 0 && indent > Math.max(procIndent, 0)
|
|
164
|
+
&& !/^[#|]/.test(line) && !line.endsWith(':')) continue; // a step that wraps
|
|
165
|
+
procIndent = null;
|
|
166
|
+
}
|
|
102
167
|
const heading = line.match(/^(#{1,6})\s+(.*)$/);
|
|
103
168
|
if (heading) {
|
|
104
169
|
const level = heading[1].length;
|
|
105
170
|
if (excludedAt !== null && level > excludedAt) continue; // still inside the excluded section
|
|
171
|
+
if (decisionsAt !== null && level > decisionsAt) continue; // a subsection of Design Decisions
|
|
106
172
|
// A heading both ends the previous section and decides whether this one declares or claims.
|
|
107
173
|
// Strip an ordinal prefix ("## 5. Known Issues", "## 8. Viewpoint Grouping") before
|
|
108
174
|
// testing. The regex anchors at the start, so a NUMBERED heading matched nothing and
|
|
109
175
|
// every excluded section leaked its rows back in as claims — a real viewpoint numbers
|
|
110
176
|
// its sections, so in practice the exclusion list was doing nothing at all (#657).
|
|
111
|
-
|
|
177
|
+
// `Phần 4:` / `Part 4.` / `Mục 4.` number a section the same way.
|
|
178
|
+
const headText = heading[2].replace(/[*`]/g, '').replace(/^\s*(?:(?:[Pp]hần|[Pp]art|[Mm]ục|[Ss]ection)\s+)?(?:\d+|[A-Z])\s*[.):]\s*/, '').trim();
|
|
112
179
|
inDeclarationSection = DECLARATION_SECTION.test(headText);
|
|
113
180
|
excludedAt = inDeclarationSection ? level : null;
|
|
181
|
+
decisionsAt = DECISIONS_SECTION.test(headText) ? level : null;
|
|
114
182
|
continue;
|
|
115
183
|
}
|
|
116
184
|
if (inDeclarationSection) continue;
|
|
117
185
|
let text = '';
|
|
118
186
|
let lead = '';
|
|
187
|
+
let declared: RegExpMatchArray | null = null;
|
|
119
188
|
const bullet = line.match(/^(?:[-*+]|\d+[.)])\s+(.*)$/);
|
|
120
|
-
if (bullet) {
|
|
189
|
+
if (bullet) {
|
|
190
|
+
text = bullet[1]; lead = bullet[1];
|
|
191
|
+
declared = lead.replace(/[*`]/g, '').match(BULLET_ID);
|
|
192
|
+
}
|
|
121
193
|
else if (line.startsWith('|')) { // table data row
|
|
122
194
|
if (/^\|[\s|:-]+\|?$/.test(line)) continue; // separator
|
|
195
|
+
// The row right above a `|---|` separator IS the header, whatever its words (`Skill #`,
|
|
196
|
+
// `Case #`) — structure, not a guessed word list, so a data row such as `C#` or `Issue #`
|
|
197
|
+
// is never mistaken for one (#832). The word list below still covers a separator-less table.
|
|
198
|
+
const next = (lines[i + 1] ?? '').trim();
|
|
199
|
+
if (/^\|[\s|:-]+\|?$/.test(next) && next.includes('-')) continue; // header
|
|
123
200
|
const cells = line.split('|').map((c) => c.trim()).filter(Boolean);
|
|
124
201
|
if (/^(vp|id|viewpoint|priority|reason|no\.?|category|item|trigger|#|pattern|applicable|notes|field|constraint|code|description|status|step|flow|ref|level|question|screen|actor|branches from|own steps|component|thành phần|trường|bước)$/i.test(cells[0] || '')) continue; // header
|
|
125
202
|
text = cells.join(' — ');
|
|
126
|
-
lead
|
|
203
|
+
// The lead is the row's FIRST cell, empty or not: a continuation row (`| | | case | … see SG-02 |`)
|
|
204
|
+
// declares nothing. A later cell holding an id and nothing else still declares (`| x | VP-UI-001 |`),
|
|
205
|
+
// and so does a row number followed by a cell that starts with one (`| 1 | VP-UI-001 — … |`).
|
|
206
|
+
const raw = line.replace(/^\|/, '').replace(/\|$/, '').split('|').map((c) => c.replace(/[*`]/g, '').trim());
|
|
207
|
+
lead = raw[0] ?? '';
|
|
208
|
+
declared = lead.match(ID_RE)
|
|
209
|
+
?? (/^\d+\.?$/.test(lead) ? (raw[1] ?? '').match(BULLET_ID) : null)
|
|
210
|
+
?? raw.slice(1).map((c) => c.match(WHOLE_ID)).find(Boolean) ?? null;
|
|
127
211
|
} else continue;
|
|
128
212
|
text = text.replace(/[*`]/g, '').trim();
|
|
129
213
|
if (!text) continue;
|
|
@@ -138,15 +222,16 @@ export function parseViewpointItems(viewpointPath: string): { id?: string; text:
|
|
|
138
222
|
// under a grouping section. It declares nothing; it points at things declared elsewhere.
|
|
139
223
|
const leadIds = new Set((leadClean.match(new RegExp(ID_RE.source, 'g')) || []).filter((x) => /\d/.test(x)));
|
|
140
224
|
if (leadIds.size > 1) continue;
|
|
141
|
-
const
|
|
142
|
-
const id = idM && /\d/.test(idM[1]) ? idM[1] : undefined; // require a digit so prose words aren't IDs
|
|
225
|
+
const id = declared && /\d/.test(declared[1]) ? declared[1] : undefined; // require a digit so prose words aren't IDs
|
|
143
226
|
// A row keyed by a bare GROUP prefix ("VP-LOGIC — transitions — High — why the flow exists")
|
|
144
227
|
// declares a family and its priority, not a checklist item: nothing can "cover" it, and
|
|
145
228
|
// demanding coverage sends the author looking for a test that was never meant to exist.
|
|
146
229
|
if (/^VP-[A-Z]+\s*(?:—|-|\||$)/.test(leadClean) && !/\d/.test(leadClean)) continue;
|
|
147
230
|
const words = (text.toLowerCase().match(/[a-z][a-z-]{3,}/g) || []).filter((w) => !GENERIC.has(w));
|
|
148
231
|
if (!id && words.length < 2) continue; // not substantive enough to track
|
|
149
|
-
items.push(
|
|
232
|
+
items.push(decisionsAt !== null && id && DECISION_ID.test(id)
|
|
233
|
+
? { id, text: text.slice(0, 100), decision: true }
|
|
234
|
+
: { id, text: text.slice(0, 100) });
|
|
150
235
|
}
|
|
151
236
|
return items;
|
|
152
237
|
}
|
|
@@ -157,6 +242,7 @@ export function viewpointLedger(viewpointPath: string, scenarios: ScenarioInfo[]
|
|
|
157
242
|
// (#622) — silently collapsing those would remove its input.
|
|
158
243
|
const seen = new Set<string>();
|
|
159
244
|
const items = parseViewpointItems(viewpointPath)
|
|
245
|
+
.filter((i) => !i.decision)
|
|
160
246
|
.filter((i) => { const k = `${i.id ?? ''}|${i.text}`; if (seen.has(k)) return false; seen.add(k); return true; });
|
|
161
247
|
if (!fs.existsSync(viewpointPath) || items.length === 0) {
|
|
162
248
|
return { hasViewpoint: fs.existsSync(viewpointPath), total: 0, covered: 0, ratio: 1, missing: [], manualOnly: [], partial: [] };
|
|
@@ -190,15 +276,14 @@ export function viewpointLedger(viewpointPath: string, scenarios: ScenarioInfo[]
|
|
|
190
276
|
const n = claimedCount(item.text);
|
|
191
277
|
const auto = carriers.filter((s) => !s.manual);
|
|
192
278
|
if (n !== null && auto.length > 0 && !auto.some((s) => /\bsee all\b/.test(s.stepsText))) {
|
|
193
|
-
const asserted = Math.max(...auto.map((s) =>
|
|
194
|
-
(s.stepsText.match(/\bsee \[[^\]]+\][^|]*?\b(?:with|contains) \{\{/g) ?? []).length));
|
|
279
|
+
const asserted = Math.max(...auto.map((s) => countedAssertions(s.stepsText)));
|
|
195
280
|
if (asserted > 0 && asserted < n) partial.push({ id: item.id, text: item.text, claimed: n, asserted });
|
|
196
281
|
}
|
|
197
282
|
}
|
|
198
283
|
} else {
|
|
199
284
|
const words = [...new Set((item.text.toLowerCase().match(/[a-z][a-z-]{3,}/g) || []).filter((w) => !GENERIC.has(w)))];
|
|
200
285
|
const need = Math.min(2, words.length);
|
|
201
|
-
isCovered = words.length > 0 && scenarios.some((s) => words.filter((w) => s.haystack
|
|
286
|
+
isCovered = words.length > 0 && scenarios.some((s) => words.filter((w) => keywordHit(s.haystack, w)).length >= need);
|
|
202
287
|
}
|
|
203
288
|
if (isCovered) covered++;
|
|
204
289
|
else missing.push({ id: item.id, text: item.text });
|
|
@@ -348,13 +433,16 @@ export function viewpointIntegrity(
|
|
|
348
433
|
}
|
|
349
434
|
|
|
350
435
|
// The Priority-Viewpoints table: `| <id> | <priority> | <reason> |`.
|
|
351
|
-
|
|
352
|
-
const
|
|
353
|
-
|
|
354
|
-
.matchAll(/^\|\s*((?:VP|FL)[A-Z0-9._-]*)\s*\|\s*(critical|high|medium|normal|low|deferred)\s*\|/gim)]
|
|
436
|
+
const vpText = readViewpointText(viewpointPath);
|
|
437
|
+
const words = [...vpText
|
|
438
|
+
.matchAll(new RegExp(`^\\|\\s*((?:VP|FL)[A-Z0-9._-]*)\\s*\\|\\s*(${PRIORITY_WORDS})\\s*\\|`, 'gim'))]
|
|
355
439
|
.map((m) => m[2].toLowerCase());
|
|
356
|
-
|
|
357
|
-
|
|
440
|
+
// Fold only synonyms (Medium / Med / Normal are one level). `critical` stays its own word: a
|
|
441
|
+
// Critical / High / High table is a ranking, and folding it into `high` made it read as uniform.
|
|
442
|
+
const level = (w: string): string => (/^(?:medium|med|normal)$/.test(w) ? 'normal' : w);
|
|
443
|
+
const uniformPriority = words.length >= 3 && new Set(words.map(level)).size === 1
|
|
444
|
+
? { levels: [words[0]], rows: words.length } // the word QA wrote, not the folded key
|
|
445
|
+
: null;
|
|
358
446
|
|
|
359
447
|
return { duplicateIds, danglingRefs, uniformPriority };
|
|
360
448
|
}
|