@sun-asterisk/sungen 3.2.0 → 3.2.2-beta.1

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 (315) hide show
  1. package/dist/capabilities/context-router.d.ts +11 -2
  2. package/dist/capabilities/context-router.d.ts.map +1 -1
  3. package/dist/capabilities/context-router.js +10 -3
  4. package/dist/capabilities/context-router.js.map +1 -1
  5. package/dist/capabilities/discover.js +1 -1
  6. package/dist/capabilities/discover.js.map +1 -1
  7. package/dist/cli/commands/audit.d.ts.map +1 -1
  8. package/dist/cli/commands/audit.js +5 -3
  9. package/dist/cli/commands/audit.js.map +1 -1
  10. package/dist/cli/commands/delivery.d.ts.map +1 -1
  11. package/dist/cli/commands/delivery.js +31 -0
  12. package/dist/cli/commands/delivery.js.map +1 -1
  13. package/dist/exporters/feature-parser.d.ts +25 -0
  14. package/dist/exporters/feature-parser.d.ts.map +1 -1
  15. package/dist/exporters/feature-parser.js +59 -0
  16. package/dist/exporters/feature-parser.js.map +1 -1
  17. package/dist/exporters/types.d.ts +38 -0
  18. package/dist/exporters/types.d.ts.map +1 -1
  19. package/dist/exporters/xlsx-exporter.d.ts +31 -2
  20. package/dist/exporters/xlsx-exporter.d.ts.map +1 -1
  21. package/dist/exporters/xlsx-exporter.js +144 -1
  22. package/dist/exporters/xlsx-exporter.js.map +1 -1
  23. package/dist/generators/test-generator/adapters/appium/appium-adapter.d.ts +54 -0
  24. package/dist/generators/test-generator/adapters/appium/appium-adapter.d.ts.map +1 -0
  25. package/dist/generators/test-generator/adapters/appium/appium-adapter.js +52 -0
  26. package/dist/generators/test-generator/adapters/appium/appium-adapter.js.map +1 -0
  27. package/dist/generators/test-generator/adapters/appium/templates/after-all.hbs +8 -0
  28. package/dist/generators/test-generator/adapters/appium/templates/after-each.hbs +8 -0
  29. package/dist/generators/test-generator/adapters/appium/templates/before-all.hbs +8 -0
  30. package/dist/generators/test-generator/adapters/appium/templates/before-each.hbs +8 -0
  31. package/dist/generators/test-generator/adapters/appium/templates/imports.hbs +8 -0
  32. package/dist/generators/test-generator/adapters/appium/templates/scenario.hbs +8 -0
  33. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/alert-accept-action.hbs +4 -0
  34. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/alert-dismiss-action.hbs +2 -0
  35. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/alert-fill-action.hbs +2 -0
  36. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/check-action.hbs +10 -0
  37. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/clear-action.hbs +1 -0
  38. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/click-action.hbs +1 -0
  39. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/click-element-with-text.hbs +2 -0
  40. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/click-select-action.hbs +4 -0
  41. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/dismiss-action.hbs +3 -0
  42. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/double-click-action.hbs +6 -0
  43. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/drag-action.hbs +2 -0
  44. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/expand-action.hbs +2 -0
  45. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/fill-action.hbs +14 -0
  46. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/fill-editor-action.hbs +12 -0
  47. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/frame-enter-action.hbs +6 -0
  48. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/frame-exit-action.hbs +2 -0
  49. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/hide-keyboard-action.hbs +4 -0
  50. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/hover-action.hbs +2 -0
  51. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/keyboard-global-action.hbs +2 -0
  52. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/press-action.hbs +4 -0
  53. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/radio-select-action.hbs +4 -0
  54. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/scroll-action.hbs +19 -0
  55. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/select-action.hbs +5 -0
  56. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/table-action-in-row.hbs +2 -0
  57. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/toggle-action.hbs +3 -0
  58. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/uncheck-action.hbs +8 -0
  59. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/unknown-element-action.hbs +2 -0
  60. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/upload-action.hbs +3 -0
  61. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/wait-for-page.hbs +3 -0
  62. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/wait-for-role-with-data.hbs +2 -0
  63. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/wait-for-role.hbs +3 -0
  64. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/alert-text-assertion.hbs +2 -0
  65. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/attribute-assertion.hbs +1 -0
  66. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/checked-assertion.hbs +1 -0
  67. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/clipboard-text-assertion.hbs +2 -0
  68. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/column-cell-assertion.hbs +2 -0
  69. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/contain-text-assertion.hbs +14 -0
  70. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/count-assertion.hbs +1 -0
  71. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/disabled-assertion.hbs +1 -0
  72. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/empty-assertion.hbs +1 -0
  73. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/enabled-assertion.hbs +1 -0
  74. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/focused-assertion.hbs +1 -0
  75. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/have-text-assertion.hbs +15 -0
  76. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/have-value-assertion.hbs +1 -0
  77. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/is-hidden-assertion.hbs +1 -0
  78. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/label-value-assertion.hbs +12 -0
  79. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/list-item-count-assertion.hbs +2 -0
  80. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/loading-assertion.hbs +2 -0
  81. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/not-checked-assertion.hbs +1 -0
  82. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/page-assertion.hbs +1 -0
  83. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/route-assertion.hbs +2 -0
  84. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/selected-assertion.hbs +1 -0
  85. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/sorted-assertion.hbs +2 -0
  86. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/table-column-exists.hbs +2 -0
  87. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/table-empty.hbs +2 -0
  88. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/table-match-data.hbs +2 -0
  89. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/table-row-count.hbs +2 -0
  90. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/table-row-exists.hbs +2 -0
  91. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/table-row-not-exists.hbs +2 -0
  92. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/visible-assertion.hbs +1 -0
  93. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/background-action.hbs +1 -0
  94. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/grant-permission-action.hbs +11 -0
  95. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/long-press-action.hbs +5 -0
  96. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/open-notifications-action.hbs +3 -0
  97. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/pinch-zoom-action.hbs +5 -0
  98. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/pull-to-refresh-action.hbs +5 -0
  99. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/rotate-action.hbs +1 -0
  100. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/set-clipboard-action.hbs +2 -0
  101. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/set-geolocation-action.hbs +2 -0
  102. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/swipe-action.hbs +5 -0
  103. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/tap-top-action.hbs +24 -0
  104. package/dist/generators/test-generator/adapters/appium/templates/steps/navigation/navigation.hbs +1 -0
  105. package/dist/generators/test-generator/adapters/appium/templates/steps/navigation/wait-for-element-with-text.hbs +1 -0
  106. package/dist/generators/test-generator/adapters/appium/templates/steps/navigation/wait-for-element.hbs +1 -0
  107. package/dist/generators/test-generator/adapters/appium/templates/steps/navigation/wait-timeout.hbs +1 -0
  108. package/dist/generators/test-generator/adapters/appium/templates/steps/partials/appium-selector-expr.hbs +8 -0
  109. package/dist/generators/test-generator/adapters/appium/templates/steps/partials/appium-selector.hbs +15 -0
  110. package/dist/generators/test-generator/adapters/appium/templates/steps/partials/locator.hbs +8 -0
  111. package/dist/generators/test-generator/adapters/appium/templates/steps/setup/application-running.hbs +1 -0
  112. package/dist/generators/test-generator/adapters/appium/templates/steps/setup/clear-auth.hbs +2 -0
  113. package/dist/generators/test-generator/adapters/appium/templates/steps/setup/clear-browser-state.hbs +1 -0
  114. package/dist/generators/test-generator/adapters/appium/templates/steps/setup/clear-database.hbs +1 -0
  115. package/dist/generators/test-generator/adapters/appium/templates/steps/setup/user-login-todo.hbs +2 -0
  116. package/dist/generators/test-generator/adapters/appium/templates/test-file.hbs +226 -0
  117. package/dist/generators/test-generator/adapters/index.d.ts +1 -0
  118. package/dist/generators/test-generator/adapters/index.d.ts.map +1 -1
  119. package/dist/generators/test-generator/adapters/index.js +9 -1
  120. package/dist/generators/test-generator/adapters/index.js.map +1 -1
  121. package/dist/generators/test-generator/code-generator.d.ts.map +1 -1
  122. package/dist/generators/test-generator/code-generator.js +3 -2
  123. package/dist/generators/test-generator/code-generator.js.map +1 -1
  124. package/dist/generators/test-generator/step-mapper.d.ts +1 -0
  125. package/dist/generators/test-generator/step-mapper.d.ts.map +1 -1
  126. package/dist/generators/test-generator/step-mapper.js +7 -37
  127. package/dist/generators/test-generator/step-mapper.js.map +1 -1
  128. package/dist/generators/test-generator/template-engine.d.ts.map +1 -1
  129. package/dist/generators/test-generator/template-engine.js +13 -1
  130. package/dist/generators/test-generator/template-engine.js.map +1 -1
  131. package/dist/harness/audit.d.ts +16 -2
  132. package/dist/harness/audit.d.ts.map +1 -1
  133. package/dist/harness/audit.js +74 -11
  134. package/dist/harness/audit.js.map +1 -1
  135. package/dist/harness/capability-plan.d.ts +2 -0
  136. package/dist/harness/capability-plan.d.ts.map +1 -1
  137. package/dist/harness/capability-plan.js +4 -1
  138. package/dist/harness/capability-plan.js.map +1 -1
  139. package/dist/harness/catalog/drivers.yaml +1 -1
  140. package/dist/harness/flow-check.d.ts.map +1 -1
  141. package/dist/harness/flow-check.js +13 -4
  142. package/dist/harness/flow-check.js.map +1 -1
  143. package/dist/harness/parse.d.ts +2 -0
  144. package/dist/harness/parse.d.ts.map +1 -1
  145. package/dist/harness/parse.js +13 -2
  146. package/dist/harness/parse.js.map +1 -1
  147. package/dist/harness/quality-gates.d.ts +6 -0
  148. package/dist/harness/quality-gates.d.ts.map +1 -1
  149. package/dist/harness/quality-gates.js +15 -1
  150. package/dist/harness/quality-gates.js.map +1 -1
  151. package/dist/harness/sensors.d.ts +27 -0
  152. package/dist/harness/sensors.d.ts.map +1 -1
  153. package/dist/harness/sensors.js +91 -21
  154. package/dist/harness/sensors.js.map +1 -1
  155. package/dist/orchestrator/ai-rules-updater.d.ts.map +1 -1
  156. package/dist/orchestrator/ai-rules-updater.js +9 -0
  157. package/dist/orchestrator/ai-rules-updater.js.map +1 -1
  158. package/dist/orchestrator/templates/ai-instructions/claude-agent-generator.md +44 -0
  159. package/dist/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +18 -1
  160. package/dist/orchestrator/templates/ai-instructions/claude-skill-capture-mobile.md +184 -0
  161. package/dist/orchestrator/templates/ai-instructions/claude-skill-delivery.md +27 -0
  162. package/dist/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +2 -0
  163. package/dist/orchestrator/templates/ai-instructions/claude-skill-mobile-gestures.md +109 -0
  164. package/dist/orchestrator/templates/ai-instructions/claude-skill-selector-fix-mobile.md +316 -0
  165. package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +2 -0
  166. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +2 -1
  167. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mobile.md +184 -0
  168. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-delivery.md +27 -0
  169. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +2 -0
  170. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-mobile-gestures.md +109 -0
  171. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix-mobile.md +316 -0
  172. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +2 -0
  173. package/dist/orchestrator/templates/env.appium.example +25 -0
  174. package/dist/orchestrator/templates/specs-api.d.ts +7 -0
  175. package/dist/orchestrator/templates/specs-api.d.ts.map +1 -1
  176. package/dist/orchestrator/templates/specs-api.js +13 -2
  177. package/dist/orchestrator/templates/specs-api.js.map +1 -1
  178. package/dist/orchestrator/templates/specs-api.ts +13 -2
  179. package/dist/orchestrator/templates/specs-pw-shape-reporter.ts +92 -0
  180. package/dist/orchestrator/templates/wdio.conf.ts +295 -0
  181. package/dist/utils/selector-types.d.ts +1 -1
  182. package/dist/utils/selector-types.d.ts.map +1 -1
  183. package/dist/utils/selector-types.js +5 -0
  184. package/dist/utils/selector-types.js.map +1 -1
  185. package/package.json +4 -4
  186. package/src/capabilities/context-router.ts +15 -3
  187. package/src/capabilities/discover.ts +1 -1
  188. package/src/cli/commands/audit.ts +5 -3
  189. package/src/cli/commands/delivery.ts +32 -2
  190. package/src/exporters/feature-parser.ts +57 -0
  191. package/src/exporters/types.ts +38 -0
  192. package/src/exporters/xlsx-exporter.ts +176 -2
  193. package/src/generators/test-generator/adapters/appium/appium-adapter.ts +57 -0
  194. package/src/generators/test-generator/adapters/appium/templates/after-all.hbs +8 -0
  195. package/src/generators/test-generator/adapters/appium/templates/after-each.hbs +8 -0
  196. package/src/generators/test-generator/adapters/appium/templates/before-all.hbs +8 -0
  197. package/src/generators/test-generator/adapters/appium/templates/before-each.hbs +8 -0
  198. package/src/generators/test-generator/adapters/appium/templates/imports.hbs +8 -0
  199. package/src/generators/test-generator/adapters/appium/templates/scenario.hbs +8 -0
  200. package/src/generators/test-generator/adapters/appium/templates/steps/actions/alert-accept-action.hbs +4 -0
  201. package/src/generators/test-generator/adapters/appium/templates/steps/actions/alert-dismiss-action.hbs +2 -0
  202. package/src/generators/test-generator/adapters/appium/templates/steps/actions/alert-fill-action.hbs +2 -0
  203. package/src/generators/test-generator/adapters/appium/templates/steps/actions/check-action.hbs +10 -0
  204. package/src/generators/test-generator/adapters/appium/templates/steps/actions/clear-action.hbs +1 -0
  205. package/src/generators/test-generator/adapters/appium/templates/steps/actions/click-action.hbs +1 -0
  206. package/src/generators/test-generator/adapters/appium/templates/steps/actions/click-element-with-text.hbs +2 -0
  207. package/src/generators/test-generator/adapters/appium/templates/steps/actions/click-select-action.hbs +4 -0
  208. package/src/generators/test-generator/adapters/appium/templates/steps/actions/dismiss-action.hbs +3 -0
  209. package/src/generators/test-generator/adapters/appium/templates/steps/actions/double-click-action.hbs +6 -0
  210. package/src/generators/test-generator/adapters/appium/templates/steps/actions/drag-action.hbs +2 -0
  211. package/src/generators/test-generator/adapters/appium/templates/steps/actions/expand-action.hbs +2 -0
  212. package/src/generators/test-generator/adapters/appium/templates/steps/actions/fill-action.hbs +14 -0
  213. package/src/generators/test-generator/adapters/appium/templates/steps/actions/fill-editor-action.hbs +12 -0
  214. package/src/generators/test-generator/adapters/appium/templates/steps/actions/frame-enter-action.hbs +6 -0
  215. package/src/generators/test-generator/adapters/appium/templates/steps/actions/frame-exit-action.hbs +2 -0
  216. package/src/generators/test-generator/adapters/appium/templates/steps/actions/hide-keyboard-action.hbs +4 -0
  217. package/src/generators/test-generator/adapters/appium/templates/steps/actions/hover-action.hbs +2 -0
  218. package/src/generators/test-generator/adapters/appium/templates/steps/actions/keyboard-global-action.hbs +2 -0
  219. package/src/generators/test-generator/adapters/appium/templates/steps/actions/press-action.hbs +4 -0
  220. package/src/generators/test-generator/adapters/appium/templates/steps/actions/radio-select-action.hbs +4 -0
  221. package/src/generators/test-generator/adapters/appium/templates/steps/actions/scroll-action.hbs +19 -0
  222. package/src/generators/test-generator/adapters/appium/templates/steps/actions/select-action.hbs +5 -0
  223. package/src/generators/test-generator/adapters/appium/templates/steps/actions/table-action-in-row.hbs +2 -0
  224. package/src/generators/test-generator/adapters/appium/templates/steps/actions/toggle-action.hbs +3 -0
  225. package/src/generators/test-generator/adapters/appium/templates/steps/actions/uncheck-action.hbs +8 -0
  226. package/src/generators/test-generator/adapters/appium/templates/steps/actions/unknown-element-action.hbs +2 -0
  227. package/src/generators/test-generator/adapters/appium/templates/steps/actions/upload-action.hbs +3 -0
  228. package/src/generators/test-generator/adapters/appium/templates/steps/actions/wait-for-page.hbs +3 -0
  229. package/src/generators/test-generator/adapters/appium/templates/steps/actions/wait-for-role-with-data.hbs +2 -0
  230. package/src/generators/test-generator/adapters/appium/templates/steps/actions/wait-for-role.hbs +3 -0
  231. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/alert-text-assertion.hbs +2 -0
  232. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/attribute-assertion.hbs +1 -0
  233. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/checked-assertion.hbs +1 -0
  234. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/clipboard-text-assertion.hbs +2 -0
  235. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/column-cell-assertion.hbs +2 -0
  236. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/contain-text-assertion.hbs +14 -0
  237. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/count-assertion.hbs +1 -0
  238. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/disabled-assertion.hbs +1 -0
  239. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/empty-assertion.hbs +1 -0
  240. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/enabled-assertion.hbs +1 -0
  241. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/focused-assertion.hbs +1 -0
  242. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/have-text-assertion.hbs +15 -0
  243. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/have-value-assertion.hbs +1 -0
  244. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/is-hidden-assertion.hbs +1 -0
  245. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/label-value-assertion.hbs +12 -0
  246. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/list-item-count-assertion.hbs +2 -0
  247. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/loading-assertion.hbs +2 -0
  248. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/not-checked-assertion.hbs +1 -0
  249. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/page-assertion.hbs +1 -0
  250. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/route-assertion.hbs +2 -0
  251. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/selected-assertion.hbs +1 -0
  252. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/sorted-assertion.hbs +2 -0
  253. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/table-column-exists.hbs +2 -0
  254. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/table-empty.hbs +2 -0
  255. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/table-match-data.hbs +2 -0
  256. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/table-row-count.hbs +2 -0
  257. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/table-row-exists.hbs +2 -0
  258. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/table-row-not-exists.hbs +2 -0
  259. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/visible-assertion.hbs +1 -0
  260. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/background-action.hbs +1 -0
  261. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/grant-permission-action.hbs +11 -0
  262. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/long-press-action.hbs +5 -0
  263. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/open-notifications-action.hbs +3 -0
  264. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/pinch-zoom-action.hbs +5 -0
  265. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/pull-to-refresh-action.hbs +5 -0
  266. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/rotate-action.hbs +1 -0
  267. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/set-clipboard-action.hbs +2 -0
  268. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/set-geolocation-action.hbs +2 -0
  269. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/swipe-action.hbs +5 -0
  270. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/tap-top-action.hbs +24 -0
  271. package/src/generators/test-generator/adapters/appium/templates/steps/navigation/navigation.hbs +1 -0
  272. package/src/generators/test-generator/adapters/appium/templates/steps/navigation/wait-for-element-with-text.hbs +1 -0
  273. package/src/generators/test-generator/adapters/appium/templates/steps/navigation/wait-for-element.hbs +1 -0
  274. package/src/generators/test-generator/adapters/appium/templates/steps/navigation/wait-timeout.hbs +1 -0
  275. package/src/generators/test-generator/adapters/appium/templates/steps/partials/appium-selector-expr.hbs +8 -0
  276. package/src/generators/test-generator/adapters/appium/templates/steps/partials/appium-selector.hbs +15 -0
  277. package/src/generators/test-generator/adapters/appium/templates/steps/partials/locator.hbs +8 -0
  278. package/src/generators/test-generator/adapters/appium/templates/steps/setup/application-running.hbs +1 -0
  279. package/src/generators/test-generator/adapters/appium/templates/steps/setup/clear-auth.hbs +2 -0
  280. package/src/generators/test-generator/adapters/appium/templates/steps/setup/clear-browser-state.hbs +1 -0
  281. package/src/generators/test-generator/adapters/appium/templates/steps/setup/clear-database.hbs +1 -0
  282. package/src/generators/test-generator/adapters/appium/templates/steps/setup/user-login-todo.hbs +2 -0
  283. package/src/generators/test-generator/adapters/appium/templates/test-file.hbs +226 -0
  284. package/src/generators/test-generator/adapters/index.ts +7 -0
  285. package/src/generators/test-generator/code-generator.ts +3 -2
  286. package/src/generators/test-generator/step-mapper.ts +8 -5
  287. package/src/generators/test-generator/template-engine.ts +13 -1
  288. package/src/harness/audit.ts +84 -14
  289. package/src/harness/capability-plan.ts +5 -2
  290. package/src/harness/catalog/drivers.yaml +1 -1
  291. package/src/harness/flow-check.ts +13 -4
  292. package/src/harness/parse.ts +15 -2
  293. package/src/harness/quality-gates.ts +14 -1
  294. package/src/harness/sensors.ts +110 -22
  295. package/src/orchestrator/ai-rules-updater.ts +9 -0
  296. package/src/orchestrator/templates/ai-instructions/claude-agent-generator.md +44 -0
  297. package/src/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +18 -1
  298. package/src/orchestrator/templates/ai-instructions/claude-skill-capture-mobile.md +184 -0
  299. package/src/orchestrator/templates/ai-instructions/claude-skill-delivery.md +27 -0
  300. package/src/orchestrator/templates/ai-instructions/claude-skill-gherkin-syntax.md +2 -0
  301. package/src/orchestrator/templates/ai-instructions/claude-skill-mobile-gestures.md +109 -0
  302. package/src/orchestrator/templates/ai-instructions/claude-skill-selector-fix-mobile.md +316 -0
  303. package/src/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +2 -0
  304. package/src/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +2 -1
  305. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-capture-mobile.md +184 -0
  306. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-delivery.md +27 -0
  307. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-gherkin-syntax.md +2 -0
  308. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-mobile-gestures.md +109 -0
  309. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix-mobile.md +316 -0
  310. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +2 -0
  311. package/src/orchestrator/templates/env.appium.example +25 -0
  312. package/src/orchestrator/templates/specs-api.ts +13 -2
  313. package/src/orchestrator/templates/specs-pw-shape-reporter.ts +92 -0
  314. package/src/orchestrator/templates/wdio.conf.ts +295 -0
  315. package/src/utils/selector-types.ts +5 -0
@@ -1,9 +1,9 @@
1
1
  import { ParsedStep } from '../gherkin-parser';
2
2
  import { TemplateEngine } from './template-engine';
3
+ import { adapterRegistry } from './adapters';
3
4
  import { PatternRegistry, PatternContext } from './patterns';
4
5
  import { SelectorResolver } from './utils/selector-resolver';
5
6
  import { DataResolver } from './utils/data-resolver';
6
- import * as path from 'path';
7
7
 
8
8
  export interface MappedStep {
9
9
  code: string; // Generated Playwright code
@@ -35,15 +35,18 @@ export class StepMapper {
35
35
  private inRowScope: boolean = false;
36
36
  private rowScopeTable: string = '';
37
37
 
38
- constructor(options: { verbose?: boolean; baseURL?: string; featureName?: string; screenName?: string; featurePath?: string; runtimeData?: boolean } = {}) {
38
+ constructor(options: { verbose?: boolean; baseURL?: string; featureName?: string; screenName?: string; featurePath?: string; runtimeData?: boolean; framework?: string } = {}) {
39
39
  this.verbose = options.verbose ?? false;
40
40
  this.baseURL = options.baseURL || null; // null means path-only navigation
41
41
  this.featureName = options.featureName;
42
42
  this.screenName = options.screenName;
43
43
  this.featurePath = options.featurePath;
44
- // Use Playwright templates as default for pattern-based generation
45
- const playwrightTemplatesDir = path.join(__dirname, 'adapters', 'playwright', 'templates');
46
- this.templateEngine = new TemplateEngine(playwrightTemplatesDir);
44
+ // Render step templates from the ACTIVE adapter's template set (web→Playwright,
45
+ // mobile→Appium). The adapter owns its templatesDir; resolving it here keeps step
46
+ // rendering and the file skeleton (code-generator) on the same framework.
47
+ const framework = options.framework || 'web';
48
+ const templatesDir = adapterRegistry.getAdapter(framework).templatesDir;
49
+ this.templateEngine = new TemplateEngine(templatesDir);
47
50
  this.patternRegistry = new PatternRegistry();
48
51
  this.selectorResolver = new SelectorResolver(undefined, options.screenName);
49
52
  this.dataResolver = new DataResolver(undefined, options.screenName, options.runtimeData);
@@ -168,6 +168,18 @@ export class TemplateEngine {
168
168
  const dialogRootContent = fs.readFileSync(dialogRootPath, 'utf-8');
169
169
  Handlebars.registerPartial('dialog-root', dialogRootContent);
170
170
  }
171
+
172
+ // Adapter-agnostic: register any OTHER top-level partial in partials/ by its basename
173
+ // (the named registrations above are playwright's; the appium adapter ships
174
+ // appium-selector / appium-selector-expr). Idempotent — re-registering by name is harmless.
175
+ const partialsDir = path.join(this.stepsTemplatesDir, 'partials');
176
+ if (fs.existsSync(partialsDir)) {
177
+ for (const f of fs.readdirSync(partialsDir)) {
178
+ if (!f.endsWith('.hbs')) continue;
179
+ const name = f.replace(/\.hbs$/, '');
180
+ Handlebars.registerPartial(name, fs.readFileSync(path.join(partialsDir, f), 'utf-8'));
181
+ }
182
+ }
171
183
  }
172
184
 
173
185
  private loadTemplate(templateName: string, isStepTemplate: boolean = false): HandlebarsTemplateDelegate {
@@ -179,7 +191,7 @@ export class TemplateEngine {
179
191
 
180
192
  // Try to find template in organized folders
181
193
  if (isStepTemplate) {
182
- const folders = ['actions', 'assertions', 'navigation', 'setup', 'partials'];
194
+ const folders = ['actions', 'assertions', 'navigation', 'setup', 'gestures', 'partials'];
183
195
 
184
196
  for (const folder of folders) {
185
197
  const templatePath = path.join(baseDir, folder, `${templateName}.hbs`);
@@ -12,8 +12,8 @@ import { loadScenarios, parseViewpointOverview, ScenarioInfo, ViewpointEntry } f
12
12
  import { featureBasename } from './unit-paths';
13
13
  import {
14
14
  loadCatalog, viewpointGate, assertionDepth, dataThemesFor, depthThresholdFor, coverageBalance, duplicateClusters, traceability, claimProof, taxonomyLint,
15
- automatableManual, flowCoveredThemes,
16
- GateResult, DepthResult, BalanceResult, DuplicateResult, TraceResult, ClaimProofResult, TaxonomyResult, Catalog, AutomatableManualResult,
15
+ automatableManual, flowCoveredThemes, flowRegressionDepth, oracleStrength,
16
+ GateResult, DepthResult, BalanceResult, DuplicateResult, TraceResult, ClaimProofResult, TaxonomyResult, Catalog, AutomatableManualResult, FlowDepthResult, OracleStrengthResult,
17
17
  } from './sensors';
18
18
  import { loadFlowScenarios } from './flow-check';
19
19
  import { manualReasonMismatches, MANUAL_REASONS, buildPlan } from './capability-plan';
@@ -22,7 +22,7 @@ import { readIntent, projectRootFromScreenDir, IntentProfile } from './intent';
22
22
  import { getProvenance, Provenance } from './provenance';
23
23
  import { specCoverage, SpecCoverageResult, parseSpecClauses } from './spec-coverage';
24
24
  import { downstreamScope, manualOracle, readText, DownstreamResult, ManualOracleResult,
25
- negativeSideEffect, sourceBacked, crossArtifactOwnership } from './quality-gates';
25
+ negativeSideEffect, sourceBacked, crossArtifactOwnership, isolationRisk } from './quality-gates';
26
26
  import { viewpointLedger, parseViewpointItems, LedgerResult } from './viewpoint-ledger';
27
27
  import { capabilityRegistry } from '../capabilities/registry';
28
28
  import { discoverAndRegisterCapabilities } from '../capabilities/discover';
@@ -42,6 +42,8 @@ export interface AuditReport {
42
42
  downstream: DownstreamResult; // downstream screens referenced but under-covered
43
43
  manualOracle: ManualOracleResult; // @manual scenarios lacking setup/action/oracle
44
44
  automatableManual: AutomatableManualResult; // @manual that is actually automatable (deferred, not judgment) — TQ-2
45
+ flowDepth: FlowDepthResult; // H3 — stateful-flow regression depth (count / teardown / multi-source)
46
+ oracle: OracleStrengthResult; // H4 — facet-oracle strength (weak name-substring vs title/detail/API/DB)
45
47
  ledger: LedgerResult; // atomic viewpoint-item coverage (per-bullet status)
46
48
  calibration: { // #8 — multi-axis score so a high overall can't hide a weak axis
47
49
  axes: Record<string, number>;
@@ -81,9 +83,28 @@ function catalogIdFromScreenDir(screenDir: string): string {
81
83
  * future `mobile/<x>` or `perf/<x>` unit routes to that capability with no core change. `flows/<flow>`
82
84
  * has no `flows` capability → default (UI), which is correct (flows are a UI concept).
83
85
  */
84
- export function scoringCapabilityFor(catalogScreenName: string, defaultCap: string | undefined): string | undefined {
86
+ export function scoringCapabilityFor(catalogScreenName: string, defaultCap: string | undefined, platform?: string): string | undefined {
85
87
  const seg = catalogScreenName.split('/')[0];
86
- return seg && capabilityRegistry.get(seg) ? seg : defaultCap;
88
+ // Path segment wins (api/<area> → api). Then the active platform capability (mobile → mobile),
89
+ // when a driver registered one with that id. Else the default (web/bare screen → ui — unchanged,
90
+ // since `web` has no capability of its own). See docs/spec/sungen-platform-capability-routing-spec.md.
91
+ if (seg && capabilityRegistry.get(seg)) return seg;
92
+ if (platform && capabilityRegistry.get(platform)) return platform;
93
+ return defaultCap;
94
+ }
95
+
96
+ /**
97
+ * H7 — senior-grade band. The top decile (≥9) is reserved for suites that ALSO clear the senior
98
+ * axes: a stateful flow with FULL regression depth (count + teardown + multi-source), no weak facet
99
+ * oracle, and no parallel-cart isolation risk. Otherwise the score is held just below 9 (8.9), so
100
+ * "≥9" means senior-grade — not merely "themes covered". Neutral for screens/api (no signals → 10).
101
+ */
102
+ export function seniorBandedOverall(
103
+ rawOverall: number,
104
+ s: { flowStateful: boolean; flowRatio: number; oracleWeak: number; isolationRisk: boolean },
105
+ ): number {
106
+ const seniorGrade = (!s.flowStateful || s.flowRatio >= 1) && s.oracleWeak === 0 && !s.isolationRisk;
107
+ return Math.min(rawOverall, seniorGrade ? 10 : 8.9);
87
108
  }
88
109
 
89
110
  export function runAudit(screenDir: string, screenName: string): AuditReport {
@@ -106,8 +127,11 @@ export function runAudit(screenDir: string, screenName: string): AuditReport {
106
127
  // UI capability). A capability that provides no catalog/gate falls back to the in-core UI
107
128
  // functions, so UI units — and api units until AO-2 adds the api providers — are byte-identical.
108
129
  discoverAndRegisterCapabilities();
130
+ // The active platform (web | mobile | …) activates its own capability for scoring + sensor routing.
131
+ // `web` has no capability of its own → scoringCap stays the default `ui` (byte-identical).
132
+ const platform = readCapabilities(projectRootFromScreenDir(screenDir)).platform;
109
133
  const defaultCap = capabilityRegistry.defaultCapabilityId();
110
- const scoringCapId = scoringCapabilityFor(catalogScreenName, defaultCap);
134
+ const scoringCapId = scoringCapabilityFor(catalogScreenName, defaultCap, platform);
111
135
  const scoringCap = scoringCapId ? capabilityRegistry.get(scoringCapId) : undefined;
112
136
  const catalog = (scoringCap?.viewpoints?.() as Catalog | undefined) || loadCatalog();
113
137
  const spec = specCoverage(specPath, scenarios, featureText);
@@ -157,16 +181,43 @@ export function runAudit(screenDir: string, screenName: string): AuditReport {
157
181
  const ownership = crossArtifactOwnership(screenDir, scenarios);
158
182
  const unsourced = sourceBacked(scenarios, parseSpecClauses(specPath).frs.map((f) => f.id), parseViewpointItems(viewpointPath).map((i) => i.text), viewpoints.map((v) => v.id), featureText);
159
183
 
184
+ // H3 — stateful-flow regression depth. For a UI flow whose scenarios mutate a cart/checkout
185
+ // collection, the regression dimensions (count/quantity proof · teardown · multi-source) cap the
186
+ // businessDepth headroom: it can reach 1.0 only when all three are exercised, so a present-but-
187
+ // shallow flow can't claim a perfect score (floor 0.5 — assertion depth still dominates).
188
+ const isUiFlow = /^flows\//.test(catalogScreenName);
189
+ const flowDepth = isUiFlow ? flowRegressionDepth(scenarios) : { stateful: false, countProof: false, teardown: false, multiSource: false, ratio: 1, missing: [] } as FlowDepthResult;
190
+ const FLOW_DEPTH_FLOOR = 0.5;
191
+ // H4 — oracle strength: a weak facet oracle (name-substring "proves" category/brand membership)
192
+ // caps businessDepth the same way (floor 0.5). A suite with no facet claim, or a strong oracle, is
193
+ // neutral (ratio 1 → no cap), so existing snapshots are unaffected.
194
+ const oracle = oracleStrength(scenarios);
195
+ const ORACLE_FLOOR = 0.5;
196
+
160
197
  // Sub-scores
161
198
  const coverage = gate.coverageRatio;
162
- const businessDepth = depth.bcDepthRatio;
163
- const balanceScore = balance.coreCount + balance.secondaryCount > 0
164
- ? Math.min(1, balance.coreCount / Math.max(1, balance.secondaryCount))
165
- : 1;
199
+ const businessDepth = Math.min(
200
+ depth.bcDepthRatio,
201
+ flowDepth.stateful ? FLOW_DEPTH_FLOOR + (1 - FLOW_DEPTH_FLOOR) * flowDepth.ratio : 1,
202
+ oracle.weak.length ? ORACLE_FLOOR + (1 - ORACLE_FLOOR) * oracle.ratio : 1,
203
+ );
204
+ // When the taxonomy drifted (most scenarios unclassified), the balance axis is unreliable — cap it
205
+ // at 0.5 instead of awarding a vacuous 1.0 so a stale taxonomy fails loudly, not silently (H1).
206
+ const balanceScore = balance.unclassifiedRatio > 0.4
207
+ ? 0.5
208
+ : balance.coreCount + balance.secondaryCount > 0
209
+ ? Math.min(1, balance.coreCount / Math.max(1, balance.secondaryCount))
210
+ : 1;
166
211
  const traceScore = 0.5 * trace.withVpCodeRatio + 0.5 * trace.mappedRatio;
167
212
 
168
213
  // Business-weighted overall (coverage + depth dominate)
169
- const overall = (0.4 * coverage + 0.3 * businessDepth + 0.15 * balanceScore + 0.15 * traceScore) * 10;
214
+ const rawOverall = (0.4 * coverage + 0.3 * businessDepth + 0.15 * balanceScore + 0.15 * traceScore) * 10;
215
+ // H7 — senior-grade band: the top decile (≥9) is reserved for suites that also clear the senior
216
+ // axes — a stateful flow with FULL regression depth (count + teardown + multi-source), no weak
217
+ // facet oracle, and no parallel-cart isolation risk. Otherwise the score is held just below 9, so
218
+ // "≥9" means senior-grade, not merely "themes covered". Neutral for screens/api (no signals).
219
+ const isoRisk = isolationRisk(featureText, flowDepth.stateful);
220
+ const overall = seniorBandedOverall(rawOverall, { flowStateful: flowDepth.stateful, flowRatio: flowDepth.ratio, oracleWeak: oracle.weak.length, isolationRisk: isoRisk });
170
221
 
171
222
  const findings: string[] = [];
172
223
  for (const c of flowCredits) {
@@ -187,6 +238,23 @@ export function runAudit(screenDir: string, screenName: string): AuditReport {
187
238
  `add data assertions (\`... with {{value}}\`, \`see all ... contain {{v}}\`) or, if cross-screen, defer to a flow with @manual + reason.`,
188
239
  );
189
240
  }
241
+ // H5 — state isolation: a @parallel stateful flow that mutates the cart needs per-scenario
242
+ // isolation, else count/quantity asserts go flaky. Warn (advisory) when no mitigation is present
243
+ // (@cleanup:cart / @isolate / a "Given … empty cart" background / fresh context).
244
+ if (isoRisk) {
245
+ findings.push('ISOLATION-RISK: this @parallel flow mutates the cart but has no per-scenario isolation → cart/count/quantity asserts can go flaky when scenarios share state. Add `@cleanup:cart` (or `@isolate`, or a `Given User has an empty cart` background) so each scenario starts clean.');
246
+ }
247
+ if (flowDepth.stateful && flowDepth.missing.length) {
248
+ const how: Record<string, string> = {
249
+ 'count-proof': 'assert the cart ROW COUNT / item QUANTITY (e.g. `... table with {{two_rows}}`, `Quantity column with {{qty}}`), not just the row presence',
250
+ 'teardown': 'add a REMOVE/clear scenario that returns the cart to its empty state (the inverse operation)',
251
+ 'multi-source': 'add to the cart from EVERY source on the page (the main list AND the recommended/related rail), not just one',
252
+ };
253
+ findings.push(`FLOW-DEPTH: this stateful flow exercises ${3 - flowDepth.missing.length}/3 regression dimensions — missing [${flowDepth.missing.join(', ')}] → ${flowDepth.missing.map((m) => how[m]).join('; ')}. (businessDepth is capped until covered.)`);
254
+ }
255
+ for (const w of oracle.weak) {
256
+ findings.push(`ORACLE-WEAK: "${w.name}" — ${w.hint}`);
257
+ }
190
258
  for (const u of claim.unproven) {
191
259
  const tag = u.severity === 'fail' ? 'CLAIM-UNPROVEN' : 'CLAIM-WEAK';
192
260
  findings.push(`${tag}: "${u.name}" — title claims [${u.claim}] but steps lack ${u.need}. ${u.hint}`);
@@ -194,7 +262,9 @@ export function runAudit(screenDir: string, screenName: string): AuditReport {
194
262
  for (const m of taxonomy.mislabeled) {
195
263
  findings.push(`VP-MISLABEL: "${m.name}" is coded VP-${m.current} but reads as ${m.suggested} (signal: "${m.signal}") → re-tag VP-${m.suggested}-NNN so the coverage matrix isn't skewed.`);
196
264
  }
197
- if (balance.imbalanced) {
265
+ if (balance.unclassifiedRatio > 0.4) {
266
+ findings.push(`TAXONOMY-UNCLASSIFIED: ${balance.note} → align the VP-<CATEGORY> codes with the catalog (or extend the bucket keywords) so coverage-balance is meaningful.`);
267
+ } else if (balance.imbalanced) {
198
268
  findings.push(`BALANCE: ${balance.note} Stop expanding secondary viewpoints until business-core gaps are filled.`);
199
269
  }
200
270
  if (trace.mappedRatio < 0.5) {
@@ -273,7 +343,7 @@ export function runAudit(screenDir: string, screenName: string): AuditReport {
273
343
  ...(scenarios.some((s) => s.queryRefs && s.queryRefs.length) ? ['@query'] : []),
274
344
  ...(scenarios.some((s) => s.apiRefs && s.apiRefs.length) ? ['@api'] : []),
275
345
  ];
276
- const routedGateIds = contextRouter.route({ target: { kind: 'screen', id: screenName }, artifact: 'feature', tags: featureTags }).gateSensorIds;
346
+ const routedGateIds = contextRouter.route({ target: { kind: 'screen', id: screenName }, artifact: 'feature', tags: featureTags, platform }).gateSensorIds;
277
347
  const gateSensorFindings = capabilityRegistry.sensors('gate')
278
348
  .filter((s) => routedGateIds.includes(s.id))
279
349
  .flatMap((s) => s.run({ screenName: catalogScreenName, cwd: projectRootFromScreenDir(screenDir), featureText, scenarios, universalGaps: gate.universalGaps }));
@@ -315,7 +385,7 @@ export function runAudit(screenDir: string, screenName: string): AuditReport {
315
385
  screen: screenName,
316
386
  scenarioCount: scenarios.length,
317
387
  gate, depth, claim, taxonomy, balance, duplicates, trace, spec,
318
- taxonomyMismatch, downstream, manualOracle: manualOracleResult, automatableManual: autoManual, ledger, calibration,
388
+ taxonomyMismatch, downstream, manualOracle: manualOracleResult, automatableManual: autoManual, flowDepth, oracle, ledger, calibration,
319
389
  score: {
320
390
  overall: Math.round(overall * 10) / 10,
321
391
  coverage: Math.round(coverage * 100) / 100,
@@ -56,7 +56,7 @@ export function classifyReason(text: string): string {
56
56
  return '';
57
57
  }
58
58
 
59
- interface ParsedScenario { name: string; tags: string[]; manual: boolean; reason: string }
59
+ interface ParsedScenario { name: string; tags: string[]; manual: boolean; reason: string; deferredToFlow: boolean; ownedBy?: string }
60
60
 
61
61
  /** Parse scenarios with their tags + the reason comment line above (for @manual). */
62
62
  export function parseScenarios(featurePath: string): ParsedScenario[] {
@@ -84,7 +84,10 @@ export function parseScenarios(featurePath: string): ParsedScenario[] {
84
84
  else if (l === '') continue;
85
85
  else break; // a real step → stop
86
86
  }
87
- out.push({ name: m[1].trim(), tags, manual: tags.some((t) => /^@manual\b/i.test(t)), reason });
87
+ const deferredToFlow = tags.some((t) => /^@deferred:flow$/i.test(t));
88
+ const ownedBy = (tags.find((t) => /^@owned-by:/i.test(t)) || '').slice('@owned-by:'.length) || undefined;
89
+ // @deferred:flow accounts like @manual on the screen (owned by a flow, not automated here) (H6).
90
+ out.push({ name: m[1].trim(), tags, manual: tags.some((t) => /^@manual\b/i.test(t)) || deferredToFlow, reason, deferredToFlow, ownedBy });
88
91
  }
89
92
  return out;
90
93
  }
@@ -31,7 +31,7 @@ drivers:
31
31
  mobile:
32
32
  kind: platform
33
33
  package: "@sungen/driver-mobile"
34
- status: planned # PoC on the feat/mobile branch (Appium / Flutter)
34
+ status: shipped # @sungen/driver-mobile Appium/WebdriverIO, gesture steps + web-parity gate
35
35
  runtime: appium
36
36
  adapter: mobile
37
37
  capabilities: ["@ui"]
@@ -74,14 +74,23 @@ export function buildFlowCheck(cwd: string, onlyFlow?: string): FlowCheckReport
74
74
  const deferrals: Deferral[] = [];
75
75
  for (const sc of screens) {
76
76
  for (const s of parseScenarios(featurePath(cwd, 'screens', sc))) {
77
- if (!s.manual || !/deferred to a flow/i.test(s.reason)) continue;
78
- const targets = targetsFromHint(s.reason);
79
- const matches = flowScenarios.filter((fs2) => targets.some((t) => fs2.haystack.includes(t)));
77
+ // A deferral is the first-class `@deferred:flow` tag (H6) OR the legacy `@manual` + a
78
+ // "deferred to a flow" comment (back-compat). Either marks a cross-screen case owned by a flow.
79
+ const isDeferral = s.deferredToFlow || (s.manual && /deferred to a flow/i.test(s.reason));
80
+ if (!isDeferral) continue;
81
+ // Targets come from the comment hint; a tag-only @deferred:flow (no comment) falls back to the
82
+ // scenario TITLE so the covering flow scenario can still be located.
83
+ const targets = targetsFromHint([s.reason, s.name].join(' '));
84
+ // `@owned-by:<flow>` names the owner explicitly → only that flow's scenarios can cover it
85
+ // (a false @owned-by is then surfaced as missing). Else any flow may cover it (legacy).
86
+ const pool = s.ownedBy ? flowScenarios.filter((fs2) => fs2.flow === s.ownedBy) : flowScenarios;
87
+ const matches = pool.filter((fs2) => targets.some((t) => fs2.haystack.includes(t)));
80
88
  let verdict: Deferral['verdict'] = 'missing';
81
89
  let via: string | undefined;
82
90
  if (matches.some((m) => m.deep)) { verdict = 'covered'; via = matches.find((m) => m.deep)!.flow; }
83
91
  else if (matches.length) { verdict = 'shallow'; via = matches[0].flow; }
84
- deferrals.push({ screen: sc, scenario: s.name, hint: s.reason, targets, verdict, via });
92
+ const hint = s.ownedBy ? `${s.reason || 'deferred to a flow'} (owned-by: ${s.ownedBy})` : s.reason;
93
+ deferrals.push({ screen: sc, scenario: s.name, hint, targets, verdict, via });
85
94
  }
86
95
  }
87
96
 
@@ -34,6 +34,8 @@ export interface ScenarioInfo {
34
34
  queryRefs?: string[]; // named queries referenced by this scenario (inline `query [name]` + @query: tags)
35
35
  apiRefs?: string[]; // named API endpoints referenced by this scenario (@api: tags)
36
36
  requiresCaps?: string[]; // @requires:<cap> — automation-ready but needs an opt-in driver (TQ-11)
37
+ deferredToFlow?: boolean; // @deferred:flow — owned by a flow, not automated on this screen (H6)
38
+ ownedByFlow?: string; // @owned-by:<flow> — the flow that owns this deferred scenario (H6)
37
39
  }
38
40
 
39
41
  /** Format-tolerant: is this token an ID (project's scheme), not a prose word?
@@ -101,7 +103,13 @@ const PRIORITY_TAGS: Record<string, Priority> = { '@high': 'high', '@normal': 'n
101
103
 
102
104
  function classifyScenario(sc: ParsedScenario): ScenarioInfo {
103
105
  const tags = sc.tags || [];
104
- const manual = tags.includes('@manual');
106
+ const deferredToFlow = tags.includes('@deferred:flow');
107
+ const ownedByFlow = (tags.find((t: string) => /^@owned-by:/i.test(t)) || '').slice('@owned-by:'.length) || undefined;
108
+ // @deferred:flow is owned by a flow → not automated on this screen, so it accounts like @manual (H6).
109
+ // Recognize both bare `@manual` and the reason-coded `@manual:Mx` convention (what the generator emits);
110
+ // must match capability-plan.ts's detection, or `@manual:Mx` scenarios stay in the businessDepth
111
+ // denominator and silently suppress the ratio (#386).
112
+ const manual = tags.some((t) => /^@manual\b/i.test(t)) || deferredToFlow;
105
113
  const casesTag = tags.find((t) => t.startsWith('@cases:'));
106
114
  const casesDataset = casesTag ? casesTag.slice('@cases:'.length).trim() : undefined;
107
115
  // Named-query references: @query:<name>[(overrides)] tags + inline `query [name]` step refs.
@@ -118,7 +126,10 @@ function classifyScenario(sc: ParsedScenario): ScenarioInfo {
118
126
  let priority: Priority = 'unknown';
119
127
  for (const t of tags) if (PRIORITY_TAGS[t]) priority = PRIORITY_TAGS[t];
120
128
 
121
- const codeMatch = sc.name.match(/\bVP-([A-Z]+)-\d+/i);
129
+ // Category is everything between `VP-` and the final `-<sequence>` — INCLUDING hyphens, so
130
+ // compound categories (VP-LIST-DISPLAY-01, VP-ADD-TO-CART-03, VP-PRODUCT-DISCOVERY-02) parse,
131
+ // not just single-word ones. A single-word category (VP-CART-001) still works. (H1)
132
+ const codeMatch = sc.name.match(/\bVP-([A-Z]+(?:-[A-Z]+)*)-\d+/i);
122
133
  const vpCode = codeMatch ? codeMatch[0].toUpperCase() : undefined;
123
134
  const category = codeMatch ? codeMatch[1].toUpperCase() : undefined;
124
135
  // Project-scheme ID: the leading token of the title (VP0-001 / MS-HP-001 / VP-LIST-001).
@@ -173,6 +184,8 @@ function classifyScenario(sc: ParsedScenario): ScenarioInfo {
173
184
  queryRefs: queryRefs.size ? [...queryRefs] : undefined,
174
185
  apiRefs: apiRefs.size ? [...apiRefs] : undefined,
175
186
  requiresCaps: requiresCaps.length ? requiresCaps : undefined,
187
+ deferredToFlow: deferredToFlow || undefined,
188
+ ownedByFlow,
176
189
  };
177
190
  }
178
191
 
@@ -20,7 +20,10 @@ function downstreamRoutes(specText: string): string[] {
20
20
  const routes = new Set<string>();
21
21
  for (const line of specText.split('\n')) {
22
22
  if (!/success|navigat|to \(|→/i.test(line)) continue;
23
- for (const m of line.matchAll(/`?(\/[a-z][a-z0-9/_-]+)`?/gi)) {
23
+ // A real route's leading `/` sits at a path boundary (start, whitespace, backtick, quote, paren),
24
+ // NOT after a letter/digit. The lookbehind rejects prose slashes like "text/icon" or
25
+ // "category/brand" that aren't routes at all (H2 — they produced /icon, /button, /brand).
26
+ for (const m of line.matchAll(/(?<![A-Za-z0-9])(\/[a-z][a-z0-9/_-]+)`?/gi)) {
24
27
  const r = m[1];
25
28
  if (r !== ownRoute && r.split('/').length > ownRoute.split('/').length - 0) routes.add(r);
26
29
  }
@@ -29,6 +32,16 @@ function downstreamRoutes(specText: string): string[] {
29
32
  return [...routes].filter((r) => r !== ownRoute && (!ownRoute || r.startsWith(ownRoute + '/') || r.split('/').length >= 3));
30
33
  }
31
34
 
35
+ /**
36
+ * H5 — a @parallel stateful (cart-mutating) flow with NO per-scenario isolation is flaky: scenarios
37
+ * share state, so cart count/quantity asserts race. Mitigations: @cleanup:cart, @isolate, a fresh
38
+ * browser context, or a "Given … empty cart" background. Returns true when the risk is unmitigated.
39
+ */
40
+ export function isolationRisk(featureText: string, stateful: boolean): boolean {
41
+ if (!stateful || !/@parallel\b/i.test(featureText)) return false;
42
+ return !/@cleanup:cart\b|@isolate\b|empty cart|fresh (?:browser )?context|new context/i.test(featureText);
43
+ }
44
+
32
45
  export function downstreamScope(specText: string, scenarios: ScenarioInfo[]): DownstreamResult {
33
46
  const routes = downstreamRoutes(specText);
34
47
  const underCovered: { route: string; slug: string }[] = [];
@@ -11,17 +11,36 @@ import * as path from 'path';
11
11
  import { parse as parseYaml } from 'yaml';
12
12
  import { ScenarioInfo, ViewpointEntry, idPrefix } from './parse';
13
13
 
14
- // Business-critical category codes (project VP-<CAT> prefixes). Configurable later.
15
- const BUSINESS_CRITICAL_CATS = ['LIST', 'CART', 'PRODUCT', 'FILTER', 'CHECKOUT', 'ORDER'];
16
-
17
- // Buckets for coverage-balance.
18
- const BUCKETS: Record<string, string[]> = {
19
- 'business-core': BUSINESS_CRITICAL_CATS,
20
- 'presentation': ['UI'],
21
- 'validation-security': ['VAL', 'SEC', 'SUB'],
22
- 'behavior': ['LOGIC'],
23
- 'navigation': ['NAV'],
24
- };
14
+ // Business-critical category keywords (matched by CONTAINMENT against the VP category, so a
15
+ // compound category like LIST-DISPLAY / ADD-TO-CART / PRODUCT-DISCOVERY classifies correctly).
16
+ const BUSINESS_CRITICAL_CATS = [
17
+ // UI commerce cores
18
+ 'LIST', 'CART', 'PRODUCT', 'FILTER', 'CHECKOUT', 'ORDER', 'DETAIL', 'DISCOVERY', 'CATEGORY', 'BRAND', 'DUPLICATE', 'CONSISTENCY',
19
+ // API / DB capability cores — for an api/db suite the operation IS the business core
20
+ 'API', 'ENDPOINT', 'CRUD', 'QUERY', 'CONTRACT', 'RESOURCE',
21
+ ];
22
+
23
+ // Bucket keyword sets for coverage-balance, in PRECEDENCE order (first match wins). Matched by
24
+ // substring containment so compound categories land in the right bucket (H1): e.g. LIST-DISPLAY
25
+ // → business-core (LIST) not presentation (DISPLAY); CART-TRANSITION → business-core (CART).
26
+ const BUCKET_ORDER: Array<[string, string[]]> = [
27
+ ['business-core', BUSINESS_CRITICAL_CATS],
28
+ ['behavior', ['LOGIC', 'TRANSITION', 'WORKFLOW']],
29
+ ['validation-security', ['VAL', 'SEC', 'SUB', 'AUTH', 'LOGIN']],
30
+ ['navigation', ['NAV']],
31
+ ['presentation', ['UI', 'LAYOUT', 'RESPONSIVE', 'DISPLAY', 'SEO', 'ACCESSIBILITY', 'USABILITY', 'VISUAL']],
32
+ ];
33
+ const BUCKETS: Record<string, string[]> = Object.fromEntries(BUCKET_ORDER);
34
+
35
+ /** Classify a VP category into a balance bucket by keyword containment + precedence (H1). */
36
+ export function bucketForCategory(category: string | undefined): string {
37
+ const cat = (category || '').toUpperCase();
38
+ if (!cat) return 'other';
39
+ for (const [bucket, kws] of BUCKET_ORDER) {
40
+ if (kws.some((k) => cat.includes(k))) return bucket;
41
+ }
42
+ return 'other';
43
+ }
25
44
 
26
45
  export interface ThemeDepth {
27
46
  requires: string; // 'data-assertion' → scenarios on this theme must assert DATA
@@ -242,6 +261,72 @@ export function flowCoveredThemes(
242
261
  return out;
243
262
  }
244
263
 
264
+ // ---------- Sensor: Flow regression-depth (H3) ----------
265
+
266
+ export interface FlowDepthResult {
267
+ stateful: boolean; // the suite mutates a cart/checkout collection (add/remove/quantity)
268
+ countProof: boolean; // asserts a row count / item quantity, not just presence
269
+ teardown: boolean; // removes an item and verifies the empty/zero state
270
+ multiSource: boolean; // adds from >1 distinct source (e.g. main list AND recommended)
271
+ ratio: number; // covered dimensions / 3 (1 when not stateful → neutral)
272
+ missing: string[];
273
+ }
274
+
275
+ /**
276
+ * Grades a STATEFUL flow's regression depth beyond "theme covered": a cart/checkout flow that only
277
+ * proves an item is present is shallower than one that proves the quantity/count, tears the state
278
+ * back down (remove → empty), and exercises every add-to-cart source. The ratio caps the
279
+ * businessDepth headroom (audit.ts) so a thin stateful flow can't reach a perfect score. (H3)
280
+ */
281
+ export function flowRegressionDepth(scenarios: ScenarioInfo[]): FlowDepthResult {
282
+ const hay = scenarios.map((s) => s.haystack);
283
+ const any = (re: RegExp) => hay.some((h) => re.test(h));
284
+ const addsToCart = any(/\b(add to cart|add to basket|added (?:to )?(?:the )?cart|adds? .* cart)\b/i);
285
+ const stateful = (any(/\b(cart|basket|checkout)\b/i) && (addsToCart || any(/\b(remove|delete|quantity|cart line|cart row)\b/i)));
286
+ if (!stateful) return { stateful: false, countProof: false, teardown: false, multiSource: false, ratio: 1, missing: [] };
287
+
288
+ // 1. Count/quantity proof — a row count or item quantity, not just presence of a row.
289
+ const countProof = any(/\b(quantity|qty|two (?:rows|lines|cart)|row count|count column|number of items|one[_ ]row|two[_ ]rows|qty[_ ])/i);
290
+ // 2. Teardown — removes the item and verifies the empty/zero state (the inverse operation).
291
+ const teardown = any(/\b(remove|delete|clear)\b/i) && any(/\b(empty|no items|zero|removed|0 items)\b/i);
292
+ // 3. Multi-source — the cart is fed from >1 source (the main list AND a recommended/related rail).
293
+ const multiSource = any(/\b(recommended|related|you may also|suggest)\b/i) && addsToCart;
294
+
295
+ const dims: Array<[string, boolean]> = [['count-proof', countProof], ['teardown', teardown], ['multi-source', multiSource]];
296
+ const missing = dims.filter(([, v]) => !v).map(([k]) => k);
297
+ return { stateful: true, countProof, teardown, multiSource, ratio: (dims.length - missing.length) / dims.length, missing };
298
+ }
299
+
300
+ // ---------- Sensor: Oracle strength (H4) ----------
301
+
302
+ export interface OracleStrengthResult {
303
+ weak: { name: string; hint: string }[]; // scenarios proving facet membership by a name-substring
304
+ facetClaims: number; // scenarios that touch a category/brand facet (denominator)
305
+ ratio: number; // 1 - weak/facetClaims (1 when none) — caps businessDepth
306
+ }
307
+
308
+ // "see all [<item name/title>] ... contain(s) {{<facet>.term}}" — asserting every item's NAME carries
309
+ // a category/brand term does NOT prove the item BELONGS to that facet (a "Dress" item need not contain
310
+ // "Dress" in its name). The strong oracle is the results-page title/header, a detail-page facet field,
311
+ // an API/DB query, or an explicit @manual:M2 deferral.
312
+ const WEAK_FACET_ORACLE = /\bsee all\b\s*\[[^\]]*\b(name|title|label)\b[^\]]*\][^{[]*\bcontains?\b[^{]*\{\{[^}]*\b(categ|brand|facet|filter|term)/i;
313
+ const FACET_REF = /\{\{[^}]*\b(categ|brand|facet|filter)\b[^}]*\}\}|\b(category|brand)\b/i;
314
+
315
+ export function oracleStrength(scenarios: ScenarioInfo[]): OracleStrengthResult {
316
+ const weak: { name: string; hint: string }[] = [];
317
+ for (const s of scenarios) {
318
+ if (s.manual) continue; // a @manual facet check is a deliberate deferral, not a weak automated oracle
319
+ if (WEAK_FACET_ORACLE.test(s.stepsText)) {
320
+ weak.push({
321
+ name: s.name.slice(0, 80),
322
+ hint: 'asserting every item NAME contains a category/brand term does not prove facet membership — assert the results-page TITLE/header carries the facet, a detail-page facet field, or an API/DB oracle; or defer the exhaustive check to @manual:M2.',
323
+ });
324
+ }
325
+ }
326
+ const facetClaims = scenarios.filter((s) => FACET_REF.test(s.stepsText) || FACET_REF.test(s.name)).length;
327
+ return { weak, facetClaims, ratio: facetClaims ? 1 - weak.length / Math.max(1, facetClaims) : 1 };
328
+ }
329
+
245
330
  /** Collect data-correctness themes (depth.requires) for a page-type + universal. */
246
331
  export function dataThemesFor(catalog: Catalog, pageType: string | null): CatalogTheme[] {
247
332
  const themes: CatalogTheme[] = [];
@@ -258,6 +343,7 @@ export interface BalanceResult {
258
343
  coreCount: number;
259
344
  secondaryCount: number;
260
345
  imbalanced: boolean;
346
+ unclassifiedRatio: number; // share of scenarios that fell into `other` (taxonomy drift signal, H1)
261
347
  note: string;
262
348
  }
263
349
 
@@ -270,23 +356,21 @@ export function coverageBalance(scenarios: ScenarioInfo[]): BalanceResult {
270
356
  for (const s of scenarios) {
271
357
  const cat = s.category || 'NONE';
272
358
  byCategory[cat] = (byCategory[cat] || 0) + 1;
273
- const bucket = Object.entries(BUCKETS).find(([, cats]) => cats.includes(cat))?.[0] || 'other';
274
- byBucket[bucket]++;
359
+ byBucket[bucketForCategory(s.category)]++;
275
360
  }
276
361
 
277
362
  const core = byBucket['business-core'];
278
363
  const secondary = byBucket['presentation'] + byBucket['validation-security'];
279
364
  const imbalanced = secondary > core * 1.5 && core > 0;
280
- return {
281
- byBucket,
282
- byCategory,
283
- coreCount: core,
284
- secondaryCount: secondary,
285
- imbalanced,
286
- note: imbalanced
365
+ const unclassifiedRatio = scenarios.length ? byBucket['other'] / scenarios.length : 0;
366
+ // A high `other` share means the VP taxonomy drifted from the catalog — the balance axis is then
367
+ // unreliable, so we surface it (audit.ts caps the balance contribution on this signal).
368
+ const note = unclassifiedRatio > 0.4
369
+ ? `Taxonomy drift: ${byBucket['other']}/${scenarios.length} scenarios have an unrecognised VP category (bucket=other) — balance is unreliable until the viewpoint codes match the catalog.`
370
+ : imbalanced
287
371
  ? `Secondary viewpoints (presentation+validation/security = ${secondary}) outweigh business-core (${core}) by >1.5x.`
288
- : 'Balanced.',
289
- };
372
+ : 'Balanced.';
373
+ return { byBucket, byCategory, coreCount: core, secondaryCount: secondary, imbalanced, unclassifiedRatio, note };
290
374
  }
291
375
 
292
376
  // ---------- Sensor 4: Duplicate clusters ----------
@@ -300,6 +384,10 @@ export interface DuplicateResult {
300
384
  export function duplicateClusters(scenarios: ScenarioInfo[]): DuplicateResult {
301
385
  const map = new Map<string, ScenarioInfo[]>();
302
386
  for (const s of scenarios) {
387
+ // @manual scenarios compile to a degenerate skeleton (no executable steps), so they cluster
388
+ // with each other even though each is a distinct judgment/capability-manual viewpoint. Excluding
389
+ // them keeps the exact-dup signal about genuinely-copied AUTOMATED scenarios (H2).
390
+ if (s.manual) continue;
303
391
  const arr = map.get(s.stepSkeleton) || [];
304
392
  arr.push(s);
305
393
  map.set(s.stepSkeleton, arr);
@@ -65,11 +65,16 @@ export const AI_RULES_FILE_MAPPING: [string, string][] = [
65
65
  ['claude-skill-capture-mode-live.md', '.claude/skills/sungen-capture/mode-live.md'],
66
66
  ['claude-skill-capture-mode-local.md', '.claude/skills/sungen-capture/mode-local.md'],
67
67
  ['claude-skill-locale.md', '.claude/skills/sungen-locale/SKILL.md'],
68
+ // Mobile (Appium) skills — relevant for platform: mobile (MOB-5).
69
+ ['claude-skill-mobile-gestures.md', '.claude/skills/sungen-mobile-gestures/SKILL.md'],
70
+ ['claude-skill-capture-mobile.md', '.claude/skills/sungen-capture-mobile/SKILL.md'],
71
+ ['claude-skill-selector-fix-mobile.md', '.claude/skills/sungen-selector-fix-mobile/SKILL.md'],
68
72
 
69
73
  // Agents — Claude Code sub-agents (isolated context). Copilot runs these inline.
70
74
  ['claude-agent-reviewer.md', '.claude/agents/sungen-reviewer.md'],
71
75
  ['claude-agent-discovery.md', '.claude/agents/sungen-discovery.md'],
72
76
  ['claude-agent-challenge.md', '.claude/agents/sungen-challenge.md'],
77
+ ['claude-agent-generator.md', '.claude/agents/sungen-generator.md'],
73
78
 
74
79
  // Skills — GitHub Copilot
75
80
  ['github-skill-sungen-gherkin-syntax.md', '.github/skills/sungen-gherkin-syntax/SKILL.md'],
@@ -96,6 +101,10 @@ export const AI_RULES_FILE_MAPPING: [string, string][] = [
96
101
  ['github-skill-sungen-capture-mode-live.md', '.github/skills/sungen-capture/mode-live.md'],
97
102
  ['github-skill-sungen-capture-mode-local.md', '.github/skills/sungen-capture/mode-local.md'],
98
103
  ['github-skill-sungen-locale.md', '.github/skills/sungen-locale/SKILL.md'],
104
+ // Mobile (Appium) skills — relevant for platform: mobile (MOB-5).
105
+ ['github-skill-sungen-mobile-gestures.md', '.github/skills/sungen-mobile-gestures/SKILL.md'],
106
+ ['github-skill-sungen-capture-mobile.md', '.github/skills/sungen-capture-mobile/SKILL.md'],
107
+ ['github-skill-sungen-selector-fix-mobile.md', '.github/skills/sungen-selector-fix-mobile/SKILL.md'],
99
108
  ];
100
109
 
101
110
  // Skill/asset directories retired in a previous refactor. `sungen update` removes
@@ -0,0 +1,44 @@
1
+ ---
2
+ name: sungen-generator
3
+ description: Generates Gherkin scenarios for ONE shard (a viewpoint theme or a spec section) in an isolated context and writes a self-contained fragment — so create-test can fan out many generators in parallel and the orchestrator stays lean. Each shard owns a disjoint VP-prefix namespace, so fragments merge without renumbering. Invoked by create-test/design during parallel generation.
4
+ tools: Read, Grep, Glob, Bash, Write, Edit, Skill
5
+ ---
6
+
7
+ You are a **single-shard test-case generator**. You run in an **isolated context** and produce the scenarios for **exactly one shard** — never the whole screen. The orchestrator runs several of you in parallel, then merges the fragments. Keeping each fragment small is also what keeps every generator under the output-token cap.
8
+
9
+ ## What a shard is
10
+ A shard is one **coverage unit**, sized for real parallelism (not the 5 coarse viewpoint-router groups — a screen loads only 1–2 of those). It is **one of**:
11
+ - a **viewpoint theme** — a `VP-` prefix from the viewpoint overview (e.g. `VP-SEC`, `VP-ERROR-EMPTY-STATE`, `VP-CAROUSEL`), or
12
+ - a **spec section** — one `spec.md` section per the `sungen-tc-generation` Mapping Contract (Table 1).
13
+
14
+ Your shard owns its `VP-` prefix, so your ids never collide with sibling shards.
15
+
16
+ ## Inputs (passed by the orchestrator)
17
+ - **Your shard**: the theme/section name + its viewpoint items (the slice).
18
+ - **The `sungen-discovery` report** (Step 3): condensed facts — use it instead of re-reading every source.
19
+ - **Relevant context**: only the `spec.md` section(s) your shard maps to, and **which** `sungen-viewpoint` group file holds your shard's patterns (load only that one).
20
+ - **Unit context**: screen vs flow, the unit name, the chosen tier (1 / 2 / 3 / full), and your fragment paths.
21
+
22
+ ## Generate (your shard ONLY)
23
+ 1. Load **only** the skills you need: `sungen-tc-generation` (output format + mapping), `sungen-gherkin-syntax` (step patterns), and the **one** `sungen-viewpoint` group file your shard belongs to. Do not load the others.
24
+ 2. Produce the scenarios for your shard's viewpoint items at the requested tier, following the skill's mapping contract. Keep every `VP-` id under **your shard's prefix** so it stays in a disjoint namespace.
25
+ 3. **Flows**: use `[Screen:Element]` namespace refs, namespace test-data by phase, add the `@flow` tag per the skill.
26
+ 4. Tag `@manual:Mx` (with a reason) only for true judgment / missing-capability items, per the skill.
27
+
28
+ ## Write your fragment (do NOT write the final feature)
29
+ Write two self-contained fragment files (the orchestrator merges them):
30
+ - `.sungen/fragments/<unit>/<shard>.feature` — a **headerless** block: just your `@tag`-decorated `Scenario:` / `Scenario Outline:` blocks, no `Feature:` line (the orchestrator owns the single Feature header).
31
+ - `.sungen/fragments/<unit>/<shard>.test-data.yaml` — only the `{{variables}}` your scenarios introduce.
32
+
33
+ Distinct paths per shard ⇒ no write conflict with sibling generators.
34
+
35
+ ## Return (compact — your only message back)
36
+ ```
37
+ SHARD: <theme-or-section>
38
+ SCENARIOS: <n> (VP ids: <VP-...-001..NNN>)
39
+ TEST-DATA KEYS: <keys you added>
40
+ SPEC SECTIONS COVERED: <list>
41
+ ASSUMPTIONS / DEFERRED: <items you marked @manual or could not source>
42
+ FRAGMENT: .sungen/fragments/<unit>/<shard>.feature
43
+ ```
44
+ Keep it tight. Do not audit, do not merge, do not touch other shards' fragments or the final `.feature`.