aislop 0.9.0 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/cli.js +662 -180
- package/dist/index.d.ts +4 -0
- package/dist/index.js +615 -164
- package/dist/{json-DZfGz2xa.js → json-DZHn6AE3.js} +1 -1
- package/dist/mcp.js +422 -148
- package/dist/{version-D_rqBdyj.js → version-C3JZkQGA.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-C3JZkQGA.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";
|
|
@@ -28,6 +28,7 @@ const DEFAULT_CONFIG = {
|
|
|
28
28
|
"build",
|
|
29
29
|
"coverage"
|
|
30
30
|
],
|
|
31
|
+
include: [],
|
|
31
32
|
engines: {
|
|
32
33
|
format: true,
|
|
33
34
|
lint: true,
|
|
@@ -63,7 +64,7 @@ const DEFAULT_CONFIG = {
|
|
|
63
64
|
smoothing: 20
|
|
64
65
|
},
|
|
65
66
|
ci: {
|
|
66
|
-
failBelow:
|
|
67
|
+
failBelow: 70,
|
|
67
68
|
format: "json"
|
|
68
69
|
},
|
|
69
70
|
telemetry: { enabled: true }
|
|
@@ -189,7 +190,7 @@ const ScoringSchema = z.object({
|
|
|
189
190
|
smoothing: z.number().nonnegative().default(20)
|
|
190
191
|
});
|
|
191
192
|
const CiSchema = z.object({
|
|
192
|
-
failBelow: z.number().default(
|
|
193
|
+
failBelow: z.number().default(70),
|
|
193
194
|
format: z.enum(["json"]).default("json")
|
|
194
195
|
});
|
|
195
196
|
const TelemetrySchema = z.object({ enabled: z.boolean().default(true) });
|
|
@@ -223,7 +224,7 @@ const AislopConfigSchema = z.object({
|
|
|
223
224
|
smoothing: 20
|
|
224
225
|
})),
|
|
225
226
|
ci: CiSchema.default(() => ({
|
|
226
|
-
failBelow:
|
|
227
|
+
failBelow: 70,
|
|
227
228
|
format: "json"
|
|
228
229
|
})),
|
|
229
230
|
telemetry: TelemetrySchema.default(() => ({ enabled: true })),
|
|
@@ -233,7 +234,8 @@ const AislopConfigSchema = z.object({
|
|
|
233
234
|
"dist",
|
|
234
235
|
"build",
|
|
235
236
|
"coverage"
|
|
236
|
-
])
|
|
237
|
+
]),
|
|
238
|
+
include: z.array(z.string()).default(() => [])
|
|
237
239
|
});
|
|
238
240
|
const defaults = AislopConfigSchema.parse({});
|
|
239
241
|
/**
|
|
@@ -561,31 +563,68 @@ const EXCLUDED_DIRS = [
|
|
|
561
563
|
"dist",
|
|
562
564
|
"build",
|
|
563
565
|
".git",
|
|
566
|
+
".agents",
|
|
564
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",
|
|
565
584
|
"tests",
|
|
566
585
|
"test",
|
|
567
586
|
"__tests__",
|
|
568
587
|
"__test__",
|
|
569
588
|
"spec",
|
|
570
589
|
"__mocks__",
|
|
571
|
-
"fixtures",
|
|
572
590
|
"test_data",
|
|
573
591
|
".next",
|
|
574
592
|
".nuxt",
|
|
575
593
|
"coverage",
|
|
576
|
-
".turbo"
|
|
594
|
+
".turbo",
|
|
595
|
+
"public"
|
|
577
596
|
];
|
|
578
597
|
const FIND_PRUNE_DIRS = [
|
|
579
598
|
"node_modules",
|
|
580
599
|
"dist",
|
|
581
600
|
"build",
|
|
582
601
|
".git",
|
|
602
|
+
".agents",
|
|
583
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",
|
|
584
620
|
".next",
|
|
585
621
|
".nuxt",
|
|
586
622
|
"coverage",
|
|
587
|
-
".turbo"
|
|
623
|
+
".turbo",
|
|
624
|
+
"public"
|
|
588
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));
|
|
589
628
|
const TEST_FILE_PATTERNS = [
|
|
590
629
|
/(?:^|\/).*\.test\.[^/]+$/i,
|
|
591
630
|
/(?:^|\/).*\.spec\.[^/]+$/i,
|
|
@@ -610,6 +649,7 @@ const hasAllowedExtension = (filePath, extraExtensions) => {
|
|
|
610
649
|
return SOURCE_EXTENSIONS.has(extension) || extraExtensions.has(extension);
|
|
611
650
|
};
|
|
612
651
|
const isExcludedPath = (filePath) => EXCLUDED_DIRS.some((dir) => filePath === dir || filePath.startsWith(`${dir}/`) || filePath.includes(`/${dir}/`));
|
|
652
|
+
const isExcludedFromScan = (relativePath) => isExcludedPath(relativePath) || isBuildCacheFile(relativePath);
|
|
613
653
|
const isTestFile$2 = (filePath) => TEST_FILE_PATTERNS.some((pattern) => pattern.test(filePath));
|
|
614
654
|
const getIgnoredPaths = (rootDirectory, files) => {
|
|
615
655
|
if (files.length === 0) return /* @__PURE__ */ new Set();
|
|
@@ -668,7 +708,7 @@ const normalizeExcludePatterns = (patterns) => {
|
|
|
668
708
|
return [p];
|
|
669
709
|
});
|
|
670
710
|
};
|
|
671
|
-
const filterProjectFiles = (rootDirectory, files, extraExtensions = [], exclude = []) => {
|
|
711
|
+
const filterProjectFiles = (rootDirectory, files, extraExtensions = [], exclude = [], include = []) => {
|
|
672
712
|
const extraSet = new Set(extraExtensions);
|
|
673
713
|
const normalizedFiles = files.map((file) => {
|
|
674
714
|
const absolutePath = path.isAbsolute(file) ? file : path.resolve(rootDirectory, file);
|
|
@@ -683,8 +723,16 @@ const filterProjectFiles = (rootDirectory, files, extraExtensions = [], exclude
|
|
|
683
723
|
if (!normalizedExcludePatterns.length) return false;
|
|
684
724
|
return micromatch.isMatch(relativePath, normalizedExcludePatterns, { dot: true });
|
|
685
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
|
+
};
|
|
686
731
|
return normalizedFiles.filter(({ absolutePath, relativePath }) => {
|
|
687
|
-
|
|
732
|
+
if (!fs.existsSync(absolutePath) || !isWithinProject(relativePath) || isExcludedPath(relativePath) || isTestFile$2(relativePath) || isBuildCacheFile(relativePath) || ignoredPaths.has(relativePath)) return false;
|
|
733
|
+
if (!isUserIncluded(relativePath)) return false;
|
|
734
|
+
if (isUserExcluded(relativePath)) return false;
|
|
735
|
+
return hasAllowedExtension(relativePath, extraSet);
|
|
688
736
|
}).map(({ absolutePath }) => absolutePath);
|
|
689
737
|
};
|
|
690
738
|
const filterExplicitFiles = (rootDirectory, files, extraExtensions = []) => {
|
|
@@ -1869,6 +1917,86 @@ const PYTHON_IMPORT_TO_PIP = {
|
|
|
1869
1917
|
redis: "redis"
|
|
1870
1918
|
};
|
|
1871
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
|
+
|
|
1872
2000
|
//#endregion
|
|
1873
2001
|
//#region src/engines/ai-slop/hallucinated-imports.ts
|
|
1874
2002
|
const JS_EXTENSIONS$2 = new Set([
|
|
@@ -2005,10 +2133,26 @@ const buildAliasMatcher = (key) => {
|
|
|
2005
2133
|
};
|
|
2006
2134
|
const collectAliasMatchersFromConfig = (configPath, matchers) => {
|
|
2007
2135
|
const opts = readJson(configPath)?.compilerOptions;
|
|
2008
|
-
if (!opts
|
|
2136
|
+
if (!opts) return;
|
|
2009
2137
|
const paths = opts.paths;
|
|
2010
|
-
if (
|
|
2011
|
-
|
|
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
|
+
}
|
|
2012
2156
|
};
|
|
2013
2157
|
const collectTsPathAliases = (rootDir) => {
|
|
2014
2158
|
const matchers = [];
|
|
@@ -2016,97 +2160,35 @@ const collectTsPathAliases = (rootDir) => {
|
|
|
2016
2160
|
for (const dir of dirs) for (const fname of TS_CONFIG_FILES) collectAliasMatchersFromConfig(path.join(dir, fname), matchers);
|
|
2017
2161
|
return matchers;
|
|
2018
2162
|
};
|
|
2019
|
-
const addPyDep = (pyDeps, name) => {
|
|
2020
|
-
const normalized = name.toLowerCase().replace(/_/g, "-");
|
|
2021
|
-
pyDeps.add(normalized);
|
|
2022
|
-
};
|
|
2023
|
-
const collectFromRequirementsTxt = (rootDir, pyDeps) => {
|
|
2024
|
-
const reqPath = path.join(rootDir, "requirements.txt");
|
|
2025
|
-
if (!fs.existsSync(reqPath)) return false;
|
|
2026
|
-
try {
|
|
2027
|
-
const content = fs.readFileSync(reqPath, "utf-8");
|
|
2028
|
-
for (const line of content.split("\n")) {
|
|
2029
|
-
const trimmed = line.trim();
|
|
2030
|
-
if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("-")) continue;
|
|
2031
|
-
const match = trimmed.match(/^([a-zA-Z0-9_\-.]+)/);
|
|
2032
|
-
if (match) addPyDep(pyDeps, match[1]);
|
|
2033
|
-
}
|
|
2034
|
-
return true;
|
|
2035
|
-
} catch {
|
|
2036
|
-
return false;
|
|
2037
|
-
}
|
|
2038
|
-
};
|
|
2039
|
-
const collectFromPyproject = (rootDir, pyDeps) => {
|
|
2040
|
-
const pyprojPath = path.join(rootDir, "pyproject.toml");
|
|
2041
|
-
if (!fs.existsSync(pyprojPath)) return false;
|
|
2042
|
-
try {
|
|
2043
|
-
const content = fs.readFileSync(pyprojPath, "utf-8");
|
|
2044
|
-
const projectNameMatch = content.match(/\[project\][\s\S]*?^\s*name\s*=\s*["']([^"']+)/m);
|
|
2045
|
-
if (projectNameMatch) addPyDep(pyDeps, projectNameMatch[1]);
|
|
2046
|
-
const poetryNameMatch = content.match(/\[tool\.poetry\][\s\S]*?^\s*name\s*=\s*["']([^"']+)/m);
|
|
2047
|
-
if (poetryNameMatch) addPyDep(pyDeps, poetryNameMatch[1]);
|
|
2048
|
-
const pep621 = content.match(/^\s*dependencies\s*=\s*\[([\s\S]*?)\]/m);
|
|
2049
|
-
if (pep621) for (const line of pep621[1].split("\n")) {
|
|
2050
|
-
const m = line.match(/["']\s*([a-zA-Z0-9_\-.]+)/);
|
|
2051
|
-
if (m) addPyDep(pyDeps, m[1]);
|
|
2052
|
-
}
|
|
2053
|
-
const poetryRe = /\[tool\.poetry(?:\.group\.[a-z]+)?\.dependencies\]([\s\S]*?)(?=\n\[|$)/g;
|
|
2054
|
-
let match = poetryRe.exec(content);
|
|
2055
|
-
while (match !== null) {
|
|
2056
|
-
for (const line of match[1].split("\n")) {
|
|
2057
|
-
const m = line.trim().match(/^([a-zA-Z0-9_\-.]+)\s*=/);
|
|
2058
|
-
if (m && m[1] !== "python") addPyDep(pyDeps, m[1]);
|
|
2059
|
-
}
|
|
2060
|
-
match = poetryRe.exec(content);
|
|
2061
|
-
}
|
|
2062
|
-
return true;
|
|
2063
|
-
} catch {
|
|
2064
|
-
return false;
|
|
2065
|
-
}
|
|
2066
|
-
};
|
|
2067
|
-
const collectFromPipfile = (rootDir, pyDeps) => {
|
|
2068
|
-
const pipfilePath = path.join(rootDir, "Pipfile");
|
|
2069
|
-
if (!fs.existsSync(pipfilePath)) return false;
|
|
2070
|
-
try {
|
|
2071
|
-
const content = fs.readFileSync(pipfilePath, "utf-8");
|
|
2072
|
-
const sectionRe = /\[(packages|dev-packages)\]([\s\S]*?)(?=\n\[|$)/g;
|
|
2073
|
-
let match = sectionRe.exec(content);
|
|
2074
|
-
while (match !== null) {
|
|
2075
|
-
for (const line of match[2].split("\n")) {
|
|
2076
|
-
const m = line.trim().match(/^([a-zA-Z0-9_\-.]+)\s*=/);
|
|
2077
|
-
if (m) addPyDep(pyDeps, m[1]);
|
|
2078
|
-
}
|
|
2079
|
-
match = sectionRe.exec(content);
|
|
2080
|
-
}
|
|
2081
|
-
return true;
|
|
2082
|
-
} catch {
|
|
2083
|
-
return false;
|
|
2084
|
-
}
|
|
2085
|
-
};
|
|
2086
2163
|
const loadManifest = (rootDir) => {
|
|
2087
2164
|
const jsDeps = /* @__PURE__ */ new Set();
|
|
2088
|
-
const pyDeps = /* @__PURE__ */ new Set();
|
|
2089
2165
|
const hasJsManifest = collectJsDeps(rootDir, jsDeps);
|
|
2090
|
-
const
|
|
2091
|
-
const hasPyproject = collectFromPyproject(rootDir, pyDeps);
|
|
2092
|
-
const hasPipfile = collectFromPipfile(rootDir, pyDeps);
|
|
2166
|
+
const { pyDeps, hasPyManifest } = collectPythonDeps(rootDir);
|
|
2093
2167
|
return {
|
|
2094
2168
|
jsDeps,
|
|
2095
2169
|
pyDeps,
|
|
2096
2170
|
hasJsManifest,
|
|
2097
|
-
hasPyManifest
|
|
2171
|
+
hasPyManifest
|
|
2098
2172
|
};
|
|
2099
2173
|
};
|
|
2100
2174
|
const isJsRelativeOrAbsolute = (spec) => spec.startsWith(".") || spec.startsWith("/") || spec.startsWith("~/");
|
|
2175
|
+
const RUNTIME_BUILTINS = new Set(["bun"]);
|
|
2101
2176
|
const isJsBuiltin = (spec) => {
|
|
2177
|
+
if (RUNTIME_BUILTINS.has(spec)) return true;
|
|
2102
2178
|
return isBuiltin(spec.startsWith("node:") ? spec.slice(5) : spec) || isBuiltin(spec);
|
|
2103
2179
|
};
|
|
2104
2180
|
const VIRTUAL_MODULE_PREFIXES = [
|
|
2105
2181
|
"astro:",
|
|
2106
2182
|
"virtual:",
|
|
2107
|
-
"bun:"
|
|
2183
|
+
"bun:",
|
|
2184
|
+
"~icons/"
|
|
2108
2185
|
];
|
|
2109
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" };
|
|
2110
2192
|
const TEMPLATE_PLACEHOLDER_RE = /\$\{/;
|
|
2111
2193
|
const isLikelyRealImportSpec = (spec) => {
|
|
2112
2194
|
if (spec.length === 0) return false;
|
|
@@ -2173,10 +2255,14 @@ const extractPyImports = (content) => {
|
|
|
2173
2255
|
}
|
|
2174
2256
|
return results;
|
|
2175
2257
|
};
|
|
2176
|
-
const checkJsImport = (
|
|
2258
|
+
const checkJsImport = (rawSpec, manifest, tsAliasMatchers) => {
|
|
2259
|
+
const spec = stripImportQuery(rawSpec);
|
|
2260
|
+
if (spec.length === 0) return null;
|
|
2177
2261
|
if (isJsRelativeOrAbsolute(spec)) return null;
|
|
2178
2262
|
if (isJsBuiltin(spec)) return null;
|
|
2179
2263
|
if (isJsVirtualModule(spec)) return null;
|
|
2264
|
+
const virtualOwner = VIRTUAL_ASSET_FILES[spec];
|
|
2265
|
+
if (virtualOwner && manifest.jsDeps.has(virtualOwner)) return null;
|
|
2180
2266
|
if (tsAliasMatchers.some((m) => m(spec))) return null;
|
|
2181
2267
|
const pkg = packageNameFromImport(spec);
|
|
2182
2268
|
if (manifest.jsDeps.has(pkg)) return null;
|
|
@@ -3520,64 +3606,88 @@ const analyzeFunctions = (content, ext) => {
|
|
|
3520
3606
|
}
|
|
3521
3607
|
return functions;
|
|
3522
3608
|
};
|
|
3523
|
-
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
|
+
};
|
|
3524
3621
|
const checkFileDiagnostics = (relativePath, content, limits) => {
|
|
3525
3622
|
const results = [];
|
|
3526
3623
|
const lineCount = content.split("\n").length;
|
|
3527
3624
|
const ext = path.extname(relativePath).toLowerCase();
|
|
3528
3625
|
if (isDataFile(content)) return results;
|
|
3529
|
-
const configuredMax = ext
|
|
3626
|
+
const configuredMax = fileLocBudget(ext, relativePath, limits.maxFileLoc);
|
|
3627
|
+
if (!Number.isFinite(configuredMax)) return results;
|
|
3530
3628
|
if (lineCount > Math.ceil(configuredMax * 1.1)) results.push({
|
|
3531
3629
|
filePath: relativePath,
|
|
3532
3630
|
engine: "code-quality",
|
|
3533
3631
|
rule: "complexity/file-too-large",
|
|
3534
3632
|
severity: "warning",
|
|
3535
|
-
message: `File
|
|
3633
|
+
message: `File too large (max: ${configuredMax})`,
|
|
3536
3634
|
help: "Consider splitting this file into smaller modules",
|
|
3537
3635
|
line: 0,
|
|
3538
3636
|
column: 0,
|
|
3539
3637
|
category: "Complexity",
|
|
3540
|
-
fixable: false
|
|
3638
|
+
fixable: false,
|
|
3639
|
+
detail: `${lineCount} lines`
|
|
3541
3640
|
});
|
|
3542
3641
|
return results;
|
|
3543
3642
|
};
|
|
3544
|
-
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) => {
|
|
3545
3651
|
const results = [];
|
|
3546
|
-
|
|
3652
|
+
const fnMax = functionLocBudget(fn, ext, limits.maxFunctionLoc);
|
|
3653
|
+
if (fn.lineCount - fn.templateLines > Math.ceil(fnMax * 1.1)) results.push({
|
|
3547
3654
|
filePath: relativePath,
|
|
3548
3655
|
engine: "code-quality",
|
|
3549
3656
|
rule: "complexity/function-too-long",
|
|
3550
3657
|
severity: "warning",
|
|
3551
|
-
message: `Function
|
|
3658
|
+
message: `Function too long (max: ${fnMax})`,
|
|
3552
3659
|
help: "Consider breaking this function into smaller pieces",
|
|
3553
3660
|
line: fn.startLine,
|
|
3554
3661
|
column: 0,
|
|
3555
3662
|
category: "Complexity",
|
|
3556
|
-
fixable: false
|
|
3663
|
+
fixable: false,
|
|
3664
|
+
detail: `${fn.name} · ${fn.lineCount} lines`
|
|
3557
3665
|
});
|
|
3558
3666
|
if (fn.maxNesting > limits.maxNesting) results.push({
|
|
3559
3667
|
filePath: relativePath,
|
|
3560
3668
|
engine: "code-quality",
|
|
3561
3669
|
rule: "complexity/deep-nesting",
|
|
3562
3670
|
severity: "warning",
|
|
3563
|
-
message: `Function
|
|
3671
|
+
message: `Function nested too deeply (max: ${limits.maxNesting})`,
|
|
3564
3672
|
help: "Consider using early returns or extracting nested logic",
|
|
3565
3673
|
line: fn.startLine,
|
|
3566
3674
|
column: 0,
|
|
3567
3675
|
category: "Complexity",
|
|
3568
|
-
fixable: false
|
|
3676
|
+
fixable: false,
|
|
3677
|
+
detail: `${fn.name} · depth ${fn.maxNesting}`
|
|
3569
3678
|
});
|
|
3570
3679
|
if (fn.paramCount > limits.maxParams) results.push({
|
|
3571
3680
|
filePath: relativePath,
|
|
3572
3681
|
engine: "code-quality",
|
|
3573
3682
|
rule: "complexity/too-many-params",
|
|
3574
3683
|
severity: "warning",
|
|
3575
|
-
message: `Function
|
|
3684
|
+
message: `Function has too many parameters (max: ${limits.maxParams})`,
|
|
3576
3685
|
help: "Consider using an options object parameter",
|
|
3577
3686
|
line: fn.startLine,
|
|
3578
3687
|
column: 0,
|
|
3579
3688
|
category: "Complexity",
|
|
3580
|
-
fixable: false
|
|
3689
|
+
fixable: false,
|
|
3690
|
+
detail: `${fn.name} · ${fn.paramCount} params`
|
|
3581
3691
|
});
|
|
3582
3692
|
return results;
|
|
3583
3693
|
};
|
|
@@ -3592,7 +3702,7 @@ const checkFileComplexity = (filePath, rootDirectory, limits) => {
|
|
|
3592
3702
|
}
|
|
3593
3703
|
const ext = path.extname(filePath).toLowerCase();
|
|
3594
3704
|
const diagnostics = checkFileDiagnostics(relativePath, content, limits);
|
|
3595
|
-
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));
|
|
3596
3706
|
return diagnostics;
|
|
3597
3707
|
};
|
|
3598
3708
|
const checkComplexity = async (context) => {
|
|
@@ -3697,17 +3807,19 @@ const findDuplicateBlocks = (content, relativePath) => {
|
|
|
3697
3807
|
});
|
|
3698
3808
|
}
|
|
3699
3809
|
return reports.map((r) => {
|
|
3810
|
+
const span = r.currentEnd - r.currentStart + 1;
|
|
3700
3811
|
return {
|
|
3701
3812
|
filePath: relativePath,
|
|
3702
3813
|
engine: "code-quality",
|
|
3703
3814
|
rule: "code-quality/duplicate-block",
|
|
3704
3815
|
severity: "warning",
|
|
3705
|
-
message:
|
|
3816
|
+
message: "Duplicate code block — extract a shared helper",
|
|
3706
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.`,
|
|
3707
3818
|
line: r.currentStart,
|
|
3708
3819
|
column: 0,
|
|
3709
3820
|
category: "Complexity",
|
|
3710
|
-
fixable: false
|
|
3821
|
+
fixable: false,
|
|
3822
|
+
detail: `${span} lines duplicate block at L${r.priorStart}`
|
|
3711
3823
|
};
|
|
3712
3824
|
});
|
|
3713
3825
|
};
|
|
@@ -4284,16 +4396,34 @@ const fixGofmt = async (rootDirectory) => {
|
|
|
4284
4396
|
if (result.exitCode !== 0) throw new Error(result.stderr || result.stdout || `gofmt exited with code ${result.exitCode}`);
|
|
4285
4397
|
};
|
|
4286
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
|
+
|
|
4287
4415
|
//#endregion
|
|
4288
4416
|
//#region src/engines/format/ruff-format.ts
|
|
4289
4417
|
const runRuffFormat = async (context) => {
|
|
4290
4418
|
const ruffBinary = resolveToolBinary("ruff");
|
|
4419
|
+
const targets = getPythonTargets(context);
|
|
4420
|
+
if (targets.length === 0) return [];
|
|
4291
4421
|
try {
|
|
4292
4422
|
const result = await runSubprocess(ruffBinary, [
|
|
4293
4423
|
"format",
|
|
4294
4424
|
"--check",
|
|
4295
4425
|
"--diff",
|
|
4296
|
-
|
|
4426
|
+
...targets
|
|
4297
4427
|
], {
|
|
4298
4428
|
cwd: context.rootDirectory,
|
|
4299
4429
|
timeout: 6e4
|
|
@@ -4309,9 +4439,9 @@ const parseRuffFormatOutput = (output, rootDir) => {
|
|
|
4309
4439
|
const filePattern = /^--- (.+)$/gm;
|
|
4310
4440
|
let match;
|
|
4311
4441
|
while ((match = filePattern.exec(output)) !== null) {
|
|
4312
|
-
const filePath = match[1]
|
|
4442
|
+
const filePath = getRuffDiagnosticPath(rootDir, match[1]);
|
|
4313
4443
|
diagnostics.push({
|
|
4314
|
-
filePath
|
|
4444
|
+
filePath,
|
|
4315
4445
|
engine: "format",
|
|
4316
4446
|
rule: "python-formatting",
|
|
4317
4447
|
severity: "warning",
|
|
@@ -4715,6 +4845,95 @@ const resolveOxlintBinary = () => {
|
|
|
4715
4845
|
return "oxlint";
|
|
4716
4846
|
}
|
|
4717
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
|
+
};
|
|
4718
4937
|
const parseRuleCode = (code) => {
|
|
4719
4938
|
if (!code) return {
|
|
4720
4939
|
plugin: "eslint",
|
|
@@ -4811,6 +5030,8 @@ const runOxlint = async (context) => {
|
|
|
4811
5030
|
framework: context.frameworks.find((f) => f !== "none"),
|
|
4812
5031
|
testFramework: detectTestFramework(context.rootDirectory)
|
|
4813
5032
|
});
|
|
5033
|
+
const ambientSources = detectAmbientSources(context.rootDirectory);
|
|
5034
|
+
sstReferencedFiles.clear();
|
|
4814
5035
|
try {
|
|
4815
5036
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
4816
5037
|
const args = [
|
|
@@ -4850,6 +5071,11 @@ const runOxlint = async (context) => {
|
|
|
4850
5071
|
fixable: false
|
|
4851
5072
|
};
|
|
4852
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;
|
|
4853
5079
|
const key = `${d.filePath}:${d.line}:${d.rule}:${d.message}`;
|
|
4854
5080
|
if (seen.has(key)) return false;
|
|
4855
5081
|
seen.add(key);
|
|
@@ -4913,18 +5139,20 @@ const fixOxlint = async (context, options = {}) => {
|
|
|
4913
5139
|
//#region src/engines/lint/ruff.ts
|
|
4914
5140
|
const runRuffLint = async (context) => {
|
|
4915
5141
|
const ruffBinary = resolveToolBinary("ruff");
|
|
5142
|
+
const targets = getPythonTargets(context);
|
|
5143
|
+
if (targets.length === 0) return [];
|
|
4916
5144
|
try {
|
|
4917
5145
|
const output = (await runSubprocess(ruffBinary, [
|
|
4918
5146
|
"check",
|
|
4919
5147
|
"--output-format=json",
|
|
4920
|
-
|
|
5148
|
+
...targets
|
|
4921
5149
|
], {
|
|
4922
5150
|
cwd: context.rootDirectory,
|
|
4923
5151
|
timeout: 6e4
|
|
4924
5152
|
})).stdout;
|
|
4925
5153
|
if (!output) return [];
|
|
4926
5154
|
return JSON.parse(output).map((d) => ({
|
|
4927
|
-
filePath:
|
|
5155
|
+
filePath: getRuffDiagnosticPath(context.rootDirectory, d.filename),
|
|
4928
5156
|
engine: "lint",
|
|
4929
5157
|
rule: `ruff/${d.code}`,
|
|
4930
5158
|
severity: d.code.startsWith("E") || d.code.startsWith("F") ? "error" : "warning",
|
|
@@ -5038,56 +5266,94 @@ const runPnpmAuditWithFallback = async (rootDir, timeout) => {
|
|
|
5038
5266
|
return [];
|
|
5039
5267
|
}
|
|
5040
5268
|
};
|
|
5269
|
+
const SEVERITY_RANK = {
|
|
5270
|
+
critical: 4,
|
|
5271
|
+
high: 3,
|
|
5272
|
+
moderate: 2,
|
|
5273
|
+
low: 1
|
|
5274
|
+
};
|
|
5041
5275
|
const toSeverity = (value) => value === "critical" || value === "high" ? "error" : "warning";
|
|
5042
|
-
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
|
+
};
|
|
5043
5336
|
const parseLegacyAdvisories = (advisories, source) => {
|
|
5044
|
-
const
|
|
5045
|
-
for (const [key, advisory] of Object.entries(advisories))
|
|
5046
|
-
|
|
5047
|
-
const severity = (advisory.severity ?? "moderate").toLowerCase();
|
|
5048
|
-
const recommendation = advisory.recommendation ?? advisory.title ?? `Run \`${defaultAuditFixCommand(source)}\` to resolve`;
|
|
5049
|
-
diagnostics.push({
|
|
5050
|
-
filePath: "package.json",
|
|
5051
|
-
engine: "security",
|
|
5052
|
-
rule: "security/vulnerable-dependency",
|
|
5053
|
-
severity: toSeverity(severity),
|
|
5054
|
-
message: `Vulnerable dependency (${source}): ${packageName} (${severity})`,
|
|
5055
|
-
help: withFixHint(recommendation),
|
|
5056
|
-
line: 0,
|
|
5057
|
-
column: 0,
|
|
5058
|
-
category: "Security",
|
|
5059
|
-
fixable: false
|
|
5060
|
-
});
|
|
5061
|
-
}
|
|
5062
|
-
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));
|
|
5063
5340
|
};
|
|
5064
5341
|
const parseModernVulnerabilities = (vulnerabilities, source) => {
|
|
5065
|
-
const
|
|
5342
|
+
const bucket = /* @__PURE__ */ new Map();
|
|
5066
5343
|
for (const [packageName, vulnerability] of Object.entries(vulnerabilities)) {
|
|
5067
5344
|
const severity = (vulnerability.severity ?? "moderate").toLowerCase();
|
|
5068
5345
|
const fixAvailable = vulnerability.fixAvailable;
|
|
5069
5346
|
const isDirect = vulnerability.isDirect === true;
|
|
5070
|
-
let recommendation =
|
|
5071
|
-
if (fixAvailable === false) recommendation = isDirect ? "
|
|
5072
|
-
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";
|
|
5073
5350
|
else if (fixAvailable && typeof fixAvailable === "object" && "name" in fixAvailable && "version" in fixAvailable) {
|
|
5074
5351
|
const target = fixAvailable;
|
|
5075
|
-
if (target.name && target.version) recommendation = `
|
|
5352
|
+
if (target.name && target.version) recommendation = `upgrade to ${target.name}@${target.version}`;
|
|
5076
5353
|
}
|
|
5077
|
-
|
|
5078
|
-
filePath: "package.json",
|
|
5079
|
-
engine: "security",
|
|
5080
|
-
rule: "security/vulnerable-dependency",
|
|
5081
|
-
severity: toSeverity(severity),
|
|
5082
|
-
message: `Vulnerable dependency (${source}): ${packageName} (${severity})`,
|
|
5083
|
-
help: withFixHint(recommendation),
|
|
5084
|
-
line: 0,
|
|
5085
|
-
column: 0,
|
|
5086
|
-
category: "Security",
|
|
5087
|
-
fixable: false
|
|
5088
|
-
});
|
|
5354
|
+
upsertVuln(bucket, packageName, severity, recommendation);
|
|
5089
5355
|
}
|
|
5090
|
-
return
|
|
5356
|
+
return [...bucket.values()].map((agg) => aggregateToDiagnostic(agg, source));
|
|
5091
5357
|
};
|
|
5092
5358
|
const parseJsAudit = (output, source) => {
|
|
5093
5359
|
if (!output) return [];
|
|
@@ -6266,8 +6532,53 @@ const wrapHelpText = (text, maxWidth, indent) => {
|
|
|
6266
6532
|
};
|
|
6267
6533
|
const terminalWidth = () => {
|
|
6268
6534
|
const raw = process.stdout.columns;
|
|
6269
|
-
if (typeof raw !== "number" || raw <= 0) return
|
|
6270
|
-
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("");
|
|
6271
6582
|
};
|
|
6272
6583
|
const renderDiagnostics = (diagnostics, verbose) => {
|
|
6273
6584
|
const lines = [];
|
|
@@ -6276,29 +6587,23 @@ const renderDiagnostics = (diagnostics, verbose) => {
|
|
|
6276
6587
|
const label = getEngineLabel(engine);
|
|
6277
6588
|
lines.push(` ${style(theme, "bold", `${symbols.engineActive} ${label}`)}`);
|
|
6278
6589
|
const sorted = [...groupBy(engineDiags, (d) => `${d.rule}:${d.message}`).entries()].sort(([, a], [, b]) => {
|
|
6279
|
-
|
|
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;
|
|
6280
6594
|
});
|
|
6281
|
-
|
|
6595
|
+
const maxRules = verbose ? Infinity : 40;
|
|
6596
|
+
for (const [, ruleDiags] of sorted.slice(0, maxRules)) {
|
|
6282
6597
|
const first = ruleDiags[0];
|
|
6283
|
-
|
|
6284
|
-
|
|
6285
|
-
const status = colorBySeverity(level, first.severity);
|
|
6286
|
-
const fixableTag = first.fixable ? ` ${style(theme, "muted", "[auto]")}` : "";
|
|
6287
|
-
const fixableWidth = first.fixable ? 7 : 0;
|
|
6288
|
-
const badgePrefix = ` [${status}]${fixableTag} `;
|
|
6289
|
-
const badgePrefixWidth = 5 + level.length + 1 + fixableWidth + 1;
|
|
6290
|
-
const wrappedMsg = wrapText(`${first.message}${count}`, terminalWidth(), badgePrefixWidth, " ");
|
|
6291
|
-
lines.push(`${badgePrefix}${wrappedMsg[0]}`);
|
|
6292
|
-
for (let i = 1; i < wrappedMsg.length; i++) lines.push(wrappedMsg[i]);
|
|
6293
|
-
const locations = verbose ? ruleDiags : ruleDiags.slice(0, 3);
|
|
6294
|
-
for (const diagnostic of locations) lines.push(style(theme, "muted", ` ${toLocationLabel(diagnostic)}`));
|
|
6295
|
-
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);
|
|
6296
6600
|
if (first.help) {
|
|
6297
6601
|
const wrapped = wrapHelpText(first.help, terminalWidth(), " ");
|
|
6298
6602
|
for (const line of wrapped) lines.push(style(theme, "muted", line));
|
|
6299
6603
|
}
|
|
6300
6604
|
lines.push("");
|
|
6301
6605
|
}
|
|
6606
|
+
if (sorted.length > maxRules) renderHiddenFooter(sorted, maxRules, lines);
|
|
6302
6607
|
}
|
|
6303
6608
|
return `${lines.join("\n")}\n`;
|
|
6304
6609
|
};
|
|
@@ -6436,6 +6741,81 @@ var LiveGrid = class {
|
|
|
6436
6741
|
}
|
|
6437
6742
|
};
|
|
6438
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
|
+
|
|
6439
6819
|
//#endregion
|
|
6440
6820
|
//#region src/ui/summary.ts
|
|
6441
6821
|
const elapsed = (ms) => ms < 1e3 ? `${Math.round(ms)}ms` : `${(ms / 1e3).toFixed(1)}s`;
|
|
@@ -6462,6 +6842,34 @@ const renderSummary = (input, deps = {}) => {
|
|
|
6462
6842
|
` ${style(t, "muted", `${input.files} files`)} ${sep} ${style(t, "muted", `${input.engines} engines`)} ${sep} ${style(t, "muted", elapsed(input.elapsedMs))}`,
|
|
6463
6843
|
""
|
|
6464
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
|
+
}
|
|
6465
6873
|
if (input.nextSteps.length > 0) {
|
|
6466
6874
|
for (const step of input.nextSteps) {
|
|
6467
6875
|
const glyph = step.emphasis === "primary" ? s.hint : s.bullet;
|
|
@@ -6534,6 +6942,39 @@ const getStagedFiles = (cwd) => {
|
|
|
6534
6942
|
//#region src/commands/scan.ts
|
|
6535
6943
|
const shouldUseSpinner = () => Boolean(process.stderr.isTTY) && process.env.CI !== "true" && process.env.CI !== "1";
|
|
6536
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
|
+
};
|
|
6537
6978
|
const buildScanRender = (input) => {
|
|
6538
6979
|
const deps = {
|
|
6539
6980
|
theme: createTheme(),
|
|
@@ -6583,6 +7024,7 @@ const buildScanRender = (input) => {
|
|
|
6583
7024
|
engines: input.results.length,
|
|
6584
7025
|
elapsedMs: input.elapsedMs,
|
|
6585
7026
|
nextSteps,
|
|
7027
|
+
breakdown: computeBreakdown(input.diagnostics),
|
|
6586
7028
|
thresholds: input.thresholds
|
|
6587
7029
|
}, deps)}`;
|
|
6588
7030
|
};
|
|
@@ -6695,7 +7137,7 @@ const runScanBody = async (resolvedDir, config, options, projectInfo) => {
|
|
|
6695
7137
|
engineTimings
|
|
6696
7138
|
};
|
|
6697
7139
|
if (options.json) {
|
|
6698
|
-
const { buildJsonOutput } = await import("./json-
|
|
7140
|
+
const { buildJsonOutput } = await import("./json-DZHn6AE3.js");
|
|
6699
7141
|
const jsonOut = buildJsonOutput(results, scoreResult, projectInfo.sourceFileCount, elapsedMs);
|
|
6700
7142
|
console.log(JSON.stringify(jsonOut, null, 2));
|
|
6701
7143
|
return completion;
|
|
@@ -8306,10 +8748,18 @@ const promptForConfigChoices = async () => {
|
|
|
8306
8748
|
return {
|
|
8307
8749
|
engines: enginesSelection,
|
|
8308
8750
|
failBelow: Number(failBelowRaw),
|
|
8751
|
+
typecheck: DEFAULT_CONFIG.lint.typecheck,
|
|
8309
8752
|
telemetryEnabled: telemetryChoice === "enabled",
|
|
8310
8753
|
writeGithubWorkflow: workflowChoice === "yes"
|
|
8311
8754
|
};
|
|
8312
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
|
+
});
|
|
8313
8763
|
const writeAislopConfig = (configDir, configPath, choices) => {
|
|
8314
8764
|
const selected = new Set(choices.engines);
|
|
8315
8765
|
const engines = {
|
|
@@ -8324,6 +8774,7 @@ const writeAislopConfig = (configDir, configPath, choices) => {
|
|
|
8324
8774
|
version: DEFAULT_CONFIG.version,
|
|
8325
8775
|
engines,
|
|
8326
8776
|
quality: { ...DEFAULT_CONFIG.quality },
|
|
8777
|
+
lint: { typecheck: choices.typecheck },
|
|
8327
8778
|
security: { ...DEFAULT_CONFIG.security },
|
|
8328
8779
|
scoring: {
|
|
8329
8780
|
weights: { ...DEFAULT_CONFIG.scoring.weights },
|
|
@@ -8376,7 +8827,7 @@ const initCommand = async (directory, options = {}) => {
|
|
|
8376
8827
|
return;
|
|
8377
8828
|
}
|
|
8378
8829
|
}
|
|
8379
|
-
const choices = await promptForConfigChoices();
|
|
8830
|
+
const choices = options.strict ? strictChoices() : await promptForConfigChoices();
|
|
8380
8831
|
if (!choices) return;
|
|
8381
8832
|
writeAislopConfig(configDir, configPath, choices);
|
|
8382
8833
|
const steps = [{
|