# FreeCAD Electrical Auto Routing Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Finish the first production-ready FreeCAD electrical auto-routing loop from QET `wires[]` tasks to saved `AutoSuggested` 3D wires with actionable diagnostics. **Architecture:** Keep FreeCAD as the 3D truth source and QET as the electrical truth source. QET exports richer `wires[]` task metadata; FreeCAD consumes that task list, prepares a route-carrier graph, runs Dijkstra, stores routed wires and diagnostics in `scene.FCStd`, and never writes 3D wire geometry into the database. **Tech Stack:** FreeCAD Python API, Qt/C++ QET export service, JSON exchange files, `unittest`, fake FreeCAD test harness, FreeCAD smoke scripts. --- ## Scope Check The approved spec touches two repos, but the work is still one coherent feature because QET only supplies task metadata and FreeCAD owns all routing behavior. Treat the QET change as a small upstream contract task. Do not run QET build commands in `D:\code\zwl`; that repo's `AGENTS.md` forbids build, cmake, make, and ninja. ## File Structure - Modify: `D:\code\zwl\sources\FreeCAD\FreeCADExchangeExportService.cpp` - Responsibility: include `start_instance_id` and `end_instance_id` in exported `wires[]` entries so FreeCAD can bind local template terminals more reliably. - Modify: `src/Mod/FreeCADExchange/WiringImport.py` - Responsibility: preserve `wire_style_id`, start/end instance IDs, and endpoint display metadata on `QETWiring_01_Tasks`. - Modify: `src/Mod/FreeCADExchange/WiringObjects.py` - Responsibility: provide shared helpers for wire task/routed wire metadata, including route length and diagnostic payload fields. - Modify: `src/Mod/FreeCADExchange/AutoRouting.py` - Responsibility: compute route length, carry `wire_style_id`, create diagnostic summaries, and enrich batch reports. - Modify: `src/Mod/FreeCADExchange/AutoRoutingPanel.py` - Responsibility: show the improved batch summary without adding extra workflow steps. - Modify: `tests/python/freecad_exchange_wiring_import_test.py` - Responsibility: lock `wires[]` task import metadata. - Modify: `tests/python/freecad_exchange_auto_routing_test.py` - Responsibility: lock auto-route metadata, diagnostic group output, and one-click reporting. - Modify: `tests/manual/freecad_auto_routing_smoke.py` - Responsibility: verify the end-to-end FreeCAD document can save, reopen, and retain auto routes. ## Task 1: Add QET wire endpoint instance IDs to `wires[]` **Files:** - Modify: `D:\code\zwl\sources\FreeCAD\FreeCADExchangeExportService.cpp:412-477` - Modify: `D:\code\zwl\sources\FreeCAD\FreeCADExchangeExportService.cpp:928` - [ ] **Step 1: Confirm the current export gap** Run: ```powershell rg -n "start_instance_id|end_instance_id|buildWireObjectsForDiagram" D:\code\zwl\sources\FreeCAD\FreeCADExchangeExportService.cpp ``` Expected before this task: `buildWireObjectsForDiagram` exists, but `start_instance_id` and `end_instance_id` are not inserted into `wireObject`. - [ ] **Step 2: Change the function signature** In `D:\code\zwl\sources\FreeCAD\FreeCADExchangeExportService.cpp`, change the function header from: ```cpp QJsonArray buildWireObjectsForDiagram(Diagram *diagram) ``` to: ```cpp QJsonArray buildWireObjectsForDiagram( Diagram *diagram, const QHash &terminalInstanceIds) ``` - [ ] **Step 3: Insert endpoint instance IDs** Inside the loop that builds `wireObject`, immediately after the existing terminal UUID inserts, add: ```cpp const QString startInstanceId = terminalInstanceIds.value(startTerminalUuid).trimmed(); const QString endInstanceId = terminalInstanceIds.value(endTerminalUuid).trimmed(); wireObject.insert(QStringLiteral("start_instance_id"), startInstanceId); wireObject.insert(QStringLiteral("end_instance_id"), endInstanceId); ``` The surrounding block should read: ```cpp wireObject.insert(QStringLiteral("start_element_uuid"), uuidText(startElementUuid)); wireObject.insert(QStringLiteral("start_terminal_uuid"), uuidText(startTerminalUuid)); wireObject.insert(QStringLiteral("start_instance_id"), startInstanceId); wireObject.insert(QStringLiteral("end_element_uuid"), uuidText(endElementUuid)); wireObject.insert(QStringLiteral("end_terminal_uuid"), uuidText(endTerminalUuid)); wireObject.insert(QStringLiteral("end_instance_id"), endInstanceId); ``` - [ ] **Step 4: Pass the binding map from the exporter** Replace: ```cpp wiresArray = buildWireObjectsForDiagram(diagram); ``` with: ```cpp wiresArray = buildWireObjectsForDiagram(diagram, terminalInstanceIds); ``` - [ ] **Step 5: Run static verification** Run: ```powershell rg -n "start_instance_id|end_instance_id|buildWireObjectsForDiagram\\(" D:\code\zwl\sources\FreeCAD\FreeCADExchangeExportService.cpp ``` Expected: one function definition with the new parameter, one call that passes `terminalInstanceIds`, and two JSON inserts for `start_instance_id` / `end_instance_id`. - [ ] **Step 6: Commit the QET contract change** Run in `D:\code\zwl`: ```powershell git status --short git add -- sources/FreeCAD/FreeCADExchangeExportService.cpp git commit -m "feat: export freecad wire endpoint instances" ``` Expected: the commit contains only `sources/FreeCAD/FreeCADExchangeExportService.cpp`. ## Task 2: Preserve wire style and endpoint metadata on FreeCAD tasks **Files:** - Modify: `src/Mod/FreeCADExchange/WiringImport.py:47-109` - Modify: `src/Mod/FreeCADExchange/WiringImport.py:145-190` - Modify: `src/Mod/FreeCADExchange/WiringObjects.py:181-213` - Test: `tests/python/freecad_exchange_wiring_import_test.py` - [ ] **Step 1: Write the failing import test** Add this test to `tests/python/freecad_exchange_wiring_import_test.py` inside `WiringImportTest`: ```python def test_import_wire_tasks_preserves_auto_routing_metadata(self): wiring_import = _reload_modules() app = sys.modules["FreeCAD"] doc = FakeDocument() payload = { "project_uuid": "project-1", "wires": [ { "wire_id": "wire-1", "wire_label": "W1", "wire_mark": "N4111", "wire_mark_is_manual": True, "wire_style_id": 42, "net_uuid": "net-1", "group_uuid": "group-1", "start_element_uuid": "device-a", "start_instance_id": "instance-a", "start_terminal_uuid": "terminal-a", "start_terminal_display": "A1", "end_element_uuid": "device-b", "end_instance_id": "instance-b", "end_terminal_uuid": "terminal-b", "end_terminal_display": "B1", } ], } report = wiring_import.import_wire_tasks_from_payload(payload, doc) task = doc.getObject("QETWiring_01_Tasks").Group[0] self.assertEqual(1, report["imported_tasks"]) self.assertEqual("42", task.QetWireStyleId) self.assertEqual("instance-a", task.QetStartInstanceId) self.assertEqual("instance-b", task.QetEndInstanceId) self.assertEqual("A1", task.QetStartTerminalDisplay) self.assertEqual("B1", task.QetEndTerminalDisplay) ``` - [ ] **Step 2: Run the test and verify it fails** Run: ```powershell D:\FreeCAD-1.1.1\bin\python.exe -m unittest tests.python.freecad_exchange_wiring_import_test.WiringImportTest.test_import_wire_tasks_preserves_auto_routing_metadata ``` Expected: FAIL because `QetWireStyleId` is not set. - [ ] **Step 3: Extend normalized wire entries** In `src/Mod/FreeCADExchange/WiringImport.py`, add this helper near `_bool_value`: ```python def _int_text_value(item, field_name): value = item.get(field_name, "") if value is None: return "" try: return str(int(value)).strip() except Exception: return str(value).strip() ``` Then add this field in `_normalize_wire_entry`: ```python "wire_style_id": _int_text_value(item, "wire_style_id"), ``` - [ ] **Step 4: Persist the style ID on tasks** In `_set_task_extra_properties`, add: ```python _ensure_string_property(task, "QetWireStyleId", entry["wire_style_id"]) ``` - [ ] **Step 5: Run the import test again** Run: ```powershell D:\FreeCAD-1.1.1\bin\python.exe -m unittest tests.python.freecad_exchange_wiring_import_test.WiringImportTest.test_import_wire_tasks_preserves_auto_routing_metadata ``` Expected: PASS. - [ ] **Step 6: Run the wiring import test file** Run: ```powershell D:\FreeCAD-1.1.1\bin\python.exe -m unittest tests.python.freecad_exchange_wiring_import_test ``` Expected: all tests in the file pass. - [ ] **Step 7: Commit** Run: ```powershell git add src/Mod/FreeCADExchange/WiringImport.py tests/python/freecad_exchange_wiring_import_test.py git commit -m "feat: preserve freecad wire task metadata" ``` Expected: the commit contains the import code and its test. ## Task 3: Store auto-route length and wire style diagnostics **Files:** - Modify: `src/Mod/FreeCADExchange/AutoRouting.py:260-300` - Modify: `src/Mod/FreeCADExchange/AutoRouting.py:860-970` - Modify: `src/Mod/FreeCADExchange/AutoRouting.py:1169-1190` - Test: `tests/python/freecad_exchange_auto_routing_test.py` - [ ] **Step 1: Write the failing routed-wire metadata test** Add this test to `tests/python/freecad_exchange_auto_routing_test.py` inside `AutoRoutingTest`: ```python def test_auto_route_stores_length_and_wire_style_diagnostics(self): _install_fake_freecad() terminal_objects, _wiring_objects, routing_network, auto_routing = _reload_modules() app = sys.modules["FreeCAD"] doc = FakeDocument() terminal_objects.ensure_root_group(doc, "project-1") start = _terminal(doc, terminal_objects, "TerminalStart", "terminal-start", app.Vector(0, 0, 0)) end = _terminal(doc, terminal_objects, "TerminalEnd", "terminal-end", app.Vector(100, 0, 0)) routing_network.create_route_carrier( doc, [app.Vector(0, 0, 20), app.Vector(100, 0, 20)], project_uuid="project-1", kind="WireDuct", ) result = auto_routing.route_between_terminals( doc, start, end, wire_uuid="wire-1", wire_label="N4111", options={"wire_style_id": "42"}, ) wire = result["wire"] payload = json.loads(wire.QetAutoRouteDiagnosticsJson) self.assertGreater(float(wire.QetAutoRouteLengthMm), 0.0) self.assertEqual("42", wire.QetWireStyleId) self.assertEqual("42", payload["wire_style_id"]) self.assertGreater(payload["length_mm"], 0.0) ``` If the test file does not already import `json`, add: ```python import json ``` - [ ] **Step 2: Run the new test and verify it fails** Run: ```powershell D:\FreeCAD-1.1.1\bin\python.exe -m unittest tests.python.freecad_exchange_auto_routing_test.AutoRoutingTest.test_auto_route_stores_length_and_wire_style_diagnostics ``` Expected: FAIL because `QetAutoRouteLengthMm` and `QetWireStyleId` are not set on routed wires. - [ ] **Step 3: Add route length helpers** In `src/Mod/FreeCADExchange/AutoRouting.py`, add near `_point_payload`: ```python def _route_length(points): total = 0.0 normalized = [_vector(point) for point in points or []] for index in range(len(normalized) - 1): total += _distance(normalized[index], normalized[index + 1]) return total ``` - [ ] **Step 4: Add wire style and length to the diagnostic payload** Change `_route_payload` to accept `wire_style_id`: ```python def _route_payload(route_data, collisions, wire_style_id=""): points = route_data.get("points", []) return { "algorithm": route_data.get("algorithm", ""), "length_mm": _route_length(points), "wire_style_id": str(wire_style_id or "").strip(), "points": [_point_payload(point) for point in points], "collision_count": len(collisions), "collisions": collisions, "network": route_data.get("network", {}), } ``` Change `_set_auto_metadata` to accept and store `wire_style_id`: ```python def _set_auto_metadata(wire, route_data, collisions, wire_style_id=""): length_mm = _route_length(route_data.get("points", [])) _set_string(wire, "QetAutoRouteAlgorithm", route_data.get("algorithm", ""), "Auto-routing algorithm used for this wire") _set_string(wire, "QetAutoRouteLengthMm", "{0:.3f}".format(length_mm), "Auto route length in millimeters") _set_string(wire, "QetWireStyleId", str(wire_style_id or "").strip(), "QET wire style ID") _set_string( wire, "QetAutoRouteDiagnosticsJson", json.dumps(_route_payload(route_data, collisions, wire_style_id=wire_style_id), ensure_ascii=False), "Auto-routing diagnostics for this wire", ) if route_data.get("network"): _set_string( wire, "QetAutoRouteNetworkJson", json.dumps(route_data.get("network", {}), ensure_ascii=False), "Route network metadata used by this wire", ) ``` - [ ] **Step 5: Thread `wire_style_id` through routing** Add a parameter to `route_between_terminals`: ```python wire_style_id="", ``` Before metadata is written, merge explicit parameter and options: ```python effective_wire_style_id = str(wire_style_id or opts.get("wire_style_id", "") or "").strip() ``` Then replace: ```python _set_auto_metadata(wire, route_data, collisions) ``` with: ```python _set_auto_metadata(wire, route_data, collisions, wire_style_id=effective_wire_style_id) ``` - [ ] **Step 6: Pass task style from payload and task objects** In `route_all_from_payload`, pass: ```python wire_style_id=_wire_item_value(item, "wire_style_id"), ``` In `_wire_tasks_payload`, add: ```python "wire_style_id": (getattr(task, "QetWireStyleId", "") or "").strip(), ``` - [ ] **Step 7: Run the focused test** Run: ```powershell D:\FreeCAD-1.1.1\bin\python.exe -m unittest tests.python.freecad_exchange_auto_routing_test.AutoRoutingTest.test_auto_route_stores_length_and_wire_style_diagnostics ``` Expected: PASS. - [ ] **Step 8: Run the auto-routing test file** Run: ```powershell D:\FreeCAD-1.1.1\bin\python.exe -m unittest tests.python.freecad_exchange_auto_routing_test ``` Expected: all tests in the file pass. - [ ] **Step 9: Commit** Run: ```powershell git add src/Mod/FreeCADExchange/AutoRouting.py tests/python/freecad_exchange_auto_routing_test.py git commit -m "feat: record freecad auto route diagnostics" ``` Expected: the commit contains route metadata and tests. ## Task 4: Write batch diagnostics into `QETWiring_05_Diagnostics` **Files:** - Modify: `src/Mod/FreeCADExchange/AutoRouting.py:1022-1152` - Modify: `src/Mod/FreeCADExchange/WiringObjects.py:153-156` - Test: `tests/python/freecad_exchange_auto_routing_test.py` - [ ] **Step 1: Write the failing diagnostics group test** Add this test to `tests/python/freecad_exchange_auto_routing_test.py` inside `AutoRoutingTest`: ```python def test_route_all_writes_diagnostic_object_for_missing_terminal(self): _install_fake_freecad() terminal_objects, _wiring_objects, _routing_network, auto_routing = _reload_modules() app = sys.modules["FreeCAD"] doc = FakeDocument() terminal_objects.ensure_root_group(doc, "project-1") _terminal(doc, terminal_objects, "TerminalStart", "terminal-start", app.Vector(0, 0, 0)) payload = { "project_uuid": "project-1", "wires": [ { "wire_id": "wire-1", "start_terminal_uuid": "terminal-start", "end_terminal_uuid": "terminal-missing", } ], } report = auto_routing.route_all_from_payload(doc, payload) diagnostic_group = doc.getObject("QETWiring_05_Diagnostics") self.assertEqual(1, report["skipped_missing_terminal"]) self.assertIsNotNone(diagnostic_group) self.assertEqual(1, len(diagnostic_group.Group)) diagnostic = diagnostic_group.Group[0] self.assertEqual("AutoRouteBatch", diagnostic.QetDiagnosticKind) self.assertIn("terminal-missing", diagnostic.QetDiagnosticJson) ``` - [ ] **Step 2: Run the new test and verify it fails** Run: ```powershell D:\FreeCAD-1.1.1\bin\python.exe -m unittest tests.python.freecad_exchange_auto_routing_test.AutoRoutingTest.test_route_all_writes_diagnostic_object_for_missing_terminal ``` Expected: FAIL because no diagnostic object is created. - [ ] **Step 3: Add diagnostic helper functions** In `src/Mod/FreeCADExchange/AutoRouting.py`, add after `format_route_all_report`: ```python def _clear_auto_route_batch_diagnostics(doc): group = WiringObjects.ensure_diagnostic_group(doc, _project_uuid(doc)) removed = 0 for obj in list(getattr(group, "Group", []) or []): if (getattr(obj, "QetDiagnosticKind", "") or "").strip() != "AutoRouteBatch": continue try: group.removeObject(obj) except Exception: pass try: if doc.getObject(getattr(obj, "Name", "")) is not None: doc.removeObject(obj.Name) removed += 1 except Exception: pass return removed def _write_auto_route_batch_diagnostic(doc, report): if doc is None or not isinstance(report, dict): return None if not report.get("errors") and not report.get("missing_endpoint_uuids") and report.get("collision_warnings", 0) <= 0: return None project_uuid = _project_uuid(doc) group = WiringObjects.ensure_diagnostic_group(doc, project_uuid) _clear_auto_route_batch_diagnostics(doc) diagnostic = doc.addObject("App::DocumentObjectGroup", _unique_name(doc, "QETAutoRouteDiagnostic")) diagnostic.Label = "QET Auto Route Diagnostic" _set_string(diagnostic, "QetDiagnosticKind", "AutoRouteBatch", "QET diagnostic kind") _set_string( diagnostic, "QetDiagnosticJson", json.dumps(report, ensure_ascii=False), "QET auto-routing batch diagnostic payload", ) group.addObject(diagnostic) return diagnostic ``` - [ ] **Step 4: Call the diagnostic writer** At the end of `route_all_from_payload`, immediately before `return report`, add: ```python _write_auto_route_batch_diagnostic(doc, report) ``` - [ ] **Step 5: Run the focused test** Run: ```powershell D:\FreeCAD-1.1.1\bin\python.exe -m unittest tests.python.freecad_exchange_auto_routing_test.AutoRoutingTest.test_route_all_writes_diagnostic_object_for_missing_terminal ``` Expected: PASS. - [ ] **Step 6: Run auto-routing tests** Run: ```powershell D:\FreeCAD-1.1.1\bin\python.exe -m unittest tests.python.freecad_exchange_auto_routing_test ``` Expected: all tests pass. - [ ] **Step 7: Commit** Run: ```powershell git add src/Mod/FreeCADExchange/AutoRouting.py tests/python/freecad_exchange_auto_routing_test.py git commit -m "feat: write freecad auto routing diagnostics" ``` Expected: the commit contains diagnostic object behavior and tests. ## Task 5: Improve one-click panel summary **Files:** - Modify: `src/Mod/FreeCADExchange/AutoRouting.py:1116-1152` - Modify: `src/Mod/FreeCADExchange/AutoRoutingPanel.py:137-160` - Test: `tests/python/freecad_exchange_auto_routing_test.py` - [ ] **Step 1: Write the failing report text test** Add this test to `tests/python/freecad_exchange_auto_routing_test.py` inside `AutoRoutingTest`: ```python def test_route_all_report_includes_network_and_first_error(self): _install_fake_freecad() _terminal_objects, _wiring_objects, _routing_network, auto_routing = _reload_modules() report = { "total_wires": 2, "routed": 1, "collision_warnings": 1, "skipped_missing_terminal": 1, "prepared_layout": { "wire_duct_carriers": 2, "surface_carriers": 4, "terminal_access_carriers": 6, }, "missing_endpoint_samples": [ { "start_terminal_uuid": "terminal-a", "end_terminal_uuid": "terminal-b", } ], "errors": ["没有可用的线槽/路由路径网络"], } message = auto_routing.format_route_all_report(report) self.assertIn("routed=1", message) self.assertIn("线槽路径 2 条", message) self.assertIn("首个错误:没有可用的线槽/路由路径网络", message) self.assertIn("缺失示例:terminal-a -> terminal-b", message) ``` - [ ] **Step 2: Run the new report test** Run: ```powershell D:\FreeCAD-1.1.1\bin\python.exe -m unittest tests.python.freecad_exchange_auto_routing_test.AutoRoutingTest.test_route_all_report_includes_network_and_first_error ``` Expected: FAIL because the first error is not included. - [ ] **Step 3: Extend `format_route_all_report`** In `src/Mod/FreeCADExchange/AutoRouting.py`, inside `format_route_all_report`, after the prepared layout block, add: ```python errors = report.get("errors", []) or [] if errors: message += "\n首个错误:{0}".format(str(errors[0])) ``` Then ensure the missing endpoint sample block runs whenever a sample exists: ```python sample = (report.get("missing_endpoint_samples") or [None])[0] if sample: message += "\n缺失示例:{0} -> {1}".format( sample.get("start_terminal_uuid", ""), sample.get("end_terminal_uuid", ""), ) ``` - [ ] **Step 4: Run the focused test** Run: ```powershell D:\FreeCAD-1.1.1\bin\python.exe -m unittest tests.python.freecad_exchange_auto_routing_test.AutoRoutingTest.test_route_all_report_includes_network_and_first_error ``` Expected: PASS. - [ ] **Step 5: Run auto-routing tests** Run: ```powershell D:\FreeCAD-1.1.1\bin\python.exe -m unittest tests.python.freecad_exchange_auto_routing_test ``` Expected: all tests pass. - [ ] **Step 6: Commit** Run: ```powershell git add src/Mod/FreeCADExchange/AutoRouting.py tests/python/freecad_exchange_auto_routing_test.py git commit -m "fix: clarify freecad auto routing report" ``` Expected: the commit contains only report-formatting code and tests. ## Task 6: Add save and reopen coverage to the FreeCAD smoke **Files:** - Modify: `tests/manual/freecad_auto_routing_smoke.py` - [ ] **Step 1: Extend smoke output assertions** In `tests/manual/freecad_auto_routing_smoke.py`, after the current document save path is created, ensure the script saves and reopens the FCStd: ```python doc.saveAs(OUT_FCSTD) App.closeDocument(doc.Name) reopened = App.openDocument(OUT_FCSTD) routed_group = reopened.getObject("QETWiring_04_Routed") reopened_wires = list(getattr(routed_group, "Group", []) or []) if routed_group else [] result_payload["reopened_routed_wire_count"] = len(reopened_wires) result_payload["reopened_has_auto_route"] = any( (getattr(wire, "RouteType", "") or "").strip() == "AutoSuggested" for wire in reopened_wires ) ``` If the script currently writes JSON before save/reopen, move the JSON write after the new payload fields are set. - [ ] **Step 2: Run the manual smoke** Run: ```powershell D:\FreeCAD-1.1.1\bin\python.exe tests\manual\freecad_auto_routing_smoke.py ``` Expected: exit code 0 and JSON output contains: ```json "reopened_has_auto_route": true ``` - [ ] **Step 3: Commit** Run: ```powershell git add tests/manual/freecad_auto_routing_smoke.py git commit -m "test: verify freecad auto routes survive reopen" ``` Expected: the commit contains only the manual smoke update. ## Task 7: Final verification **Files:** - Read: `docs/superpowers/specs/2026-05-28-freecad-electrical-auto-routing-design.md` - Read: changed files from Tasks 1-6 - [ ] **Step 1: Run FreeCADExchange unit tests** Run: ```powershell D:\FreeCAD-1.1.1\bin\python.exe -m unittest discover -s tests\python -p "freecad_exchange*_test.py" ``` Expected: all discovered tests pass. - [ ] **Step 2: Run FreeCADExchange syntax check** Run: ```powershell D:\FreeCAD-1.1.1\bin\python.exe -m py_compile src\Mod\FreeCADExchange\AutoRouting.py src\Mod\FreeCADExchange\RoutingNetwork.py src\Mod\FreeCADExchange\AutoRoutingPanel.py src\Mod\FreeCADExchange\WiringImport.py src\Mod\FreeCADExchange\WiringObjects.py ``` Expected: exit code 0 with no syntax errors. - [ ] **Step 3: Run manual smoke** Run: ```powershell D:\FreeCAD-1.1.1\bin\python.exe tests\manual\freecad_auto_routing_smoke.py ``` Expected: exit code 0, output JSON exists, and reopened auto route is true. - [ ] **Step 4: Verify no forbidden first-version database dependency was introduced** Run: ```powershell rg -n "project_3d_scene_instance|project_3d_space_object|project_2d3d_link|start_end_terminal_matches|connection_point_key|terminal_key" src\Mod\FreeCADExchange docs\superpowers\plans\2026-05-28-freecad-electrical-auto-routing.md ``` Expected: no matches in `src/Mod/FreeCADExchange`; matches in this plan are allowed only because this verification command names forbidden strings. - [ ] **Step 5: Verify staged and unstaged diffs** Run: ```powershell git status --short git diff --check ``` Expected: no whitespace errors. Untracked screenshots and diagnostic JSON files may remain; do not stage them. - [ ] **Step 6: Commit final docs if the plan was adjusted during execution** Run only if this plan file changed during execution: ```powershell git add docs/superpowers/plans/2026-05-28-freecad-electrical-auto-routing.md git commit -m "docs: update freecad auto routing plan" ``` Expected: commit contains only the plan document.