From a5e24f25c1d0e21f5e5f26e74a847bf62bf779df Mon Sep 17 00:00:00 2001 From: 4gray Date: Mon, 21 Sep 2026 06:24:22 +0200 Subject: [PATCH] fix(agents): exclude www autolinks from import scanning --- docs/development/agent-workflow.md | 4 +- tools/skills/agent-guidance-markdown.mjs | 2 +- tools/skills/validate-agent-guidance.test.mjs | 38 +++++++++++++++++++ 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/docs/development/agent-workflow.md b/docs/development/agent-workflow.md index cd315e38f..f9505a6ff 100644 --- a/docs/development/agent-workflow.md +++ b/docs/development/agent-workflow.md @@ -73,7 +73,7 @@ SVG image/use hrefs (including xlink), HTML image-input, video, audio, source an 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. -At-signs inside external URIs (including explicit opaque autolinks such as mailto) are excluded +At-signs inside external URIs (including www autolinks and explicit opaque autolinks such as mailto) are excluded per HTML text node, preserving adjacent imports. A colon directly before an import does not make that import a URI. Opaque schemes are excluded only in parsed links whose visible text equals their URI, so colon-labeled prose remains checked. @@ -82,6 +82,8 @@ Extensionless inline candidates are also imports when they resolve to repository checking the full filename before prefixes at ASCII/Unicode prose separators, including opening parentheses, brackets and braces. Each at-sign candidate is checked independently, including imports nested next to a package mention. +An at-sign inside a word (for example, foo@INSTRUCTIONS or an email address) +is not an import boundary, including within parenthetical prose. Declared scoped dependencies, scope wildcards and matching TypeScript path aliases are recognized as package/alias mentions. Traversal and document-file imports are rejected before those exemptions, including document paths with fragments or queries. diff --git a/tools/skills/agent-guidance-markdown.mjs b/tools/skills/agent-guidance-markdown.mjs index 88a6782d9..d858be21c 100644 --- a/tools/skills/agent-guidance-markdown.mjs +++ b/tools/skills/agent-guidance-markdown.mjs @@ -181,7 +181,7 @@ export function guidanceProse(markdown) { } if (node.nodeName === '#text') return node.value.replace( - /(?:\b[a-z][a-z\d+.-]*:\/\/|\/\/)[^\s]*?(?=[)\]}>][.,;:!?]*@|\s|$)/giu, + /(?:\b[a-z][a-z\d+.-]*:\/\/|\/\/|\bwww\.)[^\s]*?(?=[)\]}>][.,;:!?]*@|\s|$)/giu, (url, offset) => { const opening = ( preceding + node.value.slice(0, offset) diff --git a/tools/skills/validate-agent-guidance.test.mjs b/tools/skills/validate-agent-guidance.test.mjs index 321c37bd0..4ed638520 100644 --- a/tools/skills/validate-agent-guidance.test.mjs +++ b/tools/skills/validate-agent-guidance.test.mjs @@ -1812,3 +1812,41 @@ test('federated handle cannot hide a nested import', async (t) => { ).some((message) => message.includes('additional or inline')) ); }); + +test('www autolinks do not introduce guidance imports', async (t) => { + assert.deepEqual( + await diagnostics(t, { + 'AGENTS.md': 'See www.example.com/@docs/guide', + }), + [] + ); +}); +for (const prose of [ + 'www.example.com/path).@docs/guide.md', + '"www.example.com/path".@docs/guide.md', +]) { + test(`www URL preserves adjacent import: ${prose}`, async (t) => { + assert.ok( + ( + await diagnostics(t, { + 'CLAUDE.md': '@AGENTS.md\n\n' + prose, + }) + ).some((message) => message.includes('additional or inline')) + ); + }); +} +for (const prose of [ + 'foo@INSTRUCTIONS', + '@alice@example.social(foo@INSTRUCTIONS)', + '@alice@example.social(email@example.com)', +]) { + test(`embedded at-sign is not an import boundary: ${prose}`, async (t) => { + assert.deepEqual( + await diagnostics(t, { + 'AGENTS.md': prose, + INSTRUCTIONS: 'Guidance', + }), + [] + ); + }); +}