Files
zernie__vigiles/src/stability-verbs.test.ts
T
zernie 94af23a5b7 feat(hook,cli)!: close five adopter-reported issues — halted-turn guards, dead-ref artifacts, centralized tests (#186)
Closes #173, #174, #175, #176 and #170.

#174 — decideHook read exit 2, `decision` and `permissionDecision` but not the
harness's halt-the-turn field, so `assertBlocksDisasters` reported a guard that
stopped every disaster as blocking none. The field is read from the PORT, not
hard-coded: `continue` is documented for Claude Code and unverified for Codex.

The issue's adjacent suggestion was deliberately NOT implemented — adding the
halt to `hook-block-ineffective` as a block-attempt signal would fire on hooks
that WORK, since a halt is not event-scoped. It reads as a suppressor instead,
fixing a real false alarm. A mutation implementing the original suggestion
literally is pinned by a test that fails on it.

#173 — compile printed its errors, exited 1, and wrote a hash-valid CLAUDE.md
anyway, so `lint` went green over refs it had just called dead. Nothing is
written on a failed compile now.

#176 — eight small defects, including `audit --out` outside the repo appending
unreachable `../../..` entries to .gitignore (a documented read-only report
editing a tracked file), an unknown command exiting 1 against a documented 2,
and STABILITY.md promising the removed `scan` verb.

#175 + #176.2 — three missing capabilities, each opt-in and additive:
`{surface}` in testGlobs, `--single`, and `rulesDir` so `.claude/rules/**`
stops being invisible.

#170 — one enumeration of export forms written against the grammar, both naming
checks driven off it, and `check-internal-tag.mjs` asking the TypeScript
compiler instead of a regex. It immediately found the issue's open case, 13
tagged types the regex had never seen, and a live gap in the ESLint rule.

BREAKING CHANGE: `HookRunResult` gains a required `haltsTurn`, so code that
CONSTRUCTS one (a test fake) must set it. Reading a result is unaffected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 18:02:28 +05:00

58 lines
2.5 KiB
TypeScript

/**
* STABILITY.md's verb list must be the REAL verb list.
*
* Reported by an adopter (#176.4): the page promising a stable CLI contract
* listed `scan`, a verb removed when it became `audit` — so the strongest
* promise in the repo named a command that answers `Unknown command`. The
* heaviest kind of doc drift, and the kind that survives longest, because the
* page is read by people deciding whether to depend on the tool at all.
*
* `self-command-refs` could not catch it and still cannot: it is deliberately
* high-precision and only inspects a `vigiles <cmd>` reference in a COMMAND
* context, while STABILITY.md names its verbs as a prose list of backticked
* words. That measured limit is documented; this test closes the one page where
* the cost of the gap is highest, without widening the detector into the
* false-positive territory that got `doc-refs` disabled.
*/
import { test } from "vitest";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import { VERBS } from "./cli-commands.js";
const STABILITY = resolve(__dirname, "..", "STABILITY.md");
/** The backticked words in the "the verbs (…)" clause. */
function listedVerbs(md: string): string[] {
const m = /the verbs \(([^)]*)\)/s.exec(md);
assert.ok(m, "STABILITY.md must carry a `the verbs (…)` clause");
return [...m[1].matchAll(/`([a-z-]+)`/g)].map((x) => x[1] as string);
}
test("every verb STABILITY.md promises actually exists", () => {
const listed = listedVerbs(readFileSync(STABILITY, "utf-8"));
const real = new Set<string>(VERBS);
const ghosts = listed.filter((v) => !real.has(v));
assert.deepEqual(
ghosts,
[],
`STABILITY.md promises verb(s) the CLI does not have: ${ghosts.join(", ")}`,
);
});
test("every human-facing verb is promised by STABILITY.md", () => {
// The other direction. A shipped verb missing from the stability page is the
// quieter half of the same drift — the contract silently under-promises, and
// nobody notices because nothing is wrong on screen.
const listed = new Set(listedVerbs(readFileSync(STABILITY, "utf-8")));
// `hook-runtime` is the hidden runtime umbrella — emitted into hook configs,
// never typed by a human — so it is correctly absent from a human contract.
const missing = VERBS.filter((v) => v !== "hook-runtime" && !listed.has(v));
assert.deepEqual(
missing,
[],
`these verbs ship but STABILITY.md does not mention them: ${missing.join(", ")}`,
);
});