vibe-splain 3.4.1 → 3.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -19
- package/dist/commands/hook.d.ts +9 -0
- package/dist/commands/hook.js +84 -0
- package/dist/commands/install.d.ts +5 -0
- package/dist/commands/install.js +58 -9
- package/dist/commands/network.d.ts +1 -0
- package/dist/commands/network.js +91 -0
- package/dist/commands/scan.d.ts +13 -0
- package/dist/commands/scan.js +97 -0
- package/dist/export/ExportOrchestrator.js +8 -6
- package/dist/export/renderers/AgentMarkdownRenderer.d.ts +1 -2
- package/dist/export/renderers/AgentMarkdownRenderer.js +1 -17
- package/dist/hook/preToolUse.d.ts +30 -0
- package/dist/hook/preToolUse.js +81 -0
- package/dist/hook-entry.d.ts +2 -0
- package/dist/hook-entry.js +8 -0
- package/dist/hook.d.ts +1 -0
- package/dist/hook.js +337 -0
- package/dist/index.js +2635 -1384
- package/dist/mcp/server.js +9 -10
- package/dist/mcp/tools/explainSpecialistScope.d.ts +15 -0
- package/dist/mcp/tools/explainSpecialistScope.js +30 -0
- package/dist/mcp/tools/hydration/get_evidence_slice.js +1 -3
- package/dist/mcp/tools/prepareTaskContext.d.ts +38 -0
- package/dist/mcp/tools/prepareTaskContext.js +93 -0
- package/dist/mcp/tools/scan_project.d.ts +15 -0
- package/dist/mcp/tools/scan_project.js +16 -13
- package/dist/ui/index.html +3 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6,8 +6,9 @@ import { Command } from "commander";
|
|
|
6
6
|
// dist/commands/install.js
|
|
7
7
|
import { readFile, writeFile } from "fs/promises";
|
|
8
8
|
import { existsSync } from "fs";
|
|
9
|
-
import { join } from "path";
|
|
9
|
+
import { join, dirname } from "path";
|
|
10
10
|
import { homedir } from "os";
|
|
11
|
+
import { fileURLToPath } from "url";
|
|
11
12
|
function expandPath(p) {
|
|
12
13
|
if (p.startsWith("~")) {
|
|
13
14
|
return join(homedir(), p.slice(1));
|
|
@@ -30,8 +31,40 @@ var MCP_ENTRY = {
|
|
|
30
31
|
command: "npx",
|
|
31
32
|
args: ["-y", "vibe-splain", "serve"]
|
|
32
33
|
};
|
|
34
|
+
var HOOK_MATCHER = "Edit|Write|MultiEdit";
|
|
35
|
+
var DEFAULT_HOOK_COMMAND = "npx -y vibe-splain hook pretooluse";
|
|
36
|
+
function addPreToolUseHook(config, hookPath) {
|
|
37
|
+
if (!config.hooks)
|
|
38
|
+
config.hooks = {};
|
|
39
|
+
if (!Array.isArray(config.hooks.PreToolUse))
|
|
40
|
+
config.hooks.PreToolUse = [];
|
|
41
|
+
const hookCommand = hookPath ? `node "${hookPath}"` : DEFAULT_HOOK_COMMAND;
|
|
42
|
+
const already = config.hooks.PreToolUse.some((entry) => Array.isArray(entry?.hooks) && entry.hooks.some((h) => typeof h?.command === "string" && (h.command.includes("vibe-splain hook pretooluse") || h.command.includes("hook.js"))));
|
|
43
|
+
if (already) {
|
|
44
|
+
config.hooks.PreToolUse = config.hooks.PreToolUse.map((entry) => {
|
|
45
|
+
if (Array.isArray(entry?.hooks)) {
|
|
46
|
+
return {
|
|
47
|
+
...entry,
|
|
48
|
+
hooks: entry.hooks.map((h) => {
|
|
49
|
+
if (typeof h?.command === "string" && (h.command.includes("vibe-splain hook pretooluse") || h.command.includes("hook.js"))) {
|
|
50
|
+
return { ...h, command: hookCommand };
|
|
51
|
+
}
|
|
52
|
+
return h;
|
|
53
|
+
})
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
return entry;
|
|
57
|
+
});
|
|
58
|
+
return config;
|
|
59
|
+
}
|
|
60
|
+
config.hooks.PreToolUse.push({
|
|
61
|
+
matcher: HOOK_MATCHER,
|
|
62
|
+
hooks: [{ type: "command", command: hookCommand }]
|
|
63
|
+
});
|
|
64
|
+
return config;
|
|
65
|
+
}
|
|
33
66
|
async function installCommand() {
|
|
34
|
-
console.error("\n\u{1F527}
|
|
67
|
+
console.error("\n\u{1F527} vibe-splain Installer\n");
|
|
35
68
|
let patchedCount = 0;
|
|
36
69
|
for (const agent of AGENT_CONFIGS) {
|
|
37
70
|
const resolvedPath = expandPath(agent.path);
|
|
@@ -48,15 +81,21 @@ async function installCommand() {
|
|
|
48
81
|
console.error(` \u26A0 Could not parse JSON, skipping`);
|
|
49
82
|
continue;
|
|
50
83
|
}
|
|
51
|
-
|
|
84
|
+
const before = JSON.stringify(config);
|
|
85
|
+
if (!config.mcpServers)
|
|
86
|
+
config.mcpServers = {};
|
|
87
|
+
if (!config.mcpServers["vibe-splain"])
|
|
88
|
+
config.mcpServers["vibe-splain"] = MCP_ENTRY;
|
|
89
|
+
if (agent.name === "Claude Code CLI") {
|
|
90
|
+
const __dirname3 = dirname(fileURLToPath(import.meta.url));
|
|
91
|
+
const hookPath = join(__dirname3, "..", "hook.js");
|
|
92
|
+
addPreToolUseHook(config, hookPath);
|
|
93
|
+
}
|
|
94
|
+
if (JSON.stringify(config) === before) {
|
|
52
95
|
console.error(` \u2713 Already configured, skipping`);
|
|
53
96
|
patchedCount++;
|
|
54
97
|
continue;
|
|
55
98
|
}
|
|
56
|
-
if (!config.mcpServers) {
|
|
57
|
-
config.mcpServers = {};
|
|
58
|
-
}
|
|
59
|
-
config.mcpServers["vibe-splain"] = MCP_ENTRY;
|
|
60
99
|
await writeFile(resolvedPath, JSON.stringify(config, null, 2), "utf8");
|
|
61
100
|
console.error(` \u2705 Patched successfully`);
|
|
62
101
|
patchedCount++;
|
|
@@ -89,19 +128,19 @@ import { CallToolRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema
|
|
|
89
128
|
|
|
90
129
|
// ../brain/dist/scanner.js
|
|
91
130
|
import { extname as extname4 } from "path";
|
|
92
|
-
import { readFile as
|
|
131
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
93
132
|
|
|
94
133
|
// ../brain/dist/pipeline/orchestrator.js
|
|
95
134
|
import { join as join7 } from "path";
|
|
96
135
|
|
|
97
136
|
// ../brain/dist/pipeline/inventory.js
|
|
98
137
|
import Parser from "web-tree-sitter";
|
|
99
|
-
import { join as join2, dirname, relative, extname, basename, sep } from "path";
|
|
100
|
-
import { fileURLToPath } from "url";
|
|
138
|
+
import { join as join2, dirname as dirname2, relative, extname, basename, sep } from "path";
|
|
139
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
101
140
|
import { createRequire } from "module";
|
|
102
141
|
import { readFile as readFile2, readdir, writeFile as writeFile2, mkdir } from "fs/promises";
|
|
103
142
|
import { existsSync as existsSync2 } from "fs";
|
|
104
|
-
var __dirname =
|
|
143
|
+
var __dirname = dirname2(fileURLToPath2(import.meta.url));
|
|
105
144
|
var require2 = createRequire(import.meta.url);
|
|
106
145
|
var _parser = null;
|
|
107
146
|
var langCache = /* @__PURE__ */ new Map();
|
|
@@ -129,7 +168,7 @@ var LANG_WASM = {
|
|
|
129
168
|
var SUPPORTED_EXTENSIONS = new Set(Object.keys(EXT_LANG));
|
|
130
169
|
function resolveWasm(file) {
|
|
131
170
|
try {
|
|
132
|
-
const wasmsDir =
|
|
171
|
+
const wasmsDir = dirname2(require2.resolve("tree-sitter-wasms/package.json"));
|
|
133
172
|
const p = join2(wasmsDir, "out", file);
|
|
134
173
|
if (existsSync2(p))
|
|
135
174
|
return p;
|
|
@@ -319,19 +358,6 @@ function inferProductDomain(relPath, importSpecs) {
|
|
|
319
358
|
if (/\.generated\.|__generated__|\.prisma\//.test(p)) {
|
|
320
359
|
return "generated_noise";
|
|
321
360
|
}
|
|
322
|
-
if (p.includes("booking-audit") || p.includes("bookingaudit"))
|
|
323
|
-
return "booking_audit";
|
|
324
|
-
if (p.includes("/pages/api/book/") || p.includes("/api/book/") || p.includes("/booking-successful/") || p.includes("/reschedule/") || p.includes("booking-page-wrapper"))
|
|
325
|
-
return "booking_creation";
|
|
326
|
-
if (p.includes("bookeventform") || p.includes("availabletimes") || p.includes("availabletimeslots") || p.includes("usebookings") || p.includes("/book/") && !p.includes("booking-audit"))
|
|
327
|
-
return "booking_ui_delegate";
|
|
328
|
-
if (p.includes("modules/bookings") || p.includes("components/booking/actions") || p.includes("/bookings/[status]") || p.includes("/booking/[uid]") || p.includes("/bookings/"))
|
|
329
|
-
return "booking_management";
|
|
330
|
-
if (p.includes("event-types") || p.includes("eventtypes") || p.includes("eventavailabilitytab") || p.includes("eventadvancedtab") || p.includes("eventlimits") || p.includes("eventrecurring"))
|
|
331
|
-
return "event_type_configuration";
|
|
332
|
-
if (p.includes("availability") || p.includes("/schedules/") || p.includes("/slots/")) {
|
|
333
|
-
return "availability";
|
|
334
|
-
}
|
|
335
361
|
if (p.includes("oauth") || p.includes("nextauth") || p.includes("/auth/oauth") || p.includes("/api/auth/") || importSpecs.some((s) => s.includes("arctic") || s.includes("@auth/core")))
|
|
336
362
|
return "auth_oauth";
|
|
337
363
|
if (p.includes("/auth/") || p.includes("signup") || p.includes("login") || p.includes("forgot-password") || p.includes("reset-password") || p.includes("two-factor") || p.includes("verify-email") || importSpecs.some((s) => s.includes("next-auth") || s.includes("@clerk/")))
|
|
@@ -342,28 +368,6 @@ function inferProductDomain(relPath, importSpecs) {
|
|
|
342
368
|
return "payments";
|
|
343
369
|
if (p.includes("webhook"))
|
|
344
370
|
return "webhooks";
|
|
345
|
-
if (p.includes("app-store") || p.includes("appstore") || p.includes("/apps/") || p.includes("modules/apps"))
|
|
346
|
-
return "apps_marketplace";
|
|
347
|
-
if (p.includes("calendar") || p.includes("selected-calendars") || importSpecs.some((s) => s.includes("googleapis") || s.includes("@google-cloud/")))
|
|
348
|
-
return "calendar_integrations";
|
|
349
|
-
if (p.includes("video") || p.includes("calvideo") || p.includes("daily.co"))
|
|
350
|
-
return "video";
|
|
351
|
-
if (p.includes("onboarding") || p.includes("getting-started"))
|
|
352
|
-
return "onboarding";
|
|
353
|
-
if (p.includes("/settings/") || p.includes("/settings."))
|
|
354
|
-
return "settings";
|
|
355
|
-
if (p.includes("/admin/") || p.includes("/admin."))
|
|
356
|
-
return "admin";
|
|
357
|
-
if (p.includes("data-table") || p.includes("datatable") || p.includes("datasegment") || p.includes("segment"))
|
|
358
|
-
return "data_table";
|
|
359
|
-
if (p.includes("shell/navigation") || p.includes("navigationitem") || p.includes("/shell/") || p.includes("sidebar") || p.includes("topnav") || p.includes("mainnav"))
|
|
360
|
-
return "shell_navigation";
|
|
361
|
-
if (p.includes("form-builder") || p.includes("formbuilder") || p.includes("/forms/") || p.includes("routingforms"))
|
|
362
|
-
return "forms";
|
|
363
|
-
if (p.includes("embed"))
|
|
364
|
-
return "embed";
|
|
365
|
-
if (p.includes("notification") || p.includes("/email/") || p.includes("/emails/") || importSpecs.some((s) => s.includes("nodemailer") || s.includes("resend") || s.includes("@sendgrid/")))
|
|
366
|
-
return "notifications";
|
|
367
371
|
if (p.includes("middleware") && !p.includes("pages/api/") || p.includes("/router.") || p.includes("routerconfig"))
|
|
368
372
|
return "routing_infrastructure";
|
|
369
373
|
return "unknown";
|
|
@@ -719,7 +723,7 @@ async function detectStackAndEntrypoints(projectRoot, files) {
|
|
|
719
723
|
const b = basename(r);
|
|
720
724
|
if (b === "main.py" || b === "__main__.py")
|
|
721
725
|
entrypoints.add(r);
|
|
722
|
-
if (/^index\.(ts|tsx|js|jsx|mjs|cjs)$/.test(b) &&
|
|
726
|
+
if (/^index\.(ts|tsx|js|jsx|mjs|cjs)$/.test(b) && dirname2(r).split(sep).length <= 2)
|
|
723
727
|
entrypoints.add(r);
|
|
724
728
|
if (b === "main.go" && r.includes("cmd" + sep))
|
|
725
729
|
entrypoints.add(r);
|
|
@@ -1198,7 +1202,7 @@ async function runInventory(projectRoot) {
|
|
|
1198
1202
|
}
|
|
1199
1203
|
|
|
1200
1204
|
// ../brain/dist/pipeline/resolution.js
|
|
1201
|
-
import { join as join3, dirname as
|
|
1205
|
+
import { join as join3, dirname as dirname3, relative as relative2, extname as extname2, sep as sep2 } from "path";
|
|
1202
1206
|
import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir2 } from "fs/promises";
|
|
1203
1207
|
import { existsSync as existsSync3 } from "fs";
|
|
1204
1208
|
function parseJsonLenient(text) {
|
|
@@ -1213,10 +1217,10 @@ async function discoverAllTsConfigs(dir, projectRoot, maxDepth = 4) {
|
|
|
1213
1217
|
const result = {};
|
|
1214
1218
|
if (maxDepth < 0)
|
|
1215
1219
|
return result;
|
|
1216
|
-
const { readdir:
|
|
1220
|
+
const { readdir: readdir2 } = await import("fs/promises");
|
|
1217
1221
|
let entries = [];
|
|
1218
1222
|
try {
|
|
1219
|
-
entries = await
|
|
1223
|
+
entries = await readdir2(dir, { withFileTypes: true });
|
|
1220
1224
|
} catch {
|
|
1221
1225
|
return result;
|
|
1222
1226
|
}
|
|
@@ -1250,9 +1254,9 @@ async function extractTsConfigPaths(tsconfigPath, projectRoot, depth = 0) {
|
|
|
1250
1254
|
if (typeof parsed.extends === "string") {
|
|
1251
1255
|
let baseFile = parsed.extends;
|
|
1252
1256
|
if (baseFile.startsWith(".")) {
|
|
1253
|
-
baseFile = join3(
|
|
1257
|
+
baseFile = join3(dirname3(tsconfigPath), baseFile);
|
|
1254
1258
|
} else {
|
|
1255
|
-
let currentDir =
|
|
1259
|
+
let currentDir = dirname3(tsconfigPath);
|
|
1256
1260
|
let found = false;
|
|
1257
1261
|
while (currentDir.length >= projectRoot.length || currentDir === projectRoot) {
|
|
1258
1262
|
const candidate = join3(currentDir, "node_modules", baseFile + (baseFile.endsWith(".json") ? "" : ".json"));
|
|
@@ -1261,7 +1265,7 @@ async function extractTsConfigPaths(tsconfigPath, projectRoot, depth = 0) {
|
|
|
1261
1265
|
found = true;
|
|
1262
1266
|
break;
|
|
1263
1267
|
}
|
|
1264
|
-
const parent =
|
|
1268
|
+
const parent = dirname3(currentDir);
|
|
1265
1269
|
if (parent === currentDir)
|
|
1266
1270
|
break;
|
|
1267
1271
|
currentDir = parent;
|
|
@@ -1276,7 +1280,7 @@ async function extractTsConfigPaths(tsconfigPath, projectRoot, depth = 0) {
|
|
|
1276
1280
|
Object.assign(result, base);
|
|
1277
1281
|
}
|
|
1278
1282
|
const opts = parsed.compilerOptions || {};
|
|
1279
|
-
const baseUrl = typeof opts.baseUrl === "string" ? join3(
|
|
1283
|
+
const baseUrl = typeof opts.baseUrl === "string" ? join3(dirname3(tsconfigPath), opts.baseUrl) : dirname3(tsconfigPath);
|
|
1280
1284
|
if (typeof opts.baseUrl === "string") {
|
|
1281
1285
|
const relBase = relative2(projectRoot, baseUrl);
|
|
1282
1286
|
if (relBase && relBase !== ".") {
|
|
@@ -1312,10 +1316,10 @@ async function discoverWorkspacePackages(projectRoot) {
|
|
|
1312
1316
|
const absPrefix = join3(projectRoot, prefix);
|
|
1313
1317
|
if (!existsSync3(absPrefix))
|
|
1314
1318
|
continue;
|
|
1315
|
-
const { readdir:
|
|
1319
|
+
const { readdir: readdir2 } = await import("fs/promises");
|
|
1316
1320
|
let entries = [];
|
|
1317
1321
|
try {
|
|
1318
|
-
const dirents = await
|
|
1322
|
+
const dirents = await readdir2(absPrefix, { withFileTypes: true });
|
|
1319
1323
|
entries = dirents.filter((d) => d.isDirectory()).map((d) => d.name);
|
|
1320
1324
|
} catch {
|
|
1321
1325
|
continue;
|
|
@@ -1382,9 +1386,9 @@ function resolvePython(spec, fromAbs, projectRoot, fileSet) {
|
|
|
1382
1386
|
let modulePath;
|
|
1383
1387
|
if (spec.startsWith(".")) {
|
|
1384
1388
|
const dots = spec.match(/^\.+/)[0].length;
|
|
1385
|
-
let dir =
|
|
1389
|
+
let dir = dirname3(fromAbs);
|
|
1386
1390
|
for (let i = 1; i < dots; i++)
|
|
1387
|
-
dir =
|
|
1391
|
+
dir = dirname3(dir);
|
|
1388
1392
|
const rest = spec.slice(dots).replace(/\./g, sep2);
|
|
1389
1393
|
modulePath = rest ? join3(dir, rest) : dir;
|
|
1390
1394
|
} else {
|
|
@@ -1418,7 +1422,7 @@ function resolveImportWithAliasMap(spec, fromAbs, lang, projectRoot, fileSet, ba
|
|
|
1418
1422
|
}
|
|
1419
1423
|
if (lang === "typescript" || lang === "tsx" || lang === "javascript") {
|
|
1420
1424
|
if (spec.startsWith(".")) {
|
|
1421
|
-
const base = join3(
|
|
1425
|
+
const base = join3(dirname3(fromAbs), spec);
|
|
1422
1426
|
return { resolved: tryJsCandidates(base, projectRoot, fileSet), isAlias: false };
|
|
1423
1427
|
}
|
|
1424
1428
|
for (const [prefix, replacement] of Object.entries(aliasMap.resolvedAliases)) {
|
|
@@ -1531,8 +1535,814 @@ async function runResolution(projectRoot, inv) {
|
|
|
1531
1535
|
}
|
|
1532
1536
|
|
|
1533
1537
|
// ../brain/dist/pipeline/classification.js
|
|
1534
|
-
import { join as
|
|
1538
|
+
import { join as join5, basename as basename4, extname as extname3, sep as sep3 } from "path";
|
|
1535
1539
|
import { writeFile as writeFile4, mkdir as mkdir3 } from "fs/promises";
|
|
1540
|
+
|
|
1541
|
+
// ../brain/dist/pipeline/adapters/types.js
|
|
1542
|
+
function emptyAdapterStageResult() {
|
|
1543
|
+
return {
|
|
1544
|
+
firedAdapterIds: [],
|
|
1545
|
+
liftByFile: /* @__PURE__ */ new Map(),
|
|
1546
|
+
severityBoostByFile: /* @__PURE__ */ new Map(),
|
|
1547
|
+
loadBearingBoostByFile: /* @__PURE__ */ new Map(),
|
|
1548
|
+
classificationByFile: /* @__PURE__ */ new Map(),
|
|
1549
|
+
sideEffectsByFile: /* @__PURE__ */ new Map(),
|
|
1550
|
+
writeIntentsByFile: /* @__PURE__ */ new Map(),
|
|
1551
|
+
pillarLabelsByFile: /* @__PURE__ */ new Map(),
|
|
1552
|
+
pillarRenames: /* @__PURE__ */ new Map(),
|
|
1553
|
+
surfacePatterns: [],
|
|
1554
|
+
metrics: {}
|
|
1555
|
+
};
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
// ../brain/dist/pipeline/adapters/registry.js
|
|
1559
|
+
var AdapterRegistry = class {
|
|
1560
|
+
adapters = [];
|
|
1561
|
+
/** Register a compiled-in adapter. Re-registering the same id replaces it. */
|
|
1562
|
+
register(adapter) {
|
|
1563
|
+
this.adapters = this.adapters.filter((a) => a.id !== adapter.id);
|
|
1564
|
+
this.adapters.push(adapter);
|
|
1565
|
+
}
|
|
1566
|
+
/** All registered adapters (for tests/introspection). */
|
|
1567
|
+
list() {
|
|
1568
|
+
return [...this.adapters].sort((a, b) => a.id.localeCompare(b.id));
|
|
1569
|
+
}
|
|
1570
|
+
/** Adapters whose detect() matches, in deterministic id order. */
|
|
1571
|
+
select(ctx) {
|
|
1572
|
+
return this.adapters.filter((a) => {
|
|
1573
|
+
try {
|
|
1574
|
+
return a.detect(ctx);
|
|
1575
|
+
} catch {
|
|
1576
|
+
return false;
|
|
1577
|
+
}
|
|
1578
|
+
}).sort((a, b) => a.id.localeCompare(b.id));
|
|
1579
|
+
}
|
|
1580
|
+
/**
|
|
1581
|
+
* Run every matching adapter and compose their contributions into one
|
|
1582
|
+
* order-independent result. Returns the identity result when nothing matches.
|
|
1583
|
+
*/
|
|
1584
|
+
runStage(ctx) {
|
|
1585
|
+
const matched = this.select(ctx);
|
|
1586
|
+
if (matched.length === 0)
|
|
1587
|
+
return emptyAdapterStageResult();
|
|
1588
|
+
const out = emptyAdapterStageResult();
|
|
1589
|
+
out.firedAdapterIds = matched.map((a) => a.id);
|
|
1590
|
+
const execRoleCandidates = /* @__PURE__ */ new Map();
|
|
1591
|
+
for (const adapter of matched) {
|
|
1592
|
+
const lift = adapter.computeBehavioralLift?.(ctx);
|
|
1593
|
+
if (lift) {
|
|
1594
|
+
for (const [rel, v] of Object.entries(lift.byFile)) {
|
|
1595
|
+
if (v < 0)
|
|
1596
|
+
continue;
|
|
1597
|
+
out.liftByFile.set(rel, (out.liftByFile.get(rel) ?? 0) + v);
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
const sev = adapter.applySeverityPolicy?.(ctx);
|
|
1601
|
+
if (sev) {
|
|
1602
|
+
for (const [rel, v] of Object.entries(sev.byFile)) {
|
|
1603
|
+
if (v < 0)
|
|
1604
|
+
continue;
|
|
1605
|
+
out.severityBoostByFile.set(rel, Math.max(out.severityBoostByFile.get(rel) ?? 0, v));
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
const lb = adapter.applyLoadBearingPolicy?.(ctx);
|
|
1609
|
+
if (lb) {
|
|
1610
|
+
for (const [rel, v] of Object.entries(lb.byFile)) {
|
|
1611
|
+
if (v < 0)
|
|
1612
|
+
continue;
|
|
1613
|
+
out.loadBearingBoostByFile.set(rel, Math.max(out.loadBearingBoostByFile.get(rel) ?? 0, v));
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
const cls = adapter.classify?.(ctx);
|
|
1617
|
+
if (cls) {
|
|
1618
|
+
for (const [rel, c] of Object.entries(cls.byFile)) {
|
|
1619
|
+
const cur = out.classificationByFile.get(rel) ?? { domainTags: [] };
|
|
1620
|
+
if (c.adapterDomain != null)
|
|
1621
|
+
cur.adapterDomain = cur.adapterDomain ?? c.adapterDomain;
|
|
1622
|
+
if (c.domainTags)
|
|
1623
|
+
cur.domainTags = unionSorted(cur.domainTags, c.domainTags);
|
|
1624
|
+
if (c.executionRole != null) {
|
|
1625
|
+
const s = execRoleCandidates.get(rel) ?? /* @__PURE__ */ new Set();
|
|
1626
|
+
s.add(c.executionRole);
|
|
1627
|
+
execRoleCandidates.set(rel, s);
|
|
1628
|
+
}
|
|
1629
|
+
out.classificationByFile.set(rel, cur);
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
const eff = adapter.interpretSideEffects?.(ctx);
|
|
1633
|
+
if (eff) {
|
|
1634
|
+
for (const [rel, arr] of Object.entries(eff.byFile)) {
|
|
1635
|
+
out.sideEffectsByFile.set(rel, unionSorted(out.sideEffectsByFile.get(rel) ?? [], arr));
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
const wi = adapter.inferWriteIntents?.(ctx);
|
|
1639
|
+
if (wi) {
|
|
1640
|
+
for (const [rel, arr] of Object.entries(wi.byFile)) {
|
|
1641
|
+
out.writeIntentsByFile.set(rel, unionSorted(out.writeIntentsByFile.get(rel) ?? [], arr));
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
const pl = adapter.labelPillars?.(ctx);
|
|
1645
|
+
if (pl?.renames) {
|
|
1646
|
+
for (const [from, to] of Object.entries(pl.renames)) {
|
|
1647
|
+
if (!out.pillarRenames.has(from))
|
|
1648
|
+
out.pillarRenames.set(from, to);
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
if (pl?.byFile) {
|
|
1652
|
+
for (const [rel, label] of Object.entries(pl.byFile)) {
|
|
1653
|
+
if (!out.pillarLabelsByFile.has(rel))
|
|
1654
|
+
out.pillarLabelsByFile.set(rel, label);
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
const sp = adapter.getSurfacePatterns?.();
|
|
1658
|
+
if (sp)
|
|
1659
|
+
out.surfacePatterns.push(...sp);
|
|
1660
|
+
const met = adapter.getMetrics?.();
|
|
1661
|
+
if (met) {
|
|
1662
|
+
for (const [k, v] of Object.entries(met)) {
|
|
1663
|
+
out.metrics[k] = (out.metrics[k] ?? 0) + v;
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
for (const [rel, roles] of execRoleCandidates) {
|
|
1668
|
+
const chosen = [...roles].sort()[0];
|
|
1669
|
+
const cur = out.classificationByFile.get(rel) ?? { domainTags: [] };
|
|
1670
|
+
cur.executionRole = chosen;
|
|
1671
|
+
out.classificationByFile.set(rel, cur);
|
|
1672
|
+
}
|
|
1673
|
+
return out;
|
|
1674
|
+
}
|
|
1675
|
+
};
|
|
1676
|
+
function unionSorted(a, b) {
|
|
1677
|
+
return [.../* @__PURE__ */ new Set([...a, ...b])].sort();
|
|
1678
|
+
}
|
|
1679
|
+
var adapterRegistry = new AdapterRegistry();
|
|
1680
|
+
|
|
1681
|
+
// ../brain/dist/pipeline/adapters/calcom.js
|
|
1682
|
+
function detectCalcom(ctx) {
|
|
1683
|
+
const THRESHOLD = 5;
|
|
1684
|
+
let hits = 0;
|
|
1685
|
+
for (const f of ctx.files) {
|
|
1686
|
+
if (f.importSpecs.some((s) => s.startsWith("@calcom/"))) {
|
|
1687
|
+
if (++hits >= THRESHOLD)
|
|
1688
|
+
return true;
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
return false;
|
|
1692
|
+
}
|
|
1693
|
+
var CALCOM_SURFACE_PATTERNS = [
|
|
1694
|
+
{
|
|
1695
|
+
domain: "booking_creation",
|
|
1696
|
+
expected: [/book/i, /booking/i, /reschedule/i, /booking-success/i, /api\/book/i, /create-booking/i],
|
|
1697
|
+
wrong: [/event-type/i, /event-types/i, /eventtypes/i, /availability/i, /schedule/i]
|
|
1698
|
+
},
|
|
1699
|
+
{
|
|
1700
|
+
domain: "booking_ui_delegate",
|
|
1701
|
+
expected: [/book/i, /booking/i, /reschedule/i, /event-type/i, /event-types/i],
|
|
1702
|
+
wrong: [/settings/i, /admin/i, /onboarding/i]
|
|
1703
|
+
},
|
|
1704
|
+
{
|
|
1705
|
+
domain: "payments_webhooks",
|
|
1706
|
+
expected: [/webhook/i, /stripe/i, /payment/i],
|
|
1707
|
+
wrong: [/settings/i, /onboarding/i, /profile/i]
|
|
1708
|
+
},
|
|
1709
|
+
{
|
|
1710
|
+
domain: "auth_oauth",
|
|
1711
|
+
expected: [/oauth/i, /callback/i, /auth/i, /signin/i, /login/i],
|
|
1712
|
+
wrong: [/booking/i, /payment/i, /settings/i]
|
|
1713
|
+
}
|
|
1714
|
+
];
|
|
1715
|
+
function calcomWriteIntents(ctx) {
|
|
1716
|
+
const byFile = {};
|
|
1717
|
+
const adapterEffectsResult = calcomInterpretSideEffects(ctx);
|
|
1718
|
+
for (const f of ctx.files) {
|
|
1719
|
+
const rel = f.rel;
|
|
1720
|
+
const intents = [];
|
|
1721
|
+
const d = inferCalcomDomain(rel, f.importSpecs) ?? f.productDomain;
|
|
1722
|
+
if (d === "booking_creation" || d === "booking_ui_delegate") {
|
|
1723
|
+
intents.push("create_booking");
|
|
1724
|
+
if (rel.includes("reschedule") || rel.includes("Reschedule"))
|
|
1725
|
+
intents.push("reschedule_booking");
|
|
1726
|
+
if (rel.includes("recurring") || rel.includes("Recurring"))
|
|
1727
|
+
intents.push("create_recurring_booking");
|
|
1728
|
+
}
|
|
1729
|
+
if (d === "booking_management")
|
|
1730
|
+
intents.push("cancel_booking");
|
|
1731
|
+
if (d === "event_type_configuration")
|
|
1732
|
+
intents.push("update_event_type");
|
|
1733
|
+
if (d === "availability")
|
|
1734
|
+
intents.push("update_availability");
|
|
1735
|
+
if (d === "payments")
|
|
1736
|
+
intents.push("create_payment");
|
|
1737
|
+
if (d === "payments_webhooks")
|
|
1738
|
+
intents.push("handle_payment_webhook");
|
|
1739
|
+
if (d === "auth_oauth") {
|
|
1740
|
+
intents.push("issue_auth_token");
|
|
1741
|
+
intents.push("refresh_auth_token");
|
|
1742
|
+
}
|
|
1743
|
+
const adapterEffects = adapterEffectsResult.byFile[rel] || [];
|
|
1744
|
+
if (adapterEffects.includes("webhook_delivery"))
|
|
1745
|
+
intents.push("send_webhook");
|
|
1746
|
+
if (d === "settings")
|
|
1747
|
+
intents.push("update_user_settings");
|
|
1748
|
+
if (intents.length > 0)
|
|
1749
|
+
byFile[rel] = intents;
|
|
1750
|
+
}
|
|
1751
|
+
return { byFile };
|
|
1752
|
+
}
|
|
1753
|
+
var CALCOM_TAXONOMY = {
|
|
1754
|
+
booking_creation: { adapterDomain: "booking", executionRole: "mutation" },
|
|
1755
|
+
booking_management: { adapterDomain: "booking", executionRole: "mutation" },
|
|
1756
|
+
booking_audit: { adapterDomain: "booking", executionRole: "read" },
|
|
1757
|
+
booking_ui_delegate: { adapterDomain: "booking", executionRole: "ui_delegate" },
|
|
1758
|
+
event_type_configuration: { adapterDomain: "event_types", executionRole: "config" },
|
|
1759
|
+
availability: { adapterDomain: "availability", executionRole: "config" },
|
|
1760
|
+
auth: { adapterDomain: "auth", executionRole: "auth" },
|
|
1761
|
+
auth_oauth: { adapterDomain: "auth", executionRole: "auth" },
|
|
1762
|
+
payments: { adapterDomain: "payments", executionRole: "mutation" },
|
|
1763
|
+
payments_webhooks: { adapterDomain: "payments", executionRole: "ingress" },
|
|
1764
|
+
webhooks: { adapterDomain: "webhooks", executionRole: "ingress" },
|
|
1765
|
+
apps_marketplace: { adapterDomain: "apps", executionRole: "config" },
|
|
1766
|
+
calendar_integrations: { adapterDomain: "calendar", executionRole: "integration" },
|
|
1767
|
+
video: { adapterDomain: "video", executionRole: "integration" },
|
|
1768
|
+
onboarding: { adapterDomain: "onboarding", executionRole: "flow" },
|
|
1769
|
+
settings: { adapterDomain: "settings", executionRole: "config" },
|
|
1770
|
+
admin: { adapterDomain: "admin", executionRole: "config" },
|
|
1771
|
+
notifications: { adapterDomain: "notifications", executionRole: "delivery" },
|
|
1772
|
+
embed: { adapterDomain: "embed", executionRole: "ui_delegate" }
|
|
1773
|
+
};
|
|
1774
|
+
function inferCalcomDomain(relPath, importSpecs) {
|
|
1775
|
+
const p = relPath.toLowerCase().replace(/\\/g, "/");
|
|
1776
|
+
if (/\.test\.|\.spec\.|__tests__|\/e2e\/|\/playwright\/|\/cypress\//.test(p))
|
|
1777
|
+
return void 0;
|
|
1778
|
+
if (/\.generated\.|__generated__|\.prisma\//.test(p))
|
|
1779
|
+
return void 0;
|
|
1780
|
+
if (p.includes("booking-audit") || p.includes("bookingaudit"))
|
|
1781
|
+
return "booking_audit";
|
|
1782
|
+
if (p.includes("/pages/api/book/") || p.includes("/api/book/") || p.includes("/booking-successful/") || p.includes("/reschedule/") || p.includes("booking-page-wrapper"))
|
|
1783
|
+
return "booking_creation";
|
|
1784
|
+
if (p.includes("bookeventform") || p.includes("availabletimes") || p.includes("availabletimeslots") || p.includes("usebookings") || p.includes("/book/") && !p.includes("booking-audit"))
|
|
1785
|
+
return "booking_ui_delegate";
|
|
1786
|
+
if (p.includes("modules/bookings") || p.includes("components/booking/actions") || p.includes("/bookings/[status]") || p.includes("/booking/[uid]") || p.includes("/bookings/"))
|
|
1787
|
+
return "booking_management";
|
|
1788
|
+
if (p.includes("event-types") || p.includes("eventtypes") || p.includes("eventavailabilitytab") || p.includes("eventadvancedtab") || p.includes("eventlimits") || p.includes("eventrecurring"))
|
|
1789
|
+
return "event_type_configuration";
|
|
1790
|
+
if (p.includes("availability") || p.includes("/schedules/") || p.includes("/slots/")) {
|
|
1791
|
+
return "availability";
|
|
1792
|
+
}
|
|
1793
|
+
if (p.includes("oauth") || p.includes("nextauth") || p.includes("/auth/oauth") || p.includes("/api/auth/") || importSpecs.some((s) => s.includes("arctic") || s.includes("@auth/core")))
|
|
1794
|
+
return "auth_oauth";
|
|
1795
|
+
if (p.includes("/auth/") || p.includes("signup") || p.includes("login") || p.includes("forgot-password") || p.includes("reset-password") || p.includes("two-factor") || p.includes("verify-email") || importSpecs.some((s) => s.includes("next-auth") || s.includes("@clerk/")))
|
|
1796
|
+
return "auth";
|
|
1797
|
+
if ((p.includes("stripe") || p.includes("paypal") || p.includes("btcpay") || p.includes("alby") || p.includes("payment")) && (p.includes("webhook") || p.includes("hook")))
|
|
1798
|
+
return "payments_webhooks";
|
|
1799
|
+
if (p.includes("stripe") || p.includes("paypal") || p.includes("btcpay") || p.includes("alby") || p.includes("payment") || p.includes("billing") || p.includes("checkout") || p.includes("subscription") || importSpecs.some((s) => s.includes("stripe") || s.includes("@stripe/")))
|
|
1800
|
+
return "payments";
|
|
1801
|
+
if (p.includes("webhook"))
|
|
1802
|
+
return "webhooks";
|
|
1803
|
+
if (p.includes("app-store") || p.includes("appstore") || p.includes("/apps/") || p.includes("modules/apps"))
|
|
1804
|
+
return "apps_marketplace";
|
|
1805
|
+
if (p.includes("calendar") || p.includes("selected-calendars") || importSpecs.some((s) => s.includes("googleapis") || s.includes("@google-cloud/")))
|
|
1806
|
+
return "calendar_integrations";
|
|
1807
|
+
if (p.includes("video") || p.includes("calvideo") || p.includes("daily.co"))
|
|
1808
|
+
return "video";
|
|
1809
|
+
if (p.includes("onboarding") || p.includes("getting-started"))
|
|
1810
|
+
return "onboarding";
|
|
1811
|
+
if (p.includes("/settings/") || p.includes("/settings."))
|
|
1812
|
+
return "settings";
|
|
1813
|
+
if (p.includes("/admin/") || p.includes("/admin."))
|
|
1814
|
+
return "admin";
|
|
1815
|
+
if (p.includes("data-table") || p.includes("datatable") || p.includes("datasegment") || p.includes("segment"))
|
|
1816
|
+
return "data_table";
|
|
1817
|
+
if (p.includes("shell/navigation") || p.includes("navigationitem") || p.includes("/shell/") || p.includes("sidebar") || p.includes("topnav") || p.includes("mainnav"))
|
|
1818
|
+
return "shell_navigation";
|
|
1819
|
+
if (p.includes("form-builder") || p.includes("formbuilder") || p.includes("/forms/") || p.includes("routingforms"))
|
|
1820
|
+
return "forms";
|
|
1821
|
+
if (p.includes("embed"))
|
|
1822
|
+
return "embed";
|
|
1823
|
+
if (p.includes("notification") || p.includes("/email/") || p.includes("/emails/") || importSpecs.some((s) => s.includes("nodemailer") || s.includes("resend") || s.includes("@sendgrid/")))
|
|
1824
|
+
return "notifications";
|
|
1825
|
+
return void 0;
|
|
1826
|
+
}
|
|
1827
|
+
function calcomClassify(ctx) {
|
|
1828
|
+
const byFile = {};
|
|
1829
|
+
for (const f of ctx.files) {
|
|
1830
|
+
if (f.productDomain === "test_infrastructure" || f.productDomain === "generated_noise" || f.productDomain === "routing_infrastructure")
|
|
1831
|
+
continue;
|
|
1832
|
+
const domain = inferCalcomDomain(f.rel, f.importSpecs);
|
|
1833
|
+
if (!domain)
|
|
1834
|
+
continue;
|
|
1835
|
+
const t = CALCOM_TAXONOMY[domain];
|
|
1836
|
+
if (t) {
|
|
1837
|
+
byFile[f.rel] = {
|
|
1838
|
+
adapterDomain: t.adapterDomain,
|
|
1839
|
+
domainTags: [domain],
|
|
1840
|
+
executionRole: t.executionRole
|
|
1841
|
+
};
|
|
1842
|
+
} else {
|
|
1843
|
+
byFile[f.rel] = {
|
|
1844
|
+
adapterDomain: "unknown",
|
|
1845
|
+
domainTags: [domain],
|
|
1846
|
+
executionRole: "unknown"
|
|
1847
|
+
};
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
return { byFile };
|
|
1851
|
+
}
|
|
1852
|
+
function calcomInterpretSideEffects(ctx) {
|
|
1853
|
+
const byFile = {};
|
|
1854
|
+
for (const f of ctx.files) {
|
|
1855
|
+
const domain = [];
|
|
1856
|
+
const source = f.source;
|
|
1857
|
+
const d = f.productDomain;
|
|
1858
|
+
const frameworkRole = f.frameworkRole;
|
|
1859
|
+
if (/createBooking|handleNewBooking|cancelBooking|rescheduleBooking|handleBooking|createRecurring/.test(source) || d === "booking_creation" && /useMutation\b|\.mutate\b|\.mutateAsync\b/.test(source))
|
|
1860
|
+
domain.push("booking_mutation");
|
|
1861
|
+
if (/stripe\.webhooks\.(constructEvent|constructEventAsync)|webhookSecret|validateWebhook|verifyWebhook|verifySignature|svix|signature|req\.body|req\.rawBody/.test(source) || d === "payments_webhooks" && frameworkRole === "pages_api_route")
|
|
1862
|
+
domain.push("webhook_ingress");
|
|
1863
|
+
if (f.importSpecs.some((s) => /stripe|paypal|btcpay|alby/.test(s.toLowerCase())) || /stripe\.|paymentIntent|createPaymentIntent|confirmPayment|createCharge/.test(source) || d === "payments_webhooks" && (domain.includes("webhook_ingress") || source.includes("webhook")))
|
|
1864
|
+
domain.push("payment_mutation");
|
|
1865
|
+
if (/signIn\b|signOut\b|createSession|destroySession|issueToken|refreshToken|getToken/.test(source)) {
|
|
1866
|
+
domain.push("auth_token_mutation");
|
|
1867
|
+
}
|
|
1868
|
+
if (/triggerWebhook|sendWebhook|webhook\.send\b/.test(source))
|
|
1869
|
+
domain.push("webhook_delivery");
|
|
1870
|
+
if (/createCalendarEvent|updateCalendarEvent|deleteCalendarEvent|calendar\.events\.(insert|update|delete|patch)/.test(source)) {
|
|
1871
|
+
domain.push("calendar_mutation");
|
|
1872
|
+
}
|
|
1873
|
+
if (domain.length > 0)
|
|
1874
|
+
byFile[f.rel] = domain;
|
|
1875
|
+
}
|
|
1876
|
+
return { byFile };
|
|
1877
|
+
}
|
|
1878
|
+
var CALCOM_SIDE_EFFECT_SEVERITY = {
|
|
1879
|
+
booking_mutation: 4,
|
|
1880
|
+
payment_mutation: 4,
|
|
1881
|
+
auth_token_mutation: 4,
|
|
1882
|
+
webhook_delivery: 3,
|
|
1883
|
+
webhook_ingress: 3,
|
|
1884
|
+
calendar_mutation: 3
|
|
1885
|
+
};
|
|
1886
|
+
function calcomLoadBearingPolicy(ctx) {
|
|
1887
|
+
const byFile = {};
|
|
1888
|
+
const adapterEffectsResult = calcomInterpretSideEffects(ctx);
|
|
1889
|
+
for (const f of ctx.files) {
|
|
1890
|
+
let score = 0;
|
|
1891
|
+
const adapterEffects = adapterEffectsResult.byFile[f.rel] || [];
|
|
1892
|
+
if (adapterEffects.includes("booking_mutation"))
|
|
1893
|
+
score += 3;
|
|
1894
|
+
if (adapterEffects.includes("payment_mutation"))
|
|
1895
|
+
score += 3;
|
|
1896
|
+
if (adapterEffects.includes("auth_token_mutation"))
|
|
1897
|
+
score += 3;
|
|
1898
|
+
if (adapterEffects.includes("webhook_delivery"))
|
|
1899
|
+
score += 2;
|
|
1900
|
+
if (adapterEffects.includes("webhook_ingress"))
|
|
1901
|
+
score += 2;
|
|
1902
|
+
if (adapterEffects.includes("calendar_mutation"))
|
|
1903
|
+
score += 2;
|
|
1904
|
+
if (score > 0)
|
|
1905
|
+
byFile[f.rel] = score;
|
|
1906
|
+
}
|
|
1907
|
+
return { byFile };
|
|
1908
|
+
}
|
|
1909
|
+
function calcomSeverityPolicy(ctx) {
|
|
1910
|
+
const byFile = {};
|
|
1911
|
+
const adapterEffectsResult = calcomInterpretSideEffects(ctx);
|
|
1912
|
+
for (const f of ctx.files) {
|
|
1913
|
+
let score = 0;
|
|
1914
|
+
for (const e of f.sideEffectProfile)
|
|
1915
|
+
score += CALCOM_SIDE_EFFECT_SEVERITY[e] ?? 0;
|
|
1916
|
+
const adapterEffects = adapterEffectsResult.byFile[f.rel] || [];
|
|
1917
|
+
for (const e of adapterEffects)
|
|
1918
|
+
score += CALCOM_SIDE_EFFECT_SEVERITY[e] ?? 0;
|
|
1919
|
+
const d = inferCalcomDomain(f.rel, f.importSpecs) ?? f.productDomain;
|
|
1920
|
+
if (d === "booking_creation")
|
|
1921
|
+
score += 3;
|
|
1922
|
+
if (d === "payments" || d === "payments_webhooks")
|
|
1923
|
+
score += 3;
|
|
1924
|
+
if (d === "auth_oauth")
|
|
1925
|
+
score += 3;
|
|
1926
|
+
if (d === "webhooks")
|
|
1927
|
+
score += 2;
|
|
1928
|
+
if (score > 0)
|
|
1929
|
+
byFile[f.rel] = score;
|
|
1930
|
+
}
|
|
1931
|
+
return { byFile };
|
|
1932
|
+
}
|
|
1933
|
+
function calcomLabelPillars(ctx) {
|
|
1934
|
+
const byFile = {};
|
|
1935
|
+
function titleCase2(s) {
|
|
1936
|
+
return s.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
1937
|
+
}
|
|
1938
|
+
for (const f of ctx.files) {
|
|
1939
|
+
const domain = inferCalcomDomain(f.rel, f.importSpecs) ?? f.productDomain;
|
|
1940
|
+
if (domain === "unknown" || domain === "routing_infrastructure" || domain === "test_infrastructure" || domain === "generated_noise") {
|
|
1941
|
+
continue;
|
|
1942
|
+
}
|
|
1943
|
+
const labels = {
|
|
1944
|
+
booking_creation: "Booking",
|
|
1945
|
+
booking_management: "Booking",
|
|
1946
|
+
booking_audit: "Booking Audit",
|
|
1947
|
+
event_type_configuration: "Event Types",
|
|
1948
|
+
availability: "Availability",
|
|
1949
|
+
auth: "Auth",
|
|
1950
|
+
auth_oauth: "Auth OAuth",
|
|
1951
|
+
payments: "Payments",
|
|
1952
|
+
payments_webhooks: "Payment Webhooks",
|
|
1953
|
+
webhooks: "Webhooks",
|
|
1954
|
+
apps_marketplace: "Apps",
|
|
1955
|
+
calendar_integrations: "Calendar",
|
|
1956
|
+
video: "Video",
|
|
1957
|
+
onboarding: "Onboarding",
|
|
1958
|
+
settings: "Settings",
|
|
1959
|
+
admin: "Admin",
|
|
1960
|
+
data_table: "Data Table",
|
|
1961
|
+
shell_navigation: "Shell",
|
|
1962
|
+
forms: "Forms",
|
|
1963
|
+
embed: "Embed",
|
|
1964
|
+
notifications: "Notifications"
|
|
1965
|
+
};
|
|
1966
|
+
byFile[f.rel] = labels[domain] || titleCase2(domain.replace(/_/g, " "));
|
|
1967
|
+
}
|
|
1968
|
+
return { byFile };
|
|
1969
|
+
}
|
|
1970
|
+
var calcomAdapter = {
|
|
1971
|
+
id: "calcom",
|
|
1972
|
+
detect: detectCalcom,
|
|
1973
|
+
// Relocated/mirrored hooks (ADR-034), one at a time. Still core-authoritative:
|
|
1974
|
+
// ProductDomain assignment, sideEffectProfile, final severity, pillar labels.
|
|
1975
|
+
classify: calcomClassify,
|
|
1976
|
+
interpretSideEffects: calcomInterpretSideEffects,
|
|
1977
|
+
applySeverityPolicy: calcomSeverityPolicy,
|
|
1978
|
+
applyLoadBearingPolicy: calcomLoadBearingPolicy,
|
|
1979
|
+
getSurfacePatterns: () => CALCOM_SURFACE_PATTERNS,
|
|
1980
|
+
inferWriteIntents: calcomWriteIntents,
|
|
1981
|
+
labelPillars: calcomLabelPillars
|
|
1982
|
+
};
|
|
1983
|
+
|
|
1984
|
+
// ../brain/dist/pipeline/adapters/n8n.js
|
|
1985
|
+
import { readFileSync, existsSync as existsSync4 } from "fs";
|
|
1986
|
+
import { join as join4, basename as basename3 } from "path";
|
|
1987
|
+
|
|
1988
|
+
// ../brain/dist/pipeline/adapters/lift.js
|
|
1989
|
+
function capComponent(value, cap) {
|
|
1990
|
+
return Math.max(0, Math.min(value, cap));
|
|
1991
|
+
}
|
|
1992
|
+
function boundedSum(values, cap) {
|
|
1993
|
+
const sum = values.reduce((a, b) => a + b, 0);
|
|
1994
|
+
return capComponent(sum, cap);
|
|
1995
|
+
}
|
|
1996
|
+
|
|
1997
|
+
// ../brain/dist/pipeline/adapters/resolution.js
|
|
1998
|
+
import { basename as basename2 } from "path";
|
|
1999
|
+
function mapPath(path2, rules) {
|
|
2000
|
+
let current = path2;
|
|
2001
|
+
for (const rule of rules) {
|
|
2002
|
+
current = current.replace(rule.pattern, rule.replacement);
|
|
2003
|
+
}
|
|
2004
|
+
return current;
|
|
2005
|
+
}
|
|
2006
|
+
function getCanonicalName(path2, suffixes) {
|
|
2007
|
+
let base = basename2(path2);
|
|
2008
|
+
for (const suffix of suffixes) {
|
|
2009
|
+
base = base.replace(suffix, "");
|
|
2010
|
+
}
|
|
2011
|
+
return base;
|
|
2012
|
+
}
|
|
2013
|
+
function resolveSourceFiles(files, options) {
|
|
2014
|
+
const matches = [];
|
|
2015
|
+
if (options.expectedPath && files.some((f) => f.rel === options.expectedPath)) {
|
|
2016
|
+
matches.push(options.expectedPath);
|
|
2017
|
+
}
|
|
2018
|
+
const filter2 = options.filter ?? (() => true);
|
|
2019
|
+
if (options.aliases) {
|
|
2020
|
+
for (const alias of options.aliases) {
|
|
2021
|
+
const found = files.find((f) => filter2(f.rel) && f.rel.endsWith(`/${alias}`));
|
|
2022
|
+
if (found && !matches.includes(found.rel)) {
|
|
2023
|
+
matches.push(found.rel);
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
if (options.versionPattern) {
|
|
2028
|
+
const { baseName, suffix, versionPrefix = "V" } = options.versionPattern;
|
|
2029
|
+
const escapedBase = escapeRegExp(baseName);
|
|
2030
|
+
const escapedPrefix = escapeRegExp(versionPrefix);
|
|
2031
|
+
const escapedSuffix = escapeRegExp(suffix);
|
|
2032
|
+
const versionRegex = new RegExp(`^${escapedBase}${escapedPrefix}.*${escapedSuffix}$`);
|
|
2033
|
+
const foundGeneral = files.filter((f) => {
|
|
2034
|
+
if (!filter2(f.rel))
|
|
2035
|
+
return false;
|
|
2036
|
+
return versionRegex.test(basename2(f.rel));
|
|
2037
|
+
});
|
|
2038
|
+
for (const f of foundGeneral) {
|
|
2039
|
+
if (!matches.includes(f.rel)) {
|
|
2040
|
+
matches.push(f.rel);
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
return matches;
|
|
2045
|
+
}
|
|
2046
|
+
function escapeRegExp(string) {
|
|
2047
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2048
|
+
}
|
|
2049
|
+
var ResolutionTracker = class {
|
|
2050
|
+
stats = {};
|
|
2051
|
+
constructor(categories = []) {
|
|
2052
|
+
for (const category of categories) {
|
|
2053
|
+
this.ensureCategory(category);
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2056
|
+
ensureCategory(category) {
|
|
2057
|
+
if (!this.stats[category]) {
|
|
2058
|
+
this.stats[category] = { registered: 0, resolved: 0, unresolved: 0 };
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
/** Registers manifest item counts for a category */
|
|
2062
|
+
register(category, count = 1) {
|
|
2063
|
+
this.ensureCategory(category);
|
|
2064
|
+
this.stats[category].registered += count;
|
|
2065
|
+
}
|
|
2066
|
+
/** Increments successful resolution count for a category */
|
|
2067
|
+
resolve(category, count = 1) {
|
|
2068
|
+
this.ensureCategory(category);
|
|
2069
|
+
this.stats[category].resolved += count;
|
|
2070
|
+
}
|
|
2071
|
+
/** Increments unresolved count for a category */
|
|
2072
|
+
unresolve(category, count = 1) {
|
|
2073
|
+
this.ensureCategory(category);
|
|
2074
|
+
this.stats[category].unresolved += count;
|
|
2075
|
+
}
|
|
2076
|
+
/** Generates the metrics Record with namespaced keys, e.g. `<prefix>.<category>.registered` */
|
|
2077
|
+
getMetrics(prefix) {
|
|
2078
|
+
const metrics = {};
|
|
2079
|
+
for (const [category, stat2] of Object.entries(this.stats)) {
|
|
2080
|
+
metrics[`${prefix}.${category}.registered`] = stat2.registered;
|
|
2081
|
+
metrics[`${prefix}.${category}.resolved`] = stat2.resolved;
|
|
2082
|
+
metrics[`${prefix}.${category}.unresolved`] = stat2.unresolved;
|
|
2083
|
+
}
|
|
2084
|
+
return metrics;
|
|
2085
|
+
}
|
|
2086
|
+
};
|
|
2087
|
+
|
|
2088
|
+
// ../brain/dist/pipeline/adapters/n8n.js
|
|
2089
|
+
var N8nAdapter = class _N8nAdapter {
|
|
2090
|
+
id = "n8n";
|
|
2091
|
+
activeContextRoot = null;
|
|
2092
|
+
manifest = null;
|
|
2093
|
+
tracker = new ResolutionTracker(["nodes", "credentials"]);
|
|
2094
|
+
resolvedNodes = /* @__PURE__ */ new Set();
|
|
2095
|
+
// file rel paths
|
|
2096
|
+
resolvedCredentials = /* @__PURE__ */ new Set();
|
|
2097
|
+
// Structured manifest resolution: canonical node/credential name -> resolved
|
|
2098
|
+
// source rel paths. This is the authoritative answer to "which file does
|
|
2099
|
+
// manifest entry X point at" — the n8n gate reads this instead of guessing
|
|
2100
|
+
// filenames. Built during resolveManifest().
|
|
2101
|
+
nodeResolution = {};
|
|
2102
|
+
credentialResolution = {};
|
|
2103
|
+
// Inverse of nodeResolution: source rel path -> canonical manifest node name.
|
|
2104
|
+
// Role classification keys off the CANONICAL node identity (so HttpRequest.node.ts,
|
|
2105
|
+
// HttpRequestV1/2/3 all read as "HttpRequest"), not the file's own basename —
|
|
2106
|
+
// which would miss version-suffixed logic files like IfV2.node.ts.
|
|
2107
|
+
fileToCanonicalNode = {};
|
|
2108
|
+
detect(ctx) {
|
|
2109
|
+
const pkgPath = join4(ctx.projectRoot, "packages/nodes-base/package.json");
|
|
2110
|
+
if (!existsSync4(pkgPath))
|
|
2111
|
+
return false;
|
|
2112
|
+
try {
|
|
2113
|
+
const t0 = performance.now();
|
|
2114
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
2115
|
+
if (!pkg.n8n)
|
|
2116
|
+
return false;
|
|
2117
|
+
const tParse = performance.now() - t0;
|
|
2118
|
+
console.error(`[n8nAdapter] Timing: manifest parse time = ${tParse.toFixed(2)}ms`);
|
|
2119
|
+
this.activeContextRoot = ctx.projectRoot;
|
|
2120
|
+
this.manifest = pkg.n8n;
|
|
2121
|
+
const t1 = performance.now();
|
|
2122
|
+
this.resolveManifest(ctx);
|
|
2123
|
+
const tResolve = performance.now() - t1;
|
|
2124
|
+
console.error(`[n8nAdapter] Timing: manifest resolution time = ${tResolve.toFixed(2)}ms`);
|
|
2125
|
+
return true;
|
|
2126
|
+
} catch {
|
|
2127
|
+
return false;
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
resolveManifest(ctx) {
|
|
2131
|
+
this.tracker = new ResolutionTracker(["nodes", "credentials"]);
|
|
2132
|
+
this.resolvedNodes.clear();
|
|
2133
|
+
this.resolvedCredentials.clear();
|
|
2134
|
+
this.nodeResolution = {};
|
|
2135
|
+
this.credentialResolution = {};
|
|
2136
|
+
this.fileToCanonicalNode = {};
|
|
2137
|
+
const nodes = this.manifest.nodes || [];
|
|
2138
|
+
const credentials = this.manifest.credentials || [];
|
|
2139
|
+
this.tracker.register("nodes", nodes.length);
|
|
2140
|
+
this.tracker.register("credentials", credentials.length);
|
|
2141
|
+
const pathRules = [
|
|
2142
|
+
{ pattern: /^dist\//, replacement: "packages/nodes-base/" },
|
|
2143
|
+
{ pattern: /\.js$/, replacement: ".ts" }
|
|
2144
|
+
];
|
|
2145
|
+
const findMatches = (manifestPath) => {
|
|
2146
|
+
const expectedTs = mapPath(manifestPath, pathRules);
|
|
2147
|
+
const base = mapPath(basename3(manifestPath), [{ pattern: /\.js$/, replacement: ".ts" }]);
|
|
2148
|
+
const baseWithoutExt = getCanonicalName(base, [/\.node\.ts$/, /\.credentials\.ts$/]);
|
|
2149
|
+
const aliases = [base];
|
|
2150
|
+
if (base === "HttpRequest.node.ts") {
|
|
2151
|
+
aliases.push("HttpRequestV1.node.ts", "HttpRequestV2.node.ts", "HttpRequestV3.node.ts");
|
|
2152
|
+
}
|
|
2153
|
+
return resolveSourceFiles(ctx.files, {
|
|
2154
|
+
expectedPath: expectedTs,
|
|
2155
|
+
aliases,
|
|
2156
|
+
versionPattern: {
|
|
2157
|
+
baseName: baseWithoutExt,
|
|
2158
|
+
suffix: ".node.ts"
|
|
2159
|
+
},
|
|
2160
|
+
filter: (rel) => rel.startsWith("packages/nodes-base/")
|
|
2161
|
+
});
|
|
2162
|
+
};
|
|
2163
|
+
const canonicalName = (manifestPath) => getCanonicalName(manifestPath, [/\.node\.js$/, /\.credentials\.js$/]);
|
|
2164
|
+
for (const n of nodes) {
|
|
2165
|
+
const matches = findMatches(n);
|
|
2166
|
+
const name = canonicalName(n);
|
|
2167
|
+
this.nodeResolution[name] = matches;
|
|
2168
|
+
if (matches.length > 0) {
|
|
2169
|
+
for (const match2 of matches) {
|
|
2170
|
+
this.resolvedNodes.add(match2);
|
|
2171
|
+
if (!this.fileToCanonicalNode[match2])
|
|
2172
|
+
this.fileToCanonicalNode[match2] = name;
|
|
2173
|
+
}
|
|
2174
|
+
this.tracker.resolve("nodes");
|
|
2175
|
+
} else {
|
|
2176
|
+
this.tracker.unresolve("nodes");
|
|
2177
|
+
}
|
|
2178
|
+
}
|
|
2179
|
+
for (const c of credentials) {
|
|
2180
|
+
const matches = findMatches(c);
|
|
2181
|
+
const name = canonicalName(c);
|
|
2182
|
+
this.credentialResolution[name] = matches;
|
|
2183
|
+
if (matches.length > 0) {
|
|
2184
|
+
for (const match2 of matches) {
|
|
2185
|
+
this.resolvedCredentials.add(match2);
|
|
2186
|
+
}
|
|
2187
|
+
this.tracker.resolve("credentials");
|
|
2188
|
+
} else {
|
|
2189
|
+
this.tracker.unresolve("credentials");
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
const metrics = this.getMetrics();
|
|
2193
|
+
console.error(`[n8nAdapter] Registered nodes: ${metrics["n8n.nodes.registered"]} | Resolved: ${metrics["n8n.nodes.resolved"]} | Unresolved: ${metrics["n8n.nodes.unresolved"]}`);
|
|
2194
|
+
console.error(`[n8nAdapter] Registered credentials: ${metrics["n8n.credentials.registered"]} | Resolved: ${metrics["n8n.credentials.resolved"]}`);
|
|
2195
|
+
}
|
|
2196
|
+
getMetrics() {
|
|
2197
|
+
return this.tracker.getMetrics("n8n");
|
|
2198
|
+
}
|
|
2199
|
+
/** Resolved source rel paths for a manifest node by canonical name (e.g. "HttpRequest").
|
|
2200
|
+
* Empty array = registered in the manifest but no source file resolved. The
|
|
2201
|
+
* n8n gate uses this so rank assertions target the real manifest-resolved
|
|
2202
|
+
* files, not filename guesses. */
|
|
2203
|
+
getResolvedNode(name) {
|
|
2204
|
+
return this.nodeResolution[name] ?? [];
|
|
2205
|
+
}
|
|
2206
|
+
/** Full manifest resolution map (canonical name -> resolved source rel paths). */
|
|
2207
|
+
getNodeResolution() {
|
|
2208
|
+
return this.nodeResolution;
|
|
2209
|
+
}
|
|
2210
|
+
getCredentialResolution() {
|
|
2211
|
+
return this.credentialResolution;
|
|
2212
|
+
}
|
|
2213
|
+
// Behavioral execution role by CANONICAL node identity. The n8n execution
|
|
2214
|
+
// BACKBONE — universal triggers/ingress, control flow, data transforms, the
|
|
2215
|
+
// core HTTP protocol — is what runs in (nearly) every workflow regardless of
|
|
2216
|
+
// which SaaS is wired up. The ~420 service nodes (Slack, Aws*, Google*) AND
|
|
2217
|
+
// service-specific triggers (SlackTrigger) are interchangeable integration
|
|
2218
|
+
// leaves: their structural size is already counted by staticGravity, so the
|
|
2219
|
+
// behavioral lift gives them nothing but bare manifest membership. Only the
|
|
2220
|
+
// backbone — under-rated by static because these files are tiny — is lifted.
|
|
2221
|
+
static CORE_TRIGGER = /* @__PURE__ */ new Set([
|
|
2222
|
+
"Webhook",
|
|
2223
|
+
"ScheduleTrigger",
|
|
2224
|
+
"Cron",
|
|
2225
|
+
"Interval",
|
|
2226
|
+
"ManualTrigger",
|
|
2227
|
+
"Start",
|
|
2228
|
+
"ErrorTrigger",
|
|
2229
|
+
"WorkflowTrigger",
|
|
2230
|
+
"ExecuteWorkflowTrigger",
|
|
2231
|
+
"FormTrigger",
|
|
2232
|
+
"ChatTrigger"
|
|
2233
|
+
]);
|
|
2234
|
+
static CONTROL_FLOW = /* @__PURE__ */ new Set(["If", "Switch", "Merge", "Filter", "Compare", "CompareDatasets"]);
|
|
2235
|
+
static DATA_TRANSFORM = /* @__PURE__ */ new Set(["Set", "Code", "Function", "FunctionItem", "RenameKeys", "ItemLists", "Aggregate", "SplitOut"]);
|
|
2236
|
+
static CORE_PROTOCOL = /* @__PURE__ */ new Set(["HttpRequest", "GraphQL"]);
|
|
2237
|
+
static BACKBONE_ROLES = /* @__PURE__ */ new Set(["trigger", "control_flow", "data_transform", "core_protocol"]);
|
|
2238
|
+
roleForNode(canonical) {
|
|
2239
|
+
if (_N8nAdapter.CORE_TRIGGER.has(canonical)) {
|
|
2240
|
+
const tags2 = ["trigger_node"];
|
|
2241
|
+
if (canonical === "Webhook")
|
|
2242
|
+
tags2.push("ingress_boundary");
|
|
2243
|
+
return { role: "trigger", tags: tags2 };
|
|
2244
|
+
}
|
|
2245
|
+
if (_N8nAdapter.CONTROL_FLOW.has(canonical))
|
|
2246
|
+
return { role: "control_flow", tags: ["control_flow"] };
|
|
2247
|
+
if (_N8nAdapter.DATA_TRANSFORM.has(canonical))
|
|
2248
|
+
return { role: "data_transform", tags: ["data_transform"] };
|
|
2249
|
+
if (_N8nAdapter.CORE_PROTOCOL.has(canonical))
|
|
2250
|
+
return { role: "core_protocol", tags: ["core_protocol", "egress_boundary"] };
|
|
2251
|
+
const tags = ["external_integration"];
|
|
2252
|
+
if (canonical.endsWith("Trigger"))
|
|
2253
|
+
tags.push("service_trigger", "ingress_boundary");
|
|
2254
|
+
return { role: "external_integration", tags };
|
|
2255
|
+
}
|
|
2256
|
+
classify(ctx) {
|
|
2257
|
+
const byFile = {};
|
|
2258
|
+
for (const f of ctx.files) {
|
|
2259
|
+
const isNode = this.resolvedNodes.has(f.rel);
|
|
2260
|
+
const isCred = this.resolvedCredentials.has(f.rel);
|
|
2261
|
+
if (!isNode && !isCred)
|
|
2262
|
+
continue;
|
|
2263
|
+
let role;
|
|
2264
|
+
const tags = [];
|
|
2265
|
+
if (isCred) {
|
|
2266
|
+
role = "credential_handler";
|
|
2267
|
+
tags.push("credential_handler");
|
|
2268
|
+
} else {
|
|
2269
|
+
const canonical = this.fileToCanonicalNode[f.rel] ?? basename3(f.rel).replace(/\.node\.ts$/, "");
|
|
2270
|
+
const r = this.roleForNode(canonical);
|
|
2271
|
+
role = r.role;
|
|
2272
|
+
tags.push(...r.tags);
|
|
2273
|
+
tags.push(`node:${canonical.toLowerCase()}`);
|
|
2274
|
+
}
|
|
2275
|
+
byFile[f.rel] = { adapterDomain: "n8n", domainTags: tags, executionRole: role };
|
|
2276
|
+
}
|
|
2277
|
+
return { byFile };
|
|
2278
|
+
}
|
|
2279
|
+
computeBehavioralLift(ctx) {
|
|
2280
|
+
const t0 = performance.now();
|
|
2281
|
+
const byFile = {};
|
|
2282
|
+
const classifications = this.classify(ctx).byFile;
|
|
2283
|
+
for (const f of ctx.files) {
|
|
2284
|
+
const c = classifications[f.rel];
|
|
2285
|
+
if (!c)
|
|
2286
|
+
continue;
|
|
2287
|
+
let lift = 0;
|
|
2288
|
+
const isBackbone = c.executionRole != null && _N8nAdapter.BACKBONE_ROLES.has(c.executionRole);
|
|
2289
|
+
if (this.resolvedNodes.has(f.rel)) {
|
|
2290
|
+
lift += 10;
|
|
2291
|
+
} else if (this.resolvedCredentials.has(f.rel)) {
|
|
2292
|
+
lift += 2;
|
|
2293
|
+
}
|
|
2294
|
+
if (c.executionRole === "trigger")
|
|
2295
|
+
lift += 35;
|
|
2296
|
+
else if (c.executionRole === "control_flow")
|
|
2297
|
+
lift += 45;
|
|
2298
|
+
else if (c.executionRole === "data_transform")
|
|
2299
|
+
lift += 40;
|
|
2300
|
+
else if (c.executionRole === "core_protocol")
|
|
2301
|
+
lift += 40;
|
|
2302
|
+
else if (c.executionRole === "credential_handler")
|
|
2303
|
+
lift += 15;
|
|
2304
|
+
if (isBackbone) {
|
|
2305
|
+
if (c.domainTags?.includes("ingress_boundary"))
|
|
2306
|
+
lift += 15;
|
|
2307
|
+
if (c.domainTags?.includes("egress_boundary"))
|
|
2308
|
+
lift += 10;
|
|
2309
|
+
if (f.sideEffectProfile.includes("external_api_call"))
|
|
2310
|
+
lift += 8;
|
|
2311
|
+
if (f.sideEffectProfile.includes("database_write"))
|
|
2312
|
+
lift += 12;
|
|
2313
|
+
const cxFlags = [];
|
|
2314
|
+
if (f.gravitySignals?.publicSurface > 5)
|
|
2315
|
+
cxFlags.push(4);
|
|
2316
|
+
if (f.heatSignals?.maxNesting >= 4)
|
|
2317
|
+
cxFlags.push(4);
|
|
2318
|
+
if (f.heatSignals?.longFunctions > 0)
|
|
2319
|
+
cxFlags.push(4);
|
|
2320
|
+
if (f.gravitySignals?.loc > 500)
|
|
2321
|
+
cxFlags.push(4);
|
|
2322
|
+
else if (f.gravitySignals?.loc > 200)
|
|
2323
|
+
cxFlags.push(2);
|
|
2324
|
+
if (f.gravitySignals?.cyclomatic > 50)
|
|
2325
|
+
cxFlags.push(4);
|
|
2326
|
+
else if (f.gravitySignals?.cyclomatic > 20)
|
|
2327
|
+
cxFlags.push(2);
|
|
2328
|
+
lift += boundedSum(cxFlags, 20);
|
|
2329
|
+
if (f.staticGravity > 0)
|
|
2330
|
+
lift += capComponent(Math.floor(f.staticGravity / 10), 10);
|
|
2331
|
+
}
|
|
2332
|
+
byFile[f.rel] = lift;
|
|
2333
|
+
}
|
|
2334
|
+
const tLift = performance.now() - t0;
|
|
2335
|
+
console.error(`[n8nAdapter] Timing: lift computation time = ${tLift.toFixed(2)}ms`);
|
|
2336
|
+
return { byFile };
|
|
2337
|
+
}
|
|
2338
|
+
};
|
|
2339
|
+
var n8nAdapter = new N8nAdapter();
|
|
2340
|
+
|
|
2341
|
+
// ../brain/dist/pipeline/adapters/index.js
|
|
2342
|
+
adapterRegistry.register(calcomAdapter);
|
|
2343
|
+
adapterRegistry.register(n8nAdapter);
|
|
2344
|
+
|
|
2345
|
+
// ../brain/dist/pipeline/classification.js
|
|
1536
2346
|
function inferSideEffectProfile(source, importSpecs, productDomain, frameworkRole) {
|
|
1537
2347
|
const effects = /* @__PURE__ */ new Set();
|
|
1538
2348
|
if (/router\.(push|replace|back)\(|redirect\(|notFound\(|permanentRedirect\(/.test(source)) {
|
|
@@ -1550,22 +2360,8 @@ function inferSideEffectProfile(source, importSpecs, productDomain, frameworkRol
|
|
|
1550
2360
|
if (/prisma\s*[.?]\s*\w+\s*[.?]\s*(findMany|findUnique|findFirst|findFirstOrThrow|findUniqueOrThrow|count|aggregate|groupBy)\b/.test(source)) {
|
|
1551
2361
|
effects.add("database_read");
|
|
1552
2362
|
}
|
|
1553
|
-
if (/createBooking|handleNewBooking|cancelBooking|rescheduleBooking|handleBooking|createRecurring/.test(source) || productDomain === "booking_creation" && /useMutation\b|\.mutate\b|\.mutateAsync\b/.test(source))
|
|
1554
|
-
effects.add("booking_mutation");
|
|
1555
|
-
if (/stripe\.webhooks\.(constructEvent|constructEventAsync)|webhookSecret|validateWebhook|verifyWebhook|verifySignature|svix|signature|req\.body|req\.rawBody/.test(source) || productDomain === "payments_webhooks" && frameworkRole === "pages_api_route")
|
|
1556
|
-
effects.add("webhook_ingress");
|
|
1557
|
-
if (importSpecs.some((s) => /stripe|paypal|btcpay|alby/.test(s.toLowerCase())) || /stripe\.|paymentIntent|createPaymentIntent|confirmPayment|createCharge/.test(source) || productDomain === "payments_webhooks" && (effects.has("webhook_ingress") || source.includes("webhook")))
|
|
1558
|
-
effects.add("payment_mutation");
|
|
1559
|
-
if (/signIn\b|signOut\b|createSession|destroySession|issueToken|refreshToken|getToken/.test(source)) {
|
|
1560
|
-
effects.add("auth_token_mutation");
|
|
1561
|
-
}
|
|
1562
|
-
if (/triggerWebhook|sendWebhook|webhook\.send\b/.test(source))
|
|
1563
|
-
effects.add("webhook_delivery");
|
|
1564
2363
|
if (/sendEmail|sendMail\b|mailer\./.test(source) || importSpecs.some((s) => /nodemailer|resend|sendgrid|postmark|mailgun/.test(s)))
|
|
1565
2364
|
effects.add("email_send");
|
|
1566
|
-
if (/createCalendarEvent|updateCalendarEvent|deleteCalendarEvent|calendar\.events\.(insert|update|delete|patch)/.test(source)) {
|
|
1567
|
-
effects.add("calendar_mutation");
|
|
1568
|
-
}
|
|
1569
2365
|
if (/revalidatePath\b|revalidateTag\b/.test(source))
|
|
1570
2366
|
effects.add("cache_revalidation");
|
|
1571
2367
|
if (/localStorage\.|sessionStorage\./.test(source))
|
|
@@ -1579,37 +2375,12 @@ function inferSideEffectProfile(source, importSpecs, productDomain, frameworkRol
|
|
|
1579
2375
|
effects.add("none_detected");
|
|
1580
2376
|
return [...effects];
|
|
1581
2377
|
}
|
|
1582
|
-
function
|
|
2378
|
+
function inferGenericWriteIntents(sideEffectProfile) {
|
|
1583
2379
|
const intents = [];
|
|
1584
|
-
if (productDomain === "booking_creation" || productDomain === "booking_ui_delegate") {
|
|
1585
|
-
intents.push("create_booking");
|
|
1586
|
-
if (relPath.includes("reschedule") || relPath.includes("Reschedule"))
|
|
1587
|
-
intents.push("reschedule_booking");
|
|
1588
|
-
if (relPath.includes("recurring") || relPath.includes("Recurring"))
|
|
1589
|
-
intents.push("create_recurring_booking");
|
|
1590
|
-
}
|
|
1591
|
-
if (productDomain === "booking_management")
|
|
1592
|
-
intents.push("cancel_booking");
|
|
1593
|
-
if (productDomain === "event_type_configuration")
|
|
1594
|
-
intents.push("update_event_type");
|
|
1595
|
-
if (productDomain === "availability")
|
|
1596
|
-
intents.push("update_availability");
|
|
1597
|
-
if (productDomain === "payments")
|
|
1598
|
-
intents.push("create_payment");
|
|
1599
|
-
if (productDomain === "payments_webhooks")
|
|
1600
|
-
intents.push("handle_payment_webhook");
|
|
1601
|
-
if (productDomain === "auth_oauth") {
|
|
1602
|
-
intents.push("issue_auth_token");
|
|
1603
|
-
intents.push("refresh_auth_token");
|
|
1604
|
-
}
|
|
1605
|
-
if (sideEffectProfile.includes("webhook_delivery"))
|
|
1606
|
-
intents.push("send_webhook");
|
|
1607
|
-
if (productDomain === "settings")
|
|
1608
|
-
intents.push("update_user_settings");
|
|
1609
2380
|
if (sideEffectProfile.includes("local_storage") || sideEffectProfile.includes("indexed_db")) {
|
|
1610
2381
|
intents.push("persist_local_state");
|
|
1611
2382
|
}
|
|
1612
|
-
return intents
|
|
2383
|
+
return intents;
|
|
1613
2384
|
}
|
|
1614
2385
|
var ENTRYPOINT_ROLES = /* @__PURE__ */ new Set([
|
|
1615
2386
|
"app_route_page",
|
|
@@ -1634,7 +2405,7 @@ function inferRiskTypesPass1(rel, frameworkRole, productDomain, sideEffectProfil
|
|
|
1634
2405
|
}
|
|
1635
2406
|
if (productDomain === "forms" && (gravitySignals.fanIn > 3 || gravitySignals.publicSurface > 5))
|
|
1636
2407
|
types2.push("registry_bottleneck");
|
|
1637
|
-
if (sideEffectProfile.some((s) => ["
|
|
2408
|
+
if (sideEffectProfile.some((s) => ["database_write", "trpc_mutation", "external_api_call"].includes(s)) && gravitySignals.cyclomatic > 10)
|
|
1638
2409
|
types2.push("mutation_orchestration");
|
|
1639
2410
|
if (ENTRYPOINT_ROLES.has(frameworkRole) && sideEffectProfile.includes("database_write")) {
|
|
1640
2411
|
types2.push("route_handler_write_path");
|
|
@@ -1646,24 +2417,6 @@ function inferRiskTypesPass1(rel, frameworkRole, productDomain, sideEffectProfil
|
|
|
1646
2417
|
}
|
|
1647
2418
|
return types2;
|
|
1648
2419
|
}
|
|
1649
|
-
var DOMAIN_SURFACE_PATTERNS = {
|
|
1650
|
-
booking_creation: {
|
|
1651
|
-
expected: [/book/i, /booking/i, /reschedule/i, /booking-success/i, /api\/book/i, /create-booking/i],
|
|
1652
|
-
wrong: [/event-type/i, /event-types/i, /eventtypes/i, /availability/i, /schedule/i]
|
|
1653
|
-
},
|
|
1654
|
-
booking_ui_delegate: {
|
|
1655
|
-
expected: [/book/i, /booking/i, /reschedule/i, /event-type/i, /event-types/i],
|
|
1656
|
-
wrong: [/settings/i, /admin/i, /onboarding/i]
|
|
1657
|
-
},
|
|
1658
|
-
payments_webhooks: {
|
|
1659
|
-
expected: [/webhook/i, /stripe/i, /payment/i],
|
|
1660
|
-
wrong: [/settings/i, /onboarding/i, /profile/i]
|
|
1661
|
-
},
|
|
1662
|
-
auth_oauth: {
|
|
1663
|
-
expected: [/oauth/i, /callback/i, /auth/i, /signin/i, /login/i],
|
|
1664
|
-
wrong: [/booking/i, /payment/i, /settings/i]
|
|
1665
|
-
}
|
|
1666
|
-
};
|
|
1667
2420
|
function findRuntimeEntrypoints(relPath, importedByMap, persisted, maxDepth = 8) {
|
|
1668
2421
|
const results = [];
|
|
1669
2422
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -1703,12 +2456,12 @@ function findRuntimeEntrypoints(relPath, importedByMap, persisted, maxDepth = 8)
|
|
|
1703
2456
|
}
|
|
1704
2457
|
return [...byPath.values()].sort((a, b) => a.distance - b.distance);
|
|
1705
2458
|
}
|
|
1706
|
-
function deriveEntrypointTraceStatus(domain, entrypoints, unresolved) {
|
|
2459
|
+
function deriveEntrypointTraceStatus(domain, entrypoints, unresolved, surfacePatterns = /* @__PURE__ */ new Map()) {
|
|
1707
2460
|
if (entrypoints.length === 0 && unresolved.length > 0)
|
|
1708
2461
|
return "blocked_by_alias_resolution";
|
|
1709
2462
|
if (entrypoints.length === 0)
|
|
1710
2463
|
return "no_runtime_entrypoint_found";
|
|
1711
|
-
const patterns =
|
|
2464
|
+
const patterns = surfacePatterns.get(domain);
|
|
1712
2465
|
if (patterns) {
|
|
1713
2466
|
const allWrong = entrypoints.every((e) => patterns.wrong.some((p) => p.test(e.path)) && !patterns.expected.some((p) => p.test(e.path)));
|
|
1714
2467
|
if (allWrong)
|
|
@@ -1716,7 +2469,7 @@ function deriveEntrypointTraceStatus(domain, entrypoints, unresolved) {
|
|
|
1716
2469
|
}
|
|
1717
2470
|
return unresolved.length === 0 ? "complete" : "partial";
|
|
1718
2471
|
}
|
|
1719
|
-
function computeLoadBearingScore(gravity, heat, importedByCount, sideEffectProfile, productDomain, smellMaxSeverity, runtimeEntrypoints) {
|
|
2472
|
+
function computeLoadBearingScore(gravity, heat, importedByCount, sideEffectProfile, productDomain, smellMaxSeverity, runtimeEntrypoints, adapterLoadBearingBoost, domainTags) {
|
|
1720
2473
|
let score = 0;
|
|
1721
2474
|
if (gravity >= 85)
|
|
1722
2475
|
score += 2;
|
|
@@ -1728,18 +2481,8 @@ function computeLoadBearingScore(gravity, heat, importedByCount, sideEffectProfi
|
|
|
1728
2481
|
score += 1;
|
|
1729
2482
|
if (sideEffectProfile.includes("database_write"))
|
|
1730
2483
|
score += 3;
|
|
1731
|
-
if (
|
|
1732
|
-
score +=
|
|
1733
|
-
if (sideEffectProfile.includes("payment_mutation"))
|
|
1734
|
-
score += 3;
|
|
1735
|
-
if (sideEffectProfile.includes("auth_token_mutation"))
|
|
1736
|
-
score += 3;
|
|
1737
|
-
if (sideEffectProfile.includes("webhook_delivery"))
|
|
1738
|
-
score += 2;
|
|
1739
|
-
if (sideEffectProfile.includes("webhook_ingress"))
|
|
1740
|
-
score += 2;
|
|
1741
|
-
if (sideEffectProfile.includes("calendar_mutation"))
|
|
1742
|
-
score += 2;
|
|
2484
|
+
if (adapterLoadBearingBoost)
|
|
2485
|
+
score += adapterLoadBearingBoost;
|
|
1743
2486
|
if (sideEffectProfile.includes("redirect"))
|
|
1744
2487
|
score += 1;
|
|
1745
2488
|
if (sideEffectProfile.includes("analytics_event"))
|
|
@@ -1752,14 +2495,45 @@ function computeLoadBearingScore(gravity, heat, importedByCount, sideEffectProfi
|
|
|
1752
2495
|
"webhooks",
|
|
1753
2496
|
"payments_webhooks"
|
|
1754
2497
|
];
|
|
1755
|
-
|
|
2498
|
+
const effectiveDomain = domainTags?.[0] ?? productDomain;
|
|
2499
|
+
if (highImpactDomains.includes(effectiveDomain))
|
|
1756
2500
|
score += 2;
|
|
1757
2501
|
if (smellMaxSeverity === 5)
|
|
1758
2502
|
score += 3;
|
|
1759
2503
|
return score;
|
|
1760
2504
|
}
|
|
1761
|
-
function
|
|
1762
|
-
const
|
|
2505
|
+
function computeExecutionReach(nodes, importedBy, isRealSource) {
|
|
2506
|
+
const rawReach = /* @__PURE__ */ new Map();
|
|
2507
|
+
for (const start of nodes) {
|
|
2508
|
+
const visited = /* @__PURE__ */ new Set([start]);
|
|
2509
|
+
const queue = [start];
|
|
2510
|
+
while (queue.length > 0) {
|
|
2511
|
+
const u = queue.shift();
|
|
2512
|
+
for (const v of importedBy.get(u) ?? []) {
|
|
2513
|
+
if (!isRealSource.get(v) || visited.has(v))
|
|
2514
|
+
continue;
|
|
2515
|
+
visited.add(v);
|
|
2516
|
+
queue.push(v);
|
|
2517
|
+
}
|
|
2518
|
+
}
|
|
2519
|
+
rawReach.set(start, visited.size - 1);
|
|
2520
|
+
}
|
|
2521
|
+
let maxLogReach = 0;
|
|
2522
|
+
const logReach = /* @__PURE__ */ new Map();
|
|
2523
|
+
for (const [node, reach] of rawReach) {
|
|
2524
|
+
const logR = Math.log2(1 + reach);
|
|
2525
|
+
logReach.set(node, logR);
|
|
2526
|
+
if (logR > maxLogReach)
|
|
2527
|
+
maxLogReach = logR;
|
|
2528
|
+
}
|
|
2529
|
+
const normalized = /* @__PURE__ */ new Map();
|
|
2530
|
+
for (const [node, logR] of logReach) {
|
|
2531
|
+
normalized.set(node, maxLogReach > 0 ? logR / maxLogReach : 0);
|
|
2532
|
+
}
|
|
2533
|
+
return normalized;
|
|
2534
|
+
}
|
|
2535
|
+
function pageRank(nodes, outEdges, damping = 0.85, iters = 20) {
|
|
2536
|
+
const n = nodes.length;
|
|
1763
2537
|
const rank = /* @__PURE__ */ new Map();
|
|
1764
2538
|
if (n === 0)
|
|
1765
2539
|
return rank;
|
|
@@ -1885,7 +2659,7 @@ function pillarNameFromCluster(files) {
|
|
|
1885
2659
|
if (top)
|
|
1886
2660
|
return titleCase(top[0]);
|
|
1887
2661
|
}
|
|
1888
|
-
const topFile =
|
|
2662
|
+
const topFile = basename4(files[0].rel, extname3(files[0].rel));
|
|
1889
2663
|
return titleCase(topFile);
|
|
1890
2664
|
}
|
|
1891
2665
|
function dirname_simple(p) {
|
|
@@ -1912,7 +2686,7 @@ function buildPillars(classified, communities) {
|
|
|
1912
2686
|
const sorted = [...files].sort((a, b) => b.gravity - a.gravity);
|
|
1913
2687
|
pillars.push({
|
|
1914
2688
|
name,
|
|
1915
|
-
description: `${name} subsystem: ${files.length} file${files.length > 1 ? "s" : ""} centered on ${
|
|
2689
|
+
description: `${name} subsystem: ${files.length} file${files.length > 1 ? "s" : ""} centered on ${basename4(sorted[0].rel)}.`,
|
|
1916
2690
|
memberFiles: sorted.map((f) => f.rel)
|
|
1917
2691
|
});
|
|
1918
2692
|
}
|
|
@@ -1938,7 +2712,7 @@ function buildPillars(classified, communities) {
|
|
|
1938
2712
|
} else {
|
|
1939
2713
|
pillars.push({
|
|
1940
2714
|
name,
|
|
1941
|
-
description: `${g.files.length} files centered on ${
|
|
2715
|
+
description: `${g.files.length} files centered on ${basename4(top[0].rel)}.`,
|
|
1942
2716
|
memberFiles: top.map((f) => f.rel)
|
|
1943
2717
|
});
|
|
1944
2718
|
}
|
|
@@ -1959,10 +2733,10 @@ function buildPillars(classified, communities) {
|
|
|
1959
2733
|
for (const rel of p.memberFiles) {
|
|
1960
2734
|
const f = classified.find((c) => c.rel === rel);
|
|
1961
2735
|
const role = f?.frameworkRole || "unknown";
|
|
1962
|
-
const domain = f?.productDomain
|
|
2736
|
+
const domain = f?.domainTags?.[0] ?? f?.productDomain ?? "unknown";
|
|
1963
2737
|
let bucket;
|
|
1964
2738
|
if (domain !== "unknown" && domain !== "routing_infrastructure" && domain !== "test_infrastructure" && domain !== "generated_noise") {
|
|
1965
|
-
bucket = domainToGroupLabel(domain);
|
|
2739
|
+
bucket = f?.adapterPillarLabel ?? domainToGroupLabel(domain);
|
|
1966
2740
|
} else if (role === "hook") {
|
|
1967
2741
|
bucket = "Hooks";
|
|
1968
2742
|
} else if (["app_route_page", "app_route_handler", "app_route_layout", "pages_route", "pages_api_route", "trpc_api_route"].includes(role)) {
|
|
@@ -2045,13 +2819,26 @@ async function runClassification(projectRoot, inv, res) {
|
|
|
2045
2819
|
}
|
|
2046
2820
|
const ranks = pageRank(realNodes, outEdges);
|
|
2047
2821
|
const communities = detectCommunities(realNodes, undirected);
|
|
2822
|
+
const executionReachByFile = computeExecutionReach(realNodes, importedBy, isRealSource);
|
|
2823
|
+
const gravityV2 = process.env.VIBE_GRAVITY_V2 === "1";
|
|
2824
|
+
let maxLog2FanIn = 0;
|
|
2825
|
+
let maxLog2Cyclomatic = 0;
|
|
2826
|
+
if (gravityV2) {
|
|
2827
|
+
for (const w of work) {
|
|
2828
|
+
if (!isRealSource.get(w.rel))
|
|
2829
|
+
continue;
|
|
2830
|
+
const fanIn = [...importedBy.get(w.rel) || []].filter((src) => isRealSource.get(src)).length;
|
|
2831
|
+
maxLog2FanIn = Math.max(maxLog2FanIn, Math.log2(fanIn + 1));
|
|
2832
|
+
maxLog2Cyclomatic = Math.max(maxLog2Cyclomatic, Math.log2(w.ast.cyclomatic + 1));
|
|
2833
|
+
}
|
|
2834
|
+
}
|
|
2048
2835
|
const metaLookup = /* @__PURE__ */ new Map();
|
|
2049
2836
|
const sideEffectsByFile = /* @__PURE__ */ new Map();
|
|
2050
2837
|
const writeIntentsByFile = /* @__PURE__ */ new Map();
|
|
2051
2838
|
for (const w of work) {
|
|
2052
2839
|
const effects = inferSideEffectProfile(w.source, w.importSpecs, w.productDomain, w.frameworkRole);
|
|
2053
2840
|
sideEffectsByFile.set(w.rel, effects);
|
|
2054
|
-
writeIntentsByFile.set(w.rel,
|
|
2841
|
+
writeIntentsByFile.set(w.rel, inferGenericWriteIntents(effects));
|
|
2055
2842
|
metaLookup.set(w.rel, { frameworkRole: w.frameworkRole, productDomain: w.productDomain });
|
|
2056
2843
|
}
|
|
2057
2844
|
const riskTypesByFile = /* @__PURE__ */ new Map();
|
|
@@ -2064,21 +2851,33 @@ async function runClassification(projectRoot, inv, res) {
|
|
|
2064
2851
|
const real = isRealSource.get(w.rel);
|
|
2065
2852
|
const fanIn = [...importedBy.get(w.rel) || []].filter((src) => isRealSource.get(src)).length;
|
|
2066
2853
|
const centrality = real ? ranks.get(w.rel) || 0 : 0;
|
|
2854
|
+
const executionReach = executionReachByFile.get(w.rel) ?? 0;
|
|
2067
2855
|
const gs = {
|
|
2068
2856
|
fanIn,
|
|
2069
2857
|
fanOut: fanOut.get(w.rel) || 0,
|
|
2070
2858
|
centrality,
|
|
2071
2859
|
cyclomatic: w.ast.cyclomatic,
|
|
2072
2860
|
publicSurface: w.ast.publicSurface,
|
|
2073
|
-
loc: w.ast.loc
|
|
2861
|
+
loc: w.ast.loc,
|
|
2862
|
+
executionReach
|
|
2074
2863
|
};
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2864
|
+
let gravity;
|
|
2865
|
+
if (gravityV2) {
|
|
2866
|
+
const fanInNorm = maxLog2FanIn > 0 ? Math.log2(fanIn + 1) / maxLog2FanIn : 0;
|
|
2867
|
+
const cyclomaticNorm = maxLog2Cyclomatic > 0 ? Math.log2(w.ast.cyclomatic + 1) / maxLog2Cyclomatic : 0;
|
|
2868
|
+
let gravityRaw = centrality * 0.45 + fanInNorm * 0.35 + executionReach * 0.15 + cyclomaticNorm * 0.05;
|
|
2869
|
+
if (!real)
|
|
2870
|
+
gravityRaw *= 0.2;
|
|
2871
|
+
gravity = Math.max(0, Math.min(100, gravityRaw * 100));
|
|
2872
|
+
} else {
|
|
2873
|
+
const depthRatio = (w.ast.cyclomatic + w.ast.maxNesting * 2) / Math.max(1, w.ast.publicSurface);
|
|
2874
|
+
const depthFactor = Math.min(1, Math.log2(depthRatio + 1) / 3);
|
|
2875
|
+
const adjustedCentrality = centrality * (0.3 + 0.7 * depthFactor);
|
|
2876
|
+
let gravityRaw = adjustedCentrality * 50 + Math.log2(fanIn + 1) * 6 + Math.log2(w.ast.cyclomatic + 1) * 7 + Math.log2(w.ast.publicSurface + 1) * 2 + (w.ast.maxNesting >= 4 ? 5 : 0);
|
|
2877
|
+
if (!real)
|
|
2878
|
+
gravityRaw *= 0.2;
|
|
2879
|
+
gravity = Math.max(0, Math.min(100, gravityRaw));
|
|
2880
|
+
}
|
|
2082
2881
|
const heat = real ? computeHeat(w.ast.smells) : 0;
|
|
2083
2882
|
gravityByFile.set(w.rel, gravity);
|
|
2084
2883
|
heatByFile.set(w.rel, heat);
|
|
@@ -2116,6 +2915,51 @@ async function runClassification(projectRoot, inv, res) {
|
|
|
2116
2915
|
if (types2.length === 0)
|
|
2117
2916
|
types2.push("complexity_hotspot");
|
|
2118
2917
|
}
|
|
2918
|
+
const adapterCtx = {
|
|
2919
|
+
projectRoot,
|
|
2920
|
+
files: work.map((w) => ({
|
|
2921
|
+
rel: w.rel,
|
|
2922
|
+
lang: w.lang,
|
|
2923
|
+
isRealSource: isRealSource.get(w.rel),
|
|
2924
|
+
frameworkRole: w.frameworkRole,
|
|
2925
|
+
productDomain: w.productDomain,
|
|
2926
|
+
staticGravity: gravityByFile.get(w.rel),
|
|
2927
|
+
gravitySignals: gravitySignalsByFile.get(w.rel),
|
|
2928
|
+
heatSignals: {
|
|
2929
|
+
todos: w.ast.smells.filter((s) => s.kind === "todo").length,
|
|
2930
|
+
suppressions: w.ast.smells.filter((s) => s.kind === "suppression").length,
|
|
2931
|
+
swallowedCatches: w.ast.swallowedCatches,
|
|
2932
|
+
maxNesting: w.ast.maxNesting,
|
|
2933
|
+
longFunctions: w.ast.longFunctions,
|
|
2934
|
+
magicNumbers: w.ast.magicNumbers
|
|
2935
|
+
},
|
|
2936
|
+
sideEffectProfile: sideEffectsByFile.get(w.rel),
|
|
2937
|
+
importSpecs: w.importSpecs,
|
|
2938
|
+
source: w.source
|
|
2939
|
+
}))
|
|
2940
|
+
};
|
|
2941
|
+
const adapterStage = adapterRegistry.runStage(adapterCtx);
|
|
2942
|
+
const surfacePatterns = /* @__PURE__ */ new Map();
|
|
2943
|
+
for (const p of adapterStage.surfacePatterns) {
|
|
2944
|
+
surfacePatterns.set(p.domain, { expected: p.expected, wrong: p.wrong });
|
|
2945
|
+
}
|
|
2946
|
+
for (const w of work) {
|
|
2947
|
+
const generic = writeIntentsByFile.get(w.rel) ?? [];
|
|
2948
|
+
const fromAdapter = adapterStage.writeIntentsByFile.get(w.rel) ?? [];
|
|
2949
|
+
const merged = [.../* @__PURE__ */ new Set([...generic, ...fromAdapter])];
|
|
2950
|
+
writeIntentsByFile.set(w.rel, merged.length > 0 ? merged : ["none_detected"]);
|
|
2951
|
+
}
|
|
2952
|
+
const staticGravityByFile = new Map(gravityByFile);
|
|
2953
|
+
if (adapterStage.firedAdapterIds.length > 0) {
|
|
2954
|
+
console.error(`[vibe-splain] adapters fired: ${adapterStage.firedAdapterIds.join(", ")}`);
|
|
2955
|
+
for (const w of work) {
|
|
2956
|
+
const lift = adapterStage.liftByFile.get(w.rel) ?? 0;
|
|
2957
|
+
if (lift > 0) {
|
|
2958
|
+
const staticGravity = gravityByFile.get(w.rel);
|
|
2959
|
+
gravityByFile.set(w.rel, Math.max(staticGravity, Math.min(100, staticGravity + lift)));
|
|
2960
|
+
}
|
|
2961
|
+
}
|
|
2962
|
+
}
|
|
2119
2963
|
const classified = [];
|
|
2120
2964
|
for (const w of work) {
|
|
2121
2965
|
const real = isRealSource.get(w.rel);
|
|
@@ -2141,11 +2985,15 @@ async function runClassification(projectRoot, inv, res) {
|
|
|
2141
2985
|
const imports = [...importsResolved.get(w.rel) || /* @__PURE__ */ new Set()];
|
|
2142
2986
|
const importsUnresolvedArr = [...importsUnresolved.get(w.rel) || /* @__PURE__ */ new Set()];
|
|
2143
2987
|
const runtimeEntrypoints = findRuntimeEntrypoints(w.rel, importedBy, metaLookup);
|
|
2144
|
-
const
|
|
2988
|
+
const adapterCls = adapterStage.classificationByFile.get(w.rel);
|
|
2989
|
+
const effectiveDomain = adapterCls?.domainTags?.[0] ?? w.productDomain;
|
|
2990
|
+
const entrypointTraceStatus = deriveEntrypointTraceStatus(effectiveDomain, runtimeEntrypoints, importsUnresolvedArr, surfacePatterns);
|
|
2145
2991
|
const smellMaxSeverity = w.ast.smells.length > 0 ? Math.max(...w.ast.smells.map((s) => s.severity)) : 0;
|
|
2146
|
-
const loadBearingScore = computeLoadBearingScore(gravity, heat, fanIn, effects, w.productDomain, smellMaxSeverity, runtimeEntrypoints);
|
|
2992
|
+
const loadBearingScore = computeLoadBearingScore(gravity, heat, fanIn, effects, w.productDomain, smellMaxSeverity, runtimeEntrypoints, adapterStage.loadBearingBoostByFile.get(w.rel), adapterCls?.domainTags);
|
|
2147
2993
|
const isLoadBearing = fanIn >= 10;
|
|
2148
2994
|
const isOperationallyCritical = loadBearingScore >= 5;
|
|
2995
|
+
const staticGravity = staticGravityByFile.get(w.rel);
|
|
2996
|
+
const behavioralLift = adapterStage.liftByFile.get(w.rel) ?? 0;
|
|
2149
2997
|
classified.push({
|
|
2150
2998
|
rel: w.rel,
|
|
2151
2999
|
abs: w.abs,
|
|
@@ -2153,6 +3001,8 @@ async function runClassification(projectRoot, inv, res) {
|
|
|
2153
3001
|
isRealSource: real,
|
|
2154
3002
|
demoteReason: demoteReason.get(w.rel) || null,
|
|
2155
3003
|
gravity,
|
|
3004
|
+
staticGravity,
|
|
3005
|
+
behavioralLift,
|
|
2156
3006
|
heat,
|
|
2157
3007
|
gravitySignals: gs,
|
|
2158
3008
|
heatSignals: hs,
|
|
@@ -2173,17 +3023,22 @@ async function runClassification(projectRoot, inv, res) {
|
|
|
2173
3023
|
isOperationallyCritical,
|
|
2174
3024
|
isLoadBearing,
|
|
2175
3025
|
hotSpans: w.ast.hotSpans,
|
|
2176
|
-
source: w.source
|
|
3026
|
+
source: w.source,
|
|
3027
|
+
adapterDomain: adapterCls?.adapterDomain,
|
|
3028
|
+
domainTags: adapterCls?.domainTags,
|
|
3029
|
+
executionRole: adapterCls?.executionRole,
|
|
3030
|
+
adapterSideEffects: adapterStage.sideEffectsByFile.get(w.rel),
|
|
3031
|
+
adapterPillarLabel: adapterStage.pillarLabelsByFile.get(w.rel)
|
|
2177
3032
|
});
|
|
2178
3033
|
}
|
|
2179
|
-
const dir =
|
|
3034
|
+
const dir = join5(projectRoot, ".vibe-splainer");
|
|
2180
3035
|
await mkdir3(dir, { recursive: true });
|
|
2181
3036
|
const stage05 = Object.fromEntries(classified.map((f) => [f.rel, f.sideEffectProfile]));
|
|
2182
|
-
await writeFile4(
|
|
3037
|
+
await writeFile4(join5(dir, "stage-05-side-effects.json"), JSON.stringify(stage05, null, 2), "utf8");
|
|
2183
3038
|
const stage06 = Object.fromEntries(classified.map((f) => [f.rel, f.writeIntents]));
|
|
2184
|
-
await writeFile4(
|
|
3039
|
+
await writeFile4(join5(dir, "stage-06-write-intents.json"), JSON.stringify(stage06, null, 2), "utf8");
|
|
2185
3040
|
const stage07 = Object.fromEntries(classified.map((f) => [f.rel, f.riskTypes]));
|
|
2186
|
-
await writeFile4(
|
|
3041
|
+
await writeFile4(join5(dir, "stage-07-risk-types.json"), JSON.stringify(stage07, null, 2), "utf8");
|
|
2187
3042
|
const stage08 = Object.fromEntries(classified.map((f) => [f.rel, {
|
|
2188
3043
|
isLoadBearing: f.isLoadBearing,
|
|
2189
3044
|
isOperationallyCritical: f.isOperationallyCritical,
|
|
@@ -2191,7 +3046,7 @@ async function runClassification(projectRoot, inv, res) {
|
|
|
2191
3046
|
runtimeEntrypoints: f.runtimeEntrypoints.length,
|
|
2192
3047
|
entrypointTraceStatus: f.entrypointTraceStatus
|
|
2193
3048
|
}]));
|
|
2194
|
-
await writeFile4(
|
|
3049
|
+
await writeFile4(join5(dir, "stage-08-load-bearing.json"), JSON.stringify(stage08, null, 2), "utf8");
|
|
2195
3050
|
const realClassified = classified.filter((f) => f.isRealSource).sort((a, b) => b.gravity - a.gravity);
|
|
2196
3051
|
const wildCandidates = realClassified.filter((f) => f.heat >= 60 || f.smells.some((s) => s.severity >= 4));
|
|
2197
3052
|
const pillars = buildPillars(classified, communities);
|
|
@@ -2205,558 +3060,32 @@ async function runClassification(projectRoot, inv, res) {
|
|
|
2205
3060
|
topHeat: wildCandidates.slice(0, 12).map((f) => f.rel),
|
|
2206
3061
|
brief: null
|
|
2207
3062
|
};
|
|
2208
|
-
return { projectRoot, classified, stack: inv.stack, entrypoints, map, communities };
|
|
2209
|
-
}
|
|
2210
|
-
|
|
2211
|
-
// ../brain/dist/pipeline/binding.js
|
|
2212
|
-
import { join as join5 } from "path";
|
|
2213
|
-
import { writeFile as writeFile5, readFile as readFile4 } from "fs/promises";
|
|
2214
|
-
var FUNCTION_TYPES2 = /* @__PURE__ */ new Set([
|
|
2215
|
-
"function_declaration",
|
|
2216
|
-
"function_expression",
|
|
2217
|
-
"arrow_function",
|
|
2218
|
-
"method_definition",
|
|
2219
|
-
"function_definition",
|
|
2220
|
-
"method_declaration",
|
|
2221
|
-
"func_literal",
|
|
2222
|
-
"function_item",
|
|
2223
|
-
"closure_expression",
|
|
2224
|
-
"constructor_declaration",
|
|
2225
|
-
"generator_function_declaration",
|
|
2226
|
-
"generator_function"
|
|
2227
|
-
]);
|
|
2228
|
-
function firstLine2(s) {
|
|
2229
|
-
return s.split("\n")[0].trim();
|
|
2230
|
-
}
|
|
2231
|
-
function walkNodes(node, cb) {
|
|
2232
|
-
cb(node);
|
|
2233
|
-
for (let i = 0; i < node.childCount; i++) {
|
|
2234
|
-
const child = node.child(i);
|
|
2235
|
-
if (child)
|
|
2236
|
-
walkNodes(child, cb);
|
|
2237
|
-
}
|
|
2238
|
-
}
|
|
2239
|
-
function resolveCallee(node) {
|
|
2240
|
-
if (node.type === "identifier") {
|
|
2241
|
-
return { root: node.text, prop: null };
|
|
2242
|
-
}
|
|
2243
|
-
if (node.type === "member_expression") {
|
|
2244
|
-
const obj = node.childForFieldName("object");
|
|
2245
|
-
const prop = node.childForFieldName("property");
|
|
2246
|
-
if (obj && prop) {
|
|
2247
|
-
const nested = resolveCallee(obj);
|
|
2248
|
-
if (nested) {
|
|
2249
|
-
return { root: nested.root, prop: nested.prop ? `${nested.prop}.${prop.text}` : prop.text };
|
|
2250
|
-
}
|
|
2251
|
-
}
|
|
2252
|
-
}
|
|
2253
|
-
if (node.type === "parenthesized_expression" || node.type === "await_expression") {
|
|
2254
|
-
const inner = node.namedChildren.find((c) => c.type !== "(" && c.type !== ")" && c.type !== "await");
|
|
2255
|
-
if (inner)
|
|
2256
|
-
return resolveCallee(inner);
|
|
2257
|
-
}
|
|
2258
|
-
return null;
|
|
2259
|
-
}
|
|
2260
|
-
async function runActionBinding(projectRoot, inv, res) {
|
|
2261
|
-
const artifact = {
|
|
2262
|
-
schemaVersion: 1,
|
|
2263
|
-
projectRoot,
|
|
2264
|
-
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2265
|
-
files: {},
|
|
2266
|
-
functionIndex: {},
|
|
2267
|
-
actionIndex: {},
|
|
2268
|
-
entrypointIndex: {}
|
|
2269
|
-
};
|
|
2270
|
-
let filesProcessed = 0;
|
|
2271
|
-
let functionsExtracted = 0;
|
|
2272
|
-
let callsExtracted = 0;
|
|
2273
|
-
let callsResolved = 0;
|
|
2274
|
-
let semanticActionsExtracted = 0;
|
|
2275
|
-
let entrypointsFound = 0;
|
|
2276
|
-
let namedImportsExtracted = 0;
|
|
2277
|
-
for (const w of inv.work) {
|
|
2278
|
-
if (w.pathDemote)
|
|
2279
|
-
continue;
|
|
2280
|
-
filesProcessed++;
|
|
2281
|
-
const filePath = w.rel;
|
|
2282
|
-
const imports = [];
|
|
2283
|
-
for (const raw of w.rawNamedImports) {
|
|
2284
|
-
namedImportsExtracted++;
|
|
2285
|
-
const { resolved, isAlias } = resolveImportWithAliasMap(raw.moduleSpecifier, w.abs, w.lang, projectRoot, inv.fileSet, inv.basenameIndex, res.aliasMap);
|
|
2286
|
-
let confidence = "low";
|
|
2287
|
-
if (resolved)
|
|
2288
|
-
confidence = "high";
|
|
2289
|
-
else if (isAlias)
|
|
2290
|
-
confidence = "medium";
|
|
2291
|
-
imports.push({
|
|
2292
|
-
localName: raw.localName,
|
|
2293
|
-
importedName: raw.importedName,
|
|
2294
|
-
moduleSpecifier: raw.moduleSpecifier,
|
|
2295
|
-
resolvedFilePath: resolved,
|
|
2296
|
-
importKind: raw.importKind,
|
|
2297
|
-
isTypeOnly: raw.isTypeOnly,
|
|
2298
|
-
sourceLine: raw.sourceLine,
|
|
2299
|
-
confidence,
|
|
2300
|
-
evidenceText: raw.rawText.slice(0, 200)
|
|
2301
|
-
});
|
|
2302
|
-
}
|
|
2303
|
-
const functions = [];
|
|
2304
|
-
const nodeToRecord = /* @__PURE__ */ new Map();
|
|
2305
|
-
if (!w.tree)
|
|
2306
|
-
continue;
|
|
2307
|
-
const allNodes = [];
|
|
2308
|
-
walkNodes(w.tree.rootNode, (n) => {
|
|
2309
|
-
allNodes.push(n);
|
|
2310
|
-
});
|
|
2311
|
-
for (const node of allNodes) {
|
|
2312
|
-
if (!FUNCTION_TYPES2.has(node.type))
|
|
2313
|
-
continue;
|
|
2314
|
-
const startLine = node.startPosition.row + 1;
|
|
2315
|
-
const startCol = node.startPosition.column;
|
|
2316
|
-
const endLine = node.endPosition.row + 1;
|
|
2317
|
-
const endCol = node.endPosition.column;
|
|
2318
|
-
const isDuplicate = functions.some((f) => f.startLine === startLine && f.startCol === startCol && f.endLine === endLine && f.functionKind === node.type);
|
|
2319
|
-
if (isDuplicate)
|
|
2320
|
-
continue;
|
|
2321
|
-
functionsExtracted++;
|
|
2322
|
-
let displayName = "";
|
|
2323
|
-
let nameSource = "position_fallback";
|
|
2324
|
-
const p = node.parent;
|
|
2325
|
-
if (node.childForFieldName("name")) {
|
|
2326
|
-
displayName = node.childForFieldName("name").text;
|
|
2327
|
-
nameSource = node.type === "method_definition" ? "method_definition" : "function_declaration";
|
|
2328
|
-
} else if (p?.type === "variable_declarator" && p.childForFieldName("name")) {
|
|
2329
|
-
displayName = p.childForFieldName("name").text;
|
|
2330
|
-
nameSource = "parent_variable_declarator";
|
|
2331
|
-
} else if (p?.type === "assignment_expression" && p.childForFieldName("left")) {
|
|
2332
|
-
displayName = p.childForFieldName("left").text;
|
|
2333
|
-
nameSource = "parent_assignment";
|
|
2334
|
-
} else if (p?.type === "pair" && p.childForFieldName("key")) {
|
|
2335
|
-
displayName = p.childForFieldName("key").text;
|
|
2336
|
-
nameSource = "object_property_key";
|
|
2337
|
-
} else if (p?.type === "export_statement") {
|
|
2338
|
-
const idNode = p.children.find((c) => c.type === "identifier");
|
|
2339
|
-
if (idNode) {
|
|
2340
|
-
displayName = idNode.text;
|
|
2341
|
-
nameSource = "export_const";
|
|
2342
|
-
}
|
|
2343
|
-
} else if (p?.type === "lexical_declaration" || p?.type === "variable_declaration") {
|
|
2344
|
-
const decl = p.children.find((c) => c.type === "variable_declarator");
|
|
2345
|
-
if (decl && decl.childForFieldName("name")) {
|
|
2346
|
-
displayName = decl.childForFieldName("name").text;
|
|
2347
|
-
nameSource = "parent_variable_declarator";
|
|
2348
|
-
}
|
|
2349
|
-
}
|
|
2350
|
-
if (!displayName) {
|
|
2351
|
-
displayName = `anonymous@${startLine}:${startCol}`;
|
|
2352
|
-
nameSource = "position_fallback";
|
|
2353
|
-
}
|
|
2354
|
-
const functionId = `${filePath}::${displayName}::${startLine}:${startCol}`;
|
|
2355
|
-
let isExported = false;
|
|
2356
|
-
if (p?.type === "export_statement")
|
|
2357
|
-
isExported = true;
|
|
2358
|
-
if (p?.parent?.type === "export_statement")
|
|
2359
|
-
isExported = true;
|
|
2360
|
-
if (w.ast.exportedNames.includes(displayName))
|
|
2361
|
-
isExported = true;
|
|
2362
|
-
const isEntrypoint = (w.frameworkRole.includes("route") || w.frameworkRole.includes("page")) && isExported;
|
|
2363
|
-
if (isEntrypoint)
|
|
2364
|
-
entrypointsFound++;
|
|
2365
|
-
const fnRecord = {
|
|
2366
|
-
functionId,
|
|
2367
|
-
displayName,
|
|
2368
|
-
nameSource,
|
|
2369
|
-
functionKind: node.type,
|
|
2370
|
-
filePath,
|
|
2371
|
-
startLine,
|
|
2372
|
-
endLine: node.endPosition.row + 1,
|
|
2373
|
-
startCol,
|
|
2374
|
-
isExported,
|
|
2375
|
-
isEntrypoint,
|
|
2376
|
-
calls: [],
|
|
2377
|
-
semanticActions: [],
|
|
2378
|
-
evidenceText: firstLine2(node.text).slice(0, 200)
|
|
2379
|
-
};
|
|
2380
|
-
functions.push(fnRecord);
|
|
2381
|
-
nodeToRecord.set(node.id, fnRecord);
|
|
2382
|
-
}
|
|
2383
|
-
for (const node of allNodes) {
|
|
2384
|
-
if (node.type !== "call_expression" && node.type !== "call")
|
|
2385
|
-
continue;
|
|
2386
|
-
let curr = node.parent;
|
|
2387
|
-
let containingFnRecord = null;
|
|
2388
|
-
while (curr) {
|
|
2389
|
-
const rec = nodeToRecord.get(curr.id);
|
|
2390
|
-
if (rec) {
|
|
2391
|
-
containingFnRecord = rec;
|
|
2392
|
-
break;
|
|
2393
|
-
}
|
|
2394
|
-
curr = curr.parent;
|
|
2395
|
-
}
|
|
2396
|
-
if (!containingFnRecord)
|
|
2397
|
-
continue;
|
|
2398
|
-
callsExtracted++;
|
|
2399
|
-
const calleeNode = node.childForFieldName("function") || node.namedChild(0);
|
|
2400
|
-
if (!calleeNode)
|
|
2401
|
-
continue;
|
|
2402
|
-
const resolvedCallee = resolveCallee(calleeNode);
|
|
2403
|
-
if (!resolvedCallee)
|
|
2404
|
-
continue;
|
|
2405
|
-
const { root: calleeRoot, prop: calleeProperty } = resolvedCallee;
|
|
2406
|
-
const calleeText = calleeNode.text.slice(0, 100);
|
|
2407
|
-
const sourceLine = node.startPosition.row + 1;
|
|
2408
|
-
let isSemantic = false;
|
|
2409
|
-
let actionKind = null;
|
|
2410
|
-
let targetModel = null;
|
|
2411
|
-
let targetOperation = null;
|
|
2412
|
-
if (calleeRoot === "prisma" && calleeProperty) {
|
|
2413
|
-
if (/\.(create|update|upsert|delete|deleteMany|updateMany|createMany|executeRaw|queryRaw)$/.test("." + calleeProperty)) {
|
|
2414
|
-
isSemantic = true;
|
|
2415
|
-
actionKind = "database_write";
|
|
2416
|
-
const parts = calleeProperty.split(".");
|
|
2417
|
-
if (parts.length >= 2) {
|
|
2418
|
-
targetModel = parts[0].charAt(0).toUpperCase() + parts[0].slice(1);
|
|
2419
|
-
targetOperation = parts[parts.length - 1];
|
|
2420
|
-
}
|
|
2421
|
-
} else if (/\.(findMany|findUnique|findFirst|findFirstOrThrow|findUniqueOrThrow|count|aggregate|groupBy)$/.test("." + calleeProperty)) {
|
|
2422
|
-
isSemantic = true;
|
|
2423
|
-
actionKind = "database_read";
|
|
2424
|
-
const parts = calleeProperty.split(".");
|
|
2425
|
-
if (parts.length >= 2) {
|
|
2426
|
-
targetModel = parts[0].charAt(0).toUpperCase() + parts[0].slice(1);
|
|
2427
|
-
targetOperation = parts[parts.length - 1];
|
|
2428
|
-
}
|
|
2429
|
-
}
|
|
2430
|
-
} else if (calleeRoot === "trpc" && calleeProperty && /\.(useMutation|mutate|mutateAsync)$/.test("." + calleeProperty)) {
|
|
2431
|
-
isSemantic = true;
|
|
2432
|
-
actionKind = "external_api_call";
|
|
2433
|
-
const parts = calleeProperty.split(".");
|
|
2434
|
-
if (parts.length >= 2) {
|
|
2435
|
-
targetModel = parts[0];
|
|
2436
|
-
targetOperation = parts[parts.length - 1];
|
|
2437
|
-
}
|
|
2438
|
-
}
|
|
2439
|
-
if (!isSemantic) {
|
|
2440
|
-
if (calleeRoot === "fetch" || calleeRoot === "axios" && calleeProperty && /\.(get|post|put|patch|delete)$/.test("." + calleeProperty)) {
|
|
2441
|
-
isSemantic = true;
|
|
2442
|
-
actionKind = "external_api_call";
|
|
2443
|
-
} else if (/validate|schema\.parse|schema\.safeParse|z\.parse/i.test(calleeText) || /validate|validator/i.test(calleeRoot)) {
|
|
2444
|
-
isSemantic = true;
|
|
2445
|
-
actionKind = "validation";
|
|
2446
|
-
} else if (/getSession|getServerSession|auth\(\)|verifyToken|requireAuth|checkPermission/i.test(calleeText) || /auth|session/i.test(calleeRoot) && calleeProperty && /check|verify|get|require/i.test("." + calleeProperty)) {
|
|
2447
|
-
isSemantic = true;
|
|
2448
|
-
actionKind = "auth_check";
|
|
2449
|
-
} else if (/sendEmail|sendMail|mailer\./i.test(calleeText)) {
|
|
2450
|
-
isSemantic = true;
|
|
2451
|
-
actionKind = "email_send";
|
|
2452
|
-
} else if (/createCalendarEvent|updateCalendarEvent|deleteCalendarEvent|calendar\.events\.(insert|update|delete)/i.test(calleeText)) {
|
|
2453
|
-
isSemantic = true;
|
|
2454
|
-
actionKind = "calendar_mutation";
|
|
2455
|
-
} else if (/triggerWebhook|sendWebhook|webhook\.send/i.test(calleeText)) {
|
|
2456
|
-
isSemantic = true;
|
|
2457
|
-
actionKind = "webhook_delivery";
|
|
2458
|
-
} else if (/stripe\.webhooks\.constructEvent|validateWebhook|verifySignature/i.test(calleeText)) {
|
|
2459
|
-
isSemantic = true;
|
|
2460
|
-
actionKind = "webhook_ingress";
|
|
2461
|
-
} else if (/revalidatePath|revalidateTag/i.test(calleeText)) {
|
|
2462
|
-
isSemantic = true;
|
|
2463
|
-
actionKind = "cache_revalidation";
|
|
2464
|
-
} else if (/posthog\.|mixpanel\.|amplitude\.|ga\(/i.test(calleeText)) {
|
|
2465
|
-
isSemantic = true;
|
|
2466
|
-
actionKind = "analytics_event";
|
|
2467
|
-
} else if (calleeRoot === "redirect" || /router|redirect|notFound|permanentRedirect/.test(calleeRoot) && calleeProperty && /push|replace|back/i.test("." + calleeProperty)) {
|
|
2468
|
-
isSemantic = true;
|
|
2469
|
-
actionKind = "redirect";
|
|
2470
|
-
} else if (/cookies\(\)|headers\(\)/.test(calleeText) || calleeRoot === "cookies" || calleeRoot === "headers") {
|
|
2471
|
-
isSemantic = true;
|
|
2472
|
-
actionKind = "side_effect";
|
|
2473
|
-
} else if (/checkRateLimitAndThrowError/i.test(calleeText)) {
|
|
2474
|
-
isSemantic = true;
|
|
2475
|
-
actionKind = "auth_check";
|
|
2476
|
-
} else {
|
|
2477
|
-
const emailImport = imports.find((i) => i.localName === calleeRoot && /nodemailer|resend|sendgrid|postmark|mailgun/i.test(i.moduleSpecifier));
|
|
2478
|
-
if (emailImport) {
|
|
2479
|
-
isSemantic = true;
|
|
2480
|
-
actionKind = "email_send";
|
|
2481
|
-
}
|
|
2482
|
-
}
|
|
2483
|
-
}
|
|
2484
|
-
if (isSemantic && actionKind) {
|
|
2485
|
-
semanticActionsExtracted++;
|
|
2486
|
-
const actionId = `${containingFnRecord.functionId}::${actionKind}::${sourceLine}`;
|
|
2487
|
-
containingFnRecord.semanticActions.push({
|
|
2488
|
-
actionId,
|
|
2489
|
-
sourceFunctionId: containingFnRecord.functionId,
|
|
2490
|
-
actionKind,
|
|
2491
|
-
targetModel,
|
|
2492
|
-
targetOperation,
|
|
2493
|
-
calleeText,
|
|
2494
|
-
sourceLine,
|
|
2495
|
-
confidence: "high",
|
|
2496
|
-
evidenceText: firstLine2(node.text).slice(0, 200)
|
|
2497
|
-
});
|
|
2498
|
-
if (!artifact.actionIndex[actionKind])
|
|
2499
|
-
artifact.actionIndex[actionKind] = [];
|
|
2500
|
-
artifact.actionIndex[actionKind].push(containingFnRecord.functionId);
|
|
2501
|
-
if (targetModel) {
|
|
2502
|
-
const key1 = `${actionKind}::${targetModel}`;
|
|
2503
|
-
if (!artifact.actionIndex[key1])
|
|
2504
|
-
artifact.actionIndex[key1] = [];
|
|
2505
|
-
artifact.actionIndex[key1].push(containingFnRecord.functionId);
|
|
2506
|
-
if (targetOperation) {
|
|
2507
|
-
const key2 = `${actionKind}::${targetModel}::${targetOperation}`;
|
|
2508
|
-
if (!artifact.actionIndex[key2])
|
|
2509
|
-
artifact.actionIndex[key2] = [];
|
|
2510
|
-
artifact.actionIndex[key2].push(containingFnRecord.functionId);
|
|
2511
|
-
}
|
|
2512
|
-
}
|
|
2513
|
-
} else {
|
|
2514
|
-
let resolvedTargetFunctionId = null;
|
|
2515
|
-
let resolvedFilePath = null;
|
|
2516
|
-
let resolutionKind = "unresolved";
|
|
2517
|
-
let confidence = "unresolved";
|
|
2518
|
-
const sameFileFn = functions.find((f) => f.displayName === calleeRoot);
|
|
2519
|
-
if (sameFileFn) {
|
|
2520
|
-
resolvedTargetFunctionId = sameFileFn.functionId;
|
|
2521
|
-
resolutionKind = "same_file_function";
|
|
2522
|
-
confidence = "high";
|
|
2523
|
-
} else {
|
|
2524
|
-
const namedImp = imports.find((i) => i.localName === calleeRoot && i.importKind !== "namespace" && !i.isTypeOnly);
|
|
2525
|
-
if (namedImp && namedImp.resolvedFilePath) {
|
|
2526
|
-
resolvedFilePath = namedImp.resolvedFilePath;
|
|
2527
|
-
resolutionKind = "named_import_match";
|
|
2528
|
-
confidence = "high";
|
|
2529
|
-
} else {
|
|
2530
|
-
const nsImp = imports.find((i) => i.localName === calleeRoot && i.importKind === "namespace");
|
|
2531
|
-
if (nsImp && nsImp.resolvedFilePath) {
|
|
2532
|
-
resolvedFilePath = nsImp.resolvedFilePath;
|
|
2533
|
-
resolutionKind = "namespace_import_property";
|
|
2534
|
-
confidence = "medium";
|
|
2535
|
-
}
|
|
2536
|
-
}
|
|
2537
|
-
}
|
|
2538
|
-
if (resolutionKind !== "unresolved")
|
|
2539
|
-
callsResolved++;
|
|
2540
|
-
containingFnRecord.calls.push({
|
|
2541
|
-
callId: `${containingFnRecord.functionId}::${calleeText}::${sourceLine}`,
|
|
2542
|
-
sourceFunctionId: containingFnRecord.functionId,
|
|
2543
|
-
calleeText,
|
|
2544
|
-
calleeRoot,
|
|
2545
|
-
calleeProperty,
|
|
2546
|
-
sourceLine,
|
|
2547
|
-
sourceSpan: { startLine: node.startPosition.row + 1, endLine: node.endPosition.row + 1 },
|
|
2548
|
-
resolvedTargetFunctionId,
|
|
2549
|
-
resolvedFilePath,
|
|
2550
|
-
resolutionKind,
|
|
2551
|
-
confidence,
|
|
2552
|
-
evidenceText: firstLine2(node.text).slice(0, 200)
|
|
2553
|
-
});
|
|
2554
|
-
}
|
|
2555
|
-
}
|
|
2556
|
-
artifact.files[filePath] = {
|
|
2557
|
-
filePath,
|
|
2558
|
-
language: w.lang,
|
|
2559
|
-
sourceRole: w.frameworkRole === "test" ? "test" : "production",
|
|
2560
|
-
imports,
|
|
2561
|
-
functions
|
|
2562
|
-
};
|
|
2563
|
-
for (const fn of functions) {
|
|
2564
|
-
artifact.functionIndex[fn.functionId] = {
|
|
2565
|
-
filePath,
|
|
2566
|
-
displayName: fn.displayName,
|
|
2567
|
-
startLine: fn.startLine,
|
|
2568
|
-
endLine: fn.endLine
|
|
2569
|
-
};
|
|
2570
|
-
if (fn.isEntrypoint) {
|
|
2571
|
-
if (!artifact.entrypointIndex[filePath])
|
|
2572
|
-
artifact.entrypointIndex[filePath] = [];
|
|
2573
|
-
artifact.entrypointIndex[filePath].push(fn.functionId);
|
|
2574
|
-
}
|
|
2575
|
-
}
|
|
2576
|
-
}
|
|
2577
|
-
for (const fileRec of Object.values(artifact.files)) {
|
|
2578
|
-
for (const fnRec of fileRec.functions) {
|
|
2579
|
-
for (const callRec of fnRec.calls) {
|
|
2580
|
-
if (callRec.resolvedTargetFunctionId)
|
|
2581
|
-
continue;
|
|
2582
|
-
if (!callRec.resolvedFilePath)
|
|
2583
|
-
continue;
|
|
2584
|
-
const targetFile = artifact.files[callRec.resolvedFilePath];
|
|
2585
|
-
if (!targetFile)
|
|
2586
|
-
continue;
|
|
2587
|
-
if (callRec.resolutionKind === "named_import_match") {
|
|
2588
|
-
const targetFn = targetFile.functions.find((f) => f.displayName === callRec.calleeRoot && f.isExported);
|
|
2589
|
-
if (targetFn) {
|
|
2590
|
-
callRec.resolvedTargetFunctionId = targetFn.functionId;
|
|
2591
|
-
callRec.confidence = "high";
|
|
2592
|
-
}
|
|
2593
|
-
} else if (callRec.resolutionKind === "namespace_import_property" && callRec.calleeProperty) {
|
|
2594
|
-
const targetFn = targetFile.functions.find((f) => f.displayName === callRec.calleeProperty && f.isExported);
|
|
2595
|
-
if (targetFn) {
|
|
2596
|
-
callRec.resolvedTargetFunctionId = targetFn.functionId;
|
|
2597
|
-
callRec.confidence = "high";
|
|
2598
|
-
}
|
|
2599
|
-
} else if (callRec.resolutionKind === "namespace_import_property" && !callRec.calleeProperty) {
|
|
2600
|
-
const defaultFn = targetFile.functions.find((f) => f.displayName === "default");
|
|
2601
|
-
if (defaultFn) {
|
|
2602
|
-
callRec.resolvedTargetFunctionId = defaultFn.functionId;
|
|
2603
|
-
callRec.confidence = "high";
|
|
2604
|
-
}
|
|
2605
|
-
}
|
|
2606
|
-
}
|
|
2607
|
-
}
|
|
2608
|
-
}
|
|
2609
|
-
await writeFile5(join5(projectRoot, ".vibe-splainer", "action_bindings.json"), JSON.stringify(artifact, null, 2), "utf8");
|
|
2610
|
-
const summary = {
|
|
2611
|
-
filesProcessed,
|
|
2612
|
-
functionsExtracted,
|
|
2613
|
-
callsExtracted,
|
|
2614
|
-
callsResolved,
|
|
2615
|
-
semanticActionsExtracted,
|
|
2616
|
-
entrypointsFound,
|
|
2617
|
-
namedImportsExtracted
|
|
2618
|
-
};
|
|
2619
|
-
await writeFile5(join5(projectRoot, ".vibe-splainer", "stage-09-action-bindings-summary.json"), JSON.stringify(summary, null, 2), "utf8");
|
|
2620
|
-
return { artifact };
|
|
2621
|
-
}
|
|
2622
|
-
async function traverseCallChain(projectRoot, args) {
|
|
2623
|
-
const artifactPath = join5(projectRoot, ".vibe-splainer", "action_bindings.json");
|
|
2624
|
-
let artifact;
|
|
2625
|
-
try {
|
|
2626
|
-
const raw = await readFile4(artifactPath, "utf8");
|
|
2627
|
-
artifact = JSON.parse(raw);
|
|
2628
|
-
} catch {
|
|
2629
|
-
throw new Error("action_bindings.json not found. Run scan_project first.");
|
|
2630
|
-
}
|
|
2631
|
-
const { entrypointPath, maxDepth = 6, targetActionKind, targetModel, targetOperation, targetFunctionName, includeTests = false } = args;
|
|
2632
|
-
let seedFunctionIds = [];
|
|
2633
|
-
if (artifact.entrypointIndex[entrypointPath]) {
|
|
2634
|
-
seedFunctionIds = artifact.entrypointIndex[entrypointPath];
|
|
2635
|
-
} else if (artifact.files[entrypointPath]) {
|
|
2636
|
-
const fileRec = artifact.files[entrypointPath];
|
|
2637
|
-
seedFunctionIds = fileRec.functions.filter((f) => f.isEntrypoint).map((f) => f.functionId);
|
|
2638
|
-
if (seedFunctionIds.length === 0) {
|
|
2639
|
-
const firstExported = fileRec.functions.find((f) => f.isExported);
|
|
2640
|
-
if (firstExported)
|
|
2641
|
-
seedFunctionIds.push(firstExported.functionId);
|
|
2642
|
-
}
|
|
2643
|
-
}
|
|
2644
|
-
if (seedFunctionIds.length === 0) {
|
|
2645
|
-
throw new Error(`No entrypoint functions found in ${entrypointPath}`);
|
|
2646
|
-
}
|
|
2647
|
-
const chain = [];
|
|
2648
|
-
const unresolvedEdges = [];
|
|
2649
|
-
const visited = /* @__PURE__ */ new Set();
|
|
2650
|
-
const queue = seedFunctionIds.map((id) => ({ functionId: id, callerFunctionId: null, depth: 0 }));
|
|
2651
|
-
let targetReached = false;
|
|
2652
|
-
let truncatedAtDepth = false;
|
|
2653
|
-
while (queue.length > 0) {
|
|
2654
|
-
const { functionId, callerFunctionId, depth, callsite } = queue.shift();
|
|
2655
|
-
const visitKey = `${callerFunctionId}->${functionId}`;
|
|
2656
|
-
if (visited.has(visitKey))
|
|
2657
|
-
continue;
|
|
2658
|
-
visited.add(visitKey);
|
|
2659
|
-
const indexEntry = artifact.functionIndex[functionId];
|
|
2660
|
-
if (!indexEntry)
|
|
2661
|
-
continue;
|
|
2662
|
-
const fileRec = artifact.files[indexEntry.filePath];
|
|
2663
|
-
if (!fileRec)
|
|
2664
|
-
continue;
|
|
2665
|
-
if (!includeTests && fileRec.sourceRole === "test")
|
|
2666
|
-
continue;
|
|
2667
|
-
const fnRec = fileRec.functions.find((f) => f.functionId === functionId);
|
|
2668
|
-
if (!fnRec)
|
|
2669
|
-
continue;
|
|
2670
|
-
let isTarget = false;
|
|
2671
|
-
if (targetFunctionName && fnRec.displayName === targetFunctionName)
|
|
2672
|
-
isTarget = true;
|
|
2673
|
-
if (isTarget)
|
|
2674
|
-
targetReached = true;
|
|
2675
|
-
chain.push({
|
|
2676
|
-
functionId,
|
|
2677
|
-
callerFunctionId,
|
|
2678
|
-
displayName: fnRec.displayName,
|
|
2679
|
-
filePath: fnRec.filePath,
|
|
2680
|
-
startLine: fnRec.startLine,
|
|
2681
|
-
edgeKind: "call_edge",
|
|
2682
|
-
confidence: "high",
|
|
2683
|
-
evidenceText: fnRec.evidenceText,
|
|
2684
|
-
isTarget,
|
|
2685
|
-
depth,
|
|
2686
|
-
callsite
|
|
2687
|
-
});
|
|
2688
|
-
for (const action of fnRec.semanticActions) {
|
|
2689
|
-
let isActionTarget = false;
|
|
2690
|
-
if (targetActionKind && action.actionKind === targetActionKind) {
|
|
2691
|
-
isActionTarget = true;
|
|
2692
|
-
if (targetModel && action.targetModel !== targetModel)
|
|
2693
|
-
isActionTarget = false;
|
|
2694
|
-
if (targetOperation && action.targetOperation !== targetOperation)
|
|
2695
|
-
isActionTarget = false;
|
|
2696
|
-
} else if (targetModel && action.targetModel === targetModel) {
|
|
2697
|
-
isActionTarget = true;
|
|
2698
|
-
if (targetOperation && action.targetOperation !== targetOperation)
|
|
2699
|
-
isActionTarget = false;
|
|
2700
|
-
}
|
|
2701
|
-
if (isActionTarget)
|
|
2702
|
-
targetReached = true;
|
|
2703
|
-
chain.push({
|
|
2704
|
-
functionId: action.actionId,
|
|
2705
|
-
callerFunctionId: functionId,
|
|
2706
|
-
displayName: action.calleeText,
|
|
2707
|
-
filePath: fileRec.filePath,
|
|
2708
|
-
startLine: action.sourceLine,
|
|
2709
|
-
edgeKind: "semantic_action",
|
|
2710
|
-
actionKind: action.actionKind,
|
|
2711
|
-
targetModel: action.targetModel || void 0,
|
|
2712
|
-
targetOperation: action.targetOperation || void 0,
|
|
2713
|
-
confidence: action.confidence,
|
|
2714
|
-
evidenceText: action.evidenceText,
|
|
2715
|
-
isTarget: isActionTarget,
|
|
2716
|
-
depth: depth + 1
|
|
2717
|
-
});
|
|
2718
|
-
}
|
|
2719
|
-
if (depth < maxDepth) {
|
|
2720
|
-
for (const call of fnRec.calls) {
|
|
2721
|
-
if (call.resolvedTargetFunctionId) {
|
|
2722
|
-
queue.push({
|
|
2723
|
-
functionId: call.resolvedTargetFunctionId,
|
|
2724
|
-
callerFunctionId: functionId,
|
|
2725
|
-
depth: depth + 1,
|
|
2726
|
-
callsite: {
|
|
2727
|
-
file: fileRec.filePath,
|
|
2728
|
-
line: call.sourceLine,
|
|
2729
|
-
text: call.calleeText
|
|
2730
|
-
}
|
|
2731
|
-
});
|
|
2732
|
-
} else {
|
|
2733
|
-
unresolvedEdges.push({
|
|
2734
|
-
fromFunctionId: functionId,
|
|
2735
|
-
calleeText: call.calleeText,
|
|
2736
|
-
sourceLine: call.sourceLine,
|
|
2737
|
-
reason: call.resolutionKind
|
|
2738
|
-
});
|
|
2739
|
-
}
|
|
2740
|
-
}
|
|
2741
|
-
} else if (fnRec.calls.length > 0) {
|
|
2742
|
-
truncatedAtDepth = true;
|
|
2743
|
-
}
|
|
2744
|
-
}
|
|
2745
|
-
return {
|
|
2746
|
-
targetReached,
|
|
2747
|
-
truncatedAtDepth,
|
|
2748
|
-
chain,
|
|
2749
|
-
unresolvedEdges
|
|
2750
|
-
};
|
|
3063
|
+
return { projectRoot, classified, stack: inv.stack, entrypoints, map, communities, adapterStage };
|
|
2751
3064
|
}
|
|
2752
3065
|
|
|
2753
3066
|
// ../brain/dist/pipeline/scoring.js
|
|
2754
3067
|
import { join as join6 } from "path";
|
|
2755
|
-
import { mkdir as mkdir4
|
|
2756
|
-
function
|
|
3068
|
+
import { mkdir as mkdir4 } from "fs/promises";
|
|
3069
|
+
function genericSeverityScore(sideEffectProfile, gravity, heat, maxNesting, hasLongFunctions, swallowedCatches, runtimeEntrypoints) {
|
|
2757
3070
|
let score = 0;
|
|
2758
3071
|
if (sideEffectProfile.includes("database_write"))
|
|
2759
3072
|
score += 3;
|
|
3073
|
+
if (gravity >= 85)
|
|
3074
|
+
score += 2;
|
|
3075
|
+
if (heat >= 70)
|
|
3076
|
+
score += 2;
|
|
3077
|
+
if (maxNesting >= 4)
|
|
3078
|
+
score += 1;
|
|
3079
|
+
if (hasLongFunctions)
|
|
3080
|
+
score += 1;
|
|
3081
|
+
if (swallowedCatches >= 1)
|
|
3082
|
+
score += 1;
|
|
3083
|
+
if (runtimeEntrypoints.length >= 2)
|
|
3084
|
+
score += 2;
|
|
3085
|
+
return score;
|
|
3086
|
+
}
|
|
3087
|
+
function calcomDomainSeverityScore(sideEffectProfile, productDomain) {
|
|
3088
|
+
let score = 0;
|
|
2760
3089
|
if (sideEffectProfile.includes("booking_mutation"))
|
|
2761
3090
|
score += 4;
|
|
2762
3091
|
if (sideEffectProfile.includes("payment_mutation"))
|
|
@@ -2777,18 +3106,12 @@ function computeSeverity(sideEffectProfile, productDomain, gravity, heat, maxNes
|
|
|
2777
3106
|
score += 3;
|
|
2778
3107
|
if (productDomain === "webhooks")
|
|
2779
3108
|
score += 2;
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
if (hasLongFunctions)
|
|
2787
|
-
score += 1;
|
|
2788
|
-
if (swallowedCatches >= 1)
|
|
2789
|
-
score += 1;
|
|
2790
|
-
if (runtimeEntrypoints.length >= 2)
|
|
2791
|
-
score += 2;
|
|
3109
|
+
return score;
|
|
3110
|
+
}
|
|
3111
|
+
function computeSeverity(sideEffectProfile, productDomain, gravity, heat, maxNesting, hasLongFunctions, swallowedCatches, runtimeEntrypoints, adapterSeverityContribution, adapterSideEffects) {
|
|
3112
|
+
const effs = Array.from(/* @__PURE__ */ new Set([...sideEffectProfile, ...adapterSideEffects || []]));
|
|
3113
|
+
const domainScore = adapterSeverityContribution ?? calcomDomainSeverityScore(effs, productDomain);
|
|
3114
|
+
const score = genericSeverityScore(effs, gravity, heat, maxNesting, hasLongFunctions, swallowedCatches, runtimeEntrypoints) + domainScore;
|
|
2792
3115
|
if (score >= 10)
|
|
2793
3116
|
return 5;
|
|
2794
3117
|
if (score >= 7)
|
|
@@ -2801,13 +3124,16 @@ function computeSeverity(sideEffectProfile, productDomain, gravity, heat, maxNes
|
|
|
2801
3124
|
}
|
|
2802
3125
|
function applyCorrections(file) {
|
|
2803
3126
|
if (file.writeIntents.includes("handle_payment_webhook")) {
|
|
2804
|
-
if (!file.
|
|
2805
|
-
file.
|
|
2806
|
-
if (!file.
|
|
2807
|
-
file.
|
|
3127
|
+
if (!file.adapterSideEffects)
|
|
3128
|
+
file.adapterSideEffects = [];
|
|
3129
|
+
if (!file.adapterSideEffects.includes("payment_mutation"))
|
|
3130
|
+
file.adapterSideEffects.push("payment_mutation");
|
|
3131
|
+
if (!file.adapterSideEffects.includes("webhook_ingress"))
|
|
3132
|
+
file.adapterSideEffects.push("webhook_ingress");
|
|
2808
3133
|
file.sideEffectProfile = file.sideEffectProfile.filter((s) => s !== "none_detected");
|
|
2809
3134
|
}
|
|
2810
|
-
|
|
3135
|
+
const adapterEffects = file.adapterSideEffects || [];
|
|
3136
|
+
if (adapterEffects.includes("payment_mutation") || adapterEffects.includes("booking_mutation")) {
|
|
2811
3137
|
if (file.canonicalSeverity < 4)
|
|
2812
3138
|
file.canonicalSeverity = 4;
|
|
2813
3139
|
}
|
|
@@ -2826,21 +3152,28 @@ function deriveConfidence(fanIn, gravity) {
|
|
|
2826
3152
|
return "medium";
|
|
2827
3153
|
return "low";
|
|
2828
3154
|
}
|
|
2829
|
-
async function runScoring(projectRoot, cr
|
|
3155
|
+
async function runScoring(projectRoot, cr) {
|
|
2830
3156
|
const dir = join6(projectRoot, ".vibe-splainer");
|
|
2831
3157
|
await mkdir4(dir, { recursive: true });
|
|
2832
|
-
let bindingArtifact = binding?.artifact;
|
|
2833
|
-
if (!bindingArtifact) {
|
|
2834
|
-
try {
|
|
2835
|
-
const raw = await readFile5(join6(projectRoot, ".vibe-splainer", "action_bindings.json"), "utf8");
|
|
2836
|
-
bindingArtifact = JSON.parse(raw);
|
|
2837
|
-
} catch {
|
|
2838
|
-
}
|
|
2839
|
-
}
|
|
2840
3158
|
const persisted = {};
|
|
2841
3159
|
const severityBreakdowns = {};
|
|
3160
|
+
const adapterFired = cr.adapterStage.firedAdapterIds.length > 0;
|
|
3161
|
+
let sevBridgeChecked = 0;
|
|
3162
|
+
let sevBridgeMismatch = 0;
|
|
2842
3163
|
for (const f of cr.classified) {
|
|
2843
|
-
const
|
|
3164
|
+
const adapterDomainScore = cr.adapterStage.severityBoostByFile.get(f.rel);
|
|
3165
|
+
const effectiveDomain = f.domainTags?.[0] ?? f.adapterDomain ?? f.productDomain;
|
|
3166
|
+
const severity = computeSeverity(f.sideEffectProfile, effectiveDomain, f.gravity, f.heat, f.heatSignals.maxNesting, f.heatSignals.longFunctions > 0, f.heatSignals.swallowedCatches, f.runtimeEntrypoints, adapterDomainScore, f.adapterSideEffects);
|
|
3167
|
+
if (adapterFired) {
|
|
3168
|
+
const effs = [...f.sideEffectProfile, ...f.adapterSideEffects || []];
|
|
3169
|
+
const coreDomainScore = calcomDomainSeverityScore(effs, f.productDomain);
|
|
3170
|
+
const adapterScore = adapterDomainScore ?? 0;
|
|
3171
|
+
if (coreDomainScore > 0 || adapterScore > 0) {
|
|
3172
|
+
sevBridgeChecked++;
|
|
3173
|
+
if (coreDomainScore !== adapterScore)
|
|
3174
|
+
sevBridgeMismatch++;
|
|
3175
|
+
}
|
|
3176
|
+
}
|
|
2844
3177
|
const confidence = deriveConfidence(f.gravitySignals.fanIn, f.gravity);
|
|
2845
3178
|
const pf = {
|
|
2846
3179
|
relativePath: f.rel,
|
|
@@ -2848,6 +3181,8 @@ async function runScoring(projectRoot, cr, binding) {
|
|
|
2848
3181
|
isRealSource: f.isRealSource,
|
|
2849
3182
|
demoteReason: f.demoteReason,
|
|
2850
3183
|
gravity: Math.round(f.gravity),
|
|
3184
|
+
staticGravity: Math.round(f.staticGravity),
|
|
3185
|
+
behavioralLift: Math.round(f.behavioralLift),
|
|
2851
3186
|
heat: Math.round(f.heat),
|
|
2852
3187
|
gravitySignals: f.gravitySignals,
|
|
2853
3188
|
heatSignals: f.heatSignals,
|
|
@@ -2869,22 +3204,28 @@ async function runScoring(projectRoot, cr, binding) {
|
|
|
2869
3204
|
// STRICT: fanIn >= 10
|
|
2870
3205
|
isOperationallyCritical: f.isOperationallyCritical,
|
|
2871
3206
|
confidence,
|
|
2872
|
-
source: f.source
|
|
3207
|
+
source: f.source,
|
|
3208
|
+
adapterDomain: f.adapterDomain,
|
|
3209
|
+
domainTags: f.domainTags,
|
|
3210
|
+
executionRole: f.executionRole,
|
|
3211
|
+
adapterSideEffects: f.adapterSideEffects,
|
|
3212
|
+
adapterSeverityContribution: adapterDomainScore,
|
|
3213
|
+
// parallel; not applied yet
|
|
3214
|
+
adapterPillarLabel: f.adapterPillarLabel
|
|
2873
3215
|
};
|
|
2874
3216
|
applyCorrections(pf);
|
|
2875
3217
|
persisted[f.rel] = pf;
|
|
2876
3218
|
severityBreakdowns[f.rel] = `severity=${pf.canonicalSeverity} loadBearing=${pf.canonicalLoadBearing} effects=${pf.sideEffectProfile.join(",")} domain=${pf.productDomain}`;
|
|
2877
3219
|
}
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
}
|
|
2886
|
-
const
|
|
2887
|
-
const validationReport = await buildValidationReport(store, deltaTargets, projectRoot, cr);
|
|
3220
|
+
if (adapterFired) {
|
|
3221
|
+
console.error(`[vibe-splain] severity bridge: ${sevBridgeChecked - sevBridgeMismatch}/${sevBridgeChecked} Cal.com domain contributions match core (${sevBridgeMismatch} mismatch)`);
|
|
3222
|
+
}
|
|
3223
|
+
const store = {
|
|
3224
|
+
files: persisted,
|
|
3225
|
+
adapterFired: cr.adapterStage.firedAdapterIds,
|
|
3226
|
+
adapterMetrics: cr.adapterStage.metrics
|
|
3227
|
+
};
|
|
3228
|
+
const validationReport = await buildValidationReport(store, projectRoot, cr);
|
|
2888
3229
|
store.validationReport = validationReport;
|
|
2889
3230
|
for (const e of validationReport.errors) {
|
|
2890
3231
|
console.error(`[vibe-splain] VALIDATION ERROR [${e.rule}] ${e.file}: ${e.detail}`);
|
|
@@ -2892,9 +3233,9 @@ async function runScoring(projectRoot, cr, binding) {
|
|
|
2892
3233
|
for (const w of validationReport.warnings) {
|
|
2893
3234
|
console.error(`[vibe-splain] VALIDATION WARN [${w.rule}] ${w.file}: ${w.detail}`);
|
|
2894
3235
|
}
|
|
2895
|
-
return { store,
|
|
3236
|
+
return { store, validationReport };
|
|
2896
3237
|
}
|
|
2897
|
-
async function buildValidationReport(store,
|
|
3238
|
+
async function buildValidationReport(store, projectRoot, cr) {
|
|
2898
3239
|
const errors = [];
|
|
2899
3240
|
const warnings = [];
|
|
2900
3241
|
let passCount = 0;
|
|
@@ -2926,7 +3267,7 @@ async function buildValidationReport(store, deltaTargets, projectRoot, cr) {
|
|
|
2926
3267
|
});
|
|
2927
3268
|
continue;
|
|
2928
3269
|
}
|
|
2929
|
-
if (pf.productDomain === "booking_creation" && classified?.entrypointTraceStatus === "no_runtime_entrypoint_found" && pf.importsUnresolved.length === 0 && !["app_route_layout", "app_loading_boundary", "app_error_boundary"].includes(pf.frameworkRole) && (pf.
|
|
3270
|
+
if (pf.productDomain === "booking_creation" && classified?.entrypointTraceStatus === "no_runtime_entrypoint_found" && pf.importsUnresolved.length === 0 && !["app_route_layout", "app_loading_boundary", "app_error_boundary"].includes(pf.frameworkRole) && (pf.adapterSideEffects?.includes("booking_mutation") || pf.source.includes("createBooking") || pf.source.includes("handleNewBooking"))) {
|
|
2930
3271
|
errors.push({
|
|
2931
3272
|
file: pf.relativePath,
|
|
2932
3273
|
rule: "booking_creation_no_entrypoint_no_blockers",
|
|
@@ -2964,7 +3305,7 @@ async function buildValidationReport(store, deltaTargets, projectRoot, cr) {
|
|
|
2964
3305
|
if (!pf.isRealSource)
|
|
2965
3306
|
continue;
|
|
2966
3307
|
const hasIntent = pf.writeIntents.includes("handle_payment_webhook");
|
|
2967
|
-
const hasWebhookIngress = pf.
|
|
3308
|
+
const hasWebhookIngress = pf.adapterSideEffects?.includes("webhook_ingress");
|
|
2968
3309
|
const pathMentionsPayment = PAYMENT_PROVIDER_PATH_TERMS.some((t) => rel.toLowerCase().includes(t));
|
|
2969
3310
|
if (!hasIntent && !(hasWebhookIngress && pathMentionsPayment && pf.frameworkRole !== "component"))
|
|
2970
3311
|
continue;
|
|
@@ -2975,12 +3316,12 @@ async function buildValidationReport(store, deltaTargets, projectRoot, cr) {
|
|
|
2975
3316
|
`Payment webhook not classified as payments_webhooks`
|
|
2976
3317
|
],
|
|
2977
3318
|
[
|
|
2978
|
-
!pf.
|
|
3319
|
+
!pf.adapterSideEffects?.includes("webhook_ingress"),
|
|
2979
3320
|
"webhook_ingress_missing",
|
|
2980
3321
|
`Payment webhook missing webhook_ingress side effect`
|
|
2981
3322
|
],
|
|
2982
3323
|
[
|
|
2983
|
-
!pf.
|
|
3324
|
+
!pf.adapterSideEffects?.includes("payment_mutation"),
|
|
2984
3325
|
"webhook_payment_mutation_missing",
|
|
2985
3326
|
`Payment webhook missing payment_mutation side effect`
|
|
2986
3327
|
],
|
|
@@ -3023,9 +3364,8 @@ async function buildValidationReport(store, deltaTargets, projectRoot, cr) {
|
|
|
3023
3364
|
async function runPipeline(projectRoot) {
|
|
3024
3365
|
const inv = await runInventory(projectRoot);
|
|
3025
3366
|
const res = await runResolution(projectRoot, inv);
|
|
3026
|
-
const binding = await runActionBinding(projectRoot, inv, res);
|
|
3027
3367
|
const cr = await runClassification(projectRoot, inv, res);
|
|
3028
|
-
const scoring = await runScoring(projectRoot, cr
|
|
3368
|
+
const scoring = await runScoring(projectRoot, cr);
|
|
3029
3369
|
const files = cr.classified.filter((f) => f.isRealSource).sort((a, b) => b.gravity - a.gravity).map((f) => ({
|
|
3030
3370
|
path: f.abs,
|
|
3031
3371
|
relativePath: f.rel,
|
|
@@ -3038,9 +3378,9 @@ async function runPipeline(projectRoot) {
|
|
|
3038
3378
|
heatSignals: f.heatSignals,
|
|
3039
3379
|
smells: f.smells,
|
|
3040
3380
|
pillarHint: f.pillarHint,
|
|
3041
|
-
frameworkRole: f.frameworkRole,
|
|
3042
|
-
productDomain: f.productDomain,
|
|
3043
|
-
sideEffectProfile: f.sideEffectProfile
|
|
3381
|
+
frameworkRole: f.executionRole ?? f.frameworkRole,
|
|
3382
|
+
productDomain: f.domainTags?.[0] ?? f.adapterDomain ?? f.productDomain,
|
|
3383
|
+
sideEffectProfile: f.adapterSideEffects ?? f.sideEffectProfile
|
|
3044
3384
|
}));
|
|
3045
3385
|
const wildCandidates = cr.classified.filter((f) => f.isRealSource && (f.heat >= 60 || f.smells.some((s) => s.severity >= 4))).sort((a, b) => b.heat - a.heat).map((f) => ({
|
|
3046
3386
|
path: f.abs,
|
|
@@ -3054,9 +3394,9 @@ async function runPipeline(projectRoot) {
|
|
|
3054
3394
|
heatSignals: f.heatSignals,
|
|
3055
3395
|
smells: f.smells,
|
|
3056
3396
|
pillarHint: f.pillarHint,
|
|
3057
|
-
frameworkRole: f.frameworkRole,
|
|
3058
|
-
productDomain: f.productDomain,
|
|
3059
|
-
sideEffectProfile: f.sideEffectProfile
|
|
3397
|
+
frameworkRole: f.executionRole ?? f.frameworkRole,
|
|
3398
|
+
productDomain: f.domainTags?.[0] ?? f.adapterDomain ?? f.productDomain,
|
|
3399
|
+
sideEffectProfile: f.adapterSideEffects ?? f.sideEffectProfile
|
|
3060
3400
|
}));
|
|
3061
3401
|
const uiUrl = `file://${join7(projectRoot, ".vibe-splainer", "ui", "index.html")}`;
|
|
3062
3402
|
return {
|
|
@@ -3093,7 +3433,7 @@ async function getFileAnalysis(absPath) {
|
|
|
3093
3433
|
return null;
|
|
3094
3434
|
let source;
|
|
3095
3435
|
try {
|
|
3096
|
-
source = await
|
|
3436
|
+
source = await readFile4(absPath, "utf8");
|
|
3097
3437
|
} catch {
|
|
3098
3438
|
return null;
|
|
3099
3439
|
}
|
|
@@ -3132,20 +3472,11 @@ async function getFileAnalysis(absPath) {
|
|
|
3132
3472
|
|
|
3133
3473
|
// ../brain/dist/analysis.js
|
|
3134
3474
|
import { join as join8 } from "path";
|
|
3135
|
-
import { readFile as
|
|
3475
|
+
import { readFile as readFile5, writeFile as writeFile5, mkdir as mkdir5 } from "fs/promises";
|
|
3136
3476
|
async function readAnalysis(projectRoot) {
|
|
3137
3477
|
const p = join8(projectRoot, ".vibe-splainer", "analysis.json");
|
|
3138
3478
|
try {
|
|
3139
|
-
const raw = await
|
|
3140
|
-
return JSON.parse(raw);
|
|
3141
|
-
} catch {
|
|
3142
|
-
return null;
|
|
3143
|
-
}
|
|
3144
|
-
}
|
|
3145
|
-
async function readActionBindings(projectRoot) {
|
|
3146
|
-
const p = join8(projectRoot, ".vibe-splainer", "action_bindings.json");
|
|
3147
|
-
try {
|
|
3148
|
-
const raw = await readFile7(p, "utf8");
|
|
3479
|
+
const raw = await readFile5(p, "utf8");
|
|
3149
3480
|
return JSON.parse(raw);
|
|
3150
3481
|
} catch {
|
|
3151
3482
|
return null;
|
|
@@ -3154,11 +3485,11 @@ async function readActionBindings(projectRoot) {
|
|
|
3154
3485
|
|
|
3155
3486
|
// ../brain/dist/dossier.js
|
|
3156
3487
|
import { join as join9 } from "path";
|
|
3157
|
-
import { readFile as
|
|
3488
|
+
import { readFile as readFile6 } from "fs/promises";
|
|
3158
3489
|
async function readDossier(projectRoot) {
|
|
3159
3490
|
const dossierPath = join9(projectRoot, ".vibe-splainer", "dossier.json");
|
|
3160
3491
|
try {
|
|
3161
|
-
const raw = await
|
|
3492
|
+
const raw = await readFile6(dossierPath, "utf8");
|
|
3162
3493
|
return JSON.parse(raw);
|
|
3163
3494
|
} catch {
|
|
3164
3495
|
return null;
|
|
@@ -3262,93 +3593,1141 @@ var RecommendationEngine = class {
|
|
|
3262
3593
|
}
|
|
3263
3594
|
};
|
|
3264
3595
|
|
|
3265
|
-
// ../brain/dist/
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3596
|
+
// ../brain/dist/network/specialists.js
|
|
3597
|
+
var SPECIALIST_REGISTRY = {
|
|
3598
|
+
control_flow: {
|
|
3599
|
+
id: "control_flow",
|
|
3600
|
+
label: "Control Flow",
|
|
3601
|
+
description: "Codebase expert for branching, merging, looping, and execution routing.",
|
|
3602
|
+
owns: [
|
|
3603
|
+
"IF",
|
|
3604
|
+
"Merge",
|
|
3605
|
+
"Switch",
|
|
3606
|
+
"SplitInBatches",
|
|
3607
|
+
"LoopOverItems",
|
|
3608
|
+
"Wait",
|
|
3609
|
+
"ExecuteWorkflow",
|
|
3610
|
+
"ErrorTrigger",
|
|
3611
|
+
"StopAndError",
|
|
3612
|
+
"NoOp",
|
|
3613
|
+
"branching",
|
|
3614
|
+
"conditions",
|
|
3615
|
+
"execution routing",
|
|
3616
|
+
"workflow orchestration",
|
|
3617
|
+
"connection structure"
|
|
3618
|
+
],
|
|
3619
|
+
canCommentOn: [
|
|
3620
|
+
"branch evaluation",
|
|
3621
|
+
"merge semantics",
|
|
3622
|
+
"loop iteration",
|
|
3623
|
+
"connection path",
|
|
3624
|
+
"wrong source port",
|
|
3625
|
+
"execution path"
|
|
3626
|
+
],
|
|
3627
|
+
mustNotCommentOn: [
|
|
3628
|
+
"external API credentials",
|
|
3629
|
+
"Slack schema details",
|
|
3630
|
+
"Gmail auth details",
|
|
3631
|
+
"database credential setup",
|
|
3632
|
+
"HTTP response body shape unless handed off by data_transform"
|
|
3633
|
+
],
|
|
3634
|
+
handoffTriggers: ["data_transform", "integration_contracts"],
|
|
3635
|
+
allowedOutputFields: [
|
|
3636
|
+
"faultHypothesis",
|
|
3637
|
+
"recommendedLocalChange",
|
|
3638
|
+
"filesToEdit",
|
|
3639
|
+
"filesToAvoid",
|
|
3640
|
+
"mustNotChange",
|
|
3641
|
+
"evidence",
|
|
3642
|
+
"confidence",
|
|
3643
|
+
"handoffTo"
|
|
3644
|
+
],
|
|
3645
|
+
forbiddenOutputFields: ["contractObserved", "contractExpected"],
|
|
3646
|
+
keywords: [
|
|
3647
|
+
"if",
|
|
3648
|
+
"branch",
|
|
3649
|
+
"condition",
|
|
3650
|
+
"merge",
|
|
3651
|
+
"loop",
|
|
3652
|
+
"switch",
|
|
3653
|
+
"split",
|
|
3654
|
+
"wait",
|
|
3655
|
+
"connection",
|
|
3656
|
+
"edge",
|
|
3657
|
+
"route",
|
|
3658
|
+
"routing",
|
|
3659
|
+
"source port",
|
|
3660
|
+
"execution path"
|
|
3661
|
+
],
|
|
3662
|
+
nodeTypes: [
|
|
3663
|
+
"n8n-nodes-base.if",
|
|
3664
|
+
"n8n-nodes-base.merge",
|
|
3665
|
+
"n8n-nodes-base.switch",
|
|
3666
|
+
"n8n-nodes-base.splitInBatches",
|
|
3667
|
+
"n8n-nodes-base.loopOverItems",
|
|
3668
|
+
"n8n-nodes-base.wait",
|
|
3669
|
+
"n8n-nodes-base.executeWorkflow",
|
|
3670
|
+
"n8n-nodes-base.errorTrigger",
|
|
3671
|
+
"n8n-nodes-base.stopAndError",
|
|
3672
|
+
"n8n-nodes-base.noOp"
|
|
3673
|
+
]
|
|
3674
|
+
},
|
|
3675
|
+
data_transform: {
|
|
3676
|
+
id: "data_transform",
|
|
3677
|
+
label: "Data Transformation + HTTP",
|
|
3678
|
+
description: "Codebase expert for payload manipulation, HTTP request/response contracts, and expressions.",
|
|
3679
|
+
owns: [
|
|
3680
|
+
"HttpRequest",
|
|
3681
|
+
"Set",
|
|
3682
|
+
"Code",
|
|
3683
|
+
"Function",
|
|
3684
|
+
"ItemLists",
|
|
3685
|
+
"DateTime",
|
|
3686
|
+
"Crypto",
|
|
3687
|
+
"MoveBinaryData",
|
|
3688
|
+
"EditFields",
|
|
3689
|
+
"RespondToWebhook",
|
|
3690
|
+
"Aggregate",
|
|
3691
|
+
"Summarize",
|
|
3692
|
+
"payload mapping",
|
|
3693
|
+
"JSON path",
|
|
3694
|
+
"expression path",
|
|
3695
|
+
"item arrays",
|
|
3696
|
+
"binary versus JSON handling",
|
|
3697
|
+
"HTTP request and response shape"
|
|
3698
|
+
],
|
|
3699
|
+
canCommentOn: [
|
|
3700
|
+
"field paths",
|
|
3701
|
+
"response body shape",
|
|
3702
|
+
"item array transformations",
|
|
3703
|
+
"expression syntax",
|
|
3704
|
+
"schema mapping"
|
|
3705
|
+
],
|
|
3706
|
+
mustNotCommentOn: [
|
|
3707
|
+
"credential configuration",
|
|
3708
|
+
"auth mode selection",
|
|
3709
|
+
"external service permissions",
|
|
3710
|
+
"branching semantics unless handed off by control_flow"
|
|
3711
|
+
],
|
|
3712
|
+
handoffTriggers: ["control_flow", "integration_contracts"],
|
|
3713
|
+
allowedOutputFields: [
|
|
3714
|
+
"faultHypothesis",
|
|
3715
|
+
"contractObserved",
|
|
3716
|
+
"contractExpected",
|
|
3717
|
+
"recommendedLocalChange",
|
|
3718
|
+
"filesToEdit",
|
|
3719
|
+
"filesToAvoid",
|
|
3720
|
+
"mustNotChange",
|
|
3721
|
+
"evidence",
|
|
3722
|
+
"confidence",
|
|
3723
|
+
"handoffTo"
|
|
3724
|
+
],
|
|
3725
|
+
forbiddenOutputFields: [],
|
|
3726
|
+
keywords: [
|
|
3727
|
+
"http",
|
|
3728
|
+
"httprequest",
|
|
3729
|
+
"payload",
|
|
3730
|
+
"json",
|
|
3731
|
+
"expression",
|
|
3732
|
+
"path",
|
|
3733
|
+
"field",
|
|
3734
|
+
"schema",
|
|
3735
|
+
"body",
|
|
3736
|
+
"response",
|
|
3737
|
+
"set",
|
|
3738
|
+
"code",
|
|
3739
|
+
"item",
|
|
3740
|
+
"items",
|
|
3741
|
+
"array",
|
|
3742
|
+
"binary",
|
|
3743
|
+
"mapping",
|
|
3744
|
+
"transform"
|
|
3745
|
+
],
|
|
3746
|
+
nodeTypes: [
|
|
3747
|
+
"n8n-nodes-base.httpRequest",
|
|
3748
|
+
"n8n-nodes-base.set",
|
|
3749
|
+
"n8n-nodes-base.code",
|
|
3750
|
+
"n8n-nodes-base.function",
|
|
3751
|
+
"n8n-nodes-base.itemLists",
|
|
3752
|
+
"n8n-nodes-base.dateTime",
|
|
3753
|
+
"n8n-nodes-base.crypto",
|
|
3754
|
+
"n8n-nodes-base.moveBinaryData",
|
|
3755
|
+
"n8n-nodes-base.editFields",
|
|
3756
|
+
"n8n-nodes-base.respondToWebhook",
|
|
3757
|
+
"n8n-nodes-base.aggregate",
|
|
3758
|
+
"n8n-nodes-base.summarize"
|
|
3759
|
+
]
|
|
3760
|
+
},
|
|
3761
|
+
integration_contracts: {
|
|
3762
|
+
id: "integration_contracts",
|
|
3763
|
+
label: "Integration Contracts",
|
|
3764
|
+
description: "Codebase expert for external service contracts, authentication patterns, and API integrations.",
|
|
3765
|
+
owns: [
|
|
3766
|
+
"Slack",
|
|
3767
|
+
"Gmail",
|
|
3768
|
+
"GoogleSheets",
|
|
3769
|
+
"Postgres",
|
|
3770
|
+
"S3",
|
|
3771
|
+
"Notion",
|
|
3772
|
+
"Airtable",
|
|
3773
|
+
"Discord",
|
|
3774
|
+
"Webhook",
|
|
3775
|
+
"GitHub",
|
|
3776
|
+
"Jira",
|
|
3777
|
+
"HubSpot",
|
|
3778
|
+
"external API contracts",
|
|
3779
|
+
"credentials",
|
|
3780
|
+
"auth behavior",
|
|
3781
|
+
"rate limiting",
|
|
3782
|
+
"pagination",
|
|
3783
|
+
"integration node input and output expectations",
|
|
3784
|
+
"external service side effects"
|
|
3785
|
+
],
|
|
3786
|
+
canCommentOn: [
|
|
3787
|
+
"external service contract",
|
|
3788
|
+
"credential usage",
|
|
3789
|
+
"auth mode",
|
|
3790
|
+
"API response expectations",
|
|
3791
|
+
"integration node input requirements",
|
|
3792
|
+
"downstream service behavior"
|
|
3793
|
+
],
|
|
3794
|
+
mustNotCommentOn: [
|
|
3795
|
+
"generic branch logic",
|
|
3796
|
+
"internal item transformation mechanics",
|
|
3797
|
+
"unrelated workflow nodes",
|
|
3798
|
+
"global workflow architecture"
|
|
3799
|
+
],
|
|
3800
|
+
handoffTriggers: ["data_transform", "control_flow"],
|
|
3801
|
+
allowedOutputFields: [
|
|
3802
|
+
"faultHypothesis",
|
|
3803
|
+
"contractObserved",
|
|
3804
|
+
"contractExpected",
|
|
3805
|
+
"recommendedLocalChange",
|
|
3806
|
+
"filesToEdit",
|
|
3807
|
+
"filesToAvoid",
|
|
3808
|
+
"mustNotChange",
|
|
3809
|
+
"evidence",
|
|
3810
|
+
"confidence",
|
|
3811
|
+
"handoffTo"
|
|
3812
|
+
],
|
|
3813
|
+
forbiddenOutputFields: [],
|
|
3814
|
+
keywords: [
|
|
3815
|
+
"slack",
|
|
3816
|
+
"gmail",
|
|
3817
|
+
"webhook",
|
|
3818
|
+
"postgres",
|
|
3819
|
+
"notion",
|
|
3820
|
+
"github",
|
|
3821
|
+
"jira",
|
|
3822
|
+
"hubspot",
|
|
3823
|
+
"s3",
|
|
3824
|
+
"api",
|
|
3825
|
+
"credential",
|
|
3826
|
+
"credentials",
|
|
3827
|
+
"auth",
|
|
3828
|
+
"oauth",
|
|
3829
|
+
"token",
|
|
3830
|
+
"external",
|
|
3831
|
+
"service",
|
|
3832
|
+
"rate limit",
|
|
3833
|
+
"pagination",
|
|
3834
|
+
"contract"
|
|
3835
|
+
],
|
|
3836
|
+
nodeTypes: [
|
|
3837
|
+
"n8n-nodes-base.slack",
|
|
3838
|
+
"n8n-nodes-base.gmail",
|
|
3839
|
+
"n8n-nodes-base.googleSheets",
|
|
3840
|
+
"n8n-nodes-base.postgres",
|
|
3841
|
+
"n8n-nodes-base.s3",
|
|
3842
|
+
"n8n-nodes-base.notion",
|
|
3843
|
+
"n8n-nodes-base.airtable",
|
|
3844
|
+
"n8n-nodes-base.discord",
|
|
3845
|
+
"n8n-nodes-base.webhook",
|
|
3846
|
+
"n8n-nodes-base.github",
|
|
3847
|
+
"n8n-nodes-base.jira",
|
|
3848
|
+
"n8n-nodes-base.hubspot"
|
|
3849
|
+
]
|
|
3850
|
+
},
|
|
3851
|
+
generic_repo: {
|
|
3852
|
+
id: "generic_repo",
|
|
3853
|
+
label: "Generic Repository Fallback",
|
|
3854
|
+
description: "Fallback expert for generic static analysis, non-adapter repos, and general tasks.",
|
|
3855
|
+
owns: [
|
|
3856
|
+
"fallback routing",
|
|
3857
|
+
"unknown tasks",
|
|
3858
|
+
"generic static analysis packet",
|
|
3859
|
+
"non adapter repos",
|
|
3860
|
+
"low confidence tasks"
|
|
3861
|
+
],
|
|
3862
|
+
canCommentOn: [
|
|
3863
|
+
"selected files",
|
|
3864
|
+
"general risk warnings",
|
|
3865
|
+
"smallest safe change policy",
|
|
3866
|
+
"lack of strong specialist match"
|
|
3867
|
+
],
|
|
3868
|
+
mustNotCommentOn: [
|
|
3869
|
+
"n8n specific node behavior",
|
|
3870
|
+
"specific external API semantics",
|
|
3871
|
+
"domain specific contracts without evidence"
|
|
3872
|
+
],
|
|
3873
|
+
handoffTriggers: ["control_flow", "data_transform", "integration_contracts"],
|
|
3874
|
+
allowedOutputFields: [
|
|
3875
|
+
"faultHypothesis",
|
|
3876
|
+
"recommendedLocalChange",
|
|
3877
|
+
"filesToEdit",
|
|
3878
|
+
"filesToAvoid",
|
|
3879
|
+
"mustNotChange",
|
|
3880
|
+
"evidence",
|
|
3881
|
+
"confidence",
|
|
3882
|
+
"handoffTo"
|
|
3883
|
+
],
|
|
3884
|
+
forbiddenOutputFields: ["contractObserved", "contractExpected"],
|
|
3885
|
+
keywords: [],
|
|
3886
|
+
nodeTypes: []
|
|
3887
|
+
}
|
|
3888
|
+
};
|
|
3889
|
+
function getSpecialistRegistry() {
|
|
3890
|
+
return Object.values(SPECIALIST_REGISTRY);
|
|
3891
|
+
}
|
|
3892
|
+
function getSpecialistProfile(id) {
|
|
3893
|
+
const profile = SPECIALIST_REGISTRY[id];
|
|
3894
|
+
if (!profile) {
|
|
3895
|
+
throw new Error(`Unknown specialist ID: ${id}`);
|
|
3896
|
+
}
|
|
3897
|
+
return profile;
|
|
3898
|
+
}
|
|
3899
|
+
function isSpecialistId(value) {
|
|
3900
|
+
return value in SPECIALIST_REGISTRY;
|
|
3901
|
+
}
|
|
3902
|
+
|
|
3903
|
+
// ../brain/dist/network/router.js
|
|
3904
|
+
function routeTaskToPanel(input, candidateFiles, behaviorUnits) {
|
|
3905
|
+
const taskText = (input.task || "").toLowerCase();
|
|
3906
|
+
const intendedText = (input.intendedBehavior || "").toLowerCase();
|
|
3907
|
+
const errorText = (input.errorTrace || "").toLowerCase();
|
|
3908
|
+
const inputFiles = (input.files || []).map((f) => f.toLowerCase());
|
|
3909
|
+
const taskWords = new Set(taskText.split(/[^a-z0-9_]+/));
|
|
3910
|
+
const intendedWords = new Set(intendedText.split(/[^a-z0-9_]+/));
|
|
3911
|
+
const errorWords = new Set(errorText.split(/[^a-z0-9_]+/));
|
|
3912
|
+
const registry = getSpecialistRegistry();
|
|
3913
|
+
const scores = [];
|
|
3914
|
+
const commonNodeNames = /* @__PURE__ */ new Set(["code", "set", "if", "wait"]);
|
|
3915
|
+
for (const profile of registry) {
|
|
3916
|
+
if (profile.id === "generic_repo") {
|
|
3917
|
+
continue;
|
|
3918
|
+
}
|
|
3919
|
+
let score = 0;
|
|
3920
|
+
const reasons = [];
|
|
3921
|
+
let fileMatch = false;
|
|
3922
|
+
if (candidateFiles) {
|
|
3923
|
+
for (const file of candidateFiles) {
|
|
3924
|
+
const pathLower = file.path.toLowerCase();
|
|
3925
|
+
const isExplicit = inputFiles.length === 0 || inputFiles.some((f) => pathLower.includes(f) || f.includes(pathLower));
|
|
3926
|
+
if (isExplicit) {
|
|
3927
|
+
if (file.adapterDomain === profile.id) {
|
|
3928
|
+
score += 35;
|
|
3929
|
+
reasons.push(`Explicit candidate file match on adapter domain: ${file.path}`);
|
|
3930
|
+
fileMatch = true;
|
|
3931
|
+
} else if (file.domainTags?.includes(profile.id)) {
|
|
3932
|
+
score += 35;
|
|
3933
|
+
reasons.push(`Explicit candidate file match on domain tag: ${file.path}`);
|
|
3934
|
+
fileMatch = true;
|
|
3935
|
+
}
|
|
3936
|
+
}
|
|
3286
3937
|
}
|
|
3287
3938
|
}
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3939
|
+
let ownsWordMatch = false;
|
|
3940
|
+
for (const item of profile.owns) {
|
|
3941
|
+
const itemLower = item.toLowerCase();
|
|
3942
|
+
if (taskWords.has(itemLower) || intendedWords.has(itemLower) || errorWords.has(itemLower)) {
|
|
3943
|
+
if (commonNodeNames.has(itemLower)) {
|
|
3944
|
+
const pattern = new RegExp(`\\b${itemLower}\\s+nodes?\\b|\\bn8n\\s+${itemLower}\\b`);
|
|
3945
|
+
if (pattern.test(taskText) || pattern.test(intendedText) || pattern.test(errorText)) {
|
|
3946
|
+
ownsWordMatch = true;
|
|
3947
|
+
break;
|
|
3948
|
+
}
|
|
3949
|
+
} else {
|
|
3950
|
+
ownsWordMatch = true;
|
|
3951
|
+
break;
|
|
3952
|
+
}
|
|
3293
3953
|
}
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3954
|
+
}
|
|
3955
|
+
for (const nt of profile.nodeTypes) {
|
|
3956
|
+
const ntLower = nt.toLowerCase();
|
|
3957
|
+
const baseName = nt.split(".").pop().toLowerCase();
|
|
3958
|
+
const hasBase = taskWords.has(baseName) || intendedWords.has(baseName) || errorWords.has(baseName);
|
|
3959
|
+
const hasFull = taskWords.has(ntLower) || intendedWords.has(ntLower) || errorWords.has(ntLower);
|
|
3960
|
+
if (hasFull) {
|
|
3961
|
+
ownsWordMatch = true;
|
|
3962
|
+
break;
|
|
3298
3963
|
}
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3964
|
+
if (hasBase) {
|
|
3965
|
+
if (commonNodeNames.has(baseName)) {
|
|
3966
|
+
const pattern = new RegExp(`\\b${baseName}\\s+nodes?\\b|\\bn8n\\s+${baseName}\\b`);
|
|
3967
|
+
if (pattern.test(taskText) || pattern.test(intendedText) || pattern.test(errorText)) {
|
|
3968
|
+
ownsWordMatch = true;
|
|
3969
|
+
break;
|
|
3970
|
+
}
|
|
3971
|
+
} else {
|
|
3972
|
+
ownsWordMatch = true;
|
|
3973
|
+
break;
|
|
3974
|
+
}
|
|
3302
3975
|
}
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3976
|
+
}
|
|
3977
|
+
if (ownsWordMatch) {
|
|
3978
|
+
score += 35;
|
|
3979
|
+
reasons.push("Explicit owned node or concept mentioned in task/error trace");
|
|
3980
|
+
fileMatch = true;
|
|
3981
|
+
}
|
|
3982
|
+
for (const file of inputFiles) {
|
|
3983
|
+
if (profile.keywords.some((k) => file.includes(k)) || profile.owns.some((o) => file.includes(o.toLowerCase()))) {
|
|
3984
|
+
score += 35;
|
|
3985
|
+
reasons.push(`Explicit file path match on keyword/ownership: ${file}`);
|
|
3986
|
+
fileMatch = true;
|
|
3987
|
+
}
|
|
3988
|
+
}
|
|
3989
|
+
let keywordMatched = false;
|
|
3990
|
+
for (const keyword of profile.keywords) {
|
|
3991
|
+
if (commonNodeNames.has(keyword)) {
|
|
3992
|
+
const pattern = new RegExp(`\\b${keyword}\\s+nodes?\\b|\\bn8n\\s+${keyword}\\b`);
|
|
3993
|
+
if (pattern.test(taskText) || pattern.test(intendedText)) {
|
|
3994
|
+
keywordMatched = true;
|
|
3995
|
+
break;
|
|
3996
|
+
}
|
|
3997
|
+
} else {
|
|
3998
|
+
if (taskText.includes(keyword) || intendedText.includes(keyword)) {
|
|
3999
|
+
keywordMatched = true;
|
|
4000
|
+
break;
|
|
4001
|
+
}
|
|
4002
|
+
}
|
|
4003
|
+
}
|
|
4004
|
+
if (keywordMatched) {
|
|
4005
|
+
score += 25;
|
|
4006
|
+
reasons.push("Task/Intended behavior matched domain keywords");
|
|
4007
|
+
}
|
|
4008
|
+
let errorMatched = false;
|
|
4009
|
+
if (errorText) {
|
|
4010
|
+
for (const keyword of profile.keywords) {
|
|
4011
|
+
if (commonNodeNames.has(keyword)) {
|
|
4012
|
+
const pattern = new RegExp(`\\b${keyword}\\s+nodes?\\b|\\bn8n\\s+${keyword}\\b`);
|
|
4013
|
+
if (pattern.test(errorText)) {
|
|
4014
|
+
errorMatched = true;
|
|
4015
|
+
break;
|
|
4016
|
+
}
|
|
4017
|
+
} else {
|
|
4018
|
+
if (errorText.includes(keyword)) {
|
|
4019
|
+
errorMatched = true;
|
|
4020
|
+
break;
|
|
3309
4021
|
}
|
|
3310
|
-
} catch {
|
|
3311
|
-
errors.push(`UnreadableProof: cannot parse proof blob for ${proof.pointer}`);
|
|
3312
4022
|
}
|
|
3313
4023
|
}
|
|
4024
|
+
if (errorMatched) {
|
|
4025
|
+
score += 20;
|
|
4026
|
+
reasons.push("Error trace matched domain keywords");
|
|
4027
|
+
}
|
|
3314
4028
|
}
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
4029
|
+
let buMatchCount = 0;
|
|
4030
|
+
if (behaviorUnits) {
|
|
4031
|
+
for (const bu of behaviorUnits) {
|
|
4032
|
+
if (bu.domain === profile.id || bu.specialist === profile.id) {
|
|
4033
|
+
score += 15;
|
|
4034
|
+
buMatchCount++;
|
|
4035
|
+
}
|
|
4036
|
+
}
|
|
4037
|
+
if (buMatchCount > 0) {
|
|
4038
|
+
reasons.push(`Matched ${buMatchCount} behavior units`);
|
|
3318
4039
|
}
|
|
3319
4040
|
}
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
4041
|
+
let adapterMatchCount = 0;
|
|
4042
|
+
if (candidateFiles) {
|
|
4043
|
+
for (const file of candidateFiles) {
|
|
4044
|
+
if (file.adapterDomain === profile.id || file.domainTags?.includes(profile.id)) {
|
|
4045
|
+
score += 15;
|
|
4046
|
+
adapterMatchCount++;
|
|
4047
|
+
}
|
|
3323
4048
|
}
|
|
3324
|
-
if (
|
|
3325
|
-
|
|
4049
|
+
if (adapterMatchCount > 0) {
|
|
4050
|
+
reasons.push(`Candidate file metadata matches domain: ${adapterMatchCount} files`);
|
|
3326
4051
|
}
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
4052
|
+
}
|
|
4053
|
+
let sideEffectMatch = false;
|
|
4054
|
+
if (candidateFiles) {
|
|
4055
|
+
for (const file of candidateFiles) {
|
|
4056
|
+
if (file.adapterSideEffects) {
|
|
4057
|
+
for (const se of file.adapterSideEffects) {
|
|
4058
|
+
const seLower = se.toLowerCase();
|
|
4059
|
+
if (profile.owns.some((o) => seLower.includes(o.toLowerCase()))) {
|
|
4060
|
+
score += 10;
|
|
4061
|
+
sideEffectMatch = true;
|
|
4062
|
+
}
|
|
4063
|
+
}
|
|
4064
|
+
}
|
|
3330
4065
|
}
|
|
3331
4066
|
}
|
|
4067
|
+
if (profile.id === "integration_contracts" && (taskText.includes("auth") || taskText.includes("credential") || taskText.includes("api"))) {
|
|
4068
|
+
score += 10;
|
|
4069
|
+
sideEffectMatch = true;
|
|
4070
|
+
}
|
|
4071
|
+
if (sideEffectMatch) {
|
|
4072
|
+
reasons.push("Matched side effect or API capabilities");
|
|
4073
|
+
}
|
|
4074
|
+
if (score > 0) {
|
|
4075
|
+
scores.push({ specialist: profile.id, score, reasons });
|
|
4076
|
+
}
|
|
4077
|
+
}
|
|
4078
|
+
scores.sort((a, b) => b.score - a.score);
|
|
4079
|
+
const topScore = scores.length > 0 ? scores[0].score : 0;
|
|
4080
|
+
if (topScore < 30) {
|
|
3332
4081
|
return {
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
4082
|
+
primarySpecialist: "generic_repo",
|
|
4083
|
+
panel: ["generic_repo"],
|
|
4084
|
+
confidence: "low",
|
|
4085
|
+
reason: "No specialist domain met the primary relevance threshold of 30.",
|
|
4086
|
+
scores: [
|
|
4087
|
+
{ specialist: "generic_repo", score: 1, reasons: ["Fallback generic_repo baseline"] },
|
|
4088
|
+
...scores
|
|
4089
|
+
]
|
|
3336
4090
|
};
|
|
3337
4091
|
}
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
4092
|
+
const primarySpecialist = scores[0].specialist;
|
|
4093
|
+
const panel = [primarySpecialist];
|
|
4094
|
+
for (let i = 1; i < scores.length; i++) {
|
|
4095
|
+
const item = scores[i];
|
|
4096
|
+
if (item.score >= 15 && topScore - item.score <= 35) {
|
|
4097
|
+
panel.push(item.specialist);
|
|
4098
|
+
}
|
|
4099
|
+
}
|
|
4100
|
+
const confidence = topScore >= 60 ? "high" : topScore >= 45 ? "medium" : "low";
|
|
4101
|
+
const panelString = panel.join(", ");
|
|
4102
|
+
return {
|
|
4103
|
+
primarySpecialist,
|
|
4104
|
+
panel,
|
|
4105
|
+
confidence,
|
|
4106
|
+
reason: `Routed to ${panelString} based on primary score of ${topScore} (${scores[0].reasons.join("; ")}).`,
|
|
4107
|
+
scores: [
|
|
4108
|
+
...scores,
|
|
4109
|
+
{ specialist: "generic_repo", score: 1, reasons: ["Generic fallback available"] }
|
|
4110
|
+
]
|
|
4111
|
+
};
|
|
4112
|
+
}
|
|
4113
|
+
|
|
4114
|
+
// ../brain/dist/network/scope.js
|
|
4115
|
+
function slicePacketForSpecialist(packet, profile) {
|
|
4116
|
+
const allowedDomains = /* @__PURE__ */ new Set([profile.id]);
|
|
4117
|
+
profile.handoffTriggers.forEach((d) => allowedDomains.add(d));
|
|
4118
|
+
packet.route.panel.forEach((d) => allowedDomains.add(d));
|
|
4119
|
+
const selectedFiles = packet.selectedFiles.filter((file) => {
|
|
4120
|
+
const fileDomain = file.adapterDomain;
|
|
4121
|
+
if (!fileDomain || fileDomain === "generic_repo") {
|
|
4122
|
+
return true;
|
|
4123
|
+
}
|
|
4124
|
+
if (fileDomain === profile.id) {
|
|
4125
|
+
return true;
|
|
4126
|
+
}
|
|
4127
|
+
if (packet.route.panel.includes(fileDomain)) {
|
|
4128
|
+
return true;
|
|
4129
|
+
}
|
|
4130
|
+
const pathLower = file.path.toLowerCase();
|
|
4131
|
+
if (profile.keywords.some((k) => pathLower.includes(k))) {
|
|
4132
|
+
return true;
|
|
4133
|
+
}
|
|
4134
|
+
return false;
|
|
4135
|
+
});
|
|
4136
|
+
const allowedFilesPaths = selectedFiles.map((f) => f.path);
|
|
4137
|
+
const forbiddenFilesPaths = packet.selectedFiles.map((f) => f.path).filter((p) => !allowedFilesPaths.includes(p));
|
|
4138
|
+
const behaviorUnits = packet.behaviorUnits.filter((bu) => {
|
|
4139
|
+
return bu.specialist === profile.id || bu.domain === profile.id || allowedDomains.has(bu.domain);
|
|
4140
|
+
});
|
|
4141
|
+
const pathExamples = packet.pathExamples.filter((pe) => {
|
|
4142
|
+
return pe.domains.some((d) => allowedDomains.has(d));
|
|
4143
|
+
});
|
|
4144
|
+
const riskWarnings = packet.riskWarnings.filter((rw) => {
|
|
4145
|
+
if (rw.specialist === profile.id || rw.specialist && allowedDomains.has(rw.specialist)) {
|
|
4146
|
+
return true;
|
|
4147
|
+
}
|
|
4148
|
+
if (rw.file && allowedFilesPaths.includes(rw.file)) {
|
|
4149
|
+
return true;
|
|
4150
|
+
}
|
|
4151
|
+
return false;
|
|
4152
|
+
});
|
|
4153
|
+
const evidence = packet.evidence.filter((ev) => {
|
|
4154
|
+
if (ev.file && allowedFilesPaths.includes(ev.file)) {
|
|
4155
|
+
return true;
|
|
4156
|
+
}
|
|
4157
|
+
return ev.confidence === "high" || ev.kind === "keyword";
|
|
4158
|
+
});
|
|
4159
|
+
const scopePolicy = {
|
|
4160
|
+
specialist: profile.id,
|
|
4161
|
+
allowedFiles: allowedFilesPaths,
|
|
4162
|
+
forbiddenFiles: forbiddenFilesPaths,
|
|
4163
|
+
allowedDomains: Array.from(allowedDomains),
|
|
4164
|
+
forbiddenClaims: profile.mustNotCommentOn,
|
|
4165
|
+
mustNotModify: forbiddenFilesPaths,
|
|
4166
|
+
handoffRequiredWhen: profile.handoffTriggers.map((id) => `requires handoff when modifying ${id} surface`)
|
|
4167
|
+
};
|
|
4168
|
+
return {
|
|
4169
|
+
packetVersion: "scoped_specialist_v0",
|
|
4170
|
+
task: packet.task,
|
|
4171
|
+
specialist: profile.id,
|
|
4172
|
+
profile,
|
|
4173
|
+
route: packet.route,
|
|
4174
|
+
selectedFiles,
|
|
4175
|
+
behaviorUnits,
|
|
4176
|
+
pathExamples,
|
|
4177
|
+
riskWarnings,
|
|
4178
|
+
scopePolicy,
|
|
4179
|
+
smallestSafeChangePolicy: packet.smallestSafeChangePolicy,
|
|
4180
|
+
evidence
|
|
4181
|
+
};
|
|
3342
4182
|
}
|
|
3343
|
-
|
|
3344
|
-
const
|
|
3345
|
-
|
|
4183
|
+
function validateAdvisory(advisory, scopedPacket, profile) {
|
|
4184
|
+
const errors = [];
|
|
4185
|
+
const warnings = [];
|
|
4186
|
+
if (advisory.scopeStatus === "out_of_scope") {
|
|
4187
|
+
if (advisory.recommendedLocalChange) {
|
|
4188
|
+
errors.push("Advisory scopeStatus is out_of_scope, but recommendedLocalChange is present.");
|
|
4189
|
+
}
|
|
4190
|
+
if (advisory.filesToEdit && advisory.filesToEdit.length > 0) {
|
|
4191
|
+
errors.push("Advisory scopeStatus is out_of_scope, but filesToEdit is not empty.");
|
|
4192
|
+
}
|
|
4193
|
+
if (advisory.specialist !== "generic_repo") {
|
|
4194
|
+
if (!advisory.handoffTo || advisory.handoffTo.length === 0) {
|
|
4195
|
+
errors.push("Advisory scopeStatus is out_of_scope, but handoffTo is empty or missing.");
|
|
4196
|
+
}
|
|
4197
|
+
}
|
|
4198
|
+
}
|
|
4199
|
+
if (advisory.recommendedLocalChange && advisory.scopeStatus === "out_of_scope") {
|
|
4200
|
+
errors.push("Advisory provides a recommendedLocalChange but scopeStatus is out_of_scope.");
|
|
4201
|
+
}
|
|
4202
|
+
if (advisory.filesToEdit) {
|
|
4203
|
+
for (const file of advisory.filesToEdit) {
|
|
4204
|
+
if (!scopedPacket.scopePolicy.allowedFiles.includes(file)) {
|
|
4205
|
+
errors.push(`Advisory recommends editing file outside allowed scope: ${file}`);
|
|
4206
|
+
}
|
|
4207
|
+
if (scopedPacket.scopePolicy.forbiddenFiles.includes(file)) {
|
|
4208
|
+
errors.push(`Advisory recommends editing forbidden file: ${file}`);
|
|
4209
|
+
}
|
|
4210
|
+
if (scopedPacket.scopePolicy.mustNotModify.includes(file)) {
|
|
4211
|
+
errors.push(`Advisory recommends editing file listed in mustNotModify: ${file}`);
|
|
4212
|
+
}
|
|
4213
|
+
}
|
|
4214
|
+
}
|
|
4215
|
+
const textToScan = [
|
|
4216
|
+
advisory.faultHypothesis || "",
|
|
4217
|
+
advisory.recommendedLocalChange || "",
|
|
4218
|
+
advisory.contractObserved || "",
|
|
4219
|
+
advisory.contractExpected || ""
|
|
4220
|
+
].join(" ").toLowerCase();
|
|
4221
|
+
for (const claim of scopedPacket.scopePolicy.forbiddenClaims) {
|
|
4222
|
+
if (textToScan.includes(claim.toLowerCase())) {
|
|
4223
|
+
errors.push(`Advisory violates scope by discussing forbidden claim/concept: "${claim}"`);
|
|
4224
|
+
}
|
|
4225
|
+
}
|
|
4226
|
+
if (advisory.confidence === "high" && (!advisory.evidence || advisory.evidence.length === 0)) {
|
|
4227
|
+
errors.push("Advisory has high confidence but provides no evidence.");
|
|
4228
|
+
}
|
|
4229
|
+
if (advisory.specialist === "generic_repo") {
|
|
4230
|
+
const n8nKeywords = ["slack", "gmail", "if node", "httprequest", "set node", "merge node", "splitinbatches"];
|
|
4231
|
+
for (const kw of n8nKeywords) {
|
|
4232
|
+
if (textToScan.includes(kw)) {
|
|
4233
|
+
warnings.push(`Generic repository specialist discusses n8n-specific node behavior: "${kw}"`);
|
|
4234
|
+
}
|
|
4235
|
+
}
|
|
4236
|
+
}
|
|
4237
|
+
return {
|
|
4238
|
+
valid: errors.length === 0,
|
|
4239
|
+
errors,
|
|
4240
|
+
warnings
|
|
4241
|
+
};
|
|
4242
|
+
}
|
|
4243
|
+
|
|
4244
|
+
// ../brain/dist/network/packet.js
|
|
4245
|
+
function buildExpertNetworkPacket(input) {
|
|
4246
|
+
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4247
|
+
const task = input.task || "";
|
|
4248
|
+
const projectRoot = input.projectRoot || "";
|
|
4249
|
+
const adapterId = input.adapterId || null;
|
|
4250
|
+
const maxFiles = input.maxFiles || 8;
|
|
4251
|
+
let selectedFiles = [];
|
|
4252
|
+
let behaviorUnits = [];
|
|
4253
|
+
const rawArtifact = input.scanArtifact;
|
|
4254
|
+
if (rawArtifact && rawArtifact.files) {
|
|
4255
|
+
const filesRecord = rawArtifact.files;
|
|
4256
|
+
const allFiles = [];
|
|
4257
|
+
for (const [relPath, file] of Object.entries(filesRecord)) {
|
|
4258
|
+
const gravity = file.gravity !== void 0 ? file.gravity : 0;
|
|
4259
|
+
const staticGravity = file.staticGravity !== void 0 ? file.staticGravity : 0;
|
|
4260
|
+
const heat = file.heat !== void 0 ? file.heat : 0;
|
|
4261
|
+
const severity = file.canonicalSeverity !== void 0 ? file.canonicalSeverity : void 0;
|
|
4262
|
+
allFiles.push({
|
|
4263
|
+
path: relPath,
|
|
4264
|
+
reason: "File analysis from project scan.",
|
|
4265
|
+
gravity,
|
|
4266
|
+
staticGravity,
|
|
4267
|
+
heat,
|
|
4268
|
+
severity,
|
|
4269
|
+
domainTags: file.domainTags,
|
|
4270
|
+
adapterDomain: file.adapterDomain,
|
|
4271
|
+
executionRole: file.executionRole,
|
|
4272
|
+
adapterSideEffects: file.adapterSideEffects,
|
|
4273
|
+
blastRadius: gravity > 70 ? "high" : gravity > 40 ? "medium" : "low",
|
|
4274
|
+
includeMode: "excerpt"
|
|
4275
|
+
});
|
|
4276
|
+
}
|
|
4277
|
+
allFiles.sort((a, b) => (b.gravity || 0) - (a.gravity || 0));
|
|
4278
|
+
if (input.files && input.files.length > 0) {
|
|
4279
|
+
const inputLower = input.files.map((f) => f.toLowerCase());
|
|
4280
|
+
selectedFiles = allFiles.filter((f) => {
|
|
4281
|
+
const pathLower = f.path.toLowerCase();
|
|
4282
|
+
return inputLower.some((inf) => pathLower.includes(inf) || inf.includes(pathLower));
|
|
4283
|
+
});
|
|
4284
|
+
} else {
|
|
4285
|
+
selectedFiles = allFiles.slice(0, maxFiles);
|
|
4286
|
+
}
|
|
4287
|
+
selectedFiles.forEach((file, index) => {
|
|
4288
|
+
if (file.adapterDomain) {
|
|
4289
|
+
behaviorUnits.push({
|
|
4290
|
+
id: `bu_${file.path}_node`,
|
|
4291
|
+
unitType: "node",
|
|
4292
|
+
unitName: file.path.split("/").pop() || file.path,
|
|
4293
|
+
file: file.path,
|
|
4294
|
+
domain: file.adapterDomain,
|
|
4295
|
+
specialist: file.adapterDomain,
|
|
4296
|
+
gravityRank: index + 1,
|
|
4297
|
+
gravity: file.gravity,
|
|
4298
|
+
source: "scan",
|
|
4299
|
+
validatedBy: ["static_analysis"],
|
|
4300
|
+
confidence: "medium",
|
|
4301
|
+
evidence: [
|
|
4302
|
+
{
|
|
4303
|
+
kind: "scan",
|
|
4304
|
+
file: file.path,
|
|
4305
|
+
summary: `File identified with gravity ${file.gravity} in domain ${file.adapterDomain}.`,
|
|
4306
|
+
confidence: "medium"
|
|
4307
|
+
}
|
|
4308
|
+
]
|
|
4309
|
+
});
|
|
4310
|
+
}
|
|
4311
|
+
});
|
|
4312
|
+
} else if (input.files && input.files.length > 0) {
|
|
4313
|
+
selectedFiles = input.files.map((file) => ({
|
|
4314
|
+
path: file,
|
|
4315
|
+
reason: "User explicitly specified file.",
|
|
4316
|
+
blastRadius: "medium",
|
|
4317
|
+
includeMode: "excerpt"
|
|
4318
|
+
}));
|
|
4319
|
+
}
|
|
4320
|
+
const route = routeTaskToPanel({
|
|
4321
|
+
task: input.task,
|
|
4322
|
+
files: input.files,
|
|
4323
|
+
errorTrace: input.errorTrace,
|
|
4324
|
+
intendedBehavior: input.intendedBehavior
|
|
4325
|
+
}, selectedFiles, behaviorUnits);
|
|
4326
|
+
const riskWarnings = [];
|
|
4327
|
+
const taskTextLower = task.toLowerCase();
|
|
4328
|
+
selectedFiles.forEach((file) => {
|
|
4329
|
+
if (file.gravity && file.gravity > 70) {
|
|
4330
|
+
riskWarnings.push({
|
|
4331
|
+
id: `rw_${file.path}_high_gravity`,
|
|
4332
|
+
level: "critical",
|
|
4333
|
+
file: file.path,
|
|
4334
|
+
specialist: file.adapterDomain,
|
|
4335
|
+
message: "High-gravity file is central to the project. Modifying it may have a large blast radius.",
|
|
4336
|
+
reason: `Gravity score is ${file.gravity}.`,
|
|
4337
|
+
relatedSideEffects: file.adapterSideEffects || []
|
|
4338
|
+
});
|
|
4339
|
+
}
|
|
4340
|
+
if (file.severity && file.severity >= 4) {
|
|
4341
|
+
riskWarnings.push({
|
|
4342
|
+
id: `rw_${file.path}_high_severity`,
|
|
4343
|
+
level: "warning",
|
|
4344
|
+
file: file.path,
|
|
4345
|
+
specialist: file.adapterDomain,
|
|
4346
|
+
message: "High-severity smells detected in this file.",
|
|
4347
|
+
reason: `Smell severity score is ${file.severity}.`,
|
|
4348
|
+
relatedSideEffects: []
|
|
4349
|
+
});
|
|
4350
|
+
}
|
|
4351
|
+
if (file.adapterSideEffects && file.adapterSideEffects.length > 0) {
|
|
4352
|
+
const hasSecurity = file.adapterSideEffects.some((se) => {
|
|
4353
|
+
const sel = se.toLowerCase();
|
|
4354
|
+
return sel.includes("auth") || sel.includes("credential") || sel.includes("webhook");
|
|
4355
|
+
});
|
|
4356
|
+
if (hasSecurity) {
|
|
4357
|
+
riskWarnings.push({
|
|
4358
|
+
id: `rw_${file.path}_security_side_effects`,
|
|
4359
|
+
level: "critical",
|
|
4360
|
+
file: file.path,
|
|
4361
|
+
specialist: file.adapterDomain,
|
|
4362
|
+
message: "Security-sensitive side effects detected. Do not modify credentials or auth configuration.",
|
|
4363
|
+
reason: `File side effects: ${file.adapterSideEffects.join(", ")}`,
|
|
4364
|
+
relatedSideEffects: file.adapterSideEffects
|
|
4365
|
+
});
|
|
4366
|
+
}
|
|
4367
|
+
}
|
|
4368
|
+
});
|
|
4369
|
+
if (taskTextLower.includes("auth") || taskTextLower.includes("credential") || taskTextLower.includes("password")) {
|
|
4370
|
+
riskWarnings.push({
|
|
4371
|
+
id: "rw_task_security_sensitive",
|
|
4372
|
+
level: "critical",
|
|
4373
|
+
message: "Do not modify credentials or auth configuration unless explicitly required.",
|
|
4374
|
+
reason: "Security or credential keyword match in task description.",
|
|
4375
|
+
relatedSideEffects: []
|
|
4376
|
+
});
|
|
4377
|
+
}
|
|
4378
|
+
const smallestSafeChangePolicy = {
|
|
4379
|
+
summary: "Make the smallest localized change that addresses the requested fault. Do not modify unrelated files, triggers, credentials, auth configuration, or neighboring workflow nodes unless this packet explicitly lists them as required.",
|
|
4380
|
+
rules: [
|
|
4381
|
+
"Locate the exact failure site before making edits.",
|
|
4382
|
+
"Do not perform general cleanup or refactoring.",
|
|
4383
|
+
"Verify modifications against existing contracts and Jest fixtures."
|
|
4384
|
+
],
|
|
4385
|
+
filesToAvoid: selectedFiles.filter((f) => f.blastRadius === "high").map((f) => f.path),
|
|
4386
|
+
forbiddenEditClasses: ["credentials", "auth_configuration", "global_refactoring"]
|
|
4387
|
+
};
|
|
4388
|
+
const registry = getSpecialistRegistry();
|
|
4389
|
+
const scopePolicy = [];
|
|
4390
|
+
const scopedPackets = [];
|
|
4391
|
+
const stubAdvisories = [];
|
|
4392
|
+
const evidence = [
|
|
4393
|
+
{
|
|
4394
|
+
kind: "user_file",
|
|
4395
|
+
summary: `User provided task: "${task}"`,
|
|
4396
|
+
confidence: "high"
|
|
4397
|
+
}
|
|
4398
|
+
];
|
|
4399
|
+
if (route.primarySpecialist !== "generic_repo") {
|
|
4400
|
+
evidence.push({
|
|
4401
|
+
kind: "keyword",
|
|
4402
|
+
summary: `Deterministic router mapped task to specialist panel with confidence ${route.confidence}.`,
|
|
4403
|
+
confidence: "high"
|
|
4404
|
+
});
|
|
4405
|
+
} else {
|
|
4406
|
+
evidence.push({
|
|
4407
|
+
kind: "fallback",
|
|
4408
|
+
summary: "Deterministic router fell back to generic_repo due to low domain score.",
|
|
4409
|
+
confidence: "low"
|
|
4410
|
+
});
|
|
4411
|
+
}
|
|
4412
|
+
const packetPlaceholder = {
|
|
4413
|
+
packetVersion: "expert_network_v0",
|
|
4414
|
+
createdAt,
|
|
4415
|
+
projectRoot,
|
|
4416
|
+
task,
|
|
4417
|
+
adapterId,
|
|
4418
|
+
route,
|
|
4419
|
+
selectedFiles,
|
|
4420
|
+
behaviorUnits,
|
|
4421
|
+
pathExamples: [],
|
|
4422
|
+
riskWarnings,
|
|
4423
|
+
scopePolicy: [],
|
|
4424
|
+
corpusQuality: [],
|
|
4425
|
+
smallestSafeChangePolicy,
|
|
4426
|
+
scopedPackets: [],
|
|
4427
|
+
stubAdvisories: [],
|
|
4428
|
+
evidence
|
|
4429
|
+
};
|
|
4430
|
+
for (const profile of registry) {
|
|
4431
|
+
const scopedPacket = slicePacketForSpecialist(packetPlaceholder, profile);
|
|
4432
|
+
scopePolicy.push(scopedPacket.scopePolicy);
|
|
4433
|
+
scopedPackets.push(scopedPacket);
|
|
4434
|
+
const inPanel = route.panel.includes(profile.id);
|
|
4435
|
+
let advisory;
|
|
4436
|
+
if (inPanel) {
|
|
4437
|
+
advisory = {
|
|
4438
|
+
specialist: profile.id,
|
|
4439
|
+
scopeStatus: "in_scope",
|
|
4440
|
+
faultHypothesis: `The issue likely resides in the ${profile.label} domain, specifically affecting the task: "${task}".`,
|
|
4441
|
+
contractObserved: "Dynamic contract observation not computed in Phase 1.",
|
|
4442
|
+
contractExpected: "Refer to node schemas or Jest test fixtures for contract expectations.",
|
|
4443
|
+
recommendedLocalChange: `Perform a localized fix targeting the ${profile.label} components. Make the smallest safe change and avoid modifying unrelated nodes.`,
|
|
4444
|
+
filesToEdit: scopedPacket.selectedFiles.map((f) => f.path),
|
|
4445
|
+
filesToAvoid: smallestSafeChangePolicy.filesToAvoid,
|
|
4446
|
+
mustNotChange: scopedPacket.scopePolicy.forbiddenFiles,
|
|
4447
|
+
handoffTo: [],
|
|
4448
|
+
evidence: [
|
|
4449
|
+
{
|
|
4450
|
+
kind: "keyword",
|
|
4451
|
+
summary: `Routed to ${profile.label} due to deterministic keyword match in task text.`,
|
|
4452
|
+
confidence: "medium"
|
|
4453
|
+
}
|
|
4454
|
+
],
|
|
4455
|
+
confidence: route.confidence
|
|
4456
|
+
};
|
|
4457
|
+
} else {
|
|
4458
|
+
advisory = {
|
|
4459
|
+
specialist: profile.id,
|
|
4460
|
+
scopeStatus: "out_of_scope",
|
|
4461
|
+
filesToEdit: [],
|
|
4462
|
+
filesToAvoid: [],
|
|
4463
|
+
mustNotChange: [],
|
|
4464
|
+
handoffTo: profile.id === "generic_repo" ? [] : [route.primarySpecialist],
|
|
4465
|
+
evidence: [],
|
|
4466
|
+
confidence: "low"
|
|
4467
|
+
};
|
|
4468
|
+
}
|
|
4469
|
+
const validation = validateAdvisory(advisory, scopedPacket, profile);
|
|
4470
|
+
if (!validation.valid) {
|
|
4471
|
+
console.error(`[vibe-splain] Generated stub advisory failed validation for ${profile.id}:`, validation.errors.join("; "));
|
|
4472
|
+
}
|
|
4473
|
+
stubAdvisories.push(advisory);
|
|
4474
|
+
}
|
|
4475
|
+
const corpusQuality = registry.map((p) => {
|
|
4476
|
+
return {
|
|
4477
|
+
domain: p.id,
|
|
4478
|
+
score: p.id === "generic_repo" ? 0 : 50,
|
|
4479
|
+
state: p.id === "generic_repo" ? "not_computed" : "pilot_trainable",
|
|
4480
|
+
behaviorUnitCount: 0,
|
|
4481
|
+
highConfidenceCount: 0,
|
|
4482
|
+
topGravityCoverage: 0,
|
|
4483
|
+
typeSchemaCoverage: 0,
|
|
4484
|
+
validationStrength: 0,
|
|
4485
|
+
notes: ["Phase 1 placeholder quality score. Builder not executed."]
|
|
4486
|
+
};
|
|
4487
|
+
});
|
|
4488
|
+
return {
|
|
4489
|
+
packetVersion: "expert_network_v0",
|
|
4490
|
+
createdAt,
|
|
4491
|
+
projectRoot,
|
|
4492
|
+
task,
|
|
4493
|
+
adapterId,
|
|
4494
|
+
route,
|
|
4495
|
+
selectedFiles,
|
|
4496
|
+
behaviorUnits,
|
|
4497
|
+
pathExamples: [],
|
|
4498
|
+
riskWarnings,
|
|
4499
|
+
scopePolicy,
|
|
4500
|
+
corpusQuality,
|
|
4501
|
+
smallestSafeChangePolicy,
|
|
4502
|
+
scopedPackets,
|
|
4503
|
+
stubAdvisories,
|
|
4504
|
+
evidence
|
|
4505
|
+
};
|
|
4506
|
+
}
|
|
4507
|
+
|
|
4508
|
+
// ../brain/dist/network/gateIndex.js
|
|
4509
|
+
function isDenoisedImporter(relPath) {
|
|
4510
|
+
if (!relPath)
|
|
4511
|
+
return false;
|
|
4512
|
+
const norm = relPath.replace(/\\/g, "/");
|
|
4513
|
+
const segs = norm.split("/");
|
|
4514
|
+
const excludedDirs = /* @__PURE__ */ new Set([
|
|
4515
|
+
"node_modules",
|
|
4516
|
+
"vendor",
|
|
4517
|
+
"vendored",
|
|
4518
|
+
"site-packages",
|
|
4519
|
+
"third_party",
|
|
4520
|
+
"third-party",
|
|
4521
|
+
".yarn",
|
|
4522
|
+
"bower_components",
|
|
4523
|
+
"venv",
|
|
4524
|
+
".venv",
|
|
4525
|
+
"env",
|
|
4526
|
+
"virtualenv",
|
|
4527
|
+
".git",
|
|
4528
|
+
".vibe-splainer",
|
|
4529
|
+
"dist",
|
|
4530
|
+
"build",
|
|
4531
|
+
"out",
|
|
4532
|
+
"target",
|
|
4533
|
+
".next",
|
|
4534
|
+
".nuxt",
|
|
4535
|
+
".docusaurus",
|
|
4536
|
+
"coverage",
|
|
4537
|
+
".nyc_output",
|
|
4538
|
+
".cache"
|
|
4539
|
+
]);
|
|
4540
|
+
for (const s of segs) {
|
|
4541
|
+
const sLower = s.toLowerCase();
|
|
4542
|
+
if (excludedDirs.has(sLower)) {
|
|
4543
|
+
return false;
|
|
4544
|
+
}
|
|
4545
|
+
if (sLower.endsWith(".venv")) {
|
|
4546
|
+
return false;
|
|
4547
|
+
}
|
|
4548
|
+
}
|
|
4549
|
+
const fileName = segs[segs.length - 1];
|
|
4550
|
+
const fileNameLower = fileName.toLowerCase();
|
|
4551
|
+
if (/\.min\.[a-z]+$/.test(fileNameLower) || fileNameLower.includes(".min.")) {
|
|
4552
|
+
return false;
|
|
4553
|
+
}
|
|
4554
|
+
if (/\.generated\./.test(fileNameLower) || fileNameLower.includes("__generated__")) {
|
|
4555
|
+
return false;
|
|
4556
|
+
}
|
|
4557
|
+
if (fileNameLower.endsWith(".lock")) {
|
|
4558
|
+
return false;
|
|
4559
|
+
}
|
|
4560
|
+
if (norm.startsWith("virtual:") || norm.startsWith("__virtual:") || norm.startsWith("webpack:")) {
|
|
4561
|
+
return false;
|
|
4562
|
+
}
|
|
4563
|
+
return true;
|
|
4564
|
+
}
|
|
4565
|
+
function hasBehavioralSubstance(file) {
|
|
4566
|
+
const isTypeDefinition = file.frameworkRole === "type_definition";
|
|
4567
|
+
const cyclomatic = file.gravitySignals?.cyclomatic ?? 0;
|
|
4568
|
+
const hasSideEffects = (file.sideEffectProfile ?? []).some((e) => e !== "none_detected");
|
|
4569
|
+
const hasStrongRiskType = (file.riskTypes ?? []).some((r) => r === "state_machine" || r === "mutation_orchestration" || r === "registry_bottleneck" || r === "side_effect_coupling");
|
|
4570
|
+
return !isTypeDefinition && (cyclomatic >= 5 || hasSideEffects || hasStrongRiskType);
|
|
4571
|
+
}
|
|
4572
|
+
function buildGateIndex(store) {
|
|
4573
|
+
const files = {};
|
|
4574
|
+
if (store && store.files) {
|
|
4575
|
+
for (const [key, file] of Object.entries(store.files)) {
|
|
4576
|
+
const dependents = (file.importedBy ?? []).filter(isDenoisedImporter);
|
|
4577
|
+
const sideEffects = (file.sideEffectProfile ?? []).filter((e) => e !== "none_detected");
|
|
4578
|
+
files[key] = {
|
|
4579
|
+
relativePath: file.relativePath,
|
|
4580
|
+
gravity: file.gravity ?? 0,
|
|
4581
|
+
demoteReason: file.demoteReason,
|
|
4582
|
+
hasBehavioralSubstance: hasBehavioralSubstance(file),
|
|
4583
|
+
dependents,
|
|
4584
|
+
fanIn: file.gravitySignals?.fanIn ?? dependents.length,
|
|
4585
|
+
fanOut: file.gravitySignals?.fanOut ?? 0,
|
|
4586
|
+
centrality: file.gravitySignals?.centrality ?? 0,
|
|
4587
|
+
severity: file.canonicalSeverity ?? 1,
|
|
4588
|
+
sideEffects,
|
|
4589
|
+
riskTypes: file.riskTypes ?? []
|
|
4590
|
+
};
|
|
4591
|
+
}
|
|
4592
|
+
}
|
|
4593
|
+
return { files };
|
|
4594
|
+
}
|
|
4595
|
+
|
|
4596
|
+
// ../brain/dist/network/escalation.js
|
|
4597
|
+
var SECURITY_PATH = /\b(auth|credential|secret|token|webhook|payment|password|oauth|session)\b/i;
|
|
4598
|
+
var SENSITIVE_EFFECTS = /* @__PURE__ */ new Set([
|
|
4599
|
+
"database_write",
|
|
4600
|
+
"server_action",
|
|
4601
|
+
"trpc_mutation",
|
|
4602
|
+
"email_send",
|
|
4603
|
+
"external_api_call"
|
|
4604
|
+
]);
|
|
4605
|
+
var NOTABLE_RISKS = /* @__PURE__ */ new Set([
|
|
4606
|
+
"state_machine",
|
|
4607
|
+
"mutation_orchestration",
|
|
4608
|
+
"side_effect_coupling",
|
|
4609
|
+
"async_race_risk",
|
|
4610
|
+
"registry_bottleneck",
|
|
4611
|
+
"error_swallowing"
|
|
4612
|
+
]);
|
|
4613
|
+
function buildEscalationContext(targetFile, gateIndex) {
|
|
4614
|
+
const file = lookupFile(targetFile, gateIndex);
|
|
4615
|
+
if (!file)
|
|
4616
|
+
return null;
|
|
4617
|
+
const rel = file.relativePath;
|
|
4618
|
+
const gravity = file.gravity;
|
|
4619
|
+
const dependents = file.dependents;
|
|
4620
|
+
const dependentsCount = dependents.length;
|
|
4621
|
+
const sideEffects = file.sideEffects;
|
|
4622
|
+
const riskTypes = file.riskTypes;
|
|
4623
|
+
const severity = file.severity;
|
|
4624
|
+
const gravity_tier = gravity > 70 ? "high" : gravity > 40 ? "medium" : "low";
|
|
4625
|
+
const raw_substance_tier = file.hasBehavioralSubstance && dependentsCount >= 10 ? "high" : file.hasBehavioralSubstance && dependentsCount >= 4 ? "medium" : "low";
|
|
4626
|
+
const tierOrder = { low: 1, medium: 2, high: 3 };
|
|
4627
|
+
let blastRadius = tierOrder[gravity_tier] >= tierOrder[raw_substance_tier] ? gravity_tier : raw_substance_tier;
|
|
4628
|
+
const isGeneratedOrVendored = !isDenoisedImporter(targetFile) || file.demoteReason !== null && file.demoteReason !== "no inbound references from application code";
|
|
4629
|
+
if (isGeneratedOrVendored) {
|
|
4630
|
+
blastRadius = "low";
|
|
4631
|
+
}
|
|
4632
|
+
const riskWarnings = buildRiskWarnings(rel, gravity, dependentsCount, severity, sideEffects, riskTypes);
|
|
4633
|
+
return {
|
|
4634
|
+
targetFile: rel,
|
|
4635
|
+
gravity,
|
|
4636
|
+
blastRadius,
|
|
4637
|
+
dependents,
|
|
4638
|
+
dependentCount: dependentsCount,
|
|
4639
|
+
fanIn: file.fanIn,
|
|
4640
|
+
fanOut: file.fanOut,
|
|
4641
|
+
centrality: file.centrality,
|
|
4642
|
+
severity,
|
|
4643
|
+
sideEffects,
|
|
4644
|
+
riskTypes,
|
|
4645
|
+
riskWarnings,
|
|
4646
|
+
smallestSafeChange: buildSafeChangePolicy(blastRadius)
|
|
4647
|
+
};
|
|
4648
|
+
}
|
|
4649
|
+
function lookupFile(targetFile, gateIndex) {
|
|
4650
|
+
const files = gateIndex?.files;
|
|
4651
|
+
if (!files)
|
|
4652
|
+
return null;
|
|
4653
|
+
const norm = targetFile.replace(/\\/g, "/");
|
|
4654
|
+
if (files[norm])
|
|
4655
|
+
return files[norm];
|
|
4656
|
+
const suffixHits = [];
|
|
4657
|
+
for (const [key, rec] of Object.entries(files)) {
|
|
4658
|
+
if (norm === key || norm.endsWith("/" + key) || key.endsWith("/" + norm)) {
|
|
4659
|
+
suffixHits.push(rec);
|
|
4660
|
+
}
|
|
4661
|
+
}
|
|
4662
|
+
return suffixHits.length === 1 ? suffixHits[0] : null;
|
|
4663
|
+
}
|
|
4664
|
+
function buildRiskWarnings(rel, gravity, dependentCount, severity, sideEffects, riskTypes) {
|
|
4665
|
+
const warnings = [];
|
|
4666
|
+
if (gravity > 70) {
|
|
4667
|
+
warnings.push({
|
|
4668
|
+
id: `rw_${rel}_blast_radius`,
|
|
4669
|
+
level: "critical",
|
|
4670
|
+
message: `Central file \u2014 editing it has a large blast radius. ${dependentCount} file(s) depend on it.`,
|
|
4671
|
+
reason: `Gravity ${gravity}/100; fan-in ${dependentCount}.`
|
|
4672
|
+
});
|
|
4673
|
+
}
|
|
4674
|
+
if (severity >= 4) {
|
|
4675
|
+
warnings.push({
|
|
4676
|
+
id: `rw_${rel}_severity`,
|
|
4677
|
+
level: "warning",
|
|
4678
|
+
message: "High-severity smells already present here. Avoid making them worse.",
|
|
4679
|
+
reason: `Canonical severity ${severity}/5.`
|
|
4680
|
+
});
|
|
4681
|
+
}
|
|
4682
|
+
if (SECURITY_PATH.test(rel)) {
|
|
4683
|
+
warnings.push({
|
|
4684
|
+
id: `rw_${rel}_security_path`,
|
|
4685
|
+
level: "critical",
|
|
4686
|
+
message: "Security-sensitive file (auth/credential/webhook/payment). Do not alter auth or credential handling unless that is the explicit task.",
|
|
4687
|
+
reason: `Path matches security-sensitive pattern.`
|
|
4688
|
+
});
|
|
4689
|
+
}
|
|
4690
|
+
const sensitive = sideEffects.filter((e) => SENSITIVE_EFFECTS.has(e));
|
|
4691
|
+
if (sensitive.length > 0) {
|
|
4692
|
+
warnings.push({
|
|
4693
|
+
id: `rw_${rel}_side_effects`,
|
|
4694
|
+
level: "warning",
|
|
4695
|
+
message: `This file performs side effects (${sensitive.join(", ")}). Preserve existing behavior; do not drop or reorder them.`,
|
|
4696
|
+
reason: `Side-effect profile: ${sensitive.join(", ")}.`
|
|
4697
|
+
});
|
|
4698
|
+
}
|
|
4699
|
+
const notable = riskTypes.filter((r) => NOTABLE_RISKS.has(r));
|
|
4700
|
+
if (notable.length > 0) {
|
|
4701
|
+
warnings.push({
|
|
4702
|
+
id: `rw_${rel}_risk_types`,
|
|
4703
|
+
level: "info",
|
|
4704
|
+
message: `Structural risk patterns detected (${notable.join(", ")}). Trace the affected paths before editing.`,
|
|
4705
|
+
reason: `Risk types: ${notable.join(", ")}.`
|
|
4706
|
+
});
|
|
4707
|
+
}
|
|
4708
|
+
return warnings;
|
|
4709
|
+
}
|
|
4710
|
+
function buildSafeChangePolicy(blastRadius) {
|
|
4711
|
+
const rules = [
|
|
4712
|
+
"Locate the exact failure site before editing.",
|
|
4713
|
+
"Make the smallest localized change that addresses the task.",
|
|
4714
|
+
"Do not perform general cleanup or refactoring.",
|
|
4715
|
+
"Do not modify unrelated files, credentials, or auth configuration."
|
|
4716
|
+
];
|
|
4717
|
+
if (blastRadius === "high") {
|
|
4718
|
+
rules.push("This file is load-bearing: verify each dependent still type-checks against the changed surface.");
|
|
4719
|
+
}
|
|
4720
|
+
return {
|
|
4721
|
+
summary: "Make the smallest localized change that addresses the requested task. Do not modify unrelated files, credentials, auth configuration, or neighboring modules unless the task explicitly requires it.",
|
|
4722
|
+
rules,
|
|
4723
|
+
forbiddenEditClasses: ["credentials", "auth_configuration", "global_refactoring"]
|
|
4724
|
+
};
|
|
3346
4725
|
}
|
|
3347
4726
|
|
|
3348
4727
|
// dist/export/ArtifactBundleWriter.js
|
|
3349
4728
|
import { join as join10 } from "path";
|
|
3350
|
-
import { writeFile as
|
|
3351
|
-
import { createHash
|
|
4729
|
+
import { writeFile as writeFile6, mkdir as mkdir6, rm, rename } from "fs/promises";
|
|
4730
|
+
import { createHash } from "crypto";
|
|
3352
4731
|
var ArtifactBundleWriter = class {
|
|
3353
4732
|
projectRoot;
|
|
3354
4733
|
constructor(projectRoot) {
|
|
@@ -3361,9 +4740,9 @@ var ArtifactBundleWriter = class {
|
|
|
3361
4740
|
try {
|
|
3362
4741
|
await rm(stagingDir, { recursive: true, force: true });
|
|
3363
4742
|
await rm(oldDir, { recursive: true, force: true });
|
|
3364
|
-
const { existsSync:
|
|
4743
|
+
const { existsSync: existsSync8 } = await import("fs");
|
|
3365
4744
|
const { cp } = await import("fs/promises");
|
|
3366
|
-
if (
|
|
4745
|
+
if (existsSync8(outputDir)) {
|
|
3367
4746
|
await cp(outputDir, stagingDir, { recursive: true });
|
|
3368
4747
|
} else {
|
|
3369
4748
|
await mkdir6(stagingDir, { recursive: true });
|
|
@@ -3372,13 +4751,13 @@ var ArtifactBundleWriter = class {
|
|
|
3372
4751
|
for (const artifact of artifacts) {
|
|
3373
4752
|
const destPath = join10(stagingDir, artifact.path);
|
|
3374
4753
|
await mkdir6(join10(destPath, ".."), { recursive: true });
|
|
3375
|
-
await
|
|
4754
|
+
await writeFile6(destPath, artifact.content);
|
|
3376
4755
|
const contentStr = artifact.content;
|
|
3377
4756
|
const buffer = typeof contentStr === "string" ? Buffer.from(contentStr, "utf-8") : contentStr;
|
|
3378
4757
|
manifestArtifacts.push({
|
|
3379
4758
|
type: artifact.type,
|
|
3380
4759
|
path: artifact.path,
|
|
3381
|
-
checksum: "sha256:" +
|
|
4760
|
+
checksum: "sha256:" + createHash("sha256").update(buffer).digest("hex"),
|
|
3382
4761
|
sizeBytes: buffer.length
|
|
3383
4762
|
});
|
|
3384
4763
|
}
|
|
@@ -3388,9 +4767,9 @@ var ArtifactBundleWriter = class {
|
|
|
3388
4767
|
projectRoot: this.projectRoot,
|
|
3389
4768
|
artifacts: manifestArtifacts
|
|
3390
4769
|
};
|
|
3391
|
-
await
|
|
4770
|
+
await writeFile6(join10(stagingDir, "artifact_manifest.json"), JSON.stringify(manifest, null, 2), "utf8");
|
|
3392
4771
|
let swapped = false;
|
|
3393
|
-
if (
|
|
4772
|
+
if (existsSync8(outputDir)) {
|
|
3394
4773
|
await rename(outputDir, oldDir);
|
|
3395
4774
|
swapped = true;
|
|
3396
4775
|
}
|
|
@@ -3426,10 +4805,10 @@ var JsonRenderer = class {
|
|
|
3426
4805
|
};
|
|
3427
4806
|
|
|
3428
4807
|
// dist/export/renderers/HtmlRenderer.js
|
|
3429
|
-
import { join as join11, dirname as
|
|
3430
|
-
import { fileURLToPath as
|
|
3431
|
-
import { existsSync as existsSync5, readFileSync, readdirSync, statSync } from "fs";
|
|
3432
|
-
var __dirname2 =
|
|
4808
|
+
import { join as join11, dirname as dirname4, relative as relative3 } from "path";
|
|
4809
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
4810
|
+
import { existsSync as existsSync5, readFileSync as readFileSync2, readdirSync, statSync } from "fs";
|
|
4811
|
+
var __dirname2 = dirname4(fileURLToPath3(import.meta.url));
|
|
3433
4812
|
function getAllFiles(dirPath, arrayOfFiles = []) {
|
|
3434
4813
|
const files = readdirSync(dirPath);
|
|
3435
4814
|
files.forEach(function(file) {
|
|
@@ -3482,7 +4861,7 @@ var HtmlRenderer = class {
|
|
|
3482
4861
|
for (const file of allFiles) {
|
|
3483
4862
|
const relPath = relative3(templateDir, file);
|
|
3484
4863
|
if (relPath === "index.html") {
|
|
3485
|
-
const templateHtml =
|
|
4864
|
+
const templateHtml = readFileSync2(file, "utf8");
|
|
3486
4865
|
const injection = `<script>window.__VIBE_DOSSIER__ = ${JSON.stringify(viewModel)};</script>`;
|
|
3487
4866
|
const bakedHtml = templateHtml.replace("<!-- VIBE_DOSSIER_INJECTION_POINT -->", injection);
|
|
3488
4867
|
artifacts.push({
|
|
@@ -3494,41 +4873,19 @@ var HtmlRenderer = class {
|
|
|
3494
4873
|
artifacts.push({
|
|
3495
4874
|
type: "asset",
|
|
3496
4875
|
path: join11("ui", relPath),
|
|
3497
|
-
content:
|
|
4876
|
+
content: readFileSync2(file)
|
|
3498
4877
|
});
|
|
3499
4878
|
}
|
|
3500
|
-
}
|
|
3501
|
-
return artifacts;
|
|
3502
|
-
}
|
|
3503
|
-
};
|
|
3504
|
-
|
|
3505
|
-
// dist/export/renderers/DeltaRenderer.js
|
|
3506
|
-
var DeltaRenderer = class {
|
|
3507
|
-
render(_viewModel, store) {
|
|
3508
|
-
const deltaTargets = Object.values(store.files).filter((pf) => pf.isRealSource).sort((a, b) => b.gravity - a.gravity).map((pf) => ({
|
|
3509
|
-
path: pf.relativePath,
|
|
3510
|
-
gravity: Math.round(pf.gravity),
|
|
3511
|
-
isLoadBearing: pf.canonicalLoadBearing,
|
|
3512
|
-
blastRadius: pf.importedBy,
|
|
3513
|
-
pillarHint: pf.pillarHint
|
|
3514
|
-
}));
|
|
3515
|
-
return [
|
|
3516
|
-
{
|
|
3517
|
-
type: "delta",
|
|
3518
|
-
path: "delta_targets.json",
|
|
3519
|
-
content: JSON.stringify(deltaTargets, null, 2)
|
|
3520
|
-
}
|
|
3521
|
-
];
|
|
4879
|
+
}
|
|
4880
|
+
return artifacts;
|
|
3522
4881
|
}
|
|
3523
4882
|
};
|
|
3524
4883
|
|
|
3525
4884
|
// dist/export/renderers/AgentMarkdownRenderer.js
|
|
3526
4885
|
var AgentMarkdownRenderer = class {
|
|
3527
4886
|
budget;
|
|
3528
|
-
|
|
3529
|
-
constructor(budget = 8e3, bindings = null) {
|
|
4887
|
+
constructor(budget = 8e3) {
|
|
3530
4888
|
this.budget = budget;
|
|
3531
|
-
this.bindings = bindings;
|
|
3532
4889
|
}
|
|
3533
4890
|
render(viewModel, store) {
|
|
3534
4891
|
let md = `# Architectural Dossier: ${viewModel.projectRoot}
|
|
@@ -3591,23 +4948,6 @@ ${viewModel.map.brief}
|
|
|
3591
4948
|
md += `**Narrative**: ${card.narrative}
|
|
3592
4949
|
`;
|
|
3593
4950
|
}
|
|
3594
|
-
if (this.bindings && this.bindings.files[path2]) {
|
|
3595
|
-
const fileBinding = this.bindings.files[path2];
|
|
3596
|
-
const criticalFunctions = fileBinding.functions.filter((fn) => fn.semanticActions.length > 0 || fn.isEntrypoint);
|
|
3597
|
-
if (criticalFunctions.length > 0) {
|
|
3598
|
-
md += `
|
|
3599
|
-
**Critical Functions**:
|
|
3600
|
-
`;
|
|
3601
|
-
for (const fn of criticalFunctions) {
|
|
3602
|
-
md += `- \`${fn.displayName}\` (lines ${fn.startLine}-${fn.endLine})${fn.isEntrypoint ? " [Entrypoint]" : ""}
|
|
3603
|
-
`;
|
|
3604
|
-
for (const action of fn.semanticActions) {
|
|
3605
|
-
md += ` - **${action.actionKind}**${action.targetModel ? ` on ${action.targetModel}` : ""}: \`${action.calleeText}\` (line ${action.sourceLine})
|
|
3606
|
-
`;
|
|
3607
|
-
}
|
|
3608
|
-
}
|
|
3609
|
-
}
|
|
3610
|
-
}
|
|
3611
4951
|
if (recs.length > 0) {
|
|
3612
4952
|
md += `
|
|
3613
4953
|
**Safe Patch Strategies**:
|
|
@@ -3704,8 +5044,8 @@ var GraphRenderer = class {
|
|
|
3704
5044
|
};
|
|
3705
5045
|
|
|
3706
5046
|
// dist/store/BlobStore.js
|
|
3707
|
-
import { createHash as
|
|
3708
|
-
import { mkdir as mkdir7, writeFile as
|
|
5047
|
+
import { createHash as createHash2 } from "crypto";
|
|
5048
|
+
import { mkdir as mkdir7, writeFile as writeFile7, open, rename as rename2, stat } from "fs/promises";
|
|
3709
5049
|
import { existsSync as existsSync6 } from "fs";
|
|
3710
5050
|
import { join as join12 } from "path";
|
|
3711
5051
|
var BlobStore = class {
|
|
@@ -3722,14 +5062,14 @@ var BlobStore = class {
|
|
|
3722
5062
|
async writeAtomic(payload) {
|
|
3723
5063
|
await this.ensureDirs();
|
|
3724
5064
|
const buf = typeof payload === "string" ? Buffer.from(payload, "utf8") : payload;
|
|
3725
|
-
const hex =
|
|
5065
|
+
const hex = createHash2("sha256").update(buf).digest("hex");
|
|
3726
5066
|
const contentHash = `sha256:${hex}`;
|
|
3727
5067
|
const blobPath = join12(this.blobsDir, `sha256_${hex}`);
|
|
3728
5068
|
if (existsSync6(blobPath)) {
|
|
3729
5069
|
return { contentHash, blobPath };
|
|
3730
5070
|
}
|
|
3731
5071
|
const tmpPath = join12(this.tmpDir, `tmp_${hex}_${Date.now()}`);
|
|
3732
|
-
await
|
|
5072
|
+
await writeFile7(tmpPath, buf);
|
|
3733
5073
|
const fh = await open(tmpPath, "r");
|
|
3734
5074
|
try {
|
|
3735
5075
|
await fh.datasync();
|
|
@@ -3740,8 +5080,8 @@ var BlobStore = class {
|
|
|
3740
5080
|
return { contentHash, blobPath };
|
|
3741
5081
|
}
|
|
3742
5082
|
async readBlob(blobPath) {
|
|
3743
|
-
const { readFile:
|
|
3744
|
-
return
|
|
5083
|
+
const { readFile: readFile14 } = await import("fs/promises");
|
|
5084
|
+
return readFile14(blobPath);
|
|
3745
5085
|
}
|
|
3746
5086
|
async blobExists(contentHash) {
|
|
3747
5087
|
const hex = contentHash.replace("sha256:", "");
|
|
@@ -3754,9 +5094,9 @@ var BlobStore = class {
|
|
|
3754
5094
|
}
|
|
3755
5095
|
async verifyIntegrity(blobPath, expectedHash) {
|
|
3756
5096
|
try {
|
|
3757
|
-
const { readFile:
|
|
3758
|
-
const buf = await
|
|
3759
|
-
const hex =
|
|
5097
|
+
const { readFile: readFile14 } = await import("fs/promises");
|
|
5098
|
+
const buf = await readFile14(blobPath);
|
|
5099
|
+
const hex = createHash2("sha256").update(buf).digest("hex");
|
|
3760
5100
|
return `sha256:${hex}` === expectedHash;
|
|
3761
5101
|
} catch {
|
|
3762
5102
|
return false;
|
|
@@ -3765,8 +5105,8 @@ var BlobStore = class {
|
|
|
3765
5105
|
/** List all blob paths for GC reference counting */
|
|
3766
5106
|
async listBlobPaths() {
|
|
3767
5107
|
try {
|
|
3768
|
-
const { readdir:
|
|
3769
|
-
const files = await
|
|
5108
|
+
const { readdir: readdir2 } = await import("fs/promises");
|
|
5109
|
+
const files = await readdir2(this.blobsDir);
|
|
3770
5110
|
return files.filter((f) => f.startsWith("sha256_")).map((f) => join12(this.blobsDir, f));
|
|
3771
5111
|
} catch {
|
|
3772
5112
|
return [];
|
|
@@ -3782,9 +5122,9 @@ var BlobStore = class {
|
|
|
3782
5122
|
}
|
|
3783
5123
|
};
|
|
3784
5124
|
async function hashFile(filePath) {
|
|
3785
|
-
const { readFile:
|
|
3786
|
-
const buf = await
|
|
3787
|
-
return `sha256:${
|
|
5125
|
+
const { readFile: readFile14 } = await import("fs/promises");
|
|
5126
|
+
const buf = await readFile14(filePath);
|
|
5127
|
+
return `sha256:${createHash2("sha256").update(buf).digest("hex")}`;
|
|
3788
5128
|
}
|
|
3789
5129
|
|
|
3790
5130
|
// dist/store/PointerStore.js
|
|
@@ -3798,11 +5138,11 @@ var E_ALREADY_LOCKED = new Error("mutex already locked");
|
|
|
3798
5138
|
var E_CANCELED = new Error("request for lock canceled");
|
|
3799
5139
|
var __awaiter$2 = function(thisArg, _arguments, P, generator) {
|
|
3800
5140
|
function adopt(value) {
|
|
3801
|
-
return value instanceof P ? value : new P(function(
|
|
3802
|
-
|
|
5141
|
+
return value instanceof P ? value : new P(function(resolve2) {
|
|
5142
|
+
resolve2(value);
|
|
3803
5143
|
});
|
|
3804
5144
|
}
|
|
3805
|
-
return new (P || (P = Promise))(function(
|
|
5145
|
+
return new (P || (P = Promise))(function(resolve2, reject) {
|
|
3806
5146
|
function fulfilled(value) {
|
|
3807
5147
|
try {
|
|
3808
5148
|
step(generator.next(value));
|
|
@@ -3818,7 +5158,7 @@ var __awaiter$2 = function(thisArg, _arguments, P, generator) {
|
|
|
3818
5158
|
}
|
|
3819
5159
|
}
|
|
3820
5160
|
function step(result) {
|
|
3821
|
-
result.done ?
|
|
5161
|
+
result.done ? resolve2(result.value) : adopt(result.value).then(fulfilled, rejected);
|
|
3822
5162
|
}
|
|
3823
5163
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
3824
5164
|
});
|
|
@@ -3833,8 +5173,8 @@ var Semaphore = class {
|
|
|
3833
5173
|
acquire(weight = 1, priority = 0) {
|
|
3834
5174
|
if (weight <= 0)
|
|
3835
5175
|
throw new Error(`invalid weight ${weight}: must be positive`);
|
|
3836
|
-
return new Promise((
|
|
3837
|
-
const task = { resolve, reject, weight, priority };
|
|
5176
|
+
return new Promise((resolve2, reject) => {
|
|
5177
|
+
const task = { resolve: resolve2, reject, weight, priority };
|
|
3838
5178
|
const i = findIndexFromEnd(this._queue, (other) => priority <= other.priority);
|
|
3839
5179
|
if (i === -1 && weight <= this._value) {
|
|
3840
5180
|
this._dispatchItem(task);
|
|
@@ -3859,10 +5199,10 @@ var Semaphore = class {
|
|
|
3859
5199
|
if (this._couldLockImmediately(weight, priority)) {
|
|
3860
5200
|
return Promise.resolve();
|
|
3861
5201
|
} else {
|
|
3862
|
-
return new Promise((
|
|
5202
|
+
return new Promise((resolve2) => {
|
|
3863
5203
|
if (!this._weightedWaiters[weight - 1])
|
|
3864
5204
|
this._weightedWaiters[weight - 1] = [];
|
|
3865
|
-
insertSorted(this._weightedWaiters[weight - 1], { resolve, priority });
|
|
5205
|
+
insertSorted(this._weightedWaiters[weight - 1], { resolve: resolve2, priority });
|
|
3866
5206
|
});
|
|
3867
5207
|
}
|
|
3868
5208
|
}
|
|
@@ -3945,11 +5285,11 @@ function findIndexFromEnd(a, predicate) {
|
|
|
3945
5285
|
}
|
|
3946
5286
|
var __awaiter$1 = function(thisArg, _arguments, P, generator) {
|
|
3947
5287
|
function adopt(value) {
|
|
3948
|
-
return value instanceof P ? value : new P(function(
|
|
3949
|
-
|
|
5288
|
+
return value instanceof P ? value : new P(function(resolve2) {
|
|
5289
|
+
resolve2(value);
|
|
3950
5290
|
});
|
|
3951
5291
|
}
|
|
3952
|
-
return new (P || (P = Promise))(function(
|
|
5292
|
+
return new (P || (P = Promise))(function(resolve2, reject) {
|
|
3953
5293
|
function fulfilled(value) {
|
|
3954
5294
|
try {
|
|
3955
5295
|
step(generator.next(value));
|
|
@@ -3965,7 +5305,7 @@ var __awaiter$1 = function(thisArg, _arguments, P, generator) {
|
|
|
3965
5305
|
}
|
|
3966
5306
|
}
|
|
3967
5307
|
function step(result) {
|
|
3968
|
-
result.done ?
|
|
5308
|
+
result.done ? resolve2(result.value) : adopt(result.value).then(fulfilled, rejected);
|
|
3969
5309
|
}
|
|
3970
5310
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
3971
5311
|
});
|
|
@@ -4203,7 +5543,6 @@ var ExportOrchestrator = class {
|
|
|
4203
5543
|
if (!finalStore) {
|
|
4204
5544
|
throw new Error("Analysis store not found. Scan the project first.");
|
|
4205
5545
|
}
|
|
4206
|
-
const bindings = await readActionBindings(this.projectRoot);
|
|
4207
5546
|
for (const p of dossier.pillars) {
|
|
4208
5547
|
p.decisions = p.decisions.filter((c) => !(c.severity === 1 && c.category === "Convention"));
|
|
4209
5548
|
p.cardCount = p.decisions.length;
|
|
@@ -4211,12 +5550,17 @@ var ExportOrchestrator = class {
|
|
|
4211
5550
|
const viewModel = this.buildViewModel(dossier, finalStore);
|
|
4212
5551
|
const artifacts = [];
|
|
4213
5552
|
artifacts.push(...await new JsonRenderer().render(viewModel, finalStore));
|
|
4214
|
-
artifacts.push(...await new DeltaRenderer().render(viewModel, finalStore));
|
|
4215
5553
|
artifacts.push(...await new ValidationRenderer().render(viewModel, finalStore));
|
|
4216
5554
|
artifacts.push(...await new RawAnalysisRenderer().render(viewModel, finalStore));
|
|
4217
5555
|
if (graph) {
|
|
4218
5556
|
artifacts.push(...await new GraphRenderer(graph).render(viewModel, finalStore));
|
|
4219
5557
|
}
|
|
5558
|
+
const gateIndex = buildGateIndex(finalStore);
|
|
5559
|
+
artifacts.push({
|
|
5560
|
+
type: "gate",
|
|
5561
|
+
path: "gate.json",
|
|
5562
|
+
content: JSON.stringify(gateIndex, null, 2)
|
|
5563
|
+
});
|
|
4220
5564
|
const formats = ["html", "markdown"];
|
|
4221
5565
|
if (options.format && options.format !== "json" && options.format !== "delta") {
|
|
4222
5566
|
formats.length = 0;
|
|
@@ -4226,7 +5570,7 @@ var ExportOrchestrator = class {
|
|
|
4226
5570
|
artifacts.push(...await new HtmlRenderer().render(viewModel, finalStore));
|
|
4227
5571
|
}
|
|
4228
5572
|
if (formats.includes("markdown")) {
|
|
4229
|
-
artifacts.push(...await new AgentMarkdownRenderer(options.budget
|
|
5573
|
+
artifacts.push(...await new AgentMarkdownRenderer(options.budget).render(viewModel, finalStore));
|
|
4230
5574
|
}
|
|
4231
5575
|
const writer = new ArtifactBundleWriter(this.projectRoot);
|
|
4232
5576
|
await writer.writeBundle(artifacts);
|
|
@@ -4333,8 +5677,8 @@ var ExportOrchestrator = class {
|
|
|
4333
5677
|
|
|
4334
5678
|
// dist/export/Watcher.js
|
|
4335
5679
|
import chokidar from "chokidar";
|
|
4336
|
-
import { createHash as
|
|
4337
|
-
import { readFile as
|
|
5680
|
+
import { createHash as createHash3 } from "crypto";
|
|
5681
|
+
import { readFile as readFile7 } from "fs/promises";
|
|
4338
5682
|
import { join as join14 } from "path";
|
|
4339
5683
|
var activeWatchers = /* @__PURE__ */ new Map();
|
|
4340
5684
|
async function startWatcher(projectRoot, watchedPaths) {
|
|
@@ -4353,8 +5697,8 @@ async function startWatcher(projectRoot, watchedPaths) {
|
|
|
4353
5697
|
const dossier = await readDossier(projectRoot);
|
|
4354
5698
|
if (!dossier)
|
|
4355
5699
|
return;
|
|
4356
|
-
const content = await
|
|
4357
|
-
const newHash =
|
|
5700
|
+
const content = await readFile7(filepath, "utf8");
|
|
5701
|
+
const newHash = createHash3("sha256").update(content).digest("hex");
|
|
4358
5702
|
let mutated = false;
|
|
4359
5703
|
for (const pillar of dossier.pillars) {
|
|
4360
5704
|
for (const card of pillar.decisions) {
|
|
@@ -4398,11 +5742,7 @@ var scanProjectTool = {
|
|
|
4398
5742
|
required: ["projectRoot"]
|
|
4399
5743
|
}
|
|
4400
5744
|
};
|
|
4401
|
-
async function
|
|
4402
|
-
const projectRoot = args.projectRoot;
|
|
4403
|
-
if (!projectRoot)
|
|
4404
|
-
throw new Error("projectRoot is required");
|
|
4405
|
-
console.error(`[vibe-splain] Scanning project: ${projectRoot}`);
|
|
5745
|
+
async function performScan(projectRoot, options = {}) {
|
|
4406
5746
|
const result = await scanProject(projectRoot);
|
|
4407
5747
|
const existing = await readDossier(projectRoot);
|
|
4408
5748
|
const brief = existing?.map?.brief ?? null;
|
|
@@ -4435,16 +5775,22 @@ async function handleScanProject(args, options = {}) {
|
|
|
4435
5775
|
budget: options.budget ? parseInt(options.budget, 10) : void 0,
|
|
4436
5776
|
scope: options.scope
|
|
4437
5777
|
}, result.store, result.graph, scanId);
|
|
4438
|
-
|
|
5778
|
+
return { result, dossier, scanId, manifestPointer };
|
|
5779
|
+
}
|
|
5780
|
+
async function handleScanProject(args, options = {}) {
|
|
5781
|
+
const projectRoot = args.projectRoot;
|
|
5782
|
+
if (!projectRoot)
|
|
5783
|
+
throw new Error("projectRoot is required");
|
|
5784
|
+
console.error(`[vibe-splain] Scanning project: ${projectRoot}`);
|
|
5785
|
+
const { result, scanId, manifestPointer } = await performScan(projectRoot, options);
|
|
5786
|
+
if (options.watch !== false) {
|
|
5787
|
+
await startWatcher(projectRoot, result.files.map((f) => f.path));
|
|
5788
|
+
}
|
|
4439
5789
|
console.error(`[vibe-splain] Scan complete. ${result.totalFilesScanned} files, ${result.realSourceCount} real-source, ${result.wildCandidates.length} wild candidates.`);
|
|
4440
5790
|
const validation = result.validation ?? { passed: true, errors: 0, warnings: 0, reportPath: ".vibe-splainer/validation_report.json" };
|
|
4441
|
-
let statusMsg = "Scan complete.";
|
|
4442
|
-
if (!validation.passed) {
|
|
4443
|
-
statusMsg = `SCAN QUALITY WARNING: ${validation.errors} errors and ${validation.warnings} warnings found in validation report. Delta Engine automation may be blocked.`;
|
|
4444
|
-
}
|
|
4445
5791
|
return {
|
|
4446
5792
|
ok: true,
|
|
4447
|
-
message:
|
|
5793
|
+
message: "Scan complete.",
|
|
4448
5794
|
scanId,
|
|
4449
5795
|
manifestPointer,
|
|
4450
5796
|
validation: result.fullValidationReport || {
|
|
@@ -4455,7 +5801,6 @@ async function handleScanProject(args, options = {}) {
|
|
|
4455
5801
|
},
|
|
4456
5802
|
artifacts: {
|
|
4457
5803
|
analysis: ".vibe-splainer/analysis.json",
|
|
4458
|
-
deltaTargets: ".vibe-splainer/delta_targets.json",
|
|
4459
5804
|
dossier: ".vibe-splainer/dossier.json",
|
|
4460
5805
|
graph: ".vibe-splainer/graph.json",
|
|
4461
5806
|
html: ".vibe-splainer/ui/index.html"
|
|
@@ -4560,7 +5905,7 @@ async function handleSetProjectBrief(args, options = {}) {
|
|
|
4560
5905
|
}
|
|
4561
5906
|
|
|
4562
5907
|
// dist/mcp/tools/get_file_context.js
|
|
4563
|
-
import { readFile as
|
|
5908
|
+
import { readFile as readFile8 } from "fs/promises";
|
|
4564
5909
|
import { join as join15, relative as relative4, isAbsolute } from "path";
|
|
4565
5910
|
var getFileContextTool = {
|
|
4566
5911
|
name: "get_file_context",
|
|
@@ -4606,14 +5951,14 @@ async function handleGetFileContext(args) {
|
|
|
4606
5951
|
smellSpans: evidence.smellSpans
|
|
4607
5952
|
};
|
|
4608
5953
|
if (full) {
|
|
4609
|
-
result.source = await
|
|
5954
|
+
result.source = await readFile8(fullPath, "utf8");
|
|
4610
5955
|
}
|
|
4611
5956
|
return result;
|
|
4612
5957
|
}
|
|
4613
5958
|
|
|
4614
5959
|
// dist/mcp/tools/write_decision_card.js
|
|
4615
|
-
import { createHash as
|
|
4616
|
-
import { readFile as
|
|
5960
|
+
import { createHash as createHash4 } from "crypto";
|
|
5961
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
4617
5962
|
import { join as join16 } from "path";
|
|
4618
5963
|
var CATEGORIES = ["Bottleneck", "Hack", "Smart-Move", "Risk", "Convention", "Dead-Weight"];
|
|
4619
5964
|
function normalizeSnippet(s) {
|
|
@@ -4711,10 +6056,10 @@ async function handleWriteDecisionCard(args, options = {}) {
|
|
|
4711
6056
|
const heat = persisted ? Math.round(persisted.heat) : void 0;
|
|
4712
6057
|
let primaryContent = "";
|
|
4713
6058
|
try {
|
|
4714
|
-
primaryContent = await
|
|
6059
|
+
primaryContent = await readFile9(join16(projectRoot, primaryFile), "utf8");
|
|
4715
6060
|
} catch {
|
|
4716
6061
|
}
|
|
4717
|
-
const hash =
|
|
6062
|
+
const hash = createHash4("sha256").update(primaryContent).digest("hex");
|
|
4718
6063
|
const card = {
|
|
4719
6064
|
id: v4_default(),
|
|
4720
6065
|
pillar,
|
|
@@ -4941,57 +6286,8 @@ async function handleMarkStale(args, options = {}) {
|
|
|
4941
6286
|
};
|
|
4942
6287
|
}
|
|
4943
6288
|
|
|
4944
|
-
// dist/mcp/tools/get_call_chain.js
|
|
4945
|
-
var getCallChainTool = {
|
|
4946
|
-
name: "get_call_chain",
|
|
4947
|
-
description: `Trace how behavior is reached from an entrypoint by following function call edges through the codebase. Returns a step-by-step chain with exact function names, file paths, line numbers, action kinds, and evidence text. Every edge has a confidence level; unresolved edges are listed explicitly.
|
|
4948
|
-
|
|
4949
|
-
Use structured filters when you know the target:
|
|
4950
|
-
targetModel + targetOperation: "where does Booking get created?"
|
|
4951
|
-
targetActionKind: "where is auth enforced?"
|
|
4952
|
-
targetFunctionName: "how is function X reached?"
|
|
4953
|
-
No filter returns the full call tree up to maxDepth.
|
|
4954
|
-
|
|
4955
|
-
Run scan_project first \u2014 this tool reads from the generated action_bindings.json.`,
|
|
4956
|
-
inputSchema: {
|
|
4957
|
-
type: "object",
|
|
4958
|
-
properties: {
|
|
4959
|
-
projectRoot: { type: "string" },
|
|
4960
|
-
entrypointPath: { type: "string", description: "Relative path to the entrypoint file" },
|
|
4961
|
-
maxDepth: { type: "number", description: "Max traversal depth. Default 6, max 12." },
|
|
4962
|
-
targetActionKind: { type: "string", description: "Stop at this semantic action kind." },
|
|
4963
|
-
targetModel: { type: "string", description: "Stop at functions touching this model." },
|
|
4964
|
-
targetOperation: { type: "string", description: "Narrow targetModel to this operation." },
|
|
4965
|
-
targetFunctionName: { type: "string", description: "Stop at a specific function name." },
|
|
4966
|
-
includeTests: { type: "boolean", description: "Include test files in traversal. Default false." }
|
|
4967
|
-
},
|
|
4968
|
-
required: ["projectRoot", "entrypointPath"]
|
|
4969
|
-
}
|
|
4970
|
-
};
|
|
4971
|
-
async function handleGetCallChain(args) {
|
|
4972
|
-
const projectRoot = args.projectRoot;
|
|
4973
|
-
const entrypointPath = args.entrypointPath;
|
|
4974
|
-
if (!projectRoot || !entrypointPath)
|
|
4975
|
-
throw new Error("projectRoot and entrypointPath are required");
|
|
4976
|
-
const getCallChainArgs = {
|
|
4977
|
-
entrypointPath,
|
|
4978
|
-
maxDepth: typeof args.maxDepth === "number" ? args.maxDepth : void 0,
|
|
4979
|
-
targetActionKind: typeof args.targetActionKind === "string" ? args.targetActionKind : void 0,
|
|
4980
|
-
targetModel: typeof args.targetModel === "string" ? args.targetModel : void 0,
|
|
4981
|
-
targetOperation: typeof args.targetOperation === "string" ? args.targetOperation : void 0,
|
|
4982
|
-
targetFunctionName: typeof args.targetFunctionName === "string" ? args.targetFunctionName : void 0,
|
|
4983
|
-
includeTests: typeof args.includeTests === "boolean" ? args.includeTests : void 0
|
|
4984
|
-
};
|
|
4985
|
-
try {
|
|
4986
|
-
const result = await traverseCallChain(projectRoot, getCallChainArgs);
|
|
4987
|
-
return result;
|
|
4988
|
-
} catch (error) {
|
|
4989
|
-
throw new Error(`get_call_chain failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
4990
|
-
}
|
|
4991
|
-
}
|
|
4992
|
-
|
|
4993
6289
|
// dist/mcp/tools/get_file_skeleton.js
|
|
4994
|
-
import { readFile as
|
|
6290
|
+
import { readFile as readFile10 } from "fs/promises";
|
|
4995
6291
|
import { join as join17 } from "path";
|
|
4996
6292
|
|
|
4997
6293
|
// ../../node_modules/balanced-match/dist/esm/index.js
|
|
@@ -6948,7 +8244,7 @@ async function handleGetFileSkeleton(args) {
|
|
|
6948
8244
|
filePath
|
|
6949
8245
|
};
|
|
6950
8246
|
}
|
|
6951
|
-
const source = await
|
|
8247
|
+
const source = await readFile10(absolutePath, "utf8");
|
|
6952
8248
|
const skeleton = extractSkeleton(source, filePath);
|
|
6953
8249
|
const skeletonPayload = {
|
|
6954
8250
|
filePath,
|
|
@@ -6999,7 +8295,7 @@ function extractSkeleton(source, filePath) {
|
|
|
6999
8295
|
}
|
|
7000
8296
|
|
|
7001
8297
|
// dist/mcp/tools/read_file.js
|
|
7002
|
-
import { readFile as
|
|
8298
|
+
import { readFile as readFile11 } from "fs/promises";
|
|
7003
8299
|
import { join as join18 } from "path";
|
|
7004
8300
|
var readFileTool = {
|
|
7005
8301
|
name: "read_file",
|
|
@@ -7035,7 +8331,7 @@ async function handleReadFile(args) {
|
|
|
7035
8331
|
const absolutePath = filePath.startsWith("/") ? filePath : join18(projectRoot, filePath);
|
|
7036
8332
|
let content;
|
|
7037
8333
|
try {
|
|
7038
|
-
content = await
|
|
8334
|
+
content = await readFile11(absolutePath, "utf8");
|
|
7039
8335
|
} catch {
|
|
7040
8336
|
throw new Error(`FileNotFound: cannot read ${filePath}`);
|
|
7041
8337
|
}
|
|
@@ -7073,96 +8369,6 @@ async function handleReadFile(args) {
|
|
|
7073
8369
|
return await applyBudgetGuard(projectRoot, scanId, "file_read", result);
|
|
7074
8370
|
}
|
|
7075
8371
|
|
|
7076
|
-
// dist/mcp/tools/apply_patch.js
|
|
7077
|
-
import { writeFile as writeFile9, rename as rename3, mkdir as mkdir8 } from "fs/promises";
|
|
7078
|
-
import { join as join19, dirname as dirname4 } from "path";
|
|
7079
|
-
var StalePatchError = class extends Error {
|
|
7080
|
-
filePath;
|
|
7081
|
-
expectedHash;
|
|
7082
|
-
actualHash;
|
|
7083
|
-
constructor(filePath, expectedHash, actualHash) {
|
|
7084
|
-
super(`StalePatchError: ${filePath} hash mismatch \u2014 expected ${expectedHash}, got ${actualHash}. File was modified since the expectedPrePatchHash was computed. Re-read the file and regenerate the patch.`);
|
|
7085
|
-
this.filePath = filePath;
|
|
7086
|
-
this.expectedHash = expectedHash;
|
|
7087
|
-
this.actualHash = actualHash;
|
|
7088
|
-
this.name = "StalePatchError";
|
|
7089
|
-
}
|
|
7090
|
-
};
|
|
7091
|
-
var applyPatchTool = {
|
|
7092
|
-
name: "apply_patch",
|
|
7093
|
-
description: "Applies a text patch to a file within the active workOrder scope. Requires expectedPrePatchHash to prevent stale-patch corruption. Records pre- and post-patch hashes.",
|
|
7094
|
-
inputSchema: {
|
|
7095
|
-
type: "object",
|
|
7096
|
-
properties: {
|
|
7097
|
-
projectRoot: { type: "string", description: "Absolute project root" },
|
|
7098
|
-
filePath: { type: "string", description: "Path relative to projectRoot" },
|
|
7099
|
-
newContent: { type: "string", description: "Full new content of the file after the patch" },
|
|
7100
|
-
expectedPrePatchHash: {
|
|
7101
|
-
type: "string",
|
|
7102
|
-
description: "sha256:<hex> hash of the file BEFORE patching. Obtain via hashFile or the sourceHash from get_file_skeleton."
|
|
7103
|
-
},
|
|
7104
|
-
scanId: { type: "string", description: "Current scan ID for pointer registration" }
|
|
7105
|
-
},
|
|
7106
|
-
required: ["projectRoot", "filePath", "newContent", "expectedPrePatchHash", "scanId"]
|
|
7107
|
-
}
|
|
7108
|
-
};
|
|
7109
|
-
async function handleApplyPatch(args) {
|
|
7110
|
-
const projectRoot = args.projectRoot;
|
|
7111
|
-
const filePath = args.filePath;
|
|
7112
|
-
const newContent = args.newContent;
|
|
7113
|
-
const expectedPrePatchHash = args.expectedPrePatchHash;
|
|
7114
|
-
const scanId = args.scanId;
|
|
7115
|
-
if (!projectRoot || !filePath || !newContent || !expectedPrePatchHash || !scanId) {
|
|
7116
|
-
throw new Error("projectRoot, filePath, newContent, expectedPrePatchHash, and scanId are all required");
|
|
7117
|
-
}
|
|
7118
|
-
try {
|
|
7119
|
-
SessionScope.enforce(filePath);
|
|
7120
|
-
} catch (e) {
|
|
7121
|
-
if (e instanceof ScopeViolation)
|
|
7122
|
-
throw e;
|
|
7123
|
-
throw e;
|
|
7124
|
-
}
|
|
7125
|
-
const absolutePath = filePath.startsWith("/") ? filePath : join19(projectRoot, filePath);
|
|
7126
|
-
let actualPreHash;
|
|
7127
|
-
try {
|
|
7128
|
-
actualPreHash = await hashFile(absolutePath);
|
|
7129
|
-
} catch {
|
|
7130
|
-
actualPreHash = "sha256:new";
|
|
7131
|
-
}
|
|
7132
|
-
if (actualPreHash !== expectedPrePatchHash) {
|
|
7133
|
-
throw new StalePatchError(filePath, expectedPrePatchHash, actualPreHash);
|
|
7134
|
-
}
|
|
7135
|
-
const dir = dirname4(absolutePath);
|
|
7136
|
-
await mkdir8(dir, { recursive: true });
|
|
7137
|
-
const tmpPath = absolutePath + `.tmp_${Date.now()}`;
|
|
7138
|
-
await writeFile9(tmpPath, newContent, "utf8");
|
|
7139
|
-
await rename3(tmpPath, absolutePath);
|
|
7140
|
-
const postPatchHash = await hashFile(absolutePath);
|
|
7141
|
-
const blobStore = new BlobStore(projectRoot);
|
|
7142
|
-
const pointerStore = PointerStore.open(projectRoot);
|
|
7143
|
-
const { blobPath } = await blobStore.writeAtomic(newContent);
|
|
7144
|
-
const pointerId = `ptr_patch_${v4_default().replace(/-/g, "").slice(0, 12)}`;
|
|
7145
|
-
await pointerStore.insertPointer({
|
|
7146
|
-
pointerId,
|
|
7147
|
-
scanId,
|
|
7148
|
-
artifactName: "patch_record",
|
|
7149
|
-
contentHash: postPatchHash,
|
|
7150
|
-
blobPath,
|
|
7151
|
-
schemaVersion: "1.0.0",
|
|
7152
|
-
createdAt: Date.now(),
|
|
7153
|
-
expiresAt: null
|
|
7154
|
-
});
|
|
7155
|
-
const result = {
|
|
7156
|
-
ok: true,
|
|
7157
|
-
filePath,
|
|
7158
|
-
prePatchHash: actualPreHash,
|
|
7159
|
-
postPatchHash,
|
|
7160
|
-
pointerId,
|
|
7161
|
-
message: `Patch applied to ${filePath}`
|
|
7162
|
-
};
|
|
7163
|
-
return await applyBudgetGuard(projectRoot, scanId, "patch_record", result);
|
|
7164
|
-
}
|
|
7165
|
-
|
|
7166
8372
|
// dist/mcp/tools/work_orders.js
|
|
7167
8373
|
var createWorkOrderTool = {
|
|
7168
8374
|
name: "create_work_order",
|
|
@@ -7288,97 +8494,6 @@ async function handleSpawnWorker(args) {
|
|
|
7288
8494
|
};
|
|
7289
8495
|
}
|
|
7290
8496
|
|
|
7291
|
-
// dist/mcp/tools/submit_receipt.js
|
|
7292
|
-
import { join as join20 } from "path";
|
|
7293
|
-
var submitReceiptTool = {
|
|
7294
|
-
name: "submit_receipt",
|
|
7295
|
-
description: "Worker submits a WorkerReceipt for a completed Work Order. The ProofValidator checks all 8 proof conditions. Returns accept/reject with detailed errors.",
|
|
7296
|
-
inputSchema: {
|
|
7297
|
-
type: "object",
|
|
7298
|
-
properties: {
|
|
7299
|
-
projectRoot: { type: "string", description: "Absolute project root" },
|
|
7300
|
-
receipt: {
|
|
7301
|
-
type: "object",
|
|
7302
|
-
description: "WorkerReceipt object",
|
|
7303
|
-
properties: {
|
|
7304
|
-
workOrderId: { type: "string" },
|
|
7305
|
-
status: { type: "string", enum: ["completed", "failed", "blocked"] },
|
|
7306
|
-
proofPointers: {
|
|
7307
|
-
type: "array",
|
|
7308
|
-
items: {
|
|
7309
|
-
type: "object",
|
|
7310
|
-
properties: {
|
|
7311
|
-
pointer: { type: "string" },
|
|
7312
|
-
schemaName: { type: "string" },
|
|
7313
|
-
contentHash: { type: "string" }
|
|
7314
|
-
},
|
|
7315
|
-
required: ["pointer", "schemaName", "contentHash"]
|
|
7316
|
-
}
|
|
7317
|
-
},
|
|
7318
|
-
changedFiles: {
|
|
7319
|
-
type: "array",
|
|
7320
|
-
items: {
|
|
7321
|
-
type: "object",
|
|
7322
|
-
properties: {
|
|
7323
|
-
path: { type: "string" },
|
|
7324
|
-
prePatchHash: { type: "string" },
|
|
7325
|
-
postPatchHash: { type: "string" }
|
|
7326
|
-
},
|
|
7327
|
-
required: ["path", "prePatchHash", "postPatchHash"]
|
|
7328
|
-
}
|
|
7329
|
-
},
|
|
7330
|
-
summary: { type: "string" }
|
|
7331
|
-
},
|
|
7332
|
-
required: ["workOrderId", "status", "proofPointers", "changedFiles", "summary"]
|
|
7333
|
-
}
|
|
7334
|
-
},
|
|
7335
|
-
required: ["projectRoot", "receipt"]
|
|
7336
|
-
}
|
|
7337
|
-
};
|
|
7338
|
-
async function handleSubmitReceipt(args) {
|
|
7339
|
-
const projectRoot = args.projectRoot;
|
|
7340
|
-
const receipt = args.receipt;
|
|
7341
|
-
if (!projectRoot || !receipt) {
|
|
7342
|
-
throw new Error("projectRoot and receipt are required");
|
|
7343
|
-
}
|
|
7344
|
-
const pointerStore = PointerStore.open(projectRoot);
|
|
7345
|
-
const workOrder = pointerStore.getWorkOrder(receipt.workOrderId);
|
|
7346
|
-
if (!workOrder) {
|
|
7347
|
-
throw new Error(`WorkOrderNotFound: ${receipt.workOrderId}`);
|
|
7348
|
-
}
|
|
7349
|
-
if (workOrder.status !== "active") {
|
|
7350
|
-
throw new Error(`WorkOrderNotActive: ${receipt.workOrderId} is "${workOrder.status}", expected "active"`);
|
|
7351
|
-
}
|
|
7352
|
-
const allowedFiles = JSON.parse(workOrder.allowedFiles);
|
|
7353
|
-
const allowedGlobs = JSON.parse(workOrder.allowedGlobs);
|
|
7354
|
-
const requiredProof = JSON.parse(workOrder.requiredProof);
|
|
7355
|
-
const blobDir = join20(projectRoot, ".vibe-splainer", "blobs");
|
|
7356
|
-
const isAllowedFile = (filePath) => {
|
|
7357
|
-
const inExplicit = allowedFiles.some((f) => filePath === f || filePath.endsWith("/" + f) || filePath.endsWith(f));
|
|
7358
|
-
const inGlobs = allowedGlobs.some((g) => minimatch(filePath, g, { matchBase: true }));
|
|
7359
|
-
return inExplicit || inGlobs;
|
|
7360
|
-
};
|
|
7361
|
-
const validation = await ProofValidator.validate(receipt, requiredProof, isAllowedFile, blobDir);
|
|
7362
|
-
const receiptId = `rcpt_${v4_default().replace(/-/g, "").slice(0, 16)}`;
|
|
7363
|
-
const finalStatus = validation.valid ? receipt.status : "failed";
|
|
7364
|
-
await pointerStore.insertReceipt({
|
|
7365
|
-
receiptId,
|
|
7366
|
-
workOrderId: receipt.workOrderId,
|
|
7367
|
-
status: finalStatus,
|
|
7368
|
-
proofPointers: receipt.proofPointers,
|
|
7369
|
-
changedFiles: receipt.changedFiles,
|
|
7370
|
-
summary: receipt.summary
|
|
7371
|
-
});
|
|
7372
|
-
await pointerStore.updateWorkOrderStatus(receipt.workOrderId, validation.valid ? receipt.status === "completed" ? "completed" : "failed" : "failed");
|
|
7373
|
-
return {
|
|
7374
|
-
receiptId,
|
|
7375
|
-
accepted: validation.valid,
|
|
7376
|
-
workOrderId: receipt.workOrderId,
|
|
7377
|
-
validation,
|
|
7378
|
-
finalStatus
|
|
7379
|
-
};
|
|
7380
|
-
}
|
|
7381
|
-
|
|
7382
8497
|
// dist/mcp/tools/set_session_scope.js
|
|
7383
8498
|
var setSessionScopeTool = {
|
|
7384
8499
|
name: "set_session_scope",
|
|
@@ -7634,13 +8749,128 @@ async function handleGetEvidenceSlice(args) {
|
|
|
7634
8749
|
return await applyBudgetGuard(projectRoot, scanId, "evidence_slice", result);
|
|
7635
8750
|
}
|
|
7636
8751
|
|
|
8752
|
+
// dist/mcp/tools/prepareTaskContext.js
|
|
8753
|
+
import { join as join19 } from "path";
|
|
8754
|
+
import { readFile as readFile12, writeFile as writeFile8, mkdir as mkdir8 } from "fs/promises";
|
|
8755
|
+
var prepareTaskContextTool = {
|
|
8756
|
+
name: "vibe_prepare_task_context",
|
|
8757
|
+
description: "Prepares the ExpertNetworkPacket for a given task, selecting relevant files, routing to a specialist panel, analyzing risk warnings, and slicing scopes without running model inference.",
|
|
8758
|
+
inputSchema: {
|
|
8759
|
+
type: "object",
|
|
8760
|
+
properties: {
|
|
8761
|
+
projectRoot: {
|
|
8762
|
+
type: "string",
|
|
8763
|
+
description: "Absolute path to the project root directory"
|
|
8764
|
+
},
|
|
8765
|
+
task: {
|
|
8766
|
+
type: "string",
|
|
8767
|
+
description: "Description of the task or issue to resolve"
|
|
8768
|
+
},
|
|
8769
|
+
files: {
|
|
8770
|
+
type: "array",
|
|
8771
|
+
items: { type: "string" },
|
|
8772
|
+
description: "Optional list of files to explicitly focus on"
|
|
8773
|
+
},
|
|
8774
|
+
error_trace: {
|
|
8775
|
+
type: "string",
|
|
8776
|
+
description: "Optional error stack trace or exception output"
|
|
8777
|
+
},
|
|
8778
|
+
intended_behavior: {
|
|
8779
|
+
type: "string",
|
|
8780
|
+
description: "Optional description of the intended behavior"
|
|
8781
|
+
},
|
|
8782
|
+
max_files: {
|
|
8783
|
+
type: "number",
|
|
8784
|
+
description: "Optional maximum files to select"
|
|
8785
|
+
}
|
|
8786
|
+
},
|
|
8787
|
+
required: ["projectRoot", "task"]
|
|
8788
|
+
}
|
|
8789
|
+
};
|
|
8790
|
+
async function handlePrepareTaskContext(args, options = {}) {
|
|
8791
|
+
const projectRoot = args.projectRoot;
|
|
8792
|
+
const task = args.task;
|
|
8793
|
+
const files = args.files;
|
|
8794
|
+
const errorTrace = args.error_trace;
|
|
8795
|
+
const intendedBehavior = args.intended_behavior;
|
|
8796
|
+
const maxFiles = args.max_files;
|
|
8797
|
+
if (!projectRoot)
|
|
8798
|
+
throw new Error("projectRoot is required");
|
|
8799
|
+
if (!task)
|
|
8800
|
+
throw new Error("task is required");
|
|
8801
|
+
console.error(`[vibe-splain] Preparing task context for task: "${task}"`);
|
|
8802
|
+
let scanArtifact = null;
|
|
8803
|
+
const artifactPath = join19(projectRoot, ".vibe-splainer", "analysis.json");
|
|
8804
|
+
try {
|
|
8805
|
+
const raw = await readFile12(artifactPath, "utf8");
|
|
8806
|
+
scanArtifact = JSON.parse(raw);
|
|
8807
|
+
} catch (err) {
|
|
8808
|
+
console.error(`[vibe-splain] No scan artifact found at ${artifactPath}, proceeding with fallback routing.`);
|
|
8809
|
+
}
|
|
8810
|
+
const packet = buildExpertNetworkPacket({
|
|
8811
|
+
task,
|
|
8812
|
+
projectRoot,
|
|
8813
|
+
scanArtifact,
|
|
8814
|
+
files,
|
|
8815
|
+
errorTrace,
|
|
8816
|
+
intendedBehavior,
|
|
8817
|
+
maxFiles
|
|
8818
|
+
});
|
|
8819
|
+
const networkDir = join19(projectRoot, ".vibe-splainer", "network");
|
|
8820
|
+
const packetsDir = join19(networkDir, "packets");
|
|
8821
|
+
try {
|
|
8822
|
+
await mkdir8(packetsDir, { recursive: true });
|
|
8823
|
+
const latestPath = join19(networkDir, "latest_packet.json");
|
|
8824
|
+
await writeFile8(latestPath, JSON.stringify(packet, null, 2), "utf8");
|
|
8825
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
8826
|
+
const timestampedPath = join19(packetsDir, `${timestamp}.json`);
|
|
8827
|
+
await writeFile8(timestampedPath, JSON.stringify(packet, null, 2), "utf8");
|
|
8828
|
+
console.error(`[vibe-splain] Network packets saved under ${networkDir}`);
|
|
8829
|
+
} catch (err) {
|
|
8830
|
+
console.error("[vibe-splain] Failed to save network packets locally:", err.message);
|
|
8831
|
+
}
|
|
8832
|
+
return {
|
|
8833
|
+
ok: true,
|
|
8834
|
+
packet
|
|
8835
|
+
};
|
|
8836
|
+
}
|
|
8837
|
+
|
|
8838
|
+
// dist/mcp/tools/explainSpecialistScope.js
|
|
8839
|
+
var explainSpecialistScopeTool = {
|
|
8840
|
+
name: "vibe_explain_specialist_scope",
|
|
8841
|
+
description: "Explains the jurisdiction scope card and profile definitions for a given specialist ID.",
|
|
8842
|
+
inputSchema: {
|
|
8843
|
+
type: "object",
|
|
8844
|
+
properties: {
|
|
8845
|
+
specialist_id: {
|
|
8846
|
+
type: "string",
|
|
8847
|
+
description: "Specialist ID to explain (control_flow | data_transform | integration_contracts | generic_repo)"
|
|
8848
|
+
}
|
|
8849
|
+
},
|
|
8850
|
+
required: ["specialist_id"]
|
|
8851
|
+
}
|
|
8852
|
+
};
|
|
8853
|
+
async function handleExplainSpecialistScope(args, options = {}) {
|
|
8854
|
+
const specialistId = args.specialist_id;
|
|
8855
|
+
if (!specialistId)
|
|
8856
|
+
throw new Error("specialist_id is required");
|
|
8857
|
+
if (!isSpecialistId(specialistId)) {
|
|
8858
|
+
throw new Error(`Invalid specialist ID: "${specialistId}". Must be one of: control_flow, data_transform, integration_contracts, generic_repo.`);
|
|
8859
|
+
}
|
|
8860
|
+
console.error(`[vibe-splain] Fetching scope profile for specialist: "${specialistId}"`);
|
|
8861
|
+
const profile = getSpecialistProfile(specialistId);
|
|
8862
|
+
return {
|
|
8863
|
+
ok: true,
|
|
8864
|
+
profile
|
|
8865
|
+
};
|
|
8866
|
+
}
|
|
8867
|
+
|
|
7637
8868
|
// dist/mcp/server.js
|
|
7638
8869
|
var ALL_TOOLS = [
|
|
7639
8870
|
scanProjectTool,
|
|
7640
8871
|
getProjectMapTool,
|
|
7641
8872
|
setProjectBriefTool,
|
|
7642
8873
|
getFileContextTool,
|
|
7643
|
-
getCallChainTool,
|
|
7644
8874
|
writeDecisionCardTool,
|
|
7645
8875
|
getStrategicOverviewTool,
|
|
7646
8876
|
inspectPillarTool,
|
|
@@ -7652,21 +8882,21 @@ var ALL_TOOLS = [
|
|
|
7652
8882
|
getStartHereTool,
|
|
7653
8883
|
getProjectSummaryTool,
|
|
7654
8884
|
getEvidenceSliceTool,
|
|
7655
|
-
// Phase 3: Delegation
|
|
8885
|
+
// Phase 3: Worker Delegation
|
|
7656
8886
|
createWorkOrderTool,
|
|
7657
8887
|
spawnWorkerTool,
|
|
7658
|
-
applyPatchTool,
|
|
7659
|
-
submitReceiptTool,
|
|
7660
8888
|
// Phase 4: Scope & Escalation
|
|
7661
8889
|
setSessionScopeTool,
|
|
7662
|
-
yieldForScopeExpansionTool
|
|
8890
|
+
yieldForScopeExpansionTool,
|
|
8891
|
+
// Specialist Network v0
|
|
8892
|
+
prepareTaskContextTool,
|
|
8893
|
+
explainSpecialistScopeTool
|
|
7663
8894
|
];
|
|
7664
8895
|
var TOOL_HANDLERS = {
|
|
7665
8896
|
scan_project: handleScanProject,
|
|
7666
8897
|
get_project_map: handleGetProjectMap,
|
|
7667
8898
|
set_project_brief: handleSetProjectBrief,
|
|
7668
8899
|
get_file_context: handleGetFileContext,
|
|
7669
|
-
get_call_chain: handleGetCallChain,
|
|
7670
8900
|
write_decision_card: handleWriteDecisionCard,
|
|
7671
8901
|
get_strategic_overview: handleGetStrategicOverview,
|
|
7672
8902
|
inspect_pillar: handleInspectPillar,
|
|
@@ -7681,11 +8911,12 @@ var TOOL_HANDLERS = {
|
|
|
7681
8911
|
// Phase 3
|
|
7682
8912
|
create_work_order: handleCreateWorkOrder,
|
|
7683
8913
|
spawn_worker: handleSpawnWorker,
|
|
7684
|
-
apply_patch: handleApplyPatch,
|
|
7685
|
-
submit_receipt: handleSubmitReceipt,
|
|
7686
8914
|
// Phase 4
|
|
7687
8915
|
set_session_scope: handleSetSessionScope,
|
|
7688
|
-
yield_for_scope_expansion: handleYieldForScopeExpansion
|
|
8916
|
+
yield_for_scope_expansion: handleYieldForScopeExpansion,
|
|
8917
|
+
// Specialist Network v0
|
|
8918
|
+
vibe_prepare_task_context: handlePrepareTaskContext,
|
|
8919
|
+
vibe_explain_specialist_scope: handleExplainSpecialistScope
|
|
7689
8920
|
};
|
|
7690
8921
|
async function startMCPServer(options = {}) {
|
|
7691
8922
|
await initParser2();
|
|
@@ -7811,204 +9042,239 @@ async function serveCommand(options) {
|
|
|
7811
9042
|
await startMCPServer(options);
|
|
7812
9043
|
}
|
|
7813
9044
|
|
|
7814
|
-
// dist/commands/
|
|
7815
|
-
|
|
7816
|
-
|
|
7817
|
-
|
|
7818
|
-
const
|
|
7819
|
-
|
|
7820
|
-
|
|
7821
|
-
|
|
9045
|
+
// dist/commands/network.js
|
|
9046
|
+
import { join as join20, resolve } from "path";
|
|
9047
|
+
import { readFile as readFile13, writeFile as writeFile9, mkdir as mkdir9 } from "fs/promises";
|
|
9048
|
+
async function networkPlanCommand(task, options) {
|
|
9049
|
+
const projectRoot = options.root ? resolve(options.root) : process.cwd();
|
|
9050
|
+
const maxFiles = options.maxFiles ? parseInt(options.maxFiles, 10) : 8;
|
|
9051
|
+
let scanArtifact = null;
|
|
9052
|
+
const artifactPath = options.scanArtifact ? resolve(options.scanArtifact) : join20(projectRoot, ".vibe-splainer", "analysis.json");
|
|
9053
|
+
try {
|
|
9054
|
+
const raw = await readFile13(artifactPath, "utf8");
|
|
9055
|
+
scanArtifact = JSON.parse(raw);
|
|
9056
|
+
} catch (err) {
|
|
9057
|
+
if (options.scanArtifact) {
|
|
9058
|
+
console.error(`[vibe-splain] Error reading scan artifact at ${artifactPath}:`, err.message);
|
|
9059
|
+
}
|
|
7822
9060
|
}
|
|
7823
|
-
const
|
|
7824
|
-
|
|
7825
|
-
|
|
7826
|
-
|
|
7827
|
-
|
|
9061
|
+
const packet = buildExpertNetworkPacket({
|
|
9062
|
+
task,
|
|
9063
|
+
projectRoot,
|
|
9064
|
+
scanArtifact,
|
|
9065
|
+
errorTrace: options.errorTrace,
|
|
9066
|
+
intendedBehavior: options.intendedBehavior,
|
|
9067
|
+
maxFiles
|
|
7828
9068
|
});
|
|
7829
|
-
|
|
7830
|
-
|
|
7831
|
-
|
|
7832
|
-
|
|
7833
|
-
|
|
7834
|
-
|
|
7835
|
-
|
|
7836
|
-
|
|
7837
|
-
|
|
7838
|
-
|
|
7839
|
-
|
|
7840
|
-
|
|
7841
|
-
|
|
7842
|
-
|
|
7843
|
-
|
|
7844
|
-
|
|
7845
|
-
|
|
7846
|
-
|
|
7847
|
-
|
|
7848
|
-
|
|
7849
|
-
|
|
7850
|
-
|
|
7851
|
-
|
|
7852
|
-
|
|
7853
|
-
|
|
7854
|
-
|
|
7855
|
-
|
|
7856
|
-
|
|
7857
|
-
|
|
7858
|
-
|
|
7859
|
-
|
|
7860
|
-
|
|
7861
|
-
|
|
7862
|
-
|
|
9069
|
+
const networkDir = join20(projectRoot, ".vibe-splainer", "network");
|
|
9070
|
+
const packetsDir = join20(networkDir, "packets");
|
|
9071
|
+
try {
|
|
9072
|
+
await mkdir9(packetsDir, { recursive: true });
|
|
9073
|
+
const latestPath = join20(networkDir, "latest_packet.json");
|
|
9074
|
+
await writeFile9(latestPath, JSON.stringify(packet, null, 2), "utf8");
|
|
9075
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
9076
|
+
const timestampedPath = join20(packetsDir, `${timestamp}.json`);
|
|
9077
|
+
await writeFile9(timestampedPath, JSON.stringify(packet, null, 2), "utf8");
|
|
9078
|
+
if (options.json) {
|
|
9079
|
+
process.stdout.write(JSON.stringify(packet, null, 2) + "\n");
|
|
9080
|
+
} else {
|
|
9081
|
+
process.stdout.write("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
|
|
9082
|
+
process.stdout.write(" Vibe-Splain Specialist Network \u2014 Task Plan\n");
|
|
9083
|
+
process.stdout.write("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
|
|
9084
|
+
process.stdout.write(`Task: "${packet.task}"
|
|
9085
|
+
`);
|
|
9086
|
+
process.stdout.write(`Project Root: ${packet.projectRoot}
|
|
9087
|
+
`);
|
|
9088
|
+
process.stdout.write(`Primary Specialist: ${packet.route.primarySpecialist}
|
|
9089
|
+
`);
|
|
9090
|
+
process.stdout.write(`Panel: ${packet.route.panel.join(", ")}
|
|
9091
|
+
`);
|
|
9092
|
+
process.stdout.write(`Confidence: ${packet.route.confidence}
|
|
9093
|
+
`);
|
|
9094
|
+
process.stdout.write(`Reason: ${packet.route.reason}
|
|
9095
|
+
`);
|
|
9096
|
+
process.stdout.write("\nSelected Files:\n");
|
|
9097
|
+
if (packet.selectedFiles.length > 0) {
|
|
9098
|
+
packet.selectedFiles.forEach((f) => {
|
|
9099
|
+
process.stdout.write(` \u2022 ${f.path} [blastRadius: ${f.blastRadius}]
|
|
9100
|
+
`);
|
|
9101
|
+
process.stdout.write(` Reason: ${f.reason}
|
|
9102
|
+
`);
|
|
9103
|
+
});
|
|
9104
|
+
} else {
|
|
9105
|
+
process.stdout.write(" (No files selected)\n");
|
|
9106
|
+
}
|
|
9107
|
+
process.stdout.write("\nRisk Warnings:\n");
|
|
9108
|
+
if (packet.riskWarnings.length > 0) {
|
|
9109
|
+
packet.riskWarnings.forEach((w) => {
|
|
9110
|
+
const levelLabel = w.level === "critical" ? "\u{1F534} CRITICAL" : w.level === "warning" ? "\u{1F7E1} WARNING" : "\u2139\uFE0F INFO";
|
|
9111
|
+
process.stdout.write(` \u2022 [${levelLabel}] ${w.message}
|
|
9112
|
+
`);
|
|
9113
|
+
process.stdout.write(` Reason: ${w.reason}
|
|
9114
|
+
`);
|
|
9115
|
+
});
|
|
9116
|
+
} else {
|
|
9117
|
+
process.stdout.write(" (No risks identified)\n");
|
|
7863
9118
|
}
|
|
9119
|
+
process.stdout.write("\nSmallest Safe Change Policy:\n");
|
|
9120
|
+
process.stdout.write(` ${packet.smallestSafeChangePolicy.summary}
|
|
9121
|
+
`);
|
|
9122
|
+
process.stdout.write("\nSaved Artifacts:\n");
|
|
9123
|
+
process.stdout.write(` \u2022 Latest: ${latestPath}
|
|
9124
|
+
`);
|
|
9125
|
+
process.stdout.write(` \u2022 Packet: ${timestampedPath}
|
|
9126
|
+
`);
|
|
9127
|
+
process.stdout.write("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
|
|
7864
9128
|
}
|
|
7865
|
-
}
|
|
7866
|
-
|
|
7867
|
-
|
|
7868
|
-
|
|
7869
|
-
const tmpFiles = await readdir2(tmpDir);
|
|
7870
|
-
for (const f of tmpFiles) {
|
|
7871
|
-
await rm2(join21(tmpDir, f), { force: true });
|
|
9129
|
+
} catch (err) {
|
|
9130
|
+
console.error("[vibe-splain] Error saving network artifacts:", err.message);
|
|
9131
|
+
if (options.json) {
|
|
9132
|
+
process.stdout.write(JSON.stringify(packet, null, 2) + "\n");
|
|
7872
9133
|
}
|
|
7873
|
-
console.error(`[vibe-splain gc] Cleaned ${tmpFiles.length} tmp files`);
|
|
7874
|
-
} catch {
|
|
7875
9134
|
}
|
|
7876
|
-
console.error("[vibe-splain gc] Done");
|
|
7877
9135
|
}
|
|
7878
9136
|
|
|
7879
|
-
// dist/commands/
|
|
7880
|
-
import {
|
|
7881
|
-
import {
|
|
7882
|
-
import {
|
|
7883
|
-
|
|
7884
|
-
|
|
7885
|
-
|
|
7886
|
-
|
|
7887
|
-
|
|
7888
|
-
|
|
7889
|
-
|
|
7890
|
-
|
|
7891
|
-
|
|
7892
|
-
|
|
7893
|
-
|
|
7894
|
-
|
|
7895
|
-
|
|
7896
|
-
|
|
7897
|
-
|
|
7898
|
-
|
|
7899
|
-
|
|
7900
|
-
|
|
7901
|
-
|
|
7902
|
-
|
|
7903
|
-
|
|
7904
|
-
|
|
7905
|
-
|
|
7906
|
-
|
|
7907
|
-
|
|
7908
|
-
|
|
7909
|
-
|
|
7910
|
-
|
|
7911
|
-
|
|
7912
|
-
|
|
9137
|
+
// dist/commands/hook.js
|
|
9138
|
+
import { existsSync as existsSync7, writeFileSync, readFileSync as readFileSync3 } from "fs";
|
|
9139
|
+
import { dirname as dirname5, join as join21 } from "path";
|
|
9140
|
+
import { tmpdir } from "os";
|
|
9141
|
+
|
|
9142
|
+
// dist/hook/preToolUse.js
|
|
9143
|
+
var DEFER = { action: "defer" };
|
|
9144
|
+
var EDIT_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "MultiEdit"]);
|
|
9145
|
+
function decidePreToolUse(input, gateIndex, sessionState) {
|
|
9146
|
+
if (!EDIT_TOOLS.has(input.tool_name ?? ""))
|
|
9147
|
+
return DEFER;
|
|
9148
|
+
if (!gateIndex) {
|
|
9149
|
+
if (sessionState && !sessionState.warningShown) {
|
|
9150
|
+
const warnMsg = "vibe-splain: .vibe-splainer/gate.json is missing. Please run 'vibe-splain scan' to generate the scan architecture and enable tool guarding.";
|
|
9151
|
+
return {
|
|
9152
|
+
action: "emit",
|
|
9153
|
+
output: {
|
|
9154
|
+
hookSpecificOutput: {
|
|
9155
|
+
hookEventName: "PreToolUse",
|
|
9156
|
+
permissionDecision: "allow",
|
|
9157
|
+
additionalContext: warnMsg,
|
|
9158
|
+
systemMessage: warnMsg
|
|
9159
|
+
}
|
|
9160
|
+
}
|
|
9161
|
+
};
|
|
9162
|
+
}
|
|
9163
|
+
return DEFER;
|
|
9164
|
+
}
|
|
9165
|
+
const filePath = input.tool_input?.file_path;
|
|
9166
|
+
if (!filePath)
|
|
9167
|
+
return DEFER;
|
|
9168
|
+
const ctx = buildEscalationContext(filePath, gateIndex);
|
|
9169
|
+
if (!ctx || ctx.blastRadius === "low")
|
|
9170
|
+
return DEFER;
|
|
9171
|
+
const block = formatEscalation(ctx);
|
|
9172
|
+
if (ctx.blastRadius === "high") {
|
|
9173
|
+
return {
|
|
9174
|
+
action: "emit",
|
|
9175
|
+
output: {
|
|
9176
|
+
hookSpecificOutput: {
|
|
9177
|
+
hookEventName: "PreToolUse",
|
|
9178
|
+
permissionDecision: "ask",
|
|
9179
|
+
permissionDecisionReason: block,
|
|
9180
|
+
additionalContext: block
|
|
9181
|
+
}
|
|
9182
|
+
}
|
|
7913
9183
|
};
|
|
7914
|
-
|
|
7915
|
-
|
|
7916
|
-
|
|
7917
|
-
|
|
7918
|
-
|
|
7919
|
-
|
|
7920
|
-
|
|
7921
|
-
|
|
7922
|
-
if (!existsSync7(srcPath)) {
|
|
7923
|
-
console.error(`[vibe-splain bundle] Warning: blob missing for ${p.pointerId}: ${srcPath}`);
|
|
7924
|
-
continue;
|
|
9184
|
+
}
|
|
9185
|
+
return {
|
|
9186
|
+
action: "emit",
|
|
9187
|
+
output: {
|
|
9188
|
+
hookSpecificOutput: {
|
|
9189
|
+
hookEventName: "PreToolUse",
|
|
9190
|
+
permissionDecision: "allow",
|
|
9191
|
+
additionalContext: block
|
|
7925
9192
|
}
|
|
7926
|
-
await copyFile(srcPath, join22(blobsStageDir, `sha256_${hex}`));
|
|
7927
9193
|
}
|
|
7928
|
-
|
|
7929
|
-
|
|
7930
|
-
|
|
7931
|
-
|
|
7932
|
-
|
|
7933
|
-
|
|
7934
|
-
|
|
7935
|
-
|
|
7936
|
-
|
|
7937
|
-
|
|
7938
|
-
}
|
|
9194
|
+
};
|
|
9195
|
+
}
|
|
9196
|
+
function formatEscalation(ctx) {
|
|
9197
|
+
const lines = [];
|
|
9198
|
+
lines.push(`vibe-splain: ${ctx.blastRadius} blast radius \u2014 ${ctx.targetFile} (gravity ${ctx.gravity}).`);
|
|
9199
|
+
if (ctx.dependentCount > 0) {
|
|
9200
|
+
const shown = ctx.dependents.slice(0, 8);
|
|
9201
|
+
const more = ctx.dependentCount - shown.length;
|
|
9202
|
+
lines.push(`${ctx.dependentCount} file(s) depend on it:`);
|
|
9203
|
+
for (const d of shown)
|
|
9204
|
+
lines.push(` - ${d}`);
|
|
9205
|
+
if (more > 0)
|
|
9206
|
+
lines.push(` \u2026 (+${more} more)`);
|
|
9207
|
+
}
|
|
9208
|
+
for (const w of ctx.riskWarnings)
|
|
9209
|
+
lines.push(`[${w.level}] ${w.message}`);
|
|
9210
|
+
lines.push(ctx.smallestSafeChange.summary);
|
|
9211
|
+
return lines.join("\n");
|
|
7939
9212
|
}
|
|
7940
9213
|
|
|
7941
|
-
// dist/commands/
|
|
7942
|
-
|
|
7943
|
-
|
|
7944
|
-
|
|
7945
|
-
|
|
7946
|
-
|
|
7947
|
-
|
|
7948
|
-
|
|
7949
|
-
|
|
7950
|
-
|
|
7951
|
-
|
|
7952
|
-
|
|
7953
|
-
|
|
7954
|
-
|
|
7955
|
-
|
|
9214
|
+
// dist/commands/hook.js
|
|
9215
|
+
function findProjectRoot(start) {
|
|
9216
|
+
let dir = start || process.cwd();
|
|
9217
|
+
for (let i = 0; i < 64; i++) {
|
|
9218
|
+
if (existsSync7(join21(dir, ".vibe-splainer", "analysis.json")))
|
|
9219
|
+
return dir;
|
|
9220
|
+
const parent = dirname5(dir);
|
|
9221
|
+
if (parent === dir)
|
|
9222
|
+
break;
|
|
9223
|
+
dir = parent;
|
|
9224
|
+
}
|
|
9225
|
+
return null;
|
|
9226
|
+
}
|
|
9227
|
+
async function runHookCommand(rawStdin) {
|
|
9228
|
+
let input;
|
|
7956
9229
|
try {
|
|
7957
|
-
|
|
7958
|
-
|
|
7959
|
-
|
|
7960
|
-
|
|
7961
|
-
|
|
7962
|
-
|
|
7963
|
-
|
|
7964
|
-
|
|
7965
|
-
|
|
7966
|
-
|
|
7967
|
-
|
|
7968
|
-
|
|
7969
|
-
}
|
|
7970
|
-
const blobStore = new BlobStore(root);
|
|
7971
|
-
const pointerStore = PointerStore.open(root);
|
|
7972
|
-
await blobStore.ensureDirs();
|
|
7973
|
-
let imported = 0;
|
|
7974
|
-
let hashErrors = 0;
|
|
7975
|
-
for (const entry of manifest.pointers) {
|
|
7976
|
-
const blobSrcPath = join23(extractDir, entry.blobFile);
|
|
7977
|
-
if (!existsSync8(blobSrcPath)) {
|
|
7978
|
-
console.error(`[vibe-splain import] Missing blob for pointer ${entry.pointerId}: ${entry.blobFile}`);
|
|
7979
|
-
hashErrors++;
|
|
7980
|
-
continue;
|
|
7981
|
-
}
|
|
7982
|
-
const content = await readFile15(blobSrcPath);
|
|
7983
|
-
const actualHash = `sha256:${createHash6("sha256").update(content).digest("hex")}`;
|
|
7984
|
-
if (actualHash !== entry.contentHash) {
|
|
7985
|
-
console.error(`[vibe-splain import] Hash mismatch for ${entry.pointerId}: expected ${entry.contentHash}, got ${actualHash}`);
|
|
7986
|
-
hashErrors++;
|
|
7987
|
-
continue;
|
|
7988
|
-
}
|
|
7989
|
-
const { blobPath } = await blobStore.writeAtomic(content);
|
|
7990
|
-
const namespacedPointerId = `${namespace}::${entry.pointerId}`;
|
|
7991
|
-
const namespacedScanId = `${namespace}::${entry.scanId}`;
|
|
7992
|
-
await pointerStore.insertPointer({
|
|
7993
|
-
pointerId: namespacedPointerId,
|
|
7994
|
-
scanId: namespacedScanId,
|
|
7995
|
-
artifactName: entry.artifactName,
|
|
7996
|
-
contentHash: entry.contentHash,
|
|
7997
|
-
blobPath,
|
|
7998
|
-
schemaVersion: entry.schemaVersion,
|
|
7999
|
-
createdAt: entry.createdAt,
|
|
8000
|
-
expiresAt: entry.expiresAt
|
|
8001
|
-
});
|
|
8002
|
-
imported++;
|
|
9230
|
+
input = JSON.parse(rawStdin);
|
|
9231
|
+
} catch {
|
|
9232
|
+
return { stdout: null };
|
|
9233
|
+
}
|
|
9234
|
+
const root = findProjectRoot(input.cwd);
|
|
9235
|
+
const gatePath = root ? join21(root, ".vibe-splainer", "gate.json") : null;
|
|
9236
|
+
const gateExists = gatePath ? existsSync7(gatePath) : false;
|
|
9237
|
+
let gateIndex = null;
|
|
9238
|
+
if (gateExists) {
|
|
9239
|
+
try {
|
|
9240
|
+
gateIndex = JSON.parse(readFileSync3(gatePath, "utf8"));
|
|
9241
|
+
} catch {
|
|
8003
9242
|
}
|
|
8004
|
-
|
|
8005
|
-
|
|
9243
|
+
}
|
|
9244
|
+
const sessionId = input.session_id || "default";
|
|
9245
|
+
const warnFile = join21(tmpdir(), `vibe-splain-warn-${sessionId}`);
|
|
9246
|
+
const warningShown = existsSync7(warnFile);
|
|
9247
|
+
const result = decidePreToolUse(input, gateIndex, { warningShown });
|
|
9248
|
+
if (result.action === "defer")
|
|
9249
|
+
return { stdout: null };
|
|
9250
|
+
if (!gateIndex && result.action === "emit") {
|
|
9251
|
+
try {
|
|
9252
|
+
writeFileSync(warnFile, "1");
|
|
9253
|
+
} catch {
|
|
8006
9254
|
}
|
|
8007
|
-
console.error(`[vibe-splain import] Imported ${imported}/${manifest.pointers.length} pointers under namespace "${namespace}"`);
|
|
8008
|
-
console.error(`[vibe-splain import] Original scanId: ${manifest.scanId} \u2192 namespaced as: ${namespace}::${manifest.scanId}`);
|
|
8009
|
-
} finally {
|
|
8010
|
-
await rm4(extractDir, { recursive: true, force: true });
|
|
8011
9255
|
}
|
|
9256
|
+
return { stdout: JSON.stringify(result.output) };
|
|
9257
|
+
}
|
|
9258
|
+
async function hookPreToolUseCommand() {
|
|
9259
|
+
const raw = await readStdin();
|
|
9260
|
+
const { stdout } = await runHookCommand(raw);
|
|
9261
|
+
if (stdout)
|
|
9262
|
+
process.stdout.write(stdout);
|
|
9263
|
+
}
|
|
9264
|
+
function readStdin() {
|
|
9265
|
+
return new Promise((resolve2) => {
|
|
9266
|
+
let data = "";
|
|
9267
|
+
if (process.stdin.isTTY) {
|
|
9268
|
+
resolve2("");
|
|
9269
|
+
return;
|
|
9270
|
+
}
|
|
9271
|
+
process.stdin.setEncoding("utf8");
|
|
9272
|
+
process.stdin.on("data", (chunk) => {
|
|
9273
|
+
data += chunk;
|
|
9274
|
+
});
|
|
9275
|
+
process.stdin.on("end", () => resolve2(data));
|
|
9276
|
+
process.stdin.on("error", () => resolve2(data));
|
|
9277
|
+
});
|
|
8012
9278
|
}
|
|
8013
9279
|
|
|
8014
9280
|
// dist/index.js
|
|
@@ -8016,23 +9282,8 @@ var program = new Command();
|
|
|
8016
9282
|
program.name("vibe-splain").description("Architectural dossier engine for vibe-coded projects").version("3.4.0");
|
|
8017
9283
|
program.command("install").description("Patch coding agent MCP config files to register vibe-splain").action(installCommand);
|
|
8018
9284
|
program.command("serve").description("Start the MCP server (called by the coding agent, not by you)").option("--format <format>", "Export format (html, markdown, etc.)").option("--budget <budget>", "Token budget for markdown").option("--scope <scope>", "Scope for export").action((options) => serveCommand(options));
|
|
8019
|
-
program.command("
|
|
8020
|
-
|
|
8021
|
-
|
|
8022
|
-
|
|
8023
|
-
process.exit(1);
|
|
8024
|
-
});
|
|
8025
|
-
});
|
|
8026
|
-
program.command("bundle <scanId>").description("Bundle a scan into a portable vibe-bundle.tar.gz").option("--output <path>", "Output tarball path").option("--project-root <path>", "Project root (default: cwd)").action((scanId, options) => {
|
|
8027
|
-
bundleCommand(scanId, { output: options.output, projectRoot: options.projectRoot }).catch((err) => {
|
|
8028
|
-
console.error("[vibe-splain bundle] Error:", err.message);
|
|
8029
|
-
process.exit(1);
|
|
8030
|
-
});
|
|
8031
|
-
});
|
|
8032
|
-
program.command("import <tarball>").description("Import a vibe-bundle.tar.gz into the local pointer store").option("--namespace <ns>", "Bundle namespace alias (default: imported_<timestamp>)").option("--project-root <path>", "Project root (default: cwd)").action((tarball, options) => {
|
|
8033
|
-
importBundleCommand(tarball, { namespace: options.namespace, projectRoot: options.projectRoot }).catch((err) => {
|
|
8034
|
-
console.error("[vibe-splain import] Error:", err.message);
|
|
8035
|
-
process.exit(1);
|
|
8036
|
-
});
|
|
8037
|
-
});
|
|
9285
|
+
var networkCmd = program.command("network").description("Manage specialist network adapters and tasks");
|
|
9286
|
+
networkCmd.command("plan <task>").description("Build and route an Expert Network packet for a given task").option("--root <path>", "Alternative project root path").option("--scan-artifact <path>", "Custom path to a scan artifact (.vibe-splainer/analysis.json)").option("--json", "Print the compiled ExpertNetworkPacket as JSON").option("--max-files <number>", "Maximum files to select").option("--error-trace <string>", "Optional error trace or stack details").option("--intended-behavior <string>", "Optional intended behavior description").action((task, options) => networkPlanCommand(task, options));
|
|
9287
|
+
var hookCmd = program.command("hook").description("Deterministic agent hooks (called by your coding agent, not by you)");
|
|
9288
|
+
hookCmd.command("pretooluse").description("PreToolUse gate: escalate edits to high-blast-radius files").action(() => hookPreToolUseCommand());
|
|
8038
9289
|
program.parse();
|