brakit 0.8.5 → 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 +120 -115
- package/dist/api.js +371 -340
- package/dist/bin/brakit.js +457 -332
- package/dist/dashboard.html +60 -59
- package/dist/mcp/server.js +75 -90
- package/dist/runtime/index.js +637 -529
- package/package.json +1 -1
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
|
@@ -160,117 +160,81 @@ interface SecurityFinding {
|
|
|
160
160
|
count: number;
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
type
|
|
166
|
-
type FindingSource = "passive";
|
|
163
|
+
type IssueState = "open" | "fixing" | "resolved" | "stale" | "regressed";
|
|
164
|
+
type IssueSource = "passive";
|
|
165
|
+
type IssueCategory = "security" | "performance" | "reliability";
|
|
167
166
|
type AiFixStatus = "fixed" | "wont_fix";
|
|
168
|
-
interface
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
finding: SecurityFinding;
|
|
174
|
-
firstSeenAt: number;
|
|
175
|
-
lastSeenAt: number;
|
|
176
|
-
resolvedAt: number | null;
|
|
177
|
-
occurrences: number;
|
|
178
|
-
/** What AI reported after attempting a fix */
|
|
179
|
-
aiStatus: AiFixStatus | null;
|
|
180
|
-
/** AI's summary of what was done or why it can't be fixed */
|
|
181
|
-
aiNotes: string | null;
|
|
182
|
-
}
|
|
183
|
-
interface FindingsData {
|
|
184
|
-
version: typeof FINDINGS_DATA_VERSION;
|
|
185
|
-
findings: StatefulFinding[];
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
type InsightSeverity = Severity;
|
|
189
|
-
type InsightType = "n1" | "cross-endpoint" | "redundant-query" | "error" | "error-hotspot" | "duplicate" | "slow" | "query-heavy" | "select-star" | "high-rows" | "large-response" | "response-overfetch" | "security" | "regression";
|
|
190
|
-
interface Insight {
|
|
191
|
-
severity: InsightSeverity;
|
|
192
|
-
type: InsightType;
|
|
167
|
+
interface Issue {
|
|
168
|
+
category: IssueCategory;
|
|
169
|
+
/** Rule identifier: "slow", "n1", "exposed-secret", etc. */
|
|
170
|
+
rule: string;
|
|
171
|
+
severity: Severity;
|
|
193
172
|
title: string;
|
|
194
173
|
desc: string;
|
|
195
174
|
hint: string;
|
|
196
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"). */
|
|
197
179
|
nav?: string;
|
|
198
180
|
}
|
|
199
|
-
interface
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
securityFindings?: readonly SecurityFinding[];
|
|
207
|
-
}
|
|
208
|
-
interface EndpointGroup {
|
|
209
|
-
total: number;
|
|
210
|
-
errors: number;
|
|
211
|
-
totalDuration: number;
|
|
212
|
-
queryCount: number;
|
|
213
|
-
totalSize: number;
|
|
214
|
-
totalQueryTimeMs: number;
|
|
215
|
-
totalFetchTimeMs: number;
|
|
216
|
-
queryShapeDurations: Map<string, {
|
|
217
|
-
totalMs: number;
|
|
218
|
-
count: number;
|
|
219
|
-
label: string;
|
|
220
|
-
}>;
|
|
221
|
-
}
|
|
222
|
-
interface PreparedInsightContext extends InsightContext {
|
|
223
|
-
nonStatic: readonly TracedRequest[];
|
|
224
|
-
queriesByReq: ReadonlyMap<string, TracedQuery[]>;
|
|
225
|
-
fetchesByReq: ReadonlyMap<string, TracedFetch[]>;
|
|
226
|
-
reqById: ReadonlyMap<string, TracedRequest>;
|
|
227
|
-
endpointGroups: ReadonlyMap<string, EndpointGroup>;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
type InsightState = FindingState;
|
|
231
|
-
interface StatefulInsight {
|
|
232
|
-
key: string;
|
|
233
|
-
state: InsightState;
|
|
234
|
-
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;
|
|
235
188
|
firstSeenAt: number;
|
|
236
189
|
lastSeenAt: number;
|
|
237
190
|
resolvedAt: number | null;
|
|
238
|
-
|
|
239
|
-
|
|
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. */
|
|
240
198
|
aiStatus: AiFixStatus | null;
|
|
199
|
+
/** AI's summary of what was done or why it can't be fixed. */
|
|
241
200
|
aiNotes: string | null;
|
|
242
201
|
}
|
|
202
|
+
interface IssuesData {
|
|
203
|
+
version: 2;
|
|
204
|
+
issues: StatefulIssue[];
|
|
205
|
+
}
|
|
243
206
|
|
|
244
|
-
declare class
|
|
245
|
-
private
|
|
246
|
-
private
|
|
207
|
+
declare class IssueStore {
|
|
208
|
+
private dataDir;
|
|
209
|
+
private issues;
|
|
247
210
|
private flushTimer;
|
|
248
211
|
private dirty;
|
|
249
212
|
private readonly writer;
|
|
250
|
-
private readonly
|
|
251
|
-
constructor(
|
|
213
|
+
private readonly issuesPath;
|
|
214
|
+
constructor(dataDir: string);
|
|
252
215
|
start(): void;
|
|
253
216
|
stop(): void;
|
|
254
|
-
upsert(
|
|
255
|
-
transition(findingId: string, state: FindingState): boolean;
|
|
256
|
-
reportFix(findingId: string, status: AiFixStatus, notes: string): boolean;
|
|
217
|
+
upsert(issue: Issue, source: IssueSource): StatefulIssue;
|
|
257
218
|
/**
|
|
258
|
-
* Reconcile
|
|
219
|
+
* Reconcile issues against the current analysis results using evidence-based resolution.
|
|
259
220
|
*
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
* the issue has been fixed — transition it to "resolved" automatically.
|
|
263
|
-
* Active findings (from MCP verify-fix) are not auto-resolved because they
|
|
264
|
-
* 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
|
|
265
223
|
*/
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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;
|
|
270
231
|
clear(): void;
|
|
232
|
+
isDirty(): boolean;
|
|
271
233
|
private loadAsync;
|
|
272
234
|
/** Sync load for tests only — not used in production paths. */
|
|
273
235
|
loadSync(): void;
|
|
236
|
+
/** Parse and populate issues from a raw JSON string. */
|
|
237
|
+
private hydrate;
|
|
274
238
|
private flush;
|
|
275
239
|
private flushSync;
|
|
276
240
|
private serialize;
|
|
@@ -283,9 +247,15 @@ interface BrakitAdapter {
|
|
|
283
247
|
unpatch?(): void;
|
|
284
248
|
}
|
|
285
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
|
+
}
|
|
286
255
|
interface SecurityContext {
|
|
287
256
|
requests: readonly TracedRequest[];
|
|
288
257
|
logs: readonly TracedLog[];
|
|
258
|
+
parsedBodies: ParsedBodyCache;
|
|
289
259
|
}
|
|
290
260
|
interface SecurityRule {
|
|
291
261
|
id: string;
|
|
@@ -298,11 +268,56 @@ interface SecurityRule {
|
|
|
298
268
|
declare class SecurityScanner {
|
|
299
269
|
private rules;
|
|
300
270
|
register(rule: SecurityRule): void;
|
|
301
|
-
scan(
|
|
271
|
+
scan(input: {
|
|
272
|
+
requests: readonly TracedRequest[];
|
|
273
|
+
logs: readonly TracedLog[];
|
|
274
|
+
}): SecurityFinding[];
|
|
302
275
|
getRules(): readonly SecurityRule[];
|
|
303
276
|
}
|
|
304
277
|
declare function createDefaultScanner(): SecurityScanner;
|
|
305
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
|
+
|
|
306
321
|
interface InsightRule {
|
|
307
322
|
id: InsightType;
|
|
308
323
|
check(ctx: PreparedInsightContext): Insight[];
|
|
@@ -331,8 +346,7 @@ declare class AdapterRegistry {
|
|
|
331
346
|
interface AnalysisUpdate {
|
|
332
347
|
insights: readonly Insight[];
|
|
333
348
|
findings: readonly SecurityFinding[];
|
|
334
|
-
|
|
335
|
-
statefulInsights: readonly StatefulInsight[];
|
|
349
|
+
issues: readonly StatefulIssue[];
|
|
336
350
|
}
|
|
337
351
|
interface ChannelMap {
|
|
338
352
|
"telemetry:fetch": Omit<TracedFetch, "id">;
|
|
@@ -341,7 +355,7 @@ interface ChannelMap {
|
|
|
341
355
|
"telemetry:error": Omit<TracedError, "id">;
|
|
342
356
|
"request:completed": TracedRequest;
|
|
343
357
|
"analysis:updated": AnalysisUpdate;
|
|
344
|
-
"
|
|
358
|
+
"issues:changed": readonly StatefulIssue[];
|
|
345
359
|
"store:cleared": undefined;
|
|
346
360
|
}
|
|
347
361
|
type Listener<T> = (data: T) => void;
|
|
@@ -367,6 +381,10 @@ interface CaptureInput {
|
|
|
367
381
|
config: Pick<BrakitConfig, "maxBodyCapture">;
|
|
368
382
|
}
|
|
369
383
|
|
|
384
|
+
interface Lifecycle {
|
|
385
|
+
start(): void;
|
|
386
|
+
stop(): void;
|
|
387
|
+
}
|
|
370
388
|
interface TelemetryStoreInterface<T extends TelemetryEntry> {
|
|
371
389
|
add(data: Omit<T, "id">): T;
|
|
372
390
|
getAll(): readonly T[];
|
|
@@ -379,36 +397,28 @@ interface RequestStoreInterface {
|
|
|
379
397
|
getAll(): readonly TracedRequest[];
|
|
380
398
|
clear(): void;
|
|
381
399
|
}
|
|
382
|
-
interface MetricsStoreInterface {
|
|
400
|
+
interface MetricsStoreInterface extends Lifecycle {
|
|
383
401
|
recordRequest(req: TracedRequest, metrics: RequestMetrics): void;
|
|
384
402
|
getAll(): readonly EndpointMetrics[];
|
|
385
403
|
getEndpoint(endpoint: string): EndpointMetrics | undefined;
|
|
386
404
|
getLiveEndpoints(): LiveEndpointData[];
|
|
387
405
|
reset(): void;
|
|
388
|
-
start(): void;
|
|
389
|
-
stop(): void;
|
|
390
406
|
}
|
|
391
|
-
interface
|
|
392
|
-
upsert(
|
|
393
|
-
transition(
|
|
394
|
-
reportFix(
|
|
395
|
-
|
|
396
|
-
getAll(): readonly
|
|
397
|
-
getByState(state:
|
|
398
|
-
|
|
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;
|
|
399
416
|
clear(): void;
|
|
400
|
-
start(): void;
|
|
401
|
-
stop(): void;
|
|
402
417
|
}
|
|
403
|
-
interface AnalysisEngineInterface {
|
|
404
|
-
start(): void;
|
|
405
|
-
stop(): void;
|
|
418
|
+
interface AnalysisEngineInterface extends Lifecycle {
|
|
406
419
|
recompute(): void;
|
|
407
420
|
getInsights(): readonly Insight[];
|
|
408
421
|
getFindings(): readonly SecurityFinding[];
|
|
409
|
-
getStatefulInsights(): readonly StatefulInsight[];
|
|
410
|
-
getStatefulFindings(): readonly StatefulFinding[];
|
|
411
|
-
reportInsightFix(enrichedId: string, status: AiFixStatus, notes: string): boolean;
|
|
412
422
|
}
|
|
413
423
|
|
|
414
424
|
interface ServiceMap {
|
|
@@ -419,7 +429,7 @@ interface ServiceMap {
|
|
|
419
429
|
"log-store": TelemetryStoreInterface<TracedLog>;
|
|
420
430
|
"error-store": TelemetryStoreInterface<TracedError>;
|
|
421
431
|
"metrics-store": MetricsStoreInterface;
|
|
422
|
-
"
|
|
432
|
+
"issue-store": IssueStoreInterface;
|
|
423
433
|
"analysis-engine": AnalysisEngineInterface;
|
|
424
434
|
}
|
|
425
435
|
declare class ServiceRegistry {
|
|
@@ -433,10 +443,8 @@ declare class AnalysisEngine {
|
|
|
433
443
|
private registry;
|
|
434
444
|
private debounceMs;
|
|
435
445
|
private scanner;
|
|
436
|
-
private insightTracker;
|
|
437
446
|
private cachedInsights;
|
|
438
447
|
private cachedFindings;
|
|
439
|
-
private cachedStatefulInsights;
|
|
440
448
|
private debounceTimer;
|
|
441
449
|
private subs;
|
|
442
450
|
constructor(registry: ServiceRegistry, debounceMs?: number);
|
|
@@ -444,13 +452,10 @@ declare class AnalysisEngine {
|
|
|
444
452
|
stop(): void;
|
|
445
453
|
getInsights(): readonly Insight[];
|
|
446
454
|
getFindings(): readonly SecurityFinding[];
|
|
447
|
-
getStatefulFindings(): readonly StatefulFinding[];
|
|
448
|
-
getStatefulInsights(): readonly StatefulInsight[];
|
|
449
|
-
reportInsightFix(enrichedId: string, status: AiFixStatus, notes: string): boolean;
|
|
450
455
|
private scheduleRecompute;
|
|
451
456
|
recompute(): void;
|
|
452
457
|
}
|
|
453
458
|
|
|
454
459
|
declare const VERSION: string;
|
|
455
460
|
|
|
456
|
-
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 };
|