brakit 0.10.0 → 0.10.1

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 CHANGED
@@ -20,7 +20,7 @@ interface TracedRequest {
20
20
  }
21
21
  type RequestListener = (req: TracedRequest) => void;
22
22
 
23
- type Framework = "nextjs" | "remix" | "nuxt" | "vite" | "astro" | "flask" | "fastapi" | "django" | "custom" | "unknown";
23
+ type Framework = "nextjs" | "remix" | "nuxt" | "vite" | "astro" | "express" | "fastify" | "koa" | "hono" | "nestjs" | "hapi" | "adonis" | "sails" | "flask" | "fastapi" | "django" | "custom" | "unknown";
24
24
  interface DetectedProject {
25
25
  framework: Framework;
26
26
  devCommand: string;
@@ -227,10 +227,11 @@ declare class IssueStore {
227
227
  stop(): void;
228
228
  upsert(issue: Issue, source: IssueSource): StatefulIssue;
229
229
  /**
230
- * Reconcile issues against the current analysis results using evidence-based resolution.
231
- *
232
- * @param currentIssueIds - IDs of issues detected in the current analysis cycle
233
- * @param activeEndpoints - Endpoints that had requests in the current cycle
230
+ * Evidence-based reconciliation: for each active issue whose endpoint had
231
+ * traffic but the issue was NOT re-detected, increment cleanHitsSinceLastSeen.
232
+ * After CLEAN_HITS_FOR_RESOLUTION consecutive clean cycles, auto-resolve.
233
+ * Issues on endpoints with no recent traffic are marked stale after STALE_ISSUE_TTL_MS.
234
+ * Resolved and stale issues are pruned after their respective TTLs expire.
234
235
  */
235
236
  reconcile(currentIssueIds: Set<string>, activeEndpoints: Set<string>): void;
236
237
  transition(issueId: string, state: IssueState): boolean;
package/dist/api.js CHANGED
@@ -232,7 +232,7 @@ var IssueStore = class {
232
232
  existing.occurrences++;
233
233
  existing.issue = issue;
234
234
  existing.cleanHitsSinceLastSeen = 0;
235
- if (existing.state === "resolved" || existing.state === "stale") {
235
+ if (existing.aiStatus !== "wont_fix" && (existing.state === "resolved" || existing.state === "stale")) {
236
236
  existing.state = "regressed";
237
237
  existing.resolvedAt = null;
238
238
  }
@@ -258,10 +258,11 @@ var IssueStore = class {
258
258
  return stateful;
259
259
  }
260
260
  /**
261
- * Reconcile issues against the current analysis results using evidence-based resolution.
262
- *
263
- * @param currentIssueIds - IDs of issues detected in the current analysis cycle
264
- * @param activeEndpoints - Endpoints that had requests in the current cycle
261
+ * Evidence-based reconciliation: for each active issue whose endpoint had
262
+ * traffic but the issue was NOT re-detected, increment cleanHitsSinceLastSeen.
263
+ * After CLEAN_HITS_FOR_RESOLUTION consecutive clean cycles, auto-resolve.
264
+ * Issues on endpoints with no recent traffic are marked stale after STALE_ISSUE_TTL_MS.
265
+ * Resolved and stale issues are pruned after their respective TTLs expire.
265
266
  */
266
267
  reconcile(currentIssueIds, activeEndpoints) {
267
268
  const now = Date.now();
@@ -391,11 +392,22 @@ import { readFile as readFile3, readdir } from "fs/promises";
391
392
  import { existsSync as existsSync4 } from "fs";
392
393
  import { join as join2, relative } from "path";
393
394
  var FRAMEWORKS = [
395
+ // Meta-frameworks first (they bundle Express/Vite internally)
394
396
  { name: "nextjs", dep: "next", devCmd: "next dev", bin: "next", defaultPort: 3e3, devArgs: ["dev", "--port"] },
395
397
  { name: "remix", dep: "@remix-run/dev", devCmd: "remix dev", bin: "remix", defaultPort: 3e3, devArgs: ["dev"] },
396
398
  { name: "nuxt", dep: "nuxt", devCmd: "nuxt dev", bin: "nuxt", defaultPort: 3e3, devArgs: ["dev", "--port"] },
397
- { name: "vite", dep: "vite", devCmd: "vite", bin: "vite", defaultPort: 5173, devArgs: ["--port"] },
398
- { name: "astro", dep: "astro", devCmd: "astro dev", bin: "astro", defaultPort: 4321, devArgs: ["dev", "--port"] }
399
+ { name: "astro", dep: "astro", devCmd: "astro dev", bin: "astro", defaultPort: 4321, devArgs: ["dev", "--port"] },
400
+ { name: "nestjs", dep: "@nestjs/core", devCmd: "nest start", bin: "nest", defaultPort: 3e3, devArgs: ["--watch"] },
401
+ { name: "adonis", dep: "@adonisjs/core", devCmd: "node ace serve", bin: "ace", defaultPort: 3333, devArgs: ["serve", "--watch"] },
402
+ { name: "sails", dep: "sails", devCmd: "sails lift", bin: "sails", defaultPort: 1337, devArgs: ["lift"] },
403
+ // Server frameworks
404
+ { name: "hono", dep: "hono", devCmd: "node", bin: "node", defaultPort: 3e3 },
405
+ { name: "fastify", dep: "fastify", devCmd: "node", bin: "node", defaultPort: 3e3 },
406
+ { name: "koa", dep: "koa", devCmd: "node", bin: "node", defaultPort: 3e3 },
407
+ { name: "hapi", dep: "@hapi/hapi", devCmd: "node", bin: "node", defaultPort: 3e3 },
408
+ { name: "express", dep: "express", devCmd: "node", bin: "node", defaultPort: 3e3 },
409
+ // Bundlers (last — likely used alongside a framework above)
410
+ { name: "vite", dep: "vite", devCmd: "vite", bin: "vite", defaultPort: 5173, devArgs: ["--port"] }
399
411
  ];
400
412
  async function detectProject(rootDir) {
401
413
  const pkgPath = join2(rootDir, "package.json");
@@ -1170,14 +1182,10 @@ var DASHBOARD_API_GRAPH = `${DASHBOARD_PREFIX}/api/graph`;
1170
1182
  var VALID_TABS_TUPLE = [
1171
1183
  "overview",
1172
1184
  "actions",
1173
- "requests",
1174
- "fetches",
1175
- "queries",
1176
- "errors",
1177
- "logs",
1185
+ "insights",
1178
1186
  "performance",
1179
- "security",
1180
- "graph"
1187
+ "graph",
1188
+ "explorer"
1181
1189
  ];
1182
1190
  var VALID_TABS = new Set(VALID_TABS_TUPLE);
1183
1191
 
@@ -2411,7 +2419,7 @@ var AnalysisEngine = class {
2411
2419
  };
2412
2420
 
2413
2421
  // src/index.ts
2414
- var VERSION = "0.10.0";
2422
+ var VERSION = "0.10.1";
2415
2423
  export {
2416
2424
  AdapterRegistry,
2417
2425
  AnalysisEngine,
@@ -99,14 +99,10 @@ var init_labels = __esm({
99
99
  VALID_TABS_TUPLE = [
100
100
  "overview",
101
101
  "actions",
102
- "requests",
103
- "fetches",
104
- "queries",
105
- "errors",
106
- "logs",
102
+ "insights",
107
103
  "performance",
108
- "security",
109
- "graph"
104
+ "graph",
105
+ "explorer"
110
106
  ];
111
107
  VALID_TABS = new Set(VALID_TABS_TUPLE);
112
108
  POSTHOG_HOST = "https://us.i.posthog.com";
@@ -140,7 +136,7 @@ var init_features = __esm({
140
136
  MAX_TIMELINE_EVENTS = 20;
141
137
  MAX_RESOLVED_DISPLAY = 5;
142
138
  ENRICHMENT_SEVERITY_FILTER = ["critical", "warning"];
143
- MCP_SERVER_VERSION = "0.10.0";
139
+ MCP_SERVER_VERSION = "0.10.1";
144
140
  RECOVERY_WINDOW_MS = 5 * 60 * 1e3;
145
141
  PORT_MIN = 1;
146
142
  PORT_MAX = 65535;
@@ -366,10 +362,27 @@ async function enrichFindings(client) {
366
362
  if (reqData.requests.length > 0) {
367
363
  const req = reqData.requests[0];
368
364
  if (req.id) {
369
- const activity = await client.getActivity(req.id);
370
- const queryCount = activity.counts?.queries ?? 0;
371
- const fetchCount = activity.counts?.fetches ?? 0;
372
- return `Request took ${req.durationMs}ms. ${queryCount} DB queries, ${fetchCount} fetches.`;
365
+ const [activity, queries, fetches] = await Promise.all([
366
+ client.getActivity(req.id),
367
+ client.getQueries(req.id),
368
+ client.getFetches(req.id)
369
+ ]);
370
+ const lines = [`Request took ${req.durationMs}ms.`];
371
+ if (queries.entries.length > 0) {
372
+ lines.push(`DB Queries (${queries.entries.length}):`);
373
+ for (const q of queries.entries.slice(0, 5)) {
374
+ const sql = q.sql ?? `${q.operation ?? ""} ${q.table ?? q.model ?? ""}`;
375
+ lines.push(` [${q.durationMs}ms] ${sql}`);
376
+ }
377
+ if (queries.entries.length > 5) lines.push(` ... and ${queries.entries.length - 5} more`);
378
+ }
379
+ if (fetches.entries.length > 0) {
380
+ lines.push(`Fetches (${fetches.entries.length}):`);
381
+ for (const f of fetches.entries.slice(0, 3)) {
382
+ lines.push(` [${f.durationMs}ms] ${f.method} ${f.url} \u2192 ${f.statusCode}`);
383
+ }
384
+ }
385
+ return lines.join("\n");
373
386
  }
374
387
  }
375
388
  } catch {
@@ -488,6 +501,9 @@ var init_get_findings = __esm({
488
501
  return { content: [{ type: "text", text: `Invalid state "${state}". Use: open, fixing, resolved, stale, regressed.` }], isError: true };
489
502
  }
490
503
  let findings = await enrichFindings(client);
504
+ if (!state) {
505
+ findings = findings.filter((f) => f.aiStatus !== "wont_fix");
506
+ }
491
507
  if (severity) {
492
508
  findings = findings.filter((f) => f.severity === severity);
493
509
  }
@@ -499,21 +515,25 @@ var init_get_findings = __esm({
499
515
  if (findings.length === 0) {
500
516
  return { content: [{ type: "text", text: "No findings detected. The application looks healthy." }] };
501
517
  }
502
- const lines = [`Found ${findings.length} issue(s):
503
- `];
518
+ const lines = [
519
+ `Found ${findings.length} issue(s). Full context included below \u2014 no need to call get_request_detail separately.`,
520
+ `After fixing, call report_fixes ONCE with all results.
521
+ `
522
+ ];
504
523
  for (const f of findings) {
505
524
  lines.push(`[${f.severity.toUpperCase()}] ${f.title}`);
506
525
  lines.push(` ID: ${f.findingId}`);
507
526
  lines.push(` Endpoint: ${f.endpoint}`);
508
527
  lines.push(` Issue: ${f.description}`);
509
- if (f.context) lines.push(` Context: ${f.context}`);
528
+ if (f.context) {
529
+ for (const ctxLine of f.context.split("\n")) {
530
+ lines.push(` ${ctxLine}`);
531
+ }
532
+ }
510
533
  lines.push(` Fix: ${f.hint}`);
511
534
  if (f.aiStatus === "fixed") {
512
535
  lines.push(` AI Status: fixed (awaiting verification)`);
513
536
  if (f.aiNotes) lines.push(` AI Notes: ${f.aiNotes}`);
514
- } else if (f.aiStatus === "wont_fix") {
515
- lines.push(` AI Status: won't fix`);
516
- if (f.aiNotes) lines.push(` AI Notes: ${f.aiNotes}`);
517
537
  }
518
538
  lines.push("");
519
539
  }
@@ -901,6 +921,69 @@ var init_report_fix = __esm({
901
921
  }
902
922
  });
903
923
 
924
+ // src/mcp/tools/report-fixes.ts
925
+ var reportFixes;
926
+ var init_report_fixes = __esm({
927
+ "src/mcp/tools/report-fixes.ts"() {
928
+ "use strict";
929
+ init_type_guards();
930
+ reportFixes = {
931
+ name: "report_fixes",
932
+ description: "Report results for multiple findings in a single call. Use this instead of calling report_fix repeatedly \u2014 it's faster and requires only one confirmation. Pass a JSON array string where each item has finding_id, status ('fixed' or 'wont_fix'), and summary.",
933
+ inputSchema: {
934
+ type: "object",
935
+ properties: {
936
+ fixes: {
937
+ type: "string",
938
+ description: 'JSON array of fix reports. Example: [{"finding_id":"abc123","status":"fixed","summary":"Added input validation"}]'
939
+ }
940
+ },
941
+ required: ["fixes"]
942
+ },
943
+ async handler(client, args) {
944
+ let fixes;
945
+ try {
946
+ const raw = typeof args.fixes === "string" ? JSON.parse(args.fixes) : args.fixes;
947
+ if (!Array.isArray(raw) || raw.length === 0) {
948
+ return { content: [{ type: "text", text: "fixes must be a non-empty JSON array." }], isError: true };
949
+ }
950
+ fixes = raw.filter(
951
+ (item) => typeof item === "object" && item !== null && typeof item.finding_id === "string" && typeof item.status === "string" && typeof item.summary === "string"
952
+ );
953
+ if (fixes.length === 0) {
954
+ return { content: [{ type: "text", text: "No valid fix entries found. Each entry needs finding_id, status, and summary." }], isError: true };
955
+ }
956
+ } catch {
957
+ return { content: [{ type: "text", text: "fixes must be valid JSON." }], isError: true };
958
+ }
959
+ const results = [];
960
+ let errors = 0;
961
+ for (const fix of fixes) {
962
+ if (!fix.finding_id || !isValidAiFixStatus(fix.status) || !fix.summary) {
963
+ results.push(`\u2717 Invalid entry: ${fix.finding_id || "missing ID"}`);
964
+ errors++;
965
+ continue;
966
+ }
967
+ const ok = await client.reportFix(fix.finding_id, fix.status, fix.summary);
968
+ if (ok) {
969
+ const label = fix.status === "fixed" ? "fixed" : "won't fix";
970
+ results.push(`\u2713 ${fix.finding_id} \u2192 ${label}`);
971
+ } else {
972
+ results.push(`\u2717 ${fix.finding_id} \u2192 not found`);
973
+ errors++;
974
+ }
975
+ }
976
+ const errorSuffix = errors > 0 ? ` (${errors} error${errors !== 1 ? "s" : ""})` : "";
977
+ const summary = `Processed ${fixes.length} finding(s)${errorSuffix}. Dashboard updated.`;
978
+ return {
979
+ content: [{ type: "text", text: summary + "\n\n" + results.join("\n") }],
980
+ ...errors > 0 ? { isError: true } : {}
981
+ };
982
+ }
983
+ };
984
+ }
985
+ });
986
+
904
987
  // src/mcp/tools/index.ts
905
988
  function getToolDefinitions() {
906
989
  return [...TOOL_MAP.values()].map((t) => ({
@@ -930,8 +1013,9 @@ var init_tools = __esm({
930
1013
  init_get_report();
931
1014
  init_clear_findings();
932
1015
  init_report_fix();
1016
+ init_report_fixes();
933
1017
  TOOL_MAP = new Map(
934
- [getFindings, getEndpoints, getRequestDetail, verifyFix, getReport, clearFindings, reportFix].map((t) => [t.name, t])
1018
+ [getFindings, getEndpoints, getRequestDetail, verifyFix, getReport, clearFindings, reportFix, reportFixes].map((t) => [t.name, t])
935
1019
  );
936
1020
  }
937
1021
  });
@@ -941,7 +1025,7 @@ var SERVER_INSTRUCTIONS, PROMPTS, PROMPT_MESSAGES;
941
1025
  var init_prompts = __esm({
942
1026
  "src/mcp/prompts.ts"() {
943
1027
  "use strict";
944
- SERVER_INSTRUCTIONS = "Brakit is a dev tool that monitors your running app for security vulnerabilities, performance issues, and query problems. IMPORTANT: After fixing any brakit finding, always call the report_fix tool with the finding_id, status ('fixed' or 'wont_fix'), and a brief summary of what you did. This updates the dashboard in real-time so the developer can track AI-driven fixes.";
1028
+ SERVER_INSTRUCTIONS = "Brakit is a dev tool that monitors your running app for security vulnerabilities, performance issues, and query problems. IMPORTANT: After fixing brakit findings, call report_fixes (batch) with all results at once instead of calling report_fix individually. This is faster and requires only one confirmation.";
945
1029
  PROMPTS = [
946
1030
  {
947
1031
  name: "check-app",
@@ -953,8 +1037,8 @@ var init_prompts = __esm({
953
1037
  }
954
1038
  ];
955
1039
  PROMPT_MESSAGES = {
956
- "check-app": "Check my running app for security and performance issues using brakit. First get all findings, then get the endpoint summary. For any critical or warning findings, get the request detail to understand the root cause. Give me a clear report of what's wrong and offer to fix each issue. After fixing any issue, call report_fix with the finding_id, status, and summary.",
957
- "fix-findings": "Get all open brakit findings. For each finding:\n1. Get the request detail to understand the exact issue\n2. Find the source code responsible and fix it\n3. Call report_fix with the finding_id, status ('fixed' or 'wont_fix'), and a brief summary of what you did\n4. Move to the next finding\n\nAfter all findings are processed, ask me to re-trigger the endpoints so brakit can verify the fixes."
1040
+ "check-app": "Check my running app for security and performance issues using brakit. First get all findings, then get the endpoint summary. For any critical or warning findings, get the request detail to understand the root cause. Give me a clear report of what's wrong and offer to fix each issue. After fixing issues, call report_fixes once with all results.",
1041
+ "fix-findings": "Get all open brakit findings. For each finding:\n1. Get the request detail to understand the exact issue\n2. Find the source code responsible and fix it\n3. Track the finding_id, status ('fixed' or 'wont_fix'), and a brief summary\n\nAfter processing ALL findings, call report_fixes ONCE with the full array of results. Do not call report_fix individually for each finding.\n\nAfter reporting, ask me to re-trigger the endpoints so brakit can verify the fixes."
958
1042
  };
959
1043
  }
960
1044
  });
@@ -1123,11 +1207,22 @@ import { readFile as readFile3, readdir } from "fs/promises";
1123
1207
  import { existsSync as existsSync4 } from "fs";
1124
1208
  import { join as join2, relative } from "path";
1125
1209
  var FRAMEWORKS = [
1210
+ // Meta-frameworks first (they bundle Express/Vite internally)
1126
1211
  { name: "nextjs", dep: "next", devCmd: "next dev", bin: "next", defaultPort: 3e3, devArgs: ["dev", "--port"] },
1127
1212
  { name: "remix", dep: "@remix-run/dev", devCmd: "remix dev", bin: "remix", defaultPort: 3e3, devArgs: ["dev"] },
1128
1213
  { name: "nuxt", dep: "nuxt", devCmd: "nuxt dev", bin: "nuxt", defaultPort: 3e3, devArgs: ["dev", "--port"] },
1129
- { name: "vite", dep: "vite", devCmd: "vite", bin: "vite", defaultPort: 5173, devArgs: ["--port"] },
1130
- { name: "astro", dep: "astro", devCmd: "astro dev", bin: "astro", defaultPort: 4321, devArgs: ["dev", "--port"] }
1214
+ { name: "astro", dep: "astro", devCmd: "astro dev", bin: "astro", defaultPort: 4321, devArgs: ["dev", "--port"] },
1215
+ { name: "nestjs", dep: "@nestjs/core", devCmd: "nest start", bin: "nest", defaultPort: 3e3, devArgs: ["--watch"] },
1216
+ { name: "adonis", dep: "@adonisjs/core", devCmd: "node ace serve", bin: "ace", defaultPort: 3333, devArgs: ["serve", "--watch"] },
1217
+ { name: "sails", dep: "sails", devCmd: "sails lift", bin: "sails", defaultPort: 1337, devArgs: ["lift"] },
1218
+ // Server frameworks
1219
+ { name: "hono", dep: "hono", devCmd: "node", bin: "node", defaultPort: 3e3 },
1220
+ { name: "fastify", dep: "fastify", devCmd: "node", bin: "node", defaultPort: 3e3 },
1221
+ { name: "koa", dep: "koa", devCmd: "node", bin: "node", defaultPort: 3e3 },
1222
+ { name: "hapi", dep: "@hapi/hapi", devCmd: "node", bin: "node", defaultPort: 3e3 },
1223
+ { name: "express", dep: "express", devCmd: "node", bin: "node", defaultPort: 3e3 },
1224
+ // Bundlers (last — likely used alongside a framework above)
1225
+ { name: "vite", dep: "vite", devCmd: "vite", bin: "vite", defaultPort: 5173, devArgs: ["--port"] }
1131
1226
  ];
1132
1227
  async function detectProject(rootDir) {
1133
1228
  const pkgPath = join2(rootDir, "package.json");
@@ -1910,7 +2005,7 @@ init_constants();
1910
2005
  init_endpoint();
1911
2006
 
1912
2007
  // src/index.ts
1913
- var VERSION = "0.10.0";
2008
+ var VERSION = "0.10.1";
1914
2009
 
1915
2010
  // src/cli/commands/install.ts
1916
2011
  init_constants();