feat(catalog): gate catalog README labels against ref + close the drift
The catalog README's per-plugin `vX.Y.Z` labels were an UNGUARDED surface: check-versions validated each plugin's OWN README badge, never the catalog README's labels, so they drifted (config-audit shown v5.5.0 while pinned to v5.7.0; voyage v5.1.1 while pinned to v5.6.0) — the doc misstated what `claude plugin install` actually resolves. Close the class, same pattern as the ref surface: - check-versions.mjs: new ERROR rule "catalog README label == catalog ref" via pure extractCatalogLabel() (matches the /open/<name>) heading, takes the first `vX.Y.Z`, ignores a trailing lang/flag badge). No legitimate transient state lets label != ref, so ERROR (not WARN). +5 tests. - release-plugin.mjs: on --write, bump the README label atomically with the ref via pure reconcileReadmeLabel() and git add README.md on --commit, so future releases keep label and ref in lock-step. +4 tests. - README.md: reconcile the 2 stale labels (config-audit -> v5.7.0, voyage -> v5.6.0). Gate now 9 OK / 1 WARN / 0 ERROR. - CLAUDE.md: doctrine updated to document the new gate rule + atomic label bump. ms-ai-architect stays WARN by decision (1.16.0 is unreleased WIP, never tagged). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cm2RxKbomdLqjiWGcwCCPi
This commit is contained in:
parent
cf2420f7c5
commit
2e1d206ab3
6 changed files with 155 additions and 18 deletions
|
|
@ -3,7 +3,7 @@
|
|||
// is exercised by the CLI against the live tree, not here.
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { planRelease } from './release-plugin.mjs';
|
||||
import { planRelease, reconcileReadmeLabel } from './release-plugin.mjs';
|
||||
import { classifyPlugin } from './check-versions.mjs';
|
||||
|
||||
const marketplace = () => ({
|
||||
|
|
@ -102,3 +102,32 @@ test('BLOCKED: target version cannot be resolved (no --version, no plugin.json)'
|
|||
assert.equal(p.verdict, 'BLOCKED');
|
||||
assert.ok(p.blockers.some(b => /resolve.*version|version.*not/i.test(b)));
|
||||
});
|
||||
|
||||
// --- reconcileReadmeLabel: keeps the catalog README label in lock-step with the ref ---
|
||||
|
||||
test('reconcileReadmeLabel bumps only the target plugin heading label', () => {
|
||||
const readme = [
|
||||
'### [Config-Audit](https://git.fromaitochitta.com/open/config-audit) `v5.5.0`',
|
||||
'body text',
|
||||
'### [Voyage](https://git.fromaitochitta.com/open/voyage) `v5.1.1`',
|
||||
].join('\n');
|
||||
const out = reconcileReadmeLabel(readme, 'config-audit', 'v5.7.0');
|
||||
assert.ok(out.includes('/open/config-audit) `v5.7.0`'));
|
||||
assert.ok(out.includes('/open/voyage) `v5.1.1`')); // untouched
|
||||
});
|
||||
|
||||
test('reconcileReadmeLabel leaves a trailing lang/flag badge intact', () => {
|
||||
const readme = '### [MS AI Architect](https://x/open/ms-ai-architect) `v1.15.0` `🇳🇴 Norwegian`';
|
||||
const out = reconcileReadmeLabel(readme, 'ms-ai-architect', 'v1.16.0');
|
||||
assert.equal(out, '### [MS AI Architect](https://x/open/ms-ai-architect) `v1.16.0` `🇳🇴 Norwegian`');
|
||||
});
|
||||
|
||||
test('reconcileReadmeLabel returns null when the label already matches (no-op)', () => {
|
||||
const readme = '### [Config-Audit](https://x/open/config-audit) `v5.7.0`';
|
||||
assert.equal(reconcileReadmeLabel(readme, 'config-audit', 'v5.7.0'), null);
|
||||
});
|
||||
|
||||
test('reconcileReadmeLabel returns null when the plugin has no heading', () => {
|
||||
const readme = '### [Other](https://x/open/other) `v1.0.0`';
|
||||
assert.equal(reconcileReadmeLabel(readme, 'ghost', 'v2.0.0'), null);
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue