fix(agents): recognize package scopes and route source work

This commit is contained in:
4gray committed 2026-09-20 21:11:28 +02:00
1 parent d6a35cfa31
commit 231ec0a513
4 files changed
+48 -2

No files matched your search

+2
View File
@@ -53,6 +53,8 @@ as Markdown links, including decoded attributes and fragment validation.
Image references check file existence without interpreting image fragments as
Markdown headings; document links keep anchor checks even when sharing a target.
Inline guidance imports are rejected after punctuation as well as whitespace.
Scopes declared by package dependencies or TypeScript path aliases are recognized
as package/alias mentions rather than additional file imports.
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.
+1 -1
View File
@@ -22,7 +22,7 @@ are not prerequisites for reading repository contracts.
| Area / code ownership | Required contracts | Repository skill |
| --- | --- | --- |
| SQLite schema/startup; `libs/shared/database`; Electron DB events/workers/operations | [DB worker](../architecture/sqlite-db-worker.md), [migration ownership and tests](../../libs/shared/database/README.md) | [SQLite worker](../../.codex/skills/iptvnator-sqlite-db-worker/SKILL.md) |
| M3U import/state/player, XMLTV, source lifecycle, startup readiness and OS file opening; `libs/m3u-state`, `libs/playlist`, `libs/epg` | [M3U module](../architecture/m3u-playlist-module.md) | Read the contract directly |
| M3U import/state/player, XMLTV, source lifecycle, startup readiness and OS file opening; `libs/m3u-state`, `libs/playlist`, `libs/epg` | [M3U module](../architecture/m3u-playlist-module.md), [adding sources across layers](../development/agent-workflow.md#adding-behavior-across-layers) | Read both contracts when adding a source |
| Xtream API/store/data sources and routed views; `libs/portal/xtream` | [Xtream compatibility](../architecture/xtream-portal-compatibility.md), [category management](../architecture/category-management.md), [detail navigation](../architecture/portal-detail-navigation.md) | [Xtream](../../.codex/skills/xtream-electron/SKILL.md) |
| Stalker/Ministra protocol, identity, sessions and routed views; `libs/portal/stalker` | [Stalker portal](../architecture/stalker-portal.md), [Stalker EPG](../architecture/stalker-epg.md) for EPG work, [store API baseline](../architecture/stalker-store-api-baseline.md) for store API changes | [Stalker](../../.codex/skills/stalker-portal/SKILL.md) |
| Browser runtime, HTTP proxies, redirects and backend networking; `apps/web-backend`, `libs/shared/host-health` | [PWA/self-hosting](../architecture/pwa-self-hosted.md), [connectivity guard](../architecture/host-connectivity-guard.md), [Electron security](../architecture/electron-security.md) for desktop boundary changes | Read the affected runtime contract |
+27 -1
View File
@@ -67,9 +67,35 @@ async function validateReference(
}
}
async function packageScopes(rootDir) {
async function readJson(path) {
try {
return JSON.parse(await readFile(resolve(rootDir, path), 'utf8'));
} catch (error) {
if (error.code === 'ENOENT') return {};
throw error;
}
}
const manifest = await readJson('package.json');
const config = await readJson('tsconfig.base.json');
const names = [
...Object.keys(manifest.dependencies ?? {}),
...Object.keys(manifest.devDependencies ?? {}),
...Object.keys(manifest.optionalDependencies ?? {}),
...Object.keys(manifest.peerDependencies ?? {}),
...Object.keys(config.compilerOptions?.paths ?? {}),
];
return new Set(
names
.filter((name) => /^@[^/]+\//u.test(name))
.map((name) => name.slice(1, name.indexOf('/')))
);
}
export async function validateAgentGuidance({ rootDir }) {
rootDir = await realpath(rootDir);
const diagnostics = [];
const scopes = await packageScopes(rootDir);
for (const source of SURFACES) {
let markdown;
try {
@@ -103,7 +129,7 @@ export async function validateAgentGuidance({ rootDir }) {
.map((match) => match[1])
.filter(
(token) =>
!token.startsWith('iptvnator/') &&
!scopes.has(token.split('/')[0]) &&
(/[./\\]/u.test(token) ||
/^(?:LICENSE|Makefile|Dockerfile|AGENTS|CLAUDE)(?:$|[.,;)])/u.test(
token
@@ -12,6 +12,9 @@ async function fixture(t, overrides = {}) {
t.after(() => rm(rootDir, { recursive: true, force: true }));
for (const [path, body] of Object.entries({
'AGENTS.md': '# Guidance\n',
'tsconfig.base.json': JSON.stringify({
compilerOptions: { paths: { '@iptvnator/*': ['libs/*'] } },
}),
'CLAUDE.md': '@AGENTS.md\n',
[map]: '# Context\n',
[migration]: '# Migration\n',
@@ -704,3 +707,18 @@ test('non-rendered HTML text and code do not introduce imports', async (t) => {
[]
);
});
test('declared scoped dependencies and aliases are not inline imports', async (t) => {
assert.deepEqual(
await diagnostics(t, {
'package.json': JSON.stringify({
dependencies: { '@angular/core': '*', '@nx/devkit': '*' },
}),
'tsconfig.base.json': JSON.stringify({
compilerOptions: { paths: { '@custom/*': ['libs/*'] } },
}),
'AGENTS.md': 'Use @angular/core, @nx/* and @custom/services.',
}),
[]
);
});