@sun-asterisk/sungen 3.2.2-beta.9 → 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/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.map +1 -1
- package/dist/cli/commands/journey.js +24 -2
- package/dist/cli/commands/journey.js.map +1 -1
- 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/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 +37 -1
- package/dist/harness/journey.d.ts.map +1 -1
- package/dist/harness/journey.js +146 -19
- package/dist/harness/journey.js.map +1 -1
- 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 -5
- 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/dist/orchestrator/templates/{ai-instructions/claude-cmd-run-test.md → ai-src/commands/run-test.md} +116 -8
- 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} +31 -10
- 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/{src/orchestrator/templates/ai-instructions/claude-skill-selector-fix-mobile.md → dist/orchestrator/templates/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/generate.ts +2 -1
- package/src/cli/commands/init.ts +50 -3
- package/src/cli/commands/journey.ts +20 -3
- package/src/cli/commands/update.ts +14 -4
- 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 +167 -19
- 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 -5
- 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/src/orchestrator/templates/{ai-instructions/claude-cmd-run-test.md → ai-src/commands/run-test.md} +116 -8
- 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} +31 -10
- 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/{dist/orchestrator/templates/ai-instructions/claude-skill-selector-fix-mobile.md → src/orchestrator/templates/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 -94
- 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 -158
- 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 -176
- 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 -94
- 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 -158
- 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 -176
- 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
|
@@ -10,8 +10,12 @@
|
|
|
10
10
|
* load first, then `LOCAL_DRIVERS` (db → api), then `core` — i.e. the historical ui → db → api → core
|
|
11
11
|
* composition order, so pattern composition / compiled output is byte-identical.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
13
|
+
* An OPT-IN driver that isn't installed is simply skipped (capabilities are opt-in). A BUNDLED
|
|
14
|
+
* driver (a core dependency, e.g. `@sungen/driver-ui`) that fails to resolve is NOT opt-in — it
|
|
15
|
+
* signals a broken/incomplete install, so it warns LOUDLY instead of degrading silently to
|
|
16
|
+
* "Unrecognized step pattern". Errors thrown *inside* a present driver propagate — only a genuinely
|
|
17
|
+
* absent package is swallowed (resolve-then-require). Nothing here throws: the CLI must keep working
|
|
18
|
+
* (`--version`, `capability remove`) even when a driver is missing/broken.
|
|
15
19
|
*/
|
|
16
20
|
import { createRequire } from 'module';
|
|
17
21
|
import * as path from 'path';
|
|
@@ -25,6 +29,15 @@ import { LOCAL_DRIVERS, registerCoreCapability } from './builtins';
|
|
|
25
29
|
*/
|
|
26
30
|
const EXTERNAL_DRIVERS = ['@sungen/driver-ui', '@sungen/driver-db', '@sungen/driver-api', '@sungen/driver-mobile'];
|
|
27
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Bundled (non-opt-in) drivers: shipped as a core dependency (`drivers.yaml` `bundled: true`), so the
|
|
34
|
+
* project gets them without `sungen capability add`. If one fails to resolve it is NOT a declined
|
|
35
|
+
* opt-in — it is a broken install (e.g. a dev workspace that skipped `npm install`, so the workspace
|
|
36
|
+
* symlink for the package is missing). Surface it LOUDLY: otherwise the entire UI step vocabulary is
|
|
37
|
+
* absent and every step silently compiles to "Unrecognized step pattern" with no clue why.
|
|
38
|
+
*/
|
|
39
|
+
const BUNDLED_DRIVERS = new Set(['@sungen/driver-ui']);
|
|
40
|
+
|
|
28
41
|
function loadExternalDriver(name: string): void {
|
|
29
42
|
// Resolve from the user's PROJECT first, then from core's own location. Opt-in drivers
|
|
30
43
|
// (`sungen capability add db|api`) install into the PROJECT's node_modules — a globally-installed
|
|
@@ -51,7 +64,20 @@ function loadExternalDriver(name: string): void {
|
|
|
51
64
|
}
|
|
52
65
|
return; // found at this anchor (loaded or warned) — don't fall through to other anchors
|
|
53
66
|
}
|
|
54
|
-
//
|
|
67
|
+
// Not resolvable at ANY anchor.
|
|
68
|
+
if (BUNDLED_DRIVERS.has(name)) {
|
|
69
|
+
// A bundled default is missing → broken install, not a declined opt-in. Warn loudly so the user
|
|
70
|
+
// sees the real cause instead of a wall of "Unrecognized step pattern".
|
|
71
|
+
console.warn(
|
|
72
|
+
`⚠ sungen: the bundled driver "${name}" could not be resolved — its step patterns are MISSING, ` +
|
|
73
|
+
`so steps will compile to "// Unrecognized step pattern".\n` +
|
|
74
|
+
` This means the install is incomplete. Fix:\n` +
|
|
75
|
+
` • in an app project: run \`npm install\`\n` +
|
|
76
|
+
` • in the sungen monorepo: run \`npm install\` at the repo ROOT (bootstraps the workspace symlink for ${name})`,
|
|
77
|
+
);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
// Opt-in driver not installed — capabilities are opt-in, so skip silently.
|
|
55
81
|
}
|
|
56
82
|
|
|
57
83
|
export function discoverAndRegisterCapabilities(): void {
|
|
@@ -7,6 +7,7 @@ export function registerAddFlowCommand(program: Command): void {
|
|
|
7
7
|
.requiredOption('--flow <name>', 'Flow name')
|
|
8
8
|
.option('-p, --path <path>', 'Starting page route path (e.g. /login)')
|
|
9
9
|
.option('-d, --description <text>', 'Flow description')
|
|
10
|
+
.option('--reach <steps>', 'Mobile navigation recipe for the flow\'s starting screen: a ">"-separated list of nav steps from app launch, e.g. "tap [My Page] tab > tap [Settings] row". Scaffolded into the Background. Ignored (with a warning) on web projects.')
|
|
10
11
|
.action(async (options) => {
|
|
11
12
|
try {
|
|
12
13
|
const { FlowManager } = require('../../orchestrator/flow-manager');
|
|
@@ -16,6 +17,7 @@ export function registerAddFlowCommand(program: Command): void {
|
|
|
16
17
|
name: options.flow,
|
|
17
18
|
path: options.path,
|
|
18
19
|
description: options.description,
|
|
20
|
+
reach: options.reach,
|
|
19
21
|
});
|
|
20
22
|
} catch (error) {
|
|
21
23
|
console.error('Error:', error instanceof Error ? error.message : error);
|
package/src/cli/commands/add.ts
CHANGED
|
@@ -11,6 +11,8 @@ export function registerAddCommand(program: Command): void {
|
|
|
11
11
|
.option('-c, --capture', 'Auto-capture a live-page screenshot to requirements/ui/ (requires --path)')
|
|
12
12
|
.option('-f, --feature <name>', 'Add additional feature file to existing screen')
|
|
13
13
|
.option('-d, --description <text>', 'Screen description')
|
|
14
|
+
.option('--platform <platform>', 'Optional per-OS tag: android | ios. Emits @platform:android/ios on the stub for OS-specific screens. Does NOT affect directory routing (always qa/screens/). Omit for cross-platform mobile or web.')
|
|
15
|
+
.option('--reach <steps>', 'Mobile navigation recipe (in-app web-path analog): a ">"-separated list of nav steps from app launch to this screen, e.g. "tap [My Page] tab > tap [Settings] row". Scaffolded into the Background; capture-mobile replays it. Ignored (with a warning) on web projects.')
|
|
14
16
|
.option('--figma <url>', "Figma share URL — fetch design node and generate spec_figma.md + ui/*.png. QUOTE the URL ('...'): the '&' in Figma links backgrounds the shell otherwise.")
|
|
15
17
|
.option('--refresh', 'Bypass Figma cache and re-fetch from API (use with --figma)')
|
|
16
18
|
.option('--scale <n>', 'PNG export scale factor (default: 2)', parseFloat, 2)
|
|
@@ -38,6 +40,8 @@ export function registerAddCommand(program: Command): void {
|
|
|
38
40
|
capture: options.capture,
|
|
39
41
|
feature: options.feature,
|
|
40
42
|
description: options.description,
|
|
43
|
+
platform: options.platform,
|
|
44
|
+
reach: options.reach,
|
|
41
45
|
});
|
|
42
46
|
} else {
|
|
43
47
|
console.log(`Screen "${options.screen}" already exists — refreshing Figma assets only.`);
|
|
@@ -8,12 +8,34 @@ import {
|
|
|
8
8
|
} from '../../harness/capability';
|
|
9
9
|
import { buildPlan, MANUAL_REASONS } from '../../harness/capability-plan';
|
|
10
10
|
import { adapterRegistry } from '../../generators/test-generator/adapters';
|
|
11
|
+
import { emitMobileRuntime, installMobileDriver } from '../../orchestrator/mobile-runtime-scaffolder';
|
|
11
12
|
|
|
12
13
|
/** The running CLI's (core) version — opt-in drivers install in lockstep with it (not `@latest`). */
|
|
13
14
|
function coreVersion(): string {
|
|
14
15
|
try { return require('../../../package.json').version || 'latest'; } catch { return 'latest'; }
|
|
15
16
|
}
|
|
16
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Add `test:mobile` to the project's package.json scripts (if absent).
|
|
20
|
+
* Idempotent — skips if the script is already set.
|
|
21
|
+
* Returns true if the script was added, false if already present or no package.json.
|
|
22
|
+
*/
|
|
23
|
+
function addMobileScript(cwd: string): boolean {
|
|
24
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
25
|
+
if (!fs.existsSync(pkgPath)) return false;
|
|
26
|
+
let pkg: Record<string, any>;
|
|
27
|
+
try {
|
|
28
|
+
pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
if (!pkg.scripts || typeof pkg.scripts !== 'object') pkg.scripts = {};
|
|
33
|
+
if (pkg.scripts['test:mobile']) return false;
|
|
34
|
+
pkg.scripts['test:mobile'] = 'wdio run wdio.conf.ts';
|
|
35
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf-8');
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
|
|
17
39
|
/**
|
|
18
40
|
* Verify a capability driver (db/api/…) installed into the PROJECT exposes the capability SPI entry
|
|
19
41
|
* (`sungenDriver.register`). Resolved from the project's node_modules so it works with a globally
|
|
@@ -137,13 +159,33 @@ export function registerCapabilityCommand(program: Command): void {
|
|
|
137
159
|
.command('add <driver>')
|
|
138
160
|
.description('Enable a driver: install its package (if external) + record it in qa/capabilities.yaml')
|
|
139
161
|
.option('--skip-install', 'Only record in the profile; do not run npm install')
|
|
140
|
-
.
|
|
162
|
+
.option('--force', 'Switch the project platform even if a different one is already set (sungen is one platform per project)')
|
|
163
|
+
.action((driver: string, o: { skipInstall?: boolean; force?: boolean }) => {
|
|
141
164
|
try {
|
|
142
165
|
const cwd = process.cwd();
|
|
143
166
|
const meta = driverMeta(driver);
|
|
144
167
|
if (!meta) {
|
|
145
168
|
throw new Error(`Unknown driver "${driver}". Known: ${Object.keys(loadDriverCatalog()).join(', ')}`);
|
|
146
169
|
}
|
|
170
|
+
|
|
171
|
+
// Guard: a platform driver (web/mobile) sets the project's SINGLE platform. If a DIFFERENT
|
|
172
|
+
// platform is already set, adding this one would silently FLIP it (e.g. web → mobile) and make
|
|
173
|
+
// the existing feature files compile for the wrong runtime. Refuse unless --force, and point at
|
|
174
|
+
// the correct path for a web+mobile repo (two separate project folders).
|
|
175
|
+
const current = readCapabilities(cwd);
|
|
176
|
+
if (meta.kind === 'platform' && current.platform && current.platform !== driver && !o.force) {
|
|
177
|
+
const initHint = driver === 'mobile' ? ' --mobile --app-package <pkg>' : ' --base-url <url>';
|
|
178
|
+
console.error(
|
|
179
|
+
`✗ This project's platform is already "${current.platform}". Adding "${driver}" would SWITCH it to "${driver}".\n` +
|
|
180
|
+
` sungen is ONE platform per project — this is NOT "${current.platform} + ${driver}" together.\n` +
|
|
181
|
+
` • Want BOTH web and mobile? Keep this project and create a SEPARATE folder for ${driver}:\n` +
|
|
182
|
+
` sungen init ${driver}-tests${initHint}\n` +
|
|
183
|
+
` • Really converting ${current.platform} → ${driver}? Re-run with --force\n` +
|
|
184
|
+
` (your existing ${current.platform} feature files would then compile for ${driver}).`,
|
|
185
|
+
);
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
188
|
+
|
|
147
189
|
const adapterName = meta.adapter || meta.id;
|
|
148
190
|
const bundled = adapterRegistry.hasAdapter(adapterName); // e.g. `web` is bundled in Phase 2a
|
|
149
191
|
|
|
@@ -176,6 +218,27 @@ export function registerCapabilityCommand(program: Command): void {
|
|
|
176
218
|
const platform = meta.kind === 'platform' ? driver : profile.platform;
|
|
177
219
|
writeCapabilities(cwd, { platform, enabled });
|
|
178
220
|
console.log(`✓ capability "${driver}" enabled${meta.kind === 'platform' ? ` (platform=${driver})` : ''}.`);
|
|
221
|
+
|
|
222
|
+
// Emit the mobile runtime harness when the mobile platform driver is added.
|
|
223
|
+
// Idempotent: emitMobileRuntime skips files that already exist.
|
|
224
|
+
if (platform === 'mobile') {
|
|
225
|
+
console.log('');
|
|
226
|
+
console.log('🔧 Emitting mobile runtime harness...');
|
|
227
|
+
const result = emitMobileRuntime(cwd, { enabled: true });
|
|
228
|
+
for (const f of result.created) console.log(` ✓ created ${f}`);
|
|
229
|
+
for (const f of result.skipped) console.log(` – skipped ${f} (already exists)`);
|
|
230
|
+
|
|
231
|
+
// Add test:mobile script to package.json (idempotent)
|
|
232
|
+
if (addMobileScript(cwd)) {
|
|
233
|
+
console.log(' ✓ created package.json scripts[test:mobile]');
|
|
234
|
+
} else {
|
|
235
|
+
console.log(' – skipped package.json scripts[test:mobile] (already exists)');
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Best-effort driver install — non-fatal so capability add still succeeds offline.
|
|
239
|
+
installMobileDriver(cwd);
|
|
240
|
+
}
|
|
241
|
+
|
|
179
242
|
console.log('');
|
|
180
243
|
} catch (e) {
|
|
181
244
|
console.error('Error:', e instanceof Error ? e.message : e);
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
writeCsv,
|
|
22
22
|
} from '../../exporters/csv-exporter';
|
|
23
23
|
import { renderXlsxMultiSheet, writeXlsx, buildApiDetailRows, addApiDetailSheet } from '../../exporters/xlsx-exporter';
|
|
24
|
+
import { resolvePlatformAppId } from '../../exporters/mobile-app-id';
|
|
24
25
|
import { EnvironmentInfo, PreflightCheck, ScreenSummary, TestCaseRow, ApiCatalogEntry } from '../../exporters/types';
|
|
25
26
|
|
|
26
27
|
const COLOR = {
|
|
@@ -394,6 +395,40 @@ function getEnvironment(cwd: string): EnvironmentInfo {
|
|
|
394
395
|
return { baseURL, projectName, executor };
|
|
395
396
|
}
|
|
396
397
|
|
|
398
|
+
/**
|
|
399
|
+
* True when this is a mobile project (`platform: mobile` in qa/capabilities.yaml).
|
|
400
|
+
* Mobile results are written PER-OS (`<feature>-test-result.android|ios.json`) —
|
|
401
|
+
* android/ios are separate platform runs, not locales of one run — so delivery
|
|
402
|
+
* emits one csv+xlsx per OS and skips the (empty/misleading) base aggregate.
|
|
403
|
+
*/
|
|
404
|
+
function isMobileProject(cwd: string): boolean {
|
|
405
|
+
try {
|
|
406
|
+
const p = path.join(cwd, 'qa', 'capabilities.yaml');
|
|
407
|
+
if (!fs.existsSync(p)) return false;
|
|
408
|
+
const parsed = parseYaml(fs.readFileSync(p, 'utf-8')) as { platform?: string } | null;
|
|
409
|
+
return parsed?.platform === 'mobile';
|
|
410
|
+
} catch {
|
|
411
|
+
return false;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Remove a stale base (no-suffix) deliverable pair for a feature. Used in mobile
|
|
417
|
+
* per-OS mode where the base `<feature>-testcases.{csv,xlsx}` is not produced —
|
|
418
|
+
* deletes leftovers from a previous (pre-per-OS) delivery so the folder only
|
|
419
|
+
* holds the per-OS files.
|
|
420
|
+
*/
|
|
421
|
+
function removeBaseDeliverables(cwd: string, featureBaseName: string): void {
|
|
422
|
+
const dir = path.join(cwd, 'qa', 'deliverables');
|
|
423
|
+
for (const ext of ['csv', 'xlsx']) {
|
|
424
|
+
const f = path.join(dir, `${featureBaseName}-testcases.${ext}`);
|
|
425
|
+
if (fs.existsSync(f)) {
|
|
426
|
+
fs.rmSync(f);
|
|
427
|
+
log(` ${COLOR.gray}removed stale base deliverable ${path.relative(cwd, f)} (mobile → per-OS files)${COLOR.reset}`);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
397
432
|
// ----------------------------------------------------------------------------
|
|
398
433
|
// Per-screen export
|
|
399
434
|
// ----------------------------------------------------------------------------
|
|
@@ -440,10 +475,15 @@ async function exportTarget(
|
|
|
440
475
|
// one CSV per locale (CSV has no sheet concept).
|
|
441
476
|
// <2 variants → single-sheet XLSX with the canonical 'Testcases' sheet.
|
|
442
477
|
if (explicitEnv) {
|
|
478
|
+
// Mobile: a stale base (no-suffix) pair from a pre-per-OS run is misleading
|
|
479
|
+
// sitting next to the env-suffixed file — drop it.
|
|
480
|
+
if (isMobileProject(cwd)) removeBaseDeliverables(cwd, target.featureBaseName);
|
|
443
481
|
const rows = buildTestCaseRows({
|
|
444
482
|
screen: label,
|
|
445
483
|
featureName: feature.featureName,
|
|
446
|
-
|
|
484
|
+
// OS-precise env column: a mobile dual-id Path resolves to just the
|
|
485
|
+
// android package / ios bundle id for SUNGEN_ENV=android|ios.
|
|
486
|
+
featurePath: resolvePlatformAppId(featurePath, explicitEnv),
|
|
447
487
|
merged,
|
|
448
488
|
testData: loadTestData(testDataFile),
|
|
449
489
|
results: resolveResultsPath(cwd, target) ? loadPlaywrightReport(resolveResultsPath(cwd, target)!) : null,
|
|
@@ -470,6 +510,50 @@ async function exportTarget(
|
|
|
470
510
|
}
|
|
471
511
|
|
|
472
512
|
const variants = discoverLocaleVariants(cwd, target);
|
|
513
|
+
|
|
514
|
+
// Mobile: emit ONE deliverable per OS (csv + xlsx), each from its own
|
|
515
|
+
// `<feature>-test-result.<os>.json`. Android/iOS are SEPARATE platform runs,
|
|
516
|
+
// not locales of one run, so a single aggregated base workbook is empty &
|
|
517
|
+
// misleading — produce per-OS files and skip the base (issue: per-OS delivery).
|
|
518
|
+
if (isMobileProject(cwd)) {
|
|
519
|
+
// OS runs = variants whose first dot-segment is android|ios (incl. env-suffixed
|
|
520
|
+
// android.staging / ios.ja). A bare locale variant is NOT an OS run and stays on
|
|
521
|
+
// the standard aggregation path below.
|
|
522
|
+
const osVariants = variants.filter((v) => !!v.resultsPath && /^(android|ios)(\.|$)/.test(v.locale));
|
|
523
|
+
if (osVariants.length > 0) {
|
|
524
|
+
removeBaseDeliverables(cwd, target.featureBaseName); // drop any stale no-suffix pair
|
|
525
|
+
let mobileSummary: ScreenSummary | null = null;
|
|
526
|
+
for (const variant of osVariants) {
|
|
527
|
+
const variantRows = buildTestCaseRows({
|
|
528
|
+
screen: label,
|
|
529
|
+
featureName: feature.featureName,
|
|
530
|
+
featurePath: resolvePlatformAppId(featurePath, variant.locale), // OS-precise app id
|
|
531
|
+
merged,
|
|
532
|
+
testData: loadTestData(testDataFile, variant.locale || null),
|
|
533
|
+
results: variant.resultsPath ? loadPlaywrightReport(variant.resultsPath) : null,
|
|
534
|
+
env,
|
|
535
|
+
selectorKeyMap,
|
|
536
|
+
});
|
|
537
|
+
const csv = renderCsv(buildSummary(label, variantRows, ''), variantRows, specLink);
|
|
538
|
+
const csvPath = writeCsv(cwd, target.featureBaseName, csv, variant.locale);
|
|
539
|
+
const autoRows = variantRows.filter((r) => r.testcaseType !== 'Manual');
|
|
540
|
+
const manualRows = variantRows.filter((r) => r.testcaseType === 'Manual');
|
|
541
|
+
const wb = renderXlsxMultiSheet([
|
|
542
|
+
{ sheetName: 'Auto', summary: buildSummary(label, autoRows, ''), rows: autoRows, specLink },
|
|
543
|
+
{ sheetName: 'Manual', summary: buildSummary(label, manualRows, ''), rows: manualRows, specLink },
|
|
544
|
+
]);
|
|
545
|
+
await writeXlsx(cwd, target.featureBaseName, wb, variant.locale);
|
|
546
|
+
const s = buildSummary(label, variantRows, path.relative(cwd, csvPath));
|
|
547
|
+
// Prefer the android line for the single summary-table row (deterministic);
|
|
548
|
+
// startsWith covers env-suffixed android.staging etc.
|
|
549
|
+
if (!mobileSummary || variant.locale.startsWith('android')) mobileSummary = s;
|
|
550
|
+
}
|
|
551
|
+
return mobileSummary;
|
|
552
|
+
}
|
|
553
|
+
// Mobile but no per-OS results yet → fall through to the base export below
|
|
554
|
+
// so the test CASES still deliver (execution columns empty/pending).
|
|
555
|
+
}
|
|
556
|
+
|
|
473
557
|
let primarySummary: ScreenSummary | null = null;
|
|
474
558
|
let primaryCsvPath = '';
|
|
475
559
|
// XLSX is split by automation type: an "Auto" sheet (automatable TCs, results differ per
|
|
@@ -487,7 +571,10 @@ async function exportTarget(
|
|
|
487
571
|
const variantRows = buildTestCaseRows({
|
|
488
572
|
screen: label,
|
|
489
573
|
featureName: feature.featureName,
|
|
490
|
-
|
|
574
|
+
// No-op for web (URL Path); for a mobile fall-through (a no-suffix result
|
|
575
|
+
// json) this collapses the dual-id to the android package so the combined
|
|
576
|
+
// dual-id never leaks into the env column.
|
|
577
|
+
featurePath: resolvePlatformAppId(featurePath, variant.locale),
|
|
491
578
|
merged,
|
|
492
579
|
testData: variantTestData,
|
|
493
580
|
results: variantResults,
|
package/src/cli/commands/eval.ts
CHANGED
|
@@ -5,8 +5,8 @@ export function registerEvalCommand(program: Command): void {
|
|
|
5
5
|
program
|
|
6
6
|
.command('eval')
|
|
7
7
|
.description('Eval harness: quality checks on Sungen\'s own skills/instructions (dev/CI)')
|
|
8
|
-
.option('--skills', 'Static skill-lint: frontmatter, line budget,
|
|
9
|
-
.option('--dir <path>', '
|
|
8
|
+
.option('--skills', 'Static skill-lint: frontmatter, line budget, cap-fence validity')
|
|
9
|
+
.option('--dir <path>', 'Single-source dir to lint (default: bundled ai-src)')
|
|
10
10
|
.option('--json', 'Output the raw findings JSON')
|
|
11
11
|
.action((options) => {
|
|
12
12
|
try {
|
|
@@ -16,7 +16,7 @@ export function registerEvalCommand(program: Command): void {
|
|
|
16
16
|
if (options.json) { console.log(JSON.stringify(r, null, 2)); process.exit(r.errors > 0 ? 2 : 0); }
|
|
17
17
|
console.log('');
|
|
18
18
|
console.log(`━━━ Skill-lint: ${r.checked} skill template(s) ━━━`);
|
|
19
|
-
if (!r.findings.length) console.log(' ✓ all skills pass (frontmatter · line-budget ·
|
|
19
|
+
if (!r.findings.length) console.log(' ✓ all skills pass (frontmatter · line-budget · cap-fence)');
|
|
20
20
|
for (const f of r.findings) console.log(` ${f.level === 'error' ? '✗' : '⚠'} [${f.rule}] ${f.file} — ${f.detail}`);
|
|
21
21
|
console.log('');
|
|
22
22
|
process.exit(r.errors > 0 ? 2 : 0);
|
|
@@ -219,7 +219,8 @@ export function registerGenerateCommand(program: Command): void {
|
|
|
219
219
|
for (const w of ddWarnings.slice(0, 20)) console.log(` ${w.scenario ? w.scenario + ': ' : ''}${w.message}`);
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
-
|
|
222
|
+
const nextStep = platform === 'mobile' ? 'npm run test:mobile' : 'npx playwright test --ui';
|
|
223
|
+
console.log(`Next step: ${nextStep}\n`);
|
|
223
224
|
} catch (error) {
|
|
224
225
|
console.error('Error:', error instanceof Error ? error.message : error);
|
|
225
226
|
process.exit(1);
|
package/src/cli/commands/init.ts
CHANGED
|
@@ -4,11 +4,58 @@ export function registerInitCommand(program: Command): void {
|
|
|
4
4
|
program
|
|
5
5
|
.command('init [projectName]')
|
|
6
6
|
.description('Initialize Sungen project (optionally create and initialize in a new project folder)')
|
|
7
|
-
.
|
|
8
|
-
.
|
|
7
|
+
.option('--base-url <url>', 'Base URL for Playwright (web) tests (e.g. https://your-app.com). Required for web projects (omit only with --mobile)')
|
|
8
|
+
.option('--mobile', 'Mobile project (Appium/WebdriverIO): scaffold ONLY the mobile harness — no Playwright config, no Chromium download, no --base-url needed')
|
|
9
|
+
.option('--app-package <pkg>', 'Android appPackage for mobile (e.g. com.example.app). Used with --mobile')
|
|
10
|
+
.option('--app-activity <activity>', 'Android appActivity for mobile (e.g. com.example.app.MainActivity). OPTIONAL — omit to let Appium auto-resolve the launcher activity from --app-package; pass it only to pin a non-standard launcher. Used with --mobile')
|
|
11
|
+
.option('--app-apk <path>', 'Path to an .apk to install & run for mobile (alternative to package/activity). Used with --mobile')
|
|
12
|
+
.option('--android-udid <udid>', 'Default Android device/emulator UDID for mobile (default: emulator-5554)')
|
|
13
|
+
.option('--ios-bundle-id <id>', 'iOS bundle id for mobile (e.g. com.example.app.dev) — default app when MOBILE_PLATFORM=ios|both. Used with --mobile')
|
|
14
|
+
.option('--ios-device <name>', "iOS Simulator device name for mobile (default: 'iPhone 16'). Used with --mobile")
|
|
15
|
+
.option('--ios-version <ver>', "iOS Simulator platform version for mobile (default: '18.0'). Used with --mobile")
|
|
16
|
+
.option(
|
|
17
|
+
'--assistant <name>',
|
|
18
|
+
'AI assistant to install for: claude | copilot | codex | all (repeatable). Default: claude',
|
|
19
|
+
(val: string, acc: string[]) => { acc.push(val); return acc; },
|
|
20
|
+
[] as string[],
|
|
21
|
+
)
|
|
22
|
+
.option('--force', 'Overwrite user config files (CLAUDE.md, AGENTS.md, copilot-instructions.md)', false)
|
|
23
|
+
.action(async (projectName: string | undefined, options: {
|
|
24
|
+
baseUrl?: string;
|
|
25
|
+
mobile?: boolean;
|
|
26
|
+
appPackage?: string;
|
|
27
|
+
appActivity?: string;
|
|
28
|
+
appApk?: string;
|
|
29
|
+
androidUdid?: string;
|
|
30
|
+
iosBundleId?: string;
|
|
31
|
+
iosDevice?: string;
|
|
32
|
+
iosVersion?: string;
|
|
33
|
+
assistant?: string[];
|
|
34
|
+
force?: boolean;
|
|
35
|
+
}) => {
|
|
9
36
|
try {
|
|
37
|
+
// `--mobile` = a mobile-only project: no web scaffolding, no base URL needed.
|
|
38
|
+
// Plain `init` = a web project and requires `--base-url`.
|
|
39
|
+
const mobile = !!options.mobile;
|
|
40
|
+
if (!mobile && !options.baseUrl) {
|
|
41
|
+
console.error('❌ --base-url is required for a web project. For a mobile project pass --mobile (no base URL needed).');
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
const { resolveAssistants } = require('../../orchestrator/ai-skills/resolve-assistants');
|
|
10
45
|
const { ProjectInitializer } = require('../../orchestrator/project-initializer');
|
|
11
|
-
const initializer = new ProjectInitializer(options.baseUrl
|
|
46
|
+
const initializer = new ProjectInitializer(options.baseUrl || '', {
|
|
47
|
+
agents: resolveAssistants(options.assistant),
|
|
48
|
+
force: options.force ?? false,
|
|
49
|
+
enabled: mobile,
|
|
50
|
+
mobileOnly: mobile,
|
|
51
|
+
appPackage: options.appPackage,
|
|
52
|
+
appActivity: options.appActivity,
|
|
53
|
+
appApk: options.appApk,
|
|
54
|
+
androidUdid: options.androidUdid,
|
|
55
|
+
iosBundleId: options.iosBundleId,
|
|
56
|
+
iosDevice: options.iosDevice,
|
|
57
|
+
iosVersion: options.iosVersion,
|
|
58
|
+
});
|
|
12
59
|
await initializer.initialize(projectName);
|
|
13
60
|
} catch (error) {
|
|
14
61
|
console.error('❌ Init failed:', error);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import * as path from 'path';
|
|
3
3
|
import * as fs from 'fs';
|
|
4
|
-
import { runJourney, waive, renderJourneyBoard } from '../../harness/journey';
|
|
4
|
+
import { runJourney, waive, signoff, renderJourneyBoard, rollupJourney, renderRollupBoard } from '../../harness/journey';
|
|
5
5
|
import { reportSlug } from '../../harness/unit-paths';
|
|
6
6
|
|
|
7
7
|
function findScreenDir(name: string): string | null {
|
|
@@ -19,18 +19,35 @@ export function registerJourneyCommand(program: Command): void {
|
|
|
19
19
|
.command('journey')
|
|
20
20
|
.description('Durable "you are here" board (#381): obligations + what-to-review + next, synthesised read-only from the audit report + ledger already on disk.')
|
|
21
21
|
.option('-s, --screen <name>', 'Screen / flow / api unit name')
|
|
22
|
+
.option('--all', 'Roll up every unit (screens + flows + api) into one board — the serial review surface for multi-unit fan-out')
|
|
22
23
|
.option('--waive <obligation>', 'Waive an obligation (e.g. OB-coverage) — requires --reason')
|
|
23
24
|
.option('--reason <text>', 'The reason a waived obligation is acceptable (mandatory with --waive)')
|
|
25
|
+
.option('--signoff', 'Sign off the review queue — the single human gate (allowed only when every other obligation is satisfied/waived)')
|
|
26
|
+
.option('--by <name>', 'Who is signing off (recorded with --signoff)')
|
|
24
27
|
.option('--json', 'Output the raw JSON report')
|
|
25
28
|
.action((options) => {
|
|
26
29
|
try {
|
|
30
|
+
if (options.all) {
|
|
31
|
+
const rows = rollupJourney(process.cwd());
|
|
32
|
+
const board = renderRollupBoard(rows);
|
|
33
|
+
const outDir = path.join(process.cwd(), '.sungen', 'journey');
|
|
34
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
35
|
+
fs.writeFileSync(path.join(outDir, '_rollup.json'), JSON.stringify(rows, null, 2), 'utf-8');
|
|
36
|
+
fs.writeFileSync(path.join(outDir, '_rollup.board.md'), board, 'utf-8');
|
|
37
|
+
if (options.json) console.log(JSON.stringify(rows, null, 2));
|
|
38
|
+
else { console.log(''); console.log(board); console.log(` Board: ${path.relative(process.cwd(), path.join(outDir, '_rollup.board.md'))}`); console.log(''); }
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
27
42
|
const name = options.screen;
|
|
28
|
-
if (!name) throw new Error('Provide --screen <name>');
|
|
43
|
+
if (!name) throw new Error('Provide --screen <name> (or --all for the roll-up)');
|
|
29
44
|
if (!findScreenDir(name)) throw new Error(`Not found: qa/screens/${name}, qa/flows/${name}, or qa/api/${name}`);
|
|
30
45
|
|
|
31
46
|
const report = options.waive
|
|
32
47
|
? waive(process.cwd(), name, options.waive, options.reason || '')
|
|
33
|
-
:
|
|
48
|
+
: options.signoff
|
|
49
|
+
? signoff(process.cwd(), name, options.by)
|
|
50
|
+
: runJourney(process.cwd(), name);
|
|
34
51
|
|
|
35
52
|
const outDir = path.join(process.cwd(), '.sungen', 'journey');
|
|
36
53
|
fs.mkdirSync(outDir, { recursive: true });
|
|
@@ -30,6 +30,13 @@ export function registerUpdateCommand(program: Command): void {
|
|
|
30
30
|
'Reinstall @sun-asterisk/sungen (stable by default, --beta for prerelease) + refresh AI rules, commands, skills',
|
|
31
31
|
)
|
|
32
32
|
.option('--dry-run', 'Show what would be updated without making changes')
|
|
33
|
+
.option(
|
|
34
|
+
'--assistant <name>',
|
|
35
|
+
'AI assistant to install for: claude | copilot | codex | all (repeatable). Default: claude',
|
|
36
|
+
(val: string, acc: string[]) => { acc.push(val); return acc; },
|
|
37
|
+
[] as string[],
|
|
38
|
+
)
|
|
39
|
+
.option('--force', 'Overwrite user config files (CLAUDE.md, AGENTS.md, copilot-instructions.md)', false)
|
|
33
40
|
.option(
|
|
34
41
|
'--beta',
|
|
35
42
|
'Switch to the BETA channel (installs @beta — prerelease builds for testing)',
|
|
@@ -44,7 +51,7 @@ export function registerUpdateCommand(program: Command): void {
|
|
|
44
51
|
'Skip the npm install step (refresh project AI assets only)',
|
|
45
52
|
false,
|
|
46
53
|
)
|
|
47
|
-
.action(async (options: { dryRun?: boolean; beta?: boolean; tag?: string; skipNpmInstall?: boolean }) => {
|
|
54
|
+
.action(async (options: { dryRun?: boolean; beta?: boolean; tag?: string; skipNpmInstall?: boolean; assistant?: string[]; force?: boolean }) => {
|
|
48
55
|
try {
|
|
49
56
|
const skipNpm =
|
|
50
57
|
Boolean(options.skipNpmInstall) || process.env[SKIP_NPM_ENV] === '1';
|
|
@@ -56,13 +63,14 @@ export function registerUpdateCommand(program: Command): void {
|
|
|
56
63
|
if (!skipNpm) {
|
|
57
64
|
reinstallSungen(channel);
|
|
58
65
|
printCurrentVersion(channel);
|
|
59
|
-
reExecUpdateForAIAssets(options.dryRun ?? false);
|
|
66
|
+
reExecUpdateForAIAssets(options.dryRun ?? false, options.assistant ?? [], options.force ?? false);
|
|
60
67
|
return;
|
|
61
68
|
}
|
|
62
69
|
|
|
70
|
+
const { resolveAssistants } = require('../../orchestrator/ai-skills/resolve-assistants');
|
|
63
71
|
const { AIRulesUpdater } = require('../../orchestrator/ai-rules-updater');
|
|
64
72
|
const updater = new AIRulesUpdater(process.cwd());
|
|
65
|
-
await updater.update(options.dryRun ?? false);
|
|
73
|
+
await updater.update(options.dryRun ?? false, { agents: resolveAssistants(options.assistant), force: options.force ?? false });
|
|
66
74
|
} catch (error) {
|
|
67
75
|
console.error('❌ Update failed:', error);
|
|
68
76
|
process.exit(1);
|
|
@@ -96,9 +104,11 @@ function printCurrentVersion(channel: string): void {
|
|
|
96
104
|
console.log('');
|
|
97
105
|
}
|
|
98
106
|
|
|
99
|
-
function reExecUpdateForAIAssets(dryRun: boolean): void {
|
|
107
|
+
function reExecUpdateForAIAssets(dryRun: boolean, assistants: string[], force: boolean): void {
|
|
100
108
|
const args = ['update'];
|
|
101
109
|
if (dryRun) args.push('--dry-run');
|
|
110
|
+
if (force) args.push('--force');
|
|
111
|
+
for (const a of assistants) args.push('--assistant', a);
|
|
102
112
|
|
|
103
113
|
const result = spawnSync('sungen', args, {
|
|
104
114
|
stdio: 'inherit',
|
|
@@ -22,6 +22,7 @@ import { loadSelectorKeyMap } from '../exporters/selector-key-resolver';
|
|
|
22
22
|
import { loadPlaywrightReport } from '../exporters/playwright-report-parser';
|
|
23
23
|
import { mergeFeatureAndSpec } from '../exporters/scenario-merger';
|
|
24
24
|
import { buildScreenSnapshot } from '../exporters/json-exporter';
|
|
25
|
+
import { resolvePlatformAppId } from '../exporters/mobile-app-id';
|
|
25
26
|
import { getPackageVersion } from '../exporters/package-info';
|
|
26
27
|
import { EnvironmentInfo } from '../exporters/types';
|
|
27
28
|
import {
|
|
@@ -167,6 +168,12 @@ function buildOneFeature(
|
|
|
167
168
|
// Discover locale variants by scanning <feature>-test-result*.json files.
|
|
168
169
|
// Always includes base ('') as the first entry, even if its results file
|
|
169
170
|
// is missing — UI still needs to render the feature.
|
|
171
|
+
// NOTE (mobile): for a `platform: mobile` project the "locale" variants are
|
|
172
|
+
// actually the per-OS runs (android / ios) — the dashboard reuses the locale
|
|
173
|
+
// switcher to surface them as tabs. The env column per tab is OS-precise (see
|
|
174
|
+
// resolvePlatformAppId below), but the top-level featurePath (base variant)
|
|
175
|
+
// defaults to the Android id. Delivery has a dedicated per-OS path; aligning the
|
|
176
|
+
// dashboard onto a distinct "platform" axis is a possible follow-up.
|
|
170
177
|
const variants = discoverLocaleVariants(genBase, featureBaseName);
|
|
171
178
|
|
|
172
179
|
// Build per-locale ScreenSnapshot, then collect into FeatureSnapshot.locales[].
|
|
@@ -185,7 +192,9 @@ function buildOneFeature(
|
|
|
185
192
|
screen: featureBaseName,
|
|
186
193
|
isFlow: target.isFlow,
|
|
187
194
|
featureName: feature.featureName,
|
|
188
|
-
|
|
195
|
+
// OS-precise env column: a mobile dual-id Path resolves to just the android
|
|
196
|
+
// package / ios bundle id for the android|ios result variant.
|
|
197
|
+
featurePath: resolvePlatformAppId(featurePath, variant.locale),
|
|
189
198
|
specLink,
|
|
190
199
|
merged,
|
|
191
200
|
testData,
|
|
@@ -202,7 +202,9 @@ export function buildSummary(screen: string, rows: TestCaseRow[], outputFile: st
|
|
|
202
202
|
function formatEnv(baseURL: string, featurePath: string | undefined, projectName: string): string {
|
|
203
203
|
const base = (baseURL || '').replace(/\/+$/, '');
|
|
204
204
|
const p = (featurePath || '').replace(/^\/+/, '');
|
|
205
|
-
|
|
205
|
+
// Join with '/' only when there is a baseURL (web). For mobile baseURL is empty
|
|
206
|
+
// and `p` is an app id (e.g. com.kngroup.media.converter) — emit it bare, no '/'.
|
|
207
|
+
const url = base && p ? `${base}/${p}` : (p || base);
|
|
206
208
|
return `${url} (${projectName})`;
|
|
207
209
|
}
|
|
208
210
|
|
|
@@ -165,7 +165,9 @@ export function buildScreenSnapshot(input: BuildScreenSnapshotInput): ScreenSnap
|
|
|
165
165
|
function formatTestEnvironment(baseURL: string, featurePath: string | undefined, projectName: string): string {
|
|
166
166
|
const base = (baseURL || '').replace(/\/+$/, '');
|
|
167
167
|
const p = (featurePath || '').replace(/^\/+/, '');
|
|
168
|
-
|
|
168
|
+
// Join with '/' only when there is a baseURL (web). For mobile baseURL is empty
|
|
169
|
+
// and `p` is an app id (e.g. com.kngroup.media.converter) — emit it bare, no '/'.
|
|
170
|
+
const url = base && p ? `${base}/${p}` : (p || base);
|
|
169
171
|
return `${url} (${projectName})`;
|
|
170
172
|
}
|
|
171
173
|
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the OS-specific app id from a feature `Path:` for the delivery /
|
|
3
|
+
* dashboard "Test Environment" column.
|
|
4
|
+
*
|
|
5
|
+
* Mobile features carry a DUAL-ID Path: `<androidPkg>[/<activity>] | <iosBundleId>`
|
|
6
|
+
* (e.g. `com.kngroup.media.converter | com.kngroup.media.converter.dev`). The
|
|
7
|
+
* deliverable's environment column must show ONLY the id for the OS the results
|
|
8
|
+
* came from — Android → the package (launch activity stripped), iOS → the bundle
|
|
9
|
+
* id — never the combined dual-id.
|
|
10
|
+
*
|
|
11
|
+
* Web features carry a URL Path (no `|`) and are returned unchanged.
|
|
12
|
+
*
|
|
13
|
+
* @param featurePath the feature `Path:` value (dual-id for mobile, URL for web)
|
|
14
|
+
* @param platform the OS/result axis the row belongs to. The OS is the FIRST
|
|
15
|
+
* dot-segment, so env-suffixed result variants resolve too —
|
|
16
|
+
* `'ios'`, `'ios.staging'`, `'ios.ja'` all select the bundle id;
|
|
17
|
+
* anything else (`'android'`, `'android.staging'`, a bare locale,
|
|
18
|
+
* base) falls back to the Android package.
|
|
19
|
+
* @returns the resolved single id, or `featurePath` unchanged when it is not a dual-id.
|
|
20
|
+
*/
|
|
21
|
+
export function resolvePlatformAppId(
|
|
22
|
+
featurePath: string | undefined,
|
|
23
|
+
platform: string | undefined,
|
|
24
|
+
): string | undefined {
|
|
25
|
+
if (!featurePath || !featurePath.includes('|')) return featurePath;
|
|
26
|
+
const [androidPart = '', iosPart = ''] = featurePath.split('|').map((s) => s.trim());
|
|
27
|
+
const androidId = androidPart.split('/')[0].trim(); // package only (drop /MainActivity)
|
|
28
|
+
const iosId = iosPart.trim();
|
|
29
|
+
// OS = first dot-segment so `ios.staging` / `ios.ja` (env-suffixed result files
|
|
30
|
+
// <name>-test-result.<os>[.<env>].json) still resolve to the iOS bundle id.
|
|
31
|
+
const os = (platform || '').toLowerCase().split('.')[0];
|
|
32
|
+
if (os === 'ios') return iosId || androidId;
|
|
33
|
+
return androidId || iosId;
|
|
34
|
+
}
|
|
@@ -25,7 +25,9 @@ function extractTestBlock(content: string, startIdx: number): {
|
|
|
25
25
|
// The options object uses `\{.*?\}` (non-greedy, not `[^}]*`) so a tag value containing a
|
|
26
26
|
// `}` — e.g. `@query:q(p={{var}})` — doesn't truncate the match (issue #271). The header is
|
|
27
27
|
// single-line, so `.*?` anchored on `}, async` is safe.
|
|
28
|
-
|
|
28
|
+
// Accept both Playwright `test(...)` and mobile/Appium mocha `it(...)` (issue #391). The leading
|
|
29
|
+
// `\b` prevents `it(` from matching the tail of a larger word like `submit(` / `edit(`.
|
|
30
|
+
const testRegex = /\b(?:test|it)\s*\(\s*(['"`])((?:(?!\1).)+)\1\s*,\s*(?:\{.*?\}\s*,\s*)?async\s*\([^)]*\)\s*=>\s*\{/g;
|
|
29
31
|
testRegex.lastIndex = startIdx;
|
|
30
32
|
const match = testRegex.exec(content);
|
|
31
33
|
if (!match) return null;
|
|
@@ -57,7 +59,9 @@ function extractTestBlock(content: string, startIdx: number): {
|
|
|
57
59
|
*/
|
|
58
60
|
function extractDescribeBlocks(content: string): Array<{ name: string; bodyStart: number; bodyEnd: number }> {
|
|
59
61
|
const result: Array<{ name: string; bodyStart: number; bodyEnd: number }> = [];
|
|
60
|
-
|
|
62
|
+
// Accept Playwright `test.describe(...)` / `test.describe.serial(...)` AND mobile/Appium mocha
|
|
63
|
+
// `describe(...)` (issue #391). The optional `test.` prefix keeps web specs matching unchanged.
|
|
64
|
+
const regex = /(?:test\.)?describe(?:\.serial)?\s*\(\s*['"]([^'"]+)['"]\s*,\s*\(\s*\)\s*=>\s*\{/g;
|
|
61
65
|
let match: RegExpExecArray | null;
|
|
62
66
|
while ((match = regex.exec(content)) !== null) {
|
|
63
67
|
const name = match[1];
|