@sun-asterisk/sungen 3.2.2 → 3.2.3-beta.3
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/capabilities/discover.d.ts.map +1 -1
- package/dist/capabilities/discover.js +26 -3
- package/dist/capabilities/discover.js.map +1 -1
- package/dist/cli/commands/add-flow.d.ts.map +1 -1
- package/dist/cli/commands/add-flow.js +2 -0
- package/dist/cli/commands/add-flow.js.map +1 -1
- package/dist/cli/commands/add.d.ts.map +1 -1
- package/dist/cli/commands/add.js +4 -0
- package/dist/cli/commands/add.js.map +1 -1
- package/dist/cli/commands/capability.d.ts.map +1 -1
- package/dist/cli/commands/capability.js +61 -0
- package/dist/cli/commands/capability.js.map +1 -1
- package/dist/cli/commands/delivery.d.ts.map +1 -1
- package/dist/cli/commands/delivery.js +89 -2
- package/dist/cli/commands/delivery.js.map +1 -1
- package/dist/cli/commands/eval.js +3 -3
- package/dist/cli/commands/eval.js.map +1 -1
- package/dist/cli/commands/gate.d.ts +3 -0
- package/dist/cli/commands/gate.d.ts.map +1 -0
- package/dist/cli/commands/gate.js +83 -0
- package/dist/cli/commands/gate.js.map +1 -0
- package/dist/cli/commands/generate.d.ts.map +1 -1
- package/dist/cli/commands/generate.js +2 -1
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +32 -2
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/journey.d.ts +3 -0
- package/dist/cli/commands/journey.d.ts.map +1 -0
- package/dist/cli/commands/journey.js +114 -0
- package/dist/cli/commands/journey.js.map +1 -0
- package/dist/cli/commands/update.d.ts.map +1 -1
- package/dist/cli/commands/update.js +10 -3
- package/dist/cli/commands/update.js.map +1 -1
- package/dist/cli/index.js +4 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/dashboard/snapshot-builder.d.ts.map +1 -1
- package/dist/dashboard/snapshot-builder.js +10 -1
- package/dist/dashboard/snapshot-builder.js.map +1 -1
- package/dist/exporters/csv-exporter.d.ts.map +1 -1
- package/dist/exporters/csv-exporter.js +3 -1
- package/dist/exporters/csv-exporter.js.map +1 -1
- package/dist/exporters/json-exporter.js +3 -1
- package/dist/exporters/json-exporter.js.map +1 -1
- package/dist/exporters/mobile-app-id.d.ts +22 -0
- package/dist/exporters/mobile-app-id.d.ts.map +1 -0
- package/dist/exporters/mobile-app-id.js +37 -0
- package/dist/exporters/mobile-app-id.js.map +1 -0
- package/dist/exporters/spec-parser.d.ts.map +1 -1
- package/dist/exporters/spec-parser.js +6 -2
- package/dist/exporters/spec-parser.js.map +1 -1
- package/dist/generators/test-generator/adapters/adapter-interface.d.ts +3 -0
- package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -1
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/scroll-action.hbs +7 -4
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/toggle-action.hbs +20 -3
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/checked-assertion.hbs +4 -1
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/not-checked-assertion.hbs +4 -1
- package/dist/generators/test-generator/adapters/appium/templates/test-file.hbs +3 -0
- package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
- package/dist/generators/test-generator/code-generator.js +48 -9
- package/dist/generators/test-generator/code-generator.js.map +1 -1
- package/dist/generators/test-generator/template-engine.d.ts +3 -0
- package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
- package/dist/generators/test-generator/template-engine.js +11 -1
- package/dist/generators/test-generator/template-engine.js.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.d.ts +11 -1
- package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
- package/dist/generators/test-generator/utils/selector-resolver.js +33 -0
- package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
- package/dist/harness/audit.d.ts.map +1 -1
- package/dist/harness/audit.js +8 -1
- package/dist/harness/audit.js.map +1 -1
- package/dist/harness/catalog/universal-viewpoints.yaml +15 -0
- package/dist/harness/eval/skill-lint.d.ts +3 -3
- package/dist/harness/eval/skill-lint.d.ts.map +1 -1
- package/dist/harness/eval/skill-lint.js +88 -68
- package/dist/harness/eval/skill-lint.js.map +1 -1
- package/dist/harness/journey.d.ts +88 -0
- package/dist/harness/journey.d.ts.map +1 -0
- package/dist/harness/journey.js +414 -0
- package/dist/harness/journey.js.map +1 -0
- package/dist/harness/quality-gates.d.ts +13 -0
- package/dist/harness/quality-gates.d.ts.map +1 -1
- package/dist/harness/quality-gates.js +23 -0
- package/dist/harness/quality-gates.js.map +1 -1
- package/dist/harness/sensors.d.ts +1 -1
- package/dist/harness/sensors.d.ts.map +1 -1
- package/dist/harness/sensors.js +32 -9
- package/dist/harness/sensors.js.map +1 -1
- package/dist/orchestrator/ai-rules-updater.d.ts +11 -3
- package/dist/orchestrator/ai-rules-updater.d.ts.map +1 -1
- package/dist/orchestrator/ai-rules-updater.js +44 -180
- package/dist/orchestrator/ai-rules-updater.js.map +1 -1
- package/dist/orchestrator/ai-skills/adapters.d.ts +28 -0
- package/dist/orchestrator/ai-skills/adapters.d.ts.map +1 -0
- package/dist/orchestrator/ai-skills/adapters.js +34 -0
- package/dist/orchestrator/ai-skills/adapters.js.map +1 -0
- package/dist/orchestrator/ai-skills/build.d.ts +10 -0
- package/dist/orchestrator/ai-skills/build.d.ts.map +1 -0
- package/dist/orchestrator/ai-skills/build.js +128 -0
- package/dist/orchestrator/ai-skills/build.js.map +1 -0
- package/dist/orchestrator/ai-skills/cap-stripper.d.ts +27 -0
- package/dist/orchestrator/ai-skills/cap-stripper.d.ts.map +1 -0
- package/dist/orchestrator/ai-skills/cap-stripper.js +78 -0
- package/dist/orchestrator/ai-skills/cap-stripper.js.map +1 -0
- package/dist/orchestrator/ai-skills/commands.d.ts +22 -0
- package/dist/orchestrator/ai-skills/commands.d.ts.map +1 -0
- package/dist/orchestrator/ai-skills/commands.js +109 -0
- package/dist/orchestrator/ai-skills/commands.js.map +1 -0
- package/dist/orchestrator/ai-skills/generator.d.ts +23 -0
- package/dist/orchestrator/ai-skills/generator.d.ts.map +1 -0
- package/dist/orchestrator/ai-skills/generator.js +79 -0
- package/dist/orchestrator/ai-skills/generator.js.map +1 -0
- package/dist/orchestrator/ai-skills/golden-skills.test.d.ts +2 -0
- package/dist/orchestrator/ai-skills/golden-skills.test.d.ts.map +1 -0
- package/dist/orchestrator/ai-skills/golden-skills.test.js +183 -0
- package/dist/orchestrator/ai-skills/golden-skills.test.js.map +1 -0
- package/dist/orchestrator/ai-skills/resolve-assistants.d.ts +8 -0
- package/dist/orchestrator/ai-skills/resolve-assistants.d.ts.map +1 -0
- package/dist/orchestrator/ai-skills/resolve-assistants.js +28 -0
- package/dist/orchestrator/ai-skills/resolve-assistants.js.map +1 -0
- package/dist/orchestrator/ai-skills/writer.d.ts +15 -0
- package/dist/orchestrator/ai-skills/writer.d.ts.map +1 -0
- package/dist/orchestrator/ai-skills/writer.js +129 -0
- package/dist/orchestrator/ai-skills/writer.js.map +1 -0
- package/dist/orchestrator/flow-manager.d.ts +11 -0
- package/dist/orchestrator/flow-manager.d.ts.map +1 -1
- package/dist/orchestrator/flow-manager.js +147 -29
- package/dist/orchestrator/flow-manager.js.map +1 -1
- package/dist/orchestrator/mobile-runtime-scaffolder.d.ts +59 -0
- package/dist/orchestrator/mobile-runtime-scaffolder.d.ts.map +1 -0
- package/dist/orchestrator/mobile-runtime-scaffolder.js +298 -0
- package/dist/orchestrator/mobile-runtime-scaffolder.js.map +1 -0
- package/dist/orchestrator/project-initializer.d.ts +44 -7
- package/dist/orchestrator/project-initializer.d.ts.map +1 -1
- package/dist/orchestrator/project-initializer.js +147 -79
- package/dist/orchestrator/project-initializer.js.map +1 -1
- package/dist/orchestrator/screen-manager.d.ts +30 -0
- package/dist/orchestrator/screen-manager.d.ts.map +1 -1
- package/dist/orchestrator/screen-manager.js +205 -22
- package/dist/orchestrator/screen-manager.js.map +1 -1
- package/dist/orchestrator/templates/ai-src/README.md +58 -0
- package/dist/orchestrator/templates/{ai-instructions/claude-cmd-add-flow.md → ai-src/commands/add-flow.md} +63 -2
- package/dist/orchestrator/templates/{ai-instructions/claude-cmd-add-screen.md → ai-src/commands/add-screen.md} +171 -6
- package/dist/orchestrator/templates/{ai-instructions/claude-cmd-create-test.md → ai-src/commands/create-test.md} +118 -4
- package/{src/orchestrator/templates/ai-instructions/claude-cmd-dashboard.md → dist/orchestrator/templates/ai-src/commands/dashboard.md} +25 -3
- package/{src/orchestrator/templates/ai-instructions/claude-cmd-delivery.md → dist/orchestrator/templates/ai-src/commands/delivery.md} +67 -3
- package/dist/orchestrator/templates/ai-src/commands/design.md +29 -0
- package/{src/orchestrator/templates/ai-instructions/claude-cmd-feedback.md → dist/orchestrator/templates/ai-src/commands/feedback.md} +34 -3
- package/dist/orchestrator/templates/{ai-instructions/claude-cmd-locale.md → ai-src/commands/locale.md} +56 -4
- package/{src/orchestrator/templates/ai-instructions/claude-cmd-review.md → dist/orchestrator/templates/ai-src/commands/review.md} +57 -2
- package/{src/orchestrator/templates/ai-instructions/claude-cmd-run-test.md → dist/orchestrator/templates/ai-src/commands/run-test.md} +116 -5
- package/dist/orchestrator/templates/ai-src/config/codex.md +8 -0
- package/{src/orchestrator/templates/ai-instructions/claude-skill-capture-mobile.md → dist/orchestrator/templates/ai-src/skills/sungen-capture-mobile/SKILL.md} +11 -0
- package/dist/orchestrator/templates/{ai-instructions/github-skill-sungen-error-mapping.md → ai-src/skills/sungen-error-mapping/SKILL.md} +49 -9
- package/{src/orchestrator/templates/ai-instructions/claude-skill-harness-audit.md → dist/orchestrator/templates/ai-src/skills/sungen-harness-audit/SKILL.md} +5 -5
- package/{src/orchestrator/templates/ai-instructions/claude-skill-locale.md → dist/orchestrator/templates/ai-src/skills/sungen-locale/SKILL.md} +275 -3
- package/{src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md → dist/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md} +17 -0
- package/dist/orchestrator/templates/{ai-instructions/claude-skill-selector-fix-mobile.md → ai-src/skills/sungen-selector-fix-mobile/SKILL.md} +5 -0
- package/dist/orchestrator/templates/{ai-instructions/claude-skill-tc-generation.md → ai-src/skills/sungen-tc-generation/SKILL.md} +5 -0
- package/dist/orchestrator/templates/playwright.config.ts +7 -4
- package/package.json +3 -3
- package/src/capabilities/discover.ts +29 -3
- package/src/cli/commands/add-flow.ts +2 -0
- package/src/cli/commands/add.ts +4 -0
- package/src/cli/commands/capability.ts +64 -1
- package/src/cli/commands/delivery.ts +89 -2
- package/src/cli/commands/eval.ts +3 -3
- package/src/cli/commands/gate.ts +44 -0
- package/src/cli/commands/generate.ts +2 -1
- package/src/cli/commands/init.ts +50 -3
- package/src/cli/commands/journey.ts +72 -0
- package/src/cli/commands/update.ts +14 -4
- package/src/cli/index.ts +4 -0
- package/src/dashboard/snapshot-builder.ts +10 -1
- package/src/exporters/csv-exporter.ts +3 -1
- package/src/exporters/json-exporter.ts +3 -1
- package/src/exporters/mobile-app-id.ts +34 -0
- package/src/exporters/spec-parser.ts +6 -2
- package/src/generators/test-generator/adapters/adapter-interface.ts +3 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/scroll-action.hbs +7 -4
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/toggle-action.hbs +20 -3
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/checked-assertion.hbs +4 -1
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/not-checked-assertion.hbs +4 -1
- package/src/generators/test-generator/adapters/appium/templates/test-file.hbs +3 -0
- package/src/generators/test-generator/code-generator.ts +49 -9
- package/src/generators/test-generator/template-engine.ts +14 -1
- package/src/generators/test-generator/utils/selector-resolver.ts +51 -1
- package/src/harness/audit.ts +9 -2
- package/src/harness/catalog/universal-viewpoints.yaml +15 -0
- package/src/harness/eval/skill-lint.ts +71 -60
- package/src/harness/journey.ts +434 -0
- package/src/harness/quality-gates.ts +20 -0
- package/src/harness/sensors.ts +25 -8
- package/src/orchestrator/ai-rules-updater.ts +35 -197
- package/src/orchestrator/ai-skills/adapters.ts +52 -0
- package/src/orchestrator/ai-skills/build.ts +103 -0
- package/src/orchestrator/ai-skills/cap-stripper.ts +85 -0
- package/src/orchestrator/ai-skills/commands.ts +112 -0
- package/src/orchestrator/ai-skills/generator.ts +47 -0
- package/src/orchestrator/ai-skills/golden-skills.test.ts +124 -0
- package/src/orchestrator/ai-skills/resolve-assistants.ts +18 -0
- package/src/orchestrator/ai-skills/writer.ts +92 -0
- package/src/orchestrator/flow-manager.ts +165 -29
- package/src/orchestrator/mobile-runtime-scaffolder.ts +277 -0
- package/src/orchestrator/project-initializer.ts +159 -86
- package/src/orchestrator/screen-manager.ts +237 -22
- package/src/orchestrator/templates/ai-src/README.md +58 -0
- package/src/orchestrator/templates/{ai-instructions/claude-cmd-add-flow.md → ai-src/commands/add-flow.md} +63 -2
- package/src/orchestrator/templates/{ai-instructions/claude-cmd-add-screen.md → ai-src/commands/add-screen.md} +171 -6
- package/src/orchestrator/templates/{ai-instructions/claude-cmd-create-test.md → ai-src/commands/create-test.md} +118 -4
- package/{dist/orchestrator/templates/ai-instructions/claude-cmd-dashboard.md → src/orchestrator/templates/ai-src/commands/dashboard.md} +25 -3
- package/{dist/orchestrator/templates/ai-instructions/claude-cmd-delivery.md → src/orchestrator/templates/ai-src/commands/delivery.md} +67 -3
- package/src/orchestrator/templates/ai-src/commands/design.md +29 -0
- package/{dist/orchestrator/templates/ai-instructions/claude-cmd-feedback.md → src/orchestrator/templates/ai-src/commands/feedback.md} +34 -3
- package/src/orchestrator/templates/{ai-instructions/claude-cmd-locale.md → ai-src/commands/locale.md} +56 -4
- package/{dist/orchestrator/templates/ai-instructions/claude-cmd-review.md → src/orchestrator/templates/ai-src/commands/review.md} +57 -2
- package/{dist/orchestrator/templates/ai-instructions/claude-cmd-run-test.md → src/orchestrator/templates/ai-src/commands/run-test.md} +116 -5
- package/src/orchestrator/templates/ai-src/config/codex.md +8 -0
- package/{dist/orchestrator/templates/ai-instructions/claude-skill-capture-mobile.md → src/orchestrator/templates/ai-src/skills/sungen-capture-mobile/SKILL.md} +11 -0
- package/{dist/orchestrator/templates/ai-instructions/claude-skill-error-mapping.md → src/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md} +49 -9
- package/{dist/orchestrator/templates/ai-instructions/claude-skill-harness-audit.md → src/orchestrator/templates/ai-src/skills/sungen-harness-audit/SKILL.md} +5 -5
- package/{dist/orchestrator/templates/ai-instructions/claude-skill-locale.md → src/orchestrator/templates/ai-src/skills/sungen-locale/SKILL.md} +275 -3
- package/{dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md → src/orchestrator/templates/ai-src/skills/sungen-selector-fix/SKILL.md} +17 -0
- package/src/orchestrator/templates/{ai-instructions/claude-skill-selector-fix-mobile.md → ai-src/skills/sungen-selector-fix-mobile/SKILL.md} +5 -0
- package/src/orchestrator/templates/{ai-instructions/claude-skill-tc-generation.md → ai-src/skills/sungen-tc-generation/SKILL.md} +5 -0
- package/src/orchestrator/templates/playwright.config.ts +7 -4
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-design.md +0 -12
- package/dist/orchestrator/templates/ai-instructions/claude-skill-selector-fix.md +0 -347
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-add-flow.md +0 -86
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +0 -131
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +0 -93
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-dashboard.md +0 -65
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-delivery.md +0 -83
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-design.md +0 -13
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-feedback.md +0 -24
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-locale.md +0 -70
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-review.md +0 -38
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +0 -155
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mobile.md +0 -184
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-harness-audit.md +0 -85
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-locale.md +0 -291
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix-mobile.md +0 -316
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-keys.md +0 -221
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +0 -617
- package/src/orchestrator/templates/ai-instructions/claude-cmd-design.md +0 -12
- package/src/orchestrator/templates/ai-instructions/claude-skill-api-design.md +0 -62
- package/src/orchestrator/templates/ai-instructions/claude-skill-capture-mode-figma-mcp.md +0 -82
- package/src/orchestrator/templates/ai-instructions/claude-skill-capture-mode-figma-pat.md +0 -117
- package/src/orchestrator/templates/ai-instructions/claude-skill-capture-mode-live.md +0 -60
- package/src/orchestrator/templates/ai-instructions/claude-skill-capture-mode-local.md +0 -38
- package/src/orchestrator/templates/ai-instructions/claude-skill-capture.md +0 -35
- package/src/orchestrator/templates/ai-instructions/claude-skill-dashboard.md +0 -121
- package/src/orchestrator/templates/ai-instructions/claude-skill-delivery.md +0 -141
- package/src/orchestrator/templates/ai-instructions/claude-skill-error-mapping.md +0 -157
- package/src/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +0 -446
- package/src/orchestrator/templates/ai-instructions/claude-skill-ingest-legacy.md +0 -79
- package/src/orchestrator/templates/ai-instructions/claude-skill-mobile-gestures.md +0 -109
- package/src/orchestrator/templates/ai-instructions/claude-skill-selector-fix.md +0 -347
- package/src/orchestrator/templates/ai-instructions/claude-skill-tc-review.md +0 -320
- package/src/orchestrator/templates/ai-instructions/claude-skill-test-design-techniques.md +0 -118
- package/src/orchestrator/templates/ai-instructions/claude-skill-viewpoint-group-a-data-entry.md +0 -203
- package/src/orchestrator/templates/ai-instructions/claude-skill-viewpoint-group-b-data-ops.md +0 -179
- package/src/orchestrator/templates/ai-instructions/claude-skill-viewpoint-group-c-data-explore.md +0 -233
- package/src/orchestrator/templates/ai-instructions/claude-skill-viewpoint-group-d-display.md +0 -226
- package/src/orchestrator/templates/ai-instructions/claude-skill-viewpoint-group-e-identity.md +0 -177
- package/src/orchestrator/templates/ai-instructions/claude-skill-viewpoint.md +0 -118
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-add-flow.md +0 -86
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +0 -131
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +0 -93
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-dashboard.md +0 -65
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-delivery.md +0 -83
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-design.md +0 -13
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-feedback.md +0 -24
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-locale.md +0 -70
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-review.md +0 -38
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +0 -155
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-api-design.md +0 -62
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mobile.md +0 -184
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-figma-mcp.md +0 -82
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-figma-pat.md +0 -117
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-live.md +0 -60
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-local.md +0 -38
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture.md +0 -35
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-dashboard.md +0 -121
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-delivery.md +0 -141
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-error-mapping.md +0 -157
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +0 -446
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-harness-audit.md +0 -85
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-ingest-legacy.md +0 -79
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-locale.md +0 -291
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-mobile-gestures.md +0 -109
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix-mobile.md +0 -316
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-keys.md +0 -221
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +0 -617
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-review.md +0 -320
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-test-design-techniques.md +0 -118
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-viewpoint-group-a-data-entry.md +0 -203
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-viewpoint-group-b-data-ops.md +0 -179
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-viewpoint-group-c-data-explore.md +0 -233
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-viewpoint-group-d-display.md +0 -226
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-viewpoint-group-e-identity.md +0 -177
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-viewpoint.md +0 -118
- /package/dist/orchestrator/templates/{ai-instructions/claude-agent-challenge.md → ai-src/agents/challenge.md} +0 -0
- /package/dist/orchestrator/templates/{ai-instructions/claude-agent-discovery.md → ai-src/agents/discovery.md} +0 -0
- /package/dist/orchestrator/templates/{ai-instructions/claude-agent-generator.md → ai-src/agents/generator.md} +0 -0
- /package/dist/orchestrator/templates/{ai-instructions/claude-agent-reviewer.md → ai-src/agents/reviewer.md} +0 -0
- /package/dist/orchestrator/templates/{ai-instructions/claude-config.md → ai-src/config/claude.md} +0 -0
- /package/dist/orchestrator/templates/{ai-instructions/copilot-config.md → ai-src/config/copilot.md} +0 -0
- /package/dist/orchestrator/templates/{ai-instructions/claude-skill-api-design.md → ai-src/skills/sungen-api-design/SKILL.md} +0 -0
- /package/dist/orchestrator/templates/{ai-instructions/claude-skill-capture.md → ai-src/skills/sungen-capture/SKILL.md} +0 -0
- /package/dist/orchestrator/templates/{ai-instructions/claude-skill-capture-mode-figma-mcp.md → ai-src/skills/sungen-capture/mode-figma-mcp.md} +0 -0
- /package/dist/orchestrator/templates/{ai-instructions/claude-skill-capture-mode-figma-pat.md → ai-src/skills/sungen-capture/mode-figma-pat.md} +0 -0
- /package/dist/orchestrator/templates/{ai-instructions/claude-skill-capture-mode-live.md → ai-src/skills/sungen-capture/mode-live.md} +0 -0
- /package/dist/orchestrator/templates/{ai-instructions/claude-skill-capture-mode-local.md → ai-src/skills/sungen-capture/mode-local.md} +0 -0
- /package/dist/orchestrator/templates/{ai-instructions/claude-skill-dashboard.md → ai-src/skills/sungen-dashboard/SKILL.md} +0 -0
- /package/dist/orchestrator/templates/{ai-instructions/claude-skill-delivery.md → ai-src/skills/sungen-delivery/SKILL.md} +0 -0
- /package/dist/orchestrator/templates/{ai-instructions/claude-skill-gherkin-syntax.md → ai-src/skills/sungen-gherkin-syntax/SKILL.md} +0 -0
- /package/dist/orchestrator/templates/{ai-instructions/claude-skill-ingest-legacy.md → ai-src/skills/sungen-ingest-legacy/SKILL.md} +0 -0
- /package/dist/orchestrator/templates/{ai-instructions/claude-skill-mobile-gestures.md → ai-src/skills/sungen-mobile-gestures/SKILL.md} +0 -0
- /package/dist/orchestrator/templates/{ai-instructions/claude-skill-selector-keys.md → ai-src/skills/sungen-selector-keys/SKILL.md} +0 -0
- /package/dist/orchestrator/templates/{ai-instructions/claude-skill-tc-review.md → ai-src/skills/sungen-tc-review/SKILL.md} +0 -0
- /package/dist/orchestrator/templates/{ai-instructions/claude-skill-test-design-techniques.md → ai-src/skills/sungen-test-design-techniques/SKILL.md} +0 -0
- /package/dist/orchestrator/templates/{ai-instructions/claude-skill-viewpoint.md → ai-src/skills/sungen-viewpoint/SKILL.md} +0 -0
- /package/dist/orchestrator/templates/{ai-instructions/claude-skill-viewpoint-group-a-data-entry.md → ai-src/skills/sungen-viewpoint/group-a-data-entry.md} +0 -0
- /package/dist/orchestrator/templates/{ai-instructions/claude-skill-viewpoint-group-b-data-ops.md → ai-src/skills/sungen-viewpoint/group-b-data-ops.md} +0 -0
- /package/dist/orchestrator/templates/{ai-instructions/claude-skill-viewpoint-group-c-data-explore.md → ai-src/skills/sungen-viewpoint/group-c-data-explore.md} +0 -0
- /package/dist/orchestrator/templates/{ai-instructions/claude-skill-viewpoint-group-d-display.md → ai-src/skills/sungen-viewpoint/group-d-display.md} +0 -0
- /package/dist/orchestrator/templates/{ai-instructions/claude-skill-viewpoint-group-e-identity.md → ai-src/skills/sungen-viewpoint/group-e-identity.md} +0 -0
- /package/src/orchestrator/templates/{ai-instructions/claude-agent-challenge.md → ai-src/agents/challenge.md} +0 -0
- /package/src/orchestrator/templates/{ai-instructions/claude-agent-discovery.md → ai-src/agents/discovery.md} +0 -0
- /package/src/orchestrator/templates/{ai-instructions/claude-agent-generator.md → ai-src/agents/generator.md} +0 -0
- /package/src/orchestrator/templates/{ai-instructions/claude-agent-reviewer.md → ai-src/agents/reviewer.md} +0 -0
- /package/src/orchestrator/templates/{ai-instructions/claude-config.md → ai-src/config/claude.md} +0 -0
- /package/src/orchestrator/templates/{ai-instructions/copilot-config.md → ai-src/config/copilot.md} +0 -0
- /package/{dist/orchestrator/templates/ai-instructions/github-skill-sungen-api-design.md → src/orchestrator/templates/ai-src/skills/sungen-api-design/SKILL.md} +0 -0
- /package/{dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture.md → src/orchestrator/templates/ai-src/skills/sungen-capture/SKILL.md} +0 -0
- /package/{dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-figma-mcp.md → src/orchestrator/templates/ai-src/skills/sungen-capture/mode-figma-mcp.md} +0 -0
- /package/{dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-figma-pat.md → src/orchestrator/templates/ai-src/skills/sungen-capture/mode-figma-pat.md} +0 -0
- /package/{dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-live.md → src/orchestrator/templates/ai-src/skills/sungen-capture/mode-live.md} +0 -0
- /package/{dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-local.md → src/orchestrator/templates/ai-src/skills/sungen-capture/mode-local.md} +0 -0
- /package/{dist/orchestrator/templates/ai-instructions/github-skill-sungen-dashboard.md → src/orchestrator/templates/ai-src/skills/sungen-dashboard/SKILL.md} +0 -0
- /package/{dist/orchestrator/templates/ai-instructions/github-skill-sungen-delivery.md → src/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md} +0 -0
- /package/{dist/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md → src/orchestrator/templates/ai-src/skills/sungen-gherkin-syntax/SKILL.md} +0 -0
- /package/{dist/orchestrator/templates/ai-instructions/github-skill-sungen-ingest-legacy.md → src/orchestrator/templates/ai-src/skills/sungen-ingest-legacy/SKILL.md} +0 -0
- /package/{dist/orchestrator/templates/ai-instructions/github-skill-sungen-mobile-gestures.md → src/orchestrator/templates/ai-src/skills/sungen-mobile-gestures/SKILL.md} +0 -0
- /package/src/orchestrator/templates/{ai-instructions/claude-skill-selector-keys.md → ai-src/skills/sungen-selector-keys/SKILL.md} +0 -0
- /package/{dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-review.md → src/orchestrator/templates/ai-src/skills/sungen-tc-review/SKILL.md} +0 -0
- /package/{dist/orchestrator/templates/ai-instructions/github-skill-sungen-test-design-techniques.md → src/orchestrator/templates/ai-src/skills/sungen-test-design-techniques/SKILL.md} +0 -0
- /package/{dist/orchestrator/templates/ai-instructions/github-skill-sungen-viewpoint.md → src/orchestrator/templates/ai-src/skills/sungen-viewpoint/SKILL.md} +0 -0
- /package/{dist/orchestrator/templates/ai-instructions/github-skill-sungen-viewpoint-group-a-data-entry.md → src/orchestrator/templates/ai-src/skills/sungen-viewpoint/group-a-data-entry.md} +0 -0
- /package/{dist/orchestrator/templates/ai-instructions/github-skill-sungen-viewpoint-group-b-data-ops.md → src/orchestrator/templates/ai-src/skills/sungen-viewpoint/group-b-data-ops.md} +0 -0
- /package/{dist/orchestrator/templates/ai-instructions/github-skill-sungen-viewpoint-group-c-data-explore.md → src/orchestrator/templates/ai-src/skills/sungen-viewpoint/group-c-data-explore.md} +0 -0
- /package/{dist/orchestrator/templates/ai-instructions/github-skill-sungen-viewpoint-group-d-display.md → src/orchestrator/templates/ai-src/skills/sungen-viewpoint/group-d-display.md} +0 -0
- /package/{dist/orchestrator/templates/ai-instructions/github-skill-sungen-viewpoint-group-e-identity.md → src/orchestrator/templates/ai-src/skills/sungen-viewpoint/group-e-identity.md} +0 -0
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Journey board (epic #381, story S1) — the durable, read-only "you are here" view.
|
|
3
|
+
*
|
|
4
|
+
* Reconstructs the QA's position from artifacts ALREADY on disk (no recomputation, no context
|
|
5
|
+
* needed): the audit report (`.sungen/reports/<slug>-audit.json`) supplies the obligation status
|
|
6
|
+
* via its calibration axes + gate + findings; the ledger (`.sungen/ledger/<slug>.jsonl`) supplies
|
|
7
|
+
* the phase history ("you are here"). The output answers the three QA questions — what's next /
|
|
8
|
+
* what to review / what's doubtful — and persists `.sungen/journey/<slug>.{json,board.md}`.
|
|
9
|
+
*
|
|
10
|
+
* S1 = the read-only synthesis. S2 (this file) adds the **writable lifecycle**: persisted
|
|
11
|
+
* waivers (reason-required, anti-amnesia), reconcile (auto-close satisfied; re-surface a waiver
|
|
12
|
+
* when its evidence changed), via `runJourney` + `waive`. Gate-bound predicates + inter-phase
|
|
13
|
+
* gates are S3. Pure-deterministic, no AI.
|
|
14
|
+
*/
|
|
15
|
+
import * as fs from 'fs';
|
|
16
|
+
import * as path from 'path';
|
|
17
|
+
import * as crypto from 'crypto';
|
|
18
|
+
import { reportSlug } from './unit-paths';
|
|
19
|
+
|
|
20
|
+
export type ObStatus = 'satisfied' | 'needs-work' | 'pending' | 'waived';
|
|
21
|
+
|
|
22
|
+
export interface Obligation {
|
|
23
|
+
id: string;
|
|
24
|
+
title: string;
|
|
25
|
+
status: ObStatus;
|
|
26
|
+
detail: string;
|
|
27
|
+
waivedReason?: string; // S2 — set when the QA explicitly waived this obligation
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** S5 — a parsed anchor so an IDE can jump straight to what needs review. */
|
|
31
|
+
export interface Anchor { vpId?: string; file?: string; line?: number; }
|
|
32
|
+
export interface ReviewItem { text: string; anchor: Anchor; }
|
|
33
|
+
|
|
34
|
+
export interface JourneyReport {
|
|
35
|
+
unit: string;
|
|
36
|
+
generatedFrom: string[]; // which artifacts were available
|
|
37
|
+
youAreHere: string; // last ledger phase, or 'not started'
|
|
38
|
+
phasesDone: string[];
|
|
39
|
+
gateStatus: string | null;
|
|
40
|
+
score: number | null;
|
|
41
|
+
obligations: Obligation[];
|
|
42
|
+
needsYou: ReviewItem[]; // S5 — the review queue: human-judgment findings, each with an anchor
|
|
43
|
+
nextSuggested: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function readJSON(p: string): any {
|
|
47
|
+
try { return fs.existsSync(p) ? JSON.parse(fs.readFileSync(p, 'utf-8')) : null; } catch { return null; }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function readLedgerPhases(p: string): string[] {
|
|
51
|
+
if (!fs.existsSync(p)) return [];
|
|
52
|
+
const out: string[] = [];
|
|
53
|
+
for (const line of fs.readFileSync(p, 'utf-8').split('\n')) {
|
|
54
|
+
if (!line.trim()) continue;
|
|
55
|
+
try { const d = JSON.parse(line); if (d.step) out.push(String(d.step)); } catch { /* skip */ }
|
|
56
|
+
}
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Findings that ask for HUMAN judgment (the review-queue seed), vs pure machine-repair targets. */
|
|
61
|
+
function isHumanFinding(f: string): boolean {
|
|
62
|
+
return /@manual|MANUAL-|DEPTH-DEFERRED|UNSOURCEABLE|CAPABILITY-SUGGESTION|judgment|oracle|review/i.test(f);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** S5 — pull an IDE-jumpable anchor out of a finding (VP id, scenario, or `file.ext:line`). */
|
|
66
|
+
function extractAnchor(text: string): Anchor {
|
|
67
|
+
const vp = text.match(/\bVP-[A-Z0-9-]+\b/);
|
|
68
|
+
const fl = text.match(/([\w./-]+\.(?:md|feature|ts|yaml)):(\d+)/);
|
|
69
|
+
return { vpId: vp ? vp[0] : undefined, file: fl ? fl[1] : undefined, line: fl ? Number(fl[2]) : undefined };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const SAT = 0.8; // axis at/above this = satisfied (below = needs-work)
|
|
73
|
+
|
|
74
|
+
function computeFresh(projectRoot: string, unit: string): JourneyReport {
|
|
75
|
+
const slug = reportSlug(unit);
|
|
76
|
+
const audit = readJSON(path.join(projectRoot, '.sungen', 'reports', `${slug}-audit.json`));
|
|
77
|
+
const phases = readLedgerPhases(path.join(projectRoot, '.sungen', 'ledger', `${slug}.jsonl`));
|
|
78
|
+
|
|
79
|
+
const from: string[] = [];
|
|
80
|
+
if (audit) from.push('audit');
|
|
81
|
+
if (phases.length) from.push('ledger');
|
|
82
|
+
|
|
83
|
+
const youAreHere = phases.length ? phases[phases.length - 1] : 'not started';
|
|
84
|
+
const ran = (p: string) => phases.some((s) => s === p || s.startsWith(p));
|
|
85
|
+
|
|
86
|
+
const obligations: Obligation[] = [];
|
|
87
|
+
const needsYou: ReviewItem[] = [];
|
|
88
|
+
|
|
89
|
+
if (!audit) {
|
|
90
|
+
// Nothing measured yet — the journey hasn't really begun.
|
|
91
|
+
obligations.push({ id: 'OB-create', title: 'Generate test cases', status: 'pending', detail: 'No audit report yet — run /sungen:create-test.' });
|
|
92
|
+
return {
|
|
93
|
+
unit, generatedFrom: from, youAreHere, phasesDone: phases, gateStatus: null, score: null,
|
|
94
|
+
obligations, needsYou, nextSuggested: 'Run `/sungen:create-test ' + unit + '` to begin.',
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const ax = (audit.calibration && audit.calibration.axes) || {};
|
|
99
|
+
const depthThreshold = (audit.depth && audit.depth.threshold) || SAT;
|
|
100
|
+
const ob = (id: string, title: string, val: number | undefined, thr: number, detail: string): Obligation => ({
|
|
101
|
+
id, title,
|
|
102
|
+
status: val === undefined ? 'pending' : val >= thr ? 'satisfied' : 'needs-work',
|
|
103
|
+
detail: val === undefined ? detail : `${Math.round(val * 100)}% (need ${Math.round(thr * 100)}%) — ${detail}`,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
obligations.push(ob('OB-spec', 'Spec readiness', ax.specFR, 1, 'FR/sections sufficient to design from'));
|
|
107
|
+
obligations.push(ob('OB-coverage', 'Viewpoint coverage', ax.coverage, SAT, 'critical themes covered'));
|
|
108
|
+
obligations.push(ob('OB-depth', 'Assertion depth', ax.businessDepth, depthThreshold, 'business-critical scenarios assert data'));
|
|
109
|
+
obligations.push(ob('OB-trace', 'Traceability', ax.taxonomy ?? ax.traceability, SAT, 'scenarios trace to viewpoints'));
|
|
110
|
+
|
|
111
|
+
// Automation obligation: automatable @manual still pending a driver = needs-work.
|
|
112
|
+
const autoPending = audit.automatableManual && audit.automatableManual.automatable > 0;
|
|
113
|
+
obligations.push({
|
|
114
|
+
id: 'OB-automation', title: 'Automation coverage',
|
|
115
|
+
status: autoPending ? 'needs-work' : 'satisfied',
|
|
116
|
+
detail: autoPending ? `${audit.automatableManual.automatable} @manual scenario(s) a driver could automate` : 'no capability-pending automatable cases',
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Needs-you: the review queue. Prefer PRECISE per-scenario anchors from audit's structured
|
|
120
|
+
// depth data — each shallow business-critical scenario carries its own VP id — over regex-parsing
|
|
121
|
+
// the aggregate "DEPTH:" finding string (which names no scenario, so points nowhere). Then
|
|
122
|
+
// supplement with the remaining human-judgment findings.
|
|
123
|
+
const shallowBC: { name: string; category?: string }[] = (audit.depth && audit.depth.shallowBusinessCritical) || [];
|
|
124
|
+
for (const s of shallowBC) {
|
|
125
|
+
needsYou.push({
|
|
126
|
+
text: `DEEPEN — "${s.name}": assert data, not just visibility (\`… with {{value}}\` / \`see all … contain {{v}}\`), or defer cross-screen with @manual + reason.`,
|
|
127
|
+
anchor: extractAnchor(s.name),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
const haveStructuredDepth = shallowBC.length > 0;
|
|
131
|
+
for (const f of (audit.findings || [])) {
|
|
132
|
+
if (!isHumanFinding(f)) continue;
|
|
133
|
+
if (haveStructuredDepth && /^\s*DEPTH\b/i.test(f)) continue; // superseded by the per-scenario items above
|
|
134
|
+
needsYou.push({ text: f, anchor: extractAnchor(f) });
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Human sign-off: always pending until S5 explicit signoff; carries the now-final review-queue count.
|
|
138
|
+
const manualCount = (audit.manualOracle && (audit.manualOracle.manualTotal ?? audit.manualOracle.total)) || 0;
|
|
139
|
+
obligations.push({
|
|
140
|
+
id: 'OB-signoff', title: 'Human sign-off', status: 'pending',
|
|
141
|
+
detail: `${needsYou.length} item(s) queued for your review · ${manualCount} @manual scenario(s)`,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// Next suggested = first non-satisfied obligation; if all satisfied but run-test not done → run.
|
|
145
|
+
const firstGap = obligations.find((o) => o.status !== 'satisfied' && o.id !== 'OB-signoff');
|
|
146
|
+
let nextSuggested: string;
|
|
147
|
+
if (firstGap) nextSuggested = `Repair "${firstGap.title}" (${firstGap.detail}).`;
|
|
148
|
+
else if (!ran('run')) nextSuggested = `Quality satisfied — run \`/sungen:run-test ${unit}\`.`;
|
|
149
|
+
else nextSuggested = `All obligations satisfied — review the ${needsYou.length} queued item(s), then sign off & deliver.`;
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
unit, generatedFrom: from, youAreHere, phasesDone: phases,
|
|
153
|
+
gateStatus: audit.gateStatus ?? null, score: (audit.score && audit.score.overall) ?? null,
|
|
154
|
+
obligations, needsYou, nextSuggested,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ---------------- S2: writable lifecycle — persisted waivers + reconcile ----------------
|
|
159
|
+
|
|
160
|
+
interface Waiver { reason: string; at: string; auditHashAtWaive: string; }
|
|
161
|
+
interface Signoff { by?: string; at: string; auditHashAtSignoff: string; } // S5
|
|
162
|
+
interface JourneyState { unit: string; auditHash: string; waivers: Record<string, Waiver>; signoff?: Signoff; }
|
|
163
|
+
|
|
164
|
+
function statePath(projectRoot: string, slug: string): string {
|
|
165
|
+
return path.join(projectRoot, '.sungen', 'journey', `${slug}.state.json`);
|
|
166
|
+
}
|
|
167
|
+
/** Evidence cursor: the audit report's content hash. A waiver is invalidated when this changes. */
|
|
168
|
+
function auditHashOf(projectRoot: string, slug: string): string {
|
|
169
|
+
const p = path.join(projectRoot, '.sungen', 'reports', `${slug}-audit.json`);
|
|
170
|
+
return fs.existsSync(p) ? crypto.createHash('sha256').update(fs.readFileSync(p)).digest('hex') : '';
|
|
171
|
+
}
|
|
172
|
+
function loadState(p: string): JourneyState | null { return readJSON(p); }
|
|
173
|
+
function saveState(p: string, s: JourneyState): void {
|
|
174
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
175
|
+
fs.writeFileSync(p, JSON.stringify(s, null, 2), 'utf-8');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Recompute nextSuggested AFTER waivers are applied (a waived obligation is not a gap). */
|
|
179
|
+
function computeNext(r: JourneyReport, unit: string): string {
|
|
180
|
+
const gap = r.obligations.find((o) => o.status !== 'satisfied' && o.status !== 'waived' && o.id !== 'OB-signoff');
|
|
181
|
+
if (gap) return `Repair "${gap.title}" (${gap.detail}).`;
|
|
182
|
+
if (!r.phasesDone.some((s) => s === 'run' || s.startsWith('run'))) return `Quality satisfied — run \`/sungen:run-test ${unit}\`.`;
|
|
183
|
+
return `All obligations satisfied/waived — review the ${r.needsYou.length} queued item(s), then sign off & deliver.`;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* The public entry: compute fresh, then RECONCILE with the persisted state —
|
|
188
|
+
* - auto-close is automatic (fresh recompute reflects the current artifacts);
|
|
189
|
+
* - an active waiver (evidence unchanged) sets status='waived' (carries the reason);
|
|
190
|
+
* - a STALE waiver (audit changed since it was waived) is re-surfaced for re-decision (anti-amnesia).
|
|
191
|
+
* Then persist the current evidence cursor.
|
|
192
|
+
*/
|
|
193
|
+
export function runJourney(projectRoot: string, unit: string): JourneyReport {
|
|
194
|
+
const slug = reportSlug(unit);
|
|
195
|
+
const report = computeFresh(projectRoot, unit);
|
|
196
|
+
const sp = statePath(projectRoot, slug);
|
|
197
|
+
const state = loadState(sp) || { unit, auditHash: '', waivers: {} };
|
|
198
|
+
const curHash = auditHashOf(projectRoot, slug);
|
|
199
|
+
|
|
200
|
+
for (const ob of report.obligations) {
|
|
201
|
+
const w = state.waivers[ob.id];
|
|
202
|
+
if (!w) continue;
|
|
203
|
+
if (w.auditHashAtWaive === curHash) {
|
|
204
|
+
ob.status = 'waived';
|
|
205
|
+
ob.waivedReason = w.reason;
|
|
206
|
+
ob.detail = `waived — ${w.reason}`;
|
|
207
|
+
} else {
|
|
208
|
+
report.needsYou.unshift({ text: `⚠️ Waiver on "${ob.title}" is STALE (evidence changed since ${w.at}) — re-decide. Was: ${w.reason}`, anchor: { vpId: ob.id } });
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
// S5 — sign-off reconcile: a recorded sign-off satisfies OB-signoff while its evidence holds;
|
|
212
|
+
// if the audit changed since, the sign-off is STALE → re-surface for re-review (anti-amnesia).
|
|
213
|
+
const signoffOb = report.obligations.find((o) => o.id === 'OB-signoff');
|
|
214
|
+
if (signoffOb && state.signoff) {
|
|
215
|
+
if (state.signoff.auditHashAtSignoff === curHash) {
|
|
216
|
+
signoffOb.status = 'satisfied';
|
|
217
|
+
signoffOb.detail = `signed off${state.signoff.by ? ' by ' + state.signoff.by : ''} at ${state.signoff.at}`;
|
|
218
|
+
} else {
|
|
219
|
+
report.needsYou.unshift({ text: `⚠️ Sign-off is STALE (suite changed since ${state.signoff.at}) — re-review and sign off again.`, anchor: { vpId: 'OB-signoff' } });
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
report.nextSuggested = computeNext(report, unit);
|
|
223
|
+
saveState(sp, { unit, auditHash: curHash, waivers: state.waivers, signoff: state.signoff });
|
|
224
|
+
return report;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Waive an obligation — REQUIRES a reason (anti-amnesia: a waiver leaves a recorded "why").
|
|
229
|
+
* Records the current evidence cursor so reconcile can invalidate it if the audit changes.
|
|
230
|
+
*/
|
|
231
|
+
export function waive(projectRoot: string, unit: string, obId: string, reason: string): JourneyReport {
|
|
232
|
+
if (!reason || !reason.trim()) {
|
|
233
|
+
throw new Error('A reason is required to waive (anti-amnesia: a waiver must record WHY). Use --reason "...".');
|
|
234
|
+
}
|
|
235
|
+
const slug = reportSlug(unit);
|
|
236
|
+
const fresh = computeFresh(projectRoot, unit);
|
|
237
|
+
const valid = fresh.obligations.map((o) => o.id);
|
|
238
|
+
if (!valid.includes(obId)) {
|
|
239
|
+
throw new Error(`Unknown obligation "${obId}". Valid: ${valid.join(', ')}`);
|
|
240
|
+
}
|
|
241
|
+
const sp = statePath(projectRoot, slug);
|
|
242
|
+
const state = loadState(sp) || { unit, auditHash: '', waivers: {} };
|
|
243
|
+
state.waivers[obId] = { reason: reason.trim(), at: new Date().toISOString(), auditHashAtWaive: auditHashOf(projectRoot, slug) };
|
|
244
|
+
saveState(sp, state);
|
|
245
|
+
return runJourney(projectRoot, unit);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Sign off — the single human gate (S5). Allowed ONLY when every other deliver-required
|
|
250
|
+
* obligation is satisfied or waived (you cannot sign off an unclean suite). Records who/when +
|
|
251
|
+
* the evidence cursor; reconcile invalidates it (stale → re-review) if the suite later changes.
|
|
252
|
+
*/
|
|
253
|
+
export function signoff(projectRoot: string, unit: string, by?: string): JourneyReport {
|
|
254
|
+
const before = runJourney(projectRoot, unit);
|
|
255
|
+
const blockers = before.obligations.filter((o) => o.id !== 'OB-signoff' && o.status !== 'satisfied' && o.status !== 'waived');
|
|
256
|
+
if (blockers.length) {
|
|
257
|
+
throw new Error(`Cannot sign off — still open: ${blockers.map((b) => b.id).join(', ')}. Fix, or waive each with a reason, first.`);
|
|
258
|
+
}
|
|
259
|
+
const slug = reportSlug(unit);
|
|
260
|
+
const sp = statePath(projectRoot, slug);
|
|
261
|
+
const state = loadState(sp) || { unit, auditHash: '', waivers: {} };
|
|
262
|
+
state.signoff = { by, at: new Date().toISOString(), auditHashAtSignoff: auditHashOf(projectRoot, slug) };
|
|
263
|
+
saveState(sp, state);
|
|
264
|
+
return runJourney(projectRoot, unit);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const ICON: Record<ObStatus, string> = { satisfied: '✅', 'needs-work': '⚠️ ', pending: '⏳', waived: '🚫' };
|
|
268
|
+
|
|
269
|
+
export function renderJourneyBoard(r: JourneyReport): string {
|
|
270
|
+
const L: string[] = [];
|
|
271
|
+
L.push(`# Journey — ${r.unit}`);
|
|
272
|
+
L.push('');
|
|
273
|
+
L.push(`📍 **You are here:** ${r.youAreHere}` + (r.phasesDone.length ? ` (phases: ${r.phasesDone.join(' → ')})` : ''));
|
|
274
|
+
if (r.gateStatus) L.push(` gate: **${r.gateStatus}** · score: ${r.score ?? '—'}/10`);
|
|
275
|
+
L.push('');
|
|
276
|
+
L.push('## What must still be true');
|
|
277
|
+
for (const o of r.obligations) L.push(`- ${ICON[o.status]} **${o.title}** — ${o.detail}`);
|
|
278
|
+
L.push('');
|
|
279
|
+
L.push(`## 🔎 Review queue — needs your judgment (${r.needsYou.length})`);
|
|
280
|
+
if (r.needsYou.length) {
|
|
281
|
+
for (const it of r.needsYou.slice(0, 20)) {
|
|
282
|
+
const a = it.anchor;
|
|
283
|
+
const tag = [a.vpId, a.file && `${a.file}${a.line ? ':' + a.line : ''}`].filter(Boolean).join(' · ');
|
|
284
|
+
L.push(`- ${it.text}${tag ? ` 〔${tag}〕` : ''}`);
|
|
285
|
+
}
|
|
286
|
+
} else L.push('- (nothing queued)');
|
|
287
|
+
L.push('');
|
|
288
|
+
L.push(`## → Next`);
|
|
289
|
+
L.push(`${r.nextSuggested}`);
|
|
290
|
+
L.push('');
|
|
291
|
+
return L.join('\n');
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// ---------------- S3: inter-phase gate — obligations as HALT predicates (#398) ----------------
|
|
295
|
+
//
|
|
296
|
+
// A phase boundary is a deterministic gate: before the next phase may run, this phase's required
|
|
297
|
+
// obligations must each be `satisfied` OR `waived` (S2 — an explicit, reasoned human acceptance).
|
|
298
|
+
// A required obligation still `needs-work`/`pending` is a BLOCKER → HALT (no silent bad output
|
|
299
|
+
// crosses the boundary, §9). Reuses runJourney → obligations already reflect waivers/reconcile.
|
|
300
|
+
|
|
301
|
+
export type GatePhase = 'create' | 'run' | 'deliver';
|
|
302
|
+
|
|
303
|
+
export interface GateVerdict {
|
|
304
|
+
unit: string;
|
|
305
|
+
phase: GatePhase;
|
|
306
|
+
status: 'pass' | 'halt';
|
|
307
|
+
required: string[];
|
|
308
|
+
blockers: { id: string; title: string; detail: string }[];
|
|
309
|
+
waivedCredit: { id: string; title: string }[]; // required obligations accepted via an explicit waiver
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const PHASE_REQUIRED: Record<GatePhase, string[]> = {
|
|
313
|
+
// post-create (design quality): spec + coverage + depth + traceability must hold.
|
|
314
|
+
create: ['OB-spec', 'OB-coverage', 'OB-depth', 'OB-trace'],
|
|
315
|
+
// post-run: the design gates + automation coverage.
|
|
316
|
+
run: ['OB-spec', 'OB-coverage', 'OB-depth', 'OB-trace', 'OB-automation'],
|
|
317
|
+
// pre-delivery: everything automated PLUS the single human sign-off (S5).
|
|
318
|
+
deliver: ['OB-spec', 'OB-coverage', 'OB-depth', 'OB-trace', 'OB-automation', 'OB-signoff'],
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
export function runGate(projectRoot: string, unit: string, phase: GatePhase): GateVerdict {
|
|
322
|
+
const r = runJourney(projectRoot, unit);
|
|
323
|
+
const required = PHASE_REQUIRED[phase];
|
|
324
|
+
const reqObs = r.obligations.filter((o) => required.includes(o.id));
|
|
325
|
+
const blockers = reqObs
|
|
326
|
+
.filter((o) => o.status !== 'satisfied' && o.status !== 'waived')
|
|
327
|
+
.map((o) => ({ id: o.id, title: o.title, detail: o.detail }));
|
|
328
|
+
const waivedCredit = reqObs.filter((o) => o.status === 'waived').map((o) => ({ id: o.id, title: o.title }));
|
|
329
|
+
return { unit, phase, status: blockers.length ? 'halt' : 'pass', required, blockers, waivedCredit };
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
export function renderGate(v: GateVerdict): string {
|
|
333
|
+
const L: string[] = [];
|
|
334
|
+
L.push('');
|
|
335
|
+
L.push(`━━━ Gate: ${v.unit} @ phase "${v.phase}" → ${v.status === 'pass' ? '✅ PASS' : '⛔ HALT'} ━━━`);
|
|
336
|
+
if (v.blockers.length) {
|
|
337
|
+
L.push(' Blocking obligations (must be satisfied or explicitly waived):');
|
|
338
|
+
for (const b of v.blockers) L.push(` • ${b.id} ${b.title} — ${b.detail}`);
|
|
339
|
+
L.push(' → Self-correct (repair / run-test), or `sungen journey --screen ' + v.unit + ' --waive <OB> --reason "..."` if accepted.');
|
|
340
|
+
} else {
|
|
341
|
+
L.push(' All required obligations satisfied' + (v.waivedCredit.length ? ` (${v.waivedCredit.length} accepted via waiver)` : '') + '.');
|
|
342
|
+
}
|
|
343
|
+
L.push('');
|
|
344
|
+
return L.join('\n');
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// ---------------- #409: project-level roll-up across all units ----------------
|
|
348
|
+
//
|
|
349
|
+
// Multi-unit fan-out PARALLELISES generation but human review/sign-off must stay SERIAL — you can't
|
|
350
|
+
// judge N boards at once. This roll-up is that serial surface: one row per unit (gate · score · phase
|
|
351
|
+
// · what it still needs), and a single "review this next" pointer. The per-unit board is unchanged;
|
|
352
|
+
// this only aggregates the same read-only `runJourney` synthesis.
|
|
353
|
+
|
|
354
|
+
export type UnitKind = 'screen' | 'flow' | 'api';
|
|
355
|
+
export interface UnitSummary {
|
|
356
|
+
unit: string;
|
|
357
|
+
kind: UnitKind;
|
|
358
|
+
score: number | null;
|
|
359
|
+
gateStatus: string | null;
|
|
360
|
+
youAreHere: string;
|
|
361
|
+
obligationsOpen: number; // non-satisfied, non-waived (excludes OB-signoff)
|
|
362
|
+
reviewQueue: number;
|
|
363
|
+
signedOff: boolean;
|
|
364
|
+
started: boolean; // has an audit report yet
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/** Discover every unit under qa/screens, qa/flows, and qa/api top-level areas. */
|
|
368
|
+
export function listUnits(projectRoot: string): { unit: string; kind: UnitKind }[] {
|
|
369
|
+
const out: { unit: string; kind: UnitKind }[] = [];
|
|
370
|
+
const scan = (sub: string, kind: UnitKind) => {
|
|
371
|
+
const d = path.join(projectRoot, 'qa', sub);
|
|
372
|
+
if (!fs.existsSync(d)) return;
|
|
373
|
+
for (const e of fs.readdirSync(d, { withFileTypes: true })) {
|
|
374
|
+
// 'flows' under qa/api is a container for api-flows (a nested tier) — not a unit itself.
|
|
375
|
+
if (e.isDirectory() && !(sub === 'api' && e.name === 'flows')) out.push({ unit: e.name, kind });
|
|
376
|
+
}
|
|
377
|
+
};
|
|
378
|
+
scan('screens', 'screen');
|
|
379
|
+
scan('flows', 'flow');
|
|
380
|
+
scan('api', 'api');
|
|
381
|
+
return out;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/** Roll up every unit's journey into one table of summaries (serial review surface). */
|
|
385
|
+
export function rollupJourney(projectRoot: string): UnitSummary[] {
|
|
386
|
+
return listUnits(projectRoot).map(({ unit, kind }) => {
|
|
387
|
+
const r = runJourney(projectRoot, unit);
|
|
388
|
+
const signedOff = r.obligations.find((o) => o.id === 'OB-signoff')?.status === 'satisfied';
|
|
389
|
+
const obligationsOpen = r.obligations.filter(
|
|
390
|
+
(o) => o.id !== 'OB-signoff' && o.status !== 'satisfied' && o.status !== 'waived',
|
|
391
|
+
).length;
|
|
392
|
+
return {
|
|
393
|
+
unit, kind, score: r.score, gateStatus: r.gateStatus, youAreHere: r.youAreHere,
|
|
394
|
+
obligationsOpen, reviewQueue: r.needsYou.length, signedOff,
|
|
395
|
+
started: r.generatedFrom.includes('audit'),
|
|
396
|
+
};
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function unitStatusLabel(r: UnitSummary): string {
|
|
401
|
+
if (!r.started) return '◻️ not started';
|
|
402
|
+
if (r.signedOff) return '✅ signed off';
|
|
403
|
+
if (r.obligationsOpen > 0) return `⏳ ${r.obligationsOpen} obligation(s) open`;
|
|
404
|
+
if (r.reviewQueue > 0) return `🔎 review ${r.reviewQueue} item(s)`;
|
|
405
|
+
return '🟡 ready to sign off';
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
export function renderRollupBoard(rows: UnitSummary[]): string {
|
|
409
|
+
const L: string[] = [];
|
|
410
|
+
L.push('# Journey roll-up — all units');
|
|
411
|
+
L.push('');
|
|
412
|
+
if (!rows.length) {
|
|
413
|
+
L.push('No units found under `qa/screens`, `qa/flows`, or `qa/api`.');
|
|
414
|
+
return L.join('\n');
|
|
415
|
+
}
|
|
416
|
+
L.push('| Unit | Kind | Score | Gate | Phase | Needs |');
|
|
417
|
+
L.push('|---|---|---|---|---|---|');
|
|
418
|
+
for (const r of rows) {
|
|
419
|
+
L.push(`| ${r.unit} | ${r.kind} | ${r.score ?? '—'} | ${r.gateStatus ?? '—'} | ${r.youAreHere} | ${unitStatusLabel(r)} |`);
|
|
420
|
+
}
|
|
421
|
+
L.push('');
|
|
422
|
+
const blocked = rows.filter((r) => r.started && (r.obligationsOpen > 0));
|
|
423
|
+
const toReview = rows.filter((r) => r.started && r.obligationsOpen === 0 && !r.signedOff && r.reviewQueue >= 0);
|
|
424
|
+
const notStarted = rows.filter((r) => !r.started);
|
|
425
|
+
L.push(`**${rows.filter((r) => r.signedOff).length}/${rows.length} signed off** · ${blocked.length} with open obligations · ${notStarted.length} not started.`);
|
|
426
|
+
L.push('');
|
|
427
|
+
L.push('## → Next (review is serial — one unit at a time)');
|
|
428
|
+
const next = blocked[0] || toReview[0] || notStarted[0];
|
|
429
|
+
if (!next) L.push('All units signed off — `sungen delivery` to export.');
|
|
430
|
+
else if (!next.started) L.push(`Generate **${next.unit}** — \`/sungen:create-test ${next.unit}\`.`);
|
|
431
|
+
else L.push(`Review **${next.unit}** (${unitStatusLabel(next)}) — \`sungen journey --screen ${next.unit}\`, then sign off.`);
|
|
432
|
+
L.push('');
|
|
433
|
+
return L.join('\n');
|
|
434
|
+
}
|
|
@@ -42,6 +42,26 @@ export function isolationRisk(featureText: string, stateful: boolean): boolean {
|
|
|
42
42
|
return !/@cleanup:cart\b|@isolate\b|empty cart|fresh (?:browser )?context|new context/i.test(featureText);
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* #414 — the complement of isolationRisk. Per-scenario isolation is the DEFAULT now, so untagged
|
|
47
|
+
* suites can't cascade. `@serial` opts INTO a shared-page `test.describe.serial` suite, whose
|
|
48
|
+
* Playwright semantics SKIP every test after the first failure. That is correct ONLY for a genuinely
|
|
49
|
+
* stateful ordered sequence; for INDEPENDENT scenarios — each re-establishing its own start state via
|
|
50
|
+
* a navigating Background — `@serial` buys no correctness, only a cascade that *masks* failures (one
|
|
51
|
+
* early red turns the rest into greens-by-omission). Returns the scenario count when `@serial` should
|
|
52
|
+
* be dropped (the default isolation is safer), else 0.
|
|
53
|
+
*
|
|
54
|
+
* Conservative on purpose — only fires on `@serial` suites whose every scenario provably restarts (a
|
|
55
|
+
* `Background` that navigates `on [..] page`), so a real stateful sequence keeps its `@serial`.
|
|
56
|
+
*/
|
|
57
|
+
export function serialCascadeRisk(featureText: string, scenarioCount: number): number {
|
|
58
|
+
if (!/@serial\b/i.test(featureText)) return 0; // isolated by default → no cascade
|
|
59
|
+
if (scenarioCount < 8) return 0; // small blast radius — not worth the churn
|
|
60
|
+
const navBackground = /Background:[\s\S]*?\bon \[[^\]]+\] page\b/i.test(featureText);
|
|
61
|
+
if (!navBackground) return 0; // no per-scenario reset → genuinely stateful; @serial is right
|
|
62
|
+
return scenarioCount;
|
|
63
|
+
}
|
|
64
|
+
|
|
45
65
|
export function downstreamScope(specText: string, scenarios: ScenarioInfo[]): DownstreamResult {
|
|
46
66
|
const routes = downstreamRoutes(specText);
|
|
47
67
|
const underCovered: { route: string; slug: string }[] = [];
|
package/src/harness/sensors.ts
CHANGED
|
@@ -59,9 +59,17 @@ export function loadCatalog(): Catalog {
|
|
|
59
59
|
return parseYaml(fs.readFileSync(p, 'utf-8')) as Catalog;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
// Word-aware keyword match: `\b<kw>(s|es|ed|ing)?\b`, so a keyword matches whole words (plus
|
|
63
|
+
// simple plural/verb suffixes) but NOT as a substring of a larger word — e.g. `form` must not
|
|
64
|
+
// match inside `Format` or `@platform`, while `product` still matches `products`. Multi-word
|
|
65
|
+
// keywords ("add to cart", "is hidden") match the phrase with a boundary at each end.
|
|
66
|
+
const wordRe = (kw: string) => {
|
|
67
|
+
const esc = kw.toLowerCase().replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
68
|
+
return new RegExp(`\\b${esc}(?:s|es|ed|ing)?\\b`);
|
|
69
|
+
};
|
|
62
70
|
const has = (haystacks: string[], kw: string) => {
|
|
63
|
-
const
|
|
64
|
-
return haystacks.some((h) =>
|
|
71
|
+
const re = wordRe(kw);
|
|
72
|
+
return haystacks.some((h) => re.test(h));
|
|
65
73
|
};
|
|
66
74
|
|
|
67
75
|
// ---------- Sensor 1: Viewpoint Gate (catalog-driven) ----------
|
|
@@ -75,16 +83,21 @@ export interface GateResult {
|
|
|
75
83
|
universalGaps: string[];
|
|
76
84
|
}
|
|
77
85
|
|
|
78
|
-
export function viewpointGate(scenarios: ScenarioInfo[], viewpoints: ViewpointEntry[], catalog: Catalog): GateResult {
|
|
86
|
+
export function viewpointGate(scenarios: ScenarioInfo[], viewpoints: ViewpointEntry[], catalog: Catalog, isMobile = false): GateResult {
|
|
79
87
|
const haystacks = [
|
|
80
88
|
...scenarios.map((s) => s.haystack),
|
|
81
89
|
...viewpoints.map((v) => `${v.id} ${v.reason}`.toLowerCase()),
|
|
82
90
|
];
|
|
83
91
|
|
|
84
|
-
// Detect page-type by detect_keywords hit count
|
|
92
|
+
// Detect page-type by detect_keywords hit count. `mobile-*` page-types are candidates ONLY for
|
|
93
|
+
// mobile targets; web targets never consider them. Mobile targets still consider the web types
|
|
94
|
+
// too, so a mobile commerce/form screen is selected as ecommerce-list/form by keyword fit —
|
|
95
|
+
// `mobile-home` does not crowd out a better-fitting type. With no type matching → pageType stays
|
|
96
|
+
// null → no themes required → the gate passes leniently rather than false-FAILing.
|
|
85
97
|
let pageType: string | null = null;
|
|
86
98
|
let best = 0;
|
|
87
99
|
for (const [pt, def] of Object.entries(catalog.page_types)) {
|
|
100
|
+
if (pt.startsWith('mobile-') && !isMobile) continue;
|
|
88
101
|
const hits = def.detect_keywords.filter((k) => has(haystacks, k)).length;
|
|
89
102
|
if (hits > best) { best = hits; pageType = pt; }
|
|
90
103
|
}
|
|
@@ -97,10 +110,14 @@ export function viewpointGate(scenarios: ScenarioInfo[], viewpoints: ViewpointEn
|
|
|
97
110
|
// Scenarios that cover this theme by keyword (include @manual — a manual
|
|
98
111
|
// scenario with a real assertion still covers the viewpoint for design).
|
|
99
112
|
const coverers = scenarios.filter((s) => t.keywords.some((k) => s.haystack.includes(k.toLowerCase())));
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
113
|
+
if (coverers.length === 0) { gaps.push({ theme: t.theme, keywords: t.keywords, status: 'missing' }); continue; }
|
|
114
|
+
// A theme WITHOUT `depth:` is visibility/navigation — landing/seeing/leaving IS the
|
|
115
|
+
// assertion, so mere presence covers it. Only DATA-correctness themes (depth.requires:
|
|
116
|
+
// data-assertion) need a data assertion to count as deeply covered.
|
|
117
|
+
const needsDepth = t.depth?.requires === 'data-assertion';
|
|
118
|
+
if (!needsDepth) { covered++; continue; }
|
|
119
|
+
if (coverers.some((s) => s.hasDataAssertion)) covered++;
|
|
120
|
+
else gaps.push({ theme: t.theme, keywords: t.keywords, status: 'shallow' });
|
|
104
121
|
}
|
|
105
122
|
}
|
|
106
123
|
|