codebase-snapshot 0.9.12 → 0.9.14
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/CHANGELOG.md +379 -0
- package/README.md +1 -1
- package/dist/bin.js +1313 -633
- package/dist/{chunk-3H45YMBG.js → chunk-DUZK5VL2.js} +1413 -767
- package/dist/{chunk-5OR3WVPW.js → chunk-J4W74N4Y.js} +144 -44
- package/dist/cli.cjs +1305 -628
- package/dist/cli.d.cts +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +43 -54
- package/dist/{generate-worker-D5Xgh27c.d.cts → generate-worker-D_mUjgXw.d.cts} +21 -5
- package/dist/{generate-worker-D5Xgh27c.d.ts → generate-worker-D_mUjgXw.d.ts} +21 -5
- package/dist/index.cjs +1389 -760
- package/dist/index.d.cts +58 -35
- package/dist/index.d.ts +58 -35
- package/dist/index.js +1 -1
- package/dist/mcp.cjs +973 -405
- package/dist/mcp.d.cts +67 -67
- package/dist/mcp.d.ts +67 -67
- package/dist/mcp.js +2 -2
- package/dist/{types-CTewen3J.d.cts → types-DIX6zWSt.d.cts} +15 -0
- package/dist/{types-CTewen3J.d.ts → types-DIX6zWSt.d.ts} +15 -0
- package/docs/cli.md +8 -6
- package/docs/configuration.md +44 -10
- package/docs/mcp.md +16 -3
- package/docs/output.md +13 -0
- package/package.json +1 -1
|
@@ -9,11 +9,70 @@ import { createRequire } from 'module';
|
|
|
9
9
|
import { pathToFileURL, fileURLToPath } from 'url';
|
|
10
10
|
import * as childProcess from 'child_process';
|
|
11
11
|
import { execFileSync } from 'child_process';
|
|
12
|
+
import * as http from 'http';
|
|
12
13
|
import * as https from 'https';
|
|
13
14
|
import * as readline from 'readline';
|
|
14
15
|
import { Worker } from 'worker_threads';
|
|
15
16
|
|
|
16
17
|
// src/index.ts
|
|
18
|
+
var hookStdoutScope = new AsyncLocalStorage();
|
|
19
|
+
var captureScope = new AsyncLocalStorage();
|
|
20
|
+
var installs = 0;
|
|
21
|
+
var underlying = null;
|
|
22
|
+
var router = null;
|
|
23
|
+
var asText = (chunk) => typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8");
|
|
24
|
+
var callbackIn = (rest) => {
|
|
25
|
+
for (const argument of rest) {
|
|
26
|
+
if (typeof argument === "function") return argument;
|
|
27
|
+
}
|
|
28
|
+
return void 0;
|
|
29
|
+
};
|
|
30
|
+
function installStdoutRouter() {
|
|
31
|
+
if (installs === 0) {
|
|
32
|
+
underlying = process.stdout.write;
|
|
33
|
+
const realStdout = underlying.bind(process.stdout);
|
|
34
|
+
const realStderr = process.stderr.write.bind(process.stderr);
|
|
35
|
+
router = ((...args) => {
|
|
36
|
+
if (hookStdoutScope.getStore() === true) return realStderr(...args);
|
|
37
|
+
const capture = captureScope.getStore();
|
|
38
|
+
if (capture !== void 0) {
|
|
39
|
+
capture.write(asText(args[0]));
|
|
40
|
+
const callback = callbackIn(args.slice(1));
|
|
41
|
+
if (callback) callback(null);
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
return realStdout(...args);
|
|
45
|
+
});
|
|
46
|
+
process.stdout.write = router;
|
|
47
|
+
}
|
|
48
|
+
installs += 1;
|
|
49
|
+
let released = false;
|
|
50
|
+
return () => {
|
|
51
|
+
if (released) return;
|
|
52
|
+
released = true;
|
|
53
|
+
installs -= 1;
|
|
54
|
+
if (installs === 0) {
|
|
55
|
+
if (process.stdout.write === router && underlying !== null) {
|
|
56
|
+
process.stdout.write = underlying;
|
|
57
|
+
}
|
|
58
|
+
underlying = null;
|
|
59
|
+
router = null;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
async function runWithStdoutCapture(fn) {
|
|
64
|
+
let text2 = "";
|
|
65
|
+
const release = installStdoutRouter();
|
|
66
|
+
try {
|
|
67
|
+
const value = await captureScope.run(
|
|
68
|
+
{ write: (chunk) => void (text2 += chunk) },
|
|
69
|
+
fn
|
|
70
|
+
);
|
|
71
|
+
return { value, text: text2 };
|
|
72
|
+
} finally {
|
|
73
|
+
release();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
17
76
|
|
|
18
77
|
// src/ast/profiles/shared.ts
|
|
19
78
|
var MAX_SUMMARY_CHARS = 1e6;
|
|
@@ -69,7 +128,7 @@ function isCapitalizedName(name) {
|
|
|
69
128
|
}
|
|
70
129
|
|
|
71
130
|
// src/ast/profiles/cfamily.ts
|
|
72
|
-
var INCLUDE = /^#\s*include\s*[<"]([^>"]+)[>"]/;
|
|
131
|
+
var INCLUDE = /^#\s*include\s*([<"])([^>"]+)[>"]/;
|
|
73
132
|
var DEFINE = /^#\s*define\s+([A-Za-z_]\w*)/;
|
|
74
133
|
var USING = /^using\s+(?:namespace\s+)?([\w:]+)\s*;/;
|
|
75
134
|
var TYPE_DECL = /^(?:typedef\s+)?(?:struct|union|class|enum)\s+([A-Za-z_]\w*)/;
|
|
@@ -88,7 +147,7 @@ var cfamilyProfile = {
|
|
|
88
147
|
}
|
|
89
148
|
const included = INCLUDE.exec(trimmed);
|
|
90
149
|
if (included) {
|
|
91
|
-
summary.imports.push(included[1]);
|
|
150
|
+
summary.imports.push(included[1] === '"' ? `./${included[2]}` : included[2]);
|
|
92
151
|
return;
|
|
93
152
|
}
|
|
94
153
|
const used = USING.exec(trimmed);
|
|
@@ -492,7 +551,15 @@ var pythonProfile = {
|
|
|
492
551
|
}
|
|
493
552
|
const from = FROM_IMPORT.exec(trimmed);
|
|
494
553
|
if (from) {
|
|
495
|
-
|
|
554
|
+
const module = from[1];
|
|
555
|
+
if (/^\.+$/.test(module)) {
|
|
556
|
+
for (const name of from[2].split(",")) {
|
|
557
|
+
const imported2 = name.replace(/[()]/g, "").trim().split(/\s+as\s+/)[0].trim();
|
|
558
|
+
if (/^[A-Za-z_]\w*$/.test(imported2)) summary.imports.push(`${module}${imported2}`);
|
|
559
|
+
}
|
|
560
|
+
} else {
|
|
561
|
+
summary.imports.push(module);
|
|
562
|
+
}
|
|
496
563
|
routePending = false;
|
|
497
564
|
return;
|
|
498
565
|
}
|
|
@@ -541,7 +608,8 @@ var pythonProfile = {
|
|
|
541
608
|
};
|
|
542
609
|
|
|
543
610
|
// src/ast/profiles/ruby.ts
|
|
544
|
-
var
|
|
611
|
+
var REQUIRE_RELATIVE = /^require_relative\s+["']([^"']+)["']/;
|
|
612
|
+
var REQUIRE = /^require\s+["']([^"']+)["']/;
|
|
545
613
|
var CLASS_OR_MODULE = /^(?:class|module)\s+([A-Z][\w:]*)/;
|
|
546
614
|
var METHOD2 = /^def\s+(?:self\.)?([A-Za-z_]\w*[?!=]?)/;
|
|
547
615
|
var rubyProfile = {
|
|
@@ -552,6 +620,11 @@ var rubyProfile = {
|
|
|
552
620
|
eachLine(content, (line) => {
|
|
553
621
|
const trimmed = line.trim();
|
|
554
622
|
if (!trimmed || trimmed.startsWith("#")) return;
|
|
623
|
+
const relative8 = REQUIRE_RELATIVE.exec(trimmed);
|
|
624
|
+
if (relative8) {
|
|
625
|
+
summary.imports.push(`./${relative8[1]}`);
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
555
628
|
const required = REQUIRE.exec(trimmed);
|
|
556
629
|
if (required) {
|
|
557
630
|
summary.imports.push(required[1]);
|
|
@@ -748,13 +821,23 @@ function summarizeTypeScriptSource(relativePath, content) {
|
|
|
748
821
|
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
749
822
|
summary.imports.push(node.moduleSpecifier.text);
|
|
750
823
|
}
|
|
824
|
+
if (ts.isImportEqualsDeclaration(node) && ts.isExternalModuleReference(node.moduleReference) && node.moduleReference.expression !== void 0 && ts.isStringLiteral(node.moduleReference.expression)) {
|
|
825
|
+
summary.imports.push(node.moduleReference.expression.text);
|
|
826
|
+
}
|
|
827
|
+
if (ts.isCallExpression(node) && node.arguments.length >= 1) {
|
|
828
|
+
const firstArgument = node.arguments[0];
|
|
829
|
+
if (firstArgument !== void 0 && ts.isStringLiteral(firstArgument)) {
|
|
830
|
+
const isDynamicImport = node.expression.kind === ts.SyntaxKind.ImportKeyword;
|
|
831
|
+
const isRequire = ts.isIdentifier(node.expression) && node.expression.text === "require";
|
|
832
|
+
if (isDynamicImport || isRequire) summary.imports.push(firstArgument.text);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
751
835
|
if (ts.isExportDeclaration(node)) {
|
|
752
836
|
if (node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
753
837
|
summary.imports.push(node.moduleSpecifier.text);
|
|
838
|
+
summary.reExports.push(node.moduleSpecifier.text);
|
|
754
839
|
if (node.exportClause && ts.isNamespaceExport(node.exportClause)) {
|
|
755
840
|
summary.exports.push(node.exportClause.name.text);
|
|
756
|
-
} else if (!node.exportClause) {
|
|
757
|
-
summary.reExports.push(node.moduleSpecifier.text);
|
|
758
841
|
}
|
|
759
842
|
}
|
|
760
843
|
if (node.exportClause && ts.isNamedExports(node.exportClause)) {
|
|
@@ -846,7 +929,8 @@ function summarizeTypeScriptSource(relativePath, content) {
|
|
|
846
929
|
}
|
|
847
930
|
|
|
848
931
|
// src/ast/profiles/sfc.ts
|
|
849
|
-
var SCRIPT_BLOCK = /<script[^>]
|
|
932
|
+
var SCRIPT_BLOCK = /<script([^>]*)>([\s\S]*?)<\/script>/gi;
|
|
933
|
+
var JSX_LANG = /\blang\s*=\s*["']?[tj]sx\b/i;
|
|
850
934
|
var ASTRO_FRONTMATTER = /^\uFEFF?---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*(?=\r?\n|$)/;
|
|
851
935
|
function componentName(path17) {
|
|
852
936
|
const base = path17.split("/").pop() ?? path17;
|
|
@@ -857,13 +941,15 @@ var sfcProfile = {
|
|
|
857
941
|
extensions: [".vue", ".svelte", ".astro"],
|
|
858
942
|
summarize(path17, content) {
|
|
859
943
|
let script = "";
|
|
944
|
+
let usesJsx = path17.toLowerCase().endsWith(".astro");
|
|
860
945
|
if (path17.toLowerCase().endsWith(".astro")) {
|
|
861
946
|
const frontmatter = ASTRO_FRONTMATTER.exec(content);
|
|
862
947
|
if (frontmatter) script += `${frontmatter[1]}
|
|
863
948
|
`;
|
|
864
949
|
}
|
|
865
950
|
for (const match of content.matchAll(SCRIPT_BLOCK)) {
|
|
866
|
-
|
|
951
|
+
if (JSX_LANG.test(match[1] ?? "")) usesJsx = true;
|
|
952
|
+
script += `${match[2]}
|
|
867
953
|
`;
|
|
868
954
|
}
|
|
869
955
|
const name = componentName(path17);
|
|
@@ -875,7 +961,7 @@ var sfcProfile = {
|
|
|
875
961
|
}
|
|
876
962
|
return { summary: finalizeSummary(summary2), quality: "heuristic" };
|
|
877
963
|
}
|
|
878
|
-
const summary = summarizeTypeScriptSource(`${name}
|
|
964
|
+
const summary = summarizeTypeScriptSource(`${name}.${usesJsx ? "tsx" : "ts"}`, script);
|
|
879
965
|
summary.reactComponents.push(name);
|
|
880
966
|
summary.exports.push(name);
|
|
881
967
|
return { summary: finalizeSummary(summary), quality: "ast" };
|
|
@@ -1067,124 +1153,421 @@ function summarizeWithProfile(path17, content) {
|
|
|
1067
1153
|
const profile = profileForPath(path17);
|
|
1068
1154
|
return profile ? profile.summarize(path17, content) : null;
|
|
1069
1155
|
}
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1156
|
+
var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
1157
|
+
".png",
|
|
1158
|
+
".jpg",
|
|
1159
|
+
".jpeg",
|
|
1160
|
+
".gif",
|
|
1161
|
+
".webp",
|
|
1162
|
+
".avif",
|
|
1163
|
+
".ico",
|
|
1164
|
+
".bmp",
|
|
1165
|
+
".tiff",
|
|
1166
|
+
".pdf",
|
|
1167
|
+
".zip",
|
|
1168
|
+
".tar",
|
|
1169
|
+
".gz",
|
|
1170
|
+
".tgz",
|
|
1171
|
+
".br",
|
|
1172
|
+
".bz2",
|
|
1173
|
+
".xz",
|
|
1174
|
+
".zst",
|
|
1175
|
+
".lz4",
|
|
1176
|
+
".lzma",
|
|
1177
|
+
".7z",
|
|
1178
|
+
".rar",
|
|
1179
|
+
".mp3",
|
|
1180
|
+
".mp4",
|
|
1181
|
+
".wav",
|
|
1182
|
+
".ogg",
|
|
1183
|
+
".webm",
|
|
1184
|
+
".mov",
|
|
1185
|
+
".avi",
|
|
1186
|
+
".woff",
|
|
1187
|
+
".woff2",
|
|
1188
|
+
".ttf",
|
|
1189
|
+
".otf",
|
|
1190
|
+
".eot",
|
|
1191
|
+
".wasm",
|
|
1192
|
+
".node",
|
|
1193
|
+
".exe",
|
|
1194
|
+
".dll",
|
|
1195
|
+
".dylib",
|
|
1196
|
+
".so"
|
|
1197
|
+
]);
|
|
1198
|
+
function isBinaryExtension(filePath) {
|
|
1199
|
+
const ext = path16.extname(filePath).toLowerCase();
|
|
1200
|
+
return BINARY_EXTENSIONS.has(ext);
|
|
1201
|
+
}
|
|
1202
|
+
function isBinaryBuffer(buffer) {
|
|
1203
|
+
return buffer.includes(0);
|
|
1204
|
+
}
|
|
1205
|
+
function countControlBytes(buffer) {
|
|
1206
|
+
let count = 0;
|
|
1207
|
+
for (let index = 0; index < buffer.length; index++) {
|
|
1208
|
+
const byte = buffer[index];
|
|
1209
|
+
if (byte < 32) {
|
|
1210
|
+
if (byte !== 9 && byte !== 10 && byte !== 13) count += 1;
|
|
1086
1211
|
continue;
|
|
1087
1212
|
}
|
|
1088
|
-
if (
|
|
1089
|
-
|
|
1090
|
-
inBlockComment = false;
|
|
1091
|
-
i += 1;
|
|
1092
|
-
}
|
|
1213
|
+
if (byte === 127) {
|
|
1214
|
+
count += 1;
|
|
1093
1215
|
continue;
|
|
1094
1216
|
}
|
|
1095
|
-
if (
|
|
1096
|
-
|
|
1097
|
-
if (
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
i += 1;
|
|
1101
|
-
}
|
|
1102
|
-
} else if (char === '"') {
|
|
1103
|
-
inString = false;
|
|
1217
|
+
if (byte === 194 && index + 1 < buffer.length) {
|
|
1218
|
+
const next = buffer[index + 1];
|
|
1219
|
+
if (next >= 128 && next <= 159) {
|
|
1220
|
+
count += 1;
|
|
1221
|
+
index += 1;
|
|
1104
1222
|
}
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
return count;
|
|
1226
|
+
}
|
|
1227
|
+
var CONTROL_BYTES_FLOOR = 4;
|
|
1228
|
+
function countInvalidUtf8Bytes(buffer) {
|
|
1229
|
+
let invalid = 0;
|
|
1230
|
+
let index = 0;
|
|
1231
|
+
const length = buffer.length;
|
|
1232
|
+
while (index < length) {
|
|
1233
|
+
const lead = buffer[index];
|
|
1234
|
+
if (lead < 128) {
|
|
1235
|
+
index += 1;
|
|
1105
1236
|
continue;
|
|
1106
1237
|
}
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1238
|
+
let continuation = 0;
|
|
1239
|
+
let codePoint = 0;
|
|
1240
|
+
let minimum = 0;
|
|
1241
|
+
if ((lead & 224) === 192) {
|
|
1242
|
+
continuation = 1;
|
|
1243
|
+
codePoint = lead & 31;
|
|
1244
|
+
minimum = 128;
|
|
1245
|
+
} else if ((lead & 240) === 224) {
|
|
1246
|
+
continuation = 2;
|
|
1247
|
+
codePoint = lead & 15;
|
|
1248
|
+
minimum = 2048;
|
|
1249
|
+
} else if ((lead & 248) === 240) {
|
|
1250
|
+
continuation = 3;
|
|
1251
|
+
codePoint = lead & 7;
|
|
1252
|
+
minimum = 65536;
|
|
1253
|
+
} else {
|
|
1254
|
+
invalid += 1;
|
|
1255
|
+
index += 1;
|
|
1110
1256
|
continue;
|
|
1111
1257
|
}
|
|
1112
|
-
if (
|
|
1113
|
-
|
|
1114
|
-
|
|
1258
|
+
if (index + continuation >= length) {
|
|
1259
|
+
invalid += 1;
|
|
1260
|
+
index += 1;
|
|
1115
1261
|
continue;
|
|
1116
1262
|
}
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1263
|
+
let wellFormed = true;
|
|
1264
|
+
for (let offset = 1; offset <= continuation; offset++) {
|
|
1265
|
+
const next = buffer[index + offset];
|
|
1266
|
+
if ((next & 192) !== 128) {
|
|
1267
|
+
wellFormed = false;
|
|
1268
|
+
break;
|
|
1269
|
+
}
|
|
1270
|
+
codePoint = codePoint << 6 | next & 63;
|
|
1271
|
+
}
|
|
1272
|
+
const isSurrogate = codePoint >= 55296 && codePoint <= 57343;
|
|
1273
|
+
if (!wellFormed || codePoint < minimum || codePoint > 1114111 || isSurrogate) {
|
|
1274
|
+
invalid += 1;
|
|
1275
|
+
index += 1;
|
|
1120
1276
|
continue;
|
|
1121
1277
|
}
|
|
1122
|
-
|
|
1278
|
+
index += continuation + 1;
|
|
1123
1279
|
}
|
|
1124
|
-
return
|
|
1280
|
+
return invalid;
|
|
1125
1281
|
}
|
|
1126
|
-
function
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
if (
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1282
|
+
function readBomEncoding(buffer) {
|
|
1283
|
+
if (buffer.length >= 2) {
|
|
1284
|
+
if (buffer[0] === 255 && buffer[1] === 254) return "utf-16le";
|
|
1285
|
+
if (buffer[0] === 254 && buffer[1] === 255) return "utf-16be";
|
|
1286
|
+
}
|
|
1287
|
+
return null;
|
|
1288
|
+
}
|
|
1289
|
+
function looksLikeUtf16(buffer) {
|
|
1290
|
+
if (buffer.length < 8 || buffer.length % 2 !== 0) return null;
|
|
1291
|
+
const sample = Math.min(buffer.length, 4096);
|
|
1292
|
+
let nulInLowByte = 0;
|
|
1293
|
+
let nulInHighByte = 0;
|
|
1294
|
+
const units = sample / 2;
|
|
1295
|
+
for (let index = 0; index < sample; index += 2) {
|
|
1296
|
+
if (buffer[index] === 0) nulInLowByte += 1;
|
|
1297
|
+
if (buffer[index + 1] === 0) nulInHighByte += 1;
|
|
1298
|
+
}
|
|
1299
|
+
if (nulInHighByte > units * 0.3 && nulInLowByte < units * 0.05) return "utf-16le";
|
|
1300
|
+
if (nulInLowByte > units * 0.3 && nulInHighByte < units * 0.05) return "utf-16be";
|
|
1301
|
+
return null;
|
|
1302
|
+
}
|
|
1303
|
+
function decodeUtf16(buffer, encoding) {
|
|
1304
|
+
const littleEndian = encoding === "utf-16le" ? buffer : Buffer.from(buffer.subarray(0, buffer.length - buffer.length % 2)).swap16();
|
|
1305
|
+
return new TextDecoder("utf-16le").decode(littleEndian);
|
|
1306
|
+
}
|
|
1307
|
+
function decodeText(buffer) {
|
|
1308
|
+
const bomEncoding = readBomEncoding(buffer);
|
|
1309
|
+
if (bomEncoding) {
|
|
1310
|
+
const text2 = decodeUtf16(buffer.subarray(2), bomEncoding);
|
|
1311
|
+
return { text: text2, encoding: bomEncoding, invalidBytes: 0, binary: false };
|
|
1312
|
+
}
|
|
1313
|
+
if (buffer.includes(0)) {
|
|
1314
|
+
const shape = looksLikeUtf16(buffer);
|
|
1315
|
+
if (shape) {
|
|
1316
|
+
return { text: decodeUtf16(buffer, shape), encoding: shape, invalidBytes: 0, binary: false };
|
|
1138
1317
|
}
|
|
1139
|
-
|
|
1140
|
-
|
|
1318
|
+
const offset = buffer.indexOf(0);
|
|
1319
|
+
return {
|
|
1320
|
+
text: "",
|
|
1321
|
+
encoding: "utf-8",
|
|
1322
|
+
invalidBytes: 0,
|
|
1323
|
+
binary: true,
|
|
1324
|
+
binaryReason: `NUL byte at offset ${offset}`
|
|
1325
|
+
};
|
|
1326
|
+
}
|
|
1327
|
+
const invalidBytes = countInvalidUtf8Bytes(buffer);
|
|
1328
|
+
if (invalidBytes === 0) {
|
|
1329
|
+
const controlBytes = countControlBytes(buffer);
|
|
1330
|
+
if (controlBytes > CONTROL_BYTES_FLOOR && controlBytes * 100 > buffer.length) {
|
|
1141
1331
|
return {
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1332
|
+
text: "",
|
|
1333
|
+
encoding: "utf-8",
|
|
1334
|
+
invalidBytes: 0,
|
|
1335
|
+
binary: true,
|
|
1336
|
+
binaryReason: `${controlBytes} control byte(s) in ${buffer.length} B; valid UTF-8 but not text`
|
|
1146
1337
|
};
|
|
1147
1338
|
}
|
|
1148
1339
|
return {
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1340
|
+
text: new TextDecoder("utf-8").decode(buffer),
|
|
1341
|
+
encoding: "utf-8",
|
|
1342
|
+
invalidBytes: 0,
|
|
1343
|
+
binary: false
|
|
1153
1344
|
};
|
|
1154
|
-
}
|
|
1345
|
+
}
|
|
1346
|
+
if (invalidBytes >= 32 && invalidBytes * 3 >= buffer.length) {
|
|
1155
1347
|
return {
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1348
|
+
text: "",
|
|
1349
|
+
encoding: "utf-8",
|
|
1350
|
+
invalidBytes,
|
|
1351
|
+
binary: true,
|
|
1352
|
+
binaryReason: `${invalidBytes} of ${buffer.length} bytes are not UTF-8; treated as binary`
|
|
1160
1353
|
};
|
|
1161
1354
|
}
|
|
1355
|
+
return {
|
|
1356
|
+
text: buffer.toString("utf8"),
|
|
1357
|
+
encoding: "utf-8-lossy",
|
|
1358
|
+
invalidBytes,
|
|
1359
|
+
binary: false
|
|
1360
|
+
};
|
|
1162
1361
|
}
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
let
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1362
|
+
var BINARY_SCAN_BYTES = 8 * 1024 * 1024;
|
|
1363
|
+
var CHUNK = 64 * 1024;
|
|
1364
|
+
var SHAPE_SAMPLE_BYTES = 4096;
|
|
1365
|
+
function isBinaryFile(absolutePath) {
|
|
1366
|
+
if (isBinaryExtension(absolutePath)) return true;
|
|
1367
|
+
let fd;
|
|
1368
|
+
try {
|
|
1369
|
+
fd = fs14.openSync(absolutePath, "r");
|
|
1370
|
+
const buffer = Buffer.alloc(CHUNK);
|
|
1371
|
+
let sample;
|
|
1372
|
+
let position = 0;
|
|
1373
|
+
while (position < BINARY_SCAN_BYTES) {
|
|
1374
|
+
const wanted = Math.min(CHUNK, BINARY_SCAN_BYTES - position);
|
|
1375
|
+
const bytesRead = fs14.readSync(fd, buffer, 0, wanted, position);
|
|
1376
|
+
if (bytesRead === 0) return false;
|
|
1377
|
+
const chunk = buffer.subarray(0, bytesRead);
|
|
1378
|
+
if (sample === void 0) {
|
|
1379
|
+
sample = Buffer.from(chunk.subarray(0, SHAPE_SAMPLE_BYTES));
|
|
1380
|
+
}
|
|
1381
|
+
if (chunk.includes(0)) {
|
|
1382
|
+
const shape = readBomEncoding(sample) ?? looksLikeUtf16(sample);
|
|
1383
|
+
return shape === null;
|
|
1384
|
+
}
|
|
1385
|
+
position += bytesRead;
|
|
1386
|
+
}
|
|
1387
|
+
return false;
|
|
1388
|
+
} catch {
|
|
1389
|
+
return false;
|
|
1390
|
+
} finally {
|
|
1391
|
+
if (fd !== void 0) {
|
|
1392
|
+
try {
|
|
1393
|
+
fs14.closeSync(fd);
|
|
1394
|
+
} catch {
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
// src/ast/imports.ts
|
|
1401
|
+
var SUMMARY_MAX_SOURCE_BYTES = 2 * 1024 * 1024;
|
|
1402
|
+
var TS_SOURCE_EXTENSIONS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
|
|
1403
|
+
var isTsSource = (relativePath) => {
|
|
1404
|
+
const lower = relativePath.toLowerCase();
|
|
1405
|
+
return TS_SOURCE_EXTENSIONS.some((extension) => lower.endsWith(extension));
|
|
1406
|
+
};
|
|
1407
|
+
function importsOfText(relativePath, text2) {
|
|
1408
|
+
if (Buffer.byteLength(text2, "utf8") > SUMMARY_MAX_SOURCE_BYTES) return [];
|
|
1409
|
+
if (isTsSource(relativePath)) return summarizeTypeScriptSource(relativePath, text2).imports;
|
|
1410
|
+
return summarizeWithProfile(relativePath, text2)?.summary.imports ?? [];
|
|
1411
|
+
}
|
|
1412
|
+
function importsOfFile(relativePath, absolutePath) {
|
|
1413
|
+
let buffer;
|
|
1414
|
+
try {
|
|
1415
|
+
const stats = fs14.statSync(absolutePath);
|
|
1416
|
+
if (!stats.isFile() || stats.size > SUMMARY_MAX_SOURCE_BYTES) return [];
|
|
1417
|
+
buffer = fs14.readFileSync(absolutePath);
|
|
1418
|
+
} catch {
|
|
1419
|
+
return [];
|
|
1420
|
+
}
|
|
1421
|
+
const decoded = decodeText(buffer);
|
|
1422
|
+
if (decoded.binary) return [];
|
|
1423
|
+
return importsOfText(relativePath, decoded.text);
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
// src/ast/structured.ts
|
|
1427
|
+
function stripJsonComments(content) {
|
|
1428
|
+
const text2 = content.replace(/^\uFEFF/, "");
|
|
1429
|
+
const withoutComments = stripComments(text2);
|
|
1430
|
+
return stripTrailingCommas(withoutComments);
|
|
1431
|
+
}
|
|
1432
|
+
function stripComments(text2) {
|
|
1433
|
+
let out = "";
|
|
1434
|
+
let inString = false;
|
|
1435
|
+
let escaped = false;
|
|
1436
|
+
for (let index = 0; index < text2.length; index += 1) {
|
|
1437
|
+
const char = text2[index];
|
|
1438
|
+
if (inString) {
|
|
1439
|
+
out += char;
|
|
1440
|
+
if (escaped) escaped = false;
|
|
1441
|
+
else if (char === "\\") escaped = true;
|
|
1442
|
+
else if (char === '"') inString = false;
|
|
1443
|
+
continue;
|
|
1444
|
+
}
|
|
1445
|
+
if (char === '"') {
|
|
1446
|
+
inString = true;
|
|
1447
|
+
out += char;
|
|
1448
|
+
continue;
|
|
1449
|
+
}
|
|
1450
|
+
if (char === "/" && text2[index + 1] === "/") {
|
|
1451
|
+
while (index < text2.length && text2[index] !== "\n" && text2[index] !== "\r") index += 1;
|
|
1452
|
+
index -= 1;
|
|
1453
|
+
continue;
|
|
1454
|
+
}
|
|
1455
|
+
if (char === "/" && text2[index + 1] === "*") {
|
|
1456
|
+
index += 2;
|
|
1457
|
+
while (index < text2.length && !(text2[index] === "*" && text2[index + 1] === "/")) {
|
|
1458
|
+
if (text2[index] === "\n" || text2[index] === "\r") out += text2[index];
|
|
1459
|
+
index += 1;
|
|
1460
|
+
}
|
|
1461
|
+
index += 1;
|
|
1462
|
+
continue;
|
|
1463
|
+
}
|
|
1464
|
+
out += char;
|
|
1465
|
+
}
|
|
1466
|
+
return out;
|
|
1467
|
+
}
|
|
1468
|
+
function stripTrailingCommas(text2) {
|
|
1469
|
+
let out = "";
|
|
1470
|
+
let inString = false;
|
|
1471
|
+
let escaped = false;
|
|
1472
|
+
for (let index = 0; index < text2.length; index += 1) {
|
|
1473
|
+
const char = text2[index];
|
|
1474
|
+
if (inString) {
|
|
1475
|
+
out += char;
|
|
1476
|
+
if (escaped) escaped = false;
|
|
1477
|
+
else if (char === "\\") escaped = true;
|
|
1478
|
+
else if (char === '"') inString = false;
|
|
1479
|
+
continue;
|
|
1480
|
+
}
|
|
1481
|
+
if (char === '"') {
|
|
1482
|
+
inString = true;
|
|
1483
|
+
out += char;
|
|
1484
|
+
continue;
|
|
1485
|
+
}
|
|
1486
|
+
if (char === ",") {
|
|
1487
|
+
let next = index + 1;
|
|
1488
|
+
while (next < text2.length && /\s/.test(text2[next])) next += 1;
|
|
1489
|
+
if (text2[next] === "}" || text2[next] === "]") continue;
|
|
1490
|
+
}
|
|
1491
|
+
out += char;
|
|
1492
|
+
}
|
|
1493
|
+
return out;
|
|
1494
|
+
}
|
|
1495
|
+
function summarizeJson(content) {
|
|
1496
|
+
try {
|
|
1497
|
+
const parsed = JSON.parse(stripJsonComments(content));
|
|
1498
|
+
if (Array.isArray(parsed)) {
|
|
1499
|
+
const sample = parsed[0];
|
|
1500
|
+
const sampleKeys = sample && typeof sample === "object" && !Array.isArray(sample) ? Object.keys(sample).slice(0, 10).sort() : [];
|
|
1501
|
+
return {
|
|
1502
|
+
shape: "array",
|
|
1503
|
+
itemCount: parsed.length,
|
|
1504
|
+
keys: [],
|
|
1505
|
+
sampleKeys
|
|
1506
|
+
};
|
|
1507
|
+
}
|
|
1508
|
+
if (parsed && typeof parsed === "object") {
|
|
1509
|
+
const keys = Object.keys(parsed).sort();
|
|
1510
|
+
return {
|
|
1511
|
+
shape: "object",
|
|
1512
|
+
itemCount: keys.length,
|
|
1513
|
+
keys: keys.slice(0, 30),
|
|
1514
|
+
sampleKeys: []
|
|
1515
|
+
};
|
|
1516
|
+
}
|
|
1517
|
+
return {
|
|
1518
|
+
shape: typeof parsed,
|
|
1519
|
+
itemCount: null,
|
|
1520
|
+
keys: [],
|
|
1521
|
+
sampleKeys: []
|
|
1522
|
+
};
|
|
1523
|
+
} catch {
|
|
1524
|
+
return {
|
|
1525
|
+
shape: "invalid-json",
|
|
1526
|
+
itemCount: null,
|
|
1527
|
+
keys: [],
|
|
1528
|
+
sampleKeys: []
|
|
1529
|
+
};
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
function frontMatterEnd(lines) {
|
|
1533
|
+
if (lines.length < 3 || lines[0].trim() !== "---") return 0;
|
|
1534
|
+
for (let index = 1; index < lines.length; index += 1) {
|
|
1535
|
+
const line = lines[index];
|
|
1536
|
+
const candidate = line.trim();
|
|
1537
|
+
if (candidate === "---" || candidate === "...") {
|
|
1538
|
+
return index > 1 ? index : 0;
|
|
1539
|
+
}
|
|
1540
|
+
if (candidate === "") continue;
|
|
1541
|
+
if (/^#(\s|$)/.test(candidate)) continue;
|
|
1542
|
+
if (/^(`{3,}|~{3,})/.test(candidate)) return 0;
|
|
1543
|
+
if (/^#{1,6}\s+\S/.test(candidate)) return 0;
|
|
1544
|
+
const isYamlMapping = /^[A-Za-z0-9_.-]+\s*:(\s|$)/.test(candidate);
|
|
1545
|
+
const isListItem = /^-(?:(\s|$))/.test(candidate);
|
|
1546
|
+
const isIndented = /^[ \t]/.test(line);
|
|
1547
|
+
if (!isYamlMapping && !isListItem && !isIndented) return 0;
|
|
1548
|
+
}
|
|
1549
|
+
return 0;
|
|
1550
|
+
}
|
|
1551
|
+
function summarizeMarkdown(content) {
|
|
1552
|
+
const headings = [];
|
|
1553
|
+
const frontmatterKeys = [];
|
|
1554
|
+
const lines = content.replace(/^\uFEFF/, "").split(/\r\n|\n|\r/);
|
|
1555
|
+
let inFrontmatter = false;
|
|
1556
|
+
let fence = null;
|
|
1557
|
+
const frontMatterEndLine = frontMatterEnd(lines);
|
|
1176
1558
|
for (let index = 0; index < lines.length; index++) {
|
|
1177
1559
|
const line = lines[index];
|
|
1178
|
-
if (index === 0 &&
|
|
1560
|
+
if (index === 0 && frontMatterEndLine > 0) {
|
|
1179
1561
|
inFrontmatter = true;
|
|
1180
1562
|
continue;
|
|
1181
1563
|
}
|
|
1182
1564
|
if (inFrontmatter) {
|
|
1183
|
-
|
|
1565
|
+
const trimmedFrontmatter = line.trim();
|
|
1566
|
+
if (trimmedFrontmatter === "---" || trimmedFrontmatter === "...") {
|
|
1184
1567
|
inFrontmatter = false;
|
|
1185
1568
|
continue;
|
|
1186
1569
|
}
|
|
1187
|
-
const match = /^([
|
|
1570
|
+
const match = /^([A-Za-z0-9_.-]+)\s*:/.exec(line);
|
|
1188
1571
|
if (match) frontmatterKeys.push(match[1]);
|
|
1189
1572
|
continue;
|
|
1190
1573
|
}
|
|
@@ -1217,24 +1600,39 @@ function hasCollapsibleBody(node) {
|
|
|
1217
1600
|
return body !== void 0 && ts.isBlock(body);
|
|
1218
1601
|
}
|
|
1219
1602
|
function isTsxFileName(fileName) {
|
|
1220
|
-
|
|
1603
|
+
const lower = fileName.toLowerCase();
|
|
1604
|
+
return lower.endsWith(".tsx") || lower.endsWith(".jsx");
|
|
1605
|
+
}
|
|
1606
|
+
function scriptKindFor(fileName) {
|
|
1607
|
+
const lower = fileName.toLowerCase();
|
|
1608
|
+
if (lower.endsWith(".tsx")) return ts.ScriptKind.TSX;
|
|
1609
|
+
if (lower.endsWith(".jsx")) return ts.ScriptKind.JSX;
|
|
1610
|
+
if (lower.endsWith(".js") || lower.endsWith(".mjs") || lower.endsWith(".cjs")) {
|
|
1611
|
+
return ts.ScriptKind.JS;
|
|
1612
|
+
}
|
|
1613
|
+
return ts.ScriptKind.TS;
|
|
1221
1614
|
}
|
|
1222
1615
|
function skeletonizeTypeScript(source, fileName = "file.ts") {
|
|
1223
|
-
const scriptKind = isTsxFileName(fileName) ? ts.ScriptKind.TSX : ts.ScriptKind.TS;
|
|
1224
1616
|
const sourceFile = ts.createSourceFile(
|
|
1225
1617
|
fileName,
|
|
1226
1618
|
source,
|
|
1227
1619
|
ts.ScriptTarget.Latest,
|
|
1228
|
-
|
|
1229
|
-
|
|
1620
|
+
// `setParentNodes: false`. Nothing here reads `.parent` — the one `getStart` call is given the
|
|
1621
|
+
// source file explicitly, which is the reason that argument exists. Asking for parent pointers
|
|
1622
|
+
// makes the parser run a second full pass over every node in the file to set them, on top of
|
|
1623
|
+
// the summary parse this same file already pays for.
|
|
1624
|
+
false,
|
|
1625
|
+
scriptKindFor(fileName)
|
|
1230
1626
|
);
|
|
1231
1627
|
const spans = [];
|
|
1232
1628
|
const visit = (node) => {
|
|
1233
1629
|
if ((ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node) || ts.isConstructorDeclaration(node) || ts.isGetAccessorDeclaration(node) || ts.isSetAccessorDeclaration(node) || ts.isFunctionExpression(node) || ts.isArrowFunction(node)) && hasCollapsibleBody(node)) {
|
|
1234
1630
|
const body = node.body;
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1631
|
+
if (body.statements.length > 0) {
|
|
1632
|
+
const start = body.getStart(sourceFile);
|
|
1633
|
+
const end = body.getEnd();
|
|
1634
|
+
if (end > start) spans.push({ start, end });
|
|
1635
|
+
}
|
|
1238
1636
|
}
|
|
1239
1637
|
ts.forEachChild(node, visit);
|
|
1240
1638
|
};
|
|
@@ -1246,16 +1644,21 @@ function skeletonizeTypeScript(source, fileName = "file.ts") {
|
|
|
1246
1644
|
if (last && span.start < last.end) continue;
|
|
1247
1645
|
outermost.push(span);
|
|
1248
1646
|
}
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1647
|
+
const chunks = [];
|
|
1648
|
+
let cursor = 0;
|
|
1649
|
+
for (const span of outermost) {
|
|
1650
|
+
chunks.push(source.slice(cursor, span.start), "{ /* ... */ }");
|
|
1651
|
+
cursor = span.end;
|
|
1253
1652
|
}
|
|
1254
|
-
|
|
1653
|
+
chunks.push(source.slice(cursor));
|
|
1654
|
+
return { text: chunks.join(""), collapsed: outermost.length };
|
|
1255
1655
|
}
|
|
1256
1656
|
function isSkeletonizable(filePath) {
|
|
1257
1657
|
const lower = filePath.toLowerCase();
|
|
1258
|
-
|
|
1658
|
+
if (lower.endsWith(".d.ts") || lower.endsWith(".d.mts") || lower.endsWith(".d.cts")) {
|
|
1659
|
+
return false;
|
|
1660
|
+
}
|
|
1661
|
+
return lower.endsWith(".ts") || lower.endsWith(".tsx") || lower.endsWith(".mts") || lower.endsWith(".cts") || lower.endsWith(".js") || lower.endsWith(".jsx") || lower.endsWith(".mjs") || lower.endsWith(".cjs");
|
|
1259
1662
|
}
|
|
1260
1663
|
var MANIFEST_FILES = [
|
|
1261
1664
|
"package.json",
|
|
@@ -2493,9 +2896,18 @@ var DEFAULT_SECRET_PATTERNS = [
|
|
|
2493
2896
|
// The unquoted twin for YAML, docker-compose and `.env`: those formats never
|
|
2494
2897
|
// quote their values, so the rules above cannot see them. `valueGroup` keeps
|
|
2495
2898
|
// `POSTGRES_PASSWORD:` in the output.
|
|
2899
|
+
//
|
|
2900
|
+
// This is the one unquoted rule without `NOT_AFTER_QUOTE`, and the lookbehind is a
|
|
2901
|
+
// string-literal guard rather than a precision rule: a credential assignment *inside* a
|
|
2902
|
+
// literal — `const dsn = "password=hunter2;host=db"`, `curl -d "password=hunter2"` — was
|
|
2903
|
+
// rejected before it could be considered, and a DSN is a credential in a string. What made the
|
|
2904
|
+
// lookbehind redundant is the shape of `UNQUOTED_LITERAL`: a value has to start with a word
|
|
2905
|
+
// character, so `["awsSecret:", "apiKey:"]` — the list of key names the lookbehind was added
|
|
2906
|
+
// for — cannot match here anyway. `redact.ts` still refuses a match whose separator is inside
|
|
2907
|
+
// the literal, which is the case that list actually is.
|
|
2496
2908
|
rule: "credential-assignment-unquoted",
|
|
2497
2909
|
pattern: new RegExp(
|
|
2498
|
-
`${
|
|
2910
|
+
`${UNQUOTED_STRONG_NAME}\\s{0,8}${ASSIGN}\\s{0,8}${NOT_A_LITERAL_VALUE}${UNQUOTED_LITERAL}`,
|
|
2499
2911
|
"i"
|
|
2500
2912
|
),
|
|
2501
2913
|
valueGroup: 1
|
|
@@ -2530,6 +2942,27 @@ var DEFAULT_SECRET_PATTERNS = [
|
|
|
2530
2942
|
pattern: /(?<!["'])(?:api[_-]?key|secret[_-]?key|auth[_-]?token|client[_-]?secret|access[_-]?token|refresh[_-]?token|private[_-]?key|password|passwd|pwd)["']?\s*(?<![=!<>])[:=](?!=)\s*["']([^"'\s]{8,256})["']/i,
|
|
2531
2943
|
valueGroup: 1
|
|
2532
2944
|
},
|
|
2945
|
+
{
|
|
2946
|
+
// `${VAR:-fallback}` — a default value in a shell script, a `.env`, docker-compose or a
|
|
2947
|
+
// GitHub Actions workflow.
|
|
2948
|
+
//
|
|
2949
|
+
// This shape defeated every rule above it, and each refusal was individually reasonable:
|
|
2950
|
+
// the quoted rules need a quote after the separator, and the unquoted ones need the value to
|
|
2951
|
+
// begin with a word character — a fallback begins with `$`. So
|
|
2952
|
+
// `POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-MyProdPassword123!}` was emitted whole, `.env` files
|
|
2953
|
+
// and compose files included, which is where that idiom is the norm.
|
|
2954
|
+
//
|
|
2955
|
+
// The **fallback** is what gets masked, not the whole expansion: the variable name is the
|
|
2956
|
+
// readable context, exactly as `POSTGRES_PASSWORD:` is kept by the rule above. A single
|
|
2957
|
+
// interpolation with no operator (`${DB_PASSWORD}`) still does not match — there is nothing
|
|
2958
|
+
// between the braces to mask, and `isTemplateInterpolation` keeps such a value readable.
|
|
2959
|
+
rule: "interpolation-fallback-secret",
|
|
2960
|
+
pattern: new RegExp(
|
|
2961
|
+
`(?:["']${STRONG_CREDENTIAL_NAME}["']|${NOT_AFTER_QUOTE}${STRONG_CREDENTIAL_NAME})\\s{0,8}${ASSIGN}\\s{0,8}["'\`]?\\$\\{[A-Za-z_][A-Za-z0-9_]*:?[-=?+]([^}"'\`\\r\\n]{2,512})\\}["'\`]?`,
|
|
2962
|
+
"i"
|
|
2963
|
+
),
|
|
2964
|
+
valueGroup: 1
|
|
2965
|
+
},
|
|
2533
2966
|
{
|
|
2534
2967
|
// Covers `.env` / shell assignment syntax, which never quotes its values and
|
|
2535
2968
|
// was therefore invisible to the rule above. Anchored to the line start and
|
|
@@ -2557,8 +2990,8 @@ var DEFAULT_SECRET_PATTERNS = [
|
|
|
2557
2990
|
valueGroup: 1
|
|
2558
2991
|
}
|
|
2559
2992
|
];
|
|
2560
|
-
var PEM_BLOCK_PATTERN = /-----BEGIN (?:[A-Z0-9]+ )*PRIVATE KEY(?: BLOCK)?-----[\s\S]*?-----END (?:[A-Z0-9]+ )*PRIVATE KEY(?: BLOCK)?-----/g;
|
|
2561
|
-
var PEM_UNTERMINATED_PATTERN = /-----BEGIN (?:[A-Z0-9]+ )*PRIVATE KEY(?: BLOCK)?-----(?:\r
|
|
2993
|
+
var PEM_BLOCK_PATTERN = /-----BEGIN (?:[A-Z0-9]+ )*PRIVATE KEY(?: BLOCK)?-----(?:(?!-----BEGIN)[\s\S])*?-----END (?:[A-Z0-9]+ )*PRIVATE KEY(?: BLOCK)?-----/g;
|
|
2994
|
+
var PEM_UNTERMINATED_PATTERN = /-----BEGIN (?:[A-Z0-9]+ )*PRIVATE KEY(?: BLOCK)?-----(?:\r\n|\n|\r)(?:(?:[ \t]*(?:Proc-Type|DEK-Info|Comment|Version):[^\r\n]*|[ \t]*)(?:\r\n|\n|\r))*(?:[ \t]*[A-Za-z0-9+/=]{8,}[ \t]*(?:\r\n|\n|\r|$))+/g;
|
|
2562
2995
|
|
|
2563
2996
|
// src/security/redact.ts
|
|
2564
2997
|
var REDACTION_MARKER = "[REDACTED: potential secret-like value omitted from snapshot]";
|
|
@@ -2652,6 +3085,20 @@ function toGlobal(rule) {
|
|
|
2652
3085
|
return new RegExp(rule.pattern.source, [...flags].join(""));
|
|
2653
3086
|
}
|
|
2654
3087
|
var LINE_BREAK = /(\r\n|\n|\r)/;
|
|
3088
|
+
var LINE_TERMINATOR = /\r\n|\n|\r/g;
|
|
3089
|
+
var COMPILED_RULES = /* @__PURE__ */ new WeakMap();
|
|
3090
|
+
function compiledRuleFor(rule) {
|
|
3091
|
+
let compiled = COMPILED_RULES.get(rule);
|
|
3092
|
+
if (compiled === void 0) {
|
|
3093
|
+
compiled = {
|
|
3094
|
+
rule: rule.rule,
|
|
3095
|
+
pattern: toGlobal(rule),
|
|
3096
|
+
...rule.valueGroup === void 0 ? {} : { valueGroup: rule.valueGroup }
|
|
3097
|
+
};
|
|
3098
|
+
COMPILED_RULES.set(rule, compiled);
|
|
3099
|
+
}
|
|
3100
|
+
return compiled;
|
|
3101
|
+
}
|
|
2655
3102
|
function scanAndRedactSecrets(content, rules, filePath) {
|
|
2656
3103
|
const findings = [];
|
|
2657
3104
|
let redacted = content;
|
|
@@ -2663,18 +3110,15 @@ function scanAndRedactSecrets(content, rules, filePath) {
|
|
|
2663
3110
|
blockPattern.lastIndex += 1;
|
|
2664
3111
|
continue;
|
|
2665
3112
|
}
|
|
2666
|
-
const
|
|
2667
|
-
const
|
|
3113
|
+
const before = redacted.slice(0, match.index);
|
|
3114
|
+
const line = (before.match(LINE_TERMINATOR) ?? []).length + 1;
|
|
3115
|
+
const newlines = (match[0].match(LINE_TERMINATOR) ?? []).length;
|
|
2668
3116
|
findings.push({ rules: ["private-key-block"], line, path: filePath });
|
|
2669
|
-
redacted =
|
|
3117
|
+
redacted = before + PEM_REDACTION_MARKER + "\n".repeat(newlines) + redacted.slice(match.index + match[0].length);
|
|
2670
3118
|
blockPattern.lastIndex = match.index + PEM_REDACTION_MARKER.length + newlines;
|
|
2671
3119
|
}
|
|
2672
3120
|
}
|
|
2673
|
-
const compiled = rules.map(
|
|
2674
|
-
rule: rule.rule,
|
|
2675
|
-
pattern: toGlobal(rule),
|
|
2676
|
-
...rule.valueGroup === void 0 ? {} : { valueGroup: rule.valueGroup }
|
|
2677
|
-
}));
|
|
3121
|
+
const compiled = rules.map(compiledRuleFor);
|
|
2678
3122
|
const segments = redacted.split(LINE_BREAK);
|
|
2679
3123
|
let lineNumber = 1;
|
|
2680
3124
|
for (let index = 0; index < segments.length; index += 2) {
|
|
@@ -2692,8 +3136,10 @@ function scanAndRedactSecrets(content, rules, filePath) {
|
|
|
2692
3136
|
}
|
|
2693
3137
|
const group = valueGroup === void 0 ? void 0 : match.indices?.[valueGroup];
|
|
2694
3138
|
if (group && isSelfNamingConstant(line, group[0], group[1])) continue;
|
|
2695
|
-
if (valueGroup !== void 0 && startsInsideStringLiteral(line, match.index))
|
|
2696
|
-
|
|
3139
|
+
if (valueGroup !== void 0 && startsInsideStringLiteral(line, match.index, group?.[0])) {
|
|
3140
|
+
continue;
|
|
3141
|
+
}
|
|
3142
|
+
if (group && isUnquotedValue(line, group[0]) && isDeclaredBinding(line, match.index, filePath)) {
|
|
2697
3143
|
continue;
|
|
2698
3144
|
}
|
|
2699
3145
|
if (group && isTemplateInterpolation(line, group[0], group[1])) continue;
|
|
@@ -2726,14 +3172,20 @@ function expandOverToken(line, start, end) {
|
|
|
2726
3172
|
while (right < line.length && TOKEN_CHARS_RIGHT.test(line[right])) right += 1;
|
|
2727
3173
|
return { start: left, end: right };
|
|
2728
3174
|
}
|
|
2729
|
-
function startsInsideStringLiteral(line, matchIndex) {
|
|
3175
|
+
function startsInsideStringLiteral(line, matchIndex, valueStart) {
|
|
2730
3176
|
let runStart = matchIndex;
|
|
2731
3177
|
while (runStart > 0 && /[A-Za-z0-9_-]/.test(line[runStart - 1])) runStart -= 1;
|
|
2732
3178
|
const before = runStart > 0 ? line[runStart - 1] : "";
|
|
2733
|
-
|
|
3179
|
+
if (before !== '"' && before !== "'" && before !== "`") return false;
|
|
3180
|
+
if (valueStart === void 0) return true;
|
|
3181
|
+
return /["'`]/.test(line.slice(matchIndex, valueStart));
|
|
2734
3182
|
}
|
|
2735
3183
|
function isTemplateInterpolation(line, start, end) {
|
|
2736
|
-
|
|
3184
|
+
if (line[start] !== "$" || line[start + 1] !== "{" || line[end - 1] !== "}") {
|
|
3185
|
+
return false;
|
|
3186
|
+
}
|
|
3187
|
+
const inner = line.slice(start + 2, end - 1).trim();
|
|
3188
|
+
return /^[A-Za-z_$][A-Za-z0-9_$.()[\]]*$/.test(inner);
|
|
2737
3189
|
}
|
|
2738
3190
|
function isSelfNamingConstant(line, start, end) {
|
|
2739
3191
|
const value = line.slice(start, end);
|
|
@@ -2758,9 +3210,19 @@ function isUnquotedValue(line, start) {
|
|
|
2758
3210
|
const before = start > 0 ? line[start - 1] : "";
|
|
2759
3211
|
return before !== '"' && before !== "'" && before !== "`";
|
|
2760
3212
|
}
|
|
2761
|
-
var DECLARATION_KEYWORDS = /\b(?:const|let|var|readonly|public|private|protected|static|
|
|
3213
|
+
var DECLARATION_KEYWORDS = /\b(?:const|let|var|readonly|public|private|protected|static|using)\s+$/;
|
|
3214
|
+
var ASSIGNMENT_FILE = /\.(?:sh|bash|zsh|ksh|fish|env|ya?ml|toml|ini|conf|cfg|properties|tf|tfvars|service)$/i;
|
|
3215
|
+
var ASSIGNMENT_BASENAME = /^(?:\.env(?:\.[^/]*)?|\.envrc|\.profile|\.bashrc|\.zshrc|\.bash_profile)$/i;
|
|
3216
|
+
function isAssignmentFile(filePath) {
|
|
3217
|
+
if (filePath === void 0) return false;
|
|
3218
|
+
const normalised = filePath.replace(/\\/g, "/");
|
|
3219
|
+
if (ASSIGNMENT_FILE.test(normalised)) return true;
|
|
3220
|
+
const base = normalised.slice(normalised.lastIndexOf("/") + 1);
|
|
3221
|
+
return ASSIGNMENT_BASENAME.test(base);
|
|
3222
|
+
}
|
|
2762
3223
|
var DECLARATION_WINDOW = 40;
|
|
2763
|
-
function isDeclaredBinding(line, matchIndex) {
|
|
3224
|
+
function isDeclaredBinding(line, matchIndex, filePath) {
|
|
3225
|
+
if (isAssignmentFile(filePath)) return false;
|
|
2764
3226
|
let runStart = matchIndex;
|
|
2765
3227
|
while (runStart > 0 && /[A-Za-z0-9_$]/.test(line[runStart - 1])) runStart -= 1;
|
|
2766
3228
|
const from = Math.max(0, runStart - DECLARATION_WINDOW);
|
|
@@ -2802,6 +3264,15 @@ function readLinkTarget(linkPath, fallback) {
|
|
|
2802
3264
|
return fallback;
|
|
2803
3265
|
}
|
|
2804
3266
|
}
|
|
3267
|
+
function brokenLinkRecord(linkPath, relativePath, rootReal) {
|
|
3268
|
+
const rawTarget = readLinkTarget(linkPath, linkPath);
|
|
3269
|
+
const resolvedFallback = path16.resolve(path16.dirname(linkPath), rawTarget);
|
|
3270
|
+
return isInside(rootReal, resolvedFallback) ? { path: relativePath, reason: "read-error", detail: "broken symlink" } : {
|
|
3271
|
+
path: relativePath,
|
|
3272
|
+
reason: "outside-root",
|
|
3273
|
+
detail: `link target resolves outside the scanned root (target: ${rawTarget}); not followed`
|
|
3274
|
+
};
|
|
3275
|
+
}
|
|
2805
3276
|
function isInside(root, candidate) {
|
|
2806
3277
|
const relative8 = path16.relative(root, candidate);
|
|
2807
3278
|
return relative8 === "" || !relative8.startsWith(`..${path16.sep}`) && relative8 !== ".." && !path16.isAbsolute(relative8);
|
|
@@ -2891,6 +3362,13 @@ function isSensitivePath(relativePath) {
|
|
|
2891
3362
|
return matchesSensitiveList(posixPath, base);
|
|
2892
3363
|
}
|
|
2893
3364
|
var GLOBAL_PATTERNS = /* @__PURE__ */ new WeakMap();
|
|
3365
|
+
var GLOB_REGEX_CACHE = /* @__PURE__ */ new Map();
|
|
3366
|
+
var GITIGNORE_REGEX_CACHE = /* @__PURE__ */ new Map();
|
|
3367
|
+
var REGEX_CACHE_LIMIT = 4096;
|
|
3368
|
+
function rememberCompiled(cache, key, value) {
|
|
3369
|
+
if (cache.size >= REGEX_CACHE_LIMIT) cache.clear();
|
|
3370
|
+
cache.set(key, value);
|
|
3371
|
+
}
|
|
2894
3372
|
function globalPattern(pattern) {
|
|
2895
3373
|
let compiled = GLOBAL_PATTERNS.get(pattern);
|
|
2896
3374
|
if (compiled === void 0) {
|
|
@@ -2901,8 +3379,8 @@ function globalPattern(pattern) {
|
|
|
2901
3379
|
}
|
|
2902
3380
|
var DEFAULT_PATH_RULES = [...DEFAULT_SECRET_PATTERNS];
|
|
2903
3381
|
function pathScanRules(extraRules) {
|
|
2904
|
-
if (extraRules === void 0
|
|
2905
|
-
return
|
|
3382
|
+
if (extraRules === void 0) return DEFAULT_PATH_RULES;
|
|
3383
|
+
return extraRules;
|
|
2906
3384
|
}
|
|
2907
3385
|
function findSecretInPath(relativePath, extraRules) {
|
|
2908
3386
|
const posixPath = relativePath.replace(/\\/g, "/");
|
|
@@ -2951,11 +3429,22 @@ function maskSkipPaths(records, extraRules) {
|
|
|
2951
3429
|
});
|
|
2952
3430
|
}
|
|
2953
3431
|
function globToRegExp(glob) {
|
|
3432
|
+
const cached = GLOB_REGEX_CACHE.get(glob);
|
|
3433
|
+
if (cached !== void 0) return cached;
|
|
2954
3434
|
const normalized = glob.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/+$/, "");
|
|
2955
3435
|
const anchored = normalized.startsWith("/");
|
|
2956
3436
|
const body = anchored ? normalized.slice(1) : normalized;
|
|
2957
3437
|
const prefix = anchored || body.includes("/") ? "^" : "(?:^|.*/)";
|
|
2958
|
-
|
|
3438
|
+
let compiled;
|
|
3439
|
+
try {
|
|
3440
|
+
compiled = new RegExp(`${prefix}${globBodyToRegExp(body, true)}(?:/.*)?$`);
|
|
3441
|
+
} catch (error) {
|
|
3442
|
+
throw new Error(
|
|
3443
|
+
`"${glob}" is not a valid glob: ${error.message}. A character class must be ascending, as in "[a-z]".`
|
|
3444
|
+
);
|
|
3445
|
+
}
|
|
3446
|
+
rememberCompiled(GLOB_REGEX_CACHE, glob, compiled);
|
|
3447
|
+
return compiled;
|
|
2959
3448
|
}
|
|
2960
3449
|
function globBodyToRegExp(body, allowBraces) {
|
|
2961
3450
|
let out = "";
|
|
@@ -3067,6 +3556,14 @@ function escapeRegExpChar(character) {
|
|
|
3067
3556
|
return /[\\^$.*+?()[\]{}|/]/.test(character) ? `\\${character}` : character;
|
|
3068
3557
|
}
|
|
3069
3558
|
function matchGitignorePattern(target, pattern) {
|
|
3559
|
+
let compiled = GITIGNORE_REGEX_CACHE.get(pattern);
|
|
3560
|
+
if (compiled === void 0) {
|
|
3561
|
+
compiled = compileGitignorePattern(pattern);
|
|
3562
|
+
rememberCompiled(GITIGNORE_REGEX_CACHE, pattern, compiled);
|
|
3563
|
+
}
|
|
3564
|
+
return compiled.test(target);
|
|
3565
|
+
}
|
|
3566
|
+
function compileGitignorePattern(pattern) {
|
|
3070
3567
|
const anchored = pattern.startsWith("/");
|
|
3071
3568
|
const body = anchored ? pattern.slice(1) : pattern;
|
|
3072
3569
|
let source = "";
|
|
@@ -3115,7 +3612,11 @@ function matchGitignorePattern(target, pattern) {
|
|
|
3115
3612
|
source += escapeRegExpChar(character);
|
|
3116
3613
|
}
|
|
3117
3614
|
const prefix = anchored || body.includes("/") ? "^" : "(?:^|.*/)";
|
|
3118
|
-
|
|
3615
|
+
try {
|
|
3616
|
+
return new RegExp(`${prefix}${source}(?:/.*)?$`);
|
|
3617
|
+
} catch {
|
|
3618
|
+
return /$^/;
|
|
3619
|
+
}
|
|
3119
3620
|
}
|
|
3120
3621
|
function matchesGitignoreRule(rule, scoped, isDirectory) {
|
|
3121
3622
|
if (!rule.dirOnly) return matchGitignorePattern(scoped, rule.pattern);
|
|
@@ -3145,14 +3646,31 @@ function gitignoreVerdict(layers, relativePath, isDirectory) {
|
|
|
3145
3646
|
}
|
|
3146
3647
|
return verdict;
|
|
3147
3648
|
}
|
|
3148
|
-
|
|
3649
|
+
var DEFAULT_TOOL_DIR_NAMES = ["snapshot"];
|
|
3650
|
+
function toolDirNamesFor(toolOwnedPaths) {
|
|
3651
|
+
const names = new Set(DEFAULT_TOOL_DIR_NAMES);
|
|
3652
|
+
for (const entry of toolOwnedPaths) {
|
|
3653
|
+
const base = entry.replace(/\\/g, "/").replace(/\/+$/, "").split("/").pop();
|
|
3654
|
+
if (base !== void 0 && base.length > 0) names.add(base);
|
|
3655
|
+
}
|
|
3656
|
+
return [...names];
|
|
3657
|
+
}
|
|
3658
|
+
function isToolLeftoverName(name, dirNames) {
|
|
3659
|
+
for (const dirName of dirNames) {
|
|
3660
|
+
if (name.startsWith(`${dirName}.backup-`) || name.startsWith(`${dirName}.partial-`)) {
|
|
3661
|
+
return true;
|
|
3662
|
+
}
|
|
3663
|
+
}
|
|
3664
|
+
return false;
|
|
3665
|
+
}
|
|
3666
|
+
function defaultIgnoreReason(relativePath, toolDirNames = DEFAULT_TOOL_DIR_NAMES) {
|
|
3149
3667
|
const posixPath = relativePath.replace(/\\/g, "/");
|
|
3150
3668
|
const parts = posixPath.split("/");
|
|
3151
3669
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
3152
3670
|
if (DEFAULT_IGNORED_DIRS.includes(parts[i])) {
|
|
3153
3671
|
return `built-in ignore: directory "${parts[i]}"`;
|
|
3154
3672
|
}
|
|
3155
|
-
if (parts[i]
|
|
3673
|
+
if (isToolLeftoverName(parts[i], toolDirNames)) {
|
|
3156
3674
|
return `built-in ignore: leftover snapshot backup "${parts[i]}"`;
|
|
3157
3675
|
}
|
|
3158
3676
|
}
|
|
@@ -3160,6 +3678,9 @@ function defaultIgnoreReason(relativePath) {
|
|
|
3160
3678
|
if (DEFAULT_IGNORED_FILES.includes(base)) {
|
|
3161
3679
|
return `built-in ignore: filename "${base}"`;
|
|
3162
3680
|
}
|
|
3681
|
+
if (isToolLeftoverName(base, toolDirNames)) {
|
|
3682
|
+
return `built-in ignore: leftover snapshot backup "${base}"`;
|
|
3683
|
+
}
|
|
3163
3684
|
return null;
|
|
3164
3685
|
}
|
|
3165
3686
|
function listIgnoredPaths(cwd) {
|
|
@@ -3228,41 +3749,72 @@ function dropLinkedDirectories(cwd, candidates, skipped) {
|
|
|
3228
3749
|
}
|
|
3229
3750
|
const reported = /* @__PURE__ */ new Set();
|
|
3230
3751
|
const kept = [];
|
|
3752
|
+
const report = (linkedPath, record) => {
|
|
3753
|
+
if (reported.has(linkedPath)) return;
|
|
3754
|
+
reported.add(linkedPath);
|
|
3755
|
+
skipped.push(record);
|
|
3756
|
+
};
|
|
3231
3757
|
for (const candidate of candidates) {
|
|
3232
3758
|
const parts = candidate.split("/");
|
|
3233
|
-
let
|
|
3759
|
+
let linkedPath = null;
|
|
3234
3760
|
for (let depth = 1; depth <= parts.length; depth += 1) {
|
|
3235
3761
|
const prefix = parts.slice(0, depth).join("/");
|
|
3236
3762
|
if (isLink(path16.join(cwd, prefix))) {
|
|
3237
|
-
|
|
3763
|
+
linkedPath = prefix;
|
|
3238
3764
|
break;
|
|
3239
3765
|
}
|
|
3240
3766
|
}
|
|
3241
|
-
if (
|
|
3767
|
+
if (linkedPath === null) {
|
|
3242
3768
|
kept.push(candidate);
|
|
3243
3769
|
continue;
|
|
3244
3770
|
}
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
let targetReal = absolute;
|
|
3771
|
+
const absolute = path16.join(cwd, linkedPath);
|
|
3772
|
+
const rawTarget = readLinkTarget(absolute, absolute);
|
|
3773
|
+
let targetReal = null;
|
|
3249
3774
|
try {
|
|
3250
3775
|
targetReal = fs14.realpathSync(absolute);
|
|
3251
3776
|
} catch {
|
|
3777
|
+
targetReal = null;
|
|
3252
3778
|
}
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
} : {
|
|
3261
|
-
path: linkedDir,
|
|
3779
|
+
if (targetReal === null) {
|
|
3780
|
+
report(linkedPath, brokenLinkRecord(absolute, linkedPath, rootReal));
|
|
3781
|
+
continue;
|
|
3782
|
+
}
|
|
3783
|
+
if (!isInside(rootReal, targetReal)) {
|
|
3784
|
+
report(linkedPath, {
|
|
3785
|
+
path: linkedPath,
|
|
3262
3786
|
reason: "outside-root",
|
|
3263
|
-
detail: `link target resolves outside the scanned root (target: ${
|
|
3264
|
-
}
|
|
3265
|
-
|
|
3787
|
+
detail: `link target resolves outside the scanned root (target: ${rawTarget}); not followed`
|
|
3788
|
+
});
|
|
3789
|
+
continue;
|
|
3790
|
+
}
|
|
3791
|
+
let stats = null;
|
|
3792
|
+
try {
|
|
3793
|
+
stats = fs14.statSync(absolute);
|
|
3794
|
+
} catch {
|
|
3795
|
+
stats = null;
|
|
3796
|
+
}
|
|
3797
|
+
if (stats === null) {
|
|
3798
|
+
report(linkedPath, { path: linkedPath, reason: "read-error", detail: "unreadable symlink" });
|
|
3799
|
+
continue;
|
|
3800
|
+
}
|
|
3801
|
+
if (linkedPath !== candidate || stats.isDirectory()) {
|
|
3802
|
+
report(linkedPath, {
|
|
3803
|
+
path: linkedPath,
|
|
3804
|
+
reason: "symlink",
|
|
3805
|
+
detail: `directory link not followed (target: ${rawTarget})`
|
|
3806
|
+
});
|
|
3807
|
+
continue;
|
|
3808
|
+
}
|
|
3809
|
+
if (!stats.isFile()) {
|
|
3810
|
+
report(linkedPath, {
|
|
3811
|
+
path: linkedPath,
|
|
3812
|
+
reason: "symlink",
|
|
3813
|
+
detail: `link to neither a file nor a directory (target: ${rawTarget})`
|
|
3814
|
+
});
|
|
3815
|
+
continue;
|
|
3816
|
+
}
|
|
3817
|
+
kept.push(candidate);
|
|
3266
3818
|
}
|
|
3267
3819
|
return kept;
|
|
3268
3820
|
}
|
|
@@ -3309,6 +3861,7 @@ function collectFiles(cwd, options = {}) {
|
|
|
3309
3861
|
(options.toolOwnedPaths ?? []).map((entry) => entry.replace(/\\/g, "/"))
|
|
3310
3862
|
);
|
|
3311
3863
|
const isToolOwned = makeToolOwnedDecider(cwd, [...toolOwnedPaths]);
|
|
3864
|
+
const toolDirNames = toolDirNamesFor(toolOwnedPaths);
|
|
3312
3865
|
let candidates;
|
|
3313
3866
|
const skipped = [];
|
|
3314
3867
|
const discovery = options.discovery ?? "auto";
|
|
@@ -3337,7 +3890,7 @@ function collectFiles(cwd, options = {}) {
|
|
|
3337
3890
|
}
|
|
3338
3891
|
const files = candidates.filter((file) => {
|
|
3339
3892
|
if (isToolOwned(file)) return false;
|
|
3340
|
-
const ignored = defaultIgnoreReason(file);
|
|
3893
|
+
const ignored = defaultIgnoreReason(file, toolDirNames);
|
|
3341
3894
|
if (ignored) {
|
|
3342
3895
|
skipped.push({ path: file, reason: "default-ignore", detail: ignored });
|
|
3343
3896
|
return false;
|
|
@@ -3392,6 +3945,7 @@ function rootRealFor(root) {
|
|
|
3392
3945
|
function walkDirectory(dir, baseDir, rootReal, visited, reservedPaths = /* @__PURE__ */ new Set(), parentGitignoreLayers = []) {
|
|
3393
3946
|
const results = { files: [], skipped: [] };
|
|
3394
3947
|
const stack = [];
|
|
3948
|
+
const toolDirNames = toolDirNamesFor(reservedPaths);
|
|
3395
3949
|
const relativeToBase = (target) => path16.relative(baseDir, target).replace(/\\/g, "/") || ".";
|
|
3396
3950
|
const enter = (current, layers) => {
|
|
3397
3951
|
if (visited.has(current)) return false;
|
|
@@ -3436,11 +3990,11 @@ function walkDirectory(dir, baseDir, rootReal, visited, reservedPaths = /* @__PU
|
|
|
3436
3990
|
});
|
|
3437
3991
|
continue;
|
|
3438
3992
|
}
|
|
3439
|
-
if (DEFAULT_IGNORED_FILES.includes(entry.name) || entry.name
|
|
3993
|
+
if (DEFAULT_IGNORED_FILES.includes(entry.name) || isToolLeftoverName(entry.name, toolDirNames)) {
|
|
3440
3994
|
results.skipped.push({
|
|
3441
3995
|
path: relPath,
|
|
3442
3996
|
reason: "default-ignore",
|
|
3443
|
-
detail: "built-in ignore: filename"
|
|
3997
|
+
detail: isToolLeftoverName(entry.name, toolDirNames) ? `built-in ignore: leftover snapshot backup "${entry.name}" (not descended)` : "built-in ignore: filename"
|
|
3444
3998
|
});
|
|
3445
3999
|
continue;
|
|
3446
4000
|
}
|
|
@@ -3461,7 +4015,7 @@ function walkDirectory(dir, baseDir, rootReal, visited, reservedPaths = /* @__PU
|
|
|
3461
4015
|
try {
|
|
3462
4016
|
targetReal = fs14.realpathSync(fullPath);
|
|
3463
4017
|
} catch {
|
|
3464
|
-
results.skipped.push(
|
|
4018
|
+
results.skipped.push(brokenLinkRecord(fullPath, relPath, rootReal));
|
|
3465
4019
|
continue;
|
|
3466
4020
|
}
|
|
3467
4021
|
let stats;
|
|
@@ -3549,8 +4103,9 @@ function findConfigFile(cwd) {
|
|
|
3549
4103
|
const pkgJsonPath = path16.join(cwd, "package.json");
|
|
3550
4104
|
if (!fs14.existsSync(pkgJsonPath)) return null;
|
|
3551
4105
|
try {
|
|
3552
|
-
const parsed = JSON.parse(fs14.readFileSync(pkgJsonPath, "utf8"));
|
|
3553
|
-
|
|
4106
|
+
const parsed = JSON.parse(stripJsonComments2(fs14.readFileSync(pkgJsonPath, "utf8")));
|
|
4107
|
+
const snapshot = parsed?.snapshot;
|
|
4108
|
+
return typeof snapshot === "object" && snapshot !== null && !Array.isArray(snapshot) ? pkgJsonPath : null;
|
|
3554
4109
|
} catch {
|
|
3555
4110
|
return null;
|
|
3556
4111
|
}
|
|
@@ -3600,15 +4155,19 @@ function normalizeBudgets(budgets, maxPartBytes) {
|
|
|
3600
4155
|
}
|
|
3601
4156
|
function normalizeResolvedConfig(config) {
|
|
3602
4157
|
const discovery = isDiscoveryMode(config.discovery) ? config.discovery : "auto";
|
|
4158
|
+
const monolithWanted = config.includeMonolith === true || config.artifacts?.monolith === true;
|
|
4159
|
+
const artifacts = resolveArtifacts(
|
|
4160
|
+
config.artifacts,
|
|
4161
|
+
void 0,
|
|
4162
|
+
monolithWanted,
|
|
4163
|
+
monolithWanted
|
|
4164
|
+
);
|
|
3603
4165
|
return markResolvedConfig({
|
|
3604
4166
|
...config,
|
|
3605
4167
|
discovery,
|
|
4168
|
+
includeMonolith: artifacts.monolith,
|
|
3606
4169
|
secretRules: compileSecretRules(config.secretRules),
|
|
3607
|
-
artifacts
|
|
3608
|
-
config.artifacts,
|
|
3609
|
-
void 0,
|
|
3610
|
-
config.includeMonolith === true
|
|
3611
|
-
),
|
|
4170
|
+
artifacts,
|
|
3612
4171
|
include: resolveInclude(config.include),
|
|
3613
4172
|
preset: typeof config.preset === "string" ? config.preset : "generic",
|
|
3614
4173
|
presetExclude: [
|
|
@@ -3687,9 +4246,9 @@ function stripJsonComments2(raw) {
|
|
|
3687
4246
|
}
|
|
3688
4247
|
out += char;
|
|
3689
4248
|
}
|
|
3690
|
-
return
|
|
4249
|
+
return stripTrailingCommas2(out);
|
|
3691
4250
|
}
|
|
3692
|
-
function
|
|
4251
|
+
function stripTrailingCommas2(text2) {
|
|
3693
4252
|
let out = "";
|
|
3694
4253
|
let inString = false;
|
|
3695
4254
|
let escaped = false;
|
|
@@ -3817,11 +4376,21 @@ async function importConfigModule(fullPath) {
|
|
|
3817
4376
|
url.searchParams.set("codebaseSnapshotLoad", String(moduleConfigLoadToken));
|
|
3818
4377
|
return await import(url.href);
|
|
3819
4378
|
}
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
4379
|
+
function attachConfigDir(config, filePath) {
|
|
4380
|
+
if (typeof config === "object" && config !== null) {
|
|
4381
|
+
Object.defineProperty(config, CONFIG_DIR_SYMBOL, {
|
|
4382
|
+
value: path16.dirname(filePath),
|
|
4383
|
+
enumerable: false,
|
|
4384
|
+
configurable: true
|
|
4385
|
+
});
|
|
4386
|
+
}
|
|
4387
|
+
return config;
|
|
4388
|
+
}
|
|
4389
|
+
async function loadConfigFileDetailed(cwd) {
|
|
4390
|
+
const warnings = [];
|
|
4391
|
+
const errors = [];
|
|
4392
|
+
for (const filename of CONFIG_FILENAMES) {
|
|
4393
|
+
const fullPath = path16.join(cwd, filename);
|
|
3825
4394
|
if (!fs14.existsSync(fullPath)) continue;
|
|
3826
4395
|
if (filename.endsWith(".json") || filename === ".snapshotrc") {
|
|
3827
4396
|
try {
|
|
@@ -3832,7 +4401,7 @@ async function loadConfigFileDetailed(cwd) {
|
|
|
3832
4401
|
);
|
|
3833
4402
|
}
|
|
3834
4403
|
assertKnownConfigKeys(config, fullPath);
|
|
3835
|
-
return { config, file: fullPath, warnings, errors };
|
|
4404
|
+
return { config: attachConfigDir(config, fullPath), file: fullPath, warnings, errors };
|
|
3836
4405
|
} catch (err) {
|
|
3837
4406
|
const raw = err.message;
|
|
3838
4407
|
const message = raw.includes(fullPath) ? raw : `${fullPath}: ${raw}`;
|
|
@@ -3851,7 +4420,7 @@ async function loadConfigFileDetailed(cwd) {
|
|
|
3851
4420
|
const imported = await importConfigModule(fullPath);
|
|
3852
4421
|
const hasDefault = Object.prototype.hasOwnProperty.call(imported, "default");
|
|
3853
4422
|
const config = readConfigExport(hasDefault ? imported.default : imported, fullPath);
|
|
3854
|
-
return { config, file: fullPath, warnings, errors };
|
|
4423
|
+
return { config: attachConfigDir(config, fullPath), file: fullPath, warnings, errors };
|
|
3855
4424
|
} catch (err) {
|
|
3856
4425
|
const message = `Could not load ${fullPath}: ${err.message}`;
|
|
3857
4426
|
warnings.push(message);
|
|
@@ -3869,7 +4438,12 @@ async function loadConfigFileDetailed(cwd) {
|
|
|
3869
4438
|
snapshotKey,
|
|
3870
4439
|
`${pkgJsonPath} (the "snapshot" key)`
|
|
3871
4440
|
);
|
|
3872
|
-
return {
|
|
4441
|
+
return {
|
|
4442
|
+
config: attachConfigDir(snapshotKey, pkgJsonPath),
|
|
4443
|
+
file: pkgJsonPath,
|
|
4444
|
+
warnings,
|
|
4445
|
+
errors
|
|
4446
|
+
};
|
|
3873
4447
|
}
|
|
3874
4448
|
if (snapshotKey !== void 0) {
|
|
3875
4449
|
const kind = Array.isArray(snapshotKey) ? "array" : typeof snapshotKey;
|
|
@@ -3887,13 +4461,6 @@ async function loadConfigFileDetailed(cwd) {
|
|
|
3887
4461
|
}
|
|
3888
4462
|
async function loadConfigFile(cwd) {
|
|
3889
4463
|
const loaded = await loadConfigFileDetailed(cwd);
|
|
3890
|
-
if (loaded.config && loaded.file && typeof loaded.config === "object") {
|
|
3891
|
-
Object.defineProperty(loaded.config, CONFIG_DIR_SYMBOL, {
|
|
3892
|
-
value: path16.dirname(loaded.file),
|
|
3893
|
-
enumerable: false,
|
|
3894
|
-
configurable: true
|
|
3895
|
-
});
|
|
3896
|
-
}
|
|
3897
4464
|
if (loaded.errors.length > 0) {
|
|
3898
4465
|
throw new Error(`The config file could not be read: ${loaded.errors.join("; ")}`);
|
|
3899
4466
|
}
|
|
@@ -3922,7 +4489,7 @@ var DEFAULT_ARTIFACTS = {
|
|
|
3922
4489
|
function isArtifactName(value) {
|
|
3923
4490
|
return ARTIFACT_NAMES.includes(value);
|
|
3924
4491
|
}
|
|
3925
|
-
function resolveArtifacts(requested, skipped, includeMonolith, monolithOverride) {
|
|
4492
|
+
function resolveArtifacts(requested, skipped, includeMonolith, monolithOverride, only) {
|
|
3926
4493
|
const artifacts = {
|
|
3927
4494
|
...DEFAULT_ARTIFACTS,
|
|
3928
4495
|
// `includeMonolith` predates this selection and stays the switch for it.
|
|
@@ -3947,6 +4514,21 @@ function resolveArtifacts(requested, skipped, includeMonolith, monolithOverride)
|
|
|
3947
4514
|
if (monolithOverride !== void 0) {
|
|
3948
4515
|
artifacts.monolith = monolithOverride;
|
|
3949
4516
|
}
|
|
4517
|
+
if (only !== void 0 && only.length > 0) {
|
|
4518
|
+
const keep = /* @__PURE__ */ new Set();
|
|
4519
|
+
for (const name of only) {
|
|
4520
|
+
if (!isArtifactName(name)) {
|
|
4521
|
+
throw new Error(
|
|
4522
|
+
`Unknown artifact "${name}". Expected one of: ${ARTIFACT_NAMES.join(", ")}.`
|
|
4523
|
+
);
|
|
4524
|
+
}
|
|
4525
|
+
keep.add(name);
|
|
4526
|
+
}
|
|
4527
|
+
for (const name of ARTIFACT_NAMES) {
|
|
4528
|
+
artifacts[name] = keep.has(name);
|
|
4529
|
+
}
|
|
4530
|
+
return artifacts;
|
|
4531
|
+
}
|
|
3950
4532
|
for (const name of skipped ?? []) {
|
|
3951
4533
|
if (!isArtifactName(name)) {
|
|
3952
4534
|
throw new Error(
|
|
@@ -4247,7 +4829,8 @@ function resolveConfig(userConfig, cliOptions, baseDir) {
|
|
|
4247
4829
|
userConfig?.artifacts,
|
|
4248
4830
|
cliOptions?.skipArtifacts,
|
|
4249
4831
|
includeMonolith,
|
|
4250
|
-
cliMonolith
|
|
4832
|
+
cliMonolith,
|
|
4833
|
+
cliOptions?.onlyArtifacts
|
|
4251
4834
|
);
|
|
4252
4835
|
const explicitMaxPartBytes = cliOptions?.maxPartBytes ?? userConfig?.maxPartBytes;
|
|
4253
4836
|
const maxPartBytes = boundedBytes(
|
|
@@ -4421,7 +5004,18 @@ function configDigest(config) {
|
|
|
4421
5004
|
excludePatterns: config.excludePatterns,
|
|
4422
5005
|
preset: config.preset,
|
|
4423
5006
|
layersMode: config.layersMode,
|
|
4424
|
-
budgets
|
|
5007
|
+
// `budgets.byLayer` is a plain object, and `JSON.stringify` preserves insertion order — so
|
|
5008
|
+
// `{ docs, api }` and `{ api, docs }` hashed differently despite being the same settings, and
|
|
5009
|
+
// `validate` reported configuration drift for a snapshot nobody had changed. The keys are
|
|
5010
|
+
// canonicalised here; nothing else about the object is order-dependent.
|
|
5011
|
+
budgets: {
|
|
5012
|
+
default: config.budgets.default,
|
|
5013
|
+
byLayer: Object.fromEntries(
|
|
5014
|
+
Object.entries(config.budgets.byLayer).sort(
|
|
5015
|
+
([left], [right]) => left < right ? -1 : left > right ? 1 : 0
|
|
5016
|
+
)
|
|
5017
|
+
)
|
|
5018
|
+
},
|
|
4425
5019
|
// The matcher is what makes a layer a layer. Hashing only the names meant that
|
|
4426
5020
|
// moving `{ name: "api", match: "src/api/**" }` to `"src/v2/**"` produced the same
|
|
4427
5021
|
// digest, so `validate` reported "valid and up to date" for a snapshot whose files
|
|
@@ -4705,9 +5299,21 @@ function generateSarifReport(manifest, findings, rules, toolVersion) {
|
|
|
4705
5299
|
}
|
|
4706
5300
|
|
|
4707
5301
|
// src/partitioner/preview.ts
|
|
5302
|
+
var LINE_ENDINGS = /\r\n|\r/g;
|
|
5303
|
+
var normaliseLineEndings = (content) => content.replace(LINE_ENDINGS, "\n");
|
|
4708
5304
|
function countLines(content) {
|
|
4709
5305
|
if (content.length === 0) return 0;
|
|
4710
|
-
|
|
5306
|
+
let breaks = 0;
|
|
5307
|
+
for (let index = 0; index < content.length; index += 1) {
|
|
5308
|
+
const code = content.charCodeAt(index);
|
|
5309
|
+
if (code === 10) {
|
|
5310
|
+
breaks += 1;
|
|
5311
|
+
continue;
|
|
5312
|
+
}
|
|
5313
|
+
if (code === 13 && content.charCodeAt(index + 1) !== 10) breaks += 1;
|
|
5314
|
+
}
|
|
5315
|
+
const last = content.charCodeAt(content.length - 1);
|
|
5316
|
+
return last === 10 || last === 13 ? breaks : breaks + 1;
|
|
4711
5317
|
}
|
|
4712
5318
|
function truncateToBytes(content, maxBytes) {
|
|
4713
5319
|
if (maxBytes <= 0) return { content: "", isTruncated: content.length > 0 };
|
|
@@ -4718,17 +5324,18 @@ function truncateToBytes(content, maxBytes) {
|
|
|
4718
5324
|
return { content: slice, isTruncated: true };
|
|
4719
5325
|
}
|
|
4720
5326
|
function truncatePreview(content, headLines, tailLines) {
|
|
4721
|
-
const
|
|
4722
|
-
const
|
|
5327
|
+
const normalised = normaliseLineEndings(content);
|
|
5328
|
+
const lines = normalised.split("\n");
|
|
5329
|
+
const total = countLines(normalised);
|
|
4723
5330
|
if (total <= headLines + tailLines) {
|
|
4724
5331
|
return {
|
|
4725
|
-
content,
|
|
5332
|
+
content: normalised,
|
|
4726
5333
|
isTruncated: false,
|
|
4727
5334
|
originalLines: total,
|
|
4728
5335
|
includedLines: total
|
|
4729
5336
|
};
|
|
4730
5337
|
}
|
|
4731
|
-
const window =
|
|
5338
|
+
const window = normalised.endsWith("\n") ? lines.slice(0, -1) : lines;
|
|
4732
5339
|
const head = window.slice(0, headLines);
|
|
4733
5340
|
const tail = tailLines > 0 ? window.slice(window.length - tailLines) : [];
|
|
4734
5341
|
const omitted = total - head.length - tail.length;
|
|
@@ -4782,23 +5389,55 @@ function truncateWithBudgetMarker(content, maxBytes, budget, originalLines) {
|
|
|
4782
5389
|
};
|
|
4783
5390
|
}
|
|
4784
5391
|
function partitionFiles(files, maxPartBytes, contentFingerprint) {
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
const
|
|
4789
|
-
|
|
5392
|
+
return partitionFilesForBuckets([{ files, budgetBytes: maxPartBytes }], contentFingerprint);
|
|
5393
|
+
}
|
|
5394
|
+
function partitionFilesForBuckets(buckets, contentFingerprint) {
|
|
5395
|
+
const groups = [];
|
|
5396
|
+
for (const bucket of buckets) {
|
|
5397
|
+
if (bucket.files.length === 0) continue;
|
|
5398
|
+
const budget = Math.max(
|
|
5399
|
+
MIN_SECTION_BUDGET_BYTES + HEADER_RESERVE_BYTES,
|
|
5400
|
+
bucket.budgetBytes
|
|
5401
|
+
);
|
|
5402
|
+
const sectionBudget = budget - HEADER_RESERVE_BYTES;
|
|
5403
|
+
const prepared = bucket.files.map((file) => prepareSection(file, sectionBudget));
|
|
5404
|
+
for (const sections of greedyGroups(prepared, budget)) groups.push({ sections, budget });
|
|
5405
|
+
}
|
|
5406
|
+
if (groups.length === 0) return [];
|
|
5407
|
+
const measurements = /* @__PURE__ */ new Map();
|
|
5408
|
+
const widthOf = (total) => Math.max(3, String(total).length);
|
|
5409
|
+
const measure = (group, index, total) => {
|
|
5410
|
+
const cached = measurements.get(group);
|
|
5411
|
+
const width = widthOf(total);
|
|
5412
|
+
if (cached !== void 0 && cached.index === index && cached.width === width) {
|
|
5413
|
+
return cached.size;
|
|
5414
|
+
}
|
|
5415
|
+
const { content } = assembleGroup(group, index, total, contentFingerprint);
|
|
5416
|
+
const size2 = Buffer.byteLength(content, "utf8");
|
|
5417
|
+
measurements.set(group, { index, width, size: size2 });
|
|
5418
|
+
return size2;
|
|
5419
|
+
};
|
|
4790
5420
|
for (let pass = 0; pass < MAX_SPLIT_PASSES; pass++) {
|
|
4791
|
-
const
|
|
4792
|
-
|
|
4793
|
-
|
|
5421
|
+
const total = groups.length;
|
|
5422
|
+
let oversized = -1;
|
|
5423
|
+
for (let index = 0; index < total; index += 1) {
|
|
5424
|
+
const group2 = groups[index];
|
|
5425
|
+
if (measure(group2, index, total) > group2.budget) {
|
|
5426
|
+
oversized = index;
|
|
5427
|
+
break;
|
|
5428
|
+
}
|
|
5429
|
+
}
|
|
5430
|
+
if (oversized === -1) return renderGroups(groups, contentFingerprint);
|
|
4794
5431
|
const group = groups[oversized];
|
|
4795
|
-
if (group.length > 1) {
|
|
4796
|
-
const moved = group.pop();
|
|
4797
|
-
groups.splice(oversized + 1, 0, [moved]);
|
|
5432
|
+
if (group.sections.length > 1) {
|
|
5433
|
+
const moved = group.sections.pop();
|
|
5434
|
+
groups.splice(oversized + 1, 0, { sections: [moved], budget: group.budget });
|
|
5435
|
+
measurements.delete(group);
|
|
4798
5436
|
continue;
|
|
4799
5437
|
}
|
|
4800
|
-
const [section] = group;
|
|
4801
|
-
|
|
5438
|
+
const [section] = group.sections;
|
|
5439
|
+
if (section === void 0) return renderGroups(groups, contentFingerprint);
|
|
5440
|
+
const overflow = measure(group, oversized, total) - group.budget;
|
|
4802
5441
|
const currentContent = section.file.content ?? "";
|
|
4803
5442
|
const shrinkTo = Math.max(
|
|
4804
5443
|
0,
|
|
@@ -4807,26 +5446,33 @@ function partitionFiles(files, maxPartBytes, contentFingerprint) {
|
|
|
4807
5446
|
const shrunk = truncateWithBudgetMarker(
|
|
4808
5447
|
currentContent,
|
|
4809
5448
|
shrinkTo,
|
|
4810
|
-
budget,
|
|
5449
|
+
group.budget,
|
|
4811
5450
|
section.file.originalLines
|
|
4812
5451
|
);
|
|
4813
5452
|
if (shrunk.isTruncated) {
|
|
4814
5453
|
applyTruncatedContent(section.file, shrunk.content);
|
|
4815
5454
|
section.text = renderFileSection(section.file);
|
|
4816
5455
|
section.byteSize = Buffer.byteLength(section.text, "utf8");
|
|
5456
|
+
measurements.delete(group);
|
|
4817
5457
|
continue;
|
|
4818
5458
|
}
|
|
5459
|
+
const rendered = renderGroups(groups, contentFingerprint);
|
|
4819
5460
|
const marker2 = TRUNCATION_MARKER;
|
|
4820
|
-
assertBudgetHoldsMarker(budget);
|
|
5461
|
+
assertBudgetHoldsMarker(group.budget);
|
|
4821
5462
|
const target = Math.max(
|
|
4822
5463
|
0,
|
|
4823
|
-
budget - Buffer.byteLength(marker2, "utf8") - TOKEN_LINE_SLACK
|
|
5464
|
+
group.budget - Buffer.byteLength(marker2, "utf8") - TOKEN_LINE_SLACK
|
|
4824
5465
|
);
|
|
4825
5466
|
const clipped = truncateToBytes(rendered[oversized].markdown, target);
|
|
4826
|
-
rendered[oversized] = rehashPart(
|
|
5467
|
+
rendered[oversized] = rehashPart(
|
|
5468
|
+
rendered[oversized],
|
|
5469
|
+
clipped.content + marker2
|
|
5470
|
+
);
|
|
4827
5471
|
return rendered;
|
|
4828
5472
|
}
|
|
4829
|
-
return
|
|
5473
|
+
return renderGroups(groups, contentFingerprint).flatMap(
|
|
5474
|
+
(part) => enforceBudget([part], part.metadata.budgetBytes)
|
|
5475
|
+
);
|
|
4830
5476
|
}
|
|
4831
5477
|
var TRUNCATION_MARKER = "\n... [part truncated: cannot fit budget] ...\n";
|
|
4832
5478
|
function assertBudgetHoldsMarker(budget) {
|
|
@@ -4911,7 +5557,7 @@ function renumberParts(parts, contentFingerprint) {
|
|
|
4911
5557
|
(part) => enforceBudget([part], part.metadata.budgetBytes)[0] ?? part
|
|
4912
5558
|
);
|
|
4913
5559
|
}
|
|
4914
|
-
var SECTION_TRUNCATION_NOTICE = "
|
|
5560
|
+
var SECTION_TRUNCATION_NOTICE = "... [section truncated: the fixed metadata and a code fence did not fit the part budget] ...";
|
|
4915
5561
|
var METADATA_VALUE_MAX = 200;
|
|
4916
5562
|
var DISPLAY_PATH_MAX = 300;
|
|
4917
5563
|
function capValue(value, max) {
|
|
@@ -4953,13 +5599,57 @@ function prepareSection(file, sectionBudget) {
|
|
|
4953
5599
|
shrinkBody();
|
|
4954
5600
|
}
|
|
4955
5601
|
if (byteSize > sectionBudget) {
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
5602
|
+
applyTruncatedContent(file, SECTION_TRUNCATION_NOTICE);
|
|
5603
|
+
file.summary = void 0;
|
|
5604
|
+
file.summaryOmitted = true;
|
|
5605
|
+
text2 = renderFileSection(file);
|
|
4959
5606
|
byteSize = Buffer.byteLength(text2, "utf8");
|
|
5607
|
+
if (byteSize > sectionBudget) {
|
|
5608
|
+
text2 = truncateRenderedSection(text2, sectionBudget, fenceForContent(file.content ?? ""));
|
|
5609
|
+
byteSize = Buffer.byteLength(text2, "utf8");
|
|
5610
|
+
}
|
|
4960
5611
|
}
|
|
4961
5612
|
return { file, text: text2, byteSize };
|
|
4962
5613
|
}
|
|
5614
|
+
function truncateRenderedSection(text2, sectionBudget, fence) {
|
|
5615
|
+
const closing = `
|
|
5616
|
+
${SECTION_TRUNCATION_NOTICE}
|
|
5617
|
+
${fence}
|
|
5618
|
+
`;
|
|
5619
|
+
const bodyRoom = sectionBudget - Buffer.byteLength(closing, "utf8");
|
|
5620
|
+
if (bodyRoom > 0) {
|
|
5621
|
+
const cut2 = truncateToBytes(text2, bodyRoom).content;
|
|
5622
|
+
const lastBreak2 = cut2.lastIndexOf("\n");
|
|
5623
|
+
const kept = lastBreak2 >= 0 ? cut2.slice(0, lastBreak2 + 1) : "";
|
|
5624
|
+
if (fenceIsOpen(kept, fence)) return `${kept}${closing}`;
|
|
5625
|
+
}
|
|
5626
|
+
const probe = text2.slice(0, 512);
|
|
5627
|
+
const emptyFence = fenceForText(probe.slice(0, Math.max(0, probe.lastIndexOf("\n") + 1)), 4);
|
|
5628
|
+
const block = `
|
|
5629
|
+
${emptyFence}
|
|
5630
|
+
${SECTION_TRUNCATION_NOTICE}
|
|
5631
|
+
${emptyFence}
|
|
5632
|
+
`;
|
|
5633
|
+
const prefixRoom = sectionBudget - Buffer.byteLength(block, "utf8");
|
|
5634
|
+
const cut = prefixRoom > 0 ? truncateToBytes(text2, prefixRoom).content : "";
|
|
5635
|
+
const lastBreak = cut.lastIndexOf("\n");
|
|
5636
|
+
const prefix = lastBreak >= 0 ? cut.slice(0, lastBreak + 1) : "";
|
|
5637
|
+
return `${dropTrailingOpenFence(prefix, fence)}${block}`;
|
|
5638
|
+
}
|
|
5639
|
+
function dropTrailingOpenFence(text2, fence) {
|
|
5640
|
+
if (!fenceIsOpen(text2, fence)) return text2;
|
|
5641
|
+
const lines = text2.split("\n");
|
|
5642
|
+
const opening = lines.findIndex((line) => line.startsWith(fence));
|
|
5643
|
+
if (opening < 0) return text2;
|
|
5644
|
+
return lines.slice(0, opening).join("\n") + (opening > 0 ? "\n" : "");
|
|
5645
|
+
}
|
|
5646
|
+
function fenceIsOpen(text2, fence) {
|
|
5647
|
+
let open = false;
|
|
5648
|
+
for (const line of text2.split("\n")) {
|
|
5649
|
+
if (line.startsWith(fence)) open = !open;
|
|
5650
|
+
}
|
|
5651
|
+
return open;
|
|
5652
|
+
}
|
|
4963
5653
|
function applyTruncatedContent(file, content) {
|
|
4964
5654
|
file.content = content;
|
|
4965
5655
|
file.isPreviewTruncated = true;
|
|
@@ -5007,52 +5697,64 @@ function greedyGroups(sections, budget) {
|
|
|
5007
5697
|
if (current.length > 0) groups.push(current);
|
|
5008
5698
|
return groups;
|
|
5009
5699
|
}
|
|
5010
|
-
function
|
|
5700
|
+
function assembleGroup(group, index, total, contentFingerprint) {
|
|
5701
|
+
const width = Math.max(3, String(total).length);
|
|
5702
|
+
const zeroPad = (value) => String(value).padStart(width, "0");
|
|
5703
|
+
const filename = `part-${zeroPad(index + 1)}.md`;
|
|
5704
|
+
const prev = index === 0 ? "none" : `part-${zeroPad(index)}.md`;
|
|
5705
|
+
const next = index === total - 1 ? "none" : `part-${zeroPad(index + 2)}.md`;
|
|
5706
|
+
for (const section of group.sections) {
|
|
5707
|
+
section.file.part = filename;
|
|
5708
|
+
}
|
|
5709
|
+
const layerCounts = {};
|
|
5710
|
+
const kindCounts = {};
|
|
5711
|
+
for (const section of group.sections) {
|
|
5712
|
+
layerCounts[section.file.layer] = (layerCounts[section.file.layer] || 0) + 1;
|
|
5713
|
+
kindCounts[section.file.kind] = (kindCounts[section.file.kind] || 0) + 1;
|
|
5714
|
+
}
|
|
5715
|
+
const pathLines = group.sections.map((s) => displayPath(s.file.relativePath));
|
|
5716
|
+
const listFence = fenceForText(pathLines.join("\n"), 3);
|
|
5717
|
+
const header = [
|
|
5718
|
+
`# Codebase Snapshot Part ${zeroPad(index + 1)} of ${zeroPad(total)}`,
|
|
5719
|
+
"",
|
|
5720
|
+
`- Previous: ${prev}`,
|
|
5721
|
+
`- Next: ${next}`,
|
|
5722
|
+
`- Files: ${group.sections.length}`,
|
|
5723
|
+
`- Content Fingerprint: ${contentFingerprint || "none"}`,
|
|
5724
|
+
`- Layers: ${topEntries(layerCounts, 6) || "none"}`,
|
|
5725
|
+
`- Kinds: ${topEntries(kindCounts, 8) || "none"}`,
|
|
5726
|
+
"",
|
|
5727
|
+
UNTRUSTED_CONTENT_NOTICE,
|
|
5728
|
+
"",
|
|
5729
|
+
"## Files",
|
|
5730
|
+
"",
|
|
5731
|
+
`${listFence}text`,
|
|
5732
|
+
...pathLines,
|
|
5733
|
+
listFence,
|
|
5734
|
+
"",
|
|
5735
|
+
"## Contents",
|
|
5736
|
+
""
|
|
5737
|
+
].join("\n");
|
|
5738
|
+
const assembled = (header + group.sections.map((s) => s.text).join("")).replace(
|
|
5739
|
+
/\r\n|\r/g,
|
|
5740
|
+
"\n"
|
|
5741
|
+
);
|
|
5742
|
+
const estimatedTokens = estimateTokens(assembled);
|
|
5743
|
+
return { filename, content: insertTokenLine(assembled, estimatedTokens), estimatedTokens };
|
|
5744
|
+
}
|
|
5745
|
+
function renderGroups(groups, contentFingerprint) {
|
|
5011
5746
|
const total = groups.length;
|
|
5012
|
-
const zeroPad = (index) => String(index).padStart(Math.max(3, String(total).length), "0");
|
|
5013
5747
|
return groups.map((group, index) => {
|
|
5014
|
-
const filename =
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
const layersCovered = Array.from(new Set(group.map((s) => s.file.layer))).sort();
|
|
5021
|
-
const layerCounts = {};
|
|
5022
|
-
const kindCounts = {};
|
|
5023
|
-
for (const section of group) {
|
|
5024
|
-
layerCounts[section.file.layer] = (layerCounts[section.file.layer] || 0) + 1;
|
|
5025
|
-
kindCounts[section.file.kind] = (kindCounts[section.file.kind] || 0) + 1;
|
|
5026
|
-
}
|
|
5027
|
-
const pathLines = group.map((s) => displayPath(s.file.relativePath));
|
|
5028
|
-
const listFence = fenceForText(pathLines.join("\n"), 3);
|
|
5029
|
-
const header = [
|
|
5030
|
-
`# Codebase Snapshot Part ${zeroPad(index + 1)} of ${zeroPad(total)}`,
|
|
5031
|
-
"",
|
|
5032
|
-
`- Previous: ${prev}`,
|
|
5033
|
-
`- Next: ${next}`,
|
|
5034
|
-
`- Files: ${group.length}`,
|
|
5035
|
-
`- Content Fingerprint: ${contentFingerprint || "none"}`,
|
|
5036
|
-
`- Layers: ${topEntries(layerCounts, 6) || "none"}`,
|
|
5037
|
-
`- Kinds: ${topEntries(kindCounts, 8) || "none"}`,
|
|
5038
|
-
"",
|
|
5039
|
-
UNTRUSTED_CONTENT_NOTICE,
|
|
5040
|
-
"",
|
|
5041
|
-
"## Files",
|
|
5042
|
-
"",
|
|
5043
|
-
`${listFence}text`,
|
|
5044
|
-
...pathLines,
|
|
5045
|
-
listFence,
|
|
5046
|
-
"",
|
|
5047
|
-
"## Contents",
|
|
5048
|
-
""
|
|
5049
|
-
].join("\n");
|
|
5050
|
-
const assembled = (header + group.map((s) => s.text).join("")).replace(/\r\n/g, "\n");
|
|
5051
|
-
const estimatedTokens = estimateTokens(assembled);
|
|
5052
|
-
const content = insertTokenLine(assembled, estimatedTokens);
|
|
5748
|
+
const { filename, content, estimatedTokens } = assembleGroup(
|
|
5749
|
+
group,
|
|
5750
|
+
index,
|
|
5751
|
+
total,
|
|
5752
|
+
contentFingerprint
|
|
5753
|
+
);
|
|
5053
5754
|
const byteSize = Buffer.byteLength(content, "utf8");
|
|
5054
5755
|
const sha256 = crypto2.createHash("sha256").update(content).digest("hex");
|
|
5055
|
-
const
|
|
5756
|
+
const layersCovered = Array.from(new Set(group.sections.map((s) => s.file.layer))).sort();
|
|
5757
|
+
const includedFiles = group.sections.map(({ file }) => ({
|
|
5056
5758
|
relativePath: file.relativePath,
|
|
5057
5759
|
sourceBytes: file.sizeBytes,
|
|
5058
5760
|
includedBytes: file.includedBytes,
|
|
@@ -5065,10 +5767,10 @@ function renderGroups(groups, budget, contentFingerprint) {
|
|
|
5065
5767
|
filename,
|
|
5066
5768
|
byteSize,
|
|
5067
5769
|
estimatedTokens,
|
|
5068
|
-
budgetBytes: budget,
|
|
5770
|
+
budgetBytes: group.budget,
|
|
5069
5771
|
sha256,
|
|
5070
5772
|
lines: countLines(content),
|
|
5071
|
-
fileCount: group.length,
|
|
5773
|
+
fileCount: group.sections.length,
|
|
5072
5774
|
layersCovered,
|
|
5073
5775
|
includedFiles
|
|
5074
5776
|
};
|
|
@@ -5076,7 +5778,7 @@ function renderGroups(groups, budget, contentFingerprint) {
|
|
|
5076
5778
|
});
|
|
5077
5779
|
}
|
|
5078
5780
|
function emittedBody(content) {
|
|
5079
|
-
const normalised = content.replace(/\r\n/g, "\n");
|
|
5781
|
+
const normalised = content.replace(/\r\n|\r/g, "\n");
|
|
5080
5782
|
return normalised.endsWith("\n") ? normalised.slice(0, -1) : normalised;
|
|
5081
5783
|
}
|
|
5082
5784
|
function compactMetadata(file) {
|
|
@@ -5144,7 +5846,7 @@ function generateMonolithJson(manifest, filesWithContent) {
|
|
|
5144
5846
|
}
|
|
5145
5847
|
function generateMonolithMarkdown(manifest, filesWithContent) {
|
|
5146
5848
|
const sections = filesWithContent.map((file) => {
|
|
5147
|
-
const content = (file.content ?? "").replace(/\r\n/g, "\n");
|
|
5849
|
+
const content = (file.content ?? "").replace(/\r\n|\r/g, "\n");
|
|
5148
5850
|
const fence = fenceForContent(content);
|
|
5149
5851
|
return [
|
|
5150
5852
|
`## File: ${escapeControls(file.relativePath)}`,
|
|
@@ -5309,322 +6011,79 @@ function generateReadme(manifest, artifacts) {
|
|
|
5309
6011
|
`${readOrder.length + 1}. Inspect \`manifest.json\` for precise dependency and AST symbol maps.`
|
|
5310
6012
|
);
|
|
5311
6013
|
}
|
|
5312
|
-
readOrder.push(
|
|
5313
|
-
`${readOrder.length + 1}. Check \`manifest.validation.status\` before trusting the snapshot; \`snapshot validate\` re-checks it against disk.`
|
|
5314
|
-
);
|
|
5315
|
-
lines.push("", "## AI Agent Ingestion Guide", "", ...readOrder, "");
|
|
5316
|
-
return lines.join("\n");
|
|
5317
|
-
}
|
|
5318
|
-
|
|
5319
|
-
// src/generator/tree.ts
|
|
5320
|
-
var MAX_TREE_LINES = 2e4;
|
|
5321
|
-
var MAX_TREE_DEPTH = 512;
|
|
5322
|
-
function childKeys(node) {
|
|
5323
|
-
return Array.from(node.children.keys()).sort((a, b) => {
|
|
5324
|
-
const aIsDir = node.children.get(a).children.size > 0;
|
|
5325
|
-
const bIsDir = node.children.get(b).children.size > 0;
|
|
5326
|
-
if (aIsDir !== bIsDir) return aIsDir ? -1 : 1;
|
|
5327
|
-
return a < b ? -1 : a > b ? 1 : 0;
|
|
5328
|
-
});
|
|
5329
|
-
}
|
|
5330
|
-
function generateProjectTree(filePaths) {
|
|
5331
|
-
const root = { children: /* @__PURE__ */ new Map() };
|
|
5332
|
-
for (const filePath of filePaths) {
|
|
5333
|
-
const parts = filePath.split("/").filter((part) => part.length > 0);
|
|
5334
|
-
let current = root;
|
|
5335
|
-
for (const part of parts) {
|
|
5336
|
-
let child = current.children.get(part);
|
|
5337
|
-
if (!child) {
|
|
5338
|
-
child = { children: /* @__PURE__ */ new Map() };
|
|
5339
|
-
current.children.set(part, child);
|
|
5340
|
-
}
|
|
5341
|
-
current = child;
|
|
5342
|
-
}
|
|
5343
|
-
}
|
|
5344
|
-
const lines = ["."];
|
|
5345
|
-
let truncatedAt = -1;
|
|
5346
|
-
let depthTruncated = false;
|
|
5347
|
-
const stack = [{ node: root, prefix: "", depth: 0, keys: childKeys(root), index: 0 }];
|
|
5348
|
-
while (stack.length > 0) {
|
|
5349
|
-
const frame = stack[stack.length - 1];
|
|
5350
|
-
if (frame.index >= frame.keys.length) {
|
|
5351
|
-
stack.pop();
|
|
5352
|
-
continue;
|
|
5353
|
-
}
|
|
5354
|
-
const key = frame.keys[frame.index];
|
|
5355
|
-
frame.index += 1;
|
|
5356
|
-
const isLast = frame.index === frame.keys.length;
|
|
5357
|
-
const child = frame.node.children.get(key);
|
|
5358
|
-
const isDirectory = child.children.size > 0;
|
|
5359
|
-
const label = escapeControls(key);
|
|
5360
|
-
lines.push(`${frame.prefix}${isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 "}${label}${isDirectory ? "/" : ""}`);
|
|
5361
|
-
if (lines.length >= MAX_TREE_LINES) {
|
|
5362
|
-
truncatedAt = lines.length;
|
|
5363
|
-
break;
|
|
5364
|
-
}
|
|
5365
|
-
if (!isDirectory) continue;
|
|
5366
|
-
if (frame.depth + 1 > MAX_TREE_DEPTH) {
|
|
5367
|
-
depthTruncated = true;
|
|
5368
|
-
continue;
|
|
5369
|
-
}
|
|
5370
|
-
stack.push({
|
|
5371
|
-
node: child,
|
|
5372
|
-
prefix: frame.prefix + (isLast ? " " : "\u2502 "),
|
|
5373
|
-
depth: frame.depth + 1,
|
|
5374
|
-
keys: childKeys(child),
|
|
5375
|
-
index: 0
|
|
5376
|
-
});
|
|
5377
|
-
}
|
|
5378
|
-
if (truncatedAt !== -1) {
|
|
5379
|
-
lines.push(`... [tree truncated at ${MAX_TREE_LINES} lines] ...`);
|
|
5380
|
-
} else if (depthTruncated) {
|
|
5381
|
-
lines.push(`... [tree truncated at ${MAX_TREE_DEPTH} levels] ...`);
|
|
5382
|
-
}
|
|
5383
|
-
const fence = fenceForText(lines.join("\n"), 3);
|
|
5384
|
-
return ["# Project Tree", "", `${fence}text`, ...lines, fence, ""].join("\n");
|
|
5385
|
-
}
|
|
5386
|
-
var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
5387
|
-
".png",
|
|
5388
|
-
".jpg",
|
|
5389
|
-
".jpeg",
|
|
5390
|
-
".gif",
|
|
5391
|
-
".webp",
|
|
5392
|
-
".avif",
|
|
5393
|
-
".ico",
|
|
5394
|
-
".bmp",
|
|
5395
|
-
".tiff",
|
|
5396
|
-
".pdf",
|
|
5397
|
-
".zip",
|
|
5398
|
-
".tar",
|
|
5399
|
-
".gz",
|
|
5400
|
-
".tgz",
|
|
5401
|
-
".br",
|
|
5402
|
-
".bz2",
|
|
5403
|
-
".xz",
|
|
5404
|
-
".zst",
|
|
5405
|
-
".lz4",
|
|
5406
|
-
".lzma",
|
|
5407
|
-
".7z",
|
|
5408
|
-
".rar",
|
|
5409
|
-
".mp3",
|
|
5410
|
-
".mp4",
|
|
5411
|
-
".wav",
|
|
5412
|
-
".ogg",
|
|
5413
|
-
".webm",
|
|
5414
|
-
".mov",
|
|
5415
|
-
".avi",
|
|
5416
|
-
".woff",
|
|
5417
|
-
".woff2",
|
|
5418
|
-
".ttf",
|
|
5419
|
-
".otf",
|
|
5420
|
-
".eot",
|
|
5421
|
-
".wasm",
|
|
5422
|
-
".node",
|
|
5423
|
-
".exe",
|
|
5424
|
-
".dll",
|
|
5425
|
-
".dylib",
|
|
5426
|
-
".so"
|
|
5427
|
-
]);
|
|
5428
|
-
function isBinaryExtension(filePath) {
|
|
5429
|
-
const ext = path16.extname(filePath).toLowerCase();
|
|
5430
|
-
return BINARY_EXTENSIONS.has(ext);
|
|
5431
|
-
}
|
|
5432
|
-
function isBinaryBuffer(buffer) {
|
|
5433
|
-
return buffer.includes(0);
|
|
5434
|
-
}
|
|
5435
|
-
function countControlBytes(buffer) {
|
|
5436
|
-
let count = 0;
|
|
5437
|
-
for (let index = 0; index < buffer.length; index++) {
|
|
5438
|
-
const byte = buffer[index];
|
|
5439
|
-
if (byte < 32) {
|
|
5440
|
-
if (byte !== 9 && byte !== 10 && byte !== 13) count += 1;
|
|
5441
|
-
continue;
|
|
5442
|
-
}
|
|
5443
|
-
if (byte === 127) {
|
|
5444
|
-
count += 1;
|
|
5445
|
-
continue;
|
|
5446
|
-
}
|
|
5447
|
-
if (byte === 194 && index + 1 < buffer.length) {
|
|
5448
|
-
const next = buffer[index + 1];
|
|
5449
|
-
if (next >= 128 && next <= 159) {
|
|
5450
|
-
count += 1;
|
|
5451
|
-
index += 1;
|
|
5452
|
-
}
|
|
5453
|
-
}
|
|
5454
|
-
}
|
|
5455
|
-
return count;
|
|
5456
|
-
}
|
|
5457
|
-
var CONTROL_BYTES_FLOOR = 4;
|
|
5458
|
-
function countInvalidUtf8Bytes(buffer) {
|
|
5459
|
-
let invalid = 0;
|
|
5460
|
-
let index = 0;
|
|
5461
|
-
const length = buffer.length;
|
|
5462
|
-
while (index < length) {
|
|
5463
|
-
const lead = buffer[index];
|
|
5464
|
-
if (lead < 128) {
|
|
5465
|
-
index += 1;
|
|
5466
|
-
continue;
|
|
5467
|
-
}
|
|
5468
|
-
let continuation = 0;
|
|
5469
|
-
let codePoint = 0;
|
|
5470
|
-
let minimum = 0;
|
|
5471
|
-
if ((lead & 224) === 192) {
|
|
5472
|
-
continuation = 1;
|
|
5473
|
-
codePoint = lead & 31;
|
|
5474
|
-
minimum = 128;
|
|
5475
|
-
} else if ((lead & 240) === 224) {
|
|
5476
|
-
continuation = 2;
|
|
5477
|
-
codePoint = lead & 15;
|
|
5478
|
-
minimum = 2048;
|
|
5479
|
-
} else if ((lead & 248) === 240) {
|
|
5480
|
-
continuation = 3;
|
|
5481
|
-
codePoint = lead & 7;
|
|
5482
|
-
minimum = 65536;
|
|
5483
|
-
} else {
|
|
5484
|
-
invalid += 1;
|
|
5485
|
-
index += 1;
|
|
5486
|
-
continue;
|
|
5487
|
-
}
|
|
5488
|
-
if (index + continuation >= length) {
|
|
5489
|
-
invalid += 1;
|
|
5490
|
-
index += 1;
|
|
5491
|
-
continue;
|
|
5492
|
-
}
|
|
5493
|
-
let wellFormed = true;
|
|
5494
|
-
for (let offset = 1; offset <= continuation; offset++) {
|
|
5495
|
-
const next = buffer[index + offset];
|
|
5496
|
-
if ((next & 192) !== 128) {
|
|
5497
|
-
wellFormed = false;
|
|
5498
|
-
break;
|
|
5499
|
-
}
|
|
5500
|
-
codePoint = codePoint << 6 | next & 63;
|
|
5501
|
-
}
|
|
5502
|
-
const isSurrogate = codePoint >= 55296 && codePoint <= 57343;
|
|
5503
|
-
if (!wellFormed || codePoint < minimum || codePoint > 1114111 || isSurrogate) {
|
|
5504
|
-
invalid += 1;
|
|
5505
|
-
index += 1;
|
|
5506
|
-
continue;
|
|
5507
|
-
}
|
|
5508
|
-
index += continuation + 1;
|
|
5509
|
-
}
|
|
5510
|
-
return invalid;
|
|
5511
|
-
}
|
|
5512
|
-
function readBomEncoding(buffer) {
|
|
5513
|
-
if (buffer.length >= 2) {
|
|
5514
|
-
if (buffer[0] === 255 && buffer[1] === 254) return "utf-16le";
|
|
5515
|
-
if (buffer[0] === 254 && buffer[1] === 255) return "utf-16be";
|
|
5516
|
-
}
|
|
5517
|
-
return null;
|
|
5518
|
-
}
|
|
5519
|
-
function looksLikeUtf16(buffer) {
|
|
5520
|
-
if (buffer.length < 8 || buffer.length % 2 !== 0) return null;
|
|
5521
|
-
const sample = Math.min(buffer.length, 4096);
|
|
5522
|
-
let nulInLowByte = 0;
|
|
5523
|
-
let nulInHighByte = 0;
|
|
5524
|
-
const units = sample / 2;
|
|
5525
|
-
for (let index = 0; index < sample; index += 2) {
|
|
5526
|
-
if (buffer[index] === 0) nulInLowByte += 1;
|
|
5527
|
-
if (buffer[index + 1] === 0) nulInHighByte += 1;
|
|
5528
|
-
}
|
|
5529
|
-
if (nulInHighByte > units * 0.3 && nulInLowByte < units * 0.05) return "utf-16le";
|
|
5530
|
-
if (nulInLowByte > units * 0.3 && nulInHighByte < units * 0.05) return "utf-16be";
|
|
5531
|
-
return null;
|
|
5532
|
-
}
|
|
5533
|
-
function decodeUtf16(buffer, encoding) {
|
|
5534
|
-
const littleEndian = encoding === "utf-16le" ? buffer : Buffer.from(buffer.subarray(0, buffer.length - buffer.length % 2)).swap16();
|
|
5535
|
-
return new TextDecoder("utf-16le").decode(littleEndian);
|
|
5536
|
-
}
|
|
5537
|
-
function decodeText(buffer) {
|
|
5538
|
-
const bomEncoding = readBomEncoding(buffer);
|
|
5539
|
-
if (bomEncoding) {
|
|
5540
|
-
const text2 = decodeUtf16(buffer.subarray(2), bomEncoding);
|
|
5541
|
-
return { text: text2, encoding: bomEncoding, invalidBytes: 0, binary: false };
|
|
5542
|
-
}
|
|
5543
|
-
if (buffer.includes(0)) {
|
|
5544
|
-
const shape = looksLikeUtf16(buffer);
|
|
5545
|
-
if (shape) {
|
|
5546
|
-
return { text: decodeUtf16(buffer, shape), encoding: shape, invalidBytes: 0, binary: false };
|
|
5547
|
-
}
|
|
5548
|
-
const offset = buffer.indexOf(0);
|
|
5549
|
-
return {
|
|
5550
|
-
text: "",
|
|
5551
|
-
encoding: "utf-8",
|
|
5552
|
-
invalidBytes: 0,
|
|
5553
|
-
binary: true,
|
|
5554
|
-
binaryReason: `NUL byte at offset ${offset}`
|
|
5555
|
-
};
|
|
5556
|
-
}
|
|
5557
|
-
const invalidBytes = countInvalidUtf8Bytes(buffer);
|
|
5558
|
-
if (invalidBytes === 0) {
|
|
5559
|
-
const controlBytes = countControlBytes(buffer);
|
|
5560
|
-
if (controlBytes > CONTROL_BYTES_FLOOR && controlBytes * 100 > buffer.length) {
|
|
5561
|
-
return {
|
|
5562
|
-
text: "",
|
|
5563
|
-
encoding: "utf-8",
|
|
5564
|
-
invalidBytes: 0,
|
|
5565
|
-
binary: true,
|
|
5566
|
-
binaryReason: `${controlBytes} control byte(s) in ${buffer.length} B; valid UTF-8 but not text`
|
|
5567
|
-
};
|
|
5568
|
-
}
|
|
5569
|
-
return {
|
|
5570
|
-
text: new TextDecoder("utf-8").decode(buffer),
|
|
5571
|
-
encoding: "utf-8",
|
|
5572
|
-
invalidBytes: 0,
|
|
5573
|
-
binary: false
|
|
5574
|
-
};
|
|
5575
|
-
}
|
|
5576
|
-
if (invalidBytes >= 32 && invalidBytes * 3 >= buffer.length) {
|
|
5577
|
-
return {
|
|
5578
|
-
text: "",
|
|
5579
|
-
encoding: "utf-8",
|
|
5580
|
-
invalidBytes,
|
|
5581
|
-
binary: true,
|
|
5582
|
-
binaryReason: `${invalidBytes} of ${buffer.length} bytes are not UTF-8; treated as binary`
|
|
5583
|
-
};
|
|
5584
|
-
}
|
|
5585
|
-
return {
|
|
5586
|
-
text: buffer.toString("utf8"),
|
|
5587
|
-
encoding: "utf-8-lossy",
|
|
5588
|
-
invalidBytes,
|
|
5589
|
-
binary: false
|
|
5590
|
-
};
|
|
6014
|
+
readOrder.push(
|
|
6015
|
+
`${readOrder.length + 1}. Check \`manifest.validation.status\` before trusting the snapshot; \`snapshot validate\` re-checks it against disk.`
|
|
6016
|
+
);
|
|
6017
|
+
lines.push("", "## AI Agent Ingestion Guide", "", ...readOrder, "");
|
|
6018
|
+
return lines.join("\n");
|
|
5591
6019
|
}
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
var
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
if (
|
|
5612
|
-
|
|
5613
|
-
|
|
6020
|
+
|
|
6021
|
+
// src/generator/tree.ts
|
|
6022
|
+
var MAX_TREE_LINES = 2e4;
|
|
6023
|
+
var MAX_TREE_DEPTH = 512;
|
|
6024
|
+
function childKeys(node) {
|
|
6025
|
+
return Array.from(node.children.keys()).sort((a, b) => {
|
|
6026
|
+
const aIsDir = node.children.get(a).children.size > 0;
|
|
6027
|
+
const bIsDir = node.children.get(b).children.size > 0;
|
|
6028
|
+
if (aIsDir !== bIsDir) return aIsDir ? -1 : 1;
|
|
6029
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
6030
|
+
});
|
|
6031
|
+
}
|
|
6032
|
+
function generateProjectTree(filePaths) {
|
|
6033
|
+
const root = { children: /* @__PURE__ */ new Map() };
|
|
6034
|
+
for (const filePath of filePaths) {
|
|
6035
|
+
const parts = filePath.split("/").filter((part) => part.length > 0);
|
|
6036
|
+
let current = root;
|
|
6037
|
+
for (const part of parts) {
|
|
6038
|
+
let child = current.children.get(part);
|
|
6039
|
+
if (!child) {
|
|
6040
|
+
child = { children: /* @__PURE__ */ new Map() };
|
|
6041
|
+
current.children.set(part, child);
|
|
5614
6042
|
}
|
|
5615
|
-
|
|
6043
|
+
current = child;
|
|
5616
6044
|
}
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
|
|
5622
|
-
|
|
5623
|
-
|
|
5624
|
-
|
|
5625
|
-
|
|
6045
|
+
}
|
|
6046
|
+
const lines = ["."];
|
|
6047
|
+
let truncatedAt = -1;
|
|
6048
|
+
let depthTruncated = false;
|
|
6049
|
+
const stack = [{ node: root, prefix: "", depth: 0, keys: childKeys(root), index: 0 }];
|
|
6050
|
+
while (stack.length > 0) {
|
|
6051
|
+
const frame = stack[stack.length - 1];
|
|
6052
|
+
if (frame.index >= frame.keys.length) {
|
|
6053
|
+
stack.pop();
|
|
6054
|
+
continue;
|
|
6055
|
+
}
|
|
6056
|
+
const key = frame.keys[frame.index];
|
|
6057
|
+
frame.index += 1;
|
|
6058
|
+
const isLast = frame.index === frame.keys.length;
|
|
6059
|
+
const child = frame.node.children.get(key);
|
|
6060
|
+
const isDirectory = child.children.size > 0;
|
|
6061
|
+
const label = escapeControls(key);
|
|
6062
|
+
lines.push(`${frame.prefix}${isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 "}${label}${isDirectory ? "/" : ""}`);
|
|
6063
|
+
if (lines.length >= MAX_TREE_LINES) {
|
|
6064
|
+
truncatedAt = lines.length;
|
|
6065
|
+
break;
|
|
6066
|
+
}
|
|
6067
|
+
if (!isDirectory) continue;
|
|
6068
|
+
if (frame.depth + 1 > MAX_TREE_DEPTH) {
|
|
6069
|
+
depthTruncated = true;
|
|
6070
|
+
continue;
|
|
5626
6071
|
}
|
|
6072
|
+
stack.push({
|
|
6073
|
+
node: child,
|
|
6074
|
+
prefix: frame.prefix + (isLast ? " " : "\u2502 "),
|
|
6075
|
+
depth: frame.depth + 1,
|
|
6076
|
+
keys: childKeys(child),
|
|
6077
|
+
index: 0
|
|
6078
|
+
});
|
|
6079
|
+
}
|
|
6080
|
+
if (truncatedAt !== -1) {
|
|
6081
|
+
lines.push(`... [tree truncated at ${MAX_TREE_LINES} lines] ...`);
|
|
6082
|
+
} else if (depthTruncated) {
|
|
6083
|
+
lines.push(`... [tree truncated at ${MAX_TREE_DEPTH} levels] ...`);
|
|
5627
6084
|
}
|
|
6085
|
+
const fence = fenceForText(lines.join("\n"), 3);
|
|
6086
|
+
return ["# Project Tree", "", `${fence}text`, ...lines, fence, ""].join("\n");
|
|
5628
6087
|
}
|
|
5629
6088
|
function gitPaths(cwd, args) {
|
|
5630
6089
|
let output;
|
|
@@ -5711,6 +6170,10 @@ var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
5711
6170
|
".mjs",
|
|
5712
6171
|
".cjs"
|
|
5713
6172
|
]);
|
|
6173
|
+
var PYTHON_EXTENSIONS = /* @__PURE__ */ new Set([".py", ".pyi"]);
|
|
6174
|
+
var RUBY_EXTENSIONS = /* @__PURE__ */ new Set([".rb", ".rake", ".gemspec"]);
|
|
6175
|
+
var C_EXTENSIONS = /* @__PURE__ */ new Set([".c", ".h", ".cc", ".cpp", ".cxx", ".hpp", ".hxx", ".m", ".mm"]);
|
|
6176
|
+
var PYTHON_SUFFIXES = [".py", ".pyi", "/__init__.py", "/__init__.pyi"];
|
|
5714
6177
|
function buildImportResolver(files) {
|
|
5715
6178
|
const byPath = /* @__PURE__ */ new Map();
|
|
5716
6179
|
const byPathFolded = /* @__PURE__ */ new Map();
|
|
@@ -5737,11 +6200,45 @@ function buildImportResolver(files) {
|
|
|
5737
6200
|
return { byPath, byPathFolded, byStem, byStemFolded };
|
|
5738
6201
|
}
|
|
5739
6202
|
function resolveSpecifier(fromFile, specifier, resolver) {
|
|
6203
|
+
const extension = path16.posix.extname(fromFile).toLowerCase();
|
|
6204
|
+
const exact = (key) => resolver.byPath.get(key) ?? resolver.byPathFolded.get(key.toLowerCase()) ?? null;
|
|
6205
|
+
const firstOf = (candidates) => {
|
|
6206
|
+
for (const candidate of candidates) {
|
|
6207
|
+
const hit = exact(candidate);
|
|
6208
|
+
if (hit) return hit;
|
|
6209
|
+
}
|
|
6210
|
+
return null;
|
|
6211
|
+
};
|
|
6212
|
+
if (PYTHON_EXTENSIONS.has(extension) && specifier.startsWith(".")) {
|
|
6213
|
+
const dots = /^\.+/.exec(specifier)?.[0].length ?? 0;
|
|
6214
|
+
const rest = specifier.slice(dots).replace(/\./g, "/");
|
|
6215
|
+
let base = path16.posix.dirname(fromFile);
|
|
6216
|
+
for (let up = 1; up < dots; up += 1) base = path16.posix.dirname(base);
|
|
6217
|
+
const joined2 = rest ? path16.posix.normalize(path16.posix.join(base, rest)) : base;
|
|
6218
|
+
return firstOf([joined2, ...PYTHON_SUFFIXES.map((suffix) => `${joined2}${suffix}`)]);
|
|
6219
|
+
}
|
|
6220
|
+
if (RUBY_EXTENSIONS.has(extension) && specifier.startsWith("./")) {
|
|
6221
|
+
const joined2 = path16.posix.normalize(
|
|
6222
|
+
path16.posix.join(path16.posix.dirname(fromFile), specifier)
|
|
6223
|
+
);
|
|
6224
|
+
return firstOf([`${joined2}.rb`, joined2]);
|
|
6225
|
+
}
|
|
6226
|
+
if (C_EXTENSIONS.has(extension) && specifier.startsWith("./")) {
|
|
6227
|
+
const joined2 = path16.posix.normalize(
|
|
6228
|
+
path16.posix.join(path16.posix.dirname(fromFile), specifier)
|
|
6229
|
+
);
|
|
6230
|
+
return exact(joined2);
|
|
6231
|
+
}
|
|
6232
|
+
if (extension === ".dart" && !specifier.includes(":")) {
|
|
6233
|
+
const joined2 = path16.posix.normalize(
|
|
6234
|
+
path16.posix.join(path16.posix.dirname(fromFile), specifier)
|
|
6235
|
+
);
|
|
6236
|
+
return firstOf([joined2, `${joined2}.dart`]);
|
|
6237
|
+
}
|
|
5740
6238
|
if (!RELATIVE_SPECIFIER.test(specifier)) return null;
|
|
5741
6239
|
const joined = path16.posix.normalize(
|
|
5742
6240
|
path16.posix.join(path16.posix.dirname(fromFile), specifier)
|
|
5743
6241
|
);
|
|
5744
|
-
const exact = (key) => resolver.byPath.get(key) ?? resolver.byPathFolded.get(key.toLowerCase()) ?? null;
|
|
5745
6242
|
const direct = exact(joined);
|
|
5746
6243
|
if (direct) return direct;
|
|
5747
6244
|
for (const suffix of IMPORT_CANDIDATE_SUFFIXES) {
|
|
@@ -5822,28 +6319,6 @@ function detectLanguage(filePath) {
|
|
|
5822
6319
|
const ext = path16.posix.extname(base).toLowerCase();
|
|
5823
6320
|
return LANGUAGE_BY_EXTENSION[ext] ?? "text";
|
|
5824
6321
|
}
|
|
5825
|
-
var TS_SOURCE_EXTENSIONS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
|
|
5826
|
-
var isTsSource = (relativePath) => {
|
|
5827
|
-
const lower = relativePath.toLowerCase();
|
|
5828
|
-
return TS_SOURCE_EXTENSIONS.some((extension) => lower.endsWith(extension));
|
|
5829
|
-
};
|
|
5830
|
-
function importsOfText(relativePath, text2) {
|
|
5831
|
-
if (isTsSource(relativePath)) return summarizeTypeScriptSource(relativePath, text2).imports;
|
|
5832
|
-
return summarizeWithProfile(relativePath, text2)?.summary.imports ?? [];
|
|
5833
|
-
}
|
|
5834
|
-
function importsOfFile(relativePath, absolutePath) {
|
|
5835
|
-
let buffer;
|
|
5836
|
-
try {
|
|
5837
|
-
buffer = fs14.readFileSync(absolutePath);
|
|
5838
|
-
} catch {
|
|
5839
|
-
return [];
|
|
5840
|
-
}
|
|
5841
|
-
const decoded = decodeText(buffer);
|
|
5842
|
-
if (decoded.binary) return [];
|
|
5843
|
-
return importsOfText(relativePath, decoded.text);
|
|
5844
|
-
}
|
|
5845
|
-
|
|
5846
|
-
// src/validator/validate.ts
|
|
5847
6322
|
function maskedPath(value, secretRules) {
|
|
5848
6323
|
return findSecretInPath(value, secretRules)?.masked ?? value;
|
|
5849
6324
|
}
|
|
@@ -5964,9 +6439,13 @@ function describeManifestShapeProblem(value) {
|
|
|
5964
6439
|
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
5965
6440
|
return 'a "parts" entry is not an object';
|
|
5966
6441
|
}
|
|
5967
|
-
|
|
6442
|
+
const filename = entry.filename;
|
|
6443
|
+
if (typeof filename !== "string" || filename.trim() === "") {
|
|
5968
6444
|
return 'a "parts" entry has no "filename"';
|
|
5969
6445
|
}
|
|
6446
|
+
if (filename.includes("/") || filename.includes("\\") || filename.includes("\0") || filename === "." || filename === ".." || path16.isAbsolute(filename)) {
|
|
6447
|
+
return 'a "parts" entry "filename" must be a bare filename inside parts/';
|
|
6448
|
+
}
|
|
5970
6449
|
}
|
|
5971
6450
|
if (!candidate.layers || typeof candidate.layers !== "object" || Array.isArray(candidate.layers)) {
|
|
5972
6451
|
return 'missing "layers" object';
|
|
@@ -6077,7 +6556,7 @@ function validateSnapshot(configInput) {
|
|
|
6077
6556
|
});
|
|
6078
6557
|
for (const diskFile of collection.files) {
|
|
6079
6558
|
const snapshotHash = manifestFileMap.get(diskFile);
|
|
6080
|
-
if (
|
|
6559
|
+
if (snapshotHash === void 0) {
|
|
6081
6560
|
const skipReason = skippedReasonOnDisk(diskFile);
|
|
6082
6561
|
if (skipReason === "unchanged") {
|
|
6083
6562
|
if (unchangedScope !== null) {
|
|
@@ -6091,6 +6570,11 @@ function validateSnapshot(configInput) {
|
|
|
6091
6570
|
drift.missingInSnapshot.push(diskFile);
|
|
6092
6571
|
continue;
|
|
6093
6572
|
}
|
|
6573
|
+
if (unchangedScope !== null && !unchangedScope.has(diskFile) && !becameDependent(diskFile)) {
|
|
6574
|
+
drift.configMismatches.push(
|
|
6575
|
+
`scope: ${maskedPath(diskFile, config.secretRules)} is in the snapshot but is no longer in the changed-files scope`
|
|
6576
|
+
);
|
|
6577
|
+
}
|
|
6094
6578
|
let diskHash;
|
|
6095
6579
|
try {
|
|
6096
6580
|
diskHash = sha256Hex(fs14.readFileSync(path16.join(config.cwd, diskFile)));
|
|
@@ -6231,6 +6715,41 @@ function validateSnapshot(configInput) {
|
|
|
6231
6715
|
`includeSensitiveFiles: manifest ${recorded.includeSensitiveFiles} vs current ${config.includeSensitiveFiles}`
|
|
6232
6716
|
);
|
|
6233
6717
|
}
|
|
6718
|
+
if ((recorded.maxPartTokens ?? void 0) !== (config.maxPartTokens ?? void 0)) {
|
|
6719
|
+
drift.configMismatches.push(
|
|
6720
|
+
`maxPartTokens: manifest ${String(recorded.maxPartTokens ?? "unset")} vs current ${String(config.maxPartTokens ?? "unset")}`
|
|
6721
|
+
);
|
|
6722
|
+
}
|
|
6723
|
+
if ((recorded.maxMonolithBytes ?? null) !== (config.maxMonolithBytes ?? null)) {
|
|
6724
|
+
drift.configMismatches.push(
|
|
6725
|
+
`maxMonolithBytes: manifest ${String(recorded.maxMonolithBytes ?? null)} vs current ${String(config.maxMonolithBytes ?? null)}`
|
|
6726
|
+
);
|
|
6727
|
+
}
|
|
6728
|
+
if (Boolean(recorded.skeleton) !== Boolean(config.skeleton)) {
|
|
6729
|
+
drift.configMismatches.push(
|
|
6730
|
+
`skeleton: manifest ${Boolean(recorded.skeleton)} vs current ${Boolean(config.skeleton)}`
|
|
6731
|
+
);
|
|
6732
|
+
}
|
|
6733
|
+
if (Boolean(recorded.sarif) !== Boolean(config.sarif)) {
|
|
6734
|
+
drift.configMismatches.push(
|
|
6735
|
+
`sarif: manifest ${Boolean(recorded.sarif)} vs current ${Boolean(config.sarif)}`
|
|
6736
|
+
);
|
|
6737
|
+
}
|
|
6738
|
+
if ((recorded.changedSince ?? void 0) !== (config.changedSince ?? void 0)) {
|
|
6739
|
+
drift.configMismatches.push(
|
|
6740
|
+
`changedSince: manifest ${String(recorded.changedSince ?? "unset")} vs current ${String(config.changedSince ?? "unset")}`
|
|
6741
|
+
);
|
|
6742
|
+
}
|
|
6743
|
+
if (Boolean(recorded.staged) !== Boolean(config.staged)) {
|
|
6744
|
+
drift.configMismatches.push(
|
|
6745
|
+
`staged: manifest ${Boolean(recorded.staged)} vs current ${Boolean(config.staged)}`
|
|
6746
|
+
);
|
|
6747
|
+
}
|
|
6748
|
+
if (Boolean(recorded.withDependents) !== Boolean(config.withDependents)) {
|
|
6749
|
+
drift.configMismatches.push(
|
|
6750
|
+
`withDependents: manifest ${Boolean(recorded.withDependents)} vs current ${Boolean(config.withDependents)}`
|
|
6751
|
+
);
|
|
6752
|
+
}
|
|
6234
6753
|
const effectiveDiscovery = config.discovery ?? "auto";
|
|
6235
6754
|
const recordedDiscovery = recorded.discovery;
|
|
6236
6755
|
if (recordedDiscovery !== void 0 && recordedDiscovery !== effectiveDiscovery) {
|
|
@@ -6451,7 +6970,7 @@ function validateSnapshot(configInput) {
|
|
|
6451
6970
|
|
|
6452
6971
|
// src/version.ts
|
|
6453
6972
|
var GENERATOR_NAME = "codebase-snapshot";
|
|
6454
|
-
var GENERATOR_VERSION = "0.9.
|
|
6973
|
+
var GENERATOR_VERSION = "0.9.14";
|
|
6455
6974
|
var LEGACY_GENERATOR_NAMES = ["@ankurhalder/codebase-snapshot"];
|
|
6456
6975
|
function isOurGeneratorName(name) {
|
|
6457
6976
|
if (typeof name !== "string") return false;
|
|
@@ -6478,22 +6997,28 @@ function updateCommand(kind) {
|
|
|
6478
6997
|
if (kind === "local") return "npm install codebase-snapshot@latest";
|
|
6479
6998
|
return "npm install -g codebase-snapshot";
|
|
6480
6999
|
}
|
|
6481
|
-
function
|
|
6482
|
-
const
|
|
6483
|
-
|
|
7000
|
+
function parseSemver(value) {
|
|
7001
|
+
const cleaned = value.trim().replace(/^v/i, "").split("+")[0] ?? "";
|
|
7002
|
+
const dash = cleaned.indexOf("-");
|
|
7003
|
+
const corePart = dash === -1 ? cleaned : cleaned.slice(0, dash);
|
|
7004
|
+
const prerelease = dash === -1 ? null : cleaned.slice(dash + 1);
|
|
7005
|
+
const core = corePart.split(".").map((part) => {
|
|
6484
7006
|
const parsed = Number(part);
|
|
6485
7007
|
return Number.isFinite(parsed) ? parsed : 0;
|
|
6486
7008
|
});
|
|
7009
|
+
return { core, prerelease };
|
|
6487
7010
|
}
|
|
6488
7011
|
function isNewer(candidate, current) {
|
|
6489
|
-
const left =
|
|
6490
|
-
const right =
|
|
6491
|
-
|
|
7012
|
+
const left = parseSemver(candidate);
|
|
7013
|
+
const right = parseSemver(current);
|
|
7014
|
+
if (left.prerelease !== null && right.prerelease === null) return false;
|
|
7015
|
+
const length = Math.max(left.core.length, right.core.length);
|
|
6492
7016
|
for (let index = 0; index < length; index += 1) {
|
|
6493
|
-
const a = left[index] ?? 0;
|
|
6494
|
-
const b = right[index] ?? 0;
|
|
7017
|
+
const a = left.core[index] ?? 0;
|
|
7018
|
+
const b = right.core[index] ?? 0;
|
|
6495
7019
|
if (a !== b) return a > b;
|
|
6496
7020
|
}
|
|
7021
|
+
if (left.prerelease === null && right.prerelease !== null) return true;
|
|
6497
7022
|
return false;
|
|
6498
7023
|
}
|
|
6499
7024
|
function cacheDirectory(env = process.env, platform = process.platform) {
|
|
@@ -6561,14 +7086,22 @@ function writeCache(file, entry) {
|
|
|
6561
7086
|
}
|
|
6562
7087
|
}
|
|
6563
7088
|
function fetchLatestVersion(url = PACKAGE_LATEST_URL, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
6564
|
-
return new Promise((
|
|
7089
|
+
return new Promise((resolve6) => {
|
|
6565
7090
|
let settled = false;
|
|
7091
|
+
let request = null;
|
|
6566
7092
|
const finish = (value) => {
|
|
6567
7093
|
if (settled) return;
|
|
6568
7094
|
settled = true;
|
|
6569
|
-
|
|
7095
|
+
clearTimeout(deadline);
|
|
7096
|
+
resolve6(value);
|
|
6570
7097
|
};
|
|
6571
|
-
const
|
|
7098
|
+
const deadline = setTimeout(() => {
|
|
7099
|
+
request?.destroy();
|
|
7100
|
+
finish(null);
|
|
7101
|
+
}, timeoutMs);
|
|
7102
|
+
deadline.unref();
|
|
7103
|
+
const transport = url.startsWith("http:") ? http : https;
|
|
7104
|
+
request = transport.get(
|
|
6572
7105
|
url,
|
|
6573
7106
|
{
|
|
6574
7107
|
headers: {
|
|
@@ -6587,7 +7120,7 @@ function fetchLatestVersion(url = PACKAGE_LATEST_URL, timeoutMs = REQUEST_TIMEOU
|
|
|
6587
7120
|
response.on("data", (chunk) => {
|
|
6588
7121
|
body += chunk;
|
|
6589
7122
|
if (body.length > 64 * 1024) {
|
|
6590
|
-
request
|
|
7123
|
+
request?.destroy();
|
|
6591
7124
|
finish(null);
|
|
6592
7125
|
}
|
|
6593
7126
|
});
|
|
@@ -6602,10 +7135,6 @@ function fetchLatestVersion(url = PACKAGE_LATEST_URL, timeoutMs = REQUEST_TIMEOU
|
|
|
6602
7135
|
}
|
|
6603
7136
|
);
|
|
6604
7137
|
request.on("error", () => finish(null));
|
|
6605
|
-
request.setTimeout(timeoutMs, () => {
|
|
6606
|
-
request.destroy();
|
|
6607
|
-
finish(null);
|
|
6608
|
-
});
|
|
6609
7138
|
});
|
|
6610
7139
|
}
|
|
6611
7140
|
async function checkForUpdate(options) {
|
|
@@ -6649,10 +7178,13 @@ function describeMatcher(matcher) {
|
|
|
6649
7178
|
return String(matcher);
|
|
6650
7179
|
}
|
|
6651
7180
|
function normalizeTarget(cwd, target) {
|
|
6652
|
-
const
|
|
6653
|
-
const
|
|
6654
|
-
|
|
6655
|
-
|
|
7181
|
+
const resolvedCwd = path16.resolve(cwd);
|
|
7182
|
+
const absolute = path16.resolve(resolvedCwd, target);
|
|
7183
|
+
const relative8 = path16.relative(resolvedCwd, absolute);
|
|
7184
|
+
if (relative8 === "" || relative8 === ".." || relative8.startsWith(`..${path16.sep}`) || path16.isAbsolute(relative8)) {
|
|
7185
|
+
return { relative: target.replace(/\\/g, "/"), outsideRoot: true };
|
|
7186
|
+
}
|
|
7187
|
+
return { relative: relative8.split(path16.sep).join("/"), outsideRoot: false };
|
|
6656
7188
|
}
|
|
6657
7189
|
function findRuleMatch(rules, relativePath) {
|
|
6658
7190
|
return rules.find(
|
|
@@ -6679,9 +7211,19 @@ function readManifest2(config) {
|
|
|
6679
7211
|
}
|
|
6680
7212
|
}
|
|
6681
7213
|
function explainPath(config, target) {
|
|
6682
|
-
const
|
|
6683
|
-
const
|
|
7214
|
+
const normalized = normalizeTarget(config.cwd, target);
|
|
7215
|
+
const relative8 = normalized.relative;
|
|
6684
7216
|
const maskedPath2 = findSecretInPath(relative8, config.secretRules)?.masked ?? relative8;
|
|
7217
|
+
if (normalized.outsideRoot) {
|
|
7218
|
+
return {
|
|
7219
|
+
path: maskedPath2,
|
|
7220
|
+
exists: false,
|
|
7221
|
+
included: false,
|
|
7222
|
+
reason: "outside-root",
|
|
7223
|
+
detail: "path resolves outside the configured repository root and was not inspected"
|
|
7224
|
+
};
|
|
7225
|
+
}
|
|
7226
|
+
const absolute = path16.join(config.cwd, relative8);
|
|
6685
7227
|
const result = {
|
|
6686
7228
|
path: maskedPath2,
|
|
6687
7229
|
exists: fs14.existsSync(absolute),
|
|
@@ -7319,6 +7861,24 @@ function displayWidth(line) {
|
|
|
7319
7861
|
return width;
|
|
7320
7862
|
}
|
|
7321
7863
|
var GUTTER_PATTERN = /^[ \t]*(?:[❯◆◻◼▏✖][ \t]+)*/;
|
|
7864
|
+
function sliceByDisplayWidth(text2, maxCells) {
|
|
7865
|
+
let cells = 0;
|
|
7866
|
+
let lastSpace = -1;
|
|
7867
|
+
let index = 0;
|
|
7868
|
+
for (const character of text2) {
|
|
7869
|
+
const width = isWideCodePoint(character.codePointAt(0) ?? 0) ? 2 : 1;
|
|
7870
|
+
if (cells + width > maxCells) break;
|
|
7871
|
+
if (character === " ") lastSpace = index;
|
|
7872
|
+
cells += width;
|
|
7873
|
+
index += character.length;
|
|
7874
|
+
}
|
|
7875
|
+
if (index >= text2.length) return { head: text2, tail: "" };
|
|
7876
|
+
if (lastSpace > 0) {
|
|
7877
|
+
return { head: text2.slice(0, lastSpace), tail: text2.slice(lastSpace + 1).trimStart() };
|
|
7878
|
+
}
|
|
7879
|
+
const safeIndex = index > 0 ? index : Array.from(text2)[0]?.length ?? 1;
|
|
7880
|
+
return { head: text2.slice(0, safeIndex), tail: text2.slice(safeIndex).trimStart() };
|
|
7881
|
+
}
|
|
7322
7882
|
function wrapOneLine(line, limit) {
|
|
7323
7883
|
if (displayWidth(line) <= limit) return [line];
|
|
7324
7884
|
const gutter = GUTTER_PATTERN.exec(line)?.[0] ?? "";
|
|
@@ -7333,12 +7893,11 @@ function wrapOneLine(line, limit) {
|
|
|
7333
7893
|
rows.push(prefix + rest);
|
|
7334
7894
|
break;
|
|
7335
7895
|
}
|
|
7336
|
-
|
|
7337
|
-
|
|
7338
|
-
|
|
7339
|
-
|
|
7340
|
-
|
|
7341
|
-
rest = rest.slice(cut).trimStart();
|
|
7896
|
+
const { head, tail } = sliceByDisplayWidth(rest, room);
|
|
7897
|
+
rows.push(
|
|
7898
|
+
`${prefix}${head.length > 0 ? head : rest.slice(0, 1)}`
|
|
7899
|
+
);
|
|
7900
|
+
rest = tail;
|
|
7342
7901
|
prefix = continuation;
|
|
7343
7902
|
}
|
|
7344
7903
|
return rows;
|
|
@@ -7409,7 +7968,7 @@ function keyReader(input) {
|
|
|
7409
7968
|
next() {
|
|
7410
7969
|
const queued = queue.shift();
|
|
7411
7970
|
if (queued) return Promise.resolve(queued);
|
|
7412
|
-
return new Promise((
|
|
7971
|
+
return new Promise((resolve6) => waiters.push(resolve6));
|
|
7413
7972
|
},
|
|
7414
7973
|
close() {
|
|
7415
7974
|
input.off("keypress", onKey);
|
|
@@ -7626,17 +8185,25 @@ var CONFIG_CANDIDATES = [
|
|
|
7626
8185
|
".snapshotrc.json",
|
|
7627
8186
|
".snapshotrc"
|
|
7628
8187
|
];
|
|
7629
|
-
function
|
|
7630
|
-
|
|
7631
|
-
|
|
7632
|
-
|
|
8188
|
+
function parsePackageJson(raw) {
|
|
8189
|
+
const parsed = JSON.parse(stripJsonComments(raw));
|
|
8190
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
8191
|
+
throw new Error("package.json is not a JSON object");
|
|
8192
|
+
}
|
|
8193
|
+
return parsed;
|
|
8194
|
+
}
|
|
8195
|
+
function readPackageJson(cwd) {
|
|
7633
8196
|
try {
|
|
7634
|
-
|
|
7635
|
-
return parsed.type === "module" ? "snapshot.config.ts" : "snapshot.config.mjs";
|
|
8197
|
+
return parsePackageJson(fs14.readFileSync(path16.join(cwd, "package.json"), "utf8"));
|
|
7636
8198
|
} catch {
|
|
7637
|
-
return
|
|
8199
|
+
return null;
|
|
7638
8200
|
}
|
|
7639
8201
|
}
|
|
8202
|
+
function chooseConfigFileName(cwd) {
|
|
8203
|
+
if (!supportsTypeScriptImports()) return "snapshot.config.mjs";
|
|
8204
|
+
const parsed = readPackageJson(cwd);
|
|
8205
|
+
return parsed?.type === "module" ? "snapshot.config.ts" : "snapshot.config.mjs";
|
|
8206
|
+
}
|
|
7640
8207
|
var SIZE_CHOICES = [
|
|
7641
8208
|
{ value: "60k", label: "Small", hint: "60 KB \xB7 ~15k tokens \xB7 tight context windows" },
|
|
7642
8209
|
{ value: "120k", label: "Medium", hint: "120 KB \xB7 ~30k tokens \xB7 recommended" },
|
|
@@ -7644,12 +8211,9 @@ var SIZE_CHOICES = [
|
|
|
7644
8211
|
{ value: "custom", label: "Custom\u2026", hint: "type a size like 150k or 1.5mb" }
|
|
7645
8212
|
];
|
|
7646
8213
|
function packageJsonHasConfig(cwd) {
|
|
7647
|
-
|
|
7648
|
-
|
|
7649
|
-
|
|
7650
|
-
} catch {
|
|
7651
|
-
return false;
|
|
7652
|
-
}
|
|
8214
|
+
const parsed = readPackageJson(cwd);
|
|
8215
|
+
if (parsed === null) return false;
|
|
8216
|
+
return typeof parsed.snapshot === "object" && parsed.snapshot !== null && !Array.isArray(parsed.snapshot);
|
|
7653
8217
|
}
|
|
7654
8218
|
function artifactAnswers(options) {
|
|
7655
8219
|
const artifacts = { ...DEFAULT_ANSWERS.artifacts };
|
|
@@ -7690,12 +8254,7 @@ function writeFileOrExplain(target, content) {
|
|
|
7690
8254
|
function canWriteModuleConfig(name, cwd) {
|
|
7691
8255
|
if (name.endsWith(".mjs") || name.endsWith(".ts")) return true;
|
|
7692
8256
|
if (!name.endsWith(".js")) return false;
|
|
7693
|
-
|
|
7694
|
-
const parsed = JSON.parse(fs14.readFileSync(path16.join(cwd, "package.json"), "utf8"));
|
|
7695
|
-
return parsed.type === "module";
|
|
7696
|
-
} catch {
|
|
7697
|
-
return false;
|
|
7698
|
-
}
|
|
8257
|
+
return readPackageJson(cwd)?.type === "module";
|
|
7699
8258
|
}
|
|
7700
8259
|
function configFormatLabel(name) {
|
|
7701
8260
|
if (name.endsWith(".js")) return 'CommonJS `.js` (package.json has no "type": "module")';
|
|
@@ -7723,7 +8282,8 @@ function serializePackageJson(raw, value) {
|
|
|
7723
8282
|
const crlf = raw.includes("\r\n");
|
|
7724
8283
|
const body = JSON.stringify(value, null, spacing);
|
|
7725
8284
|
const text2 = crlf ? body.replace(/\n/g, "\r\n") : body;
|
|
7726
|
-
|
|
8285
|
+
const bom = raw.startsWith("\uFEFF") ? "\uFEFF" : "";
|
|
8286
|
+
return `${bom}${text2}${raw.endsWith("\n") ? crlf ? "\r\n" : "\n" : ""}`;
|
|
7727
8287
|
}
|
|
7728
8288
|
function updatePackageJson(cwd, notes) {
|
|
7729
8289
|
const packagePath = path16.join(cwd, "package.json");
|
|
@@ -7739,7 +8299,7 @@ function updatePackageJson(cwd, notes) {
|
|
|
7739
8299
|
let parsed;
|
|
7740
8300
|
try {
|
|
7741
8301
|
raw = fs14.readFileSync(packagePath, "utf8");
|
|
7742
|
-
parsed =
|
|
8302
|
+
parsed = parsePackageJson(raw);
|
|
7743
8303
|
} catch {
|
|
7744
8304
|
notes.push("Could not parse package.json; npm scripts and the devDependency were not added.");
|
|
7745
8305
|
return unchanged;
|
|
@@ -8278,7 +8838,10 @@ const { parentPort, workerData } = require("node:worker_threads");
|
|
|
8278
8838
|
parentPort.postMessage({ ok: false, message: loaded.errors.join("; ") });
|
|
8279
8839
|
return;
|
|
8280
8840
|
}
|
|
8281
|
-
|
|
8841
|
+
// \`hooks: {}\` is a value, not an absence: the config is applied with the hook list emptied,
|
|
8842
|
+
// so the run is the documented MCP run rather than the CLI run with one setting removed.
|
|
8843
|
+
const base = loaded.config ? { ...loaded.config, hooks: {} } : null;
|
|
8844
|
+
const config = mod.resolveConfig(base, workerData.cliOptions, workerData.cwd);
|
|
8282
8845
|
const manifest = mod.generateSnapshot(config);
|
|
8283
8846
|
parentPort.postMessage({
|
|
8284
8847
|
ok: true,
|
|
@@ -8301,14 +8864,15 @@ const { parentPort, workerData } = require("node:worker_threads");
|
|
|
8301
8864
|
})();
|
|
8302
8865
|
`;
|
|
8303
8866
|
function generateInWorker(cwd, cliOptions) {
|
|
8304
|
-
return new Promise((
|
|
8867
|
+
return new Promise((resolve6) => {
|
|
8305
8868
|
let settled = false;
|
|
8869
|
+
let worker = null;
|
|
8306
8870
|
const finish = (result) => {
|
|
8307
8871
|
if (settled) return;
|
|
8308
8872
|
settled = true;
|
|
8309
|
-
|
|
8873
|
+
if (worker !== null) void worker.terminate();
|
|
8874
|
+
resolve6(result);
|
|
8310
8875
|
};
|
|
8311
|
-
let worker;
|
|
8312
8876
|
const isCjs = typeof __dirname !== "undefined";
|
|
8313
8877
|
const directory = isCjs ? __dirname : path16.dirname(fileURLToPath(import.meta.url));
|
|
8314
8878
|
try {
|
|
@@ -8341,7 +8905,100 @@ function generateInWorker(cwd, cliOptions) {
|
|
|
8341
8905
|
|
|
8342
8906
|
// src/index.ts
|
|
8343
8907
|
var SCHEMA_VERSION = "3.0.0";
|
|
8344
|
-
var
|
|
8908
|
+
var SUMMARY_MAX_SOURCE_BYTES2 = SUMMARY_MAX_SOURCE_BYTES;
|
|
8909
|
+
var FILE_DERIVATIONS = /* @__PURE__ */ new Map();
|
|
8910
|
+
var FILE_DERIVATION_LIMIT_BYTES = 64 * 1024 * 1024;
|
|
8911
|
+
var fileDerivationBytes = 0;
|
|
8912
|
+
function takeDerivation(path17, hash, settings) {
|
|
8913
|
+
const cached = FILE_DERIVATIONS.get(path17);
|
|
8914
|
+
if (cached === void 0 || cached.hash !== hash || cached.settings !== settings) return void 0;
|
|
8915
|
+
return cached;
|
|
8916
|
+
}
|
|
8917
|
+
function rememberDerivation(path17, hash, settings, derivation) {
|
|
8918
|
+
const bodyBytes = derivation.finalContent === derivation.redacted ? derivation.redacted.length : derivation.redacted.length + derivation.finalContent.length;
|
|
8919
|
+
const bytes = bodyBytes * 2 + 512;
|
|
8920
|
+
if (bytes > FILE_DERIVATION_LIMIT_BYTES / 4) return;
|
|
8921
|
+
while (fileDerivationBytes + bytes > FILE_DERIVATION_LIMIT_BYTES) {
|
|
8922
|
+
const oldest = FILE_DERIVATIONS.keys().next();
|
|
8923
|
+
if (oldest.done === true) break;
|
|
8924
|
+
const evicted = FILE_DERIVATIONS.get(oldest.value);
|
|
8925
|
+
FILE_DERIVATIONS.delete(oldest.value);
|
|
8926
|
+
if (evicted !== void 0) fileDerivationBytes -= evicted.bytes;
|
|
8927
|
+
}
|
|
8928
|
+
FILE_DERIVATIONS.set(path17, { ...derivation, hash, settings, bytes });
|
|
8929
|
+
fileDerivationBytes += bytes;
|
|
8930
|
+
}
|
|
8931
|
+
function derivationSettings(config) {
|
|
8932
|
+
const rules = config.secretRules.map(
|
|
8933
|
+
(rule) => `${rule.rule}\0${rule.pattern.source}\0${rule.pattern.flags}\0${rule.valueGroup ?? ""}`
|
|
8934
|
+
).join("");
|
|
8935
|
+
return `${GENERATOR_VERSION}${configDigest(config)}${sha256Hex2(rules)}`;
|
|
8936
|
+
}
|
|
8937
|
+
function deriveFile(relativePath, rawContent, sizeBytes, config) {
|
|
8938
|
+
const lineCount = countLines(rawContent);
|
|
8939
|
+
const { redacted, findings } = scanAndRedactSecrets(rawContent, config.secretRules, relativePath);
|
|
8940
|
+
const isLarge = sizeBytes > config.largeFileBytes;
|
|
8941
|
+
let finalContent = redacted;
|
|
8942
|
+
let isPreviewTruncated = false;
|
|
8943
|
+
let includedLines = lineCount;
|
|
8944
|
+
let inclusionMode = "full";
|
|
8945
|
+
if (config.skeleton === true && isSkeletonizable(relativePath)) {
|
|
8946
|
+
const skeleton = skeletonizeTypeScript(redacted, relativePath);
|
|
8947
|
+
if (skeleton.collapsed > 0) {
|
|
8948
|
+
finalContent = skeleton.text;
|
|
8949
|
+
inclusionMode = "signatures";
|
|
8950
|
+
includedLines = countLines(skeleton.text);
|
|
8951
|
+
}
|
|
8952
|
+
}
|
|
8953
|
+
if (isLarge && inclusionMode !== "signatures") {
|
|
8954
|
+
const preview = truncatePreview(redacted, config.previewHeadLines, config.previewTailLines);
|
|
8955
|
+
finalContent = preview.content;
|
|
8956
|
+
isPreviewTruncated = preview.isTruncated;
|
|
8957
|
+
includedLines = preview.includedLines;
|
|
8958
|
+
inclusionMode = isPreviewTruncated ? "preview" : "full";
|
|
8959
|
+
}
|
|
8960
|
+
let summary;
|
|
8961
|
+
let summaryQuality;
|
|
8962
|
+
let summarySkip = null;
|
|
8963
|
+
let summaryWarning = null;
|
|
8964
|
+
let dependentImports = null;
|
|
8965
|
+
if (Buffer.byteLength(rawContent, "utf8") > SUMMARY_MAX_SOURCE_BYTES2) {
|
|
8966
|
+
summarySkip = "oversize";
|
|
8967
|
+
dependentImports = [];
|
|
8968
|
+
} else if (isPreviewTruncated && hasStructuralSummary(relativePath)) {
|
|
8969
|
+
summarySkip = "preview";
|
|
8970
|
+
dependentImports = importsOfText(relativePath, rawContent);
|
|
8971
|
+
} else {
|
|
8972
|
+
try {
|
|
8973
|
+
const outcome = summarizeFile(relativePath, rawContent);
|
|
8974
|
+
summary = outcome?.summary ? maskSummaryStrings(outcome.summary, config.secretRules) : void 0;
|
|
8975
|
+
summaryQuality = outcome?.quality;
|
|
8976
|
+
if (isSourceSummary(summary) && summary.parseErrors) {
|
|
8977
|
+
summaryWarning = `Parse errors in ${maskPathText(relativePath, config.secretRules)} (${summary.parseErrors}); its summary is incomplete.`;
|
|
8978
|
+
}
|
|
8979
|
+
} catch (err) {
|
|
8980
|
+
summaryWarning = `Summary skipped for ${maskPathText(relativePath, config.secretRules)}: ${err.message}`.replace(
|
|
8981
|
+
/\s+/g,
|
|
8982
|
+
" "
|
|
8983
|
+
);
|
|
8984
|
+
}
|
|
8985
|
+
}
|
|
8986
|
+
return {
|
|
8987
|
+
redacted,
|
|
8988
|
+
findings,
|
|
8989
|
+
lineCount,
|
|
8990
|
+
finalContent,
|
|
8991
|
+
inclusionMode,
|
|
8992
|
+
isPreviewTruncated,
|
|
8993
|
+
includedLines,
|
|
8994
|
+
isLarge,
|
|
8995
|
+
summary,
|
|
8996
|
+
summaryQuality,
|
|
8997
|
+
summarySkip,
|
|
8998
|
+
summaryWarning,
|
|
8999
|
+
dependentImports
|
|
9000
|
+
};
|
|
9001
|
+
}
|
|
8345
9002
|
function sha256Hex2(input) {
|
|
8346
9003
|
return crypto2.createHash("sha256").update(input).digest("hex");
|
|
8347
9004
|
}
|
|
@@ -8885,10 +9542,7 @@ function acquireRunLock(stagingRoot) {
|
|
|
8885
9542
|
};
|
|
8886
9543
|
} catch (err) {
|
|
8887
9544
|
if (err.code !== "EEXIST") throw err;
|
|
8888
|
-
if (attempt === 0 &&
|
|
8889
|
-
fs14.rmSync(lockPath, { force: true });
|
|
8890
|
-
continue;
|
|
8891
|
-
}
|
|
9545
|
+
if (attempt === 0 && reclaimStaleLock(lockPath)) continue;
|
|
8892
9546
|
throw new Error(
|
|
8893
9547
|
`Another generate run is already using the staging directory ${stagingRoot}. Wait for it to finish, or delete ${lockPath} if no other run is active.`
|
|
8894
9548
|
);
|
|
@@ -8897,14 +9551,25 @@ function acquireRunLock(stagingRoot) {
|
|
|
8897
9551
|
throw new Error(`Could not acquire the generate lock for ${stagingRoot}.`);
|
|
8898
9552
|
}
|
|
8899
9553
|
function lockKeyFor(stagingRoot) {
|
|
8900
|
-
let key = stagingRoot;
|
|
8901
|
-
try {
|
|
8902
|
-
key = fs14.realpathSync(stagingRoot);
|
|
8903
|
-
} catch {
|
|
8904
|
-
}
|
|
9554
|
+
let key = canonicalLockTarget(stagingRoot);
|
|
8905
9555
|
if (process.platform === "win32" || process.platform === "darwin") key = key.toLowerCase();
|
|
8906
9556
|
return sha256Hex2(key).slice(0, 16);
|
|
8907
9557
|
}
|
|
9558
|
+
function canonicalLockTarget(targetPath) {
|
|
9559
|
+
const resolved = path16.resolve(targetPath);
|
|
9560
|
+
let existing = resolved;
|
|
9561
|
+
const tail = [];
|
|
9562
|
+
for (; ; ) {
|
|
9563
|
+
try {
|
|
9564
|
+
return path16.join(fs14.realpathSync(existing), ...tail);
|
|
9565
|
+
} catch {
|
|
9566
|
+
const parent = path16.dirname(existing);
|
|
9567
|
+
if (parent === existing) return resolved;
|
|
9568
|
+
tail.unshift(path16.basename(existing));
|
|
9569
|
+
existing = parent;
|
|
9570
|
+
}
|
|
9571
|
+
}
|
|
9572
|
+
}
|
|
8908
9573
|
function lockPathFor(stagingRoot) {
|
|
8909
9574
|
return path16.join(os.tmpdir(), `codebase-snapshot-${lockKeyFor(stagingRoot)}.lock`);
|
|
8910
9575
|
}
|
|
@@ -8929,6 +9594,24 @@ function lockOwnerIsGone(lockPath) {
|
|
|
8929
9594
|
return err.code === "ESRCH";
|
|
8930
9595
|
}
|
|
8931
9596
|
}
|
|
9597
|
+
function reclaimStaleLock(lockPath) {
|
|
9598
|
+
let before;
|
|
9599
|
+
try {
|
|
9600
|
+
before = fs14.statSync(lockPath);
|
|
9601
|
+
} catch {
|
|
9602
|
+
return true;
|
|
9603
|
+
}
|
|
9604
|
+
if (!lockOwnerIsGone(lockPath)) return false;
|
|
9605
|
+
try {
|
|
9606
|
+
const current = fs14.statSync(lockPath);
|
|
9607
|
+
const sameFile = current.ino === before.ino && current.birthtimeMs === before.birthtimeMs && current.mtimeMs === before.mtimeMs;
|
|
9608
|
+
if (!sameFile) return false;
|
|
9609
|
+
fs14.rmSync(lockPath, { force: true });
|
|
9610
|
+
return true;
|
|
9611
|
+
} catch {
|
|
9612
|
+
return false;
|
|
9613
|
+
}
|
|
9614
|
+
}
|
|
8932
9615
|
function lockFileIsAncient(lockPath) {
|
|
8933
9616
|
try {
|
|
8934
9617
|
return Date.now() - fs14.statSync(lockPath).mtimeMs > STALE_RUN_MS;
|
|
@@ -8986,7 +9669,6 @@ function swapOnce(stagingDir, outDir) {
|
|
|
8986
9669
|
throw copyErr;
|
|
8987
9670
|
}
|
|
8988
9671
|
}
|
|
8989
|
-
if (hadPrevious) fs14.rmSync(backupDir, { recursive: true, force: true });
|
|
8990
9672
|
} catch (err) {
|
|
8991
9673
|
if (hadPrevious && fs14.existsSync(backupDir)) {
|
|
8992
9674
|
if (fs14.existsSync(outDir)) fs14.rmSync(outDir, { recursive: true, force: true });
|
|
@@ -8994,6 +9676,16 @@ function swapOnce(stagingDir, outDir) {
|
|
|
8994
9676
|
}
|
|
8995
9677
|
throw err;
|
|
8996
9678
|
}
|
|
9679
|
+
if (hadPrevious) {
|
|
9680
|
+
try {
|
|
9681
|
+
fs14.rmSync(backupDir, { recursive: true, force: true });
|
|
9682
|
+
} catch (err) {
|
|
9683
|
+
process.stderr.write(
|
|
9684
|
+
`\u26A0 The previous snapshot could not be removed from ${backupDir} (${err.message}). The new snapshot is in place; delete that directory by hand when convenient.
|
|
9685
|
+
`
|
|
9686
|
+
);
|
|
9687
|
+
}
|
|
9688
|
+
}
|
|
8997
9689
|
}
|
|
8998
9690
|
function sleepSync(milliseconds) {
|
|
8999
9691
|
const buffer = new Int32Array(new SharedArrayBuffer(4));
|
|
@@ -9016,22 +9708,18 @@ function artifactWarnings(artifacts, sarif) {
|
|
|
9016
9708
|
}
|
|
9017
9709
|
return messages;
|
|
9018
9710
|
}
|
|
9711
|
+
function installHookStdoutRedirect() {
|
|
9712
|
+
return installStdoutRouter();
|
|
9713
|
+
}
|
|
9019
9714
|
function runAfterGenerate(config, manifest) {
|
|
9020
9715
|
const hook = config.hooks.afterGenerate;
|
|
9021
9716
|
if (!hook) return null;
|
|
9022
|
-
const
|
|
9023
|
-
const stderrWrite = process.stderr.write.bind(process.stderr);
|
|
9024
|
-
const hookScope = new AsyncLocalStorage();
|
|
9025
|
-
const patchedWrite = ((...args) => hookScope.getStore() === true ? stderrWrite(...args) : stdoutWrite(...args));
|
|
9026
|
-
const restoreStdout = () => {
|
|
9027
|
-
if (process.stdout.write === patchedWrite) process.stdout.write = stdoutWrite;
|
|
9028
|
-
};
|
|
9717
|
+
const releaseRedirect = installHookStdoutRedirect();
|
|
9029
9718
|
let returned;
|
|
9030
9719
|
try {
|
|
9031
|
-
|
|
9032
|
-
returned = hookScope.run(true, () => hook(manifest));
|
|
9720
|
+
returned = hookStdoutScope.run(true, () => hook(manifest));
|
|
9033
9721
|
} catch (err) {
|
|
9034
|
-
|
|
9722
|
+
releaseRedirect();
|
|
9035
9723
|
const message = maskPathText(err.message, config.secretRules);
|
|
9036
9724
|
manifest.warnings.push(`hooks.afterGenerate failed: ${message}`);
|
|
9037
9725
|
manifest.totals.warningCount = manifest.warnings.length;
|
|
@@ -9039,10 +9727,10 @@ function runAfterGenerate(config, manifest) {
|
|
|
9039
9727
|
}
|
|
9040
9728
|
const thenable = returned;
|
|
9041
9729
|
if (!thenable || typeof thenable.then !== "function") {
|
|
9042
|
-
|
|
9730
|
+
releaseRedirect();
|
|
9043
9731
|
return null;
|
|
9044
9732
|
}
|
|
9045
|
-
return Promise.resolve(returned).finally(
|
|
9733
|
+
return Promise.resolve(returned).finally(releaseRedirect);
|
|
9046
9734
|
}
|
|
9047
9735
|
function partitionByBudget(files, config, fingerprint) {
|
|
9048
9736
|
const budgetFor = (file) => config.budgets.byLayer[file.layer] ?? config.budgets.default;
|
|
@@ -9053,23 +9741,14 @@ function partitionByBudget(files, config, fingerprint) {
|
|
|
9053
9741
|
if (bucket) bucket.push(file);
|
|
9054
9742
|
else buckets.set(budget, [file]);
|
|
9055
9743
|
}
|
|
9056
|
-
if (buckets.size
|
|
9057
|
-
|
|
9058
|
-
return partitionFiles(files,
|
|
9744
|
+
if (buckets.size === 0) return [];
|
|
9745
|
+
if (buckets.size === 1) {
|
|
9746
|
+
return partitionFiles(files, Array.from(buckets.keys())[0], fingerprint);
|
|
9059
9747
|
}
|
|
9060
|
-
|
|
9061
|
-
|
|
9062
|
-
|
|
9063
|
-
|
|
9064
|
-
const merged = renumberParts(parts, fingerprint);
|
|
9065
|
-
const byPath = new Map(files.map((file) => [file.relativePath, file]));
|
|
9066
|
-
for (const part of merged) {
|
|
9067
|
-
for (const included of part.metadata.includedFiles) {
|
|
9068
|
-
const file = byPath.get(included.relativePath);
|
|
9069
|
-
if (file) file.part = part.filename;
|
|
9070
|
-
}
|
|
9071
|
-
}
|
|
9072
|
-
return merged;
|
|
9748
|
+
return partitionFilesForBuckets(
|
|
9749
|
+
Array.from(buckets.entries()).sort(([left], [right]) => left - right).map(([budgetBytes, bucketFiles]) => ({ files: bucketFiles, budgetBytes })),
|
|
9750
|
+
fingerprint
|
|
9751
|
+
);
|
|
9073
9752
|
}
|
|
9074
9753
|
function generateSnapshot(configInput, options) {
|
|
9075
9754
|
assertUsableConfigInput(configInput);
|
|
@@ -9101,6 +9780,7 @@ function generateSnapshot(configInput, options) {
|
|
|
9101
9780
|
return false;
|
|
9102
9781
|
}) : collection.files;
|
|
9103
9782
|
const files = [];
|
|
9783
|
+
const derivationKey = derivationSettings(config);
|
|
9104
9784
|
const monolithBodies = /* @__PURE__ */ new Map();
|
|
9105
9785
|
const skipped = maskSkipRecords([...collection.skipped], config.secretRules);
|
|
9106
9786
|
const secretFindings = [];
|
|
@@ -9113,6 +9793,7 @@ function generateSnapshot(configInput, options) {
|
|
|
9113
9793
|
const fileWarnings = [];
|
|
9114
9794
|
const oversizeSummaryPaths = [];
|
|
9115
9795
|
const previewSummaryPaths = [];
|
|
9796
|
+
const skippedSummaryImports = /* @__PURE__ */ new Map();
|
|
9116
9797
|
const layerCounts = {};
|
|
9117
9798
|
const kindCounts = {};
|
|
9118
9799
|
let totalLines = 0;
|
|
@@ -9177,8 +9858,6 @@ function generateSnapshot(configInput, options) {
|
|
|
9177
9858
|
}
|
|
9178
9859
|
const hashSha256 = sha256Hex2(buffer);
|
|
9179
9860
|
const rawContent = decoded.text;
|
|
9180
|
-
const lineCount = countLines(rawContent);
|
|
9181
|
-
totalLines += lineCount;
|
|
9182
9861
|
totalSizeBytes += stats.size;
|
|
9183
9862
|
if (decoded.encoding !== "utf-8") {
|
|
9184
9863
|
encodingNotes.push({
|
|
@@ -9192,66 +9871,25 @@ function generateSnapshot(configInput, options) {
|
|
|
9192
9871
|
const language = detectLanguage(relativePath);
|
|
9193
9872
|
bump(layerCounts, layer2);
|
|
9194
9873
|
bump(kindCounts, kind);
|
|
9195
|
-
const
|
|
9196
|
-
rawContent,
|
|
9197
|
-
|
|
9198
|
-
|
|
9199
|
-
);
|
|
9200
|
-
|
|
9201
|
-
|
|
9202
|
-
|
|
9203
|
-
|
|
9204
|
-
|
|
9205
|
-
|
|
9206
|
-
|
|
9207
|
-
|
|
9208
|
-
|
|
9209
|
-
|
|
9210
|
-
|
|
9211
|
-
|
|
9212
|
-
|
|
9213
|
-
|
|
9214
|
-
}
|
|
9215
|
-
if (isLarge && inclusionMode !== "signatures") {
|
|
9216
|
-
const preview = truncatePreview(
|
|
9217
|
-
redacted,
|
|
9218
|
-
config.previewHeadLines,
|
|
9219
|
-
config.previewTailLines
|
|
9220
|
-
);
|
|
9221
|
-
finalContent = preview.content;
|
|
9222
|
-
isPreviewTruncated = preview.isTruncated;
|
|
9223
|
-
includedLines = preview.includedLines;
|
|
9224
|
-
inclusionMode = isPreviewTruncated ? "preview" : "full";
|
|
9225
|
-
}
|
|
9226
|
-
if (config.includeMonolith && (isPreviewTruncated || inclusionMode === "signatures")) {
|
|
9227
|
-
monolithBodies.set(relativePath, redacted);
|
|
9228
|
-
}
|
|
9229
|
-
let summary;
|
|
9230
|
-
let summaryQuality;
|
|
9231
|
-
if (Buffer.byteLength(rawContent, "utf8") > SUMMARY_MAX_SOURCE_BYTES) {
|
|
9232
|
-
oversizeSummaryPaths.push(relativePath);
|
|
9233
|
-
} else if (isPreviewTruncated && hasStructuralSummary(relativePath)) {
|
|
9234
|
-
previewSummaryPaths.push(relativePath);
|
|
9235
|
-
} else {
|
|
9236
|
-
try {
|
|
9237
|
-
const outcome = summarizeFile(relativePath, rawContent);
|
|
9238
|
-
summary = outcome?.summary ? maskSummaryStrings(outcome.summary, config.secretRules) : void 0;
|
|
9239
|
-
summaryQuality = outcome?.quality;
|
|
9240
|
-
if (isSourceSummary(summary) && summary.parseErrors) {
|
|
9241
|
-
fileWarnings.push({
|
|
9242
|
-
path: relativePath,
|
|
9243
|
-
message: `Parse errors in ${maskPathText(relativePath, config.secretRules)} (${summary.parseErrors}); its summary is incomplete.`
|
|
9244
|
-
});
|
|
9245
|
-
}
|
|
9246
|
-
} catch (err) {
|
|
9247
|
-
fileWarnings.push({
|
|
9248
|
-
path: relativePath,
|
|
9249
|
-
message: `Summary skipped for ${maskPathText(relativePath, config.secretRules)}: ${err.message}`.replace(
|
|
9250
|
-
/\s+/g,
|
|
9251
|
-
" "
|
|
9252
|
-
)
|
|
9253
|
-
});
|
|
9254
|
-
}
|
|
9874
|
+
const derivation = takeDerivation(absolutePath, hashSha256, derivationKey) ?? (() => {
|
|
9875
|
+
const fresh = deriveFile(relativePath, rawContent, stats.size, config);
|
|
9876
|
+
rememberDerivation(absolutePath, hashSha256, derivationKey, fresh);
|
|
9877
|
+
return fresh;
|
|
9878
|
+
})();
|
|
9879
|
+
const { lineCount } = derivation;
|
|
9880
|
+
totalLines += lineCount;
|
|
9881
|
+
secretFindings.push(...derivation.findings);
|
|
9882
|
+
if (derivation.isLarge) largeFileCount += 1;
|
|
9883
|
+
if (derivation.summarySkip === "oversize") oversizeSummaryPaths.push(relativePath);
|
|
9884
|
+
else if (derivation.summarySkip === "preview") previewSummaryPaths.push(relativePath);
|
|
9885
|
+
if (derivation.dependentImports !== null) {
|
|
9886
|
+
skippedSummaryImports.set(relativePath, derivation.dependentImports);
|
|
9887
|
+
}
|
|
9888
|
+
if (derivation.summaryWarning !== null) {
|
|
9889
|
+
fileWarnings.push({ path: relativePath, message: derivation.summaryWarning });
|
|
9890
|
+
}
|
|
9891
|
+
if (config.includeMonolith && (derivation.isPreviewTruncated || derivation.inclusionMode === "signatures")) {
|
|
9892
|
+
monolithBodies.set(relativePath, derivation.redacted);
|
|
9255
9893
|
}
|
|
9256
9894
|
files.push({
|
|
9257
9895
|
relativePath,
|
|
@@ -9262,24 +9900,25 @@ function generateSnapshot(configInput, options) {
|
|
|
9262
9900
|
layer: layer2,
|
|
9263
9901
|
kind,
|
|
9264
9902
|
language,
|
|
9265
|
-
inclusionMode,
|
|
9266
|
-
isLarge,
|
|
9267
|
-
isPreviewTruncated,
|
|
9903
|
+
inclusionMode: derivation.inclusionMode,
|
|
9904
|
+
isLarge: derivation.isLarge,
|
|
9905
|
+
isPreviewTruncated: derivation.isPreviewTruncated,
|
|
9268
9906
|
originalLines: lineCount,
|
|
9269
|
-
includedLines,
|
|
9270
|
-
includedBytes: Buffer.byteLength(finalContent, "utf8"),
|
|
9271
|
-
summary,
|
|
9272
|
-
summaryQuality,
|
|
9907
|
+
includedLines: derivation.includedLines,
|
|
9908
|
+
includedBytes: Buffer.byteLength(derivation.finalContent, "utf8"),
|
|
9909
|
+
summary: derivation.summary,
|
|
9910
|
+
summaryQuality: derivation.summaryQuality,
|
|
9273
9911
|
...decoded.encoding === "utf-8" ? {} : { encoding: decoded.encoding },
|
|
9274
|
-
content: finalContent
|
|
9912
|
+
content: derivation.finalContent
|
|
9275
9913
|
});
|
|
9276
9914
|
}
|
|
9277
9915
|
if (changedScope !== null && changedPaths !== null && config.withDependents === true) {
|
|
9278
9916
|
const keep = new Set(changedPaths);
|
|
9279
9917
|
const resolver = buildImportResolver(files);
|
|
9280
9918
|
for (const file of files) {
|
|
9281
|
-
|
|
9282
|
-
if (
|
|
9919
|
+
const fileImports = isSourceSummary(file.summary) ? file.summary.imports : skippedSummaryImports.get(file.relativePath) ?? [];
|
|
9920
|
+
if (fileImports.length === 0) continue;
|
|
9921
|
+
if (importsChangedFile(file.relativePath, fileImports, changedPaths, resolver)) {
|
|
9283
9922
|
keep.add(file.relativePath);
|
|
9284
9923
|
}
|
|
9285
9924
|
}
|
|
@@ -9310,6 +9949,9 @@ function generateSnapshot(configInput, options) {
|
|
|
9310
9949
|
for (const path17 of [...monolithBodies.keys()]) {
|
|
9311
9950
|
if (!keep.has(path17)) monolithBodies.delete(path17);
|
|
9312
9951
|
}
|
|
9952
|
+
for (const path17 of [...skippedSummaryImports.keys()]) {
|
|
9953
|
+
if (!keep.has(path17)) skippedSummaryImports.delete(path17);
|
|
9954
|
+
}
|
|
9313
9955
|
}
|
|
9314
9956
|
if (changedScope !== null) {
|
|
9315
9957
|
for (const relativePath of unchangedPaths) {
|
|
@@ -9345,7 +9987,7 @@ function generateSnapshot(configInput, options) {
|
|
|
9345
9987
|
const shown = oversizeSummaryPaths.slice(0, ENCODING_PATHS_SHOWN).map((entry) => maskPathText(entry, config.secretRules));
|
|
9346
9988
|
const hidden = oversizeSummaryPaths.length - ENCODING_PATHS_SHOWN;
|
|
9347
9989
|
warnings.push(
|
|
9348
|
-
`${oversizeSummaryPaths.length} file(s) exceed ${Math.round(
|
|
9990
|
+
`${oversizeSummaryPaths.length} file(s) exceed ${Math.round(SUMMARY_MAX_SOURCE_BYTES2 / (1024 * 1024))} MB and were not parsed for a structural summary: ${shown.join(", ")}${hidden > 0 ? ` and ${hidden} more` : ""}. Their bodies are emitted as head/tail previews.`
|
|
9349
9991
|
);
|
|
9350
9992
|
}
|
|
9351
9993
|
for (const [encoding, bucket] of encodingBuckets) {
|
|
@@ -9369,11 +10011,13 @@ function generateSnapshot(configInput, options) {
|
|
|
9369
10011
|
).join("\n")
|
|
9370
10012
|
);
|
|
9371
10013
|
const fullContents = /* @__PURE__ */ new Map();
|
|
9372
|
-
|
|
9373
|
-
|
|
9374
|
-
|
|
9375
|
-
|
|
9376
|
-
|
|
10014
|
+
if (config.includeMonolith) {
|
|
10015
|
+
for (const file of files) {
|
|
10016
|
+
fullContents.set(
|
|
10017
|
+
file.relativePath,
|
|
10018
|
+
monolithBodies.get(file.relativePath) ?? file.content ?? ""
|
|
10019
|
+
);
|
|
10020
|
+
}
|
|
9377
10021
|
}
|
|
9378
10022
|
const parts = partitionByBudget(files, config, fingerprint);
|
|
9379
10023
|
const truncatedPreviewCount = files.filter((file) => file.isPreviewTruncated).length;
|
|
@@ -9634,7 +10278,7 @@ function generateSnapshot(configInput, options) {
|
|
|
9634
10278
|
};
|
|
9635
10279
|
shipHookState();
|
|
9636
10280
|
if (pendingHook) {
|
|
9637
|
-
pendingHook.then(
|
|
10281
|
+
const tracked = pendingHook.then(
|
|
9638
10282
|
() => {
|
|
9639
10283
|
try {
|
|
9640
10284
|
shipHookState();
|
|
@@ -9653,8 +10297,10 @@ function generateSnapshot(configInput, options) {
|
|
|
9653
10297
|
} catch {
|
|
9654
10298
|
}
|
|
9655
10299
|
}
|
|
9656
|
-
)
|
|
9657
|
-
|
|
10300
|
+
).finally(() => {
|
|
10301
|
+
if (pendingHooks.get(outDir) === tracked) pendingHooks.delete(outDir);
|
|
10302
|
+
});
|
|
10303
|
+
pendingHooks.set(outDir, tracked);
|
|
9658
10304
|
}
|
|
9659
10305
|
return manifest;
|
|
9660
10306
|
} catch (err) {
|
|
@@ -9672,15 +10318,15 @@ function generateSnapshot(configInput, options) {
|
|
|
9672
10318
|
}
|
|
9673
10319
|
var pendingHooks = /* @__PURE__ */ new Map();
|
|
9674
10320
|
async function generateSnapshotAsync(configInput, options) {
|
|
9675
|
-
|
|
10321
|
+
assertUsableConfigInput(configInput);
|
|
9676
10322
|
const config = isResolvedConfig(configInput) ? normalizeResolvedConfig(configInput) : resolveConfig(configInput ?? null);
|
|
10323
|
+
const manifest = generateSnapshot(config, options);
|
|
9677
10324
|
const { outDir } = resolveSafeOutputDirs(config);
|
|
9678
10325
|
const pending = pendingHooks.get(outDir);
|
|
9679
10326
|
if (pending !== void 0) {
|
|
9680
|
-
pendingHooks.delete(outDir);
|
|
9681
10327
|
await pending.catch(() => void 0);
|
|
9682
10328
|
}
|
|
9683
10329
|
return manifest;
|
|
9684
10330
|
}
|
|
9685
10331
|
|
|
9686
|
-
export { ARTIFACT_NAMES, CHECK_INTERVAL_MS, CONFIG_KEYS, DEFAULT_ANSWERS, DEFAULT_SECRET_PATTERNS, FAILURE_INTERVAL_MS, GENERATOR_NAME, GENERATOR_VERSION, HOOK_MARKER, LANGUAGE_PROFILES, MIN_MAX_PART_BYTES, MIN_MAX_PART_TOKENS, PACKAGE_LATEST_URL, PRESETS, PromptBack, PromptCancelled, REQUEST_TIMEOUT_MS, SCHEMA_VERSION, anchorForPath, appendGitignoreEntries, assertReplaceable, assertUsableConfigInput, buildImportResolver, bytesForTokens, cacheDirectory, cacheFilePath, checkForUpdate, classifyKind, classifyLayer, collectFiles, compareManifests, configDigest, countInvalidUtf8Bytes, countLines, decodeText, defaultIO, defineConfig, describeManifestShapeProblem, detectLanguage, detectPreset, displayPath, displayWidth, enforceBudget, escapeControls, estimateTokens, explainPath, fenceForContent, fenceForText, fetchLatestVersion, findConfigFile, findSecretInPath, firstRunMessage, formatNumber, formatTokens, formatUpdateNotice, generateInWorker, generateProjectTree, generateSarifReport, generateSnapshot, generateSnapshotAsync, getGitInfo, getPreset, gitignorePath, globToRegExp, hasPriorSnapshot, importsChangedFile, importsOfFile, importsOfText, installKind, installPreCommitHook, isBinaryBuffer, isBinaryExtension, isBinaryFile, isDiscoveryMode, isEffectivelyEmpty, isInteractive, isNewer, isResolvedConfig, isSensitivePath, isSkeletonizable, isTsxFileName, listPresetNames, loadConfigFile, loadConfigFileDetailed, loadManifest, lockPathFor, markResolvedConfig, matchRule, matchesExclude, normalizeResolvedConfig, parseByteSize, partitionFiles, planFirstRun, profileForPath, renderConfig, renderDiffText, renderExplainText, renumberParts, resolveChangedScope, resolveConfig, resolveSafeOutputDirs, resolveSpecifier, runInit, scanAndRedactSecrets, shouldCheck, skeletonizeTypeScript, stripJsonComments, summarizeJson, summarizeMarkdown, summarizeTypeScriptSource, summarizeWithProfile, supportsTypeScriptImports, truncatePreview, truncateToBytes, unknownPresetMessage, updateCommand, validateSnapshot, workerEntry, wrapLines };
|
|
10332
|
+
export { ARTIFACT_NAMES, CHECK_INTERVAL_MS, CONFIG_KEYS, DEFAULT_ANSWERS, DEFAULT_SECRET_PATTERNS, FAILURE_INTERVAL_MS, GENERATOR_NAME, GENERATOR_VERSION, HOOK_MARKER, LANGUAGE_PROFILES, MIN_MAX_PART_BYTES, MIN_MAX_PART_TOKENS, PACKAGE_LATEST_URL, PRESETS, PromptBack, PromptCancelled, REQUEST_TIMEOUT_MS, SCHEMA_VERSION, anchorForPath, appendGitignoreEntries, assertReplaceable, assertUsableConfigInput, buildImportResolver, bytesForTokens, cacheDirectory, cacheFilePath, checkForUpdate, classifyKind, classifyLayer, collectFiles, compareManifests, configDigest, countInvalidUtf8Bytes, countLines, decodeText, defaultIO, defineConfig, describeManifestShapeProblem, detectLanguage, detectPreset, displayPath, displayWidth, enforceBudget, escapeControls, estimateTokens, explainPath, fenceForContent, fenceForText, fetchLatestVersion, findConfigFile, findSecretInPath, firstRunMessage, formatNumber, formatTokens, formatUpdateNotice, generateInWorker, generateProjectTree, generateSarifReport, generateSnapshot, generateSnapshotAsync, getGitInfo, getPreset, gitignorePath, globToRegExp, hasPriorSnapshot, importsChangedFile, importsOfFile, importsOfText, installKind, installPreCommitHook, isBinaryBuffer, isBinaryExtension, isBinaryFile, isDiscoveryMode, isEffectivelyEmpty, isInteractive, isNewer, isResolvedConfig, isSensitivePath, isSkeletonizable, isTsxFileName, listPresetNames, loadConfigFile, loadConfigFileDetailed, loadManifest, lockPathFor, markResolvedConfig, matchRule, matchesExclude, normalizeResolvedConfig, parseByteSize, partitionFiles, planFirstRun, profileForPath, renderConfig, renderDiffText, renderExplainText, renumberParts, resolveChangedScope, resolveConfig, resolveSafeOutputDirs, resolveSpecifier, runInit, runWithStdoutCapture, scanAndRedactSecrets, shouldCheck, skeletonizeTypeScript, stripJsonComments, summarizeJson, summarizeMarkdown, summarizeTypeScriptSource, summarizeWithProfile, supportsTypeScriptImports, truncatePreview, truncateToBytes, unknownPresetMessage, updateCommand, validateSnapshot, workerEntry, wrapLines };
|