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/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} VIBE-SPLAIN Installer\n");
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
- if (config.mcpServers?.["vibe-splain"]) {
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 readFile6 } from "fs/promises";
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 = dirname(fileURLToPath(import.meta.url));
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 = dirname(require2.resolve("tree-sitter-wasms/package.json"));
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) && dirname(r).split(sep).length <= 2)
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 dirname2, relative as relative2, extname as extname2, sep as sep2 } from "path";
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: readdir3 } = await import("fs/promises");
1220
+ const { readdir: readdir2 } = await import("fs/promises");
1217
1221
  let entries = [];
1218
1222
  try {
1219
- entries = await readdir3(dir, { withFileTypes: true });
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(dirname2(tsconfigPath), baseFile);
1257
+ baseFile = join3(dirname3(tsconfigPath), baseFile);
1254
1258
  } else {
1255
- let currentDir = dirname2(tsconfigPath);
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 = dirname2(currentDir);
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(dirname2(tsconfigPath), opts.baseUrl) : dirname2(tsconfigPath);
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: readdir3 } = await import("fs/promises");
1319
+ const { readdir: readdir2 } = await import("fs/promises");
1316
1320
  let entries = [];
1317
1321
  try {
1318
- const dirents = await readdir3(absPrefix, { withFileTypes: true });
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 = dirname2(fromAbs);
1389
+ let dir = dirname3(fromAbs);
1386
1390
  for (let i = 1; i < dots; i++)
1387
- dir = dirname2(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(dirname2(fromAbs), spec);
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 join4, basename as basename2, extname as extname3, sep as sep3 } from "path";
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 inferWriteIntents(productDomain, relPath, sideEffectProfile) {
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.length > 0 ? intents : ["none_detected"];
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) => ["booking_mutation", "payment_mutation", "auth_token_mutation"].includes(s)) && gravitySignals.cyclomatic > 10)
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 = DOMAIN_SURFACE_PATTERNS[domain];
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 (sideEffectProfile.includes("booking_mutation"))
1732
- score += 3;
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
- if (highImpactDomains.includes(productDomain))
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 pageRank(nodes, outEdges, damping = 0.85, iters = 20) {
1762
- const n = nodes.length;
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 = basename2(files[0].rel, extname3(files[0].rel));
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 ${basename2(sorted[0].rel)}.`,
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 ${basename2(top[0].rel)}.`,
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 || "unknown";
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, inferWriteIntents(w.productDomain, w.rel, effects));
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
- const depthRatio = (w.ast.cyclomatic + w.ast.maxNesting * 2) / Math.max(1, w.ast.publicSurface);
2076
- const depthFactor = Math.min(1, Math.log2(depthRatio + 1) / 3);
2077
- const adjustedCentrality = centrality * (0.3 + 0.7 * depthFactor);
2078
- 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);
2079
- if (!real)
2080
- gravityRaw *= 0.2;
2081
- const gravity = Math.max(0, Math.min(100, gravityRaw));
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 entrypointTraceStatus = deriveEntrypointTraceStatus(w.productDomain, runtimeEntrypoints, importsUnresolvedArr);
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 = join4(projectRoot, ".vibe-splainer");
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(join4(dir, "stage-05-side-effects.json"), JSON.stringify(stage05, null, 2), "utf8");
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(join4(dir, "stage-06-write-intents.json"), JSON.stringify(stage06, null, 2), "utf8");
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(join4(dir, "stage-07-risk-types.json"), JSON.stringify(stage07, null, 2), "utf8");
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(join4(dir, "stage-08-load-bearing.json"), JSON.stringify(stage08, null, 2), "utf8");
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, readFile as readFile5 } from "fs/promises";
2756
- function computeSeverity(sideEffectProfile, productDomain, gravity, heat, maxNesting, hasLongFunctions, swallowedCatches, runtimeEntrypoints) {
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
- if (gravity >= 85)
2781
- score += 2;
2782
- if (heat >= 70)
2783
- score += 2;
2784
- if (maxNesting >= 4)
2785
- score += 1;
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.sideEffectProfile.includes("payment_mutation"))
2805
- file.sideEffectProfile.push("payment_mutation");
2806
- if (!file.sideEffectProfile.includes("webhook_ingress"))
2807
- file.sideEffectProfile.push("webhook_ingress");
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
- if (file.sideEffectProfile.includes("payment_mutation") || file.sideEffectProfile.includes("booking_mutation")) {
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, binding) {
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 severity = computeSeverity(f.sideEffectProfile, f.productDomain, f.gravity, f.heat, f.heatSignals.maxNesting, f.heatSignals.longFunctions > 0, f.heatSignals.swallowedCatches, f.runtimeEntrypoints);
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
- const deltaTargets = Object.values(persisted).filter((pf) => pf.isRealSource).sort((a, b) => b.gravity - a.gravity).map((pf) => ({
2879
- path: pf.relativePath,
2880
- gravity: Math.round(pf.gravity),
2881
- isLoadBearing: pf.canonicalLoadBearing,
2882
- // STRICT: fanIn >= 10
2883
- blastRadius: pf.importedBy,
2884
- pillarHint: pf.pillarHint
2885
- }));
2886
- const store = { files: persisted };
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, deltaTargets, validationReport };
3236
+ return { store, validationReport };
2896
3237
  }
2897
- async function buildValidationReport(store, deltaTargets, projectRoot, cr) {
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.sideEffectProfile.includes("booking_mutation") || pf.source.includes("createBooking") || pf.source.includes("handleNewBooking"))) {
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.sideEffectProfile.includes("webhook_ingress");
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.sideEffectProfile.includes("webhook_ingress"),
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.sideEffectProfile.includes("payment_mutation"),
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, binding);
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 readFile6(absPath, "utf8");
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 readFile7, writeFile as writeFile6, mkdir as mkdir5 } from "fs/promises";
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 readFile7(p, "utf8");
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 readFile8 } from "fs/promises";
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 readFile8(dossierPath, "utf8");
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/ProofValidator.js
3266
- import { createHash } from "crypto";
3267
- import { readFile as readFile9 } from "fs/promises";
3268
- import { existsSync as existsSync4 } from "fs";
3269
- var ProofValidator = class {
3270
- /**
3271
- * Full validation of a WorkerReceipt against a Work Order's required proofs.
3272
- * Checks all 8 required conditions from the spec.
3273
- *
3274
- * @param receipt - The WorkerReceipt to validate
3275
- * @param requiredProof - Proof descriptors from the Work Order
3276
- * @param isAllowedFile - Predicate built by the CLI layer (handles globs/allowedFiles)
3277
- * @param blobDir - Path to .vibe-splainer/blobs/
3278
- */
3279
- static async validate(receipt, requiredProof, isAllowedFile, blobDir) {
3280
- const errors = [];
3281
- const warnings = [];
3282
- for (const req of requiredProof) {
3283
- const found = receipt.proofPointers.find((p) => p.schemaName === req.schemaName || p.pointer.includes(req.proofId));
3284
- if (!found) {
3285
- errors.push(`MissingProof: required proof "${req.proofId}" (schema: ${req.schemaName}) not in receipt`);
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
- for (const proof of receipt.proofPointers) {
3289
- const blobPath = resolveBlobPath(blobDir, proof.contentHash);
3290
- if (!existsSync4(blobPath)) {
3291
- errors.push(`UnresolvablePointer: blob not found for pointer ${proof.pointer} (hash: ${proof.contentHash})`);
3292
- continue;
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
- const actualHash = await hashBlob(blobPath);
3295
- if (actualHash !== proof.contentHash) {
3296
- errors.push(`HashMismatch: proof ${proof.pointer} expected ${proof.contentHash}, got ${actualHash}`);
3297
- continue;
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
- const reqDescriptor = requiredProof.find((r) => r.schemaName === proof.schemaName);
3300
- if (!reqDescriptor) {
3301
- warnings.push(`UnknownSchema: proof ${proof.pointer} has schema "${proof.schemaName}" not listed in requiredProof`);
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
- if (proof.schemaName.startsWith("test_report")) {
3304
- try {
3305
- const blobContent = await readFile9(blobPath, "utf8");
3306
- const report = JSON.parse(blobContent);
3307
- if (report.status !== "pass" && report.passed !== true && report.success !== true) {
3308
- errors.push(`TestFailed: proof ${proof.pointer} (schema: ${proof.schemaName}) reports status "${report.status ?? "unknown"}"`);
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
- for (const changed of receipt.changedFiles) {
3316
- if (!isAllowedFile(changed.path)) {
3317
- errors.push(`ScopeViolation: patch touched out-of-scope file "${changed.path}"`);
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
- for (const changed of receipt.changedFiles) {
3321
- if (!changed.prePatchHash.startsWith("sha256:") && changed.prePatchHash !== "sha256:new") {
3322
- errors.push(`InvalidHash: prePatchHash for ${changed.path} is not sha256 format`);
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 (!changed.postPatchHash.startsWith("sha256:")) {
3325
- errors.push(`InvalidHash: postPatchHash for ${changed.path} is not sha256 format`);
4049
+ if (adapterMatchCount > 0) {
4050
+ reasons.push(`Candidate file metadata matches domain: ${adapterMatchCount} files`);
3326
4051
  }
3327
- const matchingPatch = receipt.proofPointers.find((p) => p.schemaName === "patch_hash" && p.contentHash === changed.postPatchHash);
3328
- if (!matchingPatch) {
3329
- warnings.push(`NoMatchingPatchProof: no patch_hash proof matches postPatchHash for ${changed.path}`);
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
- valid: errors.length === 0,
3334
- errors,
3335
- warnings
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
- function resolveBlobPath(blobDir, contentHash) {
3340
- const hex = contentHash.replace("sha256:", "");
3341
- return `${blobDir}/sha256_${hex}`;
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
- async function hashBlob(blobPath) {
3344
- const buf = await readFile9(blobPath);
3345
- return `sha256:${createHash("sha256").update(buf).digest("hex")}`;
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 writeFile7, mkdir as mkdir6, rm, rename } from "fs/promises";
3351
- import { createHash as createHash2 } from "crypto";
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: existsSync9 } = await import("fs");
4743
+ const { existsSync: existsSync8 } = await import("fs");
3365
4744
  const { cp } = await import("fs/promises");
3366
- if (existsSync9(outputDir)) {
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 writeFile7(destPath, artifact.content);
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:" + createHash2("sha256").update(buffer).digest("hex"),
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 writeFile7(join10(stagingDir, "artifact_manifest.json"), JSON.stringify(manifest, null, 2), "utf8");
4770
+ await writeFile6(join10(stagingDir, "artifact_manifest.json"), JSON.stringify(manifest, null, 2), "utf8");
3392
4771
  let swapped = false;
3393
- if (existsSync9(outputDir)) {
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 dirname3, relative as relative3 } from "path";
3430
- import { fileURLToPath as fileURLToPath2 } from "url";
3431
- import { existsSync as existsSync5, readFileSync, readdirSync, statSync } from "fs";
3432
- var __dirname2 = dirname3(fileURLToPath2(import.meta.url));
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 = readFileSync(file, "utf8");
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: readFileSync(file)
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
- bindings;
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 createHash3 } from "crypto";
3708
- import { mkdir as mkdir7, writeFile as writeFile8, open, rename as rename2, stat } from "fs/promises";
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 = createHash3("sha256").update(buf).digest("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 writeFile8(tmpPath, buf);
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: readFile16 } = await import("fs/promises");
3744
- return readFile16(blobPath);
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: readFile16 } = await import("fs/promises");
3758
- const buf = await readFile16(blobPath);
3759
- const hex = createHash3("sha256").update(buf).digest("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: readdir3 } = await import("fs/promises");
3769
- const files = await readdir3(this.blobsDir);
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: readFile16 } = await import("fs/promises");
3786
- const buf = await readFile16(filePath);
3787
- return `sha256:${createHash3("sha256").update(buf).digest("hex")}`;
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(resolve) {
3802
- resolve(value);
5141
+ return value instanceof P ? value : new P(function(resolve2) {
5142
+ resolve2(value);
3803
5143
  });
3804
5144
  }
3805
- return new (P || (P = Promise))(function(resolve, reject) {
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 ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);
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((resolve, reject) => {
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((resolve) => {
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(resolve) {
3949
- resolve(value);
5288
+ return value instanceof P ? value : new P(function(resolve2) {
5289
+ resolve2(value);
3950
5290
  });
3951
5291
  }
3952
- return new (P || (P = Promise))(function(resolve, reject) {
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 ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);
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, bindings).render(viewModel, finalStore));
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 createHash4 } from "crypto";
4337
- import { readFile as readFile10 } from "fs/promises";
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 readFile10(filepath, "utf8");
4357
- const newHash = createHash4("sha256").update(content).digest("hex");
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 handleScanProject(args, options = {}) {
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
- await startWatcher(projectRoot, result.files.map((f) => f.path));
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: statusMsg,
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 readFile11 } from "fs/promises";
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 readFile11(fullPath, "utf8");
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 createHash5 } from "crypto";
4616
- import { readFile as readFile12 } from "fs/promises";
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 readFile12(join16(projectRoot, primaryFile), "utf8");
6059
+ primaryContent = await readFile9(join16(projectRoot, primaryFile), "utf8");
4715
6060
  } catch {
4716
6061
  }
4717
- const hash = createHash5("sha256").update(primaryContent).digest("hex");
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 readFile13 } from "fs/promises";
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 readFile13(absolutePath, "utf8");
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 readFile14 } from "fs/promises";
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 readFile14(absolutePath, "utf8");
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 & Proof
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/export.js
7815
- async function exportCommand(projectRoot, options) {
7816
- const root = projectRoot || process.cwd();
7817
- console.error(`[vibe-splain] Exporting dossier for ${root}`);
7818
- const dossier = await readDossier(root);
7819
- if (!dossier) {
7820
- console.error("[vibe-splain] Dossier not found. Run scan first.");
7821
- process.exit(1);
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 orchestrator = new ExportOrchestrator(root);
7824
- await orchestrator.writeBundle(dossier, {
7825
- format: options.format,
7826
- budget: options.budget ? parseInt(options.budget, 10) : void 0,
7827
- scope: options.scope
9061
+ const packet = buildExpertNetworkPacket({
9062
+ task,
9063
+ projectRoot,
9064
+ scanArtifact,
9065
+ errorTrace: options.errorTrace,
9066
+ intendedBehavior: options.intendedBehavior,
9067
+ maxFiles
7828
9068
  });
7829
- console.error("[vibe-splain] Export complete.");
7830
- }
7831
-
7832
- // dist/commands/gc.js
7833
- import { join as join21 } from "path";
7834
- import { rm as rm2, readdir as readdir2 } from "fs/promises";
7835
- var DEFAULT_KEEP_SCANS = 3;
7836
- async function gcCommand(projectRoot, opts = {}) {
7837
- const root = projectRoot ?? process.cwd();
7838
- const keepScans = opts.keepScans ?? DEFAULT_KEEP_SCANS;
7839
- console.error(`[vibe-splain gc] Running GC on ${root} (keeping last ${keepScans} scans)`);
7840
- const pointerStore = PointerStore.open(root);
7841
- const blobStore = new BlobStore(root);
7842
- const allScanIds = pointerStore.listAllScanIds();
7843
- console.error(`[vibe-splain gc] Found ${allScanIds.length} scans`);
7844
- const sorted = [...allScanIds].sort().reverse();
7845
- const keepIds = sorted.slice(0, keepScans);
7846
- const deleteIds = sorted.slice(keepScans);
7847
- if (deleteIds.length === 0) {
7848
- console.error("[vibe-splain gc] Nothing to collect");
7849
- return;
7850
- }
7851
- const keptPointers = keepIds.flatMap((id) => pointerStore.listPointersByScan(id));
7852
- const referencedBlobs = new Set(keptPointers.map((p) => p.blobPath));
7853
- const deleted = await pointerStore.gcScanPointers(keepIds);
7854
- console.error(`[vibe-splain gc] Deleted ${deleted} pointer rows`);
7855
- const allBlobs = await blobStore.listBlobPaths();
7856
- let blobsDeleted = 0;
7857
- for (const blobPath of allBlobs) {
7858
- if (!referencedBlobs.has(blobPath)) {
7859
- try {
7860
- await rm2(blobPath);
7861
- blobsDeleted++;
7862
- } catch {
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
- console.error(`[vibe-splain gc] Deleted ${blobsDeleted} unreferenced blobs`);
7867
- const tmpDir = join21(root, ".vibe-splainer", "tmp");
7868
- try {
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/bundle.js
7880
- import { join as join22 } from "path";
7881
- import { writeFile as writeFile10, mkdir as mkdir9, copyFile, rm as rm3 } from "fs/promises";
7882
- import { existsSync as existsSync7 } from "fs";
7883
- import * as tar from "tar";
7884
- async function bundleCommand(scanId, opts = {}) {
7885
- const root = opts.projectRoot ?? process.cwd();
7886
- const outputPath = opts.output ?? join22(root, `vibe-bundle-${scanId}.tar.gz`);
7887
- console.error(`[vibe-splain bundle] Bundling scan ${scanId} from ${root}`);
7888
- const pointerStore = PointerStore.open(root);
7889
- const blobStore = new BlobStore(root);
7890
- const pointers = pointerStore.listPointersByScan(scanId);
7891
- if (pointers.length === 0) {
7892
- throw new Error(`No pointers found for scanId "${scanId}"`);
7893
- }
7894
- const stagingDir = join22(root, ".vibe-splainer", "tmp", `bundle-stage-${scanId}`);
7895
- const blobsStageDir = join22(stagingDir, "blobs");
7896
- await mkdir9(blobsStageDir, { recursive: true });
7897
- try {
7898
- const bundleManifest = {
7899
- schemaVersion: "1.0.0",
7900
- scanId,
7901
- exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
7902
- projectRoot: root,
7903
- pointers: pointers.map((p) => ({
7904
- pointerId: p.pointerId,
7905
- scanId: p.scanId,
7906
- artifactName: p.artifactName,
7907
- contentHash: p.contentHash,
7908
- blobFile: `blobs/${p.contentHash.replace("sha256:", "sha256_")}`,
7909
- schemaVersion: p.schemaVersion,
7910
- createdAt: p.createdAt,
7911
- expiresAt: p.expiresAt
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
- await writeFile10(join22(stagingDir, "bundle-manifest.json"), JSON.stringify(bundleManifest, null, 2), "utf8");
7915
- const seen = /* @__PURE__ */ new Set();
7916
- for (const p of pointers) {
7917
- const hex = p.contentHash.replace("sha256:", "");
7918
- if (seen.has(hex))
7919
- continue;
7920
- seen.add(hex);
7921
- const srcPath = p.blobPath;
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
- await tar.create({
7929
- gzip: true,
7930
- file: outputPath,
7931
- cwd: stagingDir,
7932
- portable: true
7933
- }, ["."]);
7934
- console.error(`[vibe-splain bundle] Bundle written: ${outputPath}`);
7935
- console.error(`[vibe-splain bundle] ${pointers.length} pointers, ${seen.size} blobs`);
7936
- } finally {
7937
- await rm3(stagingDir, { recursive: true, force: true });
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/importBundle.js
7942
- import { join as join23 } from "path";
7943
- import { readFile as readFile15, mkdir as mkdir10, rm as rm4 } from "fs/promises";
7944
- import { existsSync as existsSync8 } from "fs";
7945
- import * as tar2 from "tar";
7946
- import { createHash as createHash6 } from "crypto";
7947
- async function importBundleCommand(tarballPath, opts = {}) {
7948
- const root = opts.projectRoot ?? process.cwd();
7949
- const namespace = opts.namespace ?? `imported_${Date.now()}`;
7950
- console.error(`[vibe-splain import] Importing ${tarballPath} into ${root} (namespace: ${namespace})`);
7951
- if (!existsSync8(tarballPath)) {
7952
- throw new Error(`Tarball not found: ${tarballPath}`);
7953
- }
7954
- const extractDir = join23(root, ".vibe-splainer", "tmp", `import-${namespace}`);
7955
- await mkdir10(extractDir, { recursive: true });
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
- await tar2.extract({
7958
- file: tarballPath,
7959
- cwd: extractDir
7960
- });
7961
- const manifestPath = join23(extractDir, "bundle-manifest.json");
7962
- if (!existsSync8(manifestPath)) {
7963
- throw new Error("Invalid bundle: missing bundle-manifest.json");
7964
- }
7965
- const manifestRaw = await readFile15(manifestPath, "utf8");
7966
- const manifest = JSON.parse(manifestRaw);
7967
- if (manifest.schemaVersion !== "1.0.0") {
7968
- throw new Error(`Unsupported bundle schema version: ${manifest.schemaVersion}`);
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
- if (hashErrors > 0) {
8005
- console.error(`[vibe-splain import] Warning: ${hashErrors} blobs failed hash verification and were skipped`);
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("export [projectRoot]").description("Manually trigger bundle generation").option("--format <format>", "Export format (html, markdown, etc.)").option("--budget <budget>", "Token budget for markdown").option("--scope <scope>", "Scope for export").action(exportCommand);
8020
- program.command("gc [projectRoot]").description("Garbage-collect old scan artifacts, keeping the last N scans").option("--keep-scans <n>", "Number of scans to keep (default: 3)", "3").action((projectRoot, options) => {
8021
- gcCommand(projectRoot, { keepScans: parseInt(options.keepScans, 10) }).catch((err) => {
8022
- console.error("[vibe-splain gc] Error:", err.message);
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();