aislop 0.8.1 → 0.8.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +115 -13
- package/dist/index.js +116 -14
- package/dist/{json-7EtVVIhd.js → json-D8h2EZW6.js} +1 -1
- package/dist/mcp.js +51 -13
- package/dist/{version-B7_FRirI.js → version-BynHxO1X.js} +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -730,9 +730,14 @@ const detectOverAbstraction = async (context) => {
|
|
|
730
730
|
return diagnostics;
|
|
731
731
|
};
|
|
732
732
|
|
|
733
|
+
//#endregion
|
|
734
|
+
//#region src/engines/ai-slop/non-production-paths.ts
|
|
735
|
+
const DIR_PATTERN = /(?:^|\/)(?:scripts|bin|examples?|demos?|bench|benches|benchmarks?|fixtures?|__fixtures__|__mocks__|__tests__|vendor|_vendor|vendored|third_party|blib2to3|lib2to3|cli|cli-[\w-]+|[\w-]+-cli)\//i;
|
|
736
|
+
const BASENAME_PATTERN = /(?:^|\/)(?:benchmark|bench|demo|example|script|seed|migrate|profile|smoke|stress|load|debug|repro)[-_.][^/]*\.[mc]?[jt]sx?$|(?:^|\/)[^/]+[-_](?:benchmark|bench|demo|example)\.[mc]?[jt]sx?$/i;
|
|
737
|
+
const isNonProductionPath = (relativePath) => DIR_PATTERN.test(relativePath) || BASENAME_PATTERN.test(relativePath);
|
|
738
|
+
|
|
733
739
|
//#endregion
|
|
734
740
|
//#region src/engines/ai-slop/comments.ts
|
|
735
|
-
const NON_PRODUCTION_DIR_PATTERN$2 = /(?:^|\/)(?:scripts|bin|examples?|demos?|bench|benches|benchmarks?|fixtures?|__fixtures__|__mocks__|__tests__|vendor|_vendor|vendored|third_party|blib2to3|lib2to3)\//i;
|
|
736
741
|
const TRIVIAL_VERB_STEMS = "Import|Defin|Initializ|Setting|Set\\s+up|Setup|Return|Check|Loop|Iterat|Creat|Updat|Delet|Remov|Handl|Get|Fetch|Increment|Decrement|Writ|Runn|Run|Pars|Execut|Extract|Sav|Load|Build|Start|Stopp|Stop|Clean(?:up|\\s+up)?|Configur|Validat|Process|Queue|Fire|Emit|Dispatch|Log|Print|Render";
|
|
737
742
|
const TRIVIAL_JS_COMMENT_PATTERNS = [/\/\/\s*This (?:function|method|class|variable|constant) (?:will |is used to |is responsible for )?/i, new RegExp(`\\/\\/\\s*(?:${TRIVIAL_VERB_STEMS})(?:e|es|ing|s)?\\b`, "i")];
|
|
738
743
|
const TRIVIAL_PYTHON_COMMENT_PATTERNS = [/^#\s*This (?:function|method|class) (?:will |is used to )?/i, new RegExp(`^#\\s*(?:${TRIVIAL_VERB_STEMS})(?:e|es|ing|s)?\\b`, "i")];
|
|
@@ -789,7 +794,7 @@ const detectTrivialComments = async (context) => {
|
|
|
789
794
|
for (const filePath of files) {
|
|
790
795
|
if (isAutoGenerated(filePath)) continue;
|
|
791
796
|
const relativePath = path.relative(context.rootDirectory, filePath);
|
|
792
|
-
if (
|
|
797
|
+
if (isNonProductionPath(relativePath)) continue;
|
|
793
798
|
let content;
|
|
794
799
|
try {
|
|
795
800
|
content = fs.readFileSync(filePath, "utf-8");
|
|
@@ -825,11 +830,10 @@ const slop = (filePath, line, rule, severity, message, help, fixable) => ({
|
|
|
825
830
|
fixable
|
|
826
831
|
});
|
|
827
832
|
const LOGGER_FILE_PATTERN = /(?:^|\/)(?:logger|logging|log)\.[^/]+$/i;
|
|
828
|
-
const NON_PRODUCTION_DIR_PATTERN$1 = /(?:^|\/)(?:scripts|bin|examples?|demos?|bench|benches|benchmarks?|fixtures?|__fixtures__|__mocks__|__tests__|cli|cli-[\w-]+|[\w-]+-cli)\//;
|
|
829
833
|
const detectConsoleLeftovers = (content, relativePath, ext) => {
|
|
830
834
|
if (!JS_EXTENSIONS$4.has(ext)) return [];
|
|
831
835
|
if (LOGGER_FILE_PATTERN.test(relativePath)) return [];
|
|
832
|
-
if (
|
|
836
|
+
if (isNonProductionPath(relativePath)) return [];
|
|
833
837
|
const diagnostics = [];
|
|
834
838
|
const lines = content.split("\n");
|
|
835
839
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -879,7 +883,7 @@ const asAnyPattern = new RegExp(`\\bas\\s+any\\b`);
|
|
|
879
883
|
const doubleAssertPattern = new RegExp(`\\bas\\s+unknown\\s+as\\s+`);
|
|
880
884
|
const detectUnsafeTypePatterns = (content, relativePath, ext) => {
|
|
881
885
|
if (ext !== ".ts" && ext !== ".tsx") return [];
|
|
882
|
-
if (
|
|
886
|
+
if (isNonProductionPath(relativePath)) return [];
|
|
883
887
|
const diagnostics = [];
|
|
884
888
|
const lines = content.split("\n");
|
|
885
889
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -1448,6 +1452,27 @@ const collectJsDeps = (rootDir, jsDeps) => {
|
|
|
1448
1452
|
collectNestedManifests(rootDir, jsDeps);
|
|
1449
1453
|
return true;
|
|
1450
1454
|
};
|
|
1455
|
+
const TS_CONFIG_FILES = ["tsconfig.json", "jsconfig.json"];
|
|
1456
|
+
const buildAliasMatcher = (key) => {
|
|
1457
|
+
const starIdx = key.indexOf("*");
|
|
1458
|
+
if (starIdx === -1) return (spec) => spec === key;
|
|
1459
|
+
const before = key.slice(0, starIdx);
|
|
1460
|
+
const after = key.slice(starIdx + 1);
|
|
1461
|
+
return (spec) => spec.length >= before.length + after.length && spec.startsWith(before) && spec.endsWith(after);
|
|
1462
|
+
};
|
|
1463
|
+
const collectAliasMatchersFromConfig = (configPath, matchers) => {
|
|
1464
|
+
const opts = readJson(configPath)?.compilerOptions;
|
|
1465
|
+
if (!opts || typeof opts !== "object") return;
|
|
1466
|
+
const paths = opts.paths;
|
|
1467
|
+
if (!paths || typeof paths !== "object") return;
|
|
1468
|
+
for (const key of Object.keys(paths)) matchers.push(buildAliasMatcher(key));
|
|
1469
|
+
};
|
|
1470
|
+
const collectTsPathAliases = (rootDir) => {
|
|
1471
|
+
const matchers = [];
|
|
1472
|
+
const dirs = [rootDir, ...expandWorkspaceDirs(rootDir, readWorkspaceGlobs(rootDir, readJson(path.join(rootDir, "package.json"))))];
|
|
1473
|
+
for (const dir of dirs) for (const fname of TS_CONFIG_FILES) collectAliasMatchersFromConfig(path.join(dir, fname), matchers);
|
|
1474
|
+
return matchers;
|
|
1475
|
+
};
|
|
1451
1476
|
const addPyDep = (pyDeps, name) => {
|
|
1452
1477
|
const normalized = name.toLowerCase().replace(/_/g, "-");
|
|
1453
1478
|
pyDeps.add(normalized);
|
|
@@ -1605,10 +1630,11 @@ const extractPyImports = (content) => {
|
|
|
1605
1630
|
}
|
|
1606
1631
|
return results;
|
|
1607
1632
|
};
|
|
1608
|
-
const checkJsImport = (spec, manifest) => {
|
|
1633
|
+
const checkJsImport = (spec, manifest, tsAliasMatchers) => {
|
|
1609
1634
|
if (isJsRelativeOrAbsolute(spec)) return null;
|
|
1610
1635
|
if (isJsBuiltin(spec)) return null;
|
|
1611
1636
|
if (isJsVirtualModule(spec)) return null;
|
|
1637
|
+
if (tsAliasMatchers.some((m) => m(spec))) return null;
|
|
1612
1638
|
const pkg = packageNameFromImport(spec);
|
|
1613
1639
|
if (manifest.jsDeps.has(pkg)) return null;
|
|
1614
1640
|
if (pkg.startsWith("@types/")) {
|
|
@@ -1629,6 +1655,7 @@ const checkPyImport = (spec, manifest) => {
|
|
|
1629
1655
|
const detectHallucinatedImports = async (context) => {
|
|
1630
1656
|
const manifest = loadManifest(context.rootDirectory);
|
|
1631
1657
|
if (!manifest.hasJsManifest && !manifest.hasPyManifest) return [];
|
|
1658
|
+
const tsAliasMatchers = manifest.hasJsManifest ? collectTsPathAliases(context.rootDirectory) : [];
|
|
1632
1659
|
const diagnostics = [];
|
|
1633
1660
|
const files = getSourceFiles(context);
|
|
1634
1661
|
for (const filePath of files) {
|
|
@@ -1648,7 +1675,7 @@ const detectHallucinatedImports = async (context) => {
|
|
|
1648
1675
|
const relPath = path.relative(context.rootDirectory, filePath);
|
|
1649
1676
|
const imports = isJs ? extractJsImports(content) : extractPyImports(content);
|
|
1650
1677
|
for (const { spec, line } of imports) {
|
|
1651
|
-
const hallucinated = isJs ? checkJsImport(spec, manifest) : checkPyImport(spec, manifest);
|
|
1678
|
+
const hallucinated = isJs ? checkJsImport(spec, manifest, tsAliasMatchers) : checkPyImport(spec, manifest);
|
|
1652
1679
|
if (!hallucinated) continue;
|
|
1653
1680
|
const manifestLabel = isJs ? "package.json" : "requirements.txt / pyproject.toml / Pipfile";
|
|
1654
1681
|
diagnostics.push({
|
|
@@ -1784,7 +1811,6 @@ const PHP_DECL_START = /^\s*(?:public|private|protected|static|final|abstract|re
|
|
|
1784
1811
|
|
|
1785
1812
|
//#endregion
|
|
1786
1813
|
//#region src/engines/ai-slop/narrative-comments.ts
|
|
1787
|
-
const NON_PRODUCTION_DIR_PATTERN = /(?:^|\/)(?:scripts|bin|examples?|demos?|bench|benches|benchmarks?|fixtures?|__fixtures__|__mocks__|__tests__|vendor|_vendor|vendored|third_party|blib2to3|lib2to3)\//i;
|
|
1788
1814
|
const stripJsdocLine = (line) => line.replace(/^\s*\/\*\*+\s?/, "").replace(/\s*\*+\/\s*$/, "").replace(/^\s*\*\s?/, "").trim();
|
|
1789
1815
|
const stripLineComment = (line) => line.replace(/^\s*(?:(?:\/\/)|#)\s?/, "");
|
|
1790
1816
|
const getCommentSyntax = (ext) => {
|
|
@@ -1935,13 +1961,21 @@ const looksLikeGoDocComment = (block, ext) => {
|
|
|
1935
1961
|
if (!declMatch) return false;
|
|
1936
1962
|
return ((block.prose.find((l) => l.length > 0) ?? "").split(/\s+/)[0] ?? "") === declMatch[1];
|
|
1937
1963
|
};
|
|
1938
|
-
const DOC_INDICATOR_RE = /`[^`]+`|\|\s*[-:]+\s*\||```|\b(?:note|warning|warn|caveat|example|caution|see)
|
|
1964
|
+
const DOC_INDICATOR_RE = /`[^`]+`|\|\s*[-:]+\s*\||```|\b(?:note|warning|warn|caveat|example|caution|see):|\(e\.g\.[^)]+\)|\(i\.e\.[^)]+\)/i;
|
|
1939
1965
|
const hasDocIndicator = (block) => {
|
|
1940
1966
|
const joined = block.prose.join(" ");
|
|
1941
1967
|
if (DOC_INDICATOR_RE.test(joined)) return true;
|
|
1942
1968
|
for (const l of block.prose) if (/^[-]\s/.test(l)) return true;
|
|
1943
1969
|
return false;
|
|
1944
1970
|
};
|
|
1971
|
+
const hasPreambleSlopSignal = (block) => {
|
|
1972
|
+
const joined = block.prose.join(" ");
|
|
1973
|
+
for (const l of block.prose) {
|
|
1974
|
+
if (EXPLANATORY_OPENERS.test(l)) return true;
|
|
1975
|
+
if (JUSTIFICATION_OPENERS.some((re) => re.test(l))) return true;
|
|
1976
|
+
}
|
|
1977
|
+
return CROSS_REFERENCE_PHRASES.some((re) => re.test(joined));
|
|
1978
|
+
};
|
|
1945
1979
|
const detectNarrativeInBlock = (block, ext) => {
|
|
1946
1980
|
if (looksLikeLicenseHeader(block)) return {
|
|
1947
1981
|
matched: false,
|
|
@@ -1981,9 +2015,13 @@ const detectNarrativeInBlock = (block, ext) => {
|
|
|
1981
2015
|
matched: false,
|
|
1982
2016
|
reason: ""
|
|
1983
2017
|
};
|
|
1984
|
-
if (block.prose.length >= 3 && looksLikeDeclarationPreamble(block.nextNonBlankLine, ext)) return {
|
|
2018
|
+
if (block.kind === "line" && block.prose.length >= 3 && looksLikeDeclarationPreamble(block.nextNonBlankLine, ext)) return {
|
|
2019
|
+
matched: true,
|
|
2020
|
+
reason: "multi-line preamble before declaration"
|
|
2021
|
+
};
|
|
2022
|
+
if (block.kind === "jsdoc" && block.prose.length >= 3 && looksLikeDeclarationPreamble(block.nextNonBlankLine, ext) && hasPreambleSlopSignal(block)) return {
|
|
1985
2023
|
matched: true,
|
|
1986
|
-
reason:
|
|
2024
|
+
reason: "JSDoc preamble with slop signal"
|
|
1987
2025
|
};
|
|
1988
2026
|
if (CROSS_REFERENCE_PHRASES.some((re) => re.test(joined))) return {
|
|
1989
2027
|
matched: true,
|
|
@@ -2023,7 +2061,7 @@ const detectNarrativeComments = async (context) => {
|
|
|
2023
2061
|
const syntax = getCommentSyntax(ext);
|
|
2024
2062
|
if (!syntax) continue;
|
|
2025
2063
|
const relativePath = path.relative(context.rootDirectory, filePath);
|
|
2026
|
-
if (
|
|
2064
|
+
if (isNonProductionPath(relativePath)) continue;
|
|
2027
2065
|
let content;
|
|
2028
2066
|
try {
|
|
2029
2067
|
content = fs.readFileSync(filePath, "utf-8");
|
|
@@ -7556,7 +7594,7 @@ const renderCleanRun = (input, deps = {}) => {
|
|
|
7556
7594
|
|
|
7557
7595
|
//#endregion
|
|
7558
7596
|
//#region src/version.ts
|
|
7559
|
-
const APP_VERSION = "0.8.
|
|
7597
|
+
const APP_VERSION = "0.8.3";
|
|
7560
7598
|
|
|
7561
7599
|
//#endregion
|
|
7562
7600
|
//#region src/utils/telemetry.ts
|
|
@@ -9382,13 +9420,77 @@ const fixDependencyAudit = async (context, onProgress) => {
|
|
|
9382
9420
|
}
|
|
9383
9421
|
onProgress?.("Dependency audit fixes · skipping (pnpm audit unavailable and no package-lock.json for npm fallback)");
|
|
9384
9422
|
};
|
|
9423
|
+
const SEMVER_PREFIX_RE = /^[~^]?/;
|
|
9424
|
+
const parseSemverMin = (spec) => {
|
|
9425
|
+
const match = spec.replace(SEMVER_PREFIX_RE, "").match(/^(\d+|x|X|\*)(?:\.(\d+|x|X|\*))?(?:\.(\d+|x|X|\*))?/);
|
|
9426
|
+
if (!match) return null;
|
|
9427
|
+
const head = match[1];
|
|
9428
|
+
if (!/^\d+$/.test(head)) return null;
|
|
9429
|
+
const toNum = (part) => {
|
|
9430
|
+
if (!part) return 0;
|
|
9431
|
+
return /^\d+$/.test(part) ? Number(part) : 0;
|
|
9432
|
+
};
|
|
9433
|
+
return [
|
|
9434
|
+
Number(head),
|
|
9435
|
+
toNum(match[2]),
|
|
9436
|
+
toNum(match[3])
|
|
9437
|
+
];
|
|
9438
|
+
};
|
|
9439
|
+
const isDowngrade = (oldSpec, newSpec) => {
|
|
9440
|
+
const oldV = parseSemverMin(oldSpec);
|
|
9441
|
+
const newV = parseSemverMin(newSpec);
|
|
9442
|
+
if (!oldV || !newV) return false;
|
|
9443
|
+
for (let i = 0; i < 3; i++) {
|
|
9444
|
+
if ((newV[i] ?? 0) < (oldV[i] ?? 0)) return true;
|
|
9445
|
+
if ((newV[i] ?? 0) > (oldV[i] ?? 0)) return false;
|
|
9446
|
+
}
|
|
9447
|
+
return false;
|
|
9448
|
+
};
|
|
9449
|
+
const DEP_BUCKETS = [
|
|
9450
|
+
"dependencies",
|
|
9451
|
+
"devDependencies",
|
|
9452
|
+
"peerDependencies",
|
|
9453
|
+
"optionalDependencies"
|
|
9454
|
+
];
|
|
9455
|
+
const snapshotPackageVersions = (pkg) => {
|
|
9456
|
+
const map = /* @__PURE__ */ new Map();
|
|
9457
|
+
for (const bucket of DEP_BUCKETS) {
|
|
9458
|
+
const deps = pkg[bucket];
|
|
9459
|
+
if (!deps || typeof deps !== "object") continue;
|
|
9460
|
+
for (const [name, version] of Object.entries(deps)) if (typeof version === "string") map.set(`${bucket}:${name}`, version);
|
|
9461
|
+
}
|
|
9462
|
+
return map;
|
|
9463
|
+
};
|
|
9464
|
+
const revertDowngrades = (rootDir, before) => {
|
|
9465
|
+
const pkgPath = path.join(rootDir, "package.json");
|
|
9466
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
9467
|
+
const reverted = [];
|
|
9468
|
+
for (const bucket of DEP_BUCKETS) {
|
|
9469
|
+
const deps = pkg[bucket];
|
|
9470
|
+
if (!deps) continue;
|
|
9471
|
+
for (const [name, version] of Object.entries(deps)) {
|
|
9472
|
+
const prior = before.get(`${bucket}:${name}`);
|
|
9473
|
+
if (!prior) continue;
|
|
9474
|
+
if (isDowngrade(prior, version)) {
|
|
9475
|
+
deps[name] = prior;
|
|
9476
|
+
reverted.push(`${name} ${version} → ${prior}`);
|
|
9477
|
+
}
|
|
9478
|
+
}
|
|
9479
|
+
}
|
|
9480
|
+
if (reverted.length > 0) fs.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
9481
|
+
return reverted;
|
|
9482
|
+
};
|
|
9385
9483
|
const runNpmAuditFix = async (rootDir, onProgress) => {
|
|
9484
|
+
const pkgPath = path.join(rootDir, "package.json");
|
|
9485
|
+
const before = snapshotPackageVersions(JSON.parse(fs.readFileSync(pkgPath, "utf-8")));
|
|
9386
9486
|
onProgress?.("Dependency audit fixes · running npm audit fix (can take a few minutes)");
|
|
9387
9487
|
const result = await runSubprocess("npm", ["audit", "fix"], {
|
|
9388
9488
|
cwd: rootDir,
|
|
9389
9489
|
timeout: INSTALL_TIMEOUT
|
|
9390
9490
|
});
|
|
9391
9491
|
if (result.exitCode !== 0 && !result.stdout && !result.stderr) throw new Error("npm audit fix failed");
|
|
9492
|
+
const reverted = revertDowngrades(rootDir, before);
|
|
9493
|
+
if (reverted.length > 0) onProgress?.(`Dependency audit fixes · reverted ${reverted.length} downgrade(s): ${reverted.join(", ")}`);
|
|
9392
9494
|
onProgress?.("Dependency audit fixes · running npm install");
|
|
9393
9495
|
const installResult = await runSubprocess("npm", ["install"], {
|
|
9394
9496
|
cwd: rootDir,
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as ENGINE_INFO, r as getEngineLabel, t as APP_VERSION } from "./version-
|
|
1
|
+
import { n as ENGINE_INFO, r as getEngineLabel, t as APP_VERSION } from "./version-BynHxO1X.js";
|
|
2
2
|
import { n as runSubprocess, t as isToolInstalled } from "./subprocess-CQUJDGgn.js";
|
|
3
3
|
import { r as runGenericLinter, t as fixRubyLint } from "./generic-BrcWMW7E.js";
|
|
4
4
|
import { n as runExpoDoctor } from "./expo-doctor-Bz0LZhQ6.js";
|
|
@@ -1272,9 +1272,14 @@ const detectOverAbstraction = async (context) => {
|
|
|
1272
1272
|
return diagnostics;
|
|
1273
1273
|
};
|
|
1274
1274
|
|
|
1275
|
+
//#endregion
|
|
1276
|
+
//#region src/engines/ai-slop/non-production-paths.ts
|
|
1277
|
+
const DIR_PATTERN = /(?:^|\/)(?:scripts|bin|examples?|demos?|bench|benches|benchmarks?|fixtures?|__fixtures__|__mocks__|__tests__|vendor|_vendor|vendored|third_party|blib2to3|lib2to3|cli|cli-[\w-]+|[\w-]+-cli)\//i;
|
|
1278
|
+
const BASENAME_PATTERN = /(?:^|\/)(?:benchmark|bench|demo|example|script|seed|migrate|profile|smoke|stress|load|debug|repro)[-_.][^/]*\.[mc]?[jt]sx?$|(?:^|\/)[^/]+[-_](?:benchmark|bench|demo|example)\.[mc]?[jt]sx?$/i;
|
|
1279
|
+
const isNonProductionPath = (relativePath) => DIR_PATTERN.test(relativePath) || BASENAME_PATTERN.test(relativePath);
|
|
1280
|
+
|
|
1275
1281
|
//#endregion
|
|
1276
1282
|
//#region src/engines/ai-slop/comments.ts
|
|
1277
|
-
const NON_PRODUCTION_DIR_PATTERN$2 = /(?:^|\/)(?:scripts|bin|examples?|demos?|bench|benches|benchmarks?|fixtures?|__fixtures__|__mocks__|__tests__|vendor|_vendor|vendored|third_party|blib2to3|lib2to3)\//i;
|
|
1278
1283
|
const TRIVIAL_VERB_STEMS = "Import|Defin|Initializ|Setting|Set\\s+up|Setup|Return|Check|Loop|Iterat|Creat|Updat|Delet|Remov|Handl|Get|Fetch|Increment|Decrement|Writ|Runn|Run|Pars|Execut|Extract|Sav|Load|Build|Start|Stopp|Stop|Clean(?:up|\\s+up)?|Configur|Validat|Process|Queue|Fire|Emit|Dispatch|Log|Print|Render";
|
|
1279
1284
|
const TRIVIAL_JS_COMMENT_PATTERNS = [/\/\/\s*This (?:function|method|class|variable|constant) (?:will |is used to |is responsible for )?/i, new RegExp(`\\/\\/\\s*(?:${TRIVIAL_VERB_STEMS})(?:e|es|ing|s)?\\b`, "i")];
|
|
1280
1285
|
const TRIVIAL_PYTHON_COMMENT_PATTERNS = [/^#\s*This (?:function|method|class) (?:will |is used to )?/i, new RegExp(`^#\\s*(?:${TRIVIAL_VERB_STEMS})(?:e|es|ing|s)?\\b`, "i")];
|
|
@@ -1331,7 +1336,7 @@ const detectTrivialComments = async (context) => {
|
|
|
1331
1336
|
for (const filePath of files) {
|
|
1332
1337
|
if (isAutoGenerated(filePath)) continue;
|
|
1333
1338
|
const relativePath = path.relative(context.rootDirectory, filePath);
|
|
1334
|
-
if (
|
|
1339
|
+
if (isNonProductionPath(relativePath)) continue;
|
|
1335
1340
|
let content;
|
|
1336
1341
|
try {
|
|
1337
1342
|
content = fs.readFileSync(filePath, "utf-8");
|
|
@@ -1367,11 +1372,10 @@ const slop = (filePath, line, rule, severity, message, help, fixable) => ({
|
|
|
1367
1372
|
fixable
|
|
1368
1373
|
});
|
|
1369
1374
|
const LOGGER_FILE_PATTERN = /(?:^|\/)(?:logger|logging|log)\.[^/]+$/i;
|
|
1370
|
-
const NON_PRODUCTION_DIR_PATTERN$1 = /(?:^|\/)(?:scripts|bin|examples?|demos?|bench|benches|benchmarks?|fixtures?|__fixtures__|__mocks__|__tests__|cli|cli-[\w-]+|[\w-]+-cli)\//;
|
|
1371
1375
|
const detectConsoleLeftovers = (content, relativePath, ext) => {
|
|
1372
1376
|
if (!JS_EXTENSIONS$4.has(ext)) return [];
|
|
1373
1377
|
if (LOGGER_FILE_PATTERN.test(relativePath)) return [];
|
|
1374
|
-
if (
|
|
1378
|
+
if (isNonProductionPath(relativePath)) return [];
|
|
1375
1379
|
const diagnostics = [];
|
|
1376
1380
|
const lines = content.split("\n");
|
|
1377
1381
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -1421,7 +1425,7 @@ const asAnyPattern = new RegExp(`\\bas\\s+any\\b`);
|
|
|
1421
1425
|
const doubleAssertPattern = new RegExp(`\\bas\\s+unknown\\s+as\\s+`);
|
|
1422
1426
|
const detectUnsafeTypePatterns = (content, relativePath, ext) => {
|
|
1423
1427
|
if (ext !== ".ts" && ext !== ".tsx") return [];
|
|
1424
|
-
if (
|
|
1428
|
+
if (isNonProductionPath(relativePath)) return [];
|
|
1425
1429
|
const diagnostics = [];
|
|
1426
1430
|
const lines = content.split("\n");
|
|
1427
1431
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -1990,6 +1994,27 @@ const collectJsDeps = (rootDir, jsDeps) => {
|
|
|
1990
1994
|
collectNestedManifests(rootDir, jsDeps);
|
|
1991
1995
|
return true;
|
|
1992
1996
|
};
|
|
1997
|
+
const TS_CONFIG_FILES = ["tsconfig.json", "jsconfig.json"];
|
|
1998
|
+
const buildAliasMatcher = (key) => {
|
|
1999
|
+
const starIdx = key.indexOf("*");
|
|
2000
|
+
if (starIdx === -1) return (spec) => spec === key;
|
|
2001
|
+
const before = key.slice(0, starIdx);
|
|
2002
|
+
const after = key.slice(starIdx + 1);
|
|
2003
|
+
return (spec) => spec.length >= before.length + after.length && spec.startsWith(before) && spec.endsWith(after);
|
|
2004
|
+
};
|
|
2005
|
+
const collectAliasMatchersFromConfig = (configPath, matchers) => {
|
|
2006
|
+
const opts = readJson(configPath)?.compilerOptions;
|
|
2007
|
+
if (!opts || typeof opts !== "object") return;
|
|
2008
|
+
const paths = opts.paths;
|
|
2009
|
+
if (!paths || typeof paths !== "object") return;
|
|
2010
|
+
for (const key of Object.keys(paths)) matchers.push(buildAliasMatcher(key));
|
|
2011
|
+
};
|
|
2012
|
+
const collectTsPathAliases = (rootDir) => {
|
|
2013
|
+
const matchers = [];
|
|
2014
|
+
const dirs = [rootDir, ...expandWorkspaceDirs(rootDir, readWorkspaceGlobs(rootDir, readJson(path.join(rootDir, "package.json"))))];
|
|
2015
|
+
for (const dir of dirs) for (const fname of TS_CONFIG_FILES) collectAliasMatchersFromConfig(path.join(dir, fname), matchers);
|
|
2016
|
+
return matchers;
|
|
2017
|
+
};
|
|
1993
2018
|
const addPyDep = (pyDeps, name) => {
|
|
1994
2019
|
const normalized = name.toLowerCase().replace(/_/g, "-");
|
|
1995
2020
|
pyDeps.add(normalized);
|
|
@@ -2147,10 +2172,11 @@ const extractPyImports = (content) => {
|
|
|
2147
2172
|
}
|
|
2148
2173
|
return results;
|
|
2149
2174
|
};
|
|
2150
|
-
const checkJsImport = (spec, manifest) => {
|
|
2175
|
+
const checkJsImport = (spec, manifest, tsAliasMatchers) => {
|
|
2151
2176
|
if (isJsRelativeOrAbsolute(spec)) return null;
|
|
2152
2177
|
if (isJsBuiltin(spec)) return null;
|
|
2153
2178
|
if (isJsVirtualModule(spec)) return null;
|
|
2179
|
+
if (tsAliasMatchers.some((m) => m(spec))) return null;
|
|
2154
2180
|
const pkg = packageNameFromImport(spec);
|
|
2155
2181
|
if (manifest.jsDeps.has(pkg)) return null;
|
|
2156
2182
|
if (pkg.startsWith("@types/")) {
|
|
@@ -2171,6 +2197,7 @@ const checkPyImport = (spec, manifest) => {
|
|
|
2171
2197
|
const detectHallucinatedImports = async (context) => {
|
|
2172
2198
|
const manifest = loadManifest(context.rootDirectory);
|
|
2173
2199
|
if (!manifest.hasJsManifest && !manifest.hasPyManifest) return [];
|
|
2200
|
+
const tsAliasMatchers = manifest.hasJsManifest ? collectTsPathAliases(context.rootDirectory) : [];
|
|
2174
2201
|
const diagnostics = [];
|
|
2175
2202
|
const files = getSourceFiles(context);
|
|
2176
2203
|
for (const filePath of files) {
|
|
@@ -2190,7 +2217,7 @@ const detectHallucinatedImports = async (context) => {
|
|
|
2190
2217
|
const relPath = path.relative(context.rootDirectory, filePath);
|
|
2191
2218
|
const imports = isJs ? extractJsImports(content) : extractPyImports(content);
|
|
2192
2219
|
for (const { spec, line } of imports) {
|
|
2193
|
-
const hallucinated = isJs ? checkJsImport(spec, manifest) : checkPyImport(spec, manifest);
|
|
2220
|
+
const hallucinated = isJs ? checkJsImport(spec, manifest, tsAliasMatchers) : checkPyImport(spec, manifest);
|
|
2194
2221
|
if (!hallucinated) continue;
|
|
2195
2222
|
const manifestLabel = isJs ? "package.json" : "requirements.txt / pyproject.toml / Pipfile";
|
|
2196
2223
|
diagnostics.push({
|
|
@@ -2326,7 +2353,6 @@ const PHP_DECL_START = /^\s*(?:public|private|protected|static|final|abstract|re
|
|
|
2326
2353
|
|
|
2327
2354
|
//#endregion
|
|
2328
2355
|
//#region src/engines/ai-slop/narrative-comments.ts
|
|
2329
|
-
const NON_PRODUCTION_DIR_PATTERN = /(?:^|\/)(?:scripts|bin|examples?|demos?|bench|benches|benchmarks?|fixtures?|__fixtures__|__mocks__|__tests__|vendor|_vendor|vendored|third_party|blib2to3|lib2to3)\//i;
|
|
2330
2356
|
const stripJsdocLine = (line) => line.replace(/^\s*\/\*\*+\s?/, "").replace(/\s*\*+\/\s*$/, "").replace(/^\s*\*\s?/, "").trim();
|
|
2331
2357
|
const stripLineComment = (line) => line.replace(/^\s*(?:(?:\/\/)|#)\s?/, "");
|
|
2332
2358
|
const getCommentSyntax = (ext) => {
|
|
@@ -2477,13 +2503,21 @@ const looksLikeGoDocComment = (block, ext) => {
|
|
|
2477
2503
|
if (!declMatch) return false;
|
|
2478
2504
|
return ((block.prose.find((l) => l.length > 0) ?? "").split(/\s+/)[0] ?? "") === declMatch[1];
|
|
2479
2505
|
};
|
|
2480
|
-
const DOC_INDICATOR_RE = /`[^`]+`|\|\s*[-:]+\s*\||```|\b(?:note|warning|warn|caveat|example|caution|see)
|
|
2506
|
+
const DOC_INDICATOR_RE = /`[^`]+`|\|\s*[-:]+\s*\||```|\b(?:note|warning|warn|caveat|example|caution|see):|\(e\.g\.[^)]+\)|\(i\.e\.[^)]+\)/i;
|
|
2481
2507
|
const hasDocIndicator = (block) => {
|
|
2482
2508
|
const joined = block.prose.join(" ");
|
|
2483
2509
|
if (DOC_INDICATOR_RE.test(joined)) return true;
|
|
2484
2510
|
for (const l of block.prose) if (/^[-]\s/.test(l)) return true;
|
|
2485
2511
|
return false;
|
|
2486
2512
|
};
|
|
2513
|
+
const hasPreambleSlopSignal = (block) => {
|
|
2514
|
+
const joined = block.prose.join(" ");
|
|
2515
|
+
for (const l of block.prose) {
|
|
2516
|
+
if (EXPLANATORY_OPENERS.test(l)) return true;
|
|
2517
|
+
if (JUSTIFICATION_OPENERS.some((re) => re.test(l))) return true;
|
|
2518
|
+
}
|
|
2519
|
+
return CROSS_REFERENCE_PHRASES.some((re) => re.test(joined));
|
|
2520
|
+
};
|
|
2487
2521
|
const detectNarrativeInBlock = (block, ext) => {
|
|
2488
2522
|
if (looksLikeLicenseHeader(block)) return {
|
|
2489
2523
|
matched: false,
|
|
@@ -2523,9 +2557,13 @@ const detectNarrativeInBlock = (block, ext) => {
|
|
|
2523
2557
|
matched: false,
|
|
2524
2558
|
reason: ""
|
|
2525
2559
|
};
|
|
2526
|
-
if (block.prose.length >= 3 && looksLikeDeclarationPreamble(block.nextNonBlankLine, ext)) return {
|
|
2560
|
+
if (block.kind === "line" && block.prose.length >= 3 && looksLikeDeclarationPreamble(block.nextNonBlankLine, ext)) return {
|
|
2561
|
+
matched: true,
|
|
2562
|
+
reason: "multi-line preamble before declaration"
|
|
2563
|
+
};
|
|
2564
|
+
if (block.kind === "jsdoc" && block.prose.length >= 3 && looksLikeDeclarationPreamble(block.nextNonBlankLine, ext) && hasPreambleSlopSignal(block)) return {
|
|
2527
2565
|
matched: true,
|
|
2528
|
-
reason:
|
|
2566
|
+
reason: "JSDoc preamble with slop signal"
|
|
2529
2567
|
};
|
|
2530
2568
|
if (CROSS_REFERENCE_PHRASES.some((re) => re.test(joined))) return {
|
|
2531
2569
|
matched: true,
|
|
@@ -2565,7 +2603,7 @@ const detectNarrativeComments = async (context) => {
|
|
|
2565
2603
|
const syntax = getCommentSyntax(ext);
|
|
2566
2604
|
if (!syntax) continue;
|
|
2567
2605
|
const relativePath = path.relative(context.rootDirectory, filePath);
|
|
2568
|
-
if (
|
|
2606
|
+
if (isNonProductionPath(relativePath)) continue;
|
|
2569
2607
|
let content;
|
|
2570
2608
|
try {
|
|
2571
2609
|
content = fs.readFileSync(filePath, "utf-8");
|
|
@@ -6361,7 +6399,7 @@ const scanCommand = async (directory, config, options) => {
|
|
|
6361
6399
|
});
|
|
6362
6400
|
}
|
|
6363
6401
|
if (options.json) {
|
|
6364
|
-
const { buildJsonOutput } = await import("./json-
|
|
6402
|
+
const { buildJsonOutput } = await import("./json-D8h2EZW6.js");
|
|
6365
6403
|
const jsonOut = buildJsonOutput(results, scoreResult, projectInfo.sourceFileCount, elapsedMs);
|
|
6366
6404
|
console.log(JSON.stringify(jsonOut, null, 2));
|
|
6367
6405
|
return { exitCode };
|
|
@@ -7361,13 +7399,77 @@ const fixDependencyAudit = async (context, onProgress) => {
|
|
|
7361
7399
|
}
|
|
7362
7400
|
onProgress?.("Dependency audit fixes · skipping (pnpm audit unavailable and no package-lock.json for npm fallback)");
|
|
7363
7401
|
};
|
|
7402
|
+
const SEMVER_PREFIX_RE = /^[~^]?/;
|
|
7403
|
+
const parseSemverMin = (spec) => {
|
|
7404
|
+
const match = spec.replace(SEMVER_PREFIX_RE, "").match(/^(\d+|x|X|\*)(?:\.(\d+|x|X|\*))?(?:\.(\d+|x|X|\*))?/);
|
|
7405
|
+
if (!match) return null;
|
|
7406
|
+
const head = match[1];
|
|
7407
|
+
if (!/^\d+$/.test(head)) return null;
|
|
7408
|
+
const toNum = (part) => {
|
|
7409
|
+
if (!part) return 0;
|
|
7410
|
+
return /^\d+$/.test(part) ? Number(part) : 0;
|
|
7411
|
+
};
|
|
7412
|
+
return [
|
|
7413
|
+
Number(head),
|
|
7414
|
+
toNum(match[2]),
|
|
7415
|
+
toNum(match[3])
|
|
7416
|
+
];
|
|
7417
|
+
};
|
|
7418
|
+
const isDowngrade = (oldSpec, newSpec) => {
|
|
7419
|
+
const oldV = parseSemverMin(oldSpec);
|
|
7420
|
+
const newV = parseSemverMin(newSpec);
|
|
7421
|
+
if (!oldV || !newV) return false;
|
|
7422
|
+
for (let i = 0; i < 3; i++) {
|
|
7423
|
+
if ((newV[i] ?? 0) < (oldV[i] ?? 0)) return true;
|
|
7424
|
+
if ((newV[i] ?? 0) > (oldV[i] ?? 0)) return false;
|
|
7425
|
+
}
|
|
7426
|
+
return false;
|
|
7427
|
+
};
|
|
7428
|
+
const DEP_BUCKETS = [
|
|
7429
|
+
"dependencies",
|
|
7430
|
+
"devDependencies",
|
|
7431
|
+
"peerDependencies",
|
|
7432
|
+
"optionalDependencies"
|
|
7433
|
+
];
|
|
7434
|
+
const snapshotPackageVersions = (pkg) => {
|
|
7435
|
+
const map = /* @__PURE__ */ new Map();
|
|
7436
|
+
for (const bucket of DEP_BUCKETS) {
|
|
7437
|
+
const deps = pkg[bucket];
|
|
7438
|
+
if (!deps || typeof deps !== "object") continue;
|
|
7439
|
+
for (const [name, version] of Object.entries(deps)) if (typeof version === "string") map.set(`${bucket}:${name}`, version);
|
|
7440
|
+
}
|
|
7441
|
+
return map;
|
|
7442
|
+
};
|
|
7443
|
+
const revertDowngrades = (rootDir, before) => {
|
|
7444
|
+
const pkgPath = path.join(rootDir, "package.json");
|
|
7445
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
7446
|
+
const reverted = [];
|
|
7447
|
+
for (const bucket of DEP_BUCKETS) {
|
|
7448
|
+
const deps = pkg[bucket];
|
|
7449
|
+
if (!deps) continue;
|
|
7450
|
+
for (const [name, version] of Object.entries(deps)) {
|
|
7451
|
+
const prior = before.get(`${bucket}:${name}`);
|
|
7452
|
+
if (!prior) continue;
|
|
7453
|
+
if (isDowngrade(prior, version)) {
|
|
7454
|
+
deps[name] = prior;
|
|
7455
|
+
reverted.push(`${name} ${version} → ${prior}`);
|
|
7456
|
+
}
|
|
7457
|
+
}
|
|
7458
|
+
}
|
|
7459
|
+
if (reverted.length > 0) fs.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
7460
|
+
return reverted;
|
|
7461
|
+
};
|
|
7364
7462
|
const runNpmAuditFix = async (rootDir, onProgress) => {
|
|
7463
|
+
const pkgPath = path.join(rootDir, "package.json");
|
|
7464
|
+
const before = snapshotPackageVersions(JSON.parse(fs.readFileSync(pkgPath, "utf-8")));
|
|
7365
7465
|
onProgress?.("Dependency audit fixes · running npm audit fix (can take a few minutes)");
|
|
7366
7466
|
const result = await runSubprocess("npm", ["audit", "fix"], {
|
|
7367
7467
|
cwd: rootDir,
|
|
7368
7468
|
timeout: INSTALL_TIMEOUT
|
|
7369
7469
|
});
|
|
7370
7470
|
if (result.exitCode !== 0 && !result.stdout && !result.stderr) throw new Error("npm audit fix failed");
|
|
7471
|
+
const reverted = revertDowngrades(rootDir, before);
|
|
7472
|
+
if (reverted.length > 0) onProgress?.(`Dependency audit fixes · reverted ${reverted.length} downgrade(s): ${reverted.join(", ")}`);
|
|
7371
7473
|
onProgress?.("Dependency audit fixes · running npm install");
|
|
7372
7474
|
const installResult = await runSubprocess("npm", ["install"], {
|
|
7373
7475
|
cwd: rootDir,
|
package/dist/mcp.js
CHANGED
|
@@ -531,9 +531,14 @@ const detectOverAbstraction = async (context) => {
|
|
|
531
531
|
return diagnostics;
|
|
532
532
|
};
|
|
533
533
|
|
|
534
|
+
//#endregion
|
|
535
|
+
//#region src/engines/ai-slop/non-production-paths.ts
|
|
536
|
+
const DIR_PATTERN = /(?:^|\/)(?:scripts|bin|examples?|demos?|bench|benches|benchmarks?|fixtures?|__fixtures__|__mocks__|__tests__|vendor|_vendor|vendored|third_party|blib2to3|lib2to3|cli|cli-[\w-]+|[\w-]+-cli)\//i;
|
|
537
|
+
const BASENAME_PATTERN = /(?:^|\/)(?:benchmark|bench|demo|example|script|seed|migrate|profile|smoke|stress|load|debug|repro)[-_.][^/]*\.[mc]?[jt]sx?$|(?:^|\/)[^/]+[-_](?:benchmark|bench|demo|example)\.[mc]?[jt]sx?$/i;
|
|
538
|
+
const isNonProductionPath = (relativePath) => DIR_PATTERN.test(relativePath) || BASENAME_PATTERN.test(relativePath);
|
|
539
|
+
|
|
534
540
|
//#endregion
|
|
535
541
|
//#region src/engines/ai-slop/comments.ts
|
|
536
|
-
const NON_PRODUCTION_DIR_PATTERN$2 = /(?:^|\/)(?:scripts|bin|examples?|demos?|bench|benches|benchmarks?|fixtures?|__fixtures__|__mocks__|__tests__|vendor|_vendor|vendored|third_party|blib2to3|lib2to3)\//i;
|
|
537
542
|
const TRIVIAL_VERB_STEMS = "Import|Defin|Initializ|Setting|Set\\s+up|Setup|Return|Check|Loop|Iterat|Creat|Updat|Delet|Remov|Handl|Get|Fetch|Increment|Decrement|Writ|Runn|Run|Pars|Execut|Extract|Sav|Load|Build|Start|Stopp|Stop|Clean(?:up|\\s+up)?|Configur|Validat|Process|Queue|Fire|Emit|Dispatch|Log|Print|Render";
|
|
538
543
|
const TRIVIAL_JS_COMMENT_PATTERNS = [/\/\/\s*This (?:function|method|class|variable|constant) (?:will |is used to |is responsible for )?/i, new RegExp(`\\/\\/\\s*(?:${TRIVIAL_VERB_STEMS})(?:e|es|ing|s)?\\b`, "i")];
|
|
539
544
|
const TRIVIAL_PYTHON_COMMENT_PATTERNS = [/^#\s*This (?:function|method|class) (?:will |is used to )?/i, new RegExp(`^#\\s*(?:${TRIVIAL_VERB_STEMS})(?:e|es|ing|s)?\\b`, "i")];
|
|
@@ -590,7 +595,7 @@ const detectTrivialComments = async (context) => {
|
|
|
590
595
|
for (const filePath of files) {
|
|
591
596
|
if (isAutoGenerated(filePath)) continue;
|
|
592
597
|
const relativePath = path.relative(context.rootDirectory, filePath);
|
|
593
|
-
if (
|
|
598
|
+
if (isNonProductionPath(relativePath)) continue;
|
|
594
599
|
let content;
|
|
595
600
|
try {
|
|
596
601
|
content = fs.readFileSync(filePath, "utf-8");
|
|
@@ -626,11 +631,10 @@ const slop = (filePath, line, rule, severity, message, help, fixable) => ({
|
|
|
626
631
|
fixable
|
|
627
632
|
});
|
|
628
633
|
const LOGGER_FILE_PATTERN = /(?:^|\/)(?:logger|logging|log)\.[^/]+$/i;
|
|
629
|
-
const NON_PRODUCTION_DIR_PATTERN$1 = /(?:^|\/)(?:scripts|bin|examples?|demos?|bench|benches|benchmarks?|fixtures?|__fixtures__|__mocks__|__tests__|cli|cli-[\w-]+|[\w-]+-cli)\//;
|
|
630
634
|
const detectConsoleLeftovers = (content, relativePath, ext) => {
|
|
631
635
|
if (!JS_EXTENSIONS$3.has(ext)) return [];
|
|
632
636
|
if (LOGGER_FILE_PATTERN.test(relativePath)) return [];
|
|
633
|
-
if (
|
|
637
|
+
if (isNonProductionPath(relativePath)) return [];
|
|
634
638
|
const diagnostics = [];
|
|
635
639
|
const lines = content.split("\n");
|
|
636
640
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -680,7 +684,7 @@ const asAnyPattern = new RegExp(`\\bas\\s+any\\b`);
|
|
|
680
684
|
const doubleAssertPattern = new RegExp(`\\bas\\s+unknown\\s+as\\s+`);
|
|
681
685
|
const detectUnsafeTypePatterns = (content, relativePath, ext) => {
|
|
682
686
|
if (ext !== ".ts" && ext !== ".tsx") return [];
|
|
683
|
-
if (
|
|
687
|
+
if (isNonProductionPath(relativePath)) return [];
|
|
684
688
|
const diagnostics = [];
|
|
685
689
|
const lines = content.split("\n");
|
|
686
690
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -1249,6 +1253,27 @@ const collectJsDeps = (rootDir, jsDeps) => {
|
|
|
1249
1253
|
collectNestedManifests(rootDir, jsDeps);
|
|
1250
1254
|
return true;
|
|
1251
1255
|
};
|
|
1256
|
+
const TS_CONFIG_FILES = ["tsconfig.json", "jsconfig.json"];
|
|
1257
|
+
const buildAliasMatcher = (key) => {
|
|
1258
|
+
const starIdx = key.indexOf("*");
|
|
1259
|
+
if (starIdx === -1) return (spec) => spec === key;
|
|
1260
|
+
const before = key.slice(0, starIdx);
|
|
1261
|
+
const after = key.slice(starIdx + 1);
|
|
1262
|
+
return (spec) => spec.length >= before.length + after.length && spec.startsWith(before) && spec.endsWith(after);
|
|
1263
|
+
};
|
|
1264
|
+
const collectAliasMatchersFromConfig = (configPath, matchers) => {
|
|
1265
|
+
const opts = readJson(configPath)?.compilerOptions;
|
|
1266
|
+
if (!opts || typeof opts !== "object") return;
|
|
1267
|
+
const paths = opts.paths;
|
|
1268
|
+
if (!paths || typeof paths !== "object") return;
|
|
1269
|
+
for (const key of Object.keys(paths)) matchers.push(buildAliasMatcher(key));
|
|
1270
|
+
};
|
|
1271
|
+
const collectTsPathAliases = (rootDir) => {
|
|
1272
|
+
const matchers = [];
|
|
1273
|
+
const dirs = [rootDir, ...expandWorkspaceDirs(rootDir, readWorkspaceGlobs(rootDir, readJson(path.join(rootDir, "package.json"))))];
|
|
1274
|
+
for (const dir of dirs) for (const fname of TS_CONFIG_FILES) collectAliasMatchersFromConfig(path.join(dir, fname), matchers);
|
|
1275
|
+
return matchers;
|
|
1276
|
+
};
|
|
1252
1277
|
const addPyDep = (pyDeps, name) => {
|
|
1253
1278
|
const normalized = name.toLowerCase().replace(/_/g, "-");
|
|
1254
1279
|
pyDeps.add(normalized);
|
|
@@ -1406,10 +1431,11 @@ const extractPyImports = (content) => {
|
|
|
1406
1431
|
}
|
|
1407
1432
|
return results;
|
|
1408
1433
|
};
|
|
1409
|
-
const checkJsImport = (spec, manifest) => {
|
|
1434
|
+
const checkJsImport = (spec, manifest, tsAliasMatchers) => {
|
|
1410
1435
|
if (isJsRelativeOrAbsolute(spec)) return null;
|
|
1411
1436
|
if (isJsBuiltin(spec)) return null;
|
|
1412
1437
|
if (isJsVirtualModule(spec)) return null;
|
|
1438
|
+
if (tsAliasMatchers.some((m) => m(spec))) return null;
|
|
1413
1439
|
const pkg = packageNameFromImport(spec);
|
|
1414
1440
|
if (manifest.jsDeps.has(pkg)) return null;
|
|
1415
1441
|
if (pkg.startsWith("@types/")) {
|
|
@@ -1430,6 +1456,7 @@ const checkPyImport = (spec, manifest) => {
|
|
|
1430
1456
|
const detectHallucinatedImports = async (context) => {
|
|
1431
1457
|
const manifest = loadManifest(context.rootDirectory);
|
|
1432
1458
|
if (!manifest.hasJsManifest && !manifest.hasPyManifest) return [];
|
|
1459
|
+
const tsAliasMatchers = manifest.hasJsManifest ? collectTsPathAliases(context.rootDirectory) : [];
|
|
1433
1460
|
const diagnostics = [];
|
|
1434
1461
|
const files = getSourceFiles(context);
|
|
1435
1462
|
for (const filePath of files) {
|
|
@@ -1449,7 +1476,7 @@ const detectHallucinatedImports = async (context) => {
|
|
|
1449
1476
|
const relPath = path.relative(context.rootDirectory, filePath);
|
|
1450
1477
|
const imports = isJs ? extractJsImports(content) : extractPyImports(content);
|
|
1451
1478
|
for (const { spec, line } of imports) {
|
|
1452
|
-
const hallucinated = isJs ? checkJsImport(spec, manifest) : checkPyImport(spec, manifest);
|
|
1479
|
+
const hallucinated = isJs ? checkJsImport(spec, manifest, tsAliasMatchers) : checkPyImport(spec, manifest);
|
|
1453
1480
|
if (!hallucinated) continue;
|
|
1454
1481
|
const manifestLabel = isJs ? "package.json" : "requirements.txt / pyproject.toml / Pipfile";
|
|
1455
1482
|
diagnostics.push({
|
|
@@ -1585,7 +1612,6 @@ const PHP_DECL_START = /^\s*(?:public|private|protected|static|final|abstract|re
|
|
|
1585
1612
|
|
|
1586
1613
|
//#endregion
|
|
1587
1614
|
//#region src/engines/ai-slop/narrative-comments.ts
|
|
1588
|
-
const NON_PRODUCTION_DIR_PATTERN = /(?:^|\/)(?:scripts|bin|examples?|demos?|bench|benches|benchmarks?|fixtures?|__fixtures__|__mocks__|__tests__|vendor|_vendor|vendored|third_party|blib2to3|lib2to3)\//i;
|
|
1589
1615
|
const stripJsdocLine = (line) => line.replace(/^\s*\/\*\*+\s?/, "").replace(/\s*\*+\/\s*$/, "").replace(/^\s*\*\s?/, "").trim();
|
|
1590
1616
|
const stripLineComment = (line) => line.replace(/^\s*(?:(?:\/\/)|#)\s?/, "");
|
|
1591
1617
|
const getCommentSyntax = (ext) => {
|
|
@@ -1736,13 +1762,21 @@ const looksLikeGoDocComment = (block, ext) => {
|
|
|
1736
1762
|
if (!declMatch) return false;
|
|
1737
1763
|
return ((block.prose.find((l) => l.length > 0) ?? "").split(/\s+/)[0] ?? "") === declMatch[1];
|
|
1738
1764
|
};
|
|
1739
|
-
const DOC_INDICATOR_RE = /`[^`]+`|\|\s*[-:]+\s*\||```|\b(?:note|warning|warn|caveat|example|caution|see)
|
|
1765
|
+
const DOC_INDICATOR_RE = /`[^`]+`|\|\s*[-:]+\s*\||```|\b(?:note|warning|warn|caveat|example|caution|see):|\(e\.g\.[^)]+\)|\(i\.e\.[^)]+\)/i;
|
|
1740
1766
|
const hasDocIndicator = (block) => {
|
|
1741
1767
|
const joined = block.prose.join(" ");
|
|
1742
1768
|
if (DOC_INDICATOR_RE.test(joined)) return true;
|
|
1743
1769
|
for (const l of block.prose) if (/^[-]\s/.test(l)) return true;
|
|
1744
1770
|
return false;
|
|
1745
1771
|
};
|
|
1772
|
+
const hasPreambleSlopSignal = (block) => {
|
|
1773
|
+
const joined = block.prose.join(" ");
|
|
1774
|
+
for (const l of block.prose) {
|
|
1775
|
+
if (EXPLANATORY_OPENERS.test(l)) return true;
|
|
1776
|
+
if (JUSTIFICATION_OPENERS.some((re) => re.test(l))) return true;
|
|
1777
|
+
}
|
|
1778
|
+
return CROSS_REFERENCE_PHRASES.some((re) => re.test(joined));
|
|
1779
|
+
};
|
|
1746
1780
|
const detectNarrativeInBlock = (block, ext) => {
|
|
1747
1781
|
if (looksLikeLicenseHeader(block)) return {
|
|
1748
1782
|
matched: false,
|
|
@@ -1782,9 +1816,13 @@ const detectNarrativeInBlock = (block, ext) => {
|
|
|
1782
1816
|
matched: false,
|
|
1783
1817
|
reason: ""
|
|
1784
1818
|
};
|
|
1785
|
-
if (block.prose.length >= 3 && looksLikeDeclarationPreamble(block.nextNonBlankLine, ext)) return {
|
|
1819
|
+
if (block.kind === "line" && block.prose.length >= 3 && looksLikeDeclarationPreamble(block.nextNonBlankLine, ext)) return {
|
|
1820
|
+
matched: true,
|
|
1821
|
+
reason: "multi-line preamble before declaration"
|
|
1822
|
+
};
|
|
1823
|
+
if (block.kind === "jsdoc" && block.prose.length >= 3 && looksLikeDeclarationPreamble(block.nextNonBlankLine, ext) && hasPreambleSlopSignal(block)) return {
|
|
1786
1824
|
matched: true,
|
|
1787
|
-
reason:
|
|
1825
|
+
reason: "JSDoc preamble with slop signal"
|
|
1788
1826
|
};
|
|
1789
1827
|
if (CROSS_REFERENCE_PHRASES.some((re) => re.test(joined))) return {
|
|
1790
1828
|
matched: true,
|
|
@@ -1824,7 +1862,7 @@ const detectNarrativeComments = async (context) => {
|
|
|
1824
1862
|
const syntax = getCommentSyntax(ext);
|
|
1825
1863
|
if (!syntax) continue;
|
|
1826
1864
|
const relativePath = path.relative(context.rootDirectory, filePath);
|
|
1827
|
-
if (
|
|
1865
|
+
if (isNonProductionPath(relativePath)) continue;
|
|
1828
1866
|
let content;
|
|
1829
1867
|
try {
|
|
1830
1868
|
content = fs.readFileSync(filePath, "utf-8");
|
|
@@ -5056,7 +5094,7 @@ const handleAislopBaseline = (input) => {
|
|
|
5056
5094
|
|
|
5057
5095
|
//#endregion
|
|
5058
5096
|
//#region src/version.ts
|
|
5059
|
-
const APP_VERSION = "0.8.
|
|
5097
|
+
const APP_VERSION = "0.8.3";
|
|
5060
5098
|
|
|
5061
5099
|
//#endregion
|
|
5062
5100
|
//#region src/mcp.ts
|
|
@@ -29,7 +29,7 @@ const getEngineLabel = (engine) => ENGINE_INFO[engine].label;
|
|
|
29
29
|
|
|
30
30
|
//#endregion
|
|
31
31
|
//#region src/version.ts
|
|
32
|
-
const APP_VERSION = "0.8.
|
|
32
|
+
const APP_VERSION = "0.8.3";
|
|
33
33
|
|
|
34
34
|
//#endregion
|
|
35
35
|
export { ENGINE_INFO as n, getEngineLabel as r, APP_VERSION as t };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aislop",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.3",
|
|
4
4
|
"description": "The engineering standards layer and quality gate for AI-written code. Define your standard once. Every agent — Claude Code, Cursor, Codex — is held to it automatically, on every edit and every PR. Catches the slop they leave behind, enforces the rules your team sets. 8+ languages. Deterministic.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|