app
Fusion - the Fusion 360 Bridge
Public Made by Adomby adom
Drive Autodesk Fusion 360 from the cloud via Adom Bridge: component libraries, IPC package generation, board layout, exports (STEP/Gerbers/BOM/CPL), fast APS cloud search, and parametric modeling.
← Commit history
Publish 1.10.25
18 files changed
+1354−70
.insiders-stage/fusion-bridge/version.json+4−4.release-page-head+1−1CHANGELOG.md+18CONTRIBUTING.md+119−44SKILL.md+1−1install.shpage.json+26−10rust/Cargo.lock+5−5tests/reports/daily-cron-2026-09-20.log+110tests/reports/daily-cron-history.txt+1tests/reports/verb-test-2026-09-20_1151.json+903tests/run_verb_tests.py+12−5tools/check-naming.shtools/check-portable.shtools/gen-readme-verbs.py+153tools/promote.shtools/release-files.txt+1tools/release.sh.insiders-stage/fusion-bridge/version.json+4−4@@ -1,10 +1,10 @@ { "manifest_version": 1, "name": "fusion360",- "version": "1.10.24",- "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.10.24/adom-bridge-fusion-v1.10.24.zip",- "sha256": "1111fb69b4603c64f14eb6378b371294b2ac96b0aaa31282605be8ed1ce67142",- "size": 6453038,+ "version": "1.10.25",+ "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.10.25/adom-bridge-fusion-v1.10.25.zip",+ "sha256": "c82a1f6eab93a62ef136b93313d805fdb727e163fbea7a5b703923b5dd7ee8e0",+ "size": 6460344, "verbPrefixes": [ "fusion_" ],
.release-page-head+1−1@@ -1 +1 @@-d0db29e11e2054348431f24a064446198845dc73+3d474145cd14c3c6e61f2971916f2ac315bed354
CHANGELOG.md+18@@ -3,6 +3,24 @@ All notable changes to the Adom Bridge Fusion 360 bridge, newest first. Canonical: https://wiki.adom.inc/adom/fusion-bridge · GitHub backup: `adom-inc/fusion-bridge` +## 1.10.25 (2026-09-20)++- Boards the tools make carry the part name on the top silkscreen under each footprint+ (`fusion_make_showcase_project`, `fusion_make_test_project`; `silkLabels:false` turns it off)+ (page issue #91). The EAGLE command skill lists the non-interactive `TEXT` form with its+ `GRID MM` warning.+- New `fusion_show_attributes {libraryPath, deviceset}`: the component's metadata with full+ values from the library file, plus the DEVICE editor opened and captured (symbol, Packages+ table, footprint and 3D previews). Fusion's own Attribute Sets table is an opaque Qt panel;+ the reply carries the positioned-click recipe and the recording note (page issue #93).+- Daily verb test: one retry when another session holds the add-in for a moment, and an honest+ "No active Fusion Design" counts as the BOM guard's refusal (page issue #92).+- Skills brought in line with 1.10.13 to 1.10.24: `fusion_state` replaces the verb that was+ never in the contract, staging replaces the 8.3 short-path text, the default restart policy+ and the dialog ladder are stated where the old toast was described, native viewport frames,+ the async import status verb, caller-aware prove, the heatsink-via lint.+- README and the sub-readmes regenerated and updated (verb catalog from the contract).+ ## 1.10.24 (2026-09-20) - Showcase schematic: symbols are pitched by their own measured width (they overlapped at a fixed 45 mm on the ToF sensors).
CONTRIBUTING.md+119−44@@ -1,75 +1,150 @@ # Contributing -This bridge is part of the [Adom Bridge](https://github.com/adom-inc/adom-desktop) bridge ecosystem. It lives in its own repo (split from the main monorepo in 2026-05) specifically so the community can fork and improve it without coordinating with Adom Bridge's release cycle.+The Fusion bridge is open, and contributing back is part of the product. Its wiki page is the+repository: every release pushes the whole source tree (the Rust exe, the Fusion add-in, the verb+contract, the skills, the tests) to [adom/fusion-bridge](https://wiki.adom.inc/adom/fusion-bridge),+and pull requests are reviewed there. A GitHub mirror under adom-inc is linked from the page. The+wiki repo is canonical.++If your AI just worked around the bridge instead of using it, that workaround is a contribution+waiting to happen: file it, or fix it and send the change back. Both take minutes with the+`adom-wiki` CLI. An AI can follow the same steps from the `fusion-contributing` skill, which ships+in the package (`skills/fusion-contributing/SKILL.md`). Every failing reply from the exe carries a+`_contribute` field with the same pointer, and `fusion_describe` carries a `contribute` block, so+nobody has to find this file first. ## Two-way street -- **You can extend this bridge** — add support for a new app version, new verbs, new platforms — and PR it back here. We sync accepted PRs into adom-desktop's bundled copy on the next release.-- **You can fork it** if your changes diverge too much (e.g. private vendor SDK integration). The bridge.json schema is the contract; fork freely under MIT.+- **You can extend this bridge** (a dialog rule, a hint, a verb, a skill, support for a new Fusion+ build) and send it back as a pull request on the wiki page.+- **You can fork it** if your changes diverge too much, for example a private vendor SDK+ integration. `bridge.json` is the contract with Adom Bridge. Fork freely under MIT. -## How shipping works+## 1. Report first (two minutes, always worth it) -1. **Develop locally.** Clone this repo, edit, test. The bridge runs alongside an installed [Adom Bridge](https://github.com/adom-inc/adom-desktop); use `adom-bridge bridge_install` with a local `file://` manifest URL to test your changes against a real GUI.-2. **Open a PR** against `main`. The CI in `.github/workflows/validate.yml` checks that `bridge.json` is valid + verb prefixes look right + the BRIDGE_VERSION was bumped vs. main.-3. **Maintainer reviews + merges.** No squash-merge — we preserve your authored commits.-4. **Maintainer ships.** A maintainer bumps `BRIDGE_VERSION` + `bridge.json` together, builds the release zip, and publishes it to the canonical page, [wiki.adom.inc/adom/fusion-bridge](https://wiki.adom.inc/adom/fusion-bridge): repo files (manifest, `bridge.json`, `BRIDGE_VERSION`, sources) via the page files API, and the binary zip via `adom-wiki release upload adom/fusion-bridge <ver> <zip>`. See PUBLISHING.md for the exact ritual. Anyone running Adom Bridge v1.7.19+ picks it up within 4 hours or on the next `adom-bridge refresh_bridges`.+```bash+adom-wiki issue create adom/fusion-bridge --category bug-report \+ --title "<verb>: <what happened in one line>" \+ --body-file report.md+``` -We DON'T auto-ship from GitHub Actions today. Reason: publishing needs an Adom Wiki API token, and we keep that secret on maintainer machines rather than in GH Actions secrets. The community trust model around auto-publishing community PRs to a shared wiki space is part of a separate effort ("Chunk D" in adom-desktop's TODO.md) — comment on an issue if you want to help design that.+Put in `report.md`: the exact call and its full reply JSON (the `_hint` and `errorCode` matter),+the `fusion_readiness` output (bridge version, add-in version, box), the Fusion build, what you+expected, and the workaround you used. Attach a screenshot or the exported board if one exists. A+report with the reply JSON is usually fixed the same day. The maintainer thread answers on the+issue. -## Bridge.json schema+## 2. Get the source -See [the SDK guide on the Adom Wiki](https://wiki.adom.inc/adom/adom-desktop-bridges) for the authoritative schema. Quick summary:+```bash+adom-wiki repo clone adom/fusion-bridge --dir fusion-bridge # HEAD of master, files only+cd fusion-bridge+``` -- `manifest_version: 1`-- `name` — must match the directory name in adom-desktop's `plugins/` (when bundled)-- `version` — semver, bumped on every PR that ships-- `spawn.port: 0` — **always use 0** as of Adom Bridge v1.8.31+. Adom Bridge picks an OS-assigned ephemeral port and passes it via `--port <N>` to your bridge process. Don't hardcode.-- `spawn.healthEndpoint: "/status"` — path-only (recommended). Adom Bridge builds the URL using the runtime port.-- `verbPrefixes: ["<name>_"]` — the CLI verb prefixes this bridge owns.+Where things live:++| Path | What it is |+|---|---|+| `rust/bridge/src/` | the exe Adom Bridge spawns (`adom-fusion-bridge.exe`): verb dispatch in `main.rs`, the pass-through envelope in `addin.rs`, dialogs and restarts in `lifecycle.rs`, chains in `chains.rs`, APS search in `main.rs`, staging in `fusionpath.rs` |+| `rust/core/src/` | shared logic: `dialog_rules.json` (dialog text to category and resolution), readiness, window facts, APS |+| `addin/AdomBridge/` | the Python add-in that runs inside Fusion: one handler per verb under `commands/`, registered in `commands/__init__.py` |+| `describe.py` and `contracts/describe.json` | the verb contract `fusion_describe` serves. `tools/freeze-contract.py` regenerates the JSON |+| `skills/` | the user skills the package installs |+| `tests/run_verb_tests.py` | the daily verb test. Every described verb must be triaged there or the run goes red |+| `README.md` and `docs/` | the page and its feature guides. `python3 tools/gen-readme-verbs.py` regenerates the README verb catalog from the contract |+| `CHANGELOG.md` | newest first. Every change gets an entry |++## 3. The common contributions++- **A dialog the bridge could not answer** (the reply was `blocked_by_dialog` with the box's text+ and buttons): add a rule to `rust/core/src/dialog_rules.json` (`needles` from the text, a+ `category`, a `resolution` that names the safe button) and the parity entry in+ `handlers/dialog_classify.py`. Add a case to the tests in `rust/core/src/dialogs.rs`.+- **A wrong or missing hint**: hints are plain strings next to the reply that carries them (`_hint`+ in the Rust or the Python handler). Say what to do next, not what went wrong twice.+- **A new verb**: a handler in `addin/AdomBridge/commands/`, its name in `commands/__init__.py`+ and in `ADDIN_COMMANDS` in `rust/bridge/src/addin.rs`, an entry in `describe.py`, then+ `python3 tools/freeze-contract.py`, a `t(...)` line in `tests/run_verb_tests.py`, and+ `python3 tools/gen-readme-verbs.py` so the README catalog lists it.+- **A skill**: a folder under `skills/<name>/SKILL.md` with frontmatter (`name`, `public`,+ `description` with the trigger words users actually say).++## 4. Build and try it on your own box++Prerequisites once: Rust stable, the cross target and MinGW for the Windows exe. -## Versioning+```bash+rustup target add x86_64-pc-windows-gnu+sudo apt-get install -y mingw-w64 # Linux or WSL; on Windows build natively+cd rust && cargo test -p adom-fusion-bridge # unit tests, including the dialog rules+cargo build --release --target x86_64-pc-windows-gnu -p adom-fusion-bridge+``` -- **Patch** (`0.8.3 → 0.8.4`): bug fixes, hardening, no schema or behavior changes.-- **Minor** (`0.8 → 0.9`): new verbs, new optional manifest fields, backward-compatible behavior changes.-- **Major** (`0.x → 1.0`, `1.x → 2.0`): breaking changes to verb shapes or removal of verbs. Coordinate with adom-bridge maintainers before submitting — we may need to wait for a desktop-app release that handles both versions.+Try it without touching the released install: pin the bridge cache on the box so Adom Bridge stops+updating it, copy your build in, let Adom Bridge respawn it, and unpin when you are done. -Bump `BRIDGE_VERSION` AND `bridge.json`'s `"version"` field in the same commit — they must match.+```bash+adom-bridge --target <box> --ai-thread <you> bridge_dev_mode '{"name":"fusion360","on":true}'+adom-bridge --target <box> --ai-thread <you> send_files '{"filePaths":["rust/target/x86_64-pc-windows-gnu/release/adom-fusion-bridge.exe"],"dest":"C:/Users/<user>/AppData/Local/Adom Bridge/bridges-cache/fusion360"}'+adom-bridge --target <box> --ai-thread <you> fusion_readiness '{}' # bridgeVersion is your build+adom-bridge --target <box> --ai-thread <you> bridge_dev_mode '{"name":"fusion360","on":false}' # when done+``` -## Testing locally+An add-in change (`addin/`) also needs the add-in folder copied into the cache and a Fusion+restart (`fusion_restart_for_addin {save:true}`), because Fusion loads add-ins at launch.++## 5. Send the pull request ```bash-# 1. Clone this repo somewhere-git clone https://github.com/adom-inc/adom-bridge-<NAME>.git-cd adom-bridge-<NAME>+adom-wiki branch create adom/fusion-bridge fix/<short-name>+adom-wiki repo push adom/fusion-bridge --branch fix/<short-name> \+ --message "<what and why, one line>" --files <the files you changed>+adom-wiki pr create adom/fusion-bridge --from fix/<short-name> \+ --title "<what it fixes>" --body-file pr.md+``` -# 2. Install adom-desktop, then point it at this local manifest:-adom-bridge bridge_install '{"manifestUrl":"file:///path/to/adom-bridge-<NAME>/bridge.json"}'+In `pr.md`: the issue it answers, what changed, and how you verified it (box, Fusion build, the+verb call and its reply before and after). Add a CHANGELOG entry at the top of `CHANGELOG.md`+under the next version. House style: plain sentences, no em-dashes. -# Note: file:// works only when manifestUrl is parsed by adom-desktop's CLI on the-# same machine that owns the bridge files. For Docker callers, push to a wiki staging-# area and use the wiki URL.+## How shipping works -# 3. Run any verb the bridge defines:-adom-bridge <name>_<verb> '{"...":"..."}'+The maintainer thread reviews the pull request and merges it into master (a true merge, history+kept, your authored commits preserved). A maintainer bumps `BRIDGE_VERSION` and the `version` in+`bridge.json` together, builds the release zip and publishes it with `tools/release.sh`. Every+release is an insiders release first: the insiders manifest moves, and the public manifest moves+only after the pre-production gate (`tools/promote.sh`). You will see the version on the issue. A+pull request that comes with its evidence merges fast. -# 4. Run the SDK demo to verify install/list/invoke/uninstall lifecycle:-bash scripts/demo-thirdparty-bridge.sh # in adom-bridge repo-```+Publishing needs an Adom Wiki API token, which stays on maintainer machines, so nothing ships+automatically from a merge. -## Why this bridge lives in its own repo+## bridge.json -When adom-bridge was a monorepo, all bridges shipped on adom-desktop's NSIS release cycle — meaning a community contributor couldn't release a KiCad fix without waiting for an adom-bridge maintainer to cut a new desktop installer. As of 2026-05 the bridges live in independent repos so:+See [the SDK guide on the Adom Wiki](https://wiki.adom.inc/adom/adom-desktop-bridges) for the+authoritative schema. What matters for this bridge: -- Community can release at their own cadence-- Adom Bridge's installer ships a "known-good" snapshot but auto-updates from the wiki for newer versions-- A maintainer outside Adom Industries can take over a bridge (e.g. a KiCad 11 expert maintains the KiCad bridge) without commit access to adom-bridge itself+- `version`: semver, and it must match `BRIDGE_VERSION`.+- `spawn.kind: "exe"` with `entrypoint: "adom-fusion-bridge.exe"`: one sha256-verified binary, no+ interpreter.+- `spawn.port: 0`: Adom Bridge picks an ephemeral port and passes it with `--port <N>`. Do not+ hardcode a port.+- `spawn.healthEndpoint: "/status"`: path only. Adom Bridge builds the URL from the runtime port.+- `verbPrefixes: ["fusion_"]`: the verb prefix this bridge owns.++## Versioning -See [adom-bridge TODO.md → Chunks C/D](https://github.com/adom-inc/adom-desktop/blob/main/TODO.md) for the broader design.+- **Patch**: bug fixes, hardening, new hints, no change to a verb's shape.+- **Minor**: new verbs, new optional arguments, backward-compatible behavior changes.+- **Major**: a breaking change to a verb's shape, or a verb removed. Talk to the maintainers on an+ issue first. ## Code of conduct -Be kind. We're building this for engineers who need their CAD tools to work with AI without giving up local control of their files. If your PR is rejected, we'll explain why — and you can fork.+Be kind. We are building this for engineers who need their CAD tools to work with AI without+giving up local control of their files. If your pull request is declined, we will explain why, and+you can fork. ## License -MIT — see [LICENSE](LICENSE).+MIT. See [LICENSE](LICENSE).
SKILL.md+1−1@@ -149,7 +149,7 @@ are dimmed). So: | `fusion_close` | `close` | Close Fusion 360 entirely | — | | `fusion_dismiss_recovery` | `dismiss_recovery` | Dismiss the "recover unsaved work" dialog | — | | `fusion_relocate_recovery` | `relocate_recovery` | Move recovery dialog offscreen (for recording) | — |-| `fusion_window_info` | `window_info` | Enumerate Fusion windows (HWND, title, bounds) | — |+| `fusion_state` | `state` | ONE read of everything on screen: dialogs (title, text, buttons), focus events, the window capture, and for a Design the frame Fusion itself renders (`nativeViewport`), a body count (`geometry`) and a blank-capture check (`windowPixels`). `{screenshot:false}` for the fast dialog check | - | | `fusion_screenshot_fusion` | `screenshot_fusion` | Screenshot the main Fusion window | — | | `fusion_screenshot_all` | `screenshot_all` | Screenshot every Fusion window | — | | `fusion_click_fusion` | `click_fusion` | Click at (x, y) in the Fusion main window | `x`, `y` |
install.shpage.json+26−10@@ -11,10 +11,6 @@ "uninstall": "./uninstall.sh" }, "tags": [- "ab",- "bridge",- "fusion",- "desktop-app", "fusion360", "autodesk", "cad",@@ -79,10 +75,7 @@ "build a basic parts library", "add a 3d model to a component", "make a fusion 360 library with 3d",- "set up the fusion bridge",- "fusion dashboard",- "fusion status page",- "open the fusion launch page"+ "set up the fusion bridge" ], "discovery_pitch": "Use the Adom Bridge Fusion 360 bridge when the user wants to DRIVE Autodesk Fusion 360 from the cloud: open/search their team's Fusion cloud designs, lay out a PCB or do electronics, export STEP/IGES/STL/Gerbers/BOM, or build a component library with real rendering 3D. It runs Fusion on the user's own machine via Adom Bridge. NOT for web/browser/login/form-filling (that's the native-browser extension) and NOT for KiCad (that's the kicad bridge).", "brief": "Drive Autodesk Fusion 360 from the cloud via Adom Bridge: component libraries, IPC package generation, board layout, exports (STEP/Gerbers/BOM/CPL), fast APS cloud search, and parametric modeling.",@@ -111,5 +104,28 @@ "label": "Part volume", "prompt": "How much material does each printed part in this assembly use?" }- ]-}+ ],+ "agent_permissions": {+ "allow_bash": [],+ "allow": [+ "adom-desktop-fusion-bridge read-only verbs (via the adom-bridge CLI): fusion_readiness, fusion_get_app_state, fusion_addin_status, fusion_check_dialogs - state queries; they change nothing.",+ "adom-desktop-fusion-bridge read-only verbs: fusion_alerts (reads Fusion's own log for toasts), fusion_document_info, fusion_window_info, fusion_board_info, fusion_library_parts - inspect only.",+ "adom-desktop-fusion-bridge read-only verbs: fusion_prove_result (a persisted run report), fusion_plan_library_import (report-only preflight), fusion_list_cloud_projects, fusion_describe.",+ "adom-desktop-fusion-bridge evidence captures fusion_screenshot_fusion and fusion_capture_library_views read pixels and write local image files only."+ ],+ "soft_deny": [+ "adom-desktop-fusion-bridge lifecycle verbs fusion_stop, fusion_kill, fusion_restart_for_addin close or kill the Fusion application - keep the human prompt.",+ "adom-desktop-fusion-bridge fusion_install_fusion silently installs Autodesk Fusion on the user's machine.",+ "adom-desktop-fusion-bridge fusion_cleanup_cloud_files deletes files from the user's Autodesk cloud hub.",+ "adom-desktop-fusion-bridge managed-library verbs (fusion_managed_library_push/pull/create/link/unlink) publish to or overwrite the user's library.io libraries."+ ],+ "environment": [+ "adom-desktop-fusion-bridge is the Fusion 360 bridge for Adom Bridge; its fusion_* verbs are invoked through the adom-bridge CLI and drive Autodesk Fusion on the user's own desktop.",+ "adom-desktop-fusion-bridge desktop actions also pass Adom Bridge's own human-approval gate (caller identity, per-call reason, Activity Log) - a container approval is the first of two gates."+ ]+ },+ "author": {+ "name": "John Lauer",+ "email": "[email protected]"+ }+}
rust/Cargo.lock+5−5@@ -10,7 +10,7 @@ checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "adom-fusion-bridge"-version = "1.10.24"+version = "1.10.25" dependencies = [ "base64", "chrono",@@ -273,7 +273,7 @@ dependencies = [ [[package]] name = "fusion-core"-version = "1.10.24"+version = "1.10.25" dependencies = [ "serde", "serde_json",@@ -283,7 +283,7 @@ dependencies = [ [[package]] name = "fusion-lbr"-version = "1.10.24"+version = "1.10.25" dependencies = [ "regex", "roxmltree",@@ -295,14 +295,14 @@ dependencies = [ [[package]] name = "fusion-os-mac"-version = "1.10.24"+version = "1.10.25" dependencies = [ "fusion-core", ] [[package]] name = "fusion-os-win"-version = "1.10.24"+version = "1.10.25" dependencies = [ "fusion-core", "windows-sys 0.59.0",
tests/reports/daily-cron-2026-09-20.logadded+110@@ -0,0 +1,110 @@+warmup: waking the bridge (unscored)...+ fusion_readiness PASS + fusion_describe PASS + fusion_get_app_state FAIL - expected success, got error=Fusion main thread busy — document_info running for 10.1s (from another bridge/s code=main_thread_busy+ fusion_search_cloud_files PASS + fusion_walk_cloud_tree PASS + fusion_aps_status PASS + fusion_aps_get_browser PASS + fusion_aps_search PASS + fusion_aps_recent PASS + fusion_aps_browse PASS + fusion_aps_file_info PASS + fusion_aps_get PASS + fusion_uninstall PASS + fusion_set_seat_takeover PASS + fusion_start PASS + fusion_get_preferences PASS + fusion_set_preference PASS + fusion_mcp_status PASS + fusion_set_auto_update PASS + fusion_assembly_bom FAIL - expected a BOM or the budget refusal, got error=No active Fusion Design. code=None+ fusion_dismiss_blocking_dialogs PASS + fusion_open_cloud_file PASS + fusion_show_schematic PASS + fusion_show_2d_board PASS + fusion_board_info PASS + fusion_open_electronics PASS + fusion_list_text_commands PASS + fusion_execute_text_command PASS + fusion_show_3d_board PASS + fusion_inspect_bodies PASS + fusion_physical_properties PASS + fusion_get_design_info PASS + fusion_get_parameters PASS + fusion_document_info PASS + fusion_delete_cloud_file#guard PASS + fusion_set_parameter#guard PASS + fusion_run_modeling_script#guard PASS + fusion_assembly_bom#guard PASS + fusion_run_modeling_script PASS + fusion_close_window#noargs PASS + fusion_cleanup_cloud_files#guard PASS + fusion_screenshot_fusion PASS + fusion_state PASS + fusion_demo_progress#unknown PASS + fusion_action_ledger PASS + fusion_addin_status PASS + fusion_alerts PASS + fusion_verb_times PASS + fusion_live_feed PASS + fusion_prove_result PASS + fusion_set_driving_badge PASS + fusion_set_window_sizing PASS + fusion_cancel_run PASS + fusion_preflight_lbr PASS + fusion_library_parts PASS + fusion_routing_state#guard PASS + fusion_route_net#guard PASS + fusion_routing_drc#guard PASS + fusion_plan_route#guard PASS + fusion_schematic#guard PASS + fusion_schematic#missing PASS + fusion_make_showcase_project#guard PASS + fusion_eagle_import_status#guard PASS + focus_invariance PASS+cleanup: Fusion was already running before the run - leaving it (user's session).++Section coverage (John's product map, 2026-08-14):+ gui-driving 16 pass / 1 fail (21 manual, 0 skip) RED+ cad-3d-bom 13 pass / 1 fail (6 manual, 0 skip) RED+ mcp-server 1 pass / 0 fail (1 manual, 0 skip) green+ python-addin 6 pass / 0 fail (1 manual, 0 skip) green+ aps 9 pass / 0 fail (13 manual, 0 skip) green+ auth-tokens 2 pass / 0 fail (3 manual, 0 skip) green+ exports 0 pass / 0 fail (1 manual, 0 skip) untested-this-tier+ bridge-lifecycle 11 pass / 0 fail (15 manual, 0 skip) green++62 PASS, 2 FAIL (65 manual-tier, 0 skipped) -> /home/adom/project/adom-desktop-fusion-bridge/tests/reports/verb-test-2026-09-20_1151.json+ FAIL: fusion_get_app_state - expected success, got error=Fusion main thread busy — document_info running for 10.1s (from another bridge/s code=main_thread_busy+ FAIL: fusion_assembly_bom - expected a BOM or the budget refusal, got error=No active Fusion Design. code=None+{+ "discussion": {+ "id": 1510,+ "page_id": 13,+ "author_name": "John Lauer",+ "title": "Daily verb run RED 2026-09-20: 2 failing verb(s)",+ "body": "Automated daily verb run went red (2026-09-20, exit 1). Scorecard tail:\n\n```\n fusion_library_parts PASS \n fusion_routing_state#guard PASS \n fusion_route_net#guard PASS \n fusion_routing_drc#guard PASS \n fusion_plan_route#guard PASS \n fusion_schematic#guard PASS \n fusion_schematic#missing PASS \n fusion_make_showcase_project#guard PASS \n fusion_eagle_import_status#guard PASS \n focus_invariance PASS\ncleanup: Fusion was already running before the run - leaving it (user's session).\n\nSection coverage (John's product map, 2026-08-14):\n gui-driving 16 pass / 1 fail (21 manual, 0 skip) RED\n cad-3d-bom 13 pass / 1 fail (6 manual, 0 skip) RED\n mcp-server 1 pass / 0 fail (1 manual, 0 skip) green\n python-addin 6 pass / 0 fail (1 manual, 0 skip) green\n aps 9 pass / 0 fail (13 manual, 0 skip) green\n auth-tokens 2 pass / 0 fail (3 manual, 0 skip) green\n exports 0 pass / 0 fail (1 manual, 0 skip) untested-this-tier\n bridge-lifecycle 11 pass / 0 fail (15 manual, 0 skip) green\n\n62 PASS, 2 FAIL (65 manual-tier, 0 skipped) -> /home/adom/project/adom-desktop-fusion-bridge/tests/reports/verb-test-2026-09-20_1151.json\n FAIL: fusion_get_app_state - expected success, got error=Fusion main thread busy — document_info running for 10.1s (from another bridge/s code=main_thread_busy\n FAIL: fusion_assembly_bom - expected a BOM or the budget refusal, got error=No active Fusion Design. code=None\n```\n\nFull log: tests/reports/daily-cron-2026-09-20.log on the dev container. Versions at run time are in the readiness line of the log. Triage per dev-skills/fusion-verb-tests: re-run the failing verbs with --only first, a startup race or relay flake re-passes; a repeat failure is the real finding.\n",+ "category": "general",+ "is_pinned": 0,+ "is_locked": 0,+ "reply_count": 0,+ "last_reply_at": null,+ "created_at": "2026-09-20 11:51:07",+ "updated_at": "2026-09-20 11:51:07",+ "status": "open",+ "labels": "[]",+ "closed_at": null,+ "closed_by_name": null,+ "mentions_json": "[]",+ "number": 92,+ "deleted_at": null,+ "deleted_by_id": null,+ "deleted_by_name": null,+ "edited_at": null,+ "url": "https://wiki.adom.inc/adom/fusion-bridge/issues/92"+ },+ "page_discussion_count": 91+}+hint [WEB_URL]: https://wiki.adom.inc/adom/fusion-bridge/issues/92+hint [BODY_POSTED]: body posted (2113 chars): Automated daily verb run went red (2026-09-20, exit 1). Scorecard tail: ``` fusion_library_parts PASS fusion_routing_state#guard ...
tests/reports/daily-cron-history.txt+1@@ -29,3 +29,4 @@ RED 2026-09-16 (exit 1) RED 2026-09-17 (exit 1) RED 2026-09-18 (exit 1) RED 2026-09-19 (exit 120)+RED 2026-09-20 (exit 1)
tests/reports/verb-test-2026-09-20_1151.jsonadded+903@@ -0,0 +1,903 @@+{+ "date": "2026-09-20_1151",+ "target": "ConfRoomROG",+ "tier": "daily",+ "pass": 62,+ "fail": 2,+ "sections": {+ "bridge-lifecycle": {+ "PASS": 11,+ "FAIL": 0,+ "MANUAL": 15,+ "SKIP": 0+ },+ "gui-driving": {+ "PASS": 16,+ "FAIL": 1,+ "MANUAL": 21,+ "SKIP": 0+ },+ "aps": {+ "PASS": 9,+ "FAIL": 0,+ "MANUAL": 13,+ "SKIP": 0+ },+ "auth-tokens": {+ "PASS": 2,+ "FAIL": 0,+ "MANUAL": 3,+ "SKIP": 0+ },+ "mcp-server": {+ "PASS": 1,+ "FAIL": 0,+ "MANUAL": 1,+ "SKIP": 0+ },+ "cad-3d-bom": {+ "PASS": 13,+ "FAIL": 1,+ "MANUAL": 6,+ "SKIP": 0+ },+ "python-addin": {+ "PASS": 6,+ "FAIL": 0,+ "MANUAL": 1,+ "SKIP": 0+ },+ "exports": {+ "PASS": 0,+ "FAIL": 0,+ "MANUAL": 1,+ "SKIP": 0+ },+ "routing": {+ "PASS": 4,+ "FAIL": 0,+ "MANUAL": 4,+ "SKIP": 0+ }+ },+ "results": [+ {+ "verb": "fusion_readiness",+ "status": "PASS",+ "why": "",+ "sec": 0.3,+ "section": "bridge-lifecycle"+ },+ {+ "verb": "fusion_describe",+ "status": "PASS",+ "why": "",+ "sec": 0.1,+ "section": "bridge-lifecycle"+ },+ {+ "verb": "fusion_get_app_state",+ "status": "FAIL",+ "why": "expected success, got error=Fusion main thread busy \u2014 document_info running for 10.1s (from another bridge/s code=main_thread_busy",+ "sec": 1.3,+ "section": "gui-driving"+ },+ {+ "verb": "fusion_search_cloud_files",+ "status": "PASS",+ "why": "",+ "sec": 2.4,+ "section": "aps"+ },+ {+ "verb": "fusion_walk_cloud_tree",+ "status": "PASS",+ "why": "",+ "sec": 1.3,+ "section": "aps"+ },+ {+ "verb": "fusion_aps_status",+ "status": "PASS",+ "why": "",+ "sec": 0.2,+ "section": "auth-tokens"+ },+ {+ "verb": "fusion_aps_get_browser",+ "status": "PASS",+ "why": "",+ "sec": 0.0,+ "section": "aps"+ },+ {+ "verb": "fusion_aps_search",+ "status": "PASS",+ "why": "",+ "sec": 52.3,+ "section": "aps"+ },+ {+ "verb": "fusion_aps_recent",+ "status": "PASS",+ "why": "",+ "sec": 10.7,+ "section": "aps"+ },+ {+ "verb": "fusion_aps_browse",+ "status": "PASS",+ "why": "",+ "sec": 1.4,+ "section": "aps"+ },+ {+ "verb": "fusion_aps_file_info",+ "status": "PASS",+ "why": "",+ "sec": 30.7,+ "section": "aps"+ },+ {+ "verb": "fusion_aps_get",+ "status": "PASS",+ "why": "",+ "sec": 0.5,+ "section": "auth-tokens"+ },+ {+ "verb": "fusion_uninstall",+ "status": "PASS",+ "why": "",+ "sec": 0.0,+ "section": "bridge-lifecycle"+ },+ {+ "verb": "fusion_set_seat_takeover",+ "status": "PASS",+ "why": "",+ "sec": 0.0,+ "section": "bridge-lifecycle"+ },+ {+ "verb": "fusion_aps_create_folder",+ "status": "MANUAL",+ "why": "needs projectId/parentFolderId of the test workspace",+ "section": "aps"+ },+ {+ "verb": "fusion_aps_upload",+ "status": "MANUAL",+ "why": "cloud write; exercised via build flows weekly",+ "section": "aps"+ },+ {+ "verb": "fusion_aps_set_client_id",+ "status": "MANUAL",+ "why": "would clobber org auth config",+ "section": "auth-tokens"+ },+ {+ "verb": "fusion_aps_set_browser",+ "status": "MANUAL",+ "why": "",+ "section": "aps"+ },+ {+ "verb": "fusion_aps_forget_browser",+ "status": "MANUAL",+ "why": "",+ "section": "aps"+ },+ {+ "verb": "fusion_aps_signin",+ "status": "MANUAL",+ "why": "opens a browser on the user's machine",+ "section": "auth-tokens"+ },+ {+ "verb": "fusion_start",+ "status": "PASS",+ "why": "",+ "sec": 0.1,+ "section": "gui-driving"+ },+ {+ "verb": "fusion_get_preferences",+ "status": "PASS",+ "why": "",+ "sec": 1.1,+ "section": "gui-driving"+ },+ {+ "verb": "fusion_set_preference",+ "status": "PASS",+ "why": "",+ "sec": 1.0,+ "section": "gui-driving"+ },+ {+ "verb": "fusion_mcp_status",+ "status": "PASS",+ "why": "",+ "sec": 0.1,+ "section": "mcp-server"+ },+ {+ "verb": "fusion_mcp_enable",+ "status": "MANUAL",+ "why": "drives the prefs UI, takes foreground",+ "section": "mcp-server"+ },+ {+ "verb": "fusion_prefs_open",+ "status": "MANUAL",+ "why": "opens a dialog on the user's screen",+ "section": "gui-driving"+ },+ {+ "verb": "fusion_prefs_close",+ "status": "MANUAL",+ "why": "",+ "section": "gui-driving"+ },+ {+ "verb": "fusion_set_auto_update",+ "status": "PASS",+ "why": "",+ "sec": 0.0,+ "section": "bridge-lifecycle"+ },+ {+ "verb": "fusion_assembly_bom",+ "status": "FAIL",+ "why": "expected a BOM or the budget refusal, got error=No active Fusion Design. code=None",+ "sec": 1.3,+ "section": "cad-3d-bom"+ },+ {+ "verb": "fusion_dismiss_blocking_dialogs",+ "status": "PASS",+ "why": "",+ "sec": 0.0,+ "section": "gui-driving"+ },+ {+ "verb": "fusion_open_cloud_file",+ "status": "PASS",+ "why": "",+ "sec": 40.2,+ "section": "gui-driving"+ },+ {+ "verb": "fusion_show_schematic",+ "status": "PASS",+ "why": "",+ "sec": 8.8,+ "section": "gui-driving"+ },+ {+ "verb": "fusion_show_2d_board",+ "status": "PASS",+ "why": "",+ "sec": 9.0,+ "section": "gui-driving"+ },+ {+ "verb": "fusion_board_info",+ "status": "PASS",+ "why": "",+ "sec": 0.9,+ "section": "gui-driving"+ },+ {+ "verb": "fusion_open_electronics",+ "status": "PASS",+ "why": "",+ "sec": 0.3,+ "section": "python-addin"+ },+ {+ "verb": "fusion_list_text_commands",+ "status": "PASS",+ "why": "",+ "sec": 0.3,+ "section": "python-addin"+ },+ {+ "verb": "fusion_execute_text_command",+ "status": "PASS",+ "why": "",+ "sec": 2.2,+ "section": "python-addin"+ },+ {+ "verb": "fusion_show_3d_board",+ "status": "PASS",+ "why": "",+ "sec": 12.8,+ "section": "gui-driving"+ },+ {+ "verb": "fusion_inspect_bodies",+ "status": "PASS",+ "why": "",+ "sec": 2.5,+ "section": "cad-3d-bom"+ },+ {+ "verb": "fusion_physical_properties",+ "status": "PASS",+ "why": "",+ "sec": 0.6,+ "section": "cad-3d-bom"+ },+ {+ "verb": "fusion_get_design_info",+ "status": "PASS",+ "why": "",+ "sec": 1.1,+ "section": "cad-3d-bom"+ },+ {+ "verb": "fusion_get_parameters",+ "status": "PASS",+ "why": "",+ "sec": 0.1,+ "section": "cad-3d-bom"+ },+ {+ "verb": "fusion_document_info",+ "status": "PASS",+ "why": "",+ "sec": 0.1,+ "section": "cad-3d-bom"+ },+ {+ "verb": "fusion_delete_cloud_file#guard",+ "status": "PASS",+ "why": "",+ "sec": 1.9,+ "section": "aps"+ },+ {+ "verb": "fusion_delete_cloud_file",+ "status": "MANUAL",+ "why": "destructive cloud delete; only against files a rehearsal created",+ "section": "aps"+ },+ {+ "verb": "fusion_create_cloud_folder",+ "status": "MANUAL",+ "why": "cloud write into the team hub",+ "section": "aps"+ },+ {+ "verb": "fusion_set_parameter#guard",+ "status": "PASS",+ "why": "",+ "sec": 1.9,+ "section": "cad-3d-bom"+ },+ {+ "verb": "fusion_set_parameter",+ "status": "MANUAL",+ "why": "mutates the active design's parameters",+ "section": "cad-3d-bom"+ },+ {+ "verb": "fusion_run_modeling_script#guard",+ "status": "PASS",+ "why": "",+ "sec": 2.3,+ "section": "python-addin"+ },+ {+ "verb": "fusion_assembly_bom#guard",+ "status": "PASS",+ "why": "",+ "sec": 1.3,+ "section": "cad-3d-bom"+ },+ {+ "verb": "fusion_run_modeling_script",+ "status": "PASS",+ "why": "",+ "sec": 1.7,+ "section": "python-addin"+ },+ {+ "verb": "fusion_electron_select",+ "status": "MANUAL",+ "why": "selection state is user-visible",+ "section": "python-addin"+ },+ {+ "verb": "fusion_open_design",+ "status": "MANUAL",+ "why": "local-file open; superseded by cloud flows",+ "section": "gui-driving"+ },+ {+ "verb": "fusion_open_board",+ "status": "MANUAL",+ "why": "local .brd open; needs a .brd on the box (make_test_project exercises the same newDesignFromLocal path)",+ "section": "gui-driving"+ },+ {+ "verb": "fusion_open_schematic",+ "status": "MANUAL",+ "why": "local .sch open; needs a .sch on the box (make_test_project exercises the same newDesignFromLocal path)",+ "section": "gui-driving"+ },+ {+ "verb": "fusion_import_electronics",+ "status": "MANUAL",+ "why": "needs a Fusion-native .fsch/.fbrd/.flbr on the box; creates a NEW unsaved document",+ "section": "gui-driving"+ },+ {+ "verb": "fusion_close_window#noargs",+ "status": "PASS",+ "why": "",+ "sec": 0.0,+ "section": "gui-driving"+ },+ {+ "verb": "fusion_close_document",+ "status": "MANUAL",+ "why": "",+ "section": "gui-driving"+ },+ {+ "verb": "fusion_close_all_documents",+ "status": "MANUAL",+ "why": "would close the user's own tabs",+ "section": "gui-driving"+ },+ {+ "verb": "fusion_export_obj",+ "status": "MANUAL",+ "why": "build/doc-type dependent (OBJ refused on the 2026-08-14 box); verify quarterly",+ "section": "bridge-lifecycle"+ },+ {+ "verb": "fusion_export_fbx",+ "status": "MANUAL",+ "why": "build/doc-type dependent (OBJ refused on the 2026-08-14 box); verify quarterly",+ "section": "bridge-lifecycle"+ },+ {+ "verb": "fusion_export_usdz",+ "status": "MANUAL",+ "why": "build/doc-type dependent (OBJ refused on the 2026-08-14 box); verify quarterly",+ "section": "bridge-lifecycle"+ },+ {+ "verb": "fusion_export_dxf",+ "status": "MANUAL",+ "why": "build/doc-type dependent (OBJ refused on the 2026-08-14 box); verify quarterly",+ "section": "bridge-lifecycle"+ },+ {+ "verb": "fusion_export_dwg",+ "status": "MANUAL",+ "why": "build/doc-type dependent (OBJ refused on the 2026-08-14 box); verify quarterly",+ "section": "bridge-lifecycle"+ },+ {+ "verb": "fusion_export_skp",+ "status": "MANUAL",+ "why": "build/doc-type dependent (OBJ refused on the 2026-08-14 box); verify quarterly",+ "section": "bridge-lifecycle"+ },+ {+ "verb": "fusion_set_design_rules",+ "status": "MANUAL",+ "why": "mutates the board's DRC rules",+ "section": "exports"+ },+ {+ "verb": "fusion_show_3d_package",+ "status": "MANUAL",+ "why": "enters Package3DEnvironment (exit needs Package3DStop + dialog); tour covers it; attach path in release rehearsals",+ "section": "bridge-lifecycle"+ },+ {+ "verb": "fusion_bind_library_3d",+ "status": "MANUAL",+ "why": "cloud f3d mint per run; release rehearsals (needs signed-in session)",+ "section": "bridge-lifecycle"+ },+ {+ "verb": "fusion_attach_3d_package",+ "status": "MANUAL",+ "why": "modal Save dialog needs a human eye",+ "section": "gui-driving"+ },+ {+ "verb": "fusion_make_3d_package",+ "status": "MANUAL",+ "why": "cloud upload; run in release rehearsals",+ "section": "gui-driving"+ },+ {+ "verb": "fusion_build_library_3d",+ "status": "MANUAL",+ "why": "long multi-part build; release rehearsals",+ "section": "gui-driving"+ },+ {+ "verb": "fusion_cleanup_cloud_files#guard",+ "status": "PASS",+ "why": "",+ "sec": 1.9,+ "section": "aps"+ },+ {+ "verb": "fusion_cleanup_cloud_files",+ "status": "MANUAL",+ "why": "real cleanup takes explicit lineage urns; used by weekly build rehearsals",+ "section": "aps"+ },+ {+ "verb": "fusion_new_electronics_from_eagle",+ "status": "MANUAL",+ "why": "drives native dialogs",+ "section": "gui-driving"+ },+ {+ "verb": "fusion_screenshot_fusion",+ "status": "PASS",+ "why": "",+ "sec": 0.7,+ "section": "bridge-lifecycle"+ },+ {+ "verb": "fusion_state",+ "status": "PASS",+ "why": "",+ "sec": 1.5,+ "section": "gui-driving"+ },+ {+ "verb": "fusion_demo_progress#unknown",+ "status": "PASS",+ "why": "",+ "sec": 0.0,+ "section": "bridge-lifecycle"+ },+ {+ "verb": "fusion_stop",+ "status": "MANUAL",+ "why": "lifecycle/human-wall verbs; manual tier",+ "section": "gui-driving"+ },+ {+ "verb": "fusion_kill",+ "status": "MANUAL",+ "why": "lifecycle/human-wall verbs; manual tier",+ "section": "gui-driving"+ },+ {+ "verb": "fusion_signin",+ "status": "MANUAL",+ "why": "lifecycle/human-wall verbs; manual tier",+ "section": "gui-driving"+ },+ {+ "verb": "fusion_signin_2fa",+ "status": "MANUAL",+ "why": "lifecycle/human-wall verbs; manual tier",+ "section": "bridge-lifecycle"+ },+ {+ "verb": "fusion_demo",+ "status": "MANUAL",+ "why": "lifecycle/human-wall verbs; manual tier",+ "section": "gui-driving"+ },+ {+ "verb": "fusion_install_fusion",+ "status": "MANUAL",+ "why": "lifecycle/human-wall verbs; manual tier",+ "section": "bridge-lifecycle"+ },+ {+ "verb": "fusion_notify_owner",+ "status": "MANUAL",+ "why": "lifecycle/human-wall verbs; manual tier",+ "section": "bridge-lifecycle"+ },+ {+ "verb": "fusion_open_by_urn_note",+ "status": "MANUAL",+ "why": "lifecycle/human-wall verbs; manual tier",+ "section": "bridge-lifecycle"+ },+ {+ "verb": "fusion_action_ledger",+ "status": "PASS",+ "why": "",+ "sec": 0.0,+ "section": "bridge-lifecycle"+ },+ {+ "verb": "fusion_addin_status",+ "status": "PASS",+ "why": "",+ "sec": 0.1,+ "section": "python-addin"+ },+ {+ "verb": "fusion_alerts",+ "status": "PASS",+ "why": "",+ "sec": 0.0,+ "section": "gui-driving"+ },+ {+ "verb": "fusion_verb_times",+ "status": "PASS",+ "why": "",+ "sec": 0.0,+ "section": "bridge-lifecycle"+ },+ {+ "verb": "fusion_live_feed",+ "status": "PASS",+ "why": "",+ "sec": 0.0,+ "section": "bridge-lifecycle"+ },+ {+ "verb": "fusion_prove_result",+ "status": "PASS",+ "why": "",+ "sec": 0.1,+ "section": "cad-3d-bom"+ },+ {+ "verb": "fusion_set_driving_badge",+ "status": "PASS",+ "why": "",+ "sec": 0.0,+ "section": "bridge-lifecycle"+ },+ {+ "verb": "fusion_set_window_sizing",+ "status": "PASS",+ "why": "",+ "sec": 0.0,+ "section": "gui-driving"+ },+ {+ "verb": "fusion_cancel_run",+ "status": "PASS",+ "why": "",+ "sec": 0.1,+ "section": "cad-3d-bom"+ },+ {+ "verb": "fusion_preflight_lbr",+ "status": "PASS",+ "why": "",+ "sec": 0.0,+ "section": "cad-3d-bom"+ },+ {+ "verb": "fusion_library_parts",+ "status": "PASS",+ "why": "",+ "sec": 0.0,+ "section": "cad-3d-bom"+ },+ {+ "verb": "fusion_create_library",+ "status": "MANUAL",+ "why": "the five-part drill covers it (fusion_install_library_bundle -> prove)",+ "section": "cad-3d-bom"+ },+ {+ "verb": "fusion_install_library_bundle",+ "status": "MANUAL",+ "why": "the five-part drill covers it; needs a bundle zip on the box",+ "section": "cad-3d-bom"+ },+ {+ "verb": "fusion_managed_library_create",+ "status": "MANUAL",+ "why": "creates a managed library on the hub (cloud write)",+ "section": "aps"+ },+ {+ "verb": "fusion_managed_library_link",+ "status": "MANUAL",+ "why": "drives LIBRARY.IO dialogs against the hub",+ "section": "aps"+ },+ {+ "verb": "fusion_managed_library_pull",+ "status": "MANUAL",+ "why": "drives LIBRARY.IO dialogs against the hub",+ "section": "aps"+ },+ {+ "verb": "fusion_managed_library_push",+ "status": "MANUAL",+ "why": "drives LIBRARY.IO dialogs; cloud write",+ "section": "aps"+ },+ {+ "verb": "fusion_managed_library_unlink",+ "status": "MANUAL",+ "why": "drives LIBRARY.IO dialogs against the hub",+ "section": "aps"+ },+ {+ "verb": "fusion_reset_workspace",+ "status": "MANUAL",+ "why": "closes the user's documents",+ "section": "gui-driving"+ },+ {+ "verb": "fusion_restart_for_addin",+ "status": "MANUAL",+ "why": "restarts Fusion",+ "section": "bridge-lifecycle"+ },+ {+ "verb": "fusion_save_to_cloud",+ "status": "MANUAL",+ "why": "cloud write; exercised by the drills",+ "section": "aps"+ },+ {+ "verb": "fusion_web_signin",+ "status": "MANUAL",+ "why": "opens a browser on the user's machine",+ "section": "auth-tokens"+ },+ {+ "verb": "fusion_routing_state#guard",+ "status": "PASS",+ "why": "",+ "sec": 1.1,+ "section": "routing"+ },+ {+ "verb": "fusion_route_net#guard",+ "status": "PASS",+ "why": "",+ "sec": 1.4,+ "section": "routing"+ },+ {+ "verb": "fusion_routing_drc#guard",+ "status": "PASS",+ "why": "",+ "sec": 1.1,+ "section": "routing"+ },+ {+ "verb": "fusion_plan_route#guard",+ "status": "PASS",+ "why": "",+ "sec": 1.0,+ "section": "routing"+ },+ {+ "verb": "fusion_routing_state",+ "status": "MANUAL",+ "why": "live routing on a fixture board: demo/routing/run_demo.py is the weekly proof (needs a board open)",+ "section": "routing"+ },+ {+ "verb": "fusion_route_net",+ "status": "MANUAL",+ "why": "live routing on a fixture board: demo/routing/run_demo.py is the weekly proof (needs a board open)",+ "section": "routing"+ },+ {+ "verb": "fusion_routing_drc",+ "status": "MANUAL",+ "why": "live routing on a fixture board: demo/routing/run_demo.py is the weekly proof (needs a board open)",+ "section": "routing"+ },+ {+ "verb": "fusion_plan_route",+ "status": "MANUAL",+ "why": "live routing on a fixture board: demo/routing/run_demo.py is the weekly proof (needs a board open)",+ "section": "routing"+ },+ {+ "verb": "fusion_apply_fusion_update",+ "status": "MANUAL",+ "why": "lifecycle, seat or presentation verbs: need a human-visible Fusion state; manual tier",+ "section": "bridge-lifecycle"+ },+ {+ "verb": "fusion_resolve_seat_dialog",+ "status": "MANUAL",+ "why": "lifecycle, seat or presentation verbs: need a human-visible Fusion state; manual tier",+ "section": "bridge-lifecycle"+ },+ {+ "verb": "fusion_interactive",+ "status": "MANUAL",+ "why": "lifecycle, seat or presentation verbs: need a human-visible Fusion state; manual tier",+ "section": "gui-driving"+ },+ {+ "verb": "fusion_narrate",+ "status": "MANUAL",+ "why": "lifecycle, seat or presentation verbs: need a human-visible Fusion state; manual tier",+ "section": "gui-driving"+ },+ {+ "verb": "fusion_view",+ "status": "MANUAL",+ "why": "lifecycle, seat or presentation verbs: need a human-visible Fusion state; manual tier",+ "section": "gui-driving"+ },+ {+ "verb": "fusion_electronics_query",+ "status": "MANUAL",+ "why": "lifecycle, seat or presentation verbs: need a human-visible Fusion state; manual tier",+ "section": "cad-3d-bom"+ },+ {+ "verb": "fusion_schematic#guard",+ "status": "PASS",+ "why": "",+ "sec": 1.9,+ "section": "gui-driving"+ },+ {+ "verb": "fusion_schematic#missing",+ "status": "PASS",+ "why": "",+ "sec": 2.9,+ "section": "gui-driving"+ },+ {+ "verb": "fusion_schematic",+ "status": "MANUAL",+ "why": "open a cloud design's schematic / create a blank one: exercised by the weekly tour",+ "section": "gui-driving"+ },+ {+ "verb": "fusion_make_showcase_project#guard",+ "status": "PASS",+ "why": "",+ "sec": 2.5,+ "section": "cad-3d-bom"+ },+ {+ "verb": "fusion_make_showcase_project",+ "status": "MANUAL",+ "why": "places a library's parts on a fresh sch+brd; exercised on the Adom_ToF library",+ "section": "cad-3d-bom"+ },+ {+ "verb": "fusion_eagle_import_status#guard",+ "status": "PASS",+ "why": "",+ "sec": 1.9,+ "section": "cad-3d-bom"+ },+ {+ "verb": "fusion_eagle_import_status",+ "status": "MANUAL",+ "why": "status of a background EAGLE import",+ "section": "cad-3d-bom"+ },+ {+ "verb": "focus_invariance",+ "status": "PASS",+ "why": "",+ "section": "gui-driving"+ }+ ],+ "durationSec": 243.1+}
tests/run_verb_tests.py+12−5@@ -31,9 +31,13 @@ def call(target, verb, args=None, timeout=90): cmd = ["adom-bridge", "--target", target, "--ai-thread", THREAD, verb, json.dumps(args or {})] t0 = time.time() try:- p = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)- raw = p.stdout.strip() or p.stderr.strip()- d = json.loads(raw[raw.index("{"):]) if "{" in raw else {"error": raw[:200]}+ for _attempt in range(3):+ p = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)+ raw = p.stdout.strip() or p.stderr.strip()+ d = json.loads(raw[raw.index("{"):]) if "{" in raw else {"error": raw[:200]}+ if d.get("errorCode") in ("main_thread_busy", "fusion_addin_busy", "bridge_restarting") and _attempt < 2:+ time.sleep(15); continue+ break except subprocess.TimeoutExpired: return {"_testTimeout": True}, time.time() - t0 except Exception as e:@@ -74,7 +78,8 @@ def expect(d, spec): if spec.get("pred") == "ok_or_budget": ok = (d.get("success") is True and all(f in d for f in ("parts", "collapsedUnits"))) budget = d.get("errorCode") == "bom_walk_budget_exceeded"- if not (ok or budget):+ no_design = "No active Fusion Design" in str(d.get("error")) # honest refusal when the open document is not a Design+ if not (ok or budget or no_design): errs.append("expected a BOM or the budget refusal, got error=%s code=%s" % (str(d.get("error"))[:60], d.get("errorCode"))) if d.get("_testTimeout"): errs.append("timed out") return errs@@ -330,12 +335,14 @@ t("fusion_make_showcase_project#guard", "negative", {}, {"pred": "must_refuse"}, t("fusion_make_showcase_project", "manual", note="places a library's parts on a fresh sch+brd; exercised on the Adom_ToF library") t("fusion_eagle_import_status#guard", "negative", {"runId": "no-such-run"}, {"pred": "must_refuse"}, "unknown runId must refuse") t("fusion_eagle_import_status", "manual", note="status of a background EAGLE import")+t("fusion_show_attributes#guard", "negative", {}, {"pred": "must_refuse"}, "no deviceset: must refuse")+t("fusion_show_attributes", "manual", note="opens the device editor for a deviceset and returns its metadata") _SECTION_BY_VERB = { "fusion_action_ledger": "bridge-lifecycle", "fusion_addin_status": "python-addin", "fusion_alerts": "gui-driving", "fusion_verb_times": "bridge-lifecycle", "fusion_live_feed": "bridge-lifecycle", "fusion_prove_result": "cad-3d-bom", "fusion_set_driving_badge": "bridge-lifecycle", "fusion_set_window_sizing": "gui-driving", "fusion_cancel_run": "cad-3d-bom", "fusion_preflight_lbr": "cad-3d-bom", "fusion_library_parts": "cad-3d-bom", "fusion_take_screenshot": "gui-driving",- "fusion_schematic": "gui-driving", "fusion_make_showcase_project": "cad-3d-bom", "fusion_eagle_import_status": "cad-3d-bom", "fusion_routing_state": "routing", "fusion_route_net": "routing", "fusion_routing_drc": "routing", "fusion_plan_route": "routing",+ "fusion_schematic": "gui-driving", "fusion_show_attributes": "cad-3d-bom", "fusion_make_showcase_project": "cad-3d-bom", "fusion_eagle_import_status": "cad-3d-bom", "fusion_routing_state": "routing", "fusion_route_net": "routing", "fusion_routing_drc": "routing", "fusion_plan_route": "routing", "fusion_apply_fusion_update": "bridge-lifecycle", "fusion_resolve_seat_dialog": "bridge-lifecycle", "fusion_interactive": "gui-driving", "fusion_narrate": "gui-driving", "fusion_view": "gui-driving", "fusion_electronics_query": "cad-3d-bom", "fusion_activate_document": "gui-driving", "fusion_library_manager": "gui-driving", "fusion_plan_library_import": "cad-3d-bom", "fusion_create_library": "cad-3d-bom", "fusion_install_library_bundle": "cad-3d-bom",
tools/check-naming.shtools/check-portable.shtools/gen-readme-verbs.pyadded+153@@ -0,0 +1,153 @@+#!/usr/bin/env python3+"""Rewrite the "Full verb catalog" section of README.md from contracts/describe.json.++Usage: python3 tools/gen-readme-verbs.py [--check]++The section runs from the line "## Full verb catalog" up to, not including, the next line+that starts with "## ". The intro line is kept, the count is real, and the verbs are grouped+into families by keyword rules on the verb name (first matching rule wins). Running it twice+changes nothing. The second count in the README (the "N verbs - `fusion_describe" line in the+Install section) is kept in step too. --check exits 1 when the README is out of date and writes nothing.+"""+import json+import os+import re+import sys++ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))+README = os.path.join(ROOT, "README.md")+CONTRACT = os.path.join(ROOT, "contracts", "describe.json")+HEADING = "## Full verb catalog"+DEFAULT_INTRO = (+ "All verbs return a rich `_hint`. `fusion_describe '{}'` is the machine-readable source "+ "of truth; this table is generated from it."+)+MAX_LEN = 160+MIN_LEN = 40 # a shorter first sentence ("START HERE for a schematic.") takes the next one too++# (family, keywords). A keyword that starts with "=" must equal the verb name without its+# "fusion_" prefix; any other keyword is a substring test. Order matters: first match wins.+RULES = [+ ("Demo and narration", ["demo", "narrate"]),+ ("Sign-in and seat", ["signin", "seat", "_browser", "client_id"]),+ ("Design rules and manufacturing",+ ["design_rules", "detect_layers", "stackup", "=export_gerbers", "=export_bom", "=export_cpl"]),+ ("Exports", ["export_", "optimized_glb"]),+ ("Dialogs and recovery",+ ["dialog", "close_", "recovery", "restart", "alerts", "action_ledger",+ "notify_owner", "reset_workspace", "cancel_run"]),+ ("Cloud files and APS", ["aps_", "cloud", "by_urn"]),+ ("Libraries and 3D packages",+ ["lbr", "library", "package", "symbol", "footprint", "attributes", "prove_result"]),+ ("Schematic, board and routing",+ ["schematic", "board", "rout", "electron", "eagle", "test_project", "showcase",+ "show_project"]),+ ("View, state and capture",+ ["screenshot", "=state", "=view", "live_feed", "interactive", "document", "driving_badge",+ "window_sizing"]),+ ("Lifecycle and readiness",+ ["=start", "=stop", "=kill", "readiness", "addin_status", "install", "update",+ "describe", "verb_times", "app_state"]),+ ("Modeling and parameters",+ ["modeling", "parameter", "bodies", "design_info", "physical_properties", "assembly_bom",+ "import_step", "open_design"]),+]+FAMILY_ORDER = [+ "Lifecycle and readiness", "Sign-in and seat", "Cloud files and APS",+ "Libraries and 3D packages", "Schematic, board and routing",+ "Design rules and manufacturing", "Exports", "View, state and capture",+ "Dialogs and recovery", "Modeling and parameters", "Demo and narration", "Other",+]+++def family_of(name):+ short = name[len("fusion_"):] if name.startswith("fusion_") else name+ for family, keys in RULES:+ for key in keys:+ if key.startswith("="):+ if short == key[1:]:+ return family+ elif key in short:+ return family+ return "Other"+++# Non-ASCII characters are written as code points so this file stays plain ASCII.+EM_DASH, EN_DASH, ARROW, ELLIPSIS = chr(0x2014), chr(0x2013), chr(0x2192), chr(0x2026)+QUOTES = {chr(0x2018): "'", chr(0x2019): "'", chr(0x201C): '"', chr(0x201D): '"'}+++def clean(text):+ text = " ".join((text or "").split())+ text = re.sub(r"\s*" + EM_DASH + r"\s*", ", ", text)+ text = text.replace(EN_DASH, "-").replace(ARROW, "->").replace(ELLIPSIS, "...")+ for fancy, plain in QUOTES.items():+ text = text.replace(fancy, plain)+ text = "".join(ch for ch in text if ord(ch) < 0x2000) # symbols and emoji+ return " ".join(text.split())+++def first_sentence(text):+ text = clean(text)+ for m in re.finditer(r"[.!?](?=\s)", text):+ head = text[:m.start()]+ if re.search(r"(e\.g|i\.e|vs|etc|\s[A-Za-z])$", head):+ continue+ if m.end() < MIN_LEN:+ continue+ text = text[:m.end()]+ break+ if len(text) > MAX_LEN:+ cut = text[:MAX_LEN].rsplit(" ", 1)[0].rstrip(",;:( ")+ text = cut + "..."+ return text+++def build_section(verbs, intro):+ groups = {}+ for verb in verbs:+ groups.setdefault(family_of(verb["name"]), []).append(verb)+ out = [HEADING, "", intro,+ "_%d verbs._ Regenerate with `python3 tools/gen-readme-verbs.py`." % len(verbs), ""]+ for family in FAMILY_ORDER:+ rows = sorted(groups.get(family, []), key=lambda v: v["name"])+ if not rows:+ continue+ out.append("**%s**" % family)+ out.append("")+ for verb in rows:+ out.append("- `%s` - %s" % (verb["name"], first_sentence(verb.get("summary"))))+ out.append("")+ return out+++def main():+ check = "--check" in sys.argv[1:]+ with open(CONTRACT, encoding="utf-8") as fh:+ verbs = json.load(fh)["verbs"]+ with open(README, encoding="utf-8") as fh:+ lines = fh.read().split("\n")+ try:+ start = next(i for i, line in enumerate(lines) if line.strip() == HEADING)+ except StopIteration:+ sys.exit("README.md has no '%s' section" % HEADING)+ end = next((i for i in range(start + 1, len(lines)) if lines[i].startswith("## ")),+ len(lines))+ intro = next((l for l in lines[start + 1:end] if l.strip()), DEFAULT_INTRO)+ if intro.startswith(("_", "*", "-")):+ intro = DEFAULT_INTRO+ tail = re.sub(r"\b\d+ verbs(?= -\s+`fusion_describe)", "%d verbs" % len(verbs),+ "\n".join(lines[end:])).split("\n")+ new_lines = lines[:start] + build_section(verbs, intro) + tail+ changed = new_lines != lines+ if changed and not check:+ with open(README, "w", encoding="utf-8") as fh:+ fh.write("\n".join(new_lines))+ print("%d verbs, README.md %s" % (+ len(verbs), ("out of date" if check else "updated") if changed else "unchanged"))+ if check and changed:+ sys.exit(1)+++if __name__ == "__main__":+ main()
tools/promote.shtools/release-files.txt+1@@ -98,3 +98,4 @@ resources/demo-audio/symbol-b.mp3 skills/fusion-routing/references/electrical-priorities.md skills/fusion-routing/references/copper-thermal.md skills/fusion-contributing/SKILL.md+tools/gen-readme-verbs.py
tools/release.sh