aislop 0.8.1 → 0.8.2

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 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 (NON_PRODUCTION_DIR_PATTERN$2.test(relativePath)) continue;
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 (NON_PRODUCTION_DIR_PATTERN$1.test(relativePath)) return [];
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 (NON_PRODUCTION_DIR_PATTERN$1.test(relativePath)) return [];
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++) {
@@ -1784,7 +1788,6 @@ const PHP_DECL_START = /^\s*(?:public|private|protected|static|final|abstract|re
1784
1788
 
1785
1789
  //#endregion
1786
1790
  //#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
1791
  const stripJsdocLine = (line) => line.replace(/^\s*\/\*\*+\s?/, "").replace(/\s*\*+\/\s*$/, "").replace(/^\s*\*\s?/, "").trim();
1789
1792
  const stripLineComment = (line) => line.replace(/^\s*(?:(?:\/\/)|#)\s?/, "");
1790
1793
  const getCommentSyntax = (ext) => {
@@ -1935,13 +1938,21 @@ const looksLikeGoDocComment = (block, ext) => {
1935
1938
  if (!declMatch) return false;
1936
1939
  return ((block.prose.find((l) => l.length > 0) ?? "").split(/\s+/)[0] ?? "") === declMatch[1];
1937
1940
  };
1938
- const DOC_INDICATOR_RE = /`[^`]+`|\|\s*[-:]+\s*\||```|\b(?:note|warning|warn|caveat|example|caution|see):/i;
1941
+ const DOC_INDICATOR_RE = /`[^`]+`|\|\s*[-:]+\s*\||```|\b(?:note|warning|warn|caveat|example|caution|see):|\(e\.g\.[^)]+\)|\(i\.e\.[^)]+\)/i;
1939
1942
  const hasDocIndicator = (block) => {
1940
1943
  const joined = block.prose.join(" ");
1941
1944
  if (DOC_INDICATOR_RE.test(joined)) return true;
1942
1945
  for (const l of block.prose) if (/^[-]\s/.test(l)) return true;
1943
1946
  return false;
1944
1947
  };
1948
+ const hasPreambleSlopSignal = (block) => {
1949
+ const joined = block.prose.join(" ");
1950
+ for (const l of block.prose) {
1951
+ if (EXPLANATORY_OPENERS.test(l)) return true;
1952
+ if (JUSTIFICATION_OPENERS.some((re) => re.test(l))) return true;
1953
+ }
1954
+ return CROSS_REFERENCE_PHRASES.some((re) => re.test(joined));
1955
+ };
1945
1956
  const detectNarrativeInBlock = (block, ext) => {
1946
1957
  if (looksLikeLicenseHeader(block)) return {
1947
1958
  matched: false,
@@ -1981,9 +1992,13 @@ const detectNarrativeInBlock = (block, ext) => {
1981
1992
  matched: false,
1982
1993
  reason: ""
1983
1994
  };
1984
- if (block.prose.length >= 3 && looksLikeDeclarationPreamble(block.nextNonBlankLine, ext)) return {
1995
+ if (block.kind === "line" && block.prose.length >= 3 && looksLikeDeclarationPreamble(block.nextNonBlankLine, ext)) return {
1996
+ matched: true,
1997
+ reason: "multi-line preamble before declaration"
1998
+ };
1999
+ if (block.kind === "jsdoc" && block.prose.length >= 3 && looksLikeDeclarationPreamble(block.nextNonBlankLine, ext) && hasPreambleSlopSignal(block)) return {
1985
2000
  matched: true,
1986
- reason: block.kind === "jsdoc" ? "JSDoc preamble before declaration" : "multi-line preamble before declaration"
2001
+ reason: "JSDoc preamble with slop signal"
1987
2002
  };
1988
2003
  if (CROSS_REFERENCE_PHRASES.some((re) => re.test(joined))) return {
1989
2004
  matched: true,
@@ -2023,7 +2038,7 @@ const detectNarrativeComments = async (context) => {
2023
2038
  const syntax = getCommentSyntax(ext);
2024
2039
  if (!syntax) continue;
2025
2040
  const relativePath = path.relative(context.rootDirectory, filePath);
2026
- if (NON_PRODUCTION_DIR_PATTERN.test(relativePath)) continue;
2041
+ if (isNonProductionPath(relativePath)) continue;
2027
2042
  let content;
2028
2043
  try {
2029
2044
  content = fs.readFileSync(filePath, "utf-8");
@@ -7556,7 +7571,7 @@ const renderCleanRun = (input, deps = {}) => {
7556
7571
 
7557
7572
  //#endregion
7558
7573
  //#region src/version.ts
7559
- const APP_VERSION = "0.8.1";
7574
+ const APP_VERSION = "0.8.2";
7560
7575
 
7561
7576
  //#endregion
7562
7577
  //#region src/utils/telemetry.ts
@@ -9382,13 +9397,77 @@ const fixDependencyAudit = async (context, onProgress) => {
9382
9397
  }
9383
9398
  onProgress?.("Dependency audit fixes · skipping (pnpm audit unavailable and no package-lock.json for npm fallback)");
9384
9399
  };
9400
+ const SEMVER_PREFIX_RE = /^[~^]?/;
9401
+ const parseSemverMin = (spec) => {
9402
+ const match = spec.replace(SEMVER_PREFIX_RE, "").match(/^(\d+|x|X|\*)(?:\.(\d+|x|X|\*))?(?:\.(\d+|x|X|\*))?/);
9403
+ if (!match) return null;
9404
+ const head = match[1];
9405
+ if (!/^\d+$/.test(head)) return null;
9406
+ const toNum = (part) => {
9407
+ if (!part) return 0;
9408
+ return /^\d+$/.test(part) ? Number(part) : 0;
9409
+ };
9410
+ return [
9411
+ Number(head),
9412
+ toNum(match[2]),
9413
+ toNum(match[3])
9414
+ ];
9415
+ };
9416
+ const isDowngrade = (oldSpec, newSpec) => {
9417
+ const oldV = parseSemverMin(oldSpec);
9418
+ const newV = parseSemverMin(newSpec);
9419
+ if (!oldV || !newV) return false;
9420
+ for (let i = 0; i < 3; i++) {
9421
+ if ((newV[i] ?? 0) < (oldV[i] ?? 0)) return true;
9422
+ if ((newV[i] ?? 0) > (oldV[i] ?? 0)) return false;
9423
+ }
9424
+ return false;
9425
+ };
9426
+ const DEP_BUCKETS = [
9427
+ "dependencies",
9428
+ "devDependencies",
9429
+ "peerDependencies",
9430
+ "optionalDependencies"
9431
+ ];
9432
+ const snapshotPackageVersions = (pkg) => {
9433
+ const map = /* @__PURE__ */ new Map();
9434
+ for (const bucket of DEP_BUCKETS) {
9435
+ const deps = pkg[bucket];
9436
+ if (!deps || typeof deps !== "object") continue;
9437
+ for (const [name, version] of Object.entries(deps)) if (typeof version === "string") map.set(`${bucket}:${name}`, version);
9438
+ }
9439
+ return map;
9440
+ };
9441
+ const revertDowngrades = (rootDir, before) => {
9442
+ const pkgPath = path.join(rootDir, "package.json");
9443
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
9444
+ const reverted = [];
9445
+ for (const bucket of DEP_BUCKETS) {
9446
+ const deps = pkg[bucket];
9447
+ if (!deps) continue;
9448
+ for (const [name, version] of Object.entries(deps)) {
9449
+ const prior = before.get(`${bucket}:${name}`);
9450
+ if (!prior) continue;
9451
+ if (isDowngrade(prior, version)) {
9452
+ deps[name] = prior;
9453
+ reverted.push(`${name} ${version} → ${prior}`);
9454
+ }
9455
+ }
9456
+ }
9457
+ if (reverted.length > 0) fs.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
9458
+ return reverted;
9459
+ };
9385
9460
  const runNpmAuditFix = async (rootDir, onProgress) => {
9461
+ const pkgPath = path.join(rootDir, "package.json");
9462
+ const before = snapshotPackageVersions(JSON.parse(fs.readFileSync(pkgPath, "utf-8")));
9386
9463
  onProgress?.("Dependency audit fixes · running npm audit fix (can take a few minutes)");
9387
9464
  const result = await runSubprocess("npm", ["audit", "fix"], {
9388
9465
  cwd: rootDir,
9389
9466
  timeout: INSTALL_TIMEOUT
9390
9467
  });
9391
9468
  if (result.exitCode !== 0 && !result.stdout && !result.stderr) throw new Error("npm audit fix failed");
9469
+ const reverted = revertDowngrades(rootDir, before);
9470
+ if (reverted.length > 0) onProgress?.(`Dependency audit fixes · reverted ${reverted.length} downgrade(s): ${reverted.join(", ")}`);
9392
9471
  onProgress?.("Dependency audit fixes · running npm install");
9393
9472
  const installResult = await runSubprocess("npm", ["install"], {
9394
9473
  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-B7_FRirI.js";
1
+ import { n as ENGINE_INFO, r as getEngineLabel, t as APP_VERSION } from "./version-G3ekYjY1.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 (NON_PRODUCTION_DIR_PATTERN$2.test(relativePath)) continue;
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 (NON_PRODUCTION_DIR_PATTERN$1.test(relativePath)) return [];
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 (NON_PRODUCTION_DIR_PATTERN$1.test(relativePath)) return [];
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++) {
@@ -2326,7 +2330,6 @@ const PHP_DECL_START = /^\s*(?:public|private|protected|static|final|abstract|re
2326
2330
 
2327
2331
  //#endregion
2328
2332
  //#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
2333
  const stripJsdocLine = (line) => line.replace(/^\s*\/\*\*+\s?/, "").replace(/\s*\*+\/\s*$/, "").replace(/^\s*\*\s?/, "").trim();
2331
2334
  const stripLineComment = (line) => line.replace(/^\s*(?:(?:\/\/)|#)\s?/, "");
2332
2335
  const getCommentSyntax = (ext) => {
@@ -2477,13 +2480,21 @@ const looksLikeGoDocComment = (block, ext) => {
2477
2480
  if (!declMatch) return false;
2478
2481
  return ((block.prose.find((l) => l.length > 0) ?? "").split(/\s+/)[0] ?? "") === declMatch[1];
2479
2482
  };
2480
- const DOC_INDICATOR_RE = /`[^`]+`|\|\s*[-:]+\s*\||```|\b(?:note|warning|warn|caveat|example|caution|see):/i;
2483
+ const DOC_INDICATOR_RE = /`[^`]+`|\|\s*[-:]+\s*\||```|\b(?:note|warning|warn|caveat|example|caution|see):|\(e\.g\.[^)]+\)|\(i\.e\.[^)]+\)/i;
2481
2484
  const hasDocIndicator = (block) => {
2482
2485
  const joined = block.prose.join(" ");
2483
2486
  if (DOC_INDICATOR_RE.test(joined)) return true;
2484
2487
  for (const l of block.prose) if (/^[-]\s/.test(l)) return true;
2485
2488
  return false;
2486
2489
  };
2490
+ const hasPreambleSlopSignal = (block) => {
2491
+ const joined = block.prose.join(" ");
2492
+ for (const l of block.prose) {
2493
+ if (EXPLANATORY_OPENERS.test(l)) return true;
2494
+ if (JUSTIFICATION_OPENERS.some((re) => re.test(l))) return true;
2495
+ }
2496
+ return CROSS_REFERENCE_PHRASES.some((re) => re.test(joined));
2497
+ };
2487
2498
  const detectNarrativeInBlock = (block, ext) => {
2488
2499
  if (looksLikeLicenseHeader(block)) return {
2489
2500
  matched: false,
@@ -2523,9 +2534,13 @@ const detectNarrativeInBlock = (block, ext) => {
2523
2534
  matched: false,
2524
2535
  reason: ""
2525
2536
  };
2526
- if (block.prose.length >= 3 && looksLikeDeclarationPreamble(block.nextNonBlankLine, ext)) return {
2537
+ if (block.kind === "line" && block.prose.length >= 3 && looksLikeDeclarationPreamble(block.nextNonBlankLine, ext)) return {
2538
+ matched: true,
2539
+ reason: "multi-line preamble before declaration"
2540
+ };
2541
+ if (block.kind === "jsdoc" && block.prose.length >= 3 && looksLikeDeclarationPreamble(block.nextNonBlankLine, ext) && hasPreambleSlopSignal(block)) return {
2527
2542
  matched: true,
2528
- reason: block.kind === "jsdoc" ? "JSDoc preamble before declaration" : "multi-line preamble before declaration"
2543
+ reason: "JSDoc preamble with slop signal"
2529
2544
  };
2530
2545
  if (CROSS_REFERENCE_PHRASES.some((re) => re.test(joined))) return {
2531
2546
  matched: true,
@@ -2565,7 +2580,7 @@ const detectNarrativeComments = async (context) => {
2565
2580
  const syntax = getCommentSyntax(ext);
2566
2581
  if (!syntax) continue;
2567
2582
  const relativePath = path.relative(context.rootDirectory, filePath);
2568
- if (NON_PRODUCTION_DIR_PATTERN.test(relativePath)) continue;
2583
+ if (isNonProductionPath(relativePath)) continue;
2569
2584
  let content;
2570
2585
  try {
2571
2586
  content = fs.readFileSync(filePath, "utf-8");
@@ -6361,7 +6376,7 @@ const scanCommand = async (directory, config, options) => {
6361
6376
  });
6362
6377
  }
6363
6378
  if (options.json) {
6364
- const { buildJsonOutput } = await import("./json-7EtVVIhd.js");
6379
+ const { buildJsonOutput } = await import("./json-DxLkV8n2.js");
6365
6380
  const jsonOut = buildJsonOutput(results, scoreResult, projectInfo.sourceFileCount, elapsedMs);
6366
6381
  console.log(JSON.stringify(jsonOut, null, 2));
6367
6382
  return { exitCode };
@@ -7361,13 +7376,77 @@ const fixDependencyAudit = async (context, onProgress) => {
7361
7376
  }
7362
7377
  onProgress?.("Dependency audit fixes · skipping (pnpm audit unavailable and no package-lock.json for npm fallback)");
7363
7378
  };
7379
+ const SEMVER_PREFIX_RE = /^[~^]?/;
7380
+ const parseSemverMin = (spec) => {
7381
+ const match = spec.replace(SEMVER_PREFIX_RE, "").match(/^(\d+|x|X|\*)(?:\.(\d+|x|X|\*))?(?:\.(\d+|x|X|\*))?/);
7382
+ if (!match) return null;
7383
+ const head = match[1];
7384
+ if (!/^\d+$/.test(head)) return null;
7385
+ const toNum = (part) => {
7386
+ if (!part) return 0;
7387
+ return /^\d+$/.test(part) ? Number(part) : 0;
7388
+ };
7389
+ return [
7390
+ Number(head),
7391
+ toNum(match[2]),
7392
+ toNum(match[3])
7393
+ ];
7394
+ };
7395
+ const isDowngrade = (oldSpec, newSpec) => {
7396
+ const oldV = parseSemverMin(oldSpec);
7397
+ const newV = parseSemverMin(newSpec);
7398
+ if (!oldV || !newV) return false;
7399
+ for (let i = 0; i < 3; i++) {
7400
+ if ((newV[i] ?? 0) < (oldV[i] ?? 0)) return true;
7401
+ if ((newV[i] ?? 0) > (oldV[i] ?? 0)) return false;
7402
+ }
7403
+ return false;
7404
+ };
7405
+ const DEP_BUCKETS = [
7406
+ "dependencies",
7407
+ "devDependencies",
7408
+ "peerDependencies",
7409
+ "optionalDependencies"
7410
+ ];
7411
+ const snapshotPackageVersions = (pkg) => {
7412
+ const map = /* @__PURE__ */ new Map();
7413
+ for (const bucket of DEP_BUCKETS) {
7414
+ const deps = pkg[bucket];
7415
+ if (!deps || typeof deps !== "object") continue;
7416
+ for (const [name, version] of Object.entries(deps)) if (typeof version === "string") map.set(`${bucket}:${name}`, version);
7417
+ }
7418
+ return map;
7419
+ };
7420
+ const revertDowngrades = (rootDir, before) => {
7421
+ const pkgPath = path.join(rootDir, "package.json");
7422
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
7423
+ const reverted = [];
7424
+ for (const bucket of DEP_BUCKETS) {
7425
+ const deps = pkg[bucket];
7426
+ if (!deps) continue;
7427
+ for (const [name, version] of Object.entries(deps)) {
7428
+ const prior = before.get(`${bucket}:${name}`);
7429
+ if (!prior) continue;
7430
+ if (isDowngrade(prior, version)) {
7431
+ deps[name] = prior;
7432
+ reverted.push(`${name} ${version} → ${prior}`);
7433
+ }
7434
+ }
7435
+ }
7436
+ if (reverted.length > 0) fs.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
7437
+ return reverted;
7438
+ };
7364
7439
  const runNpmAuditFix = async (rootDir, onProgress) => {
7440
+ const pkgPath = path.join(rootDir, "package.json");
7441
+ const before = snapshotPackageVersions(JSON.parse(fs.readFileSync(pkgPath, "utf-8")));
7365
7442
  onProgress?.("Dependency audit fixes · running npm audit fix (can take a few minutes)");
7366
7443
  const result = await runSubprocess("npm", ["audit", "fix"], {
7367
7444
  cwd: rootDir,
7368
7445
  timeout: INSTALL_TIMEOUT
7369
7446
  });
7370
7447
  if (result.exitCode !== 0 && !result.stdout && !result.stderr) throw new Error("npm audit fix failed");
7448
+ const reverted = revertDowngrades(rootDir, before);
7449
+ if (reverted.length > 0) onProgress?.(`Dependency audit fixes · reverted ${reverted.length} downgrade(s): ${reverted.join(", ")}`);
7371
7450
  onProgress?.("Dependency audit fixes · running npm install");
7372
7451
  const installResult = await runSubprocess("npm", ["install"], {
7373
7452
  cwd: rootDir,
@@ -1,4 +1,4 @@
1
- import { n as ENGINE_INFO, t as APP_VERSION } from "./version-B7_FRirI.js";
1
+ import { n as ENGINE_INFO, t as APP_VERSION } from "./version-G3ekYjY1.js";
2
2
 
3
3
  //#region src/output/json.ts
4
4
  const buildJsonOutput = (results, scoreResult, fileCount, elapsedMs) => {
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 (NON_PRODUCTION_DIR_PATTERN$2.test(relativePath)) continue;
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 (NON_PRODUCTION_DIR_PATTERN$1.test(relativePath)) return [];
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 (NON_PRODUCTION_DIR_PATTERN$1.test(relativePath)) return [];
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++) {
@@ -1585,7 +1589,6 @@ const PHP_DECL_START = /^\s*(?:public|private|protected|static|final|abstract|re
1585
1589
 
1586
1590
  //#endregion
1587
1591
  //#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
1592
  const stripJsdocLine = (line) => line.replace(/^\s*\/\*\*+\s?/, "").replace(/\s*\*+\/\s*$/, "").replace(/^\s*\*\s?/, "").trim();
1590
1593
  const stripLineComment = (line) => line.replace(/^\s*(?:(?:\/\/)|#)\s?/, "");
1591
1594
  const getCommentSyntax = (ext) => {
@@ -1736,13 +1739,21 @@ const looksLikeGoDocComment = (block, ext) => {
1736
1739
  if (!declMatch) return false;
1737
1740
  return ((block.prose.find((l) => l.length > 0) ?? "").split(/\s+/)[0] ?? "") === declMatch[1];
1738
1741
  };
1739
- const DOC_INDICATOR_RE = /`[^`]+`|\|\s*[-:]+\s*\||```|\b(?:note|warning|warn|caveat|example|caution|see):/i;
1742
+ const DOC_INDICATOR_RE = /`[^`]+`|\|\s*[-:]+\s*\||```|\b(?:note|warning|warn|caveat|example|caution|see):|\(e\.g\.[^)]+\)|\(i\.e\.[^)]+\)/i;
1740
1743
  const hasDocIndicator = (block) => {
1741
1744
  const joined = block.prose.join(" ");
1742
1745
  if (DOC_INDICATOR_RE.test(joined)) return true;
1743
1746
  for (const l of block.prose) if (/^[-]\s/.test(l)) return true;
1744
1747
  return false;
1745
1748
  };
1749
+ const hasPreambleSlopSignal = (block) => {
1750
+ const joined = block.prose.join(" ");
1751
+ for (const l of block.prose) {
1752
+ if (EXPLANATORY_OPENERS.test(l)) return true;
1753
+ if (JUSTIFICATION_OPENERS.some((re) => re.test(l))) return true;
1754
+ }
1755
+ return CROSS_REFERENCE_PHRASES.some((re) => re.test(joined));
1756
+ };
1746
1757
  const detectNarrativeInBlock = (block, ext) => {
1747
1758
  if (looksLikeLicenseHeader(block)) return {
1748
1759
  matched: false,
@@ -1782,9 +1793,13 @@ const detectNarrativeInBlock = (block, ext) => {
1782
1793
  matched: false,
1783
1794
  reason: ""
1784
1795
  };
1785
- if (block.prose.length >= 3 && looksLikeDeclarationPreamble(block.nextNonBlankLine, ext)) return {
1796
+ if (block.kind === "line" && block.prose.length >= 3 && looksLikeDeclarationPreamble(block.nextNonBlankLine, ext)) return {
1797
+ matched: true,
1798
+ reason: "multi-line preamble before declaration"
1799
+ };
1800
+ if (block.kind === "jsdoc" && block.prose.length >= 3 && looksLikeDeclarationPreamble(block.nextNonBlankLine, ext) && hasPreambleSlopSignal(block)) return {
1786
1801
  matched: true,
1787
- reason: block.kind === "jsdoc" ? "JSDoc preamble before declaration" : "multi-line preamble before declaration"
1802
+ reason: "JSDoc preamble with slop signal"
1788
1803
  };
1789
1804
  if (CROSS_REFERENCE_PHRASES.some((re) => re.test(joined))) return {
1790
1805
  matched: true,
@@ -1824,7 +1839,7 @@ const detectNarrativeComments = async (context) => {
1824
1839
  const syntax = getCommentSyntax(ext);
1825
1840
  if (!syntax) continue;
1826
1841
  const relativePath = path.relative(context.rootDirectory, filePath);
1827
- if (NON_PRODUCTION_DIR_PATTERN.test(relativePath)) continue;
1842
+ if (isNonProductionPath(relativePath)) continue;
1828
1843
  let content;
1829
1844
  try {
1830
1845
  content = fs.readFileSync(filePath, "utf-8");
@@ -5056,7 +5071,7 @@ const handleAislopBaseline = (input) => {
5056
5071
 
5057
5072
  //#endregion
5058
5073
  //#region src/version.ts
5059
- const APP_VERSION = "0.8.1";
5074
+ const APP_VERSION = "0.8.2";
5060
5075
 
5061
5076
  //#endregion
5062
5077
  //#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.1";
32
+ const APP_VERSION = "0.8.2";
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.1",
3
+ "version": "0.8.2",
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": {