aislop 0.2.1 → 0.3.1
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/README.md +3 -1
- package/dist/cli.js +675 -93
- package/dist/{engine-info-DFze-2GQ.js → engine-info-D19chfzD.js} +1 -1
- package/dist/{expo-doctor-CGXGLgMJ.js → expo-doctor-Cm5892Y8.js} +76 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3406 -2966
- package/dist/{json-Ci_gvHLS.js → json-DD60WkDS.js} +1 -1
- package/package.json +1 -1
- package/dist/expo-doctor-vDz4kh9-.js +0 -127
- package/dist/subprocess-99puEEGl.js +0 -59
package/dist/cli.js
CHANGED
|
@@ -13,6 +13,23 @@ import pc from "picocolors";
|
|
|
13
13
|
import ora from "ora";
|
|
14
14
|
import { emitKeypressEvents } from "node:readline";
|
|
15
15
|
|
|
16
|
+
//#region \0rolldown/runtime.js
|
|
17
|
+
var __defProp = Object.defineProperty;
|
|
18
|
+
var __exportAll = (all, no_symbols) => {
|
|
19
|
+
let target = {};
|
|
20
|
+
for (var name in all) {
|
|
21
|
+
__defProp(target, name, {
|
|
22
|
+
get: all[name],
|
|
23
|
+
enumerable: true
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
if (!no_symbols) {
|
|
27
|
+
__defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
28
|
+
}
|
|
29
|
+
return target;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
//#endregion
|
|
16
33
|
//#region src/config/defaults.ts
|
|
17
34
|
const DEFAULT_CONFIG = {
|
|
18
35
|
version: 1,
|
|
@@ -36,18 +53,18 @@ const DEFAULT_CONFIG = {
|
|
|
36
53
|
},
|
|
37
54
|
scoring: {
|
|
38
55
|
weights: {
|
|
39
|
-
format: .
|
|
40
|
-
lint:
|
|
41
|
-
"code-quality":
|
|
42
|
-
"ai-slop":
|
|
56
|
+
format: .3,
|
|
57
|
+
lint: .6,
|
|
58
|
+
"code-quality": .8,
|
|
59
|
+
"ai-slop": 2.5,
|
|
43
60
|
architecture: 1,
|
|
44
|
-
security:
|
|
61
|
+
security: 1.5
|
|
45
62
|
},
|
|
46
63
|
thresholds: {
|
|
47
64
|
good: 75,
|
|
48
65
|
ok: 50
|
|
49
66
|
},
|
|
50
|
-
smoothing:
|
|
67
|
+
smoothing: 20
|
|
51
68
|
},
|
|
52
69
|
ci: {
|
|
53
70
|
failBelow: 0,
|
|
@@ -77,15 +94,16 @@ security:
|
|
|
77
94
|
|
|
78
95
|
scoring:
|
|
79
96
|
weights:
|
|
80
|
-
format: 0.
|
|
81
|
-
lint:
|
|
82
|
-
code-quality:
|
|
83
|
-
ai-slop:
|
|
97
|
+
format: 0.3
|
|
98
|
+
lint: 0.6
|
|
99
|
+
code-quality: 0.8
|
|
100
|
+
ai-slop: 2.5
|
|
84
101
|
architecture: 1.0
|
|
85
|
-
security:
|
|
102
|
+
security: 1.5
|
|
86
103
|
thresholds:
|
|
87
104
|
good: 75
|
|
88
105
|
ok: 50
|
|
106
|
+
smoothing: 20
|
|
89
107
|
|
|
90
108
|
ci:
|
|
91
109
|
failBelow: 0
|
|
@@ -113,12 +131,12 @@ const DEFAULT_RULES_YAML = `# Architecture rules (BYO)
|
|
|
113
131
|
//#endregion
|
|
114
132
|
//#region src/config/schema.ts
|
|
115
133
|
const DEFAULT_WEIGHTS = {
|
|
116
|
-
format: .
|
|
117
|
-
lint:
|
|
118
|
-
"code-quality":
|
|
119
|
-
"ai-slop":
|
|
134
|
+
format: .3,
|
|
135
|
+
lint: .6,
|
|
136
|
+
"code-quality": .8,
|
|
137
|
+
"ai-slop": 2.5,
|
|
120
138
|
architecture: 1,
|
|
121
|
-
security:
|
|
139
|
+
security: 1.5
|
|
122
140
|
};
|
|
123
141
|
const EnginesSchema = z.object({
|
|
124
142
|
format: z.boolean().default(true),
|
|
@@ -148,7 +166,7 @@ const ScoringSchema = z.object({
|
|
|
148
166
|
good: 75,
|
|
149
167
|
ok: 50
|
|
150
168
|
})),
|
|
151
|
-
smoothing: z.number().nonnegative().default(
|
|
169
|
+
smoothing: z.number().nonnegative().default(20)
|
|
152
170
|
});
|
|
153
171
|
const CiSchema = z.object({
|
|
154
172
|
failBelow: z.number().default(0),
|
|
@@ -181,7 +199,7 @@ const AislopConfigSchema = z.object({
|
|
|
181
199
|
good: 75,
|
|
182
200
|
ok: 50
|
|
183
201
|
},
|
|
184
|
-
smoothing:
|
|
202
|
+
smoothing: 20
|
|
185
203
|
})),
|
|
186
204
|
ci: CiSchema.default(() => ({
|
|
187
205
|
failBelow: 0,
|
|
@@ -1842,10 +1860,10 @@ const codeQualityEngine = {
|
|
|
1842
1860
|
|
|
1843
1861
|
//#endregion
|
|
1844
1862
|
//#region src/engines/format/biome.ts
|
|
1845
|
-
const esmRequire$
|
|
1863
|
+
const esmRequire$3 = createRequire(import.meta.url);
|
|
1846
1864
|
const resolveLocalBiomeScript = () => {
|
|
1847
1865
|
try {
|
|
1848
|
-
const packageJsonPath = esmRequire$
|
|
1866
|
+
const packageJsonPath = esmRequire$3.resolve("@biomejs/biome/package.json");
|
|
1849
1867
|
return path.join(path.dirname(packageJsonPath), "bin", "biome");
|
|
1850
1868
|
} catch {
|
|
1851
1869
|
return null;
|
|
@@ -1919,12 +1937,14 @@ const parseBiomeJsonOutput = (output, rootDir) => {
|
|
|
1919
1937
|
const rawPath = entry.location?.path;
|
|
1920
1938
|
if (!rawPath) continue;
|
|
1921
1939
|
const severity = entry.severity === "error" ? "error" : "warning";
|
|
1940
|
+
const rawMessage = entry.message ?? "";
|
|
1941
|
+
const message = !rawMessage || rawMessage.toLowerCase().includes("would have printed") ? "File is not formatted correctly" : rawMessage;
|
|
1922
1942
|
diagnostics.push({
|
|
1923
1943
|
filePath: path.isAbsolute(rawPath) ? path.relative(rootDir, rawPath) : rawPath,
|
|
1924
1944
|
engine: "format",
|
|
1925
1945
|
rule: "formatting",
|
|
1926
1946
|
severity,
|
|
1927
|
-
message
|
|
1947
|
+
message,
|
|
1928
1948
|
help: "Run `aislop fix` to auto-format",
|
|
1929
1949
|
line: entry.location?.start?.line ?? 0,
|
|
1930
1950
|
column: entry.location?.start?.column ?? 0,
|
|
@@ -2092,7 +2112,7 @@ const fixGofmt = async (rootDirectory) => {
|
|
|
2092
2112
|
//#endregion
|
|
2093
2113
|
//#region src/utils/tooling.ts
|
|
2094
2114
|
const THIS_FILE = fileURLToPath(import.meta.url);
|
|
2095
|
-
const esmRequire$
|
|
2115
|
+
const esmRequire$2 = createRequire(import.meta.url);
|
|
2096
2116
|
const resolvePackageRoot = (startFile) => {
|
|
2097
2117
|
let current = path.dirname(startFile);
|
|
2098
2118
|
while (true) {
|
|
@@ -2123,7 +2143,7 @@ const isToolAvailable = async (toolName) => {
|
|
|
2123
2143
|
};
|
|
2124
2144
|
const isNodePackageAvailable = (packageName) => {
|
|
2125
2145
|
try {
|
|
2126
|
-
esmRequire$
|
|
2146
|
+
esmRequire$2.resolve(`${packageName}/package.json`);
|
|
2127
2147
|
return true;
|
|
2128
2148
|
} catch {
|
|
2129
2149
|
return false;
|
|
@@ -2406,10 +2426,10 @@ const createOxlintConfig = (options) => {
|
|
|
2406
2426
|
|
|
2407
2427
|
//#endregion
|
|
2408
2428
|
//#region src/engines/lint/oxlint.ts
|
|
2409
|
-
const esmRequire = createRequire(import.meta.url);
|
|
2429
|
+
const esmRequire$1 = createRequire(import.meta.url);
|
|
2410
2430
|
const resolveOxlintBinary = () => {
|
|
2411
2431
|
try {
|
|
2412
|
-
const oxlintMainPath = esmRequire.resolve("oxlint");
|
|
2432
|
+
const oxlintMainPath = esmRequire$1.resolve("oxlint");
|
|
2413
2433
|
const oxlintDir = path.resolve(path.dirname(oxlintMainPath), "..");
|
|
2414
2434
|
return path.join(oxlintDir, "bin", "oxlint");
|
|
2415
2435
|
} catch {
|
|
@@ -2417,6 +2437,10 @@ const resolveOxlintBinary = () => {
|
|
|
2417
2437
|
}
|
|
2418
2438
|
};
|
|
2419
2439
|
const parseRuleCode = (code) => {
|
|
2440
|
+
if (!code) return {
|
|
2441
|
+
plugin: "unknown",
|
|
2442
|
+
rule: "unknown"
|
|
2443
|
+
};
|
|
2420
2444
|
const match = code.match(/^(.+)\((.+)\)$/);
|
|
2421
2445
|
if (!match) return {
|
|
2422
2446
|
plugin: "unknown",
|
|
@@ -2444,6 +2468,143 @@ const detectTestFramework = (rootDir) => {
|
|
|
2444
2468
|
} catch {}
|
|
2445
2469
|
return null;
|
|
2446
2470
|
};
|
|
2471
|
+
const extractUnusedVarName = (message) => {
|
|
2472
|
+
const variableMatch = message.match(/Variable '([^']+)' is declared but never used/);
|
|
2473
|
+
if (variableMatch?.[1]) return {
|
|
2474
|
+
name: variableMatch[1],
|
|
2475
|
+
type: "variable"
|
|
2476
|
+
};
|
|
2477
|
+
const paramMatch = message.match(/Parameter '([^']+)' is declared but never used/);
|
|
2478
|
+
if (paramMatch?.[1]) return {
|
|
2479
|
+
name: paramMatch[1],
|
|
2480
|
+
type: "parameter"
|
|
2481
|
+
};
|
|
2482
|
+
const catchMatch = message.match(/Catch parameter '([^']+)' is caught but never used/);
|
|
2483
|
+
if (catchMatch?.[1]) return {
|
|
2484
|
+
name: catchMatch[1],
|
|
2485
|
+
type: "parameter"
|
|
2486
|
+
};
|
|
2487
|
+
return null;
|
|
2488
|
+
};
|
|
2489
|
+
const collectUnusedVarCandidates = (diagnostics) => diagnostics.filter((d) => d.rule === "eslint/no-unused-vars").map((d) => {
|
|
2490
|
+
const extracted = extractUnusedVarName(d.message);
|
|
2491
|
+
if (!extracted || extracted.name.startsWith("_")) return null;
|
|
2492
|
+
return {
|
|
2493
|
+
filePath: d.filePath,
|
|
2494
|
+
line: d.line,
|
|
2495
|
+
column: d.column,
|
|
2496
|
+
name: extracted.name,
|
|
2497
|
+
type: extracted.type
|
|
2498
|
+
};
|
|
2499
|
+
}).filter((candidate) => candidate !== null);
|
|
2500
|
+
const prefixIdentifierOnLine = (line, name, column, type) => {
|
|
2501
|
+
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2502
|
+
if (type === "parameter") {
|
|
2503
|
+
const destructureMatch = line.match(/\{[^}]*\}/);
|
|
2504
|
+
if (destructureMatch) {
|
|
2505
|
+
const { 0: content, index: start } = destructureMatch;
|
|
2506
|
+
const propPattern = new RegExp(`(?<!:\\s*)\\b${escaped}\\b(?!\\s*:)`);
|
|
2507
|
+
if (propPattern.test(content)) {
|
|
2508
|
+
const updated = content.replace(propPattern, `${name}: _${name}`);
|
|
2509
|
+
if (updated !== content) return line.slice(0, start) + updated + line.slice(start + content.length);
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
const paramPattern = new RegExp(`\\b${escaped}\\b`);
|
|
2513
|
+
return paramPattern.test(line) ? line.replace(paramPattern, `_${name}`) : line;
|
|
2514
|
+
}
|
|
2515
|
+
const assignPattern = new RegExp(`(\\s*)(const|let|var)\\s+${escaped}\\s*=\\s*(.+)$`);
|
|
2516
|
+
const assignMatch = line.match(assignPattern);
|
|
2517
|
+
if (assignMatch) {
|
|
2518
|
+
const [, indent, , expression] = assignMatch;
|
|
2519
|
+
if (/await\s/.test(expression) || /\w+\s*\(/.test(expression)) return `${indent}${expression}`;
|
|
2520
|
+
return "";
|
|
2521
|
+
}
|
|
2522
|
+
const destructureMatch = line.match(/\{[^}]*\}/);
|
|
2523
|
+
if (destructureMatch) {
|
|
2524
|
+
const { 0: content, index: start } = destructureMatch;
|
|
2525
|
+
if (new RegExp(`\\b${escaped}\\b`).test(content)) {
|
|
2526
|
+
let updated = content.replace(new RegExp(`\\b${escaped}\\b\\s*,?`), "");
|
|
2527
|
+
updated = updated.replace(/,\s*\},/, "}").replace(/\{,\s*/, "{").replace(/\s*,\s*\}/, "}");
|
|
2528
|
+
if (updated !== content) return line.slice(0, start) + updated + line.slice(start + content.length);
|
|
2529
|
+
}
|
|
2530
|
+
}
|
|
2531
|
+
let bestStart = -1;
|
|
2532
|
+
let bestEnd = -1;
|
|
2533
|
+
let bestDistance = Number.POSITIVE_INFINITY;
|
|
2534
|
+
const target = Math.max(0, column - 1);
|
|
2535
|
+
for (const match of line.matchAll(new RegExp(`\\b${escaped}\\b`, "g"))) {
|
|
2536
|
+
if (match.index === void 0) continue;
|
|
2537
|
+
const start = match.index;
|
|
2538
|
+
const end = start + name.length;
|
|
2539
|
+
if (start > 0 && line[start - 1] === "_") continue;
|
|
2540
|
+
const distance = target >= start && target <= end ? 0 : Math.abs(start - target);
|
|
2541
|
+
if (distance < bestDistance) {
|
|
2542
|
+
bestDistance = distance;
|
|
2543
|
+
bestStart = start;
|
|
2544
|
+
bestEnd = end;
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
if (bestStart < 0 || bestEnd < 0) return line;
|
|
2548
|
+
return `${line.slice(0, bestStart)}_${name}${line.slice(bestEnd)}`;
|
|
2549
|
+
};
|
|
2550
|
+
const applyUnusedVarPrefixFixes = (rootDirectory, candidates) => {
|
|
2551
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
2552
|
+
for (const candidate of candidates) {
|
|
2553
|
+
const absolute = path.isAbsolute(candidate.filePath) ? candidate.filePath : path.join(rootDirectory, candidate.filePath);
|
|
2554
|
+
const entries = byFile.get(absolute) ?? [];
|
|
2555
|
+
entries.push(candidate);
|
|
2556
|
+
byFile.set(absolute, entries);
|
|
2557
|
+
}
|
|
2558
|
+
for (const [filePath, fileCandidates] of byFile.entries()) {
|
|
2559
|
+
if (!fs.existsSync(filePath)) continue;
|
|
2560
|
+
const lines = fs.readFileSync(filePath, "utf-8").split("\n");
|
|
2561
|
+
const ordered = [...fileCandidates].sort((a, b) => {
|
|
2562
|
+
if (a.line !== b.line) return a.line - b.line;
|
|
2563
|
+
return a.column - b.column;
|
|
2564
|
+
});
|
|
2565
|
+
let changed = false;
|
|
2566
|
+
for (const candidate of ordered) {
|
|
2567
|
+
const lineIndex = candidate.line - 1;
|
|
2568
|
+
if (lineIndex < 0 || lineIndex >= lines.length) continue;
|
|
2569
|
+
const current = lines[lineIndex];
|
|
2570
|
+
const updated = prefixIdentifierOnLine(current, candidate.name, candidate.column, candidate.type);
|
|
2571
|
+
if (updated !== current) {
|
|
2572
|
+
lines[lineIndex] = updated;
|
|
2573
|
+
changed = true;
|
|
2574
|
+
}
|
|
2575
|
+
}
|
|
2576
|
+
if (changed) fs.writeFileSync(filePath, lines.join("\n"));
|
|
2577
|
+
}
|
|
2578
|
+
};
|
|
2579
|
+
const removeDuplicateKeyLines = (rootDirectory, diagnostics) => {
|
|
2580
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
2581
|
+
for (const d of diagnostics) {
|
|
2582
|
+
const keyMatch = d.message.match(/Duplicate key '([^']+)'/);
|
|
2583
|
+
if (!keyMatch) continue;
|
|
2584
|
+
const absolute = path.isAbsolute(d.filePath) ? d.filePath : path.join(rootDirectory, d.filePath);
|
|
2585
|
+
const entries = byFile.get(absolute) ?? [];
|
|
2586
|
+
entries.push({
|
|
2587
|
+
key: keyMatch[1],
|
|
2588
|
+
line: d.line
|
|
2589
|
+
});
|
|
2590
|
+
byFile.set(absolute, entries);
|
|
2591
|
+
}
|
|
2592
|
+
for (const [filePath, dupes] of byFile) {
|
|
2593
|
+
if (!fs.existsSync(filePath)) continue;
|
|
2594
|
+
const lines = fs.readFileSync(filePath, "utf-8").split("\n");
|
|
2595
|
+
const toRemove = /* @__PURE__ */ new Set();
|
|
2596
|
+
for (const { key } of dupes) {
|
|
2597
|
+
const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2598
|
+
const keyPattern = new RegExp(`^\\s*['"]?${escaped}['"]?\\s*:|^\\s*${escaped}\\s*:`);
|
|
2599
|
+
const matches = [];
|
|
2600
|
+
for (let i = 0; i < lines.length; i++) if (keyPattern.test(lines[i])) matches.push(i);
|
|
2601
|
+
for (let j = 1; j < matches.length; j++) toRemove.add(matches[j]);
|
|
2602
|
+
}
|
|
2603
|
+
if (toRemove.size === 0) continue;
|
|
2604
|
+
const filtered = lines.filter((_, i) => !toRemove.has(i));
|
|
2605
|
+
fs.writeFileSync(filePath, filtered.join("\n"));
|
|
2606
|
+
}
|
|
2607
|
+
};
|
|
2447
2608
|
const runOxlint = async (context) => {
|
|
2448
2609
|
const configPath = path.join(os.tmpdir(), `aislop-oxlintrc-${process.pid}.json`);
|
|
2449
2610
|
const config = createOxlintConfig({
|
|
@@ -2506,6 +2667,7 @@ const fixOxlint = async (context) => {
|
|
|
2506
2667
|
configPath,
|
|
2507
2668
|
"--fix",
|
|
2508
2669
|
"--fix-suggestions",
|
|
2670
|
+
"--fix-dangerously",
|
|
2509
2671
|
"."
|
|
2510
2672
|
];
|
|
2511
2673
|
const result = await runSubprocess(process.execPath, args, {
|
|
@@ -2513,10 +2675,18 @@ const fixOxlint = async (context) => {
|
|
|
2513
2675
|
timeout: 12e4
|
|
2514
2676
|
});
|
|
2515
2677
|
if (result.exitCode !== 0) throw new Error(result.stderr || result.stdout || `Oxlint exited with code ${result.exitCode}`);
|
|
2678
|
+
const remaining = await runOxlint(context);
|
|
2679
|
+
const candidates = collectUnusedVarCandidates(remaining);
|
|
2680
|
+
if (candidates.length > 0) applyUnusedVarPrefixFixes(context.rootDirectory, candidates);
|
|
2681
|
+
const duplicateKeys = remaining.filter((d) => d.message.startsWith("Duplicate key"));
|
|
2682
|
+
if (duplicateKeys.length > 0) removeDuplicateKeyLines(context.rootDirectory, duplicateKeys);
|
|
2516
2683
|
} finally {
|
|
2517
2684
|
if (fs.existsSync(configPath)) fs.unlinkSync(configPath);
|
|
2518
2685
|
}
|
|
2519
2686
|
};
|
|
2687
|
+
const fixOxlintForce = async (context) => {
|
|
2688
|
+
return fixOxlint(context);
|
|
2689
|
+
};
|
|
2520
2690
|
|
|
2521
2691
|
//#endregion
|
|
2522
2692
|
//#region src/engines/lint/ruff.ts
|
|
@@ -2559,6 +2729,18 @@ const fixRuffLint = async (rootDirectory) => {
|
|
|
2559
2729
|
});
|
|
2560
2730
|
if (result.exitCode !== 0) throw new Error(result.stderr || result.stdout || `ruff check --fix exited with code ${result.exitCode}`);
|
|
2561
2731
|
};
|
|
2732
|
+
const fixRuffLintForce = async (rootDirectory) => {
|
|
2733
|
+
const result = await runSubprocess(resolveToolBinary("ruff"), [
|
|
2734
|
+
"check",
|
|
2735
|
+
"--fix",
|
|
2736
|
+
"--unsafe-fixes",
|
|
2737
|
+
rootDirectory
|
|
2738
|
+
], {
|
|
2739
|
+
cwd: rootDirectory,
|
|
2740
|
+
timeout: 6e4
|
|
2741
|
+
});
|
|
2742
|
+
if (result.exitCode !== 0) throw new Error(result.stderr || result.stdout || `ruff check --fix exited with code ${result.exitCode}`);
|
|
2743
|
+
};
|
|
2562
2744
|
|
|
2563
2745
|
//#endregion
|
|
2564
2746
|
//#region src/engines/lint/index.ts
|
|
@@ -2569,7 +2751,7 @@ const lintEngine = {
|
|
|
2569
2751
|
const { languages, installedTools } = context;
|
|
2570
2752
|
const promises = [];
|
|
2571
2753
|
if (languages.includes("typescript") || languages.includes("javascript")) promises.push(runOxlint(context));
|
|
2572
|
-
if (context.frameworks.includes("expo")) promises.push(
|
|
2754
|
+
if (context.frameworks.includes("expo")) promises.push(Promise.resolve().then(() => expo_doctor_exports).then((mod) => mod.runExpoDoctor(context)));
|
|
2573
2755
|
if (languages.includes("python") && installedTools["ruff"]) promises.push(runRuffLint(context));
|
|
2574
2756
|
if (languages.includes("go") && installedTools["golangci-lint"]) promises.push(runGolangciLint(context));
|
|
2575
2757
|
if (languages.includes("rust") && installedTools["cargo"]) promises.push(runGenericLinter(context, "rust"));
|
|
@@ -3194,11 +3376,11 @@ const logger = {
|
|
|
3194
3376
|
* Application version — injected at build time by tsdown from package.json.
|
|
3195
3377
|
* The fallback should always match the "version" field in package.json.
|
|
3196
3378
|
*/
|
|
3197
|
-
const APP_VERSION = "0.
|
|
3379
|
+
const APP_VERSION = "0.3.1";
|
|
3198
3380
|
|
|
3199
3381
|
//#endregion
|
|
3200
3382
|
//#region src/output/layout.ts
|
|
3201
|
-
const formatElapsed$
|
|
3383
|
+
const formatElapsed$2 = (elapsedMs) => elapsedMs < 1e3 ? `${Math.round(elapsedMs)}ms` : `${(elapsedMs / 1e3).toFixed(1)}s`;
|
|
3202
3384
|
const printCommandHeader = (commandName) => {
|
|
3203
3385
|
logger.log(`${highlighter.bold(`aislop ${commandName.toLowerCase()}`)} ${highlighter.dim(`v${APP_VERSION}`)}`);
|
|
3204
3386
|
logger.break();
|
|
@@ -3213,7 +3395,7 @@ const printProjectMetadata = (project) => {
|
|
|
3213
3395
|
|
|
3214
3396
|
//#endregion
|
|
3215
3397
|
//#region src/output/scan-progress.ts
|
|
3216
|
-
const SPINNER_FRAMES = [
|
|
3398
|
+
const SPINNER_FRAMES$1 = [
|
|
3217
3399
|
"⠋",
|
|
3218
3400
|
"⠙",
|
|
3219
3401
|
"⠹",
|
|
@@ -3226,20 +3408,20 @@ const SPINNER_FRAMES = [
|
|
|
3226
3408
|
"⠏"
|
|
3227
3409
|
];
|
|
3228
3410
|
const shouldRenderLiveScanProgress = () => Boolean(process.stderr.isTTY) && process.env.CI !== "true" && process.env.CI !== "1";
|
|
3229
|
-
const formatElapsed = (elapsedMs) => elapsedMs < 1e3 ? `${Math.round(elapsedMs)}ms` : `${(elapsedMs / 1e3).toFixed(1)}s`;
|
|
3411
|
+
const formatElapsed$1 = (elapsedMs) => elapsedMs < 1e3 ? `${Math.round(elapsedMs)}ms` : `${(elapsedMs / 1e3).toFixed(1)}s`;
|
|
3230
3412
|
const truncateText = (text, maxLength = 52) => text.length <= maxLength ? text : `${text.slice(0, maxLength - 1)}…`;
|
|
3231
3413
|
const getIssueSummary = (result) => {
|
|
3232
3414
|
const errors = result.diagnostics.filter((d) => d.severity === "error").length;
|
|
3233
3415
|
const warnings = result.diagnostics.filter((d) => d.severity === "warning").length;
|
|
3234
|
-
if (errors === 0 && warnings === 0) return `Done (0 issues, ${formatElapsed(result.elapsed)})`;
|
|
3416
|
+
if (errors === 0 && warnings === 0) return `Done (0 issues, ${formatElapsed$1(result.elapsed)})`;
|
|
3235
3417
|
const parts = [];
|
|
3236
3418
|
if (errors > 0) parts.push(`${errors} error${errors === 1 ? "" : "s"}`);
|
|
3237
3419
|
if (warnings > 0) parts.push(`${warnings} warning${warnings === 1 ? "" : "s"}`);
|
|
3238
|
-
return `Done (${parts.join(", ")}, ${formatElapsed(result.elapsed)})`;
|
|
3420
|
+
return `Done (${parts.join(", ")}, ${formatElapsed$1(result.elapsed)})`;
|
|
3239
3421
|
};
|
|
3240
|
-
const getStatusParts = (state, frameIndex) => {
|
|
3422
|
+
const getStatusParts$1 = (state, frameIndex) => {
|
|
3241
3423
|
if (state.status === "running") return {
|
|
3242
|
-
icon: highlighter.info(SPINNER_FRAMES[frameIndex % SPINNER_FRAMES.length]),
|
|
3424
|
+
icon: highlighter.info(SPINNER_FRAMES$1[frameIndex % SPINNER_FRAMES$1.length]),
|
|
3243
3425
|
detail: highlighter.info("Running")
|
|
3244
3426
|
};
|
|
3245
3427
|
if (state.status === "done") {
|
|
@@ -3278,11 +3460,11 @@ const renderScanProgressBlock = (states, frameIndex) => {
|
|
|
3278
3460
|
const headingStatus = completedCount === states.length ? highlighter.dim("complete") : runningCount > 0 ? highlighter.dim(`${runningCount} running`) : highlighter.dim("starting");
|
|
3279
3461
|
return `${[` ${highlighter.bold(`Engines ${completedCount}/${states.length}`)} ${headingStatus}`, ...states.map((state) => {
|
|
3280
3462
|
const label = getEngineLabel(state.engine).padEnd(labelWidth, " ");
|
|
3281
|
-
const { icon, detail } = getStatusParts(state, frameIndex);
|
|
3463
|
+
const { icon, detail } = getStatusParts$1(state, frameIndex);
|
|
3282
3464
|
return ` ${icon} ${label} ${detail}`;
|
|
3283
3465
|
})].join("\n")}\n`;
|
|
3284
3466
|
};
|
|
3285
|
-
const clearRenderedLines$
|
|
3467
|
+
const clearRenderedLines$2 = (lineCount) => {
|
|
3286
3468
|
if (lineCount === 0) return;
|
|
3287
3469
|
process.stderr.write(`\u001B[${lineCount}F`);
|
|
3288
3470
|
for (let index = 0; index < lineCount; index += 1) {
|
|
@@ -3334,7 +3516,7 @@ var ScanProgressRenderer = class {
|
|
|
3334
3516
|
}
|
|
3335
3517
|
render() {
|
|
3336
3518
|
if (!shouldRenderLiveScanProgress()) return;
|
|
3337
|
-
if (this.previousLineCount > 0) clearRenderedLines$
|
|
3519
|
+
if (this.previousLineCount > 0) clearRenderedLines$2(this.previousLineCount);
|
|
3338
3520
|
const output = renderScanProgressBlock(this.states, this.frameIndex);
|
|
3339
3521
|
process.stderr.write(output);
|
|
3340
3522
|
this.previousLineCount = output.split("\n").length - 1;
|
|
@@ -3976,6 +4158,42 @@ const doctorCommand = async (directory) => {
|
|
|
3976
4158
|
printDoctorConclusion(isAllGood());
|
|
3977
4159
|
};
|
|
3978
4160
|
|
|
4161
|
+
//#endregion
|
|
4162
|
+
//#region src/engines/ai-slop/dead-patterns-fix.ts
|
|
4163
|
+
/**
|
|
4164
|
+
* Removes lines flagged as fixable by the trivial-comment and dead-pattern detectors.
|
|
4165
|
+
* Specifically handles:
|
|
4166
|
+
* - ai-slop/trivial-comment (trivial comments that restate the code)
|
|
4167
|
+
* - ai-slop/console-leftover (console.log/debug/info left in production)
|
|
4168
|
+
*/
|
|
4169
|
+
const fixDeadPatterns = async (context) => {
|
|
4170
|
+
const fixable = [...await detectTrivialComments(context), ...await detectDeadPatterns(context)].filter((d) => d.fixable);
|
|
4171
|
+
if (fixable.length === 0) return;
|
|
4172
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
4173
|
+
for (const d of fixable) {
|
|
4174
|
+
const absolute = path.isAbsolute(d.filePath) ? d.filePath : path.join(context.rootDirectory, d.filePath);
|
|
4175
|
+
const lines = byFile.get(absolute) ?? /* @__PURE__ */ new Set();
|
|
4176
|
+
lines.add(d.line);
|
|
4177
|
+
byFile.set(absolute, lines);
|
|
4178
|
+
}
|
|
4179
|
+
for (const [filePath, lineNumbers] of byFile) {
|
|
4180
|
+
if (!fs.existsSync(filePath)) continue;
|
|
4181
|
+
const lines = fs.readFileSync(filePath, "utf-8").split("\n");
|
|
4182
|
+
const filtered = [];
|
|
4183
|
+
for (let i = 0; i < lines.length; i++) {
|
|
4184
|
+
const lineNo = i + 1;
|
|
4185
|
+
if (lineNumbers.has(lineNo)) continue;
|
|
4186
|
+
filtered.push(lines[i]);
|
|
4187
|
+
}
|
|
4188
|
+
const collapsed = [];
|
|
4189
|
+
for (const line of filtered) {
|
|
4190
|
+
if (line.trim() === "" && collapsed.length > 0 && collapsed[collapsed.length - 1].trim() === "") continue;
|
|
4191
|
+
collapsed.push(line);
|
|
4192
|
+
}
|
|
4193
|
+
fs.writeFileSync(filePath, collapsed.join("\n"));
|
|
4194
|
+
}
|
|
4195
|
+
};
|
|
4196
|
+
|
|
3979
4197
|
//#endregion
|
|
3980
4198
|
//#region src/engines/ai-slop/unused-imports-fix.ts
|
|
3981
4199
|
const fixUnusedImports = async (context) => {
|
|
@@ -3997,7 +4215,7 @@ const fixUnusedImports = async (context) => {
|
|
|
3997
4215
|
for (const [lineNo, syms] of symbolsByLine) {
|
|
3998
4216
|
const lineIdx = lineNo - 1;
|
|
3999
4217
|
const allUnused = syms.every((s) => unusedNames.has(s.name));
|
|
4000
|
-
const importSpan =
|
|
4218
|
+
const importSpan = JS_EXTENSIONS.has(analysis.ext) ? getJsImportSpan(lines, lineIdx) : [lineIdx];
|
|
4001
4219
|
if (allUnused) for (const idx of importSpan) linesToRemove.add(idx);
|
|
4002
4220
|
else if (JS_EXTENSIONS.has(analysis.ext)) rewriteJsImportSpan(lines, importSpan, syms, unusedNames);
|
|
4003
4221
|
else if (PY_EXTENSIONS.has(analysis.ext)) rewritePyImportLine(lines, lineIdx, syms, unusedNames);
|
|
@@ -4010,11 +4228,14 @@ const fixUnusedImports = async (context) => {
|
|
|
4010
4228
|
fs.writeFileSync(filePath, filtered.join("\n"));
|
|
4011
4229
|
}
|
|
4012
4230
|
};
|
|
4013
|
-
const
|
|
4231
|
+
const getJsImportSpan = (lines, startIdx) => {
|
|
4014
4232
|
const span = [startIdx];
|
|
4233
|
+
let fullImport = lines[startIdx]?.trim() ?? "";
|
|
4234
|
+
if (!fullImport.startsWith("import ")) return span;
|
|
4015
4235
|
let idx = startIdx + 1;
|
|
4016
|
-
while (
|
|
4236
|
+
while (!fullImport.includes("from") && idx < lines.length) {
|
|
4017
4237
|
span.push(idx);
|
|
4238
|
+
fullImport += ` ${lines[idx].trim()}`;
|
|
4018
4239
|
idx++;
|
|
4019
4240
|
}
|
|
4020
4241
|
return span;
|
|
@@ -4024,23 +4245,31 @@ const rewriteJsImportSpan = (lines, span, syms, unusedNames) => {
|
|
|
4024
4245
|
const namedMatch = fullImport.match(/\{([^}]+)\}/s);
|
|
4025
4246
|
if (!namedMatch) return;
|
|
4026
4247
|
const unusedNamed = syms.filter((s) => !s.isDefault && !s.isNamespace && unusedNames.has(s.name));
|
|
4027
|
-
|
|
4248
|
+
const defaultUnused = syms.some((s) => s.isDefault && unusedNames.has(s.name));
|
|
4249
|
+
if (unusedNamed.length === 0 && !defaultUnused) return;
|
|
4028
4250
|
const unusedNamedSet = new Set(unusedNamed.map((s) => s.name));
|
|
4029
4251
|
const keptSpecifiers = namedMatch[1].split(",").map((s) => s.trim()).filter(Boolean).filter((spec) => {
|
|
4030
4252
|
const parts = spec.split(/\s+as\s+/);
|
|
4031
4253
|
const localName = parts.length > 1 ? parts[1].trim().replace(/^type\s+/, "") : parts[0].trim().replace(/^type\s+/, "");
|
|
4032
4254
|
return !unusedNamedSet.has(localName);
|
|
4033
4255
|
});
|
|
4256
|
+
const fromMatch = fullImport.match(/from\s+["']([^"']+)["'];?/);
|
|
4257
|
+
const fromClause = fromMatch ? `from "${fromMatch[1]}"` : "";
|
|
4034
4258
|
if (keptSpecifiers.length === 0) {
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4259
|
+
const usedDefault = syms.find((s) => s.isDefault && !unusedNames.has(s.name));
|
|
4260
|
+
if (usedDefault) {
|
|
4261
|
+
const defaultMatch = fullImport.match(/^import\s+(\w+)/);
|
|
4262
|
+
const defaultName = defaultMatch ? defaultMatch[1] : usedDefault.name;
|
|
4263
|
+
lines[span[0]] = `import ${defaultName} ${fromClause};`;
|
|
4038
4264
|
for (let i = 1; i < span.length; i++) lines[span[i]] = REMOVE_MARKER;
|
|
4039
|
-
}
|
|
4265
|
+
} else for (const idx of span) lines[idx] = REMOVE_MARKER;
|
|
4266
|
+
return;
|
|
4267
|
+
}
|
|
4268
|
+
if (defaultUnused) {
|
|
4269
|
+
lines[span[0]] = `import { ${keptSpecifiers.join(", ")} } ${fromClause};`;
|
|
4270
|
+
for (let i = 1; i < span.length; i++) lines[span[i]] = REMOVE_MARKER;
|
|
4040
4271
|
return;
|
|
4041
4272
|
}
|
|
4042
|
-
const fromMatch = fullImport.match(/\}\s*(from\s+.+)$/s);
|
|
4043
|
-
const fromClause = fromMatch ? fromMatch[1].trim() : "";
|
|
4044
4273
|
const importPrefix = fullImport.match(/^(import\s+(?:\w+\s*,\s*)?)/);
|
|
4045
4274
|
const prefix = importPrefix ? importPrefix[1] : "import ";
|
|
4046
4275
|
const wasMultiLine = span.length > 1;
|
|
@@ -4048,8 +4277,8 @@ const rewriteJsImportSpan = (lines, span, syms, unusedNames) => {
|
|
|
4048
4277
|
if (wasMultiLine && keptSpecifiers.length > 2) {
|
|
4049
4278
|
const indentMatch = lines[span[1]]?.match(/^(\s+)/);
|
|
4050
4279
|
const indent = indentMatch ? indentMatch[1] : " ";
|
|
4051
|
-
newImport = `${prefix}{\n${keptSpecifiers.map((s) => `${indent}${s},`).join("\n")}\n} ${fromClause}
|
|
4052
|
-
} else newImport = `${prefix}{ ${keptSpecifiers.join(", ")} } ${fromClause}
|
|
4280
|
+
newImport = `${prefix}{\n${keptSpecifiers.map((s) => `${indent}${s},`).join("\n")}\n} ${fromClause};`;
|
|
4281
|
+
} else newImport = `${prefix}{ ${keptSpecifiers.join(", ")} } ${fromClause};`;
|
|
4053
4282
|
lines[span[0]] = newImport;
|
|
4054
4283
|
for (let i = 1; i < span.length; i++) lines[span[i]] = REMOVE_MARKER;
|
|
4055
4284
|
};
|
|
@@ -4070,7 +4299,289 @@ const rewritePyImportLine = (lines, lineIdx, syms, unusedNames) => {
|
|
|
4070
4299
|
};
|
|
4071
4300
|
|
|
4072
4301
|
//#endregion
|
|
4073
|
-
//#region src/
|
|
4302
|
+
//#region src/engines/lint/expo-doctor.ts
|
|
4303
|
+
var expo_doctor_exports = /* @__PURE__ */ __exportAll({ runExpoDoctor: () => runExpoDoctor });
|
|
4304
|
+
const esmRequire = createRequire(import.meta.url);
|
|
4305
|
+
const ISSUE_PREFIX = "✖ ";
|
|
4306
|
+
const resolveExpoDoctorScript = () => {
|
|
4307
|
+
try {
|
|
4308
|
+
const packageJsonPath = esmRequire.resolve("expo-doctor/package.json");
|
|
4309
|
+
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
4310
|
+
const binRelativePath = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.["expo-doctor"];
|
|
4311
|
+
if (!binRelativePath) return null;
|
|
4312
|
+
return path.join(path.dirname(packageJsonPath), binRelativePath);
|
|
4313
|
+
} catch {
|
|
4314
|
+
return null;
|
|
4315
|
+
}
|
|
4316
|
+
};
|
|
4317
|
+
const toRuleSuffix = (title) => {
|
|
4318
|
+
const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
4319
|
+
return slug.length > 0 ? slug : "issue";
|
|
4320
|
+
};
|
|
4321
|
+
const parseIssues = (output) => {
|
|
4322
|
+
const lines = output.split("\n").map((line) => line.trimEnd());
|
|
4323
|
+
const startIndex = lines.findIndex((line) => line.includes("Possible issues detected:"));
|
|
4324
|
+
if (startIndex < 0) return [];
|
|
4325
|
+
const issues = [];
|
|
4326
|
+
let current = null;
|
|
4327
|
+
let inAdvice = false;
|
|
4328
|
+
for (let i = startIndex + 1; i < lines.length; i += 1) {
|
|
4329
|
+
const line = lines[i].trim();
|
|
4330
|
+
if (/^\d+\s+checks failed/.test(line)) break;
|
|
4331
|
+
if (line.length === 0) continue;
|
|
4332
|
+
if (line.startsWith(ISSUE_PREFIX)) {
|
|
4333
|
+
if (current) issues.push(current);
|
|
4334
|
+
current = {
|
|
4335
|
+
title: line.slice(2).trim(),
|
|
4336
|
+
details: [],
|
|
4337
|
+
advice: []
|
|
4338
|
+
};
|
|
4339
|
+
inAdvice = false;
|
|
4340
|
+
continue;
|
|
4341
|
+
}
|
|
4342
|
+
if (!current) continue;
|
|
4343
|
+
if (line === "Advice:") {
|
|
4344
|
+
inAdvice = true;
|
|
4345
|
+
continue;
|
|
4346
|
+
}
|
|
4347
|
+
if (inAdvice) current.advice.push(line);
|
|
4348
|
+
else current.details.push(line);
|
|
4349
|
+
}
|
|
4350
|
+
if (current) issues.push(current);
|
|
4351
|
+
return issues;
|
|
4352
|
+
};
|
|
4353
|
+
const parseConfigError = (output) => {
|
|
4354
|
+
const line = output.split("\n").find((candidate) => candidate.trim().startsWith("ConfigError:"));
|
|
4355
|
+
return line ? line.trim() : null;
|
|
4356
|
+
};
|
|
4357
|
+
const toDiagnostics = (issues) => issues.map((issue) => {
|
|
4358
|
+
const helpParts = [issue.details.join(" ").trim(), issue.advice.join(" ").trim()].filter((part) => part.length > 0);
|
|
4359
|
+
return {
|
|
4360
|
+
filePath: "package.json",
|
|
4361
|
+
engine: "lint",
|
|
4362
|
+
rule: `expo-doctor/${toRuleSuffix(issue.title)}`,
|
|
4363
|
+
severity: "warning",
|
|
4364
|
+
message: `Expo Doctor: ${issue.title}`,
|
|
4365
|
+
help: helpParts.join(" "),
|
|
4366
|
+
line: 0,
|
|
4367
|
+
column: 0,
|
|
4368
|
+
category: "Expo",
|
|
4369
|
+
fixable: false
|
|
4370
|
+
};
|
|
4371
|
+
});
|
|
4372
|
+
const runExpoDoctor = async (context) => {
|
|
4373
|
+
const scriptPath = resolveExpoDoctorScript();
|
|
4374
|
+
let stdout = "";
|
|
4375
|
+
let stderr = "";
|
|
4376
|
+
try {
|
|
4377
|
+
if (scriptPath) {
|
|
4378
|
+
const result = await runSubprocess(process.execPath, [
|
|
4379
|
+
scriptPath,
|
|
4380
|
+
context.rootDirectory,
|
|
4381
|
+
"--verbose"
|
|
4382
|
+
], {
|
|
4383
|
+
cwd: context.rootDirectory,
|
|
4384
|
+
timeout: 12e4
|
|
4385
|
+
});
|
|
4386
|
+
stdout = result.stdout;
|
|
4387
|
+
stderr = result.stderr;
|
|
4388
|
+
} else {
|
|
4389
|
+
const result = await runSubprocess("npx", [
|
|
4390
|
+
"--yes",
|
|
4391
|
+
"expo-doctor",
|
|
4392
|
+
context.rootDirectory,
|
|
4393
|
+
"--verbose"
|
|
4394
|
+
], {
|
|
4395
|
+
cwd: context.rootDirectory,
|
|
4396
|
+
timeout: 12e4
|
|
4397
|
+
});
|
|
4398
|
+
stdout = result.stdout;
|
|
4399
|
+
stderr = result.stderr;
|
|
4400
|
+
}
|
|
4401
|
+
} catch {
|
|
4402
|
+
return [];
|
|
4403
|
+
}
|
|
4404
|
+
const output = [stdout, stderr].filter(Boolean).join("\n");
|
|
4405
|
+
if (!output) return [];
|
|
4406
|
+
const configError = parseConfigError(output);
|
|
4407
|
+
if (configError) return [{
|
|
4408
|
+
filePath: "package.json",
|
|
4409
|
+
engine: "lint",
|
|
4410
|
+
rule: "expo-doctor/config-error",
|
|
4411
|
+
severity: "warning",
|
|
4412
|
+
message: configError,
|
|
4413
|
+
help: "Install project dependencies, then re-run `aislop scan`.",
|
|
4414
|
+
line: 0,
|
|
4415
|
+
column: 0,
|
|
4416
|
+
category: "Expo",
|
|
4417
|
+
fixable: false
|
|
4418
|
+
}];
|
|
4419
|
+
return toDiagnostics(parseIssues(output));
|
|
4420
|
+
};
|
|
4421
|
+
|
|
4422
|
+
//#endregion
|
|
4423
|
+
//#region src/output/fix-progress.ts
|
|
4424
|
+
const SPINNER_FRAMES = [
|
|
4425
|
+
"⠋",
|
|
4426
|
+
"⠙",
|
|
4427
|
+
"⠹",
|
|
4428
|
+
"⠸",
|
|
4429
|
+
"⠼",
|
|
4430
|
+
"⠴",
|
|
4431
|
+
"⠦",
|
|
4432
|
+
"⠧",
|
|
4433
|
+
"⠇",
|
|
4434
|
+
"⠏"
|
|
4435
|
+
];
|
|
4436
|
+
const shouldRenderLive = () => Boolean(process.stderr.isTTY) && process.env.CI !== "true" && process.env.CI !== "1";
|
|
4437
|
+
const formatElapsed = (elapsedMs) => elapsedMs < 1e3 ? `${Math.round(elapsedMs)}ms` : `${(elapsedMs / 1e3).toFixed(1)}s`;
|
|
4438
|
+
const getStepSummary = (result) => {
|
|
4439
|
+
if (result.failed) return `Failed (${result.afterIssues} remain)`;
|
|
4440
|
+
if (result.beforeIssues === 0) return `0 issues, ${formatElapsed(result.elapsedMs)}`;
|
|
4441
|
+
if (result.afterIssues === 0) return `${result.resolvedIssues} resolved, ${formatElapsed(result.elapsedMs)}`;
|
|
4442
|
+
if (result.resolvedIssues > 0) return `${result.resolvedIssues} resolved, ${result.afterIssues} remaining, ${formatElapsed(result.elapsedMs)}`;
|
|
4443
|
+
return `no changes, ${result.afterIssues} issue${result.afterIssues === 1 ? "" : "s"}, ${formatElapsed(result.elapsedMs)}`;
|
|
4444
|
+
};
|
|
4445
|
+
const getStatusParts = (state, frameIndex) => {
|
|
4446
|
+
if (state.status === "running") return {
|
|
4447
|
+
icon: highlighter.info(SPINNER_FRAMES[frameIndex % SPINNER_FRAMES.length]),
|
|
4448
|
+
detail: highlighter.info("Running")
|
|
4449
|
+
};
|
|
4450
|
+
if (state.status === "done" && state.result) return {
|
|
4451
|
+
icon: state.result.afterIssues > 0 ? highlighter.warn("!") : highlighter.success("✓"),
|
|
4452
|
+
detail: highlighter.dim(`Done (${getStepSummary(state.result)})`)
|
|
4453
|
+
};
|
|
4454
|
+
if (state.status === "failed" && state.result) return {
|
|
4455
|
+
icon: highlighter.error("✗"),
|
|
4456
|
+
detail: highlighter.dim(`Failed (${getStepSummary(state.result)})`)
|
|
4457
|
+
};
|
|
4458
|
+
return {
|
|
4459
|
+
icon: highlighter.dim("○"),
|
|
4460
|
+
detail: highlighter.dim("Waiting")
|
|
4461
|
+
};
|
|
4462
|
+
};
|
|
4463
|
+
const clearRenderedLines$1 = (lineCount) => {
|
|
4464
|
+
if (lineCount === 0) return;
|
|
4465
|
+
process.stderr.write(`\u001B[${lineCount}F`);
|
|
4466
|
+
for (let index = 0; index < lineCount; index += 1) {
|
|
4467
|
+
process.stderr.write("\x1B[2K");
|
|
4468
|
+
if (index < lineCount - 1) process.stderr.write("\x1B[1E");
|
|
4469
|
+
}
|
|
4470
|
+
if (lineCount > 1) process.stderr.write(`\u001B[${lineCount - 1}F`);
|
|
4471
|
+
};
|
|
4472
|
+
const renderFixProgressBlock = (states, frameIndex) => {
|
|
4473
|
+
if (states.length === 0) return ` ${highlighter.bold("Fixes 0/0")} ${highlighter.dim("nothing to run")}\n`;
|
|
4474
|
+
const completedCount = states.filter((s) => s.status === "done" || s.status === "failed").length;
|
|
4475
|
+
const runningCount = states.filter((s) => s.status === "running").length;
|
|
4476
|
+
const labelWidth = Math.max(...states.map((s) => s.name.length));
|
|
4477
|
+
const headingStatus = completedCount === states.length ? highlighter.dim("complete") : runningCount > 0 ? highlighter.dim(`${runningCount} running`) : highlighter.dim("starting");
|
|
4478
|
+
return `${[` ${highlighter.bold(`Fixes ${completedCount}/${states.length}`)} ${headingStatus}`, ...states.map((state) => {
|
|
4479
|
+
const label = state.name.padEnd(labelWidth, " ");
|
|
4480
|
+
const { icon, detail } = getStatusParts(state, frameIndex);
|
|
4481
|
+
return ` ${icon} ${label} ${detail}`;
|
|
4482
|
+
})].join("\n")}\n`;
|
|
4483
|
+
};
|
|
4484
|
+
var FixProgressRenderer = class {
|
|
4485
|
+
states;
|
|
4486
|
+
previousLineCount = 0;
|
|
4487
|
+
frameIndex = 0;
|
|
4488
|
+
timer;
|
|
4489
|
+
live;
|
|
4490
|
+
constructor(stepNames) {
|
|
4491
|
+
this.states = stepNames.map((name) => ({
|
|
4492
|
+
name,
|
|
4493
|
+
status: "pending"
|
|
4494
|
+
}));
|
|
4495
|
+
this.live = shouldRenderLive();
|
|
4496
|
+
}
|
|
4497
|
+
isLive() {
|
|
4498
|
+
return this.live;
|
|
4499
|
+
}
|
|
4500
|
+
start() {
|
|
4501
|
+
if (!this.live) return;
|
|
4502
|
+
this.render();
|
|
4503
|
+
this.timer = setInterval(() => {
|
|
4504
|
+
this.frameIndex += 1;
|
|
4505
|
+
this.render();
|
|
4506
|
+
}, 100);
|
|
4507
|
+
this.timer.unref();
|
|
4508
|
+
}
|
|
4509
|
+
markStarted(name) {
|
|
4510
|
+
const state = this.states.find((s) => s.name === name);
|
|
4511
|
+
if (!state) return;
|
|
4512
|
+
state.status = "running";
|
|
4513
|
+
this.render();
|
|
4514
|
+
}
|
|
4515
|
+
markComplete(name, result) {
|
|
4516
|
+
const state = this.states.find((s) => s.name === name);
|
|
4517
|
+
if (!state) return;
|
|
4518
|
+
state.status = result.failed ? "failed" : "done";
|
|
4519
|
+
state.result = result;
|
|
4520
|
+
this.render();
|
|
4521
|
+
}
|
|
4522
|
+
stop() {
|
|
4523
|
+
if (this.timer) {
|
|
4524
|
+
clearInterval(this.timer);
|
|
4525
|
+
this.timer = void 0;
|
|
4526
|
+
}
|
|
4527
|
+
if (!this.live) return;
|
|
4528
|
+
this.render();
|
|
4529
|
+
}
|
|
4530
|
+
render() {
|
|
4531
|
+
if (!this.live) return;
|
|
4532
|
+
if (this.previousLineCount > 0) clearRenderedLines$1(this.previousLineCount);
|
|
4533
|
+
const output = renderFixProgressBlock(this.states, this.frameIndex);
|
|
4534
|
+
process.stderr.write(output);
|
|
4535
|
+
this.previousLineCount = output.split("\n").length - 1;
|
|
4536
|
+
}
|
|
4537
|
+
};
|
|
4538
|
+
|
|
4539
|
+
//#endregion
|
|
4540
|
+
//#region src/commands/fix-force.ts
|
|
4541
|
+
const getJsAuditFixCommand = (rootDirectory) => {
|
|
4542
|
+
if (fs.existsSync(path.join(rootDirectory, "pnpm-lock.yaml"))) return {
|
|
4543
|
+
command: "pnpm",
|
|
4544
|
+
args: ["audit", "--fix"]
|
|
4545
|
+
};
|
|
4546
|
+
if (fs.existsSync(path.join(rootDirectory, "package-lock.json")) || fs.existsSync(path.join(rootDirectory, "package.json"))) return {
|
|
4547
|
+
command: "npm",
|
|
4548
|
+
args: ["audit", "fix"]
|
|
4549
|
+
};
|
|
4550
|
+
return null;
|
|
4551
|
+
};
|
|
4552
|
+
const fixDependencyAudit = async (context) => {
|
|
4553
|
+
const auditFix = getJsAuditFixCommand(context.rootDirectory);
|
|
4554
|
+
if (!auditFix) return;
|
|
4555
|
+
const result = await runSubprocess(auditFix.command, auditFix.args, {
|
|
4556
|
+
cwd: context.rootDirectory,
|
|
4557
|
+
timeout: 18e4
|
|
4558
|
+
});
|
|
4559
|
+
if (result.exitCode !== 0) throw new Error(result.stderr || result.stdout || `${auditFix.command} audit fix failed`);
|
|
4560
|
+
};
|
|
4561
|
+
const fixExpoDependencies = async (context) => {
|
|
4562
|
+
if ((await runSubprocess("npx", [
|
|
4563
|
+
"--yes",
|
|
4564
|
+
"expo",
|
|
4565
|
+
"install",
|
|
4566
|
+
"--fix"
|
|
4567
|
+
], {
|
|
4568
|
+
cwd: context.rootDirectory,
|
|
4569
|
+
timeout: 18e4
|
|
4570
|
+
})).exitCode === 0) return;
|
|
4571
|
+
const checkResult = await runSubprocess("npx", [
|
|
4572
|
+
"--yes",
|
|
4573
|
+
"expo",
|
|
4574
|
+
"install",
|
|
4575
|
+
"--check"
|
|
4576
|
+
], {
|
|
4577
|
+
cwd: context.rootDirectory,
|
|
4578
|
+
timeout: 18e4
|
|
4579
|
+
});
|
|
4580
|
+
if (checkResult.exitCode !== 0) throw new Error(checkResult.stderr || checkResult.stdout || "expo dependency check failed");
|
|
4581
|
+
};
|
|
4582
|
+
|
|
4583
|
+
//#endregion
|
|
4584
|
+
//#region src/commands/fix-step.ts
|
|
4074
4585
|
const uniqueFiles = (diagnostics) => [...new Set(diagnostics.map((d) => d.filePath))];
|
|
4075
4586
|
const uniqueFileCount = (diagnostics) => uniqueFiles(diagnostics).length;
|
|
4076
4587
|
const getFilePreviewLines = (title, files, verbose) => {
|
|
@@ -4094,7 +4605,8 @@ const getStepStatusLine = (result, name, elapsedLabel) => {
|
|
|
4094
4605
|
if (result.resolvedIssues > 0) return highlighter.warn(` ! ${name}: done (${result.resolvedIssues} resolved, ${result.afterIssues} remaining, ${elapsedLabel})`);
|
|
4095
4606
|
return highlighter.warn(` ! ${name}: done (no auto-fix changes, ${result.afterIssues} issue${result.afterIssues === 1 ? "" : "s"}, ${elapsedLabel})`);
|
|
4096
4607
|
};
|
|
4097
|
-
const runFixStep = async (name, detect, applyFix, options) => {
|
|
4608
|
+
const runFixStep = async (name, detect, applyFix, options, progress) => {
|
|
4609
|
+
progress.markStarted(name);
|
|
4098
4610
|
const stepStart = performance.now();
|
|
4099
4611
|
const before = await detect();
|
|
4100
4612
|
let applyError = null;
|
|
@@ -4114,28 +4626,21 @@ const runFixStep = async (name, detect, applyFix, options) => {
|
|
|
4114
4626
|
failed: applyError !== null && before.length === after.length,
|
|
4115
4627
|
elapsedMs
|
|
4116
4628
|
};
|
|
4117
|
-
|
|
4118
|
-
if (
|
|
4119
|
-
const
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4629
|
+
progress.markComplete(name, result);
|
|
4630
|
+
if (!progress.isLive()) {
|
|
4631
|
+
const lines = [getStepStatusLine(result, name, formatElapsed$2(result.elapsedMs))];
|
|
4632
|
+
if (applyError) {
|
|
4633
|
+
const reasonLines = getReasonLines(applyError instanceof Error ? applyError.message : String(applyError));
|
|
4634
|
+
const reasonToPrint = options.verbose ? reasonLines.printable : reasonLines.firstLine;
|
|
4635
|
+
for (const line of reasonToPrint.split("\n")) lines.push(highlighter.dim(` ${line}`));
|
|
4636
|
+
if (!options.verbose && reasonLines.printable !== reasonToPrint) lines.push(highlighter.dim(" Re-run with -d for full tool output."));
|
|
4637
|
+
}
|
|
4638
|
+
lines.push(...getFilePreviewLines("Affected", uniqueFiles(before), options.verbose));
|
|
4639
|
+
if (after.length > 0) lines.push(...getFilePreviewLines("Remaining", uniqueFiles(after), options.verbose));
|
|
4640
|
+
process.stdout.write(`${lines.join("\n")}\n\n`);
|
|
4641
|
+
}
|
|
4127
4642
|
return result;
|
|
4128
4643
|
};
|
|
4129
|
-
const createEngineContext = (rootDirectory, projectInfo, config) => ({
|
|
4130
|
-
rootDirectory,
|
|
4131
|
-
languages: projectInfo.languages,
|
|
4132
|
-
frameworks: projectInfo.frameworks,
|
|
4133
|
-
installedTools: projectInfo.installedTools,
|
|
4134
|
-
config: {
|
|
4135
|
-
quality: config.quality,
|
|
4136
|
-
security: config.security
|
|
4137
|
-
}
|
|
4138
|
-
});
|
|
4139
4644
|
const summarizeFixRun = (steps) => {
|
|
4140
4645
|
const totals = steps.reduce((acc, step) => {
|
|
4141
4646
|
acc.beforeIssues += step.beforeIssues;
|
|
@@ -4155,6 +4660,19 @@ const summarizeFixRun = (steps) => {
|
|
|
4155
4660
|
} else logger.log(` Fix summary: checked ${steps.length} step(s), resolved ${totals.resolvedIssues} issue(s), remaining ${totals.afterIssues}.`);
|
|
4156
4661
|
if (totals.failedSteps === 0 && totals.beforeIssues > 0 && totals.resolvedIssues === 0) logger.dim(" No auto-fixable changes were applied. Current findings are likely manual-fix categories.");
|
|
4157
4662
|
};
|
|
4663
|
+
|
|
4664
|
+
//#endregion
|
|
4665
|
+
//#region src/commands/fix.ts
|
|
4666
|
+
const createEngineContext = (rootDirectory, projectInfo, config) => ({
|
|
4667
|
+
rootDirectory,
|
|
4668
|
+
languages: projectInfo.languages,
|
|
4669
|
+
frameworks: projectInfo.frameworks,
|
|
4670
|
+
installedTools: projectInfo.installedTools,
|
|
4671
|
+
config: {
|
|
4672
|
+
quality: config.quality,
|
|
4673
|
+
security: config.security
|
|
4674
|
+
}
|
|
4675
|
+
});
|
|
4158
4676
|
const fixCommand = async (directory, config, options = {
|
|
4159
4677
|
verbose: false,
|
|
4160
4678
|
showHeader: true
|
|
@@ -4166,38 +4684,98 @@ const fixCommand = async (directory, config, options = {
|
|
|
4166
4684
|
printProjectMetadata(projectInfo);
|
|
4167
4685
|
const context = createEngineContext(resolvedDir, projectInfo, config);
|
|
4168
4686
|
const steps = [];
|
|
4169
|
-
|
|
4687
|
+
const stepNames = [];
|
|
4688
|
+
if (config.engines["ai-slop"]) {
|
|
4689
|
+
stepNames.push("Unused imports");
|
|
4690
|
+
stepNames.push("Dead code & comments");
|
|
4691
|
+
}
|
|
4692
|
+
if (config.engines.lint) {
|
|
4693
|
+
if (projectInfo.languages.includes("typescript") || projectInfo.languages.includes("javascript")) stepNames.push("JS/TS lint fixes");
|
|
4694
|
+
if (projectInfo.languages.includes("python") && projectInfo.installedTools.ruff) stepNames.push("Python lint fixes");
|
|
4695
|
+
}
|
|
4696
|
+
if (config.engines["code-quality"]) {
|
|
4697
|
+
if (projectInfo.languages.includes("typescript") || projectInfo.languages.includes("javascript")) stepNames.push("Unused dependencies");
|
|
4698
|
+
}
|
|
4699
|
+
if (config.engines.format) {
|
|
4700
|
+
if (projectInfo.languages.includes("typescript") || projectInfo.languages.includes("javascript")) stepNames.push("JS/TS formatting");
|
|
4701
|
+
if (projectInfo.languages.includes("python") && projectInfo.installedTools.ruff) stepNames.push("Python formatting");
|
|
4702
|
+
if (projectInfo.languages.includes("go") && projectInfo.installedTools.gofmt) stepNames.push("Go formatting");
|
|
4703
|
+
}
|
|
4704
|
+
if (options.force) {
|
|
4705
|
+
if (config.engines.security) stepNames.push("Dependency audit fixes");
|
|
4706
|
+
if (projectInfo.frameworks.includes("expo")) stepNames.push("Expo dependency alignment");
|
|
4707
|
+
}
|
|
4708
|
+
const progress = new FixProgressRenderer(stepNames);
|
|
4709
|
+
progress.start();
|
|
4710
|
+
if (config.engines["ai-slop"]) {
|
|
4711
|
+
steps.push(await runFixStep("Unused imports", () => detectUnusedImports(context), () => fixUnusedImports(context), options, progress));
|
|
4712
|
+
const detectFixableSlop = async () => {
|
|
4713
|
+
const [comments, dead] = await Promise.all([detectTrivialComments(context), detectDeadPatterns(context)]);
|
|
4714
|
+
return [...comments, ...dead].filter((d) => d.fixable);
|
|
4715
|
+
};
|
|
4716
|
+
steps.push(await runFixStep("Dead code & comments", detectFixableSlop, () => fixDeadPatterns(context), options, progress));
|
|
4717
|
+
}
|
|
4170
4718
|
if (config.engines.lint) {
|
|
4171
|
-
if (projectInfo.languages.includes("typescript") || projectInfo.languages.includes("javascript")) steps.push(await runFixStep("JS/TS lint fixes", () => runOxlint(context), () => fixOxlint(context), options));
|
|
4172
|
-
if (projectInfo.languages.includes("python") && projectInfo.installedTools.ruff) steps.push(await runFixStep("Python lint fixes", () => runRuffLint(context), () => fixRuffLint(resolvedDir), options));
|
|
4719
|
+
if (projectInfo.languages.includes("typescript") || projectInfo.languages.includes("javascript")) steps.push(await runFixStep("JS/TS lint fixes", () => runOxlint(context), () => options.force ? fixOxlintForce(context) : fixOxlint(context), options, progress));
|
|
4720
|
+
if (projectInfo.languages.includes("python") && projectInfo.installedTools.ruff) steps.push(await runFixStep("Python lint fixes", () => runRuffLint(context), () => options.force ? fixRuffLintForce(resolvedDir) : fixRuffLint(resolvedDir), options, progress));
|
|
4173
4721
|
else if (projectInfo.languages.includes("python")) logger.warn(" Python detected but ruff is not installed; skipping Python lint fixes.");
|
|
4174
4722
|
}
|
|
4175
4723
|
if (config.engines["code-quality"]) {
|
|
4176
|
-
if (projectInfo.languages.includes("typescript") || projectInfo.languages.includes("javascript")) steps.push(await runFixStep("Unused dependencies", () => runKnipDependencyCheck(resolvedDir), () => fixUnusedDependencies(resolvedDir), options));
|
|
4724
|
+
if (projectInfo.languages.includes("typescript") || projectInfo.languages.includes("javascript")) steps.push(await runFixStep("Unused dependencies", () => runKnipDependencyCheck(resolvedDir), () => fixUnusedDependencies(resolvedDir), options, progress));
|
|
4177
4725
|
}
|
|
4178
4726
|
if (config.engines.format) {
|
|
4179
|
-
if (projectInfo.languages.includes("typescript") || projectInfo.languages.includes("javascript")) steps.push(await runFixStep("JS/TS formatting", () => runBiomeFormat(context), () => fixBiomeFormat(context), options));
|
|
4180
|
-
if (projectInfo.languages.includes("python") && projectInfo.installedTools.ruff) steps.push(await runFixStep("Python formatting", () => runRuffFormat(context), () => fixRuffFormat(resolvedDir), options));
|
|
4727
|
+
if (projectInfo.languages.includes("typescript") || projectInfo.languages.includes("javascript")) steps.push(await runFixStep("JS/TS formatting", () => runBiomeFormat(context), () => fixBiomeFormat(context), options, progress));
|
|
4728
|
+
if (projectInfo.languages.includes("python") && projectInfo.installedTools.ruff) steps.push(await runFixStep("Python formatting", () => runRuffFormat(context), () => fixRuffFormat(resolvedDir), options, progress));
|
|
4181
4729
|
else if (projectInfo.languages.includes("python")) logger.warn(" Python detected but ruff is not installed; skipping Python formatting fixes.");
|
|
4182
|
-
if (projectInfo.languages.includes("go") && projectInfo.installedTools.gofmt) steps.push(await runFixStep("Go formatting", () => runGofmt(context), () => fixGofmt(resolvedDir), options));
|
|
4730
|
+
if (projectInfo.languages.includes("go") && projectInfo.installedTools.gofmt) steps.push(await runFixStep("Go formatting", () => runGofmt(context), () => fixGofmt(resolvedDir), options, progress));
|
|
4183
4731
|
else if (projectInfo.languages.includes("go")) logger.warn(" Go detected but gofmt is not installed; skipping Go formatting fixes.");
|
|
4184
4732
|
}
|
|
4733
|
+
if (options.force) {
|
|
4734
|
+
if (config.engines.security) steps.push(await runFixStep("Dependency audit fixes", () => runDependencyAudit(context), () => fixDependencyAudit(context), options, progress));
|
|
4735
|
+
if (projectInfo.frameworks.includes("expo")) steps.push(await runFixStep("Expo dependency alignment", () => runExpoDoctor(context), () => fixExpoDependencies(context), options, progress));
|
|
4736
|
+
}
|
|
4737
|
+
progress.stop();
|
|
4738
|
+
const totalResolved = steps.reduce((sum, s) => sum + s.resolvedIssues, 0);
|
|
4185
4739
|
if (steps.length === 0) logger.dim(" No applicable auto-fixers found for this project.");
|
|
4186
4740
|
else {
|
|
4187
4741
|
logger.break();
|
|
4188
4742
|
summarizeFixRun(steps);
|
|
4189
4743
|
}
|
|
4190
|
-
if (!isTelemetryDisabled(config.telemetry?.enabled)) {
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
fixResolved: totalResolved
|
|
4197
|
-
});
|
|
4198
|
-
}
|
|
4744
|
+
if (!isTelemetryDisabled(config.telemetry?.enabled)) trackEvent({
|
|
4745
|
+
command: "fix",
|
|
4746
|
+
languages: projectInfo.languages,
|
|
4747
|
+
fixSteps: steps.length,
|
|
4748
|
+
fixResolved: totalResolved
|
|
4749
|
+
});
|
|
4199
4750
|
logger.break();
|
|
4200
|
-
|
|
4751
|
+
const configDir = findConfigDir(resolvedDir);
|
|
4752
|
+
const rulesPath = configDir ? path.join(configDir, RULES_FILE) : void 0;
|
|
4753
|
+
const engineConfig = {
|
|
4754
|
+
quality: config.quality,
|
|
4755
|
+
security: config.security,
|
|
4756
|
+
architectureRulesPath: config.engines.architecture ? rulesPath : void 0
|
|
4757
|
+
};
|
|
4758
|
+
const allDiagnostics = (await runEngines({
|
|
4759
|
+
rootDirectory: resolvedDir,
|
|
4760
|
+
languages: projectInfo.languages,
|
|
4761
|
+
frameworks: projectInfo.frameworks,
|
|
4762
|
+
installedTools: projectInfo.installedTools,
|
|
4763
|
+
config: engineConfig
|
|
4764
|
+
}, config.engines, () => {}, () => {})).flatMap((r) => r.diagnostics);
|
|
4765
|
+
const scoreResult = calculateScore(allDiagnostics, config.scoring.weights, config.scoring.thresholds, projectInfo.sourceFileCount, config.scoring.smoothing);
|
|
4766
|
+
const errors = allDiagnostics.filter((d) => d.severity === "error").length;
|
|
4767
|
+
const warnings = allDiagnostics.filter((d) => d.severity === "warning").length;
|
|
4768
|
+
const fixable = allDiagnostics.filter((d) => d.fixable).length;
|
|
4769
|
+
const manual = errors + warnings - fixable;
|
|
4770
|
+
const scoreColor = scoreResult.score >= config.scoring.thresholds.good ? highlighter.success : scoreResult.score >= config.scoring.thresholds.ok ? highlighter.warn : highlighter.error;
|
|
4771
|
+
logger.log(highlighter.dim("------------------------------------------------------------"));
|
|
4772
|
+
logger.log(highlighter.bold("Result"));
|
|
4773
|
+
logger.log(` Score: ${scoreColor(`${scoreResult.score}/100`)} ${scoreColor(`(${scoreResult.label})`)}`);
|
|
4774
|
+
logger.log(` Resolved: ${highlighter.success(String(totalResolved))} issue${totalResolved === 1 ? "" : "s"}`);
|
|
4775
|
+
logger.log(` Remaining: ${errors + warnings > 0 ? highlighter.warn(String(errors + warnings)) : highlighter.success("0")} (${errors} error${errors === 1 ? "" : "s"}, ${warnings} warning${warnings === 1 ? "" : "s"})`);
|
|
4776
|
+
if (fixable > 0) logger.log(` Auto-fixable: ${highlighter.info(String(fixable))}`);
|
|
4777
|
+
if (manual > 0) logger.log(` Manual effort: ${highlighter.dim(String(manual))}`);
|
|
4778
|
+
logger.log(highlighter.dim("------------------------------------------------------------"));
|
|
4201
4779
|
logger.break();
|
|
4202
4780
|
};
|
|
4203
4781
|
|
|
@@ -4558,7 +5136,7 @@ const program = new Command().name("aislop").description("The unified code quali
|
|
|
4558
5136
|
}).addHelpText("after", `
|
|
4559
5137
|
${highlighter.dim("Commands:")}
|
|
4560
5138
|
aislop scan [dir] Full code quality scan
|
|
4561
|
-
aislop fix [dir] Auto-fix
|
|
5139
|
+
aislop fix [dir] Auto-fix ai slop in codebase
|
|
4562
5140
|
aislop init [dir] Initialize aislop config
|
|
4563
5141
|
aislop doctor [dir] Check installed tools
|
|
4564
5142
|
aislop ci [dir] CI-friendly JSON output
|
|
@@ -4570,7 +5148,8 @@ ${highlighter.dim("Examples:")}
|
|
|
4570
5148
|
aislop scan -d Scan with file/line details
|
|
4571
5149
|
aislop scan --changes Scan only changed files
|
|
4572
5150
|
aislop scan --staged Scan only staged files (for hooks)
|
|
4573
|
-
aislop fix Auto-fix
|
|
5151
|
+
aislop fix Auto-fix ai slop in codebase
|
|
5152
|
+
aislop fix --force Run aggressive fixes (includes audit and dependency alignment)
|
|
4574
5153
|
aislop ci JSON output for CI pipelines
|
|
4575
5154
|
`);
|
|
4576
5155
|
program.command("scan [directory]").description("Run full code quality scan").option("--changes", "only scan changed files").option("--staged", "only scan staged files").option("-d, --verbose", "show file details per rule").option("--json", "output JSON").action(async (directory = ".", _flags, command) => {
|
|
@@ -4586,9 +5165,12 @@ program.command("scan [directory]").description("Run full code quality scan").op
|
|
|
4586
5165
|
process.exit(exitCode);
|
|
4587
5166
|
}
|
|
4588
5167
|
});
|
|
4589
|
-
program.command("fix [directory]").description("Auto-fix
|
|
5168
|
+
program.command("fix [directory]").description("Auto-fix ai slop in codebase").option("-d, --verbose", "show detailed fix progress").option("-f, --force", "run aggressive fixes (audit and framework dependency alignment)").action(async (directory = ".", _flags, command) => {
|
|
4590
5169
|
const flags = command.optsWithGlobals();
|
|
4591
|
-
await fixCommand(directory, loadConfig(directory), {
|
|
5170
|
+
await fixCommand(directory, loadConfig(directory), {
|
|
5171
|
+
verbose: Boolean(flags.verbose),
|
|
5172
|
+
force: Boolean(flags.force)
|
|
5173
|
+
});
|
|
4592
5174
|
});
|
|
4593
5175
|
program.command("init [directory]").description("Initialize aislop config in project").action(async (directory = ".") => {
|
|
4594
5176
|
await initCommand(directory);
|
|
@@ -4613,4 +5195,4 @@ const main = async () => {
|
|
|
4613
5195
|
main();
|
|
4614
5196
|
|
|
4615
5197
|
//#endregion
|
|
4616
|
-
export { ENGINE_INFO as n,
|
|
5198
|
+
export { ENGINE_INFO as n, APP_VERSION as t };
|