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.
Files changed (194) hide show
  1. {ha_mcp_dev-7.14.2.dev2193/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.14.2.dev2196}/PKG-INFO +1 -1
  2. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/pyproject.toml +1 -1
  3. {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
  4. {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
  5. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/resources/skills-vendor/AGENTS.md +6 -0
  6. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +6 -0
  7. {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
  8. {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
  9. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/best_practice_checker.py +38 -93
  10. ha_mcp_dev-7.14.2.dev2196/src/ha_mcp/tools/best_practice_result.py +95 -0
  11. ha_mcp_dev-7.14.2.dev2196/src/ha_mcp/tools/best_practice_variables.py +432 -0
  12. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_config_automations.py +43 -14
  13. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
  14. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp_dev.egg-info/SOURCES.txt +2 -0
  15. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/LICENSE +0 -0
  16. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/MANIFEST.in +0 -0
  17. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/README.md +0 -0
  18. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/setup.cfg +0 -0
  19. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/__init__.py +0 -0
  20. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/__main__.py +0 -0
  21. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/_pypi_marker +0 -0
  22. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/_version.py +0 -0
  23. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/auth/__init__.py +0 -0
  24. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/auth/consent_form.py +0 -0
  25. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/auth/provider.py +0 -0
  26. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/backup_manager.py +0 -0
  27. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/browser_landing.py +0 -0
  28. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/client/__init__.py +0 -0
  29. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/client/rest_client.py +0 -0
  30. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/client/supervisor_client.py +0 -0
  31. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/client/websocket_client.py +0 -0
  32. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/client/websocket_listener.py +0 -0
  33. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/config.py +0 -0
  34. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/dashboard_screenshot/__init__.py +0 -0
  35. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/dashboard_screenshot/capture.py +0 -0
  36. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/dashboard_screenshot/content.py +0 -0
  37. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/dashboard_screenshot/paths.py +0 -0
  38. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/dashboard_screenshot/provision.py +0 -0
  39. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/dashboard_screenshot/theme_guard.py +0 -0
  40. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/errors.py +0 -0
  41. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/llm_exposure.py +0 -0
  42. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/policy/__init__.py +0 -0
  43. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/policy/approval_queue.py +0 -0
  44. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/policy/evaluator.py +0 -0
  45. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/policy/handlers.py +0 -0
  46. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/policy/middleware.py +0 -0
  47. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/policy/model.py +0 -0
  48. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/policy/persistence.py +0 -0
  49. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/policy/value_sources.py +0 -0
  50. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/py.typed +0 -0
  51. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/read_only.py +0 -0
  52. {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
  53. {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
  54. {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
  55. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  56. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  57. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  58. {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
  59. {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
  60. {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
  61. {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
  62. {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
  63. {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
  64. {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
  65. {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
  66. {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
  67. {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
  68. {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
  69. {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
  70. {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
  71. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/server.py +0 -0
  72. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/settings_ui/__init__.py +0 -0
  73. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/settings_ui/_handlers_advanced.py +0 -0
  74. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/settings_ui/_handlers_backups.py +0 -0
  75. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/settings_ui/_handlers_fs.py +0 -0
  76. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/settings_ui/_handlers_server.py +0 -0
  77. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/settings_ui/_handlers_theme.py +0 -0
  78. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/settings_ui/_handlers_tools.py +0 -0
  79. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/settings_ui/_i18n.py +0 -0
  80. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/settings_ui/_persistence.py +0 -0
  81. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/settings_ui/_supervisor.py +0 -0
  82. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/settings_ui/_theme.py +0 -0
  83. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/settings_ui/_tools_meta.py +0 -0
  84. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/settings_ui/locales/README.md +0 -0
  85. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/settings_ui/locales/de.json +0 -0
  86. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/settings_ui/locales/en.json +0 -0
  87. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/settings_ui/locales/es.json +0 -0
  88. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/settings_ui/locales/fr.json +0 -0
  89. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/settings_ui/locales/it.json +0 -0
  90. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/settings_ui/locales/ru.json +0 -0
  91. {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
  92. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/settings_ui/settings.css +0 -0
  93. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/settings_ui/settings.html +0 -0
  94. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/settings_ui/settings.js +0 -0
  95. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/smoke_test.py +0 -0
  96. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
  97. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/strict_bps.py +0 -0
  98. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/__init__.py +0 -0
  99. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/auto_backup.py +0 -0
  100. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/backup.py +0 -0
  101. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/component_api.py +0 -0
  102. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/component_config_reads.py +0 -0
  103. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/component_devices.py +0 -0
  104. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/component_registries.py +0 -0
  105. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/component_registry_lookup.py +0 -0
  106. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/config_entry_flow.py +0 -0
  107. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/device_control.py +0 -0
  108. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/helpers.py +0 -0
  109. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/radio/__init__.py +0 -0
  110. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/radio/base.py +0 -0
  111. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/radio/matter.py +0 -0
  112. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/radio/thread.py +0 -0
  113. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/radio/zigbee.py +0 -0
  114. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/radio/zwave.py +0 -0
  115. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/reference_validator.py +0 -0
  116. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/registry.py +0 -0
  117. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/smart_search/__init__.py +0 -0
  118. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/smart_search/_base.py +0 -0
  119. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/smart_search/_config.py +0 -0
  120. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/smart_search/_deep.py +0 -0
  121. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/smart_search/_entities.py +0 -0
  122. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/smart_search/_fetch.py +0 -0
  123. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/smart_search/_overview.py +0 -0
  124. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/smart_search/_scenes.py +0 -0
  125. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/smart_search/_scoring.py +0 -0
  126. {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
  127. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_addons.py +0 -0
  128. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_areas.py +0 -0
  129. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  130. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_bug_report.py +0 -0
  131. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_calendar.py +0 -0
  132. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_camera.py +0 -0
  133. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_categories.py +0 -0
  134. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_code.py +0 -0
  135. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
  136. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
  137. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
  138. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_config_scripts.py +0 -0
  139. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_dashboard_screenshot.py +0 -0
  140. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_dev.py +0 -0
  141. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_energy.py +0 -0
  142. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_entities.py +0 -0
  143. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_filesystem.py +0 -0
  144. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_groups.py +0 -0
  145. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_hacs.py +0 -0
  146. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_history.py +0 -0
  147. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_integrations.py +0 -0
  148. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_labels.py +0 -0
  149. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_radio.py +0 -0
  150. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_registry.py +0 -0
  151. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_resources.py +0 -0
  152. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_search.py +0 -0
  153. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_service.py +0 -0
  154. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_services.py +0 -0
  155. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_system.py +0 -0
  156. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_themes.py +0 -0
  157. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_todo.py +0 -0
  158. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_traces.py +0 -0
  159. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_updates.py +0 -0
  160. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_utility.py +0 -0
  161. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  162. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
  163. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_yaml_read.py +0 -0
  164. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/tools_zones.py +0 -0
  165. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/util_helpers.py +0 -0
  166. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/tools/validation_middleware.py +0 -0
  167. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/transforms/__init__.py +0 -0
  168. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/transforms/categorized_search.py +0 -0
  169. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
  170. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/transport_security.py +0 -0
  171. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/update_check.py +0 -0
  172. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/utils/__init__.py +0 -0
  173. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/utils/config_hash.py +0 -0
  174. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/utils/config_write_lock.py +0 -0
  175. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/utils/data_paths.py +0 -0
  176. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/utils/domain_handlers.py +0 -0
  177. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  178. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
  179. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/utils/operation_manager.py +0 -0
  180. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/utils/python_sandbox.py +0 -0
  181. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/utils/skill_loader.py +0 -0
  182. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/utils/usage_logger.py +0 -0
  183. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/visibility/__init__.py +0 -0
  184. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/visibility/enforcement.py +0 -0
  185. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/visibility/model.py +0 -0
  186. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/visibility/persistence.py +0 -0
  187. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp/visibility/resolver.py +0 -0
  188. {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
  189. {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
  190. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  191. {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
  192. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/tests/__init__.py +0 -0
  193. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/tests/test_constants.py +0 -0
  194. {ha_mcp_dev-7.14.2.dev2193 → ha_mcp_dev-7.14.2.dev2196}/tests/test_env_manager.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ha-mcp-dev
3
- Version: 7.14.2.dev2193
3
+ Version: 7.14.2.dev2196
4
4
  Summary: Home Assistant MCP Server - Complete control of Home Assistant through MCP
5
5
  Author-email: Julien <github@qc-h.net>
6
6
  License: MIT
@@ -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.dev2193"
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
@@ -6,7 +6,7 @@
6
6
  },
7
7
  "metadata": {
8
8
  "description": "Agent skills for Home Assistant: best practices for automations, helpers, templates, device control, and dashboards",
9
- "version": "0.15.0"
9
+ "version": "0.16.0"
10
10
  },
11
11
  "plugins": [
12
12
  {
@@ -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.15.0",
4
+ "version": "0.16.0",
5
5
  "repository": "https://github.com/homeassistant-ai/skills",
6
6
  "license": "MIT"
7
7
  }
@@ -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
@@ -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
@@ -24,7 +24,7 @@ description: >
24
24
  - Agent tells user to edit configuration.yaml for UI integrations
25
25
  - Agent hardcodes entities in a Blueprint or uses free-text input over a selector
26
26
  metadata:
27
- version: 15
27
+ version: 16
28
28
  ---
29
29
 
30
30
  # Home Assistant Best Practices
@@ -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
@@ -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
- _SKILL_URI_PREFIX = "skill://home-assistant-best-practices/references"
61
- _DEFAULT_SKILL_PREFIX = _SKILL_URI_PREFIX
62
- _SKILL_NAME = "home-assistant-best-practices"
63
-
64
-
65
- class BestPracticeCheckResult(list[str]):
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 a single action."""
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
- for key in ("then", "else", "default"):
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 "parallel" in action and isinstance(action["parallel"], list):
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)