vibe-splain 2.1.1 → 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
|
@@ -244,6 +244,163 @@ var VENDOR_SEGMENTS = /* @__PURE__ */ new Set([
|
|
|
244
244
|
"third_party",
|
|
245
245
|
"third-party"
|
|
246
246
|
]);
|
|
247
|
+
var PILLAR_KEYWORDS = {
|
|
248
|
+
"Auth": [
|
|
249
|
+
"passport",
|
|
250
|
+
"jsonwebtoken",
|
|
251
|
+
"bcrypt",
|
|
252
|
+
"bcryptjs",
|
|
253
|
+
"oauth",
|
|
254
|
+
"session",
|
|
255
|
+
"cookie-parser",
|
|
256
|
+
"next-auth",
|
|
257
|
+
"@auth/",
|
|
258
|
+
"lucia",
|
|
259
|
+
"clerk",
|
|
260
|
+
"@clerk/",
|
|
261
|
+
"supabase/auth",
|
|
262
|
+
"@supabase/auth-helpers",
|
|
263
|
+
"iron-session",
|
|
264
|
+
"jose",
|
|
265
|
+
"jwt",
|
|
266
|
+
"@auth/core",
|
|
267
|
+
"arctic"
|
|
268
|
+
],
|
|
269
|
+
"Database": [
|
|
270
|
+
"prisma",
|
|
271
|
+
"@prisma/",
|
|
272
|
+
"mongoose",
|
|
273
|
+
"sequelize",
|
|
274
|
+
"typeorm",
|
|
275
|
+
"knex",
|
|
276
|
+
"pg",
|
|
277
|
+
"mysql",
|
|
278
|
+
"mysql2",
|
|
279
|
+
"better-sqlite3",
|
|
280
|
+
"drizzle-orm",
|
|
281
|
+
"drizzle",
|
|
282
|
+
"kysely",
|
|
283
|
+
"@supabase/supabase-js",
|
|
284
|
+
"mongodb",
|
|
285
|
+
"redis",
|
|
286
|
+
"ioredis"
|
|
287
|
+
],
|
|
288
|
+
"Payments": [
|
|
289
|
+
"stripe",
|
|
290
|
+
"@stripe/",
|
|
291
|
+
"paypal",
|
|
292
|
+
"braintree",
|
|
293
|
+
"plaid",
|
|
294
|
+
"lemonsqueezy",
|
|
295
|
+
"@lemonsqueezy/",
|
|
296
|
+
"paddle",
|
|
297
|
+
"lemon-squeezy"
|
|
298
|
+
],
|
|
299
|
+
"Routing": [
|
|
300
|
+
"express",
|
|
301
|
+
"fastify",
|
|
302
|
+
"koa",
|
|
303
|
+
"koa-router",
|
|
304
|
+
"next/router",
|
|
305
|
+
"next/navigation",
|
|
306
|
+
"react-router",
|
|
307
|
+
"@remix-run/",
|
|
308
|
+
"hono",
|
|
309
|
+
"express-rate-limit",
|
|
310
|
+
"cors",
|
|
311
|
+
"helmet"
|
|
312
|
+
],
|
|
313
|
+
"Queue": [
|
|
314
|
+
"bull",
|
|
315
|
+
"bullmq",
|
|
316
|
+
"amqplib",
|
|
317
|
+
"kafkajs",
|
|
318
|
+
"kafka",
|
|
319
|
+
"upstash",
|
|
320
|
+
"@upstash/",
|
|
321
|
+
"bee-queue",
|
|
322
|
+
"agenda"
|
|
323
|
+
],
|
|
324
|
+
"Storage": [
|
|
325
|
+
"aws-sdk",
|
|
326
|
+
"@aws-sdk/",
|
|
327
|
+
"multer",
|
|
328
|
+
"cloudinary",
|
|
329
|
+
"@google-cloud/storage",
|
|
330
|
+
"minio",
|
|
331
|
+
"@vercel/blob",
|
|
332
|
+
"sharp",
|
|
333
|
+
"imagekit"
|
|
334
|
+
],
|
|
335
|
+
"Config": [
|
|
336
|
+
"dotenv",
|
|
337
|
+
"convict",
|
|
338
|
+
"env-var",
|
|
339
|
+
"@t3-oss/env",
|
|
340
|
+
"envalid"
|
|
341
|
+
],
|
|
342
|
+
"Email": [
|
|
343
|
+
"nodemailer",
|
|
344
|
+
"resend",
|
|
345
|
+
"@sendgrid/",
|
|
346
|
+
"postmark",
|
|
347
|
+
"@resend/",
|
|
348
|
+
"mailgun"
|
|
349
|
+
],
|
|
350
|
+
"Realtime": [
|
|
351
|
+
"socket.io",
|
|
352
|
+
"ws",
|
|
353
|
+
"pusher",
|
|
354
|
+
"ably",
|
|
355
|
+
"@supabase/realtime",
|
|
356
|
+
"socket.io-client"
|
|
357
|
+
]
|
|
358
|
+
};
|
|
359
|
+
var PILLAR_PATH_PATTERNS = {
|
|
360
|
+
"Auth": /(?:^|[\/\\])(?:auth|login|signup|register|session|oauth)(?:[\/\\]|$)/i,
|
|
361
|
+
"Database": /(?:^|[\/\\])(?:db|database|models?|schema|migrations?|seeds?)(?:[\/\\]|$)/i,
|
|
362
|
+
"Payments": /(?:^|[\/\\])(?:pay|payments?|billing|checkout|subscriptions?|stripe)(?:[\/\\]|$)/i,
|
|
363
|
+
"Routing": /(?:^|[\/\\])(?:routes?|router|middleware|api)(?:[\/\\]|$)/i,
|
|
364
|
+
"Queue": /(?:^|[\/\\])(?:queues?|workers?|jobs?|consumers?|producers?)(?:[\/\\]|$)/i,
|
|
365
|
+
"Storage": /(?:^|[\/\\])(?:storage|uploads?|s3|blobs?|media)(?:[\/\\]|$)/i,
|
|
366
|
+
"Config": /(?:^|[\/\\])(?:config|env|settings?)(?:[\/\\]|$)/i,
|
|
367
|
+
"Email": /(?:^|[\/\\])(?:emails?|mail|notifications?)(?:[\/\\]|$)/i
|
|
368
|
+
};
|
|
369
|
+
var MEANINGLESS_SEGMENTS = /* @__PURE__ */ new Set([
|
|
370
|
+
"src",
|
|
371
|
+
"lib",
|
|
372
|
+
"app",
|
|
373
|
+
"pages",
|
|
374
|
+
"components",
|
|
375
|
+
"modules",
|
|
376
|
+
"features",
|
|
377
|
+
"core",
|
|
378
|
+
"common",
|
|
379
|
+
"shared",
|
|
380
|
+
"internal",
|
|
381
|
+
"pkg",
|
|
382
|
+
"packages"
|
|
383
|
+
]);
|
|
384
|
+
function matchPillarByImports(importSpecs) {
|
|
385
|
+
const scores = /* @__PURE__ */ new Map();
|
|
386
|
+
for (const spec of importSpecs) {
|
|
387
|
+
for (const [pillar, keywords] of Object.entries(PILLAR_KEYWORDS)) {
|
|
388
|
+
if (keywords.some((kw) => spec === kw || spec.startsWith(kw + "/"))) {
|
|
389
|
+
scores.set(pillar, (scores.get(pillar) || 0) + 1);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
if (scores.size === 0)
|
|
394
|
+
return null;
|
|
395
|
+
return [...scores.entries()].sort((a, b) => b[1] - a[1])[0][0];
|
|
396
|
+
}
|
|
397
|
+
function matchPillarByPath(relPath) {
|
|
398
|
+
for (const [pillar, pattern] of Object.entries(PILLAR_PATH_PATTERNS)) {
|
|
399
|
+
if (pattern.test(relPath))
|
|
400
|
+
return pillar;
|
|
401
|
+
}
|
|
402
|
+
return null;
|
|
403
|
+
}
|
|
247
404
|
async function collectFiles(dir, projectRoot, acc) {
|
|
248
405
|
let entries;
|
|
249
406
|
try {
|
|
@@ -824,9 +981,9 @@ function detectCommunities(nodes, adjacency) {
|
|
|
824
981
|
if (!neighbors || neighbors.size === 0)
|
|
825
982
|
continue;
|
|
826
983
|
const counts = /* @__PURE__ */ new Map();
|
|
827
|
-
for (const nb of neighbors) {
|
|
984
|
+
for (const [nb, weight] of neighbors) {
|
|
828
985
|
const l = label.get(nb);
|
|
829
|
-
counts.set(l, (counts.get(l) || 0) +
|
|
986
|
+
counts.set(l, (counts.get(l) || 0) + weight);
|
|
830
987
|
}
|
|
831
988
|
let best = label.get(node), bestCount = -1;
|
|
832
989
|
for (const [l, c] of counts) {
|
|
@@ -1018,7 +1175,7 @@ async function scanProject(projectRoot) {
|
|
|
1018
1175
|
const undirected = /* @__PURE__ */ new Map();
|
|
1019
1176
|
for (const node of realNodes) {
|
|
1020
1177
|
outEdges.set(node, /* @__PURE__ */ new Set());
|
|
1021
|
-
undirected.set(node, /* @__PURE__ */ new
|
|
1178
|
+
undirected.set(node, /* @__PURE__ */ new Map());
|
|
1022
1179
|
}
|
|
1023
1180
|
for (const w of work) {
|
|
1024
1181
|
if (!realSet.has(w.rel))
|
|
@@ -1027,8 +1184,11 @@ async function scanProject(projectRoot) {
|
|
|
1027
1184
|
if (!realSet.has(target))
|
|
1028
1185
|
continue;
|
|
1029
1186
|
outEdges.get(w.rel).add(target);
|
|
1030
|
-
|
|
1031
|
-
|
|
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);
|
|
1032
1192
|
}
|
|
1033
1193
|
}
|
|
1034
1194
|
const ranks = pageRank(realNodes, outEdges);
|
|
@@ -1047,7 +1207,10 @@ async function scanProject(projectRoot) {
|
|
|
1047
1207
|
publicSurface: w.ast.publicSurface,
|
|
1048
1208
|
loc: w.ast.loc
|
|
1049
1209
|
};
|
|
1050
|
-
|
|
1210
|
+
const depthRatio = (w.ast.cyclomatic + w.ast.maxNesting * 2) / Math.max(1, w.ast.publicSurface);
|
|
1211
|
+
const depthFactor = Math.min(1, Math.log2(depthRatio + 1) / 3);
|
|
1212
|
+
const adjustedCentrality = centrality * (0.3 + 0.7 * depthFactor);
|
|
1213
|
+
let gravityRaw = adjustedCentrality * 50 + Math.log2(fanIn + 1) * 6 + Math.log2(w.ast.cyclomatic + 1) * 7 + Math.log2(w.ast.publicSurface + 1) * 2 + (w.ast.maxNesting >= 4 ? 5 : 0);
|
|
1051
1214
|
if (!real)
|
|
1052
1215
|
gravityRaw *= 0.2;
|
|
1053
1216
|
const gravity = Math.max(0, Math.min(100, gravityRaw));
|
|
@@ -1060,7 +1223,9 @@ async function scanProject(projectRoot) {
|
|
|
1060
1223
|
magicNumbers: w.ast.magicNumbers
|
|
1061
1224
|
};
|
|
1062
1225
|
const heat = real ? computeHeat(w.ast.smells) : 0;
|
|
1063
|
-
const
|
|
1226
|
+
const keywordPillar = matchPillarByImports(w.importSpecs);
|
|
1227
|
+
const pathPillar = matchPillarByPath(w.rel);
|
|
1228
|
+
const pillarHint = real ? keywordPillar || pathPillar || `community-${communities.get(w.rel)}` : null;
|
|
1064
1229
|
const fa = {
|
|
1065
1230
|
path: w.abs,
|
|
1066
1231
|
relativePath: w.rel,
|
|
@@ -1119,28 +1284,96 @@ async function scanProject(projectRoot) {
|
|
|
1119
1284
|
graph
|
|
1120
1285
|
};
|
|
1121
1286
|
}
|
|
1122
|
-
function buildPillars(real, communities,
|
|
1123
|
-
const
|
|
1287
|
+
function buildPillars(real, communities, _stack) {
|
|
1288
|
+
const keywordGroups = /* @__PURE__ */ new Map();
|
|
1289
|
+
const unlabeled = [];
|
|
1124
1290
|
for (const a of real) {
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
const pillars =
|
|
1134
|
-
|
|
1135
|
-
const
|
|
1136
|
-
|
|
1291
|
+
if (a.pillarHint && !a.pillarHint.startsWith("community-")) {
|
|
1292
|
+
if (!keywordGroups.has(a.pillarHint))
|
|
1293
|
+
keywordGroups.set(a.pillarHint, []);
|
|
1294
|
+
keywordGroups.get(a.pillarHint).push(a);
|
|
1295
|
+
} else {
|
|
1296
|
+
unlabeled.push(a);
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
const pillars = [];
|
|
1300
|
+
for (const [name, files] of keywordGroups) {
|
|
1301
|
+
const sorted = [...files].sort((a, b) => b.gravity - a.gravity);
|
|
1302
|
+
pillars.push({
|
|
1137
1303
|
name,
|
|
1138
|
-
description:
|
|
1139
|
-
memberFiles:
|
|
1140
|
-
};
|
|
1304
|
+
description: `${name} subsystem: ${files.length} file${files.length > 1 ? "s" : ""} centered on ${basename(sorted[0].relativePath)}.`,
|
|
1305
|
+
memberFiles: sorted.map((f) => f.relativePath)
|
|
1306
|
+
});
|
|
1307
|
+
}
|
|
1308
|
+
if (unlabeled.length > 0) {
|
|
1309
|
+
const communityGroups = /* @__PURE__ */ new Map();
|
|
1310
|
+
for (const a of unlabeled) {
|
|
1311
|
+
const c = communities.get(a.relativePath);
|
|
1312
|
+
if (c === void 0)
|
|
1313
|
+
continue;
|
|
1314
|
+
if (!communityGroups.has(c))
|
|
1315
|
+
communityGroups.set(c, []);
|
|
1316
|
+
communityGroups.get(c).push(a);
|
|
1317
|
+
}
|
|
1318
|
+
const remainingSlots = Math.max(0, 6 - pillars.length);
|
|
1319
|
+
const sorted = [...communityGroups.entries()].map(([id, files]) => ({ id, files, weight: files.reduce((s, f) => s + f.gravity, 0) })).filter((g) => g.files.length >= 2).sort((a, b) => b.weight - a.weight).slice(0, remainingSlots);
|
|
1320
|
+
for (const g of sorted) {
|
|
1321
|
+
const top = [...g.files].sort((a, b) => b.gravity - a.gravity);
|
|
1322
|
+
const name = pillarNameFromCluster(top);
|
|
1323
|
+
const existing = pillars.find((p) => p.name === name);
|
|
1324
|
+
if (existing) {
|
|
1325
|
+
existing.memberFiles.push(...top.map((f) => f.relativePath));
|
|
1326
|
+
existing.description = `${name} subsystem: ${existing.memberFiles.length} files centered on ${basename(existing.memberFiles[0])}.`;
|
|
1327
|
+
} else {
|
|
1328
|
+
pillars.push({
|
|
1329
|
+
name,
|
|
1330
|
+
description: `${g.files.length} files centered on ${basename(top[0].relativePath)}.`,
|
|
1331
|
+
memberFiles: top.map((f) => f.relativePath)
|
|
1332
|
+
});
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
pillars.sort((a, b) => {
|
|
1337
|
+
const gravA = real.filter((f) => a.memberFiles.includes(f.relativePath)).reduce((s, f) => s + f.gravity, 0);
|
|
1338
|
+
const gravB = real.filter((f) => b.memberFiles.includes(f.relativePath)).reduce((s, f) => s + f.gravity, 0);
|
|
1339
|
+
return gravB - gravA;
|
|
1141
1340
|
});
|
|
1142
|
-
|
|
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 = [];
|
|
1143
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) {
|
|
1144
1377
|
let n = p.name, i = 2;
|
|
1145
1378
|
while (seen.has(n)) {
|
|
1146
1379
|
n = `${p.name} ${i++}`;
|
|
@@ -1148,24 +1381,35 @@ function buildPillars(real, communities, stack) {
|
|
|
1148
1381
|
p.name = n;
|
|
1149
1382
|
seen.add(n);
|
|
1150
1383
|
}
|
|
1151
|
-
|
|
1152
|
-
pillars.push({ name: "Core", description: "Primary application code.", memberFiles: real.slice(0, 20).map((f) => f.relativePath) });
|
|
1153
|
-
}
|
|
1154
|
-
return pillars;
|
|
1384
|
+
return finalPillars;
|
|
1155
1385
|
}
|
|
1156
|
-
function
|
|
1386
|
+
function pillarNameFromCluster(files) {
|
|
1387
|
+
const hintCounts = /* @__PURE__ */ new Map();
|
|
1388
|
+
for (const f of files) {
|
|
1389
|
+
if (f.pillarHint && !f.pillarHint.startsWith("community-")) {
|
|
1390
|
+
hintCounts.set(f.pillarHint, (hintCounts.get(f.pillarHint) || 0) + 1);
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
if (hintCounts.size > 0) {
|
|
1394
|
+
const best = [...hintCounts.entries()].sort((a, b) => b[1] - a[1])[0];
|
|
1395
|
+
if (best[1] >= files.length * 0.4)
|
|
1396
|
+
return best[0];
|
|
1397
|
+
}
|
|
1157
1398
|
const dirs = files.map((f) => dirname(f.relativePath)).filter((d) => d && d !== ".");
|
|
1158
1399
|
if (dirs.length) {
|
|
1159
|
-
const
|
|
1400
|
+
const segCounts = /* @__PURE__ */ new Map();
|
|
1160
1401
|
for (const d of dirs) {
|
|
1161
|
-
const
|
|
1162
|
-
|
|
1402
|
+
const segments = d.split(sep).filter((s) => !MEANINGLESS_SEGMENTS.has(s.toLowerCase()));
|
|
1403
|
+
const meaningful = segments.pop();
|
|
1404
|
+
if (meaningful)
|
|
1405
|
+
segCounts.set(meaningful, (segCounts.get(meaningful) || 0) + 1);
|
|
1163
1406
|
}
|
|
1164
|
-
const top = [...
|
|
1407
|
+
const top = [...segCounts.entries()].sort((a, b) => b[1] - a[1])[0];
|
|
1165
1408
|
if (top)
|
|
1166
1409
|
return titleCase(top[0]);
|
|
1167
1410
|
}
|
|
1168
|
-
|
|
1411
|
+
const topFile = basename(files[0].relativePath, extname(files[0].relativePath));
|
|
1412
|
+
return titleCase(topFile);
|
|
1169
1413
|
}
|
|
1170
1414
|
function titleCase(s) {
|
|
1171
1415
|
return s.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
@@ -1234,11 +1478,30 @@ async function readDossier(projectRoot) {
|
|
|
1234
1478
|
}
|
|
1235
1479
|
async function writeDossier(projectRoot, dossier) {
|
|
1236
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
|
+
}));
|
|
1237
1498
|
const dir = join5(projectRoot, ".vibe-splainer");
|
|
1238
1499
|
await mkdir3(dir, { recursive: true });
|
|
1239
1500
|
const dossierPath = join5(dir, "dossier.json");
|
|
1240
1501
|
const tmp = dossierPath + ".tmp";
|
|
1241
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");
|
|
1242
1505
|
const { rename } = await import("fs/promises");
|
|
1243
1506
|
await rename(tmp, dossierPath);
|
|
1244
1507
|
await regenerateUI(projectRoot, dossier);
|
|
@@ -1492,6 +1755,7 @@ async function handleGetFileContext(args) {
|
|
|
1492
1755
|
heatSignals: evidence.heatSignals,
|
|
1493
1756
|
importedBy: persisted?.importedBy ?? [],
|
|
1494
1757
|
imports: persisted?.imports ?? [],
|
|
1758
|
+
pillarHint: persisted?.pillarHint ?? null,
|
|
1495
1759
|
signature: evidence.signature,
|
|
1496
1760
|
hotSpans: evidence.hotSpans,
|
|
1497
1761
|
smellSpans: evidence.smellSpans
|
|
@@ -1531,7 +1795,7 @@ var writeDecisionCardTool = {
|
|
|
1531
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." },
|
|
1532
1796
|
tradeoff: { type: "string", description: "What was given up, or why the obvious approach was rejected. Null only if genuinely none." },
|
|
1533
1797
|
blastRadius: { type: "string", description: "What breaks if this changes. Ground it in the fan-in (importedBy) from get_file_context." },
|
|
1534
|
-
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".' },
|
|
1535
1799
|
evidence: {
|
|
1536
1800
|
type: "array",
|
|
1537
1801
|
items: {
|
|
@@ -1720,17 +1984,17 @@ var inspectPillarTool = {
|
|
|
1720
1984
|
};
|
|
1721
1985
|
async function handleInspectPillar(args) {
|
|
1722
1986
|
const projectRoot = args.projectRoot;
|
|
1723
|
-
const
|
|
1724
|
-
if (!projectRoot || !
|
|
1987
|
+
const pillarName = args.pillarName;
|
|
1988
|
+
if (!projectRoot || !pillarName)
|
|
1725
1989
|
throw new Error("projectRoot and pillarName are required");
|
|
1726
1990
|
const dossier = await readDossier(projectRoot);
|
|
1727
1991
|
if (!dossier) {
|
|
1728
1992
|
return { error: "No dossier found. Run scan_project first." };
|
|
1729
1993
|
}
|
|
1730
|
-
const pillar = dossier.pillars.find((p) => p.name ===
|
|
1994
|
+
const pillar = dossier.pillars.find((p) => p.name === pillarName);
|
|
1731
1995
|
if (!pillar) {
|
|
1732
1996
|
return {
|
|
1733
|
-
error: `Pillar "${
|
|
1997
|
+
error: `Pillar "${pillarName}" not found. Available pillars: ${dossier.pillars.map((p) => p.name).join(", ")}`
|
|
1734
1998
|
};
|
|
1735
1999
|
}
|
|
1736
2000
|
return pillar;
|
|
@@ -39,6 +39,7 @@ export async function handleGetFileContext(args) {
|
|
|
39
39
|
heatSignals: evidence.heatSignals,
|
|
40
40
|
importedBy: persisted?.importedBy ?? [],
|
|
41
41
|
imports: persisted?.imports ?? [],
|
|
42
|
+
pillarHint: persisted?.pillarHint ?? null,
|
|
42
43
|
signature: evidence.signature,
|
|
43
44
|
hotSpans: evidence.hotSpans,
|
|
44
45
|
smellSpans: evidence.smellSpans,
|
|
@@ -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: {
|