fix: address Copilot review round 6

- `reachableScripts` now follows every npm spelling of `run` — the
  canonical `run-script` and its `run`/`rum`/`urn` aliases — with a unit
  case, so `npm run-script validate:web` appended to `local:gate` fails
  the whole-gate check instead of slipping past it.
- docs/quality-gate.md `verify:skills` row, AGENTS.md skills rule 1 and the
  #2085 narration now describe the actual chain (`validate:guards`, reached
  by both `validate` and `local:validate`) and the qualified promise.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PsmUAZ6aLUAFqenjJKSSRp
Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
cliffhall 2026-09-12 20:40:44 -04:00
commit 61fcada059
2 changed files with 28 additions and 6 deletions

View file

@ -25,14 +25,17 @@ export function tokenize(command) {
* <name>` references within a single manifest's `scripts`, plus npm's implicit
* `pre<name>`/`post<name>` lifecycle hooks (npm runs those around `<name>`
* without an explicit `npm run`, so a gate moved into e.g. `prevalidate` is
* still reached). A gate harvested from a script that nothing reachable from
* still reached). Every spelling npm accepts for `run` is followed —
* `run-script` is the canonical command and `run`, `rum` and `urn` are its
* aliases — so a chain written as `npm run-script validate:web` reaches the
* same scripts as `npm run validate:web` (Copilot, #2341). A gate harvested from a script that nothing reachable from
* `entry` invokes gates nothing, so callers restrict to this set to assert "CI
* actually runs this", not merely "the script exists".
*/
export function reachableScripts(scripts, entry = "validate") {
const reached = new Set();
const queue = [entry];
const runRef = /npm run ([\w:-]+)/g;
const runRef = /npm (?:run-script|run|rum|urn) ([\w:-]+)/g;
while (queue.length > 0) {
const name = queue.shift();
if (reached.has(name)) continue;

View file

@ -48,6 +48,25 @@ test("reachableScripts: follows `npm run` refs and pre/post hooks (r10)", () =>
assert.ok(!reached.has("unrelated"));
});
test("reachableScripts: follows every npm spelling of `run` (#2341)", () => {
// `run-script` is the canonical command; `run`, `rum` and `urn` are its
// aliases. A chain written in any of them reaches the same scripts, so a
// guard that only followed `npm run` would let `npm run-script validate:web`
// reintroduce what it exists to forbid (Copilot).
const scripts = {
gate: "npm run-script a && npm rum b && npm urn c && npm run d",
a: "noop",
b: "noop",
c: "noop",
d: "noop",
e: "noop",
};
const reached = reachableScripts(scripts, "gate");
for (const name of ["a", "b", "c", "d"])
assert.ok(reached.has(name), `npm spelling reaching ${name}`);
assert.ok(!reached.has("e"));
});
test("reachableScripts: a `prevalidate`-hosted typecheck is reachable (r10)", () => {
const scripts = {
validate: "npm run build && npm run test",