brakit 0.8.1 → 0.8.2

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.
@@ -28,12 +28,10 @@ var init_routes = __esm({
28
28
  });
29
29
 
30
30
  // src/constants/limits.ts
31
- var MAX_REQUEST_ENTRIES, MAX_TELEMETRY_ENTRIES, MAX_INGEST_BYTES;
31
+ var MAX_INGEST_BYTES;
32
32
  var init_limits = __esm({
33
33
  "src/constants/limits.ts"() {
34
34
  "use strict";
35
- MAX_REQUEST_ENTRIES = 1e3;
36
- MAX_TELEMETRY_ENTRIES = 1e3;
37
35
  MAX_INGEST_BYTES = 10 * 1024 * 1024;
38
36
  }
39
37
  });
@@ -79,23 +77,47 @@ var init_network = __esm({
79
77
  });
80
78
 
81
79
  // src/constants/mcp.ts
82
- var MCP_SERVER_NAME, MCP_SERVER_VERSION, INITIAL_DISCOVERY_TIMEOUT_MS, LAZY_DISCOVERY_TIMEOUT_MS, CLIENT_FETCH_TIMEOUT_MS, HEALTH_CHECK_TIMEOUT_MS, DISCOVERY_POLL_INTERVAL_MS, MAX_TIMELINE_EVENTS, MAX_RESOLVED_DISPLAY, ENRICHMENT_SEVERITY_FILTER;
80
+ var MCP_SERVER_NAME, MCP_SERVER_VERSION, INITIAL_DISCOVERY_TIMEOUT_MS, LAZY_DISCOVERY_TIMEOUT_MS, CLIENT_FETCH_TIMEOUT_MS, HEALTH_CHECK_TIMEOUT_MS, DISCOVERY_POLL_INTERVAL_MS, MAX_DISCOVERY_DEPTH, MAX_TIMELINE_EVENTS, MAX_RESOLVED_DISPLAY, ENRICHMENT_SEVERITY_FILTER;
83
81
  var init_mcp = __esm({
84
82
  "src/constants/mcp.ts"() {
85
83
  "use strict";
86
84
  MCP_SERVER_NAME = "brakit";
87
- MCP_SERVER_VERSION = "0.8.1";
85
+ MCP_SERVER_VERSION = "0.8.2";
88
86
  INITIAL_DISCOVERY_TIMEOUT_MS = 5e3;
89
87
  LAZY_DISCOVERY_TIMEOUT_MS = 2e3;
90
88
  CLIENT_FETCH_TIMEOUT_MS = 1e4;
91
89
  HEALTH_CHECK_TIMEOUT_MS = 3e3;
92
90
  DISCOVERY_POLL_INTERVAL_MS = 500;
91
+ MAX_DISCOVERY_DEPTH = 5;
93
92
  MAX_TIMELINE_EVENTS = 20;
94
93
  MAX_RESOLVED_DISPLAY = 5;
95
94
  ENRICHMENT_SEVERITY_FILTER = ["critical", "warning"];
96
95
  }
97
96
  });
98
97
 
98
+ // src/constants/encoding.ts
99
+ var init_encoding = __esm({
100
+ "src/constants/encoding.ts"() {
101
+ "use strict";
102
+ }
103
+ });
104
+
105
+ // src/constants/severity.ts
106
+ var SEVERITY_CRITICAL, SEVERITY_WARNING, SEVERITY_INFO, SEVERITY_ICON_MAP;
107
+ var init_severity = __esm({
108
+ "src/constants/severity.ts"() {
109
+ "use strict";
110
+ SEVERITY_CRITICAL = "critical";
111
+ SEVERITY_WARNING = "warning";
112
+ SEVERITY_INFO = "info";
113
+ SEVERITY_ICON_MAP = {
114
+ [SEVERITY_CRITICAL]: { icon: "\u2717", cls: "critical" },
115
+ [SEVERITY_WARNING]: { icon: "\u26A0", cls: "warning" },
116
+ [SEVERITY_INFO]: { icon: "\u2139", cls: "info" }
117
+ };
118
+ }
119
+ });
120
+
99
121
  // src/constants/index.ts
100
122
  var init_constants = __esm({
101
123
  "src/constants/index.ts"() {
@@ -108,6 +130,8 @@ var init_constants = __esm({
108
130
  init_headers();
109
131
  init_network();
110
132
  init_mcp();
133
+ init_encoding();
134
+ init_severity();
111
135
  }
112
136
  });
113
137
 
@@ -220,22 +244,55 @@ var init_client = __esm({
220
244
  });
221
245
 
222
246
  // src/mcp/discovery.ts
223
- import { readFileSync as readFileSync4, existsSync as existsSync4 } from "fs";
224
- import { resolve as resolve6 } from "path";
247
+ import { readFileSync as readFileSync3, existsSync as existsSync4, readdirSync, statSync } from "fs";
248
+ import { resolve as resolve5, dirname } from "path";
249
+ function readPort(portPath) {
250
+ if (!existsSync4(portPath)) return null;
251
+ const raw = readFileSync3(portPath, "utf-8").trim();
252
+ const port = parseInt(raw, 10);
253
+ return isNaN(port) || port < 1 || port > 65535 ? null : port;
254
+ }
255
+ function portInDir(dir) {
256
+ return readPort(resolve5(dir, PORT_FILE));
257
+ }
258
+ function portInChildren(dir) {
259
+ try {
260
+ for (const entry of readdirSync(dir)) {
261
+ if (entry.startsWith(".") || entry === "node_modules") continue;
262
+ const child = resolve5(dir, entry);
263
+ try {
264
+ if (!statSync(child).isDirectory()) continue;
265
+ } catch {
266
+ continue;
267
+ }
268
+ const port = portInDir(child);
269
+ if (port) return port;
270
+ }
271
+ } catch {
272
+ }
273
+ return null;
274
+ }
275
+ function searchForPort(startDir) {
276
+ const start = resolve5(startDir);
277
+ const initial = portInDir(start) ?? portInChildren(start);
278
+ if (initial) return initial;
279
+ let dir = dirname(start);
280
+ for (let depth = 0; depth < MAX_DISCOVERY_DEPTH; depth++) {
281
+ const port = portInDir(dir);
282
+ if (port) return port;
283
+ const parent = dirname(dir);
284
+ if (parent === dir) break;
285
+ dir = parent;
286
+ }
287
+ return null;
288
+ }
225
289
  function discoverBrakitPort(cwd) {
226
- const root = cwd ?? process.cwd();
227
- const portPath = resolve6(root, PORT_FILE);
228
- if (!existsSync4(portPath)) {
290
+ const port = searchForPort(cwd ?? process.cwd());
291
+ if (!port) {
229
292
  throw new Error(
230
- `Brakit is not running. No port file found at ${portPath}.
231
- Start your app with brakit enabled first.`
293
+ "Brakit is not running. Start your app with brakit enabled first."
232
294
  );
233
295
  }
234
- const raw = readFileSync4(portPath, "utf-8").trim();
235
- const port = parseInt(raw, 10);
236
- if (isNaN(port) || port < 1 || port > 65535) {
237
- throw new Error(`Invalid port in ${portPath}: "${raw}"`);
238
- }
239
296
  return { port, baseUrl: `http://localhost:${port}` };
240
297
  }
241
298
  async function waitForBrakit(cwd, timeoutMs = 1e4, pollMs = DISCOVERY_POLL_INTERVAL_MS) {
@@ -927,22 +984,24 @@ import { runMain } from "citty";
927
984
 
928
985
  // src/cli/commands/install.ts
929
986
  import { defineCommand } from "citty";
930
- import { resolve as resolve4, join as join2 } from "path";
931
- import { readFile as readFile3, writeFile as writeFile4 } from "fs/promises";
987
+ import { resolve as resolve3, join as join2 } from "path";
988
+ import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
932
989
  import { execSync } from "child_process";
933
990
  import pc from "picocolors";
934
991
 
935
992
  // src/store/finding-store.ts
936
993
  init_constants();
994
+ import { readFileSync as readFileSync2, existsSync as existsSync3 } from "fs";
995
+ import { resolve as resolve2 } from "path";
996
+
997
+ // src/utils/atomic-writer.ts
937
998
  import {
938
- readFileSync as readFileSync2,
939
999
  writeFileSync as writeFileSync2,
940
1000
  existsSync as existsSync2,
941
1001
  mkdirSync as mkdirSync2,
942
1002
  renameSync
943
1003
  } from "fs";
944
1004
  import { writeFile as writeFile2, mkdir, rename } from "fs/promises";
945
- import { resolve as resolve2 } from "path";
946
1005
 
947
1006
  // src/utils/fs.ts
948
1007
  import { access } from "fs/promises";
@@ -1476,166 +1535,9 @@ var responsePiiLeakRule = {
1476
1535
  }
1477
1536
  };
1478
1537
 
1479
- // src/store/request-store.ts
1480
- init_constants();
1481
-
1482
- // src/utils/static-patterns.ts
1483
- var STATIC_PATTERNS = [
1484
- /^\/_next\//,
1485
- /\.(?:js|css|map|ico|png|jpg|jpeg|gif|svg|webp|woff2?|ttf|eot)$/,
1486
- /^\/favicon/,
1487
- /^\/__nextjs/
1488
- ];
1489
- function isStaticPath(urlPath) {
1490
- return STATIC_PATTERNS.some((p) => p.test(urlPath));
1491
- }
1492
-
1493
- // src/store/request-store.ts
1494
- function flattenHeaders(headers) {
1495
- const flat = {};
1496
- for (const [key, value] of Object.entries(headers)) {
1497
- if (value === void 0) continue;
1498
- flat[key] = Array.isArray(value) ? value.join(", ") : value;
1499
- }
1500
- return flat;
1501
- }
1502
- var RequestStore = class {
1503
- constructor(maxEntries = MAX_REQUEST_ENTRIES) {
1504
- this.maxEntries = maxEntries;
1505
- }
1506
- requests = [];
1507
- listeners = [];
1508
- capture(input) {
1509
- const url = input.url;
1510
- const path = url.split("?")[0];
1511
- let requestBodyStr = null;
1512
- if (input.requestBody && input.requestBody.length > 0) {
1513
- requestBodyStr = input.requestBody.subarray(0, input.config.maxBodyCapture).toString("utf-8");
1514
- }
1515
- let responseBodyStr = null;
1516
- if (input.responseBody && input.responseBody.length > 0) {
1517
- const ct = input.responseContentType;
1518
- if (ct.includes("json") || ct.includes("text") || ct.includes("html")) {
1519
- responseBodyStr = input.responseBody.subarray(0, input.config.maxBodyCapture).toString("utf-8");
1520
- }
1521
- }
1522
- const entry = {
1523
- id: input.requestId,
1524
- method: input.method,
1525
- url,
1526
- path,
1527
- headers: flattenHeaders(input.requestHeaders),
1528
- requestBody: requestBodyStr,
1529
- statusCode: input.statusCode,
1530
- responseHeaders: flattenHeaders(input.responseHeaders),
1531
- responseBody: responseBodyStr,
1532
- startedAt: input.startTime,
1533
- durationMs: Math.round((input.endTime ?? performance.now()) - input.startTime),
1534
- responseSize: input.responseBody?.length ?? 0,
1535
- isStatic: isStaticPath(path)
1536
- };
1537
- this.requests.push(entry);
1538
- if (this.requests.length > this.maxEntries) {
1539
- this.requests.shift();
1540
- }
1541
- for (const fn of this.listeners) {
1542
- fn(entry);
1543
- }
1544
- return entry;
1545
- }
1546
- getAll() {
1547
- return this.requests;
1548
- }
1549
- clear() {
1550
- this.requests.length = 0;
1551
- }
1552
- onRequest(fn) {
1553
- this.listeners.push(fn);
1554
- }
1555
- offRequest(fn) {
1556
- const idx = this.listeners.indexOf(fn);
1557
- if (idx !== -1) this.listeners.splice(idx, 1);
1558
- }
1559
- };
1560
-
1561
- // src/store/request-log.ts
1562
- var defaultStore = new RequestStore();
1563
-
1564
- // src/store/telemetry-store.ts
1565
- init_constants();
1566
- import { randomUUID } from "crypto";
1567
- var TelemetryStore = class {
1568
- constructor(maxEntries = MAX_TELEMETRY_ENTRIES) {
1569
- this.maxEntries = maxEntries;
1570
- }
1571
- entries = [];
1572
- listeners = [];
1573
- add(data) {
1574
- const entry = { id: randomUUID(), ...data };
1575
- this.entries.push(entry);
1576
- if (this.entries.length > this.maxEntries) this.entries.shift();
1577
- for (const fn of this.listeners) fn(entry);
1578
- return entry;
1579
- }
1580
- getAll() {
1581
- return this.entries;
1582
- }
1583
- getByRequest(requestId) {
1584
- return this.entries.filter((e) => e.parentRequestId === requestId);
1585
- }
1586
- clear() {
1587
- this.entries.length = 0;
1588
- }
1589
- onEntry(fn) {
1590
- this.listeners.push(fn);
1591
- }
1592
- offEntry(fn) {
1593
- const idx = this.listeners.indexOf(fn);
1594
- if (idx !== -1) this.listeners.splice(idx, 1);
1595
- }
1596
- };
1597
-
1598
- // src/store/fetch-store.ts
1599
- var FetchStore = class extends TelemetryStore {
1600
- };
1601
- var defaultFetchStore = new FetchStore();
1602
-
1603
- // src/store/log-store.ts
1604
- var LogStore = class extends TelemetryStore {
1605
- };
1606
- var defaultLogStore = new LogStore();
1607
-
1608
- // src/store/error-store.ts
1609
- var ErrorStore = class extends TelemetryStore {
1610
- };
1611
- var defaultErrorStore = new ErrorStore();
1612
-
1613
- // src/store/query-store.ts
1614
- var QueryStore = class extends TelemetryStore {
1615
- };
1616
- var defaultQueryStore = new QueryStore();
1617
-
1618
- // src/store/metrics/metrics-store.ts
1619
- init_constants();
1620
- import { randomUUID as randomUUID2 } from "crypto";
1621
- init_endpoint();
1622
-
1623
- // src/store/metrics/persistence.ts
1624
- init_constants();
1625
- import {
1626
- readFileSync as readFileSync3,
1627
- writeFileSync as writeFileSync3,
1628
- mkdirSync as mkdirSync3,
1629
- existsSync as existsSync3,
1630
- unlinkSync,
1631
- renameSync as renameSync2
1632
- } from "fs";
1633
- import { writeFile as writeFile3, mkdir as mkdir2, rename as rename2 } from "fs/promises";
1634
- import { resolve as resolve3 } from "path";
1635
-
1636
1538
  // src/analysis/group.ts
1637
1539
  init_constants();
1638
- import { randomUUID as randomUUID3 } from "crypto";
1540
+ import { randomUUID } from "crypto";
1639
1541
 
1640
1542
  // src/analysis/label.ts
1641
1543
  init_constants();
@@ -1650,50 +1552,50 @@ init_thresholds();
1650
1552
 
1651
1553
  // src/analysis/insights/rules/n1.ts
1652
1554
  init_endpoint();
1653
- init_thresholds();
1555
+ init_constants();
1654
1556
 
1655
1557
  // src/analysis/insights/rules/cross-endpoint.ts
1656
1558
  init_endpoint();
1657
- init_thresholds();
1559
+ init_constants();
1658
1560
 
1659
1561
  // src/analysis/insights/rules/redundant-query.ts
1660
1562
  init_endpoint();
1661
- init_thresholds();
1563
+ init_constants();
1662
1564
 
1663
1565
  // src/analysis/insights/rules/error-hotspot.ts
1664
- init_thresholds();
1566
+ init_constants();
1665
1567
 
1666
1568
  // src/analysis/insights/rules/duplicate.ts
1667
- init_thresholds();
1569
+ init_constants();
1668
1570
 
1669
1571
  // src/analysis/insights/rules/slow.ts
1670
- init_thresholds();
1572
+ init_constants();
1671
1573
 
1672
1574
  // src/analysis/insights/rules/query-heavy.ts
1673
- init_thresholds();
1575
+ init_constants();
1674
1576
 
1675
1577
  // src/analysis/insights/rules/select-star.ts
1676
- init_thresholds();
1578
+ init_constants();
1677
1579
 
1678
1580
  // src/analysis/insights/rules/high-rows.ts
1679
- init_thresholds();
1581
+ init_constants();
1680
1582
 
1681
1583
  // src/analysis/insights/rules/response-overfetch.ts
1682
1584
  init_endpoint();
1683
- init_thresholds();
1585
+ init_constants();
1684
1586
 
1685
1587
  // src/analysis/insights/rules/large-response.ts
1686
- init_thresholds();
1588
+ init_constants();
1687
1589
 
1688
1590
  // src/analysis/insights/rules/regression.ts
1689
- init_thresholds();
1591
+ init_constants();
1690
1592
 
1691
1593
  // src/analysis/insight-tracker.ts
1692
1594
  init_endpoint();
1693
1595
  init_thresholds();
1694
1596
 
1695
1597
  // src/index.ts
1696
- var VERSION = "0.8.1";
1598
+ var VERSION = "0.8.2";
1697
1599
 
1698
1600
  // src/cli/commands/install.ts
1699
1601
  var IMPORT_LINE = `import "brakit";`;
@@ -1713,10 +1615,21 @@ var install_default = defineCommand({
1713
1615
  }
1714
1616
  },
1715
1617
  async run({ args }) {
1716
- const rootDir = resolve4(args.dir);
1618
+ const rootDir = resolve3(args.dir);
1717
1619
  const pkgPath = join2(rootDir, "package.json");
1718
1620
  if (!await fileExists(pkgPath)) {
1719
- console.error(pc.red(" No package.json found. Run this from your project root."));
1621
+ console.error(pc.red(" No project found. Run this from your project directory."));
1622
+ process.exit(1);
1623
+ }
1624
+ let pkg;
1625
+ try {
1626
+ pkg = JSON.parse(await readFile3(pkgPath, "utf-8"));
1627
+ } catch {
1628
+ console.error(pc.red(" Failed to read package.json."));
1629
+ process.exit(1);
1630
+ }
1631
+ if (!pkg.name || typeof pkg.name !== "string") {
1632
+ console.error(pc.red(" No project found. Run this from your project directory."));
1720
1633
  process.exit(1);
1721
1634
  }
1722
1635
  let project;
@@ -1813,7 +1726,7 @@ async function setupNextjs(rootDir) {
1813
1726
  `}`,
1814
1727
  ``
1815
1728
  ].join("\n");
1816
- await writeFile4(absPath, content);
1729
+ await writeFile3(absPath, content);
1817
1730
  return { action: "created", file: relPath, content };
1818
1731
  }
1819
1732
  async function setupNuxt(rootDir) {
@@ -1829,9 +1742,9 @@ async function setupNuxt(rootDir) {
1829
1742
  const content = `${IMPORT_LINE}
1830
1743
  `;
1831
1744
  const dir = join2(rootDir, "server/plugins");
1832
- const { mkdirSync: mkdirSync4 } = await import("fs");
1833
- mkdirSync4(dir, { recursive: true });
1834
- await writeFile4(absPath, content);
1745
+ const { mkdirSync: mkdirSync3 } = await import("fs");
1746
+ mkdirSync3(dir, { recursive: true });
1747
+ await writeFile3(absPath, content);
1835
1748
  return { action: "created", file: relPath, content };
1836
1749
  }
1837
1750
  async function setupPrepend(rootDir, ...candidates) {
@@ -1842,7 +1755,7 @@ async function setupPrepend(rootDir, ...candidates) {
1842
1755
  if (content.includes(IMPORT_MARKER)) {
1843
1756
  return { action: "exists", file: relPath };
1844
1757
  }
1845
- await writeFile4(absPath, `${IMPORT_LINE}
1758
+ await writeFile3(absPath, `${IMPORT_LINE}
1846
1759
  ${content}`);
1847
1760
  return { action: "prepended", file: relPath };
1848
1761
  }
@@ -1892,13 +1805,13 @@ async function setupMcp(rootDir) {
1892
1805
  const config = JSON.parse(raw);
1893
1806
  if (config?.mcpServers?.brakit) return "exists";
1894
1807
  config.mcpServers = { ...config.mcpServers, ...MCP_CONFIG.mcpServers };
1895
- await writeFile4(mcpPath, JSON.stringify(config, null, 2) + "\n");
1808
+ await writeFile3(mcpPath, JSON.stringify(config, null, 2) + "\n");
1896
1809
  await ensureGitignoreMcp(rootDir);
1897
1810
  return "updated";
1898
1811
  } catch {
1899
1812
  }
1900
1813
  }
1901
- await writeFile4(mcpPath, JSON.stringify(MCP_CONFIG, null, 2) + "\n");
1814
+ await writeFile3(mcpPath, JSON.stringify(MCP_CONFIG, null, 2) + "\n");
1902
1815
  await ensureGitignoreMcp(rootDir);
1903
1816
  return "created";
1904
1817
  }
@@ -1908,9 +1821,9 @@ async function ensureGitignoreMcp(rootDir) {
1908
1821
  if (await fileExists(gitignorePath)) {
1909
1822
  const content = await readFile3(gitignorePath, "utf-8");
1910
1823
  if (content.split("\n").some((l) => l.trim() === ".mcp.json")) return;
1911
- await writeFile4(gitignorePath, content.trimEnd() + "\n.mcp.json\n");
1824
+ await writeFile3(gitignorePath, content.trimEnd() + "\n.mcp.json\n");
1912
1825
  } else {
1913
- await writeFile4(gitignorePath, ".mcp.json\n");
1826
+ await writeFile3(gitignorePath, ".mcp.json\n");
1914
1827
  }
1915
1828
  } catch {
1916
1829
  }
@@ -1934,8 +1847,8 @@ function printManualInstructions(framework) {
1934
1847
 
1935
1848
  // src/cli/commands/uninstall.ts
1936
1849
  import { defineCommand as defineCommand2 } from "citty";
1937
- import { resolve as resolve5, join as join3 } from "path";
1938
- import { readFile as readFile4, writeFile as writeFile5, unlink, rm } from "fs/promises";
1850
+ import { resolve as resolve4, join as join3 } from "path";
1851
+ import { readFile as readFile4, writeFile as writeFile4, unlink, rm } from "fs/promises";
1939
1852
  import { execSync as execSync2 } from "child_process";
1940
1853
  import pc2 from "picocolors";
1941
1854
  init_constants();
@@ -1976,7 +1889,7 @@ var uninstall_default = defineCommand2({
1976
1889
  }
1977
1890
  },
1978
1891
  async run({ args }) {
1979
- const rootDir = resolve5(args.dir);
1892
+ const rootDir = resolve4(args.dir);
1980
1893
  let project = null;
1981
1894
  try {
1982
1895
  project = await detectProject(rootDir);
@@ -2014,7 +1927,7 @@ var uninstall_default = defineCommand2({
2014
1927
  const content = await readFile4(absPath, "utf-8");
2015
1928
  if (!content.includes(IMPORT_LINE2)) continue;
2016
1929
  const updated = content.split("\n").filter((line) => line.trim() !== IMPORT_LINE2.trim()).join("\n");
2017
- await writeFile5(absPath, updated);
1930
+ await writeFile4(absPath, updated);
2018
1931
  console.log(pc2.green(` \u2713 Removed brakit import from ${relPath}`));
2019
1932
  removed = true;
2020
1933
  break;
@@ -2054,7 +1967,7 @@ async function removeMcpConfig(rootDir) {
2054
1967
  if (Object.keys(config.mcpServers).length === 0) {
2055
1968
  await unlink(mcpPath);
2056
1969
  } else {
2057
- await writeFile5(mcpPath, JSON.stringify(config, null, 2) + "\n");
1970
+ await writeFile4(mcpPath, JSON.stringify(config, null, 2) + "\n");
2058
1971
  }
2059
1972
  return true;
2060
1973
  } catch {
@@ -2101,7 +2014,7 @@ async function cleanGitignore(rootDir) {
2101
2014
  const lines = content.split("\n");
2102
2015
  const filtered = lines.filter((line) => line.trim() !== METRICS_DIR);
2103
2016
  if (filtered.length === lines.length) return false;
2104
- await writeFile5(gitignorePath, filtered.join("\n"));
2017
+ await writeFile4(gitignorePath, filtered.join("\n"));
2105
2018
  return true;
2106
2019
  } catch {
2107
2020
  return false;
@@ -22,12 +22,13 @@ var DASHBOARD_API_FINDINGS = "/__brakit/api/findings";
22
22
 
23
23
  // src/constants/mcp.ts
24
24
  var MCP_SERVER_NAME = "brakit";
25
- var MCP_SERVER_VERSION = "0.8.1";
25
+ var MCP_SERVER_VERSION = "0.8.2";
26
26
  var INITIAL_DISCOVERY_TIMEOUT_MS = 5e3;
27
27
  var LAZY_DISCOVERY_TIMEOUT_MS = 2e3;
28
28
  var CLIENT_FETCH_TIMEOUT_MS = 1e4;
29
29
  var HEALTH_CHECK_TIMEOUT_MS = 3e3;
30
30
  var DISCOVERY_POLL_INTERVAL_MS = 500;
31
+ var MAX_DISCOVERY_DEPTH = 5;
31
32
  var MAX_TIMELINE_EVENTS = 20;
32
33
  var MAX_RESOLVED_DISPLAY = 5;
33
34
  var ENRICHMENT_SEVERITY_FILTER = ["critical", "warning"];
@@ -107,8 +108,8 @@ var BrakitClient = class {
107
108
  };
108
109
 
109
110
  // src/mcp/discovery.ts
110
- import { readFileSync, existsSync } from "fs";
111
- import { resolve } from "path";
111
+ import { readFileSync, existsSync, readdirSync, statSync } from "fs";
112
+ import { resolve, dirname } from "path";
112
113
 
113
114
  // src/constants/limits.ts
114
115
  var MAX_INGEST_BYTES = 10 * 1024 * 1024;
@@ -116,21 +117,64 @@ var MAX_INGEST_BYTES = 10 * 1024 * 1024;
116
117
  // src/constants/metrics.ts
117
118
  var PORT_FILE = ".brakit/port";
118
119
 
120
+ // src/constants/severity.ts
121
+ var SEVERITY_CRITICAL = "critical";
122
+ var SEVERITY_WARNING = "warning";
123
+ var SEVERITY_INFO = "info";
124
+ var SEVERITY_ICON_MAP = {
125
+ [SEVERITY_CRITICAL]: { icon: "\u2717", cls: "critical" },
126
+ [SEVERITY_WARNING]: { icon: "\u26A0", cls: "warning" },
127
+ [SEVERITY_INFO]: { icon: "\u2139", cls: "info" }
128
+ };
129
+
119
130
  // src/mcp/discovery.ts
131
+ function readPort(portPath) {
132
+ if (!existsSync(portPath)) return null;
133
+ const raw = readFileSync(portPath, "utf-8").trim();
134
+ const port = parseInt(raw, 10);
135
+ return isNaN(port) || port < 1 || port > 65535 ? null : port;
136
+ }
137
+ function portInDir(dir) {
138
+ return readPort(resolve(dir, PORT_FILE));
139
+ }
140
+ function portInChildren(dir) {
141
+ try {
142
+ for (const entry of readdirSync(dir)) {
143
+ if (entry.startsWith(".") || entry === "node_modules") continue;
144
+ const child = resolve(dir, entry);
145
+ try {
146
+ if (!statSync(child).isDirectory()) continue;
147
+ } catch {
148
+ continue;
149
+ }
150
+ const port = portInDir(child);
151
+ if (port) return port;
152
+ }
153
+ } catch {
154
+ }
155
+ return null;
156
+ }
157
+ function searchForPort(startDir) {
158
+ const start = resolve(startDir);
159
+ const initial = portInDir(start) ?? portInChildren(start);
160
+ if (initial) return initial;
161
+ let dir = dirname(start);
162
+ for (let depth = 0; depth < MAX_DISCOVERY_DEPTH; depth++) {
163
+ const port = portInDir(dir);
164
+ if (port) return port;
165
+ const parent = dirname(dir);
166
+ if (parent === dir) break;
167
+ dir = parent;
168
+ }
169
+ return null;
170
+ }
120
171
  function discoverBrakitPort(cwd) {
121
- const root = cwd ?? process.cwd();
122
- const portPath = resolve(root, PORT_FILE);
123
- if (!existsSync(portPath)) {
172
+ const port = searchForPort(cwd ?? process.cwd());
173
+ if (!port) {
124
174
  throw new Error(
125
- `Brakit is not running. No port file found at ${portPath}.
126
- Start your app with brakit enabled first.`
175
+ "Brakit is not running. Start your app with brakit enabled first."
127
176
  );
128
177
  }
129
- const raw = readFileSync(portPath, "utf-8").trim();
130
- const port = parseInt(raw, 10);
131
- if (isNaN(port) || port < 1 || port > 65535) {
132
- throw new Error(`Invalid port in ${portPath}: "${raw}"`);
133
- }
134
178
  return { port, baseUrl: `http://localhost:${port}` };
135
179
  }
136
180
  async function waitForBrakit(cwd, timeoutMs = 1e4, pollMs = DISCOVERY_POLL_INTERVAL_MS) {