diff --git a/AGENTS.md b/AGENTS.md index 93d1abdca..b461b300c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -108,7 +108,7 @@ use repository documentation and available CLI discovery. ## General Guidelines for working with Nx - For workspace exploration, use the `nx-workspace` skill when available; - otherwise inspect `project.json`, `pnpm nx show projects` and `pnpm nx graph`. + otherwise inspect project-local project.json files, `pnpm nx show projects` and `pnpm nx graph`. - Run project tasks through local `pnpm nx`, not a global Nx installation. - Use the Nx MCP server when available; otherwise use CLI discovery. - Check `node_modules/@nx//PLUGIN.md` for plugin guidance when present. diff --git a/docs/development/agent-workflow.md b/docs/development/agent-workflow.md index cba31b279..366d26c03 100644 --- a/docs/development/agent-workflow.md +++ b/docs/development/agent-workflow.md @@ -31,6 +31,11 @@ Run `pnpm run agents:validate` after guidance changes. It checks line/byte budge 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. +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. 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. diff --git a/docs/maintenance/agent-context-map.md b/docs/maintenance/agent-context-map.md index 19a9f8d90..ab14e114d 100644 --- a/docs/maintenance/agent-context-map.md +++ b/docs/maintenance/agent-context-map.md @@ -11,7 +11,7 @@ are not prerequisites for reading repository contracts. | Area / code ownership | Canonical documents | Repository skill | | --- | --- | --- | -| Bootstrap, project placement, dependencies, aliases and lint configuration; root Nx config and `project.json` files | [Nx boundaries](../architecture/nx-workspace-boundaries.md), [security overrides](../architecture/dependency-security-overrides.md) | [Nx architecture](../../.codex/skills/iptvnator-nx-architecture/SKILL.md) | +| Bootstrap, project placement, dependencies, aliases and lint configuration; root Nx config and project-local project.json files | [Nx boundaries](../architecture/nx-workspace-boundaries.md), [security overrides](../architecture/dependency-security-overrides.md) | [Nx architecture](../../.codex/skills/iptvnator-nx-architecture/SKILL.md) | | Angular conventions; docs and skills maintenance | [Agent workflow](../development/agent-workflow.md) | Use the area's skill below | | Unit, E2E, lint and coverage; `tools/coverage` | [Validation map](../architecture/validation-map.md) | Use the area's validation section | | Electron entry/events/preload and CDP; `apps/electron-backend` | [Debugging and trace flags](../development/electron-debugging.md), [Electron security](../architecture/electron-security.md) | Use the available global electron skill for automation | diff --git a/tools/skills/agent-guidance-markdown.mjs b/tools/skills/agent-guidance-markdown.mjs index b2a0cc45d..7d2a68c8b 100644 --- a/tools/skills/agent-guidance-markdown.mjs +++ b/tools/skills/agent-guidance-markdown.mjs @@ -60,15 +60,26 @@ export function guidanceAnchors(markdown) { } function isLiteralRepositoryPath(token) { - const local = - /^(?:apps|libs|docs|tools|patches|\.codex|\.claude|\.github|\.changes|\.plans)\//u.test( + // A typo in the directory or a new root filename must still be checked. + // Exclude recognizable prose/code forms instead of allowlisting paths. + if (/^(?:@|--|[a-z][a-z\d+.-]*:|\/\/)/iu.test(token)) return false; + 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. + if ( + /^(?:window|document|console|process|globalThis|this|Math|JSON|Object|Array|Promise|Reflect|Symbol)\.[\p{L}_][\p{L}\p{N}_]*$/u.test( token - ) || - /^(?:AGENTS\.md|CLAUDE\.md|README\.md|package\.json|pnpm-lock\.yaml|pnpm-workspace\.yaml|nx\.json|tsconfig\.base\.json|eslint\.config\.mjs|\.nvmrc)$/u.test( - token - ); + ) + ) + return false; + const path = token.split('#')[0]; return ( - local && !/[\s*?[\]{}<>|]/u.test(token) && !token.includes('YYYY-MM-DD') + path.includes('/') || + /^(?:\.[\p{L}\p{N}_-]+|[\p{L}\p{N}_-][\p{L}\p{N}_.-]*\.[\p{L}][\p{L}\p{N}_-]*)$/u.test( + path + ) ); } diff --git a/tools/skills/validate-agent-guidance.test.mjs b/tools/skills/validate-agent-guidance.test.mjs index a9669c411..94c9e0b02 100644 --- a/tools/skills/validate-agent-guidance.test.mjs +++ b/tools/skills/validate-agent-guidance.test.mjs @@ -165,6 +165,80 @@ test('literal root paths validate without interpreting commands or globs', async /does not exist/ ); }); +for (const path of [ + 'lib/definitely-missing.ts', + 'services/definitely-missing.ts', + 'electron-builder.json', + 'custom.config-format', + '.custom-config', + 'arbitrary/directory', +]) { + test(`checks generic literal repository path ${path}`, async (t) => { + assert.match( + (await diagnostics(t, { [map]: '`' + path + '`' })).join('\n'), + /does not exist/ + ); + assert.deepEqual( + await diagnostics(t, { [map]: '`' + path + '`', [path]: '' }), + [] + ); + }); +} +test('AGENTS rejects the reported unlisted directory and root filename', async (t) => { + const errors = await diagnostics(t, { + 'AGENTS.md': + '`services/definitely-missing.ts` and `electron-builder.json`', + }); + assert.equal(errors.length, 2); + assert.match( + errors.join('\n'), + /does not exist: services\/definitely-missing\.ts/ + ); + assert.match(errors.join('\n'), /does not exist: electron-builder\.json/); +}); +test('literal paths retain fragment checks', async (t) => { + assert.deepEqual( + await diagnostics(t, { [map]: '`docs/example.md#hello-world`' }), + [] + ); + assert.match( + (await diagnostics(t, { [map]: '`docs/example.md#missing`' })).join( + '\n' + ), + /missing anchor/ + ); +}); +test('literal detection excludes commands, templates, URLs, aliases and code expressions', async (t) => { + const examples = [ + 'pnpm nx test web', + 'node tools/example.mjs', + '--config=missing.json', + 'docs/*.md', + 'docs/{one,two}.md', + 'docs/.md', + '.plans/YYYY-MM-DD-short-topic.md', + 'docs/.../example.md', + 'https://example.com/guide.md', + 'file:///tmp/example.md', + '@iptvnator/services', + '@angular/core', + 'node:fs/promises', + 'window.electron', + 'document.body', + 'process.env', + 'this.store', + 'store.setState()', + 'source?.id', + 'a/b+c', + '$HOME/example.md', + ]; + assert.deepEqual( + await diagnostics(t, { + [map]: examples.map((value) => '`' + value + '`').join(' '), + }), + [] + ); +}); test('rejects traversal and symlink escapes', async (t) => { const rootDir = await fixture(t, { 'AGENTS.md': '[Escape](../outside.md) [Symlink](outside)',