@salesforce/afv-skills 1.13.0 → 1.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- 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-ui-bundle-app/SKILL.md +33 -8
- package/skills/generating-custom-application/SKILL.md +1 -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-ui-bundle-custom-app/SKILL.md +93 -0
- package/skills/generating-ui-bundle-custom-app/docs/configure-metadata-custom-application.md +70 -0
- package/skills/generating-ui-bundle-metadata/SKILL.md +39 -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/reviewing-lwc-mobile-offline/SKILL.md +168 -0
- package/skills/reviewing-lwc-mobile-offline/references/grounding.md +7 -0
- package/skills/reviewing-lwc-mobile-offline/references/inline-graphql.md +43 -0
- package/skills/reviewing-lwc-mobile-offline/references/komaci-eslint.md +125 -0
- package/skills/reviewing-lwc-mobile-offline/references/lwc-if.md +78 -0
- package/skills/reviewing-lwc-mobile-offline/scripts/komaci.config.mjs +18 -0
- package/skills/reviewing-lwc-mobile-offline/scripts/package.json +10 -0
- package/skills/reviewing-lwc-mobile-offline/scripts/run-komaci.sh +69 -0
- 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/using-mobile-native-capabilities/SKILL.md +182 -0
- package/skills/using-mobile-native-capabilities/references/app-review.md +68 -0
- package/skills/using-mobile-native-capabilities/references/ar-space-capture.md +125 -0
- package/skills/using-mobile-native-capabilities/references/barcode-scanner.md +219 -0
- package/skills/using-mobile-native-capabilities/references/base-capability.md +22 -0
- package/skills/using-mobile-native-capabilities/references/biometrics.md +90 -0
- package/skills/using-mobile-native-capabilities/references/calendar.md +213 -0
- package/skills/using-mobile-native-capabilities/references/contacts.md +232 -0
- package/skills/using-mobile-native-capabilities/references/document-scanner.md +342 -0
- package/skills/using-mobile-native-capabilities/references/geofencing.md +123 -0
- package/skills/using-mobile-native-capabilities/references/location.md +158 -0
- package/skills/using-mobile-native-capabilities/references/mobile-capabilities.md +30 -0
- package/skills/using-mobile-native-capabilities/references/nfc.md +181 -0
- package/skills/using-mobile-native-capabilities/references/payments.md +95 -0
- 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,397 @@
|
|
|
1
|
+
"""Tests for ``parse_wave.main`` orchestrator + tree-walking helpers.
|
|
2
|
+
|
|
3
|
+
Covers:
|
|
4
|
+
- ``walk_and_inflate`` recursive Flow inflation
|
|
5
|
+
- ``inflate_flow_leaf`` cycle + depth-cap behavior
|
|
6
|
+
- ``build_root_children`` bundle → root children + new_refs
|
|
7
|
+
- ``main`` (full path) env-driven pipeline against a tmp WORK_DIR
|
|
8
|
+
- ``main --finalize-cap`` drain pending → unresolved
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import os
|
|
14
|
+
import unittest
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from tempfile import TemporaryDirectory
|
|
17
|
+
from unittest import mock
|
|
18
|
+
|
|
19
|
+
from . import _bootstrap # noqa: F401 — sys.path setup
|
|
20
|
+
|
|
21
|
+
import parse_wave # type: ignore
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
_FLOW_NS = "http://soap.sforce.com/2006/04/metadata"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# -----------------------------------------------------------------------------
|
|
28
|
+
# walk_and_inflate
|
|
29
|
+
# -----------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class WalkAndInflateTests(unittest.TestCase):
|
|
33
|
+
|
|
34
|
+
def test_no_op_for_non_FLOW_non_GEN_AI_FUNCTION_node(self):
|
|
35
|
+
node = {"kind": "TOPIC", "api_name": "T1", "children": []}
|
|
36
|
+
flow_children: dict = {}
|
|
37
|
+
# Should not raise; should not mutate.
|
|
38
|
+
parse_wave.walk_and_inflate(node, flow_children)
|
|
39
|
+
self.assertEqual(node["children"], [])
|
|
40
|
+
|
|
41
|
+
def test_inflates_flow_leaf_under_gen_ai_function(self):
|
|
42
|
+
# GEN_AI_FUNCTION wraps a FLOW leaf — walker recurses into it.
|
|
43
|
+
node = {
|
|
44
|
+
"kind": "GEN_AI_FUNCTION",
|
|
45
|
+
"api_name": "GAF1",
|
|
46
|
+
"children": [{"kind": "FLOW", "api_name": "MyFlow", "children": []}],
|
|
47
|
+
}
|
|
48
|
+
flow_children = {"MyFlow": [
|
|
49
|
+
{"kind": "APEX", "element_name": "callA", "api_name": "MyClass"},
|
|
50
|
+
]}
|
|
51
|
+
parse_wave.walk_and_inflate(node, flow_children)
|
|
52
|
+
# FLOW leaf got inflated with the APEX child
|
|
53
|
+
flow_leaf = node["children"][0]
|
|
54
|
+
self.assertEqual(len(flow_leaf["children"]), 1)
|
|
55
|
+
self.assertEqual(flow_leaf["children"][0]["kind"], "APEX")
|
|
56
|
+
|
|
57
|
+
def test_recursively_walks_through_topic_to_inflate_flow(self):
|
|
58
|
+
# Walker should descend through TOPIC/non-special nodes too.
|
|
59
|
+
node = {
|
|
60
|
+
"kind": "BOT_DEFINITION", "api_name": "Agent",
|
|
61
|
+
"children": [{
|
|
62
|
+
"kind": "TOPIC", "api_name": "T1",
|
|
63
|
+
"children": [{
|
|
64
|
+
"kind": "GEN_AI_FUNCTION", "api_name": "GAF1",
|
|
65
|
+
"children": [{"kind": "FLOW", "api_name": "DeepFlow",
|
|
66
|
+
"children": []}],
|
|
67
|
+
}],
|
|
68
|
+
}],
|
|
69
|
+
}
|
|
70
|
+
flow_children = {"DeepFlow": [
|
|
71
|
+
{"kind": "APEX", "element_name": "callA", "api_name": "DeepClass"},
|
|
72
|
+
]}
|
|
73
|
+
parse_wave.walk_and_inflate(node, flow_children)
|
|
74
|
+
# DeepFlow got the APEX child
|
|
75
|
+
deep_flow = node["children"][0]["children"][0]["children"][0]
|
|
76
|
+
self.assertEqual(deep_flow["children"][0]["api_name"], "DeepClass")
|
|
77
|
+
|
|
78
|
+
def test_pending_out_collects_depth_cap_truncations(self):
|
|
79
|
+
# Build a chain longer than MAX_BFS_DEPTH so the cap trips.
|
|
80
|
+
# Generate 25 flows that each subflow into the next.
|
|
81
|
+
flow_children: dict = {}
|
|
82
|
+
for i in range(25):
|
|
83
|
+
next_name = f"F{i+1}" if i < 24 else None
|
|
84
|
+
kids = []
|
|
85
|
+
if next_name:
|
|
86
|
+
kids.append({"kind": "FLOW", "element_name": f"sub_{i}",
|
|
87
|
+
"api_name": next_name})
|
|
88
|
+
flow_children[f"F{i}"] = kids
|
|
89
|
+
node = {"kind": "FLOW", "api_name": "F0", "children": []}
|
|
90
|
+
pending: dict = parse_wave.empty_kind_sets()
|
|
91
|
+
parse_wave.walk_and_inflate(node, flow_children, pending_out=pending)
|
|
92
|
+
# Some subflow ended up in pending due to the depth cap (>= MAX_BFS_DEPTH).
|
|
93
|
+
self.assertTrue(any(pending[k] for k in parse_wave.BFS_KINDS))
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# -----------------------------------------------------------------------------
|
|
97
|
+
# inflate_flow_leaf — cycle detection
|
|
98
|
+
# -----------------------------------------------------------------------------
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class InflateFlowLeafCycleTests(unittest.TestCase):
|
|
102
|
+
|
|
103
|
+
def test_self_recursion_marked_as_cycle(self):
|
|
104
|
+
# Flow A subflows back to A itself → cycle.
|
|
105
|
+
flow_children = {"A": [
|
|
106
|
+
{"kind": "FLOW", "element_name": "self", "api_name": "A"},
|
|
107
|
+
]}
|
|
108
|
+
leaf = {"kind": "FLOW", "api_name": "A", "children": []}
|
|
109
|
+
parse_wave.inflate_flow_leaf(leaf, flow_children)
|
|
110
|
+
# The recursive child got annotated with _truncated.
|
|
111
|
+
kid = leaf["children"][0]
|
|
112
|
+
self.assertIn("_truncated", kid)
|
|
113
|
+
self.assertEqual(kid["_truncated"]["reason"],
|
|
114
|
+
parse_wave.TRUNCATION_CYCLE)
|
|
115
|
+
|
|
116
|
+
def test_unknown_flow_no_inflation(self):
|
|
117
|
+
# leaf.api_name not in flow_children → nothing to inflate.
|
|
118
|
+
leaf = {"kind": "FLOW", "api_name": "Missing", "children": []}
|
|
119
|
+
parse_wave.inflate_flow_leaf(leaf, {})
|
|
120
|
+
self.assertEqual(leaf["children"], [])
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
# -----------------------------------------------------------------------------
|
|
124
|
+
# build_root_children
|
|
125
|
+
# -----------------------------------------------------------------------------
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class BuildRootChildrenTests(unittest.TestCase):
|
|
129
|
+
|
|
130
|
+
def test_topics_become_root_children(self):
|
|
131
|
+
bundle = {
|
|
132
|
+
"topics": [
|
|
133
|
+
{"name": "Greetings", "actions": [
|
|
134
|
+
{"name": "Hello", "invocationTarget": "MyFlow",
|
|
135
|
+
"invocationTargetType": "flow"},
|
|
136
|
+
]},
|
|
137
|
+
],
|
|
138
|
+
}
|
|
139
|
+
visited = parse_wave.empty_kind_sets()
|
|
140
|
+
aux: set = set()
|
|
141
|
+
children, new_refs = parse_wave.build_root_children(bundle, visited, aux)
|
|
142
|
+
self.assertEqual(len(children), 1)
|
|
143
|
+
self.assertEqual(children[0]["kind"], "TOPIC")
|
|
144
|
+
self.assertEqual(children[0]["api_name"], "Greetings")
|
|
145
|
+
# GEN_AI_FUNCTION wrapping the action
|
|
146
|
+
self.assertEqual(children[0]["children"][0]["kind"], "GEN_AI_FUNCTION")
|
|
147
|
+
# FLOW ref harvested into new_refs
|
|
148
|
+
self.assertIn("MyFlow", new_refs["FLOW"])
|
|
149
|
+
# Topic + action recorded in aux_visited
|
|
150
|
+
self.assertIn(("TOPIC", "Greetings"), aux)
|
|
151
|
+
self.assertIn(("GEN_AI_FUNCTION", "Hello"), aux)
|
|
152
|
+
|
|
153
|
+
def test_skips_already_visited_refs(self):
|
|
154
|
+
bundle = {
|
|
155
|
+
"topics": [{"name": "T", "actions": [
|
|
156
|
+
{"name": "A1", "invocationTarget": "FlowA",
|
|
157
|
+
"invocationTargetType": "flow"},
|
|
158
|
+
]}],
|
|
159
|
+
}
|
|
160
|
+
visited = parse_wave.empty_kind_sets()
|
|
161
|
+
visited["FLOW"].add("FlowA")
|
|
162
|
+
aux: set = set()
|
|
163
|
+
_, new_refs = parse_wave.build_root_children(bundle, visited, aux)
|
|
164
|
+
# FlowA already visited → not added to new_refs
|
|
165
|
+
self.assertNotIn("FlowA", new_refs["FLOW"])
|
|
166
|
+
|
|
167
|
+
def test_standard_action_does_not_pollute_new_refs(self):
|
|
168
|
+
# STANDARD_ACTION is declared-only, must NOT
|
|
169
|
+
# land in new_refs (would pollute _pending_fetches).
|
|
170
|
+
bundle = {
|
|
171
|
+
"topics": [{"name": "T", "actions": [
|
|
172
|
+
{"name": "A1", "invocationTarget": "createRecord",
|
|
173
|
+
"invocationTargetType": "standardinvocableaction"},
|
|
174
|
+
]}],
|
|
175
|
+
}
|
|
176
|
+
visited = parse_wave.empty_kind_sets()
|
|
177
|
+
aux: set = set()
|
|
178
|
+
_, new_refs = parse_wave.build_root_children(bundle, visited, aux)
|
|
179
|
+
self.assertNotIn("createRecord", new_refs["STANDARD_ACTION"])
|
|
180
|
+
|
|
181
|
+
def test_empty_bundle_returns_empty_children(self):
|
|
182
|
+
children, new_refs = parse_wave.build_root_children(
|
|
183
|
+
{}, parse_wave.empty_kind_sets(), set(),
|
|
184
|
+
)
|
|
185
|
+
self.assertEqual(children, [])
|
|
186
|
+
for v in new_refs.values():
|
|
187
|
+
self.assertEqual(v, set())
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
# -----------------------------------------------------------------------------
|
|
191
|
+
# main() — happy path + finalize-cap branch
|
|
192
|
+
# -----------------------------------------------------------------------------
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def _setup_workdir(tmp: Path, *, bundle: dict | None = None) -> Path:
|
|
196
|
+
"""Plant a WORK_DIR with _bundle_parsed.json + minimal sf_meta layout."""
|
|
197
|
+
work_dir = tmp / "work"
|
|
198
|
+
work_dir.mkdir()
|
|
199
|
+
bundle = bundle or {
|
|
200
|
+
"plannerName": "MyPlanner",
|
|
201
|
+
"plannerType": "Atlas__Reasoning",
|
|
202
|
+
"generation": "nga",
|
|
203
|
+
"topics": [{"name": "Greetings", "actions": [
|
|
204
|
+
{"name": "SayHi", "invocationTarget": "GreetingsFlow",
|
|
205
|
+
"invocationTargetType": "flow"},
|
|
206
|
+
]}],
|
|
207
|
+
}
|
|
208
|
+
(work_dir / "_bundle_parsed.json").write_text(json.dumps(bundle))
|
|
209
|
+
# Empty sf_meta is fine — harvest_waves returns empty when missing.
|
|
210
|
+
return work_dir
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _run_main(*, work_dir: Path, argv_extra: list[str] | None = None,
|
|
214
|
+
env_extra: dict | None = None) -> int:
|
|
215
|
+
"""Invoke parse_wave.main with controlled env + argv."""
|
|
216
|
+
saved_env = dict(os.environ)
|
|
217
|
+
saved_argv = list(parse_wave.sys.argv)
|
|
218
|
+
try:
|
|
219
|
+
os.environ.update({
|
|
220
|
+
"WORK_DIR": str(work_dir),
|
|
221
|
+
"AGENT_API_NAME": "DemoAgent",
|
|
222
|
+
"AGENT_VERSION": "v3",
|
|
223
|
+
"BOT_ID": "0Xx000000000ABC",
|
|
224
|
+
"BOT_MASTER_LABEL": "Demo Agent",
|
|
225
|
+
"VERSION_AUTO_PICKED": "false",
|
|
226
|
+
})
|
|
227
|
+
if env_extra:
|
|
228
|
+
os.environ.update(env_extra)
|
|
229
|
+
parse_wave.sys.argv = ["parse_wave.py", *(argv_extra or [])]
|
|
230
|
+
return parse_wave.main()
|
|
231
|
+
finally:
|
|
232
|
+
os.environ.clear()
|
|
233
|
+
os.environ.update(saved_env)
|
|
234
|
+
parse_wave.sys.argv = saved_argv
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
class MainHappyPathTests(unittest.TestCase):
|
|
238
|
+
|
|
239
|
+
def test_main_writes_declared_action_tree_json(self):
|
|
240
|
+
with TemporaryDirectory() as t:
|
|
241
|
+
tmp = Path(t)
|
|
242
|
+
work_dir = _setup_workdir(tmp)
|
|
243
|
+
with mock.patch.object(parse_wave.sys, "stderr"):
|
|
244
|
+
rc = _run_main(work_dir=work_dir)
|
|
245
|
+
self.assertEqual(rc, 0)
|
|
246
|
+
tree_path = work_dir / "declared_action_tree.json"
|
|
247
|
+
self.assertTrue(tree_path.is_file())
|
|
248
|
+
tree = json.loads(tree_path.read_text())
|
|
249
|
+
# Tree has the expected shape
|
|
250
|
+
self.assertEqual(tree["_schema_version"], "3.1")
|
|
251
|
+
self.assertEqual(tree["agent"]["api_name"], "DemoAgent")
|
|
252
|
+
self.assertEqual(tree["root"]["kind"], "BOT_DEFINITION")
|
|
253
|
+
# One topic became a root child
|
|
254
|
+
kinds = [c["kind"] for c in tree["root"]["children"]]
|
|
255
|
+
self.assertIn("TOPIC", kinds)
|
|
256
|
+
|
|
257
|
+
def test_main_records_pending_for_unfetched_flow(self):
|
|
258
|
+
with TemporaryDirectory() as t:
|
|
259
|
+
tmp = Path(t)
|
|
260
|
+
work_dir = _setup_workdir(tmp)
|
|
261
|
+
with mock.patch.object(parse_wave.sys, "stderr"):
|
|
262
|
+
_run_main(work_dir=work_dir)
|
|
263
|
+
tree = json.loads(
|
|
264
|
+
(work_dir / "declared_action_tree.json").read_text()
|
|
265
|
+
)
|
|
266
|
+
# GreetingsFlow not on disk → in _pending_fetches.FLOW
|
|
267
|
+
self.assertIn("GreetingsFlow", tree["_pending_fetches"]["FLOW"])
|
|
268
|
+
self.assertTrue(tree["_partial"])
|
|
269
|
+
# _partial_reason populated on first run when pending refs exist
|
|
270
|
+
# but no depth cap was tripped.
|
|
271
|
+
self.assertEqual(tree["_partial_reason"], "pending-refs")
|
|
272
|
+
|
|
273
|
+
def test_main_writes_pending_refs_reason_when_initial_reason_is_none(self):
|
|
274
|
+
# init_tree seeds `_partial_reason=None`. The writer's elif branch
|
|
275
|
+
# must populate it with "pending-refs" when any pending bucket is
|
|
276
|
+
# non-empty and the depth cap did not trip — a setdefault against
|
|
277
|
+
# `None` is a no-op, which is the bug this guards.
|
|
278
|
+
with TemporaryDirectory() as t:
|
|
279
|
+
tmp = Path(t)
|
|
280
|
+
work_dir = _setup_workdir(tmp)
|
|
281
|
+
with mock.patch.object(parse_wave.sys, "stderr"):
|
|
282
|
+
_run_main(work_dir=work_dir)
|
|
283
|
+
tree = json.loads(
|
|
284
|
+
(work_dir / "declared_action_tree.json").read_text()
|
|
285
|
+
)
|
|
286
|
+
# Sanity: pending exists, depth cap was NOT tripped.
|
|
287
|
+
self.assertIn("GreetingsFlow", tree["_pending_fetches"]["FLOW"])
|
|
288
|
+
# The fix: a None-valued reason gets promoted to "pending-refs".
|
|
289
|
+
self.assertEqual(tree["_partial_reason"], "pending-refs")
|
|
290
|
+
|
|
291
|
+
def test_main_preserves_existing_partial_reason_when_pending_refs(self):
|
|
292
|
+
# init_tree primes _partial_reason=None; setdefault used to be a
|
|
293
|
+
# no-op against that None and left the reason blank. Today the
|
|
294
|
+
# writer treats None as "unset" and fills in "pending-refs".
|
|
295
|
+
# When a prior reason exists (e.g. "max-depth-cap" from a previous
|
|
296
|
+
# wave), the writer must NOT overwrite it.
|
|
297
|
+
with TemporaryDirectory() as t:
|
|
298
|
+
tmp = Path(t)
|
|
299
|
+
work_dir = _setup_workdir(tmp)
|
|
300
|
+
# Plant a tree on disk that mimics a previously-suspended run:
|
|
301
|
+
# _partial_reason="max-depth-cap" and empty root children, so
|
|
302
|
+
# main() enters the fresh-tree branch and re-collects bundle
|
|
303
|
+
# refs (GreetingsFlow pending) yet sees the prior reason.
|
|
304
|
+
tree_path = work_dir / "declared_action_tree.json"
|
|
305
|
+
tree_path.write_text(json.dumps({
|
|
306
|
+
"_schema_version": "3.1",
|
|
307
|
+
"agent": {"api_name": "DemoAgent", "version": "v3"},
|
|
308
|
+
"root": {"kind": "BOT_DEFINITION", "api_name": "DemoAgent",
|
|
309
|
+
"children": []},
|
|
310
|
+
"_partial": True,
|
|
311
|
+
"_partial_reason": "max-depth-cap",
|
|
312
|
+
"_pending_fetches": {k: [] for k in parse_wave.BFS_KINDS},
|
|
313
|
+
"_unresolved": [],
|
|
314
|
+
"_visited": [],
|
|
315
|
+
}))
|
|
316
|
+
with mock.patch.object(parse_wave.sys, "stderr"):
|
|
317
|
+
_run_main(work_dir=work_dir)
|
|
318
|
+
tree2 = json.loads(tree_path.read_text())
|
|
319
|
+
# GreetingsFlow is pending → any_pending=True → elif branch runs.
|
|
320
|
+
self.assertIn("GreetingsFlow", tree2["_pending_fetches"]["FLOW"])
|
|
321
|
+
# Existing more-specific reason is preserved.
|
|
322
|
+
self.assertEqual(tree2["_partial_reason"], "max-depth-cap")
|
|
323
|
+
|
|
324
|
+
def test_main_returns_one_when_work_dir_env_missing(self):
|
|
325
|
+
saved_env = dict(os.environ)
|
|
326
|
+
try:
|
|
327
|
+
for k in ("WORK_DIR", "AGENT_API_NAME", "AGENT_VERSION"):
|
|
328
|
+
os.environ.pop(k, None)
|
|
329
|
+
with mock.patch.object(parse_wave.sys, "argv", ["parse_wave.py"]):
|
|
330
|
+
with mock.patch.object(parse_wave.sys, "stderr"):
|
|
331
|
+
rc = parse_wave.main()
|
|
332
|
+
finally:
|
|
333
|
+
os.environ.clear()
|
|
334
|
+
os.environ.update(saved_env)
|
|
335
|
+
self.assertEqual(rc, 1)
|
|
336
|
+
|
|
337
|
+
def test_main_returns_one_when_bundle_missing(self):
|
|
338
|
+
with TemporaryDirectory() as t:
|
|
339
|
+
tmp = Path(t)
|
|
340
|
+
work_dir = tmp / "work"
|
|
341
|
+
work_dir.mkdir()
|
|
342
|
+
# No _bundle_parsed.json
|
|
343
|
+
with mock.patch.object(parse_wave.sys, "stderr"):
|
|
344
|
+
rc = _run_main(work_dir=work_dir)
|
|
345
|
+
self.assertEqual(rc, 1)
|
|
346
|
+
|
|
347
|
+
def test_main_reparses_existing_tree(self):
|
|
348
|
+
# Two-pass: first run creates the tree, second re-uses it.
|
|
349
|
+
with TemporaryDirectory() as t:
|
|
350
|
+
tmp = Path(t)
|
|
351
|
+
work_dir = _setup_workdir(tmp)
|
|
352
|
+
with mock.patch.object(parse_wave.sys, "stderr"):
|
|
353
|
+
_run_main(work_dir=work_dir)
|
|
354
|
+
# Second invocation should load + augment without crashing
|
|
355
|
+
rc = _run_main(work_dir=work_dir)
|
|
356
|
+
self.assertEqual(rc, 0)
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
class MainFinalizeCapTests(unittest.TestCase):
|
|
360
|
+
|
|
361
|
+
def test_finalize_cap_drains_pending_into_unresolved(self):
|
|
362
|
+
with TemporaryDirectory() as t:
|
|
363
|
+
tmp = Path(t)
|
|
364
|
+
work_dir = _setup_workdir(tmp)
|
|
365
|
+
# First run produces a tree with pending FLOW=GreetingsFlow.
|
|
366
|
+
with mock.patch.object(parse_wave.sys, "stderr"):
|
|
367
|
+
_run_main(work_dir=work_dir)
|
|
368
|
+
# --finalize-cap drains pending → unresolved
|
|
369
|
+
rc = _run_main(
|
|
370
|
+
work_dir=work_dir, argv_extra=["--finalize-cap"],
|
|
371
|
+
)
|
|
372
|
+
self.assertEqual(rc, 0)
|
|
373
|
+
tree = json.loads(
|
|
374
|
+
(work_dir / "declared_action_tree.json").read_text()
|
|
375
|
+
)
|
|
376
|
+
# All buckets emptied
|
|
377
|
+
for kind in parse_wave.BFS_KINDS:
|
|
378
|
+
self.assertEqual(tree["_pending_fetches"][kind], [])
|
|
379
|
+
# GreetingsFlow now in _unresolved
|
|
380
|
+
api_names = [u["api_name"] for u in tree["_unresolved"]]
|
|
381
|
+
self.assertIn("GreetingsFlow", api_names)
|
|
382
|
+
|
|
383
|
+
def test_finalize_cap_with_no_existing_tree_is_noop(self):
|
|
384
|
+
with TemporaryDirectory() as t:
|
|
385
|
+
tmp = Path(t)
|
|
386
|
+
work_dir = tmp / "work"
|
|
387
|
+
work_dir.mkdir()
|
|
388
|
+
with mock.patch.object(parse_wave.sys, "stderr"):
|
|
389
|
+
rc = _run_main(
|
|
390
|
+
work_dir=work_dir, argv_extra=["--finalize-cap"],
|
|
391
|
+
)
|
|
392
|
+
# No tree file → noop, exit 0.
|
|
393
|
+
self.assertEqual(rc, 0)
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
if __name__ == "__main__":
|
|
397
|
+
unittest.main()
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
"""2026-05-03 revision: `inflate_flow_leaf` uses a per-branch ancestor
|
|
2
|
+
path set (`visited_in_path`) for cycle detection. The defensive
|
|
3
|
+
`MAX_BFS_DEPTH` cap is preserved as a last-resort termination guard
|
|
4
|
+
but is no longer the primitive that prevents runaway recursion.
|
|
5
|
+
|
|
6
|
+
The bug this regression suite prevents: shared utility flows such as
|
|
7
|
+
`handleFlowFault` appear on every real Agentforce flow's fault path.
|
|
8
|
+
Under the old `MAX_BFS_DEPTH = 5` behaviour the utility showed up in
|
|
9
|
+
`_pending_fetches["FLOW"]` with `PARTIAL_REASON=max-depth-cap` on any
|
|
10
|
+
moderately nested tree (e.g. `AGNT_Baz_Qux →
|
|
11
|
+
handleFlowFault`), even though `handleFlowFault` was trivially
|
|
12
|
+
expandable. Per-branch path-set semantics fix this: the same flow on
|
|
13
|
+
two sibling branches is NOT a cycle, only an ancestor-chain recurrence
|
|
14
|
+
is.
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import unittest
|
|
19
|
+
|
|
20
|
+
from . import _bootstrap # noqa: F401
|
|
21
|
+
|
|
22
|
+
import parse_wave # type: ignore
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _find_all(node: dict, api_name: str) -> list[dict]:
|
|
26
|
+
"""Collect every node in `node`'s subtree whose `api_name` matches."""
|
|
27
|
+
found: list[dict] = []
|
|
28
|
+
if node.get("api_name") == api_name:
|
|
29
|
+
found.append(node)
|
|
30
|
+
for c in node.get("children", []) or []:
|
|
31
|
+
found.extend(_find_all(c, api_name))
|
|
32
|
+
return found
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class SharedUtilityFlowExpansionTests(unittest.TestCase):
|
|
36
|
+
"""Direct repro of the real-org `handleFlowFault` bug.
|
|
37
|
+
|
|
38
|
+
Parent flow has two sibling children, each of which invokes the
|
|
39
|
+
same utility flow. Both utility instances must expand fully.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def test_shared_utility_flow_expands_on_every_branch(self):
|
|
43
|
+
# parent -> [childA, childB]; childA -> utility; childB -> utility
|
|
44
|
+
# utility itself calls a leaf apex (so we can assert `utility`
|
|
45
|
+
# got fully inflated with a child, not just a stub).
|
|
46
|
+
flow_children = {
|
|
47
|
+
"parent": [
|
|
48
|
+
{"kind": "FLOW", "api_name": "childA", "element_name": "call_A"},
|
|
49
|
+
{"kind": "FLOW", "api_name": "childB", "element_name": "call_B"},
|
|
50
|
+
],
|
|
51
|
+
"childA": [
|
|
52
|
+
{"kind": "FLOW", "api_name": "utility", "element_name": "call_util"},
|
|
53
|
+
],
|
|
54
|
+
"childB": [
|
|
55
|
+
{"kind": "FLOW", "api_name": "utility", "element_name": "call_util"},
|
|
56
|
+
],
|
|
57
|
+
"utility": [
|
|
58
|
+
{"kind": "APEX", "api_name": "XCSF_FlowFaultMessage", "element_name": "log"},
|
|
59
|
+
],
|
|
60
|
+
}
|
|
61
|
+
root = {"kind": "FLOW", "api_name": "parent", "children": []}
|
|
62
|
+
pending = parse_wave.empty_kind_sets()
|
|
63
|
+
|
|
64
|
+
parse_wave.inflate_flow_leaf(root, flow_children, pending_out=pending)
|
|
65
|
+
|
|
66
|
+
utilities = _find_all(root, "utility")
|
|
67
|
+
self.assertEqual(
|
|
68
|
+
len(utilities), 2,
|
|
69
|
+
"utility should appear once on each sibling branch",
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# Both utility instances must have expanded children (the apex
|
|
73
|
+
# leaf). The regression shape was: first branch expanded, second
|
|
74
|
+
# either in `_pending_fetches` or emitted as an empty-children
|
|
75
|
+
# stub due to depth-cap or false-cycle pruning.
|
|
76
|
+
for i, u in enumerate(utilities):
|
|
77
|
+
kids = u.get("children", [])
|
|
78
|
+
self.assertEqual(
|
|
79
|
+
len(kids), 1,
|
|
80
|
+
f"utility instance {i} should have 1 expanded child, got {kids}",
|
|
81
|
+
)
|
|
82
|
+
self.assertEqual(kids[0]["api_name"], "XCSF_FlowFaultMessage")
|
|
83
|
+
self.assertNotIn("_truncated", u)
|
|
84
|
+
self.assertNotIn("_cycle_back_to", u)
|
|
85
|
+
|
|
86
|
+
# Critically: nothing pending. Shared-utility expansion must not
|
|
87
|
+
# leak into `_pending_fetches`.
|
|
88
|
+
self.assertEqual(pending["FLOW"], set())
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class TrueCycleDetectionTests(unittest.TestCase):
|
|
92
|
+
"""Per-branch path-set must still catch genuine cycles."""
|
|
93
|
+
|
|
94
|
+
def test_true_cycle_detected(self):
|
|
95
|
+
"""A -> B -> A: second A is annotated, not recursed into, and
|
|
96
|
+
not surfaced in `_pending_fetches`."""
|
|
97
|
+
flow_children = {
|
|
98
|
+
"A": [{"kind": "FLOW", "api_name": "B", "element_name": "call_B"}],
|
|
99
|
+
"B": [{"kind": "FLOW", "api_name": "A", "element_name": "call_A"}],
|
|
100
|
+
}
|
|
101
|
+
root = {"kind": "FLOW", "api_name": "A", "children": []}
|
|
102
|
+
pending = parse_wave.empty_kind_sets()
|
|
103
|
+
|
|
104
|
+
parse_wave.inflate_flow_leaf(root, flow_children, pending_out=pending)
|
|
105
|
+
|
|
106
|
+
# A -> B -> A(cycle)
|
|
107
|
+
b = root["children"][0]
|
|
108
|
+
self.assertEqual(b["api_name"], "B")
|
|
109
|
+
a_cycle = b["children"][0]
|
|
110
|
+
self.assertEqual(a_cycle["api_name"], "A")
|
|
111
|
+
self.assertEqual(
|
|
112
|
+
a_cycle["_truncated"],
|
|
113
|
+
{"reason": "cycle", "target": "FLOW:A"},
|
|
114
|
+
)
|
|
115
|
+
self.assertEqual(a_cycle["_cycle_back_to"], "FLOW:A")
|
|
116
|
+
|
|
117
|
+
# Cycles live in the tree, not the pending accumulator.
|
|
118
|
+
self.assertEqual(pending["FLOW"], set())
|
|
119
|
+
|
|
120
|
+
def test_self_recursive_flow(self):
|
|
121
|
+
"""A -> A: direct self-cycle is annotated on first recurrence."""
|
|
122
|
+
flow_children = {
|
|
123
|
+
"A": [{"kind": "FLOW", "api_name": "A", "element_name": "self_call"}],
|
|
124
|
+
}
|
|
125
|
+
root = {"kind": "FLOW", "api_name": "A", "children": []}
|
|
126
|
+
pending = parse_wave.empty_kind_sets()
|
|
127
|
+
|
|
128
|
+
parse_wave.inflate_flow_leaf(root, flow_children, pending_out=pending)
|
|
129
|
+
|
|
130
|
+
kids = root.get("children", [])
|
|
131
|
+
self.assertEqual(len(kids), 1)
|
|
132
|
+
self.assertEqual(kids[0]["api_name"], "A")
|
|
133
|
+
self.assertEqual(
|
|
134
|
+
kids[0]["_truncated"],
|
|
135
|
+
{"reason": "cycle", "target": "FLOW:A"},
|
|
136
|
+
)
|
|
137
|
+
self.assertEqual(pending["FLOW"], set())
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class DeepNonCyclicChainTests(unittest.TestCase):
|
|
141
|
+
"""Linear chains longer than the old `MAX_BFS_DEPTH = 5` must
|
|
142
|
+
expand fully under the revised semantics. Previously they tripped
|
|
143
|
+
`PARTIAL_REASON=max-depth-cap`; now they expand cleanly.
|
|
144
|
+
"""
|
|
145
|
+
|
|
146
|
+
def test_deep_non_cyclic_chain_fully_expands(self):
|
|
147
|
+
# A -> B -> C -> D -> E -> F -> G (7 unique flows, depth 6
|
|
148
|
+
# from the root). Old cap of 5 would have truncated at F;
|
|
149
|
+
# new cap of 20 doesn't care.
|
|
150
|
+
names = list("ABCDEFG")
|
|
151
|
+
flow_children: dict = {}
|
|
152
|
+
for i, n in enumerate(names):
|
|
153
|
+
if i + 1 < len(names):
|
|
154
|
+
flow_children[n] = [{
|
|
155
|
+
"kind": "FLOW",
|
|
156
|
+
"api_name": names[i + 1],
|
|
157
|
+
"element_name": f"call_{names[i + 1]}",
|
|
158
|
+
}]
|
|
159
|
+
else:
|
|
160
|
+
flow_children[n] = []
|
|
161
|
+
|
|
162
|
+
root = {"kind": "FLOW", "api_name": "A", "children": []}
|
|
163
|
+
pending = parse_wave.empty_kind_sets()
|
|
164
|
+
parse_wave.inflate_flow_leaf(root, flow_children, pending_out=pending)
|
|
165
|
+
|
|
166
|
+
node = root
|
|
167
|
+
for expected in names[1:]:
|
|
168
|
+
kids = node.get("children", [])
|
|
169
|
+
self.assertEqual(
|
|
170
|
+
len(kids), 1,
|
|
171
|
+
f"{node['api_name']} should have expanded child {expected}",
|
|
172
|
+
)
|
|
173
|
+
self.assertEqual(kids[0]["api_name"], expected)
|
|
174
|
+
self.assertNotIn("_truncated", kids[0])
|
|
175
|
+
node = kids[0]
|
|
176
|
+
|
|
177
|
+
# Terminal leaf has no children and carries no truncation
|
|
178
|
+
# annotation — the cap was never relevant.
|
|
179
|
+
self.assertEqual(node.get("children", []), [])
|
|
180
|
+
self.assertNotIn("_truncated", node)
|
|
181
|
+
self.assertEqual(pending["FLOW"], set())
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class DefensiveCapStillTerminatesTests(unittest.TestCase):
|
|
185
|
+
"""Pathological chain longer than the defensive cap must still
|
|
186
|
+
terminate cleanly. This proves the safety net still works even
|
|
187
|
+
though per-branch cycle detection does the real work in practice.
|
|
188
|
+
"""
|
|
189
|
+
|
|
190
|
+
def test_defensive_cap_still_terminates(self):
|
|
191
|
+
# 25 unique flows, linear — longer than the defensive cap of 20.
|
|
192
|
+
# Either the tree fully expands (if someone lifts the cap
|
|
193
|
+
# further) OR it caps at MAX_BFS_DEPTH and annotates the
|
|
194
|
+
# unreached flow. The test asserts termination and one of
|
|
195
|
+
# those two well-defined shapes.
|
|
196
|
+
n = 25
|
|
197
|
+
names = [f"F{i:02d}" for i in range(n)]
|
|
198
|
+
flow_children: dict = {}
|
|
199
|
+
for i, name in enumerate(names):
|
|
200
|
+
if i + 1 < n:
|
|
201
|
+
flow_children[name] = [{
|
|
202
|
+
"kind": "FLOW",
|
|
203
|
+
"api_name": names[i + 1],
|
|
204
|
+
"element_name": "sub",
|
|
205
|
+
}]
|
|
206
|
+
else:
|
|
207
|
+
flow_children[name] = []
|
|
208
|
+
|
|
209
|
+
root = {"kind": "FLOW", "api_name": names[0], "children": []}
|
|
210
|
+
pending = parse_wave.empty_kind_sets()
|
|
211
|
+
|
|
212
|
+
# Primary assertion: this call terminates and does not recurse
|
|
213
|
+
# past Python's default recursion limit. If the defensive cap
|
|
214
|
+
# regresses, this test will hang or raise RecursionError.
|
|
215
|
+
parse_wave.inflate_flow_leaf(root, flow_children, pending_out=pending)
|
|
216
|
+
|
|
217
|
+
# Collect every flow that appears in the tree.
|
|
218
|
+
def all_api_names(node: dict) -> set[str]:
|
|
219
|
+
out = {node.get("api_name")}
|
|
220
|
+
for c in node.get("children", []) or []:
|
|
221
|
+
out.update(all_api_names(c))
|
|
222
|
+
return out - {None}
|
|
223
|
+
|
|
224
|
+
present = all_api_names(root)
|
|
225
|
+
|
|
226
|
+
# At least the first MAX_BFS_DEPTH flows must be present. The
|
|
227
|
+
# tail may or may not appear depending on where the cap trips.
|
|
228
|
+
for i in range(parse_wave.MAX_BFS_DEPTH):
|
|
229
|
+
self.assertIn(
|
|
230
|
+
names[i], present,
|
|
231
|
+
f"{names[i]} (index {i}) should be expanded — "
|
|
232
|
+
f"it's below MAX_BFS_DEPTH={parse_wave.MAX_BFS_DEPTH}",
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
# If any flow is pending, it must be a real descendant (not a
|
|
236
|
+
# fabricated name) and must carry the max-depth annotation
|
|
237
|
+
# somewhere in the tree.
|
|
238
|
+
if pending["FLOW"]:
|
|
239
|
+
for pending_name in pending["FLOW"]:
|
|
240
|
+
self.assertIn(pending_name, names)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
if __name__ == "__main__":
|
|
244
|
+
unittest.main()
|