aislop 0.8.3 → 0.9.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 +1 -0
- package/dist/cli.js +1193 -304
- package/dist/index.d.ts +4 -0
- package/dist/index.js +1007 -237
- package/dist/{json-D8h2EZW6.js → json-B_2_Zt7I.js} +1 -1
- package/dist/{json-BbMwrgyd.js → json-OIzja7OM.js} +1 -1
- package/dist/mcp.js +669 -155
- package/dist/{typecheck-B1MXNAy-.js → typecheck-wVSohmOX.js} +1 -1
- package/dist/{version-BynHxO1X.js → version-CBcgcofs.js} +1 -1
- package/package.json +2 -1
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-CBcgcofs.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";
|
|
@@ -15,6 +15,7 @@ import { fileURLToPath } from "node:url";
|
|
|
15
15
|
import { performance } from "node:perf_hooks";
|
|
16
16
|
import os from "node:os";
|
|
17
17
|
import ts from "typescript";
|
|
18
|
+
import { randomUUID } from "node:crypto";
|
|
18
19
|
import { isCancel, multiselect, select, text } from "@clack/prompts";
|
|
19
20
|
|
|
20
21
|
//#region src/config/defaults.ts
|
|
@@ -27,6 +28,7 @@ const DEFAULT_CONFIG = {
|
|
|
27
28
|
"build",
|
|
28
29
|
"coverage"
|
|
29
30
|
],
|
|
31
|
+
include: [],
|
|
30
32
|
engines: {
|
|
31
33
|
format: true,
|
|
32
34
|
lint: true,
|
|
@@ -62,7 +64,7 @@ const DEFAULT_CONFIG = {
|
|
|
62
64
|
smoothing: 20
|
|
63
65
|
},
|
|
64
66
|
ci: {
|
|
65
|
-
failBelow:
|
|
67
|
+
failBelow: 70,
|
|
66
68
|
format: "json"
|
|
67
69
|
},
|
|
68
70
|
telemetry: { enabled: true }
|
|
@@ -188,7 +190,7 @@ const ScoringSchema = z.object({
|
|
|
188
190
|
smoothing: z.number().nonnegative().default(20)
|
|
189
191
|
});
|
|
190
192
|
const CiSchema = z.object({
|
|
191
|
-
failBelow: z.number().default(
|
|
193
|
+
failBelow: z.number().default(70),
|
|
192
194
|
format: z.enum(["json"]).default("json")
|
|
193
195
|
});
|
|
194
196
|
const TelemetrySchema = z.object({ enabled: z.boolean().default(true) });
|
|
@@ -222,7 +224,7 @@ const AislopConfigSchema = z.object({
|
|
|
222
224
|
smoothing: 20
|
|
223
225
|
})),
|
|
224
226
|
ci: CiSchema.default(() => ({
|
|
225
|
-
failBelow:
|
|
227
|
+
failBelow: 70,
|
|
226
228
|
format: "json"
|
|
227
229
|
})),
|
|
228
230
|
telemetry: TelemetrySchema.default(() => ({ enabled: true })),
|
|
@@ -232,7 +234,8 @@ const AislopConfigSchema = z.object({
|
|
|
232
234
|
"dist",
|
|
233
235
|
"build",
|
|
234
236
|
"coverage"
|
|
235
|
-
])
|
|
237
|
+
]),
|
|
238
|
+
include: z.array(z.string()).default(() => [])
|
|
236
239
|
});
|
|
237
240
|
const defaults = AislopConfigSchema.parse({});
|
|
238
241
|
/**
|
|
@@ -560,31 +563,68 @@ const EXCLUDED_DIRS = [
|
|
|
560
563
|
"dist",
|
|
561
564
|
"build",
|
|
562
565
|
".git",
|
|
566
|
+
".agents",
|
|
563
567
|
"vendor",
|
|
568
|
+
"examples",
|
|
569
|
+
"example",
|
|
570
|
+
"demos",
|
|
571
|
+
"demo",
|
|
572
|
+
"bench",
|
|
573
|
+
"benches",
|
|
574
|
+
"benchmarks",
|
|
575
|
+
"fixtures",
|
|
576
|
+
"fixture",
|
|
577
|
+
"samples",
|
|
578
|
+
"sample",
|
|
579
|
+
"tutorials",
|
|
580
|
+
"tutorial",
|
|
581
|
+
"code_samples",
|
|
582
|
+
"code-samples",
|
|
583
|
+
"notebooks",
|
|
564
584
|
"tests",
|
|
565
585
|
"test",
|
|
566
586
|
"__tests__",
|
|
567
587
|
"__test__",
|
|
568
588
|
"spec",
|
|
569
589
|
"__mocks__",
|
|
570
|
-
"fixtures",
|
|
571
590
|
"test_data",
|
|
572
591
|
".next",
|
|
573
592
|
".nuxt",
|
|
574
593
|
"coverage",
|
|
575
|
-
".turbo"
|
|
594
|
+
".turbo",
|
|
595
|
+
"public"
|
|
576
596
|
];
|
|
577
597
|
const FIND_PRUNE_DIRS = [
|
|
578
598
|
"node_modules",
|
|
579
599
|
"dist",
|
|
580
600
|
"build",
|
|
581
601
|
".git",
|
|
602
|
+
".agents",
|
|
582
603
|
"vendor",
|
|
604
|
+
"examples",
|
|
605
|
+
"example",
|
|
606
|
+
"demos",
|
|
607
|
+
"demo",
|
|
608
|
+
"bench",
|
|
609
|
+
"benches",
|
|
610
|
+
"benchmarks",
|
|
611
|
+
"fixtures",
|
|
612
|
+
"fixture",
|
|
613
|
+
"samples",
|
|
614
|
+
"sample",
|
|
615
|
+
"tutorials",
|
|
616
|
+
"tutorial",
|
|
617
|
+
"code_samples",
|
|
618
|
+
"code-samples",
|
|
619
|
+
"notebooks",
|
|
583
620
|
".next",
|
|
584
621
|
".nuxt",
|
|
585
622
|
"coverage",
|
|
586
|
-
".turbo"
|
|
623
|
+
".turbo",
|
|
624
|
+
"public"
|
|
587
625
|
];
|
|
626
|
+
const BUILD_CACHE_FILE_PATTERNS = [/\.timestamp-\d+-[a-z0-9]+\.[mc]?js$/i];
|
|
627
|
+
const isBuildCacheFile = (filePath) => BUILD_CACHE_FILE_PATTERNS.some((pattern) => pattern.test(filePath));
|
|
588
628
|
const TEST_FILE_PATTERNS = [
|
|
589
629
|
/(?:^|\/).*\.test\.[^/]+$/i,
|
|
590
630
|
/(?:^|\/).*\.spec\.[^/]+$/i,
|
|
@@ -609,6 +649,7 @@ const hasAllowedExtension = (filePath, extraExtensions) => {
|
|
|
609
649
|
return SOURCE_EXTENSIONS.has(extension) || extraExtensions.has(extension);
|
|
610
650
|
};
|
|
611
651
|
const isExcludedPath = (filePath) => EXCLUDED_DIRS.some((dir) => filePath === dir || filePath.startsWith(`${dir}/`) || filePath.includes(`/${dir}/`));
|
|
652
|
+
const isExcludedFromScan = (relativePath) => isExcludedPath(relativePath) || isBuildCacheFile(relativePath);
|
|
612
653
|
const isTestFile$2 = (filePath) => TEST_FILE_PATTERNS.some((pattern) => pattern.test(filePath));
|
|
613
654
|
const getIgnoredPaths = (rootDirectory, files) => {
|
|
614
655
|
if (files.length === 0) return /* @__PURE__ */ new Set();
|
|
@@ -667,7 +708,7 @@ const normalizeExcludePatterns = (patterns) => {
|
|
|
667
708
|
return [p];
|
|
668
709
|
});
|
|
669
710
|
};
|
|
670
|
-
const filterProjectFiles = (rootDirectory, files, extraExtensions = [], exclude = []) => {
|
|
711
|
+
const filterProjectFiles = (rootDirectory, files, extraExtensions = [], exclude = [], include = []) => {
|
|
671
712
|
const extraSet = new Set(extraExtensions);
|
|
672
713
|
const normalizedFiles = files.map((file) => {
|
|
673
714
|
const absolutePath = path.isAbsolute(file) ? file : path.resolve(rootDirectory, file);
|
|
@@ -682,8 +723,16 @@ const filterProjectFiles = (rootDirectory, files, extraExtensions = [], exclude
|
|
|
682
723
|
if (!normalizedExcludePatterns.length) return false;
|
|
683
724
|
return micromatch.isMatch(relativePath, normalizedExcludePatterns, { dot: true });
|
|
684
725
|
};
|
|
726
|
+
const hasIncludePatterns = include.length > 0;
|
|
727
|
+
const isUserIncluded = (relativePath) => {
|
|
728
|
+
if (!hasIncludePatterns) return true;
|
|
729
|
+
return micromatch.isMatch(relativePath, include, { dot: true });
|
|
730
|
+
};
|
|
685
731
|
return normalizedFiles.filter(({ absolutePath, relativePath }) => {
|
|
686
|
-
|
|
732
|
+
if (!fs.existsSync(absolutePath) || !isWithinProject(relativePath) || isExcludedPath(relativePath) || isTestFile$2(relativePath) || ignoredPaths.has(relativePath)) return false;
|
|
733
|
+
if (!isUserIncluded(relativePath)) return false;
|
|
734
|
+
if (isUserExcluded(relativePath)) return false;
|
|
735
|
+
return hasAllowedExtension(relativePath, extraSet);
|
|
687
736
|
}).map(({ absolutePath }) => absolutePath);
|
|
688
737
|
};
|
|
689
738
|
const filterExplicitFiles = (rootDirectory, files, extraExtensions = []) => {
|
|
@@ -1868,6 +1917,86 @@ const PYTHON_IMPORT_TO_PIP = {
|
|
|
1868
1917
|
redis: "redis"
|
|
1869
1918
|
};
|
|
1870
1919
|
|
|
1920
|
+
//#endregion
|
|
1921
|
+
//#region src/engines/ai-slop/python-manifest.ts
|
|
1922
|
+
const addPyDep = (pyDeps, name) => {
|
|
1923
|
+
const normalized = name.toLowerCase().replace(/_/g, "-");
|
|
1924
|
+
pyDeps.add(normalized);
|
|
1925
|
+
};
|
|
1926
|
+
const collectFromRequirementsTxt = (rootDir, pyDeps) => {
|
|
1927
|
+
const reqPath = path.join(rootDir, "requirements.txt");
|
|
1928
|
+
if (!fs.existsSync(reqPath)) return false;
|
|
1929
|
+
try {
|
|
1930
|
+
const content = fs.readFileSync(reqPath, "utf-8");
|
|
1931
|
+
for (const line of content.split("\n")) {
|
|
1932
|
+
const trimmed = line.trim();
|
|
1933
|
+
if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("-")) continue;
|
|
1934
|
+
const match = trimmed.match(/^([a-zA-Z0-9_\-.]+)/);
|
|
1935
|
+
if (match) addPyDep(pyDeps, match[1]);
|
|
1936
|
+
}
|
|
1937
|
+
return true;
|
|
1938
|
+
} catch {
|
|
1939
|
+
return false;
|
|
1940
|
+
}
|
|
1941
|
+
};
|
|
1942
|
+
const collectFromPyproject = (rootDir, pyDeps) => {
|
|
1943
|
+
const pyprojPath = path.join(rootDir, "pyproject.toml");
|
|
1944
|
+
if (!fs.existsSync(pyprojPath)) return false;
|
|
1945
|
+
try {
|
|
1946
|
+
const content = fs.readFileSync(pyprojPath, "utf-8");
|
|
1947
|
+
const projectNameMatch = content.match(/\[project\][\s\S]*?^\s*name\s*=\s*["']([^"']+)/m);
|
|
1948
|
+
if (projectNameMatch) addPyDep(pyDeps, projectNameMatch[1]);
|
|
1949
|
+
const poetryNameMatch = content.match(/\[tool\.poetry\][\s\S]*?^\s*name\s*=\s*["']([^"']+)/m);
|
|
1950
|
+
if (poetryNameMatch) addPyDep(pyDeps, poetryNameMatch[1]);
|
|
1951
|
+
const pep621 = content.match(/^\s*dependencies\s*=\s*\[([\s\S]*?)\]/m);
|
|
1952
|
+
if (pep621) for (const line of pep621[1].split("\n")) {
|
|
1953
|
+
const m = line.match(/["']\s*([a-zA-Z0-9_\-.]+)/);
|
|
1954
|
+
if (m) addPyDep(pyDeps, m[1]);
|
|
1955
|
+
}
|
|
1956
|
+
const poetryRe = /\[tool\.poetry(?:\.group\.[a-z]+)?\.dependencies\]([\s\S]*?)(?=\n\[|$)/g;
|
|
1957
|
+
let match = poetryRe.exec(content);
|
|
1958
|
+
while (match !== null) {
|
|
1959
|
+
for (const line of match[1].split("\n")) {
|
|
1960
|
+
const m = line.trim().match(/^([a-zA-Z0-9_\-.]+)\s*=/);
|
|
1961
|
+
if (m && m[1] !== "python") addPyDep(pyDeps, m[1]);
|
|
1962
|
+
}
|
|
1963
|
+
match = poetryRe.exec(content);
|
|
1964
|
+
}
|
|
1965
|
+
return true;
|
|
1966
|
+
} catch {
|
|
1967
|
+
return false;
|
|
1968
|
+
}
|
|
1969
|
+
};
|
|
1970
|
+
const collectFromPipfile = (rootDir, pyDeps) => {
|
|
1971
|
+
const pipfilePath = path.join(rootDir, "Pipfile");
|
|
1972
|
+
if (!fs.existsSync(pipfilePath)) return false;
|
|
1973
|
+
try {
|
|
1974
|
+
const content = fs.readFileSync(pipfilePath, "utf-8");
|
|
1975
|
+
const sectionRe = /\[(packages|dev-packages)\]([\s\S]*?)(?=\n\[|$)/g;
|
|
1976
|
+
let match = sectionRe.exec(content);
|
|
1977
|
+
while (match !== null) {
|
|
1978
|
+
for (const line of match[2].split("\n")) {
|
|
1979
|
+
const m = line.trim().match(/^([a-zA-Z0-9_\-.]+)\s*=/);
|
|
1980
|
+
if (m) addPyDep(pyDeps, m[1]);
|
|
1981
|
+
}
|
|
1982
|
+
match = sectionRe.exec(content);
|
|
1983
|
+
}
|
|
1984
|
+
return true;
|
|
1985
|
+
} catch {
|
|
1986
|
+
return false;
|
|
1987
|
+
}
|
|
1988
|
+
};
|
|
1989
|
+
const collectPythonDeps = (rootDir) => {
|
|
1990
|
+
const pyDeps = /* @__PURE__ */ new Set();
|
|
1991
|
+
const hasReq = collectFromRequirementsTxt(rootDir, pyDeps);
|
|
1992
|
+
const hasPyproject = collectFromPyproject(rootDir, pyDeps);
|
|
1993
|
+
const hasPipfile = collectFromPipfile(rootDir, pyDeps);
|
|
1994
|
+
return {
|
|
1995
|
+
pyDeps,
|
|
1996
|
+
hasPyManifest: hasReq || hasPyproject || hasPipfile
|
|
1997
|
+
};
|
|
1998
|
+
};
|
|
1999
|
+
|
|
1871
2000
|
//#endregion
|
|
1872
2001
|
//#region src/engines/ai-slop/hallucinated-imports.ts
|
|
1873
2002
|
const JS_EXTENSIONS$2 = new Set([
|
|
@@ -2004,10 +2133,26 @@ const buildAliasMatcher = (key) => {
|
|
|
2004
2133
|
};
|
|
2005
2134
|
const collectAliasMatchersFromConfig = (configPath, matchers) => {
|
|
2006
2135
|
const opts = readJson(configPath)?.compilerOptions;
|
|
2007
|
-
if (!opts
|
|
2136
|
+
if (!opts) return;
|
|
2008
2137
|
const paths = opts.paths;
|
|
2009
|
-
if (
|
|
2010
|
-
|
|
2138
|
+
if (paths && typeof paths === "object") for (const key of Object.keys(paths)) matchers.push(buildAliasMatcher(key));
|
|
2139
|
+
const baseUrl = opts.baseUrl;
|
|
2140
|
+
if (typeof baseUrl === "string") {
|
|
2141
|
+
const baseUrlDir = path.resolve(path.dirname(configPath), baseUrl);
|
|
2142
|
+
let entries;
|
|
2143
|
+
try {
|
|
2144
|
+
entries = fs.readdirSync(baseUrlDir);
|
|
2145
|
+
} catch {
|
|
2146
|
+
return;
|
|
2147
|
+
}
|
|
2148
|
+
const baseSpecifiers = /* @__PURE__ */ new Set();
|
|
2149
|
+
for (const entry of entries) {
|
|
2150
|
+
if (entry.startsWith(".") || entry === "node_modules") continue;
|
|
2151
|
+
const base = entry.replace(/\.(?:[jt]sx?|mjs|cjs|d\.ts)$/i, "");
|
|
2152
|
+
if (base.length > 0) baseSpecifiers.add(base);
|
|
2153
|
+
}
|
|
2154
|
+
for (const name of baseSpecifiers) matchers.push((spec) => spec === name || spec.startsWith(`${name}/`));
|
|
2155
|
+
}
|
|
2011
2156
|
};
|
|
2012
2157
|
const collectTsPathAliases = (rootDir) => {
|
|
2013
2158
|
const matchers = [];
|
|
@@ -2015,97 +2160,35 @@ const collectTsPathAliases = (rootDir) => {
|
|
|
2015
2160
|
for (const dir of dirs) for (const fname of TS_CONFIG_FILES) collectAliasMatchersFromConfig(path.join(dir, fname), matchers);
|
|
2016
2161
|
return matchers;
|
|
2017
2162
|
};
|
|
2018
|
-
const addPyDep = (pyDeps, name) => {
|
|
2019
|
-
const normalized = name.toLowerCase().replace(/_/g, "-");
|
|
2020
|
-
pyDeps.add(normalized);
|
|
2021
|
-
};
|
|
2022
|
-
const collectFromRequirementsTxt = (rootDir, pyDeps) => {
|
|
2023
|
-
const reqPath = path.join(rootDir, "requirements.txt");
|
|
2024
|
-
if (!fs.existsSync(reqPath)) return false;
|
|
2025
|
-
try {
|
|
2026
|
-
const content = fs.readFileSync(reqPath, "utf-8");
|
|
2027
|
-
for (const line of content.split("\n")) {
|
|
2028
|
-
const trimmed = line.trim();
|
|
2029
|
-
if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("-")) continue;
|
|
2030
|
-
const match = trimmed.match(/^([a-zA-Z0-9_\-.]+)/);
|
|
2031
|
-
if (match) addPyDep(pyDeps, match[1]);
|
|
2032
|
-
}
|
|
2033
|
-
return true;
|
|
2034
|
-
} catch {
|
|
2035
|
-
return false;
|
|
2036
|
-
}
|
|
2037
|
-
};
|
|
2038
|
-
const collectFromPyproject = (rootDir, pyDeps) => {
|
|
2039
|
-
const pyprojPath = path.join(rootDir, "pyproject.toml");
|
|
2040
|
-
if (!fs.existsSync(pyprojPath)) return false;
|
|
2041
|
-
try {
|
|
2042
|
-
const content = fs.readFileSync(pyprojPath, "utf-8");
|
|
2043
|
-
const projectNameMatch = content.match(/\[project\][\s\S]*?^\s*name\s*=\s*["']([^"']+)/m);
|
|
2044
|
-
if (projectNameMatch) addPyDep(pyDeps, projectNameMatch[1]);
|
|
2045
|
-
const poetryNameMatch = content.match(/\[tool\.poetry\][\s\S]*?^\s*name\s*=\s*["']([^"']+)/m);
|
|
2046
|
-
if (poetryNameMatch) addPyDep(pyDeps, poetryNameMatch[1]);
|
|
2047
|
-
const pep621 = content.match(/^\s*dependencies\s*=\s*\[([\s\S]*?)\]/m);
|
|
2048
|
-
if (pep621) for (const line of pep621[1].split("\n")) {
|
|
2049
|
-
const m = line.match(/["']\s*([a-zA-Z0-9_\-.]+)/);
|
|
2050
|
-
if (m) addPyDep(pyDeps, m[1]);
|
|
2051
|
-
}
|
|
2052
|
-
const poetryRe = /\[tool\.poetry(?:\.group\.[a-z]+)?\.dependencies\]([\s\S]*?)(?=\n\[|$)/g;
|
|
2053
|
-
let match = poetryRe.exec(content);
|
|
2054
|
-
while (match !== null) {
|
|
2055
|
-
for (const line of match[1].split("\n")) {
|
|
2056
|
-
const m = line.trim().match(/^([a-zA-Z0-9_\-.]+)\s*=/);
|
|
2057
|
-
if (m && m[1] !== "python") addPyDep(pyDeps, m[1]);
|
|
2058
|
-
}
|
|
2059
|
-
match = poetryRe.exec(content);
|
|
2060
|
-
}
|
|
2061
|
-
return true;
|
|
2062
|
-
} catch {
|
|
2063
|
-
return false;
|
|
2064
|
-
}
|
|
2065
|
-
};
|
|
2066
|
-
const collectFromPipfile = (rootDir, pyDeps) => {
|
|
2067
|
-
const pipfilePath = path.join(rootDir, "Pipfile");
|
|
2068
|
-
if (!fs.existsSync(pipfilePath)) return false;
|
|
2069
|
-
try {
|
|
2070
|
-
const content = fs.readFileSync(pipfilePath, "utf-8");
|
|
2071
|
-
const sectionRe = /\[(packages|dev-packages)\]([\s\S]*?)(?=\n\[|$)/g;
|
|
2072
|
-
let match = sectionRe.exec(content);
|
|
2073
|
-
while (match !== null) {
|
|
2074
|
-
for (const line of match[2].split("\n")) {
|
|
2075
|
-
const m = line.trim().match(/^([a-zA-Z0-9_\-.]+)\s*=/);
|
|
2076
|
-
if (m) addPyDep(pyDeps, m[1]);
|
|
2077
|
-
}
|
|
2078
|
-
match = sectionRe.exec(content);
|
|
2079
|
-
}
|
|
2080
|
-
return true;
|
|
2081
|
-
} catch {
|
|
2082
|
-
return false;
|
|
2083
|
-
}
|
|
2084
|
-
};
|
|
2085
2163
|
const loadManifest = (rootDir) => {
|
|
2086
2164
|
const jsDeps = /* @__PURE__ */ new Set();
|
|
2087
|
-
const pyDeps = /* @__PURE__ */ new Set();
|
|
2088
2165
|
const hasJsManifest = collectJsDeps(rootDir, jsDeps);
|
|
2089
|
-
const
|
|
2090
|
-
const hasPyproject = collectFromPyproject(rootDir, pyDeps);
|
|
2091
|
-
const hasPipfile = collectFromPipfile(rootDir, pyDeps);
|
|
2166
|
+
const { pyDeps, hasPyManifest } = collectPythonDeps(rootDir);
|
|
2092
2167
|
return {
|
|
2093
2168
|
jsDeps,
|
|
2094
2169
|
pyDeps,
|
|
2095
2170
|
hasJsManifest,
|
|
2096
|
-
hasPyManifest
|
|
2171
|
+
hasPyManifest
|
|
2097
2172
|
};
|
|
2098
2173
|
};
|
|
2099
2174
|
const isJsRelativeOrAbsolute = (spec) => spec.startsWith(".") || spec.startsWith("/") || spec.startsWith("~/");
|
|
2175
|
+
const RUNTIME_BUILTINS = new Set(["bun"]);
|
|
2100
2176
|
const isJsBuiltin = (spec) => {
|
|
2177
|
+
if (RUNTIME_BUILTINS.has(spec)) return true;
|
|
2101
2178
|
return isBuiltin(spec.startsWith("node:") ? spec.slice(5) : spec) || isBuiltin(spec);
|
|
2102
2179
|
};
|
|
2103
2180
|
const VIRTUAL_MODULE_PREFIXES = [
|
|
2104
2181
|
"astro:",
|
|
2105
2182
|
"virtual:",
|
|
2106
|
-
"bun:"
|
|
2183
|
+
"bun:",
|
|
2184
|
+
"~icons/"
|
|
2107
2185
|
];
|
|
2108
2186
|
const isJsVirtualModule = (spec) => VIRTUAL_MODULE_PREFIXES.some((p) => spec.startsWith(p));
|
|
2187
|
+
const stripImportQuery = (spec) => {
|
|
2188
|
+
const idx = spec.indexOf("?");
|
|
2189
|
+
return idx === -1 ? spec : spec.slice(0, idx);
|
|
2190
|
+
};
|
|
2191
|
+
const VIRTUAL_ASSET_FILES = { "unfonts.css": "unplugin-fonts" };
|
|
2109
2192
|
const TEMPLATE_PLACEHOLDER_RE = /\$\{/;
|
|
2110
2193
|
const isLikelyRealImportSpec = (spec) => {
|
|
2111
2194
|
if (spec.length === 0) return false;
|
|
@@ -2172,10 +2255,14 @@ const extractPyImports = (content) => {
|
|
|
2172
2255
|
}
|
|
2173
2256
|
return results;
|
|
2174
2257
|
};
|
|
2175
|
-
const checkJsImport = (
|
|
2258
|
+
const checkJsImport = (rawSpec, manifest, tsAliasMatchers) => {
|
|
2259
|
+
const spec = stripImportQuery(rawSpec);
|
|
2260
|
+
if (spec.length === 0) return null;
|
|
2176
2261
|
if (isJsRelativeOrAbsolute(spec)) return null;
|
|
2177
2262
|
if (isJsBuiltin(spec)) return null;
|
|
2178
2263
|
if (isJsVirtualModule(spec)) return null;
|
|
2264
|
+
const virtualOwner = VIRTUAL_ASSET_FILES[spec];
|
|
2265
|
+
if (virtualOwner && manifest.jsDeps.has(virtualOwner)) return null;
|
|
2179
2266
|
if (tsAliasMatchers.some((m) => m(spec))) return null;
|
|
2180
2267
|
const pkg = packageNameFromImport(spec);
|
|
2181
2268
|
if (manifest.jsDeps.has(pkg)) return null;
|
|
@@ -3519,64 +3606,88 @@ const analyzeFunctions = (content, ext) => {
|
|
|
3519
3606
|
}
|
|
3520
3607
|
return functions;
|
|
3521
3608
|
};
|
|
3522
|
-
const
|
|
3609
|
+
const FILE_LOC_MULTIPLIERS = {
|
|
3610
|
+
".tsx": 1.5,
|
|
3611
|
+
".jsx": 1.5,
|
|
3612
|
+
".rs": 2.5,
|
|
3613
|
+
".go": 1.5
|
|
3614
|
+
};
|
|
3615
|
+
const DECLARATION_FILE_RE = /\.d\.ts$/i;
|
|
3616
|
+
const fileLocBudget = (ext, relativePath, base) => {
|
|
3617
|
+
if (DECLARATION_FILE_RE.test(relativePath)) return Number.POSITIVE_INFINITY;
|
|
3618
|
+
const multiplier = FILE_LOC_MULTIPLIERS[ext] ?? 1;
|
|
3619
|
+
return Math.ceil(base * multiplier);
|
|
3620
|
+
};
|
|
3523
3621
|
const checkFileDiagnostics = (relativePath, content, limits) => {
|
|
3524
3622
|
const results = [];
|
|
3525
3623
|
const lineCount = content.split("\n").length;
|
|
3526
3624
|
const ext = path.extname(relativePath).toLowerCase();
|
|
3527
3625
|
if (isDataFile(content)) return results;
|
|
3528
|
-
const configuredMax = ext
|
|
3626
|
+
const configuredMax = fileLocBudget(ext, relativePath, limits.maxFileLoc);
|
|
3627
|
+
if (!Number.isFinite(configuredMax)) return results;
|
|
3529
3628
|
if (lineCount > Math.ceil(configuredMax * 1.1)) results.push({
|
|
3530
3629
|
filePath: relativePath,
|
|
3531
3630
|
engine: "code-quality",
|
|
3532
3631
|
rule: "complexity/file-too-large",
|
|
3533
3632
|
severity: "warning",
|
|
3534
|
-
message: `File
|
|
3633
|
+
message: `File too large (max: ${configuredMax})`,
|
|
3535
3634
|
help: "Consider splitting this file into smaller modules",
|
|
3536
3635
|
line: 0,
|
|
3537
3636
|
column: 0,
|
|
3538
3637
|
category: "Complexity",
|
|
3539
|
-
fixable: false
|
|
3638
|
+
fixable: false,
|
|
3639
|
+
detail: `${lineCount} lines`
|
|
3540
3640
|
});
|
|
3541
3641
|
return results;
|
|
3542
3642
|
};
|
|
3543
|
-
const
|
|
3643
|
+
const JSX_EXTENSIONS = new Set([".tsx", ".jsx"]);
|
|
3644
|
+
const isComponentFunction = (name, ext) => JSX_EXTENSIONS.has(ext) && /^[A-Z]/.test(name);
|
|
3645
|
+
const functionLocBudget = (fn, ext, base) => {
|
|
3646
|
+
if (isComponentFunction(fn.name, ext)) return Math.ceil(base * 2);
|
|
3647
|
+
if (ext === ".rs") return Math.ceil(base * 1.5);
|
|
3648
|
+
return base;
|
|
3649
|
+
};
|
|
3650
|
+
const checkFunctionDiagnostics = (relativePath, fn, limits, ext) => {
|
|
3544
3651
|
const results = [];
|
|
3545
|
-
|
|
3652
|
+
const fnMax = functionLocBudget(fn, ext, limits.maxFunctionLoc);
|
|
3653
|
+
if (fn.lineCount - fn.templateLines > Math.ceil(fnMax * 1.1)) results.push({
|
|
3546
3654
|
filePath: relativePath,
|
|
3547
3655
|
engine: "code-quality",
|
|
3548
3656
|
rule: "complexity/function-too-long",
|
|
3549
3657
|
severity: "warning",
|
|
3550
|
-
message: `Function
|
|
3658
|
+
message: `Function too long (max: ${fnMax})`,
|
|
3551
3659
|
help: "Consider breaking this function into smaller pieces",
|
|
3552
3660
|
line: fn.startLine,
|
|
3553
3661
|
column: 0,
|
|
3554
3662
|
category: "Complexity",
|
|
3555
|
-
fixable: false
|
|
3663
|
+
fixable: false,
|
|
3664
|
+
detail: `${fn.name} · ${fn.lineCount} lines`
|
|
3556
3665
|
});
|
|
3557
3666
|
if (fn.maxNesting > limits.maxNesting) results.push({
|
|
3558
3667
|
filePath: relativePath,
|
|
3559
3668
|
engine: "code-quality",
|
|
3560
3669
|
rule: "complexity/deep-nesting",
|
|
3561
3670
|
severity: "warning",
|
|
3562
|
-
message: `Function
|
|
3671
|
+
message: `Function nested too deeply (max: ${limits.maxNesting})`,
|
|
3563
3672
|
help: "Consider using early returns or extracting nested logic",
|
|
3564
3673
|
line: fn.startLine,
|
|
3565
3674
|
column: 0,
|
|
3566
3675
|
category: "Complexity",
|
|
3567
|
-
fixable: false
|
|
3676
|
+
fixable: false,
|
|
3677
|
+
detail: `${fn.name} · depth ${fn.maxNesting}`
|
|
3568
3678
|
});
|
|
3569
3679
|
if (fn.paramCount > limits.maxParams) results.push({
|
|
3570
3680
|
filePath: relativePath,
|
|
3571
3681
|
engine: "code-quality",
|
|
3572
3682
|
rule: "complexity/too-many-params",
|
|
3573
3683
|
severity: "warning",
|
|
3574
|
-
message: `Function
|
|
3684
|
+
message: `Function has too many parameters (max: ${limits.maxParams})`,
|
|
3575
3685
|
help: "Consider using an options object parameter",
|
|
3576
3686
|
line: fn.startLine,
|
|
3577
3687
|
column: 0,
|
|
3578
3688
|
category: "Complexity",
|
|
3579
|
-
fixable: false
|
|
3689
|
+
fixable: false,
|
|
3690
|
+
detail: `${fn.name} · ${fn.paramCount} params`
|
|
3580
3691
|
});
|
|
3581
3692
|
return results;
|
|
3582
3693
|
};
|
|
@@ -3591,7 +3702,7 @@ const checkFileComplexity = (filePath, rootDirectory, limits) => {
|
|
|
3591
3702
|
}
|
|
3592
3703
|
const ext = path.extname(filePath).toLowerCase();
|
|
3593
3704
|
const diagnostics = checkFileDiagnostics(relativePath, content, limits);
|
|
3594
|
-
for (const fn of analyzeFunctions(content, ext)) diagnostics.push(...checkFunctionDiagnostics(relativePath, fn, limits));
|
|
3705
|
+
for (const fn of analyzeFunctions(content, ext)) diagnostics.push(...checkFunctionDiagnostics(relativePath, fn, limits, ext));
|
|
3595
3706
|
return diagnostics;
|
|
3596
3707
|
};
|
|
3597
3708
|
const checkComplexity = async (context) => {
|
|
@@ -3696,17 +3807,19 @@ const findDuplicateBlocks = (content, relativePath) => {
|
|
|
3696
3807
|
});
|
|
3697
3808
|
}
|
|
3698
3809
|
return reports.map((r) => {
|
|
3810
|
+
const span = r.currentEnd - r.currentStart + 1;
|
|
3699
3811
|
return {
|
|
3700
3812
|
filePath: relativePath,
|
|
3701
3813
|
engine: "code-quality",
|
|
3702
3814
|
rule: "code-quality/duplicate-block",
|
|
3703
3815
|
severity: "warning",
|
|
3704
|
-
message:
|
|
3816
|
+
message: "Duplicate code block — extract a shared helper",
|
|
3705
3817
|
help: `Pull the shared logic into a function both sites can call. Keeps one version of the truth and makes future changes one-shot instead of N-shot.`,
|
|
3706
3818
|
line: r.currentStart,
|
|
3707
3819
|
column: 0,
|
|
3708
3820
|
category: "Complexity",
|
|
3709
|
-
fixable: false
|
|
3821
|
+
fixable: false,
|
|
3822
|
+
detail: `${span} lines duplicate block at L${r.priorStart}`
|
|
3710
3823
|
};
|
|
3711
3824
|
});
|
|
3712
3825
|
};
|
|
@@ -4283,16 +4396,34 @@ const fixGofmt = async (rootDirectory) => {
|
|
|
4283
4396
|
if (result.exitCode !== 0) throw new Error(result.stderr || result.stdout || `gofmt exited with code ${result.exitCode}`);
|
|
4284
4397
|
};
|
|
4285
4398
|
|
|
4399
|
+
//#endregion
|
|
4400
|
+
//#region src/engines/python-targets.ts
|
|
4401
|
+
const PYTHON_EXTENSIONS = new Set([".py", ".pyi"]);
|
|
4402
|
+
const normalizeProjectPath = (filePath) => filePath.split(path.sep).join("/");
|
|
4403
|
+
const getPythonTargets = (context) => {
|
|
4404
|
+
const targets = (context.files ?? getSourceFiles(context)).filter((filePath) => PYTHON_EXTENSIONS.has(path.extname(filePath).toLowerCase())).map((filePath) => {
|
|
4405
|
+
const absolutePath = path.isAbsolute(filePath) ? filePath : path.resolve(context.rootDirectory, filePath);
|
|
4406
|
+
return normalizeProjectPath(path.relative(context.rootDirectory, absolutePath));
|
|
4407
|
+
}).filter((filePath) => filePath.length > 0 && !filePath.startsWith(".."));
|
|
4408
|
+
return [...new Set(targets)];
|
|
4409
|
+
};
|
|
4410
|
+
const getRuffDiagnosticPath = (rootDirectory, filePath) => {
|
|
4411
|
+
const normalizedPath = filePath.replace(/^a\//, "");
|
|
4412
|
+
return normalizeProjectPath(path.isAbsolute(normalizedPath) ? path.relative(rootDirectory, normalizedPath) : normalizedPath);
|
|
4413
|
+
};
|
|
4414
|
+
|
|
4286
4415
|
//#endregion
|
|
4287
4416
|
//#region src/engines/format/ruff-format.ts
|
|
4288
4417
|
const runRuffFormat = async (context) => {
|
|
4289
4418
|
const ruffBinary = resolveToolBinary("ruff");
|
|
4419
|
+
const targets = getPythonTargets(context);
|
|
4420
|
+
if (targets.length === 0) return [];
|
|
4290
4421
|
try {
|
|
4291
4422
|
const result = await runSubprocess(ruffBinary, [
|
|
4292
4423
|
"format",
|
|
4293
4424
|
"--check",
|
|
4294
4425
|
"--diff",
|
|
4295
|
-
|
|
4426
|
+
...targets
|
|
4296
4427
|
], {
|
|
4297
4428
|
cwd: context.rootDirectory,
|
|
4298
4429
|
timeout: 6e4
|
|
@@ -4308,9 +4439,9 @@ const parseRuffFormatOutput = (output, rootDir) => {
|
|
|
4308
4439
|
const filePattern = /^--- (.+)$/gm;
|
|
4309
4440
|
let match;
|
|
4310
4441
|
while ((match = filePattern.exec(output)) !== null) {
|
|
4311
|
-
const filePath = match[1]
|
|
4442
|
+
const filePath = getRuffDiagnosticPath(rootDir, match[1]);
|
|
4312
4443
|
diagnostics.push({
|
|
4313
|
-
filePath
|
|
4444
|
+
filePath,
|
|
4314
4445
|
engine: "format",
|
|
4315
4446
|
rule: "python-formatting",
|
|
4316
4447
|
severity: "warning",
|
|
@@ -4714,6 +4845,95 @@ const resolveOxlintBinary = () => {
|
|
|
4714
4845
|
return "oxlint";
|
|
4715
4846
|
}
|
|
4716
4847
|
};
|
|
4848
|
+
const VITE_QUERY_RE = /["'][^"']*\?(worker|sharedworker|worker-url|url|raw|inline|init)\b/;
|
|
4849
|
+
const isViteVirtualImportFalsePositive = (rule, message) => rule.startsWith("import/") && VITE_QUERY_RE.test(message);
|
|
4850
|
+
const AMBIENT_GLOBAL_DEPS = [
|
|
4851
|
+
"unplugin-icons",
|
|
4852
|
+
"@types/bun",
|
|
4853
|
+
"bun-types"
|
|
4854
|
+
];
|
|
4855
|
+
const SST_PLATFORM_REF_RE = /\/\/\/\s*<reference\s+path=["'][^"']*sst[\\/]+platform[\\/]+config\.d\.ts["']/;
|
|
4856
|
+
const ICON_AUTOIMPORT_RE = /^Icon[A-Z]/;
|
|
4857
|
+
const NO_UNDEF_IDENT_RE = /^['‘"`]([^'’"`]+)['’"`]/;
|
|
4858
|
+
const detectAmbientSources = (rootDir) => {
|
|
4859
|
+
const found = /* @__PURE__ */ new Set();
|
|
4860
|
+
const skipDirs = new Set([
|
|
4861
|
+
"node_modules",
|
|
4862
|
+
".git",
|
|
4863
|
+
"dist",
|
|
4864
|
+
"build",
|
|
4865
|
+
"out",
|
|
4866
|
+
"target",
|
|
4867
|
+
"coverage",
|
|
4868
|
+
".next",
|
|
4869
|
+
".turbo"
|
|
4870
|
+
]);
|
|
4871
|
+
const walk = (dir, depth) => {
|
|
4872
|
+
if (depth > 4 || found.size === AMBIENT_GLOBAL_DEPS.length) return;
|
|
4873
|
+
let entries;
|
|
4874
|
+
try {
|
|
4875
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
4876
|
+
} catch {
|
|
4877
|
+
return;
|
|
4878
|
+
}
|
|
4879
|
+
for (const entry of entries) {
|
|
4880
|
+
if (found.size === AMBIENT_GLOBAL_DEPS.length) return;
|
|
4881
|
+
if (entry.name.startsWith(".") && entry.name !== ".github") continue;
|
|
4882
|
+
if (skipDirs.has(entry.name)) continue;
|
|
4883
|
+
const full = path.join(dir, entry.name);
|
|
4884
|
+
if (entry.isDirectory()) walk(full, depth + 1);
|
|
4885
|
+
else if (entry.name === "package.json") try {
|
|
4886
|
+
const pkg = JSON.parse(fs.readFileSync(full, "utf-8"));
|
|
4887
|
+
const allDeps = {
|
|
4888
|
+
...pkg.dependencies ?? {},
|
|
4889
|
+
...pkg.devDependencies ?? {},
|
|
4890
|
+
...pkg.peerDependencies ?? {}
|
|
4891
|
+
};
|
|
4892
|
+
for (const dep of AMBIENT_GLOBAL_DEPS) if (dep in allDeps) found.add(dep);
|
|
4893
|
+
} catch {}
|
|
4894
|
+
}
|
|
4895
|
+
};
|
|
4896
|
+
walk(rootDir, 0);
|
|
4897
|
+
return found;
|
|
4898
|
+
};
|
|
4899
|
+
const extractNoUndefIdentifier = (message) => {
|
|
4900
|
+
return NO_UNDEF_IDENT_RE.exec(message)?.[1] ?? null;
|
|
4901
|
+
};
|
|
4902
|
+
const isAmbientFalsePositive = (rule, message, sources) => {
|
|
4903
|
+
if (rule !== "eslint/no-undef") return false;
|
|
4904
|
+
const ident = extractNoUndefIdentifier(message);
|
|
4905
|
+
if (!ident) return false;
|
|
4906
|
+
if (sources.has("unplugin-icons") && ICON_AUTOIMPORT_RE.test(ident)) return true;
|
|
4907
|
+
if ((sources.has("@types/bun") || sources.has("bun-types")) && ident === "Bun") return true;
|
|
4908
|
+
return false;
|
|
4909
|
+
};
|
|
4910
|
+
const sstReferencedFiles = /* @__PURE__ */ new Map();
|
|
4911
|
+
const fileReferencesSstPlatform = (rootDir, relativeFilePath) => {
|
|
4912
|
+
const cached = sstReferencedFiles.get(relativeFilePath);
|
|
4913
|
+
if (cached !== void 0) return cached;
|
|
4914
|
+
const absolute = path.isAbsolute(relativeFilePath) ? relativeFilePath : path.join(rootDir, relativeFilePath);
|
|
4915
|
+
let referenced = false;
|
|
4916
|
+
try {
|
|
4917
|
+
const fd = fs.openSync(absolute, "r");
|
|
4918
|
+
try {
|
|
4919
|
+
const buf = Buffer.alloc(512);
|
|
4920
|
+
const bytesRead = fs.readSync(fd, buf, 0, 512, 0);
|
|
4921
|
+
referenced = SST_PLATFORM_REF_RE.test(buf.toString("utf-8", 0, bytesRead));
|
|
4922
|
+
} finally {
|
|
4923
|
+
fs.closeSync(fd);
|
|
4924
|
+
}
|
|
4925
|
+
} catch {
|
|
4926
|
+
referenced = false;
|
|
4927
|
+
}
|
|
4928
|
+
sstReferencedFiles.set(relativeFilePath, referenced);
|
|
4929
|
+
return referenced;
|
|
4930
|
+
};
|
|
4931
|
+
const UNUSED_VAR_IDENT_RE = /(?:Variable|Parameter|Catch parameter) '([^']+)' (?:is declared but never used|is caught but never used)/;
|
|
4932
|
+
const isUnderscoreUnusedVar = (rule, message) => {
|
|
4933
|
+
if (rule !== "eslint/no-unused-vars") return false;
|
|
4934
|
+
const match = UNUSED_VAR_IDENT_RE.exec(message);
|
|
4935
|
+
return match ? match[1].startsWith("_") : false;
|
|
4936
|
+
};
|
|
4717
4937
|
const parseRuleCode = (code) => {
|
|
4718
4938
|
if (!code) return {
|
|
4719
4939
|
plugin: "eslint",
|
|
@@ -4810,6 +5030,8 @@ const runOxlint = async (context) => {
|
|
|
4810
5030
|
framework: context.frameworks.find((f) => f !== "none"),
|
|
4811
5031
|
testFramework: detectTestFramework(context.rootDirectory)
|
|
4812
5032
|
});
|
|
5033
|
+
const ambientSources = detectAmbientSources(context.rootDirectory);
|
|
5034
|
+
sstReferencedFiles.clear();
|
|
4813
5035
|
try {
|
|
4814
5036
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
4815
5037
|
const args = [
|
|
@@ -4849,6 +5071,11 @@ const runOxlint = async (context) => {
|
|
|
4849
5071
|
fixable: false
|
|
4850
5072
|
};
|
|
4851
5073
|
}).filter((d) => {
|
|
5074
|
+
if (isExcludedFromScan(path.isAbsolute(d.filePath) ? path.relative(context.rootDirectory, d.filePath) : d.filePath)) return false;
|
|
5075
|
+
if (isViteVirtualImportFalsePositive(d.rule, d.message)) return false;
|
|
5076
|
+
if (isAmbientFalsePositive(d.rule, d.message, ambientSources)) return false;
|
|
5077
|
+
if (isUnderscoreUnusedVar(d.rule, d.message)) return false;
|
|
5078
|
+
if (d.rule === "eslint/no-undef" && fileReferencesSstPlatform(context.rootDirectory, d.filePath)) return false;
|
|
4852
5079
|
const key = `${d.filePath}:${d.line}:${d.rule}:${d.message}`;
|
|
4853
5080
|
if (seen.has(key)) return false;
|
|
4854
5081
|
seen.add(key);
|
|
@@ -4912,18 +5139,20 @@ const fixOxlint = async (context, options = {}) => {
|
|
|
4912
5139
|
//#region src/engines/lint/ruff.ts
|
|
4913
5140
|
const runRuffLint = async (context) => {
|
|
4914
5141
|
const ruffBinary = resolveToolBinary("ruff");
|
|
5142
|
+
const targets = getPythonTargets(context);
|
|
5143
|
+
if (targets.length === 0) return [];
|
|
4915
5144
|
try {
|
|
4916
5145
|
const output = (await runSubprocess(ruffBinary, [
|
|
4917
5146
|
"check",
|
|
4918
5147
|
"--output-format=json",
|
|
4919
|
-
|
|
5148
|
+
...targets
|
|
4920
5149
|
], {
|
|
4921
5150
|
cwd: context.rootDirectory,
|
|
4922
5151
|
timeout: 6e4
|
|
4923
5152
|
})).stdout;
|
|
4924
5153
|
if (!output) return [];
|
|
4925
5154
|
return JSON.parse(output).map((d) => ({
|
|
4926
|
-
filePath:
|
|
5155
|
+
filePath: getRuffDiagnosticPath(context.rootDirectory, d.filename),
|
|
4927
5156
|
engine: "lint",
|
|
4928
5157
|
rule: `ruff/${d.code}`,
|
|
4929
5158
|
severity: d.code.startsWith("E") || d.code.startsWith("F") ? "error" : "warning",
|
|
@@ -5037,56 +5266,94 @@ const runPnpmAuditWithFallback = async (rootDir, timeout) => {
|
|
|
5037
5266
|
return [];
|
|
5038
5267
|
}
|
|
5039
5268
|
};
|
|
5269
|
+
const SEVERITY_RANK = {
|
|
5270
|
+
critical: 4,
|
|
5271
|
+
high: 3,
|
|
5272
|
+
moderate: 2,
|
|
5273
|
+
low: 1
|
|
5274
|
+
};
|
|
5040
5275
|
const toSeverity = (value) => value === "critical" || value === "high" ? "error" : "warning";
|
|
5041
|
-
const
|
|
5276
|
+
const upsertVuln = (bucket, packageName, severity, recommendation) => {
|
|
5277
|
+
const existing = bucket.get(packageName);
|
|
5278
|
+
if (existing) {
|
|
5279
|
+
existing.advisories++;
|
|
5280
|
+
if ((SEVERITY_RANK[severity] ?? 0) > (SEVERITY_RANK[existing.worstSeverity] ?? 0)) existing.worstSeverity = severity;
|
|
5281
|
+
if (recommendation) existing.recommendations.add(recommendation);
|
|
5282
|
+
} else bucket.set(packageName, {
|
|
5283
|
+
packageName,
|
|
5284
|
+
worstSeverity: severity,
|
|
5285
|
+
advisories: 1,
|
|
5286
|
+
recommendations: recommendation ? new Set([recommendation]) : /* @__PURE__ */ new Set()
|
|
5287
|
+
});
|
|
5288
|
+
};
|
|
5289
|
+
const SEMVER_RE = /(\d+)\.(\d+)\.(\d+)/;
|
|
5290
|
+
const cmpSemver = (a, b) => {
|
|
5291
|
+
const [, a1, a2, a3] = SEMVER_RE.exec(a) ?? [
|
|
5292
|
+
"",
|
|
5293
|
+
"0",
|
|
5294
|
+
"0",
|
|
5295
|
+
"0"
|
|
5296
|
+
];
|
|
5297
|
+
const [, b1, b2, b3] = SEMVER_RE.exec(b) ?? [
|
|
5298
|
+
"",
|
|
5299
|
+
"0",
|
|
5300
|
+
"0",
|
|
5301
|
+
"0"
|
|
5302
|
+
];
|
|
5303
|
+
if (Number(a1) !== Number(b1)) return Number(a1) - Number(b1);
|
|
5304
|
+
if (Number(a2) !== Number(b2)) return Number(a2) - Number(b2);
|
|
5305
|
+
return Number(a3) - Number(b3);
|
|
5306
|
+
};
|
|
5307
|
+
const pickBestRecommendation = (recs) => {
|
|
5308
|
+
if (recs.length <= 1) return recs[0] ?? "";
|
|
5309
|
+
const versioned = recs.filter((r) => SEMVER_RE.test(r));
|
|
5310
|
+
if (versioned.length === 0) return recs[0];
|
|
5311
|
+
return versioned.reduce((best, r) => cmpSemver(r, best) > 0 ? r : best);
|
|
5312
|
+
};
|
|
5313
|
+
const cleanRecommendation = (raw) => {
|
|
5314
|
+
const t = raw.trim();
|
|
5315
|
+
if (!t || t.toLowerCase() === "none") return "no fix available";
|
|
5316
|
+
return t;
|
|
5317
|
+
};
|
|
5318
|
+
const aggregateToDiagnostic = (agg, source) => {
|
|
5319
|
+
const best = cleanRecommendation(pickBestRecommendation([...agg.recommendations]));
|
|
5320
|
+
const countLabel = agg.advisories > 1 ? ` (${agg.advisories} advisories)` : "";
|
|
5321
|
+
const recLabel = best ? ` — ${best}` : "";
|
|
5322
|
+
return {
|
|
5323
|
+
filePath: "package.json",
|
|
5324
|
+
engine: "security",
|
|
5325
|
+
rule: "security/vulnerable-dependency",
|
|
5326
|
+
severity: toSeverity(agg.worstSeverity),
|
|
5327
|
+
message: `${agg.packageName} (${agg.worstSeverity})${recLabel}${countLabel}`,
|
|
5328
|
+
help: "",
|
|
5329
|
+
line: 0,
|
|
5330
|
+
column: 0,
|
|
5331
|
+
category: "Security",
|
|
5332
|
+
fixable: false,
|
|
5333
|
+
detail: source === "npm audit" ? "npm" : "pnpm"
|
|
5334
|
+
};
|
|
5335
|
+
};
|
|
5042
5336
|
const parseLegacyAdvisories = (advisories, source) => {
|
|
5043
|
-
const
|
|
5044
|
-
for (const [key, advisory] of Object.entries(advisories))
|
|
5045
|
-
|
|
5046
|
-
const severity = (advisory.severity ?? "moderate").toLowerCase();
|
|
5047
|
-
const recommendation = advisory.recommendation ?? advisory.title ?? `Run \`${defaultAuditFixCommand(source)}\` to resolve`;
|
|
5048
|
-
diagnostics.push({
|
|
5049
|
-
filePath: "package.json",
|
|
5050
|
-
engine: "security",
|
|
5051
|
-
rule: "security/vulnerable-dependency",
|
|
5052
|
-
severity: toSeverity(severity),
|
|
5053
|
-
message: `Vulnerable dependency (${source}): ${packageName} (${severity})`,
|
|
5054
|
-
help: withFixHint(recommendation),
|
|
5055
|
-
line: 0,
|
|
5056
|
-
column: 0,
|
|
5057
|
-
category: "Security",
|
|
5058
|
-
fixable: false
|
|
5059
|
-
});
|
|
5060
|
-
}
|
|
5061
|
-
return diagnostics;
|
|
5337
|
+
const bucket = /* @__PURE__ */ new Map();
|
|
5338
|
+
for (const [key, advisory] of Object.entries(advisories)) upsertVuln(bucket, advisory.module_name ?? advisory.name ?? advisory.package ?? key, (advisory.severity ?? "moderate").toLowerCase(), advisory.recommendation ?? advisory.title ?? "");
|
|
5339
|
+
return [...bucket.values()].map((agg) => aggregateToDiagnostic(agg, source));
|
|
5062
5340
|
};
|
|
5063
5341
|
const parseModernVulnerabilities = (vulnerabilities, source) => {
|
|
5064
|
-
const
|
|
5342
|
+
const bucket = /* @__PURE__ */ new Map();
|
|
5065
5343
|
for (const [packageName, vulnerability] of Object.entries(vulnerabilities)) {
|
|
5066
5344
|
const severity = (vulnerability.severity ?? "moderate").toLowerCase();
|
|
5067
5345
|
const fixAvailable = vulnerability.fixAvailable;
|
|
5068
5346
|
const isDirect = vulnerability.isDirect === true;
|
|
5069
|
-
let recommendation =
|
|
5070
|
-
if (fixAvailable === false) recommendation = isDirect ? "
|
|
5071
|
-
else if (!isDirect && fixAvailable === true) recommendation = "
|
|
5347
|
+
let recommendation = "";
|
|
5348
|
+
if (fixAvailable === false) recommendation = isDirect ? "no automatic fix" : "transitive — needs override or parent upgrade";
|
|
5349
|
+
else if (!isDirect && fixAvailable === true) recommendation = "transitive — may need override or parent upgrade";
|
|
5072
5350
|
else if (fixAvailable && typeof fixAvailable === "object" && "name" in fixAvailable && "version" in fixAvailable) {
|
|
5073
5351
|
const target = fixAvailable;
|
|
5074
|
-
if (target.name && target.version) recommendation = `
|
|
5352
|
+
if (target.name && target.version) recommendation = `upgrade to ${target.name}@${target.version}`;
|
|
5075
5353
|
}
|
|
5076
|
-
|
|
5077
|
-
filePath: "package.json",
|
|
5078
|
-
engine: "security",
|
|
5079
|
-
rule: "security/vulnerable-dependency",
|
|
5080
|
-
severity: toSeverity(severity),
|
|
5081
|
-
message: `Vulnerable dependency (${source}): ${packageName} (${severity})`,
|
|
5082
|
-
help: withFixHint(recommendation),
|
|
5083
|
-
line: 0,
|
|
5084
|
-
column: 0,
|
|
5085
|
-
category: "Security",
|
|
5086
|
-
fixable: false
|
|
5087
|
-
});
|
|
5354
|
+
upsertVuln(bucket, packageName, severity, recommendation);
|
|
5088
5355
|
}
|
|
5089
|
-
return
|
|
5356
|
+
return [...bucket.values()].map((agg) => aggregateToDiagnostic(agg, source));
|
|
5090
5357
|
};
|
|
5091
5358
|
const parseJsAudit = (output, source) => {
|
|
5092
5359
|
if (!output) return [];
|
|
@@ -5874,60 +6141,348 @@ var LiveRail = class {
|
|
|
5874
6141
|
};
|
|
5875
6142
|
|
|
5876
6143
|
//#endregion
|
|
5877
|
-
//#region src/
|
|
5878
|
-
const
|
|
5879
|
-
const
|
|
5880
|
-
|
|
5881
|
-
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
|
|
5885
|
-
if (
|
|
5886
|
-
if (
|
|
5887
|
-
if (
|
|
5888
|
-
return
|
|
5889
|
-
|
|
5890
|
-
|
|
6144
|
+
//#region src/telemetry/env.ts
|
|
6145
|
+
const detectPackageManager$1 = (env = process.env) => {
|
|
6146
|
+
const execPath = env.npm_execpath ?? "";
|
|
6147
|
+
if (execPath.includes("npx")) return "npx";
|
|
6148
|
+
const userAgent = env.npm_config_user_agent ?? "";
|
|
6149
|
+
if (userAgent.startsWith("pnpm/")) return "pnpm";
|
|
6150
|
+
if (userAgent.startsWith("yarn/")) return "yarn";
|
|
6151
|
+
if (userAgent.startsWith("bun/")) return "bun";
|
|
6152
|
+
if (userAgent.startsWith("npm/")) return "npm";
|
|
6153
|
+
if (execPath.includes("pnpm")) return "pnpm";
|
|
6154
|
+
if (execPath.includes("yarn")) return "yarn";
|
|
6155
|
+
if (execPath.includes("bun")) return "bun";
|
|
6156
|
+
if (execPath.includes("npm")) return "npm";
|
|
6157
|
+
return "unknown";
|
|
6158
|
+
};
|
|
6159
|
+
const CI_ENV_KEYS = [
|
|
6160
|
+
"CI",
|
|
6161
|
+
"GITHUB_ACTIONS",
|
|
6162
|
+
"GITLAB_CI",
|
|
6163
|
+
"CIRCLECI",
|
|
6164
|
+
"TRAVIS",
|
|
6165
|
+
"BUILDKITE",
|
|
6166
|
+
"DRONE",
|
|
6167
|
+
"TEAMCITY_VERSION",
|
|
6168
|
+
"TF_BUILD"
|
|
6169
|
+
];
|
|
6170
|
+
const isCiEnv = (env = process.env) => CI_ENV_KEYS.some((k) => {
|
|
6171
|
+
const v = env[k];
|
|
6172
|
+
return v === "true" || v === "1" || v != null && v.length > 0 && k !== "CI";
|
|
6173
|
+
}) || env.CI === "true" || env.CI === "1";
|
|
6174
|
+
const fileCountBucket = (count) => {
|
|
6175
|
+
if (count < 10) return "0-10";
|
|
6176
|
+
if (count < 50) return "10-50";
|
|
6177
|
+
if (count < 100) return "50-100";
|
|
6178
|
+
if (count < 500) return "100-500";
|
|
6179
|
+
if (count < 1e3) return "500-1000";
|
|
6180
|
+
return "1000+";
|
|
6181
|
+
};
|
|
6182
|
+
const scoreBucket = (score) => {
|
|
5891
6183
|
if (score >= 75) return "75-100";
|
|
5892
6184
|
if (score >= 50) return "50-75";
|
|
5893
6185
|
if (score >= 25) return "25-50";
|
|
5894
6186
|
return "0-25";
|
|
5895
6187
|
};
|
|
5896
|
-
|
|
5897
|
-
|
|
5898
|
-
|
|
5899
|
-
|
|
5900
|
-
|
|
6188
|
+
|
|
6189
|
+
//#endregion
|
|
6190
|
+
//#region src/telemetry/identity.ts
|
|
6191
|
+
const FILE_BASENAME = "install_id";
|
|
6192
|
+
const resolveInstallIdPath = (homedir = os.homedir(), env = process.env) => {
|
|
6193
|
+
if (process.platform === "linux" && env.XDG_STATE_HOME) return path.join(env.XDG_STATE_HOME, "aislop", FILE_BASENAME);
|
|
6194
|
+
return path.join(homedir, ".aislop", FILE_BASENAME);
|
|
6195
|
+
};
|
|
6196
|
+
const ensureInstallId = (idPath = resolveInstallIdPath()) => {
|
|
6197
|
+
if (fs.existsSync(idPath)) {
|
|
6198
|
+
const existing = fs.readFileSync(idPath, "utf-8").trim();
|
|
6199
|
+
if (existing.length > 0) return {
|
|
6200
|
+
installId: existing,
|
|
6201
|
+
created: false
|
|
6202
|
+
};
|
|
6203
|
+
}
|
|
6204
|
+
const dir = path.dirname(idPath);
|
|
6205
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
6206
|
+
const installId = randomUUID();
|
|
6207
|
+
const tmpPath = `${idPath}.${process.pid}.tmp`;
|
|
6208
|
+
fs.writeFileSync(tmpPath, `${installId}\n`, { mode: 384 });
|
|
6209
|
+
try {
|
|
6210
|
+
fs.renameSync(tmpPath, idPath);
|
|
6211
|
+
return {
|
|
6212
|
+
installId,
|
|
6213
|
+
created: true
|
|
6214
|
+
};
|
|
6215
|
+
} catch {
|
|
6216
|
+
fs.rmSync(tmpPath, { force: true });
|
|
6217
|
+
return {
|
|
6218
|
+
installId: fs.readFileSync(idPath, "utf-8").trim(),
|
|
6219
|
+
created: false
|
|
6220
|
+
};
|
|
6221
|
+
}
|
|
6222
|
+
};
|
|
6223
|
+
|
|
6224
|
+
//#endregion
|
|
6225
|
+
//#region src/telemetry/redaction.ts
|
|
6226
|
+
const SAFE_PROPERTY_NAMES = new Set([
|
|
6227
|
+
"aislop_version",
|
|
6228
|
+
"node_version",
|
|
6229
|
+
"os",
|
|
6230
|
+
"arch",
|
|
6231
|
+
"schema_version",
|
|
6232
|
+
"anonymous_install_id",
|
|
6233
|
+
"package_manager",
|
|
6234
|
+
"is_ci",
|
|
6235
|
+
"command",
|
|
6236
|
+
"language_summary",
|
|
6237
|
+
"lang_typescript",
|
|
6238
|
+
"lang_javascript",
|
|
6239
|
+
"lang_python",
|
|
6240
|
+
"lang_java",
|
|
6241
|
+
"file_count_bucket",
|
|
6242
|
+
"exit_code",
|
|
6243
|
+
"duration_ms",
|
|
6244
|
+
"error_kind",
|
|
6245
|
+
"score",
|
|
6246
|
+
"score_bucket",
|
|
6247
|
+
"finding_count",
|
|
6248
|
+
"error_count",
|
|
6249
|
+
"warning_count",
|
|
6250
|
+
"fixable_count",
|
|
6251
|
+
"fix_steps",
|
|
6252
|
+
"fix_resolved",
|
|
6253
|
+
"fix_score_delta",
|
|
6254
|
+
"engine_format_issues",
|
|
6255
|
+
"engine_format_ms",
|
|
6256
|
+
"engine_lint_issues",
|
|
6257
|
+
"engine_lint_ms",
|
|
6258
|
+
"engine_code_quality_issues",
|
|
6259
|
+
"engine_code_quality_ms",
|
|
6260
|
+
"engine_ai_slop_issues",
|
|
6261
|
+
"engine_ai_slop_ms",
|
|
6262
|
+
"engine_architecture_issues",
|
|
6263
|
+
"engine_architecture_ms",
|
|
6264
|
+
"engine_security_issues",
|
|
6265
|
+
"engine_security_ms",
|
|
6266
|
+
"tool",
|
|
6267
|
+
"ok",
|
|
6268
|
+
"agent",
|
|
6269
|
+
"score_delta"
|
|
6270
|
+
]);
|
|
6271
|
+
const redactProperties = (props) => {
|
|
6272
|
+
const clean = {};
|
|
6273
|
+
const dropped = [];
|
|
6274
|
+
for (const [key, value] of Object.entries(props)) {
|
|
6275
|
+
if (value === void 0) continue;
|
|
6276
|
+
if (SAFE_PROPERTY_NAMES.has(key)) clean[key] = value;
|
|
6277
|
+
else dropped.push(key);
|
|
6278
|
+
}
|
|
6279
|
+
return {
|
|
6280
|
+
clean,
|
|
6281
|
+
dropped
|
|
6282
|
+
};
|
|
6283
|
+
};
|
|
6284
|
+
|
|
6285
|
+
//#endregion
|
|
6286
|
+
//#region src/telemetry/client.ts
|
|
6287
|
+
const POSTHOG_HOST = "https://eu.i.posthog.com";
|
|
6288
|
+
const POSTHOG_KEY = "phc_eY2cOMFva9q24GrWeOuvuVIOhCIdjOALxeAR3ItrqbJ";
|
|
6289
|
+
const SCHEMA_VERSION = "v2";
|
|
6290
|
+
const REQUEST_TIMEOUT_MS = 3e3;
|
|
6291
|
+
const isTelemetryDisabled = (config) => {
|
|
6292
|
+
const env = process.env;
|
|
6293
|
+
if (env.AISLOP_NO_TELEMETRY === "1" || env.DO_NOT_TRACK === "1") return true;
|
|
6294
|
+
if (config?.enabled === false) return true;
|
|
6295
|
+
if (config?.enabled === true) return false;
|
|
6296
|
+
if (env.CI === "true" || env.CI === "1") return true;
|
|
6297
|
+
return false;
|
|
5901
6298
|
};
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
6299
|
+
const isDebug = () => process.env.AISLOP_TELEMETRY_DEBUG === "1";
|
|
6300
|
+
const pendingRequests = /* @__PURE__ */ new Set();
|
|
6301
|
+
let cachedInstallId = null;
|
|
6302
|
+
let installCreated = false;
|
|
6303
|
+
const baseProperties = (installId) => ({
|
|
6304
|
+
aislop_version: APP_VERSION,
|
|
6305
|
+
node_version: process.version,
|
|
6306
|
+
os: os.platform(),
|
|
6307
|
+
arch: os.arch(),
|
|
6308
|
+
schema_version: SCHEMA_VERSION,
|
|
6309
|
+
anonymous_install_id: installId,
|
|
6310
|
+
package_manager: detectPackageManager$1(),
|
|
6311
|
+
is_ci: isCiEnv()
|
|
6312
|
+
});
|
|
6313
|
+
const track = (input) => {
|
|
6314
|
+
if (isTelemetryDisabled(input.config)) return { installCreated: false };
|
|
6315
|
+
if (cachedInstallId == null) {
|
|
6316
|
+
const ensured = ensureInstallId(resolveInstallIdPath());
|
|
6317
|
+
cachedInstallId = ensured.installId;
|
|
6318
|
+
installCreated = ensured.created;
|
|
6319
|
+
}
|
|
6320
|
+
const { clean, dropped } = redactProperties({
|
|
6321
|
+
...baseProperties(cachedInstallId),
|
|
6322
|
+
...input.properties
|
|
6323
|
+
});
|
|
6324
|
+
if (isDebug()) {
|
|
6325
|
+
const compact = JSON.stringify({
|
|
6326
|
+
event: input.event,
|
|
6327
|
+
properties: clean
|
|
6328
|
+
});
|
|
6329
|
+
process.stderr.write(`[telemetry] ${compact}\n`);
|
|
6330
|
+
if (dropped.length > 0) for (const key of dropped) process.stderr.write(`[telemetry] dropped non-allowlisted property: ${key}\n`);
|
|
6331
|
+
}
|
|
6332
|
+
if (process.env.AISLOP_TELEMETRY_DRY_RUN === "1") return { installCreated };
|
|
5905
6333
|
const payload = {
|
|
5906
6334
|
api_key: POSTHOG_KEY,
|
|
5907
|
-
event:
|
|
5908
|
-
distinct_id:
|
|
5909
|
-
properties:
|
|
5910
|
-
version: APP_VERSION,
|
|
5911
|
-
node_version: process.version,
|
|
5912
|
-
os: os.platform(),
|
|
5913
|
-
arch: os.arch(),
|
|
5914
|
-
languages: event.languages,
|
|
5915
|
-
score_bucket: event.scoreBucket,
|
|
5916
|
-
engine_issues: event.engineIssues,
|
|
5917
|
-
engine_timings: event.engineTimings,
|
|
5918
|
-
elapsed_ms: event.elapsedMs,
|
|
5919
|
-
file_count: event.fileCount,
|
|
5920
|
-
fix_steps: event.fixSteps,
|
|
5921
|
-
fix_resolved: event.fixResolved
|
|
5922
|
-
},
|
|
6335
|
+
event: input.event,
|
|
6336
|
+
distinct_id: cachedInstallId,
|
|
6337
|
+
properties: clean,
|
|
5923
6338
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
5924
6339
|
};
|
|
5925
|
-
|
|
6340
|
+
const request = fetch(`${POSTHOG_HOST}/capture/`, {
|
|
5926
6341
|
method: "POST",
|
|
5927
6342
|
headers: { "Content-Type": "application/json" },
|
|
5928
6343
|
body: JSON.stringify(payload),
|
|
5929
|
-
signal: AbortSignal.timeout(
|
|
5930
|
-
}).then(() => {}).catch(() => {})
|
|
6344
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
6345
|
+
}).then(() => {}).catch(() => {}).finally(() => {
|
|
6346
|
+
pendingRequests.delete(request);
|
|
6347
|
+
});
|
|
6348
|
+
pendingRequests.add(request);
|
|
6349
|
+
return { installCreated };
|
|
6350
|
+
};
|
|
6351
|
+
const flushTelemetry = async () => {
|
|
6352
|
+
if (pendingRequests.size === 0) return;
|
|
6353
|
+
await Promise.all(pendingRequests);
|
|
6354
|
+
};
|
|
6355
|
+
|
|
6356
|
+
//#endregion
|
|
6357
|
+
//#region src/telemetry/language.ts
|
|
6358
|
+
const ALL_LANGUAGES = [
|
|
6359
|
+
"typescript",
|
|
6360
|
+
"javascript",
|
|
6361
|
+
"python",
|
|
6362
|
+
"java"
|
|
6363
|
+
];
|
|
6364
|
+
const buildLanguageProperties = (detected) => {
|
|
6365
|
+
const present = new Set(detected);
|
|
6366
|
+
const summary = [...present].filter((l) => ALL_LANGUAGES.includes(l));
|
|
6367
|
+
summary.sort();
|
|
6368
|
+
return {
|
|
6369
|
+
language_summary: summary.join(","),
|
|
6370
|
+
lang_typescript: present.has("typescript"),
|
|
6371
|
+
lang_javascript: present.has("javascript"),
|
|
6372
|
+
lang_python: present.has("python"),
|
|
6373
|
+
lang_java: present.has("java")
|
|
6374
|
+
};
|
|
6375
|
+
};
|
|
6376
|
+
|
|
6377
|
+
//#endregion
|
|
6378
|
+
//#region src/telemetry/events.ts
|
|
6379
|
+
const buildCommandStartedProps = (input) => {
|
|
6380
|
+
const props = { command: input.command };
|
|
6381
|
+
if (input.languages) Object.assign(props, buildLanguageProperties(input.languages));
|
|
6382
|
+
if (typeof input.fileCount === "number") props.file_count_bucket = fileCountBucket(input.fileCount);
|
|
6383
|
+
return props;
|
|
6384
|
+
};
|
|
6385
|
+
const ENGINE_KEY_MAP = {
|
|
6386
|
+
format: "engine_format",
|
|
6387
|
+
lint: "engine_lint",
|
|
6388
|
+
"code-quality": "engine_code_quality",
|
|
6389
|
+
"ai-slop": "engine_ai_slop",
|
|
6390
|
+
architecture: "engine_architecture",
|
|
6391
|
+
security: "engine_security"
|
|
6392
|
+
};
|
|
6393
|
+
const flattenEngineStats = (issues, timings) => {
|
|
6394
|
+
const out = {};
|
|
6395
|
+
for (const [engine, count] of Object.entries(issues)) {
|
|
6396
|
+
const key = ENGINE_KEY_MAP[engine];
|
|
6397
|
+
if (key != null && typeof count === "number") out[`${key}_issues`] = count;
|
|
6398
|
+
}
|
|
6399
|
+
for (const [engine, ms] of Object.entries(timings)) {
|
|
6400
|
+
const key = ENGINE_KEY_MAP[engine];
|
|
6401
|
+
if (key != null && typeof ms === "number") out[`${key}_ms`] = Math.round(ms);
|
|
6402
|
+
}
|
|
6403
|
+
return out;
|
|
6404
|
+
};
|
|
6405
|
+
const buildCommandCompletedProps = (input) => {
|
|
6406
|
+
const props = {
|
|
6407
|
+
...input.startProps,
|
|
6408
|
+
exit_code: input.exitCode,
|
|
6409
|
+
duration_ms: Math.round(input.durationMs)
|
|
6410
|
+
};
|
|
6411
|
+
if (input.errorKind) props.error_kind = input.errorKind;
|
|
6412
|
+
if (typeof input.score === "number") {
|
|
6413
|
+
props.score = input.score;
|
|
6414
|
+
props.score_bucket = scoreBucket(input.score);
|
|
6415
|
+
}
|
|
6416
|
+
if (typeof input.findingCount === "number") props.finding_count = input.findingCount;
|
|
6417
|
+
if (typeof input.errorCount === "number") props.error_count = input.errorCount;
|
|
6418
|
+
if (typeof input.warningCount === "number") props.warning_count = input.warningCount;
|
|
6419
|
+
if (typeof input.fixableCount === "number") props.fixable_count = input.fixableCount;
|
|
6420
|
+
if (input.engineIssues && input.engineTimings) Object.assign(props, flattenEngineStats(input.engineIssues, input.engineTimings));
|
|
6421
|
+
if (typeof input.fixSteps === "number") props.fix_steps = input.fixSteps;
|
|
6422
|
+
if (typeof input.fixResolved === "number") props.fix_resolved = input.fixResolved;
|
|
6423
|
+
if (typeof input.fixScoreDelta === "number") props.fix_score_delta = input.fixScoreDelta;
|
|
6424
|
+
return props;
|
|
6425
|
+
};
|
|
6426
|
+
const errorKindFromException = (error) => {
|
|
6427
|
+
const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
|
|
6428
|
+
if (message.includes("timeout") || message.includes("timed out")) return "timeout";
|
|
6429
|
+
if (message.includes("invalid config") || message.includes("config_invalid")) return "config_invalid";
|
|
6430
|
+
if (message.includes("engine") && message.includes("crash")) return "engine_crash";
|
|
6431
|
+
return "unknown";
|
|
6432
|
+
};
|
|
6433
|
+
|
|
6434
|
+
//#endregion
|
|
6435
|
+
//#region src/telemetry/lifecycle.ts
|
|
6436
|
+
const withCommandLifecycle = async (start, run) => {
|
|
6437
|
+
const startProps = buildCommandStartedProps({
|
|
6438
|
+
command: start.command,
|
|
6439
|
+
languages: start.languages,
|
|
6440
|
+
fileCount: start.fileCount
|
|
6441
|
+
});
|
|
6442
|
+
track({
|
|
6443
|
+
event: "cli_command_started",
|
|
6444
|
+
properties: startProps,
|
|
6445
|
+
config: start.config
|
|
6446
|
+
});
|
|
6447
|
+
const startedAt = performance.now();
|
|
6448
|
+
try {
|
|
6449
|
+
const result = await run();
|
|
6450
|
+
const durationMs = performance.now() - startedAt;
|
|
6451
|
+
track({
|
|
6452
|
+
event: "cli_command_completed",
|
|
6453
|
+
properties: buildCommandCompletedProps({
|
|
6454
|
+
startProps,
|
|
6455
|
+
exitCode: result.exitCode,
|
|
6456
|
+
durationMs,
|
|
6457
|
+
score: result.score,
|
|
6458
|
+
findingCount: result.findingCount,
|
|
6459
|
+
errorCount: result.errorCount,
|
|
6460
|
+
warningCount: result.warningCount,
|
|
6461
|
+
fixableCount: result.fixableCount,
|
|
6462
|
+
engineIssues: result.engineIssues,
|
|
6463
|
+
engineTimings: result.engineTimings,
|
|
6464
|
+
fixSteps: result.fixSteps,
|
|
6465
|
+
fixResolved: result.fixResolved,
|
|
6466
|
+
fixScoreDelta: result.fixScoreDelta
|
|
6467
|
+
}),
|
|
6468
|
+
config: start.config
|
|
6469
|
+
});
|
|
6470
|
+
await flushTelemetry();
|
|
6471
|
+
return result;
|
|
6472
|
+
} catch (error) {
|
|
6473
|
+
track({
|
|
6474
|
+
event: "cli_command_completed",
|
|
6475
|
+
properties: buildCommandCompletedProps({
|
|
6476
|
+
startProps,
|
|
6477
|
+
exitCode: 1,
|
|
6478
|
+
durationMs: performance.now() - startedAt,
|
|
6479
|
+
errorKind: errorKindFromException(error)
|
|
6480
|
+
}),
|
|
6481
|
+
config: start.config
|
|
6482
|
+
});
|
|
6483
|
+
await flushTelemetry();
|
|
6484
|
+
throw error;
|
|
6485
|
+
}
|
|
5931
6486
|
};
|
|
5932
6487
|
|
|
5933
6488
|
//#endregion
|
|
@@ -5977,8 +6532,53 @@ const wrapHelpText = (text, maxWidth, indent) => {
|
|
|
5977
6532
|
};
|
|
5978
6533
|
const terminalWidth = () => {
|
|
5979
6534
|
const raw = process.stdout.columns;
|
|
5980
|
-
if (typeof raw !== "number" || raw <= 0) return
|
|
5981
|
-
return Math.min(raw,
|
|
6535
|
+
if (typeof raw !== "number" || raw <= 0) return 120;
|
|
6536
|
+
return Math.min(raw, 120);
|
|
6537
|
+
};
|
|
6538
|
+
const renderRuleHeader = (first, count, lines) => {
|
|
6539
|
+
const level = toSeverityLabel(first.severity);
|
|
6540
|
+
const countLabel = count > 1 ? ` (${count})` : "";
|
|
6541
|
+
const status = colorBySeverity(level, first.severity);
|
|
6542
|
+
const fixableTag = first.fixable ? ` ${style(theme, "muted", "[auto]")}` : "";
|
|
6543
|
+
const fixableWidth = first.fixable ? 7 : 0;
|
|
6544
|
+
const badgePrefix = ` [${status}]${fixableTag} `;
|
|
6545
|
+
const badgePrefixWidth = 5 + level.length + 1 + fixableWidth + 1;
|
|
6546
|
+
const wrapped = wrapText(`${first.message}${countLabel}`, terminalWidth(), badgePrefixWidth, " ");
|
|
6547
|
+
lines.push(`${badgePrefix}${wrapped[0]}`);
|
|
6548
|
+
for (let i = 1; i < wrapped.length; i++) lines.push(wrapped[i]);
|
|
6549
|
+
};
|
|
6550
|
+
const renderLocations = (ruleDiags, verbose, lines) => {
|
|
6551
|
+
const unique = [];
|
|
6552
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6553
|
+
for (const d of ruleDiags) {
|
|
6554
|
+
const label = toLocationLabel(d);
|
|
6555
|
+
const detail = d.detail ?? "";
|
|
6556
|
+
const key = `${label}|${detail}`;
|
|
6557
|
+
if (seen.has(key)) continue;
|
|
6558
|
+
seen.add(key);
|
|
6559
|
+
unique.push({
|
|
6560
|
+
label,
|
|
6561
|
+
detail
|
|
6562
|
+
});
|
|
6563
|
+
}
|
|
6564
|
+
const shown = verbose ? unique : unique.slice(0, 3);
|
|
6565
|
+
const maxLabel = shown.reduce((w, l) => Math.max(w, l.label.length), 0);
|
|
6566
|
+
for (const { label, detail } of shown) {
|
|
6567
|
+
const padded = detail ? `${label.padEnd(maxLabel)} ${detail}` : label;
|
|
6568
|
+
lines.push(style(theme, "muted", ` ${padded}`));
|
|
6569
|
+
}
|
|
6570
|
+
if (!verbose && unique.length > shown.length) lines.push(style(theme, "muted", ` +${unique.length - shown.length} more location(s), use -d for full list`));
|
|
6571
|
+
};
|
|
6572
|
+
const renderHiddenFooter = (sorted, maxRules, lines) => {
|
|
6573
|
+
const hidden = sorted.slice(maxRules);
|
|
6574
|
+
const hiddenErrors = hidden.reduce((acc, [, diags]) => acc + (diags[0].severity === "error" ? diags.length : 0), 0);
|
|
6575
|
+
const hiddenWarnings = hidden.reduce((acc, [, diags]) => acc + (diags[0].severity === "warning" ? diags.length : 0), 0);
|
|
6576
|
+
const parts = [];
|
|
6577
|
+
if (hiddenErrors > 0) parts.push(`${hiddenErrors} error${hiddenErrors === 1 ? "" : "s"}`);
|
|
6578
|
+
if (hiddenWarnings > 0) parts.push(`${hiddenWarnings} warning${hiddenWarnings === 1 ? "" : "s"}`);
|
|
6579
|
+
const detail = parts.length > 0 ? ` (${parts.join(", ")})` : "";
|
|
6580
|
+
lines.push(style(theme, "muted", ` ... and ${hidden.length} more rules hidden${detail}. Run with -v or --verbose to see full output.`));
|
|
6581
|
+
lines.push("");
|
|
5982
6582
|
};
|
|
5983
6583
|
const renderDiagnostics = (diagnostics, verbose) => {
|
|
5984
6584
|
const lines = [];
|
|
@@ -5987,29 +6587,23 @@ const renderDiagnostics = (diagnostics, verbose) => {
|
|
|
5987
6587
|
const label = getEngineLabel(engine);
|
|
5988
6588
|
lines.push(` ${style(theme, "bold", `${symbols.engineActive} ${label}`)}`);
|
|
5989
6589
|
const sorted = [...groupBy(engineDiags, (d) => `${d.rule}:${d.message}`).entries()].sort(([, a], [, b]) => {
|
|
5990
|
-
|
|
6590
|
+
const sa = a[0].severity === "error" ? 0 : a[0].severity === "warning" ? 1 : 2;
|
|
6591
|
+
const sb = b[0].severity === "error" ? 0 : b[0].severity === "warning" ? 1 : 2;
|
|
6592
|
+
if (sa !== sb) return sa - sb;
|
|
6593
|
+
return b.length - a.length;
|
|
5991
6594
|
});
|
|
5992
|
-
|
|
6595
|
+
const maxRules = verbose ? Infinity : 40;
|
|
6596
|
+
for (const [, ruleDiags] of sorted.slice(0, maxRules)) {
|
|
5993
6597
|
const first = ruleDiags[0];
|
|
5994
|
-
|
|
5995
|
-
|
|
5996
|
-
const status = colorBySeverity(level, first.severity);
|
|
5997
|
-
const fixableTag = first.fixable ? ` ${style(theme, "muted", "[auto]")}` : "";
|
|
5998
|
-
const fixableWidth = first.fixable ? 7 : 0;
|
|
5999
|
-
const badgePrefix = ` [${status}]${fixableTag} `;
|
|
6000
|
-
const badgePrefixWidth = 5 + level.length + 1 + fixableWidth + 1;
|
|
6001
|
-
const wrappedMsg = wrapText(`${first.message}${count}`, terminalWidth(), badgePrefixWidth, " ");
|
|
6002
|
-
lines.push(`${badgePrefix}${wrappedMsg[0]}`);
|
|
6003
|
-
for (let i = 1; i < wrappedMsg.length; i++) lines.push(wrappedMsg[i]);
|
|
6004
|
-
const locations = verbose ? ruleDiags : ruleDiags.slice(0, 3);
|
|
6005
|
-
for (const diagnostic of locations) lines.push(style(theme, "muted", ` ${toLocationLabel(diagnostic)}`));
|
|
6006
|
-
if (!verbose && ruleDiags.length > locations.length) lines.push(style(theme, "muted", ` +${ruleDiags.length - locations.length} more location(s), use -d for full list`));
|
|
6598
|
+
renderRuleHeader(first, ruleDiags.length, lines);
|
|
6599
|
+
renderLocations(ruleDiags, verbose, lines);
|
|
6007
6600
|
if (first.help) {
|
|
6008
6601
|
const wrapped = wrapHelpText(first.help, terminalWidth(), " ");
|
|
6009
6602
|
for (const line of wrapped) lines.push(style(theme, "muted", line));
|
|
6010
6603
|
}
|
|
6011
6604
|
lines.push("");
|
|
6012
6605
|
}
|
|
6606
|
+
if (sorted.length > maxRules) renderHiddenFooter(sorted, maxRules, lines);
|
|
6013
6607
|
}
|
|
6014
6608
|
return `${lines.join("\n")}\n`;
|
|
6015
6609
|
};
|
|
@@ -6147,6 +6741,81 @@ var LiveGrid = class {
|
|
|
6147
6741
|
}
|
|
6148
6742
|
};
|
|
6149
6743
|
|
|
6744
|
+
//#endregion
|
|
6745
|
+
//#region src/output/rule-labels.ts
|
|
6746
|
+
const RULE_LABELS = {
|
|
6747
|
+
formatting: "Code not formatted",
|
|
6748
|
+
"code-quality/duplicate-block": "Duplicate code block",
|
|
6749
|
+
"complexity/file-too-large": "File too large",
|
|
6750
|
+
"complexity/function-too-long": "Function too long",
|
|
6751
|
+
"complexity/deep-nesting": "Deeply nested code",
|
|
6752
|
+
"complexity/too-many-params": "Too many parameters",
|
|
6753
|
+
"knip/files": "Unused file",
|
|
6754
|
+
"knip/dependencies": "Unused dependency",
|
|
6755
|
+
"knip/devDependencies": "Unused dev dependency",
|
|
6756
|
+
"knip/unlisted": "Used but not in package.json",
|
|
6757
|
+
"knip/unresolved": "Unresolved import",
|
|
6758
|
+
"knip/binaries": "Unused binary",
|
|
6759
|
+
"knip/exports": "Unused export",
|
|
6760
|
+
"knip/types": "Unused type",
|
|
6761
|
+
"ai-slop/trivial-comment": "Trivial restating comment",
|
|
6762
|
+
"ai-slop/swallowed-exception": "Empty catch (swallowed error)",
|
|
6763
|
+
"ai-slop/thin-wrapper": "Thin function wrapper",
|
|
6764
|
+
"ai-slop/generic-naming": "Generic/vague identifier name",
|
|
6765
|
+
"ai-slop/unused-import": "Unused import",
|
|
6766
|
+
"ai-slop/console-leftover": "console.log left in code",
|
|
6767
|
+
"ai-slop/todo-stub": "Unresolved TODO/FIXME",
|
|
6768
|
+
"ai-slop/unreachable-code": "Unreachable code",
|
|
6769
|
+
"ai-slop/constant-condition": "Constant condition",
|
|
6770
|
+
"ai-slop/empty-function": "Empty function body",
|
|
6771
|
+
"ai-slop/unsafe-type-assertion": "Unsafe type cast",
|
|
6772
|
+
"ai-slop/double-type-assertion": "Double type cast",
|
|
6773
|
+
"ai-slop/ts-directive": "@ts-ignore / @ts-expect-error",
|
|
6774
|
+
"ai-slop/narrative-comment": "Narrative comment block",
|
|
6775
|
+
"ai-slop/duplicate-import": "Duplicate import statement",
|
|
6776
|
+
"ai-slop/python-bare-except": "Bare except",
|
|
6777
|
+
"ai-slop/python-broad-except": "Broad except",
|
|
6778
|
+
"ai-slop/python-mutable-default": "Mutable default argument",
|
|
6779
|
+
"ai-slop/python-print-debug": "print() left in code",
|
|
6780
|
+
"ai-slop/go-library-panic": "panic() in Go library code",
|
|
6781
|
+
"ai-slop/rust-non-test-unwrap": "Rust .unwrap() in production code",
|
|
6782
|
+
"ai-slop/rust-todo-stub": "Rust todo!() stub",
|
|
6783
|
+
"ai-slop/hallucinated-import": "Import not in package.json",
|
|
6784
|
+
"security/hardcoded-secret": "Possible hardcoded secret",
|
|
6785
|
+
"security/vulnerable-dependency": "Vulnerable dependency",
|
|
6786
|
+
"security/eval": "eval() usage",
|
|
6787
|
+
"security/innerhtml": "innerHTML assignment",
|
|
6788
|
+
"security/dangerously-set-innerhtml": "dangerouslySetInnerHTML (XSS risk)",
|
|
6789
|
+
"security/sql-injection": "Possible SQL injection",
|
|
6790
|
+
"security/shell-injection": "Possible shell injection",
|
|
6791
|
+
"eslint/no-undef": "Undefined identifier",
|
|
6792
|
+
"eslint/no-unused-vars": "Unused variable",
|
|
6793
|
+
"eslint/no-unassigned-vars": "Variable never assigned",
|
|
6794
|
+
"eslint/no-empty": "Empty block statement",
|
|
6795
|
+
"eslint/no-unused-expressions": "Unused expression",
|
|
6796
|
+
"eslint/no-shadow-restricted-names": "Shadowing restricted name",
|
|
6797
|
+
"eslint/no-constant-binary-expression": "Constant binary expression",
|
|
6798
|
+
"eslint/no-unsafe-optional-chaining": "Unsafe optional chaining",
|
|
6799
|
+
"eslint/require-yield": "Generator with no yield",
|
|
6800
|
+
"import/no-duplicates": "Duplicate import path",
|
|
6801
|
+
"import/default": "Missing default export",
|
|
6802
|
+
"import/named": "Missing named export",
|
|
6803
|
+
"import/namespace": "Invalid namespace import",
|
|
6804
|
+
"typescript-eslint/triple-slash-reference": "Triple-slash reference",
|
|
6805
|
+
"unicorn/no-useless-fallback-in-spread": "Useless spread fallback",
|
|
6806
|
+
"unicorn/no-invalid-remove-event-listener": "Invalid removeEventListener",
|
|
6807
|
+
"unicorn/no-empty-file": "Empty file",
|
|
6808
|
+
"unicorn/no-useless-length-check": "Useless array length check",
|
|
6809
|
+
"unicorn/no-new-array": "Avoid new Array(n)",
|
|
6810
|
+
"unicorn/no-useless-spread": "Useless spread",
|
|
6811
|
+
"unicorn/no-single-promise-in-promise-methods": "Single-element Promise.all"
|
|
6812
|
+
};
|
|
6813
|
+
const prettifyFallback = (ruleId) => {
|
|
6814
|
+
const spaced = (ruleId.includes("/") ? ruleId.slice(ruleId.indexOf("/") + 1) : ruleId).replace(/[-_]/g, " ").replace(/\//g, " · ");
|
|
6815
|
+
return spaced.charAt(0).toUpperCase() + spaced.slice(1);
|
|
6816
|
+
};
|
|
6817
|
+
const labelForRule = (ruleId) => RULE_LABELS[ruleId] ?? prettifyFallback(ruleId);
|
|
6818
|
+
|
|
6150
6819
|
//#endregion
|
|
6151
6820
|
//#region src/ui/summary.ts
|
|
6152
6821
|
const elapsed = (ms) => ms < 1e3 ? `${Math.round(ms)}ms` : `${(ms / 1e3).toFixed(1)}s`;
|
|
@@ -6173,6 +6842,34 @@ const renderSummary = (input, deps = {}) => {
|
|
|
6173
6842
|
` ${style(t, "muted", `${input.files} files`)} ${sep} ${style(t, "muted", `${input.engines} engines`)} ${sep} ${style(t, "muted", elapsed(input.elapsedMs))}`,
|
|
6174
6843
|
""
|
|
6175
6844
|
];
|
|
6845
|
+
if (input.breakdown && input.breakdown.rows.length > 0) {
|
|
6846
|
+
lines.push(` ${style(t, "bold", "Top findings")}`);
|
|
6847
|
+
const maxCountWidth = input.breakdown.rows.reduce((w, r) => Math.max(w, String(r.errors + r.warnings + r.info).length), 0);
|
|
6848
|
+
const labels = input.breakdown.rows.map((r) => labelForRule(r.rule));
|
|
6849
|
+
const maxLabelWidth = labels.reduce((w, l) => Math.max(w, l.length), 0);
|
|
6850
|
+
for (let i = 0; i < input.breakdown.rows.length; i++) {
|
|
6851
|
+
const row = input.breakdown.rows[i];
|
|
6852
|
+
const total = row.errors + row.warnings + row.info;
|
|
6853
|
+
const count = String(total).padStart(maxCountWidth);
|
|
6854
|
+
const label = padEnd(labels[i], maxLabelWidth);
|
|
6855
|
+
const tags = [];
|
|
6856
|
+
if (row.errors > 0) tags.push(style(t, "danger", `${row.errors} err`));
|
|
6857
|
+
if (row.warnings > 0) tags.push(style(t, "warn", `${row.warnings} warn`));
|
|
6858
|
+
if (row.info > 0) tags.push(style(t, "muted", `${row.info} info`));
|
|
6859
|
+
if (row.fixable > 0) tags.push(style(t, "success", `${row.fixable} fix`));
|
|
6860
|
+
const tagBlock = tags.length > 0 ? ` ${style(t, "muted", "·")} ${tags.join(" ")}` : "";
|
|
6861
|
+
const ruleHint = style(t, "muted", `(${row.rule})`);
|
|
6862
|
+
lines.push(` ${style(t, "muted", count)} ${label} ${ruleHint}${tagBlock}`);
|
|
6863
|
+
}
|
|
6864
|
+
if (input.breakdown.hiddenRules > 0) {
|
|
6865
|
+
const hiddenParts = [];
|
|
6866
|
+
if (input.breakdown.hiddenErrors > 0) hiddenParts.push(`${input.breakdown.hiddenErrors} error${input.breakdown.hiddenErrors === 1 ? "" : "s"}`);
|
|
6867
|
+
if (input.breakdown.hiddenWarnings > 0) hiddenParts.push(`${input.breakdown.hiddenWarnings} warning${input.breakdown.hiddenWarnings === 1 ? "" : "s"}`);
|
|
6868
|
+
const detail = hiddenParts.length > 0 ? ` (${hiddenParts.join(", ")})` : "";
|
|
6869
|
+
lines.push(style(t, "muted", ` +${input.breakdown.hiddenRules} more rule${input.breakdown.hiddenRules === 1 ? "" : "s"}${detail}. Run with -v for the full list.`));
|
|
6870
|
+
}
|
|
6871
|
+
lines.push("");
|
|
6872
|
+
}
|
|
6176
6873
|
if (input.nextSteps.length > 0) {
|
|
6177
6874
|
for (const step of input.nextSteps) {
|
|
6178
6875
|
const glyph = step.emphasis === "primary" ? s.hint : s.bullet;
|
|
@@ -6245,6 +6942,39 @@ const getStagedFiles = (cwd) => {
|
|
|
6245
6942
|
//#region src/commands/scan.ts
|
|
6246
6943
|
const shouldUseSpinner = () => Boolean(process.stderr.isTTY) && process.env.CI !== "true" && process.env.CI !== "1";
|
|
6247
6944
|
const ALL_ENGINE_NAMES = Object.keys(ENGINE_INFO);
|
|
6945
|
+
const BREAKDOWN_TOP_N = 10;
|
|
6946
|
+
const computeBreakdown = (diagnostics) => {
|
|
6947
|
+
const byRule = /* @__PURE__ */ new Map();
|
|
6948
|
+
for (const d of diagnostics) {
|
|
6949
|
+
const row = byRule.get(d.rule) ?? {
|
|
6950
|
+
rule: d.rule,
|
|
6951
|
+
errors: 0,
|
|
6952
|
+
warnings: 0,
|
|
6953
|
+
info: 0,
|
|
6954
|
+
fixable: 0
|
|
6955
|
+
};
|
|
6956
|
+
if (d.severity === "error") row.errors++;
|
|
6957
|
+
else if (d.severity === "warning") row.warnings++;
|
|
6958
|
+
else row.info++;
|
|
6959
|
+
if (d.fixable) row.fixable++;
|
|
6960
|
+
byRule.set(d.rule, row);
|
|
6961
|
+
}
|
|
6962
|
+
const sorted = [...byRule.values()].sort((a, b) => {
|
|
6963
|
+
const aTotal = a.errors + a.warnings + a.info;
|
|
6964
|
+
const bTotal = b.errors + b.warnings + b.info;
|
|
6965
|
+
if (aTotal !== bTotal) return bTotal - aTotal;
|
|
6966
|
+
if (a.errors !== b.errors) return b.errors - a.errors;
|
|
6967
|
+
return a.rule.localeCompare(b.rule);
|
|
6968
|
+
});
|
|
6969
|
+
const rows = sorted.slice(0, BREAKDOWN_TOP_N);
|
|
6970
|
+
const hidden = sorted.slice(BREAKDOWN_TOP_N);
|
|
6971
|
+
return {
|
|
6972
|
+
rows,
|
|
6973
|
+
hiddenRules: hidden.length,
|
|
6974
|
+
hiddenErrors: hidden.reduce((acc, r) => acc + r.errors, 0),
|
|
6975
|
+
hiddenWarnings: hidden.reduce((acc, r) => acc + r.warnings, 0)
|
|
6976
|
+
};
|
|
6977
|
+
};
|
|
6248
6978
|
const buildScanRender = (input) => {
|
|
6249
6979
|
const deps = {
|
|
6250
6980
|
theme: createTheme(),
|
|
@@ -6294,11 +7024,11 @@ const buildScanRender = (input) => {
|
|
|
6294
7024
|
engines: input.results.length,
|
|
6295
7025
|
elapsedMs: input.elapsedMs,
|
|
6296
7026
|
nextSteps,
|
|
7027
|
+
breakdown: computeBreakdown(input.diagnostics),
|
|
6297
7028
|
thresholds: input.thresholds
|
|
6298
7029
|
}, deps)}`;
|
|
6299
7030
|
};
|
|
6300
7031
|
const scanCommand = async (directory, config, options) => {
|
|
6301
|
-
const startTime = performance.now();
|
|
6302
7032
|
const resolvedDir = path.resolve(directory);
|
|
6303
7033
|
if (!fs.existsSync(resolvedDir)) {
|
|
6304
7034
|
const msg = `Path does not exist: ${resolvedDir}`;
|
|
@@ -6312,9 +7042,18 @@ const scanCommand = async (directory, config, options) => {
|
|
|
6312
7042
|
else log.error(msg);
|
|
6313
7043
|
return { exitCode: 1 };
|
|
6314
7044
|
}
|
|
7045
|
+
const projectInfo = await discoverProject(resolvedDir);
|
|
7046
|
+
return withCommandLifecycle({
|
|
7047
|
+
command: options.command ?? "scan",
|
|
7048
|
+
config: config.telemetry,
|
|
7049
|
+
languages: projectInfo.languages,
|
|
7050
|
+
fileCount: projectInfo.sourceFileCount
|
|
7051
|
+
}, () => runScanBody(resolvedDir, config, options, projectInfo));
|
|
7052
|
+
};
|
|
7053
|
+
const runScanBody = async (resolvedDir, config, options, projectInfo) => {
|
|
7054
|
+
const startTime = performance.now();
|
|
6315
7055
|
const showHeader = options.showHeader !== false;
|
|
6316
7056
|
const useLiveProgress = !options.json && shouldUseSpinner();
|
|
6317
|
-
const projectInfo = await discoverProject(resolvedDir);
|
|
6318
7057
|
let files;
|
|
6319
7058
|
if (options.staged) {
|
|
6320
7059
|
files = filterProjectFiles(resolvedDir, getStagedFiles(resolvedDir), [], config.exclude);
|
|
@@ -6381,28 +7120,27 @@ const scanCommand = async (directory, config, options) => {
|
|
|
6381
7120
|
const elapsedMs = performance.now() - startTime;
|
|
6382
7121
|
const scoreResult = calculateScore(allDiagnostics, config.scoring.weights, config.scoring.thresholds, projectInfo.sourceFileCount, config.scoring.smoothing);
|
|
6383
7122
|
const exitCode = allDiagnostics.some((d) => d.severity === "error") || scoreResult.score < config.ci.failBelow ? 1 : 0;
|
|
6384
|
-
|
|
6385
|
-
|
|
6386
|
-
|
|
6387
|
-
|
|
6388
|
-
|
|
6389
|
-
|
|
6390
|
-
|
|
6391
|
-
|
|
6392
|
-
|
|
6393
|
-
|
|
6394
|
-
|
|
6395
|
-
|
|
6396
|
-
|
|
6397
|
-
|
|
6398
|
-
|
|
6399
|
-
|
|
6400
|
-
}
|
|
7123
|
+
const engineIssues = {};
|
|
7124
|
+
const engineTimings = {};
|
|
7125
|
+
for (const r of results) {
|
|
7126
|
+
engineIssues[r.engine] = r.diagnostics.length;
|
|
7127
|
+
engineTimings[r.engine] = Math.round(r.elapsed);
|
|
7128
|
+
}
|
|
7129
|
+
const completion = {
|
|
7130
|
+
exitCode,
|
|
7131
|
+
score: scoreResult.score,
|
|
7132
|
+
findingCount: allDiagnostics.length,
|
|
7133
|
+
errorCount: allDiagnostics.filter((d) => d.severity === "error").length,
|
|
7134
|
+
warningCount: allDiagnostics.filter((d) => d.severity === "warning").length,
|
|
7135
|
+
fixableCount: allDiagnostics.filter((d) => d.fixable).length,
|
|
7136
|
+
engineIssues,
|
|
7137
|
+
engineTimings
|
|
7138
|
+
};
|
|
6401
7139
|
if (options.json) {
|
|
6402
|
-
const { buildJsonOutput } = await import("./json-
|
|
7140
|
+
const { buildJsonOutput } = await import("./json-B_2_Zt7I.js");
|
|
6403
7141
|
const jsonOut = buildJsonOutput(results, scoreResult, projectInfo.sourceFileCount, elapsedMs);
|
|
6404
7142
|
console.log(JSON.stringify(jsonOut, null, 2));
|
|
6405
|
-
return
|
|
7143
|
+
return completion;
|
|
6406
7144
|
}
|
|
6407
7145
|
const projectName = projectInfo.projectName ?? "project";
|
|
6408
7146
|
const language = projectInfo.languages[0] ?? "unknown";
|
|
@@ -6419,7 +7157,7 @@ const scanCommand = async (directory, config, options) => {
|
|
|
6419
7157
|
includeHeader: showHeader,
|
|
6420
7158
|
printBrand: options.printBrand
|
|
6421
7159
|
}));
|
|
6422
|
-
return
|
|
7160
|
+
return completion;
|
|
6423
7161
|
};
|
|
6424
7162
|
|
|
6425
7163
|
//#endregion
|
|
@@ -7764,15 +8502,23 @@ const fixCommand = async (directory, config, options = {
|
|
|
7764
8502
|
verbose: false,
|
|
7765
8503
|
showHeader: true
|
|
7766
8504
|
}) => {
|
|
7767
|
-
const startTime = performance.now();
|
|
7768
8505
|
const resolvedDir = path.resolve(directory);
|
|
7769
8506
|
if (!fs.existsSync(resolvedDir) || !fs.statSync(resolvedDir).isDirectory()) {
|
|
7770
8507
|
const msg = !fs.existsSync(resolvedDir) ? `Path does not exist: ${resolvedDir}` : `Not a directory: ${resolvedDir}`;
|
|
7771
8508
|
log.error(msg);
|
|
7772
8509
|
return;
|
|
7773
8510
|
}
|
|
7774
|
-
const showHeader = options.showHeader !== false;
|
|
7775
8511
|
const projectInfo = await discoverProject(resolvedDir);
|
|
8512
|
+
await withCommandLifecycle({
|
|
8513
|
+
command: "fix",
|
|
8514
|
+
config: config.telemetry,
|
|
8515
|
+
languages: projectInfo.languages,
|
|
8516
|
+
fileCount: projectInfo.sourceFileCount
|
|
8517
|
+
}, () => runFixBody(resolvedDir, config, options, projectInfo));
|
|
8518
|
+
};
|
|
8519
|
+
const runFixBody = async (resolvedDir, config, options, projectInfo) => {
|
|
8520
|
+
const startTime = performance.now();
|
|
8521
|
+
const showHeader = options.showHeader !== false;
|
|
7776
8522
|
const projectName = projectInfo.projectName ?? "project";
|
|
7777
8523
|
if (showHeader) process.stdout.write(renderHeader({
|
|
7778
8524
|
version: APP_VERSION,
|
|
@@ -7809,12 +8555,6 @@ const fixCommand = async (directory, config, options = {
|
|
|
7809
8555
|
await runFormattingStep(pipelineDeps);
|
|
7810
8556
|
await runForceSteps(pipelineDeps);
|
|
7811
8557
|
const totalResolved = steps.reduce((sum, s) => sum + s.resolvedIssues, 0);
|
|
7812
|
-
if (!isTelemetryDisabled(config.telemetry?.enabled)) trackEvent({
|
|
7813
|
-
command: "fix",
|
|
7814
|
-
languages: projectInfo.languages,
|
|
7815
|
-
fixSteps: steps.length,
|
|
7816
|
-
fixResolved: totalResolved
|
|
7817
|
-
});
|
|
7818
8558
|
const configDir = findConfigDir(resolvedDir);
|
|
7819
8559
|
const rulesPath = configDir ? path.join(configDir, RULES_FILE) : void 0;
|
|
7820
8560
|
const engineConfig = {
|
|
@@ -7837,7 +8577,9 @@ const fixCommand = async (directory, config, options = {
|
|
|
7837
8577
|
});
|
|
7838
8578
|
const allDiagnostics = scanResults.flatMap((r) => r.diagnostics);
|
|
7839
8579
|
const scoreResult = calculateScore(allDiagnostics, config.scoring.weights, config.scoring.thresholds, projectInfo.sourceFileCount, config.scoring.smoothing);
|
|
7840
|
-
const
|
|
8580
|
+
const errors = allDiagnostics.filter((d) => d.severity === "error").length;
|
|
8581
|
+
const warnings = allDiagnostics.filter((d) => d.severity === "warning").length;
|
|
8582
|
+
const remaining = errors + warnings;
|
|
7841
8583
|
if (steps.length === 0) rail.complete({
|
|
7842
8584
|
status: "skipped",
|
|
7843
8585
|
label: "No applicable auto-fixers found"
|
|
@@ -7866,12 +8608,31 @@ const fixCommand = async (directory, config, options = {
|
|
|
7866
8608
|
}
|
|
7867
8609
|
if (options.agent) {
|
|
7868
8610
|
launchAgent(options.agent, resolvedDir, allDiagnostics, scoreResult.score);
|
|
7869
|
-
return
|
|
8611
|
+
return {
|
|
8612
|
+
exitCode: 0,
|
|
8613
|
+
score: scoreResult.score,
|
|
8614
|
+
fixSteps: steps.length,
|
|
8615
|
+
fixResolved: totalResolved
|
|
8616
|
+
};
|
|
7870
8617
|
}
|
|
7871
8618
|
if (options.prompt) {
|
|
7872
8619
|
printPrompt(resolvedDir, allDiagnostics, scoreResult.score);
|
|
7873
|
-
return
|
|
8620
|
+
return {
|
|
8621
|
+
exitCode: 0,
|
|
8622
|
+
score: scoreResult.score,
|
|
8623
|
+
fixSteps: steps.length,
|
|
8624
|
+
fixResolved: totalResolved
|
|
8625
|
+
};
|
|
7874
8626
|
}
|
|
8627
|
+
return {
|
|
8628
|
+
exitCode: 0,
|
|
8629
|
+
score: scoreResult.score,
|
|
8630
|
+
findingCount: allDiagnostics.length,
|
|
8631
|
+
errorCount: errors,
|
|
8632
|
+
warningCount: warnings,
|
|
8633
|
+
fixSteps: steps.length,
|
|
8634
|
+
fixResolved: totalResolved
|
|
8635
|
+
};
|
|
7875
8636
|
};
|
|
7876
8637
|
|
|
7877
8638
|
//#endregion
|
|
@@ -7987,10 +8748,18 @@ const promptForConfigChoices = async () => {
|
|
|
7987
8748
|
return {
|
|
7988
8749
|
engines: enginesSelection,
|
|
7989
8750
|
failBelow: Number(failBelowRaw),
|
|
8751
|
+
typecheck: DEFAULT_CONFIG.lint.typecheck,
|
|
7990
8752
|
telemetryEnabled: telemetryChoice === "enabled",
|
|
7991
8753
|
writeGithubWorkflow: workflowChoice === "yes"
|
|
7992
8754
|
};
|
|
7993
8755
|
};
|
|
8756
|
+
const strictChoices = () => ({
|
|
8757
|
+
engines: Object.keys(DEFAULT_CONFIG.engines),
|
|
8758
|
+
failBelow: 85,
|
|
8759
|
+
typecheck: true,
|
|
8760
|
+
telemetryEnabled: DEFAULT_CONFIG.telemetry.enabled,
|
|
8761
|
+
writeGithubWorkflow: true
|
|
8762
|
+
});
|
|
7994
8763
|
const writeAislopConfig = (configDir, configPath, choices) => {
|
|
7995
8764
|
const selected = new Set(choices.engines);
|
|
7996
8765
|
const engines = {
|
|
@@ -8005,6 +8774,7 @@ const writeAislopConfig = (configDir, configPath, choices) => {
|
|
|
8005
8774
|
version: DEFAULT_CONFIG.version,
|
|
8006
8775
|
engines,
|
|
8007
8776
|
quality: { ...DEFAULT_CONFIG.quality },
|
|
8777
|
+
lint: { typecheck: choices.typecheck },
|
|
8008
8778
|
security: { ...DEFAULT_CONFIG.security },
|
|
8009
8779
|
scoring: {
|
|
8010
8780
|
weights: { ...DEFAULT_CONFIG.scoring.weights },
|
|
@@ -8057,7 +8827,7 @@ const initCommand = async (directory, options = {}) => {
|
|
|
8057
8827
|
return;
|
|
8058
8828
|
}
|
|
8059
8829
|
}
|
|
8060
|
-
const choices = await promptForConfigChoices();
|
|
8830
|
+
const choices = options.strict ? strictChoices() : await promptForConfigChoices();
|
|
8061
8831
|
if (!choices) return;
|
|
8062
8832
|
writeAislopConfig(configDir, configPath, choices);
|
|
8063
8833
|
const steps = [{
|