Contributing
Add a database adapter, security rule, or language SDK. Each takes one file and one interface.
Setup
git clone https://github.com/brakit-ai/brakit.git
cd brakit
npm install
npm run build
npm testDuring development, npm run dev runs tsup in watch mode. Tests use vitest (npm test or npm run test:watch).
Project Structure
src/
analysis/ # Flow grouping, insights, and security rules
rules/ # SecurityRule implementations (one file per rule)
cli/ # CLI command definitions (citty)
commands/ # install, uninstall, telemetry commands
constants/ # Shared thresholds, route paths, limits
dashboard/ # HTML dashboard, REST API handlers, SSE
api/ # API route handlers
client/ # Client-side template-literal JavaScript
detect/ # Framework auto-detection
instrument/ # In-process instrumentation
adapters/ # BrakitAdapter implementations (one file per library)
hooks/ # Core runtime hooks (fetch, console, errors)
output/ # Terminal output formatting
runtime/ # Core runtime - activation, HTTP capture, setup
store/ # In-memory bounded stores with pub/sub
telemetry/ # Anonymous usage telemetry
types/ # TypeScript type definitionsAdding a Database Adapter
This is the most common contribution. Each adapter is one file in src/instrument/adapters/ implementing the BrakitAdapter interface.
1. Create the adapter file
import type { BrakitAdapter } from "../adapter.js";
import type { TelemetryEvent } from "../../types/index.js";
import { tryRequire, captureRequestId } from "./shared.js";
import { normalizeSQL } from "./normalize.js";
export const drizzleAdapter: BrakitAdapter = {
name: "drizzle",
detect() {
return tryRequire("drizzle-orm") !== null;
},
patch(emit) {
const drizzle = tryRequire("drizzle-orm");
if (!drizzle) return;
// Find the prototype method to patch.
// Each library is different - read the library's source
// to find the right interception point.
const origMethod = /* ... */;
/* replace with wrapper */ = function (...args) {
const start = performance.now();
const requestId = captureRequestId();
const result = origMethod.apply(this, args);
emit({
type: "query",
data: {
driver: "drizzle",
source: "drizzle",
sql: /* raw SQL if available */,
normalizedOp: /* use normalizeSQL() */,
table: /* table name */,
durationMs: Math.round(performance.now() - start),
rowCount: /* if available */,
parentRequestId: requestId,
timestamp: Date.now(),
},
});
return result;
};
},
};2. Register it
import { drizzleAdapter } from "./drizzle.js";
export function createDefaultRegistry(): AdapterRegistry {
const registry = new AdapterRegistry();
registry.register(pgAdapter);
registry.register(mysql2Adapter);
registry.register(prismaAdapter);
registry.register(drizzleAdapter); // add here
return registry;
}3. Mark as external
Add the library to the external array in tsup.config.ts so it's resolved from the user's project at runtime:
external: ["pg", "mysql2", "@prisma/client", "drizzle-orm"]captureRequestId() before any async operation. Database drivers use connection pools and native bindings that break AsyncLocalStorage propagation.Adding a Security Rule
Each security rule is one file in src/analysis/rules/ implementing the SecurityRule interface.
import type { SecurityRule } from "./rule.js";
import type { SecurityFinding } from "../../types/index.js";
export const myRule: SecurityRule = {
id: "my-rule-id",
severity: "warning",
name: "Human Readable Name",
hint: "Actionable suggestion for how to fix this.",
check(ctx) {
const findings: SecurityFinding[] = [];
for (const r of ctx.requests) {
if (/* your detection logic */) {
findings.push({
severity: "warning",
rule: this.id,
title: this.name,
desc: `${r.method} ${r.path}: what was found`,
hint: this.hint,
endpoint: `${r.method} ${r.path}`,
count: 1,
});
}
}
return findings;
},
};Register it in src/analysis/rules/scanner.ts and export from src/analysis/rules/index.ts. The scanner automatically picks it up.
Building a Language SDK
Any language can send events to brakit by POSTing to the ingest endpoint. See the SDK Protocol page for the full specification, payload format, and event types.
A minimal SDK needs three things: (1) HTTP middleware to generate a unique request ID, (2) library patches to capture queries, and (3) a batched transport to POST events.
Code Style
- TypeScript strict mode. ESM only.
- Comments only where the logic isn't self-evident.
- One adapter per file, one rule per file, one concern per module.
- Commit messages in imperative mood:
Add drizzle adapter,Fix N+1 false positive