brakit 0.8.4 → 0.8.6
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 +3 -3
- package/dist/api.d.ts +133 -111
- package/dist/api.js +468 -327
- package/dist/bin/brakit.js +864 -448
- package/dist/dashboard.html +2653 -0
- package/dist/mcp/server.js +248 -158
- package/dist/runtime/index.js +1357 -783
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
<p align="center">
|
|
42
42
|
<img width="700" src="docs/images/vscode.png" alt="Claude reading Brakit findings in VS Code" />
|
|
43
43
|
<br />
|
|
44
|
-
<sub>Claude reads
|
|
44
|
+
<sub>Claude reads issues via MCP and fixes your code</sub>
|
|
45
45
|
</p>
|
|
46
46
|
|
|
47
47
|
## Quick Start
|
|
@@ -56,7 +56,7 @@ That's it. Brakit detects your framework, adds itself as a devDependency, and cr
|
|
|
56
56
|
npm run dev
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
Dashboard at `http://localhost:<port>/__brakit`.
|
|
59
|
+
Dashboard at `http://localhost:<port>/__brakit`. Issues in the terminal.
|
|
60
60
|
|
|
61
61
|
> **Requirements:** Node.js >= 18 and a project with `package.json`.
|
|
62
62
|
|
|
@@ -74,7 +74,7 @@ Dashboard at `http://localhost:<port>/__brakit`. Insights in the terminal.
|
|
|
74
74
|
- **Full server tracing** — fetch calls, DB queries, console logs, errors — zero code changes
|
|
75
75
|
- **Response overfetch** — large JSON responses with many fields your client doesn't use
|
|
76
76
|
- **Performance tracking** — health grades and p95 trends across dev sessions
|
|
77
|
-
- **AI-native via MCP** — Claude Code and Cursor can query
|
|
77
|
+
- **AI-native via MCP** — Claude Code and Cursor can query issues, inspect endpoints, and verify fixes directly
|
|
78
78
|
|
|
79
79
|
---
|
|
80
80
|
|
package/dist/api.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { IncomingHttpHeaders } from 'node:http';
|
|
2
2
|
|
|
3
|
-
type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS"
|
|
3
|
+
type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS";
|
|
4
4
|
type FlatHeaders = Record<string, string>;
|
|
5
5
|
interface TracedRequest {
|
|
6
6
|
id: string;
|
|
@@ -19,7 +19,7 @@ interface TracedRequest {
|
|
|
19
19
|
}
|
|
20
20
|
type RequestListener = (req: TracedRequest) => void;
|
|
21
21
|
|
|
22
|
-
type Framework = "nextjs" | "remix" | "nuxt" | "vite" | "astro" | "custom" | "unknown";
|
|
22
|
+
type Framework = "nextjs" | "remix" | "nuxt" | "vite" | "astro" | "flask" | "fastapi" | "django" | "custom" | "unknown";
|
|
23
23
|
interface DetectedProject {
|
|
24
24
|
framework: Framework;
|
|
25
25
|
devCommand: string;
|
|
@@ -79,7 +79,7 @@ interface TracedError extends TelemetryEntry {
|
|
|
79
79
|
}
|
|
80
80
|
type NormalizedOp = "SELECT" | "INSERT" | "UPDATE" | "DELETE" | "OTHER";
|
|
81
81
|
interface TracedQuery extends TelemetryEntry {
|
|
82
|
-
driver: "pg" | "mysql2" | "prisma" |
|
|
82
|
+
driver: "pg" | "mysql2" | "prisma" | "sdk";
|
|
83
83
|
sql?: string;
|
|
84
84
|
model?: string;
|
|
85
85
|
operation?: string;
|
|
@@ -160,105 +160,81 @@ interface SecurityFinding {
|
|
|
160
160
|
count: number;
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
-
type
|
|
164
|
-
type
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
lastSeenAt: number;
|
|
173
|
-
resolvedAt: number | null;
|
|
174
|
-
occurrences: number;
|
|
175
|
-
}
|
|
176
|
-
interface FindingsData {
|
|
177
|
-
version: 1;
|
|
178
|
-
findings: StatefulFinding[];
|
|
179
|
-
}
|
|
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;
|
|
163
|
+
type IssueState = "open" | "fixing" | "resolved" | "stale" | "regressed";
|
|
164
|
+
type IssueSource = "passive";
|
|
165
|
+
type IssueCategory = "security" | "performance" | "reliability";
|
|
166
|
+
type AiFixStatus = "fixed" | "wont_fix";
|
|
167
|
+
interface Issue {
|
|
168
|
+
category: IssueCategory;
|
|
169
|
+
/** Rule identifier: "slow", "n1", "exposed-secret", etc. */
|
|
170
|
+
rule: string;
|
|
171
|
+
severity: Severity;
|
|
186
172
|
title: string;
|
|
187
173
|
desc: string;
|
|
188
174
|
hint: string;
|
|
189
175
|
detail?: string;
|
|
176
|
+
/** Explicit endpoint key (e.g., "GET /api/users"). */
|
|
177
|
+
endpoint?: string;
|
|
178
|
+
/** Dashboard tab to link to (e.g., "requests", "queries", "security"). */
|
|
190
179
|
nav?: string;
|
|
191
180
|
}
|
|
192
|
-
interface
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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;
|
|
181
|
+
interface StatefulIssue {
|
|
182
|
+
/** Stable ID derived from rule + endpoint + description hash. */
|
|
183
|
+
issueId: string;
|
|
184
|
+
state: IssueState;
|
|
185
|
+
source: IssueSource;
|
|
186
|
+
category: IssueCategory;
|
|
187
|
+
issue: Issue;
|
|
228
188
|
firstSeenAt: number;
|
|
229
189
|
lastSeenAt: number;
|
|
230
190
|
resolvedAt: number | null;
|
|
231
|
-
|
|
232
|
-
|
|
191
|
+
occurrences: number;
|
|
192
|
+
/**
|
|
193
|
+
* Number of requests to this endpoint that did NOT reproduce the issue
|
|
194
|
+
* since it was last seen. Used for evidence-based resolution.
|
|
195
|
+
*/
|
|
196
|
+
cleanHitsSinceLastSeen: number;
|
|
197
|
+
/** What AI reported after attempting a fix. */
|
|
198
|
+
aiStatus: AiFixStatus | null;
|
|
199
|
+
/** AI's summary of what was done or why it can't be fixed. */
|
|
200
|
+
aiNotes: string | null;
|
|
201
|
+
}
|
|
202
|
+
interface IssuesData {
|
|
203
|
+
version: 2;
|
|
204
|
+
issues: StatefulIssue[];
|
|
233
205
|
}
|
|
234
206
|
|
|
235
|
-
declare class
|
|
236
|
-
private
|
|
237
|
-
private
|
|
207
|
+
declare class IssueStore {
|
|
208
|
+
private dataDir;
|
|
209
|
+
private issues;
|
|
238
210
|
private flushTimer;
|
|
239
211
|
private dirty;
|
|
240
212
|
private readonly writer;
|
|
241
|
-
private readonly
|
|
242
|
-
constructor(
|
|
213
|
+
private readonly issuesPath;
|
|
214
|
+
constructor(dataDir: string);
|
|
243
215
|
start(): void;
|
|
244
216
|
stop(): void;
|
|
245
|
-
upsert(
|
|
246
|
-
transition(findingId: string, state: FindingState): boolean;
|
|
217
|
+
upsert(issue: Issue, source: IssueSource): StatefulIssue;
|
|
247
218
|
/**
|
|
248
|
-
* Reconcile
|
|
219
|
+
* Reconcile issues against the current analysis results using evidence-based resolution.
|
|
249
220
|
*
|
|
250
|
-
*
|
|
251
|
-
*
|
|
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.
|
|
221
|
+
* @param currentIssueIds - IDs of issues detected in the current analysis cycle
|
|
222
|
+
* @param activeEndpoints - Endpoints that had requests in the current cycle
|
|
255
223
|
*/
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
224
|
+
reconcile(currentIssueIds: Set<string>, activeEndpoints: Set<string>): void;
|
|
225
|
+
transition(issueId: string, state: IssueState): boolean;
|
|
226
|
+
reportFix(issueId: string, status: AiFixStatus, notes: string): boolean;
|
|
227
|
+
getAll(): readonly StatefulIssue[];
|
|
228
|
+
getByState(state: IssueState): readonly StatefulIssue[];
|
|
229
|
+
getByCategory(category: IssueCategory): readonly StatefulIssue[];
|
|
230
|
+
get(issueId: string): StatefulIssue | undefined;
|
|
260
231
|
clear(): void;
|
|
261
|
-
|
|
232
|
+
isDirty(): boolean;
|
|
233
|
+
private loadAsync;
|
|
234
|
+
/** Sync load for tests only — not used in production paths. */
|
|
235
|
+
loadSync(): void;
|
|
236
|
+
/** Parse and populate issues from a raw JSON string. */
|
|
237
|
+
private hydrate;
|
|
262
238
|
private flush;
|
|
263
239
|
private flushSync;
|
|
264
240
|
private serialize;
|
|
@@ -271,9 +247,15 @@ interface BrakitAdapter {
|
|
|
271
247
|
unpatch?(): void;
|
|
272
248
|
}
|
|
273
249
|
|
|
250
|
+
/** Pre-parsed JSON bodies keyed by request ID, built once per scan cycle. */
|
|
251
|
+
interface ParsedBodyCache {
|
|
252
|
+
response: Map<string, unknown>;
|
|
253
|
+
request: Map<string, unknown>;
|
|
254
|
+
}
|
|
274
255
|
interface SecurityContext {
|
|
275
256
|
requests: readonly TracedRequest[];
|
|
276
257
|
logs: readonly TracedLog[];
|
|
258
|
+
parsedBodies: ParsedBodyCache;
|
|
277
259
|
}
|
|
278
260
|
interface SecurityRule {
|
|
279
261
|
id: string;
|
|
@@ -286,11 +268,56 @@ interface SecurityRule {
|
|
|
286
268
|
declare class SecurityScanner {
|
|
287
269
|
private rules;
|
|
288
270
|
register(rule: SecurityRule): void;
|
|
289
|
-
scan(
|
|
271
|
+
scan(input: {
|
|
272
|
+
requests: readonly TracedRequest[];
|
|
273
|
+
logs: readonly TracedLog[];
|
|
274
|
+
}): SecurityFinding[];
|
|
290
275
|
getRules(): readonly SecurityRule[];
|
|
291
276
|
}
|
|
292
277
|
declare function createDefaultScanner(): SecurityScanner;
|
|
293
278
|
|
|
279
|
+
type InsightSeverity = Severity;
|
|
280
|
+
type InsightType = "n1" | "cross-endpoint" | "redundant-query" | "error" | "error-hotspot" | "duplicate" | "slow" | "query-heavy" | "select-star" | "high-rows" | "large-response" | "response-overfetch" | "security" | "regression";
|
|
281
|
+
interface Insight {
|
|
282
|
+
severity: InsightSeverity;
|
|
283
|
+
type: InsightType;
|
|
284
|
+
title: string;
|
|
285
|
+
desc: string;
|
|
286
|
+
hint: string;
|
|
287
|
+
detail?: string;
|
|
288
|
+
nav?: string;
|
|
289
|
+
}
|
|
290
|
+
interface InsightContext {
|
|
291
|
+
requests: readonly TracedRequest[];
|
|
292
|
+
queries: readonly TracedQuery[];
|
|
293
|
+
errors: readonly TracedError[];
|
|
294
|
+
flows: readonly RequestFlow[];
|
|
295
|
+
fetches: readonly TracedFetch[];
|
|
296
|
+
previousMetrics?: readonly EndpointMetrics[];
|
|
297
|
+
securityFindings?: readonly SecurityFinding[];
|
|
298
|
+
}
|
|
299
|
+
interface EndpointGroup {
|
|
300
|
+
total: number;
|
|
301
|
+
errors: number;
|
|
302
|
+
totalDuration: number;
|
|
303
|
+
queryCount: number;
|
|
304
|
+
totalSize: number;
|
|
305
|
+
totalQueryTimeMs: number;
|
|
306
|
+
totalFetchTimeMs: number;
|
|
307
|
+
queryShapeDurations: Map<string, {
|
|
308
|
+
totalMs: number;
|
|
309
|
+
count: number;
|
|
310
|
+
label: string;
|
|
311
|
+
}>;
|
|
312
|
+
}
|
|
313
|
+
interface PreparedInsightContext extends InsightContext {
|
|
314
|
+
nonStatic: readonly TracedRequest[];
|
|
315
|
+
queriesByReq: ReadonlyMap<string, TracedQuery[]>;
|
|
316
|
+
fetchesByReq: ReadonlyMap<string, TracedFetch[]>;
|
|
317
|
+
reqById: ReadonlyMap<string, TracedRequest>;
|
|
318
|
+
endpointGroups: ReadonlyMap<string, EndpointGroup>;
|
|
319
|
+
}
|
|
320
|
+
|
|
294
321
|
interface InsightRule {
|
|
295
322
|
id: InsightType;
|
|
296
323
|
check(ctx: PreparedInsightContext): Insight[];
|
|
@@ -317,10 +344,9 @@ declare class AdapterRegistry {
|
|
|
317
344
|
}
|
|
318
345
|
|
|
319
346
|
interface AnalysisUpdate {
|
|
320
|
-
insights: Insight[];
|
|
321
|
-
findings: SecurityFinding[];
|
|
322
|
-
|
|
323
|
-
statefulInsights: readonly StatefulInsight[];
|
|
347
|
+
insights: readonly Insight[];
|
|
348
|
+
findings: readonly SecurityFinding[];
|
|
349
|
+
issues: readonly StatefulIssue[];
|
|
324
350
|
}
|
|
325
351
|
interface ChannelMap {
|
|
326
352
|
"telemetry:fetch": Omit<TracedFetch, "id">;
|
|
@@ -329,7 +355,8 @@ interface ChannelMap {
|
|
|
329
355
|
"telemetry:error": Omit<TracedError, "id">;
|
|
330
356
|
"request:completed": TracedRequest;
|
|
331
357
|
"analysis:updated": AnalysisUpdate;
|
|
332
|
-
"
|
|
358
|
+
"issues:changed": readonly StatefulIssue[];
|
|
359
|
+
"store:cleared": undefined;
|
|
333
360
|
}
|
|
334
361
|
type Listener<T> = (data: T) => void;
|
|
335
362
|
declare class EventBus {
|
|
@@ -354,6 +381,10 @@ interface CaptureInput {
|
|
|
354
381
|
config: Pick<BrakitConfig, "maxBodyCapture">;
|
|
355
382
|
}
|
|
356
383
|
|
|
384
|
+
interface Lifecycle {
|
|
385
|
+
start(): void;
|
|
386
|
+
stop(): void;
|
|
387
|
+
}
|
|
357
388
|
interface TelemetryStoreInterface<T extends TelemetryEntry> {
|
|
358
389
|
add(data: Omit<T, "id">): T;
|
|
359
390
|
getAll(): readonly T[];
|
|
@@ -362,37 +393,32 @@ interface TelemetryStoreInterface<T extends TelemetryEntry> {
|
|
|
362
393
|
}
|
|
363
394
|
interface RequestStoreInterface {
|
|
364
395
|
capture(input: CaptureInput): TracedRequest;
|
|
396
|
+
add(entry: TracedRequest): void;
|
|
365
397
|
getAll(): readonly TracedRequest[];
|
|
366
398
|
clear(): void;
|
|
367
399
|
}
|
|
368
|
-
interface MetricsStoreInterface {
|
|
400
|
+
interface MetricsStoreInterface extends Lifecycle {
|
|
369
401
|
recordRequest(req: TracedRequest, metrics: RequestMetrics): void;
|
|
370
402
|
getAll(): readonly EndpointMetrics[];
|
|
371
403
|
getEndpoint(endpoint: string): EndpointMetrics | undefined;
|
|
372
404
|
getLiveEndpoints(): LiveEndpointData[];
|
|
373
405
|
reset(): void;
|
|
374
|
-
start(): void;
|
|
375
|
-
stop(): void;
|
|
376
406
|
}
|
|
377
|
-
interface
|
|
378
|
-
upsert(
|
|
379
|
-
transition(
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
407
|
+
interface IssueStoreInterface extends Lifecycle {
|
|
408
|
+
upsert(issue: Issue, source: IssueSource): StatefulIssue;
|
|
409
|
+
transition(issueId: string, state: IssueState): boolean;
|
|
410
|
+
reportFix(issueId: string, status: AiFixStatus, notes: string): boolean;
|
|
411
|
+
reconcile(currentIssueIds: Set<string>, activeEndpoints: Set<string>): void;
|
|
412
|
+
getAll(): readonly StatefulIssue[];
|
|
413
|
+
getByState(state: IssueState): readonly StatefulIssue[];
|
|
414
|
+
getByCategory(category: IssueCategory): readonly StatefulIssue[];
|
|
415
|
+
get(issueId: string): StatefulIssue | undefined;
|
|
384
416
|
clear(): void;
|
|
385
|
-
start(): void;
|
|
386
|
-
stop(): void;
|
|
387
417
|
}
|
|
388
|
-
interface AnalysisEngineInterface {
|
|
389
|
-
start(): void;
|
|
390
|
-
stop(): void;
|
|
418
|
+
interface AnalysisEngineInterface extends Lifecycle {
|
|
391
419
|
recompute(): void;
|
|
392
420
|
getInsights(): readonly Insight[];
|
|
393
421
|
getFindings(): readonly SecurityFinding[];
|
|
394
|
-
getStatefulInsights(): readonly StatefulInsight[];
|
|
395
|
-
getStatefulFindings(): readonly StatefulFinding[];
|
|
396
422
|
}
|
|
397
423
|
|
|
398
424
|
interface ServiceMap {
|
|
@@ -403,7 +429,7 @@ interface ServiceMap {
|
|
|
403
429
|
"log-store": TelemetryStoreInterface<TracedLog>;
|
|
404
430
|
"error-store": TelemetryStoreInterface<TracedError>;
|
|
405
431
|
"metrics-store": MetricsStoreInterface;
|
|
406
|
-
"
|
|
432
|
+
"issue-store": IssueStoreInterface;
|
|
407
433
|
"analysis-engine": AnalysisEngineInterface;
|
|
408
434
|
}
|
|
409
435
|
declare class ServiceRegistry {
|
|
@@ -417,10 +443,8 @@ declare class AnalysisEngine {
|
|
|
417
443
|
private registry;
|
|
418
444
|
private debounceMs;
|
|
419
445
|
private scanner;
|
|
420
|
-
private insightTracker;
|
|
421
446
|
private cachedInsights;
|
|
422
447
|
private cachedFindings;
|
|
423
|
-
private cachedStatefulInsights;
|
|
424
448
|
private debounceTimer;
|
|
425
449
|
private subs;
|
|
426
450
|
constructor(registry: ServiceRegistry, debounceMs?: number);
|
|
@@ -428,12 +452,10 @@ declare class AnalysisEngine {
|
|
|
428
452
|
stop(): void;
|
|
429
453
|
getInsights(): readonly Insight[];
|
|
430
454
|
getFindings(): readonly SecurityFinding[];
|
|
431
|
-
getStatefulFindings(): readonly StatefulFinding[];
|
|
432
|
-
getStatefulInsights(): readonly StatefulInsight[];
|
|
433
455
|
private scheduleRecompute;
|
|
434
456
|
recompute(): void;
|
|
435
457
|
}
|
|
436
458
|
|
|
437
459
|
declare const VERSION: string;
|
|
438
460
|
|
|
439
|
-
export { AdapterRegistry, AnalysisEngine, type BrakitAdapter, type BrakitConfig, type DetectedProject, type
|
|
461
|
+
export { AdapterRegistry, AnalysisEngine, type BrakitAdapter, type BrakitConfig, type DetectedProject, type FlatHeaders, type Framework, type HttpMethod, type Insight, type InsightContext, type InsightRule, InsightRunner, type IssueCategory, type IssueSource, type IssueState, IssueStore, type IssuesData, type NormalizedOp, type RequestCategory, type RequestListener, type SecurityContext, type SecurityFinding, type SecurityRule, SecurityScanner, type SecuritySeverity, type StatefulIssue, type TracedRequest, VERSION, computeInsights, createDefaultInsightRunner, createDefaultScanner, detectProject };
|