brakit 0.7.5 → 0.8.0

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,6 +29,7 @@ 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
 
@@ -93,7 +94,7 @@ var init_transport = __esm({
93
94
  });
94
95
 
95
96
  // src/constants/metrics.ts
96
- var METRICS_DIR, METRICS_FILE, METRICS_FLUSH_INTERVAL_MS, METRICS_MAX_SESSIONS, METRICS_MAX_DATA_POINTS;
97
+ 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
98
  var init_metrics = __esm({
98
99
  "src/constants/metrics.ts"() {
99
100
  "use strict";
@@ -102,6 +103,9 @@ var init_metrics = __esm({
102
103
  METRICS_FLUSH_INTERVAL_MS = 3e4;
103
104
  METRICS_MAX_SESSIONS = 50;
104
105
  METRICS_MAX_DATA_POINTS = 200;
106
+ PORT_FILE = ".brakit/port";
107
+ FINDINGS_FILE = ".brakit/findings.json";
108
+ FINDINGS_FLUSH_INTERVAL_MS = 1e4;
105
109
  }
106
110
  });
107
111
 
@@ -148,6 +152,13 @@ var init_network = __esm({
148
152
  }
149
153
  });
150
154
 
155
+ // src/constants/mcp.ts
156
+ var init_mcp = __esm({
157
+ "src/constants/mcp.ts"() {
158
+ "use strict";
159
+ }
160
+ });
161
+
151
162
  // src/constants/index.ts
152
163
  var init_constants = __esm({
153
164
  "src/constants/index.ts"() {
@@ -159,6 +170,7 @@ var init_constants = __esm({
159
170
  init_metrics();
160
171
  init_headers();
161
172
  init_network();
173
+ init_mcp();
162
174
  }
163
175
  });
164
176
 
@@ -2055,6 +2067,33 @@ var init_insights = __esm({
2055
2067
  }
2056
2068
  });
2057
2069
 
2070
+ // src/dashboard/api/findings.ts
2071
+ function createFindingsHandler(findingStore) {
2072
+ return (req, res) => {
2073
+ if (!requireGet(req, res)) return;
2074
+ const url = new URL(req.url ?? "/", "http://localhost");
2075
+ const stateParam = url.searchParams.get("state");
2076
+ let findings;
2077
+ if (stateParam && VALID_STATES.has(stateParam)) {
2078
+ findings = findingStore.getByState(stateParam);
2079
+ } else {
2080
+ findings = findingStore.getAll();
2081
+ }
2082
+ sendJson(req, res, 200, {
2083
+ total: findings.length,
2084
+ findings
2085
+ });
2086
+ };
2087
+ }
2088
+ var VALID_STATES;
2089
+ var init_findings = __esm({
2090
+ "src/dashboard/api/findings.ts"() {
2091
+ "use strict";
2092
+ init_shared2();
2093
+ VALID_STATES = /* @__PURE__ */ new Set(["open", "fixing", "resolved"]);
2094
+ }
2095
+ });
2096
+
2058
2097
  // src/dashboard/sse.ts
2059
2098
  function createSSEHandler(engine) {
2060
2099
  return (req, res) => {
@@ -2687,6 +2726,197 @@ var init_styles = __esm({
2687
2726
  }
2688
2727
  });
2689
2728
 
2729
+ // src/store/finding-id.ts
2730
+ import { createHash } from "crypto";
2731
+ function computeFindingId(finding) {
2732
+ const key = `${finding.rule}:${finding.endpoint}:${finding.desc}`;
2733
+ return createHash("sha256").update(key).digest("hex").slice(0, 16);
2734
+ }
2735
+ var init_finding_id = __esm({
2736
+ "src/store/finding-id.ts"() {
2737
+ "use strict";
2738
+ }
2739
+ });
2740
+
2741
+ // src/store/finding-store.ts
2742
+ import {
2743
+ readFileSync as readFileSync3,
2744
+ writeFileSync as writeFileSync3,
2745
+ existsSync as existsSync3,
2746
+ mkdirSync as mkdirSync3,
2747
+ renameSync as renameSync2
2748
+ } from "fs";
2749
+ import { writeFile as writeFile3, mkdir as mkdir2, rename as rename2 } from "fs/promises";
2750
+ import { resolve as resolve3 } from "path";
2751
+ var FindingStore;
2752
+ var init_finding_store = __esm({
2753
+ "src/store/finding-store.ts"() {
2754
+ "use strict";
2755
+ init_constants();
2756
+ init_fs();
2757
+ init_finding_id();
2758
+ FindingStore = class {
2759
+ constructor(rootDir) {
2760
+ this.rootDir = rootDir;
2761
+ this.metricsDir = resolve3(rootDir, METRICS_DIR);
2762
+ this.findingsPath = resolve3(rootDir, FINDINGS_FILE);
2763
+ this.tmpPath = this.findingsPath + ".tmp";
2764
+ this.load();
2765
+ }
2766
+ findings = /* @__PURE__ */ new Map();
2767
+ flushTimer = null;
2768
+ dirty = false;
2769
+ writing = false;
2770
+ findingsPath;
2771
+ tmpPath;
2772
+ metricsDir;
2773
+ start() {
2774
+ this.flushTimer = setInterval(
2775
+ () => this.flush(),
2776
+ FINDINGS_FLUSH_INTERVAL_MS
2777
+ );
2778
+ this.flushTimer.unref();
2779
+ }
2780
+ stop() {
2781
+ if (this.flushTimer) {
2782
+ clearInterval(this.flushTimer);
2783
+ this.flushTimer = null;
2784
+ }
2785
+ this.flushSync();
2786
+ }
2787
+ upsert(finding, source) {
2788
+ const id = computeFindingId(finding);
2789
+ const existing = this.findings.get(id);
2790
+ const now = Date.now();
2791
+ if (existing) {
2792
+ existing.lastSeenAt = now;
2793
+ existing.occurrences++;
2794
+ existing.finding = finding;
2795
+ if (existing.state === "resolved") {
2796
+ existing.state = "open";
2797
+ existing.resolvedAt = null;
2798
+ }
2799
+ this.dirty = true;
2800
+ return existing;
2801
+ }
2802
+ const stateful = {
2803
+ findingId: id,
2804
+ state: "open",
2805
+ source,
2806
+ finding,
2807
+ firstSeenAt: now,
2808
+ lastSeenAt: now,
2809
+ resolvedAt: null,
2810
+ occurrences: 1
2811
+ };
2812
+ this.findings.set(id, stateful);
2813
+ this.dirty = true;
2814
+ return stateful;
2815
+ }
2816
+ transition(findingId, state) {
2817
+ const finding = this.findings.get(findingId);
2818
+ if (!finding) return false;
2819
+ finding.state = state;
2820
+ if (state === "resolved") {
2821
+ finding.resolvedAt = Date.now();
2822
+ }
2823
+ this.dirty = true;
2824
+ return true;
2825
+ }
2826
+ reconcilePassive(currentFindings) {
2827
+ const currentIds = new Set(currentFindings.map(computeFindingId));
2828
+ for (const [id, stateful] of this.findings) {
2829
+ if (stateful.source === "passive" && stateful.state === "open" && !currentIds.has(id)) {
2830
+ stateful.state = "resolved";
2831
+ stateful.resolvedAt = Date.now();
2832
+ this.dirty = true;
2833
+ }
2834
+ }
2835
+ }
2836
+ getAll() {
2837
+ return [...this.findings.values()];
2838
+ }
2839
+ getByState(state) {
2840
+ return [...this.findings.values()].filter((f) => f.state === state);
2841
+ }
2842
+ get(findingId) {
2843
+ return this.findings.get(findingId);
2844
+ }
2845
+ clear() {
2846
+ this.findings.clear();
2847
+ this.dirty = true;
2848
+ }
2849
+ load() {
2850
+ try {
2851
+ if (existsSync3(this.findingsPath)) {
2852
+ const raw = readFileSync3(this.findingsPath, "utf-8");
2853
+ const parsed = JSON.parse(raw);
2854
+ if (parsed?.version === 1 && Array.isArray(parsed.findings)) {
2855
+ for (const f of parsed.findings) {
2856
+ this.findings.set(f.findingId, f);
2857
+ }
2858
+ }
2859
+ }
2860
+ } catch {
2861
+ }
2862
+ }
2863
+ flush() {
2864
+ if (!this.dirty) return;
2865
+ this.writeAsync();
2866
+ }
2867
+ flushSync() {
2868
+ if (!this.dirty) return;
2869
+ try {
2870
+ this.ensureDir();
2871
+ const data = {
2872
+ version: 1,
2873
+ findings: [...this.findings.values()]
2874
+ };
2875
+ writeFileSync3(this.tmpPath, JSON.stringify(data));
2876
+ renameSync2(this.tmpPath, this.findingsPath);
2877
+ this.dirty = false;
2878
+ } catch (err) {
2879
+ process.stderr.write(
2880
+ `[brakit] failed to save findings: ${err.message}
2881
+ `
2882
+ );
2883
+ }
2884
+ }
2885
+ async writeAsync() {
2886
+ if (this.writing) return;
2887
+ this.writing = true;
2888
+ try {
2889
+ if (!existsSync3(this.metricsDir)) {
2890
+ await mkdir2(this.metricsDir, { recursive: true });
2891
+ ensureGitignore(this.metricsDir, METRICS_DIR);
2892
+ }
2893
+ const data = {
2894
+ version: 1,
2895
+ findings: [...this.findings.values()]
2896
+ };
2897
+ await writeFile3(this.tmpPath, JSON.stringify(data));
2898
+ await rename2(this.tmpPath, this.findingsPath);
2899
+ this.dirty = false;
2900
+ } catch (err) {
2901
+ process.stderr.write(
2902
+ `[brakit] failed to save findings: ${err.message}
2903
+ `
2904
+ );
2905
+ } finally {
2906
+ this.writing = false;
2907
+ if (this.dirty) this.writeAsync();
2908
+ }
2909
+ }
2910
+ ensureDir() {
2911
+ if (!existsSync3(this.metricsDir)) {
2912
+ mkdirSync3(this.metricsDir, { recursive: true });
2913
+ ensureGitignore(this.metricsDir, METRICS_DIR);
2914
+ }
2915
+ }
2916
+ };
2917
+ }
2918
+ });
2919
+
2690
2920
  // src/detect/project.ts
2691
2921
  import { readFile as readFile2 } from "fs/promises";
2692
2922
  import { join } from "path";
@@ -3076,6 +3306,36 @@ var init_cors_credentials = __esm({
3076
3306
  }
3077
3307
  });
3078
3308
 
3309
+ // src/utils/response.ts
3310
+ function unwrapResponse(parsed) {
3311
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return parsed;
3312
+ const obj = parsed;
3313
+ const keys = Object.keys(obj);
3314
+ if (keys.length > 3) return parsed;
3315
+ let best = null;
3316
+ let bestSize = 0;
3317
+ for (const key of keys) {
3318
+ const val = obj[key];
3319
+ if (Array.isArray(val) && val.length > bestSize) {
3320
+ best = val;
3321
+ bestSize = val.length;
3322
+ } else if (val && typeof val === "object" && !Array.isArray(val)) {
3323
+ const size = Object.keys(val).length;
3324
+ if (size > bestSize) {
3325
+ best = val;
3326
+ bestSize = size;
3327
+ }
3328
+ }
3329
+ }
3330
+ return best && bestSize >= OVERFETCH_UNWRAP_MIN_SIZE ? best : parsed;
3331
+ }
3332
+ var init_response = __esm({
3333
+ "src/utils/response.ts"() {
3334
+ "use strict";
3335
+ init_thresholds();
3336
+ }
3337
+ });
3338
+
3079
3339
  // src/analysis/rules/response-pii-leak.ts
3080
3340
  function tryParseJson2(body) {
3081
3341
  if (!body) return null;
@@ -3117,28 +3377,6 @@ function hasInternalIds(obj) {
3117
3377
  }
3118
3378
  return false;
3119
3379
  }
3120
- function unwrapResponse(parsed) {
3121
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return parsed;
3122
- const obj = parsed;
3123
- const keys = Object.keys(obj);
3124
- if (keys.length > 3) return parsed;
3125
- let best = null;
3126
- let bestSize = 0;
3127
- for (const key of keys) {
3128
- const val = obj[key];
3129
- if (Array.isArray(val) && val.length > bestSize) {
3130
- best = val;
3131
- bestSize = val.length;
3132
- } else if (val && typeof val === "object" && !Array.isArray(val)) {
3133
- const size = Object.keys(val).length;
3134
- if (size > bestSize) {
3135
- best = val;
3136
- bestSize = size;
3137
- }
3138
- }
3139
- }
3140
- return best && bestSize >= 3 ? best : parsed;
3141
- }
3142
3380
  function detectPII(method, reqBody, resBody) {
3143
3381
  const target = unwrapResponse(resBody);
3144
3382
  if (WRITE_METHODS.has(method) && reqBody && typeof reqBody === "object") {
@@ -3186,6 +3424,7 @@ var init_response_pii_leak = __esm({
3186
3424
  "src/analysis/rules/response-pii-leak.ts"() {
3187
3425
  "use strict";
3188
3426
  init_patterns();
3427
+ init_response();
3189
3428
  WRITE_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH"]);
3190
3429
  FULL_RECORD_MIN_FIELDS = 5;
3191
3430
  LIST_PII_MIN_ITEMS = 2;
@@ -3879,36 +4118,6 @@ var init_high_rows = __esm({
3879
4118
  }
3880
4119
  });
3881
4120
 
3882
- // src/utils/response.ts
3883
- function unwrapResponse2(parsed) {
3884
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return parsed;
3885
- const obj = parsed;
3886
- const keys = Object.keys(obj);
3887
- if (keys.length > 3) return parsed;
3888
- let best = null;
3889
- let bestSize = 0;
3890
- for (const key of keys) {
3891
- const val = obj[key];
3892
- if (Array.isArray(val) && val.length > bestSize) {
3893
- best = val;
3894
- bestSize = val.length;
3895
- } else if (val && typeof val === "object" && !Array.isArray(val)) {
3896
- const size = Object.keys(val).length;
3897
- if (size > bestSize) {
3898
- best = val;
3899
- bestSize = size;
3900
- }
3901
- }
3902
- }
3903
- return best && bestSize >= OVERFETCH_UNWRAP_MIN_SIZE ? best : parsed;
3904
- }
3905
- var init_response = __esm({
3906
- "src/utils/response.ts"() {
3907
- "use strict";
3908
- init_thresholds();
3909
- }
3910
- });
3911
-
3912
4121
  // src/analysis/insights/rules/response-overfetch.ts
3913
4122
  var responseOverfetchRule;
3914
4123
  var init_response_overfetch = __esm({
@@ -3933,7 +4142,7 @@ var init_response_overfetch = __esm({
3933
4142
  } catch {
3934
4143
  continue;
3935
4144
  }
3936
- const target = unwrapResponse2(parsed);
4145
+ const target = unwrapResponse(parsed);
3937
4146
  const inspectObj = Array.isArray(target) && target.length > 0 ? target[0] : target;
3938
4147
  if (!inspectObj || typeof inspectObj !== "object" || Array.isArray(inspectObj)) continue;
3939
4148
  const fields = Object.keys(inspectObj);
@@ -4133,8 +4342,9 @@ var init_engine = __esm({
4133
4342
  init_rules();
4134
4343
  init_insights3();
4135
4344
  AnalysisEngine = class {
4136
- constructor(metricsStore, debounceMs = 300) {
4345
+ constructor(metricsStore, findingStore, debounceMs = 300) {
4137
4346
  this.metricsStore = metricsStore;
4347
+ this.findingStore = findingStore;
4138
4348
  this.debounceMs = debounceMs;
4139
4349
  this.scanner = createDefaultScanner();
4140
4350
  this.boundRequestListener = () => this.scheduleRecompute();
@@ -4195,6 +4405,12 @@ var init_engine = __esm({
4195
4405
  const fetches = defaultFetchStore.getAll();
4196
4406
  const flows = groupRequestsIntoFlows(requests);
4197
4407
  this.cachedFindings = this.scanner.scan({ requests, logs });
4408
+ if (this.findingStore) {
4409
+ for (const finding of this.cachedFindings) {
4410
+ this.findingStore.upsert(finding, "passive");
4411
+ }
4412
+ this.findingStore.reconcilePassive(this.cachedFindings);
4413
+ }
4198
4414
  this.cachedInsights = computeInsights({
4199
4415
  requests,
4200
4416
  queries,
@@ -4220,13 +4436,14 @@ var VERSION;
4220
4436
  var init_src = __esm({
4221
4437
  "src/index.ts"() {
4222
4438
  "use strict";
4439
+ init_finding_store();
4223
4440
  init_project();
4224
4441
  init_adapter_registry();
4225
4442
  init_rules();
4226
4443
  init_engine();
4227
4444
  init_insights3();
4228
4445
  init_insights2();
4229
- VERSION = "0.7.5";
4446
+ VERSION = "0.8.0";
4230
4447
  }
4231
4448
  });
4232
4449
 
@@ -6660,12 +6877,12 @@ var init_page = __esm({
6660
6877
  // src/telemetry/config.ts
6661
6878
  import { homedir } from "os";
6662
6879
  import { join as join2 } from "path";
6663
- import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
6880
+ import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4 } from "fs";
6664
6881
  import { randomUUID as randomUUID5 } from "crypto";
6665
6882
  function readConfig() {
6666
6883
  try {
6667
- if (!existsSync3(CONFIG_PATH)) return null;
6668
- return JSON.parse(readFileSync3(CONFIG_PATH, "utf-8"));
6884
+ if (!existsSync4(CONFIG_PATH)) return null;
6885
+ return JSON.parse(readFileSync4(CONFIG_PATH, "utf-8"));
6669
6886
  } catch {
6670
6887
  return null;
6671
6888
  }
@@ -6728,6 +6945,9 @@ function createDashboardHandler(deps) {
6728
6945
  routes[DASHBOARD_API_INSIGHTS] = createInsightsHandler(deps.analysisEngine);
6729
6946
  routes[DASHBOARD_API_SECURITY] = createSecurityHandler(deps.analysisEngine);
6730
6947
  }
6948
+ if (deps.findingStore) {
6949
+ routes[DASHBOARD_API_FINDINGS] = createFindingsHandler(deps.findingStore);
6950
+ }
6731
6951
  routes[DASHBOARD_API_TAB] = (req, res) => {
6732
6952
  const raw = (req.url ?? "").split("tab=")[1];
6733
6953
  if (raw) {
@@ -6760,6 +6980,7 @@ var init_router = __esm({
6760
6980
  init_constants();
6761
6981
  init_api();
6762
6982
  init_insights();
6983
+ init_findings();
6763
6984
  init_sse();
6764
6985
  init_page();
6765
6986
  init_telemetry();
@@ -6800,12 +7021,11 @@ function colorTitle(severity, text) {
6800
7021
  function truncate(s, max = 80) {
6801
7022
  return s.length <= max ? s : s.slice(0, max - 1) + "\u2026";
6802
7023
  }
6803
- function formatConsoleLine(insight, dashboardUrl, suffix) {
7024
+ function formatConsoleLine(insight, suffix) {
6804
7025
  const icon = severityIcon(insight.severity);
6805
7026
  const title = colorTitle(insight.severity, insight.title);
6806
7027
  const desc = pc.dim(truncate(insight.desc) + (suffix ?? ""));
6807
- const link = pc.dim(`\u2192 ${dashboardUrl}`);
6808
- let line = ` ${icon} ${title} \u2014 ${desc} ${link}`;
7028
+ let line = ` ${icon} ${title} \u2014 ${desc}`;
6809
7029
  if (insight.detail) {
6810
7030
  line += `
6811
7031
  ${pc.dim("\u2514 " + insight.detail)}`;
@@ -6831,11 +7051,13 @@ function createConsoleInsightListener(proxyPort, metricsStore) {
6831
7051
  suffix = ` (\u2191 from ${prev.p95DurationMs < 1e3 ? prev.p95DurationMs + "ms" : (prev.p95DurationMs / 1e3).toFixed(1) + "s"})`;
6832
7052
  }
6833
7053
  }
6834
- lines.push(formatConsoleLine(insight, dashUrl, suffix));
7054
+ lines.push(formatConsoleLine(insight, suffix));
6835
7055
  }
6836
7056
  if (lines.length > 0) {
6837
7057
  print("");
6838
7058
  for (const line of lines) print(line);
7059
+ print("");
7060
+ 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"')}`);
6839
7061
  }
6840
7062
  };
6841
7063
  }
@@ -7071,6 +7293,8 @@ var setup_exports = {};
7071
7293
  __export(setup_exports, {
7072
7294
  setup: () => setup
7073
7295
  });
7296
+ import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, existsSync as existsSync5, unlinkSync as unlinkSync2 } from "fs";
7297
+ import { resolve as resolve4 } from "path";
7074
7298
  function setup() {
7075
7299
  if (initialized) return;
7076
7300
  initialized = true;
@@ -7083,7 +7307,9 @@ function setup() {
7083
7307
  const cwd = process.cwd();
7084
7308
  const metricsStore = new MetricsStore(new FileMetricsPersistence(cwd));
7085
7309
  metricsStore.start();
7086
- const analysisEngine = new AnalysisEngine(metricsStore);
7310
+ const findingStore = new FindingStore(cwd);
7311
+ findingStore.start();
7312
+ const analysisEngine = new AnalysisEngine(metricsStore, findingStore);
7087
7313
  analysisEngine.start();
7088
7314
  const config = {
7089
7315
  proxyPort: 0,
@@ -7091,7 +7317,7 @@ function setup() {
7091
7317
  showStatic: false,
7092
7318
  maxBodyCapture: DEFAULT_MAX_BODY_CAPTURE
7093
7319
  };
7094
- const handleDashboard = createDashboardHandler({ metricsStore, analysisEngine });
7320
+ const handleDashboard = createDashboardHandler({ metricsStore, analysisEngine, findingStore });
7095
7321
  onRequest((req) => {
7096
7322
  const queries = defaultQueryStore.getByRequest(req.id);
7097
7323
  const fetches = defaultFetchStore.getByRequest(req.id);
@@ -7105,6 +7331,9 @@ function setup() {
7105
7331
  handleDashboard,
7106
7332
  config,
7107
7333
  onFirstRequest(port) {
7334
+ const dir = resolve4(cwd, METRICS_DIR);
7335
+ if (!existsSync5(dir)) mkdirSync5(dir, { recursive: true });
7336
+ writeFileSync5(resolve4(cwd, PORT_FILE), String(port));
7108
7337
  analysisEngine.onUpdate(createConsoleInsightListener(port, metricsStore));
7109
7338
  process.stdout.write(` brakit v${VERSION} \u2014 http://localhost:${port}${DASHBOARD_PREFIX}
7110
7339
  `);
@@ -7113,7 +7342,13 @@ function setup() {
7113
7342
  health.setTeardown(() => {
7114
7343
  uninstallInterceptor();
7115
7344
  analysisEngine.stop();
7345
+ findingStore.stop();
7116
7346
  metricsStore.stop();
7347
+ try {
7348
+ const portPath = resolve4(cwd, PORT_FILE);
7349
+ if (existsSync5(portPath)) unlinkSync2(portPath);
7350
+ } catch {
7351
+ }
7117
7352
  });
7118
7353
  }
7119
7354
  function routeEvent2(event) {
@@ -7144,6 +7379,7 @@ var init_setup = __esm({
7144
7379
  init_router();
7145
7380
  init_request_log();
7146
7381
  init_store();
7382
+ init_finding_store();
7147
7383
  init_engine();
7148
7384
  init_terminal();
7149
7385
  init_src();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brakit",
3
- "version": "0.7.5",
3
+ "version": "0.8.0",
4
4
  "description": "See what your API is really doing. Security scanning, N+1 detection, duplicate calls, DB queries — one command, zero config.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -28,6 +28,9 @@
28
28
  "typecheck": "tsc --noEmit",
29
29
  "test": "vitest run",
30
30
  "test:watch": "vitest",
31
+ "test:contracts": "vitest run tests/contracts tests/adapters",
32
+ "test:integration": "vitest run tests/integration",
33
+ "ci": "npm run typecheck && npm test && npm run build",
31
34
  "lint": "tsc --noEmit"
32
35
  },
33
36
  "repository": {
@@ -40,6 +43,7 @@
40
43
  },
41
44
  "author": "Brakit <dev@brakit.ai> (https://brakit.ai)",
42
45
  "dependencies": {
46
+ "@modelcontextprotocol/sdk": "^1.0.0",
43
47
  "citty": "^0.1.6",
44
48
  "picocolors": "^1.1.1"
45
49
  },