All notable changes to this project are documented here. The format is loosely based on Keep a Changelog.
This project uses a non-standard semver convention:
- Major (first digit): Reserved. Stays at
0. - Minor (second digit): Big feature changes — multiple new tools, architectural shifts, breaking changes.
- Patch (third digit): Single-tool enhancements, single-tool additions, bug fixes.
Release automation (.github/workflows/release.yml) generates each entry below by collecting PRs merged since the previous vX.Y.Z tag. Entries are added automatically when the maintainer labels a PR release:patch|minor|major and merges it. Unlabeled PRs still merge, and their content accumulates into the next labeled release.
Contributors: do not edit this file. Your PR title becomes the bullet. To customize the bullet (richer prose than the title alone), add a ## Release notes section to your PR body — the bot picks that up preferentially.
- feat: batch device commands through hub_call_device_command (#383, @TonyMajorDev)
hub_call_device_commandcan now send up to 20 mixed device commands in one call withcommands, returning ordered per-device outcomes and aggregate success counts.
- fix: close MRTR write-cap leaks and make the search corpus self-heal (#391, @level99)
- Fixed memory being retained after an interrupted Rule Machine or app write.
- Stopping or starting several rules in one call now reports each rule once, by its final
- A batch where every rule failed is now reported as a failure rather than a partial success.
- When a large batch reaches the continuation limit, the response now lists what each rule did
hub_list_filesnow documents itsfilterargument, so files can be found by name substring- Tool search results now pick up description and search-hint changes as soon as new code is
-
build(deps): bump gradle/actions from 6 to 6.2.0 in the github-actions group (#375, @app/dependabot)
-
feat: batch rule pause/run, modifyAction retarget, stageDisabled on clone/import (#377, @kingpanther13)
- Pause, resume, run, or set the private boolean of several Rule Machine rules in one call — pass an array of rule ids
- Retarget a "Run Rule Actions" (or Stop/Pause/Private-Boolean) action to a different rule in one step with hub_set_rule's new modifyAction shortcut
- Clone or import an app pre-disabled with the new stageDisabled option — safe staging for rule migrations (clones of active rules land active otherwise)
-
chore: Create Code Review Style Guide for Hubitat MCP Server (#379, @kingpanther13)
-
build(deps): bump gradle/actions from 6.2.0 to 6.3.0 in the github-actions group (#380, @app/dependabot)
-
ci: dispatchable fast lanes, and promote Groovy 2.5 Spock to a required gate (#382, @kingpanther13)
-
chore: vendor final MCP 2026-07-28 schema (#384, @kingpanther13)
-
feat: replace slow-write tokens with MCP request state, change to MRTR from new mcp spec (#388, @kingpanther13)
- Long Rule Machine and native-app writes now continue automatically using the current MCP protocol instead of timing out at the cloud relay boundary.
- Write tools respond noticeably faster over the cloud relay: per-call bookkeeping no longer performs hub-database round trips on every write.
- Package updates now acknowledge quickly, run in the background, reject duplicate deployment attempts, and publish a request-correlated final outcome.
- Concurrent writes remain globally capped to protect the hub from clients that issue large batches or parallel tool calls.
-
build(deps): bump net.bytebuddy:byte-buddy from 1.18.10 to 1.18.11 in the gradle-dependencies group across 1 directory (#368, @app/dependabot)
-
chore: renormalize gradlew.bat to match its eol=crlf attribute (#370, @kingpanther13)
-
ci: stop rebooting the test hub, move device fixtures off the app (#371, @kingpanther13)
-
docs: pin mcp SDK <2.0.0 in the Claude Desktop mcp-proxy config (#372, @kingpanther13)
-
feat: context snapshot, device-state filters, MCP resources, bearer-auth docs (#374, @kingpanther13)
- hub_list_devices: new format='context' — a one-call, token-cheap plain-text snapshot of the whole house (mode + one line per device with room, capabilities, and current attribute values)
- hub_list_devices: new filters — roomFilter, onlyOn (what's on right now), changedSince (what changed since a timestamp), attributeNames
- MCP resources support: resources/list + resources/read serve the tool-guide sections and a live house context snapshot (hubitat://context-summary) for clients that prefer resources over tool calls
- The access token can now also be sent as an Authorization: Bearer header (works on both the local endpoint and the cloud relay)
- test: close review findings on the conformance harness (#369, @kingpanther13)
- Fixed a hub load-limiter recovery path in the test suite that could undo the write it was recovering, and corrected the
hub_set_rule_pausedguidance that said an app disable/enable clears the limiter — only a hub reboot resets the platform's load counters.
- Fixed a hub load-limiter recovery path in the test suite that could undo the write it was recovering, and corrected the
- test: add conformance harness — official MCP SDK client + wire-schema validation (#367, @kingpanther13)
- feat: add MCP 2026-07-28 support (server/discover, modern-transport validation, cache hints) (#365, @kingpanther13)
- Full MCP 2026-07-28 spec support: new
server/discovermethod, stateless per-request protocol versioning with standard-header validation, and cache hints ontools/list— fully backward compatible; existing clients continue on the classicinitializepath unchanged (now negotiating up to2025-11-25) - Fixed
hub_update_app: enabling OAuth programmatically and thetriggerUpdatedlifecycle refresh both work again on current firmware, and the refresh now guarantees a configured instance's settings — including device selections — survive - Fixed silent failures in device show-on-home, default-state, room, label, and replace operations, WiFi/static-IP network config, and several Z-Wave/Zigbee/Matter radio operations (a URL-building bug made the hub reject these requests on current firmware)
- New advanced settings:
Originrequest validation (log-only by default, optional strict enforcement, custom allowlist)
- Full MCP 2026-07-28 spec support: new
- fix: heartbeat-based e2e dead-man + deferred opToken result uploads (#363, @kingpanther13)
- Hub variable operations (create/delete variables and connectors) respond ~0.5-1s faster: the post-write verification now checks immediately instead of sleeping first.
- fix: backup gate and create-backup confirmation read the hub's real backup list (#362, @kingpanther13)
- Destructive tools no longer refuse with "BACKUP REQUIRED" when a real hub backup exists: the safety gate now also accepts any backup in the hub's own local backup list (scheduled backups count), not just backups created through hub_create_backup.
- hub_create_backup now recognizes completion as soon as the backup file appears, even while a Hub Protect cloud upload is still running, instead of failing after a minute of waiting.
- A call rejected for invalid arguments no longer burns its opToken — fix the arguments and re-issue with the same token.
-
chore: disable sunset Gemini Code Assist reviews (#357, @kingpanther13)
-
build(deps): bump actions/setup-python from 6 to 7 in the github-actions group (#358, @app/dependabot)
-
feat: expose live enabled/paused/disabled status in hub_list_rules (#359) (#360, @kingpanther13)
- Rule listings now report each Rule Machine rule's live status — active, paused, or disabled — plus whether a rule's required expression is currently blocking it, so you can ask "which of my rules are paused?" and get a direct answer
- Visual Rules Builder rule listings now report each rule's paused state and no longer show raw HTML in rule names
- feat: fold hub_get_op_result into the opToken write-retry flow (#356, @kingpanther13)
- Recovering a slow call after a dropped response is now one simple rule for every tool, reads included: re-issue the same call with the same
opToken(the token alone is enough) — the hub refuses duplicates while running and replays the finished result. The separatehub_get_op_resulttool is retired. hub_update_packagenow refuses a concurrent second deploy while one is running, so a client timeout can no longer cause a double deploy.- Parallel tokened calls no longer risk losing each other's recovery records.
- Rule-mutation responses no longer report failure — or auto-restore a backup — when only the post-operation health probe could not be read: health verdicts now separate couldn't-check (
unreadable: true) from checked-and-broken. - New advanced setting: LAN time budget (default off) lets slow multi-step rule edits pause-and-resume on LAN connections whose client timeout is shorter than the edit.
- Recovering a slow call after a dropped response is now one simple rule for every tool, reads included: re-issue the same call with the same
- fix: one-time reset of publishOutputSchemas to OFF on update (issue #354) (#355, @kingpanther13)
- The advanced "Publish tool output schemas" setting is reset to OFF once on update. When ON it makes strict MCP clients such as Claude Desktop reject every tool call (issue #354). If you genuinely rely on it, re-enable it after updating — your choice is kept from then on.
- Clearer warnings on that setting everywhere it can be flipped: the Advanced settings page, the self-admin tool, and the tool guide now all say to leave it OFF if you use Claude Desktop.
- fix: harden RM authoring against relay-budget drop, RE-rule export, and offset waitEvents slot (#352, @level99)
- Bulk rule authoring over the cloud relay now returns a resumable result when it
- Clearer, accurate error when exporting a rule that has a Required Expression.
- Fixed "Wait for Events" failing on a rule that has a Required Expression or
- fix: fail loud on a non-existent target rule id + RM authoring discoverability (#349, @level99)
- Rule Machine authoring now fails loud, before any write, when a run-rule / cancel-timers / pause-resume-rule / set-private-boolean action targets a rule that doesn't exist -- validated against the live rule list (so a non-rule app id or a stale id is caught) instead of silently baking a dangling reference. The error names the id and points at
hub_list_rules. A bad target in areplaceActionsbatch is rejected before the existing actions are cleared. - Clearer docs: reading a rule's config while authoring, and the rule-level Required-Expression / function-mode flags.
- Rule Machine authoring now fails loud, before any write, when a run-rule / cancel-timers / pause-resume-rule / set-private-boolean action targets a rule that doesn't exist -- validated against the live rule list (so a non-rule app id or a stale id is caught) instead of silently baking a dangling reference. The error names the id and points at
- feat: opToken response replay + cloud-relay self-budget for slow writes (#350, @kingpanther13)
- Slow operations no longer get lost over the Hubitat cloud relay: pass an
opTokenon big writes and fetch the finished result with the newhub_get_op_resulttool if the connection drops mid-call. - Retrying a write with the same
opTokenis now safe — the hub returns the original result instead of running the operation twice. - Large rule edits made through the cloud relay now pause safely before the relay's time limit and return exact instructions to continue; local-network calls are unaffected.
- Slow operations no longer get lost over the Hubitat cloud relay: pass an
-
ci: honor manual re-runs in lane-gate (attempt > 1 overrides the frozen no-op decision) (#347, @kingpanther13)
-
feat: fail-loud non-condition/unconfigurable capabilities + RM authoring discoverability (#346, @level99)
- Rule Machine authoring now fails loud instead of quietly committing a broken or incomplete condition when you pick a capability it can't author:
- "Last Event Device" (which belongs in actions, not conditions) now returns a clear error instead of creating a broken condition.
- "Lock codes" (which needs a lock device and a code name the tool can't set) now points you at the Rule Machine UI instead of creating an empty condition.
- Creating a rule now returns its id, so you can run, pause, or set the private boolean on the new rule in the next step without a separate lookup.
- A clearer recovery hint when the hub's load limiter blocks a pause or resume.
- Rule Machine authoring now fails loud instead of quietly committing a broken or incomplete condition when you pick a capability it can't author:
-
fix: fail-loud parity for silent-drop RM authoring shapes (#344, @level99)
- Fixed native Rule Machine authoring so several trigger/action/condition shapes that used to be silently ignored now either work or return a clear error:
- An "on any change" trigger (
comparator:'*changed*') now works on every device-state sensor (contacts, locks, water/smoke/CO sensors, shades, …) instead of being ignored and firing on every event. - Common mistakes — a mis-wrapped condition list, using
state:whereaction:belongs on a switch/fan/shade, an unknown capability name, a comma-joined mode string, or a bare start-time — now return a helpful error naming the correct shape instead of quietly doing the wrong thing.
- An "on any change" trigger (
- Fixed native Rule Machine authoring so several trigger/action/condition shapes that used to be silently ignored now either work or return a clear error:
-
docs: note the follow-up-label recovery for a release cut without its tag (#345, @kingpanther13)
- fix: return structuredContent when outputSchema is advertised so spec-validating clients stop failing successful calls (#343, @kingpanther13)
- Fixed successful tool calls being reported as generic failures to strict MCP clients (Claude Desktop, mcp-proxy) when the advanced "Publish tool output schemas" setting is enabled — results now include the structured content the spec requires
- Bug reports from hub_report_issue now include the tool-surface mode (gateway/flat) and output-schema setting, and no longer contain internal library line markers
- Tool descriptions no longer show internal library line markers on hubs installed via HPM bundles
- The "Publish tool output schemas" setting now lives under Advanced settings with a clear keep-it-OFF recommendation (it remains OFF by default)
- feat: HSM action, switch onlyOn, waitEvents duration, contains comparator, and device-list partial fix (#340, @level99)
- Added:
hub_set_rulecan now set Hubitat Safety Monitor as a rule action — arm away/home/night, disarm, re-arm, disarm all, arm all HSM rules, or cancel all alerts. - Added: the switch on/off action gained the "command only switches that are on" option.
- Added: a "and stays that way for N" hold duration on a
Wait for Eventsper-event wait. - Added: the string
*contains*comparator for Variable and Custom Attribute conditions (negate with the condition's NOT toggle). - Fixed: a fully-committed shade / switch / lock / fan action no longer reports a spurious "partial" result that could nudge an agent into an unnecessary repair pass.
- Added:
- fix: route waitEvents Mode events to the mode picker, not tstate (#337, @level99)
- Fixed: a
Modeevent inside aWait for Eventsaction is no longer silently dropped —
- Fixed: a
- feat: extend the dashboard tools to cover legacy Hubitat® Dashboards (#339, @kingpanther13)
- The dashboard tools now cover the legacy Hubitat® Dashboard app, not just Easy Dashboards: list and inspect them (full tile layout), create new ones, edit tiles granularly (add/move/resize/remove), change grid/colors/fonts, rename, authorize devices, clone, and delete — all through the same six dashboard tools.
-
test: reframe BAT test_prompts goal-first — state goals, not tool names (#335, @kingpanther13)
- Acceptance-test suite improvements only — no functional changes to the app.
-
fix: consolidate the consumer skill and TOOL_GUIDE into one reference (#336, @kingpanther13)
- Claude.ai Agent Skill slimmed down: it now defers all per-tool reference to the live server (
hub_get_tool_guide/hub_search_tools), so an installed skill no longer goes stale as the tool surface evolves. Re-download the skill zip from the repo to pick up the new version.
- Claude.ai Agent Skill slimmed down: it now defers all per-tool reference to the live server (
-
fix: gateway-path parity bugs surfaced by making gateway mode the primary e2e invocation path (#338, @kingpanther13)
- Gateway-routed calls now report errors against the actual failing sub-tool instead of the gateway name.
- Missing-parameter errors now have the same JSON-RPC shape whether a tool is called through a gateway or directly.
- Schema probes (
guide/discover) onhub_set_native_appnow work through gateways withoutconfirm, matchinghub_set_rule. - Tools blocked by Read/Write masters or per-tool overrides no longer leak their parameter schemas through gateway missing-parameter errors.
- Server instructions now match the catalog mode: flat-catalog clients are no longer told to call gateway tools.
-
build(deps): bump the gradle-dependencies group with 2 updates (#332, @app/dependabot)
-
fix: fail loud on wrong-shaped addTrigger specs instead of silently committing a broken trigger (#334, @level99)
hub_set_rulenow returns a clear, actionable error when a rule trigger is given a
- feat: add bypassDeviceAllowlist toggle for full device-tool parity on unlisted devices (#330, @level99)
- New Bypass Device Allowlist setting (default OFF). When enabled, the MCP server can read, command, reconfigure, and read events for any device on the hub by id — ignoring the device-selection list — for automated testing or whole-hub control. Settable from the app's Device Access page or via
hub_update_mcp_settings. This removes the device-selection boundary; enable it only if you intend to expose the whole hub.
- New Bypass Device Allowlist setting (default OFF). When enabled, the MCP server can read, command, reconfigure, and read events for any device on the hub by id — ignoring the device-selection list — for automated testing or whole-hub control. Settable from the app's Device Access page or via
- refactor: shrink flat tools/list catalog + Anthropic best-practice tool audit (#296) (#331, @kingpanther13)
- Tool definitions streamlined: tighter descriptions and clearer parameter schemas, with deep reference detail available through
hub_get_tool_guide. Frees significant headroom in the flat tool list. - Several tool parameters were renamed for consistency (e.g.
hub_set_hsmmode→armCommand, the dashboard toolsid→dashboardId,hub_delete_itemid→item_id). MCP clients pick these up automatically on the next tool-list refresh.
- Tool definitions streamlined: tighter descriptions and clearer parameter schemas, with deep reference detail available through
-
docs: correct device-edit endpoint notes — drop unsupported firmware-causation claim (#328, @kingpanther13)
-
feat: hub_set_system_settings dark mode + network config; rename hub_call_destructive_radio → hub_call_destructive_ops (adds network/cloud) (#329, @kingpanther13)
hub_set_system_settingscan now set the admin-UI dark mode and the hub's network configuration — DHCP, static IP, WiFi, and Ethernet autonegotiation.- The destructive tool
hub_call_destructive_radiois renamed tohub_call_destructive_opsand now also performs network disconnect (WiFi/Ethernet) and cloud controller disable/enable, in addition to the existing radio reset/firmware actions. Itsradioparameter is nowtarget.
- feat: device-edit properties, full driver catalog, create-from-driver, and compatible-devices lookup (#327, @kingpanther13)
- Device updates can now set Show on Home page, the Status-column attribute (default current state), and device tags.
- List drivers can now return the full driver-type catalog (built-in, virtual, and user), not just user-installed drivers.
- New: create a device from a driver type — for LAN/integration/software drivers that have no pairing flow.
- New: search Hubitat's compatible-device catalog for supported models and their pairing / exclusion / factory-reset instructions.
-
fix: report distinct tool count from hub_search_tools, not gateway-membership rows (#323, @kingpanther13)
- Fixed
hub_search_toolsreporting an inflatedtotalToolsSearched— it now counts each tool once instead of once per gateway it appears in.
- Fixed
-
ci: fail-fast bind-check in e2e deploy (#321, @kingpanther13)
-
feat: hub_manage_dashboards — Easy Dashboard CRUD (#259 item 9) (#324, @kingpanther13)
- Manage Hubitat Easy Dashboards from MCP: list, view, create, update, clone, and delete dashboards (deleting requires confirmation).
- HPM bundle is now compressed, so package updates download smaller and faster.
- feat: let hub_update_mcp_settings manage the MCP device-access scope (selectedDevices) (#318, @level99)
hub_update_mcp_settingscan now manage the MCP server's device-access list (selectedDevices) — add, remove, or replace which devices the server is authorized to see and control, from an agent instead of the app's device-selection page (Developer Mode).
- feat: best-practice acknowledgment gate and reactive hints for write tools (#317, @kingpanther13)
- Best-practice acknowledgment gate is now ON by default. Write tools require the AI to first read the in-app best-practice guide (
hub_get_tool_guide, sectionbest_practice_reference) and pass the key it publishes. Turn it off in the new Best-Practice Guidance settings section (or viahub_update_mcp_settings) for clients that can't carry the extra context. Reads, the guide, and the settings tool are never gated, so the AI can never lock itself out. - Failed write tools now include a best-practice hint pointing at that specific tool's guide section, in both flat and gateway mode, to help the AI recover from the failing tool's reference.
- Best-practice acknowledgment gate is now ON by default. Write tools require the AI to first read the in-app best-practice guide (
- feat: hub-database backup management (list, restore, delete, schedule, upload) (#316, @kingpanther13)
- New hub-database backup management: list, restore, and delete local & cloud hub backups, set the automatic-backup schedule, and restore a backup uploaded from a URL (e.g. migrating from another hub) — grouped under the new "Manage Backups" tool group.
hub_create_backupcan now also set the automatic-backup schedule.
- feat: add absolute since bookmark filter to hub_list_device_events (#314, @level99)
hub_list_device_eventsgains asinceparameter — an absolute timestamp (ISO-8601 or epoch-ms) that returns only events after that moment, complementing the existing relativehoursBackwindow. Ideal for change-watching loops: bookmark a returned event date and pass it straight back assinceto fetch only what's new.
- feat: hub_set_system_settings — set hub timezone, units, location, name (#315, @kingpanther13)
- New
hub_set_system_settingstool — set the hub's time zone, temperature units (F/C), location (latitude/longitude/zip), and hub name. Changing the time zone reboots the hub and requiresconfirm: true.
- New
- feat: fold hub_set_rule to a flat self-gateway + trim RM tool/gateway prose (#310, @kingpanther13)
hub_set_ruleis now far smaller in flat (non-gateway) mode — it presents a compactoperationselector and returns each operation's argument schema on demand instead of one giant schema.- Leaner in gateway mode too —
hub_set_rule's schema was de-duplicated/trimmed and the Rule Machine gateway descriptions (hub_manage_rule_machine,hub_manage_native_rules_and_apps) were tightened, so the tool surface costs fewer tokens for every client. How you author rules is unchanged. hub_set_app_disabledparameter renamedapp_id→appIdfor consistency (cached clients refresh their tool list on update).- Clearer
hub_set_native_appdocumentation on which app types can be created vs only edited.
- feat: full mode-management surface (hub_manage_mode + hub_set_mode_manager) (#313, @kingpanther13)
- Full location-mode management. New
hub_manage_modecreates, renames, deletes, and activates hub modes (Day/Night/Away/…), andhub_set_mode_managerconfigures the built-in Mode Manager (which manager runs + its per-mode automation conditions).hub_list_modesnow also reports each mode's icon and the active Mode Manager. (The oldhub_set_modeis folded intohub_manage_mode'sactivateaction.)
- Full location-mode management. New
- feat: multi-device convergence on hub_get_device_attribute (deviceIds + mode any/all) (#306, @level99)
hub_get_device_attributecan now poll multiple devices at once: passdeviceIds(a list, instead of a singledeviceId) plusmode: "any"or"all"to wait until any/all of the devices reach the condition. Returns a compact per-device result plusconvergedCount. Up to 20 devices per call.
-
ci: fix lease double-booking + FIFO-order the e2e queue (#308, @kingpanther13)
-
ci: re-run e2e only when a full-run label flips the lane (not on every toggle) (#309, @kingpanther13)
-
feat: add hub_call_device_replace (re-point a device onto replacement hardware) (#307, @kingpanther13)
- New
hub_call_device_replace— replace a dead or failing device's hardware while keeping the device's id and all of its automations/dashboard tiles intact (the original device adopts the new hardware). Call withlist_options=trueto see the hub's compatible replacements first, then apply withconfirm=true. Different from "swap", which moves a device's references onto a different existing device.
- New
- feat: enable OAuth on apps via hub_update_app (#305, @kingpanther13)
hub_update_appcan now enable/configure OAuth on an app (oauth={enabled:true,…}) — turn OAuth on for an app and get its generated client id/secret back, without the manual Apps Code UI step. Returns the credentials underresult.oauth, and preserves an already-enabled app's creds when you don't pass them.
-
chore: clarify the hub-var refresh-failure log message (#302, @kingpanther13)
-
build(deps): bump actions/checkout from 6 to 7 in the github-actions group (#304, @app/dependabot)
-
feat: comparator + stableForMs read-side convergence (hub_get_device_attribute + waitFor) (#303, @level99)
hub_get_device_attributeand thewaitForoption onhub_call_device_commandcan- A new optional
stableForMsdebounce waits until the value has held the target for
- perf: cut RM condition-build hub round-trips + two-lane e2e CI (closes #237) (#301, @kingpanther13)
- Rule Machine condition building no longer times out over the cloud relay. Building a multi-condition Required Expression (or a condition that compares against another device) is now reliable — the wizard reuses each write's returned page, caches reads, and defers a one-time internal cleanup to when it's actually needed, cutting the per-build hub round-trips. A multi-condition AND / sub-expression build that previously failed intermittently now succeeds clean.
- feat: hub_call_device_command: confirm the resulting device state (state snapshot + waitFor) (#300, @level99)
hub_call_device_commandnow reports the device's state right after sending a- New optional
waitForoption waits until the device actually reaches the
- fix: e2e-surfaced tool fixes (404 degrade, button-rule page, zigbee, watchdog jobs) + e2e runner/suite refactor (#298, @kingpanther13)
- Fixed
hub_get_app_config/hub_list_app_pagesreturning an opaque error instead of a clean - Fixed Button Rule editing leaving the rule on a missing page ("Cannot find page 'mainPage'").
- Fixed the Zigbee settings error wrongly suggesting a credential problem when the hub has no Zigbee radio.
- (Internal: e2e test-runner + suite refactor/speedups; no user-facing behaviour change.)
- Fixed
- feat: rule-local variable write, list, and remove (#294, @level99)
- Rule Machine rule-local variables are now fully manageable from the MCP: an action can write one (
setLocalVariable), you can list a rule's locals (hub_list_rule_local_variables), and you can remove one (removeLocalVariable). Previously a local could only be created and read — not written by an action, listed, or removed without deleting the rule. hub_list_rule_local_variablesreturns each local's RM-internal type token (integer/bigdecimal/string/boolean/datetime); the tool guide maps these back to theaddLocalVariableinput labels (Number/Decimal/ …).
- Rule Machine rule-local variables are now fully manageable from the MCP: an action can write one (
- feat: add hub_update_firmware to install pending hub firmware (#297, @kingpanther13)
- New
hub_update_firmwaretool: install the hub's pending platform/firmware update (downloads, installs, and reboots the hub), confirm-gated, with astatusOnlyprogress poll. hub_get_update_statushas been removed — its checks moved intohub_get_info: pending hub firmware is always underplatformUpdate, and the MCP-server app-version check is nowhub_get_info(includeAppUpdate=true)(underappUpdate).
- New
- feat: hub_manage_radio gateway — full Z-Wave/Zigbee/Matter radio control (#295, @kingpanther13)
- New radio management tools (the
hub_manage_radiogateway): configure the Z-Wave, Zigbee, and Matter radios; run mesh repair, device inclusion/exclusion, node maintenance, and Matter commissioning; read per-node state, lifecycle status, channel scans, SmartStart entries, and firmware-eligibility — all from the MCP. Network/fabric reset and firmware flash for each radio are available (confirm-gated) under the destructive-ops gateway. hub_get_radio_detailsnow reads Matter fabric/device details (radio='matter') plus richer Z-Wave/Zigbee read folds.hub_get_device_healthcan now run a hub traceroute to an IP or a WAN speedtest, alongside its existing ICMP ping.
- New radio management tools (the
-
fix: lean hub_update_mcp_settings description to restore flat tools/list budget (#293, @kingpanther13)
- No user-facing change — internal: keeps the flat
tools/listunder the hub's 124 KB response cap
- No user-facing change — internal: keeps the flat
-
feat: bulk form for hub_create_variable + RM variable-source doc notes (#287, @level99)
hub_create_variablecan now create several variables in one call — passvariables=[{name, type, value}, ...](the single form still works). Each item succeeds or fails independently, with per-item status in the result.hub_create_variablenow rejects an empty initial value for a String variable (an empty String silently fails to persist on the hub).- Clarified RM docs: fan speed is a fixed enum (use
runCommandfor a variable-driven speed), and the supported numeric-condition comparison shapes (device-vs-value, device-vs-another-device, variable-vs-variable).
-
ci: stop the e2e required check from blocking docs-only PRs (also completes hub2-source inventory) (#286, @kingpanther13)
- No user-facing change — internal CI reliability (docs-only PRs are no longer blocked by the e2e
-
docs: fix Claude Desktop MCP setup instructions (mcp-remote bridge) (#288, @kingpanther13)
- Fixed the Claude Desktop setup instructions. The old
"type": "url"config does not work in Claude Desktop — the README now shows how to bridge the local and cloud endpoints withmcp-remote, and notes that adding the server in Claude.ai makes it appear in Claude Desktop automatically. - Corrected the Claude Code config example to use
"type": "http".
- Fixed the Claude Desktop setup instructions. The old
-
docs: choose the Claude Desktop bridge proxy by OS (#292, @kingpanther13)
- Clarified Claude Desktop setup: use
mcp-proxy(viauvx) on Windows andmcp-remote(vianpx) on macOS. The previous instructions only documentedmcp-remote, whose tool calls fail in Claude Desktop on Windows.
- Clarified Claude Desktop setup: use
-
fix: make outputSchema publishing opt-in (default off) so strict MCP clients work (#291, @kingpanther13)
- Fixed MCP tool calls failing with
-32600("has an output schema but did not return structured content") in strict clients such as Claude Desktop — tool output schemas are no longer advertised to clients by default. - Added an advanced "Publish tool output schemas" setting (default off). Enable it only if your client wants
outputSchemaadvertised; note that strict clients which require structured content will then reject tool calls.
- Fixed MCP tool calls failing with
-
feat: all-hub device scope, app enable/disable tool, and mesh topology (#257) (#289, @kingpanther13)
hub_list_devicesnow supportsscope='all'to list every device on the hub — including ones not- New
hub_set_app_disabledtool: disable or re-enable any installed app (Room Lighting, Notifier, hub_get_radio_detailscan now include the Z-Wave/Zigbee mesh route map (include_topology=true)
-
refactor: route the variable-name enum pickers through the canonical reader (#281, @level99)
- Internal refactor: Rule Machine variable-name pickers now share a single option-list reader. No user-facing behaviour change.
-
feat: add walkStep drive mode and pare hub_set_rule prose into the tool guide (#280, @kingpanther13)
hub_set_ruledescriptions are leaner — the full trigger/action/expression reference now lives inhub_get_tool_guide(section='set_rule_reference')(fetch inline withguide:true), keeping the tool catalog under the response cap.- New
walkStepdrive mode: pass{operation:'drive', steps:[...]}to run a whole wizard sequence in one call instead of step by step. hub_create_custom_rulenow declares its trigger/condition/action types as schema enums for better validation hints.
- refactor: extract native Rule Machine + classic-app tools into McpNativeRulesLib (#279, @kingpanther13)
- Internal modularization: the native Rule Machine and classic-app tools now ship from a bundled
#includelibrary. No change to tool behaviour, names, or schemas.
- Internal modularization: the native Rule Machine and classic-app tools now ship from a bundled
- feat: prefer compiled rule state for hub_get_rule_health (RM + Visual Rules + classic apps) (#276, @kingpanther13)
hub_get_rule_healthnow covers Visual Rules Builder rules AND classic apps (Button Controller, Basic Rule) in addition to Rule Machine, and reports an authoritative broken verdict from each engine's compiled state (RMbroken/ graphvalidationErrors) instead of only matching rendered text — more reliable broken-rule detection. Newsourceparameter andruleFormatfield show which path/engine answered.- Rule Machine, Visual Rules, and classic-app edit responses now include the rule's health report (success and failure), so a broken rule is flagged immediately.
- feat: add device-attribute and variable-math source modes to setVariable (#277, @level99)
- Feature: the Set Variable action can now read its value **from a device
- fix: align replaceRequiredExpression result-contract docs with behavior + post-delete safety net (#278, @kingpanther13)
hub_set_rulereplaceRequiredExpression: corrected the result-field documentation (a rejected trailingupdateRuleauto-restores rather than leaving a committed-but-not-live expression, and auto-restore can reportrequiredExpressionRestored:false), and added a backstop so an unexpected post-delete error reports the restore outcome honestly instead of "no changes made".
- feat: add replaceRequiredExpression shortcut to hub_set_rule (#270, @level99)
- Add
replaceRequiredExpressiontohub_set_rule— change a Rule Machine 5.1 Required Expression in place (one call, no clone), with validate-before-delete and auto-restore-on-failure safety.
- Add
- fix: enum-shadowing Custom Attribute + no-value changed comparator (#195) (#271, @level99)
- Fix: a Custom Attribute trigger/condition whose attribute name shadows
- ci: make the e2e bundle skip honest — verify/heal the hub, and re-target the restore at current main (#275, @kingpanther13)
- CI-only: no change to the app, tools, or the HPM package.
- The e2e pipeline now verifies the test hub's libraries match the PR on every run and repairs the hub automatically when a previous run left it behind.
- CI-only: no change to the app, tools, or the HPM package.
- ci: unify bundle delivery on the bundle-artifacts branch so e2e installs exactly what HPM serves (#274, @kingpanther13)
- The libraries bundle is now delivered from the same continuously-verified pipeline the test suite installs from on every run — each release's bundle is the exact artifact that passed live end-to-end testing. No action needed; HPM installs and updates work as before.
-
refactor: split every remaining tool domain into #include libraries with co-located per-tool metadata (#209) (#269, @kingpanther13)
- Internal modularization: the server's tools now ship as 18 #include libraries delivered by the existing libraries bundle — no functional or tool-surface change; HPM updates install everything with the same single Update click.
- hub_update_package (Developer Mode) can now install an unmerged branch or commit with its exact library code, resolved from per-push bundle artifacts, and reports whenever it had to fall back to a committed zip.
-
fix: rebuild-bundle pushes with the release deploy key + carry the rebuilt 18-library zip (#273, @kingpanther13)
- Internal modularization (#269): the server's tools now ship as 18 #include libraries delivered by the existing libraries bundle — no functional or tool-surface change; HPM updates install everything with the same single Update click.
- hub_update_package (Developer Mode) can now install an unmerged branch or commit with its exact library code, resolved from per-push bundle artifacts, and reports whenever it had to fall back to a committed zip.
- Fixed: after the modularization merge, the libraries bundle on main briefly lagged the app — a manual install in that window could fail to compile. The bundle is current again.
- fix: VRB-first rule guidance; rebuild e2e restore on the HPM importUrl path (#268, @kingpanther13)
- Clearer guidance steering AI clients to the Visual Rules Builder tools for most automations — including if/then/else rules — with Rule Machine reserved for complex logic
- removeAction/removeTrigger now recover automatically from Rule Machine's silently dropped first delete click (verified re-click), completing in ~4-7s instead of timing out with an error
- hub_create_backup now triggers the backup asynchronously and confirms via status polling — the old in-app download of the multi-MB archive could trip Hubitat's per-app load limiter
- feat: per-app JSON reads, direct-alias app resolution, and hub_call_device_swap (#267, @kingpanther13)
- New: swap a device across every app and rule that references it in one operation (
hub_call_device_swap), with a compatibility check and blast-radius preview - App configuration can now be read in a fast identity-only summary mode
- Event history can now be read per app or rule, not just per device or location
- Hub variable management no longer needs a slow first-time discovery scan on hubs that hide the Hub Variables app
- New: swap a device across every app and rule that references it in one operation (
-
docs: list string/decimal for runCommand parameter types (#266, @level99)
- Docs: the RM action-subtype reference now lists
string/decimal(in addition tonumber) for Run Custom Action parameter types.
- Docs: the RM action-subtype reference now lists
-
feat: add Visual Rules Builder tools to the rule machine gateway (#265, @kingpanther13)
- New Visual Rules Builder tools: create, read, update, pause, and delete VRB rules directly — the simplest way for an AI to build basic automations (one JSON write, no wizard)
hub_get_visual_rulelists VRB rules or reads one rule's full definitionhub_set_visual_rulecreates or edits a rule (both the classic and the new graph format are supported)hub_delete_visual_rulesafely deletes a VRB rule and returns its definition for recovery
- Backups now cover Visual Rules: deleting one through the generic delete tool snapshots its full definition, and
hub_restore_backupcan bring the rule back — definition, name, and pause state included - AI clients are now steered to prefer Visual Rules Builder for simple automations, falling back to Rule Machine for complex logic
- Creating a Visual Rule through
hub_set_native_appnow returns a clear pointer to the new tool instead of silently failing
- New Visual Rules Builder tools: create, read, update, pause, and delete VRB rules directly — the simplest way for an AI to build basic automations (one JSON write, no wizard)
- feat: friendly tool titles, descriptive overrides menu, and the full four-hint annotation surface (#264, @kingpanther13)
- Tools now show friendly names (for example "List Devices") in MCP clients such as claude.ai, instead of only bare names
- The Advanced: Per-tool Overrides menu now describes every tool and gateway — friendly name, read/write marker, and a short description — and long entries wrap properly, including on phones
- Tools now advertise retry-safety and internet-access hints to MCP clients, and retry edge cases were fixed (no duplicate backup files, no lost undo snapshots)
- Tool search understands friendly phrasing and stays current after updates
- feat: update app/driver code via saveOrUpdateJson instead of legacy ajax/update (#263, @kingpanther13)
- App and driver code updates now use the same modern hub endpoint as code creation, with compile errors reported verbatim
- An update that gets no response from the hub is now reported as unverified instead of assumed successful
- Restoring a code backup of the MCP server itself now reports its outcome honestly and records it for later inspection
- App and driver code updates now use the same modern hub endpoint as code creation, with compile errors reported verbatim
- Wire device-relative compareToDevice to the real RM 5.1.8 control (isDev_) (#262, @level99)
- feat: register basic_rule, add Button Rule creation + walkStep to the native-app tool (#260, @kingpanther13)
- Basic Rules: you can now create a Basic Rule through the MCP, and editing one no longer breaks its page.
- Button Rules: create a button rule directly under its Button Controller in one step — its trigger is set up for you; add its actions next. The required built-in parent app is installed automatically if missing.
- Fixed: editing a classic app's device settings (e.g. a Button Controller's button device) no longer silently reverts the assignment.
- The step-by-step page walker (
walkStep) now works on any classic app, not just Rule Machine rules.
- feat: hub_update_package full HPM-repair deploy, top-level dev-mode tool (#261, @kingpanther13)
- Developer Mode —
hub_update_packagenow does a full HPM-style repair at a git ref: it installs the library bundle and redeploys the app(s), overriding whatever is installed (the same one-click way Hubitat Package Manager does), pinned to the ref you give it.- It is now its own top-level tool when Developer Mode is on (no longer inside the
hub_manage_mcpgateway). - Deploys the parent app, the child rule app, and the library bundle in one call;
dryRun=truepreviews the plan with no changes.
- It is now its own top-level tool when Developer Mode is on (no longer inside the
- Developer Mode —
- fix: enum-recognized Custom Attribute false-partial across all RM 5.1 wizard surfaces (#244, @level99)
- Fixed: creating a Rule Machine rule with a Custom Attribute trigger or condition on an enum-recognized attribute (e.g.
switch,motion,contact,lock) no longer reports a false "partial" result — the selected value lands correctly and the build is clean across triggers, conditional triggers, Required Expressions, and IF-THEN action conditions. - Fixed: a Custom Attribute condition on a standard capability (e.g. a Temperature device's custom attribute), and a device-relative (
compareToDevice) condition, now keep their comparator — including!=/==, which are written as the operator glyphs RM expects. - Added: guidance in the addAction reference for activating a Scene / Room-Lighting group (RM 5.1 has no native activate-scene action — drive the activator device's switch instead).
- Fixed: creating a Rule Machine rule with a Custom Attribute trigger or condition on an enum-recognized attribute (e.g.
-
chore: vendor classic-UI dynamicPage engine as RM wire-format reference (#253, @kingpanther13)
- No user-facing changes — internal developer reference assets (vendored hub admin-UI source) and docs only.
-
ci: speed up the watchdog e2e ~23% (bundle-only deploy, batched fixtures, deferred deletes) (#251, @kingpanther13)
- None — internal e2e / CI tooling and test-suite changes only. The dead-man watchdog is test-hub-only and never shipped; no user-facing or server behaviour change.
-
docs: point AGENTS.md at resources/hub2-source as the reverse-engineering reference (#255, @kingpanther13)
- No user-facing changes — contributor docs only.
-
feat: surface pending hub firmware update + health alerts from /hub2/hubData (#256, @kingpanther13)
hub_get_infoandhub_get_update_statusnow report a pending Hubitat firmware/platform update (separate from the MCP server-app update).hub_get_infonow reports Safe Mode and, viaincludeHealthAlerts=true, the hub's full health-alerts block (radio offline, backup failures, low memory, DB bloat, weak mesh);hub_get_metricsreturns the full block too.
-
ci: auto-run e2e on trusted fork PRs, gate other contributors; lease waits for a busy hub (#246, @kingpanther13)
- Internal CI change — no user-facing impact.
-
ci: e2e dead-man watchdog v2 as a 2nd MCP server — drive deploy through it (kills self-update 504s) (#248, @kingpanther13)
- Internal/CI only — no user-facing change. The dead-man watchdog and these CI scripts are e2e-test-hub
-
docs: standalone-watchdog e2e architecture + pull_request_target trigger gotcha (#249, @kingpanther13)
- Internal/docs only — no user-facing change.
-
feat: modularize Rooms + bundle tools into #include libraries + bundle-management tools (#209) (#247, @kingpanther13)
- New bundle-management tools.
hub_list_bundles,hub_delete_bundle, andhub_export_bundlelet you see, remove, and back up the Bundle-Manager bundles installed on your hub (distinct from Libraries Code). - Internal: the Room tools and all bundle tools now live in
#includeGroovy libraries (issue #209 modularization), delivered by the package bundle. No change to how any existing tool behaves.- HPM installs/updates the libraries automatically in one Install/Update click (they appear under Libraries Code). Manual (non-HPM) installers must now install the libraries before the parent app — see the updated README.
- New bundle-management tools.
- feat: installAsUserApp install-commit fix + #include smoke test + e2e dead-man watchdog (#209) (#243, @kingpanther13)
- Fix:
hub_create_app/hub_create_drivernow create app/driver code on current Hubitat firmware (2.5.x) — they use the working save endpoint; the previous path silently created nothing. - Fix:
hub_create_app(installAsUserApp:)now actually completes the "Add User App" install — it commits the config page soinstalled()/initialize()run and the new instance's schedules/subscriptions register. The old call left an uninstalled shell while reporting success. - New:
hub_install_bundleinstalls an HPM bundle (.zip) the way Hubitat Package Manager does (part of the #209 modularization groundwork). - Internal/CI only (not installed on your hub): a temporary
#includesmoke test — a throwawaysmokeTestMarkerappears inhub_get_infoand is removed once the #209 split is validated — plus self-deploy compile-error capture and an e2e-hub-only dead-man watchdog.
- Fix:
- feat: hub_update_package dev tool — one-call app+library deploy at a git ref (#209) (#242, @kingpanther13)
- New Developer-Mode tool
hub_update_package— with Developer Mode on, deploys the app and all of its libraries together from a git ref in one step (a dev/CI convenience for the upcoming code-modularization work). Hidden unless Developer Mode is enabled; no change to normal operation.
- New Developer-Mode tool
- feat: add hub_list_libraries read tool (#241, @kingpanther13)
- New read tool
hub_list_libraries— lists the Groovy libraries installed on the hub (id, name, namespace, version), so you can discover a library's id and then read its source withhub_get_source. Lives in thehub_read_apps_codegateway; supports optional cursor pagination.
- New read tool
-
docs: hub firmware-update research notes + resilience plan (#240, @kingpanther13)
- Docs only — internal research notes for future firmware-update resilience work; no user-facing change.
-
chore: HPM bundle smoke-test library for issue #209 modularization (#239, @kingpanther13)
- Internal: installs a small throwaway test library used to validate library delivery for an upcoming modularization of the server (#209). It is referenced by nothing, makes no functional change, and will be removed once the delivery path is confirmed.
-
build(deps): bump the gradle-dependencies group with 2 updates (#223, @app/dependabot)
-
refactor: hub_ rename + consolidation of MCP tool surface (issue #105 PR1A) (#224, @kingpanther13)
- Every MCP tool is renamed to a
hub_prefix with consistent verb-noun naming (e.g.hub_list_devices,hub_call_device_command,hub_get_source). Update any saved prompts/automations that call tools by name. - Tool surface consolidated 103 → 89 (
tools/list36 → 33) for a clearer catalog:- Polling folded into
hub_get_device_attribute(passexpectedValueto wait for a value). - App/driver/library source reads → one
hub_get_source(type, id); deletes → onehub_delete_item(type, id). - Z-Wave + Zigbee radio info → one
hub_get_radio_details(radio). - Pause/resume a rule →
hub_set_rule_paused(value). - Custom-rule list/get/diagnostics →
hub_get_custom_rule; HPM drift →hub_list_hpm_packages(includeDrift); logging status →hub_get_debug_logs(mode='status'); virtual devices →hub_list_devices(filter='virtual').
- Polling folded into
- Every MCP tool is renamed to a
-
docs: add issue #105 PR2 backend/server audit game plan (#226, @kingpanther13)
-
feat: split MCP tool gateways into read-only (hub_read_) and write-bearing (hub_manage_) (#225, @kingpanther13)
- Tool gateways reorganized into read/write groups. The MCP tool surface is now 19 gateways — 7 read-only (
hub_read_*) + 12 write-bearing (hub_manage_*) — up from 13 mixed gateways. Enable ahub_read_*gateway in your LLM client for safe read-only queries and gate the write gateways separately.tools/listnow shows 30 entries (11 always-visible core tools + 19 gateways), covering 88 tools total. - New read-only gateways:
hub_read_apps_code,hub_read_devices,hub_read_diagnostics,hub_read_files,hub_read_rooms,hub_read_rules,hub_read_variables. Every read tool is reachable from a read-only gateway (or stays flat) — never locked behind a write gateway. - New dedicated Rule Machine gateway (
hub_manage_rule_machine) and device gateways (hub_read_devices/hub_manage_devices). - Renamed gateways/tools (cached MCP clients refresh automatically; no deprecation aliases):
hub_manage_rules→hub_manage_custom_rules,hub_manage_code_write→hub_manage_code,hub_manage_native_rules→hub_manage_native_rules_and_apps;hub_list_installed_appsfolded intohub_list_apps(scope='instances'). hub_export_custom_rulenow saves the exported rule JSON to the hub File Manager (it's now a write).hub_get_metricsno longer records a history snapshot by default — passrecordSnapshot=trueto record one.- Switch the tool surface between gateway and flat mode from the agent (Developer Mode).
useGatewaysis now adjustable viahub_update_mcp_settings, so an AI agent or CI/CD pipeline running in Developer Mode can consolidate tools behind gateways or expose them flat without editing the app settings page. Like theenable*toggles, the client must reconnect afterward to pick up the new tool surface.
- Tool gateways reorganized into read/write groups. The MCP tool surface is now 19 gateways — 7 read-only (
-
feat: add outputSchema to every MCP tool (issue #105 PR1C) (#229, @kingpanther13)
- MCP tools now publish an output schema. Every tool advertises an
outputSchemadescribing the structure of its result, so MCP clients can validate tool responses. It is included in gateway mode and the gateway catalog disclosure; it is intentionally omitted from the flattools/listto keep that catalog under the hub's response-size limit. - Clearer tool guidance on a few tools:
hub_set_mode(usehub_list_modesfor valid modes),hub_get_update_status(the version check is asynchronous — call again if it returns "unknown"), andhub_get_device_attribute(poll timeout/interval bounds are now in the schema). - Native Rule Machine fixes. "Between two times" conditions using Sunrise/Sunset with an offset now build correctly (the sun-event offset field is bound on both the start and end sides). Exporting, cloning, or importing a rule no longer leaves a hidden "Export/Import/Clone" helper app behind — the temporary cloner is cleaned up. A multi-action build no longer raises a spurious "missing END-IF" while an IF/ELSE block is still being assembled within the same call.
- Custom-rule tools reachable when the engine is off. The legacy custom-rule gateway (
hub_manage_custom_rules) is now usable for its read/test operations even with the Custom Rule Engine toggle off (the read-only gate no longer mis-fires on the gateway's own name). - Clearer errors and logs.
removeAction/removeTriggergive honest verify-then-retry recovery guidance instead of a misleading "may commit later" message;hub_get_logsandhub_report_issueflag Rule Machine's own benign internal "periodic" log noise so it isn't mistaken for a real error; the structural-health hint no longer suggests restoring a rule that's simply mid-build. - Cleaner output + accurate descriptions. Names from
hub_list_apps/hub_get_app_configare stripped of embedded HTML (tags removed, entities like<decoded). Plus accuracy fixes across several tools: HSM status text + arm-command list,hub_set_rule_pausedstate echo, device-command argument types, logpatternModevalues (any/all), device-health scope, variable-change cross-link, and more.
- MCP tools now publish an output schema. Every tool advertises an
-
fix: PR2a — correctness & security fixes for the MCP server backend (issue #105) (#231, @kingpanther13)
- Fixed:
hub_get_app_configno longer returns password-type setting values. - Fixed: native rule/app edits now report failure when the hub rejects a write, instead of silently reporting success.
- Fixed: captured device states are stored more reliably (atomicState) — preventing the common case where a capture was lost or a restore used stale data; existing captures migrate on update.
- Fixed: a hub-variable subscription leak that duplicated change history on every settings save.
- Fixed:
-
ci: add additive Groovy 2.5 Spock runtime lane (issues #227, #230) (#232, @kingpanther13)
- Development-only: new CI lane that exercises the test suite against a hub-closer Groovy 2.5 runtime. No change to the app users install.
-
feat: PR2b/2c backend robustness, performance, and cleanup (issue #105) (#233, @kingpanther13)
- More resilient hub communication: internal requests retry once on an expired Hub Security session, and a successful write whose response was unreadable is no longer mis-reported as a failure.
- Clearer diagnostics: error logs carry the exception type on the Hubitat Logs page, and unusual log levels are surfaced instead of silently dropped.
- Cleaner app lifecycle: removing the app cleans up its hub-variable registrations, subscriptions, and scheduled jobs; routine settings saves no longer re-check GitHub for updates.
- Standards-aligned MCP transport: protocol-version negotiation, a 202 response for notification-only requests, a JSON-RPC error for GET, and a guard against oversized request batches.
- Faster tool search and gateway dispatch on busy hubs (internal caching; results unchanged).
- New: a confirm-gated "Regenerate access token" button for rotating a possibly-compromised token on demand. Regenerating changes your MCP endpoint URL -- re-copy the new URL into your MCP client(s).
-
feat: universal Read/Write permission masters + per-tool/gateway overrides (#235, @kingpanther13)
- Simpler permissions: two master toggles, Read and Write. Every tool is now governed by a single Read toggle (all read-only tools) and a single Write toggle (all state-changing tools), both ON by default. The separate Hub Admin and Built-in App toggles are retired.
- New "Advanced: Per-tool Overrides" page. Turn off individual tools or whole gateways without touching the master toggles — handy for giving an AI a narrower, safer tool surface. Disabling is global and reversible from the same page.
- Developer Mode, Custom Rule Engine, and the gateway-consolidation toggle are unchanged.
- Note:
hub_get_infonow includes hub location/PII by default when Read is on; turn Read off (or disablehub_get_infoin Advanced) to withhold it.
-
fix: PR2-legacy — child-app bug fixes + state hygiene (issue #105) (#234, @kingpanther13)
- Custom rules: a misconfigured Set Mode or Set HSM action now logs an error and skips just that step, instead of silently halting the rest of the rule.
- Custom rules: HTTP Request actions no longer write the full URL to the logs — embedded credentials (basic-auth or secret query parameters like
token/api_key/password) are masked. The request itself is unchanged. - Custom rules: more robust state housekeeping — runaway local-variable growth is flagged in the logs, and stale delay/loop-guard bookkeeping is cleared when a rule is disabled or edited.
-
feat: split native-app CRUD into hub_set_rule (RM) + hub_set_native_app (generic) (#137) (#236, @kingpanther13)
- Rule Machine rules now have a dedicated create-and-edit tool,
hub_set_rule— create a rule and add a trigger and action in a single call. - Other classic apps get their own create-and-edit tool,
hub_set_native_app(Room Lighting, Button Controllers, Notifier, Groups+Scenes, etc.), without the Rule-Machine-specific options cluttering the schema. - RM action authoring now fails fast with a clear error when a required field is missing — a color-temperature, color, shade-position, fan-speed, thermostat, delay, or repeat action with its key field omitted previously created a broken/uncommitted action silently; it now returns a specific error naming the missing field.
- The action-discovery schema now lists per-action required fields for more device types (color, color temperature, shade, fan, button, switch).
- Tool descriptions corrected so the AI sees accurate arguments and guidance -- e.g. it now knows the code tools accept
importUrl(hub-fetched source), thathub_update_devicecan enable/disable a device, and the file-read length argument is named correctly. - The old
create_native_app/update_native_apptools are replaced by these two; delete and read tools are unchanged.
- Rule Machine rules now have a dedicated create-and-edit tool,
- fix: complete RM 5.1 Periodic Schedule trigger support (#222, @level99)
- Rule Machine Periodic Schedule triggers created via the MCP now work for every schedule type:
- Every N seconds / minutes / hours, daily, weekly (specific days), and cron-string schedules.
- Monthly and yearly schedules, including "the Nth weekday of the month/year" (e.g. the second Monday of every month, the third Thursday of November).
- Multiple periodic triggers in a single rule (e.g. every 5 minutes OR daily at 7 AM) — previously a second periodic trigger would silently overwrite the first.
- Clear, actionable errors for invalid periodic input (out-of-range interval, or mixing a calendar day with an Nth-weekday).
- Rule Machine Periodic Schedule triggers created via the MCP now work for every schedule type:
-
chore: pin commons-collections to 3.2.2 (test-only deserialization fix) (#221, @kingpanther13)
- No user-facing changes — internal test-only dependency security pin.
-
fix: compareToVariable on the RM condition walker + addTrigger partial-filter + compareToDevice guard (#220, @level99)
update_native_appaddRequiredExpressionandaddActionIF-expressions now support variable-vs-variable conditions (compareToVariable) — previously these rendered anullright-hand side on those paths.- A working Custom-Attribute
*changed*trigger no longer returns a cosmeticpartial:true. compareToDeviceconditions without acomparatornow fail with a clear validation error instead of writing an incomplete condition.
-
docs: introducing security policy (#218, @kingpanther13)
-
Potential fix for code scanning alerts: Workflow does not contain permissions (#219, @kingpanther13)
-
fix: commit clearActions/replaceActions delete synchronously via full page-form submit (closes #172) (#217, @level99)
update_native_appclearActionsandreplaceActionsnow commit the action delete synchronously — the actions are removed by the time the call returns (the tool replays the native UI's full page-form submit, which runs Rule Machine's delete handler in-band). This closes a strand where the delete could hang in trash-confirmation mode or commit late, and thereplaceActionsdata-loss case where the clear committed late but the add never ran.- A thin defensive verify-retry remains for the rare residual (stuck
state.editActor firmware commit lag); if it fires, the response carriesasyncCommitLikely: true, partial: truewith asafeRecoveryblock — verify viaget_app_configbefore retrying.
- feat: importUrl + installAsUserApp + triggerUpdated for app/driver/library install + update tools (#213, @kingpanther13)
- New
importUrlparameter oninstall_app,install_driver,install_library,update_app_code,update_driver_code,update_library_code— pass anhttp://orhttps://URL and the hub fetches the Groovy source directly, mirroring what the code editor's "Import Code from Website" → Save flow does in the UI. One tool call instead of curl-upload-then-install. - New
installAsUserAppparameter oninstall_app— after installing code viainstall_app, a second callinstall_app(installAsUserApp:<codeAppId>, confirm:true)creates a running instance (the equivalent of clicking "Add User App" in the UI) and firesinstalled()automatically. Mutually exclusive with source-install args to keep the two phases explicit. - New optional
triggerUpdatedparameter onupdate_app_code— set to a running instance's appId and the tool will fireupdated()on that instance after the source save succeeds, so subscriptions/schedules/atomicState refresh against the new code. Default is omitted (no extra lifecycle action) to match what the editor's Save button does. _fetchSourceFromUrlhelper handles up to 1.25 MB payloads in under a second on a Hubitat C-8 (empirically measured againstraw.githubusercontent.com).
- New
- Revert "refactor: flat-mode tool surface reduction (103 → 95 tools)" (#216, @kingpanther13)
- Reverts the tool-surface reduction that shipped in 1.4.0. The tool catalog returns to its pre-1.4.0 shape (103 tools, 23 core + 13 gateways in tools/list). Callers that had adapted to the merged/renamed tools from 1.4.0 (`save_app`/`save_driver`/`save_library` overloads, `get_attribute` poll-folding, `get_debug_log_state`/`update_debug_logs`, expanded `get_hub_info`, etc.) should revert to the prior tool names and shapes.
- refactor: flat-mode tool surface reduction (103 → 95 tools) (#208, @kingpanther13)
- Tool surface reduced from 103 → 95 (
tools/list36 → 34) — clearer LLM tool catalog custom_list_rulesremoved;custom_get_rule()with noruleIdreturns the list summary insteadpoll_until_attributeremoved; passexpectedValue/expectedValuestoget_attributefor read-then-wait, orwaitFortosend_commandfor command-then-waitget_debug_logs+get_logging_status→get_debug_log_state(mode='logs'|'status')clear_debug_logs+set_log_level→update_debug_logs(action='clear'|'setLevel')hub_metricsremoved; CSV history + trends folded intoget_hub_info(opt-in viarecordSnapshot=true)install_app+update_app_code→save_app(omitappIdto install, provide it to update)install_driver+update_driver_code→save_driver(same overload; bulk viainstalls/updatesarrays preserved)install_library+update_library_code→save_library(same overload)- Removed dead orphan function
toolGetHubDetails(already covered byget_hub_info)
- Tool surface reduced from 103 → 95 (
-
docs: codify MCP tool design rules in AGENTS.md + derivative docs (part of #105) (#210, @kingpanther13)
- Documentation: added Tool design rules section to
AGENTS.mdcovering naming convention, verb vocabulary, consolidation guidelines, and annotation requirements for future MCP tools.
- Documentation: added Tool design rules section to
-
docs: vendor hub2 Vue SPA source as reference resource (#211, @kingpanther13)
- No user-facing changes — docs/resources only.
-
docs: add PR1 (issue #105 tool audit) game plan to docs/ (#214, @kingpanther13)
- Documentation: added
docs/issue-105-pr1-game-plan.mdcapturing the tool-audit plan for the upcoming PR1 refactor work.
- Documentation: added
-
feat: per-capability reveal walker fixes Required Expression & ifThen Broken Conditions (issue #195 Group A) (#203, @level99)
- Required Expression and
addAction ifThenconditions now build correctly for Mode, Between two times, Variable comparison, Custom Attribute, and compareToDevice — previously these rendered**Broken Condition**or silently empty fields. addActionfirst-class support forsetVariable(set hub variable to constant or copy from another hub variable).- New per-condition spec shapes accepted by
addRequiredExpression/addAction.expression.conditions:{capability: 'Mode', state: '<mode name>'}or{capability: 'Mode', modeIds: ['<id>']}{capability: 'Between two times', start: {type: 'clock'|'sunrise'|'sunset', time?, offset?}, end: {...}}{capability: 'Variable', variable: '<name>', comparator: '=', value: <n>}{capability: 'Custom Attribute', deviceIds: [...], attribute: '<attr>', comparator: '!=', value: <v>}{capability: <attr>, deviceIds: [...], compareToDevice: {deviceId, attribute, offset?}}
addTriggerandaddRequiredExpressiontrailingupdateRulefailures now surface dedicated response slots —updateRuleFailed,subscriptionsNotLive/expressionNotLive,updateRuleError— pluspartial: trueand arepairHintsentry naming the retry path.- Walker fail-loud surfaces include
success: false,error,restoreHint, andwizardStuck: boolso callers can distinguish recoverable failures from stuck wizard states. compareToDeviceon firmware whererhsTypedoesn't surface returnspartial: truewithsettingsSkipped: [{key: 'compareToDevice', reason: 'rhs_type_not_revealed'}]and actionablerepairHintsinstead of crashing.- Documentation: per-capability extended spec shapes now in TOOL_GUIDE.md;
addRequiredExpression/addActiontool descriptions flag Water, Carbon Monoxide, Smoke, and Tamper as discrete-event sensors.
- Required Expression and
- fix: prevent (and detect) IF/END-IF + Repeat structural imbalance (#178) (#206, @kingpanther13)
removeAction,addAction(capability='endIf'|'stopRepeat'), andreplaceActionsnow refuse the call before touching Rule Machine when the change would leave the rule with unmatched IF / ELSE-IF / ELSE / END-IF or Repeat / End-Repeat blocks. The previous behavior (where a #172-class false-fail could commit the bad change post-response) is gone — the buggy state is rejected before any HTTP click goes out. Same protection applies insidepatches[{replaceActions: [...]}].check_rule_healthnow flags rules that are already structurally imbalanced (e.g. 3 IFs and 2 END-IFs from a pre-fix mutation, or a raw settings write that bypasses the structured helpers) — the response gains a newstructuralIssuesarray and the human-readable summary is appended toissues.
- fix: drop top-level anyOf from import_native_app input_schema (Haiku 4.5 compat) (#205, @kingpanther13)
- Fix: Hubitat MCP tool catalog no longer trips Anthropic's MCP `input_schema` validator (HTTP 400 on `tools..custom.input_schema: input_schema does not support oneOf, allOf, or anyOf at the top level`). Claude Code users dispatching `haiku` subagents (or any Anthropic-API caller hitting Haiku 4.5) with the Hubitat MCP server connected will stop seeing the 400.
- Fix: `clone_native_app` and `import_native_app` now surface `isError: true` and an `error` field on the soft-failure path where the cloner fires but child discovery can't find the new app, so MCP clients branching on the error envelope see the failure instead of treating it as a successful result. Existing `success: false` + `note` fields are preserved.
- feat: addAction Set-Variable, runCommand variable parameters, Set-Mode modeName (#196, @level99)
- Native Rule Machine rules built via MCP can now use Set Variable actions (set a hub variable to a constant or from another variable).
runCommandactions can now pass a hub variable as a command parameter (not just a constant value); both forms are validated and render correctly.- Set Mode actions accept a mode name (
modeName) and resolve it correctly instead of producing a brokenMode: nullaction.
- feat: MCP readOnlyHint + destructiveHint annotations on every tool (#202, @kingpanther13)
- Tools are now grouped as Read vs Write in MCP clients that surface that distinction (e.g. Claude.ai connectors), instead of all landing in a generic "Other tools" bucket. Every write also flags
destructiveHint=true, so clients show the cautious permission prompt for writes. - Under "Custom Rule Engine readonly" + gateway mode,
manage_rules_adminno longer advertises the 4 write-sidecustom_*sub-tools that would fail when called. The gateway catalog reflects the live executable surface.
- Tools are now grouped as Read vs Write in MCP clients that surface that distinction (e.g. Claude.ai connectors), instead of all landing in a generic "Other tools" bucket. Every write also flags
- fix(get_tool_guide): expose schema-referenced reference sections + add drift lint (#201, @kingpanther13)
- Internal:
get_tool_guidenow serves two new sections (update_native_app_reference,create_native_app_reference) that the flat-modetools/listtrim depends on. Flat-mode callers can now actually fetch the reference content that the schema descriptions point them at.
- Internal:
- fix: release-notes generator was picking issue refs instead of PR numbers (#200, @kingpanther13)
- Fix release-notes generator dropping PR titles when squash-commit subjects
- Affected entries in v1.3.6 – v1.3.8: the bare
- PR #181 / #169 / #187 / #174 - Future releases will show the correct PR title (and any author-supplied
- Affected entries in v1.3.6 – v1.3.8: the bare
- Fix release-notes generator dropping PR titles when squash-commit subjects
-
docs: add CONTRIBUTING.md and link it from styleguide (#197, @kingpanther13)
- Added
CONTRIBUTING.md— human-contributor guide for local dev setup, PR workflow, and the release-notes / BAT-test conventions.
- Added
-
fix: end beta-status paragraph with bug-report guidance, not dev jargon (#199, @kingpanther13)
- Beta-status note in Built-in App Integration now points to the
generate_bug_reporttool and GitHub Issues instead of internal developer jargon.
- Beta-status note in Built-in App Integration now points to the
-
PR #181
-
test: close issue #141 Section A Spock coverage gaps (#193, @kingpanther13)
- Internal test coverage only — no user-visible HPM change. Adds 5 new Spock scenarios covering structured-authoring surfaces that previously relied on the live-hub BAT suite for regression signal (
addLocalVariable5-type matrix,walkStepclick / navigate / done,addTrigger.conditionMap shape, backup-before-write hard gate).
- Internal test coverage only — no user-visible HPM change. Adds 5 new Spock scenarios covering structured-authoring surfaces that previously relied on the live-hub BAT suite for regression signal (
-
ci(hub-e2e): deploy PR source to test hub before running tests (#192, @kingpanther13)
- (no user-facing change — CI-only)
-
PR #169
- PR #187
- PR #174
- feat: optimistic-lock + self-update guard for update_app_code / update_driver_code (#189, @kingpanther13)
- Optional
expectedVersionparameter onupdate_app_codeandupdate_driver_code(including bulk mode's per-itemupdates[]shape). When supplied, the update aborts with a conflict result instead of silently overwriting if the hub's version moved between when you read the source and when you POST -- useful when multiple agents are touching the same item, or when a long LLM read-modify-write cycle could overlap a human Save in the editor. update_app_codenow refuses to overwrite the MCP server's own app source unless the existingEnable Developer Mode Toolstoggle is on in MCP rule app settings. A bad self-update can brick the MCP loop -- dev-mode users explicitly opt in for self-modification workflows. Driver updates and updates targeting other apps are unaffected.- Every self-update attempt (blocked or allowed) is audit-logged at WARN under the
hub-admincomponent.
- Optional
- feat: fold location event history into get_device_history (#188, @kingpanther13)
get_device_historynow also returns location events (mode changes, HSM status/alerts, hub-variable changes,sendLocationEventemissions) — just omitdeviceId.attributefilter scopes location results too (e.g.'mode','hsmStatus','hsmAlert', or a hub-variable name).- Response includes a
source: 'device' | 'location'field; existing fields are unchanged.
- feat(manage_virtual_device): allow custom-driver instantiation via {namespace, name} (#168, @level99)
manage_virtual_devicenow acceptscustomDriver={namespace, name}for instantiating user-installed custom drivers. Useful for testing community-installed driver types without manual UI steps.action="create"response renamedtypeNametodriverType+driverNamespacefor clarity.list_virtual_devicesis unaffected.
-
test: cut gradle test suite time ~87% via per-JVM compile cache + strict-mode CI matrix (#184, @kingpanther13)
- Internal test-harness optimisation that cuts CI/local gradle suite runtime from ~7-9 min to ~1 min, with no behaviour change. No user-visible HPM update beyond the maintainer's smoother dev loop.
-
feat: add identify-hub LED option to get_hub_info and device_health_check (#186, @kingpanther13)
- New optional
blinkLEDflag onget_hub_infoandidentifyHubflag ondevice_health_check— passtrueto also fire the hub's Identify-LED sequence (blue → red → green) so you can physically locate which hub answered. Useful when running multiple hubs side by side. Requires firmware 2.5.0.123+; no behaviour change when the flag is omitted orfalse.
- New optional
-
feat(tools/list, manage_hpm): cursor pagination + R7 doc/spec follow-up (#180, @kingpanther13)
- New:
tools/listis now paginated per the MCP cursor/nextCursor protocol. Page size 50. Gateway-mode (default useGateways=ON) catalog fits in a single page so most clients see no change; flat mode (useGateways=OFF) returns multiple pages and clients iteratenextCursor. Keeps the catalog response under the hub's 128KB JSON-RPC limit as it grows. manage_hpmdocumentation: clarifiedpackagesWithActionableDriftunder-count cases (now covers both disabled-detection AND required=true + non-scalar heID drop), restored theactualTypeNamelabel enum in TOOL_GUIDE / tool-reference, flagged the data-quality warning type list as exhaustive.
- New:
- feat: rework generate_bug_report for issue templates, scoped logs, public-safe mode (#182, @kingpanther13)
generate_bug_reportnow files bug, enhancement, or agent-behavior issues — passissueTypeto choose.- The returned
submitUrlis a prefilled GitHub issue link with the title already filled in. Paste the report content into the form's "Agent report output" field. - Recent error logs are scoped to the failure when you pass
failingTool/ruleId/nativeAppId;includeUnrelatedRecentLogs=truekeeps the broader log dump. - New
privacyMode: "public"produces a public-safe report (hub name placeholder, raw logs suppressed). Always review before posting — tool-side sanitization is a best-effort assist, not a guarantee. - Three new GitHub issue forms make it easier to file structured bug / enhancement / agent-behavior reports.
-
chore(sandbox_lint): enforce tool-count consistency + sync doc drift (#165, @level99)
- Sandbox lint now enforces tool-count consistency between Groovy source-of-truth and docs. Drift fails CI the same way version-string drift does today.
- All quoted tool counts across user-facing docs are now synced to canonical (101 total / 35 on
tools/list, 23 core + 12 gateway tools).
-
feat(manage_hpm): HPM read-only gateway — list_hpm_packages + get_hpm_drift (#167, @level99)
- New
manage_hpmMCP gateway for AI-driven introspection of Hubitat Package Manager state. list_hpm_packages— enumerate all HPM-tracked packages with their components.get_hpm_drift— detect packages whose required components are missing or whoseheIDs no longer exist in Apps Code, plus per-package data-quality warnings.
- New
- feat(manage_app_driver_code): library management (install/update/delete/get_source) (#164, @level99)
- New
install_librarytool — install Hubitat Groovy libraries via inline source or curl-uploaded File Manager file. Post-install verification + fail-closed on hub null/no-id response with anti-retry guidance. - New
update_library_codetool — 3 modes (source/sourceFile/resave). Auto-backup with 1-hour dedup (preserves original baseline through rapid edits) + optimistic-lock. - New
delete_librarytool — auto-backs up source to File Manager before deletion. - New
get_library_sourcetool — chunked reading with large-file auto-save for round-trip workflows. restore_item_backupnow handles library entries — returns redirect toupdate_library_code(libraries don't delete-then-recreate; IDs persist across source updates).- Tool count: 90 → 94.
manage_app_driver_codegateway: 7 → 11 sub-tools.
- New
- feat: add clone/export/import_native_app via Hubitat appCloner (#158, @kingpanther13)
- New
clone_native_apptool — clone any classic Hubitat automation app (RM rule, Room Lighting, Button Controller, Basic Rule, Notifier, etc.) without rebuilding it from scratch via the wizard. Returns the new app's id; passnewNameto set a custom label in one call. - New
export_native_apptool — export any classic Hubitat app to its canonical JSON shape (the same format Hubitat's UI "Export" button produces). Returns the JSON content directly; optionalsaveAsalso writes it to File Manager for backup or HPM-style distribution. - New
import_native_apptool — re-create a rule/app from a previously-exported JSON. Round-trips cleanly withexport_native_app, enabling edit-as-text workflows (export → mutate JSON → import) that are more reliable than the wizard-driving CRUD path for complex rules. - All three require the existing
Enable Built-in App Toolstoggle. Clone and import additionally requireHub Admin Write+confirm=true; export is read-only. - The
manage_native_rules_and_appsgateway now lists 12 sub-tools (was 9).
- New
-
docs(futureplans): align rule-tool names with PR #134 custom_ rename (#162, @kingpanther13)
- Internal docs only; no end-user changes.
-
feat(manage_app_driver_code): driver-code lifecycle improvements -- sourceFile + bulk + token-economy (#163, @level99)
install_appandinstall_drivernow accept asourceFileparameter — install drivers/apps from File Manager files instead of inline source strings (matches existingupdate_*_codepattern). Tool descriptions prescribe the curl-upload + sourceFile workflow that bypasses agent transcript bytes.install_drivernow verifies the driver was actually persisted to the hub before returning success (fixes a false-positive failure mode).update_driver_codenow accepts anupdatesarray of{driverId, sourceFile|source|resave}pairs for bulk multi-driver updates in a single tool call (single round-trip vs N).install_drivernow accepts aninstallsarray of{source|sourceFile}items for bulk multi-driver install (driver families).
- ci: consolidate post-merge automation into release workflow (closes race) (#160, @kingpanther13)
- Internal CI fix; no end-user changes.
-
feat(devices): add poll_until_attribute -- block-poll until attribute matches (#157, @level99)
- New
poll_until_attributetool blocks until a device attribute matches an expected value (or list of values), or a timeout elapses. Replaces client-side polling loops with a single server-side call. - Common use: verify a
send_commandactually took effect on flaky Z-Wave/Zigbee meshes, wait for a sensor to cross a threshold, or wait for a state transition. - Cost profile: BLOCKS up to
timeoutMs(default 5s, max 60s). Use sparingly; prefer event-driven flows when available.
- New
-
PR #92
-
docs: correct AGENTS.md falsehoods and auto-sync CLAUDE.md (#156, @kingpanther13)
- Internal docs only; no end-user changes.
-
feat(get-hub-logs): server-side regex / multi-pattern / time-window filters (#155, @level99)
get_hub_logsnow acceptspattern(case-insensitive regex on message),patterns+patternMode(multi-regex AND/OR),since/until(ISO-8601 or relative offset like'30m'/'2h') — all server-side, applied before pagination.- Token-friendly: "errors mentioning humidity in the last hour" can be answered in a single targeted call instead of fetching the full buffer and filtering client-side.
- Fully backward-compatible — existing calls without the new args produce identical results.
- feat(list-devices): server-side label/capability filters + format/fields projection (#153, @level99)
list_devicesnow acceptslabelFilter(substring on label),capabilityFilter(exact match on capability name),format(summary/detailed/ids), andfields(projection list) — all server-side, applied before pagination.- Token-friendly: "which Switch-capable kitchen devices do I have?" can be answered in a single targeted call instead of fetching every device and filtering client-side. On a 140-device hub, response payload drops from ~67 KB to a few hundred bytes for the same answer.
- Fully backward-compatible — existing calls without the new args produce identical results.
- feat(rule-tools): redirect hint when caller passes a built-in RM rule id (addresses #118 Option A) (#135, @level99)
- Custom rule tools (
custom_get_rule,custom_test_rule,custom_export_rule,custom_clone_rule,custom_update_rule,custom_delete_rule) now return a redirect hint when called with a rule id that belongs to a Hubitat built-in rule app (Rule Machine, Room Lighting, Basic Rules, Visual Rule Builder). - Read verbs point at
manage_installed_apps → get_app_config. Write verbs point at the matching native CRUD tool undermanage_native_rules_and_apps. - New MCP-app-side toggle
enableBuiltinApp(off by default) gates the helper — keeps the "no information leak" posture for sites that prefer the plain error.
- Custom rule tools (
-
docs: add AGENTS.md for AI contributors (#149, @kingpanther13)
- New
AGENTS.mdandCLAUDE.mdat repo root document PR conventions for AI coding agents- All mainstream agent harnesses (Codex, Cursor, Copilot, Aider, etc.) auto-load
AGENTS.md; Claude Code auto-loadsCLAUDE.md - The two files are kept byte-identical and CI enforces the sync — contributors edit
AGENTS.mdand runcp AGENTS.md CLAUDE.mdbefore committing - End users see no behaviour change — this is documentation only
- All mainstream agent harnesses (Codex, Cursor, Copilot, Aider, etc.) auto-load
- New
-
PR #91
- feat: optional flat tool-list mode (toggle off category gateways) (#136, @kingpanther13)
- New optional Consolidate tools behind category gateways setting (default ON, no change for existing installs)
- Turn OFF if your MCP client has its own tool search / progressive disclosure (e.g. Claude with deferred tools) — every MCP tool is then exposed individually on
tools/list - When OFF,
search_toolsis hidden because its only job is finding gateway-hidden tools - Stale clients calling a gateway name after you flip OFF receive a friendly error pointing at the real sub-tools to call
- Turn OFF if your MCP client has its own tool search / progressive disclosure (e.g. Claude with deferred tools) — every MCP tool is then exposed individually on
- New optional Consolidate tools behind category gateways setting (default ON, no change for existing installs)
-
ci(hub-e2e): self-configuring CI workflow against test hub (closes #77) (#148, @level99)
-
feat(rm-native): native Rule Machine tools and classic-app CRUD + custom_ rename of MCP rule engine tools (#134, @kingpanther13)
- Native Rule Machine CRUD via AI. New gateway
manage_native_rules_and_appslets the AI create, update, and delete RM rules visible in Hubitat's Rule Machine app list and web UI. Gated behind a new "Built-in App Tools" toggle (default OFF) plus Hub Admin Write. The new tools arecreate_native_app,update_native_app,delete_native_app,check_rule_health. - "Built-in App Integration" toggle ships in BETA. Most rule patterns work cleanly (time / mode / event triggers, Run Custom Action with string + number params, nested IF/THEN, switch / dimmer / lock / log / delay / repeat / exitRule). Known cosmetic edge cases are surfaced in the toggle's limitations paragraph so users see them before opting in.
- Legacy MCP-managed rule engine renamed.
enableRuleEngine->enableCustomRuleEngine(default flipped from ON to OFF). Existing MCP custom rules continue firing without interruption; a one-time migration handles the rename on upgrade. The AI's custom-rule tools are now prefixedcustom_(e.g.create_rule->custom_create_rule) so the LLM unambiguously picks the right surface. - New app-introspection tools.
list_installed_apps(full installed-app tree),get_device_in_use_by(which apps reference a given device -- call this BEFORE deleting),get_app_config(read any installed app's configuration page),list_app_pages(discover sub-pages on multi-page apps like HPM). - Tool count: 89 total, 34 on tools/list (post-rename, post-CRUD additions).
- Native Rule Machine CRUD via AI. New gateway
-
build(deps): bump gradle-wrapper from 9.4.1 to 9.5.0 in the gradle-dependencies group (#143, @app/dependabot)
-
feat(release): author-curated, minor-scoped HPM release notes + PR review tooling (#146, @kingpanther13)
- HPM release notes are now scoped to the current minor version
- Patch bumps within a minor still accumulate (e.g. v0.11.2 will show v0.11.0 / v0.11.1 / v0.11.2 together)
- Minor and major bumps wipe and start fresh
- CHANGELOG.md continues to hold the full long-term history
- Author-curated release notes via the new PR template
## Release Notessection- Bullets only — prose is silently skipped, but the workflow now warns when an author writes a section with no bullets
- Sub-bullets are supported (this is itself a sub-bullet to verify HPM nested rendering)
- Bundled improvements
- New PR template with title-prefix conventions and Type-of-change checkbox
- Gemini Code Assist styleguide that requests (never blocks) Release Notes presence and prefix/checkbox congruence
- Full pytest coverage for every Python file in the repo, run via a new path-filtered
Python Testsworkflow
- HPM release notes are now scoped to the current minor version
-
fix(release): positive-match the skip cascade so recursion guard short-circuits cleanly (#123, @kingpanther13)
…ally stops downstream steps
When
prechecksetskip=trueon the self-triggered release-commit push, downstreamif:conditions usedsteps.X.outputs.skip != 'true'— which evaluates TRUE for empty outputs. A skipped upstream step has empty outputs, soempty != 'true'let the next step run anyway, cascading past the guard untilrelease_bump.pyran with empty env and errored.Switch to positive
== 'false'matches. Empty output fails the match, so once any upstream skip fires the rest of the job correctly short-circuits.Observed on the self-triggered run after the 0.10.1 release commit — the actual v0.10.1 release landed fine, but the recursive workflow run falsely reported failure.
-
PR #120
-
tests: RM 5.1 native BAT suite — acceptance gate for #120 (#133, @kingpanther13)
Catalogs every Rule Machine 5.1 feature (48 trigger/condition capabilities, 80+ actions across 12 categories, expressions, variables, Private Boolean, lifecycle verbs, RM's own HTTP endpoint surface) with empty columns for fixture paths, Spock spec names, and live-smoke status.
Derived from the official RM 5.1 docs; meant as the acceptance-matrix gate for the native-RM CRUD tools landing in #120. Every row must be populated with captured fixtures + passing tests before the Phase 2 PR can merge.
Flags the multi-device capability marshaling-flag contract (settings[] + .type + .multiple=true must ship together) discovered during Phase 1 reverse-engineering as the single most important payload-shape invariant the tools must enforce.
-
docs: add Gemini testing results for PR #134 (#138, @kingpanther13)
-
fix(manage_virtual_device): rename "Virtual Presence Sensor" enum entry to "Virtual Presence" (#144, @level99)
…ual Presence"
The deviceType enum entry "Virtual Presence Sensor" doesn't match any actual Hubitat built-in driver. Hubitat's real driver name is "Virtual Presence" (no "Sensor" suffix). When manage_virtual_device attempts to instantiate by the wrong name, Hubitat throws a generic java.lang.Exception ("An unexpected error occurred") that bubbles through the MCP layer opaquely.
Verified by inspecting the Add Device wizard search on test hub firmware 2.5.0.126:
- search "Virtual Presence Sensor" → "No results found"
- search "Presence" → returns "Virtual Presence" + "Virtual Presence with Switch"
Spot-checked the other 14 enum entries against the Add Device wizard; all match Hubitat's actual driver names. Only Presence was off.
Files updated:
- hubitat-mcp-server.groovy: enum, error message, internal type list, doc table
- SKILL.md: supported-types prose
- TOOL_GUIDE.md: virtual device table
- tests/BAT-rm-native-crud.md: 2 BAT scenarios that previously created
devices with the wrong name (T-numbered tests around motion/presence
- presence/powersource setups)
No tool surface change, no new tests, no version bump.
-
feat(developer-mode): add manage_mcp_self gateway + delete_variable (#145, @level99)
… LogWrapper fix
Adds the Developer Mode self-administration surface (#78 MVP) and rolls in delete_variable (#102). Both are gated behind a new
enableDeveloperModetoggle on the MCP rule app's main page (default OFF, every successful write logged at WARN for audit).Tools added:
- manage_mcp_self gateway (12th gateway)
- update_mcp_settings: flip the MCP rule app's own settings via app.updateSetting(). Allowlisted keys: mcpLogLevel, debugLogging, maxCapturedStates, loopGuardMax, loopGuardWindowSec, enableHubAdminRead, enableBuiltinAppRead, enableRuleEngine. Excluded: enableHubAdminWrite (footgun - would disable own write path mid-session), enableDeveloperMode (lockout protection - must remain UI-only to disable), selectedDevices (different wire format, separate tool planned). All-or-nothing batch validation: a single bad key blocks the entire write.
- manage_hub_variables.delete_variable: remove a rule_engine variable (DESTRUCTIVE - no undo). Connector-namespace deletion not yet supported via MCP; error message redirects users to Settings -> Hub Variables UI.
Tool count: 83 -> 85 total, 33 -> 34 on tools/list (added manage_mcp_self gateway). Gateway count 11 -> 12.
Bugfix surfaced during testing: dispatcher's
log.error "msg", ecalls at hubitat-mcp-server.groovy:391 and :442 trigger a Hubitat-sandbox MissingMethodException because LogWrapper.error() does not accept (String, Throwable). The cascade crashed the error handler, producing a generic "An unexpected error occurred" response and masking the real exception's message - visible to any caller exercising a non-IllegalArgumentException tool path (the new IllegalStateException for the toggle-off gate was the first to hit this). Switched both call sites to single-string form; mcpLog above each captures the stack trace into the MCP-readable log buffer.Why now: blocks a fully-self-configuring CI environment for #77 (E2E live-hub tests against the test hub, in support of PR #134). CI workflows need to flip enable* toggles to set up test environments without manual UI intervention. This is the first tool under a broader Developer Mode pattern; additional self-admin tools (device-access management, true Hub Variables namespace support, artifact cleanup) are planned as follow-ups under the same toggle.
Live-tested on a real hub (instance 38) - all paths verified including gate refusal, allowlist rejection, audit logging, and settings persistence via get_logging_status round-trip. Unit tests added: 9 new specs for delete_variable extending ToolHubVariablesSpec, 15 new specs in a new ToolUpdateMcpSettingsSpec covering gates, validation, allowlist, and golden paths. Full ./gradlew test suite green.
Closes #78 (MVP), Closes #102. Part of #77 (E2E enablement).
- manage_mcp_self gateway (12th gateway)
-
test(server): unit-test manage_destructive_hub_ops / manage_apps_drivers / manage_app_driver_code gateways (#110, @kingpanther13)
…ers / manage_app_driver_code gateways
Closes #73.
New specs cover every tool in the three gateways, golden-path + error-path per tool: reboot_hub, shutdown_hub, delete_device, list_hub_apps, list_hub_drivers, get_app_source, get_driver_source, list_item_backups, get_item_backup, install_app, install_driver, update_app_code (source / sourceFile / resave variants), update_driver_code, delete_app, delete_driver, restore_item_backup. Every destructive tool has explicit confirm and Hub-Admin-Write gate tests.
Fixes a GString/String map-key bug in toolDeleteItem and toolUpdateItemCode that the new tests uncovered. backupItemSource stores manifest entries using subscript assignment (state.itemBackupManifest[key] = manifest) which coerces the GString key to String. The cleanup paths in toolDeleteItem (on delete success) and toolUpdateItemCode (on update success) looked up that entry with an explicit .get("${type}${itemId}") / .remove("${type}${itemId}") — explicit .get(GString) / .remove(GString) do NOT coerce, and HashMap hashes GString and String differently, so the lookup/removal silently no-op'd. The visible effects were delete_app / delete_driver always returning backupFile: null + restoreHint: null, and update_app_code / update_driver_code leaving stale manifest entries that list_item_backups would then still show after a successful update. Fix is a .toString() at both call sites.
-
test(server): unit-test manage_logs / manage_diagnostics / manage_files gateways (#111, @kingpanther13)
…es gateways
Closes #74.
New specs cover every tool in the three gateways, golden-path + error-path per tool across 23 tools:
manage_logs (8): get_hub_logs (expanded: level filter per-level, source substring, limit trim, empty-response), get_device_history, get_performance_stats (sortBy variants, type filtering), get_hub_jobs, get_debug_logs (level/ component/limit filters), clear_debug_logs, set_log_level, get_logging_status.
manage_diagnostics (11): get_set_hub_metrics (CSV snapshot write, low-memory warning, recordSnapshot=false skip), get_memory_history (CSV parsing, summary stats, limit truncation, empty-response), force_garbage_collection (before/ after deltas, non-numeric probe handling), device_health_check (healthy/ stale/unknown classification, includeHealthy toggle), get_rule_diagnostics (via childAppAccessor fixture), get_zwave_details / get_zigbee_details (sdk_only fallback + hub_api_raw + hub_api branches), zwave_repair (confirm
- Hub Admin Write gate + POST success/failure), list_captured_states (sorted newest-first, at-capacity warning), delete_captured_state (not-found path), clear_captured_states.
manage_files (4): list_files (JSON + HTML fallback + API-unavailable), read_file (chunk boundaries, offset+length, missing-file), write_file (confirm gate, Hub Admin Write gate, missing-backup gate, invalid filenames, existing-file backup, upload failure), delete_file (confirm gate, skip-backup-of-backup, delete failure).
Adds TestLocation support fixture. Hubitat's real Location exposes a magic `hub` property that eighty20results' Location interface doesn't declare, so @AutoImplement on a concrete class with a settable `hub` field gives tests a clean way to drive `location.hub.zwaveVersion` / `location.hub.uptime` reads without reimplementing the 20+ Location methods.
Two @Shared stubs layered on the AppExecutor mock per spec:
- ToolManageLogsSpec: getApp() -> TestChildApp (for toolSetLogLevel's app.updateSetting via the @Delegate chain).
- ToolManageDiagnosticsSpec: getLocation() -> TestLocation (per-test hub mutation; cleanup() resets between tests).
clear_captured_states / delete_captured_state are not gated on confirm=true in the current server source (dispatch is direct through clearAllCapturedStates / deleteCapturedState). Tests pin existing no-confirm behaviour; issue #74 listed them under confirm gating but adding a gate would be a separate design change.
-
PR #75
-
test(rules): breadth coverage for conditions, actions, triggers, loop guard, error paths (closes #75) (#115, @kingpanther13)
Prep for #75's breadth specs:
- TestLocation gains mutable mode/sunrise/sunset/hsmStatus fields plus
a setMode() call log so specs can exercise
mode/sun_position/hsm_statusconditions and theset_modeaction without closure-Map tricks. The hsmStatus field isn't on HubitatCI's Location interface, but Groovy property dispatch resolves it on the concrete object (real Hubitat exposes it the same way). - RuleHarnessSpec now shares a single TestLocation instance across the spec class, and setup() resets its mutable fields so per-test state doesn't leak between features.
- TestDevice grows the command methods the rule engine's action types dispatch to (setColor, setColorTemperature, lock/unlock, deviceNotification, thermostat setters, speak, valve/fan/shade, plus hasCapability/hasCommand). Each delegates to invokeCommand so Spy(TestDevice) { 1 * setColor(_) } still works.
No rule-engine production code changes. Existing primitive specs keep their behaviour — the harness changes are additive.
- TestLocation gains mutable mode/sunrise/sunset/hsmStatus fields plus
a setMode() call log so specs can exercise
-
test: backfill regression specs from CHANGELOG / release-notes history (closes #76) (#116, @kingpanther13)
…y (#76)
Adds two regression spec files — one server-side, one rule-engine — that pin behaviour restored by specific historical fixes so future refactors can't silently reintroduce the original bugs.
Server regressions (src/test/groovy/server/RegressionsFromHistorySpec.groovy):
- v0.7.7 formatAge singular/plural grammar across minute/hour/day + "just now" + null/zero fallback
- v0.6.1 BigDecimal.round() crash in checkForUpdate skip-branch (the "nightly 3 AM crash"): calling with a fresh-enough checkedAt no longer throws
- v0.5.3/5.4/7.6 device_health_check hoursAgo formula: one-decimal fractional hours, not 10x off, no BigDecimal.round
- v0.8.5 get_hub_logs source filter now matches against the message field (previously compared against timestamp, silently missing every real source search)
- v0.5.1 get_hub_logs falls back to newline-split on a non-JSON response
- v0.8.2 normalizeCommandParams parses a JSON-string element into a Map (setColor regression)
- v0.8.5 normalizeCommandParams brace-matches an embedded JSON object out of the raw-String wrapper Hubitat hands through when its parser chokes on nested JSON
- convertParamElements numeric coercion for Integer + Double
Rule regressions (src/test/groovy/rules/RegressionsFromHistorySpec.groovy):
- v0.1.6 repeat action honours the legacy
countparameter as a fallback fortimes - v0.1.22 action-return semantics: a
stopinside arepeathalts all remaining iterations AND prevents subsequent outer actions - v0.7.6 substituteVariables
%now%resolves to a timestamp even when atomicState.localVariables holds a key namednow(the shadow fix) - v0.7.7 evaluateConditions fail-closed when an evaluator throws (repeated here with an explicit release-note citation so the backfill trail is self-contained; aggregator already pinned by EvaluateConditionsSpec)
The rule-engine spec's class Javadoc enumerates which historical regressions are NOT duplicated here because an existing breadth spec (ConditionTypesSpec, EvaluateConditionsSpec, ActionTypesSpec, TriggerBreadthSpec) already pins them, with pointers.
Scope note on sandbox-only bugs: log.isDebugEnabled() not available in the hub sandbox (v0.8.2), Date.format(String, Locale) not available (v0.8.6), and getClass()/Eval.me restrictions (SANDBOX-001) cannot be asserted at the unit level — the harness runs with Flags.DontRestrictGroovy and a PermissiveLog. Those stay guarded by sandbox_lint.py at CI lint time; see PR #103 and PR #107 for the rationale.
-
PR #76
-
Add get_app_config + list_app_pages (manage_installed_apps gateway) (#112, @level99)
New core tool (on tools/list) that reads an installed app's config via the hub's SDK-level rendering endpoint /installedapp/configure/json/[/]. Works for Rule Machine rules, Room Lighting instances, Basic Rules, Hubitat Package Manager, Mode Manager, Button Controllers, and any other legacy SmartApp — same endpoint the Hubitat Web UI itself consumes for every config page.
Returns a normalized structure:
- app: identity (id, label, name, appType summary, disabled, parentAppId, installed)
- page: {name, title, install, refreshInterval, sections: [{title, inputs, paragraphs}]} where each input has {name, type, title, description?, multiple?, required?, options?, value?}
- childApps: [{id, label, name}] summary
- endpoint: the actual URL hit (for debugging)
- settingsKeyCount: size of the raw settings map
- settings: only included when includeSettings=true (large apps have 500-1000 keys with app-specific encoding like Room Lighting's 'dm~~' — opt-in for power users who want to decode them)
Multi-page apps (Rule Machine 5.1, HPM) expose sub-pages by name via the pageName arg — e.g. get_app_config(appId=35, pageName="prefPkgModify") returns HPM's installed-packages list.
Runtime fingerprint check: the tool asserts the top-level invariants it depends on (app object, configPage object, configPage.sections array). If Hubitat firmware drifts the endpoint contract, callers see a clear error pointing at the specific shape mismatch, not malformed data. Paired with a new standalone audit script (scripts/app_config_audit.py) that can be run manually or in CI to pre-emptively detect drift across known app IDs before users hit it.
Other additions:
- New 'App Introspection' core-tools section in README, TOOL_GUIDE.md, and the agent-skill references. Tool counts updated (21→22 core, 69→70 total, 30→31 on tools/list).
- scripts/audit_config.example.json — sample target list for the audit script.
- Helper stripAppConfigHtml() to clean Hubitat's color-span HTML from labels/titles in user-facing fields.
Gate: requireHubAdminRead() — same tier as existing read-only admin endpoints (/hub2/appsList, /device/fullJson/, etc.).
Sandbox lint clean. Live-tested on firmware 2.4.4.156 against Room Lighting, Rule Machine 5.0/5.1, and HPM — including error paths (non-numeric appId, unknown appId, invalid pageName chars, HPM sub-page navigation, includeSettings=true). Audit script verified against the same five targets.
Read-only — no write path in this PR. Write support (creating/modifying app config) would be a separate opt-in, deliberately out of scope for initial introduction.
-
test: backfill sunrise/sunset silent-failure fix + broader silent-device-not-found coverage (#76) (#117, @kingpanther13)
…roader silent-device-not-found coverage (#76)
Two backfill gaps from the first regression PR (#116) — both for bugs that shipped "silent," meaning no exception, no log, no user-visible signal. Exactly the class of regression hardest to catch without dedicated tests.
Adds src/test/groovy/server/NormalizeTriggerSpec.groovy
Covers hubitat-mcp-server.groovy::normalizeTrigger (commit 2a6da11, v0.2.12 follow-up). Pre-fix: the rule engine only recognised the canonical sunrise/sunset trigger shape {type:'time', sunrise:true}, while create_rule / update_rule accepted four additional LLM-natural shapes (e.g. {type:'sunrise'}, {type:'time', time:'sunrise'}). Rules saved under those shapes silently never fired — no log, no error, no hint. normalizeTrigger is the bridge that converts every accepted input shape to the canonical form before persistence.
Tests cover:
- All five input shapes from the original bug report convert correctly
- Canonical {type:'time', sunrise:true} passes through unchanged
- Unrelated trigger types (device_event) are not mutated
- offsetMinutes is renamed to offset; an existing canonical offset wins over a legacy offsetMinutes on the same input
- Return value is a fresh map — mutating the output does not affect the caller's input (defends the new LinkedHashMap(trigger) clone)
Extends src/test/groovy/rules/RegressionsFromHistorySpec.groovy
Adds a @Unroll feature method covering 8 action types from commit 83aee5b's "Comprehensive debug logging overhaul - eliminate silent failures": set_level, set_color, toggle_device, lock, unlock, speak, send_notification, set_thermostat. Before 83aee5b each would NPE on a missing-device method call, get swallowed by the outer try/catch, and silently skip with no log. The fix added an explicit
if (device) { ... } else { ruleLog("warn", ...) }branch at each call site. For each type the test asserts:- a warn-level ruleLog is emitted citing both the action type and the missing device id
- the NEXT action in the chain still fires — no silent throw stopped execution
ErrorPathsSpec already pinned the device_command slice of this regression. This extends that guard to the rest of the action types the 83aee5b commit named. activate_scene / set_valve / set_fan_speed / set_shade / set_color_temperature use the same shape and are implicitly covered by the same fix; adding them would be pure repetition.
-
PR #77
-
test(integration): in-harness dispatch drive-through for handleMcpRequest + subscribe/fire (#77) (#119, @kingpanther13)
…fire (#77)
Adds two seams to the unit-test harness that production code relies on but existing specs don't exercise, alongside two specs that prove the scaffolding works.
Server:
handleMcpRequest()HTTP pipeline (McpRequestDriver)request.JSONreads andrender(Map)writes round-trip through a per-test driver so specs can POST a JSON-RPC body and assert on the captured envelope (status / contentType / parsed response).requestis installed via reflection into HubitatAppScript's privateinjectedMappingHandlerDataMap — the class's@CompileStaticgetProperty(String)override short-circuits the namerequestto that field before MOP dispatch, so a metaClass hook on the script is never consulted.render(Map)is stubbed via thesetupSpecdispatcher pattern already documented as class-2 in the dispatch cheat sheet.- Covers: initialize / tools/list / tools/call happy paths, notifications (204 no-content), batch requests, empty-batch -32600, null-body -32700, unknown-method -32601, and invalid-request -32600 (missing jsonrpc marker).
Rule engine:
subscribe()+ event replay (SubscriptionRecorder)- Records every
subscribe(source, attribute, handlerName)call and exposesfireEvent(script, source, attribute, value)to replay a synthetic event back at the recorded handler. AppSubscriptionReadervalidates that the subscribe source is aDeviceWrapperbefore the Mock stub can fire —RuleHarnessSpecaddsFlags.DontValidateSubscriptionsto thePassThroughAppValidatorto let plainTestDevicePOJOs through.- Covers: single-device + multi-device + mode_change wire-up, end-to-end
handler→action dispatch on a matching event, non-matching value no-op,
and a loud failure mode when
fireEventcan't find a subscription.
Docs
- New "E2E drive-through" section in
docs/testing.mdexplains the wiring, includes spec patterns, and flags the trigger.deviceId-must-be- String gotcha that broke the initial draft of the rule spec.
Part of #77 — in-harness drive-through. The literal-fake-hub HTTP-server variant (#77's stretch read) can build on this; this PR delivers the 90% of value that doesn't need its own process.
Full suite: 606 tests, 0 failures.
-
fix(release): push via deploy key to bypass main-branch ruleset (#122, @kingpanther13)
The release workflow runs on push to main after a labeled PR merges, generates a chore(release): version-bump commit locally on the runner, and pushes it to main. Since the commit is brand-new on the runner, the required status checks (test + guard) have never run on it, and main-branch protection rejects the push.
For user-owned repos GitHub only allows DeployKey / RepositoryRole / Team actors in a ruleset's bypass list — Integration (GitHub App) bypass is org-only. A new repo-wide deploy key
release-bot (ruleset bypass)now has write access and is listed as a bypass actor on themain-required-checksruleset. Private half lives in the repo secret RELEASE_DEPLOY_KEY.Swap
token: GITHUB_TOKENforssh-key: RELEASE_DEPLOY_KEYon the checkout step so actions/checkout configures the origin as SSH and loads the deploy key into ssh-agent; subsequentgit push origin mainandgit push origin v<version>then push as the deploy key and bypass cleanly.Old-style branch protection deleted; the ruleset is now the sole enforcement mechanism on main (still requires test + guard for every human PR).
-
docs: re-collapse Future Plans + refresh MCP tools list (#102, @kingpanther13)
Commit 51b6c3a (Apr 20 auto-sync) stripped the
Details
wrapper around the Future Plans section, so the speculative backlog now dominates the rendered README. Wrap the section back inand update the sync workflow so future auto-syncs preserve the collapse.While in the file, refresh the MCP tools catalog to match the current Groovy source:
- 74 total / 31 on tools/list / 22 core / 52 gateway subtools
- add search_tools (v0.9.1) under Reference
- manage_logs grew to 8: add get_performance_stats + get_hub_jobs
- manage_diagnostics grew to 11: add get_memory_history + force_garbage_collection
-
build(deps): bump the gradle-dependencies group with 2 updates (#101, @app/dependabot)
Bumps the gradle-dependencies group with 2 updates: org.quartz-scheduler:quartz and su.litvak.chromecast:api-v2.
Updates
org.quartz-scheduler:quartzfrom 2.3.0 to 2.5.2Updates
su.litvak.chromecast:api-v2from 0.11.1 to 0.11.3
updated-dependencies:
- dependency-name: org.quartz-scheduler:quartz dependency-version: 2.5.2 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: gradle-dependencies
- dependency-name: su.litvak.chromecast:api-v2 dependency-version: 0.11.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: gradle-dependencies ...
Signed-off-by: dependabot[bot] support@github.com
-
fix(lint): scan GString interpolations for sandbox violations (#103, @kingpanther13)
sandbox_lint.py stripped all string contents before running regex rules, which meant violations inside GString
${...}interpolations (which are evaluated as Groovy at runtime) were silently ignored. Found during PR #79 review:"...(type=${rawKey?.getClass()?.simpleName})..."passed lint but would raise SecurityException on a real hub.- Preserve
${...}bodies when stripping double-quoted and triple-double- quoted strings; blank literal text only. - Handle nested braces inside interpolations (closures, method chains).
- Leave single-quoted strings fully blanked (Groovy single-quotes aren't GStrings).
- Add
--self-testmode with 8 fixture cases covering the common shapes: interpolated getClass/Locale hit; plain-literal and single-quoted getClass miss; escaped\${does not open an interpolation. - Wire --self-test into the sandbox-lint CI workflow as a pre-scan step.
- Preserve
-
perf(test): cache HubitatAppSandbox parse per spec class (5m → 1.5m) (#104, @kingpanther13)
Before this change, HarnessSpec.setup() and RuleHarnessSpec.setup() ran before every Spock feature method and rebuilt the sandbox from scratch: read the 8000-line hubitat-mcp-server.groovy (or 4000-line hubitat-mcp-rule.groovy) from disk, AST-validate it through HubitatCI, compile it in the Groovy sandbox, and bootstrap the script. That was ~3 seconds of pure parse/compile per test. With 81 tests on main (and ~160 on the feature branches that add tests) the per-test re-parse dominated CI runtime — the unit-tests job was taking 7 minutes on PR #79 and 5 minutes on a fresh main, almost none of it spent in actual test logic.
Approach:
- Move
new HubitatAppSandbox(...)/sandbox.run(...)into setupSpec, stored on @Shared fields so the compiled script is reused across every feature method in a spec class. - Keep fixture collections (stateMap, atomicStateMap, settingsMap,
childDevicesList, childAppsList) as @Shared
finalreferences so the AppExecutor mock's stubs (also built in setupSpec) continue to see them. setup() clears contents between tests — no reassignment, so the captured references stay valid. - Leave wireScriptOverrides / wireOverrides in setup() rather than setupSpec. They rewire metaClass hooks and reflective field sets on the shared script each test, so subclasses can override them with closures that capture the current feature instance's non-@Shared fields (e.g. SubstituteVariablesSpec.globalVars). Cheap compared to the parse cost, and keeps the extension contract intuitive.
- HubInternalGetMock gains a reset() that clears handlers + calls, so the shared instance is safe to reuse across tests.
- RuleHarnessSpec: _parent stays per-feature; setup() explicitly resets it and calls script.setParent(null) to cancel out any previous test's state on the shared script.
Observed locally (JDK 11, fresh daemon, --rerun-tasks, 81 tests):
- Before: 5m 7s wall / 289.9s of test time / ~3.5s per test
- After: 1m 36s wall / 70.6s of test time / ~0.9s per test (69% wall-clock reduction, 76% test-time reduction)
Per-spec, the flat base cost is now one parse per Spec class (~4-6s) plus near-zero per feature method — so adding more tests to an existing spec is essentially free, and the 7m CI job on PR #79 should drop into the 1.5-2m range.
Also in this PR:
- testLogging.events now includes 'started', so future harness regressions surface per-test in the CI log instead of leaving the runner silent for minutes (the original 7m job showed zero test events between JVM start and finish).
- unit-tests.yml uploads build/reports/tests/test and build/test-results/test unconditionally (not just on failure), so per-spec time="..." data is available for perf regression tracking.
No subclass spec changes required — the protected HarnessSpec / RuleHarnessSpec API (script, appExecutor, stateMap, etc.) is preserved.
- Move
-
Built-in app visibility + Rule Machine interop (2 new gateways, 7 tools) (#79, @level99)
Adds two new gateways giving the AI visibility into Hubitat's built-in apps and a read/trigger surface on Rule Machine. Closes a large functional gap — prior to this PR the AI had no way to see what Rule Machine rules, Room Lighting instances, Scenes, Mode Manager schedules, or dashboards existed on the hub, or which of them referenced a given device.
-
list_installed_apps(filter?, includeHidden?): Enumerates every app on the hub — built-in system apps + user-installed — with parent/child tree. Backed by /hub2/appsList (the same undocumented-but-stable JSON endpoint the Hubitat Web UI uses for its Apps page). Filters: all / builtin / user / disabled / parents / children. Hidden child apps suppressed by default and re-parented to the nearest visible ancestor so parentId references never orphan. -
get_device_in_use_by(deviceId): Reverse lookup — given a device, lists the apps that reference it (Room Lighting, Rule Machine, Groups, Mode Manager, dashboards, Maker API, etc.). Backed by /device/fullJson/ which already exposes appsUsing + appsUsingCount. Answers "if I delete this device, which automations break?"
Uses the official hubitat.helper.RMUtils helper class — the community- documented entry point that RM's own companion apps use. Read + trigger only; CANNOT create, modify, or delete RM rules: Hubitat's platform blocks addChildApp parent-type validation for third-party apps. Documented prominently so the AI refuses invalid requests rather than fabricating fake tools.
-
list_rm_rules: Enumerate RM 4.x + 5.x rules (combined, deduplicated by id), id + label for each. Handles both RM shapes: the documented[{id, label}]and the undocumented RM 5.x single-entry-Map shape[<id>:"<label>"], coercing keys to Integer either way. -
run_rm_rule(ruleId, action): Trigger an existing rule.- action=rule (default): full evaluation (triggers + conditions + actions, as if the rule fired naturally)
- action=actions: bypass conditions, run actions only (manual override)
- action=stop: cancel in-flight actions (kills running delays)
-
pause_rm_rule/resume_rm_rule(ruleId): Reversible pause-state management. Paused rules don't fire on triggers. -
set_rm_rule_boolean(ruleId, value): Set an RM rule's private boolean. RM rules can reference "Private Boolean" in their conditions; this lets MCP flip that flag from outside the rule UI.
Both gateways are gated behind a new opt-in setting, "Enable Built-in App Tools" (off by default). Read-only — no backup required. Setting is separate from Hub Admin Read since these endpoints have a slightly different firmware-stability profile (documented in SKILL.md's Hub Internal API Endpoints Reference, now with rows for /hub2/appsList and /device/fullJson).
- Closure parameter in the installed-apps tree walker uses
noderather thanappto avoid shadowing Hubitat's reservedappsymbol. - All new
IllegalArgumentExceptionmessages use ASCII punctuation for consistency with existing exception messages in the file. - BAT-v2.md gains a new Section 11 (T200-T211) with 14 test scenarios covering the new tools, their mutual-exclusion and gate errors, and the refuse-fake-tools safety rule.
- Tool counts in docs bumped to post-feature values (22 core, 11 gateways, 33 on tools/list, 81 total) since those are facts about what exists.
- No version bump. No packageManifest.json changes — leaving those for the release cut. Internal endpoints used are the same tier as existing ones already in the codebase (/hub2/zwaveInfo, /hub2/roomsList, etc.).
Sandbox lint clean. Live-tested on firmware 2.4.4.156 across all 7 new tools: list_installed_apps filters, get_device_in_use_by reverse lookup, list_rm_rules dedup, run_rm_rule rule/actions/stop variants, pause/resume cycle, set_rm_rule_boolean true/false/string-bool.
-
-
test(server): unit-test manage_rules_admin / manage_hub_variables / manage_rooms gateways (#106, @kingpanther13)
…anage_rooms gateways (closes #72)
Adds golden-path and error-path specs for all 13 tools in three gateways:
- manage_rules_admin: delete_rule (auto-backup + skipBackupCheck + confirm gate
- unknown ruleId), test_rule, export_rule, import_rule (golden + exportData validation), clone_rule.
- manage_hub_variables: list_variables (both sources + hub-unavailable fallback
- empty), get_variable (hub / rule fallback / not-found), set_variable (hub / rule fallback).
- manage_rooms: list_rooms (empty + sorted), get_room (by name / id / unknown / blank), create_room (confirm + admin-write + blank-name + duplicate + POST /room/save roundtrip), delete_room (missing id / POST /room/delete/ roundtrip / not-found), rename_room (POST /room/save / missing newName / duplicate-name collision).
Mocking: hub-variables + rooms use script.metaClass for the dynamic Hubitat APIs (getAllGlobalConnectorVariables, getRooms, getHubSecurityCookie, uploadHubFile); rooms uses a @Shared Closure httpPostHandler dispatched from a permanent _ * appExecutor.httpPost(_, _) stub installed in setupSpec — this is the only reliable interception point for BaseExecutor methods because the eighty20results delegate chain bypasses per-instance metaClass (intra-class dispatch, same trap as PR #100's addChildApp) and per-feature
>>stubs in given: do not propagate into the @Shared Mock's interaction set.docs/testing.md gains a new "Which interception point to use" section with a three-column cheat sheet (purely dynamic / BaseExecutor / child-app private-closure), a setupSpec-dispatcher recipe, and an explicit anti-patterns list so the next contributor doesn't rediscover this the hard way.
No production code changes — test-infrastructure + docs only.
- manage_rules_admin: delete_rule (auto-backup + skipBackupCheck + confirm gate
-
test(rm-interop): pin registerRmRule warn-log emission and type classification (#107, @kingpanther13)
…ification
Follow-up to #79. The round-2 regression-guard spec in ToolListRmRulesSpec carried an inline comment claiming "running under HubitatAppSandbox.run() exercises the sandbox security manager, so any getClass()/Eval.me reintroduced in registerRmRule would blow up the test loudly." That was wrong — HarnessSpec uses Flags.DontRestrictGroovy, which disables the AST restrictions that enforce SANDBOX-001 on a real hub. Under the test harness, getClass() compiles and runs fine; the old buggy code would have passed this test. The real sandbox-safety guarantee comes from sandbox_lint.py (broadened in #103 to scan GString interpolations).
Promote the misleading comment to an actual behavioral assertion:
- Install a per-test script.metaClass.mcpLog collector (the same pattern ToolGetDeviceInUseBySpec uses — valid here because mcpLog is a script-local method, not inherited from AppExecutor/HubitatAppScript; see docs/testing.md dispatch cheat sheet).
- Assert the warn log fires with the expected instanceof-ladder output (type=String for a String key 'not-a-number').
- Rename the test so the "sandbox-safe" claim isn't load-bearing in the feature description.
If registerRmRule's warn-log is deleted, the assertion fails. If the instanceof ladder is refactored away and the GString no longer surfaces the key type, the assertion fails. The getClass()-revert case is still covered — by sandbox_lint.py's SANDBOX-001 at CI lint time, not here.
-
fix(release): trigger on push to main (fork-PR bot-permission workaround) (#108, @kingpanther13)
The pull_request: closed trigger inherits GitHub's forced read-only GITHUB_TOKEN when the merged PR came from a fork (security policy, can't be overridden by workflow-level permissions: contents: write). This caused the v0.10.0 release run for PR #79 (from level99's fork) to fail at the git push step with HTTP 403, even though every prior release from internal branches succeeded.
Switching the trigger to push: branches: [main] sidesteps the fork penalty — once a PR is merged, the push event on main is in the base repo's context and the token follows the workflow's declared permissions. PR metadata (number, author, labels) is recovered from the merge commit via the /repos/{owner}/{repo}/commits/{sha}/pulls API.
Two new guards prevent release-commit recursion when the bot's own version-bump commit pushes to main:
- Skip if head_commit.author.name is github-actions[bot]
- Skip if head_commit.message first line starts with "chore(release):"
The commits-to-PRs lookup handles all merge strategies (merge commit, squash, rebase) and returns empty for direct pushes (which are then skipped). User-facing behavior is unchanged: add a release:{major,minor, patch} label and merge — the bot does the bump + tag + push.
Note: this fix only works if the repo's Settings -> Actions -> General -> Workflow permissions is set to "Read and write permissions" (default_workflow_permissions=write at the API level). With the current repo setting of read-only, this workflow's contents: write declaration is capped at read and the bot will still 403 on push. The setting flip is a separate one-time repo-admin action.
Does not retroactively release v0.10.0 — PR #79 is already merged and the push event for that merge has passed. Cutting v0.10.0 requires a separate one-off intervention (manual version bump + tag, or an empty re-release commit landed to main by the bot after this workflow ships).
-
fix(release): cascade skip flags + retry PR lookup for indexing lag (#109, @kingpanther13)
Two bugs exposed by run #26 (the first push-event run of the new release.yml, firing on PR #108's own merge):
-
Downstream if-guards used the wrong polarity. Steps after Determine release label had
if: steps.label.outputs.skip != 'true'. When thelabelstep was skipped (becausepr.outputs.skip == 'true'), its outputs are empty strings — and'' != 'true'is true, so Checkout main / setup-python / Bump version all ran with LABEL unset. Bump version then exited 1 with "LABEL env var not set", turning a legitimate skip into a job failure.Fix: switch to
if: steps.label.outputs.skip == 'false'. Empty strings fail that check, so skipped ancestors correctly cascade. -
Commit-to-PR lookup hit indexing lag. On run #26 the step fired at 22:41:36 — roughly 15s after PR #108 merged — and the API returned an empty result for the merge-commit SHA, producing "No PR associated with — direct push or unrelated event, skipping". Manual re-query a few minutes later returned PR #108 correctly, confirming eventual consistency is the cause.
Fix: retry the commits-to-PR lookup up to 5 times with 6-second backoff (30s worst case) before declaring "no PR". Adds a little latency to direct-push skips, but avoids falsely reporting no PR when one exists.
Both changes are isolated to .github/workflows/release.yml. No other surface area. User-facing behavior unchanged (add release:* label, merge, bot releases).
-
-
build: add Groovy/Spock/HubitatCI test harness (#69) (#81, @kingpanther13)
Sets up a Gradle wrapper (8.10), Spock 2.3-groovy-2.5, and HubitatCI 0.17 (via Azure Artifacts public feed) as the test harness for upcoming unit-test work. Adds a trivial SanitySpec to prove the toolchain.
HubitatCI uses Groovy 2.5 (not 4.0) — dependency versions are aligned accordingly. Azure Artifacts feed is publicly readable without credentials.
Part of #69.
-
test: gateway proxy dispatch + JSON-RPC envelope + resolution paths (#82, @kingpanther13)
Covers handleGateway's catalog mode, unknown-gateway / unknown-tool errors, recursive-gateway-call prevention, missing-required soft error (Option D), and valid dispatch via manage_rooms.list_rooms.
Part of #70.
-
ci: silence Groovy 2.5 reflective warnings + run Gradle daemon on JDK 17 (#83, @kingpanther13)
…recations
Two orthogonal diagnostics rolled into one PR so we can see the full warning story in a single CI run:
-
build.gradle — add jvmArgs '--add-opens=java.base/java.lang=ALL-UNNAMED' to the test task. Groovy 2.5.23's CachedClass reflects on Object.finalize(), which JDK 11+ flags with "illegal reflective access" warnings on every run. We're pinned to Groovy 2.5 by hubitat_ci's compiled-against version; the open keeps the access legal without changing behaviour. Removable if/when hubitat_ci moves to Groovy 4.
-
unit-tests.yml — pass --warning-mode all to ./gradlew test. Gradle currently prints "Deprecated Gradle features were used" without naming them; this surfaces the specific deprecations so we can decide which are on our side vs upstream (hubitat_ci / groovy plugin) before Gradle 9 removes them.
Expected outcome: the reflective-access warnings disappear; the deprecation block expands into itemised warnings we can triage.
-
-
chore: migrate hubitat_ci to joelwetzel fork + Dependabot + version-check (#85, @kingpanther13)
…heck
Moves off biocomp/hubitat_ci:0.17 (upstream dormant on Maven side since 2019) to joelwetzel/hubitat_ci master (active through 2026-03, v0.49), consumed via JitPack pinned by commit SHA a62a351eed.
Fork selection — joelwetzel over eighty20results:
Evaluated both active forks. eighty20results has bigger upsides (Groovy 3.0, drops the 14-entry --add-opens block from PR #83, Hubitat-behavior correctness fixes) but also has substantive breaking API changes in child-app handling: requires a new childAppResolver closure on HubitatAppSandbox.compile, replaces InstalledAppWrapperImpl's methodMissing dispatch so parent.findDevice no longer works transparently, re-architects getChildApps through a registry instead of AppExecutor delegation.
Confirmed on a trial migration: three of our existing specs (ToolCreateRuleSpec, ToolUpdateRuleSpec, RuleEngineSmokeSpec) fail under eighty20results with IllegalArgumentException: 'childAppResolver is required', 'Rule not found: 42', and MissingMethodException on InstalledAppWrapperImpl.findDevice respectively. Fixing those requires harness rework (HarnessSpec child-app wiring, rewriting RuleEngineSmokeSpec to avoid parent.findDevice, figuring out the new getChildApps registry model) with uncertain scope.
joelwetzel preserves biocomp's API exactly: HarnessSpec, ToolSpecBase, PermissiveLog, and all specs under src/test/groovy/ compile and run without changes. Still on Groovy 2.5 (matches Hubitat runtime 2.4 closer than 3.0), so PR #83's --add-opens block stays. Version 0.49 with 127 commits beyond biocomp; adds integration-testing framework (time control, device fixtures, scheduler simulation) as a future-facing bonus.
Rolled in:
- dep: me.biocomp.hubitat_ci:hubitat_ci:0.17 -> com.github.joelwetzel:hubitat_ci:a62a351eed
- repo: Azure Artifacts -> JitPack (scoped to com.github.joelwetzel)
- byte-buddy: 1.14.18 -> 1.18.8 (stale patch)
- .github/dependabot.yml: github-actions weekly + gradle monthly with ignores for the pinned testing deps
- .github/workflows/hubitat-ci-version-check.yml: weekly poll of joelwetzel master HEAD with tracking issues on new commits
Supersedes #84 (biocomp-oriented Dependabot config).
-
docs: credit biocomp and joelwetzel for the test harness (#88, @kingpanther13)
Two updates:
-
README.md Acknowledgments: add bullets for biocomp/hubitat_ci (original Apache 2.0 framework our harness is built on) and joelwetzel/hubitat_ci (the actively-maintained fork we consume via JitPack).
-
docs/testing.md: update the testing framework reference to point at joelwetzel's fork (via JitPack coord) while preserving the link back to biocomp as original. Fixes stale references to HubitatCI 0.17 from the Azure Artifacts feed that are no longer accurate post PR #85.
-
-
build(deps): bump the github-actions group with 5 updates (#87, @app/dependabot)
Bumps the github-actions group with 5 updates:
Package From To actions/checkout 46actions/setup-python 56actions/setup-java 45gradle/actions 46actions/upload-artifact 47Updates
actions/checkoutfrom 4 to 6Updates
actions/setup-pythonfrom 5 to 6Updates
actions/setup-javafrom 4 to 5Updates
gradle/actionsfrom 4 to 6Updates
actions/upload-artifactfrom 4 to 7
updated-dependencies:
- dependency-name: actions/checkout dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions
- dependency-name: actions/setup-python dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions
- dependency-name: actions/setup-java dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions
- dependency-name: gradle/actions dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions
- dependency-name: actions/upload-artifact dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major dependency-group: github-actions ...
Signed-off-by: dependabot[bot] support@github.com
-
build(deps): bump gradle-wrapper from 8.10 to 9.4.1 in the gradle-dependencies group (#86, @app/dependabot)
Bumps the gradle-dependencies group with 1 update: gradle-wrapper.
Updates
gradle-wrapperfrom 8.10 to 9.4.1
updated-dependencies:
- dependency-name: gradle-wrapper dependency-version: 9.4.1 dependency-type: direct:production update-type: version-update:semver-major dependency-group: gradle-dependencies ...
Signed-off-by: dependabot[bot] support@github.com
-
build: assignment syntax for url + exceptionFormat (Gradle 10 prep) (#89, @kingpanther13)
Gradle 9.4 (landed in PR #86) deprecated the Groovy-DSL space- assignment syntax 'propName value' — it'll be removed in Gradle 10. Our build.gradle had two instances that surfaced on every CI run as deprecation warnings:
- 'url 'https://jitpack.io'' -> 'url = 'https://jitpack.io''
- 'exceptionFormat 'full'' -> 'exceptionFormat = 'full''
No semantic change; just the assignment form the current and future Gradle DSLs want. Clears the deprecation warnings from the Unit Tests log and means the next major-Gradle Dependabot bump doesn't fail the build.
-
docs(futureplans): correct two 'infeasible' claims contradicted by Hubitat docs (#90, @kingpanther13)
…bitat docs
Two items were previously marked as requiring workarounds (polling) or infeasible (ICMP ping) based on incorrect assumptions about the Hubitat sandbox. Both are officially supported from apps per the developer docs and should be implemented as native, first-class features.
Previously framed as "Hubitat does not expose a native subscribe() for hub variable changes" with polling as the recommended approach. Per the Hub Variable API docs at docs2.hubitat.com/en/developer/interfaces/hub-variable-api, hub variable changes fire Location Events named "variable:" that apps can subscribe to directly:
subscribe(location, "variable:<name>", handler) subscribe(location, "variable:<name>.value", handler)No polling, no Variable Connector workaround. Downgraded difficulty from 3/M to 2/S and rewrote the implementation plan to use native subscription plus addInUseGlobalVar() for in-use registration and globalVarRenamed() for rename handling.
Also fixed the downstream reference in "Variable change events" that pointed at the now-removed polling path.
Previously struck through as "Not directly feasible. Hubitat's sandboxed Groovy environment does not provide ICMP, HubAction, raw sockets, or Runtime.exec()." The NetworkUtils helper class, documented at docs2.hubitat.com/en/developer/networkutils-object, exposes hubitat.helper.NetworkUtils.ping(ipAddress, count) to both apps and drivers, returning a PingData object with rttAvg/Min/Max, packetsTransmitted, packetsReceived, and packetLoss.
Un-struck the item, changed difficulty from 3/M to 1/S, and rewrote it as a first-class ping tool + rule action. The HTTP reachability check item is kept as a complementary action for hosts that don't respond to ICMP or where HTTP-layer health matters, not as a replacement.
Removed the "Ping IP address" entry from the Infeasible Items Summary.
Promoted both items into Phase 1 quick wins (items 2 and 3) and renumbered the downstream list. Removed "Hub variable change triggers — Polling trade-offs" from Phase 4 since it is now a Phase 1 item.
-
test: rule-engine primitive specs (closes #71) (#98, @kingpanther13)
Three specs covering the rule engine's condition layer:
-
EvaluateComparisonSpec — every operator branch of the pure evaluateComparison(current, operator, target) helper: equals/not_equals, </>/>=/<=, null-current handling (null+equals+null=true, null+numeric=false-fail-closed), numeric-conversion fallback to string equality, unknown-operator fallback.
-
EvaluateConditionSpec — single-condition dispatch for device_state (via parent.findDevice + device.currentValue + evaluateComparison) and variable (local atomicState first, parent.getVariableValue fallback). Unknown-condition-type fail-closed. Other types (mode, time_range, sun_position) need location-mock wiring and are deferred to #75.
-
EvaluateConditionsSpec — plural aggregator: settings.conditionLogic "all" vs "any" semantics, vacuous-truth on empty list, default-to- "all" when unset, fail-closed on per-condition exceptions via the ThrowingParent fixture.
Part of #71.
-
-
chore: migrate test harness from joelwetzel to eighty20results/hubitat_ci (#100, @kingpanther13)
Switch the Groovy test harness off joelwetzel/hubitat_ci (Groovy 2.5 fork, consumed by commit SHA) onto eighty20results/hubitat_ci v0.28.6, an actively-released Groovy 3.0 fork with first-class hubitat.helper.RMUtils mapping.
build.gradle:
- eighty20results via JitPack, pinned by release tag
- Groovy 3.0.25 on the test classpath (was 2.5.23), Spock 2.3-groovy-3.0
- byte-buddy 1.18.8 retained for Spock class-gen
- Drops the main-source-set groovy-all dep (no stubs needed — the fork ships common_api.RMUtils and maps the sandbox lookup natively)
- Drops the full --add-opens jvmArgs block: Groovy 3 is module-aware, so the JDK 11+ reflective-access warnings that forced the PR #83 workaround don't reappear
HarnessSpec / RuleHarnessSpec:
- Supply childAppResolver closure so the sandbox options validator accepts our invocation (eighty20results requires this even when no child app is actually built)
- Override script.metaClass.addChildApp + getChildApps: eighty20results' HubitatAppScript defines both as concrete methods that route through a private childAppRegistry, bypassing the @Delegate → AppExecutor path our mock relies on
- RuleHarnessSpec.setParent() now also calls script.setParent(): the new
HubitatAppScript resolves the
parentproperty via a @CompileStatic getProperty override that reads its own private field, not via the @Delegate chain, so mock-based parent stubs don't propagate
-
fix: drop PR reference from packageManifest.json releaseNotes (#80, @kingpanther13)
HPM shows the manifest's releaseNotes string to end users when they consider installing an update. PR numbers, URLs, and author handles are developer-facing metadata that doesn't belong in that UI — users want to see WHAT changed (commit message content), not the GitHub PR link. Stripping the '(#NN, @author)' suffix from each title when flattening to manifest format.
CHANGELOG.md keeps the full PR reference (link + author) because that's dev-facing and clicking through to the PR makes sense there. README Version History also keeps PR refs for the same reason.
The commit-message content (title + main commit's extended body) remains in the manifest — that's what HPM users actually need.
-
feat: include PR main-commit extended description in release notes (#78, @kingpanther13)
CHANGELOG bullets now render as:
-
(#NN, @author)
<extended commit description from the PR's main commit, indented 2 spaces as a list-item continuation>
Source is the first branch commit's messageBody via 'gh pr view --json commits' — NOT the PR body, and NOT the merge commit body. The merge commit body under merge-commit strategy is usually just the PR title (useless noise); the PR body is often a template with lots of scaffolding; the commit body is where authors actually explain the WHY in focused prose.
If the main commit has no extended body, the bullet is just the title line — we don't walk past the first commit looking for one, because surfacing a fixup's body instead would misrepresent the PR.
Also:
- manifest releaseNotes: flattens both the title and extended body into 'Title (#NN (url), @author): body...' per PR, PRs separated by ' / '.
- README Version History: compact (titles + PR refs only, no body) so it stays scannable. Rich content lives in CHANGELOG + manifest.
- Strips 'Co-authored-by:' and '🤖 Generated with Claude Code' trailers so autogenerated noise doesn't land in the changelog.
-
- fix: release workflow pushes the version tag explicitly (#67, @kingpanther13)
- chore: drop unused json import in sandbox_lint.py (#68, @kingpanther13)
- Release automation: bot-driven version bumps + CHANGELOG + release notes sync (#66, @kingpanther13)
- Enriched
list_devicessummary: new fieldsdisabled(bool),deviceNetworkId,lastActivity(ISO timestamp), andparentDeviceIdreturned on every device without requiring per-deviceget_deviceroundtrips. Newfilterarg acceptsall(default),enabled,disabled, orstale:<hours>for server-side filtering before pagination. Group client-side onparentDeviceIdto derive child counts. Closes the N+1 roundtrip problem for common bulk questions. (#63, @level99) - Fix
get_hub_logsordering: now returns most recent entries first (was returning oldest from the hub's ring buffer, making recent activity invisible). NewdeviceId/appIdserver-side scope args for the hub's/logs/past/jsonendpoint — ~93% payload reduction when scoped to a single source;deviceIdandappIdare mutually exclusive. (#64, @level99)
- New
search_tools: BM25 natural language search across all 74 MCP tools (core + gateway sub-tools). Searches tool names, descriptions, and parameter names. Returns matching tools ranked by relevance with gateway attribution. Inspired by FastMCP 3.1 Tool Search transform. 74 MCP tools total (31 ontools/list).
Older releases (pre-0.9.1) are documented in packageManifest.json releaseNotes and README Version History. They may be migrated into this file opportunistically.