@sun-asterisk/sungen 3.2.32 ā 3.2.34
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 +26 -4
- 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 +35 -17
- 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/delivery.d.ts.map +1 -1
- package/dist/cli/commands/delivery.js +79 -53
- 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/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/index.js +7 -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.map +1 -1
- package/dist/exporters/csv-exporter.js +11 -10
- package/dist/exporters/csv-exporter.js.map +1 -1
- package/dist/exporters/feature-parser.d.ts +21 -0
- package/dist/exporters/feature-parser.d.ts.map +1 -1
- package/dist/exporters/feature-parser.js +32 -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 +19 -9
- package/dist/exporters/matrix/build.js.map +1 -1
- package/dist/exporters/matrix/carry-manual-results.d.ts +70 -0
- package/dist/exporters/matrix/carry-manual-results.d.ts.map +1 -0
- package/dist/exporters/matrix/carry-manual-results.js +232 -0
- package/dist/exporters/matrix/carry-manual-results.js.map +1 -0
- package/dist/exporters/matrix/export.d.ts +5 -0
- package/dist/exporters/matrix/export.d.ts.map +1 -1
- package/dist/exporters/matrix/export.js +8 -1
- 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-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/types.d.ts +8 -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/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 +47 -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/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 +29 -12
- 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/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/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/next-step.d.ts.map +1 -1
- package/dist/harness/next-step.js +1 -0
- package/dist/harness/next-step.js.map +1 -1
- package/dist/harness/parse.d.ts +24 -0
- package/dist/harness/parse.d.ts.map +1 -1
- package/dist/harness/parse.js +117 -13
- 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/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 +102 -27
- 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/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-ledger.d.ts.map +1 -1
- package/dist/harness/viewpoint-ledger.js +66 -10
- package/dist/harness/viewpoint-ledger.js.map +1 -1
- 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/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 +3 -11
- 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 +2 -2
- package/dist/orchestrator/templates/ai-src/commands/review.md +2 -2
- package/dist/orchestrator/templates/ai-src/commands/run-test.md +70 -13
- 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 +23 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +26 -8
- 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 +41 -1
- package/dist/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +7 -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 +20 -0
- package/dist/orchestrator/viewpoint-pattern-catalog.d.ts.map +1 -0
- package/dist/orchestrator/viewpoint-pattern-catalog.js +45 -0
- package/dist/orchestrator/viewpoint-pattern-catalog.js.map +1 -0
- package/package.json +3 -3
- package/src/cli/commands/audit.ts +24 -5
- package/src/cli/commands/capability.ts +51 -18
- package/src/cli/commands/changed.ts +2 -4
- package/src/cli/commands/dashboard.ts +1 -26
- package/src/cli/commands/delivery.ts +75 -48
- 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/update.ts +3 -0
- package/src/cli/index.ts +7 -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 +7 -10
- package/src/exporters/feature-parser.ts +32 -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 +20 -10
- package/src/exporters/matrix/carry-manual-results.ts +229 -0
- package/src/exporters/matrix/export.ts +14 -4
- package/src/exporters/matrix/gates.ts +6 -0
- package/src/exporters/matrix/render-testcase-sheet.ts +10 -4
- package/src/exporters/matrix/types.ts +8 -1
- package/src/exporters/matrix/wording.ts +36 -2
- package/src/exporters/run-environment.ts +89 -0
- package/src/exporters/scenario-merger.ts +49 -4
- package/src/exporters/test-data-resolver.ts +0 -6
- 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 +37 -16
- 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/data-driven-lint.ts +82 -24
- 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/next-step.ts +1 -0
- package/src/harness/parse.ts +123 -16
- 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/secret-scan.ts +60 -4
- package/src/harness/select-by-ids.ts +83 -0
- package/src/harness/sensors.ts +109 -28
- package/src/harness/spec-branches.ts +6 -3
- package/src/harness/spec-coverage.ts +14 -4
- package/src/harness/ui-inventory.ts +164 -0
- package/src/harness/viewpoint-ledger.ts +59 -12
- 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/project-deps-sync.ts +23 -1
- package/src/orchestrator/project-initializer.ts +54 -6
- package/src/orchestrator/screen-manager.ts +3 -11
- 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 +2 -2
- package/src/orchestrator/templates/ai-src/commands/review.md +2 -2
- package/src/orchestrator/templates/ai-src/commands/run-test.md +70 -13
- 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 +23 -1
- package/src/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md +26 -8
- 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 +41 -1
- package/src/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md +7 -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 +58 -0
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* Generic: no project data.
|
|
10
10
|
*/
|
|
11
11
|
import * as fs from 'fs';
|
|
12
|
+
import { keywordHit } from './keyword-match';
|
|
12
13
|
import { ScenarioInfo } from './parse';
|
|
13
14
|
import { readTextFile } from './read-text';
|
|
14
15
|
|
|
@@ -139,7 +140,11 @@ function directionCovered(block: string, dir: FrDirection, subject: string[]): b
|
|
|
139
140
|
const pool = lines.length ? lines : [block];
|
|
140
141
|
// Narrow to the lines about the clause's element when the suite names it the same way the spec
|
|
141
142
|
// does; a spec in one language and selector keys in another must not read as "no evidence".
|
|
142
|
-
|
|
143
|
+
// Word-aware (#779), or the narrowing lets in a line about something else and then trusts it:
|
|
144
|
+
// `underĀ·score` is not a mention of `Score`, but it selected the only absence assertion in the
|
|
145
|
+
// block and so proved a MUST NOT clause about the Score. (`keywordHit` keeps substring semantics
|
|
146
|
+
// for a non-ASCII element name, which is what the cross-language case above depends on.)
|
|
147
|
+
const about = subject.length ? pool.filter((l) => subject.some((x) => keywordHit(l, x))) : [];
|
|
143
148
|
const scan = about.length ? about : pool;
|
|
144
149
|
return scan.some((l) => NEGATIVE_ASSERTION.test(l) === (dir.polarity === 'negate'));
|
|
145
150
|
}
|
|
@@ -269,7 +274,12 @@ export function specCoverage(specPath: string, scenarios: ScenarioInfo[], featur
|
|
|
269
274
|
const cited = featLower.includes(idLower) || cites.has(fr.id.toUpperCase());
|
|
270
275
|
const words = [...new Set((fr.text.toLowerCase().match(/[a-z][a-z-]{4,}/g) || []))]
|
|
271
276
|
.filter((w) => !/must|should|system|screen|users?|value|input|field/.test(w));
|
|
272
|
-
|
|
277
|
+
// Word-aware, not substring (#779). `haystack.includes('format')` is satisfied by
|
|
278
|
+
// inĀ·formatĀ·ion and `includes('length')` by waveĀ·length, and two such collisions clear the
|
|
279
|
+
// `>= 2 words` bar as easily as one ā so a MUST clause nobody tested read as covered. The
|
|
280
|
+
// threshold is a separate judgement (is one word enough evidence?) and is unchanged; see
|
|
281
|
+
// keyword-match.ts for why the boundary is leading-only.
|
|
282
|
+
const kwHit = words.length > 0 && scenarios.some((s) => words.filter((w) => keywordHit(s.haystack, w)).length >= Math.min(2, words.length));
|
|
273
283
|
if (!cited && !kwHit) {
|
|
274
284
|
if (fr.modality === 'MUST') uncoveredMust.push({ id: fr.id, text: fr.text.slice(0, 90) });
|
|
275
285
|
continue;
|
|
@@ -279,7 +289,7 @@ export function specCoverage(specPath: string, scenarios: ScenarioInfo[], featur
|
|
|
279
289
|
// Which scenarios speak for this requirement ā by its id (tags and comments sit in the block)
|
|
280
290
|
// or by the same keyword rule used above.
|
|
281
291
|
const owning = blocks.filter((b) => b.includes(idLower)
|
|
282
|
-
|| (words.length > 0 && words.filter((w) => b
|
|
292
|
+
|| (words.length > 0 && words.filter((w) => keywordHit(b, w)).length >= Math.min(2, words.length)));
|
|
283
293
|
// Cited from outside any scenario (a header comment): nothing to judge a direction against, so
|
|
284
294
|
// report nothing rather than invent a gap.
|
|
285
295
|
if (!owning.length) { frCovered += units; continue; }
|
|
@@ -299,7 +309,7 @@ export function specCoverage(specPath: string, scenarios: ScenarioInfo[], featur
|
|
|
299
309
|
// blocks that belong to this constraint: cite its msg code (primary), or mention a
|
|
300
310
|
// DISTINCTIVE constraint word (generic words filtered to avoid matching happy paths).
|
|
301
311
|
const own = blocks.filter((b) =>
|
|
302
|
-
(row.code && b.includes(row.code.toLowerCase())) || kw.some((w) => b
|
|
312
|
+
(row.code && b.includes(row.code.toLowerCase())) || kw.some((w) => keywordHit(b, w)),
|
|
303
313
|
);
|
|
304
314
|
if (!own.length) continue; // constraint has no scenarios at all ā FR/viewpoint gate covers that
|
|
305
315
|
const found = [...new Set(own.flatMap((b) => actionTriggersIn(b)))];
|
|
@@ -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
|
+
}
|
|
@@ -9,8 +9,9 @@
|
|
|
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';
|
|
14
15
|
|
|
15
16
|
export interface LedgerItem { id?: string; text: string; covered: boolean }
|
|
16
17
|
|
|
@@ -68,7 +69,10 @@ const ID_RE = /\b([A-Z]{1,5}\d{0,2}(?:[.\-][A-Za-z0-9]+)*-?\d{0,3})\b/; // VP0.T
|
|
|
68
69
|
const PLACEHOLDER_ITEM = /^(none\b|n\/a\b|tbd\b|no known\b|nothing\b|do not invent\b|-+$)/i;
|
|
69
70
|
/** A priority-DECLARATION row: `| VP-LOGIC | High | <reason prose> |`. The category id and its
|
|
70
71
|
* priority are consumed by the traceability + balance axes; the reason is rationale. */
|
|
71
|
-
|
|
72
|
+
// The word must be the WHOLE second cell: `skip` / `none` / `n/a` also open real claims
|
|
73
|
+
// ("Skip link moves focus to main", "None of the optional fields block submit") ā and so did
|
|
74
|
+
// `high`/`low` ("High contrast mode ā¦"), which the old word-boundary match swallowed too.
|
|
75
|
+
const PRIORITY_ROW = new RegExp(`^(?:VP|FL)[A-Z0-9._-]*\\s+ā\\s+[*_\`]*(?:${PRIORITY_WORDS})[*_\`.]*(?=\\s+ā|\\s*$)`, 'i');
|
|
72
76
|
/**
|
|
73
77
|
* Whole SECTIONS that declare something ABOUT the checklist rather than listing claims to prove.
|
|
74
78
|
*
|
|
@@ -81,13 +85,26 @@ const PRIORITY_ROW = /^(?:VP|FL)[A-Z0-9._-]*\s+ā\s+(?:critical|high|medium|nor
|
|
|
81
85
|
* `Design Decisions` deliberately stays IN: "values are buffered between step 3 and 4, a reload
|
|
82
86
|
* loses them" is a real claim, and it is what the continuity check reads.
|
|
83
87
|
*/
|
|
84
|
-
const DECLARATION_SECTION =
|
|
88
|
+
const DECLARATION_SECTION = NON_DECLARATION_SECTION; // shared with the overview parser (#833)
|
|
89
|
+
/**
|
|
90
|
+
* A lead-in that introduces a manual check's PROCEDURE ā `How to run VP-SEC-1:`, `Procedures:`,
|
|
91
|
+
* `Steps to reproduce:`. The list under it is how to EXECUTE a claim already declared above;
|
|
92
|
+
* counting each step as its own claim made gaps no scenario can close (8 of 13 surplus items on a
|
|
93
|
+
* field unit, #855).
|
|
94
|
+
*
|
|
95
|
+
* Deliberately narrow, because a false match HIDES claims: the lead may name one id and nothing
|
|
96
|
+
* else (`How to verify the following claims hold:` introduces claims, not steps), and a bare
|
|
97
|
+
* `Steps:` (a flow lists real step claims under it) and `Tester verifies:` (expected outcomes ā
|
|
98
|
+
* exactly what the ledger counts) never qualify.
|
|
99
|
+
*/
|
|
100
|
+
const PROCEDURE_LEAD = /^(?:how to (?:run|test|reproduce|verify)|(?:manual )?procedures?|manual steps|steps to reproduce)(?:\s+(?:VP|FL)-[\w.-]+)?\s*:$/i;
|
|
101
|
+
const LIST_ITEM = /^(?:[-*+]|\d+[.)])\s+/;
|
|
85
102
|
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
103
|
|
|
87
104
|
/** Extract atomic checklist items from a viewpoint file (format-tolerant). */
|
|
88
105
|
export function parseViewpointItems(viewpointPath: string): { id?: string; text: string }[] {
|
|
89
106
|
if (!fs.existsSync(viewpointPath)) return [];
|
|
90
|
-
const lines =
|
|
107
|
+
const lines = readViewpointText(viewpointPath).split('\n');
|
|
91
108
|
const items: { id?: string; text: string }[] = [];
|
|
92
109
|
let inFence = false;
|
|
93
110
|
let inDeclarationSection = false;
|
|
@@ -95,10 +112,36 @@ export function parseViewpointItems(viewpointPath: string): { id?: string; text:
|
|
|
95
112
|
// nested under `## 8. Traceability Index` used to re-open the section and hand its rows
|
|
96
113
|
// back as claims, because exclusion was recomputed from scratch at every heading.
|
|
97
114
|
let excludedAt: number | null = null;
|
|
98
|
-
|
|
99
|
-
|
|
115
|
+
// Inside the list a PROCEDURE_LEAD introduced: the indent of the lead (-1 for a paragraph lead),
|
|
116
|
+
// whose list items ā and their wrapped continuation lines ā are steps. A heading, a table, a
|
|
117
|
+
// new lead, or (after a paragraph lead) a blank line once a step was read ends it: when in
|
|
118
|
+
// doubt a step stays a claim, because a swallowed claim is a gap nobody ever sees ā so a LOOSE
|
|
119
|
+
// procedure (steps separated by blank lines) leaks its later steps as claims, by design.
|
|
120
|
+
// Only an INDENTED line continues a step (Markdown's continuation); unindented prose ends it.
|
|
121
|
+
let procIndent: number | null = null;
|
|
122
|
+
let procSteps = 0;
|
|
123
|
+
let prevBlank = false;
|
|
124
|
+
for (let i = 0; i < lines.length; i++) {
|
|
125
|
+
const line = lines[i].trim();
|
|
100
126
|
if (line.startsWith('```')) { inFence = !inFence; continue; }
|
|
101
|
-
if (inFence
|
|
127
|
+
if (inFence) continue;
|
|
128
|
+
if (!line) { prevBlank = true; continue; }
|
|
129
|
+
const wasBlank = prevBlank;
|
|
130
|
+
prevBlank = false;
|
|
131
|
+
const isItem = LIST_ITEM.test(line);
|
|
132
|
+
const indent = lines[i].length - lines[i].trimStart().length;
|
|
133
|
+
if (PROCEDURE_LEAD.test(line.replace(LIST_ITEM, '').replace(/[*_`]/g, '').trim())) {
|
|
134
|
+
procIndent = isItem ? indent : -1;
|
|
135
|
+
procSteps = 0;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (procIndent !== null) {
|
|
139
|
+
const listEnded = wasBlank && procIndent < 0 && procSteps > 0;
|
|
140
|
+
if (!listEnded && isItem && indent > procIndent) { procSteps++; continue; } // a step
|
|
141
|
+
if (!listEnded && !isItem && !wasBlank && procSteps > 0 && indent > Math.max(procIndent, 0)
|
|
142
|
+
&& !/^[#|]/.test(line) && !line.endsWith(':')) continue; // a step that wraps
|
|
143
|
+
procIndent = null;
|
|
144
|
+
}
|
|
102
145
|
const heading = line.match(/^(#{1,6})\s+(.*)$/);
|
|
103
146
|
if (heading) {
|
|
104
147
|
const level = heading[1].length;
|
|
@@ -120,6 +163,11 @@ export function parseViewpointItems(viewpointPath: string): { id?: string; text:
|
|
|
120
163
|
if (bullet) { text = bullet[1]; lead = bullet[1]; }
|
|
121
164
|
else if (line.startsWith('|')) { // table data row
|
|
122
165
|
if (/^\|[\s|:-]+\|?$/.test(line)) continue; // separator
|
|
166
|
+
// The row right above a `|---|` separator IS the header, whatever its words (`Skill #`,
|
|
167
|
+
// `Case #`) ā structure, not a guessed word list, so a data row such as `C#` or `Issue #`
|
|
168
|
+
// is never mistaken for one (#832). The word list below still covers a separator-less table.
|
|
169
|
+
const next = (lines[i + 1] ?? '').trim();
|
|
170
|
+
if (/^\|[\s|:-]+\|?$/.test(next) && next.includes('-')) continue; // header
|
|
123
171
|
const cells = line.split('|').map((c) => c.trim()).filter(Boolean);
|
|
124
172
|
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
173
|
text = cells.join(' ā ');
|
|
@@ -198,7 +246,7 @@ export function viewpointLedger(viewpointPath: string, scenarios: ScenarioInfo[]
|
|
|
198
246
|
} else {
|
|
199
247
|
const words = [...new Set((item.text.toLowerCase().match(/[a-z][a-z-]{3,}/g) || []).filter((w) => !GENERIC.has(w)))];
|
|
200
248
|
const need = Math.min(2, words.length);
|
|
201
|
-
isCovered = words.length > 0 && scenarios.some((s) => words.filter((w) => s.haystack
|
|
249
|
+
isCovered = words.length > 0 && scenarios.some((s) => words.filter((w) => keywordHit(s.haystack, w)).length >= need);
|
|
202
250
|
}
|
|
203
251
|
if (isCovered) covered++;
|
|
204
252
|
else missing.push({ id: item.id, text: item.text });
|
|
@@ -348,10 +396,9 @@ export function viewpointIntegrity(
|
|
|
348
396
|
}
|
|
349
397
|
|
|
350
398
|
// The Priority-Viewpoints table: `| <id> | <priority> | <reason> |`.
|
|
351
|
-
|
|
352
|
-
const vpText = fs.existsSync(viewpointPath) ? readTextFile(viewpointPath) : '';
|
|
399
|
+
const vpText = readViewpointText(viewpointPath);
|
|
353
400
|
const rows = [...vpText
|
|
354
|
-
.matchAll(
|
|
401
|
+
.matchAll(new RegExp(`^\\|\\s*((?:VP|FL)[A-Z0-9._-]*)\\s*\\|\\s*(${PRIORITY_WORDS})\\s*\\|`, 'gim'))]
|
|
355
402
|
.map((m) => m[2].toLowerCase());
|
|
356
403
|
const levels = [...new Set(rows)];
|
|
357
404
|
const uniformPriority = rows.length >= 3 && levels.length === 1 ? { levels, rows: rows.length } : null;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Write a file so that a crash mid-write can never leave a truncated one behind: write a sibling
|
|
3
|
+
* tmp, then rename. Same directory = same device, so the rename is atomic; a concurrent writer is
|
|
4
|
+
* last-rename-wins. On failure the tmp is removed and the error rethrown ā the caller still learns.
|
|
5
|
+
*
|
|
6
|
+
* `results merge` and `results sanitize` each grew this block inline, for the same reason and with
|
|
7
|
+
* the same comment. The third caller (`ai-skills/writer.ts`) did not, and its truncated manifest is
|
|
8
|
+
* precisely the corrupt input that #782's readers then absorbed in silence ā so the two halves of
|
|
9
|
+
* that defect are one file apart.
|
|
10
|
+
*/
|
|
11
|
+
import * as fs from 'fs';
|
|
12
|
+
|
|
13
|
+
export function writeFileAtomic(file: string, data: string): void {
|
|
14
|
+
const tmp = `${file}.tmp-${process.pid}`;
|
|
15
|
+
try {
|
|
16
|
+
fs.writeFileSync(tmp, data);
|
|
17
|
+
fs.renameSync(tmp, file);
|
|
18
|
+
} catch (err) {
|
|
19
|
+
try { fs.rmSync(tmp, { force: true }); } catch { /* best-effort cleanup ā the real error is below */ }
|
|
20
|
+
throw err;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -16,6 +16,8 @@ import * as path from 'path';
|
|
|
16
16
|
import * as crypto from 'crypto';
|
|
17
17
|
import { BuiltFile } from './build';
|
|
18
18
|
import { AdapterKey } from './adapters';
|
|
19
|
+
import { readJsonConfig, warnUnreadableConfig } from '../../harness/config-file';
|
|
20
|
+
import { writeFileAtomic } from '../../harness/write-atomic';
|
|
19
21
|
|
|
20
22
|
export interface ApplyOptions { force?: boolean; dryRun?: boolean; version: string }
|
|
21
23
|
export interface ApplyReport {
|
|
@@ -42,10 +44,21 @@ function adapterOf(relPath: string): AdapterKey | null {
|
|
|
42
44
|
return null;
|
|
43
45
|
}
|
|
44
46
|
|
|
47
|
+
/**
|
|
48
|
+
* The manifest is what makes orphan cleanup possible: it is the only record of which files a
|
|
49
|
+
* PREVIOUS version managed. An unreadable one used to read as an empty one ā so cleanup found
|
|
50
|
+
* nothing to remove, and the stale files it failed to remove are absent from the NEW manifest too.
|
|
51
|
+
* No later run can ever find them again. Say it, then carry on with what can still be done.
|
|
52
|
+
*/
|
|
45
53
|
function readManifest(cwd: string): Manifest {
|
|
46
54
|
const p = path.join(cwd, MANIFEST_REL);
|
|
47
|
-
|
|
48
|
-
|
|
55
|
+
const read = readJsonConfig<Manifest>(p);
|
|
56
|
+
if (read.state === 'unreadable') {
|
|
57
|
+
warnUnreadableConfig(p, read.error,
|
|
58
|
+
'orphan cleanup is skipped this run, so files managed by an older version stay in place');
|
|
59
|
+
return { version: '', managed: {} };
|
|
60
|
+
}
|
|
61
|
+
return read.state === 'ok' ? read.value : { version: '', managed: {} };
|
|
49
62
|
}
|
|
50
63
|
|
|
51
64
|
export function applyOutputs(cwd: string, outputs: BuiltFile[], opts: ApplyOptions): ApplyReport {
|
|
@@ -121,7 +134,9 @@ export function applyOutputs(cwd: string, outputs: BuiltFile[], opts: ApplyOptio
|
|
|
121
134
|
Object.assign(mergedManaged, newManaged);
|
|
122
135
|
const mPath = path.join(cwd, MANIFEST_REL);
|
|
123
136
|
fs.mkdirSync(path.dirname(mPath), { recursive: true });
|
|
124
|
-
|
|
137
|
+
// Atomic: an interrupted `sungen init`/`update` must not leave a half-written manifest, because
|
|
138
|
+
// the reader above can only report that damage ā it cannot undo it.
|
|
139
|
+
writeFileAtomic(mPath, JSON.stringify({ version: opts.version, managed: mergedManaged }, null, 2));
|
|
125
140
|
}
|
|
126
141
|
return r;
|
|
127
142
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import { getPackageVersion } from '../exporters/package-info';
|
|
4
|
+
import { readJsonConfig, warnUnreadableConfig } from '../harness/config-file';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Are this project's AI assets (commands + skills under `.claude/` etc.) the ones
|
|
@@ -27,12 +28,16 @@ export function checkAssetsDrift(cwd: string): AssetsDrift | null {
|
|
|
27
28
|
const manifestPath = path.join(cwd, '.sungen', 'manifest.json');
|
|
28
29
|
if (!fs.existsSync(manifestPath)) return null; // not a sungen project (yet)
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
const read = readJsonConfig<{ version?: string; managed?: Record<string, string> }>(manifestPath);
|
|
32
|
+
if (read.state === 'unreadable') {
|
|
33
|
+
// This function IS the drift alarm. Staying silent on a broken manifest disables the only
|
|
34
|
+
// warning that a project's .claude/ assets have gone stale ā and a truncated manifest is the
|
|
35
|
+
// likeliest way to get one (it is written non-atomically by `ai-skills/writer.ts`).
|
|
36
|
+
warnUnreadableConfig(manifestPath, read.error, 'stale-assets drift cannot be checked ā re-run `sungen init` to rewrite it');
|
|
37
|
+
return null;
|
|
35
38
|
}
|
|
39
|
+
if (read.state === 'absent') return null;
|
|
40
|
+
const manifest = read.value;
|
|
36
41
|
const recordedVersion = manifest.version ?? '';
|
|
37
42
|
const runningVersion = getPackageVersion();
|
|
38
43
|
const managed = manifest.managed ?? {};
|
|
@@ -2,6 +2,7 @@ import { spawnSync } from 'child_process';
|
|
|
2
2
|
import * as fs from 'fs';
|
|
3
3
|
import * as path from 'path';
|
|
4
4
|
import { resolveDriverVersion } from './lockstep-version';
|
|
5
|
+
import { ensureSecurityOverrides, reportSecurityOverrides } from './security-overrides';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Project-local sync of the sungen package family after a global `sungen update`.
|
|
@@ -143,8 +144,29 @@ export function syncProjectSungenDeps(cwd: string, core: string, dryRun: boolean
|
|
|
143
144
|
}
|
|
144
145
|
for (const s of plan.skipped) console.log(` ā skipped ${s.pkg} ā ${s.why}`);
|
|
145
146
|
|
|
147
|
+
// A project scaffolded before #764 carries no security overrides, and npm will never pick them up
|
|
148
|
+
// from sungen's own manifest ā `sungen update` is the one pass that can put them there. Write them
|
|
149
|
+
// before any install below, since npm reads `overrides` when it resolves.
|
|
150
|
+
const overridePlan = ensureSecurityOverrides(path.join(cwd, 'package.json'), dryRun);
|
|
151
|
+
reportSecurityOverrides(overridePlan, dryRun);
|
|
152
|
+
|
|
146
153
|
if (!plan.specs.length) {
|
|
147
|
-
console.log(` ā already on ${core}: ${plan.found.map((f) => f.pkg).join(', ')}
|
|
154
|
+
console.log(` ā already on ${core}: ${plan.found.map((f) => f.pkg).join(', ')}`);
|
|
155
|
+
// Family already in lockstep, so nothing would re-resolve ā but freshly written overrides only
|
|
156
|
+
// reach the tree through a resolution pass. Run one.
|
|
157
|
+
if (overridePlan?.add.length) {
|
|
158
|
+
if (dryRun) {
|
|
159
|
+
console.log(' [dry-run] would run: npm install (re-resolve under the new security overrides)\n');
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
console.log(' š¦ npm install (re-resolving under the new security overrides)');
|
|
163
|
+
const r = spawnSync('npm', ['install'], { cwd, stdio: 'inherit', shell: true });
|
|
164
|
+
if (r.status !== 0) {
|
|
165
|
+
console.warn('ā ļø Overrides were written to package.json but `npm install` failed ā run it by hand.\n');
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
console.log('');
|
|
148
170
|
return true;
|
|
149
171
|
}
|
|
150
172
|
|
|
@@ -17,6 +17,8 @@ import {
|
|
|
17
17
|
installMobileDriver,
|
|
18
18
|
} from './mobile-runtime-scaffolder';
|
|
19
19
|
import { emitAppiumEnvironment, runAppiumEnvironmentSetup, appiumEnvironmentRerunCommand } from './appium-environment-setup';
|
|
20
|
+
import { ensureSecurityOverrides, reportSecurityOverrides } from './security-overrides';
|
|
21
|
+
import { readTextFile } from '../harness/read-text';
|
|
20
22
|
|
|
21
23
|
export { MobileInitOptions };
|
|
22
24
|
|
|
@@ -94,6 +96,14 @@ export class ProjectInitializer {
|
|
|
94
96
|
private cwd: string;
|
|
95
97
|
private createdItems: string[] = [];
|
|
96
98
|
private skippedItems: string[] = [];
|
|
99
|
+
/**
|
|
100
|
+
* Actions the user asked for that could NOT be performed. Kept apart from `skippedItems`:
|
|
101
|
+
* that list renders under "Already exists", which is a statement of fact about the project ā
|
|
102
|
+
* printing "package.json scripts (invalid JSON)" under it tells the user the opposite of what
|
|
103
|
+
* happened. An action that was not done needs its own heading, or the init summary reads as a
|
|
104
|
+
* clean success while `npm test` is missing.
|
|
105
|
+
*/
|
|
106
|
+
private notDoneItems: string[] = [];
|
|
97
107
|
/** set by createCodexMCPConfig when the project isn't Codex-trusted (config would never load) */
|
|
98
108
|
private codexTrustWarning = false;
|
|
99
109
|
private agents: AdapterKey[];
|
|
@@ -135,6 +145,7 @@ export class ProjectInitializer {
|
|
|
135
145
|
: this.baseCwd;
|
|
136
146
|
this.createdItems = [];
|
|
137
147
|
this.skippedItems = [];
|
|
148
|
+
this.notDoneItems = [];
|
|
138
149
|
|
|
139
150
|
console.log(`š Initializing Sungen ${this.mobileOnly ? 'mobile-only ' : ''}project...\n`);
|
|
140
151
|
|
|
@@ -439,9 +450,17 @@ export class ProjectInitializer {
|
|
|
439
450
|
|
|
440
451
|
if (existed) {
|
|
441
452
|
try {
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
453
|
+
// via readTextFile: a UTF-8 BOM is the one thing `JSON.parse` rejects that every other tool
|
|
454
|
+
// reading this file ignores, and it is invisible in an editor. A config the rest of the
|
|
455
|
+
// toolchain loads fine must not cost the user their MCP servers here.
|
|
456
|
+
config = JSON.parse(readTextFile(filePath));
|
|
457
|
+
} catch (e) {
|
|
458
|
+
// Anything still unparseable is genuinely broken. Say WHERE ā "is not valid JSON" alone
|
|
459
|
+
// leaves the user to find a missing comma in a file they did not know was the problem.
|
|
460
|
+
this.notDoneItems.push(
|
|
461
|
+
`${displayName} (${(e as Error).message.split('\n')[0]}) ā MCP servers not registered; `
|
|
462
|
+
+ `fix the JSON and re-run \`sungen init\``,
|
|
463
|
+
);
|
|
445
464
|
return;
|
|
446
465
|
}
|
|
447
466
|
}
|
|
@@ -487,6 +506,13 @@ export class ProjectInitializer {
|
|
|
487
506
|
});
|
|
488
507
|
}
|
|
489
508
|
|
|
509
|
+
if (this.notDoneItems.length > 0) {
|
|
510
|
+
console.log('\nā Not done ā needs your attention:');
|
|
511
|
+
this.notDoneItems.forEach(item => {
|
|
512
|
+
console.log(` ā ${item}`);
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
|
|
490
516
|
if (this.codexTrustWarning) {
|
|
491
517
|
console.log('\nā Codex: this project is not trusted yet, so .codex/config.toml (the MCP browser');
|
|
492
518
|
console.log(' config for run-test) will be SILENTLY IGNORED by Codex. To fix, either:');
|
|
@@ -685,6 +711,13 @@ export class ProjectInitializer {
|
|
|
685
711
|
// Ensure standard scripts exist in package.json
|
|
686
712
|
this.ensurePackageScripts(packageJsonPath);
|
|
687
713
|
|
|
714
|
+
// Pin the transitive packages that would otherwise make `npm audit` red here. This MUST happen
|
|
715
|
+
// before the install below: npm reads `overrides` at resolution time, so a lockfile written
|
|
716
|
+
// first would already hold the vulnerable versions. See security-overrides.ts for why the pins
|
|
717
|
+
// cannot simply live in sungen's own manifest.
|
|
718
|
+
const overridePlan = ensureSecurityOverrides(packageJsonPath);
|
|
719
|
+
reportSecurityOverrides(overridePlan);
|
|
720
|
+
|
|
688
721
|
// Mobile-only drops @playwright/test (no web runner needed). `ajv`/`ajv-formats` back the API
|
|
689
722
|
// Driver's `expect {{name.body}} matches schema [Ref]` (loaded lazily; harmless if unused).
|
|
690
723
|
// fast-xml-parser: the API driver's XML request/response support lazy-requires it
|
|
@@ -696,7 +729,7 @@ export class ProjectInitializer {
|
|
|
696
729
|
|
|
697
730
|
let missingDeps: string[] = requiredDeps;
|
|
698
731
|
try {
|
|
699
|
-
const packageJson = JSON.parse(
|
|
732
|
+
const packageJson = JSON.parse(readTextFile(packageJsonPath));
|
|
700
733
|
const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
|
|
701
734
|
missingDeps = requiredDeps.filter(d => !deps[d]);
|
|
702
735
|
} catch {
|
|
@@ -705,6 +738,12 @@ export class ProjectInitializer {
|
|
|
705
738
|
|
|
706
739
|
if (missingDeps.length === 0) {
|
|
707
740
|
console.log('ā All dependencies already installed\n');
|
|
741
|
+
// Nothing to install means nothing re-resolves, so overrides just written would sit in
|
|
742
|
+
// package.json while the lockfile keeps the vulnerable tree. Re-resolve once so they land.
|
|
743
|
+
if (overridePlan?.add.length) {
|
|
744
|
+
console.log('š¦ Re-resolving the tree so the new security overrides take effect...\n');
|
|
745
|
+
execSync('npm install', execOpts);
|
|
746
|
+
}
|
|
708
747
|
} else {
|
|
709
748
|
console.log(`š¦ Installing ${missingDeps.join(', ')}...\n`);
|
|
710
749
|
execSync(`npm install -D ${missingDeps.join(' ')}`, execOpts);
|
|
@@ -725,8 +764,17 @@ export class ProjectInitializer {
|
|
|
725
764
|
private ensurePackageScripts(packageJsonPath: string): void {
|
|
726
765
|
let packageJson: Record<string, any>;
|
|
727
766
|
try {
|
|
728
|
-
|
|
729
|
-
|
|
767
|
+
// via readTextFile: a package.json carrying a UTF-8 BOM is one npm reads without complaint,
|
|
768
|
+
// so it must not be treated as unparseable here. That was the trigger that actually reached
|
|
769
|
+
// this catch ā genuinely broken JSON aborts `npm install` a few lines below instead.
|
|
770
|
+
packageJson = JSON.parse(readTextFile(packageJsonPath));
|
|
771
|
+
} catch (e) {
|
|
772
|
+
// Returning here used to add nothing and say nothing, so init reported success and the user
|
|
773
|
+
// met `npm error Missing script: "test"` later with nothing pointing back to this run.
|
|
774
|
+
this.notDoneItems.push(
|
|
775
|
+
`package.json scripts (${(e as Error).message.split('\n')[0]}) ā `
|
|
776
|
+
+ `fix the JSON and re-run \`sungen init\`, or add the scripts by hand`,
|
|
777
|
+
);
|
|
730
778
|
return;
|
|
731
779
|
}
|
|
732
780
|
|
|
@@ -9,6 +9,7 @@ import { chromium } from '@playwright/test';
|
|
|
9
9
|
import { readCapabilities } from '../harness/capability';
|
|
10
10
|
import { testDataHeader, ensureTestDataGuideFiles } from './test-data-guide';
|
|
11
11
|
import { resolveWebBaseUrl } from '../exporters/env-qa';
|
|
12
|
+
import { uiPatternRows } from './viewpoint-pattern-catalog';
|
|
12
13
|
|
|
13
14
|
export interface ScreenOptions {
|
|
14
15
|
name: string;
|
|
@@ -191,20 +192,11 @@ export class ScreenManager {
|
|
|
191
192
|
'',
|
|
192
193
|
'## UI Patterns Identified',
|
|
193
194
|
'',
|
|
194
|
-
'<!-- Check applicable patterns for this screen (
|
|
195
|
+
'<!-- Check applicable patterns for this screen (numbered as in the sungen-viewpoint skill):',
|
|
195
196
|
'',
|
|
196
197
|
'| # | Pattern | Applicable? | Notes |',
|
|
197
198
|
'|---|---------|-------------|-------|',
|
|
198
|
-
|
|
199
|
-
'| 2 | Data Table | ā | |',
|
|
200
|
-
'| 3 | Create / Add | ā | |',
|
|
201
|
-
'| 4 | Update / Edit | ā | |',
|
|
202
|
-
'| 5 | Delete | ā | |',
|
|
203
|
-
'| 6 | Search | ā | |',
|
|
204
|
-
'| 7 | Filter | ā | |',
|
|
205
|
-
'| 8 | Pagination | ā | |',
|
|
206
|
-
'| 9 | Modal / Dialog | ā | |',
|
|
207
|
-
'| 10 | List / Card | ā | |',
|
|
199
|
+
...uiPatternRows(isMobile),
|
|
208
200
|
'-->',
|
|
209
201
|
'',
|
|
210
202
|
'## Priority Viewpoints',
|