mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
fix(agents): parse JSONC and constrain package exemptions
This commit is contained in:
1 parent
231ec0a513
commit
73e98bd00b
4 files changed
+71
-13
No files matched your search
@@ -53,12 +53,13 @@ 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.
|
||||
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.
|
||||
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.
|
||||
The Nx test hash includes `marked`, `parse5`, `github-slugger` and `parse-srcset`
|
||||
The Nx test hash includes `marked`, `parse5`, `github-slugger`, `parse-srcset` and `typescript`
|
||||
so dependency changes invalidate parser coverage.
|
||||
It cannot prove semantic equivalence; review changed contracts as well.
|
||||
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
"marked",
|
||||
"parse5",
|
||||
"github-slugger",
|
||||
"parse-srcset"
|
||||
"parse-srcset",
|
||||
"typescript"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import ts from 'typescript';
|
||||
import { readFile, realpath } from 'node:fs/promises';
|
||||
import { dirname, isAbsolute, relative, resolve, sep } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
@@ -67,10 +68,20 @@ async function validateReference(
|
||||
}
|
||||
}
|
||||
|
||||
async function packageScopes(rootDir) {
|
||||
async function packageMentions(rootDir) {
|
||||
async function readJson(path) {
|
||||
try {
|
||||
return JSON.parse(await readFile(resolve(rootDir, path), 'utf8'));
|
||||
const text = await readFile(resolve(rootDir, path), 'utf8');
|
||||
if (path !== 'tsconfig.base.json') return JSON.parse(text);
|
||||
const parsed = ts.parseConfigFileTextToJson(path, text);
|
||||
if (parsed.error)
|
||||
throw new Error(
|
||||
ts.flattenDiagnosticMessageText(
|
||||
parsed.error.messageText,
|
||||
'\n'
|
||||
)
|
||||
);
|
||||
return parsed.config;
|
||||
} catch (error) {
|
||||
if (error.code === 'ENOENT') return {};
|
||||
throw error;
|
||||
@@ -85,17 +96,32 @@ async function packageScopes(rootDir) {
|
||||
...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('/')))
|
||||
);
|
||||
const declared = names
|
||||
.filter((name) => /^@[^/]+\//u.test(name))
|
||||
.map((name) => name.slice(1));
|
||||
const scopes = new Set(declared.map((name) => name.split('/')[0]));
|
||||
return (raw) => {
|
||||
const token = raw.replace(/[.,;:)"'\]}]+$/u, '');
|
||||
if (
|
||||
token.split(/[\/\\]/u).some((part) => part === '.' || part === '..')
|
||||
)
|
||||
return false;
|
||||
if (/\.(?:md|mdx|txt|json|ya?ml|html?)$/iu.test(token)) return false;
|
||||
if (token.endsWith('/*') && scopes.has(token.slice(0, -2))) return true;
|
||||
return declared.some((name) => {
|
||||
const star = name.indexOf('*');
|
||||
return star < 0
|
||||
? token === name
|
||||
: token.startsWith(name.slice(0, star)) &&
|
||||
token.endsWith(name.slice(star + 1));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export async function validateAgentGuidance({ rootDir }) {
|
||||
rootDir = await realpath(rootDir);
|
||||
const diagnostics = [];
|
||||
const scopes = await packageScopes(rootDir);
|
||||
const isPackageMention = await packageMentions(rootDir);
|
||||
for (const source of SURFACES) {
|
||||
let markdown;
|
||||
try {
|
||||
@@ -129,7 +155,7 @@ export async function validateAgentGuidance({ rootDir }) {
|
||||
.map((match) => match[1])
|
||||
.filter(
|
||||
(token) =>
|
||||
!scopes.has(token.split('/')[0]) &&
|
||||
!isPackageMention(token) &&
|
||||
(/[./\\]/u.test(token) ||
|
||||
/^(?:LICENSE|Makefile|Dockerfile|AGENTS|CLAUDE)(?:$|[.,;)])/u.test(
|
||||
token
|
||||
|
||||
@@ -722,3 +722,33 @@ test('declared scoped dependencies and aliases are not inline imports', async (t
|
||||
[]
|
||||
);
|
||||
});
|
||||
|
||||
test('TypeScript scope discovery supports JSONC', async (t) => {
|
||||
assert.deepEqual(
|
||||
await diagnostics(t, {
|
||||
'tsconfig.base.json':
|
||||
'{ // comment\n "compilerOptions": { "paths": { "@custom/*": ["libs/*"], }, }, }',
|
||||
'AGENTS.md': 'Use @custom/services.',
|
||||
}),
|
||||
[]
|
||||
);
|
||||
});
|
||||
for (const target of [
|
||||
'angular/../docs/example.md',
|
||||
'angular/missing.md',
|
||||
'angular/undeclared',
|
||||
]) {
|
||||
test(`declared scopes do not exempt undeclared imports: ${target}`, async (t) => {
|
||||
assert.match(
|
||||
(
|
||||
await diagnostics(t, {
|
||||
'package.json': JSON.stringify({
|
||||
dependencies: { '@angular/core': '*' },
|
||||
}),
|
||||
'CLAUDE.md': '@AGENTS.md\n\nRead @' + target,
|
||||
})
|
||||
).join('\n'),
|
||||
/imports are not allowed/
|
||||
);
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user