chore: address Copilot review round 15

- The CI wiring guard matched a substring, and last round's justification for
  that was wrong: it accepts a longer script name
  (`npm run verify:skills:cli:disabled`) and a mere mention (`echo npm run
  verify:skills:cli`), either of which lets the real validator be removed while
  the guard stays green. `runsCommand` now splits the script on the separators
  that begin a new command and requires a segment to equal the invocation
  exactly, with both counterexamples as regression cases.
- `local-dev`'s own description and its AGENTS.md index row both still promised
  "where a new dependency must be declared" — the rules round 13 moved out of it
  entirely. Both now describe what the skill actually carries: the reasoning
  behind those rules. Recorded listing share updated to 1,199/4,000.

Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
cliffhall 2026-08-29 04:24:48 -04:00
commit 3905ceef06
2 changed files with 71 additions and 8 deletions

View file

@ -24,7 +24,11 @@ import { tmpdir } from "node:os";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { checkWiring, ciRunsUnconditionally } from "./verify-skills.mjs";
import {
checkWiring,
ciRunsUnconditionally,
runsCommand,
} from "./verify-skills.mjs";
const SCRIPT = path.join(
path.dirname(fileURLToPath(import.meta.url)),
@ -342,3 +346,41 @@ test("the repository's workflow runs the validator unconditionally", () => {
true,
);
});
test("runsCommand matches an exact invocation, not a substring", () => {
const C = "npm run verify:skills:cli";
for (const script of [
C,
`npm run validate && ${C}`,
`npm run validate\n${C}\n`,
`${C} ; npm run something`,
]) {
assert.equal(runsCommand(script, C), true, script);
}
});
test("runsCommand rejects a longer script name or a mere mention", () => {
// Both would let the real validator be removed while the wiring guard stayed
// green — the counterexamples to the substring test this replaced.
const C = "npm run verify:skills:cli";
for (const script of [
"npm run verify:skills:cli:disabled",
"echo npm run verify:skills:cli",
"npm run verify:skills",
"npm run verify:skills:cli --silent",
"",
]) {
assert.equal(runsCommand(script, C), false, script);
}
});
test("ciRunsUnconditionally inherits the exact-invocation rule", () => {
const C = "npm run verify:skills:cli";
assert.equal(
ciRunsUnconditionally(
"jobs:\n build:\n steps:\n - run: npm run verify:skills:cli:disabled\n",
C,
),
false,
);
});

View file

@ -60,6 +60,30 @@ export function skillDirs(
.sort();
}
/**
* Whether a shell script invokes exactly `command`.
*
* A substring test is not enough, and my own justification for one was wrong:
* it accepts a LONGER script name (`npm run verify:skills:cli:disabled`) and a
* script merely named by another command (`echo npm run verify:skills:cli`),
* either of which would let the real validator be removed while this guard
* stayed green (Copilot).
*
* So the script is split on the separators that start a new command, and a
* segment must equal the invocation exactly. That is precise for the commands
* this guard checks, which take no arguments; a command that did would need
* prefix matching with an argument boundary instead.
*
* @param {string} script A step's `run:` block.
* @param {string} command e.g. `npm run verify:skills:cli`.
*/
export function runsCommand(script, command) {
return script
.split(/[\n;|&]+/)
.map((segment) => segment.trim())
.some((segment) => segment === command);
}
/**
* Whether the workflow has a step that runs `command` on every push.
*
@ -90,10 +114,7 @@ export function ciRunsUnconditionally(workflowText, command) {
for (const step of steps) {
if (step === null || typeof step !== "object") continue;
if ("if" in step) continue;
// A plain substring is enough and cannot over-match: the command is a
// full `npm run <script>` invocation, so `verify:skills` does not match
// a search for `verify:skills:cli`.
if (typeof step.run === "string" && step.run.includes(command))
if (typeof step.run === "string" && runsCommand(step.run, command))
return true;
}
}