Commit Graph
7 Commits
Author SHA1 Message Date
mroberts 58851c8a8c Resolve python tools off PATH, not just as importable modules
1.1.0 reported an installed ruff as missing and skipped the lint. Its
resolution order ended at `python -m <tool>`, guarded by an `import <tool>`
probe, but ruff ships as a standalone Rust binary and is never importable.
The probe failed, the check was recorded as skipped, and the gate reported a
coverage gap that did not exist.

This also regressed 1.0.2, which called a bare `ruff` and let PATH resolve it.
Restore that path, ordered after the project venv and uv so a project-local
tool still wins, and before `python -m` so a binary is found even when a
same-named module is not importable.

Verified by injecting an unused import into audit-terraform and confirming the
guard blocks with `ruff check .`. The new regression test fails against the
1.1.0 resolution order and passes against this one.

Tests: 10 passing (bash-guard), 197 (audit-code), 106 (audit-terraform),
ruff clean across both skills.
2026-07-21 12:30:36 -05:00
mroberts fc4a939482 Gate nested projects in bash-guard instead of passing them ungated
The build-and-test gate searched for marker files only two levels below the
repo root. This repo keeps its python projects at
plugins/<name>/skills/<skill>/, four levels down, so every push reported "no
recognized project layout" and pushed without running a single test. A gate
that announces it did nothing is worse than no gate, because the announcement
scrolls past and the push still succeeds.

Walk to depth 4, and resolve python tooling properly. A bare `ruff` or
`pytest` is usually absent from PATH, and run() treats ENOENT as a pass, so an
unresolved tool would have skipped the check just as silently. Resolution now
tries the project's own .venv, then uv (which needs a [project] table that
audit-terraform does not have), then the interpreter's -m form.

A tool that is genuinely not installed is reported as an advisory note rather
than blocking. Its absence is a gap in coverage, not a defect in the change
being pushed. Silence is the one outcome that is never acceptable.

Verified by injecting a failing test into each skill and confirming the guard
blocks: audit-code resolves through its .venv, audit-terraform through
python -m pytest. The new regression tests fail against the old depth-2 walk
and pass against this one.

Tests: 8 passing (bash-guard), 197 (audit-code), 106 (audit-terraform).
2026-07-21 11:49:48 -05:00
mroberts 600c1fef86 Updated marketplace with the reviews plugin 2026-07-21 10:35:34 -05:00
mroberts 129354cda8 Read hook payload from fd 0, not /dev/stdin
Claude Code delivers the hook payload on a socket. Opening it by path
('/dev/stdin' -> /proc/self/fd/0) fails with ENXIO, so bash-guard.mjs threw on
every invocation and its bare catch exited 0 silently. The guard looked like it
was never dispatched; it was dying on line 29 each time.

readFileSync(0) is read() on the descriptor with no open(), which works on a
socket. The catch now logs instead of swallowing, so this failure mode can never
again masquerade as non-dispatch.

Adds test-bash-guard.py, which drives the guard over a socketpair. A pipe would
not reproduce the bug, so the socket is load-bearing. Verified failing against
the pre-fix guard (silent, no output) and passing after.

Removes the five diagnostic probes and probe.mjs; they served their purpose.
Bumps to 1.0.2 because the plugin cache is keyed by version and an unchanged
version silently skips reinstall.
2026-07-21 10:24:06 -05:00
mroberts a651c2cb8c Bump guards to 1.0.1 so the probe change actually installs
The plugin cache is keyed by version (cache/mroberts/guards/<version>), so
'claude plugin update' reports 'already at the latest version' and skips the
reinstall whenever content changes without a version bump. The marketplace
clone had fetched the new commit; the cache never saw it.
2026-07-20 15:23:06 -05:00
mroberts 0e9ccaad9f Add hook dispatch probes to isolate why bash-guard never fires
Registers five inert probe entries covering the 2x2 of timeout and
statusMessage on PreToolUse:Bash, plus a PostToolUse statusMessage cell.
Each probe is in its own matcher block so a dropped entry cannot take
the others with it, and logs its identity before reading stdin so a
failed dispatch stays distinguishable from a failed payload read.

The real bash-guard entry is unchanged and serves as the control.
2026-07-20 15:15:49 -05:00
mroberts 391b2a3bc6 Add mroberts plugin marketplace with guards plugin 2026-07-20 14:25:19 -05:00