feat(gate): install truth, honest badges anywhere, and two stripCode bugs
Install truth is the brief's first control and the gate only checked syntax. Now: the marketplace URL must be the real one (offline, from the register), and the plugin must actually be pinned in the catalog (one call, SKIP if unreachable). A well-formed `claude plugin install x@mkt` fails silently when x was never pinned. This makes the gate block ITSELF until publication finishes - the run against this repo now has exactly one ERROR, and it is true: repo-standard is not in the catalog yet. That is the post-publish acceptance test, enforced mechanically instead of remembered. Badge honesty no longer keys on img.shields.io. A self-hosted SVG asserts the same unverified thing, and the README claimed the general rule while the code checked one host. Two stripCode bugs, both silent false passes: - 4-space indent treated as code unconditionally made links inside nested list items invisible. Fixed by requiring a blank line to OPEN a block. - That fix alone ended the block after line 1, so multi-line indented templates leaked back into scanning. Caught by the gate on this repo's own SKILL.md, which shows a README template containing a CHANGELOG link. A block now opens on a blank line and continues while the indent holds. Also corrected two claims in this README: it said "one network call" when there are two, and it still argued against a CONTRIBUTING using reasoning the solo-maintainer section had already replaced. 77 tests. llm-security regression: still zero link and boilerplate noise. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WYJ3FHLtVgzFXMZ6UF598h
This commit is contained in:
parent
720850a9ad
commit
6b1db0096e
5 changed files with 187 additions and 16 deletions
|
|
@ -24,6 +24,7 @@ import {
|
|||
checkLicenseClaim,
|
||||
checkInternalLinks,
|
||||
resolveRelative,
|
||||
checkInstallTruth,
|
||||
classifyRepo,
|
||||
levelOf,
|
||||
} from './repo-standard-check.mjs';
|
||||
|
|
@ -408,6 +409,7 @@ test('a fully compliant plugin repo classifies OK', () => {
|
|||
readmeBadge: '0.7.0',
|
||||
changelogTop: '0.7.0',
|
||||
tags: ['v0.7.0'],
|
||||
catalogNames: ['repo-mailbox'],
|
||||
},
|
||||
REGISTER,
|
||||
);
|
||||
|
|
@ -694,3 +696,84 @@ test('a nested link to a genuinely absent sibling is still an ERROR', () => {
|
|||
});
|
||||
assert.equal(f.some((x) => x.code === 'LINK-INTERNAL-MISSING'), true);
|
||||
});
|
||||
|
||||
// ------------------------------- stripCode must not swallow nested list items
|
||||
|
||||
test('a link inside a nested list item is still scanned', () => {
|
||||
// An indented line is only a code block when a blank line precedes it.
|
||||
// Treating every 4-space indent as code made nested bullets invisible to both
|
||||
// the link and boilerplate checks — a silent false pass, which is worse than
|
||||
// the noise it was meant to remove.
|
||||
const text = ['- top level', ' - nested with [a link](gone.md)'].join('\n');
|
||||
const f = checkInternalLinks({ files: { 'README.md': text }, present: ['README.md'] });
|
||||
assert.equal(f.some((x) => x.code === 'LINK-INTERNAL-MISSING'), true);
|
||||
});
|
||||
|
||||
test('a genuine indented code block is still skipped', () => {
|
||||
const text = ['Example:', '', ' curl [x](not-real.md)'].join('\n');
|
||||
const f = checkInternalLinks({ files: { 'README.md': text }, present: ['README.md'] });
|
||||
assert.equal(f.filter((x) => x.level === 'ERROR').length, 0);
|
||||
});
|
||||
|
||||
// ------------------------------------------ badge honesty beyond shields.io
|
||||
|
||||
test('a self-hosted badge asserting a run is caught too', () => {
|
||||
const f = checkBadges({ readme: '' });
|
||||
assert.equal(f.some((x) => x.code === 'BADGE-STATIC-CLAIM'), true);
|
||||
});
|
||||
|
||||
// ------------------------------------------------------------ install truth
|
||||
|
||||
test('the marketplace URL in the install block must be the real one', () => {
|
||||
const readme = [
|
||||
'## Install',
|
||||
'claude plugin marketplace add https://git.fromaitochitta.com/open/WRONG.git',
|
||||
`claude plugin install repo-standard@${MKT.name}`,
|
||||
].join('\n');
|
||||
const f = checkInstallBlock({ readme, name: 'repo-standard', klass: 'plugin' }, REGISTER);
|
||||
assert.equal(f.some((x) => x.code === 'INSTALL-URL-MISMATCH' && x.bucket === 'broken'), true);
|
||||
});
|
||||
|
||||
test('a plugin absent from the catalog has an install line that cannot work', () => {
|
||||
// Syntax is not truth. The brief's first control asks whether the command
|
||||
// works for a stranger, not whether it is well-formed.
|
||||
const f = checkInstallTruth({ name: 'ghost', klass: 'plugin', catalogNames: ['repo-mailbox', 'llm-security'] });
|
||||
assert.equal(f.some((x) => x.code === 'INSTALL-NOT-IN-CATALOG' && x.bucket === 'broken'), true);
|
||||
});
|
||||
|
||||
test('a plugin present in the catalog passes install truth', () => {
|
||||
const f = checkInstallTruth({ name: 'repo-mailbox', klass: 'plugin', catalogNames: ['repo-mailbox'] });
|
||||
assert.equal(f.filter((x) => x.level === 'ERROR').length, 0);
|
||||
});
|
||||
|
||||
test('an unreachable catalog is SKIP, never a pass', () => {
|
||||
const f = checkInstallTruth({ name: 'repo-mailbox', klass: 'plugin', catalogNames: null });
|
||||
assert.equal(f[0].level, 'SKIP');
|
||||
});
|
||||
|
||||
test('non-plugin classes are not measured against the catalog', () => {
|
||||
const f = checkInstallTruth({ name: 'portfolio-optimiser', klass: 'standalone', catalogNames: null });
|
||||
assert.equal(f.filter((x) => x.level !== 'OK').length, 0);
|
||||
});
|
||||
|
||||
test('an indented code block stays code past its first line', () => {
|
||||
// The blank-line rule fixed nested lists but broke multi-line indented blocks:
|
||||
// only line 1 followed a blank line, so lines 2+ leaked back into scanning.
|
||||
// Caught by the gate on this plugin's own SKILL.md, which shows a README
|
||||
// template containing a link that does not exist relative to that file.
|
||||
const text = [
|
||||
'Template:',
|
||||
'',
|
||||
' # <name>',
|
||||
' ## Changelog',
|
||||
' See [CHANGELOG.md](CHANGELOG.md).',
|
||||
].join('\n');
|
||||
const f = checkInternalLinks({ files: { 'skills/x/SKILL.md': text }, present: ['skills/x/SKILL.md'] });
|
||||
assert.equal(f.filter((x) => x.level === 'ERROR').length, 0);
|
||||
});
|
||||
|
||||
test('an indented block ends at the first unindented line', () => {
|
||||
const text = ['Template:', '', ' code here', '', 'Back to prose with [a link](gone.md).'].join('\n');
|
||||
const f = checkInternalLinks({ files: { 'README.md': text }, present: ['README.md'] });
|
||||
assert.equal(f.some((x) => x.code === 'LINK-INTERNAL-MISSING'), true);
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue