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 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 findings via MCP and fixes your code</sub>
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`. Insights in the terminal.
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 findings, inspect endpoints, and verify fixes directly
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
- declare const FINDINGS_DATA_VERSION = 1;
164
-
165
- type FindingState = "open" | "fixing" | "resolved";
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 StatefulFinding {
169
- /** Stable ID derived from rule + endpoint + description hash */
170
- findingId: string;
171
- state: FindingState;
172
- source: FindingSource;
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 InsightContext {
200
- requests: readonly TracedRequest[];
201
- queries: readonly TracedQuery[];
202
- errors: readonly TracedError[];
203
- flows: readonly RequestFlow[];
204
- fetches: readonly TracedFetch[];
205
- previousMetrics?: readonly EndpointMetrics[];
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
- /** Consecutive recompute cycles where the insight was not detected. */
239
- consecutiveAbsences: number;
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 FindingStore {
245
- private rootDir;
246
- private findings;
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 findingsPath;
251
- constructor(rootDir: string);
213
+ private readonly issuesPath;
214
+ constructor(dataDir: string);
252
215
  start(): void;
253
216
  stop(): void;
254
- upsert(finding: SecurityFinding, source: FindingSource): StatefulFinding;
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 passive findings against the current analysis results.
219
+ * Reconcile issues against the current analysis results using evidence-based resolution.
259
220
  *
260
- * Passive findings are detected by continuous scanning (not user-triggered).
261
- * When a previously-seen finding is absent from the current results, it means
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
- reconcilePassive(currentFindings: readonly SecurityFinding[]): void;
267
- getAll(): readonly StatefulFinding[];
268
- getByState(state: FindingState): readonly StatefulFinding[];
269
- get(findingId: string): StatefulFinding | undefined;
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(ctx: SecurityContext): SecurityFinding[];
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
- statefulFindings: readonly StatefulFinding[];
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
- "findings:changed": readonly StatefulFinding[];
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 FindingStoreInterface {
392
- upsert(finding: SecurityFinding, source: FindingSource): StatefulFinding;
393
- transition(findingId: string, state: FindingState): boolean;
394
- reportFix(findingId: string, status: AiFixStatus, notes: string): boolean;
395
- reconcilePassive(findings: readonly SecurityFinding[]): void;
396
- getAll(): readonly StatefulFinding[];
397
- getByState(state: FindingState): readonly StatefulFinding[];
398
- get(findingId: string): StatefulFinding | undefined;
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
- "finding-store": FindingStoreInterface;
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 FindingSource, type FindingState, FindingStore, type FindingsData, type FlatHeaders, type Framework, type HttpMethod, type Insight, type InsightContext, type InsightRule, InsightRunner, type NormalizedOp, type RequestCategory, type RequestListener, type SecurityContext, type SecurityFinding, type SecurityRule, SecurityScanner, type SecuritySeverity, type StatefulFinding, type TracedRequest, VERSION, computeInsights, createDefaultInsightRunner, createDefaultScanner, detectProject };
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 };