@kevinrabun/judges 3.48.0 → 3.49.0
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 +12 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +56 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/incident-response.d.ts +8 -0
- package/dist/commands/incident-response.d.ts.map +1 -0
- package/dist/commands/incident-response.js +255 -0
- package/dist/commands/incident-response.js.map +1 -0
- package/dist/commands/learning-path.d.ts +9 -0
- package/dist/commands/learning-path.d.ts.map +1 -0
- package/dist/commands/learning-path.js +326 -0
- package/dist/commands/learning-path.js.map +1 -0
- package/dist/commands/license-scan.d.ts +9 -0
- package/dist/commands/license-scan.d.ts.map +1 -0
- package/dist/commands/license-scan.js +180 -0
- package/dist/commands/license-scan.js.map +1 -0
- package/dist/commands/org-policy.d.ts +8 -0
- package/dist/commands/org-policy.d.ts.map +1 -0
- package/dist/commands/org-policy.js +208 -0
- package/dist/commands/org-policy.js.map +1 -0
- package/dist/commands/predict.d.ts +8 -0
- package/dist/commands/predict.d.ts.map +1 -0
- package/dist/commands/predict.js +219 -0
- package/dist/commands/predict.js.map +1 -0
- package/dist/commands/risk-heatmap.d.ts +8 -0
- package/dist/commands/risk-heatmap.d.ts.map +1 -0
- package/dist/commands/risk-heatmap.js +224 -0
- package/dist/commands/risk-heatmap.js.map +1 -0
- package/dist/commands/sbom-export.d.ts +8 -0
- package/dist/commands/sbom-export.d.ts.map +1 -0
- package/dist/commands/sbom-export.js +162 -0
- package/dist/commands/sbom-export.js.map +1 -0
- package/dist/commands/test-correlate.d.ts +8 -0
- package/dist/commands/test-correlate.d.ts.map +1 -0
- package/dist/commands/test-correlate.js +222 -0
- package/dist/commands/test-correlate.js.map +1 -0
- package/package.json +1 -1
- package/server.json +2 -2
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* License scan — scans project dependencies for license
|
|
3
|
+
* compatibility, flags copyleft/unknown licenses, and
|
|
4
|
+
* generates a license obligations report.
|
|
5
|
+
*
|
|
6
|
+
* All data from local files.
|
|
7
|
+
*/
|
|
8
|
+
import { existsSync, readFileSync, mkdirSync, writeFileSync } from "fs";
|
|
9
|
+
import { join } from "path";
|
|
10
|
+
// ─── License DB ─────────────────────────────────────────────────────────────
|
|
11
|
+
const LICENSE_CATEGORIES = {
|
|
12
|
+
MIT: { category: "permissive", risk: "low" },
|
|
13
|
+
ISC: { category: "permissive", risk: "low" },
|
|
14
|
+
"BSD-2-Clause": { category: "permissive", risk: "low" },
|
|
15
|
+
"BSD-3-Clause": { category: "permissive", risk: "low" },
|
|
16
|
+
"Apache-2.0": { category: "permissive", risk: "low" },
|
|
17
|
+
Unlicense: { category: "permissive", risk: "low" },
|
|
18
|
+
"0BSD": { category: "permissive", risk: "low" },
|
|
19
|
+
"CC0-1.0": { category: "permissive", risk: "low" },
|
|
20
|
+
Zlib: { category: "permissive", risk: "low" },
|
|
21
|
+
"GPL-2.0": { category: "copyleft", risk: "high" },
|
|
22
|
+
"GPL-3.0": { category: "copyleft", risk: "high" },
|
|
23
|
+
"AGPL-3.0": { category: "copyleft", risk: "high" },
|
|
24
|
+
"GPL-2.0-only": { category: "copyleft", risk: "high" },
|
|
25
|
+
"GPL-3.0-only": { category: "copyleft", risk: "high" },
|
|
26
|
+
"AGPL-3.0-only": { category: "copyleft", risk: "high" },
|
|
27
|
+
"LGPL-2.1": { category: "weak-copyleft", risk: "medium" },
|
|
28
|
+
"LGPL-3.0": { category: "weak-copyleft", risk: "medium" },
|
|
29
|
+
"MPL-2.0": { category: "weak-copyleft", risk: "medium" },
|
|
30
|
+
"EPL-1.0": { category: "weak-copyleft", risk: "medium" },
|
|
31
|
+
"EPL-2.0": { category: "weak-copyleft", risk: "medium" },
|
|
32
|
+
"CDDL-1.0": { category: "weak-copyleft", risk: "medium" },
|
|
33
|
+
};
|
|
34
|
+
function classifyLicense(license) {
|
|
35
|
+
const normalized = license.trim().replace(/\s+/g, "-");
|
|
36
|
+
return LICENSE_CATEGORIES[normalized] || { category: "unknown", risk: "high" };
|
|
37
|
+
}
|
|
38
|
+
// ─── Scanning ───────────────────────────────────────────────────────────────
|
|
39
|
+
function scanNpmLicenses() {
|
|
40
|
+
const results = [];
|
|
41
|
+
// Try node_modules approach
|
|
42
|
+
if (existsSync("package.json")) {
|
|
43
|
+
try {
|
|
44
|
+
const pkg = JSON.parse(readFileSync("package.json", "utf-8"));
|
|
45
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
46
|
+
for (const [name, ver] of Object.entries(allDeps)) {
|
|
47
|
+
let license = "UNKNOWN";
|
|
48
|
+
// Check node_modules for the package's package.json
|
|
49
|
+
const depPkgPath = join("node_modules", name, "package.json");
|
|
50
|
+
if (existsSync(depPkgPath)) {
|
|
51
|
+
try {
|
|
52
|
+
const depPkg = JSON.parse(readFileSync(depPkgPath, "utf-8"));
|
|
53
|
+
if (typeof depPkg.license === "string") {
|
|
54
|
+
license = depPkg.license;
|
|
55
|
+
}
|
|
56
|
+
else if (depPkg.license?.type) {
|
|
57
|
+
license = depPkg.license.type;
|
|
58
|
+
}
|
|
59
|
+
else if (Array.isArray(depPkg.licenses)) {
|
|
60
|
+
license = depPkg.licenses.map((l) => l.type || "UNKNOWN").join(" OR ");
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
/* skip */
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const { category, risk } = classifyLicense(license);
|
|
68
|
+
results.push({
|
|
69
|
+
dependency: name,
|
|
70
|
+
version: String(ver).replace(/^[\^~>=<]+/, ""),
|
|
71
|
+
license,
|
|
72
|
+
category,
|
|
73
|
+
risk,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
/* skip */
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return results;
|
|
82
|
+
}
|
|
83
|
+
function detectConflicts(licenses) {
|
|
84
|
+
const conflicts = [];
|
|
85
|
+
const hasCopyleft = licenses.some((l) => l.category === "copyleft");
|
|
86
|
+
const hasProprietary = licenses.some((l) => l.category === "proprietary");
|
|
87
|
+
if (hasCopyleft && hasProprietary) {
|
|
88
|
+
conflicts.push("Copyleft and proprietary licenses detected — may be incompatible");
|
|
89
|
+
}
|
|
90
|
+
const agpl = licenses.filter((l) => l.license.includes("AGPL"));
|
|
91
|
+
if (agpl.length > 0) {
|
|
92
|
+
conflicts.push(`AGPL license detected in: ${agpl.map((l) => l.dependency).join(", ")} — requires source disclosure for network use`);
|
|
93
|
+
}
|
|
94
|
+
const unknown = licenses.filter((l) => l.category === "unknown");
|
|
95
|
+
if (unknown.length > 0) {
|
|
96
|
+
conflicts.push(`Unknown licenses in: ${unknown.map((l) => l.dependency).join(", ")} — review manually`);
|
|
97
|
+
}
|
|
98
|
+
return conflicts;
|
|
99
|
+
}
|
|
100
|
+
// ─── CLI ────────────────────────────────────────────────────────────────────
|
|
101
|
+
const STORE = ".judges-licenses";
|
|
102
|
+
export function runLicenseScan(argv) {
|
|
103
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
104
|
+
console.log(`
|
|
105
|
+
judges license-scan — Dependency license compliance scanning
|
|
106
|
+
|
|
107
|
+
Usage:
|
|
108
|
+
judges license-scan
|
|
109
|
+
judges license-scan --risk high
|
|
110
|
+
judges license-scan --category copyleft
|
|
111
|
+
judges license-scan --save
|
|
112
|
+
|
|
113
|
+
Options:
|
|
114
|
+
--risk <level> Filter by risk level (low, medium, high)
|
|
115
|
+
--category <cat> Filter by category (permissive, copyleft, weak-copyleft, proprietary, unknown)
|
|
116
|
+
--save Save report to ${STORE}/
|
|
117
|
+
--format json JSON output
|
|
118
|
+
--help, -h Show this help
|
|
119
|
+
`);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const format = argv.find((_a, i) => argv[i - 1] === "--format") || "text";
|
|
123
|
+
let licenses = scanNpmLicenses();
|
|
124
|
+
if (licenses.length === 0) {
|
|
125
|
+
console.log(" No dependencies found. Run from a project root with package.json.");
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
// Filters
|
|
129
|
+
const riskFilter = argv.find((_a, i) => argv[i - 1] === "--risk");
|
|
130
|
+
if (riskFilter)
|
|
131
|
+
licenses = licenses.filter((l) => l.risk === riskFilter);
|
|
132
|
+
const catFilter = argv.find((_a, i) => argv[i - 1] === "--category");
|
|
133
|
+
if (catFilter)
|
|
134
|
+
licenses = licenses.filter((l) => l.category === catFilter);
|
|
135
|
+
const conflicts = detectConflicts(licenses);
|
|
136
|
+
const report = {
|
|
137
|
+
licenses,
|
|
138
|
+
summary: {
|
|
139
|
+
permissive: licenses.filter((l) => l.category === "permissive").length,
|
|
140
|
+
copyleft: licenses.filter((l) => l.category === "copyleft").length,
|
|
141
|
+
weakCopyleft: licenses.filter((l) => l.category === "weak-copyleft").length,
|
|
142
|
+
proprietary: licenses.filter((l) => l.category === "proprietary").length,
|
|
143
|
+
unknown: licenses.filter((l) => l.category === "unknown").length,
|
|
144
|
+
},
|
|
145
|
+
conflicts,
|
|
146
|
+
timestamp: new Date().toISOString(),
|
|
147
|
+
};
|
|
148
|
+
if (argv.includes("--save")) {
|
|
149
|
+
if (!existsSync(STORE))
|
|
150
|
+
mkdirSync(STORE, { recursive: true });
|
|
151
|
+
writeFileSync(join(STORE, "license-report.json"), JSON.stringify(report, null, 2));
|
|
152
|
+
console.log(` Report saved to ${STORE}/license-report.json`);
|
|
153
|
+
}
|
|
154
|
+
if (format === "json") {
|
|
155
|
+
console.log(JSON.stringify(report, null, 2));
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
console.log(`\n License Scan — ${licenses.length} dependencies`);
|
|
159
|
+
console.log(` ──────────────────────────`);
|
|
160
|
+
console.log(` Permissive: ${report.summary.permissive}`);
|
|
161
|
+
console.log(` Weak-copyleft: ${report.summary.weakCopyleft}`);
|
|
162
|
+
console.log(` Copyleft: ${report.summary.copyleft}`);
|
|
163
|
+
console.log(` Unknown: ${report.summary.unknown}`);
|
|
164
|
+
if (conflicts.length > 0) {
|
|
165
|
+
console.log(`\n ⚠️ Conflicts:`);
|
|
166
|
+
for (const c of conflicts)
|
|
167
|
+
console.log(` ${c}`);
|
|
168
|
+
}
|
|
169
|
+
// Show high-risk
|
|
170
|
+
const highRisk = licenses.filter((l) => l.risk === "high");
|
|
171
|
+
if (highRisk.length > 0) {
|
|
172
|
+
console.log(`\n High Risk (${highRisk.length}):`);
|
|
173
|
+
for (const l of highRisk) {
|
|
174
|
+
console.log(` ${l.dependency.padEnd(30)} ${l.license.padEnd(15)} ${l.category}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
console.log("");
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
//# sourceMappingURL=license-scan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"license-scan.js","sourceRoot":"","sources":["../../src/commands/license-scan.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Org policy — defines, validates, and enforces organization-wide
|
|
3
|
+
* policy manifests that cascade into per-repo .judgesrc files.
|
|
4
|
+
*
|
|
5
|
+
* All data stored locally.
|
|
6
|
+
*/
|
|
7
|
+
export declare function runOrgPolicy(argv: string[]): void;
|
|
8
|
+
//# sourceMappingURL=org-policy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"org-policy.d.ts","sourceRoot":"","sources":["../../src/commands/org-policy.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAqHH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAiIjD"}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Org policy — defines, validates, and enforces organization-wide
|
|
3
|
+
* policy manifests that cascade into per-repo .judgesrc files.
|
|
4
|
+
*
|
|
5
|
+
* All data stored locally.
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, readFileSync, mkdirSync, writeFileSync } from "fs";
|
|
8
|
+
import { join } from "path";
|
|
9
|
+
// ─── Default Policy ─────────────────────────────────────────────────────────
|
|
10
|
+
const DEFAULT_POLICY = {
|
|
11
|
+
name: "default",
|
|
12
|
+
version: "1.0.0",
|
|
13
|
+
minSeverity: "medium",
|
|
14
|
+
requiredJudges: [],
|
|
15
|
+
bannedRules: [],
|
|
16
|
+
maxSuppressionsPerRepo: 50,
|
|
17
|
+
requiredPreset: "",
|
|
18
|
+
enforcedOptions: {},
|
|
19
|
+
lastUpdated: new Date().toISOString(),
|
|
20
|
+
};
|
|
21
|
+
// ─── Compliance Check ───────────────────────────────────────────────────────
|
|
22
|
+
function checkCompliance(policy) {
|
|
23
|
+
const violations = [];
|
|
24
|
+
const warnings = [];
|
|
25
|
+
// Check .judgesrc exists
|
|
26
|
+
if (!existsSync(".judgesrc")) {
|
|
27
|
+
violations.push("Missing .judgesrc configuration file");
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
try {
|
|
31
|
+
const config = JSON.parse(readFileSync(".judgesrc", "utf-8"));
|
|
32
|
+
// Check min severity
|
|
33
|
+
if (policy.minSeverity) {
|
|
34
|
+
const sevOrder = ["critical", "high", "medium", "low", "info"];
|
|
35
|
+
const policyIdx = sevOrder.indexOf(policy.minSeverity);
|
|
36
|
+
const configIdx = sevOrder.indexOf(config.minSeverity || "low");
|
|
37
|
+
if (configIdx > policyIdx) {
|
|
38
|
+
violations.push(`minSeverity '${config.minSeverity || "low"}' is less strict than policy requirement '${policy.minSeverity}'`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// Check required judges
|
|
42
|
+
const disabledJudges = config.disabledJudges || [];
|
|
43
|
+
for (const required of policy.requiredJudges) {
|
|
44
|
+
if (disabledJudges.includes(required)) {
|
|
45
|
+
violations.push(`Required judge '${required}' is disabled in .judgesrc`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// Check banned rules
|
|
49
|
+
const enabledRules = config.ruleOverrides ? Object.keys(config.ruleOverrides) : [];
|
|
50
|
+
for (const banned of policy.bannedRules) {
|
|
51
|
+
if (enabledRules.includes(banned)) {
|
|
52
|
+
warnings.push(`Banned rule '${banned}' has overrides in .judgesrc`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// Check preset
|
|
56
|
+
if (policy.requiredPreset && config.preset !== policy.requiredPreset) {
|
|
57
|
+
violations.push(`Required preset '${policy.requiredPreset}' not configured (found: '${config.preset || "none"}')`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
violations.push("Invalid .judgesrc — cannot parse JSON");
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// Check suppressions count
|
|
65
|
+
if (existsSync(".judges-suppressions.json")) {
|
|
66
|
+
try {
|
|
67
|
+
const supps = JSON.parse(readFileSync(".judges-suppressions.json", "utf-8"));
|
|
68
|
+
const count = Array.isArray(supps) ? supps.length : supps.suppressions?.length || 0;
|
|
69
|
+
if (count > policy.maxSuppressionsPerRepo) {
|
|
70
|
+
violations.push(`Suppressions count (${count}) exceeds policy limit (${policy.maxSuppressionsPerRepo})`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
/* skip */
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
compliant: violations.length === 0,
|
|
79
|
+
violations,
|
|
80
|
+
warnings,
|
|
81
|
+
checkedAt: new Date().toISOString(),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
// ─── CLI ────────────────────────────────────────────────────────────────────
|
|
85
|
+
const STORE = ".judges-org-policy";
|
|
86
|
+
export function runOrgPolicy(argv) {
|
|
87
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
88
|
+
console.log(`
|
|
89
|
+
judges org-policy — Organization-wide policy management
|
|
90
|
+
|
|
91
|
+
Usage:
|
|
92
|
+
judges org-policy --init
|
|
93
|
+
judges org-policy --check
|
|
94
|
+
judges org-policy --show
|
|
95
|
+
judges org-policy --set-required-judges "owasp-judge,crypto-judge"
|
|
96
|
+
judges org-policy --set-min-severity medium
|
|
97
|
+
|
|
98
|
+
Options:
|
|
99
|
+
--init Create default org policy file
|
|
100
|
+
--check Check repo compliance against org policy
|
|
101
|
+
--show Show current org policy
|
|
102
|
+
--set-required-judges Comma-separated list of required judge IDs
|
|
103
|
+
--set-banned-rules Comma-separated list of banned rule IDs
|
|
104
|
+
--set-min-severity Minimum severity level
|
|
105
|
+
--set-preset Required preset name
|
|
106
|
+
--set-max-suppressions Maximum allowed suppressions per repo
|
|
107
|
+
--format json JSON output
|
|
108
|
+
--help, -h Show this help
|
|
109
|
+
`);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const format = argv.find((_a, i) => argv[i - 1] === "--format") || "text";
|
|
113
|
+
const policyPath = join(STORE, "org-policy.json");
|
|
114
|
+
// Init
|
|
115
|
+
if (argv.includes("--init")) {
|
|
116
|
+
if (!existsSync(STORE))
|
|
117
|
+
mkdirSync(STORE, { recursive: true });
|
|
118
|
+
if (existsSync(policyPath)) {
|
|
119
|
+
console.log(" Org policy already exists. Edit directly or use --set-* options.");
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
writeFileSync(policyPath, JSON.stringify(DEFAULT_POLICY, null, 2));
|
|
123
|
+
console.log(` Initialized org policy at ${policyPath}`);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
// Load policy
|
|
127
|
+
let policy;
|
|
128
|
+
if (existsSync(policyPath)) {
|
|
129
|
+
policy = JSON.parse(readFileSync(policyPath, "utf-8"));
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
if (!argv.includes("--init")) {
|
|
133
|
+
console.log(" No org policy found. Run --init to create one.");
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
policy = DEFAULT_POLICY;
|
|
137
|
+
}
|
|
138
|
+
// Set options
|
|
139
|
+
let modified = false;
|
|
140
|
+
const reqJudges = argv.find((_a, i) => argv[i - 1] === "--set-required-judges");
|
|
141
|
+
if (reqJudges) {
|
|
142
|
+
policy.requiredJudges = reqJudges.split(",");
|
|
143
|
+
modified = true;
|
|
144
|
+
}
|
|
145
|
+
const bannedRules = argv.find((_a, i) => argv[i - 1] === "--set-banned-rules");
|
|
146
|
+
if (bannedRules) {
|
|
147
|
+
policy.bannedRules = bannedRules.split(",");
|
|
148
|
+
modified = true;
|
|
149
|
+
}
|
|
150
|
+
const minSev = argv.find((_a, i) => argv[i - 1] === "--set-min-severity");
|
|
151
|
+
if (minSev) {
|
|
152
|
+
policy.minSeverity = minSev;
|
|
153
|
+
modified = true;
|
|
154
|
+
}
|
|
155
|
+
const preset = argv.find((_a, i) => argv[i - 1] === "--set-preset");
|
|
156
|
+
if (preset) {
|
|
157
|
+
policy.requiredPreset = preset;
|
|
158
|
+
modified = true;
|
|
159
|
+
}
|
|
160
|
+
const maxSupp = argv.find((_a, i) => argv[i - 1] === "--set-max-suppressions");
|
|
161
|
+
if (maxSupp) {
|
|
162
|
+
policy.maxSuppressionsPerRepo = parseInt(maxSupp, 10);
|
|
163
|
+
modified = true;
|
|
164
|
+
}
|
|
165
|
+
if (modified) {
|
|
166
|
+
policy.lastUpdated = new Date().toISOString();
|
|
167
|
+
if (!existsSync(STORE))
|
|
168
|
+
mkdirSync(STORE, { recursive: true });
|
|
169
|
+
writeFileSync(policyPath, JSON.stringify(policy, null, 2));
|
|
170
|
+
console.log(" Org policy updated.");
|
|
171
|
+
}
|
|
172
|
+
// Check compliance
|
|
173
|
+
if (argv.includes("--check")) {
|
|
174
|
+
const result = checkCompliance(policy);
|
|
175
|
+
if (format === "json") {
|
|
176
|
+
console.log(JSON.stringify(result, null, 2));
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
console.log(`\n Org Policy Compliance\n ──────────────────────────`);
|
|
180
|
+
console.log(` Status: ${result.compliant ? "✅ Compliant" : "❌ Non-compliant"}\n`);
|
|
181
|
+
for (const v of result.violations)
|
|
182
|
+
console.log(` ❌ ${v}`);
|
|
183
|
+
for (const w of result.warnings)
|
|
184
|
+
console.log(` ⚠️ ${w}`);
|
|
185
|
+
if (result.compliant && result.warnings.length === 0)
|
|
186
|
+
console.log(` All checks passed`);
|
|
187
|
+
console.log("");
|
|
188
|
+
}
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
// Show
|
|
192
|
+
if (argv.includes("--show") || !modified) {
|
|
193
|
+
if (format === "json") {
|
|
194
|
+
console.log(JSON.stringify(policy, null, 2));
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
console.log(`\n Org Policy: ${policy.name} v${policy.version}`);
|
|
198
|
+
console.log(` ──────────────────────────`);
|
|
199
|
+
console.log(` Min severity: ${policy.minSeverity}`);
|
|
200
|
+
console.log(` Required preset: ${policy.requiredPreset || "(none)"}`);
|
|
201
|
+
console.log(` Required judges: ${policy.requiredJudges.length > 0 ? policy.requiredJudges.join(", ") : "(none)"}`);
|
|
202
|
+
console.log(` Banned rules: ${policy.bannedRules.length > 0 ? policy.bannedRules.join(", ") : "(none)"}`);
|
|
203
|
+
console.log(` Max suppressions: ${policy.maxSuppressionsPerRepo}`);
|
|
204
|
+
console.log(` Last updated: ${policy.lastUpdated}\n`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
//# sourceMappingURL=org-policy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"org-policy.js","sourceRoot":"","sources":["../../src/commands/org-policy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Predict — applies trend analysis to finding snapshots to
|
|
3
|
+
* forecast remediation timelines and regression-prone files.
|
|
4
|
+
*
|
|
5
|
+
* All data from local snapshot history.
|
|
6
|
+
*/
|
|
7
|
+
export declare function runPredict(argv: string[]): void;
|
|
8
|
+
//# sourceMappingURL=predict.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"predict.d.ts","sourceRoot":"","sources":["../../src/commands/predict.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAuJH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAyG/C"}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Predict — applies trend analysis to finding snapshots to
|
|
3
|
+
* forecast remediation timelines and regression-prone files.
|
|
4
|
+
*
|
|
5
|
+
* All data from local snapshot history.
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, readFileSync, mkdirSync, writeFileSync } from "fs";
|
|
8
|
+
import { join } from "path";
|
|
9
|
+
// ─── Analysis ───────────────────────────────────────────────────────────────
|
|
10
|
+
function loadSnapshots() {
|
|
11
|
+
const paths = [
|
|
12
|
+
join(".judges-snapshots", "history.json"),
|
|
13
|
+
".judges-snapshots.json",
|
|
14
|
+
join(".judges-burndown", "snapshots.json"),
|
|
15
|
+
];
|
|
16
|
+
for (const p of paths) {
|
|
17
|
+
if (!existsSync(p))
|
|
18
|
+
continue;
|
|
19
|
+
try {
|
|
20
|
+
const data = JSON.parse(readFileSync(p, "utf-8"));
|
|
21
|
+
if (Array.isArray(data))
|
|
22
|
+
return data;
|
|
23
|
+
if (data.snapshots)
|
|
24
|
+
return data.snapshots;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
/* skip */
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
function linearRegression(points) {
|
|
33
|
+
const n = points.length;
|
|
34
|
+
if (n < 2)
|
|
35
|
+
return { slope: 0, intercept: points[0]?.y || 0, r2: 0 };
|
|
36
|
+
const sumX = points.reduce((s, p) => s + p.x, 0);
|
|
37
|
+
const sumY = points.reduce((s, p) => s + p.y, 0);
|
|
38
|
+
const sumXY = points.reduce((s, p) => s + p.x * p.y, 0);
|
|
39
|
+
const sumX2 = points.reduce((s, p) => s + p.x * p.x, 0);
|
|
40
|
+
const sumY2 = points.reduce((s, p) => s + p.y * p.y, 0);
|
|
41
|
+
const denom = n * sumX2 - sumX * sumX;
|
|
42
|
+
if (denom === 0)
|
|
43
|
+
return { slope: 0, intercept: sumY / n, r2: 0 };
|
|
44
|
+
const slope = (n * sumXY - sumX * sumY) / denom;
|
|
45
|
+
const intercept = (sumY - slope * sumX) / n;
|
|
46
|
+
// R²
|
|
47
|
+
const yMean = sumY / n;
|
|
48
|
+
const ssTot = sumY2 - n * yMean * yMean;
|
|
49
|
+
const ssRes = points.reduce((s, p) => s + Math.pow(p.y - (slope * p.x + intercept), 2), 0);
|
|
50
|
+
const r2 = ssTot === 0 ? 0 : Math.max(0, 1 - ssRes / ssTot);
|
|
51
|
+
return { slope, intercept, r2 };
|
|
52
|
+
}
|
|
53
|
+
function predictMetric(snapshots, field) {
|
|
54
|
+
if (snapshots.length < 2) {
|
|
55
|
+
return {
|
|
56
|
+
metric: field,
|
|
57
|
+
currentValue: snapshots[0]?.[field] || 0,
|
|
58
|
+
trend: "stable",
|
|
59
|
+
ratePerDay: 0,
|
|
60
|
+
estimatedZeroDate: null,
|
|
61
|
+
confidence: 0,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
const t0 = new Date(snapshots[0].timestamp).getTime();
|
|
65
|
+
const points = snapshots.map((s) => ({
|
|
66
|
+
x: (new Date(s.timestamp).getTime() - t0) / (1000 * 60 * 60 * 24), // days
|
|
67
|
+
y: s[field],
|
|
68
|
+
}));
|
|
69
|
+
const { slope, r2 } = linearRegression(points);
|
|
70
|
+
const current = points[points.length - 1].y;
|
|
71
|
+
let trend = "stable";
|
|
72
|
+
if (slope < -0.1)
|
|
73
|
+
trend = "decreasing";
|
|
74
|
+
else if (slope > 0.1)
|
|
75
|
+
trend = "increasing";
|
|
76
|
+
let estimatedZeroDate = null;
|
|
77
|
+
if (slope < 0 && current > 0) {
|
|
78
|
+
const daysToZero = -current / slope;
|
|
79
|
+
const zeroDate = new Date(Date.now() + daysToZero * 24 * 60 * 60 * 1000);
|
|
80
|
+
estimatedZeroDate = zeroDate.toISOString().split("T")[0];
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
metric: field,
|
|
84
|
+
currentValue: current,
|
|
85
|
+
trend,
|
|
86
|
+
ratePerDay: Math.round(slope * 100) / 100,
|
|
87
|
+
estimatedZeroDate,
|
|
88
|
+
confidence: Math.round(r2 * 100),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function loadRegressionData() {
|
|
92
|
+
const paths = [".judges-regressions.json", join(".judges-regression-alert", "history.json")];
|
|
93
|
+
for (const p of paths) {
|
|
94
|
+
if (!existsSync(p))
|
|
95
|
+
continue;
|
|
96
|
+
try {
|
|
97
|
+
const data = JSON.parse(readFileSync(p, "utf-8"));
|
|
98
|
+
const files = Array.isArray(data) ? data : data.regressions || [];
|
|
99
|
+
const counts = new Map();
|
|
100
|
+
for (const r of files) {
|
|
101
|
+
const file = r.file || r.path || "unknown";
|
|
102
|
+
counts.set(file, (counts.get(file) || 0) + 1);
|
|
103
|
+
}
|
|
104
|
+
return [...counts.entries()]
|
|
105
|
+
.map(([file, count]) => ({
|
|
106
|
+
file,
|
|
107
|
+
regressionCount: count,
|
|
108
|
+
risk: count > 3 ? "high" : count > 1 ? "medium" : "low",
|
|
109
|
+
}))
|
|
110
|
+
.sort((a, b) => b.regressionCount - a.regressionCount);
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
/* skip */
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
118
|
+
// ─── CLI ────────────────────────────────────────────────────────────────────
|
|
119
|
+
const STORE = ".judges-predictions";
|
|
120
|
+
export function runPredict(argv) {
|
|
121
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
122
|
+
console.log(`
|
|
123
|
+
judges predict — Forecast remediation timelines and regression risk
|
|
124
|
+
|
|
125
|
+
Usage:
|
|
126
|
+
judges predict
|
|
127
|
+
judges predict --metric critical
|
|
128
|
+
judges predict --regressions
|
|
129
|
+
judges predict --save
|
|
130
|
+
|
|
131
|
+
Options:
|
|
132
|
+
--metric <name> Predict specific metric (findings, critical, high, medium, low)
|
|
133
|
+
--regressions Show regression-prone files prediction
|
|
134
|
+
--save Save predictions to ${STORE}/
|
|
135
|
+
--format json JSON output
|
|
136
|
+
--help, -h Show this help
|
|
137
|
+
`);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const format = argv.find((_a, i) => argv[i - 1] === "--format") || "text";
|
|
141
|
+
const snapshots = loadSnapshots();
|
|
142
|
+
if (snapshots.length < 2 && !argv.includes("--regressions")) {
|
|
143
|
+
console.log(" Need at least 2 snapshots for predictions.");
|
|
144
|
+
console.log(" Run scans over time and they'll be recorded automatically.");
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
// Single metric
|
|
148
|
+
const metricName = argv.find((_a, i) => argv[i - 1] === "--metric");
|
|
149
|
+
if (metricName) {
|
|
150
|
+
const pred = predictMetric(snapshots, metricName);
|
|
151
|
+
if (format === "json") {
|
|
152
|
+
console.log(JSON.stringify(pred, null, 2));
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
console.log(`\n Prediction: ${pred.metric}\n ──────────────────────────`);
|
|
156
|
+
console.log(` Current: ${pred.currentValue}`);
|
|
157
|
+
console.log(` Trend: ${pred.trend} (${pred.ratePerDay >= 0 ? "+" : ""}${pred.ratePerDay}/day)`);
|
|
158
|
+
console.log(` Confidence: ${pred.confidence}%`);
|
|
159
|
+
if (pred.estimatedZeroDate)
|
|
160
|
+
console.log(` Estimated zero: ${pred.estimatedZeroDate}`);
|
|
161
|
+
console.log("");
|
|
162
|
+
}
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
// Regressions
|
|
166
|
+
if (argv.includes("--regressions")) {
|
|
167
|
+
const regressions = loadRegressionData();
|
|
168
|
+
if (format === "json") {
|
|
169
|
+
console.log(JSON.stringify(regressions, null, 2));
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
console.log(`\n Regression-Prone Files\n ──────────────────────────`);
|
|
173
|
+
if (regressions.length === 0) {
|
|
174
|
+
console.log(` No regression data found.\n`);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
for (const r of regressions.slice(0, 15)) {
|
|
178
|
+
console.log(` [${r.risk.toUpperCase().padEnd(6)}] ${r.file} (${r.regressionCount} regressions)`);
|
|
179
|
+
}
|
|
180
|
+
console.log("");
|
|
181
|
+
}
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
// Full prediction
|
|
185
|
+
const metrics = ["findings", "critical", "high", "medium", "low"];
|
|
186
|
+
const predictions = metrics.map((m) => predictMetric(snapshots, m));
|
|
187
|
+
const regressions = loadRegressionData();
|
|
188
|
+
const report = {
|
|
189
|
+
predictions,
|
|
190
|
+
regressionRisk: regressions.slice(0, 10),
|
|
191
|
+
timestamp: new Date().toISOString(),
|
|
192
|
+
};
|
|
193
|
+
if (argv.includes("--save")) {
|
|
194
|
+
if (!existsSync(STORE))
|
|
195
|
+
mkdirSync(STORE, { recursive: true });
|
|
196
|
+
writeFileSync(join(STORE, "prediction-report.json"), JSON.stringify(report, null, 2));
|
|
197
|
+
console.log(` Saved to ${STORE}/prediction-report.json`);
|
|
198
|
+
}
|
|
199
|
+
if (format === "json") {
|
|
200
|
+
console.log(JSON.stringify(report, null, 2));
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
console.log(`\n Prediction Report (${snapshots.length} snapshots)`);
|
|
204
|
+
console.log(` ──────────────────────────`);
|
|
205
|
+
for (const p of predictions) {
|
|
206
|
+
const arrow = p.trend === "decreasing" ? "↓" : p.trend === "increasing" ? "↑" : "→";
|
|
207
|
+
const zero = p.estimatedZeroDate ? ` → zero by ${p.estimatedZeroDate}` : "";
|
|
208
|
+
console.log(` ${p.metric.padEnd(12)} ${String(p.currentValue).padEnd(6)} ${arrow} ${p.ratePerDay >= 0 ? "+" : ""}${p.ratePerDay}/day (${p.confidence}% conf)${zero}`);
|
|
209
|
+
}
|
|
210
|
+
if (regressions.length > 0) {
|
|
211
|
+
console.log(`\n Regression-Prone Files:`);
|
|
212
|
+
for (const r of regressions.slice(0, 5)) {
|
|
213
|
+
console.log(` [${r.risk.toUpperCase().padEnd(6)}] ${r.file}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
console.log("");
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
//# sourceMappingURL=predict.js.map
|