brakit 0.8.5 → 0.8.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/README.md +10 -4
- package/dist/api.d.ts +124 -117
- package/dist/api.js +417 -363
- package/dist/bin/brakit.js +499 -339
- package/dist/dashboard-client.global.js +703 -0
- package/dist/dashboard.html +895 -2168
- package/dist/mcp/server.js +75 -90
- package/dist/runtime/index.js +2934 -5028
- package/package.json +4 -2
package/dist/mcp/server.js
CHANGED
|
@@ -51,7 +51,7 @@ var MAX_DISCOVERY_DEPTH = 5;
|
|
|
51
51
|
var MAX_TIMELINE_EVENTS = 20;
|
|
52
52
|
var MAX_RESOLVED_DISPLAY = 5;
|
|
53
53
|
var ENRICHMENT_SEVERITY_FILTER = ["critical", "warning"];
|
|
54
|
-
var MCP_SERVER_VERSION = "0.8.
|
|
54
|
+
var MCP_SERVER_VERSION = "0.8.7";
|
|
55
55
|
|
|
56
56
|
// src/mcp/client.ts
|
|
57
57
|
var BrakitClient = class {
|
|
@@ -67,11 +67,11 @@ var BrakitClient = class {
|
|
|
67
67
|
if (params?.offset) url.searchParams.set("offset", String(params.offset));
|
|
68
68
|
return this.fetchJson(url);
|
|
69
69
|
}
|
|
70
|
-
async
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
return this.fetchJson(
|
|
70
|
+
async getIssues(params) {
|
|
71
|
+
const url = new URL(`${this.baseUrl}${DASHBOARD_API_INSIGHTS}`);
|
|
72
|
+
if (params?.state) url.searchParams.set("state", params.state);
|
|
73
|
+
if (params?.category) url.searchParams.set("category", params.category);
|
|
74
|
+
return this.fetchJson(url);
|
|
75
75
|
}
|
|
76
76
|
async getQueries(requestId) {
|
|
77
77
|
const url = new URL(`${this.baseUrl}${DASHBOARD_API_QUERIES}`);
|
|
@@ -145,7 +145,10 @@ import { readFile, readdir, stat } from "fs/promises";
|
|
|
145
145
|
import { resolve, dirname } from "path";
|
|
146
146
|
|
|
147
147
|
// src/constants/limits.ts
|
|
148
|
-
var
|
|
148
|
+
var ISSUE_PRUNE_TTL_MS = 10 * 60 * 1e3;
|
|
149
|
+
|
|
150
|
+
// src/constants/thresholds.ts
|
|
151
|
+
var STALE_ISSUE_TTL_MS = 30 * 60 * 1e3;
|
|
149
152
|
|
|
150
153
|
// src/constants/metrics.ts
|
|
151
154
|
var PORT_FILE = ".brakit/port";
|
|
@@ -156,7 +159,7 @@ var PORT_MIN = 1;
|
|
|
156
159
|
var PORT_MAX = 65535;
|
|
157
160
|
|
|
158
161
|
// src/constants/lifecycle.ts
|
|
159
|
-
var
|
|
162
|
+
var VALID_ISSUE_STATES = /* @__PURE__ */ new Set(["open", "fixing", "resolved", "stale", "regressed"]);
|
|
160
163
|
var VALID_AI_FIX_STATUSES = /* @__PURE__ */ new Set(["fixed", "wont_fix"]);
|
|
161
164
|
var VALID_SECURITY_SEVERITIES = /* @__PURE__ */ new Set(["critical", "warning"]);
|
|
162
165
|
|
|
@@ -241,13 +244,6 @@ async function waitForBrakit(cwd, timeoutMs = 1e4, pollMs = DISCOVERY_POLL_INTER
|
|
|
241
244
|
);
|
|
242
245
|
}
|
|
243
246
|
|
|
244
|
-
// src/store/finding-id.ts
|
|
245
|
-
import { createHash } from "crypto";
|
|
246
|
-
function computeInsightId(type, endpoint, desc) {
|
|
247
|
-
const key = `${type}:${endpoint}:${desc}`;
|
|
248
|
-
return createHash("sha256").update(key).digest("hex").slice(0, FINDING_ID_HASH_LENGTH);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
247
|
// src/utils/endpoint.ts
|
|
252
248
|
function parseEndpointKey(endpoint) {
|
|
253
249
|
const spaceIdx = endpoint.indexOf(" ");
|
|
@@ -259,14 +255,16 @@ function parseEndpointKey(endpoint) {
|
|
|
259
255
|
|
|
260
256
|
// src/mcp/enrichment.ts
|
|
261
257
|
async function enrichFindings(client) {
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
258
|
+
const issuesData = await client.getIssues();
|
|
259
|
+
const issues = issuesData.issues.filter(
|
|
260
|
+
(si) => si.state !== "resolved" && si.state !== "stale"
|
|
261
|
+
);
|
|
266
262
|
const contexts = await Promise.all(
|
|
267
|
-
|
|
263
|
+
issues.map(async (si) => {
|
|
264
|
+
const endpoint = si.issue.endpoint;
|
|
265
|
+
if (!endpoint) return si.issue.detail ?? "";
|
|
268
266
|
try {
|
|
269
|
-
const { path } = parseEndpointKey(
|
|
267
|
+
const { path } = parseEndpointKey(endpoint);
|
|
270
268
|
const reqData = await client.getRequests({ search: path, limit: 1 });
|
|
271
269
|
if (reqData.requests.length > 0) {
|
|
272
270
|
const req = reqData.requests[0];
|
|
@@ -280,38 +278,22 @@ async function enrichFindings(client) {
|
|
|
280
278
|
} catch {
|
|
281
279
|
return "(context unavailable)";
|
|
282
280
|
}
|
|
283
|
-
return "";
|
|
281
|
+
return si.issue.detail ?? "";
|
|
284
282
|
})
|
|
285
283
|
);
|
|
286
|
-
const enriched =
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
severity: f.severity,
|
|
291
|
-
title: f.title,
|
|
292
|
-
endpoint: f.endpoint,
|
|
293
|
-
description: f.desc,
|
|
294
|
-
hint: f.hint,
|
|
295
|
-
occurrences: f.count,
|
|
296
|
-
context: contexts[i],
|
|
297
|
-
aiStatus: sf.aiStatus,
|
|
298
|
-
aiNotes: sf.aiNotes
|
|
299
|
-
};
|
|
300
|
-
});
|
|
301
|
-
for (const si of insightsData.insights) {
|
|
302
|
-
if (si.state === "resolved") continue;
|
|
303
|
-
const i = si.insight;
|
|
304
|
-
if (!ENRICHMENT_SEVERITY_FILTER.includes(i.severity)) continue;
|
|
305
|
-
const endpoint = i.nav ?? "global";
|
|
284
|
+
const enriched = [];
|
|
285
|
+
for (let i = 0; i < issues.length; i++) {
|
|
286
|
+
const si = issues[i];
|
|
287
|
+
if (!ENRICHMENT_SEVERITY_FILTER.includes(si.issue.severity)) continue;
|
|
306
288
|
enriched.push({
|
|
307
|
-
findingId:
|
|
308
|
-
severity:
|
|
309
|
-
title:
|
|
310
|
-
endpoint,
|
|
311
|
-
description:
|
|
312
|
-
hint:
|
|
313
|
-
occurrences:
|
|
314
|
-
context: i
|
|
289
|
+
findingId: si.issueId,
|
|
290
|
+
severity: si.issue.severity,
|
|
291
|
+
title: si.issue.title,
|
|
292
|
+
endpoint: si.issue.endpoint ?? "global",
|
|
293
|
+
description: si.issue.desc,
|
|
294
|
+
hint: si.issue.hint,
|
|
295
|
+
occurrences: si.occurrences,
|
|
296
|
+
context: contexts[i],
|
|
315
297
|
aiStatus: si.aiStatus,
|
|
316
298
|
aiNotes: si.aiNotes
|
|
317
299
|
});
|
|
@@ -370,8 +352,8 @@ async function buildRequestDetail(client, req) {
|
|
|
370
352
|
function isNonEmptyString(val) {
|
|
371
353
|
return typeof val === "string" && val.trim().length > 0;
|
|
372
354
|
}
|
|
373
|
-
function
|
|
374
|
-
return typeof val === "string" &&
|
|
355
|
+
function isValidIssueState(val) {
|
|
356
|
+
return typeof val === "string" && VALID_ISSUE_STATES.has(val);
|
|
375
357
|
}
|
|
376
358
|
function isValidAiFixStatus(val) {
|
|
377
359
|
return typeof val === "string" && VALID_AI_FIX_STATUSES.has(val);
|
|
@@ -391,8 +373,8 @@ var getFindings = {
|
|
|
391
373
|
},
|
|
392
374
|
state: {
|
|
393
375
|
type: "string",
|
|
394
|
-
enum: ["open", "fixing", "resolved"],
|
|
395
|
-
description: "Filter by
|
|
376
|
+
enum: ["open", "fixing", "resolved", "stale", "regressed"],
|
|
377
|
+
description: "Filter by issue state"
|
|
396
378
|
}
|
|
397
379
|
}
|
|
398
380
|
},
|
|
@@ -402,17 +384,17 @@ var getFindings = {
|
|
|
402
384
|
if (severity && !VALID_SECURITY_SEVERITIES.has(severity)) {
|
|
403
385
|
return { content: [{ type: "text", text: `Invalid severity "${severity}". Use: critical, warning.` }], isError: true };
|
|
404
386
|
}
|
|
405
|
-
if (state && !
|
|
406
|
-
return { content: [{ type: "text", text: `Invalid state "${state}". Use: open, fixing, resolved.` }], isError: true };
|
|
387
|
+
if (state && !isValidIssueState(state)) {
|
|
388
|
+
return { content: [{ type: "text", text: `Invalid state "${state}". Use: open, fixing, resolved, stale, regressed.` }], isError: true };
|
|
407
389
|
}
|
|
408
390
|
let findings = await enrichFindings(client);
|
|
409
391
|
if (severity) {
|
|
410
392
|
findings = findings.filter((f) => f.severity === severity);
|
|
411
393
|
}
|
|
412
394
|
if (state) {
|
|
413
|
-
const
|
|
414
|
-
const
|
|
415
|
-
findings = findings.filter((f) =>
|
|
395
|
+
const issuesData = await client.getIssues({ state });
|
|
396
|
+
const issueIds = new Set(issuesData.issues.map((i) => i.issueId));
|
|
397
|
+
findings = findings.filter((f) => issueIds.has(f.findingId));
|
|
416
398
|
}
|
|
417
399
|
if (findings.length === 0) {
|
|
418
400
|
return { content: [{ type: "text", text: "No findings detected. The application looks healthy." }] };
|
|
@@ -570,20 +552,21 @@ var verifyFix = {
|
|
|
570
552
|
}
|
|
571
553
|
if (findingId) {
|
|
572
554
|
const data = await client.getFindings();
|
|
573
|
-
const finding = data.findings.find((f) => f.
|
|
555
|
+
const finding = data.findings.find((f) => f.issueId === findingId);
|
|
574
556
|
if (!finding) {
|
|
575
557
|
return {
|
|
576
558
|
content: [{
|
|
577
559
|
type: "text",
|
|
578
560
|
text: `Finding ${findingId} not found. It may have already been resolved and cleaned up.`
|
|
579
|
-
}]
|
|
561
|
+
}],
|
|
562
|
+
isError: true
|
|
580
563
|
};
|
|
581
564
|
}
|
|
582
565
|
if (finding.state === "resolved") {
|
|
583
566
|
return {
|
|
584
567
|
content: [{
|
|
585
568
|
type: "text",
|
|
586
|
-
text: `RESOLVED: "${finding.
|
|
569
|
+
text: `RESOLVED: "${finding.issue.title}" on ${finding.issue.endpoint ?? "global"} is no longer detected. The fix worked.`
|
|
587
570
|
}]
|
|
588
571
|
};
|
|
589
572
|
}
|
|
@@ -591,12 +574,12 @@ var verifyFix = {
|
|
|
591
574
|
content: [{
|
|
592
575
|
type: "text",
|
|
593
576
|
text: [
|
|
594
|
-
`STILL PRESENT: "${finding.
|
|
577
|
+
`STILL PRESENT: "${finding.issue.title}" on ${finding.issue.endpoint ?? "global"}`,
|
|
595
578
|
` State: ${finding.state}`,
|
|
596
579
|
` Last seen: ${new Date(finding.lastSeenAt).toISOString()}`,
|
|
597
580
|
` Occurrences: ${finding.occurrences}`,
|
|
598
|
-
` Issue: ${finding.
|
|
599
|
-
` Hint: ${finding.
|
|
581
|
+
` Issue: ${finding.issue.desc}`,
|
|
582
|
+
` Hint: ${finding.issue.hint}`,
|
|
600
583
|
"",
|
|
601
584
|
"Make sure the user has triggered the endpoint again after the fix, so Brakit can re-analyze."
|
|
602
585
|
].join("\n")
|
|
@@ -606,7 +589,7 @@ var verifyFix = {
|
|
|
606
589
|
if (endpoint) {
|
|
607
590
|
const data = await client.getFindings();
|
|
608
591
|
const endpointFindings = data.findings.filter(
|
|
609
|
-
(f) => f.
|
|
592
|
+
(f) => f.issue.endpoint === endpoint || f.issue.endpoint && f.issue.endpoint.endsWith(` ${endpoint}`)
|
|
610
593
|
);
|
|
611
594
|
if (endpointFindings.length === 0) {
|
|
612
595
|
return {
|
|
@@ -616,7 +599,7 @@ var verifyFix = {
|
|
|
616
599
|
}]
|
|
617
600
|
};
|
|
618
601
|
}
|
|
619
|
-
const open = endpointFindings.filter((f) => f.state === "open");
|
|
602
|
+
const open = endpointFindings.filter((f) => f.state === "open" || f.state === "regressed");
|
|
620
603
|
const resolved = endpointFindings.filter((f) => f.state === "resolved");
|
|
621
604
|
const lines = [
|
|
622
605
|
`Endpoint: ${endpoint}`,
|
|
@@ -625,10 +608,10 @@ var verifyFix = {
|
|
|
625
608
|
""
|
|
626
609
|
];
|
|
627
610
|
for (const f of open) {
|
|
628
|
-
lines.push(` [${f.
|
|
611
|
+
lines.push(` [${f.issue.severity}] ${f.issue.title}: ${f.issue.desc}`);
|
|
629
612
|
}
|
|
630
613
|
for (const f of resolved) {
|
|
631
|
-
lines.push(` [resolved] ${f.
|
|
614
|
+
lines.push(` [resolved] ${f.issue.title}`);
|
|
632
615
|
}
|
|
633
616
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
634
617
|
}
|
|
@@ -636,7 +619,8 @@ var verifyFix = {
|
|
|
636
619
|
content: [{
|
|
637
620
|
type: "text",
|
|
638
621
|
text: "Please provide either a finding_id or an endpoint to verify."
|
|
639
|
-
}]
|
|
622
|
+
}],
|
|
623
|
+
isError: true
|
|
640
624
|
};
|
|
641
625
|
}
|
|
642
626
|
};
|
|
@@ -650,51 +634,52 @@ var getReport = {
|
|
|
650
634
|
properties: {}
|
|
651
635
|
},
|
|
652
636
|
async handler(client, _args) {
|
|
653
|
-
const [
|
|
654
|
-
client.
|
|
655
|
-
client.getSecurityFindings(),
|
|
656
|
-
client.getInsights(),
|
|
637
|
+
const [issuesData, metricsData] = await Promise.all([
|
|
638
|
+
client.getIssues(),
|
|
657
639
|
client.getLiveMetrics()
|
|
658
640
|
]);
|
|
659
|
-
const
|
|
660
|
-
const open =
|
|
661
|
-
const resolved =
|
|
662
|
-
const fixing =
|
|
663
|
-
const
|
|
664
|
-
const
|
|
641
|
+
const issues = issuesData.issues;
|
|
642
|
+
const open = issues.filter((f) => f.state === "open" || f.state === "regressed");
|
|
643
|
+
const resolved = issues.filter((f) => f.state === "resolved");
|
|
644
|
+
const fixing = issues.filter((f) => f.state === "fixing");
|
|
645
|
+
const stale = issues.filter((f) => f.state === "stale");
|
|
646
|
+
const criticalOpen = open.filter((f) => f.issue.severity === "critical");
|
|
647
|
+
const warningOpen = open.filter((f) => f.issue.severity === "warning");
|
|
648
|
+
const securityIssues = issues.filter((f) => f.category === "security");
|
|
649
|
+
const perfIssues = issues.filter((f) => f.category === "performance");
|
|
665
650
|
const totalRequests = metricsData.endpoints.reduce(
|
|
666
651
|
(s, ep) => s + ep.summary.totalRequests,
|
|
667
652
|
0
|
|
668
653
|
);
|
|
669
|
-
const openInsightCount = insightsData.insights.filter((si) => si.state === "open").length;
|
|
670
654
|
const lines = [
|
|
671
655
|
"=== Brakit Report ===",
|
|
672
656
|
"",
|
|
673
657
|
`Endpoints observed: ${metricsData.endpoints.length}`,
|
|
674
658
|
`Total requests captured: ${totalRequests}`,
|
|
675
|
-
`
|
|
676
|
-
`Performance
|
|
659
|
+
`Security issues: ${securityIssues.length}`,
|
|
660
|
+
`Performance issues: ${perfIssues.length}`,
|
|
677
661
|
"",
|
|
678
|
-
"---
|
|
679
|
-
`Total: ${
|
|
662
|
+
"--- Issue Summary ---",
|
|
663
|
+
`Total: ${issues.length}`,
|
|
680
664
|
` Open: ${open.length} (${criticalOpen.length} critical, ${warningOpen.length} warning)`,
|
|
681
665
|
` In progress: ${fixing.length}`,
|
|
682
|
-
` Resolved: ${resolved.length}
|
|
666
|
+
` Resolved: ${resolved.length}`,
|
|
667
|
+
` Stale: ${stale.length}`
|
|
683
668
|
];
|
|
684
669
|
if (criticalOpen.length > 0) {
|
|
685
670
|
lines.push("");
|
|
686
671
|
lines.push("--- Critical Issues (fix first) ---");
|
|
687
672
|
for (const f of criticalOpen) {
|
|
688
|
-
lines.push(` [CRITICAL] ${f.
|
|
689
|
-
lines.push(` ${f.
|
|
690
|
-
lines.push(` Fix: ${f.
|
|
673
|
+
lines.push(` [CRITICAL] ${f.issue.title} \u2014 ${f.issue.endpoint ?? "global"}`);
|
|
674
|
+
lines.push(` ${f.issue.desc}`);
|
|
675
|
+
lines.push(` Fix: ${f.issue.hint}`);
|
|
691
676
|
}
|
|
692
677
|
}
|
|
693
678
|
if (resolved.length > 0) {
|
|
694
679
|
lines.push("");
|
|
695
680
|
lines.push("--- Recently Resolved ---");
|
|
696
681
|
for (const f of resolved.slice(0, MAX_RESOLVED_DISPLAY)) {
|
|
697
|
-
lines.push(` \u2713 ${f.
|
|
682
|
+
lines.push(` \u2713 ${f.issue.title} \u2014 ${f.issue.endpoint ?? "global"}`);
|
|
698
683
|
}
|
|
699
684
|
if (resolved.length > MAX_RESOLVED_DISPLAY) {
|
|
700
685
|
lines.push(` ... and ${resolved.length - MAX_RESOLVED_DISPLAY} more`);
|