@salesforce/afv-skills 1.14.0 → 1.16.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 (365) hide show
  1. package/package.json +1 -1
  2. package/skills/activating-datacloud/SKILL.md +0 -1
  3. package/skills/analyzing-omnistudio-dependencies/SKILL.md +0 -1
  4. package/skills/applying-slds/SKILL.md +322 -0
  5. package/skills/applying-slds/checklists.md +83 -0
  6. package/skills/applying-slds/examples.md +283 -0
  7. package/skills/applying-slds/guidance/README.md +83 -0
  8. package/skills/applying-slds/guidance/blueprints-index.md +213 -0
  9. package/skills/applying-slds/guidance/icons-guidance.md +186 -0
  10. package/skills/applying-slds/guidance/overviews/borders.md +236 -0
  11. package/skills/applying-slds/guidance/overviews/color.md +266 -0
  12. package/skills/applying-slds/guidance/overviews/display-density.md +366 -0
  13. package/skills/applying-slds/guidance/overviews/icons.md +240 -0
  14. package/skills/applying-slds/guidance/overviews/illustrations.md +235 -0
  15. package/skills/applying-slds/guidance/overviews/shadows.md +176 -0
  16. package/skills/applying-slds/guidance/overviews/spacing.md +216 -0
  17. package/skills/applying-slds/guidance/overviews/typography.md +323 -0
  18. package/skills/applying-slds/guidance/overviews/utilities.md +542 -0
  19. package/skills/applying-slds/guidance/slds-development-guide.md +288 -0
  20. package/skills/applying-slds/guidance/styling-hooks/borders.md +202 -0
  21. package/skills/applying-slds/guidance/styling-hooks/color/expressive-palette-hooks.md +153 -0
  22. package/skills/applying-slds/guidance/styling-hooks/color/index.md +171 -0
  23. package/skills/applying-slds/guidance/styling-hooks/color/semantic/accent-hooks.md +204 -0
  24. package/skills/applying-slds/guidance/styling-hooks/color/semantic/feedback-hooks.md +768 -0
  25. package/skills/applying-slds/guidance/styling-hooks/color/semantic/surface-hooks.md +337 -0
  26. package/skills/applying-slds/guidance/styling-hooks/color/system-hooks.md +132 -0
  27. package/skills/applying-slds/guidance/styling-hooks/index.md +327 -0
  28. package/skills/applying-slds/guidance/styling-hooks/shadows.md +238 -0
  29. package/skills/applying-slds/guidance/styling-hooks/spacing.md +254 -0
  30. package/skills/applying-slds/guidance/styling-hooks/typography.md +448 -0
  31. package/skills/applying-slds/guidance/utilities/alignment.md +119 -0
  32. package/skills/applying-slds/guidance/utilities/borders.md +131 -0
  33. package/skills/applying-slds/guidance/utilities/box.md +125 -0
  34. package/skills/applying-slds/guidance/utilities/color.md +165 -0
  35. package/skills/applying-slds/guidance/utilities/dark-mode.md +111 -0
  36. package/skills/applying-slds/guidance/utilities/description-list.md +168 -0
  37. package/skills/applying-slds/guidance/utilities/floats.md +117 -0
  38. package/skills/applying-slds/guidance/utilities/grid.md +264 -0
  39. package/skills/applying-slds/guidance/utilities/horizontal-list.md +110 -0
  40. package/skills/applying-slds/guidance/utilities/hyphenation.md +84 -0
  41. package/skills/applying-slds/guidance/utilities/index.md +205 -0
  42. package/skills/applying-slds/guidance/utilities/interactions.md +89 -0
  43. package/skills/applying-slds/guidance/utilities/layout.md +109 -0
  44. package/skills/applying-slds/guidance/utilities/line-clamp.md +131 -0
  45. package/skills/applying-slds/guidance/utilities/margin.md +155 -0
  46. package/skills/applying-slds/guidance/utilities/media-object.md +161 -0
  47. package/skills/applying-slds/guidance/utilities/name-value-list.md +152 -0
  48. package/skills/applying-slds/guidance/utilities/padding.md +155 -0
  49. package/skills/applying-slds/guidance/utilities/position.md +177 -0
  50. package/skills/applying-slds/guidance/utilities/print.md +114 -0
  51. package/skills/applying-slds/guidance/utilities/scrollable.md +126 -0
  52. package/skills/applying-slds/guidance/utilities/sizing.md +190 -0
  53. package/skills/applying-slds/guidance/utilities/themes.md +121 -0
  54. package/skills/applying-slds/guidance/utilities/truncate.md +127 -0
  55. package/skills/applying-slds/guidance/utilities/typography.md +166 -0
  56. package/skills/applying-slds/guidance/utilities/vertical-list.md +166 -0
  57. package/skills/applying-slds/guidance/utilities/visibility.md +228 -0
  58. package/skills/applying-slds/metadata/README.md +84 -0
  59. package/skills/applying-slds/metadata/blueprints/components/accordion.yaml +304 -0
  60. package/skills/applying-slds/metadata/blueprints/components/activity-timeline.yaml +92 -0
  61. package/skills/applying-slds/metadata/blueprints/components/alert.yaml +103 -0
  62. package/skills/applying-slds/metadata/blueprints/components/app-launcher.yaml +94 -0
  63. package/skills/applying-slds/metadata/blueprints/components/avatar-group.yaml +81 -0
  64. package/skills/applying-slds/metadata/blueprints/components/avatar.yaml +97 -0
  65. package/skills/applying-slds/metadata/blueprints/components/badges.yaml +102 -0
  66. package/skills/applying-slds/metadata/blueprints/components/brand-band.yaml +198 -0
  67. package/skills/applying-slds/metadata/blueprints/components/breadcrumbs.yaml +95 -0
  68. package/skills/applying-slds/metadata/blueprints/components/builder-header.yaml +192 -0
  69. package/skills/applying-slds/metadata/blueprints/components/button-groups.yaml +82 -0
  70. package/skills/applying-slds/metadata/blueprints/components/button-icons.yaml +295 -0
  71. package/skills/applying-slds/metadata/blueprints/components/buttons.yaml +230 -0
  72. package/skills/applying-slds/metadata/blueprints/components/cards.yaml +124 -0
  73. package/skills/applying-slds/metadata/blueprints/components/carousel.yaml +140 -0
  74. package/skills/applying-slds/metadata/blueprints/components/chat.yaml +179 -0
  75. package/skills/applying-slds/metadata/blueprints/components/checkbox-button-group.yaml +192 -0
  76. package/skills/applying-slds/metadata/blueprints/components/checkbox-button.yaml +204 -0
  77. package/skills/applying-slds/metadata/blueprints/components/checkbox-toggle.yaml +177 -0
  78. package/skills/applying-slds/metadata/blueprints/components/checkbox.yaml +108 -0
  79. package/skills/applying-slds/metadata/blueprints/components/color-picker.yaml +172 -0
  80. package/skills/applying-slds/metadata/blueprints/components/combobox.yaml +136 -0
  81. package/skills/applying-slds/metadata/blueprints/components/counter.yaml +147 -0
  82. package/skills/applying-slds/metadata/blueprints/components/data-tables.yaml +157 -0
  83. package/skills/applying-slds/metadata/blueprints/components/datepickers.yaml +130 -0
  84. package/skills/applying-slds/metadata/blueprints/components/datetime-picker.yaml +155 -0
  85. package/skills/applying-slds/metadata/blueprints/components/docked-composer.yaml +201 -0
  86. package/skills/applying-slds/metadata/blueprints/components/docked-form-footer.yaml +161 -0
  87. package/skills/applying-slds/metadata/blueprints/components/docked-utility-bar.yaml +175 -0
  88. package/skills/applying-slds/metadata/blueprints/components/drop-zone.yaml +115 -0
  89. package/skills/applying-slds/metadata/blueprints/components/dueling-picklist.yaml +196 -0
  90. package/skills/applying-slds/metadata/blueprints/components/dynamic-icons.yaml +128 -0
  91. package/skills/applying-slds/metadata/blueprints/components/dynamic-menu.yaml +141 -0
  92. package/skills/applying-slds/metadata/blueprints/components/expandable-section.yaml +115 -0
  93. package/skills/applying-slds/metadata/blueprints/components/expression.yaml +143 -0
  94. package/skills/applying-slds/metadata/blueprints/components/feeds.yaml +125 -0
  95. package/skills/applying-slds/metadata/blueprints/components/file-selector.yaml +154 -0
  96. package/skills/applying-slds/metadata/blueprints/components/files.yaml +119 -0
  97. package/skills/applying-slds/metadata/blueprints/components/form-element.yaml +145 -0
  98. package/skills/applying-slds/metadata/blueprints/components/global-header.yaml +120 -0
  99. package/skills/applying-slds/metadata/blueprints/components/global-navigation.yaml +100 -0
  100. package/skills/applying-slds/metadata/blueprints/components/icons.yaml +138 -0
  101. package/skills/applying-slds/metadata/blueprints/components/illustration.yaml +205 -0
  102. package/skills/applying-slds/metadata/blueprints/components/input.yaml +151 -0
  103. package/skills/applying-slds/metadata/blueprints/components/list-builder.yaml +127 -0
  104. package/skills/applying-slds/metadata/blueprints/components/lookups.yaml +132 -0
  105. package/skills/applying-slds/metadata/blueprints/components/map.yaml +118 -0
  106. package/skills/applying-slds/metadata/blueprints/components/menus.yaml +134 -0
  107. package/skills/applying-slds/metadata/blueprints/components/modals.yaml +152 -0
  108. package/skills/applying-slds/metadata/blueprints/components/notifications.yaml +88 -0
  109. package/skills/applying-slds/metadata/blueprints/components/page-headers.yaml +135 -0
  110. package/skills/applying-slds/metadata/blueprints/components/panels.yaml +149 -0
  111. package/skills/applying-slds/metadata/blueprints/components/path.yaml +154 -0
  112. package/skills/applying-slds/metadata/blueprints/components/picklist.yaml +125 -0
  113. package/skills/applying-slds/metadata/blueprints/components/pills.yaml +154 -0
  114. package/skills/applying-slds/metadata/blueprints/components/popovers.yaml +120 -0
  115. package/skills/applying-slds/metadata/blueprints/components/progress-bar.yaml +110 -0
  116. package/skills/applying-slds/metadata/blueprints/components/progress-indicator.yaml +133 -0
  117. package/skills/applying-slds/metadata/blueprints/components/progress-ring.yaml +102 -0
  118. package/skills/applying-slds/metadata/blueprints/components/prompt.yaml +126 -0
  119. package/skills/applying-slds/metadata/blueprints/components/publishers.yaml +178 -0
  120. package/skills/applying-slds/metadata/blueprints/components/radio-button-group.yaml +172 -0
  121. package/skills/applying-slds/metadata/blueprints/components/radio-group.yaml +112 -0
  122. package/skills/applying-slds/metadata/blueprints/components/rich-text-editor.yaml +135 -0
  123. package/skills/applying-slds/metadata/blueprints/components/scoped-notifications.yaml +188 -0
  124. package/skills/applying-slds/metadata/blueprints/components/scoped-tabs.yaml +97 -0
  125. package/skills/applying-slds/metadata/blueprints/components/select.yaml +127 -0
  126. package/skills/applying-slds/metadata/blueprints/components/setup-assistant.yaml +152 -0
  127. package/skills/applying-slds/metadata/blueprints/components/slider.yaml +111 -0
  128. package/skills/applying-slds/metadata/blueprints/components/spinners.yaml +135 -0
  129. package/skills/applying-slds/metadata/blueprints/components/split-view.yaml +112 -0
  130. package/skills/applying-slds/metadata/blueprints/components/summary-detail.yaml +103 -0
  131. package/skills/applying-slds/metadata/blueprints/components/tabs.yaml +138 -0
  132. package/skills/applying-slds/metadata/blueprints/components/textarea.yaml +116 -0
  133. package/skills/applying-slds/metadata/blueprints/components/tiles.yaml +108 -0
  134. package/skills/applying-slds/metadata/blueprints/components/timepicker.yaml +111 -0
  135. package/skills/applying-slds/metadata/blueprints/components/toast.yaml +154 -0
  136. package/skills/applying-slds/metadata/blueprints/components/tooltips.yaml +107 -0
  137. package/skills/applying-slds/metadata/blueprints/components/tree-grid.yaml +116 -0
  138. package/skills/applying-slds/metadata/blueprints/components/trees.yaml +116 -0
  139. package/skills/applying-slds/metadata/blueprints/components/trial-bar.yaml +112 -0
  140. package/skills/applying-slds/metadata/blueprints/components/vertical-navigation.yaml +130 -0
  141. package/skills/applying-slds/metadata/blueprints/components/vertical-tabs.yaml +140 -0
  142. package/skills/applying-slds/metadata/blueprints/components/visual-picker.yaml +150 -0
  143. package/skills/applying-slds/metadata/blueprints/components/welcome-mat.yaml +136 -0
  144. package/skills/applying-slds/metadata/hooks-index.json +6272 -0
  145. package/skills/applying-slds/metadata/icon-metadata.json +38466 -0
  146. package/skills/applying-slds/metadata/utilities-index.json +21912 -0
  147. package/skills/applying-slds/references/component-selection.md +112 -0
  148. package/skills/applying-slds/references/icons-decision-guide.md +124 -0
  149. package/skills/applying-slds/references/styling-decision-guide.md +228 -0
  150. package/skills/applying-slds/references/utilities-quick-ref.md +125 -0
  151. package/skills/applying-slds/scripts/search-blueprints.cjs +117 -0
  152. package/skills/applying-slds/scripts/search-hooks.cjs +139 -0
  153. package/skills/applying-slds/scripts/search-icons.cjs +174 -0
  154. package/skills/applying-slds/scripts/search-utilities.cjs +161 -0
  155. package/skills/building-mobile-apps/SKILL.md +0 -1
  156. package/skills/building-omnistudio-callable-apex/SKILL.md +0 -1
  157. package/skills/building-omnistudio-datamapper/SKILL.md +0 -1
  158. package/skills/building-omnistudio-flexcard/SKILL.md +0 -1
  159. package/skills/building-omnistudio-integration-procedure/SKILL.md +0 -1
  160. package/skills/building-omnistudio-omniscript/SKILL.md +0 -1
  161. package/skills/building-sf-integrations/SKILL.md +0 -1
  162. package/skills/configuring-connected-apps/SKILL.md +0 -1
  163. package/skills/connecting-datacloud/SKILL.md +0 -1
  164. package/skills/creating-b2b-commerce-store/SKILL.md +0 -1
  165. package/skills/debugging-apex-logs/SKILL.md +0 -1
  166. package/skills/deploying-metadata/SKILL.md +0 -1
  167. package/skills/deploying-omnistudio-datapacks/SKILL.md +0 -1
  168. package/skills/developing-agentforce/SKILL.md +0 -1
  169. package/skills/fetching-salesforce-docs/SKILL.md +0 -1
  170. package/skills/generating-custom-lightning-type/SKILL.md +17 -39
  171. package/skills/generating-custom-lightning-type/assets/primitive-types-and-constraints.md +41 -0
  172. package/skills/generating-custom-lightning-type/references/widget-rendition.md +124 -0
  173. package/skills/generating-lwc-components/SKILL.md +0 -1
  174. package/skills/generating-mermaid-diagrams/SKILL.md +0 -1
  175. package/skills/generating-visual-diagrams/SKILL.md +0 -1
  176. package/skills/handling-sf-data/SKILL.md +0 -1
  177. package/skills/harmonizing-datacloud/SKILL.md +0 -1
  178. package/skills/integrating-b2b-commerce-open-code-components/SKILL.md +0 -1
  179. package/skills/investigating-agentforce-architecture/README.md +156 -0
  180. package/skills/investigating-agentforce-architecture/SKILL.md +230 -0
  181. package/skills/investigating-agentforce-architecture/assets/cli/describe_sobject.yaml +16 -0
  182. package/skills/investigating-agentforce-architecture/assets/cli/describe_tooling_sobject.yaml +17 -0
  183. package/skills/investigating-agentforce-architecture/assets/cli/list_metadata_genaiprompttemplate.yaml +17 -0
  184. package/skills/investigating-agentforce-architecture/assets/cli/org_display.yaml +15 -0
  185. package/skills/investigating-agentforce-architecture/assets/cli/retrieve_genai_plugin.yaml +18 -0
  186. package/skills/investigating-agentforce-architecture/assets/cli/show_access_token.yaml +27 -0
  187. package/skills/investigating-agentforce-architecture/assets/mermaid/action_tree.mmd +20 -0
  188. package/skills/investigating-agentforce-architecture/assets/mermaid/data_flow.mmd +19 -0
  189. package/skills/investigating-agentforce-architecture/assets/mermaid/dependency_graph.mmd +19 -0
  190. package/skills/investigating-agentforce-architecture/assets/mermaid/invocation_sequence.mmd +20 -0
  191. package/skills/investigating-agentforce-architecture/assets/mermaid/planner_state.mmd +18 -0
  192. package/skills/investigating-agentforce-architecture/assets/soql/apex_class_bodies_by_ids.soql +3 -0
  193. package/skills/investigating-agentforce-architecture/assets/soql/apex_class_bodies_by_names.soql +3 -0
  194. package/skills/investigating-agentforce-architecture/assets/soql/bot_definition_details.soql +3 -0
  195. package/skills/investigating-agentforce-architecture/assets/soql/bot_version_lookup.soql +4 -0
  196. package/skills/investigating-agentforce-architecture/assets/soql/flow_definition_by_ids.soql +3 -0
  197. package/skills/investigating-agentforce-architecture/assets/soql/flow_definition_ids_by_names.soql +3 -0
  198. package/skills/investigating-agentforce-architecture/assets/soql/flow_definition_view_by_durable_ids.soql +4 -0
  199. package/skills/investigating-agentforce-architecture/assets/soql/flow_metadata_by_id.soql +3 -0
  200. package/skills/investigating-agentforce-architecture/assets/soql/functions_by_plugins.soql +5 -0
  201. package/skills/investigating-agentforce-architecture/assets/soql/planner_attrs_by_parent_ids.soql +3 -0
  202. package/skills/investigating-agentforce-architecture/assets/soql/planner_bundle_functions.soql +3 -0
  203. package/skills/investigating-agentforce-architecture/assets/soql/planner_definition_by_agent_chain.soql +3 -0
  204. package/skills/investigating-agentforce-architecture/assets/soql/plugin_functions_by_plugin_ids.soql +3 -0
  205. package/skills/investigating-agentforce-architecture/assets/soql/plugin_instructions_by_plugin_ids.soql +3 -0
  206. package/skills/investigating-agentforce-architecture/assets/soql/plugins_by_planner.soql +4 -0
  207. package/skills/investigating-agentforce-architecture/references/architecture_sections.md +243 -0
  208. package/skills/investigating-agentforce-architecture/references/contract.json +244 -0
  209. package/skills/investigating-agentforce-architecture/references/soql_fields.md +512 -0
  210. package/skills/investigating-agentforce-architecture/scripts/_shared/__init__.py +1 -0
  211. package/skills/investigating-agentforce-architecture/scripts/_shared/fs_guard.py +329 -0
  212. package/skills/investigating-agentforce-architecture/scripts/_shared/paths.py +110 -0
  213. package/skills/investigating-agentforce-architecture/scripts/_shared/runtime.py +59 -0
  214. package/skills/investigating-agentforce-architecture/scripts/_shared/sql.py +10 -0
  215. package/skills/investigating-agentforce-architecture/scripts/cache_check.py +234 -0
  216. package/skills/investigating-agentforce-architecture/scripts/config.py +131 -0
  217. package/skills/investigating-agentforce-architecture/scripts/fetch_soql.py +689 -0
  218. package/skills/investigating-agentforce-architecture/scripts/finalize.py +295 -0
  219. package/skills/investigating-agentforce-architecture/scripts/main.py +2835 -0
  220. package/skills/investigating-agentforce-architecture/scripts/metadata_listing.py +265 -0
  221. package/skills/investigating-agentforce-architecture/scripts/parallel_retrieve.py +69 -0
  222. package/skills/investigating-agentforce-architecture/scripts/parse_bundle.py +215 -0
  223. package/skills/investigating-agentforce-architecture/scripts/parse_wave.py +845 -0
  224. package/skills/investigating-agentforce-architecture/scripts/probe_channels.py +302 -0
  225. package/skills/investigating-agentforce-architecture/scripts/render_architecture.py +1043 -0
  226. package/skills/investigating-agentforce-architecture/scripts/resolve_bot.py +255 -0
  227. package/skills/investigating-agentforce-architecture/scripts/resolve_invocation_target.py +130 -0
  228. package/skills/investigating-agentforce-architecture/scripts/rest_client.py +763 -0
  229. package/skills/investigating-agentforce-architecture/scripts/retrieve_planner.py +13 -0
  230. package/skills/investigating-agentforce-architecture/scripts/sf_cli.py +242 -0
  231. package/skills/investigating-agentforce-architecture/scripts/soql_loader.py +253 -0
  232. package/skills/investigating-agentforce-architecture/scripts/summarize_tree.py +143 -0
  233. package/skills/investigating-agentforce-architecture/scripts/tests/__init__.py +0 -0
  234. package/skills/investigating-agentforce-architecture/scripts/tests/_bootstrap.py +23 -0
  235. package/skills/investigating-agentforce-architecture/scripts/tests/fixtures/__init__.py +0 -0
  236. package/skills/investigating-agentforce-architecture/scripts/tests/fixtures/genai_payloads.py +400 -0
  237. package/skills/investigating-agentforce-architecture/scripts/tests/test_cache_check.py +307 -0
  238. package/skills/investigating-agentforce-architecture/scripts/tests/test_cache_check_main.py +283 -0
  239. package/skills/investigating-agentforce-architecture/scripts/tests/test_config.py +115 -0
  240. package/skills/investigating-agentforce-architecture/scripts/tests/test_end_to_end_fixture.py +651 -0
  241. package/skills/investigating-agentforce-architecture/scripts/tests/test_finalize.py +278 -0
  242. package/skills/investigating-agentforce-architecture/scripts/tests/test_flow_children_inflation.py +582 -0
  243. package/skills/investigating-agentforce-architecture/scripts/tests/test_fs_guard.py +113 -0
  244. package/skills/investigating-agentforce-architecture/scripts/tests/test_iterative_wave_b.py +478 -0
  245. package/skills/investigating-agentforce-architecture/scripts/tests/test_main_pipeline.py +3359 -0
  246. package/skills/investigating-agentforce-architecture/scripts/tests/test_parallel_retrieve.py +131 -0
  247. package/skills/investigating-agentforce-architecture/scripts/tests/test_parse_bundle.py +400 -0
  248. package/skills/investigating-agentforce-architecture/scripts/tests/test_parse_wave.py +644 -0
  249. package/skills/investigating-agentforce-architecture/scripts/tests/test_parse_wave_classifiers.py +224 -0
  250. package/skills/investigating-agentforce-architecture/scripts/tests/test_parse_wave_helpers.py +380 -0
  251. package/skills/investigating-agentforce-architecture/scripts/tests/test_parse_wave_main.py +397 -0
  252. package/skills/investigating-agentforce-architecture/scripts/tests/test_per_branch_visited.py +244 -0
  253. package/skills/investigating-agentforce-architecture/scripts/tests/test_probe_channels.py +359 -0
  254. package/skills/investigating-agentforce-architecture/scripts/tests/test_probe_cli_recipes.py +185 -0
  255. package/skills/investigating-agentforce-architecture/scripts/tests/test_render_architecture.py +810 -0
  256. package/skills/investigating-agentforce-architecture/scripts/tests/test_resolve_bot.py +203 -0
  257. package/skills/investigating-agentforce-architecture/scripts/tests/test_resolve_creds.py +157 -0
  258. package/skills/investigating-agentforce-architecture/scripts/tests/test_resolve_invocation_target.py +145 -0
  259. package/skills/investigating-agentforce-architecture/scripts/tests/test_rest_client.py +1253 -0
  260. package/skills/investigating-agentforce-architecture/scripts/tests/test_runtime_override.py +100 -0
  261. package/skills/investigating-agentforce-architecture/scripts/tests/test_sf_cli.py +261 -0
  262. package/skills/investigating-agentforce-architecture/scripts/tests/test_signature_stamping.py +466 -0
  263. package/skills/investigating-agentforce-architecture/scripts/tests/test_soql_loader.py +501 -0
  264. package/skills/investigating-agentforce-architecture/scripts/tests/test_summarize_tree.py +241 -0
  265. package/skills/investigating-agentforce-architecture/scripts/tests/test_write_emit_ctx.py +480 -0
  266. package/skills/investigating-agentforce-architecture/tools/emit_env.py +157 -0
  267. package/skills/investigating-agentforce-architecture/tools/emit_result.py +262 -0
  268. package/skills/investigating-agentforce-architecture/tools/sanitize.py +33 -0
  269. package/skills/investigating-agentforce-architecture/tools/write_emit_ctx.py +332 -0
  270. package/skills/investigating-agentforce-d360/README.md +123 -0
  271. package/skills/investigating-agentforce-d360/SKILL.md +163 -0
  272. package/skills/investigating-agentforce-d360/assets/dc/app_generation.sql +51 -0
  273. package/skills/investigating-agentforce-d360/assets/dc/content_category.sql +44 -0
  274. package/skills/investigating-agentforce-d360/assets/dc/content_quality.sql +41 -0
  275. package/skills/investigating-agentforce-d360/assets/dc/discover_sessions.sql +36 -0
  276. package/skills/investigating-agentforce-d360/assets/dc/feedback.sql +47 -0
  277. package/skills/investigating-agentforce-d360/assets/dc/feedback_details.sql +38 -0
  278. package/skills/investigating-agentforce-d360/assets/dc/gateway_records.sql +45 -0
  279. package/skills/investigating-agentforce-d360/assets/dc/gateway_request_llm.sql +50 -0
  280. package/skills/investigating-agentforce-d360/assets/dc/gateway_request_metadata.sql +44 -0
  281. package/skills/investigating-agentforce-d360/assets/dc/gateway_request_tags.sql +42 -0
  282. package/skills/investigating-agentforce-d360/assets/dc/gateway_requests.sql +89 -0
  283. package/skills/investigating-agentforce-d360/assets/dc/gateway_responses.sql +43 -0
  284. package/skills/investigating-agentforce-d360/assets/dc/generations.sql +52 -0
  285. package/skills/investigating-agentforce-d360/assets/dc/interactions.sql +53 -0
  286. package/skills/investigating-agentforce-d360/assets/dc/messages.sql +53 -0
  287. package/skills/investigating-agentforce-d360/assets/dc/messaging_session.sql +37 -0
  288. package/skills/investigating-agentforce-d360/assets/dc/moment_interactions.sql +34 -0
  289. package/skills/investigating-agentforce-d360/assets/dc/moments.sql +39 -0
  290. package/skills/investigating-agentforce-d360/assets/dc/participants.sql +48 -0
  291. package/skills/investigating-agentforce-d360/assets/dc/sessions.sql +78 -0
  292. package/skills/investigating-agentforce-d360/assets/dc/steps.sql +64 -0
  293. package/skills/investigating-agentforce-d360/assets/dc/tag_associations.sql +46 -0
  294. package/skills/investigating-agentforce-d360/assets/dc/tag_definition_associations.sql +37 -0
  295. package/skills/investigating-agentforce-d360/assets/dc/tag_definitions.sql +50 -0
  296. package/skills/investigating-agentforce-d360/assets/dc/tags.sql +37 -0
  297. package/skills/investigating-agentforce-d360/assets/dc/telemetry_spans.sql +55 -0
  298. package/skills/investigating-agentforce-d360/references/artifacts.md +50 -0
  299. package/skills/investigating-agentforce-d360/references/dc_dmo_fields.md +823 -0
  300. package/skills/investigating-agentforce-d360/references/dc_pipeline_contract.md +608 -0
  301. package/skills/investigating-agentforce-d360/scripts/_shared/__init__.py +2 -0
  302. package/skills/investigating-agentforce-d360/scripts/_shared/cli_override.py +98 -0
  303. package/skills/investigating-agentforce-d360/scripts/_shared/fs_guard.py +334 -0
  304. package/skills/investigating-agentforce-d360/scripts/_shared/paths.py +155 -0
  305. package/skills/investigating-agentforce-d360/scripts/_shared/runtime.py +59 -0
  306. package/skills/investigating-agentforce-d360/scripts/_shared/sql.py +14 -0
  307. package/skills/investigating-agentforce-d360/scripts/assemble_dc.py +1624 -0
  308. package/skills/investigating-agentforce-d360/scripts/config.py +45 -0
  309. package/skills/investigating-agentforce-d360/scripts/dc.py +188 -0
  310. package/skills/investigating-agentforce-d360/scripts/discover_sessions.py +556 -0
  311. package/skills/investigating-agentforce-d360/scripts/fetch_dc.py +1045 -0
  312. package/skills/investigating-agentforce-d360/scripts/render_dc.py +1750 -0
  313. package/skills/investigating-agentforce-d360/scripts/resolve_session.py +264 -0
  314. package/skills/investigating-agentforce-d360/scripts/storage.py +92 -0
  315. package/skills/investigating-agentforce-d360/scripts/tests/__init__.py +0 -0
  316. package/skills/investigating-agentforce-d360/scripts/tests/_bootstrap.py +15 -0
  317. package/skills/investigating-agentforce-d360/scripts/tests/fixtures/__init__.py +0 -0
  318. package/skills/investigating-agentforce-d360/scripts/tests/fixtures/synthetic_session.py +424 -0
  319. package/skills/investigating-agentforce-d360/scripts/tests/test_assemble_dc_bootstrap_and_mode.py +115 -0
  320. package/skills/investigating-agentforce-d360/scripts/tests/test_assemble_dc_gateway_direct.py +220 -0
  321. package/skills/investigating-agentforce-d360/scripts/tests/test_assemble_dc_gateway_direct_integration.py +158 -0
  322. package/skills/investigating-agentforce-d360/scripts/tests/test_assemble_dc_helpers.py +287 -0
  323. package/skills/investigating-agentforce-d360/scripts/tests/test_assemble_dc_integration.py +247 -0
  324. package/skills/investigating-agentforce-d360/scripts/tests/test_dc_and_resolve_session.py +433 -0
  325. package/skills/investigating-agentforce-d360/scripts/tests/test_discover_sessions.py +458 -0
  326. package/skills/investigating-agentforce-d360/scripts/tests/test_discover_sessions_grep_ci.py +193 -0
  327. package/skills/investigating-agentforce-d360/scripts/tests/test_fetch_dc_helpers.py +266 -0
  328. package/skills/investigating-agentforce-d360/scripts/tests/test_fetch_dc_identity.py +528 -0
  329. package/skills/investigating-agentforce-d360/scripts/tests/test_fetch_dc_main.py +251 -0
  330. package/skills/investigating-agentforce-d360/scripts/tests/test_fetch_dc_waterfall.py +229 -0
  331. package/skills/investigating-agentforce-d360/scripts/tests/test_fetch_dc_waterfall_full.py +283 -0
  332. package/skills/investigating-agentforce-d360/scripts/tests/test_identity_coherence.py +327 -0
  333. package/skills/investigating-agentforce-d360/scripts/tests/test_render_dc_branches.py +256 -0
  334. package/skills/investigating-agentforce-d360/scripts/tests/test_render_dc_gateway_direct.py +130 -0
  335. package/skills/investigating-agentforce-d360/scripts/tests/test_render_dc_helpers.py +291 -0
  336. package/skills/investigating-agentforce-d360/scripts/tests/test_render_dc_integration.py +220 -0
  337. package/skills/investigating-agentforce-d360/scripts/tests/test_render_dc_planner_llm_calls.py +284 -0
  338. package/skills/investigating-agentforce-d360/scripts/tests/test_render_dc_show_prompts_gating.py +215 -0
  339. package/skills/investigating-agentforce-d360/scripts/tests/test_resolve_from_disk.py +100 -0
  340. package/skills/investigating-agentforce-d360/scripts/tests/test_resolve_session_main.py +149 -0
  341. package/skills/investigating-agentforce-d360/scripts/tests/test_runtime_override.py +104 -0
  342. package/skills/investigating-agentforce-d360/scripts/tests/test_session_shape.py +95 -0
  343. package/skills/investigating-agentforce-d360/scripts/tests/test_session_shape_dropped_by_stdm.py +85 -0
  344. package/skills/managing-managed-event-subscription/SKILL.md +152 -0
  345. package/skills/managing-managed-event-subscription/assets/managed-event-subscription-template.xml +20 -0
  346. package/skills/managing-managed-event-subscription/references/delete-guide.md +57 -0
  347. package/skills/managing-managed-event-subscription/references/topic-name-formats.md +26 -0
  348. package/skills/managing-managed-event-subscription/references/update-constraints.md +30 -0
  349. package/skills/modeling-omnistudio-epc-catalog/SKILL.md +0 -1
  350. package/skills/observing-agentforce/SKILL.md +0 -1
  351. package/skills/orchestrating-datacloud/SKILL.md +0 -1
  352. package/skills/preparing-datacloud/SKILL.md +0 -1
  353. package/skills/querying-soql/SKILL.md +0 -1
  354. package/skills/retrieving-datacloud/SKILL.md +0 -1
  355. package/skills/running-apex-tests/SKILL.md +0 -1
  356. package/skills/running-code-analyzer/SKILL.md +0 -1
  357. package/skills/segmenting-datacloud/SKILL.md +0 -1
  358. package/skills/testing-agentforce/SKILL.md +0 -1
  359. package/skills/uplifting-components-to-slds2/SKILL.md +3 -2
  360. package/skills/uplifting-components-to-slds2/references/color-hooks-decision-guide.md +30 -9
  361. package/skills/uplifting-components-to-slds2/references/examples.md +24 -6
  362. package/skills/validating-slds/SKILL.md +262 -0
  363. package/skills/validating-slds/references/quality-checks.md +308 -0
  364. package/skills/validating-slds/references/report-format.md +302 -0
  365. package/skills/validating-slds/scripts/analyze-quality.cjs +521 -0
@@ -0,0 +1,480 @@
1
+ """Integration tests for (REMEDIATE): tree → write_emit_ctx → emit_result.
2
+
3
+ The defect: parse_wave.py writes `_partial_reason` and `_pending_fetches`
4
+ into the tree on disk. emit_result.py reads `ctx["partial_reason"]` and
5
+ `ctx["pending_fetches_count"]` from the emit ctx. But write_emit_ctx.py
6
+ NEVER populated those ctx keys from the tree — they silently defaulted to
7
+ empty / 0 on every run. Depth-cap truncation was invisible downstream.
8
+
9
+ These tests exercise both halves of the wiring:
10
+
11
+ test_write_emit_ctx_reads_tree_partial_signals
12
+ fake tree JSON → write_emit_ctx → assert ctx keys populated.
13
+
14
+ test_end_to_end_tree_surfaces_partial_reason_in_result_block
15
+ tree → write_emit_ctx → emit_result → assert RESULT block carries
16
+ PARTIAL_REASON + PENDING_FETCHES_COUNT. End-to-end — this is the
17
+ test both reviewers called for.
18
+
19
+ Missing-tree behavior is also covered so early-abort paths still work.
20
+ """
21
+ from __future__ import annotations
22
+
23
+ import json
24
+ import os
25
+ import pathlib
26
+ import subprocess
27
+ import sys
28
+ import tempfile
29
+ import unittest
30
+
31
+ from . import _bootstrap # noqa: F401
32
+
33
+
34
+ def _tools_dir() -> pathlib.Path:
35
+ """Absolute path to the skill's tools/ dir — where the scripts live."""
36
+ here = pathlib.Path(__file__).resolve()
37
+ return here.parent.parent.parent / "tools"
38
+
39
+
40
+ def _run_script(script_name: str, env: dict) -> subprocess.CompletedProcess:
41
+ """Invoke `python3 <tools>/<script>.py` with the given env.
42
+
43
+ We deliberately run the scripts as subprocesses rather than importing
44
+ them: they're shipped as stand-alone CLIs (the agent bash invokes them
45
+ via `python3 write_emit_ctx.py`) and `main()` reads env at call time +
46
+ returns an exit code. Subprocess gives us the exact production call
47
+ path without having to monkey-patch os.environ.
48
+ """
49
+ script_path = _tools_dir() / script_name
50
+ return subprocess.run(
51
+ [sys.executable, str(script_path)],
52
+ env=env,
53
+ capture_output=True,
54
+ text=True,
55
+ timeout=30,
56
+ )
57
+
58
+
59
+ class WriteEmitCtxTreeSignalsTests(unittest.TestCase):
60
+ """write_emit_ctx must plumb `_partial_reason` + rollup from tree."""
61
+
62
+ def setUp(self) -> None:
63
+ self._tmp = tempfile.TemporaryDirectory()
64
+ self.addCleanup(self._tmp.cleanup)
65
+ self.work_dir = pathlib.Path(self._tmp.name) / "work"
66
+ self.data_dir = pathlib.Path(self._tmp.name) / "data"
67
+ self.work_dir.mkdir()
68
+ self.data_dir.mkdir()
69
+
70
+ def _base_env(self, status: str = "OK") -> dict:
71
+ return {
72
+ **os.environ,
73
+ "WORK_DIR": str(self.work_dir),
74
+ "DATA_DIR": str(self.data_dir),
75
+ "STATUS": status,
76
+ "AGENT_API_NAME": "MyBot",
77
+ "AGENT_VERSION": "v1",
78
+ "ORG_ID_15": "00D000000000ABC",
79
+ "BOT_ID": "0XxFAKEBOTID",
80
+ "NODE_COUNT": "42",
81
+ "DEPTH": "3",
82
+ "PARTIAL": "true",
83
+ "START_EPOCH": "0",
84
+ }
85
+
86
+ def _write_tree(self, payload: dict) -> None:
87
+ (self.work_dir / "declared_action_tree.json").write_text(
88
+ json.dumps(payload)
89
+ )
90
+
91
+ def _read_ctx(self) -> dict:
92
+ return json.loads((self.work_dir / ".emit_ctx.json").read_text())
93
+
94
+ def test_write_emit_ctx_reads_tree_partial_signals(self):
95
+ """Fake tree → ctx carries partial_reason + pending_fetches_count."""
96
+ self._write_tree({
97
+ "_partial": True,
98
+ "_partial_reason": "max-depth-cap",
99
+ "_pending_fetches": {
100
+ "FLOW": ["F6"],
101
+ "APEX": [],
102
+ "PROMPT_TEMPLATE": [],
103
+ "STANDARD_ACTION": [],
104
+ },
105
+ })
106
+
107
+ result = _run_script("write_emit_ctx.py", self._base_env())
108
+ self.assertEqual(result.returncode, 0, msg=result.stderr)
109
+
110
+ ctx = self._read_ctx()
111
+ self.assertEqual(ctx["partial_reason"], "max-depth-cap")
112
+ self.assertEqual(ctx["pending_fetches_count"], 1)
113
+
114
+ def test_write_emit_ctx_rollup_sums_across_kinds(self):
115
+ self._write_tree({
116
+ "_partial": True,
117
+ "_partial_reason": "pending-refs",
118
+ "_pending_fetches": {
119
+ "FLOW": ["F1", "F2"],
120
+ "APEX": ["A1"],
121
+ "PROMPT_TEMPLATE": [],
122
+ "STANDARD_ACTION": ["S1", "S2", "S3"],
123
+ },
124
+ })
125
+
126
+ result = _run_script("write_emit_ctx.py", self._base_env())
127
+ self.assertEqual(result.returncode, 0, msg=result.stderr)
128
+
129
+ ctx = self._read_ctx()
130
+ self.assertEqual(ctx["partial_reason"], "pending-refs")
131
+ self.assertEqual(ctx["pending_fetches_count"], 6)
132
+
133
+ def test_write_emit_ctx_missing_tree_defaults_safely(self):
134
+ """Early-abort paths never write a tree — ctx must default cleanly."""
135
+ # No tree file written.
136
+ result = _run_script(
137
+ "write_emit_ctx.py",
138
+ self._base_env(status="AGENT_NOT_FOUND"),
139
+ )
140
+ self.assertEqual(result.returncode, 0, msg=result.stderr)
141
+ ctx = self._read_ctx()
142
+ self.assertEqual(ctx["partial_reason"], "")
143
+ self.assertEqual(ctx["pending_fetches_count"], 0)
144
+
145
+ def test_write_emit_ctx_malformed_tree_defaults_safely(self):
146
+ (self.work_dir / "declared_action_tree.json").write_text("{ not json")
147
+ result = _run_script("write_emit_ctx.py", self._base_env())
148
+ self.assertEqual(result.returncode, 0, msg=result.stderr)
149
+ ctx = self._read_ctx()
150
+ self.assertEqual(ctx["partial_reason"], "")
151
+ self.assertEqual(ctx["pending_fetches_count"], 0)
152
+
153
+
154
+ class TreeToResultBlockEndToEndTests(unittest.TestCase):
155
+ """end-to-end: tree → write_emit_ctx → emit_result → RESULT block.
156
+
157
+ This is the test both reviewers called for — it would have caught the
158
+ original defect. A fix that only makes write_emit_ctx plumb the
159
+ fields but doesn't verify they land in the RESULT block is not a fix.
160
+ """
161
+
162
+ def setUp(self) -> None:
163
+ self._tmp = tempfile.TemporaryDirectory()
164
+ self.addCleanup(self._tmp.cleanup)
165
+ self.work_dir = pathlib.Path(self._tmp.name) / "work"
166
+ self.data_dir = pathlib.Path(self._tmp.name) / "data"
167
+ self.work_dir.mkdir()
168
+ self.data_dir.mkdir()
169
+
170
+ def test_end_to_end_tree_surfaces_partial_reason_in_result_block(self):
171
+ # 1. Tree on disk — depth-cap tripped, one pending flow.
172
+ (self.work_dir / "declared_action_tree.json").write_text(json.dumps({
173
+ "_partial": True,
174
+ "_partial_reason": "max-depth-cap",
175
+ "_pending_fetches": {
176
+ "FLOW": ["F6"],
177
+ "APEX": [],
178
+ "PROMPT_TEMPLATE": [],
179
+ "STANDARD_ACTION": [],
180
+ },
181
+ }))
182
+
183
+ # 2. write_emit_ctx — populates .emit_ctx.json from env + tree.
184
+ env = {
185
+ **os.environ,
186
+ "WORK_DIR": str(self.work_dir),
187
+ "DATA_DIR": str(self.data_dir),
188
+ "STATUS": "OK",
189
+ "AGENT_API_NAME": "MyBot",
190
+ "AGENT_VERSION": "v1",
191
+ "ORG_ID_15": "00D000000000ABC",
192
+ "BOT_ID": "0XxFAKEBOTID",
193
+ "NODE_COUNT": "5",
194
+ "DEPTH": "5",
195
+ "PARTIAL": "true",
196
+ "START_EPOCH": "0",
197
+ }
198
+ r1 = _run_script("write_emit_ctx.py", env)
199
+ self.assertEqual(r1.returncode, 0, msg=r1.stderr)
200
+
201
+ # 3. emit_result — reads .emit_ctx.json, prints RESULT block.
202
+ r2 = _run_script("emit_result.py", env)
203
+ self.assertEqual(r2.returncode, 0, msg=r2.stderr)
204
+
205
+ # 4. Assert the RESULT block carries the plumbed fields.
206
+ block = r2.stdout
207
+ self.assertIn("PARTIAL_REASON=max-depth-cap", block)
208
+ self.assertIn("PENDING_FETCHES_COUNT=1", block)
209
+ # Auto-promote: partial=True + status=OK → PARTIAL_OK.
210
+ self.assertIn("STATUS=PARTIAL_OK", block)
211
+ self.assertNotIn("STATUS=OK\n", block) # not the base OK status
212
+
213
+ def test_end_to_end_no_tree_produces_empty_partial_fields(self):
214
+ """Early-abort path (no tree) → RESULT block carries empties, not crashes."""
215
+ env = {
216
+ **os.environ,
217
+ "WORK_DIR": str(self.work_dir),
218
+ "DATA_DIR": str(self.data_dir),
219
+ "STATUS": "AGENT_NOT_FOUND",
220
+ "AGENT_API_NAME": "Missing",
221
+ "AGENT_VERSION": "",
222
+ "ORG_ID_15": "00D000000000ABC",
223
+ "BOT_ID": "",
224
+ "START_EPOCH": "0",
225
+ }
226
+ r1 = _run_script("write_emit_ctx.py", env)
227
+ self.assertEqual(r1.returncode, 0, msg=r1.stderr)
228
+ r2 = _run_script("emit_result.py", env)
229
+ self.assertEqual(r2.returncode, 0, msg=r2.stderr)
230
+
231
+ block = r2.stdout
232
+ self.assertIn("STATUS=AGENT_NOT_FOUND", block)
233
+ self.assertIn("PARTIAL_REASON=", block)
234
+ self.assertIn("PENDING_FETCHES_COUNT=0", block)
235
+
236
+
237
+ class WriteEmitCtxArchitectureSignalsTests(unittest.TestCase):
238
+ """write_emit_ctx must plumb architecture-render signals.
239
+
240
+ Cases:
241
+ * Render succeeded → architecture.md present, no sidecar →
242
+ ctx.architecture_path set, render_failed=False, detail="".
243
+ * Render failed → sidecar present → render_failed=True,
244
+ detail populated (truncated + redacted).
245
+ * Render not attempted → no file, no sidecar → empties,
246
+ render_failed=False. This is the early-abort / cache-hit path.
247
+ """
248
+
249
+ def setUp(self) -> None:
250
+ self._tmp = tempfile.TemporaryDirectory()
251
+ self.addCleanup(self._tmp.cleanup)
252
+ self.work_dir = pathlib.Path(self._tmp.name) / "work"
253
+ self.data_dir = pathlib.Path(self._tmp.name) / "data"
254
+ self.work_dir.mkdir()
255
+ self.data_dir.mkdir()
256
+
257
+ def _base_env(self, status: str = "OK") -> dict:
258
+ return {
259
+ **os.environ,
260
+ "WORK_DIR": str(self.work_dir),
261
+ "DATA_DIR": str(self.data_dir),
262
+ "STATUS": status,
263
+ "AGENT_API_NAME": "MyBot",
264
+ "AGENT_VERSION": "v1",
265
+ "ORG_ID_15": "00D000000000ABC",
266
+ "BOT_ID": "0XxFAKEBOTID",
267
+ "START_EPOCH": "0",
268
+ }
269
+
270
+ def _read_ctx(self) -> dict:
271
+ return json.loads((self.work_dir / ".emit_ctx.json").read_text())
272
+
273
+ def test_render_success_surfaces_architecture_path(self):
274
+ # filename is {agent}_{ver}_architecture.md.
275
+ (self.data_dir / "MyBot_v1_architecture.md").write_text("# Architecture\n")
276
+ result = _run_script("write_emit_ctx.py", self._base_env())
277
+ self.assertEqual(result.returncode, 0, msg=result.stderr)
278
+ ctx = self._read_ctx()
279
+ self.assertEqual(
280
+ ctx["architecture_path"],
281
+ str(self.data_dir / "MyBot_v1_architecture.md"),
282
+ )
283
+ self.assertFalse(ctx["render_failed"])
284
+ self.assertEqual(ctx["render_error_detail"], "")
285
+
286
+ def test_render_failure_surfaces_sidecar_detail(self):
287
+ (self.data_dir / "MyBot_v1_architecture.md.error").write_text(
288
+ "render_architecture failed: KeyError: 'agent'\n"
289
+ )
290
+ result = _run_script("write_emit_ctx.py", self._base_env())
291
+ self.assertEqual(result.returncode, 0, msg=result.stderr)
292
+ ctx = self._read_ctx()
293
+ self.assertTrue(ctx["render_failed"])
294
+ self.assertIn("KeyError", ctx["render_error_detail"])
295
+ # Sidecar's truncation + redaction happened — detail shouldn't
296
+ # exceed 200 chars even for a pathological sidecar.
297
+ self.assertLessEqual(len(ctx["render_error_detail"]), 200)
298
+ # No architecture.md present → architecture_path stays empty.
299
+ self.assertEqual(ctx["architecture_path"], "")
300
+
301
+ def test_render_failure_detail_is_redacted(self):
302
+ # Sidecar content mimics an exception that echoed back an
303
+ # Authorization header — the redactor must scrub it.
304
+ (self.data_dir / "MyBot_v1_architecture.md.error").write_text(
305
+ "render_architecture failed: RequestError: Authorization: Bearer TESTONLY_RENDER_TOKEN xyz\n"
306
+ )
307
+ result = _run_script("write_emit_ctx.py", self._base_env())
308
+ self.assertEqual(result.returncode, 0, msg=result.stderr)
309
+ ctx = self._read_ctx()
310
+ self.assertTrue(ctx["render_failed"])
311
+ self.assertNotIn("TESTONLY_RENDER_TOKEN", ctx["render_error_detail"])
312
+ self.assertIn("<redacted>", ctx["render_error_detail"])
313
+
314
+ def test_render_not_attempted_keeps_fields_empty(self):
315
+ # No architecture.md, no sidecar — e.g. early-abort paths.
316
+ result = _run_script(
317
+ "write_emit_ctx.py",
318
+ self._base_env(status="AGENT_NOT_FOUND"),
319
+ )
320
+ self.assertEqual(result.returncode, 0, msg=result.stderr)
321
+ ctx = self._read_ctx()
322
+ self.assertEqual(ctx["architecture_path"], "")
323
+ self.assertFalse(ctx["render_failed"])
324
+ self.assertEqual(ctx["render_error_detail"], "")
325
+
326
+ def test_render_success_and_failure_concurrent_surfaces_both(self):
327
+ # Edge case: architecture.md present + sidecar present. This
328
+ # shouldn't happen in practice (finalize writes one or the
329
+ # other), but the reader shouldn't mutate behaviour based on
330
+ # cross-key state — path present + render_failed=True.
331
+ (self.data_dir / "MyBot_v1_architecture.md").write_text("# Architecture\n")
332
+ (self.data_dir / "MyBot_v1_architecture.md.error").write_text(
333
+ "render_architecture failed: RuntimeError: partial output\n"
334
+ )
335
+ result = _run_script("write_emit_ctx.py", self._base_env())
336
+ self.assertEqual(result.returncode, 0, msg=result.stderr)
337
+ ctx = self._read_ctx()
338
+ self.assertTrue(ctx["render_failed"])
339
+ self.assertEqual(
340
+ ctx["architecture_path"],
341
+ str(self.data_dir / "MyBot_v1_architecture.md"),
342
+ )
343
+
344
+
345
+ class EmitResultArchitectureSignalsTests(unittest.TestCase):
346
+ """emit_result surfaces architecture + render-failure fields.
347
+
348
+ The integration tests in TreeToResultBlockEndToEndTests cover the
349
+ write_emit_ctx -> emit_result handoff; these tests exercise
350
+ emit_result's block-shaping logic in isolation via `build_block`.
351
+ """
352
+
353
+ def _import_mod(self):
354
+ import importlib.util
355
+ spec = importlib.util.spec_from_file_location(
356
+ "emit_result_mod", _tools_dir() / "emit_result.py",
357
+ )
358
+ assert spec is not None and spec.loader is not None
359
+ mod = importlib.util.module_from_spec(spec)
360
+ spec.loader.exec_module(mod)
361
+ return mod
362
+
363
+ def test_render_ok_emits_architecture_path_and_render_failed_false(self):
364
+ mod = self._import_mod()
365
+ ctx = {
366
+ "status": "OK",
367
+ "architecture_path": "/tmp/data/architecture.md",
368
+ "render_failed": False,
369
+ }
370
+ block = mod.build_block(ctx, wall_time_seconds=0.5)
371
+ self.assertIn("OUTPUT_ARCHITECTURE_PATH=/tmp/data/architecture.md", block)
372
+ self.assertIn("RENDER_FAILED=false", block)
373
+ # Detail key is ONLY emitted on failure — must NOT appear here.
374
+ self.assertNotIn("RENDER_ERROR_DETAIL=", block)
375
+ # Status stays OK.
376
+ self.assertIn("STATUS=OK", block)
377
+ self.assertNotIn("STATUS=PARTIAL_OK", block)
378
+
379
+ def test_render_failed_auto_promotes_to_partial_ok(self):
380
+ mod = self._import_mod()
381
+ ctx = {
382
+ "status": "OK",
383
+ "architecture_path": "", # render failed, no file produced
384
+ "render_failed": True,
385
+ "render_error_detail": "KeyError: 'agent'",
386
+ }
387
+ block = mod.build_block(ctx, wall_time_seconds=0.5)
388
+ self.assertIn("STATUS=PARTIAL_OK", block)
389
+ self.assertIn("RENDER_FAILED=true", block)
390
+ self.assertIn("RENDER_ERROR_DETAIL=KeyError: 'agent'", block)
391
+ # PARTIAL_REASON pinned to render-failed when the tree didn't
392
+ # already claim a reason.
393
+ self.assertIn("PARTIAL_REASON=render-failed", block)
394
+ # OUTPUT_ARCHITECTURE_PATH always emitted, empty on failure.
395
+ self.assertIn("OUTPUT_ARCHITECTURE_PATH=", block)
396
+
397
+ def test_render_failed_preserves_tree_partial_reason(self):
398
+ # When the tree is ALREADY partial with a reason, render_failed
399
+ # must NOT clobber the tree's reason — the tree's signal is more
400
+ # informative for triage.
401
+ mod = self._import_mod()
402
+ ctx = {
403
+ "status": "OK",
404
+ "partial": True,
405
+ "partial_reason": "max-depth-cap",
406
+ "render_failed": True,
407
+ "render_error_detail": "RuntimeError: ...",
408
+ }
409
+ block = mod.build_block(ctx, wall_time_seconds=0.5)
410
+ self.assertIn("PARTIAL_REASON=max-depth-cap", block)
411
+ self.assertNotIn("PARTIAL_REASON=render-failed", block)
412
+ # Still surfaces as PARTIAL_OK (either the tree or the render
413
+ # flag is sufficient to trip the auto-promote).
414
+ self.assertIn("STATUS=PARTIAL_OK", block)
415
+ self.assertIn("RENDER_FAILED=true", block)
416
+
417
+ def test_render_failed_does_not_clobber_error_status(self):
418
+ mod = self._import_mod()
419
+ ctx = {
420
+ "status": "AUTH_REQUIRED",
421
+ "render_failed": True,
422
+ "render_error_detail": "ignored under error status",
423
+ }
424
+ block = mod.build_block(ctx, wall_time_seconds=0.1)
425
+ self.assertIn("STATUS=AUTH_REQUIRED", block)
426
+ self.assertNotIn("STATUS=PARTIAL_OK", block)
427
+ # The render fields still surface for diagnostics.
428
+ self.assertIn("RENDER_FAILED=true", block)
429
+
430
+
431
+ class EmitResultPositionalSafetyTests(unittest.TestCase):
432
+ """emit_result must assert lines[1] starts with 'STATUS=' before rewrite.
433
+
434
+ The auto-promote branch (PARTIAL → PARTIAL_OK) rewrites `lines[1]`
435
+ in place. If a future refactor reorders the header, the rewrite
436
+ would clobber the wrong line. The assertion catches that at test
437
+ time instead of silently producing a malformed RESULT block in prod.
438
+ """
439
+
440
+ def test_build_block_happy_path_auto_promotes_without_asserting(self):
441
+ """Sanity: the assertion does NOT fire under normal operation."""
442
+ # Force import of emit_result from the tools/ dir.
443
+ import importlib.util
444
+ spec = importlib.util.spec_from_file_location(
445
+ "emit_result_mod", _tools_dir() / "emit_result.py",
446
+ )
447
+ assert spec is not None and spec.loader is not None
448
+ mod = importlib.util.module_from_spec(spec)
449
+ spec.loader.exec_module(mod)
450
+
451
+ ctx = {
452
+ "status": "OK",
453
+ "partial": True,
454
+ "partial_reason": "max-depth-cap",
455
+ "pending_fetches_count": 3,
456
+ "node_count": 10,
457
+ "depth": 5,
458
+ }
459
+ block = mod.build_block(ctx, wall_time_seconds=1.23)
460
+ self.assertIn("STATUS=PARTIAL_OK", block)
461
+ self.assertIn("PARTIAL_REASON=max-depth-cap", block)
462
+ self.assertIn("PENDING_FETCHES_COUNT=3", block)
463
+
464
+ def test_positional_assertion_fires_when_header_reordered(self):
465
+ """Simulate a refactor that puts something other than STATUS at lines[1].
466
+
467
+ We construct a fake `lines` list directly and run the same
468
+ assertion the production code runs. This is the exact guarantee
469
+ adds — the production assert catches the drift.
470
+ """
471
+ # This mirrors the production code's assertion verbatim.
472
+ lines = ["=== RESULT ===", "WAS_MOVED=true", "STATUS=OK"]
473
+ with self.assertRaises(AssertionError):
474
+ assert lines[1].startswith("STATUS="), (
475
+ f"emit block reordered — lines[1]={lines[1]!r}"
476
+ )
477
+
478
+
479
+ if __name__ == "__main__":
480
+ unittest.main()
@@ -0,0 +1,157 @@
1
+ #!/usr/bin/env python3
2
+ """Generic JSON-in → shell-export-out extractor.
3
+
4
+ Reads a JSON document (from stdin or a file), traverses it by dot-path specs,
5
+ and emits `export KEY=shlex.quote(VALUE)` lines on stdout. Callers consume via
6
+ `eval "$(python3 emit_env.py ...)"`.
7
+
8
+ Replaces four single-purpose parsers that previously existed as inline Python
9
+ heredocs in the agent:
10
+ * sf org display result parser
11
+ * Q1 row-count + SESSION_ACTIVE derivation
12
+ * _cache_meta.json reader (CACHED_AT_UTC, SESSION_ACTIVE_AT_CACHE)
13
+ * individual field extractors
14
+
15
+ Field-spec DSL:
16
+ EXPORT_KEY:path — plain dot-path traversal
17
+ EXPORT_KEY:path[:N] — string slice after traversal (first N chars)
18
+ EXPORT_KEY:path.length — length of array / string at path
19
+ EXPORT_KEY:path.empty — "true" if missing or empty string, else "false"
20
+
21
+ The DSL is deliberately small: three operators, no dot-inside-key escaping.
22
+ Any path component is a dictionary key OR an integer array index
23
+ (`data[0].ssot__Foo` works). If the path doesn't resolve, the emitted value
24
+ is the empty string — caller decides whether that's OK.
25
+
26
+ Usage:
27
+ # Parse `sf org display --json` output via stdin
28
+ eval "$(printf '%s' "$_SF_JSON" | python3 emit_env.py stdin \\
29
+ 'ACCESS_TOKEN:result.accessToken' \\
30
+ 'INSTANCE_URL:result.instanceUrl' \\
31
+ 'ORG_ID_18:result.id' \\
32
+ 'ORG_ID_15:result.id[:15]')"
33
+
34
+ # Q1 post-wave: derive SESSION_ACTIVE from row 0's end timestamp
35
+ eval "$(python3 emit_env.py "$WORK_DIR/_q1_body.json" \\
36
+ 'SESSION_ACTIVE:data[0].ssot__EndTimestamp__c.empty')"
37
+
38
+ # Cache-hit meta read
39
+ eval "$(python3 emit_env.py "$WORK_DIR/$SID.dc.json" \\
40
+ 'CACHED_AT_UTC:_cache_meta.cached_at_utc' \\
41
+ 'SESSION_ACTIVE:_cache_meta.session_active_at_cache')"
42
+
43
+ Inputs:
44
+ argv[1] 'stdin' or an absolute file path
45
+ argv[2+] field specs (at least one)
46
+
47
+ Outputs:
48
+ stdout zero or more `export K=V` lines
49
+ exit 0 always (missing keys just produce empty values)
50
+ exit 1 bad argv, unparseable JSON, or I/O error
51
+ """
52
+ import json
53
+ import pathlib
54
+ import re
55
+ import shlex
56
+ import sys
57
+
58
+ SLICE_RE = re.compile(r"^(.*?)\[:(\d+)\]$")
59
+
60
+
61
+ def traverse(data, path: str):
62
+ # Path components are dot-separated. Numeric components index into arrays;
63
+ # everything else is a dict key. Missing/invalid → None (emitted as empty).
64
+ node = data
65
+ for part in path.split("."):
66
+ if node is None:
67
+ return None
68
+ if part.isdigit():
69
+ try:
70
+ node = node[int(part)]
71
+ except (IndexError, KeyError, TypeError):
72
+ return None
73
+ elif isinstance(node, dict):
74
+ node = node.get(part)
75
+ else:
76
+ return None
77
+ return node
78
+
79
+
80
+ def apply_spec(data, spec: str) -> tuple[str, str]:
81
+ # spec: "EXPORT_KEY:path<operator>"
82
+ if ":" not in spec:
83
+ raise ValueError(f"spec missing colon: {spec}")
84
+ key, rhs = spec.split(":", 1)
85
+ if not key:
86
+ raise ValueError(f"spec has empty key: {spec}")
87
+
88
+ # Resolve the operator suffix on rhs. Check slice LAST because it's the
89
+ # only one that doesn't alter the traversed value's type.
90
+ if rhs.endswith(".length"):
91
+ value = traverse(data, rhs[:-7])
92
+ if value is None:
93
+ return key, "0"
94
+ if isinstance(value, (list, str)):
95
+ return key, str(len(value))
96
+ return key, "0"
97
+
98
+ if rhs.endswith(".empty"):
99
+ value = traverse(data, rhs[:-6])
100
+ is_empty = value is None or value == ""
101
+ return key, "true" if is_empty else "false"
102
+
103
+ m = SLICE_RE.match(rhs)
104
+ if m:
105
+ path, n = m.group(1), int(m.group(2))
106
+ value = traverse(data, path)
107
+ if value is None:
108
+ return key, ""
109
+ return key, str(value)[:n]
110
+
111
+ # Plain traversal.
112
+ value = traverse(data, rhs)
113
+ if value is None:
114
+ return key, ""
115
+ if isinstance(value, bool):
116
+ # Python `True` / `False` → `true` / `false` so bash string compares
117
+ # work without an extra lowercase step.
118
+ return key, "true" if value else "false"
119
+ return key, str(value)
120
+
121
+
122
+ def main() -> int:
123
+ if len(sys.argv) < 3:
124
+ sys.stderr.write("emit_env.py: need source ('stdin' or path) and at least one field spec\n")
125
+ return 1
126
+
127
+ source = sys.argv[1]
128
+ specs = sys.argv[2:]
129
+
130
+ try:
131
+ if source == "stdin":
132
+ raw = sys.stdin.read()
133
+ else:
134
+ raw = pathlib.Path(source).read_text()
135
+ except OSError as e:
136
+ sys.stderr.write(f"emit_env.py: cannot read {source}: {e}\n")
137
+ return 1
138
+
139
+ try:
140
+ data = json.loads(raw)
141
+ except json.JSONDecodeError as e:
142
+ sys.stderr.write(f"emit_env.py: JSON parse error: {e}\n")
143
+ return 1
144
+
145
+ for spec in specs:
146
+ try:
147
+ key, value = apply_spec(data, spec)
148
+ except ValueError as e:
149
+ sys.stderr.write(f"emit_env.py: {e}\n")
150
+ return 1
151
+ sys.stdout.write(f"export {key}={shlex.quote(value)}\n")
152
+
153
+ return 0
154
+
155
+
156
+ if __name__ == "__main__":
157
+ sys.exit(main())