diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 3f2e03d..f5c7ba7 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -48,7 +48,7 @@ gzipped captures in `assets/`; `stats.mjs`/`doctor.mjs`/`build_playbook.mjs` tes inline fixtures. Adding a test means extending the relevant `selfTest()`, not adding a file. Keep `package.json`'s `test` script in sync when a new `*:test` is added. -CI (`.github/workflows/ci.yml`, Node 18 + 20) runs `npm test` plus two things `npm test` does not: +CI (`.github/workflows/ci.yml`, Node 22 + 24) runs `npm test` plus two things `npm test` does not: ```bash # offline smoke chain @@ -128,8 +128,12 @@ These have no single source of truth; an edit to one side silently breaks the ot table and `doctor.mjs`'s `DEFAULT_CONFIG`. Same class as the chrome-devtools-mcp pin — `check_invariants.mjs`'s `checkConfigKeys()` catches drift. - **`Guidance:` ids** in `references/knowledge/*.md` and `assets/playbook.seed.json` name - `modern-web-guidance` guide ids. Key on the bare `id` — upstream category directories churn. A - citation that stops resolving fails silently; nothing validates it. + `modern-web-guidance` guide ids. Key on the bare `id` — upstream category directories churn. + `ci.yml` retrieves every cited id at the pinned version and fails on any that don't resolve. +- **modern-web-guidance pin** — `references/tools.md`'s ``**Pinned: `modern-web-guidance@X.Y.Z`.**`` + line is the source; every `modern-web-guidance@` in `SKILL.md` and `references/knowledge/INDEX.md` + must match it (`checkGuidancePin()`), and `ci.yml` reads it from there. It needs Node ≥ 22, which + is why that's the floor. ## Project rules @@ -146,7 +150,7 @@ These have no single source of truth; an edit to one side silently breaks the ot - **Knowledge-base status tiers** (`proven` / `documented` / `ungated hypothesis` / `dead end`) in `references/knowledge/` require a real measured before/after to upgrade. A citation is not a measurement. -- Node ≥ 18, ESM throughout. The only runtime deps are `@babel/parser` + `@babel/traverse` (used +- Node ≥ 22, ESM throughout. The only runtime deps are `@babel/parser` + `@babel/traverse` (used solely by `perf_scan.mjs`) and `react-devtools-cdt-mcp` at an exact version (never imported by a script — `page_tools.mjs serve` reads its page script from `node_modules` and serves it to the target page). The `SessionStart` hook installs them, and its guard checks for both. Keep it that way. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a657e5f..510a573 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,6 +4,8 @@ # build_perf_map --renders from the render-overlay fixture pair # - JSON validation of all plugin manifests # - official plugin manifest validation (claude plugin validate --strict) +# - every `Guidance:` id resolves at the pinned modern-web-guidance (the one networked step: +# it downloads the pinned package once) # # The live path (chrome-devtools-mcp / real browser / MCP runtime) cannot be CI-tested # because it requires a running Chrome instance and an active Claude Code session. @@ -16,13 +18,17 @@ on: push: branches: [master] +# Read-only token: the guidance step runs a third-party npm package, and checkout persists the token. +permissions: + contents: read + jobs: ci: runs-on: ubuntu-latest strategy: matrix: - node-version: [18.x, 20.x] + node-version: [22.x, 24.x] steps: - name: Checkout @@ -60,6 +66,24 @@ jobs: test -s /tmp/perf-map.html || (echo "perf-map.html is missing or empty" && exit 1) grep -q '"renders":3' /tmp/perf-map.html || (echo "perf-map.html is missing the render overlay" && exit 1) + - name: Guidance ids resolve at the pinned modern-web-guidance + if: matrix.node-version == '22.x' + env: + DISABLE_TELEMETRY: 1 + run: | + pin=$(grep -oE 'Pinned: `modern-web-guidance@[0-9]+\.[0-9]+\.[0-9]+' skills/browsergnome/references/tools.md | cut -d@ -f2) + test -n "$pin" || { echo "::error::no modern-web-guidance pin in references/tools.md"; exit 1; } + ids=$( { grep -rh '^Guidance:' skills/browsergnome/references/knowledge | sed 's/^Guidance:[[:space:]]*//' + grep -oE '"guidance"[[:space:]]*:[[:space:]]*"[^"]*"' skills/browsergnome/assets/playbook.seed.json | cut -d'"' -f4; } | sort -u) + test -n "$ids" || { echo "::error::found no Guidance ids to check"; exit 1; } + fail=0 + while IFS= read -r id; do + if ! printf '%s' "$id" | grep -qE '^[a-z0-9-]+$'; then echo "::error::Guidance '$id' is not a bare modern-web-guidance id"; fail=1 + elif npx --yes "modern-web-guidance@$pin" retrieve "$id" > /dev/null 2>&1; then echo " ok $id" + else echo "::error::Guidance id '$id' does not resolve at modern-web-guidance@$pin"; fail=1; fi + done <<< "$ids" + exit $fail + - name: Validate JSON files run: | node -e " diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 7a266d7..27d3e33 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -48,7 +48,7 @@ jobs: - name: Set up Node.js 20 uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 22 - name: Install dependencies run: npm ci diff --git a/AGENTS.md b/AGENTS.md index f0b615e..8e6e7e8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -64,7 +64,7 @@ These have no single source of truth; an edit to one side silently breaks the ot - **Knowledge-base status tiers** (`proven` / `documented` / `ungated hypothesis` / `dead end`) in `references/knowledge/` require a real measured before/after to upgrade. A citation is not a measurement. -- Node ≥ 18, ESM throughout. The only runtime deps are `@babel/parser` + `@babel/traverse`. +- Node ≥ 22, ESM throughout. The only runtime deps are `@babel/parser` + `@babel/traverse`. ## Full detail diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 838a581..799cc5d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,7 +13,7 @@ cd browsergnome npm install # @babel/parser + @babel/traverse, used only by perf_scan.mjs ``` -No build step. Node ≥ 18, ESM throughout. +No build step. Node ≥ 22, ESM throughout. ## Running the tests @@ -25,7 +25,7 @@ npm run invariants # the invariants check itself (not a self-test — asse # files agree; see "Cross-file invariants" below) ``` -`ci.yml` runs this same `npm test` chain on Node 18 and 20 for every PR and push to `master` — the +`ci.yml` runs this same `npm test` chain on Node 22 and 24 for every PR and push to `master` — the per-script `selfTest()`s aren't an untested side-channel, they gate merges. Each testable script carries its own `selfTest()` behind `--self-test`, with a local pass/fail-tally @@ -54,11 +54,11 @@ have no other single source of truth and silently drift if only one side of an e `npm run invariants` (part of `npm test`) mechanically checks the chrome-devtools-mcp pin and its `--categoryExperimentalThirdParty` flag, the `react-devtools-cdt-mcp` pin, the stack-catalog filenames, -the `depPulse`/`depPulseAutoApply`/`pageTools` config keys, and the Perf Map calibration SHA, and fails -the build on drift. Two invariants stay a manual review responsibility, -because a script can't meaningfully check them: the "one confident, unhedged voice" rule across -product docs is editorial, not mechanical; and the `Guidance:` ids resolve against the external -`modern-web-guidance` catalog, which isn't installed in CI. +the `depPulse`/`depPulseAutoApply`/`pageTools` config keys, the Perf Map calibration SHA, and the +`modern-web-guidance` pin, and fails the build on drift. A separate `ci.yml` step retrieves every +`Guidance:` id at that pin and fails on any that don't resolve. One invariant stays a manual review +responsibility: the "one confident, unhedged voice" rule across product docs is editorial, not +mechanical. ## Proposing a change diff --git a/README.md b/README.md index 4b84084..c3781d4 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ [![Forks](https://img.shields.io/github/forks/uphold/browsergnome?color=5b6472)](https://github.com/uphold/browsergnome/network/members) [![Issues](https://img.shields.io/github/issues/uphold/browsergnome?color=5b6472)](https://github.com/uphold/browsergnome/issues) [![Live demo](https://img.shields.io/badge/demo-live-E45FD8)](https://uphold.github.io/browsergnome/) -[![Node >=18](https://img.shields.io/badge/node-%3E%3D18-5b6472)](package.json) +[![Node >=22](https://img.shields.io/badge/node-%3E%3D22-5b6472)](package.json)