ha-mcp-dev 7.6.0.dev610__tar.gz → 7.6.0.dev612__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 (124) hide show
  1. {ha_mcp_dev-7.6.0.dev610/src/ha_mcp_dev.egg-info → ha_mcp_dev-7.6.0.dev612}/PKG-INFO +1 -1
  2. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/pyproject.toml +1 -1
  3. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/tools/best_practice_checker.py +45 -0
  4. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/tools/tools_config_automations.py +2 -2
  5. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/tools/tools_config_scripts.py +3 -0
  6. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/tools/tools_utility.py +1 -1
  7. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612/src/ha_mcp_dev.egg-info}/PKG-INFO +1 -1
  8. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/LICENSE +0 -0
  9. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/MANIFEST.in +0 -0
  10. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/README.md +0 -0
  11. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/setup.cfg +0 -0
  12. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/__init__.py +0 -0
  13. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/__main__.py +0 -0
  14. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/_pypi_marker +0 -0
  15. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/_version.py +0 -0
  16. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/auth/__init__.py +0 -0
  17. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/auth/consent_form.py +0 -0
  18. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/auth/provider.py +0 -0
  19. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/backup_manager.py +0 -0
  20. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/client/__init__.py +0 -0
  21. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/client/rest_client.py +0 -0
  22. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/client/supervisor_client.py +0 -0
  23. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/client/websocket_client.py +0 -0
  24. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/client/websocket_listener.py +0 -0
  25. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/config.py +0 -0
  26. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/errors.py +0 -0
  27. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/policy/__init__.py +0 -0
  28. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/policy/approval_queue.py +0 -0
  29. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/policy/evaluator.py +0 -0
  30. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/policy/handlers.py +0 -0
  31. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/policy/middleware.py +0 -0
  32. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/policy/model.py +0 -0
  33. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/policy/persistence.py +0 -0
  34. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/policy/value_sources.py +0 -0
  35. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/py.typed +0 -0
  36. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/resources/skills-vendor/.claude/settings.json +0 -0
  37. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/resources/skills-vendor/.claude-plugin/marketplace.json +0 -0
  38. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/resources/skills-vendor/.claude-plugin/plugin.json +0 -0
  39. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/resources/skills-vendor/.github/ISSUE_TEMPLATE/skill-rca.md +0 -0
  40. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/resources/skills-vendor/.github/pull_request_template.md +0 -0
  41. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/resources/skills-vendor/AGENTS.md +0 -0
  42. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/resources/skills-vendor/CLAUDE.md +0 -0
  43. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/resources/skills-vendor/CONTRIBUTING.md +0 -0
  44. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/resources/skills-vendor/LICENSE +0 -0
  45. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/resources/skills-vendor/README.md +0 -0
  46. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/SKILL.md +0 -0
  47. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/evals/evals.json +0 -0
  48. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/automation-patterns.md +0 -0
  49. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-cards.md +0 -0
  50. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/dashboard-guide.md +0 -0
  51. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/device-control.md +0 -0
  52. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/domain-docs.md +0 -0
  53. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/examples.yaml +0 -0
  54. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/helper-selection.md +0 -0
  55. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/safe-refactoring.md +0 -0
  56. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/template-guidelines.md +0 -0
  57. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/resources/skills-vendor/skills/home-assistant-best-practices/references/yaml-only-integrations.md +0 -0
  58. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/server.py +0 -0
  59. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/settings_ui.py +0 -0
  60. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/smoke_test.py +0 -0
  61. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/stdio_settings_sidecar.py +0 -0
  62. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/tools/__init__.py +0 -0
  63. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/tools/auto_backup.py +0 -0
  64. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/tools/backup.py +0 -0
  65. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/tools/device_control.py +0 -0
  66. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/tools/enhanced.py +0 -0
  67. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/tools/helpers.py +0 -0
  68. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/tools/reference_validator.py +0 -0
  69. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/tools/registry.py +0 -0
  70. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/tools/smart_search.py +0 -0
  71. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/tools/tools_addons.py +0 -0
  72. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/tools/tools_areas.py +0 -0
  73. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/tools/tools_blueprints.py +0 -0
  74. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/tools/tools_bug_report.py +0 -0
  75. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/tools/tools_calendar.py +0 -0
  76. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/tools/tools_camera.py +0 -0
  77. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/tools/tools_categories.py +0 -0
  78. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/tools/tools_code.py +0 -0
  79. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/tools/tools_config_dashboards.py +0 -0
  80. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/tools/tools_config_entry_flow.py +0 -0
  81. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/tools/tools_config_helpers.py +0 -0
  82. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/tools/tools_config_scenes.py +0 -0
  83. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/tools/tools_energy.py +0 -0
  84. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/tools/tools_entities.py +0 -0
  85. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/tools/tools_filesystem.py +0 -0
  86. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/tools/tools_groups.py +0 -0
  87. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/tools/tools_hacs.py +0 -0
  88. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/tools/tools_history.py +0 -0
  89. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/tools/tools_integrations.py +0 -0
  90. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/tools/tools_labels.py +0 -0
  91. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/tools/tools_mcp_component.py +0 -0
  92. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/tools/tools_registry.py +0 -0
  93. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/tools/tools_resources.py +0 -0
  94. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/tools/tools_search.py +0 -0
  95. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/tools/tools_service.py +0 -0
  96. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/tools/tools_services.py +0 -0
  97. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/tools/tools_system.py +0 -0
  98. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/tools/tools_todo.py +0 -0
  99. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/tools/tools_traces.py +0 -0
  100. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/tools/tools_updates.py +0 -0
  101. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/tools/tools_voice_assistant.py +0 -0
  102. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/tools/tools_yaml_config.py +0 -0
  103. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/tools/tools_zones.py +0 -0
  104. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/tools/util_helpers.py +0 -0
  105. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/transforms/__init__.py +0 -0
  106. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/transforms/categorized_search.py +0 -0
  107. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/transforms/lite_docstrings.py +0 -0
  108. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/utils/__init__.py +0 -0
  109. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/utils/config_hash.py +0 -0
  110. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/utils/data_paths.py +0 -0
  111. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/utils/domain_handlers.py +0 -0
  112. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/utils/fuzzy_search.py +0 -0
  113. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/utils/kill_signal_diagnostics.py +0 -0
  114. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/utils/operation_manager.py +0 -0
  115. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/utils/python_sandbox.py +0 -0
  116. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp/utils/usage_logger.py +0 -0
  117. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp_dev.egg-info/SOURCES.txt +0 -0
  118. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp_dev.egg-info/dependency_links.txt +0 -0
  119. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp_dev.egg-info/entry_points.txt +0 -0
  120. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp_dev.egg-info/requires.txt +0 -0
  121. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/src/ha_mcp_dev.egg-info/top_level.txt +0 -0
  122. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/tests/__init__.py +0 -0
  123. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/tests/test_constants.py +0 -0
  124. {ha_mcp_dev-7.6.0.dev610 → ha_mcp_dev-7.6.0.dev612}/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.6.0.dev610
3
+ Version: 7.6.0.dev612
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.6.0.dev610"
7
+ version = "7.6.0.dev612"
8
8
  description = "Home Assistant MCP Server - Complete control of Home Assistant through MCP"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.13,<3.14"
@@ -77,6 +77,13 @@ _RE_SUN = re.compile(r"(?:is_state|state_attr|states)\s*\(\s*['\"]sun\.sun['\"]"
77
77
  _RE_STATE_IN = re.compile(r"states\s*\([^)]+\)\s+in\s+[\[(]")
78
78
  # Unsafe direct state access: states.sensor.x.state
79
79
  _RE_DIRECT_STATE = re.compile(r"\bstates\.\w+\.\w+\.state\b")
80
+ # Duration/recency checks via last_changed or last_updated arithmetic.
81
+ # Both alternations require at least one dotted qualifier (e.g. ``states.sensor.x.``)
82
+ # so bare Jinja variables named ``last_changed`` are not falsely flagged.
83
+ _RE_DURATION_MATH = re.compile(
84
+ r"\bnow\(\)\s*-\s*(?:\w+\.)+last_(?:changed|updated)\b"
85
+ r"|\b(?:\w+\.)+last_(?:changed|updated)\s*<\s*now\(\)"
86
+ )
80
87
  # Motion entity pattern
81
88
  _RE_MOTION = re.compile(r"binary_sensor\.\w*motion", re.IGNORECASE)
82
89
  # Any Jinja template marker — catch-all and target-field scan.
@@ -273,6 +280,16 @@ def _check_template_string(
273
280
  "function instead (returns 'unknown' if missing rather than raising)."
274
281
  + _ref(skill_prefix, "template-guidelines.md#common-patterns")
275
282
  )
283
+ if _RE_DURATION_MATH.search(template):
284
+ warnings.append(
285
+ f"{label} uses template for duration/recency check "
286
+ "(`now() - X.last_changed/last_updated`) — use the native `for:` field "
287
+ "on a `state` trigger or condition instead "
288
+ "(e.g., `platform: state, entity_id: binary_sensor.motion, to: 'off', "
289
+ "for: {minutes: 5}`). Native `for:` is event-driven and avoids repeated "
290
+ "template evaluation on every state change."
291
+ + _ref(skill_prefix, "automation-patterns.md#native-conditions")
292
+ )
276
293
 
277
294
  # Generic fallback: any Jinja in this logic position that didn't match
278
295
  # a specific detector. Catches new anti-patterns (issue #1011) and
@@ -505,6 +522,19 @@ def _check_triggers(
505
522
  "automation-patterns.md#trigger-types",
506
523
  )
507
524
  )
525
+ if _RE_DURATION_MATH.search(vt):
526
+ warnings.append(
527
+ "Trigger uses template for duration/recency check "
528
+ "(`now() - X.last_changed/last_updated`) — use the native "
529
+ "`for:` field on a `state` trigger instead "
530
+ "(e.g., `platform: state, entity_id: binary_sensor.motion, "
531
+ "to: 'off', for: {minutes: 5}`). Native `for:` is event-driven "
532
+ "and doesn't re-evaluate on every state change."
533
+ + _ref(
534
+ skill_prefix,
535
+ "automation-patterns.md#trigger-types",
536
+ )
537
+ )
508
538
  # Generic fallback for unmatched template triggers.
509
539
  if len(warnings) == initial and _RE_ANY_TEMPLATE.search(vt):
510
540
  warnings.append(
@@ -518,6 +548,21 @@ def _check_triggers(
518
548
  )
519
549
  )
520
550
 
551
+ # numeric_state trigger: value_template can also contain duration math
552
+ # (e.g. transforming last_changed into a seconds value for the threshold).
553
+ if platform == "numeric_state":
554
+ vt = trigger.get("value_template", "")
555
+ if isinstance(vt, str) and _RE_DURATION_MATH.search(vt):
556
+ warnings.append(
557
+ "`numeric_state` trigger uses `value_template` for duration/recency "
558
+ "check (`now() - X.last_changed/last_updated`) — use the native "
559
+ "`for:` field on a `state` trigger instead "
560
+ "(e.g., `platform: state, entity_id: binary_sensor.motion, "
561
+ "to: 'off', for: {minutes: 5}`). Native `for:` is event-driven "
562
+ "and doesn't re-evaluate on every state change."
563
+ + _ref(skill_prefix, "automation-patterns.md#trigger-types")
564
+ )
565
+
521
566
 
522
567
  # ---------------------------------------------------------------------------
523
568
  # Mode + motion check
@@ -482,8 +482,8 @@ class AutomationConfigTools:
482
482
  `{{ states(x) in [...] }}`
483
483
  - `condition: time` instead of `{{ now().hour ... }}` or `{{ now().weekday() ... }}`
484
484
  - `condition: sun` instead of `{{ is_state('sun.sun', ...) }}`
485
- - Native `for:` field on `state`/`numeric_state` triggers and conditions over
486
- `{{ now() - X.last_changed > timedelta(...) }}` duration math.
485
+ - Native `for:` field on `state`/`numeric_state` triggers and `state`
486
+ conditions over `{{ now() - X.last_changed > timedelta(...) }}` duration math.
487
487
  - `wait_for_trigger` instead of `wait_template`
488
488
  - `choose` action instead of template-based service names
489
489
  - For one-shot date firing, use a `time` trigger plus `automation.turn_off` on a
@@ -436,6 +436,9 @@ class ConfigScriptTools:
436
436
  schema. Templates in logic positions fail silently and obscure intent.
437
437
  - `choose` / `if/then/else` instead of template-based service names
438
438
  - `wait_for_trigger` instead of `wait_template`
439
+ - Native `for:` field on `state` conditions inside `choose`/`if`, and on
440
+ `state`/`numeric_state` triggers in `wait_for_trigger`, instead of
441
+ `{{ now() - X.last_changed > timedelta(...) }}` duration math.
439
442
  - `repeat` with `for_each` instead of template loops
440
443
  - Hardcode `target.entity_id` literals — never `{{ this.entity_id }}`.
441
444
  Templates are appropriate ONLY in `data.*` fields, notification message/title,
@@ -1206,7 +1206,7 @@ def register_utility_tools(mcp: Any, client: Any, **kwargs: Any) -> None:
1206
1206
  - `condition: numeric_state` over `{{ states('x') | float > N }}`
1207
1207
  - `condition: state` over `{{ is_state(...) }}`
1208
1208
  - `condition: time` / `condition: sun` over `now().hour` / `is_state('sun.sun', ...)`
1209
- - Native `for:` field on state/numeric_state triggers and conditions over
1209
+ - Native `for:` field on state/numeric_state triggers and state conditions over
1210
1210
  `{{ now() - X.last_changed > timedelta(...) }}` duration math
1211
1211
  - `choose` action over templated `service:` / `action:` strings
1212
1212
  See `ha_get_skill_guide` (best-practices skill) for the full anti-pattern list.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ha-mcp-dev
3
- Version: 7.6.0.dev610
3
+ Version: 7.6.0.dev612
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