From 673d3fd791c125f1202e1b9ffeead4a59380e388 Mon Sep 17 00:00:00 2001 From: 4gray Date: Mon, 21 Sep 2026 04:09:46 +0200 Subject: [PATCH] fix(agents): handle opening prose delimiters --- docs/development/agent-workflow.md | 5 ++-- tools/skills/validate-agent-guidance.mjs | 5 ++-- tools/skills/validate-agent-guidance.test.mjs | 25 +++++++++++++++++++ 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/docs/development/agent-workflow.md b/docs/development/agent-workflow.md index f28a58dc0..dc7c20170 100644 --- a/docs/development/agent-workflow.md +++ b/docs/development/agent-workflow.md @@ -77,7 +77,8 @@ does not make that import a URI. Opaque schemes are excluded only in parsed link whose visible text equals their URI, so colon-labeled prose remains checked. A closing bracket followed by punctuation and an at-sign terminates a bare URL exclusion. Extensionless inline candidates are also imports when they resolve to repository files, -checking the full filename before prefixes at ASCII/Unicode prose separators. +checking the full filename before prefixes at ASCII/Unicode prose separators, +including opening parentheses, brackets and braces. 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. @@ -90,7 +91,7 @@ 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. Qualifier handling includes unscoped names; terminal sentence punctuation is removed before matching a declared package, as are straight/curly apostrophe possessives. -Unicode punctuation and ASCII commas, semicolons, colons, question/exclamation marks +Unicode punctuation and ASCII opening delimiters, commas, semicolons, colons, question/exclamation marks separate package mentions from adjacent prose. Markdown destinations decode HTML entities before URI parsing, matching rendered links. Heading-anchor lookup is limited to Markdown targets. Source-file line fragments, diff --git a/tools/skills/validate-agent-guidance.mjs b/tools/skills/validate-agent-guidance.mjs index 73c42b072..45f85c27a 100644 --- a/tools/skills/validate-agent-guidance.mjs +++ b/tools/skills/validate-agent-guidance.mjs @@ -140,8 +140,9 @@ async function packageMentions(rootDir) { .map((name) => name.slice(1)); const scopes = new Set(declared.map((name) => name.split('/')[0])); return (raw) => { + if (packages.includes(`@${raw}`) || packages.includes(raw)) return true; // ASCII punctuation also belongs to package names and version ranges. - let token = raw.split(/[,;:!?]|(?=[^\x00-\x7f])\p{P}/u, 1)[0]; + let token = raw.split(/[,;:!?([{]|(?=[^\x00-\x7f])\p{P}/u, 1)[0]; token = token.replace(/[?!.,;:)"'\]}]+$/u, ''); token = token.replace(/['’]s$/iu, ''); token = token.replace( @@ -231,7 +232,7 @@ export async function validateAgentGuidance({ rootDir }) { token.replace(/[?!.,;:)"'\]}]+$/u, ''), ]); for (const boundary of token.matchAll( - /[,;:!?]|(?=[^\x00-\x7f])\p{P}/gu + /[,;:!?([{]|(?=[^\x00-\x7f])\p{P}/gu )) candidates.add(token.slice(0, boundary.index)); for (const candidate of candidates) { diff --git a/tools/skills/validate-agent-guidance.test.mjs b/tools/skills/validate-agent-guidance.test.mjs index ad07a9999..fb439347b 100644 --- a/tools/skills/validate-agent-guidance.test.mjs +++ b/tools/skills/validate-agent-guidance.test.mjs @@ -1532,3 +1532,28 @@ test('colon-labeled prose cannot hide imports', async (t) => { ).some((message) => message.includes('additional or inline')) ); }); + +for (const opening of ['(', '[', '{']) { + test(`opening delimiter separates package and import prose: ${opening}`, async (t) => { + assert.deepEqual( + await diagnostics(t, { + 'package.json': JSON.stringify({ + dependencies: { '@angular/core': '*' }, + }), + 'AGENTS.md': 'Use @angular/core' + opening + 'test helpers)', + }), + [] + ); + assert.ok( + ( + await diagnostics(t, { + 'CLAUDE.md': + '@AGENTS.md\n\nRead @INSTRUCTIONS' + + opening + + 'then continue)', + INSTRUCTIONS: 'Guidance', + }) + ).some((message) => message.includes('additional or inline')) + ); + }); +}