54 Commits
Author SHA1 Message Date
Nikolay MiroshnichenkoandClaude Opus 5 a3f436fc74 generate_ha: one climate entity per physical device, fail closed when devices cannot be told apart
Found on a real 1312-GA house: a room with floor heating, a convector and an AC unit became
ONE climate (convector setpoint, AC controller mode, AC fan speed as the valve). Room 1.09
matched 2.09 and a central "09." GA, "Kids room 1" took "Kids room 2 temperature", and valves
came from light brightness and blind position statuses. The zone was the room alone: device
words were stripped as qualifiers, "А/С" vanishes in the tokenizer, the floor digit of "1.09"
is dropped.

Now a climate is assembled per room AND device type (floor, wall, radiator/convector, fan
coil, AC; RU/EN/DE words, removed from the zone as whole words):
- room code "N.NN" only at the start of a name, and it must match on both sides;
- standalone numbers of the anchor must be in a device member; a shared room sensor's
  numbers must be among the anchor's; digits in dotted codes and values do not count;
- an untyped GA is shared room data: any role if the room has one device, otherwise only
  the current temperature, which several devices may reuse; with a room code the room's
  untyped sensor qualifies even without the device's extra words;
- a word that sets another device of the room apart keeps its GAs away;
- control roles must be unambiguous: more than one candidate -> review climate_ambiguous;
- two mode GAs that cannot be told apart -> review climate_duplicate_anchor, not dropped;
- only 20.102 / 20.105 anchor; valve needs a valve word; AC never gets a valve;
- stable address order, so results never depend on parse order.

Two gates. Own audit on seven real projects, every changed entity inspected: no control GA
used by two climates, no address lost; house 16 -> 33 climates (one per device), manual
review 33 -> 12; flat 7 -> 9; HDL 3 -> 5; demo 6 -> 13; villa 23 -> 21 with the 3 it cannot
disambiguate sent to review instead of guessed. LLM council (three models + devil's
advocate, sanitised packet): accept with hardening; its P0s (same-type devices, silent
first pick) and P1s (mid-name codes, dotted digits, non-102/105 anchors, generic valve
words) are all in this commit, with tests E and F covering its failure catalogue.
Out of scope, recorded: identically named rooms of different flats without room codes, and
lexical identity in general -> device-channel identity ("lever 0").

Also works around a mypy 2.3.1 parse error on a comment right after a compound if.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 19:20:49 +02:00
Nikolay MiroshnichenkoandClaude Opus 5 76249469bb generate_ha: a command GA the actuator reports on is its own state address
Found on a real 1312-GA house (43 of 159 "switch without status" had a Read+Transmit
object on the command GA), measured on all seven corpus projects.

detect_missing_status already counted these as satisfied through pairing.self_reporting();
the generator did not and put them on the review list without a state_address. It now
uses the same function: no separate status found and the command GA has an R+T object
-> state_address = the command GA. Same for the on/off sibling of a dimmable light and
for brightness_state_address on a self-reporting brightness GA. Write-only objects still
go to review.

Corpus: no entity count changed, no address lost; review items 598 -> 555 on the house,
-36, -40, -12, -1, -32 on the others. Test C in test_real_house_fixes.py covers R+T,
write-only and a non-lighting switch.

Not verified on a live Home Assistant: state_address equal to address is accepted by the
KNX schema and xknx reads from it, but no running instance was used here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 18:34:40 +02:00
Nikolay MiroshnichenkoandClaude Opus 5 cb226a008f generate_ha: on/off lighting becomes a light; brightness-only "lights" go to review instead of invalid YAML
Found on a real 1312-GA house, measured on all seven corpus projects.

B. Lighting on/off (1.001, lighting category) is emitted as a Home Assistant light with
   address + state_address, not a switch. The HA KNX light platform documents exactly
   that ("Simple light"). suggest.py already proposed light for these; the two engines
   disagreed. Without status it is reported as light_without_status.

D. A 5.001 lighting GA with no on/off GA in its zone produced a light with only
   brightness_address. Home Assistant requires address on a KNX light, so that YAML was
   invalid. 51 of them across six projects, mostly motion-detector parameters on 5.001.
   They now go to review as light_without_switch, and the brightness status is no longer
   consumed for an entity that does not exist, so it surfaces as not_mapped.

Corpus, before -> after: lights without address 51 -> 0 everywhere; no address lost from
YAML or review on any project; switch -> light moves of 220 (house), 205, 62, 43, 17, 10,
13. test_pipeline B1 fixture gains its on/off GAs (it encoded an invalid light) plus a
test for the review route. CHANGELOG also records fix A from 481bab0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 18:32:42 +02:00
Nikolay MiroshnichenkoandClaude Opus 5 80f490715c changelog: replace real project labels with neutral size labels
Two confidential client projects were named by town in a 0.8.1-era entry.
Same numbers, labelled by size instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 09:07:23 +02:00
Nikolay MiroshnichenkoandClaude Opus 5 9000fc0b7f 0.8.2
load_ga_export (32nd tool), multi-address entity naming, climate setpoint shift,
suggest_repairs crash fix, CI runs the whole test suite. Details in CHANGELOG.

Pre-release checks: corpus guard no drift on six real projects; twine check PASSED
for wheel and sdist; mcp-name marker present in METADATA; wheel carries ga_export
and no tools/tests/corpus files; sdist has no corpus map or project files; a clean
isolated venv from the wheel imports 0.8.2 and exposes load_ga_export. CHANGELOG
compare links added for 0.8.1 and 0.8.2.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 00:18:27 +02:00
Nikolay MiroshnichenkoandClaude Opus 5 ec63bcf106 load_ga_export, entity naming, climate setpoint shift; fix suggest_repairs crash; CI runs every test
Three things found by auditing a TapPlan export, plus one regression found on the way.

load_ga_export(path): new tool. Reads an ETS ga-export/01 XML (ETS "Export Group
Addresses", or the import file a planning tool writes) into a project without
devices, through safe_fromstring, capped at 50 MB and 8 levels of range nesting.
Invalid or duplicate addresses and unknown DPT tokens go to import_warnings, never
silently. Round trip with our own generate_ets_xml is covered by a test.

Entity naming: lights, covers and climates are named after the common word prefix
of their member names, cutting only function words. The first version of the rule
turned "01. <room> - All Blinds - Move" into "01" on a real project, so anything not
in the function vocabulary now stays. On six real projects 214 of 1189 entities got
a shorter name, each rename reviewed, no address mapping changed. Regenerated
packages show different names; noted in the changelog.

Setpoint shift: 9.002 / 6.010 with a shift word maps to setpoint_shift_address,
setpoint_shift_state_address and setpoint_shift_mode (keys checked against the HA
KNX climate docs). It used to become a plain sensor.

Fixed: suggest_repairs raised UnboundLocalError on any project with a missing status
GA. My local rename in the 12.09 typing cleanup left two references to the old name.
test_council_fixes covers it but was not in CI, so it shipped. CI now runs all 21
test files instead of a hand-picked 10; the 11 added ones pass from a clean clone.

Verified: all 21 tests, ruff, mypy with the package installed, corpus guard no drift.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 00:11:45 +02:00
Nikolay Miroshnichenko 07352cdb61 suggest: split multi-output channels by vendor object marker; drop device diagnostics
- _subunit()/_split_subunits(): '[1] Switch On/Off' style markers separate the outputs a vendor
  packs into one ETS channel; each sub-unit is classified on its own (ids and secondary_info carry it)
- diagnostics (error/communication/firmware/version/bus voltage/reset, EN+DE+RU) never become sensors
  and are excluded from the name-based fallback; counted in hints.diagnostics_skipped
- real-project effect (3646 GAs): entities 341->513, sensors 752->367, platform agreement 92->95 %,
  address-key agreement 94->95 %
- tests: multi-output split (two lights, statuses consumed, no sensor fallout) + diagnostics filter
2026-09-12 09:09:08 +02:00
Nikolay Miroshnichenko 1ac0527c2c 0.8.1: published to PyPI and to the official MCP Registry
- server.json name fixed to io.github.NickoScope/nickol-knx-mcp (registry permissions are case-sensitive)
- README mcp-name marker in the same case; 0.8.0 had it lowercase, which the registry rejects
- version bumped in pyproject + __init__; CHANGELOG 0.8.1
- registry: io.github.NickoScope/nickol-knx-mcp v0.8.1 active, package pypi/nickol-knx-mcp 0.8.1
2026-09-12 09:00:12 +02:00
Nikolay Miroshnichenko f83819c434 changelog: merge duplicated Added heading 2026-09-12 08:41:24 +02:00
Nikolay Miroshnichenko 3d134b3bc7 registry: server.json + mcp-name ownership marker in README (official MCP Registry prerequisites)
- server.json: schema 2025-12-11, registryType pypi, name io.github.nickoscope/nickol-knx-mcp,
  stdio transport, NICKOL_KNX_WORKSPACE / NICKOL_KNX_CATALOG documented as optional env vars
- README: <!-- mcp-name: io.github.nickoscope/nickol-knx-mcp --> (becomes the PyPI description,
  which is what the registry checks for ownership)
- verified: uv build produces sdist+wheel, twine check PASSED, marker present in wheel METADATA
2026-09-12 08:40:55 +02:00
Nikolay Miroshnichenko aea59066ba suggest.py: structure-first entity suggestions prototype (HA SuggestionProvider #2 contract)
- channel -> object flags (write=sink, transmit=source, dual ignored) -> DPT pattern
  (cover, climate, light/switch, sensors) -> vendor object texts -> names as tie-break
- FB-covered channels skipped (also in fallback); shared status GAs excluded;
  duplicate writers deduped; pseudo-channels for channel-less devices; unwired flagged
- tests/test_suggest.py: FB-provider nameless fixture reproduced from structure (CI)
- tools/eval_suggest.py: agreement vs name/Function engine on real projects
- CHANGELOG (Unreleased/Added, experimental)
2026-09-10 12:18:49 +02:00
Nikolay Miroshnichenko a32b1970f3 check_policy: example profile seeded from the loaded project's own main groups (issue #13)
- example_policy_yaml(project): mains written with inferred domain, range name and
  category mix; no-majority mains commented-out with their mix; defaults only as a
  labelled comment; reserve.expect_range follows the project; empty/2-level -> {}.
- taxonomy_seed() is the single source for _infer_taxonomy and the example.
- server.check_policy(write_example_to) passes the loaded project, reports seeded_from.
- tests/test_policy.py: cases 4-6 (no leaked mains, round-trip, mixed main, quotes, no project); added to CI.
- README/README.ru/CHANGELOG.
2026-09-09 20:03:32 +02:00
Nikolay MiroshnichenkoandClaude Opus 4.8 8312d531eb feat: check_topology — per-line device count + individual-address validity (KNX canon)
New design-time check grounded in the KNX standard (KNX Handbook + xknx via
deepwiki). detect_topology_issues() + check_topology() MCP tool (30->31 tools),
folded into analyze_all counters:
- devices per line vs TP1 segment 64 / line 256 (KNX Handbook p.36/40/55) — TP-only
- individual address valid A.L.D (area 0-15, line 0-15, device 0-255; xknx-confirmed)
- duplicate individual address -> error; missing .0 coupler on a multi-line TP -> info
Medium-gated on the real xknxproject string ('Twisted Pair (TP)'), so IP/PL/RF
lines are never falsely flagged (fixed a dead 'medium==IP' guard caught in audit;
regression test added). senior-code-audit: APPROVED. Functional smoke on 2 real
projects: IP lines clean, TP coupler/count notes correct. 64/256 cited to the
Handbook, not attributed to xknx. tests/test_topology.py 7/7; suite 27 passed
(pre-existing unrelated test_protocol collection error untouched).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 21:41:14 +02:00
Nikolay1andClaude Opus 4.8 1a44f348be room-library R1: docs + packaging (README 28->30, CHANGELOG, package-data)
Document both new tools in README (tool count 28 -> 30), add a CHANGELOG entry
under Unreleased, and ship the room_templates/*.yaml + SCHEMA.md as package data.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 21:26:23 +02:00
Nikolay1andClaude Opus 4.8 ccfedbdeca fix(P5): dogfood round 2 — presence is a sensor, 9.001 soft, passive-range rule
- DPT 1.018 occupancy: category sensor (was diagnostics; the name always says
  sensor and the table fought it into a fake conflict).
- 9.001 temperature is SOFT: room temperature defaults to hvac, but a weather-
  named temperature re-domains to sensor without a fabricated conflict.
- illuminance terms: "освещённость"/"illuminance" are sensor words and must not
  be swallowed by the greedy lighting prefix "освещ".
- passive-range rule: a measurement inside a Sensors/Energy/Diagnostics main
  takes that main's domain; a COMMAND is never retyped by a passive range.

Deliberately NOT tuned further: remaining real-project findings (Minsk 10/685,
Razdory 75/3646, demo 2/239) are defensible mixed-main observations a reviewer
should see once — zeroing them out would overfit one integrator's idiosyncrasy.
13/13 tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 11:33:20 +02:00
Nikolay1andClaude Opus 4.8 3c086fe228 fix(P5): classifier dogfood round — range-map depth bug, fallback-DPT softness, RU terms
Building a deliberately messy synthetic house instantly exposed four real gaps:

- _build_range_name_map: a middle group 0 starts at the same raw address as its
  main, so the modulo heuristic let "Вкл/Выкл" clobber "Освещение" and the
  main-range rescue silently died. Depth in the range tree decides now.
- a category from a whole-main DPT fallback (e.g. 20.609 -> hvac) was treated as
  strong; only an exact (main,sub) table entry is strong now (is_exact_dpt).
- DPT 1.010 start/stop marked soft: ventilation timers use it, not just shutters.
- terms learned: Cyrillic а/с, сплит, вытяжк, яркост (RU only); weather/метео in
  sensor, and sensor now outranks hvac so weather-station temperature is a
  sensor, not an HVAC actuator.

Real-project noise after the round: Minsk 1 outlier/685 GAs (was 31 pre-P5),
demo 2/239, Razdory 46/3646. Regressions in test_domain_classifier. 13/13 green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 11:27:22 +02:00
Nikolay1andClaude Opus 4.8 e9105d45aa feat(P5): contextual domain classification — DPT is one signal, not the verdict
A 1-bit switch is domain-agnostic, yet DPT 1.001 defaulted to 'lighting', so an
"AC on/off" was classified lighting and tripped downstream checks. The domain is
now a combination: explicit name > strong domain-encoding DPT > main-group name;
a bare 1-bit GA with no signal is honestly 'unknown', never a guessed lighting.
An explicit name contradicting a STRONG DPT yields 'unknown' (a genuine conflict,
shown by explain_ga as contested), not a silent pick. Word-boundary matching so
"ac" no longer fires inside "terrace"; name terms broadened (EN+RU HVAC incl.
ac/a-c/air-conditioner, RGB/colour lighting).

check_policy taxonomy-outlier now flags only misplaced ACTUATORS (lighting/
shutter/hvac); sensors, scene links, thresholds and timeout params are the
cross-cutting GAs they are. HA generator still emits a switch for a now-HVAC
simple on/off, so no device is dropped.

Verified on 3 real projects: every AC on/off is HVAC; outlier noise fell demo
11->2, Minsk 31->6, Razdory 200->57. tests/test_domain_classifier.py (13/13 green).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 10:44:04 +02:00
Nikolay1andClaude Opus 4.8 1a60264c78 feat(P7b): explainable aggregate scores — expose denominator + formula
Matter readiness, the completeness grade and command/status coverage each now
carry a `math` block: numerator, denominator, the exact formula that reproduces
the percentage, and (for Matter) the functions excluded from the denominator
because their category has no Matter cluster — previously a silent skip and the
biggest "why is this number what it is?" gap. Completeness also states its band
thresholds. Report-only, additive; scores unchanged, only now auditable.
Raised by external review. tests/test_explainable_aggregates.py (12/12 green).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 10:15:40 +02:00
Nikolay1andClaude Opus 4.8 f68c85bb48 feat(P7a): harden parsing of untrusted .knxproj (zip-bomb / XXE defense)
A .knxproj is a user-supplied ZIP-of-XML — untrusted input. New safexml.py
centralises hardened parsing for every place we open an archive or parse XML
ourselves (load_project, check_device_parameters, app-program parser):

  XML  - dependency-free reject of DOCTYPE/ENTITY (billion-laughs / XXE;
         legitimate ETS XML never carries one) + defusedxml parser-level
         blocking when installed (added as a dependency).
  ZIP  - pre-flight against absolute caps (archive size, entry count, per-member
         and total decompressed size, compression ratio) rejects zip-bombs;
         member names checked for path-traversal / absolute paths; each member
         read through a streaming cap so a lying header can't exhaust memory.

Violations return a normal error dict, not a traceback. Still read-only.
Raised by external security review. tests/test_safexml.py (11/11 suite green).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 10:11:38 +02:00
Nikolay1andClaude Opus 4.8 84c1c7dbd9 fix(P3): root-cause suppression — an empty-name GA no longer cascades
Council #2: the empty GA 2/5/2 spawned missing_status + policy_taxonomy_outlier +
a lighting classification on top of its real defect. An empty name means the
classifier can't be trusted, so detect_missing_status and check_policy now skip
blank-name GAs; the single root cause is empty_name (check_naming). Demo 2/5/2:
3 findings -> 1. tests/test_root_cause.py; full suite green (10 tests).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 09:29:54 +02:00
Nikolay1andClaude Opus 4.8 10169275e3 feat(P1): explain_ga — provenance/confidence for one GA (27->28 tools)
Three independent reviewers (two external councils + a field integrator) asked for
the same thing: the enriched model mixes ETS facts, DPT structure and name
heuristics, and downstream tools treat it almost as fact. explain_ga makes the
reasoning auditable per GA — evidence per decision with a confidence tier
(authoritative ETS Function > structural DPT > heuristic name), the status-pairing
method, and CONFLICTS (name 'AC' vs DPT 'lighting' -> contested), the silent-
misclassification hotspot. Additive, read-only, no core-model change. Full suite green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 09:27:10 +02:00
Nikolay1andClaude Opus 4.8 5036a19b4b feat(P2): role-aware feedback completeness — catch a value command with no value status
'Does the function have *a* status' missed a dimmer with on/off status but no
brightness status (demo planted error #3), silently inflating coverage/Matter.
detect_role_completeness flags a brightness/position command (5.001) whose device
has no matching value status (missing_value_status), with a device-identity match
so it doesn't borrow a sibling's status. Demo recall 4/5 -> 5/5. Raised by the
external expert review. Test + demo ground-truth updated. Full suite green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 09:23:02 +02:00
Nikolay1andClaude Opus 4.8 c53f388dcb fix: council-review noise cuts — scenes/central-macros exempt, language-matched repairs
External expert review (real run on the demo house) flagged: scenes (18.001)
wrongly marked missing_status + an unsafe DPT-1.011 status repair for them;
'All blinds down' warned while 'All lights off' was correctly INFO; and a Russian
'(статус)' suffix synthesised on an English project. Fixes: scene_no_status INFO
+ no synth status for scenes; broadened central-macro detection (generic 'all
blinds/shutters/...'); language-matched repair suffixes. Demo missing_status
warnings ~7 -> 1 genuine. tests/test_council_fixes.py; full suite green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 09:09:57 +02:00
Nikolay1andClaude Opus 4.8 1e924ede6f feat(param_check): significance layer — a focus list of config-value outliers
Rank findings by significance: config_value (setpoint/hysteresis/time/threshold —
an odd device is usually a real mistake) vs config_flag (mode/type) vs label
(per-room text, often intentional). New focus list surfaces the config-value clear
outliers first. On a real 275-device project this turns 422 raw outliers into a
9-item focus that includes the thermostats whose init-setpoint differs from their
siblings. Multilingual name hints (EN/DE/RU). Test updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 22:28:32 +02:00
Nikolay1andClaude Opus 4.8 81799169c5 feat: check_policy — Project Policy Profile (26->27 tools)
Validate a project against ITS OWN rules, not a universal standard. With a YAML
profile the declared main-group taxonomy / naming / pairing is authoritative;
with no profile the taxonomy is INFERRED from the project itself and GAs that
deviate from their main group's own majority domain are flagged — never against
an alien standard (a well-organised real 356-GA project drops from 329 false
mismatches vs the default to 31 genuine self-deviations). Answers the recurring
integrator critique that 'your best practices aren't universal'. Example profile
+ test_policy.py included.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 22:06:59 +02:00
Nikolay1andClaude Opus 4.8 4c36bc182e feat: check_device_parameters — cross-device parameter outlier QA (25->26 tools)
New module param_check.py + MCP tool check_device_parameters: reads per-device
ParameterInstanceRef values from the .knxproj project part (xknxproject does not
expose them), groups identical devices by application program, and flags the odd
one out — clear_outliers (a strong majority with a small minority, e.g. one
thermostat with a different setpoint/hysteresis) and split_configs (balanced
variants → review). Parameter names resolved from the app-program; encrypted
projects skipped honestly. Read-only, no ETS/bus. Validated on real 42-275-device
projects; synthetic test_param_check.py. Answers a community feature request.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 21:53:32 +02:00
Nikolay Miroshnichenko bb608af0b0 fix(ha-git-backup): pin core.sshCommand + repo-local known_hosts in install.sh
The nightly HA sync runs via shell_command in the Core container, not the SSH
add-on where install ran. Core has an empty known_hosts, so git push failed
"Host key verification failed" — the commit was made locally but never reached
GitHub. install.sh now seeds a repo-local known_hosts (ssh-keyscan) and pins
git config core.sshCommand so every context (add-on, Core, cron) uses the same
working ssh. Also drops a stray `.git-sync` arg from the pre-commit chmod line.

Records LESSON-06 + a symptom-table row in SKILL.md and SKILL.ru.md; CHANGELOG
Fixed entry. Field-validated on a live Home Assistant.
2026-07-09 19:19:14 +02:00
Nikolay MiroshnichenkoandClaude Fable 5 1aea8a6d9b feat(skills): ship ha-git-backup — two-circuit HA backup ops-companion skill
Circuit 1: real git in /config (deploy key, pre-commit secret scanner,
meaningful commits). Circuit 2: age-encrypted native HA backups in GitHub
Releases with rotation and a mandatory monthly restore drill. Ships
install/sync/scan/offsite/restore scripts, HA automations/snippets,
setup + architecture + incident + runbook references, EN + RU skill docs.
README/README.ru gain an ops-companion bullet in Scenario 3;
CHANGELOG [Unreleased] Added entry (no version bump).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 22:15:44 +02:00
Nikolay Miroshnichenko 74ac2ac830 feat(parser): read manufacturer names from the archive's knx_master.xml (dynamic map, static fallback) 2026-07-07 12:40:09 +02:00
Nikolay Miroshnichenko ff93d44166 feat: positional status pairing + self-reporting commands, app-program parser v2 (ComObjectRef merge, version pick), authoritative main names, grader range-name evidence (v0.8.0)
Closes #3, closes #4, closes #5, closes #6
2026-07-07 12:24:24 +02:00
Nikolay Miroshnichenko 4dae539d31 fix(deps): pin mcp<2 — SDK v2 renames FastMCP to MCPServer (breaking import) 2026-07-02 19:42:19 +02:00
Nikolay MiroshnichenkoandClaude Fable 5 5651be2930 feat: exact device models — local catalog for decompose_device + parse_devices_from_project (v0.7.0)
- device_library: NICKOL_KNX_CATALOG env points at a local device-library
  YAML file/dir; decompose_device returns the exact vendor object model
  (source: catalog-exact) and falls back to generic recipes
  (source: recipe-approximate). Env unset = behaviour unchanged.
- appprog_parser (new) + MCP tool parse_devices_from_project: deterministic
  extraction of exact comm-object models from M-* application programs in a
  .knxproj/.knxprod (order number via nested <Product>, DPST-x-y -> x.00y,
  per-channel block/stride detection, coverage manifest). Read-only,
  PII-safe: never reads the client P-*/0.xml. Now 25 MCP tools.
- tests: test_device_catalog.py + test_appprog_parser.py (synthetic,
  self-contained)
- docs: README/README.ru/docs site/announcements synced to v0.7.0, 25 tools

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 06:48:19 +02:00
Nikolay MiroshnichenkoandClaude Opus 4.8 224f44a9c5 docs: sync release surfaces to v0.6.0 (C4 consistency)
- CHANGELOG: fix stale compare-link footer (Unreleased→v0.6.0, add
  0.4.0/0.5.0/0.6.0 links)
- docs/index.html: bump hero version pill v0.2.0 → v0.6.0
- docs/announcements: update 'Current:' marker to v0.6.0 / 24 tools

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 21:17:40 +02:00
Nikolay MiroshnichenkoandClaude Opus 4.8 ab310b399a feat: matter/energy/diff/iot/completeness + climate & expose (v0.6.0)
Completes the roadmap. The tool now validates, repairs, generates
(HA/ETS/handover/IoT), diffs, grades and drafts acceptance protocols —
all design-time & read-only.

B-tier:
- B2 climate-correctness review note (controller/operation modes, setpoint
  shift cmd+state, mode-without-state flag) in generate_ha.py
- B3 semantic project diff (diffproj.py; diff_projects / diff_loaded)
- B4 per-function acceptance test protocol (advanced.py; generate_test_protocol)
- B5 Matter readiness (advanced.py; check_matter)
- B6 metering/energy DPT check + PV/battery/EVSE scaffold (advanced.py; check_energy)

C-tier:
- C1 KNX IoT Turtle/RDF export (iot.py; generate_knx_iot)
- C2 naming-hygiene suggestions (advanced.py; suggest_names)
- C3 as-built completeness grader (advanced.py; grade_completeness)

A-tier quick wins:
- A5 Areas/voice UI-only note; A6 time/date DPT-19.001 expose block (generate_ha.py)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 20:31:14 +02:00
Nikolay MiroshnichenkoandClaude Opus 4.8 1e130bbe19 feat: repair-suggestion engine + relative-dim + cover-invert (v0.5.0)
From validator to repairer: propose concrete fixes, not just flag problems.

- B1 repair-suggestion engine (repair.py) + suggest_repairs MCP tool: infer a
  DPT for a missing-DPT GA, correct a suspect sub-DPT, synthesise a status GA in
  a free slot, or add an absolute-brightness GA. Suggestions only; accepted new
  GAs feed generate_ets_group_addresses; never writes to ETS or the bus.
- A2 relative-only-dimming detector (analyze.py): 3.007 relative dimmer with no
  5.001 absolute-brightness GA in its zone -> HA cannot set a level.
- A3 cover invert/travel-time surfacing (generate_ha.py): verify_cover_invert
  review note lists actuator-dependent flags absent from the .knxproj and warns
  when position lacks a state address.
- A2/A3/B1 regression tests; CHANGELOG + version bump to 0.5.0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 19:37:26 +02:00
Nikolay MiroshnichenkoandClaude Opus 4.8 44e1ae3aa7 feat: sub-DPT linter + KNX Secure posture (v0.4.0)
A1 — sub-DPT sanity linter: when a GA name implies a specific DPT
sub-type (temperature->9.001, power->14.056, brightness/position->5.001),
flag a wrong sub or wrong main. Multilingual, conservative; surfaced as
the subdpt_suspect finding via check_dpt / analyze_all.

A4 — KNX Data Secure posture: secure_posture() + new check_secure MCP
tool. Report-only summary (secured vs plaintext counts, mixed
secure/plaintext middle groups, keyring handover checklist). Reads only
the per-GA Security flag; no key material touched. Handover pack section
5 rewritten to this posture section.

Version bump to 0.4.0; CHANGELOG updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 19:26:28 +02:00
Nikolay MiroshnichenkoandClaude Opus 4.8 b16492eb68 feat(handover): itemise QA findings (addresses + names), not just totals
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 18:51:01 +02:00
Nikolay MiroshnichenkoandClaude Opus 4.8 d7df3dce60 feat: device-library decompose_device + spec→structure methodology (v0.3.0)
Turn the tool from a .knxproj validator into a design aid.

- device_library.py + decompose_device / list_device_recipes MCP tools:
  expand a device (order number / type / alias) into its group-address
  recipe — command/status/dimming/position/mode objects with DPTs —
  across Zennio + ABB families. Generic vendor facts, typical-wired set.
- docs/spec-to-structure.md: the spec→structure methodology, with a
  measured account of what a spec reproduces (~90%) vs the per-device
  object count it cannot (2–9× per-project parameterisation).
- Ship alongside the Track B generate_handover_pack and the de-noise
  refinements accumulated since 0.2.0.
- CHANGELOG 0.3.0; version bump 0.2.0 → 0.3.0; new device-library tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 18:33:08 +02:00
Nikolay MiroshnichenkoandClaude Opus 4.8 2f2b8b1f91 fix(handover): read domain names from GroupRanges, not mislabeled GARecord.main_name
Main/middle range names now resolve correctly (e.g. [1] Освещение 1 этаж, [5] Климат)
instead of a middle-group name leaking into the main.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 14:54:44 +02:00
Nikolay MiroshnichenkoandClaude Opus 4.8 a621ced701 feat(handover): generate_handover_pack — as-built commissioning deliverable (Track B)
Assembles handover.md (equipment inventory, GA-domain map, feedback coverage,
KNX Secure scope, QA state) + standalone topology.svg + group-addresses.csv +
ha-package.yaml from a read-only .knxproj. Reuses existing analysis passes.
Regression test covers all 7 sections, inventory, summary and well-formed SVG.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 14:17:07 +02:00
Nikolay MiroshnichenkoandClaude Opus 4.8 87f065dde7 feat(analyze): de-noise [LF] objects, central macros, separator scratch
Three false-alarm downgrades derived from validating on a larger multi-vendor
villa, each turning a false positive into an INFO note without hiding any real
defect:

- intent: divider/separator names (punctuation-only or a marker wrapped in it,
  e.g. "-----addition------", "---") classify as `scratch`, so a missing DPT on
  them is INFO, not a red error.
- analyze: typed GAs wired into Zennio "[LF] ... Data Entry" type-agnostic
  logic-function containers surface as INFO `dpt_on_logic_object` instead of a
  false `dpt_mismatch_co` warning.
- analyze: all-groups broadcast/central-macro commands surface as INFO
  `central_macro_no_status` instead of `missing_status_address`, since a fan-out
  broadcast has no single state to read back.

Adds a regression block covering all three; real DPT mismatches and real
missing-status gaps still warn. Unreleased; no version bump.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 13:55:37 +02:00
Nikolay MiroshnichenkoandClaude Opus 4.8 44d7226bf1 chore(release): v0.2.0 — colour/climate assembly + GA-intent noise reduction
Bump version 0.1.2 -> 0.2.0 (pyproject + __init__). Promote the
Unreleased changelog section to [0.2.0] — 2026-06-30. Scrub a client
city label from two test comments (provenance note only, no PII/topology).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 21:28:37 +02:00
Nikolay MiroshnichenkoandClaude Opus 4.8 514caa5fd2 feat: colour lights + climate entities + status-pairing fix (Track A)
Colour control assembled into light entities (RGB 232.600, RGBW 251.600,
xyY 242.600, colour-temp 7.600) with statuses, matched by zone identity.
Climate entities generated from HVAC mode zones (temp 9.001 + target status
+ operation/controller mode 20.102/20.105 + valve command_value 5.001),
emitted only when HA-required keys are present, else routed to review.
B1 fix: a command no longer borrows a sibling's status (identity must nest,
status maps to exactly one entity) — removed all shared-status addresses.
New DPTs 232.600/251.600/242.600/7.600/20.105/1.100. HA keys verified vs docs.

Real files: signed demo 6 climate + 4 colour lights; Zennio 685-GA 7 climate;
zero shared-status; all 10 regression groups pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 20:05:59 +02:00
Nikolay MiroshnichenkoandClaude Opus 4.8 2ffb6d371f feat: GA-intent classification to cut real-project noise (Track D)
Classify every group address as functional/reserve/logic/scratch (intent.py)
and reclassify intentional non-functional GAs instead of crying wolf:
- reserve spare with no DPT -> INFO reserve_without_dpt (not a missing_dpt error)
- reserve name reused across DPTs -> no duplicate_name / inconsistent_dpt
- logic/virtual + scratch GAs -> excluded from missing-status warnings
dpt_mismatch_co and all real functional findings are untouched.

Driven by a real 685-GA Zennio project: false errors 29 -> 6,
missing-status noise 79 -> 45, all 12 real dpt_mismatch_co catches preserved.
Intent breakdown now exposed via list_group_addresses, report inventory and
the analyze_all summary. New regression test covers the reserve/logic/scratch
patterns and proves real DPT + status problems still surface.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 19:48:28 +02:00
Nikolay MiroshnichenkoandClaude Opus 4.8 eb50a8247f Add docs/ GitHub Pages landing site
Self-contained HTML showcase: hero, two-layer architecture, demo-house stats +
the 5 planted mistakes, an interactive 5-tab dashboard preview (Overview/Climate/
Lighting/Energy/Presence), the HA brain, and a testers CTA. .nojekyll included.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 19:46:40 +02:00
Nikolay MiroshnichenkoandClaude Opus 4.8 f41439f82e Add examples/demo-home: end-to-end worked example
A synthetic 239-GA / 47-Function demo .knxproj (ETS6 schema, parseable by
xknxproject) with 5 deliberate mistakes, the tool's generated artifacts
(project report, Home Assistant entities, ETS XML/CSV), and a ha-brain/ smart
layer: circadian lighting, an 8-factor climate setpoint, presence/season/time
state machine and statistics. Whitelist the demo .knxproj past the *.knxproj
gitignore (synthetic, no real topology/secrets).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 12:01:15 +02:00
Nikolay MiroshnichenkoandClaude Opus 4.8 68654dde05 Release v0.1.2: HA mapping quality
Bump to 0.1.2 and finalize CHANGELOG. Second hardening pass (round-2 public
ETS4/5/6 fixtures): complete dimmable lights, wider shutter detection
(1.001/1.017 + zone-identity guard), and date/time/text DPT routing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 11:29:40 +02:00
Nikolay MiroshnichenkoandClaude Opus 4.8 64bb4c87e6 HA mapping: full dimmable lights, wider shutters, datetime DPTs
Round-2 fixes found by running against more public ETS4/5/6 fixtures (yene
DemoCase, KnxProjParser, knxray, open-knxviewer):
- Lights assemble both on/off and brightness status via identity-based pairing
  and fold the on/off command into one light entity (no duplicate switch),
  pairing even when device identity is a single name token. Switches too.
- Cover up/down recognised on 1.008 or any 1.x named up/down; stop on
  1.007/1.010/1.017 or a stop-name. Siblings attach only to the same shutter
  (zone-identity guard) so multiple blinds don't cross-wire.
- Date/time/text DPTs (10/11/16/19) route to review (manual_datetime/manual_text).
- Add regression tests for all three.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 11:26:09 +02:00
Nikolay MiroshnichenkoandClaude Opus 4.8 28ae21f09c Release v0.1.1: real-fixture hardening
Bump version to 0.1.1 and finalize CHANGELOG. This release bundles the fixes
found by running the tool against real ETS5/ETS6 project files: the critical
load_project recursion, the now-implemented ETS Function role pairing, no-silent
-drops in HA generation, smarter shutter/German classification, venetian slat
tilt handling, and diagnostics alarms as binary_sensors. 5 regression tests added.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 10:53:19 +02:00
Nikolay MiroshnichenkoandClaude Opus 4.8 1fc64a7a89 Polish shutter slats and diagnostics alarms (HA mapping)
- Venetian slat GAs (Lamelle/slat/tilt) attach to their parent blind cover as
  move_short_address instead of becoming standalone covers; unmatched slats are
  flagged shutter_slat_unattached for manual attachment.
- 1-bit diagnostics GAs (wind/frost/smoke/leak alarm, fault) become a read-only
  binary_sensor instead of a phantom command switch.
- Add regression tests for both.

Backlog item #6 from VALIDATION-FINDINGS, surfaced by real ETS fixtures.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 10:49:24 +02:00