slopbrick 0.24.0 → 0.25.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/dist/index.js CHANGED
@@ -19,7 +19,7 @@ var VERSION;
19
19
  var init_header = __esm({
20
20
  "src/types/_header.ts"() {
21
21
  "use strict";
22
- VERSION = "0.24.0";
22
+ VERSION = "0.25.1";
23
23
  }
24
24
  });
25
25
 
@@ -309,6 +309,43 @@ var init_defaults = __esm({
309
309
  */
310
310
  testIntelligence: {
311
311
  missingEdgeCase: false
312
+ },
313
+ /**
314
+ * v0.25.0: self-scan exclude paths. Defaults cover the three paths
315
+ * that are always false positives when scanning the slopbrick repo
316
+ * itself:
317
+ *
318
+ * - `src/rules/**` — rule definitions contain example patterns
319
+ * the rules themselves detect (self-fire). E.g. a
320
+ * `security/sql-construction` rule's source file has a SQL
321
+ * concat string literal that fires its own regex.
322
+ * - `tests/fixtures/**` — test fixtures contain intentional bad
323
+ * code that the rules must fire on to be useful (each fixture
324
+ * is a positive test case). Scanning them in a self-scan
325
+ * produces ~70 false-positive "issues" that are really just
326
+ * test data.
327
+ * - `tests/rules/**` — rule test files contain expected-issue
328
+ * assertions, also meta-code.
329
+ *
330
+ * Three patterns (broadened in v0.25.1 from the v0.25.0 narrow
331
+ * set, which missed `tests/engine/**`, `tests/cli/**`, and
332
+ * `snippet/**`), ~80 issues removed per self-scan. Combined
333
+ * with the v0.25.0 graded security cap, this restores the
334
+ * v9 plan's "security ≥ 80" criterion (unachievable in
335
+ * v0.24.0 due to 90 self-scan FPs collapsing the score to 0).
336
+ *
337
+ * Set `selfScan: { excludePaths: [] }` in `slopbrick.config.mjs`
338
+ * to opt out and scan every file (legacy behavior).
339
+ */
340
+ selfScan: {
341
+ excludePaths: [
342
+ "**/src/rules/**",
343
+ // rule definitions are meta-code
344
+ "**/snippet/**",
345
+ // RULE_HINTS examples are intentional bad code
346
+ "**/tests/**"
347
+ // all test files (fixtures + unit + integration)
348
+ ]
312
349
  }
313
350
  };
314
351
  }
@@ -36365,6 +36402,7 @@ function parseSource(source, filePath) {
36365
36402
  case "py":
36366
36403
  case "go":
36367
36404
  case "rs":
36405
+ case "java":
36368
36406
  return parseBlankModule(source);
36369
36407
  default:
36370
36408
  return parseWithSwc(source, filePath);
@@ -49856,13 +49894,13 @@ var init_signal_strength = __esm({
49856
49894
  defaultOff: true
49857
49895
  },
49858
49896
  "java/arraylist-vs-linkedlist": {
49859
- recall: 0,
49860
- fpRate: 0,
49861
- ratio: 1,
49862
- precision: 0,
49863
- lastCalibratedAt: "2026-07-01T00:00:00Z",
49897
+ recall: 35e-4,
49898
+ fpRate: 94e-4,
49899
+ ratio: 0.37,
49900
+ precision: 0.0446,
49901
+ lastCalibratedAt: "2026-07-02T19:34:00Z",
49864
49902
  verdict: "DORMANT",
49865
- _calibrationNote: "v0.20: new rule (new LinkedList<>() \u2014 use ArrayList (Effective Java Item 28)). Not yet calibrated. Scheduled for v9 Java corpus calibration.",
49903
+ _calibrationNote: "v9 Java corpus calibration (v0.25.0, 2026-07-02): 81891 neg + 10305 pos Java files = 92196 total. v9 TP=36, FP=771, P=4.46%, FPR=0.94%, recall=0.35%, ratio=0.37, lift=P/FPR=4.74. Rule fires more on legacy enterprise Java (neg) than on AI/ML-integration Java (pos) because modern Java idiom defaults to ArrayList. v0.20: new rule (new LinkedList<>() \u2014 use ArrayList, Effective Java Item 28).",
49866
49904
  aiSpecific: true,
49867
49905
  _v7Verdict: "DORMANT",
49868
49906
  _v7Lift: 1,
@@ -49871,16 +49909,21 @@ var init_signal_strength = __esm({
49871
49909
  _v7Precision: 0,
49872
49910
  _v8Verdict: "DORMANT",
49873
49911
  _v8Lift: 1,
49912
+ _v9Verdict: "DORMANT",
49913
+ _v9Lift: 4.74,
49914
+ _v9Recall: 35e-4,
49915
+ _v9FpRate: 94e-4,
49916
+ _v9Precision: 0.0446,
49874
49917
  defaultOff: true
49875
49918
  },
49876
49919
  "java/empty-catch-block": {
49877
- recall: 0,
49878
- fpRate: 0,
49879
- ratio: 1,
49880
- precision: 0,
49881
- lastCalibratedAt: "2026-07-01T00:00:00Z",
49920
+ recall: 8e-3,
49921
+ fpRate: 0.1118,
49922
+ ratio: 0.07,
49923
+ precision: 89e-4,
49924
+ lastCalibratedAt: "2026-07-02T19:34:00Z",
49882
49925
  verdict: "DORMANT",
49883
- _calibrationNote: "v0.20: new rule (Empty catch block \u2014 silently swallows exceptions). Not yet calibrated. Scheduled for v9 Java corpus calibration.",
49926
+ _calibrationNote: "v9 Java corpus calibration (v0.25.0, 2026-07-02): 81891 neg + 10305 pos Java files. v9 TP=82, FP=9159, P=0.89%, FPR=11.18%, recall=0.80%, ratio=0.07, lift=P/FPR=0.08. Strongly INVERTED: empty catch blocks are 14x more common in legacy enterprise Java than in AI/Java-modern pos repos. Heuristic likely needs to be tightened (e.g., require at least a comment) before it can serve as a meaningful discriminator. v0.20: new rule.",
49884
49927
  aiSpecific: true,
49885
49928
  _v7Verdict: "DORMANT",
49886
49929
  _v7Lift: 1,
@@ -49889,16 +49932,21 @@ var init_signal_strength = __esm({
49889
49932
  _v7Precision: 0,
49890
49933
  _v8Verdict: "DORMANT",
49891
49934
  _v8Lift: 1,
49935
+ _v9Verdict: "DORMANT",
49936
+ _v9Lift: 0.08,
49937
+ _v9Recall: 8e-3,
49938
+ _v9FpRate: 0.1118,
49939
+ _v9Precision: 89e-4,
49892
49940
  defaultOff: true
49893
49941
  },
49894
49942
  "java/legacy-date-api": {
49895
- recall: 0,
49896
- fpRate: 0,
49897
- ratio: 1,
49898
- precision: 0,
49899
- lastCalibratedAt: "2026-07-01T00:00:00Z",
49943
+ recall: 0.0285,
49944
+ fpRate: 0.0511,
49945
+ ratio: 0.56,
49946
+ precision: 0.0657,
49947
+ lastCalibratedAt: "2026-07-02T19:34:00Z",
49900
49948
  verdict: "DORMANT",
49901
- _calibrationNote: "v0.20: new rule (Legacy java.util.Date / Calendar \u2014 use java.time (JSR-310)). Not yet calibrated. Scheduled for v9 Java corpus calibration.",
49949
+ _calibrationNote: "v9 Java corpus calibration (v0.25.0, 2026-07-02): 81891 neg + 10305 pos Java files. v9 TP=294, FP=4181, P=6.57%, FPR=5.11%, recall=2.85%, ratio=0.56, lift=P/FPR=1.29. INVERTED-to-NOISY: legacy date API is more common in older enterprise Java (neg) than in modern AI/Java repos (pos). The heuristic correctly flags the legacy pattern, but the legacy pattern is over-represented in the neg baseline (older codebases). v0.20: new rule.",
49902
49950
  aiSpecific: true,
49903
49951
  _v7Verdict: "DORMANT",
49904
49952
  _v7Lift: 1,
@@ -49907,16 +49955,21 @@ var init_signal_strength = __esm({
49907
49955
  _v7Precision: 0,
49908
49956
  _v8Verdict: "DORMANT",
49909
49957
  _v8Lift: 1,
49958
+ _v9Verdict: "DORMANT",
49959
+ _v9Lift: 1.29,
49960
+ _v9Recall: 0.0285,
49961
+ _v9FpRate: 0.0511,
49962
+ _v9Precision: 0.0657,
49910
49963
  defaultOff: true
49911
49964
  },
49912
49965
  "java/raw-type-overuse": {
49913
- recall: 0,
49914
- fpRate: 0,
49915
- ratio: 1,
49916
- precision: 0,
49917
- lastCalibratedAt: "2026-07-01T00:00:00Z",
49966
+ recall: 0.1093,
49967
+ fpRate: 0.2041,
49968
+ ratio: 0.54,
49969
+ precision: 0.0632,
49970
+ lastCalibratedAt: "2026-07-02T19:34:00Z",
49918
49971
  verdict: "DORMANT",
49919
- _calibrationNote: "v0.20: new rule (Raw type usage \u2014 use generics). DORMANT until v9 Java corpus calibration.",
49972
+ _calibrationNote: "v9 Java corpus calibration (v0.25.0, 2026-07-02): 81891 neg + 10305 pos Java files. v9 TP=1126, FP=16712, P=6.32%, FPR=20.41%, recall=10.93%, ratio=0.54, lift=P/FPR=0.31. Strongly INVERTED: raw types (no generics) are 2x more common in legacy enterprise Java (neg) than in modern Java repos (pos). Heuristic is correctly identifying the legacy anti-pattern, but it's an age marker more than an AI-fingerprint. v0.20: new rule.",
49920
49973
  aiSpecific: true,
49921
49974
  _v7Verdict: "DORMANT",
49922
49975
  _v7Lift: 1,
@@ -49925,16 +49978,21 @@ var init_signal_strength = __esm({
49925
49978
  _v7Precision: 0,
49926
49979
  _v8Verdict: "DORMANT",
49927
49980
  _v8Lift: 1,
49981
+ _v9Verdict: "DORMANT",
49982
+ _v9Lift: 0.31,
49983
+ _v9Recall: 0.1093,
49984
+ _v9FpRate: 0.2041,
49985
+ _v9Precision: 0.0632,
49928
49986
  defaultOff: true
49929
49987
  },
49930
49988
  "java/string-concat-loop": {
49931
- recall: 0,
49932
- fpRate: 0,
49933
- ratio: 1,
49934
- precision: 0,
49935
- lastCalibratedAt: "2026-07-01T00:00:00Z",
49989
+ recall: 46e-4,
49990
+ fpRate: 0.0358,
49991
+ ratio: 0.13,
49992
+ precision: 0.0158,
49993
+ lastCalibratedAt: "2026-07-02T19:34:00Z",
49936
49994
  verdict: "DORMANT",
49937
- _calibrationNote: "v0.20: new rule (String concat in loop \u2014 use StringBuilder). DORMANT until v9 Java corpus calibration.",
49995
+ _calibrationNote: "v9 Java corpus calibration (v0.25.0, 2026-07-02): 81891 neg + 10305 pos Java files. v9 TP=47, FP=2933, P=1.58%, FPR=3.58%, recall=0.46%, ratio=0.13, lift=P/FPR=0.44. Strongly INVERTED: string concatenation in a loop is 8x more common in legacy enterprise Java (neg) than in AI/ML/Java-modern pos repos. Modern Java training data and modern IDE warnings discourage the pattern. v0.20: new rule.",
49938
49996
  aiSpecific: true,
49939
49997
  _v7Verdict: "DORMANT",
49940
49998
  _v7Lift: 1,
@@ -49943,16 +50001,21 @@ var init_signal_strength = __esm({
49943
50001
  _v7Precision: 0,
49944
50002
  _v8Verdict: "DORMANT",
49945
50003
  _v8Lift: 1,
50004
+ _v9Verdict: "DORMANT",
50005
+ _v9Lift: 0.44,
50006
+ _v9Recall: 46e-4,
50007
+ _v9FpRate: 0.0358,
50008
+ _v9Precision: 0.0158,
49946
50009
  defaultOff: true
49947
50010
  },
49948
50011
  "java/system-out-println": {
49949
- recall: 0,
49950
- fpRate: 0,
49951
- ratio: 1,
49952
- precision: 0,
49953
- lastCalibratedAt: "2026-07-01T00:00:00Z",
50012
+ recall: 0.1503,
50013
+ fpRate: 0.2533,
50014
+ ratio: 0.59,
50015
+ precision: 0.0695,
50016
+ lastCalibratedAt: "2026-07-02T19:34:00Z",
49954
50017
  verdict: "DORMANT",
49955
- _calibrationNote: "v0.20: new rule (System.out.println in production code \u2014 use a logger). Not yet calibrated. Scheduled for v9 Java corpus calibration.",
50018
+ _calibrationNote: "v9 Java corpus calibration (v0.25.0, 2026-07-02): 81891 neg + 10305 pos Java files. v9 TP=1549, FP=20737, P=6.95%, FPR=25.32%, recall=15.03%, ratio=0.59, lift=P/FPR=0.27. INVERTED: System.out.println is more common in legacy enterprise Java (neg) than in modern AI/Java repos (pos). Modern Java defaults to SLF4J/Log4j, AI-generated Java starts with a logger import. The pattern is the opposite of the v0.20 design intent (which assumed println was the AI-fingerprint signal). v0.25.x: consider gating this rule by Java era (recent file mtime) or making it an age-fingerprint rather than AI-fingerprint. v0.20: new rule.",
49956
50019
  aiSpecific: true,
49957
50020
  _v7Verdict: "DORMANT",
49958
50021
  _v7Lift: 1,
@@ -49961,6 +50024,11 @@ var init_signal_strength = __esm({
49961
50024
  _v7Precision: 0,
49962
50025
  _v8Verdict: "DORMANT",
49963
50026
  _v8Lift: 1,
50027
+ _v9Verdict: "DORMANT",
50028
+ _v9Lift: 0.27,
50029
+ _v9Recall: 0.1503,
50030
+ _v9FpRate: 0.2533,
50031
+ _v9Precision: 0.0695,
49964
50032
  defaultOff: true
49965
50033
  },
49966
50034
  "layout/forced-layout": {
@@ -51516,7 +51584,8 @@ var init_signal_strength2 = __esm({
51516
51584
  // src/engine/worker.ts
51517
51585
  import { isMainThread, parentPort, workerData } from "worker_threads";
51518
51586
  import { extname as extname5 } from "path";
51519
- import { join as join13 } from "path";
51587
+ import { join as join13, relative as relative5, sep as sep2 } from "path";
51588
+ import { minimatch as minimatch2 } from "minimatch";
51520
51589
  function buildParserCacheConfig(cwd) {
51521
51590
  const envVal = process.env.SLOP_AUDIT_CACHE;
51522
51591
  const enabled = envVal === "1" || envVal === "true";
@@ -51536,7 +51605,23 @@ function applyRuleOverrides(issues, rules) {
51536
51605
  }
51537
51606
  return result;
51538
51607
  }
51608
+ function isExcludedBySelfScan(filePath, cwd, excludePaths) {
51609
+ if (!excludePaths || excludePaths.length === 0) return false;
51610
+ const rel = relative5(cwd, filePath).split(sep2).join("/");
51611
+ return excludePaths.some((pattern) => minimatch2(rel, pattern, { dot: true }));
51612
+ }
51539
51613
  async function scanFile(filePath, config, registry, cwd = process.cwd()) {
51614
+ if (isExcludedBySelfScan(filePath, cwd, config.selfScan?.excludePaths)) {
51615
+ return {
51616
+ filePath,
51617
+ componentCount: 0,
51618
+ issues: [],
51619
+ gapValues: [],
51620
+ styleSources: [],
51621
+ elementTags: [],
51622
+ unmatchedStringLiterals: []
51623
+ };
51624
+ }
51540
51625
  const cache = buildParserCacheConfig(cwd);
51541
51626
  const ext = extname5(filePath).toLowerCase();
51542
51627
  const UNSUPPORTED_LANGS = /* @__PURE__ */ new Set([
@@ -51551,7 +51636,6 @@ async function scanFile(filePath, config, registry, cwd = process.cwd()) {
51551
51636
  ".h",
51552
51637
  ".hpp",
51553
51638
  ".hxx",
51554
- ".java",
51555
51639
  ".rb",
51556
51640
  ".php"
51557
51641
  ]);
@@ -51839,45 +51923,6 @@ var init_flywheel = __esm({
51839
51923
  }
51840
51924
  });
51841
51925
 
51842
- // src/engine/ai-security-risk.ts
51843
- function computeAiSecurityRisk(issues) {
51844
- const findings = { critical: 0, high: 0, medium: 0, low: 0 };
51845
- for (const issue of issues) {
51846
- switch (issue.severity) {
51847
- case "high":
51848
- findings.high += 1;
51849
- break;
51850
- case "medium":
51851
- findings.medium += 1;
51852
- break;
51853
- case "low":
51854
- findings.low += 1;
51855
- break;
51856
- default:
51857
- findings.low += 1;
51858
- }
51859
- }
51860
- let risk = "low";
51861
- if (findings.critical >= 1 || findings.high >= 3) {
51862
- risk = "critical";
51863
- } else if (findings.high >= 1 || findings.medium >= 3) {
51864
- risk = "high";
51865
- } else if (findings.medium >= 1) {
51866
- risk = "medium";
51867
- }
51868
- return { risk, findings };
51869
- }
51870
- function formatAiSecurityRiskLine(risk, findings) {
51871
- const total = findings.critical + findings.high + findings.medium + findings.low;
51872
- if (total === 0) return "AI Security Risk: low";
51873
- return `AI Security Risk: ${risk.toUpperCase()} (${total} findings)`;
51874
- }
51875
- var init_ai_security_risk = __esm({
51876
- "src/engine/ai-security-risk.ts"() {
51877
- "use strict";
51878
- }
51879
- });
51880
-
51881
51926
  // src/engine/test-quality.ts
51882
51927
  var test_quality_exports = {};
51883
51928
  __export(test_quality_exports, {
@@ -51946,7 +51991,7 @@ var init_test_quality = __esm({
51946
51991
  });
51947
51992
 
51948
51993
  // src/engine/metrics.ts
51949
- import { isAbsolute as isAbsolute2, relative as relative5 } from "path";
51994
+ import { isAbsolute as isAbsolute2, relative as relative6 } from "path";
51950
51995
  function bucketFor(ruleId) {
51951
51996
  return RULE_TO_BUCKET[ruleId] ?? "visual";
51952
51997
  }
@@ -51970,7 +52015,7 @@ function scoreFile(result, frameworkMultiplier, config, baseline, cwd) {
51970
52015
  100,
51971
52016
  rawScore * frameworkMultiplier * CONTEXT_DENSITY_MULTIPLIER
51972
52017
  );
51973
- const baselineKey = cwd ? isAbsolute2(result.filePath) ? relative5(cwd, result.filePath) : result.filePath : result.filePath;
52018
+ const baselineKey = cwd ? isAbsolute2(result.filePath) ? relative6(cwd, result.filePath) : result.filePath : result.filePath;
51974
52019
  const baselineScore = baseline?.scores[baselineKey]?.baselineScore ?? 0;
51975
52020
  const adjustedScore = baseline ? Math.max(0, componentScore - baselineScore) : componentScore;
51976
52021
  return {
@@ -52054,14 +52099,10 @@ function aggregateReport(scores, issueGroups, config, compositeScores) {
52054
52099
  });
52055
52100
  }
52056
52101
  }
52057
- const { risk } = computeAiSecurityRisk(flatIssues);
52058
- const securityFromRisk = {
52059
- low: 100,
52060
- medium: 67,
52061
- high: 33,
52062
- critical: 0
52063
- };
52064
- const security = securityFromRisk[risk];
52102
+ const securityIssueCount = flatIssues.filter(
52103
+ (i) => i.category === "security" || (i.ruleId ?? "").startsWith("security/")
52104
+ ).length;
52105
+ const security = Math.max(0, 100 / (1 + securityIssueCount / 5));
52065
52106
  const engineeringCategoryScores = [
52066
52107
  categoryScores.arch ?? 0,
52067
52108
  categoryScores.logic ?? 0,
@@ -52119,7 +52160,6 @@ var SEVERITY_WEIGHTS, CONTEXT_DENSITY_MULTIPLIER, COMPOSITE_WEIGHTS, RULE_TO_BUC
52119
52160
  var init_metrics = __esm({
52120
52161
  "src/engine/metrics.ts"() {
52121
52162
  "use strict";
52122
- init_ai_security_risk();
52123
52163
  init_test_quality();
52124
52164
  SEVERITY_WEIGHTS = {
52125
52165
  low: 1,
@@ -54051,6 +54091,45 @@ var init_business_logic = __esm({
54051
54091
  }
54052
54092
  });
54053
54093
 
54094
+ // src/engine/ai-security-risk.ts
54095
+ function computeAiSecurityRisk(issues) {
54096
+ const findings = { critical: 0, high: 0, medium: 0, low: 0 };
54097
+ for (const issue of issues) {
54098
+ switch (issue.severity) {
54099
+ case "high":
54100
+ findings.high += 1;
54101
+ break;
54102
+ case "medium":
54103
+ findings.medium += 1;
54104
+ break;
54105
+ case "low":
54106
+ findings.low += 1;
54107
+ break;
54108
+ default:
54109
+ findings.low += 1;
54110
+ }
54111
+ }
54112
+ let risk = "low";
54113
+ if (findings.critical >= 1 || findings.high >= 3) {
54114
+ risk = "critical";
54115
+ } else if (findings.high >= 1 || findings.medium >= 3) {
54116
+ risk = "high";
54117
+ } else if (findings.medium >= 1) {
54118
+ risk = "medium";
54119
+ }
54120
+ return { risk, findings };
54121
+ }
54122
+ function formatAiSecurityRiskLine(risk, findings) {
54123
+ const total = findings.critical + findings.high + findings.medium + findings.low;
54124
+ if (total === 0) return "AI Security Risk: low";
54125
+ return `AI Security Risk: ${risk.toUpperCase()} (${total} findings)`;
54126
+ }
54127
+ var init_ai_security_risk = __esm({
54128
+ "src/engine/ai-security-risk.ts"() {
54129
+ "use strict";
54130
+ }
54131
+ });
54132
+
54054
54133
  // src/engine/maintenance-cost.ts
54055
54134
  var maintenance_cost_exports = {};
54056
54135
  __export(maintenance_cost_exports, {
@@ -54327,7 +54406,7 @@ __export(db_health_exports, {
54327
54406
  buildDbHealth: () => buildDbHealth
54328
54407
  });
54329
54408
  import { readFileSync as readFileSync17 } from "fs";
54330
- import { relative as relative6 } from "path";
54409
+ import { relative as relative7 } from "path";
54331
54410
  import { globby as globby3 } from "globby";
54332
54411
  import { parse as parseSql6, loadModule as loadSqlModule6 } from "pgsql-parser";
54333
54412
  function ensureSqlModule() {
@@ -54378,7 +54457,7 @@ async function buildDbHealth(cwd, _config, options = {}) {
54378
54457
  for (const abs of sqlFiles.slice(0, maxFiles)) {
54379
54458
  const parsed = await parseSqlFile(abs);
54380
54459
  if (!parsed) continue;
54381
- const relPath = relative6(cwd, abs);
54460
+ const relPath = relative7(cwd, abs);
54382
54461
  parsedFiles.push({ relPath, parsed });
54383
54462
  for (const stmt of parsed.statements) {
54384
54463
  if (stmt.type !== "IndexStmt") continue;
@@ -54433,7 +54512,7 @@ async function buildDbHealth(cwd, _config, options = {}) {
54433
54512
  } catch {
54434
54513
  continue;
54435
54514
  }
54436
- const relPath = relative6(cwd, abs);
54515
+ const relPath = relative7(cwd, abs);
54437
54516
  const context = { config: _config, filePath: relPath, cwd };
54438
54517
  const facts = { filePath: relPath, v2: { _source: source } };
54439
54518
  const ruleContext = sqlConcatRule.create(context);
@@ -54790,7 +54869,7 @@ function computeDomainScores(issues) {
54790
54869
  performance2.score = issueCountToScore(performance2.issueCount);
54791
54870
  const security = tally(SECURITY_CATEGORIES);
54792
54871
  security.name = "security";
54793
- security.score = security.issueCount > 0 ? 0 : 100;
54872
+ security.score = Math.max(0, 100 / (1 + security.issueCount / 5));
54794
54873
  return { codeHygiene, accessibility, performance: performance2, security };
54795
54874
  }
54796
54875
  var COHERENCE_WEIGHTS, AI_DEBT_NUMERIC, CODE_HYGIENE_CATEGORIES, ACCESSIBILITY_CATEGORIES, PERFORMANCE_CATEGORIES, SECURITY_CATEGORIES;
@@ -54818,7 +54897,7 @@ var init_coherence = __esm({
54818
54897
 
54819
54898
  // src/cli/report/enrichReport.ts
54820
54899
  import { readFileSync as readFileSync18 } from "fs";
54821
- import { relative as relative7 } from "path";
54900
+ import { relative as relative8 } from "path";
54822
54901
  function collectBusinessLogicIssues(cwd, filePaths) {
54823
54902
  const issues = [];
54824
54903
  for (const absPath of filePaths) {
@@ -54832,7 +54911,7 @@ function collectBusinessLogicIssues(cwd, filePaths) {
54832
54911
  for (const issue of fileIssues) {
54833
54912
  issues.push({
54834
54913
  ...issue,
54835
- filePath: relative7(cwd, absPath) || absPath
54914
+ filePath: relative8(cwd, absPath) || absPath
54836
54915
  });
54837
54916
  }
54838
54917
  }
@@ -55223,7 +55302,7 @@ import {
55223
55302
  statSync as statSync6
55224
55303
  } from "fs";
55225
55304
  import { createHash as createHash10 } from "crypto";
55226
- import { dirname as dirname11, join as join17, relative as relative8 } from "path";
55305
+ import { dirname as dirname11, join as join17, relative as relative9 } from "path";
55227
55306
  function telemetryPath2(cwd) {
55228
55307
  return join17(cwd, TELEMETRY_DIR, TELEMETRY_FILE2);
55229
55308
  }
@@ -55232,7 +55311,7 @@ function hashString(input) {
55232
55311
  }
55233
55312
  function safeRelative(cwd, filePath) {
55234
55313
  try {
55235
- return relative8(cwd, filePath);
55314
+ return relative9(cwd, filePath);
55236
55315
  } catch {
55237
55316
  return filePath;
55238
55317
  }
@@ -55580,7 +55659,7 @@ var init_structure_md = __esm({
55580
55659
 
55581
55660
  // src/cli/report/persistRun.ts
55582
55661
  import { existsSync as existsSync18, writeFileSync as writeFileSync7, mkdirSync as mkdirSync7 } from "fs";
55583
- import { join as join19, relative as relative9 } from "path";
55662
+ import { join as join19, relative as relative10 } from "path";
55584
55663
  async function persistRun(input) {
55585
55664
  const {
55586
55665
  cwd,
@@ -55642,7 +55721,7 @@ async function persistRun(input) {
55642
55721
  const recentTopHashes = telemetryPayloads.map(
55643
55722
  (payload) => [...payload.files].sort((a, b) => b.score - a.score).slice(0, 10).map((file) => file.hash)
55644
55723
  );
55645
- const currentTopFiles = [...report.components].sort((a, b) => b.adjustedScore - a.adjustedScore).slice(0, 10).map((c) => ({ filePath: c.filePath, hash: hashFile(relative9(cwd, c.filePath)) }));
55724
+ const currentTopFiles = [...report.components].sort((a, b) => b.adjustedScore - a.adjustedScore).slice(0, 10).map((c) => ({ filePath: c.filePath, hash: hashFile(relative10(cwd, c.filePath)) }));
55646
55725
  const unmatchedStringLiterals = results.flatMap((r) => r.unmatchedStringLiterals ?? []);
55647
55726
  const flywheelOutput = computeFlywheelOutput(
55648
55727
  runs,
@@ -55847,11 +55926,11 @@ var init_finalizeReport = __esm({
55847
55926
  });
55848
55927
 
55849
55928
  // src/cli/report/baseline-cache.ts
55850
- import { relative as relative10 } from "path";
55929
+ import { relative as relative11 } from "path";
55851
55930
  function buildBaselineCache(report, configHash, gitHead, cwd) {
55852
55931
  const scores = {};
55853
55932
  for (const component of report.components) {
55854
- scores[relative10(cwd, component.filePath)] = {
55933
+ scores[relative11(cwd, component.filePath)] = {
55855
55934
  baselineScore: component.componentScore,
55856
55935
  componentCount: component.componentCount
55857
55936
  };
@@ -55929,14 +56008,14 @@ var init_json = __esm({
55929
56008
  // src/report/sarif.ts
55930
56009
  import { createHash as createHash11 } from "crypto";
55931
56010
  import { readFileSync as readFileSync21 } from "fs";
55932
- import { basename as basename3, isAbsolute as isAbsolute3, relative as relative11, resolve as resolve9 } from "path";
56011
+ import { basename as basename3, isAbsolute as isAbsolute3, relative as relative12, resolve as resolve9 } from "path";
55933
56012
  function buildArtifactUri(filePath, cwd) {
55934
56013
  if (!filePath) {
55935
56014
  return ".";
55936
56015
  }
55937
56016
  if (cwd) {
55938
56017
  const absoluteFilePath = isAbsolute3(filePath) ? filePath : resolve9(cwd, filePath);
55939
- const rel = relative11(cwd, absoluteFilePath);
56018
+ const rel = relative12(cwd, absoluteFilePath);
55940
56019
  if (rel.startsWith("..")) {
55941
56020
  return basename3(filePath);
55942
56021
  }
@@ -57311,7 +57390,7 @@ var init_layout_token = __esm({
57311
57390
 
57312
57391
  // src/report/unified-diff.ts
57313
57392
  import { existsSync as existsSync19, readFileSync as readFileSync23 } from "fs";
57314
- import { relative as relative12 } from "path";
57393
+ import { relative as relative13 } from "path";
57315
57394
  function collectAllFixes(issue) {
57316
57395
  return [...issue.fix ? [issue.fix] : [], ...issue.fixes ?? []];
57317
57396
  }
@@ -57397,7 +57476,7 @@ function formatUnifiedDiff(report, cwd) {
57397
57476
  parts.push("");
57398
57477
  hasHunk = true;
57399
57478
  }
57400
- const rel = relative12(cwd, filePath);
57479
+ const rel = relative13(cwd, filePath);
57401
57480
  parts.push(`--- a/${rel}`);
57402
57481
  parts.push(`+++ b/${rel}`);
57403
57482
  parts.push(...formatHunk(original, patched));
@@ -57421,7 +57500,7 @@ __export(heatmap_exports, {
57421
57500
  buildHeatmap: () => buildHeatmap,
57422
57501
  formatHeatmap: () => formatHeatmap
57423
57502
  });
57424
- import { relative as relative13 } from "path";
57503
+ import { relative as relative14 } from "path";
57425
57504
  function isWithinDays(lastModified, reference, days) {
57426
57505
  const msPerDay = 1e3 * 60 * 60 * 24;
57427
57506
  const daysAgo = (reference.getTime() - lastModified.getTime()) / msPerDay;
@@ -57437,7 +57516,7 @@ async function buildHeatmap(report, cwd, helpers = { getFileEditCount, getFileLa
57437
57516
  const reference = new Date(report.generatedAt);
57438
57517
  const entries = await Promise.all(
57439
57518
  report.components.map(async (component) => {
57440
- const relPath = relative13(cwd, component.filePath) || component.filePath;
57519
+ const relPath = relative14(cwd, component.filePath) || component.filePath;
57441
57520
  const [edits, lastModified] = await Promise.all([
57442
57521
  helpers.getFileEditCount(cwd, relPath, RECENCY_DAYS),
57443
57522
  helpers.getFileLastModifiedDate(cwd, relPath)
@@ -57782,7 +57861,7 @@ __export(scan_exports, {
57782
57861
  watchProject: () => watchProject
57783
57862
  });
57784
57863
  import { existsSync as existsSync20, statSync as statSync8 } from "fs";
57785
- import { resolve as resolve12, relative as relative14, extname as extname7, sep as sep2 } from "path";
57864
+ import { resolve as resolve12, relative as relative15, extname as extname7, sep as sep3 } from "path";
57786
57865
  import { randomUUID } from "crypto";
57787
57866
  async function runScan(options, explicitPaths) {
57788
57867
  setLoggerQuiet(!!options.quiet);
@@ -57830,7 +57909,7 @@ async function runScan(options, explicitPaths) {
57830
57909
  let files;
57831
57910
  if (explicitPaths && explicitPaths.length > 0) {
57832
57911
  const { globby: globby4 } = await import("globby");
57833
- const { minimatch: minimatch3 } = await import("minimatch");
57912
+ const { minimatch: minimatch4 } = await import("minimatch");
57834
57913
  const resolved = explicitPaths.map((p) => resolve12(cwd, p));
57835
57914
  const expanded = [];
57836
57915
  for (const p of resolved) {
@@ -57838,11 +57917,11 @@ async function runScan(options, explicitPaths) {
57838
57917
  const found = await globby4(`${p}/**/*`, { absolute: true, onlyFiles: true });
57839
57918
  for (const f of found) {
57840
57919
  if (!ALL_SOURCE_EXTENSIONS.has(extname7(f).toLowerCase())) continue;
57841
- const rel = relative14(cwd, f).split(sep2).join("/");
57842
- if (config.include.length > 0 && !config.include.some((pattern) => minimatch3(rel, pattern))) {
57920
+ const rel = relative15(cwd, f).split(sep3).join("/");
57921
+ if (config.include.length > 0 && !config.include.some((pattern) => minimatch4(rel, pattern))) {
57843
57922
  continue;
57844
57923
  }
57845
- if (config.exclude.some((pattern) => minimatch3(rel, pattern, { dot: true }))) {
57924
+ if (config.exclude.some((pattern) => minimatch4(rel, pattern, { dot: true }))) {
57846
57925
  continue;
57847
57926
  }
57848
57927
  expanded.push(f);
@@ -61076,7 +61155,7 @@ import { resolve as resolve26 } from "path";
61076
61155
  init_discover();
61077
61156
  init_patterns();
61078
61157
  import { readFileSync as readFileSync30 } from "fs";
61079
- import { basename as basename4, relative as relative15 } from "path";
61158
+ import { basename as basename4, relative as relative16 } from "path";
61080
61159
  async function runDrift(cwd, config, options = {}) {
61081
61160
  const maxFiles = options.maxFiles ?? 1e3;
61082
61161
  const allFiles = await discoverFiles(cwd, config);
@@ -61098,7 +61177,7 @@ async function runDrift(cwd, config, options = {}) {
61098
61177
  byCategory[v.category] = (byCategory[v.category] ?? 0) + 1;
61099
61178
  byFile.push({
61100
61179
  file: absPath,
61101
- relPath: relative15(cwd, absPath),
61180
+ relPath: relative16(cwd, absPath),
61102
61181
  category: v.category,
61103
61182
  import: v.import,
61104
61183
  declared: v.declared,
@@ -61220,10 +61299,10 @@ init_metrics();
61220
61299
  init_patterns();
61221
61300
  init_discover();
61222
61301
  import { readFileSync as readFileSync31 } from "fs";
61223
- import { extname as extname9, relative as relative16, resolve as resolve27 } from "path";
61302
+ import { extname as extname9, relative as relative17, resolve as resolve27 } from "path";
61224
61303
  import { execFile as execFileCb } from "child_process";
61225
61304
  import { promisify as promisify2 } from "util";
61226
- import { minimatch as minimatch2 } from "minimatch";
61305
+ import { minimatch as minimatch3 } from "minimatch";
61227
61306
  var execFile2 = promisify2(execFileCb);
61228
61307
  var PR_EXTENSIONS = /* @__PURE__ */ new Set([...SOURCE_EXTENSIONS, ".mdx"]);
61229
61308
  async function refExists(cwd, ref) {
@@ -61264,11 +61343,11 @@ async function discoverPrFiles(cwd, config, base, head, maxFiles) {
61264
61343
  const abs = resolve27(cwd, relOrAbs);
61265
61344
  const ext = extname9(abs).toLowerCase();
61266
61345
  if (!PR_EXTENSIONS.has(ext)) continue;
61267
- const rel = relative16(cwd, abs).split("\\").join("/");
61268
- if (config.include.length > 0 && !config.include.some((pattern) => minimatch2(rel, pattern))) {
61346
+ const rel = relative17(cwd, abs).split("\\").join("/");
61347
+ if (config.include.length > 0 && !config.include.some((pattern) => minimatch3(rel, pattern))) {
61269
61348
  continue;
61270
61349
  }
61271
- if (config.exclude.some((pattern) => minimatch2(rel, pattern))) {
61350
+ if (config.exclude.some((pattern) => minimatch3(rel, pattern))) {
61272
61351
  continue;
61273
61352
  }
61274
61353
  sourceFiles.push(abs);
@@ -61335,7 +61414,7 @@ async function runPrScan(cwd, config, options = {}) {
61335
61414
  totalScore += fileScore;
61336
61415
  files.push({
61337
61416
  file: absPath,
61338
- relPath: relative16(cwd, absPath).split("\\").join("/"),
61417
+ relPath: relative17(cwd, absPath).split("\\").join("/"),
61339
61418
  score: fileScore,
61340
61419
  issueCount: issues.length,
61341
61420
  constitutionViolationCount: constitutionViolations.length,
@@ -61712,7 +61791,7 @@ import { resolve as resolve33 } from "path";
61712
61791
  init_discover();
61713
61792
  init_business_logic();
61714
61793
  import { readFileSync as readFileSync32 } from "fs";
61715
- import { relative as relative17 } from "path";
61794
+ import { relative as relative18 } from "path";
61716
61795
  async function runBusinessLogicScan(cwd, config, options = {}) {
61717
61796
  const maxFiles = options.maxFiles ?? 500;
61718
61797
  const allFiles = await discoverFiles(cwd, config);
@@ -61731,7 +61810,7 @@ async function runBusinessLogicScan(cwd, config, options = {}) {
61731
61810
  for (const issue of fileIssues) {
61732
61811
  issues.push({
61733
61812
  ...issue,
61734
- filePath: relative17(cwd, absPath) || absPath
61813
+ filePath: relative18(cwd, absPath) || absPath
61735
61814
  });
61736
61815
  }
61737
61816
  }
@@ -62293,7 +62372,7 @@ import { resolve as resolve37 } from "path";
62293
62372
  init_patterns();
62294
62373
  init_discover();
62295
62374
  import { readFileSync as readFileSync33 } from "fs";
62296
- import { basename as basename5, relative as relative18 } from "path";
62375
+ import { basename as basename5, relative as relative19 } from "path";
62297
62376
  var PATTERN_CATEGORIES = [
62298
62377
  "modal",
62299
62378
  "button",
@@ -62385,7 +62464,7 @@ function detectApiFromFiles(files, cwd) {
62385
62464
  const out = [];
62386
62465
  for (const f of files) {
62387
62466
  if (API_PATH_RE2.test(f)) {
62388
- out.push(relative18(cwd, f).split("\\").join("/"));
62467
+ out.push(relative19(cwd, f).split("\\").join("/"));
62389
62468
  }
62390
62469
  }
62391
62470
  return out;