fix(agents): handle fenced imports and encoded paths

This commit is contained in:
4gray committed 2026-09-20 19:30:55 +02:00
1 parent 46d7f58a6c
commit 1206f0d255
4 files changed
+62 -7

No files matched your search

+3
View File
@@ -37,6 +37,9 @@ Write generic filenames as prose; commands, templates, globs, URLs, package
aliases and dotted code symbols are excluded. Bare dotted names with conventional
file suffixes (such as .md, .json or .ts) are treated as filenames. Use a `./`
prefix or Markdown link for other ambiguous filenames that resemble code symbols.
Multi-part dotfiles are path candidates too. Link paths and fragments are decoded
separately so encoded filename delimiters stay in the filename. Fenced examples
do not count as root guidance imports or satisfy the required Claude import.
Heading anchors are derived from parsed text, not HTML sanitization. The Nx test
hash includes `marked` so dependency changes invalidate parser coverage.
It cannot prove semantic equivalence; review changed contracts as well.
+1 -1
View File
@@ -83,7 +83,7 @@ function isLiteralRepositoryPath(token) {
return false;
return (
path.includes('/') ||
/^(?:\.[\p{L}\p{N}_-]+|[\p{L}\p{N}_-][\p{L}\p{N}_.-]*\.[\p{L}][\p{L}\p{N}_-]*)$/u.test(
/^(?:\.[\p{L}\p{N}_-][\p{L}\p{N}_.-]*|[\p{L}\p{N}_-][\p{L}\p{N}_.-]*\.[\p{L}][\p{L}\p{N}_-]*)$/u.test(
path
)
);
+10 -6
View File
@@ -50,14 +50,17 @@ async function validateReference(
if (unresolvedReference !== undefined)
return `${source}: unresolved Markdown reference "${unresolvedReference}"`;
if (/^(?:[a-z][a-z\d+.-]*:|\/\/)/iu.test(target)) return;
let decoded;
let path;
let anchor;
try {
decoded = decodeURIComponent(target);
const hash = target.indexOf('#');
const pathAndQuery = hash < 0 ? target : target.slice(0, hash);
path = decodeURIComponent(pathAndQuery.split('?')[0]);
anchor =
hash < 0 ? undefined : decodeURIComponent(target.slice(hash + 1));
} catch {
return `${source}: malformed local link: ${target}`;
}
const [pathAndQuery, anchor] = decoded.split('#');
const path = pathAndQuery.split('?')[0];
const absolute = path
? resolve(literal ? rootDir : dirname(resolve(rootDir, source)), path)
: resolve(rootDir, source);
@@ -106,10 +109,11 @@ export async function validateAgentGuidance({ rootDir }) {
diagnostics.push(
`${source}: at most ${maxBytes} UTF-8 bytes allowed (received ${bytes})`
);
const imports = [...markdown.matchAll(/^\s*@([^\s]+)\s*$/gmu)].map(
const unfenced = withoutFences(markdown);
const imports = [...unfenced.matchAll(/^\s*@([^\s]+)\s*$/gmu)].map(
(match) => match[1]
);
const prose = withoutFences(markdown).replace(/`[^`\n]+`/gu, '');
const prose = unfenced.replace(/`[^`\n]+`/gu, '');
const inlineImports = [...prose.matchAll(/(?:^|[\s(])@([^\s]+)/gu)]
.map((match) => match[1])
.filter(
@@ -172,6 +172,8 @@ for (const path of [
'missing.scss',
'custom.config-format',
'.custom-config',
'.env.local',
'.eslintrc.json',
'arbitrary/directory',
]) {
test(`checks generic literal repository path ${path}`, async (t) => {
@@ -406,3 +408,49 @@ test('explicit HTML anchors work but anchors in code examples do not', async (t)
});
assert.match(errors.join('\n'), /missing anchor/);
});
for (const source of ['AGENTS.md', 'CLAUDE.md']) {
test(`${source}: fenced decorators are not root imports`, async (t) => {
const prefix = source === 'CLAUDE.md' ? '@AGENTS.md\n' : '';
assert.deepEqual(
await diagnostics(t, {
[source]:
prefix + '```ts\n@Injectable()\n@docs/missing.md\n```\n',
}),
[]
);
});
}
test('a fenced import cannot satisfy the required CLAUDE import', async (t) => {
assert.match(
(
await diagnostics(t, {
'CLAUDE.md': '```text\n@AGENTS.md\n```\n',
})
).join('\n'),
/standalone @AGENTS.md/
);
});
for (const [encoded, filename] of [
['guide%23one.md', 'guide#one.md'],
['guide%3Fone.md', 'guide?one.md'],
]) {
test(`encoded filename delimiters stay in path: ${encoded}`, async (t) => {
assert.deepEqual(
await diagnostics(t, {
'AGENTS.md': `[Doc](docs/${encoded}?view=1#hello%2Dworld)`,
[`docs/${filename}`]: '# Hello World\n',
}),
[]
);
assert.match(
(
await diagnostics(t, {
'AGENTS.md': `[Doc](docs/${encoded}#missing)`,
[`docs/${filename}`]: '# Hello World\n',
})
).join('\n'),
/missing anchor/
);
});
}