aislop 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -217,8 +217,6 @@ Custom import and path rules defined in `.aislop/rules.yml`.
|
|
|
217
217
|
|
|
218
218
|
---
|
|
219
219
|
|
|
220
|
-
## Scoring
|
|
221
|
-
|
|
222
220
|
Every diagnostic contributes a weighted penalty:
|
|
223
221
|
|
|
224
222
|
| Severity | Penalty |
|
|
@@ -227,7 +225,7 @@ Every diagnostic contributes a weighted penalty:
|
|
|
227
225
|
| Warning | 1.0 |
|
|
228
226
|
| Info | 0.25 |
|
|
229
227
|
|
|
230
|
-
Penalties are multiplied by engine weight (configurable, security defaults to 2x). The final score uses logarithmic scaling so a few issues
|
|
228
|
+
Penalties are multiplied by engine weight (configurable, security defaults to 2x). The final score uses logarithmic scaling with issue-density normalization (relative to source file count), so a few issues still matter but a single finding in an otherwise clean project remains proportional.
|
|
231
229
|
|
|
232
230
|
| Score | Label |
|
|
233
231
|
|---|---|
|
|
@@ -356,4 +354,4 @@ See [SECURITY.md](SECURITY.md) for reporting vulnerabilities.
|
|
|
356
354
|
|
|
357
355
|
## License
|
|
358
356
|
|
|
359
|
-
[MIT](LICENSE) -- see the [LICENSE](LICENSE) file.
|
|
357
|
+
[MIT](LICENSE) -- see the [LICENSE](LICENSE) file.
|
package/dist/cli.js
CHANGED
|
@@ -46,7 +46,8 @@ const DEFAULT_CONFIG = {
|
|
|
46
46
|
thresholds: {
|
|
47
47
|
good: 75,
|
|
48
48
|
ok: 50
|
|
49
|
-
}
|
|
49
|
+
},
|
|
50
|
+
smoothing: 10
|
|
50
51
|
},
|
|
51
52
|
ci: {
|
|
52
53
|
failBelow: 0,
|
|
@@ -146,7 +147,8 @@ const ScoringSchema = z.object({
|
|
|
146
147
|
thresholds: ThresholdsSchema.default(() => ({
|
|
147
148
|
good: 75,
|
|
148
149
|
ok: 50
|
|
149
|
-
}))
|
|
150
|
+
})),
|
|
151
|
+
smoothing: z.number().nonnegative().default(10)
|
|
150
152
|
});
|
|
151
153
|
const CiSchema = z.object({
|
|
152
154
|
failBelow: z.number().default(0),
|
|
@@ -178,7 +180,8 @@ const AislopConfigSchema = z.object({
|
|
|
178
180
|
thresholds: {
|
|
179
181
|
good: 75,
|
|
180
182
|
ok: 50
|
|
181
|
-
}
|
|
183
|
+
},
|
|
184
|
+
smoothing: 10
|
|
182
185
|
})),
|
|
183
186
|
ci: CiSchema.default(() => ({
|
|
184
187
|
failBelow: 0,
|
|
@@ -1279,10 +1282,7 @@ const FUNCTION_PATTERNS = [
|
|
|
1279
1282
|
]
|
|
1280
1283
|
}
|
|
1281
1284
|
];
|
|
1282
|
-
const countParams = (
|
|
1283
|
-
if (!paramStr.trim()) return 0;
|
|
1284
|
-
return paramStr.split(",").length;
|
|
1285
|
-
};
|
|
1285
|
+
const countParams = (p) => p.trim() ? p.split(",").length : 0;
|
|
1286
1286
|
const matchFunctionOnLine = (line, ext) => {
|
|
1287
1287
|
for (let i = 0; i < FUNCTION_PATTERNS.length; i++) {
|
|
1288
1288
|
const pattern = FUNCTION_PATTERNS[i];
|
|
@@ -2760,7 +2760,7 @@ const RISKY_PATTERNS = [
|
|
|
2760
2760
|
help: "Avoid dynamic code execution — refactor to use static code paths"
|
|
2761
2761
|
},
|
|
2762
2762
|
{
|
|
2763
|
-
pattern:
|
|
2763
|
+
pattern: new RegExp(`\\.innerHTML\\s*=`, "g"),
|
|
2764
2764
|
extensions: [
|
|
2765
2765
|
".ts",
|
|
2766
2766
|
".tsx",
|
|
@@ -2857,6 +2857,10 @@ const detectRiskyConstructs = async (context) => {
|
|
|
2857
2857
|
let match;
|
|
2858
2858
|
while ((match = regex.exec(content)) !== null) {
|
|
2859
2859
|
const line = content.slice(0, match.index).split("\n").length;
|
|
2860
|
+
if (name === "innerhtml") {
|
|
2861
|
+
const beforeMatch = content.slice(Math.max(0, match.index - 200), match.index);
|
|
2862
|
+
if (/(?:template|tmpl|tpl)$/i.test(beforeMatch.trimEnd()) || /createElement\s*\(\s*['"]template['"]\s*\)$/.test(beforeMatch.trimEnd())) continue;
|
|
2863
|
+
}
|
|
2860
2864
|
if (name === "sql-injection") {
|
|
2861
2865
|
const afterMatch = content.slice(match.index + match[0].length, match.index + match[0].length + 100);
|
|
2862
2866
|
if (/^(?:\w+\.join\s*\(|[A-Z_]+\}|tableName\}|table\})/.test(afterMatch)) continue;
|
|
@@ -3114,7 +3118,7 @@ const logger = {
|
|
|
3114
3118
|
* Application version — injected at build time by tsdown from package.json.
|
|
3115
3119
|
* The fallback should always match the "version" field in package.json.
|
|
3116
3120
|
*/
|
|
3117
|
-
const APP_VERSION = "0.1.
|
|
3121
|
+
const APP_VERSION = "0.1.3";
|
|
3118
3122
|
|
|
3119
3123
|
//#endregion
|
|
3120
3124
|
//#region src/output/layout.ts
|
|
@@ -3340,7 +3344,12 @@ var ScanProgressRenderer = class {
|
|
|
3340
3344
|
//#endregion
|
|
3341
3345
|
//#region src/scoring/index.ts
|
|
3342
3346
|
const PERFECT_SCORE$1 = 100;
|
|
3343
|
-
const
|
|
3347
|
+
const getEffectiveFileCount = (diagnostics, sourceFileCount) => {
|
|
3348
|
+
if (typeof sourceFileCount === "number" && sourceFileCount > 0) return sourceFileCount;
|
|
3349
|
+
const filesWithDiagnostics = new Set(diagnostics.map((d) => d.filePath)).size;
|
|
3350
|
+
return Math.max(1, filesWithDiagnostics);
|
|
3351
|
+
};
|
|
3352
|
+
const calculateScore = (diagnostics, weights, thresholds, sourceFileCount, smoothing) => {
|
|
3344
3353
|
if (diagnostics.length === 0) return {
|
|
3345
3354
|
score: PERFECT_SCORE$1,
|
|
3346
3355
|
label: "Healthy"
|
|
@@ -3351,7 +3360,11 @@ const calculateScore = (diagnostics, weights, thresholds) => {
|
|
|
3351
3360
|
const severityPenalty = d.severity === "error" ? 3 : d.severity === "warning" ? 1 : .25;
|
|
3352
3361
|
deductions += severityPenalty * engineWeight;
|
|
3353
3362
|
}
|
|
3354
|
-
const
|
|
3363
|
+
const effectiveFileCount = getEffectiveFileCount(diagnostics, sourceFileCount);
|
|
3364
|
+
const smoothingConstant = typeof smoothing === "number" ? smoothing : 10;
|
|
3365
|
+
const issueDensity = Math.min(1, diagnostics.length / (effectiveFileCount + smoothingConstant));
|
|
3366
|
+
const scaledDeductions = deductions * Math.sqrt(issueDensity);
|
|
3367
|
+
const score = Math.max(0, Math.round(PERFECT_SCORE$1 - PERFECT_SCORE$1 * Math.log1p(scaledDeductions) / Math.log1p(PERFECT_SCORE$1 + scaledDeductions)));
|
|
3355
3368
|
return {
|
|
3356
3369
|
score,
|
|
3357
3370
|
label: score >= thresholds.good ? "Healthy" : score >= thresholds.ok ? "Needs Work" : "Critical"
|
|
@@ -3767,8 +3780,8 @@ const scanCommand = async (directory, config, options) => {
|
|
|
3767
3780
|
progressRenderer?.stop();
|
|
3768
3781
|
const allDiagnostics = results.flatMap((r) => r.diagnostics);
|
|
3769
3782
|
const elapsedMs = performance.now() - startTime;
|
|
3770
|
-
const scoreResult = calculateScore(allDiagnostics, config.scoring.weights, config.scoring.thresholds);
|
|
3771
|
-
const exitCode = scoreResult.score < config.ci.failBelow ? 1 : 0;
|
|
3783
|
+
const scoreResult = calculateScore(allDiagnostics, config.scoring.weights, config.scoring.thresholds, projectInfo.sourceFileCount, config.scoring.smoothing);
|
|
3784
|
+
const exitCode = allDiagnostics.some((d) => d.severity === "error") || scoreResult.score < config.ci.failBelow ? 1 : 0;
|
|
3772
3785
|
if (!isTelemetryDisabled(config.telemetry?.enabled)) {
|
|
3773
3786
|
const engineIssues = {};
|
|
3774
3787
|
const engineTimings = {};
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Application version — injected at build time by tsdown from package.json.
|
|
4
4
|
* The fallback should always match the "version" field in package.json.
|
|
5
5
|
*/
|
|
6
|
-
const APP_VERSION = "0.1.
|
|
6
|
+
const APP_VERSION = "0.1.3";
|
|
7
7
|
|
|
8
8
|
//#endregion
|
|
9
9
|
//#region src/output/engine-info.ts
|
package/dist/index.d.ts
CHANGED
|
@@ -30,6 +30,7 @@ declare const AislopConfigSchema: z.ZodObject<{
|
|
|
30
30
|
good: z.ZodDefault<z.ZodNumber>;
|
|
31
31
|
ok: z.ZodDefault<z.ZodNumber>;
|
|
32
32
|
}, z.core.$strip>>;
|
|
33
|
+
smoothing: z.ZodDefault<z.ZodNumber>;
|
|
33
34
|
}, z.core.$strip>>;
|
|
34
35
|
ci: z.ZodDefault<z.ZodObject<{
|
|
35
36
|
failBelow: z.ZodDefault<z.ZodNumber>;
|
|
@@ -114,6 +115,6 @@ interface ScoreResult {
|
|
|
114
115
|
declare const calculateScore: (diagnostics: Diagnostic[], weights: Record<string, number>, thresholds: {
|
|
115
116
|
good: number;
|
|
116
117
|
ok: number;
|
|
117
|
-
}) => ScoreResult;
|
|
118
|
+
}, sourceFileCount?: number, smoothing?: number) => ScoreResult;
|
|
118
119
|
//#endregion
|
|
119
120
|
export { type AislopConfig, type Diagnostic, type EngineName, type EngineResult, type Framework, type Language, type ProjectInfo, type ScoreResult, type Severity, calculateScore, discoverProject, doctorCommand, fixCommand, initCommand, loadConfig, scanCommand };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as getEngineLabel, r as APP_VERSION, t as ENGINE_INFO } from "./engine-info-
|
|
1
|
+
import { n as getEngineLabel, r as APP_VERSION, t as ENGINE_INFO } from "./engine-info-Bi8pE12U.js";
|
|
2
2
|
import { n as runSubprocess, t as isToolInstalled } from "./subprocess-99puEEGl.js";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import fs from "node:fs";
|
|
@@ -1273,7 +1273,8 @@ const DEFAULT_CONFIG = {
|
|
|
1273
1273
|
thresholds: {
|
|
1274
1274
|
good: 75,
|
|
1275
1275
|
ok: 50
|
|
1276
|
-
}
|
|
1276
|
+
},
|
|
1277
|
+
smoothing: 10
|
|
1277
1278
|
},
|
|
1278
1279
|
ci: {
|
|
1279
1280
|
failBelow: 0,
|
|
@@ -1373,7 +1374,8 @@ const ScoringSchema = z.object({
|
|
|
1373
1374
|
thresholds: ThresholdsSchema.default(() => ({
|
|
1374
1375
|
good: 75,
|
|
1375
1376
|
ok: 50
|
|
1376
|
-
}))
|
|
1377
|
+
})),
|
|
1378
|
+
smoothing: z.number().nonnegative().default(10)
|
|
1377
1379
|
});
|
|
1378
1380
|
const CiSchema = z.object({
|
|
1379
1381
|
failBelow: z.number().default(0),
|
|
@@ -1405,7 +1407,8 @@ const AislopConfigSchema = z.object({
|
|
|
1405
1407
|
thresholds: {
|
|
1406
1408
|
good: 75,
|
|
1407
1409
|
ok: 50
|
|
1408
|
-
}
|
|
1410
|
+
},
|
|
1411
|
+
smoothing: 10
|
|
1409
1412
|
})),
|
|
1410
1413
|
ci: CiSchema.default(() => ({
|
|
1411
1414
|
failBelow: 0,
|
|
@@ -2392,10 +2395,7 @@ const FUNCTION_PATTERNS = [
|
|
|
2392
2395
|
]
|
|
2393
2396
|
}
|
|
2394
2397
|
];
|
|
2395
|
-
const countParams = (
|
|
2396
|
-
if (!paramStr.trim()) return 0;
|
|
2397
|
-
return paramStr.split(",").length;
|
|
2398
|
-
};
|
|
2398
|
+
const countParams = (p) => p.trim() ? p.split(",").length : 0;
|
|
2399
2399
|
const matchFunctionOnLine = (line, ext) => {
|
|
2400
2400
|
for (let i = 0; i < FUNCTION_PATTERNS.length; i++) {
|
|
2401
2401
|
const pattern = FUNCTION_PATTERNS[i];
|
|
@@ -3358,7 +3358,7 @@ const RISKY_PATTERNS = [
|
|
|
3358
3358
|
help: "Avoid dynamic code execution — refactor to use static code paths"
|
|
3359
3359
|
},
|
|
3360
3360
|
{
|
|
3361
|
-
pattern:
|
|
3361
|
+
pattern: new RegExp(`\\.innerHTML\\s*=`, "g"),
|
|
3362
3362
|
extensions: [
|
|
3363
3363
|
".ts",
|
|
3364
3364
|
".tsx",
|
|
@@ -3455,6 +3455,10 @@ const detectRiskyConstructs = async (context) => {
|
|
|
3455
3455
|
let match;
|
|
3456
3456
|
while ((match = regex.exec(content)) !== null) {
|
|
3457
3457
|
const line = content.slice(0, match.index).split("\n").length;
|
|
3458
|
+
if (name === "innerhtml") {
|
|
3459
|
+
const beforeMatch = content.slice(Math.max(0, match.index - 200), match.index);
|
|
3460
|
+
if (/(?:template|tmpl|tpl)$/i.test(beforeMatch.trimEnd()) || /createElement\s*\(\s*['"]template['"]\s*\)$/.test(beforeMatch.trimEnd())) continue;
|
|
3461
|
+
}
|
|
3458
3462
|
if (name === "sql-injection") {
|
|
3459
3463
|
const afterMatch = content.slice(match.index + match[0].length, match.index + match[0].length + 100);
|
|
3460
3464
|
if (/^(?:\w+\.join\s*\(|[A-Z_]+\}|tableName\}|table\})/.test(afterMatch)) continue;
|
|
@@ -3772,7 +3776,12 @@ var ScanProgressRenderer = class {
|
|
|
3772
3776
|
//#endregion
|
|
3773
3777
|
//#region src/scoring/index.ts
|
|
3774
3778
|
const PERFECT_SCORE$1 = 100;
|
|
3775
|
-
const
|
|
3779
|
+
const getEffectiveFileCount = (diagnostics, sourceFileCount) => {
|
|
3780
|
+
if (typeof sourceFileCount === "number" && sourceFileCount > 0) return sourceFileCount;
|
|
3781
|
+
const filesWithDiagnostics = new Set(diagnostics.map((d) => d.filePath)).size;
|
|
3782
|
+
return Math.max(1, filesWithDiagnostics);
|
|
3783
|
+
};
|
|
3784
|
+
const calculateScore = (diagnostics, weights, thresholds, sourceFileCount, smoothing) => {
|
|
3776
3785
|
if (diagnostics.length === 0) return {
|
|
3777
3786
|
score: PERFECT_SCORE$1,
|
|
3778
3787
|
label: "Healthy"
|
|
@@ -3783,7 +3792,11 @@ const calculateScore = (diagnostics, weights, thresholds) => {
|
|
|
3783
3792
|
const severityPenalty = d.severity === "error" ? 3 : d.severity === "warning" ? 1 : .25;
|
|
3784
3793
|
deductions += severityPenalty * engineWeight;
|
|
3785
3794
|
}
|
|
3786
|
-
const
|
|
3795
|
+
const effectiveFileCount = getEffectiveFileCount(diagnostics, sourceFileCount);
|
|
3796
|
+
const smoothingConstant = typeof smoothing === "number" ? smoothing : 10;
|
|
3797
|
+
const issueDensity = Math.min(1, diagnostics.length / (effectiveFileCount + smoothingConstant));
|
|
3798
|
+
const scaledDeductions = deductions * Math.sqrt(issueDensity);
|
|
3799
|
+
const score = Math.max(0, Math.round(PERFECT_SCORE$1 - PERFECT_SCORE$1 * Math.log1p(scaledDeductions) / Math.log1p(PERFECT_SCORE$1 + scaledDeductions)));
|
|
3787
3800
|
return {
|
|
3788
3801
|
score,
|
|
3789
3802
|
label: score >= thresholds.good ? "Healthy" : score >= thresholds.ok ? "Needs Work" : "Critical"
|
|
@@ -3967,8 +3980,8 @@ const scanCommand = async (directory, config, options) => {
|
|
|
3967
3980
|
progressRenderer?.stop();
|
|
3968
3981
|
const allDiagnostics = results.flatMap((r) => r.diagnostics);
|
|
3969
3982
|
const elapsedMs = performance.now() - startTime;
|
|
3970
|
-
const scoreResult = calculateScore(allDiagnostics, config.scoring.weights, config.scoring.thresholds);
|
|
3971
|
-
const exitCode = scoreResult.score < config.ci.failBelow ? 1 : 0;
|
|
3983
|
+
const scoreResult = calculateScore(allDiagnostics, config.scoring.weights, config.scoring.thresholds, projectInfo.sourceFileCount, config.scoring.smoothing);
|
|
3984
|
+
const exitCode = allDiagnostics.some((d) => d.severity === "error") || scoreResult.score < config.ci.failBelow ? 1 : 0;
|
|
3972
3985
|
if (!isTelemetryDisabled(config.telemetry?.enabled)) {
|
|
3973
3986
|
const engineIssues = {};
|
|
3974
3987
|
const engineTimings = {};
|
|
@@ -3987,7 +4000,7 @@ const scanCommand = async (directory, config, options) => {
|
|
|
3987
4000
|
});
|
|
3988
4001
|
}
|
|
3989
4002
|
if (options.json) {
|
|
3990
|
-
const { buildJsonOutput } = await import("./json-
|
|
4003
|
+
const { buildJsonOutput } = await import("./json-66-1kHeg.js");
|
|
3991
4004
|
const jsonOut = buildJsonOutput(results, scoreResult, projectInfo.sourceFileCount, elapsedMs);
|
|
3992
4005
|
console.log(JSON.stringify(jsonOut, null, 2));
|
|
3993
4006
|
return { exitCode };
|