fix(plh): downgrade cross-plugin command-name overlap to low ambiguity

Commands are namespaced (/name:command), so a command name shared by two
differently-named plugins keeps both reachable — it is ambiguity, not a hard
conflict. The check now mirrors COL's plugin-vs-plugin skill finding: severity
LOW (was HIGH), category plugin-hygiene, COL-shaped details.namespaces, and a
group-first shape (one finding per command name listing every namespace, not
pairwise). It keys on the declared namespace (was folder basename) and fires
only across 2+ distinct namespaces — when plugins share a declared name, the
namespace-collision finding (medium) is the right signal, so this stays silent.

Removes the inaccurate "only one wins" humanizer entry. Adds fixtures
(duplicate-command-name; a shared command in duplicate-plugin-name's colliding
namespace) and 4 tests. Suite 932->936. self-audit A 97 / A 100, scanners 13.
This commit is contained in:
Kjell Tore Guttormsen 2026-06-19 15:30:35 +02:00
commit 0874188fe4
13 changed files with 160 additions and 27 deletions

View file

@ -109,7 +109,7 @@ Default: auto-detects scope from git context. Override with `/config-audit full|
node --test 'tests/**/*.test.mjs'
```
932 tests across 56 test files (17 lib + 29 scanner + 1 hook + 1 agent + 3 commands + 1 knowledge + 4 top-level). Test fixtures in `tests/fixtures/`. Top-level humanizer tests: `json-backcompat.test.mjs`, `raw-backcompat.test.mjs`, `scenario-read-test.test.mjs`, `snapshot-default-output.test.mjs`.
936 tests across 56 test files (17 lib + 29 scanner + 1 hook + 1 agent + 3 commands + 1 knowledge + 4 top-level). Test fixtures in `tests/fixtures/`. Top-level humanizer tests: `json-backcompat.test.mjs`, `raw-backcompat.test.mjs`, `scenario-read-test.test.mjs`, `snapshot-default-output.test.mjs`.
### CML scanner — context-window-scaled char budget
@ -168,9 +168,18 @@ COL-shaped `details.namespaces` payload (`{ source: 'plugin:<dir>', name, path }
Two design notes: (1) the check keys on the declared `name` field, **not** `basename(dir)`
the folder name is irrelevant to the namespace; `scanSinglePlugin` now returns `declaredName`
for this. (2) Name-less plugins are excluded from the collision map (they are flagged by the
missing-field check and must never group on an `undefined` key). The pre-existing cross-plugin
**command**-name conflict check still uses the folder basename and over-reports given
namespacing — left as a separate backlog candidate, intentionally out of scope here.
missing-field check and must never group on an `undefined` key).
The sibling cross-plugin **command-name** check was corrected to match the same model. Because
commands are namespaced (`/name:command`), a command name shared by two **differently-named**
plugins is ambiguity — not a hard conflict — so it now mirrors COL's plugin-vs-plugin skill
finding: severity **LOW**, `category: 'plugin-hygiene'`, COL-shaped `details.namespaces`, and a
group-first shape (one finding per command name listing every namespace, not pairwise). It keys
on the declared namespace and fires only when a name spans **2+ distinct** namespaces; when two
plugins share the same declared name, the namespace-collision finding above is the right (more
severe) signal, so the command check stays silent there to avoid a redundant `"dup, dup"` report.
The earlier HIGH `Cross-plugin command name conflict` finding (basename-keyed, "only one wins")
is gone, along with its now-inaccurate humanizer entry.
## Gotchas