brakit 0.10.0 → 0.10.1

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.
@@ -105,11 +105,14 @@ var init_config = __esm({
105
105
  "nuxt",
106
106
  "vite",
107
107
  "astro",
108
+ "@nestjs/core",
109
+ "@adonisjs/core",
110
+ "sails",
108
111
  "express",
109
112
  "fastify",
110
113
  "hono",
111
114
  "koa",
112
- "nest",
115
+ "@hapi/hapi",
113
116
  "prisma",
114
117
  "drizzle-orm",
115
118
  "typeorm",
@@ -145,14 +148,10 @@ var init_labels = __esm({
145
148
  VALID_TABS_TUPLE = [
146
149
  "overview",
147
150
  "actions",
148
- "requests",
149
- "fetches",
150
- "queries",
151
- "errors",
152
- "logs",
151
+ "insights",
153
152
  "performance",
154
- "security",
155
- "graph"
153
+ "graph",
154
+ "explorer"
156
155
  ];
157
156
  VALID_TABS = new Set(VALID_TABS_TUPLE);
158
157
  BRAKIT_REQUEST_ID_HEADER = "x-brakit-request-id";
@@ -339,6 +338,214 @@ var init_fetch = __esm({
339
338
  }
340
339
  });
341
340
 
341
+ // src/utils/log.ts
342
+ function brakitWarn(message) {
343
+ process.stderr.write(`${PREFIX} ${message}
344
+ `);
345
+ }
346
+ function brakitDebug(message) {
347
+ if (process.env.DEBUG_BRAKIT) {
348
+ process.stderr.write(`${PREFIX}:debug ${message}
349
+ `);
350
+ }
351
+ }
352
+ var PREFIX;
353
+ var init_log = __esm({
354
+ "src/utils/log.ts"() {
355
+ "use strict";
356
+ PREFIX = "[brakit]";
357
+ }
358
+ });
359
+
360
+ // src/utils/type-guards.ts
361
+ function isString(val) {
362
+ return typeof val === "string";
363
+ }
364
+ function isNumber(val) {
365
+ return typeof val === "number" && !isNaN(val);
366
+ }
367
+ function isBoolean(val) {
368
+ return typeof val === "boolean";
369
+ }
370
+ function isThenable(value) {
371
+ return value != null && typeof value.then === "function";
372
+ }
373
+ function getErrorMessage(err) {
374
+ if (err instanceof Error) return err.message;
375
+ if (typeof err === "string") return err;
376
+ return String(err);
377
+ }
378
+ function isValidIssueState(val) {
379
+ return typeof val === "string" && VALID_ISSUE_STATES.has(val);
380
+ }
381
+ function isValidIssueCategory(val) {
382
+ return typeof val === "string" && VALID_ISSUE_CATEGORIES.has(val);
383
+ }
384
+ function isValidAiFixStatus(val) {
385
+ return typeof val === "string" && VALID_AI_FIX_STATUSES.has(val);
386
+ }
387
+ function validateIssuesData(parsed) {
388
+ if (parsed == null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
389
+ const obj = parsed;
390
+ if (obj.version === ISSUES_DATA_VERSION && Array.isArray(obj.issues)) {
391
+ return parsed;
392
+ }
393
+ return null;
394
+ }
395
+ function validateMetricsData(parsed) {
396
+ if (parsed == null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
397
+ const obj = parsed;
398
+ if (obj.version === 1 && Array.isArray(obj.endpoints)) {
399
+ return parsed;
400
+ }
401
+ return null;
402
+ }
403
+ var init_type_guards = __esm({
404
+ "src/utils/type-guards.ts"() {
405
+ "use strict";
406
+ init_config();
407
+ }
408
+ });
409
+
410
+ // src/runtime/capture.ts
411
+ import { gunzip, brotliDecompress, inflate } from "zlib";
412
+ function outgoingToIncoming(headers2) {
413
+ const result = {};
414
+ for (const [key, value] of Object.entries(headers2)) {
415
+ if (value === void 0) continue;
416
+ if (Array.isArray(value)) {
417
+ result[key] = value.map(String);
418
+ } else {
419
+ result[key] = String(value);
420
+ }
421
+ }
422
+ return result;
423
+ }
424
+ function getDecompressor(encoding) {
425
+ if (encoding === CONTENT_ENCODING_GZIP) return gunzip;
426
+ if (encoding === CONTENT_ENCODING_BR) return brotliDecompress;
427
+ if (encoding === CONTENT_ENCODING_DEFLATE) return inflate;
428
+ return null;
429
+ }
430
+ function decompressAsync(body, encoding) {
431
+ const decompressor = getDecompressor(encoding);
432
+ if (!decompressor) return Promise.resolve(body);
433
+ return new Promise((resolve6) => {
434
+ decompressor(body, (err, result) => {
435
+ resolve6(err ? body : result);
436
+ });
437
+ });
438
+ }
439
+ function toBuffer(chunk) {
440
+ if (Buffer.isBuffer(chunk)) return chunk;
441
+ if (chunk instanceof Uint8Array) return Buffer.from(chunk.buffer, chunk.byteOffset, chunk.byteLength);
442
+ if (typeof chunk === "string") return Buffer.from(chunk);
443
+ return null;
444
+ }
445
+ function drainPendingCaptures() {
446
+ if (pendingCaptures === 0) return Promise.resolve();
447
+ return new Promise((resolve6) => {
448
+ drainResolvers.push(resolve6);
449
+ });
450
+ }
451
+ function onCaptureSettled() {
452
+ pendingCaptures--;
453
+ if (pendingCaptures === 0 && drainResolvers.length > 0) {
454
+ const resolvers = drainResolvers;
455
+ drainResolvers = [];
456
+ for (const resolve6 of resolvers) resolve6();
457
+ }
458
+ }
459
+ function captureInProcess(req, res, requestId, requestStore, isChild = false) {
460
+ const startTime = performance.now();
461
+ const method = req.method ?? "GET";
462
+ const resChunks = [];
463
+ let resSize = 0;
464
+ const originalWrite = res.write;
465
+ const originalEnd = res.end;
466
+ let truncated = false;
467
+ res.write = function(...args) {
468
+ try {
469
+ const chunk = args[0];
470
+ if (chunk != null && typeof chunk !== "function") {
471
+ if (resSize < DEFAULT_MAX_BODY_CAPTURE) {
472
+ const buf = toBuffer(chunk);
473
+ if (buf) {
474
+ resChunks.push(buf);
475
+ resSize += buf.length;
476
+ }
477
+ } else {
478
+ truncated = true;
479
+ }
480
+ }
481
+ } catch (e) {
482
+ brakitDebug(`capture write: ${getErrorMessage(e)}`);
483
+ }
484
+ return originalWrite.apply(this, args);
485
+ };
486
+ res.end = function(...args) {
487
+ try {
488
+ const chunk = typeof args[0] !== "function" ? args[0] : void 0;
489
+ if (chunk != null && resSize < DEFAULT_MAX_BODY_CAPTURE) {
490
+ const buf = toBuffer(chunk);
491
+ if (buf) {
492
+ resChunks.push(buf);
493
+ }
494
+ }
495
+ } catch (e) {
496
+ brakitDebug(`capture end: ${getErrorMessage(e)}`);
497
+ }
498
+ const result = originalEnd.apply(this, args);
499
+ const endTime = performance.now();
500
+ const encoding = String(res.getHeader("content-encoding") ?? "").toLowerCase();
501
+ const statusCode = res.statusCode;
502
+ const responseHeaders = outgoingToIncoming(res.getHeaders());
503
+ const responseContentType = String(res.getHeader("content-type") ?? "");
504
+ const capturedChunks = resChunks.slice();
505
+ if (!isChild) {
506
+ pendingCaptures++;
507
+ void (async () => {
508
+ try {
509
+ let body = capturedChunks.length > 0 ? Buffer.concat(capturedChunks) : null;
510
+ if (body && encoding && !truncated) {
511
+ body = await decompressAsync(body, encoding);
512
+ }
513
+ requestStore.capture({
514
+ requestId,
515
+ method,
516
+ url: req.url ?? "/",
517
+ requestHeaders: req.headers,
518
+ requestBody: null,
519
+ statusCode,
520
+ responseHeaders,
521
+ responseBody: body,
522
+ responseContentType,
523
+ startTime,
524
+ endTime,
525
+ config: { maxBodyCapture: DEFAULT_MAX_BODY_CAPTURE }
526
+ });
527
+ } catch (e) {
528
+ brakitDebug(`capture store: ${getErrorMessage(e)}`);
529
+ } finally {
530
+ onCaptureSettled();
531
+ }
532
+ })();
533
+ }
534
+ return result;
535
+ };
536
+ }
537
+ var pendingCaptures, drainResolvers;
538
+ var init_capture = __esm({
539
+ "src/runtime/capture.ts"() {
540
+ "use strict";
541
+ init_constants();
542
+ init_log();
543
+ init_type_guards();
544
+ pendingCaptures = 0;
545
+ drainResolvers = [];
546
+ }
547
+ });
548
+
342
549
  // src/instrument/hooks/console.ts
343
550
  import { format } from "util";
344
551
  function setupConsoleHook(emit) {
@@ -539,75 +746,6 @@ var init_normalize = __esm({
539
746
  }
540
747
  });
541
748
 
542
- // src/utils/type-guards.ts
543
- function isString(val) {
544
- return typeof val === "string";
545
- }
546
- function isNumber(val) {
547
- return typeof val === "number" && !isNaN(val);
548
- }
549
- function isBoolean(val) {
550
- return typeof val === "boolean";
551
- }
552
- function isThenable(value) {
553
- return value != null && typeof value.then === "function";
554
- }
555
- function getErrorMessage(err) {
556
- if (err instanceof Error) return err.message;
557
- if (typeof err === "string") return err;
558
- return String(err);
559
- }
560
- function isValidIssueState(val) {
561
- return typeof val === "string" && VALID_ISSUE_STATES.has(val);
562
- }
563
- function isValidIssueCategory(val) {
564
- return typeof val === "string" && VALID_ISSUE_CATEGORIES.has(val);
565
- }
566
- function isValidAiFixStatus(val) {
567
- return typeof val === "string" && VALID_AI_FIX_STATUSES.has(val);
568
- }
569
- function validateIssuesData(parsed) {
570
- if (parsed == null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
571
- const obj = parsed;
572
- if (obj.version === ISSUES_DATA_VERSION && Array.isArray(obj.issues)) {
573
- return parsed;
574
- }
575
- return null;
576
- }
577
- function validateMetricsData(parsed) {
578
- if (parsed == null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
579
- const obj = parsed;
580
- if (obj.version === 1 && Array.isArray(obj.endpoints)) {
581
- return parsed;
582
- }
583
- return null;
584
- }
585
- var init_type_guards = __esm({
586
- "src/utils/type-guards.ts"() {
587
- "use strict";
588
- init_config();
589
- }
590
- });
591
-
592
- // src/utils/log.ts
593
- function brakitWarn(message) {
594
- process.stderr.write(`${PREFIX} ${message}
595
- `);
596
- }
597
- function brakitDebug(message) {
598
- if (process.env.DEBUG_BRAKIT) {
599
- process.stderr.write(`${PREFIX}:debug ${message}
600
- `);
601
- }
602
- }
603
- var PREFIX;
604
- var init_log = __esm({
605
- "src/utils/log.ts"() {
606
- "use strict";
607
- PREFIX = "[brakit]";
608
- }
609
- });
610
-
611
749
  // src/instrument/adapters/shared.ts
612
750
  import { createRequire } from "module";
613
751
  function tryRequire(id) {
@@ -1845,29 +1983,25 @@ function createIngestHandler(services) {
1845
1983
  const routeEvent = (event) => {
1846
1984
  switch (event.type) {
1847
1985
  case TIMELINE_FETCH:
1848
- services.fetchStore.add(event.data);
1986
+ bus.emit("telemetry:fetch", event.data);
1849
1987
  break;
1850
1988
  case TIMELINE_LOG:
1851
- services.logStore.add(event.data);
1989
+ bus.emit("telemetry:log", event.data);
1852
1990
  break;
1853
1991
  case TIMELINE_ERROR:
1854
- services.errorStore.add(event.data);
1992
+ bus.emit("telemetry:error", event.data);
1855
1993
  break;
1856
1994
  case TIMELINE_QUERY:
1857
- services.queryStore.add(event.data);
1995
+ bus.emit("telemetry:query", event.data);
1858
1996
  break;
1859
1997
  }
1860
1998
  };
1861
- const queryStore = services.queryStore;
1862
- const fetchStore = services.fetchStore;
1863
- const logStore = services.logStore;
1864
- const errorStore = services.errorStore;
1865
- const requestStore = services.requestStore;
1999
+ const { bus, requestStore } = services;
1866
2000
  const stores = {
1867
- addQuery: (data) => queryStore.add(data),
1868
- addFetch: (data) => fetchStore.add(data),
1869
- addLog: (data) => logStore.add(data),
1870
- addError: (data) => errorStore.add(data),
2001
+ addQuery: (data) => bus.emit("telemetry:query", data),
2002
+ addFetch: (data) => bus.emit("telemetry:fetch", data),
2003
+ addLog: (data) => bus.emit("telemetry:log", data),
2004
+ addError: (data) => bus.emit("telemetry:error", data),
1871
2005
  addRequest: (data) => requestStore.add(data)
1872
2006
  };
1873
2007
  return (req, res) => {
@@ -2426,7 +2560,7 @@ var init_issue_store = __esm({
2426
2560
  existing.occurrences++;
2427
2561
  existing.issue = issue;
2428
2562
  existing.cleanHitsSinceLastSeen = 0;
2429
- if (existing.state === "resolved" || existing.state === "stale") {
2563
+ if (existing.aiStatus !== "wont_fix" && (existing.state === "resolved" || existing.state === "stale")) {
2430
2564
  existing.state = "regressed";
2431
2565
  existing.resolvedAt = null;
2432
2566
  }
@@ -2452,10 +2586,11 @@ var init_issue_store = __esm({
2452
2586
  return stateful;
2453
2587
  }
2454
2588
  /**
2455
- * Reconcile issues against the current analysis results using evidence-based resolution.
2456
- *
2457
- * @param currentIssueIds - IDs of issues detected in the current analysis cycle
2458
- * @param activeEndpoints - Endpoints that had requests in the current cycle
2589
+ * Evidence-based reconciliation: for each active issue whose endpoint had
2590
+ * traffic but the issue was NOT re-detected, increment cleanHitsSinceLastSeen.
2591
+ * After CLEAN_HITS_FOR_RESOLUTION consecutive clean cycles, auto-resolve.
2592
+ * Issues on endpoints with no recent traffic are marked stale after STALE_ISSUE_TTL_MS.
2593
+ * Resolved and stale issues are pruned after their respective TTLs expire.
2459
2594
  */
2460
2595
  reconcile(currentIssueIds, activeEndpoints) {
2461
2596
  const now = Date.now();
@@ -2605,11 +2740,22 @@ var init_project = __esm({
2605
2740
  "use strict";
2606
2741
  init_fs();
2607
2742
  FRAMEWORKS = [
2743
+ // Meta-frameworks first (they bundle Express/Vite internally)
2608
2744
  { name: "nextjs", dep: "next", devCmd: "next dev", bin: "next", defaultPort: 3e3, devArgs: ["dev", "--port"] },
2609
2745
  { name: "remix", dep: "@remix-run/dev", devCmd: "remix dev", bin: "remix", defaultPort: 3e3, devArgs: ["dev"] },
2610
2746
  { name: "nuxt", dep: "nuxt", devCmd: "nuxt dev", bin: "nuxt", defaultPort: 3e3, devArgs: ["dev", "--port"] },
2611
- { name: "vite", dep: "vite", devCmd: "vite", bin: "vite", defaultPort: 5173, devArgs: ["--port"] },
2612
- { name: "astro", dep: "astro", devCmd: "astro dev", bin: "astro", defaultPort: 4321, devArgs: ["dev", "--port"] }
2747
+ { name: "astro", dep: "astro", devCmd: "astro dev", bin: "astro", defaultPort: 4321, devArgs: ["dev", "--port"] },
2748
+ { name: "nestjs", dep: "@nestjs/core", devCmd: "nest start", bin: "nest", defaultPort: 3e3, devArgs: ["--watch"] },
2749
+ { name: "adonis", dep: "@adonisjs/core", devCmd: "node ace serve", bin: "ace", defaultPort: 3333, devArgs: ["serve", "--watch"] },
2750
+ { name: "sails", dep: "sails", devCmd: "sails lift", bin: "sails", defaultPort: 1337, devArgs: ["lift"] },
2751
+ // Server frameworks
2752
+ { name: "hono", dep: "hono", devCmd: "node", bin: "node", defaultPort: 3e3 },
2753
+ { name: "fastify", dep: "fastify", devCmd: "node", bin: "node", defaultPort: 3e3 },
2754
+ { name: "koa", dep: "koa", devCmd: "node", bin: "node", defaultPort: 3e3 },
2755
+ { name: "hapi", dep: "@hapi/hapi", devCmd: "node", bin: "node", defaultPort: 3e3 },
2756
+ { name: "express", dep: "express", devCmd: "node", bin: "node", defaultPort: 3e3 },
2757
+ // Bundlers (last — likely used alongside a framework above)
2758
+ { name: "vite", dep: "vite", devCmd: "vite", bin: "vite", defaultPort: 5173, devArgs: ["--port"] }
2613
2759
  ];
2614
2760
  }
2615
2761
  });
@@ -4235,7 +4381,7 @@ var init_src = __esm({
4235
4381
  init_engine();
4236
4382
  init_insights2();
4237
4383
  init_insights();
4238
- VERSION = "0.10.0";
4384
+ VERSION = "0.10.1";
4239
4385
  }
4240
4386
  });
4241
4387
 
@@ -4255,7 +4401,10 @@ function getBaseStyles() {
4255
4401
  --red:#dc2626;
4256
4402
  --cyan:#0891b2;
4257
4403
  --green-bg:rgba(22,163,74,0.08);--green-bg-subtle:rgba(22,163,74,0.05);--green-border:rgba(22,163,74,0.2);--green-border-subtle:rgba(22,163,74,0.15);
4258
- --amber-bg:rgba(217,119,6,0.07);--red-bg:rgba(220,38,38,0.07);--blue-bg:rgba(37,99,235,0.08);--cyan-bg:rgba(8,145,178,0.07);
4404
+ --amber-bg:rgba(217,119,6,0.08);--amber-border:rgba(217,119,6,0.15);
4405
+ --red-bg:rgba(220,38,38,0.08);--red-border:rgba(220,38,38,0.2);
4406
+ --blue-bg:rgba(37,99,235,0.08);--cyan-bg:rgba(8,145,178,0.07);
4407
+ --accent-bg:rgba(99,102,241,0.08);
4259
4408
  --sidebar-width:232px;--header-height:52px;
4260
4409
  --radius:8px;--radius-sm:6px;
4261
4410
  --shadow-sm:0 1px 3px rgba(0,0,0,0.06),0 1px 2px rgba(0,0,0,0.03);
@@ -4312,6 +4461,7 @@ function getLayoutStyles() {
4312
4461
  .sidebar-logo .logo-version{font-weight:400;font-size:11px;color:var(--text-muted);margin-left:8px;letter-spacing:0}
4313
4462
  .sidebar-nav{padding:12px;flex:1}
4314
4463
  .sidebar-section{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.8px;color:var(--text-muted);padding:16px 12px 8px}
4464
+ .sidebar-divider{height:1px;background:var(--border-subtle);margin:8px 12px}
4315
4465
  .sidebar-item{display:flex;align-items:center;gap:12px;padding:10px 12px;border-radius:var(--radius);color:var(--text-dim);font-size:14px;font-weight:500;cursor:pointer;transition:all .15s;border:none;background:transparent;width:100%;text-align:left;font-family:var(--sans)}
4316
4466
  .sidebar-item:hover{background:var(--bg-hover);color:var(--text)}
4317
4467
  .sidebar-item.active{background:var(--bg-active);color:var(--accent)}
@@ -4345,7 +4495,7 @@ function getLayoutStyles() {
4345
4495
  /* Content */
4346
4496
  .main-content{flex:1;overflow-y:auto}
4347
4497
  bk-dashboard{display:contents}
4348
- bk-overview-view,bk-flows-view,bk-requests-view,bk-fetches-view,bk-queries-view,bk-errors-view,bk-logs-view,bk-security-view,bk-performance-view,bk-timeline-panel,bk-empty-state{display:block}
4498
+ bk-overview-view,bk-flows-view,bk-requests-view,bk-fetches-view,bk-queries-view,bk-errors-view,bk-logs-view,bk-security-view,bk-performance-view,bk-explorer-view,bk-insights-view,bk-timeline-panel,bk-empty-state{display:block}
4349
4499
  bk-graph-view{display:block}
4350
4500
  bk-method-badge,bk-status-pill,bk-duration-label,bk-copy-button{display:inline-flex;flex-shrink:0}
4351
4501
  bk-stat-card{display:inline-flex}
@@ -4732,53 +4882,25 @@ var init_graph2 = __esm({
4732
4882
  // src/dashboard/styles/overview.ts
4733
4883
  function getOverviewStyles() {
4734
4884
  return `
4735
- /* Overview */
4736
- .ov-container{padding:24px 28px}
4737
-
4738
- /* Summary banner */
4739
- .ov-summary{display:flex;gap:10px;margin-bottom:24px;flex-wrap:wrap}
4740
- .ov-stat{display:flex;flex-direction:column;gap:4px;flex:1;min-width:100px;padding:16px 18px;background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow-sm);transition:box-shadow var(--transition, .15s ease)}
4741
- .ov-stat:hover{box-shadow:var(--shadow-md)}
4742
- .ov-stat-value{font-size:22px;font-weight:700;font-family:var(--mono);color:var(--text);line-height:1.2}
4743
- .ov-stat-label{font-size:10px;text-transform:uppercase;letter-spacing:.8px;color:var(--text-muted);font-weight:600}
4744
-
4745
- /* Section header */
4746
- .ov-section-title{font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.8px;color:var(--text-muted);margin-bottom:12px;display:flex;align-items:center;gap:8px}
4747
- .ov-issue-count{font-size:11px;font-family:var(--mono);color:var(--text-dim);background:var(--bg-muted);border:1px solid var(--border);padding:1px 8px;border-radius:10px}
4748
-
4749
- /* Insight cards */
4750
- .ov-cards{display:flex;flex-direction:column;gap:8px}
4751
- .ov-card{display:flex;align-items:flex-start;gap:14px;padding:16px 20px;background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius);cursor:pointer;transition:all var(--transition, .15s ease);box-shadow:var(--shadow-sm)}
4752
- .ov-card:hover{background:var(--bg-hover);border-color:var(--border-light);box-shadow:var(--shadow-md);transform:translateY(-1px)}
4753
- .ov-card-icon{width:20px;height:20px;display:flex;align-items:center;justify-content:center;flex-shrink:0;font-size:10px;border-radius:50%;margin-top:2px}
4754
- .ov-card-icon.critical{background:rgba(220,38,38,.08);color:var(--red)}
4755
- .ov-card-icon.warning{background:rgba(217,119,6,.08);color:var(--amber)}
4756
- .ov-card-icon.info{background:rgba(37,99,235,.08);color:var(--blue)}
4757
- .ov-card-icon.resolved{background:var(--green-bg);color:var(--green)}
4758
- .ov-card-body{flex:1;min-width:0}
4759
- .ov-card-title{font-size:13px;font-weight:600;color:var(--text);margin-bottom:2px}
4760
- .ov-card-desc{font-size:12px;color:var(--text-dim);line-height:1.5}
4761
- .ov-card-detail{font-size:11px;font-family:var(--mono);color:var(--text-muted);margin-top:6px;padding:8px 10px;background:var(--bg-muted);border:1px solid var(--border-subtle);border-radius:var(--radius-sm);line-height:1.5}
4762
- .ov-card-desc strong{color:var(--text);font-family:var(--mono);font-weight:600}
4763
- .ov-card-arrow{color:var(--text-muted);font-size:12px;flex-shrink:0;margin-top:2px;font-family:var(--mono);transition:transform .15s}
4764
-
4765
- /* Expanded card */
4766
- .ov-card.expanded{border-color:var(--border-light);box-shadow:var(--shadow-md)}
4767
- .ov-card-expand{display:none;margin-top:10px;padding-top:10px;border-top:1px solid var(--border)}
4768
- .ov-card-hint{font-size:12px;color:var(--text-dim);line-height:1.5;margin-bottom:10px}
4769
- .ov-card-link{font-size:12px;font-weight:600;color:var(--blue);cursor:pointer;display:inline-block;padding:4px 0}
4770
- .ov-card-link:hover{text-decoration:underline}
4771
- .ov-detail-label{font-size:11px;font-weight:600;color:var(--text-muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:4px}
4772
- .ov-detail-item{font-size:12px;color:var(--text);font-family:var(--mono);padding:2px 0}
4773
-
4774
- /* All-clear banner */
4775
- .ov-clear{display:flex;align-items:center;gap:12px;padding:16px 20px;background:var(--green-bg-subtle);border:1px solid var(--green-border);border-radius:var(--radius);color:var(--green);font-size:13px;font-weight:500}
4776
- .ov-clear-icon{font-size:16px}
4885
+ .ov-container{padding:28px}
4777
4886
 
4778
- /* Resolved section */
4779
- .ov-resolved-title{margin-top:24px}
4780
- .ov-card-resolved{opacity:.7;border-color:var(--green-border);cursor:default}
4781
- .ov-card-resolved:hover{opacity:1;box-shadow:var(--shadow-sm)}
4887
+ .ov-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:16px}
4888
+
4889
+ .ov-card-nav{display:flex;align-items:center;gap:16px;padding:20px 24px;background:var(--bg-card);border:1px solid var(--border);border-radius:12px;cursor:pointer;transition:all .18s ease;box-shadow:0 1px 3px rgba(0,0,0,.04)}
4890
+ .ov-card-nav:hover{border-color:var(--border-light);box-shadow:0 4px 16px rgba(0,0,0,.06);transform:translateY(-2px)}
4891
+ .ov-card-nav:active{transform:translateY(0)}
4892
+
4893
+ .ov-card-empty{opacity:.55}
4894
+ .ov-card-empty:hover{opacity:.8}
4895
+
4896
+ .ov-card-icon-lg{font-size:22px;width:40px;height:40px;display:flex;align-items:center;justify-content:center;flex-shrink:0;background:var(--bg-muted);border-radius:10px;color:var(--text-muted)}
4897
+
4898
+ .ov-card-content{flex:1;min-width:0}
4899
+ .ov-card-headline{font-size:16px;font-weight:700;color:var(--text);font-family:var(--mono);line-height:1.3}
4900
+ .ov-card-context{font-size:12px;color:var(--text-muted);margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
4901
+
4902
+ .ov-card-arrow{font-size:16px;color:var(--text-muted);opacity:0;transition:opacity .15s,transform .15s;flex-shrink:0}
4903
+ .ov-card-nav:hover .ov-card-arrow{opacity:1;transform:translateX(2px)}
4782
4904
  `;
4783
4905
  }
4784
4906
  var init_overview = __esm({
@@ -5172,9 +5294,93 @@ var init_graph_view = __esm({
5172
5294
  }
5173
5295
  });
5174
5296
 
5297
+ // src/dashboard/styles/explorer.ts
5298
+ function getExplorerStyles() {
5299
+ return `
5300
+ /* Explorer sub-tabs */
5301
+ .explorer-tabs{display:flex;gap:0;border-bottom:1px solid var(--border);padding:0 28px;background:var(--bg);position:sticky;top:0;z-index:2}
5302
+ .explorer-tab{padding:10px 16px;font-size:13px;font-weight:500;color:var(--text-muted);background:none;border:none;border-bottom:2px solid transparent;cursor:pointer;transition:all .15s;display:flex;align-items:center;gap:6px;font-family:var(--sans);white-space:nowrap}
5303
+ .explorer-tab:hover{color:var(--text)}
5304
+ .explorer-tab.active{color:var(--accent);border-bottom-color:var(--accent)}
5305
+ .explorer-tab-count{font-size:11px;font-family:var(--mono);color:var(--text-muted);background:var(--bg-muted);padding:1px 6px;border-radius:8px}
5306
+ .explorer-tab.active .explorer-tab-count{color:var(--accent);background:var(--accent-bg)}
5307
+ `;
5308
+ }
5309
+ var init_explorer = __esm({
5310
+ "src/dashboard/styles/explorer.ts"() {
5311
+ "use strict";
5312
+ }
5313
+ });
5314
+
5315
+ // src/dashboard/styles/insights.ts
5316
+ function getInsightsStyles() {
5317
+ return `
5318
+ /* Insights filter chips */
5319
+ .insights-filters{display:flex;gap:6px;padding:16px 28px;border-bottom:1px solid var(--border);background:var(--bg);position:sticky;top:0;z-index:2}
5320
+ .insights-chip{font-size:12px;font-weight:500;padding:5px 14px;border:1px solid var(--border);border-radius:20px;background:var(--bg);color:var(--text-muted);cursor:pointer;transition:all .15s;display:flex;align-items:center;gap:5px;font-family:var(--sans)}
5321
+ .insights-chip:hover{border-color:var(--text-muted);color:var(--text)}
5322
+ .insights-chip.active{background:var(--accent);color:white;border-color:var(--accent)}
5323
+ .insights-chip-count{font-size:10px;font-family:var(--mono);background:rgba(0,0,0,.08);padding:1px 5px;border-radius:8px}
5324
+ .insights-chip.active .insights-chip-count{background:rgba(255,255,255,.25)}
5325
+
5326
+ /* Insights card list */
5327
+ .insights-list{padding:16px 28px}
5328
+
5329
+ .insights-empty{display:flex;align-items:center;gap:10px;padding:24px;color:var(--green);font-size:14px;font-weight:500}
5330
+ .insights-empty-icon{font-size:18px}
5331
+
5332
+ .insights-card{display:flex;align-items:flex-start;gap:12px;padding:14px 18px;background:var(--bg-card);border:1px solid var(--border);border-radius:10px;cursor:pointer;transition:all .15s;margin-bottom:8px}
5333
+ .insights-card:hover{border-color:var(--border-light);box-shadow:0 2px 8px rgba(0,0,0,.04)}
5334
+ .insights-card.expanded{border-color:var(--border-light);box-shadow:0 2px 8px rgba(0,0,0,.04)}
5335
+ .insights-card.resolved{opacity:.55}
5336
+ .insights-card.resolved:hover{opacity:.8}
5337
+
5338
+ .insights-card-left{flex-shrink:0;padding-top:2px}
5339
+ .insights-sev{width:22px;height:22px;display:flex;align-items:center;justify-content:center;font-size:10px;border-radius:50%}
5340
+ .insights-sev.critical{background:var(--red-bg);color:var(--red)}
5341
+ .insights-sev.warning{background:var(--amber-bg);color:var(--amber)}
5342
+ .insights-sev.info{background:var(--blue-bg);color:var(--blue)}
5343
+
5344
+ .insights-card-body{flex:1;min-width:0}
5345
+ .insights-card-header{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:3px}
5346
+ .insights-card-title{font-size:13px;font-weight:600;color:var(--text)}
5347
+ .insights-card-title.resolved{text-decoration:line-through;color:var(--text-muted)}
5348
+ .insights-card-cat{font-size:9px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;color:var(--text-muted);background:var(--bg-muted);padding:1px 6px;border-radius:4px}
5349
+ .insights-card-count{font-size:11px;font-family:var(--mono);color:var(--text-muted)}
5350
+ .insights-card-desc{font-size:12px;color:var(--text-dim);line-height:1.5}
5351
+ .insights-card-detail{font-size:11px;font-family:var(--mono);color:var(--text-muted);margin-top:6px;padding:6px 10px;background:var(--bg-muted);border:1px solid var(--border-subtle);border-radius:6px;line-height:1.5}
5352
+ .insights-card-progress{font-size:11px;color:var(--text-muted);margin-top:4px;font-family:var(--mono)}
5353
+ .insights-card-hint{font-size:12px;color:var(--text-dim);line-height:1.6;margin-top:8px;padding-top:8px;border-top:1px solid var(--border)}
5354
+
5355
+ .insights-badge-regressed{font-size:9px;font-weight:700;color:var(--red);background:var(--red-bg);padding:1px 6px;border-radius:4px}
5356
+ .insights-badge-verifying{font-size:9px;font-weight:700;color:var(--amber);background:var(--amber-bg);padding:1px 6px;border-radius:4px}
5357
+ .insights-badge-resolved{font-size:9px;font-weight:700;color:var(--green);background:var(--green-bg);padding:1px 6px;border-radius:4px}
5358
+
5359
+ .insights-card-arrow{color:var(--text-muted);font-size:12px;flex-shrink:0;padding-top:2px;font-family:var(--mono);transition:transform .15s}
5360
+
5361
+ .insights-section{display:flex;align-items:center;gap:8px;padding:14px 0 8px;margin-top:4px;font-size:12px;font-weight:700;color:var(--text);text-transform:uppercase;letter-spacing:.5px;border-top:1px solid var(--border);user-select:none}
5362
+ .insights-section:first-child{border-top:none;margin-top:0}
5363
+ .insights-section-icon{font-size:11px;width:16px;text-align:center}
5364
+ .insights-section-count{font-size:11px;font-family:var(--mono);color:var(--text-muted);background:var(--bg-muted);padding:1px 7px;border-radius:8px;font-weight:500}
5365
+ .insights-section-regressed{color:var(--red)}
5366
+ .insights-section-regressed .insights-section-count{color:var(--red);background:var(--red-bg)}
5367
+ .insights-section-verifying{color:var(--amber)}
5368
+ .insights-section-verifying .insights-section-count{color:var(--amber);background:var(--amber-bg)}
5369
+ .insights-section-resolved{color:var(--green)}
5370
+ .insights-section-resolved .insights-section-count{color:var(--green);background:var(--green-bg)}
5371
+ .insights-section-dismissed{color:var(--text-muted);cursor:pointer}
5372
+ .insights-section-dismissed:hover{color:var(--text)}
5373
+ `;
5374
+ }
5375
+ var init_insights3 = __esm({
5376
+ "src/dashboard/styles/insights.ts"() {
5377
+ "use strict";
5378
+ }
5379
+ });
5380
+
5175
5381
  // src/dashboard/styles.ts
5176
5382
  function getStyles() {
5177
- return getBaseStyles() + getLayoutStyles() + getFlowStyles() + getRequestStyles() + getPerformanceStyles() + getOverviewStyles() + getSecurityStyles() + getTimelineStyles() + getGraphViewStyles();
5383
+ return getBaseStyles() + getLayoutStyles() + getFlowStyles() + getRequestStyles() + getPerformanceStyles() + getOverviewStyles() + getSecurityStyles() + getTimelineStyles() + getGraphViewStyles() + getExplorerStyles() + getInsightsStyles();
5178
5384
  }
5179
5385
  var init_styles = __esm({
5180
5386
  "src/dashboard/styles.ts"() {
@@ -5188,6 +5394,8 @@ var init_styles = __esm({
5188
5394
  init_security2();
5189
5395
  init_timeline();
5190
5396
  init_graph_view();
5397
+ init_explorer();
5398
+ init_insights3();
5191
5399
  }
5192
5400
  });
5193
5401
 
@@ -5435,8 +5643,8 @@ function trackSession(services) {
5435
5643
  tabs_viewed: [...session.tabsViewed],
5436
5644
  dashboard_opened: session.dashboardOpened,
5437
5645
  explain_used: session.explainUsed,
5438
- session_duration_s: Math.round((now - session.startTime) / 1e3),
5439
- // Enhanced fields
5646
+ session_duration_s: Math.ceil((now - session.startTime) / 1e3),
5647
+ session_duration_ms: now - session.startTime,
5440
5648
  setup_succeeded: session.setupSucceeded,
5441
5649
  setup_duration_ms: session.setupDurationMs,
5442
5650
  framework_detection_candidates: session.frameworkCandidates,
@@ -6729,6 +6937,7 @@ function startTerminalInsights(services, proxyPort) {
6729
6937
  const resolvedLines = [];
6730
6938
  const regressedLines = [];
6731
6939
  for (const si of issues) {
6940
+ if (si.aiStatus === "wont_fix") continue;
6732
6941
  if (si.state === "resolved") {
6733
6942
  if (resolvedKeys.has(si.issueId)) continue;
6734
6943
  resolvedKeys.add(si.issueId);
@@ -6873,125 +7082,6 @@ var init_guard = __esm({
6873
7082
  }
6874
7083
  });
6875
7084
 
6876
- // src/runtime/capture.ts
6877
- import { gunzip, brotliDecompress, inflate } from "zlib";
6878
- function outgoingToIncoming(headers2) {
6879
- const result = {};
6880
- for (const [key, value] of Object.entries(headers2)) {
6881
- if (value === void 0) continue;
6882
- if (Array.isArray(value)) {
6883
- result[key] = value.map(String);
6884
- } else {
6885
- result[key] = String(value);
6886
- }
6887
- }
6888
- return result;
6889
- }
6890
- function getDecompressor(encoding) {
6891
- if (encoding === CONTENT_ENCODING_GZIP) return gunzip;
6892
- if (encoding === CONTENT_ENCODING_BR) return brotliDecompress;
6893
- if (encoding === CONTENT_ENCODING_DEFLATE) return inflate;
6894
- return null;
6895
- }
6896
- function decompressAsync(body, encoding) {
6897
- const decompressor = getDecompressor(encoding);
6898
- if (!decompressor) return Promise.resolve(body);
6899
- return new Promise((resolve6) => {
6900
- decompressor(body, (err, result) => {
6901
- resolve6(err ? body : result);
6902
- });
6903
- });
6904
- }
6905
- function toBuffer(chunk) {
6906
- if (Buffer.isBuffer(chunk)) return chunk;
6907
- if (chunk instanceof Uint8Array) return Buffer.from(chunk.buffer, chunk.byteOffset, chunk.byteLength);
6908
- if (typeof chunk === "string") return Buffer.from(chunk);
6909
- return null;
6910
- }
6911
- function captureInProcess(req, res, requestId, requestStore, isChild = false) {
6912
- const startTime = performance.now();
6913
- const method = req.method ?? "GET";
6914
- const resChunks = [];
6915
- let resSize = 0;
6916
- const originalWrite = res.write;
6917
- const originalEnd = res.end;
6918
- let truncated = false;
6919
- res.write = function(...args) {
6920
- try {
6921
- const chunk = args[0];
6922
- if (chunk != null && typeof chunk !== "function") {
6923
- if (resSize < DEFAULT_MAX_BODY_CAPTURE) {
6924
- const buf = toBuffer(chunk);
6925
- if (buf) {
6926
- resChunks.push(buf);
6927
- resSize += buf.length;
6928
- }
6929
- } else {
6930
- truncated = true;
6931
- }
6932
- }
6933
- } catch (e) {
6934
- brakitDebug(`capture write: ${getErrorMessage(e)}`);
6935
- }
6936
- return originalWrite.apply(this, args);
6937
- };
6938
- res.end = function(...args) {
6939
- try {
6940
- const chunk = typeof args[0] !== "function" ? args[0] : void 0;
6941
- if (chunk != null && resSize < DEFAULT_MAX_BODY_CAPTURE) {
6942
- const buf = toBuffer(chunk);
6943
- if (buf) {
6944
- resChunks.push(buf);
6945
- }
6946
- }
6947
- } catch (e) {
6948
- brakitDebug(`capture end: ${getErrorMessage(e)}`);
6949
- }
6950
- const result = originalEnd.apply(this, args);
6951
- const endTime = performance.now();
6952
- const encoding = String(res.getHeader("content-encoding") ?? "").toLowerCase();
6953
- const statusCode = res.statusCode;
6954
- const responseHeaders = outgoingToIncoming(res.getHeaders());
6955
- const responseContentType = String(res.getHeader("content-type") ?? "");
6956
- const capturedChunks = resChunks.slice();
6957
- if (!isChild) {
6958
- void (async () => {
6959
- try {
6960
- let body = capturedChunks.length > 0 ? Buffer.concat(capturedChunks) : null;
6961
- if (body && encoding && !truncated) {
6962
- body = await decompressAsync(body, encoding);
6963
- }
6964
- requestStore.capture({
6965
- requestId,
6966
- method,
6967
- url: req.url ?? "/",
6968
- requestHeaders: req.headers,
6969
- requestBody: null,
6970
- statusCode,
6971
- responseHeaders,
6972
- responseBody: body,
6973
- responseContentType,
6974
- startTime,
6975
- endTime,
6976
- config: { maxBodyCapture: DEFAULT_MAX_BODY_CAPTURE }
6977
- });
6978
- } catch (e) {
6979
- brakitDebug(`capture store: ${getErrorMessage(e)}`);
6980
- }
6981
- })();
6982
- }
6983
- return result;
6984
- };
6985
- }
6986
- var init_capture = __esm({
6987
- "src/runtime/capture.ts"() {
6988
- "use strict";
6989
- init_constants();
6990
- init_log();
6991
- init_type_guards();
6992
- }
6993
- });
6994
-
6995
7085
  // src/runtime/interceptor.ts
6996
7086
  import http from "http";
6997
7087
  import { randomUUID as randomUUID8 } from "crypto";
@@ -7177,7 +7267,8 @@ function registerLifecycle(allServices, stores, services, cwd) {
7177
7267
  process.on("SIGTERM", () => {
7178
7268
  recordExitReason(EXIT_REASON_SIGTERM);
7179
7269
  });
7180
- process.on("beforeExit", () => {
7270
+ process.on("beforeExit", async () => {
7271
+ await drainPendingCaptures();
7181
7272
  recordExitReason(EXIT_REASON_CLEAN);
7182
7273
  sendTelemetry();
7183
7274
  });
@@ -7270,6 +7361,7 @@ var init_setup = __esm({
7270
7361
  "src/runtime/setup.ts"() {
7271
7362
  "use strict";
7272
7363
  init_fetch();
7364
+ init_capture();
7273
7365
  init_console();
7274
7366
  init_errors();
7275
7367
  init_adapters();