fix(plugin-health): make the command able to read what the scanner found

Dogfooding `/config-audit plugin-health` against a fasit registered before the
run: 11 of 12 predictions confirmed, 1 refuted with evidence, 0 deviations.
The command's default path could not produce the report it documents.

M-BUG-21 (third arm): the argument loop ended in
`else if (!args[i].startsWith('-')) targetPath = args[i]` with no unknown-flag
branch, so `--output-file /tmp/x.json` was dropped and its value became the scan
target. Worse than in drift-cli: a non-existent path discovers no plugins, so the
scanner answered "No plugins found" (info) with exit 0 — a reassuring answer, not
an error. Unknown options and a value-less `--output-file` now exit 3.

M-BUG-33: the scanner had no `--output-file` and its default-mode report goes to
stderr, which `commands/plugin-health.md` discards with `2>/dev/null` before
telling the agent to read stdout. Zero bytes captured.

M-BUG-34: per-plugin rows and the grade formula never left `scan()` — the only
grade code, `formatPluginHealthReport`, had no caller — and cross-plugin findings
were flattened behind a `category` they share with per-plugin findings. The
mandated table and Cross-Plugin section were unbuildable, so the command had to
fabricate them. `scanDetailed()` now returns them; `scan()`'s frozen v5.0.0
envelope is unchanged by construction.

M-BUG-35: `.claude-plugin/marketplace.json` was flagged as an unknown file. It is
the documented catalog location, and `"source": "./"` makes the repo root its own
plugin, so one `.claude-plugin/` legitimately holds both.

Also: `commands/posture.md` ran both optional scanners in default mode under
`2>/dev/null` and read stdout — the same class as feature-gap.md:133 in the fix
chunk. A CLI-side flag fix does not close its callers.

Tests 1420 -> 1432, red first. Frozen v5.0.0 snapshots untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XhhZ8zg1amR7YCAPqiZWdt
This commit is contained in:
Kjell Tore Guttormsen 2026-07-31 21:08:32 +02:00
commit 001090261e
8 changed files with 398 additions and 33 deletions

View file

@ -8,6 +8,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Fixed
- **`M-BUG-21` (third arm) — `plugin-health-scanner.mjs` swallowed unknown flags, and the wrong
target looked *green*.** The same `else if (!args[i].startsWith('-')) targetPath = args[i]` loop:
`--output-file /tmp/x.json` was dropped and `/tmp/x.json` became the scan target. Where `drift`
produced phantom drift, this produced a **reassuring** answer — a non-existent path discovers no
plugins, so the scanner reported `No plugins found` (info) and exit `0`. Unknown options and a
value-less `--output-file` now exit `3`.
- **`M-BUG-33``/config-audit plugin-health` read zero bytes.** The scanner had no `--output-file`
(ux-rules rule 2) and its default-mode report goes to **stderr**, which `commands/plugin-health.md`
discards with `2>/dev/null` before telling the agent to "read stdout output (JSON)". The command's
default path could not produce the report it documents. `--output-file` now writes a humanized
payload; `--raw`/`--json` stdout is unchanged and byte-stable.
- **`M-BUG-34` — the report's per-plugin table and Cross-Plugin section were unbuildable.** Per-plugin
data (`commandCount`, `agentCount`) and the grade formula never left `scan()` — the only grade code,
`formatPluginHealthReport`, had no caller — and cross-plugin findings were flattened into `findings`
behind a `category: 'plugin-hygiene'` they share with per-plugin findings. The command mandated both,
so it had to fabricate them. The payload now carries `plugins[]` (name, declaredName, counts, score,
grade via the shared `pluginGrade`) and `cross_plugin_findings[]` (also marked `crossPlugin: true`),
via a new `scanDetailed()`; `scan()`'s frozen v5.0.0 envelope is untouched.
- **`M-BUG-35``.claude-plugin/marketplace.json` was reported as an unknown file.** It is the
documented, required location for a marketplace catalog, and a marketplace entry with
`"source": "./"` makes the repo root its own plugin — such a repo legitimately carries both files.
Genuinely unexpected files in `.claude-plugin/` are still flagged.
- **`commands/posture.md` discarded both optional scanners' output.** Its `--drift` and
`--plugin-health` sections ran `drift-cli.mjs` / `plugin-health-scanner.mjs` in default mode under
`2>/dev/null` and read stdout, which is empty in that mode. Both calls now use `--output-file`.
- **`M-BUG-21``drift-cli.mjs` had no `--output-file`, and its argument loop turned the missing
flag into a wrong scan target.** The loop ended in `else if (!arg.startsWith('-')) targetPath = arg`
with no unknown-flag branch, so an unrecognised flag was dropped silently and its *value* fell