diff --git a/docs/development/agent-workflow.md b/docs/development/agent-workflow.md index 2e0add38e..cd315e38f 100644 --- a/docs/development/agent-workflow.md +++ b/docs/development/agent-workflow.md @@ -92,7 +92,8 @@ CLAUDE, INSTRUCTIONS, README, CONTRIBUTING and SECURITY, case-insensitively) are 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; trailing closing -ASCII/Unicode punctuation and possessive apostrophe-s suffixes are ignored, but extra at-signs do not qualify for that exemption. +ASCII/Unicode punctuation and possessive apostrophe-s suffixes are ignored. Opening +delimiters separate adjacent prose; nested imports remain checked. Extra at-signs do not qualify for that exemption. 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 diff --git a/tools/skills/validate-agent-guidance.mjs b/tools/skills/validate-agent-guidance.mjs index f1f19cd6b..f09a6a22a 100644 --- a/tools/skills/validate-agent-guidance.mjs +++ b/tools/skills/validate-agent-guidance.mjs @@ -228,7 +228,10 @@ export async function validateAgentGuidance({ rootDir }) { if (isPackageMention(token)) continue; if ( /^[\w.-]+@(?:[a-z\d](?:[a-z\d-]*[a-z\d])?\.)+[a-z]{2,}$/iu.test( - token.replace(/\p{P}+$/gu, '').replace(/['’]s$/iu, '') + token + .split(/[([{]/u, 1)[0] + .replace(/\p{P}+$/gu, '') + .replace(/['’]s$/iu, '') ) ) continue; diff --git a/tools/skills/validate-agent-guidance.test.mjs b/tools/skills/validate-agent-guidance.test.mjs index 6371ae1e9..321c37bd0 100644 --- a/tools/skills/validate-agent-guidance.test.mjs +++ b/tools/skills/validate-agent-guidance.test.mjs @@ -1790,3 +1790,25 @@ for (const suffix of ["'s", '’s']) { ); }); } + +for (const suffix of ['(admin)', '[admin]', '{admin}']) { + test(`federated handle permits parenthetical prose: ${suffix}`, async (t) => { + assert.deepEqual( + await diagnostics(t, { + 'AGENTS.md': 'Contact @alice@example.social' + suffix, + }), + [] + ); + }); +} +test('federated handle cannot hide a nested import', async (t) => { + assert.ok( + ( + await diagnostics(t, { + 'CLAUDE.md': + '@AGENTS.md\n\n@alice@example.social(@INSTRUCTIONS)', + INSTRUCTIONS: 'Guidance', + }) + ).some((message) => message.includes('additional or inline')) + ); +});