@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,359 @@
1
+ """Tests for channel probe TTL (7d), mandatory-field gate,
2
+ runtime invalidate + re-probe.
3
+
4
+ Every test mocks `sf_cli.run_sf` — no real org calls. Cache dir is
5
+ redirected into a tempdir via monkey-patching `config.PROBE_CACHE_ROOT`
6
+ to keep tests hermetic.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import os
12
+ import stat
13
+ import sys
14
+ import tempfile
15
+ import time
16
+ import unittest
17
+ import unittest.mock as mock
18
+ from pathlib import Path
19
+
20
+ from . import _bootstrap # noqa: F401
21
+
22
+ import config # type: ignore
23
+ import probe_channels # type: ignore
24
+
25
+
26
+ def _fake_describe_ok(fields: list[str]) -> dict:
27
+ """Shape the sf_cli.run_sf output: success envelope wrapping a fields list."""
28
+ return {
29
+ "status": 0,
30
+ "result": {
31
+ "fields": [{"name": f, "queryable": True} for f in fields],
32
+ },
33
+ }
34
+
35
+
36
+ # A "universal" success payload: every mandatory field for every sObject
37
+ # present. Individual tests override for specific sObjects.
38
+ def _all_fields_ok_run_sf(recipe: str, **params: str) -> dict:
39
+ sobject = params["SOBJECT"]
40
+ mandatory = probe_channels.MANDATORY_FIELDS.get(sobject, set())
41
+ # Add a couple of bonus fields so queryable_fields isn't identical to
42
+ # mandatory_missing-inverse by construction.
43
+ extra = {"CreatedDate", "LastModifiedDate"}
44
+ return _fake_describe_ok(sorted(mandatory | extra))
45
+
46
+
47
+ class ProbeChannelsCacheTests(unittest.TestCase):
48
+ """Cache hit / miss / TTL semantics."""
49
+
50
+ def setUp(self) -> None:
51
+ self._tmp = tempfile.TemporaryDirectory()
52
+ self.addCleanup(self._tmp.cleanup)
53
+ self._orig_root = config.PROBE_CACHE_ROOT
54
+ config.PROBE_CACHE_ROOT = Path(self._tmp.name) / "_channel_probe"
55
+
56
+ def tearDown(self) -> None:
57
+ config.PROBE_CACHE_ROOT = self._orig_root
58
+
59
+ def test_cache_hit_within_ttl_no_network(self):
60
+ """A fresh cache file + force_refresh=False → no run_sf calls."""
61
+ cache_dir = config.build_probe_cache_dir("00D000000000ABC", "v60.0")
62
+ cache_dir.mkdir(parents=True, exist_ok=True)
63
+ cache_file = cache_dir / "channels.json"
64
+ payload = {
65
+ "_schema": "channels/1",
66
+ "_built_at_utc": time.time(), # right now → fresh
67
+ "status": "OK",
68
+ "channels": {"BotDefinition": {"queryable_fields": ["Id"],
69
+ "mandatory_missing": [],
70
+ "describe_error": None}},
71
+ }
72
+ cache_file.write_text(json.dumps(payload))
73
+
74
+ with mock.patch.object(probe_channels, "run_sf") as m:
75
+ out = probe_channels.probe_channels(
76
+ "myorg", "00D000000000ABC", "v60.0", force_refresh=False,
77
+ )
78
+ self.assertEqual(out["status"], "OK")
79
+ m.assert_not_called()
80
+
81
+ def test_cache_stale_triggers_reprobe(self):
82
+ """Cached file older than TTL → re-run describes."""
83
+ cache_dir = config.build_probe_cache_dir("00D000000000ABC", "v60.0")
84
+ cache_dir.mkdir(parents=True, exist_ok=True)
85
+ cache_file = cache_dir / "channels.json"
86
+ stale = {
87
+ "_schema": "channels/1",
88
+ "_built_at_utc": time.time() - (8 * 86400), # 8 days old
89
+ "status": "OK",
90
+ "channels": {},
91
+ }
92
+ cache_file.write_text(json.dumps(stale))
93
+
94
+ with mock.patch.object(probe_channels, "run_sf",
95
+ side_effect=_all_fields_ok_run_sf) as m:
96
+ out = probe_channels.probe_channels(
97
+ "myorg", "00D000000000ABC", "v60.0", force_refresh=False,
98
+ )
99
+ self.assertEqual(out["status"], "OK")
100
+ # One call per sObject.
101
+ self.assertEqual(m.call_count, len(probe_channels.ALL_SOBJECTS))
102
+
103
+ def test_force_refresh_always_reprobes(self):
104
+ """force_refresh=True bypasses a fresh cache."""
105
+ cache_dir = config.build_probe_cache_dir("00D000000000ABC", "v60.0")
106
+ cache_dir.mkdir(parents=True, exist_ok=True)
107
+ cache_file = cache_dir / "channels.json"
108
+ fresh = {
109
+ "_schema": "channels/1",
110
+ "_built_at_utc": time.time(),
111
+ "status": "OK",
112
+ "channels": {},
113
+ }
114
+ cache_file.write_text(json.dumps(fresh))
115
+
116
+ with mock.patch.object(probe_channels, "run_sf",
117
+ side_effect=_all_fields_ok_run_sf) as m:
118
+ probe_channels.probe_channels(
119
+ "myorg", "00D000000000ABC", "v60.0", force_refresh=True,
120
+ )
121
+ self.assertEqual(m.call_count, len(probe_channels.ALL_SOBJECTS))
122
+
123
+
124
+ class InvalidateAndReprobeTests(unittest.TestCase):
125
+ """runtime escape valve when SOQL returns INVALID_FIELD."""
126
+
127
+ def setUp(self) -> None:
128
+ self._tmp = tempfile.TemporaryDirectory()
129
+ self.addCleanup(self._tmp.cleanup)
130
+ self._orig_root = config.PROBE_CACHE_ROOT
131
+ config.PROBE_CACHE_ROOT = Path(self._tmp.name) / "_channel_probe"
132
+
133
+ def tearDown(self) -> None:
134
+ config.PROBE_CACHE_ROOT = self._orig_root
135
+
136
+ def test_invalidate_deletes_cache_and_reruns(self):
137
+ cache_dir = config.build_probe_cache_dir("00D000000000ABC", "v60.0")
138
+ cache_dir.mkdir(parents=True, exist_ok=True)
139
+ cache_file = cache_dir / "channels.json"
140
+ cache_file.write_text(json.dumps({
141
+ "_schema": "channels/1",
142
+ "_built_at_utc": time.time(),
143
+ "status": "OK",
144
+ "channels": {},
145
+ }))
146
+
147
+ with mock.patch.object(probe_channels, "run_sf",
148
+ side_effect=_all_fields_ok_run_sf) as m:
149
+ out = probe_channels.invalidate_and_reprobe(
150
+ "myorg", "00D000000000ABC", "v60.0",
151
+ )
152
+
153
+ self.assertEqual(out["status"], "OK")
154
+ self.assertTrue(cache_file.is_file(), "fresh cache must be rewritten")
155
+ self.assertEqual(m.call_count, len(probe_channels.ALL_SOBJECTS))
156
+
157
+ def test_invalidate_tolerates_missing_cache(self):
158
+ """Cold-path invalidate (no file yet) must not crash."""
159
+ with mock.patch.object(probe_channels, "run_sf",
160
+ side_effect=_all_fields_ok_run_sf):
161
+ out = probe_channels.invalidate_and_reprobe(
162
+ "myorg", "00D000000000ABC", "v60.0",
163
+ )
164
+ self.assertEqual(out["status"], "OK")
165
+
166
+
167
+ class MandatoryFieldGateTests(unittest.TestCase):
168
+ """Missing mandatory field → status flips to PROBE_FAILED."""
169
+
170
+ def setUp(self) -> None:
171
+ self._tmp = tempfile.TemporaryDirectory()
172
+ self.addCleanup(self._tmp.cleanup)
173
+ self._orig_root = config.PROBE_CACHE_ROOT
174
+ config.PROBE_CACHE_ROOT = Path(self._tmp.name) / "_channel_probe"
175
+
176
+ def tearDown(self) -> None:
177
+ config.PROBE_CACHE_ROOT = self._orig_root
178
+
179
+ def test_missing_invocation_target_flags_probe_failed(self):
180
+ """SF release drops `InvocationTarget` on
181
+ GenAiFunctionDefinition → probe must surface PROBE_FAILED.
182
+ """
183
+
184
+ def mock_run_sf(recipe: str, **params: str) -> dict:
185
+ sobject = params["SOBJECT"]
186
+ mandatory = set(probe_channels.MANDATORY_FIELDS.get(sobject, set()))
187
+ if sobject == "GenAiFunctionDefinition":
188
+ # Simulate the schema drift: drop InvocationTarget.
189
+ mandatory.discard("InvocationTarget")
190
+ return _fake_describe_ok(sorted(mandatory))
191
+
192
+ with mock.patch.object(probe_channels, "run_sf", side_effect=mock_run_sf):
193
+ out = probe_channels.probe_channels(
194
+ "myorg", "00D000000000ABC", "v60.0", force_refresh=True,
195
+ )
196
+
197
+ self.assertEqual(out["status"], "PROBE_FAILED")
198
+ entry = out["channels"]["GenAiFunctionDefinition"]
199
+ self.assertIn("InvocationTarget", entry["mandatory_missing"])
200
+ # Other sObjects unaffected.
201
+ self.assertEqual(
202
+ out["channels"]["BotDefinition"]["mandatory_missing"], [],
203
+ )
204
+
205
+
206
+ class CacheFilePermissionsTests(unittest.TestCase):
207
+ """cached channels.json must be 0o600 (owner RW only)."""
208
+
209
+ def setUp(self) -> None:
210
+ self._tmp = tempfile.TemporaryDirectory()
211
+ self.addCleanup(self._tmp.cleanup)
212
+ self._orig_root = config.PROBE_CACHE_ROOT
213
+ config.PROBE_CACHE_ROOT = Path(self._tmp.name) / "_channel_probe"
214
+
215
+ def tearDown(self) -> None:
216
+ config.PROBE_CACHE_ROOT = self._orig_root
217
+
218
+ def test_cache_file_is_mode_0o600(self):
219
+ with mock.patch.object(probe_channels, "run_sf",
220
+ side_effect=_all_fields_ok_run_sf):
221
+ probe_channels.probe_channels(
222
+ "myorg", "00D000000000ABC", "v60.0", force_refresh=True,
223
+ )
224
+ cache_file = (
225
+ config.build_probe_cache_dir("00D000000000ABC", "v60.0")
226
+ / "channels.json"
227
+ )
228
+ self.assertTrue(cache_file.is_file())
229
+ mode = stat.S_IMODE(cache_file.stat().st_mode)
230
+ self.assertEqual(
231
+ mode, 0o600,
232
+ f"cache file mode must be 0o600, got 0o{mode:03o} ",
233
+ )
234
+
235
+
236
+ class CacheFreshnessBoundsTests(unittest.TestCase):
237
+ """`_is_cache_fresh` must reject future timestamps as stale.
238
+
239
+ A `_built_at_utc` in the future is a symptom of clock skew (NTP
240
+ drift after suspend, container clock jump) or cache tampering —
241
+ either way, we treat it as untrustworthy and force a reprobe.
242
+ """
243
+
244
+ def setUp(self) -> None:
245
+ self._tmp = tempfile.TemporaryDirectory()
246
+ self.addCleanup(self._tmp.cleanup)
247
+
248
+ def test_future_timestamp_is_not_fresh(self):
249
+ # Year 3000 → unambiguously future.
250
+ future_epoch = 32503680000.0
251
+ cache_file = Path(self._tmp.name) / "channels.json"
252
+ cache_file.write_text(json.dumps({
253
+ "_schema": "channels/1",
254
+ "_built_at_utc": future_epoch,
255
+ "status": "OK",
256
+ "channels": {},
257
+ }))
258
+ self.assertFalse(
259
+ probe_channels._is_cache_fresh(cache_file, ttl_days=7),
260
+ "future _built_at_utc must be rejected as stale",
261
+ )
262
+
263
+ def test_present_timestamp_is_fresh(self):
264
+ cache_file = Path(self._tmp.name) / "channels.json"
265
+ cache_file.write_text(json.dumps({
266
+ "_schema": "channels/1",
267
+ "_built_at_utc": time.time(),
268
+ "status": "OK",
269
+ "channels": {},
270
+ }))
271
+ self.assertTrue(probe_channels._is_cache_fresh(cache_file, ttl_days=7))
272
+
273
+
274
+ class WriteCacheConcurrencyTests(unittest.TestCase):
275
+ """concurrent _write_cache calls must not collide on tmp names.
276
+
277
+ The prior design used `path.with_suffix(path.suffix + ".tmp")` — a
278
+ deterministic name shared by every concurrent writer. This test
279
+ fires N threads that all write the same target path; after the dust
280
+ settles there must be exactly one final file, no leftover `.tmp`
281
+ files, and the contents must be valid JSON from ONE of the writers
282
+ (any of them is acceptable — what matters is integrity).
283
+ """
284
+
285
+ def setUp(self) -> None:
286
+ self._tmp = tempfile.TemporaryDirectory()
287
+ self.addCleanup(self._tmp.cleanup)
288
+
289
+ def test_concurrent_writes_no_collision_no_corruption(self):
290
+ import threading
291
+
292
+ target_dir = Path(self._tmp.name) / "probe"
293
+ target_path = target_dir / "channels.json"
294
+
295
+ N = 8
296
+ errors: list[BaseException] = []
297
+ barrier = threading.Barrier(N)
298
+
299
+ def worker(i: int) -> None:
300
+ payload = {
301
+ "_schema": "channels/1",
302
+ "_built_at_utc": float(i + 1),
303
+ "status": "OK",
304
+ "channels": {"writer": {"queryable_fields": [f"w{i}"]}},
305
+ }
306
+ barrier.wait() # maximise contention
307
+ try:
308
+ probe_channels._write_cache(target_path, payload)
309
+ except BaseException as e: # noqa: BLE001 — thread boundary
310
+ errors.append(e)
311
+
312
+ threads = [threading.Thread(target=worker, args=(i,)) for i in range(N)]
313
+ for t in threads:
314
+ t.start()
315
+ for t in threads:
316
+ t.join(timeout=5.0)
317
+
318
+ self.assertEqual(errors, [], f"worker errors: {errors}")
319
+ self.assertTrue(target_path.is_file())
320
+
321
+ # No stray tmp files left behind.
322
+ leftover = list(target_dir.glob(".channels.*.tmp"))
323
+ self.assertEqual(
324
+ leftover, [],
325
+ f"concurrent writes left tmp leftovers: {leftover}",
326
+ )
327
+
328
+ # Final file is parseable JSON (not a half-written corruption).
329
+ parsed = json.loads(target_path.read_text())
330
+ # One of the N payloads won the race; its _built_at_utc is in [1, N].
331
+ self.assertIn(parsed["_built_at_utc"], [float(i + 1) for i in range(N)])
332
+
333
+ def test_parent_dir_permissions_are_0o700(self):
334
+ """parent dir is normalized to 0o700 after write.
335
+
336
+ Even if the directory pre-existed with looser permissions, a
337
+ successful _write_cache leaves it at 0o700.
338
+ """
339
+ target_path = Path(self._tmp.name) / "probe" / "channels.json"
340
+ target_path.parent.mkdir(parents=True, exist_ok=True)
341
+ # Deliberately widen perms to prove _write_cache narrows them.
342
+ os.chmod(target_path.parent, 0o755)
343
+
344
+ probe_channels._write_cache(target_path, {
345
+ "_schema": "channels/1",
346
+ "_built_at_utc": time.time(),
347
+ "status": "OK",
348
+ "channels": {},
349
+ })
350
+
351
+ mode = stat.S_IMODE(target_path.parent.stat().st_mode)
352
+ self.assertEqual(
353
+ mode, 0o700,
354
+ f"parent dir mode must be 0o700, got 0o{mode:03o}",
355
+ )
356
+
357
+
358
+ if __name__ == "__main__":
359
+ unittest.main()
@@ -0,0 +1,185 @@
1
+ """structural validation of the describe CLI recipes.
2
+
3
+ Before these recipes shipped, `scripts/probe_channels.py::_describe_sobject`
4
+ called `run_sf("describe_sobject", ...)` / `run_sf("describe_tooling_sobject",
5
+ ...)` against YAMLs that didn't exist on disk. Every unit test mocked
6
+ `run_sf`, so the suite stayed green — but the first real invocation would
7
+ have raised `SfCliError: recipe not found`. This module closes that gap
8
+ by loading each YAML via `sf_cli._load_recipe` and asserting the schema
9
+ `sf_cli.run_sf` expects.
10
+
11
+ Deliberately does NOT invoke `sf sobject describe` — that needs a live
12
+ org. Structural validation only; the wire-level contract is covered by
13
+ the existing probe_channels integration path once the skill ships.
14
+ """
15
+ from __future__ import annotations
16
+
17
+ import unittest
18
+ import unittest.mock as mock
19
+ from pathlib import Path
20
+
21
+ from . import _bootstrap # noqa: F401
22
+
23
+ import sf_cli # type: ignore
24
+
25
+ # SKILL_ROOT is now file-relative (config.py uses
26
+ # Path(__file__).resolve().parent.parent), so config.CLI_DIR auto-resolves
27
+ # to the repo's assets/cli/ under test. _REPO_CLI_DIR is kept for tests
28
+ # that compare paths or pass them as explicit args.
29
+ _REPO_CLI_DIR = (
30
+ Path(__file__).resolve().parent.parent.parent / "assets" / "cli"
31
+ )
32
+
33
+
34
+ class DescribeRecipeStructureTests(unittest.TestCase):
35
+ """Both describe recipes must parse + conform to the run_sf schema."""
36
+
37
+ def setUp(self) -> None:
38
+ # patch the module-level CLI_DIR binding inside sf_cli so
39
+ # _load_recipe reads from the repo's assets/cli/. `sf_cli`
40
+ # imported `CLI_DIR` by name at module load, so we must patch
41
+ # the *local* reference in sf_cli — patching `config.CLI_DIR`
42
+ # alone would leave the stale import behind.
43
+ self._patch = mock.patch.object(sf_cli, "CLI_DIR", _REPO_CLI_DIR)
44
+ self._patch.start()
45
+ self.addCleanup(self._patch.stop)
46
+
47
+ def _load(self, name: str) -> dict:
48
+ # _load_recipe is the same function run_sf uses at runtime, so if
49
+ # it parses here it will parse in production. yaml.safe_load + the
50
+ # mapping-shape guard both run inside _load_recipe .
51
+ return sf_cli._load_recipe(name)
52
+
53
+ # ---- parsing + mapping shape -----------------------------------------
54
+
55
+ def test_describe_sobject_parses(self):
56
+ recipe = self._load("describe_sobject")
57
+ self.assertIsInstance(recipe, dict)
58
+ self.assertEqual(recipe.get("name"), "describe_sobject")
59
+
60
+ def test_describe_tooling_sobject_parses(self):
61
+ recipe = self._load("describe_tooling_sobject")
62
+ self.assertIsInstance(recipe, dict)
63
+ self.assertEqual(recipe.get("name"), "describe_tooling_sobject")
64
+
65
+ # ---- required_params: a list, covers what probe_channels passes -------
66
+
67
+ def test_required_params_are_list_of_strings(self):
68
+ for name in ("describe_sobject", "describe_tooling_sobject"):
69
+ with self.subTest(recipe=name):
70
+ recipe = self._load(name)
71
+ required = recipe.get("required_params")
72
+ self.assertIsInstance(required, list)
73
+ self.assertTrue(
74
+ all(isinstance(p, str) for p in required),
75
+ f"{name}: required_params must be list[str]",
76
+ )
77
+ # probe_channels._describe_sobject passes exactly these two.
78
+ # If the recipe drops either, run_sf will SfCliError before
79
+ # the subprocess call — so assert both are present.
80
+ self.assertIn("ORG_ALIAS", required)
81
+ self.assertIn("SOBJECT", required)
82
+
83
+ # ---- argv: a list of strings with both placeholders referenced --------
84
+
85
+ def test_argv_is_list_of_strings(self):
86
+ for name in ("describe_sobject", "describe_tooling_sobject"):
87
+ with self.subTest(recipe=name):
88
+ recipe = self._load(name)
89
+ argv = recipe.get("argv")
90
+ self.assertIsInstance(argv, list)
91
+ self.assertGreater(len(argv), 0)
92
+ self.assertTrue(
93
+ all(isinstance(e, str) for e in argv),
94
+ f"{name}: argv must be list[str]",
95
+ )
96
+
97
+ def test_argv_references_both_required_placeholders(self):
98
+ """Every key in required_params must appear as `{{KEY}}` somewhere
99
+ in argv — otherwise the substitution is a no-op and the caller's
100
+ input never reaches the CLI. Defensive sanity check, not a
101
+ functional run.
102
+ """
103
+ for name in ("describe_sobject", "describe_tooling_sobject"):
104
+ with self.subTest(recipe=name):
105
+ recipe = self._load(name)
106
+ argv_joined = " ".join(recipe["argv"])
107
+ for key in recipe.get("required_params") or []:
108
+ self.assertIn(
109
+ f"{{{{{key}}}}}", argv_joined,
110
+ f"{name}: required param {key} not referenced in argv",
111
+ )
112
+
113
+ # ---- success_check contract -----------------------------------------
114
+
115
+ def test_success_check_is_stdout_json_status_zero(self):
116
+ """run_sf's success-detection code path (in sf_cli.run_sf) assumes
117
+ `stdout_json_status_zero`. Any other value silently changes the
118
+ pass/fail contract, so pin it.
119
+ """
120
+ for name in ("describe_sobject", "describe_tooling_sobject"):
121
+ with self.subTest(recipe=name):
122
+ recipe = self._load(name)
123
+ self.assertEqual(
124
+ recipe.get("success_check"), "stdout_json_status_zero",
125
+ )
126
+
127
+ # ---- timeout is a positive int ---------------------------------------
128
+
129
+ def test_timeout_is_positive_int(self):
130
+ for name in ("describe_sobject", "describe_tooling_sobject"):
131
+ with self.subTest(recipe=name):
132
+ recipe = self._load(name)
133
+ t = recipe.get("timeout_seconds")
134
+ self.assertIsInstance(t, int)
135
+ self.assertGreater(t, 0)
136
+
137
+ # ---- auth_required_stderr_patterns -----------------------------------
138
+
139
+ def test_auth_patterns_cover_the_known_failures(self):
140
+ """sf CLI surfaces `NoOrgAuthenticationError` for a never-logged-in
141
+ alias and `AuthInfoError` for a stale/revoked session. Both must
142
+ classify as AuthRequired, not a generic SfCliError, so the skill
143
+ can prompt the user to re-login instead of aborting.
144
+ """
145
+ for name in ("describe_sobject", "describe_tooling_sobject"):
146
+ with self.subTest(recipe=name):
147
+ recipe = self._load(name)
148
+ patterns = recipe.get("auth_required_stderr_patterns") or []
149
+ self.assertIn("NoOrgAuthenticationError", patterns)
150
+ self.assertIn("AuthInfoError", patterns)
151
+
152
+ # ---- tooling recipe has --use-tooling-api; data-API recipe does NOT ---
153
+
154
+ def test_tooling_recipe_uses_tooling_api_flag(self):
155
+ recipe = self._load("describe_tooling_sobject")
156
+ self.assertIn("--use-tooling-api", recipe["argv"])
157
+
158
+ def test_data_api_recipe_does_not_use_tooling_api_flag(self):
159
+ """Separating the two recipes rather than toggling via a param
160
+ was a deliberate design call (call sites stay explicit). Verify
161
+ the separation hasn't regressed into a single merged recipe.
162
+ """
163
+ recipe = self._load("describe_sobject")
164
+ self.assertNotIn("--use-tooling-api", recipe["argv"])
165
+
166
+ # ---- run_sf missing-param surfacing runs end-to-end through recipe ----
167
+
168
+ def test_run_sf_raises_cleanly_when_required_param_absent(self):
169
+ """This exercises the full load_recipe → required_params check
170
+ path inside run_sf, with a REAL (on-disk) recipe rather than a
171
+ mock. If the YAML parses but required_params is malformed, this
172
+ would surface as something other than SfCliError with a clear
173
+ 'missing required params' message.
174
+ """
175
+ with self.assertRaises(sf_cli.SfCliError) as ctx:
176
+ # Deliberately omit SOBJECT — should raise at the required-
177
+ # params check, well before subprocess.run. Pass ORG_ALIAS so
178
+ # we isolate the failure to the missing param.
179
+ sf_cli.run_sf("describe_sobject", ORG_ALIAS="whatever")
180
+ self.assertIn("missing required params", str(ctx.exception))
181
+ self.assertIn("SOBJECT", str(ctx.exception))
182
+
183
+
184
+ if __name__ == "__main__":
185
+ unittest.main()