app
KiCad - the KiCad Bridge
Public Made by Adomby adom
Reference implementation of the KiCad bridge: multi-instance Python server, forward path via kicad-cli, reverse path via in-process plugin. Most complex of the three bundled bridges.
← Commit history
v0.9.81: kicad_uninstall testResidue mode — the verb suite cleans up ALL its residue after every run (phase 7)
6 files changed
+104−8
BRIDGE_VERSION+1−1@@ -1 +1 @@-0.9.80+0.9.81
adom-bridge-kicad-manifest.json+5−5@@ -1,15 +1,15 @@ { "manifest_version": 1, "name": "kicad",- "version": "0.9.80",- "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.80/adom-bridge-kicad-v0.9.80.zip",- "sha256": "50bfae0f62908302026ad365d44d5037c5ca84a0c20ec98a1837d3435753de60",- "size": 375858,+ "version": "0.9.81",+ "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.81/adom-bridge-kicad-v0.9.81.zip",+ "sha256": "7aa4415764ca5187da26322ee5563f2367e54a6e05b02121c84b41f1347b40a7",+ "size": 376754, "verbPrefixes": [ "kicad_" ], "healthEndpoint": "/status",- "released_at": "2026-08-14T11:44:39Z",+ "released_at": "2026-08-14T12:59:59Z", "hero": "https://wiki.adom.inc/blob/app/kicad-bridge/kicad-hero.png", "languages": [ "Python",
adom-bridge-kicad-v0.9.81.zipadded⋯ 1 unchanged line ⋯
bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "kicad", "displayName": "KiCad EDA",- "version": "0.9.80",+ "version": "0.9.81", "description": "Reverse bridge for KiCad \u2014 board/schematic introspection, lint via kicad-cli, plugin install, multi-instance probe, in-process DRC.", "homepage": "https://wiki.adom.inc/adom/adom-bridge", "author": "Adom Inc.",
handlers/uninstall.py+90−1@@ -88,8 +88,20 @@ def _kicad_bin_dirs(kicad_info: dict) -> list[Path]: def handle_uninstall(kicad_info: dict, args: dict) -> dict:- """Remove every artifact this bridge created. Args: dryRun (default false)."""+ """Remove every artifact this bridge created.++ Args:+ dryRun (default false) — preview only.+ testResidue (default false) — remove ONLY what the daily verb suite+ creates (kicad-bridge-test skill): the verbtest fixture dir, the demo+ project, the generated ADOM_VERBTEST symbol/footprint inside the Adom+ libraries, the AdomVerbTest lib-table registration, and stale discovery+ files. Leaves the plugin, the Adom libraries themselves, and every+ user artifact alone. This is how the suite cleans up after every run+ (John, 2026-08-14: "clean up after yourself").+ """ dry = bool(args.get("dryRun"))+ test_residue = bool(args.get("testResidue")) removed: list[str] = [] failed: list[dict] = [] absent = 0@@ -118,6 +130,10 @@ def handle_uninstall(kicad_info: dict, args: dict) -> dict: except Exception as e: failed.append({"item": f"{label}: {p}", "error": str(e)}) + if test_residue:+ return _remove_test_residue(kicad_info, dry, rm_file, rm_tree,+ removed, failed, lambda: absent)+ # 1) plugin payload (+ pycache), 2) Adom symbol lib file, 3) Adom.pretty for ver_dir in _kicad_user_roots(): site = ver_dir / "3rdparty" / "Python311" / "site-packages"@@ -192,3 +208,76 @@ def handle_uninstall(kicad_info: dict, args: dict) -> dict: ".pyc open; re-run after KiCad exits if anything failed. Bridge removes its own " "bridges-cache during the cascade."), }+++def _remove_test_residue(kicad_info, dry, rm_file, rm_tree, removed, failed, absent_fn):+ """The suite-residue subset (see handle_uninstall docstring)."""+ up = os.environ.get("USERPROFILE")+ if up:+ rm_tree(Path(up) / "Documents" / "adom-kicad-verbtest", "verbtest fixture dir")+ rm_tree(Path(up) / "Documents" / "adom-kicad-demo", "demo project")+ # ADOM_VERBTEST entries inside the Adom libraries (surgical, not the libs)+ for ver_dir in _kicad_user_roots():+ rm_file(ver_dir / "footprints" / "Adom.pretty" / "ADOM_VERBTEST.kicad_mod",+ "test footprint")+ sym = ver_dir / "symbols" / "Adom.kicad_sym"+ if sym.exists():+ try:+ import adom_library+ text = sym.read_text(encoding="utf-8")+ blocks = adom_library._extract_top_level_symbols(text)+ hit = [b for n, b in blocks if n == "ADOM_VERBTEST"]+ if hit:+ if not dry:+ for b in hit:+ text = text.replace(b, "")+ sym.write_text(text, encoding="utf-8")+ removed.append(f"ADOM_VERBTEST symbol in {sym}")+ except Exception as e:+ failed.append({"item": f"ADOM_VERBTEST in {sym}", "error": str(e)})+ # AdomVerbTest lib-table registration (points at the deleted fixture dir)+ try:+ from lib_table import LibTable+ for cfg in _kicad_config_roots():+ for table_name in ("sym-lib-table", "fp-lib-table"):+ tp = cfg / table_name+ if not tp.exists():+ continue+ try:+ table = LibTable.parse_file(str(tp))+ if table.has_library("AdomVerbTest"):+ if not dry:+ table.remove_library("AdomVerbTest")+ table.write_file(str(tp))+ removed.append(f"'AdomVerbTest' row in {tp}")+ except Exception as e:+ failed.append({"item": f"'AdomVerbTest' row in {tp}", "error": str(e)})+ except Exception as e:+ failed.append({"item": "lib-table cleanup", "error": str(e)})+ # stale discovery files (dead pids)+ tmp = Path(tempfile.gettempdir())+ for f in tmp.glob("adom-kicad-bridge-*.json"):+ try:+ pid = int(f.stem.rsplit("-", 1)[-1])+ except ValueError:+ rm_file(f, "legacy discovery file")+ continue+ try:+ import ctypes+ h = ctypes.windll.kernel32.OpenProcess(0x1000, False, pid)+ if h:+ ctypes.windll.kernel32.CloseHandle(h)+ else:+ rm_file(f, "stale discovery file")+ except Exception:+ pass+ ok = len(failed) == 0+ return {+ "success": ok, "testResidue": True, "dryRun": dry,+ "removed": removed, "failed": failed,+ "alreadyClean": len(removed) == 0 and ok,+ "summary": f"{'would remove' if dry else 'removed'} {len(removed)} test-residue item(s), "+ f"{len(failed)} failed",+ "_hint": ("Suite residue only — plugin, Adom libraries, KiCad and user files untouched. "+ "Run after every verb-suite pass; idempotent."),+ }
skills/kicad-bridge-test/run_verb_tests.py+7@@ -368,6 +368,13 @@ def main(): if phase == 3 and not a.skip_gui: time.sleep(2) + # phase 7: clean up EVERYTHING the suite created on the box (John 2026-08-14)+ if not only and not a.skip_gui:+ cr = ab(a.target, "kicad_uninstall", {"testResidue": True}, timeout=120)+ print(f"[7] cleanup: {cr.get('summary') or cr.get('error') or cr.get('status')}")+ results["_cleanup"] = {"result": "PASS" if ok(cr) else "FAIL",+ "response_head": json.dumps(cr)[:400]}+ # skips recorded for non-phase entries for verb, spec in M.items(): if spec.get("skip") and verb not in results: