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 cause a noticeable drop, but the score does not collapse to zero from minor findings.
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 = (paramStr) => {
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: /\.innerHTML\s*=/g,
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.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 calculateScore = (diagnostics, weights, thresholds) => {
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 score = Math.max(0, Math.round(PERFECT_SCORE$1 - PERFECT_SCORE$1 * Math.log1p(deductions) / Math.log1p(PERFECT_SCORE$1 + deductions)));
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.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-Bw_OOj3G.js";
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 = (paramStr) => {
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: /\.innerHTML\s*=/g,
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 calculateScore = (diagnostics, weights, thresholds) => {
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 score = Math.max(0, Math.round(PERFECT_SCORE$1 - PERFECT_SCORE$1 * Math.log1p(deductions) / Math.log1p(PERFECT_SCORE$1 + deductions)));
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-SHDiefXX.js");
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 };
@@ -1,4 +1,4 @@
1
- import { r as APP_VERSION, t as ENGINE_INFO } from "./engine-info-Bw_OOj3G.js";
1
+ import { r as APP_VERSION, t as ENGINE_INFO } from "./engine-info-Bi8pE12U.js";
2
2
 
3
3
  //#region src/output/json.ts
4
4
  const buildJsonOutput = (results, scoreResult, fileCount, elapsedMs) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aislop",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Stop AI slop from shipping. A unified code quality CLI that catches the lazy patterns AI coding tools leave behind.",
5
5
  "type": "module",
6
6
  "bin": {