You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
LightWork3D/docs/superpowers/plans/2026-05-28-freecad-electric...

25 KiB

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:

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:

QJsonArray buildWireObjectsForDiagram(Diagram *diagram)

to:

QJsonArray buildWireObjectsForDiagram(
    Diagram *diagram,
    const QHash<QUuid, QString> &terminalInstanceIds)
  • Step 3: Insert endpoint instance IDs

Inside the loop that builds wireObject, immediately after the existing terminal UUID inserts, add:

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:

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:

wiresArray = buildWireObjectsForDiagram(diagram);

with:

wiresArray = buildWireObjectsForDiagram(diagram, terminalInstanceIds);
  • Step 5: Run static verification

Run:

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:

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:

    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:

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:

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:

"wire_style_id": _int_text_value(item, "wire_style_id"),
  • Step 4: Persist the style ID on tasks

In _set_task_extra_properties, add:

_ensure_string_property(task, "QetWireStyleId", entry["wire_style_id"])
  • Step 5: Run the import test again

Run:

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:

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:

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:

    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:

import json
  • Step 2: Run the new test and verify it fails

Run:

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:

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:

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:

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:

wire_style_id="",

Before metadata is written, merge explicit parameter and options:

effective_wire_style_id = str(wire_style_id or opts.get("wire_style_id", "") or "").strip()

Then replace:

_set_auto_metadata(wire, route_data, collisions)

with:

_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:

wire_style_id=_wire_item_value(item, "wire_style_id"),

In _wire_tasks_payload, add:

"wire_style_id": (getattr(task, "QetWireStyleId", "") or "").strip(),
  • Step 7: Run the focused test

Run:

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:

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:

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:

    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:

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:

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:

_write_auto_route_batch_diagnostic(doc, report)
  • Step 5: Run the focused test

Run:

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:

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:

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:

    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:

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:

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:

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:

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:

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:

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:

    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:

D:\FreeCAD-1.1.1\bin\python.exe tests\manual\freecad_auto_routing_smoke.py

Expected: exit code 0 and JSON output contains:

"reopened_has_auto_route": true
  • Step 3: Commit

Run:

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:

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:

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:

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:

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:

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:

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.