diff --git a/docs/development/agent-workflow.md b/docs/development/agent-workflow.md index 950007ecb..1f6a857ae 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. -SVG image hrefs (including xlink), HTML image-input, video, audio, source and track `src` assets and video posters use the same +SVG image/use 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. @@ -91,6 +91,8 @@ from package exemptions. Recognized extensionless guidance names (including AGEN 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. +Federated handles in the @user@host form are prose, not imports. +Exact declared packages remain exempt after version normalization. Declared package mentions may include a version (including semver comparators and wildcard ranges) or dist-tag qualifier. Qualifier handling includes unscoped names; terminal sentence punctuation is removed before matching a declared package, as are straight/curly apostrophe possessives. diff --git a/tools/skills/agent-guidance-markdown.mjs b/tools/skills/agent-guidance-markdown.mjs index 173905117..88a6782d9 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', 'image'].includes(node.tagName) && + (['a', 'area', 'image', 'use'].includes(node.tagName) && attribute.name === 'href') || ([ 'img', @@ -100,6 +100,7 @@ function htmlNavigation(html, inspect = () => {}) { )) ) references.push({ + svgUse: node.tagName === 'use', target: attribute.value .replace(/[\t\n\r]/gu, '') .replace(/^[\u0000-\u0020]+|[\u0000-\u0020]+$/gu, ''), @@ -135,6 +136,16 @@ function htmlNavigation(html, inspect = () => {}) { for (const child of node.childNodes ?? []) visit(child); } visit(parseFragment(html)); + for (const reference of references) { + if ( + reference.svgUse && + !reference.embeddedAnchors && + reference.target.startsWith('#') + ) { + reference.image = false; + reference.embeddedAnchors = anchors; + } + } return { anchors, references: diff --git a/tools/skills/validate-agent-guidance.mjs b/tools/skills/validate-agent-guidance.mjs index 228598340..0b8169546 100644 --- a/tools/skills/validate-agent-guidance.mjs +++ b/tools/skills/validate-agent-guidance.mjs @@ -162,6 +162,8 @@ async function packageMentions(rootDir) { token.split(/[\/\\]/u).some((part) => part === '.' || part === '..') ) return false; + if (packages.includes(token) || packages.includes(`@${token}`)) + return true; const path = token.split(/[?#]/u, 1)[0]; if ( /%[\da-f]{2}/iu.test(token) || @@ -224,6 +226,12 @@ export async function validateAgentGuidance({ rootDir }) { )) { const token = match[1]; if (isPackageMention(token)) continue; + if ( + /^[\w.-]+@(?:[a-z\d](?:[a-z\d-]*[a-z\d])?\.)+[a-z]{2,}[.,;!?]?$/iu.test( + token + ) + ) + continue; if ( /[./\\]/u.test(token) || /^(?:LICENSE|Makefile|Dockerfile|AGENTS|CLAUDE)(?:$|[.,;)])/u.test( diff --git a/tools/skills/validate-agent-guidance.test.mjs b/tools/skills/validate-agent-guidance.test.mjs index 3c251366a..8bfd3c2fd 100644 --- a/tools/skills/validate-agent-guidance.test.mjs +++ b/tools/skills/validate-agent-guidance.test.mjs @@ -1702,3 +1702,48 @@ for (const name of ['CONTRIBUTING', 'SECURITY', 'code_of_conduct', 'SUPPORT']) { ); }); } + +test('versioned declared package may share a guidance basename', async (t) => { + assert.deepEqual( + await diagnostics(t, { + 'package.json': JSON.stringify({ + dependencies: { '@scope/support': '*' }, + }), + 'AGENTS.md': 'Use @scope/support@^2', + }), + [] + ); +}); +test('federated handle is not an import', async (t) => { + assert.deepEqual( + await diagnostics(t, { 'AGENTS.md': 'Contact @alice@example.social' }), + [] + ); +}); +for (const attribute of ['href', 'xlink:href']) { + test(`SVG use references are checked: ${attribute}`, async (t) => { + assert.ok( + ( + await diagnostics(t, { + 'AGENTS.md': ``, + }) + ).length > 0 + ); + assert.deepEqual( + await diagnostics(t, { + 'AGENTS.md': ``, + }), + [] + ); + }); +} + +test('srcdoc SVG use keeps its own anchors', async (t) => { + assert.deepEqual( + await diagnostics(t, { + 'AGENTS.md': + '', + }), + [] + ); +});