@kevinrabun/judges 3.23.5 → 3.23.7

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.
Files changed (105) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +37 -2
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/benchmark.d.ts.map +1 -1
  6. package/dist/commands/benchmark.js +556 -0
  7. package/dist/commands/benchmark.js.map +1 -1
  8. package/dist/commands/diff.d.ts.map +1 -1
  9. package/dist/commands/diff.js +17 -1
  10. package/dist/commands/diff.js.map +1 -1
  11. package/dist/commands/review.d.ts +37 -0
  12. package/dist/commands/review.d.ts.map +1 -0
  13. package/dist/commands/review.js +539 -0
  14. package/dist/commands/review.js.map +1 -0
  15. package/dist/commands/tune.d.ts +25 -0
  16. package/dist/commands/tune.d.ts.map +1 -0
  17. package/dist/commands/tune.js +408 -0
  18. package/dist/commands/tune.js.map +1 -0
  19. package/dist/evaluators/accessibility.d.ts.map +1 -1
  20. package/dist/evaluators/accessibility.js +5 -1
  21. package/dist/evaluators/accessibility.js.map +1 -1
  22. package/dist/evaluators/ai-code-safety.d.ts.map +1 -1
  23. package/dist/evaluators/ai-code-safety.js +6 -1
  24. package/dist/evaluators/ai-code-safety.js.map +1 -1
  25. package/dist/evaluators/authentication.d.ts.map +1 -1
  26. package/dist/evaluators/authentication.js +5 -1
  27. package/dist/evaluators/authentication.js.map +1 -1
  28. package/dist/evaluators/ci-cd.d.ts.map +1 -1
  29. package/dist/evaluators/ci-cd.js +6 -2
  30. package/dist/evaluators/ci-cd.js.map +1 -1
  31. package/dist/evaluators/cloud-readiness.d.ts.map +1 -1
  32. package/dist/evaluators/cloud-readiness.js +12 -3
  33. package/dist/evaluators/cloud-readiness.js.map +1 -1
  34. package/dist/evaluators/compliance.d.ts.map +1 -1
  35. package/dist/evaluators/compliance.js +5 -1
  36. package/dist/evaluators/compliance.js.map +1 -1
  37. package/dist/evaluators/configuration-management.d.ts.map +1 -1
  38. package/dist/evaluators/configuration-management.js +5 -1
  39. package/dist/evaluators/configuration-management.js.map +1 -1
  40. package/dist/evaluators/cost-effectiveness.d.ts.map +1 -1
  41. package/dist/evaluators/cost-effectiveness.js +5 -3
  42. package/dist/evaluators/cost-effectiveness.js.map +1 -1
  43. package/dist/evaluators/cybersecurity.d.ts.map +1 -1
  44. package/dist/evaluators/cybersecurity.js +5 -1
  45. package/dist/evaluators/cybersecurity.js.map +1 -1
  46. package/dist/evaluators/data-security.d.ts.map +1 -1
  47. package/dist/evaluators/data-security.js +12 -1
  48. package/dist/evaluators/data-security.js.map +1 -1
  49. package/dist/evaluators/data-sovereignty.d.ts.map +1 -1
  50. package/dist/evaluators/data-sovereignty.js +5 -1
  51. package/dist/evaluators/data-sovereignty.js.map +1 -1
  52. package/dist/evaluators/database.d.ts.map +1 -1
  53. package/dist/evaluators/database.js +11 -1
  54. package/dist/evaluators/database.js.map +1 -1
  55. package/dist/evaluators/error-handling.d.ts.map +1 -1
  56. package/dist/evaluators/error-handling.js +2 -2
  57. package/dist/evaluators/error-handling.js.map +1 -1
  58. package/dist/evaluators/ethics-bias.d.ts.map +1 -1
  59. package/dist/evaluators/ethics-bias.js +5 -1
  60. package/dist/evaluators/ethics-bias.js.map +1 -1
  61. package/dist/evaluators/false-positive-review.js +4 -4
  62. package/dist/evaluators/false-positive-review.js.map +1 -1
  63. package/dist/evaluators/iac-security.d.ts.map +1 -1
  64. package/dist/evaluators/iac-security.js +14 -2
  65. package/dist/evaluators/iac-security.js.map +1 -1
  66. package/dist/evaluators/internationalization.d.ts.map +1 -1
  67. package/dist/evaluators/internationalization.js +5 -1
  68. package/dist/evaluators/internationalization.js.map +1 -1
  69. package/dist/evaluators/logging-privacy.d.ts.map +1 -1
  70. package/dist/evaluators/logging-privacy.js +8 -4
  71. package/dist/evaluators/logging-privacy.js.map +1 -1
  72. package/dist/evaluators/maintainability.d.ts.map +1 -1
  73. package/dist/evaluators/maintainability.js +37 -28
  74. package/dist/evaluators/maintainability.js.map +1 -1
  75. package/dist/evaluators/observability.js +2 -2
  76. package/dist/evaluators/observability.js.map +1 -1
  77. package/dist/evaluators/performance.d.ts.map +1 -1
  78. package/dist/evaluators/performance.js +9 -5
  79. package/dist/evaluators/performance.js.map +1 -1
  80. package/dist/evaluators/portability.d.ts.map +1 -1
  81. package/dist/evaluators/portability.js +33 -11
  82. package/dist/evaluators/portability.js.map +1 -1
  83. package/dist/evaluators/reliability.js +2 -2
  84. package/dist/evaluators/reliability.js.map +1 -1
  85. package/dist/evaluators/scalability.d.ts.map +1 -1
  86. package/dist/evaluators/scalability.js +5 -3
  87. package/dist/evaluators/scalability.js.map +1 -1
  88. package/dist/evaluators/shared.d.ts +34 -0
  89. package/dist/evaluators/shared.d.ts.map +1 -1
  90. package/dist/evaluators/shared.js +60 -0
  91. package/dist/evaluators/shared.js.map +1 -1
  92. package/dist/evaluators/software-practices.js +2 -2
  93. package/dist/evaluators/software-practices.js.map +1 -1
  94. package/dist/finding-lifecycle.d.ts +93 -0
  95. package/dist/finding-lifecycle.d.ts.map +1 -0
  96. package/dist/finding-lifecycle.js +214 -0
  97. package/dist/finding-lifecycle.js.map +1 -0
  98. package/dist/patches/index.d.ts.map +1 -1
  99. package/dist/patches/index.js +127 -0
  100. package/dist/patches/index.js.map +1 -1
  101. package/dist/presets.d.ts.map +1 -1
  102. package/dist/presets.js +103 -0
  103. package/dist/presets.js.map +1 -1
  104. package/package.json +1 -1
  105. package/server.json +2 -2
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Finding Lifecycle Tracking
3
+ *
4
+ * Tracks individual findings across multiple evaluation runs, enabling:
5
+ * - New vs. recurring finding classification
6
+ * - "Fixed" detection when a finding disappears
7
+ * - Finding trend statistics (are things getting better or worse?)
8
+ * - Age tracking (how long has a finding been open?)
9
+ *
10
+ * Data stored in .judges-findings.json
11
+ */
12
+ import type { Finding, Severity } from "./types.js";
13
+ export interface TrackedFinding {
14
+ /** Stable fingerprint based on ruleId + file + code context */
15
+ fingerprint: string;
16
+ /** Rule that generated this finding */
17
+ ruleId: string;
18
+ /** Finding severity */
19
+ severity: Severity;
20
+ /** File where the finding was detected */
21
+ filePath: string;
22
+ /** Finding title for display */
23
+ title: string;
24
+ /** First seen timestamp */
25
+ firstSeen: string;
26
+ /** Last seen timestamp */
27
+ lastSeen: string;
28
+ /** Number of consecutive runs this finding has appeared */
29
+ runCount: number;
30
+ /** Whether this finding is currently active */
31
+ status: "open" | "fixed";
32
+ /** When the finding was resolved */
33
+ fixedAt?: string;
34
+ }
35
+ export interface FindingStore {
36
+ version: string;
37
+ lastRunAt: string;
38
+ runNumber: number;
39
+ findings: TrackedFinding[];
40
+ }
41
+ export interface FindingDelta {
42
+ /** Findings that appeared for the first time in this run */
43
+ introduced: TrackedFinding[];
44
+ /** Findings that are still present from previous runs */
45
+ recurring: TrackedFinding[];
46
+ /** Findings from previous runs that are no longer present */
47
+ fixed: TrackedFinding[];
48
+ /** Summary statistics */
49
+ stats: {
50
+ totalOpen: number;
51
+ totalFixed: number;
52
+ introduced: number;
53
+ recurring: number;
54
+ fixed: number;
55
+ trend: "improving" | "stable" | "degrading";
56
+ };
57
+ }
58
+ /**
59
+ * Generate a stable fingerprint for a finding that survives minor code edits.
60
+ * Based on ruleId + file path + approximate code location.
61
+ */
62
+ export declare function generateFindingFingerprint(finding: Finding, filePath: string): string;
63
+ export declare function loadFindingStore(dir?: string): FindingStore;
64
+ export declare function saveFindingStore(store: FindingStore, dir?: string): void;
65
+ /**
66
+ * Update the finding store with results from a new evaluation run.
67
+ * Returns a delta describing what changed since the last run.
68
+ *
69
+ * When `dirOrStore` is a string, reads/writes `.judges-findings.json` in that
70
+ * directory. When a `FindingStore` object is passed directly, operates
71
+ * in-memory (useful for testing) and does NOT persist to disk.
72
+ */
73
+ export declare function updateFindings(currentFindings: Array<{
74
+ finding: Finding;
75
+ filePath: string;
76
+ }>, dirOrStore?: string | FindingStore): FindingDelta;
77
+ /**
78
+ * Get summary statistics from the finding store.
79
+ */
80
+ export declare function getFindingStats(dirOrStore?: string | FindingStore): {
81
+ totalOpen: number;
82
+ totalFixed: number;
83
+ oldestOpen: string | undefined;
84
+ bySeverity: Record<string, number>;
85
+ byRule: Record<string, number>;
86
+ avgAge: number;
87
+ runCount: number;
88
+ };
89
+ /**
90
+ * Format finding delta as a human-readable summary for CLI output.
91
+ */
92
+ export declare function formatDelta(delta: FindingDelta): string;
93
+ //# sourceMappingURL=finding-lifecycle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"finding-lifecycle.d.ts","sourceRoot":"","sources":["../src/finding-lifecycle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAIpD,MAAM,WAAW,cAAc;IAC7B,+DAA+D;IAC/D,WAAW,EAAE,MAAM,CAAC;IACpB,uCAAuC;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,uBAAuB;IACvB,QAAQ,EAAE,QAAQ,CAAC;IACnB,0CAA0C;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,gCAAgC;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,2BAA2B;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,0BAA0B;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,2DAA2D;IAC3D,QAAQ,EAAE,MAAM,CAAC;IACjB,+CAA+C;IAC/C,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;IACzB,oCAAoC;IACpC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,cAAc,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,YAAY;IAC3B,4DAA4D;IAC5D,UAAU,EAAE,cAAc,EAAE,CAAC;IAC7B,yDAAyD;IACzD,SAAS,EAAE,cAAc,EAAE,CAAC;IAC5B,6DAA6D;IAC7D,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,yBAAyB;IACzB,KAAK,EAAE;QACL,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,EAAE,MAAM,CAAC;QACnB,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,WAAW,GAAG,QAAQ,GAAG,WAAW,CAAC;KAC7C,CAAC;CACH;AAID;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAOrF;AAMD,wBAAgB,gBAAgB,CAAC,GAAG,GAAE,MAAY,GAAG,YAAY,CAoBhE;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,GAAE,MAAY,GAAG,IAAI,CAG7E;AAID;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAC5B,eAAe,EAAE,KAAK,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,EAC9D,UAAU,GAAE,MAAM,GAAG,YAAkB,GACtC,YAAY,CAmGd;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,UAAU,GAAE,MAAM,GAAG,YAAkB,GAAG;IACxE,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB,CA6BA;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CAgCvD"}
@@ -0,0 +1,214 @@
1
+ /**
2
+ * Finding Lifecycle Tracking
3
+ *
4
+ * Tracks individual findings across multiple evaluation runs, enabling:
5
+ * - New vs. recurring finding classification
6
+ * - "Fixed" detection when a finding disappears
7
+ * - Finding trend statistics (are things getting better or worse?)
8
+ * - Age tracking (how long has a finding been open?)
9
+ *
10
+ * Data stored in .judges-findings.json
11
+ */
12
+ import { readFileSync, writeFileSync, existsSync } from "fs";
13
+ import { resolve } from "path";
14
+ // ─── Fingerprinting ─────────────────────────────────────────────────────────
15
+ /**
16
+ * Generate a stable fingerprint for a finding that survives minor code edits.
17
+ * Based on ruleId + file path + approximate code location.
18
+ */
19
+ export function generateFindingFingerprint(finding, filePath) {
20
+ // Use ruleId + file + line range bucket (groups of 5 lines to handle minor shifts)
21
+ const lineBucket = finding.lineNumbers?.[0] ? Math.floor(finding.lineNumbers[0] / 5) * 5 : 0;
22
+ // Simple hash-like string for fast comparison
23
+ const key = `${finding.ruleId}::${filePath}::${lineBucket}::${finding.title.slice(0, 50)}`;
24
+ return key;
25
+ }
26
+ // ─── Store I/O ───────────────────────────────────────────────────────────────
27
+ const FINDINGS_FILE = ".judges-findings.json";
28
+ export function loadFindingStore(dir = ".") {
29
+ const filePath = resolve(dir, FINDINGS_FILE);
30
+ if (!existsSync(filePath)) {
31
+ return {
32
+ version: "1.0.0",
33
+ lastRunAt: new Date().toISOString(),
34
+ runNumber: 0,
35
+ findings: [],
36
+ };
37
+ }
38
+ try {
39
+ return JSON.parse(readFileSync(filePath, "utf-8"));
40
+ }
41
+ catch {
42
+ return {
43
+ version: "1.0.0",
44
+ lastRunAt: new Date().toISOString(),
45
+ runNumber: 0,
46
+ findings: [],
47
+ };
48
+ }
49
+ }
50
+ export function saveFindingStore(store, dir = ".") {
51
+ const filePath = resolve(dir, FINDINGS_FILE);
52
+ writeFileSync(filePath, JSON.stringify(store, null, 2) + "\n", "utf-8");
53
+ }
54
+ // ─── Lifecycle Operations ────────────────────────────────────────────────────
55
+ /**
56
+ * Update the finding store with results from a new evaluation run.
57
+ * Returns a delta describing what changed since the last run.
58
+ *
59
+ * When `dirOrStore` is a string, reads/writes `.judges-findings.json` in that
60
+ * directory. When a `FindingStore` object is passed directly, operates
61
+ * in-memory (useful for testing) and does NOT persist to disk.
62
+ */
63
+ export function updateFindings(currentFindings, dirOrStore = ".") {
64
+ const inMemory = typeof dirOrStore !== "string";
65
+ const store = inMemory ? dirOrStore : loadFindingStore(dirOrStore);
66
+ const now = new Date().toISOString();
67
+ store.runNumber++;
68
+ store.lastRunAt = now;
69
+ // Build fingerprint → finding map for current run
70
+ const currentMap = new Map();
71
+ for (const entry of currentFindings) {
72
+ const fp = generateFindingFingerprint(entry.finding, entry.filePath);
73
+ currentMap.set(fp, entry);
74
+ }
75
+ // Build fingerprint → tracked finding map for existing store
76
+ const existingMap = new Map();
77
+ for (const tracked of store.findings) {
78
+ existingMap.set(tracked.fingerprint, tracked);
79
+ }
80
+ const delta = {
81
+ introduced: [],
82
+ recurring: [],
83
+ fixed: [],
84
+ stats: {
85
+ totalOpen: 0,
86
+ totalFixed: 0,
87
+ introduced: 0,
88
+ recurring: 0,
89
+ fixed: 0,
90
+ trend: "stable",
91
+ },
92
+ };
93
+ // Process current findings
94
+ for (const [fp, entry] of currentMap) {
95
+ const existing = existingMap.get(fp);
96
+ if (existing) {
97
+ // Recurring — update last seen and run count
98
+ existing.lastSeen = now;
99
+ existing.runCount++;
100
+ existing.status = "open";
101
+ existing.fixedAt = undefined;
102
+ delta.recurring.push(existing);
103
+ }
104
+ else {
105
+ // New finding
106
+ const tracked = {
107
+ fingerprint: fp,
108
+ ruleId: entry.finding.ruleId,
109
+ severity: entry.finding.severity,
110
+ filePath: entry.filePath,
111
+ title: entry.finding.title,
112
+ firstSeen: now,
113
+ lastSeen: now,
114
+ runCount: 1,
115
+ status: "open",
116
+ };
117
+ store.findings.push(tracked);
118
+ delta.introduced.push(tracked);
119
+ }
120
+ }
121
+ // Detect fixed findings (were open, no longer present)
122
+ for (const tracked of store.findings) {
123
+ if (tracked.status === "open" && !currentMap.has(tracked.fingerprint)) {
124
+ tracked.status = "fixed";
125
+ tracked.fixedAt = now;
126
+ delta.fixed.push(tracked);
127
+ }
128
+ }
129
+ // Compute stats
130
+ const openFindings = store.findings.filter((f) => f.status === "open");
131
+ const fixedFindings = store.findings.filter((f) => f.status === "fixed");
132
+ delta.stats.totalOpen = openFindings.length;
133
+ delta.stats.totalFixed = fixedFindings.length;
134
+ delta.stats.introduced = delta.introduced.length;
135
+ delta.stats.recurring = delta.recurring.length;
136
+ delta.stats.fixed = delta.fixed.length;
137
+ // Determine trend
138
+ if (delta.fixed.length > delta.introduced.length) {
139
+ delta.stats.trend = "improving";
140
+ }
141
+ else if (delta.introduced.length > delta.fixed.length) {
142
+ delta.stats.trend = "degrading";
143
+ }
144
+ else {
145
+ delta.stats.trend = "stable";
146
+ }
147
+ // Prune very old fixed findings (> 30 days)
148
+ const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString();
149
+ store.findings = store.findings.filter((f) => f.status === "open" || (f.fixedAt && f.fixedAt > thirtyDaysAgo));
150
+ if (!inMemory) {
151
+ saveFindingStore(store, dirOrStore);
152
+ }
153
+ return delta;
154
+ }
155
+ /**
156
+ * Get summary statistics from the finding store.
157
+ */
158
+ export function getFindingStats(dirOrStore = ".") {
159
+ const store = typeof dirOrStore === "string" ? loadFindingStore(dirOrStore) : dirOrStore;
160
+ const openFindings = store.findings.filter((f) => f.status === "open");
161
+ const fixedFindings = store.findings.filter((f) => f.status === "fixed");
162
+ const bySeverity = {};
163
+ const byRule = {};
164
+ let totalAgeDays = 0;
165
+ for (const f of openFindings) {
166
+ bySeverity[f.severity] = (bySeverity[f.severity] || 0) + 1;
167
+ byRule[f.ruleId] = (byRule[f.ruleId] || 0) + 1;
168
+ const age = (Date.now() - new Date(f.firstSeen).getTime()) / (1000 * 60 * 60 * 24);
169
+ totalAgeDays += age;
170
+ }
171
+ const sortedOpen = [...openFindings].sort((a, b) => new Date(a.firstSeen).getTime() - new Date(b.firstSeen).getTime());
172
+ return {
173
+ totalOpen: openFindings.length,
174
+ totalFixed: fixedFindings.length,
175
+ oldestOpen: sortedOpen[0]?.firstSeen,
176
+ bySeverity,
177
+ byRule,
178
+ avgAge: openFindings.length > 0 ? totalAgeDays / openFindings.length : 0,
179
+ runCount: store.runNumber,
180
+ };
181
+ }
182
+ /**
183
+ * Format finding delta as a human-readable summary for CLI output.
184
+ */
185
+ export function formatDelta(delta) {
186
+ const trendEmoji = delta.stats.trend === "improving" ? "📈" : delta.stats.trend === "degrading" ? "📉" : "➡️";
187
+ const lines = [
188
+ ` Finding Lifecycle: ${trendEmoji} ${delta.stats.trend}`,
189
+ ` Open: ${delta.stats.totalOpen} | Fixed: ${delta.stats.totalFixed}`,
190
+ ];
191
+ if (delta.stats.introduced > 0) {
192
+ lines.push(` 🆕 New: ${delta.stats.introduced}`);
193
+ for (const f of delta.introduced.slice(0, 5)) {
194
+ lines.push(` + [${f.severity.toUpperCase()}] ${f.ruleId}: ${f.title}`);
195
+ }
196
+ if (delta.introduced.length > 5) {
197
+ lines.push(` ... and ${delta.introduced.length - 5} more`);
198
+ }
199
+ }
200
+ if (delta.stats.fixed > 0) {
201
+ lines.push(` ✅ Fixed: ${delta.stats.fixed}`);
202
+ for (const f of delta.fixed.slice(0, 5)) {
203
+ lines.push(` - [${f.severity.toUpperCase()}] ${f.ruleId}: ${f.title}`);
204
+ }
205
+ if (delta.fixed.length > 5) {
206
+ lines.push(` ... and ${delta.fixed.length - 5} more`);
207
+ }
208
+ }
209
+ if (delta.stats.recurring > 0) {
210
+ lines.push(` 🔄 Recurring: ${delta.stats.recurring}`);
211
+ }
212
+ return lines.join("\n");
213
+ }
214
+ //# sourceMappingURL=finding-lifecycle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"finding-lifecycle.js","sourceRoot":"","sources":["../src/finding-lifecycle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAC7D,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAqD/B,+EAA+E;AAE/E;;;GAGG;AACH,MAAM,UAAU,0BAA0B,CAAC,OAAgB,EAAE,QAAgB;IAC3E,mFAAmF;IACnF,MAAM,UAAU,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAE7F,8CAA8C;IAC9C,MAAM,GAAG,GAAG,GAAG,OAAO,CAAC,MAAM,KAAK,QAAQ,KAAK,UAAU,KAAK,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC;IAC3F,OAAO,GAAG,CAAC;AACb,CAAC;AAED,gFAAgF;AAEhF,MAAM,aAAa,GAAG,uBAAuB,CAAC;AAE9C,MAAM,UAAU,gBAAgB,CAAC,MAAc,GAAG;IAChD,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,EAAE,aAAa,CAAC,CAAC;IAC7C,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC1B,OAAO;YACL,OAAO,EAAE,OAAO;YAChB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,SAAS,EAAE,CAAC;YACZ,QAAQ,EAAE,EAAE;SACb,CAAC;IACJ,CAAC;IACD,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC;IACrD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO;YACL,OAAO,EAAE,OAAO;YAChB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,SAAS,EAAE,CAAC;YACZ,QAAQ,EAAE,EAAE;SACb,CAAC;IACJ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,KAAmB,EAAE,MAAc,GAAG;IACrE,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,EAAE,aAAa,CAAC,CAAC;IAC7C,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,OAAO,CAAC,CAAC;AAC1E,CAAC;AAED,gFAAgF;AAEhF;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAC5B,eAA8D,EAC9D,aAAoC,GAAG;IAEvC,MAAM,QAAQ,GAAG,OAAO,UAAU,KAAK,QAAQ,CAAC;IAChD,MAAM,KAAK,GAAG,QAAQ,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,gBAAgB,CAAC,UAAU,CAAC,CAAC;IACnE,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACrC,KAAK,CAAC,SAAS,EAAE,CAAC;IAClB,KAAK,CAAC,SAAS,GAAG,GAAG,CAAC;IAEtB,kDAAkD;IAClD,MAAM,UAAU,GAAG,IAAI,GAAG,EAAkD,CAAC;IAC7E,KAAK,MAAM,KAAK,IAAI,eAAe,EAAE,CAAC;QACpC,MAAM,EAAE,GAAG,0BAA0B,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;QACrE,UAAU,CAAC,GAAG,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;IAC5B,CAAC;IAED,6DAA6D;IAC7D,MAAM,WAAW,GAAG,IAAI,GAAG,EAA0B,CAAC;IACtD,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;QACrC,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;IAChD,CAAC;IAED,MAAM,KAAK,GAAiB;QAC1B,UAAU,EAAE,EAAE;QACd,SAAS,EAAE,EAAE;QACb,KAAK,EAAE,EAAE;QACT,KAAK,EAAE;YACL,SAAS,EAAE,CAAC;YACZ,UAAU,EAAE,CAAC;YACb,UAAU,EAAE,CAAC;YACb,SAAS,EAAE,CAAC;YACZ,KAAK,EAAE,CAAC;YACR,KAAK,EAAE,QAAQ;SAChB;KACF,CAAC;IAEF,2BAA2B;IAC3B,KAAK,MAAM,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,UAAU,EAAE,CAAC;QACrC,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAErC,IAAI,QAAQ,EAAE,CAAC;YACb,6CAA6C;YAC7C,QAAQ,CAAC,QAAQ,GAAG,GAAG,CAAC;YACxB,QAAQ,CAAC,QAAQ,EAAE,CAAC;YACpB,QAAQ,CAAC,MAAM,GAAG,MAAM,CAAC;YACzB,QAAQ,CAAC,OAAO,GAAG,SAAS,CAAC;YAC7B,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACjC,CAAC;aAAM,CAAC;YACN,cAAc;YACd,MAAM,OAAO,GAAmB;gBAC9B,WAAW,EAAE,EAAE;gBACf,MAAM,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM;gBAC5B,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,QAAQ;gBAChC,QAAQ,EAAE,KAAK,CAAC,QAAQ;gBACxB,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK;gBAC1B,SAAS,EAAE,GAAG;gBACd,QAAQ,EAAE,GAAG;gBACb,QAAQ,EAAE,CAAC;gBACX,MAAM,EAAE,MAAM;aACf,CAAC;YACF,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAC7B,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACjC,CAAC;IACH,CAAC;IAED,uDAAuD;IACvD,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;QACrC,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;YACtE,OAAO,CAAC,MAAM,GAAG,OAAO,CAAC;YACzB,OAAO,CAAC,OAAO,GAAG,GAAG,CAAC;YACtB,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC;IAED,gBAAgB;IAChB,MAAM,YAAY,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC;IACvE,MAAM,aAAa,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC;IAEzE,KAAK,CAAC,KAAK,CAAC,SAAS,GAAG,YAAY,CAAC,MAAM,CAAC;IAC5C,KAAK,CAAC,KAAK,CAAC,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC;IAC9C,KAAK,CAAC,KAAK,CAAC,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC;IACjD,KAAK,CAAC,KAAK,CAAC,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC;IAC/C,KAAK,CAAC,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC;IAEvC,kBAAkB;IAClB,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC;QACjD,KAAK,CAAC,KAAK,CAAC,KAAK,GAAG,WAAW,CAAC;IAClC,CAAC;SAAM,IAAI,KAAK,CAAC,UAAU,CAAC,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;QACxD,KAAK,CAAC,KAAK,CAAC,KAAK,GAAG,WAAW,CAAC;IAClC,CAAC;SAAM,CAAC;QACN,KAAK,CAAC,KAAK,CAAC,KAAK,GAAG,QAAQ,CAAC;IAC/B,CAAC;IAED,4CAA4C;IAC5C,MAAM,aAAa,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;IACpF,KAAK,CAAC,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,IAAI,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,OAAO,GAAG,aAAa,CAAC,CAAC,CAAC;IAE/G,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,gBAAgB,CAAC,KAAK,EAAE,UAAoB,CAAC,CAAC;IAChD,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe,CAAC,aAAoC,GAAG;IASrE,MAAM,KAAK,GAAG,OAAO,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,gBAAgB,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC;IACzF,MAAM,YAAY,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC;IACvE,MAAM,aAAa,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC;IAEzE,MAAM,UAAU,GAA2B,EAAE,CAAC;IAC9C,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,IAAI,YAAY,GAAG,CAAC,CAAC;IAErB,KAAK,MAAM,CAAC,IAAI,YAAY,EAAE,CAAC;QAC7B,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;QAC3D,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;QAC/C,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,IAAI,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;QACnF,YAAY,IAAI,GAAG,CAAC;IACtB,CAAC;IAED,MAAM,UAAU,GAAG,CAAC,GAAG,YAAY,CAAC,CAAC,IAAI,CACvC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAC5E,CAAC;IAEF,OAAO;QACL,SAAS,EAAE,YAAY,CAAC,MAAM;QAC9B,UAAU,EAAE,aAAa,CAAC,MAAM;QAChC,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,SAAS;QACpC,UAAU;QACV,MAAM;QACN,MAAM,EAAE,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACxE,QAAQ,EAAE,KAAK,CAAC,SAAS;KAC1B,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,WAAW,CAAC,KAAmB;IAC7C,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,KAAK,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,KAAK,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;IAC9G,MAAM,KAAK,GAAG;QACZ,wBAAwB,UAAU,IAAI,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE;QACzD,WAAW,KAAK,CAAC,KAAK,CAAC,SAAS,aAAa,KAAK,CAAC,KAAK,CAAC,UAAU,EAAE;KACtE,CAAC;IAEF,IAAI,KAAK,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,EAAE,CAAC;QAC/B,KAAK,CAAC,IAAI,CAAC,aAAa,KAAK,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC;QAClD,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;YAC7C,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,QAAQ,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;QAC7E,CAAC;QACD,IAAI,KAAK,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAChC,KAAK,CAAC,IAAI,CAAC,gBAAgB,KAAK,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,OAAO,CAAC,CAAC;QACjE,CAAC;IACH,CAAC;IAED,IAAI,KAAK,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,EAAE,CAAC;QAC1B,KAAK,CAAC,IAAI,CAAC,cAAc,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC;QAC9C,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;YACxC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,QAAQ,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;QAC7E,CAAC;QACD,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC3B,KAAK,CAAC,IAAI,CAAC,gBAAgB,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,OAAO,CAAC,CAAC;QAC5D,CAAC;IACH,CAAC;IAED,IAAI,KAAK,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,EAAE,CAAC;QAC9B,KAAK,CAAC,IAAI,CAAC,mBAAmB,KAAK,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC,CAAC;IACzD,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/patches/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AA6tC3C,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,EAAE,CAyC9E"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/patches/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAu1C3C,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,EAAE,CAyC9E"}
@@ -710,6 +710,133 @@ const PATCH_RULES = [
710
710
  return { oldText: m[0], newText: `${m[1]}// SAFETY: TODO: document why unsafe is needed\n${m[1]}unsafe {` };
711
711
  },
712
712
  },
713
+ // Rust: panic!() in library → return Result comment
714
+ {
715
+ match: /panic.*library|panic.*production|avoid.*panic/i,
716
+ generate: (line) => {
717
+ const m = line.match(/panic!\s*\(\s*(["'][^"']*["'])\s*\)/);
718
+ if (!m)
719
+ return null;
720
+ return { oldText: m[0], newText: `return Err(anyhow::anyhow!(${m[1]})) /* TODO: replace panic with Result */` };
721
+ },
722
+ },
723
+ // Rust: .clone() hint → borrow comment
724
+ {
725
+ match: /unnecessary.*clone|avoid.*clone|excessive.*clone/i,
726
+ generate: (line) => {
727
+ const m = line.match(/(\w+)\.clone\(\)/);
728
+ if (!m)
729
+ return null;
730
+ return { oldText: m[0], newText: `${m[1]}.clone() /* TODO: consider borrowing &${m[1]} instead */` };
731
+ },
732
+ },
733
+ // ── Additional Python Patches ──
734
+ // Python: eval() → ast.literal_eval()
735
+ {
736
+ match: /dangerous.*eval|python.*eval|eval.*usage/i,
737
+ generate: (line) => {
738
+ const m = line.match(/\beval\s*\(\s*(\w+)\s*\)/);
739
+ if (!m)
740
+ return null;
741
+ // Only match if it looks like Python (no 'new Function' around it)
742
+ if (line.includes("new Function") || line.includes("Function("))
743
+ return null;
744
+ return { oldText: m[0], newText: `ast.literal_eval(${m[1]})` };
745
+ },
746
+ },
747
+ // Python: requests without verify → verify=True
748
+ {
749
+ match: /ssl.*verif.*disabled|tls.*verif.*disabled|certificate.*verif/i,
750
+ generate: (line) => {
751
+ const m = line.match(/(requests\.(?:get|post|put|delete|patch)\s*\([^)]*?)verify\s*=\s*False/);
752
+ if (!m)
753
+ return null;
754
+ return { oldText: "verify=False", newText: "verify=True" };
755
+ },
756
+ },
757
+ // Python: subprocess with shell=True → shell=False
758
+ {
759
+ match: /shell.*true|subprocess.*shell|shell.*inject/i,
760
+ generate: (line) => {
761
+ const m = line.match(/(subprocess\.(?:run|call|check_call|check_output|Popen)\s*\([^)]*?)shell\s*=\s*True/);
762
+ if (!m)
763
+ return null;
764
+ return { oldText: "shell=True", newText: "shell=False" };
765
+ },
766
+ },
767
+ // Python: open() without encoding → add encoding
768
+ {
769
+ match: /missing.*encoding|file.*encoding|open.*without.*encoding/i,
770
+ generate: (line) => {
771
+ const m = line.match(/(open\s*\(\s*\w+\s*,\s*["'][rw](?:t)?["'])\s*\)/);
772
+ if (!m)
773
+ return null;
774
+ return { oldText: m[0], newText: `${m[1]}, encoding="utf-8")` };
775
+ },
776
+ },
777
+ // ── Additional Go Patches ──
778
+ // Go: log.Fatal in HTTP handler → http.Error
779
+ {
780
+ match: /log\.fatal.*handler|fatal.*http.*handler|log.*fatal.*request/i,
781
+ generate: (line) => {
782
+ const m = line.match(/log\.Fatal(?:f|ln)?\s*\(([^)]*)\)/);
783
+ if (!m)
784
+ return null;
785
+ return { oldText: m[0], newText: `http.Error(w, ${m[1]}, http.StatusInternalServerError)` };
786
+ },
787
+ },
788
+ // Go: defer file.Close() without error check → named return
789
+ {
790
+ match: /defer.*close.*error|close.*without.*check|resource.*leak/i,
791
+ generate: (line) => {
792
+ const m = line.match(/(defer\s+\w+\.Close)\s*\(\s*\)/);
793
+ if (!m)
794
+ return null;
795
+ return { oldText: m[0], newText: `${m[1]}() // TODO: check Close() error in named return` };
796
+ },
797
+ },
798
+ // ── Additional Java Patches ──
799
+ // Java: System.out.println → Logger
800
+ {
801
+ match: /system\.out|console.*output|print.*instead.*log/i,
802
+ generate: (line) => {
803
+ const m = line.match(/System\.out\.println\s*\(([^)]*)\)/);
804
+ if (!m)
805
+ return null;
806
+ return { oldText: m[0], newText: `logger.info(${m[1]})` };
807
+ },
808
+ },
809
+ // Java: String concatenation in SQL → PreparedStatement marker
810
+ {
811
+ match: /sql.*concatenat|string.*concat.*sql|sql.*inject/i,
812
+ generate: (line) => {
813
+ const m = line.match(/(Statement\s*\w+\s*=\s*\w+\.createStatement)\s*\(\s*\)/);
814
+ if (!m)
815
+ return null;
816
+ return { oldText: m[0], newText: `/* TODO: use PreparedStatement instead */ ${m[0]}` };
817
+ },
818
+ },
819
+ // ── Additional C# Patches ──
820
+ // C#: string.Format/interpolation in SQL → parameterized
821
+ {
822
+ match: /sql.*inject|string.*interpol.*sql|sql.*concat/i,
823
+ generate: (line) => {
824
+ const m = line.match(/ExecuteSqlRaw\s*\(\s*\$/);
825
+ if (!m)
826
+ return null;
827
+ return { oldText: "ExecuteSqlRaw($", newText: "ExecuteSqlInterpolated($" };
828
+ },
829
+ },
830
+ // C#: Console.WriteLine → ILogger
831
+ {
832
+ match: /console.*writeline.*log|console.*instead.*logger/i,
833
+ generate: (line) => {
834
+ const m = line.match(/Console\.WriteLine\s*\(([^)]*)\)/);
835
+ if (!m)
836
+ return null;
837
+ return { oldText: m[0], newText: `_logger.LogInformation(${m[1]})` };
838
+ },
839
+ },
713
840
  // ── Hardcoded Secrets ──
714
841
  // Hardcoded password/secret → environment variable
715
842
  {