@sun-asterisk/sungen 3.2.2 → 3.2.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.
Files changed (355) hide show
  1. package/dist/capabilities/discover.d.ts.map +1 -1
  2. package/dist/capabilities/discover.js +26 -3
  3. package/dist/capabilities/discover.js.map +1 -1
  4. package/dist/cli/commands/add-flow.d.ts.map +1 -1
  5. package/dist/cli/commands/add-flow.js +2 -0
  6. package/dist/cli/commands/add-flow.js.map +1 -1
  7. package/dist/cli/commands/add.d.ts.map +1 -1
  8. package/dist/cli/commands/add.js +4 -0
  9. package/dist/cli/commands/add.js.map +1 -1
  10. package/dist/cli/commands/capability.d.ts.map +1 -1
  11. package/dist/cli/commands/capability.js +61 -0
  12. package/dist/cli/commands/capability.js.map +1 -1
  13. package/dist/cli/commands/delivery.d.ts.map +1 -1
  14. package/dist/cli/commands/delivery.js +89 -2
  15. package/dist/cli/commands/delivery.js.map +1 -1
  16. package/dist/cli/commands/eval.js +3 -3
  17. package/dist/cli/commands/eval.js.map +1 -1
  18. package/dist/cli/commands/gate.d.ts +3 -0
  19. package/dist/cli/commands/gate.d.ts.map +1 -0
  20. package/dist/cli/commands/gate.js +83 -0
  21. package/dist/cli/commands/gate.js.map +1 -0
  22. package/dist/cli/commands/generate.d.ts.map +1 -1
  23. package/dist/cli/commands/generate.js +2 -1
  24. package/dist/cli/commands/generate.js.map +1 -1
  25. package/dist/cli/commands/init.d.ts.map +1 -1
  26. package/dist/cli/commands/init.js +32 -2
  27. package/dist/cli/commands/init.js.map +1 -1
  28. package/dist/cli/commands/journey.d.ts +3 -0
  29. package/dist/cli/commands/journey.d.ts.map +1 -0
  30. package/dist/cli/commands/journey.js +114 -0
  31. package/dist/cli/commands/journey.js.map +1 -0
  32. package/dist/cli/commands/update.d.ts.map +1 -1
  33. package/dist/cli/commands/update.js +10 -3
  34. package/dist/cli/commands/update.js.map +1 -1
  35. package/dist/cli/index.js +4 -0
  36. package/dist/cli/index.js.map +1 -1
  37. package/dist/dashboard/snapshot-builder.d.ts.map +1 -1
  38. package/dist/dashboard/snapshot-builder.js +10 -1
  39. package/dist/dashboard/snapshot-builder.js.map +1 -1
  40. package/dist/exporters/csv-exporter.d.ts.map +1 -1
  41. package/dist/exporters/csv-exporter.js +3 -1
  42. package/dist/exporters/csv-exporter.js.map +1 -1
  43. package/dist/exporters/json-exporter.js +3 -1
  44. package/dist/exporters/json-exporter.js.map +1 -1
  45. package/dist/exporters/mobile-app-id.d.ts +22 -0
  46. package/dist/exporters/mobile-app-id.d.ts.map +1 -0
  47. package/dist/exporters/mobile-app-id.js +37 -0
  48. package/dist/exporters/mobile-app-id.js.map +1 -0
  49. package/dist/exporters/spec-parser.d.ts.map +1 -1
  50. package/dist/exporters/spec-parser.js +6 -2
  51. package/dist/exporters/spec-parser.js.map +1 -1
  52. package/dist/generators/test-generator/adapters/adapter-interface.d.ts +3 -0
  53. package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -1
  54. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/scroll-action.hbs +7 -4
  55. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/toggle-action.hbs +20 -3
  56. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/checked-assertion.hbs +4 -1
  57. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/not-checked-assertion.hbs +4 -1
  58. package/dist/generators/test-generator/adapters/appium/templates/test-file.hbs +3 -0
  59. package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
  60. package/dist/generators/test-generator/code-generator.js +48 -9
  61. package/dist/generators/test-generator/code-generator.js.map +1 -1
  62. package/dist/generators/test-generator/template-engine.d.ts +3 -0
  63. package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
  64. package/dist/generators/test-generator/template-engine.js +11 -1
  65. package/dist/generators/test-generator/template-engine.js.map +1 -1
  66. package/dist/generators/test-generator/utils/selector-resolver.d.ts +11 -1
  67. package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
  68. package/dist/generators/test-generator/utils/selector-resolver.js +33 -0
  69. package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
  70. package/dist/harness/audit.d.ts.map +1 -1
  71. package/dist/harness/audit.js +8 -1
  72. package/dist/harness/audit.js.map +1 -1
  73. package/dist/harness/catalog/universal-viewpoints.yaml +15 -0
  74. package/dist/harness/eval/skill-lint.d.ts +3 -3
  75. package/dist/harness/eval/skill-lint.d.ts.map +1 -1
  76. package/dist/harness/eval/skill-lint.js +88 -68
  77. package/dist/harness/eval/skill-lint.js.map +1 -1
  78. package/dist/harness/journey.d.ts +88 -0
  79. package/dist/harness/journey.d.ts.map +1 -0
  80. package/dist/harness/journey.js +414 -0
  81. package/dist/harness/journey.js.map +1 -0
  82. package/dist/harness/quality-gates.d.ts +13 -0
  83. package/dist/harness/quality-gates.d.ts.map +1 -1
  84. package/dist/harness/quality-gates.js +23 -0
  85. package/dist/harness/quality-gates.js.map +1 -1
  86. package/dist/harness/sensors.d.ts +1 -1
  87. package/dist/harness/sensors.d.ts.map +1 -1
  88. package/dist/harness/sensors.js +32 -9
  89. package/dist/harness/sensors.js.map +1 -1
  90. package/dist/orchestrator/ai-rules-updater.d.ts +11 -3
  91. package/dist/orchestrator/ai-rules-updater.d.ts.map +1 -1
  92. package/dist/orchestrator/ai-rules-updater.js +44 -180
  93. package/dist/orchestrator/ai-rules-updater.js.map +1 -1
  94. package/dist/orchestrator/ai-skills/adapters.d.ts +28 -0
  95. package/dist/orchestrator/ai-skills/adapters.d.ts.map +1 -0
  96. package/dist/orchestrator/ai-skills/adapters.js +34 -0
  97. package/dist/orchestrator/ai-skills/adapters.js.map +1 -0
  98. package/dist/orchestrator/ai-skills/build.d.ts +10 -0
  99. package/dist/orchestrator/ai-skills/build.d.ts.map +1 -0
  100. package/dist/orchestrator/ai-skills/build.js +128 -0
  101. package/dist/orchestrator/ai-skills/build.js.map +1 -0
  102. package/dist/orchestrator/ai-skills/cap-stripper.d.ts +27 -0
  103. package/dist/orchestrator/ai-skills/cap-stripper.d.ts.map +1 -0
  104. package/dist/orchestrator/ai-skills/cap-stripper.js +78 -0
  105. package/dist/orchestrator/ai-skills/cap-stripper.js.map +1 -0
  106. package/dist/orchestrator/ai-skills/commands.d.ts +22 -0
  107. package/dist/orchestrator/ai-skills/commands.d.ts.map +1 -0
  108. package/dist/orchestrator/ai-skills/commands.js +109 -0
  109. package/dist/orchestrator/ai-skills/commands.js.map +1 -0
  110. package/dist/orchestrator/ai-skills/generator.d.ts +23 -0
  111. package/dist/orchestrator/ai-skills/generator.d.ts.map +1 -0
  112. package/dist/orchestrator/ai-skills/generator.js +79 -0
  113. package/dist/orchestrator/ai-skills/generator.js.map +1 -0
  114. package/dist/orchestrator/ai-skills/golden-skills.test.d.ts +2 -0
  115. package/dist/orchestrator/ai-skills/golden-skills.test.d.ts.map +1 -0
  116. package/dist/orchestrator/ai-skills/golden-skills.test.js +183 -0
  117. package/dist/orchestrator/ai-skills/golden-skills.test.js.map +1 -0
  118. package/dist/orchestrator/ai-skills/resolve-assistants.d.ts +8 -0
  119. package/dist/orchestrator/ai-skills/resolve-assistants.d.ts.map +1 -0
  120. package/dist/orchestrator/ai-skills/resolve-assistants.js +28 -0
  121. package/dist/orchestrator/ai-skills/resolve-assistants.js.map +1 -0
  122. package/dist/orchestrator/ai-skills/writer.d.ts +15 -0
  123. package/dist/orchestrator/ai-skills/writer.d.ts.map +1 -0
  124. package/dist/orchestrator/ai-skills/writer.js +129 -0
  125. package/dist/orchestrator/ai-skills/writer.js.map +1 -0
  126. package/dist/orchestrator/flow-manager.d.ts +11 -0
  127. package/dist/orchestrator/flow-manager.d.ts.map +1 -1
  128. package/dist/orchestrator/flow-manager.js +147 -29
  129. package/dist/orchestrator/flow-manager.js.map +1 -1
  130. package/dist/orchestrator/mobile-runtime-scaffolder.d.ts +59 -0
  131. package/dist/orchestrator/mobile-runtime-scaffolder.d.ts.map +1 -0
  132. package/dist/orchestrator/mobile-runtime-scaffolder.js +298 -0
  133. package/dist/orchestrator/mobile-runtime-scaffolder.js.map +1 -0
  134. package/dist/orchestrator/project-initializer.d.ts +44 -7
  135. package/dist/orchestrator/project-initializer.d.ts.map +1 -1
  136. package/dist/orchestrator/project-initializer.js +147 -79
  137. package/dist/orchestrator/project-initializer.js.map +1 -1
  138. package/dist/orchestrator/screen-manager.d.ts +30 -0
  139. package/dist/orchestrator/screen-manager.d.ts.map +1 -1
  140. package/dist/orchestrator/screen-manager.js +205 -22
  141. package/dist/orchestrator/screen-manager.js.map +1 -1
  142. package/dist/orchestrator/templates/ai-src/README.md +58 -0
  143. package/dist/orchestrator/templates/{ai-instructions/claude-cmd-add-flow.md → ai-src/commands/add-flow.md} +63 -2
  144. package/dist/orchestrator/templates/{ai-instructions/claude-cmd-add-screen.md → ai-src/commands/add-screen.md} +171 -6
  145. package/dist/orchestrator/templates/{ai-instructions/claude-cmd-create-test.md → ai-src/commands/create-test.md} +118 -4
  146. package/{src/orchestrator/templates/ai-instructions/claude-cmd-dashboard.md → dist/orchestrator/templates/ai-src/commands/dashboard.md} +25 -3
  147. package/{src/orchestrator/templates/ai-instructions/claude-cmd-delivery.md → dist/orchestrator/templates/ai-src/commands/delivery.md} +67 -3
  148. package/dist/orchestrator/templates/ai-src/commands/design.md +29 -0
  149. package/{src/orchestrator/templates/ai-instructions/claude-cmd-feedback.md → dist/orchestrator/templates/ai-src/commands/feedback.md} +34 -3
  150. package/dist/orchestrator/templates/{ai-instructions/claude-cmd-locale.md → ai-src/commands/locale.md} +56 -4
  151. package/{src/orchestrator/templates/ai-instructions/claude-cmd-review.md → dist/orchestrator/templates/ai-src/commands/review.md} +57 -2
  152. package/{src/orchestrator/templates/ai-instructions/claude-cmd-run-test.md → dist/orchestrator/templates/ai-src/commands/run-test.md} +116 -5
  153. package/dist/orchestrator/templates/ai-src/config/codex.md +8 -0
  154. package/{src/orchestrator/templates/ai-instructions/claude-skill-capture-mobile.md → dist/orchestrator/templates/ai-src/skills/sungen-capture-mobile/SKILL.md} +11 -0
  155. package/dist/orchestrator/templates/{ai-instructions/github-skill-sungen-error-mapping.md → ai-src/skills/sungen-error-mapping/SKILL.md} +49 -9
  156. package/{src/orchestrator/templates/ai-instructions/claude-skill-harness-audit.md → dist/orchestrator/templates/ai-src/skills/sungen-harness-audit/SKILL.md} +5 -5
  157. package/{src/orchestrator/templates/ai-instructions/claude-skill-locale.md → dist/orchestrator/templates/ai-src/skills/sungen-locale/SKILL.md} +275 -3
  158. 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
  159. package/dist/orchestrator/templates/{ai-instructions/claude-skill-selector-fix-mobile.md → ai-src/skills/sungen-selector-fix-mobile/SKILL.md} +5 -0
  160. package/dist/orchestrator/templates/{ai-instructions/claude-skill-tc-generation.md → ai-src/skills/sungen-tc-generation/SKILL.md} +5 -0
  161. package/dist/orchestrator/templates/playwright.config.ts +7 -4
  162. package/package.json +3 -3
  163. package/src/capabilities/discover.ts +29 -3
  164. package/src/cli/commands/add-flow.ts +2 -0
  165. package/src/cli/commands/add.ts +4 -0
  166. package/src/cli/commands/capability.ts +64 -1
  167. package/src/cli/commands/delivery.ts +89 -2
  168. package/src/cli/commands/eval.ts +3 -3
  169. package/src/cli/commands/gate.ts +44 -0
  170. package/src/cli/commands/generate.ts +2 -1
  171. package/src/cli/commands/init.ts +50 -3
  172. package/src/cli/commands/journey.ts +72 -0
  173. package/src/cli/commands/update.ts +14 -4
  174. package/src/cli/index.ts +4 -0
  175. package/src/dashboard/snapshot-builder.ts +10 -1
  176. package/src/exporters/csv-exporter.ts +3 -1
  177. package/src/exporters/json-exporter.ts +3 -1
  178. package/src/exporters/mobile-app-id.ts +34 -0
  179. package/src/exporters/spec-parser.ts +6 -2
  180. package/src/generators/test-generator/adapters/adapter-interface.ts +3 -0
  181. package/src/generators/test-generator/adapters/appium/templates/steps/actions/scroll-action.hbs +7 -4
  182. package/src/generators/test-generator/adapters/appium/templates/steps/actions/toggle-action.hbs +20 -3
  183. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/checked-assertion.hbs +4 -1
  184. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/not-checked-assertion.hbs +4 -1
  185. package/src/generators/test-generator/adapters/appium/templates/test-file.hbs +3 -0
  186. package/src/generators/test-generator/code-generator.ts +49 -9
  187. package/src/generators/test-generator/template-engine.ts +14 -1
  188. package/src/generators/test-generator/utils/selector-resolver.ts +51 -1
  189. package/src/harness/audit.ts +9 -2
  190. package/src/harness/catalog/universal-viewpoints.yaml +15 -0
  191. package/src/harness/eval/skill-lint.ts +71 -60
  192. package/src/harness/journey.ts +434 -0
  193. package/src/harness/quality-gates.ts +20 -0
  194. package/src/harness/sensors.ts +25 -8
  195. package/src/orchestrator/ai-rules-updater.ts +35 -197
  196. package/src/orchestrator/ai-skills/adapters.ts +52 -0
  197. package/src/orchestrator/ai-skills/build.ts +103 -0
  198. package/src/orchestrator/ai-skills/cap-stripper.ts +85 -0
  199. package/src/orchestrator/ai-skills/commands.ts +112 -0
  200. package/src/orchestrator/ai-skills/generator.ts +47 -0
  201. package/src/orchestrator/ai-skills/golden-skills.test.ts +124 -0
  202. package/src/orchestrator/ai-skills/resolve-assistants.ts +18 -0
  203. package/src/orchestrator/ai-skills/writer.ts +92 -0
  204. package/src/orchestrator/flow-manager.ts +165 -29
  205. package/src/orchestrator/mobile-runtime-scaffolder.ts +277 -0
  206. package/src/orchestrator/project-initializer.ts +159 -86
  207. package/src/orchestrator/screen-manager.ts +237 -22
  208. package/src/orchestrator/templates/ai-src/README.md +58 -0
  209. package/src/orchestrator/templates/{ai-instructions/claude-cmd-add-flow.md → ai-src/commands/add-flow.md} +63 -2
  210. package/src/orchestrator/templates/{ai-instructions/claude-cmd-add-screen.md → ai-src/commands/add-screen.md} +171 -6
  211. package/src/orchestrator/templates/{ai-instructions/claude-cmd-create-test.md → ai-src/commands/create-test.md} +118 -4
  212. package/{dist/orchestrator/templates/ai-instructions/claude-cmd-dashboard.md → src/orchestrator/templates/ai-src/commands/dashboard.md} +25 -3
  213. package/{dist/orchestrator/templates/ai-instructions/claude-cmd-delivery.md → src/orchestrator/templates/ai-src/commands/delivery.md} +67 -3
  214. package/src/orchestrator/templates/ai-src/commands/design.md +29 -0
  215. package/{dist/orchestrator/templates/ai-instructions/claude-cmd-feedback.md → src/orchestrator/templates/ai-src/commands/feedback.md} +34 -3
  216. package/src/orchestrator/templates/{ai-instructions/claude-cmd-locale.md → ai-src/commands/locale.md} +56 -4
  217. package/{dist/orchestrator/templates/ai-instructions/claude-cmd-review.md → src/orchestrator/templates/ai-src/commands/review.md} +57 -2
  218. package/{dist/orchestrator/templates/ai-instructions/claude-cmd-run-test.md → src/orchestrator/templates/ai-src/commands/run-test.md} +116 -5
  219. package/src/orchestrator/templates/ai-src/config/codex.md +8 -0
  220. package/{dist/orchestrator/templates/ai-instructions/claude-skill-capture-mobile.md → src/orchestrator/templates/ai-src/skills/sungen-capture-mobile/SKILL.md} +11 -0
  221. package/{dist/orchestrator/templates/ai-instructions/claude-skill-error-mapping.md → src/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md} +49 -9
  222. package/{dist/orchestrator/templates/ai-instructions/claude-skill-harness-audit.md → src/orchestrator/templates/ai-src/skills/sungen-harness-audit/SKILL.md} +5 -5
  223. package/{dist/orchestrator/templates/ai-instructions/claude-skill-locale.md → src/orchestrator/templates/ai-src/skills/sungen-locale/SKILL.md} +275 -3
  224. 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
  225. package/src/orchestrator/templates/{ai-instructions/claude-skill-selector-fix-mobile.md → ai-src/skills/sungen-selector-fix-mobile/SKILL.md} +5 -0
  226. package/src/orchestrator/templates/{ai-instructions/claude-skill-tc-generation.md → ai-src/skills/sungen-tc-generation/SKILL.md} +5 -0
  227. package/src/orchestrator/templates/playwright.config.ts +7 -4
  228. package/dist/orchestrator/templates/ai-instructions/claude-cmd-design.md +0 -12
  229. package/dist/orchestrator/templates/ai-instructions/claude-skill-selector-fix.md +0 -347
  230. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-add-flow.md +0 -86
  231. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +0 -131
  232. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +0 -93
  233. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-dashboard.md +0 -65
  234. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-delivery.md +0 -83
  235. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-design.md +0 -13
  236. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-feedback.md +0 -24
  237. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-locale.md +0 -70
  238. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-review.md +0 -38
  239. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +0 -155
  240. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mobile.md +0 -184
  241. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-harness-audit.md +0 -85
  242. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-locale.md +0 -291
  243. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix-mobile.md +0 -316
  244. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-keys.md +0 -221
  245. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +0 -617
  246. package/src/orchestrator/templates/ai-instructions/claude-cmd-design.md +0 -12
  247. package/src/orchestrator/templates/ai-instructions/claude-skill-api-design.md +0 -62
  248. package/src/orchestrator/templates/ai-instructions/claude-skill-capture-mode-figma-mcp.md +0 -82
  249. package/src/orchestrator/templates/ai-instructions/claude-skill-capture-mode-figma-pat.md +0 -117
  250. package/src/orchestrator/templates/ai-instructions/claude-skill-capture-mode-live.md +0 -60
  251. package/src/orchestrator/templates/ai-instructions/claude-skill-capture-mode-local.md +0 -38
  252. package/src/orchestrator/templates/ai-instructions/claude-skill-capture.md +0 -35
  253. package/src/orchestrator/templates/ai-instructions/claude-skill-dashboard.md +0 -121
  254. package/src/orchestrator/templates/ai-instructions/claude-skill-delivery.md +0 -141
  255. package/src/orchestrator/templates/ai-instructions/claude-skill-error-mapping.md +0 -157
  256. package/src/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +0 -446
  257. package/src/orchestrator/templates/ai-instructions/claude-skill-ingest-legacy.md +0 -79
  258. package/src/orchestrator/templates/ai-instructions/claude-skill-mobile-gestures.md +0 -109
  259. package/src/orchestrator/templates/ai-instructions/claude-skill-selector-fix.md +0 -347
  260. package/src/orchestrator/templates/ai-instructions/claude-skill-tc-review.md +0 -320
  261. package/src/orchestrator/templates/ai-instructions/claude-skill-test-design-techniques.md +0 -118
  262. package/src/orchestrator/templates/ai-instructions/claude-skill-viewpoint-group-a-data-entry.md +0 -203
  263. package/src/orchestrator/templates/ai-instructions/claude-skill-viewpoint-group-b-data-ops.md +0 -179
  264. package/src/orchestrator/templates/ai-instructions/claude-skill-viewpoint-group-c-data-explore.md +0 -233
  265. package/src/orchestrator/templates/ai-instructions/claude-skill-viewpoint-group-d-display.md +0 -226
  266. package/src/orchestrator/templates/ai-instructions/claude-skill-viewpoint-group-e-identity.md +0 -177
  267. package/src/orchestrator/templates/ai-instructions/claude-skill-viewpoint.md +0 -118
  268. package/src/orchestrator/templates/ai-instructions/copilot-cmd-add-flow.md +0 -86
  269. package/src/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +0 -131
  270. package/src/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +0 -93
  271. package/src/orchestrator/templates/ai-instructions/copilot-cmd-dashboard.md +0 -65
  272. package/src/orchestrator/templates/ai-instructions/copilot-cmd-delivery.md +0 -83
  273. package/src/orchestrator/templates/ai-instructions/copilot-cmd-design.md +0 -13
  274. package/src/orchestrator/templates/ai-instructions/copilot-cmd-feedback.md +0 -24
  275. package/src/orchestrator/templates/ai-instructions/copilot-cmd-locale.md +0 -70
  276. package/src/orchestrator/templates/ai-instructions/copilot-cmd-review.md +0 -38
  277. package/src/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +0 -155
  278. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-api-design.md +0 -62
  279. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mobile.md +0 -184
  280. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-figma-mcp.md +0 -82
  281. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-figma-pat.md +0 -117
  282. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-live.md +0 -60
  283. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-local.md +0 -38
  284. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture.md +0 -35
  285. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-dashboard.md +0 -121
  286. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-delivery.md +0 -141
  287. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-error-mapping.md +0 -157
  288. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +0 -446
  289. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-harness-audit.md +0 -85
  290. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-ingest-legacy.md +0 -79
  291. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-locale.md +0 -291
  292. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-mobile-gestures.md +0 -109
  293. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix-mobile.md +0 -316
  294. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-keys.md +0 -221
  295. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +0 -617
  296. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-review.md +0 -320
  297. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-test-design-techniques.md +0 -118
  298. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-viewpoint-group-a-data-entry.md +0 -203
  299. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-viewpoint-group-b-data-ops.md +0 -179
  300. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-viewpoint-group-c-data-explore.md +0 -233
  301. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-viewpoint-group-d-display.md +0 -226
  302. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-viewpoint-group-e-identity.md +0 -177
  303. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-viewpoint.md +0 -118
  304. /package/dist/orchestrator/templates/{ai-instructions/claude-agent-challenge.md → ai-src/agents/challenge.md} +0 -0
  305. /package/dist/orchestrator/templates/{ai-instructions/claude-agent-discovery.md → ai-src/agents/discovery.md} +0 -0
  306. /package/dist/orchestrator/templates/{ai-instructions/claude-agent-generator.md → ai-src/agents/generator.md} +0 -0
  307. /package/dist/orchestrator/templates/{ai-instructions/claude-agent-reviewer.md → ai-src/agents/reviewer.md} +0 -0
  308. /package/dist/orchestrator/templates/{ai-instructions/claude-config.md → ai-src/config/claude.md} +0 -0
  309. /package/dist/orchestrator/templates/{ai-instructions/copilot-config.md → ai-src/config/copilot.md} +0 -0
  310. /package/dist/orchestrator/templates/{ai-instructions/claude-skill-api-design.md → ai-src/skills/sungen-api-design/SKILL.md} +0 -0
  311. /package/dist/orchestrator/templates/{ai-instructions/claude-skill-capture.md → ai-src/skills/sungen-capture/SKILL.md} +0 -0
  312. /package/dist/orchestrator/templates/{ai-instructions/claude-skill-capture-mode-figma-mcp.md → ai-src/skills/sungen-capture/mode-figma-mcp.md} +0 -0
  313. /package/dist/orchestrator/templates/{ai-instructions/claude-skill-capture-mode-figma-pat.md → ai-src/skills/sungen-capture/mode-figma-pat.md} +0 -0
  314. /package/dist/orchestrator/templates/{ai-instructions/claude-skill-capture-mode-live.md → ai-src/skills/sungen-capture/mode-live.md} +0 -0
  315. /package/dist/orchestrator/templates/{ai-instructions/claude-skill-capture-mode-local.md → ai-src/skills/sungen-capture/mode-local.md} +0 -0
  316. /package/dist/orchestrator/templates/{ai-instructions/claude-skill-dashboard.md → ai-src/skills/sungen-dashboard/SKILL.md} +0 -0
  317. /package/dist/orchestrator/templates/{ai-instructions/claude-skill-delivery.md → ai-src/skills/sungen-delivery/SKILL.md} +0 -0
  318. /package/dist/orchestrator/templates/{ai-instructions/claude-skill-gherkin-syntax.md → ai-src/skills/sungen-gherkin-syntax/SKILL.md} +0 -0
  319. /package/dist/orchestrator/templates/{ai-instructions/claude-skill-ingest-legacy.md → ai-src/skills/sungen-ingest-legacy/SKILL.md} +0 -0
  320. /package/dist/orchestrator/templates/{ai-instructions/claude-skill-mobile-gestures.md → ai-src/skills/sungen-mobile-gestures/SKILL.md} +0 -0
  321. /package/dist/orchestrator/templates/{ai-instructions/claude-skill-selector-keys.md → ai-src/skills/sungen-selector-keys/SKILL.md} +0 -0
  322. /package/dist/orchestrator/templates/{ai-instructions/claude-skill-tc-review.md → ai-src/skills/sungen-tc-review/SKILL.md} +0 -0
  323. /package/dist/orchestrator/templates/{ai-instructions/claude-skill-test-design-techniques.md → ai-src/skills/sungen-test-design-techniques/SKILL.md} +0 -0
  324. /package/dist/orchestrator/templates/{ai-instructions/claude-skill-viewpoint.md → ai-src/skills/sungen-viewpoint/SKILL.md} +0 -0
  325. /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
  326. /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
  327. /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
  328. /package/dist/orchestrator/templates/{ai-instructions/claude-skill-viewpoint-group-d-display.md → ai-src/skills/sungen-viewpoint/group-d-display.md} +0 -0
  329. /package/dist/orchestrator/templates/{ai-instructions/claude-skill-viewpoint-group-e-identity.md → ai-src/skills/sungen-viewpoint/group-e-identity.md} +0 -0
  330. /package/src/orchestrator/templates/{ai-instructions/claude-agent-challenge.md → ai-src/agents/challenge.md} +0 -0
  331. /package/src/orchestrator/templates/{ai-instructions/claude-agent-discovery.md → ai-src/agents/discovery.md} +0 -0
  332. /package/src/orchestrator/templates/{ai-instructions/claude-agent-generator.md → ai-src/agents/generator.md} +0 -0
  333. /package/src/orchestrator/templates/{ai-instructions/claude-agent-reviewer.md → ai-src/agents/reviewer.md} +0 -0
  334. /package/src/orchestrator/templates/{ai-instructions/claude-config.md → ai-src/config/claude.md} +0 -0
  335. /package/src/orchestrator/templates/{ai-instructions/copilot-config.md → ai-src/config/copilot.md} +0 -0
  336. /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
  337. /package/{dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture.md → src/orchestrator/templates/ai-src/skills/sungen-capture/SKILL.md} +0 -0
  338. /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
  339. /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
  340. /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
  341. /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
  342. /package/{dist/orchestrator/templates/ai-instructions/github-skill-sungen-dashboard.md → src/orchestrator/templates/ai-src/skills/sungen-dashboard/SKILL.md} +0 -0
  343. /package/{dist/orchestrator/templates/ai-instructions/github-skill-sungen-delivery.md → src/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md} +0 -0
  344. /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
  345. /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
  346. /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
  347. /package/src/orchestrator/templates/{ai-instructions/claude-skill-selector-keys.md → ai-src/skills/sungen-selector-keys/SKILL.md} +0 -0
  348. /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
  349. /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
  350. /package/{dist/orchestrator/templates/ai-instructions/github-skill-sungen-viewpoint.md → src/orchestrator/templates/ai-src/skills/sungen-viewpoint/SKILL.md} +0 -0
  351. /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
  352. /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
  353. /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
  354. /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
  355. /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
- * A driver that isn't installed is simply skipped (capabilities are opt-in). Errors thrown *inside* a
14
- * present driver propagate only a genuinely absent package is swallowed (resolve-then-require).
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
- // not installed at any anchor — capabilities are opt-in, so skip silently
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);
@@ -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
- .action((driver: string, o: { skipInstall?: boolean }) => {
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
- featurePath,
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
- featurePath,
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,
@@ -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, claude↔github sync, registration')
9
- .option('--dir <path>', 'Templates dir to lint (default: bundled ai-instructions)')
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 · variant-sync · registration)');
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);
@@ -0,0 +1,44 @@
1
+ import { Command } from 'commander';
2
+ import * as path from 'path';
3
+ import * as fs from 'fs';
4
+ import { runGate, renderGate, GatePhase } from '../../harness/journey';
5
+
6
+ function findScreenDir(name: string): string | null {
7
+ const candidates = [
8
+ path.join(process.cwd(), 'qa', 'screens', name),
9
+ path.join(process.cwd(), 'qa', 'flows', name),
10
+ path.join(process.cwd(), 'qa', 'api', name),
11
+ ];
12
+ for (const c of candidates) if (fs.existsSync(c)) return c;
13
+ return null;
14
+ }
15
+
16
+ const PHASES: GatePhase[] = ['create', 'run', 'deliver'];
17
+
18
+ export function registerGateCommand(program: Command): void {
19
+ program
20
+ .command('gate')
21
+ .description('Inter-phase HALT gate (#381): a phase boundary passes only when its required obligations are satisfied or explicitly waived. Exit 2 = HALT (no silent bad output crosses the boundary).')
22
+ .option('-s, --screen <name>', 'Screen / flow / api unit name')
23
+ .option('-p, --phase <phase>', `Phase boundary: ${PHASES.join(' | ')}`)
24
+ .option('--json', 'Output the raw verdict')
25
+ .action((options) => {
26
+ try {
27
+ const name = options.screen;
28
+ if (!name) throw new Error('Provide --screen <name>');
29
+ const phase = options.phase as GatePhase;
30
+ if (!PHASES.includes(phase)) throw new Error(`Provide --phase <${PHASES.join('|')}>`);
31
+ if (!findScreenDir(name)) throw new Error(`Not found: qa/screens/${name}, qa/flows/${name}, or qa/api/${name}`);
32
+
33
+ const verdict = runGate(process.cwd(), name, phase);
34
+ if (options.json) console.log(JSON.stringify(verdict, null, 2));
35
+ else console.log(renderGate(verdict));
36
+
37
+ // Exit 2 on HALT — usable in CI / the orchestration loop to block the next phase.
38
+ process.exit(verdict.status === 'halt' ? 2 : 0);
39
+ } catch (error) {
40
+ console.error('Error:', error instanceof Error ? error.message : error);
41
+ process.exit(1);
42
+ }
43
+ });
44
+ }
@@ -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
- console.log(`Next step: npx playwright test --ui\n`);
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);
@@ -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
- .requiredOption('--base-url <url>', 'Base URL for Playwright tests (e.g. https://your-app.com)')
8
- .action(async (projectName: string | undefined, options: { baseUrl: string }) => {
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);
@@ -0,0 +1,72 @@
1
+ import { Command } from 'commander';
2
+ import * as path from 'path';
3
+ import * as fs from 'fs';
4
+ import { runJourney, waive, signoff, renderJourneyBoard, rollupJourney, renderRollupBoard } from '../../harness/journey';
5
+ import { reportSlug } from '../../harness/unit-paths';
6
+
7
+ function findScreenDir(name: string): string | null {
8
+ const candidates = [
9
+ path.join(process.cwd(), 'qa', 'screens', name),
10
+ path.join(process.cwd(), 'qa', 'flows', name),
11
+ path.join(process.cwd(), 'qa', 'api', name),
12
+ ];
13
+ for (const c of candidates) if (fs.existsSync(c)) return c;
14
+ return null;
15
+ }
16
+
17
+ export function registerJourneyCommand(program: Command): void {
18
+ program
19
+ .command('journey')
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
+ .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')
23
+ .option('--waive <obligation>', 'Waive an obligation (e.g. OB-coverage) — requires --reason')
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)')
27
+ .option('--json', 'Output the raw JSON report')
28
+ .action((options) => {
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
+
42
+ const name = options.screen;
43
+ if (!name) throw new Error('Provide --screen <name> (or --all for the roll-up)');
44
+ if (!findScreenDir(name)) throw new Error(`Not found: qa/screens/${name}, qa/flows/${name}, or qa/api/${name}`);
45
+
46
+ const report = options.waive
47
+ ? waive(process.cwd(), name, options.waive, options.reason || '')
48
+ : options.signoff
49
+ ? signoff(process.cwd(), name, options.by)
50
+ : runJourney(process.cwd(), name);
51
+
52
+ const outDir = path.join(process.cwd(), '.sungen', 'journey');
53
+ fs.mkdirSync(outDir, { recursive: true });
54
+ const slug = reportSlug(name);
55
+ fs.writeFileSync(path.join(outDir, `${slug}.json`), JSON.stringify(report, null, 2), 'utf-8');
56
+ const board = renderJourneyBoard(report);
57
+ fs.writeFileSync(path.join(outDir, `${slug}.board.md`), board, 'utf-8');
58
+
59
+ if (options.json) {
60
+ console.log(JSON.stringify(report, null, 2));
61
+ } else {
62
+ console.log('');
63
+ console.log(board);
64
+ console.log(` Board: ${path.relative(process.cwd(), path.join(outDir, `${slug}.board.md`))}`);
65
+ console.log('');
66
+ }
67
+ } catch (error) {
68
+ console.error('Error:', error instanceof Error ? error.message : error);
69
+ process.exit(1);
70
+ }
71
+ });
72
+ }
@@ -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',
package/src/cli/index.ts CHANGED
@@ -16,6 +16,8 @@ import { registerAddFlowCommand } from './commands/add-flow';
16
16
  import { registerDashboardCommand } from './commands/dashboard';
17
17
  import { registerAuditCommand } from './commands/audit';
18
18
  import { registerDepthLintCommand } from './commands/depth-lint';
19
+ import { registerJourneyCommand } from './commands/journey';
20
+ import { registerGateCommand } from './commands/gate';
19
21
  import { registerIngestCommand } from './commands/ingest';
20
22
  import { registerEvalCommand } from './commands/eval';
21
23
  import { registerManifestCommand } from './commands/manifest';
@@ -59,6 +61,8 @@ async function main() {
59
61
  registerDashboardCommand(program);
60
62
  registerAuditCommand(program);
61
63
  registerDepthLintCommand(program);
64
+ registerJourneyCommand(program);
65
+ registerGateCommand(program);
62
66
  registerManifestCommand(program);
63
67
  registerLedgerCommand(program);
64
68
  registerFeedbackCommand(program);
@@ -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
- featurePath,
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
- const url = p ? `${base}/${p}` : base;
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