Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ breaking changes may land in a minor release.

### Fixed

- **Windows installs now pull `psutil` automatically** — moved from the opt-in `non-linux` extra to a
platform-scoped core dependency (`sys_platform == 'win32'`), so the TUI liveness column no longer
shows every run as `?` on a stock install. macOS keeps the `non-linux` extra; Linux stays dep-free.
(#72, closes #71)
- **`bmad-loop-setup` no longer deletes live core BMAD config or the installer manifest.** In a
multi-module BMAD v6 project the setup scripts hardcoded `core` (and `--also-remove _config`) into
their delete lists, destroying `_bmad/core/config.yaml`, per-module config, and the whole
Expand Down
5 changes: 3 additions & 2 deletions docs/game-engine-plugin-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,8 +242,9 @@ guards, by script:

- **`unity_teardown.py` — process discovery.** Linux uses a zero-dependency
`/proc` scan to find the worktree-bound Editor/MCP-server; non-Linux falls back
to the same scan over **`psutil`**, imported lazily from the optional `non-linux`
extra (`pip install 'bmad-loop[non-linux]'`) with a clear error if missing. The
to the same scan over **`psutil`**, imported lazily — a core dependency on
Windows, the optional `non-linux` extra on macOS (`pip install
'bmad-loop[non-linux]'`) — with a clear error if missing. The
hard-kill uses `signal.SIGKILL` where present, degrading to `SIGTERM`/`taskkill`
on Windows. Liveness uses `os.kill(pid, 0)` on POSIX but `psutil.pid_exists` on
Windows (where `os.kill(pid, 0)` would _terminate_ the process).
Expand Down
5 changes: 3 additions & 2 deletions docs/plugin-authoring-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -634,8 +634,9 @@ seams instead of re-implementing OS primitives. Follow this discipline:
script runs anywhere the core does. If you genuinely need a third-party package on
one platform, make it an **optional extra** in `pyproject.toml` and **import it
lazily** with a clear error if missing — never at module top level. The bundled
Unity plugin does exactly this for `psutil`: a `non-linux` extra it imports only on
non-Linux process _discovery_, so the dep-free Linux/WSL path never pulls it in.
Unity plugin does exactly this for `psutil`: imported only on non-Linux process
_discovery_ (a core dependency on Windows, the `non-linux` extra on macOS), so the
dep-free Linux/WSL path never pulls it in.

- **Guard the primitives that have no seam.** Anything absent or differently-shaped
on Windows that isn't behind a seam — `cp`/`--reflink`, symlinks, `/proc` scanning,
Expand Down
13 changes: 8 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,20 @@ license = "MIT"
license-files = ["LICENSE"]
authors = [{ name = "BMad Code, LLC" }]
requires-python = ">=3.11"
dependencies = ["pyyaml>=6.0"]
dependencies = [
"pyyaml>=6.0",
# win32 liveness needs psutil (no /proc; os.kill(pid,0) is destructive there);
# without it the TUI shows every run as `?`. Hard dep so no install path misses it.
"psutil>=7.2.2; sys_platform == 'win32'",
Comment thread
dracic marked this conversation as resolved.
]

[project.scripts]
bmad-loop = "bmad_loop.cli:main"

[project.optional-dependencies]
tui = ["textual>=8.0,<9", "tomlkit>=0.13", "pyte>=0.8.2"]
# Process discovery on non-Linux (no /proc): the Unity plugin's teardown lazily
# imports psutil whenever it runs off Linux — macOS as well as a native-Windows
# backend. The core stays dep-free; install with `pip install bmad-loop[non-linux]`
# on those platforms.
# macOS opt-in for psutil (identity/force-kill niceties); win32 gets it as a core
# dep above, Linux stays dep-free. `pip install bmad-loop[non-linux]` on macOS.
non-linux = ["psutil>=7.2.2"]

[dependency-groups]
Expand Down
11 changes: 6 additions & 5 deletions src/bmad_loop/data/plugins/unity/unity_teardown.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,17 @@


def _psutil():
"""Lazily import psutil (the optional ``non-linux`` extra), used only for
non-Linux process discovery (macOS and Windows). The dep-free core never imports
it; raise a clear, actionable error if it's missing on a platform that needs it."""
"""Lazily import psutil — a core dep on Windows, the ``non-linux`` extra on
macOS — used only for non-Linux process discovery (macOS and Windows). The
dep-free core never imports it; raise a clear, actionable error if it's missing
on a platform that needs it."""
try:
import psutil # noqa: PLC0415 (intentional lazy import — keeps the core dep-free)
except ImportError as exc: # pragma: no cover - exercised only off Linux
raise RuntimeError(
f"unity_teardown: process discovery on {sys.platform!r} needs psutil; "
"install the optional extra (pip install 'bmad-loop[non-linux]') or run "
"under Linux/WSL"
"on Windows reinstall bmad-loop (psutil is a required dependency there), "
"on macOS run `pip install 'bmad-loop[non-linux]'`"
) from exc
return psutil

Expand Down
12 changes: 6 additions & 6 deletions src/bmad_loop/process_host.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,17 +219,17 @@ def _proc_starttime(pid: int) -> float | None:


def _psutil():
"""Lazily import psutil (the optional ``non-linux`` extra), used only for the
non-destructive Windows/macOS liveness + identity probes. The dep-free core
never imports it on Linux; raise a clear, actionable error if it's missing where
it's needed."""
"""Lazily import psutil — a core dep on Windows, the ``non-linux`` extra on
macOS — used only for the non-destructive Windows/macOS liveness + identity
probes. The dep-free core never imports it on Linux; raise a clear, actionable
error if it's missing where it's needed."""
try:
import psutil # noqa: PLC0415 (intentional lazy import — keeps the core dep-free)
except ImportError as exc: # pragma: no cover - exercised only off Linux
raise ProcessHostError(
f"process_host: pid operations on {sys.platform!r} need psutil; "
"install the optional extra (pip install 'bmad-loop[non-linux]') or run "
"under Linux/WSL"
"on Windows reinstall bmad-loop (psutil is a required dependency there), "
"on macOS run `pip install 'bmad-loop[non-linux]'`"
) from exc
return psutil

Expand Down
2 changes: 2 additions & 0 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading