@vibecheck-ai/mcp 24.4.0 → 24.4.3

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.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1 -1
  3. package/dist/index.js +1919 -1712
  4. package/package.json +11 -13
package/dist/index.js CHANGED
@@ -221643,7 +221643,7 @@ ${JSON.stringify(t2, null, 2)}`);
221643
221643
 
221644
221644
  // ../context-engine/dist/chunk-3ZE34DAJ.js
221645
221645
  import { readFile as readFile2 } from "fs/promises";
221646
- import { join as join3, dirname as dirname5 } from "path";
221646
+ import { join as join3, dirname as dirname4 } from "path";
221647
221647
  import { fileURLToPath } from "url";
221648
221648
  async function initParser() {
221649
221649
  if (ParserClass) return ParserClass;
@@ -221783,7 +221783,7 @@ var init_chunk_3ZE34DAJ = __esm({
221783
221783
  SymbolKind2["Export"] = "export";
221784
221784
  return SymbolKind2;
221785
221785
  })(SymbolKind || {});
221786
- GRAMMAR_DIR = join3(dirname5(fileURLToPath(import.meta.url)), "../../../../node_modules/tree-sitter-wasms/out");
221786
+ GRAMMAR_DIR = join3(dirname4(fileURLToPath(import.meta.url)), "../../../../node_modules/tree-sitter-wasms/out");
221787
221787
  EXT_TO_GRAMMAR = {
221788
221788
  ".ts": "typescript",
221789
221789
  ".tsx": "tsx",
@@ -289071,1860 +289071,1860 @@ rules:
289071
289071
  match: "src/repositories/**"
289072
289072
  `;
289073
289073
 
289074
- // ../context-engine/dist/chunk-INBCP46U.js
289075
- import * as path5 from "path";
289076
- var ContextSynthesizer = class {
289074
+ // ../context-engine/dist/chunk-HMKLYBWJ.js
289075
+ var import_better_sqlite3 = __toESM(require_lib(), 1);
289076
+ var import_fast_glob2 = __toESM(require_out4(), 1);
289077
+ import { createHash as createHash2 } from "crypto";
289078
+ import { readFile as readFile3, stat } from "fs/promises";
289079
+ import { mkdirSync } from "fs";
289080
+ import { join as join4 } from "path";
289081
+ import { readFile as readFile22 } from "fs/promises";
289082
+ import { accessSync } from "fs";
289083
+ import { extname as extname2, resolve as resolve2, dirname as dirname5 } from "path";
289084
+ import { createHash as createHash22 } from "crypto";
289085
+ var SCHEMA_VERSION = 1;
289086
+ var PersistentIndex = class {
289087
+ db;
289088
+ config;
289077
289089
  rootPath;
289078
- data;
289079
- dna;
289080
- graph;
289081
- ruleResult;
289082
- constructor(rootPath, data, dna, graph, ruleResult) {
289083
- this.rootPath = rootPath;
289084
- this.data = data;
289085
- this.dna = dna;
289086
- this.graph = graph;
289087
- this.ruleResult = ruleResult || null;
289088
- }
289089
- /**
289090
- * Synthesize the full context — the complete brain dump for AI agents.
289091
- */
289092
- synthesize() {
289093
- const projectIdentity = this.buildProjectIdentity();
289094
- const archRules = this.buildArchRuleSummary();
289095
- const activeViolations = this.ruleResult?.violations || [];
289096
- const codebaseDNA = this.buildDNASummary();
289097
- const fileContexts = this.buildFileContexts();
289098
- const taskPlaybooks = this.buildTaskPlaybooks();
289099
- const verificationSteps = this.buildVerificationSteps();
289100
- const riskBriefing = this.buildRiskBriefing();
289101
- return {
289102
- version: "2.0.0",
289103
- generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
289104
- projectIdentity,
289105
- architecturalRules: archRules,
289106
- activeViolations,
289107
- codebaseDNA,
289108
- fileContexts,
289109
- taskPlaybooks,
289110
- verificationSteps,
289111
- riskBriefing
289090
+ constructor(config) {
289091
+ this.rootPath = config.rootPath;
289092
+ this.config = {
289093
+ rootPath: config.rootPath,
289094
+ dbPath: config.dbPath ?? join4(config.rootPath, ".vibecheck", "index.db"),
289095
+ includePatterns: config.includePatterns ?? ["**/*.{ts,tsx,js,jsx,py,rs,go,java,c,cpp,h,hpp,rb,swift,kt,lua,zig}"],
289096
+ excludePatterns: config.excludePatterns ?? [
289097
+ "**/node_modules/**",
289098
+ "**/dist/**",
289099
+ "**/build/**",
289100
+ "**/.next/**",
289101
+ "**/.git/**",
289102
+ "**/coverage/**",
289103
+ "**/.turbo/**",
289104
+ "**/__pycache__/**",
289105
+ "**/target/**",
289106
+ "**/.mcp_data/**"
289107
+ ],
289108
+ maxFileSize: config.maxFileSize ?? 5e5,
289109
+ maxFiles: config.maxFiles ?? 1e4,
289110
+ storeContent: config.storeContent ?? true
289112
289111
  };
289112
+ const dbDir = join4(this.config.dbPath, "..");
289113
+ mkdirSync(dbDir, { recursive: true });
289114
+ this.db = new import_better_sqlite3.default(this.config.dbPath);
289115
+ this.db.pragma("journal_mode = WAL");
289116
+ this.db.pragma("synchronous = NORMAL");
289117
+ this.db.pragma("cache_size = -64000");
289118
+ this.initSchema();
289113
289119
  }
289114
- /**
289115
- * Generate context for a specific file — what an AI agent needs to know
289116
- * before editing this file.
289117
- */
289118
- synthesizeForFile(filePath) {
289119
- const rel = this.toRelative(filePath);
289120
- const file = this.data.files.find((f) => f.relativePath === rel || f.path === filePath);
289121
- if (!file) return null;
289122
- return this.buildSingleFileContext(file);
289120
+ // ═══════════════════════════════════════════════════════════════════════════
289121
+ // SCHEMA
289122
+ // ═══════════════════════════════════════════════════════════════════════════
289123
+ initSchema() {
289124
+ const version3 = this.getSchemaVersion();
289125
+ if (version3 === SCHEMA_VERSION) return;
289126
+ this.db.exec(`
289127
+ DROP TABLE IF EXISTS file_hashes;
289128
+ DROP TABLE IF EXISTS files;
289129
+ DROP TABLE IF EXISTS symbols;
289130
+ DROP TABLE IF EXISTS imports;
289131
+ DROP TABLE IF EXISTS call_edges;
289132
+ DROP TABLE IF EXISTS routes;
289133
+ DROP TABLE IF EXISTS services;
289134
+ DROP TABLE IF EXISTS embeddings;
289135
+ DROP TABLE IF EXISTS meta;
289136
+
289137
+ CREATE TABLE meta (
289138
+ key TEXT PRIMARY KEY,
289139
+ value TEXT NOT NULL
289140
+ );
289141
+
289142
+ CREATE TABLE file_hashes (
289143
+ relative_path TEXT PRIMARY KEY,
289144
+ content_hash TEXT NOT NULL,
289145
+ size_bytes INTEGER NOT NULL,
289146
+ modified_ms INTEGER NOT NULL,
289147
+ indexed_at INTEGER NOT NULL DEFAULT (unixepoch('now'))
289148
+ );
289149
+
289150
+ CREATE TABLE files (
289151
+ id TEXT PRIMARY KEY,
289152
+ path TEXT NOT NULL,
289153
+ relative_path TEXT NOT NULL UNIQUE,
289154
+ language TEXT NOT NULL,
289155
+ line_count INTEGER NOT NULL,
289156
+ exports TEXT NOT NULL DEFAULT '[]',
289157
+ content TEXT
289158
+ );
289159
+
289160
+ CREATE TABLE symbols (
289161
+ id TEXT PRIMARY KEY,
289162
+ name TEXT NOT NULL,
289163
+ kind TEXT NOT NULL,
289164
+ file_path TEXT NOT NULL,
289165
+ start_line INTEGER NOT NULL,
289166
+ end_line INTEGER NOT NULL,
289167
+ exported INTEGER NOT NULL DEFAULT 0,
289168
+ async INTEGER NOT NULL DEFAULT 0,
289169
+ params INTEGER,
289170
+ branches INTEGER,
289171
+ signature TEXT
289172
+ );
289173
+
289174
+ CREATE TABLE imports (
289175
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
289176
+ file_id TEXT NOT NULL,
289177
+ file_path TEXT NOT NULL,
289178
+ source_path TEXT NOT NULL,
289179
+ resolved_path TEXT NOT NULL DEFAULT '',
289180
+ imported_symbols TEXT NOT NULL DEFAULT '[]',
289181
+ is_type_only INTEGER NOT NULL DEFAULT 0,
289182
+ is_dynamic INTEGER NOT NULL DEFAULT 0,
289183
+ line INTEGER NOT NULL DEFAULT 0
289184
+ );
289185
+
289186
+ CREATE TABLE call_edges (
289187
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
289188
+ caller_id TEXT NOT NULL,
289189
+ callee_id TEXT NOT NULL,
289190
+ caller_name TEXT NOT NULL,
289191
+ callee_name TEXT NOT NULL,
289192
+ caller_file TEXT NOT NULL,
289193
+ callee_file TEXT NOT NULL
289194
+ );
289195
+
289196
+ CREATE TABLE routes (
289197
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
289198
+ path TEXT NOT NULL,
289199
+ method TEXT NOT NULL,
289200
+ handler TEXT NOT NULL,
289201
+ file TEXT NOT NULL,
289202
+ line INTEGER NOT NULL DEFAULT 0,
289203
+ middleware TEXT NOT NULL DEFAULT '[]',
289204
+ auth INTEGER
289205
+ );
289206
+
289207
+ CREATE TABLE services (
289208
+ id TEXT PRIMARY KEY,
289209
+ name TEXT NOT NULL,
289210
+ root_path TEXT NOT NULL DEFAULT ''
289211
+ );
289212
+
289213
+ CREATE TABLE embeddings (
289214
+ path TEXT NOT NULL,
289215
+ chunk_id TEXT NOT NULL,
289216
+ chunk_type TEXT NOT NULL DEFAULT 'file',
289217
+ content_hash TEXT NOT NULL,
289218
+ vector BLOB NOT NULL,
289219
+ metadata TEXT NOT NULL DEFAULT '{}',
289220
+ PRIMARY KEY (path, chunk_id)
289221
+ );
289222
+
289223
+ -- Indexes for fast lookups
289224
+ CREATE INDEX idx_symbols_file ON symbols(file_path);
289225
+ CREATE INDEX idx_symbols_name ON symbols(name);
289226
+ CREATE INDEX idx_symbols_kind ON symbols(kind);
289227
+ CREATE INDEX idx_imports_file ON imports(file_path);
289228
+ CREATE INDEX idx_imports_source ON imports(source_path);
289229
+ CREATE INDEX idx_imports_resolved ON imports(resolved_path);
289230
+ CREATE INDEX idx_call_edges_caller ON call_edges(caller_file);
289231
+ CREATE INDEX idx_call_edges_callee ON call_edges(callee_file);
289232
+ CREATE INDEX idx_embeddings_type ON embeddings(chunk_type);
289233
+ `);
289234
+ this.setMeta("schema_version", String(SCHEMA_VERSION));
289235
+ this.setMeta("created_at", (/* @__PURE__ */ new Date()).toISOString());
289236
+ }
289237
+ getSchemaVersion() {
289238
+ try {
289239
+ const row = this.db.prepare("SELECT value FROM meta WHERE key = ?").get("schema_version");
289240
+ return row ? Number.parseInt(row.value, 10) : 0;
289241
+ } catch {
289242
+ return 0;
289243
+ }
289244
+ }
289245
+ setMeta(key, value) {
289246
+ this.db.prepare("INSERT OR REPLACE INTO meta (key, value) VALUES (?, ?)").run(key, value);
289247
+ }
289248
+ getMeta(key) {
289249
+ const row = this.db.prepare("SELECT value FROM meta WHERE key = ?").get(key);
289250
+ return row?.value ?? null;
289123
289251
  }
289252
+ // ═══════════════════════════════════════════════════════════════════════════
289253
+ // INCREMENTAL INDEXING
289254
+ // ═══════════════════════════════════════════════════════════════════════════
289124
289255
  /**
289125
- * Generate a compact markdown context document for IDE rules.
289126
- * This replaces the old static rule generation with intelligence-driven context.
289256
+ * Discover files, diff against stored hashes, return only changed files.
289127
289257
  */
289128
- generateContextDocument() {
289129
- const ctx = this.synthesize();
289130
- const lines = [];
289131
- lines.push(`# ${ctx.projectIdentity.name} \u2014 AI Context`);
289132
- lines.push(`<!-- Generated by @repo/context-engine at ${ctx.generatedAt} -->`);
289133
- lines.push("");
289134
- lines.push("## Project Identity");
289135
- lines.push(`- **Stack**: ${ctx.projectIdentity.stack}`);
289136
- lines.push(`- **Architecture**: ${ctx.projectIdentity.architecture}`);
289137
- if (ctx.projectIdentity.keyPatterns.length > 0) {
289138
- lines.push(`- **Key Patterns**: ${ctx.projectIdentity.keyPatterns.join(", ")}`);
289139
- }
289140
- lines.push("");
289141
- if (ctx.codebaseDNA.conventions.length > 0) {
289142
- lines.push("## Conventions (Auto-Discovered)");
289143
- for (const conv of ctx.codebaseDNA.conventions) {
289144
- lines.push(`- ${conv}`);
289145
- }
289146
- lines.push("");
289147
- }
289148
- if (ctx.architecturalRules.length > 0) {
289149
- lines.push("## Architecture Rules");
289150
- for (const rule of ctx.architecturalRules) {
289151
- const icon = rule.severity === "error" ? "MUST" : rule.severity === "warning" ? "SHOULD" : "MAY";
289152
- lines.push(`- **[${icon}]** ${rule.name}: ${rule.description}`);
289153
- if (rule.violationCount > 0) {
289154
- lines.push(` - ${rule.violationCount} active violations`);
289155
- }
289156
- }
289157
- lines.push("");
289158
- }
289159
- if (ctx.codebaseDNA.boundaries.length > 0) {
289160
- lines.push("## Module Boundaries");
289161
- for (const boundary of ctx.codebaseDNA.boundaries) {
289162
- lines.push(`- ${boundary}`);
289163
- }
289164
- lines.push("");
289165
- }
289166
- if (ctx.codebaseDNA.hotFiles.length > 0) {
289167
- lines.push("## High-Impact Files (Edit With Care)");
289168
- for (const file of ctx.codebaseDNA.hotFiles.slice(0, 10)) {
289169
- lines.push(`- \`${file}\``);
289258
+ async diffFiles() {
289259
+ const discoveredFiles = await this.discoverFiles();
289260
+ const storedHashes = this.getStoredHashes();
289261
+ const changed = [];
289262
+ const unchanged = [];
289263
+ const currentPaths = /* @__PURE__ */ new Set();
289264
+ for (const file of discoveredFiles) {
289265
+ currentPaths.add(file.relativePath);
289266
+ const stored = storedHashes.get(file.relativePath);
289267
+ if (!stored || stored.contentHash !== file.contentHash) {
289268
+ changed.push(file.relativePath);
289269
+ } else {
289270
+ unchanged.push(file.relativePath);
289170
289271
  }
289171
- lines.push("");
289172
289272
  }
289173
- if (ctx.riskBriefing.securityConcerns.length > 0 || ctx.riskBriefing.testGaps.length > 0) {
289174
- lines.push("## Risk Areas");
289175
- for (const concern of ctx.riskBriefing.securityConcerns) {
289176
- lines.push(`- ${concern}`);
289177
- }
289178
- for (const gap of ctx.riskBriefing.testGaps.slice(0, 5)) {
289179
- lines.push(`- ${gap}`);
289273
+ const deleted = [];
289274
+ for (const storedPath of storedHashes.keys()) {
289275
+ if (!currentPaths.has(storedPath)) {
289276
+ deleted.push(storedPath);
289180
289277
  }
289181
- lines.push("");
289182
289278
  }
289183
- if (ctx.projectIdentity.noGoZones.length > 0) {
289184
- lines.push("## No-Go Zones");
289185
- for (const zone of ctx.projectIdentity.noGoZones) {
289186
- lines.push(`- ${zone}`);
289187
- }
289188
- lines.push("");
289279
+ return { changed, deleted, unchanged };
289280
+ }
289281
+ /**
289282
+ * Full reindex — scan all files and store data.
289283
+ * Returns parsed CodebaseData + stats.
289284
+ */
289285
+ async reindex(parser4) {
289286
+ const startMs = Date.now();
289287
+ const { changed, deleted, unchanged } = await this.diffFiles();
289288
+ if (deleted.length > 0) {
289289
+ this.removeFiles(deleted);
289189
289290
  }
289190
- if (ctx.taskPlaybooks.length > 0) {
289191
- lines.push("## Task Playbooks");
289192
- for (const playbook of ctx.taskPlaybooks) {
289193
- lines.push(`### ${playbook.taskType}`);
289194
- for (const step of playbook.steps) {
289195
- lines.push(`1. ${step}`);
289196
- }
289197
- if (playbook.mustVerify.length > 0) {
289198
- lines.push(`**Verify**: ${playbook.mustVerify.join(", ")}`);
289199
- }
289200
- lines.push("");
289291
+ const parsedFiles = [];
289292
+ for (const relPath of changed) {
289293
+ const absPath = join4(this.rootPath, relPath);
289294
+ try {
289295
+ const parsed = await parser4.parseFile(absPath, relPath);
289296
+ parsedFiles.push(parsed);
289297
+ } catch {
289201
289298
  }
289202
289299
  }
289203
- if (ctx.verificationSteps.length > 0) {
289204
- lines.push("## Verification Protocol");
289205
- for (const step of ctx.verificationSteps) {
289206
- lines.push(`### On ${step.trigger}`);
289207
- for (const check of step.checks) {
289208
- lines.push(`- ${check}`);
289209
- }
289210
- if (step.commands.length > 0) {
289211
- lines.push(`**Run**: \`${step.commands.join(" && ")}\``);
289212
- }
289213
- lines.push("");
289300
+ this.storeFiles(parsedFiles);
289301
+ const data = this.loadCodebaseData();
289302
+ const stats = {
289303
+ totalFiles: data.files.length,
289304
+ totalSymbols: data.symbols.length,
289305
+ totalImports: data.imports.length,
289306
+ indexedAt: (/* @__PURE__ */ new Date()).toISOString(),
289307
+ reindexedFiles: changed.length,
289308
+ skippedFiles: unchanged.length,
289309
+ durationMs: Date.now() - startMs
289310
+ };
289311
+ this.setMeta("last_index_at", stats.indexedAt);
289312
+ this.setMeta("last_index_stats", JSON.stringify(stats));
289313
+ return { data, stats };
289314
+ }
289315
+ /**
289316
+ * Incremental update — only reindex specific files.
289317
+ */
289318
+ async reindexFiles(relativePaths, parser4) {
289319
+ const startMs = Date.now();
289320
+ this.removeFiles(relativePaths);
289321
+ const parsedFiles = [];
289322
+ for (const relPath of relativePaths) {
289323
+ const absPath = join4(this.rootPath, relPath);
289324
+ try {
289325
+ const parsed = await parser4.parseFile(absPath, relPath);
289326
+ parsedFiles.push(parsed);
289327
+ } catch {
289214
289328
  }
289215
289329
  }
289216
- lines.push("## Codebase Health");
289217
- lines.push(`- **Overall**: ${this.dna.healthScore.overall}/100`);
289218
- const dims = this.dna.healthScore.dimensions;
289219
- lines.push(`- Architecture: ${dims.architecture} | Tests: ${dims.testCoverage} | Conventions: ${dims.conventions} | Dependencies: ${dims.dependencies}`);
289220
- lines.push("");
289221
- lines.push("---");
289222
- lines.push("<!-- context-engine:v2 -->");
289223
- return lines.join("\n");
289330
+ this.storeFiles(parsedFiles);
289331
+ const totalFiles = this.db.prepare("SELECT COUNT(*) as cnt FROM files").get();
289332
+ const totalSymbols = this.db.prepare("SELECT COUNT(*) as cnt FROM symbols").get();
289333
+ const totalImports = this.db.prepare("SELECT COUNT(*) as cnt FROM imports").get();
289334
+ return {
289335
+ totalFiles: totalFiles.cnt,
289336
+ totalSymbols: totalSymbols.cnt,
289337
+ totalImports: totalImports.cnt,
289338
+ indexedAt: (/* @__PURE__ */ new Date()).toISOString(),
289339
+ reindexedFiles: parsedFiles.length,
289340
+ skippedFiles: 0,
289341
+ durationMs: Date.now() - startMs
289342
+ };
289224
289343
  }
289225
289344
  // ═══════════════════════════════════════════════════════════════════════════
289226
- // IDENTITY
289345
+ // DATA LOADING (warm start)
289227
289346
  // ═══════════════════════════════════════════════════════════════════════════
289228
- buildProjectIdentity() {
289229
- const fp = this.dna.fingerprint;
289230
- const stack = [fp.framework, fp.language, fp.orm, fp.validator, fp.authLib, fp.router].filter(Boolean).join(" | ");
289231
- const keyPatterns = this.dna.patterns.map((p) => p.name);
289232
- const criticalPaths = this.dna.hotspots.slice(0, 5).map((h) => h.file);
289233
- const noGoZones = [];
289234
- if (this.ruleResult) {
289235
- const errorRules = this.ruleResult.violations.filter((v) => v.severity === "error");
289236
- const uniqueMessages = [...new Set(errorRules.map((v) => v.message))];
289237
- noGoZones.push(...uniqueMessages.slice(0, 5));
289347
+ /**
289348
+ * Load full CodebaseData from the persistent store.
289349
+ * This is the warm-start path sub-second for indexed repos.
289350
+ */
289351
+ loadCodebaseData() {
289352
+ const files = this.loadFiles();
289353
+ const symbols = this.loadSymbols();
289354
+ const imports = this.loadImports();
289355
+ const callEdges = this.loadCallEdges();
289356
+ const routes = this.loadRoutes();
289357
+ const services = this.loadServices();
289358
+ return { files, symbols, imports, callEdges, routes, services };
289359
+ }
289360
+ /**
289361
+ * Check if the index exists and has data.
289362
+ */
289363
+ isPopulated() {
289364
+ try {
289365
+ const row = this.db.prepare("SELECT COUNT(*) as cnt FROM files").get();
289366
+ return row.cnt > 0;
289367
+ } catch {
289368
+ return false;
289238
289369
  }
289239
- for (const cycle of this.graph.cycles) {
289240
- noGoZones.push(`Circular dependency: ${cycle.nodes.slice(0, 3).join(" \u2192 ")}...`);
289370
+ }
289371
+ /**
289372
+ * Get the last index timestamp.
289373
+ */
289374
+ getLastIndexedAt() {
289375
+ return this.getMeta("last_index_at");
289376
+ }
289377
+ /**
289378
+ * Get the last index stats.
289379
+ */
289380
+ getLastIndexStats() {
289381
+ const raw = this.getMeta("last_index_stats");
289382
+ if (!raw) return null;
289383
+ try {
289384
+ return JSON.parse(raw);
289385
+ } catch {
289386
+ return null;
289241
289387
  }
289242
- const architecture = this.dna.conventions.filter((c) => c.area === "structure").map((c) => c.description).join("; ") || fp.framework;
289243
- return {
289244
- name: fp.name,
289245
- stack,
289246
- architecture,
289247
- keyPatterns,
289248
- criticalPaths,
289249
- noGoZones: noGoZones.slice(0, 10)
289250
- };
289251
289388
  }
289252
289389
  // ═══════════════════════════════════════════════════════════════════════════
289253
- // RULES SUMMARY
289390
+ // EMBEDDING STORAGE
289254
289391
  // ═══════════════════════════════════════════════════════════════════════════
289255
- buildArchRuleSummary() {
289256
- if (!this.ruleResult) return [];
289257
- const breakdown = this.ruleResult.ruleBreakdown;
289258
- return Object.entries(breakdown).map(([ruleId, count]) => {
289259
- const violation = this.ruleResult.violations.find((v) => v.ruleId === ruleId);
289260
- return {
289261
- id: ruleId,
289262
- name: violation?.ruleName || ruleId,
289263
- type: "import_forbidden",
289264
- severity: violation?.severity || "warning",
289265
- scope: violation?.sourceSymbol.filePath || "",
289266
- description: violation?.message || "",
289267
- violationCount: count
289268
- };
289269
- });
289392
+ /**
289393
+ * Store a chunk embedding (file-level, function-level, etc.)
289394
+ */
289395
+ storeEmbedding(path10, chunkId, chunkType, contentHash, vector, metadata2) {
289396
+ const vectorBuf = Buffer.from(new Float32Array(vector).buffer);
289397
+ this.db.prepare(`
289398
+ INSERT OR REPLACE INTO embeddings (path, chunk_id, chunk_type, content_hash, vector, metadata)
289399
+ VALUES (?, ?, ?, ?, ?, ?)
289400
+ `).run(path10, chunkId, chunkType, contentHash, vectorBuf, JSON.stringify(metadata2 ?? {}));
289270
289401
  }
289271
- // ═══════════════════════════════════════════════════════════════════════════
289272
- // DNA SUMMARY
289273
- // ═══════════════════════════════════════════════════════════════════════════
289274
- buildDNASummary() {
289402
+ /**
289403
+ * Load embedding for a specific chunk.
289404
+ */
289405
+ loadEmbedding(path10, chunkId) {
289406
+ const row = this.db.prepare("SELECT vector, content_hash, metadata FROM embeddings WHERE path = ? AND chunk_id = ?").get(path10, chunkId);
289407
+ if (!row) return null;
289275
289408
  return {
289276
- conventions: this.dna.conventions.filter((c) => c.confidence > 0.5).map((c) => c.description),
289277
- patterns: this.dna.patterns.map((p) => `${p.name}: ${p.description}`),
289278
- boundaries: this.dna.boundaries.filter((b) => b.importCount > 3).map((b) => `${b.from} \u2192 ${b.to} (${b.importCount} imports${b.isCircular ? ", CIRCULAR" : ""})`),
289279
- hotFiles: this.dna.hotspots.slice(0, 10).map((h) => h.file),
289280
- riskAreas: this.dna.riskMap.filter((r) => r.riskLevel === "critical" || r.riskLevel === "high").map((r) => `${r.file}: ${r.factors[0]}`)
289409
+ vector: Array.from(new Float32Array(row.vector.buffer, row.vector.byteOffset, row.vector.byteLength / 4)),
289410
+ contentHash: row.content_hash,
289411
+ metadata: JSON.parse(row.metadata)
289281
289412
  };
289282
289413
  }
289283
- // ═══════════════════════════════════════════════════════════════════════════
289284
- // FILE CONTEXTS
289285
- // ═══════════════════════════════════════════════════════════════════════════
289286
- buildFileContexts() {
289287
- const contexts = /* @__PURE__ */ new Map();
289288
- for (const file of this.data.files) {
289289
- contexts.set(file.relativePath, this.buildSingleFileContext(file));
289290
- }
289291
- return contexts;
289292
- }
289293
- buildSingleFileContext(file) {
289294
- const rel = file.relativePath;
289295
- const role = this.classifyRole(file);
289296
- const graphNode = this.graph.nodes.find((n) => n.relativePath === rel);
289297
- const layer = graphNode?.layer;
289298
- const dependsOn = this.graph.edges.filter((e) => e.from === rel).map((e) => e.to);
289299
- const dependedOnBy = this.graph.edges.filter((e) => e.to === rel).map((e) => e.from);
289300
- const applicableRules = [];
289301
- if (this.ruleResult) {
289302
- for (const v of this.ruleResult.violations) {
289303
- if (v.sourceSymbol.filePath.includes(rel) || v.targetSymbol && v.targetSymbol.filePath.includes(rel)) {
289304
- if (!applicableRules.includes(v.ruleId)) applicableRules.push(v.ruleId);
289305
- }
289306
- }
289307
- }
289308
- const conventions = this.dna.conventions.filter((c) => this.conventionAppliesToFile(c.area, file)).map((c) => c.description);
289309
- const patterns = this.dna.patterns.filter((p) => p.fileMatches.some((m) => m === rel)).map((p) => p.name);
289310
- const riskEntry = this.dna.riskMap.find((r) => r.file === rel);
289311
- const riskLevel = riskEntry?.riskLevel || "low";
289312
- const relatedFiles = this.findRelatedFiles(file, role).slice(0, 8);
289313
- const editGuidance = this.generateEditGuidance(file, role, layer, dependedOnBy, conventions);
289314
- return {
289315
- filePath: file.path,
289316
- role,
289317
- layer,
289318
- dependsOn,
289319
- dependedOnBy,
289320
- applicableRules,
289321
- conventions,
289322
- patterns,
289323
- riskLevel,
289324
- relatedFiles,
289325
- editGuidance
289326
- };
289414
+ /**
289415
+ * Load all embeddings of a given type for vector search.
289416
+ */
289417
+ loadEmbeddingsByType(chunkType) {
289418
+ const rows = this.db.prepare("SELECT path, chunk_id, vector, content_hash, metadata FROM embeddings WHERE chunk_type = ?").all(chunkType);
289419
+ return rows.map((row) => ({
289420
+ path: row.path,
289421
+ chunkId: row.chunk_id,
289422
+ vector: Array.from(new Float32Array(row.vector.buffer, row.vector.byteOffset, row.vector.byteLength / 4)),
289423
+ contentHash: row.content_hash,
289424
+ metadata: JSON.parse(row.metadata)
289425
+ }));
289327
289426
  }
289328
- classifyRole(file) {
289329
- const rel = file.relativePath.toLowerCase();
289330
- if (rel.includes(".test.") || rel.includes(".spec.") || rel.includes("__tests__")) return "test";
289331
- if (rel.includes("fixture") || rel.includes("mock")) return "fixture";
289332
- if (rel.includes("migration")) return "migration";
289333
- if (rel.match(/\.(css|scss|less|styl)$/)) return "style";
289334
- if (rel.includes(".config.") || rel.includes("config/") || rel === "tsconfig.json") return "config";
289335
- if (rel.includes("middleware")) return "middleware";
289336
- if (rel.includes("/api/") || rel.includes("route")) return "route-handler";
289337
- if (rel.includes("service") || rel.includes("Service")) return "service";
289338
- if (rel.includes("repositor") || rel.includes("Repositor")) return "repository";
289339
- if (rel.endsWith(".tsx") && !rel.includes("page.")) return "component";
289340
- if (rel.includes("/types") || rel.endsWith(".d.ts")) return "type";
289341
- if (rel.includes("util") || rel.includes("helper") || rel.includes("lib/")) return "util";
289342
- if (rel.includes("script") || rel.includes("bin/")) return "script";
289343
- if (rel.match(/^(src\/)?index\.|^(src\/)?main\.|^(src\/)?app\./)) return "entry";
289344
- return "unknown";
289427
+ /**
289428
+ * Remove stale embeddings for files no longer in the index.
289429
+ */
289430
+ pruneStaleEmbeddings() {
289431
+ const result = this.db.prepare(`
289432
+ DELETE FROM embeddings WHERE path NOT IN (SELECT relative_path FROM files)
289433
+ `).run();
289434
+ return result.changes;
289345
289435
  }
289346
- conventionAppliesToFile(area, file) {
289347
- switch (area) {
289348
- case "naming":
289349
- return true;
289350
- case "imports":
289351
- return file.relativePath.endsWith(".ts") || file.relativePath.endsWith(".tsx");
289352
- case "exports":
289353
- return file.exports.length > 0;
289354
- case "testing":
289355
- return file.relativePath.includes(".test.") || file.relativePath.includes(".spec.");
289356
- case "error-handling":
289357
- return !file.relativePath.includes(".test.");
289358
- case "types":
289359
- return file.relativePath.endsWith(".ts") || file.relativePath.endsWith(".tsx");
289360
- default:
289361
- return true;
289362
- }
289436
+ // ═══════════════════════════════════════════════════════════════════════════
289437
+ // QUERYING
289438
+ // ═══════════════════════════════════════════════════════════════════════════
289439
+ /**
289440
+ * Get all symbols in a specific file.
289441
+ */
289442
+ getSymbolsForFile(filePath) {
289443
+ return this.loadSymbolsWhere("file_path = ?", [filePath]);
289363
289444
  }
289364
- findRelatedFiles(file, role) {
289365
- const related = [];
289366
- const dir = path5.dirname(file.relativePath);
289367
- for (const other of this.data.files) {
289368
- if (other.path === file.path) continue;
289369
- if (path5.dirname(other.relativePath) === dir) {
289370
- related.push(other.relativePath);
289371
- }
289372
- }
289373
- if (related.length < 5) {
289374
- for (const other of this.data.files) {
289375
- if (other.path === file.path) continue;
289376
- if (related.includes(other.relativePath)) continue;
289377
- if (this.classifyRole(other) === role) {
289378
- related.push(other.relativePath);
289379
- if (related.length >= 8) break;
289380
- }
289381
- }
289382
- }
289383
- return related;
289445
+ /**
289446
+ * Search symbols by name pattern.
289447
+ */
289448
+ searchSymbols(namePattern, limit = 50) {
289449
+ return this.loadSymbolsWhere("name LIKE ?", [`%${namePattern}%`]).slice(0, limit);
289384
289450
  }
289385
- generateEditGuidance(file, role, layer, dependedOnBy, conventions) {
289386
- const guidance = [];
289387
- if (dependedOnBy.length > 10) {
289388
- guidance.push(`HIGH IMPACT: ${dependedOnBy.length} files depend on this. Changes have wide blast radius.`);
289389
- }
289390
- switch (role) {
289391
- case "route-handler":
289392
- guidance.push("Validate all inputs with schemas before processing.");
289393
- guidance.push("Return consistent response shapes ({ success, data } or { success, error }).");
289394
- guidance.push("Ensure authentication middleware is applied to protected endpoints.");
289395
- break;
289396
- case "service":
289397
- guidance.push("Keep business logic here, not in controllers/routes.");
289398
- guidance.push("Use dependency injection for testability.");
289399
- if (layer) guidance.push(`This is in the ${layer} layer \u2014 only import from lower layers.`);
289400
- break;
289401
- case "repository":
289402
- guidance.push("Only data access logic belongs here \u2014 no business rules.");
289403
- guidance.push("Return domain objects, not raw database rows.");
289404
- break;
289405
- case "component":
289406
- guidance.push("Keep components focused and composable.");
289407
- guidance.push("Extract complex logic to custom hooks.");
289408
- break;
289409
- case "middleware":
289410
- guidance.push("Middleware must call next() or return a response \u2014 never leave the request hanging.");
289411
- guidance.push("Keep middleware focused on a single concern.");
289412
- break;
289413
- case "test":
289414
- guidance.push("Follow Arrange-Act-Assert pattern.");
289415
- guidance.push("Test edge cases and error conditions, not just happy path.");
289416
- break;
289417
- }
289418
- for (const conv of conventions.slice(0, 3)) {
289419
- guidance.push(`Convention: ${conv}`);
289420
- }
289421
- return guidance;
289451
+ /**
289452
+ * Get files that import a given file.
289453
+ */
289454
+ getDependents(filePath) {
289455
+ const rows = this.db.prepare("SELECT DISTINCT file_path FROM imports WHERE resolved_path = ?").all(filePath);
289456
+ return rows.map((r) => r.file_path);
289457
+ }
289458
+ /**
289459
+ * Get files that a given file imports.
289460
+ */
289461
+ getDependencies(filePath) {
289462
+ const rows = this.db.prepare('SELECT DISTINCT resolved_path FROM imports WHERE file_path = ? AND resolved_path != ""').all(filePath);
289463
+ return rows.map((r) => r.resolved_path);
289422
289464
  }
289423
289465
  // ═══════════════════════════════════════════════════════════════════════════
289424
- // TASK PLAYBOOKS
289466
+ // CLEANUP
289425
289467
  // ═══════════════════════════════════════════════════════════════════════════
289426
- buildTaskPlaybooks() {
289427
- const fp = this.dna.fingerprint;
289428
- const playbooks = [];
289429
- playbooks.push({
289430
- taskType: "Bug Fix",
289431
- steps: [
289432
- "Reproduce the bug and understand the expected vs actual behavior",
289433
- "Identify the root cause file(s) using the dependency graph",
289434
- "Write a failing test that reproduces the bug",
289435
- "Apply the minimal fix at the root cause",
289436
- "Verify the fix passes the test and does not break existing tests",
289437
- "Check that the fix does not violate any architecture rules"
289438
- ],
289439
- mustRead: this.dna.hotspots.slice(0, 3).map((h) => h.file),
289440
- mustUpdate: ["The buggy file", "Related test file"],
289441
- mustVerify: ["All existing tests pass", "New regression test passes", "No new arch rule violations"],
289442
- stopConditions: ["Never modify tests to make them pass \u2014 fix the code", "Do not change public API signatures without discussion"]
289443
- });
289444
- if (fp.router) {
289445
- playbooks.push({
289446
- taskType: "Add API Endpoint",
289447
- steps: [
289448
- "Check existing routes in truthpack to avoid duplicates",
289449
- "Create the route handler following existing patterns",
289450
- "Add input validation using the project validator",
289451
- "Add authentication middleware if the route is protected",
289452
- "Write tests for success, validation failure, and auth failure",
289453
- "Update the truthpack (run vibecheck scan)"
289454
- ],
289455
- mustRead: ["truthpack/routes.json", ...this.dna.patterns.filter((p) => p.category === "api").map((p) => p.exemplar)],
289456
- mustUpdate: ["Route file", "Test file", "Truthpack"],
289457
- mustVerify: ["Route responds correctly", "Input validation works", "Auth is enforced", "Test passes"],
289458
- stopConditions: ["Do not create duplicate routes", "Do not hardcode mock data in handlers"]
289459
- });
289460
- }
289461
- if (fp.framework.includes("Next") || fp.framework.includes("React")) {
289462
- playbooks.push({
289463
- taskType: "Add UI Component",
289464
- steps: [
289465
- "Check if a similar component already exists",
289466
- "Create the component following existing naming and structure patterns",
289467
- "Add TypeScript props interface",
289468
- "Add unit test for the component",
289469
- "If using state, determine if it should be a client component"
289470
- ],
289471
- mustRead: this.dna.patterns.filter((p) => p.category === "ui" || p.category === "state").map((p) => p.exemplar),
289472
- mustUpdate: ["Component file", "Test file", "Parent component that uses it"],
289473
- mustVerify: ["Component renders correctly", "Props are typed", "Test passes"],
289474
- stopConditions: ['Do not use "any" type for props', 'Do not add useState in server components without "use client"']
289475
- });
289476
- }
289477
- playbooks.push({
289478
- taskType: "Refactor",
289479
- steps: [
289480
- "Identify all callers/dependents of the code being refactored",
289481
- "Ensure comprehensive tests exist before refactoring",
289482
- "Apply changes incrementally, testing after each step",
289483
- "Update all dependents to use the new API",
289484
- "Remove old code only after all dependents are migrated",
289485
- "Verify no architecture rules are violated"
289486
- ],
289487
- mustRead: ["Dependency graph for affected files"],
289488
- mustUpdate: ["Refactored file", "All dependent files", "Tests"],
289489
- mustVerify: ["All tests pass", "No new violations", "No regressions"],
289490
- stopConditions: ["Never break existing public APIs without migration path", "Do not refactor and add features in the same change"]
289491
- });
289492
- return playbooks;
289468
+ /**
289469
+ * Close the database connection.
289470
+ */
289471
+ close() {
289472
+ this.db.close();
289473
+ }
289474
+ /**
289475
+ * Wipe all data and rebuild schema.
289476
+ */
289477
+ reset() {
289478
+ this.db.exec("DROP TABLE IF EXISTS file_hashes");
289479
+ this.db.exec("DROP TABLE IF EXISTS files");
289480
+ this.db.exec("DROP TABLE IF EXISTS symbols");
289481
+ this.db.exec("DROP TABLE IF EXISTS imports");
289482
+ this.db.exec("DROP TABLE IF EXISTS call_edges");
289483
+ this.db.exec("DROP TABLE IF EXISTS routes");
289484
+ this.db.exec("DROP TABLE IF EXISTS services");
289485
+ this.db.exec("DROP TABLE IF EXISTS embeddings");
289486
+ this.db.exec("DROP TABLE IF EXISTS meta");
289487
+ this.initSchema();
289493
289488
  }
289494
289489
  // ═══════════════════════════════════════════════════════════════════════════
289495
- // VERIFICATION STEPS
289490
+ // PRIVATE — File Discovery
289496
289491
  // ═══════════════════════════════════════════════════════════════════════════
289497
- buildVerificationSteps() {
289498
- const fp = this.dna.fingerprint;
289499
- const steps = [];
289500
- if (fp.language === "TypeScript") {
289501
- steps.push({
289502
- trigger: "Any TypeScript file change",
289503
- checks: ["TypeScript compilation succeeds", "No new type errors introduced"],
289504
- commands: [fp.packageManager === "pnpm" ? "pnpm run check-types" : "npm run check-types"],
289505
- artifacts: []
289506
- });
289507
- }
289508
- if (fp.testRunner) {
289509
- steps.push({
289510
- trigger: "Any source file change",
289511
- checks: ["Related tests pass", "No test regressions"],
289512
- commands: [`${fp.packageManager} run test`],
289513
- artifacts: ["test-results.json"]
289514
- });
289492
+ async discoverFiles() {
289493
+ const files = await (0, import_fast_glob2.glob)(this.config.includePatterns, {
289494
+ cwd: this.rootPath,
289495
+ ignore: this.config.excludePatterns,
289496
+ absolute: false,
289497
+ dot: false,
289498
+ onlyFiles: true
289499
+ });
289500
+ const hashes = [];
289501
+ const limit = this.config.maxFiles;
289502
+ for (const relPath of files.slice(0, limit)) {
289503
+ const absPath = join4(this.rootPath, relPath);
289504
+ try {
289505
+ const fileStat = await stat(absPath);
289506
+ if (fileStat.size > this.config.maxFileSize) continue;
289507
+ const content = await readFile3(absPath, "utf-8");
289508
+ hashes.push({
289509
+ relativePath: relPath.replace(/\\/g, "/"),
289510
+ contentHash: hashContent2(content),
289511
+ sizeBytes: fileStat.size,
289512
+ modifiedMs: Math.floor(fileStat.mtimeMs)
289513
+ });
289514
+ } catch {
289515
+ }
289515
289516
  }
289516
- if (fp.router) {
289517
- steps.push({
289518
- trigger: "Route handler added or modified",
289519
- checks: ["Route responds with correct status", "Auth middleware is applied", "Input validation works"],
289520
- commands: ["vibecheck scan"],
289521
- artifacts: ["truthpack/routes.json"]
289517
+ return hashes;
289518
+ }
289519
+ getStoredHashes() {
289520
+ const rows = this.db.prepare("SELECT relative_path, content_hash, size_bytes, modified_ms FROM file_hashes").all();
289521
+ const map = /* @__PURE__ */ new Map();
289522
+ for (const row of rows) {
289523
+ map.set(row.relative_path, {
289524
+ relativePath: row.relative_path,
289525
+ contentHash: row.content_hash,
289526
+ sizeBytes: row.size_bytes,
289527
+ modifiedMs: row.modified_ms
289522
289528
  });
289523
289529
  }
289524
- steps.push({
289525
- trigger: "Any source file change",
289526
- checks: ["No new architecture rule violations", "No new circular dependencies"],
289527
- commands: ["vibecheck arch-rules"],
289528
- artifacts: []
289529
- });
289530
- return steps;
289530
+ return map;
289531
289531
  }
289532
289532
  // ═══════════════════════════════════════════════════════════════════════════
289533
- // RISK BRIEFING
289533
+ // PRIVATE — Storage
289534
289534
  // ═══════════════════════════════════════════════════════════════════════════
289535
- buildRiskBriefing() {
289536
- const criticalFiles = this.dna.hotspots.filter((h) => h.score > 30).slice(0, 10).map((h) => h.file);
289537
- const recentViolations = this.ruleResult?.violations.filter((v) => v.severity === "error").slice(0, 10) || [];
289538
- const securityConcerns = [];
289539
- for (const risk of this.dna.riskMap) {
289540
- if (risk.riskLevel === "critical") {
289541
- securityConcerns.push(`${risk.file}: ${risk.factors.join(", ")}`);
289542
- }
289543
- }
289544
- const testGaps = [];
289545
- const sourceFiles = this.data.files.filter(
289546
- (f) => !f.relativePath.includes(".test.") && !f.relativePath.includes(".spec.") && (f.relativePath.endsWith(".ts") || f.relativePath.endsWith(".tsx")) && !f.relativePath.includes("config") && !f.relativePath.includes(".d.ts")
289535
+ storeFiles(parsedFiles) {
289536
+ if (parsedFiles.length === 0) return;
289537
+ const insertHash = this.db.prepare(
289538
+ "INSERT OR REPLACE INTO file_hashes (relative_path, content_hash, size_bytes, modified_ms) VALUES (?, ?, ?, ?)"
289547
289539
  );
289548
- const testFiles = this.data.files.filter(
289549
- (f) => f.relativePath.includes(".test.") || f.relativePath.includes(".spec.")
289540
+ const insertFile = this.db.prepare(
289541
+ "INSERT OR REPLACE INTO files (id, path, relative_path, language, line_count, exports, content) VALUES (?, ?, ?, ?, ?, ?, ?)"
289550
289542
  );
289551
- const testedBases = new Set(testFiles.map(
289552
- (f) => path5.basename(f.relativePath).replace(/\.(test|spec)\.(ts|tsx|js|jsx)$/, "")
289553
- ));
289554
- for (const file of sourceFiles) {
289555
- const baseName = path5.basename(file.relativePath).replace(/\.(ts|tsx|js|jsx)$/, "");
289556
- if (!testedBases.has(baseName) && file.exports.length > 0) {
289557
- testGaps.push(`${file.relativePath} has exports but no test file`);
289558
- }
289559
- }
289560
- const driftWarnings = [];
289561
- for (const cycle of this.graph.cycles) {
289562
- driftWarnings.push(`Circular dependency: ${cycle.nodes.slice(0, 3).join(" \u2192 ")}${cycle.nodes.length > 3 ? "..." : ""}`);
289563
- }
289564
- return {
289565
- criticalFiles,
289566
- recentViolations,
289567
- securityConcerns: securityConcerns.slice(0, 5),
289568
- testGaps: testGaps.slice(0, 10),
289569
- driftWarnings: driftWarnings.slice(0, 5)
289570
- };
289571
- }
289572
- // ═══════════════════════════════════════════════════════════════════════════
289573
- // HELPERS
289574
- // ═══════════════════════════════════════════════════════════════════════════
289575
- toRelative(filePath) {
289576
- if (filePath.startsWith(this.rootPath)) {
289577
- return filePath.slice(this.rootPath.length + 1).replace(/\\/g, "/");
289578
- }
289579
- return filePath.replace(/\\/g, "/");
289580
- }
289581
- };
289582
- var ContextExplainer = class {
289583
- /**
289584
- * Generate a rich explanation for a file.
289585
- */
289586
- explain(filePath, ctx) {
289587
- const fc = ctx.fileContext;
289588
- const paragraphs = [];
289589
- const quickFacts = [];
289590
- const warnings = [];
289591
- const relatedFiles = [];
289592
- const overlays = [];
289593
- const role = fc?.role ?? "unknown";
289594
- const roleSummary = this.buildRoleSummary(filePath, role, ctx);
289595
- if (fc) {
289596
- const depCount = fc.dependedOnBy.length;
289597
- const depsOnCount = fc.dependsOn.length;
289598
- if (depCount > 0 || depsOnCount > 0) {
289599
- const parts2 = [];
289600
- if (depCount > 0) {
289601
- const critical = depCount > 10 ? " \u2014 changes here have wide blast radius" : "";
289602
- parts2.push(`${depCount} file${depCount > 1 ? "s" : ""} depend on this${critical}`);
289603
- }
289604
- if (depsOnCount > 0) {
289605
- parts2.push(`it imports from ${depsOnCount} file${depsOnCount > 1 ? "s" : ""}`);
289606
- }
289607
- paragraphs.push({
289608
- heading: "Dependencies",
289609
- text: parts2.join(". ") + ".",
289610
- importance: depCount > 10 ? "critical" : depCount > 5 ? "high" : "medium"
289611
- });
289612
- }
289613
- quickFacts.push({ label: "Role", value: role, icon: "layer" });
289614
- if (fc.layer) quickFacts.push({ label: "Layer", value: fc.layer, icon: "layer" });
289615
- quickFacts.push({ label: "Dependents", value: String(depCount), icon: "dependency" });
289616
- quickFacts.push({ label: "Dependencies", value: String(depsOnCount), icon: "dependency" });
289617
- if (fc.riskLevel === "critical" || fc.riskLevel === "high") {
289618
- quickFacts.push({ label: "Risk", value: fc.riskLevel.toUpperCase(), icon: "warning" });
289619
- warnings.push({
289620
- message: `This file is classified as ${fc.riskLevel} risk`,
289621
- severity: fc.riskLevel === "critical" ? "error" : "warning",
289622
- action: "Add comprehensive tests and review carefully before merging changes"
289623
- });
289624
- }
289625
- for (const dep of fc.dependedOnBy.slice(0, 5)) {
289626
- relatedFiles.push({
289627
- filePath: dep,
289628
- reason: `Imports from this file`,
289629
- relationship: "depended-by",
289630
- confidence: 0.9
289631
- });
289632
- }
289633
- overlays.push({
289634
- type: "code-lens",
289635
- line: 1,
289636
- text: `${depCount} dependent${depCount !== 1 ? "s" : ""} \xB7 ${depsOnCount} import${depsOnCount !== 1 ? "s" : ""} \xB7 ${role}`,
289637
- tooltip: `This ${role} file has ${depCount} files that depend on it and imports from ${depsOnCount} files`
289638
- });
289639
- }
289640
- if (fc && fc.conventions.length > 0) {
289641
- paragraphs.push({
289642
- heading: "Conventions",
289643
- text: `Follow these discovered conventions: ${fc.conventions.slice(0, 3).join("; ")}.`,
289644
- importance: "medium"
289645
- });
289646
- }
289647
- if (ctx.rules) {
289648
- const fileViolations = ctx.rules.violations.filter(
289649
- (v) => v.sourceSymbol.filePath.includes(shortName(filePath)) || v.targetSymbol && v.targetSymbol.filePath.includes(shortName(filePath))
289650
- );
289651
- if (fileViolations.length > 0) {
289652
- const errors = fileViolations.filter((v) => v.severity === "error");
289653
- const warns = fileViolations.filter((v) => v.severity === "warning");
289654
- paragraphs.push({
289655
- heading: "Architecture Violations",
289656
- text: `${errors.length} error${errors.length !== 1 ? "s" : ""} and ${warns.length} warning${warns.length !== 1 ? "s" : ""} from architecture rules.`,
289657
- importance: errors.length > 0 ? "critical" : "high"
289658
- });
289659
- for (const v of fileViolations.slice(0, 5)) {
289660
- warnings.push({
289661
- message: v.message,
289662
- severity: v.severity === "error" ? "error" : "warning",
289663
- action: v.suggestedFix ?? "Review and fix the violation",
289664
- ruleId: v.ruleId
289665
- });
289666
- overlays.push({
289667
- type: "diagnostic",
289668
- line: v.sourceSymbol.line,
289669
- text: v.message,
289670
- severity: v.severity === "error" ? "error" : "warning",
289671
- tooltip: v.suggestedFix
289672
- });
289673
- }
289674
- }
289675
- }
289676
- if (ctx.callGraph) {
289677
- const fileNodes = ctx.callGraph.nodes.filter((n) => n.filePath.includes(shortName(filePath)));
289678
- const hotFunctions = fileNodes.filter((n) => n.callerCount > 5);
289679
- if (hotFunctions.length > 0) {
289680
- paragraphs.push({
289681
- heading: "Hot Functions",
289682
- text: hotFunctions.map(
289683
- (f) => `\`${f.name}\` is called by ${f.callerCount} function${f.callerCount !== 1 ? "s" : ""}${f.calleeCount > 0 ? ` and calls ${f.calleeCount}` : ""}`
289684
- ).join(". ") + ".",
289685
- importance: "high"
289686
- });
289687
- for (const fn of hotFunctions) {
289688
- overlays.push({
289689
- type: "code-lens",
289690
- line: 0,
289691
- // Would need symbol line mapping
289692
- text: `${fn.callerCount} callers \xB7 ${fn.calleeCount} callees`,
289693
- tooltip: `Function ${fn.name} has ${fn.callerCount} callers and ${fn.calleeCount} callees`
289694
- });
289543
+ const insertSymbol = this.db.prepare(
289544
+ "INSERT OR REPLACE INTO symbols (id, name, kind, file_path, start_line, end_line, exported, async, params, branches, signature) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
289545
+ );
289546
+ const insertImport = this.db.prepare(
289547
+ "INSERT INTO imports (file_id, file_path, source_path, resolved_path, imported_symbols, is_type_only, is_dynamic, line) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
289548
+ );
289549
+ const insertCallEdge = this.db.prepare(
289550
+ "INSERT INTO call_edges (caller_id, callee_id, caller_name, callee_name, caller_file, callee_file) VALUES (?, ?, ?, ?, ?, ?)"
289551
+ );
289552
+ const txn = this.db.transaction(() => {
289553
+ for (const parsed of parsedFiles) {
289554
+ const { file, symbols, imports, callEdges, contentHash } = parsed;
289555
+ insertHash.run(file.relativePath, contentHash, 0, Date.now());
289556
+ insertFile.run(
289557
+ file.id,
289558
+ file.path,
289559
+ file.relativePath,
289560
+ file.language,
289561
+ file.lineCount,
289562
+ JSON.stringify(file.exports),
289563
+ this.config.storeContent ? file.content ?? null : null
289564
+ );
289565
+ for (const sym of symbols) {
289566
+ insertSymbol.run(
289567
+ sym.id,
289568
+ sym.name,
289569
+ sym.kind,
289570
+ sym.filePath,
289571
+ sym.startLine,
289572
+ sym.endLine,
289573
+ sym.exported ? 1 : 0,
289574
+ sym.async ? 1 : 0,
289575
+ sym.params ?? null,
289576
+ sym.branches ?? null,
289577
+ sym.signature ?? null
289578
+ );
289695
289579
  }
289696
- }
289697
- const deadInFile = ctx.callGraph.stats.deadFunctions.filter(
289698
- (d) => d.filePath.includes(shortName(filePath))
289699
- );
289700
- if (deadInFile.length > 0) {
289701
- for (const dead of deadInFile) {
289702
- warnings.push({
289703
- message: `\`${dead.name}\` appears to be dead code (exported but never called)`,
289704
- severity: "info",
289705
- action: "Verify this function is not called via dynamic dispatch or external consumers, then consider removing it"
289706
- });
289580
+ for (const imp of imports) {
289581
+ insertImport.run(
289582
+ imp.fileId,
289583
+ imp.filePath,
289584
+ imp.sourcePath,
289585
+ imp.resolvedPath,
289586
+ JSON.stringify(imp.importedSymbols),
289587
+ imp.isTypeOnly ? 1 : 0,
289588
+ imp.isDynamic ? 1 : 0,
289589
+ imp.line
289590
+ );
289707
289591
  }
289708
- }
289709
- }
289710
- if (ctx.temporal) {
289711
- const hotspot = ctx.temporal.changeHotspots.find((h) => filePath.includes(h.file) || h.file.includes(shortName(filePath)));
289712
- if (hotspot) {
289713
- const daysSince = Math.round((Date.now() - new Date(hotspot.lastChanged).getTime()) / 864e5);
289714
- paragraphs.push({
289715
- heading: "Recent Activity",
289716
- text: `Changed ${hotspot.commits} times in the last ${ctx.temporal.stats.analysisWindowDays} days by ${hotspot.authors} author${hotspot.authors !== 1 ? "s" : ""}. Last modified ${daysSince} day${daysSince !== 1 ? "s" : ""} ago.`,
289717
- importance: hotspot.commits > 10 ? "high" : "medium"
289718
- });
289719
- quickFacts.push({ label: "Recent commits", value: String(hotspot.commits), icon: "git" });
289720
- quickFacts.push({ label: "Last changed", value: `${daysSince}d ago`, icon: "git" });
289721
- quickFacts.push({ label: "Authors", value: String(hotspot.authors), icon: "git" });
289722
- }
289723
- const churn = ctx.temporal.churnFiles.find((c) => filePath.includes(c.file) || c.file.includes(shortName(filePath)));
289724
- if (churn && churn.severity !== "low") {
289725
- warnings.push({
289726
- message: churn.reason,
289727
- severity: churn.severity === "high" ? "warning" : "info",
289728
- action: "Consider whether this file needs refactoring to reduce change frequency"
289729
- });
289730
- }
289731
- const expertise = ctx.temporal.authorExpertise.find(
289732
- (e) => filePath.startsWith(e.area) || filePath.includes(e.area)
289733
- );
289734
- if (expertise && expertise.busFactor === 1) {
289735
- warnings.push({
289736
- message: `Bus factor of 1 \u2014 ${expertise.primaryAuthor} has made ${expertise.authors[0]?.percentage}% of changes to this area`,
289737
- severity: "info",
289738
- action: "Consider knowledge sharing or pair programming for this area"
289739
- });
289740
- }
289741
- }
289742
- if (ctx.learned) {
289743
- const coEdits = ctx.learned.coEdits.filter((p) => p.files[0].includes(shortName(filePath)) || p.files[1].includes(shortName(filePath))).slice(0, 3);
289744
- if (coEdits.length > 0) {
289745
- for (const pair of coEdits) {
289746
- const other = pair.files[0].includes(shortName(filePath)) ? pair.files[1] : pair.files[0];
289747
- relatedFiles.push({
289748
- filePath: other,
289749
- reason: `Often edited together (${pair.count} times)`,
289750
- relationship: "co-edited",
289751
- confidence: pair.weight
289752
- });
289592
+ for (const edge of callEdges) {
289593
+ insertCallEdge.run(
289594
+ edge.callerId,
289595
+ edge.calleeId,
289596
+ edge.callerName,
289597
+ edge.calleeName,
289598
+ edge.callerFile,
289599
+ edge.calleeFile
289600
+ );
289753
289601
  }
289754
289602
  }
289755
- }
289756
- if (fc && fc.editGuidance.length > 0) {
289757
- paragraphs.push({
289758
- heading: "Edit Guidance",
289759
- text: fc.editGuidance.join(" "),
289760
- importance: "medium"
289761
- });
289762
- }
289763
- return {
289764
- filePath,
289765
- roleSummary,
289766
- paragraphs,
289767
- quickFacts,
289768
- warnings,
289769
- relatedFiles,
289770
- overlays
289771
- };
289603
+ });
289604
+ txn();
289772
289605
  }
289773
- /**
289774
- * Generate a compact markdown explanation for AI agent consumption.
289775
- */
289776
- explainForAgent(filePath, ctx) {
289777
- const explanation = this.explain(filePath, ctx);
289778
- const lines = [];
289779
- lines.push(`## ${shortName(filePath)}: ${explanation.roleSummary}`);
289780
- lines.push("");
289781
- if (explanation.quickFacts.length > 0) {
289782
- lines.push(explanation.quickFacts.map((f) => `**${f.label}**: ${f.value}`).join(" \xB7 "));
289783
- lines.push("");
289784
- }
289785
- for (const p of explanation.paragraphs.filter((p2) => p2.importance === "critical" || p2.importance === "high")) {
289786
- lines.push(`### ${p.heading}`);
289787
- lines.push(p.text);
289788
- lines.push("");
289789
- }
289790
- if (explanation.warnings.length > 0) {
289791
- lines.push("### Warnings");
289792
- for (const w of explanation.warnings) {
289793
- const icon = w.severity === "error" ? "MUST FIX" : w.severity === "warning" ? "SHOULD FIX" : "NOTE";
289794
- lines.push(`- **[${icon}]** ${w.message} \u2014 ${w.action}`);
289795
- }
289796
- lines.push("");
289797
- }
289798
- if (explanation.relatedFiles.length > 0) {
289799
- lines.push("### Related Files");
289800
- for (const rf of explanation.relatedFiles.slice(0, 5)) {
289801
- lines.push(`- \`${rf.filePath}\` \u2014 ${rf.reason}`);
289606
+ removeFiles(relativePaths) {
289607
+ if (relativePaths.length === 0) return;
289608
+ const txn = this.db.transaction(() => {
289609
+ for (const relPath of relativePaths) {
289610
+ const absPath = join4(this.rootPath, relPath);
289611
+ this.db.prepare("DELETE FROM file_hashes WHERE relative_path = ?").run(relPath);
289612
+ this.db.prepare("DELETE FROM files WHERE relative_path = ?").run(relPath);
289613
+ this.db.prepare("DELETE FROM symbols WHERE file_path = ? OR file_path = ?").run(absPath, relPath);
289614
+ this.db.prepare("DELETE FROM imports WHERE file_path = ? OR file_path = ?").run(absPath, relPath);
289615
+ this.db.prepare("DELETE FROM call_edges WHERE caller_file = ? OR callee_file = ? OR caller_file = ? OR callee_file = ?").run(absPath, absPath, relPath, relPath);
289616
+ this.db.prepare("DELETE FROM embeddings WHERE path = ?").run(relPath);
289802
289617
  }
289803
- lines.push("");
289804
- }
289805
- return lines.join("\n");
289806
- }
289807
- /**
289808
- * Generate IDE overlay data for the VS Code extension to consume.
289809
- */
289810
- getIDEOverlays(filePath, ctx) {
289811
- const explanation = this.explain(filePath, ctx);
289812
- return explanation.overlays;
289813
- }
289814
- /**
289815
- * Generate a health report explanation.
289816
- */
289817
- explainHealth(health, dna) {
289818
- const lines = [];
289819
- const dims = health.dimensions;
289820
- lines.push(`## Codebase Health: ${health.overall}/100`);
289821
- lines.push("");
289822
- const entries = [
289823
- { name: "Architecture", score: dims.architecture, explain: this.explainArchScore(dims.architecture, dna) },
289824
- { name: "Test Coverage", score: dims.testCoverage, explain: this.explainTestScore(dims.testCoverage) },
289825
- { name: "Conventions", score: dims.conventions, explain: this.explainConventionScore(dims.conventions, dna) },
289826
- { name: "Dependencies", score: dims.dependencies, explain: this.explainDependencyScore(dims.dependencies, dna) },
289827
- { name: "Security", score: dims.security, explain: dims.security >= 80 ? "No critical security concerns detected" : "Critical risk areas identified" },
289828
- { name: "Complexity", score: dims.complexity, explain: dims.complexity >= 80 ? "Complexity is well-managed" : "High-complexity hotspots detected" }
289829
- ];
289830
- for (const entry of entries) {
289831
- const bar = this.renderBar(entry.score);
289832
- lines.push(`${bar} **${entry.name}**: ${entry.score}/100 \u2014 ${entry.explain}`);
289833
- }
289834
- return lines.join("\n");
289618
+ });
289619
+ txn();
289835
289620
  }
289836
289621
  // ═══════════════════════════════════════════════════════════════════════════
289837
- // PRIVATE
289622
+ // PRIVATE — Loading
289838
289623
  // ═══════════════════════════════════════════════════════════════════════════
289839
- buildRoleSummary(filePath, role, ctx) {
289840
- const parts2 = [];
289841
- switch (role) {
289842
- case "service":
289843
- parts2.push("Business logic service");
289844
- break;
289845
- case "route-handler":
289846
- parts2.push("API route handler");
289847
- break;
289848
- case "component":
289849
- parts2.push("UI component");
289850
- break;
289851
- case "repository":
289852
- parts2.push("Data access layer");
289853
- break;
289854
- case "middleware":
289855
- parts2.push("Request middleware");
289856
- break;
289857
- case "test":
289858
- parts2.push("Test file");
289859
- break;
289860
- case "config":
289861
- parts2.push("Configuration");
289862
- break;
289863
- case "type":
289864
- parts2.push("Type definitions");
289865
- break;
289866
- case "util":
289867
- parts2.push("Utility module");
289868
- break;
289869
- case "entry":
289870
- parts2.push("Entry point");
289871
- break;
289872
- default:
289873
- parts2.push("Source file");
289874
- }
289875
- if (ctx.fileContext) {
289876
- if (ctx.fileContext.layer) parts2.push(`in ${ctx.fileContext.layer} layer`);
289877
- if (ctx.fileContext.dependedOnBy.length > 10) parts2.push("(high-impact)");
289878
- }
289879
- return parts2.join(" ");
289624
+ loadFiles() {
289625
+ const rows = this.db.prepare("SELECT id, path, relative_path, language, line_count, exports, content FROM files").all();
289626
+ return rows.map((row) => ({
289627
+ id: row.id,
289628
+ path: row.path,
289629
+ relativePath: row.relative_path,
289630
+ language: row.language,
289631
+ lineCount: row.line_count,
289632
+ exports: JSON.parse(row.exports),
289633
+ content: row.content ?? void 0
289634
+ }));
289880
289635
  }
289881
- explainArchScore(score, dna) {
289882
- if (score >= 80) return `Strong architecture with ${dna.patterns.length} recognized patterns`;
289883
- if (score >= 50) return `Moderate architecture \u2014 ${dna.patterns.length} patterns detected, room to strengthen boundaries`;
289884
- return "Architecture needs attention \u2014 few recognized patterns or boundaries";
289636
+ loadSymbols() {
289637
+ const rows = this.db.prepare("SELECT id, name, kind, file_path, start_line, end_line, exported, async, params, branches FROM symbols").all();
289638
+ return rows.map((row) => ({
289639
+ id: row.id,
289640
+ name: row.name,
289641
+ kind: row.kind,
289642
+ filePath: row.file_path,
289643
+ startLine: row.start_line,
289644
+ endLine: row.end_line,
289645
+ exported: row.exported === 1,
289646
+ async: row.async === 1,
289647
+ params: row.params ?? void 0,
289648
+ branches: row.branches ?? void 0
289649
+ }));
289885
289650
  }
289886
- explainTestScore(score) {
289887
- if (score >= 80) return "Good test coverage across source files";
289888
- if (score >= 50) return "Moderate coverage \u2014 some source files lack tests";
289889
- return "Low test coverage \u2014 many exported modules have no test files";
289651
+ loadSymbolsWhere(where, params) {
289652
+ const rows = this.db.prepare(`SELECT id, name, kind, file_path, start_line, end_line, exported, async, params, branches FROM symbols WHERE ${where}`).all(...params);
289653
+ return rows.map((row) => ({
289654
+ id: row.id,
289655
+ name: row.name,
289656
+ kind: row.kind,
289657
+ filePath: row.file_path,
289658
+ startLine: row.start_line,
289659
+ endLine: row.end_line,
289660
+ exported: row.exported === 1,
289661
+ async: row.async === 1,
289662
+ params: row.params ?? void 0,
289663
+ branches: row.branches ?? void 0
289664
+ }));
289890
289665
  }
289891
- explainConventionScore(score, dna) {
289892
- const strong = dna.conventions.filter((c) => c.confidence > 0.6).length;
289893
- if (score >= 80) return `${strong} strong conventions enforced consistently`;
289894
- if (score >= 50) return `${strong} conventions detected but inconsistently applied`;
289895
- return "Few consistent conventions \u2014 codebase style varies across files";
289666
+ loadImports() {
289667
+ const rows = this.db.prepare("SELECT file_id, file_path, source_path, resolved_path, imported_symbols, is_type_only, is_dynamic, line FROM imports").all();
289668
+ return rows.map((row) => ({
289669
+ fileId: row.file_id,
289670
+ filePath: row.file_path,
289671
+ sourcePath: row.source_path,
289672
+ resolvedPath: row.resolved_path,
289673
+ importedSymbols: JSON.parse(row.imported_symbols),
289674
+ isTypeOnly: row.is_type_only === 1,
289675
+ isDynamic: row.is_dynamic === 1,
289676
+ line: row.line
289677
+ }));
289678
+ }
289679
+ loadCallEdges() {
289680
+ const rows = this.db.prepare("SELECT caller_id, callee_id, caller_name, callee_name, caller_file, callee_file FROM call_edges").all();
289681
+ return rows.map((row) => ({
289682
+ callerId: row.caller_id,
289683
+ calleeId: row.callee_id,
289684
+ callerName: row.caller_name,
289685
+ calleeName: row.callee_name,
289686
+ callerFile: row.caller_file,
289687
+ calleeFile: row.callee_file
289688
+ }));
289896
289689
  }
289897
- explainDependencyScore(score, dna) {
289898
- const circular = dna.boundaries.filter((b) => b.isCircular).length;
289899
- if (score >= 80) return "Clean dependency graph with no circular dependencies";
289900
- if (circular > 0) return `${circular} circular dependency${circular > 1 ? "ies" : "y"} detected \u2014 these increase coupling and make code harder to reason about`;
289901
- return "Dependency health needs improvement";
289690
+ loadRoutes() {
289691
+ const rows = this.db.prepare("SELECT path, method, handler, file, line, middleware, auth FROM routes").all();
289692
+ return rows.map((row) => ({
289693
+ path: row.path,
289694
+ method: row.method,
289695
+ handler: row.handler,
289696
+ file: row.file,
289697
+ line: row.line,
289698
+ middleware: JSON.parse(row.middleware),
289699
+ auth: row.auth === null ? void 0 : row.auth === 1
289700
+ }));
289902
289701
  }
289903
- renderBar(score) {
289904
- const filled = Math.round(score / 10);
289905
- return "\u2588".repeat(filled) + "\u2591".repeat(10 - filled);
289702
+ loadServices() {
289703
+ const rows = this.db.prepare("SELECT id, name, root_path FROM services").all();
289704
+ return rows.map((row) => ({
289705
+ id: row.id,
289706
+ name: row.name,
289707
+ rootPath: row.root_path
289708
+ }));
289906
289709
  }
289907
289710
  };
289908
- function shortName(filePath) {
289909
- const parts2 = filePath.split("/");
289910
- return parts2[parts2.length - 1] ?? filePath;
289711
+ function hashContent2(content) {
289712
+ return createHash2("sha256").update(content).digest("hex").slice(0, 16);
289911
289713
  }
289912
-
289913
- // ../context-engine/dist/chunk-HMKLYBWJ.js
289914
- var import_better_sqlite3 = __toESM(require_lib(), 1);
289915
- var import_fast_glob2 = __toESM(require_out4(), 1);
289916
- import { createHash as createHash2 } from "crypto";
289917
- import { readFile as readFile3, stat } from "fs/promises";
289918
- import { mkdirSync } from "fs";
289919
- import { join as join4 } from "path";
289920
- import { readFile as readFile22 } from "fs/promises";
289921
- import { accessSync } from "fs";
289922
- import { extname as extname2, resolve as resolve2, dirname as dirname6 } from "path";
289923
- import { createHash as createHash22 } from "crypto";
289924
- var SCHEMA_VERSION = 1;
289925
- var PersistentIndex = class {
289926
- db;
289927
- config;
289714
+ var EXT_LANG = {
289715
+ ".ts": "typescript",
289716
+ ".tsx": "typescript",
289717
+ ".js": "javascript",
289718
+ ".jsx": "javascript",
289719
+ ".mjs": "javascript",
289720
+ ".cjs": "javascript",
289721
+ ".py": "python",
289722
+ ".rs": "rust",
289723
+ ".go": "go",
289724
+ ".java": "java",
289725
+ ".c": "c",
289726
+ ".h": "c",
289727
+ ".cpp": "cpp",
289728
+ ".hpp": "cpp",
289729
+ ".rb": "ruby",
289730
+ ".swift": "swift",
289731
+ ".kt": "kotlin",
289732
+ ".lua": "lua",
289733
+ ".zig": "zig",
289734
+ ".cs": "csharp"
289735
+ };
289736
+ var TS_IMPORT_RE = /^import\s+(?:type\s+)?(?:\{[^}]*\}|[\w*]+(?:\s*,\s*\{[^}]*\})?)\s+from\s+['"]([^'"]+)['"]/gm;
289737
+ var TS_IMPORT_TYPE_RE = /^import\s+type\s+/;
289738
+ var TS_DYNAMIC_IMPORT_RE = /(?:import|require)\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
289739
+ var PY_IMPORT_RE = /^(?:from\s+([\w.]+)\s+import|import\s+([\w.]+))/gm;
289740
+ var GO_IMPORT_RE = /import\s+(?:\(\s*([\s\S]*?)\s*\)|"([^"]+)")/g;
289741
+ var DefaultFileParser = class {
289928
289742
  rootPath;
289929
- constructor(config) {
289930
- this.rootPath = config.rootPath;
289931
- this.config = {
289932
- rootPath: config.rootPath,
289933
- dbPath: config.dbPath ?? join4(config.rootPath, ".vibecheck", "index.db"),
289934
- includePatterns: config.includePatterns ?? ["**/*.{ts,tsx,js,jsx,py,rs,go,java,c,cpp,h,hpp,rb,swift,kt,lua,zig}"],
289935
- excludePatterns: config.excludePatterns ?? [
289936
- "**/node_modules/**",
289937
- "**/dist/**",
289938
- "**/build/**",
289939
- "**/.next/**",
289940
- "**/.git/**",
289941
- "**/coverage/**",
289942
- "**/.turbo/**",
289943
- "**/__pycache__/**",
289944
- "**/target/**",
289945
- "**/.mcp_data/**"
289946
- ],
289947
- maxFileSize: config.maxFileSize ?? 5e5,
289948
- maxFiles: config.maxFiles ?? 1e4,
289949
- storeContent: config.storeContent ?? true
289743
+ treeSitterParser = null;
289744
+ treeSitterLoaded = false;
289745
+ constructor(rootPath) {
289746
+ this.rootPath = rootPath;
289747
+ }
289748
+ async parseFile(absolutePath, relativePath) {
289749
+ const content = await readFile22(absolutePath, "utf-8");
289750
+ const lines = content.split("\n");
289751
+ const ext2 = extname2(absolutePath).toLowerCase();
289752
+ const language = EXT_LANG[ext2] ?? "unknown";
289753
+ const contentHash = createHash22("sha256").update(content).digest("hex").slice(0, 16);
289754
+ const exports2 = this.extractExports(content, language);
289755
+ const symbols = await this.extractSymbols(content, lines, absolutePath, relativePath, language);
289756
+ const imports = this.extractImports(content, absolutePath, relativePath, language);
289757
+ const callEdges = this.extractCallEdges(content, symbols, absolutePath);
289758
+ const fileId = `file:${relativePath}`;
289759
+ const file = {
289760
+ id: fileId,
289761
+ path: absolutePath,
289762
+ relativePath,
289763
+ language,
289764
+ lineCount: lines.length,
289765
+ exports: exports2,
289766
+ content
289950
289767
  };
289951
- const dbDir = join4(this.config.dbPath, "..");
289952
- mkdirSync(dbDir, { recursive: true });
289953
- this.db = new import_better_sqlite3.default(this.config.dbPath);
289954
- this.db.pragma("journal_mode = WAL");
289955
- this.db.pragma("synchronous = NORMAL");
289956
- this.db.pragma("cache_size = -64000");
289957
- this.initSchema();
289768
+ return { file, symbols, imports, callEdges, contentHash };
289958
289769
  }
289959
289770
  // ═══════════════════════════════════════════════════════════════════════════
289960
- // SCHEMA
289771
+ // EXPORTS
289961
289772
  // ═══════════════════════════════════════════════════════════════════════════
289962
- initSchema() {
289963
- const version3 = this.getSchemaVersion();
289964
- if (version3 === SCHEMA_VERSION) return;
289965
- this.db.exec(`
289966
- DROP TABLE IF EXISTS file_hashes;
289967
- DROP TABLE IF EXISTS files;
289968
- DROP TABLE IF EXISTS symbols;
289969
- DROP TABLE IF EXISTS imports;
289970
- DROP TABLE IF EXISTS call_edges;
289971
- DROP TABLE IF EXISTS routes;
289972
- DROP TABLE IF EXISTS services;
289973
- DROP TABLE IF EXISTS embeddings;
289974
- DROP TABLE IF EXISTS meta;
289975
-
289976
- CREATE TABLE meta (
289977
- key TEXT PRIMARY KEY,
289978
- value TEXT NOT NULL
289979
- );
289980
-
289981
- CREATE TABLE file_hashes (
289982
- relative_path TEXT PRIMARY KEY,
289983
- content_hash TEXT NOT NULL,
289984
- size_bytes INTEGER NOT NULL,
289985
- modified_ms INTEGER NOT NULL,
289986
- indexed_at INTEGER NOT NULL DEFAULT (unixepoch('now'))
289987
- );
289988
-
289989
- CREATE TABLE files (
289990
- id TEXT PRIMARY KEY,
289991
- path TEXT NOT NULL,
289992
- relative_path TEXT NOT NULL UNIQUE,
289993
- language TEXT NOT NULL,
289994
- line_count INTEGER NOT NULL,
289995
- exports TEXT NOT NULL DEFAULT '[]',
289996
- content TEXT
289997
- );
289998
-
289999
- CREATE TABLE symbols (
290000
- id TEXT PRIMARY KEY,
290001
- name TEXT NOT NULL,
290002
- kind TEXT NOT NULL,
290003
- file_path TEXT NOT NULL,
290004
- start_line INTEGER NOT NULL,
290005
- end_line INTEGER NOT NULL,
290006
- exported INTEGER NOT NULL DEFAULT 0,
290007
- async INTEGER NOT NULL DEFAULT 0,
290008
- params INTEGER,
290009
- branches INTEGER,
290010
- signature TEXT
290011
- );
290012
-
290013
- CREATE TABLE imports (
290014
- id INTEGER PRIMARY KEY AUTOINCREMENT,
290015
- file_id TEXT NOT NULL,
290016
- file_path TEXT NOT NULL,
290017
- source_path TEXT NOT NULL,
290018
- resolved_path TEXT NOT NULL DEFAULT '',
290019
- imported_symbols TEXT NOT NULL DEFAULT '[]',
290020
- is_type_only INTEGER NOT NULL DEFAULT 0,
290021
- is_dynamic INTEGER NOT NULL DEFAULT 0,
290022
- line INTEGER NOT NULL DEFAULT 0
290023
- );
290024
-
290025
- CREATE TABLE call_edges (
290026
- id INTEGER PRIMARY KEY AUTOINCREMENT,
290027
- caller_id TEXT NOT NULL,
290028
- callee_id TEXT NOT NULL,
290029
- caller_name TEXT NOT NULL,
290030
- callee_name TEXT NOT NULL,
290031
- caller_file TEXT NOT NULL,
290032
- callee_file TEXT NOT NULL
290033
- );
290034
-
290035
- CREATE TABLE routes (
290036
- id INTEGER PRIMARY KEY AUTOINCREMENT,
290037
- path TEXT NOT NULL,
290038
- method TEXT NOT NULL,
290039
- handler TEXT NOT NULL,
290040
- file TEXT NOT NULL,
290041
- line INTEGER NOT NULL DEFAULT 0,
290042
- middleware TEXT NOT NULL DEFAULT '[]',
290043
- auth INTEGER
290044
- );
290045
-
290046
- CREATE TABLE services (
290047
- id TEXT PRIMARY KEY,
290048
- name TEXT NOT NULL,
290049
- root_path TEXT NOT NULL DEFAULT ''
290050
- );
290051
-
290052
- CREATE TABLE embeddings (
290053
- path TEXT NOT NULL,
290054
- chunk_id TEXT NOT NULL,
290055
- chunk_type TEXT NOT NULL DEFAULT 'file',
290056
- content_hash TEXT NOT NULL,
290057
- vector BLOB NOT NULL,
290058
- metadata TEXT NOT NULL DEFAULT '{}',
290059
- PRIMARY KEY (path, chunk_id)
290060
- );
290061
-
290062
- -- Indexes for fast lookups
290063
- CREATE INDEX idx_symbols_file ON symbols(file_path);
290064
- CREATE INDEX idx_symbols_name ON symbols(name);
290065
- CREATE INDEX idx_symbols_kind ON symbols(kind);
290066
- CREATE INDEX idx_imports_file ON imports(file_path);
290067
- CREATE INDEX idx_imports_source ON imports(source_path);
290068
- CREATE INDEX idx_imports_resolved ON imports(resolved_path);
290069
- CREATE INDEX idx_call_edges_caller ON call_edges(caller_file);
290070
- CREATE INDEX idx_call_edges_callee ON call_edges(callee_file);
290071
- CREATE INDEX idx_embeddings_type ON embeddings(chunk_type);
290072
- `);
290073
- this.setMeta("schema_version", String(SCHEMA_VERSION));
290074
- this.setMeta("created_at", (/* @__PURE__ */ new Date()).toISOString());
289773
+ extractExports(content, language) {
289774
+ if (language !== "typescript" && language !== "javascript") return [];
289775
+ const exports2 = [];
289776
+ const exportRe = /export\s+(?:default\s+)?(?:async\s+)?(?:function|class|const|let|var|type|interface|enum)\s+(\w+)/g;
289777
+ for (const match2 of content.matchAll(exportRe)) {
289778
+ if (match2[1]) exports2.push(match2[1]);
289779
+ }
289780
+ if (/export\s+default\s/.test(content) && !exports2.includes("default")) {
289781
+ exports2.push("default");
289782
+ }
289783
+ const reExportRe = /export\s+\{([^}]+)\}\s+from/g;
289784
+ for (const match2 of content.matchAll(reExportRe)) {
289785
+ for (const sym of match2[1].split(",")) {
289786
+ const name2 = sym.trim().split(/\s+as\s+/).pop()?.trim();
289787
+ if (name2) exports2.push(name2);
289788
+ }
289789
+ }
289790
+ return [...new Set(exports2)];
290075
289791
  }
290076
- getSchemaVersion() {
289792
+ // ═══════════════════════════════════════════════════════════════════════════
289793
+ // SYMBOLS
289794
+ // ═══════════════════════════════════════════════════════════════════════════
289795
+ async extractSymbols(content, lines, absolutePath, relativePath, language) {
289796
+ const tsSymbols = await this.tryTreeSitter(content, absolutePath);
289797
+ if (tsSymbols) {
289798
+ return tsSymbols.map((sym, i2) => ({
289799
+ id: `sym:${relativePath}:${sym.name}:${sym.line}`,
289800
+ name: sym.name,
289801
+ kind: mapTreeSitterKind(sym.kind),
289802
+ filePath: absolutePath,
289803
+ startLine: sym.line,
289804
+ endLine: sym.endLine,
289805
+ exported: this.isExported(content, sym.name, language),
289806
+ async: sym.signature.includes("async "),
289807
+ params: this.countParams(sym.signature),
289808
+ branches: void 0,
289809
+ signature: sym.signature
289810
+ }));
289811
+ }
289812
+ return this.extractSymbolsRegex(content, lines, absolutePath, relativePath, language);
289813
+ }
289814
+ async tryTreeSitter(content, filePath) {
289815
+ if (!this.treeSitterLoaded) {
289816
+ this.treeSitterLoaded = true;
289817
+ try {
289818
+ const mod = await Promise.resolve().then(() => (init_tree_sitter_H5E7LKR4(), tree_sitter_H5E7LKR4_exports));
289819
+ this.treeSitterParser = mod.parseWithTreeSitter;
289820
+ } catch {
289821
+ this.treeSitterParser = null;
289822
+ }
289823
+ }
289824
+ if (!this.treeSitterParser) return null;
290077
289825
  try {
290078
- const row = this.db.prepare("SELECT value FROM meta WHERE key = ?").get("schema_version");
290079
- return row ? Number.parseInt(row.value, 10) : 0;
289826
+ const ext2 = extname2(filePath).toLowerCase();
289827
+ const symbols = await this.treeSitterParser(content, ext2);
289828
+ if (!symbols || symbols.length === 0) return null;
289829
+ return flattenCodeSymbols(symbols);
290080
289830
  } catch {
290081
- return 0;
289831
+ return null;
290082
289832
  }
290083
289833
  }
290084
- setMeta(key, value) {
290085
- this.db.prepare("INSERT OR REPLACE INTO meta (key, value) VALUES (?, ?)").run(key, value);
290086
- }
290087
- getMeta(key) {
290088
- const row = this.db.prepare("SELECT value FROM meta WHERE key = ?").get(key);
290089
- return row?.value ?? null;
289834
+ extractSymbolsRegex(content, lines, absolutePath, relativePath, language) {
289835
+ const symbols = [];
289836
+ if (language === "typescript" || language === "javascript") {
289837
+ const patterns = [
289838
+ { re: /^(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*(<[^>]*>)?\s*\(([^)]*)\)/gm, kind: "function" },
289839
+ { re: /^(?:export\s+)?(?:abstract\s+)?class\s+(\w+)/gm, kind: "class" },
289840
+ { re: /^(?:export\s+)?interface\s+(\w+)/gm, kind: "interface" },
289841
+ { re: /^(?:export\s+)?type\s+(\w+)\s*(?:<[^>]*>)?\s*=/gm, kind: "type" },
289842
+ { re: /^(?:export\s+)?(?:const\s+)?enum\s+(\w+)/gm, kind: "enum" },
289843
+ { re: /^(?:export\s+)?const\s+(\w+)\s*(?::\s*[^=]+)?\s*=\s*(?:async\s+)?\(/gm, kind: "function" }
289844
+ ];
289845
+ for (const { re, kind } of patterns) {
289846
+ for (const match2 of content.matchAll(re)) {
289847
+ const name2 = match2[1];
289848
+ if (!name2) continue;
289849
+ const line = content.slice(0, match2.index).split("\n").length;
289850
+ const endLine = this.findBlockEnd(lines, line - 1);
289851
+ symbols.push({
289852
+ id: `sym:${relativePath}:${name2}:${line}`,
289853
+ name: name2,
289854
+ kind,
289855
+ filePath: absolutePath,
289856
+ startLine: line,
289857
+ endLine,
289858
+ exported: this.isExported(content, name2, language),
289859
+ async: match2[0].includes("async"),
289860
+ params: this.countParams(match2[0]),
289861
+ branches: this.countBranches(lines, line - 1, endLine - 1),
289862
+ signature: match2[0].trim().replace(/\s*\{?\s*$/, "")
289863
+ });
289864
+ }
289865
+ }
289866
+ }
289867
+ if (language === "python") {
289868
+ const re = /^(?:async\s+)?(?:def|class)\s+(\w+)/gm;
289869
+ for (const match2 of content.matchAll(re)) {
289870
+ const name2 = match2[1];
289871
+ const kind = match2[0].includes("class") ? "class" : "function";
289872
+ const line = content.slice(0, match2.index).split("\n").length;
289873
+ symbols.push({
289874
+ id: `sym:${relativePath}:${name2}:${line}`,
289875
+ name: name2,
289876
+ kind,
289877
+ filePath: absolutePath,
289878
+ startLine: line,
289879
+ endLine: line + 10,
289880
+ exported: true,
289881
+ async: match2[0].includes("async"),
289882
+ params: this.countParams(match2[0]),
289883
+ branches: void 0,
289884
+ signature: match2[0].trim()
289885
+ });
289886
+ }
289887
+ }
289888
+ return symbols;
290090
289889
  }
290091
289890
  // ═══════════════════════════════════════════════════════════════════════════
290092
- // INCREMENTAL INDEXING
289891
+ // IMPORTS
290093
289892
  // ═══════════════════════════════════════════════════════════════════════════
290094
- /**
290095
- * Discover files, diff against stored hashes, return only changed files.
290096
- */
290097
- async diffFiles() {
290098
- const discoveredFiles = await this.discoverFiles();
290099
- const storedHashes = this.getStoredHashes();
290100
- const changed = [];
290101
- const unchanged = [];
290102
- const currentPaths = /* @__PURE__ */ new Set();
290103
- for (const file of discoveredFiles) {
290104
- currentPaths.add(file.relativePath);
290105
- const stored = storedHashes.get(file.relativePath);
290106
- if (!stored || stored.contentHash !== file.contentHash) {
290107
- changed.push(file.relativePath);
290108
- } else {
290109
- unchanged.push(file.relativePath);
289893
+ extractImports(content, absolutePath, relativePath, language) {
289894
+ const imports = [];
289895
+ const fileId = `file:${relativePath}`;
289896
+ if (language === "typescript" || language === "javascript") {
289897
+ for (const match2 of content.matchAll(TS_IMPORT_RE)) {
289898
+ const sourcePath = match2[1];
289899
+ const line = content.slice(0, match2.index).split("\n").length;
289900
+ const isTypeOnly = TS_IMPORT_TYPE_RE.test(match2[0]);
289901
+ const importedSymbols = this.extractImportedSymbols(match2[0]);
289902
+ const resolvedPath = this.resolveImportPath(sourcePath, absolutePath);
289903
+ imports.push({
289904
+ fileId,
289905
+ filePath: absolutePath,
289906
+ sourcePath,
289907
+ resolvedPath,
289908
+ importedSymbols,
289909
+ isTypeOnly,
289910
+ isDynamic: false,
289911
+ line
289912
+ });
290110
289913
  }
290111
- }
290112
- const deleted = [];
290113
- for (const storedPath of storedHashes.keys()) {
290114
- if (!currentPaths.has(storedPath)) {
290115
- deleted.push(storedPath);
289914
+ for (const match2 of content.matchAll(TS_DYNAMIC_IMPORT_RE)) {
289915
+ const sourcePath = match2[1];
289916
+ const line = content.slice(0, match2.index).split("\n").length;
289917
+ imports.push({
289918
+ fileId,
289919
+ filePath: absolutePath,
289920
+ sourcePath,
289921
+ resolvedPath: this.resolveImportPath(sourcePath, absolutePath),
289922
+ importedSymbols: [],
289923
+ isTypeOnly: false,
289924
+ isDynamic: true,
289925
+ line
289926
+ });
290116
289927
  }
290117
289928
  }
290118
- return { changed, deleted, unchanged };
290119
- }
290120
- /**
290121
- * Full reindex — scan all files and store data.
290122
- * Returns parsed CodebaseData + stats.
290123
- */
290124
- async reindex(parser4) {
290125
- const startMs = Date.now();
290126
- const { changed, deleted, unchanged } = await this.diffFiles();
290127
- if (deleted.length > 0) {
290128
- this.removeFiles(deleted);
289929
+ if (language === "python") {
289930
+ for (const match2 of content.matchAll(PY_IMPORT_RE)) {
289931
+ const sourcePath = match2[1] || match2[2];
289932
+ if (!sourcePath) continue;
289933
+ const line = content.slice(0, match2.index).split("\n").length;
289934
+ imports.push({
289935
+ fileId,
289936
+ filePath: absolutePath,
289937
+ sourcePath,
289938
+ resolvedPath: "",
289939
+ importedSymbols: [],
289940
+ isTypeOnly: false,
289941
+ isDynamic: false,
289942
+ line
289943
+ });
289944
+ }
290129
289945
  }
290130
- const parsedFiles = [];
290131
- for (const relPath of changed) {
290132
- const absPath = join4(this.rootPath, relPath);
290133
- try {
290134
- const parsed = await parser4.parseFile(absPath, relPath);
290135
- parsedFiles.push(parsed);
290136
- } catch {
289946
+ if (language === "go") {
289947
+ for (const match2 of content.matchAll(GO_IMPORT_RE)) {
289948
+ const block = match2[1] || match2[2];
289949
+ if (!block) continue;
289950
+ const paths = block.match(/"([^"]+)"/g) ?? [];
289951
+ for (const p of paths) {
289952
+ const sourcePath = p.replace(/"/g, "");
289953
+ imports.push({
289954
+ fileId,
289955
+ filePath: absolutePath,
289956
+ sourcePath,
289957
+ resolvedPath: "",
289958
+ importedSymbols: [],
289959
+ isTypeOnly: false,
289960
+ isDynamic: false,
289961
+ line: 0
289962
+ });
289963
+ }
290137
289964
  }
290138
289965
  }
290139
- this.storeFiles(parsedFiles);
290140
- const data = this.loadCodebaseData();
290141
- const stats = {
290142
- totalFiles: data.files.length,
290143
- totalSymbols: data.symbols.length,
290144
- totalImports: data.imports.length,
290145
- indexedAt: (/* @__PURE__ */ new Date()).toISOString(),
290146
- reindexedFiles: changed.length,
290147
- skippedFiles: unchanged.length,
290148
- durationMs: Date.now() - startMs
290149
- };
290150
- this.setMeta("last_index_at", stats.indexedAt);
290151
- this.setMeta("last_index_stats", JSON.stringify(stats));
290152
- return { data, stats };
289966
+ return imports;
290153
289967
  }
290154
- /**
290155
- * Incremental update — only reindex specific files.
290156
- */
290157
- async reindexFiles(relativePaths, parser4) {
290158
- const startMs = Date.now();
290159
- this.removeFiles(relativePaths);
290160
- const parsedFiles = [];
290161
- for (const relPath of relativePaths) {
290162
- const absPath = join4(this.rootPath, relPath);
289968
+ extractImportedSymbols(importLine) {
289969
+ const braceMatch = importLine.match(/\{([^}]+)\}/);
289970
+ if (!braceMatch) {
289971
+ const defaultMatch = importLine.match(/import\s+(?:type\s+)?(\w+)\s+from/);
289972
+ return defaultMatch?.[1] ? [defaultMatch[1]] : [];
289973
+ }
289974
+ return braceMatch[1].split(",").map((s) => {
289975
+ const parts2 = s.trim().split(/\s+as\s+/);
289976
+ return parts2[parts2.length - 1].trim();
289977
+ }).filter(Boolean);
289978
+ }
289979
+ resolveImportPath(sourcePath, fromFile) {
289980
+ if (!sourcePath.startsWith(".")) return sourcePath;
289981
+ const dir = dirname5(fromFile);
289982
+ const resolved = resolve2(dir, sourcePath);
289983
+ const extensions = [".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.tsx", "/index.js"];
289984
+ for (const ext2 of extensions) {
289985
+ const candidate = resolved + ext2;
290163
289986
  try {
290164
- const parsed = await parser4.parseFile(absPath, relPath);
290165
- parsedFiles.push(parsed);
289987
+ accessSync(candidate);
289988
+ return candidate;
290166
289989
  } catch {
290167
289990
  }
290168
289991
  }
290169
- this.storeFiles(parsedFiles);
290170
- const totalFiles = this.db.prepare("SELECT COUNT(*) as cnt FROM files").get();
290171
- const totalSymbols = this.db.prepare("SELECT COUNT(*) as cnt FROM symbols").get();
290172
- const totalImports = this.db.prepare("SELECT COUNT(*) as cnt FROM imports").get();
290173
- return {
290174
- totalFiles: totalFiles.cnt,
290175
- totalSymbols: totalSymbols.cnt,
290176
- totalImports: totalImports.cnt,
290177
- indexedAt: (/* @__PURE__ */ new Date()).toISOString(),
290178
- reindexedFiles: parsedFiles.length,
290179
- skippedFiles: 0,
290180
- durationMs: Date.now() - startMs
290181
- };
289992
+ return resolved;
290182
289993
  }
290183
289994
  // ═══════════════════════════════════════════════════════════════════════════
290184
- // DATA LOADING (warm start)
289995
+ // CALL EDGES (basic extraction)
290185
289996
  // ═══════════════════════════════════════════════════════════════════════════
290186
- /**
290187
- * Load full CodebaseData from the persistent store.
290188
- * This is the warm-start path sub-second for indexed repos.
290189
- */
290190
- loadCodebaseData() {
290191
- const files = this.loadFiles();
290192
- const symbols = this.loadSymbols();
290193
- const imports = this.loadImports();
290194
- const callEdges = this.loadCallEdges();
290195
- const routes = this.loadRoutes();
290196
- const services = this.loadServices();
290197
- return { files, symbols, imports, callEdges, routes, services };
290198
- }
290199
- /**
290200
- * Check if the index exists and has data.
290201
- */
290202
- isPopulated() {
290203
- try {
290204
- const row = this.db.prepare("SELECT COUNT(*) as cnt FROM files").get();
290205
- return row.cnt > 0;
290206
- } catch {
290207
- return false;
290208
- }
290209
- }
290210
- /**
290211
- * Get the last index timestamp.
290212
- */
290213
- getLastIndexedAt() {
290214
- return this.getMeta("last_index_at");
290215
- }
290216
- /**
290217
- * Get the last index stats.
290218
- */
290219
- getLastIndexStats() {
290220
- const raw = this.getMeta("last_index_stats");
290221
- if (!raw) return null;
290222
- try {
290223
- return JSON.parse(raw);
290224
- } catch {
290225
- return null;
289997
+ extractCallEdges(content, symbols, filePath) {
289998
+ const edges = [];
289999
+ const functionNames = new Set(symbols.filter((s) => s.kind === "function" || s.kind === "method").map((s) => s.name));
290000
+ for (const caller of symbols) {
290001
+ if (caller.kind !== "function" && caller.kind !== "method") continue;
290002
+ const body2 = content.split("\n").slice(caller.startLine - 1, caller.endLine).join("\n");
290003
+ for (const calleeName of functionNames) {
290004
+ if (calleeName === caller.name) continue;
290005
+ const callRe = new RegExp(`\\b${calleeName}\\s*\\(`, "g");
290006
+ if (callRe.test(body2)) {
290007
+ const callee = symbols.find((s) => s.name === calleeName);
290008
+ if (callee) {
290009
+ edges.push({
290010
+ callerId: caller.id,
290011
+ calleeId: callee.id,
290012
+ callerName: caller.name,
290013
+ calleeName: callee.name,
290014
+ callerFile: filePath,
290015
+ calleeFile: filePath
290016
+ });
290017
+ }
290018
+ }
290019
+ }
290226
290020
  }
290021
+ return edges;
290227
290022
  }
290228
290023
  // ═══════════════════════════════════════════════════════════════════════════
290229
- // EMBEDDING STORAGE
290024
+ // HELPERS
290230
290025
  // ═══════════════════════════════════════════════════════════════════════════
290231
- /**
290232
- * Store a chunk embedding (file-level, function-level, etc.)
290233
- */
290234
- storeEmbedding(path10, chunkId, chunkType, contentHash, vector, metadata2) {
290235
- const vectorBuf = Buffer.from(new Float32Array(vector).buffer);
290236
- this.db.prepare(`
290237
- INSERT OR REPLACE INTO embeddings (path, chunk_id, chunk_type, content_hash, vector, metadata)
290238
- VALUES (?, ?, ?, ?, ?, ?)
290239
- `).run(path10, chunkId, chunkType, contentHash, vectorBuf, JSON.stringify(metadata2 ?? {}));
290240
- }
290241
- /**
290242
- * Load embedding for a specific chunk.
290243
- */
290244
- loadEmbedding(path10, chunkId) {
290245
- const row = this.db.prepare("SELECT vector, content_hash, metadata FROM embeddings WHERE path = ? AND chunk_id = ?").get(path10, chunkId);
290246
- if (!row) return null;
290247
- return {
290248
- vector: Array.from(new Float32Array(row.vector.buffer, row.vector.byteOffset, row.vector.byteLength / 4)),
290249
- contentHash: row.content_hash,
290250
- metadata: JSON.parse(row.metadata)
290251
- };
290026
+ isExported(content, name2, language) {
290027
+ if (language === "python") return true;
290028
+ const re = new RegExp(`export\\s+(?:default\\s+)?(?:async\\s+)?(?:function|class|const|let|var|type|interface|enum)\\s+${name2}\\b`);
290029
+ if (re.test(content)) return true;
290030
+ const reExport = new RegExp(`export\\s+\\{[^}]*\\b${name2}\\b[^}]*\\}`);
290031
+ return reExport.test(content);
290252
290032
  }
290253
- /**
290254
- * Load all embeddings of a given type for vector search.
290255
- */
290256
- loadEmbeddingsByType(chunkType) {
290257
- const rows = this.db.prepare("SELECT path, chunk_id, vector, content_hash, metadata FROM embeddings WHERE chunk_type = ?").all(chunkType);
290258
- return rows.map((row) => ({
290259
- path: row.path,
290260
- chunkId: row.chunk_id,
290261
- vector: Array.from(new Float32Array(row.vector.buffer, row.vector.byteOffset, row.vector.byteLength / 4)),
290262
- contentHash: row.content_hash,
290263
- metadata: JSON.parse(row.metadata)
290264
- }));
290033
+ countParams(signature) {
290034
+ const parenMatch = signature.match(/\(([^)]*)\)/);
290035
+ if (!parenMatch || !parenMatch[1].trim()) return 0;
290036
+ return parenMatch[1].split(",").length;
290265
290037
  }
290266
- /**
290267
- * Remove stale embeddings for files no longer in the index.
290268
- */
290269
- pruneStaleEmbeddings() {
290270
- const result = this.db.prepare(`
290271
- DELETE FROM embeddings WHERE path NOT IN (SELECT relative_path FROM files)
290272
- `).run();
290273
- return result.changes;
290038
+ countBranches(lines, startIdx, endIdx) {
290039
+ let branches = 0;
290040
+ const branchRe = /\b(if|else if|case|for|while|catch|&&|\|\||\?\?)\b/g;
290041
+ for (let i2 = startIdx; i2 < Math.min(endIdx, lines.length); i2++) {
290042
+ const matches = lines[i2].match(branchRe);
290043
+ if (matches) branches += matches.length;
290044
+ }
290045
+ return branches;
290274
290046
  }
290275
- // ═══════════════════════════════════════════════════════════════════════════
290276
- // QUERYING
290277
- // ═══════════════════════════════════════════════════════════════════════════
290278
- /**
290279
- * Get all symbols in a specific file.
290280
- */
290281
- getSymbolsForFile(filePath) {
290282
- return this.loadSymbolsWhere("file_path = ?", [filePath]);
290047
+ findBlockEnd(lines, startIdx) {
290048
+ let depth = 0;
290049
+ let seenOpen = false;
290050
+ for (let i2 = startIdx; i2 < lines.length; i2++) {
290051
+ for (const ch of lines[i2]) {
290052
+ if (ch === "{") {
290053
+ depth++;
290054
+ seenOpen = true;
290055
+ } else if (ch === "}" && seenOpen) {
290056
+ depth--;
290057
+ if (depth <= 0) return i2 + 1;
290058
+ }
290059
+ }
290060
+ }
290061
+ return startIdx + 1;
290283
290062
  }
290284
- /**
290285
- * Search symbols by name pattern.
290286
- */
290287
- searchSymbols(namePattern, limit = 50) {
290288
- return this.loadSymbolsWhere("name LIKE ?", [`%${namePattern}%`]).slice(0, limit);
290063
+ };
290064
+ function mapTreeSitterKind(kind) {
290065
+ const map = {
290066
+ function: "function",
290067
+ method: "method",
290068
+ class: "class",
290069
+ interface: "interface",
290070
+ type: "type",
290071
+ enum: "enum",
290072
+ const: "variable",
290073
+ variable: "variable",
290074
+ struct: "class",
290075
+ trait: "interface",
290076
+ export: "variable"
290077
+ };
290078
+ return map[kind] ?? "function";
290079
+ }
290080
+ function flattenCodeSymbols(symbols) {
290081
+ const flat = [];
290082
+ for (const sym of symbols) {
290083
+ flat.push({ name: sym.name, kind: sym.kind, line: sym.line, endLine: sym.endLine, signature: sym.signature });
290084
+ if (sym.children && Array.isArray(sym.children)) {
290085
+ flat.push(...flattenCodeSymbols(sym.children));
290086
+ }
290289
290087
  }
290290
- /**
290291
- * Get files that import a given file.
290292
- */
290293
- getDependents(filePath) {
290294
- const rows = this.db.prepare("SELECT DISTINCT file_path FROM imports WHERE resolved_path = ?").all(filePath);
290295
- return rows.map((r) => r.file_path);
290088
+ return flat;
290089
+ }
290090
+
290091
+ // ../context-engine/dist/chunk-INBCP46U.js
290092
+ import * as path5 from "path";
290093
+ var ContextSynthesizer = class {
290094
+ rootPath;
290095
+ data;
290096
+ dna;
290097
+ graph;
290098
+ ruleResult;
290099
+ constructor(rootPath, data, dna, graph, ruleResult) {
290100
+ this.rootPath = rootPath;
290101
+ this.data = data;
290102
+ this.dna = dna;
290103
+ this.graph = graph;
290104
+ this.ruleResult = ruleResult || null;
290296
290105
  }
290297
290106
  /**
290298
- * Get files that a given file imports.
290107
+ * Synthesize the full context the complete brain dump for AI agents.
290299
290108
  */
290300
- getDependencies(filePath) {
290301
- const rows = this.db.prepare('SELECT DISTINCT resolved_path FROM imports WHERE file_path = ? AND resolved_path != ""').all(filePath);
290302
- return rows.map((r) => r.resolved_path);
290109
+ synthesize() {
290110
+ const projectIdentity = this.buildProjectIdentity();
290111
+ const archRules = this.buildArchRuleSummary();
290112
+ const activeViolations = this.ruleResult?.violations || [];
290113
+ const codebaseDNA = this.buildDNASummary();
290114
+ const fileContexts = this.buildFileContexts();
290115
+ const taskPlaybooks = this.buildTaskPlaybooks();
290116
+ const verificationSteps = this.buildVerificationSteps();
290117
+ const riskBriefing = this.buildRiskBriefing();
290118
+ return {
290119
+ version: "2.0.0",
290120
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
290121
+ projectIdentity,
290122
+ architecturalRules: archRules,
290123
+ activeViolations,
290124
+ codebaseDNA,
290125
+ fileContexts,
290126
+ taskPlaybooks,
290127
+ verificationSteps,
290128
+ riskBriefing
290129
+ };
290303
290130
  }
290304
- // ═══════════════════════════════════════════════════════════════════════════
290305
- // CLEANUP
290306
- // ═══════════════════════════════════════════════════════════════════════════
290307
290131
  /**
290308
- * Close the database connection.
290132
+ * Generate context for a specific file — what an AI agent needs to know
290133
+ * before editing this file.
290309
290134
  */
290310
- close() {
290311
- this.db.close();
290135
+ synthesizeForFile(filePath) {
290136
+ const rel = this.toRelative(filePath);
290137
+ const file = this.data.files.find((f) => f.relativePath === rel || f.path === filePath);
290138
+ if (!file) return null;
290139
+ return this.buildSingleFileContext(file);
290312
290140
  }
290313
290141
  /**
290314
- * Wipe all data and rebuild schema.
290142
+ * Generate a compact markdown context document for IDE rules.
290143
+ * This replaces the old static rule generation with intelligence-driven context.
290315
290144
  */
290316
- reset() {
290317
- this.db.exec("DROP TABLE IF EXISTS file_hashes");
290318
- this.db.exec("DROP TABLE IF EXISTS files");
290319
- this.db.exec("DROP TABLE IF EXISTS symbols");
290320
- this.db.exec("DROP TABLE IF EXISTS imports");
290321
- this.db.exec("DROP TABLE IF EXISTS call_edges");
290322
- this.db.exec("DROP TABLE IF EXISTS routes");
290323
- this.db.exec("DROP TABLE IF EXISTS services");
290324
- this.db.exec("DROP TABLE IF EXISTS embeddings");
290325
- this.db.exec("DROP TABLE IF EXISTS meta");
290326
- this.initSchema();
290327
- }
290328
- // ═══════════════════════════════════════════════════════════════════════════
290329
- // PRIVATE File Discovery
290330
- // ═══════════════════════════════════════════════════════════════════════════
290331
- async discoverFiles() {
290332
- const files = await (0, import_fast_glob2.glob)(this.config.includePatterns, {
290333
- cwd: this.rootPath,
290334
- ignore: this.config.excludePatterns,
290335
- absolute: false,
290336
- dot: false,
290337
- onlyFiles: true
290338
- });
290339
- const hashes = [];
290340
- const limit = this.config.maxFiles;
290341
- for (const relPath of files.slice(0, limit)) {
290342
- const absPath = join4(this.rootPath, relPath);
290343
- try {
290344
- const fileStat = await stat(absPath);
290345
- if (fileStat.size > this.config.maxFileSize) continue;
290346
- const content = await readFile3(absPath, "utf-8");
290347
- hashes.push({
290348
- relativePath: relPath.replace(/\\/g, "/"),
290349
- contentHash: hashContent2(content),
290350
- sizeBytes: fileStat.size,
290351
- modifiedMs: Math.floor(fileStat.mtimeMs)
290352
- });
290353
- } catch {
290145
+ generateContextDocument() {
290146
+ const ctx = this.synthesize();
290147
+ const lines = [];
290148
+ lines.push(`# ${ctx.projectIdentity.name} \u2014 AI Context`);
290149
+ lines.push(`<!-- Generated by @repo/context-engine at ${ctx.generatedAt} -->`);
290150
+ lines.push("");
290151
+ lines.push("## Project Identity");
290152
+ lines.push(`- **Stack**: ${ctx.projectIdentity.stack}`);
290153
+ lines.push(`- **Architecture**: ${ctx.projectIdentity.architecture}`);
290154
+ if (ctx.projectIdentity.keyPatterns.length > 0) {
290155
+ lines.push(`- **Key Patterns**: ${ctx.projectIdentity.keyPatterns.join(", ")}`);
290156
+ }
290157
+ lines.push("");
290158
+ if (ctx.codebaseDNA.conventions.length > 0) {
290159
+ lines.push("## Conventions (Auto-Discovered)");
290160
+ for (const conv of ctx.codebaseDNA.conventions) {
290161
+ lines.push(`- ${conv}`);
290354
290162
  }
290163
+ lines.push("");
290355
290164
  }
290356
- return hashes;
290357
- }
290358
- getStoredHashes() {
290359
- const rows = this.db.prepare("SELECT relative_path, content_hash, size_bytes, modified_ms FROM file_hashes").all();
290360
- const map = /* @__PURE__ */ new Map();
290361
- for (const row of rows) {
290362
- map.set(row.relative_path, {
290363
- relativePath: row.relative_path,
290364
- contentHash: row.content_hash,
290365
- sizeBytes: row.size_bytes,
290366
- modifiedMs: row.modified_ms
290367
- });
290165
+ if (ctx.architecturalRules.length > 0) {
290166
+ lines.push("## Architecture Rules");
290167
+ for (const rule of ctx.architecturalRules) {
290168
+ const icon = rule.severity === "error" ? "MUST" : rule.severity === "warning" ? "SHOULD" : "MAY";
290169
+ lines.push(`- **[${icon}]** ${rule.name}: ${rule.description}`);
290170
+ if (rule.violationCount > 0) {
290171
+ lines.push(` - ${rule.violationCount} active violations`);
290172
+ }
290173
+ }
290174
+ lines.push("");
290368
290175
  }
290369
- return map;
290370
- }
290371
- // ═══════════════════════════════════════════════════════════════════════════
290372
- // PRIVATE — Storage
290373
- // ═══════════════════════════════════════════════════════════════════════════
290374
- storeFiles(parsedFiles) {
290375
- if (parsedFiles.length === 0) return;
290376
- const insertHash = this.db.prepare(
290377
- "INSERT OR REPLACE INTO file_hashes (relative_path, content_hash, size_bytes, modified_ms) VALUES (?, ?, ?, ?)"
290378
- );
290379
- const insertFile = this.db.prepare(
290380
- "INSERT OR REPLACE INTO files (id, path, relative_path, language, line_count, exports, content) VALUES (?, ?, ?, ?, ?, ?, ?)"
290381
- );
290382
- const insertSymbol = this.db.prepare(
290383
- "INSERT OR REPLACE INTO symbols (id, name, kind, file_path, start_line, end_line, exported, async, params, branches, signature) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
290384
- );
290385
- const insertImport = this.db.prepare(
290386
- "INSERT INTO imports (file_id, file_path, source_path, resolved_path, imported_symbols, is_type_only, is_dynamic, line) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
290387
- );
290388
- const insertCallEdge = this.db.prepare(
290389
- "INSERT INTO call_edges (caller_id, callee_id, caller_name, callee_name, caller_file, callee_file) VALUES (?, ?, ?, ?, ?, ?)"
290390
- );
290391
- const txn = this.db.transaction(() => {
290392
- for (const parsed of parsedFiles) {
290393
- const { file, symbols, imports, callEdges, contentHash } = parsed;
290394
- insertHash.run(file.relativePath, contentHash, 0, Date.now());
290395
- insertFile.run(
290396
- file.id,
290397
- file.path,
290398
- file.relativePath,
290399
- file.language,
290400
- file.lineCount,
290401
- JSON.stringify(file.exports),
290402
- this.config.storeContent ? file.content ?? null : null
290403
- );
290404
- for (const sym of symbols) {
290405
- insertSymbol.run(
290406
- sym.id,
290407
- sym.name,
290408
- sym.kind,
290409
- sym.filePath,
290410
- sym.startLine,
290411
- sym.endLine,
290412
- sym.exported ? 1 : 0,
290413
- sym.async ? 1 : 0,
290414
- sym.params ?? null,
290415
- sym.branches ?? null,
290416
- sym.signature ?? null
290417
- );
290176
+ if (ctx.codebaseDNA.boundaries.length > 0) {
290177
+ lines.push("## Module Boundaries");
290178
+ for (const boundary of ctx.codebaseDNA.boundaries) {
290179
+ lines.push(`- ${boundary}`);
290180
+ }
290181
+ lines.push("");
290182
+ }
290183
+ if (ctx.codebaseDNA.hotFiles.length > 0) {
290184
+ lines.push("## High-Impact Files (Edit With Care)");
290185
+ for (const file of ctx.codebaseDNA.hotFiles.slice(0, 10)) {
290186
+ lines.push(`- \`${file}\``);
290187
+ }
290188
+ lines.push("");
290189
+ }
290190
+ if (ctx.riskBriefing.securityConcerns.length > 0 || ctx.riskBriefing.testGaps.length > 0) {
290191
+ lines.push("## Risk Areas");
290192
+ for (const concern of ctx.riskBriefing.securityConcerns) {
290193
+ lines.push(`- ${concern}`);
290194
+ }
290195
+ for (const gap of ctx.riskBriefing.testGaps.slice(0, 5)) {
290196
+ lines.push(`- ${gap}`);
290197
+ }
290198
+ lines.push("");
290199
+ }
290200
+ if (ctx.projectIdentity.noGoZones.length > 0) {
290201
+ lines.push("## No-Go Zones");
290202
+ for (const zone of ctx.projectIdentity.noGoZones) {
290203
+ lines.push(`- ${zone}`);
290204
+ }
290205
+ lines.push("");
290206
+ }
290207
+ if (ctx.taskPlaybooks.length > 0) {
290208
+ lines.push("## Task Playbooks");
290209
+ for (const playbook of ctx.taskPlaybooks) {
290210
+ lines.push(`### ${playbook.taskType}`);
290211
+ for (const step of playbook.steps) {
290212
+ lines.push(`1. ${step}`);
290418
290213
  }
290419
- for (const imp of imports) {
290420
- insertImport.run(
290421
- imp.fileId,
290422
- imp.filePath,
290423
- imp.sourcePath,
290424
- imp.resolvedPath,
290425
- JSON.stringify(imp.importedSymbols),
290426
- imp.isTypeOnly ? 1 : 0,
290427
- imp.isDynamic ? 1 : 0,
290428
- imp.line
290429
- );
290214
+ if (playbook.mustVerify.length > 0) {
290215
+ lines.push(`**Verify**: ${playbook.mustVerify.join(", ")}`);
290430
290216
  }
290431
- for (const edge of callEdges) {
290432
- insertCallEdge.run(
290433
- edge.callerId,
290434
- edge.calleeId,
290435
- edge.callerName,
290436
- edge.calleeName,
290437
- edge.callerFile,
290438
- edge.calleeFile
290439
- );
290217
+ lines.push("");
290218
+ }
290219
+ }
290220
+ if (ctx.verificationSteps.length > 0) {
290221
+ lines.push("## Verification Protocol");
290222
+ for (const step of ctx.verificationSteps) {
290223
+ lines.push(`### On ${step.trigger}`);
290224
+ for (const check of step.checks) {
290225
+ lines.push(`- ${check}`);
290226
+ }
290227
+ if (step.commands.length > 0) {
290228
+ lines.push(`**Run**: \`${step.commands.join(" && ")}\``);
290440
290229
  }
290230
+ lines.push("");
290441
290231
  }
290442
- });
290443
- txn();
290232
+ }
290233
+ lines.push("## Codebase Health");
290234
+ lines.push(`- **Overall**: ${this.dna.healthScore.overall}/100`);
290235
+ const dims = this.dna.healthScore.dimensions;
290236
+ lines.push(`- Architecture: ${dims.architecture} | Tests: ${dims.testCoverage} | Conventions: ${dims.conventions} | Dependencies: ${dims.dependencies}`);
290237
+ lines.push("");
290238
+ lines.push("---");
290239
+ lines.push("<!-- context-engine:v2 -->");
290240
+ return lines.join("\n");
290444
290241
  }
290445
- removeFiles(relativePaths) {
290446
- if (relativePaths.length === 0) return;
290447
- const txn = this.db.transaction(() => {
290448
- for (const relPath of relativePaths) {
290449
- const absPath = join4(this.rootPath, relPath);
290450
- this.db.prepare("DELETE FROM file_hashes WHERE relative_path = ?").run(relPath);
290451
- this.db.prepare("DELETE FROM files WHERE relative_path = ?").run(relPath);
290452
- this.db.prepare("DELETE FROM symbols WHERE file_path = ? OR file_path = ?").run(absPath, relPath);
290453
- this.db.prepare("DELETE FROM imports WHERE file_path = ? OR file_path = ?").run(absPath, relPath);
290454
- this.db.prepare("DELETE FROM call_edges WHERE caller_file = ? OR callee_file = ? OR caller_file = ? OR callee_file = ?").run(absPath, absPath, relPath, relPath);
290455
- this.db.prepare("DELETE FROM embeddings WHERE path = ?").run(relPath);
290456
- }
290457
- });
290458
- txn();
290242
+ // ═══════════════════════════════════════════════════════════════════════════
290243
+ // IDENTITY
290244
+ // ═══════════════════════════════════════════════════════════════════════════
290245
+ buildProjectIdentity() {
290246
+ const fp = this.dna.fingerprint;
290247
+ const stack = [fp.framework, fp.language, fp.orm, fp.validator, fp.authLib, fp.router].filter(Boolean).join(" | ");
290248
+ const keyPatterns = this.dna.patterns.map((p) => p.name);
290249
+ const criticalPaths = this.dna.hotspots.slice(0, 5).map((h) => h.file);
290250
+ const noGoZones = [];
290251
+ if (this.ruleResult) {
290252
+ const errorRules = this.ruleResult.violations.filter((v) => v.severity === "error");
290253
+ const uniqueMessages = [...new Set(errorRules.map((v) => v.message))];
290254
+ noGoZones.push(...uniqueMessages.slice(0, 5));
290255
+ }
290256
+ for (const cycle of this.graph.cycles) {
290257
+ noGoZones.push(`Circular dependency: ${cycle.nodes.slice(0, 3).join(" \u2192 ")}...`);
290258
+ }
290259
+ const architecture = this.dna.conventions.filter((c) => c.area === "structure").map((c) => c.description).join("; ") || fp.framework;
290260
+ return {
290261
+ name: fp.name,
290262
+ stack,
290263
+ architecture,
290264
+ keyPatterns,
290265
+ criticalPaths,
290266
+ noGoZones: noGoZones.slice(0, 10)
290267
+ };
290459
290268
  }
290460
290269
  // ═══════════════════════════════════════════════════════════════════════════
290461
- // PRIVATE — Loading
290270
+ // RULES SUMMARY
290462
290271
  // ═══════════════════════════════════════════════════════════════════════════
290463
- loadFiles() {
290464
- const rows = this.db.prepare("SELECT id, path, relative_path, language, line_count, exports, content FROM files").all();
290465
- return rows.map((row) => ({
290466
- id: row.id,
290467
- path: row.path,
290468
- relativePath: row.relative_path,
290469
- language: row.language,
290470
- lineCount: row.line_count,
290471
- exports: JSON.parse(row.exports),
290472
- content: row.content ?? void 0
290473
- }));
290272
+ buildArchRuleSummary() {
290273
+ if (!this.ruleResult) return [];
290274
+ const breakdown = this.ruleResult.ruleBreakdown;
290275
+ return Object.entries(breakdown).map(([ruleId, count]) => {
290276
+ const violation = this.ruleResult.violations.find((v) => v.ruleId === ruleId);
290277
+ return {
290278
+ id: ruleId,
290279
+ name: violation?.ruleName || ruleId,
290280
+ type: "import_forbidden",
290281
+ severity: violation?.severity || "warning",
290282
+ scope: violation?.sourceSymbol.filePath || "",
290283
+ description: violation?.message || "",
290284
+ violationCount: count
290285
+ };
290286
+ });
290474
290287
  }
290475
- loadSymbols() {
290476
- const rows = this.db.prepare("SELECT id, name, kind, file_path, start_line, end_line, exported, async, params, branches FROM symbols").all();
290477
- return rows.map((row) => ({
290478
- id: row.id,
290479
- name: row.name,
290480
- kind: row.kind,
290481
- filePath: row.file_path,
290482
- startLine: row.start_line,
290483
- endLine: row.end_line,
290484
- exported: row.exported === 1,
290485
- async: row.async === 1,
290486
- params: row.params ?? void 0,
290487
- branches: row.branches ?? void 0
290488
- }));
290288
+ // ═══════════════════════════════════════════════════════════════════════════
290289
+ // DNA SUMMARY
290290
+ // ═══════════════════════════════════════════════════════════════════════════
290291
+ buildDNASummary() {
290292
+ return {
290293
+ conventions: this.dna.conventions.filter((c) => c.confidence > 0.5).map((c) => c.description),
290294
+ patterns: this.dna.patterns.map((p) => `${p.name}: ${p.description}`),
290295
+ boundaries: this.dna.boundaries.filter((b) => b.importCount > 3).map((b) => `${b.from} \u2192 ${b.to} (${b.importCount} imports${b.isCircular ? ", CIRCULAR" : ""})`),
290296
+ hotFiles: this.dna.hotspots.slice(0, 10).map((h) => h.file),
290297
+ riskAreas: this.dna.riskMap.filter((r) => r.riskLevel === "critical" || r.riskLevel === "high").map((r) => `${r.file}: ${r.factors[0]}`)
290298
+ };
290489
290299
  }
290490
- loadSymbolsWhere(where, params) {
290491
- const rows = this.db.prepare(`SELECT id, name, kind, file_path, start_line, end_line, exported, async, params, branches FROM symbols WHERE ${where}`).all(...params);
290492
- return rows.map((row) => ({
290493
- id: row.id,
290494
- name: row.name,
290495
- kind: row.kind,
290496
- filePath: row.file_path,
290497
- startLine: row.start_line,
290498
- endLine: row.end_line,
290499
- exported: row.exported === 1,
290500
- async: row.async === 1,
290501
- params: row.params ?? void 0,
290502
- branches: row.branches ?? void 0
290503
- }));
290300
+ // ═══════════════════════════════════════════════════════════════════════════
290301
+ // FILE CONTEXTS
290302
+ // ═══════════════════════════════════════════════════════════════════════════
290303
+ buildFileContexts() {
290304
+ const contexts = /* @__PURE__ */ new Map();
290305
+ for (const file of this.data.files) {
290306
+ contexts.set(file.relativePath, this.buildSingleFileContext(file));
290307
+ }
290308
+ return contexts;
290504
290309
  }
290505
- loadImports() {
290506
- const rows = this.db.prepare("SELECT file_id, file_path, source_path, resolved_path, imported_symbols, is_type_only, is_dynamic, line FROM imports").all();
290507
- return rows.map((row) => ({
290508
- fileId: row.file_id,
290509
- filePath: row.file_path,
290510
- sourcePath: row.source_path,
290511
- resolvedPath: row.resolved_path,
290512
- importedSymbols: JSON.parse(row.imported_symbols),
290513
- isTypeOnly: row.is_type_only === 1,
290514
- isDynamic: row.is_dynamic === 1,
290515
- line: row.line
290516
- }));
290310
+ buildSingleFileContext(file) {
290311
+ const rel = file.relativePath;
290312
+ const role = this.classifyRole(file);
290313
+ const graphNode = this.graph.nodes.find((n) => n.relativePath === rel);
290314
+ const layer = graphNode?.layer;
290315
+ const dependsOn = this.graph.edges.filter((e) => e.from === rel).map((e) => e.to);
290316
+ const dependedOnBy = this.graph.edges.filter((e) => e.to === rel).map((e) => e.from);
290317
+ const applicableRules = [];
290318
+ if (this.ruleResult) {
290319
+ for (const v of this.ruleResult.violations) {
290320
+ if (v.sourceSymbol.filePath.includes(rel) || v.targetSymbol && v.targetSymbol.filePath.includes(rel)) {
290321
+ if (!applicableRules.includes(v.ruleId)) applicableRules.push(v.ruleId);
290322
+ }
290323
+ }
290324
+ }
290325
+ const conventions = this.dna.conventions.filter((c) => this.conventionAppliesToFile(c.area, file)).map((c) => c.description);
290326
+ const patterns = this.dna.patterns.filter((p) => p.fileMatches.some((m) => m === rel)).map((p) => p.name);
290327
+ const riskEntry = this.dna.riskMap.find((r) => r.file === rel);
290328
+ const riskLevel = riskEntry?.riskLevel || "low";
290329
+ const relatedFiles = this.findRelatedFiles(file, role).slice(0, 8);
290330
+ const editGuidance = this.generateEditGuidance(file, role, layer, dependedOnBy, conventions);
290331
+ return {
290332
+ filePath: file.path,
290333
+ role,
290334
+ layer,
290335
+ dependsOn,
290336
+ dependedOnBy,
290337
+ applicableRules,
290338
+ conventions,
290339
+ patterns,
290340
+ riskLevel,
290341
+ relatedFiles,
290342
+ editGuidance
290343
+ };
290517
290344
  }
290518
- loadCallEdges() {
290519
- const rows = this.db.prepare("SELECT caller_id, callee_id, caller_name, callee_name, caller_file, callee_file FROM call_edges").all();
290520
- return rows.map((row) => ({
290521
- callerId: row.caller_id,
290522
- calleeId: row.callee_id,
290523
- callerName: row.caller_name,
290524
- calleeName: row.callee_name,
290525
- callerFile: row.caller_file,
290526
- calleeFile: row.callee_file
290527
- }));
290345
+ classifyRole(file) {
290346
+ const rel = file.relativePath.toLowerCase();
290347
+ if (rel.includes(".test.") || rel.includes(".spec.") || rel.includes("__tests__")) return "test";
290348
+ if (rel.includes("fixture") || rel.includes("mock")) return "fixture";
290349
+ if (rel.includes("migration")) return "migration";
290350
+ if (rel.match(/\.(css|scss|less|styl)$/)) return "style";
290351
+ if (rel.includes(".config.") || rel.includes("config/") || rel === "tsconfig.json") return "config";
290352
+ if (rel.includes("middleware")) return "middleware";
290353
+ if (rel.includes("/api/") || rel.includes("route")) return "route-handler";
290354
+ if (rel.includes("service") || rel.includes("Service")) return "service";
290355
+ if (rel.includes("repositor") || rel.includes("Repositor")) return "repository";
290356
+ if (rel.endsWith(".tsx") && !rel.includes("page.")) return "component";
290357
+ if (rel.includes("/types") || rel.endsWith(".d.ts")) return "type";
290358
+ if (rel.includes("util") || rel.includes("helper") || rel.includes("lib/")) return "util";
290359
+ if (rel.includes("script") || rel.includes("bin/")) return "script";
290360
+ if (rel.match(/^(src\/)?index\.|^(src\/)?main\.|^(src\/)?app\./)) return "entry";
290361
+ return "unknown";
290528
290362
  }
290529
- loadRoutes() {
290530
- const rows = this.db.prepare("SELECT path, method, handler, file, line, middleware, auth FROM routes").all();
290531
- return rows.map((row) => ({
290532
- path: row.path,
290533
- method: row.method,
290534
- handler: row.handler,
290535
- file: row.file,
290536
- line: row.line,
290537
- middleware: JSON.parse(row.middleware),
290538
- auth: row.auth === null ? void 0 : row.auth === 1
290539
- }));
290363
+ conventionAppliesToFile(area, file) {
290364
+ switch (area) {
290365
+ case "naming":
290366
+ return true;
290367
+ case "imports":
290368
+ return file.relativePath.endsWith(".ts") || file.relativePath.endsWith(".tsx");
290369
+ case "exports":
290370
+ return file.exports.length > 0;
290371
+ case "testing":
290372
+ return file.relativePath.includes(".test.") || file.relativePath.includes(".spec.");
290373
+ case "error-handling":
290374
+ return !file.relativePath.includes(".test.");
290375
+ case "types":
290376
+ return file.relativePath.endsWith(".ts") || file.relativePath.endsWith(".tsx");
290377
+ default:
290378
+ return true;
290379
+ }
290540
290380
  }
290541
- loadServices() {
290542
- const rows = this.db.prepare("SELECT id, name, root_path FROM services").all();
290543
- return rows.map((row) => ({
290544
- id: row.id,
290545
- name: row.name,
290546
- rootPath: row.root_path
290547
- }));
290381
+ findRelatedFiles(file, role) {
290382
+ const related = [];
290383
+ const dir = path5.dirname(file.relativePath);
290384
+ for (const other of this.data.files) {
290385
+ if (other.path === file.path) continue;
290386
+ if (path5.dirname(other.relativePath) === dir) {
290387
+ related.push(other.relativePath);
290388
+ }
290389
+ }
290390
+ if (related.length < 5) {
290391
+ for (const other of this.data.files) {
290392
+ if (other.path === file.path) continue;
290393
+ if (related.includes(other.relativePath)) continue;
290394
+ if (this.classifyRole(other) === role) {
290395
+ related.push(other.relativePath);
290396
+ if (related.length >= 8) break;
290397
+ }
290398
+ }
290399
+ }
290400
+ return related;
290548
290401
  }
290549
- };
290550
- function hashContent2(content) {
290551
- return createHash2("sha256").update(content).digest("hex").slice(0, 16);
290552
- }
290553
- var EXT_LANG = {
290554
- ".ts": "typescript",
290555
- ".tsx": "typescript",
290556
- ".js": "javascript",
290557
- ".jsx": "javascript",
290558
- ".mjs": "javascript",
290559
- ".cjs": "javascript",
290560
- ".py": "python",
290561
- ".rs": "rust",
290562
- ".go": "go",
290563
- ".java": "java",
290564
- ".c": "c",
290565
- ".h": "c",
290566
- ".cpp": "cpp",
290567
- ".hpp": "cpp",
290568
- ".rb": "ruby",
290569
- ".swift": "swift",
290570
- ".kt": "kotlin",
290571
- ".lua": "lua",
290572
- ".zig": "zig",
290573
- ".cs": "csharp"
290574
- };
290575
- var TS_IMPORT_RE = /^import\s+(?:type\s+)?(?:\{[^}]*\}|[\w*]+(?:\s*,\s*\{[^}]*\})?)\s+from\s+['"]([^'"]+)['"]/gm;
290576
- var TS_IMPORT_TYPE_RE = /^import\s+type\s+/;
290577
- var TS_DYNAMIC_IMPORT_RE = /(?:import|require)\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
290578
- var PY_IMPORT_RE = /^(?:from\s+([\w.]+)\s+import|import\s+([\w.]+))/gm;
290579
- var GO_IMPORT_RE = /import\s+(?:\(\s*([\s\S]*?)\s*\)|"([^"]+)")/g;
290580
- var DefaultFileParser = class {
290581
- rootPath;
290582
- treeSitterParser = null;
290583
- treeSitterLoaded = false;
290584
- constructor(rootPath) {
290585
- this.rootPath = rootPath;
290402
+ generateEditGuidance(file, role, layer, dependedOnBy, conventions) {
290403
+ const guidance = [];
290404
+ if (dependedOnBy.length > 10) {
290405
+ guidance.push(`HIGH IMPACT: ${dependedOnBy.length} files depend on this. Changes have wide blast radius.`);
290406
+ }
290407
+ switch (role) {
290408
+ case "route-handler":
290409
+ guidance.push("Validate all inputs with schemas before processing.");
290410
+ guidance.push("Return consistent response shapes ({ success, data } or { success, error }).");
290411
+ guidance.push("Ensure authentication middleware is applied to protected endpoints.");
290412
+ break;
290413
+ case "service":
290414
+ guidance.push("Keep business logic here, not in controllers/routes.");
290415
+ guidance.push("Use dependency injection for testability.");
290416
+ if (layer) guidance.push(`This is in the ${layer} layer \u2014 only import from lower layers.`);
290417
+ break;
290418
+ case "repository":
290419
+ guidance.push("Only data access logic belongs here \u2014 no business rules.");
290420
+ guidance.push("Return domain objects, not raw database rows.");
290421
+ break;
290422
+ case "component":
290423
+ guidance.push("Keep components focused and composable.");
290424
+ guidance.push("Extract complex logic to custom hooks.");
290425
+ break;
290426
+ case "middleware":
290427
+ guidance.push("Middleware must call next() or return a response \u2014 never leave the request hanging.");
290428
+ guidance.push("Keep middleware focused on a single concern.");
290429
+ break;
290430
+ case "test":
290431
+ guidance.push("Follow Arrange-Act-Assert pattern.");
290432
+ guidance.push("Test edge cases and error conditions, not just happy path.");
290433
+ break;
290434
+ }
290435
+ for (const conv of conventions.slice(0, 3)) {
290436
+ guidance.push(`Convention: ${conv}`);
290437
+ }
290438
+ return guidance;
290586
290439
  }
290587
- async parseFile(absolutePath, relativePath) {
290588
- const content = await readFile22(absolutePath, "utf-8");
290589
- const lines = content.split("\n");
290590
- const ext2 = extname2(absolutePath).toLowerCase();
290591
- const language = EXT_LANG[ext2] ?? "unknown";
290592
- const contentHash = createHash22("sha256").update(content).digest("hex").slice(0, 16);
290593
- const exports2 = this.extractExports(content, language);
290594
- const symbols = await this.extractSymbols(content, lines, absolutePath, relativePath, language);
290595
- const imports = this.extractImports(content, absolutePath, relativePath, language);
290596
- const callEdges = this.extractCallEdges(content, symbols, absolutePath);
290597
- const fileId = `file:${relativePath}`;
290598
- const file = {
290599
- id: fileId,
290600
- path: absolutePath,
290601
- relativePath,
290602
- language,
290603
- lineCount: lines.length,
290604
- exports: exports2,
290605
- content
290606
- };
290607
- return { file, symbols, imports, callEdges, contentHash };
290440
+ // ═══════════════════════════════════════════════════════════════════════════
290441
+ // TASK PLAYBOOKS
290442
+ // ═══════════════════════════════════════════════════════════════════════════
290443
+ buildTaskPlaybooks() {
290444
+ const fp = this.dna.fingerprint;
290445
+ const playbooks = [];
290446
+ playbooks.push({
290447
+ taskType: "Bug Fix",
290448
+ steps: [
290449
+ "Reproduce the bug and understand the expected vs actual behavior",
290450
+ "Identify the root cause file(s) using the dependency graph",
290451
+ "Write a failing test that reproduces the bug",
290452
+ "Apply the minimal fix at the root cause",
290453
+ "Verify the fix passes the test and does not break existing tests",
290454
+ "Check that the fix does not violate any architecture rules"
290455
+ ],
290456
+ mustRead: this.dna.hotspots.slice(0, 3).map((h) => h.file),
290457
+ mustUpdate: ["The buggy file", "Related test file"],
290458
+ mustVerify: ["All existing tests pass", "New regression test passes", "No new arch rule violations"],
290459
+ stopConditions: ["Never modify tests to make them pass \u2014 fix the code", "Do not change public API signatures without discussion"]
290460
+ });
290461
+ if (fp.router) {
290462
+ playbooks.push({
290463
+ taskType: "Add API Endpoint",
290464
+ steps: [
290465
+ "Check existing routes in truthpack to avoid duplicates",
290466
+ "Create the route handler following existing patterns",
290467
+ "Add input validation using the project validator",
290468
+ "Add authentication middleware if the route is protected",
290469
+ "Write tests for success, validation failure, and auth failure",
290470
+ "Update the truthpack (run vibecheck scan)"
290471
+ ],
290472
+ mustRead: ["truthpack/routes.json", ...this.dna.patterns.filter((p) => p.category === "api").map((p) => p.exemplar)],
290473
+ mustUpdate: ["Route file", "Test file", "Truthpack"],
290474
+ mustVerify: ["Route responds correctly", "Input validation works", "Auth is enforced", "Test passes"],
290475
+ stopConditions: ["Do not create duplicate routes", "Do not hardcode mock data in handlers"]
290476
+ });
290477
+ }
290478
+ if (fp.framework.includes("Next") || fp.framework.includes("React")) {
290479
+ playbooks.push({
290480
+ taskType: "Add UI Component",
290481
+ steps: [
290482
+ "Check if a similar component already exists",
290483
+ "Create the component following existing naming and structure patterns",
290484
+ "Add TypeScript props interface",
290485
+ "Add unit test for the component",
290486
+ "If using state, determine if it should be a client component"
290487
+ ],
290488
+ mustRead: this.dna.patterns.filter((p) => p.category === "ui" || p.category === "state").map((p) => p.exemplar),
290489
+ mustUpdate: ["Component file", "Test file", "Parent component that uses it"],
290490
+ mustVerify: ["Component renders correctly", "Props are typed", "Test passes"],
290491
+ stopConditions: ['Do not use "any" type for props', 'Do not add useState in server components without "use client"']
290492
+ });
290493
+ }
290494
+ playbooks.push({
290495
+ taskType: "Refactor",
290496
+ steps: [
290497
+ "Identify all callers/dependents of the code being refactored",
290498
+ "Ensure comprehensive tests exist before refactoring",
290499
+ "Apply changes incrementally, testing after each step",
290500
+ "Update all dependents to use the new API",
290501
+ "Remove old code only after all dependents are migrated",
290502
+ "Verify no architecture rules are violated"
290503
+ ],
290504
+ mustRead: ["Dependency graph for affected files"],
290505
+ mustUpdate: ["Refactored file", "All dependent files", "Tests"],
290506
+ mustVerify: ["All tests pass", "No new violations", "No regressions"],
290507
+ stopConditions: ["Never break existing public APIs without migration path", "Do not refactor and add features in the same change"]
290508
+ });
290509
+ return playbooks;
290608
290510
  }
290609
290511
  // ═══════════════════════════════════════════════════════════════════════════
290610
- // EXPORTS
290512
+ // VERIFICATION STEPS
290611
290513
  // ═══════════════════════════════════════════════════════════════════════════
290612
- extractExports(content, language) {
290613
- if (language !== "typescript" && language !== "javascript") return [];
290614
- const exports2 = [];
290615
- const exportRe = /export\s+(?:default\s+)?(?:async\s+)?(?:function|class|const|let|var|type|interface|enum)\s+(\w+)/g;
290616
- for (const match2 of content.matchAll(exportRe)) {
290617
- if (match2[1]) exports2.push(match2[1]);
290514
+ buildVerificationSteps() {
290515
+ const fp = this.dna.fingerprint;
290516
+ const steps = [];
290517
+ if (fp.language === "TypeScript") {
290518
+ steps.push({
290519
+ trigger: "Any TypeScript file change",
290520
+ checks: ["TypeScript compilation succeeds", "No new type errors introduced"],
290521
+ commands: [fp.packageManager === "pnpm" ? "pnpm run check-types" : "npm run check-types"],
290522
+ artifacts: []
290523
+ });
290618
290524
  }
290619
- if (/export\s+default\s/.test(content) && !exports2.includes("default")) {
290620
- exports2.push("default");
290525
+ if (fp.testRunner) {
290526
+ steps.push({
290527
+ trigger: "Any source file change",
290528
+ checks: ["Related tests pass", "No test regressions"],
290529
+ commands: [`${fp.packageManager} run test`],
290530
+ artifacts: ["test-results.json"]
290531
+ });
290621
290532
  }
290622
- const reExportRe = /export\s+\{([^}]+)\}\s+from/g;
290623
- for (const match2 of content.matchAll(reExportRe)) {
290624
- for (const sym of match2[1].split(",")) {
290625
- const name2 = sym.trim().split(/\s+as\s+/).pop()?.trim();
290626
- if (name2) exports2.push(name2);
290533
+ if (fp.router) {
290534
+ steps.push({
290535
+ trigger: "Route handler added or modified",
290536
+ checks: ["Route responds with correct status", "Auth middleware is applied", "Input validation works"],
290537
+ commands: ["vibecheck scan"],
290538
+ artifacts: ["truthpack/routes.json"]
290539
+ });
290540
+ }
290541
+ steps.push({
290542
+ trigger: "Any source file change",
290543
+ checks: ["No new architecture rule violations", "No new circular dependencies"],
290544
+ commands: ["vibecheck arch-rules"],
290545
+ artifacts: []
290546
+ });
290547
+ return steps;
290548
+ }
290549
+ // ═══════════════════════════════════════════════════════════════════════════
290550
+ // RISK BRIEFING
290551
+ // ═══════════════════════════════════════════════════════════════════════════
290552
+ buildRiskBriefing() {
290553
+ const criticalFiles = this.dna.hotspots.filter((h) => h.score > 30).slice(0, 10).map((h) => h.file);
290554
+ const recentViolations = this.ruleResult?.violations.filter((v) => v.severity === "error").slice(0, 10) || [];
290555
+ const securityConcerns = [];
290556
+ for (const risk of this.dna.riskMap) {
290557
+ if (risk.riskLevel === "critical") {
290558
+ securityConcerns.push(`${risk.file}: ${risk.factors.join(", ")}`);
290627
290559
  }
290628
290560
  }
290629
- return [...new Set(exports2)];
290561
+ const testGaps = [];
290562
+ const sourceFiles = this.data.files.filter(
290563
+ (f) => !f.relativePath.includes(".test.") && !f.relativePath.includes(".spec.") && (f.relativePath.endsWith(".ts") || f.relativePath.endsWith(".tsx")) && !f.relativePath.includes("config") && !f.relativePath.includes(".d.ts")
290564
+ );
290565
+ const testFiles = this.data.files.filter(
290566
+ (f) => f.relativePath.includes(".test.") || f.relativePath.includes(".spec.")
290567
+ );
290568
+ const testedBases = new Set(testFiles.map(
290569
+ (f) => path5.basename(f.relativePath).replace(/\.(test|spec)\.(ts|tsx|js|jsx)$/, "")
290570
+ ));
290571
+ for (const file of sourceFiles) {
290572
+ const baseName = path5.basename(file.relativePath).replace(/\.(ts|tsx|js|jsx)$/, "");
290573
+ if (!testedBases.has(baseName) && file.exports.length > 0) {
290574
+ testGaps.push(`${file.relativePath} has exports but no test file`);
290575
+ }
290576
+ }
290577
+ const driftWarnings = [];
290578
+ for (const cycle of this.graph.cycles) {
290579
+ driftWarnings.push(`Circular dependency: ${cycle.nodes.slice(0, 3).join(" \u2192 ")}${cycle.nodes.length > 3 ? "..." : ""}`);
290580
+ }
290581
+ return {
290582
+ criticalFiles,
290583
+ recentViolations,
290584
+ securityConcerns: securityConcerns.slice(0, 5),
290585
+ testGaps: testGaps.slice(0, 10),
290586
+ driftWarnings: driftWarnings.slice(0, 5)
290587
+ };
290630
290588
  }
290631
290589
  // ═══════════════════════════════════════════════════════════════════════════
290632
- // SYMBOLS
290590
+ // HELPERS
290633
290591
  // ═══════════════════════════════════════════════════════════════════════════
290634
- async extractSymbols(content, lines, absolutePath, relativePath, language) {
290635
- const tsSymbols = await this.tryTreeSitter(content, absolutePath);
290636
- if (tsSymbols) {
290637
- return tsSymbols.map((sym, i2) => ({
290638
- id: `sym:${relativePath}:${sym.name}:${sym.line}`,
290639
- name: sym.name,
290640
- kind: mapTreeSitterKind(sym.kind),
290641
- filePath: absolutePath,
290642
- startLine: sym.line,
290643
- endLine: sym.endLine,
290644
- exported: this.isExported(content, sym.name, language),
290645
- async: sym.signature.includes("async "),
290646
- params: this.countParams(sym.signature),
290647
- branches: void 0,
290648
- signature: sym.signature
290649
- }));
290592
+ toRelative(filePath) {
290593
+ if (filePath.startsWith(this.rootPath)) {
290594
+ return filePath.slice(this.rootPath.length + 1).replace(/\\/g, "/");
290650
290595
  }
290651
- return this.extractSymbolsRegex(content, lines, absolutePath, relativePath, language);
290596
+ return filePath.replace(/\\/g, "/");
290652
290597
  }
290653
- async tryTreeSitter(content, filePath) {
290654
- if (!this.treeSitterLoaded) {
290655
- this.treeSitterLoaded = true;
290656
- try {
290657
- const mod = await Promise.resolve().then(() => (init_tree_sitter_H5E7LKR4(), tree_sitter_H5E7LKR4_exports));
290658
- this.treeSitterParser = mod.parseWithTreeSitter;
290659
- } catch {
290660
- this.treeSitterParser = null;
290598
+ };
290599
+ var ContextExplainer = class {
290600
+ /**
290601
+ * Generate a rich explanation for a file.
290602
+ */
290603
+ explain(filePath, ctx) {
290604
+ const fc = ctx.fileContext;
290605
+ const paragraphs = [];
290606
+ const quickFacts = [];
290607
+ const warnings = [];
290608
+ const relatedFiles = [];
290609
+ const overlays = [];
290610
+ const role = fc?.role ?? "unknown";
290611
+ const roleSummary = this.buildRoleSummary(filePath, role, ctx);
290612
+ if (fc) {
290613
+ const depCount = fc.dependedOnBy.length;
290614
+ const depsOnCount = fc.dependsOn.length;
290615
+ if (depCount > 0 || depsOnCount > 0) {
290616
+ const parts2 = [];
290617
+ if (depCount > 0) {
290618
+ const critical = depCount > 10 ? " \u2014 changes here have wide blast radius" : "";
290619
+ parts2.push(`${depCount} file${depCount > 1 ? "s" : ""} depend on this${critical}`);
290620
+ }
290621
+ if (depsOnCount > 0) {
290622
+ parts2.push(`it imports from ${depsOnCount} file${depsOnCount > 1 ? "s" : ""}`);
290623
+ }
290624
+ paragraphs.push({
290625
+ heading: "Dependencies",
290626
+ text: parts2.join(". ") + ".",
290627
+ importance: depCount > 10 ? "critical" : depCount > 5 ? "high" : "medium"
290628
+ });
290629
+ }
290630
+ quickFacts.push({ label: "Role", value: role, icon: "layer" });
290631
+ if (fc.layer) quickFacts.push({ label: "Layer", value: fc.layer, icon: "layer" });
290632
+ quickFacts.push({ label: "Dependents", value: String(depCount), icon: "dependency" });
290633
+ quickFacts.push({ label: "Dependencies", value: String(depsOnCount), icon: "dependency" });
290634
+ if (fc.riskLevel === "critical" || fc.riskLevel === "high") {
290635
+ quickFacts.push({ label: "Risk", value: fc.riskLevel.toUpperCase(), icon: "warning" });
290636
+ warnings.push({
290637
+ message: `This file is classified as ${fc.riskLevel} risk`,
290638
+ severity: fc.riskLevel === "critical" ? "error" : "warning",
290639
+ action: "Add comprehensive tests and review carefully before merging changes"
290640
+ });
290641
+ }
290642
+ for (const dep of fc.dependedOnBy.slice(0, 5)) {
290643
+ relatedFiles.push({
290644
+ filePath: dep,
290645
+ reason: `Imports from this file`,
290646
+ relationship: "depended-by",
290647
+ confidence: 0.9
290648
+ });
290661
290649
  }
290650
+ overlays.push({
290651
+ type: "code-lens",
290652
+ line: 1,
290653
+ text: `${depCount} dependent${depCount !== 1 ? "s" : ""} \xB7 ${depsOnCount} import${depsOnCount !== 1 ? "s" : ""} \xB7 ${role}`,
290654
+ tooltip: `This ${role} file has ${depCount} files that depend on it and imports from ${depsOnCount} files`
290655
+ });
290662
290656
  }
290663
- if (!this.treeSitterParser) return null;
290664
- try {
290665
- const ext2 = extname2(filePath).toLowerCase();
290666
- const symbols = await this.treeSitterParser(content, ext2);
290667
- if (!symbols || symbols.length === 0) return null;
290668
- return flattenCodeSymbols(symbols);
290669
- } catch {
290670
- return null;
290657
+ if (fc && fc.conventions.length > 0) {
290658
+ paragraphs.push({
290659
+ heading: "Conventions",
290660
+ text: `Follow these discovered conventions: ${fc.conventions.slice(0, 3).join("; ")}.`,
290661
+ importance: "medium"
290662
+ });
290671
290663
  }
290672
- }
290673
- extractSymbolsRegex(content, lines, absolutePath, relativePath, language) {
290674
- const symbols = [];
290675
- if (language === "typescript" || language === "javascript") {
290676
- const patterns = [
290677
- { re: /^(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*(<[^>]*>)?\s*\(([^)]*)\)/gm, kind: "function" },
290678
- { re: /^(?:export\s+)?(?:abstract\s+)?class\s+(\w+)/gm, kind: "class" },
290679
- { re: /^(?:export\s+)?interface\s+(\w+)/gm, kind: "interface" },
290680
- { re: /^(?:export\s+)?type\s+(\w+)\s*(?:<[^>]*>)?\s*=/gm, kind: "type" },
290681
- { re: /^(?:export\s+)?(?:const\s+)?enum\s+(\w+)/gm, kind: "enum" },
290682
- { re: /^(?:export\s+)?const\s+(\w+)\s*(?::\s*[^=]+)?\s*=\s*(?:async\s+)?\(/gm, kind: "function" }
290683
- ];
290684
- for (const { re, kind } of patterns) {
290685
- for (const match2 of content.matchAll(re)) {
290686
- const name2 = match2[1];
290687
- if (!name2) continue;
290688
- const line = content.slice(0, match2.index).split("\n").length;
290689
- const endLine = this.findBlockEnd(lines, line - 1);
290690
- symbols.push({
290691
- id: `sym:${relativePath}:${name2}:${line}`,
290692
- name: name2,
290693
- kind,
290694
- filePath: absolutePath,
290695
- startLine: line,
290696
- endLine,
290697
- exported: this.isExported(content, name2, language),
290698
- async: match2[0].includes("async"),
290699
- params: this.countParams(match2[0]),
290700
- branches: this.countBranches(lines, line - 1, endLine - 1),
290701
- signature: match2[0].trim().replace(/\s*\{?\s*$/, "")
290664
+ if (ctx.rules) {
290665
+ const fileViolations = ctx.rules.violations.filter(
290666
+ (v) => v.sourceSymbol.filePath.includes(shortName(filePath)) || v.targetSymbol && v.targetSymbol.filePath.includes(shortName(filePath))
290667
+ );
290668
+ if (fileViolations.length > 0) {
290669
+ const errors = fileViolations.filter((v) => v.severity === "error");
290670
+ const warns = fileViolations.filter((v) => v.severity === "warning");
290671
+ paragraphs.push({
290672
+ heading: "Architecture Violations",
290673
+ text: `${errors.length} error${errors.length !== 1 ? "s" : ""} and ${warns.length} warning${warns.length !== 1 ? "s" : ""} from architecture rules.`,
290674
+ importance: errors.length > 0 ? "critical" : "high"
290675
+ });
290676
+ for (const v of fileViolations.slice(0, 5)) {
290677
+ warnings.push({
290678
+ message: v.message,
290679
+ severity: v.severity === "error" ? "error" : "warning",
290680
+ action: v.suggestedFix ?? "Review and fix the violation",
290681
+ ruleId: v.ruleId
290682
+ });
290683
+ overlays.push({
290684
+ type: "diagnostic",
290685
+ line: v.sourceSymbol.line,
290686
+ text: v.message,
290687
+ severity: v.severity === "error" ? "error" : "warning",
290688
+ tooltip: v.suggestedFix
290702
290689
  });
290703
290690
  }
290704
290691
  }
290705
290692
  }
290706
- if (language === "python") {
290707
- const re = /^(?:async\s+)?(?:def|class)\s+(\w+)/gm;
290708
- for (const match2 of content.matchAll(re)) {
290709
- const name2 = match2[1];
290710
- const kind = match2[0].includes("class") ? "class" : "function";
290711
- const line = content.slice(0, match2.index).split("\n").length;
290712
- symbols.push({
290713
- id: `sym:${relativePath}:${name2}:${line}`,
290714
- name: name2,
290715
- kind,
290716
- filePath: absolutePath,
290717
- startLine: line,
290718
- endLine: line + 10,
290719
- exported: true,
290720
- async: match2[0].includes("async"),
290721
- params: this.countParams(match2[0]),
290722
- branches: void 0,
290723
- signature: match2[0].trim()
290693
+ if (ctx.callGraph) {
290694
+ const fileNodes = ctx.callGraph.nodes.filter((n) => n.filePath.includes(shortName(filePath)));
290695
+ const hotFunctions = fileNodes.filter((n) => n.callerCount > 5);
290696
+ if (hotFunctions.length > 0) {
290697
+ paragraphs.push({
290698
+ heading: "Hot Functions",
290699
+ text: hotFunctions.map(
290700
+ (f) => `\`${f.name}\` is called by ${f.callerCount} function${f.callerCount !== 1 ? "s" : ""}${f.calleeCount > 0 ? ` and calls ${f.calleeCount}` : ""}`
290701
+ ).join(". ") + ".",
290702
+ importance: "high"
290724
290703
  });
290704
+ for (const fn of hotFunctions) {
290705
+ overlays.push({
290706
+ type: "code-lens",
290707
+ line: 0,
290708
+ // Would need symbol line mapping
290709
+ text: `${fn.callerCount} callers \xB7 ${fn.calleeCount} callees`,
290710
+ tooltip: `Function ${fn.name} has ${fn.callerCount} callers and ${fn.calleeCount} callees`
290711
+ });
290712
+ }
290713
+ }
290714
+ const deadInFile = ctx.callGraph.stats.deadFunctions.filter(
290715
+ (d) => d.filePath.includes(shortName(filePath))
290716
+ );
290717
+ if (deadInFile.length > 0) {
290718
+ for (const dead of deadInFile) {
290719
+ warnings.push({
290720
+ message: `\`${dead.name}\` appears to be dead code (exported but never called)`,
290721
+ severity: "info",
290722
+ action: "Verify this function is not called via dynamic dispatch or external consumers, then consider removing it"
290723
+ });
290724
+ }
290725
290725
  }
290726
290726
  }
290727
- return symbols;
290728
- }
290729
- // ═══════════════════════════════════════════════════════════════════════════
290730
- // IMPORTS
290731
- // ═══════════════════════════════════════════════════════════════════════════
290732
- extractImports(content, absolutePath, relativePath, language) {
290733
- const imports = [];
290734
- const fileId = `file:${relativePath}`;
290735
- if (language === "typescript" || language === "javascript") {
290736
- for (const match2 of content.matchAll(TS_IMPORT_RE)) {
290737
- const sourcePath = match2[1];
290738
- const line = content.slice(0, match2.index).split("\n").length;
290739
- const isTypeOnly = TS_IMPORT_TYPE_RE.test(match2[0]);
290740
- const importedSymbols = this.extractImportedSymbols(match2[0]);
290741
- const resolvedPath = this.resolveImportPath(sourcePath, absolutePath);
290742
- imports.push({
290743
- fileId,
290744
- filePath: absolutePath,
290745
- sourcePath,
290746
- resolvedPath,
290747
- importedSymbols,
290748
- isTypeOnly,
290749
- isDynamic: false,
290750
- line
290727
+ if (ctx.temporal) {
290728
+ const hotspot = ctx.temporal.changeHotspots.find((h) => filePath.includes(h.file) || h.file.includes(shortName(filePath)));
290729
+ if (hotspot) {
290730
+ const daysSince = Math.round((Date.now() - new Date(hotspot.lastChanged).getTime()) / 864e5);
290731
+ paragraphs.push({
290732
+ heading: "Recent Activity",
290733
+ text: `Changed ${hotspot.commits} times in the last ${ctx.temporal.stats.analysisWindowDays} days by ${hotspot.authors} author${hotspot.authors !== 1 ? "s" : ""}. Last modified ${daysSince} day${daysSince !== 1 ? "s" : ""} ago.`,
290734
+ importance: hotspot.commits > 10 ? "high" : "medium"
290751
290735
  });
290736
+ quickFacts.push({ label: "Recent commits", value: String(hotspot.commits), icon: "git" });
290737
+ quickFacts.push({ label: "Last changed", value: `${daysSince}d ago`, icon: "git" });
290738
+ quickFacts.push({ label: "Authors", value: String(hotspot.authors), icon: "git" });
290752
290739
  }
290753
- for (const match2 of content.matchAll(TS_DYNAMIC_IMPORT_RE)) {
290754
- const sourcePath = match2[1];
290755
- const line = content.slice(0, match2.index).split("\n").length;
290756
- imports.push({
290757
- fileId,
290758
- filePath: absolutePath,
290759
- sourcePath,
290760
- resolvedPath: this.resolveImportPath(sourcePath, absolutePath),
290761
- importedSymbols: [],
290762
- isTypeOnly: false,
290763
- isDynamic: true,
290764
- line
290740
+ const churn = ctx.temporal.churnFiles.find((c) => filePath.includes(c.file) || c.file.includes(shortName(filePath)));
290741
+ if (churn && churn.severity !== "low") {
290742
+ warnings.push({
290743
+ message: churn.reason,
290744
+ severity: churn.severity === "high" ? "warning" : "info",
290745
+ action: "Consider whether this file needs refactoring to reduce change frequency"
290765
290746
  });
290766
290747
  }
290767
- }
290768
- if (language === "python") {
290769
- for (const match2 of content.matchAll(PY_IMPORT_RE)) {
290770
- const sourcePath = match2[1] || match2[2];
290771
- if (!sourcePath) continue;
290772
- const line = content.slice(0, match2.index).split("\n").length;
290773
- imports.push({
290774
- fileId,
290775
- filePath: absolutePath,
290776
- sourcePath,
290777
- resolvedPath: "",
290778
- importedSymbols: [],
290779
- isTypeOnly: false,
290780
- isDynamic: false,
290781
- line
290748
+ const expertise = ctx.temporal.authorExpertise.find(
290749
+ (e) => filePath.startsWith(e.area) || filePath.includes(e.area)
290750
+ );
290751
+ if (expertise && expertise.busFactor === 1) {
290752
+ warnings.push({
290753
+ message: `Bus factor of 1 \u2014 ${expertise.primaryAuthor} has made ${expertise.authors[0]?.percentage}% of changes to this area`,
290754
+ severity: "info",
290755
+ action: "Consider knowledge sharing or pair programming for this area"
290782
290756
  });
290783
290757
  }
290784
290758
  }
290785
- if (language === "go") {
290786
- for (const match2 of content.matchAll(GO_IMPORT_RE)) {
290787
- const block = match2[1] || match2[2];
290788
- if (!block) continue;
290789
- const paths = block.match(/"([^"]+)"/g) ?? [];
290790
- for (const p of paths) {
290791
- const sourcePath = p.replace(/"/g, "");
290792
- imports.push({
290793
- fileId,
290794
- filePath: absolutePath,
290795
- sourcePath,
290796
- resolvedPath: "",
290797
- importedSymbols: [],
290798
- isTypeOnly: false,
290799
- isDynamic: false,
290800
- line: 0
290759
+ if (ctx.learned) {
290760
+ const coEdits = ctx.learned.coEdits.filter((p) => p.files[0].includes(shortName(filePath)) || p.files[1].includes(shortName(filePath))).slice(0, 3);
290761
+ if (coEdits.length > 0) {
290762
+ for (const pair of coEdits) {
290763
+ const other = pair.files[0].includes(shortName(filePath)) ? pair.files[1] : pair.files[0];
290764
+ relatedFiles.push({
290765
+ filePath: other,
290766
+ reason: `Often edited together (${pair.count} times)`,
290767
+ relationship: "co-edited",
290768
+ confidence: pair.weight
290801
290769
  });
290802
290770
  }
290803
290771
  }
290804
290772
  }
290805
- return imports;
290806
- }
290807
- extractImportedSymbols(importLine) {
290808
- const braceMatch = importLine.match(/\{([^}]+)\}/);
290809
- if (!braceMatch) {
290810
- const defaultMatch = importLine.match(/import\s+(?:type\s+)?(\w+)\s+from/);
290811
- return defaultMatch?.[1] ? [defaultMatch[1]] : [];
290773
+ if (fc && fc.editGuidance.length > 0) {
290774
+ paragraphs.push({
290775
+ heading: "Edit Guidance",
290776
+ text: fc.editGuidance.join(" "),
290777
+ importance: "medium"
290778
+ });
290812
290779
  }
290813
- return braceMatch[1].split(",").map((s) => {
290814
- const parts2 = s.trim().split(/\s+as\s+/);
290815
- return parts2[parts2.length - 1].trim();
290816
- }).filter(Boolean);
290780
+ return {
290781
+ filePath,
290782
+ roleSummary,
290783
+ paragraphs,
290784
+ quickFacts,
290785
+ warnings,
290786
+ relatedFiles,
290787
+ overlays
290788
+ };
290817
290789
  }
290818
- resolveImportPath(sourcePath, fromFile) {
290819
- if (!sourcePath.startsWith(".")) return sourcePath;
290820
- const dir = dirname6(fromFile);
290821
- const resolved = resolve2(dir, sourcePath);
290822
- const extensions = [".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.tsx", "/index.js"];
290823
- for (const ext2 of extensions) {
290824
- const candidate = resolved + ext2;
290825
- try {
290826
- accessSync(candidate);
290827
- return candidate;
290828
- } catch {
290790
+ /**
290791
+ * Generate a compact markdown explanation for AI agent consumption.
290792
+ */
290793
+ explainForAgent(filePath, ctx) {
290794
+ const explanation = this.explain(filePath, ctx);
290795
+ const lines = [];
290796
+ lines.push(`## ${shortName(filePath)}: ${explanation.roleSummary}`);
290797
+ lines.push("");
290798
+ if (explanation.quickFacts.length > 0) {
290799
+ lines.push(explanation.quickFacts.map((f) => `**${f.label}**: ${f.value}`).join(" \xB7 "));
290800
+ lines.push("");
290801
+ }
290802
+ for (const p of explanation.paragraphs.filter((p2) => p2.importance === "critical" || p2.importance === "high")) {
290803
+ lines.push(`### ${p.heading}`);
290804
+ lines.push(p.text);
290805
+ lines.push("");
290806
+ }
290807
+ if (explanation.warnings.length > 0) {
290808
+ lines.push("### Warnings");
290809
+ for (const w of explanation.warnings) {
290810
+ const icon = w.severity === "error" ? "MUST FIX" : w.severity === "warning" ? "SHOULD FIX" : "NOTE";
290811
+ lines.push(`- **[${icon}]** ${w.message} \u2014 ${w.action}`);
290829
290812
  }
290813
+ lines.push("");
290830
290814
  }
290831
- return resolved;
290832
- }
290833
- // ═══════════════════════════════════════════════════════════════════════════
290834
- // CALL EDGES (basic extraction)
290835
- // ═══════════════════════════════════════════════════════════════════════════
290836
- extractCallEdges(content, symbols, filePath) {
290837
- const edges = [];
290838
- const functionNames = new Set(symbols.filter((s) => s.kind === "function" || s.kind === "method").map((s) => s.name));
290839
- for (const caller of symbols) {
290840
- if (caller.kind !== "function" && caller.kind !== "method") continue;
290841
- const body2 = content.split("\n").slice(caller.startLine - 1, caller.endLine).join("\n");
290842
- for (const calleeName of functionNames) {
290843
- if (calleeName === caller.name) continue;
290844
- const callRe = new RegExp(`\\b${calleeName}\\s*\\(`, "g");
290845
- if (callRe.test(body2)) {
290846
- const callee = symbols.find((s) => s.name === calleeName);
290847
- if (callee) {
290848
- edges.push({
290849
- callerId: caller.id,
290850
- calleeId: callee.id,
290851
- callerName: caller.name,
290852
- calleeName: callee.name,
290853
- callerFile: filePath,
290854
- calleeFile: filePath
290855
- });
290856
- }
290857
- }
290815
+ if (explanation.relatedFiles.length > 0) {
290816
+ lines.push("### Related Files");
290817
+ for (const rf of explanation.relatedFiles.slice(0, 5)) {
290818
+ lines.push(`- \`${rf.filePath}\` \u2014 ${rf.reason}`);
290858
290819
  }
290820
+ lines.push("");
290859
290821
  }
290860
- return edges;
290822
+ return lines.join("\n");
290823
+ }
290824
+ /**
290825
+ * Generate IDE overlay data for the VS Code extension to consume.
290826
+ */
290827
+ getIDEOverlays(filePath, ctx) {
290828
+ const explanation = this.explain(filePath, ctx);
290829
+ return explanation.overlays;
290830
+ }
290831
+ /**
290832
+ * Generate a health report explanation.
290833
+ */
290834
+ explainHealth(health, dna) {
290835
+ const lines = [];
290836
+ const dims = health.dimensions;
290837
+ lines.push(`## Codebase Health: ${health.overall}/100`);
290838
+ lines.push("");
290839
+ const entries = [
290840
+ { name: "Architecture", score: dims.architecture, explain: this.explainArchScore(dims.architecture, dna) },
290841
+ { name: "Test Coverage", score: dims.testCoverage, explain: this.explainTestScore(dims.testCoverage) },
290842
+ { name: "Conventions", score: dims.conventions, explain: this.explainConventionScore(dims.conventions, dna) },
290843
+ { name: "Dependencies", score: dims.dependencies, explain: this.explainDependencyScore(dims.dependencies, dna) },
290844
+ { name: "Security", score: dims.security, explain: dims.security >= 80 ? "No critical security concerns detected" : "Critical risk areas identified" },
290845
+ { name: "Complexity", score: dims.complexity, explain: dims.complexity >= 80 ? "Complexity is well-managed" : "High-complexity hotspots detected" }
290846
+ ];
290847
+ for (const entry of entries) {
290848
+ const bar = this.renderBar(entry.score);
290849
+ lines.push(`${bar} **${entry.name}**: ${entry.score}/100 \u2014 ${entry.explain}`);
290850
+ }
290851
+ return lines.join("\n");
290861
290852
  }
290862
290853
  // ═══════════════════════════════════════════════════════════════════════════
290863
- // HELPERS
290854
+ // PRIVATE
290864
290855
  // ═══════════════════════════════════════════════════════════════════════════
290865
- isExported(content, name2, language) {
290866
- if (language === "python") return true;
290867
- const re = new RegExp(`export\\s+(?:default\\s+)?(?:async\\s+)?(?:function|class|const|let|var|type|interface|enum)\\s+${name2}\\b`);
290868
- if (re.test(content)) return true;
290869
- const reExport = new RegExp(`export\\s+\\{[^}]*\\b${name2}\\b[^}]*\\}`);
290870
- return reExport.test(content);
290856
+ buildRoleSummary(filePath, role, ctx) {
290857
+ const parts2 = [];
290858
+ switch (role) {
290859
+ case "service":
290860
+ parts2.push("Business logic service");
290861
+ break;
290862
+ case "route-handler":
290863
+ parts2.push("API route handler");
290864
+ break;
290865
+ case "component":
290866
+ parts2.push("UI component");
290867
+ break;
290868
+ case "repository":
290869
+ parts2.push("Data access layer");
290870
+ break;
290871
+ case "middleware":
290872
+ parts2.push("Request middleware");
290873
+ break;
290874
+ case "test":
290875
+ parts2.push("Test file");
290876
+ break;
290877
+ case "config":
290878
+ parts2.push("Configuration");
290879
+ break;
290880
+ case "type":
290881
+ parts2.push("Type definitions");
290882
+ break;
290883
+ case "util":
290884
+ parts2.push("Utility module");
290885
+ break;
290886
+ case "entry":
290887
+ parts2.push("Entry point");
290888
+ break;
290889
+ default:
290890
+ parts2.push("Source file");
290891
+ }
290892
+ if (ctx.fileContext) {
290893
+ if (ctx.fileContext.layer) parts2.push(`in ${ctx.fileContext.layer} layer`);
290894
+ if (ctx.fileContext.dependedOnBy.length > 10) parts2.push("(high-impact)");
290895
+ }
290896
+ return parts2.join(" ");
290871
290897
  }
290872
- countParams(signature) {
290873
- const parenMatch = signature.match(/\(([^)]*)\)/);
290874
- if (!parenMatch || !parenMatch[1].trim()) return 0;
290875
- return parenMatch[1].split(",").length;
290898
+ explainArchScore(score, dna) {
290899
+ if (score >= 80) return `Strong architecture with ${dna.patterns.length} recognized patterns`;
290900
+ if (score >= 50) return `Moderate architecture \u2014 ${dna.patterns.length} patterns detected, room to strengthen boundaries`;
290901
+ return "Architecture needs attention \u2014 few recognized patterns or boundaries";
290876
290902
  }
290877
- countBranches(lines, startIdx, endIdx) {
290878
- let branches = 0;
290879
- const branchRe = /\b(if|else if|case|for|while|catch|&&|\|\||\?\?)\b/g;
290880
- for (let i2 = startIdx; i2 < Math.min(endIdx, lines.length); i2++) {
290881
- const matches = lines[i2].match(branchRe);
290882
- if (matches) branches += matches.length;
290883
- }
290884
- return branches;
290903
+ explainTestScore(score) {
290904
+ if (score >= 80) return "Good test coverage across source files";
290905
+ if (score >= 50) return "Moderate coverage \u2014 some source files lack tests";
290906
+ return "Low test coverage \u2014 many exported modules have no test files";
290885
290907
  }
290886
- findBlockEnd(lines, startIdx) {
290887
- let depth = 0;
290888
- let seenOpen = false;
290889
- for (let i2 = startIdx; i2 < lines.length; i2++) {
290890
- for (const ch of lines[i2]) {
290891
- if (ch === "{") {
290892
- depth++;
290893
- seenOpen = true;
290894
- } else if (ch === "}" && seenOpen) {
290895
- depth--;
290896
- if (depth <= 0) return i2 + 1;
290897
- }
290898
- }
290899
- }
290900
- return startIdx + 1;
290908
+ explainConventionScore(score, dna) {
290909
+ const strong = dna.conventions.filter((c) => c.confidence > 0.6).length;
290910
+ if (score >= 80) return `${strong} strong conventions enforced consistently`;
290911
+ if (score >= 50) return `${strong} conventions detected but inconsistently applied`;
290912
+ return "Few consistent conventions \u2014 codebase style varies across files";
290901
290913
  }
290902
- };
290903
- function mapTreeSitterKind(kind) {
290904
- const map = {
290905
- function: "function",
290906
- method: "method",
290907
- class: "class",
290908
- interface: "interface",
290909
- type: "type",
290910
- enum: "enum",
290911
- const: "variable",
290912
- variable: "variable",
290913
- struct: "class",
290914
- trait: "interface",
290915
- export: "variable"
290916
- };
290917
- return map[kind] ?? "function";
290918
- }
290919
- function flattenCodeSymbols(symbols) {
290920
- const flat = [];
290921
- for (const sym of symbols) {
290922
- flat.push({ name: sym.name, kind: sym.kind, line: sym.line, endLine: sym.endLine, signature: sym.signature });
290923
- if (sym.children && Array.isArray(sym.children)) {
290924
- flat.push(...flattenCodeSymbols(sym.children));
290925
- }
290914
+ explainDependencyScore(score, dna) {
290915
+ const circular = dna.boundaries.filter((b) => b.isCircular).length;
290916
+ if (score >= 80) return "Clean dependency graph with no circular dependencies";
290917
+ if (circular > 0) return `${circular} circular dependency${circular > 1 ? "ies" : "y"} detected \u2014 these increase coupling and make code harder to reason about`;
290918
+ return "Dependency health needs improvement";
290926
290919
  }
290927
- return flat;
290920
+ renderBar(score) {
290921
+ const filled = Math.round(score / 10);
290922
+ return "\u2588".repeat(filled) + "\u2591".repeat(10 - filled);
290923
+ }
290924
+ };
290925
+ function shortName(filePath) {
290926
+ const parts2 = filePath.split("/");
290927
+ return parts2[parts2.length - 1] ?? filePath;
290928
290928
  }
290929
290929
 
290930
290930
  // ../context-engine/dist/index.js
@@ -291808,7 +291808,7 @@ var ContextEngine = class {
291808
291808
  // src/server.ts
291809
291809
  init_dist();
291810
291810
 
291811
- // ../subscriptions/dist/chunk-SS6N4T7D.js
291811
+ // ../subscriptions/dist/chunk-PGIBAA63.js
291812
291812
  var PLAN_IDS = ["free", "pro", "team", "enterprise"];
291813
291813
  var PLAN_RANK = Object.fromEntries(
291814
291814
  PLAN_IDS.map((id, index2) => [id, index2])
@@ -291877,19 +291877,16 @@ if (typeof process !== "undefined") {
291877
291877
  var PLAN_DEFINITIONS = {
291878
291878
  free: {
291879
291879
  displayName: "Free",
291880
- tagline: "Real scans and truthpack on your repo \u2014 upgrade when you want automation.",
291880
+ tagline: "$0 forever, no credit card required.",
291881
291881
  monthlyPriceUsd: 0,
291882
291882
  priceLabel: "$0",
291883
291883
  billingInterval: "month",
291884
291884
  badgeToken: "tier-free",
291885
291885
  highlights: [
291886
- "5 scans per day",
291887
- "Full scan summaries (counts & severity) on every run",
291888
- "Deep detail (why, fix, snippets) on 5 findings per scan",
291889
- "Ship score & basic report",
291890
- "PDF/HTML scan reports (CLI)",
291891
- "Firewall observe mode",
291892
- "No AI auto-fix on Free (upgrade to apply fixes)"
291886
+ "Unlimited scans",
291887
+ "Issue counts + severity",
291888
+ "Trust score",
291889
+ "All 16 detection engines"
291893
291890
  ],
291894
291891
  bestFor: "Individual devs exploring AI code quality",
291895
291892
  popular: false,
@@ -291899,29 +291896,33 @@ var PLAN_DEFINITIONS = {
291899
291896
  },
291900
291897
  pro: {
291901
291898
  displayName: "Pro",
291902
- tagline: "Full verification for you and your team \u2014 fix, prove, and gate AI-generated code.",
291903
- monthlyPriceUsd: 19,
291904
- priceLabel: "$19/mo",
291899
+ tagline: "$9.99/mo or $99.99/yr (save 17%).",
291900
+ monthlyPriceUsd: 9.99,
291901
+ priceLabel: "$9.99/mo",
291905
291902
  billingInterval: "month",
291906
291903
  badgeToken: "tier-pro",
291907
291904
  highlights: [
291908
- "Everything in Free, plus unlimited daily scans, full finding details, and AI auto-fix",
291909
- "Sandbox, Reality Mode, ISL Studio, and advanced scan / proof workflows",
291910
- "CommitShield, CI integration, PR comments, status checks, and merge protection",
291911
- "Context Engine, Firewall Agent, MCP server, and API access",
291912
- "Cloud sync, proof history, verified badges, and priority support"
291905
+ "Unlimited scans",
291906
+ "Full evidence reports",
291907
+ "Line-level detail (file, line number, fix path)",
291908
+ "All 16 detection engines + fix suggestions",
291909
+ "Fix suggestions",
291910
+ "Scan history & trends",
291911
+ "SARIF export for CI/CD",
291912
+ "CLI with no throttle",
291913
+ "Project-wide scanning"
291913
291914
  ],
291914
- bestFor: "Builders and teams shipping AI-assisted code with proof",
291915
+ bestFor: "Builders shipping AI-assisted code with proof",
291915
291916
  popular: true,
291916
- cta: "Get Pro",
291917
+ cta: "Start Pro",
291917
291918
  ctaVariant: "default",
291918
291919
  supportLevel: "standard"
291919
291920
  },
291920
291921
  team: {
291921
291922
  displayName: "Team",
291922
291923
  tagline: "Verification-first building at scale.",
291923
- monthlyPriceUsd: 19,
291924
- priceLabel: "$19/mo",
291924
+ monthlyPriceUsd: 9.99,
291925
+ priceLabel: "$9.99/mo",
291925
291926
  billingInterval: "month",
291926
291927
  badgeToken: "tier-team",
291927
291928
  highlights: [
@@ -291944,8 +291945,8 @@ var PLAN_DEFINITIONS = {
291944
291945
  enterprise: {
291945
291946
  displayName: "Enterprise",
291946
291947
  tagline: "Teams, compliance, and scale.",
291947
- monthlyPriceUsd: 19,
291948
- priceLabel: "$19/mo",
291948
+ monthlyPriceUsd: 9.99,
291949
+ priceLabel: "$9.99/mo",
291949
291950
  billingInterval: "month",
291950
291951
  badgeToken: "tier-enterprise",
291951
291952
  highlights: [
@@ -292077,6 +292078,7 @@ var PLAN_QUOTAS = {
292077
292078
  maxSeats: 1,
292078
292079
  autoFixesPerMonth: 0,
292079
292080
  scansPerMonth: Infinity,
292081
+ /** Taste-then-gate: daily scan cap enforced server-side for signed-in free users (see server-scan-meter). */
292080
292082
  scansPerDay: 5,
292081
292083
  vibePromptGenerationsPerMonth: 0,
292082
292084
  workflowCopiesPerMonth: 0,
@@ -292302,10 +292304,11 @@ var ENTITLEMENTS = {
292302
292304
  };
292303
292305
  var FREE_SET = /* @__PURE__ */ new Set([
292304
292306
  // ── Core free features (product spec: sign-in required for all) ──────────
292305
- // Free users ONLY get: 5 scans/day, kickoff, doctor, roast, reports,
292307
+ // Free users get: unlimited scans (summary only), kickoff, doctor, roast, reports,
292306
292308
  // truthpack, codegraph, wikicode, vibe flow. Everything else is PAID.
292309
+ // For new features, prefer isPaidPlan() over adding new entitlement keys.
292307
292310
  ENTITLEMENTS.SCAN_BASIC,
292308
- // 5 scans/day (quota enforced server-side)
292311
+ // unlimited scans, gated evidence (summary only for free)
292309
292312
  ENTITLEMENTS.KICKOFF_CONNECT,
292310
292313
  // vibecheckAI-Official kickoff / connect my project
292311
292314
  ENTITLEMENTS.DOCTOR,
@@ -292525,6 +292528,210 @@ if (typeof process !== "undefined") {
292525
292528
  }
292526
292529
  }
292527
292530
  }
292531
+ var ENTITLEMENTS_V2 = {
292532
+ // ── FREE ────────────────────────────────────────────────────────────────
292533
+ SCAN: "v2.scan",
292534
+ SCAN_WORKSPACE: "v2.scan_workspace",
292535
+ // ── PRO ─────────────────────────────────────────────────────────────────
292536
+ GITHUB_ACTION: "v2.github_action",
292537
+ FULL_EVIDENCE: "v2.full_evidence",
292538
+ SARIF_EXPORT: "v2.sarif_export",
292539
+ API_ACCESS: "v2.api_access",
292540
+ PRIORITY_ENGINES: "v2.priority_engines",
292541
+ ADVANCED_REPORT: "v2.advanced_report",
292542
+ AUTOFIX: "v2.autofix",
292543
+ CI_BLOCK: "v2.ci_block",
292544
+ CONTEXT_ENGINE: "v2.context_engine",
292545
+ SANDBOX: "v2.sandbox",
292546
+ SUPPORT: "v2.support",
292547
+ // ── TEAM ────────────────────────────────────────────────────────────────
292548
+ TEAM_COLLABORATION: "v2.team_collaboration",
292549
+ // ── ENTERPRISE ──────────────────────────────────────────────────────────
292550
+ ENTERPRISE_COMPLIANCE: "v2.enterprise_compliance"
292551
+ };
292552
+ var LEGACY_TO_V2_MAP = {
292553
+ // ── FREE tier keys ──────────────────────────────────────────────────────
292554
+ [ENTITLEMENTS.SCAN_BASIC]: ENTITLEMENTS_V2.SCAN,
292555
+ [ENTITLEMENTS.SCAN_UNLIMITED]: ENTITLEMENTS_V2.SCAN_WORKSPACE,
292556
+ [ENTITLEMENTS.SHIP_SCORE]: ENTITLEMENTS_V2.SCAN_WORKSPACE,
292557
+ [ENTITLEMENTS.DOCTOR]: ENTITLEMENTS_V2.SCAN_WORKSPACE,
292558
+ [ENTITLEMENTS.ROAST]: ENTITLEMENTS_V2.SCAN_WORKSPACE,
292559
+ [ENTITLEMENTS.REPORTS_HTML]: ENTITLEMENTS_V2.SCAN,
292560
+ [ENTITLEMENTS.CHECKPOINT]: null,
292561
+ // Not visible in UI
292562
+ [ENTITLEMENTS.MISSIONS_VIEW]: null,
292563
+ // Missions merged into autofix
292564
+ [ENTITLEMENTS.TEMPLATES_BROWSE]: null,
292565
+ // Templates culled (P8)
292566
+ [ENTITLEMENTS.TEMPLATES_INSTALL]: null,
292567
+ // Templates culled (P8)
292568
+ [ENTITLEMENTS.TRUTHPACK_GENERATE]: null,
292569
+ // Truthpack culled (P8)
292570
+ [ENTITLEMENTS.TRUTHPACK_VALIDATE]: null,
292571
+ // Truthpack culled (P8)
292572
+ [ENTITLEMENTS.KICKOFF_CONNECT]: null,
292573
+ // Merged into onboarding
292574
+ [ENTITLEMENTS.CODEGRAPH_VIEW]: ENTITLEMENTS_V2.SCAN_WORKSPACE,
292575
+ [ENTITLEMENTS.WIKICODE_VIEW]: ENTITLEMENTS_V2.SCAN_WORKSPACE,
292576
+ [ENTITLEMENTS.VIBE_FLOW]: null,
292577
+ // Culled (P8)
292578
+ [ENTITLEMENTS.REVIEW_WORKFLOWS]: null,
292579
+ // Culled (P8)
292580
+ [ENTITLEMENTS.FLOW_WORKFLOWS]: null,
292581
+ // Culled (P8)
292582
+ [ENTITLEMENTS.ATLAS]: ENTITLEMENTS_V2.SCAN_WORKSPACE,
292583
+ [ENTITLEMENTS.ISL_STUDIO]: null,
292584
+ // Free to browse; generation is credit-based
292585
+ [ENTITLEMENTS.REPORTS_PDF]: ENTITLEMENTS_V2.ADVANCED_REPORT,
292586
+ // ── PRO tier keys ───────────────────────────────────────────────────────
292587
+ [ENTITLEMENTS.FORGE_BASIC]: null,
292588
+ // Forge culled
292589
+ [ENTITLEMENTS.PROMPT_TEMPLATES_BASIC]: null,
292590
+ // Part of context_engine
292591
+ [ENTITLEMENTS.WATCH_MODE]: null,
292592
+ // Not actively used
292593
+ [ENTITLEMENTS.TRACE_ANALYSIS]: null,
292594
+ // Not visible
292595
+ [ENTITLEMENTS.FIREWALL_OBSERVE]: ENTITLEMENTS_V2.FULL_EVIDENCE,
292596
+ [ENTITLEMENTS.FILE_LOCKING]: null,
292597
+ // Feature incomplete
292598
+ [ENTITLEMENTS.AUTOFIX_LIMITED]: ENTITLEMENTS_V2.AUTOFIX,
292599
+ [ENTITLEMENTS.COMMIT_SHIELD_WARNINGS]: ENTITLEMENTS_V2.AUTOFIX,
292600
+ [ENTITLEMENTS.GITHUB_ACTION_WARN]: ENTITLEMENTS_V2.GITHUB_ACTION,
292601
+ [ENTITLEMENTS.VIBE_PROMPT]: null,
292602
+ // Culled
292603
+ [ENTITLEMENTS.PROOF_VIEW]: ENTITLEMENTS_V2.FULL_EVIDENCE,
292604
+ [ENTITLEMENTS.AUTOFIX_UNLIMITED]: ENTITLEMENTS_V2.AUTOFIX,
292605
+ [ENTITLEMENTS.AUTOFIX_APPLY]: ENTITLEMENTS_V2.AUTOFIX,
292606
+ [ENTITLEMENTS.REALITY_MODE]: ENTITLEMENTS_V2.FULL_EVIDENCE,
292607
+ [ENTITLEMENTS.COMMIT_SHIELD_FULL]: ENTITLEMENTS_V2.CI_BLOCK,
292608
+ [ENTITLEMENTS.COMMIT_SHIELD_AUDITOR]: null,
292609
+ // Auditor culled
292610
+ [ENTITLEMENTS.REPORTS_EXECUTIVE]: ENTITLEMENTS_V2.ADVANCED_REPORT,
292611
+ [ENTITLEMENTS.CLOUD_SYNC]: null,
292612
+ // Implicit in paid
292613
+ [ENTITLEMENTS.CERTIFY]: ENTITLEMENTS_V2.FULL_EVIDENCE,
292614
+ [ENTITLEMENTS.BADGE_VERIFIED]: null,
292615
+ // Badges culled (P8)
292616
+ [ENTITLEMENTS.PROOF_HISTORY]: ENTITLEMENTS_V2.FULL_EVIDENCE,
292617
+ [ENTITLEMENTS.SHAREABLE_REPORTS]: ENTITLEMENTS_V2.ADVANCED_REPORT,
292618
+ [ENTITLEMENTS.PRIORITY_SUPPORT]: ENTITLEMENTS_V2.SUPPORT,
292619
+ [ENTITLEMENTS.GITHUB_ACTION]: ENTITLEMENTS_V2.GITHUB_ACTION,
292620
+ [ENTITLEMENTS.CI_INTEGRATION]: ENTITLEMENTS_V2.GITHUB_ACTION,
292621
+ [ENTITLEMENTS.API_ACCESS]: ENTITLEMENTS_V2.API_ACCESS,
292622
+ [ENTITLEMENTS.PR_COMMENTS]: ENTITLEMENTS_V2.GITHUB_ACTION,
292623
+ [ENTITLEMENTS.STATUS_CHECKS]: ENTITLEMENTS_V2.GITHUB_ACTION,
292624
+ [ENTITLEMENTS.BRANCH_PROTECTION]: ENTITLEMENTS_V2.CI_BLOCK,
292625
+ [ENTITLEMENTS.WEBHOOK_INTEGRATION]: null,
292626
+ // Webhooks culled
292627
+ [ENTITLEMENTS.CI_GATE_BLOCK]: ENTITLEMENTS_V2.CI_BLOCK,
292628
+ [ENTITLEMENTS.CONTEXT_ENGINE]: ENTITLEMENTS_V2.CONTEXT_ENGINE,
292629
+ [ENTITLEMENTS.ISL_VERIFY]: null,
292630
+ // ISL culled
292631
+ [ENTITLEMENTS.DEEP_SCAN]: ENTITLEMENTS_V2.PRIORITY_ENGINES,
292632
+ [ENTITLEMENTS.SCAN_PRO_ENGINES]: ENTITLEMENTS_V2.PRIORITY_ENGINES,
292633
+ [ENTITLEMENTS.DRIFT_DETECTION]: ENTITLEMENTS_V2.FULL_EVIDENCE,
292634
+ [ENTITLEMENTS.CHAOS_AGENT]: null,
292635
+ // Chaos culled
292636
+ [ENTITLEMENTS.MODEL_FINGERPRINT]: null,
292637
+ // Model fingerprint culled
292638
+ [ENTITLEMENTS.PROVENANCE_TRACKING]: ENTITLEMENTS_V2.FULL_EVIDENCE,
292639
+ [ENTITLEMENTS.FIREWALL_AGENT]: ENTITLEMENTS_V2.FULL_EVIDENCE,
292640
+ [ENTITLEMENTS.FIREWALL_ENFORCE]: ENTITLEMENTS_V2.CI_BLOCK,
292641
+ [ENTITLEMENTS.FIREWALL_LOCKDOWN]: null,
292642
+ // Lockdown not implemented
292643
+ [ENTITLEMENTS.FORGE_EXTENDED]: null,
292644
+ // Forge culled
292645
+ [ENTITLEMENTS.PROMPT_TEMPLATES_PRO]: null,
292646
+ // Part of context_engine
292647
+ [ENTITLEMENTS.AI_GENERATION]: ENTITLEMENTS_V2.SANDBOX,
292648
+ [ENTITLEMENTS.REPLAY_VIEWER_FULL]: null,
292649
+ // Replay culled
292650
+ // ── Sandbox & AI ────────────────────────────────────────────────────────
292651
+ [ENTITLEMENTS.SANDBOX_ACCESS]: ENTITLEMENTS_V2.SANDBOX,
292652
+ [ENTITLEMENTS.SANDBOX_GENERATIONS]: ENTITLEMENTS_V2.SANDBOX,
292653
+ [ENTITLEMENTS.SANDBOX_PREMIUM_MODELS]: null,
292654
+ // Not offered
292655
+ [ENTITLEMENTS.SANDBOX_SLASH_COMMANDS]: ENTITLEMENTS_V2.SANDBOX,
292656
+ [ENTITLEMENTS.SANDBOX_AGENT_MODES]: null,
292657
+ // Agent modes culled
292658
+ [ENTITLEMENTS.SANDBOX_PROOF_BUNDLES]: null,
292659
+ // Proof bundles culled
292660
+ [ENTITLEMENTS.SANDBOX_CLAUDE_SKILLS]: null,
292661
+ // Skills culled
292662
+ [ENTITLEMENTS.AI_CONFIDENCE_METER]: null,
292663
+ // Not visible
292664
+ [ENTITLEMENTS.AI_HALLUCINATION_SHIELD]: null,
292665
+ // Part of core scanning
292666
+ [ENTITLEMENTS.VERIFIED_BUILD_CERT]: null,
292667
+ // Build certs culled
292668
+ // ── Team keys ───────────────────────────────────────────────────────────
292669
+ [ENTITLEMENTS.TEAM_DASHBOARD]: ENTITLEMENTS_V2.TEAM_COLLABORATION,
292670
+ [ENTITLEMENTS.TEAM_COLLABORATION]: ENTITLEMENTS_V2.TEAM_COLLABORATION,
292671
+ [ENTITLEMENTS.TEAM_CROSS_REPO_SCANNING]: ENTITLEMENTS_V2.TEAM_COLLABORATION,
292672
+ [ENTITLEMENTS.TEAM_SHARED_POLICIES]: ENTITLEMENTS_V2.TEAM_COLLABORATION,
292673
+ [ENTITLEMENTS.TEAM_PROVENANCE_INSIGHTS]: ENTITLEMENTS_V2.TEAM_COLLABORATION,
292674
+ [ENTITLEMENTS.TEAM_ADMIN_POLICY_ENFORCEMENT]: ENTITLEMENTS_V2.TEAM_COLLABORATION,
292675
+ [ENTITLEMENTS.TEAM_AUDIT_LOG_EXPORT]: ENTITLEMENTS_V2.ENTERPRISE_COMPLIANCE,
292676
+ [ENTITLEMENTS.TEAM_CONTEXT_ENGINE_SHARED]: ENTITLEMENTS_V2.TEAM_COLLABORATION,
292677
+ [ENTITLEMENTS.TEAM_PRIORITY_QUEUE]: null,
292678
+ // Not implemented
292679
+ [ENTITLEMENTS.TEAM_ANALYTICS]: ENTITLEMENTS_V2.TEAM_COLLABORATION,
292680
+ [ENTITLEMENTS.TEAM_SLACK_ALERTS]: null,
292681
+ // Slack culled
292682
+ [ENTITLEMENTS.TEAM_LEADERBOARDS]: null,
292683
+ // Gamification culled
292684
+ [ENTITLEMENTS.TEAM_BULK_INVITE]: ENTITLEMENTS_V2.TEAM_COLLABORATION,
292685
+ [ENTITLEMENTS.TEAM_ROLES]: ENTITLEMENTS_V2.TEAM_COLLABORATION,
292686
+ [ENTITLEMENTS.TEAM_SCAN_BUDGETS]: null,
292687
+ // Budgeting not implemented
292688
+ // ── Enterprise keys ─────────────────────────────────────────────────────
292689
+ [ENTITLEMENTS.COMPLIANCE_SOC2]: ENTITLEMENTS_V2.ENTERPRISE_COMPLIANCE,
292690
+ [ENTITLEMENTS.COMPLIANCE_HIPAA]: ENTITLEMENTS_V2.ENTERPRISE_COMPLIANCE,
292691
+ [ENTITLEMENTS.COMPLIANCE_PCI_DSS]: ENTITLEMENTS_V2.ENTERPRISE_COMPLIANCE,
292692
+ [ENTITLEMENTS.COMPLIANCE_GDPR]: ENTITLEMENTS_V2.ENTERPRISE_COMPLIANCE,
292693
+ [ENTITLEMENTS.COMPLIANCE_ISO27001]: ENTITLEMENTS_V2.ENTERPRISE_COMPLIANCE,
292694
+ [ENTITLEMENTS.SDK_GENERATOR]: null,
292695
+ // SDK gen culled
292696
+ [ENTITLEMENTS.POLICY_ENGINE]: null,
292697
+ // Part of context_engine
292698
+ [ENTITLEMENTS.SSO_SAML]: ENTITLEMENTS_V2.ENTERPRISE_COMPLIANCE,
292699
+ [ENTITLEMENTS.ON_PREMISE]: null,
292700
+ // On-premise not offered
292701
+ [ENTITLEMENTS.DEDICATED_SLA]: ENTITLEMENTS_V2.ENTERPRISE_COMPLIANCE,
292702
+ [ENTITLEMENTS.COMMIT_SHIELD_TEAM]: ENTITLEMENTS_V2.TEAM_COLLABORATION,
292703
+ [ENTITLEMENTS.COMMIT_SHIELD_ENTERPRISE]: ENTITLEMENTS_V2.ENTERPRISE_COMPLIANCE,
292704
+ [ENTITLEMENTS.COMMIT_SHIELD_COMPLIANCE]: ENTITLEMENTS_V2.ENTERPRISE_COMPLIANCE,
292705
+ [ENTITLEMENTS.ENTERPRISE_MULTI_REPO]: ENTITLEMENTS_V2.TEAM_COLLABORATION,
292706
+ [ENTITLEMENTS.ENTERPRISE_SIGNED_BUNDLES]: null
292707
+ // Not implemented
292708
+ };
292709
+ var V2_FREE_SET = /* @__PURE__ */ new Set([
292710
+ ENTITLEMENTS_V2.SCAN,
292711
+ ENTITLEMENTS_V2.SCAN_WORKSPACE
292712
+ ]);
292713
+ var V2_PRO_SET = /* @__PURE__ */ new Set([
292714
+ ...V2_FREE_SET,
292715
+ ENTITLEMENTS_V2.GITHUB_ACTION,
292716
+ ENTITLEMENTS_V2.FULL_EVIDENCE,
292717
+ ENTITLEMENTS_V2.SARIF_EXPORT,
292718
+ ENTITLEMENTS_V2.API_ACCESS,
292719
+ ENTITLEMENTS_V2.PRIORITY_ENGINES,
292720
+ ENTITLEMENTS_V2.ADVANCED_REPORT,
292721
+ ENTITLEMENTS_V2.AUTOFIX,
292722
+ ENTITLEMENTS_V2.CI_BLOCK,
292723
+ ENTITLEMENTS_V2.CONTEXT_ENGINE,
292724
+ ENTITLEMENTS_V2.SANDBOX,
292725
+ ENTITLEMENTS_V2.SUPPORT
292726
+ ]);
292727
+ var V2_TEAM_SET = /* @__PURE__ */ new Set([
292728
+ ...V2_PRO_SET,
292729
+ ENTITLEMENTS_V2.TEAM_COLLABORATION
292730
+ ]);
292731
+ var V2_ENTERPRISE_SET = /* @__PURE__ */ new Set([
292732
+ ...V2_TEAM_SET,
292733
+ ENTITLEMENTS_V2.ENTERPRISE_COMPLIANCE
292734
+ ]);
292528
292735
  var FEATURE_REGISTRY = {
292529
292736
  "Unlimited Auto-Fix": {
292530
292737
  entitlement: ENTITLEMENTS.AUTOFIX_UNLIMITED,
@@ -292761,7 +292968,7 @@ var FEATURE_REGISTRY = {
292761
292968
  entitlement: ENTITLEMENTS.DEEP_SCAN,
292762
292969
  title: "Deep Scan",
292763
292970
  subtitle: "Advanced security analysis",
292764
- benefits: ["14 engines", "Runtime proof", "Mock detection"],
292971
+ benefits: ["18 production engines", "Runtime proof", "Mock detection"],
292765
292972
  requiredPlan: "pro",
292766
292973
  category: "analysis"
292767
292974
  },
@@ -293997,7 +294204,7 @@ function createScanIdempotencyKey(prefix) {
293997
294204
  // src/mcp-scan-meter-client.ts
293998
294205
  var MCP_SCAN_METER_CLIENT = {
293999
294206
  type: "mcp",
294000
- version: "24.4.0"
294207
+ version: "24.4.3"
294001
294208
  };
294002
294209
 
294003
294210
  // src/server.ts
@@ -294171,10 +294378,10 @@ async function runRoast(targetPath, opts) {
294171
294378
  const preset = opts?.enginePreset ?? "full";
294172
294379
  const engineToggles = preset === "full" ? null : ENGINE_FOCUS_PRESETS[preset]();
294173
294380
  const { findings, workspaceRoot } = await executeScan(targetPath, engineToggles);
294174
- const score = findings.length > 0 ? computeTrustScore(findings).overall : 100;
294381
+ const score = findings.length > 0 ? computeTrustScore(findings).overall : void 0;
294175
294382
  const roast = formatRoastHtml(findings, {
294176
294383
  workspaceRoot,
294177
- score,
294384
+ ...score !== void 0 ? { score } : {},
294178
294385
  displayLimit: 25
294179
294386
  });
294180
294387
  return roast.text;