From f646e6e86b1f2f1ecc0def14fe2e8ad98b530441 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 12 Aug 2026 18:34:43 +0000 Subject: [PATCH] ci(lint): run the skill-frame freshness gate's self-test half in CI (#8110) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `check:skill-frame-freshness` is `--self-test && the scan`, and only the SCAN half is CI-hostile: on a fresh merge ref "is this tree current with origin/main" is a tautological green, and on any PR that legitimately edits the decision frame it is a false red. That reasoning — the script's own header — was keeping the OTHER half out of CI too, and the price is measured on #8024: the self-test fixture had stranded twice, neither time noticed, while the gate sat in the gate list reading as coverage and protecting nothing. Add one step to lint.yml's `typecheck` job, next to `check:skill-frame-sync`, running only `node scripts/check-skill-frame-freshness.mjs --self-test`. Invoked with `node`, deliberately not `pnpm check:skill-frame-freshness`, which would drag the scan half in with it. Same step shape as the changeset-family self-tests already in this job (#6509): self-test halves in lint.yml, the CI-hostile scan left where it belongs. Measured before wiring, in a `--depth 1` clone with no remote-tracking ref: green, ~1.3s, no network and no history — the specimen is manufactured from the tree's own frame documents and driven through temp git repos whose `origin` points at a path that does not exist. Adversarially: rewording axis prose across all three frame files keeps it green; a structural change (an axis dropped, or a declaring sentence reworded past the shared anchors) reddens it AND reddens `check:skill-frame-sync` above with the same diagnosis, so this step adds no failure class the neighbouring step does not already have. Claude-Session: https://claude.ai/code/session_01P6QzoCR4U6RKxdbCHMJp3V Co-authored-by: Claude --- .github/workflows/lint.yml | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index a7587263b6..3e3f2bc75d 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1081,6 +1081,42 @@ jobs: - name: Check the decision frame is in sync across its four copies run: pnpm check:skill-frame-sync + # The SECOND skill-frame gate — and deliberately only HALF of it (#8110). + # + # `pnpm check:skill-frame-freshness` is `--self-test && the scan`, and only the + # SCAN half is CI-hostile: it asks "is THIS WORKING TREE's frame current with + # origin/main?", which on a fresh merge ref is a tautological green, and on any + # PR that legitimately edits the frame would be a false red — "differs from main" + # is that PR's whole purpose. That script's own header ("WHERE IT RUNS") argues + # it, and the scan stays out of CI for exactly that reason. It is NOT wired here. + # + # The `--self-test` half is a different animal. It DERIVES a coherent two-axis + # specimen from this tree's own frame documents, commits it and the real + # documents into throwaway git repos whose `origin` points at a path that does + # not exist, and asserts 12 verdicts against the gate's own code. No network, no + # origin/main, no history: measured green in a `--depth 1` clone carrying no + # remote-tracking ref at all, in ~1.3s. So the half that CANNOT run in CI was + # keeping the half that CAN out of it, and the price was measured on #8024 — + # the fixture had stranded TWICE, neither time noticed, while the gate sat in + # the gate list reading as coverage and protecting nothing. + # + # This step cannot redden a PR that edits the frame on purpose: the specimen is + # re-derived from whatever the frame currently is, so a reword, a reflow or a + # principles-only rewrite carries the fixture with it (verified adversarially + # before wiring this — rewording axis prose across all three frame files keeps + # both gates green). What DOES redden it is a STRUCTURAL change — an axis added, + # removed, or its declaring sentence reworded past the shared anchors — and that + # reddens `check:skill-frame-sync` directly above with the same diagnosis, which + # the self-test's own failure message names as the thing to fix first. No PR can + # reach this step red with the step above green. + # + # Invoked as `node`, NOT `pnpm check:skill-frame-freshness`, precisely because + # that npm script would drag the scan half in with it — the same shape as the + # `dev` chain calling check-dev-prereqs.mjs directly rather than through its + # pnpm script (see that script's header). + - name: Self-test the skill-frame freshness gate (self-test half only, never the scan) + run: node scripts/check-skill-frame-freshness.mjs --self-test + # The third gate over SKILL.md, covering the one line the other two cannot see # (#5331). check:skill-docs / check:skill-refs compare generated artifacts and # check:skill-examples typechecks `os:check` blocks; none of them reads the