brakit 0.7.6 → 0.8.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.
@@ -9,7 +9,7 @@ var __export = (target, all) => {
9
9
  };
10
10
 
11
11
  // src/constants/routes.ts
12
- var DASHBOARD_PREFIX, DASHBOARD_API_REQUESTS, DASHBOARD_API_EVENTS, DASHBOARD_API_FLOWS, DASHBOARD_API_CLEAR, DASHBOARD_API_LOGS, DASHBOARD_API_FETCHES, DASHBOARD_API_ERRORS, DASHBOARD_API_QUERIES, DASHBOARD_API_INGEST, DASHBOARD_API_METRICS, DASHBOARD_API_ACTIVITY, DASHBOARD_API_METRICS_LIVE, DASHBOARD_API_INSIGHTS, DASHBOARD_API_SECURITY, DASHBOARD_API_TAB;
12
+ var DASHBOARD_PREFIX, DASHBOARD_API_REQUESTS, DASHBOARD_API_EVENTS, DASHBOARD_API_FLOWS, DASHBOARD_API_CLEAR, DASHBOARD_API_LOGS, DASHBOARD_API_FETCHES, DASHBOARD_API_ERRORS, DASHBOARD_API_QUERIES, DASHBOARD_API_INGEST, DASHBOARD_API_METRICS, DASHBOARD_API_ACTIVITY, DASHBOARD_API_METRICS_LIVE, DASHBOARD_API_INSIGHTS, DASHBOARD_API_SECURITY, DASHBOARD_API_TAB, DASHBOARD_API_FINDINGS;
13
13
  var init_routes = __esm({
14
14
  "src/constants/routes.ts"() {
15
15
  "use strict";
@@ -29,11 +29,12 @@ var init_routes = __esm({
29
29
  DASHBOARD_API_INSIGHTS = "/__brakit/api/insights";
30
30
  DASHBOARD_API_SECURITY = "/__brakit/api/security";
31
31
  DASHBOARD_API_TAB = "/__brakit/api/tab";
32
+ DASHBOARD_API_FINDINGS = "/__brakit/api/findings";
32
33
  }
33
34
  });
34
35
 
35
36
  // src/constants/limits.ts
36
- var MAX_REQUEST_ENTRIES, DEFAULT_MAX_BODY_CAPTURE, DEFAULT_API_LIMIT, MAX_TELEMETRY_ENTRIES, MAX_TAB_NAME_LENGTH;
37
+ var MAX_REQUEST_ENTRIES, DEFAULT_MAX_BODY_CAPTURE, DEFAULT_API_LIMIT, MAX_TELEMETRY_ENTRIES, MAX_TAB_NAME_LENGTH, MAX_INGEST_BYTES, TERMINAL_TRUNCATE_LENGTH, SENSITIVE_MASK_MIN_LENGTH, SENSITIVE_MASK_VISIBLE_CHARS;
37
38
  var init_limits = __esm({
38
39
  "src/constants/limits.ts"() {
39
40
  "use strict";
@@ -42,11 +43,15 @@ var init_limits = __esm({
42
43
  DEFAULT_API_LIMIT = 500;
43
44
  MAX_TELEMETRY_ENTRIES = 1e3;
44
45
  MAX_TAB_NAME_LENGTH = 32;
46
+ MAX_INGEST_BYTES = 10 * 1024 * 1024;
47
+ TERMINAL_TRUNCATE_LENGTH = 80;
48
+ SENSITIVE_MASK_MIN_LENGTH = 8;
49
+ SENSITIVE_MASK_VISIBLE_CHARS = 4;
45
50
  }
46
51
  });
47
52
 
48
53
  // src/constants/thresholds.ts
49
- var FLOW_GAP_MS, SLOW_REQUEST_THRESHOLD_MS, MIN_POLLING_SEQUENCE, ENDPOINT_TRUNCATE_LENGTH, N1_QUERY_THRESHOLD, ERROR_RATE_THRESHOLD_PCT, SLOW_ENDPOINT_THRESHOLD_MS, MIN_REQUESTS_FOR_INSIGHT, HIGH_QUERY_COUNT_PER_REQ, CROSS_ENDPOINT_MIN_ENDPOINTS, CROSS_ENDPOINT_PCT, CROSS_ENDPOINT_MIN_OCCURRENCES, REDUNDANT_QUERY_MIN_COUNT, LARGE_RESPONSE_BYTES, HIGH_ROW_COUNT, OVERFETCH_MIN_REQUESTS, OVERFETCH_MIN_FIELDS, OVERFETCH_MIN_INTERNAL_IDS, OVERFETCH_NULL_RATIO, REGRESSION_PCT_THRESHOLD, REGRESSION_MIN_INCREASE_MS, REGRESSION_MIN_REQUESTS, QUERY_COUNT_REGRESSION_RATIO, OVERFETCH_MANY_FIELDS, OVERFETCH_UNWRAP_MIN_SIZE, MAX_DUPLICATE_INSIGHTS;
54
+ var FLOW_GAP_MS, SLOW_REQUEST_THRESHOLD_MS, MIN_POLLING_SEQUENCE, ENDPOINT_TRUNCATE_LENGTH, N1_QUERY_THRESHOLD, ERROR_RATE_THRESHOLD_PCT, SLOW_ENDPOINT_THRESHOLD_MS, MIN_REQUESTS_FOR_INSIGHT, HIGH_QUERY_COUNT_PER_REQ, CROSS_ENDPOINT_MIN_ENDPOINTS, CROSS_ENDPOINT_PCT, CROSS_ENDPOINT_MIN_OCCURRENCES, REDUNDANT_QUERY_MIN_COUNT, LARGE_RESPONSE_BYTES, HIGH_ROW_COUNT, OVERFETCH_MIN_REQUESTS, OVERFETCH_MIN_FIELDS, OVERFETCH_MIN_INTERNAL_IDS, OVERFETCH_NULL_RATIO, REGRESSION_PCT_THRESHOLD, REGRESSION_MIN_INCREASE_MS, REGRESSION_MIN_REQUESTS, QUERY_COUNT_REGRESSION_RATIO, OVERFETCH_MANY_FIELDS, OVERFETCH_UNWRAP_MIN_SIZE, MAX_DUPLICATE_INSIGHTS, INSIGHT_WINDOW_PER_ENDPOINT, RESOLVE_AFTER_ABSENCES, RESOLVED_INSIGHT_TTL_MS;
50
55
  var init_thresholds = __esm({
51
56
  "src/constants/thresholds.ts"() {
52
57
  "use strict";
@@ -76,6 +81,9 @@ var init_thresholds = __esm({
76
81
  OVERFETCH_MANY_FIELDS = 12;
77
82
  OVERFETCH_UNWRAP_MIN_SIZE = 3;
78
83
  MAX_DUPLICATE_INSIGHTS = 3;
84
+ INSIGHT_WINDOW_PER_ENDPOINT = 2;
85
+ RESOLVE_AFTER_ABSENCES = 3;
86
+ RESOLVED_INSIGHT_TTL_MS = 18e5;
79
87
  }
80
88
  });
81
89
 
@@ -93,7 +101,7 @@ var init_transport = __esm({
93
101
  });
94
102
 
95
103
  // src/constants/metrics.ts
96
- var METRICS_DIR, METRICS_FILE, METRICS_FLUSH_INTERVAL_MS, METRICS_MAX_SESSIONS, METRICS_MAX_DATA_POINTS;
104
+ var METRICS_DIR, METRICS_FILE, METRICS_FLUSH_INTERVAL_MS, METRICS_MAX_SESSIONS, METRICS_MAX_DATA_POINTS, PORT_FILE, FINDINGS_FILE, FINDINGS_FLUSH_INTERVAL_MS;
97
105
  var init_metrics = __esm({
98
106
  "src/constants/metrics.ts"() {
99
107
  "use strict";
@@ -102,13 +110,25 @@ var init_metrics = __esm({
102
110
  METRICS_FLUSH_INTERVAL_MS = 3e4;
103
111
  METRICS_MAX_SESSIONS = 50;
104
112
  METRICS_MAX_DATA_POINTS = 200;
113
+ PORT_FILE = ".brakit/port";
114
+ FINDINGS_FILE = ".brakit/findings.json";
115
+ FINDINGS_FLUSH_INTERVAL_MS = 1e4;
105
116
  }
106
117
  });
107
118
 
108
119
  // src/constants/headers.ts
120
+ var SENSITIVE_HEADER_NAMES;
109
121
  var init_headers = __esm({
110
122
  "src/constants/headers.ts"() {
111
123
  "use strict";
124
+ SENSITIVE_HEADER_NAMES = /* @__PURE__ */ new Set([
125
+ "authorization",
126
+ "cookie",
127
+ "set-cookie",
128
+ "proxy-authorization",
129
+ "x-api-key",
130
+ "x-auth-token"
131
+ ]);
112
132
  }
113
133
  });
114
134
 
@@ -148,6 +168,13 @@ var init_network = __esm({
148
168
  }
149
169
  });
150
170
 
171
+ // src/constants/mcp.ts
172
+ var init_mcp = __esm({
173
+ "src/constants/mcp.ts"() {
174
+ "use strict";
175
+ }
176
+ });
177
+
151
178
  // src/constants/index.ts
152
179
  var init_constants = __esm({
153
180
  "src/constants/index.ts"() {
@@ -159,6 +186,7 @@ var init_constants = __esm({
159
186
  init_metrics();
160
187
  init_headers();
161
188
  init_network();
189
+ init_mcp();
162
190
  }
163
191
  });
164
192
 
@@ -1309,9 +1337,14 @@ var init_math = __esm({
1309
1337
  function getEndpointKey(method, path) {
1310
1338
  return `${method} ${path}`;
1311
1339
  }
1340
+ function extractEndpointFromDesc(desc) {
1341
+ return desc.match(ENDPOINT_PREFIX_RE)?.[1] ?? null;
1342
+ }
1343
+ var ENDPOINT_PREFIX_RE;
1312
1344
  var init_endpoint = __esm({
1313
1345
  "src/utils/endpoint.ts"() {
1314
1346
  "use strict";
1347
+ ENDPOINT_PREFIX_RE = /^(\S+\s+\S+)/;
1315
1348
  }
1316
1349
  });
1317
1350
 
@@ -1654,7 +1687,7 @@ function maskSensitiveHeaders(headers) {
1654
1687
  for (const [key, value] of Object.entries(headers)) {
1655
1688
  if (SENSITIVE_HEADER_NAMES.has(key.toLowerCase())) {
1656
1689
  const s = String(value);
1657
- masked[key] = s.length <= 8 ? "****" : s.slice(0, 4) + "..." + s.slice(-4);
1690
+ masked[key] = s.length <= SENSITIVE_MASK_MIN_LENGTH ? "****" : s.slice(0, SENSITIVE_MASK_VISIBLE_CHARS) + "..." + s.slice(-SENSITIVE_MASK_VISIBLE_CHARS);
1658
1691
  } else {
1659
1692
  masked[key] = value;
1660
1693
  }
@@ -1701,19 +1734,11 @@ function handleTelemetryGet(req, res, store) {
1701
1734
  const entries = requestId ? store.getByRequest(requestId) : [...store.getAll()];
1702
1735
  sendJson(req, res, 200, { total: entries.length, entries: entries.reverse() });
1703
1736
  }
1704
- var SENSITIVE_HEADER_NAMES;
1705
1737
  var init_shared2 = __esm({
1706
1738
  "src/dashboard/api/shared.ts"() {
1707
1739
  "use strict";
1708
1740
  init_constants();
1709
- SENSITIVE_HEADER_NAMES = /* @__PURE__ */ new Set([
1710
- "authorization",
1711
- "cookie",
1712
- "set-cookie",
1713
- "proxy-authorization",
1714
- "x-api-key",
1715
- "x-auth-token"
1716
- ]);
1741
+ init_limits();
1717
1742
  }
1718
1743
  });
1719
1744
 
@@ -1770,7 +1795,7 @@ function handleApiFlows(req, res) {
1770
1795
  }));
1771
1796
  sendJson(req, res, 200, { total: flows.length, flows });
1772
1797
  }
1773
- function createClearHandler(metricsStore) {
1798
+ function createClearHandler(metricsStore, findingStore) {
1774
1799
  return (req, res) => {
1775
1800
  if (req.method !== "POST") {
1776
1801
  sendJson(req, res, 405, { error: "Method not allowed" });
@@ -1782,6 +1807,7 @@ function createClearHandler(metricsStore) {
1782
1807
  defaultErrorStore.clear();
1783
1808
  defaultQueryStore.clear();
1784
1809
  metricsStore.reset();
1810
+ findingStore?.clear();
1785
1811
  sendJson(req, res, 200, { cleared: true });
1786
1812
  };
1787
1813
  }
@@ -1892,7 +1918,6 @@ function handleApiIngest(req, res) {
1892
1918
  sendJson(req, res, 405, { error: "Method not allowed" });
1893
1919
  return;
1894
1920
  }
1895
- const MAX_INGEST_BYTES = 10 * 1024 * 1024;
1896
1921
  const chunks = [];
1897
1922
  let totalSize = 0;
1898
1923
  req.on("data", (chunk) => {
@@ -1934,6 +1959,7 @@ var init_ingest = __esm({
1934
1959
  "src/dashboard/api/ingest.ts"() {
1935
1960
  "use strict";
1936
1961
  init_store();
1962
+ init_limits();
1937
1963
  init_shared2();
1938
1964
  }
1939
1965
  });
@@ -2039,13 +2065,13 @@ var init_api = __esm({
2039
2065
  function createInsightsHandler(engine) {
2040
2066
  return (req, res) => {
2041
2067
  if (!requireGet(req, res)) return;
2042
- sendJson(req, res, 200, { insights: engine.getInsights() });
2068
+ sendJson(req, res, 200, { insights: engine.getStatefulInsights() });
2043
2069
  };
2044
2070
  }
2045
2071
  function createSecurityHandler(engine) {
2046
2072
  return (req, res) => {
2047
2073
  if (!requireGet(req, res)) return;
2048
- sendJson(req, res, 200, { findings: engine.getFindings() });
2074
+ sendJson(req, res, 200, { findings: engine.getStatefulFindings() });
2049
2075
  };
2050
2076
  }
2051
2077
  var init_insights = __esm({
@@ -2055,6 +2081,33 @@ var init_insights = __esm({
2055
2081
  }
2056
2082
  });
2057
2083
 
2084
+ // src/dashboard/api/findings.ts
2085
+ function createFindingsHandler(findingStore) {
2086
+ return (req, res) => {
2087
+ if (!requireGet(req, res)) return;
2088
+ const url = new URL(req.url ?? "/", "http://localhost");
2089
+ const stateParam = url.searchParams.get("state");
2090
+ let findings;
2091
+ if (stateParam && VALID_STATES.has(stateParam)) {
2092
+ findings = findingStore.getByState(stateParam);
2093
+ } else {
2094
+ findings = findingStore.getAll();
2095
+ }
2096
+ sendJson(req, res, 200, {
2097
+ total: findings.length,
2098
+ findings
2099
+ });
2100
+ };
2101
+ }
2102
+ var VALID_STATES;
2103
+ var init_findings = __esm({
2104
+ "src/dashboard/api/findings.ts"() {
2105
+ "use strict";
2106
+ init_shared2();
2107
+ VALID_STATES = /* @__PURE__ */ new Set(["open", "fixing", "resolved"]);
2108
+ }
2109
+ });
2110
+
2058
2111
  // src/dashboard/sse.ts
2059
2112
  function createSSEHandler(engine) {
2060
2113
  return (req, res) => {
@@ -2093,9 +2146,9 @@ data: ${data}
2093
2146
  const queryListener = (entry) => {
2094
2147
  writeEvent("query", JSON.stringify(entry));
2095
2148
  };
2096
- const analysisListener = engine ? (insights, findings) => {
2097
- writeEvent("insights", JSON.stringify(insights));
2098
- writeEvent("security", JSON.stringify(findings));
2149
+ const analysisListener = engine ? ({ statefulInsights, statefulFindings }) => {
2150
+ writeEvent("insights", JSON.stringify(statefulInsights));
2151
+ writeEvent("security", JSON.stringify(statefulFindings));
2099
2152
  } : void 0;
2100
2153
  onRequest(requestListener);
2101
2154
  defaultFetchStore.onEntry(fetchListener);
@@ -2145,6 +2198,7 @@ function getBaseStyles() {
2145
2198
  --amber:#d97706;
2146
2199
  --red:#dc2626;
2147
2200
  --cyan:#0891b2;
2201
+ --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);
2148
2202
  --sidebar-width:232px;--header-height:52px;
2149
2203
  --radius:8px;--radius-sm:6px;
2150
2204
  --shadow-sm:0 1px 2px rgba(0,0,0,0.05);
@@ -2261,7 +2315,7 @@ function getFlowStyles() {
2261
2315
  .flow-label{font-weight:500;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text)}
2262
2316
  .flow-req-count{font-family:var(--mono);font-size:12px;color:var(--text-muted);flex-shrink:0;text-align:right}
2263
2317
  .flow-badge-pill{font-size:11px;flex-shrink:0;font-family:var(--mono);font-weight:600;padding:2px 10px;border-radius:10px;text-align:center}
2264
- .flow-badge-pill.badge-clean{background:rgba(22,163,74,0.07);color:var(--green)}
2318
+ .flow-badge-pill.badge-clean{background:var(--green-bg);color:var(--green)}
2265
2319
  .flow-badge-pill.badge-warn{background:rgba(217,119,6,0.07);color:var(--amber)}
2266
2320
  .flow-badge-pill.badge-error{background:rgba(220,38,38,0.07);color:var(--red)}
2267
2321
  .flow-duration{font-family:var(--mono);font-size:12px;color:var(--text-muted);flex-shrink:0;width:60px;text-align:right}
@@ -2281,7 +2335,7 @@ function getFlowStyles() {
2281
2335
 
2282
2336
  /* Method badges */
2283
2337
  .method-badge{display:inline-flex;align-items:center;justify-content:center;padding:3px 8px;border-radius:5px;font-size:10px;font-weight:700;font-family:var(--mono);letter-spacing:.3px;flex-shrink:0}
2284
- .method-badge-GET{background:rgba(22,163,74,0.08);color:var(--green)}
2338
+ .method-badge-GET{background:var(--green-bg);color:var(--green)}
2285
2339
  .method-badge-POST{background:rgba(37,99,235,0.08);color:var(--blue)}
2286
2340
  .method-badge-PUT,.method-badge-PATCH{background:rgba(217,119,6,0.08);color:var(--amber)}
2287
2341
  .method-badge-DELETE{background:rgba(220,38,38,0.08);color:var(--red)}
@@ -2289,7 +2343,7 @@ function getFlowStyles() {
2289
2343
 
2290
2344
  /* Status pills */
2291
2345
  .status-pill{display:inline-flex;align-items:center;padding:1px 7px;border-radius:4px;font-size:11px;font-weight:600;font-family:var(--mono);flex-shrink:0}
2292
- .status-pill-2xx{background:rgba(22,163,74,0.07);color:var(--green)}
2346
+ .status-pill-2xx{background:var(--green-bg);color:var(--green)}
2293
2347
  .status-pill-3xx{background:rgba(8,145,178,0.07);color:var(--cyan)}
2294
2348
  .status-pill-4xx{background:rgba(217,119,6,0.07);color:var(--amber)}
2295
2349
  .status-pill-5xx{background:rgba(220,38,38,0.07);color:var(--red)}
@@ -2556,6 +2610,7 @@ function getOverviewStyles() {
2556
2610
  .ov-card-icon.critical{background:rgba(220,38,38,.08);color:var(--red)}
2557
2611
  .ov-card-icon.warning{background:rgba(217,119,6,.08);color:var(--amber)}
2558
2612
  .ov-card-icon.info{background:rgba(37,99,235,.08);color:var(--blue)}
2613
+ .ov-card-icon.resolved{background:var(--green-bg);color:var(--green)}
2559
2614
  .ov-card-body{flex:1;min-width:0}
2560
2615
  .ov-card-title{font-size:13px;font-weight:600;color:var(--text);margin-bottom:2px}
2561
2616
  .ov-card-desc{font-size:12px;color:var(--text-dim);line-height:1.5}
@@ -2572,8 +2627,13 @@ function getOverviewStyles() {
2572
2627
  .ov-detail-item{font-size:12px;color:var(--text);font-family:var(--mono);padding:2px 0}
2573
2628
 
2574
2629
  /* All-clear banner */
2575
- .ov-clear{display:flex;align-items:center;gap:12px;padding:16px 20px;background:rgba(22,163,74,.06);border:1px solid rgba(22,163,74,.2);border-radius:var(--radius);color:var(--green);font-size:13px;font-weight:500}
2630
+ .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}
2576
2631
  .ov-clear-icon{font-size:16px}
2632
+
2633
+ /* Resolved section */
2634
+ .ov-resolved-title{margin-top:24px}
2635
+ .ov-card-resolved{opacity:.7;border-color:var(--green-border);cursor:default}
2636
+ .ov-card-resolved:hover{opacity:1;box-shadow:var(--shadow-sm)}
2577
2637
  `;
2578
2638
  }
2579
2639
  var init_overview = __esm({
@@ -2589,7 +2649,7 @@ function getSecurityStyles() {
2589
2649
  .sec-container{padding:24px 28px}
2590
2650
 
2591
2651
  /* All-clear */
2592
- .sec-clear{display:flex;align-items:center;gap:16px;padding:20px 24px;background:rgba(22,163,74,.05);border:1px solid rgba(22,163,74,.15);border-radius:var(--radius);margin-bottom:24px}
2652
+ .sec-clear{display:flex;align-items:center;gap:16px;padding:20px 24px;background:var(--green-bg-subtle);border:1px solid var(--green-border-subtle);border-radius:var(--radius);margin-bottom:24px}
2593
2653
  .sec-clear-icon{font-size:24px;color:var(--green);flex-shrink:0}
2594
2654
  .sec-clear-title{font-size:15px;font-weight:600;color:var(--green);margin-bottom:2px}
2595
2655
  .sec-clear-sub{font-size:12px;color:var(--text-dim)}
@@ -2625,6 +2685,19 @@ function getSecurityStyles() {
2625
2685
  .sec-item-desc{color:var(--text-dim);line-height:1.5;flex:1;min-width:0}
2626
2686
  .sec-item-desc strong{color:var(--text);font-family:var(--mono);font-weight:600}
2627
2687
  .sec-item-count{font-size:10px;font-family:var(--mono);color:var(--text-muted);flex-shrink:0;margin-left:12px}
2688
+
2689
+ /* Resolved badge in summary */
2690
+ .sec-resolved-badge{font-size:11px;font-weight:600;padding:3px 10px;border-radius:10px;background:var(--green-bg);color:var(--green);margin-left:12px}
2691
+
2692
+ /* Resolved section */
2693
+ .sec-resolved-title{display:flex;align-items:center;gap:8px;font-size:13px;font-weight:600;color:var(--text-dim);margin:20px 0 8px 0}
2694
+ .sec-resolved-check{color:var(--green);font-size:14px}
2695
+ .sec-resolved-count{font-size:11px;font-family:var(--mono);color:var(--text-muted);background:var(--bg-muted);padding:1px 8px;border-radius:10px;border:1px solid var(--border)}
2696
+ .sec-group-resolved{opacity:.7;border-color:var(--green-border)}
2697
+ .sec-group-resolved:hover{opacity:1}
2698
+ .sec-item-resolved{color:var(--text-muted)}
2699
+ .sec-item-resolved .sec-item-desc{text-decoration:line-through;text-decoration-color:var(--text-muted)}
2700
+ .sec-resolved-item-icon{color:var(--green);font-size:12px;flex-shrink:0;margin-right:8px}
2628
2701
  `;
2629
2702
  }
2630
2703
  var init_security = __esm({
@@ -2687,6 +2760,197 @@ var init_styles = __esm({
2687
2760
  }
2688
2761
  });
2689
2762
 
2763
+ // src/store/finding-id.ts
2764
+ import { createHash } from "crypto";
2765
+ function computeFindingId(finding) {
2766
+ const key = `${finding.rule}:${finding.endpoint}:${finding.desc}`;
2767
+ return createHash("sha256").update(key).digest("hex").slice(0, 16);
2768
+ }
2769
+ var init_finding_id = __esm({
2770
+ "src/store/finding-id.ts"() {
2771
+ "use strict";
2772
+ }
2773
+ });
2774
+
2775
+ // src/store/finding-store.ts
2776
+ import {
2777
+ readFileSync as readFileSync3,
2778
+ writeFileSync as writeFileSync3,
2779
+ existsSync as existsSync3,
2780
+ mkdirSync as mkdirSync3,
2781
+ renameSync as renameSync2
2782
+ } from "fs";
2783
+ import { writeFile as writeFile3, mkdir as mkdir2, rename as rename2 } from "fs/promises";
2784
+ import { resolve as resolve3 } from "path";
2785
+ var FindingStore;
2786
+ var init_finding_store = __esm({
2787
+ "src/store/finding-store.ts"() {
2788
+ "use strict";
2789
+ init_constants();
2790
+ init_fs();
2791
+ init_finding_id();
2792
+ FindingStore = class {
2793
+ constructor(rootDir) {
2794
+ this.rootDir = rootDir;
2795
+ this.metricsDir = resolve3(rootDir, METRICS_DIR);
2796
+ this.findingsPath = resolve3(rootDir, FINDINGS_FILE);
2797
+ this.tmpPath = this.findingsPath + ".tmp";
2798
+ this.load();
2799
+ }
2800
+ findings = /* @__PURE__ */ new Map();
2801
+ flushTimer = null;
2802
+ dirty = false;
2803
+ writing = false;
2804
+ findingsPath;
2805
+ tmpPath;
2806
+ metricsDir;
2807
+ start() {
2808
+ this.flushTimer = setInterval(
2809
+ () => this.flush(),
2810
+ FINDINGS_FLUSH_INTERVAL_MS
2811
+ );
2812
+ this.flushTimer.unref();
2813
+ }
2814
+ stop() {
2815
+ if (this.flushTimer) {
2816
+ clearInterval(this.flushTimer);
2817
+ this.flushTimer = null;
2818
+ }
2819
+ this.flushSync();
2820
+ }
2821
+ upsert(finding, source) {
2822
+ const id = computeFindingId(finding);
2823
+ const existing = this.findings.get(id);
2824
+ const now = Date.now();
2825
+ if (existing) {
2826
+ existing.lastSeenAt = now;
2827
+ existing.occurrences++;
2828
+ existing.finding = finding;
2829
+ if (existing.state === "resolved") {
2830
+ existing.state = "open";
2831
+ existing.resolvedAt = null;
2832
+ }
2833
+ this.dirty = true;
2834
+ return existing;
2835
+ }
2836
+ const stateful = {
2837
+ findingId: id,
2838
+ state: "open",
2839
+ source,
2840
+ finding,
2841
+ firstSeenAt: now,
2842
+ lastSeenAt: now,
2843
+ resolvedAt: null,
2844
+ occurrences: 1
2845
+ };
2846
+ this.findings.set(id, stateful);
2847
+ this.dirty = true;
2848
+ return stateful;
2849
+ }
2850
+ transition(findingId, state) {
2851
+ const finding = this.findings.get(findingId);
2852
+ if (!finding) return false;
2853
+ finding.state = state;
2854
+ if (state === "resolved") {
2855
+ finding.resolvedAt = Date.now();
2856
+ }
2857
+ this.dirty = true;
2858
+ return true;
2859
+ }
2860
+ reconcilePassive(currentFindings) {
2861
+ const currentIds = new Set(currentFindings.map(computeFindingId));
2862
+ for (const [id, stateful] of this.findings) {
2863
+ if (stateful.source === "passive" && stateful.state === "open" && !currentIds.has(id)) {
2864
+ stateful.state = "resolved";
2865
+ stateful.resolvedAt = Date.now();
2866
+ this.dirty = true;
2867
+ }
2868
+ }
2869
+ }
2870
+ getAll() {
2871
+ return [...this.findings.values()];
2872
+ }
2873
+ getByState(state) {
2874
+ return [...this.findings.values()].filter((f) => f.state === state);
2875
+ }
2876
+ get(findingId) {
2877
+ return this.findings.get(findingId);
2878
+ }
2879
+ clear() {
2880
+ this.findings.clear();
2881
+ this.dirty = true;
2882
+ }
2883
+ load() {
2884
+ try {
2885
+ if (existsSync3(this.findingsPath)) {
2886
+ const raw = readFileSync3(this.findingsPath, "utf-8");
2887
+ const parsed = JSON.parse(raw);
2888
+ if (parsed?.version === 1 && Array.isArray(parsed.findings)) {
2889
+ for (const f of parsed.findings) {
2890
+ this.findings.set(f.findingId, f);
2891
+ }
2892
+ }
2893
+ }
2894
+ } catch {
2895
+ }
2896
+ }
2897
+ flush() {
2898
+ if (!this.dirty) return;
2899
+ this.writeAsync();
2900
+ }
2901
+ flushSync() {
2902
+ if (!this.dirty) return;
2903
+ try {
2904
+ this.ensureDir();
2905
+ const data = {
2906
+ version: 1,
2907
+ findings: [...this.findings.values()]
2908
+ };
2909
+ writeFileSync3(this.tmpPath, JSON.stringify(data));
2910
+ renameSync2(this.tmpPath, this.findingsPath);
2911
+ this.dirty = false;
2912
+ } catch (err) {
2913
+ process.stderr.write(
2914
+ `[brakit] failed to save findings: ${err.message}
2915
+ `
2916
+ );
2917
+ }
2918
+ }
2919
+ async writeAsync() {
2920
+ if (this.writing) return;
2921
+ this.writing = true;
2922
+ try {
2923
+ if (!existsSync3(this.metricsDir)) {
2924
+ await mkdir2(this.metricsDir, { recursive: true });
2925
+ ensureGitignore(this.metricsDir, METRICS_DIR);
2926
+ }
2927
+ const data = {
2928
+ version: 1,
2929
+ findings: [...this.findings.values()]
2930
+ };
2931
+ await writeFile3(this.tmpPath, JSON.stringify(data));
2932
+ await rename2(this.tmpPath, this.findingsPath);
2933
+ this.dirty = false;
2934
+ } catch (err) {
2935
+ process.stderr.write(
2936
+ `[brakit] failed to save findings: ${err.message}
2937
+ `
2938
+ );
2939
+ } finally {
2940
+ this.writing = false;
2941
+ if (this.dirty) this.writeAsync();
2942
+ }
2943
+ }
2944
+ ensureDir() {
2945
+ if (!existsSync3(this.metricsDir)) {
2946
+ mkdirSync3(this.metricsDir, { recursive: true });
2947
+ ensureGitignore(this.metricsDir, METRICS_DIR);
2948
+ }
2949
+ }
2950
+ };
2951
+ }
2952
+ });
2953
+
2690
2954
  // src/detect/project.ts
2691
2955
  import { readFile as readFile2 } from "fs/promises";
2692
2956
  import { join } from "path";
@@ -3359,6 +3623,23 @@ function createEndpointGroup() {
3359
3623
  queryShapeDurations: /* @__PURE__ */ new Map()
3360
3624
  };
3361
3625
  }
3626
+ function windowByEndpoint(requests) {
3627
+ const byEndpoint = /* @__PURE__ */ new Map();
3628
+ for (const r of requests) {
3629
+ const ep = getEndpointKey(r.method, r.path);
3630
+ let list = byEndpoint.get(ep);
3631
+ if (!list) {
3632
+ list = [];
3633
+ byEndpoint.set(ep, list);
3634
+ }
3635
+ list.push(r);
3636
+ }
3637
+ const windowed = [];
3638
+ for (const [, reqs] of byEndpoint) {
3639
+ windowed.push(...reqs.slice(-INSIGHT_WINDOW_PER_ENDPOINT));
3640
+ }
3641
+ return windowed;
3642
+ }
3362
3643
  function prepareContext(ctx) {
3363
3644
  const nonStatic = ctx.requests.filter(
3364
3645
  (r) => !r.isStatic && (!r.path || !r.path.startsWith(DASHBOARD_PREFIX))
@@ -3366,8 +3647,9 @@ function prepareContext(ctx) {
3366
3647
  const queriesByReq = groupBy(ctx.queries, (q) => q.parentRequestId);
3367
3648
  const fetchesByReq = groupBy(ctx.fetches, (f) => f.parentRequestId);
3368
3649
  const reqById = new Map(nonStatic.map((r) => [r.id, r]));
3650
+ const recent = windowByEndpoint(nonStatic);
3369
3651
  const endpointGroups = /* @__PURE__ */ new Map();
3370
- for (const r of nonStatic) {
3652
+ for (const r of recent) {
3371
3653
  const ep = getEndpointKey(r.method, r.path);
3372
3654
  let g = endpointGroups.get(ep);
3373
3655
  if (!g) {
@@ -3412,6 +3694,7 @@ var init_prepare = __esm({
3412
3694
  init_collections();
3413
3695
  init_endpoint();
3414
3696
  init_constants();
3697
+ init_thresholds();
3415
3698
  init_query_helpers();
3416
3699
  }
3417
3700
  });
@@ -4100,6 +4383,69 @@ var init_insights3 = __esm({
4100
4383
  }
4101
4384
  });
4102
4385
 
4386
+ // src/analysis/insight-tracker.ts
4387
+ function computeInsightKey(insight) {
4388
+ const identifier = extractEndpointFromDesc(insight.desc) ?? insight.title;
4389
+ return `${insight.type}:${identifier}`;
4390
+ }
4391
+ var InsightTracker;
4392
+ var init_insight_tracker = __esm({
4393
+ "src/analysis/insight-tracker.ts"() {
4394
+ "use strict";
4395
+ init_endpoint();
4396
+ init_thresholds();
4397
+ InsightTracker = class {
4398
+ tracked = /* @__PURE__ */ new Map();
4399
+ reconcile(current) {
4400
+ const currentKeys = /* @__PURE__ */ new Set();
4401
+ const now = Date.now();
4402
+ for (const insight of current) {
4403
+ const key = computeInsightKey(insight);
4404
+ currentKeys.add(key);
4405
+ const existing = this.tracked.get(key);
4406
+ if (existing) {
4407
+ existing.insight = insight;
4408
+ existing.lastSeenAt = now;
4409
+ existing.consecutiveAbsences = 0;
4410
+ if (existing.state === "resolved") {
4411
+ existing.state = "open";
4412
+ existing.resolvedAt = null;
4413
+ }
4414
+ } else {
4415
+ this.tracked.set(key, {
4416
+ key,
4417
+ state: "open",
4418
+ insight,
4419
+ firstSeenAt: now,
4420
+ lastSeenAt: now,
4421
+ resolvedAt: null,
4422
+ consecutiveAbsences: 0
4423
+ });
4424
+ }
4425
+ }
4426
+ for (const [key, stateful] of this.tracked) {
4427
+ if (stateful.state === "open" && !currentKeys.has(stateful.key)) {
4428
+ stateful.consecutiveAbsences++;
4429
+ if (stateful.consecutiveAbsences >= RESOLVE_AFTER_ABSENCES) {
4430
+ stateful.state = "resolved";
4431
+ stateful.resolvedAt = now;
4432
+ }
4433
+ } else if (stateful.state === "resolved" && stateful.resolvedAt !== null && now - stateful.resolvedAt > RESOLVED_INSIGHT_TTL_MS) {
4434
+ this.tracked.delete(key);
4435
+ }
4436
+ }
4437
+ return [...this.tracked.values()];
4438
+ }
4439
+ getAll() {
4440
+ return [...this.tracked.values()];
4441
+ }
4442
+ clear() {
4443
+ this.tracked.clear();
4444
+ }
4445
+ };
4446
+ }
4447
+ });
4448
+
4103
4449
  // src/analysis/engine.ts
4104
4450
  var AnalysisEngine;
4105
4451
  var init_engine = __esm({
@@ -4111,9 +4457,11 @@ var init_engine = __esm({
4111
4457
  init_group();
4112
4458
  init_rules();
4113
4459
  init_insights3();
4460
+ init_insight_tracker();
4114
4461
  AnalysisEngine = class {
4115
- constructor(metricsStore, debounceMs = 300) {
4462
+ constructor(metricsStore, findingStore, debounceMs = 300) {
4116
4463
  this.metricsStore = metricsStore;
4464
+ this.findingStore = findingStore;
4117
4465
  this.debounceMs = debounceMs;
4118
4466
  this.scanner = createDefaultScanner();
4119
4467
  this.boundRequestListener = () => this.scheduleRecompute();
@@ -4122,8 +4470,10 @@ var init_engine = __esm({
4122
4470
  this.boundLogListener = () => this.scheduleRecompute();
4123
4471
  }
4124
4472
  scanner;
4473
+ insightTracker = new InsightTracker();
4125
4474
  cachedInsights = [];
4126
4475
  cachedFindings = [];
4476
+ cachedStatefulInsights = [];
4127
4477
  debounceTimer = null;
4128
4478
  listeners = [];
4129
4479
  boundRequestListener;
@@ -4159,6 +4509,12 @@ var init_engine = __esm({
4159
4509
  getFindings() {
4160
4510
  return this.cachedFindings;
4161
4511
  }
4512
+ getStatefulFindings() {
4513
+ return this.findingStore?.getAll() ?? [];
4514
+ }
4515
+ getStatefulInsights() {
4516
+ return this.cachedStatefulInsights;
4517
+ }
4162
4518
  scheduleRecompute() {
4163
4519
  if (this.debounceTimer) return;
4164
4520
  this.debounceTimer = setTimeout(() => {
@@ -4174,6 +4530,12 @@ var init_engine = __esm({
4174
4530
  const fetches = defaultFetchStore.getAll();
4175
4531
  const flows = groupRequestsIntoFlows(requests);
4176
4532
  this.cachedFindings = this.scanner.scan({ requests, logs });
4533
+ if (this.findingStore) {
4534
+ for (const finding of this.cachedFindings) {
4535
+ this.findingStore.upsert(finding, "passive");
4536
+ }
4537
+ this.findingStore.reconcilePassive(this.cachedFindings);
4538
+ }
4177
4539
  this.cachedInsights = computeInsights({
4178
4540
  requests,
4179
4541
  queries,
@@ -4183,9 +4545,16 @@ var init_engine = __esm({
4183
4545
  previousMetrics: this.metricsStore.getAll(),
4184
4546
  securityFindings: this.cachedFindings
4185
4547
  });
4548
+ this.cachedStatefulInsights = this.insightTracker.reconcile(this.cachedInsights);
4549
+ const update = {
4550
+ insights: this.cachedInsights,
4551
+ findings: this.cachedFindings,
4552
+ statefulFindings: this.getStatefulFindings(),
4553
+ statefulInsights: this.cachedStatefulInsights
4554
+ };
4186
4555
  for (const fn of this.listeners) {
4187
4556
  try {
4188
- fn(this.cachedInsights, this.cachedFindings);
4557
+ fn(update);
4189
4558
  } catch {
4190
4559
  }
4191
4560
  }
@@ -4199,13 +4568,14 @@ var VERSION;
4199
4568
  var init_src = __esm({
4200
4569
  "src/index.ts"() {
4201
4570
  "use strict";
4571
+ init_finding_store();
4202
4572
  init_project();
4203
4573
  init_adapter_registry();
4204
4574
  init_rules();
4205
4575
  init_engine();
4206
4576
  init_insights3();
4207
4577
  init_insights2();
4208
- VERSION = "0.7.6";
4578
+ VERSION = "0.8.1";
4209
4579
  }
4210
4580
  });
4211
4581
 
@@ -4383,7 +4753,7 @@ var init_thresholds2 = __esm({
4383
4753
  });
4384
4754
 
4385
4755
  // src/dashboard/client/constants/display.ts
4386
- var QUERY_OP_COLORS, LOG_LEVEL_COLORS, GRAPH_COLORS, DOT_COLORS, HEALTH_GRADES, CHART_GRID_COLOR, CHART_LABEL_COLOR, CHART_FONT, CHART_FONT_SM, CHART_FONT_XS, CHART_PAD, TL_TYPE_COLORS, TL_TYPE_LABELS, SENSITIVE_HEADERS, HTTP_STATUS_MAP, NAV_LABELS, CURL_SKIP_HEADERS;
4756
+ var QUERY_OP_COLORS, LOG_LEVEL_COLORS, GRAPH_COLORS, DOT_COLORS, HEALTH_GRADES, CHART_GRID_COLOR, CHART_LABEL_COLOR, CHART_FONT, CHART_FONT_SM, CHART_FONT_XS, CHART_PAD, TL_TYPE_COLORS, TL_TYPE_LABELS, SENSITIVE_HEADERS, HTTP_STATUS_MAP, NAV_LABELS, CURL_SKIP_HEADERS, SEVERITY_MAP;
4387
4757
  var init_display = __esm({
4388
4758
  "src/dashboard/client/constants/display.ts"() {
4389
4759
  "use strict";
@@ -4411,6 +4781,7 @@ var init_display = __esm({
4411
4781
  HTTP_STATUS_MAP = `{400:'Bad Request',401:'Unauthorized',403:'Forbidden',404:'Not Found',405:'Method Not Allowed',408:'Timeout',409:'Conflict',422:'Unprocessable',429:'Too Many Requests',500:'Internal Server Error',502:'Bad Gateway',503:'Service Unavailable',504:'Gateway Timeout'}`;
4412
4782
  NAV_LABELS = `{ queries: 'Queries', requests: 'Requests', actions: 'Actions', errors: 'Errors', security: 'Security', fetches: 'Fetches', logs: 'Logs', performance: 'Performance' }`;
4413
4783
  CURL_SKIP_HEADERS = `['host', 'connection', 'accept-encoding']`;
4784
+ SEVERITY_MAP = `{ critical: { icon: '\\u2717', cls: 'critical', sort: 0 }, warning: { icon: '\\u26A0', cls: 'warning', sort: 1 }, info: { icon: '\\u2139', cls: 'info', sort: 2 } }`;
4414
4785
  }
4415
4786
  });
4416
4787
 
@@ -6150,9 +6521,11 @@ function getOverviewRender() {
6150
6521
  '<div class="ov-stat"><span class="ov-stat-value">' + state.fetches.length + '</span><span class="ov-stat-label">Fetches</span></div>';
6151
6522
  container.appendChild(summary);
6152
6523
 
6153
- var insights = state.insights || [];
6524
+ var all = state.insights || [];
6525
+ var open = all.filter(function(si) { return si.state === 'open'; });
6526
+ var resolved = all.filter(function(si) { return si.state === 'resolved'; });
6154
6527
 
6155
- if (insights.length === 0) {
6528
+ if (open.length === 0 && resolved.length === 0) {
6156
6529
  var clear = document.createElement('div');
6157
6530
  clear.className = 'ov-clear';
6158
6531
  clear.innerHTML = '<span class="ov-clear-icon">\\u2713</span>All clear \u2014 no issues detected';
@@ -6160,67 +6533,104 @@ function getOverviewRender() {
6160
6533
  return;
6161
6534
  }
6162
6535
 
6163
- var title = document.createElement('div');
6164
- title.className = 'ov-section-title';
6165
- title.innerHTML = 'Issues Found <span class="ov-issue-count">' + insights.length + '</span>';
6166
- container.appendChild(title);
6167
-
6168
- var cards = document.createElement('div');
6169
- cards.className = 'ov-cards';
6536
+ if (open.length === 0 && resolved.length > 0) {
6537
+ var allFixed = document.createElement('div');
6538
+ allFixed.className = 'ov-clear';
6539
+ allFixed.innerHTML = '<span class="ov-clear-icon">\\u2713</span>All issues resolved \u2014 ' + resolved.length + ' finding' + (resolved.length !== 1 ? 's were' : ' was') + ' detected and fixed';
6540
+ container.appendChild(allFixed);
6541
+ }
6170
6542
 
6171
6543
  var NAV_LABELS = ${NAV_LABELS};
6544
+ var SEV = ${SEVERITY_MAP};
6172
6545
 
6173
- for (var i = 0; i < insights.length; i++) {
6174
- (function(insight) {
6175
- var card = document.createElement('div');
6176
- card.className = 'ov-card';
6546
+ if (open.length > 0) {
6547
+ var title = document.createElement('div');
6548
+ title.className = 'ov-section-title';
6549
+ title.innerHTML = 'Issues Found <span class="ov-issue-count">' + open.length + '</span>';
6550
+ container.appendChild(title);
6177
6551
 
6178
- var iconCls = insight.severity === 'critical' ? 'critical' : insight.severity === 'info' ? 'info' : 'warning';
6179
- var iconChar = insight.severity === 'critical' ? '\\u2717' : insight.severity === 'info' ? '\\u2139' : '\\u26A0';
6552
+ var cards = document.createElement('div');
6553
+ cards.className = 'ov-cards';
6554
+
6555
+ for (var i = 0; i < open.length; i++) {
6556
+ (function(si) {
6557
+ var insight = si.insight;
6558
+ var card = document.createElement('div');
6559
+ card.className = 'ov-card';
6560
+
6561
+ var sevCfg = SEV[insight.severity];
6562
+ var iconCls = sevCfg.cls;
6563
+ var iconChar = sevCfg.icon;
6564
+
6565
+ var expandHtml = '';
6566
+ if (insight.detail) expandHtml += insight.detail;
6567
+ if (insight.hint) expandHtml += '<div class="ov-card-hint">' + escHtml(insight.hint) + '</div>';
6568
+ expandHtml += '<span class="ov-card-link" data-nav="' + insight.nav + '">View in ' + (NAV_LABELS[insight.nav] || insight.nav) + ' \\u2192</span>';
6569
+
6570
+ card.innerHTML =
6571
+ '<span class="ov-card-icon ' + iconCls + '">' + iconChar + '</span>' +
6572
+ '<div class="ov-card-body">' +
6573
+ '<div class="ov-card-title">' + escHtml(insight.title) + '</div>' +
6574
+ '<div class="ov-card-desc">' + insight.desc + '</div>' +
6575
+ '<div class="ov-card-expand">' + expandHtml + '</div>' +
6576
+ '</div>' +
6577
+ '<span class="ov-card-arrow">\\u2192</span>';
6578
+
6579
+ card.addEventListener('click', function(e) {
6580
+ var target = e.target;
6581
+ while (target && target !== card) {
6582
+ if (target.classList && target.classList.contains('ov-card-link')) {
6583
+ var navView = target.getAttribute('data-nav');
6584
+ var sidebarItem = document.querySelector('.sidebar-item[data-view="' + navView + '"]');
6585
+ if (sidebarItem) sidebarItem.click();
6586
+ return;
6587
+ }
6588
+ target = target.parentElement;
6589
+ }
6590
+ var expand = card.querySelector('.ov-card-expand');
6591
+ var arrow = card.querySelector('.ov-card-arrow');
6592
+ if (card.classList.contains('expanded')) {
6593
+ card.classList.remove('expanded');
6594
+ expand.style.display = 'none';
6595
+ arrow.textContent = '\\u2192';
6596
+ } else {
6597
+ card.classList.add('expanded');
6598
+ expand.style.display = 'block';
6599
+ arrow.textContent = '\\u2193';
6600
+ }
6601
+ });
6180
6602
 
6181
- var expandHtml = '';
6182
- if (insight.detail) expandHtml += insight.detail;
6183
- if (insight.hint) expandHtml += '<div class="ov-card-hint">' + escHtml(insight.hint) + '</div>';
6184
- expandHtml += '<span class="ov-card-link" data-nav="' + insight.nav + '">View in ' + (NAV_LABELS[insight.nav] || insight.nav) + ' \\u2192</span>';
6603
+ cards.appendChild(card);
6604
+ })(open[i]);
6605
+ }
6606
+
6607
+ container.appendChild(cards);
6608
+ }
6185
6609
 
6186
- card.innerHTML =
6187
- '<span class="ov-card-icon ' + iconCls + '">' + iconChar + '</span>' +
6610
+ if (resolved.length > 0) {
6611
+ var resolvedTitle = document.createElement('div');
6612
+ resolvedTitle.className = 'ov-section-title ov-resolved-title';
6613
+ resolvedTitle.innerHTML = '<span style="color:var(--green)">\\u2713</span> Resolved <span class="ov-issue-count">' + resolved.length + '</span>';
6614
+ container.appendChild(resolvedTitle);
6615
+
6616
+ var resolvedCards = document.createElement('div');
6617
+ resolvedCards.className = 'ov-cards';
6618
+
6619
+ for (var ri = 0; ri < resolved.length; ri++) {
6620
+ var rInsight = resolved[ri].insight;
6621
+ var rCard = document.createElement('div');
6622
+ rCard.className = 'ov-card ov-card-resolved';
6623
+ rCard.innerHTML =
6624
+ '<span class="ov-card-icon resolved">\\u2713</span>' +
6188
6625
  '<div class="ov-card-body">' +
6189
- '<div class="ov-card-title">' + escHtml(insight.title) + '</div>' +
6190
- '<div class="ov-card-desc">' + insight.desc + '</div>' +
6191
- '<div class="ov-card-expand">' + expandHtml + '</div>' +
6192
- '</div>' +
6193
- '<span class="ov-card-arrow">\\u2192</span>';
6194
-
6195
- card.addEventListener('click', function(e) {
6196
- var target = e.target;
6197
- while (target && target !== card) {
6198
- if (target.classList && target.classList.contains('ov-card-link')) {
6199
- var navView = target.getAttribute('data-nav');
6200
- var sidebarItem = document.querySelector('.sidebar-item[data-view="' + navView + '"]');
6201
- if (sidebarItem) sidebarItem.click();
6202
- return;
6203
- }
6204
- target = target.parentElement;
6205
- }
6206
- var expand = card.querySelector('.ov-card-expand');
6207
- var arrow = card.querySelector('.ov-card-arrow');
6208
- if (card.classList.contains('expanded')) {
6209
- card.classList.remove('expanded');
6210
- expand.style.display = 'none';
6211
- arrow.textContent = '\\u2192';
6212
- } else {
6213
- card.classList.add('expanded');
6214
- expand.style.display = 'block';
6215
- arrow.textContent = '\\u2193';
6216
- }
6217
- });
6626
+ '<div class="ov-card-title" style="text-decoration:line-through;color:var(--text-muted)">' + escHtml(rInsight.title) + '</div>' +
6627
+ '<div class="ov-card-desc">' + rInsight.desc + '</div>' +
6628
+ '</div>';
6629
+ resolvedCards.appendChild(rCard);
6630
+ }
6218
6631
 
6219
- cards.appendChild(card);
6220
- })(insights[i]);
6632
+ container.appendChild(resolvedCards);
6221
6633
  }
6222
-
6223
- container.appendChild(cards);
6224
6634
  }
6225
6635
  `;
6226
6636
  }
@@ -6250,10 +6660,13 @@ function getSecurityView() {
6250
6660
  var container = document.getElementById('security-content');
6251
6661
  if (!container) return;
6252
6662
  container.innerHTML = '';
6663
+ var SEV = ${SEVERITY_MAP};
6253
6664
 
6254
- var findings = state.findings || [];
6665
+ var all = state.findings || [];
6666
+ var open = all.filter(function(f) { return f.state === 'open' || f.state === 'fixing'; });
6667
+ var resolved = all.filter(function(f) { return f.state === 'resolved'; });
6255
6668
 
6256
- if (findings.length === 0) {
6669
+ if (open.length === 0 && resolved.length === 0) {
6257
6670
  var hasData = state.requests.length > 0 || state.logs.length > 0 || state.queries.length > 0;
6258
6671
  if (!hasData) {
6259
6672
  container.innerHTML = '<div class="empty" style="height:400px"><span class="empty-title">Waiting for requests...</span><span class="empty-sub">Start using your app to see security findings here</span></div>';
@@ -6264,17 +6677,20 @@ function getSecurityView() {
6264
6677
  }
6265
6678
 
6266
6679
  var critCount = 0, warnCount = 0, infoCount = 0;
6267
- for (var ci = 0; ci < findings.length; ci++) {
6268
- if (findings[ci].severity === 'critical') critCount++;
6269
- else if (findings[ci].severity === 'info') infoCount++;
6680
+ for (var ci = 0; ci < open.length; ci++) {
6681
+ var sev = open[ci].finding.severity;
6682
+ if (sev === 'critical') critCount++;
6683
+ else if (sev === 'info') infoCount++;
6270
6684
  else warnCount++;
6271
6685
  }
6686
+
6272
6687
  var summaryEl = document.createElement('div');
6273
6688
  summaryEl.className = 'sec-summary';
6274
6689
  summaryEl.innerHTML =
6275
6690
  '<div class="sec-summary-left">' +
6276
- '<span class="sec-summary-count">' + findings.length + '</span>' +
6277
- '<span class="sec-summary-label">issue' + (findings.length !== 1 ? 's' : '') + ' found</span>' +
6691
+ '<span class="sec-summary-count">' + open.length + '</span>' +
6692
+ '<span class="sec-summary-label">open issue' + (open.length !== 1 ? 's' : '') + '</span>' +
6693
+ (resolved.length > 0 ? '<span class="sec-resolved-badge">' + resolved.length + ' resolved</span>' : '') +
6278
6694
  '</div>' +
6279
6695
  '<div class="sec-summary-right">' +
6280
6696
  (critCount > 0 ? '<span class="sec-badge critical">' + critCount + ' critical</span>' : '') +
@@ -6283,60 +6699,93 @@ function getSecurityView() {
6283
6699
  '</div>';
6284
6700
  container.appendChild(summaryEl);
6285
6701
 
6286
- var groups = {};
6287
- var groupOrder = [];
6288
- for (var gi = 0; gi < findings.length; gi++) {
6289
- var f = findings[gi];
6290
- if (!groups[f.rule]) {
6291
- groups[f.rule] = { rule: f.rule, title: f.title, severity: f.severity, hint: f.hint, items: [] };
6292
- groupOrder.push(f.rule);
6293
- }
6294
- groups[f.rule].items.push(f);
6702
+ if (open.length === 0 && resolved.length > 0) {
6703
+ var allFixed = document.createElement('div');
6704
+ allFixed.className = 'sec-clear';
6705
+ allFixed.innerHTML = '<span class="sec-clear-icon">\\u2713</span><div class="sec-clear-text"><div class="sec-clear-title">All issues resolved</div><div class="sec-clear-sub">' + resolved.length + ' finding' + (resolved.length !== 1 ? 's were' : ' was') + ' detected and fixed</div></div>';
6706
+ container.appendChild(allFixed);
6295
6707
  }
6296
6708
 
6297
- groupOrder.sort(function(a, b) {
6298
- var sa = groups[a].severity === 'critical' ? 0 : groups[a].severity === 'warning' ? 1 : 2;
6299
- var sb = groups[b].severity === 'critical' ? 0 : groups[b].severity === 'warning' ? 1 : 2;
6300
- if (sa !== sb) return sa - sb;
6301
- return groups[b].items.length - groups[a].items.length;
6302
- });
6709
+ if (open.length > 0) {
6710
+ var groups = {};
6711
+ var groupOrder = [];
6712
+ for (var gi = 0; gi < open.length; gi++) {
6713
+ var f = open[gi].finding;
6714
+ if (!groups[f.rule]) {
6715
+ groups[f.rule] = { rule: f.rule, title: f.title, severity: f.severity, hint: f.hint, items: [] };
6716
+ groupOrder.push(f.rule);
6717
+ }
6718
+ groups[f.rule].items.push(f);
6719
+ }
6303
6720
 
6304
- for (var oi = 0; oi < groupOrder.length; oi++) {
6305
- var group = groups[groupOrder[oi]];
6306
- var section = document.createElement('div');
6307
- section.className = 'sec-group';
6721
+ groupOrder.sort(function(a, b) {
6722
+ var sa = SEV[groups[a].severity].sort;
6723
+ var sb = SEV[groups[b].severity].sort;
6724
+ if (sa !== sb) return sa - sb;
6725
+ return groups[b].items.length - groups[a].items.length;
6726
+ });
6308
6727
 
6309
- var iconCls = group.severity === 'critical' ? 'critical' : group.severity === 'info' ? 'info' : 'warning';
6310
- var iconChar = group.severity === 'critical' ? '\\u2717' : group.severity === 'info' ? '\\u2139' : '\\u26A0';
6728
+ for (var oi = 0; oi < groupOrder.length; oi++) {
6729
+ var group = groups[groupOrder[oi]];
6730
+ var section = document.createElement('div');
6731
+ section.className = 'sec-group';
6732
+
6733
+ var sevCfg = SEV[group.severity];
6734
+ var iconCls = sevCfg.cls;
6735
+ var iconChar = sevCfg.icon;
6736
+
6737
+ var header = document.createElement('div');
6738
+ header.className = 'sec-group-header';
6739
+ header.innerHTML =
6740
+ '<span class="sec-group-icon ' + iconCls + '">' + iconChar + '</span>' +
6741
+ '<span class="sec-group-title">' + escHtml(group.title) + '</span>' +
6742
+ '<span class="sec-group-count">' + group.items.length + '</span>';
6743
+ section.appendChild(header);
6744
+
6745
+ if (group.hint) {
6746
+ var hintEl = document.createElement('div');
6747
+ hintEl.className = 'sec-hint';
6748
+ hintEl.textContent = group.hint;
6749
+ section.appendChild(hintEl);
6750
+ }
6311
6751
 
6312
- var header = document.createElement('div');
6313
- header.className = 'sec-group-header';
6314
- header.innerHTML =
6315
- '<span class="sec-group-icon ' + iconCls + '">' + iconChar + '</span>' +
6316
- '<span class="sec-group-title">' + escHtml(group.title) + '</span>' +
6317
- '<span class="sec-group-count">' + group.items.length + '</span>';
6318
- section.appendChild(header);
6319
-
6320
- if (group.hint) {
6321
- var hintEl = document.createElement('div');
6322
- hintEl.className = 'sec-hint';
6323
- hintEl.textContent = group.hint;
6324
- section.appendChild(hintEl);
6325
- }
6326
-
6327
- var list = document.createElement('div');
6328
- list.className = 'sec-items';
6329
- for (var ii = 0; ii < group.items.length; ii++) {
6330
- var item = group.items[ii];
6331
- var row = document.createElement('div');
6332
- row.className = 'sec-item';
6333
- row.innerHTML =
6334
- '<div class="sec-item-desc">' + item.desc + '</div>' +
6335
- (item.count > 1 ? '<span class="sec-item-count">' + item.count + 'x</span>' : '');
6336
- list.appendChild(row);
6752
+ var list = document.createElement('div');
6753
+ list.className = 'sec-items';
6754
+ for (var ii = 0; ii < group.items.length; ii++) {
6755
+ var item = group.items[ii];
6756
+ var row = document.createElement('div');
6757
+ row.className = 'sec-item';
6758
+ row.innerHTML =
6759
+ '<div class="sec-item-desc">' + item.desc + '</div>' +
6760
+ (item.count > 1 ? '<span class="sec-item-count">' + item.count + 'x</span>' : '');
6761
+ list.appendChild(row);
6762
+ }
6763
+ section.appendChild(list);
6764
+ container.appendChild(section);
6337
6765
  }
6338
- section.appendChild(list);
6339
- container.appendChild(section);
6766
+ }
6767
+
6768
+ if (resolved.length > 0) {
6769
+ var resolvedTitle = document.createElement('div');
6770
+ resolvedTitle.className = 'sec-resolved-title';
6771
+ resolvedTitle.innerHTML = '<span class="sec-resolved-check">\\u2713</span> Resolved <span class="sec-resolved-count">' + resolved.length + '</span>';
6772
+ container.appendChild(resolvedTitle);
6773
+
6774
+ var resolvedGroup = document.createElement('div');
6775
+ resolvedGroup.className = 'sec-group sec-group-resolved';
6776
+ var resolvedItems = document.createElement('div');
6777
+ resolvedItems.className = 'sec-items';
6778
+ for (var ri = 0; ri < resolved.length; ri++) {
6779
+ var rf = resolved[ri].finding;
6780
+ var rRow = document.createElement('div');
6781
+ rRow.className = 'sec-item sec-item-resolved';
6782
+ rRow.innerHTML =
6783
+ '<span class="sec-resolved-item-icon">\\u2713</span>' +
6784
+ '<div class="sec-item-desc">' + escHtml(rf.title) + ' \\u2014 ' + escHtml(rf.endpoint) + '</div>';
6785
+ resolvedItems.appendChild(rRow);
6786
+ }
6787
+ resolvedGroup.appendChild(resolvedItems);
6788
+ container.appendChild(resolvedGroup);
6340
6789
  }
6341
6790
  }
6342
6791
  `;
@@ -6344,6 +6793,7 @@ function getSecurityView() {
6344
6793
  var init_security3 = __esm({
6345
6794
  "src/dashboard/client/views/security.ts"() {
6346
6795
  "use strict";
6796
+ init_display();
6347
6797
  }
6348
6798
  });
6349
6799
 
@@ -6524,7 +6974,7 @@ function getApp() {
6524
6974
  if (queryCount) queryCount.textContent = state.queries.length;
6525
6975
  var secCount = document.getElementById('sidebar-count-security');
6526
6976
  if (secCount) {
6527
- var numFindings = (state.findings || []).length;
6977
+ var numFindings = (state.findings || []).filter(function(f) { return f.state !== 'resolved'; }).length;
6528
6978
  secCount.textContent = numFindings;
6529
6979
  secCount.style.display = numFindings > 0 ? '' : 'none';
6530
6980
  }
@@ -6639,12 +7089,12 @@ var init_page = __esm({
6639
7089
  // src/telemetry/config.ts
6640
7090
  import { homedir } from "os";
6641
7091
  import { join as join2 } from "path";
6642
- import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
7092
+ import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4 } from "fs";
6643
7093
  import { randomUUID as randomUUID5 } from "crypto";
6644
7094
  function readConfig() {
6645
7095
  try {
6646
- if (!existsSync3(CONFIG_PATH)) return null;
6647
- return JSON.parse(readFileSync3(CONFIG_PATH, "utf-8"));
7096
+ if (!existsSync4(CONFIG_PATH)) return null;
7097
+ return JSON.parse(readFileSync4(CONFIG_PATH, "utf-8"));
6648
7098
  } catch {
6649
7099
  return null;
6650
7100
  }
@@ -6693,7 +7143,7 @@ function createDashboardHandler(deps) {
6693
7143
  [DASHBOARD_API_REQUESTS]: handleApiRequests,
6694
7144
  [DASHBOARD_API_EVENTS]: createSSEHandler(deps.analysisEngine),
6695
7145
  [DASHBOARD_API_FLOWS]: handleApiFlows,
6696
- [DASHBOARD_API_CLEAR]: createClearHandler(deps.metricsStore),
7146
+ [DASHBOARD_API_CLEAR]: createClearHandler(deps.metricsStore, deps.findingStore),
6697
7147
  [DASHBOARD_API_LOGS]: handleApiLogs,
6698
7148
  [DASHBOARD_API_FETCHES]: handleApiFetches,
6699
7149
  [DASHBOARD_API_ERRORS]: handleApiErrors,
@@ -6707,6 +7157,9 @@ function createDashboardHandler(deps) {
6707
7157
  routes[DASHBOARD_API_INSIGHTS] = createInsightsHandler(deps.analysisEngine);
6708
7158
  routes[DASHBOARD_API_SECURITY] = createSecurityHandler(deps.analysisEngine);
6709
7159
  }
7160
+ if (deps.findingStore) {
7161
+ routes[DASHBOARD_API_FINDINGS] = createFindingsHandler(deps.findingStore);
7162
+ }
6710
7163
  routes[DASHBOARD_API_TAB] = (req, res) => {
6711
7164
  const raw = (req.url ?? "").split("tab=")[1];
6712
7165
  if (raw) {
@@ -6739,6 +7192,7 @@ var init_router = __esm({
6739
7192
  init_constants();
6740
7193
  init_api();
6741
7194
  init_insights();
7195
+ init_findings();
6742
7196
  init_sse();
6743
7197
  init_page();
6744
7198
  init_telemetry();
@@ -6761,30 +7215,39 @@ var init_router = __esm({
6761
7215
  }
6762
7216
  });
6763
7217
 
7218
+ // src/constants/severity.ts
7219
+ var SEVERITY_ICON;
7220
+ var init_severity = __esm({
7221
+ "src/constants/severity.ts"() {
7222
+ "use strict";
7223
+ SEVERITY_ICON = {
7224
+ critical: "\u2717",
7225
+ warning: "\u26A0",
7226
+ info: "\u2139"
7227
+ };
7228
+ }
7229
+ });
7230
+
6764
7231
  // src/output/terminal.ts
6765
7232
  import pc from "picocolors";
6766
7233
  function print(line) {
6767
7234
  process.stdout.write(line + "\n");
6768
7235
  }
6769
7236
  function severityIcon(severity) {
6770
- if (severity === "critical") return pc.red("\u2717");
6771
- if (severity === "warning") return pc.yellow("\u26A0");
6772
- return pc.dim("\u25CB");
7237
+ return SEVERITY_COLOR[severity](SEVERITY_ICON[severity]);
6773
7238
  }
6774
7239
  function colorTitle(severity, text) {
6775
- if (severity === "critical") return pc.red(pc.bold(text));
6776
- if (severity === "warning") return pc.yellow(pc.bold(text));
6777
- return pc.dim(text);
7240
+ const color = SEVERITY_COLOR[severity];
7241
+ return severity === "info" ? color(text) : color(pc.bold(text));
6778
7242
  }
6779
- function truncate(s, max = 80) {
7243
+ function truncate(s, max = TERMINAL_TRUNCATE_LENGTH) {
6780
7244
  return s.length <= max ? s : s.slice(0, max - 1) + "\u2026";
6781
7245
  }
6782
- function formatConsoleLine(insight, dashboardUrl, suffix) {
7246
+ function formatConsoleLine(insight, suffix) {
6783
7247
  const icon = severityIcon(insight.severity);
6784
7248
  const title = colorTitle(insight.severity, insight.title);
6785
7249
  const desc = pc.dim(truncate(insight.desc) + (suffix ?? ""));
6786
- const link = pc.dim(`\u2192 ${dashboardUrl}`);
6787
- let line = ` ${icon} ${title} \u2014 ${desc} ${link}`;
7250
+ let line = ` ${icon} ${title} \u2014 ${desc}`;
6788
7251
  if (insight.detail) {
6789
7252
  line += `
6790
7253
  ${pc.dim("\u2514 " + insight.detail)}`;
@@ -6793,36 +7256,66 @@ function formatConsoleLine(insight, dashboardUrl, suffix) {
6793
7256
  }
6794
7257
  function createConsoleInsightListener(proxyPort, metricsStore) {
6795
7258
  const printedKeys = /* @__PURE__ */ new Set();
7259
+ const resolvedKeys = /* @__PURE__ */ new Set();
6796
7260
  const dashUrl = `localhost:${proxyPort}${DASHBOARD_PREFIX}`;
6797
- return (insights) => {
6798
- const lines = [];
6799
- for (const insight of insights) {
6800
- if (insight.severity === "info") continue;
6801
- const endpoint = insight.desc.match(/^(\S+\s+\S+)/)?.[1] ?? insight.desc;
6802
- const key = `${insight.type}:${endpoint}`;
6803
- if (printedKeys.has(key)) continue;
6804
- printedKeys.add(key);
7261
+ return ({ statefulInsights }) => {
7262
+ const newLines = [];
7263
+ const resolvedLines = [];
7264
+ for (const si of statefulInsights) {
7265
+ if (si.state === "resolved") {
7266
+ if (resolvedKeys.has(si.key)) continue;
7267
+ resolvedKeys.add(si.key);
7268
+ printedKeys.delete(si.key);
7269
+ const title = pc.green(pc.bold(`\u2713 ${si.insight.title}`));
7270
+ const desc = pc.dim(truncate(si.insight.desc));
7271
+ resolvedLines.push(` ${title} \u2014 ${desc} ${pc.green("resolved")}`);
7272
+ continue;
7273
+ }
7274
+ resolvedKeys.delete(si.key);
7275
+ if (si.insight.severity === "info") continue;
7276
+ if (printedKeys.has(si.key)) continue;
7277
+ printedKeys.add(si.key);
6805
7278
  let suffix;
6806
- if (insight.type === "slow") {
6807
- const ep = metricsStore.getEndpoint(endpoint);
6808
- if (ep && ep.sessions.length > 1) {
6809
- const prev = ep.sessions[ep.sessions.length - 2];
6810
- suffix = ` (\u2191 from ${prev.p95DurationMs < 1e3 ? prev.p95DurationMs + "ms" : (prev.p95DurationMs / 1e3).toFixed(1) + "s"})`;
7279
+ if (si.insight.type === "slow") {
7280
+ const endpoint = extractEndpointFromDesc(si.insight.desc);
7281
+ if (endpoint) {
7282
+ const ep = metricsStore.getEndpoint(endpoint);
7283
+ if (ep && ep.sessions.length > 1) {
7284
+ const prev = ep.sessions[ep.sessions.length - 2];
7285
+ suffix = ` (\u2191 from ${prev.p95DurationMs < 1e3 ? prev.p95DurationMs + "ms" : (prev.p95DurationMs / 1e3).toFixed(1) + "s"})`;
7286
+ }
6811
7287
  }
6812
7288
  }
6813
- lines.push(formatConsoleLine(insight, dashUrl, suffix));
7289
+ newLines.push(formatConsoleLine(si.insight, suffix));
7290
+ }
7291
+ if (newLines.length > 0) {
7292
+ print("");
7293
+ for (const line of newLines) print(line);
7294
+ print("");
7295
+ print(` ${pc.magenta(pc.bold("brakit"))} ${pc.dim("\u2192")} ${pc.dim("Dashboard:")} ${pc.underline(`http://${dashUrl}`)} ${pc.dim("or ask your AI:")} ${pc.bold('"Fix brakit findings"')}`);
6814
7296
  }
6815
- if (lines.length > 0) {
7297
+ if (resolvedLines.length > 0) {
7298
+ print("");
7299
+ for (const line of resolvedLines) print(line);
6816
7300
  print("");
6817
- for (const line of lines) print(line);
7301
+ print(` ${pc.magenta(pc.bold("brakit"))} ${pc.dim("\u2192")} ${pc.green("Issues fixed!")}`);
6818
7302
  }
6819
7303
  };
6820
7304
  }
7305
+ var SEVERITY_COLOR;
6821
7306
  var init_terminal = __esm({
6822
7307
  "src/output/terminal.ts"() {
6823
7308
  "use strict";
6824
7309
  init_src();
6825
7310
  init_constants();
7311
+ init_limits();
7312
+ init_severity();
7313
+ init_endpoint();
7314
+ SEVERITY_COLOR = {
7315
+ critical: pc.red,
7316
+ warning: pc.yellow,
7317
+ info: pc.dim
7318
+ };
6826
7319
  }
6827
7320
  });
6828
7321
 
@@ -7050,6 +7543,8 @@ var setup_exports = {};
7050
7543
  __export(setup_exports, {
7051
7544
  setup: () => setup
7052
7545
  });
7546
+ import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, existsSync as existsSync5, unlinkSync as unlinkSync2 } from "fs";
7547
+ import { resolve as resolve4 } from "path";
7053
7548
  function setup() {
7054
7549
  if (initialized) return;
7055
7550
  initialized = true;
@@ -7062,7 +7557,9 @@ function setup() {
7062
7557
  const cwd = process.cwd();
7063
7558
  const metricsStore = new MetricsStore(new FileMetricsPersistence(cwd));
7064
7559
  metricsStore.start();
7065
- const analysisEngine = new AnalysisEngine(metricsStore);
7560
+ const findingStore = new FindingStore(cwd);
7561
+ findingStore.start();
7562
+ const analysisEngine = new AnalysisEngine(metricsStore, findingStore);
7066
7563
  analysisEngine.start();
7067
7564
  const config = {
7068
7565
  proxyPort: 0,
@@ -7070,7 +7567,7 @@ function setup() {
7070
7567
  showStatic: false,
7071
7568
  maxBodyCapture: DEFAULT_MAX_BODY_CAPTURE
7072
7569
  };
7073
- const handleDashboard = createDashboardHandler({ metricsStore, analysisEngine });
7570
+ const handleDashboard = createDashboardHandler({ metricsStore, analysisEngine, findingStore });
7074
7571
  onRequest((req) => {
7075
7572
  const queries = defaultQueryStore.getByRequest(req.id);
7076
7573
  const fetches = defaultFetchStore.getByRequest(req.id);
@@ -7084,6 +7581,9 @@ function setup() {
7084
7581
  handleDashboard,
7085
7582
  config,
7086
7583
  onFirstRequest(port) {
7584
+ const dir = resolve4(cwd, METRICS_DIR);
7585
+ if (!existsSync5(dir)) mkdirSync5(dir, { recursive: true });
7586
+ writeFileSync5(resolve4(cwd, PORT_FILE), String(port));
7087
7587
  analysisEngine.onUpdate(createConsoleInsightListener(port, metricsStore));
7088
7588
  process.stdout.write(` brakit v${VERSION} \u2014 http://localhost:${port}${DASHBOARD_PREFIX}
7089
7589
  `);
@@ -7092,7 +7592,13 @@ function setup() {
7092
7592
  health.setTeardown(() => {
7093
7593
  uninstallInterceptor();
7094
7594
  analysisEngine.stop();
7595
+ findingStore.stop();
7095
7596
  metricsStore.stop();
7597
+ try {
7598
+ const portPath = resolve4(cwd, PORT_FILE);
7599
+ if (existsSync5(portPath)) unlinkSync2(portPath);
7600
+ } catch {
7601
+ }
7096
7602
  });
7097
7603
  }
7098
7604
  function routeEvent2(event) {
@@ -7123,6 +7629,7 @@ var init_setup = __esm({
7123
7629
  init_router();
7124
7630
  init_request_log();
7125
7631
  init_store();
7632
+ init_finding_store();
7126
7633
  init_engine();
7127
7634
  init_terminal();
7128
7635
  init_src();