@sun-asterisk/sungen 3.2.3 → 3.2.5

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 (184) hide show
  1. package/dist/cli/commands/dashboard.js +2 -2
  2. package/dist/cli/commands/dashboard.js.map +1 -1
  3. package/dist/cli/commands/delivery.d.ts.map +1 -1
  4. package/dist/cli/commands/delivery.js +22 -5
  5. package/dist/cli/commands/delivery.js.map +1 -1
  6. package/dist/cli/commands/feedback.d.ts +11 -0
  7. package/dist/cli/commands/feedback.d.ts.map +1 -1
  8. package/dist/cli/commands/feedback.js +154 -57
  9. package/dist/cli/commands/feedback.js.map +1 -1
  10. package/dist/cli/commands/login.d.ts +19 -0
  11. package/dist/cli/commands/login.d.ts.map +1 -0
  12. package/dist/cli/commands/login.js +76 -0
  13. package/dist/cli/commands/login.js.map +1 -0
  14. package/dist/cli/commands/qa-feedback.d.ts +14 -0
  15. package/dist/cli/commands/qa-feedback.d.ts.map +1 -0
  16. package/dist/cli/commands/qa-feedback.js +83 -0
  17. package/dist/cli/commands/qa-feedback.js.map +1 -0
  18. package/dist/cli/index.js +4 -0
  19. package/dist/cli/index.js.map +1 -1
  20. package/dist/dashboard/snapshot-builder.d.ts +3 -0
  21. package/dist/dashboard/snapshot-builder.d.ts.map +1 -1
  22. package/dist/dashboard/snapshot-builder.js +155 -52
  23. package/dist/dashboard/snapshot-builder.js.map +1 -1
  24. package/dist/dashboard/templates/index.html +23 -23
  25. package/dist/dashboard/types.d.ts +8 -0
  26. package/dist/dashboard/types.d.ts.map +1 -1
  27. package/dist/exporters/api-testcase-formatter.d.ts +55 -0
  28. package/dist/exporters/api-testcase-formatter.d.ts.map +1 -0
  29. package/dist/exporters/api-testcase-formatter.js +304 -0
  30. package/dist/exporters/api-testcase-formatter.js.map +1 -0
  31. package/dist/exporters/csv-exporter.d.ts +13 -1
  32. package/dist/exporters/csv-exporter.d.ts.map +1 -1
  33. package/dist/exporters/csv-exporter.js +35 -6
  34. package/dist/exporters/csv-exporter.js.map +1 -1
  35. package/dist/exporters/feature-parser.d.ts +4 -0
  36. package/dist/exporters/feature-parser.d.ts.map +1 -1
  37. package/dist/exporters/feature-parser.js +7 -2
  38. package/dist/exporters/feature-parser.js.map +1 -1
  39. package/dist/exporters/json-exporter.d.ts.map +1 -1
  40. package/dist/exporters/json-exporter.js +11 -3
  41. package/dist/exporters/json-exporter.js.map +1 -1
  42. package/dist/exporters/playwright-report-parser.d.ts +21 -2
  43. package/dist/exporters/playwright-report-parser.d.ts.map +1 -1
  44. package/dist/exporters/playwright-report-parser.js +52 -12
  45. package/dist/exporters/playwright-report-parser.js.map +1 -1
  46. package/dist/exporters/scenario-merger.d.ts.map +1 -1
  47. package/dist/exporters/scenario-merger.js +50 -2
  48. package/dist/exporters/scenario-merger.js.map +1 -1
  49. package/dist/exporters/spec-parser.js +1 -1
  50. package/dist/exporters/spec-parser.js.map +1 -1
  51. package/dist/exporters/test-data-resolver.d.ts +7 -0
  52. package/dist/exporters/test-data-resolver.d.ts.map +1 -1
  53. package/dist/exporters/test-data-resolver.js +13 -0
  54. package/dist/exporters/test-data-resolver.js.map +1 -1
  55. package/dist/exporters/types.d.ts +13 -0
  56. package/dist/exporters/types.d.ts.map +1 -1
  57. package/dist/exporters/xlsx-exporter.d.ts.map +1 -1
  58. package/dist/exporters/xlsx-exporter.js +131 -42
  59. package/dist/exporters/xlsx-exporter.js.map +1 -1
  60. package/dist/generators/gherkin-parser/index.d.ts +15 -0
  61. package/dist/generators/gherkin-parser/index.d.ts.map +1 -1
  62. package/dist/generators/gherkin-parser/index.js +33 -1
  63. package/dist/generators/gherkin-parser/index.js.map +1 -1
  64. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/check-action.hbs +29 -8
  65. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/scroll-action.hbs +21 -17
  66. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/toggle-action.hbs +3 -0
  67. package/dist/generators/test-generator/adapters/appium/templates/steps/actions/uncheck-action.hbs +26 -6
  68. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/is-hidden-assertion.hbs +3 -1
  69. package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/visible-assertion.hbs +14 -0
  70. package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/tap-top-action.hbs +3 -0
  71. package/dist/generators/test-generator/adapters/appium/templates/steps/partials/locator.hbs +9 -2
  72. package/dist/generators/test-generator/adapters/appium/templates/test-file.hbs +115 -0
  73. package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row.hbs +1 -1
  74. package/dist/generators/test-generator/patterns/index.d.ts +12 -0
  75. package/dist/generators/test-generator/patterns/index.d.ts.map +1 -1
  76. package/dist/generators/test-generator/patterns/index.js +21 -1
  77. package/dist/generators/test-generator/patterns/index.js.map +1 -1
  78. package/dist/harness/script-check.d.ts.map +1 -1
  79. package/dist/harness/script-check.js +10 -5
  80. package/dist/harness/script-check.js.map +1 -1
  81. package/dist/orchestrator/ai-skills/writer.d.ts.map +1 -1
  82. package/dist/orchestrator/ai-skills/writer.js +46 -4
  83. package/dist/orchestrator/ai-skills/writer.js.map +1 -1
  84. package/dist/orchestrator/project-initializer.d.ts +14 -0
  85. package/dist/orchestrator/project-initializer.d.ts.map +1 -1
  86. package/dist/orchestrator/project-initializer.js +58 -0
  87. package/dist/orchestrator/project-initializer.js.map +1 -1
  88. package/dist/orchestrator/templates/ai-src/commands/add-flow.md +52 -5
  89. package/dist/orchestrator/templates/ai-src/commands/create-test.md +7 -0
  90. package/dist/orchestrator/templates/ai-src/commands/feedback.md +6 -6
  91. package/dist/orchestrator/templates/ai-src/commands/locale.md +26 -4
  92. package/dist/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +5 -4
  93. package/dist/orchestrator/templates/wdio.conf.ts +39 -3
  94. package/dist/tools/sungen-api/edit-in-editor.d.ts +15 -0
  95. package/dist/tools/sungen-api/edit-in-editor.d.ts.map +1 -0
  96. package/dist/tools/sungen-api/edit-in-editor.js +85 -0
  97. package/dist/tools/sungen-api/edit-in-editor.js.map +1 -0
  98. package/dist/tools/sungen-api/feedback-client.d.ts +24 -0
  99. package/dist/tools/sungen-api/feedback-client.d.ts.map +1 -0
  100. package/dist/tools/sungen-api/feedback-client.js +53 -0
  101. package/dist/tools/sungen-api/feedback-client.js.map +1 -0
  102. package/dist/tools/sungen-api/read-stdin.d.ts +6 -0
  103. package/dist/tools/sungen-api/read-stdin.d.ts.map +1 -0
  104. package/dist/tools/sungen-api/read-stdin.js +19 -0
  105. package/dist/tools/sungen-api/read-stdin.js.map +1 -0
  106. package/dist/tools/sungen-auth/api-base-url.d.ts +14 -0
  107. package/dist/tools/sungen-auth/api-base-url.d.ts.map +1 -0
  108. package/dist/tools/sungen-auth/api-base-url.js +28 -0
  109. package/dist/tools/sungen-auth/api-base-url.js.map +1 -0
  110. package/dist/tools/sungen-auth/credentials-store.d.ts +19 -0
  111. package/dist/tools/sungen-auth/credentials-store.d.ts.map +1 -0
  112. package/dist/tools/sungen-auth/credentials-store.js +94 -0
  113. package/dist/tools/sungen-auth/credentials-store.js.map +1 -0
  114. package/dist/tools/sungen-auth/login-flow.d.ts +33 -0
  115. package/dist/tools/sungen-auth/login-flow.d.ts.map +1 -0
  116. package/dist/tools/sungen-auth/login-flow.js +107 -0
  117. package/dist/tools/sungen-auth/login-flow.js.map +1 -0
  118. package/dist/tools/sungen-auth/loopback-server.d.ts +20 -0
  119. package/dist/tools/sungen-auth/loopback-server.d.ts.map +1 -0
  120. package/dist/tools/sungen-auth/loopback-server.js +132 -0
  121. package/dist/tools/sungen-auth/loopback-server.js.map +1 -0
  122. package/dist/tools/sungen-auth/open-browser.d.ts +10 -0
  123. package/dist/tools/sungen-auth/open-browser.d.ts.map +1 -0
  124. package/dist/tools/sungen-auth/open-browser.js +43 -0
  125. package/dist/tools/sungen-auth/open-browser.js.map +1 -0
  126. package/dist/tools/sungen-auth/pkce.d.ts +13 -0
  127. package/dist/tools/sungen-auth/pkce.d.ts.map +1 -0
  128. package/dist/tools/sungen-auth/pkce.js +27 -0
  129. package/dist/tools/sungen-auth/pkce.js.map +1 -0
  130. package/dist/tools/sungen-auth/sungen-auth-types.d.ts +41 -0
  131. package/dist/tools/sungen-auth/sungen-auth-types.d.ts.map +1 -0
  132. package/dist/tools/sungen-auth/sungen-auth-types.js +7 -0
  133. package/dist/tools/sungen-auth/sungen-auth-types.js.map +1 -0
  134. package/package.json +4 -3
  135. package/src/cli/commands/dashboard.ts +2 -2
  136. package/src/cli/commands/delivery.ts +24 -6
  137. package/src/cli/commands/feedback.ts +123 -51
  138. package/src/cli/commands/login.ts +75 -0
  139. package/src/cli/commands/qa-feedback.ts +77 -0
  140. package/src/cli/index.ts +4 -0
  141. package/src/dashboard/snapshot-builder.ts +146 -46
  142. package/src/dashboard/templates/index.html +23 -23
  143. package/src/dashboard/types.ts +10 -2
  144. package/src/exporters/api-testcase-formatter.ts +325 -0
  145. package/src/exporters/csv-exporter.ts +46 -7
  146. package/src/exporters/feature-parser.ts +7 -2
  147. package/src/exporters/json-exporter.ts +11 -3
  148. package/src/exporters/playwright-report-parser.ts +50 -11
  149. package/src/exporters/scenario-merger.ts +49 -3
  150. package/src/exporters/spec-parser.ts +1 -1
  151. package/src/exporters/test-data-resolver.ts +13 -0
  152. package/src/exporters/types.ts +13 -0
  153. package/src/exporters/xlsx-exporter.ts +155 -49
  154. package/src/generators/gherkin-parser/index.ts +44 -1
  155. package/src/generators/test-generator/adapters/appium/templates/steps/actions/check-action.hbs +29 -8
  156. package/src/generators/test-generator/adapters/appium/templates/steps/actions/scroll-action.hbs +21 -17
  157. package/src/generators/test-generator/adapters/appium/templates/steps/actions/toggle-action.hbs +3 -0
  158. package/src/generators/test-generator/adapters/appium/templates/steps/actions/uncheck-action.hbs +26 -6
  159. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/is-hidden-assertion.hbs +3 -1
  160. package/src/generators/test-generator/adapters/appium/templates/steps/assertions/visible-assertion.hbs +14 -0
  161. package/src/generators/test-generator/adapters/appium/templates/steps/gestures/tap-top-action.hbs +3 -0
  162. package/src/generators/test-generator/adapters/appium/templates/steps/partials/locator.hbs +9 -2
  163. package/src/generators/test-generator/adapters/appium/templates/test-file.hbs +115 -0
  164. package/src/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row.hbs +1 -1
  165. package/src/generators/test-generator/patterns/index.ts +21 -1
  166. package/src/harness/script-check.ts +10 -5
  167. package/src/orchestrator/ai-skills/writer.ts +39 -4
  168. package/src/orchestrator/project-initializer.ts +59 -0
  169. package/src/orchestrator/templates/ai-src/commands/add-flow.md +52 -5
  170. package/src/orchestrator/templates/ai-src/commands/create-test.md +7 -0
  171. package/src/orchestrator/templates/ai-src/commands/feedback.md +6 -6
  172. package/src/orchestrator/templates/ai-src/commands/locale.md +26 -4
  173. package/src/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +5 -4
  174. package/src/orchestrator/templates/wdio.conf.ts +39 -3
  175. package/src/tools/sungen-api/edit-in-editor.ts +48 -0
  176. package/src/tools/sungen-api/feedback-client.ts +62 -0
  177. package/src/tools/sungen-api/read-stdin.ts +15 -0
  178. package/src/tools/sungen-auth/api-base-url.ts +23 -0
  179. package/src/tools/sungen-auth/credentials-store.ts +57 -0
  180. package/src/tools/sungen-auth/login-flow.ts +135 -0
  181. package/src/tools/sungen-auth/loopback-server.ts +113 -0
  182. package/src/tools/sungen-auth/open-browser.ts +42 -0
  183. package/src/tools/sungen-auth/pkce.ts +27 -0
  184. package/src/tools/sungen-auth/sungen-auth-types.ts +45 -0
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ /**
3
+ * `sungen login` / `sungen logout`.
4
+ *
5
+ * login — RFC 8252 native-app login (PKCE-S256 + loopback + one-time code)
6
+ * against the FastSchema backend; caches the token at
7
+ * ~/.sungen/credentials.json (0600).
8
+ * logout — clears that file.
9
+ *
10
+ * Mirrors the structure of `commands/figma.ts` (commander + scrubbed errors).
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.performLogin = performLogin;
14
+ exports.registerLoginCommand = registerLoginCommand;
15
+ const pkce_1 = require("../../tools/sungen-auth/pkce");
16
+ const loopback_server_1 = require("../../tools/sungen-auth/loopback-server");
17
+ const login_flow_1 = require("../../tools/sungen-auth/login-flow");
18
+ const credentials_store_1 = require("../../tools/sungen-auth/credentials-store");
19
+ const open_browser_1 = require("../../tools/sungen-auth/open-browser");
20
+ const api_base_url_1 = require("../../tools/sungen-auth/api-base-url");
21
+ /**
22
+ * Run the full login flow against `apiBaseUrl`, persist the token, and return
23
+ * the stored credentials. Reused by `sungen feedback` for on-demand re-auth.
24
+ */
25
+ async function performLogin(apiBaseUrl) {
26
+ const pkce = (0, pkce_1.generatePkce)();
27
+ const correlation = (0, pkce_1.randomCorrelation)();
28
+ const loopback = await (0, loopback_server_1.startLoopback)();
29
+ try {
30
+ const result = await (0, login_flow_1.runLoginFlow)({
31
+ apiBaseUrl,
32
+ pkce,
33
+ correlation,
34
+ redirectUri: loopback.redirectUri,
35
+ openBrowser: open_browser_1.openBrowser,
36
+ waitForCode: (corr, timeoutMs) => loopback.waitForCode(corr, timeoutMs),
37
+ });
38
+ const creds = {
39
+ token: result.token,
40
+ email: result.email,
41
+ api_base_url: apiBaseUrl,
42
+ expires_at: result.expires_at,
43
+ };
44
+ (0, credentials_store_1.saveCredentials)(creds);
45
+ return creds;
46
+ }
47
+ finally {
48
+ loopback.close();
49
+ }
50
+ }
51
+ function registerLoginCommand(program) {
52
+ program
53
+ .command('login')
54
+ .description('Log in to the sungen feedback backend (opens your browser)')
55
+ .option('--api-base-url <url>', 'Backend base URL (overrides SUNGEN_API_BASE_URL + stored value)')
56
+ .action(async (options) => {
57
+ const apiBaseUrl = (0, api_base_url_1.resolveApiBaseUrl)(options.apiBaseUrl);
58
+ try {
59
+ const creds = await performLogin(apiBaseUrl);
60
+ console.log(`Logged in as ${creds.email}.`);
61
+ console.log(`Token saved to ${(0, credentials_store_1.credentialsPath)()} (mode 0600).`);
62
+ }
63
+ catch (err) {
64
+ console.error(`Login failed: ${err?.message ?? err}`);
65
+ process.exit(1);
66
+ }
67
+ });
68
+ program
69
+ .command('logout')
70
+ .description('Remove the cached sungen login token')
71
+ .action(() => {
72
+ (0, credentials_store_1.clearCredentials)();
73
+ console.log(`Logged out. Removed ${(0, credentials_store_1.credentialsPath)()} (if present).`);
74
+ });
75
+ }
76
+ //# sourceMappingURL=login.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.js","sourceRoot":"","sources":["../../../src/cli/commands/login.ts"],"names":[],"mappings":";AAAA;;;;;;;;;GASG;;AAeH,oCAwBC;AAED,oDAwBC;AA9DD,uDAA+E;AAC/E,6EAAwE;AACxE,mEAAkE;AAClE,iFAA+G;AAC/G,uEAAmE;AACnE,uEAAyE;AAGzE;;;GAGG;AACI,KAAK,UAAU,YAAY,CAAC,UAAkB;IACnD,MAAM,IAAI,GAAG,IAAA,mBAAY,GAAE,CAAC;IAC5B,MAAM,WAAW,GAAG,IAAA,wBAAiB,GAAE,CAAC;IACxC,MAAM,QAAQ,GAAG,MAAM,IAAA,+BAAa,GAAE,CAAC;IACvC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,IAAA,yBAAY,EAAC;YAChC,UAAU;YACV,IAAI;YACJ,WAAW;YACX,WAAW,EAAE,QAAQ,CAAC,WAAW;YACjC,WAAW,EAAX,0BAAW;YACX,WAAW,EAAE,CAAC,IAAI,EAAE,SAAS,EAAE,EAAE,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,EAAE,SAAS,CAAC;SACxE,CAAC,CAAC;QACH,MAAM,KAAK,GAAgB;YACzB,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,YAAY,EAAE,UAAU;YACxB,UAAU,EAAE,MAAM,CAAC,UAAU;SAC9B,CAAC;QACF,IAAA,mCAAe,EAAC,KAAK,CAAC,CAAC;QACvB,OAAO,KAAK,CAAC;IACf,CAAC;YAAS,CAAC;QACT,QAAQ,CAAC,KAAK,EAAE,CAAC;IACnB,CAAC;AACH,CAAC;AAED,SAAgB,oBAAoB,CAAC,OAAgB;IACnD,OAAO;SACJ,OAAO,CAAC,OAAO,CAAC;SAChB,WAAW,CAAC,4DAA4D,CAAC;SACzE,MAAM,CAAC,sBAAsB,EAAE,iEAAiE,CAAC;SACjG,MAAM,CAAC,KAAK,EAAE,OAAgC,EAAE,EAAE;QACjD,MAAM,UAAU,GAAG,IAAA,gCAAiB,EAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QACzD,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,YAAY,CAAC,UAAU,CAAC,CAAC;YAC7C,OAAO,CAAC,GAAG,CAAC,gBAAgB,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC;YAC5C,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAA,mCAAe,GAAE,eAAe,CAAC,CAAC;QAClE,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAClB,OAAO,CAAC,KAAK,CAAC,iBAAiB,GAAG,EAAE,OAAO,IAAI,GAAG,EAAE,CAAC,CAAC;YACtD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC,CAAC,CAAC;IAEL,OAAO;SACJ,OAAO,CAAC,QAAQ,CAAC;SACjB,WAAW,CAAC,sCAAsC,CAAC;SACnD,MAAM,CAAC,GAAG,EAAE;QACX,IAAA,oCAAgB,GAAE,CAAC;QACnB,OAAO,CAAC,GAAG,CAAC,uBAAuB,IAAA,mCAAe,GAAE,gBAAgB,CAAC,CAAC;IACxE,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * `sungen qa-feedback` — the LOCAL QA-feedback log (test-design knowledge +
3
+ * product telemetry), persisted to .sungen/feedback/feedback.jsonl.
4
+ *
5
+ * This is the persistence primitive behind the `/sungen:feedback` slash command
6
+ * (which auto-attaches screen/viewpoint/decision context and then calls
7
+ * `sungen qa-feedback record`). It is rarely typed by hand.
8
+ *
9
+ * NOTE: distinct from `sungen feedback` (see commands/feedback.ts) — that command
10
+ * submits PRODUCT feedback to the remote backend. This one only writes locally.
11
+ */
12
+ import { Command } from 'commander';
13
+ export declare function registerQaFeedbackCommand(program: Command): void;
14
+ //# sourceMappingURL=qa-feedback.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qa-feedback.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/qa-feedback.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAMpC,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA0DhE"}
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ /**
3
+ * `sungen qa-feedback` — the LOCAL QA-feedback log (test-design knowledge +
4
+ * product telemetry), persisted to .sungen/feedback/feedback.jsonl.
5
+ *
6
+ * This is the persistence primitive behind the `/sungen:feedback` slash command
7
+ * (which auto-attaches screen/viewpoint/decision context and then calls
8
+ * `sungen qa-feedback record`). It is rarely typed by hand.
9
+ *
10
+ * NOTE: distinct from `sungen feedback` (see commands/feedback.ts) — that command
11
+ * submits PRODUCT feedback to the remote backend. This one only writes locally.
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.registerQaFeedbackCommand = registerQaFeedbackCommand;
15
+ const feedback_1 = require("../../harness/feedback");
16
+ const TYPES = ['test-design', 'product', 'other'];
17
+ const DECISIONS = ['accept', 'reject', 'edit', 'add', 'none'];
18
+ function registerQaFeedbackCommand(program) {
19
+ const fb = program
20
+ .command('qa-feedback')
21
+ .description('Local-first QA feedback log (test-design knowledge + product telemetry). Primarily invoked by the /sungen:feedback slash command.');
22
+ fb
23
+ .command('record')
24
+ .description('Record a feedback entry')
25
+ .requiredOption('--message <text>', 'The feedback')
26
+ .option('--type <t>', `Feedback type (${TYPES.join('|')})`, 'test-design')
27
+ .option('-s, --screen <name>', 'Screen/flow the feedback relates to')
28
+ .option('--target <ref>', 'What it is about (viewpoint id / scenario / command / artifact)')
29
+ .option('--decision <d>', `QA decision (${DECISIONS.join('|')})`, 'none')
30
+ .option('--reason <text>', 'Why')
31
+ .option('--source <who>', 'Who gave the feedback', 'qa')
32
+ .action((o) => {
33
+ try {
34
+ if (!TYPES.includes(o.type))
35
+ throw new Error(`--type must be one of ${TYPES.join(', ')}`);
36
+ if (!DECISIONS.includes(o.decision))
37
+ throw new Error(`--decision must be one of ${DECISIONS.join(', ')}`);
38
+ const p = (0, feedback_1.recordFeedback)({
39
+ type: o.type,
40
+ screen: o.screen,
41
+ target: o.target,
42
+ decision: o.decision,
43
+ message: o.message,
44
+ reason: o.reason,
45
+ source: o.source,
46
+ });
47
+ console.log(`✓ feedback recorded (${o.type}${o.screen ? ' · ' + o.screen : ''}) → ${p}`);
48
+ }
49
+ catch (e) {
50
+ console.error('Error:', e instanceof Error ? e.message : e);
51
+ process.exit(1);
52
+ }
53
+ });
54
+ fb
55
+ .command('list')
56
+ .description('List / summarise recorded feedback')
57
+ .option('-s, --screen <name>', 'Filter by screen')
58
+ .option('--type <t>', 'Filter by type')
59
+ .option('--json', 'Output raw JSON')
60
+ .action((o) => {
61
+ try {
62
+ const s = (0, feedback_1.summarize)({ screen: o.screen, type: o.type });
63
+ if (o.json) {
64
+ console.log(JSON.stringify(s, null, 2));
65
+ return;
66
+ }
67
+ console.log(`\n━━━ Feedback (${s.total}) ━━━`);
68
+ console.log(` by type: ${Object.entries(s.byType).map(([k, v]) => `${k}=${v}`).join(' ') || '—'}`);
69
+ console.log(` by decision: ${Object.entries(s.byDecision).map(([k, v]) => `${k}=${v}`).join(' ') || '—'}\n`);
70
+ for (const e of s.entries.slice(-20)) {
71
+ console.log(` [${e.type}${e.decision && e.decision !== 'none' ? '/' + e.decision : ''}] ${e.screen ? e.screen + ' · ' : ''}${e.target ? e.target + ' — ' : ''}${e.message}`);
72
+ if (e.reason)
73
+ console.log(` reason: ${e.reason}`);
74
+ }
75
+ console.log('');
76
+ }
77
+ catch (e) {
78
+ console.error('Error:', e instanceof Error ? e.message : e);
79
+ process.exit(1);
80
+ }
81
+ });
82
+ }
83
+ //# sourceMappingURL=qa-feedback.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qa-feedback.js","sourceRoot":"","sources":["../../../src/cli/commands/qa-feedback.ts"],"names":[],"mappings":"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"}
package/dist/cli/index.js CHANGED
@@ -24,6 +24,7 @@ const eval_1 = require("./commands/eval");
24
24
  const manifest_1 = require("./commands/manifest");
25
25
  const ledger_1 = require("./commands/ledger");
26
26
  const feedback_1 = require("./commands/feedback");
27
+ const qa_feedback_1 = require("./commands/qa-feedback");
27
28
  const script_check_1 = require("./commands/script-check");
28
29
  const trace_1 = require("./commands/trace");
29
30
  const challenge_1 = require("./commands/challenge");
@@ -32,6 +33,7 @@ const capability_1 = require("./commands/capability");
32
33
  const flow_check_1 = require("./commands/flow-check");
33
34
  const context_1 = require("./commands/context");
34
35
  const repair_1 = require("./commands/repair");
36
+ const login_1 = require("./commands/login");
35
37
  const registry_1 = require("../capabilities/registry");
36
38
  const discover_1 = require("../capabilities/discover");
37
39
  // Read version from package.json so `--version` never drifts from the released version.
@@ -62,6 +64,7 @@ async function main() {
62
64
  (0, manifest_1.registerManifestCommand)(program);
63
65
  (0, ledger_1.registerLedgerCommand)(program);
64
66
  (0, feedback_1.registerFeedbackCommand)(program);
67
+ (0, qa_feedback_1.registerQaFeedbackCommand)(program);
65
68
  (0, script_check_1.registerScriptCheckCommand)(program);
66
69
  (0, trace_1.registerTraceCommand)(program);
67
70
  (0, challenge_1.registerChallengeCommand)(program);
@@ -72,6 +75,7 @@ async function main() {
72
75
  (0, repair_1.registerRepairCommand)(program);
73
76
  (0, ingest_1.registerIngestCommand)(program);
74
77
  (0, eval_1.registerEvalCommand)(program);
78
+ (0, login_1.registerLoginCommand)(program);
75
79
  // Capability-contributed CLI commands (Capability SPI): drivers own their authoring commands
76
80
  // (e.g. @sungen/driver-api → `sungen api import`). Discover, then register each.
77
81
  (0, discover_1.discoverAndRegisterCapabilities)();
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
@@ -14,6 +14,9 @@ import { EnvironmentInfo } from '../exporters/types';
14
14
  import { DashboardSnapshot, ScenarioSnapshot } from './types';
15
15
  export interface DashboardTarget {
16
16
  name: string;
17
+ kind: 'screen' | 'flow' | 'api';
18
+ /** Derived convenience (`kind === 'flow'`) — kept for the flow-specific
19
+ * branches that predate the api tier. */
17
20
  isFlow: boolean;
18
21
  }
19
22
  export interface BuildSnapshotOptions {
@@ -1 +1 @@
1
- {"version":3,"file":"snapshot-builder.d.ts","sourceRoot":"","sources":["../../src/dashboard/snapshot-builder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAeH,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAEL,iBAAiB,EAGjB,gBAAgB,EAIjB,MAAM,SAAS,CAAC;AAQjB,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,oBAAoB;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,GAAG,EAAE,eAAe,CAAC;IACrB,oEAAoE;IACpE,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,oBAAoB,GAAG,iBAAiB,CAwBvF;AAwSD,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,EAAE,CAkBnE;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,eAAe,CAK5E;AAwFD,YAAY,EAAE,gBAAgB,EAAE,CAAC"}
1
+ {"version":3,"file":"snapshot-builder.d.ts","sourceRoot":"","sources":["../../src/dashboard/snapshot-builder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAeH,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAEL,iBAAiB,EAGjB,gBAAgB,EAIjB,MAAM,SAAS,CAAC;AAQjB,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,QAAQ,GAAG,MAAM,GAAG,KAAK,CAAC;IAChC;8CAC0C;IAC1C,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,oBAAoB;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,GAAG,EAAE,eAAe,CAAC;IACrB,oEAAoE;IACpE,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,oBAAoB,GAAG,iBAAiB,CAwBvF;AA8XD,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,EAAE,CAwBnE;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,eAAe,CAU5E;AAwFD,YAAY,EAAE,gBAAgB,EAAE,CAAC"}
@@ -96,6 +96,24 @@ function buildDashboardSnapshot(options) {
96
96
  // ----------------------------------------------------------------------------
97
97
  // Per-target build
98
98
  // ----------------------------------------------------------------------------
99
+ /**
100
+ * True when this is a mobile project (`platform: mobile` in qa/capabilities.yaml).
101
+ * Mobile projects run per-OS (android/ios) — the dashboard surfaces those on a
102
+ * distinct platform axis (one FeatureSnapshot per OS) rather than as locale tabs.
103
+ * Mirrors `isMobileProject` in src/cli/commands/delivery.ts.
104
+ */
105
+ function isMobileProject(cwd) {
106
+ try {
107
+ const p = path.join(cwd, 'qa', 'capabilities.yaml');
108
+ if (!fs.existsSync(p))
109
+ return false;
110
+ const parsed = (0, yaml_1.parse)(fs.readFileSync(p, 'utf-8'));
111
+ return parsed?.platform === 'mobile';
112
+ }
113
+ catch {
114
+ return false;
115
+ }
116
+ }
99
117
  function buildOneScreen(cwd, target, env) {
100
118
  const base = qaDir(cwd, target);
101
119
  const featuresDir = path.join(base, 'features');
@@ -111,28 +129,49 @@ function buildOneScreen(cwd, target, env) {
111
129
  return null;
112
130
  const specMdFile = path.join(base, 'requirements', 'spec.md');
113
131
  const specLink = fs.existsSync(specMdFile) ? path.relative(cwd, specMdFile) : undefined;
132
+ const mobile = isMobileProject(cwd);
114
133
  const features = [];
115
134
  for (const featureBaseName of featureBaseNames) {
116
- const fs2 = buildOneFeature(cwd, target, featureBaseName, env, specLink);
117
- if (fs2)
118
- features.push(fs2);
135
+ features.push(...buildOneFeature(cwd, target, featureBaseName, env, specLink, mobile));
119
136
  }
120
137
  if (features.length === 0)
121
138
  return null;
122
139
  // Flat scenarios across all features (each scenario already carries its
123
140
  // own `featureBaseName`, so consumers that want to group can do so cheaply).
141
+ // For mobile, per-OS features flatten here so the Compare view can key each
142
+ // scenario by (tcId, platform) via its OS-precise testEnvironment.
124
143
  const scenarios = features.flatMap((f) => f.scenarios);
125
- const label = target.isFlow ? `flow/${target.name}` : target.name;
126
- const primary = features[0];
144
+ const label = target.kind === 'flow' ? `flow/${target.name}`
145
+ : target.kind === 'api' ? `api/${target.name}`
146
+ : target.name;
147
+ // Screen-level platform axis: 'mobile' when any feature is OS-tagged; the
148
+ // union of OSes that actually produced a result file this run.
149
+ const execSet = new Set();
150
+ let anyPlatform = false;
151
+ for (const f of features) {
152
+ if (f.platform)
153
+ anyPlatform = true;
154
+ for (const p of f.executedPlatforms ?? [])
155
+ execSet.add(p);
156
+ }
157
+ const executedPlatforms = execSet.size > 0
158
+ ? ['android', 'ios'].filter((p) => execSet.has(p))
159
+ : undefined;
160
+ // Representative feature for the back-compat top-level fields: prefer Android
161
+ // (deterministic, matches delivery's summary-row preference).
162
+ const primary = features.find((f) => f.platform === 'android') ?? features[0];
127
163
  return {
128
164
  name: label,
129
165
  isFlow: target.isFlow,
166
+ kind: target.kind,
167
+ platform: anyPlatform ? 'mobile' : undefined,
130
168
  featureName: primary.featureName,
131
169
  featurePath: primary.featurePath,
132
170
  specLink,
133
171
  summary: rollupScreenSummary(features),
134
172
  scenarios,
135
173
  features,
174
+ executedPlatforms,
136
175
  };
137
176
  }
138
177
  /**
@@ -140,14 +179,14 @@ function buildOneScreen(cwd, target, env) {
140
179
  * TC IDs are generated using `featureBaseName` as the prefix so they match
141
180
  * the delivery CSV (e.g. `HOME-MODAL-UI-001`, not `HOME-UI-001`).
142
181
  */
143
- function buildOneFeature(cwd, target, featureBaseName, env, specLink) {
182
+ function buildOneFeature(cwd, target, featureBaseName, env, specLink, mobile) {
144
183
  const base = qaDir(cwd, target);
145
184
  const genBase = generatedDir(cwd, target);
146
185
  const featureFile = path.join(base, 'features', `${featureBaseName}.feature`);
147
186
  const testDataFile = path.join(base, 'test-data', `${featureBaseName}.yaml`);
148
187
  const specFile = path.join(genBase, `${featureBaseName}.spec.ts`);
149
188
  if (!fs.existsSync(featureFile))
150
- return null;
189
+ return [];
151
190
  const feature = (0, feature_parser_1.parseFeatureMetadata)(featureFile);
152
191
  const spec = fs.existsSync(specFile)
153
192
  ? (0, spec_parser_1.parseSpecFile)(specFile)
@@ -164,24 +203,15 @@ function buildOneFeature(cwd, target, featureBaseName, env, specLink) {
164
203
  // Discover locale variants by scanning <feature>-test-result*.json files.
165
204
  // Always includes base ('') as the first entry, even if its results file
166
205
  // is missing — UI still needs to render the feature.
167
- // NOTE (mobile): for a `platform: mobile` project the "locale" variants are
168
- // actually the per-OS runs (android / ios) — the dashboard reuses the locale
169
- // switcher to surface them as tabs. The env column per tab is OS-precise (see
170
- // resolvePlatformAppId below), but the top-level featurePath (base variant)
171
- // defaults to the Android id. Delivery has a dedicated per-OS path; aligning the
172
- // dashboard onto a distinct "platform" axis is a possible follow-up.
173
206
  const variants = discoverLocaleVariants(genBase, featureBaseName);
174
- // Build per-locale ScreenSnapshot, then collect into FeatureSnapshot.locales[].
175
- // Base variant is also used to populate the top-level (`scenarios`, `summary`)
176
- // for back-compat with consumers that don't read the locales array.
177
- const locales = [];
178
- let baseBuilt = null;
179
- for (const variant of variants) {
207
+ // Build one per-variant ScreenSnapshot (test-data overlay + results + OS-precise
208
+ // env column). Reused by both the web/i18n (locale-axis) and mobile (platform-axis) paths.
209
+ const buildVariant = (variant) => {
180
210
  const testData = fs.existsSync(testDataFile)
181
211
  ? (0, test_data_resolver_1.loadTestData)(testDataFile, variant.locale || null)
182
212
  : {};
183
213
  const results = variant.resultsPath ? (0, playwright_report_parser_1.loadPlaywrightReport)(variant.resultsPath) : null;
184
- const built = (0, json_exporter_1.buildScreenSnapshot)({
214
+ return (0, json_exporter_1.buildScreenSnapshot)({
185
215
  screen: featureBaseName,
186
216
  isFlow: target.isFlow,
187
217
  featureName: feature.featureName,
@@ -196,15 +226,63 @@ function buildOneFeature(cwd, target, featureBaseName, env, specLink) {
196
226
  featureBaseName,
197
227
  selectorKeyMap,
198
228
  });
229
+ };
230
+ // -- Mobile (platform axis): the per-OS runs (android / ios) are their own
231
+ // FeatureSnapshot entries — one per OS, `platform` tagged — NOT locale tabs.
232
+ // Locale overlays for a given OS (e.g. `android.ja`) nest inside that OS's
233
+ // entry as `locales[]`. This mirrors the delivery per-OS deliverables. --
234
+ if (mobile) {
235
+ const osOf = (loc) => /^android(\.|$)/.test(loc) ? 'android' : /^ios(\.|$)/.test(loc) ? 'ios' : null;
236
+ const osVariants = variants.filter((v) => osOf(v.locale));
237
+ if (osVariants.length > 0) {
238
+ const executedPlatforms = ['android', 'ios'].filter((os) => osVariants.some((v) => osOf(v.locale) === os && v.resultsPath));
239
+ const feats = [];
240
+ for (const os of ['android', 'ios']) {
241
+ const group = osVariants.filter((v) => osOf(v.locale) === os);
242
+ if (group.length === 0)
243
+ continue;
244
+ const localeSnaps = [];
245
+ let primaryBuilt = null;
246
+ for (const v of group) {
247
+ const built = buildVariant(v);
248
+ localeSnaps.push({ locale: v.locale, displayCode: v.displayCode, summary: built.summary, scenarios: built.scenarios });
249
+ // Representative = the bare OS run (locale === os); else the first overlay.
250
+ if (v.locale === os || !primaryBuilt)
251
+ primaryBuilt = built;
252
+ }
253
+ if (!primaryBuilt)
254
+ continue;
255
+ feats.push({
256
+ featureBaseName,
257
+ featureName: primaryBuilt.featureName,
258
+ featurePath: primaryBuilt.featurePath,
259
+ specLink: primaryBuilt.specLink,
260
+ summary: primaryBuilt.summary,
261
+ scenarios: primaryBuilt.scenarios,
262
+ platform: os,
263
+ executedPlatforms,
264
+ locales: localeSnaps.length >= 2 ? localeSnaps : undefined,
265
+ });
266
+ }
267
+ if (feats.length > 0)
268
+ return feats;
269
+ }
270
+ // Mobile project but no per-OS result files yet → fall through to the base
271
+ // (locale-axis) path so the test CASES still render.
272
+ }
273
+ // -- Web / i18n (locale axis): one FeatureSnapshot; variants become locales[]. --
274
+ const locales = [];
275
+ let baseBuilt = null;
276
+ for (const variant of variants) {
277
+ const built = buildVariant(variant);
199
278
  locales.push({
200
279
  locale: variant.locale,
201
280
  displayCode: variant.displayCode,
202
281
  summary: built.summary,
203
282
  scenarios: built.scenarios,
204
283
  });
205
- if (variant.locale === '') {
284
+ if (variant.locale === '')
206
285
  baseBuilt = built;
207
- }
208
286
  }
209
287
  // Guarantee a base entry — every feature in the dashboard must have
210
288
  // something to fall back to even if base results file is missing.
@@ -218,18 +296,18 @@ function buildOneFeature(cwd, target, featureBaseName, env, specLink) {
218
296
  };
219
297
  }
220
298
  if (!baseBuilt)
221
- return null;
222
- return {
223
- featureBaseName,
224
- featureName: baseBuilt.featureName,
225
- featurePath: baseBuilt.featurePath,
226
- specLink: baseBuilt.specLink,
227
- summary: baseBuilt.summary,
228
- scenarios: baseBuilt.scenarios,
229
- // Only expose `locales` array when 2+ variants exist — single-locale
230
- // features stay slimmer and UI can skip the locale tab UI entirely.
231
- locales: locales.length >= 2 ? locales : undefined,
232
- };
299
+ return [];
300
+ return [{
301
+ featureBaseName,
302
+ featureName: baseBuilt.featureName,
303
+ featurePath: baseBuilt.featurePath,
304
+ specLink: baseBuilt.specLink,
305
+ summary: baseBuilt.summary,
306
+ scenarios: baseBuilt.scenarios,
307
+ // Only expose `locales` array when 2+ variants exist — single-locale
308
+ // features stay slimmer and UI can skip the locale tab UI entirely.
309
+ locales: locales.length >= 2 ? locales : undefined,
310
+ }];
233
311
  }
234
312
  /**
235
313
  * Scan the generated directory for `<basename>-test-result*.json` files
@@ -327,40 +405,65 @@ function bumpKey(record, key) {
327
405
  // ----------------------------------------------------------------------------
328
406
  // Path helpers (mirror delivery.ts to stay consistent)
329
407
  // ----------------------------------------------------------------------------
408
+ /** qa/ subfolder for a unit kind (mirrors delivery.ts's qaParent). */
409
+ function qaParent(kind) {
410
+ return kind === 'flow' ? 'flows' : kind === 'api' ? 'api' : 'screens';
411
+ }
330
412
  function qaDir(cwd, target) {
331
- return path.join(cwd, 'qa', target.isFlow ? 'flows' : 'screens', target.name);
413
+ return path.join(cwd, 'qa', qaParent(target.kind), target.name);
332
414
  }
333
415
  function generatedDir(cwd, target) {
334
- return target.isFlow
335
- ? path.join(cwd, 'specs', 'generated', 'flows', target.name)
336
- : path.join(cwd, 'specs', 'generated', target.name);
416
+ // Mirrors delivery.ts: screen → specs/generated/<name>, flow → …/flows/<name>,
417
+ // api → …/api/<name> (api flows carry name 'flows/<flow>', composing correctly).
418
+ const sub = target.kind === 'flow'
419
+ ? path.join('flows', target.name)
420
+ : target.kind === 'api'
421
+ ? path.join('api', target.name)
422
+ : target.name;
423
+ return path.join(cwd, 'specs', 'generated', sub);
337
424
  }
338
425
  // ----------------------------------------------------------------------------
339
426
  // Discovery (also used by CLI)
340
427
  // ----------------------------------------------------------------------------
341
428
  function listDashboardTargets(cwd) {
342
429
  const targets = [];
343
- const screensDir = path.join(cwd, 'qa', 'screens');
344
- if (fs.existsSync(screensDir)) {
345
- for (const d of fs.readdirSync(screensDir, { withFileTypes: true })) {
346
- if (d.isDirectory())
347
- targets.push({ name: d.name, isFlow: false });
430
+ const scan = (kind, skip = () => false) => {
431
+ const dir = path.join(cwd, 'qa', qaParent(kind));
432
+ if (!fs.existsSync(dir))
433
+ return;
434
+ for (const d of fs.readdirSync(dir, { withFileTypes: true })) {
435
+ if (d.isDirectory() && !skip(d.name))
436
+ targets.push({ name: d.name, kind, isFlow: kind === 'flow' });
348
437
  }
349
- }
350
- const flowsDir = path.join(cwd, 'qa', 'flows');
351
- if (fs.existsSync(flowsDir)) {
352
- for (const d of fs.readdirSync(flowsDir, { withFileTypes: true })) {
438
+ };
439
+ scan('screen');
440
+ scan('flow');
441
+ scan('api', (n) => n === 'flows'); // api areas: qa/api/<area> (the flows/ container is a nested tier)
442
+ // api flows: qa/api/flows/<flow> → name 'flows/<flow>', kind 'api'
443
+ // (paths compose to qa/api/flows/… + specs/generated/api/flows/…), mirroring delivery.ts.
444
+ const apiFlowsRoot = path.join(cwd, 'qa', 'api', 'flows');
445
+ if (fs.existsSync(apiFlowsRoot)) {
446
+ for (const d of fs.readdirSync(apiFlowsRoot, { withFileTypes: true })) {
353
447
  if (d.isDirectory())
354
- targets.push({ name: d.name, isFlow: true });
448
+ targets.push({ name: path.posix.join('flows', d.name), kind: 'api', isFlow: false });
355
449
  }
356
450
  }
357
451
  return targets.sort((a, b) => a.name.localeCompare(b.name));
358
452
  }
359
453
  function resolveTargetType(cwd, name) {
360
- if (fs.existsSync(path.join(cwd, 'qa', 'flows', name))) {
361
- return { name, isFlow: true };
362
- }
363
- return { name, isFlow: false };
454
+ if (fs.existsSync(path.join(cwd, 'qa', 'flows', name)))
455
+ return { name, kind: 'flow', isFlow: true };
456
+ // Screen before api so `dashboard <name>` resolves the same unit as `delivery <name>`
457
+ // when a repo has both qa/screens/<name> and qa/api/<name> (delivery.ts precedence).
458
+ if (fs.existsSync(path.join(cwd, 'qa', 'screens', name)))
459
+ return { name, kind: 'screen', isFlow: false };
460
+ if (fs.existsSync(path.join(cwd, 'qa', 'api', name)))
461
+ return { name, kind: 'api', isFlow: false };
462
+ // `flows/<flow>` or a bare api-flow name → qa/api/flows/<flow>
463
+ const apiFlow = name.startsWith('flows/') ? name : path.posix.join('flows', name);
464
+ if (fs.existsSync(path.join(cwd, 'qa', 'api', apiFlow)))
465
+ return { name: apiFlow, kind: 'api', isFlow: false };
466
+ return { name, kind: 'screen', isFlow: false };
364
467
  }
365
468
  // ----------------------------------------------------------------------------
366
469
  // Run ID + environment metadata (Vietnam Indochina Time, UTC+7, no DST)