fix(agents): distinguish code symbols and shortcut images

This commit is contained in:
4gray committed 2026-09-20 19:07:24 +02:00
1 parent a10045f0b2
commit 989ba24ecf
3 files changed
+38 -10

No files matched your search

+4 -3
View File
@@ -30,12 +30,13 @@ Later normal edits maintain the canonical docs, not duplicate historical prose.
Run `pnpm run agents:validate` after guidance changes. It checks line/byte budgets,
root imports and local navigation links/anchors, including migration destinations.
Markdown navigation is parsed with the already-declared `marked` dependency;
undefined explicit reference links are errors, and code examples are excluded.
undefined explicit references (including shortcut images) are errors, and code examples are excluded.
Backticked concrete paths in root guidance and the context map are checked from
the repository root, including unknown top-level directories and filenames.
Write generic filenames as prose; commands, templates, globs, URLs, package
aliases and recognized JavaScript globals are excluded. Use Markdown links for
ambiguous filenames that resemble code symbols.
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.
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.
+13 -7
View File
@@ -8,12 +8,16 @@ const markdownLexer = new Marked({
if (token?.type !== 'text') return token;
// Marked otherwise turns unresolved references into ordinary text.
// Reuse its grammar, retaining only explicit full/collapsed forms;
// Retain explicit full/collapsed forms and shortcut images;
// a bare [word] without a definition remains ordinary prose.
const full = this.rules.inline.reflink.exec(source);
const collapsed = this.rules.inline.nolink.exec(source);
const match =
full ?? (collapsed?.[0].endsWith('[]') ? collapsed : undefined);
full ??
(collapsed?.[0].endsWith('[]') ||
collapsed?.[0].startsWith('![')
? collapsed
: undefined);
if (!match) return token;
return {
type: 'unresolved-reference',
@@ -66,15 +70,17 @@ function isLiteralRepositoryPath(token) {
if (/[^\p{L}\p{N}_./#-]/u.test(token)) return false;
if (token.includes('YYYY-MM-DD') || /(?:^|\/)\.\.\.(?:\/|$)/u.test(token))
return false;
// Recognized JavaScript globals are code-symbol exceptions, not a claim
// that every dotted code expression can be distinguished from a filename.
const path = token.split('#')[0];
// Bare dotted identifiers are ambiguous. Recognize conventional file
// suffixes; other filenames can be made explicit with ./ or a Markdown link.
// This applies to user-defined symbols as well as JavaScript globals.
if (
/^(?:window|document|console|process|globalThis|this|Math|JSON|Object|Array|Promise|Reflect|Symbol)\.[\p{L}_][\p{L}\p{N}_]*$/u.test(
token
/^[\p{L}_][\p{L}\p{N}_]*(?:\.[\p{L}_][\p{L}\p{N}_]*)+$/u.test(path) &&
!/\.(?:md|mdx|json|jsonc|ya?ml|[cm]?[jt]sx?|html?|[sc]ss|sass|less|toml|xml|txt|sh|py|sql|svg|png|jpe?g|webp|gif|m3u8?|conf|ini|lock)$/iu.test(
path
)
)
return false;
const path = token.split('#')[0];
return (
path.includes('/') ||
/^(?:\.[\p{L}\p{N}_-]+|[\p{L}\p{N}_-][\p{L}\p{N}_.-]*\.[\p{L}][\p{L}\p{N}_-]*)$/u.test(
@@ -196,6 +196,21 @@ test('AGENTS rejects the reported unlisted directory and root filename', async (
);
assert.match(errors.join('\n'), /does not exist: electron-builder\.json/);
});
test('ambiguous dotted filenames can be explicitly marked as paths', async (t) => {
for (const reference of ['`./custom.symbol`', '[File](custom.symbol)']) {
assert.match(
(await diagnostics(t, { [map]: reference })).join('\n'),
/does not exist/
);
}
assert.deepEqual(
await diagnostics(t, {
[map]: '`./custom.symbol`',
'custom.symbol': '',
}),
[]
);
});
test('literal paths retain fragment checks', async (t) => {
assert.deepEqual(
await diagnostics(t, { [map]: '`docs/example.md#hello-world`' }),
@@ -224,6 +239,11 @@ test('literal detection excludes commands, templates, URLs, aliases and code exp
'@angular/core',
'node:fs/promises',
'window.electron',
'Date.now',
'String.raw',
'Buffer.from',
'customStore.selectedItem',
'process.env.NODE_ENV',
'document.body',
'process.env',
'this.store',
@@ -304,6 +324,7 @@ for (const reference of [
'[**Guide**][missing]',
'[Guide `code`][missing]',
'![Guide][missing]',
'![Diagram]',
]) {
test(`diagnoses unresolved explicit reference ${reference}`, async (t) => {
assert.match(