Closes #181, closes #182, closes #183, closes #184, closes #185, and the residual half of #173. #185 — every per-surface check read ONE root, so a monorepo with `skills/` plus `plugins/*/skills/` scored only the first: 4 skills over budget, 2 reported, exit 0. The default now NAMES the bundles it did not score; `--bundles=all` scores them in one pass with one exit code. Root-only stays the default so a vendored third-party corpus is not scored as if it were yours. #181 — `orphan-docs` had a documented severity nothing read, and the duplicate-rules check had no id at all, so neither could be tiered. Both are real rules now and the exit code respects their severity. #183 — the log and `--json` disagreed (21 lines against 88 findings) because some checks print per finding and others per group. One computed total, printed and carried in `--json`. #182 — `--json-out=<file>` writes the JSON while stdout stays human-readable, so a CI job stops scanning twice. #184 — `vigiles test` resolves a harness's bare `vigiles` import from the CLI's own install, so `npx vigiles@<version> test` needs nothing in the project. NODE_PATH was measured, not assumed: Node ignores it for ESM. #173 residual — `lint` verified the integrity HASH, which says nothing about whether the things a file NAMES still exist, so an artifact committed while its refs were live stayed green after the target was deleted. The new `spec-refs` rule re-derives them. It immediately found four dead references in this repo's own `examples/CLAUDE.md.spec.ts`, left behind by the hexagonal refactor. BREAKING CHANGE: `orphan-docs` and `duplicate-rules` now default to "warn" — a repo relying on either to fail CI must set "error" explicitly. Registering them surfaced that both are heuristic proxies, and this repo's calibration rule is that a heuristic never defaults to error. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2.1 KiB
duplicate-rules
Near-duplicate rules within one spec — two rules that say the same thing in different words. Detected by NCD (gzip-based) similarity, so it is deterministic and needs no model.
{ "rules": { "duplicate-rules": "warn" } }
| Default | warn |
| Surface | instruction-file specs (*.spec.ts) |
| Bucket | heuristic-behavioral |
| Detector | findDuplicateRules |
What it checks
Every pair of rules inside a spec is compared by normalized compression distance. A pair below the similarity threshold is reported as spec bloat: duplicated guidance is guidance the reader has to reconcile, and the model has to weigh twice.
Severity
"warn"(default) — reported, exit code untouched."error"— a duplicate pair fails the build.false/"off"— the check does not run.
Why it defaults to warn
It is a proxy, not a fact: NCD says two strings compress well together, which is evidence that they overlap and not proof that they are redundant. Two rules can legitimately share vocabulary and mean different things.
This repo's calibration rule is that a heuristic never defaults to error,
because a rule that fails a correct build gets switched off rather than fixed.
History
Until 2026-09 this check had no rule id at all — the finding fed the exit
code directly, so vigiles lint could exit 1 over it with no name a config could
even mention, and no way to tier it (#181). Registering it surfaced a second
problem: it was behaving as error while being a heuristic, which is exactly the
combination the calibration rule exists to prevent. Set it back to "error"
explicitly if the old blocking behaviour is what you want.
See also
orphan-docs— the other check that used to be untierable for the same reason.- The validation-rule matrix in verifying instruction files.