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 +41 -14
- package/dist/api.js +105 -17
- package/dist/bin/brakit.js +307 -37
- package/dist/dashboard-client.global.js +204 -191
- package/dist/dashboard.html +216 -192
- package/dist/mcp/server.js +78 -1
- package/dist/runtime/index.js +1330 -1122
- package/package.json +1 -1
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:
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
379
|
-
|
|
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 =
|
|
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.
|
|
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.
|
|
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.
|
|
2510
|
+
var VERSION = "0.10.2";
|
|
2423
2511
|
export {
|
|
2424
2512
|
AdapterRegistry,
|
|
2425
2513
|
AnalysisEngine,
|