blocker split blocked_by on the first "@", so "transitive:@scope/name@1.2.3"
read the package name as "" and left "scope/name@1.2.3" stuck to the
version -- the format check then rejected the entry outright. rpartition
splits on the last "@" instead, which is always the version separator
even when the package name carries its own leading "@".
Found adding a typescript-eslint entry (blocked by its own
@typescript-eslint/scope-manager not having published 8.70.1 yet) in
signal-bot.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Zg7ULrGyB1gMcKC1mkbLb
Two npm-specific corrections found by running the gate over web_ui.
**`peerDependencies` must not be exact-pinned.** The gate was reporting
`react: ">=19"` as "not exact-pinned", and acting on that would have been
actively harmful: a peer range declares what a CONSUMER may bring, so pinning
it forces every consumer of @kuhyx/web-ui onto one exact React. Peers are now
judged the way toolchain ranges are -- does the range still admit latest? --
and only reported when it does not.
**`packageManager` is a real dependency.** corepack enforces it, so
`pnpm@10.13.1` is an exact pin that nothing was checking. It is now read and
compared, which immediately surfaced that both TS packages sit on pnpm 10.13.1
against 11.24.0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CRykabMwPgDzFHcARoqpaK
"Are we on the newest version?" was not mechanically answerable across these
repos: constraint styles had drifted per-ecosystem and a caret range is
"newest-compatible" by definition, which gives a checker nothing to compare.
This gate makes it answerable and then enforces the answer.
Checks two things at once -- that every dependency carries an exact pin, and
that the pin equals its registry's latest STABLE release -- across pub.dev,
PyPI, npm, crates.io, the Go proxy, kuhyx/utils git tags, and the Flutter /
Node / Python toolchains.
Three deliberate design points:
* Pre-releases are rejected per-ecosystem, because every ecosystem leaks them
differently and each leak fails GREEN. npm lets a maintainer point
`dist-tags.latest` at an rc; crates.io's `max_version` includes them; PyPI's
legacy `info.version` includes yanked releases. Unit-tested with fixtures,
since an end-to-end run cannot surface a wrong "up to date".
* Offline, the gate serves an expired cache entry and PASSES with a DEGRADED
banner (exit 3 only under --strict, which is CI). `--no-verify` is banned
here, so a hook that hard-failed offline would leave no way to commit at all.
* The allowlist has two entry classes. `blocked_by: transitive:<pkg>@<ver>` is
a predicate that clears itself when upstream moves and must NOT carry a
date; everything else is discretionary and requires `expires`, capped at 90
days. Exact pins plus latest-everywhere is provably unsatisfiable when a
dependency internally constrains a shared transitive, and expiring the
predicate class on a calendar would turn the gate red for a reason no commit
could fix. An entry with nothing left to excuse is itself an error.
One hook id, one shim, one shared implementation -- always delegating, so it
cannot fork the way the 250-line cap forked into four ids, two of which were
independent reimplementations.
256 tests, 100% branch coverage on every module of the gate. Also adds the
workflow that runs them, which incidentally gives md_naming's 71 tests their
first CI run.
Not yet installed in any repo: a repo that gets the gate before its deps are
bumped is instantly and permanently red, so the gate and the upgrade land
together, per repo.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CRykabMwPgDzFHcARoqpaK