@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.
- package/LICENSE +21 -0
- package/README.md +1 -1
- package/dist/index.js +1919 -1712
- 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
|
|
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(
|
|
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-
|
|
289075
|
-
|
|
289076
|
-
var
|
|
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
|
-
|
|
289079
|
-
|
|
289080
|
-
|
|
289081
|
-
|
|
289082
|
-
|
|
289083
|
-
|
|
289084
|
-
|
|
289085
|
-
|
|
289086
|
-
|
|
289087
|
-
|
|
289088
|
-
|
|
289089
|
-
|
|
289090
|
-
|
|
289091
|
-
|
|
289092
|
-
|
|
289093
|
-
|
|
289094
|
-
|
|
289095
|
-
|
|
289096
|
-
|
|
289097
|
-
|
|
289098
|
-
|
|
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
|
-
|
|
289116
|
-
|
|
289117
|
-
|
|
289118
|
-
|
|
289119
|
-
|
|
289120
|
-
|
|
289121
|
-
|
|
289122
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
289129
|
-
const
|
|
289130
|
-
const
|
|
289131
|
-
|
|
289132
|
-
|
|
289133
|
-
|
|
289134
|
-
|
|
289135
|
-
|
|
289136
|
-
|
|
289137
|
-
|
|
289138
|
-
|
|
289139
|
-
|
|
289140
|
-
|
|
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
|
-
|
|
289174
|
-
|
|
289175
|
-
|
|
289176
|
-
|
|
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
|
-
|
|
289184
|
-
|
|
289185
|
-
|
|
289186
|
-
|
|
289187
|
-
|
|
289188
|
-
|
|
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
|
-
|
|
289191
|
-
|
|
289192
|
-
|
|
289193
|
-
|
|
289194
|
-
|
|
289195
|
-
|
|
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
|
-
|
|
289204
|
-
|
|
289205
|
-
|
|
289206
|
-
|
|
289207
|
-
|
|
289208
|
-
|
|
289209
|
-
|
|
289210
|
-
|
|
289211
|
-
|
|
289212
|
-
|
|
289213
|
-
|
|
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
|
-
|
|
289217
|
-
|
|
289218
|
-
const
|
|
289219
|
-
|
|
289220
|
-
|
|
289221
|
-
|
|
289222
|
-
|
|
289223
|
-
|
|
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
|
-
//
|
|
289345
|
+
// DATA LOADING (warm start)
|
|
289227
289346
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
289228
|
-
|
|
289229
|
-
|
|
289230
|
-
|
|
289231
|
-
|
|
289232
|
-
|
|
289233
|
-
const
|
|
289234
|
-
|
|
289235
|
-
|
|
289236
|
-
|
|
289237
|
-
|
|
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
|
-
|
|
289240
|
-
|
|
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
|
-
//
|
|
289390
|
+
// EMBEDDING STORAGE
|
|
289254
289391
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
289255
|
-
|
|
289256
|
-
|
|
289257
|
-
|
|
289258
|
-
|
|
289259
|
-
|
|
289260
|
-
|
|
289261
|
-
|
|
289262
|
-
|
|
289263
|
-
|
|
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
|
-
|
|
289273
|
-
|
|
289274
|
-
|
|
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
|
-
|
|
289277
|
-
|
|
289278
|
-
|
|
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
|
-
|
|
289285
|
-
|
|
289286
|
-
|
|
289287
|
-
const
|
|
289288
|
-
|
|
289289
|
-
|
|
289290
|
-
|
|
289291
|
-
|
|
289292
|
-
|
|
289293
|
-
|
|
289294
|
-
|
|
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
|
-
|
|
289329
|
-
|
|
289330
|
-
|
|
289331
|
-
|
|
289332
|
-
|
|
289333
|
-
|
|
289334
|
-
|
|
289335
|
-
|
|
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
|
-
|
|
289347
|
-
|
|
289348
|
-
|
|
289349
|
-
|
|
289350
|
-
|
|
289351
|
-
|
|
289352
|
-
|
|
289353
|
-
|
|
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
|
-
|
|
289365
|
-
|
|
289366
|
-
|
|
289367
|
-
|
|
289368
|
-
|
|
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
|
-
|
|
289386
|
-
|
|
289387
|
-
|
|
289388
|
-
|
|
289389
|
-
|
|
289390
|
-
|
|
289391
|
-
|
|
289392
|
-
|
|
289393
|
-
|
|
289394
|
-
|
|
289395
|
-
|
|
289396
|
-
|
|
289397
|
-
|
|
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
|
-
//
|
|
289466
|
+
// CLEANUP
|
|
289425
289467
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
289426
|
-
|
|
289427
|
-
|
|
289428
|
-
|
|
289429
|
-
|
|
289430
|
-
|
|
289431
|
-
|
|
289432
|
-
|
|
289433
|
-
|
|
289434
|
-
|
|
289435
|
-
|
|
289436
|
-
|
|
289437
|
-
|
|
289438
|
-
|
|
289439
|
-
|
|
289440
|
-
|
|
289441
|
-
|
|
289442
|
-
|
|
289443
|
-
|
|
289444
|
-
|
|
289445
|
-
|
|
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
|
-
//
|
|
289490
|
+
// PRIVATE — File Discovery
|
|
289496
289491
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
289497
|
-
|
|
289498
|
-
const
|
|
289499
|
-
|
|
289500
|
-
|
|
289501
|
-
|
|
289502
|
-
|
|
289503
|
-
|
|
289504
|
-
|
|
289505
|
-
|
|
289506
|
-
|
|
289507
|
-
|
|
289508
|
-
|
|
289509
|
-
|
|
289510
|
-
|
|
289511
|
-
|
|
289512
|
-
|
|
289513
|
-
|
|
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
|
-
|
|
289517
|
-
|
|
289518
|
-
|
|
289519
|
-
|
|
289520
|
-
|
|
289521
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
289533
|
+
// PRIVATE — Storage
|
|
289534
289534
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
289535
|
-
|
|
289536
|
-
|
|
289537
|
-
const
|
|
289538
|
-
|
|
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
|
|
289549
|
-
(
|
|
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
|
|
289552
|
-
(
|
|
289553
|
-
)
|
|
289554
|
-
|
|
289555
|
-
|
|
289556
|
-
|
|
289557
|
-
|
|
289558
|
-
|
|
289559
|
-
|
|
289560
|
-
const
|
|
289561
|
-
|
|
289562
|
-
|
|
289563
|
-
|
|
289564
|
-
|
|
289565
|
-
|
|
289566
|
-
|
|
289567
|
-
|
|
289568
|
-
|
|
289569
|
-
|
|
289570
|
-
|
|
289571
|
-
|
|
289572
|
-
|
|
289573
|
-
|
|
289574
|
-
|
|
289575
|
-
|
|
289576
|
-
|
|
289577
|
-
|
|
289578
|
-
|
|
289579
|
-
|
|
289580
|
-
|
|
289581
|
-
|
|
289582
|
-
|
|
289583
|
-
|
|
289584
|
-
|
|
289585
|
-
|
|
289586
|
-
|
|
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
|
-
|
|
289698
|
-
|
|
289699
|
-
|
|
289700
|
-
|
|
289701
|
-
|
|
289702
|
-
|
|
289703
|
-
|
|
289704
|
-
|
|
289705
|
-
|
|
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
|
-
|
|
289711
|
-
|
|
289712
|
-
|
|
289713
|
-
|
|
289714
|
-
|
|
289715
|
-
|
|
289716
|
-
|
|
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
|
-
|
|
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
|
-
|
|
289775
|
-
|
|
289776
|
-
|
|
289777
|
-
|
|
289778
|
-
|
|
289779
|
-
|
|
289780
|
-
|
|
289781
|
-
|
|
289782
|
-
|
|
289783
|
-
|
|
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
|
-
|
|
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
|
-
|
|
289840
|
-
const
|
|
289841
|
-
|
|
289842
|
-
|
|
289843
|
-
|
|
289844
|
-
|
|
289845
|
-
|
|
289846
|
-
|
|
289847
|
-
|
|
289848
|
-
|
|
289849
|
-
|
|
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
|
-
|
|
289882
|
-
|
|
289883
|
-
|
|
289884
|
-
|
|
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
|
-
|
|
289887
|
-
|
|
289888
|
-
|
|
289889
|
-
|
|
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
|
-
|
|
289892
|
-
const
|
|
289893
|
-
|
|
289894
|
-
|
|
289895
|
-
|
|
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
|
-
|
|
289898
|
-
const
|
|
289899
|
-
|
|
289900
|
-
|
|
289901
|
-
|
|
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
|
-
|
|
289904
|
-
const
|
|
289905
|
-
return
|
|
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
|
|
289909
|
-
|
|
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
|
-
|
|
289914
|
-
|
|
289915
|
-
|
|
289916
|
-
|
|
289917
|
-
|
|
289918
|
-
|
|
289919
|
-
|
|
289920
|
-
|
|
289921
|
-
|
|
289922
|
-
|
|
289923
|
-
|
|
289924
|
-
|
|
289925
|
-
|
|
289926
|
-
|
|
289927
|
-
|
|
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
|
-
|
|
289930
|
-
|
|
289931
|
-
|
|
289932
|
-
|
|
289933
|
-
|
|
289934
|
-
|
|
289935
|
-
|
|
289936
|
-
|
|
289937
|
-
|
|
289938
|
-
|
|
289939
|
-
|
|
289940
|
-
|
|
289941
|
-
|
|
289942
|
-
|
|
289943
|
-
|
|
289944
|
-
|
|
289945
|
-
|
|
289946
|
-
|
|
289947
|
-
|
|
289948
|
-
|
|
289949
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
289771
|
+
// EXPORTS
|
|
289961
289772
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
289962
|
-
|
|
289963
|
-
|
|
289964
|
-
|
|
289965
|
-
|
|
289966
|
-
|
|
289967
|
-
|
|
289968
|
-
|
|
289969
|
-
|
|
289970
|
-
|
|
289971
|
-
|
|
289972
|
-
|
|
289973
|
-
|
|
289974
|
-
|
|
289975
|
-
|
|
289976
|
-
|
|
289977
|
-
|
|
289978
|
-
|
|
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
|
-
|
|
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
|
|
290079
|
-
|
|
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
|
|
289831
|
+
return null;
|
|
290082
289832
|
}
|
|
290083
289833
|
}
|
|
290084
|
-
|
|
290085
|
-
|
|
290086
|
-
|
|
290087
|
-
|
|
290088
|
-
|
|
290089
|
-
|
|
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
|
-
//
|
|
289891
|
+
// IMPORTS
|
|
290093
289892
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
290094
|
-
|
|
290095
|
-
|
|
290096
|
-
|
|
290097
|
-
|
|
290098
|
-
|
|
290099
|
-
|
|
290100
|
-
|
|
290101
|
-
|
|
290102
|
-
|
|
290103
|
-
|
|
290104
|
-
|
|
290105
|
-
|
|
290106
|
-
|
|
290107
|
-
|
|
290108
|
-
|
|
290109
|
-
|
|
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
|
-
|
|
290113
|
-
|
|
290114
|
-
|
|
290115
|
-
|
|
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
|
-
|
|
290119
|
-
|
|
290120
|
-
|
|
290121
|
-
|
|
290122
|
-
|
|
290123
|
-
|
|
290124
|
-
|
|
290125
|
-
|
|
290126
|
-
|
|
290127
|
-
|
|
290128
|
-
|
|
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
|
-
|
|
290131
|
-
|
|
290132
|
-
|
|
290133
|
-
|
|
290134
|
-
const
|
|
290135
|
-
|
|
290136
|
-
|
|
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
|
-
|
|
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
|
-
|
|
290156
|
-
|
|
290157
|
-
|
|
290158
|
-
|
|
290159
|
-
|
|
290160
|
-
|
|
290161
|
-
|
|
290162
|
-
|
|
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
|
-
|
|
290165
|
-
|
|
289987
|
+
accessSync(candidate);
|
|
289988
|
+
return candidate;
|
|
290166
289989
|
} catch {
|
|
290167
289990
|
}
|
|
290168
289991
|
}
|
|
290169
|
-
|
|
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
|
-
//
|
|
289995
|
+
// CALL EDGES (basic extraction)
|
|
290185
289996
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
290186
|
-
|
|
290187
|
-
|
|
290188
|
-
|
|
290189
|
-
|
|
290190
|
-
|
|
290191
|
-
|
|
290192
|
-
|
|
290193
|
-
|
|
290194
|
-
|
|
290195
|
-
|
|
290196
|
-
|
|
290197
|
-
|
|
290198
|
-
|
|
290199
|
-
|
|
290200
|
-
|
|
290201
|
-
|
|
290202
|
-
|
|
290203
|
-
|
|
290204
|
-
|
|
290205
|
-
|
|
290206
|
-
|
|
290207
|
-
|
|
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
|
-
//
|
|
290024
|
+
// HELPERS
|
|
290230
290025
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
290231
|
-
|
|
290232
|
-
|
|
290233
|
-
|
|
290234
|
-
|
|
290235
|
-
const
|
|
290236
|
-
|
|
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
|
-
|
|
290255
|
-
|
|
290256
|
-
|
|
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
|
-
|
|
290268
|
-
|
|
290269
|
-
|
|
290270
|
-
|
|
290271
|
-
|
|
290272
|
-
|
|
290273
|
-
return
|
|
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
|
-
|
|
290277
|
-
|
|
290278
|
-
|
|
290279
|
-
|
|
290280
|
-
|
|
290281
|
-
|
|
290282
|
-
|
|
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
|
-
|
|
290286
|
-
|
|
290287
|
-
|
|
290288
|
-
|
|
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
|
-
|
|
290292
|
-
|
|
290293
|
-
|
|
290294
|
-
|
|
290295
|
-
|
|
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
|
-
*
|
|
290107
|
+
* Synthesize the full context — the complete brain dump for AI agents.
|
|
290299
290108
|
*/
|
|
290300
|
-
|
|
290301
|
-
const
|
|
290302
|
-
|
|
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
|
-
*
|
|
290132
|
+
* Generate context for a specific file — what an AI agent needs to know
|
|
290133
|
+
* before editing this file.
|
|
290309
290134
|
*/
|
|
290310
|
-
|
|
290311
|
-
this.
|
|
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
|
-
*
|
|
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
|
-
|
|
290317
|
-
this.
|
|
290318
|
-
|
|
290319
|
-
|
|
290320
|
-
|
|
290321
|
-
|
|
290322
|
-
|
|
290323
|
-
|
|
290324
|
-
|
|
290325
|
-
|
|
290326
|
-
|
|
290327
|
-
|
|
290328
|
-
|
|
290329
|
-
|
|
290330
|
-
|
|
290331
|
-
|
|
290332
|
-
|
|
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
|
-
|
|
290357
|
-
|
|
290358
|
-
|
|
290359
|
-
|
|
290360
|
-
|
|
290361
|
-
|
|
290362
|
-
|
|
290363
|
-
|
|
290364
|
-
|
|
290365
|
-
|
|
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
|
-
|
|
290370
|
-
|
|
290371
|
-
|
|
290372
|
-
|
|
290373
|
-
|
|
290374
|
-
|
|
290375
|
-
|
|
290376
|
-
|
|
290377
|
-
"
|
|
290378
|
-
|
|
290379
|
-
|
|
290380
|
-
|
|
290381
|
-
|
|
290382
|
-
|
|
290383
|
-
|
|
290384
|
-
|
|
290385
|
-
|
|
290386
|
-
|
|
290387
|
-
|
|
290388
|
-
|
|
290389
|
-
|
|
290390
|
-
|
|
290391
|
-
|
|
290392
|
-
|
|
290393
|
-
|
|
290394
|
-
|
|
290395
|
-
|
|
290396
|
-
|
|
290397
|
-
|
|
290398
|
-
|
|
290399
|
-
|
|
290400
|
-
|
|
290401
|
-
|
|
290402
|
-
|
|
290403
|
-
);
|
|
290404
|
-
for (const
|
|
290405
|
-
|
|
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
|
-
|
|
290420
|
-
|
|
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
|
-
|
|
290432
|
-
|
|
290433
|
-
|
|
290434
|
-
|
|
290435
|
-
|
|
290436
|
-
|
|
290437
|
-
|
|
290438
|
-
|
|
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
|
-
|
|
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
|
-
|
|
290446
|
-
|
|
290447
|
-
|
|
290448
|
-
|
|
290449
|
-
|
|
290450
|
-
|
|
290451
|
-
|
|
290452
|
-
|
|
290453
|
-
|
|
290454
|
-
|
|
290455
|
-
|
|
290456
|
-
|
|
290457
|
-
|
|
290458
|
-
|
|
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
|
-
//
|
|
290270
|
+
// RULES SUMMARY
|
|
290462
290271
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
290463
|
-
|
|
290464
|
-
|
|
290465
|
-
|
|
290466
|
-
|
|
290467
|
-
|
|
290468
|
-
|
|
290469
|
-
|
|
290470
|
-
|
|
290471
|
-
|
|
290472
|
-
|
|
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
|
-
|
|
290476
|
-
|
|
290477
|
-
|
|
290478
|
-
|
|
290479
|
-
|
|
290480
|
-
|
|
290481
|
-
|
|
290482
|
-
|
|
290483
|
-
|
|
290484
|
-
|
|
290485
|
-
|
|
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
|
-
|
|
290491
|
-
|
|
290492
|
-
|
|
290493
|
-
|
|
290494
|
-
|
|
290495
|
-
|
|
290496
|
-
|
|
290497
|
-
|
|
290498
|
-
|
|
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
|
-
|
|
290506
|
-
const
|
|
290507
|
-
|
|
290508
|
-
|
|
290509
|
-
|
|
290510
|
-
|
|
290511
|
-
|
|
290512
|
-
|
|
290513
|
-
|
|
290514
|
-
|
|
290515
|
-
|
|
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
|
-
|
|
290519
|
-
const
|
|
290520
|
-
|
|
290521
|
-
|
|
290522
|
-
|
|
290523
|
-
|
|
290524
|
-
|
|
290525
|
-
|
|
290526
|
-
|
|
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
|
-
|
|
290530
|
-
|
|
290531
|
-
|
|
290532
|
-
|
|
290533
|
-
|
|
290534
|
-
|
|
290535
|
-
|
|
290536
|
-
|
|
290537
|
-
|
|
290538
|
-
|
|
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
|
-
|
|
290542
|
-
const
|
|
290543
|
-
|
|
290544
|
-
|
|
290545
|
-
|
|
290546
|
-
|
|
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
|
-
|
|
290551
|
-
|
|
290552
|
-
}
|
|
290553
|
-
|
|
290554
|
-
|
|
290555
|
-
|
|
290556
|
-
|
|
290557
|
-
|
|
290558
|
-
|
|
290559
|
-
|
|
290560
|
-
|
|
290561
|
-
|
|
290562
|
-
|
|
290563
|
-
|
|
290564
|
-
|
|
290565
|
-
|
|
290566
|
-
|
|
290567
|
-
|
|
290568
|
-
|
|
290569
|
-
|
|
290570
|
-
|
|
290571
|
-
|
|
290572
|
-
|
|
290573
|
-
|
|
290574
|
-
|
|
290575
|
-
|
|
290576
|
-
|
|
290577
|
-
|
|
290578
|
-
|
|
290579
|
-
|
|
290580
|
-
|
|
290581
|
-
|
|
290582
|
-
|
|
290583
|
-
|
|
290584
|
-
|
|
290585
|
-
|
|
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
|
-
|
|
290588
|
-
|
|
290589
|
-
|
|
290590
|
-
|
|
290591
|
-
const
|
|
290592
|
-
const
|
|
290593
|
-
|
|
290594
|
-
|
|
290595
|
-
|
|
290596
|
-
|
|
290597
|
-
|
|
290598
|
-
|
|
290599
|
-
|
|
290600
|
-
|
|
290601
|
-
|
|
290602
|
-
|
|
290603
|
-
|
|
290604
|
-
|
|
290605
|
-
|
|
290606
|
-
|
|
290607
|
-
|
|
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
|
-
//
|
|
290512
|
+
// VERIFICATION STEPS
|
|
290611
290513
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
290612
|
-
|
|
290613
|
-
|
|
290614
|
-
const
|
|
290615
|
-
|
|
290616
|
-
|
|
290617
|
-
|
|
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 (
|
|
290620
|
-
|
|
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
|
-
|
|
290623
|
-
|
|
290624
|
-
|
|
290625
|
-
|
|
290626
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
290590
|
+
// HELPERS
|
|
290633
290591
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
290634
|
-
|
|
290635
|
-
|
|
290636
|
-
|
|
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
|
|
290596
|
+
return filePath.replace(/\\/g, "/");
|
|
290652
290597
|
}
|
|
290653
|
-
|
|
290654
|
-
|
|
290655
|
-
|
|
290656
|
-
|
|
290657
|
-
|
|
290658
|
-
|
|
290659
|
-
|
|
290660
|
-
|
|
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 (
|
|
290664
|
-
|
|
290665
|
-
|
|
290666
|
-
|
|
290667
|
-
|
|
290668
|
-
|
|
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
|
-
|
|
290674
|
-
|
|
290675
|
-
|
|
290676
|
-
|
|
290677
|
-
|
|
290678
|
-
|
|
290679
|
-
{
|
|
290680
|
-
|
|
290681
|
-
|
|
290682
|
-
|
|
290683
|
-
|
|
290684
|
-
|
|
290685
|
-
|
|
290686
|
-
|
|
290687
|
-
|
|
290688
|
-
|
|
290689
|
-
|
|
290690
|
-
|
|
290691
|
-
|
|
290692
|
-
|
|
290693
|
-
|
|
290694
|
-
|
|
290695
|
-
|
|
290696
|
-
|
|
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 (
|
|
290707
|
-
const
|
|
290708
|
-
|
|
290709
|
-
|
|
290710
|
-
|
|
290711
|
-
|
|
290712
|
-
|
|
290713
|
-
|
|
290714
|
-
|
|
290715
|
-
|
|
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
|
-
|
|
290728
|
-
|
|
290729
|
-
|
|
290730
|
-
|
|
290731
|
-
|
|
290732
|
-
|
|
290733
|
-
|
|
290734
|
-
|
|
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
|
-
|
|
290754
|
-
|
|
290755
|
-
|
|
290756
|
-
|
|
290757
|
-
|
|
290758
|
-
|
|
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
|
-
|
|
290769
|
-
|
|
290770
|
-
|
|
290771
|
-
|
|
290772
|
-
|
|
290773
|
-
|
|
290774
|
-
|
|
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 (
|
|
290786
|
-
|
|
290787
|
-
|
|
290788
|
-
|
|
290789
|
-
|
|
290790
|
-
|
|
290791
|
-
|
|
290792
|
-
|
|
290793
|
-
|
|
290794
|
-
|
|
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
|
-
|
|
290806
|
-
|
|
290807
|
-
|
|
290808
|
-
|
|
290809
|
-
|
|
290810
|
-
|
|
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
|
|
290814
|
-
|
|
290815
|
-
|
|
290816
|
-
|
|
290780
|
+
return {
|
|
290781
|
+
filePath,
|
|
290782
|
+
roleSummary,
|
|
290783
|
+
paragraphs,
|
|
290784
|
+
quickFacts,
|
|
290785
|
+
warnings,
|
|
290786
|
+
relatedFiles,
|
|
290787
|
+
overlays
|
|
290788
|
+
};
|
|
290817
290789
|
}
|
|
290818
|
-
|
|
290819
|
-
|
|
290820
|
-
|
|
290821
|
-
|
|
290822
|
-
const
|
|
290823
|
-
|
|
290824
|
-
|
|
290825
|
-
|
|
290826
|
-
|
|
290827
|
-
|
|
290828
|
-
|
|
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
|
-
|
|
290832
|
-
|
|
290833
|
-
|
|
290834
|
-
|
|
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
|
|
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
|
-
//
|
|
290854
|
+
// PRIVATE
|
|
290864
290855
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
290865
|
-
|
|
290866
|
-
|
|
290867
|
-
|
|
290868
|
-
|
|
290869
|
-
|
|
290870
|
-
|
|
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
|
-
|
|
290873
|
-
|
|
290874
|
-
if (
|
|
290875
|
-
return
|
|
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
|
-
|
|
290878
|
-
|
|
290879
|
-
|
|
290880
|
-
|
|
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
|
-
|
|
290887
|
-
|
|
290888
|
-
|
|
290889
|
-
|
|
290890
|
-
|
|
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
|
-
|
|
290904
|
-
|
|
290905
|
-
|
|
290906
|
-
|
|
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
|
-
|
|
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-
|
|
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: "
|
|
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
|
-
"
|
|
291887
|
-
"
|
|
291888
|
-
"
|
|
291889
|
-
"
|
|
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: "
|
|
291903
|
-
monthlyPriceUsd:
|
|
291904
|
-
priceLabel: "$
|
|
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
|
-
"
|
|
291909
|
-
"
|
|
291910
|
-
"
|
|
291911
|
-
"
|
|
291912
|
-
"
|
|
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
|
|
291915
|
+
bestFor: "Builders shipping AI-assisted code with proof",
|
|
291915
291916
|
popular: true,
|
|
291916
|
-
cta: "
|
|
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:
|
|
291924
|
-
priceLabel: "$
|
|
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:
|
|
291948
|
-
priceLabel: "$
|
|
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
|
|
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
|
-
//
|
|
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: ["
|
|
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.
|
|
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 :
|
|
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;
|