@kevinrabun/judges 3.40.0 → 3.42.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 +39 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +133 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/auto-calibrate.d.ts +15 -0
- package/dist/commands/auto-calibrate.d.ts.map +1 -0
- package/dist/commands/auto-calibrate.js +107 -0
- package/dist/commands/auto-calibrate.js.map +1 -0
- package/dist/commands/auto-triage.d.ts +32 -0
- package/dist/commands/auto-triage.d.ts.map +1 -0
- package/dist/commands/auto-triage.js +126 -0
- package/dist/commands/auto-triage.js.map +1 -0
- package/dist/commands/config-migrate.d.ts +44 -0
- package/dist/commands/config-migrate.d.ts.map +1 -0
- package/dist/commands/config-migrate.js +241 -0
- package/dist/commands/config-migrate.js.map +1 -0
- package/dist/commands/coverage-map.d.ts +23 -0
- package/dist/commands/coverage-map.d.ts.map +1 -0
- package/dist/commands/coverage-map.js +223 -0
- package/dist/commands/coverage-map.js.map +1 -0
- package/dist/commands/dedup-report.d.ts +13 -0
- package/dist/commands/dedup-report.d.ts.map +1 -0
- package/dist/commands/dedup-report.js +138 -0
- package/dist/commands/dedup-report.js.map +1 -0
- package/dist/commands/dep-audit.d.ts +53 -0
- package/dist/commands/dep-audit.d.ts.map +1 -0
- package/dist/commands/dep-audit.js +278 -0
- package/dist/commands/dep-audit.js.map +1 -0
- package/dist/commands/deprecated.d.ts +48 -0
- package/dist/commands/deprecated.d.ts.map +1 -0
- package/dist/commands/deprecated.js +202 -0
- package/dist/commands/deprecated.js.map +1 -0
- package/dist/commands/diff-only.d.ts +34 -0
- package/dist/commands/diff-only.d.ts.map +1 -0
- package/dist/commands/diff-only.js +152 -0
- package/dist/commands/diff-only.js.map +1 -0
- package/dist/commands/fix-pr.d.ts +23 -0
- package/dist/commands/fix-pr.d.ts.map +1 -0
- package/dist/commands/fix-pr.js +323 -0
- package/dist/commands/fix-pr.js.map +1 -0
- package/dist/commands/group-findings.d.ts +23 -0
- package/dist/commands/group-findings.d.ts.map +1 -0
- package/dist/commands/group-findings.js +155 -0
- package/dist/commands/group-findings.js.map +1 -0
- package/dist/commands/interactive-fix.d.ts +23 -0
- package/dist/commands/interactive-fix.d.ts.map +1 -0
- package/dist/commands/interactive-fix.js +140 -0
- package/dist/commands/interactive-fix.js.map +1 -0
- package/dist/commands/monorepo.d.ts +38 -0
- package/dist/commands/monorepo.d.ts.map +1 -0
- package/dist/commands/monorepo.js +233 -0
- package/dist/commands/monorepo.js.map +1 -0
- package/dist/commands/notify.d.ts +79 -0
- package/dist/commands/notify.d.ts.map +1 -0
- package/dist/commands/notify.js +325 -0
- package/dist/commands/notify.js.map +1 -0
- package/dist/commands/pr-summary.d.ts +26 -0
- package/dist/commands/pr-summary.d.ts.map +1 -0
- package/dist/commands/pr-summary.js +188 -0
- package/dist/commands/pr-summary.js.map +1 -0
- package/dist/commands/profile.d.ts +38 -0
- package/dist/commands/profile.d.ts.map +1 -0
- package/dist/commands/profile.js +102 -0
- package/dist/commands/profile.js.map +1 -0
- package/dist/commands/quality-gate.d.ts +70 -0
- package/dist/commands/quality-gate.d.ts.map +1 -0
- package/dist/commands/quality-gate.js +264 -0
- package/dist/commands/quality-gate.js.map +1 -0
- package/dist/commands/smart-select.d.ts +27 -0
- package/dist/commands/smart-select.d.ts.map +1 -0
- package/dist/commands/smart-select.js +346 -0
- package/dist/commands/smart-select.js.map +1 -0
- package/dist/commands/upload.d.ts +14 -0
- package/dist/commands/upload.d.ts.map +1 -0
- package/dist/commands/upload.js +173 -0
- package/dist/commands/upload.js.map +1 -0
- package/dist/commands/validate-config.d.ts +17 -0
- package/dist/commands/validate-config.d.ts.map +1 -0
- package/dist/commands/validate-config.js +268 -0
- package/dist/commands/validate-config.js.map +1 -0
- package/dist/commands/warm-cache.d.ts +31 -0
- package/dist/commands/warm-cache.d.ts.map +1 -0
- package/dist/commands/warm-cache.js +166 -0
- package/dist/commands/warm-cache.js.map +1 -0
- package/dist/evaluators/framework-rules.d.ts +59 -0
- package/dist/evaluators/framework-rules.d.ts.map +1 -0
- package/dist/evaluators/framework-rules.js +292 -0
- package/dist/evaluators/framework-rules.js.map +1 -0
- package/dist/parallel.d.ts +53 -0
- package/dist/parallel.d.ts.map +1 -0
- package/dist/parallel.js +170 -0
- package/dist/parallel.js.map +1 -0
- package/package.json +1 -1
- package/server.json +2 -2
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `judges notify` — Webhook notification system for findings alerts.
|
|
3
|
+
*
|
|
4
|
+
* Sends evaluation results to configured webhook endpoints (Slack, Teams,
|
|
5
|
+
* generic HTTP). Users configure their own endpoints — Judges never stores
|
|
6
|
+
* or processes data on behalf of users.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* judges notify --file results.json --channel slack # Send to Slack
|
|
10
|
+
* judges notify --file results.json --channel teams # Send to Teams
|
|
11
|
+
* judges notify --file results.json --channel webhook # Generic webhook
|
|
12
|
+
* judges eval src/app.ts --notify # Evaluate + notify
|
|
13
|
+
*
|
|
14
|
+
* Configuration in .judgesrc:
|
|
15
|
+
* ```json
|
|
16
|
+
* {
|
|
17
|
+
* "notifications": {
|
|
18
|
+
* "channels": [
|
|
19
|
+
* { "type": "slack", "url": "https://hooks.slack.com/..." },
|
|
20
|
+
* { "type": "teams", "url": "https://outlook.office.com/webhook/..." },
|
|
21
|
+
* { "type": "webhook", "url": "https://my-server.com/judges-hook",
|
|
22
|
+
* "headers": { "Authorization": "Bearer ..." } }
|
|
23
|
+
* ],
|
|
24
|
+
* "minSeverity": "medium",
|
|
25
|
+
* "onlyOnFailure": false
|
|
26
|
+
* }
|
|
27
|
+
* }
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
import type { Finding, Severity, TribunalVerdict } from "../types.js";
|
|
31
|
+
export type NotificationChannelType = "slack" | "teams" | "webhook";
|
|
32
|
+
export interface NotificationChannel {
|
|
33
|
+
/** Channel type */
|
|
34
|
+
type: NotificationChannelType;
|
|
35
|
+
/** Webhook URL — provided and hosted by the user */
|
|
36
|
+
url: string;
|
|
37
|
+
/** Optional custom headers (e.g. auth tokens) */
|
|
38
|
+
headers?: Record<string, string>;
|
|
39
|
+
/** Optional display name for this channel */
|
|
40
|
+
name?: string;
|
|
41
|
+
}
|
|
42
|
+
export interface NotificationConfig {
|
|
43
|
+
/** Channels to send notifications to */
|
|
44
|
+
channels: NotificationChannel[];
|
|
45
|
+
/** Only notify for findings at or above this severity */
|
|
46
|
+
minSeverity?: Severity;
|
|
47
|
+
/** Only send when the verdict is "fail" */
|
|
48
|
+
onlyOnFailure?: boolean;
|
|
49
|
+
}
|
|
50
|
+
export interface NotificationPayload {
|
|
51
|
+
/** Project or file being evaluated */
|
|
52
|
+
target: string;
|
|
53
|
+
/** Overall verdict */
|
|
54
|
+
verdict: "pass" | "fail" | "warning";
|
|
55
|
+
/** Aggregate score (0-10) */
|
|
56
|
+
score: number;
|
|
57
|
+
/** Summary counts by severity */
|
|
58
|
+
summary: Record<Severity, number>;
|
|
59
|
+
/** Total finding count */
|
|
60
|
+
totalFindings: number;
|
|
61
|
+
/** Top findings (limited to 10 for brevity) */
|
|
62
|
+
topFindings: Array<{
|
|
63
|
+
ruleId: string;
|
|
64
|
+
severity: Severity;
|
|
65
|
+
title: string;
|
|
66
|
+
line?: number;
|
|
67
|
+
}>;
|
|
68
|
+
/** Timestamp of evaluation */
|
|
69
|
+
timestamp: string;
|
|
70
|
+
}
|
|
71
|
+
export declare function buildNotificationPayload(target: string, verdict: TribunalVerdict, filteredFindings: Finding[]): NotificationPayload;
|
|
72
|
+
export declare function sendNotification(channel: NotificationChannel, payload: NotificationPayload): Promise<{
|
|
73
|
+
success: boolean;
|
|
74
|
+
error?: string;
|
|
75
|
+
}>;
|
|
76
|
+
export declare function notifyAllChannels(config: NotificationConfig, target: string, verdict: TribunalVerdict): Promise<void>;
|
|
77
|
+
export declare function parseNotificationConfig(obj: Record<string, unknown>): NotificationConfig | undefined;
|
|
78
|
+
export declare function runNotify(argv: string[]): Promise<void>;
|
|
79
|
+
//# sourceMappingURL=notify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notify.d.ts","sourceRoot":"","sources":["../../src/commands/notify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAItE,MAAM,MAAM,uBAAuB,GAAG,OAAO,GAAG,OAAO,GAAG,SAAS,CAAC;AAEpE,MAAM,WAAW,mBAAmB;IAClC,mBAAmB;IACnB,IAAI,EAAE,uBAAuB,CAAC;IAC9B,oDAAoD;IACpD,GAAG,EAAE,MAAM,CAAC;IACZ,iDAAiD;IACjD,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,6CAA6C;IAC7C,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,wCAAwC;IACxC,QAAQ,EAAE,mBAAmB,EAAE,CAAC;IAChC,yDAAyD;IACzD,WAAW,CAAC,EAAE,QAAQ,CAAC;IACvB,2CAA2C;IAC3C,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,mBAAmB;IAClC,sCAAsC;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,sBAAsB;IACtB,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IACrC,6BAA6B;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,iCAAiC;IACjC,OAAO,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAClC,0BAA0B;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,+CAA+C;IAC/C,WAAW,EAAE,KAAK,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,QAAQ,CAAC;QACnB,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC,CAAC;IACH,8BAA8B;IAC9B,SAAS,EAAE,MAAM,CAAC;CACnB;AAaD,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,eAAe,EACxB,gBAAgB,EAAE,OAAO,EAAE,GAC1B,mBAAmB,CAyBrB;AA6FD,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,mBAAmB,EAC5B,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAgC/C;AAID,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,kBAAkB,EAC1B,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,IAAI,CAAC,CAkCf;AAMD,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,kBAAkB,GAAG,SAAS,CAmCpG;AAID,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAyF7D"}
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `judges notify` — Webhook notification system for findings alerts.
|
|
3
|
+
*
|
|
4
|
+
* Sends evaluation results to configured webhook endpoints (Slack, Teams,
|
|
5
|
+
* generic HTTP). Users configure their own endpoints — Judges never stores
|
|
6
|
+
* or processes data on behalf of users.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* judges notify --file results.json --channel slack # Send to Slack
|
|
10
|
+
* judges notify --file results.json --channel teams # Send to Teams
|
|
11
|
+
* judges notify --file results.json --channel webhook # Generic webhook
|
|
12
|
+
* judges eval src/app.ts --notify # Evaluate + notify
|
|
13
|
+
*
|
|
14
|
+
* Configuration in .judgesrc:
|
|
15
|
+
* ```json
|
|
16
|
+
* {
|
|
17
|
+
* "notifications": {
|
|
18
|
+
* "channels": [
|
|
19
|
+
* { "type": "slack", "url": "https://hooks.slack.com/..." },
|
|
20
|
+
* { "type": "teams", "url": "https://outlook.office.com/webhook/..." },
|
|
21
|
+
* { "type": "webhook", "url": "https://my-server.com/judges-hook",
|
|
22
|
+
* "headers": { "Authorization": "Bearer ..." } }
|
|
23
|
+
* ],
|
|
24
|
+
* "minSeverity": "medium",
|
|
25
|
+
* "onlyOnFailure": false
|
|
26
|
+
* }
|
|
27
|
+
* }
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
// ─── Severity Filtering ─────────────────────────────────────────────────────
|
|
31
|
+
const SEVERITY_RANK = { critical: 0, high: 1, medium: 2, low: 3, info: 4 };
|
|
32
|
+
function meetsMinSeverity(findings, minSeverity) {
|
|
33
|
+
const threshold = SEVERITY_RANK[minSeverity] ?? 4;
|
|
34
|
+
return findings.filter((f) => (SEVERITY_RANK[f.severity] ?? 4) <= threshold);
|
|
35
|
+
}
|
|
36
|
+
// ─── Payload Construction ───────────────────────────────────────────────────
|
|
37
|
+
export function buildNotificationPayload(target, verdict, filteredFindings) {
|
|
38
|
+
const summary = { critical: 0, high: 0, medium: 0, low: 0, info: 0 };
|
|
39
|
+
for (const f of filteredFindings) {
|
|
40
|
+
summary[f.severity] = (summary[f.severity] || 0) + 1;
|
|
41
|
+
}
|
|
42
|
+
const topFindings = filteredFindings
|
|
43
|
+
.sort((a, b) => (SEVERITY_RANK[a.severity] ?? 4) - (SEVERITY_RANK[b.severity] ?? 4))
|
|
44
|
+
.slice(0, 10)
|
|
45
|
+
.map((f) => ({
|
|
46
|
+
ruleId: f.ruleId,
|
|
47
|
+
severity: f.severity,
|
|
48
|
+
title: f.title,
|
|
49
|
+
line: f.lineNumbers?.[0],
|
|
50
|
+
}));
|
|
51
|
+
return {
|
|
52
|
+
target,
|
|
53
|
+
verdict: verdict.overallVerdict,
|
|
54
|
+
score: verdict.overallScore,
|
|
55
|
+
summary,
|
|
56
|
+
totalFindings: filteredFindings.length,
|
|
57
|
+
topFindings,
|
|
58
|
+
timestamp: new Date().toISOString(),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
// ─── Channel Formatters ─────────────────────────────────────────────────────
|
|
62
|
+
function formatSlackPayload(payload) {
|
|
63
|
+
const emoji = payload.verdict === "pass" ? ":white_check_mark:" : payload.verdict === "fail" ? ":x:" : ":warning:";
|
|
64
|
+
const color = payload.verdict === "pass" ? "#36a64f" : payload.verdict === "fail" ? "#e01e5a" : "#ecb22e";
|
|
65
|
+
const findingLines = payload.topFindings
|
|
66
|
+
.map((f) => `• \`${f.ruleId}\` [${f.severity.toUpperCase()}] ${f.title}${f.line ? ` (L${f.line})` : ""}`)
|
|
67
|
+
.join("\n");
|
|
68
|
+
return {
|
|
69
|
+
attachments: [
|
|
70
|
+
{
|
|
71
|
+
color,
|
|
72
|
+
blocks: [
|
|
73
|
+
{
|
|
74
|
+
type: "section",
|
|
75
|
+
text: {
|
|
76
|
+
type: "mrkdwn",
|
|
77
|
+
text: `${emoji} *Judges Panel — ${payload.verdict.toUpperCase()}*\n*Target:* \`${payload.target}\`\n*Score:* ${payload.score}/10 | *Findings:* ${payload.totalFindings}`,
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
...(payload.totalFindings > 0
|
|
81
|
+
? [
|
|
82
|
+
{
|
|
83
|
+
type: "section",
|
|
84
|
+
text: {
|
|
85
|
+
type: "mrkdwn",
|
|
86
|
+
text: `*Summary:* ${Object.entries(payload.summary)
|
|
87
|
+
.filter(([, v]) => v > 0)
|
|
88
|
+
.map(([k, v]) => `${k}: ${v}`)
|
|
89
|
+
.join(" | ")}`,
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
]
|
|
93
|
+
: []),
|
|
94
|
+
...(findingLines
|
|
95
|
+
? [
|
|
96
|
+
{
|
|
97
|
+
type: "section",
|
|
98
|
+
text: {
|
|
99
|
+
type: "mrkdwn",
|
|
100
|
+
text: `*Top Findings:*\n${findingLines}`,
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
]
|
|
104
|
+
: []),
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function formatTeamsPayload(payload) {
|
|
111
|
+
const color = payload.verdict === "pass" ? "00FF00" : payload.verdict === "fail" ? "FF0000" : "FFAA00";
|
|
112
|
+
const icon = payload.verdict === "pass" ? "✅" : payload.verdict === "fail" ? "❌" : "⚠️";
|
|
113
|
+
const findingRows = payload.topFindings.map((f) => `| ${f.ruleId} | ${f.severity} | ${f.title} |`).join("\n");
|
|
114
|
+
return {
|
|
115
|
+
"@type": "MessageCard",
|
|
116
|
+
"@context": "http://schema.org/extensions",
|
|
117
|
+
themeColor: color,
|
|
118
|
+
summary: `Judges Panel — ${payload.verdict.toUpperCase()}`,
|
|
119
|
+
sections: [
|
|
120
|
+
{
|
|
121
|
+
activityTitle: `${icon} Judges Panel — ${payload.verdict.toUpperCase()}`,
|
|
122
|
+
facts: [
|
|
123
|
+
{ name: "Target", value: payload.target },
|
|
124
|
+
{ name: "Score", value: `${payload.score}/10` },
|
|
125
|
+
{ name: "Findings", value: String(payload.totalFindings) },
|
|
126
|
+
...Object.entries(payload.summary)
|
|
127
|
+
.filter(([, v]) => v > 0)
|
|
128
|
+
.map(([k, v]) => ({ name: k.charAt(0).toUpperCase() + k.slice(1), value: String(v) })),
|
|
129
|
+
],
|
|
130
|
+
markdown: true,
|
|
131
|
+
text: payload.topFindings.length > 0
|
|
132
|
+
? `**Top Findings:**\n\n| Rule | Severity | Title |\n|------|----------|-------|\n${findingRows}`
|
|
133
|
+
: "",
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function formatGenericWebhookPayload(payload) {
|
|
139
|
+
return payload;
|
|
140
|
+
}
|
|
141
|
+
// ─── Send Notification ──────────────────────────────────────────────────────
|
|
142
|
+
export async function sendNotification(channel, payload) {
|
|
143
|
+
let body;
|
|
144
|
+
switch (channel.type) {
|
|
145
|
+
case "slack":
|
|
146
|
+
body = formatSlackPayload(payload);
|
|
147
|
+
break;
|
|
148
|
+
case "teams":
|
|
149
|
+
body = formatTeamsPayload(payload);
|
|
150
|
+
break;
|
|
151
|
+
case "webhook":
|
|
152
|
+
default:
|
|
153
|
+
body = formatGenericWebhookPayload(payload);
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
try {
|
|
157
|
+
const response = await fetch(channel.url, {
|
|
158
|
+
method: "POST",
|
|
159
|
+
headers: {
|
|
160
|
+
"Content-Type": "application/json",
|
|
161
|
+
...(channel.headers ?? {}),
|
|
162
|
+
},
|
|
163
|
+
body: JSON.stringify(body),
|
|
164
|
+
});
|
|
165
|
+
if (!response.ok) {
|
|
166
|
+
return { success: false, error: `HTTP ${response.status}: ${response.statusText}` };
|
|
167
|
+
}
|
|
168
|
+
return { success: true };
|
|
169
|
+
}
|
|
170
|
+
catch (err) {
|
|
171
|
+
return { success: false, error: err instanceof Error ? err.message : String(err) };
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// ─── Notify All Channels ────────────────────────────────────────────────────
|
|
175
|
+
export async function notifyAllChannels(config, target, verdict) {
|
|
176
|
+
// Apply severity filter
|
|
177
|
+
const filtered = config.minSeverity ? meetsMinSeverity(verdict.findings, config.minSeverity) : verdict.findings;
|
|
178
|
+
// Skip if onlyOnFailure and verdict is pass
|
|
179
|
+
if (config.onlyOnFailure && verdict.overallVerdict === "pass") {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
// Skip if no findings after filtering and onlyOnFailure
|
|
183
|
+
if (filtered.length === 0 && config.onlyOnFailure) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const payload = buildNotificationPayload(target, verdict, filtered);
|
|
187
|
+
const results = await Promise.allSettled(config.channels.map(async (ch) => {
|
|
188
|
+
const result = await sendNotification(ch, payload);
|
|
189
|
+
return { channel: ch.name || ch.type, ...result };
|
|
190
|
+
}));
|
|
191
|
+
for (const r of results) {
|
|
192
|
+
if (r.status === "fulfilled") {
|
|
193
|
+
if (r.value.success) {
|
|
194
|
+
console.log(` ✓ Notification sent to ${r.value.channel}`);
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
console.error(` ✗ Notification failed for ${r.value.channel}: ${r.value.error}`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
console.error(` ✗ Notification error: ${r.reason}`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
// ─── Config Parsing ─────────────────────────────────────────────────────────
|
|
206
|
+
const VALID_SEVERITIES = new Set(["critical", "high", "medium", "low", "info"]);
|
|
207
|
+
export function parseNotificationConfig(obj) {
|
|
208
|
+
if (!obj.notifications)
|
|
209
|
+
return undefined;
|
|
210
|
+
const raw = obj.notifications;
|
|
211
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw))
|
|
212
|
+
return undefined;
|
|
213
|
+
if (!Array.isArray(raw.channels))
|
|
214
|
+
return undefined;
|
|
215
|
+
const channels = [];
|
|
216
|
+
for (const ch of raw.channels) {
|
|
217
|
+
if (typeof ch !== "object" || ch === null)
|
|
218
|
+
continue;
|
|
219
|
+
const type = ch.type;
|
|
220
|
+
const url = ch.url;
|
|
221
|
+
if (!type || !url || !["slack", "teams", "webhook"].includes(type))
|
|
222
|
+
continue;
|
|
223
|
+
if (typeof url !== "string" || !url.startsWith("https://"))
|
|
224
|
+
continue;
|
|
225
|
+
channels.push({
|
|
226
|
+
type: type,
|
|
227
|
+
url,
|
|
228
|
+
headers: typeof ch.headers === "object" && ch.headers !== null ? ch.headers : undefined,
|
|
229
|
+
name: typeof ch.name === "string" ? ch.name : undefined,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
if (channels.length === 0)
|
|
233
|
+
return undefined;
|
|
234
|
+
return {
|
|
235
|
+
channels,
|
|
236
|
+
minSeverity: typeof raw.minSeverity === "string" && VALID_SEVERITIES.has(raw.minSeverity)
|
|
237
|
+
? raw.minSeverity
|
|
238
|
+
: undefined,
|
|
239
|
+
onlyOnFailure: typeof raw.onlyOnFailure === "boolean" ? raw.onlyOnFailure : false,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
// ─── CLI Runner ─────────────────────────────────────────────────────────────
|
|
243
|
+
export async function runNotify(argv) {
|
|
244
|
+
const file = argv.find((_a, i) => argv[i - 1] === "--file") || argv.find((_a, i) => argv[i - 1] === "-f");
|
|
245
|
+
const channelType = argv.find((_a, i) => argv[i - 1] === "--channel");
|
|
246
|
+
const url = argv.find((_a, i) => argv[i - 1] === "--url");
|
|
247
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
248
|
+
console.log(`
|
|
249
|
+
judges notify — Send evaluation results to webhook channels
|
|
250
|
+
|
|
251
|
+
Usage:
|
|
252
|
+
judges notify --file results.json Send to configured channels
|
|
253
|
+
judges notify --file results.json --channel slack Send to specific channel type
|
|
254
|
+
judges notify --url https://hooks.slack.com/... Send to ad-hoc webhook
|
|
255
|
+
|
|
256
|
+
Options:
|
|
257
|
+
--file, -f Path to a Judges JSON result file
|
|
258
|
+
--channel Channel type filter: slack | teams | webhook
|
|
259
|
+
--url Ad-hoc webhook URL (overrides config)
|
|
260
|
+
--help, -h Show this help
|
|
261
|
+
`);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
// Load notification config from .judgesrc
|
|
265
|
+
const { existsSync: exists, readFileSync: readFile } = await import("fs");
|
|
266
|
+
let notifConfig;
|
|
267
|
+
for (const name of [".judgesrc", ".judgesrc.json"]) {
|
|
268
|
+
if (exists(name)) {
|
|
269
|
+
try {
|
|
270
|
+
const raw = JSON.parse(readFile(name, "utf-8"));
|
|
271
|
+
notifConfig = parseNotificationConfig(raw);
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
// Skip invalid config
|
|
275
|
+
}
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
// Ad-hoc URL override
|
|
280
|
+
if (url) {
|
|
281
|
+
notifConfig = {
|
|
282
|
+
channels: [{ type: channelType || "webhook", url }],
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
if (!notifConfig || notifConfig.channels.length === 0) {
|
|
286
|
+
console.error('Error: No notification channels configured. Add "notifications" to .judgesrc or use --url.');
|
|
287
|
+
process.exit(1);
|
|
288
|
+
}
|
|
289
|
+
// Filter to specific channel type if requested
|
|
290
|
+
if (channelType && !url) {
|
|
291
|
+
notifConfig.channels = notifConfig.channels.filter((ch) => ch.type === channelType);
|
|
292
|
+
if (notifConfig.channels.length === 0) {
|
|
293
|
+
console.error(`Error: No ${channelType} channels configured.`);
|
|
294
|
+
process.exit(1);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
if (!file) {
|
|
298
|
+
console.error("Error: --file is required. Provide a Judges JSON result file.");
|
|
299
|
+
process.exit(1);
|
|
300
|
+
}
|
|
301
|
+
if (!exists(file)) {
|
|
302
|
+
console.error(`Error: File not found: ${file}`);
|
|
303
|
+
process.exit(1);
|
|
304
|
+
}
|
|
305
|
+
try {
|
|
306
|
+
const data = JSON.parse(readFile(file, "utf-8"));
|
|
307
|
+
const verdict = {
|
|
308
|
+
overallVerdict: data.overallVerdict || data.verdict || "pass",
|
|
309
|
+
overallScore: data.overallScore || data.score || 0,
|
|
310
|
+
findings: data.findings || [],
|
|
311
|
+
evaluations: data.evaluations || [],
|
|
312
|
+
summary: data.summary || "",
|
|
313
|
+
criticalCount: data.criticalCount || 0,
|
|
314
|
+
highCount: data.highCount || 0,
|
|
315
|
+
timestamp: data.timestamp || new Date().toISOString(),
|
|
316
|
+
};
|
|
317
|
+
console.log(`Sending notifications for ${file}...`);
|
|
318
|
+
await notifyAllChannels(notifConfig, file, verdict);
|
|
319
|
+
}
|
|
320
|
+
catch (err) {
|
|
321
|
+
console.error(`Error reading results file: ${err instanceof Error ? err.message : String(err)}`);
|
|
322
|
+
process.exit(1);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
//# sourceMappingURL=notify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notify.js","sourceRoot":"","sources":["../../src/commands/notify.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PR summary comment — post a top-level comment on a GitHub PR
|
|
3
|
+
* with the overall Judges verdict, score, and finding counts.
|
|
4
|
+
*
|
|
5
|
+
* Uses the GitHub API to create or update a comment with a distinctive
|
|
6
|
+
* marker so subsequent runs update in-place rather than spamming.
|
|
7
|
+
*/
|
|
8
|
+
import type { TribunalVerdict } from "../types.js";
|
|
9
|
+
export interface PrSummaryOptions {
|
|
10
|
+
owner: string;
|
|
11
|
+
repo: string;
|
|
12
|
+
prNumber: number;
|
|
13
|
+
token: string;
|
|
14
|
+
verdict: TribunalVerdict;
|
|
15
|
+
baseUrl?: string;
|
|
16
|
+
}
|
|
17
|
+
interface PrSummaryResult {
|
|
18
|
+
commentId: number;
|
|
19
|
+
updated: boolean;
|
|
20
|
+
url: string;
|
|
21
|
+
}
|
|
22
|
+
export declare function formatPrSummary(verdict: TribunalVerdict): string;
|
|
23
|
+
export declare function postPrSummary(options: PrSummaryOptions): Promise<PrSummaryResult>;
|
|
24
|
+
export declare function runPrSummary(argv: string[]): Promise<void>;
|
|
25
|
+
export {};
|
|
26
|
+
//# sourceMappingURL=pr-summary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pr-summary.d.ts","sourceRoot":"","sources":["../../src/commands/pr-summary.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAInD,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,eAAe,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,eAAe;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;CACb;AAmBD,wBAAgB,eAAe,CAAC,OAAO,EAAE,eAAe,GAAG,MAAM,CAoDhE;AAsBD,wBAAsB,aAAa,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC,CAiCvF;AAID,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAiFhE"}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PR summary comment — post a top-level comment on a GitHub PR
|
|
3
|
+
* with the overall Judges verdict, score, and finding counts.
|
|
4
|
+
*
|
|
5
|
+
* Uses the GitHub API to create or update a comment with a distinctive
|
|
6
|
+
* marker so subsequent runs update in-place rather than spamming.
|
|
7
|
+
*/
|
|
8
|
+
// ─── Marker ─────────────────────────────────────────────────────────────────
|
|
9
|
+
const COMMENT_MARKER = "<!-- judges-pr-summary -->";
|
|
10
|
+
// ─── Formatting ─────────────────────────────────────────────────────────────
|
|
11
|
+
function verdictEmoji(verdict) {
|
|
12
|
+
if (verdict === "pass")
|
|
13
|
+
return "✅";
|
|
14
|
+
if (verdict === "fail")
|
|
15
|
+
return "❌";
|
|
16
|
+
return "⚠️";
|
|
17
|
+
}
|
|
18
|
+
function severityBadge(severity) {
|
|
19
|
+
const map = { critical: "🔴", high: "🟠", medium: "🟡", low: "🔵", info: "⚪" };
|
|
20
|
+
return map[severity] || "⚪";
|
|
21
|
+
}
|
|
22
|
+
export function formatPrSummary(verdict) {
|
|
23
|
+
const emoji = verdictEmoji(verdict.overallVerdict);
|
|
24
|
+
const lines = [
|
|
25
|
+
COMMENT_MARKER,
|
|
26
|
+
`## ${emoji} Judges Code Review — ${verdict.overallVerdict.toUpperCase()}`,
|
|
27
|
+
"",
|
|
28
|
+
`| Metric | Value |`,
|
|
29
|
+
`|--------|-------|`,
|
|
30
|
+
`| **Verdict** | ${verdict.overallVerdict} |`,
|
|
31
|
+
`| **Score** | ${verdict.overallScore}/100 |`,
|
|
32
|
+
`| **Critical** | ${verdict.criticalCount} |`,
|
|
33
|
+
`| **High** | ${verdict.highCount} |`,
|
|
34
|
+
`| **Judges Run** | ${verdict.evaluations.length} |`,
|
|
35
|
+
"",
|
|
36
|
+
];
|
|
37
|
+
// Per-judge breakdown
|
|
38
|
+
if (verdict.evaluations.length > 0) {
|
|
39
|
+
lines.push("### Judge Breakdown", "");
|
|
40
|
+
lines.push("| Judge | Verdict | Score | Findings |");
|
|
41
|
+
lines.push("|-------|---------|-------|----------|");
|
|
42
|
+
for (const evaluation of verdict.evaluations) {
|
|
43
|
+
const jEmoji = verdictEmoji(evaluation.verdict);
|
|
44
|
+
const findingCount = evaluation.findings?.length ?? 0;
|
|
45
|
+
lines.push(`| ${evaluation.judgeId} | ${jEmoji} ${evaluation.verdict} | ${evaluation.score}/100 | ${findingCount} |`);
|
|
46
|
+
}
|
|
47
|
+
lines.push("");
|
|
48
|
+
}
|
|
49
|
+
// Top findings
|
|
50
|
+
const allFindings = verdict.evaluations.flatMap((e) => e.findings || []);
|
|
51
|
+
const topFindings = allFindings.filter((f) => f.severity === "critical" || f.severity === "high").slice(0, 10);
|
|
52
|
+
if (topFindings.length > 0) {
|
|
53
|
+
lines.push("### Top Findings", "");
|
|
54
|
+
for (const f of topFindings) {
|
|
55
|
+
const badge = severityBadge(f.severity);
|
|
56
|
+
const loc = f.lineNumbers?.length ? ` (line ${f.lineNumbers[0]})` : "";
|
|
57
|
+
lines.push(`- ${badge} **${f.severity.toUpperCase()}** — ${f.ruleId}: ${f.title}${loc}`);
|
|
58
|
+
}
|
|
59
|
+
lines.push("");
|
|
60
|
+
}
|
|
61
|
+
lines.push("---", `<sub>Generated by [Judges](https://github.com/KevinRabun/judges) at ${new Date().toISOString()}</sub>`);
|
|
62
|
+
return lines.join("\n");
|
|
63
|
+
}
|
|
64
|
+
// ─── GitHub API ─────────────────────────────────────────────────────────────
|
|
65
|
+
async function findExistingComment(owner, repo, prNumber, token, baseUrl) {
|
|
66
|
+
const url = `${baseUrl}/repos/${owner}/${repo}/issues/${prNumber}/comments?per_page=100`;
|
|
67
|
+
const res = await fetch(url, {
|
|
68
|
+
headers: { Authorization: `Bearer ${token}`, Accept: "application/vnd.github+json" },
|
|
69
|
+
});
|
|
70
|
+
if (!res.ok)
|
|
71
|
+
return null;
|
|
72
|
+
const comments = (await res.json());
|
|
73
|
+
const existing = comments.find((c) => c.body.includes(COMMENT_MARKER));
|
|
74
|
+
return existing ? { id: existing.id, url: existing.html_url } : null;
|
|
75
|
+
}
|
|
76
|
+
export async function postPrSummary(options) {
|
|
77
|
+
const baseUrl = options.baseUrl || "https://api.github.com";
|
|
78
|
+
const body = formatPrSummary(options.verdict);
|
|
79
|
+
const headers = {
|
|
80
|
+
Authorization: `Bearer ${options.token}`,
|
|
81
|
+
Accept: "application/vnd.github+json",
|
|
82
|
+
"Content-Type": "application/json",
|
|
83
|
+
};
|
|
84
|
+
// Check for existing comment to update
|
|
85
|
+
const existing = await findExistingComment(options.owner, options.repo, options.prNumber, options.token, baseUrl);
|
|
86
|
+
if (existing) {
|
|
87
|
+
const url = `${baseUrl}/repos/${options.owner}/${options.repo}/issues/comments/${existing.id}`;
|
|
88
|
+
const res = await fetch(url, {
|
|
89
|
+
method: "PATCH",
|
|
90
|
+
headers,
|
|
91
|
+
body: JSON.stringify({ body }),
|
|
92
|
+
});
|
|
93
|
+
if (!res.ok)
|
|
94
|
+
throw new Error(`Failed to update comment: ${res.status} ${res.statusText}`);
|
|
95
|
+
return { commentId: existing.id, updated: true, url: existing.url };
|
|
96
|
+
}
|
|
97
|
+
const url = `${baseUrl}/repos/${options.owner}/${options.repo}/issues/${options.prNumber}/comments`;
|
|
98
|
+
const res = await fetch(url, {
|
|
99
|
+
method: "POST",
|
|
100
|
+
headers,
|
|
101
|
+
body: JSON.stringify({ body }),
|
|
102
|
+
});
|
|
103
|
+
if (!res.ok)
|
|
104
|
+
throw new Error(`Failed to create comment: ${res.status} ${res.statusText}`);
|
|
105
|
+
const data = (await res.json());
|
|
106
|
+
return { commentId: data.id, updated: false, url: data.html_url };
|
|
107
|
+
}
|
|
108
|
+
// ─── CLI ────────────────────────────────────────────────────────────────────
|
|
109
|
+
export async function runPrSummary(argv) {
|
|
110
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
111
|
+
console.log(`
|
|
112
|
+
judges pr-summary — Post a PR summary comment with Judges results
|
|
113
|
+
|
|
114
|
+
Usage:
|
|
115
|
+
judges eval --file src/app.ts --format json | judges pr-summary --pr 42 --repo owner/repo
|
|
116
|
+
|
|
117
|
+
Options:
|
|
118
|
+
--pr <number> PR number (required)
|
|
119
|
+
--repo <owner/repo> GitHub repo (auto-detected from git)
|
|
120
|
+
--token <token> GitHub token (default: GITHUB_TOKEN env)
|
|
121
|
+
--sarif <path> Read results from SARIF file instead of stdin
|
|
122
|
+
--help, -h Show this help
|
|
123
|
+
`);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const { readFileSync, existsSync } = await import("fs");
|
|
127
|
+
const { execSync } = await import("child_process");
|
|
128
|
+
const prArg = argv.find((_a, i) => argv[i - 1] === "--pr");
|
|
129
|
+
if (!prArg) {
|
|
130
|
+
console.error("Error: --pr <number> is required");
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
const prNumber = parseInt(prArg, 10);
|
|
134
|
+
let repoArg = argv.find((_a, i) => argv[i - 1] === "--repo");
|
|
135
|
+
if (!repoArg) {
|
|
136
|
+
try {
|
|
137
|
+
const remote = execSync("git remote get-url origin", { encoding: "utf-8" }).trim();
|
|
138
|
+
const match = remote.match(/github\.com[:/](.+?)(?:\.git)?$/);
|
|
139
|
+
if (match)
|
|
140
|
+
repoArg = match[1];
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
/* ignore */
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (!repoArg) {
|
|
147
|
+
console.error("Error: --repo <owner/repo> required (could not auto-detect)");
|
|
148
|
+
process.exit(1);
|
|
149
|
+
}
|
|
150
|
+
const [owner, repo] = repoArg.split("/");
|
|
151
|
+
const token = argv.find((_a, i) => argv[i - 1] === "--token") || process.env.GITHUB_TOKEN || "";
|
|
152
|
+
if (!token) {
|
|
153
|
+
console.error("Error: --token or GITHUB_TOKEN env required");
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
// Load verdict
|
|
157
|
+
const sarifPath = argv.find((_a, i) => argv[i - 1] === "--sarif");
|
|
158
|
+
let verdict;
|
|
159
|
+
if (sarifPath && existsSync(sarifPath)) {
|
|
160
|
+
// Parse SARIF and synthesize a verdict
|
|
161
|
+
const sarif = JSON.parse(readFileSync(sarifPath, "utf-8"));
|
|
162
|
+
const findings = sarif.runs?.[0]?.results || [];
|
|
163
|
+
verdict = {
|
|
164
|
+
overallVerdict: findings.some((f) => f.level === "error") ? "fail" : "pass",
|
|
165
|
+
overallScore: Math.max(0, 100 - findings.length * 5),
|
|
166
|
+
summary: `SARIF upload with ${findings.length} findings`,
|
|
167
|
+
evaluations: [],
|
|
168
|
+
findings: [],
|
|
169
|
+
criticalCount: findings.filter((f) => f.level === "error").length,
|
|
170
|
+
highCount: findings.filter((f) => f.level === "warning").length,
|
|
171
|
+
timestamp: new Date().toISOString(),
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
// Try reading JSON verdict from stdin arg or file
|
|
176
|
+
const jsonArg = argv.find((_a, i) => argv[i - 1] === "--json");
|
|
177
|
+
if (jsonArg && existsSync(jsonArg)) {
|
|
178
|
+
verdict = JSON.parse(readFileSync(jsonArg, "utf-8"));
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
console.error("Error: provide --sarif <path> or --json <path>");
|
|
182
|
+
process.exit(1);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
const result = await postPrSummary({ owner, repo, prNumber, token, verdict });
|
|
186
|
+
console.log(`${result.updated ? "Updated" : "Created"} PR summary comment: ${result.url}`);
|
|
187
|
+
}
|
|
188
|
+
//# sourceMappingURL=pr-summary.js.map
|