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