chore: address Copilot review round 17

- ciRunsUnconditionally never looked at the workflow-level `on:` trigger, so a
  workflow switched to `on: release` still contained the step and reported as
  wired while no PR ever ran the validator. It now requires `push` or
  `pull_request`, handling all three shapes `on:` takes. The positive fixtures
  had no trigger at all, so they could not assert the "on every push" half of
  the contract; they carry one now, alongside a release-only negative. Verified
  by mutation.
- Stale JSDoc from the round-16 version change: `probeClaudeVersion` is now
  generic in its parser's result rather than hard-coding `number[]`,
  `validatorCommand` documents `{ parts, prerelease }`, and `runValidator`
  documents the `platform` option it already accepted. Annotations that lie make
  valid call sites look type-invalid.

Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
cliffhall 2026-08-29 04:54:04 -04:00
commit 1203246537

View file

@ -209,7 +209,7 @@ const WIRED_SCRIPTS = {
"verify:skills:cli": "node scripts/verify-skills-cli.mjs",
};
const WIRED_WORKFLOW =
"jobs:\n build:\n steps:\n - run: npm run verify:skills:cli\n";
"on:\n push:\njobs:\n build:\n steps:\n - run: npm run verify:skills:cli\n";
test("checkWiring is silent when both gates are wired", () => {
assert.deepEqual(checkWiring(WIRED_SCRIPTS, WIRED_WORKFLOW), []);
@ -238,7 +238,7 @@ test("checkWiring catches the authoritative validator dropped from CI", () => {
assert.match(
checkWiring(
WIRED_SCRIPTS,
"jobs:\n build:\n steps:\n - run: npm run validate\n",
"on:\n push:\njobs:\n build:\n steps:\n - run: npm run validate\n",
).join(),
/has no unconditional step that runs/,
);
@ -272,9 +272,9 @@ test("checkWiring is not satisfied by a mention outside an executable step", ()
// including on a step whose NAME describes the check it no longer performs,
// which is exactly how a wiring guard goes quietly vacuous.
const mentions = [
"# npm run verify:skills:cli\njobs:\n build:\n steps:\n - run: npm run validate\n",
"jobs:\n build:\n steps:\n - name: npm run verify:skills:cli\n run: npm run validate\n",
"on:\n workflow_dispatch:\n inputs:\n cmd:\n default: npm run verify:skills:cli\njobs:\n build:\n steps:\n - run: npm run validate\n",
"# npm run verify:skills:cli\non:\n push:\njobs:\n build:\n steps:\n - run: npm run validate\n",
"on:\n push:\njobs:\n build:\n steps:\n - name: npm run verify:skills:cli\n run: npm run validate\n",
"on:\n push:\n workflow_dispatch:\n inputs:\n cmd:\n default: npm run verify:skills:cli\njobs:\n build:\n steps:\n - run: npm run validate\n",
];
for (const workflow of mentions) {
assert.match(
@ -287,20 +287,20 @@ test("checkWiring is not satisfied by a mention outside an executable step", ()
test("checkWiring accepts the command inside a multi-line run step", () => {
const workflow =
"jobs:\n build:\n steps:\n - run: |\n npm run validate\n npm run verify:skills:cli\n";
"on:\n push:\njobs:\n build:\n steps:\n - run: |\n npm run validate\n npm run verify:skills:cli\n";
assert.deepEqual(checkWiring(WIRED_SCRIPTS, workflow), []);
});
test("ciRunsUnconditionally requires a step that runs on every push", () => {
const C = "npm run verify:skills:cli";
const wired =
"jobs:\n build:\n steps:\n - run: npm run verify:skills:cli\n";
"on:\n push:\njobs:\n build:\n steps:\n - run: npm run verify:skills:cli\n";
assert.equal(ciRunsUnconditionally(wired, C), true);
// A multi-line `run:` block is the obvious way to over-correct; it counts.
assert.equal(
ciRunsUnconditionally(
"jobs:\n build:\n steps:\n - run: |\n npm run validate\n npm run verify:skills:cli\n",
"on:\n push:\njobs:\n build:\n steps:\n - run: |\n npm run validate\n npm run verify:skills:cli\n",
C,
),
true,
@ -312,9 +312,9 @@ test("ciRunsUnconditionally rejects a step that only runs sometimes", () => {
// Both are syntactically `run:` steps, so an executable-position check alone
// reports them as wired while PR CI validates nothing.
const releaseOnly =
"jobs:\n publish:\n if: github.event_name == 'release'\n steps:\n - run: npm run verify:skills:cli\n";
"on:\n push:\njobs:\n publish:\n if: github.event_name == 'release'\n steps:\n - run: npm run verify:skills:cli\n";
const stepGated =
"jobs:\n build:\n steps:\n - if: false\n run: npm run verify:skills:cli\n";
"on:\n push:\njobs:\n build:\n steps:\n - if: false\n run: npm run verify:skills:cli\n";
assert.equal(ciRunsUnconditionally(releaseOnly, C), false);
assert.equal(ciRunsUnconditionally(stepGated, C), false);
});
@ -324,7 +324,7 @@ test("ciRunsUnconditionally is not satisfied by an unrelated or absent command",
// `verify:skills` must not satisfy a search for `verify:skills:cli`.
assert.equal(
ciRunsUnconditionally(
"jobs:\n build:\n steps:\n - run: npm run verify:skills\n",
"on:\n push:\njobs:\n build:\n steps:\n - run: npm run verify:skills\n",
C,
),
false,
@ -378,9 +378,33 @@ 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",
"on:\n push:\njobs:\n build:\n steps:\n - run: npm run verify:skills:cli:disabled\n",
C,
),
false,
);
});
test("ciRunsUnconditionally requires the workflow to fire on ordinary changes", () => {
const C = "npm run verify:skills:cli";
const step =
"jobs:\n build:\n steps:\n - run: npm run verify:skills:cli\n";
// `on:` takes three shapes, and any of them may name the event.
assert.equal(ciRunsUnconditionally(`on:\n push:\n${step}`, C), true);
assert.equal(ciRunsUnconditionally(`on: push\n${step}`, C), true);
assert.equal(ciRunsUnconditionally(`on: [push, release]\n${step}`, C), true);
assert.equal(ciRunsUnconditionally(`on:\n pull_request:\n${step}`, C), true);
// A workflow switched to release-only still CONTAINS the step, and an
// unconditional-step check alone reports it as wired — while no PR ever runs
// the validator (Copilot).
assert.equal(
ciRunsUnconditionally(
`on:\n release:\n types: [published]\n${step}`,
C,
),
false,
);
assert.equal(ciRunsUnconditionally(step, C), false);
});