@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,480 @@
|
|
|
1
|
+
"""Integration tests for (REMEDIATE): tree → write_emit_ctx → emit_result.
|
|
2
|
+
|
|
3
|
+
The defect: parse_wave.py writes `_partial_reason` and `_pending_fetches`
|
|
4
|
+
into the tree on disk. emit_result.py reads `ctx["partial_reason"]` and
|
|
5
|
+
`ctx["pending_fetches_count"]` from the emit ctx. But write_emit_ctx.py
|
|
6
|
+
NEVER populated those ctx keys from the tree — they silently defaulted to
|
|
7
|
+
empty / 0 on every run. Depth-cap truncation was invisible downstream.
|
|
8
|
+
|
|
9
|
+
These tests exercise both halves of the wiring:
|
|
10
|
+
|
|
11
|
+
test_write_emit_ctx_reads_tree_partial_signals
|
|
12
|
+
fake tree JSON → write_emit_ctx → assert ctx keys populated.
|
|
13
|
+
|
|
14
|
+
test_end_to_end_tree_surfaces_partial_reason_in_result_block
|
|
15
|
+
tree → write_emit_ctx → emit_result → assert RESULT block carries
|
|
16
|
+
PARTIAL_REASON + PENDING_FETCHES_COUNT. End-to-end — this is the
|
|
17
|
+
test both reviewers called for.
|
|
18
|
+
|
|
19
|
+
Missing-tree behavior is also covered so early-abort paths still work.
|
|
20
|
+
"""
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import json
|
|
24
|
+
import os
|
|
25
|
+
import pathlib
|
|
26
|
+
import subprocess
|
|
27
|
+
import sys
|
|
28
|
+
import tempfile
|
|
29
|
+
import unittest
|
|
30
|
+
|
|
31
|
+
from . import _bootstrap # noqa: F401
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _tools_dir() -> pathlib.Path:
|
|
35
|
+
"""Absolute path to the skill's tools/ dir — where the scripts live."""
|
|
36
|
+
here = pathlib.Path(__file__).resolve()
|
|
37
|
+
return here.parent.parent.parent / "tools"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _run_script(script_name: str, env: dict) -> subprocess.CompletedProcess:
|
|
41
|
+
"""Invoke `python3 <tools>/<script>.py` with the given env.
|
|
42
|
+
|
|
43
|
+
We deliberately run the scripts as subprocesses rather than importing
|
|
44
|
+
them: they're shipped as stand-alone CLIs (the agent bash invokes them
|
|
45
|
+
via `python3 write_emit_ctx.py`) and `main()` reads env at call time +
|
|
46
|
+
returns an exit code. Subprocess gives us the exact production call
|
|
47
|
+
path without having to monkey-patch os.environ.
|
|
48
|
+
"""
|
|
49
|
+
script_path = _tools_dir() / script_name
|
|
50
|
+
return subprocess.run(
|
|
51
|
+
[sys.executable, str(script_path)],
|
|
52
|
+
env=env,
|
|
53
|
+
capture_output=True,
|
|
54
|
+
text=True,
|
|
55
|
+
timeout=30,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class WriteEmitCtxTreeSignalsTests(unittest.TestCase):
|
|
60
|
+
"""write_emit_ctx must plumb `_partial_reason` + rollup from tree."""
|
|
61
|
+
|
|
62
|
+
def setUp(self) -> None:
|
|
63
|
+
self._tmp = tempfile.TemporaryDirectory()
|
|
64
|
+
self.addCleanup(self._tmp.cleanup)
|
|
65
|
+
self.work_dir = pathlib.Path(self._tmp.name) / "work"
|
|
66
|
+
self.data_dir = pathlib.Path(self._tmp.name) / "data"
|
|
67
|
+
self.work_dir.mkdir()
|
|
68
|
+
self.data_dir.mkdir()
|
|
69
|
+
|
|
70
|
+
def _base_env(self, status: str = "OK") -> dict:
|
|
71
|
+
return {
|
|
72
|
+
**os.environ,
|
|
73
|
+
"WORK_DIR": str(self.work_dir),
|
|
74
|
+
"DATA_DIR": str(self.data_dir),
|
|
75
|
+
"STATUS": status,
|
|
76
|
+
"AGENT_API_NAME": "MyBot",
|
|
77
|
+
"AGENT_VERSION": "v1",
|
|
78
|
+
"ORG_ID_15": "00D000000000ABC",
|
|
79
|
+
"BOT_ID": "0XxFAKEBOTID",
|
|
80
|
+
"NODE_COUNT": "42",
|
|
81
|
+
"DEPTH": "3",
|
|
82
|
+
"PARTIAL": "true",
|
|
83
|
+
"START_EPOCH": "0",
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
def _write_tree(self, payload: dict) -> None:
|
|
87
|
+
(self.work_dir / "declared_action_tree.json").write_text(
|
|
88
|
+
json.dumps(payload)
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
def _read_ctx(self) -> dict:
|
|
92
|
+
return json.loads((self.work_dir / ".emit_ctx.json").read_text())
|
|
93
|
+
|
|
94
|
+
def test_write_emit_ctx_reads_tree_partial_signals(self):
|
|
95
|
+
"""Fake tree → ctx carries partial_reason + pending_fetches_count."""
|
|
96
|
+
self._write_tree({
|
|
97
|
+
"_partial": True,
|
|
98
|
+
"_partial_reason": "max-depth-cap",
|
|
99
|
+
"_pending_fetches": {
|
|
100
|
+
"FLOW": ["F6"],
|
|
101
|
+
"APEX": [],
|
|
102
|
+
"PROMPT_TEMPLATE": [],
|
|
103
|
+
"STANDARD_ACTION": [],
|
|
104
|
+
},
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
result = _run_script("write_emit_ctx.py", self._base_env())
|
|
108
|
+
self.assertEqual(result.returncode, 0, msg=result.stderr)
|
|
109
|
+
|
|
110
|
+
ctx = self._read_ctx()
|
|
111
|
+
self.assertEqual(ctx["partial_reason"], "max-depth-cap")
|
|
112
|
+
self.assertEqual(ctx["pending_fetches_count"], 1)
|
|
113
|
+
|
|
114
|
+
def test_write_emit_ctx_rollup_sums_across_kinds(self):
|
|
115
|
+
self._write_tree({
|
|
116
|
+
"_partial": True,
|
|
117
|
+
"_partial_reason": "pending-refs",
|
|
118
|
+
"_pending_fetches": {
|
|
119
|
+
"FLOW": ["F1", "F2"],
|
|
120
|
+
"APEX": ["A1"],
|
|
121
|
+
"PROMPT_TEMPLATE": [],
|
|
122
|
+
"STANDARD_ACTION": ["S1", "S2", "S3"],
|
|
123
|
+
},
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
result = _run_script("write_emit_ctx.py", self._base_env())
|
|
127
|
+
self.assertEqual(result.returncode, 0, msg=result.stderr)
|
|
128
|
+
|
|
129
|
+
ctx = self._read_ctx()
|
|
130
|
+
self.assertEqual(ctx["partial_reason"], "pending-refs")
|
|
131
|
+
self.assertEqual(ctx["pending_fetches_count"], 6)
|
|
132
|
+
|
|
133
|
+
def test_write_emit_ctx_missing_tree_defaults_safely(self):
|
|
134
|
+
"""Early-abort paths never write a tree — ctx must default cleanly."""
|
|
135
|
+
# No tree file written.
|
|
136
|
+
result = _run_script(
|
|
137
|
+
"write_emit_ctx.py",
|
|
138
|
+
self._base_env(status="AGENT_NOT_FOUND"),
|
|
139
|
+
)
|
|
140
|
+
self.assertEqual(result.returncode, 0, msg=result.stderr)
|
|
141
|
+
ctx = self._read_ctx()
|
|
142
|
+
self.assertEqual(ctx["partial_reason"], "")
|
|
143
|
+
self.assertEqual(ctx["pending_fetches_count"], 0)
|
|
144
|
+
|
|
145
|
+
def test_write_emit_ctx_malformed_tree_defaults_safely(self):
|
|
146
|
+
(self.work_dir / "declared_action_tree.json").write_text("{ not json")
|
|
147
|
+
result = _run_script("write_emit_ctx.py", self._base_env())
|
|
148
|
+
self.assertEqual(result.returncode, 0, msg=result.stderr)
|
|
149
|
+
ctx = self._read_ctx()
|
|
150
|
+
self.assertEqual(ctx["partial_reason"], "")
|
|
151
|
+
self.assertEqual(ctx["pending_fetches_count"], 0)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class TreeToResultBlockEndToEndTests(unittest.TestCase):
|
|
155
|
+
"""end-to-end: tree → write_emit_ctx → emit_result → RESULT block.
|
|
156
|
+
|
|
157
|
+
This is the test both reviewers called for — it would have caught the
|
|
158
|
+
original defect. A fix that only makes write_emit_ctx plumb the
|
|
159
|
+
fields but doesn't verify they land in the RESULT block is not a fix.
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
def setUp(self) -> None:
|
|
163
|
+
self._tmp = tempfile.TemporaryDirectory()
|
|
164
|
+
self.addCleanup(self._tmp.cleanup)
|
|
165
|
+
self.work_dir = pathlib.Path(self._tmp.name) / "work"
|
|
166
|
+
self.data_dir = pathlib.Path(self._tmp.name) / "data"
|
|
167
|
+
self.work_dir.mkdir()
|
|
168
|
+
self.data_dir.mkdir()
|
|
169
|
+
|
|
170
|
+
def test_end_to_end_tree_surfaces_partial_reason_in_result_block(self):
|
|
171
|
+
# 1. Tree on disk — depth-cap tripped, one pending flow.
|
|
172
|
+
(self.work_dir / "declared_action_tree.json").write_text(json.dumps({
|
|
173
|
+
"_partial": True,
|
|
174
|
+
"_partial_reason": "max-depth-cap",
|
|
175
|
+
"_pending_fetches": {
|
|
176
|
+
"FLOW": ["F6"],
|
|
177
|
+
"APEX": [],
|
|
178
|
+
"PROMPT_TEMPLATE": [],
|
|
179
|
+
"STANDARD_ACTION": [],
|
|
180
|
+
},
|
|
181
|
+
}))
|
|
182
|
+
|
|
183
|
+
# 2. write_emit_ctx — populates .emit_ctx.json from env + tree.
|
|
184
|
+
env = {
|
|
185
|
+
**os.environ,
|
|
186
|
+
"WORK_DIR": str(self.work_dir),
|
|
187
|
+
"DATA_DIR": str(self.data_dir),
|
|
188
|
+
"STATUS": "OK",
|
|
189
|
+
"AGENT_API_NAME": "MyBot",
|
|
190
|
+
"AGENT_VERSION": "v1",
|
|
191
|
+
"ORG_ID_15": "00D000000000ABC",
|
|
192
|
+
"BOT_ID": "0XxFAKEBOTID",
|
|
193
|
+
"NODE_COUNT": "5",
|
|
194
|
+
"DEPTH": "5",
|
|
195
|
+
"PARTIAL": "true",
|
|
196
|
+
"START_EPOCH": "0",
|
|
197
|
+
}
|
|
198
|
+
r1 = _run_script("write_emit_ctx.py", env)
|
|
199
|
+
self.assertEqual(r1.returncode, 0, msg=r1.stderr)
|
|
200
|
+
|
|
201
|
+
# 3. emit_result — reads .emit_ctx.json, prints RESULT block.
|
|
202
|
+
r2 = _run_script("emit_result.py", env)
|
|
203
|
+
self.assertEqual(r2.returncode, 0, msg=r2.stderr)
|
|
204
|
+
|
|
205
|
+
# 4. Assert the RESULT block carries the plumbed fields.
|
|
206
|
+
block = r2.stdout
|
|
207
|
+
self.assertIn("PARTIAL_REASON=max-depth-cap", block)
|
|
208
|
+
self.assertIn("PENDING_FETCHES_COUNT=1", block)
|
|
209
|
+
# Auto-promote: partial=True + status=OK → PARTIAL_OK.
|
|
210
|
+
self.assertIn("STATUS=PARTIAL_OK", block)
|
|
211
|
+
self.assertNotIn("STATUS=OK\n", block) # not the base OK status
|
|
212
|
+
|
|
213
|
+
def test_end_to_end_no_tree_produces_empty_partial_fields(self):
|
|
214
|
+
"""Early-abort path (no tree) → RESULT block carries empties, not crashes."""
|
|
215
|
+
env = {
|
|
216
|
+
**os.environ,
|
|
217
|
+
"WORK_DIR": str(self.work_dir),
|
|
218
|
+
"DATA_DIR": str(self.data_dir),
|
|
219
|
+
"STATUS": "AGENT_NOT_FOUND",
|
|
220
|
+
"AGENT_API_NAME": "Missing",
|
|
221
|
+
"AGENT_VERSION": "",
|
|
222
|
+
"ORG_ID_15": "00D000000000ABC",
|
|
223
|
+
"BOT_ID": "",
|
|
224
|
+
"START_EPOCH": "0",
|
|
225
|
+
}
|
|
226
|
+
r1 = _run_script("write_emit_ctx.py", env)
|
|
227
|
+
self.assertEqual(r1.returncode, 0, msg=r1.stderr)
|
|
228
|
+
r2 = _run_script("emit_result.py", env)
|
|
229
|
+
self.assertEqual(r2.returncode, 0, msg=r2.stderr)
|
|
230
|
+
|
|
231
|
+
block = r2.stdout
|
|
232
|
+
self.assertIn("STATUS=AGENT_NOT_FOUND", block)
|
|
233
|
+
self.assertIn("PARTIAL_REASON=", block)
|
|
234
|
+
self.assertIn("PENDING_FETCHES_COUNT=0", block)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
class WriteEmitCtxArchitectureSignalsTests(unittest.TestCase):
|
|
238
|
+
"""write_emit_ctx must plumb architecture-render signals.
|
|
239
|
+
|
|
240
|
+
Cases:
|
|
241
|
+
* Render succeeded → architecture.md present, no sidecar →
|
|
242
|
+
ctx.architecture_path set, render_failed=False, detail="".
|
|
243
|
+
* Render failed → sidecar present → render_failed=True,
|
|
244
|
+
detail populated (truncated + redacted).
|
|
245
|
+
* Render not attempted → no file, no sidecar → empties,
|
|
246
|
+
render_failed=False. This is the early-abort / cache-hit path.
|
|
247
|
+
"""
|
|
248
|
+
|
|
249
|
+
def setUp(self) -> None:
|
|
250
|
+
self._tmp = tempfile.TemporaryDirectory()
|
|
251
|
+
self.addCleanup(self._tmp.cleanup)
|
|
252
|
+
self.work_dir = pathlib.Path(self._tmp.name) / "work"
|
|
253
|
+
self.data_dir = pathlib.Path(self._tmp.name) / "data"
|
|
254
|
+
self.work_dir.mkdir()
|
|
255
|
+
self.data_dir.mkdir()
|
|
256
|
+
|
|
257
|
+
def _base_env(self, status: str = "OK") -> dict:
|
|
258
|
+
return {
|
|
259
|
+
**os.environ,
|
|
260
|
+
"WORK_DIR": str(self.work_dir),
|
|
261
|
+
"DATA_DIR": str(self.data_dir),
|
|
262
|
+
"STATUS": status,
|
|
263
|
+
"AGENT_API_NAME": "MyBot",
|
|
264
|
+
"AGENT_VERSION": "v1",
|
|
265
|
+
"ORG_ID_15": "00D000000000ABC",
|
|
266
|
+
"BOT_ID": "0XxFAKEBOTID",
|
|
267
|
+
"START_EPOCH": "0",
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
def _read_ctx(self) -> dict:
|
|
271
|
+
return json.loads((self.work_dir / ".emit_ctx.json").read_text())
|
|
272
|
+
|
|
273
|
+
def test_render_success_surfaces_architecture_path(self):
|
|
274
|
+
# filename is {agent}_{ver}_architecture.md.
|
|
275
|
+
(self.data_dir / "MyBot_v1_architecture.md").write_text("# Architecture\n")
|
|
276
|
+
result = _run_script("write_emit_ctx.py", self._base_env())
|
|
277
|
+
self.assertEqual(result.returncode, 0, msg=result.stderr)
|
|
278
|
+
ctx = self._read_ctx()
|
|
279
|
+
self.assertEqual(
|
|
280
|
+
ctx["architecture_path"],
|
|
281
|
+
str(self.data_dir / "MyBot_v1_architecture.md"),
|
|
282
|
+
)
|
|
283
|
+
self.assertFalse(ctx["render_failed"])
|
|
284
|
+
self.assertEqual(ctx["render_error_detail"], "")
|
|
285
|
+
|
|
286
|
+
def test_render_failure_surfaces_sidecar_detail(self):
|
|
287
|
+
(self.data_dir / "MyBot_v1_architecture.md.error").write_text(
|
|
288
|
+
"render_architecture failed: KeyError: 'agent'\n"
|
|
289
|
+
)
|
|
290
|
+
result = _run_script("write_emit_ctx.py", self._base_env())
|
|
291
|
+
self.assertEqual(result.returncode, 0, msg=result.stderr)
|
|
292
|
+
ctx = self._read_ctx()
|
|
293
|
+
self.assertTrue(ctx["render_failed"])
|
|
294
|
+
self.assertIn("KeyError", ctx["render_error_detail"])
|
|
295
|
+
# Sidecar's truncation + redaction happened — detail shouldn't
|
|
296
|
+
# exceed 200 chars even for a pathological sidecar.
|
|
297
|
+
self.assertLessEqual(len(ctx["render_error_detail"]), 200)
|
|
298
|
+
# No architecture.md present → architecture_path stays empty.
|
|
299
|
+
self.assertEqual(ctx["architecture_path"], "")
|
|
300
|
+
|
|
301
|
+
def test_render_failure_detail_is_redacted(self):
|
|
302
|
+
# Sidecar content mimics an exception that echoed back an
|
|
303
|
+
# Authorization header — the redactor must scrub it.
|
|
304
|
+
(self.data_dir / "MyBot_v1_architecture.md.error").write_text(
|
|
305
|
+
"render_architecture failed: RequestError: Authorization: Bearer TESTONLY_RENDER_TOKEN xyz\n"
|
|
306
|
+
)
|
|
307
|
+
result = _run_script("write_emit_ctx.py", self._base_env())
|
|
308
|
+
self.assertEqual(result.returncode, 0, msg=result.stderr)
|
|
309
|
+
ctx = self._read_ctx()
|
|
310
|
+
self.assertTrue(ctx["render_failed"])
|
|
311
|
+
self.assertNotIn("TESTONLY_RENDER_TOKEN", ctx["render_error_detail"])
|
|
312
|
+
self.assertIn("<redacted>", ctx["render_error_detail"])
|
|
313
|
+
|
|
314
|
+
def test_render_not_attempted_keeps_fields_empty(self):
|
|
315
|
+
# No architecture.md, no sidecar — e.g. early-abort paths.
|
|
316
|
+
result = _run_script(
|
|
317
|
+
"write_emit_ctx.py",
|
|
318
|
+
self._base_env(status="AGENT_NOT_FOUND"),
|
|
319
|
+
)
|
|
320
|
+
self.assertEqual(result.returncode, 0, msg=result.stderr)
|
|
321
|
+
ctx = self._read_ctx()
|
|
322
|
+
self.assertEqual(ctx["architecture_path"], "")
|
|
323
|
+
self.assertFalse(ctx["render_failed"])
|
|
324
|
+
self.assertEqual(ctx["render_error_detail"], "")
|
|
325
|
+
|
|
326
|
+
def test_render_success_and_failure_concurrent_surfaces_both(self):
|
|
327
|
+
# Edge case: architecture.md present + sidecar present. This
|
|
328
|
+
# shouldn't happen in practice (finalize writes one or the
|
|
329
|
+
# other), but the reader shouldn't mutate behaviour based on
|
|
330
|
+
# cross-key state — path present + render_failed=True.
|
|
331
|
+
(self.data_dir / "MyBot_v1_architecture.md").write_text("# Architecture\n")
|
|
332
|
+
(self.data_dir / "MyBot_v1_architecture.md.error").write_text(
|
|
333
|
+
"render_architecture failed: RuntimeError: partial output\n"
|
|
334
|
+
)
|
|
335
|
+
result = _run_script("write_emit_ctx.py", self._base_env())
|
|
336
|
+
self.assertEqual(result.returncode, 0, msg=result.stderr)
|
|
337
|
+
ctx = self._read_ctx()
|
|
338
|
+
self.assertTrue(ctx["render_failed"])
|
|
339
|
+
self.assertEqual(
|
|
340
|
+
ctx["architecture_path"],
|
|
341
|
+
str(self.data_dir / "MyBot_v1_architecture.md"),
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
class EmitResultArchitectureSignalsTests(unittest.TestCase):
|
|
346
|
+
"""emit_result surfaces architecture + render-failure fields.
|
|
347
|
+
|
|
348
|
+
The integration tests in TreeToResultBlockEndToEndTests cover the
|
|
349
|
+
write_emit_ctx -> emit_result handoff; these tests exercise
|
|
350
|
+
emit_result's block-shaping logic in isolation via `build_block`.
|
|
351
|
+
"""
|
|
352
|
+
|
|
353
|
+
def _import_mod(self):
|
|
354
|
+
import importlib.util
|
|
355
|
+
spec = importlib.util.spec_from_file_location(
|
|
356
|
+
"emit_result_mod", _tools_dir() / "emit_result.py",
|
|
357
|
+
)
|
|
358
|
+
assert spec is not None and spec.loader is not None
|
|
359
|
+
mod = importlib.util.module_from_spec(spec)
|
|
360
|
+
spec.loader.exec_module(mod)
|
|
361
|
+
return mod
|
|
362
|
+
|
|
363
|
+
def test_render_ok_emits_architecture_path_and_render_failed_false(self):
|
|
364
|
+
mod = self._import_mod()
|
|
365
|
+
ctx = {
|
|
366
|
+
"status": "OK",
|
|
367
|
+
"architecture_path": "/tmp/data/architecture.md",
|
|
368
|
+
"render_failed": False,
|
|
369
|
+
}
|
|
370
|
+
block = mod.build_block(ctx, wall_time_seconds=0.5)
|
|
371
|
+
self.assertIn("OUTPUT_ARCHITECTURE_PATH=/tmp/data/architecture.md", block)
|
|
372
|
+
self.assertIn("RENDER_FAILED=false", block)
|
|
373
|
+
# Detail key is ONLY emitted on failure — must NOT appear here.
|
|
374
|
+
self.assertNotIn("RENDER_ERROR_DETAIL=", block)
|
|
375
|
+
# Status stays OK.
|
|
376
|
+
self.assertIn("STATUS=OK", block)
|
|
377
|
+
self.assertNotIn("STATUS=PARTIAL_OK", block)
|
|
378
|
+
|
|
379
|
+
def test_render_failed_auto_promotes_to_partial_ok(self):
|
|
380
|
+
mod = self._import_mod()
|
|
381
|
+
ctx = {
|
|
382
|
+
"status": "OK",
|
|
383
|
+
"architecture_path": "", # render failed, no file produced
|
|
384
|
+
"render_failed": True,
|
|
385
|
+
"render_error_detail": "KeyError: 'agent'",
|
|
386
|
+
}
|
|
387
|
+
block = mod.build_block(ctx, wall_time_seconds=0.5)
|
|
388
|
+
self.assertIn("STATUS=PARTIAL_OK", block)
|
|
389
|
+
self.assertIn("RENDER_FAILED=true", block)
|
|
390
|
+
self.assertIn("RENDER_ERROR_DETAIL=KeyError: 'agent'", block)
|
|
391
|
+
# PARTIAL_REASON pinned to render-failed when the tree didn't
|
|
392
|
+
# already claim a reason.
|
|
393
|
+
self.assertIn("PARTIAL_REASON=render-failed", block)
|
|
394
|
+
# OUTPUT_ARCHITECTURE_PATH always emitted, empty on failure.
|
|
395
|
+
self.assertIn("OUTPUT_ARCHITECTURE_PATH=", block)
|
|
396
|
+
|
|
397
|
+
def test_render_failed_preserves_tree_partial_reason(self):
|
|
398
|
+
# When the tree is ALREADY partial with a reason, render_failed
|
|
399
|
+
# must NOT clobber the tree's reason — the tree's signal is more
|
|
400
|
+
# informative for triage.
|
|
401
|
+
mod = self._import_mod()
|
|
402
|
+
ctx = {
|
|
403
|
+
"status": "OK",
|
|
404
|
+
"partial": True,
|
|
405
|
+
"partial_reason": "max-depth-cap",
|
|
406
|
+
"render_failed": True,
|
|
407
|
+
"render_error_detail": "RuntimeError: ...",
|
|
408
|
+
}
|
|
409
|
+
block = mod.build_block(ctx, wall_time_seconds=0.5)
|
|
410
|
+
self.assertIn("PARTIAL_REASON=max-depth-cap", block)
|
|
411
|
+
self.assertNotIn("PARTIAL_REASON=render-failed", block)
|
|
412
|
+
# Still surfaces as PARTIAL_OK (either the tree or the render
|
|
413
|
+
# flag is sufficient to trip the auto-promote).
|
|
414
|
+
self.assertIn("STATUS=PARTIAL_OK", block)
|
|
415
|
+
self.assertIn("RENDER_FAILED=true", block)
|
|
416
|
+
|
|
417
|
+
def test_render_failed_does_not_clobber_error_status(self):
|
|
418
|
+
mod = self._import_mod()
|
|
419
|
+
ctx = {
|
|
420
|
+
"status": "AUTH_REQUIRED",
|
|
421
|
+
"render_failed": True,
|
|
422
|
+
"render_error_detail": "ignored under error status",
|
|
423
|
+
}
|
|
424
|
+
block = mod.build_block(ctx, wall_time_seconds=0.1)
|
|
425
|
+
self.assertIn("STATUS=AUTH_REQUIRED", block)
|
|
426
|
+
self.assertNotIn("STATUS=PARTIAL_OK", block)
|
|
427
|
+
# The render fields still surface for diagnostics.
|
|
428
|
+
self.assertIn("RENDER_FAILED=true", block)
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
class EmitResultPositionalSafetyTests(unittest.TestCase):
|
|
432
|
+
"""emit_result must assert lines[1] starts with 'STATUS=' before rewrite.
|
|
433
|
+
|
|
434
|
+
The auto-promote branch (PARTIAL → PARTIAL_OK) rewrites `lines[1]`
|
|
435
|
+
in place. If a future refactor reorders the header, the rewrite
|
|
436
|
+
would clobber the wrong line. The assertion catches that at test
|
|
437
|
+
time instead of silently producing a malformed RESULT block in prod.
|
|
438
|
+
"""
|
|
439
|
+
|
|
440
|
+
def test_build_block_happy_path_auto_promotes_without_asserting(self):
|
|
441
|
+
"""Sanity: the assertion does NOT fire under normal operation."""
|
|
442
|
+
# Force import of emit_result from the tools/ dir.
|
|
443
|
+
import importlib.util
|
|
444
|
+
spec = importlib.util.spec_from_file_location(
|
|
445
|
+
"emit_result_mod", _tools_dir() / "emit_result.py",
|
|
446
|
+
)
|
|
447
|
+
assert spec is not None and spec.loader is not None
|
|
448
|
+
mod = importlib.util.module_from_spec(spec)
|
|
449
|
+
spec.loader.exec_module(mod)
|
|
450
|
+
|
|
451
|
+
ctx = {
|
|
452
|
+
"status": "OK",
|
|
453
|
+
"partial": True,
|
|
454
|
+
"partial_reason": "max-depth-cap",
|
|
455
|
+
"pending_fetches_count": 3,
|
|
456
|
+
"node_count": 10,
|
|
457
|
+
"depth": 5,
|
|
458
|
+
}
|
|
459
|
+
block = mod.build_block(ctx, wall_time_seconds=1.23)
|
|
460
|
+
self.assertIn("STATUS=PARTIAL_OK", block)
|
|
461
|
+
self.assertIn("PARTIAL_REASON=max-depth-cap", block)
|
|
462
|
+
self.assertIn("PENDING_FETCHES_COUNT=3", block)
|
|
463
|
+
|
|
464
|
+
def test_positional_assertion_fires_when_header_reordered(self):
|
|
465
|
+
"""Simulate a refactor that puts something other than STATUS at lines[1].
|
|
466
|
+
|
|
467
|
+
We construct a fake `lines` list directly and run the same
|
|
468
|
+
assertion the production code runs. This is the exact guarantee
|
|
469
|
+
adds — the production assert catches the drift.
|
|
470
|
+
"""
|
|
471
|
+
# This mirrors the production code's assertion verbatim.
|
|
472
|
+
lines = ["=== RESULT ===", "WAS_MOVED=true", "STATUS=OK"]
|
|
473
|
+
with self.assertRaises(AssertionError):
|
|
474
|
+
assert lines[1].startswith("STATUS="), (
|
|
475
|
+
f"emit block reordered — lines[1]={lines[1]!r}"
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
if __name__ == "__main__":
|
|
480
|
+
unittest.main()
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Generic JSON-in → shell-export-out extractor.
|
|
3
|
+
|
|
4
|
+
Reads a JSON document (from stdin or a file), traverses it by dot-path specs,
|
|
5
|
+
and emits `export KEY=shlex.quote(VALUE)` lines on stdout. Callers consume via
|
|
6
|
+
`eval "$(python3 emit_env.py ...)"`.
|
|
7
|
+
|
|
8
|
+
Replaces four single-purpose parsers that previously existed as inline Python
|
|
9
|
+
heredocs in the agent:
|
|
10
|
+
* sf org display result parser
|
|
11
|
+
* Q1 row-count + SESSION_ACTIVE derivation
|
|
12
|
+
* _cache_meta.json reader (CACHED_AT_UTC, SESSION_ACTIVE_AT_CACHE)
|
|
13
|
+
* individual field extractors
|
|
14
|
+
|
|
15
|
+
Field-spec DSL:
|
|
16
|
+
EXPORT_KEY:path — plain dot-path traversal
|
|
17
|
+
EXPORT_KEY:path[:N] — string slice after traversal (first N chars)
|
|
18
|
+
EXPORT_KEY:path.length — length of array / string at path
|
|
19
|
+
EXPORT_KEY:path.empty — "true" if missing or empty string, else "false"
|
|
20
|
+
|
|
21
|
+
The DSL is deliberately small: three operators, no dot-inside-key escaping.
|
|
22
|
+
Any path component is a dictionary key OR an integer array index
|
|
23
|
+
(`data[0].ssot__Foo` works). If the path doesn't resolve, the emitted value
|
|
24
|
+
is the empty string — caller decides whether that's OK.
|
|
25
|
+
|
|
26
|
+
Usage:
|
|
27
|
+
# Parse `sf org display --json` output via stdin
|
|
28
|
+
eval "$(printf '%s' "$_SF_JSON" | python3 emit_env.py stdin \\
|
|
29
|
+
'ACCESS_TOKEN:result.accessToken' \\
|
|
30
|
+
'INSTANCE_URL:result.instanceUrl' \\
|
|
31
|
+
'ORG_ID_18:result.id' \\
|
|
32
|
+
'ORG_ID_15:result.id[:15]')"
|
|
33
|
+
|
|
34
|
+
# Q1 post-wave: derive SESSION_ACTIVE from row 0's end timestamp
|
|
35
|
+
eval "$(python3 emit_env.py "$WORK_DIR/_q1_body.json" \\
|
|
36
|
+
'SESSION_ACTIVE:data[0].ssot__EndTimestamp__c.empty')"
|
|
37
|
+
|
|
38
|
+
# Cache-hit meta read
|
|
39
|
+
eval "$(python3 emit_env.py "$WORK_DIR/$SID.dc.json" \\
|
|
40
|
+
'CACHED_AT_UTC:_cache_meta.cached_at_utc' \\
|
|
41
|
+
'SESSION_ACTIVE:_cache_meta.session_active_at_cache')"
|
|
42
|
+
|
|
43
|
+
Inputs:
|
|
44
|
+
argv[1] 'stdin' or an absolute file path
|
|
45
|
+
argv[2+] field specs (at least one)
|
|
46
|
+
|
|
47
|
+
Outputs:
|
|
48
|
+
stdout zero or more `export K=V` lines
|
|
49
|
+
exit 0 always (missing keys just produce empty values)
|
|
50
|
+
exit 1 bad argv, unparseable JSON, or I/O error
|
|
51
|
+
"""
|
|
52
|
+
import json
|
|
53
|
+
import pathlib
|
|
54
|
+
import re
|
|
55
|
+
import shlex
|
|
56
|
+
import sys
|
|
57
|
+
|
|
58
|
+
SLICE_RE = re.compile(r"^(.*?)\[:(\d+)\]$")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def traverse(data, path: str):
|
|
62
|
+
# Path components are dot-separated. Numeric components index into arrays;
|
|
63
|
+
# everything else is a dict key. Missing/invalid → None (emitted as empty).
|
|
64
|
+
node = data
|
|
65
|
+
for part in path.split("."):
|
|
66
|
+
if node is None:
|
|
67
|
+
return None
|
|
68
|
+
if part.isdigit():
|
|
69
|
+
try:
|
|
70
|
+
node = node[int(part)]
|
|
71
|
+
except (IndexError, KeyError, TypeError):
|
|
72
|
+
return None
|
|
73
|
+
elif isinstance(node, dict):
|
|
74
|
+
node = node.get(part)
|
|
75
|
+
else:
|
|
76
|
+
return None
|
|
77
|
+
return node
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def apply_spec(data, spec: str) -> tuple[str, str]:
|
|
81
|
+
# spec: "EXPORT_KEY:path<operator>"
|
|
82
|
+
if ":" not in spec:
|
|
83
|
+
raise ValueError(f"spec missing colon: {spec}")
|
|
84
|
+
key, rhs = spec.split(":", 1)
|
|
85
|
+
if not key:
|
|
86
|
+
raise ValueError(f"spec has empty key: {spec}")
|
|
87
|
+
|
|
88
|
+
# Resolve the operator suffix on rhs. Check slice LAST because it's the
|
|
89
|
+
# only one that doesn't alter the traversed value's type.
|
|
90
|
+
if rhs.endswith(".length"):
|
|
91
|
+
value = traverse(data, rhs[:-7])
|
|
92
|
+
if value is None:
|
|
93
|
+
return key, "0"
|
|
94
|
+
if isinstance(value, (list, str)):
|
|
95
|
+
return key, str(len(value))
|
|
96
|
+
return key, "0"
|
|
97
|
+
|
|
98
|
+
if rhs.endswith(".empty"):
|
|
99
|
+
value = traverse(data, rhs[:-6])
|
|
100
|
+
is_empty = value is None or value == ""
|
|
101
|
+
return key, "true" if is_empty else "false"
|
|
102
|
+
|
|
103
|
+
m = SLICE_RE.match(rhs)
|
|
104
|
+
if m:
|
|
105
|
+
path, n = m.group(1), int(m.group(2))
|
|
106
|
+
value = traverse(data, path)
|
|
107
|
+
if value is None:
|
|
108
|
+
return key, ""
|
|
109
|
+
return key, str(value)[:n]
|
|
110
|
+
|
|
111
|
+
# Plain traversal.
|
|
112
|
+
value = traverse(data, rhs)
|
|
113
|
+
if value is None:
|
|
114
|
+
return key, ""
|
|
115
|
+
if isinstance(value, bool):
|
|
116
|
+
# Python `True` / `False` → `true` / `false` so bash string compares
|
|
117
|
+
# work without an extra lowercase step.
|
|
118
|
+
return key, "true" if value else "false"
|
|
119
|
+
return key, str(value)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def main() -> int:
|
|
123
|
+
if len(sys.argv) < 3:
|
|
124
|
+
sys.stderr.write("emit_env.py: need source ('stdin' or path) and at least one field spec\n")
|
|
125
|
+
return 1
|
|
126
|
+
|
|
127
|
+
source = sys.argv[1]
|
|
128
|
+
specs = sys.argv[2:]
|
|
129
|
+
|
|
130
|
+
try:
|
|
131
|
+
if source == "stdin":
|
|
132
|
+
raw = sys.stdin.read()
|
|
133
|
+
else:
|
|
134
|
+
raw = pathlib.Path(source).read_text()
|
|
135
|
+
except OSError as e:
|
|
136
|
+
sys.stderr.write(f"emit_env.py: cannot read {source}: {e}\n")
|
|
137
|
+
return 1
|
|
138
|
+
|
|
139
|
+
try:
|
|
140
|
+
data = json.loads(raw)
|
|
141
|
+
except json.JSONDecodeError as e:
|
|
142
|
+
sys.stderr.write(f"emit_env.py: JSON parse error: {e}\n")
|
|
143
|
+
return 1
|
|
144
|
+
|
|
145
|
+
for spec in specs:
|
|
146
|
+
try:
|
|
147
|
+
key, value = apply_spec(data, spec)
|
|
148
|
+
except ValueError as e:
|
|
149
|
+
sys.stderr.write(f"emit_env.py: {e}\n")
|
|
150
|
+
return 1
|
|
151
|
+
sys.stdout.write(f"export {key}={shlex.quote(value)}\n")
|
|
152
|
+
|
|
153
|
+
return 0
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
if __name__ == "__main__":
|
|
157
|
+
sys.exit(main())
|