vibe-splain 2.3.0 → 2.3.1
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
|
@@ -119,7 +119,29 @@ async function readAnalysis(projectRoot) {
|
|
|
119
119
|
async function writeAnalysis(projectRoot, store) {
|
|
120
120
|
const dir = join3(projectRoot, ".vibe-splainer");
|
|
121
121
|
await mkdir2(dir, { recursive: true });
|
|
122
|
-
|
|
122
|
+
const dest = join3(dir, "analysis.json");
|
|
123
|
+
const tmp = dest + ".tmp";
|
|
124
|
+
await writeFile3(tmp, JSON.stringify(store, null, 2), "utf8");
|
|
125
|
+
const { rename } = await import("fs/promises");
|
|
126
|
+
await rename(tmp, dest);
|
|
127
|
+
}
|
|
128
|
+
var LOAD_BEARING_FAN_IN_THRESHOLD = 10;
|
|
129
|
+
async function writeDeltaTargets(projectRoot, store) {
|
|
130
|
+
const targets = Object.values(store.files).filter((f) => f.isRealSource).sort((a, b) => b.gravity - a.gravity).map((f) => ({
|
|
131
|
+
path: f.relativePath,
|
|
132
|
+
gravity: f.gravity,
|
|
133
|
+
isLoadBearing: f.gravitySignals.fanIn >= LOAD_BEARING_FAN_IN_THRESHOLD,
|
|
134
|
+
blastRadius: f.importedBy,
|
|
135
|
+
// community-{N} labels are internal graph IDs — not useful to Delta Engine
|
|
136
|
+
pillarHint: f.pillarHint && !f.pillarHint.startsWith("community-") ? f.pillarHint : null
|
|
137
|
+
}));
|
|
138
|
+
const dir = join3(projectRoot, ".vibe-splainer");
|
|
139
|
+
await mkdir2(dir, { recursive: true });
|
|
140
|
+
const dest = join3(dir, "delta_targets.json");
|
|
141
|
+
const tmp = dest + ".tmp";
|
|
142
|
+
await writeFile3(tmp, JSON.stringify(targets, null, 2), "utf8");
|
|
143
|
+
const { rename } = await import("fs/promises");
|
|
144
|
+
await rename(tmp, dest);
|
|
123
145
|
}
|
|
124
146
|
|
|
125
147
|
// ../brain/dist/scanner.js
|
|
@@ -1271,7 +1293,9 @@ async function scanProject(projectRoot) {
|
|
|
1271
1293
|
brief: null
|
|
1272
1294
|
};
|
|
1273
1295
|
await writeGraph(projectRoot, graph);
|
|
1274
|
-
|
|
1296
|
+
const analysisStore = { files: persisted };
|
|
1297
|
+
await writeAnalysis(projectRoot, analysisStore);
|
|
1298
|
+
await writeDeltaTargets(projectRoot, analysisStore);
|
|
1275
1299
|
const uiUrl = `file://${join4(projectRoot, ".vibe-splainer", "ui", "index.html")}`;
|
|
1276
1300
|
return {
|
|
1277
1301
|
projectRoot,
|
|
@@ -1482,26 +1506,11 @@ async function writeDossier(projectRoot, dossier) {
|
|
|
1482
1506
|
p.decisions = p.decisions.filter((c) => !(c.severity === 1 && c.category === "Convention"));
|
|
1483
1507
|
p.cardCount = p.decisions.length;
|
|
1484
1508
|
}
|
|
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
|
-
}));
|
|
1498
1509
|
const dir = join5(projectRoot, ".vibe-splainer");
|
|
1499
1510
|
await mkdir3(dir, { recursive: true });
|
|
1500
1511
|
const dossierPath = join5(dir, "dossier.json");
|
|
1501
1512
|
const tmp = dossierPath + ".tmp";
|
|
1502
1513
|
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");
|
|
1505
1514
|
const { rename } = await import("fs/promises");
|
|
1506
1515
|
await rename(tmp, dossierPath);
|
|
1507
1516
|
await regenerateUI(projectRoot, dossier);
|
|
@@ -1544,6 +1553,7 @@ function validateMermaidNodeCount(diagram) {
|
|
|
1544
1553
|
import chokidar from "chokidar";
|
|
1545
1554
|
import { createHash } from "crypto";
|
|
1546
1555
|
import { readFile as readFile6 } from "fs/promises";
|
|
1556
|
+
import { join as join6 } from "path";
|
|
1547
1557
|
function startWatcher(projectRoot, watchedPaths) {
|
|
1548
1558
|
const watcher = chokidar.watch(watchedPaths.length > 0 ? watchedPaths : projectRoot, {
|
|
1549
1559
|
ignoreInitial: true,
|
|
@@ -1560,13 +1570,15 @@ function startWatcher(projectRoot, watchedPaths) {
|
|
|
1560
1570
|
let mutated = false;
|
|
1561
1571
|
for (const pillar of dossier.pillars) {
|
|
1562
1572
|
for (const card of pillar.decisions) {
|
|
1563
|
-
if (card.
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1573
|
+
if (!card.primaryFile)
|
|
1574
|
+
continue;
|
|
1575
|
+
const absMatch = filepath === join6(projectRoot, card.primaryFile) || filepath.endsWith("/" + card.primaryFile);
|
|
1576
|
+
if (absMatch && card.lastScannedHash !== newHash) {
|
|
1577
|
+
card.status = "stale";
|
|
1578
|
+
const rel = card.primaryFile;
|
|
1579
|
+
if (!dossier.stalePaths.includes(rel))
|
|
1580
|
+
dossier.stalePaths.push(rel);
|
|
1581
|
+
mutated = true;
|
|
1570
1582
|
}
|
|
1571
1583
|
}
|
|
1572
1584
|
}
|
|
@@ -1716,7 +1728,7 @@ async function handleSetProjectBrief(args) {
|
|
|
1716
1728
|
|
|
1717
1729
|
// dist/mcp/tools/get_file_context.js
|
|
1718
1730
|
import { readFile as readFile7 } from "fs/promises";
|
|
1719
|
-
import { join as
|
|
1731
|
+
import { join as join7, relative as relative2, isAbsolute } from "path";
|
|
1720
1732
|
var getFileContextTool = {
|
|
1721
1733
|
name: "get_file_context",
|
|
1722
1734
|
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.",
|
|
@@ -1736,7 +1748,7 @@ async function handleGetFileContext(args) {
|
|
|
1736
1748
|
const full = args.full === true;
|
|
1737
1749
|
if (!projectRoot || !filePath)
|
|
1738
1750
|
throw new Error("projectRoot and filePath are required");
|
|
1739
|
-
const fullPath = isAbsolute(filePath) ? filePath :
|
|
1751
|
+
const fullPath = isAbsolute(filePath) ? filePath : join7(projectRoot, filePath);
|
|
1740
1752
|
const relPath = relative2(projectRoot, fullPath);
|
|
1741
1753
|
const evidence = await getFileAnalysis(fullPath);
|
|
1742
1754
|
if (!evidence) {
|
|
@@ -1770,7 +1782,7 @@ async function handleGetFileContext(args) {
|
|
|
1770
1782
|
import { v4 as uuidv4 } from "uuid";
|
|
1771
1783
|
import { createHash as createHash2 } from "crypto";
|
|
1772
1784
|
import { readFile as readFile8 } from "fs/promises";
|
|
1773
|
-
import { join as
|
|
1785
|
+
import { join as join8 } from "path";
|
|
1774
1786
|
var CATEGORIES = ["Bottleneck", "Hack", "Smart-Move", "Risk", "Convention", "Dead-Weight"];
|
|
1775
1787
|
function normalizeSnippet(s) {
|
|
1776
1788
|
let out = (s ?? "").replace(/\r\n/g, "\n");
|
|
@@ -1860,15 +1872,12 @@ async function handleWriteDecisionCard(args) {
|
|
|
1860
1872
|
const persisted = store?.files[primaryFile];
|
|
1861
1873
|
const gravity = persisted ? Math.round(persisted.gravity) : void 0;
|
|
1862
1874
|
const heat = persisted ? Math.round(persisted.heat) : void 0;
|
|
1863
|
-
let
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
} catch {
|
|
1868
|
-
combinedContent += e.snippet;
|
|
1869
|
-
}
|
|
1875
|
+
let primaryContent = "";
|
|
1876
|
+
try {
|
|
1877
|
+
primaryContent = await readFile8(join8(projectRoot, primaryFile), "utf8");
|
|
1878
|
+
} catch {
|
|
1870
1879
|
}
|
|
1871
|
-
const hash = createHash2("sha256").update(
|
|
1880
|
+
const hash = createHash2("sha256").update(primaryContent).digest("hex");
|
|
1872
1881
|
const card = {
|
|
1873
1882
|
id: uuidv4(),
|
|
1874
1883
|
pillar,
|
|
@@ -2003,7 +2012,7 @@ async function handleInspectPillar(args) {
|
|
|
2003
2012
|
// dist/mcp/tools/get_wild_discoveries.js
|
|
2004
2013
|
var getWildDiscoveriesTool = {
|
|
2005
2014
|
name: "get_wild_discoveries",
|
|
2006
|
-
description: "Returns
|
|
2015
|
+
description: "Returns Decision Cards that are both high-heat (heat \u2265 60) AND/OR high-severity (severity \u2265 4) \u2014 the files that are load-bearing AND smelly. These are the highest-leverage things to understand and fix first.",
|
|
2007
2016
|
inputSchema: {
|
|
2008
2017
|
type: "object",
|
|
2009
2018
|
properties: {
|
|
@@ -2234,7 +2243,7 @@ async function serveCommand() {
|
|
|
2234
2243
|
|
|
2235
2244
|
// dist/index.js
|
|
2236
2245
|
var program = new Command();
|
|
2237
|
-
program.name("vibe-splain").description("Architectural dossier engine for vibe-coded projects").version("
|
|
2246
|
+
program.name("vibe-splain").description("Architectural dossier engine for vibe-coded projects").version("2.3.1");
|
|
2238
2247
|
program.command("install").description("Patch coding agent MCP config files to register vibe-splain").action(installCommand);
|
|
2239
2248
|
program.command("serve").description("Start the MCP server (called by the coding agent, not by you)").action(serveCommand);
|
|
2240
2249
|
program.parse();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readDossier } from '@vibe-splain/brain';
|
|
2
2
|
export const getWildDiscoveriesTool = {
|
|
3
3
|
name: 'get_wild_discoveries',
|
|
4
|
-
description: 'Returns
|
|
4
|
+
description: 'Returns Decision Cards that are both high-heat (heat ≥ 60) AND/OR high-severity (severity ≥ 4) — the files that are load-bearing AND smelly. These are the highest-leverage things to understand and fix first.',
|
|
5
5
|
inputSchema: {
|
|
6
6
|
type: 'object',
|
|
7
7
|
properties: {
|
|
@@ -99,17 +99,13 @@ export async function handleWriteDecisionCard(args) {
|
|
|
99
99
|
const persisted = store?.files[primaryFile];
|
|
100
100
|
const gravity = persisted ? Math.round(persisted.gravity) : undefined;
|
|
101
101
|
const heat = persisted ? Math.round(persisted.heat) : undefined;
|
|
102
|
-
// Hash
|
|
103
|
-
let
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
combinedContent += await readFile(join(projectRoot, e.file), 'utf8');
|
|
107
|
-
}
|
|
108
|
-
catch {
|
|
109
|
-
combinedContent += e.snippet;
|
|
110
|
-
}
|
|
102
|
+
// Hash the primaryFile so the watcher can detect staleness per-file.
|
|
103
|
+
let primaryContent = '';
|
|
104
|
+
try {
|
|
105
|
+
primaryContent = await readFile(join(projectRoot, primaryFile), 'utf8');
|
|
111
106
|
}
|
|
112
|
-
|
|
107
|
+
catch { /* */ }
|
|
108
|
+
const hash = createHash('sha256').update(primaryContent).digest('hex');
|
|
113
109
|
const card = {
|
|
114
110
|
id: uuidv4(),
|
|
115
111
|
pillar, title, thesis, category, severity, narrative,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vibe-splain",
|
|
3
|
-
"version": "2.3.
|
|
4
|
-
"description": "Architectural dossier engine for vibe-coded projects. Runs as an MCP server inside your coding agent.",
|
|
3
|
+
"version": "2.3.1",
|
|
4
|
+
"description": "Architectural dossier engine for vibe-coded TypeScript/JavaScript projects. Runs as an MCP server inside your coding agent.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "abp2204",
|