From 91d04d94cbdaa212bf46eca968df8e1bddbd0738 Mon Sep 17 00:00:00 2001 From: 4gray Date: Mon, 21 Sep 2026 05:10:12 +0200 Subject: [PATCH] fix(agents): validate SVG images and conventional guides --- docs/development/agent-workflow.md | 4 +-- tools/skills/agent-guidance-markdown.mjs | 2 +- tools/skills/validate-agent-guidance.mjs | 2 +- tools/skills/validate-agent-guidance.test.mjs | 33 +++++++++++++++++++ 4 files changed, 37 insertions(+), 4 deletions(-) diff --git a/docs/development/agent-workflow.md b/docs/development/agent-workflow.md index 11b549f78..950007ecb 100644 --- a/docs/development/agent-workflow.md +++ b/docs/development/agent-workflow.md @@ -69,7 +69,7 @@ Image and media references require a nonempty path that resolves to a file, not Direct file URLs and file-scheme HTML bases are rejected; use portable repository-relative paths. Image references check file existence without interpreting image fragments as Markdown headings; document links keep anchor checks even when sharing a target. -HTML image-input, video, audio, source and track `src` assets and video posters use the same +SVG image hrefs (including xlink), HTML image-input, video, audio, source and track `src` assets and video posters use the same existence checks as images. Entity decoding uses full HTML text/attribute rules, including references whose semicolon may be omitted. Inline guidance imports are rejected after punctuation as well as whitespace. @@ -88,7 +88,7 @@ rejected before those exemptions, including document paths with fragments or que All recognized Markdown extensions share the document-import guard; reStructuredText and AsciiDoc, PDF, Word, OpenDocument, RTF, Org and TeX documents are also excluded from package exemptions. Recognized extensionless guidance names (including AGENTS, -CLAUDE, INSTRUCTIONS and README, case-insensitively) are excluded in package subpaths too. URL-encoded +CLAUDE, INSTRUCTIONS, README, CONTRIBUTING and SECURITY, case-insensitively) are excluded in package subpaths too. URL-encoded paths do not receive package exemptions. TypeScript configuration is parsed as JSONC. Declared packages also permit safe subpaths; exact aliases stay exact. Declared package mentions may include a version (including semver comparators and wildcard ranges) or dist-tag qualifier. diff --git a/tools/skills/agent-guidance-markdown.mjs b/tools/skills/agent-guidance-markdown.mjs index 5f697c1b9..173905117 100644 --- a/tools/skills/agent-guidance-markdown.mjs +++ b/tools/skills/agent-guidance-markdown.mjs @@ -77,7 +77,7 @@ function htmlNavigation(html, inspect = () => {}) { ) anchors.push(attribute.value); if ( - (['a', 'area'].includes(node.tagName) && + (['a', 'area', 'image'].includes(node.tagName) && attribute.name === 'href') || ([ 'img', diff --git a/tools/skills/validate-agent-guidance.mjs b/tools/skills/validate-agent-guidance.mjs index 1af1f3a17..228598340 100644 --- a/tools/skills/validate-agent-guidance.mjs +++ b/tools/skills/validate-agent-guidance.mjs @@ -166,7 +166,7 @@ async function packageMentions(rootDir) { if ( /%[\da-f]{2}/iu.test(token) || MARKDOWN_EXTENSION.test(path) || - /(?:^|\/)(?:AGENTS|CLAUDE|INSTRUCTIONS|README|LICENSE|LICENCE|NOTICE|COPYING|AUTHORS|CONTRIBUTORS|CHANGELOG)$/iu.test( + /(?:^|\/)(?:AGENTS|CLAUDE|INSTRUCTIONS|README|LICENSE|LICENCE|NOTICE|COPYING|AUTHORS|CONTRIBUTORS|CHANGELOG|CONTRIBUTING|SECURITY|CODE_OF_CONDUCT|SUPPORT)$/iu.test( path ) || DOCUMENT_EXTENSION.test(path) diff --git a/tools/skills/validate-agent-guidance.test.mjs b/tools/skills/validate-agent-guidance.test.mjs index ea7364c92..3c251366a 100644 --- a/tools/skills/validate-agent-guidance.test.mjs +++ b/tools/skills/validate-agent-guidance.test.mjs @@ -1669,3 +1669,36 @@ test('image input source is validated', async (t) => { [] ); }); + +for (const attribute of ['href', 'xlink:href']) { + test(`SVG image reference is checked: ${attribute}`, async (t) => { + assert.ok( + ( + await diagnostics(t, { + 'AGENTS.md': ``, + }) + ).length > 0 + ); + assert.deepEqual( + await diagnostics(t, { + 'AGENTS.md': ``, + }), + [] + ); + }); +} +for (const name of ['CONTRIBUTING', 'SECURITY', 'code_of_conduct', 'SUPPORT']) { + test(`conventional guidance basename is not a package: ${name}`, async (t) => { + assert.ok( + ( + await diagnostics(t, { + 'package.json': JSON.stringify({ + dependencies: { '@angular/core': '*' }, + }), + 'CLAUDE.md': + '@AGENTS.md\n\nRead @angular/core/docs/' + name, + }) + ).some((message) => message.includes('additional or inline')) + ); + }); +}