@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.
- package/CHANGELOG.md +27 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +37 -2
- package/dist/cli.js.map +1 -1
- package/dist/commands/benchmark.d.ts.map +1 -1
- package/dist/commands/benchmark.js +556 -0
- package/dist/commands/benchmark.js.map +1 -1
- package/dist/commands/diff.d.ts.map +1 -1
- package/dist/commands/diff.js +17 -1
- package/dist/commands/diff.js.map +1 -1
- package/dist/commands/review.d.ts +37 -0
- package/dist/commands/review.d.ts.map +1 -0
- package/dist/commands/review.js +539 -0
- package/dist/commands/review.js.map +1 -0
- package/dist/commands/tune.d.ts +25 -0
- package/dist/commands/tune.d.ts.map +1 -0
- package/dist/commands/tune.js +408 -0
- package/dist/commands/tune.js.map +1 -0
- package/dist/evaluators/accessibility.d.ts.map +1 -1
- package/dist/evaluators/accessibility.js +5 -1
- package/dist/evaluators/accessibility.js.map +1 -1
- package/dist/evaluators/ai-code-safety.d.ts.map +1 -1
- package/dist/evaluators/ai-code-safety.js +6 -1
- package/dist/evaluators/ai-code-safety.js.map +1 -1
- package/dist/evaluators/authentication.d.ts.map +1 -1
- package/dist/evaluators/authentication.js +5 -1
- package/dist/evaluators/authentication.js.map +1 -1
- package/dist/evaluators/ci-cd.d.ts.map +1 -1
- package/dist/evaluators/ci-cd.js +6 -2
- package/dist/evaluators/ci-cd.js.map +1 -1
- package/dist/evaluators/cloud-readiness.d.ts.map +1 -1
- package/dist/evaluators/cloud-readiness.js +12 -3
- package/dist/evaluators/cloud-readiness.js.map +1 -1
- package/dist/evaluators/compliance.d.ts.map +1 -1
- package/dist/evaluators/compliance.js +5 -1
- package/dist/evaluators/compliance.js.map +1 -1
- package/dist/evaluators/configuration-management.d.ts.map +1 -1
- package/dist/evaluators/configuration-management.js +5 -1
- package/dist/evaluators/configuration-management.js.map +1 -1
- package/dist/evaluators/cost-effectiveness.d.ts.map +1 -1
- package/dist/evaluators/cost-effectiveness.js +5 -3
- package/dist/evaluators/cost-effectiveness.js.map +1 -1
- package/dist/evaluators/cybersecurity.d.ts.map +1 -1
- package/dist/evaluators/cybersecurity.js +5 -1
- package/dist/evaluators/cybersecurity.js.map +1 -1
- package/dist/evaluators/data-security.d.ts.map +1 -1
- package/dist/evaluators/data-security.js +12 -1
- package/dist/evaluators/data-security.js.map +1 -1
- package/dist/evaluators/data-sovereignty.d.ts.map +1 -1
- package/dist/evaluators/data-sovereignty.js +5 -1
- package/dist/evaluators/data-sovereignty.js.map +1 -1
- package/dist/evaluators/database.d.ts.map +1 -1
- package/dist/evaluators/database.js +11 -1
- package/dist/evaluators/database.js.map +1 -1
- package/dist/evaluators/error-handling.d.ts.map +1 -1
- package/dist/evaluators/error-handling.js +2 -2
- package/dist/evaluators/error-handling.js.map +1 -1
- package/dist/evaluators/ethics-bias.d.ts.map +1 -1
- package/dist/evaluators/ethics-bias.js +5 -1
- package/dist/evaluators/ethics-bias.js.map +1 -1
- package/dist/evaluators/false-positive-review.js +4 -4
- package/dist/evaluators/false-positive-review.js.map +1 -1
- package/dist/evaluators/iac-security.d.ts.map +1 -1
- package/dist/evaluators/iac-security.js +14 -2
- package/dist/evaluators/iac-security.js.map +1 -1
- package/dist/evaluators/internationalization.d.ts.map +1 -1
- package/dist/evaluators/internationalization.js +5 -1
- package/dist/evaluators/internationalization.js.map +1 -1
- package/dist/evaluators/logging-privacy.d.ts.map +1 -1
- package/dist/evaluators/logging-privacy.js +8 -4
- package/dist/evaluators/logging-privacy.js.map +1 -1
- package/dist/evaluators/maintainability.d.ts.map +1 -1
- package/dist/evaluators/maintainability.js +37 -28
- package/dist/evaluators/maintainability.js.map +1 -1
- package/dist/evaluators/observability.js +2 -2
- package/dist/evaluators/observability.js.map +1 -1
- package/dist/evaluators/performance.d.ts.map +1 -1
- package/dist/evaluators/performance.js +9 -5
- package/dist/evaluators/performance.js.map +1 -1
- package/dist/evaluators/portability.d.ts.map +1 -1
- package/dist/evaluators/portability.js +33 -11
- package/dist/evaluators/portability.js.map +1 -1
- package/dist/evaluators/reliability.js +2 -2
- package/dist/evaluators/reliability.js.map +1 -1
- package/dist/evaluators/scalability.d.ts.map +1 -1
- package/dist/evaluators/scalability.js +5 -3
- package/dist/evaluators/scalability.js.map +1 -1
- package/dist/evaluators/shared.d.ts +34 -0
- package/dist/evaluators/shared.d.ts.map +1 -1
- package/dist/evaluators/shared.js +60 -0
- package/dist/evaluators/shared.js.map +1 -1
- package/dist/evaluators/software-practices.js +2 -2
- package/dist/evaluators/software-practices.js.map +1 -1
- package/dist/finding-lifecycle.d.ts +93 -0
- package/dist/finding-lifecycle.d.ts.map +1 -0
- package/dist/finding-lifecycle.js +214 -0
- package/dist/finding-lifecycle.js.map +1 -0
- package/dist/patches/index.d.ts.map +1 -1
- package/dist/patches/index.js +127 -0
- package/dist/patches/index.js.map +1 -1
- package/dist/presets.d.ts.map +1 -1
- package/dist/presets.js +103 -0
- package/dist/presets.js.map +1 -1
- package/package.json +1 -1
- 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":"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"}
|
|
@@ -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;
|
|
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"}
|
package/dist/patches/index.js
CHANGED
|
@@ -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
|
{
|