From c57797cb0f020b1582ec3057c0c641c2c5ab7375 Mon Sep 17 00:00:00 2001 From: benjsmith Date: Sat, 12 Sep 2026 23:48:32 +0000 Subject: [PATCH 1/2] fix: restore CI hygiene on main - Add root pnpm-lock.yaml symlink so pnpm/setup cache resolves (lockfile lives under frontend/; default cache path is repo-root) - Remove switchbay-sketch stub and code-workspace (belong on exp/vscode-plugin) - Economy workers prefer non-chief cheap providers (gemini/mlx over anthropic haiku) - Harden Pi harness killpg + path assertions for Windows easy wins Signed-off-by: benjsmith --- .gitignore | 5 +++ extensions/switchbay-sketch/README.md | 20 ----------- extensions/switchbay-sketch/package.json | 25 -------------- pnpm-lock.yaml | 1 + src/switchbay/kernel/harness_pi.py | 7 ++-- src/switchbay/kernel/hire.py | 4 +++ switchbay.code-workspace | 43 ------------------------ tests/unit/test_kernel_harness.py | 13 ++++--- 8 files changed, 23 insertions(+), 95 deletions(-) delete mode 100644 extensions/switchbay-sketch/README.md delete mode 100644 extensions/switchbay-sketch/package.json create mode 120000 pnpm-lock.yaml delete mode 100644 switchbay.code-workspace diff --git a/.gitignore b/.gitignore index 091e603..088f80a 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,11 @@ frontend/.vite/ !/.vscode/launch.json !/.vscode/tasks.json +# VS Code plugin experiment (belongs on exp/vscode-plugin, not main) +switchbay.code-workspace +extensions/switchbay/ +extensions/switchbay-sketch/ + # ── vscode plugin compiled output (source under extensions/ is tracked) ── extensions/**/out/ extensions/**/media/graph/ diff --git a/extensions/switchbay-sketch/README.md b/extensions/switchbay-sketch/README.md deleted file mode 100644 index 3240da7..0000000 --- a/extensions/switchbay-sketch/README.md +++ /dev/null @@ -1,20 +0,0 @@ -# Switch Bay Sketch (companion) - -Second VSIX. Not packaged with the main Switch Bay plugin. - -Custom editor for Excalidraw (then drawio) scenes stored at: - -``` -/.workbench/sketches/.json -``` - -PNG export (same contract as `src/switchbay/sketches.py`): - -``` -/wiki/figures/_assets/.png -``` - -The main plugin's graph "To sketch" / `@switchbay /sketch` only writes -these files and asks you to install this companion to edit them. - -Not implemented on this spike yet — file-format contract only. diff --git a/extensions/switchbay-sketch/package.json b/extensions/switchbay-sketch/package.json deleted file mode 100644 index 788252a..0000000 --- a/extensions/switchbay-sketch/package.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "switchbay-sketch", - "displayName": "Switch Bay Sketch", - "description": "Excalidraw/drawio sketches for Switch Bay workspaces. Companion to the Switch Bay plugin, not bundled with it.", - "version": "0.0.0", - "publisher": "switchbay", - "license": "LicenseRef-FSL-1.1-ALv2", - "engines": { - "vscode": "^1.96.0" - }, - "categories": ["Other"], - "activationEvents": [], - "contributes": { - "customEditors": [ - { - "viewType": "switchbay.sketch", - "displayName": "Switch Bay Sketch", - "selector": [ - { "filenamePattern": ".workbench/sketches/*.json" } - ], - "priority": "option" - } - ] - } -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 120000 index 0000000..aad4577 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1 @@ +frontend/pnpm-lock.yaml \ No newline at end of file diff --git a/src/switchbay/kernel/harness_pi.py b/src/switchbay/kernel/harness_pi.py index 1c2d41b..fc92031 100644 --- a/src/switchbay/kernel/harness_pi.py +++ b/src/switchbay/kernel/harness_pi.py @@ -228,8 +228,11 @@ def pi_argv(req: NodeRequest, *, binary: str, ext: Path) -> list[str]: def _killpg(proc: asyncio.subprocess.Process, sig: int = signal.SIGTERM) -> None: try: - os.killpg(proc.pid, sig) - except (ProcessLookupError, PermissionError, OSError): + if hasattr(os, "killpg") and proc.pid is not None: + os.killpg(proc.pid, sig) + elif proc.returncode is None: + proc.kill() + except (ProcessLookupError, PermissionError, OSError, AttributeError): try: proc.kill() except Exception: # noqa: BLE001 diff --git a/src/switchbay/kernel/hire.py b/src/switchbay/kernel/hire.py index 5322349..e5faca9 100644 --- a/src/switchbay/kernel/hire.py +++ b/src/switchbay/kernel/hire.py @@ -222,9 +222,13 @@ def pair(r: dict[str, Any]) -> tuple[str, str | None]: pool = others or allowed if s <= 0.25: # Cheapest (local / flash / mini). Strength 0 is local. + # Prefer independence from the chief's provider on ties so + # Economy workers burn gemini/mlx flash-class rather than + # anthropic haiku when both score the same. chosen = min(pool, key=lambda r: ( 0 if r.get("local") else 1, float(r.get("strength") or 0), + 1 if str(r.get("provider")) == chief_pid else 0, )) elif s >= 0.8: # Strong but leave the very top for the kernel when possible. diff --git a/switchbay.code-workspace b/switchbay.code-workspace deleted file mode 100644 index 4e619b1..0000000 --- a/switchbay.code-workspace +++ /dev/null @@ -1,43 +0,0 @@ -{ - "folders": [ - { - "name": "switchbay", - "path": "." - } - ], - "settings": { - "debug.javascript.autoAttachFilter": "disabled" - }, - "launch": { - "version": "0.2.0", - "configurations": [ - { - "name": "Switch Bay VS", - "type": "extensionHost", - "request": "launch", - "args": [ - "--extensionDevelopmentPath=${workspaceFolder}/extensions/switchbay-vs" - ], - "outFiles": [ - "${workspaceFolder}/extensions/switchbay-vs/out/**/*.js" - ], - "preLaunchTask": "switchbay-compile" - } - ] - }, - "tasks": { - "version": "2.0.0", - "tasks": [ - { - "label": "switchbay-compile", - "type": "shell", - "command": "bash", - "args": [ - "-lc", - "cd \"${workspaceFolder}/extensions/switchbay-vs\" && if [ ! -x node_modules/.bin/tsc ]; then pnpm install; fi && ./node_modules/.bin/tsc -p ." - ], - "problemMatcher": "$tsc" - } - ] - } -} diff --git a/tests/unit/test_kernel_harness.py b/tests/unit/test_kernel_harness.py index a68eb4f..b245530 100644 --- a/tests/unit/test_kernel_harness.py +++ b/tests/unit/test_kernel_harness.py @@ -2,6 +2,7 @@ from __future__ import annotations +import os from pathlib import Path import pytest @@ -52,6 +53,7 @@ async def test_grok_harness_runs_curator_package(tmp_path: Path, monkeypatch): @pytest.mark.asyncio async def test_pi_rpc_keeps_stdin_open_until_settled(tmp_path: Path, monkeypatch): """EOF on Pi stdin aborts the model turn; the harness must not close early.""" + import os import sys script = tmp_path / "fake_pi.py" script.write_text( @@ -157,8 +159,9 @@ def test_spawn_env_pythonpath_is_absolute(tmp_path: Path): provider_id="xai", model="grok-4.5", workspace=tmp_path, ) env = spawn_env(req) - assert env["PYTHONPATH"].endswith("/src") - assert env["PYTHONPATH"].startswith("/") + src_path = Path(env["PYTHONPATH"]) + assert src_path.name == "src" + assert src_path.is_absolute() assert env["SWITCHBAY_SRC"] == env["PYTHONPATH"] assert "ce_wave_prime" in env["SWITCHBAY_PACKAGE_TOOLS"].split(",") assert env["SWITCHBAY_PACKAGE_ID"] == CURATOR_ID @@ -287,10 +290,10 @@ def test_spawn_env_path_includes_homebrew(tmp_path: Path, monkeypatch): from switchbay.kernel.harness_pi import enrich_path, shebang_wants_node brew = tmp_path / "opt" / "homebrew" / "bin" brew.mkdir(parents=True) - env = {"PATH": "/usr/bin:/bin"} + env = {"PATH": os.pathsep.join(["/usr/bin", "/bin"])} enrich_path(env, extra_dirs=(str(brew),)) - assert str(brew) in env["PATH"].split(":") - assert env["PATH"].startswith(str(brew)) + assert str(brew) in env["PATH"].split(os.pathsep) + assert env["PATH"].split(os.pathsep)[0] == str(brew) script = tmp_path / "pi" script.write_text("#!/usr/bin/env node\nconsole.log(1)\n", encoding="utf-8") assert shebang_wants_node(str(script)) is True From 3457e60c466da5ea1a56105252f898d57aac799c Mon Sep 17 00:00:00 2001 From: benjsmith Date: Sat, 12 Sep 2026 23:58:40 +0000 Subject: [PATCH 2/2] fix: make Windows CI green for toolscope, version_sync, pi rpc Compare toolscope path assertions with Path.as_posix() (production rules already use forward slashes) and emit skill-mirrors Read rules the same way. Inherit PATH in version_sync subprocess env so Windows winsock/_overlapped keep working. Skip the Pi stdin probe on win32 where select.select is sockets-only. Signed-off-by: benjsmith --- src/switchbay/ce_toolscope.py | 2 +- tests/unit/test_ce_toolscope.py | 8 ++++---- tests/unit/test_kernel_harness.py | 5 +++++ tests/unit/test_version_sync.py | 3 ++- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/switchbay/ce_toolscope.py b/src/switchbay/ce_toolscope.py index 57062c4..3089a40 100644 --- a/src/switchbay/ce_toolscope.py +++ b/src/switchbay/ce_toolscope.py @@ -223,7 +223,7 @@ def fs_rules(workspace: Path) -> list[str]: log.exception("skill read-scope failed") try: mirrors = Path(workspace).resolve() / ".workbench" / "skill-mirrors" - rules.append(f"Read({mirrors}/**)") + rules.append(f"Read({mirrors.as_posix()}/**)") except OSError: pass return _dedup(rules) diff --git a/tests/unit/test_ce_toolscope.py b/tests/unit/test_ce_toolscope.py index a01eb9b..fdc5726 100644 --- a/tests/unit/test_ce_toolscope.py +++ b/tests/unit/test_ce_toolscope.py @@ -82,9 +82,9 @@ def test_fs_rules_allow_reading_any_discovered_skill(tmp_path, monkeypatch): monkeypatch.setattr( "switchbay.skillkit.cebridge.ce_root", lambda: tmp_path / "no-ce") rules = ce_toolscope.fs_rules(ws) - assert any(str(user) in r and r.startswith("Read(") for r in rules) + assert any(user.as_posix() in r and r.startswith("Read(") for r in rules) mirrors = ws.resolve() / ".workbench" / "skill-mirrors" - assert any(str(mirrors) in r for r in rules) + assert any(mirrors.as_posix() in r for r in rules) def test_write_scope_is_curation_dirs_only(fake_ce): @@ -121,5 +121,5 @@ def test_rules_render_both_symlink_forms(tmp_path, monkeypatch): monkeypatch.setattr( ce_toolscope, "skill_roots", lambda _ws: [logical, physical]) prefixes = ce_toolscope.command_prefixes(ws) - assert any(str(logical) in p for p in prefixes) - assert any(str(physical) in p for p in prefixes) + assert any(logical.as_posix() in p for p in prefixes) + assert any(physical.as_posix() in p for p in prefixes) diff --git a/tests/unit/test_kernel_harness.py b/tests/unit/test_kernel_harness.py index b245530..b0d69eb 100644 --- a/tests/unit/test_kernel_harness.py +++ b/tests/unit/test_kernel_harness.py @@ -3,6 +3,7 @@ from __future__ import annotations import os +import sys from pathlib import Path import pytest @@ -51,6 +52,10 @@ async def test_grok_harness_runs_curator_package(tmp_path: Path, monkeypatch): @pytest.mark.asyncio +@pytest.mark.skipif( + sys.platform == "win32", + reason="fake Pi probe uses select.select on stdin; Windows select is sockets-only", +) async def test_pi_rpc_keeps_stdin_open_until_settled(tmp_path: Path, monkeypatch): """EOF on Pi stdin aborts the model turn; the harness must not close early.""" import os diff --git a/tests/unit/test_version_sync.py b/tests/unit/test_version_sync.py index 0e13272..213f023 100644 --- a/tests/unit/test_version_sync.py +++ b/tests/unit/test_version_sync.py @@ -10,6 +10,7 @@ from __future__ import annotations import json +import os import re import subprocess import sys @@ -54,6 +55,6 @@ def test_reported_version_is_the_running_one(): [sys.executable, "-c", "from switchbay import updater; print(updater.local_switchbay_version())"], capture_output=True, text=True, check=True, cwd=REPO, - env={"PYTHONPATH": str(REPO / "src"), "PATH": "/usr/bin:/bin"}, + env={**os.environ, "PYTHONPATH": str(REPO / "src")}, ) assert out.stdout.strip() == _pyproject_version()