brakit 0.8.5 → 0.8.6

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.
@@ -2141,8 +2141,8 @@ span.perf-breakdown-dot.perf-breakdown-app{background:var(--breakdown-app)}
2141
2141
  '<div class="ov-stat"><span class="ov-stat-value">' + state.fetches.length + '</span><span class="ov-stat-label">Fetches</span></div>';
2142
2142
  container.appendChild(summary);
2143
2143
 
2144
- var all = state.insights || [];
2145
- var open = all.filter(function(si) { return si.state === 'open' || si.state === 'fixing'; });
2144
+ var all = state.issues || [];
2145
+ var open = all.filter(function(si) { return si.state === 'open' || si.state === 'fixing' || si.state === 'regressed'; });
2146
2146
  var resolved = all.filter(function(si) { return si.state === 'resolved'; });
2147
2147
 
2148
2148
  if (open.length === 0 && resolved.length === 0) {
@@ -2174,31 +2174,35 @@ span.perf-breakdown-dot.perf-breakdown-app{background:var(--breakdown-app)}
2174
2174
 
2175
2175
  for (var i = 0; i < open.length; i++) {
2176
2176
  (function(si) {
2177
- var insight = si.insight;
2177
+ var issue = si.issue;
2178
2178
  var card = document.createElement('div');
2179
2179
  card.className = 'ov-card';
2180
2180
 
2181
- var sevCfg = SEV[insight.severity];
2181
+ var sevCfg = SEV[issue.severity];
2182
2182
  var iconCls = sevCfg.cls;
2183
2183
  var iconChar = sevCfg.icon;
2184
2184
 
2185
2185
  var expandHtml = '';
2186
- if (insight.detail) expandHtml += insight.detail;
2187
- if (insight.hint) expandHtml += '<div class="ov-card-hint">' + escHtml(insight.hint) + '</div>';
2188
- expandHtml += '<span class="ov-card-link" data-nav="' + insight.nav + '">View in ' + (NAV_LABELS[insight.nav] || insight.nav) + ' \u2192</span>';
2186
+ if (issue.detail) expandHtml += issue.detail;
2187
+ if (issue.hint) expandHtml += '<div class="ov-card-hint">' + escHtml(issue.hint) + '</div>';
2188
+ if (issue.nav) expandHtml += '<span class="ov-card-link" data-nav="' + issue.nav + '">View in ' + (NAV_LABELS[issue.nav] || issue.nav) + ' \u2192</span>';
2189
2189
 
2190
2190
  var aiBadge = '';
2191
2191
  if (si.state === 'fixing' && si.aiStatus === 'fixed') {
2192
2192
  aiBadge = '<span class="sec-ai-badge sec-ai-fixing">AI fixed \u2014 awaiting verification</span>';
2193
2193
  } else if (si.aiStatus === 'wont_fix') {
2194
2194
  aiBadge = '<span class="sec-ai-badge sec-ai-wontfix">AI: won\u2019t fix</span>';
2195
+ } else if (si.state === 'regressed') {
2196
+ aiBadge = '<span class="sec-ai-badge sec-ai-fixing" style="background:var(--red)">regressed</span>';
2195
2197
  }
2196
2198
 
2199
+ var occBadge = si.occurrences > 1 ? ' <span class="sec-item-count">' + si.occurrences + 'x</span>' : '';
2200
+
2197
2201
  card.innerHTML =
2198
2202
  '<span class="ov-card-icon ' + iconCls + '">' + iconChar + '</span>' +
2199
2203
  '<div class="ov-card-body">' +
2200
- '<div class="ov-card-title">' + escHtml(insight.title) + aiBadge + '</div>' +
2201
- '<div class="ov-card-desc">' + insight.desc + '</div>' +
2204
+ '<div class="ov-card-title">' + escHtml(issue.title) + occBadge + aiBadge + '</div>' +
2205
+ '<div class="ov-card-desc">' + issue.desc + '</div>' +
2202
2206
  '<div class="ov-card-expand">' + expandHtml + '</div>' +
2203
2207
  '</div>' +
2204
2208
  '<span class="ov-card-arrow">\u2192</span>';
@@ -2244,14 +2248,14 @@ span.perf-breakdown-dot.perf-breakdown-app{background:var(--breakdown-app)}
2244
2248
  resolvedCards.className = 'ov-cards';
2245
2249
 
2246
2250
  for (var ri = 0; ri < resolved.length; ri++) {
2247
- var rInsight = resolved[ri].insight;
2251
+ var rIssue = resolved[ri].issue;
2248
2252
  var rCard = document.createElement('div');
2249
2253
  rCard.className = 'ov-card ov-card-resolved';
2250
2254
  rCard.innerHTML =
2251
2255
  '<span class="ov-card-icon resolved">\u2713</span>' +
2252
2256
  '<div class="ov-card-body">' +
2253
- '<div class="ov-card-title" style="text-decoration:line-through;color:var(--text-muted)">' + escHtml(rInsight.title) + '</div>' +
2254
- '<div class="ov-card-desc">' + rInsight.desc + '</div>' +
2257
+ '<div class="ov-card-title" style="text-decoration:line-through;color:var(--text-muted)">' + escHtml(rIssue.title) + '</div>' +
2258
+ '<div class="ov-card-desc">' + rIssue.desc + '</div>' +
2255
2259
  '</div>';
2256
2260
  resolvedCards.appendChild(rCard);
2257
2261
  }
@@ -2267,30 +2271,12 @@ span.perf-breakdown-dot.perf-breakdown-app{background:var(--breakdown-app)}
2267
2271
  container.innerHTML = '';
2268
2272
  var SEV = { critical: { icon: '\u2717', cls: 'critical', sort: 0 }, warning: { icon: '\u26A0', cls: 'warning', sort: 1 }, info: { icon: '\u2139', cls: 'info', sort: 2 } };
2269
2273
 
2270
- var all = (state.findings || []).slice();
2271
- var insightsList = state.insights || [];
2272
- for (var ix = 0; ix < insightsList.length; ix++) {
2273
- var si = insightsList[ix];
2274
- all.push({
2275
- findingId: si.key,
2276
- state: si.state,
2277
- aiStatus: si.aiStatus,
2278
- aiNotes: si.aiNotes,
2279
- finding: {
2280
- severity: si.insight.severity,
2281
- rule: 'insight-' + si.insight.type,
2282
- title: si.insight.title,
2283
- desc: si.insight.desc,
2284
- hint: si.insight.hint,
2285
- endpoint: si.insight.nav || 'global',
2286
- count: 1
2287
- }
2288
- });
2289
- }
2290
- var open = all.filter(function(f) { return f.state === 'open' || f.state === 'fixing'; });
2274
+ var all = (state.issues || []).slice();
2275
+ var open = all.filter(function(f) { return f.state === 'open' || f.state === 'fixing' || f.state === 'regressed'; });
2291
2276
  var resolved = all.filter(function(f) { return f.state === 'resolved'; });
2277
+ var stale = all.filter(function(f) { return f.state === 'stale'; });
2292
2278
 
2293
- if (open.length === 0 && resolved.length === 0) {
2279
+ if (open.length === 0 && resolved.length === 0 && stale.length === 0) {
2294
2280
  var hasData = state.requests.length > 0 || state.logs.length > 0 || state.queries.length > 0;
2295
2281
  if (!hasData) {
2296
2282
  container.innerHTML = '<div class="empty"><span class="empty-title">Waiting for requests...</span><span class="empty-sub">Start using your app to see security findings here</span></div>';
@@ -2302,7 +2288,7 @@ span.perf-breakdown-dot.perf-breakdown-app{background:var(--breakdown-app)}
2302
2288
 
2303
2289
  var critCount = 0, warnCount = 0, infoCount = 0;
2304
2290
  for (var ci = 0; ci < open.length; ci++) {
2305
- var sev = open[ci].finding.severity;
2291
+ var sev = open[ci].issue.severity;
2306
2292
  if (sev === 'critical') critCount++;
2307
2293
  else if (sev === 'info') infoCount++;
2308
2294
  else warnCount++;
@@ -2335,7 +2321,7 @@ span.perf-breakdown-dot.perf-breakdown-app{background:var(--breakdown-app)}
2335
2321
  var groupOrder = [];
2336
2322
  for (var gi = 0; gi < open.length; gi++) {
2337
2323
  var sf = open[gi];
2338
- var f = sf.finding;
2324
+ var f = sf.issue;
2339
2325
  if (!groups[f.rule]) {
2340
2326
  groups[f.rule] = { rule: f.rule, title: f.title, severity: f.severity, hint: f.hint, items: [] };
2341
2327
  groupOrder.push(f.rule);
@@ -2378,7 +2364,7 @@ span.perf-breakdown-dot.perf-breakdown-app{background:var(--breakdown-app)}
2378
2364
  list.className = 'sec-items';
2379
2365
  for (var ii = 0; ii < group.items.length; ii++) {
2380
2366
  var sf2 = group.items[ii];
2381
- var item = sf2.finding;
2367
+ var item = sf2.issue;
2382
2368
  var row = document.createElement('div');
2383
2369
  row.className = 'sec-item';
2384
2370
  var aiBadge = '';
@@ -2386,11 +2372,14 @@ span.perf-breakdown-dot.perf-breakdown-app{background:var(--breakdown-app)}
2386
2372
  aiBadge = '<span class="sec-ai-badge sec-ai-fixing">AI fixed \u2014 awaiting verification</span>';
2387
2373
  } else if (sf2.aiStatus === 'wont_fix') {
2388
2374
  aiBadge = '<span class="sec-ai-badge sec-ai-wontfix">AI: won\u2019t fix</span>';
2375
+ } else if (sf2.state === 'regressed') {
2376
+ aiBadge = '<span class="sec-ai-badge sec-ai-fixing" style="background:var(--red)">regressed</span>';
2389
2377
  }
2390
2378
  var aiNotes = sf2.aiNotes ? '<div class="sec-ai-notes">' + escHtml(sf2.aiNotes) + '</div>' : '';
2379
+ var occBadge = sf2.occurrences > 1 ? '<span class="sec-item-count">' + sf2.occurrences + 'x</span>' : '';
2391
2380
  row.innerHTML =
2392
2381
  '<div class="sec-item-desc">' + escHtml(item.desc) + '</div>' +
2393
- (item.count > 1 ? '<span class="sec-item-count">' + item.count + 'x</span>' : '') +
2382
+ occBadge +
2394
2383
  aiBadge + aiNotes;
2395
2384
  list.appendChild(row);
2396
2385
  }
@@ -2411,20 +2400,44 @@ span.perf-breakdown-dot.perf-breakdown-app{background:var(--breakdown-app)}
2411
2400
  resolvedItems.className = 'sec-items';
2412
2401
  for (var ri = 0; ri < resolved.length; ri++) {
2413
2402
  var rsf = resolved[ri];
2414
- var rf = rsf.finding;
2403
+ var rf = rsf.issue;
2415
2404
  var rRow = document.createElement('div');
2416
2405
  rRow.className = 'sec-item sec-item-resolved';
2417
2406
  var verifiedBadge = rsf.aiStatus === 'fixed' ? '<span class="sec-ai-badge sec-ai-verified">Verified fix</span>' : '';
2418
2407
  var rNotes = rsf.aiNotes ? '<div class="sec-ai-notes">' + escHtml(rsf.aiNotes) + '</div>' : '';
2419
2408
  rRow.innerHTML =
2420
2409
  '<span class="sec-resolved-item-icon">\u2713</span>' +
2421
- '<div class="sec-item-desc">' + escHtml(rf.title) + ' \u2014 ' + escHtml(rf.endpoint) + '</div>' +
2410
+ '<div class="sec-item-desc">' + escHtml(rf.title) + ' \u2014 ' + escHtml(rf.endpoint || 'global') + '</div>' +
2422
2411
  verifiedBadge + rNotes;
2423
2412
  resolvedItems.appendChild(rRow);
2424
2413
  }
2425
2414
  resolvedGroup.appendChild(resolvedItems);
2426
2415
  container.appendChild(resolvedGroup);
2427
2416
  }
2417
+
2418
+ if (stale.length > 0) {
2419
+ var staleTitle = document.createElement('div');
2420
+ staleTitle.className = 'sec-resolved-title';
2421
+ staleTitle.innerHTML = '<span style="color:var(--text-muted)">\u23F8</span> Stale <span class="sec-resolved-count">' + stale.length + '</span>';
2422
+ container.appendChild(staleTitle);
2423
+
2424
+ var staleGroup = document.createElement('div');
2425
+ staleGroup.className = 'sec-group sec-group-resolved';
2426
+ var staleItems = document.createElement('div');
2427
+ staleItems.className = 'sec-items';
2428
+ for (var sti = 0; sti < stale.length; sti++) {
2429
+ var ssf = stale[sti];
2430
+ var sf3 = ssf.issue;
2431
+ var sRow = document.createElement('div');
2432
+ sRow.className = 'sec-item sec-item-resolved';
2433
+ sRow.innerHTML =
2434
+ '<span style="color:var(--text-muted)">\u23F8</span>' +
2435
+ '<div class="sec-item-desc" style="color:var(--text-muted)">' + escHtml(sf3.title) + ' \u2014 endpoint inactive</div>';
2436
+ staleItems.appendChild(sRow);
2437
+ }
2438
+ staleGroup.appendChild(staleItems);
2439
+ container.appendChild(staleGroup);
2440
+ }
2428
2441
  }
2429
2442
 
2430
2443
 
@@ -2482,13 +2495,7 @@ span.perf-breakdown-dot.perf-breakdown-app{background:var(--breakdown-app)}
2482
2495
  try {
2483
2496
  var res3 = await fetch('/__brakit/api/insights');
2484
2497
  var data3 = await res3.json();
2485
- state.insights = data3.insights || [];
2486
- } catch(e) { console.warn('[brakit]', e); }
2487
-
2488
- try {
2489
- var res4 = await fetch('/__brakit/api/security');
2490
- var data4 = await res4.json();
2491
- state.findings = data4.findings || [];
2498
+ state.issues = data3.issues || [];
2492
2499
  } catch(e) { console.warn('[brakit]', e); }
2493
2500
 
2494
2501
  updateStats();
@@ -2527,19 +2534,13 @@ span.perf-breakdown-dot.perf-breakdown-app{background:var(--breakdown-app)}
2527
2534
  registerTelemetryListener('error_event', 'errors', prependErrorRow);
2528
2535
  registerTelemetryListener('query', 'queries', prependQueryRow);
2529
2536
 
2530
- events.addEventListener('insights', function(e) {
2531
- state.insights = JSON.parse(e.data);
2537
+ events.addEventListener('issues', function(e) {
2538
+ state.issues = JSON.parse(e.data);
2532
2539
  if (state.activeView === 'overview') renderOverview();
2533
2540
  if (state.activeView === 'security') renderSecurity();
2534
2541
  updateStats();
2535
2542
  });
2536
2543
 
2537
- events.addEventListener('security', function(e) {
2538
- state.findings = JSON.parse(e.data);
2539
- if (state.activeView === 'security') renderSecurity();
2540
- updateStats();
2541
- });
2542
-
2543
2544
  window.addEventListener('beforeunload', function() {
2544
2545
  events.close();
2545
2546
  clearTimeout(reloadTimer);
@@ -2618,9 +2619,9 @@ span.perf-breakdown-dot.perf-breakdown-app{background:var(--breakdown-app)}
2618
2619
  if (queryCount) queryCount.textContent = state.queries.length;
2619
2620
  var secCount = document.getElementById('sidebar-count-security');
2620
2621
  if (secCount) {
2621
- var numFindings = (state.findings || []).filter(function(f) { return f.state !== 'resolved'; }).length;
2622
- secCount.textContent = numFindings;
2623
- secCount.style.display = numFindings > 0 ? '' : 'none';
2622
+ var numIssues = (state.issues || []).filter(function(f) { return f.state !== 'resolved' && f.state !== 'stale'; }).length;
2623
+ secCount.textContent = numIssues;
2624
+ secCount.style.display = numIssues > 0 ? '' : 'none';
2624
2625
  }
2625
2626
  }
2626
2627
 
@@ -2638,7 +2639,7 @@ span.perf-breakdown-dot.perf-breakdown-app{background:var(--breakdown-app)}
2638
2639
  if (!confirm('This will clear all data including performance metrics history. Continue?')) return;
2639
2640
  await fetch('/__brakit/api/clear', {method: 'POST'});
2640
2641
  state.flows = []; state.requests = []; state.fetches = []; state.errors = []; state.logs = []; state.queries = [];
2641
- state.insights = []; state.findings = [];
2642
+ state.issues = [];
2642
2643
  graphData = []; selectedEndpoint = '__all__'; timelineCache = {};
2643
2644
  renderFlows(); renderRequests(); renderFetches(); renderErrors(); renderLogs(); renderQueries(); renderGraph(); renderOverview(); renderSecurity(); updateStats();
2644
2645
  showToast('Cleared');
@@ -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.6";
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`);