vibe-splain 3.5.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +110 -158
  2. package/dist/commands/install.d.ts +0 -5
  3. package/dist/commands/install.js +5 -55
  4. package/dist/commands/serve.js +1 -1
  5. package/dist/export/ArtifactBundleWriter.js +3 -3
  6. package/dist/export/ExportOrchestrator.d.ts +2 -20
  7. package/dist/export/ExportOrchestrator.js +5 -116
  8. package/dist/export/Watcher.js +5 -5
  9. package/dist/export/renderers/AgentMarkdownRenderer.d.ts +1 -1
  10. package/dist/export/renderers/GraphRenderer.d.ts +2 -2
  11. package/dist/export/renderers/HtmlRenderer.d.ts +1 -1
  12. package/dist/export/renderers/HtmlRenderer.js +1 -1
  13. package/dist/export/renderers/JsonRenderer.d.ts +1 -1
  14. package/dist/export/renderers/RawAnalysisRenderer.d.ts +1 -1
  15. package/dist/export/renderers/Renderer.d.ts +1 -1
  16. package/dist/export/renderers/ValidationRenderer.d.ts +1 -1
  17. package/dist/index.js +441 -3787
  18. package/dist/mcp/server.js +6 -23
  19. package/dist/mcp/tools/get_file_context.js +1 -1
  20. package/dist/mcp/tools/get_project_map.js +1 -1
  21. package/dist/mcp/tools/get_strategic_overview.js +1 -1
  22. package/dist/mcp/tools/get_wild_discoveries.js +1 -1
  23. package/dist/mcp/tools/inspect_pillar.js +1 -1
  24. package/dist/mcp/tools/mark_stale.js +1 -1
  25. package/dist/mcp/tools/scan_project.d.ts +2 -3
  26. package/dist/mcp/tools/scan_project.js +12 -13
  27. package/dist/mcp/tools/set_project_brief.js +1 -1
  28. package/dist/mcp/tools/write_decision_card.d.ts +1 -1
  29. package/dist/mcp/tools/write_decision_card.js +2 -2
  30. package/dist/ui/index.html +1 -1
  31. package/package.json +9 -14
  32. package/dist/commands/hook.d.ts +0 -9
  33. package/dist/commands/hook.js +0 -84
  34. package/dist/hook/preToolUse.d.ts +0 -30
  35. package/dist/hook/preToolUse.js +0 -81
  36. package/dist/hook.d.ts +0 -1
  37. package/dist/hook.js +0 -337
  38. package/dist/mcp/BudgetGuard.d.ts +0 -13
  39. package/dist/mcp/BudgetGuard.js +0 -55
  40. package/dist/mcp/SessionScope.d.ts +0 -26
  41. package/dist/mcp/SessionScope.js +0 -56
  42. package/dist/mcp/tools/get_file_skeleton.d.ts +0 -23
  43. package/dist/mcp/tools/get_file_skeleton.js +0 -124
  44. package/dist/mcp/tools/hydration/get_evidence_slice.d.ts +0 -31
  45. package/dist/mcp/tools/hydration/get_evidence_slice.js +0 -57
  46. package/dist/mcp/tools/hydration/get_project_summary.d.ts +0 -23
  47. package/dist/mcp/tools/hydration/get_project_summary.js +0 -58
  48. package/dist/mcp/tools/hydration/get_start_here.d.ts +0 -23
  49. package/dist/mcp/tools/hydration/get_start_here.js +0 -52
  50. package/dist/mcp/tools/read_file.d.ts +0 -31
  51. package/dist/mcp/tools/read_file.js +0 -90
  52. package/dist/store/BlobStore.d.ts +0 -22
  53. package/dist/store/BlobStore.js +0 -96
  54. package/dist/store/PointerStore.d.ts +0 -52
  55. package/dist/store/PointerStore.js +0 -154
package/dist/index.js CHANGED
@@ -11,14 +11,14 @@ var __export = (target, all) => {
11
11
 
12
12
  // ../brain/dist/pipeline/inventory.js
13
13
  import Parser from "web-tree-sitter";
14
- import { join as join2, dirname as dirname2, relative, extname, basename, sep } from "path";
15
- import { fileURLToPath as fileURLToPath2 } from "url";
14
+ import { join as join2, dirname, relative, extname, basename, sep } from "path";
15
+ import { fileURLToPath } from "url";
16
16
  import { createRequire } from "module";
17
17
  import { readFile as readFile2, readdir, writeFile as writeFile2, mkdir } from "fs/promises";
18
18
  import { existsSync as existsSync2 } from "fs";
19
19
  function resolveWasm(file) {
20
20
  try {
21
- const wasmsDir = dirname2(require2.resolve("tree-sitter-wasms/package.json"));
21
+ const wasmsDir = dirname(require2.resolve("tree-sitter-wasms/package.json"));
22
22
  const p = join2(wasmsDir, "out", file);
23
23
  if (existsSync2(p))
24
24
  return p;
@@ -33,7 +33,7 @@ async function getLanguage(lang) {
33
33
  return cached;
34
34
  const wasm = resolveWasm(LANG_WASM[lang]);
35
35
  if (!wasm) {
36
- console.error(`[vibe-splain] grammar missing for ${lang} (${LANG_WASM[lang]}); skipping`);
36
+ console.error(`[vibesplain] grammar missing for ${lang} (${LANG_WASM[lang]}); skipping`);
37
37
  return null;
38
38
  }
39
39
  try {
@@ -41,7 +41,7 @@ async function getLanguage(lang) {
41
41
  langCache.set(lang, loaded);
42
42
  return loaded;
43
43
  } catch (err) {
44
- console.error(`[vibe-splain] failed to load grammar for ${lang}:`, err instanceof Error ? err.message : err);
44
+ console.error(`[vibesplain] failed to load grammar for ${lang}:`, err instanceof Error ? err.message : err);
45
45
  return null;
46
46
  }
47
47
  }
@@ -57,10 +57,13 @@ async function initParser() {
57
57
  }
58
58
  async function parseAs(lang, source) {
59
59
  const p = await initParser();
60
- const language = await getLanguage(lang);
61
- if (!language)
62
- return null;
63
- p.setLanguage(language);
60
+ if (_parserCurrentLang !== lang) {
61
+ const language = await getLanguage(lang);
62
+ if (!language)
63
+ return null;
64
+ p.setLanguage(language);
65
+ _parserCurrentLang = lang;
66
+ }
64
67
  try {
65
68
  return p.parse(source);
66
69
  } catch {
@@ -85,8 +88,8 @@ async function collectFiles(dir, projectRoot, acc) {
85
88
  if (entry.isDirectory()) {
86
89
  await collectFiles(fullPath, projectRoot, acc);
87
90
  } else if (entry.isFile()) {
88
- const ext2 = extname(entry.name);
89
- if (!SUPPORTED_EXTENSIONS.has(ext2))
91
+ const ext = extname(entry.name);
92
+ if (!SUPPORTED_EXTENSIONS.has(ext))
90
93
  continue;
91
94
  if (EXCLUDE_FILE_PATTERNS.some((p) => p.test(entry.name)))
92
95
  continue;
@@ -420,7 +423,7 @@ async function detectStackAndEntrypoints(projectRoot, files) {
420
423
  const b = basename(r);
421
424
  if (b === "main.py" || b === "__main__.py")
422
425
  entrypoints.add(r);
423
- if (/^index\.(ts|tsx|js|jsx|mjs|cjs)$/.test(b) && dirname2(r).split(sep).length <= 2)
426
+ if (/^index\.(ts|tsx|js|jsx|mjs|cjs)$/.test(b) && dirname(r).split(sep).length <= 2)
424
427
  entrypoints.add(r);
425
428
  if (b === "main.go" && r.includes("cmd" + sep))
426
429
  entrypoints.add(r);
@@ -759,8 +762,8 @@ async function runInventory(projectRoot) {
759
762
  const work = [];
760
763
  for (const file of abs) {
761
764
  const rel = relative(projectRoot, file);
762
- const ext2 = extname(file);
763
- const lang = EXT_LANG[ext2];
765
+ const ext = extname(file);
766
+ const lang = EXT_LANG[ext];
764
767
  if (!lang)
765
768
  continue;
766
769
  let source;
@@ -786,7 +789,6 @@ async function runInventory(projectRoot) {
786
789
  lang,
787
790
  source,
788
791
  ast,
789
- tree,
790
792
  importSpecs,
791
793
  rawNamedImports,
792
794
  pathDemote: pathDemoteReason(rel),
@@ -794,7 +796,7 @@ async function runInventory(projectRoot) {
794
796
  productDomain
795
797
  });
796
798
  }
797
- const dir = join2(projectRoot, ".vibe-splainer");
799
+ const dir = join2(projectRoot, ".vibesplain");
798
800
  await mkdir(dir, { recursive: true });
799
801
  const stage01 = {
800
802
  files: work.map((w) => ({
@@ -806,20 +808,23 @@ async function runInventory(projectRoot) {
806
808
  totalCount: work.length,
807
809
  realSourceCount: work.filter((w) => !w.pathDemote).length
808
810
  };
809
- await writeFile2(join2(dir, "stage-01-inventory.json"), JSON.stringify(stage01, null, 2), "utf8");
810
811
  const stage02 = Object.fromEntries(work.map((w) => [w.rel, w.frameworkRole]));
811
- await writeFile2(join2(dir, "stage-02-framework-roles.json"), JSON.stringify(stage02, null, 2), "utf8");
812
812
  const stage03 = Object.fromEntries(work.map((w) => [w.rel, w.productDomain]));
813
- await writeFile2(join2(dir, "stage-03-domains.json"), JSON.stringify(stage03, null, 2), "utf8");
813
+ await Promise.all([
814
+ writeFile2(join2(dir, "stage-01-inventory.json"), JSON.stringify(stage01, null, 2), "utf8"),
815
+ writeFile2(join2(dir, "stage-02-framework-roles.json"), JSON.stringify(stage02, null, 2), "utf8"),
816
+ writeFile2(join2(dir, "stage-03-domains.json"), JSON.stringify(stage03, null, 2), "utf8")
817
+ ]);
814
818
  return { projectRoot, work, stack, entrypoints, fileSet, basenameIndex };
815
819
  }
816
- var __dirname, require2, _parser, langCache, EXT_LANG, LANG_WASM, SUPPORTED_EXTENSIONS, EXCLUDE_DIRS, EXCLUDE_FILE_PATTERNS, DEMOTE_SEGMENTS, VENDOR_SEGMENTS, PILLAR_KEYWORDS, PILLAR_PATH_PATTERNS, MEANINGLESS_SEGMENTS, FUNCTION_TYPES, NESTING_TYPES, DECISION_TYPES, CATCH_TYPES, LONG_FN_LOC, DEEP_NESTING, GOD_FILE_LOC, GOD_FILE_EXPORTS, TODO_RE, SUPPRESS_RE, SMELL_WEIGHT;
820
+ var __dirname, require2, _parser, _parserCurrentLang, langCache, EXT_LANG, LANG_WASM, SUPPORTED_EXTENSIONS, EXCLUDE_DIRS, EXCLUDE_FILE_PATTERNS, DEMOTE_SEGMENTS, VENDOR_SEGMENTS, PILLAR_KEYWORDS, PILLAR_PATH_PATTERNS, MEANINGLESS_SEGMENTS, FUNCTION_TYPES, NESTING_TYPES, DECISION_TYPES, CATCH_TYPES, LONG_FN_LOC, DEEP_NESTING, GOD_FILE_LOC, GOD_FILE_EXPORTS, TODO_RE, SUPPRESS_RE, SMELL_WEIGHT;
817
821
  var init_inventory = __esm({
818
822
  "../brain/dist/pipeline/inventory.js"() {
819
823
  "use strict";
820
- __dirname = dirname2(fileURLToPath2(import.meta.url));
824
+ __dirname = dirname(fileURLToPath(import.meta.url));
821
825
  require2 = createRequire(import.meta.url);
822
826
  _parser = null;
827
+ _parserCurrentLang = null;
823
828
  langCache = /* @__PURE__ */ new Map();
824
829
  EXT_LANG = {
825
830
  ".ts": "typescript",
@@ -849,7 +854,7 @@ var init_inventory = __esm({
849
854
  "build",
850
855
  ".next",
851
856
  "out",
852
- ".vibe-splainer",
857
+ ".vibesplain",
853
858
  ".git",
854
859
  ".venv",
855
860
  "venv",
@@ -1084,7 +1089,7 @@ var init_inventory = __esm({
1084
1089
  });
1085
1090
 
1086
1091
  // ../brain/dist/pipeline/resolution.js
1087
- import { join as join3, dirname as dirname3, relative as relative2, extname as extname2, sep as sep2 } from "path";
1092
+ import { join as join3, dirname as dirname2, relative as relative2, extname as extname2, sep as sep2 } from "path";
1088
1093
  import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir2 } from "fs/promises";
1089
1094
  import { existsSync as existsSync3 } from "fs";
1090
1095
  function parseJsonLenient(text) {
@@ -1109,7 +1114,7 @@ async function discoverAllTsConfigs(dir, projectRoot, maxDepth = 4) {
1109
1114
  for (const entry of entries) {
1110
1115
  const fullPath = join3(dir, entry.name);
1111
1116
  if (entry.isDirectory()) {
1112
- if (entry.name === "node_modules" || entry.name === ".git")
1117
+ if (TSCONFIG_SKIP_DIRS.has(entry.name))
1113
1118
  continue;
1114
1119
  const sub = await discoverAllTsConfigs(fullPath, projectRoot, maxDepth - 1);
1115
1120
  Object.assign(result, sub);
@@ -1136,9 +1141,9 @@ async function extractTsConfigPaths(tsconfigPath, projectRoot, depth = 0) {
1136
1141
  if (typeof parsed.extends === "string") {
1137
1142
  let baseFile = parsed.extends;
1138
1143
  if (baseFile.startsWith(".")) {
1139
- baseFile = join3(dirname3(tsconfigPath), baseFile);
1144
+ baseFile = join3(dirname2(tsconfigPath), baseFile);
1140
1145
  } else {
1141
- let currentDir = dirname3(tsconfigPath);
1146
+ let currentDir = dirname2(tsconfigPath);
1142
1147
  let found = false;
1143
1148
  while (currentDir.length >= projectRoot.length || currentDir === projectRoot) {
1144
1149
  const candidate = join3(currentDir, "node_modules", baseFile + (baseFile.endsWith(".json") ? "" : ".json"));
@@ -1147,7 +1152,7 @@ async function extractTsConfigPaths(tsconfigPath, projectRoot, depth = 0) {
1147
1152
  found = true;
1148
1153
  break;
1149
1154
  }
1150
- const parent = dirname3(currentDir);
1155
+ const parent = dirname2(currentDir);
1151
1156
  if (parent === currentDir)
1152
1157
  break;
1153
1158
  currentDir = parent;
@@ -1162,7 +1167,7 @@ async function extractTsConfigPaths(tsconfigPath, projectRoot, depth = 0) {
1162
1167
  Object.assign(result, base);
1163
1168
  }
1164
1169
  const opts = parsed.compilerOptions || {};
1165
- const baseUrl = typeof opts.baseUrl === "string" ? join3(dirname3(tsconfigPath), opts.baseUrl) : dirname3(tsconfigPath);
1170
+ const baseUrl = typeof opts.baseUrl === "string" ? join3(dirname2(tsconfigPath), opts.baseUrl) : dirname2(tsconfigPath);
1166
1171
  if (typeof opts.baseUrl === "string") {
1167
1172
  const relBase = relative2(projectRoot, baseUrl);
1168
1173
  if (relBase && relBase !== ".") {
@@ -1236,10 +1241,10 @@ async function buildAliasMap(projectRoot) {
1236
1241
  function tryJsCandidates(base, projectRoot, fileSet) {
1237
1242
  const candidates = [];
1238
1243
  candidates.unshift(base);
1239
- for (const ext2 of JS_EXTS)
1240
- candidates.push(base + ext2);
1241
- for (const ext2 of JS_EXTS)
1242
- candidates.push(join3(base, "index" + ext2));
1244
+ for (const ext of JS_EXTS)
1245
+ candidates.push(base + ext);
1246
+ for (const ext of JS_EXTS)
1247
+ candidates.push(join3(base, "index" + ext));
1243
1248
  for (const c of candidates) {
1244
1249
  const rel = relative2(projectRoot, c);
1245
1250
  if (fileSet.has(rel))
@@ -1251,9 +1256,9 @@ function resolvePython(spec, fromAbs, projectRoot, fileSet) {
1251
1256
  let modulePath;
1252
1257
  if (spec.startsWith(".")) {
1253
1258
  const dots = spec.match(/^\.+/)[0].length;
1254
- let dir = dirname3(fromAbs);
1259
+ let dir = dirname2(fromAbs);
1255
1260
  for (let i = 1; i < dots; i++)
1256
- dir = dirname3(dir);
1261
+ dir = dirname2(dir);
1257
1262
  const rest = spec.slice(dots).replace(/\./g, sep2);
1258
1263
  modulePath = rest ? join3(dir, rest) : dir;
1259
1264
  } else {
@@ -1287,7 +1292,7 @@ function resolveImportWithAliasMap(spec, fromAbs, lang, projectRoot, fileSet, ba
1287
1292
  }
1288
1293
  if (lang === "typescript" || lang === "tsx" || lang === "javascript") {
1289
1294
  if (spec.startsWith(".")) {
1290
- const base = join3(dirname3(fromAbs), spec);
1295
+ const base = join3(dirname2(fromAbs), spec);
1291
1296
  return { resolved: tryJsCandidates(base, projectRoot, fileSet), isAlias: false };
1292
1297
  }
1293
1298
  for (const [prefix, replacement] of Object.entries(aliasMap.resolvedAliases)) {
@@ -1352,11 +1357,18 @@ async function runResolution(projectRoot, inv) {
1352
1357
  const resolutionFailuresByFile = {};
1353
1358
  const resolutionFailureReasons = {};
1354
1359
  const unresolvedSet = /* @__PURE__ */ new Set();
1360
+ const resolveCache = /* @__PURE__ */ new Map();
1355
1361
  for (const w of work) {
1356
1362
  const distinctModules = /* @__PURE__ */ new Set();
1357
1363
  for (const spec of w.importSpecs) {
1358
1364
  distinctModules.add(spec);
1359
- const { resolved, isAlias, reason } = resolveImportWithAliasMap(spec, w.abs, w.lang, projectRoot, fileSet, basenameIndex, aliasMap);
1365
+ const cacheKey = `${w.abs}\0${spec}`;
1366
+ let cached = resolveCache.get(cacheKey);
1367
+ if (!cached) {
1368
+ cached = resolveImportWithAliasMap(spec, w.abs, w.lang, projectRoot, fileSet, basenameIndex, aliasMap);
1369
+ resolveCache.set(cacheKey, cached);
1370
+ }
1371
+ const { resolved, isAlias, reason } = cached;
1360
1372
  if (resolved && resolved !== w.rel && importedBy.has(resolved)) {
1361
1373
  importedBy.get(resolved).add(w.rel);
1362
1374
  importsResolved.get(w.rel).add(resolved);
@@ -1376,7 +1388,7 @@ async function runResolution(projectRoot, inv) {
1376
1388
  fanOut.set(w.rel, distinctModules.size);
1377
1389
  }
1378
1390
  const unresolvedImports = [...unresolvedSet];
1379
- const dir = join3(projectRoot, ".vibe-splainer");
1391
+ const dir = join3(projectRoot, ".vibesplain");
1380
1392
  await mkdir2(dir, { recursive: true });
1381
1393
  const stage04 = {
1382
1394
  resolvedAliases: aliasMap.resolvedAliases,
@@ -1398,10 +1410,30 @@ async function runResolution(projectRoot, inv) {
1398
1410
  resolutionFailureReasons
1399
1411
  };
1400
1412
  }
1401
- var CONVENTIONAL_ALIASES, JS_EXTS;
1413
+ var TSCONFIG_SKIP_DIRS, CONVENTIONAL_ALIASES, JS_EXTS;
1402
1414
  var init_resolution = __esm({
1403
1415
  "../brain/dist/pipeline/resolution.js"() {
1404
1416
  "use strict";
1417
+ TSCONFIG_SKIP_DIRS = /* @__PURE__ */ new Set([
1418
+ "node_modules",
1419
+ ".git",
1420
+ "dist",
1421
+ "build",
1422
+ ".next",
1423
+ "out",
1424
+ ".cache",
1425
+ ".vibesplain",
1426
+ "__pycache__",
1427
+ "target",
1428
+ ".mypy_cache",
1429
+ ".pytest_cache",
1430
+ ".tox",
1431
+ "venv",
1432
+ ".venv",
1433
+ "env",
1434
+ "site-packages",
1435
+ "vendor"
1436
+ ]);
1405
1437
  CONVENTIONAL_ALIASES = [
1406
1438
  { prefix: "~/", replacement: "modules/" },
1407
1439
  { prefix: "~/", replacement: "" },
@@ -1617,32 +1649,32 @@ function inferGenericWriteIntents(sideEffectProfile) {
1617
1649
  return intents;
1618
1650
  }
1619
1651
  function inferRiskTypesPass1(rel, frameworkRole, productDomain, sideEffectProfile, gravitySignals, smellKinds) {
1620
- const types2 = [];
1652
+ const types = [];
1621
1653
  const smThreshold = ["provider", "store"].includes(frameworkRole) ? 8 : 20;
1622
1654
  if (gravitySignals.cyclomatic > smThreshold)
1623
- types2.push("state_machine");
1655
+ types.push("state_machine");
1624
1656
  if (smellKinds.has("god-file")) {
1625
1657
  if (frameworkRole === "hook")
1626
- types2.push("god_hook");
1658
+ types.push("god_hook");
1627
1659
  else
1628
- types2.push("god_component");
1660
+ types.push("god_component");
1629
1661
  }
1630
1662
  if (sideEffectProfile.length > 3 && !sideEffectProfile.includes("none_detected")) {
1631
- types2.push("side_effect_coupling");
1663
+ types.push("side_effect_coupling");
1632
1664
  }
1633
1665
  if (productDomain === "forms" && (gravitySignals.fanIn > 3 || gravitySignals.publicSurface > 5))
1634
- types2.push("registry_bottleneck");
1666
+ types.push("registry_bottleneck");
1635
1667
  if (sideEffectProfile.some((s) => ["database_write", "trpc_mutation", "external_api_call"].includes(s)) && gravitySignals.cyclomatic > 10)
1636
- types2.push("mutation_orchestration");
1668
+ types.push("mutation_orchestration");
1637
1669
  if (ENTRYPOINT_ROLES.has(frameworkRole) && sideEffectProfile.includes("database_write")) {
1638
- types2.push("route_handler_write_path");
1670
+ types.push("route_handler_write_path");
1639
1671
  }
1640
1672
  if (smellKinds.has("swallowed-catch"))
1641
- types2.push("error_swallowing");
1673
+ types.push("error_swallowing");
1642
1674
  if (sideEffectProfile.includes("local_storage") || sideEffectProfile.includes("indexed_db")) {
1643
- types2.push("storage_persistence_risk");
1675
+ types.push("storage_persistence_risk");
1644
1676
  }
1645
- return types2;
1677
+ return types;
1646
1678
  }
1647
1679
  function findRuntimeEntrypoints(relPath, importedByMap, persisted, maxDepth = 8) {
1648
1680
  const results = [];
@@ -2116,16 +2148,16 @@ async function runClassification(projectRoot, inv, res) {
2116
2148
  const gs = gravitySignalsByFile.get(w.rel);
2117
2149
  const smellKinds = new Set(w.ast.smells.map((s) => s.kind));
2118
2150
  const effects = sideEffectsByFile.get(w.rel);
2119
- const types2 = inferRiskTypesPass1(w.rel, w.frameworkRole, w.productDomain, effects, gs, smellKinds);
2120
- riskTypesByFile.set(w.rel, types2);
2151
+ const types = inferRiskTypesPass1(w.rel, w.frameworkRole, w.productDomain, effects, gs, smellKinds);
2152
+ riskTypesByFile.set(w.rel, types);
2121
2153
  }
2122
2154
  for (const w of work) {
2123
2155
  if (w.productDomain === "forms" && (w.frameworkRole === "component" || w.frameworkRole === "hook")) {
2124
2156
  const importsResolved_w = importsResolved.get(w.rel) || /* @__PURE__ */ new Set();
2125
2157
  const importsAny = [...importsResolved_w, ...w.importSpecs.filter((s) => s.startsWith("@"))];
2126
2158
  const consumesBottleneck = importsAny.some((dep) => {
2127
- const types3 = riskTypesByFile.get(dep);
2128
- return types3?.includes("registry_bottleneck");
2159
+ const types2 = riskTypesByFile.get(dep);
2160
+ return types2?.includes("registry_bottleneck");
2129
2161
  });
2130
2162
  if (consumesBottleneck) {
2131
2163
  const existing = riskTypesByFile.get(w.rel);
@@ -2138,9 +2170,9 @@ async function runClassification(projectRoot, inv, res) {
2138
2170
  existing.splice(idx, 1);
2139
2171
  }
2140
2172
  }
2141
- const types2 = riskTypesByFile.get(w.rel);
2142
- if (types2.length === 0)
2143
- types2.push("complexity_hotspot");
2173
+ const types = riskTypesByFile.get(w.rel);
2174
+ if (types.length === 0)
2175
+ types.push("complexity_hotspot");
2144
2176
  }
2145
2177
  const adapterCtx = {
2146
2178
  projectRoot,
@@ -2178,7 +2210,7 @@ async function runClassification(projectRoot, inv, res) {
2178
2210
  }
2179
2211
  const staticGravityByFile = new Map(gravityByFile);
2180
2212
  if (adapterStage.firedAdapterIds.length > 0) {
2181
- console.error(`[vibe-splain] adapters fired: ${adapterStage.firedAdapterIds.join(", ")}`);
2213
+ console.error(`[vibesplain] adapters fired: ${adapterStage.firedAdapterIds.join(", ")}`);
2182
2214
  for (const w of work) {
2183
2215
  const lift = adapterStage.liftByFile.get(w.rel) ?? 0;
2184
2216
  if (lift > 0) {
@@ -2258,7 +2290,7 @@ async function runClassification(projectRoot, inv, res) {
2258
2290
  adapterPillarLabel: adapterStage.pillarLabelsByFile.get(w.rel)
2259
2291
  });
2260
2292
  }
2261
- const dir = join4(projectRoot, ".vibe-splainer");
2293
+ const dir = join4(projectRoot, ".vibesplain");
2262
2294
  await mkdir3(dir, { recursive: true });
2263
2295
  const stage05 = Object.fromEntries(classified.map((f) => [f.rel, f.sideEffectProfile]));
2264
2296
  await writeFile4(join4(dir, "stage-05-side-effects.json"), JSON.stringify(stage05, null, 2), "utf8");
@@ -2395,7 +2427,7 @@ function deriveConfidence(fanIn, gravity) {
2395
2427
  return "low";
2396
2428
  }
2397
2429
  async function runScoring(projectRoot, cr) {
2398
- const dir = join5(projectRoot, ".vibe-splainer");
2430
+ const dir = join5(projectRoot, ".vibesplain");
2399
2431
  await mkdir4(dir, { recursive: true });
2400
2432
  const persisted = {};
2401
2433
  const severityBreakdowns = {};
@@ -2460,7 +2492,7 @@ async function runScoring(projectRoot, cr) {
2460
2492
  severityBreakdowns[f.rel] = `severity=${pf.canonicalSeverity} loadBearing=${pf.canonicalLoadBearing} effects=${pf.sideEffectProfile.join(",")} domain=${pf.productDomain}`;
2461
2493
  }
2462
2494
  if (adapterFired) {
2463
- console.error(`[vibe-splain] severity bridge: ${sevBridgeChecked - sevBridgeMismatch}/${sevBridgeChecked} domain contributions match core (${sevBridgeMismatch} mismatch)`);
2495
+ console.error(`[vibesplain] severity bridge: ${sevBridgeChecked - sevBridgeMismatch}/${sevBridgeChecked} domain contributions match core (${sevBridgeMismatch} mismatch)`);
2464
2496
  }
2465
2497
  const store = {
2466
2498
  files: persisted,
@@ -2470,10 +2502,10 @@ async function runScoring(projectRoot, cr) {
2470
2502
  const validationReport = await buildValidationReport(store, projectRoot, cr);
2471
2503
  store.validationReport = validationReport;
2472
2504
  for (const e of validationReport.errors) {
2473
- console.error(`[vibe-splain] VALIDATION ERROR [${e.rule}] ${e.file}: ${e.detail}`);
2505
+ console.error(`[vibesplain] VALIDATION ERROR [${e.rule}] ${e.file}: ${e.detail}`);
2474
2506
  }
2475
2507
  for (const w of validationReport.warnings) {
2476
- console.error(`[vibe-splain] VALIDATION WARN [${w.rule}] ${w.file}: ${w.detail}`);
2508
+ console.error(`[vibesplain] VALIDATION WARN [${w.rule}] ${w.file}: ${w.detail}`);
2477
2509
  }
2478
2510
  return { store, validationReport };
2479
2511
  }
@@ -2614,39 +2646,34 @@ async function runPipeline(projectRoot) {
2614
2646
  const res = await runResolution(projectRoot, inv);
2615
2647
  const cr = await runClassification(projectRoot, inv, res);
2616
2648
  const scoring = await runScoring(projectRoot, cr);
2617
- const files = cr.classified.filter((f) => f.isRealSource).sort((a, b) => b.gravity - a.gravity).map((f) => ({
2618
- path: f.abs,
2619
- relativePath: f.rel,
2620
- language: f.lang,
2621
- isRealSource: f.isRealSource,
2622
- demoteReason: f.demoteReason,
2623
- gravity: Math.round(f.gravity),
2624
- heat: Math.round(f.heat),
2625
- gravitySignals: f.gravitySignals,
2626
- heatSignals: f.heatSignals,
2627
- smells: f.smells,
2628
- pillarHint: f.pillarHint,
2629
- frameworkRole: f.executionRole ?? f.frameworkRole,
2630
- productDomain: f.domainTags?.[0] ?? f.adapterDomain ?? f.productDomain,
2631
- sideEffectProfile: f.adapterSideEffects ?? f.sideEffectProfile
2632
- }));
2633
- 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) => ({
2634
- path: f.abs,
2635
- relativePath: f.rel,
2636
- language: f.lang,
2637
- isRealSource: f.isRealSource,
2638
- demoteReason: f.demoteReason,
2639
- gravity: Math.round(f.gravity),
2640
- heat: Math.round(f.heat),
2641
- gravitySignals: f.gravitySignals,
2642
- heatSignals: f.heatSignals,
2643
- smells: f.smells,
2644
- pillarHint: f.pillarHint,
2645
- frameworkRole: f.executionRole ?? f.frameworkRole,
2646
- productDomain: f.domainTags?.[0] ?? f.adapterDomain ?? f.productDomain,
2647
- sideEffectProfile: f.adapterSideEffects ?? f.sideEffectProfile
2648
- }));
2649
- const uiUrl = `file://${join6(projectRoot, ".vibe-splainer", "ui", "index.html")}`;
2649
+ const files = [];
2650
+ const wildCandidates = [];
2651
+ for (const f of cr.classified) {
2652
+ if (!f.isRealSource)
2653
+ continue;
2654
+ const fa = {
2655
+ path: f.abs,
2656
+ relativePath: f.rel,
2657
+ language: f.lang,
2658
+ isRealSource: f.isRealSource,
2659
+ demoteReason: f.demoteReason,
2660
+ gravity: Math.round(f.gravity),
2661
+ heat: Math.round(f.heat),
2662
+ gravitySignals: f.gravitySignals,
2663
+ heatSignals: f.heatSignals,
2664
+ smells: f.smells,
2665
+ pillarHint: f.pillarHint,
2666
+ frameworkRole: f.executionRole ?? f.frameworkRole,
2667
+ productDomain: f.domainTags?.[0] ?? f.adapterDomain ?? f.productDomain,
2668
+ sideEffectProfile: f.adapterSideEffects ?? f.sideEffectProfile
2669
+ };
2670
+ files.push(fa);
2671
+ if (f.heat >= 60 || f.smells.some((s) => s.severity >= 4))
2672
+ wildCandidates.push(fa);
2673
+ }
2674
+ files.sort((a, b) => b.gravity - a.gravity);
2675
+ wildCandidates.sort((a, b) => b.heat - a.heat);
2676
+ const uiUrl = `file://${join6(projectRoot, ".vibesplain", "ui", "index.html")}`;
2650
2677
  return {
2651
2678
  projectRoot,
2652
2679
  totalFilesScanned: cr.classified.length,
@@ -2661,7 +2688,7 @@ async function runPipeline(projectRoot) {
2661
2688
  passed: scoring.validationReport.passed,
2662
2689
  errors: scoring.validationReport.summary.errorCount,
2663
2690
  warnings: scoring.validationReport.summary.warningCount,
2664
- reportPath: ".vibe-splainer/validation_report.json"
2691
+ reportPath: ".vibesplain/validation_report.json"
2665
2692
  },
2666
2693
  fullValidationReport: scoring.validationReport
2667
2694
  };
@@ -2686,8 +2713,8 @@ async function scanProject(projectRoot) {
2686
2713
  return runPipeline(projectRoot);
2687
2714
  }
2688
2715
  async function getFileAnalysis(absPath) {
2689
- const ext2 = extname4(absPath);
2690
- const lang = EXT_LANG[ext2];
2716
+ const ext = extname4(absPath);
2717
+ const lang = EXT_LANG[ext];
2691
2718
  if (!lang)
2692
2719
  return null;
2693
2720
  let source;
@@ -2742,7 +2769,7 @@ var init_scanner = __esm({
2742
2769
  import { join as join7 } from "path";
2743
2770
  import { readFile as readFile5, writeFile as writeFile5, mkdir as mkdir5 } from "fs/promises";
2744
2771
  async function readAnalysis(projectRoot) {
2745
- const p = join7(projectRoot, ".vibe-splainer", "analysis.json");
2772
+ const p = join7(projectRoot, ".vibesplain", "analysis.json");
2746
2773
  try {
2747
2774
  const raw = await readFile5(p, "utf8");
2748
2775
  return JSON.parse(raw);
@@ -2760,7 +2787,7 @@ var init_analysis = __esm({
2760
2787
  import { join as join8 } from "path";
2761
2788
  import { readFile as readFile6 } from "fs/promises";
2762
2789
  async function readDossier(projectRoot) {
2763
- const dossierPath = join8(projectRoot, ".vibe-splainer", "dossier.json");
2790
+ const dossierPath = join8(projectRoot, ".vibesplain", "dossier.json");
2764
2791
  try {
2765
2792
  const raw = await readFile6(dossierPath, "utf8");
2766
2793
  return JSON.parse(raw);
@@ -2774,10 +2801,10 @@ function validateMermaidNodeCount(diagram) {
2774
2801
  const nodePattern = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s*[\[({|>]/gm;
2775
2802
  const statePattern = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s*:/gm;
2776
2803
  const nodes = /* @__PURE__ */ new Set();
2777
- for (const match2 of diagram.matchAll(nodePattern))
2778
- nodes.add(match2[1]);
2779
- for (const match2 of diagram.matchAll(statePattern))
2780
- nodes.add(match2[1]);
2804
+ for (const match of diagram.matchAll(nodePattern))
2805
+ nodes.add(match[1]);
2806
+ for (const match of diagram.matchAll(statePattern))
2807
+ nodes.add(match[1]);
2781
2808
  if (diagram.includes("[*]"))
2782
2809
  nodes.add("[*]");
2783
2810
  return nodes.size <= 7;
@@ -2788,95 +2815,6 @@ var init_dossier = __esm({
2788
2815
  }
2789
2816
  });
2790
2817
 
2791
- // ../brain/dist/policy/RecommendationEngine.js
2792
- function getEffortValue(effort) {
2793
- if (effort === "low")
2794
- return 1;
2795
- if (effort === "medium")
2796
- return 2;
2797
- return 3;
2798
- }
2799
- function getImpactValue(impact) {
2800
- if (impact === "low")
2801
- return 1;
2802
- if (impact === "medium")
2803
- return 2;
2804
- return 3;
2805
- }
2806
- var RecommendationEngine;
2807
- var init_RecommendationEngine = __esm({
2808
- "../brain/dist/policy/RecommendationEngine.js"() {
2809
- "use strict";
2810
- RecommendationEngine = class {
2811
- static generateRecommendations(file) {
2812
- const recommendations = [];
2813
- if (file.riskTypes.includes("mutation_orchestration")) {
2814
- recommendations.push({
2815
- strategy: "Extract Decision Logic",
2816
- description: "Do not rewrite inline. Extract pure decision logic into a tested reducer or state machine first. Preserve all side-effect call sites (redirect URLs, SDK event names, response shapes) as invariants.",
2817
- effort: "high",
2818
- impact: "high"
2819
- });
2820
- }
2821
- if (file.riskTypes.includes("registry_bottleneck")) {
2822
- recommendations.push({
2823
- strategy: "Append-Only Registry Updates",
2824
- description: "Add new entries without removing existing keys. Treat the registry map as append-only until all consumers are verified.",
2825
- effort: "low",
2826
- impact: "medium"
2827
- });
2828
- }
2829
- if (file.riskTypes.includes("registry_consumer")) {
2830
- recommendations.push({
2831
- strategy: "Verify Registry Contract",
2832
- description: "Verify the registry contract before patching. Changes to field types must be reflected in both the registry and all rendering paths.",
2833
- effort: "medium",
2834
- impact: "low"
2835
- });
2836
- }
2837
- if (file.riskTypes.includes("route_handler_write_path")) {
2838
- recommendations.push({
2839
- strategy: "Integration Testing First",
2840
- description: "Add integration tests covering success and failure paths before modifying. Verify HTTP status codes and response shapes are preserved.",
2841
- effort: "medium",
2842
- impact: "high"
2843
- });
2844
- }
2845
- if (file.riskTypes.includes("god_component") || file.riskTypes.includes("god_hook")) {
2846
- recommendations.push({
2847
- strategy: "Extract Sub-Concerns",
2848
- description: "Extract sub-concerns into separate modules first. Only refactor the extraction points after tests confirm equivalence.",
2849
- effort: "high",
2850
- impact: "high"
2851
- });
2852
- }
2853
- if (file.sideEffectProfile.includes("database_write")) {
2854
- recommendations.push({
2855
- strategy: "Feature Flags & Staging",
2856
- description: "Wrap changes in a transaction or use a feature flag. Run against a staging database before production.",
2857
- effort: "medium",
2858
- impact: "high"
2859
- });
2860
- }
2861
- if (recommendations.length === 0 && file.importedBy.length >= 5) {
2862
- recommendations.push({
2863
- strategy: "Review Blast Radius",
2864
- description: "Review importedBy before patching. Run affected integration tests.",
2865
- effort: "medium",
2866
- impact: "medium"
2867
- });
2868
- }
2869
- recommendations.sort((a, b) => {
2870
- const ratioA = getImpactValue(a.impact) / getEffortValue(a.effort);
2871
- const ratioB = getImpactValue(b.impact) / getEffortValue(b.effort);
2872
- return ratioB - ratioA;
2873
- });
2874
- return recommendations;
2875
- }
2876
- };
2877
- }
2878
- });
2879
-
2880
2818
  // ../brain/dist/graph.js
2881
2819
  var init_graph = __esm({
2882
2820
  "../brain/dist/graph.js"() {
@@ -2884,246 +2822,6 @@ var init_graph = __esm({
2884
2822
  }
2885
2823
  });
2886
2824
 
2887
- // ../brain/dist/network/gateIndex.js
2888
- function isDenoisedImporter(relPath) {
2889
- if (!relPath)
2890
- return false;
2891
- const norm = relPath.replace(/\\/g, "/");
2892
- const segs = norm.split("/");
2893
- const excludedDirs = /* @__PURE__ */ new Set([
2894
- "node_modules",
2895
- "vendor",
2896
- "vendored",
2897
- "site-packages",
2898
- "third_party",
2899
- "third-party",
2900
- ".yarn",
2901
- "bower_components",
2902
- "venv",
2903
- ".venv",
2904
- "env",
2905
- "virtualenv",
2906
- ".git",
2907
- ".vibe-splainer",
2908
- "dist",
2909
- "build",
2910
- "out",
2911
- "target",
2912
- ".next",
2913
- ".nuxt",
2914
- ".docusaurus",
2915
- "coverage",
2916
- ".nyc_output",
2917
- ".cache"
2918
- ]);
2919
- for (const s of segs) {
2920
- const sLower = s.toLowerCase();
2921
- if (excludedDirs.has(sLower)) {
2922
- return false;
2923
- }
2924
- if (sLower.endsWith(".venv")) {
2925
- return false;
2926
- }
2927
- }
2928
- const fileName = segs[segs.length - 1];
2929
- const fileNameLower = fileName.toLowerCase();
2930
- if (/\.min\.[a-z]+$/.test(fileNameLower) || fileNameLower.includes(".min.")) {
2931
- return false;
2932
- }
2933
- if (/\.generated\./.test(fileNameLower) || fileNameLower.includes("__generated__")) {
2934
- return false;
2935
- }
2936
- if (fileNameLower.endsWith(".lock")) {
2937
- return false;
2938
- }
2939
- if (norm.startsWith("virtual:") || norm.startsWith("__virtual:") || norm.startsWith("webpack:")) {
2940
- return false;
2941
- }
2942
- return true;
2943
- }
2944
- function hasBehavioralSubstance(file) {
2945
- const isTypeDefinition = file.frameworkRole === "type_definition";
2946
- const cyclomatic = file.gravitySignals?.cyclomatic ?? 0;
2947
- const hasSideEffects = (file.sideEffectProfile ?? []).some((e) => e !== "none_detected");
2948
- const hasStrongRiskType = (file.riskTypes ?? []).some((r) => r === "state_machine" || r === "mutation_orchestration" || r === "registry_bottleneck" || r === "side_effect_coupling");
2949
- return !isTypeDefinition && (cyclomatic >= 5 || hasSideEffects || hasStrongRiskType);
2950
- }
2951
- function buildGateIndex(store) {
2952
- const files = {};
2953
- if (store && store.files) {
2954
- for (const [key, file] of Object.entries(store.files)) {
2955
- const dependents = (file.importedBy ?? []).filter(isDenoisedImporter);
2956
- const sideEffects = (file.sideEffectProfile ?? []).filter((e) => e !== "none_detected");
2957
- files[key] = {
2958
- relativePath: file.relativePath,
2959
- gravity: file.gravity ?? 0,
2960
- demoteReason: file.demoteReason,
2961
- hasBehavioralSubstance: hasBehavioralSubstance(file),
2962
- dependents,
2963
- fanIn: file.gravitySignals?.fanIn ?? dependents.length,
2964
- fanOut: file.gravitySignals?.fanOut ?? 0,
2965
- centrality: file.gravitySignals?.centrality ?? 0,
2966
- severity: file.canonicalSeverity ?? 1,
2967
- sideEffects,
2968
- riskTypes: file.riskTypes ?? []
2969
- };
2970
- }
2971
- }
2972
- return { files };
2973
- }
2974
- var init_gateIndex = __esm({
2975
- "../brain/dist/network/gateIndex.js"() {
2976
- "use strict";
2977
- }
2978
- });
2979
-
2980
- // ../brain/dist/network/escalation.js
2981
- function buildEscalationContext(targetFile, gateIndex) {
2982
- const file = lookupFile(targetFile, gateIndex);
2983
- if (!file)
2984
- return null;
2985
- const rel = file.relativePath;
2986
- const gravity = file.gravity;
2987
- const dependents = file.dependents;
2988
- const dependentsCount = dependents.length;
2989
- const sideEffects = file.sideEffects;
2990
- const riskTypes = file.riskTypes;
2991
- const severity = file.severity;
2992
- const gravity_tier = gravity > 70 ? "high" : gravity > 40 ? "medium" : "low";
2993
- const raw_substance_tier = file.hasBehavioralSubstance && dependentsCount >= 10 ? "high" : file.hasBehavioralSubstance && dependentsCount >= 4 ? "medium" : "low";
2994
- const tierOrder = { low: 1, medium: 2, high: 3 };
2995
- let blastRadius = tierOrder[gravity_tier] >= tierOrder[raw_substance_tier] ? gravity_tier : raw_substance_tier;
2996
- const isGeneratedOrVendored = !isDenoisedImporter(targetFile) || file.demoteReason !== null && file.demoteReason !== "no inbound references from application code";
2997
- if (isGeneratedOrVendored) {
2998
- blastRadius = "low";
2999
- }
3000
- const riskWarnings = buildRiskWarnings(rel, gravity, dependentsCount, severity, sideEffects, riskTypes);
3001
- return {
3002
- targetFile: rel,
3003
- gravity,
3004
- blastRadius,
3005
- dependents,
3006
- dependentCount: dependentsCount,
3007
- fanIn: file.fanIn,
3008
- fanOut: file.fanOut,
3009
- centrality: file.centrality,
3010
- severity,
3011
- sideEffects,
3012
- riskTypes,
3013
- riskWarnings,
3014
- smallestSafeChange: buildSafeChangePolicy(blastRadius)
3015
- };
3016
- }
3017
- function lookupFile(targetFile, gateIndex) {
3018
- const files = gateIndex?.files;
3019
- if (!files)
3020
- return null;
3021
- const norm = targetFile.replace(/\\/g, "/");
3022
- if (files[norm])
3023
- return files[norm];
3024
- const suffixHits = [];
3025
- for (const [key, rec] of Object.entries(files)) {
3026
- if (norm === key || norm.endsWith("/" + key) || key.endsWith("/" + norm)) {
3027
- suffixHits.push(rec);
3028
- }
3029
- }
3030
- return suffixHits.length === 1 ? suffixHits[0] : null;
3031
- }
3032
- function buildRiskWarnings(rel, gravity, dependentCount, severity, sideEffects, riskTypes) {
3033
- const warnings = [];
3034
- if (gravity > 70) {
3035
- warnings.push({
3036
- id: `rw_${rel}_blast_radius`,
3037
- level: "critical",
3038
- message: `Central file \u2014 editing it has a large blast radius. ${dependentCount} file(s) depend on it.`,
3039
- reason: `Gravity ${gravity}/100; fan-in ${dependentCount}.`
3040
- });
3041
- }
3042
- if (severity >= 4) {
3043
- warnings.push({
3044
- id: `rw_${rel}_severity`,
3045
- level: "warning",
3046
- message: "High-severity smells already present here. Avoid making them worse.",
3047
- reason: `Canonical severity ${severity}/5.`
3048
- });
3049
- }
3050
- if (SECURITY_PATH.test(rel)) {
3051
- warnings.push({
3052
- id: `rw_${rel}_security_path`,
3053
- level: "critical",
3054
- message: "Security-sensitive file (auth/credential/webhook/payment). Do not alter auth or credential handling unless that is the explicit task.",
3055
- reason: `Path matches security-sensitive pattern.`
3056
- });
3057
- }
3058
- const sensitive = sideEffects.filter((e) => SENSITIVE_EFFECTS.has(e));
3059
- if (sensitive.length > 0) {
3060
- warnings.push({
3061
- id: `rw_${rel}_side_effects`,
3062
- level: "warning",
3063
- message: `This file performs side effects (${sensitive.join(", ")}). Preserve existing behavior; do not drop or reorder them.`,
3064
- reason: `Side-effect profile: ${sensitive.join(", ")}.`
3065
- });
3066
- }
3067
- const notable = riskTypes.filter((r) => NOTABLE_RISKS.has(r));
3068
- if (notable.length > 0) {
3069
- warnings.push({
3070
- id: `rw_${rel}_risk_types`,
3071
- level: "info",
3072
- message: `Structural risk patterns detected (${notable.join(", ")}). Trace the affected paths before editing.`,
3073
- reason: `Risk types: ${notable.join(", ")}.`
3074
- });
3075
- }
3076
- return warnings;
3077
- }
3078
- function buildSafeChangePolicy(blastRadius) {
3079
- const rules = [
3080
- "Locate the exact failure site before editing.",
3081
- "Make the smallest localized change that addresses the task.",
3082
- "Do not perform general cleanup or refactoring.",
3083
- "Do not modify unrelated files, credentials, or auth configuration."
3084
- ];
3085
- if (blastRadius === "high") {
3086
- rules.push("This file is load-bearing: verify each dependent still type-checks against the changed surface.");
3087
- }
3088
- return {
3089
- 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.",
3090
- rules,
3091
- forbiddenEditClasses: ["credentials", "auth_configuration", "global_refactoring"]
3092
- };
3093
- }
3094
- var SECURITY_PATH, SENSITIVE_EFFECTS, NOTABLE_RISKS;
3095
- var init_escalation = __esm({
3096
- "../brain/dist/network/escalation.js"() {
3097
- "use strict";
3098
- init_gateIndex();
3099
- SECURITY_PATH = /\b(auth|credential|secret|token|webhook|payment|password|oauth|session)\b/i;
3100
- SENSITIVE_EFFECTS = /* @__PURE__ */ new Set([
3101
- "database_write",
3102
- "server_action",
3103
- "trpc_mutation",
3104
- "email_send",
3105
- "external_api_call"
3106
- ]);
3107
- NOTABLE_RISKS = /* @__PURE__ */ new Set([
3108
- "state_machine",
3109
- "mutation_orchestration",
3110
- "side_effect_coupling",
3111
- "async_race_risk",
3112
- "registry_bottleneck",
3113
- "error_swallowing"
3114
- ]);
3115
- }
3116
- });
3117
-
3118
- // ../brain/dist/network/index.js
3119
- var init_network = __esm({
3120
- "../brain/dist/network/index.js"() {
3121
- "use strict";
3122
- init_escalation();
3123
- init_gateIndex();
3124
- }
3125
- });
3126
-
3127
2825
  // ../brain/dist/index.js
3128
2826
  var init_dist = __esm({
3129
2827
  "../brain/dist/index.js"() {
@@ -3133,10 +2831,8 @@ var init_dist = __esm({
3133
2831
  init_scoring();
3134
2832
  init_classification();
3135
2833
  init_dossier();
3136
- init_RecommendationEngine();
3137
2834
  init_graph();
3138
2835
  init_adapters();
3139
- init_network();
3140
2836
  }
3141
2837
  });
3142
2838
 
@@ -3154,15 +2850,15 @@ var init_ArtifactBundleWriter = __esm({
3154
2850
  this.projectRoot = projectRoot;
3155
2851
  }
3156
2852
  async writeBundle(artifacts) {
3157
- const outputDir = join9(this.projectRoot, ".vibe-splainer");
3158
- const stagingDir = join9(this.projectRoot, ".vibe-splainer.tmp");
3159
- const oldDir = join9(this.projectRoot, ".vibe-splainer.old");
2853
+ const outputDir = join9(this.projectRoot, ".vibesplain");
2854
+ const stagingDir = join9(this.projectRoot, ".vibesplain.tmp");
2855
+ const oldDir = join9(this.projectRoot, ".vibesplain.old");
3160
2856
  try {
3161
2857
  await rm(stagingDir, { recursive: true, force: true });
3162
2858
  await rm(oldDir, { recursive: true, force: true });
3163
- const { existsSync: existsSync7 } = await import("fs");
2859
+ const { existsSync: existsSync5 } = await import("fs");
3164
2860
  const { cp } = await import("fs/promises");
3165
- if (existsSync7(outputDir)) {
2861
+ if (existsSync5(outputDir)) {
3166
2862
  await cp(outputDir, stagingDir, { recursive: true });
3167
2863
  } else {
3168
2864
  await mkdir6(stagingDir, { recursive: true });
@@ -3189,7 +2885,7 @@ var init_ArtifactBundleWriter = __esm({
3189
2885
  };
3190
2886
  await writeFile6(join9(stagingDir, "artifact_manifest.json"), JSON.stringify(manifest, null, 2), "utf8");
3191
2887
  let swapped = false;
3192
- if (existsSync7(outputDir)) {
2888
+ if (existsSync5(outputDir)) {
3193
2889
  await rename(outputDir, oldDir);
3194
2890
  swapped = true;
3195
2891
  }
@@ -3233,8 +2929,8 @@ var init_JsonRenderer = __esm({
3233
2929
  });
3234
2930
 
3235
2931
  // dist/export/renderers/HtmlRenderer.js
3236
- import { join as join10, dirname as dirname4, relative as relative3 } from "path";
3237
- import { fileURLToPath as fileURLToPath3 } from "url";
2932
+ import { join as join10, dirname as dirname3, relative as relative3 } from "path";
2933
+ import { fileURLToPath as fileURLToPath2 } from "url";
3238
2934
  import { existsSync as existsSync4, readFileSync, readdirSync, statSync } from "fs";
3239
2935
  function getAllFiles(dirPath, arrayOfFiles = []) {
3240
2936
  const files = readdirSync(dirPath);
@@ -3252,7 +2948,7 @@ var __dirname2, HtmlRenderer;
3252
2948
  var init_HtmlRenderer = __esm({
3253
2949
  "dist/export/renderers/HtmlRenderer.js"() {
3254
2950
  "use strict";
3255
- __dirname2 = dirname4(fileURLToPath3(import.meta.url));
2951
+ __dirname2 = dirname3(fileURLToPath2(import.meta.url));
3256
2952
  HtmlRenderer = class {
3257
2953
  render(viewModel, _store) {
3258
2954
  const candidatePaths = [
@@ -3285,7 +2981,7 @@ var init_HtmlRenderer = __esm({
3285
2981
  }
3286
2982
  }
3287
2983
  if (!templateDir) {
3288
- console.error("[vibe-splain] UI template not found. Checked:", candidatePaths);
2984
+ console.error("[vibesplain] UI template not found. Checked:", candidatePaths);
3289
2985
  return [];
3290
2986
  }
3291
2987
  const artifacts = [];
@@ -3367,11 +3063,11 @@ ${viewModel.map.brief}
3367
3063
  md += `## Tier 1: Critical Files & Risks
3368
3064
 
3369
3065
  `;
3370
- for (const path2 of tier1) {
3371
- const f = store.files[path2];
3372
- const card = uniqueDecisions.get(path2);
3373
- const recs = viewModel.recommendations[path2] || [];
3374
- md += `### ${path2}
3066
+ for (const path of tier1) {
3067
+ const f = store.files[path];
3068
+ const card = uniqueDecisions.get(path);
3069
+ const recs = viewModel.recommendations[path] || [];
3070
+ md += `### ${path}
3375
3071
  `;
3376
3072
  md += `- Gravity: ${Math.round(f.gravity)} | Heat: ${Math.round(f.heat)}
3377
3073
  `;
@@ -3403,10 +3099,10 @@ ${viewModel.map.brief}
3403
3099
  md += `## Tier 2: Important Files
3404
3100
 
3405
3101
  `;
3406
- for (const path2 of tier2) {
3407
- const f = store.files[path2];
3408
- const card = uniqueDecisions.get(path2);
3409
- md += `- **${path2}** (Gravity: ${Math.round(f.gravity)})`;
3102
+ for (const path of tier2) {
3103
+ const f = store.files[path];
3104
+ const card = uniqueDecisions.get(path);
3105
+ md += `- **${path}** (Gravity: ${Math.round(f.gravity)})`;
3410
3106
  if (card) {
3411
3107
  md += ` \u2014 ${card.thesis}`;
3412
3108
  }
@@ -3418,9 +3114,9 @@ ${viewModel.map.brief}
3418
3114
  md += `## Tier 3: Index
3419
3115
 
3420
3116
  `;
3421
- for (const path2 of tier3) {
3422
- const f = store.files[path2];
3423
- md += `- ${path2} (Gravity: ${Math.round(f.gravity)})
3117
+ for (const path of tier3) {
3118
+ const f = store.files[path];
3119
+ md += `- ${path} (Gravity: ${Math.round(f.gravity)})
3424
3120
  `;
3425
3121
  }
3426
3122
  return [
@@ -3501,750 +3197,111 @@ var init_GraphRenderer = __esm({
3501
3197
  }
3502
3198
  });
3503
3199
 
3504
- // dist/store/BlobStore.js
3505
- import { createHash as createHash2 } from "crypto";
3506
- import { mkdir as mkdir7, writeFile as writeFile7, open, rename as rename2, stat } from "fs/promises";
3507
- import { existsSync as existsSync5 } from "fs";
3508
- import { join as join11 } from "path";
3509
- async function hashFile(filePath) {
3510
- const { readFile: readFile12 } = await import("fs/promises");
3511
- const buf = await readFile12(filePath);
3512
- return `sha256:${createHash2("sha256").update(buf).digest("hex")}`;
3513
- }
3514
- var BlobStore;
3515
- var init_BlobStore = __esm({
3516
- "dist/store/BlobStore.js"() {
3200
+ // dist/export/ExportOrchestrator.js
3201
+ var ExportOrchestrator;
3202
+ var init_ExportOrchestrator = __esm({
3203
+ "dist/export/ExportOrchestrator.js"() {
3517
3204
  "use strict";
3518
- BlobStore = class {
3519
- blobsDir;
3520
- tmpDir;
3205
+ init_dist();
3206
+ init_ArtifactBundleWriter();
3207
+ init_JsonRenderer();
3208
+ init_HtmlRenderer();
3209
+ init_AgentMarkdownRenderer();
3210
+ init_ValidationRenderer();
3211
+ init_RawAnalysisRenderer();
3212
+ init_GraphRenderer();
3213
+ ExportOrchestrator = class {
3214
+ projectRoot;
3521
3215
  constructor(projectRoot) {
3522
- this.blobsDir = join11(projectRoot, ".vibe-splainer", "blobs");
3523
- this.tmpDir = join11(projectRoot, ".vibe-splainer", "tmp");
3524
- }
3525
- async ensureDirs() {
3526
- await mkdir7(this.blobsDir, { recursive: true });
3527
- await mkdir7(this.tmpDir, { recursive: true });
3216
+ this.projectRoot = projectRoot;
3528
3217
  }
3529
- async writeAtomic(payload) {
3530
- await this.ensureDirs();
3531
- const buf = typeof payload === "string" ? Buffer.from(payload, "utf8") : payload;
3532
- const hex = createHash2("sha256").update(buf).digest("hex");
3533
- const contentHash = `sha256:${hex}`;
3534
- const blobPath = join11(this.blobsDir, `sha256_${hex}`);
3535
- if (existsSync5(blobPath)) {
3536
- return { contentHash, blobPath };
3218
+ async writeBundle(dossier, options = {}, store, graph) {
3219
+ const finalStore = store || await readAnalysis(this.projectRoot);
3220
+ if (!finalStore) {
3221
+ throw new Error("Analysis store not found. Scan the project first.");
3537
3222
  }
3538
- const tmpPath = join11(this.tmpDir, `tmp_${hex}_${Date.now()}`);
3539
- await writeFile7(tmpPath, buf);
3540
- const fh = await open(tmpPath, "r");
3541
- try {
3542
- await fh.datasync();
3543
- } finally {
3544
- await fh.close();
3223
+ for (const p of dossier.pillars) {
3224
+ p.decisions = p.decisions.filter((c) => !(c.severity === 1 && c.category === "Convention"));
3225
+ p.cardCount = p.decisions.length;
3545
3226
  }
3546
- await rename2(tmpPath, blobPath);
3547
- return { contentHash, blobPath };
3548
- }
3549
- async readBlob(blobPath) {
3550
- const { readFile: readFile12 } = await import("fs/promises");
3551
- return readFile12(blobPath);
3552
- }
3553
- async blobExists(contentHash) {
3554
- const hex = contentHash.replace("sha256:", "");
3555
- const blobPath = join11(this.blobsDir, `sha256_${hex}`);
3556
- return existsSync5(blobPath);
3557
- }
3558
- blobPathForHash(contentHash) {
3559
- const hex = contentHash.replace("sha256:", "");
3560
- return join11(this.blobsDir, `sha256_${hex}`);
3561
- }
3562
- async verifyIntegrity(blobPath, expectedHash) {
3563
- try {
3564
- const { readFile: readFile12 } = await import("fs/promises");
3565
- const buf = await readFile12(blobPath);
3566
- const hex = createHash2("sha256").update(buf).digest("hex");
3567
- return `sha256:${hex}` === expectedHash;
3568
- } catch {
3569
- return false;
3227
+ const viewModel = this.buildViewModel(dossier, finalStore);
3228
+ const artifacts = [];
3229
+ artifacts.push(...await new JsonRenderer().render(viewModel, finalStore));
3230
+ artifacts.push(...await new ValidationRenderer().render(viewModel, finalStore));
3231
+ artifacts.push(...await new RawAnalysisRenderer().render(viewModel, finalStore));
3232
+ if (graph) {
3233
+ artifacts.push(...await new GraphRenderer(graph).render(viewModel, finalStore));
3570
3234
  }
3571
- }
3572
- /** List all blob paths for GC reference counting */
3573
- async listBlobPaths() {
3574
- try {
3575
- const { readdir: readdir2 } = await import("fs/promises");
3576
- const files = await readdir2(this.blobsDir);
3577
- return files.filter((f) => f.startsWith("sha256_")).map((f) => join11(this.blobsDir, f));
3578
- } catch {
3579
- return [];
3235
+ const formats = ["html", "markdown"];
3236
+ if (options.format && options.format !== "json" && options.format !== "delta") {
3237
+ formats.length = 0;
3238
+ formats.push(options.format);
3580
3239
  }
3581
- }
3582
- async getBlobSize(blobPath) {
3583
- try {
3584
- const info = await stat(blobPath);
3585
- return info.size;
3586
- } catch {
3587
- return 0;
3240
+ if (formats.includes("html")) {
3241
+ artifacts.push(...await new HtmlRenderer().render(viewModel, finalStore));
3242
+ }
3243
+ if (formats.includes("markdown")) {
3244
+ artifacts.push(...await new AgentMarkdownRenderer(options.budget).render(viewModel, finalStore));
3588
3245
  }
3246
+ const writer = new ArtifactBundleWriter(this.projectRoot);
3247
+ await writer.writeBundle(artifacts);
3248
+ }
3249
+ buildViewModel(dossier, _store) {
3250
+ return { ...dossier, recommendations: {} };
3589
3251
  }
3590
3252
  };
3591
3253
  }
3592
3254
  });
3593
3255
 
3594
- // ../../node_modules/async-mutex/index.mjs
3595
- function insertSorted(a, v) {
3596
- const i = findIndexFromEnd(a, (other) => v.priority <= other.priority);
3597
- a.splice(i + 1, 0, v);
3598
- }
3599
- function findIndexFromEnd(a, predicate) {
3600
- for (let i = a.length - 1; i >= 0; i--) {
3601
- if (predicate(a[i])) {
3602
- return i;
3603
- }
3256
+ // dist/export/Watcher.js
3257
+ import chokidar from "chokidar";
3258
+ import { createHash as createHash2 } from "crypto";
3259
+ import { readFile as readFile7 } from "fs/promises";
3260
+ import { join as join11 } from "path";
3261
+ async function startWatcher(projectRoot, watchedPaths) {
3262
+ const existing = activeWatchers.get(projectRoot);
3263
+ if (existing) {
3264
+ await existing.close();
3265
+ activeWatchers.delete(projectRoot);
3604
3266
  }
3605
- return -1;
3606
- }
3607
- var E_TIMEOUT, E_ALREADY_LOCKED, E_CANCELED, __awaiter$2, Semaphore, __awaiter$1, Mutex;
3608
- var init_async_mutex = __esm({
3609
- "../../node_modules/async-mutex/index.mjs"() {
3610
- E_TIMEOUT = new Error("timeout while waiting for mutex to become available");
3611
- E_ALREADY_LOCKED = new Error("mutex already locked");
3612
- E_CANCELED = new Error("request for lock canceled");
3613
- __awaiter$2 = function(thisArg, _arguments, P, generator) {
3614
- function adopt(value) {
3615
- return value instanceof P ? value : new P(function(resolve2) {
3616
- resolve2(value);
3617
- });
3618
- }
3619
- return new (P || (P = Promise))(function(resolve2, reject) {
3620
- function fulfilled(value) {
3621
- try {
3622
- step(generator.next(value));
3623
- } catch (e) {
3624
- reject(e);
3625
- }
3626
- }
3627
- function rejected(value) {
3628
- try {
3629
- step(generator["throw"](value));
3630
- } catch (e) {
3631
- reject(e);
3632
- }
3633
- }
3634
- function step(result) {
3635
- result.done ? resolve2(result.value) : adopt(result.value).then(fulfilled, rejected);
3636
- }
3637
- step((generator = generator.apply(thisArg, _arguments || [])).next());
3638
- });
3639
- };
3640
- Semaphore = class {
3641
- constructor(_value, _cancelError = E_CANCELED) {
3642
- this._value = _value;
3643
- this._cancelError = _cancelError;
3644
- this._queue = [];
3645
- this._weightedWaiters = [];
3646
- }
3647
- acquire(weight = 1, priority = 0) {
3648
- if (weight <= 0)
3649
- throw new Error(`invalid weight ${weight}: must be positive`);
3650
- return new Promise((resolve2, reject) => {
3651
- const task = { resolve: resolve2, reject, weight, priority };
3652
- const i = findIndexFromEnd(this._queue, (other) => priority <= other.priority);
3653
- if (i === -1 && weight <= this._value) {
3654
- this._dispatchItem(task);
3655
- } else {
3656
- this._queue.splice(i + 1, 0, task);
3657
- }
3658
- });
3659
- }
3660
- runExclusive(callback_1) {
3661
- return __awaiter$2(this, arguments, void 0, function* (callback, weight = 1, priority = 0) {
3662
- const [value, release] = yield this.acquire(weight, priority);
3663
- try {
3664
- return yield callback(value);
3665
- } finally {
3666
- release();
3667
- }
3668
- });
3669
- }
3670
- waitForUnlock(weight = 1, priority = 0) {
3671
- if (weight <= 0)
3672
- throw new Error(`invalid weight ${weight}: must be positive`);
3673
- if (this._couldLockImmediately(weight, priority)) {
3674
- return Promise.resolve();
3675
- } else {
3676
- return new Promise((resolve2) => {
3677
- if (!this._weightedWaiters[weight - 1])
3678
- this._weightedWaiters[weight - 1] = [];
3679
- insertSorted(this._weightedWaiters[weight - 1], { resolve: resolve2, priority });
3680
- });
3681
- }
3682
- }
3683
- isLocked() {
3684
- return this._value <= 0;
3685
- }
3686
- getValue() {
3687
- return this._value;
3688
- }
3689
- setValue(value) {
3690
- this._value = value;
3691
- this._dispatchQueue();
3692
- }
3693
- release(weight = 1) {
3694
- if (weight <= 0)
3695
- throw new Error(`invalid weight ${weight}: must be positive`);
3696
- this._value += weight;
3697
- this._dispatchQueue();
3698
- }
3699
- cancel() {
3700
- this._queue.forEach((entry) => entry.reject(this._cancelError));
3701
- this._queue = [];
3702
- }
3703
- _dispatchQueue() {
3704
- this._drainUnlockWaiters();
3705
- while (this._queue.length > 0 && this._queue[0].weight <= this._value) {
3706
- this._dispatchItem(this._queue.shift());
3707
- this._drainUnlockWaiters();
3708
- }
3709
- }
3710
- _dispatchItem(item) {
3711
- const previousValue = this._value;
3712
- this._value -= item.weight;
3713
- item.resolve([previousValue, this._newReleaser(item.weight)]);
3714
- }
3715
- _newReleaser(weight) {
3716
- let called = false;
3717
- return () => {
3718
- if (called)
3719
- return;
3720
- called = true;
3721
- this.release(weight);
3722
- };
3723
- }
3724
- _drainUnlockWaiters() {
3725
- if (this._queue.length === 0) {
3726
- for (let weight = this._value; weight > 0; weight--) {
3727
- const waiters = this._weightedWaiters[weight - 1];
3728
- if (!waiters)
3729
- continue;
3730
- waiters.forEach((waiter) => waiter.resolve());
3731
- this._weightedWaiters[weight - 1] = [];
3732
- }
3733
- } else {
3734
- const queuedPriority = this._queue[0].priority;
3735
- for (let weight = this._value; weight > 0; weight--) {
3736
- const waiters = this._weightedWaiters[weight - 1];
3737
- if (!waiters)
3738
- continue;
3739
- const i = waiters.findIndex((waiter) => waiter.priority <= queuedPriority);
3740
- (i === -1 ? waiters : waiters.splice(0, i)).forEach(((waiter) => waiter.resolve()));
3267
+ const watcher = chokidar.watch(watchedPaths.length > 0 ? watchedPaths : projectRoot, {
3268
+ ignoreInitial: true,
3269
+ ignored: ["**/node_modules/**", "**/dist/**", "**/build/**", "**/.vibesplain/**"],
3270
+ persistent: true
3271
+ });
3272
+ watcher.on("change", async (filepath) => {
3273
+ try {
3274
+ const dossier = await readDossier(projectRoot);
3275
+ if (!dossier)
3276
+ return;
3277
+ const content = await readFile7(filepath, "utf8");
3278
+ const newHash = createHash2("sha256").update(content).digest("hex");
3279
+ let mutated = false;
3280
+ for (const pillar of dossier.pillars) {
3281
+ for (const card of pillar.decisions) {
3282
+ if (!card.primaryFile)
3283
+ continue;
3284
+ const absMatch = filepath === join11(projectRoot, card.primaryFile) || filepath.endsWith("/" + card.primaryFile);
3285
+ if (absMatch && card.lastScannedHash !== newHash) {
3286
+ card.status = "stale";
3287
+ const rel = card.primaryFile;
3288
+ if (!dossier.stalePaths.includes(rel))
3289
+ dossier.stalePaths.push(rel);
3290
+ mutated = true;
3741
3291
  }
3742
3292
  }
3743
3293
  }
3744
- _couldLockImmediately(weight, priority) {
3745
- return (this._queue.length === 0 || this._queue[0].priority < priority) && weight <= this._value;
3746
- }
3747
- };
3748
- __awaiter$1 = function(thisArg, _arguments, P, generator) {
3749
- function adopt(value) {
3750
- return value instanceof P ? value : new P(function(resolve2) {
3751
- resolve2(value);
3752
- });
3753
- }
3754
- return new (P || (P = Promise))(function(resolve2, reject) {
3755
- function fulfilled(value) {
3756
- try {
3757
- step(generator.next(value));
3758
- } catch (e) {
3759
- reject(e);
3760
- }
3761
- }
3762
- function rejected(value) {
3763
- try {
3764
- step(generator["throw"](value));
3765
- } catch (e) {
3766
- reject(e);
3767
- }
3768
- }
3769
- function step(result) {
3770
- result.done ? resolve2(result.value) : adopt(result.value).then(fulfilled, rejected);
3771
- }
3772
- step((generator = generator.apply(thisArg, _arguments || [])).next());
3773
- });
3774
- };
3775
- Mutex = class {
3776
- constructor(cancelError) {
3777
- this._semaphore = new Semaphore(1, cancelError);
3778
- }
3779
- acquire() {
3780
- return __awaiter$1(this, arguments, void 0, function* (priority = 0) {
3781
- const [, releaser] = yield this._semaphore.acquire(1, priority);
3782
- return releaser;
3783
- });
3784
- }
3785
- runExclusive(callback, priority = 0) {
3786
- return this._semaphore.runExclusive(() => callback(), 1, priority);
3787
- }
3788
- isLocked() {
3789
- return this._semaphore.isLocked();
3790
- }
3791
- waitForUnlock(priority = 0) {
3792
- return this._semaphore.waitForUnlock(1, priority);
3793
- }
3794
- release() {
3795
- if (this._semaphore.isLocked())
3796
- this._semaphore.release();
3797
- }
3798
- cancel() {
3799
- return this._semaphore.cancel();
3800
- }
3801
- };
3802
- }
3803
- });
3804
-
3805
- // dist/store/PointerStore.js
3806
- import Database from "better-sqlite3";
3807
- import { join as join12 } from "path";
3808
- import { mkdirSync } from "fs";
3809
- var instance, PointerStore;
3810
- var init_PointerStore = __esm({
3811
- "dist/store/PointerStore.js"() {
3812
- "use strict";
3813
- init_async_mutex();
3814
- instance = null;
3815
- PointerStore = class _PointerStore {
3816
- db;
3817
- writeMutex = new Mutex();
3818
- constructor(projectRoot) {
3819
- const dir = join12(projectRoot, ".vibe-splainer");
3820
- mkdirSync(dir, { recursive: true });
3821
- this.db = new Database(join12(dir, "pointer_store.db"), { timeout: 1e4 });
3822
- let retries = 5;
3823
- while (retries > 0) {
3824
- try {
3825
- this.db.pragma("journal_mode = WAL");
3826
- this.db.pragma("foreign_keys = ON");
3827
- break;
3828
- } catch (e) {
3829
- if (e.code === "SQLITE_BUSY" && retries > 1) {
3830
- retries--;
3831
- const delay = 100 + Math.random() * 200;
3832
- const start = Date.now();
3833
- while (Date.now() - start < delay) {
3834
- }
3835
- continue;
3836
- }
3837
- throw e;
3838
- }
3839
- }
3840
- this._migrate();
3841
- }
3842
- static open(projectRoot) {
3843
- if (!instance)
3844
- instance = new _PointerStore(projectRoot);
3845
- return instance;
3846
- }
3847
- static reset() {
3848
- instance = null;
3849
- }
3850
- _migrate() {
3851
- this.db.exec(`
3852
- CREATE TABLE IF NOT EXISTS pointers (
3853
- pointerId TEXT PRIMARY KEY,
3854
- scanId TEXT NOT NULL,
3855
- artifactName TEXT NOT NULL,
3856
- contentHash TEXT NOT NULL,
3857
- blobPath TEXT NOT NULL,
3858
- schemaVersion TEXT NOT NULL DEFAULT '1.0.0',
3859
- createdAt INTEGER NOT NULL,
3860
- expiresAt INTEGER
3861
- );
3862
- CREATE INDEX IF NOT EXISTS idx_pointers_scan ON pointers(scanId);
3863
- CREATE INDEX IF NOT EXISTS idx_pointers_hash ON pointers(contentHash);
3864
-
3865
- CREATE TABLE IF NOT EXISTS work_orders (
3866
- workOrderId TEXT PRIMARY KEY,
3867
- intent TEXT NOT NULL,
3868
- allowedFiles TEXT NOT NULL DEFAULT '[]',
3869
- allowedGlobs TEXT NOT NULL DEFAULT '[]',
3870
- deniedGlobs TEXT NOT NULL DEFAULT '[]',
3871
- requiredProof TEXT NOT NULL DEFAULT '[]',
3872
- status TEXT NOT NULL DEFAULT 'pending',
3873
- createdAt INTEGER NOT NULL
3874
- );
3875
-
3876
- CREATE TABLE IF NOT EXISTS receipts (
3877
- receiptId TEXT PRIMARY KEY,
3878
- workOrderId TEXT NOT NULL REFERENCES work_orders(workOrderId),
3879
- status TEXT NOT NULL,
3880
- proofPointers TEXT NOT NULL DEFAULT '[]',
3881
- changedFiles TEXT NOT NULL DEFAULT '[]',
3882
- summary TEXT NOT NULL DEFAULT '',
3883
- createdAt INTEGER NOT NULL,
3884
- FOREIGN KEY (workOrderId) REFERENCES work_orders(workOrderId)
3885
- );
3886
- `);
3887
- }
3888
- async insertPointer(row) {
3889
- await this.writeMutex.runExclusive(() => {
3890
- this.db.prepare(`
3891
- INSERT OR REPLACE INTO pointers
3892
- (pointerId, scanId, artifactName, contentHash, blobPath, schemaVersion, createdAt, expiresAt)
3893
- VALUES
3894
- (@pointerId, @scanId, @artifactName, @contentHash, @blobPath, @schemaVersion, @createdAt, @expiresAt)
3895
- `).run(row);
3896
- });
3897
- }
3898
- getPointer(pointerId) {
3899
- return this.db.prepare("SELECT * FROM pointers WHERE pointerId = ?").get(pointerId) ?? null;
3900
- }
3901
- listPointersByScan(scanId) {
3902
- return this.db.prepare("SELECT * FROM pointers WHERE scanId = ?").all(scanId);
3903
- }
3904
- async insertWorkOrder(row) {
3905
- await this.writeMutex.runExclusive(() => {
3906
- this.db.prepare(`
3907
- INSERT OR REPLACE INTO work_orders
3908
- (workOrderId, intent, allowedFiles, allowedGlobs, deniedGlobs, requiredProof, status, createdAt)
3909
- VALUES
3910
- (@workOrderId, @intent, @allowedFiles, @allowedGlobs, @deniedGlobs, @requiredProof, @status, @createdAt)
3911
- `).run(row);
3912
- });
3913
- }
3914
- getWorkOrder(workOrderId) {
3915
- return this.db.prepare("SELECT * FROM work_orders WHERE workOrderId = ?").get(workOrderId) ?? null;
3916
- }
3917
- async updateWorkOrderStatus(workOrderId, status) {
3918
- await this.writeMutex.runExclusive(() => {
3919
- this.db.prepare("UPDATE work_orders SET status = ? WHERE workOrderId = ?").run(status, workOrderId);
3920
- });
3921
- }
3922
- async insertReceipt(receipt) {
3923
- await this.writeMutex.runExclusive(() => {
3924
- this.db.prepare(`
3925
- INSERT OR REPLACE INTO receipts
3926
- (receiptId, workOrderId, status, proofPointers, changedFiles, summary, createdAt)
3927
- VALUES
3928
- (@receiptId, @workOrderId, @status, @proofPointers, @changedFiles, @summary, @createdAt)
3929
- `).run({
3930
- ...receipt,
3931
- proofPointers: JSON.stringify(receipt.proofPointers),
3932
- changedFiles: JSON.stringify(receipt.changedFiles),
3933
- createdAt: Date.now()
3934
- });
3935
- });
3936
- }
3937
- /** GC: delete pointers older than cutoffMs and not pinned, return deleted count */
3938
- async gcScanPointers(keepScanIds) {
3939
- return await this.writeMutex.runExclusive(() => {
3940
- const placeholders = keepScanIds.map(() => "?").join(",");
3941
- const whereClause = keepScanIds.length > 0 ? `WHERE scanId NOT IN (${placeholders})` : "";
3942
- const result = this.db.prepare(`DELETE FROM pointers ${whereClause}`).run(...keepScanIds);
3943
- return result.changes;
3944
- });
3945
- }
3946
- listAllScanIds() {
3947
- const rows = this.db.prepare("SELECT DISTINCT scanId FROM pointers").all();
3948
- return rows.map((r) => r.scanId);
3949
- }
3950
- countPointers() {
3951
- const row = this.db.prepare("SELECT COUNT(*) as cnt FROM pointers").get();
3952
- return row.cnt;
3953
- }
3954
- close() {
3955
- this.db.close();
3956
- instance = null;
3957
- }
3958
- };
3959
- }
3960
- });
3961
-
3962
- // ../../node_modules/uuid/dist/esm-node/rng.js
3963
- import crypto from "crypto";
3964
- function rng() {
3965
- if (poolPtr > rnds8Pool.length - 16) {
3966
- crypto.randomFillSync(rnds8Pool);
3967
- poolPtr = 0;
3968
- }
3969
- return rnds8Pool.slice(poolPtr, poolPtr += 16);
3970
- }
3971
- var rnds8Pool, poolPtr;
3972
- var init_rng = __esm({
3973
- "../../node_modules/uuid/dist/esm-node/rng.js"() {
3974
- rnds8Pool = new Uint8Array(256);
3975
- poolPtr = rnds8Pool.length;
3976
- }
3977
- });
3978
-
3979
- // ../../node_modules/uuid/dist/esm-node/stringify.js
3980
- function unsafeStringify(arr, offset = 0) {
3981
- return byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]];
3982
- }
3983
- var byteToHex;
3984
- var init_stringify = __esm({
3985
- "../../node_modules/uuid/dist/esm-node/stringify.js"() {
3986
- byteToHex = [];
3987
- for (let i = 0; i < 256; ++i) {
3988
- byteToHex.push((i + 256).toString(16).slice(1));
3989
- }
3990
- }
3991
- });
3992
-
3993
- // ../../node_modules/uuid/dist/esm-node/native.js
3994
- import crypto2 from "crypto";
3995
- var native_default;
3996
- var init_native = __esm({
3997
- "../../node_modules/uuid/dist/esm-node/native.js"() {
3998
- native_default = {
3999
- randomUUID: crypto2.randomUUID
4000
- };
4001
- }
4002
- });
4003
-
4004
- // ../../node_modules/uuid/dist/esm-node/v4.js
4005
- function v4(options, buf, offset) {
4006
- if (native_default.randomUUID && !buf && !options) {
4007
- return native_default.randomUUID();
4008
- }
4009
- options = options || {};
4010
- const rnds = options.random || (options.rng || rng)();
4011
- rnds[6] = rnds[6] & 15 | 64;
4012
- rnds[8] = rnds[8] & 63 | 128;
4013
- if (buf) {
4014
- offset = offset || 0;
4015
- for (let i = 0; i < 16; ++i) {
4016
- buf[offset + i] = rnds[i];
4017
- }
4018
- return buf;
4019
- }
4020
- return unsafeStringify(rnds);
4021
- }
4022
- var v4_default;
4023
- var init_v4 = __esm({
4024
- "../../node_modules/uuid/dist/esm-node/v4.js"() {
4025
- init_native();
4026
- init_rng();
4027
- init_stringify();
4028
- v4_default = v4;
4029
- }
4030
- });
4031
-
4032
- // ../../node_modules/uuid/dist/esm-node/index.js
4033
- var init_esm_node = __esm({
4034
- "../../node_modules/uuid/dist/esm-node/index.js"() {
4035
- init_v4();
4036
- }
4037
- });
4038
-
4039
- // dist/export/ExportOrchestrator.js
4040
- var ExportOrchestrator;
4041
- var init_ExportOrchestrator = __esm({
4042
- "dist/export/ExportOrchestrator.js"() {
4043
- "use strict";
4044
- init_dist();
4045
- init_ArtifactBundleWriter();
4046
- init_JsonRenderer();
4047
- init_HtmlRenderer();
4048
- init_AgentMarkdownRenderer();
4049
- init_ValidationRenderer();
4050
- init_RawAnalysisRenderer();
4051
- init_GraphRenderer();
4052
- init_BlobStore();
4053
- init_PointerStore();
4054
- init_esm_node();
4055
- ExportOrchestrator = class {
4056
- projectRoot;
4057
- constructor(projectRoot) {
4058
- this.projectRoot = projectRoot;
4059
- }
4060
- async writeBundle(dossier, options = {}, store, graph, scanId) {
4061
- const finalStore = store || await readAnalysis(this.projectRoot);
4062
- if (!finalStore) {
4063
- throw new Error("Analysis store not found. Scan the project first.");
4064
- }
4065
- for (const p of dossier.pillars) {
4066
- p.decisions = p.decisions.filter((c) => !(c.severity === 1 && c.category === "Convention"));
4067
- p.cardCount = p.decisions.length;
4068
- }
4069
- const viewModel = this.buildViewModel(dossier, finalStore);
4070
- const artifacts = [];
4071
- artifacts.push(...await new JsonRenderer().render(viewModel, finalStore));
4072
- artifacts.push(...await new ValidationRenderer().render(viewModel, finalStore));
4073
- artifacts.push(...await new RawAnalysisRenderer().render(viewModel, finalStore));
4074
- if (graph) {
4075
- artifacts.push(...await new GraphRenderer(graph).render(viewModel, finalStore));
4076
- }
4077
- const gateIndex = buildGateIndex(finalStore);
4078
- artifacts.push({
4079
- type: "gate",
4080
- path: "gate.json",
4081
- content: JSON.stringify(gateIndex, null, 2)
4082
- });
4083
- const formats = ["html", "markdown"];
4084
- if (options.format && options.format !== "json" && options.format !== "delta") {
4085
- formats.length = 0;
4086
- formats.push(options.format);
4087
- }
4088
- if (formats.includes("html")) {
4089
- artifacts.push(...await new HtmlRenderer().render(viewModel, finalStore));
4090
- }
4091
- if (formats.includes("markdown")) {
4092
- artifacts.push(...await new AgentMarkdownRenderer(options.budget).render(viewModel, finalStore));
4093
- }
4094
- const writer = new ArtifactBundleWriter(this.projectRoot);
4095
- await writer.writeBundle(artifacts);
4096
- const effectiveScanId = scanId ?? `scan_${Date.now()}`;
4097
- const blobStore = new BlobStore(this.projectRoot);
4098
- const pointerStore = PointerStore.open(this.projectRoot);
4099
- const now = Date.now();
4100
- const manifestEntries = [];
4101
- for (const artifact of artifacts) {
4102
- const content = typeof artifact.content === "string" ? Buffer.from(artifact.content, "utf8") : artifact.content;
4103
- const { contentHash, blobPath } = await blobStore.writeAtomic(content);
4104
- const pointerId = `ptr_${v4_default().replace(/-/g, "").slice(0, 16)}`;
4105
- await pointerStore.insertPointer({
4106
- pointerId,
4107
- scanId: effectiveScanId,
4108
- artifactName: artifact.type,
4109
- contentHash,
4110
- blobPath,
4111
- schemaVersion: "1.0.0",
4112
- createdAt: now,
4113
- expiresAt: null
4114
- });
4115
- const entry = {
4116
- name: artifact.type,
4117
- pointer: pointerId,
4118
- contentHash,
4119
- sizeBytes: content.length
4120
- };
4121
- if (artifact.type === "analysis") {
4122
- entry.hydrators = ["get_project_summary", "get_start_here"];
4123
- const analysisIndex = {
4124
- schemaVersion: "1.0.0",
4125
- scanId: effectiveScanId,
4126
- startHere: dossier.map.topGravity.slice(0, 12),
4127
- topHeat: dossier.map.topHeat.slice(0, 12),
4128
- pillarSummary: dossier.map.pillars.map((p) => ({
4129
- name: p.name,
4130
- fileCount: p.memberFiles?.length ?? 0
4131
- })),
4132
- totalFiles: Object.keys(finalStore.files).length,
4133
- realSourceFiles: Object.values(finalStore.files).filter((f) => f.isRealSource).length
4134
- };
4135
- const indexContent = Buffer.from(JSON.stringify(analysisIndex, null, 2), "utf8");
4136
- const indexWrite = await blobStore.writeAtomic(indexContent);
4137
- const indexPointerId = `ptr_${v4_default().replace(/-/g, "").slice(0, 16)}`;
4138
- await pointerStore.insertPointer({
4139
- pointerId: indexPointerId,
4140
- scanId: effectiveScanId,
4141
- artifactName: "analysis.index",
4142
- contentHash: indexWrite.contentHash,
4143
- blobPath: indexWrite.blobPath,
4144
- schemaVersion: "1.0.0",
4145
- createdAt: now,
4146
- expiresAt: null
4147
- });
4148
- entry.indexes = { startHere: indexPointerId };
4149
- }
4150
- manifestEntries.push(entry);
4151
- }
4152
- const manifest = {
4153
- schemaVersion: "2.0.0",
4154
- scanId: effectiveScanId,
4155
- generatedAt: new Date(now).toISOString(),
4156
- projectRoot: this.projectRoot,
4157
- artifacts: manifestEntries
4158
- };
4159
- const manifestContent = Buffer.from(JSON.stringify(manifest, null, 2), "utf8");
4160
- const manifestWrite = await blobStore.writeAtomic(manifestContent);
4161
- const manifestPointerId = `ptr_manifest_${effectiveScanId}`;
4162
- await pointerStore.insertPointer({
4163
- pointerId: manifestPointerId,
4164
- scanId: effectiveScanId,
4165
- artifactName: "artifact_manifest",
4166
- contentHash: manifestWrite.contentHash,
4167
- blobPath: manifestWrite.blobPath,
4168
- schemaVersion: "2.0.0",
4169
- createdAt: now,
4170
- expiresAt: null
4171
- });
4172
- return { scanId: effectiveScanId, manifestPointer: manifestPointerId };
4173
- }
4174
- buildViewModel(dossier, store) {
4175
- const recommendations = {};
4176
- for (const file of dossier.map.topGravity) {
4177
- const persisted = store.files[file];
4178
- if (persisted) {
4179
- recommendations[file] = RecommendationEngine.generateRecommendations(persisted);
4180
- }
4181
- }
4182
- for (const file of dossier.map.topHeat) {
4183
- if (!recommendations[file]) {
4184
- const persisted = store.files[file];
4185
- if (persisted) {
4186
- recommendations[file] = RecommendationEngine.generateRecommendations(persisted);
4187
- }
4188
- }
4189
- }
4190
- return {
4191
- ...dossier,
4192
- recommendations
4193
- };
4194
- }
4195
- };
4196
- }
4197
- });
4198
-
4199
- // dist/export/Watcher.js
4200
- import chokidar from "chokidar";
4201
- import { createHash as createHash3 } from "crypto";
4202
- import { readFile as readFile7 } from "fs/promises";
4203
- import { join as join13 } from "path";
4204
- async function startWatcher(projectRoot, watchedPaths) {
4205
- const existing = activeWatchers.get(projectRoot);
4206
- if (existing) {
4207
- await existing.close();
4208
- activeWatchers.delete(projectRoot);
4209
- }
4210
- const watcher = chokidar.watch(watchedPaths.length > 0 ? watchedPaths : projectRoot, {
4211
- ignoreInitial: true,
4212
- ignored: ["**/node_modules/**", "**/dist/**", "**/build/**", "**/.vibe-splainer/**"],
4213
- persistent: true
4214
- });
4215
- watcher.on("change", async (filepath) => {
4216
- try {
4217
- const dossier = await readDossier(projectRoot);
4218
- if (!dossier)
4219
- return;
4220
- const content = await readFile7(filepath, "utf8");
4221
- const newHash = createHash3("sha256").update(content).digest("hex");
4222
- let mutated = false;
4223
- for (const pillar of dossier.pillars) {
4224
- for (const card of pillar.decisions) {
4225
- if (!card.primaryFile)
4226
- continue;
4227
- const absMatch = filepath === join13(projectRoot, card.primaryFile) || filepath.endsWith("/" + card.primaryFile);
4228
- if (absMatch && card.lastScannedHash !== newHash) {
4229
- card.status = "stale";
4230
- const rel = card.primaryFile;
4231
- if (!dossier.stalePaths.includes(rel))
4232
- dossier.stalePaths.push(rel);
4233
- mutated = true;
4234
- }
4235
- }
4236
- }
4237
- if (mutated) {
4238
- const orchestrator = new ExportOrchestrator(projectRoot);
4239
- await orchestrator.writeBundle(dossier);
4240
- console.error(`[vibe-splain] File changed: ${filepath}. Dossier artifacts updated.`);
3294
+ if (mutated) {
3295
+ const orchestrator = new ExportOrchestrator(projectRoot);
3296
+ await orchestrator.writeBundle(dossier);
3297
+ console.error(`[vibesplain] File changed: ${filepath}. Dossier artifacts updated.`);
4241
3298
  }
4242
3299
  } catch (err) {
4243
- console.error("[vibe-splain] Watcher error:", err);
3300
+ console.error("[vibesplain] Watcher error:", err);
4244
3301
  }
4245
3302
  });
4246
3303
  activeWatchers.set(projectRoot, watcher);
4247
- console.error("[vibe-splain] File watcher started");
3304
+ console.error("[vibesplain] File watcher started");
4248
3305
  }
4249
3306
  var activeWatchers;
4250
3307
  var init_Watcher = __esm({
@@ -4291,29 +3348,28 @@ async function performScan(projectRoot, options = {}) {
4291
3348
  }
4292
3349
  const scanId = `scan_${Date.now()}`;
4293
3350
  const orchestrator = new ExportOrchestrator(projectRoot);
4294
- const { manifestPointer } = await orchestrator.writeBundle(dossier, {
3351
+ await orchestrator.writeBundle(dossier, {
4295
3352
  format: options.format,
4296
3353
  budget: options.budget ? parseInt(options.budget, 10) : void 0,
4297
3354
  scope: options.scope
4298
- }, result.store, result.graph, scanId);
4299
- return { result, dossier, scanId, manifestPointer };
3355
+ }, result.store, result.graph);
3356
+ return { result, dossier, scanId };
4300
3357
  }
4301
3358
  async function handleScanProject(args, options = {}) {
4302
3359
  const projectRoot = args.projectRoot;
4303
3360
  if (!projectRoot)
4304
3361
  throw new Error("projectRoot is required");
4305
- console.error(`[vibe-splain] Scanning project: ${projectRoot}`);
4306
- const { result, scanId, manifestPointer } = await performScan(projectRoot, options);
3362
+ console.error(`[vibesplain] Scanning project: ${projectRoot}`);
3363
+ const { result, scanId } = await performScan(projectRoot, options);
4307
3364
  if (options.watch !== false) {
4308
3365
  await startWatcher(projectRoot, result.files.map((f) => f.path));
4309
3366
  }
4310
- console.error(`[vibe-splain] Scan complete. ${result.totalFilesScanned} files, ${result.realSourceCount} real-source, ${result.wildCandidates.length} wild candidates.`);
4311
- const validation = result.validation ?? { passed: true, errors: 0, warnings: 0, reportPath: ".vibe-splainer/validation_report.json" };
3367
+ console.error(`[vibesplain] Scan complete. ${result.totalFilesScanned} files, ${result.realSourceCount} real-source, ${result.wildCandidates.length} wild candidates.`);
3368
+ const validation = result.validation ?? { passed: true, errors: 0, warnings: 0, reportPath: ".vibesplain/validation_report.json" };
4312
3369
  return {
4313
3370
  ok: true,
4314
3371
  message: "Scan complete.",
4315
3372
  scanId,
4316
- manifestPointer,
4317
3373
  validation: result.fullValidationReport || {
4318
3374
  passed: validation.passed,
4319
3375
  errors: validation.errors,
@@ -4321,10 +3377,10 @@ async function handleScanProject(args, options = {}) {
4321
3377
  reportPath: validation.reportPath
4322
3378
  },
4323
3379
  artifacts: {
4324
- analysis: ".vibe-splainer/analysis.json",
4325
- dossier: ".vibe-splainer/dossier.json",
4326
- graph: ".vibe-splainer/graph.json",
4327
- html: ".vibe-splainer/ui/index.html"
3380
+ analysis: ".vibesplain/analysis.json",
3381
+ dossier: ".vibesplain/dossier.json",
3382
+ graph: ".vibesplain/graph.json",
3383
+ html: ".vibesplain/ui/index.html"
4328
3384
  },
4329
3385
  projectRoot: result.projectRoot,
4330
3386
  totalFilesScanned: result.totalFilesScanned,
@@ -4374,9 +3430,8 @@ import { Command } from "commander";
4374
3430
  // dist/commands/install.js
4375
3431
  import { readFile, writeFile } from "fs/promises";
4376
3432
  import { existsSync } from "fs";
4377
- import { join, dirname } from "path";
3433
+ import { join } from "path";
4378
3434
  import { homedir } from "os";
4379
- import { fileURLToPath } from "url";
4380
3435
  function expandPath(p) {
4381
3436
  if (p.startsWith("~")) {
4382
3437
  return join(homedir(), p.slice(1));
@@ -4399,40 +3454,8 @@ var MCP_ENTRY = {
4399
3454
  command: "npx",
4400
3455
  args: ["-y", "vibe-splain", "serve"]
4401
3456
  };
4402
- var HOOK_MATCHER = "Edit|Write|MultiEdit";
4403
- var DEFAULT_HOOK_COMMAND = "npx -y vibe-splain hook pretooluse";
4404
- function addPreToolUseHook(config, hookPath) {
4405
- if (!config.hooks)
4406
- config.hooks = {};
4407
- if (!Array.isArray(config.hooks.PreToolUse))
4408
- config.hooks.PreToolUse = [];
4409
- const hookCommand = hookPath ? `node "${hookPath}"` : DEFAULT_HOOK_COMMAND;
4410
- 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"))));
4411
- if (already) {
4412
- config.hooks.PreToolUse = config.hooks.PreToolUse.map((entry) => {
4413
- if (Array.isArray(entry?.hooks)) {
4414
- return {
4415
- ...entry,
4416
- hooks: entry.hooks.map((h) => {
4417
- if (typeof h?.command === "string" && (h.command.includes("vibe-splain hook pretooluse") || h.command.includes("hook.js"))) {
4418
- return { ...h, command: hookCommand };
4419
- }
4420
- return h;
4421
- })
4422
- };
4423
- }
4424
- return entry;
4425
- });
4426
- return config;
4427
- }
4428
- config.hooks.PreToolUse.push({
4429
- matcher: HOOK_MATCHER,
4430
- hooks: [{ type: "command", command: hookCommand }]
4431
- });
4432
- return config;
4433
- }
4434
3457
  async function installCommand() {
4435
- console.error("\n\u{1F527} vibe-splain Installer\n");
3458
+ console.error("\n\u{1F527} vibesplain Installer\n");
4436
3459
  let patchedCount = 0;
4437
3460
  for (const agent of AGENT_CONFIGS) {
4438
3461
  const resolvedPath = expandPath(agent.path);
@@ -4452,13 +3475,8 @@ async function installCommand() {
4452
3475
  const before = JSON.stringify(config);
4453
3476
  if (!config.mcpServers)
4454
3477
  config.mcpServers = {};
4455
- if (!config.mcpServers["vibe-splain"])
4456
- config.mcpServers["vibe-splain"] = MCP_ENTRY;
4457
- if (agent.name === "Claude Code CLI") {
4458
- const __dirname3 = dirname(fileURLToPath(import.meta.url));
4459
- const hookPath = join(__dirname3, "..", "hook.js");
4460
- addPreToolUseHook(config, hookPath);
4461
- }
3478
+ if (!config.mcpServers["vibesplain"])
3479
+ config.mcpServers["vibesplain"] = MCP_ENTRY;
4462
3480
  if (JSON.stringify(config) === before) {
4463
3481
  console.error(` \u2713 Already configured, skipping`);
4464
3482
  patchedCount++;
@@ -4478,7 +3496,7 @@ async function installCommand() {
4478
3496
  `);
4479
3497
  console.error(JSON.stringify({
4480
3498
  mcpServers: {
4481
- "vibe-splain": MCP_ENTRY
3499
+ "vibesplain": MCP_ENTRY
4482
3500
  }
4483
3501
  }, null, 2));
4484
3502
  process.exit(1);
@@ -4583,7 +3601,7 @@ async function handleSetProjectBrief(args, options = {}) {
4583
3601
  // dist/mcp/tools/get_file_context.js
4584
3602
  init_dist();
4585
3603
  import { readFile as readFile8 } from "fs/promises";
4586
- import { join as join14, relative as relative4, isAbsolute } from "path";
3604
+ import { join as join12, relative as relative4, isAbsolute } from "path";
4587
3605
  var getFileContextTool = {
4588
3606
  name: "get_file_context",
4589
3607
  description: "Returns PRE-EXTRACTED evidence for a file so you do not have to read the whole thing and paraphrase its header comment. Returns: gravity/heat scores + signals, importedBy (named fan-in \u2014 use this for blastRadius), hotSpans (the gnarliest function bodies, comment-stripped, each with a reason), smellSpans (located tech debt with \xB13 lines of context), and signature (the exported API surface). Base your evidence on hotSpans/smellSpans \u2014 NEVER on header comments. Pass { full: true } only if you truly need the raw source.",
@@ -4603,7 +3621,7 @@ async function handleGetFileContext(args) {
4603
3621
  const full = args.full === true;
4604
3622
  if (!projectRoot || !filePath)
4605
3623
  throw new Error("projectRoot and filePath are required");
4606
- const fullPath = isAbsolute(filePath) ? filePath : join14(projectRoot, filePath);
3624
+ const fullPath = isAbsolute(filePath) ? filePath : join12(projectRoot, filePath);
4607
3625
  const relPath = relative4(projectRoot, fullPath);
4608
3626
  const evidence = await getFileAnalysis(fullPath);
4609
3627
  if (!evidence) {
@@ -4636,42 +3654,90 @@ async function handleGetFileContext(args) {
4636
3654
  // dist/mcp/tools/write_decision_card.js
4637
3655
  init_dist();
4638
3656
  init_ExportOrchestrator();
4639
- init_esm_node();
4640
- import { createHash as createHash4 } from "crypto";
4641
- import { readFile as readFile9 } from "fs/promises";
4642
- import { join as join15 } from "path";
4643
- var CATEGORIES = ["Bottleneck", "Hack", "Smart-Move", "Risk", "Convention", "Dead-Weight"];
4644
- function normalizeSnippet(s) {
4645
- let out = (s ?? "").replace(/\r\n/g, "\n");
4646
- if (/\\[nt]/.test(out)) {
4647
- out = out.replace(/\\r\\n/g, "\n").replace(/\\n/g, "\n").replace(/\\t/g, " ");
3657
+
3658
+ // ../../node_modules/uuid/dist/esm-node/rng.js
3659
+ import crypto from "crypto";
3660
+ var rnds8Pool = new Uint8Array(256);
3661
+ var poolPtr = rnds8Pool.length;
3662
+ function rng() {
3663
+ if (poolPtr > rnds8Pool.length - 16) {
3664
+ crypto.randomFillSync(rnds8Pool);
3665
+ poolPtr = 0;
4648
3666
  }
4649
- return out.split("\n").map((l) => l.replace(/\s+$/, "")).join("\n").replace(/^\n+|\n+$/g, "");
3667
+ return rnds8Pool.slice(poolPtr, poolPtr += 16);
4650
3668
  }
4651
- var writeDecisionCardTool = {
4652
- name: "write_decision_card",
4653
- description: "Persists ONE Decision Card about ONE file. This is a hostile architecture review, not documentation. The thesis must be a VERDICT, not a description. The pillar MUST be one of the names from get_project_map (free-form is rejected). One card per file (duplicates rejected). Evidence must come from get_file_context hotSpans/smellSpans \u2014 never the header comment, never the whole file.",
4654
- inputSchema: {
4655
- type: "object",
4656
- properties: {
4657
- projectRoot: { type: "string" },
4658
- pillar: { type: "string", description: "MUST be one of the pillar names from get_project_map. Free-form values are rejected." },
4659
- primaryFile: { type: "string", description: "The single file this card is about (relative path). Used to reject duplicate cards." },
4660
- title: { type: "string" },
4661
- thesis: { type: "string", description: "ONE sharp sentence. A verdict, not a description. Take a position. Bad: 'This file implements a panel system.' Good: 'A 600-line god-component that owns drag, zoom, persistence AND the host bridge \u2014 the single highest-risk refactor in the app.'" },
4662
- category: { type: "string", enum: CATEGORIES },
4663
- severity: { type: "integer", minimum: 1, maximum: 5 },
4664
- narrative: { type: "string", description: "3-5 sentences. WHY it exists and WHY it's built this way. Do NOT restate the file's header comments." },
4665
- tradeoff: { type: "string", description: "What was given up, or why the obvious approach was rejected. Null only if genuinely none." },
4666
- blastRadius: { type: "string", description: "What breaks if this changes. Ground it in the fan-in (importedBy) from get_file_context." },
4667
- confidence: { type: "string", enum: ["low", "medium", "high"], description: 'Do NOT default to "high". Reserve "high" ONLY for provable execution anti-patterns. Score subjective stylistic choices or abstractions as "low" or "medium".' },
4668
- evidence: {
4669
- type: "array",
4670
- items: {
4671
- type: "object",
4672
- properties: {
4673
- file: { type: "string" },
4674
- startLine: { type: "number" },
3669
+
3670
+ // ../../node_modules/uuid/dist/esm-node/stringify.js
3671
+ var byteToHex = [];
3672
+ for (let i = 0; i < 256; ++i) {
3673
+ byteToHex.push((i + 256).toString(16).slice(1));
3674
+ }
3675
+ function unsafeStringify(arr, offset = 0) {
3676
+ return byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]];
3677
+ }
3678
+
3679
+ // ../../node_modules/uuid/dist/esm-node/native.js
3680
+ import crypto2 from "crypto";
3681
+ var native_default = {
3682
+ randomUUID: crypto2.randomUUID
3683
+ };
3684
+
3685
+ // ../../node_modules/uuid/dist/esm-node/v4.js
3686
+ function v4(options, buf, offset) {
3687
+ if (native_default.randomUUID && !buf && !options) {
3688
+ return native_default.randomUUID();
3689
+ }
3690
+ options = options || {};
3691
+ const rnds = options.random || (options.rng || rng)();
3692
+ rnds[6] = rnds[6] & 15 | 64;
3693
+ rnds[8] = rnds[8] & 63 | 128;
3694
+ if (buf) {
3695
+ offset = offset || 0;
3696
+ for (let i = 0; i < 16; ++i) {
3697
+ buf[offset + i] = rnds[i];
3698
+ }
3699
+ return buf;
3700
+ }
3701
+ return unsafeStringify(rnds);
3702
+ }
3703
+ var v4_default = v4;
3704
+
3705
+ // dist/mcp/tools/write_decision_card.js
3706
+ import { createHash as createHash3 } from "crypto";
3707
+ import { readFile as readFile9 } from "fs/promises";
3708
+ import { join as join13 } from "path";
3709
+ var CATEGORIES = ["Bottleneck", "Hack", "Smart-Move", "Risk", "Convention", "Dead-Weight"];
3710
+ function normalizeSnippet(s) {
3711
+ let out = (s ?? "").replace(/\r\n/g, "\n");
3712
+ if (/\\[nt]/.test(out)) {
3713
+ out = out.replace(/\\r\\n/g, "\n").replace(/\\n/g, "\n").replace(/\\t/g, " ");
3714
+ }
3715
+ return out.split("\n").map((l) => l.replace(/\s+$/, "")).join("\n").replace(/^\n+|\n+$/g, "");
3716
+ }
3717
+ var writeDecisionCardTool = {
3718
+ name: "write_decision_card",
3719
+ description: "Persists ONE Decision Card about ONE file. This is a hostile architecture review, not documentation. The thesis must be a VERDICT, not a description. The pillar MUST be one of the names from get_project_map (free-form is rejected). One card per file (duplicates rejected). Evidence must come from get_file_context hotSpans/smellSpans \u2014 never the header comment, never the whole file.",
3720
+ inputSchema: {
3721
+ type: "object",
3722
+ properties: {
3723
+ projectRoot: { type: "string" },
3724
+ pillar: { type: "string", description: "MUST be one of the pillar names from get_project_map. Free-form values are rejected." },
3725
+ primaryFile: { type: "string", description: "The single file this card is about (relative path). Used to reject duplicate cards." },
3726
+ title: { type: "string" },
3727
+ thesis: { type: "string", description: "ONE sharp sentence. A verdict, not a description. Take a position. Bad: 'This file implements a panel system.' Good: 'A 600-line god-component that owns drag, zoom, persistence AND the host bridge \u2014 the single highest-risk refactor in the app.'" },
3728
+ category: { type: "string", enum: CATEGORIES },
3729
+ severity: { type: "integer", minimum: 1, maximum: 5 },
3730
+ narrative: { type: "string", description: "3-5 sentences. WHY it exists and WHY it's built this way. Do NOT restate the file's header comments." },
3731
+ tradeoff: { type: "string", description: "What was given up, or why the obvious approach was rejected. Null only if genuinely none." },
3732
+ blastRadius: { type: "string", description: "What breaks if this changes. Ground it in the fan-in (importedBy) from get_file_context." },
3733
+ confidence: { type: "string", enum: ["low", "medium", "high"], description: 'Do NOT default to "high". Reserve "high" ONLY for provable execution anti-patterns. Score subjective stylistic choices or abstractions as "low" or "medium".' },
3734
+ evidence: {
3735
+ type: "array",
3736
+ items: {
3737
+ type: "object",
3738
+ properties: {
3739
+ file: { type: "string" },
3740
+ startLine: { type: "number" },
4675
3741
  endLine: { type: "number" },
4676
3742
  snippet: { type: "string" }
4677
3743
  },
@@ -4736,10 +3802,10 @@ async function handleWriteDecisionCard(args, options = {}) {
4736
3802
  const heat = persisted ? Math.round(persisted.heat) : void 0;
4737
3803
  let primaryContent = "";
4738
3804
  try {
4739
- primaryContent = await readFile9(join15(projectRoot, primaryFile), "utf8");
3805
+ primaryContent = await readFile9(join13(projectRoot, primaryFile), "utf8");
4740
3806
  } catch {
4741
3807
  }
4742
- const hash = createHash4("sha256").update(primaryContent).digest("hex");
3808
+ const hash = createHash3("sha256").update(primaryContent).digest("hex");
4743
3809
  const card = {
4744
3810
  id: v4_default(),
4745
3811
  pillar,
@@ -4776,7 +3842,7 @@ async function handleWriteDecisionCard(args, options = {}) {
4776
3842
  budget: options.budget ? parseInt(options.budget, 10) : void 0,
4777
3843
  scope: options.scope
4778
3844
  });
4779
- console.error(`[vibe-splain] Card written: "${title}" [${category} sev${severity}] in "${pillar}"${isWild ? " (Wild Discovery)" : ""}`);
3845
+ console.error(`[vibesplain] Card written: "${title}" [${category} sev${severity}] in "${pillar}"${isWild ? " (Wild Discovery)" : ""}`);
4780
3846
  const documented = new Set([...dossier.pillars.flatMap((p) => p.decisions), ...dossier.wildDiscoveries].map((c) => c.primaryFile).filter(Boolean));
4781
3847
  const remaining = [.../* @__PURE__ */ new Set([...dossier.map.topGravity, ...dossier.map.topHeat])].filter((f) => !documented.has(f));
4782
3848
  return {
@@ -4971,2377 +4037,111 @@ async function handleMarkStale(args, options = {}) {
4971
4037
  };
4972
4038
  }
4973
4039
 
4974
- // dist/mcp/tools/get_file_skeleton.js
4975
- import { readFile as readFile10 } from "fs/promises";
4976
- import { join as join16 } from "path";
4977
-
4978
- // ../../node_modules/balanced-match/dist/esm/index.js
4979
- var balanced = (a, b, str) => {
4980
- const ma = a instanceof RegExp ? maybeMatch(a, str) : a;
4981
- const mb = b instanceof RegExp ? maybeMatch(b, str) : b;
4982
- const r = ma !== null && mb != null && range(ma, mb, str);
4983
- return r && {
4984
- start: r[0],
4985
- end: r[1],
4986
- pre: str.slice(0, r[0]),
4987
- body: str.slice(r[0] + ma.length, r[1]),
4988
- post: str.slice(r[1] + mb.length)
4989
- };
4990
- };
4991
- var maybeMatch = (reg, str) => {
4992
- const m = str.match(reg);
4993
- return m ? m[0] : null;
4040
+ // dist/mcp/server.js
4041
+ var ALL_TOOLS = [
4042
+ scanProjectTool,
4043
+ getProjectMapTool,
4044
+ setProjectBriefTool,
4045
+ getFileContextTool,
4046
+ writeDecisionCardTool,
4047
+ getStrategicOverviewTool,
4048
+ inspectPillarTool,
4049
+ getWildDiscoveriesTool,
4050
+ markStaleTool
4051
+ ];
4052
+ var TOOL_HANDLERS = {
4053
+ scan_project: handleScanProject,
4054
+ get_project_map: handleGetProjectMap,
4055
+ set_project_brief: handleSetProjectBrief,
4056
+ get_file_context: handleGetFileContext,
4057
+ write_decision_card: handleWriteDecisionCard,
4058
+ get_strategic_overview: handleGetStrategicOverview,
4059
+ inspect_pillar: handleInspectPillar,
4060
+ get_wild_discoveries: handleGetWildDiscoveries,
4061
+ mark_stale: handleMarkStale
4994
4062
  };
4995
- var range = (a, b, str) => {
4996
- let begs, beg, left, right = void 0, result;
4997
- let ai = str.indexOf(a);
4998
- let bi = str.indexOf(b, ai + 1);
4999
- let i = ai;
5000
- if (ai >= 0 && bi > 0) {
5001
- if (a === b) {
5002
- return [ai, bi];
5003
- }
5004
- begs = [];
5005
- left = str.length;
5006
- while (i >= 0 && !result) {
5007
- if (i === ai) {
5008
- begs.push(i);
5009
- ai = str.indexOf(a, i + 1);
5010
- } else if (begs.length === 1) {
5011
- const r = begs.pop();
5012
- if (r !== void 0)
5013
- result = [r, bi];
5014
- } else {
5015
- beg = begs.pop();
5016
- if (beg !== void 0 && beg < left) {
5017
- left = beg;
5018
- right = bi;
5019
- }
5020
- bi = str.indexOf(b, i + 1);
4063
+ async function startMCPServer(options = {}) {
4064
+ await initParser2();
4065
+ console.error("[vibesplain] Tree-Sitter parser initialized");
4066
+ const server = new Server({ name: "vibesplain", version: "4.0.0" }, { capabilities: { tools: {}, prompts: {} } });
4067
+ server.setRequestHandler(ListPromptsRequestSchema, async () => ({
4068
+ prompts: [
4069
+ {
4070
+ name: "build_dossier",
4071
+ description: "Build a full architectural dossier using vibesplain (replaces the need to copy-paste the README prompt)"
5021
4072
  }
5022
- i = ai < bi && ai >= 0 ? ai : bi;
5023
- }
5024
- if (begs.length && right !== void 0) {
5025
- result = [left, right];
4073
+ ]
4074
+ }));
4075
+ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
4076
+ if (request.params.name !== "build_dossier") {
4077
+ throw new Error(`Unknown prompt: ${request.params.name}`);
5026
4078
  }
5027
- }
5028
- return result;
5029
- };
4079
+ return {
4080
+ description: "Build a full architectural dossier",
4081
+ messages: [
4082
+ {
4083
+ role: "user",
4084
+ content: {
4085
+ type: "text",
4086
+ text: `You are a skeptical staff engineer doing a HOSTILE architecture review of this codebase.
4087
+ You are NOT writing documentation. You are finding the load-bearing walls, the landmines,
4088
+ and the clever moves, and you are taking positions on them.
5030
4089
 
5031
- // ../../node_modules/brace-expansion/dist/esm/index.js
5032
- var escSlash = "\0SLASH" + Math.random() + "\0";
5033
- var escOpen = "\0OPEN" + Math.random() + "\0";
5034
- var escClose = "\0CLOSE" + Math.random() + "\0";
5035
- var escComma = "\0COMMA" + Math.random() + "\0";
5036
- var escPeriod = "\0PERIOD" + Math.random() + "\0";
5037
- var escSlashPattern = new RegExp(escSlash, "g");
5038
- var escOpenPattern = new RegExp(escOpen, "g");
5039
- var escClosePattern = new RegExp(escClose, "g");
5040
- var escCommaPattern = new RegExp(escComma, "g");
5041
- var escPeriodPattern = new RegExp(escPeriod, "g");
5042
- var slashPattern = /\\\\/g;
5043
- var openPattern = /\\{/g;
5044
- var closePattern = /\\}/g;
5045
- var commaPattern = /\\,/g;
5046
- var periodPattern = /\\\./g;
5047
- var EXPANSION_MAX = 1e5;
5048
- function numeric(str) {
5049
- return !isNaN(str) ? parseInt(str, 10) : str.charCodeAt(0);
5050
- }
5051
- function escapeBraces(str) {
5052
- return str.replace(slashPattern, escSlash).replace(openPattern, escOpen).replace(closePattern, escClose).replace(commaPattern, escComma).replace(periodPattern, escPeriod);
5053
- }
5054
- function unescapeBraces(str) {
5055
- return str.replace(escSlashPattern, "\\").replace(escOpenPattern, "{").replace(escClosePattern, "}").replace(escCommaPattern, ",").replace(escPeriodPattern, ".");
5056
- }
5057
- function parseCommaParts(str) {
5058
- if (!str) {
5059
- return [""];
5060
- }
5061
- const parts = [];
5062
- const m = balanced("{", "}", str);
5063
- if (!m) {
5064
- return str.split(",");
5065
- }
5066
- const { pre, body, post } = m;
5067
- const p = pre.split(",");
5068
- p[p.length - 1] += "{" + body + "}";
5069
- const postParts = parseCommaParts(post);
5070
- if (post.length) {
5071
- ;
5072
- p[p.length - 1] += postParts.shift();
5073
- p.push.apply(p, postParts);
5074
- }
5075
- parts.push.apply(parts, p);
5076
- return parts;
5077
- }
5078
- function expand(str, options = {}) {
5079
- if (!str) {
5080
- return [];
5081
- }
5082
- const { max = EXPANSION_MAX } = options;
5083
- if (str.slice(0, 2) === "{}") {
5084
- str = "\\{\\}" + str.slice(2);
5085
- }
5086
- return expand_(escapeBraces(str), max, true).map(unescapeBraces);
5087
- }
5088
- function embrace(str) {
5089
- return "{" + str + "}";
5090
- }
5091
- function isPadded(el) {
5092
- return /^-?0\d/.test(el);
5093
- }
5094
- function lte(i, y) {
5095
- return i <= y;
5096
- }
5097
- function gte(i, y) {
5098
- return i >= y;
5099
- }
5100
- function expand_(str, max, isTop) {
5101
- const expansions = [];
5102
- const m = balanced("{", "}", str);
5103
- if (!m)
5104
- return [str];
5105
- const pre = m.pre;
5106
- const post = m.post.length ? expand_(m.post, max, false) : [""];
5107
- if (/\$$/.test(m.pre)) {
5108
- for (let k = 0; k < post.length && k < max; k++) {
5109
- const expansion = pre + "{" + m.body + "}" + post[k];
5110
- expansions.push(expansion);
5111
- }
5112
- } else {
5113
- const isNumericSequence = /^-?\d+\.\.-?\d+(?:\.\.-?\d+)?$/.test(m.body);
5114
- const isAlphaSequence = /^[a-zA-Z]\.\.[a-zA-Z](?:\.\.-?\d+)?$/.test(m.body);
5115
- const isSequence = isNumericSequence || isAlphaSequence;
5116
- const isOptions = m.body.indexOf(",") >= 0;
5117
- if (!isSequence && !isOptions) {
5118
- if (m.post.match(/,(?!,).*\}/)) {
5119
- str = m.pre + "{" + m.body + escClose + m.post;
5120
- return expand_(str, max, true);
5121
- }
5122
- return [str];
5123
- }
5124
- let n;
5125
- if (isSequence) {
5126
- n = m.body.split(/\.\./);
5127
- } else {
5128
- n = parseCommaParts(m.body);
5129
- if (n.length === 1 && n[0] !== void 0) {
5130
- n = expand_(n[0], max, false).map(embrace);
5131
- if (n.length === 1) {
5132
- return post.map((p) => m.pre + n[0] + p);
5133
- }
5134
- }
5135
- }
5136
- let N;
5137
- if (isSequence && n[0] !== void 0 && n[1] !== void 0) {
5138
- const x = numeric(n[0]);
5139
- const y = numeric(n[1]);
5140
- const width = Math.max(n[0].length, n[1].length);
5141
- let incr = n.length === 3 && n[2] !== void 0 ? Math.max(Math.abs(numeric(n[2])), 1) : 1;
5142
- let test = lte;
5143
- const reverse = y < x;
5144
- if (reverse) {
5145
- incr *= -1;
5146
- test = gte;
5147
- }
5148
- const pad = n.some(isPadded);
5149
- N = [];
5150
- for (let i = x; test(i, y) && N.length < max; i += incr) {
5151
- let c;
5152
- if (isAlphaSequence) {
5153
- c = String.fromCharCode(i);
5154
- if (c === "\\") {
5155
- c = "";
5156
- }
5157
- } else {
5158
- c = String(i);
5159
- if (pad) {
5160
- const need = width - c.length;
5161
- if (need > 0) {
5162
- const z = new Array(need + 1).join("0");
5163
- if (i < 0) {
5164
- c = "-" + z + c.slice(1);
5165
- } else {
5166
- c = z + c;
5167
- }
5168
- }
5169
- }
5170
- }
5171
- N.push(c);
5172
- }
5173
- } else {
5174
- N = [];
5175
- for (let j = 0; j < n.length; j++) {
5176
- N.push.apply(N, expand_(n[j], max, false));
5177
- }
5178
- }
5179
- for (let j = 0; j < N.length; j++) {
5180
- for (let k = 0; k < post.length && expansions.length < max; k++) {
5181
- const expansion = pre + N[j] + post[k];
5182
- if (!isTop || isSequence || expansion) {
5183
- expansions.push(expansion);
5184
- }
5185
- }
5186
- }
5187
- }
5188
- return expansions;
5189
- }
4090
+ PROCESS \u2014 follow in order:
4091
+ 1. Call scan_project, then get_project_map. The map gives you: the detected stack,
4092
+ the FIXED set of pillars (you may not invent others), the Start-Here files (highest
4093
+ gravity = most depended-upon), and Wild-Discovery candidates (highest heat = most smell).
4094
+ 2. Read the map's stack and entrypoints. Write a 3-5 sentence project brief: what IS this,
4095
+ what's the real stack, and \u2014 critically \u2014 which files are the actual application vs.
4096
+ mockups/generated/vendored noise. Pass it via set_project_brief. Do this BEFORE any card.
4097
+ 3. Work the Start-Here files first (highest gravity), then the Wild-Discovery files.
4098
+ For each, call get_file_context. It returns hotSpans (the gnarliest functions) and
4099
+ smellSpans (located tech debt) \u2014 base your evidence on THOSE, never on header comments.
4100
+ 4. Write one decision card per file via write_decision_card.
5190
4101
 
5191
- // ../../node_modules/minimatch/dist/esm/assert-valid-pattern.js
5192
- var MAX_PATTERN_LENGTH = 1024 * 64;
5193
- var assertValidPattern = (pattern) => {
5194
- if (typeof pattern !== "string") {
5195
- throw new TypeError("invalid pattern");
5196
- }
5197
- if (pattern.length > MAX_PATTERN_LENGTH) {
5198
- throw new TypeError("pattern is too long");
5199
- }
5200
- };
4102
+ This is an AUTONOMOUS loop. Every tool response includes a \`nextStep\` and often a
4103
+ \`remainingFiles\` list \u2014 OBEY them. Do NOT stop, summarize, or ask the user "how would
4104
+ you like to proceed" until every Start-Here and Wild-Discovery file has a card. Writing
4105
+ the brief is the START of the work, not the end. Keep calling get_file_context +
4106
+ write_decision_card until remainingFiles is empty.
5201
4107
 
5202
- // ../../node_modules/minimatch/dist/esm/brace-expressions.js
5203
- var posixClasses = {
5204
- "[:alnum:]": ["\\p{L}\\p{Nl}\\p{Nd}", true],
5205
- "[:alpha:]": ["\\p{L}\\p{Nl}", true],
5206
- "[:ascii:]": ["\\x00-\\x7f", false],
5207
- "[:blank:]": ["\\p{Zs}\\t", true],
5208
- "[:cntrl:]": ["\\p{Cc}", true],
5209
- "[:digit:]": ["\\p{Nd}", true],
5210
- "[:graph:]": ["\\p{Z}\\p{C}", true, true],
5211
- "[:lower:]": ["\\p{Ll}", true],
5212
- "[:print:]": ["\\p{C}", true],
5213
- "[:punct:]": ["\\p{P}", true],
5214
- "[:space:]": ["\\p{Z}\\t\\r\\n\\v\\f", true],
5215
- "[:upper:]": ["\\p{Lu}", true],
5216
- "[:word:]": ["\\p{L}\\p{Nl}\\p{Nd}\\p{Pc}", true],
5217
- "[:xdigit:]": ["A-Fa-f0-9", false]
5218
- };
5219
- var braceEscape = (s) => s.replace(/[[\]\\-]/g, "\\$&");
5220
- var regexpEscape = (s) => s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
5221
- var rangesToString = (ranges) => ranges.join("");
5222
- var parseClass = (glob, position) => {
5223
- const pos = position;
5224
- if (glob.charAt(pos) !== "[") {
5225
- throw new Error("not in a brace expression");
5226
- }
5227
- const ranges = [];
5228
- const negs = [];
5229
- let i = pos + 1;
5230
- let sawStart = false;
5231
- let uflag = false;
5232
- let escaping = false;
5233
- let negate = false;
5234
- let endPos = pos;
5235
- let rangeStart = "";
5236
- WHILE: while (i < glob.length) {
5237
- const c = glob.charAt(i);
5238
- if ((c === "!" || c === "^") && i === pos + 1) {
5239
- negate = true;
5240
- i++;
5241
- continue;
5242
- }
5243
- if (c === "]" && sawStart && !escaping) {
5244
- endPos = i + 1;
5245
- break;
5246
- }
5247
- sawStart = true;
5248
- if (c === "\\") {
5249
- if (!escaping) {
5250
- escaping = true;
5251
- i++;
5252
- continue;
5253
- }
5254
- }
5255
- if (c === "[" && !escaping) {
5256
- for (const [cls, [unip, u, neg]] of Object.entries(posixClasses)) {
5257
- if (glob.startsWith(cls, i)) {
5258
- if (rangeStart) {
5259
- return ["$.", false, glob.length - pos, true];
5260
- }
5261
- i += cls.length;
5262
- if (neg)
5263
- negs.push(unip);
5264
- else
5265
- ranges.push(unip);
5266
- uflag = uflag || u;
5267
- continue WHILE;
5268
- }
5269
- }
5270
- }
5271
- escaping = false;
5272
- if (rangeStart) {
5273
- if (c > rangeStart) {
5274
- ranges.push(braceEscape(rangeStart) + "-" + braceEscape(c));
5275
- } else if (c === rangeStart) {
5276
- ranges.push(braceEscape(c));
5277
- }
5278
- rangeStart = "";
5279
- i++;
5280
- continue;
5281
- }
5282
- if (glob.startsWith("-]", i + 1)) {
5283
- ranges.push(braceEscape(c + "-"));
5284
- i += 2;
5285
- continue;
5286
- }
5287
- if (glob.startsWith("-", i + 1)) {
5288
- rangeStart = c;
5289
- i += 2;
5290
- continue;
5291
- }
5292
- ranges.push(braceEscape(c));
5293
- i++;
5294
- }
5295
- if (endPos < i) {
5296
- return ["", false, 0, false];
5297
- }
5298
- if (!ranges.length && !negs.length) {
5299
- return ["$.", false, glob.length - pos, true];
5300
- }
5301
- if (negs.length === 0 && ranges.length === 1 && /^\\?.$/.test(ranges[0]) && !negate) {
5302
- const r = ranges[0].length === 2 ? ranges[0].slice(-1) : ranges[0];
5303
- return [regexpEscape(r), false, endPos - pos, false];
5304
- }
5305
- const sranges = "[" + (negate ? "^" : "") + rangesToString(ranges) + "]";
5306
- const snegs = "[" + (negate ? "" : "^") + rangesToString(negs) + "]";
5307
- const comb = ranges.length && negs.length ? "(" + sranges + "|" + snegs + ")" : ranges.length ? sranges : snegs;
5308
- return [comb, uflag, endPos - pos, true];
5309
- };
4108
+ RULES FOR EVERY CARD \u2014 non-negotiable:
4109
+ - The \`thesis\` is a VERDICT in one sentence. Take a position. If you can't, you don't
4110
+ understand the file yet \u2014 read more.
4111
+ - Pick a \`category\`: Bottleneck, Hack, Smart-Move, Risk, Convention, or Dead-Weight.
4112
+ - \`blastRadius\` must reference the real fan-in (get_file_context.importedBy).
4113
+ - NEVER paraphrase the file's own comments. If the insight is already in a // block,
4114
+ it is not insight \u2014 go deeper into the logic.
4115
+ - Evidence = 5-20 lines of the ACTUAL interesting code (hotSpans/smellSpans). Never the
4116
+ whole file, never the doc-header.
4117
+ - For every Wild-Discovery candidate, name the specific smell and rate its severity.
5310
4118
 
5311
- // ../../node_modules/minimatch/dist/esm/unescape.js
5312
- var unescape = (s, { windowsPathsNoEscape = false, magicalBraces = true } = {}) => {
5313
- if (magicalBraces) {
5314
- return windowsPathsNoEscape ? s.replace(/\[([^/\\])\]/g, "$1") : s.replace(/((?!\\).|^)\[([^/\\])\]/g, "$1$2").replace(/\\([^/])/g, "$1");
5315
- }
5316
- return windowsPathsNoEscape ? s.replace(/\[([^/\\{}])\]/g, "$1") : s.replace(/((?!\\).|^)\[([^/\\{}])\]/g, "$1$2").replace(/\\([^/{}])/g, "$1");
5317
- };
4119
+ \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
4120
+ EXAMPLE \u2014 what GOOD vs BAD looks like:
5318
4121
 
5319
- // ../../node_modules/minimatch/dist/esm/ast.js
5320
- var _a;
5321
- var types = /* @__PURE__ */ new Set(["!", "?", "+", "*", "@"]);
5322
- var isExtglobType = (c) => types.has(c);
5323
- var isExtglobAST = (c) => isExtglobType(c.type);
5324
- var adoptionMap = /* @__PURE__ */ new Map([
5325
- ["!", ["@"]],
5326
- ["?", ["?", "@"]],
5327
- ["@", ["@"]],
5328
- ["*", ["*", "+", "?", "@"]],
5329
- ["+", ["+", "@"]]
5330
- ]);
5331
- var adoptionWithSpaceMap = /* @__PURE__ */ new Map([
5332
- ["!", ["?"]],
5333
- ["@", ["?"]],
5334
- ["+", ["?", "*"]]
5335
- ]);
5336
- var adoptionAnyMap = /* @__PURE__ */ new Map([
5337
- ["!", ["?", "@"]],
5338
- ["?", ["?", "@"]],
5339
- ["@", ["?", "@"]],
5340
- ["*", ["*", "+", "?", "@"]],
5341
- ["+", ["+", "@", "?", "*"]]
5342
- ]);
5343
- var usurpMap = /* @__PURE__ */ new Map([
5344
- ["!", /* @__PURE__ */ new Map([["!", "@"]])],
5345
- [
5346
- "?",
5347
- /* @__PURE__ */ new Map([
5348
- ["*", "*"],
5349
- ["+", "*"]
5350
- ])
5351
- ],
5352
- [
5353
- "@",
5354
- /* @__PURE__ */ new Map([
5355
- ["!", "!"],
5356
- ["?", "?"],
5357
- ["@", "@"],
5358
- ["*", "*"],
5359
- ["+", "+"]
5360
- ])
5361
- ],
5362
- [
5363
- "+",
5364
- /* @__PURE__ */ new Map([
5365
- ["?", "*"],
5366
- ["*", "*"]
5367
- ])
5368
- ]
5369
- ]);
5370
- var startNoTraversal = "(?!(?:^|/)\\.\\.?(?:$|/))";
5371
- var startNoDot = "(?!\\.)";
5372
- var addPatternStart = /* @__PURE__ */ new Set(["[", "."]);
5373
- var justDots = /* @__PURE__ */ new Set(["..", "."]);
5374
- var reSpecials = new Set("().*{}+?[]^$\\!");
5375
- var regExpEscape = (s) => s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
5376
- var qmark = "[^/]";
5377
- var star = qmark + "*?";
5378
- var starNoEmpty = qmark + "+?";
5379
- var ID = 0;
5380
- var AST = class {
5381
- type;
5382
- #root;
5383
- #hasMagic;
5384
- #uflag = false;
5385
- #parts = [];
5386
- #parent;
5387
- #parentIndex;
5388
- #negs;
5389
- #filledNegs = false;
5390
- #options;
5391
- #toString;
5392
- // set to true if it's an extglob with no children
5393
- // (which really means one child of '')
5394
- #emptyExt = false;
5395
- id = ++ID;
5396
- get depth() {
5397
- return (this.#parent?.depth ?? -1) + 1;
5398
- }
5399
- [/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")]() {
5400
- return {
5401
- "@@type": "AST",
5402
- id: this.id,
5403
- type: this.type,
5404
- root: this.#root.id,
5405
- parent: this.#parent?.id,
5406
- depth: this.depth,
5407
- partsLength: this.#parts.length,
5408
- parts: this.#parts
5409
- };
5410
- }
5411
- constructor(type, parent, options = {}) {
5412
- this.type = type;
5413
- if (type)
5414
- this.#hasMagic = true;
5415
- this.#parent = parent;
5416
- this.#root = this.#parent ? this.#parent.#root : this;
5417
- this.#options = this.#root === this ? options : this.#root.#options;
5418
- this.#negs = this.#root === this ? [] : this.#root.#negs;
5419
- if (type === "!" && !this.#root.#filledNegs)
5420
- this.#negs.push(this);
5421
- this.#parentIndex = this.#parent ? this.#parent.#parts.length : 0;
5422
- }
5423
- get hasMagic() {
5424
- if (this.#hasMagic !== void 0)
5425
- return this.#hasMagic;
5426
- for (const p of this.#parts) {
5427
- if (typeof p === "string")
5428
- continue;
5429
- if (p.type || p.hasMagic)
5430
- return this.#hasMagic = true;
5431
- }
5432
- return this.#hasMagic;
5433
- }
5434
- // reconstructs the pattern
5435
- toString() {
5436
- return this.#toString !== void 0 ? this.#toString : !this.type ? this.#toString = this.#parts.map((p) => String(p)).join("") : this.#toString = this.type + "(" + this.#parts.map((p) => String(p)).join("|") + ")";
5437
- }
5438
- #fillNegs() {
5439
- if (this !== this.#root)
5440
- throw new Error("should only call on root");
5441
- if (this.#filledNegs)
5442
- return this;
5443
- this.toString();
5444
- this.#filledNegs = true;
5445
- let n;
5446
- while (n = this.#negs.pop()) {
5447
- if (n.type !== "!")
5448
- continue;
5449
- let p = n;
5450
- let pp = p.#parent;
5451
- while (pp) {
5452
- for (let i = p.#parentIndex + 1; !pp.type && i < pp.#parts.length; i++) {
5453
- for (const part of n.#parts) {
5454
- if (typeof part === "string") {
5455
- throw new Error("string part in extglob AST??");
5456
- }
5457
- part.copyIn(pp.#parts[i]);
5458
- }
5459
- }
5460
- p = pp;
5461
- pp = p.#parent;
5462
- }
5463
- }
5464
- return this;
5465
- }
5466
- push(...parts) {
5467
- for (const p of parts) {
5468
- if (p === "")
5469
- continue;
5470
- if (typeof p !== "string" && !(p instanceof _a && p.#parent === this)) {
5471
- throw new Error("invalid part: " + p);
5472
- }
5473
- this.#parts.push(p);
5474
- }
5475
- }
5476
- toJSON() {
5477
- const ret = this.type === null ? this.#parts.slice().map((p) => typeof p === "string" ? p : p.toJSON()) : [this.type, ...this.#parts.map((p) => p.toJSON())];
5478
- if (this.isStart() && !this.type)
5479
- ret.unshift([]);
5480
- if (this.isEnd() && (this === this.#root || this.#root.#filledNegs && this.#parent?.type === "!")) {
5481
- ret.push({});
5482
- }
5483
- return ret;
5484
- }
5485
- isStart() {
5486
- if (this.#root === this)
5487
- return true;
5488
- if (!this.#parent?.isStart())
5489
- return false;
5490
- if (this.#parentIndex === 0)
5491
- return true;
5492
- const p = this.#parent;
5493
- for (let i = 0; i < this.#parentIndex; i++) {
5494
- const pp = p.#parts[i];
5495
- if (!(pp instanceof _a && pp.type === "!")) {
5496
- return false;
5497
- }
5498
- }
5499
- return true;
5500
- }
5501
- isEnd() {
5502
- if (this.#root === this)
5503
- return true;
5504
- if (this.#parent?.type === "!")
5505
- return true;
5506
- if (!this.#parent?.isEnd())
5507
- return false;
5508
- if (!this.type)
5509
- return this.#parent?.isEnd();
5510
- const pl = this.#parent ? this.#parent.#parts.length : 0;
5511
- return this.#parentIndex === pl - 1;
5512
- }
5513
- copyIn(part) {
5514
- if (typeof part === "string")
5515
- this.push(part);
5516
- else
5517
- this.push(part.clone(this));
5518
- }
5519
- clone(parent) {
5520
- const c = new _a(this.type, parent);
5521
- for (const p of this.#parts) {
5522
- c.copyIn(p);
5523
- }
5524
- return c;
5525
- }
5526
- static #parseAST(str, ast, pos, opt, extDepth) {
5527
- const maxDepth = opt.maxExtglobRecursion ?? 2;
5528
- let escaping = false;
5529
- let inBrace = false;
5530
- let braceStart = -1;
5531
- let braceNeg = false;
5532
- if (ast.type === null) {
5533
- let i2 = pos;
5534
- let acc2 = "";
5535
- while (i2 < str.length) {
5536
- const c = str.charAt(i2++);
5537
- if (escaping || c === "\\") {
5538
- escaping = !escaping;
5539
- acc2 += c;
5540
- continue;
5541
- }
5542
- if (inBrace) {
5543
- if (i2 === braceStart + 1) {
5544
- if (c === "^" || c === "!") {
5545
- braceNeg = true;
5546
- }
5547
- } else if (c === "]" && !(i2 === braceStart + 2 && braceNeg)) {
5548
- inBrace = false;
5549
- }
5550
- acc2 += c;
5551
- continue;
5552
- } else if (c === "[") {
5553
- inBrace = true;
5554
- braceStart = i2;
5555
- braceNeg = false;
5556
- acc2 += c;
5557
- continue;
5558
- }
5559
- const doRecurse = !opt.noext && isExtglobType(c) && str.charAt(i2) === "(" && extDepth <= maxDepth;
5560
- if (doRecurse) {
5561
- ast.push(acc2);
5562
- acc2 = "";
5563
- const ext2 = new _a(c, ast);
5564
- i2 = _a.#parseAST(str, ext2, i2, opt, extDepth + 1);
5565
- ast.push(ext2);
5566
- continue;
5567
- }
5568
- acc2 += c;
5569
- }
5570
- ast.push(acc2);
5571
- return i2;
5572
- }
5573
- let i = pos + 1;
5574
- let part = new _a(null, ast);
5575
- const parts = [];
5576
- let acc = "";
5577
- while (i < str.length) {
5578
- const c = str.charAt(i++);
5579
- if (escaping || c === "\\") {
5580
- escaping = !escaping;
5581
- acc += c;
5582
- continue;
5583
- }
5584
- if (inBrace) {
5585
- if (i === braceStart + 1) {
5586
- if (c === "^" || c === "!") {
5587
- braceNeg = true;
5588
- }
5589
- } else if (c === "]" && !(i === braceStart + 2 && braceNeg)) {
5590
- inBrace = false;
5591
- }
5592
- acc += c;
5593
- continue;
5594
- } else if (c === "[") {
5595
- inBrace = true;
5596
- braceStart = i;
5597
- braceNeg = false;
5598
- acc += c;
5599
- continue;
5600
- }
5601
- const doRecurse = !opt.noext && isExtglobType(c) && str.charAt(i) === "(" && /* c8 ignore start - the maxDepth is sufficient here */
5602
- (extDepth <= maxDepth || ast && ast.#canAdoptType(c));
5603
- if (doRecurse) {
5604
- const depthAdd = ast && ast.#canAdoptType(c) ? 0 : 1;
5605
- part.push(acc);
5606
- acc = "";
5607
- const ext2 = new _a(c, part);
5608
- part.push(ext2);
5609
- i = _a.#parseAST(str, ext2, i, opt, extDepth + depthAdd);
5610
- continue;
5611
- }
5612
- if (c === "|") {
5613
- part.push(acc);
5614
- acc = "";
5615
- parts.push(part);
5616
- part = new _a(null, ast);
5617
- continue;
5618
- }
5619
- if (c === ")") {
5620
- if (acc === "" && ast.#parts.length === 0) {
5621
- ast.#emptyExt = true;
5622
- }
5623
- part.push(acc);
5624
- acc = "";
5625
- ast.push(...parts, part);
5626
- return i;
5627
- }
5628
- acc += c;
5629
- }
5630
- ast.type = null;
5631
- ast.#hasMagic = void 0;
5632
- ast.#parts = [str.substring(pos - 1)];
5633
- return i;
5634
- }
5635
- #canAdoptWithSpace(child) {
5636
- return this.#canAdopt(child, adoptionWithSpaceMap);
5637
- }
5638
- #canAdopt(child, map = adoptionMap) {
5639
- if (!child || typeof child !== "object" || child.type !== null || child.#parts.length !== 1 || this.type === null) {
5640
- return false;
5641
- }
5642
- const gc = child.#parts[0];
5643
- if (!gc || typeof gc !== "object" || gc.type === null) {
5644
- return false;
5645
- }
5646
- return this.#canAdoptType(gc.type, map);
5647
- }
5648
- #canAdoptType(c, map = adoptionAnyMap) {
5649
- return !!map.get(this.type)?.includes(c);
5650
- }
5651
- #adoptWithSpace(child, index) {
5652
- const gc = child.#parts[0];
5653
- const blank = new _a(null, gc, this.options);
5654
- blank.#parts.push("");
5655
- gc.push(blank);
5656
- this.#adopt(child, index);
5657
- }
5658
- #adopt(child, index) {
5659
- const gc = child.#parts[0];
5660
- this.#parts.splice(index, 1, ...gc.#parts);
5661
- for (const p of gc.#parts) {
5662
- if (typeof p === "object")
5663
- p.#parent = this;
5664
- }
5665
- this.#toString = void 0;
5666
- }
5667
- #canUsurpType(c) {
5668
- const m = usurpMap.get(this.type);
5669
- return !!m?.has(c);
5670
- }
5671
- #canUsurp(child) {
5672
- if (!child || typeof child !== "object" || child.type !== null || child.#parts.length !== 1 || this.type === null || this.#parts.length !== 1) {
5673
- return false;
5674
- }
5675
- const gc = child.#parts[0];
5676
- if (!gc || typeof gc !== "object" || gc.type === null) {
5677
- return false;
5678
- }
5679
- return this.#canUsurpType(gc.type);
5680
- }
5681
- #usurp(child) {
5682
- const m = usurpMap.get(this.type);
5683
- const gc = child.#parts[0];
5684
- const nt = m?.get(gc.type);
5685
- if (!nt)
5686
- return false;
5687
- this.#parts = gc.#parts;
5688
- for (const p of this.#parts) {
5689
- if (typeof p === "object") {
5690
- p.#parent = this;
5691
- }
5692
- }
5693
- this.type = nt;
5694
- this.#toString = void 0;
5695
- this.#emptyExt = false;
5696
- }
5697
- static fromGlob(pattern, options = {}) {
5698
- const ast = new _a(null, void 0, options);
5699
- _a.#parseAST(pattern, ast, 0, options, 0);
5700
- return ast;
5701
- }
5702
- // returns the regular expression if there's magic, or the unescaped
5703
- // string if not.
5704
- toMMPattern() {
5705
- if (this !== this.#root)
5706
- return this.#root.toMMPattern();
5707
- const glob = this.toString();
5708
- const [re, body, hasMagic, uflag] = this.toRegExpSource();
5709
- const anyMagic = hasMagic || this.#hasMagic || this.#options.nocase && !this.#options.nocaseMagicOnly && glob.toUpperCase() !== glob.toLowerCase();
5710
- if (!anyMagic) {
5711
- return body;
5712
- }
5713
- const flags = (this.#options.nocase ? "i" : "") + (uflag ? "u" : "");
5714
- return Object.assign(new RegExp(`^${re}$`, flags), {
5715
- _src: re,
5716
- _glob: glob
5717
- });
5718
- }
5719
- get options() {
5720
- return this.#options;
5721
- }
5722
- // returns the string match, the regexp source, whether there's magic
5723
- // in the regexp (so a regular expression is required) and whether or
5724
- // not the uflag is needed for the regular expression (for posix classes)
5725
- // TODO: instead of injecting the start/end at this point, just return
5726
- // the BODY of the regexp, along with the start/end portions suitable
5727
- // for binding the start/end in either a joined full-path makeRe context
5728
- // (where we bind to (^|/), or a standalone matchPart context (where
5729
- // we bind to ^, and not /). Otherwise slashes get duped!
5730
- //
5731
- // In part-matching mode, the start is:
5732
- // - if not isStart: nothing
5733
- // - if traversal possible, but not allowed: ^(?!\.\.?$)
5734
- // - if dots allowed or not possible: ^
5735
- // - if dots possible and not allowed: ^(?!\.)
5736
- // end is:
5737
- // - if not isEnd(): nothing
5738
- // - else: $
5739
- //
5740
- // In full-path matching mode, we put the slash at the START of the
5741
- // pattern, so start is:
5742
- // - if first pattern: same as part-matching mode
5743
- // - if not isStart(): nothing
5744
- // - if traversal possible, but not allowed: /(?!\.\.?(?:$|/))
5745
- // - if dots allowed or not possible: /
5746
- // - if dots possible and not allowed: /(?!\.)
5747
- // end is:
5748
- // - if last pattern, same as part-matching mode
5749
- // - else nothing
5750
- //
5751
- // Always put the (?:$|/) on negated tails, though, because that has to be
5752
- // there to bind the end of the negated pattern portion, and it's easier to
5753
- // just stick it in now rather than try to inject it later in the middle of
5754
- // the pattern.
5755
- //
5756
- // We can just always return the same end, and leave it up to the caller
5757
- // to know whether it's going to be used joined or in parts.
5758
- // And, if the start is adjusted slightly, can do the same there:
5759
- // - if not isStart: nothing
5760
- // - if traversal possible, but not allowed: (?:/|^)(?!\.\.?$)
5761
- // - if dots allowed or not possible: (?:/|^)
5762
- // - if dots possible and not allowed: (?:/|^)(?!\.)
5763
- //
5764
- // But it's better to have a simpler binding without a conditional, for
5765
- // performance, so probably better to return both start options.
5766
- //
5767
- // Then the caller just ignores the end if it's not the first pattern,
5768
- // and the start always gets applied.
5769
- //
5770
- // But that's always going to be $ if it's the ending pattern, or nothing,
5771
- // so the caller can just attach $ at the end of the pattern when building.
5772
- //
5773
- // So the todo is:
5774
- // - better detect what kind of start is needed
5775
- // - return both flavors of starting pattern
5776
- // - attach $ at the end of the pattern when creating the actual RegExp
5777
- //
5778
- // Ah, but wait, no, that all only applies to the root when the first pattern
5779
- // is not an extglob. If the first pattern IS an extglob, then we need all
5780
- // that dot prevention biz to live in the extglob portions, because eg
5781
- // +(*|.x*) can match .xy but not .yx.
5782
- //
5783
- // So, return the two flavors if it's #root and the first child is not an
5784
- // AST, otherwise leave it to the child AST to handle it, and there,
5785
- // use the (?:^|/) style of start binding.
5786
- //
5787
- // Even simplified further:
5788
- // - Since the start for a join is eg /(?!\.) and the start for a part
5789
- // is ^(?!\.), we can just prepend (?!\.) to the pattern (either root
5790
- // or start or whatever) and prepend ^ or / at the Regexp construction.
5791
- toRegExpSource(allowDot) {
5792
- const dot = allowDot ?? !!this.#options.dot;
5793
- if (this.#root === this) {
5794
- this.#flatten();
5795
- this.#fillNegs();
5796
- }
5797
- if (!isExtglobAST(this)) {
5798
- const noEmpty = this.isStart() && this.isEnd() && !this.#parts.some((s) => typeof s !== "string");
5799
- const src = this.#parts.map((p) => {
5800
- const [re, _, hasMagic, uflag] = typeof p === "string" ? _a.#parseGlob(p, this.#hasMagic, noEmpty) : p.toRegExpSource(allowDot);
5801
- this.#hasMagic = this.#hasMagic || hasMagic;
5802
- this.#uflag = this.#uflag || uflag;
5803
- return re;
5804
- }).join("");
5805
- let start2 = "";
5806
- if (this.isStart()) {
5807
- if (typeof this.#parts[0] === "string") {
5808
- const dotTravAllowed = this.#parts.length === 1 && justDots.has(this.#parts[0]);
5809
- if (!dotTravAllowed) {
5810
- const aps = addPatternStart;
5811
- const needNoTrav = (
5812
- // dots are allowed, and the pattern starts with [ or .
5813
- dot && aps.has(src.charAt(0)) || // the pattern starts with \., and then [ or .
5814
- src.startsWith("\\.") && aps.has(src.charAt(2)) || // the pattern starts with \.\., and then [ or .
5815
- src.startsWith("\\.\\.") && aps.has(src.charAt(4))
5816
- );
5817
- const needNoDot = !dot && !allowDot && aps.has(src.charAt(0));
5818
- start2 = needNoTrav ? startNoTraversal : needNoDot ? startNoDot : "";
5819
- }
5820
- }
5821
- }
5822
- let end = "";
5823
- if (this.isEnd() && this.#root.#filledNegs && this.#parent?.type === "!") {
5824
- end = "(?:$|\\/)";
5825
- }
5826
- const final2 = start2 + src + end;
5827
- return [
5828
- final2,
5829
- unescape(src),
5830
- this.#hasMagic = !!this.#hasMagic,
5831
- this.#uflag
5832
- ];
5833
- }
5834
- const repeated = this.type === "*" || this.type === "+";
5835
- const start = this.type === "!" ? "(?:(?!(?:" : "(?:";
5836
- let body = this.#partsToRegExp(dot);
5837
- if (this.isStart() && this.isEnd() && !body && this.type !== "!") {
5838
- const s = this.toString();
5839
- const me = this;
5840
- me.#parts = [s];
5841
- me.type = null;
5842
- me.#hasMagic = void 0;
5843
- return [s, unescape(this.toString()), false, false];
5844
- }
5845
- let bodyDotAllowed = !repeated || allowDot || dot || !startNoDot ? "" : this.#partsToRegExp(true);
5846
- if (bodyDotAllowed === body) {
5847
- bodyDotAllowed = "";
5848
- }
5849
- if (bodyDotAllowed) {
5850
- body = `(?:${body})(?:${bodyDotAllowed})*?`;
5851
- }
5852
- let final = "";
5853
- if (this.type === "!" && this.#emptyExt) {
5854
- final = (this.isStart() && !dot ? startNoDot : "") + starNoEmpty;
5855
- } else {
5856
- const close = this.type === "!" ? (
5857
- // !() must match something,but !(x) can match ''
5858
- "))" + (this.isStart() && !dot && !allowDot ? startNoDot : "") + star + ")"
5859
- ) : this.type === "@" ? ")" : this.type === "?" ? ")?" : this.type === "+" && bodyDotAllowed ? ")" : this.type === "*" && bodyDotAllowed ? `)?` : `)${this.type}`;
5860
- final = start + body + close;
5861
- }
5862
- return [
5863
- final,
5864
- unescape(body),
5865
- this.#hasMagic = !!this.#hasMagic,
5866
- this.#uflag
5867
- ];
5868
- }
5869
- #flatten() {
5870
- if (!isExtglobAST(this)) {
5871
- for (const p of this.#parts) {
5872
- if (typeof p === "object") {
5873
- p.#flatten();
5874
- }
5875
- }
5876
- } else {
5877
- let iterations = 0;
5878
- let done = false;
5879
- do {
5880
- done = true;
5881
- for (let i = 0; i < this.#parts.length; i++) {
5882
- const c = this.#parts[i];
5883
- if (typeof c === "object") {
5884
- c.#flatten();
5885
- if (this.#canAdopt(c)) {
5886
- done = false;
5887
- this.#adopt(c, i);
5888
- } else if (this.#canAdoptWithSpace(c)) {
5889
- done = false;
5890
- this.#adoptWithSpace(c, i);
5891
- } else if (this.#canUsurp(c)) {
5892
- done = false;
5893
- this.#usurp(c);
5894
- }
5895
- }
5896
- }
5897
- } while (!done && ++iterations < 10);
5898
- }
5899
- this.#toString = void 0;
5900
- }
5901
- #partsToRegExp(dot) {
5902
- return this.#parts.map((p) => {
5903
- if (typeof p === "string") {
5904
- throw new Error("string type in extglob ast??");
5905
- }
5906
- const [re, _, _hasMagic, uflag] = p.toRegExpSource(dot);
5907
- this.#uflag = this.#uflag || uflag;
5908
- return re;
5909
- }).filter((p) => !(this.isStart() && this.isEnd()) || !!p).join("|");
5910
- }
5911
- static #parseGlob(glob, hasMagic, noEmpty = false) {
5912
- let escaping = false;
5913
- let re = "";
5914
- let uflag = false;
5915
- let inStar = false;
5916
- for (let i = 0; i < glob.length; i++) {
5917
- const c = glob.charAt(i);
5918
- if (escaping) {
5919
- escaping = false;
5920
- re += (reSpecials.has(c) ? "\\" : "") + c;
5921
- continue;
5922
- }
5923
- if (c === "*") {
5924
- if (inStar)
5925
- continue;
5926
- inStar = true;
5927
- re += noEmpty && /^[*]+$/.test(glob) ? starNoEmpty : star;
5928
- hasMagic = true;
5929
- continue;
5930
- } else {
5931
- inStar = false;
5932
- }
5933
- if (c === "\\") {
5934
- if (i === glob.length - 1) {
5935
- re += "\\\\";
5936
- } else {
5937
- escaping = true;
5938
- }
5939
- continue;
5940
- }
5941
- if (c === "[") {
5942
- const [src, needUflag, consumed, magic] = parseClass(glob, i);
5943
- if (consumed) {
5944
- re += src;
5945
- uflag = uflag || needUflag;
5946
- i += consumed - 1;
5947
- hasMagic = hasMagic || magic;
5948
- continue;
5949
- }
5950
- }
5951
- if (c === "?") {
5952
- re += qmark;
5953
- hasMagic = true;
5954
- continue;
5955
- }
5956
- re += regExpEscape(c);
5957
- }
5958
- return [re, unescape(glob), !!hasMagic, uflag];
5959
- }
5960
- };
5961
- _a = AST;
5962
-
5963
- // ../../node_modules/minimatch/dist/esm/escape.js
5964
- var escape = (s, { windowsPathsNoEscape = false, magicalBraces = false } = {}) => {
5965
- if (magicalBraces) {
5966
- return windowsPathsNoEscape ? s.replace(/[?*()[\]{}]/g, "[$&]") : s.replace(/[?*()[\]\\{}]/g, "\\$&");
5967
- }
5968
- return windowsPathsNoEscape ? s.replace(/[?*()[\]]/g, "[$&]") : s.replace(/[?*()[\]\\]/g, "\\$&");
5969
- };
5970
-
5971
- // ../../node_modules/minimatch/dist/esm/index.js
5972
- var minimatch = (p, pattern, options = {}) => {
5973
- assertValidPattern(pattern);
5974
- if (!options.nocomment && pattern.charAt(0) === "#") {
5975
- return false;
5976
- }
5977
- return new Minimatch(pattern, options).match(p);
5978
- };
5979
- var starDotExtRE = /^\*+([^+@!?*[(]*)$/;
5980
- var starDotExtTest = (ext2) => (f) => !f.startsWith(".") && f.endsWith(ext2);
5981
- var starDotExtTestDot = (ext2) => (f) => f.endsWith(ext2);
5982
- var starDotExtTestNocase = (ext2) => {
5983
- ext2 = ext2.toLowerCase();
5984
- return (f) => !f.startsWith(".") && f.toLowerCase().endsWith(ext2);
5985
- };
5986
- var starDotExtTestNocaseDot = (ext2) => {
5987
- ext2 = ext2.toLowerCase();
5988
- return (f) => f.toLowerCase().endsWith(ext2);
5989
- };
5990
- var starDotStarRE = /^\*+\.\*+$/;
5991
- var starDotStarTest = (f) => !f.startsWith(".") && f.includes(".");
5992
- var starDotStarTestDot = (f) => f !== "." && f !== ".." && f.includes(".");
5993
- var dotStarRE = /^\.\*+$/;
5994
- var dotStarTest = (f) => f !== "." && f !== ".." && f.startsWith(".");
5995
- var starRE = /^\*+$/;
5996
- var starTest = (f) => f.length !== 0 && !f.startsWith(".");
5997
- var starTestDot = (f) => f.length !== 0 && f !== "." && f !== "..";
5998
- var qmarksRE = /^\?+([^+@!?*[(]*)?$/;
5999
- var qmarksTestNocase = ([$0, ext2 = ""]) => {
6000
- const noext = qmarksTestNoExt([$0]);
6001
- if (!ext2)
6002
- return noext;
6003
- ext2 = ext2.toLowerCase();
6004
- return (f) => noext(f) && f.toLowerCase().endsWith(ext2);
6005
- };
6006
- var qmarksTestNocaseDot = ([$0, ext2 = ""]) => {
6007
- const noext = qmarksTestNoExtDot([$0]);
6008
- if (!ext2)
6009
- return noext;
6010
- ext2 = ext2.toLowerCase();
6011
- return (f) => noext(f) && f.toLowerCase().endsWith(ext2);
6012
- };
6013
- var qmarksTestDot = ([$0, ext2 = ""]) => {
6014
- const noext = qmarksTestNoExtDot([$0]);
6015
- return !ext2 ? noext : (f) => noext(f) && f.endsWith(ext2);
6016
- };
6017
- var qmarksTest = ([$0, ext2 = ""]) => {
6018
- const noext = qmarksTestNoExt([$0]);
6019
- return !ext2 ? noext : (f) => noext(f) && f.endsWith(ext2);
6020
- };
6021
- var qmarksTestNoExt = ([$0]) => {
6022
- const len = $0.length;
6023
- return (f) => f.length === len && !f.startsWith(".");
6024
- };
6025
- var qmarksTestNoExtDot = ([$0]) => {
6026
- const len = $0.length;
6027
- return (f) => f.length === len && f !== "." && f !== "..";
6028
- };
6029
- var defaultPlatform = typeof process === "object" && process ? typeof process.env === "object" && process.env && process.env.__MINIMATCH_TESTING_PLATFORM__ || process.platform : "posix";
6030
- var path = {
6031
- win32: { sep: "\\" },
6032
- posix: { sep: "/" }
6033
- };
6034
- var sep4 = defaultPlatform === "win32" ? path.win32.sep : path.posix.sep;
6035
- minimatch.sep = sep4;
6036
- var GLOBSTAR = /* @__PURE__ */ Symbol("globstar **");
6037
- minimatch.GLOBSTAR = GLOBSTAR;
6038
- var qmark2 = "[^/]";
6039
- var star2 = qmark2 + "*?";
6040
- var twoStarDot = "(?:(?!(?:\\/|^)(?:\\.{1,2})($|\\/)).)*?";
6041
- var twoStarNoDot = "(?:(?!(?:\\/|^)\\.).)*?";
6042
- var filter = (pattern, options = {}) => (p) => minimatch(p, pattern, options);
6043
- minimatch.filter = filter;
6044
- var ext = (a, b = {}) => Object.assign({}, a, b);
6045
- var defaults = (def) => {
6046
- if (!def || typeof def !== "object" || !Object.keys(def).length) {
6047
- return minimatch;
6048
- }
6049
- const orig = minimatch;
6050
- const m = (p, pattern, options = {}) => orig(p, pattern, ext(def, options));
6051
- return Object.assign(m, {
6052
- Minimatch: class Minimatch extends orig.Minimatch {
6053
- constructor(pattern, options = {}) {
6054
- super(pattern, ext(def, options));
6055
- }
6056
- static defaults(options) {
6057
- return orig.defaults(ext(def, options)).Minimatch;
6058
- }
6059
- },
6060
- AST: class AST extends orig.AST {
6061
- /* c8 ignore start */
6062
- constructor(type, parent, options = {}) {
6063
- super(type, parent, ext(def, options));
6064
- }
6065
- /* c8 ignore stop */
6066
- static fromGlob(pattern, options = {}) {
6067
- return orig.AST.fromGlob(pattern, ext(def, options));
6068
- }
6069
- },
6070
- unescape: (s, options = {}) => orig.unescape(s, ext(def, options)),
6071
- escape: (s, options = {}) => orig.escape(s, ext(def, options)),
6072
- filter: (pattern, options = {}) => orig.filter(pattern, ext(def, options)),
6073
- defaults: (options) => orig.defaults(ext(def, options)),
6074
- makeRe: (pattern, options = {}) => orig.makeRe(pattern, ext(def, options)),
6075
- braceExpand: (pattern, options = {}) => orig.braceExpand(pattern, ext(def, options)),
6076
- match: (list, pattern, options = {}) => orig.match(list, pattern, ext(def, options)),
6077
- sep: orig.sep,
6078
- GLOBSTAR
6079
- });
6080
- };
6081
- minimatch.defaults = defaults;
6082
- var braceExpand = (pattern, options = {}) => {
6083
- assertValidPattern(pattern);
6084
- if (options.nobrace || !/\{(?:(?!\{).)*\}/.test(pattern)) {
6085
- return [pattern];
6086
- }
6087
- return expand(pattern, { max: options.braceExpandMax });
6088
- };
6089
- minimatch.braceExpand = braceExpand;
6090
- var makeRe = (pattern, options = {}) => new Minimatch(pattern, options).makeRe();
6091
- minimatch.makeRe = makeRe;
6092
- var match = (list, pattern, options = {}) => {
6093
- const mm = new Minimatch(pattern, options);
6094
- list = list.filter((f) => mm.match(f));
6095
- if (mm.options.nonull && !list.length) {
6096
- list.push(pattern);
6097
- }
6098
- return list;
6099
- };
6100
- minimatch.match = match;
6101
- var globMagic = /[?*]|[+@!]\(.*?\)|\[|\]/;
6102
- var regExpEscape2 = (s) => s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
6103
- var Minimatch = class {
6104
- options;
6105
- set;
6106
- pattern;
6107
- windowsPathsNoEscape;
6108
- nonegate;
6109
- negate;
6110
- comment;
6111
- empty;
6112
- preserveMultipleSlashes;
6113
- partial;
6114
- globSet;
6115
- globParts;
6116
- nocase;
6117
- isWindows;
6118
- platform;
6119
- windowsNoMagicRoot;
6120
- maxGlobstarRecursion;
6121
- regexp;
6122
- constructor(pattern, options = {}) {
6123
- assertValidPattern(pattern);
6124
- options = options || {};
6125
- this.options = options;
6126
- this.maxGlobstarRecursion = options.maxGlobstarRecursion ?? 200;
6127
- this.pattern = pattern;
6128
- this.platform = options.platform || defaultPlatform;
6129
- this.isWindows = this.platform === "win32";
6130
- const awe = "allowWindowsEscape";
6131
- this.windowsPathsNoEscape = !!options.windowsPathsNoEscape || options[awe] === false;
6132
- if (this.windowsPathsNoEscape) {
6133
- this.pattern = this.pattern.replace(/\\/g, "/");
6134
- }
6135
- this.preserveMultipleSlashes = !!options.preserveMultipleSlashes;
6136
- this.regexp = null;
6137
- this.negate = false;
6138
- this.nonegate = !!options.nonegate;
6139
- this.comment = false;
6140
- this.empty = false;
6141
- this.partial = !!options.partial;
6142
- this.nocase = !!this.options.nocase;
6143
- this.windowsNoMagicRoot = options.windowsNoMagicRoot !== void 0 ? options.windowsNoMagicRoot : !!(this.isWindows && this.nocase);
6144
- this.globSet = [];
6145
- this.globParts = [];
6146
- this.set = [];
6147
- this.make();
6148
- }
6149
- hasMagic() {
6150
- if (this.options.magicalBraces && this.set.length > 1) {
6151
- return true;
6152
- }
6153
- for (const pattern of this.set) {
6154
- for (const part of pattern) {
6155
- if (typeof part !== "string")
6156
- return true;
6157
- }
6158
- }
6159
- return false;
6160
- }
6161
- debug(..._) {
6162
- }
6163
- make() {
6164
- const pattern = this.pattern;
6165
- const options = this.options;
6166
- if (!options.nocomment && pattern.charAt(0) === "#") {
6167
- this.comment = true;
6168
- return;
6169
- }
6170
- if (!pattern) {
6171
- this.empty = true;
6172
- return;
6173
- }
6174
- this.parseNegate();
6175
- this.globSet = [...new Set(this.braceExpand())];
6176
- if (options.debug) {
6177
- this.debug = (...args) => console.error(...args);
6178
- }
6179
- this.debug(this.pattern, this.globSet);
6180
- const rawGlobParts = this.globSet.map((s) => this.slashSplit(s));
6181
- this.globParts = this.preprocess(rawGlobParts);
6182
- this.debug(this.pattern, this.globParts);
6183
- let set = this.globParts.map((s, _, __) => {
6184
- if (this.isWindows && this.windowsNoMagicRoot) {
6185
- const isUNC = s[0] === "" && s[1] === "" && (s[2] === "?" || !globMagic.test(s[2])) && !globMagic.test(s[3]);
6186
- const isDrive = /^[a-z]:/i.test(s[0]);
6187
- if (isUNC) {
6188
- return [
6189
- ...s.slice(0, 4),
6190
- ...s.slice(4).map((ss) => this.parse(ss))
6191
- ];
6192
- } else if (isDrive) {
6193
- return [s[0], ...s.slice(1).map((ss) => this.parse(ss))];
6194
- }
6195
- }
6196
- return s.map((ss) => this.parse(ss));
6197
- });
6198
- this.debug(this.pattern, set);
6199
- this.set = set.filter((s) => s.indexOf(false) === -1);
6200
- if (this.isWindows) {
6201
- for (let i = 0; i < this.set.length; i++) {
6202
- const p = this.set[i];
6203
- if (p[0] === "" && p[1] === "" && this.globParts[i][2] === "?" && typeof p[3] === "string" && /^[a-z]:$/i.test(p[3])) {
6204
- p[2] = "?";
6205
- }
6206
- }
6207
- }
6208
- this.debug(this.pattern, this.set);
6209
- }
6210
- // various transforms to equivalent pattern sets that are
6211
- // faster to process in a filesystem walk. The goal is to
6212
- // eliminate what we can, and push all ** patterns as far
6213
- // to the right as possible, even if it increases the number
6214
- // of patterns that we have to process.
6215
- preprocess(globParts) {
6216
- if (this.options.noglobstar) {
6217
- for (const partset of globParts) {
6218
- for (let j = 0; j < partset.length; j++) {
6219
- if (partset[j] === "**") {
6220
- partset[j] = "*";
6221
- }
6222
- }
6223
- }
6224
- }
6225
- const { optimizationLevel = 1 } = this.options;
6226
- if (optimizationLevel >= 2) {
6227
- globParts = this.firstPhasePreProcess(globParts);
6228
- globParts = this.secondPhasePreProcess(globParts);
6229
- } else if (optimizationLevel >= 1) {
6230
- globParts = this.levelOneOptimize(globParts);
6231
- } else {
6232
- globParts = this.adjascentGlobstarOptimize(globParts);
6233
- }
6234
- return globParts;
6235
- }
6236
- // just get rid of adjascent ** portions
6237
- adjascentGlobstarOptimize(globParts) {
6238
- return globParts.map((parts) => {
6239
- let gs = -1;
6240
- while (-1 !== (gs = parts.indexOf("**", gs + 1))) {
6241
- let i = gs;
6242
- while (parts[i + 1] === "**") {
6243
- i++;
6244
- }
6245
- if (i !== gs) {
6246
- parts.splice(gs, i - gs);
6247
- }
6248
- }
6249
- return parts;
6250
- });
6251
- }
6252
- // get rid of adjascent ** and resolve .. portions
6253
- levelOneOptimize(globParts) {
6254
- return globParts.map((parts) => {
6255
- parts = parts.reduce((set, part) => {
6256
- const prev = set[set.length - 1];
6257
- if (part === "**" && prev === "**") {
6258
- return set;
6259
- }
6260
- if (part === "..") {
6261
- if (prev && prev !== ".." && prev !== "." && prev !== "**") {
6262
- set.pop();
6263
- return set;
6264
- }
6265
- }
6266
- set.push(part);
6267
- return set;
6268
- }, []);
6269
- return parts.length === 0 ? [""] : parts;
6270
- });
6271
- }
6272
- levelTwoFileOptimize(parts) {
6273
- if (!Array.isArray(parts)) {
6274
- parts = this.slashSplit(parts);
6275
- }
6276
- let didSomething = false;
6277
- do {
6278
- didSomething = false;
6279
- if (!this.preserveMultipleSlashes) {
6280
- for (let i = 1; i < parts.length - 1; i++) {
6281
- const p = parts[i];
6282
- if (i === 1 && p === "" && parts[0] === "")
6283
- continue;
6284
- if (p === "." || p === "") {
6285
- didSomething = true;
6286
- parts.splice(i, 1);
6287
- i--;
6288
- }
6289
- }
6290
- if (parts[0] === "." && parts.length === 2 && (parts[1] === "." || parts[1] === "")) {
6291
- didSomething = true;
6292
- parts.pop();
6293
- }
6294
- }
6295
- let dd = 0;
6296
- while (-1 !== (dd = parts.indexOf("..", dd + 1))) {
6297
- const p = parts[dd - 1];
6298
- if (p && p !== "." && p !== ".." && p !== "**" && !(this.isWindows && /^[a-z]:$/i.test(p))) {
6299
- didSomething = true;
6300
- parts.splice(dd - 1, 2);
6301
- dd -= 2;
6302
- }
6303
- }
6304
- } while (didSomething);
6305
- return parts.length === 0 ? [""] : parts;
6306
- }
6307
- // First phase: single-pattern processing
6308
- // <pre> is 1 or more portions
6309
- // <rest> is 1 or more portions
6310
- // <p> is any portion other than ., .., '', or **
6311
- // <e> is . or ''
6312
- //
6313
- // **/.. is *brutal* for filesystem walking performance, because
6314
- // it effectively resets the recursive walk each time it occurs,
6315
- // and ** cannot be reduced out by a .. pattern part like a regexp
6316
- // or most strings (other than .., ., and '') can be.
6317
- //
6318
- // <pre>/**/../<p>/<p>/<rest> -> {<pre>/../<p>/<p>/<rest>,<pre>/**/<p>/<p>/<rest>}
6319
- // <pre>/<e>/<rest> -> <pre>/<rest>
6320
- // <pre>/<p>/../<rest> -> <pre>/<rest>
6321
- // **/**/<rest> -> **/<rest>
6322
- //
6323
- // **/*/<rest> -> */**/<rest> <== not valid because ** doesn't follow
6324
- // this WOULD be allowed if ** did follow symlinks, or * didn't
6325
- firstPhasePreProcess(globParts) {
6326
- let didSomething = false;
6327
- do {
6328
- didSomething = false;
6329
- for (let parts of globParts) {
6330
- let gs = -1;
6331
- while (-1 !== (gs = parts.indexOf("**", gs + 1))) {
6332
- let gss = gs;
6333
- while (parts[gss + 1] === "**") {
6334
- gss++;
6335
- }
6336
- if (gss > gs) {
6337
- parts.splice(gs + 1, gss - gs);
6338
- }
6339
- let next = parts[gs + 1];
6340
- const p = parts[gs + 2];
6341
- const p2 = parts[gs + 3];
6342
- if (next !== "..")
6343
- continue;
6344
- if (!p || p === "." || p === ".." || !p2 || p2 === "." || p2 === "..") {
6345
- continue;
6346
- }
6347
- didSomething = true;
6348
- parts.splice(gs, 1);
6349
- const other = parts.slice(0);
6350
- other[gs] = "**";
6351
- globParts.push(other);
6352
- gs--;
6353
- }
6354
- if (!this.preserveMultipleSlashes) {
6355
- for (let i = 1; i < parts.length - 1; i++) {
6356
- const p = parts[i];
6357
- if (i === 1 && p === "" && parts[0] === "")
6358
- continue;
6359
- if (p === "." || p === "") {
6360
- didSomething = true;
6361
- parts.splice(i, 1);
6362
- i--;
6363
- }
6364
- }
6365
- if (parts[0] === "." && parts.length === 2 && (parts[1] === "." || parts[1] === "")) {
6366
- didSomething = true;
6367
- parts.pop();
6368
- }
6369
- }
6370
- let dd = 0;
6371
- while (-1 !== (dd = parts.indexOf("..", dd + 1))) {
6372
- const p = parts[dd - 1];
6373
- if (p && p !== "." && p !== ".." && p !== "**") {
6374
- didSomething = true;
6375
- const needDot = dd === 1 && parts[dd + 1] === "**";
6376
- const splin = needDot ? ["."] : [];
6377
- parts.splice(dd - 1, 2, ...splin);
6378
- if (parts.length === 0)
6379
- parts.push("");
6380
- dd -= 2;
6381
- }
6382
- }
6383
- }
6384
- } while (didSomething);
6385
- return globParts;
6386
- }
6387
- // second phase: multi-pattern dedupes
6388
- // {<pre>/*/<rest>,<pre>/<p>/<rest>} -> <pre>/*/<rest>
6389
- // {<pre>/<rest>,<pre>/<rest>} -> <pre>/<rest>
6390
- // {<pre>/**/<rest>,<pre>/<rest>} -> <pre>/**/<rest>
6391
- //
6392
- // {<pre>/**/<rest>,<pre>/**/<p>/<rest>} -> <pre>/**/<rest>
6393
- // ^-- not valid because ** doens't follow symlinks
6394
- secondPhasePreProcess(globParts) {
6395
- for (let i = 0; i < globParts.length - 1; i++) {
6396
- for (let j = i + 1; j < globParts.length; j++) {
6397
- const matched = this.partsMatch(globParts[i], globParts[j], !this.preserveMultipleSlashes);
6398
- if (matched) {
6399
- globParts[i] = [];
6400
- globParts[j] = matched;
6401
- break;
6402
- }
6403
- }
6404
- }
6405
- return globParts.filter((gs) => gs.length);
6406
- }
6407
- partsMatch(a, b, emptyGSMatch = false) {
6408
- let ai = 0;
6409
- let bi = 0;
6410
- let result = [];
6411
- let which = "";
6412
- while (ai < a.length && bi < b.length) {
6413
- if (a[ai] === b[bi]) {
6414
- result.push(which === "b" ? b[bi] : a[ai]);
6415
- ai++;
6416
- bi++;
6417
- } else if (emptyGSMatch && a[ai] === "**" && b[bi] === a[ai + 1]) {
6418
- result.push(a[ai]);
6419
- ai++;
6420
- } else if (emptyGSMatch && b[bi] === "**" && a[ai] === b[bi + 1]) {
6421
- result.push(b[bi]);
6422
- bi++;
6423
- } else if (a[ai] === "*" && b[bi] && (this.options.dot || !b[bi].startsWith(".")) && b[bi] !== "**") {
6424
- if (which === "b")
6425
- return false;
6426
- which = "a";
6427
- result.push(a[ai]);
6428
- ai++;
6429
- bi++;
6430
- } else if (b[bi] === "*" && a[ai] && (this.options.dot || !a[ai].startsWith(".")) && a[ai] !== "**") {
6431
- if (which === "a")
6432
- return false;
6433
- which = "b";
6434
- result.push(b[bi]);
6435
- ai++;
6436
- bi++;
6437
- } else {
6438
- return false;
6439
- }
6440
- }
6441
- return a.length === b.length && result;
6442
- }
6443
- parseNegate() {
6444
- if (this.nonegate)
6445
- return;
6446
- const pattern = this.pattern;
6447
- let negate = false;
6448
- let negateOffset = 0;
6449
- for (let i = 0; i < pattern.length && pattern.charAt(i) === "!"; i++) {
6450
- negate = !negate;
6451
- negateOffset++;
6452
- }
6453
- if (negateOffset)
6454
- this.pattern = pattern.slice(negateOffset);
6455
- this.negate = negate;
6456
- }
6457
- // set partial to true to test if, for example,
6458
- // "/a/b" matches the start of "/*/b/*/d"
6459
- // Partial means, if you run out of file before you run
6460
- // out of pattern, then that's fine, as long as all
6461
- // the parts match.
6462
- matchOne(file, pattern, partial = false) {
6463
- let fileStartIndex = 0;
6464
- let patternStartIndex = 0;
6465
- if (this.isWindows) {
6466
- const fileDrive = typeof file[0] === "string" && /^[a-z]:$/i.test(file[0]);
6467
- const fileUNC = !fileDrive && file[0] === "" && file[1] === "" && file[2] === "?" && /^[a-z]:$/i.test(file[3]);
6468
- const patternDrive = typeof pattern[0] === "string" && /^[a-z]:$/i.test(pattern[0]);
6469
- const patternUNC = !patternDrive && pattern[0] === "" && pattern[1] === "" && pattern[2] === "?" && typeof pattern[3] === "string" && /^[a-z]:$/i.test(pattern[3]);
6470
- const fdi = fileUNC ? 3 : fileDrive ? 0 : void 0;
6471
- const pdi = patternUNC ? 3 : patternDrive ? 0 : void 0;
6472
- if (typeof fdi === "number" && typeof pdi === "number") {
6473
- const [fd, pd] = [
6474
- file[fdi],
6475
- pattern[pdi]
6476
- ];
6477
- if (fd.toLowerCase() === pd.toLowerCase()) {
6478
- pattern[pdi] = fd;
6479
- patternStartIndex = pdi;
6480
- fileStartIndex = fdi;
6481
- }
6482
- }
6483
- }
6484
- const { optimizationLevel = 1 } = this.options;
6485
- if (optimizationLevel >= 2) {
6486
- file = this.levelTwoFileOptimize(file);
6487
- }
6488
- if (pattern.includes(GLOBSTAR)) {
6489
- return this.#matchGlobstar(file, pattern, partial, fileStartIndex, patternStartIndex);
6490
- }
6491
- return this.#matchOne(file, pattern, partial, fileStartIndex, patternStartIndex);
6492
- }
6493
- #matchGlobstar(file, pattern, partial, fileIndex, patternIndex) {
6494
- const firstgs = pattern.indexOf(GLOBSTAR, patternIndex);
6495
- const lastgs = pattern.lastIndexOf(GLOBSTAR);
6496
- const [head, body, tail] = partial ? [
6497
- pattern.slice(patternIndex, firstgs),
6498
- pattern.slice(firstgs + 1),
6499
- []
6500
- ] : [
6501
- pattern.slice(patternIndex, firstgs),
6502
- pattern.slice(firstgs + 1, lastgs),
6503
- pattern.slice(lastgs + 1)
6504
- ];
6505
- if (head.length) {
6506
- const fileHead = file.slice(fileIndex, fileIndex + head.length);
6507
- if (!this.#matchOne(fileHead, head, partial, 0, 0)) {
6508
- return false;
6509
- }
6510
- fileIndex += head.length;
6511
- patternIndex += head.length;
6512
- }
6513
- let fileTailMatch = 0;
6514
- if (tail.length) {
6515
- if (tail.length + fileIndex > file.length)
6516
- return false;
6517
- let tailStart = file.length - tail.length;
6518
- if (this.#matchOne(file, tail, partial, tailStart, 0)) {
6519
- fileTailMatch = tail.length;
6520
- } else {
6521
- if (file[file.length - 1] !== "" || fileIndex + tail.length === file.length) {
6522
- return false;
6523
- }
6524
- tailStart--;
6525
- if (!this.#matchOne(file, tail, partial, tailStart, 0)) {
6526
- return false;
6527
- }
6528
- fileTailMatch = tail.length + 1;
6529
- }
6530
- }
6531
- if (!body.length) {
6532
- let sawSome = !!fileTailMatch;
6533
- for (let i2 = fileIndex; i2 < file.length - fileTailMatch; i2++) {
6534
- const f = String(file[i2]);
6535
- sawSome = true;
6536
- if (f === "." || f === ".." || !this.options.dot && f.startsWith(".")) {
6537
- return false;
6538
- }
6539
- }
6540
- return partial || sawSome;
6541
- }
6542
- const bodySegments = [[[], 0]];
6543
- let currentBody = bodySegments[0];
6544
- let nonGsParts = 0;
6545
- const nonGsPartsSums = [0];
6546
- for (const b of body) {
6547
- if (b === GLOBSTAR) {
6548
- nonGsPartsSums.push(nonGsParts);
6549
- currentBody = [[], 0];
6550
- bodySegments.push(currentBody);
6551
- } else {
6552
- currentBody[0].push(b);
6553
- nonGsParts++;
6554
- }
6555
- }
6556
- let i = bodySegments.length - 1;
6557
- const fileLength = file.length - fileTailMatch;
6558
- for (const b of bodySegments) {
6559
- b[1] = fileLength - (nonGsPartsSums[i--] + b[0].length);
6560
- }
6561
- return !!this.#matchGlobStarBodySections(file, bodySegments, fileIndex, 0, partial, 0, !!fileTailMatch);
6562
- }
6563
- // return false for "nope, not matching"
6564
- // return null for "not matching, cannot keep trying"
6565
- #matchGlobStarBodySections(file, bodySegments, fileIndex, bodyIndex, partial, globStarDepth, sawTail) {
6566
- const bs = bodySegments[bodyIndex];
6567
- if (!bs) {
6568
- for (let i = fileIndex; i < file.length; i++) {
6569
- sawTail = true;
6570
- const f = file[i];
6571
- if (f === "." || f === ".." || !this.options.dot && f.startsWith(".")) {
6572
- return false;
6573
- }
6574
- }
6575
- return sawTail;
6576
- }
6577
- const [body, after] = bs;
6578
- while (fileIndex <= after) {
6579
- const m = this.#matchOne(file.slice(0, fileIndex + body.length), body, partial, fileIndex, 0);
6580
- if (m && globStarDepth < this.maxGlobstarRecursion) {
6581
- const sub = this.#matchGlobStarBodySections(file, bodySegments, fileIndex + body.length, bodyIndex + 1, partial, globStarDepth + 1, sawTail);
6582
- if (sub !== false) {
6583
- return sub;
6584
- }
6585
- }
6586
- const f = file[fileIndex];
6587
- if (f === "." || f === ".." || !this.options.dot && f.startsWith(".")) {
6588
- return false;
6589
- }
6590
- fileIndex++;
6591
- }
6592
- return partial || null;
6593
- }
6594
- #matchOne(file, pattern, partial, fileIndex, patternIndex) {
6595
- let fi;
6596
- let pi;
6597
- let pl;
6598
- let fl;
6599
- for (fi = fileIndex, pi = patternIndex, fl = file.length, pl = pattern.length; fi < fl && pi < pl; fi++, pi++) {
6600
- this.debug("matchOne loop");
6601
- let p = pattern[pi];
6602
- let f = file[fi];
6603
- this.debug(pattern, p, f);
6604
- if (p === false || p === GLOBSTAR) {
6605
- return false;
6606
- }
6607
- let hit;
6608
- if (typeof p === "string") {
6609
- hit = f === p;
6610
- this.debug("string match", p, f, hit);
6611
- } else {
6612
- hit = p.test(f);
6613
- this.debug("pattern match", p, f, hit);
6614
- }
6615
- if (!hit)
6616
- return false;
6617
- }
6618
- if (fi === fl && pi === pl) {
6619
- return true;
6620
- } else if (fi === fl) {
6621
- return partial;
6622
- } else if (pi === pl) {
6623
- return fi === fl - 1 && file[fi] === "";
6624
- } else {
6625
- throw new Error("wtf?");
6626
- }
6627
- }
6628
- braceExpand() {
6629
- return braceExpand(this.pattern, this.options);
6630
- }
6631
- parse(pattern) {
6632
- assertValidPattern(pattern);
6633
- const options = this.options;
6634
- if (pattern === "**")
6635
- return GLOBSTAR;
6636
- if (pattern === "")
6637
- return "";
6638
- let m;
6639
- let fastTest = null;
6640
- if (m = pattern.match(starRE)) {
6641
- fastTest = options.dot ? starTestDot : starTest;
6642
- } else if (m = pattern.match(starDotExtRE)) {
6643
- fastTest = (options.nocase ? options.dot ? starDotExtTestNocaseDot : starDotExtTestNocase : options.dot ? starDotExtTestDot : starDotExtTest)(m[1]);
6644
- } else if (m = pattern.match(qmarksRE)) {
6645
- fastTest = (options.nocase ? options.dot ? qmarksTestNocaseDot : qmarksTestNocase : options.dot ? qmarksTestDot : qmarksTest)(m);
6646
- } else if (m = pattern.match(starDotStarRE)) {
6647
- fastTest = options.dot ? starDotStarTestDot : starDotStarTest;
6648
- } else if (m = pattern.match(dotStarRE)) {
6649
- fastTest = dotStarTest;
6650
- }
6651
- const re = AST.fromGlob(pattern, this.options).toMMPattern();
6652
- if (fastTest && typeof re === "object") {
6653
- Reflect.defineProperty(re, "test", { value: fastTest });
6654
- }
6655
- return re;
6656
- }
6657
- makeRe() {
6658
- if (this.regexp || this.regexp === false)
6659
- return this.regexp;
6660
- const set = this.set;
6661
- if (!set.length) {
6662
- this.regexp = false;
6663
- return this.regexp;
6664
- }
6665
- const options = this.options;
6666
- const twoStar = options.noglobstar ? star2 : options.dot ? twoStarDot : twoStarNoDot;
6667
- const flags = new Set(options.nocase ? ["i"] : []);
6668
- let re = set.map((pattern) => {
6669
- const pp = pattern.map((p) => {
6670
- if (p instanceof RegExp) {
6671
- for (const f of p.flags.split(""))
6672
- flags.add(f);
6673
- }
6674
- return typeof p === "string" ? regExpEscape2(p) : p === GLOBSTAR ? GLOBSTAR : p._src;
6675
- });
6676
- pp.forEach((p, i) => {
6677
- const next = pp[i + 1];
6678
- const prev = pp[i - 1];
6679
- if (p !== GLOBSTAR || prev === GLOBSTAR) {
6680
- return;
6681
- }
6682
- if (prev === void 0) {
6683
- if (next !== void 0 && next !== GLOBSTAR) {
6684
- pp[i + 1] = "(?:\\/|" + twoStar + "\\/)?" + next;
6685
- } else {
6686
- pp[i] = twoStar;
6687
- }
6688
- } else if (next === void 0) {
6689
- pp[i - 1] = prev + "(?:\\/|\\/" + twoStar + ")?";
6690
- } else if (next !== GLOBSTAR) {
6691
- pp[i - 1] = prev + "(?:\\/|\\/" + twoStar + "\\/)" + next;
6692
- pp[i + 1] = GLOBSTAR;
6693
- }
6694
- });
6695
- const filtered = pp.filter((p) => p !== GLOBSTAR);
6696
- if (this.partial && filtered.length >= 1) {
6697
- const prefixes = [];
6698
- for (let i = 1; i <= filtered.length; i++) {
6699
- prefixes.push(filtered.slice(0, i).join("/"));
6700
- }
6701
- return "(?:" + prefixes.join("|") + ")";
6702
- }
6703
- return filtered.join("/");
6704
- }).join("|");
6705
- const [open2, close] = set.length > 1 ? ["(?:", ")"] : ["", ""];
6706
- re = "^" + open2 + re + close + "$";
6707
- if (this.partial) {
6708
- re = "^(?:\\/|" + open2 + re.slice(1, -1) + close + ")$";
6709
- }
6710
- if (this.negate)
6711
- re = "^(?!" + re + ").+$";
6712
- try {
6713
- this.regexp = new RegExp(re, [...flags].join(""));
6714
- } catch {
6715
- this.regexp = false;
6716
- }
6717
- return this.regexp;
6718
- }
6719
- slashSplit(p) {
6720
- if (this.preserveMultipleSlashes) {
6721
- return p.split("/");
6722
- } else if (this.isWindows && /^\/\/[^/]+/.test(p)) {
6723
- return ["", ...p.split(/\/+/)];
6724
- } else {
6725
- return p.split(/\/+/);
6726
- }
6727
- }
6728
- match(f, partial = this.partial) {
6729
- this.debug("match", f, this.pattern);
6730
- if (this.comment) {
6731
- return false;
6732
- }
6733
- if (this.empty) {
6734
- return f === "";
6735
- }
6736
- if (f === "/" && partial) {
6737
- return true;
6738
- }
6739
- const options = this.options;
6740
- if (this.isWindows) {
6741
- f = f.split("\\").join("/");
6742
- }
6743
- const ff = this.slashSplit(f);
6744
- this.debug(this.pattern, "split", ff);
6745
- const set = this.set;
6746
- this.debug(this.pattern, "set", set);
6747
- let filename = ff[ff.length - 1];
6748
- if (!filename) {
6749
- for (let i = ff.length - 2; !filename && i >= 0; i--) {
6750
- filename = ff[i];
6751
- }
6752
- }
6753
- for (const pattern of set) {
6754
- let file = ff;
6755
- if (options.matchBase && pattern.length === 1) {
6756
- file = [filename];
6757
- }
6758
- const hit = this.matchOne(file, pattern, partial);
6759
- if (hit) {
6760
- if (options.flipNegate) {
6761
- return true;
6762
- }
6763
- return !this.negate;
6764
- }
6765
- }
6766
- if (options.flipNegate) {
6767
- return false;
6768
- }
6769
- return this.negate;
6770
- }
6771
- static defaults(def) {
6772
- return minimatch.defaults(def).Minimatch;
6773
- }
6774
- };
6775
- minimatch.AST = AST;
6776
- minimatch.Minimatch = Minimatch;
6777
- minimatch.escape = escape;
6778
- minimatch.unescape = unescape;
6779
-
6780
- // dist/mcp/SessionScope.js
6781
- var ScopeViolation = class extends Error {
6782
- path;
6783
- workOrderId;
6784
- constructor(path2, workOrderId, reason) {
6785
- super(`ScopeViolation [${workOrderId}]: ${reason} \u2014 path: ${path2}`);
6786
- this.path = path2;
6787
- this.workOrderId = workOrderId;
6788
- this.name = "ScopeViolation";
6789
- }
6790
- };
6791
- var activeScope = null;
6792
- var SessionScope = {
6793
- set(policy) {
6794
- activeScope = policy;
6795
- },
6796
- clear() {
6797
- activeScope = null;
6798
- },
6799
- get() {
6800
- return activeScope;
6801
- },
6802
- /**
6803
- * Enforce scope for a file path.
6804
- * Throws ScopeViolation if:
6805
- * - a scope is active AND the path is not allowed
6806
- * If no scope is active, all paths are permitted.
6807
- */
6808
- enforce(filePath) {
6809
- if (!activeScope)
6810
- return;
6811
- const { workOrderId, allowedFiles, allowedGlobs, deniedGlobs } = activeScope;
6812
- const inAllowedFiles = allowedFiles.some((f) => filePath === f || filePath.endsWith("/" + f) || filePath.endsWith(f));
6813
- const inAllowedGlobs = allowedGlobs.some((g) => minimatch(filePath, g, { matchBase: true }));
6814
- if (!inAllowedFiles && !inAllowedGlobs) {
6815
- throw new ScopeViolation(filePath, workOrderId, "path not in allowedFiles or allowedGlobs");
6816
- }
6817
- const isDenied = deniedGlobs.some((g) => minimatch(filePath, g, { matchBase: true }));
6818
- if (isDenied) {
6819
- throw new ScopeViolation(filePath, workOrderId, "path matches deniedGlobs");
6820
- }
6821
- },
6822
- fromWorkOrderRow(row) {
6823
- return {
6824
- workOrderId: row.workOrderId,
6825
- allowedFiles: JSON.parse(row.allowedFiles),
6826
- allowedGlobs: JSON.parse(row.allowedGlobs),
6827
- deniedGlobs: JSON.parse(row.deniedGlobs),
6828
- requiredProof: JSON.parse(row.requiredProof)
6829
- };
6830
- }
6831
- };
6832
-
6833
- // dist/mcp/tools/get_file_skeleton.js
6834
- init_BlobStore();
6835
- init_PointerStore();
6836
-
6837
- // dist/mcp/BudgetGuard.js
6838
- init_BlobStore();
6839
- init_PointerStore();
6840
- init_esm_node();
6841
- var BUDGET_CHARS = 8e3;
6842
- async function applyBudgetGuard(projectRoot, scanId, artifactName, output) {
6843
- const serialized = JSON.stringify(output, null, 2);
6844
- if (serialized.length <= BUDGET_CHARS)
6845
- return output;
6846
- const blobStore = new BlobStore(projectRoot);
6847
- const pointerStore = PointerStore.open(projectRoot);
6848
- const { contentHash, blobPath } = await blobStore.writeAtomic(serialized);
6849
- const pointerId = `ptr_${v4_default().replace(/-/g, "").slice(0, 16)}`;
6850
- await pointerStore.insertPointer({
6851
- pointerId,
6852
- scanId,
6853
- artifactName,
6854
- contentHash,
6855
- blobPath,
6856
- schemaVersion: "1.0.0",
6857
- createdAt: Date.now(),
6858
- expiresAt: null
6859
- });
6860
- const result = {
6861
- pointerId,
6862
- contentHash,
6863
- sizeBytes: serialized.length,
6864
- summary: `Output exceeded context budget (${serialized.length} chars). Written to artifact blob.`,
6865
- hydrators: ["get_evidence_slice", "get_start_here", "get_project_summary"]
6866
- };
6867
- return result;
6868
- }
6869
- async function hydratePointer(projectRoot, pointerId) {
6870
- const pointerStore = PointerStore.open(projectRoot);
6871
- const row = pointerStore.getPointer(pointerId);
6872
- if (!row) {
6873
- throw new Error(`ArtifactNotFound: pointer ${pointerId} does not exist`);
6874
- }
6875
- if (row.expiresAt !== null && row.expiresAt < Date.now()) {
6876
- throw new Error(`ArtifactCollectedError: pointer ${pointerId} has expired`);
6877
- }
6878
- const SUPPORTED_VERSIONS = ["1.0.0", "2.0.0"];
6879
- if (!SUPPORTED_VERSIONS.includes(row.schemaVersion)) {
6880
- throw new Error(`UnsupportedSchema: pointer ${pointerId} has schema version ${row.schemaVersion}`);
6881
- }
6882
- const blobStore = new BlobStore(projectRoot);
6883
- const content = await blobStore.readBlob(row.blobPath);
6884
- const valid = await blobStore.verifyIntegrity(row.blobPath, row.contentHash);
6885
- if (!valid) {
6886
- throw new Error(`IntegrityError: blob for pointer ${pointerId} failed hash verification`);
6887
- }
6888
- return { content, row };
6889
- }
6890
-
6891
- // dist/mcp/tools/get_file_skeleton.js
6892
- init_esm_node();
6893
- var getFileSkeletonTool = {
6894
- name: "get_file_skeleton",
6895
- description: "Returns a content-addressed skeleton view of a source file (function signatures, class names, exported symbols). Enforces active workOrder scope. Results are content-addressed \u2014 repeated calls on unchanged files return cached pointers.",
6896
- inputSchema: {
6897
- type: "object",
6898
- properties: {
6899
- projectRoot: { type: "string", description: "Absolute project root" },
6900
- filePath: { type: "string", description: "Path relative to projectRoot" },
6901
- scanId: { type: "string", description: "Current scan ID for pointer registration" }
6902
- },
6903
- required: ["projectRoot", "filePath", "scanId"]
6904
- }
6905
- };
6906
- async function handleGetFileSkeleton(args) {
6907
- const projectRoot = args.projectRoot;
6908
- const filePath = args.filePath;
6909
- const scanId = args.scanId;
6910
- if (!projectRoot || !filePath || !scanId) {
6911
- throw new Error("projectRoot, filePath, and scanId are required");
6912
- }
6913
- try {
6914
- SessionScope.enforce(filePath);
6915
- } catch (e) {
6916
- if (e instanceof ScopeViolation)
6917
- throw e;
6918
- throw e;
6919
- }
6920
- const absolutePath = filePath.startsWith("/") ? filePath : join16(projectRoot, filePath);
6921
- let currentHash;
6922
- try {
6923
- currentHash = await hashFile(absolutePath);
6924
- } catch {
6925
- throw new Error(`FileNotFound: cannot read ${filePath}`);
6926
- }
6927
- const parserVersion = "1.0.0";
6928
- const cacheKey = `skeleton:${currentHash}:${parserVersion}`;
6929
- const pointerStore = PointerStore.open(projectRoot);
6930
- const existingPointers = pointerStore.listPointersByScan(scanId);
6931
- const cached = existingPointers.find((p) => p.artifactName === "file_skeleton" && p.contentHash === cacheKey);
6932
- if (cached) {
6933
- return {
6934
- pointerId: cached.pointerId,
6935
- contentHash: currentHash,
6936
- cached: true,
6937
- filePath
6938
- };
6939
- }
6940
- const source = await readFile10(absolutePath, "utf8");
6941
- const skeleton = extractSkeleton(source, filePath);
6942
- const skeletonPayload = {
6943
- filePath,
6944
- sourceHash: currentHash,
6945
- parserVersion,
6946
- skeleton
6947
- };
6948
- const blobStore = new BlobStore(projectRoot);
6949
- const serialized = JSON.stringify(skeletonPayload, null, 2);
6950
- const { contentHash: skeletonHash, blobPath } = await blobStore.writeAtomic(serialized);
6951
- const pointerId = `ptr_skel_${v4_default().replace(/-/g, "").slice(0, 12)}`;
6952
- await pointerStore.insertPointer({
6953
- pointerId,
6954
- scanId,
6955
- artifactName: "file_skeleton",
6956
- contentHash: cacheKey,
6957
- // cache key encodes source hash + parser version
6958
- blobPath,
6959
- schemaVersion: "1.0.0",
6960
- createdAt: Date.now(),
6961
- expiresAt: null
6962
- });
6963
- const result = {
6964
- filePath,
6965
- sourceHash: currentHash,
6966
- pointerId,
6967
- skeleton
6968
- };
6969
- return await applyBudgetGuard(projectRoot, scanId, "file_skeleton", result);
6970
- }
6971
- function extractSkeleton(source, filePath) {
6972
- const lines = source.split("\n");
6973
- const skeleton = [];
6974
- const ext2 = filePath.split(".").pop() ?? "";
6975
- const isTS = ["ts", "tsx"].includes(ext2);
6976
- const isJS = ["js", "jsx", "mjs", "cjs"].includes(ext2);
6977
- if (isTS || isJS) {
6978
- for (let i = 0; i < lines.length; i++) {
6979
- const line = lines[i].trim();
6980
- if (/^(export\s+)?(async\s+)?function\b/.test(line) || /^(export\s+)?(abstract\s+)?class\b/.test(line) || /^(export\s+)?interface\b/.test(line) || /^(export\s+)?type\s+\w+/.test(line) || /^(export\s+)?enum\b/.test(line) || /^(export\s+)?const\s+\w+\s*[:=(]/.test(line) || /^(export\s+)?let\s+\w+\s*[:=(]/.test(line) || /^(export\s+)?(default\s+)/.test(line) || /^\s*(public|private|protected|static|readonly|abstract)\s+/.test(line) || /^import\b/.test(line)) {
6981
- skeleton.push(`L${i + 1}: ${lines[i]}`);
6982
- }
6983
- }
6984
- } else {
6985
- return lines.slice(0, 80).map((l, i) => `L${i + 1}: ${l}`);
6986
- }
6987
- return skeleton;
6988
- }
6989
-
6990
- // dist/mcp/tools/read_file.js
6991
- import { readFile as readFile11 } from "fs/promises";
6992
- import { join as join17 } from "path";
6993
- init_BlobStore();
6994
- init_BlobStore();
6995
- init_PointerStore();
6996
- init_esm_node();
6997
- var readFileTool = {
6998
- name: "read_file",
6999
- description: "Reads a file within the active workOrder scope. Enforces allowedFiles/allowedGlobs/deniedGlobs. Records content hash. Output is budgeted.",
7000
- inputSchema: {
7001
- type: "object",
7002
- properties: {
7003
- projectRoot: { type: "string", description: "Absolute project root" },
7004
- filePath: { type: "string", description: "Path relative to projectRoot" },
7005
- scanId: { type: "string", description: "Current scan ID for pointer registration" },
7006
- startLine: { type: "number", description: "Optional: 1-based start line to return a slice" },
7007
- endLine: { type: "number", description: "Optional: 1-based end line (inclusive). Capped at startLine+500." }
7008
- },
7009
- required: ["projectRoot", "filePath", "scanId"]
7010
- }
7011
- };
7012
- async function handleReadFile(args) {
7013
- const projectRoot = args.projectRoot;
7014
- const filePath = args.filePath;
7015
- const scanId = args.scanId;
7016
- const startLine = args.startLine !== void 0 ? Number(args.startLine) : void 0;
7017
- const endLine = args.endLine !== void 0 ? Math.min(Number(args.endLine), (startLine ?? 1) + 500) : void 0;
7018
- if (!projectRoot || !filePath || !scanId) {
7019
- throw new Error("projectRoot, filePath, and scanId are required");
7020
- }
7021
- try {
7022
- SessionScope.enforce(filePath);
7023
- } catch (e) {
7024
- if (e instanceof ScopeViolation)
7025
- throw e;
7026
- throw e;
7027
- }
7028
- const absolutePath = filePath.startsWith("/") ? filePath : join17(projectRoot, filePath);
7029
- let content;
7030
- try {
7031
- content = await readFile11(absolutePath, "utf8");
7032
- } catch {
7033
- throw new Error(`FileNotFound: cannot read ${filePath}`);
7034
- }
7035
- const contentHash = await hashFile(absolutePath);
7036
- let output = content;
7037
- let sliceInfo;
7038
- if (startLine !== void 0) {
7039
- const lines = content.split("\n");
7040
- const end = endLine ?? lines.length;
7041
- output = lines.slice(startLine - 1, end).join("\n");
7042
- sliceInfo = { startLine, endLine: end, totalLines: lines.length };
7043
- }
7044
- const blobStore = new BlobStore(projectRoot);
7045
- const pointerStore = PointerStore.open(projectRoot);
7046
- const { blobPath } = await blobStore.writeAtomic(content);
7047
- const pointerId = `ptr_file_${v4_default().replace(/-/g, "").slice(0, 12)}`;
7048
- await pointerStore.insertPointer({
7049
- pointerId,
7050
- scanId,
7051
- artifactName: "file_read",
7052
- contentHash,
7053
- blobPath,
7054
- schemaVersion: "1.0.0",
7055
- createdAt: Date.now(),
7056
- expiresAt: null
7057
- });
7058
- const result = {
7059
- filePath,
7060
- contentHash,
7061
- pointerId,
7062
- content: output
7063
- };
7064
- if (sliceInfo)
7065
- result.slice = sliceInfo;
7066
- return await applyBudgetGuard(projectRoot, scanId, "file_read", result);
7067
- }
7068
-
7069
- // dist/mcp/tools/hydration/get_start_here.js
7070
- var getStartHereTool = {
7071
- name: "get_start_here",
7072
- description: "Hydrates the start-here index for a scan manifest pointer. Returns the top 5 highest-gravity files. Pointer must be valid and unexpired.",
7073
- inputSchema: {
7074
- type: "object",
7075
- properties: {
7076
- projectRoot: { type: "string", description: "Absolute project root" },
7077
- manifestPointer: { type: "string", description: "Pointer ID for the scan manifest or analysis.index artifact" },
7078
- scanId: { type: "string", description: "Current scan ID for budget pointer registration" }
7079
- },
7080
- required: ["projectRoot", "manifestPointer", "scanId"]
7081
- }
7082
- };
7083
- async function handleGetStartHere(args) {
7084
- const projectRoot = args.projectRoot;
7085
- const manifestPointer = args.manifestPointer;
7086
- const scanId = args.scanId;
7087
- if (!projectRoot || !manifestPointer || !scanId) {
7088
- throw new Error("projectRoot, manifestPointer, and scanId are required");
7089
- }
7090
- const { content, row } = await hydratePointer(projectRoot, manifestPointer);
7091
- const payload = JSON.parse(content.toString("utf8"));
7092
- if (row.artifactName === "artifact_manifest") {
7093
- const manifest = payload;
7094
- const analysisEntry = manifest.artifacts.find((a) => a.name === "analysis" || a.name === "analysis.index");
7095
- if (!analysisEntry?.indexes?.startHere) {
7096
- throw new Error("Manifest has no analysis.index entry \u2014 rescan to regenerate");
7097
- }
7098
- const { content: indexContent } = await hydratePointer(projectRoot, analysisEntry.indexes.startHere);
7099
- const index = JSON.parse(indexContent.toString("utf8"));
7100
- const result = {
7101
- startHere: index.startHere.slice(0, 5),
7102
- schemaVersion: index.schemaVersion,
7103
- scanId: index.scanId
7104
- };
7105
- return await applyBudgetGuard(projectRoot, scanId, "get_start_here_result", result);
7106
- }
7107
- if (row.artifactName === "analysis.index") {
7108
- const result = {
7109
- startHere: payload.startHere.slice(0, 5),
7110
- schemaVersion: payload.schemaVersion,
7111
- scanId: payload.scanId
7112
- };
7113
- return await applyBudgetGuard(projectRoot, scanId, "get_start_here_result", result);
7114
- }
7115
- throw new Error(`Unsupported artifact type for get_start_here: ${row.artifactName}`);
7116
- }
7117
-
7118
- // dist/mcp/tools/hydration/get_project_summary.js
7119
- var getProjectSummaryTool = {
7120
- name: "get_project_summary",
7121
- description: "Returns high-level project metrics from a scan manifest pointer: file counts, pillar summary, stack. Token-safe. Pointer must be valid and unexpired.",
7122
- inputSchema: {
7123
- type: "object",
7124
- properties: {
7125
- projectRoot: { type: "string", description: "Absolute project root" },
7126
- manifestPointer: { type: "string", description: "Pointer ID for the scan manifest" },
7127
- scanId: { type: "string", description: "Current scan ID for budget pointer registration" }
7128
- },
7129
- required: ["projectRoot", "manifestPointer", "scanId"]
7130
- }
7131
- };
7132
- async function handleGetProjectSummary(args) {
7133
- const projectRoot = args.projectRoot;
7134
- const manifestPointer = args.manifestPointer;
7135
- const scanId = args.scanId;
7136
- if (!projectRoot || !manifestPointer || !scanId) {
7137
- throw new Error("projectRoot, manifestPointer, and scanId are required");
7138
- }
7139
- const { content, row } = await hydratePointer(projectRoot, manifestPointer);
7140
- const payload = JSON.parse(content.toString("utf8"));
7141
- if (row.artifactName === "artifact_manifest") {
7142
- const manifest = payload;
7143
- const analysisEntry = manifest.artifacts.find((a) => a.name === "analysis" && a.indexes?.startHere);
7144
- let indexData = {};
7145
- if (analysisEntry?.indexes?.startHere) {
7146
- const { content: ic } = await hydratePointer(projectRoot, analysisEntry.indexes.startHere);
7147
- indexData = JSON.parse(ic.toString("utf8"));
7148
- }
7149
- const result = {
7150
- scanId: manifest.scanId,
7151
- generatedAt: manifest.generatedAt,
7152
- artifactCount: manifest.artifacts.length,
7153
- totalArtifactBytes: manifest.artifacts.reduce((s, a) => s + (a.sizeBytes ?? 0), 0),
7154
- startHere: indexData.startHere,
7155
- topHeat: indexData.topHeat,
7156
- pillarSummary: indexData.pillarSummary,
7157
- totalFiles: indexData.totalFiles,
7158
- realSourceFiles: indexData.realSourceFiles
7159
- };
7160
- return await applyBudgetGuard(projectRoot, scanId, "get_project_summary_result", result);
7161
- }
7162
- if (row.artifactName === "analysis.index") {
7163
- const result = {
7164
- scanId: payload.scanId,
7165
- startHere: payload.startHere,
7166
- topHeat: payload.topHeat,
7167
- pillarSummary: payload.pillarSummary,
7168
- totalFiles: payload.totalFiles,
7169
- realSourceFiles: payload.realSourceFiles
7170
- };
7171
- return await applyBudgetGuard(projectRoot, scanId, "get_project_summary_result", result);
7172
- }
7173
- throw new Error(`Unsupported artifact type for get_project_summary: ${row.artifactName}`);
7174
- }
7175
-
7176
- // dist/mcp/tools/hydration/get_evidence_slice.js
7177
- var getEvidenceSliceTool = {
7178
- name: "get_evidence_slice",
7179
- description: "Raw fallback: returns a line-range slice from a blob artifact. Pointer must be valid and unexpired. Output is budgeted.",
7180
- inputSchema: {
7181
- type: "object",
7182
- properties: {
7183
- projectRoot: { type: "string", description: "Absolute project root" },
7184
- pointerId: { type: "string", description: "Pointer ID for the target artifact" },
7185
- startLine: { type: "number", description: "Inclusive start line (1-based)" },
7186
- endLine: { type: "number", description: "Inclusive end line (1-based). Capped at startLine+200." },
7187
- scanId: { type: "string", description: "Current scan ID for budget pointer registration" }
7188
- },
7189
- required: ["projectRoot", "pointerId", "startLine", "endLine", "scanId"]
7190
- }
7191
- };
7192
- async function handleGetEvidenceSlice(args) {
7193
- const projectRoot = args.projectRoot;
7194
- const pointerId = args.pointerId;
7195
- const startLine = Number(args.startLine);
7196
- const endLine = Math.min(Number(args.endLine), startLine + 200);
7197
- const scanId = args.scanId;
7198
- if (!projectRoot || !pointerId || !scanId) {
7199
- throw new Error("projectRoot, pointerId, startLine, endLine, and scanId are required");
7200
- }
7201
- const { content, row } = await hydratePointer(projectRoot, pointerId);
7202
- const rawText = content.toString("utf8");
7203
- const scope = SessionScope.get();
7204
- if (scope && (row.artifactName === "file_read" || row.artifactName === "file_skeleton")) {
7205
- try {
7206
- const parsed = JSON.parse(rawText);
7207
- if (parsed.filePath) {
7208
- SessionScope.enforce(parsed.filePath);
7209
- }
7210
- } catch (e) {
7211
- if (e.name === "ScopeViolation")
7212
- throw e;
7213
- }
7214
- }
7215
- const lines = rawText.split("\n");
7216
- const sliced = lines.slice(startLine - 1, endLine);
7217
- const result = {
7218
- pointerId,
7219
- artifactName: row.artifactName,
7220
- startLine,
7221
- endLine,
7222
- totalLines: lines.length,
7223
- slice: sliced
7224
- };
7225
- return await applyBudgetGuard(projectRoot, scanId, "evidence_slice", result);
7226
- }
7227
-
7228
- // dist/mcp/server.js
7229
- var ALL_TOOLS = [
7230
- scanProjectTool,
7231
- getProjectMapTool,
7232
- setProjectBriefTool,
7233
- getFileContextTool,
7234
- writeDecisionCardTool,
7235
- getStrategicOverviewTool,
7236
- inspectPillarTool,
7237
- getWildDiscoveriesTool,
7238
- markStaleTool,
7239
- // Phase 2: Skeletons + Hydration
7240
- getFileSkeletonTool,
7241
- readFileTool,
7242
- getStartHereTool,
7243
- getProjectSummaryTool,
7244
- getEvidenceSliceTool
7245
- ];
7246
- var TOOL_HANDLERS = {
7247
- scan_project: handleScanProject,
7248
- get_project_map: handleGetProjectMap,
7249
- set_project_brief: handleSetProjectBrief,
7250
- get_file_context: handleGetFileContext,
7251
- write_decision_card: handleWriteDecisionCard,
7252
- get_strategic_overview: handleGetStrategicOverview,
7253
- inspect_pillar: handleInspectPillar,
7254
- get_wild_discoveries: handleGetWildDiscoveries,
7255
- mark_stale: handleMarkStale,
7256
- // Phase 2
7257
- get_file_skeleton: handleGetFileSkeleton,
7258
- read_file: handleReadFile,
7259
- get_start_here: handleGetStartHere,
7260
- get_project_summary: handleGetProjectSummary,
7261
- get_evidence_slice: handleGetEvidenceSlice
7262
- };
7263
- async function startMCPServer(options = {}) {
7264
- await initParser2();
7265
- console.error("[vibe-splain] Tree-Sitter parser initialized");
7266
- const server = new Server({ name: "vibe-splain", version: "3.5.0" }, { capabilities: { tools: {}, prompts: {} } });
7267
- server.setRequestHandler(ListPromptsRequestSchema, async () => ({
7268
- prompts: [
7269
- {
7270
- name: "build_dossier",
7271
- description: "Build a full architectural dossier using vibe-splain (replaces the need to copy-paste the README prompt)"
7272
- }
7273
- ]
7274
- }));
7275
- server.setRequestHandler(GetPromptRequestSchema, async (request) => {
7276
- if (request.params.name !== "build_dossier") {
7277
- throw new Error(`Unknown prompt: ${request.params.name}`);
7278
- }
7279
- return {
7280
- description: "Build a full architectural dossier",
7281
- messages: [
7282
- {
7283
- role: "user",
7284
- content: {
7285
- type: "text",
7286
- text: `You are a skeptical staff engineer doing a HOSTILE architecture review of this codebase.
7287
- You are NOT writing documentation. You are finding the load-bearing walls, the landmines,
7288
- and the clever moves, and you are taking positions on them.
7289
-
7290
- PROCESS \u2014 follow in order:
7291
- 1. Call scan_project, then get_project_map. The map gives you: the detected stack,
7292
- the FIXED set of pillars (you may not invent others), the Start-Here files (highest
7293
- gravity = most depended-upon), and Wild-Discovery candidates (highest heat = most smell).
7294
- 2. Read the map's stack and entrypoints. Write a 3-5 sentence project brief: what IS this,
7295
- what's the real stack, and \u2014 critically \u2014 which files are the actual application vs.
7296
- mockups/generated/vendored noise. Pass it via set_project_brief. Do this BEFORE any card.
7297
- 3. Work the Start-Here files first (highest gravity), then the Wild-Discovery files.
7298
- For each, call get_file_context. It returns hotSpans (the gnarliest functions) and
7299
- smellSpans (located tech debt) \u2014 base your evidence on THOSE, never on header comments.
7300
- 4. Write one decision card per file via write_decision_card.
7301
-
7302
- This is an AUTONOMOUS loop. Every tool response includes a \`nextStep\` and often a
7303
- \`remainingFiles\` list \u2014 OBEY them. Do NOT stop, summarize, or ask the user "how would
7304
- you like to proceed" until every Start-Here and Wild-Discovery file has a card. Writing
7305
- the brief is the START of the work, not the end. Keep calling get_file_context +
7306
- write_decision_card until remainingFiles is empty.
7307
-
7308
- RULES FOR EVERY CARD \u2014 non-negotiable:
7309
- - The \`thesis\` is a VERDICT in one sentence. Take a position. If you can't, you don't
7310
- understand the file yet \u2014 read more.
7311
- - Pick a \`category\`: Bottleneck, Hack, Smart-Move, Risk, Convention, or Dead-Weight.
7312
- - \`blastRadius\` must reference the real fan-in (get_file_context.importedBy).
7313
- - NEVER paraphrase the file's own comments. If the insight is already in a // block,
7314
- it is not insight \u2014 go deeper into the logic.
7315
- - Evidence = 5-20 lines of the ACTUAL interesting code (hotSpans/smellSpans). Never the
7316
- whole file, never the doc-header.
7317
- - For every Wild-Discovery candidate, name the specific smell and rate its severity.
7318
-
7319
- \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
7320
- EXAMPLE \u2014 what GOOD vs BAD looks like:
7321
-
7322
- BAD (rejected \u2014 this is a book report):
7323
- title: "Panel Component Framework"
7324
- narrative: "This module establishes the structural framework for the panel-based
7325
- interface. It defines the generic Panel shell that standardizes look and feel..."
7326
- \u2192 Restates the header comment. No position. No risk. No tradeoff. Worthless.
7327
-
7328
- GOOD (accepted):
7329
- title: "Panel shell carries 14 props and 6 tools in one file"
7330
- thesis: "cipher-panels-a.jsx is a god-file: one 600-line module owns the shared shell
7331
- AND three unrelated generators, so any panel change risks all of them."
7332
- category: "Risk" severity: 4
7333
- narrative: "Panel was built as a single shell to guarantee visual consistency, but the
7334
- three generators (Palette/Vibe/Pocket) were folded in beside it instead of
7335
- split out. The shell threads 14 props through every tool, so the generators
7336
- are now coupled to the shell's drag/compact state they don't use."
7337
- tradeoff: "Bought consistency and one import site; paid with a module no one can change
7338
- safely and props that leak shell concerns into pure generators."
7339
- blastRadius: "Imported by cipher-shell.jsx (the app root) \u2014 a regression here is a
7340
- full-app regression."
7341
- evidence: [ the 14-param Panel signature; the prop-drill into PalettePanel ]
7342
- \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
7343
-
7344
- When done, share the exact file:// UI link returned by scan_project. Never invent a URL.`
4122
+ BAD (rejected \u2014 this is a book report):
4123
+ title: "Panel Component Framework"
4124
+ narrative: "This module establishes the structural framework for the panel-based
4125
+ interface. It defines the generic Panel shell that standardizes look and feel..."
4126
+ \u2192 Restates the header comment. No position. No risk. No tradeoff. Worthless.
4127
+
4128
+ GOOD (accepted):
4129
+ title: "Panel shell carries 14 props and 6 tools in one file"
4130
+ thesis: "cipher-panels-a.jsx is a god-file: one 600-line module owns the shared shell
4131
+ AND three unrelated generators, so any panel change risks all of them."
4132
+ category: "Risk" severity: 4
4133
+ narrative: "Panel was built as a single shell to guarantee visual consistency, but the
4134
+ three generators (Palette/Vibe/Pocket) were folded in beside it instead of
4135
+ split out. The shell threads 14 props through every tool, so the generators
4136
+ are now coupled to the shell's drag/compact state they don't use."
4137
+ tradeoff: "Bought consistency and one import site; paid with a module no one can change
4138
+ safely and props that leak shell concerns into pure generators."
4139
+ blastRadius: "Imported by cipher-shell.jsx (the app root) \u2014 a regression here is a
4140
+ full-app regression."
4141
+ evidence: [ the 14-param Panel signature; the prop-drill into PalettePanel ]
4142
+ \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
4143
+
4144
+ When done, share the exact file:// UI link returned by scan_project. Never invent a URL.`
7345
4145
  }
7346
4146
  }
7347
4147
  ]
@@ -7366,7 +4166,7 @@ When done, share the exact file:// UI link returned by scan_project. Never inven
7366
4166
  };
7367
4167
  } catch (err) {
7368
4168
  const message = err instanceof Error ? err.message : String(err);
7369
- console.error(`[vibe-splain] Tool ${name} error:`, message);
4169
+ console.error(`[vibesplain] Tool ${name} error:`, message);
7370
4170
  return {
7371
4171
  content: [{ type: "text", text: `Error: ${message}` }],
7372
4172
  isError: true
@@ -7375,162 +4175,18 @@ When done, share the exact file:// UI link returned by scan_project. Never inven
7375
4175
  });
7376
4176
  const transport = new StdioServerTransport();
7377
4177
  await server.connect(transport);
7378
- console.error("[vibe-splain] MCP server running on stdio");
4178
+ console.error("[vibesplain] MCP server running on stdio");
7379
4179
  }
7380
4180
 
7381
4181
  // dist/commands/serve.js
7382
4182
  async function serveCommand(options) {
7383
- console.error("[vibe-splain] Starting MCP server...");
4183
+ console.error("[vibesplain] Starting MCP server...");
7384
4184
  await startMCPServer(options);
7385
4185
  }
7386
4186
 
7387
- // dist/commands/hook.js
7388
- import { existsSync as existsSync6, writeFileSync, readFileSync as readFileSync2 } from "fs";
7389
- import { dirname as dirname5, join as join18 } from "path";
7390
- import { tmpdir } from "os";
7391
-
7392
- // dist/hook/preToolUse.js
7393
- init_escalation();
7394
- var DEFER = { action: "defer" };
7395
- var EDIT_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "MultiEdit"]);
7396
- function decidePreToolUse(input, gateIndex, sessionState) {
7397
- if (!EDIT_TOOLS.has(input.tool_name ?? ""))
7398
- return DEFER;
7399
- if (!gateIndex) {
7400
- if (sessionState && !sessionState.warningShown) {
7401
- const warnMsg = "vibe-splain: .vibe-splainer/gate.json is missing. Please run 'vibe-splain scan' to generate the scan architecture and enable tool guarding.";
7402
- return {
7403
- action: "emit",
7404
- output: {
7405
- hookSpecificOutput: {
7406
- hookEventName: "PreToolUse",
7407
- permissionDecision: "allow",
7408
- additionalContext: warnMsg,
7409
- systemMessage: warnMsg
7410
- }
7411
- }
7412
- };
7413
- }
7414
- return DEFER;
7415
- }
7416
- const filePath = input.tool_input?.file_path;
7417
- if (!filePath)
7418
- return DEFER;
7419
- const ctx = buildEscalationContext(filePath, gateIndex);
7420
- if (!ctx || ctx.blastRadius === "low")
7421
- return DEFER;
7422
- const block = formatEscalation(ctx);
7423
- if (ctx.blastRadius === "high") {
7424
- return {
7425
- action: "emit",
7426
- output: {
7427
- hookSpecificOutput: {
7428
- hookEventName: "PreToolUse",
7429
- permissionDecision: "ask",
7430
- permissionDecisionReason: block,
7431
- additionalContext: block
7432
- }
7433
- }
7434
- };
7435
- }
7436
- return {
7437
- action: "emit",
7438
- output: {
7439
- hookSpecificOutput: {
7440
- hookEventName: "PreToolUse",
7441
- permissionDecision: "allow",
7442
- additionalContext: block
7443
- }
7444
- }
7445
- };
7446
- }
7447
- function formatEscalation(ctx) {
7448
- const lines = [];
7449
- lines.push(`vibe-splain: ${ctx.blastRadius} blast radius \u2014 ${ctx.targetFile} (gravity ${ctx.gravity}).`);
7450
- if (ctx.dependentCount > 0) {
7451
- const shown = ctx.dependents.slice(0, 8);
7452
- const more = ctx.dependentCount - shown.length;
7453
- lines.push(`${ctx.dependentCount} file(s) depend on it:`);
7454
- for (const d of shown)
7455
- lines.push(` - ${d}`);
7456
- if (more > 0)
7457
- lines.push(` \u2026 (+${more} more)`);
7458
- }
7459
- for (const w of ctx.riskWarnings)
7460
- lines.push(`[${w.level}] ${w.message}`);
7461
- lines.push(ctx.smallestSafeChange.summary);
7462
- return lines.join("\n");
7463
- }
7464
-
7465
- // dist/commands/hook.js
7466
- function findProjectRoot(start) {
7467
- let dir = start || process.cwd();
7468
- for (let i = 0; i < 64; i++) {
7469
- if (existsSync6(join18(dir, ".vibe-splainer", "analysis.json")))
7470
- return dir;
7471
- const parent = dirname5(dir);
7472
- if (parent === dir)
7473
- break;
7474
- dir = parent;
7475
- }
7476
- return null;
7477
- }
7478
- async function runHookCommand(rawStdin) {
7479
- let input;
7480
- try {
7481
- input = JSON.parse(rawStdin);
7482
- } catch {
7483
- return { stdout: null };
7484
- }
7485
- const root = findProjectRoot(input.cwd);
7486
- const gatePath = root ? join18(root, ".vibe-splainer", "gate.json") : null;
7487
- const gateExists = gatePath ? existsSync6(gatePath) : false;
7488
- let gateIndex = null;
7489
- if (gateExists) {
7490
- try {
7491
- gateIndex = JSON.parse(readFileSync2(gatePath, "utf8"));
7492
- } catch {
7493
- }
7494
- }
7495
- const sessionId = input.session_id || "default";
7496
- const warnFile = join18(tmpdir(), `vibe-splain-warn-${sessionId}`);
7497
- const warningShown = existsSync6(warnFile);
7498
- const result = decidePreToolUse(input, gateIndex, { warningShown });
7499
- if (result.action === "defer")
7500
- return { stdout: null };
7501
- if (!gateIndex && result.action === "emit") {
7502
- try {
7503
- writeFileSync(warnFile, "1");
7504
- } catch {
7505
- }
7506
- }
7507
- return { stdout: JSON.stringify(result.output) };
7508
- }
7509
- async function hookPreToolUseCommand() {
7510
- const raw = await readStdin();
7511
- const { stdout } = await runHookCommand(raw);
7512
- if (stdout)
7513
- process.stdout.write(stdout);
7514
- }
7515
- function readStdin() {
7516
- return new Promise((resolve2) => {
7517
- let data = "";
7518
- if (process.stdin.isTTY) {
7519
- resolve2("");
7520
- return;
7521
- }
7522
- process.stdin.setEncoding("utf8");
7523
- process.stdin.on("data", (chunk) => {
7524
- data += chunk;
7525
- });
7526
- process.stdin.on("end", () => resolve2(data));
7527
- process.stdin.on("error", () => resolve2(data));
7528
- });
7529
- }
7530
-
7531
4187
  // dist/index.js
7532
4188
  var program = new Command();
7533
- program.name("vibe-splain").description("Architectural dossier engine for vibe-coded projects").version("3.5.0");
4189
+ program.name("vibesplain").description("Architectural dossier engine for vibe-coded projects").version("4.0.0");
7534
4190
  program.command("scan").description("Scan a project and write dossier artifacts").option("--root <root>", "Project root directory to scan", ".").option("--format <format>", "Export format (html, markdown, etc.)").option("--budget <budget>", "Token budget for markdown").option("--scope <scope>", "Scope for export").action(async (options) => {
7535
4191
  try {
7536
4192
  const rootPath = resolve(options.root);
@@ -7542,8 +4198,6 @@ program.command("scan").description("Scan a project and write dossier artifacts"
7542
4198
  process.exit(1);
7543
4199
  }
7544
4200
  });
7545
- program.command("install").description("Patch coding agent MCP config files to register vibe-splain").action(installCommand);
4201
+ program.command("install").description("Patch coding agent MCP config files to register vibesplain").action(installCommand);
7546
4202
  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));
7547
- var hookCmd = program.command("hook").description("Deterministic agent hooks (called by your coding agent, not by you)");
7548
- hookCmd.command("pretooluse").description("PreToolUse gate: escalate edits to high-blast-radius files").action(() => hookPreToolUseCommand());
7549
4203
  program.parse();