brakit 0.10.1 → 0.10.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.
package/dist/api.d.ts CHANGED
@@ -1,5 +1,38 @@
1
1
  import { IncomingHttpHeaders } from 'node:http';
2
2
 
3
+ /**
4
+ * Canonical type definitions shared between server and browser client.
5
+ *
6
+ * Both `src/types/telemetry.ts` (server) and
7
+ * `src/dashboard/client/store/types.ts` (browser) import from here.
8
+ * This is the single source of truth for union types used across the
9
+ * bundling boundary.
10
+ */
11
+ type DbDriver = "pg" | "mysql2" | "prisma" | "asyncpg" | "sqlalchemy" | "sdk";
12
+ type LogLevel = "log" | "warn" | "error" | "info" | "debug";
13
+ type NormalizedOp = "SELECT" | "INSERT" | "UPDATE" | "DELETE" | "OTHER";
14
+ type Severity = "critical" | "warning" | "info";
15
+ /**
16
+ * State machine for issue lifecycle:
17
+ *
18
+ * open ──→ fixing ──→ resolved
19
+ * │ │ │
20
+ * │ ▼ ▼
21
+ * │ regressed (pruned after PRUNE_ISSUE_TTL_MS)
22
+ * │
23
+ * └──→ stale (no traffic for STALE_ISSUE_TTL_MS)
24
+ */
25
+ type IssueState = "open" | "fixing" | "resolved" | "stale" | "regressed";
26
+ type IssueSource = "passive";
27
+ type IssueCategory = "security" | "performance" | "reliability";
28
+ type AiFixStatus = "fixed" | "wont_fix";
29
+ interface SourceLocation {
30
+ file: string;
31
+ line: number;
32
+ column?: number;
33
+ fn?: string;
34
+ }
35
+
3
36
  type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS";
4
37
  type FlatHeaders = Record<string, string>;
5
38
  interface TracedRequest {
@@ -69,9 +102,10 @@ interface TracedFetch extends TelemetryEntry {
69
102
  method: string;
70
103
  statusCode: number;
71
104
  durationMs: number;
105
+ callSite?: SourceLocation;
72
106
  }
73
107
  interface TracedLog extends TelemetryEntry {
74
- level: "log" | "warn" | "error" | "info" | "debug";
108
+ level: LogLevel;
75
109
  message: string;
76
110
  }
77
111
  interface TracedError extends TelemetryEntry {
@@ -79,9 +113,8 @@ interface TracedError extends TelemetryEntry {
79
113
  message: string;
80
114
  stack?: string;
81
115
  }
82
- type NormalizedOp = "SELECT" | "INSERT" | "UPDATE" | "DELETE" | "OTHER";
83
116
  interface TracedQuery extends TelemetryEntry {
84
- driver: "pg" | "mysql2" | "prisma" | "asyncpg" | "sqlalchemy" | "sdk";
117
+ driver: DbDriver;
85
118
  sql?: string;
86
119
  model?: string;
87
120
  operation?: string;
@@ -91,6 +124,7 @@ interface TracedQuery extends TelemetryEntry {
91
124
  table?: string;
92
125
  source?: string;
93
126
  parentFetchId?: string;
127
+ callSite?: SourceLocation;
94
128
  }
95
129
  type TelemetryEvent = {
96
130
  type: "fetch";
@@ -157,8 +191,6 @@ interface RequestMetrics {
157
191
  fetchTimeMs: number;
158
192
  }
159
193
 
160
- /** Shared severity levels used by both security findings and insights. */
161
- type Severity = "critical" | "warning" | "info";
162
194
  type SecuritySeverity = Severity;
163
195
  interface SecurityFinding {
164
196
  severity: SecuritySeverity;
@@ -171,10 +203,6 @@ interface SecurityFinding {
171
203
  count: number;
172
204
  }
173
205
 
174
- type IssueState = "open" | "fixing" | "resolved" | "stale" | "regressed";
175
- type IssueSource = "passive";
176
- type IssueCategory = "security" | "performance" | "reliability";
177
- type AiFixStatus = "fixed" | "wont_fix";
178
206
  interface Issue {
179
207
  category: IssueCategory;
180
208
  /** Rule identifier: "slow", "n1", "exposed-secret", etc. */
@@ -188,6 +216,8 @@ interface Issue {
188
216
  endpoint?: string;
189
217
  /** Dashboard tab to link to (e.g., "requests", "queries", "security"). */
190
218
  nav?: string;
219
+ /** Occurrence count for rules that aggregate (e.g., N+1 query count). */
220
+ count?: number;
191
221
  }
192
222
  interface StatefulIssue {
193
223
  /** Stable ID derived from rule + endpoint + description hash. */
@@ -202,12 +232,11 @@ interface StatefulIssue {
202
232
  occurrences: number;
203
233
  /**
204
234
  * Number of requests to this endpoint that did NOT reproduce the issue
205
- * since it was last seen. Used for evidence-based resolution.
235
+ * since it was last seen. Used for evidence-based resolution:
236
+ * after CLEAN_HITS_FOR_RESOLUTION clean hits the issue auto-resolves.
206
237
  */
207
238
  cleanHitsSinceLastSeen: number;
208
- /** What AI reported after attempting a fix. */
209
239
  aiStatus: AiFixStatus | null;
210
- /** AI's summary of what was done or why it can't be fixed. */
211
240
  aiNotes: string | null;
212
241
  }
213
242
  interface IssuesData {
@@ -245,7 +274,6 @@ declare class IssueStore {
245
274
  private loadAsync;
246
275
  /** Sync load for tests only — not used in production paths. */
247
276
  loadSync(): void;
248
- /** Parse and populate issues from a raw JSON string. */
249
277
  private hydrate;
250
278
  private flush;
251
279
  private flushSync;
@@ -408,7 +436,6 @@ declare class RequestStore {
408
436
  }
409
437
 
410
438
  type TelemetryListener<T> = (entry: T) => void;
411
- /** Read-only view of a TelemetryStore — used by API handlers that only query data. */
412
439
  interface ReadonlyTelemetryStore {
413
440
  getAll(): readonly TelemetryEntry[];
414
441
  getByRequest(requestId: string): TelemetryEntry[];
package/dist/api.js CHANGED
@@ -360,9 +360,15 @@ var IssueStore = class {
360
360
  brakitDebug(`IssueStore: could not load issues file, starting fresh: ${err}`);
361
361
  }
362
362
  }
363
- /** Parse and populate issues from a raw JSON string. */
364
363
  hydrate(raw) {
365
- const validated = validateIssuesData(JSON.parse(raw));
364
+ let parsed;
365
+ try {
366
+ parsed = JSON.parse(raw);
367
+ } catch (err) {
368
+ brakitDebug(`IssueStore: corrupt JSON in issues file, starting fresh: ${err}`);
369
+ return;
370
+ }
371
+ const validated = validateIssuesData(parsed);
366
372
  if (!validated) return;
367
373
  for (const issue of validated.issues) {
368
374
  this.issues.set(issue.issueId, issue);
@@ -375,8 +381,12 @@ var IssueStore = class {
375
381
  }
376
382
  flushSync() {
377
383
  if (!this.dirty) return;
378
- this.writer.writeSync(this.serialize());
379
- this.dirty = false;
384
+ try {
385
+ this.writer.writeSync(this.serialize());
386
+ this.dirty = false;
387
+ } catch (err) {
388
+ brakitDebug(`IssueStore: flush failed, will retry: ${err}`);
389
+ }
380
390
  }
381
391
  serialize() {
382
392
  const data = {
@@ -391,6 +401,85 @@ var IssueStore = class {
391
401
  import { readFile as readFile3, readdir } from "fs/promises";
392
402
  import { existsSync as existsSync4 } from "fs";
393
403
  import { join as join2, relative } from "path";
404
+
405
+ // src/constants/detection.ts
406
+ var KNOWN_DEPENDENCY_NAMES = [
407
+ // -- Frameworks (meta) --
408
+ "next",
409
+ "@remix-run/dev",
410
+ "nuxt",
411
+ "astro",
412
+ // -- Frameworks (backend) --
413
+ "@nestjs/core",
414
+ "@adonisjs/core",
415
+ "sails",
416
+ "express",
417
+ "fastify",
418
+ "hono",
419
+ "koa",
420
+ "@hapi/hapi",
421
+ "elysia",
422
+ "h3",
423
+ "nitro",
424
+ "@trpc/server",
425
+ // -- Bundlers --
426
+ "vite",
427
+ // -- ORM / query builders --
428
+ "prisma",
429
+ "@prisma/client",
430
+ "drizzle-orm",
431
+ "typeorm",
432
+ "sequelize",
433
+ "mongoose",
434
+ "kysely",
435
+ "knex",
436
+ "@mikro-orm/core",
437
+ "objection",
438
+ // -- DB drivers --
439
+ "pg",
440
+ "mysql2",
441
+ "mongodb",
442
+ "better-sqlite3",
443
+ "@libsql/client",
444
+ "@planetscale/database",
445
+ "ioredis",
446
+ "redis",
447
+ // -- Auth --
448
+ "lucia",
449
+ "next-auth",
450
+ "@auth/core",
451
+ "passport",
452
+ // -- Queues / messaging --
453
+ "bullmq",
454
+ "amqplib",
455
+ "kafkajs",
456
+ // -- Validation --
457
+ "zod",
458
+ "joi",
459
+ "yup",
460
+ "arktype",
461
+ "valibot",
462
+ // -- HTTP clients --
463
+ "axios",
464
+ "got",
465
+ "ky",
466
+ "undici",
467
+ // -- Realtime --
468
+ "socket.io",
469
+ "ws",
470
+ // -- CSS / styling --
471
+ "tailwindcss",
472
+ // -- Testing --
473
+ "vitest",
474
+ "jest",
475
+ "mocha",
476
+ // -- Runtime indicators --
477
+ "bun-types",
478
+ "@types/bun"
479
+ ];
480
+ var KNOWN_DEPENDENCY_SET = new Set(KNOWN_DEPENDENCY_NAMES);
481
+
482
+ // src/detect/project.ts
394
483
  var FRAMEWORKS = [
395
484
  // Meta-frameworks first (they bundle Express/Vite internally)
396
485
  { name: "nextjs", dep: "next", devCmd: "next dev", bin: "next", defaultPort: 3e3, devArgs: ["dev", "--port"] },
@@ -419,23 +508,22 @@ async function detectProject(rootDir) {
419
508
  const devCommand = matched?.devCmd ?? "";
420
509
  const devBin = matched ? join2(rootDir, "node_modules", ".bin", matched.bin) : "";
421
510
  const defaultPort = matched?.defaultPort ?? 3e3;
422
- const packageManager = await detectPackageManager(rootDir);
511
+ const packageManager = detectPackageManager(rootDir);
423
512
  return { framework, devCommand, devBin, defaultPort, packageManager };
424
513
  }
425
- async function detectPackageManager(rootDir) {
426
- if (await fileExists(join2(rootDir, "bun.lockb"))) return "bun";
427
- if (await fileExists(join2(rootDir, "bun.lock"))) return "bun";
428
- if (await fileExists(join2(rootDir, "pnpm-lock.yaml"))) return "pnpm";
429
- if (await fileExists(join2(rootDir, "yarn.lock"))) return "yarn";
430
- if (await fileExists(join2(rootDir, "package-lock.json"))) return "npm";
431
- return "unknown";
432
- }
433
514
  function detectFrameworkFromDeps(allDeps) {
434
515
  for (const f of FRAMEWORKS) {
435
516
  if (allDeps[f.dep]) return f.name;
436
517
  }
437
518
  return "unknown";
438
519
  }
520
+ function detectPackageManager(rootDir) {
521
+ if (existsSync4(join2(rootDir, "bun.lockb")) || existsSync4(join2(rootDir, "bun.lock"))) return "bun";
522
+ if (existsSync4(join2(rootDir, "pnpm-lock.yaml"))) return "pnpm";
523
+ if (existsSync4(join2(rootDir, "yarn.lock"))) return "yarn";
524
+ if (existsSync4(join2(rootDir, "package-lock.json"))) return "npm";
525
+ return "unknown";
526
+ }
439
527
 
440
528
  // src/instrument/adapter-registry.ts
441
529
  var AdapterRegistry = class {
@@ -1145,14 +1233,14 @@ function createDefaultScanner() {
1145
1233
  // src/core/disposable.ts
1146
1234
  var SubscriptionBag = class {
1147
1235
  constructor() {
1148
- this.items = [];
1236
+ this.items = /* @__PURE__ */ new Set();
1149
1237
  }
1150
1238
  add(teardown) {
1151
- this.items.push(typeof teardown === "function" ? { dispose: teardown } : teardown);
1239
+ this.items.add(typeof teardown === "function" ? { dispose: teardown } : teardown);
1152
1240
  }
1153
1241
  dispose() {
1154
1242
  for (const d of this.items) d.dispose();
1155
- this.items.length = 0;
1243
+ this.items.clear();
1156
1244
  }
1157
1245
  };
1158
1246
 
@@ -2419,7 +2507,7 @@ var AnalysisEngine = class {
2419
2507
  };
2420
2508
 
2421
2509
  // src/index.ts
2422
- var VERSION = "0.10.1";
2510
+ var VERSION = "0.10.2";
2423
2511
  export {
2424
2512
  AdapterRegistry,
2425
2513
  AnalysisEngine,