fix(agents): decode link entities and allow package subpaths

This commit is contained in:
4gray committed 2026-09-20 21:35:52 +02:00
1 parent 73e98bd00b
commit f2f7a94857
4 files changed
+46 -4

No files matched your search

+2
View File
@@ -56,6 +56,8 @@ Inline guidance imports are rejected after punctuation as well as whitespace.
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. TypeScript configuration is parsed as JSONC.
Declared packages also permit safe subpaths; exact aliases stay exact.
Markdown destinations decode HTML entities before URI parsing, matching rendered links.
The import scan includes visible HTML text and excludes code and non-rendered containers.
Image source sets use `parse-srcset` to check each candidate URL. Root-relative
literals never suppress source-relative definition checks.
+2 -2
View File
@@ -198,8 +198,8 @@ export function guidanceReferences(markdown, includeLiterals) {
}
markdownLexer.walkTokens(markdownLexer.lexer(markdown), (token) => {
if (['link', 'image'].includes(token.type))
add(token.href, false, token.type === 'image');
if (token.type === 'def') definitions.push(token.href);
add(decodeEntities(token.href), false, token.type === 'image');
if (token.type === 'def') definitions.push(decodeEntities(token.href));
if (token.type === 'html') html.push(token.raw);
if (token.type === 'unresolved-reference')
result.push({ unresolvedReference: token.label });
+7 -2
View File
@@ -89,11 +89,14 @@ async function packageMentions(rootDir) {
}
const manifest = await readJson('package.json');
const config = await readJson('tsconfig.base.json');
const names = [
const packages = [
...Object.keys(manifest.dependencies ?? {}),
...Object.keys(manifest.devDependencies ?? {}),
...Object.keys(manifest.optionalDependencies ?? {}),
...Object.keys(manifest.peerDependencies ?? {}),
];
const names = [
...packages,
...Object.keys(config.compilerOptions?.paths ?? {}),
];
const declared = names
@@ -111,7 +114,9 @@ async function packageMentions(rootDir) {
return declared.some((name) => {
const star = name.indexOf('*');
return star < 0
? token === name
? token === name ||
(packages.includes(`@${name}`) &&
token.startsWith(`${name}/`))
: token.startsWith(name.slice(0, star)) &&
token.endsWith(name.slice(star + 1));
});
@@ -752,3 +752,38 @@ for (const target of [
);
});
}
test('Markdown destination entities resolve rendered filenames', async (t) => {
for (const reference of [
'[Guide](docs/a&amp;b.md)',
'[Guide][ref]\n\n[ref]: docs/a&amp;b.md',
]) {
assert.deepEqual(
await diagnostics(t, {
'AGENTS.md': reference,
'docs/a&b.md': '# Guide',
}),
[]
);
assert.match(
(
await diagnostics(t, {
'AGENTS.md': reference,
'docs/a&amp;b.md': '# Wrong name',
})
).join('\n'),
/does not exist/
);
}
});
test('declared packages allow safe subpaths', async (t) => {
assert.deepEqual(
await diagnostics(t, {
'package.json': JSON.stringify({
dependencies: { '@angular/core': '*' },
}),
'AGENTS.md': 'Use @angular/core/testing.',
}),
[]
);
});