@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.
Files changed (346) 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/generate.d.ts.map +1 -1
  19. package/dist/cli/commands/generate.js +2 -1
  20. package/dist/cli/commands/generate.js.map +1 -1
  21. package/dist/cli/commands/init.d.ts.map +1 -1
  22. package/dist/cli/commands/init.js +32 -2
  23. package/dist/cli/commands/init.js.map +1 -1
  24. package/dist/cli/commands/journey.d.ts.map +1 -1
  25. package/dist/cli/commands/journey.js +24 -2
  26. package/dist/cli/commands/journey.js.map +1 -1
  27. package/dist/cli/commands/update.d.ts.map +1 -1
  28. package/dist/cli/commands/update.js +10 -3
  29. package/dist/cli/commands/update.js.map +1 -1
  30. package/dist/dashboard/snapshot-builder.d.ts.map +1 -1
  31. package/dist/dashboard/snapshot-builder.js +10 -1
  32. package/dist/dashboard/snapshot-builder.js.map +1 -1
  33. package/dist/exporters/csv-exporter.d.ts.map +1 -1
  34. package/dist/exporters/csv-exporter.js +3 -1
  35. package/dist/exporters/csv-exporter.js.map +1 -1
  36. package/dist/exporters/json-exporter.js +3 -1
  37. package/dist/exporters/json-exporter.js.map +1 -1
  38. package/dist/exporters/mobile-app-id.d.ts +22 -0
  39. package/dist/exporters/mobile-app-id.d.ts.map +1 -0
  40. package/dist/exporters/mobile-app-id.js +37 -0
  41. package/dist/exporters/mobile-app-id.js.map +1 -0
  42. package/dist/exporters/spec-parser.d.ts.map +1 -1
  43. package/dist/exporters/spec-parser.js +6 -2
  44. package/dist/exporters/spec-parser.js.map +1 -1
  45. package/dist/generators/test-generator/adapters/adapter-interface.d.ts +3 -0
  46. package/dist/generators/test-generator/adapters/adapter-interface.d.ts.map +1 -1
  47. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/scroll-action.hbs +7 -4
  48. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/toggle-action.hbs +20 -3
  49. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/checked-assertion.hbs +4 -1
  50. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/not-checked-assertion.hbs +4 -1
  51. package/dist/generators/test-generator/adapters/appium/templates/test-file.hbs +3 -0
  52. package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
  53. package/dist/generators/test-generator/code-generator.js +48 -9
  54. package/dist/generators/test-generator/code-generator.js.map +1 -1
  55. package/dist/generators/test-generator/template-engine.d.ts +3 -0
  56. package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
  57. package/dist/generators/test-generator/template-engine.js +11 -1
  58. package/dist/generators/test-generator/template-engine.js.map +1 -1
  59. package/dist/generators/test-generator/utils/selector-resolver.d.ts +11 -1
  60. package/dist/generators/test-generator/utils/selector-resolver.d.ts.map +1 -1
  61. package/dist/generators/test-generator/utils/selector-resolver.js +33 -0
  62. package/dist/generators/test-generator/utils/selector-resolver.js.map +1 -1
  63. package/dist/harness/audit.d.ts.map +1 -1
  64. package/dist/harness/audit.js +8 -1
  65. package/dist/harness/audit.js.map +1 -1
  66. package/dist/harness/catalog/universal-viewpoints.yaml +15 -0
  67. package/dist/harness/eval/skill-lint.d.ts +3 -3
  68. package/dist/harness/eval/skill-lint.d.ts.map +1 -1
  69. package/dist/harness/eval/skill-lint.js +88 -68
  70. package/dist/harness/eval/skill-lint.js.map +1 -1
  71. package/dist/harness/journey.d.ts +37 -1
  72. package/dist/harness/journey.d.ts.map +1 -1
  73. package/dist/harness/journey.js +146 -19
  74. package/dist/harness/journey.js.map +1 -1
  75. package/dist/harness/quality-gates.d.ts +13 -0
  76. package/dist/harness/quality-gates.d.ts.map +1 -1
  77. package/dist/harness/quality-gates.js +23 -0
  78. package/dist/harness/quality-gates.js.map +1 -1
  79. package/dist/harness/sensors.d.ts +1 -1
  80. package/dist/harness/sensors.d.ts.map +1 -1
  81. package/dist/harness/sensors.js +32 -9
  82. package/dist/harness/sensors.js.map +1 -1
  83. package/dist/orchestrator/ai-rules-updater.d.ts +11 -3
  84. package/dist/orchestrator/ai-rules-updater.d.ts.map +1 -1
  85. package/dist/orchestrator/ai-rules-updater.js +44 -180
  86. package/dist/orchestrator/ai-rules-updater.js.map +1 -1
  87. package/dist/orchestrator/ai-skills/adapters.d.ts +28 -0
  88. package/dist/orchestrator/ai-skills/adapters.d.ts.map +1 -0
  89. package/dist/orchestrator/ai-skills/adapters.js +34 -0
  90. package/dist/orchestrator/ai-skills/adapters.js.map +1 -0
  91. package/dist/orchestrator/ai-skills/build.d.ts +10 -0
  92. package/dist/orchestrator/ai-skills/build.d.ts.map +1 -0
  93. package/dist/orchestrator/ai-skills/build.js +128 -0
  94. package/dist/orchestrator/ai-skills/build.js.map +1 -0
  95. package/dist/orchestrator/ai-skills/cap-stripper.d.ts +27 -0
  96. package/dist/orchestrator/ai-skills/cap-stripper.d.ts.map +1 -0
  97. package/dist/orchestrator/ai-skills/cap-stripper.js +78 -0
  98. package/dist/orchestrator/ai-skills/cap-stripper.js.map +1 -0
  99. package/dist/orchestrator/ai-skills/commands.d.ts +22 -0
  100. package/dist/orchestrator/ai-skills/commands.d.ts.map +1 -0
  101. package/dist/orchestrator/ai-skills/commands.js +109 -0
  102. package/dist/orchestrator/ai-skills/commands.js.map +1 -0
  103. package/dist/orchestrator/ai-skills/generator.d.ts +23 -0
  104. package/dist/orchestrator/ai-skills/generator.d.ts.map +1 -0
  105. package/dist/orchestrator/ai-skills/generator.js +79 -0
  106. package/dist/orchestrator/ai-skills/generator.js.map +1 -0
  107. package/dist/orchestrator/ai-skills/golden-skills.test.d.ts +2 -0
  108. package/dist/orchestrator/ai-skills/golden-skills.test.d.ts.map +1 -0
  109. package/dist/orchestrator/ai-skills/golden-skills.test.js +183 -0
  110. package/dist/orchestrator/ai-skills/golden-skills.test.js.map +1 -0
  111. package/dist/orchestrator/ai-skills/resolve-assistants.d.ts +8 -0
  112. package/dist/orchestrator/ai-skills/resolve-assistants.d.ts.map +1 -0
  113. package/dist/orchestrator/ai-skills/resolve-assistants.js +28 -0
  114. package/dist/orchestrator/ai-skills/resolve-assistants.js.map +1 -0
  115. package/dist/orchestrator/ai-skills/writer.d.ts +15 -0
  116. package/dist/orchestrator/ai-skills/writer.d.ts.map +1 -0
  117. package/dist/orchestrator/ai-skills/writer.js +129 -0
  118. package/dist/orchestrator/ai-skills/writer.js.map +1 -0
  119. package/dist/orchestrator/flow-manager.d.ts +11 -0
  120. package/dist/orchestrator/flow-manager.d.ts.map +1 -1
  121. package/dist/orchestrator/flow-manager.js +147 -29
  122. package/dist/orchestrator/flow-manager.js.map +1 -1
  123. package/dist/orchestrator/mobile-runtime-scaffolder.d.ts +59 -0
  124. package/dist/orchestrator/mobile-runtime-scaffolder.d.ts.map +1 -0
  125. package/dist/orchestrator/mobile-runtime-scaffolder.js +298 -0
  126. package/dist/orchestrator/mobile-runtime-scaffolder.js.map +1 -0
  127. package/dist/orchestrator/project-initializer.d.ts +44 -7
  128. package/dist/orchestrator/project-initializer.d.ts.map +1 -1
  129. package/dist/orchestrator/project-initializer.js +147 -79
  130. package/dist/orchestrator/project-initializer.js.map +1 -1
  131. package/dist/orchestrator/screen-manager.d.ts +30 -0
  132. package/dist/orchestrator/screen-manager.d.ts.map +1 -1
  133. package/dist/orchestrator/screen-manager.js +205 -22
  134. package/dist/orchestrator/screen-manager.js.map +1 -1
  135. package/dist/orchestrator/templates/ai-src/README.md +58 -0
  136. package/dist/orchestrator/templates/{ai-instructions/claude-cmd-add-flow.md → ai-src/commands/add-flow.md} +63 -2
  137. package/dist/orchestrator/templates/{ai-instructions/claude-cmd-add-screen.md → ai-src/commands/add-screen.md} +171 -6
  138. package/dist/orchestrator/templates/{ai-instructions/claude-cmd-create-test.md → ai-src/commands/create-test.md} +118 -5
  139. package/{src/orchestrator/templates/ai-instructions/claude-cmd-dashboard.md → dist/orchestrator/templates/ai-src/commands/dashboard.md} +25 -3
  140. package/{src/orchestrator/templates/ai-instructions/claude-cmd-delivery.md → dist/orchestrator/templates/ai-src/commands/delivery.md} +67 -3
  141. package/dist/orchestrator/templates/ai-src/commands/design.md +29 -0
  142. package/{src/orchestrator/templates/ai-instructions/claude-cmd-feedback.md → dist/orchestrator/templates/ai-src/commands/feedback.md} +34 -3
  143. package/dist/orchestrator/templates/{ai-instructions/claude-cmd-locale.md → ai-src/commands/locale.md} +56 -4
  144. package/{src/orchestrator/templates/ai-instructions/claude-cmd-review.md → dist/orchestrator/templates/ai-src/commands/review.md} +57 -2
  145. package/dist/orchestrator/templates/{ai-instructions/claude-cmd-run-test.md → ai-src/commands/run-test.md} +116 -8
  146. package/dist/orchestrator/templates/ai-src/config/codex.md +8 -0
  147. package/{src/orchestrator/templates/ai-instructions/claude-skill-capture-mobile.md → dist/orchestrator/templates/ai-src/skills/sungen-capture-mobile/SKILL.md} +11 -0
  148. package/dist/orchestrator/templates/{ai-instructions/github-skill-sungen-error-mapping.md → ai-src/skills/sungen-error-mapping/SKILL.md} +31 -10
  149. package/{src/orchestrator/templates/ai-instructions/claude-skill-harness-audit.md → dist/orchestrator/templates/ai-src/skills/sungen-harness-audit/SKILL.md} +5 -5
  150. package/{src/orchestrator/templates/ai-instructions/claude-skill-locale.md → dist/orchestrator/templates/ai-src/skills/sungen-locale/SKILL.md} +275 -3
  151. 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
  152. 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
  153. package/dist/orchestrator/templates/{ai-instructions/claude-skill-tc-generation.md → ai-src/skills/sungen-tc-generation/SKILL.md} +5 -0
  154. package/dist/orchestrator/templates/playwright.config.ts +7 -4
  155. package/package.json +3 -3
  156. package/src/capabilities/discover.ts +29 -3
  157. package/src/cli/commands/add-flow.ts +2 -0
  158. package/src/cli/commands/add.ts +4 -0
  159. package/src/cli/commands/capability.ts +64 -1
  160. package/src/cli/commands/delivery.ts +89 -2
  161. package/src/cli/commands/eval.ts +3 -3
  162. package/src/cli/commands/generate.ts +2 -1
  163. package/src/cli/commands/init.ts +50 -3
  164. package/src/cli/commands/journey.ts +20 -3
  165. package/src/cli/commands/update.ts +14 -4
  166. package/src/dashboard/snapshot-builder.ts +10 -1
  167. package/src/exporters/csv-exporter.ts +3 -1
  168. package/src/exporters/json-exporter.ts +3 -1
  169. package/src/exporters/mobile-app-id.ts +34 -0
  170. package/src/exporters/spec-parser.ts +6 -2
  171. package/src/generators/test-generator/adapters/adapter-interface.ts +3 -0
  172. package/src/generators/test-generator/adapters/appium/templates/steps/actions/scroll-action.hbs +7 -4
  173. package/src/generators/test-generator/adapters/appium/templates/steps/actions/toggle-action.hbs +20 -3
  174. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/checked-assertion.hbs +4 -1
  175. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/not-checked-assertion.hbs +4 -1
  176. package/src/generators/test-generator/adapters/appium/templates/test-file.hbs +3 -0
  177. package/src/generators/test-generator/code-generator.ts +49 -9
  178. package/src/generators/test-generator/template-engine.ts +14 -1
  179. package/src/generators/test-generator/utils/selector-resolver.ts +51 -1
  180. package/src/harness/audit.ts +9 -2
  181. package/src/harness/catalog/universal-viewpoints.yaml +15 -0
  182. package/src/harness/eval/skill-lint.ts +71 -60
  183. package/src/harness/journey.ts +167 -19
  184. package/src/harness/quality-gates.ts +20 -0
  185. package/src/harness/sensors.ts +25 -8
  186. package/src/orchestrator/ai-rules-updater.ts +35 -197
  187. package/src/orchestrator/ai-skills/adapters.ts +52 -0
  188. package/src/orchestrator/ai-skills/build.ts +103 -0
  189. package/src/orchestrator/ai-skills/cap-stripper.ts +85 -0
  190. package/src/orchestrator/ai-skills/commands.ts +112 -0
  191. package/src/orchestrator/ai-skills/generator.ts +47 -0
  192. package/src/orchestrator/ai-skills/golden-skills.test.ts +124 -0
  193. package/src/orchestrator/ai-skills/resolve-assistants.ts +18 -0
  194. package/src/orchestrator/ai-skills/writer.ts +92 -0
  195. package/src/orchestrator/flow-manager.ts +165 -29
  196. package/src/orchestrator/mobile-runtime-scaffolder.ts +277 -0
  197. package/src/orchestrator/project-initializer.ts +159 -86
  198. package/src/orchestrator/screen-manager.ts +237 -22
  199. package/src/orchestrator/templates/ai-src/README.md +58 -0
  200. package/src/orchestrator/templates/{ai-instructions/claude-cmd-add-flow.md → ai-src/commands/add-flow.md} +63 -2
  201. package/src/orchestrator/templates/{ai-instructions/claude-cmd-add-screen.md → ai-src/commands/add-screen.md} +171 -6
  202. package/src/orchestrator/templates/{ai-instructions/claude-cmd-create-test.md → ai-src/commands/create-test.md} +118 -5
  203. package/{dist/orchestrator/templates/ai-instructions/claude-cmd-dashboard.md → src/orchestrator/templates/ai-src/commands/dashboard.md} +25 -3
  204. package/{dist/orchestrator/templates/ai-instructions/claude-cmd-delivery.md → src/orchestrator/templates/ai-src/commands/delivery.md} +67 -3
  205. package/src/orchestrator/templates/ai-src/commands/design.md +29 -0
  206. package/{dist/orchestrator/templates/ai-instructions/claude-cmd-feedback.md → src/orchestrator/templates/ai-src/commands/feedback.md} +34 -3
  207. package/src/orchestrator/templates/{ai-instructions/claude-cmd-locale.md → ai-src/commands/locale.md} +56 -4
  208. package/{dist/orchestrator/templates/ai-instructions/claude-cmd-review.md → src/orchestrator/templates/ai-src/commands/review.md} +57 -2
  209. package/src/orchestrator/templates/{ai-instructions/claude-cmd-run-test.md → ai-src/commands/run-test.md} +116 -8
  210. package/src/orchestrator/templates/ai-src/config/codex.md +8 -0
  211. package/{dist/orchestrator/templates/ai-instructions/claude-skill-capture-mobile.md → src/orchestrator/templates/ai-src/skills/sungen-capture-mobile/SKILL.md} +11 -0
  212. package/{dist/orchestrator/templates/ai-instructions/claude-skill-error-mapping.md → src/orchestrator/templates/ai-src/skills/sungen-error-mapping/SKILL.md} +31 -10
  213. package/{dist/orchestrator/templates/ai-instructions/claude-skill-harness-audit.md → src/orchestrator/templates/ai-src/skills/sungen-harness-audit/SKILL.md} +5 -5
  214. package/{dist/orchestrator/templates/ai-instructions/claude-skill-locale.md → src/orchestrator/templates/ai-src/skills/sungen-locale/SKILL.md} +275 -3
  215. 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
  216. 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
  217. package/src/orchestrator/templates/{ai-instructions/claude-skill-tc-generation.md → ai-src/skills/sungen-tc-generation/SKILL.md} +5 -0
  218. package/src/orchestrator/templates/playwright.config.ts +7 -4
  219. package/dist/orchestrator/templates/ai-instructions/claude-cmd-design.md +0 -12
  220. package/dist/orchestrator/templates/ai-instructions/claude-skill-selector-fix.md +0 -347
  221. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-add-flow.md +0 -86
  222. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +0 -131
  223. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +0 -94
  224. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-dashboard.md +0 -65
  225. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-delivery.md +0 -83
  226. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-design.md +0 -13
  227. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-feedback.md +0 -24
  228. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-locale.md +0 -70
  229. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-review.md +0 -38
  230. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +0 -158
  231. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mobile.md +0 -184
  232. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-harness-audit.md +0 -85
  233. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-locale.md +0 -291
  234. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix-mobile.md +0 -316
  235. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-keys.md +0 -221
  236. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +0 -617
  237. package/src/orchestrator/templates/ai-instructions/claude-cmd-design.md +0 -12
  238. package/src/orchestrator/templates/ai-instructions/claude-skill-api-design.md +0 -62
  239. package/src/orchestrator/templates/ai-instructions/claude-skill-capture-mode-figma-mcp.md +0 -82
  240. package/src/orchestrator/templates/ai-instructions/claude-skill-capture-mode-figma-pat.md +0 -117
  241. package/src/orchestrator/templates/ai-instructions/claude-skill-capture-mode-live.md +0 -60
  242. package/src/orchestrator/templates/ai-instructions/claude-skill-capture-mode-local.md +0 -38
  243. package/src/orchestrator/templates/ai-instructions/claude-skill-capture.md +0 -35
  244. package/src/orchestrator/templates/ai-instructions/claude-skill-dashboard.md +0 -121
  245. package/src/orchestrator/templates/ai-instructions/claude-skill-delivery.md +0 -141
  246. package/src/orchestrator/templates/ai-instructions/claude-skill-error-mapping.md +0 -176
  247. package/src/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +0 -446
  248. package/src/orchestrator/templates/ai-instructions/claude-skill-ingest-legacy.md +0 -79
  249. package/src/orchestrator/templates/ai-instructions/claude-skill-mobile-gestures.md +0 -109
  250. package/src/orchestrator/templates/ai-instructions/claude-skill-selector-fix.md +0 -347
  251. package/src/orchestrator/templates/ai-instructions/claude-skill-tc-review.md +0 -320
  252. package/src/orchestrator/templates/ai-instructions/claude-skill-test-design-techniques.md +0 -118
  253. package/src/orchestrator/templates/ai-instructions/claude-skill-viewpoint-group-a-data-entry.md +0 -203
  254. package/src/orchestrator/templates/ai-instructions/claude-skill-viewpoint-group-b-data-ops.md +0 -179
  255. package/src/orchestrator/templates/ai-instructions/claude-skill-viewpoint-group-c-data-explore.md +0 -233
  256. package/src/orchestrator/templates/ai-instructions/claude-skill-viewpoint-group-d-display.md +0 -226
  257. package/src/orchestrator/templates/ai-instructions/claude-skill-viewpoint-group-e-identity.md +0 -177
  258. package/src/orchestrator/templates/ai-instructions/claude-skill-viewpoint.md +0 -118
  259. package/src/orchestrator/templates/ai-instructions/copilot-cmd-add-flow.md +0 -86
  260. package/src/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +0 -131
  261. package/src/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +0 -94
  262. package/src/orchestrator/templates/ai-instructions/copilot-cmd-dashboard.md +0 -65
  263. package/src/orchestrator/templates/ai-instructions/copilot-cmd-delivery.md +0 -83
  264. package/src/orchestrator/templates/ai-instructions/copilot-cmd-design.md +0 -13
  265. package/src/orchestrator/templates/ai-instructions/copilot-cmd-feedback.md +0 -24
  266. package/src/orchestrator/templates/ai-instructions/copilot-cmd-locale.md +0 -70
  267. package/src/orchestrator/templates/ai-instructions/copilot-cmd-review.md +0 -38
  268. package/src/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +0 -158
  269. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-api-design.md +0 -62
  270. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mobile.md +0 -184
  271. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-figma-mcp.md +0 -82
  272. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-figma-pat.md +0 -117
  273. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-live.md +0 -60
  274. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mode-local.md +0 -38
  275. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture.md +0 -35
  276. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-dashboard.md +0 -121
  277. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-delivery.md +0 -141
  278. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-error-mapping.md +0 -176
  279. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +0 -446
  280. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-harness-audit.md +0 -85
  281. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-ingest-legacy.md +0 -79
  282. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-locale.md +0 -291
  283. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-mobile-gestures.md +0 -109
  284. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix-mobile.md +0 -316
  285. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-keys.md +0 -221
  286. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +0 -617
  287. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-review.md +0 -320
  288. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-test-design-techniques.md +0 -118
  289. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-viewpoint-group-a-data-entry.md +0 -203
  290. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-viewpoint-group-b-data-ops.md +0 -179
  291. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-viewpoint-group-c-data-explore.md +0 -233
  292. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-viewpoint-group-d-display.md +0 -226
  293. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-viewpoint-group-e-identity.md +0 -177
  294. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-viewpoint.md +0 -118
  295. /package/dist/orchestrator/templates/{ai-instructions/claude-agent-challenge.md → ai-src/agents/challenge.md} +0 -0
  296. /package/dist/orchestrator/templates/{ai-instructions/claude-agent-discovery.md → ai-src/agents/discovery.md} +0 -0
  297. /package/dist/orchestrator/templates/{ai-instructions/claude-agent-generator.md → ai-src/agents/generator.md} +0 -0
  298. /package/dist/orchestrator/templates/{ai-instructions/claude-agent-reviewer.md → ai-src/agents/reviewer.md} +0 -0
  299. /package/dist/orchestrator/templates/{ai-instructions/claude-config.md → ai-src/config/claude.md} +0 -0
  300. /package/dist/orchestrator/templates/{ai-instructions/copilot-config.md → ai-src/config/copilot.md} +0 -0
  301. /package/dist/orchestrator/templates/{ai-instructions/claude-skill-api-design.md → ai-src/skills/sungen-api-design/SKILL.md} +0 -0
  302. /package/dist/orchestrator/templates/{ai-instructions/claude-skill-capture.md → ai-src/skills/sungen-capture/SKILL.md} +0 -0
  303. /package/dist/orchestrator/templates/{ai-instructions/claude-skill-capture-mode-figma-mcp.md → ai-src/skills/sungen-capture/mode-figma-mcp.md} +0 -0
  304. /package/dist/orchestrator/templates/{ai-instructions/claude-skill-capture-mode-figma-pat.md → ai-src/skills/sungen-capture/mode-figma-pat.md} +0 -0
  305. /package/dist/orchestrator/templates/{ai-instructions/claude-skill-capture-mode-live.md → ai-src/skills/sungen-capture/mode-live.md} +0 -0
  306. /package/dist/orchestrator/templates/{ai-instructions/claude-skill-capture-mode-local.md → ai-src/skills/sungen-capture/mode-local.md} +0 -0
  307. /package/dist/orchestrator/templates/{ai-instructions/claude-skill-dashboard.md → ai-src/skills/sungen-dashboard/SKILL.md} +0 -0
  308. /package/dist/orchestrator/templates/{ai-instructions/claude-skill-delivery.md → ai-src/skills/sungen-delivery/SKILL.md} +0 -0
  309. /package/dist/orchestrator/templates/{ai-instructions/claude-skill-gherkin-syntax.md → ai-src/skills/sungen-gherkin-syntax/SKILL.md} +0 -0
  310. /package/dist/orchestrator/templates/{ai-instructions/claude-skill-ingest-legacy.md → ai-src/skills/sungen-ingest-legacy/SKILL.md} +0 -0
  311. /package/dist/orchestrator/templates/{ai-instructions/claude-skill-mobile-gestures.md → ai-src/skills/sungen-mobile-gestures/SKILL.md} +0 -0
  312. /package/dist/orchestrator/templates/{ai-instructions/claude-skill-selector-keys.md → ai-src/skills/sungen-selector-keys/SKILL.md} +0 -0
  313. /package/dist/orchestrator/templates/{ai-instructions/claude-skill-tc-review.md → ai-src/skills/sungen-tc-review/SKILL.md} +0 -0
  314. /package/dist/orchestrator/templates/{ai-instructions/claude-skill-test-design-techniques.md → ai-src/skills/sungen-test-design-techniques/SKILL.md} +0 -0
  315. /package/dist/orchestrator/templates/{ai-instructions/claude-skill-viewpoint.md → ai-src/skills/sungen-viewpoint/SKILL.md} +0 -0
  316. /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
  317. /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
  318. /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
  319. /package/dist/orchestrator/templates/{ai-instructions/claude-skill-viewpoint-group-d-display.md → ai-src/skills/sungen-viewpoint/group-d-display.md} +0 -0
  320. /package/dist/orchestrator/templates/{ai-instructions/claude-skill-viewpoint-group-e-identity.md → ai-src/skills/sungen-viewpoint/group-e-identity.md} +0 -0
  321. /package/src/orchestrator/templates/{ai-instructions/claude-agent-challenge.md → ai-src/agents/challenge.md} +0 -0
  322. /package/src/orchestrator/templates/{ai-instructions/claude-agent-discovery.md → ai-src/agents/discovery.md} +0 -0
  323. /package/src/orchestrator/templates/{ai-instructions/claude-agent-generator.md → ai-src/agents/generator.md} +0 -0
  324. /package/src/orchestrator/templates/{ai-instructions/claude-agent-reviewer.md → ai-src/agents/reviewer.md} +0 -0
  325. /package/src/orchestrator/templates/{ai-instructions/claude-config.md → ai-src/config/claude.md} +0 -0
  326. /package/src/orchestrator/templates/{ai-instructions/copilot-config.md → ai-src/config/copilot.md} +0 -0
  327. /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
  328. /package/{dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture.md → src/orchestrator/templates/ai-src/skills/sungen-capture/SKILL.md} +0 -0
  329. /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
  330. /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
  331. /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
  332. /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
  333. /package/{dist/orchestrator/templates/ai-instructions/github-skill-sungen-dashboard.md → src/orchestrator/templates/ai-src/skills/sungen-dashboard/SKILL.md} +0 -0
  334. /package/{dist/orchestrator/templates/ai-instructions/github-skill-sungen-delivery.md → src/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md} +0 -0
  335. /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
  336. /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
  337. /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
  338. /package/src/orchestrator/templates/{ai-instructions/claude-skill-selector-keys.md → ai-src/skills/sungen-selector-keys/SKILL.md} +0 -0
  339. /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
  340. /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
  341. /package/{dist/orchestrator/templates/ai-instructions/github-skill-sungen-viewpoint.md → src/orchestrator/templates/ai-src/skills/sungen-viewpoint/SKILL.md} +0 -0
  342. /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
  343. /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
  344. /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
  345. /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
  346. /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);
@@ -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);
@@ -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
- : runJourney(process.cwd(), name);
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
- 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
 
@@ -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
- const url = p ? `${base}/${p}` : base;
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
- const testRegex = /test\s*\(\s*(['"`])((?:(?!\1).)+)\1\s*,\s*(?:\{.*?\}\s*,\s*)?async\s*\([^)]*\)\s*=>\s*\{/g;
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
- const regex = /test\.describe(?:\.serial)?\s*\(\s*['"]([^'"]+)['"]\s*,\s*\(\s*\)\s*=>\s*\{/g;
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];