Files
zernie__vigiles/src/cli-help.test.ts
zernie 4f0a01b2f9 CLI clarity, an execution-tier finding, and the Action's boundary documented (#163)
Five changes from a measured review of the CLI surface and of a consumer's CI.

1. BEHAVIOUR — a positional target that matches nothing now FAILS. Bare discovery
   finding nothing still exits 0: an empty repo is a legitimate place to stand, but a
   path or glob you NAMED that matched nothing is stale, wrong, or never reached.
   Collapsing the two cost a real repo three days of green CI verifying zero files.
   Jest and Vitest fail on no-tests-found and make you opt in with --passWithNoTests;
   pytest exits 5. --min=0 is the opt-out here, so no flag is added.

2. HELP — the four checkers (audit/lint/test/eval) all read as "check my stuff". They
   are now grouped by READ vs RUN, and each line states its own consequence positively.
   The success criterion was checkable and is met: "NOT a CI step — use vigiles lint in
   CI" is deleted, because nothing needs defending. A help line that says what a command
   ISN'T is a description that failed. Per-verb flag detail moves into the per-verb
   help; audit's four wrapped lines naming nine flags were most of the felt crowding.
   Verb count is unchanged at 8 — the smallest of every tool measured (vitest 8 verbs
   and 164 flags, cargo 48, docker 57, git 166), and none of them thinned their surface
   by removing verbs.

3. DOCBLOCK — src/cli.ts listed four of the eight commands, having quietly stopped
   growing. Replaced by a pointer to the single source; a new test asserts every verb
   appears in the RENDERED help exactly once, because a constant is what drifted.

4. AUDIT — new Tested finding: "N surfaces counted as covered by PLACEMENT only — no
   run on record ever exercised one". Colocated evidence is a claim about the
   filesystem and the provenance line already admits it; nobody reads a provenance
   line. Measured in a consumer: 26 colocated and 0 executed, while its CI skipped
   every one of those harnesses and reported success. The corpus-wide threshold is a
   stated limit in the code, not a silence.

5. DOCS + SKILL — the Action reference now explains why there is no test input (the
   Action never runs npm ci; harnesses import the library), and the test-harness skill
   gained a six-line CI pointer: an Action exists, init wires it, inputs are documented
   there. It REFERENCES rather than explains, because copied explanations rot — three
   measured proofs, one of them inside this product.

Gates, in order: vitest, lint, prettier, api:check, build — via the check script,
18 of 18.

Co-authored-by: Claude <noreply@anthropic.com>
2026-08-19 14:02:16 +05:00

76 lines
2.4 KiB
TypeScript

/**
* The top-level help, asserted as a CONTRACT rather than eyeballed.
*
* Two of these assertions exist because the old help failed them. It described `audit`
* with "NOT a CI step — use `vigiles lint` in CI" — a sentence that says what a command
* ISN'T, which is a description admitting it failed; deleting that sentence was the
* checkable success criterion of the 2026-08-18 rewrite, and this test is what keeps it
* deleted. And the module docblock listed four of the eight verbs, having quietly stopped
* growing — so the coverage assertion below reads the RENDERED output, not a constant,
* because a constant is the thing that drifted.
*/
import { test } from "vitest";
import assert from "node:assert/strict";
import { execFileSync } from "node:child_process";
const help = execFileSync("node", ["dist/cli.js", "--help"], {
encoding: "utf-8",
});
test("every user-facing verb appears in the grouped help exactly once", () => {
for (const verb of [
"init",
"compile",
"eject",
"audit",
"lint",
"test",
"eval",
"generate",
]) {
const hits = help
.split("\n")
.filter((l) => l.trimStart().startsWith(`vigiles ${verb} `));
assert.equal(
hits.length,
1,
`expected exactly one help line for \`${verb}\`, got ${hits.length}`,
);
}
});
test("the four checkers are grouped by whether they READ or RUN", () => {
const read = help.indexOf("Check your harness");
const run = help.indexOf("Run your harness");
assert.ok(
read > 0 && run > read,
"both group headings must be present, read before run",
);
const between = (a: number, b: number, verb: string) => {
const at = help.indexOf(`vigiles ${verb} `, a);
assert.ok(at > a && at < b, `\`${verb}\` must sit inside its own group`);
};
between(read, run, "audit");
between(read, run, "lint");
between(run, help.length, "test");
between(run, help.length, "eval");
});
test("no command is described by what it is NOT", () => {
assert.doesNotMatch(
help,
/NOT a CI step/,
"a help line that must disclaim what a command isn't is a description that failed — " +
"state the consequence positively instead (audit: 'fails nothing')",
);
});
test("per-verb flag detail stays OUT of the top-level list", () => {
assert.doesNotMatch(
help,
/--no-html/,
"flag detail belongs in `vigiles <verb> --help`",
);
assert.match(help, /Flags live in `vigiles <command> --help`/);
});