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.
@@ -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.5";
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 getSecurityFindings() {
71
- return this.fetchJson(`${this.baseUrl}${DASHBOARD_API_SECURITY}`);
72
- }
73
- async getInsights() {
74
- return this.fetchJson(`${this.baseUrl}${DASHBOARD_API_INSIGHTS}`);
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 FINDING_ID_HASH_LENGTH = 16;
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 VALID_FINDING_STATES = /* @__PURE__ */ new Set(["open", "fixing", "resolved"]);
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 [securityData, insightsData] = await Promise.all([
263
- client.getSecurityFindings(),
264
- client.getInsights()
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
- securityData.findings.map(async (sf) => {
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(sf.finding.endpoint);
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 = securityData.findings.map((sf, i) => {
287
- const f = sf.finding;
288
- return {
289
- findingId: sf.findingId,
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: computeInsightId(i.type, endpoint, i.desc),
308
- severity: i.severity,
309
- title: i.title,
310
- endpoint,
311
- description: i.desc,
312
- hint: i.hint,
313
- occurrences: 1,
314
- context: i.detail ?? "",
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 isValidFindingState(val) {
374
- return typeof val === "string" && VALID_FINDING_STATES.has(val);
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 finding state (from finding lifecycle)"
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 && !isValidFindingState(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 stateful = await client.getFindings(state);
414
- const statefulIds = new Set(stateful.findings.map((f) => f.findingId));
415
- findings = findings.filter((f) => statefulIds.has(f.findingId));
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.findingId === findingId);
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.finding.title}" on ${finding.finding.endpoint} is no longer detected. The fix worked.`
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.finding.title}" on ${finding.finding.endpoint}`,
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.finding.desc}`,
599
- ` Hint: ${finding.finding.hint}`,
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.finding.endpoint === endpoint || f.finding.endpoint.endsWith(` ${endpoint}`)
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.finding.severity}] ${f.finding.title}: ${f.finding.desc}`);
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.finding.title}`);
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 [findingsData, securityData, insightsData, metricsData] = await Promise.all([
654
- client.getFindings(),
655
- client.getSecurityFindings(),
656
- client.getInsights(),
637
+ const [issuesData, metricsData] = await Promise.all([
638
+ client.getIssues(),
657
639
  client.getLiveMetrics()
658
640
  ]);
659
- const findings = findingsData.findings;
660
- const open = findings.filter((f) => f.state === "open");
661
- const resolved = findings.filter((f) => f.state === "resolved");
662
- const fixing = findings.filter((f) => f.state === "fixing");
663
- const criticalOpen = open.filter((f) => f.finding.severity === "critical");
664
- const warningOpen = open.filter((f) => f.finding.severity === "warning");
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
- `Active security rules: ${securityData.findings.length} finding(s)`,
676
- `Performance insights: ${openInsightCount} open, ${insightsData.insights.length - openInsightCount} resolved`,
659
+ `Security issues: ${securityIssues.length}`,
660
+ `Performance issues: ${perfIssues.length}`,
677
661
  "",
678
- "--- Finding Summary ---",
679
- `Total: ${findings.length}`,
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.finding.title} \u2014 ${f.finding.endpoint}`);
689
- lines.push(` ${f.finding.desc}`);
690
- lines.push(` Fix: ${f.finding.hint}`);
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.finding.title} \u2014 ${f.finding.endpoint}`);
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`);