@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,644 @@
1
+ """Tests for parse_wave BFS + inflate uses (kind, canonical_name)
2
+ tuple-keyed visited tracking. A flat per-name visited set would silently
3
+ drop the second of two same-named different-kind nodes (Flow Foo + Apex Foo),
4
+ and would infinite-loop / depth-cap on self-cycles without annotation.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import unittest
9
+
10
+ from . import _bootstrap # noqa: F401
11
+
12
+ import parse_wave # type: ignore
13
+
14
+
15
+ class BfsStepTupleKeyingTests(unittest.TestCase):
16
+ """Same-name, different-kind refs must not collide."""
17
+
18
+ def test_flow_and_apex_same_name_are_distinct(self):
19
+ pending = {k: set() for k in parse_wave._BFS_KINDS}
20
+ visited = {k: set() for k in parse_wave._BFS_KINDS}
21
+ new_refs = {"FLOW": {"Foo"}, "APEX": {"Foo"}}
22
+
23
+ merged, cycles = parse_wave.bfs_step(pending, visited, new_refs)
24
+
25
+ self.assertIn("Foo", merged["FLOW"])
26
+ self.assertIn("Foo", merged["APEX"])
27
+ self.assertEqual(cycles, [])
28
+
29
+ def test_already_visited_ref_skipped_and_recorded_as_cycle(self):
30
+ pending = {k: set() for k in parse_wave._BFS_KINDS}
31
+ visited = {k: set() for k in parse_wave._BFS_KINDS}
32
+ visited["FLOW"] = {"Foo"} # simulate prior wave visit
33
+ new_refs = {"FLOW": {"Foo", "Bar"}}
34
+
35
+ merged, cycles = parse_wave.bfs_step(pending, visited, new_refs)
36
+
37
+ self.assertNotIn("Foo", merged["FLOW"]) # filtered out
38
+ self.assertIn("Bar", merged["FLOW"])
39
+ self.assertIn(("FLOW", "Foo"), cycles)
40
+
41
+ def test_cross_type_cycle_is_distinct(self):
42
+ """Flow Foo visited does not mask Apex Foo on the same wave."""
43
+ pending = {k: set() for k in parse_wave._BFS_KINDS}
44
+ visited = {k: set() for k in parse_wave._BFS_KINDS}
45
+ visited["FLOW"] = {"Foo"}
46
+ new_refs = {"FLOW": {"Foo"}, "APEX": {"Foo"}}
47
+
48
+ merged, cycles = parse_wave.bfs_step(pending, visited, new_refs)
49
+
50
+ self.assertEqual(merged["FLOW"], set()) # Foo is visited
51
+ self.assertEqual(merged["APEX"], {"Foo"}) # distinct kind → keeps
52
+ self.assertIn(("FLOW", "Foo"), cycles)
53
+ self.assertNotIn(("APEX", "Foo"), cycles)
54
+
55
+ def test_empty_initial_pending_terminates(self):
56
+ pending = {k: set() for k in parse_wave._BFS_KINDS}
57
+ visited = {k: set() for k in parse_wave._BFS_KINDS}
58
+ merged, cycles = parse_wave.bfs_step(pending, visited, {})
59
+ self.assertEqual(cycles, [])
60
+ for k in parse_wave._BFS_KINDS:
61
+ self.assertEqual(merged[k], set())
62
+
63
+ def test_unknown_kind_raises(self):
64
+ """unknown BFS kinds raise ValueError — bfs_step is an
65
+ internal API and a typo in a caller is a programming error, not a
66
+ runtime condition we can silently paper over. Silent-drop produced
67
+ false confidence: the ref was never fetched and no log mentioned it.
68
+ """
69
+ pending = {k: set() for k in parse_wave._BFS_KINDS}
70
+ visited = {k: set() for k in parse_wave._BFS_KINDS}
71
+ new_refs = {"NOT_A_KIND": {"X"}}
72
+
73
+ with self.assertRaises(ValueError) as ctx:
74
+ parse_wave.bfs_step(pending, visited, new_refs)
75
+
76
+ self.assertIn("unknown BFS kind", ctx.exception.args[0])
77
+
78
+
79
+ class InflateCycleDetectionTests(unittest.TestCase):
80
+ """`inflate_flow_leaf` must terminate on self-cycles and annotate
81
+ `_cycle_back_to` on the repeat node."""
82
+
83
+ def test_self_cycle_flow_a_calls_subflow_a(self):
84
+ """Flow A → subflow A — one real node + a stub child with cycle tag."""
85
+ flow_children = {
86
+ "A": [
87
+ {"kind": "FLOW", "element_name": "recurse", "api_name": "A"},
88
+ ],
89
+ }
90
+ leaf = {"kind": "FLOW", "api_name": "A", "children": []}
91
+
92
+ parse_wave.inflate_flow_leaf(leaf, flow_children)
93
+
94
+ self.assertEqual(len(leaf["children"]), 1)
95
+ child = leaf["children"][0]
96
+ self.assertEqual(child["kind"], "FLOW")
97
+ self.assertEqual(child["api_name"], "A")
98
+ self.assertEqual(child.get("_cycle_back_to"), "FLOW:A")
99
+ # Must NOT have expanded children on the cycle stub.
100
+ self.assertEqual(child.get("children", []), [])
101
+
102
+ def test_cross_type_cycle_flow_a_to_apex_b_to_flow_a(self):
103
+ """Flow A → Apex B (leaf) → nothing (Apex doesn't recurse in inflate).
104
+
105
+ The inflate layer only descends through FLOW→FLOW. A cycle through
106
+ Apex would be caught at the BFS layer (bfs_step), not here. This
107
+ test confirms the inflate layer doesn't misclassify a FLOW→APEX
108
+ edge as a cycle and doesn't over-recurse on it.
109
+ """
110
+ flow_children = {
111
+ "A": [
112
+ {"kind": "APEX", "element_name": "callApex", "api_name": "B"},
113
+ ],
114
+ }
115
+ leaf = {"kind": "FLOW", "api_name": "A", "children": []}
116
+
117
+ parse_wave.inflate_flow_leaf(leaf, flow_children)
118
+
119
+ self.assertEqual(len(leaf["children"]), 1)
120
+ self.assertEqual(leaf["children"][0]["kind"], "APEX")
121
+ self.assertNotIn("_cycle_back_to", leaf["children"][0])
122
+
123
+ def test_three_layer_linear_graph_fully_expanded(self):
124
+ """A → B → C — each Flow visited once, no false cycle tags."""
125
+ flow_children = {
126
+ "A": [{"kind": "FLOW", "element_name": "callB", "api_name": "B"}],
127
+ "B": [{"kind": "FLOW", "element_name": "callC", "api_name": "C"}],
128
+ "C": [],
129
+ }
130
+ leaf = {"kind": "FLOW", "api_name": "A", "children": []}
131
+
132
+ parse_wave.inflate_flow_leaf(leaf, flow_children)
133
+
134
+ # A has one child: B
135
+ self.assertEqual(len(leaf["children"]), 1)
136
+ b = leaf["children"][0]
137
+ self.assertEqual(b["api_name"], "B")
138
+ self.assertNotIn("_cycle_back_to", b)
139
+ # B has one child: C
140
+ self.assertEqual(len(b["children"]), 1)
141
+ c = b["children"][0]
142
+ self.assertEqual(c["api_name"], "C")
143
+ self.assertNotIn("_cycle_back_to", c)
144
+ # C has no children (empty list in flow_children)
145
+ self.assertEqual(c.get("children", []), [])
146
+
147
+ def test_sibling_flows_sharing_target_not_cross_contaminated(self):
148
+ """If Flow X is called by two different siblings, both sibling
149
+ branches must fully expand X — not prune the second as a cycle.
150
+
151
+ Frozenset path-visited semantics protect this: siblings don't see
152
+ each other's descent. A mutable shared set would cause the second
153
+ sibling to treat X as a cycle.
154
+ """
155
+ flow_children = {
156
+ "Root": [
157
+ {"kind": "FLOW", "element_name": "a", "api_name": "Sib1"},
158
+ {"kind": "FLOW", "element_name": "b", "api_name": "Sib2"},
159
+ ],
160
+ "Sib1": [{"kind": "FLOW", "element_name": "shared", "api_name": "Shared"}],
161
+ "Sib2": [{"kind": "FLOW", "element_name": "shared", "api_name": "Shared"}],
162
+ "Shared": [],
163
+ }
164
+ leaf = {"kind": "FLOW", "api_name": "Root", "children": []}
165
+
166
+ parse_wave.inflate_flow_leaf(leaf, flow_children)
167
+
168
+ self.assertEqual(len(leaf["children"]), 2)
169
+ for sib in leaf["children"]:
170
+ # Each sibling must have Shared as a fully-expanded child —
171
+ # NOT a cycle stub.
172
+ self.assertEqual(len(sib["children"]), 1)
173
+ shared = sib["children"][0]
174
+ self.assertEqual(shared["api_name"], "Shared")
175
+ self.assertNotIn("_cycle_back_to", shared)
176
+
177
+ def test_empty_flow_children_returns_immediately(self):
178
+ """No data for this leaf → preserve existing children (no-op)."""
179
+ leaf = {"kind": "FLOW", "api_name": "X", "children": [{"kind": "APEX", "api_name": "Y"}]}
180
+ parse_wave.inflate_flow_leaf(leaf, {}) # empty map
181
+ # Preserved unchanged
182
+ self.assertEqual(leaf["children"], [{"kind": "APEX", "api_name": "Y"}])
183
+
184
+ def test_non_flow_leaf_ignored(self):
185
+ """Only FLOW kind is inflated."""
186
+ leaf = {"kind": "APEX", "api_name": "X"}
187
+ parse_wave.inflate_flow_leaf(leaf, {"X": [{"kind": "FLOW", "api_name": "Z"}]})
188
+ self.assertNotIn("children", leaf)
189
+
190
+
191
+ class CycleKeyTests(unittest.TestCase):
192
+ """Unit test for the tuple-key helper — safety net on schema drift."""
193
+
194
+ def test_tuple_shape(self):
195
+ self.assertEqual(
196
+ parse_wave._cycle_key({"kind": "FLOW", "api_name": "Foo"}),
197
+ ("FLOW", "Foo"),
198
+ )
199
+
200
+ def test_missing_kind_or_name_safe(self):
201
+ self.assertEqual(parse_wave._cycle_key({}), ("", ""))
202
+
203
+
204
+ def _linear_flow_chain(names: list[str]) -> dict:
205
+ """Build a flow_children graph: FLOW[i] → FLOW[i+1], last flow has no kids.
206
+
207
+ Returns the `flow_children` mapping that inflate_flow_leaf consumes.
208
+ """
209
+ graph: dict = {}
210
+ for i, n in enumerate(names):
211
+ if i + 1 < len(names):
212
+ graph[n] = [
213
+ {"kind": "FLOW", "element_name": f"call_{names[i+1]}",
214
+ "api_name": names[i+1]},
215
+ ]
216
+ else:
217
+ graph[n] = []
218
+ return graph
219
+
220
+
221
+ class DepthCapPartialTests(unittest.TestCase):
222
+ """`MAX_BFS_DEPTH` is a defensive guard,
223
+ not a functional chain-depth limit. Fixtures here push past the cap
224
+ to prove it still terminates and still populates `pending_out` when
225
+ it trips. Cycle semantics live in per-branch ancestor tracking and
226
+ are covered by `test_per_branch_visited.py`.
227
+ """
228
+
229
+ def _deep_linear_names(self, n: int) -> list[str]:
230
+ return [f"F{i}" for i in range(1, n + 1)]
231
+
232
+ def test_chain_under_cap_fully_expanded(self):
233
+ """A linear chain shorter than `MAX_BFS_DEPTH` fully expands with
234
+ no pending. Prior to the revision this used a 5-flow chain; with
235
+ a defensive cap of 20, 5 is trivially under-cap but the shape of
236
+ the assertion is unchanged.
237
+ """
238
+ names = ["F1", "F2", "F3", "F4", "F5"]
239
+ graph = _linear_flow_chain(names)
240
+ leaf = {"kind": "FLOW", "api_name": "F1", "children": []}
241
+ pending: dict[str, set[str]] = {k: set() for k in parse_wave._BFS_KINDS}
242
+
243
+ parse_wave.inflate_flow_leaf(leaf, graph, pending_out=pending)
244
+
245
+ node = leaf
246
+ for expected in names[1:]:
247
+ kids = node.get("children", [])
248
+ self.assertEqual(len(kids), 1, f"expected one child under {node['api_name']}")
249
+ self.assertEqual(kids[0]["api_name"], expected)
250
+ node = kids[0]
251
+ self.assertEqual(node.get("children", []), [])
252
+
253
+ for k in parse_wave._BFS_KINDS:
254
+ self.assertEqual(pending[k], set())
255
+
256
+ def test_chain_at_exact_cap_trips_and_records_pending(self):
257
+ """A linear chain of MAX_BFS_DEPTH+1 unique flows trips the
258
+ defensive cap: the last flow must NOT be expanded AND must land
259
+ in `pending_out["FLOW"]`. This is the only functional invariant
260
+ of the cap after the revision.
261
+ """
262
+ n = parse_wave.MAX_BFS_DEPTH + 1
263
+ names = self._deep_linear_names(n)
264
+ graph = _linear_flow_chain(names)
265
+ leaf = {"kind": "FLOW", "api_name": names[0], "children": []}
266
+ pending: dict[str, set[str]] = {k: set() for k in parse_wave._BFS_KINDS}
267
+
268
+ parse_wave.inflate_flow_leaf(leaf, graph, pending_out=pending)
269
+
270
+ # Walk down to the flow at `MAX_BFS_DEPTH - 1` index (last one
271
+ # before the cap trips on its child). All prior flows must have
272
+ # been fully expanded.
273
+ node = leaf
274
+ for expected in names[1:parse_wave.MAX_BFS_DEPTH]:
275
+ kids = node.get("children", [])
276
+ self.assertEqual(len(kids), 1)
277
+ self.assertEqual(kids[0]["api_name"], expected)
278
+ node = kids[0]
279
+
280
+ # The last flow in the chain (the (MAX_BFS_DEPTH+1)-th) must be
281
+ # in pending_out.
282
+ self.assertIn(names[-1], pending["FLOW"])
283
+
284
+ def test_deep_chain_with_tail_self_loop_terminates_via_cycle_detection(self):
285
+ """Long chain with a self-loop at the tail terminates via
286
+ per-branch cycle detection (the revised primitive), NOT the
287
+ defensive cap. The self-loop flow appears once with children
288
+ expanded, its self-reference annotated `_cycle_back_to`.
289
+ """
290
+ # Chain of 7 unique flows, each pointing to the next, then the
291
+ # last one points at itself — 7 is well under the cap of 20 so
292
+ # this proves cycle detection, not depth, terminates the walk.
293
+ names = ["F1", "F2", "F3", "F4", "F5", "F6", "F7"]
294
+ graph = _linear_flow_chain(names)
295
+ # Rewrite F7 to self-loop instead of empty.
296
+ graph["F7"] = [{"kind": "FLOW", "api_name": "F7", "element_name": "self"}]
297
+
298
+ leaf = {"kind": "FLOW", "api_name": "F1", "children": []}
299
+ pending: dict[str, set[str]] = {k: set() for k in parse_wave._BFS_KINDS}
300
+
301
+ # Must not hang / recurse infinitely.
302
+ parse_wave.inflate_flow_leaf(leaf, graph, pending_out=pending)
303
+
304
+ # Walk to F7.
305
+ node = leaf
306
+ for expected in names[1:]:
307
+ node = node["children"][0]
308
+ self.assertEqual(node["api_name"], expected)
309
+
310
+ # F7 has ONE child — the cycle-annotated self-reference.
311
+ f7_kids = node.get("children", [])
312
+ self.assertEqual(len(f7_kids), 1)
313
+ self.assertEqual(f7_kids[0]["api_name"], "F7")
314
+ self.assertEqual(f7_kids[0]["_cycle_back_to"], "FLOW:F7")
315
+ self.assertEqual(
316
+ f7_kids[0]["_truncated"],
317
+ {"reason": "cycle", "target": "FLOW:F7"},
318
+ )
319
+
320
+ # Nothing in pending — cycle is annotated, not pended.
321
+ self.assertEqual(pending["FLOW"], set())
322
+
323
+ def test_chain_under_cap_with_legacy_pending_none(self):
324
+ """Callers pre-dating pass no `pending_out`. An under-cap
325
+ chain still expands fully — no crash on `None`, no spurious
326
+ truncation. The pre-revision form of this test asserted cap=5
327
+ behavior; now it asserts that a short chain expands cleanly when
328
+ the cap plays no role.
329
+ """
330
+ names = ["F1", "F2", "F3", "F4", "F5", "F6"]
331
+ graph = _linear_flow_chain(names)
332
+ leaf = {"kind": "FLOW", "api_name": "F1", "children": []}
333
+
334
+ # No pending_out — legacy call shape. Must not crash.
335
+ parse_wave.inflate_flow_leaf(leaf, graph)
336
+
337
+ # All flows expanded to the terminal leaf.
338
+ node = leaf
339
+ for expected in names[1:]:
340
+ kids = node.get("children", [])
341
+ self.assertEqual(len(kids), 1, f"{node['api_name']} should have one child")
342
+ self.assertEqual(kids[0]["api_name"], expected)
343
+ node = kids[0]
344
+ self.assertEqual(node.get("children", []), [])
345
+
346
+ def test_partial_and_unresolved_coexist(self):
347
+ """_unresolved + _partial-reason must both be reported.
348
+
349
+ Simulate: finalize_cap drains some pending into _unresolved, but
350
+ depth-cap already set _partial_reason=max-depth-cap. Finalize must
351
+ NOT clobber the depth-cap reason.
352
+ """
353
+ tree = {
354
+ "_partial": True,
355
+ "_partial_reason": "max-depth-cap",
356
+ "_pending_fetches": {
357
+ "FLOW": ["LateFlow"],
358
+ "APEX": [],
359
+ "PROMPT_TEMPLATE": [],
360
+ "STANDARD_ACTION": [],
361
+ },
362
+ "_unresolved": [{"kind": "APEX", "api_name": "PriorUnresolved",
363
+ "reason": "resolve_invocation_target failed"}],
364
+ }
365
+
366
+ parse_wave.finalize_cap(tree)
367
+
368
+ # Pending drained into unresolved.
369
+ self.assertEqual(tree["_pending_fetches"]["FLOW"], [])
370
+ self.assertEqual(len(tree["_unresolved"]), 2)
371
+ # Depth-cap reason preserved (NOT overwritten by max-wave-depth).
372
+ self.assertEqual(tree["_partial_reason"], "max-depth-cap")
373
+ self.assertTrue(tree["_partial"])
374
+
375
+
376
+ class MaxBfsDepthConstantTests(unittest.TestCase):
377
+ """`MAX_BFS_DEPTH` is a defensive guard,
378
+ not a functional chain-depth limit. The constant must match
379
+ `scripts/config.py::MAX_BFS_DEPTH` exactly (duplicated intentionally
380
+ — see comment there on the "no intra-skill imports" convention).
381
+ """
382
+
383
+ def test_max_bfs_depth_matches_config(self):
384
+ """Cross-module consistency. If someone bumps one literal they
385
+ must bump the other."""
386
+ import config # type: ignore
387
+ self.assertEqual(parse_wave.MAX_BFS_DEPTH, config.MAX_BFS_DEPTH)
388
+
389
+ def test_max_bfs_depth_is_defensive_not_functional(self):
390
+ """The cap should be comfortably larger than any realistic
391
+ production flow-chain depth, so that per-branch cycle detection
392
+ — not the cap — terminates the walk in practice. `>= 15` is a
393
+ loose sanity bound; if someone quietly tightens it below 15
394
+ they're probably reintroducing the shared-utility-flow bug."""
395
+ self.assertGreaterEqual(parse_wave.MAX_BFS_DEPTH, 15)
396
+
397
+ def test_legacy_alias_tracks_new_constant(self):
398
+ """MAX_INFLATE_DEPTH is retained as an alias."""
399
+ self.assertEqual(parse_wave.MAX_INFLATE_DEPTH, parse_wave.MAX_BFS_DEPTH)
400
+
401
+
402
+ class PublicSymbolPromotionTests(unittest.TestCase):
403
+ """`_BFS_KINDS` and `_empty_kind_sets` were
404
+ promoted to public names (`BFS_KINDS`, `empty_kind_sets`) so the
405
+ in-process `main.py` orchestrator doesn't have to reach across the
406
+ leading-underscore boundary that closed for `redact_text`.
407
+ The underscore forms remain as deprecated aliases for one more minor
408
+ version.
409
+ """
410
+
411
+ def test_public_bfs_kinds_importable(self):
412
+ """`parse_wave.BFS_KINDS` exists, is a tuple, and carries the
413
+ expected four tokens in the same order as before promotion.
414
+ """
415
+ self.assertTrue(hasattr(parse_wave, "BFS_KINDS"))
416
+ self.assertIsInstance(parse_wave.BFS_KINDS, tuple)
417
+ self.assertEqual(
418
+ parse_wave.BFS_KINDS,
419
+ ("FLOW", "APEX", "PROMPT_TEMPLATE", "STANDARD_ACTION"),
420
+ )
421
+
422
+ def test_public_empty_kind_sets_importable_and_callable(self):
423
+ """`parse_wave.empty_kind_sets()` returns a fresh {kind: set()}
424
+ mapping keyed by every BFS_KINDS entry. Each call yields an
425
+ independent dict — callers must not share buckets across waves.
426
+ """
427
+ self.assertTrue(hasattr(parse_wave, "empty_kind_sets"))
428
+ d1 = parse_wave.empty_kind_sets()
429
+ d2 = parse_wave.empty_kind_sets()
430
+ self.assertEqual(set(d1.keys()), set(parse_wave.BFS_KINDS))
431
+ for kind in parse_wave.BFS_KINDS:
432
+ self.assertEqual(d1[kind], set())
433
+ # Independence: mutating one must not affect the other.
434
+ d1["FLOW"].add("X")
435
+ self.assertEqual(d2["FLOW"], set())
436
+
437
+ def test_underscore_aliases_still_work_for_backcompat(self):
438
+ """Legacy `_BFS_KINDS` / `_empty_kind_sets` imports continue to
439
+ resolve. They are deprecated but not yet removed — existing
440
+ tests and any external callers must keep working through the
441
+ migration window.
442
+ """
443
+ self.assertTrue(hasattr(parse_wave, "_BFS_KINDS"))
444
+ self.assertTrue(hasattr(parse_wave, "_empty_kind_sets"))
445
+ # Same object (alias, not a copy) so equality holds structurally.
446
+ self.assertIs(parse_wave._BFS_KINDS, parse_wave.BFS_KINDS)
447
+ self.assertIs(parse_wave._empty_kind_sets, parse_wave.empty_kind_sets)
448
+
449
+
450
+ class FetchableKindsTests(unittest.TestCase):
451
+ """STANDARD_ACTION is declared-only, never fetched.
452
+ Must stay out of `_pending_fetches` even though it remains a BFS kind
453
+ for tree counts + visited dedup."""
454
+
455
+ def test_fetchable_kinds_excludes_standard_action(self):
456
+ """FETCHABLE_KINDS = (FLOW, APEX, PROMPT_TEMPLATE). STANDARD_ACTION
457
+ must be in BFS_KINDS (tree bookkeeping) but NOT in
458
+ FETCHABLE_KINDS (body-fetch eligibility)."""
459
+ self.assertIn("STANDARD_ACTION", parse_wave.BFS_KINDS)
460
+ self.assertNotIn("STANDARD_ACTION", parse_wave.FETCHABLE_KINDS)
461
+ self.assertEqual(
462
+ parse_wave.FETCHABLE_KINDS,
463
+ ("FLOW", "APEX", "PROMPT_TEMPLATE"),
464
+ )
465
+
466
+ def test_build_root_children_skips_standard_action_refs(self):
467
+ """A GenAiFunction whose unwrap is STANDARD_ACTION must NOT land
468
+ in new_refs. Before this fix, `streamKnowledgeSearch` landed in
469
+ `new_refs["STANDARD_ACTION"]` → merged into pending → never
470
+ visited → surfaced in _pending_fetches (pollution)."""
471
+ bundle = {
472
+ "topics": [{
473
+ "name": "T",
474
+ "actions": [{
475
+ "name": "A1",
476
+ "invocationTarget": "streamKnowledgeSearch",
477
+ "invocationTargetType": "standardInvocableAction",
478
+ }],
479
+ }],
480
+ "plannerActions": [],
481
+ }
482
+ visited = parse_wave.empty_kind_sets()
483
+ aux_visited: set = set()
484
+ children, new_refs = parse_wave.build_root_children(
485
+ bundle, visited, aux_visited,
486
+ )
487
+ # Children list is built normally — tree still has the leaf.
488
+ self.assertEqual(len(children), 1)
489
+ # But new_refs["STANDARD_ACTION"] is empty → nothing to pend on.
490
+ self.assertEqual(new_refs["STANDARD_ACTION"], set())
491
+ self.assertEqual(new_refs["FLOW"], set())
492
+ self.assertEqual(new_refs["APEX"], set())
493
+ self.assertEqual(new_refs["PROMPT_TEMPLATE"], set())
494
+
495
+ def test_build_root_children_still_captures_flow_apex_prompt_refs(self):
496
+ """Positive control — fetchable kinds still accumulate into
497
+ new_refs. The FOLLOWUP-2 gate is on STANDARD_ACTION only."""
498
+ bundle = {
499
+ "topics": [{
500
+ "name": "T",
501
+ "actions": [
502
+ {"name": "F1", "invocationTarget": "FlowA",
503
+ "invocationTargetType": "flow"},
504
+ {"name": "A1", "invocationTarget": "ApexA",
505
+ "invocationTargetType": "apex"},
506
+ {"name": "P1", "invocationTarget": "PromptA",
507
+ "invocationTargetType": "generatePromptResponse"},
508
+ ],
509
+ }],
510
+ "plannerActions": [],
511
+ }
512
+ visited = parse_wave.empty_kind_sets()
513
+ aux_visited: set = set()
514
+ _, new_refs = parse_wave.build_root_children(
515
+ bundle, visited, aux_visited,
516
+ )
517
+ self.assertIn("FlowA", new_refs["FLOW"])
518
+ self.assertIn("ApexA", new_refs["APEX"])
519
+ self.assertIn("PromptA", new_refs["PROMPT_TEMPLATE"])
520
+ self.assertEqual(new_refs["STANDARD_ACTION"], set())
521
+
522
+
523
+ class UnifiedTruncationAnnotationTests(unittest.TestCase):
524
+ """both cycle and max-depth code paths annotate truncated
525
+ nodes with the unified `_truncated = {reason, target}` sub-object.
526
+ `_cycle_back_to` is preserved as a deprecated alias (backcompat)."""
527
+
528
+ def test_truncation_constants_are_exposed(self):
529
+ """Public constants let consumers match on strings without
530
+ duplicating the literal values."""
531
+ self.assertEqual(parse_wave.TRUNCATION_CYCLE, "cycle")
532
+ self.assertEqual(parse_wave.TRUNCATION_MAX_DEPTH, "max-depth")
533
+ self.assertIn(
534
+ parse_wave.TRUNCATION_CYCLE, parse_wave.TRUNCATION_REASONS,
535
+ )
536
+ self.assertIn(
537
+ parse_wave.TRUNCATION_MAX_DEPTH, parse_wave.TRUNCATION_REASONS,
538
+ )
539
+
540
+ def test_cycle_emits_unified_truncated_and_legacy_alias(self):
541
+ """Cycle annotations carry BOTH the unified `_truncated` and the
542
+ legacy `_cycle_back_to` string (same target). Consumers that
543
+ haven't migrated still work; new consumers read one field."""
544
+ flow_children = {
545
+ # A simple A → A self-cycle inside one expansion.
546
+ "A": [{"kind": "FLOW", "api_name": "A", "element_name": "loop"}],
547
+ }
548
+ leaf = {"kind": "FLOW", "api_name": "A", "children": []}
549
+ parse_wave.inflate_flow_leaf(leaf, flow_children)
550
+
551
+ # Find the cycle-annotated child (second encounter of A).
552
+ kids = leaf.get("children") or []
553
+ self.assertEqual(len(kids), 1)
554
+ cycle_node = kids[0]
555
+
556
+ # Unified annotation present.
557
+ self.assertIn("_truncated", cycle_node)
558
+ self.assertEqual(
559
+ cycle_node["_truncated"],
560
+ {"reason": "cycle", "target": "FLOW:A"},
561
+ )
562
+
563
+ # Legacy annotation still present for backcompat.
564
+ self.assertEqual(cycle_node["_cycle_back_to"], "FLOW:A")
565
+
566
+ def test_depth_cap_emits_unified_truncated_on_leaf(self):
567
+ """When MAX_BFS_DEPTH trips, the truncated FLOW leaf picks up
568
+ `_truncated = {reason: 'max-depth', target: 'FLOW:<name>'}`.
569
+ Before the leaf carried no per-node signal at all — only
570
+ the tree-level `_partial_reason` and the `pending_out`
571
+ accumulator identified it.
572
+
573
+ 2026-05-03: since the cap was bumped from 5 to 20 (defensive
574
+ guard, not functional limit), the chain here is built
575
+ programmatically at `MAX_BFS_DEPTH + 1` unique flows so the
576
+ test tracks the constant instead of hard-coding chain length.
577
+ """
578
+ n = parse_wave.MAX_BFS_DEPTH + 1 # one past the cap
579
+ names = [f"F{i}" for i in range(n)]
580
+ flow_children: dict[str, list[dict]] = {}
581
+ for i, name in enumerate(names):
582
+ if i + 1 < n:
583
+ flow_children[name] = [{
584
+ "kind": "FLOW",
585
+ "api_name": names[i + 1],
586
+ "element_name": "sub",
587
+ }]
588
+ else:
589
+ flow_children[name] = []
590
+
591
+ root = {"kind": "FLOW", "api_name": names[0], "children": []}
592
+ pending_out = parse_wave.empty_kind_sets()
593
+ parse_wave.inflate_flow_leaf(root, flow_children, pending_out=pending_out)
594
+
595
+ # Walk down the chain to the terminal (unreached) flow.
596
+ def walk(n, name):
597
+ if n.get("api_name") == name:
598
+ return n
599
+ for c in n.get("children") or []:
600
+ found = walk(c, name)
601
+ if found:
602
+ return found
603
+ return None
604
+
605
+ tail = walk(root, names[-1])
606
+ self.assertIsNotNone(tail, f"{names[-1]} should appear as a leaf in the tree")
607
+
608
+ # Unified annotation on the depth-capped leaf.
609
+ self.assertIn("_truncated", tail)
610
+ self.assertEqual(
611
+ tail["_truncated"],
612
+ {"reason": "max-depth", "target": f"FLOW:{names[-1]}"},
613
+ )
614
+
615
+ # The capped leaf should NOT also carry _cycle_back_to — this
616
+ # is a depth-cap, not a cycle.
617
+ self.assertNotIn("_cycle_back_to", tail)
618
+
619
+ # pending_out still picks up the unreached name — existing
620
+ # contract preserved.
621
+ self.assertIn(names[-1], pending_out["FLOW"])
622
+
623
+ def test_non_flow_leaf_depth_cap_no_annotation(self):
624
+ """The cap is scoped to FLOW leaves. Non-FLOW leaves at
625
+ cap depth are just no-op returns — no spurious `_truncated`."""
626
+ flow_children = {
627
+ "X": [{"kind": "APEX", "api_name": "SomeApex"}],
628
+ }
629
+ # Build an APEX leaf that we try to "inflate" at depth = MAX.
630
+ # inflate_flow_leaf with a non-FLOW leaf at depth >= MAX does
631
+ # nothing and emits no annotation.
632
+ leaf = {"kind": "APEX", "api_name": "SomeApex"}
633
+ pending_out = parse_wave.empty_kind_sets()
634
+ parse_wave.inflate_flow_leaf(
635
+ leaf, flow_children,
636
+ depth=parse_wave.MAX_BFS_DEPTH,
637
+ pending_out=pending_out,
638
+ )
639
+ self.assertNotIn("_truncated", leaf)
640
+ self.assertEqual(pending_out["FLOW"], set())
641
+
642
+
643
+ if __name__ == "__main__":
644
+ unittest.main()