ha-mcp-dev 7.14.2.dev2193__tar.gz → 7.14.2.dev2196__tar.gz
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.
- {ha_mcp_dev-7.14.2.dev2193/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.14.2.dev2196}/PKG-INFO +1 -1
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/pyproject.toml +1 -1
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +1 -1
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +1 -1
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/resources/skills-vendor/AGENTS.md +6 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +6 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +1 -1
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +30 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/best_practice_checker.py +38 -93
- ha_mcp_dev-7.14.2.dev2196/src/ha_mcp/tools/best_practice_result.py +95 -0
- ha_mcp_dev-7.14.2.dev2196/src/ha_mcp/tools/best_practice_variables.py +432 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_config_automations.py +43 -14
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp_dev.egg-info/SOURCES.txt +2 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/LICENSE +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/MANIFEST.in +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/README.md +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/setup.cfg +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/__main__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/_pypi_marker +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/_version.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/auth/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/auth/consent_form.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/auth/provider.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/backup_manager.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/browser_landing.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/client/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/client/rest_client.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/client/supervisor_client.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/client/websocket_client.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/client/websocket_listener.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/config.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/dashboard_screenshot/content.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/dashboard_screenshot/paths.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/dashboard_screenshot/theme_guard.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/errors.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/llm_exposure.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/policy/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/policy/approval_queue.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/policy/evaluator.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/policy/handlers.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/policy/middleware.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/policy/model.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/policy/persistence.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/policy/value_sources.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/py.typed +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/read_only.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/appdaemon.md +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/blueprint-guide.md +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/scenes.md +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/server.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/settings_ui/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/settings_ui/_handlers_advanced.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/settings_ui/_handlers_backups.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/settings_ui/_handlers_fs.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/settings_ui/_handlers_server.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/settings_ui/_handlers_theme.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/settings_ui/_handlers_tools.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/settings_ui/_i18n.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/settings_ui/_persistence.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/settings_ui/_supervisor.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/settings_ui/_theme.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/settings_ui/_tools_meta.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/settings_ui/locales/README.md +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/settings_ui/locales/de.json +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/settings_ui/locales/en.json +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/settings_ui/locales/es.json +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/settings_ui/locales/fr.json +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/settings_ui/locales/it.json +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/settings_ui/locales/ru.json +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/settings_ui/locales/zh-Hans.json +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/settings_ui/settings.css +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/settings_ui/settings.html +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/settings_ui/settings.js +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/smoke_test.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/strict_bps.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/auto_backup.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/backup.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/component_api.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/component_config_reads.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/component_devices.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/component_registries.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/component_registry_lookup.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/config_entry_flow.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/device_control.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/helpers.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/radio/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/radio/base.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/radio/matter.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/radio/thread.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/radio/zigbee.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/radio/zwave.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/reference_validator.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/registry.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/smart_search/_base.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/smart_search/_config.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/smart_search/_deep.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/smart_search/_entities.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/smart_search/_scenes.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tool_search_hint_middleware.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_addons.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_areas.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_blueprints.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_bug_report.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_calendar.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_camera.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_categories.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_code.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_dev.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_energy.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_entities.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_filesystem.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_groups.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_hacs.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_history.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_integrations.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_labels.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_radio.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_registry.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_resources.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_search.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_service.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_services.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_system.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_themes.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_todo.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_traces.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_updates.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_utility.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_yaml_read.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_zones.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/util_helpers.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/validation_middleware.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/transforms/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/transforms/categorized_search.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/transport_security.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/update_check.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/utils/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/utils/config_hash.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/utils/config_write_lock.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/utils/data_paths.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/utils/domain_handlers.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/utils/fuzzy_search.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/utils/operation_manager.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/utils/python_sandbox.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/utils/skill_loader.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/utils/usage_logger.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/visibility/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/visibility/enforcement.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/visibility/model.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/visibility/persistence.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/visibility/resolver.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/tests/__init__.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/tests/test_constants.py +0 -0
- {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/tests/test_env_manager.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ha-mcp-dev"
|
|
7
|
-
version = "7.14.2.
|
|
7
|
+
version = "7.14.2.dev2196"
|
|
8
8
|
description = "Home Assistant MCP Server - Complete control of Home Assistant through MCP"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
# Upper bound widened to <3.15 so the package installs on Home Assistant's
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "home-assistant-skills",
|
|
3
3
|
"description": "Agent skills for Home Assistant: best practices for automations, helpers, templates, device control, and dashboards.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.16.0",
|
|
5
5
|
"repository": "https://github.com/homeassistant-ai/skills",
|
|
6
6
|
"license": "MIT"
|
|
7
7
|
}
|
{ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/resources/skills-vendor/AGENTS.md
RENAMED
|
@@ -55,3 +55,9 @@ To validate locally:
|
|
|
55
55
|
```bash
|
|
56
56
|
uvx skills-ref validate skills/<skill-name>
|
|
57
57
|
```
|
|
58
|
+
|
|
59
|
+
## Reviewing Skill PRs
|
|
60
|
+
|
|
61
|
+
- Judge prose as agent-consumed context, not human docs — the Skill Authoring Principles above are the review bar (e.g. an operator→result lookup table beats narrative bullets, because agents land here holding one case to resolve)
|
|
62
|
+
- Skills make version-pinned claims about HA behavior — verify against source at the release tag: `gh api repos/home-assistant/core/contents/<path>?ref=2026.7.4 --jq .content | base64 -d`
|
|
63
|
+
- Community PRs come from forks: base-repo `?ref=<pr-branch>` 404s; get the fork with `gh pr view <pr> --json headRepository` and fetch files from there
|
{ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/resources/skills-vendor/CLAUDE.md
RENAMED
|
@@ -55,3 +55,9 @@ To validate locally:
|
|
|
55
55
|
```bash
|
|
56
56
|
uvx skills-ref validate skills/<skill-name>
|
|
57
57
|
```
|
|
58
|
+
|
|
59
|
+
## Reviewing Skill PRs
|
|
60
|
+
|
|
61
|
+
- Judge prose as agent-consumed context, not human docs — the Skill Authoring Principles above are the review bar (e.g. an operator→result lookup table beats narrative bullets, because agents land here holding one case to resolve)
|
|
62
|
+
- Skills make version-pinned claims about HA behavior — verify against source at the release tag: `gh api repos/home-assistant/core/contents/<path>?ref=2026.7.4 --jq .content | base64 -d`
|
|
63
|
+
- Community PRs come from forks: base-repo `?ref=<pr-branch>` 404s; get the fork with `gh pr view <pr> --json headRepository` and fetch files from there
|
|
@@ -927,6 +927,36 @@ variables:
|
|
|
927
927
|
|
|
928
928
|
`trigger_variables:` is a separate top-level key evaluated **before** triggers fire — it supports **limited templates only** (no `states()`/`state_attr()`), mainly for passing a blueprint `!input` into trigger options. Don't put state-based templates there.
|
|
929
929
|
|
|
930
|
+
### Keys render in order, one at a time
|
|
931
|
+
|
|
932
|
+
A `variables:` block renders one key at a time, each rendered result feeding the context for the next. A key that reads a name declared **further down the same block** reads it while it is still undefined.
|
|
933
|
+
|
|
934
|
+
```yaml
|
|
935
|
+
# AVOID — `total` is still undefined when `msg` renders
|
|
936
|
+
variables:
|
|
937
|
+
msg: "Total: {{ total }}"
|
|
938
|
+
total: "{{ states('sensor.a') | float(0) + states('sensor.b') | float(0) }}"
|
|
939
|
+
|
|
940
|
+
# CORRECT — declare before use
|
|
941
|
+
variables:
|
|
942
|
+
total: "{{ states('sensor.a') | float(0) + states('sensor.b') | float(0) }}"
|
|
943
|
+
msg: "Total: {{ total }}"
|
|
944
|
+
```
|
|
945
|
+
|
|
946
|
+
What the forward read costs depends on how the template uses the name (behavior as of 2026.7):
|
|
947
|
+
|
|
948
|
+
| Use of the undefined name | Result |
|
|
949
|
+
|---|---|
|
|
950
|
+
| `x.attr`, `x['k']`, arithmetic, `<`/`>`/`<=`/`>=`, `\| int`, `\| float` | Raises `UndefinedError`, logs ERROR — **aborts the run** |
|
|
951
|
+
| `{{ x }}`, `{% if x %}`, `{% for i in x %}`, `'p' ~ x` | Empty and falsy, logs WARNING — runs on into the else branch |
|
|
952
|
+
| `x \| length`, `x == y`, `x != y` | `0` / `False` / `True` — **no log and no trace entry** |
|
|
953
|
+
|
|
954
|
+
For the aborting row, [`continue_on_error:`](#continue-on-error) is no escape — it is an action option, a top-level block is not an action, and on a mid-sequence step it cannot suppress a render error. A default like `| int(0)` does not save it either: the default covers an unconvertible value, not an undefined name. In the silent row, watch `!=`: `{% if later != 'x' %}` on a name that is not defined yet takes the **then** branch, leaving nothing behind to explain why.
|
|
955
|
+
|
|
956
|
+
Not a problem when the name also exists in an enclosing scope — the reference then resolves outward to that value. The two placements diverge on that collision, though: a top-level `variables:` block **skips** a key already present in the incoming scope, so the incoming value stands and the block's own definition never takes effect for that run, while a mid-sequence `- variables:` step renders and **overwrites** it. Staging a computation across consecutive `variables:` steps is therefore mid-sequence only.
|
|
957
|
+
|
|
958
|
+
When an automation declares both `trigger_variables:` and `variables:`, the two render as one sequential mapping with `trigger_variables:` first, so a `variables:` key may read a `trigger_variables:` key but not the reverse. That holds for names declared in only one of the two: a name in both merges into a single entry that keeps the `trigger_variables:` position but takes the `variables:` template, so it renders early — before any `trigger_variables:` key declared after it. The separate attach-time render noted above happens before any `variables:` key exists.
|
|
959
|
+
|
|
930
960
|
---
|
|
931
961
|
|
|
932
962
|
## Capturing Action Responses
|
{ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/best_practice_checker.py
RENAMED
|
@@ -47,6 +47,10 @@ documented legitimate dynamic-data positions per
|
|
|
47
47
|
* Top-level ``variables.*``
|
|
48
48
|
* Action ``service_data.*`` (legacy alias for ``data``)
|
|
49
49
|
|
|
50
|
+
The allowlist covers template *content*. Key *order* inside a ``variables``
|
|
51
|
+
block is separately load-bearing — HA renders one key at a time — so those
|
|
52
|
+
blocks get their own ordering-only pass (:func:`_check_variables_order`).
|
|
53
|
+
|
|
50
54
|
Anti-patterns sourced from:
|
|
51
55
|
https://github.com/homeassistant-ai/skills
|
|
52
56
|
skill://home-assistant-best-practices
|
|
@@ -57,33 +61,12 @@ from __future__ import annotations
|
|
|
57
61
|
import re
|
|
58
62
|
from typing import Any
|
|
59
63
|
|
|
60
|
-
|
|
61
|
-
_DEFAULT_SKILL_PREFIX
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
"""Warning list with an attached set of referenced skill files.
|
|
67
|
-
|
|
68
|
-
Behaves as a plain ``list[str]`` for all call sites — ``len()``,
|
|
69
|
-
indexing, iteration, equality with ``[...]`` all work unchanged.
|
|
70
|
-
``referenced_files`` is added on top so the write tools can resolve
|
|
71
|
-
and embed the relevant skill bodies into responses.
|
|
72
|
-
|
|
73
|
-
Use :func:`_emit` to append warnings; direct ``append``/``extend``
|
|
74
|
-
skip the ``referenced_files`` mirror. Slicing, ``list()`` coercion,
|
|
75
|
-
and ``copy.copy``/``copy.deepcopy`` all return plain ``list[str]``
|
|
76
|
-
without the attribute — no current call site does any of these.
|
|
77
|
-
"""
|
|
78
|
-
|
|
79
|
-
__slots__ = ("referenced_files",)
|
|
80
|
-
|
|
81
|
-
referenced_files: set[str]
|
|
82
|
-
|
|
83
|
-
def __init__(self, items: list[str] | None = None) -> None:
|
|
84
|
-
super().__init__(items or [])
|
|
85
|
-
self.referenced_files = set()
|
|
86
|
-
|
|
64
|
+
from .best_practice_result import (
|
|
65
|
+
_DEFAULT_SKILL_PREFIX,
|
|
66
|
+
BestPracticeCheckResult,
|
|
67
|
+
_emit,
|
|
68
|
+
)
|
|
69
|
+
from .best_practice_variables import _check_variables_order
|
|
87
70
|
|
|
88
71
|
# ---------------------------------------------------------------------------
|
|
89
72
|
# Regex patterns for template anti-patterns
|
|
@@ -241,6 +224,11 @@ def check_automation_config(
|
|
|
241
224
|
# Mode vs motion pattern
|
|
242
225
|
_check_mode_motion(config, warnings, skill_prefix)
|
|
243
226
|
|
|
227
|
+
# Key order inside the top-level variables blocks. Both render one key at a
|
|
228
|
+
# time, so a forward reference between siblings is silently undefined.
|
|
229
|
+
for block_key in ("variables", "trigger_variables"):
|
|
230
|
+
_check_variables_order(config.get(block_key), warnings, skill_prefix, block_key)
|
|
231
|
+
|
|
244
232
|
_dedupe_inplace(warnings)
|
|
245
233
|
return warnings
|
|
246
234
|
|
|
@@ -260,72 +248,11 @@ def check_script_config(
|
|
|
260
248
|
|
|
261
249
|
warnings = BestPracticeCheckResult()
|
|
262
250
|
_check_action_tree(config.get("sequence", []), warnings, skill_prefix)
|
|
251
|
+
_check_variables_order(config.get("variables"), warnings, skill_prefix, "variables")
|
|
263
252
|
_dedupe_inplace(warnings)
|
|
264
253
|
return warnings
|
|
265
254
|
|
|
266
255
|
|
|
267
|
-
# ---------------------------------------------------------------------------
|
|
268
|
-
# Warning emission + skill-reference helpers
|
|
269
|
-
# ---------------------------------------------------------------------------
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
def _emit(
|
|
273
|
-
warnings: BestPracticeCheckResult,
|
|
274
|
-
message: str,
|
|
275
|
-
skill_prefix: str | None,
|
|
276
|
-
file_ref: str,
|
|
277
|
-
) -> None:
|
|
278
|
-
"""Append a warning with the 2-route ' See ...' suffix and track the file.
|
|
279
|
-
|
|
280
|
-
Args:
|
|
281
|
-
warnings: Accumulator (also holds ``referenced_files``).
|
|
282
|
-
message: Human-readable warning body — the inline alternative.
|
|
283
|
-
skill_prefix: When set, embedded as the ``skill://`` URI route.
|
|
284
|
-
When ``None``, the URI route is omitted but the other two
|
|
285
|
-
routes still appear.
|
|
286
|
-
file_ref: File path relative to the ``references/`` directory of
|
|
287
|
-
the home-assistant-best-practices skill, optionally with a
|
|
288
|
-
``#anchor`` suffix
|
|
289
|
-
(e.g. ``"automation-patterns.md#native-conditions"``). The
|
|
290
|
-
anchor is preserved end-to-end: in the ``skill://`` URI for
|
|
291
|
-
display, and in ``referenced_files`` so the auto-embed path
|
|
292
|
-
ships only the matching markdown section instead of the
|
|
293
|
-
whole 10-20 KB reference file.
|
|
294
|
-
"""
|
|
295
|
-
warnings.append(message + _skill_route_suffix(skill_prefix, file_ref))
|
|
296
|
-
warnings.referenced_files.add(f"references/{file_ref}")
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
def _skill_route_suffix(skill_prefix: str | None, file_ref: str) -> str:
|
|
300
|
-
"""Build the ' See ...' suffix naming the available skill access routes.
|
|
301
|
-
|
|
302
|
-
When ``skill_prefix`` is ``None`` the entire suffix is suppressed —
|
|
303
|
-
skills are off server-wide, so neither route resolves. Otherwise the
|
|
304
|
-
suffix names both so the LLM has a working path regardless of which
|
|
305
|
-
mechanism its client supports:
|
|
306
|
-
|
|
307
|
-
1. ``skill://`` URI — for clients that auto-fetch resource URIs.
|
|
308
|
-
Anchor preserved.
|
|
309
|
-
2. ``ha_get_skill_guide(skill=..., file=...)`` — explicit tool call,
|
|
310
|
-
works on every MCP client. Anchor stripped (the tool reads the
|
|
311
|
-
whole file).
|
|
312
|
-
|
|
313
|
-
Auto-embed of the matching section still happens in the next
|
|
314
|
-
write's response, driven by ``referenced_files``; the
|
|
315
|
-
``MandatoryBPS`` opt-out param is not named here by design.
|
|
316
|
-
"""
|
|
317
|
-
if not skill_prefix:
|
|
318
|
-
# Skills feature is disabled server-wide; none of the routes work.
|
|
319
|
-
# Matches the historical no-suffix behaviour.
|
|
320
|
-
return ""
|
|
321
|
-
bare_file = f"references/{file_ref.split('#', 1)[0]}"
|
|
322
|
-
routes = [
|
|
323
|
-
f"{skill_prefix}/{file_ref}",
|
|
324
|
-
f"call ha_get_skill_guide(skill={_SKILL_NAME!r}, file={bare_file!r})",
|
|
325
|
-
]
|
|
326
|
-
return " See " + " | ".join(routes)
|
|
327
|
-
|
|
328
|
-
|
|
329
256
|
# ---------------------------------------------------------------------------
|
|
330
257
|
# Condition template checks
|
|
331
258
|
# ---------------------------------------------------------------------------
|
|
@@ -588,16 +515,27 @@ def _check_repeat_actions(
|
|
|
588
515
|
def _check_control_flow_actions(
|
|
589
516
|
action: dict[str, Any], warnings: BestPracticeCheckResult, skill_prefix: str | None
|
|
590
517
|
) -> None:
|
|
591
|
-
"""Check choose/if/then/else/repeat/parallel sub-trees in
|
|
518
|
+
"""Check choose/if/then/else/repeat/parallel/sequence sub-trees in one action.
|
|
519
|
+
|
|
520
|
+
``sequence`` is both a grouping action of its own
|
|
521
|
+
(``cv.SCRIPT_ACTION_SEQUENCE``) and the canonical shape of a ``parallel:``
|
|
522
|
+
branch — HA normalises the shorthand branch list into ``{"sequence": ...}``
|
|
523
|
+
(``_SCRIPT_PARALLEL_SCHEMA`` / ``_parallel_sequence_action``). Without this
|
|
524
|
+
arm only the shorthand branch was walked, so the canonical form of both was
|
|
525
|
+
invisible to every check below this point.
|
|
526
|
+
"""
|
|
592
527
|
if "choose" in action:
|
|
593
528
|
_check_choose_actions(action["choose"], warnings, skill_prefix)
|
|
594
529
|
|
|
595
530
|
if "if" in action:
|
|
596
531
|
_check_condition_templates(action["if"], warnings, skill_prefix)
|
|
597
532
|
|
|
598
|
-
|
|
533
|
+
# Every one of these is a `SCRIPT_SCHEMA` position, and that schema is
|
|
534
|
+
# `vol.All(ensure_list, [script_action])` — a lone action mapping is valid
|
|
535
|
+
# wherever a list is, so both shapes have to be walked.
|
|
536
|
+
for key in ("then", "else", "default", "sequence"):
|
|
599
537
|
nested = action.get(key)
|
|
600
|
-
if isinstance(nested, list):
|
|
538
|
+
if isinstance(nested, list | dict):
|
|
601
539
|
_check_action_tree(nested, warnings, skill_prefix)
|
|
602
540
|
|
|
603
541
|
if "repeat" in action and isinstance(action["repeat"], dict):
|
|
@@ -606,7 +544,7 @@ def _check_control_flow_actions(
|
|
|
606
544
|
# `parallel:` runs sub-actions concurrently — same shape as `sequence`,
|
|
607
545
|
# different semantics. Recurse so templates inside parallel branches
|
|
608
546
|
# are inspected the same as templates inside choose/repeat sequences.
|
|
609
|
-
if
|
|
547
|
+
if isinstance(action.get("parallel"), list | dict):
|
|
610
548
|
_check_action_tree(action["parallel"], warnings, skill_prefix)
|
|
611
549
|
|
|
612
550
|
|
|
@@ -646,6 +584,13 @@ def _check_action_tree(
|
|
|
646
584
|
# service names based on state.
|
|
647
585
|
_check_service_template(action, warnings, skill_prefix)
|
|
648
586
|
|
|
587
|
+
# A `variables:` step is a legitimate template position, so it is never
|
|
588
|
+
# walked for template misuse. Its key *order* is still load-bearing —
|
|
589
|
+
# scanned separately.
|
|
590
|
+
_check_variables_order(
|
|
591
|
+
action.get("variables"), warnings, skill_prefix, "variables"
|
|
592
|
+
)
|
|
593
|
+
|
|
649
594
|
# Templates in target sub-fields. Action `data`, `event_data`,
|
|
650
595
|
# `service_data`, notification message/title, and `variables` are
|
|
651
596
|
# legitimate dynamic-data positions per template-guidelines.md and
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""Warning accumulator and skill-reference routing for the best-practice checks.
|
|
2
|
+
|
|
3
|
+
Split out of :mod:`best_practice_checker` so a check module can emit a warning
|
|
4
|
+
without importing the checker back (AGENTS.md - Module Size). Nothing here
|
|
5
|
+
inspects a config; it only carries warnings and builds the ' See ...' suffix.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
_SKILL_URI_PREFIX = "skill://home-assistant-best-practices/references"
|
|
9
|
+
_DEFAULT_SKILL_PREFIX = _SKILL_URI_PREFIX
|
|
10
|
+
_SKILL_NAME = "home-assistant-best-practices"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class BestPracticeCheckResult(list[str]):
|
|
14
|
+
"""Warning list with an attached set of referenced skill files.
|
|
15
|
+
|
|
16
|
+
Behaves as a plain ``list[str]`` for all call sites — ``len()``,
|
|
17
|
+
indexing, iteration, equality with ``[...]`` all work unchanged.
|
|
18
|
+
``referenced_files`` is added on top so the write tools can resolve
|
|
19
|
+
and embed the relevant skill bodies into responses.
|
|
20
|
+
|
|
21
|
+
Use :func:`_emit` to append warnings; direct ``append``/``extend``
|
|
22
|
+
skip the ``referenced_files`` mirror. Slicing, ``list()`` coercion,
|
|
23
|
+
and ``copy.copy``/``copy.deepcopy`` all return plain ``list[str]``
|
|
24
|
+
without the attribute — no current call site does any of these.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
__slots__ = ("referenced_files",)
|
|
28
|
+
|
|
29
|
+
referenced_files: set[str]
|
|
30
|
+
|
|
31
|
+
def __init__(self, items: list[str] | None = None) -> None:
|
|
32
|
+
super().__init__(items or [])
|
|
33
|
+
self.referenced_files = set()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# ---------------------------------------------------------------------------
|
|
37
|
+
# Warning emission + skill-reference helpers
|
|
38
|
+
# ---------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _emit(
|
|
42
|
+
warnings: BestPracticeCheckResult,
|
|
43
|
+
message: str,
|
|
44
|
+
skill_prefix: str | None,
|
|
45
|
+
file_ref: str,
|
|
46
|
+
) -> None:
|
|
47
|
+
"""Append a warning with the 2-route ' See ...' suffix and track the file.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
warnings: Accumulator (also holds ``referenced_files``).
|
|
51
|
+
message: Human-readable warning body — the inline alternative.
|
|
52
|
+
skill_prefix: When set, embedded as the ``skill://`` URI route.
|
|
53
|
+
When falsy, the whole ``See ...`` suffix is dropped rather than
|
|
54
|
+
just that route — see :func:`_skill_route_suffix`.
|
|
55
|
+
file_ref: File path relative to the ``references/`` directory of
|
|
56
|
+
the home-assistant-best-practices skill, optionally with a
|
|
57
|
+
``#anchor`` suffix
|
|
58
|
+
(e.g. ``"automation-patterns.md#native-conditions"``). The
|
|
59
|
+
anchor is preserved end-to-end: in the ``skill://`` URI for
|
|
60
|
+
display, and in ``referenced_files`` so the auto-embed path
|
|
61
|
+
ships only the matching markdown section instead of the
|
|
62
|
+
whole 10-20 KB reference file.
|
|
63
|
+
"""
|
|
64
|
+
warnings.append(message + _skill_route_suffix(skill_prefix, file_ref))
|
|
65
|
+
warnings.referenced_files.add(f"references/{file_ref}")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _skill_route_suffix(skill_prefix: str | None, file_ref: str) -> str:
|
|
69
|
+
"""Build the ' See ...' suffix naming the available skill access routes.
|
|
70
|
+
|
|
71
|
+
When ``skill_prefix`` is ``None`` the entire suffix is suppressed —
|
|
72
|
+
skills are off server-wide, so neither route resolves. Otherwise the
|
|
73
|
+
suffix names both so the LLM has a working path regardless of which
|
|
74
|
+
mechanism its client supports:
|
|
75
|
+
|
|
76
|
+
1. ``skill://`` URI — for clients that auto-fetch resource URIs.
|
|
77
|
+
Anchor preserved.
|
|
78
|
+
2. ``ha_get_skill_guide(skill=..., file=...)`` — explicit tool call,
|
|
79
|
+
works on every MCP client. Anchor stripped (the tool reads the
|
|
80
|
+
whole file).
|
|
81
|
+
|
|
82
|
+
Auto-embed of the matching section still happens in the next
|
|
83
|
+
write's response, driven by ``referenced_files``; the
|
|
84
|
+
``MandatoryBPS`` opt-out param is not named here by design.
|
|
85
|
+
"""
|
|
86
|
+
if not skill_prefix:
|
|
87
|
+
# Skills feature is disabled server-wide; none of the routes work.
|
|
88
|
+
# Matches the historical no-suffix behaviour.
|
|
89
|
+
return ""
|
|
90
|
+
bare_file = f"references/{file_ref.split('#', 1)[0]}"
|
|
91
|
+
routes = [
|
|
92
|
+
f"{skill_prefix}/{file_ref}",
|
|
93
|
+
f"call ha_get_skill_guide(skill={_SKILL_NAME!r}, file={bare_file!r})",
|
|
94
|
+
]
|
|
95
|
+
return " See " + " | ".join(routes)
|