@aifabrix/builder 2.52.0 → 2.53.0

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 (310) hide show
  1. package/lib/api/datasources-extended.api.js +8 -8
  2. package/lib/api/keycloak.api.js +92 -0
  3. package/lib/api/token-providers.api.js +77 -0
  4. package/lib/api/types/keycloak.types.js +23 -0
  5. package/lib/api/types/token-providers.types.js +65 -0
  6. package/lib/app/deploy-config.js +3 -2
  7. package/lib/app/down.js +16 -1
  8. package/lib/channels/add-channel.js +134 -57
  9. package/lib/channels/approval-guides/chatgpt.js +1 -1
  10. package/lib/channels/approval-guides/index.js +3 -0
  11. package/lib/channels/approval-guides/microsoft-copilot.js +27 -14
  12. package/lib/channels/channel-add-ensure.js +128 -0
  13. package/lib/channels/channel-add-existing.js +72 -0
  14. package/lib/channels/channel-artifact-generator.js +11 -1
  15. package/lib/channels/channel-branding.js +94 -0
  16. package/lib/channels/channel-credential.js +8 -0
  17. package/lib/channels/channel-discovery.js +330 -0
  18. package/lib/channels/channel-external-system.js +1 -1
  19. package/lib/channels/channel-runtime-resolver.js +107 -14
  20. package/lib/channels/channel-status-display.js +36 -3
  21. package/lib/channels/microsoft-copilot-channel-add.js +138 -0
  22. package/lib/channels/microsoft-copilot-entra-sso-form.js +251 -0
  23. package/lib/channels/microsoft-copilot-entra-sso.js +285 -0
  24. package/lib/channels/microsoft-copilot-icons.js +74 -0
  25. package/lib/channels/microsoft-copilot-limits.js +103 -0
  26. package/lib/channels/microsoft-copilot-package.js +178 -52
  27. package/lib/channels/microsoft-copilot-validate.js +227 -0
  28. package/lib/cli/attach-default-help-examples.js +102 -0
  29. package/lib/cli/index.js +4 -0
  30. package/lib/cli/infra-guided.js +13 -6
  31. package/lib/cli/setup-credential-deployment.js +7 -5
  32. package/lib/cli/setup-infra.js +31 -4
  33. package/lib/cli/setup-utility-validate.js +1 -0
  34. package/lib/commands/channel-add-setup.js +28 -9
  35. package/lib/commands/channel.js +14 -10
  36. package/lib/commands/credential-push.js +37 -5
  37. package/lib/commands/integration-definition.js +158 -0
  38. package/lib/commands/package-score.js +72 -0
  39. package/lib/commands/repair-auth-apply.js +19 -5
  40. package/lib/commands/repair-datasource-sync-operations.js +4 -1
  41. package/lib/commands/role-assistant-test-output.js +200 -0
  42. package/lib/commands/role-assistant.js +163 -166
  43. package/lib/datasource/capability/capability-resolve.js +15 -5
  44. package/lib/datasource/capability/copy-operations.js +14 -4
  45. package/lib/deployment/deployer-poll-run.js +2 -2
  46. package/lib/deployment/deployer.js +1 -1
  47. package/lib/deployment/environment.js +2 -2
  48. package/lib/external-system/deploy.js +11 -21
  49. package/lib/infrastructure/index.js +19 -4
  50. package/lib/integration-definition/apply.js +476 -0
  51. package/lib/integration-definition/canonical-json.js +51 -0
  52. package/lib/integration-definition/emit-suite-bid.js +94 -0
  53. package/lib/integration-definition/enrich-crm-field-mappings.js +240 -0
  54. package/lib/integration-definition/enrich-openapi-operations.js +433 -0
  55. package/lib/integration-definition/from-discovery.js +339 -0
  56. package/lib/integration-definition/hash.js +37 -0
  57. package/lib/integration-definition/index.js +37 -0
  58. package/lib/integration-definition/resource-type-catalog.js +59 -0
  59. package/lib/integration-definition/system-kind-enum.js +40 -0
  60. package/lib/integration-definition/validate.js +148 -0
  61. package/lib/lab/package-parity-score.js +417 -0
  62. package/lib/programmatic/builder-api-request.js +5 -1
  63. package/lib/programmatic/builder-help-governance.js +3 -2
  64. package/lib/programmatic/capability-registry.js +20 -0
  65. package/lib/programmatic/help-content/channel-onboarding.md +35 -0
  66. package/lib/programmatic/help-content/cip-overview.md +27 -0
  67. package/lib/programmatic/help-content/connected-system-ui.md +70 -0
  68. package/lib/programmatic/help-content/dimensions-guide.md +23 -0
  69. package/lib/programmatic/help-content/enterprise-sync-fabrix.md +123 -0
  70. package/lib/programmatic/help-content/overview.md +25 -0
  71. package/lib/programmatic/help-content/subscription-guide.md +27 -0
  72. package/lib/programmatic/help-content/workflow.md +50 -0
  73. package/lib/programmatic/openapi-component-schemas.js +7 -1
  74. package/lib/programmatic/openapi-descriptions.js +46 -0
  75. package/lib/programmatic/openapi-envelope-examples.js +280 -0
  76. package/lib/programmatic/openapi-operation-bindings.js +50 -0
  77. package/lib/programmatic/openapi-request-schemas.js +40 -0
  78. package/lib/programmatic/openapi-result-schemas-overflow.js +101 -0
  79. package/lib/programmatic/openapi-result-schemas.js +76 -99
  80. package/lib/programmatic/openapi-schema-descriptions-core.js +25 -4
  81. package/lib/programmatic/openapi-schema-descriptions-help.js +1 -1
  82. package/lib/programmatic/openapi-schema-descriptions-operations.js +43 -0
  83. package/lib/programmatic/openapi-schema-helpers.js +9 -9
  84. package/lib/programmatic/openapi-spec.js +11 -1
  85. package/lib/programmatic/orchestration-openapi-meta.js +10 -0
  86. package/lib/programmatic/persist-supplied-openapi.js +180 -0
  87. package/lib/programmatic/route-handler-map.js +35 -1
  88. package/lib/programmatic/run-channel-package.js +236 -0
  89. package/lib/programmatic/run-integration-definition.js +77 -0
  90. package/lib/role-assistant/evidence-sync.js +55 -7
  91. package/lib/role-assistant/knowledge-sync-logging.js +58 -0
  92. package/lib/role-assistant/knowledge-sync.js +88 -93
  93. package/lib/role-assistant/test-cases-bid.js +155 -0
  94. package/lib/role-assistant/test-cases-evaluate.js +9 -1
  95. package/lib/role-assistant/test-cases-honesty.js +24 -10
  96. package/lib/role-assistant/test-cases.js +28 -0
  97. package/lib/role-assistant/test-runner-attachments.js +118 -0
  98. package/lib/role-assistant/test-runner-bid-apply.js +201 -0
  99. package/lib/role-assistant/test-runner-bid-document.js +278 -0
  100. package/lib/role-assistant/test-runner-start-payload.js +96 -0
  101. package/lib/role-assistant/test-runner-workhub-answers.js +249 -20
  102. package/lib/role-assistant/test-runner-workhub-helpers.js +100 -0
  103. package/lib/role-assistant/test-runner-workhub-soft.js +86 -0
  104. package/lib/role-assistant/test-runner-workhub.js +152 -107
  105. package/lib/role-assistant/test-runner.js +5 -63
  106. package/lib/role-assistant/test-suite.js +112 -0
  107. package/lib/schema/application-schema.json +56 -6
  108. package/lib/schema/external-datasource.schema.json +103 -17
  109. package/lib/schema/external-system.schema.json +54 -3
  110. package/lib/schema/integration-definition.schema.json +195 -0
  111. package/lib/utils/authentication-shape.js +6 -0
  112. package/lib/utils/compose-generator.js +10 -1
  113. package/lib/utils/compose-traefik-extra-routes.js +107 -0
  114. package/lib/utils/docker-build.js +2 -2
  115. package/lib/utils/help-builder.js +3 -1
  116. package/lib/utils/promote-dev-docker-image.js +102 -18
  117. package/lib/utils/transient-developer-id.js +28 -0
  118. package/package.json +14 -16
  119. package/templates/README.md +12 -1
  120. package/templates/applications/dataplane/application.yaml +10 -2
  121. package/templates/applications/dataplane/env.template +7 -6
  122. package/templates/applications/miso-controller/application.yaml +23 -8
  123. package/templates/applications/miso-controller/env.template +8 -2
  124. package/templates/applications/miso-controller/rbac.yaml +1 -0
  125. package/templates/channels/README.md +11 -1
  126. package/templates/channels/brand/color-192.png +0 -0
  127. package/templates/channels/brand/dark/128.png +0 -0
  128. package/templates/channels/brand/dark/192.png +0 -0
  129. package/templates/channels/brand/dark/256.png +0 -0
  130. package/templates/channels/brand/dark/32.png +0 -0
  131. package/templates/channels/brand/dark/512.png +0 -0
  132. package/templates/channels/brand/dark/64.png +0 -0
  133. package/templates/channels/brand/light/128.png +0 -0
  134. package/templates/channels/brand/light/192.png +0 -0
  135. package/templates/channels/brand/light/256.png +0 -0
  136. package/templates/channels/brand/light/32.png +0 -0
  137. package/templates/channels/brand/light/512.png +0 -0
  138. package/templates/channels/brand/light/64.png +0 -0
  139. package/templates/channels/brand/mono-white/128.png +0 -0
  140. package/templates/channels/brand/mono-white/192.png +0 -0
  141. package/templates/channels/brand/mono-white/256.png +0 -0
  142. package/templates/channels/brand/mono-white/32.png +0 -0
  143. package/templates/channels/brand/mono-white/512.png +0 -0
  144. package/templates/channels/brand/mono-white/64.png +0 -0
  145. package/templates/channels/brand/outline-32.png +0 -0
  146. package/templates/channels/chatgpt/install.template.md.hbs +6 -4
  147. package/templates/channels/chatgpt/integration/README.md +4 -5
  148. package/templates/channels/chatgpt/integration/chatgpt-system.json +1 -16
  149. package/templates/channels/microsoft-copilot/ai-plugin.template.json.hbs +14 -16
  150. package/templates/channels/microsoft-copilot/declarative-agent.template.json.hbs +5 -3
  151. package/templates/channels/microsoft-copilot/entra-sso-registration.template.md.hbs +30 -0
  152. package/templates/channels/microsoft-copilot/fi-fi.template.json.hbs +7 -0
  153. package/templates/channels/microsoft-copilot/install.template.md.hbs +93 -22
  154. package/templates/channels/microsoft-copilot/integration/README.md +2 -3
  155. package/templates/channels/microsoft-copilot/integration/microsoft-copilot-system.json +1 -22
  156. package/templates/channels/microsoft-copilot/manifest.template.json.hbs +34 -19
  157. package/templates/channels/microsoft-copilot/oauth-plugin-vault-required.template.md.hbs +15 -0
  158. package/templates/channels/slack/integration/README.md +2 -4
  159. package/templates/channels/teams-bot/install.template.md.hbs +4 -0
  160. package/templates/channels/teams-bot/integration/README.md +3 -5
  161. package/templates/channels/web-embed/integration/web-embed-system.json +1 -16
  162. package/templates/python/docker-compose.hbs +36 -0
  163. package/templates/typescript/docker-compose.hbs +36 -0
  164. package/.cursor/plans/Done/157.1-security-quality-violations-by-type.csv +0 -374
  165. package/.cursor/process/README.md +0 -9
  166. package/.cursor/rules/anchor-docs.mdc +0 -15
  167. package/.cursor/rules/assistant-driven-integration-build.mdc +0 -40
  168. package/.cursor/rules/cli-layout.mdc +0 -81
  169. package/.cursor/rules/cli-product-neutral.mdc +0 -43
  170. package/.cursor/rules/deploy-rules/docker-image-ip-policy.mdc +0 -49
  171. package/.cursor/rules/docs-rules.mdc +0 -32
  172. package/.cursor/rules/plan-rules/application-lifecycle.mdc +0 -30
  173. package/.cursor/rules/plan-rules/plan-frontmatter.mdc +0 -35
  174. package/.cursor/rules/plan-rules/plan-lifecycle.mdc +0 -31
  175. package/.cursor/rules/plan-rules/plan-validation-gate.mdc +0 -34
  176. package/.cursor/rules/project-rules.mdc +0 -913
  177. package/.cursor/skills/README.md +0 -15
  178. package/.dockerignore +0 -52
  179. package/.eslintignore +0 -22
  180. package/.markdownlint.json +0 -14
  181. package/anchor-docs/README.md +0 -10
  182. package/anchor-docs/_TEMPLATE +0 -24
  183. package/babel.config.js +0 -6
  184. package/bin/builder-api-cleanup.js +0 -26
  185. package/bin/builder-api-nest.js +0 -10
  186. package/bin/builder-api-spike.js +0 -19
  187. package/enterprise-knowledge/README.md +0 -50
  188. package/enterprise-knowledge/enterprise-integration-lifecycle/README.md +0 -22
  189. package/enterprise-knowledge/enterprise-integration-lifecycle/application.json +0 -15
  190. package/enterprise-knowledge/enterprise-integration-lifecycle/deploy.js +0 -69
  191. package/enterprise-knowledge/enterprise-integration-lifecycle/enterprise-integration-lifecycle-deploy.json +0 -52
  192. package/enterprise-knowledge/enterprise-integration-lifecycle/enterprise-integration-lifecycle-system.json +0 -29
  193. package/enterprise-knowledge/enterprise-integration-lifecycle/env.template +0 -2
  194. package/integration/README.md +0 -81
  195. package/integration/github/README.md +0 -53
  196. package/integration/github/application.json +0 -21
  197. package/integration/github/deploy.js +0 -72
  198. package/integration/github/env.template +0 -11
  199. package/integration/github/github-datasource-issue-comments.json +0 -239
  200. package/integration/github/github-datasource-issues.json +0 -399
  201. package/integration/github/github-datasource-repository.json +0 -354
  202. package/integration/github/github-deploy.json +0 -1202
  203. package/integration/github/github-system.json +0 -82
  204. package/integration/hubspot-test/README.md +0 -159
  205. package/integration/hubspot-test/application.json +0 -54
  206. package/integration/hubspot-test/companies.json +0 -2048
  207. package/integration/hubspot-test/create-hubspot.js +0 -523
  208. package/integration/hubspot-test/env.template +0 -4
  209. package/integration/hubspot-test/hubspot-test-datasource-company.json +0 -138
  210. package/integration/hubspot-test/hubspot-test-datasource-contact.json +0 -146
  211. package/integration/hubspot-test/hubspot-test-datasource-deal.json +0 -146
  212. package/integration/hubspot-test/hubspot-test-datasource-users.json +0 -76
  213. package/integration/hubspot-test/hubspot-test-deploy.json +0 -2160
  214. package/integration/hubspot-test/hubspot-test-system.json +0 -74
  215. package/integration/hubspot-test/rbac.json +0 -166
  216. package/integration/hubspot-test/test-artifacts/wizard-hubspot-credential-real.yaml +0 -20
  217. package/integration/hubspot-test/test-artifacts/wizard-hubspot-env-vars.yaml +0 -9
  218. package/integration/hubspot-test/test-artifacts/wizard-invalid-add-datasource.yaml +0 -5
  219. package/integration/hubspot-test/test-artifacts/wizard-invalid-app-name.yaml +0 -5
  220. package/integration/hubspot-test/test-artifacts/wizard-invalid-credential-create.yaml +0 -7
  221. package/integration/hubspot-test/test-artifacts/wizard-invalid-credential-select.yaml +0 -7
  222. package/integration/hubspot-test/test-artifacts/wizard-invalid-known-platform.yaml +0 -4
  223. package/integration/hubspot-test/test-artifacts/wizard-invalid-missing-app.yaml +0 -4
  224. package/integration/hubspot-test/test-artifacts/wizard-invalid-missing-source.yaml +0 -2
  225. package/integration/hubspot-test/test-artifacts/wizard-invalid-mode.yaml +0 -5
  226. package/integration/hubspot-test/test-artifacts/wizard-invalid-openapi-file.yaml +0 -5
  227. package/integration/hubspot-test/test-artifacts/wizard-invalid-openapi-url.yaml +0 -4
  228. package/integration/hubspot-test/test-artifacts/wizard-invalid-source.yaml +0 -4
  229. package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-array-test.yaml +0 -5
  230. package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-key-test.yaml +0 -5
  231. package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-path-test.yaml +0 -5
  232. package/integration/hubspot-test/test-artifacts/wizard-valid-for-dimension-test.yaml +0 -5
  233. package/integration/hubspot-test/test-artifacts/wizard-valid-for-rbac-test.yaml +0 -5
  234. package/integration/hubspot-test/test-artifacts/wizard-valid-for-rbac-yaml-test.yaml +0 -5
  235. package/integration/hubspot-test/test-dataplane-down-helpers.js +0 -249
  236. package/integration/hubspot-test/test-dataplane-down-tests.js +0 -386
  237. package/integration/hubspot-test/test-dataplane-down.js +0 -209
  238. package/integration/hubspot-test/test.js +0 -1588
  239. package/integration/hubspot-test/wizard-hubspot-e2e.yaml +0 -16
  240. package/integration/hubspot-test/wizard-hubspot-platform.yaml +0 -8
  241. package/integration/hubspot-test/wizard-hubspot-test-headless.yaml +0 -23
  242. package/integration/roundtrip-test-local/README.md +0 -143
  243. package/integration/roundtrip-test-local/application.yaml +0 -13
  244. package/integration/roundtrip-test-local/env.template +0 -15
  245. package/integration/roundtrip-test-local/roundtrip-test-local-datasource-roundtrip-test-company.yaml +0 -14
  246. package/integration/roundtrip-test-local/roundtrip-test-local-deploy.json +0 -61
  247. package/integration/roundtrip-test-local/roundtrip-test-local-system.yaml +0 -25
  248. package/integration/roundtrip-test-local2/README.md +0 -143
  249. package/integration/roundtrip-test-local2/application.yaml +0 -13
  250. package/integration/roundtrip-test-local2/env.template +0 -15
  251. package/integration/roundtrip-test-local2/roundtrip-test-local2-datasource-company.yaml +0 -31
  252. package/integration/roundtrip-test-local2/roundtrip-test-local2-deploy.json +0 -86
  253. package/integration/roundtrip-test-local2/roundtrip-test-local2-system.yaml +0 -25
  254. package/integration/test/wizard.yaml +0 -8
  255. package/jest.config.coverage.js +0 -37
  256. package/jest.config.default.js +0 -11
  257. package/jest.config.integration.fixtures.js +0 -22
  258. package/jest.config.integration.js +0 -33
  259. package/jest.config.isolated.js +0 -11
  260. package/jest.config.manual.js +0 -30
  261. package/jest.global-live-fabrix-hooks.js +0 -36
  262. package/jest.isolated-projects.js +0 -260
  263. package/jest.projects.js +0 -76
  264. package/packages/builder-api/README.md +0 -120
  265. package/packages/builder-api/package.json +0 -30
  266. package/packages/builder-api/src/app.module.ts +0 -7
  267. package/packages/builder-api/src/auth/auth.module.ts +0 -5
  268. package/packages/builder-api/src/external-system/external-system.module.ts +0 -5
  269. package/packages/builder-api/src/main.ts +0 -16
  270. package/packages/builder-api/tsconfig.json +0 -15
  271. package/scripts/check-builder-api-architecture-grep.js +0 -79
  272. package/scripts/check-datasource-test-run-schema-sync.js +0 -34
  273. package/scripts/ci-fix.sh +0 -19
  274. package/scripts/ci-simulate.sh +0 -19
  275. package/scripts/diagnose-cli.js +0 -150
  276. package/scripts/docker_image_content_gate.js +0 -47
  277. package/scripts/docker_image_content_gate.sh +0 -5
  278. package/scripts/export-builder-api-evidence.js +0 -262
  279. package/scripts/install-local.js +0 -490
  280. package/scripts/lib/builder-api-docker-staging-copy.js +0 -60
  281. package/scripts/lib/builder-api-docker-staging-core.js +0 -183
  282. package/scripts/lib/docker-image-content-gate-core.js +0 -166
  283. package/scripts/lib/plan-actor.js +0 -109
  284. package/scripts/lib/plan-config.js +0 -20
  285. package/scripts/lib/plan-gates.js +0 -154
  286. package/scripts/lib/plan-meta.js +0 -104
  287. package/scripts/lib/plan-parse.js +0 -162
  288. package/scripts/lib/plan-prevalidate-gates.js +0 -337
  289. package/scripts/lib/plan-run-core.js +0 -125
  290. package/scripts/lib/plan-run-report.js +0 -119
  291. package/scripts/plan-approve.js +0 -24
  292. package/scripts/plan-archive.js +0 -49
  293. package/scripts/plan-run.js +0 -13
  294. package/scripts/plan-set-actor.js +0 -21
  295. package/scripts/plan-validate.js +0 -80
  296. package/scripts/pnpm/af.mjs +0 -110
  297. package/scripts/pnpm/check-quiet.mjs +0 -32
  298. package/scripts/pnpm/dev-install.mjs +0 -29
  299. package/scripts/pnpm/dev-reload.mjs +0 -18
  300. package/scripts/pnpm/help.mjs +0 -55
  301. package/scripts/pnpm/lib/dev-reload-core.mjs +0 -189
  302. package/scripts/pnpm/lib/dev-reload-log-capture.mjs +0 -67
  303. package/scripts/pnpm/lib/dev-reload-log-utils.mjs +0 -32
  304. package/scripts/pnpm/lib/resolve-aifabrix-bin.mjs +0 -124
  305. package/scripts/pnpm/lib/resolve-app-url.mjs +0 -183
  306. package/scripts/pnpm/lib/spinner.mjs +0 -42
  307. package/scripts/pnpm-global-remove.js +0 -48
  308. package/scripts/stage_builder_api_for_docker.js +0 -65
  309. package/scripts/sync-builder-api-template.js +0 -44
  310. package/scripts/test-dataplane-bootstrap.js +0 -331
@@ -0,0 +1,200 @@
1
+ /**
2
+ * Role Assistant test CLI envelope + human summary output.
3
+ * @fileoverview Split from role-assistant.js for max-lines / complexity
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const logger = require('../utils/logger');
9
+ const { VERDICT } = require('../lifecycle/product-model');
10
+ const {
11
+ sectionTitle,
12
+ headerKeyValue,
13
+ formatSuccessLine,
14
+ formatSuccessParagraph,
15
+ formatWarningLine,
16
+ formatBlockingError,
17
+ metadata
18
+ } = require('../utils/cli-test-layout-chalk');
19
+
20
+ /**
21
+ * Bucket a lifecycle step status into ok | failed | skipped | other.
22
+ * @param {string} status
23
+ * @returns {'ok'|'failed'|'skipped'|'other'}
24
+ */
25
+ function bucketStepStatus(status) {
26
+ const normalized = String(status || 'ok').toLowerCase();
27
+ if (normalized === 'ok' || normalized === 'verified' || normalized === 'passed') {
28
+ return 'ok';
29
+ }
30
+ if (normalized === 'failed' || normalized === 'error') {
31
+ return 'failed';
32
+ }
33
+ if (normalized === 'skipped' || normalized === 'n/a' || normalized === 'not_applicable') {
34
+ return 'skipped';
35
+ }
36
+ return 'other';
37
+ }
38
+
39
+ /**
40
+ * Summarize lifecycle stepsRun rows (API uses step/status/datasourceKey, not pillar).
41
+ * @param {Array<Object|string>} steps
42
+ * @returns {string}
43
+ */
44
+ function formatStepsRunSummary(steps) {
45
+ const byStep = Object.create(null);
46
+ for (const row of steps) {
47
+ if (!row || typeof row !== 'object') {
48
+ const label = String(row);
49
+ byStep[label] = byStep[label] || { ok: 0, failed: 0, skipped: 0, other: 0 };
50
+ byStep[label].other += 1;
51
+ continue;
52
+ }
53
+ const stepName = String(row.step || row.pillar || row.name || 'step').trim() || 'step';
54
+ const status = row.status || (row.success === false ? 'failed' : 'ok');
55
+ byStep[stepName] = byStep[stepName] || { ok: 0, failed: 0, skipped: 0, other: 0 };
56
+ byStep[stepName][bucketStepStatus(status)] += 1;
57
+ }
58
+ return Object.keys(byStep)
59
+ .map((stepName) => {
60
+ const counts = byStep[stepName];
61
+ const parts = [];
62
+ if (counts.ok) parts.push(`${counts.ok} ok`);
63
+ if (counts.failed) parts.push(`${counts.failed} failed`);
64
+ if (counts.skipped) parts.push(`${counts.skipped} skipped`);
65
+ if (counts.other) parts.push(`${counts.other} other`);
66
+ return parts.length > 0 ? `${stepName} (${parts.join(', ')})` : stepName;
67
+ })
68
+ .join('; ');
69
+ }
70
+
71
+ function mapCaseEnvelopeRow(row) {
72
+ return {
73
+ caseId: row.caseId,
74
+ sourceFile: row.sourceFile,
75
+ passed: row.passed,
76
+ lifecycleState: row.lifecycleState,
77
+ assertions: row.assertions,
78
+ failedAssertions: row.failedAssertions,
79
+ warnings: Array.isArray(row.warnings) ? row.warnings : [],
80
+ execution: row.execution || null
81
+ };
82
+ }
83
+
84
+ function attachEnvelopeCases(envelope, caseResult) {
85
+ if (Array.isArray(caseResult)) {
86
+ envelope.cases = caseResult.map(mapCaseEnvelopeRow);
87
+ const passed = envelope.cases.filter((c) => c.passed).length;
88
+ envelope.summary = {
89
+ total: envelope.cases.length,
90
+ passed,
91
+ failed: envelope.cases.length - passed
92
+ };
93
+ envelope.case = envelope.cases.length === 1 ? envelope.cases[0] : null;
94
+ return;
95
+ }
96
+ if (caseResult) {
97
+ envelope.case = mapCaseEnvelopeRow(caseResult);
98
+ }
99
+ }
100
+
101
+ function buildRoleAssistantTestEnvelope(roleKey, verdict, portalUrl, result, opts, caseResult = null) {
102
+ const report = result.report || {};
103
+ const multiCase = Array.isArray(caseResult) && caseResult.length > 1;
104
+ const envelope = {
105
+ roleKey,
106
+ command: 'role-assistant test',
107
+ mode: opts.suite || multiCase ? 'suite' : 'single',
108
+ suite: opts.suite || null,
109
+ verdict,
110
+ portalUrl,
111
+ openApiPublished: result.openApiPublished === true,
112
+ certification: report.certification || null,
113
+ recommendations: Array.isArray(report.recommendations) ? report.recommendations : [],
114
+ warnings: Array.isArray(report.warnings) ? report.warnings : [],
115
+ stepsRun: result.stepsRun || [],
116
+ case: null,
117
+ cases: null,
118
+ summary: null
119
+ };
120
+ attachEnvelopeCases(envelope, caseResult);
121
+ if (opts.verbose || opts.debug) {
122
+ envelope.report = report;
123
+ }
124
+ if (opts.debug) {
125
+ envelope.debug = {
126
+ observation: caseResult && !Array.isArray(caseResult) ? caseResult.observation : null,
127
+ lifecycleResult: result
128
+ };
129
+ }
130
+ return envelope;
131
+ }
132
+
133
+ function displayCaseResultSummary(caseResult) {
134
+ if (!caseResult) {
135
+ return;
136
+ }
137
+ logger.log(headerKeyValue('Case:', caseResult.caseId));
138
+ logger.log(headerKeyValue('Case file:', caseResult.sourceFile));
139
+ logger.log(headerKeyValue('Observed state:', caseResult.lifecycleState));
140
+ if (caseResult.execution) {
141
+ logger.log(headerKeyValue('Execution id:', caseResult.execution.processExecutionId || 'n/a'));
142
+ logger.log(headerKeyValue('Final runtime status:', caseResult.execution.finalStatus || 'n/a'));
143
+ }
144
+ if (caseResult.passed) {
145
+ logger.log(formatSuccessLine('Case assertions passed.'));
146
+ const warnings = Array.isArray(caseResult.warnings) ? caseResult.warnings : [];
147
+ for (const row of warnings) {
148
+ logger.log(formatWarningLine(row.message || String(row)));
149
+ }
150
+ return;
151
+ }
152
+ logger.log(formatBlockingError('Case assertions failed.'));
153
+ for (const row of caseResult.failedAssertions) {
154
+ logger.log(formatWarningLine(row.message));
155
+ }
156
+ }
157
+
158
+ function displayPortalAndVerdict(portalUrl, verdict) {
159
+ if (portalUrl) {
160
+ logger.log(headerKeyValue('Portal:', portalUrl));
161
+ } else {
162
+ logger.log(formatWarningLine('Portal link unavailable. Configure dataplane public URL and retry.'));
163
+ }
164
+ const verdictMessage =
165
+ verdict === VERDICT.VERIFIED
166
+ ? 'Lifecycle checks passed. Open portal for full execution steps and logs.'
167
+ : 'Lifecycle checks are not fully verified. Open portal to inspect blocking steps.';
168
+ if (verdict === VERDICT.VERIFIED) {
169
+ logger.log(formatSuccessParagraph(verdictMessage));
170
+ } else {
171
+ logger.log(formatWarningLine(verdictMessage));
172
+ }
173
+ }
174
+
175
+ function displayRoleAssistantTestSummary(roleKey, verdict, portalUrl, result, caseResult = null) {
176
+ logger.log('');
177
+ logger.log(sectionTitle(`${String(roleKey).toUpperCase()} ROLE ASSISTANT TEST`));
178
+ logger.log('');
179
+ logger.log(headerKeyValue('Role key:', roleKey));
180
+ displayPortalAndVerdict(portalUrl, verdict);
181
+ const steps = Array.isArray(result.stepsRun) ? result.stepsRun : [];
182
+ if (steps.length > 0) {
183
+ logger.log(metadata(`Steps run: ${formatStepsRunSummary(steps)}`));
184
+ }
185
+ if (Array.isArray(caseResult)) {
186
+ for (const row of caseResult) {
187
+ displayCaseResultSummary(row);
188
+ }
189
+ const passed = caseResult.filter((c) => c.passed).length;
190
+ logger.log(headerKeyValue('Suite summary:', `${passed}/${caseResult.length} passed`));
191
+ return;
192
+ }
193
+ displayCaseResultSummary(caseResult);
194
+ }
195
+
196
+ module.exports = {
197
+ formatStepsRunSummary,
198
+ buildRoleAssistantTestEnvelope,
199
+ displayRoleAssistantTestSummary
200
+ };
@@ -8,6 +8,7 @@
8
8
 
9
9
  'use strict';
10
10
 
11
+ const path = require('path');
11
12
  const logger = require('../utils/logger');
12
13
  const { handleCommandError } = require('../utils/cli-utils');
13
14
  const {
@@ -22,6 +23,11 @@ const {
22
23
  loadRoleAssistantTestCase,
23
24
  evaluateRoleAssistantTestCase
24
25
  } = require('../role-assistant/test-cases');
26
+ const {
27
+ resolveCasesRoot,
28
+ discoverSuiteCaseFiles,
29
+ filterDiscoveredCases
30
+ } = require('../role-assistant/test-suite');
25
31
  const {
26
32
  isExecutableRoleAssistantCase,
27
33
  runRoleAssistantCaseLifecycle,
@@ -32,66 +38,39 @@ const { collectString } = require('./governance');
32
38
  const {
33
39
  sectionTitle,
34
40
  headerKeyValue,
35
- formatSuccessLine,
36
- formatSuccessParagraph,
37
- formatWarningLine,
38
- formatBlockingError,
39
41
  metadata
40
42
  } = require('../utils/cli-test-layout-chalk');
43
+ const {
44
+ formatStepsRunSummary,
45
+ buildRoleAssistantTestEnvelope,
46
+ displayRoleAssistantTestSummary
47
+ } = require('./role-assistant-test-output');
41
48
  const {
42
49
  bareRoleKey,
43
50
  roleAssistantSystemKey
44
51
  } = require('../utils/role-assistant-paths');
45
52
 
46
- /**
47
- * Bucket a lifecycle step status into ok | failed | skipped | other.
48
- * @param {string} status
49
- * @returns {'ok'|'failed'|'skipped'|'other'}
50
- */
51
- function bucketStepStatus(status) {
52
- const normalized = String(status || 'ok').toLowerCase();
53
- if (normalized === 'ok' || normalized === 'verified' || normalized === 'passed') {
54
- return 'ok';
55
- }
56
- if (normalized === 'failed' || normalized === 'error') {
57
- return 'failed';
58
- }
59
- if (normalized === 'skipped' || normalized === 'n/a' || normalized === 'not_applicable') {
60
- return 'skipped';
61
- }
62
- return 'other';
63
- }
53
+ function roleAssistantGroupHelpAfter() {
54
+ return `
55
+ Subcommands:
56
+ test <roleKey> Run lifecycle checks, optional YAML cases/suites, and print a portal deep link
64
57
 
65
- /**
66
- * Summarize lifecycle stepsRun rows (API uses step/status/datasourceKey, not pillar).
67
- * @param {Array<Object|string>} steps
68
- * @returns {string}
69
- */
70
- function formatStepsRunSummary(steps) {
71
- const byStep = Object.create(null);
72
- for (const row of steps) {
73
- if (!row || typeof row !== 'object') {
74
- const label = String(row);
75
- byStep[label] = byStep[label] || { ok: 0, failed: 0, skipped: 0, other: 0 };
76
- byStep[label].other += 1;
77
- continue;
78
- }
79
- const stepName = String(row.step || row.pillar || row.name || 'step').trim() || 'step';
80
- const status = row.status || (row.success === false ? 'failed' : 'ok');
81
- byStep[stepName] = byStep[stepName] || { ok: 0, failed: 0, skipped: 0, other: 0 };
82
- byStep[stepName][bucketStepStatus(status)] += 1;
83
- }
84
- return Object.keys(byStep)
85
- .map((stepName) => {
86
- const counts = byStep[stepName];
87
- const parts = [];
88
- if (counts.ok) parts.push(`${counts.ok} ok`);
89
- if (counts.failed) parts.push(`${counts.failed} failed`);
90
- if (counts.skipped) parts.push(`${counts.skipped} skipped`);
91
- if (counts.other) parts.push(`${counts.other} other`);
92
- return parts.length > 0 ? `${stepName} (${parts.join(', ')})` : stepName;
93
- })
94
- .join('; ');
58
+ Package workspace (top-level commands — same digital-worker package):
59
+ download / validate / upload / deploy <roleKey> Manage role-assistant/ra-<roleKey>/
60
+
61
+ Examples:
62
+ $ aifabrix role-assistant test customer-support-bot
63
+ $ aifabrix role-assistant test customer-support-bot --suite 001
64
+ $ aifabrix role-assistant test customer-support-bot --list-cases
65
+ $ aifabrix role-assistant test customer-support-bot --env tst --json
66
+ $ aifabrix upload customer-support-bot --env dev
67
+ $ aifabrix test ra-customer-support-bot
68
+
69
+ Notes:
70
+ - Local packages live under role-assistant/ra-<roleKey>/ (bare <roleKey> in CLI args).
71
+ - test uses the public/running worker by default; pass --publish (or upload) to refresh from local files.
72
+ - See: aifabrix role-assistant test -h
73
+ `;
95
74
  }
96
75
 
97
76
  function roleAssistantHelpAfter() {
@@ -101,15 +80,19 @@ Examples:
101
80
  $ aifabrix role-assistant test customer-support-bot --case smoke
102
81
  $ aifabrix role-assistant test customer-support-bot --case tests/customer-address-change.yaml
103
82
  $ aifabrix role-assistant test customer-support-bot --subject-email ops@example.com
104
- $ aifabrix role-assistant test customer-support-bot --json
105
- $ aifabrix role-assistant test customer-support-bot --publish --force
83
+ $ aifabrix role-assistant test customer-support-bot --suite 001
84
+ $ aifabrix role-assistant test customer-support-bot --list-cases
85
+ $ aifabrix role-assistant test customer-support-bot --suite 001 --fail-fast --json
86
+ $ aifabrix test ra-customer-support-bot
106
87
 
107
88
  Notes:
89
+ - <roleKey> is bare (e.g. customer-support-bot); package folder is role-assistant/ra-<roleKey>/.
108
90
  - Uses the public/running Role Assistant on the dataplane by default (does not publish local datasources).
109
91
  - Use \`aifabrix upload <roleKey> --env …\` (or \`--publish\`) when you intentionally refresh the package.
110
92
  - Runs lifecycle checks, then prints a portal deep link for full step-by-step status.
111
93
  - Case files with input.message/input.content execute Work Hub plan→accept (or work.start when executionPath: intent).
112
94
  - answers[] and approvals[] are matched to runtime wait payloads (questionKey/requestKey/waitReason).
95
+ - --suite runs YAML under role-assistant/ra-<roleKey>/tests/<suite>/ (preferred), or tests/ra-<roleKey>/<roleKey>/<suite>/, or legacy tests/role-assistant/ra-<roleKey>/<suite>/ (override with --cases-root).
113
96
  - Use the portal URL for detailed human review while automation reads CLI/JSON output.
114
97
  `;
115
98
  }
@@ -146,96 +129,129 @@ function normalizeRoleAssistantTestOptions(options, cmd) {
146
129
  certificationProfile: options.certificationProfile || 'bronze',
147
130
  case: trimOptionalString(options.case),
148
131
  testsDir: trimOptionalString(options.testsDir),
132
+ suite: trimOptionalString(options.suite),
133
+ casesRoot: trimOptionalString(options.casesRoot),
134
+ listCases: optionOrFlag(options, 'listCases', cmd, '--list-cases'),
135
+ failFast: optionOrFlag(options, 'failFast', cmd, '--fail-fast'),
149
136
  run: true
150
137
  };
151
138
  }
152
139
 
153
- function buildRoleAssistantTestEnvelope(roleKey, verdict, portalUrl, result, opts, caseResult = null) {
154
- const report = result.report || {};
155
- const envelope = {
156
- roleKey,
157
- command: 'role-assistant test',
158
- verdict,
159
- portalUrl,
160
- openApiPublished: result.openApiPublished === true,
161
- certification: report.certification || null,
162
- recommendations: Array.isArray(report.recommendations) ? report.recommendations : [],
163
- warnings: Array.isArray(report.warnings) ? report.warnings : [],
164
- stepsRun: result.stepsRun || [],
165
- case: caseResult
166
- ? {
167
- caseId: caseResult.caseId,
168
- sourceFile: caseResult.sourceFile,
169
- passed: caseResult.passed,
170
- lifecycleState: caseResult.lifecycleState,
171
- assertions: caseResult.assertions,
172
- failedAssertions: caseResult.failedAssertions,
173
- warnings: Array.isArray(caseResult.warnings) ? caseResult.warnings : [],
174
- execution: caseResult.execution || null
175
- }
176
- : null
177
- };
178
- if (opts.verbose || opts.debug) {
179
- envelope.report = report;
140
+ function computeRoleAssistantExitCode(verdict, caseResult) {
141
+ if (Array.isArray(caseResult)) {
142
+ if (caseResult.some((row) => row && row.passed === false)) {
143
+ return 2;
144
+ }
145
+ return 0;
180
146
  }
181
- if (opts.debug) {
182
- envelope.debug = {
183
- observation: caseResult ? caseResult.observation : null,
184
- lifecycleResult: result
185
- };
147
+ if (caseResult) {
148
+ return caseResult.passed ? 0 : 2;
149
+ }
150
+ if (verdict === VERDICT.VERIFIED) {
151
+ return 0;
186
152
  }
187
- return envelope;
153
+ return exitCodeFromVerdict(verdict, { partial: verdict === VERDICT.NOT_VERIFIED });
188
154
  }
189
155
 
190
- function displayCaseResultSummary(caseResult) {
191
- if (!caseResult) {
192
- return;
156
+ function listRoleAssistantCases(roleKey, opts) {
157
+ const root = resolveCasesRoot(roleKey, { casesRoot: opts.casesRoot || undefined });
158
+ const rows = discoverSuiteCaseFiles(root, { suite: opts.suite || undefined });
159
+ const filtered = filterDiscoveredCases(rows, opts.case);
160
+ if (opts.json) {
161
+ logger.log(
162
+ JSON.stringify(
163
+ {
164
+ roleKey: bareRoleKey(roleKey),
165
+ command: 'role-assistant test',
166
+ mode: 'list-cases',
167
+ casesRoot: root,
168
+ cases: filtered.map((r) => ({
169
+ suite: r.suite,
170
+ file: r.file,
171
+ path: r.absPath,
172
+ caseIdHint: r.caseIdHint
173
+ }))
174
+ },
175
+ null,
176
+ 2
177
+ )
178
+ );
179
+ } else {
180
+ logger.log(sectionTitle('ROLE ASSISTANT CASES'));
181
+ logger.log(headerKeyValue('Cases root:', root));
182
+ for (const row of filtered) {
183
+ logger.log(`${row.suite}\t${row.file}\t${row.absPath}`);
184
+ }
185
+ logger.log(metadata(`total ${filtered.length}`));
193
186
  }
194
- logger.log(headerKeyValue('Case:', caseResult.caseId));
195
- logger.log(headerKeyValue('Case file:', caseResult.sourceFile));
196
- logger.log(headerKeyValue('Observed state:', caseResult.lifecycleState));
197
- if (caseResult.execution) {
198
- logger.log(headerKeyValue('Execution id:', caseResult.execution.processExecutionId || 'n/a'));
199
- logger.log(headerKeyValue('Final runtime status:', caseResult.execution.finalStatus || 'n/a'));
187
+ return 0;
188
+ }
189
+
190
+ async function resolveSuiteCaseResults(systemKey, opts, result, verdict, portalUrl) {
191
+ const cwd = process.cwd();
192
+ const root = resolveCasesRoot(systemKey, { casesRoot: opts.casesRoot || undefined, cwd });
193
+ const discovered = discoverSuiteCaseFiles(root, { suite: opts.suite || undefined });
194
+ const selected = filterDiscoveredCases(discovered, opts.case);
195
+ if (selected.length === 0) {
196
+ const { listCasesRootCandidates } = require('../role-assistant/test-suite');
197
+ const tried = opts.casesRoot
198
+ ? [root]
199
+ : listCasesRootCandidates(systemKey, cwd);
200
+ throw new Error(
201
+ `No role-assistant cases found under ${root}` +
202
+ (opts.suite ? ` suite=${opts.suite}` : '') +
203
+ `. Looked in: ${tried.join(', ')}`
204
+ );
200
205
  }
201
- if (caseResult.passed) {
202
- logger.log(formatSuccessLine('Case assertions passed.'));
203
- const warnings = Array.isArray(caseResult.warnings) ? caseResult.warnings : [];
204
- for (const row of warnings) {
205
- logger.log(formatWarningLine(row.message || String(row)));
206
+ const outcomes = [];
207
+ for (const row of selected) {
208
+ const caseOpts = { ...opts, case: row.absPath, testsDir: path.dirname(row.absPath) };
209
+ const one = await resolveRoleAssistantCaseResult(systemKey, caseOpts, result, verdict, portalUrl);
210
+ outcomes.push(one);
211
+ if (opts.failFast && one && one.passed === false) {
212
+ break;
206
213
  }
207
- return;
208
- }
209
- logger.log(formatBlockingError('Case assertions failed.'));
210
- for (const row of caseResult.failedAssertions) {
211
- logger.log(formatWarningLine(row.message));
212
214
  }
215
+ return outcomes;
213
216
  }
214
217
 
215
- function displayRoleAssistantTestSummary(roleKey, verdict, portalUrl, result, caseResult = null) {
216
- logger.log('');
217
- logger.log(sectionTitle(`${String(roleKey).toUpperCase()} ROLE ASSISTANT TEST`));
218
- logger.log('');
219
- logger.log(headerKeyValue('Role key:', roleKey));
220
- if (portalUrl) {
221
- logger.log(headerKeyValue('Portal:', portalUrl));
218
+ async function runRoleAssistantTestAction(roleKey, options, cmd) {
219
+ const opts = normalizeRoleAssistantTestOptions(options, cmd);
220
+ const systemKey = roleAssistantSystemKey(roleKey);
221
+ const displayKey = bareRoleKey(roleKey);
222
+
223
+ if (opts.listCases) {
224
+ return listRoleAssistantCases(systemKey, opts);
225
+ }
226
+
227
+ const result = await runLifecycleForExternalSystem(systemKey, opts);
228
+ const verdict = lifecycleVerdictFromReport(result.report || {});
229
+ const portalUrl = buildExternalSystemUiUrl(result.dataplaneUiBaseUrl, systemKey);
230
+
231
+ let caseResult = null;
232
+ if (opts.suite) {
233
+ caseResult = await resolveSuiteCaseResults(systemKey, opts, result, verdict, portalUrl);
222
234
  } else {
223
- logger.log(formatWarningLine('Portal link unavailable. Configure dataplane public URL and retry.'));
235
+ caseResult = await resolveRoleAssistantCaseResult(systemKey, opts, result, verdict, portalUrl);
224
236
  }
225
- const verdictMessage =
226
- verdict === VERDICT.VERIFIED
227
- ? 'Lifecycle checks passed. Open portal for full execution steps and logs.'
228
- : 'Lifecycle checks are not fully verified. Open portal to inspect blocking steps.';
229
- if (verdict === VERDICT.VERIFIED) {
230
- logger.log(formatSuccessParagraph(verdictMessage));
237
+
238
+ if (opts.json) {
239
+ logger.log(
240
+ JSON.stringify(
241
+ buildRoleAssistantTestEnvelope(displayKey, verdict, portalUrl, result, opts, caseResult),
242
+ null,
243
+ 2
244
+ )
245
+ );
231
246
  } else {
232
- logger.log(formatWarningLine(verdictMessage));
247
+ displayRoleAssistantTestSummary(displayKey, verdict, portalUrl, result, caseResult);
233
248
  }
234
- const steps = Array.isArray(result.stepsRun) ? result.stepsRun : [];
235
- if (steps.length > 0) {
236
- logger.log(metadata(`Steps run: ${formatStepsRunSummary(steps)}`));
249
+
250
+ const exitCode = computeRoleAssistantExitCode(verdict, caseResult);
251
+ if (exitCode !== 0) {
252
+ process.exit(exitCode);
237
253
  }
238
- displayCaseResultSummary(caseResult);
254
+ return exitCode;
239
255
  }
240
256
 
241
257
  async function resolveRoleAssistantCaseResult(roleKey, opts, result, verdict, portalUrl) {
@@ -272,44 +288,6 @@ async function resolveRoleAssistantCaseResult(roleKey, opts, result, verdict, po
272
288
  return evaluation;
273
289
  }
274
290
 
275
- function computeRoleAssistantExitCode(verdict, caseResult) {
276
- if (caseResult) {
277
- return caseResult.passed ? 0 : 2;
278
- }
279
- if (verdict === VERDICT.VERIFIED) {
280
- return 0;
281
- }
282
- return exitCodeFromVerdict(verdict, { partial: verdict === VERDICT.NOT_VERIFIED });
283
- }
284
-
285
- async function runRoleAssistantTestAction(roleKey, options, cmd) {
286
- const opts = normalizeRoleAssistantTestOptions(options, cmd);
287
- // Dataplane ExternalSystem key is always ra-{roleKey}; bare roleKey is display/settings only.
288
- const systemKey = roleAssistantSystemKey(roleKey);
289
- const displayKey = bareRoleKey(roleKey);
290
- const result = await runLifecycleForExternalSystem(systemKey, opts);
291
- const verdict = lifecycleVerdictFromReport(result.report || {});
292
- const portalUrl = buildExternalSystemUiUrl(result.dataplaneUiBaseUrl, systemKey);
293
- const caseResult = await resolveRoleAssistantCaseResult(systemKey, opts, result, verdict, portalUrl);
294
-
295
- if (opts.json) {
296
- logger.log(
297
- JSON.stringify(
298
- buildRoleAssistantTestEnvelope(displayKey, verdict, portalUrl, result, opts, caseResult),
299
- null,
300
- 2
301
- )
302
- );
303
- } else {
304
- displayRoleAssistantTestSummary(displayKey, verdict, portalUrl, result, caseResult);
305
- }
306
-
307
- const exitCode = computeRoleAssistantExitCode(verdict, caseResult);
308
- if (exitCode !== 0) {
309
- process.exit(exitCode);
310
- }
311
- }
312
-
313
291
  async function roleAssistantTestHandler(roleKey, options, cmd) {
314
292
  try {
315
293
  await runRoleAssistantTestAction(roleKey, options, cmd);
@@ -322,14 +300,29 @@ async function roleAssistantTestHandler(roleKey, options, cmd) {
322
300
  function setupRoleAssistantCommands(program) {
323
301
  const roleAssistant = program
324
302
  .command('role-assistant')
325
- .description('Role Assistant lifecycle and portal workflows');
303
+ .description(
304
+ 'Role Assistant packages: lifecycle checks, YAML cases/suites, and portal deep links'
305
+ )
306
+ .addHelpText('after', roleAssistantGroupHelpAfter());
326
307
 
327
308
  roleAssistant
328
309
  .command('test <roleKey>')
329
- .description('Run role-assistant lifecycle checks and print portal deep link')
310
+ .description(
311
+ 'Lifecycle checks + optional cases/suites; prints portal deep link (bare <roleKey>)'
312
+ )
330
313
  .option('-e, --env <env>', 'Environment: dev, tst, or pro', 'dev')
331
314
  .option('--case <caseIdOrPath>', 'Run and evaluate one role-assistant test case')
332
315
  .option('--tests-dir <dir>', 'Override role-assistant test case directory')
316
+ .option(
317
+ '--suite <suiteId>',
318
+ 'Run all YAML under role-assistant/ra-<roleKey>/tests/<suiteId>/ (or tests/ra-<roleKey>/<roleKey>/<suiteId>/)'
319
+ )
320
+ .option(
321
+ '--cases-root <dir>',
322
+ 'Override cases root (default: package tests/, then tests/ra-<roleKey>/<roleKey>/)'
323
+ )
324
+ .option('--list-cases', 'List discoverable case files and exit')
325
+ .option('--fail-fast', 'Stop suite run after the first failing case')
333
326
  .option('-v, --verbose', 'Include full lifecycle report payload in --json output')
334
327
  .option('-d, --debug', 'Write debug logs under role-assistant/<roleKey>/logs/')
335
328
  .option(
@@ -367,5 +360,9 @@ module.exports = {
367
360
  buildRoleAssistantTestEnvelope,
368
361
  computeRoleAssistantExitCode,
369
362
  resolveRoleAssistantCaseResult,
370
- formatStepsRunSummary
363
+ resolveSuiteCaseResults,
364
+ listRoleAssistantCases,
365
+ formatStepsRunSummary,
366
+ roleAssistantGroupHelpAfter,
367
+ roleAssistantHelpAfter
371
368
  };
@@ -6,7 +6,7 @@
6
6
  * @version 2.0.0
7
7
  */
8
8
 
9
- const { capabilitiesIncludes } = require('./capability-manifest');
9
+ const { capabilitiesIncludes, capabilityKeyFromEntry } = require('./capability-manifest');
10
10
 
11
11
  /**
12
12
  * @param {object|undefined} operations
@@ -44,6 +44,16 @@ function resolveSingleOpsKey(operations, logicalKey, label) {
44
44
  );
45
45
  }
46
46
 
47
+ /**
48
+ * @param {unknown} entry
49
+ * @param {string} userKey
50
+ * @returns {boolean}
51
+ */
52
+ function capabilityEntryMatches(entry, userKey) {
53
+ const key = capabilityKeyFromEntry(entry);
54
+ return Boolean(key) && key.toLowerCase() === String(userKey).toLowerCase();
55
+ }
56
+
47
57
  /**
48
58
  * For --from on copy: when capabilities[] is non-empty, the name must appear there (case-insensitive).
49
59
  *
@@ -56,14 +66,14 @@ function resolveSourceCapabilityForCopy(doc, userKey) {
56
66
  const u = String(userKey).trim();
57
67
  const caps = doc.capabilities;
58
68
  if (Array.isArray(caps) && caps.length > 0) {
59
- const hit = caps.find((c) => String(c).toLowerCase() === u.toLowerCase());
69
+ const hit = caps.find((c) => capabilityEntryMatches(c, u));
60
70
  if (hit === undefined) {
61
71
  throw new Error(
62
72
  `Source capability "${userKey}" not found in capabilities[]. ` +
63
73
  'Use a name that appears in capabilities[] (case-insensitive match is allowed).'
64
74
  );
65
75
  }
66
- return String(hit);
76
+ return capabilityKeyFromEntry(hit) || u;
67
77
  }
68
78
  return u;
69
79
  }
@@ -79,9 +89,9 @@ function resolveLogicalNameForRemove(doc, userKey) {
79
89
  const u = String(userKey).trim();
80
90
  const caps = doc.capabilities;
81
91
  if (Array.isArray(caps) && caps.length > 0) {
82
- const hit = caps.find((c) => String(c).toLowerCase() === u.toLowerCase());
92
+ const hit = caps.find((c) => capabilityEntryMatches(c, u));
83
93
  if (hit !== undefined) {
84
- return String(hit);
94
+ return capabilityKeyFromEntry(hit) || u;
85
95
  }
86
96
  }
87
97
  return u;