From 22b2d7a2ac758a631000adeebf011c95d615d53d Mon Sep 17 00:00:00 2001 From: 4gray Date: Mon, 21 Sep 2026 01:21:42 +0200 Subject: [PATCH] fix(agents): unify Markdown suffix and encoded import guards --- docs/development/agent-workflow.md | 3 ++- tools/skills/validate-agent-guidance.mjs | 13 +++++----- tools/skills/validate-agent-guidance.test.mjs | 24 +++++++++++++++++++ 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/docs/development/agent-workflow.md b/docs/development/agent-workflow.md index 0c85a3209..088df49b1 100644 --- a/docs/development/agent-workflow.md +++ b/docs/development/agent-workflow.md @@ -69,7 +69,8 @@ checking the full filename before prefixes at ASCII/Unicode prose separators. 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. -TypeScript configuration is parsed as JSONC. +All recognized Markdown extensions share the document-import guard. 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) or dist-tag qualifier. Qualifier handling includes unscoped names; terminal sentence punctuation is diff --git a/tools/skills/validate-agent-guidance.mjs b/tools/skills/validate-agent-guidance.mjs index d9e6fc7aa..5ebdd2fc3 100644 --- a/tools/skills/validate-agent-guidance.mjs +++ b/tools/skills/validate-agent-guidance.mjs @@ -16,6 +16,8 @@ import { guidanceReferences as references, } from './agent-guidance-markdown.mjs'; +const MARKDOWN_EXTENSION = /\.(?:md|markdown|mdown|mkd|mdx)$/iu; + const SURFACES = [ 'AGENTS.md', 'CLAUDE.md', @@ -62,9 +64,7 @@ async function validateReference( if ( anchor && !image && - /^(?:\.md|\.markdown|\.mdown|\.mkd|\.mdx)$/iu.test( - extname(actual) - ) && + MARKDOWN_EXTENSION.test(extname(actual)) && !anchors(await readFile(actual, 'utf8')).has(anchor) ) { return `${source}: missing anchor "${anchor}" in ${target}`; @@ -126,10 +126,11 @@ async function packageMentions(rootDir) { token.split(/[\/\\]/u).some((part) => part === '.' || part === '..') ) return false; + const path = token.split(/[?#]/u, 1)[0]; if ( - /\.(?:md|mdx|txt|json|ya?ml|html?)$/iu.test( - token.split(/[?#]/u, 1)[0] - ) + /%[\da-f]{2}/iu.test(token) || + MARKDOWN_EXTENSION.test(path) || + /\.(?:txt|json|ya?ml|html?)$/iu.test(path) ) return false; if (packages.includes(token)) return true; diff --git a/tools/skills/validate-agent-guidance.test.mjs b/tools/skills/validate-agent-guidance.test.mjs index 01fda4ee3..0a31d981d 100644 --- a/tools/skills/validate-agent-guidance.test.mjs +++ b/tools/skills/validate-agent-guidance.test.mjs @@ -1212,3 +1212,27 @@ for (const suffix of [ ); }); } + +for (const suffix of [ + '.markdown', + '.mdown', + '.mkd', + '.md%23rules', + '.md%3Fraw', + '%2Emd', + '/%2e%2e/extra.md', +]) { + test(`document package exemptions reject alternate and encoded paths: ${suffix}`, async (t) => { + assert.ok( + ( + await diagnostics(t, { + 'package.json': JSON.stringify({ + dependencies: { '@angular/core': '*' }, + }), + 'CLAUDE.md': + '@AGENTS.md\n\nRead @angular/core/docs/extra' + suffix, + }) + ).some((message) => message.includes('additional or inline')) + ); + }); +}