@salesforce/afv-skills 1.13.0 → 1.15.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 (359) hide show
  1. package/package.json +3 -3
  2. package/skills/applying-slds/SKILL.md +322 -0
  3. package/skills/applying-slds/checklists.md +83 -0
  4. package/skills/applying-slds/examples.md +283 -0
  5. package/skills/applying-slds/guidance/README.md +83 -0
  6. package/skills/applying-slds/guidance/blueprints-index.md +213 -0
  7. package/skills/applying-slds/guidance/icons-guidance.md +186 -0
  8. package/skills/applying-slds/guidance/overviews/borders.md +236 -0
  9. package/skills/applying-slds/guidance/overviews/color.md +266 -0
  10. package/skills/applying-slds/guidance/overviews/display-density.md +366 -0
  11. package/skills/applying-slds/guidance/overviews/icons.md +240 -0
  12. package/skills/applying-slds/guidance/overviews/illustrations.md +235 -0
  13. package/skills/applying-slds/guidance/overviews/shadows.md +176 -0
  14. package/skills/applying-slds/guidance/overviews/spacing.md +216 -0
  15. package/skills/applying-slds/guidance/overviews/typography.md +323 -0
  16. package/skills/applying-slds/guidance/overviews/utilities.md +542 -0
  17. package/skills/applying-slds/guidance/slds-development-guide.md +288 -0
  18. package/skills/applying-slds/guidance/styling-hooks/borders.md +202 -0
  19. package/skills/applying-slds/guidance/styling-hooks/color/expressive-palette-hooks.md +153 -0
  20. package/skills/applying-slds/guidance/styling-hooks/color/index.md +171 -0
  21. package/skills/applying-slds/guidance/styling-hooks/color/semantic/accent-hooks.md +204 -0
  22. package/skills/applying-slds/guidance/styling-hooks/color/semantic/feedback-hooks.md +768 -0
  23. package/skills/applying-slds/guidance/styling-hooks/color/semantic/surface-hooks.md +337 -0
  24. package/skills/applying-slds/guidance/styling-hooks/color/system-hooks.md +132 -0
  25. package/skills/applying-slds/guidance/styling-hooks/index.md +327 -0
  26. package/skills/applying-slds/guidance/styling-hooks/shadows.md +238 -0
  27. package/skills/applying-slds/guidance/styling-hooks/spacing.md +254 -0
  28. package/skills/applying-slds/guidance/styling-hooks/typography.md +448 -0
  29. package/skills/applying-slds/guidance/utilities/alignment.md +119 -0
  30. package/skills/applying-slds/guidance/utilities/borders.md +131 -0
  31. package/skills/applying-slds/guidance/utilities/box.md +125 -0
  32. package/skills/applying-slds/guidance/utilities/color.md +165 -0
  33. package/skills/applying-slds/guidance/utilities/dark-mode.md +111 -0
  34. package/skills/applying-slds/guidance/utilities/description-list.md +168 -0
  35. package/skills/applying-slds/guidance/utilities/floats.md +117 -0
  36. package/skills/applying-slds/guidance/utilities/grid.md +264 -0
  37. package/skills/applying-slds/guidance/utilities/horizontal-list.md +110 -0
  38. package/skills/applying-slds/guidance/utilities/hyphenation.md +84 -0
  39. package/skills/applying-slds/guidance/utilities/index.md +205 -0
  40. package/skills/applying-slds/guidance/utilities/interactions.md +89 -0
  41. package/skills/applying-slds/guidance/utilities/layout.md +109 -0
  42. package/skills/applying-slds/guidance/utilities/line-clamp.md +131 -0
  43. package/skills/applying-slds/guidance/utilities/margin.md +155 -0
  44. package/skills/applying-slds/guidance/utilities/media-object.md +161 -0
  45. package/skills/applying-slds/guidance/utilities/name-value-list.md +152 -0
  46. package/skills/applying-slds/guidance/utilities/padding.md +155 -0
  47. package/skills/applying-slds/guidance/utilities/position.md +177 -0
  48. package/skills/applying-slds/guidance/utilities/print.md +114 -0
  49. package/skills/applying-slds/guidance/utilities/scrollable.md +126 -0
  50. package/skills/applying-slds/guidance/utilities/sizing.md +190 -0
  51. package/skills/applying-slds/guidance/utilities/themes.md +121 -0
  52. package/skills/applying-slds/guidance/utilities/truncate.md +127 -0
  53. package/skills/applying-slds/guidance/utilities/typography.md +166 -0
  54. package/skills/applying-slds/guidance/utilities/vertical-list.md +166 -0
  55. package/skills/applying-slds/guidance/utilities/visibility.md +228 -0
  56. package/skills/applying-slds/metadata/README.md +84 -0
  57. package/skills/applying-slds/metadata/blueprints/components/accordion.yaml +304 -0
  58. package/skills/applying-slds/metadata/blueprints/components/activity-timeline.yaml +92 -0
  59. package/skills/applying-slds/metadata/blueprints/components/alert.yaml +103 -0
  60. package/skills/applying-slds/metadata/blueprints/components/app-launcher.yaml +94 -0
  61. package/skills/applying-slds/metadata/blueprints/components/avatar-group.yaml +81 -0
  62. package/skills/applying-slds/metadata/blueprints/components/avatar.yaml +97 -0
  63. package/skills/applying-slds/metadata/blueprints/components/badges.yaml +102 -0
  64. package/skills/applying-slds/metadata/blueprints/components/brand-band.yaml +198 -0
  65. package/skills/applying-slds/metadata/blueprints/components/breadcrumbs.yaml +95 -0
  66. package/skills/applying-slds/metadata/blueprints/components/builder-header.yaml +192 -0
  67. package/skills/applying-slds/metadata/blueprints/components/button-groups.yaml +82 -0
  68. package/skills/applying-slds/metadata/blueprints/components/button-icons.yaml +295 -0
  69. package/skills/applying-slds/metadata/blueprints/components/buttons.yaml +230 -0
  70. package/skills/applying-slds/metadata/blueprints/components/cards.yaml +124 -0
  71. package/skills/applying-slds/metadata/blueprints/components/carousel.yaml +140 -0
  72. package/skills/applying-slds/metadata/blueprints/components/chat.yaml +179 -0
  73. package/skills/applying-slds/metadata/blueprints/components/checkbox-button-group.yaml +192 -0
  74. package/skills/applying-slds/metadata/blueprints/components/checkbox-button.yaml +204 -0
  75. package/skills/applying-slds/metadata/blueprints/components/checkbox-toggle.yaml +177 -0
  76. package/skills/applying-slds/metadata/blueprints/components/checkbox.yaml +108 -0
  77. package/skills/applying-slds/metadata/blueprints/components/color-picker.yaml +172 -0
  78. package/skills/applying-slds/metadata/blueprints/components/combobox.yaml +136 -0
  79. package/skills/applying-slds/metadata/blueprints/components/counter.yaml +147 -0
  80. package/skills/applying-slds/metadata/blueprints/components/data-tables.yaml +157 -0
  81. package/skills/applying-slds/metadata/blueprints/components/datepickers.yaml +130 -0
  82. package/skills/applying-slds/metadata/blueprints/components/datetime-picker.yaml +155 -0
  83. package/skills/applying-slds/metadata/blueprints/components/docked-composer.yaml +201 -0
  84. package/skills/applying-slds/metadata/blueprints/components/docked-form-footer.yaml +161 -0
  85. package/skills/applying-slds/metadata/blueprints/components/docked-utility-bar.yaml +175 -0
  86. package/skills/applying-slds/metadata/blueprints/components/drop-zone.yaml +115 -0
  87. package/skills/applying-slds/metadata/blueprints/components/dueling-picklist.yaml +196 -0
  88. package/skills/applying-slds/metadata/blueprints/components/dynamic-icons.yaml +128 -0
  89. package/skills/applying-slds/metadata/blueprints/components/dynamic-menu.yaml +141 -0
  90. package/skills/applying-slds/metadata/blueprints/components/expandable-section.yaml +115 -0
  91. package/skills/applying-slds/metadata/blueprints/components/expression.yaml +143 -0
  92. package/skills/applying-slds/metadata/blueprints/components/feeds.yaml +125 -0
  93. package/skills/applying-slds/metadata/blueprints/components/file-selector.yaml +154 -0
  94. package/skills/applying-slds/metadata/blueprints/components/files.yaml +119 -0
  95. package/skills/applying-slds/metadata/blueprints/components/form-element.yaml +145 -0
  96. package/skills/applying-slds/metadata/blueprints/components/global-header.yaml +120 -0
  97. package/skills/applying-slds/metadata/blueprints/components/global-navigation.yaml +100 -0
  98. package/skills/applying-slds/metadata/blueprints/components/icons.yaml +138 -0
  99. package/skills/applying-slds/metadata/blueprints/components/illustration.yaml +205 -0
  100. package/skills/applying-slds/metadata/blueprints/components/input.yaml +151 -0
  101. package/skills/applying-slds/metadata/blueprints/components/list-builder.yaml +127 -0
  102. package/skills/applying-slds/metadata/blueprints/components/lookups.yaml +132 -0
  103. package/skills/applying-slds/metadata/blueprints/components/map.yaml +118 -0
  104. package/skills/applying-slds/metadata/blueprints/components/menus.yaml +134 -0
  105. package/skills/applying-slds/metadata/blueprints/components/modals.yaml +152 -0
  106. package/skills/applying-slds/metadata/blueprints/components/notifications.yaml +88 -0
  107. package/skills/applying-slds/metadata/blueprints/components/page-headers.yaml +135 -0
  108. package/skills/applying-slds/metadata/blueprints/components/panels.yaml +149 -0
  109. package/skills/applying-slds/metadata/blueprints/components/path.yaml +154 -0
  110. package/skills/applying-slds/metadata/blueprints/components/picklist.yaml +125 -0
  111. package/skills/applying-slds/metadata/blueprints/components/pills.yaml +154 -0
  112. package/skills/applying-slds/metadata/blueprints/components/popovers.yaml +120 -0
  113. package/skills/applying-slds/metadata/blueprints/components/progress-bar.yaml +110 -0
  114. package/skills/applying-slds/metadata/blueprints/components/progress-indicator.yaml +133 -0
  115. package/skills/applying-slds/metadata/blueprints/components/progress-ring.yaml +102 -0
  116. package/skills/applying-slds/metadata/blueprints/components/prompt.yaml +126 -0
  117. package/skills/applying-slds/metadata/blueprints/components/publishers.yaml +178 -0
  118. package/skills/applying-slds/metadata/blueprints/components/radio-button-group.yaml +172 -0
  119. package/skills/applying-slds/metadata/blueprints/components/radio-group.yaml +112 -0
  120. package/skills/applying-slds/metadata/blueprints/components/rich-text-editor.yaml +135 -0
  121. package/skills/applying-slds/metadata/blueprints/components/scoped-notifications.yaml +188 -0
  122. package/skills/applying-slds/metadata/blueprints/components/scoped-tabs.yaml +97 -0
  123. package/skills/applying-slds/metadata/blueprints/components/select.yaml +127 -0
  124. package/skills/applying-slds/metadata/blueprints/components/setup-assistant.yaml +152 -0
  125. package/skills/applying-slds/metadata/blueprints/components/slider.yaml +111 -0
  126. package/skills/applying-slds/metadata/blueprints/components/spinners.yaml +135 -0
  127. package/skills/applying-slds/metadata/blueprints/components/split-view.yaml +112 -0
  128. package/skills/applying-slds/metadata/blueprints/components/summary-detail.yaml +103 -0
  129. package/skills/applying-slds/metadata/blueprints/components/tabs.yaml +138 -0
  130. package/skills/applying-slds/metadata/blueprints/components/textarea.yaml +116 -0
  131. package/skills/applying-slds/metadata/blueprints/components/tiles.yaml +108 -0
  132. package/skills/applying-slds/metadata/blueprints/components/timepicker.yaml +111 -0
  133. package/skills/applying-slds/metadata/blueprints/components/toast.yaml +154 -0
  134. package/skills/applying-slds/metadata/blueprints/components/tooltips.yaml +107 -0
  135. package/skills/applying-slds/metadata/blueprints/components/tree-grid.yaml +116 -0
  136. package/skills/applying-slds/metadata/blueprints/components/trees.yaml +116 -0
  137. package/skills/applying-slds/metadata/blueprints/components/trial-bar.yaml +112 -0
  138. package/skills/applying-slds/metadata/blueprints/components/vertical-navigation.yaml +130 -0
  139. package/skills/applying-slds/metadata/blueprints/components/vertical-tabs.yaml +140 -0
  140. package/skills/applying-slds/metadata/blueprints/components/visual-picker.yaml +150 -0
  141. package/skills/applying-slds/metadata/blueprints/components/welcome-mat.yaml +136 -0
  142. package/skills/applying-slds/metadata/hooks-index.json +6272 -0
  143. package/skills/applying-slds/metadata/icon-metadata.json +38466 -0
  144. package/skills/applying-slds/metadata/utilities-index.json +21912 -0
  145. package/skills/applying-slds/references/component-selection.md +112 -0
  146. package/skills/applying-slds/references/icons-decision-guide.md +124 -0
  147. package/skills/applying-slds/references/styling-decision-guide.md +228 -0
  148. package/skills/applying-slds/references/utilities-quick-ref.md +125 -0
  149. package/skills/applying-slds/scripts/search-blueprints.cjs +117 -0
  150. package/skills/applying-slds/scripts/search-hooks.cjs +139 -0
  151. package/skills/applying-slds/scripts/search-icons.cjs +174 -0
  152. package/skills/applying-slds/scripts/search-utilities.cjs +161 -0
  153. package/skills/building-ui-bundle-app/SKILL.md +33 -8
  154. package/skills/generating-custom-application/SKILL.md +1 -1
  155. package/skills/generating-custom-lightning-type/SKILL.md +17 -39
  156. package/skills/generating-custom-lightning-type/assets/primitive-types-and-constraints.md +41 -0
  157. package/skills/generating-custom-lightning-type/references/widget-rendition.md +124 -0
  158. package/skills/generating-ui-bundle-custom-app/SKILL.md +93 -0
  159. package/skills/generating-ui-bundle-custom-app/docs/configure-metadata-custom-application.md +70 -0
  160. package/skills/generating-ui-bundle-metadata/SKILL.md +39 -1
  161. package/skills/investigating-agentforce-architecture/README.md +156 -0
  162. package/skills/investigating-agentforce-architecture/SKILL.md +230 -0
  163. package/skills/investigating-agentforce-architecture/assets/cli/describe_sobject.yaml +16 -0
  164. package/skills/investigating-agentforce-architecture/assets/cli/describe_tooling_sobject.yaml +17 -0
  165. package/skills/investigating-agentforce-architecture/assets/cli/list_metadata_genaiprompttemplate.yaml +17 -0
  166. package/skills/investigating-agentforce-architecture/assets/cli/org_display.yaml +15 -0
  167. package/skills/investigating-agentforce-architecture/assets/cli/retrieve_genai_plugin.yaml +18 -0
  168. package/skills/investigating-agentforce-architecture/assets/cli/show_access_token.yaml +27 -0
  169. package/skills/investigating-agentforce-architecture/assets/mermaid/action_tree.mmd +20 -0
  170. package/skills/investigating-agentforce-architecture/assets/mermaid/data_flow.mmd +19 -0
  171. package/skills/investigating-agentforce-architecture/assets/mermaid/dependency_graph.mmd +19 -0
  172. package/skills/investigating-agentforce-architecture/assets/mermaid/invocation_sequence.mmd +20 -0
  173. package/skills/investigating-agentforce-architecture/assets/mermaid/planner_state.mmd +18 -0
  174. package/skills/investigating-agentforce-architecture/assets/soql/apex_class_bodies_by_ids.soql +3 -0
  175. package/skills/investigating-agentforce-architecture/assets/soql/apex_class_bodies_by_names.soql +3 -0
  176. package/skills/investigating-agentforce-architecture/assets/soql/bot_definition_details.soql +3 -0
  177. package/skills/investigating-agentforce-architecture/assets/soql/bot_version_lookup.soql +4 -0
  178. package/skills/investigating-agentforce-architecture/assets/soql/flow_definition_by_ids.soql +3 -0
  179. package/skills/investigating-agentforce-architecture/assets/soql/flow_definition_ids_by_names.soql +3 -0
  180. package/skills/investigating-agentforce-architecture/assets/soql/flow_definition_view_by_durable_ids.soql +4 -0
  181. package/skills/investigating-agentforce-architecture/assets/soql/flow_metadata_by_id.soql +3 -0
  182. package/skills/investigating-agentforce-architecture/assets/soql/functions_by_plugins.soql +5 -0
  183. package/skills/investigating-agentforce-architecture/assets/soql/planner_attrs_by_parent_ids.soql +3 -0
  184. package/skills/investigating-agentforce-architecture/assets/soql/planner_bundle_functions.soql +3 -0
  185. package/skills/investigating-agentforce-architecture/assets/soql/planner_definition_by_agent_chain.soql +3 -0
  186. package/skills/investigating-agentforce-architecture/assets/soql/plugin_functions_by_plugin_ids.soql +3 -0
  187. package/skills/investigating-agentforce-architecture/assets/soql/plugin_instructions_by_plugin_ids.soql +3 -0
  188. package/skills/investigating-agentforce-architecture/assets/soql/plugins_by_planner.soql +4 -0
  189. package/skills/investigating-agentforce-architecture/references/architecture_sections.md +243 -0
  190. package/skills/investigating-agentforce-architecture/references/contract.json +244 -0
  191. package/skills/investigating-agentforce-architecture/references/soql_fields.md +512 -0
  192. package/skills/investigating-agentforce-architecture/scripts/_shared/__init__.py +1 -0
  193. package/skills/investigating-agentforce-architecture/scripts/_shared/fs_guard.py +329 -0
  194. package/skills/investigating-agentforce-architecture/scripts/_shared/paths.py +110 -0
  195. package/skills/investigating-agentforce-architecture/scripts/_shared/runtime.py +59 -0
  196. package/skills/investigating-agentforce-architecture/scripts/_shared/sql.py +10 -0
  197. package/skills/investigating-agentforce-architecture/scripts/cache_check.py +234 -0
  198. package/skills/investigating-agentforce-architecture/scripts/config.py +131 -0
  199. package/skills/investigating-agentforce-architecture/scripts/fetch_soql.py +689 -0
  200. package/skills/investigating-agentforce-architecture/scripts/finalize.py +295 -0
  201. package/skills/investigating-agentforce-architecture/scripts/main.py +2835 -0
  202. package/skills/investigating-agentforce-architecture/scripts/metadata_listing.py +265 -0
  203. package/skills/investigating-agentforce-architecture/scripts/parallel_retrieve.py +69 -0
  204. package/skills/investigating-agentforce-architecture/scripts/parse_bundle.py +215 -0
  205. package/skills/investigating-agentforce-architecture/scripts/parse_wave.py +845 -0
  206. package/skills/investigating-agentforce-architecture/scripts/probe_channels.py +302 -0
  207. package/skills/investigating-agentforce-architecture/scripts/render_architecture.py +1043 -0
  208. package/skills/investigating-agentforce-architecture/scripts/resolve_bot.py +255 -0
  209. package/skills/investigating-agentforce-architecture/scripts/resolve_invocation_target.py +130 -0
  210. package/skills/investigating-agentforce-architecture/scripts/rest_client.py +763 -0
  211. package/skills/investigating-agentforce-architecture/scripts/retrieve_planner.py +13 -0
  212. package/skills/investigating-agentforce-architecture/scripts/sf_cli.py +242 -0
  213. package/skills/investigating-agentforce-architecture/scripts/soql_loader.py +253 -0
  214. package/skills/investigating-agentforce-architecture/scripts/summarize_tree.py +143 -0
  215. package/skills/investigating-agentforce-architecture/scripts/tests/__init__.py +0 -0
  216. package/skills/investigating-agentforce-architecture/scripts/tests/_bootstrap.py +23 -0
  217. package/skills/investigating-agentforce-architecture/scripts/tests/fixtures/__init__.py +0 -0
  218. package/skills/investigating-agentforce-architecture/scripts/tests/fixtures/genai_payloads.py +400 -0
  219. package/skills/investigating-agentforce-architecture/scripts/tests/test_cache_check.py +307 -0
  220. package/skills/investigating-agentforce-architecture/scripts/tests/test_cache_check_main.py +283 -0
  221. package/skills/investigating-agentforce-architecture/scripts/tests/test_config.py +115 -0
  222. package/skills/investigating-agentforce-architecture/scripts/tests/test_end_to_end_fixture.py +651 -0
  223. package/skills/investigating-agentforce-architecture/scripts/tests/test_finalize.py +278 -0
  224. package/skills/investigating-agentforce-architecture/scripts/tests/test_flow_children_inflation.py +582 -0
  225. package/skills/investigating-agentforce-architecture/scripts/tests/test_fs_guard.py +113 -0
  226. package/skills/investigating-agentforce-architecture/scripts/tests/test_iterative_wave_b.py +478 -0
  227. package/skills/investigating-agentforce-architecture/scripts/tests/test_main_pipeline.py +3359 -0
  228. package/skills/investigating-agentforce-architecture/scripts/tests/test_parallel_retrieve.py +131 -0
  229. package/skills/investigating-agentforce-architecture/scripts/tests/test_parse_bundle.py +400 -0
  230. package/skills/investigating-agentforce-architecture/scripts/tests/test_parse_wave.py +644 -0
  231. package/skills/investigating-agentforce-architecture/scripts/tests/test_parse_wave_classifiers.py +224 -0
  232. package/skills/investigating-agentforce-architecture/scripts/tests/test_parse_wave_helpers.py +380 -0
  233. package/skills/investigating-agentforce-architecture/scripts/tests/test_parse_wave_main.py +397 -0
  234. package/skills/investigating-agentforce-architecture/scripts/tests/test_per_branch_visited.py +244 -0
  235. package/skills/investigating-agentforce-architecture/scripts/tests/test_probe_channels.py +359 -0
  236. package/skills/investigating-agentforce-architecture/scripts/tests/test_probe_cli_recipes.py +185 -0
  237. package/skills/investigating-agentforce-architecture/scripts/tests/test_render_architecture.py +810 -0
  238. package/skills/investigating-agentforce-architecture/scripts/tests/test_resolve_bot.py +203 -0
  239. package/skills/investigating-agentforce-architecture/scripts/tests/test_resolve_creds.py +157 -0
  240. package/skills/investigating-agentforce-architecture/scripts/tests/test_resolve_invocation_target.py +145 -0
  241. package/skills/investigating-agentforce-architecture/scripts/tests/test_rest_client.py +1253 -0
  242. package/skills/investigating-agentforce-architecture/scripts/tests/test_runtime_override.py +100 -0
  243. package/skills/investigating-agentforce-architecture/scripts/tests/test_sf_cli.py +261 -0
  244. package/skills/investigating-agentforce-architecture/scripts/tests/test_signature_stamping.py +466 -0
  245. package/skills/investigating-agentforce-architecture/scripts/tests/test_soql_loader.py +501 -0
  246. package/skills/investigating-agentforce-architecture/scripts/tests/test_summarize_tree.py +241 -0
  247. package/skills/investigating-agentforce-architecture/scripts/tests/test_write_emit_ctx.py +480 -0
  248. package/skills/investigating-agentforce-architecture/tools/emit_env.py +157 -0
  249. package/skills/investigating-agentforce-architecture/tools/emit_result.py +262 -0
  250. package/skills/investigating-agentforce-architecture/tools/sanitize.py +33 -0
  251. package/skills/investigating-agentforce-architecture/tools/write_emit_ctx.py +332 -0
  252. package/skills/investigating-agentforce-d360/README.md +123 -0
  253. package/skills/investigating-agentforce-d360/SKILL.md +163 -0
  254. package/skills/investigating-agentforce-d360/assets/dc/app_generation.sql +51 -0
  255. package/skills/investigating-agentforce-d360/assets/dc/content_category.sql +44 -0
  256. package/skills/investigating-agentforce-d360/assets/dc/content_quality.sql +41 -0
  257. package/skills/investigating-agentforce-d360/assets/dc/discover_sessions.sql +36 -0
  258. package/skills/investigating-agentforce-d360/assets/dc/feedback.sql +47 -0
  259. package/skills/investigating-agentforce-d360/assets/dc/feedback_details.sql +38 -0
  260. package/skills/investigating-agentforce-d360/assets/dc/gateway_records.sql +45 -0
  261. package/skills/investigating-agentforce-d360/assets/dc/gateway_request_llm.sql +50 -0
  262. package/skills/investigating-agentforce-d360/assets/dc/gateway_request_metadata.sql +44 -0
  263. package/skills/investigating-agentforce-d360/assets/dc/gateway_request_tags.sql +42 -0
  264. package/skills/investigating-agentforce-d360/assets/dc/gateway_requests.sql +89 -0
  265. package/skills/investigating-agentforce-d360/assets/dc/gateway_responses.sql +43 -0
  266. package/skills/investigating-agentforce-d360/assets/dc/generations.sql +52 -0
  267. package/skills/investigating-agentforce-d360/assets/dc/interactions.sql +53 -0
  268. package/skills/investigating-agentforce-d360/assets/dc/messages.sql +53 -0
  269. package/skills/investigating-agentforce-d360/assets/dc/messaging_session.sql +37 -0
  270. package/skills/investigating-agentforce-d360/assets/dc/moment_interactions.sql +34 -0
  271. package/skills/investigating-agentforce-d360/assets/dc/moments.sql +39 -0
  272. package/skills/investigating-agentforce-d360/assets/dc/participants.sql +48 -0
  273. package/skills/investigating-agentforce-d360/assets/dc/sessions.sql +78 -0
  274. package/skills/investigating-agentforce-d360/assets/dc/steps.sql +64 -0
  275. package/skills/investigating-agentforce-d360/assets/dc/tag_associations.sql +46 -0
  276. package/skills/investigating-agentforce-d360/assets/dc/tag_definition_associations.sql +37 -0
  277. package/skills/investigating-agentforce-d360/assets/dc/tag_definitions.sql +50 -0
  278. package/skills/investigating-agentforce-d360/assets/dc/tags.sql +37 -0
  279. package/skills/investigating-agentforce-d360/assets/dc/telemetry_spans.sql +55 -0
  280. package/skills/investigating-agentforce-d360/references/artifacts.md +50 -0
  281. package/skills/investigating-agentforce-d360/references/dc_dmo_fields.md +823 -0
  282. package/skills/investigating-agentforce-d360/references/dc_pipeline_contract.md +608 -0
  283. package/skills/investigating-agentforce-d360/scripts/_shared/__init__.py +2 -0
  284. package/skills/investigating-agentforce-d360/scripts/_shared/cli_override.py +98 -0
  285. package/skills/investigating-agentforce-d360/scripts/_shared/fs_guard.py +334 -0
  286. package/skills/investigating-agentforce-d360/scripts/_shared/paths.py +155 -0
  287. package/skills/investigating-agentforce-d360/scripts/_shared/runtime.py +59 -0
  288. package/skills/investigating-agentforce-d360/scripts/_shared/sql.py +14 -0
  289. package/skills/investigating-agentforce-d360/scripts/assemble_dc.py +1624 -0
  290. package/skills/investigating-agentforce-d360/scripts/config.py +45 -0
  291. package/skills/investigating-agentforce-d360/scripts/dc.py +188 -0
  292. package/skills/investigating-agentforce-d360/scripts/discover_sessions.py +556 -0
  293. package/skills/investigating-agentforce-d360/scripts/fetch_dc.py +1045 -0
  294. package/skills/investigating-agentforce-d360/scripts/render_dc.py +1750 -0
  295. package/skills/investigating-agentforce-d360/scripts/resolve_session.py +264 -0
  296. package/skills/investigating-agentforce-d360/scripts/storage.py +92 -0
  297. package/skills/investigating-agentforce-d360/scripts/tests/__init__.py +0 -0
  298. package/skills/investigating-agentforce-d360/scripts/tests/_bootstrap.py +15 -0
  299. package/skills/investigating-agentforce-d360/scripts/tests/fixtures/__init__.py +0 -0
  300. package/skills/investigating-agentforce-d360/scripts/tests/fixtures/synthetic_session.py +424 -0
  301. package/skills/investigating-agentforce-d360/scripts/tests/test_assemble_dc_bootstrap_and_mode.py +115 -0
  302. package/skills/investigating-agentforce-d360/scripts/tests/test_assemble_dc_gateway_direct.py +220 -0
  303. package/skills/investigating-agentforce-d360/scripts/tests/test_assemble_dc_gateway_direct_integration.py +158 -0
  304. package/skills/investigating-agentforce-d360/scripts/tests/test_assemble_dc_helpers.py +287 -0
  305. package/skills/investigating-agentforce-d360/scripts/tests/test_assemble_dc_integration.py +247 -0
  306. package/skills/investigating-agentforce-d360/scripts/tests/test_dc_and_resolve_session.py +433 -0
  307. package/skills/investigating-agentforce-d360/scripts/tests/test_discover_sessions.py +458 -0
  308. package/skills/investigating-agentforce-d360/scripts/tests/test_discover_sessions_grep_ci.py +193 -0
  309. package/skills/investigating-agentforce-d360/scripts/tests/test_fetch_dc_helpers.py +266 -0
  310. package/skills/investigating-agentforce-d360/scripts/tests/test_fetch_dc_identity.py +528 -0
  311. package/skills/investigating-agentforce-d360/scripts/tests/test_fetch_dc_main.py +251 -0
  312. package/skills/investigating-agentforce-d360/scripts/tests/test_fetch_dc_waterfall.py +229 -0
  313. package/skills/investigating-agentforce-d360/scripts/tests/test_fetch_dc_waterfall_full.py +283 -0
  314. package/skills/investigating-agentforce-d360/scripts/tests/test_identity_coherence.py +327 -0
  315. package/skills/investigating-agentforce-d360/scripts/tests/test_render_dc_branches.py +256 -0
  316. package/skills/investigating-agentforce-d360/scripts/tests/test_render_dc_gateway_direct.py +130 -0
  317. package/skills/investigating-agentforce-d360/scripts/tests/test_render_dc_helpers.py +291 -0
  318. package/skills/investigating-agentforce-d360/scripts/tests/test_render_dc_integration.py +220 -0
  319. package/skills/investigating-agentforce-d360/scripts/tests/test_render_dc_planner_llm_calls.py +284 -0
  320. package/skills/investigating-agentforce-d360/scripts/tests/test_render_dc_show_prompts_gating.py +215 -0
  321. package/skills/investigating-agentforce-d360/scripts/tests/test_resolve_from_disk.py +100 -0
  322. package/skills/investigating-agentforce-d360/scripts/tests/test_resolve_session_main.py +149 -0
  323. package/skills/investigating-agentforce-d360/scripts/tests/test_runtime_override.py +104 -0
  324. package/skills/investigating-agentforce-d360/scripts/tests/test_session_shape.py +95 -0
  325. package/skills/investigating-agentforce-d360/scripts/tests/test_session_shape_dropped_by_stdm.py +85 -0
  326. package/skills/managing-managed-event-subscription/SKILL.md +152 -0
  327. package/skills/managing-managed-event-subscription/assets/managed-event-subscription-template.xml +20 -0
  328. package/skills/managing-managed-event-subscription/references/delete-guide.md +57 -0
  329. package/skills/managing-managed-event-subscription/references/topic-name-formats.md +26 -0
  330. package/skills/managing-managed-event-subscription/references/update-constraints.md +30 -0
  331. package/skills/reviewing-lwc-mobile-offline/SKILL.md +168 -0
  332. package/skills/reviewing-lwc-mobile-offline/references/grounding.md +7 -0
  333. package/skills/reviewing-lwc-mobile-offline/references/inline-graphql.md +43 -0
  334. package/skills/reviewing-lwc-mobile-offline/references/komaci-eslint.md +125 -0
  335. package/skills/reviewing-lwc-mobile-offline/references/lwc-if.md +78 -0
  336. package/skills/reviewing-lwc-mobile-offline/scripts/komaci.config.mjs +18 -0
  337. package/skills/reviewing-lwc-mobile-offline/scripts/package.json +10 -0
  338. package/skills/reviewing-lwc-mobile-offline/scripts/run-komaci.sh +69 -0
  339. package/skills/uplifting-components-to-slds2/SKILL.md +3 -2
  340. package/skills/uplifting-components-to-slds2/references/color-hooks-decision-guide.md +30 -9
  341. package/skills/uplifting-components-to-slds2/references/examples.md +24 -6
  342. package/skills/using-mobile-native-capabilities/SKILL.md +182 -0
  343. package/skills/using-mobile-native-capabilities/references/app-review.md +68 -0
  344. package/skills/using-mobile-native-capabilities/references/ar-space-capture.md +125 -0
  345. package/skills/using-mobile-native-capabilities/references/barcode-scanner.md +219 -0
  346. package/skills/using-mobile-native-capabilities/references/base-capability.md +22 -0
  347. package/skills/using-mobile-native-capabilities/references/biometrics.md +90 -0
  348. package/skills/using-mobile-native-capabilities/references/calendar.md +213 -0
  349. package/skills/using-mobile-native-capabilities/references/contacts.md +232 -0
  350. package/skills/using-mobile-native-capabilities/references/document-scanner.md +342 -0
  351. package/skills/using-mobile-native-capabilities/references/geofencing.md +123 -0
  352. package/skills/using-mobile-native-capabilities/references/location.md +158 -0
  353. package/skills/using-mobile-native-capabilities/references/mobile-capabilities.md +30 -0
  354. package/skills/using-mobile-native-capabilities/references/nfc.md +181 -0
  355. package/skills/using-mobile-native-capabilities/references/payments.md +95 -0
  356. package/skills/validating-slds/SKILL.md +262 -0
  357. package/skills/validating-slds/references/quality-checks.md +308 -0
  358. package/skills/validating-slds/references/report-format.md +302 -0
  359. package/skills/validating-slds/scripts/analyze-quality.cjs +521 -0
@@ -0,0 +1,397 @@
1
+ """Tests for ``parse_wave.main`` orchestrator + tree-walking helpers.
2
+
3
+ Covers:
4
+ - ``walk_and_inflate`` recursive Flow inflation
5
+ - ``inflate_flow_leaf`` cycle + depth-cap behavior
6
+ - ``build_root_children`` bundle → root children + new_refs
7
+ - ``main`` (full path) env-driven pipeline against a tmp WORK_DIR
8
+ - ``main --finalize-cap`` drain pending → unresolved
9
+ """
10
+ from __future__ import annotations
11
+
12
+ import json
13
+ import os
14
+ import unittest
15
+ from pathlib import Path
16
+ from tempfile import TemporaryDirectory
17
+ from unittest import mock
18
+
19
+ from . import _bootstrap # noqa: F401 — sys.path setup
20
+
21
+ import parse_wave # type: ignore
22
+
23
+
24
+ _FLOW_NS = "http://soap.sforce.com/2006/04/metadata"
25
+
26
+
27
+ # -----------------------------------------------------------------------------
28
+ # walk_and_inflate
29
+ # -----------------------------------------------------------------------------
30
+
31
+
32
+ class WalkAndInflateTests(unittest.TestCase):
33
+
34
+ def test_no_op_for_non_FLOW_non_GEN_AI_FUNCTION_node(self):
35
+ node = {"kind": "TOPIC", "api_name": "T1", "children": []}
36
+ flow_children: dict = {}
37
+ # Should not raise; should not mutate.
38
+ parse_wave.walk_and_inflate(node, flow_children)
39
+ self.assertEqual(node["children"], [])
40
+
41
+ def test_inflates_flow_leaf_under_gen_ai_function(self):
42
+ # GEN_AI_FUNCTION wraps a FLOW leaf — walker recurses into it.
43
+ node = {
44
+ "kind": "GEN_AI_FUNCTION",
45
+ "api_name": "GAF1",
46
+ "children": [{"kind": "FLOW", "api_name": "MyFlow", "children": []}],
47
+ }
48
+ flow_children = {"MyFlow": [
49
+ {"kind": "APEX", "element_name": "callA", "api_name": "MyClass"},
50
+ ]}
51
+ parse_wave.walk_and_inflate(node, flow_children)
52
+ # FLOW leaf got inflated with the APEX child
53
+ flow_leaf = node["children"][0]
54
+ self.assertEqual(len(flow_leaf["children"]), 1)
55
+ self.assertEqual(flow_leaf["children"][0]["kind"], "APEX")
56
+
57
+ def test_recursively_walks_through_topic_to_inflate_flow(self):
58
+ # Walker should descend through TOPIC/non-special nodes too.
59
+ node = {
60
+ "kind": "BOT_DEFINITION", "api_name": "Agent",
61
+ "children": [{
62
+ "kind": "TOPIC", "api_name": "T1",
63
+ "children": [{
64
+ "kind": "GEN_AI_FUNCTION", "api_name": "GAF1",
65
+ "children": [{"kind": "FLOW", "api_name": "DeepFlow",
66
+ "children": []}],
67
+ }],
68
+ }],
69
+ }
70
+ flow_children = {"DeepFlow": [
71
+ {"kind": "APEX", "element_name": "callA", "api_name": "DeepClass"},
72
+ ]}
73
+ parse_wave.walk_and_inflate(node, flow_children)
74
+ # DeepFlow got the APEX child
75
+ deep_flow = node["children"][0]["children"][0]["children"][0]
76
+ self.assertEqual(deep_flow["children"][0]["api_name"], "DeepClass")
77
+
78
+ def test_pending_out_collects_depth_cap_truncations(self):
79
+ # Build a chain longer than MAX_BFS_DEPTH so the cap trips.
80
+ # Generate 25 flows that each subflow into the next.
81
+ flow_children: dict = {}
82
+ for i in range(25):
83
+ next_name = f"F{i+1}" if i < 24 else None
84
+ kids = []
85
+ if next_name:
86
+ kids.append({"kind": "FLOW", "element_name": f"sub_{i}",
87
+ "api_name": next_name})
88
+ flow_children[f"F{i}"] = kids
89
+ node = {"kind": "FLOW", "api_name": "F0", "children": []}
90
+ pending: dict = parse_wave.empty_kind_sets()
91
+ parse_wave.walk_and_inflate(node, flow_children, pending_out=pending)
92
+ # Some subflow ended up in pending due to the depth cap (>= MAX_BFS_DEPTH).
93
+ self.assertTrue(any(pending[k] for k in parse_wave.BFS_KINDS))
94
+
95
+
96
+ # -----------------------------------------------------------------------------
97
+ # inflate_flow_leaf — cycle detection
98
+ # -----------------------------------------------------------------------------
99
+
100
+
101
+ class InflateFlowLeafCycleTests(unittest.TestCase):
102
+
103
+ def test_self_recursion_marked_as_cycle(self):
104
+ # Flow A subflows back to A itself → cycle.
105
+ flow_children = {"A": [
106
+ {"kind": "FLOW", "element_name": "self", "api_name": "A"},
107
+ ]}
108
+ leaf = {"kind": "FLOW", "api_name": "A", "children": []}
109
+ parse_wave.inflate_flow_leaf(leaf, flow_children)
110
+ # The recursive child got annotated with _truncated.
111
+ kid = leaf["children"][0]
112
+ self.assertIn("_truncated", kid)
113
+ self.assertEqual(kid["_truncated"]["reason"],
114
+ parse_wave.TRUNCATION_CYCLE)
115
+
116
+ def test_unknown_flow_no_inflation(self):
117
+ # leaf.api_name not in flow_children → nothing to inflate.
118
+ leaf = {"kind": "FLOW", "api_name": "Missing", "children": []}
119
+ parse_wave.inflate_flow_leaf(leaf, {})
120
+ self.assertEqual(leaf["children"], [])
121
+
122
+
123
+ # -----------------------------------------------------------------------------
124
+ # build_root_children
125
+ # -----------------------------------------------------------------------------
126
+
127
+
128
+ class BuildRootChildrenTests(unittest.TestCase):
129
+
130
+ def test_topics_become_root_children(self):
131
+ bundle = {
132
+ "topics": [
133
+ {"name": "Greetings", "actions": [
134
+ {"name": "Hello", "invocationTarget": "MyFlow",
135
+ "invocationTargetType": "flow"},
136
+ ]},
137
+ ],
138
+ }
139
+ visited = parse_wave.empty_kind_sets()
140
+ aux: set = set()
141
+ children, new_refs = parse_wave.build_root_children(bundle, visited, aux)
142
+ self.assertEqual(len(children), 1)
143
+ self.assertEqual(children[0]["kind"], "TOPIC")
144
+ self.assertEqual(children[0]["api_name"], "Greetings")
145
+ # GEN_AI_FUNCTION wrapping the action
146
+ self.assertEqual(children[0]["children"][0]["kind"], "GEN_AI_FUNCTION")
147
+ # FLOW ref harvested into new_refs
148
+ self.assertIn("MyFlow", new_refs["FLOW"])
149
+ # Topic + action recorded in aux_visited
150
+ self.assertIn(("TOPIC", "Greetings"), aux)
151
+ self.assertIn(("GEN_AI_FUNCTION", "Hello"), aux)
152
+
153
+ def test_skips_already_visited_refs(self):
154
+ bundle = {
155
+ "topics": [{"name": "T", "actions": [
156
+ {"name": "A1", "invocationTarget": "FlowA",
157
+ "invocationTargetType": "flow"},
158
+ ]}],
159
+ }
160
+ visited = parse_wave.empty_kind_sets()
161
+ visited["FLOW"].add("FlowA")
162
+ aux: set = set()
163
+ _, new_refs = parse_wave.build_root_children(bundle, visited, aux)
164
+ # FlowA already visited → not added to new_refs
165
+ self.assertNotIn("FlowA", new_refs["FLOW"])
166
+
167
+ def test_standard_action_does_not_pollute_new_refs(self):
168
+ # STANDARD_ACTION is declared-only, must NOT
169
+ # land in new_refs (would pollute _pending_fetches).
170
+ bundle = {
171
+ "topics": [{"name": "T", "actions": [
172
+ {"name": "A1", "invocationTarget": "createRecord",
173
+ "invocationTargetType": "standardinvocableaction"},
174
+ ]}],
175
+ }
176
+ visited = parse_wave.empty_kind_sets()
177
+ aux: set = set()
178
+ _, new_refs = parse_wave.build_root_children(bundle, visited, aux)
179
+ self.assertNotIn("createRecord", new_refs["STANDARD_ACTION"])
180
+
181
+ def test_empty_bundle_returns_empty_children(self):
182
+ children, new_refs = parse_wave.build_root_children(
183
+ {}, parse_wave.empty_kind_sets(), set(),
184
+ )
185
+ self.assertEqual(children, [])
186
+ for v in new_refs.values():
187
+ self.assertEqual(v, set())
188
+
189
+
190
+ # -----------------------------------------------------------------------------
191
+ # main() — happy path + finalize-cap branch
192
+ # -----------------------------------------------------------------------------
193
+
194
+
195
+ def _setup_workdir(tmp: Path, *, bundle: dict | None = None) -> Path:
196
+ """Plant a WORK_DIR with _bundle_parsed.json + minimal sf_meta layout."""
197
+ work_dir = tmp / "work"
198
+ work_dir.mkdir()
199
+ bundle = bundle or {
200
+ "plannerName": "MyPlanner",
201
+ "plannerType": "Atlas__Reasoning",
202
+ "generation": "nga",
203
+ "topics": [{"name": "Greetings", "actions": [
204
+ {"name": "SayHi", "invocationTarget": "GreetingsFlow",
205
+ "invocationTargetType": "flow"},
206
+ ]}],
207
+ }
208
+ (work_dir / "_bundle_parsed.json").write_text(json.dumps(bundle))
209
+ # Empty sf_meta is fine — harvest_waves returns empty when missing.
210
+ return work_dir
211
+
212
+
213
+ def _run_main(*, work_dir: Path, argv_extra: list[str] | None = None,
214
+ env_extra: dict | None = None) -> int:
215
+ """Invoke parse_wave.main with controlled env + argv."""
216
+ saved_env = dict(os.environ)
217
+ saved_argv = list(parse_wave.sys.argv)
218
+ try:
219
+ os.environ.update({
220
+ "WORK_DIR": str(work_dir),
221
+ "AGENT_API_NAME": "DemoAgent",
222
+ "AGENT_VERSION": "v3",
223
+ "BOT_ID": "0Xx000000000ABC",
224
+ "BOT_MASTER_LABEL": "Demo Agent",
225
+ "VERSION_AUTO_PICKED": "false",
226
+ })
227
+ if env_extra:
228
+ os.environ.update(env_extra)
229
+ parse_wave.sys.argv = ["parse_wave.py", *(argv_extra or [])]
230
+ return parse_wave.main()
231
+ finally:
232
+ os.environ.clear()
233
+ os.environ.update(saved_env)
234
+ parse_wave.sys.argv = saved_argv
235
+
236
+
237
+ class MainHappyPathTests(unittest.TestCase):
238
+
239
+ def test_main_writes_declared_action_tree_json(self):
240
+ with TemporaryDirectory() as t:
241
+ tmp = Path(t)
242
+ work_dir = _setup_workdir(tmp)
243
+ with mock.patch.object(parse_wave.sys, "stderr"):
244
+ rc = _run_main(work_dir=work_dir)
245
+ self.assertEqual(rc, 0)
246
+ tree_path = work_dir / "declared_action_tree.json"
247
+ self.assertTrue(tree_path.is_file())
248
+ tree = json.loads(tree_path.read_text())
249
+ # Tree has the expected shape
250
+ self.assertEqual(tree["_schema_version"], "3.1")
251
+ self.assertEqual(tree["agent"]["api_name"], "DemoAgent")
252
+ self.assertEqual(tree["root"]["kind"], "BOT_DEFINITION")
253
+ # One topic became a root child
254
+ kinds = [c["kind"] for c in tree["root"]["children"]]
255
+ self.assertIn("TOPIC", kinds)
256
+
257
+ def test_main_records_pending_for_unfetched_flow(self):
258
+ with TemporaryDirectory() as t:
259
+ tmp = Path(t)
260
+ work_dir = _setup_workdir(tmp)
261
+ with mock.patch.object(parse_wave.sys, "stderr"):
262
+ _run_main(work_dir=work_dir)
263
+ tree = json.loads(
264
+ (work_dir / "declared_action_tree.json").read_text()
265
+ )
266
+ # GreetingsFlow not on disk → in _pending_fetches.FLOW
267
+ self.assertIn("GreetingsFlow", tree["_pending_fetches"]["FLOW"])
268
+ self.assertTrue(tree["_partial"])
269
+ # _partial_reason populated on first run when pending refs exist
270
+ # but no depth cap was tripped.
271
+ self.assertEqual(tree["_partial_reason"], "pending-refs")
272
+
273
+ def test_main_writes_pending_refs_reason_when_initial_reason_is_none(self):
274
+ # init_tree seeds `_partial_reason=None`. The writer's elif branch
275
+ # must populate it with "pending-refs" when any pending bucket is
276
+ # non-empty and the depth cap did not trip — a setdefault against
277
+ # `None` is a no-op, which is the bug this guards.
278
+ with TemporaryDirectory() as t:
279
+ tmp = Path(t)
280
+ work_dir = _setup_workdir(tmp)
281
+ with mock.patch.object(parse_wave.sys, "stderr"):
282
+ _run_main(work_dir=work_dir)
283
+ tree = json.loads(
284
+ (work_dir / "declared_action_tree.json").read_text()
285
+ )
286
+ # Sanity: pending exists, depth cap was NOT tripped.
287
+ self.assertIn("GreetingsFlow", tree["_pending_fetches"]["FLOW"])
288
+ # The fix: a None-valued reason gets promoted to "pending-refs".
289
+ self.assertEqual(tree["_partial_reason"], "pending-refs")
290
+
291
+ def test_main_preserves_existing_partial_reason_when_pending_refs(self):
292
+ # init_tree primes _partial_reason=None; setdefault used to be a
293
+ # no-op against that None and left the reason blank. Today the
294
+ # writer treats None as "unset" and fills in "pending-refs".
295
+ # When a prior reason exists (e.g. "max-depth-cap" from a previous
296
+ # wave), the writer must NOT overwrite it.
297
+ with TemporaryDirectory() as t:
298
+ tmp = Path(t)
299
+ work_dir = _setup_workdir(tmp)
300
+ # Plant a tree on disk that mimics a previously-suspended run:
301
+ # _partial_reason="max-depth-cap" and empty root children, so
302
+ # main() enters the fresh-tree branch and re-collects bundle
303
+ # refs (GreetingsFlow pending) yet sees the prior reason.
304
+ tree_path = work_dir / "declared_action_tree.json"
305
+ tree_path.write_text(json.dumps({
306
+ "_schema_version": "3.1",
307
+ "agent": {"api_name": "DemoAgent", "version": "v3"},
308
+ "root": {"kind": "BOT_DEFINITION", "api_name": "DemoAgent",
309
+ "children": []},
310
+ "_partial": True,
311
+ "_partial_reason": "max-depth-cap",
312
+ "_pending_fetches": {k: [] for k in parse_wave.BFS_KINDS},
313
+ "_unresolved": [],
314
+ "_visited": [],
315
+ }))
316
+ with mock.patch.object(parse_wave.sys, "stderr"):
317
+ _run_main(work_dir=work_dir)
318
+ tree2 = json.loads(tree_path.read_text())
319
+ # GreetingsFlow is pending → any_pending=True → elif branch runs.
320
+ self.assertIn("GreetingsFlow", tree2["_pending_fetches"]["FLOW"])
321
+ # Existing more-specific reason is preserved.
322
+ self.assertEqual(tree2["_partial_reason"], "max-depth-cap")
323
+
324
+ def test_main_returns_one_when_work_dir_env_missing(self):
325
+ saved_env = dict(os.environ)
326
+ try:
327
+ for k in ("WORK_DIR", "AGENT_API_NAME", "AGENT_VERSION"):
328
+ os.environ.pop(k, None)
329
+ with mock.patch.object(parse_wave.sys, "argv", ["parse_wave.py"]):
330
+ with mock.patch.object(parse_wave.sys, "stderr"):
331
+ rc = parse_wave.main()
332
+ finally:
333
+ os.environ.clear()
334
+ os.environ.update(saved_env)
335
+ self.assertEqual(rc, 1)
336
+
337
+ def test_main_returns_one_when_bundle_missing(self):
338
+ with TemporaryDirectory() as t:
339
+ tmp = Path(t)
340
+ work_dir = tmp / "work"
341
+ work_dir.mkdir()
342
+ # No _bundle_parsed.json
343
+ with mock.patch.object(parse_wave.sys, "stderr"):
344
+ rc = _run_main(work_dir=work_dir)
345
+ self.assertEqual(rc, 1)
346
+
347
+ def test_main_reparses_existing_tree(self):
348
+ # Two-pass: first run creates the tree, second re-uses it.
349
+ with TemporaryDirectory() as t:
350
+ tmp = Path(t)
351
+ work_dir = _setup_workdir(tmp)
352
+ with mock.patch.object(parse_wave.sys, "stderr"):
353
+ _run_main(work_dir=work_dir)
354
+ # Second invocation should load + augment without crashing
355
+ rc = _run_main(work_dir=work_dir)
356
+ self.assertEqual(rc, 0)
357
+
358
+
359
+ class MainFinalizeCapTests(unittest.TestCase):
360
+
361
+ def test_finalize_cap_drains_pending_into_unresolved(self):
362
+ with TemporaryDirectory() as t:
363
+ tmp = Path(t)
364
+ work_dir = _setup_workdir(tmp)
365
+ # First run produces a tree with pending FLOW=GreetingsFlow.
366
+ with mock.patch.object(parse_wave.sys, "stderr"):
367
+ _run_main(work_dir=work_dir)
368
+ # --finalize-cap drains pending → unresolved
369
+ rc = _run_main(
370
+ work_dir=work_dir, argv_extra=["--finalize-cap"],
371
+ )
372
+ self.assertEqual(rc, 0)
373
+ tree = json.loads(
374
+ (work_dir / "declared_action_tree.json").read_text()
375
+ )
376
+ # All buckets emptied
377
+ for kind in parse_wave.BFS_KINDS:
378
+ self.assertEqual(tree["_pending_fetches"][kind], [])
379
+ # GreetingsFlow now in _unresolved
380
+ api_names = [u["api_name"] for u in tree["_unresolved"]]
381
+ self.assertIn("GreetingsFlow", api_names)
382
+
383
+ def test_finalize_cap_with_no_existing_tree_is_noop(self):
384
+ with TemporaryDirectory() as t:
385
+ tmp = Path(t)
386
+ work_dir = tmp / "work"
387
+ work_dir.mkdir()
388
+ with mock.patch.object(parse_wave.sys, "stderr"):
389
+ rc = _run_main(
390
+ work_dir=work_dir, argv_extra=["--finalize-cap"],
391
+ )
392
+ # No tree file → noop, exit 0.
393
+ self.assertEqual(rc, 0)
394
+
395
+
396
+ if __name__ == "__main__":
397
+ unittest.main()
@@ -0,0 +1,244 @@
1
+ """2026-05-03 revision: `inflate_flow_leaf` uses a per-branch ancestor
2
+ path set (`visited_in_path`) for cycle detection. The defensive
3
+ `MAX_BFS_DEPTH` cap is preserved as a last-resort termination guard
4
+ but is no longer the primitive that prevents runaway recursion.
5
+
6
+ The bug this regression suite prevents: shared utility flows such as
7
+ `handleFlowFault` appear on every real Agentforce flow's fault path.
8
+ Under the old `MAX_BFS_DEPTH = 5` behaviour the utility showed up in
9
+ `_pending_fetches["FLOW"]` with `PARTIAL_REASON=max-depth-cap` on any
10
+ moderately nested tree (e.g. `AGNT_Baz_Qux →
11
+ handleFlowFault`), even though `handleFlowFault` was trivially
12
+ expandable. Per-branch path-set semantics fix this: the same flow on
13
+ two sibling branches is NOT a cycle, only an ancestor-chain recurrence
14
+ is.
15
+ """
16
+ from __future__ import annotations
17
+
18
+ import unittest
19
+
20
+ from . import _bootstrap # noqa: F401
21
+
22
+ import parse_wave # type: ignore
23
+
24
+
25
+ def _find_all(node: dict, api_name: str) -> list[dict]:
26
+ """Collect every node in `node`'s subtree whose `api_name` matches."""
27
+ found: list[dict] = []
28
+ if node.get("api_name") == api_name:
29
+ found.append(node)
30
+ for c in node.get("children", []) or []:
31
+ found.extend(_find_all(c, api_name))
32
+ return found
33
+
34
+
35
+ class SharedUtilityFlowExpansionTests(unittest.TestCase):
36
+ """Direct repro of the real-org `handleFlowFault` bug.
37
+
38
+ Parent flow has two sibling children, each of which invokes the
39
+ same utility flow. Both utility instances must expand fully.
40
+ """
41
+
42
+ def test_shared_utility_flow_expands_on_every_branch(self):
43
+ # parent -> [childA, childB]; childA -> utility; childB -> utility
44
+ # utility itself calls a leaf apex (so we can assert `utility`
45
+ # got fully inflated with a child, not just a stub).
46
+ flow_children = {
47
+ "parent": [
48
+ {"kind": "FLOW", "api_name": "childA", "element_name": "call_A"},
49
+ {"kind": "FLOW", "api_name": "childB", "element_name": "call_B"},
50
+ ],
51
+ "childA": [
52
+ {"kind": "FLOW", "api_name": "utility", "element_name": "call_util"},
53
+ ],
54
+ "childB": [
55
+ {"kind": "FLOW", "api_name": "utility", "element_name": "call_util"},
56
+ ],
57
+ "utility": [
58
+ {"kind": "APEX", "api_name": "XCSF_FlowFaultMessage", "element_name": "log"},
59
+ ],
60
+ }
61
+ root = {"kind": "FLOW", "api_name": "parent", "children": []}
62
+ pending = parse_wave.empty_kind_sets()
63
+
64
+ parse_wave.inflate_flow_leaf(root, flow_children, pending_out=pending)
65
+
66
+ utilities = _find_all(root, "utility")
67
+ self.assertEqual(
68
+ len(utilities), 2,
69
+ "utility should appear once on each sibling branch",
70
+ )
71
+
72
+ # Both utility instances must have expanded children (the apex
73
+ # leaf). The regression shape was: first branch expanded, second
74
+ # either in `_pending_fetches` or emitted as an empty-children
75
+ # stub due to depth-cap or false-cycle pruning.
76
+ for i, u in enumerate(utilities):
77
+ kids = u.get("children", [])
78
+ self.assertEqual(
79
+ len(kids), 1,
80
+ f"utility instance {i} should have 1 expanded child, got {kids}",
81
+ )
82
+ self.assertEqual(kids[0]["api_name"], "XCSF_FlowFaultMessage")
83
+ self.assertNotIn("_truncated", u)
84
+ self.assertNotIn("_cycle_back_to", u)
85
+
86
+ # Critically: nothing pending. Shared-utility expansion must not
87
+ # leak into `_pending_fetches`.
88
+ self.assertEqual(pending["FLOW"], set())
89
+
90
+
91
+ class TrueCycleDetectionTests(unittest.TestCase):
92
+ """Per-branch path-set must still catch genuine cycles."""
93
+
94
+ def test_true_cycle_detected(self):
95
+ """A -> B -> A: second A is annotated, not recursed into, and
96
+ not surfaced in `_pending_fetches`."""
97
+ flow_children = {
98
+ "A": [{"kind": "FLOW", "api_name": "B", "element_name": "call_B"}],
99
+ "B": [{"kind": "FLOW", "api_name": "A", "element_name": "call_A"}],
100
+ }
101
+ root = {"kind": "FLOW", "api_name": "A", "children": []}
102
+ pending = parse_wave.empty_kind_sets()
103
+
104
+ parse_wave.inflate_flow_leaf(root, flow_children, pending_out=pending)
105
+
106
+ # A -> B -> A(cycle)
107
+ b = root["children"][0]
108
+ self.assertEqual(b["api_name"], "B")
109
+ a_cycle = b["children"][0]
110
+ self.assertEqual(a_cycle["api_name"], "A")
111
+ self.assertEqual(
112
+ a_cycle["_truncated"],
113
+ {"reason": "cycle", "target": "FLOW:A"},
114
+ )
115
+ self.assertEqual(a_cycle["_cycle_back_to"], "FLOW:A")
116
+
117
+ # Cycles live in the tree, not the pending accumulator.
118
+ self.assertEqual(pending["FLOW"], set())
119
+
120
+ def test_self_recursive_flow(self):
121
+ """A -> A: direct self-cycle is annotated on first recurrence."""
122
+ flow_children = {
123
+ "A": [{"kind": "FLOW", "api_name": "A", "element_name": "self_call"}],
124
+ }
125
+ root = {"kind": "FLOW", "api_name": "A", "children": []}
126
+ pending = parse_wave.empty_kind_sets()
127
+
128
+ parse_wave.inflate_flow_leaf(root, flow_children, pending_out=pending)
129
+
130
+ kids = root.get("children", [])
131
+ self.assertEqual(len(kids), 1)
132
+ self.assertEqual(kids[0]["api_name"], "A")
133
+ self.assertEqual(
134
+ kids[0]["_truncated"],
135
+ {"reason": "cycle", "target": "FLOW:A"},
136
+ )
137
+ self.assertEqual(pending["FLOW"], set())
138
+
139
+
140
+ class DeepNonCyclicChainTests(unittest.TestCase):
141
+ """Linear chains longer than the old `MAX_BFS_DEPTH = 5` must
142
+ expand fully under the revised semantics. Previously they tripped
143
+ `PARTIAL_REASON=max-depth-cap`; now they expand cleanly.
144
+ """
145
+
146
+ def test_deep_non_cyclic_chain_fully_expands(self):
147
+ # A -> B -> C -> D -> E -> F -> G (7 unique flows, depth 6
148
+ # from the root). Old cap of 5 would have truncated at F;
149
+ # new cap of 20 doesn't care.
150
+ names = list("ABCDEFG")
151
+ flow_children: dict = {}
152
+ for i, n in enumerate(names):
153
+ if i + 1 < len(names):
154
+ flow_children[n] = [{
155
+ "kind": "FLOW",
156
+ "api_name": names[i + 1],
157
+ "element_name": f"call_{names[i + 1]}",
158
+ }]
159
+ else:
160
+ flow_children[n] = []
161
+
162
+ root = {"kind": "FLOW", "api_name": "A", "children": []}
163
+ pending = parse_wave.empty_kind_sets()
164
+ parse_wave.inflate_flow_leaf(root, flow_children, pending_out=pending)
165
+
166
+ node = root
167
+ for expected in names[1:]:
168
+ kids = node.get("children", [])
169
+ self.assertEqual(
170
+ len(kids), 1,
171
+ f"{node['api_name']} should have expanded child {expected}",
172
+ )
173
+ self.assertEqual(kids[0]["api_name"], expected)
174
+ self.assertNotIn("_truncated", kids[0])
175
+ node = kids[0]
176
+
177
+ # Terminal leaf has no children and carries no truncation
178
+ # annotation — the cap was never relevant.
179
+ self.assertEqual(node.get("children", []), [])
180
+ self.assertNotIn("_truncated", node)
181
+ self.assertEqual(pending["FLOW"], set())
182
+
183
+
184
+ class DefensiveCapStillTerminatesTests(unittest.TestCase):
185
+ """Pathological chain longer than the defensive cap must still
186
+ terminate cleanly. This proves the safety net still works even
187
+ though per-branch cycle detection does the real work in practice.
188
+ """
189
+
190
+ def test_defensive_cap_still_terminates(self):
191
+ # 25 unique flows, linear — longer than the defensive cap of 20.
192
+ # Either the tree fully expands (if someone lifts the cap
193
+ # further) OR it caps at MAX_BFS_DEPTH and annotates the
194
+ # unreached flow. The test asserts termination and one of
195
+ # those two well-defined shapes.
196
+ n = 25
197
+ names = [f"F{i:02d}" for i in range(n)]
198
+ flow_children: dict = {}
199
+ for i, name in enumerate(names):
200
+ if i + 1 < n:
201
+ flow_children[name] = [{
202
+ "kind": "FLOW",
203
+ "api_name": names[i + 1],
204
+ "element_name": "sub",
205
+ }]
206
+ else:
207
+ flow_children[name] = []
208
+
209
+ root = {"kind": "FLOW", "api_name": names[0], "children": []}
210
+ pending = parse_wave.empty_kind_sets()
211
+
212
+ # Primary assertion: this call terminates and does not recurse
213
+ # past Python's default recursion limit. If the defensive cap
214
+ # regresses, this test will hang or raise RecursionError.
215
+ parse_wave.inflate_flow_leaf(root, flow_children, pending_out=pending)
216
+
217
+ # Collect every flow that appears in the tree.
218
+ def all_api_names(node: dict) -> set[str]:
219
+ out = {node.get("api_name")}
220
+ for c in node.get("children", []) or []:
221
+ out.update(all_api_names(c))
222
+ return out - {None}
223
+
224
+ present = all_api_names(root)
225
+
226
+ # At least the first MAX_BFS_DEPTH flows must be present. The
227
+ # tail may or may not appear depending on where the cap trips.
228
+ for i in range(parse_wave.MAX_BFS_DEPTH):
229
+ self.assertIn(
230
+ names[i], present,
231
+ f"{names[i]} (index {i}) should be expanded — "
232
+ f"it's below MAX_BFS_DEPTH={parse_wave.MAX_BFS_DEPTH}",
233
+ )
234
+
235
+ # If any flow is pending, it must be a real descendant (not a
236
+ # fabricated name) and must carry the max-depth annotation
237
+ # somewhere in the tree.
238
+ if pending["FLOW"]:
239
+ for pending_name in pending["FLOW"]:
240
+ self.assertIn(pending_name, names)
241
+
242
+
243
+ if __name__ == "__main__":
244
+ unittest.main()