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.
- package/README.md +110 -158
- package/dist/commands/install.d.ts +0 -5
- package/dist/commands/install.js +5 -55
- package/dist/commands/serve.js +1 -1
- package/dist/export/ArtifactBundleWriter.js +3 -3
- package/dist/export/ExportOrchestrator.d.ts +2 -20
- package/dist/export/ExportOrchestrator.js +5 -116
- package/dist/export/Watcher.js +5 -5
- package/dist/export/renderers/AgentMarkdownRenderer.d.ts +1 -1
- package/dist/export/renderers/GraphRenderer.d.ts +2 -2
- package/dist/export/renderers/HtmlRenderer.d.ts +1 -1
- package/dist/export/renderers/HtmlRenderer.js +1 -1
- package/dist/export/renderers/JsonRenderer.d.ts +1 -1
- package/dist/export/renderers/RawAnalysisRenderer.d.ts +1 -1
- package/dist/export/renderers/Renderer.d.ts +1 -1
- package/dist/export/renderers/ValidationRenderer.d.ts +1 -1
- package/dist/index.js +441 -3787
- package/dist/mcp/server.js +6 -23
- package/dist/mcp/tools/get_file_context.js +1 -1
- package/dist/mcp/tools/get_project_map.js +1 -1
- package/dist/mcp/tools/get_strategic_overview.js +1 -1
- package/dist/mcp/tools/get_wild_discoveries.js +1 -1
- package/dist/mcp/tools/inspect_pillar.js +1 -1
- package/dist/mcp/tools/mark_stale.js +1 -1
- package/dist/mcp/tools/scan_project.d.ts +2 -3
- package/dist/mcp/tools/scan_project.js +12 -13
- package/dist/mcp/tools/set_project_brief.js +1 -1
- package/dist/mcp/tools/write_decision_card.d.ts +1 -1
- package/dist/mcp/tools/write_decision_card.js +2 -2
- package/dist/ui/index.html +1 -1
- package/package.json +9 -14
- package/dist/commands/hook.d.ts +0 -9
- package/dist/commands/hook.js +0 -84
- package/dist/hook/preToolUse.d.ts +0 -30
- package/dist/hook/preToolUse.js +0 -81
- package/dist/hook.d.ts +0 -1
- package/dist/hook.js +0 -337
- package/dist/mcp/BudgetGuard.d.ts +0 -13
- package/dist/mcp/BudgetGuard.js +0 -55
- package/dist/mcp/SessionScope.d.ts +0 -26
- package/dist/mcp/SessionScope.js +0 -56
- package/dist/mcp/tools/get_file_skeleton.d.ts +0 -23
- package/dist/mcp/tools/get_file_skeleton.js +0 -124
- package/dist/mcp/tools/hydration/get_evidence_slice.d.ts +0 -31
- package/dist/mcp/tools/hydration/get_evidence_slice.js +0 -57
- package/dist/mcp/tools/hydration/get_project_summary.d.ts +0 -23
- package/dist/mcp/tools/hydration/get_project_summary.js +0 -58
- package/dist/mcp/tools/hydration/get_start_here.d.ts +0 -23
- package/dist/mcp/tools/hydration/get_start_here.js +0 -52
- package/dist/mcp/tools/read_file.d.ts +0 -31
- package/dist/mcp/tools/read_file.js +0 -90
- package/dist/store/BlobStore.d.ts +0 -22
- package/dist/store/BlobStore.js +0 -96
- package/dist/store/PointerStore.d.ts +0 -52
- 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
|
|
15
|
-
import { fileURLToPath
|
|
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 =
|
|
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(`[
|
|
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(`[
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
|
89
|
-
if (!SUPPORTED_EXTENSIONS.has(
|
|
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) &&
|
|
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
|
|
763
|
-
const lang = EXT_LANG[
|
|
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, ".
|
|
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
|
|
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 =
|
|
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
|
-
".
|
|
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
|
|
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 (
|
|
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(
|
|
1144
|
+
baseFile = join3(dirname2(tsconfigPath), baseFile);
|
|
1140
1145
|
} else {
|
|
1141
|
-
let currentDir =
|
|
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 =
|
|
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(
|
|
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
|
|
1240
|
-
candidates.push(base +
|
|
1241
|
-
for (const
|
|
1242
|
-
candidates.push(join3(base, "index" +
|
|
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 =
|
|
1259
|
+
let dir = dirname2(fromAbs);
|
|
1255
1260
|
for (let i = 1; i < dots; i++)
|
|
1256
|
-
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(
|
|
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
|
|
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, ".
|
|
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
|
|
1652
|
+
const types = [];
|
|
1621
1653
|
const smThreshold = ["provider", "store"].includes(frameworkRole) ? 8 : 20;
|
|
1622
1654
|
if (gravitySignals.cyclomatic > smThreshold)
|
|
1623
|
-
|
|
1655
|
+
types.push("state_machine");
|
|
1624
1656
|
if (smellKinds.has("god-file")) {
|
|
1625
1657
|
if (frameworkRole === "hook")
|
|
1626
|
-
|
|
1658
|
+
types.push("god_hook");
|
|
1627
1659
|
else
|
|
1628
|
-
|
|
1660
|
+
types.push("god_component");
|
|
1629
1661
|
}
|
|
1630
1662
|
if (sideEffectProfile.length > 3 && !sideEffectProfile.includes("none_detected")) {
|
|
1631
|
-
|
|
1663
|
+
types.push("side_effect_coupling");
|
|
1632
1664
|
}
|
|
1633
1665
|
if (productDomain === "forms" && (gravitySignals.fanIn > 3 || gravitySignals.publicSurface > 5))
|
|
1634
|
-
|
|
1666
|
+
types.push("registry_bottleneck");
|
|
1635
1667
|
if (sideEffectProfile.some((s) => ["database_write", "trpc_mutation", "external_api_call"].includes(s)) && gravitySignals.cyclomatic > 10)
|
|
1636
|
-
|
|
1668
|
+
types.push("mutation_orchestration");
|
|
1637
1669
|
if (ENTRYPOINT_ROLES.has(frameworkRole) && sideEffectProfile.includes("database_write")) {
|
|
1638
|
-
|
|
1670
|
+
types.push("route_handler_write_path");
|
|
1639
1671
|
}
|
|
1640
1672
|
if (smellKinds.has("swallowed-catch"))
|
|
1641
|
-
|
|
1673
|
+
types.push("error_swallowing");
|
|
1642
1674
|
if (sideEffectProfile.includes("local_storage") || sideEffectProfile.includes("indexed_db")) {
|
|
1643
|
-
|
|
1675
|
+
types.push("storage_persistence_risk");
|
|
1644
1676
|
}
|
|
1645
|
-
return
|
|
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
|
|
2120
|
-
riskTypesByFile.set(w.rel,
|
|
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
|
|
2128
|
-
return
|
|
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
|
|
2142
|
-
if (
|
|
2143
|
-
|
|
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(`[
|
|
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, ".
|
|
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, ".
|
|
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(`[
|
|
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(`[
|
|
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(`[
|
|
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 =
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
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: ".
|
|
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
|
|
2690
|
-
const lang = EXT_LANG[
|
|
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, ".
|
|
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, ".
|
|
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
|
|
2778
|
-
nodes.add(
|
|
2779
|
-
for (const
|
|
2780
|
-
nodes.add(
|
|
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, ".
|
|
3158
|
-
const stagingDir = join9(this.projectRoot, ".
|
|
3159
|
-
const oldDir = join9(this.projectRoot, ".
|
|
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:
|
|
2859
|
+
const { existsSync: existsSync5 } = await import("fs");
|
|
3164
2860
|
const { cp } = await import("fs/promises");
|
|
3165
|
-
if (
|
|
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 (
|
|
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
|
|
3237
|
-
import { fileURLToPath as
|
|
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 =
|
|
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("[
|
|
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
|
|
3371
|
-
const f = store.files[
|
|
3372
|
-
const card = uniqueDecisions.get(
|
|
3373
|
-
const recs = viewModel.recommendations[
|
|
3374
|
-
md += `### ${
|
|
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
|
|
3407
|
-
const f = store.files[
|
|
3408
|
-
const card = uniqueDecisions.get(
|
|
3409
|
-
md += `- **${
|
|
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
|
|
3422
|
-
const f = store.files[
|
|
3423
|
-
md += `- ${
|
|
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/
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
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
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
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.
|
|
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
|
|
3530
|
-
await this.
|
|
3531
|
-
|
|
3532
|
-
|
|
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
|
|
3539
|
-
|
|
3540
|
-
|
|
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
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
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
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
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
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
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
|
-
//
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
}
|
|
3599
|
-
function
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
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
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
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
|
-
|
|
3745
|
-
|
|
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("[
|
|
3300
|
+
console.error("[vibesplain] Watcher error:", err);
|
|
4244
3301
|
}
|
|
4245
3302
|
});
|
|
4246
3303
|
activeWatchers.set(projectRoot, watcher);
|
|
4247
|
-
console.error("[
|
|
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
|
-
|
|
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
|
|
4299
|
-
return { result, dossier, scanId
|
|
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(`[
|
|
4306
|
-
const { result, scanId
|
|
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(`[
|
|
4311
|
-
const validation = result.validation ?? { passed: true, errors: 0, warnings: 0, reportPath: ".
|
|
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: ".
|
|
4325
|
-
dossier: ".
|
|
4326
|
-
graph: ".
|
|
4327
|
-
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
|
|
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}
|
|
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["
|
|
4456
|
-
config.mcpServers["
|
|
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
|
-
"
|
|
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
|
|
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 :
|
|
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
|
-
|
|
4640
|
-
|
|
4641
|
-
import
|
|
4642
|
-
|
|
4643
|
-
var
|
|
4644
|
-
function
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
|
|
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
|
|
3667
|
+
return rnds8Pool.slice(poolPtr, poolPtr += 16);
|
|
4650
3668
|
}
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
|
|
4661
|
-
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
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(
|
|
3805
|
+
primaryContent = await readFile9(join13(projectRoot, primaryFile), "utf8");
|
|
4740
3806
|
} catch {
|
|
4741
3807
|
}
|
|
4742
|
-
const hash =
|
|
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(`[
|
|
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/
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
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
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
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
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5032
|
-
|
|
5033
|
-
|
|
5034
|
-
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
|
|
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
|
-
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
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
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5206
|
-
|
|
5207
|
-
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
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
|
-
|
|
5312
|
-
|
|
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
|
-
|
|
5320
|
-
|
|
5321
|
-
|
|
5322
|
-
|
|
5323
|
-
|
|
5324
|
-
|
|
5325
|
-
|
|
5326
|
-
|
|
5327
|
-
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
|
|
5331
|
-
|
|
5332
|
-
|
|
5333
|
-
|
|
5334
|
-
|
|
5335
|
-
|
|
5336
|
-
|
|
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(`[
|
|
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("[
|
|
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("[
|
|
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("
|
|
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
|
|
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();
|