mirror of
https://github.com/addyosmani/agent-skills.git
synced 2026-10-26 15:05:04 +00:00
Merge #517: exempt fenced examples from the references link check
This commit is contained in:
2 files changed
+118
No files matched your search
@@ -151,3 +151,76 @@ test('reports every unresolvable link, not just the first per skill', () => {
|
||||
assert.equal(result.status, 1, result.stdout + result.stderr);
|
||||
assert.match(result.stdout, /1 skills checked — 2 error\(s\) — FAILED/);
|
||||
});
|
||||
|
||||
test('a link inside a fenced block is an example, not a link to resolve', () => {
|
||||
const root = makeSandbox();
|
||||
writeFile(root, 'references/definition-of-done.md', '# DoD\n');
|
||||
writeFile(
|
||||
root,
|
||||
'skills/using-agent-skills/SKILL.md',
|
||||
[
|
||||
'Shared checklists live two levels up:',
|
||||
'',
|
||||
'See `../../references/definition-of-done.md`.',
|
||||
'',
|
||||
'Do not write it this way:',
|
||||
'',
|
||||
'```markdown',
|
||||
'[Definition of Done](references/definition-of-done.md)',
|
||||
'```',
|
||||
'',
|
||||
].join('\n')
|
||||
);
|
||||
|
||||
const result = run(root);
|
||||
|
||||
assert.equal(result.status, 0, result.stdout + result.stderr);
|
||||
assert.match(result.stdout, /1 skills checked — 0 error\(s\) — PASSED/);
|
||||
});
|
||||
|
||||
test('fence exemption follows CommonMark rather than a bare ``` match', () => {
|
||||
const root = makeSandbox();
|
||||
writeFile(
|
||||
root,
|
||||
'skills/using-agent-skills/SKILL.md',
|
||||
[
|
||||
'~~~markdown',
|
||||
'[a](references/missing-a.md)',
|
||||
'```', // wrong marker: must not close the ~~~ block
|
||||
'[b](references/missing-b.md)',
|
||||
'~~~',
|
||||
'',
|
||||
' ```markdown', // three-space indent is still a fence
|
||||
'[c](references/missing-c.md)',
|
||||
' `````', // a longer closer is legal
|
||||
'',
|
||||
].join('\n')
|
||||
);
|
||||
|
||||
const result = run(root);
|
||||
|
||||
assert.equal(result.status, 0, result.stdout + result.stderr);
|
||||
assert.match(result.stdout, /0 error\(s\) — PASSED/);
|
||||
});
|
||||
|
||||
test('a real broken link outside any fence is still reported', () => {
|
||||
const root = makeSandbox();
|
||||
writeFile(
|
||||
root,
|
||||
'skills/using-agent-skills/SKILL.md',
|
||||
[
|
||||
'```markdown',
|
||||
'[shown as an example](references/example-only.md)',
|
||||
'```',
|
||||
'',
|
||||
'See `references/definition-of-done.md`.', // genuinely wrong, outside the fence
|
||||
'',
|
||||
].join('\n')
|
||||
);
|
||||
|
||||
const result = run(root);
|
||||
|
||||
assert.equal(result.status, 1, result.stdout + result.stderr);
|
||||
assert.match(result.stdout, /1 skills checked — 1 error\(s\) — FAILED/);
|
||||
assert.match(result.stdout, /L5: references\/definition-of-done\.md/);
|
||||
});
|
||||
@@ -25,6 +25,12 @@
|
||||
* mention paths that do not exist yet (`tasks/todo.md`, `PERF.md`,
|
||||
* `docs/ideas/[idea-name].md`), and those must not fail the build.
|
||||
*
|
||||
* Fenced code blocks are exempt for the same reason. Text inside a fence is
|
||||
* an example, not a link an agent will follow — and this rule in particular
|
||||
* has to be documentable: the failure message below tells authors to write
|
||||
* `../../references/<file>.md` rather than `references/<file>.md`, which no
|
||||
* skill could show as an example without failing the very check explaining it.
|
||||
*
|
||||
* Exit codes: 0 = all clear, 1 = one or more unresolvable links.
|
||||
*/
|
||||
|
||||
@@ -41,11 +47,50 @@ const SKILLS_DIR = path.join(ROOT, 'skills');
|
||||
// non-path character so `myreferences/x.md` does not match.
|
||||
const REFERENCE_LINK_RE = /(?<![A-Za-z0-9._/-])((?:\.\.\/)*references\/[A-Za-z0-9._-]+\.md)/g;
|
||||
|
||||
/**
|
||||
* Line indices (0-based) that sit inside a fenced code block.
|
||||
*
|
||||
* Scans line by line rather than matching one regex over the document, so the
|
||||
* three fence forms CommonMark allows are all handled: tilde fences, fences
|
||||
* indented up to three spaces, and a closing fence longer than its opener. A
|
||||
* fence closes only on the same marker character repeated at least as many
|
||||
* times, so a ``` line inside a ~~~ block does not end it.
|
||||
*
|
||||
* Deliberately local rather than shared with `scripts/lib/skill-lint.js`:
|
||||
* that module's `stripFencedCodeBlocks` is a single regex with the gaps
|
||||
* described in #437, and #444 is already rewriting it. Duplicating a correct
|
||||
* scanner here is the smaller cost than forking a rewrite in flight; once
|
||||
* #444 lands, the two should collapse into one helper under `scripts/lib/`.
|
||||
*/
|
||||
function fencedLineNumbers(lines) {
|
||||
const fenced = new Set();
|
||||
let fence = null;
|
||||
|
||||
lines.forEach((line, i) => {
|
||||
const marker = line.match(/^ {0,3}(`{3,}|~{3,})/);
|
||||
if (fence) {
|
||||
fenced.add(i);
|
||||
if (marker && marker[1][0] === fence.marker && marker[1].length >= fence.length) {
|
||||
fence = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (marker) {
|
||||
fence = { marker: marker[1][0], length: marker[1].length };
|
||||
fenced.add(i);
|
||||
}
|
||||
});
|
||||
|
||||
return fenced;
|
||||
}
|
||||
|
||||
function findViolations(skillDir, skillFile) {
|
||||
const violations = [];
|
||||
const lines = fs.readFileSync(skillFile, 'utf8').split(/\r?\n/);
|
||||
const fenced = fencedLineNumbers(lines);
|
||||
|
||||
lines.forEach((line, i) => {
|
||||
if (fenced.has(i)) return;
|
||||
for (const match of line.matchAll(REFERENCE_LINK_RE)) {
|
||||
const link = match[1];
|
||||
if (!fs.existsSync(path.resolve(skillDir, link))) {
|
||||
|
||||
Reference in new issue
Block a user