vibe-splain 2.2.0 → 2.3.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.
package/dist/index.js CHANGED
@@ -981,9 +981,9 @@ function detectCommunities(nodes, adjacency) {
981
981
  if (!neighbors || neighbors.size === 0)
982
982
  continue;
983
983
  const counts = /* @__PURE__ */ new Map();
984
- for (const nb of neighbors) {
984
+ for (const [nb, weight] of neighbors) {
985
985
  const l = label.get(nb);
986
- counts.set(l, (counts.get(l) || 0) + 1);
986
+ counts.set(l, (counts.get(l) || 0) + weight);
987
987
  }
988
988
  let best = label.get(node), bestCount = -1;
989
989
  for (const [l, c] of counts) {
@@ -1175,7 +1175,7 @@ async function scanProject(projectRoot) {
1175
1175
  const undirected = /* @__PURE__ */ new Map();
1176
1176
  for (const node of realNodes) {
1177
1177
  outEdges.set(node, /* @__PURE__ */ new Set());
1178
- undirected.set(node, /* @__PURE__ */ new Set());
1178
+ undirected.set(node, /* @__PURE__ */ new Map());
1179
1179
  }
1180
1180
  for (const w of work) {
1181
1181
  if (!realSet.has(w.rel))
@@ -1184,8 +1184,11 @@ async function scanProject(projectRoot) {
1184
1184
  if (!realSet.has(target))
1185
1185
  continue;
1186
1186
  outEdges.get(w.rel).add(target);
1187
- undirected.get(w.rel).add(target);
1188
- undirected.get(target).add(w.rel);
1187
+ const wDir = w.rel.split(sep)[0];
1188
+ const tDir = target.split(sep)[0];
1189
+ const weight = wDir === tDir ? 1 : 0.5;
1190
+ undirected.get(w.rel).set(target, weight);
1191
+ undirected.get(target).set(w.rel, weight);
1189
1192
  }
1190
1193
  }
1191
1194
  const ranks = pageRank(realNodes, outEdges);
@@ -1335,8 +1338,42 @@ function buildPillars(real, communities, _stack) {
1335
1338
  const gravB = real.filter((f) => b.memberFiles.includes(f.relativePath)).reduce((s, f) => s + f.gravity, 0);
1336
1339
  return gravB - gravA;
1337
1340
  });
1338
- const seen = /* @__PURE__ */ new Set();
1341
+ if (pillars.length === 0 && real.length > 0) {
1342
+ pillars.push({ name: "Core", description: "Primary application code.", memberFiles: real.slice(0, 20).map((f) => f.relativePath) });
1343
+ }
1344
+ const finalPillars = [];
1339
1345
  for (const p of pillars) {
1346
+ if (p.memberFiles.length > 15) {
1347
+ const groups = /* @__PURE__ */ new Map();
1348
+ for (const f of p.memberFiles) {
1349
+ let bucket = "Core";
1350
+ if (f.includes("app/") || f.includes("pages/") || f.includes("routes/"))
1351
+ bucket = "Routing";
1352
+ else if (f.includes("components/") || f.includes("ui/"))
1353
+ bucket = "Components";
1354
+ else if (f.includes("hooks/") || f.includes("lib/") || f.includes("utils/"))
1355
+ bucket = "Logic";
1356
+ const d = basename(dirname(f));
1357
+ const key = `${p.name} (${bucket} - ${d})`;
1358
+ if (!groups.has(key))
1359
+ groups.set(key, []);
1360
+ groups.get(key).push(f);
1361
+ }
1362
+ for (const [key, files] of groups) {
1363
+ if (files.length > 0) {
1364
+ finalPillars.push({
1365
+ name: key,
1366
+ description: `Subdivided from ${p.name}`,
1367
+ memberFiles: files
1368
+ });
1369
+ }
1370
+ }
1371
+ } else {
1372
+ finalPillars.push(p);
1373
+ }
1374
+ }
1375
+ const seen = /* @__PURE__ */ new Set();
1376
+ for (const p of finalPillars) {
1340
1377
  let n = p.name, i = 2;
1341
1378
  while (seen.has(n)) {
1342
1379
  n = `${p.name} ${i++}`;
@@ -1344,10 +1381,7 @@ function buildPillars(real, communities, _stack) {
1344
1381
  p.name = n;
1345
1382
  seen.add(n);
1346
1383
  }
1347
- if (pillars.length === 0 && real.length > 0) {
1348
- pillars.push({ name: "Core", description: "Primary application code.", memberFiles: real.slice(0, 20).map((f) => f.relativePath) });
1349
- }
1350
- return pillars;
1384
+ return finalPillars;
1351
1385
  }
1352
1386
  function pillarNameFromCluster(files) {
1353
1387
  const hintCounts = /* @__PURE__ */ new Map();
@@ -1444,11 +1478,30 @@ async function readDossier(projectRoot) {
1444
1478
  }
1445
1479
  async function writeDossier(projectRoot, dossier) {
1446
1480
  await dossierMutex.runExclusive(async () => {
1481
+ for (const p of dossier.pillars) {
1482
+ p.decisions = p.decisions.filter((c) => !(c.severity === 1 && c.category === "Convention"));
1483
+ p.cardCount = p.decisions.length;
1484
+ }
1485
+ const uniqueCards = /* @__PURE__ */ new Map();
1486
+ for (const p of dossier.pillars) {
1487
+ for (const c of p.decisions)
1488
+ uniqueCards.set(c.id, c);
1489
+ }
1490
+ for (const c of dossier.wildDiscoveries)
1491
+ uniqueCards.set(c.id, c);
1492
+ const deltaTargets = Array.from(uniqueCards.values()).filter((c) => c.severity >= 4).map((c) => ({
1493
+ target_path: c.primaryFile,
1494
+ bottleneck_title: c.title,
1495
+ structural_intent: c.thesis,
1496
+ evidence_snippets: c.evidence
1497
+ }));
1447
1498
  const dir = join5(projectRoot, ".vibe-splainer");
1448
1499
  await mkdir3(dir, { recursive: true });
1449
1500
  const dossierPath = join5(dir, "dossier.json");
1450
1501
  const tmp = dossierPath + ".tmp";
1451
1502
  await writeFile4(tmp, JSON.stringify(dossier, null, 2), "utf8");
1503
+ const targetsPath = join5(dir, "delta_targets.json");
1504
+ await writeFile4(targetsPath, JSON.stringify(deltaTargets, null, 2), "utf8");
1452
1505
  const { rename } = await import("fs/promises");
1453
1506
  await rename(tmp, dossierPath);
1454
1507
  await regenerateUI(projectRoot, dossier);
@@ -1742,7 +1795,7 @@ var writeDecisionCardTool = {
1742
1795
  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." },
1743
1796
  tradeoff: { type: "string", description: "What was given up, or why the obvious approach was rejected. Null only if genuinely none." },
1744
1797
  blastRadius: { type: "string", description: "What breaks if this changes. Ground it in the fan-in (importedBy) from get_file_context." },
1745
- confidence: { type: "string", enum: ["low", "medium", "high"] },
1798
+ 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".' },
1746
1799
  evidence: {
1747
1800
  type: "array",
1748
1801
  items: {
@@ -47,6 +47,7 @@ export declare const writeDecisionCardTool: {
47
47
  confidence: {
48
48
  type: string;
49
49
  enum: string[];
50
+ description: string;
50
51
  };
51
52
  evidence: {
52
53
  type: string;
@@ -29,7 +29,7 @@ export const writeDecisionCardTool = {
29
29
  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." },
30
30
  tradeoff: { type: 'string', description: 'What was given up, or why the obvious approach was rejected. Null only if genuinely none.' },
31
31
  blastRadius: { type: 'string', description: 'What breaks if this changes. Ground it in the fan-in (importedBy) from get_file_context.' },
32
- confidence: { type: 'string', enum: ['low', 'medium', 'high'] },
32
+ 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".' },
33
33
  evidence: {
34
34
  type: 'array',
35
35
  items: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibe-splain",
3
- "version": "2.2.0",
3
+ "version": "2.3.0",
4
4
  "description": "Architectural dossier engine for vibe-coded projects. Runs as an MCP server inside your coding agent.",
5
5
  "type": "module",
6
6
  "license": "MIT",