brakit 0.8.0 → 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.
- package/README.md +17 -6
- package/dist/api.d.ts +165 -82
- package/dist/api.js +245 -262
- package/dist/bin/brakit.js +132 -210
- package/dist/mcp/server.js +65 -15
- package/dist/runtime/index.js +2192 -1858
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<h1 align="center"><img src="docs/images/icon.png" height="24" alt="" /> Brakit</h1>
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
|
-
<b>
|
|
5
|
-
Every request, query, and security issue
|
|
6
|
-
<b>Open source · Local first · Zero config ·
|
|
4
|
+
<b>AI writes your code. Brakit watches what it does.</b> <br />
|
|
5
|
+
Every request, query, and security issue, caught before you ship. <br />
|
|
6
|
+
<b>Open source · Local first · Zero config · AI-native via MCP</b>
|
|
7
7
|
</p>
|
|
8
8
|
|
|
9
9
|
<h3 align="center">
|
|
@@ -22,6 +22,9 @@
|
|
|
22
22
|
<a href="https://typescriptlang.org">
|
|
23
23
|
<img src="https://img.shields.io/badge/built%20with-TypeScript-3178c6.svg" alt="TypeScript" />
|
|
24
24
|
</a>
|
|
25
|
+
<a href="https://www.npmjs.com/package/brakit">
|
|
26
|
+
<img src="https://img.shields.io/npm/v/brakit" alt="npm version" />
|
|
27
|
+
</a>
|
|
25
28
|
<a href="CONTRIBUTING.md">
|
|
26
29
|
<img src="https://img.shields.io/badge/PRs-Welcome-brightgreen" alt="PRs welcome!" />
|
|
27
30
|
</a>
|
|
@@ -30,7 +33,15 @@
|
|
|
30
33
|
---
|
|
31
34
|
|
|
32
35
|
<p align="center">
|
|
33
|
-
<img width="700" src="docs/images/dashboard.png" alt="Brakit Dashboard" />
|
|
36
|
+
<img width="700" src="docs/images/dashboard.png" alt="Brakit Dashboard — issues surfaced automatically" />
|
|
37
|
+
<br />
|
|
38
|
+
<sub>Brakit catches N+1 queries, PII leaks, and slow endpoints as you develop</sub>
|
|
39
|
+
</p>
|
|
40
|
+
|
|
41
|
+
<p align="center">
|
|
42
|
+
<img width="700" src="docs/images/vscode.png" alt="Claude reading Brakit findings in VS Code" />
|
|
43
|
+
<br />
|
|
44
|
+
<sub>Claude reads findings via MCP and fixes your code</sub>
|
|
34
45
|
</p>
|
|
35
46
|
|
|
36
47
|
## Quick Start
|
|
@@ -63,7 +74,7 @@ Dashboard at `http://localhost:<port>/__brakit`. Insights in the terminal.
|
|
|
63
74
|
- **Full server tracing** — fetch calls, DB queries, console logs, errors — zero code changes
|
|
64
75
|
- **Response overfetch** — large JSON responses with many fields your client doesn't use
|
|
65
76
|
- **Performance tracking** — health grades and p95 trends across dev sessions
|
|
66
|
-
- **AI-native via MCP** — Claude
|
|
77
|
+
- **AI-native via MCP** — Claude Code and Cursor can query findings, inspect endpoints, and verify fixes directly
|
|
67
78
|
|
|
68
79
|
---
|
|
69
80
|
|
|
@@ -177,7 +188,7 @@ npm run typecheck # Type-check without emitting
|
|
|
177
188
|
npm test # Run tests with vitest
|
|
178
189
|
```
|
|
179
190
|
|
|
180
|
-
|
|
191
|
+
Minimal production dependencies. Everything else is Node.js built-ins.
|
|
181
192
|
|
|
182
193
|
### Architecture
|
|
183
194
|
|
package/dist/api.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { IncomingHttpHeaders } from 'node:http';
|
|
2
|
+
|
|
1
3
|
type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS" | (string & {});
|
|
2
4
|
type FlatHeaders = Record<string, string>;
|
|
3
5
|
interface TracedRequest {
|
|
@@ -117,10 +119,6 @@ interface EndpointMetrics {
|
|
|
117
119
|
sessions: SessionMetric[];
|
|
118
120
|
dataPoints?: LiveRequestPoint[];
|
|
119
121
|
}
|
|
120
|
-
interface MetricsData {
|
|
121
|
-
version: 1;
|
|
122
|
-
endpoints: EndpointMetrics[];
|
|
123
|
-
}
|
|
124
122
|
interface LiveRequestPoint {
|
|
125
123
|
timestamp: number;
|
|
126
124
|
durationMs: number;
|
|
@@ -149,7 +147,9 @@ interface RequestMetrics {
|
|
|
149
147
|
fetchTimeMs: number;
|
|
150
148
|
}
|
|
151
149
|
|
|
152
|
-
|
|
150
|
+
/** Shared severity levels used by both security findings and insights. */
|
|
151
|
+
type Severity = "critical" | "warning" | "info";
|
|
152
|
+
type SecuritySeverity = Severity;
|
|
153
153
|
interface SecurityFinding {
|
|
154
154
|
severity: SecuritySeverity;
|
|
155
155
|
rule: string;
|
|
@@ -178,20 +178,81 @@ interface FindingsData {
|
|
|
178
178
|
findings: StatefulFinding[];
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
+
type InsightSeverity = Severity;
|
|
182
|
+
type InsightType = "n1" | "cross-endpoint" | "redundant-query" | "error" | "error-hotspot" | "duplicate" | "slow" | "query-heavy" | "select-star" | "high-rows" | "large-response" | "response-overfetch" | "security" | "regression";
|
|
183
|
+
interface Insight {
|
|
184
|
+
severity: InsightSeverity;
|
|
185
|
+
type: InsightType;
|
|
186
|
+
title: string;
|
|
187
|
+
desc: string;
|
|
188
|
+
hint: string;
|
|
189
|
+
detail?: string;
|
|
190
|
+
nav?: string;
|
|
191
|
+
}
|
|
192
|
+
interface InsightContext {
|
|
193
|
+
requests: readonly TracedRequest[];
|
|
194
|
+
queries: readonly TracedQuery[];
|
|
195
|
+
errors: readonly TracedError[];
|
|
196
|
+
flows: readonly RequestFlow[];
|
|
197
|
+
fetches: readonly TracedFetch[];
|
|
198
|
+
previousMetrics?: readonly EndpointMetrics[];
|
|
199
|
+
securityFindings?: readonly SecurityFinding[];
|
|
200
|
+
}
|
|
201
|
+
interface EndpointGroup {
|
|
202
|
+
total: number;
|
|
203
|
+
errors: number;
|
|
204
|
+
totalDuration: number;
|
|
205
|
+
queryCount: number;
|
|
206
|
+
totalSize: number;
|
|
207
|
+
totalQueryTimeMs: number;
|
|
208
|
+
totalFetchTimeMs: number;
|
|
209
|
+
queryShapeDurations: Map<string, {
|
|
210
|
+
totalMs: number;
|
|
211
|
+
count: number;
|
|
212
|
+
label: string;
|
|
213
|
+
}>;
|
|
214
|
+
}
|
|
215
|
+
interface PreparedInsightContext extends InsightContext {
|
|
216
|
+
nonStatic: readonly TracedRequest[];
|
|
217
|
+
queriesByReq: ReadonlyMap<string, TracedQuery[]>;
|
|
218
|
+
fetchesByReq: ReadonlyMap<string, TracedFetch[]>;
|
|
219
|
+
reqById: ReadonlyMap<string, TracedRequest>;
|
|
220
|
+
endpointGroups: ReadonlyMap<string, EndpointGroup>;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
type InsightState = "open" | "resolved";
|
|
224
|
+
interface StatefulInsight {
|
|
225
|
+
key: string;
|
|
226
|
+
state: InsightState;
|
|
227
|
+
insight: Insight;
|
|
228
|
+
firstSeenAt: number;
|
|
229
|
+
lastSeenAt: number;
|
|
230
|
+
resolvedAt: number | null;
|
|
231
|
+
/** Consecutive recompute cycles where the insight was not detected. */
|
|
232
|
+
consecutiveAbsences: number;
|
|
233
|
+
}
|
|
234
|
+
|
|
181
235
|
declare class FindingStore {
|
|
182
236
|
private rootDir;
|
|
183
237
|
private findings;
|
|
184
238
|
private flushTimer;
|
|
185
239
|
private dirty;
|
|
186
|
-
private
|
|
240
|
+
private readonly writer;
|
|
187
241
|
private readonly findingsPath;
|
|
188
|
-
private readonly tmpPath;
|
|
189
|
-
private readonly metricsDir;
|
|
190
242
|
constructor(rootDir: string);
|
|
191
243
|
start(): void;
|
|
192
244
|
stop(): void;
|
|
193
245
|
upsert(finding: SecurityFinding, source: FindingSource): StatefulFinding;
|
|
194
246
|
transition(findingId: string, state: FindingState): boolean;
|
|
247
|
+
/**
|
|
248
|
+
* Reconcile passive findings against the current analysis results.
|
|
249
|
+
*
|
|
250
|
+
* Passive findings are detected by continuous scanning (not user-triggered).
|
|
251
|
+
* When a previously-seen finding is absent from the current results, it means
|
|
252
|
+
* the issue has been fixed — transition it to "resolved" automatically.
|
|
253
|
+
* Active findings (from MCP verify-fix) are not auto-resolved because they
|
|
254
|
+
* require explicit verification.
|
|
255
|
+
*/
|
|
195
256
|
reconcilePassive(currentFindings: readonly SecurityFinding[]): void;
|
|
196
257
|
getAll(): readonly StatefulFinding[];
|
|
197
258
|
getByState(state: FindingState): readonly StatefulFinding[];
|
|
@@ -200,8 +261,7 @@ declare class FindingStore {
|
|
|
200
261
|
private load;
|
|
201
262
|
private flush;
|
|
202
263
|
private flushSync;
|
|
203
|
-
private
|
|
204
|
-
private ensureDir;
|
|
264
|
+
private serialize;
|
|
205
265
|
}
|
|
206
266
|
|
|
207
267
|
interface BrakitAdapter {
|
|
@@ -231,48 +291,6 @@ declare class SecurityScanner {
|
|
|
231
291
|
}
|
|
232
292
|
declare function createDefaultScanner(): SecurityScanner;
|
|
233
293
|
|
|
234
|
-
type InsightSeverity = "critical" | "warning" | "info";
|
|
235
|
-
type InsightType = "n1" | "cross-endpoint" | "redundant-query" | "error" | "error-hotspot" | "duplicate" | "slow" | "query-heavy" | "select-star" | "high-rows" | "large-response" | "response-overfetch" | "security" | "regression";
|
|
236
|
-
interface Insight {
|
|
237
|
-
severity: InsightSeverity;
|
|
238
|
-
type: InsightType;
|
|
239
|
-
title: string;
|
|
240
|
-
desc: string;
|
|
241
|
-
hint: string;
|
|
242
|
-
detail?: string;
|
|
243
|
-
nav?: string;
|
|
244
|
-
}
|
|
245
|
-
interface InsightContext {
|
|
246
|
-
requests: readonly TracedRequest[];
|
|
247
|
-
queries: readonly TracedQuery[];
|
|
248
|
-
errors: readonly TracedError[];
|
|
249
|
-
flows: readonly RequestFlow[];
|
|
250
|
-
fetches: readonly TracedFetch[];
|
|
251
|
-
previousMetrics?: readonly EndpointMetrics[];
|
|
252
|
-
securityFindings?: readonly SecurityFinding[];
|
|
253
|
-
}
|
|
254
|
-
interface EndpointGroup {
|
|
255
|
-
total: number;
|
|
256
|
-
errors: number;
|
|
257
|
-
totalDuration: number;
|
|
258
|
-
queryCount: number;
|
|
259
|
-
totalSize: number;
|
|
260
|
-
totalQueryTimeMs: number;
|
|
261
|
-
totalFetchTimeMs: number;
|
|
262
|
-
queryShapeDurations: Map<string, {
|
|
263
|
-
totalMs: number;
|
|
264
|
-
count: number;
|
|
265
|
-
label: string;
|
|
266
|
-
}>;
|
|
267
|
-
}
|
|
268
|
-
interface PreparedInsightContext extends InsightContext {
|
|
269
|
-
nonStatic: readonly TracedRequest[];
|
|
270
|
-
queriesByReq: ReadonlyMap<string, TracedQuery[]>;
|
|
271
|
-
fetchesByReq: ReadonlyMap<string, TracedFetch[]>;
|
|
272
|
-
reqById: ReadonlyMap<string, TracedRequest>;
|
|
273
|
-
endpointGroups: ReadonlyMap<string, EndpointGroup>;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
294
|
interface InsightRule {
|
|
277
295
|
id: InsightType;
|
|
278
296
|
check(ctx: PreparedInsightContext): Insight[];
|
|
@@ -298,55 +316,120 @@ declare class AdapterRegistry {
|
|
|
298
316
|
getActive(): readonly BrakitAdapter[];
|
|
299
317
|
}
|
|
300
318
|
|
|
301
|
-
interface
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
319
|
+
interface AnalysisUpdate {
|
|
320
|
+
insights: Insight[];
|
|
321
|
+
findings: SecurityFinding[];
|
|
322
|
+
statefulFindings: readonly StatefulFinding[];
|
|
323
|
+
statefulInsights: readonly StatefulInsight[];
|
|
324
|
+
}
|
|
325
|
+
interface ChannelMap {
|
|
326
|
+
"telemetry:fetch": Omit<TracedFetch, "id">;
|
|
327
|
+
"telemetry:query": Omit<TracedQuery, "id">;
|
|
328
|
+
"telemetry:log": Omit<TracedLog, "id">;
|
|
329
|
+
"telemetry:error": Omit<TracedError, "id">;
|
|
330
|
+
"request:completed": TracedRequest;
|
|
331
|
+
"analysis:updated": AnalysisUpdate;
|
|
332
|
+
"store:cleared": void;
|
|
333
|
+
}
|
|
334
|
+
type Listener<T> = (data: T) => void;
|
|
335
|
+
declare class EventBus {
|
|
336
|
+
private listeners;
|
|
337
|
+
emit<K extends keyof ChannelMap>(channel: K, data: ChannelMap[K]): void;
|
|
338
|
+
on<K extends keyof ChannelMap>(channel: K, fn: Listener<ChannelMap[K]>): () => void;
|
|
339
|
+
off<K extends keyof ChannelMap>(channel: K, fn: Listener<ChannelMap[K]>): void;
|
|
306
340
|
}
|
|
307
341
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
342
|
+
interface CaptureInput {
|
|
343
|
+
requestId: string;
|
|
344
|
+
method: string;
|
|
345
|
+
url: string;
|
|
346
|
+
requestHeaders: IncomingHttpHeaders;
|
|
347
|
+
requestBody: Buffer | null;
|
|
348
|
+
statusCode: number;
|
|
349
|
+
responseHeaders: IncomingHttpHeaders;
|
|
350
|
+
responseBody: Buffer | null;
|
|
351
|
+
responseContentType: string;
|
|
352
|
+
startTime: number;
|
|
353
|
+
endTime?: number;
|
|
354
|
+
config: Pick<BrakitConfig, "maxBodyCapture">;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
interface TelemetryStoreInterface<T extends TelemetryEntry> {
|
|
358
|
+
add(data: Omit<T, "id">): T;
|
|
359
|
+
getAll(): readonly T[];
|
|
360
|
+
getByRequest(requestId: string): T[];
|
|
361
|
+
clear(): void;
|
|
362
|
+
}
|
|
363
|
+
interface RequestStoreInterface {
|
|
364
|
+
capture(input: CaptureInput): TracedRequest;
|
|
365
|
+
getAll(): readonly TracedRequest[];
|
|
366
|
+
clear(): void;
|
|
367
|
+
}
|
|
368
|
+
interface MetricsStoreInterface {
|
|
320
369
|
recordRequest(req: TracedRequest, metrics: RequestMetrics): void;
|
|
321
370
|
getAll(): readonly EndpointMetrics[];
|
|
322
371
|
getEndpoint(endpoint: string): EndpointMetrics | undefined;
|
|
323
372
|
getLiveEndpoints(): LiveEndpointData[];
|
|
324
373
|
reset(): void;
|
|
325
|
-
|
|
326
|
-
|
|
374
|
+
start(): void;
|
|
375
|
+
stop(): void;
|
|
376
|
+
}
|
|
377
|
+
interface FindingStoreInterface {
|
|
378
|
+
upsert(finding: SecurityFinding, source: FindingSource): StatefulFinding;
|
|
379
|
+
transition(findingId: string, state: FindingState): boolean;
|
|
380
|
+
reconcilePassive(findings: readonly SecurityFinding[]): void;
|
|
381
|
+
getAll(): readonly StatefulFinding[];
|
|
382
|
+
getByState(state: FindingState): readonly StatefulFinding[];
|
|
383
|
+
get(findingId: string): StatefulFinding | undefined;
|
|
384
|
+
clear(): void;
|
|
385
|
+
start(): void;
|
|
386
|
+
stop(): void;
|
|
387
|
+
}
|
|
388
|
+
interface AnalysisEngineInterface {
|
|
389
|
+
start(): void;
|
|
390
|
+
stop(): void;
|
|
391
|
+
recompute(): void;
|
|
392
|
+
getInsights(): readonly Insight[];
|
|
393
|
+
getFindings(): readonly SecurityFinding[];
|
|
394
|
+
getStatefulInsights(): readonly StatefulInsight[];
|
|
395
|
+
getStatefulFindings(): readonly StatefulFinding[];
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
interface ServiceMap {
|
|
399
|
+
"event-bus": EventBus;
|
|
400
|
+
"request-store": RequestStoreInterface;
|
|
401
|
+
"query-store": TelemetryStoreInterface<TracedQuery>;
|
|
402
|
+
"fetch-store": TelemetryStoreInterface<TracedFetch>;
|
|
403
|
+
"log-store": TelemetryStoreInterface<TracedLog>;
|
|
404
|
+
"error-store": TelemetryStoreInterface<TracedError>;
|
|
405
|
+
"metrics-store": MetricsStoreInterface;
|
|
406
|
+
"finding-store": FindingStoreInterface;
|
|
407
|
+
"analysis-engine": AnalysisEngineInterface;
|
|
408
|
+
}
|
|
409
|
+
declare class ServiceRegistry {
|
|
410
|
+
private services;
|
|
411
|
+
register<K extends keyof ServiceMap>(name: K, service: ServiceMap[K]): void;
|
|
412
|
+
get<K extends keyof ServiceMap>(name: K): ServiceMap[K];
|
|
413
|
+
has<K extends keyof ServiceMap>(name: K): boolean;
|
|
327
414
|
}
|
|
328
415
|
|
|
329
|
-
type AnalysisListener = (insights: Insight[], findings: SecurityFinding[]) => void;
|
|
330
416
|
declare class AnalysisEngine {
|
|
331
|
-
private
|
|
332
|
-
private findingStore?;
|
|
417
|
+
private registry;
|
|
333
418
|
private debounceMs;
|
|
334
419
|
private scanner;
|
|
420
|
+
private insightTracker;
|
|
335
421
|
private cachedInsights;
|
|
336
422
|
private cachedFindings;
|
|
423
|
+
private cachedStatefulInsights;
|
|
337
424
|
private debounceTimer;
|
|
338
|
-
private
|
|
339
|
-
|
|
340
|
-
private boundQueryListener;
|
|
341
|
-
private boundErrorListener;
|
|
342
|
-
private boundLogListener;
|
|
343
|
-
constructor(metricsStore: MetricsStore, findingStore?: FindingStore | undefined, debounceMs?: number);
|
|
425
|
+
private subs;
|
|
426
|
+
constructor(registry: ServiceRegistry, debounceMs?: number);
|
|
344
427
|
start(): void;
|
|
345
428
|
stop(): void;
|
|
346
|
-
onUpdate(fn: AnalysisListener): void;
|
|
347
|
-
offUpdate(fn: AnalysisListener): void;
|
|
348
429
|
getInsights(): readonly Insight[];
|
|
349
430
|
getFindings(): readonly SecurityFinding[];
|
|
431
|
+
getStatefulFindings(): readonly StatefulFinding[];
|
|
432
|
+
getStatefulInsights(): readonly StatefulInsight[];
|
|
350
433
|
private scheduleRecompute;
|
|
351
434
|
recompute(): void;
|
|
352
435
|
}
|