diff --git a/docs/development/agent-workflow.md b/docs/development/agent-workflow.md index 1f6a857ae..5a44a2f1d 100644 --- a/docs/development/agent-workflow.md +++ b/docs/development/agent-workflow.md @@ -91,7 +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. +Federated handles in the @user@host form are prose, not imports; trailing closing +punctuation is ignored, but 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 0b8169546..5c4f93b5c 100644 --- a/tools/skills/validate-agent-guidance.mjs +++ b/tools/skills/validate-agent-guidance.mjs @@ -227,8 +227,8 @@ 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 + /^[\w.-]+@(?:[a-z\d](?:[a-z\d-]*[a-z\d])?\.)+[a-z]{2,}$/iu.test( + token.replace(/[.,;:!?\)\]\}"'”’]+$/gu, '') ) ) continue; diff --git a/tools/skills/validate-agent-guidance.test.mjs b/tools/skills/validate-agent-guidance.test.mjs index 8bfd3c2fd..be1360d8c 100644 --- a/tools/skills/validate-agent-guidance.test.mjs +++ b/tools/skills/validate-agent-guidance.test.mjs @@ -1747,3 +1747,23 @@ test('srcdoc SVG use keeps its own anchors', async (t) => { [] ); }); + +for (const suffix of [').', '];', '”']) { + test(`federated handle accepts closing punctuation: ${suffix}`, async (t) => { + assert.deepEqual( + await diagnostics(t, { + 'AGENTS.md': 'Contact (@alice@example.social' + suffix, + }), + [] + ); + }); +} +test('multiple at-signs cannot disguise an adjacent document import', async (t) => { + assert.ok( + ( + await diagnostics(t, { + 'CLAUDE.md': '@AGENTS.md\n\n@guide.md@alice@example.social', + }) + ).some((message) => message.includes('additional or inline')) + ); +});