@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.
- package/package.json +1 -1
- package/skills/activating-datacloud/SKILL.md +0 -1
- package/skills/analyzing-omnistudio-dependencies/SKILL.md +0 -1
- package/skills/applying-slds/SKILL.md +322 -0
- package/skills/applying-slds/checklists.md +83 -0
- package/skills/applying-slds/examples.md +283 -0
- package/skills/applying-slds/guidance/README.md +83 -0
- package/skills/applying-slds/guidance/blueprints-index.md +213 -0
- package/skills/applying-slds/guidance/icons-guidance.md +186 -0
- package/skills/applying-slds/guidance/overviews/borders.md +236 -0
- package/skills/applying-slds/guidance/overviews/color.md +266 -0
- package/skills/applying-slds/guidance/overviews/display-density.md +366 -0
- package/skills/applying-slds/guidance/overviews/icons.md +240 -0
- package/skills/applying-slds/guidance/overviews/illustrations.md +235 -0
- package/skills/applying-slds/guidance/overviews/shadows.md +176 -0
- package/skills/applying-slds/guidance/overviews/spacing.md +216 -0
- package/skills/applying-slds/guidance/overviews/typography.md +323 -0
- package/skills/applying-slds/guidance/overviews/utilities.md +542 -0
- package/skills/applying-slds/guidance/slds-development-guide.md +288 -0
- package/skills/applying-slds/guidance/styling-hooks/borders.md +202 -0
- package/skills/applying-slds/guidance/styling-hooks/color/expressive-palette-hooks.md +153 -0
- package/skills/applying-slds/guidance/styling-hooks/color/index.md +171 -0
- package/skills/applying-slds/guidance/styling-hooks/color/semantic/accent-hooks.md +204 -0
- package/skills/applying-slds/guidance/styling-hooks/color/semantic/feedback-hooks.md +768 -0
- package/skills/applying-slds/guidance/styling-hooks/color/semantic/surface-hooks.md +337 -0
- package/skills/applying-slds/guidance/styling-hooks/color/system-hooks.md +132 -0
- package/skills/applying-slds/guidance/styling-hooks/index.md +327 -0
- package/skills/applying-slds/guidance/styling-hooks/shadows.md +238 -0
- package/skills/applying-slds/guidance/styling-hooks/spacing.md +254 -0
- package/skills/applying-slds/guidance/styling-hooks/typography.md +448 -0
- package/skills/applying-slds/guidance/utilities/alignment.md +119 -0
- package/skills/applying-slds/guidance/utilities/borders.md +131 -0
- package/skills/applying-slds/guidance/utilities/box.md +125 -0
- package/skills/applying-slds/guidance/utilities/color.md +165 -0
- package/skills/applying-slds/guidance/utilities/dark-mode.md +111 -0
- package/skills/applying-slds/guidance/utilities/description-list.md +168 -0
- package/skills/applying-slds/guidance/utilities/floats.md +117 -0
- package/skills/applying-slds/guidance/utilities/grid.md +264 -0
- package/skills/applying-slds/guidance/utilities/horizontal-list.md +110 -0
- package/skills/applying-slds/guidance/utilities/hyphenation.md +84 -0
- package/skills/applying-slds/guidance/utilities/index.md +205 -0
- package/skills/applying-slds/guidance/utilities/interactions.md +89 -0
- package/skills/applying-slds/guidance/utilities/layout.md +109 -0
- package/skills/applying-slds/guidance/utilities/line-clamp.md +131 -0
- package/skills/applying-slds/guidance/utilities/margin.md +155 -0
- package/skills/applying-slds/guidance/utilities/media-object.md +161 -0
- package/skills/applying-slds/guidance/utilities/name-value-list.md +152 -0
- package/skills/applying-slds/guidance/utilities/padding.md +155 -0
- package/skills/applying-slds/guidance/utilities/position.md +177 -0
- package/skills/applying-slds/guidance/utilities/print.md +114 -0
- package/skills/applying-slds/guidance/utilities/scrollable.md +126 -0
- package/skills/applying-slds/guidance/utilities/sizing.md +190 -0
- package/skills/applying-slds/guidance/utilities/themes.md +121 -0
- package/skills/applying-slds/guidance/utilities/truncate.md +127 -0
- package/skills/applying-slds/guidance/utilities/typography.md +166 -0
- package/skills/applying-slds/guidance/utilities/vertical-list.md +166 -0
- package/skills/applying-slds/guidance/utilities/visibility.md +228 -0
- package/skills/applying-slds/metadata/README.md +84 -0
- package/skills/applying-slds/metadata/blueprints/components/accordion.yaml +304 -0
- package/skills/applying-slds/metadata/blueprints/components/activity-timeline.yaml +92 -0
- package/skills/applying-slds/metadata/blueprints/components/alert.yaml +103 -0
- package/skills/applying-slds/metadata/blueprints/components/app-launcher.yaml +94 -0
- package/skills/applying-slds/metadata/blueprints/components/avatar-group.yaml +81 -0
- package/skills/applying-slds/metadata/blueprints/components/avatar.yaml +97 -0
- package/skills/applying-slds/metadata/blueprints/components/badges.yaml +102 -0
- package/skills/applying-slds/metadata/blueprints/components/brand-band.yaml +198 -0
- package/skills/applying-slds/metadata/blueprints/components/breadcrumbs.yaml +95 -0
- package/skills/applying-slds/metadata/blueprints/components/builder-header.yaml +192 -0
- package/skills/applying-slds/metadata/blueprints/components/button-groups.yaml +82 -0
- package/skills/applying-slds/metadata/blueprints/components/button-icons.yaml +295 -0
- package/skills/applying-slds/metadata/blueprints/components/buttons.yaml +230 -0
- package/skills/applying-slds/metadata/blueprints/components/cards.yaml +124 -0
- package/skills/applying-slds/metadata/blueprints/components/carousel.yaml +140 -0
- package/skills/applying-slds/metadata/blueprints/components/chat.yaml +179 -0
- package/skills/applying-slds/metadata/blueprints/components/checkbox-button-group.yaml +192 -0
- package/skills/applying-slds/metadata/blueprints/components/checkbox-button.yaml +204 -0
- package/skills/applying-slds/metadata/blueprints/components/checkbox-toggle.yaml +177 -0
- package/skills/applying-slds/metadata/blueprints/components/checkbox.yaml +108 -0
- package/skills/applying-slds/metadata/blueprints/components/color-picker.yaml +172 -0
- package/skills/applying-slds/metadata/blueprints/components/combobox.yaml +136 -0
- package/skills/applying-slds/metadata/blueprints/components/counter.yaml +147 -0
- package/skills/applying-slds/metadata/blueprints/components/data-tables.yaml +157 -0
- package/skills/applying-slds/metadata/blueprints/components/datepickers.yaml +130 -0
- package/skills/applying-slds/metadata/blueprints/components/datetime-picker.yaml +155 -0
- package/skills/applying-slds/metadata/blueprints/components/docked-composer.yaml +201 -0
- package/skills/applying-slds/metadata/blueprints/components/docked-form-footer.yaml +161 -0
- package/skills/applying-slds/metadata/blueprints/components/docked-utility-bar.yaml +175 -0
- package/skills/applying-slds/metadata/blueprints/components/drop-zone.yaml +115 -0
- package/skills/applying-slds/metadata/blueprints/components/dueling-picklist.yaml +196 -0
- package/skills/applying-slds/metadata/blueprints/components/dynamic-icons.yaml +128 -0
- package/skills/applying-slds/metadata/blueprints/components/dynamic-menu.yaml +141 -0
- package/skills/applying-slds/metadata/blueprints/components/expandable-section.yaml +115 -0
- package/skills/applying-slds/metadata/blueprints/components/expression.yaml +143 -0
- package/skills/applying-slds/metadata/blueprints/components/feeds.yaml +125 -0
- package/skills/applying-slds/metadata/blueprints/components/file-selector.yaml +154 -0
- package/skills/applying-slds/metadata/blueprints/components/files.yaml +119 -0
- package/skills/applying-slds/metadata/blueprints/components/form-element.yaml +145 -0
- package/skills/applying-slds/metadata/blueprints/components/global-header.yaml +120 -0
- package/skills/applying-slds/metadata/blueprints/components/global-navigation.yaml +100 -0
- package/skills/applying-slds/metadata/blueprints/components/icons.yaml +138 -0
- package/skills/applying-slds/metadata/blueprints/components/illustration.yaml +205 -0
- package/skills/applying-slds/metadata/blueprints/components/input.yaml +151 -0
- package/skills/applying-slds/metadata/blueprints/components/list-builder.yaml +127 -0
- package/skills/applying-slds/metadata/blueprints/components/lookups.yaml +132 -0
- package/skills/applying-slds/metadata/blueprints/components/map.yaml +118 -0
- package/skills/applying-slds/metadata/blueprints/components/menus.yaml +134 -0
- package/skills/applying-slds/metadata/blueprints/components/modals.yaml +152 -0
- package/skills/applying-slds/metadata/blueprints/components/notifications.yaml +88 -0
- package/skills/applying-slds/metadata/blueprints/components/page-headers.yaml +135 -0
- package/skills/applying-slds/metadata/blueprints/components/panels.yaml +149 -0
- package/skills/applying-slds/metadata/blueprints/components/path.yaml +154 -0
- package/skills/applying-slds/metadata/blueprints/components/picklist.yaml +125 -0
- package/skills/applying-slds/metadata/blueprints/components/pills.yaml +154 -0
- package/skills/applying-slds/metadata/blueprints/components/popovers.yaml +120 -0
- package/skills/applying-slds/metadata/blueprints/components/progress-bar.yaml +110 -0
- package/skills/applying-slds/metadata/blueprints/components/progress-indicator.yaml +133 -0
- package/skills/applying-slds/metadata/blueprints/components/progress-ring.yaml +102 -0
- package/skills/applying-slds/metadata/blueprints/components/prompt.yaml +126 -0
- package/skills/applying-slds/metadata/blueprints/components/publishers.yaml +178 -0
- package/skills/applying-slds/metadata/blueprints/components/radio-button-group.yaml +172 -0
- package/skills/applying-slds/metadata/blueprints/components/radio-group.yaml +112 -0
- package/skills/applying-slds/metadata/blueprints/components/rich-text-editor.yaml +135 -0
- package/skills/applying-slds/metadata/blueprints/components/scoped-notifications.yaml +188 -0
- package/skills/applying-slds/metadata/blueprints/components/scoped-tabs.yaml +97 -0
- package/skills/applying-slds/metadata/blueprints/components/select.yaml +127 -0
- package/skills/applying-slds/metadata/blueprints/components/setup-assistant.yaml +152 -0
- package/skills/applying-slds/metadata/blueprints/components/slider.yaml +111 -0
- package/skills/applying-slds/metadata/blueprints/components/spinners.yaml +135 -0
- package/skills/applying-slds/metadata/blueprints/components/split-view.yaml +112 -0
- package/skills/applying-slds/metadata/blueprints/components/summary-detail.yaml +103 -0
- package/skills/applying-slds/metadata/blueprints/components/tabs.yaml +138 -0
- package/skills/applying-slds/metadata/blueprints/components/textarea.yaml +116 -0
- package/skills/applying-slds/metadata/blueprints/components/tiles.yaml +108 -0
- package/skills/applying-slds/metadata/blueprints/components/timepicker.yaml +111 -0
- package/skills/applying-slds/metadata/blueprints/components/toast.yaml +154 -0
- package/skills/applying-slds/metadata/blueprints/components/tooltips.yaml +107 -0
- package/skills/applying-slds/metadata/blueprints/components/tree-grid.yaml +116 -0
- package/skills/applying-slds/metadata/blueprints/components/trees.yaml +116 -0
- package/skills/applying-slds/metadata/blueprints/components/trial-bar.yaml +112 -0
- package/skills/applying-slds/metadata/blueprints/components/vertical-navigation.yaml +130 -0
- package/skills/applying-slds/metadata/blueprints/components/vertical-tabs.yaml +140 -0
- package/skills/applying-slds/metadata/blueprints/components/visual-picker.yaml +150 -0
- package/skills/applying-slds/metadata/blueprints/components/welcome-mat.yaml +136 -0
- package/skills/applying-slds/metadata/hooks-index.json +6272 -0
- package/skills/applying-slds/metadata/icon-metadata.json +38466 -0
- package/skills/applying-slds/metadata/utilities-index.json +21912 -0
- package/skills/applying-slds/references/component-selection.md +112 -0
- package/skills/applying-slds/references/icons-decision-guide.md +124 -0
- package/skills/applying-slds/references/styling-decision-guide.md +228 -0
- package/skills/applying-slds/references/utilities-quick-ref.md +125 -0
- package/skills/applying-slds/scripts/search-blueprints.cjs +117 -0
- package/skills/applying-slds/scripts/search-hooks.cjs +139 -0
- package/skills/applying-slds/scripts/search-icons.cjs +174 -0
- package/skills/applying-slds/scripts/search-utilities.cjs +161 -0
- package/skills/building-mobile-apps/SKILL.md +0 -1
- package/skills/building-omnistudio-callable-apex/SKILL.md +0 -1
- package/skills/building-omnistudio-datamapper/SKILL.md +0 -1
- package/skills/building-omnistudio-flexcard/SKILL.md +0 -1
- package/skills/building-omnistudio-integration-procedure/SKILL.md +0 -1
- package/skills/building-omnistudio-omniscript/SKILL.md +0 -1
- package/skills/building-sf-integrations/SKILL.md +0 -1
- package/skills/configuring-connected-apps/SKILL.md +0 -1
- package/skills/connecting-datacloud/SKILL.md +0 -1
- package/skills/creating-b2b-commerce-store/SKILL.md +0 -1
- package/skills/debugging-apex-logs/SKILL.md +0 -1
- package/skills/deploying-metadata/SKILL.md +0 -1
- package/skills/deploying-omnistudio-datapacks/SKILL.md +0 -1
- package/skills/developing-agentforce/SKILL.md +0 -1
- package/skills/fetching-salesforce-docs/SKILL.md +0 -1
- package/skills/generating-custom-lightning-type/SKILL.md +17 -39
- package/skills/generating-custom-lightning-type/assets/primitive-types-and-constraints.md +41 -0
- package/skills/generating-custom-lightning-type/references/widget-rendition.md +124 -0
- package/skills/generating-lwc-components/SKILL.md +0 -1
- package/skills/generating-mermaid-diagrams/SKILL.md +0 -1
- package/skills/generating-visual-diagrams/SKILL.md +0 -1
- package/skills/handling-sf-data/SKILL.md +0 -1
- package/skills/harmonizing-datacloud/SKILL.md +0 -1
- package/skills/integrating-b2b-commerce-open-code-components/SKILL.md +0 -1
- package/skills/investigating-agentforce-architecture/README.md +156 -0
- package/skills/investigating-agentforce-architecture/SKILL.md +230 -0
- package/skills/investigating-agentforce-architecture/assets/cli/describe_sobject.yaml +16 -0
- package/skills/investigating-agentforce-architecture/assets/cli/describe_tooling_sobject.yaml +17 -0
- package/skills/investigating-agentforce-architecture/assets/cli/list_metadata_genaiprompttemplate.yaml +17 -0
- package/skills/investigating-agentforce-architecture/assets/cli/org_display.yaml +15 -0
- package/skills/investigating-agentforce-architecture/assets/cli/retrieve_genai_plugin.yaml +18 -0
- package/skills/investigating-agentforce-architecture/assets/cli/show_access_token.yaml +27 -0
- package/skills/investigating-agentforce-architecture/assets/mermaid/action_tree.mmd +20 -0
- package/skills/investigating-agentforce-architecture/assets/mermaid/data_flow.mmd +19 -0
- package/skills/investigating-agentforce-architecture/assets/mermaid/dependency_graph.mmd +19 -0
- package/skills/investigating-agentforce-architecture/assets/mermaid/invocation_sequence.mmd +20 -0
- package/skills/investigating-agentforce-architecture/assets/mermaid/planner_state.mmd +18 -0
- package/skills/investigating-agentforce-architecture/assets/soql/apex_class_bodies_by_ids.soql +3 -0
- package/skills/investigating-agentforce-architecture/assets/soql/apex_class_bodies_by_names.soql +3 -0
- package/skills/investigating-agentforce-architecture/assets/soql/bot_definition_details.soql +3 -0
- package/skills/investigating-agentforce-architecture/assets/soql/bot_version_lookup.soql +4 -0
- package/skills/investigating-agentforce-architecture/assets/soql/flow_definition_by_ids.soql +3 -0
- package/skills/investigating-agentforce-architecture/assets/soql/flow_definition_ids_by_names.soql +3 -0
- package/skills/investigating-agentforce-architecture/assets/soql/flow_definition_view_by_durable_ids.soql +4 -0
- package/skills/investigating-agentforce-architecture/assets/soql/flow_metadata_by_id.soql +3 -0
- package/skills/investigating-agentforce-architecture/assets/soql/functions_by_plugins.soql +5 -0
- package/skills/investigating-agentforce-architecture/assets/soql/planner_attrs_by_parent_ids.soql +3 -0
- package/skills/investigating-agentforce-architecture/assets/soql/planner_bundle_functions.soql +3 -0
- package/skills/investigating-agentforce-architecture/assets/soql/planner_definition_by_agent_chain.soql +3 -0
- package/skills/investigating-agentforce-architecture/assets/soql/plugin_functions_by_plugin_ids.soql +3 -0
- package/skills/investigating-agentforce-architecture/assets/soql/plugin_instructions_by_plugin_ids.soql +3 -0
- package/skills/investigating-agentforce-architecture/assets/soql/plugins_by_planner.soql +4 -0
- package/skills/investigating-agentforce-architecture/references/architecture_sections.md +243 -0
- package/skills/investigating-agentforce-architecture/references/contract.json +244 -0
- package/skills/investigating-agentforce-architecture/references/soql_fields.md +512 -0
- package/skills/investigating-agentforce-architecture/scripts/_shared/__init__.py +1 -0
- package/skills/investigating-agentforce-architecture/scripts/_shared/fs_guard.py +329 -0
- package/skills/investigating-agentforce-architecture/scripts/_shared/paths.py +110 -0
- package/skills/investigating-agentforce-architecture/scripts/_shared/runtime.py +59 -0
- package/skills/investigating-agentforce-architecture/scripts/_shared/sql.py +10 -0
- package/skills/investigating-agentforce-architecture/scripts/cache_check.py +234 -0
- package/skills/investigating-agentforce-architecture/scripts/config.py +131 -0
- package/skills/investigating-agentforce-architecture/scripts/fetch_soql.py +689 -0
- package/skills/investigating-agentforce-architecture/scripts/finalize.py +295 -0
- package/skills/investigating-agentforce-architecture/scripts/main.py +2835 -0
- package/skills/investigating-agentforce-architecture/scripts/metadata_listing.py +265 -0
- package/skills/investigating-agentforce-architecture/scripts/parallel_retrieve.py +69 -0
- package/skills/investigating-agentforce-architecture/scripts/parse_bundle.py +215 -0
- package/skills/investigating-agentforce-architecture/scripts/parse_wave.py +845 -0
- package/skills/investigating-agentforce-architecture/scripts/probe_channels.py +302 -0
- package/skills/investigating-agentforce-architecture/scripts/render_architecture.py +1043 -0
- package/skills/investigating-agentforce-architecture/scripts/resolve_bot.py +255 -0
- package/skills/investigating-agentforce-architecture/scripts/resolve_invocation_target.py +130 -0
- package/skills/investigating-agentforce-architecture/scripts/rest_client.py +763 -0
- package/skills/investigating-agentforce-architecture/scripts/retrieve_planner.py +13 -0
- package/skills/investigating-agentforce-architecture/scripts/sf_cli.py +242 -0
- package/skills/investigating-agentforce-architecture/scripts/soql_loader.py +253 -0
- package/skills/investigating-agentforce-architecture/scripts/summarize_tree.py +143 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/__init__.py +0 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/_bootstrap.py +23 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/fixtures/__init__.py +0 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/fixtures/genai_payloads.py +400 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_cache_check.py +307 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_cache_check_main.py +283 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_config.py +115 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_end_to_end_fixture.py +651 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_finalize.py +278 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_flow_children_inflation.py +582 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_fs_guard.py +113 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_iterative_wave_b.py +478 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_main_pipeline.py +3359 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_parallel_retrieve.py +131 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_parse_bundle.py +400 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_parse_wave.py +644 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_parse_wave_classifiers.py +224 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_parse_wave_helpers.py +380 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_parse_wave_main.py +397 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_per_branch_visited.py +244 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_probe_channels.py +359 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_probe_cli_recipes.py +185 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_render_architecture.py +810 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_resolve_bot.py +203 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_resolve_creds.py +157 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_resolve_invocation_target.py +145 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_rest_client.py +1253 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_runtime_override.py +100 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_sf_cli.py +261 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_signature_stamping.py +466 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_soql_loader.py +501 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_summarize_tree.py +241 -0
- package/skills/investigating-agentforce-architecture/scripts/tests/test_write_emit_ctx.py +480 -0
- package/skills/investigating-agentforce-architecture/tools/emit_env.py +157 -0
- package/skills/investigating-agentforce-architecture/tools/emit_result.py +262 -0
- package/skills/investigating-agentforce-architecture/tools/sanitize.py +33 -0
- package/skills/investigating-agentforce-architecture/tools/write_emit_ctx.py +332 -0
- package/skills/investigating-agentforce-d360/README.md +123 -0
- package/skills/investigating-agentforce-d360/SKILL.md +163 -0
- package/skills/investigating-agentforce-d360/assets/dc/app_generation.sql +51 -0
- package/skills/investigating-agentforce-d360/assets/dc/content_category.sql +44 -0
- package/skills/investigating-agentforce-d360/assets/dc/content_quality.sql +41 -0
- package/skills/investigating-agentforce-d360/assets/dc/discover_sessions.sql +36 -0
- package/skills/investigating-agentforce-d360/assets/dc/feedback.sql +47 -0
- package/skills/investigating-agentforce-d360/assets/dc/feedback_details.sql +38 -0
- package/skills/investigating-agentforce-d360/assets/dc/gateway_records.sql +45 -0
- package/skills/investigating-agentforce-d360/assets/dc/gateway_request_llm.sql +50 -0
- package/skills/investigating-agentforce-d360/assets/dc/gateway_request_metadata.sql +44 -0
- package/skills/investigating-agentforce-d360/assets/dc/gateway_request_tags.sql +42 -0
- package/skills/investigating-agentforce-d360/assets/dc/gateway_requests.sql +89 -0
- package/skills/investigating-agentforce-d360/assets/dc/gateway_responses.sql +43 -0
- package/skills/investigating-agentforce-d360/assets/dc/generations.sql +52 -0
- package/skills/investigating-agentforce-d360/assets/dc/interactions.sql +53 -0
- package/skills/investigating-agentforce-d360/assets/dc/messages.sql +53 -0
- package/skills/investigating-agentforce-d360/assets/dc/messaging_session.sql +37 -0
- package/skills/investigating-agentforce-d360/assets/dc/moment_interactions.sql +34 -0
- package/skills/investigating-agentforce-d360/assets/dc/moments.sql +39 -0
- package/skills/investigating-agentforce-d360/assets/dc/participants.sql +48 -0
- package/skills/investigating-agentforce-d360/assets/dc/sessions.sql +78 -0
- package/skills/investigating-agentforce-d360/assets/dc/steps.sql +64 -0
- package/skills/investigating-agentforce-d360/assets/dc/tag_associations.sql +46 -0
- package/skills/investigating-agentforce-d360/assets/dc/tag_definition_associations.sql +37 -0
- package/skills/investigating-agentforce-d360/assets/dc/tag_definitions.sql +50 -0
- package/skills/investigating-agentforce-d360/assets/dc/tags.sql +37 -0
- package/skills/investigating-agentforce-d360/assets/dc/telemetry_spans.sql +55 -0
- package/skills/investigating-agentforce-d360/references/artifacts.md +50 -0
- package/skills/investigating-agentforce-d360/references/dc_dmo_fields.md +823 -0
- package/skills/investigating-agentforce-d360/references/dc_pipeline_contract.md +608 -0
- package/skills/investigating-agentforce-d360/scripts/_shared/__init__.py +2 -0
- package/skills/investigating-agentforce-d360/scripts/_shared/cli_override.py +98 -0
- package/skills/investigating-agentforce-d360/scripts/_shared/fs_guard.py +334 -0
- package/skills/investigating-agentforce-d360/scripts/_shared/paths.py +155 -0
- package/skills/investigating-agentforce-d360/scripts/_shared/runtime.py +59 -0
- package/skills/investigating-agentforce-d360/scripts/_shared/sql.py +14 -0
- package/skills/investigating-agentforce-d360/scripts/assemble_dc.py +1624 -0
- package/skills/investigating-agentforce-d360/scripts/config.py +45 -0
- package/skills/investigating-agentforce-d360/scripts/dc.py +188 -0
- package/skills/investigating-agentforce-d360/scripts/discover_sessions.py +556 -0
- package/skills/investigating-agentforce-d360/scripts/fetch_dc.py +1045 -0
- package/skills/investigating-agentforce-d360/scripts/render_dc.py +1750 -0
- package/skills/investigating-agentforce-d360/scripts/resolve_session.py +264 -0
- package/skills/investigating-agentforce-d360/scripts/storage.py +92 -0
- package/skills/investigating-agentforce-d360/scripts/tests/__init__.py +0 -0
- package/skills/investigating-agentforce-d360/scripts/tests/_bootstrap.py +15 -0
- package/skills/investigating-agentforce-d360/scripts/tests/fixtures/__init__.py +0 -0
- package/skills/investigating-agentforce-d360/scripts/tests/fixtures/synthetic_session.py +424 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_assemble_dc_bootstrap_and_mode.py +115 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_assemble_dc_gateway_direct.py +220 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_assemble_dc_gateway_direct_integration.py +158 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_assemble_dc_helpers.py +287 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_assemble_dc_integration.py +247 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_dc_and_resolve_session.py +433 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_discover_sessions.py +458 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_discover_sessions_grep_ci.py +193 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_fetch_dc_helpers.py +266 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_fetch_dc_identity.py +528 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_fetch_dc_main.py +251 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_fetch_dc_waterfall.py +229 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_fetch_dc_waterfall_full.py +283 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_identity_coherence.py +327 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_render_dc_branches.py +256 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_render_dc_gateway_direct.py +130 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_render_dc_helpers.py +291 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_render_dc_integration.py +220 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_render_dc_planner_llm_calls.py +284 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_render_dc_show_prompts_gating.py +215 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_resolve_from_disk.py +100 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_resolve_session_main.py +149 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_runtime_override.py +104 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_session_shape.py +95 -0
- package/skills/investigating-agentforce-d360/scripts/tests/test_session_shape_dropped_by_stdm.py +85 -0
- package/skills/managing-managed-event-subscription/SKILL.md +152 -0
- package/skills/managing-managed-event-subscription/assets/managed-event-subscription-template.xml +20 -0
- package/skills/managing-managed-event-subscription/references/delete-guide.md +57 -0
- package/skills/managing-managed-event-subscription/references/topic-name-formats.md +26 -0
- package/skills/managing-managed-event-subscription/references/update-constraints.md +30 -0
- package/skills/modeling-omnistudio-epc-catalog/SKILL.md +0 -1
- package/skills/observing-agentforce/SKILL.md +0 -1
- package/skills/orchestrating-datacloud/SKILL.md +0 -1
- package/skills/preparing-datacloud/SKILL.md +0 -1
- package/skills/querying-soql/SKILL.md +0 -1
- package/skills/retrieving-datacloud/SKILL.md +0 -1
- package/skills/running-apex-tests/SKILL.md +0 -1
- package/skills/running-code-analyzer/SKILL.md +0 -1
- package/skills/segmenting-datacloud/SKILL.md +0 -1
- package/skills/testing-agentforce/SKILL.md +0 -1
- package/skills/uplifting-components-to-slds2/SKILL.md +3 -2
- package/skills/uplifting-components-to-slds2/references/color-hooks-decision-guide.md +30 -9
- package/skills/uplifting-components-to-slds2/references/examples.md +24 -6
- package/skills/validating-slds/SKILL.md +262 -0
- package/skills/validating-slds/references/quality-checks.md +308 -0
- package/skills/validating-slds/references/report-format.md +302 -0
- package/skills/validating-slds/scripts/analyze-quality.cjs +521 -0
|
@@ -0,0 +1,1043 @@
|
|
|
1
|
+
"""Render architecture.md from metadata_tree.json.
|
|
2
|
+
|
|
3
|
+
Phase 2 Batch 2.2: 8-section architecture document with up to 3 Mermaid
|
|
4
|
+
diagrams (2 core + 1 conditional dependency graph). Generation-aware:
|
|
5
|
+
classic/ReAct, classic/SequentialPlannerIntentClassifier, NGA/
|
|
6
|
+
ConcurrentMultiAgentOrchestration, search/BYOP all produce distinct
|
|
7
|
+
structural output on the same fixture schema.
|
|
8
|
+
|
|
9
|
+
Consumers
|
|
10
|
+
---------
|
|
11
|
+
- `main.py` phase 10 — called with the finalized tree_path + a target
|
|
12
|
+
out_path under the agent data_dir.
|
|
13
|
+
- Tests — `scripts/tests/test_render_architecture.py` exercises every
|
|
14
|
+
per-generation branch and the cap/partial/cycle edge cases.
|
|
15
|
+
|
|
16
|
+
Template loader
|
|
17
|
+
---------------
|
|
18
|
+
`load_mermaid(name, **params)` mirrors `soql_loader.load_soql`'s
|
|
19
|
+
substitution shape (single-pass `str.replace` on `{{KEY}}` tokens) but
|
|
20
|
+
deliberately does NOT run `fs_guard.validate_api_name` on the values.
|
|
21
|
+
Mermaid strings are not SQL, not a filesystem path, and not a shell
|
|
22
|
+
argument — there is no injection surface downstream. We only guard
|
|
23
|
+
against a *substituted* value itself containing `{{` / `}}`, which would
|
|
24
|
+
confuse readers of the rendered diff if it silently chained into a
|
|
25
|
+
second substitution pass; the loader logs a warning and proceeds.
|
|
26
|
+
Template filenames are validated — they flow into a `Path.is_file()`
|
|
27
|
+
check on disk, same traversal surface as `load_soql`.
|
|
28
|
+
"""
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
import json
|
|
32
|
+
import logging
|
|
33
|
+
from pathlib import Path
|
|
34
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
35
|
+
|
|
36
|
+
from config import MERMAID_DIR, SKILL_ROOT, fs_guard # fs_guard re-exported by config.py from _shared/
|
|
37
|
+
|
|
38
|
+
logger = logging.getLogger(__name__)
|
|
39
|
+
|
|
40
|
+
# P2.2-1: per-diagram-type node caps. Above cap -> summary placeholder.
|
|
41
|
+
DEFAULT_MAX_MERMAID_NODES: Dict[str, int] = {
|
|
42
|
+
"flowchart": 200,
|
|
43
|
+
"stateDiagram": 40,
|
|
44
|
+
"sequenceDiagram": 60,
|
|
45
|
+
"graph": 100,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _display_name(node: Dict[str, Any]) -> str:
|
|
50
|
+
"""Return the node's rendered label.
|
|
51
|
+
|
|
52
|
+
For STANDARD_ACTION nodes, append the invocation-type qualifier
|
|
53
|
+
(e.g. `streamKnowledgeSearch (standardinvocableaction)`) so the
|
|
54
|
+
rendered tree distinguishes a real Salesforce-owned builtin from
|
|
55
|
+
a flow-element STANDARD_ACTION whose action-name equals its
|
|
56
|
+
invocation-target. Canonical key is `invocation_type` (schema
|
|
57
|
+
3.1); legacy `raw_invocation_type` / `raw_action_type` fall
|
|
58
|
+
back for caches built by an older parse_wave.
|
|
59
|
+
"""
|
|
60
|
+
name = node.get("api_name") or node.get("element_name") or "?"
|
|
61
|
+
if node.get("kind") == "STANDARD_ACTION":
|
|
62
|
+
inv = (
|
|
63
|
+
node.get("invocation_type")
|
|
64
|
+
or node.get("raw_invocation_type")
|
|
65
|
+
or node.get("raw_action_type")
|
|
66
|
+
)
|
|
67
|
+
if inv:
|
|
68
|
+
return f"{name} ({inv})"
|
|
69
|
+
return name
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# ---------------------------------------------------------------------------
|
|
73
|
+
# Template loader
|
|
74
|
+
# ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def load_mermaid(name: str, **params: str) -> str:
|
|
78
|
+
"""Read assets/mermaid/<name>.mmd and substitute {{PARAM}} values.
|
|
79
|
+
|
|
80
|
+
P2.2-1: template name is regex-validated BEFORE any filesystem access.
|
|
81
|
+
Traversal via `../../etc/...` is blocked by `fs_guard.validate_api_name`.
|
|
82
|
+
Param *values* are NOT validated — mermaid strings don't flow into
|
|
83
|
+
SOQL, REST, or filesystem paths and carry no injection surface.
|
|
84
|
+
We DO check for nested `{{`/`}}` in substituted values and log a
|
|
85
|
+
warning (the first-pass substitute call won't re-trigger, but a
|
|
86
|
+
stray `{{OTHER}}` inside a value makes the rendered markdown
|
|
87
|
+
confusing to diff). Raises `FileNotFoundError` with the template
|
|
88
|
+
name (no absolute-path leak) when the template file is missing.
|
|
89
|
+
"""
|
|
90
|
+
try:
|
|
91
|
+
fs_guard.validate_api_name(name, label="mermaid_template_name")
|
|
92
|
+
except fs_guard.ValidationError as e:
|
|
93
|
+
# Template name is attacker-controlled in theory (a caller could
|
|
94
|
+
# source it from a config file). Don't leak the full SKILL_ROOT
|
|
95
|
+
# via the default FileNotFoundError message.
|
|
96
|
+
raise FileNotFoundError(
|
|
97
|
+
f"Mermaid template name rejected: {e.reason}"
|
|
98
|
+
) from None
|
|
99
|
+
|
|
100
|
+
path = MERMAID_DIR / f"{name}.mmd"
|
|
101
|
+
if not path.is_file():
|
|
102
|
+
# Match soql_loader's SoqlTemplateNotFound hygiene — carry only
|
|
103
|
+
# the template *name*, not the absolute MERMAID_DIR path, so
|
|
104
|
+
# error text surfacing in logs doesn't disclose filesystem layout.
|
|
105
|
+
raise FileNotFoundError(f"Mermaid template not found: {name}")
|
|
106
|
+
|
|
107
|
+
template = path.read_text()
|
|
108
|
+
# the `%%` header-comment block in each template is kept
|
|
109
|
+
# verbatim in the rendered output. Mermaid treats `%%` lines as
|
|
110
|
+
# comments and ignores them at render time, and keeping them aids
|
|
111
|
+
# debuggability (rendered diff still carries the author's contract
|
|
112
|
+
# notes). Templates MUST document placeholder names as bare tokens
|
|
113
|
+
# (e.g. `NODES placeholder:`) rather than `{{NODES}}` — otherwise
|
|
114
|
+
# the single-pass `str.replace` below would substitute the comment's
|
|
115
|
+
# placeholder reference and corrupt the header.
|
|
116
|
+
|
|
117
|
+
for key, value in params.items():
|
|
118
|
+
if not isinstance(value, str):
|
|
119
|
+
# Fail loud rather than produce a literal 'None' or '[<Node>...]'
|
|
120
|
+
# in the rendered markdown.
|
|
121
|
+
raise TypeError(
|
|
122
|
+
f"Mermaid param {key!r} must be str, got {type(value).__name__}"
|
|
123
|
+
)
|
|
124
|
+
if "{{" in value or "}}" in value:
|
|
125
|
+
# Defensive log; single-pass `str.replace` still renders safely.
|
|
126
|
+
logger.warning(
|
|
127
|
+
"load_mermaid(%s): param %r contains nested placeholder tokens; "
|
|
128
|
+
"rendered output may be confusing",
|
|
129
|
+
name, key,
|
|
130
|
+
)
|
|
131
|
+
template = template.replace(f"{{{{{key}}}}}", value)
|
|
132
|
+
return template.strip()
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
# ---------------------------------------------------------------------------
|
|
136
|
+
# Public entry point
|
|
137
|
+
# ---------------------------------------------------------------------------
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def render(
|
|
141
|
+
tree_path: Path,
|
|
142
|
+
out_path: Path,
|
|
143
|
+
*,
|
|
144
|
+
max_mermaid_nodes: Optional[Dict[str, int]] = None,
|
|
145
|
+
) -> None:
|
|
146
|
+
"""Read metadata_tree.json and write architecture.md to out_path.
|
|
147
|
+
|
|
148
|
+
The renderer walks the tree once to collect catalog data (topics,
|
|
149
|
+
actions, flows, apex, prompts, unresolved refs, cycles) then emits
|
|
150
|
+
8 sections in document order. Generation-aware branches live at the
|
|
151
|
+
section-level helpers below.
|
|
152
|
+
|
|
153
|
+
Note: `_render_invocation_sequence` and `_render_planner_state`
|
|
154
|
+
remain defined and tested but are no longer part of the default
|
|
155
|
+
pipeline (heuristics distrusted by reviewers as of 2026-05).
|
|
156
|
+
Uncomment the relevant `parts.append` calls below to re-enable.
|
|
157
|
+
"""
|
|
158
|
+
caps = dict(DEFAULT_MAX_MERMAID_NODES)
|
|
159
|
+
if max_mermaid_nodes:
|
|
160
|
+
caps.update(max_mermaid_nodes)
|
|
161
|
+
|
|
162
|
+
tree = json.loads(Path(tree_path).read_text())
|
|
163
|
+
agent = tree.get("agent") or {}
|
|
164
|
+
generation = (agent.get("generation") or "classic").lower()
|
|
165
|
+
|
|
166
|
+
walker = _TreeWalker(tree)
|
|
167
|
+
walker.walk()
|
|
168
|
+
|
|
169
|
+
parts: List[str] = []
|
|
170
|
+
parts.append(_render_header(tree, agent))
|
|
171
|
+
parts.append(_render_anatomy_summary(tree, walker))
|
|
172
|
+
# parts.append(_render_invocation_sequence(tree, agent, walker, caps))
|
|
173
|
+
# ^ Disabled 2026-05: heuristic over-simplified real orchestration
|
|
174
|
+
# paths. Function + template retained; re-enable by uncommenting.
|
|
175
|
+
parts.append(_render_action_tree(tree, walker, caps))
|
|
176
|
+
parts.append(_render_topic_anatomy(walker))
|
|
177
|
+
parts.append(_render_action_catalog(walker))
|
|
178
|
+
# parts.append(_render_planner_state(agent, generation, caps))
|
|
179
|
+
# ^ Disabled 2026-05: generation-specific state diagram added more
|
|
180
|
+
# noise than signal in review. Function + template retained; re-enable
|
|
181
|
+
# by uncommenting.
|
|
182
|
+
parts.append(_render_data_flow(tree, walker, caps))
|
|
183
|
+
parts.append(_render_artifact_catalogs(walker))
|
|
184
|
+
parts.append(_render_unresolved(tree, walker))
|
|
185
|
+
|
|
186
|
+
# Conditional dependency-graph section — emitted only if there are
|
|
187
|
+
# unresolved refs or cycles detected. Not counted as one of the 9.
|
|
188
|
+
if tree.get("_unresolved") or walker.cycles:
|
|
189
|
+
parts.append(_render_dependency_graph(tree, walker, caps))
|
|
190
|
+
|
|
191
|
+
Path(out_path).write_text("\n\n".join(parts).rstrip() + "\n")
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
# ---------------------------------------------------------------------------
|
|
195
|
+
# Tree walker — single pass over the tree to collect catalog data
|
|
196
|
+
# ---------------------------------------------------------------------------
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class _TreeWalker:
|
|
200
|
+
"""Collects nodes, edges, topics, cycles in a single depth-first pass.
|
|
201
|
+
|
|
202
|
+
P2.2-1: BFS-equivalent visit budget already enforced by parse_wave
|
|
203
|
+
(MAX_BFS_DEPTH=5), so we don't need a depth cap here — the tree is
|
|
204
|
+
already bounded. We DO guard against a malformed tree with a
|
|
205
|
+
self-referential `children` ring by tracking id()'s.
|
|
206
|
+
"""
|
|
207
|
+
|
|
208
|
+
def __init__(self, tree: Dict[str, Any]) -> None:
|
|
209
|
+
self.tree = tree
|
|
210
|
+
self.topics: List[Dict[str, Any]] = []
|
|
211
|
+
# actions = top-level children of each topic plus planner-level actions
|
|
212
|
+
self.actions: List[Dict[str, Any]] = []
|
|
213
|
+
self.flows: Dict[str, Dict[str, Any]] = {}
|
|
214
|
+
self.apex: Dict[str, Dict[str, Any]] = {}
|
|
215
|
+
self.prompts: Dict[str, Dict[str, Any]] = {}
|
|
216
|
+
self.standard_actions: Dict[str, Dict[str, Any]] = {}
|
|
217
|
+
# tree edges: parent api_name -> list of child api_name
|
|
218
|
+
self.edges: List[Tuple[str, str, str]] = [] # (parent, child, kind)
|
|
219
|
+
# fan-out map for summary-placeholder top-5
|
|
220
|
+
self.fanout: Dict[str, int] = {}
|
|
221
|
+
# cycle-back annotations: list of (node_label, cycle_back_to)
|
|
222
|
+
self.cycles: List[Tuple[str, str]] = []
|
|
223
|
+
# depth-cap truncations (subset of self.cycles surfaced by
|
|
224
|
+
# the unified _truncated annotation with reason="max-depth").
|
|
225
|
+
# Kept separate so downstream renderers can distinguish the two
|
|
226
|
+
# truncation classes without re-walking the tree.
|
|
227
|
+
self.depth_capped: List[Tuple[str, str]] = []
|
|
228
|
+
self._seen_py_ids: set[int] = set()
|
|
229
|
+
|
|
230
|
+
def walk(self) -> None:
|
|
231
|
+
root = self.tree.get("root") or {}
|
|
232
|
+
for child in root.get("children") or []:
|
|
233
|
+
self._visit(child, parent_label=root.get("api_name") or "ROOT",
|
|
234
|
+
topic=None)
|
|
235
|
+
|
|
236
|
+
def _visit(
|
|
237
|
+
self,
|
|
238
|
+
node: Dict[str, Any],
|
|
239
|
+
*,
|
|
240
|
+
parent_label: str,
|
|
241
|
+
topic: Optional[Dict[str, Any]],
|
|
242
|
+
) -> None:
|
|
243
|
+
if not isinstance(node, dict):
|
|
244
|
+
return
|
|
245
|
+
nid = id(node)
|
|
246
|
+
if nid in self._seen_py_ids:
|
|
247
|
+
return
|
|
248
|
+
self._seen_py_ids.add(nid)
|
|
249
|
+
|
|
250
|
+
kind = node.get("kind") or "UNKNOWN"
|
|
251
|
+
api_name = node.get("api_name") or node.get("element_name") or ""
|
|
252
|
+
|
|
253
|
+
# Top-level children of a BOT_DEFINITION with kind TOPIC feed the
|
|
254
|
+
# topic list. Non-topic top-level children (planner-level actions)
|
|
255
|
+
# attach to a synthetic `_plannerActions` bucket.
|
|
256
|
+
if kind == "TOPIC" and topic is None:
|
|
257
|
+
topic_rec = {
|
|
258
|
+
"api_name": api_name,
|
|
259
|
+
"label": node.get("master_label") or api_name,
|
|
260
|
+
"actions": [],
|
|
261
|
+
"raw": node,
|
|
262
|
+
}
|
|
263
|
+
self.topics.append(topic_rec)
|
|
264
|
+
topic = topic_rec
|
|
265
|
+
elif topic is None and kind == "GEN_AI_FUNCTION":
|
|
266
|
+
# plannerAction (classic Sequential / NGA) — no parent topic.
|
|
267
|
+
self.actions.append({
|
|
268
|
+
"kind": kind,
|
|
269
|
+
"api_name": api_name,
|
|
270
|
+
"topic": None,
|
|
271
|
+
"raw": node,
|
|
272
|
+
})
|
|
273
|
+
elif topic is not None and kind == "GEN_AI_FUNCTION":
|
|
274
|
+
action_rec = {
|
|
275
|
+
"kind": kind,
|
|
276
|
+
"api_name": api_name,
|
|
277
|
+
"topic": topic["api_name"],
|
|
278
|
+
"raw": node,
|
|
279
|
+
}
|
|
280
|
+
topic["actions"].append(action_rec)
|
|
281
|
+
self.actions.append(action_rec)
|
|
282
|
+
|
|
283
|
+
# Per-kind catalog buckets. We key on api_name; dupes collapse.
|
|
284
|
+
if kind == "FLOW" and api_name:
|
|
285
|
+
self.flows.setdefault(api_name, node)
|
|
286
|
+
elif kind == "APEX" and api_name:
|
|
287
|
+
self.apex.setdefault(api_name, node)
|
|
288
|
+
elif kind == "PROMPT_TEMPLATE" and api_name:
|
|
289
|
+
self.prompts.setdefault(api_name, node)
|
|
290
|
+
elif kind == "STANDARD_ACTION" and api_name:
|
|
291
|
+
self.standard_actions.setdefault(api_name, node)
|
|
292
|
+
|
|
293
|
+
# per-node truncation annotation (cycle OR depth-cap).
|
|
294
|
+
# Prefer the unified `_truncated` sub-object; fall back to the
|
|
295
|
+
# deprecated `_cycle_back_to` string for trees produced by
|
|
296
|
+
# older parse_wave versions.
|
|
297
|
+
trunc = node.get("_truncated") or {}
|
|
298
|
+
cycle_to = trunc.get("target") or node.get("_cycle_back_to")
|
|
299
|
+
reason = trunc.get("reason") or ("cycle" if cycle_to else None)
|
|
300
|
+
if cycle_to:
|
|
301
|
+
self.cycles.append((api_name or parent_label, str(cycle_to)))
|
|
302
|
+
# Optional: downstream renderers may want to distinguish
|
|
303
|
+
# the two truncation classes. We keep that open by stashing
|
|
304
|
+
# `reason` when present.
|
|
305
|
+
if reason and reason != "cycle":
|
|
306
|
+
self.depth_capped.append((api_name or parent_label, str(cycle_to)))
|
|
307
|
+
|
|
308
|
+
# Edge + fanout bookkeeping
|
|
309
|
+
children = node.get("children") or []
|
|
310
|
+
if api_name and parent_label and parent_label != api_name:
|
|
311
|
+
self.edges.append((parent_label, api_name, kind))
|
|
312
|
+
self.fanout[parent_label] = self.fanout.get(parent_label, 0) + 1
|
|
313
|
+
|
|
314
|
+
for child in children:
|
|
315
|
+
self._visit(child, parent_label=api_name or parent_label,
|
|
316
|
+
topic=topic)
|
|
317
|
+
|
|
318
|
+
# ---- helpers -----------------------------------------------------
|
|
319
|
+
|
|
320
|
+
def total_nodes(self) -> int:
|
|
321
|
+
counts = self.tree.get("_kind_counts") or {}
|
|
322
|
+
return sum(counts.values()) or self.tree.get("node_count", 0)
|
|
323
|
+
|
|
324
|
+
def top_fanout(self, n: int = 5) -> List[Tuple[str, int]]:
|
|
325
|
+
return sorted(self.fanout.items(), key=lambda kv: (-kv[1], kv[0]))[:n]
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
# ---------------------------------------------------------------------------
|
|
329
|
+
# Section renderers
|
|
330
|
+
# ---------------------------------------------------------------------------
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def _md_escape(value: Any) -> str:
|
|
334
|
+
if value is None:
|
|
335
|
+
return "-"
|
|
336
|
+
s = str(value)
|
|
337
|
+
# Escape the two characters that break markdown tables.
|
|
338
|
+
return s.replace("|", r"\|").replace("\n", " ")
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def _render_header(tree: Dict[str, Any], agent: Dict[str, Any]) -> str:
|
|
342
|
+
# P2.2-1: section 1 — kv table. No input is trusted (agent fields
|
|
343
|
+
# come from SOQL + metadata retrieve), but we escape pipes anyway.
|
|
344
|
+
lines = ["# Architecture — `{}` `{}`".format(
|
|
345
|
+
_md_escape(agent.get("api_name") or "?"),
|
|
346
|
+
_md_escape(agent.get("version") or "?"),
|
|
347
|
+
)]
|
|
348
|
+
lines.append("")
|
|
349
|
+
lines.append("| Field | Value |")
|
|
350
|
+
lines.append("|---|---|")
|
|
351
|
+
rows = [
|
|
352
|
+
("Master label", agent.get("master_label")),
|
|
353
|
+
("Description", agent.get("description")),
|
|
354
|
+
("Agent type", agent.get("agent_type")),
|
|
355
|
+
("Type", agent.get("type")),
|
|
356
|
+
("Template", agent.get("agent_template")),
|
|
357
|
+
("Bot source", agent.get("bot_source")),
|
|
358
|
+
("Generation", agent.get("generation")),
|
|
359
|
+
("Planner name", agent.get("planner_name")),
|
|
360
|
+
("Planner type", agent.get("planner_type")),
|
|
361
|
+
("Bot id", agent.get("bot_id")),
|
|
362
|
+
("Schema version", tree.get("_schema_version")),
|
|
363
|
+
]
|
|
364
|
+
for label, value in rows:
|
|
365
|
+
lines.append(f"| {label} | {_md_escape(value)} |")
|
|
366
|
+
return "\n".join(lines)
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def _render_anatomy_summary(tree: Dict[str, Any], walker: _TreeWalker) -> str:
|
|
370
|
+
# P2.2-1: section 2 renders health callout when _partial=true.
|
|
371
|
+
kc = tree.get("_kind_counts") or {}
|
|
372
|
+
lines = ["## 2. Anatomy summary", ""]
|
|
373
|
+
topic_count = kc.get("TOPIC", len(walker.topics))
|
|
374
|
+
action_count = kc.get("GEN_AI_FUNCTION", len(walker.actions))
|
|
375
|
+
flow_count = kc.get("FLOW", len(walker.flows))
|
|
376
|
+
apex_count = kc.get("APEX", len(walker.apex))
|
|
377
|
+
prompt_count = kc.get("PROMPT_TEMPLATE", len(walker.prompts))
|
|
378
|
+
stdaction_count = kc.get("STANDARD_ACTION", len(walker.standard_actions))
|
|
379
|
+
|
|
380
|
+
lines.append(
|
|
381
|
+
"Agent exposes **{} topics** and **{} declared actions** "
|
|
382
|
+
"spanning **{} flows**, **{} apex classes**, **{} prompt templates**, "
|
|
383
|
+
"and **{} standard actions**. Tree depth is {} across {} total nodes.".format(
|
|
384
|
+
topic_count, action_count, flow_count, apex_count,
|
|
385
|
+
prompt_count, stdaction_count,
|
|
386
|
+
tree.get("depth", "?"), walker.total_nodes(),
|
|
387
|
+
)
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
if tree.get("_partial"):
|
|
391
|
+
pending = tree.get("_pending_fetches") or {}
|
|
392
|
+
pending_count = sum(len(v) for v in pending.values())
|
|
393
|
+
reason = tree.get("_partial_reason") or "unspecified"
|
|
394
|
+
lines.append("")
|
|
395
|
+
lines.append("> **Health: PARTIAL.** The tree did not fully converge.")
|
|
396
|
+
lines.append(f"> - Reason: `{_md_escape(reason)}`")
|
|
397
|
+
lines.append(f"> - Pending fetches: {pending_count}")
|
|
398
|
+
if pending:
|
|
399
|
+
for key, items in pending.items():
|
|
400
|
+
if items:
|
|
401
|
+
lines.append(
|
|
402
|
+
f"> - `{_md_escape(key)}`: {len(items)} outstanding"
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
# P2.2-1: health callout when planner_name is missing.
|
|
406
|
+
agent = tree.get("agent") or {}
|
|
407
|
+
if not agent.get("planner_name"):
|
|
408
|
+
lines.append("")
|
|
409
|
+
lines.append(
|
|
410
|
+
"> **Health: WARN.** `planner_name` missing from agent metadata — "
|
|
411
|
+
"downstream sections render best-effort from tree shape alone."
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
if tree.get("_unresolved"):
|
|
415
|
+
lines.append("")
|
|
416
|
+
lines.append(
|
|
417
|
+
"> **Health: WARN.** {} unresolved references — see section 8.".format(
|
|
418
|
+
len(tree["_unresolved"])
|
|
419
|
+
)
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
return "\n".join(lines)
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def _render_invocation_sequence(
|
|
426
|
+
tree: Dict[str, Any],
|
|
427
|
+
agent: Dict[str, Any],
|
|
428
|
+
walker: _TreeWalker,
|
|
429
|
+
caps: Dict[str, int],
|
|
430
|
+
) -> str:
|
|
431
|
+
# P2.2-1: section 3 — sequenceDiagram with cap check.
|
|
432
|
+
lines = ["## 3. Invocation sequence", ""]
|
|
433
|
+
generation = (agent.get("generation") or "classic").lower()
|
|
434
|
+
|
|
435
|
+
participants = ["participant User", " participant Planner"]
|
|
436
|
+
if generation == "nga":
|
|
437
|
+
participants.append(" participant Orchestrator")
|
|
438
|
+
participants.append(" participant SubAgent")
|
|
439
|
+
else:
|
|
440
|
+
participants.append(" participant TopicClassifier")
|
|
441
|
+
participants.append(" participant ActionExecutor")
|
|
442
|
+
|
|
443
|
+
messages: List[str] = []
|
|
444
|
+
messages.append(" User->>+Planner: utterance")
|
|
445
|
+
if generation == "nga":
|
|
446
|
+
messages.append(" Planner->>+Orchestrator: plan")
|
|
447
|
+
messages.append(" Orchestrator->>+SubAgent: dispatch (par/and)")
|
|
448
|
+
messages.append(" SubAgent->>+ActionExecutor: invoke action")
|
|
449
|
+
messages.append(" ActionExecutor-->>-SubAgent: result")
|
|
450
|
+
messages.append(" SubAgent-->>-Orchestrator: subresult")
|
|
451
|
+
messages.append(" Orchestrator-->>-Planner: aggregated")
|
|
452
|
+
elif (agent.get("planner_type") or "").endswith("SequentialPlannerIntentClassifier"):
|
|
453
|
+
messages.append(" Planner->>+ActionExecutor: direct intent->action")
|
|
454
|
+
messages.append(" ActionExecutor-->>-Planner: result")
|
|
455
|
+
else:
|
|
456
|
+
# Classic ReAct — one round-trip per topic (sampled at :5 for
|
|
457
|
+
# readability). The cap check below counts the ACTUAL rendered
|
|
458
|
+
# messages, so a 30-topic bot whose ReAct branch only emits 12
|
|
459
|
+
# lines is correctly NOT truncated.
|
|
460
|
+
for topic in walker.topics[:5]: # sample for readability
|
|
461
|
+
label = _md_escape(topic["api_name"])
|
|
462
|
+
messages.append(f" Planner->>+TopicClassifier: classify → {label}")
|
|
463
|
+
messages.append(f" TopicClassifier-->>-Planner: topic={label}")
|
|
464
|
+
messages.append(" Planner->>+ActionExecutor: invoke action")
|
|
465
|
+
messages.append(" ActionExecutor-->>-Planner: result")
|
|
466
|
+
|
|
467
|
+
messages.append(" Planner-->>-User: response")
|
|
468
|
+
|
|
469
|
+
# cap against ACTUAL rendered message count, not a
|
|
470
|
+
# potential / over-estimated figure. The prior implementation used
|
|
471
|
+
# `2 * len(walker.topics) + len(walker.actions) + 2`, which false-
|
|
472
|
+
# tripped on large bots because the ReAct branch only emits
|
|
473
|
+
# `2 * min(len(walker.topics), 5) + 2` lines (topics[:5] sampling).
|
|
474
|
+
# The rendered-list length is the single source of truth.
|
|
475
|
+
msg_count = len(messages)
|
|
476
|
+
if msg_count > caps.get("sequenceDiagram", 60):
|
|
477
|
+
lines.append(_truncation_placeholder(
|
|
478
|
+
kind="sequenceDiagram", total=msg_count,
|
|
479
|
+
cap=caps["sequenceDiagram"],
|
|
480
|
+
top_fanout=walker.top_fanout(5),
|
|
481
|
+
catalog_pointer="section 5 (action catalog)",
|
|
482
|
+
))
|
|
483
|
+
return "\n".join(lines)
|
|
484
|
+
|
|
485
|
+
rendered = load_mermaid(
|
|
486
|
+
"invocation_sequence",
|
|
487
|
+
PARTICIPANTS="\n".join(participants).lstrip(),
|
|
488
|
+
MESSAGES="\n".join(messages).lstrip(),
|
|
489
|
+
)
|
|
490
|
+
lines.append("```mermaid")
|
|
491
|
+
lines.append(rendered)
|
|
492
|
+
lines.append("```")
|
|
493
|
+
return "\n".join(lines)
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
def _render_action_tree(
|
|
497
|
+
tree: Dict[str, Any],
|
|
498
|
+
walker: _TreeWalker,
|
|
499
|
+
caps: Dict[str, int],
|
|
500
|
+
) -> str:
|
|
501
|
+
# P2.2-1: section 3 — flowchart + subgraphs per topic; cycle back-edges.
|
|
502
|
+
lines = ["## 3. Action tree", ""]
|
|
503
|
+
total_nodes = walker.total_nodes()
|
|
504
|
+
if total_nodes > caps.get("flowchart", 200):
|
|
505
|
+
lines.append(_truncation_placeholder(
|
|
506
|
+
kind="flowchart", total=total_nodes,
|
|
507
|
+
cap=caps["flowchart"],
|
|
508
|
+
top_fanout=walker.top_fanout(5),
|
|
509
|
+
catalog_pointer="section 5 (action catalog) and section 7 (artifact catalogs)",
|
|
510
|
+
))
|
|
511
|
+
lines.append("")
|
|
512
|
+
lines.append(_render_action_tree_ascii(walker))
|
|
513
|
+
return "\n".join(lines)
|
|
514
|
+
|
|
515
|
+
# Build subgraphs and edges
|
|
516
|
+
subgraphs: List[str] = []
|
|
517
|
+
for topic in walker.topics:
|
|
518
|
+
sg_id = _safe_id(topic["api_name"])
|
|
519
|
+
sg_lines = [f" subgraph {sg_id}[\"{_md_escape(topic['label'])}\"]"]
|
|
520
|
+
for action in topic["actions"]:
|
|
521
|
+
node_id = _safe_id(action["api_name"])
|
|
522
|
+
label = _display_name(action.get("raw") or action)
|
|
523
|
+
sg_lines.append(f" {node_id}[\"{_md_escape(label)}\"]")
|
|
524
|
+
sg_lines.append(" end")
|
|
525
|
+
subgraphs.append("\n".join(sg_lines))
|
|
526
|
+
|
|
527
|
+
# Planner-level actions (no topic)
|
|
528
|
+
planner_actions = [a for a in walker.actions if a.get("topic") is None]
|
|
529
|
+
if planner_actions:
|
|
530
|
+
sg_lines = [" subgraph _plannerActions[\"(plannerActions)\"]"]
|
|
531
|
+
for action in planner_actions:
|
|
532
|
+
node_id = _safe_id(action["api_name"])
|
|
533
|
+
label = _display_name(action.get("raw") or action)
|
|
534
|
+
sg_lines.append(f" {node_id}[\"{_md_escape(label)}\"]")
|
|
535
|
+
sg_lines.append(" end")
|
|
536
|
+
subgraphs.append("\n".join(sg_lines))
|
|
537
|
+
|
|
538
|
+
edges: List[str] = []
|
|
539
|
+
for parent, child, kind in walker.edges:
|
|
540
|
+
pid = _safe_id(parent)
|
|
541
|
+
cid = _safe_id(child)
|
|
542
|
+
edges.append(f" {pid} --> {cid}")
|
|
543
|
+
|
|
544
|
+
# Cycle back-edges (dotted)
|
|
545
|
+
for node_label, cycle_to in walker.cycles:
|
|
546
|
+
nid = _safe_id(node_label)
|
|
547
|
+
tid = _safe_id(cycle_to)
|
|
548
|
+
edges.append(f" {nid} -.->|cycle_back_to: {_md_escape(cycle_to)}| {tid}")
|
|
549
|
+
|
|
550
|
+
rendered = load_mermaid(
|
|
551
|
+
"action_tree",
|
|
552
|
+
SUBGRAPHS="\n\n".join(subgraphs).lstrip() if subgraphs else "%% no topics",
|
|
553
|
+
EDGES="\n".join(edges).lstrip() if edges else "%% no edges",
|
|
554
|
+
)
|
|
555
|
+
lines.append("```mermaid")
|
|
556
|
+
lines.append(rendered)
|
|
557
|
+
lines.append("```")
|
|
558
|
+
lines.append("")
|
|
559
|
+
lines.append("**ASCII appendix**")
|
|
560
|
+
lines.append("")
|
|
561
|
+
lines.append("```")
|
|
562
|
+
lines.append(_render_action_tree_ascii(walker))
|
|
563
|
+
lines.append("```")
|
|
564
|
+
return "\n".join(lines)
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
def _render_action_tree_ascii(walker: _TreeWalker) -> str:
|
|
568
|
+
out: List[str] = []
|
|
569
|
+
root = walker.tree.get("root") or {}
|
|
570
|
+
out.append(f"{root.get('api_name', 'ROOT')} ({root.get('kind', 'BOT_DEFINITION')})")
|
|
571
|
+
_ascii_recurse(root.get("children") or [], out, depth=1, seen=set())
|
|
572
|
+
return "\n".join(out)
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
def _ascii_recurse(
|
|
576
|
+
children: List[Dict[str, Any]],
|
|
577
|
+
out: List[str],
|
|
578
|
+
depth: int,
|
|
579
|
+
seen: set[int],
|
|
580
|
+
) -> None:
|
|
581
|
+
for child in children:
|
|
582
|
+
if not isinstance(child, dict):
|
|
583
|
+
continue
|
|
584
|
+
nid = id(child)
|
|
585
|
+
if nid in seen:
|
|
586
|
+
continue
|
|
587
|
+
seen.add(nid)
|
|
588
|
+
name = _display_name(child)
|
|
589
|
+
kind = child.get("kind") or "?"
|
|
590
|
+
# surface BOTH cycle AND max-depth truncation in the ASCII
|
|
591
|
+
# tree view. Prefer `_truncated["reason"]`; fall back to the
|
|
592
|
+
# legacy `_cycle_back_to` string (older parse_wave output).
|
|
593
|
+
trunc = child.get("_truncated") or {}
|
|
594
|
+
if trunc.get("reason") == "max-depth":
|
|
595
|
+
marker = " [depth-capped]"
|
|
596
|
+
elif trunc.get("reason") == "cycle" or child.get("_cycle_back_to"):
|
|
597
|
+
marker = " [cycle]"
|
|
598
|
+
else:
|
|
599
|
+
marker = ""
|
|
600
|
+
out.append(f"{' ' * depth}├── [{kind}] {name}{marker}")
|
|
601
|
+
grand = child.get("children") or []
|
|
602
|
+
if grand:
|
|
603
|
+
_ascii_recurse(grand, out, depth + 1, seen)
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
def _render_topic_anatomy(walker: _TreeWalker) -> str:
|
|
607
|
+
# P2.2-1: section 4 — H3 per topic + kv list. Empty-case: 0 topics is
|
|
608
|
+
# valid for SequentialPlannerIntentClassifier.
|
|
609
|
+
lines = ["## 4. Topic anatomy", ""]
|
|
610
|
+
if not walker.topics:
|
|
611
|
+
lines.append("_No topics defined (planner exposes actions directly)._")
|
|
612
|
+
return "\n".join(lines)
|
|
613
|
+
for topic in walker.topics:
|
|
614
|
+
lines.append(f"### `{_md_escape(topic['api_name'])}`")
|
|
615
|
+
lines.append("")
|
|
616
|
+
lines.append(f"- Label: {_md_escape(topic['label'])}")
|
|
617
|
+
lines.append(f"- Action count: {len(topic['actions'])}")
|
|
618
|
+
if topic["actions"]:
|
|
619
|
+
lines.append("- Actions:")
|
|
620
|
+
for action in topic["actions"]:
|
|
621
|
+
label = _display_name(action.get("raw") or action)
|
|
622
|
+
lines.append(f" - `{_md_escape(label)}`")
|
|
623
|
+
lines.append("")
|
|
624
|
+
return "\n".join(lines).rstrip()
|
|
625
|
+
|
|
626
|
+
|
|
627
|
+
def _render_action_catalog(walker: _TreeWalker) -> str:
|
|
628
|
+
# P2.2-1: section 5 — markdown table of actions.
|
|
629
|
+
lines = ["## 5. Action catalog", ""]
|
|
630
|
+
if not walker.actions:
|
|
631
|
+
lines.append("_No actions declared._")
|
|
632
|
+
return "\n".join(lines)
|
|
633
|
+
lines.append("| Action | Topic | Unwraps to |")
|
|
634
|
+
lines.append("|---|---|---|")
|
|
635
|
+
for action in walker.actions:
|
|
636
|
+
raw = action.get("raw") or {}
|
|
637
|
+
unwrap = raw.get("unwraps_to") or {}
|
|
638
|
+
unwrap_str = "-"
|
|
639
|
+
if unwrap:
|
|
640
|
+
unwrap_str = "{} `{}`".format(
|
|
641
|
+
unwrap.get("kind", "?"),
|
|
642
|
+
_display_name(unwrap),
|
|
643
|
+
)
|
|
644
|
+
lines.append("| `{}` | {} | {} |".format(
|
|
645
|
+
_md_escape(action["api_name"]),
|
|
646
|
+
_md_escape(action.get("topic") or "(plannerAction)"),
|
|
647
|
+
_md_escape(unwrap_str),
|
|
648
|
+
))
|
|
649
|
+
return "\n".join(lines)
|
|
650
|
+
|
|
651
|
+
|
|
652
|
+
def _planner_state_for_generation(
|
|
653
|
+
agent: Dict[str, Any], generation: str,
|
|
654
|
+
) -> Tuple[List[str], List[str]]:
|
|
655
|
+
"""Return (states, transitions) for the planner state machine."""
|
|
656
|
+
planner_type = (agent.get("planner_type") or "").lower()
|
|
657
|
+
if generation == "nga":
|
|
658
|
+
states = [
|
|
659
|
+
"[*] --> Planning",
|
|
660
|
+
" state Planning",
|
|
661
|
+
" state Orchestration {",
|
|
662
|
+
" direction LR",
|
|
663
|
+
" [*] --> Dispatch",
|
|
664
|
+
" Dispatch --> SubAgentA",
|
|
665
|
+
" Dispatch --> SubAgentB",
|
|
666
|
+
" --",
|
|
667
|
+
" SubAgentA --> Aggregate",
|
|
668
|
+
" SubAgentB --> Aggregate",
|
|
669
|
+
" }",
|
|
670
|
+
" state Respond",
|
|
671
|
+
]
|
|
672
|
+
transitions = [
|
|
673
|
+
" Planning --> Orchestration: par/and dispatch",
|
|
674
|
+
" Orchestration --> Respond: aggregated",
|
|
675
|
+
" Respond --> [*]",
|
|
676
|
+
]
|
|
677
|
+
return states, transitions
|
|
678
|
+
if planner_type.endswith("sequentialplannerintentclassifier"):
|
|
679
|
+
states = [
|
|
680
|
+
"[*] --> Classify",
|
|
681
|
+
" state Classify",
|
|
682
|
+
" state Execute",
|
|
683
|
+
" state Respond",
|
|
684
|
+
]
|
|
685
|
+
transitions = [
|
|
686
|
+
" Classify --> Execute: intent",
|
|
687
|
+
" Execute --> Respond: result",
|
|
688
|
+
" Respond --> [*]",
|
|
689
|
+
]
|
|
690
|
+
return states, transitions
|
|
691
|
+
# Default: classic ReAct
|
|
692
|
+
states = [
|
|
693
|
+
"[*] --> Thought",
|
|
694
|
+
" state Thought",
|
|
695
|
+
" state Action",
|
|
696
|
+
" state Observation",
|
|
697
|
+
" state Respond",
|
|
698
|
+
]
|
|
699
|
+
transitions = [
|
|
700
|
+
" Thought --> Action: pick tool",
|
|
701
|
+
" Action --> Observation: tool result",
|
|
702
|
+
" Observation --> Thought: more reasoning",
|
|
703
|
+
" Observation --> Respond: done",
|
|
704
|
+
" Respond --> [*]",
|
|
705
|
+
]
|
|
706
|
+
return states, transitions
|
|
707
|
+
|
|
708
|
+
|
|
709
|
+
def _render_planner_state(
|
|
710
|
+
agent: Dict[str, Any],
|
|
711
|
+
generation: str,
|
|
712
|
+
caps: Dict[str, int],
|
|
713
|
+
) -> str:
|
|
714
|
+
# P2.2-1: section 6 — stateDiagram-v2 with generation-aware branches.
|
|
715
|
+
lines = ["## 6. Planner state machine", ""]
|
|
716
|
+
|
|
717
|
+
if generation in ("search", "byop"):
|
|
718
|
+
lines.append(
|
|
719
|
+
"_Custom planner — structure depends on the planner's Apex class_ "
|
|
720
|
+
f"(`{_md_escape(agent.get('planner_type') or '?')}`). State "
|
|
721
|
+
"diagram skipped; see section 7 for the backing Apex class body."
|
|
722
|
+
)
|
|
723
|
+
return "\n".join(lines)
|
|
724
|
+
|
|
725
|
+
states, transitions = _planner_state_for_generation(agent, generation)
|
|
726
|
+
total = len(states) + len(transitions)
|
|
727
|
+
if total > caps.get("stateDiagram", 40):
|
|
728
|
+
lines.append(_truncation_placeholder(
|
|
729
|
+
kind="stateDiagram", total=total,
|
|
730
|
+
cap=caps["stateDiagram"],
|
|
731
|
+
top_fanout=[],
|
|
732
|
+
catalog_pointer="section 7 (artifact catalogs)",
|
|
733
|
+
))
|
|
734
|
+
return "\n".join(lines)
|
|
735
|
+
|
|
736
|
+
rendered = load_mermaid(
|
|
737
|
+
"planner_state",
|
|
738
|
+
STATES="\n".join(states).lstrip(),
|
|
739
|
+
TRANSITIONS="\n".join(transitions).lstrip(),
|
|
740
|
+
)
|
|
741
|
+
lines.append("```mermaid")
|
|
742
|
+
lines.append(rendered)
|
|
743
|
+
lines.append("```")
|
|
744
|
+
return "\n".join(lines)
|
|
745
|
+
|
|
746
|
+
|
|
747
|
+
def _render_data_flow(
|
|
748
|
+
tree: Dict[str, Any],
|
|
749
|
+
walker: _TreeWalker,
|
|
750
|
+
caps: Dict[str, int],
|
|
751
|
+
) -> str:
|
|
752
|
+
# P2.2-1: section 6 — flowchart LR with labeled param edges.
|
|
753
|
+
lines = ["## 6. Data flow / context propagation", ""]
|
|
754
|
+
|
|
755
|
+
# Build node list — User, Planner, each topic, each action.
|
|
756
|
+
nodes: List[str] = [" User([User utterance])", " Planner[Planner]"]
|
|
757
|
+
for topic in walker.topics:
|
|
758
|
+
nodes.append(f" {_safe_id(topic['api_name'])}[Topic: {_md_escape(topic['api_name'])}]")
|
|
759
|
+
|
|
760
|
+
edges: List[str] = [" User --> Planner"]
|
|
761
|
+
for topic in walker.topics:
|
|
762
|
+
edges.append(f" Planner --> {_safe_id(topic['api_name'])}")
|
|
763
|
+
for action in topic["actions"]:
|
|
764
|
+
label = _display_name(action.get("raw") or action)
|
|
765
|
+
nodes.append(
|
|
766
|
+
f" {_safe_id(action['api_name'])}[[Action: {_md_escape(label)}]]"
|
|
767
|
+
)
|
|
768
|
+
# Labeled edge when the planner attr metadata declares a
|
|
769
|
+
# parameter hand-off; fall back to a bare edge otherwise.
|
|
770
|
+
attr = (action.get("raw") or {}).get("planner_attr") or {}
|
|
771
|
+
var_name = attr.get("variable_name") or attr.get("name")
|
|
772
|
+
var_type = attr.get("data_type") or attr.get("type")
|
|
773
|
+
if var_name:
|
|
774
|
+
label = _md_escape(var_name)
|
|
775
|
+
if var_type:
|
|
776
|
+
label = f"{label}: {_md_escape(var_type)}"
|
|
777
|
+
edges.append(
|
|
778
|
+
f" {_safe_id(topic['api_name'])} -->|{label}| "
|
|
779
|
+
f"{_safe_id(action['api_name'])}"
|
|
780
|
+
)
|
|
781
|
+
else:
|
|
782
|
+
edges.append(
|
|
783
|
+
f" {_safe_id(topic['api_name'])} --> "
|
|
784
|
+
f"{_safe_id(action['api_name'])}"
|
|
785
|
+
)
|
|
786
|
+
|
|
787
|
+
total = len(nodes) + len(edges)
|
|
788
|
+
if total > caps.get("flowchart", 200):
|
|
789
|
+
lines.append(_truncation_placeholder(
|
|
790
|
+
kind="flowchart", total=total,
|
|
791
|
+
cap=caps["flowchart"],
|
|
792
|
+
top_fanout=walker.top_fanout(5),
|
|
793
|
+
catalog_pointer="section 7 (artifact catalogs)",
|
|
794
|
+
))
|
|
795
|
+
return "\n".join(lines)
|
|
796
|
+
|
|
797
|
+
rendered = load_mermaid(
|
|
798
|
+
"data_flow",
|
|
799
|
+
NODES="\n".join(nodes).lstrip(),
|
|
800
|
+
EDGES="\n".join(edges).lstrip(),
|
|
801
|
+
)
|
|
802
|
+
lines.append("```mermaid")
|
|
803
|
+
lines.append(rendered)
|
|
804
|
+
lines.append("```")
|
|
805
|
+
return "\n".join(lines)
|
|
806
|
+
|
|
807
|
+
|
|
808
|
+
def _render_artifact_catalogs(walker: _TreeWalker) -> str:
|
|
809
|
+
# P2.2-1: section 7 — H3 per flow / apex / prompt + signature.
|
|
810
|
+
lines = ["## 7. Flow / Apex / Prompt catalogs", ""]
|
|
811
|
+
|
|
812
|
+
if walker.flows:
|
|
813
|
+
lines.append("### Flows")
|
|
814
|
+
lines.append("")
|
|
815
|
+
for name in sorted(walker.flows):
|
|
816
|
+
node = walker.flows[name]
|
|
817
|
+
sig = node.get("signature") or node.get("_signature")
|
|
818
|
+
# Gap 2 fix (2026-05-05): main._stamp_signatures stamps
|
|
819
|
+
# `_signature_reason` on flows whose body we can't retrieve
|
|
820
|
+
# (managed package, no active version, metadata fetch miss).
|
|
821
|
+
# Surface the reason so the rendered markdown distinguishes
|
|
822
|
+
# a known limitation from a silent hole.
|
|
823
|
+
reason = node.get("_signature_reason")
|
|
824
|
+
lines.append(f"#### `{_md_escape(name)}`")
|
|
825
|
+
lines.append("")
|
|
826
|
+
if sig:
|
|
827
|
+
lines.append("```")
|
|
828
|
+
lines.append(str(sig))
|
|
829
|
+
lines.append("```")
|
|
830
|
+
elif reason:
|
|
831
|
+
lines.append(f"_Signature not captured — {_md_escape(reason)}._")
|
|
832
|
+
else:
|
|
833
|
+
lines.append("_Signature not captured._")
|
|
834
|
+
lines.append("")
|
|
835
|
+
|
|
836
|
+
if walker.apex:
|
|
837
|
+
lines.append("### Apex classes")
|
|
838
|
+
lines.append("")
|
|
839
|
+
for name in sorted(walker.apex):
|
|
840
|
+
node = walker.apex[name]
|
|
841
|
+
sig = node.get("signature") or node.get("_signature")
|
|
842
|
+
lines.append(f"#### `{_md_escape(name)}`")
|
|
843
|
+
lines.append("")
|
|
844
|
+
if sig:
|
|
845
|
+
lines.append("```")
|
|
846
|
+
lines.append(str(sig))
|
|
847
|
+
lines.append("```")
|
|
848
|
+
else:
|
|
849
|
+
lines.append("_Signature not captured._")
|
|
850
|
+
lines.append("")
|
|
851
|
+
|
|
852
|
+
if walker.prompts:
|
|
853
|
+
lines.append("### Prompt templates")
|
|
854
|
+
lines.append("")
|
|
855
|
+
for name in sorted(walker.prompts):
|
|
856
|
+
node = walker.prompts[name]
|
|
857
|
+
sig = node.get("signature") or node.get("_signature")
|
|
858
|
+
prompt_type = node.get("prompt_type")
|
|
859
|
+
# Gap C (2026-05-05): retrieve_prompt_templates stamps
|
|
860
|
+
# master_label / content / inputs / _body_available onto
|
|
861
|
+
# each PROMPT_TEMPLATE leaf. Emit the real body when
|
|
862
|
+
# available; fall back to the stub only when `_body_available`
|
|
863
|
+
# is explicitly False (retrieve failed / not requested) AND
|
|
864
|
+
# there's no signature/type to show.
|
|
865
|
+
master_label = node.get("master_label")
|
|
866
|
+
content = node.get("content")
|
|
867
|
+
inputs = node.get("inputs") or []
|
|
868
|
+
body_available = node.get("_body_available")
|
|
869
|
+
lines.append(f"#### `{_md_escape(name)}`")
|
|
870
|
+
lines.append("")
|
|
871
|
+
if master_label:
|
|
872
|
+
lines.append(f"_Label: {_md_escape(master_label)}_")
|
|
873
|
+
lines.append("")
|
|
874
|
+
if prompt_type:
|
|
875
|
+
lines.append(f"- Type: `{_md_escape(prompt_type)}`")
|
|
876
|
+
if inputs:
|
|
877
|
+
lines.append("**Inputs**:")
|
|
878
|
+
for inp in inputs:
|
|
879
|
+
if not isinstance(inp, dict):
|
|
880
|
+
continue
|
|
881
|
+
iname = inp.get("name") or "?"
|
|
882
|
+
itype = inp.get("dataType")
|
|
883
|
+
if itype:
|
|
884
|
+
lines.append(
|
|
885
|
+
f"- `{_md_escape(iname)}`: "
|
|
886
|
+
f"`{_md_escape(itype)}`"
|
|
887
|
+
)
|
|
888
|
+
else:
|
|
889
|
+
lines.append(f"- `{_md_escape(iname)}`")
|
|
890
|
+
lines.append("")
|
|
891
|
+
if content:
|
|
892
|
+
lines.append("```text")
|
|
893
|
+
lines.append(str(content))
|
|
894
|
+
lines.append("```")
|
|
895
|
+
elif sig:
|
|
896
|
+
lines.append("```")
|
|
897
|
+
lines.append(str(sig))
|
|
898
|
+
lines.append("```")
|
|
899
|
+
if (
|
|
900
|
+
not content and not sig and not prompt_type
|
|
901
|
+
and not master_label and not inputs
|
|
902
|
+
):
|
|
903
|
+
if body_available is False:
|
|
904
|
+
lines.append("_Body not retrieved._")
|
|
905
|
+
else:
|
|
906
|
+
lines.append("_Details not captured._")
|
|
907
|
+
lines.append("")
|
|
908
|
+
|
|
909
|
+
if not (walker.flows or walker.apex or walker.prompts):
|
|
910
|
+
lines.append("_No backing artifacts in tree._")
|
|
911
|
+
|
|
912
|
+
return "\n".join(lines).rstrip()
|
|
913
|
+
|
|
914
|
+
|
|
915
|
+
def _render_unresolved(tree: Dict[str, Any], walker: _TreeWalker) -> str:
|
|
916
|
+
# P2.2-1: section 8 — unresolved refs + artifact pointers.
|
|
917
|
+
lines = ["## 8. Unresolved refs + artifact pointers", ""]
|
|
918
|
+
unresolved = tree.get("_unresolved") or []
|
|
919
|
+
if unresolved:
|
|
920
|
+
lines.append("> **{} unresolved refs.**".format(len(unresolved)))
|
|
921
|
+
lines.append("")
|
|
922
|
+
lines.append("| Kind | Api name | Reason |")
|
|
923
|
+
lines.append("|---|---|---|")
|
|
924
|
+
for ref in unresolved:
|
|
925
|
+
lines.append("| {} | `{}` | {} |".format(
|
|
926
|
+
_md_escape(ref.get("kind") or "?"),
|
|
927
|
+
_md_escape(ref.get("api_name") or "?"),
|
|
928
|
+
_md_escape(ref.get("reason") or "?"),
|
|
929
|
+
))
|
|
930
|
+
else:
|
|
931
|
+
lines.append("_No unresolved references._")
|
|
932
|
+
lines.append("")
|
|
933
|
+
lines.append("### Artifact pointers")
|
|
934
|
+
lines.append("")
|
|
935
|
+
lines.append(
|
|
936
|
+
"- Full tree JSON: same directory as this file (`metadata_tree.json`)"
|
|
937
|
+
)
|
|
938
|
+
lines.append("- Build manifest: cache dir / `manifest.json`")
|
|
939
|
+
return "\n".join(lines)
|
|
940
|
+
|
|
941
|
+
|
|
942
|
+
def _render_dependency_graph(
|
|
943
|
+
tree: Dict[str, Any],
|
|
944
|
+
walker: _TreeWalker,
|
|
945
|
+
caps: Dict[str, int],
|
|
946
|
+
) -> str:
|
|
947
|
+
# P2.2-1: conditional — render only on unresolved or cycles.
|
|
948
|
+
lines = ["## Dependency graph (conditional)", ""]
|
|
949
|
+
unresolved = tree.get("_unresolved") or []
|
|
950
|
+
|
|
951
|
+
nodes: List[str] = []
|
|
952
|
+
edges: List[str] = []
|
|
953
|
+
seen: set[str] = set()
|
|
954
|
+
|
|
955
|
+
def add_node(label: str, unresolved_flag: bool) -> None:
|
|
956
|
+
nid = _safe_id(label)
|
|
957
|
+
if nid in seen:
|
|
958
|
+
return
|
|
959
|
+
seen.add(nid)
|
|
960
|
+
suffix = ":::unresolved" if unresolved_flag else ""
|
|
961
|
+
nodes.append(f" {nid}[{_md_escape(label)}]{suffix}")
|
|
962
|
+
|
|
963
|
+
for parent, child, _ in walker.edges:
|
|
964
|
+
add_node(parent, False)
|
|
965
|
+
add_node(child, False)
|
|
966
|
+
edges.append(f" {_safe_id(parent)} --> {_safe_id(child)}")
|
|
967
|
+
|
|
968
|
+
for ref in unresolved:
|
|
969
|
+
name = ref.get("api_name") or "?"
|
|
970
|
+
add_node(name, True)
|
|
971
|
+
|
|
972
|
+
for node_label, cycle_to in walker.cycles:
|
|
973
|
+
add_node(node_label, False)
|
|
974
|
+
add_node(cycle_to, False)
|
|
975
|
+
edges.append(
|
|
976
|
+
f" {_safe_id(node_label)} -.->|cycle| {_safe_id(cycle_to)}"
|
|
977
|
+
)
|
|
978
|
+
|
|
979
|
+
total = len(nodes)
|
|
980
|
+
if total > caps.get("graph", 100):
|
|
981
|
+
lines.append(_truncation_placeholder(
|
|
982
|
+
kind="graph", total=total,
|
|
983
|
+
cap=caps["graph"],
|
|
984
|
+
top_fanout=walker.top_fanout(5),
|
|
985
|
+
catalog_pointer="section 8 (unresolved refs)",
|
|
986
|
+
))
|
|
987
|
+
return "\n".join(lines)
|
|
988
|
+
|
|
989
|
+
rendered = load_mermaid(
|
|
990
|
+
"dependency_graph",
|
|
991
|
+
NODES="\n".join(nodes).lstrip() if nodes else "%% no nodes",
|
|
992
|
+
EDGES="\n".join(edges).lstrip() if edges else "%% no edges",
|
|
993
|
+
)
|
|
994
|
+
lines.append("```mermaid")
|
|
995
|
+
lines.append(rendered)
|
|
996
|
+
lines.append("```")
|
|
997
|
+
return "\n".join(lines)
|
|
998
|
+
|
|
999
|
+
|
|
1000
|
+
# ---------------------------------------------------------------------------
|
|
1001
|
+
# Helpers
|
|
1002
|
+
# ---------------------------------------------------------------------------
|
|
1003
|
+
|
|
1004
|
+
|
|
1005
|
+
def _truncation_placeholder(
|
|
1006
|
+
*,
|
|
1007
|
+
kind: str,
|
|
1008
|
+
total: int,
|
|
1009
|
+
cap: int,
|
|
1010
|
+
top_fanout: List[Tuple[str, int]],
|
|
1011
|
+
catalog_pointer: str,
|
|
1012
|
+
) -> str:
|
|
1013
|
+
# P2.2-1: explicit visual placeholder. Mentions the diagram kind, the
|
|
1014
|
+
# over-cap count, and points at the catalog section.
|
|
1015
|
+
lines = [
|
|
1016
|
+
f"> **[diagram truncated: {kind} — {total} elements exceed cap of {cap}]**",
|
|
1017
|
+
]
|
|
1018
|
+
if top_fanout:
|
|
1019
|
+
lines.append(">")
|
|
1020
|
+
lines.append("> Top 5 nodes by fan-out:")
|
|
1021
|
+
for name, count in top_fanout:
|
|
1022
|
+
lines.append(f"> - `{_md_escape(name)}` ({count})")
|
|
1023
|
+
lines.append(">")
|
|
1024
|
+
lines.append(f"> See {catalog_pointer} for the full listing.")
|
|
1025
|
+
return "\n".join(lines)
|
|
1026
|
+
|
|
1027
|
+
|
|
1028
|
+
def _safe_id(value: str) -> str:
|
|
1029
|
+
"""Return a mermaid-safe identifier. Mermaid node ids must be
|
|
1030
|
+
alphanumeric + underscore; anything else breaks the parser."""
|
|
1031
|
+
if not value:
|
|
1032
|
+
return "n_empty"
|
|
1033
|
+
out = []
|
|
1034
|
+
for ch in str(value):
|
|
1035
|
+
if ch.isalnum() or ch == "_":
|
|
1036
|
+
out.append(ch)
|
|
1037
|
+
else:
|
|
1038
|
+
out.append("_")
|
|
1039
|
+
# Prefix digit-leading ids so they don't collide with mermaid syntax.
|
|
1040
|
+
result = "".join(out)
|
|
1041
|
+
if result and result[0].isdigit():
|
|
1042
|
+
result = f"n_{result}"
|
|
1043
|
+
return result or "n_empty"
|