fix(agents): support package versions and source fragments

This commit is contained in:
4gray committed 2026-09-20 22:01:32 +02:00
1 parent 795946d2a0
commit e1bc4dee60
3 files changed
+55 -2

No files matched your search

+3
View File
@@ -60,7 +60,10 @@ Declared scoped dependencies, scope wildcards and matching TypeScript path alias
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.
Declared package mentions may include a version or dist-tag qualifier.
Markdown destinations decode HTML entities before URI parsing, matching rendered links.
Heading-anchor lookup is limited to Markdown targets. Source-file line fragments,
PDF page fragments and other non-Markdown fragments retain file-existence checks.
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.
+16 -2
View File
@@ -1,6 +1,13 @@
import ts from 'typescript';
import { readFile, realpath } from 'node:fs/promises';
import { dirname, isAbsolute, relative, resolve, sep } from 'node:path';
import {
dirname,
extname,
isAbsolute,
relative,
resolve,
sep,
} from 'node:path';
import { fileURLToPath } from 'node:url';
import {
guidanceAnchors as anchors,
@@ -55,6 +62,9 @@ async function validateReference(
if (
anchor &&
!image &&
/^(?:\.md|\.markdown|\.mdown|\.mkd|\.mdx)$/iu.test(
extname(actual)
) &&
!anchors(await readFile(actual, 'utf8')).has(anchor)
) {
return `${source}: missing anchor "${anchor}" in ${target}`;
@@ -104,7 +114,11 @@ async function packageMentions(rootDir) {
.map((name) => name.slice(1));
const scopes = new Set(declared.map((name) => name.split('/')[0]));
return (raw) => {
const token = raw.replace(/[.,;:)"'\]}]+$/u, '');
let token = raw.replace(/[.,;:)"'\]}]+$/u, '');
token = token.replace(
/^([^/]+\/[^/@]+)@(?:[~^]?\d[\w.+-]*|[a-z][\w-]*)$/iu,
'$1'
);
if (
token.split(/[\/\\]/u).some((part) => part === '.' || part === '..')
)
@@ -811,3 +811,39 @@ for (const filename of [
);
});
}
for (const version of ['22.1.6', '^22.1.6', 'next']) {
test(`declared package can include version ${version}`, async (t) => {
assert.deepEqual(
await diagnostics(t, {
'package.json': JSON.stringify({
dependencies: { '@angular/core': '*' },
}),
'AGENTS.md': 'Use @angular/core@' + version,
}),
[]
);
});
}
for (const [path, fragment] of [
['apps/example.ts', 'L20'],
['docs/guide.pdf', 'page=3'],
]) {
test(`non-Markdown fragments are not headings: ${path}`, async (t) => {
assert.deepEqual(
await diagnostics(t, {
'AGENTS.md': `[Target](${path}#${fragment})`,
[path]: 'fixture',
}),
[]
);
assert.match(
(
await diagnostics(t, {
'AGENTS.md': `[Target](${path}#${fragment})`,
})
).join('\n'),
/does not exist/
);
});
}