fix(check-versions): mirror stats against the pinned ref, not the working tree [skip-docs]
Found live minutes after shippingfc95cbd: llm-security went ERROR on scanners (23 vs 22) and tests (2013 vs 2034). The catalog was RIGHT and the gate was wrong. llm-security had committed past its v7.8.3 tag without bumping the version, and the gate was reading the sibling working tree — but the catalog documents what INSTALLS, and `ref: v7.8.3` still installs 23/2013. Stat badges are now read with `git show <ref>:README.md`, falling back to the working tree only when the ref cannot be read (a ref resolving to nothing is already its own ERROR, so the fallback cannot hide a dangling ref). The version-badge check is unchanged and still reads the working tree: that one is about the plugin's internal consistency, not about what the catalog promises. This also corrects a stat I got wrong infc95cbd. I had moved config-audit from 1410 to 1441 tests off the working tree; at the pinned v5.13.0 the badge says 1398. 1441 is unreleased. The catalog now says 1398 — what installs. [skip-docs]: CLAUDE.md carries the rule and the "check `git show <ref>:README.md` before believing the working tree" instruction; README.md changes by one number because the gate was wrong about it. Tests 117 -> 120 (+3, all regression). Gate green at 11 OK / 0 WARN / 0 ERROR. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RDSiMcgLMpEETwtkc86Nym
This commit is contained in:
parent
c6c82b358e
commit
780f86ec9f
4 changed files with 63 additions and 6 deletions
|
|
@ -47,3 +47,9 @@ their own Forgejo repositories under `https://git.fromaitochitta.com/open/`.
|
|||
(repo-mailbox's read `6 CLI scripts` / `251 selftest checks` against a true 8 / 374). `N+` in the
|
||||
catalog is read as a lower bound, not an equality. **Never hand-edit a stat line to silence the
|
||||
gate** — the plugin's badge is the source for every stat number; fix the catalog to match it.
|
||||
- **The stat mirror reads the plugin README AT THE PINNED `ref`, never the sibling working tree.**
|
||||
The catalog documents what *installs*, and that is the tag. A plugin that commits past its tag
|
||||
without bumping its version — measured 2026-08-02 on both llm-security (scanners 23→22, tests
|
||||
2013→2034) and config-audit (tests 1398→1441) — would otherwise make the gate demand that the
|
||||
catalog restate unreleased numbers, which is exactly backwards. **When the gate flags a stat,
|
||||
check `git show <ref>:README.md` in the plugin repo before believing the working tree.**
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ Configuration intelligence for Claude Code. Claude reads instructions from 7+ fi
|
|||
|
||||
Key commands: `/config-audit posture`, `/config-audit feature-gap`, `/config-audit fix`, `/config-audit whats-active`, `/config-audit tokens`
|
||||
|
||||
7 agents · 16 scanners · 21 commands · 1441 tests · [Full documentation →](https://git.fromaitochitta.com/open/config-audit)
|
||||
7 agents · 16 scanners · 21 commands · 1398 tests · [Full documentation →](https://git.fromaitochitta.com/open/config-audit)
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -96,6 +96,19 @@ function readStatSegment(segment, out) {
|
|||
}
|
||||
}
|
||||
|
||||
// Which copy of the plugin README the stat mirror must read.
|
||||
//
|
||||
// The catalog documents what INSTALLS, and what installs is the pinned `ref` — not
|
||||
// whatever the sibling working tree happens to hold. Measured live 2026-08-02:
|
||||
// llm-security had committed past its v7.8.3 tag (scanners 23->22, tests 2013->2034)
|
||||
// without bumping the version, so reading the working tree reported the catalog stale
|
||||
// while the catalog was correct about v7.8.3. Fall back to the working tree only when
|
||||
// the ref cannot be read at all — a ref that resolves to nothing is already its own
|
||||
// ERROR (check 1), so this fallback never hides a dangling ref.
|
||||
export function pickStatSource({ atRef, atWorktree }) {
|
||||
return atRef !== null && atRef !== undefined ? atRef : (atWorktree ?? null);
|
||||
}
|
||||
|
||||
// Catalog README -> Map(axis -> count | {atLeast}). Reads ONLY the stat line of the
|
||||
// named plugin's block, so prose numbers above it can never be mistaken for counts.
|
||||
export function extractCatalogStats(readmeText, name) {
|
||||
|
|
@ -175,6 +188,17 @@ export function classifyPlugin({ name, catalogRef, pluginVersion, readmeBadge, t
|
|||
return { name, status, findings };
|
||||
}
|
||||
|
||||
// Read one file as it exists AT a git ref, without touching the working tree.
|
||||
// null when the ref or the path does not resolve there.
|
||||
function gitShow(repoDir, ref, path) {
|
||||
if (!ref) return null;
|
||||
try {
|
||||
return execFileSync('git', ['-C', repoDir, 'show', `${ref}:${path}`], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function gitTags(repoDir) {
|
||||
try {
|
||||
const out = execFileSync('git', ['-C', repoDir, 'tag', '--list', 'v*'], { encoding: 'utf8' });
|
||||
|
|
@ -200,14 +224,18 @@ export function inspectPlugin(catalogDir, plugin) {
|
|||
pluginVersion = pj.version ?? null;
|
||||
} catch { /* leave null → flagged */ }
|
||||
|
||||
let readmeBadge = null;
|
||||
let statBadges = null;
|
||||
let worktreeReadme = null;
|
||||
try {
|
||||
const pluginReadme = readFileSync(join(repoDir, 'README.md'), 'utf8');
|
||||
readmeBadge = extractBadgeVersion(pluginReadme);
|
||||
statBadges = extractStatBadges(pluginReadme);
|
||||
worktreeReadme = readFileSync(join(repoDir, 'README.md'), 'utf8');
|
||||
} catch { /* no README → badge + stat checks skipped */ }
|
||||
|
||||
// Version-badge check 2 is about the working tree's INTERNAL consistency, so it keeps
|
||||
// reading the working tree. The stat mirror is about what the catalog promises users,
|
||||
// so it reads the pinned ref instead — see pickStatSource.
|
||||
const readmeBadge = worktreeReadme === null ? null : extractBadgeVersion(worktreeReadme);
|
||||
const statSource = pickStatSource({ atRef: gitShow(repoDir, catalogRef, 'README.md'), atWorktree: worktreeReadme });
|
||||
const statBadges = statSource === null ? null : extractStatBadges(statSource);
|
||||
|
||||
let catalogLabel = null;
|
||||
let catalogStats = null;
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {
|
|||
extractCatalogLabel,
|
||||
classifyPlugin,
|
||||
extractStatBadges,
|
||||
pickStatSource,
|
||||
extractCatalogStats,
|
||||
} from './check-versions.mjs';
|
||||
|
||||
|
|
@ -269,3 +270,25 @@ test('stat maps omitted (legacy callers) → mirroring skipped entirely', () =>
|
|||
});
|
||||
assert.equal(r.status, 'OK');
|
||||
});
|
||||
|
||||
// Regression, found live 2026-08-02: llm-security had committed past its v7.8.3 tag
|
||||
// (scanners 23->22, tests 2013->2034) WITHOUT bumping the version. The gate read the
|
||||
// sibling's working tree and reported the catalog stale — but the catalog documents
|
||||
// what INSTALLS, and `ref: v7.8.3` still installs 23/2013. The catalog was right and
|
||||
// the gate was wrong. Stat badges must therefore be read at the pinned ref.
|
||||
test('pickStatSource prefers the README at the pinned ref over the working tree', () => {
|
||||
const atRef = 'https://img.shields.io/badge/scanners-23-cyan';
|
||||
const atWorktree = 'https://img.shields.io/badge/scanners-22-cyan';
|
||||
assert.equal(pickStatSource({ atRef, atWorktree }), atRef);
|
||||
assert.equal(extractStatBadges(pickStatSource({ atRef, atWorktree })).get('scanner'), 23);
|
||||
});
|
||||
|
||||
test('pickStatSource falls back to the working tree only when the ref cannot be read', () => {
|
||||
const atWorktree = 'https://img.shields.io/badge/scanners-22-cyan';
|
||||
assert.equal(pickStatSource({ atRef: null, atWorktree }), atWorktree);
|
||||
assert.equal(pickStatSource({ atRef: null, atWorktree: null }), null);
|
||||
});
|
||||
|
||||
test('an empty README at the ref is a real read, not a failed one', () => {
|
||||
assert.equal(pickStatSource({ atRef: '', atWorktree: 'badge/scanners-22-cyan' }), '');
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue