mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-12 03:56:15 -08:00
fix(agents): validate extensionless imports and version comparators
This commit is contained in:
1 parent
cb42c74f43
commit
505fa356ab
3 files changed
+55
-16
No files matched your search
@@ -62,11 +62,12 @@ HTML video, audio, source and track `src` assets and video posters use the same
|
||||
existence checks as images. Entity decoding uses full HTML text/attribute rules,
|
||||
including references whose semicolon may be omitted.
|
||||
Inline guidance imports are rejected after punctuation as well as whitespace.
|
||||
Extensionless inline candidates are also imports when they resolve to repository files.
|
||||
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.
|
||||
Declared packages also permit safe subpaths; exact aliases stay exact.
|
||||
Declared package mentions may include a version or dist-tag qualifier.
|
||||
Declared package mentions may include a version (including semver comparators) or dist-tag qualifier.
|
||||
Qualifier handling includes unscoped names; terminal sentence punctuation is
|
||||
removed before matching a declared package, as are straight/curly apostrophe possessives.
|
||||
Markdown destinations decode HTML entities before URI parsing, matching rendered links.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import ts from 'typescript';
|
||||
import { readFile, realpath } from 'node:fs/promises';
|
||||
import { readFile, realpath, stat } from 'node:fs/promises';
|
||||
import {
|
||||
dirname,
|
||||
extname,
|
||||
@@ -117,7 +117,7 @@ async function packageMentions(rootDir) {
|
||||
let token = raw.replace(/[?!.,;:)"'\]}]+$/u, '');
|
||||
token = token.replace(/['’]s$/iu, '');
|
||||
token = token.replace(
|
||||
/^([^/@]+(?:\/[^/@]+)?)@(?:[~^]?\d[\w.+-]*|[a-z][\w-]*)$/iu,
|
||||
/^([^/@]+(?:\/[^/@]+)?)@(?:(?:[~^]|[<>]=?|=)?\d[\w.+-]*|[a-z][\w-]*)$/iu,
|
||||
'$1'
|
||||
);
|
||||
if (
|
||||
@@ -170,18 +170,30 @@ export async function validateAgentGuidance({ rootDir }) {
|
||||
const unfenced = guidanceProse(markdown);
|
||||
const imports = guidanceStandaloneImports(markdown);
|
||||
const prose = unfenced.replace(/`[^`\n]+`/gu, '');
|
||||
const inlineImports = [
|
||||
...prose.matchAll(/(?:^|[^\p{L}\p{N}_@])@([^\s]+)/gu),
|
||||
]
|
||||
.map((match) => match[1])
|
||||
.filter(
|
||||
(token) =>
|
||||
!isPackageMention(token) &&
|
||||
(/[./\\]/u.test(token) ||
|
||||
/^(?:LICENSE|Makefile|Dockerfile|AGENTS|CLAUDE)(?:$|[.,;)])/u.test(
|
||||
token
|
||||
))
|
||||
);
|
||||
const inlineImports = [];
|
||||
for (const match of prose.matchAll(
|
||||
/(?:^|[^\p{L}\p{N}_@])@([^\s]+)/gu
|
||||
)) {
|
||||
const token = match[1];
|
||||
if (isPackageMention(token)) continue;
|
||||
if (
|
||||
/[./\\]/u.test(token) ||
|
||||
/^(?:LICENSE|Makefile|Dockerfile|AGENTS|CLAUDE)(?:$|[.,;)])/u.test(
|
||||
token
|
||||
)
|
||||
) {
|
||||
inlineImports.push(token);
|
||||
continue;
|
||||
}
|
||||
const candidate = token.replace(/[?!.,;:)"'\]}]+$/u, '');
|
||||
try {
|
||||
if ((await stat(resolve(rootDir, candidate))).isFile())
|
||||
inlineImports.push(token);
|
||||
} catch (error) {
|
||||
if (error.code !== 'ENOENT' && error.code !== 'ENOTDIR')
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
if (
|
||||
inlineImports.some((token) => token !== 'AGENTS.md') ||
|
||||
(source === 'AGENTS.md' && inlineImports.length) ||
|
||||
|
||||
@@ -812,7 +812,15 @@ for (const filename of [
|
||||
});
|
||||
}
|
||||
|
||||
for (const version of ['22.1.6', '^22.1.6', 'next']) {
|
||||
for (const version of [
|
||||
'22.1.6',
|
||||
'^22.1.6',
|
||||
'next',
|
||||
'>=22.0.0',
|
||||
'<=22.0.0',
|
||||
'>22.0.0',
|
||||
'<22.0.0',
|
||||
]) {
|
||||
test(`declared package can include version ${version}`, async (t) => {
|
||||
assert.deepEqual(
|
||||
await diagnostics(t, {
|
||||
@@ -1005,3 +1013,21 @@ for (const suffix of ["'s", '’s']) {
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
for (const suffix of ['', '.']) {
|
||||
test(`existing extensionless inline import is rejected: ${suffix}`, async (t) => {
|
||||
const result = await diagnostics(t, {
|
||||
'CLAUDE.md': '@AGENTS.md\n\nRead @INSTRUCTIONS' + suffix,
|
||||
INSTRUCTIONS: 'Additional guidance',
|
||||
});
|
||||
assert.ok(
|
||||
result.some((message) => message.includes('additional or inline'))
|
||||
);
|
||||
});
|
||||
}
|
||||
test('ordinary unknown handle is not a file import', async (t) => {
|
||||
assert.deepEqual(
|
||||
await diagnostics(t, { 'AGENTS.md': 'Ask @maintainer' }),
|
||||
[]
|
||||
);
|
||||
});
|
||||
Reference in new issue
Block a user