codebase-snapshot 0.9.9 → 0.9.12
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 +494 -0
- package/README.md +3 -3
- package/dist/bin.js +5227 -3461
- package/dist/{chunk-KUVFTJHK.js → chunk-3H45YMBG.js} +1848 -507
- package/dist/{chunk-HQBEQ67K.js → chunk-5OR3WVPW.js} +366 -84
- package/dist/cli.cjs +5245 -3499
- package/dist/cli.d.cts +282 -3
- package/dist/cli.d.ts +282 -3
- package/dist/cli.js +160 -38
- package/dist/generate-worker-D5Xgh27c.d.cts +57 -0
- package/dist/generate-worker-D5Xgh27c.d.ts +57 -0
- package/dist/index.cjs +1799 -504
- package/dist/index.d.cts +293 -4
- package/dist/index.d.ts +293 -4
- package/dist/index.js +1 -3
- package/dist/mcp.cjs +5670 -4877
- package/dist/mcp.d.cts +70 -2
- package/dist/mcp.d.ts +70 -2
- package/dist/mcp.js +2 -4
- package/dist/{types-Ca4PmkiQ.d.cts → types-CTewen3J.d.cts} +115 -2
- package/dist/{types-Ca4PmkiQ.d.ts → types-CTewen3J.d.ts} +115 -2
- package/docs/agent.md +1 -1
- package/docs/cli.md +21 -3
- package/docs/configuration.md +5 -0
- package/docs/how-to-feed-a-repo-to-claude.md +61 -0
- package/docs/mcp.md +23 -10
- package/docs/output.md +36 -4
- package/docs/stale-context-is-your-bug.md +13 -0
- package/docs/vs-repomix.md +1 -1
- package/package.json +5 -3
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as crypto2 from 'crypto';
|
|
2
2
|
import { createHash } from 'crypto';
|
|
3
3
|
import { AsyncLocalStorage } from 'async_hooks';
|
|
4
|
-
import * as
|
|
4
|
+
import * as fs14 from 'fs';
|
|
5
5
|
import * as os from 'os';
|
|
6
|
-
import * as
|
|
6
|
+
import * as path16 from 'path';
|
|
7
7
|
import ts from 'typescript';
|
|
8
8
|
import { createRequire } from 'module';
|
|
9
|
-
import { pathToFileURL } from 'url';
|
|
9
|
+
import { pathToFileURL, fileURLToPath } from 'url';
|
|
10
10
|
import * as childProcess from 'child_process';
|
|
11
|
+
import { execFileSync } from 'child_process';
|
|
11
12
|
import * as https from 'https';
|
|
12
13
|
import * as readline from 'readline';
|
|
14
|
+
import { Worker } from 'worker_threads';
|
|
13
15
|
|
|
14
16
|
// src/index.ts
|
|
15
17
|
|
|
@@ -125,6 +127,59 @@ var cfamilyProfile = {
|
|
|
125
127
|
}
|
|
126
128
|
};
|
|
127
129
|
|
|
130
|
+
// src/ast/profiles/dart.ts
|
|
131
|
+
var IMPORT = /^import\s+['"]([^'"]+)['"]/;
|
|
132
|
+
var PART = /^part(?:\s+of)?\s+['"]?([\w./]+)['"]?/;
|
|
133
|
+
var TYPE = /^(?:abstract\s+|base\s+|final\s+|sealed\s+|interface\s+)*(class|mixin|enum|extension|typedef)\s+([A-Za-z_]\w*)/;
|
|
134
|
+
var FUNC = /^(?:Future<[^>]*>|Stream<[^>]*>|void|bool|int|double|num|String|dynamic|var|[A-Z][\w<>, ?]*)\s+([A-Za-z_]\w*)\s*[(<]/;
|
|
135
|
+
var VARIABLE = /^(?:late\s+)?(?:final|const|var)\s+(?:[\w<>?,. ]+?\s+)?([A-Za-z_]\w*)\s*=/;
|
|
136
|
+
var dartProfile = {
|
|
137
|
+
id: "dart",
|
|
138
|
+
extensions: [".dart"],
|
|
139
|
+
summarize(_path, content) {
|
|
140
|
+
const summary = emptySummary();
|
|
141
|
+
eachLine(content, (line) => {
|
|
142
|
+
const trimmed = line.trim();
|
|
143
|
+
if (!trimmed || trimmed.startsWith("//")) return;
|
|
144
|
+
const imported = IMPORT.exec(trimmed);
|
|
145
|
+
if (imported) {
|
|
146
|
+
summary.imports.push(imported[1]);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (/^part\b/.test(trimmed)) {
|
|
150
|
+
const part = PART.exec(trimmed);
|
|
151
|
+
summary.directives.push(part ? `part ${part[1]}` : "part");
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
if (!isTopLevel(line)) return;
|
|
155
|
+
const type = TYPE.exec(trimmed);
|
|
156
|
+
if (type) {
|
|
157
|
+
const [, keyword, name] = type;
|
|
158
|
+
if (keyword === "mixin" || keyword === "extension") summary.types.push(name);
|
|
159
|
+
else if (keyword === "enum") summary.enums.push(name);
|
|
160
|
+
else if (keyword === "typedef") summary.types.push(name);
|
|
161
|
+
else summary.classes.push(name);
|
|
162
|
+
if (isPublicName(name)) summary.exports.push(name);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
const func = FUNC.exec(trimmed);
|
|
166
|
+
if (func) {
|
|
167
|
+
const name = func[1];
|
|
168
|
+
summary.functions.push(name);
|
|
169
|
+
if (isPublicName(name)) summary.exports.push(name);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const variable = VARIABLE.exec(trimmed);
|
|
173
|
+
if (variable) {
|
|
174
|
+
const name = variable[1];
|
|
175
|
+
summary.variables.push(name);
|
|
176
|
+
if (isPublicName(name)) summary.exports.push(name);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
return { summary: finalizeSummary(summary), quality: "heuristic" };
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
|
|
128
183
|
// src/ast/profiles/dotnet.ts
|
|
129
184
|
var USING2 = /^using\s+([\w.]+)\s*;/;
|
|
130
185
|
var TYPE_DECL2 = /^\s*(?:public\s+|internal\s+|private\s+|protected\s+|sealed\s+|abstract\s+|static\s+|partial\s+|readonly\s+)*(class|interface|struct|record|enum)\s+([A-Za-z_]\w*)/;
|
|
@@ -165,8 +220,8 @@ var dotnetProfile = {
|
|
|
165
220
|
};
|
|
166
221
|
|
|
167
222
|
// src/ast/profiles/go.ts
|
|
168
|
-
var
|
|
169
|
-
var
|
|
223
|
+
var FUNC2 = /^func\s+(?:\([^)]*\)\s*)?([A-Za-z_]\w*)/;
|
|
224
|
+
var TYPE2 = /^type\s+([A-Za-z_]\w*)\s+(.*)$/;
|
|
170
225
|
var VAR_CONST = /^(?:var|const)\s+([A-Za-z_]\w*)/;
|
|
171
226
|
var IMPORT_SINGLE = /^import\s+(?:[\w.]+\s+)?"([^"]+)"/;
|
|
172
227
|
var IMPORT_OPEN = /^import\s*\(/;
|
|
@@ -199,14 +254,14 @@ var goProfile = {
|
|
|
199
254
|
return;
|
|
200
255
|
}
|
|
201
256
|
if (!isTopLevel(line)) return;
|
|
202
|
-
const method =
|
|
257
|
+
const method = FUNC2.exec(line);
|
|
203
258
|
if (method) {
|
|
204
259
|
const name = method[1];
|
|
205
260
|
summary.functions.push(name);
|
|
206
261
|
if (isCapitalizedName(name)) summary.exports.push(name);
|
|
207
262
|
return;
|
|
208
263
|
}
|
|
209
|
-
const declared =
|
|
264
|
+
const declared = TYPE2.exec(line);
|
|
210
265
|
if (declared) {
|
|
211
266
|
const name = declared[1];
|
|
212
267
|
const body = declared[2].trim();
|
|
@@ -226,8 +281,54 @@ var goProfile = {
|
|
|
226
281
|
}
|
|
227
282
|
};
|
|
228
283
|
|
|
284
|
+
// src/ast/profiles/graphql.ts
|
|
285
|
+
var DEFINITION = /^(?:extend\s+)?(type|interface|input|enum|union|scalar|schema)\s+([A-Za-z_]\w*)/;
|
|
286
|
+
var OPERATION = /^(?:query|mutation|subscription)\s+([A-Za-z_]\w*)/;
|
|
287
|
+
var DIRECTIVE = /^directive\s+@([A-Za-z_]\w*)/;
|
|
288
|
+
var IMPORT2 = /^#import\s+"([^"]+)"/;
|
|
289
|
+
var graphqlProfile = {
|
|
290
|
+
id: "graphql",
|
|
291
|
+
extensions: [".graphql", ".gql"],
|
|
292
|
+
summarize(_path, content) {
|
|
293
|
+
const summary = emptySummary();
|
|
294
|
+
eachLine(content, (line) => {
|
|
295
|
+
const trimmed = line.trim();
|
|
296
|
+
if (!trimmed || trimmed.startsWith("#") && !IMPORT2.test(trimmed)) return;
|
|
297
|
+
const imported = IMPORT2.exec(trimmed);
|
|
298
|
+
if (imported) {
|
|
299
|
+
summary.imports.push(imported[1]);
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
const directive = DIRECTIVE.exec(trimmed);
|
|
303
|
+
if (directive) {
|
|
304
|
+
summary.functions.push(`@${directive[1]}`);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
const definition = DEFINITION.exec(trimmed);
|
|
308
|
+
if (definition) {
|
|
309
|
+
const [, keyword, name] = definition;
|
|
310
|
+
if (keyword === "interface") summary.interfaces.push(name);
|
|
311
|
+
else if (keyword === "enum") summary.enums.push(name);
|
|
312
|
+
else if (keyword === "input" || keyword === "scalar" || keyword === "union") {
|
|
313
|
+
summary.types.push(name);
|
|
314
|
+
} else summary.classes.push(name);
|
|
315
|
+
summary.exports.push(name);
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
if (!isTopLevel(line)) return;
|
|
319
|
+
const operation = OPERATION.exec(trimmed);
|
|
320
|
+
if (operation) {
|
|
321
|
+
const name = operation[1];
|
|
322
|
+
summary.functions.push(name);
|
|
323
|
+
summary.routeHandlers.push(name);
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
return { summary: finalizeSummary(summary), quality: "heuristic" };
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
|
|
229
330
|
// src/ast/profiles/jvm.ts
|
|
230
|
-
var
|
|
331
|
+
var IMPORT3 = /^import\s+(?:static\s+)?([\w.]+?)(?:\.\*)?;?\s*$/;
|
|
231
332
|
var TYPE_DECL3 = /^\s*(?:public\s+|private\s+|protected\s+|internal\s+)?(?:final\s+|abstract\s+|open\s+|sealed\s+|data\s+|static\s+|partial\s+)*(class|interface|enum|record|object|trait)\s+(?:class\s+)?([A-Za-z_]\w*)/;
|
|
232
333
|
var KOTLIN_FUN = /^\s*(?:public\s+|private\s+|protected\s+|internal\s+|override\s+|suspend\s+|inline\s+|open\s+|operator\s+)*fun\s+(?:<[^>]*>\s*)?(?:[\w.]+\.)?([A-Za-z_]\w*)/;
|
|
233
334
|
var SCALA_DEF = /^\s*def\s+([A-Za-z_]\w*)/;
|
|
@@ -235,9 +336,9 @@ var JAVA_METHOD = /^\s+(?:public|private|protected)\s+(?:static\s+)?(?:final\s+)
|
|
|
235
336
|
var jvmProfile = {
|
|
236
337
|
id: "jvm",
|
|
237
338
|
extensions: [".java", ".kt", ".kts", ".scala"],
|
|
238
|
-
summarize(
|
|
339
|
+
summarize(path17, content) {
|
|
239
340
|
const summary = emptySummary();
|
|
240
|
-
const lower =
|
|
341
|
+
const lower = path17.toLowerCase();
|
|
241
342
|
const isJava = lower.endsWith(".java");
|
|
242
343
|
const isKotlin = lower.endsWith(".kt") || lower.endsWith(".kts");
|
|
243
344
|
eachLine(content, (line) => {
|
|
@@ -245,7 +346,7 @@ var jvmProfile = {
|
|
|
245
346
|
if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("*") || trimmed.startsWith("/*")) {
|
|
246
347
|
return;
|
|
247
348
|
}
|
|
248
|
-
const imported =
|
|
349
|
+
const imported = IMPORT3.exec(trimmed);
|
|
249
350
|
if (imported) {
|
|
250
351
|
summary.imports.push(imported[1]);
|
|
251
352
|
return;
|
|
@@ -326,8 +427,50 @@ var phpProfile = {
|
|
|
326
427
|
}
|
|
327
428
|
};
|
|
328
429
|
|
|
430
|
+
// src/ast/profiles/protobuf.ts
|
|
431
|
+
var DECLARATION = /^(?:message|enum|service|extend)\s+([A-Za-z_]\w*)/;
|
|
432
|
+
var RPC = /^\s*rpc\s+([A-Za-z_]\w*)/;
|
|
433
|
+
var IMPORT4 = /^\s*import\s+(?:public\s+|weak\s+)?"([^"]+)"/;
|
|
434
|
+
var PACKAGE = /^\s*package\s+([\w.]+)\s*;/;
|
|
435
|
+
var protobufProfile = {
|
|
436
|
+
id: "protobuf",
|
|
437
|
+
extensions: [".proto"],
|
|
438
|
+
summarize(_path, content) {
|
|
439
|
+
const summary = emptySummary();
|
|
440
|
+
eachLine(content, (line) => {
|
|
441
|
+
const trimmed = line.trim();
|
|
442
|
+
if (!trimmed || trimmed.startsWith("//")) return;
|
|
443
|
+
const imported = IMPORT4.exec(line);
|
|
444
|
+
if (imported) {
|
|
445
|
+
summary.imports.push(imported[1]);
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
const pkg = PACKAGE.exec(line);
|
|
449
|
+
if (pkg) {
|
|
450
|
+
summary.directives.push(`package ${pkg[1]}`);
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
const rpc = RPC.exec(line);
|
|
454
|
+
if (rpc) {
|
|
455
|
+
const name2 = rpc[1];
|
|
456
|
+
summary.functions.push(name2);
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
const declaration = DECLARATION.exec(trimmed);
|
|
460
|
+
if (!declaration) return;
|
|
461
|
+
const name = declaration[1];
|
|
462
|
+
const keyword = /^\w+/.exec(trimmed)?.[0] ?? "";
|
|
463
|
+
if (keyword === "service") summary.interfaces.push(name);
|
|
464
|
+
else if (keyword === "enum") summary.enums.push(name);
|
|
465
|
+
else summary.classes.push(name);
|
|
466
|
+
summary.exports.push(name);
|
|
467
|
+
});
|
|
468
|
+
return { summary: finalizeSummary(summary), quality: "heuristic" };
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
|
|
329
472
|
// src/ast/profiles/python.ts
|
|
330
|
-
var
|
|
473
|
+
var IMPORT5 = /^import\s+(.+)$/;
|
|
331
474
|
var FROM_IMPORT = /^from\s+([\w.]+)\s+import\s+(.+)$/;
|
|
332
475
|
var CLASS = /^class\s+([A-Za-z_]\w*)/;
|
|
333
476
|
var DEF = /^(?:async\s+)?def\s+([A-Za-z_]\w*)/;
|
|
@@ -353,7 +496,7 @@ var pythonProfile = {
|
|
|
353
496
|
routePending = false;
|
|
354
497
|
return;
|
|
355
498
|
}
|
|
356
|
-
const imported =
|
|
499
|
+
const imported = IMPORT5.exec(trimmed);
|
|
357
500
|
if (imported) {
|
|
358
501
|
for (const part of imported[1].split(",")) {
|
|
359
502
|
const name = part.trim().split(/\s+as\s+/)[0].trim();
|
|
@@ -437,7 +580,7 @@ var FN = /^(pub(?:\([^)]*\))?\s+)?(?:const\s+)?(?:async\s+)?(?:unsafe\s+)?fn\s+(
|
|
|
437
580
|
var STRUCT = /^(pub(?:\([^)]*\))?\s+)?struct\s+([A-Za-z_]\w*)/;
|
|
438
581
|
var TRAIT = /^(pub(?:\([^)]*\))?\s+)?(?:unsafe\s+)?trait\s+([A-Za-z_]\w*)/;
|
|
439
582
|
var ENUM = /^(pub(?:\([^)]*\))?\s+)?enum\s+([A-Za-z_]\w*)/;
|
|
440
|
-
var
|
|
583
|
+
var TYPE3 = /^(pub(?:\([^)]*\))?\s+)?type\s+([A-Za-z_]\w*)/;
|
|
441
584
|
var MOD = /^(pub(?:\([^)]*\))?\s+)?mod\s+([A-Za-z_]\w*)/;
|
|
442
585
|
var IMPL = /^impl(?:<[^>]*>)?\s+(?:[\w:<>]+\s+for\s+)?([A-Za-z_]\w*)/;
|
|
443
586
|
var rustProfile = {
|
|
@@ -479,7 +622,7 @@ var rustProfile = {
|
|
|
479
622
|
if (isPublic) summary.exports.push(enumeration[2]);
|
|
480
623
|
return;
|
|
481
624
|
}
|
|
482
|
-
const alias =
|
|
625
|
+
const alias = TYPE3.exec(line);
|
|
483
626
|
if (alias) {
|
|
484
627
|
summary.types.push(alias[2]);
|
|
485
628
|
if (isPublic) summary.exports.push(alias[2]);
|
|
@@ -506,6 +649,16 @@ var ROUTE_HANDLER_NAMES = /* @__PURE__ */ new Set([
|
|
|
506
649
|
"HEAD",
|
|
507
650
|
"OPTIONS"
|
|
508
651
|
]);
|
|
652
|
+
function bindingNames(name, into) {
|
|
653
|
+
if (ts.isIdentifier(name)) {
|
|
654
|
+
into.push(name.text);
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
for (const element of name.elements) {
|
|
658
|
+
if (ts.isOmittedExpression(element)) continue;
|
|
659
|
+
bindingNames(element.name, into);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
509
662
|
function summarizeTypeScriptSource(relativePath, content) {
|
|
510
663
|
const summary = {
|
|
511
664
|
directives: [],
|
|
@@ -606,12 +759,15 @@ function summarizeTypeScriptSource(relativePath, content) {
|
|
|
606
759
|
}
|
|
607
760
|
if (node.exportClause && ts.isNamedExports(node.exportClause)) {
|
|
608
761
|
for (const element of node.exportClause.elements) {
|
|
609
|
-
|
|
762
|
+
const exported = element.name.text;
|
|
763
|
+
summary.exports.push(exported);
|
|
764
|
+
if (ROUTE_HANDLER_NAMES.has(exported)) summary.routeHandlers.push(exported);
|
|
610
765
|
}
|
|
611
766
|
}
|
|
612
767
|
}
|
|
613
768
|
if (ts.isExportAssignment(node)) {
|
|
614
769
|
summary.exports.push("default");
|
|
770
|
+
if (rendersJsx(node.expression)) summary.reactComponents.push("default");
|
|
615
771
|
}
|
|
616
772
|
if (ts.isFunctionDeclaration(node)) {
|
|
617
773
|
const exported = isExported(node);
|
|
@@ -649,16 +805,18 @@ function summarizeTypeScriptSource(relativePath, content) {
|
|
|
649
805
|
if (ts.isVariableStatement(node)) {
|
|
650
806
|
const exported = isExported(node);
|
|
651
807
|
for (const declaration of node.declarationList.declarations) {
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
808
|
+
const bound = [];
|
|
809
|
+
bindingNames(declaration.name, bound);
|
|
810
|
+
for (const name of bound) {
|
|
811
|
+
summary.variables.push(name);
|
|
812
|
+
if (exported) {
|
|
813
|
+
summary.exports.push(name);
|
|
814
|
+
if (ROUTE_HANDLER_NAMES.has(name)) summary.routeHandlers.push(name);
|
|
815
|
+
}
|
|
816
|
+
if (/^use[A-Z0-9]/.test(name)) summary.hooks.push(name);
|
|
817
|
+
if (/^[A-Z]/.test(name) && declaration.initializer !== void 0 && rendersJsx(declaration.initializer)) {
|
|
818
|
+
summary.reactComponents.push(name);
|
|
819
|
+
}
|
|
662
820
|
}
|
|
663
821
|
}
|
|
664
822
|
}
|
|
@@ -690,16 +848,16 @@ function summarizeTypeScriptSource(relativePath, content) {
|
|
|
690
848
|
// src/ast/profiles/sfc.ts
|
|
691
849
|
var SCRIPT_BLOCK = /<script[^>]*>([\s\S]*?)<\/script>/gi;
|
|
692
850
|
var ASTRO_FRONTMATTER = /^\uFEFF?---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*(?=\r?\n|$)/;
|
|
693
|
-
function componentName(
|
|
694
|
-
const base =
|
|
851
|
+
function componentName(path17) {
|
|
852
|
+
const base = path17.split("/").pop() ?? path17;
|
|
695
853
|
return base.replace(/\.[^.]+$/, "");
|
|
696
854
|
}
|
|
697
855
|
var sfcProfile = {
|
|
698
856
|
id: "sfc",
|
|
699
857
|
extensions: [".vue", ".svelte", ".astro"],
|
|
700
|
-
summarize(
|
|
858
|
+
summarize(path17, content) {
|
|
701
859
|
let script = "";
|
|
702
|
-
if (
|
|
860
|
+
if (path17.toLowerCase().endsWith(".astro")) {
|
|
703
861
|
const frontmatter = ASTRO_FRONTMATTER.exec(content);
|
|
704
862
|
if (frontmatter) script += `${frontmatter[1]}
|
|
705
863
|
`;
|
|
@@ -708,7 +866,7 @@ var sfcProfile = {
|
|
|
708
866
|
script += `${match[1]}
|
|
709
867
|
`;
|
|
710
868
|
}
|
|
711
|
-
const name = componentName(
|
|
869
|
+
const name = componentName(path17);
|
|
712
870
|
if (!script.trim()) {
|
|
713
871
|
const summary2 = emptySummary();
|
|
714
872
|
if (name) {
|
|
@@ -720,7 +878,158 @@ var sfcProfile = {
|
|
|
720
878
|
const summary = summarizeTypeScriptSource(`${name}.ts`, script);
|
|
721
879
|
summary.reactComponents.push(name);
|
|
722
880
|
summary.exports.push(name);
|
|
723
|
-
return { summary, quality: "ast" };
|
|
881
|
+
return { summary: finalizeSummary(summary), quality: "ast" };
|
|
882
|
+
}
|
|
883
|
+
};
|
|
884
|
+
|
|
885
|
+
// src/ast/profiles/sql.ts
|
|
886
|
+
var CREATE = /^\s*CREATE\s+(?:OR\s+REPLACE\s+)?(?:TEMP(?:ORARY)?\s+)?(TABLE|VIEW|MATERIALIZED\s+VIEW|FUNCTION|PROCEDURE|INDEX|TYPE|SCHEMA|TRIGGER|SEQUENCE)\s+(?:IF\s+NOT\s+EXISTS\s+)?([A-Za-z_"][\w."]*)/i;
|
|
887
|
+
var ALTER = /^\s*ALTER\s+(TABLE|VIEW)\s+(?:IF\s+EXISTS\s+)?([A-Za-z_"][\w."]*)/i;
|
|
888
|
+
function bareName(identifier) {
|
|
889
|
+
const last = identifier.split(".").pop() ?? identifier;
|
|
890
|
+
return last.replace(/["`[\]]/g, "");
|
|
891
|
+
}
|
|
892
|
+
var sqlProfile = {
|
|
893
|
+
id: "sql",
|
|
894
|
+
extensions: [".sql"],
|
|
895
|
+
summarize(_path, content) {
|
|
896
|
+
const summary = emptySummary();
|
|
897
|
+
const seen = /* @__PURE__ */ new Set();
|
|
898
|
+
const declare = (kind, identifier) => {
|
|
899
|
+
const name = bareName(identifier);
|
|
900
|
+
if (!name || seen.has(`${kind}:${name}`)) return;
|
|
901
|
+
seen.add(`${kind}:${name}`);
|
|
902
|
+
const normalized = kind.toUpperCase().replace(/\s+/g, " ");
|
|
903
|
+
if (normalized === "TABLE" || normalized.endsWith("VIEW")) summary.classes.push(name);
|
|
904
|
+
else if (normalized === "TYPE" || normalized === "SCHEMA" || normalized === "SEQUENCE") {
|
|
905
|
+
summary.types.push(name);
|
|
906
|
+
} else summary.functions.push(name);
|
|
907
|
+
summary.exports.push(name);
|
|
908
|
+
};
|
|
909
|
+
eachLine(content, (line) => {
|
|
910
|
+
const trimmed = line.trim();
|
|
911
|
+
if (!trimmed || trimmed.startsWith("--")) return;
|
|
912
|
+
const created = CREATE.exec(trimmed);
|
|
913
|
+
if (created) {
|
|
914
|
+
declare(String(created[1]), String(created[2]));
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
const altered = ALTER.exec(trimmed);
|
|
918
|
+
if (altered) declare(String(altered[1]), String(altered[2]));
|
|
919
|
+
});
|
|
920
|
+
return { summary: finalizeSummary(summary), quality: "heuristic" };
|
|
921
|
+
}
|
|
922
|
+
};
|
|
923
|
+
|
|
924
|
+
// src/ast/profiles/swift.ts
|
|
925
|
+
var MODIFIERS = "(?:@\\w+(?:\\([^)]*\\))?\\s+|(?:public|open|internal|fileprivate|private|final|static|mutating|nonmutating|override|required|convenience|lazy|weak|unowned)\\s+)*";
|
|
926
|
+
var TYPE4 = new RegExp(`^${MODIFIERS}(class|struct|enum|protocol|actor|extension)\\s+([A-Za-z_]\\w*)`);
|
|
927
|
+
var FUNC3 = new RegExp(`^${MODIFIERS}func\\s+([A-Za-z_]\\w*)`);
|
|
928
|
+
var PROPERTY = new RegExp(`^${MODIFIERS}(?:let|var)\\s+([A-Za-z_]\\w*)`);
|
|
929
|
+
var IMPORT6 = /^import\s+([A-Za-z_][\w.]*)/;
|
|
930
|
+
var swiftProfile = {
|
|
931
|
+
id: "swift",
|
|
932
|
+
extensions: [".swift"],
|
|
933
|
+
summarize(_path, content) {
|
|
934
|
+
const summary = emptySummary();
|
|
935
|
+
eachLine(content, (line) => {
|
|
936
|
+
const trimmed = line.trim();
|
|
937
|
+
if (!trimmed || trimmed.startsWith("//")) return;
|
|
938
|
+
const imported = IMPORT6.exec(trimmed);
|
|
939
|
+
if (imported) {
|
|
940
|
+
summary.imports.push(imported[1]);
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
943
|
+
if (!isTopLevel(line)) return;
|
|
944
|
+
const exported = /\b(?:public|open)\b/.test(trimmed);
|
|
945
|
+
const type = TYPE4.exec(trimmed);
|
|
946
|
+
if (type) {
|
|
947
|
+
const [, keyword, name] = type;
|
|
948
|
+
if (keyword === "protocol") summary.interfaces.push(name);
|
|
949
|
+
else if (keyword === "enum") summary.enums.push(name);
|
|
950
|
+
else if (keyword === "extension") summary.types.push(name);
|
|
951
|
+
else summary.classes.push(name);
|
|
952
|
+
if (exported) summary.exports.push(name);
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
const func = FUNC3.exec(trimmed);
|
|
956
|
+
if (func) {
|
|
957
|
+
const name = func[1];
|
|
958
|
+
summary.functions.push(name);
|
|
959
|
+
if (exported) summary.exports.push(name);
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
962
|
+
const property = PROPERTY.exec(trimmed);
|
|
963
|
+
if (property) {
|
|
964
|
+
const name = property[1];
|
|
965
|
+
summary.variables.push(name);
|
|
966
|
+
if (exported && isPublicName(name)) summary.exports.push(name);
|
|
967
|
+
}
|
|
968
|
+
});
|
|
969
|
+
return { summary: finalizeSummary(summary), quality: "heuristic" };
|
|
970
|
+
}
|
|
971
|
+
};
|
|
972
|
+
|
|
973
|
+
// src/ast/profiles/terraform.ts
|
|
974
|
+
var BLOCK = /^\s*(resource|data|module|provider|variable|output|locals|terraform)\b\s*("([^"]+)")?\s*("([^"]+)")?/;
|
|
975
|
+
var TERRAFORM_VERSION = /^\s*required_version\s*=\s*"([^"]+)"/;
|
|
976
|
+
var terraformProfile = {
|
|
977
|
+
id: "terraform",
|
|
978
|
+
extensions: [".tf", ".tfvars"],
|
|
979
|
+
summarize(_path, content) {
|
|
980
|
+
const summary = emptySummary();
|
|
981
|
+
eachLine(content, (line) => {
|
|
982
|
+
const trimmed = line.trim();
|
|
983
|
+
if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("//")) return;
|
|
984
|
+
const version = TERRAFORM_VERSION.exec(line);
|
|
985
|
+
if (version) {
|
|
986
|
+
summary.directives.push(`required_version ${version[1]}`);
|
|
987
|
+
return;
|
|
988
|
+
}
|
|
989
|
+
if (/^[A-Za-z_][\w-]*\s*=/.test(trimmed)) {
|
|
990
|
+
summary.variables.push(/^([A-Za-z_][\w-]*)/.exec(trimmed)?.[1] ?? trimmed);
|
|
991
|
+
return;
|
|
992
|
+
}
|
|
993
|
+
const block = BLOCK.exec(line);
|
|
994
|
+
if (!block) return;
|
|
995
|
+
const keyword = block[1];
|
|
996
|
+
const first = block[3];
|
|
997
|
+
const second = block[5];
|
|
998
|
+
switch (keyword) {
|
|
999
|
+
case "resource":
|
|
1000
|
+
case "data": {
|
|
1001
|
+
if (!first || !second) return;
|
|
1002
|
+
const name = `${first}.${second}`;
|
|
1003
|
+
summary.classes.push(name);
|
|
1004
|
+
summary.exports.push(name);
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
case "module": {
|
|
1008
|
+
if (!first) return;
|
|
1009
|
+
summary.imports.push(first);
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
1012
|
+
case "variable": {
|
|
1013
|
+
if (!first) return;
|
|
1014
|
+
summary.variables.push(first);
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
1017
|
+
case "output": {
|
|
1018
|
+
if (!first) return;
|
|
1019
|
+
summary.exports.push(first);
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
case "provider": {
|
|
1023
|
+
if (!first) return;
|
|
1024
|
+
summary.imports.push(`provider:${first}`);
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
default: {
|
|
1028
|
+
summary.directives.push(keyword);
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
});
|
|
1032
|
+
return { summary: finalizeSummary(summary), quality: "heuristic" };
|
|
724
1033
|
}
|
|
725
1034
|
};
|
|
726
1035
|
|
|
@@ -734,7 +1043,13 @@ var LANGUAGE_PROFILES = [
|
|
|
734
1043
|
phpProfile,
|
|
735
1044
|
rubyProfile,
|
|
736
1045
|
cfamilyProfile,
|
|
737
|
-
sfcProfile
|
|
1046
|
+
sfcProfile,
|
|
1047
|
+
swiftProfile,
|
|
1048
|
+
dartProfile,
|
|
1049
|
+
sqlProfile,
|
|
1050
|
+
graphqlProfile,
|
|
1051
|
+
protobufProfile,
|
|
1052
|
+
terraformProfile
|
|
738
1053
|
];
|
|
739
1054
|
var BY_EXTENSION = /* @__PURE__ */ new Map();
|
|
740
1055
|
for (const profile of LANGUAGE_PROFILES) {
|
|
@@ -742,15 +1057,15 @@ for (const profile of LANGUAGE_PROFILES) {
|
|
|
742
1057
|
BY_EXTENSION.set(extension.toLowerCase(), profile);
|
|
743
1058
|
}
|
|
744
1059
|
}
|
|
745
|
-
function profileForPath(
|
|
746
|
-
const lower =
|
|
1060
|
+
function profileForPath(path17) {
|
|
1061
|
+
const lower = path17.toLowerCase();
|
|
747
1062
|
const dot = lower.lastIndexOf(".");
|
|
748
1063
|
if (dot === -1) return null;
|
|
749
1064
|
return BY_EXTENSION.get(lower.slice(dot)) ?? null;
|
|
750
1065
|
}
|
|
751
|
-
function summarizeWithProfile(
|
|
752
|
-
const profile = profileForPath(
|
|
753
|
-
return profile ? profile.summarize(
|
|
1066
|
+
function summarizeWithProfile(path17, content) {
|
|
1067
|
+
const profile = profileForPath(path17);
|
|
1068
|
+
return profile ? profile.summarize(path17, content) : null;
|
|
754
1069
|
}
|
|
755
1070
|
|
|
756
1071
|
// src/ast/structured.ts
|
|
@@ -850,9 +1165,17 @@ function summarizeMarkdown(content) {
|
|
|
850
1165
|
const frontmatterKeys = [];
|
|
851
1166
|
const lines = content.replace(/^\uFEFF/, "").split("\n");
|
|
852
1167
|
let inFrontmatter = false;
|
|
1168
|
+
let fence = null;
|
|
1169
|
+
let closesFrontmatter = false;
|
|
1170
|
+
for (let index = 1; index < lines.length; index++) {
|
|
1171
|
+
if (lines[index].trim() === "---") {
|
|
1172
|
+
closesFrontmatter = true;
|
|
1173
|
+
break;
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
853
1176
|
for (let index = 0; index < lines.length; index++) {
|
|
854
1177
|
const line = lines[index];
|
|
855
|
-
if (index === 0 && line.trim() === "---") {
|
|
1178
|
+
if (index === 0 && line.trim() === "---" && closesFrontmatter) {
|
|
856
1179
|
inFrontmatter = true;
|
|
857
1180
|
continue;
|
|
858
1181
|
}
|
|
@@ -865,7 +1188,20 @@ function summarizeMarkdown(content) {
|
|
|
865
1188
|
if (match) frontmatterKeys.push(match[1]);
|
|
866
1189
|
continue;
|
|
867
1190
|
}
|
|
868
|
-
const
|
|
1191
|
+
const trimmed = line.trim();
|
|
1192
|
+
const fenceMatch = /^(`{3,}|~{3,})(.*)$/.exec(trimmed);
|
|
1193
|
+
if (fenceMatch) {
|
|
1194
|
+
const run = fenceMatch[1];
|
|
1195
|
+
const rest = fenceMatch[2].trim();
|
|
1196
|
+
if (fence === null) {
|
|
1197
|
+
fence = { marker: run[0], length: run.length };
|
|
1198
|
+
} else if (run[0] === fence.marker && run.length >= fence.length && rest === "") {
|
|
1199
|
+
fence = null;
|
|
1200
|
+
}
|
|
1201
|
+
continue;
|
|
1202
|
+
}
|
|
1203
|
+
if (fence !== null) continue;
|
|
1204
|
+
const headingMatch = /^(#{1,6})\s+(.+)$/.exec(trimmed);
|
|
869
1205
|
if (headingMatch) {
|
|
870
1206
|
headings.push(`${headingMatch[1]} ${headingMatch[2].trim()}`);
|
|
871
1207
|
}
|
|
@@ -875,6 +1211,52 @@ function summarizeMarkdown(content) {
|
|
|
875
1211
|
frontmatterKeys: Array.from(new Set(frontmatterKeys)).sort()
|
|
876
1212
|
};
|
|
877
1213
|
}
|
|
1214
|
+
function hasCollapsibleBody(node) {
|
|
1215
|
+
if (!("body" in node)) return false;
|
|
1216
|
+
const body = node.body;
|
|
1217
|
+
return body !== void 0 && ts.isBlock(body);
|
|
1218
|
+
}
|
|
1219
|
+
function isTsxFileName(fileName) {
|
|
1220
|
+
return fileName.toLowerCase().endsWith(".tsx");
|
|
1221
|
+
}
|
|
1222
|
+
function skeletonizeTypeScript(source, fileName = "file.ts") {
|
|
1223
|
+
const scriptKind = isTsxFileName(fileName) ? ts.ScriptKind.TSX : ts.ScriptKind.TS;
|
|
1224
|
+
const sourceFile = ts.createSourceFile(
|
|
1225
|
+
fileName,
|
|
1226
|
+
source,
|
|
1227
|
+
ts.ScriptTarget.Latest,
|
|
1228
|
+
true,
|
|
1229
|
+
scriptKind
|
|
1230
|
+
);
|
|
1231
|
+
const spans = [];
|
|
1232
|
+
const visit = (node) => {
|
|
1233
|
+
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
|
+
const body = node.body;
|
|
1235
|
+
const start = body.getStart(sourceFile);
|
|
1236
|
+
const end = body.getEnd();
|
|
1237
|
+
if (end > start) spans.push({ start, end });
|
|
1238
|
+
}
|
|
1239
|
+
ts.forEachChild(node, visit);
|
|
1240
|
+
};
|
|
1241
|
+
visit(sourceFile);
|
|
1242
|
+
if (spans.length === 0) return { text: source, collapsed: 0 };
|
|
1243
|
+
const outermost = [];
|
|
1244
|
+
for (const span of spans.sort((a, b) => a.start - b.start || b.end - a.end)) {
|
|
1245
|
+
const last = outermost[outermost.length - 1];
|
|
1246
|
+
if (last && span.start < last.end) continue;
|
|
1247
|
+
outermost.push(span);
|
|
1248
|
+
}
|
|
1249
|
+
let text2 = source;
|
|
1250
|
+
for (let index = outermost.length - 1; index >= 0; index -= 1) {
|
|
1251
|
+
const span = outermost[index];
|
|
1252
|
+
text2 = `${text2.slice(0, span.start)}{ /* ... */ }${text2.slice(span.end)}`;
|
|
1253
|
+
}
|
|
1254
|
+
return { text: text2, collapsed: outermost.length };
|
|
1255
|
+
}
|
|
1256
|
+
function isSkeletonizable(filePath) {
|
|
1257
|
+
const lower = filePath.toLowerCase();
|
|
1258
|
+
return lower.endsWith(".ts") || lower.endsWith(".tsx") || lower.endsWith(".mts") || lower.endsWith(".cts");
|
|
1259
|
+
}
|
|
878
1260
|
var MANIFEST_FILES = [
|
|
879
1261
|
"package.json",
|
|
880
1262
|
"pyproject.toml",
|
|
@@ -921,15 +1303,15 @@ var SKIPPED_WALK_DIRS = /* @__PURE__ */ new Set([
|
|
|
921
1303
|
]);
|
|
922
1304
|
function readSmallFile(fullPath) {
|
|
923
1305
|
try {
|
|
924
|
-
const stats =
|
|
1306
|
+
const stats = fs14.statSync(fullPath);
|
|
925
1307
|
if (!stats.isFile() || stats.size > MANIFEST_MAX_BYTES) return null;
|
|
926
|
-
return
|
|
1308
|
+
return fs14.readFileSync(fullPath, "utf8");
|
|
927
1309
|
} catch {
|
|
928
1310
|
return null;
|
|
929
1311
|
}
|
|
930
1312
|
}
|
|
931
1313
|
function readManifest(cwd, name) {
|
|
932
|
-
return readSmallFile(
|
|
1314
|
+
return readSmallFile(path16.join(cwd, name));
|
|
933
1315
|
}
|
|
934
1316
|
function dependenciesFromPackageJson(raw) {
|
|
935
1317
|
const names = /* @__PURE__ */ new Set();
|
|
@@ -989,7 +1371,7 @@ function pnpmWorkspacePatterns(raw) {
|
|
|
989
1371
|
function childDirectories(cwd, parent) {
|
|
990
1372
|
let entries;
|
|
991
1373
|
try {
|
|
992
|
-
entries =
|
|
1374
|
+
entries = fs14.readdirSync(path16.join(cwd, parent), { withFileTypes: true });
|
|
993
1375
|
} catch {
|
|
994
1376
|
return [];
|
|
995
1377
|
}
|
|
@@ -1024,13 +1406,13 @@ function workspacePackageDirs(cwd, patterns) {
|
|
|
1024
1406
|
function scanWorkspacePackages(cwd, patterns) {
|
|
1025
1407
|
const files = /* @__PURE__ */ new Set();
|
|
1026
1408
|
const dependencies = /* @__PURE__ */ new Set();
|
|
1027
|
-
for (const
|
|
1028
|
-
const full =
|
|
1409
|
+
for (const relative8 of workspacePackageDirs(cwd, patterns)) {
|
|
1410
|
+
const full = path16.join(cwd, relative8);
|
|
1029
1411
|
try {
|
|
1030
|
-
for (const entry of
|
|
1412
|
+
for (const entry of fs14.readdirSync(full, { withFileTypes: true })) files.add(entry.name);
|
|
1031
1413
|
} catch {
|
|
1032
1414
|
}
|
|
1033
|
-
const raw = readSmallFile(
|
|
1415
|
+
const raw = readSmallFile(path16.join(full, "package.json"));
|
|
1034
1416
|
if (raw !== null) {
|
|
1035
1417
|
for (const name of dependenciesFromPackageJson(raw)) dependencies.add(name);
|
|
1036
1418
|
}
|
|
@@ -1047,7 +1429,7 @@ function countMarkdownFiles(cwd) {
|
|
|
1047
1429
|
directoriesSeen += 1;
|
|
1048
1430
|
let entries;
|
|
1049
1431
|
try {
|
|
1050
|
-
entries =
|
|
1432
|
+
entries = fs14.readdirSync(current.dir, { withFileTypes: true });
|
|
1051
1433
|
} catch {
|
|
1052
1434
|
continue;
|
|
1053
1435
|
}
|
|
@@ -1058,7 +1440,7 @@ function countMarkdownFiles(cwd) {
|
|
|
1058
1440
|
}
|
|
1059
1441
|
if (!entry.isDirectory() || current.depth >= 2) continue;
|
|
1060
1442
|
if (entry.name.startsWith(".") || SKIPPED_WALK_DIRS.has(entry.name)) continue;
|
|
1061
|
-
queue.push({ dir:
|
|
1443
|
+
queue.push({ dir: path16.join(current.dir, entry.name), depth: current.depth + 1 });
|
|
1062
1444
|
}
|
|
1063
1445
|
}
|
|
1064
1446
|
return count;
|
|
@@ -1067,7 +1449,7 @@ function buildPresetContext(cwd) {
|
|
|
1067
1449
|
const files = /* @__PURE__ */ new Set();
|
|
1068
1450
|
const dirs = /* @__PURE__ */ new Set();
|
|
1069
1451
|
try {
|
|
1070
|
-
for (const entry of
|
|
1452
|
+
for (const entry of fs14.readdirSync(cwd, { withFileTypes: true })) {
|
|
1071
1453
|
if (entry.isDirectory()) dirs.add(entry.name);
|
|
1072
1454
|
else if (entry.isFile()) files.add(entry.name);
|
|
1073
1455
|
}
|
|
@@ -1084,7 +1466,7 @@ function buildPresetContext(cwd) {
|
|
|
1084
1466
|
const isWorkspace = files.has("pnpm-workspace.yaml") || files.has("nx.json") || files.has("turbo.json") || files.has("lerna.json") || packageJson !== void 0 && packageJson.includes('"workspaces"') || declaredWorkspaces.length > 0;
|
|
1085
1467
|
let workspace = { files: /* @__PURE__ */ new Set(), dependencies: /* @__PURE__ */ new Set() };
|
|
1086
1468
|
if (isWorkspace) {
|
|
1087
|
-
const pnpmRaw = files.has("pnpm-workspace.yaml") ? readSmallFile(
|
|
1469
|
+
const pnpmRaw = files.has("pnpm-workspace.yaml") ? readSmallFile(path16.join(cwd, "pnpm-workspace.yaml")) : null;
|
|
1088
1470
|
const declared = pnpmRaw === null ? [] : pnpmWorkspacePatterns(pnpmRaw);
|
|
1089
1471
|
const patterns = declared.length > 0 ? declared : declaredWorkspaces.length > 0 ? declaredWorkspaces : DEFAULT_WORKSPACE_PATTERNS;
|
|
1090
1472
|
workspace = scanWorkspacePackages(cwd, patterns);
|
|
@@ -1929,10 +2311,15 @@ function detectPreset(cwd, forced) {
|
|
|
1929
2311
|
// src/security/patterns.ts
|
|
1930
2312
|
var STRONG_CREDENTIAL_NAME = "(?:password|passwd|passphrase|pwd|api[_-]?key|api[_-]?secret|secret[_-]?access[_-]?key|secret[_-]?key|client[_-]?secret[_-]?key|client[_-]?secret|private[_-]?key|aws[_-]?secret[_-]?access[_-]?key|access[_-]?token|refresh[_-]?token|auth[_-]?token|session[_-]?token|secret|token)";
|
|
1931
2313
|
var WEAK_CREDENTIAL_NAME = "(?:pass|id[_-]?token|credentials?)";
|
|
1932
|
-
var NOT_A_LITERAL_VALUE = "(?!(?:string|number|boolean|bigint|symbol|unknown|any|never|void|null|undefined|true|false|process\\.env|Buffer|Record|object)\\b)";
|
|
1933
|
-
var
|
|
1934
|
-
var
|
|
1935
|
-
var
|
|
2314
|
+
var NOT_A_LITERAL_VALUE = "(?!(?:string|number|boolean|bigint|symbol|unknown|any|never|void|null|undefined|true|false|await|async|new|this|typeof|delete|yield|function|return|process\\.env|Buffer|Record|object)\\b)";
|
|
2315
|
+
var UNQUOTED_MIN_LENGTH = 2;
|
|
2316
|
+
var PROSE_CONTINUATION = String.raw`(?!\s+[a-z])`;
|
|
2317
|
+
var UNQUOTED_LITERAL = String.raw`(?![0-9]+(?![A-Za-z0-9_+=/%:.~-]))([A-Za-z0-9_][A-Za-z0-9_+=/%:~-]{${UNQUOTED_MIN_LENGTH - 1},511})(?![A-Za-z0-9_+=/%:.~-]|[({[])` + PROSE_CONTINUATION;
|
|
2318
|
+
var UNQUOTED_AMBIGUOUS_LITERAL = String.raw`(?![0-9]+(?![A-Za-z0-9_+=/%:.~-]))([A-Za-z0-9_][A-Za-z0-9_+=/%:~-]{7,511})(?![A-Za-z0-9_+=/%:.~-]|[({[])` + PROSE_CONTINUATION;
|
|
2319
|
+
var ASSIGN = String.raw`(?<![=!<>])[:=](?!=)`;
|
|
2320
|
+
var NOT_AFTER_QUOTE = String.raw`(?<!["'])`;
|
|
2321
|
+
var NOT_A_NAMESPACED_IDENTIFIER = String.raw`(?![a-z][a-z0-9]*(?:\.[a-z][a-z0-9-]*)+/)`;
|
|
2322
|
+
var QUOTED_VALUE = String.raw`((?:[^"'\\\n]|\\\\["'](?=[A-Za-z0-9_])|\\.){1,512})`;
|
|
1936
2323
|
var UNQUOTED_STRONG_NAME = `(?<!id[-_])${STRONG_CREDENTIAL_NAME}`;
|
|
1937
2324
|
var DEFAULT_SECRET_PATTERNS = [
|
|
1938
2325
|
{
|
|
@@ -1975,7 +2362,7 @@ var DEFAULT_SECRET_PATTERNS = [
|
|
|
1975
2362
|
// literal words matched. The value is masked alone, so the key survives.
|
|
1976
2363
|
rule: "aws-secret-access-key",
|
|
1977
2364
|
pattern: new RegExp(
|
|
1978
|
-
|
|
2365
|
+
`(?:["']${STRONG_CREDENTIAL_NAME}["']|${NOT_AFTER_QUOTE}${STRONG_CREDENTIAL_NAME})\\s{0,8}${ASSIGN}\\s{0,8}["']?([A-Za-z0-9/+=]{40})["']?`,
|
|
1979
2366
|
"i"
|
|
1980
2367
|
),
|
|
1981
2368
|
valueGroup: 1
|
|
@@ -2087,7 +2474,7 @@ var DEFAULT_SECRET_PATTERNS = [
|
|
|
2087
2474
|
// the key name.
|
|
2088
2475
|
rule: "credential-assignment",
|
|
2089
2476
|
pattern: new RegExp(
|
|
2090
|
-
`(?:["']${STRONG_CREDENTIAL_NAME}["']|${STRONG_CREDENTIAL_NAME})\\s{0,8}
|
|
2477
|
+
`(?:["']${STRONG_CREDENTIAL_NAME}["']|${NOT_AFTER_QUOTE}${STRONG_CREDENTIAL_NAME})\\s{0,8}${ASSIGN}\\s{0,8}${NOT_A_NAMESPACED_IDENTIFIER}(["'\`])${QUOTED_VALUE}\\1`,
|
|
2091
2478
|
"i"
|
|
2092
2479
|
),
|
|
2093
2480
|
valueGroup: 2
|
|
@@ -2097,7 +2484,7 @@ var DEFAULT_SECRET_PATTERNS = [
|
|
|
2097
2484
|
// are accepted only against a quoted value long enough to be a credential.
|
|
2098
2485
|
rule: "credential-assignment-weak",
|
|
2099
2486
|
pattern: new RegExp(
|
|
2100
|
-
`(?:["']${WEAK_CREDENTIAL_NAME}["']|${WEAK_CREDENTIAL_NAME})\\s{0,8}
|
|
2487
|
+
`(?:["']${WEAK_CREDENTIAL_NAME}["']|${NOT_AFTER_QUOTE}${WEAK_CREDENTIAL_NAME})\\s{0,8}${ASSIGN}\\s{0,8}(["'\`])${QUOTED_VALUE}\\1`,
|
|
2101
2488
|
"i"
|
|
2102
2489
|
),
|
|
2103
2490
|
valueGroup: 2
|
|
@@ -2108,7 +2495,7 @@ var DEFAULT_SECRET_PATTERNS = [
|
|
|
2108
2495
|
// `POSTGRES_PASSWORD:` in the output.
|
|
2109
2496
|
rule: "credential-assignment-unquoted",
|
|
2110
2497
|
pattern: new RegExp(
|
|
2111
|
-
`${UNQUOTED_STRONG_NAME}\\s{0,8}
|
|
2498
|
+
`${NOT_AFTER_QUOTE}${UNQUOTED_STRONG_NAME}\\s{0,8}${ASSIGN}\\s{0,8}${NOT_A_LITERAL_VALUE}${UNQUOTED_LITERAL}`,
|
|
2112
2499
|
"i"
|
|
2113
2500
|
),
|
|
2114
2501
|
valueGroup: 1
|
|
@@ -2119,7 +2506,7 @@ var DEFAULT_SECRET_PATTERNS = [
|
|
|
2119
2506
|
// `pass: 3` / `credentials: none` readable.
|
|
2120
2507
|
rule: "credential-assignment-unquoted-weak",
|
|
2121
2508
|
pattern: new RegExp(
|
|
2122
|
-
`${WEAK_CREDENTIAL_NAME}\\s{0,8}
|
|
2509
|
+
`${NOT_AFTER_QUOTE}${WEAK_CREDENTIAL_NAME}\\s{0,8}${ASSIGN}\\s{0,8}${NOT_A_LITERAL_VALUE}${UNQUOTED_AMBIGUOUS_LITERAL}`,
|
|
2123
2510
|
"i"
|
|
2124
2511
|
),
|
|
2125
2512
|
valueGroup: 1
|
|
@@ -2140,7 +2527,7 @@ var DEFAULT_SECRET_PATTERNS = [
|
|
|
2140
2527
|
// above because it also matches a *quoted key* followed by an unquoted
|
|
2141
2528
|
// value, e.g. `"client_secret": abc123`.
|
|
2142
2529
|
rule: "generic-secret-assignment",
|
|
2143
|
-
pattern: /(?:api[_-]?key|secret[_-]?key|auth[_-]?token|client[_-]?secret|access[_-]?token|refresh[_-]?token|private[_-]?key|password|passwd|pwd)["']?\s*[:=]\s*["']([^"'\s]{8,256})["']/i,
|
|
2530
|
+
pattern: /(?<!["'])(?:api[_-]?key|secret[_-]?key|auth[_-]?token|client[_-]?secret|access[_-]?token|refresh[_-]?token|private[_-]?key|password|passwd|pwd)["']?\s*(?<![=!<>])[:=](?!=)\s*["']([^"'\s]{8,256})["']/i,
|
|
2144
2531
|
valueGroup: 1
|
|
2145
2532
|
},
|
|
2146
2533
|
{
|
|
@@ -2156,8 +2543,17 @@ var DEFAULT_SECRET_PATTERNS = [
|
|
|
2156
2543
|
// Anchoring the value to end-of-line (`[^\s#]{6,512}$`) meant ANY trailing
|
|
2157
2544
|
// comment or space disabled the rule, so `TOKEN=secret # rotate` and
|
|
2158
2545
|
// `TOKEN=secret ` were emitted in clear; a CRLF file disabled it wholesale.
|
|
2546
|
+
//
|
|
2547
|
+
// The name is now required to be uppercase. With `/i`, a reassignment inside a
|
|
2548
|
+
// function — ` key = fs.realpathSync(stagingRoot)` — matched `key`…`KEY` and
|
|
2549
|
+
// was emitted as `key = [REDACTED](stagingRoot)`, which corrupted this
|
|
2550
|
+
// repository's own `src/index.ts`. Shell and `.env` names are uppercase by
|
|
2551
|
+
// convention and a lowercase `key = …` at an indent is a variable, so the case
|
|
2552
|
+
// of the name is the discriminator that separates the two. The value must end
|
|
2553
|
+
// at end-of-line, whitespace or a comment: that is what stops a call expression
|
|
2554
|
+
// (`NAME=f(x)`) from being read as an assigned literal.
|
|
2159
2555
|
rule: "env-secret-assignment",
|
|
2160
|
-
pattern: /^\s{0,64}(?:export\s{1,8})?[A-
|
|
2556
|
+
pattern: /^\s{0,64}(?:export\s{1,8})?(?=[A-Z][A-Z0-9_]{0,191}\s{0,8}=)[A-Z0-9_]{0,96}(?:KEY|TOKEN|SECRET|PASSWORD|PASSWD|PASS|PWD|CREDENTIAL)[A-Z0-9_]{0,96}\s{0,8}=(?!=)\s{0,8}(?!["'`$<{[\]\s])([^\s#;"'`()]{1,512})(?=$|[\s#;])/,
|
|
2161
2557
|
valueGroup: 1
|
|
2162
2558
|
}
|
|
2163
2559
|
];
|
|
@@ -2295,6 +2691,12 @@ function scanAndRedactSecrets(content, rules, filePath) {
|
|
|
2295
2691
|
continue;
|
|
2296
2692
|
}
|
|
2297
2693
|
const group = valueGroup === void 0 ? void 0 : match.indices?.[valueGroup];
|
|
2694
|
+
if (group && isSelfNamingConstant(line, group[0], group[1])) continue;
|
|
2695
|
+
if (valueGroup !== void 0 && startsInsideStringLiteral(line, match.index)) continue;
|
|
2696
|
+
if (group && isUnquotedValue(line, group[0]) && isDeclaredBinding(line, match.index)) {
|
|
2697
|
+
continue;
|
|
2698
|
+
}
|
|
2699
|
+
if (group && isTemplateInterpolation(line, group[0], group[1])) continue;
|
|
2298
2700
|
spans.push(
|
|
2299
2701
|
group ? { start: group[0], end: group[1], rule } : { start: match.index, end: match.index + match[0].length, rule }
|
|
2300
2702
|
);
|
|
@@ -2324,6 +2726,46 @@ function expandOverToken(line, start, end) {
|
|
|
2324
2726
|
while (right < line.length && TOKEN_CHARS_RIGHT.test(line[right])) right += 1;
|
|
2325
2727
|
return { start: left, end: right };
|
|
2326
2728
|
}
|
|
2729
|
+
function startsInsideStringLiteral(line, matchIndex) {
|
|
2730
|
+
let runStart = matchIndex;
|
|
2731
|
+
while (runStart > 0 && /[A-Za-z0-9_-]/.test(line[runStart - 1])) runStart -= 1;
|
|
2732
|
+
const before = runStart > 0 ? line[runStart - 1] : "";
|
|
2733
|
+
return before === '"' || before === "'" || before === "`";
|
|
2734
|
+
}
|
|
2735
|
+
function isTemplateInterpolation(line, start, end) {
|
|
2736
|
+
return line[start] === "$" && line[start + 1] === "{" && line[end - 1] === "}";
|
|
2737
|
+
}
|
|
2738
|
+
function isSelfNamingConstant(line, start, end) {
|
|
2739
|
+
const value = line.slice(start, end);
|
|
2740
|
+
if (!/[a-z][A-Z]/.test(value)) return false;
|
|
2741
|
+
let index = start;
|
|
2742
|
+
if (index > 0) {
|
|
2743
|
+
const openingQuote = line[index - 1];
|
|
2744
|
+
if (openingQuote === '"' || openingQuote === "'" || openingQuote === "`") index -= 1;
|
|
2745
|
+
}
|
|
2746
|
+
while (index > 0 && /\s/.test(line[index - 1])) index -= 1;
|
|
2747
|
+
if (index === 0) return false;
|
|
2748
|
+
if (line[index - 1] !== ":" && line[index - 1] !== "=") return false;
|
|
2749
|
+
index -= 1;
|
|
2750
|
+
while (index > 0 && /\s/.test(line[index - 1])) index -= 1;
|
|
2751
|
+
if (index > 0) {
|
|
2752
|
+
const closingQuote = line[index - 1];
|
|
2753
|
+
if (closingQuote === '"' || closingQuote === "'" || closingQuote === "`") index -= 1;
|
|
2754
|
+
}
|
|
2755
|
+
return line.endsWith(value, index);
|
|
2756
|
+
}
|
|
2757
|
+
function isUnquotedValue(line, start) {
|
|
2758
|
+
const before = start > 0 ? line[start - 1] : "";
|
|
2759
|
+
return before !== '"' && before !== "'" && before !== "`";
|
|
2760
|
+
}
|
|
2761
|
+
var DECLARATION_KEYWORDS = /\b(?:const|let|var|readonly|public|private|protected|static|declare|export|using)\s+$/;
|
|
2762
|
+
var DECLARATION_WINDOW = 40;
|
|
2763
|
+
function isDeclaredBinding(line, matchIndex) {
|
|
2764
|
+
let runStart = matchIndex;
|
|
2765
|
+
while (runStart > 0 && /[A-Za-z0-9_$]/.test(line[runStart - 1])) runStart -= 1;
|
|
2766
|
+
const from = Math.max(0, runStart - DECLARATION_WINDOW);
|
|
2767
|
+
return DECLARATION_KEYWORDS.test(line.slice(from, runStart));
|
|
2768
|
+
}
|
|
2327
2769
|
function applySpans(line, spans) {
|
|
2328
2770
|
const ordered = [...spans].sort((a, b) => a.start - b.start || a.end - b.end);
|
|
2329
2771
|
const merged = [];
|
|
@@ -2350,45 +2792,58 @@ function applySpans(line, spans) {
|
|
|
2350
2792
|
function isDiscoveryMode(value) {
|
|
2351
2793
|
return value === "auto" || value === "git" || value === "walk";
|
|
2352
2794
|
}
|
|
2353
|
-
var activeDiscovery = "auto";
|
|
2354
|
-
function setActiveDiscovery(mode) {
|
|
2355
|
-
activeDiscovery = mode;
|
|
2356
|
-
}
|
|
2357
2795
|
var PRESET_IGNORES = new Set(
|
|
2358
2796
|
PRESETS.flatMap((preset) => preset.exclude ?? [])
|
|
2359
2797
|
);
|
|
2360
2798
|
function readLinkTarget(linkPath, fallback) {
|
|
2361
2799
|
try {
|
|
2362
|
-
return
|
|
2800
|
+
return fs14.readlinkSync(linkPath);
|
|
2363
2801
|
} catch {
|
|
2364
2802
|
return fallback;
|
|
2365
2803
|
}
|
|
2366
2804
|
}
|
|
2367
2805
|
function isInside(root, candidate) {
|
|
2368
|
-
const
|
|
2369
|
-
return
|
|
2806
|
+
const relative8 = path16.relative(root, candidate);
|
|
2807
|
+
return relative8 === "" || !relative8.startsWith(`..${path16.sep}`) && relative8 !== ".." && !path16.isAbsolute(relative8);
|
|
2370
2808
|
}
|
|
2809
|
+
var GIT_OUTPUT_LIMIT = 68157440;
|
|
2371
2810
|
function getGitInfo(cwd, fallbackBranch = "main") {
|
|
2372
2811
|
try {
|
|
2373
|
-
const
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2812
|
+
const output = childProcess.execFileSync(
|
|
2813
|
+
"git",
|
|
2814
|
+
["status", "--porcelain=v2", "--branch", "-z"],
|
|
2815
|
+
{
|
|
2816
|
+
cwd,
|
|
2817
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
2818
|
+
encoding: "utf8",
|
|
2819
|
+
maxBuffer: GIT_OUTPUT_LIMIT
|
|
2820
|
+
}
|
|
2821
|
+
);
|
|
2822
|
+
let branch = fallbackBranch;
|
|
2823
|
+
let commit = "unknown";
|
|
2824
|
+
let isDirty = false;
|
|
2825
|
+
const records = output.split("\0").flatMap((record) => record.split("\n"));
|
|
2826
|
+
for (const record of records) {
|
|
2827
|
+
if (record.length === 0) continue;
|
|
2828
|
+
if (!record.startsWith("# ")) {
|
|
2829
|
+
isDirty = true;
|
|
2830
|
+
break;
|
|
2831
|
+
}
|
|
2832
|
+
const oid = record.match(/^# branch\.oid (.+)$/);
|
|
2833
|
+
if (oid) {
|
|
2834
|
+
commit = oid[1] === "(initial)" ? "unknown" : oid[1];
|
|
2835
|
+
continue;
|
|
2836
|
+
}
|
|
2837
|
+
const head = record.match(/^# branch\.head (.+)$/);
|
|
2838
|
+
if (head) {
|
|
2839
|
+
branch = head[1] === "(detached)" ? fallbackBranch : head[1];
|
|
2840
|
+
}
|
|
2841
|
+
}
|
|
2842
|
+
return { branch, commit, isDirty };
|
|
2843
|
+
} catch (err) {
|
|
2844
|
+
const code = err.code;
|
|
2845
|
+
const isDirty = code === "ENOBUFS";
|
|
2846
|
+
return { branch: fallbackBranch, commit: "unknown", isDirty };
|
|
2392
2847
|
}
|
|
2393
2848
|
}
|
|
2394
2849
|
var DEFAULT_IGNORED_DIRS = [
|
|
@@ -2435,16 +2890,27 @@ function isSensitivePath(relativePath) {
|
|
|
2435
2890
|
}
|
|
2436
2891
|
return matchesSensitiveList(posixPath, base);
|
|
2437
2892
|
}
|
|
2893
|
+
var GLOBAL_PATTERNS = /* @__PURE__ */ new WeakMap();
|
|
2894
|
+
function globalPattern(pattern) {
|
|
2895
|
+
let compiled = GLOBAL_PATTERNS.get(pattern);
|
|
2896
|
+
if (compiled === void 0) {
|
|
2897
|
+
compiled = new RegExp(pattern.source, `${pattern.flags.replace(/[gy]/g, "")}g`);
|
|
2898
|
+
GLOBAL_PATTERNS.set(pattern, compiled);
|
|
2899
|
+
}
|
|
2900
|
+
return compiled;
|
|
2901
|
+
}
|
|
2902
|
+
var DEFAULT_PATH_RULES = [...DEFAULT_SECRET_PATTERNS];
|
|
2438
2903
|
function pathScanRules(extraRules) {
|
|
2439
|
-
|
|
2440
|
-
return [...DEFAULT_SECRET_PATTERNS, ...
|
|
2904
|
+
if (extraRules === void 0 || extraRules.length === 0) return DEFAULT_PATH_RULES;
|
|
2905
|
+
return [...DEFAULT_SECRET_PATTERNS, ...extraRules];
|
|
2441
2906
|
}
|
|
2442
2907
|
function findSecretInPath(relativePath, extraRules) {
|
|
2443
2908
|
const posixPath = relativePath.replace(/\\/g, "/");
|
|
2444
2909
|
const spans = [];
|
|
2445
2910
|
const rules = [];
|
|
2446
2911
|
for (const { rule, pattern } of pathScanRules(extraRules)) {
|
|
2447
|
-
const scan =
|
|
2912
|
+
const scan = globalPattern(pattern);
|
|
2913
|
+
scan.lastIndex = 0;
|
|
2448
2914
|
let match;
|
|
2449
2915
|
while ((match = scan.exec(posixPath)) !== null) {
|
|
2450
2916
|
if (match[0].length === 0) {
|
|
@@ -2571,10 +3037,10 @@ function trimUnescapedTrailingSpaces(line) {
|
|
|
2571
3037
|
return line.slice(0, end);
|
|
2572
3038
|
}
|
|
2573
3039
|
function readGitignoreRules(dir) {
|
|
2574
|
-
const gitignorePath2 =
|
|
3040
|
+
const gitignorePath2 = path16.join(dir, ".gitignore");
|
|
2575
3041
|
let raw;
|
|
2576
3042
|
try {
|
|
2577
|
-
raw =
|
|
3043
|
+
raw = fs14.readFileSync(gitignorePath2, "utf8");
|
|
2578
3044
|
} catch {
|
|
2579
3045
|
return [];
|
|
2580
3046
|
}
|
|
@@ -2600,7 +3066,7 @@ function readGitignoreRules(dir) {
|
|
|
2600
3066
|
function escapeRegExpChar(character) {
|
|
2601
3067
|
return /[\\^$.*+?()[\]{}|/]/.test(character) ? `\\${character}` : character;
|
|
2602
3068
|
}
|
|
2603
|
-
function
|
|
3069
|
+
function matchGitignorePattern(target, pattern) {
|
|
2604
3070
|
const anchored = pattern.startsWith("/");
|
|
2605
3071
|
const body = anchored ? pattern.slice(1) : pattern;
|
|
2606
3072
|
let source = "";
|
|
@@ -2634,15 +3100,33 @@ function matchEscapedGitignorePattern(target, pattern) {
|
|
|
2634
3100
|
source += "[^/]";
|
|
2635
3101
|
continue;
|
|
2636
3102
|
}
|
|
3103
|
+
if (character === "[") {
|
|
3104
|
+
const close = body.indexOf("]", i + 1);
|
|
3105
|
+
if (close !== -1 && close > i + 1) {
|
|
3106
|
+
const inner = body.slice(i + 1, close);
|
|
3107
|
+
const negated = inner.startsWith("!") || inner.startsWith("^");
|
|
3108
|
+
const chars = negated ? inner.slice(1) : inner;
|
|
3109
|
+
const safe = chars.replace(/[\\^\]]/g, (value) => `\\${value}`);
|
|
3110
|
+
source += negated ? `[^/${safe}]` : `[${safe}]`;
|
|
3111
|
+
i = close;
|
|
3112
|
+
continue;
|
|
3113
|
+
}
|
|
3114
|
+
}
|
|
2637
3115
|
source += escapeRegExpChar(character);
|
|
2638
3116
|
}
|
|
2639
3117
|
const prefix = anchored || body.includes("/") ? "^" : "(?:^|.*/)";
|
|
2640
3118
|
return new RegExp(`${prefix}${source}(?:/.*)?$`).test(target);
|
|
2641
3119
|
}
|
|
2642
|
-
function
|
|
2643
|
-
|
|
3120
|
+
function matchesGitignoreRule(rule, scoped, isDirectory) {
|
|
3121
|
+
if (!rule.dirOnly) return matchGitignorePattern(scoped, rule.pattern);
|
|
3122
|
+
const segments = scoped.split("/");
|
|
3123
|
+
const limit = isDirectory ? segments.length : segments.length - 1;
|
|
3124
|
+
for (let depth = 1; depth <= limit; depth += 1) {
|
|
3125
|
+
if (matchGitignorePattern(segments.slice(0, depth).join("/"), rule.pattern)) return true;
|
|
3126
|
+
}
|
|
3127
|
+
return false;
|
|
2644
3128
|
}
|
|
2645
|
-
function gitignoreVerdict(layers, relativePath) {
|
|
3129
|
+
function gitignoreVerdict(layers, relativePath, isDirectory) {
|
|
2646
3130
|
let verdict = null;
|
|
2647
3131
|
for (const layer2 of layers) {
|
|
2648
3132
|
let scoped;
|
|
@@ -2655,8 +3139,7 @@ function gitignoreVerdict(layers, relativePath) {
|
|
|
2655
3139
|
}
|
|
2656
3140
|
let layerVerdict = null;
|
|
2657
3141
|
for (const rule of layer2.rules) {
|
|
2658
|
-
|
|
2659
|
-
if (matchesGitignorePattern(target, rule.pattern)) layerVerdict = !rule.negated;
|
|
3142
|
+
if (matchesGitignoreRule(rule, scoped, isDirectory)) layerVerdict = !rule.negated;
|
|
2660
3143
|
}
|
|
2661
3144
|
if (layerVerdict !== null) verdict = layerVerdict;
|
|
2662
3145
|
}
|
|
@@ -2681,13 +3164,14 @@ function defaultIgnoreReason(relativePath) {
|
|
|
2681
3164
|
}
|
|
2682
3165
|
function listIgnoredPaths(cwd) {
|
|
2683
3166
|
try {
|
|
2684
|
-
const stdout = childProcess.
|
|
2685
|
-
"git
|
|
3167
|
+
const stdout = childProcess.execFileSync(
|
|
3168
|
+
"git",
|
|
3169
|
+
["ls-files", "-z", "--others", "--ignored", "--exclude-standard", "--directory"],
|
|
2686
3170
|
{
|
|
2687
3171
|
cwd,
|
|
2688
3172
|
stdio: ["ignore", "pipe", "ignore"],
|
|
2689
3173
|
encoding: "utf8",
|
|
2690
|
-
maxBuffer:
|
|
3174
|
+
maxBuffer: GIT_OUTPUT_LIMIT
|
|
2691
3175
|
}
|
|
2692
3176
|
);
|
|
2693
3177
|
return stdout.split("\0").filter((entry) => entry.length > 0).map((entry) => entry.replace(/\\/g, "/"));
|
|
@@ -2695,13 +3179,9 @@ function listIgnoredPaths(cwd) {
|
|
|
2695
3179
|
return [];
|
|
2696
3180
|
}
|
|
2697
3181
|
}
|
|
2698
|
-
var TOOL_OWNED_DIR_NAMES = /* @__PURE__ */ new Set(["snapshot", "snapshot.tmp"]);
|
|
2699
|
-
function isToolOwnedDirName(name) {
|
|
2700
|
-
return TOOL_OWNED_DIR_NAMES.has(name) || name.startsWith("snapshot.backup-");
|
|
2701
|
-
}
|
|
2702
3182
|
function hasSnapshotMarker(target) {
|
|
2703
3183
|
try {
|
|
2704
|
-
return
|
|
3184
|
+
return fs14.statSync(target).isDirectory() && fs14.existsSync(path16.join(target, ".codebase-snapshot"));
|
|
2705
3185
|
} catch {
|
|
2706
3186
|
return false;
|
|
2707
3187
|
}
|
|
@@ -2715,7 +3195,7 @@ function makeToolOwnedDecider(cwd, toolOwnedPaths) {
|
|
|
2715
3195
|
const prefix = parts.slice(0, depth).join("/");
|
|
2716
3196
|
let hasMarker = markers.get(prefix);
|
|
2717
3197
|
if (hasMarker === void 0) {
|
|
2718
|
-
hasMarker =
|
|
3198
|
+
hasMarker = fs14.existsSync(path16.join(cwd, prefix, ".codebase-snapshot"));
|
|
2719
3199
|
markers.set(prefix, hasMarker);
|
|
2720
3200
|
}
|
|
2721
3201
|
if (hasMarker) return true;
|
|
@@ -2730,7 +3210,7 @@ function makeLinkProbe() {
|
|
|
2730
3210
|
let known = cache.get(absolutePath);
|
|
2731
3211
|
if (known === void 0) {
|
|
2732
3212
|
try {
|
|
2733
|
-
known =
|
|
3213
|
+
known = fs14.lstatSync(absolutePath).isSymbolicLink();
|
|
2734
3214
|
} catch {
|
|
2735
3215
|
known = false;
|
|
2736
3216
|
}
|
|
@@ -2743,7 +3223,7 @@ function dropLinkedDirectories(cwd, candidates, skipped) {
|
|
|
2743
3223
|
const isLink = makeLinkProbe();
|
|
2744
3224
|
let rootReal = cwd;
|
|
2745
3225
|
try {
|
|
2746
|
-
rootReal =
|
|
3226
|
+
rootReal = fs14.realpathSync(cwd);
|
|
2747
3227
|
} catch {
|
|
2748
3228
|
}
|
|
2749
3229
|
const reported = /* @__PURE__ */ new Set();
|
|
@@ -2753,7 +3233,7 @@ function dropLinkedDirectories(cwd, candidates, skipped) {
|
|
|
2753
3233
|
let linkedDir = null;
|
|
2754
3234
|
for (let depth = 1; depth <= parts.length; depth += 1) {
|
|
2755
3235
|
const prefix = parts.slice(0, depth).join("/");
|
|
2756
|
-
if (isLink(
|
|
3236
|
+
if (isLink(path16.join(cwd, prefix))) {
|
|
2757
3237
|
linkedDir = prefix;
|
|
2758
3238
|
break;
|
|
2759
3239
|
}
|
|
@@ -2764,10 +3244,10 @@ function dropLinkedDirectories(cwd, candidates, skipped) {
|
|
|
2764
3244
|
}
|
|
2765
3245
|
if (reported.has(linkedDir)) continue;
|
|
2766
3246
|
reported.add(linkedDir);
|
|
2767
|
-
const absolute =
|
|
3247
|
+
const absolute = path16.join(cwd, linkedDir);
|
|
2768
3248
|
let targetReal = absolute;
|
|
2769
3249
|
try {
|
|
2770
|
-
targetReal =
|
|
3250
|
+
targetReal = fs14.realpathSync(absolute);
|
|
2771
3251
|
} catch {
|
|
2772
3252
|
}
|
|
2773
3253
|
const target = readLinkTarget(absolute, targetReal);
|
|
@@ -2787,13 +3267,14 @@ function dropLinkedDirectories(cwd, candidates, skipped) {
|
|
|
2787
3267
|
return kept;
|
|
2788
3268
|
}
|
|
2789
3269
|
function listGitCandidates(cwd, skipped, isToolOwned) {
|
|
2790
|
-
const stdout = childProcess.
|
|
2791
|
-
"git
|
|
3270
|
+
const stdout = childProcess.execFileSync(
|
|
3271
|
+
"git",
|
|
3272
|
+
["ls-files", "-z", "--cached", "--others", "--exclude-standard"],
|
|
2792
3273
|
{
|
|
2793
3274
|
cwd,
|
|
2794
3275
|
stdio: ["ignore", "pipe", "ignore"],
|
|
2795
3276
|
encoding: "utf8",
|
|
2796
|
-
maxBuffer:
|
|
3277
|
+
maxBuffer: GIT_OUTPUT_LIMIT
|
|
2797
3278
|
}
|
|
2798
3279
|
);
|
|
2799
3280
|
let candidates = stdout.split("\0").filter((entry) => entry.length > 0).map((entry) => entry.replace(/\\/g, "/"));
|
|
@@ -2830,7 +3311,7 @@ function collectFiles(cwd, options = {}) {
|
|
|
2830
3311
|
const isToolOwned = makeToolOwnedDecider(cwd, [...toolOwnedPaths]);
|
|
2831
3312
|
let candidates;
|
|
2832
3313
|
const skipped = [];
|
|
2833
|
-
const discovery = options.discovery ??
|
|
3314
|
+
const discovery = options.discovery ?? "auto";
|
|
2834
3315
|
if (discovery === "walk") {
|
|
2835
3316
|
const walked = walkDirectory(cwd, cwd, rootRealFor(cwd), /* @__PURE__ */ new Set(), toolOwnedPaths);
|
|
2836
3317
|
candidates = walked.files;
|
|
@@ -2844,7 +3325,7 @@ function collectFiles(cwd, options = {}) {
|
|
|
2844
3325
|
"Discovery mode is `git`, but git could not list this directory's files. Run inside a working repository, or pass --discovery walk (or auto) to scan the tree directly."
|
|
2845
3326
|
);
|
|
2846
3327
|
}
|
|
2847
|
-
if (
|
|
3328
|
+
if (fs14.existsSync(path16.join(cwd, ".git"))) {
|
|
2848
3329
|
throw new Error(
|
|
2849
3330
|
"This directory is a git repository, but git could not list its files. A plain walk honours .gitignore files, but git also applies .git/info/exclude and your global excludes file, includes files that are tracked although a .gitignore now matches them, and treats a submodule as a single entry. Fix the repository (git status should work), run outside git, or pass --discovery walk to accept those differences."
|
|
2850
3331
|
);
|
|
@@ -2903,7 +3384,7 @@ function collectFiles(cwd, options = {}) {
|
|
|
2903
3384
|
}
|
|
2904
3385
|
function rootRealFor(root) {
|
|
2905
3386
|
try {
|
|
2906
|
-
return
|
|
3387
|
+
return fs14.realpathSync(root);
|
|
2907
3388
|
} catch {
|
|
2908
3389
|
return root;
|
|
2909
3390
|
}
|
|
@@ -2911,18 +3392,18 @@ function rootRealFor(root) {
|
|
|
2911
3392
|
function walkDirectory(dir, baseDir, rootReal, visited, reservedPaths = /* @__PURE__ */ new Set(), parentGitignoreLayers = []) {
|
|
2912
3393
|
const results = { files: [], skipped: [] };
|
|
2913
3394
|
const stack = [];
|
|
2914
|
-
const relativeToBase = (target) =>
|
|
3395
|
+
const relativeToBase = (target) => path16.relative(baseDir, target).replace(/\\/g, "/") || ".";
|
|
2915
3396
|
const enter = (current, layers) => {
|
|
2916
3397
|
if (visited.has(current)) return false;
|
|
2917
3398
|
visited.add(current);
|
|
2918
3399
|
const localRules = readGitignoreRules(current);
|
|
2919
3400
|
const gitignoreLayers = localRules.length === 0 ? layers : [
|
|
2920
3401
|
...layers,
|
|
2921
|
-
{ prefix:
|
|
3402
|
+
{ prefix: path16.relative(baseDir, current).replace(/\\/g, "/"), rules: localRules }
|
|
2922
3403
|
];
|
|
2923
3404
|
let entries;
|
|
2924
3405
|
try {
|
|
2925
|
-
entries =
|
|
3406
|
+
entries = fs14.readdirSync(current, { withFileTypes: true });
|
|
2926
3407
|
} catch {
|
|
2927
3408
|
results.skipped.push({
|
|
2928
3409
|
path: relativeToBase(current),
|
|
@@ -2944,10 +3425,10 @@ function walkDirectory(dir, baseDir, rootReal, visited, reservedPaths = /* @__PU
|
|
|
2944
3425
|
}
|
|
2945
3426
|
const entry = frame.entries[frame.index];
|
|
2946
3427
|
frame.index += 1;
|
|
2947
|
-
const fullPath =
|
|
2948
|
-
const relPath =
|
|
2949
|
-
if (
|
|
2950
|
-
if (DEFAULT_IGNORED_DIRS.includes(entry.name)) {
|
|
3428
|
+
const fullPath = path16.join(frame.dir, entry.name);
|
|
3429
|
+
const relPath = path16.relative(baseDir, fullPath).replace(/\\/g, "/");
|
|
3430
|
+
if (reservedPaths.has(relPath)) continue;
|
|
3431
|
+
if (entry.isDirectory() && DEFAULT_IGNORED_DIRS.includes(entry.name)) {
|
|
2951
3432
|
results.skipped.push({
|
|
2952
3433
|
path: relPath,
|
|
2953
3434
|
reason: "default-ignore",
|
|
@@ -2963,7 +3444,7 @@ function walkDirectory(dir, baseDir, rootReal, visited, reservedPaths = /* @__PU
|
|
|
2963
3444
|
});
|
|
2964
3445
|
continue;
|
|
2965
3446
|
}
|
|
2966
|
-
if (gitignoreVerdict(frame.gitignoreLayers, relPath) === true) {
|
|
3447
|
+
if (gitignoreVerdict(frame.gitignoreLayers, relPath, entry.isDirectory()) === true) {
|
|
2967
3448
|
if (!hasSnapshotMarker(fullPath)) {
|
|
2968
3449
|
results.skipped.push({
|
|
2969
3450
|
path: relPath,
|
|
@@ -2978,14 +3459,14 @@ function walkDirectory(dir, baseDir, rootReal, visited, reservedPaths = /* @__PU
|
|
|
2978
3459
|
if (entry.isSymbolicLink()) {
|
|
2979
3460
|
let targetReal;
|
|
2980
3461
|
try {
|
|
2981
|
-
targetReal =
|
|
3462
|
+
targetReal = fs14.realpathSync(fullPath);
|
|
2982
3463
|
} catch {
|
|
2983
3464
|
results.skipped.push({ path: relPath, reason: "read-error", detail: "broken symlink" });
|
|
2984
3465
|
continue;
|
|
2985
3466
|
}
|
|
2986
3467
|
let stats;
|
|
2987
3468
|
try {
|
|
2988
|
-
stats =
|
|
3469
|
+
stats = fs14.statSync(fullPath);
|
|
2989
3470
|
} catch {
|
|
2990
3471
|
results.skipped.push({ path: relPath, reason: "read-error", detail: "unreadable symlink" });
|
|
2991
3472
|
continue;
|
|
@@ -3027,6 +3508,28 @@ function walkDirectory(dir, baseDir, rootReal, visited, reservedPaths = /* @__PU
|
|
|
3027
3508
|
return results;
|
|
3028
3509
|
}
|
|
3029
3510
|
|
|
3511
|
+
// src/tokenize.ts
|
|
3512
|
+
var WORD_RUN = /[A-Za-z0-9_]+/g;
|
|
3513
|
+
var SINGLE_TOKEN = /[^\sA-Za-z0-9_]/g;
|
|
3514
|
+
var LINE_BREAK_RUN = /\n+/g;
|
|
3515
|
+
var CHARS_PER_WORD_TOKEN = 6;
|
|
3516
|
+
function estimateTokens(text2) {
|
|
3517
|
+
if (text2.length === 0) return 0;
|
|
3518
|
+
let total = 0;
|
|
3519
|
+
for (const match of text2.match(WORD_RUN) ?? []) {
|
|
3520
|
+
total += Math.max(1, Math.round(match.length / CHARS_PER_WORD_TOKEN));
|
|
3521
|
+
}
|
|
3522
|
+
total += text2.match(SINGLE_TOKEN)?.length ?? 0;
|
|
3523
|
+
total += text2.match(LINE_BREAK_RUN)?.length ?? 0;
|
|
3524
|
+
return total;
|
|
3525
|
+
}
|
|
3526
|
+
function bytesForTokens(tokens) {
|
|
3527
|
+
return Math.max(0, Math.floor(tokens * 4));
|
|
3528
|
+
}
|
|
3529
|
+
function formatTokens(count) {
|
|
3530
|
+
return count.toLocaleString("en-US");
|
|
3531
|
+
}
|
|
3532
|
+
|
|
3030
3533
|
// src/config.ts
|
|
3031
3534
|
var CONFIG_DIR_SYMBOL = /* @__PURE__ */ Symbol.for("codebase-snapshot.configDir");
|
|
3032
3535
|
var CONFIG_FILENAMES = [
|
|
@@ -3040,13 +3543,13 @@ var CONFIG_FILENAMES = [
|
|
|
3040
3543
|
];
|
|
3041
3544
|
function findConfigFile(cwd) {
|
|
3042
3545
|
for (const filename of CONFIG_FILENAMES) {
|
|
3043
|
-
const fullPath =
|
|
3044
|
-
if (
|
|
3546
|
+
const fullPath = path16.join(cwd, filename);
|
|
3547
|
+
if (fs14.existsSync(fullPath)) return fullPath;
|
|
3045
3548
|
}
|
|
3046
|
-
const pkgJsonPath =
|
|
3047
|
-
if (!
|
|
3549
|
+
const pkgJsonPath = path16.join(cwd, "package.json");
|
|
3550
|
+
if (!fs14.existsSync(pkgJsonPath)) return null;
|
|
3048
3551
|
try {
|
|
3049
|
-
const parsed = JSON.parse(
|
|
3552
|
+
const parsed = JSON.parse(fs14.readFileSync(pkgJsonPath, "utf8"));
|
|
3050
3553
|
return parsed.snapshot === void 0 ? null : pkgJsonPath;
|
|
3051
3554
|
} catch {
|
|
3052
3555
|
return null;
|
|
@@ -3080,9 +3583,23 @@ function isResolvedConfig(value) {
|
|
|
3080
3583
|
);
|
|
3081
3584
|
return hasResolvedNumbers && hasResolvedStrings && hasResolvedFlags && hasResolvedRules && hasCompiledRules;
|
|
3082
3585
|
}
|
|
3586
|
+
function normalizeBudgets(budgets, maxPartBytes) {
|
|
3587
|
+
const clearer = typeof maxPartBytes === "number" && Number.isFinite(maxPartBytes) && maxPartBytes > 0 ? maxPartBytes : void 0;
|
|
3588
|
+
if (budgets === void 0 || typeof budgets !== "object" || budgets === null) {
|
|
3589
|
+
return { default: clearer ?? 0, byLayer: {} };
|
|
3590
|
+
}
|
|
3591
|
+
const fallbackDefault = clearer ?? budgets.default;
|
|
3592
|
+
const byLayer = {};
|
|
3593
|
+
const layers = budgets.byLayer;
|
|
3594
|
+
if (layers !== void 0 && typeof layers === "object" && layers !== null) {
|
|
3595
|
+
for (const [layer2, value] of Object.entries(layers)) {
|
|
3596
|
+
byLayer[layer2] = Math.min(value, fallbackDefault);
|
|
3597
|
+
}
|
|
3598
|
+
}
|
|
3599
|
+
return { default: fallbackDefault, byLayer };
|
|
3600
|
+
}
|
|
3083
3601
|
function normalizeResolvedConfig(config) {
|
|
3084
3602
|
const discovery = isDiscoveryMode(config.discovery) ? config.discovery : "auto";
|
|
3085
|
-
setActiveDiscovery(discovery);
|
|
3086
3603
|
return markResolvedConfig({
|
|
3087
3604
|
...config,
|
|
3088
3605
|
discovery,
|
|
@@ -3101,7 +3618,7 @@ function normalizeResolvedConfig(config) {
|
|
|
3101
3618
|
])
|
|
3102
3619
|
],
|
|
3103
3620
|
layersMode: config.layersMode === "replace" ? "replace" : "merge",
|
|
3104
|
-
budgets: config.budgets
|
|
3621
|
+
budgets: normalizeBudgets(config.budgets, config.maxPartBytes),
|
|
3105
3622
|
hooks: resolveHooks(config.hooks),
|
|
3106
3623
|
excludePatterns: Array.isArray(config.excludePatterns) ? config.excludePatterns : [],
|
|
3107
3624
|
layers: Array.isArray(config.layers) ? config.layers : [],
|
|
@@ -3223,6 +3740,7 @@ var CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
3223
3740
|
"artifacts",
|
|
3224
3741
|
"branch",
|
|
3225
3742
|
"budgets",
|
|
3743
|
+
"changedSince",
|
|
3226
3744
|
"cwd",
|
|
3227
3745
|
"discovery",
|
|
3228
3746
|
"exclude",
|
|
@@ -3237,12 +3755,18 @@ var CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
3237
3755
|
"layersMode",
|
|
3238
3756
|
"maxMonolithBytes",
|
|
3239
3757
|
"maxPartBytes",
|
|
3758
|
+
"maxPartTokens",
|
|
3240
3759
|
"outDir",
|
|
3241
3760
|
"preset",
|
|
3242
3761
|
"previewHeadLines",
|
|
3762
|
+
"staged",
|
|
3243
3763
|
"previewTailLines",
|
|
3764
|
+
"secretRules",
|
|
3244
3765
|
"secrets",
|
|
3245
|
-
"
|
|
3766
|
+
"sarif",
|
|
3767
|
+
"skeleton",
|
|
3768
|
+
"tmpDir",
|
|
3769
|
+
"withDependents"
|
|
3246
3770
|
]);
|
|
3247
3771
|
function assertKnownConfigKeys(value, file) {
|
|
3248
3772
|
const unknownKeys = Object.keys(value).filter((key) => !CONFIG_KEYS.has(key));
|
|
@@ -3297,11 +3821,11 @@ async function loadConfigFileDetailed(cwd) {
|
|
|
3297
3821
|
const warnings = [];
|
|
3298
3822
|
const errors = [];
|
|
3299
3823
|
for (const filename of CONFIG_FILENAMES) {
|
|
3300
|
-
const fullPath =
|
|
3301
|
-
if (!
|
|
3824
|
+
const fullPath = path16.join(cwd, filename);
|
|
3825
|
+
if (!fs14.existsSync(fullPath)) continue;
|
|
3302
3826
|
if (filename.endsWith(".json") || filename === ".snapshotrc") {
|
|
3303
3827
|
try {
|
|
3304
|
-
const config = parseJsonish(
|
|
3828
|
+
const config = parseJsonish(fs14.readFileSync(fullPath, "utf8"), fullPath);
|
|
3305
3829
|
if (config === null || typeof config !== "object" || Array.isArray(config)) {
|
|
3306
3830
|
throw new Error(
|
|
3307
3831
|
`${fullPath} does not hold a configuration object (received ${describeValue2(config)}).`
|
|
@@ -3335,12 +3859,16 @@ async function loadConfigFileDetailed(cwd) {
|
|
|
3335
3859
|
continue;
|
|
3336
3860
|
}
|
|
3337
3861
|
}
|
|
3338
|
-
const pkgJsonPath =
|
|
3339
|
-
if (
|
|
3862
|
+
const pkgJsonPath = path16.join(cwd, "package.json");
|
|
3863
|
+
if (fs14.existsSync(pkgJsonPath)) {
|
|
3340
3864
|
try {
|
|
3341
|
-
const parsed = parseJsonish(
|
|
3865
|
+
const parsed = parseJsonish(fs14.readFileSync(pkgJsonPath, "utf8"), pkgJsonPath);
|
|
3342
3866
|
const snapshotKey = parsed.snapshot;
|
|
3343
3867
|
if (snapshotKey && typeof snapshotKey === "object" && !Array.isArray(snapshotKey)) {
|
|
3868
|
+
assertKnownConfigKeys(
|
|
3869
|
+
snapshotKey,
|
|
3870
|
+
`${pkgJsonPath} (the "snapshot" key)`
|
|
3871
|
+
);
|
|
3344
3872
|
return { config: snapshotKey, file: pkgJsonPath, warnings, errors };
|
|
3345
3873
|
}
|
|
3346
3874
|
if (snapshotKey !== void 0) {
|
|
@@ -3361,7 +3889,7 @@ async function loadConfigFile(cwd) {
|
|
|
3361
3889
|
const loaded = await loadConfigFileDetailed(cwd);
|
|
3362
3890
|
if (loaded.config && loaded.file && typeof loaded.config === "object") {
|
|
3363
3891
|
Object.defineProperty(loaded.config, CONFIG_DIR_SYMBOL, {
|
|
3364
|
-
value:
|
|
3892
|
+
value: path16.dirname(loaded.file),
|
|
3365
3893
|
enumerable: false,
|
|
3366
3894
|
configurable: true
|
|
3367
3895
|
});
|
|
@@ -3372,6 +3900,7 @@ async function loadConfigFile(cwd) {
|
|
|
3372
3900
|
return loaded.config;
|
|
3373
3901
|
}
|
|
3374
3902
|
var MIN_MAX_PART_BYTES = 4096;
|
|
3903
|
+
var MIN_MAX_PART_TOKENS = MIN_MAX_PART_BYTES / 4;
|
|
3375
3904
|
var ARTIFACT_NAMES = [
|
|
3376
3905
|
"readme",
|
|
3377
3906
|
"manifest",
|
|
@@ -3520,12 +4049,12 @@ function resolveKindRules(value) {
|
|
|
3520
4049
|
function outputExcludePatterns(cwd, outDir, tmpDir) {
|
|
3521
4050
|
const patterns = [];
|
|
3522
4051
|
for (const dir of [outDir, tmpDir]) {
|
|
3523
|
-
const absolute =
|
|
3524
|
-
const
|
|
3525
|
-
if (!
|
|
4052
|
+
const absolute = path16.resolve(cwd, dir);
|
|
4053
|
+
const relative8 = path16.relative(cwd, absolute);
|
|
4054
|
+
if (!relative8 || relative8 === ".." || relative8.startsWith(`..${path16.sep}`) || path16.isAbsolute(relative8)) {
|
|
3526
4055
|
continue;
|
|
3527
4056
|
}
|
|
3528
|
-
patterns.push(`${
|
|
4057
|
+
patterns.push(`${relative8.replace(/\\/g, "/")}/**`);
|
|
3529
4058
|
}
|
|
3530
4059
|
return patterns;
|
|
3531
4060
|
}
|
|
@@ -3572,6 +4101,20 @@ function boundedBytes(value, fallback, minimum, label) {
|
|
|
3572
4101
|
}
|
|
3573
4102
|
return parsed;
|
|
3574
4103
|
}
|
|
4104
|
+
function boundedTokens(value, label) {
|
|
4105
|
+
const parsed = parseByteSize(value);
|
|
4106
|
+
if (parsed === void 0 || !Number.isInteger(parsed)) {
|
|
4107
|
+
throw new Error(
|
|
4108
|
+
`${label} must be a whole token count such as 30000 or "30k" (received ${describeBytes(value)}).`
|
|
4109
|
+
);
|
|
4110
|
+
}
|
|
4111
|
+
if (parsed < MIN_MAX_PART_TOKENS) {
|
|
4112
|
+
throw new Error(
|
|
4113
|
+
`${label} must be at least ${MIN_MAX_PART_TOKENS} tokens (${MIN_MAX_PART_BYTES} bytes is the smallest part budget); received ${describeBytes(value)}.`
|
|
4114
|
+
);
|
|
4115
|
+
}
|
|
4116
|
+
return parsed;
|
|
4117
|
+
}
|
|
3575
4118
|
function optionalBytes(value, label) {
|
|
3576
4119
|
if (value === void 0 || value === null) return null;
|
|
3577
4120
|
const parsed = parseByteSize(value);
|
|
@@ -3641,11 +4184,11 @@ function resolveCandidateCwd(userConfig, cliOptions, baseDir) {
|
|
|
3641
4184
|
);
|
|
3642
4185
|
}
|
|
3643
4186
|
const candidate = cliOptions?.cwd || userConfig?.cwd || baseDir || process.cwd();
|
|
3644
|
-
const resolved =
|
|
3645
|
-
if (!
|
|
4187
|
+
const resolved = path16.resolve(candidate);
|
|
4188
|
+
if (!fs14.existsSync(resolved)) {
|
|
3646
4189
|
throw new Error(`Working directory does not exist: ${resolved}`);
|
|
3647
4190
|
}
|
|
3648
|
-
if (!
|
|
4191
|
+
if (!fs14.statSync(resolved).isDirectory()) {
|
|
3649
4192
|
throw new Error(`Working directory is not a directory: ${resolved}`);
|
|
3650
4193
|
}
|
|
3651
4194
|
return resolved;
|
|
@@ -3660,7 +4203,6 @@ function resolveConfig(userConfig, cliOptions, baseDir) {
|
|
|
3660
4203
|
);
|
|
3661
4204
|
}
|
|
3662
4205
|
const discovery = requestedDiscovery ?? "auto";
|
|
3663
|
-
setActiveDiscovery(discovery);
|
|
3664
4206
|
const forcedPreset = resolvePresetName(userConfig?.preset);
|
|
3665
4207
|
if (forcedPreset !== void 0 && !getPreset(forcedPreset)) {
|
|
3666
4208
|
throw new Error(unknownPresetMessage(forcedPreset));
|
|
@@ -3714,13 +4256,53 @@ function resolveConfig(userConfig, cliOptions, baseDir) {
|
|
|
3714
4256
|
MIN_MAX_PART_BYTES,
|
|
3715
4257
|
"maxPartBytes"
|
|
3716
4258
|
);
|
|
4259
|
+
const rawMaxPartTokens = cliOptions?.maxPartTokens ?? userConfig?.maxPartTokens;
|
|
4260
|
+
const requestedMaxPartTokens = rawMaxPartTokens === null ? void 0 : rawMaxPartTokens;
|
|
4261
|
+
const maxPartTokens = requestedMaxPartTokens === void 0 ? void 0 : boundedTokens(requestedMaxPartTokens, "maxPartTokens");
|
|
4262
|
+
const effectiveMaxPartBytes = maxPartTokens === void 0 ? maxPartBytes : Math.min(maxPartBytes, bytesForTokens(maxPartTokens));
|
|
3717
4263
|
const budgets = resolveBudgets(
|
|
3718
4264
|
userConfig?.budgets,
|
|
3719
|
-
|
|
3720
|
-
explicitMaxPartBytes !== void 0
|
|
4265
|
+
effectiveMaxPartBytes,
|
|
4266
|
+
explicitMaxPartBytes !== void 0 || maxPartTokens !== void 0
|
|
3721
4267
|
);
|
|
4268
|
+
const resolvedMaxPartBytes = explicitMaxPartBytes !== void 0 || maxPartTokens !== void 0 ? effectiveMaxPartBytes : budgets.default;
|
|
3722
4269
|
const configuredOutDir = strictString(userConfig?.outDir, "snapshot", "`outDir`");
|
|
3723
4270
|
const outDir = cliOptions?.outDir || configuredOutDir;
|
|
4271
|
+
const rawChangedSinceSetting = cliOptions?.changedSince ?? userConfig?.changedSince;
|
|
4272
|
+
const rawChangedSince = rawChangedSinceSetting === null ? void 0 : rawChangedSinceSetting;
|
|
4273
|
+
if (rawChangedSince !== void 0 && (typeof rawChangedSince !== "string" || rawChangedSince.trim().length === 0)) {
|
|
4274
|
+
throw new Error(
|
|
4275
|
+
`\`changedSince\` must be a git ref such as "main" or "HEAD~3" (received ${describeValue2(rawChangedSince)}).`
|
|
4276
|
+
);
|
|
4277
|
+
}
|
|
4278
|
+
const changedSince = rawChangedSince === void 0 ? void 0 : String(rawChangedSince).trim();
|
|
4279
|
+
const rawStaged = cliOptions?.staged ?? userConfig?.staged;
|
|
4280
|
+
if (rawStaged !== void 0 && typeof rawStaged !== "boolean") {
|
|
4281
|
+
throw new Error(`\`staged\` must be a boolean (received ${describeValue2(rawStaged)}).`);
|
|
4282
|
+
}
|
|
4283
|
+
const staged = rawStaged === true;
|
|
4284
|
+
const rawWithDependents = cliOptions?.withDependents ?? userConfig?.withDependents;
|
|
4285
|
+
if (rawWithDependents !== void 0 && typeof rawWithDependents !== "boolean") {
|
|
4286
|
+
throw new Error(
|
|
4287
|
+
`\`withDependents\` must be a boolean (received ${describeValue2(rawWithDependents)}).`
|
|
4288
|
+
);
|
|
4289
|
+
}
|
|
4290
|
+
const withDependents = rawWithDependents === true;
|
|
4291
|
+
const rawSkeleton = cliOptions?.skeleton ?? userConfig?.skeleton;
|
|
4292
|
+
if (rawSkeleton !== void 0 && typeof rawSkeleton !== "boolean") {
|
|
4293
|
+
throw new Error(`\`skeleton\` must be a boolean (received ${describeValue2(rawSkeleton)}).`);
|
|
4294
|
+
}
|
|
4295
|
+
const skeleton = rawSkeleton === true;
|
|
4296
|
+
const rawSarif = cliOptions?.sarif ?? userConfig?.sarif;
|
|
4297
|
+
if (rawSarif !== void 0 && typeof rawSarif !== "boolean") {
|
|
4298
|
+
throw new Error(`\`sarif\` must be a boolean (received ${describeValue2(rawSarif)}).`);
|
|
4299
|
+
}
|
|
4300
|
+
const sarif = rawSarif === true;
|
|
4301
|
+
if (withDependents && changedSince === void 0 && !staged) {
|
|
4302
|
+
throw new Error(
|
|
4303
|
+
"`withDependents` widens a changed-files scope, so it needs `changedSince` or `staged` (with neither, every file is already included)."
|
|
4304
|
+
);
|
|
4305
|
+
}
|
|
3724
4306
|
const tmpDir = strictString(userConfig?.tmpDir, "snapshot.tmp", "`tmpDir`");
|
|
3725
4307
|
const branch = strictString(userConfig?.branch, "main", "`branch`");
|
|
3726
4308
|
const configuredSensitiveFiles = strictBoolean(
|
|
@@ -3741,7 +4323,8 @@ function resolveConfig(userConfig, cliOptions, baseDir) {
|
|
|
3741
4323
|
branch,
|
|
3742
4324
|
outDir,
|
|
3743
4325
|
tmpDir,
|
|
3744
|
-
maxPartBytes,
|
|
4326
|
+
maxPartBytes: resolvedMaxPartBytes,
|
|
4327
|
+
...maxPartTokens === void 0 ? {} : { maxPartTokens },
|
|
3745
4328
|
largeFileBytes: boundedBytes(cliOptions?.largeFileBytes ?? userConfig?.largeFileBytes, 200 * 1024, 1, "largeFileBytes"),
|
|
3746
4329
|
previewHeadLines: strictPositiveInteger(userConfig?.previewHeadLines, 80, "`previewHeadLines`"),
|
|
3747
4330
|
previewTailLines: strictPositiveInteger(userConfig?.previewTailLines, 40, "`previewTailLines`"),
|
|
@@ -3751,6 +4334,11 @@ function resolveConfig(userConfig, cliOptions, baseDir) {
|
|
|
3751
4334
|
"maxMonolithBytes"
|
|
3752
4335
|
),
|
|
3753
4336
|
includeSensitiveFiles,
|
|
4337
|
+
...changedSince === void 0 ? {} : { changedSince },
|
|
4338
|
+
...staged ? { staged } : {},
|
|
4339
|
+
...withDependents ? { withDependents } : {},
|
|
4340
|
+
...skeleton ? { skeleton } : {},
|
|
4341
|
+
...sarif ? { sarif } : {},
|
|
3754
4342
|
include: resolveInclude(userConfig?.include),
|
|
3755
4343
|
preset: preset.presetName,
|
|
3756
4344
|
presetExclude,
|
|
@@ -3784,14 +4372,11 @@ function matchRule(filePath, matcher) {
|
|
|
3784
4372
|
if (pattern.endsWith("/**")) {
|
|
3785
4373
|
const prefix = pattern.slice(0, -3).replace(/\/+$/, "");
|
|
3786
4374
|
if (!prefix) return true;
|
|
3787
|
-
if (!/[*?]/.test(prefix)) {
|
|
4375
|
+
if (!/[*?{[]/.test(prefix)) {
|
|
3788
4376
|
return filePath === prefix || filePath.startsWith(`${prefix}/`);
|
|
3789
4377
|
}
|
|
3790
4378
|
}
|
|
3791
|
-
|
|
3792
|
-
return globToRegExp(pattern).test(filePath);
|
|
3793
|
-
}
|
|
3794
|
-
return filePath.includes(pattern);
|
|
4379
|
+
return matchesExclude(filePath, pattern);
|
|
3795
4380
|
}
|
|
3796
4381
|
return false;
|
|
3797
4382
|
}
|
|
@@ -3810,6 +4395,21 @@ function classifyKind(filePath, kinds) {
|
|
|
3810
4395
|
function configDigest(config) {
|
|
3811
4396
|
const material = {
|
|
3812
4397
|
maxPartBytes: config.maxPartBytes,
|
|
4398
|
+
// The user's own unit. `maxPartBytes` above already reflects the converted ceiling, but
|
|
4399
|
+
// recording the token number means changing 32k to 64k moves the digest even in the
|
|
4400
|
+
// corner where both round to the same byte budget.
|
|
4401
|
+
maxPartTokens: config.maxPartTokens ?? null,
|
|
4402
|
+
// A changed-files scope decides which files are in the snapshot at all, so a snapshot
|
|
4403
|
+
// taken over a different scope is a different snapshot.
|
|
4404
|
+
changedSince: config.changedSince ?? null,
|
|
4405
|
+
staged: config.staged === true,
|
|
4406
|
+
withDependents: config.withDependents === true,
|
|
4407
|
+
// Signature compression changes the emitted bytes of every TypeScript file it touches.
|
|
4408
|
+
skeleton: config.skeleton === true,
|
|
4409
|
+
// `sarif` is not one of `artifacts`: it writes `reports/audit.sarif` beside the audit, and
|
|
4410
|
+
// leaving it out of the digest meant a snapshot taken without it and one taken with it
|
|
4411
|
+
// compared equal even though their directories differ.
|
|
4412
|
+
sarif: config.sarif === true,
|
|
3813
4413
|
largeFileBytes: config.largeFileBytes,
|
|
3814
4414
|
previewHeadLines: config.previewHeadLines,
|
|
3815
4415
|
previewTailLines: config.previewTailLines,
|
|
@@ -3822,9 +4422,19 @@ function configDigest(config) {
|
|
|
3822
4422
|
preset: config.preset,
|
|
3823
4423
|
layersMode: config.layersMode,
|
|
3824
4424
|
budgets: config.budgets,
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
4425
|
+
// The matcher is what makes a layer a layer. Hashing only the names meant that
|
|
4426
|
+
// moving `{ name: "api", match: "src/api/**" }` to `"src/v2/**"` produced the same
|
|
4427
|
+
// digest, so `validate` reported "valid and up to date" for a snapshot whose files
|
|
4428
|
+
// had all been reclassified — and per-layer budgets applied to different files.
|
|
4429
|
+
layers: config.layers.map((layer2) => ({ name: layer2.name, match: String(layer2.match) })),
|
|
4430
|
+
kinds: config.kinds.map((kind) => ({ kind: kind.kind, match: String(kind.match) })),
|
|
4431
|
+
// How files are enumerated decides what is in the snapshot at all.
|
|
4432
|
+
discovery: config.discovery ?? "auto",
|
|
4433
|
+
// `flags` and `valueGroup` change *what is masked*, and a rule's source alone does
|
|
4434
|
+
// not imply either: the same source with and without `i` redacts different lines.
|
|
4435
|
+
secretRules: config.secretRules.map(
|
|
4436
|
+
(rule) => `${rule.rule}:${rule.pattern.source}/${rule.pattern.flags}:${rule.valueGroup ?? ""}`
|
|
4437
|
+
).sort()
|
|
3828
4438
|
};
|
|
3829
4439
|
return createHash("sha256").update(JSON.stringify(material)).digest("hex");
|
|
3830
4440
|
}
|
|
@@ -4004,6 +4614,95 @@ function generateAuditReport(manifest, secretFindings, secretRules) {
|
|
|
4004
4614
|
}
|
|
4005
4615
|
return lines.join("\n");
|
|
4006
4616
|
}
|
|
4617
|
+
var SARIF_VERSION = "2.1.0";
|
|
4618
|
+
var SARIF_SCHEMA = "https://json.schemastore.org/sarif-2.1.0.json";
|
|
4619
|
+
var BUILTIN_RULE_IDS = new Map(
|
|
4620
|
+
[
|
|
4621
|
+
...DEFAULT_SECRET_PATTERNS.map((rule) => rule.rule),
|
|
4622
|
+
// Produced by the whole-block pass in `redact.ts`, not by a pattern in the catalogue.
|
|
4623
|
+
"private-key-block"
|
|
4624
|
+
].map((name, index) => [name, `CS${String(index + 1).padStart(3, "0")}`])
|
|
4625
|
+
);
|
|
4626
|
+
function ruleIdFor(name) {
|
|
4627
|
+
const builtin = BUILTIN_RULE_IDS.get(name);
|
|
4628
|
+
if (builtin !== void 0) return builtin;
|
|
4629
|
+
const digest = crypto2.createHash("sha256").update(name).digest("hex").slice(0, 8);
|
|
4630
|
+
return `CS${digest.toUpperCase()}`;
|
|
4631
|
+
}
|
|
4632
|
+
function generateSarifReport(manifest, findings, rules, toolVersion) {
|
|
4633
|
+
const ruleNames = Array.from(
|
|
4634
|
+
/* @__PURE__ */ new Set([...rules.map((rule) => rule.rule), ...findings.flatMap((finding) => finding.rules)])
|
|
4635
|
+
).sort();
|
|
4636
|
+
const ruleIndex = /* @__PURE__ */ new Map();
|
|
4637
|
+
const sarifRules = ruleNames.map((name, index) => {
|
|
4638
|
+
ruleIndex.set(name, index);
|
|
4639
|
+
const id = ruleIdFor(name);
|
|
4640
|
+
return {
|
|
4641
|
+
id,
|
|
4642
|
+
name,
|
|
4643
|
+
shortDescription: { text: `Credential-shaped value masked by the "${name}" rule` },
|
|
4644
|
+
defaultConfiguration: { level: "note" },
|
|
4645
|
+
help: {
|
|
4646
|
+
text: "A value in this line matched a secret pattern and was replaced with a redaction marker in the snapshot. If this is a real credential, rotate it; if it is a fixture, the placeholder can be made unmistakable."
|
|
4647
|
+
}
|
|
4648
|
+
};
|
|
4649
|
+
});
|
|
4650
|
+
const results = findings.filter((finding) => typeof finding.path === "string" && finding.path.length > 0).map((finding) => {
|
|
4651
|
+
const primary = finding.rules[0] ?? "unknown";
|
|
4652
|
+
const line = Math.max(1, finding.line);
|
|
4653
|
+
const findingPath = String(finding.path).replace(/\\/g, "/");
|
|
4654
|
+
return {
|
|
4655
|
+
ruleId: ruleIdFor(primary),
|
|
4656
|
+
ruleIndex: ruleIndex.get(primary) ?? 0,
|
|
4657
|
+
level: "note",
|
|
4658
|
+
message: {
|
|
4659
|
+
text: `Credential-shaped value masked (rule${finding.rules.length === 1 ? "" : "s"}: ${finding.rules.join(", ")})`
|
|
4660
|
+
},
|
|
4661
|
+
locations: [
|
|
4662
|
+
{
|
|
4663
|
+
physicalLocation: {
|
|
4664
|
+
artifactLocation: { uri: findingPath, uriBaseId: "%SRCROOT%" },
|
|
4665
|
+
region: { startLine: line }
|
|
4666
|
+
}
|
|
4667
|
+
}
|
|
4668
|
+
],
|
|
4669
|
+
partialFingerprints: {
|
|
4670
|
+
// Stable across commits for the same finding, which is what keeps an alert open
|
|
4671
|
+
// instead of closing and reopening it on every run.
|
|
4672
|
+
codebaseSnapshotFinding: `${findingPath}#L${line}#${finding.rules.join("+")}`
|
|
4673
|
+
}
|
|
4674
|
+
};
|
|
4675
|
+
}).sort(
|
|
4676
|
+
(a, b) => a.locations[0].physicalLocation.artifactLocation.uri.localeCompare(
|
|
4677
|
+
b.locations[0].physicalLocation.artifactLocation.uri
|
|
4678
|
+
) || a.locations[0].physicalLocation.region.startLine - b.locations[0].physicalLocation.region.startLine
|
|
4679
|
+
);
|
|
4680
|
+
const log = {
|
|
4681
|
+
$schema: SARIF_SCHEMA,
|
|
4682
|
+
version: SARIF_VERSION,
|
|
4683
|
+
runs: [
|
|
4684
|
+
{
|
|
4685
|
+
tool: {
|
|
4686
|
+
driver: {
|
|
4687
|
+
name: "codebase-snapshot",
|
|
4688
|
+
version: toolVersion,
|
|
4689
|
+
informationUri: "https://github.com/ankurhalder/codebase-snapshot",
|
|
4690
|
+
rules: sarifRules
|
|
4691
|
+
}
|
|
4692
|
+
},
|
|
4693
|
+
automationDetails: {
|
|
4694
|
+
// The snapshot's own fingerprint: two uploads of the same snapshot are the same run,
|
|
4695
|
+
// so Code Scanning can tell an unchanged upload from a new one.
|
|
4696
|
+
id: `codebase-snapshot/${manifest.fingerprint}`
|
|
4697
|
+
},
|
|
4698
|
+
originalUriBaseIds: { "%SRCROOT%": { uri: "file:///" } },
|
|
4699
|
+
results
|
|
4700
|
+
}
|
|
4701
|
+
]
|
|
4702
|
+
};
|
|
4703
|
+
return `${JSON.stringify(log, null, 2)}
|
|
4704
|
+
`;
|
|
4705
|
+
}
|
|
4007
4706
|
|
|
4008
4707
|
// src/partitioner/preview.ts
|
|
4009
4708
|
function countLines(content) {
|
|
@@ -4053,14 +4752,29 @@ var HEADER_RESERVE_BYTES = 2048 + Buffer.byteLength(UNTRUSTED_CONTENT_NOTICE, "u
|
|
|
4053
4752
|
var MIN_SECTION_BUDGET_BYTES = 512;
|
|
4054
4753
|
var MAX_SPLIT_PASSES = 64;
|
|
4055
4754
|
var BUDGET_MARKER_RESERVE = 96;
|
|
4755
|
+
function insertTokenLine(markdown, tokens) {
|
|
4756
|
+
return markdown.replace(
|
|
4757
|
+
/^(- Files: .*)$/m,
|
|
4758
|
+
`$1
|
|
4759
|
+
- Estimated Tokens: ~${formatTokens(tokens)}`
|
|
4760
|
+
);
|
|
4761
|
+
}
|
|
4762
|
+
function replaceTokenLine(markdown, tokens) {
|
|
4763
|
+
const line = `- Estimated Tokens: ~${formatTokens(tokens)}`;
|
|
4764
|
+
if (/^- Estimated Tokens: .*$/m.test(markdown)) {
|
|
4765
|
+
return markdown.replace(/^- Estimated Tokens: .*$/m, line);
|
|
4766
|
+
}
|
|
4767
|
+
return insertTokenLine(markdown, tokens);
|
|
4768
|
+
}
|
|
4056
4769
|
var BUDGET_MARKER_SUFFIX = /\n\.\.\. \[\d+ lines omitted: part budget \d+ B\] \.\.\.$/;
|
|
4057
|
-
|
|
4770
|
+
var TOKEN_LINE = /^- Estimated Tokens: .*$/m;
|
|
4771
|
+
var TOKEN_LINE_SLACK = 32;
|
|
4772
|
+
function truncateWithBudgetMarker(content, maxBytes, budget, originalLines) {
|
|
4058
4773
|
const base = content.replace(BUDGET_MARKER_SUFFIX, "");
|
|
4059
|
-
const baseLines = countLines(base);
|
|
4060
4774
|
const truncated = truncateToBytes(base, Math.max(0, maxBytes));
|
|
4061
4775
|
if (!truncated.isTruncated) return { content: base, isTruncated: false };
|
|
4062
4776
|
const keptLines = countLines(truncated.content);
|
|
4063
|
-
const omitted = Math.max(1,
|
|
4777
|
+
const omitted = Math.max(1, Math.max(countLines(base), originalLines) - keptLines);
|
|
4064
4778
|
return {
|
|
4065
4779
|
content: `${truncated.content}
|
|
4066
4780
|
... [${omitted} lines omitted: part budget ${budget} B] ...`,
|
|
@@ -4074,7 +4788,7 @@ function partitionFiles(files, maxPartBytes, contentFingerprint) {
|
|
|
4074
4788
|
const prepared = files.map((file) => prepareSection(file, sectionBudget));
|
|
4075
4789
|
let groups = greedyGroups(prepared, budget);
|
|
4076
4790
|
for (let pass = 0; pass < MAX_SPLIT_PASSES; pass++) {
|
|
4077
|
-
const rendered = renderGroups(groups, contentFingerprint);
|
|
4791
|
+
const rendered = renderGroups(groups, budget, contentFingerprint);
|
|
4078
4792
|
const oversized = rendered.findIndex((part) => part.metadata.byteSize > budget);
|
|
4079
4793
|
if (oversized === -1) return rendered;
|
|
4080
4794
|
const group = groups[oversized];
|
|
@@ -4090,28 +4804,49 @@ function partitionFiles(files, maxPartBytes, contentFingerprint) {
|
|
|
4090
4804
|
0,
|
|
4091
4805
|
Buffer.byteLength(currentContent, "utf8") - overflow - BUDGET_MARKER_RESERVE
|
|
4092
4806
|
);
|
|
4093
|
-
const shrunk = truncateWithBudgetMarker(
|
|
4807
|
+
const shrunk = truncateWithBudgetMarker(
|
|
4808
|
+
currentContent,
|
|
4809
|
+
shrinkTo,
|
|
4810
|
+
budget,
|
|
4811
|
+
section.file.originalLines
|
|
4812
|
+
);
|
|
4094
4813
|
if (shrunk.isTruncated) {
|
|
4095
4814
|
applyTruncatedContent(section.file, shrunk.content);
|
|
4096
4815
|
section.text = renderFileSection(section.file);
|
|
4097
4816
|
section.byteSize = Buffer.byteLength(section.text, "utf8");
|
|
4098
4817
|
continue;
|
|
4099
4818
|
}
|
|
4100
|
-
const marker2 =
|
|
4101
|
-
|
|
4819
|
+
const marker2 = TRUNCATION_MARKER;
|
|
4820
|
+
assertBudgetHoldsMarker(budget);
|
|
4821
|
+
const target = Math.max(
|
|
4822
|
+
0,
|
|
4823
|
+
budget - Buffer.byteLength(marker2, "utf8") - TOKEN_LINE_SLACK
|
|
4824
|
+
);
|
|
4102
4825
|
const clipped = truncateToBytes(rendered[oversized].markdown, target);
|
|
4103
4826
|
rendered[oversized] = rehashPart(rendered[oversized], clipped.content + marker2);
|
|
4104
4827
|
return rendered;
|
|
4105
4828
|
}
|
|
4106
|
-
return enforceBudget(renderGroups(groups, contentFingerprint), budget);
|
|
4829
|
+
return enforceBudget(renderGroups(groups, budget, contentFingerprint), budget);
|
|
4107
4830
|
}
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
const
|
|
4111
|
-
|
|
4831
|
+
var TRUNCATION_MARKER = "\n... [part truncated: cannot fit budget] ...\n";
|
|
4832
|
+
function assertBudgetHoldsMarker(budget) {
|
|
4833
|
+
const minimum = Buffer.byteLength(TRUNCATION_MARKER, "utf8");
|
|
4834
|
+
if (budget < minimum) {
|
|
4835
|
+
throw new Error(
|
|
4836
|
+
`A part budget of ${budget} B cannot hold the truncation notice (${minimum} B), so a clipped part could not fit and still say it was clipped. Use a budget of at least ${minimum} B.`
|
|
4837
|
+
);
|
|
4838
|
+
}
|
|
4839
|
+
}
|
|
4840
|
+
function enforceBudget(parts, budget) {
|
|
4841
|
+
const target = Math.max(
|
|
4842
|
+
0,
|
|
4843
|
+
budget - Buffer.byteLength(TRUNCATION_MARKER, "utf8") - TOKEN_LINE_SLACK
|
|
4844
|
+
);
|
|
4845
|
+
return parts.map((part) => {
|
|
4112
4846
|
if (part.metadata.byteSize <= budget) return part;
|
|
4847
|
+
assertBudgetHoldsMarker(budget);
|
|
4113
4848
|
const clipped = truncateToBytes(part.markdown, target);
|
|
4114
|
-
return rehashPart(part, clipped.content +
|
|
4849
|
+
return rehashPart(part, clipped.content + TRUNCATION_MARKER);
|
|
4115
4850
|
});
|
|
4116
4851
|
}
|
|
4117
4852
|
function renumberParts(parts, contentFingerprint) {
|
|
@@ -4120,7 +4855,7 @@ function renumberParts(parts, contentFingerprint) {
|
|
|
4120
4855
|
const zeroPad = (index) => String(index).padStart(Math.max(3, String(total).length), "0");
|
|
4121
4856
|
const nameFor = (position) => `part-${zeroPad(position)}.md`;
|
|
4122
4857
|
const marker2 = "## Contents\n";
|
|
4123
|
-
|
|
4858
|
+
const renumbered = parts.map((part, index) => {
|
|
4124
4859
|
const markerIndex = part.markdown.indexOf(marker2);
|
|
4125
4860
|
const body = markerIndex === -1 ? "" : part.markdown.slice(markerIndex + marker2.length);
|
|
4126
4861
|
const included = part.metadata.includedFiles;
|
|
@@ -4156,19 +4891,25 @@ function renumberParts(parts, contentFingerprint) {
|
|
|
4156
4891
|
"## Contents",
|
|
4157
4892
|
""
|
|
4158
4893
|
].join("\n");
|
|
4159
|
-
const
|
|
4894
|
+
const assembled = (header + body).replace(/\r\n/g, "\n");
|
|
4895
|
+
const estimatedTokens = estimateTokens(assembled);
|
|
4896
|
+
const markdown = insertTokenLine(assembled, estimatedTokens);
|
|
4160
4897
|
const metadata = {
|
|
4161
4898
|
...part.metadata,
|
|
4162
4899
|
index: index + 1,
|
|
4163
4900
|
filename: nameFor(index + 1),
|
|
4164
4901
|
byteSize: Buffer.byteLength(markdown, "utf8"),
|
|
4165
|
-
|
|
4902
|
+
estimatedTokens,
|
|
4903
|
+
sha256: crypto2.createHash("sha256").update(markdown).digest("hex"),
|
|
4166
4904
|
lines: countLines(markdown),
|
|
4167
4905
|
fileCount: included.length,
|
|
4168
4906
|
layersCovered: Object.keys(layerCounts).sort()
|
|
4169
4907
|
};
|
|
4170
4908
|
return { filename: metadata.filename, markdown, metadata };
|
|
4171
4909
|
});
|
|
4910
|
+
return renumbered.map(
|
|
4911
|
+
(part) => enforceBudget([part], part.metadata.budgetBytes)[0] ?? part
|
|
4912
|
+
);
|
|
4172
4913
|
}
|
|
4173
4914
|
var SECTION_TRUNCATION_NOTICE = "\n... [section truncated: metadata alone exceeds the part budget] ...\n";
|
|
4174
4915
|
var METADATA_VALUE_MAX = 200;
|
|
@@ -4192,7 +4933,12 @@ function prepareSection(file, sectionBudget) {
|
|
|
4192
4933
|
const overhead = byteSize - Buffer.byteLength(content, "utf8");
|
|
4193
4934
|
const allowed = sectionBudget - overhead - BUDGET_MARKER_RESERVE;
|
|
4194
4935
|
if (allowed <= 0) return;
|
|
4195
|
-
const truncated = truncateWithBudgetMarker(
|
|
4936
|
+
const truncated = truncateWithBudgetMarker(
|
|
4937
|
+
content,
|
|
4938
|
+
allowed,
|
|
4939
|
+
sectionBudget,
|
|
4940
|
+
file.originalLines
|
|
4941
|
+
);
|
|
4196
4942
|
if (!truncated.isTruncated) return;
|
|
4197
4943
|
applyTruncatedContent(file, truncated.content);
|
|
4198
4944
|
text2 = renderFileSection(file);
|
|
@@ -4222,14 +4968,17 @@ function applyTruncatedContent(file, content) {
|
|
|
4222
4968
|
file.includedLines = countLines(content);
|
|
4223
4969
|
}
|
|
4224
4970
|
function rehashPart(part, markdown) {
|
|
4971
|
+
const estimatedTokens = estimateTokens(markdown.replace(TOKEN_LINE, ""));
|
|
4972
|
+
const redistributed = replaceTokenLine(markdown, estimatedTokens);
|
|
4225
4973
|
return {
|
|
4226
4974
|
filename: part.filename,
|
|
4227
|
-
markdown,
|
|
4975
|
+
markdown: redistributed,
|
|
4228
4976
|
metadata: {
|
|
4229
4977
|
...part.metadata,
|
|
4230
|
-
byteSize: Buffer.byteLength(
|
|
4231
|
-
sha256:
|
|
4232
|
-
lines: countLines(
|
|
4978
|
+
byteSize: Buffer.byteLength(redistributed, "utf8"),
|
|
4979
|
+
sha256: crypto2.createHash("sha256").update(redistributed).digest("hex"),
|
|
4980
|
+
lines: countLines(redistributed),
|
|
4981
|
+
estimatedTokens
|
|
4233
4982
|
}
|
|
4234
4983
|
};
|
|
4235
4984
|
}
|
|
@@ -4258,7 +5007,7 @@ function greedyGroups(sections, budget) {
|
|
|
4258
5007
|
if (current.length > 0) groups.push(current);
|
|
4259
5008
|
return groups;
|
|
4260
5009
|
}
|
|
4261
|
-
function renderGroups(groups, contentFingerprint) {
|
|
5010
|
+
function renderGroups(groups, budget, contentFingerprint) {
|
|
4262
5011
|
const total = groups.length;
|
|
4263
5012
|
const zeroPad = (index) => String(index).padStart(Math.max(3, String(total).length), "0");
|
|
4264
5013
|
return groups.map((group, index) => {
|
|
@@ -4298,9 +5047,11 @@ function renderGroups(groups, contentFingerprint) {
|
|
|
4298
5047
|
"## Contents",
|
|
4299
5048
|
""
|
|
4300
5049
|
].join("\n");
|
|
4301
|
-
const
|
|
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);
|
|
4302
5053
|
const byteSize = Buffer.byteLength(content, "utf8");
|
|
4303
|
-
const sha256 =
|
|
5054
|
+
const sha256 = crypto2.createHash("sha256").update(content).digest("hex");
|
|
4304
5055
|
const includedFiles = group.map(({ file }) => ({
|
|
4305
5056
|
relativePath: file.relativePath,
|
|
4306
5057
|
sourceBytes: file.sizeBytes,
|
|
@@ -4313,6 +5064,8 @@ function renderGroups(groups, contentFingerprint) {
|
|
|
4313
5064
|
index: index + 1,
|
|
4314
5065
|
filename,
|
|
4315
5066
|
byteSize,
|
|
5067
|
+
estimatedTokens,
|
|
5068
|
+
budgetBytes: budget,
|
|
4316
5069
|
sha256,
|
|
4317
5070
|
lines: countLines(content),
|
|
4318
5071
|
fileCount: group.length,
|
|
@@ -4326,6 +5079,16 @@ function emittedBody(content) {
|
|
|
4326
5079
|
const normalised = content.replace(/\r\n/g, "\n");
|
|
4327
5080
|
return normalised.endsWith("\n") ? normalised.slice(0, -1) : normalised;
|
|
4328
5081
|
}
|
|
5082
|
+
function compactMetadata(file) {
|
|
5083
|
+
const count = (value) => `${value} line${value === 1 ? "" : "s"}`;
|
|
5084
|
+
const lines = file.includedLines === file.originalLines ? count(file.originalLines) : `${count(file.includedLines)} of ${count(file.originalLines)}`;
|
|
5085
|
+
return [
|
|
5086
|
+
`- Meta: ${capMetadata(file.layer)}/${capMetadata(file.kind)}`,
|
|
5087
|
+
capMetadata(file.language),
|
|
5088
|
+
file.inclusionMode,
|
|
5089
|
+
`${lines} (${file.sizeBytes} B)`
|
|
5090
|
+
].join(" | ");
|
|
5091
|
+
}
|
|
4329
5092
|
function renderFileSection(file) {
|
|
4330
5093
|
const content = file.content ?? "";
|
|
4331
5094
|
const body = emittedBody(content);
|
|
@@ -4337,16 +5100,9 @@ function renderFileSection(file) {
|
|
|
4337
5100
|
`<a id="${anchor}"></a>`,
|
|
4338
5101
|
`### FILE: ${displayPath(file.relativePath)}`,
|
|
4339
5102
|
"",
|
|
4340
|
-
|
|
4341
|
-
`- Kind: ${capMetadata(file.kind)}`,
|
|
4342
|
-
`- Language: ${capMetadata(file.language)}`,
|
|
4343
|
-
`- Inclusion: ${file.inclusionMode}`,
|
|
4344
|
-
`- Source Bytes: ${file.sizeBytes}`,
|
|
4345
|
-
`- Source Lines: ${file.originalLines}`,
|
|
4346
|
-
`- Included Lines: ${file.includedLines}`,
|
|
4347
|
-
`- SHA-256: ${file.hashSha256}`,
|
|
5103
|
+
compactMetadata(file),
|
|
4348
5104
|
...file.summaryQuality ? [`- Summary Quality: ${file.summaryQuality}`] : [],
|
|
4349
|
-
...renderSummary(file.summary),
|
|
5105
|
+
...renderSummary(file.summary, file.inclusionMode),
|
|
4350
5106
|
...file.summaryOmitted ? ["- Summary: (omitted: exceeds the part budget)"] : [],
|
|
4351
5107
|
"",
|
|
4352
5108
|
file.inclusionMode === "preview" ? "#### Bounded Preview" : "#### Content",
|
|
@@ -4366,14 +5122,15 @@ function fenceForText(content, minimum) {
|
|
|
4366
5122
|
const maxRun = matches?.reduce((max, run) => Math.max(max, run.length), 0) ?? 0;
|
|
4367
5123
|
return "`".repeat(Math.max(minimum, maxRun + 1));
|
|
4368
5124
|
}
|
|
4369
|
-
function renderSummary(summary) {
|
|
5125
|
+
function renderSummary(summary, inclusionMode) {
|
|
4370
5126
|
if (!summary) return [];
|
|
4371
5127
|
if (typeof summary === "string") return [`- Summary: ${summary}`];
|
|
5128
|
+
if (inclusionMode === "full") return [];
|
|
4372
5129
|
return ["- Summary:", "```json", JSON.stringify(summary, null, 2), "```"];
|
|
4373
5130
|
}
|
|
4374
5131
|
function anchorForPath(filePath) {
|
|
4375
5132
|
const slug = filePath.toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
4376
|
-
const digest =
|
|
5133
|
+
const digest = crypto2.createHash("sha256").update(filePath).digest("hex").slice(0, 8);
|
|
4377
5134
|
return `${slug || "file"}-${digest}`;
|
|
4378
5135
|
}
|
|
4379
5136
|
|
|
@@ -4669,7 +5426,7 @@ var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
4669
5426
|
".so"
|
|
4670
5427
|
]);
|
|
4671
5428
|
function isBinaryExtension(filePath) {
|
|
4672
|
-
const ext =
|
|
5429
|
+
const ext = path16.extname(filePath).toLowerCase();
|
|
4673
5430
|
return BINARY_EXTENSIONS.has(ext);
|
|
4674
5431
|
}
|
|
4675
5432
|
function isBinaryBuffer(buffer) {
|
|
@@ -4839,13 +5596,13 @@ function isBinaryFile(absolutePath) {
|
|
|
4839
5596
|
if (isBinaryExtension(absolutePath)) return true;
|
|
4840
5597
|
let fd;
|
|
4841
5598
|
try {
|
|
4842
|
-
fd =
|
|
5599
|
+
fd = fs14.openSync(absolutePath, "r");
|
|
4843
5600
|
const buffer = Buffer.alloc(CHUNK);
|
|
4844
5601
|
let sample;
|
|
4845
5602
|
let position = 0;
|
|
4846
5603
|
while (position < BINARY_SCAN_BYTES) {
|
|
4847
5604
|
const wanted = Math.min(CHUNK, BINARY_SCAN_BYTES - position);
|
|
4848
|
-
const bytesRead =
|
|
5605
|
+
const bytesRead = fs14.readSync(fd, buffer, 0, wanted, position);
|
|
4849
5606
|
if (bytesRead === 0) return false;
|
|
4850
5607
|
const chunk = buffer.subarray(0, bytesRead);
|
|
4851
5608
|
if (sample === void 0) {
|
|
@@ -4863,12 +5620,145 @@ function isBinaryFile(absolutePath) {
|
|
|
4863
5620
|
} finally {
|
|
4864
5621
|
if (fd !== void 0) {
|
|
4865
5622
|
try {
|
|
4866
|
-
|
|
5623
|
+
fs14.closeSync(fd);
|
|
4867
5624
|
} catch {
|
|
4868
5625
|
}
|
|
4869
5626
|
}
|
|
4870
5627
|
}
|
|
4871
5628
|
}
|
|
5629
|
+
function gitPaths(cwd, args) {
|
|
5630
|
+
let output;
|
|
5631
|
+
try {
|
|
5632
|
+
output = execFileSync("git", args, {
|
|
5633
|
+
cwd,
|
|
5634
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
5635
|
+
encoding: "utf8",
|
|
5636
|
+
maxBuffer: 64 * 1024 * 1024
|
|
5637
|
+
});
|
|
5638
|
+
} catch (err) {
|
|
5639
|
+
const stderr = err.stderr ?? "";
|
|
5640
|
+
const reason = stderr.trim() || err.message;
|
|
5641
|
+
throw new Error(
|
|
5642
|
+
`Could not list changed files (git ${args.join(" ")}): ${reason}. A changed-files snapshot needs a git working tree and a ref that exists; run \`codebase-snapshot generate\` without --changed-since to scan everything.`
|
|
5643
|
+
);
|
|
5644
|
+
}
|
|
5645
|
+
return output.split("\0").map((entry) => entry.trim().replace(/\\/g, "/")).filter((entry) => entry.length > 0);
|
|
5646
|
+
}
|
|
5647
|
+
function assertUsableRef(ref) {
|
|
5648
|
+
if (ref.startsWith("-")) {
|
|
5649
|
+
throw new Error(
|
|
5650
|
+
`--changed-since must be a git ref, not an option (received ${JSON.stringify(ref)}). Name a branch, tag, or commit such as \`main\` or \`HEAD~1\`.`
|
|
5651
|
+
);
|
|
5652
|
+
}
|
|
5653
|
+
}
|
|
5654
|
+
function resolveChangedScope(cwd, request) {
|
|
5655
|
+
const paths = /* @__PURE__ */ new Set();
|
|
5656
|
+
const descriptions = [];
|
|
5657
|
+
if (request.changedSince !== void 0) {
|
|
5658
|
+
assertUsableRef(request.changedSince);
|
|
5659
|
+
for (const path17 of gitPaths(cwd, [
|
|
5660
|
+
"diff",
|
|
5661
|
+
"--name-only",
|
|
5662
|
+
"-z",
|
|
5663
|
+
"--relative",
|
|
5664
|
+
request.changedSince,
|
|
5665
|
+
"--"
|
|
5666
|
+
])) {
|
|
5667
|
+
paths.add(path17);
|
|
5668
|
+
}
|
|
5669
|
+
for (const path17 of gitPaths(cwd, ["ls-files", "--others", "--exclude-standard", "-z", "--"])) {
|
|
5670
|
+
paths.add(path17);
|
|
5671
|
+
}
|
|
5672
|
+
descriptions.push(`changed since ${request.changedSince}`);
|
|
5673
|
+
}
|
|
5674
|
+
if (request.staged === true) {
|
|
5675
|
+
for (const path17 of gitPaths(cwd, [
|
|
5676
|
+
"diff",
|
|
5677
|
+
"--cached",
|
|
5678
|
+
"--name-only",
|
|
5679
|
+
"-z",
|
|
5680
|
+
"--relative",
|
|
5681
|
+
"--"
|
|
5682
|
+
])) {
|
|
5683
|
+
paths.add(path17);
|
|
5684
|
+
}
|
|
5685
|
+
descriptions.push("staged");
|
|
5686
|
+
}
|
|
5687
|
+
return { paths, detail: descriptions.join(" and ") };
|
|
5688
|
+
}
|
|
5689
|
+
var RELATIVE_SPECIFIER = /^\.{1,2}\//;
|
|
5690
|
+
var IMPORT_CANDIDATE_SUFFIXES = [
|
|
5691
|
+
".ts",
|
|
5692
|
+
".tsx",
|
|
5693
|
+
".mts",
|
|
5694
|
+
".cts",
|
|
5695
|
+
".js",
|
|
5696
|
+
".jsx",
|
|
5697
|
+
".mjs",
|
|
5698
|
+
".cjs",
|
|
5699
|
+
"/index.ts",
|
|
5700
|
+
"/index.tsx",
|
|
5701
|
+
"/index.js",
|
|
5702
|
+
"/index.mjs"
|
|
5703
|
+
];
|
|
5704
|
+
var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
5705
|
+
".ts",
|
|
5706
|
+
".tsx",
|
|
5707
|
+
".mts",
|
|
5708
|
+
".cts",
|
|
5709
|
+
".js",
|
|
5710
|
+
".jsx",
|
|
5711
|
+
".mjs",
|
|
5712
|
+
".cjs"
|
|
5713
|
+
]);
|
|
5714
|
+
function buildImportResolver(files) {
|
|
5715
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
5716
|
+
const byPathFolded = /* @__PURE__ */ new Map();
|
|
5717
|
+
const byStem = /* @__PURE__ */ new Map();
|
|
5718
|
+
const byStemFolded = /* @__PURE__ */ new Map();
|
|
5719
|
+
const remember = (map, key, value) => {
|
|
5720
|
+
if (!map.has(key)) map.set(key, value);
|
|
5721
|
+
};
|
|
5722
|
+
for (const file of files) {
|
|
5723
|
+
const relativePath = file.relativePath;
|
|
5724
|
+
remember(byPath, relativePath, relativePath);
|
|
5725
|
+
remember(byPathFolded, relativePath.toLowerCase(), relativePath);
|
|
5726
|
+
const extension = path16.posix.extname(relativePath);
|
|
5727
|
+
if (!SOURCE_EXTENSIONS.has(extension)) continue;
|
|
5728
|
+
const stem = relativePath.slice(0, -extension.length);
|
|
5729
|
+
remember(byStem, stem, relativePath);
|
|
5730
|
+
remember(byStemFolded, stem.toLowerCase(), relativePath);
|
|
5731
|
+
if (path16.posix.basename(stem) === "index") {
|
|
5732
|
+
const directory = path16.posix.dirname(stem);
|
|
5733
|
+
remember(byStem, directory, relativePath);
|
|
5734
|
+
remember(byStemFolded, directory.toLowerCase(), relativePath);
|
|
5735
|
+
}
|
|
5736
|
+
}
|
|
5737
|
+
return { byPath, byPathFolded, byStem, byStemFolded };
|
|
5738
|
+
}
|
|
5739
|
+
function resolveSpecifier(fromFile, specifier, resolver) {
|
|
5740
|
+
if (!RELATIVE_SPECIFIER.test(specifier)) return null;
|
|
5741
|
+
const joined = path16.posix.normalize(
|
|
5742
|
+
path16.posix.join(path16.posix.dirname(fromFile), specifier)
|
|
5743
|
+
);
|
|
5744
|
+
const exact = (key) => resolver.byPath.get(key) ?? resolver.byPathFolded.get(key.toLowerCase()) ?? null;
|
|
5745
|
+
const direct = exact(joined);
|
|
5746
|
+
if (direct) return direct;
|
|
5747
|
+
for (const suffix of IMPORT_CANDIDATE_SUFFIXES) {
|
|
5748
|
+
const hit = exact(`${joined}${suffix}`);
|
|
5749
|
+
if (hit) return hit;
|
|
5750
|
+
}
|
|
5751
|
+
const stem = joined.replace(/\.(?:js|mjs|cjs|jsx)$/, "");
|
|
5752
|
+
const byStem = (key) => resolver.byStem.get(key) ?? resolver.byStemFolded.get(key.toLowerCase()) ?? null;
|
|
5753
|
+
return byStem(joined) ?? byStem(stem);
|
|
5754
|
+
}
|
|
5755
|
+
function importsChangedFile(importerPath, imports, changedPaths, resolver) {
|
|
5756
|
+
for (const specifier of imports) {
|
|
5757
|
+
const resolved = resolveSpecifier(importerPath, specifier, resolver);
|
|
5758
|
+
if (resolved !== null && changedPaths.has(resolved)) return true;
|
|
5759
|
+
}
|
|
5760
|
+
return false;
|
|
5761
|
+
}
|
|
4872
5762
|
var LANGUAGE_BY_EXTENSION = {
|
|
4873
5763
|
".ts": "typescript",
|
|
4874
5764
|
".mts": "typescript",
|
|
@@ -4902,6 +5792,8 @@ var LANGUAGE_BY_EXTENSION = {
|
|
|
4902
5792
|
".java": "java",
|
|
4903
5793
|
".rb": "ruby",
|
|
4904
5794
|
".php": "php",
|
|
5795
|
+
".swift": "swift",
|
|
5796
|
+
".dart": "dart",
|
|
4905
5797
|
".toml": "toml",
|
|
4906
5798
|
".ini": "ini",
|
|
4907
5799
|
".env": "dotenv",
|
|
@@ -4910,7 +5802,8 @@ var LANGUAGE_BY_EXTENSION = {
|
|
|
4910
5802
|
".graphql": "graphql",
|
|
4911
5803
|
".gql": "graphql",
|
|
4912
5804
|
".proto": "protobuf",
|
|
4913
|
-
".tf": "hcl"
|
|
5805
|
+
".tf": "hcl",
|
|
5806
|
+
".tfvars": "hcl"
|
|
4914
5807
|
};
|
|
4915
5808
|
var LANGUAGE_BY_BASENAME = {
|
|
4916
5809
|
Dockerfile: "dockerfile",
|
|
@@ -4923,44 +5816,66 @@ var LANGUAGE_BY_BASENAME = {
|
|
|
4923
5816
|
};
|
|
4924
5817
|
function detectLanguage(filePath) {
|
|
4925
5818
|
const normalized = filePath.replace(/\\/g, "/");
|
|
4926
|
-
const base =
|
|
5819
|
+
const base = path16.posix.basename(normalized);
|
|
4927
5820
|
if (LANGUAGE_BY_BASENAME[base]) return LANGUAGE_BY_BASENAME[base];
|
|
4928
5821
|
if (base === ".env" || base.startsWith(".env.")) return "dotenv";
|
|
4929
|
-
const ext =
|
|
5822
|
+
const ext = path16.posix.extname(base).toLowerCase();
|
|
4930
5823
|
return LANGUAGE_BY_EXTENSION[ext] ?? "text";
|
|
4931
5824
|
}
|
|
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
|
|
4932
5847
|
function maskedPath(value, secretRules) {
|
|
4933
5848
|
return findSecretInPath(value, secretRules)?.masked ?? value;
|
|
4934
5849
|
}
|
|
4935
5850
|
function confineToDirectory(directory, filename) {
|
|
4936
5851
|
if (filename.includes("\0")) return null;
|
|
4937
|
-
if (
|
|
4938
|
-
const base =
|
|
4939
|
-
const resolved =
|
|
5852
|
+
if (path16.isAbsolute(filename)) return null;
|
|
5853
|
+
const base = path16.resolve(directory);
|
|
5854
|
+
const resolved = path16.resolve(base, filename);
|
|
4940
5855
|
const caseInsensitive = process.platform === "win32" || process.platform === "darwin";
|
|
4941
|
-
const prefix = caseInsensitive ? `${base}${
|
|
5856
|
+
const prefix = caseInsensitive ? `${base}${path16.sep}`.toLowerCase() : `${base}${path16.sep}`;
|
|
4942
5857
|
const candidate = caseInsensitive ? resolved.toLowerCase() : resolved;
|
|
4943
5858
|
return candidate.startsWith(prefix) ? resolved : null;
|
|
4944
5859
|
}
|
|
4945
5860
|
function findStagingDirectory(config) {
|
|
4946
|
-
const tmpDir =
|
|
5861
|
+
const tmpDir = path16.resolve(config.cwd, config.tmpDir);
|
|
4947
5862
|
let entries;
|
|
4948
5863
|
try {
|
|
4949
|
-
entries =
|
|
5864
|
+
entries = fs14.readdirSync(tmpDir);
|
|
4950
5865
|
} catch {
|
|
4951
5866
|
return null;
|
|
4952
5867
|
}
|
|
4953
5868
|
const runs = entries.filter((name) => name.startsWith("run-")).sort();
|
|
4954
5869
|
const newest = runs[runs.length - 1];
|
|
4955
|
-
return newest ?
|
|
5870
|
+
return newest ? path16.join(tmpDir, newest) : null;
|
|
4956
5871
|
}
|
|
4957
5872
|
function describeMissingManifest(config, manifestPath) {
|
|
4958
|
-
const outDir =
|
|
4959
|
-
const markerPath =
|
|
5873
|
+
const outDir = path16.resolve(config.cwd, config.outDir);
|
|
5874
|
+
const markerPath = path16.join(outDir, ".codebase-snapshot");
|
|
4960
5875
|
let marker2 = null;
|
|
4961
5876
|
try {
|
|
4962
|
-
if (
|
|
4963
|
-
const parsed = JSON.parse(
|
|
5877
|
+
if (fs14.existsSync(markerPath)) {
|
|
5878
|
+
const parsed = JSON.parse(fs14.readFileSync(markerPath, "utf8"));
|
|
4964
5879
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
4965
5880
|
marker2 = parsed;
|
|
4966
5881
|
}
|
|
@@ -4992,18 +5907,18 @@ function emptyDrift() {
|
|
|
4992
5907
|
};
|
|
4993
5908
|
}
|
|
4994
5909
|
function sha256Hex(buffer) {
|
|
4995
|
-
return
|
|
5910
|
+
return crypto2.createHash("sha256").update(buffer).digest("hex");
|
|
4996
5911
|
}
|
|
4997
5912
|
var VERIFY_SNIFF_MAX_BYTES = 8 * 1024 * 1024;
|
|
4998
|
-
function wouldNowBeIncluded(cwd, relativePath
|
|
4999
|
-
const absolutePath =
|
|
5913
|
+
function wouldNowBeIncluded(cwd, relativePath) {
|
|
5914
|
+
const absolutePath = path16.join(cwd, relativePath);
|
|
5000
5915
|
try {
|
|
5001
|
-
const stats =
|
|
5916
|
+
const stats = fs14.statSync(absolutePath);
|
|
5002
5917
|
if (!stats.isFile()) return false;
|
|
5003
|
-
if (
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
return !
|
|
5918
|
+
if (stats.size <= VERIFY_SNIFF_MAX_BYTES) {
|
|
5919
|
+
return !decodeText(fs14.readFileSync(absolutePath)).binary;
|
|
5920
|
+
}
|
|
5921
|
+
return !isBinaryFile(absolutePath);
|
|
5007
5922
|
} catch {
|
|
5008
5923
|
return false;
|
|
5009
5924
|
}
|
|
@@ -5061,8 +5976,8 @@ function describeManifestShapeProblem(value) {
|
|
|
5061
5976
|
function validateSnapshot(configInput) {
|
|
5062
5977
|
assertUsableConfigInput(configInput);
|
|
5063
5978
|
const config = isResolvedConfig(configInput) ? normalizeResolvedConfig(configInput) : resolveConfig(configInput);
|
|
5064
|
-
const manifestPath =
|
|
5065
|
-
if (!
|
|
5979
|
+
const manifestPath = path16.resolve(config.cwd, config.outDir, "manifest.json");
|
|
5980
|
+
if (!fs14.existsSync(manifestPath)) {
|
|
5066
5981
|
return {
|
|
5067
5982
|
valid: false,
|
|
5068
5983
|
manifestPresent: false,
|
|
@@ -5073,7 +5988,7 @@ function validateSnapshot(configInput) {
|
|
|
5073
5988
|
}
|
|
5074
5989
|
let manifest;
|
|
5075
5990
|
try {
|
|
5076
|
-
manifest = JSON.parse(
|
|
5991
|
+
manifest = JSON.parse(fs14.readFileSync(manifestPath, "utf8"));
|
|
5077
5992
|
} catch (err) {
|
|
5078
5993
|
return {
|
|
5079
5994
|
valid: false,
|
|
@@ -5095,7 +6010,7 @@ function validateSnapshot(configInput) {
|
|
|
5095
6010
|
}
|
|
5096
6011
|
const drift = emptyDrift();
|
|
5097
6012
|
const warnings = [];
|
|
5098
|
-
const snapshotDir =
|
|
6013
|
+
const snapshotDir = path16.resolve(config.cwd, config.outDir);
|
|
5099
6014
|
const manifestFileMap = /* @__PURE__ */ new Map();
|
|
5100
6015
|
for (const file of manifest.files) {
|
|
5101
6016
|
if (file && typeof file.relativePath === "string") {
|
|
@@ -5113,23 +6028,72 @@ function validateSnapshot(configInput) {
|
|
|
5113
6028
|
if (direct !== void 0) return direct;
|
|
5114
6029
|
return skippedReasons.get(maskedPath(diskFile, config.secretRules)) ?? null;
|
|
5115
6030
|
};
|
|
6031
|
+
let unchangedScope = null;
|
|
6032
|
+
const recordedChangedSince = manifest.config?.changedSince;
|
|
6033
|
+
const recordedStaged = manifest.config?.staged === true;
|
|
6034
|
+
const recordedWithDependents = manifest.config?.withDependents === true;
|
|
6035
|
+
if (recordedChangedSince !== void 0 || recordedStaged) {
|
|
6036
|
+
try {
|
|
6037
|
+
unchangedScope = new Set(
|
|
6038
|
+
resolveChangedScope(config.cwd, {
|
|
6039
|
+
...recordedChangedSince === void 0 ? {} : { changedSince: recordedChangedSince },
|
|
6040
|
+
...recordedStaged ? { staged: true } : {}
|
|
6041
|
+
}).paths
|
|
6042
|
+
);
|
|
6043
|
+
} catch {
|
|
6044
|
+
warnings.push(
|
|
6045
|
+
"The snapshot was generated with a changed-files scope, but that scope could not be re-resolved; files it recorded as `unchanged` were trusted rather than re-checked."
|
|
6046
|
+
);
|
|
6047
|
+
}
|
|
6048
|
+
}
|
|
6049
|
+
let dependentResolver = null;
|
|
6050
|
+
function becameDependent(diskFile) {
|
|
6051
|
+
if (!recordedWithDependents || unchangedScope === null) return false;
|
|
6052
|
+
dependentResolver ??= buildImportResolver(
|
|
6053
|
+
collection.files.map((file) => ({ relativePath: file }))
|
|
6054
|
+
);
|
|
6055
|
+
return importsChangedFile(
|
|
6056
|
+
diskFile,
|
|
6057
|
+
importsOfFile(diskFile, path16.join(config.cwd, diskFile)),
|
|
6058
|
+
unchangedScope,
|
|
6059
|
+
dependentResolver
|
|
6060
|
+
);
|
|
6061
|
+
}
|
|
5116
6062
|
const collection = collectFiles(config.cwd, {
|
|
5117
6063
|
includePatterns: config.include,
|
|
5118
6064
|
excludePatterns: [...config.excludePatterns, ...config.presetExclude],
|
|
5119
6065
|
includeSensitiveFiles: config.includeSensitiveFiles,
|
|
5120
|
-
secretRules: config.secretRules
|
|
6066
|
+
secretRules: config.secretRules,
|
|
6067
|
+
// Passed explicitly, like `generate` does. Relying on a process-wide "current
|
|
6068
|
+
// discovery mode" meant `validate` could enumerate files one way while the snapshot
|
|
6069
|
+
// had been generated another way, and then blame the difference on drift.
|
|
6070
|
+
discovery: config.discovery,
|
|
6071
|
+
// The same exclusion `generate` applies: without it, `validate` recounted the tool's
|
|
6072
|
+
// own output directory as a set of files missing from the snapshot, so a correct
|
|
6073
|
+
// snapshot was reported as drifting.
|
|
6074
|
+
toolOwnedPaths: [config.outDir, config.tmpDir].map(
|
|
6075
|
+
(directory) => path16.relative(config.cwd, path16.resolve(config.cwd, directory)).replace(/\\/g, "/")
|
|
6076
|
+
).filter((relative8) => relative8.length > 0 && !relative8.startsWith(".."))
|
|
5121
6077
|
});
|
|
5122
6078
|
for (const diskFile of collection.files) {
|
|
5123
6079
|
const snapshotHash = manifestFileMap.get(diskFile);
|
|
5124
6080
|
if (!snapshotHash) {
|
|
5125
6081
|
const skipReason = skippedReasonOnDisk(diskFile);
|
|
5126
|
-
if (skipReason
|
|
6082
|
+
if (skipReason === "unchanged") {
|
|
6083
|
+
if (unchangedScope !== null) {
|
|
6084
|
+
if (unchangedScope.has(diskFile) || becameDependent(diskFile)) {
|
|
6085
|
+
drift.missingInSnapshot.push(diskFile);
|
|
6086
|
+
}
|
|
6087
|
+
}
|
|
6088
|
+
continue;
|
|
6089
|
+
}
|
|
6090
|
+
if (skipReason !== null && !wouldNowBeIncluded(config.cwd, diskFile)) continue;
|
|
5127
6091
|
drift.missingInSnapshot.push(diskFile);
|
|
5128
6092
|
continue;
|
|
5129
6093
|
}
|
|
5130
6094
|
let diskHash;
|
|
5131
6095
|
try {
|
|
5132
|
-
diskHash = sha256Hex(
|
|
6096
|
+
diskHash = sha256Hex(fs14.readFileSync(path16.join(config.cwd, diskFile)));
|
|
5133
6097
|
} catch (err) {
|
|
5134
6098
|
if (err.code === "ENOENT") {
|
|
5135
6099
|
drift.missingOnDisk.push(diskFile);
|
|
@@ -5153,7 +6117,7 @@ function validateSnapshot(configInput) {
|
|
|
5153
6117
|
}
|
|
5154
6118
|
let statFailure = null;
|
|
5155
6119
|
try {
|
|
5156
|
-
|
|
6120
|
+
fs14.statSync(confined);
|
|
5157
6121
|
} catch (err) {
|
|
5158
6122
|
statFailure = err;
|
|
5159
6123
|
}
|
|
@@ -5173,7 +6137,7 @@ function validateSnapshot(configInput) {
|
|
|
5173
6137
|
...Object.values(config.budgets.byLayer)
|
|
5174
6138
|
);
|
|
5175
6139
|
const partNames = /* @__PURE__ */ new Set();
|
|
5176
|
-
const partsDir =
|
|
6140
|
+
const partsDir = path16.join(snapshotDir, "parts");
|
|
5177
6141
|
for (const part of manifest.parts) {
|
|
5178
6142
|
if (!part || typeof part.filename !== "string") continue;
|
|
5179
6143
|
partNames.add(part.filename);
|
|
@@ -5188,7 +6152,7 @@ function validateSnapshot(configInput) {
|
|
|
5188
6152
|
}
|
|
5189
6153
|
let buffer;
|
|
5190
6154
|
try {
|
|
5191
|
-
buffer =
|
|
6155
|
+
buffer = fs14.readFileSync(partPath);
|
|
5192
6156
|
} catch {
|
|
5193
6157
|
drift.partIssues.push({ part: part.filename, issue: "missing from snapshot/parts" });
|
|
5194
6158
|
continue;
|
|
@@ -5202,10 +6166,11 @@ function validateSnapshot(configInput) {
|
|
|
5202
6166
|
if (sha256Hex(buffer) !== part.sha256) {
|
|
5203
6167
|
drift.partIssues.push({ part: part.filename, issue: "sha256 does not match the manifest" });
|
|
5204
6168
|
}
|
|
5205
|
-
|
|
6169
|
+
const partBudget = typeof part.budgetBytes === "number" ? part.budgetBytes : maxEffectiveBudget;
|
|
6170
|
+
if (buffer.length > partBudget) {
|
|
5206
6171
|
drift.partIssues.push({
|
|
5207
6172
|
part: part.filename,
|
|
5208
|
-
issue: `${buffer.length} B exceeds the effective part budget ${
|
|
6173
|
+
issue: typeof part.budgetBytes === "number" ? `${buffer.length} B exceeds its part budget ${partBudget} B` : `${buffer.length} B exceeds the effective part budget ${partBudget} B`
|
|
5209
6174
|
});
|
|
5210
6175
|
}
|
|
5211
6176
|
}
|
|
@@ -5402,9 +6367,9 @@ function validateSnapshot(configInput) {
|
|
|
5402
6367
|
["full-codebase.md", manifest.artifacts?.fullMarkdown]
|
|
5403
6368
|
]) {
|
|
5404
6369
|
if (!artifact) continue;
|
|
5405
|
-
const artifactPath =
|
|
6370
|
+
const artifactPath = path16.join(snapshotDir, key);
|
|
5406
6371
|
try {
|
|
5407
|
-
const buffer =
|
|
6372
|
+
const buffer = fs14.readFileSync(artifactPath);
|
|
5408
6373
|
if (sha256Hex(buffer) !== artifact.sha256 || buffer.length !== artifact.byteSize) {
|
|
5409
6374
|
drift.partIssues.push({ part: key, issue: "artifact does not match its recorded digest" });
|
|
5410
6375
|
}
|
|
@@ -5412,6 +6377,18 @@ function validateSnapshot(configInput) {
|
|
|
5412
6377
|
drift.partIssues.push({ part: key, issue: "declared artifact is missing" });
|
|
5413
6378
|
}
|
|
5414
6379
|
}
|
|
6380
|
+
const sarifArtifact = manifest.artifacts?.sarif;
|
|
6381
|
+
if (sarifArtifact) {
|
|
6382
|
+
const sarifKey = "reports/audit.sarif";
|
|
6383
|
+
try {
|
|
6384
|
+
const buffer = fs14.readFileSync(path16.join(snapshotDir, "reports", "audit.sarif"));
|
|
6385
|
+
if (sha256Hex(buffer) !== sarifArtifact.sha256 || buffer.length !== sarifArtifact.byteSize) {
|
|
6386
|
+
drift.partIssues.push({ part: sarifKey, issue: "artifact does not match its recorded digest" });
|
|
6387
|
+
}
|
|
6388
|
+
} catch {
|
|
6389
|
+
drift.partIssues.push({ part: sarifKey, issue: "declared artifact is missing" });
|
|
6390
|
+
}
|
|
6391
|
+
}
|
|
5415
6392
|
if (manifest.git && manifest.git.branch && manifest.git.branch !== config.branch) {
|
|
5416
6393
|
warnings.push(
|
|
5417
6394
|
`Snapshot was generated on branch "${manifest.git.branch}" but the configured branch is "${config.branch}".`
|
|
@@ -5474,7 +6451,7 @@ function validateSnapshot(configInput) {
|
|
|
5474
6451
|
|
|
5475
6452
|
// src/version.ts
|
|
5476
6453
|
var GENERATOR_NAME = "codebase-snapshot";
|
|
5477
|
-
var GENERATOR_VERSION = "0.9.
|
|
6454
|
+
var GENERATOR_VERSION = "0.9.12";
|
|
5478
6455
|
var LEGACY_GENERATOR_NAMES = ["@ankurhalder/codebase-snapshot"];
|
|
5479
6456
|
function isOurGeneratorName(name) {
|
|
5480
6457
|
if (typeof name !== "string") return false;
|
|
@@ -5521,17 +6498,17 @@ function isNewer(candidate, current) {
|
|
|
5521
6498
|
}
|
|
5522
6499
|
function cacheDirectory(env = process.env, platform = process.platform) {
|
|
5523
6500
|
if (platform === "win32") {
|
|
5524
|
-
const base2 = env.LOCALAPPDATA ??
|
|
5525
|
-
return
|
|
6501
|
+
const base2 = env.LOCALAPPDATA ?? path16.join(os.homedir(), "AppData", "Local");
|
|
6502
|
+
return path16.join(base2, "codebase-snapshot");
|
|
5526
6503
|
}
|
|
5527
6504
|
if (platform === "darwin") {
|
|
5528
|
-
return
|
|
6505
|
+
return path16.join(os.homedir(), "Library", "Caches", "codebase-snapshot");
|
|
5529
6506
|
}
|
|
5530
|
-
const base = env.XDG_CACHE_HOME ??
|
|
5531
|
-
return
|
|
6507
|
+
const base = env.XDG_CACHE_HOME ?? path16.join(os.homedir(), ".cache");
|
|
6508
|
+
return path16.join(base, "codebase-snapshot");
|
|
5532
6509
|
}
|
|
5533
6510
|
function cacheFilePath(env = process.env) {
|
|
5534
|
-
return
|
|
6511
|
+
return path16.join(cacheDirectory(env), "update-check.json");
|
|
5535
6512
|
}
|
|
5536
6513
|
function shouldCheck(argv, env) {
|
|
5537
6514
|
const disabled = ["1", "true", "yes"].includes(
|
|
@@ -5544,13 +6521,23 @@ function shouldCheck(argv, env) {
|
|
|
5544
6521
|
for (const skip of ["mcp", "describe", "skill", "--version", "-v", "--help", "-h"]) {
|
|
5545
6522
|
if (args.includes(skip)) return false;
|
|
5546
6523
|
}
|
|
5547
|
-
|
|
6524
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
6525
|
+
const argument = args[index];
|
|
6526
|
+
if (argument === "--format") {
|
|
6527
|
+
if (args[index + 1] === "json") return false;
|
|
6528
|
+
break;
|
|
6529
|
+
}
|
|
6530
|
+
if (argument.startsWith("--format=")) {
|
|
6531
|
+
if (argument.slice("--format=".length) === "json") return false;
|
|
6532
|
+
break;
|
|
6533
|
+
}
|
|
6534
|
+
}
|
|
5548
6535
|
return true;
|
|
5549
6536
|
}
|
|
5550
6537
|
function readCache(file) {
|
|
5551
6538
|
let raw;
|
|
5552
6539
|
try {
|
|
5553
|
-
raw =
|
|
6540
|
+
raw = fs14.readFileSync(file, "utf8");
|
|
5554
6541
|
} catch {
|
|
5555
6542
|
return null;
|
|
5556
6543
|
}
|
|
@@ -5567,8 +6554,8 @@ function readCache(file) {
|
|
|
5567
6554
|
}
|
|
5568
6555
|
function writeCache(file, entry) {
|
|
5569
6556
|
try {
|
|
5570
|
-
|
|
5571
|
-
|
|
6557
|
+
fs14.mkdirSync(path16.dirname(file), { recursive: true });
|
|
6558
|
+
fs14.writeFileSync(file, `${JSON.stringify(entry)}
|
|
5572
6559
|
`, "utf8");
|
|
5573
6560
|
} catch {
|
|
5574
6561
|
}
|
|
@@ -5662,42 +6649,42 @@ function describeMatcher(matcher) {
|
|
|
5662
6649
|
return String(matcher);
|
|
5663
6650
|
}
|
|
5664
6651
|
function normalizeTarget(cwd, target) {
|
|
5665
|
-
const absolute =
|
|
5666
|
-
const
|
|
5667
|
-
if (
|
|
5668
|
-
return
|
|
6652
|
+
const absolute = path16.resolve(cwd, target);
|
|
6653
|
+
const relative8 = path16.relative(cwd, absolute);
|
|
6654
|
+
if (relative8.startsWith("..") || path16.isAbsolute(relative8)) return target;
|
|
6655
|
+
return relative8.split(path16.sep).join("/");
|
|
5669
6656
|
}
|
|
5670
6657
|
function findRuleMatch(rules, relativePath) {
|
|
5671
6658
|
return rules.find(
|
|
5672
6659
|
(rule) => matchRule(relativePath, rule.match)
|
|
5673
6660
|
);
|
|
5674
6661
|
}
|
|
5675
|
-
function findRecordedAncestor(skipped,
|
|
6662
|
+
function findRecordedAncestor(skipped, relative8, maskedPath2) {
|
|
5676
6663
|
let nearest;
|
|
5677
6664
|
for (const record of skipped) {
|
|
5678
6665
|
const ancestorPath = record.path;
|
|
5679
6666
|
if (ancestorPath.length === 0) continue;
|
|
5680
|
-
const isAncestor =
|
|
6667
|
+
const isAncestor = relative8.startsWith(`${ancestorPath}/`) || maskedPath2.startsWith(`${ancestorPath}/`);
|
|
5681
6668
|
if (!isAncestor) continue;
|
|
5682
6669
|
if (!nearest || ancestorPath.length > nearest.path.length) nearest = record;
|
|
5683
6670
|
}
|
|
5684
6671
|
return nearest;
|
|
5685
6672
|
}
|
|
5686
6673
|
function readManifest2(config) {
|
|
5687
|
-
const manifestPath =
|
|
6674
|
+
const manifestPath = path16.resolve(config.cwd, config.outDir, "manifest.json");
|
|
5688
6675
|
try {
|
|
5689
|
-
return JSON.parse(
|
|
6676
|
+
return JSON.parse(fs14.readFileSync(manifestPath, "utf8"));
|
|
5690
6677
|
} catch {
|
|
5691
6678
|
return null;
|
|
5692
6679
|
}
|
|
5693
6680
|
}
|
|
5694
6681
|
function explainPath(config, target) {
|
|
5695
|
-
const
|
|
5696
|
-
const absolute =
|
|
5697
|
-
const maskedPath2 = findSecretInPath(
|
|
6682
|
+
const relative8 = normalizeTarget(config.cwd, target);
|
|
6683
|
+
const absolute = path16.join(config.cwd, relative8);
|
|
6684
|
+
const maskedPath2 = findSecretInPath(relative8, config.secretRules)?.masked ?? relative8;
|
|
5698
6685
|
const result = {
|
|
5699
6686
|
path: maskedPath2,
|
|
5700
|
-
exists:
|
|
6687
|
+
exists: fs14.existsSync(absolute),
|
|
5701
6688
|
included: false
|
|
5702
6689
|
};
|
|
5703
6690
|
const collection = collectFiles(config.cwd, {
|
|
@@ -5705,15 +6692,18 @@ function explainPath(config, target) {
|
|
|
5705
6692
|
excludePatterns: [...config.excludePatterns, ...config.presetExclude],
|
|
5706
6693
|
includeSensitiveFiles: config.includeSensitiveFiles,
|
|
5707
6694
|
secretRules: config.secretRules,
|
|
6695
|
+
// `explain` must enumerate files exactly as `generate` would, or it explains a
|
|
6696
|
+
// decision the generator never made.
|
|
6697
|
+
discovery: config.discovery,
|
|
5708
6698
|
// Same exclusion `generate` applies, so `explain` never says "not-discovered" about a file
|
|
5709
6699
|
// that is only missing because the walker dropped the tool's own output directory.
|
|
5710
6700
|
toolOwnedPaths: [config.outDir, config.tmpDir].map(
|
|
5711
|
-
(directory) =>
|
|
5712
|
-
).filter((
|
|
6701
|
+
(directory) => path16.relative(config.cwd, path16.resolve(config.cwd, directory)).replace(/\\/g, "/")
|
|
6702
|
+
).filter((relative9) => relative9.length > 0 && !relative9.startsWith(".."))
|
|
5713
6703
|
});
|
|
5714
|
-
if (!collection.files.includes(
|
|
6704
|
+
if (!collection.files.includes(relative8)) {
|
|
5715
6705
|
const record = collection.skipped.find(
|
|
5716
|
-
(entry2) => entry2.path ===
|
|
6706
|
+
(entry2) => entry2.path === relative8 || entry2.path === maskedPath2
|
|
5717
6707
|
);
|
|
5718
6708
|
if (record) {
|
|
5719
6709
|
result.reason = record.reason;
|
|
@@ -5722,7 +6712,7 @@ function explainPath(config, target) {
|
|
|
5722
6712
|
result.reason = "missing";
|
|
5723
6713
|
result.detail = "no such file or directory, relative to the configured cwd";
|
|
5724
6714
|
} else {
|
|
5725
|
-
const ancestor = findRecordedAncestor(collection.skipped,
|
|
6715
|
+
const ancestor = findRecordedAncestor(collection.skipped, relative8, maskedPath2);
|
|
5726
6716
|
if (ancestor) {
|
|
5727
6717
|
result.reason = ancestor.reason;
|
|
5728
6718
|
result.detail = ancestor.detail ? `${ancestor.detail} \u2014 inherited from the skipped directory \`${ancestor.path}\`` : `inside the skipped directory \`${ancestor.path}\``;
|
|
@@ -5734,21 +6724,58 @@ function explainPath(config, target) {
|
|
|
5734
6724
|
return result;
|
|
5735
6725
|
}
|
|
5736
6726
|
result.included = true;
|
|
5737
|
-
if (
|
|
6727
|
+
if (config.changedSince !== void 0 || config.staged === true) {
|
|
6728
|
+
let inScope;
|
|
6729
|
+
let scopeDetail = "";
|
|
6730
|
+
try {
|
|
6731
|
+
const scope = resolveChangedScope(config.cwd, {
|
|
6732
|
+
...config.changedSince === void 0 ? {} : { changedSince: config.changedSince },
|
|
6733
|
+
...config.staged === true ? { staged: true } : {}
|
|
6734
|
+
});
|
|
6735
|
+
inScope = scope.paths.has(relative8);
|
|
6736
|
+
if (!inScope && config.withDependents === true) {
|
|
6737
|
+
inScope = importsChangedFile(
|
|
6738
|
+
relative8,
|
|
6739
|
+
importsOfFile(relative8, absolute),
|
|
6740
|
+
scope.paths,
|
|
6741
|
+
buildImportResolver(collection.files.map((file) => ({ relativePath: file })))
|
|
6742
|
+
);
|
|
6743
|
+
}
|
|
6744
|
+
scopeDetail = scope.detail;
|
|
6745
|
+
} catch (err) {
|
|
6746
|
+
result.included = false;
|
|
6747
|
+
result.reason = "unknown";
|
|
6748
|
+
result.detail = `the changed-files scope could not be resolved: ${err.message}`;
|
|
6749
|
+
return result;
|
|
6750
|
+
}
|
|
6751
|
+
if (!inScope) {
|
|
6752
|
+
result.included = false;
|
|
6753
|
+
result.reason = "unchanged";
|
|
6754
|
+
result.detail = `not ${scopeDetail}; the snapshot was generated with a changed-files scope, which keeps only the files that moved (plus dependents, with --with-dependents)`;
|
|
6755
|
+
return result;
|
|
6756
|
+
}
|
|
6757
|
+
}
|
|
6758
|
+
let sizeOnDisk = null;
|
|
6759
|
+
try {
|
|
6760
|
+
sizeOnDisk = fs14.statSync(absolute).size;
|
|
6761
|
+
} catch {
|
|
6762
|
+
sizeOnDisk = null;
|
|
6763
|
+
}
|
|
6764
|
+
if (sizeOnDisk !== null && sizeOnDisk > BINARY_SCAN_BYTES && isBinaryFile(absolute)) {
|
|
5738
6765
|
result.included = false;
|
|
5739
6766
|
result.reason = "binary";
|
|
5740
6767
|
result.detail = "binary content; generate skips it and records `binary` in manifest.skipped[]";
|
|
5741
6768
|
return result;
|
|
5742
6769
|
}
|
|
5743
|
-
result.layer = classifyLayer(
|
|
5744
|
-
result.kind = classifyKind(
|
|
5745
|
-
result.language = detectLanguage(
|
|
5746
|
-
const layerRule = findRuleMatch(config.layers,
|
|
6770
|
+
result.layer = classifyLayer(relative8, config.layers);
|
|
6771
|
+
result.kind = classifyKind(relative8, config.kinds);
|
|
6772
|
+
result.language = detectLanguage(relative8);
|
|
6773
|
+
const layerRule = findRuleMatch(config.layers, relative8);
|
|
5747
6774
|
if (layerRule) result.layerMatch = describeMatcher(layerRule.match);
|
|
5748
|
-
const kindRule = findRuleMatch(config.kinds,
|
|
6775
|
+
const kindRule = findRuleMatch(config.kinds, relative8);
|
|
5749
6776
|
if (kindRule) result.kindMatch = describeMatcher(kindRule.match);
|
|
5750
|
-
if (
|
|
5751
|
-
const buffer =
|
|
6777
|
+
if (fs14.existsSync(absolute) && fs14.statSync(absolute).isFile()) {
|
|
6778
|
+
const buffer = fs14.readFileSync(absolute);
|
|
5752
6779
|
const decoded = decodeText(buffer);
|
|
5753
6780
|
if (decoded.binary) {
|
|
5754
6781
|
result.included = false;
|
|
@@ -5760,12 +6787,12 @@ function explainPath(config, target) {
|
|
|
5760
6787
|
result.bytes = buffer.length;
|
|
5761
6788
|
result.lines = countLines(text2);
|
|
5762
6789
|
if (decoded.encoding !== "utf-8") result.encoding = decoded.encoding;
|
|
5763
|
-
const { findings } = scanAndRedactSecrets(text2, config.secretRules,
|
|
6790
|
+
const { findings } = scanAndRedactSecrets(text2, config.secretRules, relative8);
|
|
5764
6791
|
result.secretCount = findings.length;
|
|
5765
6792
|
result.secrets = findings.slice(0, 20).map((finding) => ({ rules: finding.rules, line: finding.line }));
|
|
5766
6793
|
}
|
|
5767
6794
|
const manifest = readManifest2(config);
|
|
5768
|
-
const entry = Array.isArray(manifest?.files) ? manifest.files.find((file) => file.relativePath ===
|
|
6795
|
+
const entry = Array.isArray(manifest?.files) ? manifest.files.find((file) => file.relativePath === relative8) : void 0;
|
|
5769
6796
|
result.part = entry?.part ?? null;
|
|
5770
6797
|
result.inclusionMode = entry?.inclusionMode;
|
|
5771
6798
|
return result;
|
|
@@ -5996,14 +7023,14 @@ function compareManifests(before, after) {
|
|
|
5996
7023
|
function loadManifest(target) {
|
|
5997
7024
|
let isDirectory;
|
|
5998
7025
|
try {
|
|
5999
|
-
isDirectory =
|
|
7026
|
+
isDirectory = fs14.statSync(target).isDirectory();
|
|
6000
7027
|
} catch {
|
|
6001
7028
|
throw new Error(`No snapshot found at ${target}: the path does not exist.`);
|
|
6002
7029
|
}
|
|
6003
|
-
const manifestPath = isDirectory ?
|
|
7030
|
+
const manifestPath = isDirectory ? path16.join(target, "manifest.json") : target;
|
|
6004
7031
|
let raw;
|
|
6005
7032
|
try {
|
|
6006
|
-
raw =
|
|
7033
|
+
raw = fs14.readFileSync(manifestPath, "utf8");
|
|
6007
7034
|
} catch {
|
|
6008
7035
|
throw new Error(`Could not read a manifest at ${manifestPath}.`);
|
|
6009
7036
|
}
|
|
@@ -6060,15 +7087,15 @@ function renderDiffText(diff) {
|
|
|
6060
7087
|
function appendGitignoreEntries(cwd, entries) {
|
|
6061
7088
|
const wanted = [...new Set(entries)].filter((entry) => entry.trim().length > 0);
|
|
6062
7089
|
if (wanted.length === 0) return { added: [], note: null };
|
|
6063
|
-
const ignorePath =
|
|
7090
|
+
const ignorePath = path16.join(cwd, ".gitignore");
|
|
6064
7091
|
const manual = `Add ${wanted.map((entry) => `"${entry}"`).join(" and ")} to your .gitignore yourself.`;
|
|
6065
7092
|
let existing = "";
|
|
6066
7093
|
try {
|
|
6067
|
-
if (
|
|
6068
|
-
if (
|
|
7094
|
+
if (fs14.existsSync(ignorePath)) {
|
|
7095
|
+
if (fs14.statSync(ignorePath).isDirectory()) {
|
|
6069
7096
|
return { added: [], note: `${ignorePath} is a directory, so it was not updated. ${manual}` };
|
|
6070
7097
|
}
|
|
6071
|
-
existing =
|
|
7098
|
+
existing = fs14.readFileSync(ignorePath, "utf8");
|
|
6072
7099
|
}
|
|
6073
7100
|
} catch (err) {
|
|
6074
7101
|
return {
|
|
@@ -6081,7 +7108,7 @@ function appendGitignoreEntries(cwd, entries) {
|
|
|
6081
7108
|
if (added.length === 0) return { added: [], note: null };
|
|
6082
7109
|
const prefix = existing.length === 0 || existing.endsWith("\n") ? "" : "\n";
|
|
6083
7110
|
try {
|
|
6084
|
-
|
|
7111
|
+
fs14.writeFileSync(ignorePath, `${existing}${prefix}${added.join("\n")}
|
|
6085
7112
|
`, "utf8");
|
|
6086
7113
|
} catch (err) {
|
|
6087
7114
|
return { added: [], note: `${err.message}. ${manual}` };
|
|
@@ -6089,7 +7116,7 @@ function appendGitignoreEntries(cwd, entries) {
|
|
|
6089
7116
|
return { added, note: null };
|
|
6090
7117
|
}
|
|
6091
7118
|
function gitignorePath(cwd, filePath) {
|
|
6092
|
-
return
|
|
7119
|
+
return path16.relative(cwd, filePath).split(path16.sep).join("/");
|
|
6093
7120
|
}
|
|
6094
7121
|
|
|
6095
7122
|
// src/wizard/config-template.ts
|
|
@@ -6097,7 +7124,12 @@ var DEFAULT_ANSWERS = {
|
|
|
6097
7124
|
preset: "generic",
|
|
6098
7125
|
branch: "main",
|
|
6099
7126
|
maxPartBytes: "300k",
|
|
7127
|
+
maxPartTokens: null,
|
|
6100
7128
|
largeFileBytes: "200k",
|
|
7129
|
+
maxMonolithBytes: null,
|
|
7130
|
+
discovery: "auto",
|
|
7131
|
+
skeleton: false,
|
|
7132
|
+
sarif: false,
|
|
6101
7133
|
include: [],
|
|
6102
7134
|
excludePatterns: [],
|
|
6103
7135
|
artifacts: {
|
|
@@ -6150,6 +7182,9 @@ function renderConfig(answers) {
|
|
|
6150
7182
|
// \u2500\u2500 Size \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
6151
7183
|
// Hard ceiling per part. "300k", 307200 and "1.5mb" are all accepted.
|
|
6152
7184
|
maxPartBytes: ${size(answers.maxPartBytes)},
|
|
7185
|
+
// The same ceiling expressed in tokens; it narrows maxPartBytes rather than
|
|
7186
|
+
// replacing it. null = not set. Minimum 1024.
|
|
7187
|
+
maxPartTokens: ${answers.maxPartTokens === null ? "null" : String(answers.maxPartTokens)},
|
|
6153
7188
|
// Files above this size get a head/tail preview instead of the full body.
|
|
6154
7189
|
largeFileBytes: ${size(answers.largeFileBytes)},
|
|
6155
7190
|
previewHeadLines: 80,
|
|
@@ -6160,6 +7195,22 @@ function renderConfig(answers) {
|
|
|
6160
7195
|
byLayer: {},
|
|
6161
7196
|
},
|
|
6162
7197
|
|
|
7198
|
+
// \u2500\u2500 Scope and compression \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
7199
|
+
// Replace function and method bodies with \`{ /* ... */ }\` in TypeScript files.
|
|
7200
|
+
// Signatures and comments stay, so the structure of the file survives.
|
|
7201
|
+
skeleton: ${answers.skeleton},
|
|
7202
|
+
// Also write reports/audit.sarif (SARIF 2.1.0) for a CI security gate. The
|
|
7203
|
+
// matched value is never included \u2014 only the path, the line and the rule.
|
|
7204
|
+
sarif: ${answers.sarif},
|
|
7205
|
+
// Snapshot only the files that differ from a git ref (for example "main"),
|
|
7206
|
+
// plus untracked ones. null and false both mean "snapshot everything".
|
|
7207
|
+
changedSince: null,
|
|
7208
|
+
// Snapshot only what is staged in the index: the pre-commit counterpart.
|
|
7209
|
+
staged: false,
|
|
7210
|
+
// With changedSince or staged, also include the files that import a changed
|
|
7211
|
+
// one, one hop out. Meaningless without a scope.
|
|
7212
|
+
withDependents: false,
|
|
7213
|
+
|
|
6163
7214
|
// \u2500\u2500 What goes in \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
6164
7215
|
// Allow-list. Empty means "everything that is not ignored".
|
|
6165
7216
|
// Example: ["src/**", "packages/*/src/**"]
|
|
@@ -6169,7 +7220,7 @@ function renderConfig(answers) {
|
|
|
6169
7220
|
// How candidate files are found: "auto" lists them through git when git can
|
|
6170
7221
|
// and walks the tree otherwise; "git" and "walk" force one path, and "walk" is
|
|
6171
7222
|
// the escape hatch when .git exists but git cannot list it.
|
|
6172
|
-
discovery:
|
|
7223
|
+
discovery: ${JSON.stringify(answers.discovery)},
|
|
6173
7224
|
|
|
6174
7225
|
// \u2500\u2500 Classification \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
6175
7226
|
// "merge" = the preset's rules plus yours (yours win). "replace" = yours only.
|
|
@@ -6200,7 +7251,7 @@ function renderConfig(answers) {
|
|
|
6200
7251
|
monolith: ${artifacts.monolith}, // full-codebase.json + .md (roughly doubles output)
|
|
6201
7252
|
},
|
|
6202
7253
|
// Skip the monolith above this size. null = no limit.
|
|
6203
|
-
maxMonolithBytes: null,
|
|
7254
|
+
maxMonolithBytes: ${answers.maxMonolithBytes === null ? "null" : size(answers.maxMonolithBytes)},
|
|
6204
7255
|
// Top-level switch for the monolith, equivalent to artifacts.monolith.
|
|
6205
7256
|
// When both are present the artifact entry wins; this one exists so the
|
|
6206
7257
|
// generated file lists every setting the tool accepts.
|
|
@@ -6577,10 +7628,10 @@ var CONFIG_CANDIDATES = [
|
|
|
6577
7628
|
];
|
|
6578
7629
|
function chooseConfigFileName(cwd) {
|
|
6579
7630
|
if (!supportsTypeScriptImports()) return "snapshot.config.mjs";
|
|
6580
|
-
const pkgPath =
|
|
6581
|
-
if (!
|
|
7631
|
+
const pkgPath = path16.join(cwd, "package.json");
|
|
7632
|
+
if (!fs14.existsSync(pkgPath)) return "snapshot.config.mjs";
|
|
6582
7633
|
try {
|
|
6583
|
-
const parsed = JSON.parse(
|
|
7634
|
+
const parsed = JSON.parse(fs14.readFileSync(pkgPath, "utf8"));
|
|
6584
7635
|
return parsed.type === "module" ? "snapshot.config.ts" : "snapshot.config.mjs";
|
|
6585
7636
|
} catch {
|
|
6586
7637
|
return "snapshot.config.mjs";
|
|
@@ -6594,7 +7645,7 @@ var SIZE_CHOICES = [
|
|
|
6594
7645
|
];
|
|
6595
7646
|
function packageJsonHasConfig(cwd) {
|
|
6596
7647
|
try {
|
|
6597
|
-
const parsed = JSON.parse(
|
|
7648
|
+
const parsed = JSON.parse(fs14.readFileSync(path16.join(cwd, "package.json"), "utf8"));
|
|
6598
7649
|
return typeof parsed.snapshot === "object" && parsed.snapshot !== null && !Array.isArray(parsed.snapshot);
|
|
6599
7650
|
} catch {
|
|
6600
7651
|
return false;
|
|
@@ -6611,11 +7662,12 @@ function artifactAnswers(options) {
|
|
|
6611
7662
|
const keep = new Set(options.onlyArtifacts);
|
|
6612
7663
|
for (const name of ARTIFACT_NAMES) artifacts[name] = keep.has(name);
|
|
6613
7664
|
}
|
|
7665
|
+
if (options.includeMonolith === true) artifacts.monolith = true;
|
|
6614
7666
|
return artifacts;
|
|
6615
7667
|
}
|
|
6616
7668
|
function topLevelDirs(cwd) {
|
|
6617
7669
|
try {
|
|
6618
|
-
return
|
|
7670
|
+
return fs14.readdirSync(cwd, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules").map((entry) => entry.name).sort();
|
|
6619
7671
|
} catch {
|
|
6620
7672
|
return [];
|
|
6621
7673
|
}
|
|
@@ -6628,7 +7680,7 @@ function describeJsonValue(value) {
|
|
|
6628
7680
|
}
|
|
6629
7681
|
function writeFileOrExplain(target, content) {
|
|
6630
7682
|
try {
|
|
6631
|
-
|
|
7683
|
+
fs14.writeFileSync(target, content, "utf8");
|
|
6632
7684
|
} catch (err) {
|
|
6633
7685
|
const code = err.code;
|
|
6634
7686
|
const reason = code === "EACCES" || code === "EPERM" || code === "EROFS" ? `permission denied (${code})` : code === "EISDIR" ? "it is a directory" : `${err.message}`;
|
|
@@ -6639,7 +7691,7 @@ function canWriteModuleConfig(name, cwd) {
|
|
|
6639
7691
|
if (name.endsWith(".mjs") || name.endsWith(".ts")) return true;
|
|
6640
7692
|
if (!name.endsWith(".js")) return false;
|
|
6641
7693
|
try {
|
|
6642
|
-
const parsed = JSON.parse(
|
|
7694
|
+
const parsed = JSON.parse(fs14.readFileSync(path16.join(cwd, "package.json"), "utf8"));
|
|
6643
7695
|
return parsed.type === "module";
|
|
6644
7696
|
} catch {
|
|
6645
7697
|
return false;
|
|
@@ -6674,19 +7726,19 @@ function serializePackageJson(raw, value) {
|
|
|
6674
7726
|
return `${text2}${raw.endsWith("\n") ? crlf ? "\r\n" : "\n" : ""}`;
|
|
6675
7727
|
}
|
|
6676
7728
|
function updatePackageJson(cwd, notes) {
|
|
6677
|
-
const packagePath =
|
|
7729
|
+
const packagePath = path16.join(cwd, "package.json");
|
|
6678
7730
|
const unchanged = {
|
|
6679
7731
|
scripts: [],
|
|
6680
7732
|
dependency: { name: GENERATOR_NAME, range: null, added: false }
|
|
6681
7733
|
};
|
|
6682
|
-
if (!
|
|
7734
|
+
if (!fs14.existsSync(packagePath)) {
|
|
6683
7735
|
notes.push("No package.json found, so npm scripts and the devDependency were not added.");
|
|
6684
7736
|
return unchanged;
|
|
6685
7737
|
}
|
|
6686
7738
|
let raw;
|
|
6687
7739
|
let parsed;
|
|
6688
7740
|
try {
|
|
6689
|
-
raw =
|
|
7741
|
+
raw = fs14.readFileSync(packagePath, "utf8");
|
|
6690
7742
|
parsed = JSON.parse(raw);
|
|
6691
7743
|
} catch {
|
|
6692
7744
|
notes.push("Could not parse package.json; npm scripts and the devDependency were not added.");
|
|
@@ -6711,17 +7763,26 @@ function updatePackageJson(cwd, notes) {
|
|
|
6711
7763
|
scriptsWereBroken = true;
|
|
6712
7764
|
}
|
|
6713
7765
|
const added = [];
|
|
7766
|
+
const isSelfRepository = parsed.name === GENERATOR_NAME;
|
|
7767
|
+
const scriptPrefix = isSelfRepository ? "node ./dist/bin.js" : `npx ${GENERATOR_NAME}`;
|
|
6714
7768
|
if (!scripts.snapshot) {
|
|
6715
|
-
scripts.snapshot =
|
|
7769
|
+
scripts.snapshot = `${scriptPrefix} generate`;
|
|
6716
7770
|
added.push("snapshot");
|
|
6717
7771
|
}
|
|
6718
7772
|
if (!scripts["snapshot:validate"]) {
|
|
6719
|
-
scripts["snapshot:validate"] =
|
|
7773
|
+
scripts["snapshot:validate"] = `${scriptPrefix} validate`;
|
|
6720
7774
|
added.push("snapshot:validate");
|
|
6721
7775
|
}
|
|
6722
7776
|
const declared = declaredRange(parsed);
|
|
6723
7777
|
let dependency;
|
|
6724
|
-
if (
|
|
7778
|
+
if (isSelfRepository) {
|
|
7779
|
+
if (declared !== null) {
|
|
7780
|
+
notes.push(
|
|
7781
|
+
`package.json declares ${GENERATOR_NAME} as a dependency of itself. It was left as it is, but a package cannot depend on itself: remove the entry and run the local build instead.`
|
|
7782
|
+
);
|
|
7783
|
+
}
|
|
7784
|
+
dependency = { name: GENERATOR_NAME, range: declared, added: false };
|
|
7785
|
+
} else if (declared !== null) {
|
|
6725
7786
|
dependency = { name: GENERATOR_NAME, range: declared, added: false };
|
|
6726
7787
|
} else {
|
|
6727
7788
|
const range = `^${GENERATOR_VERSION}`;
|
|
@@ -6771,7 +7832,15 @@ async function runInit(options) {
|
|
|
6771
7832
|
// `validate` warn about a mismatch the user never caused.
|
|
6772
7833
|
branch: getGitInfo(options.cwd).branch,
|
|
6773
7834
|
maxPartBytes: options.maxPartBytes ?? detected.recommendedBudget ?? DEFAULT_ANSWERS.maxPartBytes,
|
|
6774
|
-
|
|
7835
|
+
// Every setting the caller named is written down, not just forwarded. A config that omits
|
|
7836
|
+
// what the run used is drift the next `validate` reports, and `setup` is the one command
|
|
7837
|
+
// that is supposed to leave a project in a consistent state.
|
|
7838
|
+
maxPartTokens: options.maxPartTokens ?? DEFAULT_ANSWERS.maxPartTokens,
|
|
7839
|
+
largeFileBytes: options.largeFileBytes ?? DEFAULT_ANSWERS.largeFileBytes,
|
|
7840
|
+
maxMonolithBytes: options.maxMonolithBytes ?? DEFAULT_ANSWERS.maxMonolithBytes,
|
|
7841
|
+
discovery: options.discovery ?? DEFAULT_ANSWERS.discovery,
|
|
7842
|
+
skeleton: options.skeleton ?? DEFAULT_ANSWERS.skeleton,
|
|
7843
|
+
sarif: options.sarif ?? DEFAULT_ANSWERS.sarif,
|
|
6775
7844
|
include: options.include ?? [],
|
|
6776
7845
|
excludePatterns: options.exclude ?? [],
|
|
6777
7846
|
artifacts: artifactAnswers(options),
|
|
@@ -6978,7 +8047,7 @@ async function runInit(options) {
|
|
|
6978
8047
|
const configText = renderConfig(answers);
|
|
6979
8048
|
const preferredName = chooseConfigFileName(options.cwd);
|
|
6980
8049
|
const presentConfigs = CONFIG_CANDIDATES.filter(
|
|
6981
|
-
(name) =>
|
|
8050
|
+
(name) => fs14.existsSync(path16.join(options.cwd, name))
|
|
6982
8051
|
);
|
|
6983
8052
|
const present = presentConfigs[0] ?? null;
|
|
6984
8053
|
if (presentConfigs.length > 1) {
|
|
@@ -6987,7 +8056,7 @@ async function runInit(options) {
|
|
|
6987
8056
|
);
|
|
6988
8057
|
}
|
|
6989
8058
|
const configName = present !== null && options.force ? present : preferredName;
|
|
6990
|
-
const configPath =
|
|
8059
|
+
const configPath = path16.join(options.cwd, configName);
|
|
6991
8060
|
const reused = present !== null && options.force !== true && options.reuseConfig === true;
|
|
6992
8061
|
if (reused) notes.push(`Kept the existing ${present} instead of writing a new one.`);
|
|
6993
8062
|
if (options.dryRun) {
|
|
@@ -7002,7 +8071,8 @@ async function runInit(options) {
|
|
|
7002
8071
|
scripts: [],
|
|
7003
8072
|
gitignoreEntries: [],
|
|
7004
8073
|
dependency: { name: GENERATOR_NAME, range: `^${GENERATOR_VERSION}`, added: false },
|
|
7005
|
-
notes
|
|
8074
|
+
notes,
|
|
8075
|
+
outDir: answers.outDir
|
|
7006
8076
|
};
|
|
7007
8077
|
}
|
|
7008
8078
|
if (present !== null && present !== preferredName && !options.force && !reused) {
|
|
@@ -7020,7 +8090,7 @@ async function runInit(options) {
|
|
|
7020
8090
|
'package.json already defines a "snapshot" config. Remove that key, or re-run with --force to write a config file that takes precedence over it.'
|
|
7021
8091
|
);
|
|
7022
8092
|
}
|
|
7023
|
-
if (
|
|
8093
|
+
if (fs14.existsSync(configPath) && !options.force && !reused) {
|
|
7024
8094
|
throw new Error(`${configName} already exists. Re-run with --force to replace it.`);
|
|
7025
8095
|
}
|
|
7026
8096
|
if (!reused) writeFileOrExplain(configPath, configText);
|
|
@@ -7038,14 +8108,15 @@ async function runInit(options) {
|
|
|
7038
8108
|
scripts,
|
|
7039
8109
|
gitignoreEntries: ignore.added,
|
|
7040
8110
|
dependency,
|
|
7041
|
-
notes
|
|
8111
|
+
notes,
|
|
8112
|
+
outDir: answers.outDir
|
|
7042
8113
|
};
|
|
7043
8114
|
}
|
|
7044
8115
|
function describeTarget(cwd) {
|
|
7045
|
-
const
|
|
7046
|
-
if (
|
|
7047
|
-
if (
|
|
7048
|
-
return
|
|
8116
|
+
const relative8 = path16.relative(process.cwd(), cwd);
|
|
8117
|
+
if (relative8 === "") return "this directory";
|
|
8118
|
+
if (relative8.startsWith("..") || path16.isAbsolute(relative8)) return cwd;
|
|
8119
|
+
return relative8;
|
|
7049
8120
|
}
|
|
7050
8121
|
function firstRunMessage(reason, cwd) {
|
|
7051
8122
|
const where = describeTarget(cwd);
|
|
@@ -7068,7 +8139,7 @@ function planFirstRun(facts) {
|
|
|
7068
8139
|
var NOT_CONTENT = /* @__PURE__ */ new Set([".git", ".hg", ".svn", ".bzr", ".DS_Store", "Thumbs.db"]);
|
|
7069
8140
|
function isEffectivelyEmpty(cwd) {
|
|
7070
8141
|
try {
|
|
7071
|
-
return
|
|
8142
|
+
return fs14.readdirSync(cwd).every((entry) => NOT_CONTENT.has(entry));
|
|
7072
8143
|
} catch {
|
|
7073
8144
|
return false;
|
|
7074
8145
|
}
|
|
@@ -7076,19 +8147,203 @@ function isEffectivelyEmpty(cwd) {
|
|
|
7076
8147
|
function hasPriorSnapshot(cwd, outDir) {
|
|
7077
8148
|
let parsed;
|
|
7078
8149
|
try {
|
|
7079
|
-
parsed = JSON.parse(
|
|
8150
|
+
parsed = JSON.parse(fs14.readFileSync(path16.join(cwd, outDir, ".codebase-snapshot"), "utf8"));
|
|
7080
8151
|
} catch {
|
|
7081
8152
|
return false;
|
|
7082
8153
|
}
|
|
7083
8154
|
if (parsed === null || typeof parsed !== "object") return false;
|
|
7084
8155
|
return isOurGeneratorName(parsed.generator);
|
|
7085
8156
|
}
|
|
8157
|
+
var HOOK_MARKER = `# ${GENERATOR_NAME}: pre-commit snapshot check`;
|
|
8158
|
+
function shellQuote(value) {
|
|
8159
|
+
return value.replace(/[\\"`$]/g, (character) => `\\${character}`);
|
|
8160
|
+
}
|
|
8161
|
+
function hookScript(outDir) {
|
|
8162
|
+
const dir = shellQuote(outDir.replace(/\\/g, "/"));
|
|
8163
|
+
return `#!/bin/sh
|
|
8164
|
+
${HOOK_MARKER}
|
|
8165
|
+
#
|
|
8166
|
+
# Refuse the commit when the snapshot is out of date with the tree.
|
|
8167
|
+
#
|
|
8168
|
+
# Remove this file to stop the check, or edit it \u2014 a hook this tool did not write is never
|
|
8169
|
+
# overwritten, and neither is one it did: run \`${GENERATOR_NAME} init --install-hook\` again
|
|
8170
|
+
# to restore the body below.
|
|
8171
|
+
set -e
|
|
8172
|
+
|
|
8173
|
+
SNAPSHOT_DIR="${dir}"
|
|
8174
|
+
|
|
8175
|
+
if [ ! -f "$SNAPSHOT_DIR/manifest.json" ]; then
|
|
8176
|
+
echo "${GENERATOR_NAME}: no snapshot in $SNAPSHOT_DIR, skipping the staleness check." >&2
|
|
8177
|
+
echo " Run \\"npx ${GENERATOR_NAME} generate\\" to create one." >&2
|
|
8178
|
+
exit 0
|
|
8179
|
+
fi
|
|
8180
|
+
|
|
8181
|
+
if ! npx --no-install ${GENERATOR_NAME} validate >/dev/null 2>&1; then
|
|
8182
|
+
echo "${GENERATOR_NAME}: the snapshot is out of date." >&2
|
|
8183
|
+
echo " Run \\"npx ${GENERATOR_NAME} generate\\", stage the change, and commit again." >&2
|
|
8184
|
+
echo " (npx --no-install is used on purpose: this hook must not install anything)" >&2
|
|
8185
|
+
exit 1
|
|
8186
|
+
fi
|
|
8187
|
+
`;
|
|
8188
|
+
}
|
|
8189
|
+
function gitDirectory(cwd) {
|
|
8190
|
+
const dotGit = path16.join(cwd, ".git");
|
|
8191
|
+
let stats;
|
|
8192
|
+
try {
|
|
8193
|
+
stats = fs14.statSync(dotGit);
|
|
8194
|
+
} catch {
|
|
8195
|
+
return null;
|
|
8196
|
+
}
|
|
8197
|
+
if (stats.isDirectory()) return dotGit;
|
|
8198
|
+
if (!stats.isFile()) return null;
|
|
8199
|
+
try {
|
|
8200
|
+
const contents = fs14.readFileSync(dotGit, "utf8");
|
|
8201
|
+
const match = /^gitdir:\s*(.+)$/m.exec(contents);
|
|
8202
|
+
if (!match) return null;
|
|
8203
|
+
const target = match[1];
|
|
8204
|
+
const resolved = path16.isAbsolute(target.trim()) ? target.trim() : path16.join(cwd, target.trim());
|
|
8205
|
+
return fs14.statSync(resolved).isDirectory() ? resolved : null;
|
|
8206
|
+
} catch {
|
|
8207
|
+
return null;
|
|
8208
|
+
}
|
|
8209
|
+
}
|
|
8210
|
+
function installPreCommitHook(cwd, outDir = "snapshot") {
|
|
8211
|
+
const gitDir = gitDirectory(cwd);
|
|
8212
|
+
if (gitDir === null) {
|
|
8213
|
+
return {
|
|
8214
|
+
path: null,
|
|
8215
|
+
written: false,
|
|
8216
|
+
notes: [
|
|
8217
|
+
"Not a git working tree (no .git directory or gitdir link), so no pre-commit hook was installed."
|
|
8218
|
+
]
|
|
8219
|
+
};
|
|
8220
|
+
}
|
|
8221
|
+
const hooksDir = path16.join(gitDir, "hooks");
|
|
8222
|
+
const hookPath = path16.join(hooksDir, "pre-commit");
|
|
8223
|
+
try {
|
|
8224
|
+
fs14.mkdirSync(hooksDir, { recursive: true });
|
|
8225
|
+
} catch (err) {
|
|
8226
|
+
return {
|
|
8227
|
+
path: hookPath,
|
|
8228
|
+
written: false,
|
|
8229
|
+
notes: [`Could not create ${hooksDir}: ${err.message}`]
|
|
8230
|
+
};
|
|
8231
|
+
}
|
|
8232
|
+
let existing = null;
|
|
8233
|
+
try {
|
|
8234
|
+
existing = fs14.readFileSync(hookPath, "utf8");
|
|
8235
|
+
} catch {
|
|
8236
|
+
existing = null;
|
|
8237
|
+
}
|
|
8238
|
+
if (existing !== null && !existing.includes(HOOK_MARKER)) {
|
|
8239
|
+
return {
|
|
8240
|
+
path: hookPath,
|
|
8241
|
+
written: false,
|
|
8242
|
+
notes: [
|
|
8243
|
+
`${hookPath} already exists and was not written by ${GENERATOR_NAME}; it was left untouched.`,
|
|
8244
|
+
`Add \`npx ${GENERATOR_NAME} validate\` to it to get the same check.`
|
|
8245
|
+
]
|
|
8246
|
+
};
|
|
8247
|
+
}
|
|
8248
|
+
try {
|
|
8249
|
+
fs14.writeFileSync(hookPath, hookScript(outDir), "utf8");
|
|
8250
|
+
fs14.chmodSync(hookPath, 493);
|
|
8251
|
+
} catch (err) {
|
|
8252
|
+
return {
|
|
8253
|
+
path: hookPath,
|
|
8254
|
+
written: false,
|
|
8255
|
+
notes: [`Could not write ${hookPath}: ${err.message}`]
|
|
8256
|
+
};
|
|
8257
|
+
}
|
|
8258
|
+
return {
|
|
8259
|
+
path: hookPath,
|
|
8260
|
+
written: true,
|
|
8261
|
+
notes: [
|
|
8262
|
+
existing === null ? `Installed ${hookPath}: commits are refused while the snapshot is out of date.` : `Updated ${hookPath} to the current ${GENERATOR_NAME} check.`
|
|
8263
|
+
]
|
|
8264
|
+
};
|
|
8265
|
+
}
|
|
8266
|
+
function workerEntry(directory, isCjs) {
|
|
8267
|
+
const entry = path16.join(directory, isCjs ? "index.cjs" : "index.js");
|
|
8268
|
+
return { path: entry, url: pathToFileURL(entry).href };
|
|
8269
|
+
}
|
|
8270
|
+
var WORKER_SOURCE = `
|
|
8271
|
+
const { parentPort, workerData } = require("node:worker_threads");
|
|
8272
|
+
|
|
8273
|
+
(async () => {
|
|
8274
|
+
try {
|
|
8275
|
+
const mod = await import(workerData.entryUrl);
|
|
8276
|
+
const loaded = await mod.loadConfigFileDetailed(workerData.cwd);
|
|
8277
|
+
if (loaded.errors.length > 0) {
|
|
8278
|
+
parentPort.postMessage({ ok: false, message: loaded.errors.join("; ") });
|
|
8279
|
+
return;
|
|
8280
|
+
}
|
|
8281
|
+
const config = mod.resolveConfig(loaded.config, workerData.cliOptions, workerData.cwd);
|
|
8282
|
+
const manifest = mod.generateSnapshot(config);
|
|
8283
|
+
parentPort.postMessage({
|
|
8284
|
+
ok: true,
|
|
8285
|
+
summary: {
|
|
8286
|
+
fingerprint: manifest.fingerprint,
|
|
8287
|
+
totalFiles: manifest.counts.totalFiles,
|
|
8288
|
+
skippedCount: manifest.counts.skippedCount,
|
|
8289
|
+
partsCount: manifest.counts.partsCount,
|
|
8290
|
+
parts: manifest.parts.map((part) => ({ filename: part.filename, byteSize: part.byteSize })),
|
|
8291
|
+
totalContentBytes: manifest.totals.totalContentBytes,
|
|
8292
|
+
warnings: manifest.warnings,
|
|
8293
|
+
},
|
|
8294
|
+
});
|
|
8295
|
+
} catch (error) {
|
|
8296
|
+
parentPort.postMessage({
|
|
8297
|
+
ok: false,
|
|
8298
|
+
message: error && error.message ? error.message : String(error),
|
|
8299
|
+
});
|
|
8300
|
+
}
|
|
8301
|
+
})();
|
|
8302
|
+
`;
|
|
8303
|
+
function generateInWorker(cwd, cliOptions) {
|
|
8304
|
+
return new Promise((resolve5) => {
|
|
8305
|
+
let settled = false;
|
|
8306
|
+
const finish = (result) => {
|
|
8307
|
+
if (settled) return;
|
|
8308
|
+
settled = true;
|
|
8309
|
+
resolve5(result);
|
|
8310
|
+
};
|
|
8311
|
+
let worker;
|
|
8312
|
+
const isCjs = typeof __dirname !== "undefined";
|
|
8313
|
+
const directory = isCjs ? __dirname : path16.dirname(fileURLToPath(import.meta.url));
|
|
8314
|
+
try {
|
|
8315
|
+
worker = new Worker(WORKER_SOURCE, {
|
|
8316
|
+
eval: true,
|
|
8317
|
+
workerData: {
|
|
8318
|
+
entryUrl: workerEntry(directory, isCjs).url,
|
|
8319
|
+
cwd,
|
|
8320
|
+
cliOptions
|
|
8321
|
+
}
|
|
8322
|
+
});
|
|
8323
|
+
} catch (error) {
|
|
8324
|
+
finish({
|
|
8325
|
+
ok: false,
|
|
8326
|
+
message: `Could not start the generate worker: ${error instanceof Error ? error.message : String(error)}`
|
|
8327
|
+
});
|
|
8328
|
+
return;
|
|
8329
|
+
}
|
|
8330
|
+
worker.on("message", (message) => finish(message));
|
|
8331
|
+
worker.on("error", (error) => {
|
|
8332
|
+
finish({ ok: false, message: `The generate worker failed: ${error.message}` });
|
|
8333
|
+
});
|
|
8334
|
+
worker.on("exit", (code) => {
|
|
8335
|
+
if (!settled) {
|
|
8336
|
+
finish({ ok: false, message: `The generate worker exited (code ${code}) without a result.` });
|
|
8337
|
+
}
|
|
8338
|
+
});
|
|
8339
|
+
});
|
|
8340
|
+
}
|
|
7086
8341
|
|
|
7087
8342
|
// src/index.ts
|
|
7088
8343
|
var SCHEMA_VERSION = "3.0.0";
|
|
7089
8344
|
var SUMMARY_MAX_SOURCE_BYTES = 2 * 1024 * 1024;
|
|
7090
8345
|
function sha256Hex2(input) {
|
|
7091
|
-
return
|
|
8346
|
+
return crypto2.createHash("sha256").update(input).digest("hex");
|
|
7092
8347
|
}
|
|
7093
8348
|
var TS_EXTENSIONS = [
|
|
7094
8349
|
".ts",
|
|
@@ -7127,71 +8382,15 @@ function hasStructuralSummary(relativePath) {
|
|
|
7127
8382
|
function bump(counts, key) {
|
|
7128
8383
|
counts[key] = (counts[key] ?? 0) + 1;
|
|
7129
8384
|
}
|
|
7130
|
-
|
|
7131
|
-
|
|
7132
|
-
|
|
7133
|
-
|
|
7134
|
-
|
|
7135
|
-
|
|
7136
|
-
".js",
|
|
7137
|
-
".jsx",
|
|
7138
|
-
".mjs",
|
|
7139
|
-
".cjs",
|
|
7140
|
-
"/index.ts",
|
|
7141
|
-
"/index.tsx",
|
|
7142
|
-
"/index.js",
|
|
7143
|
-
"/index.mjs"
|
|
7144
|
-
];
|
|
7145
|
-
var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
7146
|
-
".ts",
|
|
7147
|
-
".tsx",
|
|
7148
|
-
".mts",
|
|
7149
|
-
".cts",
|
|
7150
|
-
".js",
|
|
7151
|
-
".jsx",
|
|
7152
|
-
".mjs",
|
|
7153
|
-
".cjs"
|
|
7154
|
-
]);
|
|
7155
|
-
function buildImportResolver(files) {
|
|
7156
|
-
const byPath = /* @__PURE__ */ new Map();
|
|
7157
|
-
const byPathFolded = /* @__PURE__ */ new Map();
|
|
7158
|
-
const byStem = /* @__PURE__ */ new Map();
|
|
7159
|
-
const byStemFolded = /* @__PURE__ */ new Map();
|
|
7160
|
-
const remember = (map, key, value) => {
|
|
7161
|
-
if (!map.has(key)) map.set(key, value);
|
|
7162
|
-
};
|
|
7163
|
-
for (const file of files) {
|
|
7164
|
-
const relativePath = file.relativePath;
|
|
7165
|
-
remember(byPath, relativePath, relativePath);
|
|
7166
|
-
remember(byPathFolded, relativePath.toLowerCase(), relativePath);
|
|
7167
|
-
const extension = path13.posix.extname(relativePath);
|
|
7168
|
-
if (!SOURCE_EXTENSIONS.has(extension)) continue;
|
|
7169
|
-
const stem = relativePath.slice(0, -extension.length);
|
|
7170
|
-
remember(byStem, stem, relativePath);
|
|
7171
|
-
remember(byStemFolded, stem.toLowerCase(), relativePath);
|
|
7172
|
-
if (path13.posix.basename(stem) === "index") {
|
|
7173
|
-
const directory = path13.posix.dirname(stem);
|
|
7174
|
-
remember(byStem, directory, relativePath);
|
|
7175
|
-
remember(byStemFolded, directory.toLowerCase(), relativePath);
|
|
8385
|
+
function retain(items, keep) {
|
|
8386
|
+
let write = 0;
|
|
8387
|
+
for (const item of items) {
|
|
8388
|
+
if (keep(item)) {
|
|
8389
|
+
items[write] = item;
|
|
8390
|
+
write += 1;
|
|
7176
8391
|
}
|
|
7177
8392
|
}
|
|
7178
|
-
|
|
7179
|
-
}
|
|
7180
|
-
function resolveSpecifier(fromFile, specifier, resolver) {
|
|
7181
|
-
if (!RELATIVE_SPECIFIER.test(specifier)) return null;
|
|
7182
|
-
const joined = path13.posix.normalize(
|
|
7183
|
-
path13.posix.join(path13.posix.dirname(fromFile), specifier)
|
|
7184
|
-
);
|
|
7185
|
-
const exact = (key) => resolver.byPath.get(key) ?? resolver.byPathFolded.get(key.toLowerCase()) ?? null;
|
|
7186
|
-
const direct = exact(joined);
|
|
7187
|
-
if (direct) return direct;
|
|
7188
|
-
for (const suffix of IMPORT_CANDIDATE_SUFFIXES) {
|
|
7189
|
-
const hit = exact(`${joined}${suffix}`);
|
|
7190
|
-
if (hit) return hit;
|
|
7191
|
-
}
|
|
7192
|
-
const stem = joined.replace(/\.(?:js|mjs|cjs|jsx)$/, "");
|
|
7193
|
-
const byStem = (key) => resolver.byStem.get(key) ?? resolver.byStemFolded.get(key.toLowerCase()) ?? null;
|
|
7194
|
-
return byStem(joined) ?? byStem(stem);
|
|
8393
|
+
items.length = write;
|
|
7195
8394
|
}
|
|
7196
8395
|
function buildIndexes(files) {
|
|
7197
8396
|
const resolver = buildImportResolver(files);
|
|
@@ -7243,7 +8442,7 @@ function buildIndexes(files) {
|
|
|
7243
8442
|
}))
|
|
7244
8443
|
};
|
|
7245
8444
|
}
|
|
7246
|
-
function verifyInMemory(files, parts
|
|
8445
|
+
function verifyInMemory(files, parts) {
|
|
7247
8446
|
const errors = [];
|
|
7248
8447
|
const partNames = new Set(parts.map((part) => part.filename));
|
|
7249
8448
|
for (const file of files) {
|
|
@@ -7259,8 +8458,11 @@ function verifyInMemory(files, parts, maxPartBytes) {
|
|
|
7259
8458
|
if (actualBytes !== part.metadata.byteSize) {
|
|
7260
8459
|
errors.push(`${part.filename} byteSize ${part.metadata.byteSize} != actual ${actualBytes}.`);
|
|
7261
8460
|
}
|
|
7262
|
-
|
|
7263
|
-
|
|
8461
|
+
const partBudget = part.metadata.budgetBytes;
|
|
8462
|
+
if (actualBytes > partBudget) {
|
|
8463
|
+
errors.push(
|
|
8464
|
+
`${part.filename} (${actualBytes} B) exceeds its part budget of ${partBudget} B.`
|
|
8465
|
+
);
|
|
7264
8466
|
}
|
|
7265
8467
|
if (part.metadata.includedFiles.length !== part.metadata.fileCount) {
|
|
7266
8468
|
errors.push(`${part.filename} fileCount does not match its included files.`);
|
|
@@ -7273,11 +8475,11 @@ function verifyInMemory(files, parts, maxPartBytes) {
|
|
|
7273
8475
|
return errors;
|
|
7274
8476
|
}
|
|
7275
8477
|
function resolveSafeOutputDirs(config) {
|
|
7276
|
-
const cwd =
|
|
7277
|
-
const outDir =
|
|
7278
|
-
const tmpDir =
|
|
7279
|
-
const root =
|
|
7280
|
-
const contains = (parent, child) => child === parent || child.startsWith(`${parent}${
|
|
8478
|
+
const cwd = path16.resolve(config.cwd);
|
|
8479
|
+
const outDir = path16.resolve(cwd, config.outDir);
|
|
8480
|
+
const tmpDir = path16.resolve(cwd, config.tmpDir);
|
|
8481
|
+
const root = path16.parse(cwd).root;
|
|
8482
|
+
const contains = (parent, child) => child === parent || child.startsWith(`${parent}${path16.sep}`);
|
|
7281
8483
|
const reject = (reason) => {
|
|
7282
8484
|
throw new Error(
|
|
7283
8485
|
`Refusing to generate: ${reason}. outDir=${outDir} tmpDir=${tmpDir} cwd=${cwd}`
|
|
@@ -7333,14 +8535,14 @@ function listEntries(dir) {
|
|
|
7333
8535
|
const walk = (absolute, prefix) => {
|
|
7334
8536
|
let children;
|
|
7335
8537
|
try {
|
|
7336
|
-
children =
|
|
8538
|
+
children = fs14.readdirSync(absolute, { withFileTypes: true });
|
|
7337
8539
|
} catch {
|
|
7338
8540
|
return;
|
|
7339
8541
|
}
|
|
7340
8542
|
for (const child of children) {
|
|
7341
|
-
const
|
|
7342
|
-
entries.push(
|
|
7343
|
-
if (child.isDirectory()) walk(
|
|
8543
|
+
const relative8 = prefix === "" ? child.name : `${prefix}/${child.name}`;
|
|
8544
|
+
entries.push(relative8);
|
|
8545
|
+
if (child.isDirectory()) walk(path16.join(absolute, child.name), relative8);
|
|
7344
8546
|
}
|
|
7345
8547
|
};
|
|
7346
8548
|
walk(dir, "");
|
|
@@ -7357,28 +8559,33 @@ function markerPayload(dir, state, id, createdAt) {
|
|
|
7357
8559
|
pid: process.pid,
|
|
7358
8560
|
entries: listEntries(dir)
|
|
7359
8561
|
};
|
|
7360
|
-
const manifestPath =
|
|
7361
|
-
if (
|
|
7362
|
-
marker2.manifestSha256 = sha256Hex2(
|
|
8562
|
+
const manifestPath = path16.join(dir, "manifest.json");
|
|
8563
|
+
if (fs14.existsSync(manifestPath)) {
|
|
8564
|
+
marker2.manifestSha256 = sha256Hex2(fs14.readFileSync(manifestPath));
|
|
7363
8565
|
}
|
|
7364
8566
|
return marker2;
|
|
7365
8567
|
}
|
|
7366
8568
|
function writeOwnershipMarker(dir, marker2) {
|
|
7367
|
-
|
|
7368
|
-
|
|
8569
|
+
fs14.writeFileSync(
|
|
8570
|
+
path16.join(dir, OWNERSHIP_MARKER),
|
|
7369
8571
|
`${JSON.stringify(marker2, null, 2)}
|
|
7370
8572
|
`,
|
|
7371
8573
|
"utf8"
|
|
7372
8574
|
);
|
|
7373
8575
|
}
|
|
8576
|
+
function markerBelongsTo(dir, id) {
|
|
8577
|
+
const evidence = readOwnershipMarker(dir);
|
|
8578
|
+
if (evidence.marker === null) return true;
|
|
8579
|
+
return evidence.marker.id === id;
|
|
8580
|
+
}
|
|
7374
8581
|
function readOwnershipMarker(dir) {
|
|
7375
|
-
const markerPath =
|
|
7376
|
-
if (!
|
|
8582
|
+
const markerPath = path16.join(dir, OWNERSHIP_MARKER);
|
|
8583
|
+
if (!fs14.existsSync(markerPath)) {
|
|
7377
8584
|
return { marker: null, problem: "it holds no .codebase-snapshot marker" };
|
|
7378
8585
|
}
|
|
7379
8586
|
let raw;
|
|
7380
8587
|
try {
|
|
7381
|
-
raw =
|
|
8588
|
+
raw = fs14.readFileSync(markerPath, "utf8");
|
|
7382
8589
|
} catch {
|
|
7383
8590
|
return { marker: null, problem: "its .codebase-snapshot marker could not be read" };
|
|
7384
8591
|
}
|
|
@@ -7446,11 +8653,11 @@ function assessSnapshotDirectory(dir) {
|
|
|
7446
8653
|
marker: marker2
|
|
7447
8654
|
};
|
|
7448
8655
|
}
|
|
7449
|
-
const manifestPath =
|
|
7450
|
-
if (
|
|
8656
|
+
const manifestPath = path16.join(dir, "manifest.json");
|
|
8657
|
+
if (fs14.existsSync(manifestPath)) {
|
|
7451
8658
|
let raw;
|
|
7452
8659
|
try {
|
|
7453
|
-
raw =
|
|
8660
|
+
raw = fs14.readFileSync(manifestPath, "utf8");
|
|
7454
8661
|
} catch {
|
|
7455
8662
|
return {
|
|
7456
8663
|
owned: false,
|
|
@@ -7532,13 +8739,13 @@ function containsNoFiles(dir) {
|
|
|
7532
8739
|
const current = pending.pop();
|
|
7533
8740
|
let entries;
|
|
7534
8741
|
try {
|
|
7535
|
-
entries =
|
|
8742
|
+
entries = fs14.readdirSync(current, { withFileTypes: true });
|
|
7536
8743
|
} catch {
|
|
7537
8744
|
return false;
|
|
7538
8745
|
}
|
|
7539
8746
|
for (const entry of entries) {
|
|
7540
8747
|
if (entry.isDirectory()) {
|
|
7541
|
-
pending.push(
|
|
8748
|
+
pending.push(path16.join(current, entry.name));
|
|
7542
8749
|
} else if (!IGNORABLE_ENTRIES.has(entry.name)) {
|
|
7543
8750
|
return false;
|
|
7544
8751
|
}
|
|
@@ -7547,13 +8754,13 @@ function containsNoFiles(dir) {
|
|
|
7547
8754
|
return true;
|
|
7548
8755
|
}
|
|
7549
8756
|
function assertReplaceable(target, label, secretRules) {
|
|
7550
|
-
if (!
|
|
7551
|
-
if (!
|
|
8757
|
+
if (!fs14.existsSync(target)) return;
|
|
8758
|
+
if (!fs14.statSync(target).isDirectory()) {
|
|
7552
8759
|
throw new Error(
|
|
7553
8760
|
`Refusing to write into ${label} ${target}: it exists but is not a directory.`
|
|
7554
8761
|
);
|
|
7555
8762
|
}
|
|
7556
|
-
const entries =
|
|
8763
|
+
const entries = fs14.readdirSync(target);
|
|
7557
8764
|
if (entries.length === 0) return;
|
|
7558
8765
|
if (entries.every((name) => IGNORABLE_ENTRIES.has(name))) return;
|
|
7559
8766
|
if (containsNoFiles(target)) return;
|
|
@@ -7573,18 +8780,18 @@ function assertReplaceable(target, label, secretRules) {
|
|
|
7573
8780
|
);
|
|
7574
8781
|
}
|
|
7575
8782
|
function assertStagingRootUsable(tmpDir, secretRules) {
|
|
7576
|
-
if (!
|
|
7577
|
-
if (!
|
|
8783
|
+
if (!fs14.existsSync(tmpDir)) return;
|
|
8784
|
+
if (!fs14.statSync(tmpDir).isDirectory()) {
|
|
7578
8785
|
throw new Error(`Refusing to write into tmpDir ${tmpDir}: it exists but is not a directory.`);
|
|
7579
8786
|
}
|
|
7580
|
-
const entries =
|
|
8787
|
+
const entries = fs14.readdirSync(tmpDir);
|
|
7581
8788
|
if (entries.length === 0) return;
|
|
7582
8789
|
const foreign = entries.filter((name) => {
|
|
7583
|
-
const full =
|
|
8790
|
+
const full = path16.join(tmpDir, name);
|
|
7584
8791
|
if (!name.startsWith(RUN_DIR_PREFIX)) return true;
|
|
7585
8792
|
let stats;
|
|
7586
8793
|
try {
|
|
7587
|
-
stats =
|
|
8794
|
+
stats = fs14.statSync(full);
|
|
7588
8795
|
} catch {
|
|
7589
8796
|
return true;
|
|
7590
8797
|
}
|
|
@@ -7592,7 +8799,7 @@ function assertStagingRootUsable(tmpDir, secretRules) {
|
|
|
7592
8799
|
const { marker: marker2 } = readOwnershipMarker(full);
|
|
7593
8800
|
if (marker2) return false;
|
|
7594
8801
|
try {
|
|
7595
|
-
return
|
|
8802
|
+
return fs14.readdirSync(full).length > 0;
|
|
7596
8803
|
} catch {
|
|
7597
8804
|
return true;
|
|
7598
8805
|
}
|
|
@@ -7607,13 +8814,13 @@ function assertStagingRootUsable(tmpDir, secretRules) {
|
|
|
7607
8814
|
function cleanStaleRunDirs(tmpDir) {
|
|
7608
8815
|
let entries;
|
|
7609
8816
|
try {
|
|
7610
|
-
entries =
|
|
8817
|
+
entries = fs14.readdirSync(tmpDir);
|
|
7611
8818
|
} catch {
|
|
7612
8819
|
return;
|
|
7613
8820
|
}
|
|
7614
8821
|
for (const name of entries) {
|
|
7615
8822
|
if (!name.startsWith(RUN_DIR_PREFIX)) continue;
|
|
7616
|
-
const full =
|
|
8823
|
+
const full = path16.join(tmpDir, name);
|
|
7617
8824
|
const { marker: marker2 } = readOwnershipMarker(full);
|
|
7618
8825
|
if (marker2) {
|
|
7619
8826
|
const created = Date.parse(marker2.createdAt);
|
|
@@ -7621,33 +8828,33 @@ function cleanStaleRunDirs(tmpDir) {
|
|
|
7621
8828
|
const abandoned = typeof marker2.pid === "number" && !isProcessAlive(marker2.pid);
|
|
7622
8829
|
if (aged || abandoned) {
|
|
7623
8830
|
try {
|
|
7624
|
-
|
|
8831
|
+
fs14.rmSync(full, { recursive: true, force: true });
|
|
7625
8832
|
} catch {
|
|
7626
8833
|
}
|
|
7627
8834
|
}
|
|
7628
8835
|
continue;
|
|
7629
8836
|
}
|
|
7630
8837
|
try {
|
|
7631
|
-
if (
|
|
8838
|
+
if (fs14.readdirSync(full).length === 0) fs14.rmSync(full, { recursive: true, force: true });
|
|
7632
8839
|
} catch {
|
|
7633
8840
|
}
|
|
7634
8841
|
}
|
|
7635
8842
|
}
|
|
7636
8843
|
function removeIfEmpty(dir) {
|
|
7637
8844
|
try {
|
|
7638
|
-
if (
|
|
8845
|
+
if (fs14.readdirSync(dir).length === 0) fs14.rmSync(dir, { recursive: true, force: true });
|
|
7639
8846
|
} catch {
|
|
7640
8847
|
}
|
|
7641
8848
|
}
|
|
7642
8849
|
function ensureParentDirectory(target, label) {
|
|
7643
|
-
const parent =
|
|
7644
|
-
if (
|
|
8850
|
+
const parent = path16.dirname(target);
|
|
8851
|
+
if (fs14.existsSync(parent) && !fs14.statSync(parent).isDirectory()) {
|
|
7645
8852
|
throw new Error(
|
|
7646
8853
|
`Refusing to create ${label} ${target}: ${parent} exists but is not a directory.`
|
|
7647
8854
|
);
|
|
7648
8855
|
}
|
|
7649
8856
|
try {
|
|
7650
|
-
|
|
8857
|
+
fs14.mkdirSync(parent, { recursive: true });
|
|
7651
8858
|
} catch (err) {
|
|
7652
8859
|
throw new Error(
|
|
7653
8860
|
`Refusing to create ${label} ${target}: its parent ${parent} could not be created (${err.message}).`
|
|
@@ -7655,8 +8862,7 @@ function ensureParentDirectory(target, label) {
|
|
|
7655
8862
|
}
|
|
7656
8863
|
}
|
|
7657
8864
|
function acquireRunLock(stagingRoot) {
|
|
7658
|
-
const
|
|
7659
|
-
const lockPath = path13.join(os.tmpdir(), `codebase-snapshot-${key}.lock`);
|
|
8865
|
+
const lockPath = lockPathFor(stagingRoot);
|
|
7660
8866
|
const payload = `${JSON.stringify({
|
|
7661
8867
|
pid: process.pid,
|
|
7662
8868
|
stagingRoot,
|
|
@@ -7665,22 +8871,22 @@ function acquireRunLock(stagingRoot) {
|
|
|
7665
8871
|
`;
|
|
7666
8872
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
7667
8873
|
try {
|
|
7668
|
-
const handle =
|
|
8874
|
+
const handle = fs14.openSync(lockPath, "wx");
|
|
7669
8875
|
try {
|
|
7670
|
-
|
|
8876
|
+
fs14.writeSync(handle, payload);
|
|
7671
8877
|
} finally {
|
|
7672
|
-
|
|
8878
|
+
fs14.closeSync(handle);
|
|
7673
8879
|
}
|
|
7674
8880
|
return () => {
|
|
7675
8881
|
try {
|
|
7676
|
-
|
|
8882
|
+
fs14.rmSync(lockPath, { force: true });
|
|
7677
8883
|
} catch {
|
|
7678
8884
|
}
|
|
7679
8885
|
};
|
|
7680
8886
|
} catch (err) {
|
|
7681
8887
|
if (err.code !== "EEXIST") throw err;
|
|
7682
8888
|
if (attempt === 0 && lockOwnerIsGone(lockPath)) {
|
|
7683
|
-
|
|
8889
|
+
fs14.rmSync(lockPath, { force: true });
|
|
7684
8890
|
continue;
|
|
7685
8891
|
}
|
|
7686
8892
|
throw new Error(
|
|
@@ -7693,16 +8899,19 @@ function acquireRunLock(stagingRoot) {
|
|
|
7693
8899
|
function lockKeyFor(stagingRoot) {
|
|
7694
8900
|
let key = stagingRoot;
|
|
7695
8901
|
try {
|
|
7696
|
-
key =
|
|
8902
|
+
key = fs14.realpathSync(stagingRoot);
|
|
7697
8903
|
} catch {
|
|
7698
8904
|
}
|
|
7699
8905
|
if (process.platform === "win32" || process.platform === "darwin") key = key.toLowerCase();
|
|
7700
8906
|
return sha256Hex2(key).slice(0, 16);
|
|
7701
8907
|
}
|
|
8908
|
+
function lockPathFor(stagingRoot) {
|
|
8909
|
+
return path16.join(os.tmpdir(), `codebase-snapshot-${lockKeyFor(stagingRoot)}.lock`);
|
|
8910
|
+
}
|
|
7702
8911
|
function lockOwnerIsGone(lockPath) {
|
|
7703
8912
|
let raw;
|
|
7704
8913
|
try {
|
|
7705
|
-
raw =
|
|
8914
|
+
raw = fs14.readFileSync(lockPath, "utf8");
|
|
7706
8915
|
} catch {
|
|
7707
8916
|
return false;
|
|
7708
8917
|
}
|
|
@@ -7710,9 +8919,9 @@ function lockOwnerIsGone(lockPath) {
|
|
|
7710
8919
|
try {
|
|
7711
8920
|
pid = JSON.parse(raw).pid;
|
|
7712
8921
|
} catch {
|
|
7713
|
-
return
|
|
8922
|
+
return lockFileIsAncient(lockPath);
|
|
7714
8923
|
}
|
|
7715
|
-
if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0) return
|
|
8924
|
+
if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0) return lockFileIsAncient(lockPath);
|
|
7716
8925
|
try {
|
|
7717
8926
|
process.kill(pid, 0);
|
|
7718
8927
|
return false;
|
|
@@ -7720,14 +8929,21 @@ function lockOwnerIsGone(lockPath) {
|
|
|
7720
8929
|
return err.code === "ESRCH";
|
|
7721
8930
|
}
|
|
7722
8931
|
}
|
|
8932
|
+
function lockFileIsAncient(lockPath) {
|
|
8933
|
+
try {
|
|
8934
|
+
return Date.now() - fs14.statSync(lockPath).mtimeMs > STALE_RUN_MS;
|
|
8935
|
+
} catch {
|
|
8936
|
+
return false;
|
|
8937
|
+
}
|
|
8938
|
+
}
|
|
7723
8939
|
function writeManifestAtomically(manifestPath, manifest) {
|
|
7724
8940
|
const tempPath = `${manifestPath}.${process.pid}.tmp`;
|
|
7725
|
-
|
|
8941
|
+
fs14.writeFileSync(tempPath, JSON.stringify(manifest, null, 2), "utf8");
|
|
7726
8942
|
try {
|
|
7727
|
-
|
|
8943
|
+
fs14.renameSync(tempPath, manifestPath);
|
|
7728
8944
|
} catch (err) {
|
|
7729
8945
|
try {
|
|
7730
|
-
|
|
8946
|
+
fs14.rmSync(tempPath, { force: true });
|
|
7731
8947
|
} catch {
|
|
7732
8948
|
}
|
|
7733
8949
|
throw err;
|
|
@@ -7750,31 +8966,31 @@ function swapIntoPlace(stagingDir, outDir) {
|
|
|
7750
8966
|
}
|
|
7751
8967
|
function swapOnce(stagingDir, outDir) {
|
|
7752
8968
|
const backupDir = `${outDir}.backup-${process.pid}-${Date.now()}`;
|
|
7753
|
-
const hadPrevious =
|
|
7754
|
-
if (hadPrevious)
|
|
8969
|
+
const hadPrevious = fs14.existsSync(outDir);
|
|
8970
|
+
if (hadPrevious) fs14.renameSync(outDir, backupDir);
|
|
7755
8971
|
try {
|
|
7756
8972
|
try {
|
|
7757
|
-
|
|
8973
|
+
fs14.renameSync(stagingDir, outDir);
|
|
7758
8974
|
} catch (err) {
|
|
7759
8975
|
if (err.code !== "EXDEV") throw err;
|
|
7760
8976
|
const partialDir = `${outDir}.partial-${process.pid}-${Date.now()}`;
|
|
7761
8977
|
try {
|
|
7762
|
-
|
|
7763
|
-
|
|
7764
|
-
|
|
8978
|
+
fs14.cpSync(stagingDir, partialDir, { recursive: true });
|
|
8979
|
+
fs14.renameSync(partialDir, outDir);
|
|
8980
|
+
fs14.rmSync(stagingDir, { recursive: true, force: true });
|
|
7765
8981
|
} catch (copyErr) {
|
|
7766
8982
|
try {
|
|
7767
|
-
|
|
8983
|
+
fs14.rmSync(partialDir, { recursive: true, force: true });
|
|
7768
8984
|
} catch {
|
|
7769
8985
|
}
|
|
7770
8986
|
throw copyErr;
|
|
7771
8987
|
}
|
|
7772
8988
|
}
|
|
7773
|
-
if (hadPrevious)
|
|
8989
|
+
if (hadPrevious) fs14.rmSync(backupDir, { recursive: true, force: true });
|
|
7774
8990
|
} catch (err) {
|
|
7775
|
-
if (hadPrevious &&
|
|
7776
|
-
if (
|
|
7777
|
-
|
|
8991
|
+
if (hadPrevious && fs14.existsSync(backupDir)) {
|
|
8992
|
+
if (fs14.existsSync(outDir)) fs14.rmSync(outDir, { recursive: true, force: true });
|
|
8993
|
+
fs14.renameSync(backupDir, outDir);
|
|
7778
8994
|
}
|
|
7779
8995
|
throw err;
|
|
7780
8996
|
}
|
|
@@ -7783,7 +8999,7 @@ function sleepSync(milliseconds) {
|
|
|
7783
8999
|
const buffer = new Int32Array(new SharedArrayBuffer(4));
|
|
7784
9000
|
Atomics.wait(buffer, 0, 0, milliseconds);
|
|
7785
9001
|
}
|
|
7786
|
-
function artifactWarnings(artifacts) {
|
|
9002
|
+
function artifactWarnings(artifacts, sarif) {
|
|
7787
9003
|
const messages = [];
|
|
7788
9004
|
if (!artifacts.manifest) {
|
|
7789
9005
|
messages.push("manifest.json is disabled: `validate` and `report` cannot inspect this snapshot.");
|
|
@@ -7793,7 +9009,7 @@ function artifactWarnings(artifacts) {
|
|
|
7793
9009
|
"parts are disabled but navigation.md is enabled: its links point at part files that were not written."
|
|
7794
9010
|
);
|
|
7795
9011
|
}
|
|
7796
|
-
if (ARTIFACT_NAMES.every((name) => artifacts[name] === false)) {
|
|
9012
|
+
if (!sarif && ARTIFACT_NAMES.every((name) => artifacts[name] === false)) {
|
|
7797
9013
|
messages.push(
|
|
7798
9014
|
"every artifact is disabled: only the .codebase-snapshot ownership marker will be written."
|
|
7799
9015
|
);
|
|
@@ -7865,13 +9081,25 @@ function generateSnapshot(configInput, options) {
|
|
|
7865
9081
|
excludePatterns: [...config.excludePatterns, ...config.presetExclude],
|
|
7866
9082
|
includeSensitiveFiles: config.includeSensitiveFiles,
|
|
7867
9083
|
secretRules: config.secretRules,
|
|
9084
|
+
discovery: config.discovery,
|
|
7868
9085
|
// The output and staging directories this run is about to write, relative to the root. A
|
|
7869
9086
|
// first run cannot have marked them yet, so the name alone keeps a run from recording its own
|
|
7870
9087
|
// output — but only at these exact paths, not at every directory that shares the name.
|
|
7871
9088
|
toolOwnedPaths: [config.outDir, config.tmpDir].map(
|
|
7872
|
-
(directory) =>
|
|
7873
|
-
).filter((
|
|
9089
|
+
(directory) => path16.relative(config.cwd, path16.resolve(config.cwd, directory)).replace(/\\/g, "/")
|
|
9090
|
+
).filter((relative8) => relative8.length > 0 && !relative8.startsWith(".."))
|
|
7874
9091
|
});
|
|
9092
|
+
const changedScope = config.changedSince !== void 0 || config.staged === true ? resolveChangedScope(config.cwd, {
|
|
9093
|
+
...config.changedSince === void 0 ? {} : { changedSince: config.changedSince },
|
|
9094
|
+
...config.staged === true ? { staged: true } : {}
|
|
9095
|
+
}) : null;
|
|
9096
|
+
const changedPaths = changedScope === null ? null : new Set(changedScope.paths);
|
|
9097
|
+
const unchangedPaths = [];
|
|
9098
|
+
const candidateFiles = changedPaths !== null && config.withDependents !== true ? collection.files.filter((relativePath) => {
|
|
9099
|
+
if (changedPaths.has(relativePath)) return true;
|
|
9100
|
+
unchangedPaths.push(relativePath);
|
|
9101
|
+
return false;
|
|
9102
|
+
}) : collection.files;
|
|
7875
9103
|
const files = [];
|
|
7876
9104
|
const monolithBodies = /* @__PURE__ */ new Map();
|
|
7877
9105
|
const skipped = maskSkipRecords([...collection.skipped], config.secretRules);
|
|
@@ -7882,6 +9110,7 @@ function generateSnapshot(configInput, options) {
|
|
|
7882
9110
|
return files.some((file) => file.isPreviewTruncated && !file.isLarge);
|
|
7883
9111
|
};
|
|
7884
9112
|
const encodingNotes = [];
|
|
9113
|
+
const fileWarnings = [];
|
|
7885
9114
|
const oversizeSummaryPaths = [];
|
|
7886
9115
|
const previewSummaryPaths = [];
|
|
7887
9116
|
const layerCounts = {};
|
|
@@ -7889,13 +9118,13 @@ function generateSnapshot(configInput, options) {
|
|
|
7889
9118
|
let totalLines = 0;
|
|
7890
9119
|
let totalSizeBytes = 0;
|
|
7891
9120
|
let largeFileCount = 0;
|
|
7892
|
-
for (const relativePath of
|
|
7893
|
-
const absolutePath =
|
|
9121
|
+
for (const relativePath of candidateFiles) {
|
|
9122
|
+
const absolutePath = path16.join(config.cwd, relativePath);
|
|
7894
9123
|
let stats;
|
|
7895
9124
|
try {
|
|
7896
|
-
stats =
|
|
9125
|
+
stats = fs14.statSync(absolutePath);
|
|
7897
9126
|
if (!stats.isFile()) {
|
|
7898
|
-
const nestedRepository = stats.isDirectory() &&
|
|
9127
|
+
const nestedRepository = stats.isDirectory() && fs14.existsSync(path16.join(absolutePath, ".git"));
|
|
7899
9128
|
skipped.push(
|
|
7900
9129
|
nestedRepository ? {
|
|
7901
9130
|
path: relativePath,
|
|
@@ -7909,13 +9138,17 @@ function generateSnapshot(configInput, options) {
|
|
|
7909
9138
|
skipped.push({ path: relativePath, reason: "read-error" });
|
|
7910
9139
|
continue;
|
|
7911
9140
|
}
|
|
7912
|
-
if (
|
|
9141
|
+
if (isBinaryExtension(absolutePath)) {
|
|
9142
|
+
skipped.push({ path: relativePath, reason: "binary", detail: `Size: ${stats.size} B` });
|
|
9143
|
+
continue;
|
|
9144
|
+
}
|
|
9145
|
+
if (stats.size > BINARY_SCAN_BYTES && isBinaryFile(absolutePath)) {
|
|
7913
9146
|
skipped.push({ path: relativePath, reason: "binary", detail: `Size: ${stats.size} B` });
|
|
7914
9147
|
continue;
|
|
7915
9148
|
}
|
|
7916
9149
|
let buffer;
|
|
7917
9150
|
try {
|
|
7918
|
-
buffer =
|
|
9151
|
+
buffer = fs14.readFileSync(absolutePath);
|
|
7919
9152
|
} catch {
|
|
7920
9153
|
skipped.push({ path: relativePath, reason: "read-error" });
|
|
7921
9154
|
continue;
|
|
@@ -7970,7 +9203,16 @@ function generateSnapshot(configInput, options) {
|
|
|
7970
9203
|
let finalContent = redacted;
|
|
7971
9204
|
let isPreviewTruncated = false;
|
|
7972
9205
|
let includedLines = lineCount;
|
|
7973
|
-
|
|
9206
|
+
let inclusionMode = "full";
|
|
9207
|
+
if (config.skeleton === true && isSkeletonizable(relativePath)) {
|
|
9208
|
+
const skeleton = skeletonizeTypeScript(redacted, relativePath);
|
|
9209
|
+
if (skeleton.collapsed > 0) {
|
|
9210
|
+
finalContent = skeleton.text;
|
|
9211
|
+
inclusionMode = "signatures";
|
|
9212
|
+
includedLines = countLines(skeleton.text);
|
|
9213
|
+
}
|
|
9214
|
+
}
|
|
9215
|
+
if (isLarge && inclusionMode !== "signatures") {
|
|
7974
9216
|
const preview = truncatePreview(
|
|
7975
9217
|
redacted,
|
|
7976
9218
|
config.previewHeadLines,
|
|
@@ -7979,8 +9221,9 @@ function generateSnapshot(configInput, options) {
|
|
|
7979
9221
|
finalContent = preview.content;
|
|
7980
9222
|
isPreviewTruncated = preview.isTruncated;
|
|
7981
9223
|
includedLines = preview.includedLines;
|
|
9224
|
+
inclusionMode = isPreviewTruncated ? "preview" : "full";
|
|
7982
9225
|
}
|
|
7983
|
-
if (config.includeMonolith && isPreviewTruncated) {
|
|
9226
|
+
if (config.includeMonolith && (isPreviewTruncated || inclusionMode === "signatures")) {
|
|
7984
9227
|
monolithBodies.set(relativePath, redacted);
|
|
7985
9228
|
}
|
|
7986
9229
|
let summary;
|
|
@@ -7995,14 +9238,19 @@ function generateSnapshot(configInput, options) {
|
|
|
7995
9238
|
summary = outcome?.summary ? maskSummaryStrings(outcome.summary, config.secretRules) : void 0;
|
|
7996
9239
|
summaryQuality = outcome?.quality;
|
|
7997
9240
|
if (isSourceSummary(summary) && summary.parseErrors) {
|
|
7998
|
-
|
|
7999
|
-
|
|
8000
|
-
|
|
9241
|
+
fileWarnings.push({
|
|
9242
|
+
path: relativePath,
|
|
9243
|
+
message: `Parse errors in ${maskPathText(relativePath, config.secretRules)} (${summary.parseErrors}); its summary is incomplete.`
|
|
9244
|
+
});
|
|
8001
9245
|
}
|
|
8002
9246
|
} catch (err) {
|
|
8003
|
-
|
|
8004
|
-
|
|
8005
|
-
|
|
9247
|
+
fileWarnings.push({
|
|
9248
|
+
path: relativePath,
|
|
9249
|
+
message: `Summary skipped for ${maskPathText(relativePath, config.secretRules)}: ${err.message}`.replace(
|
|
9250
|
+
/\s+/g,
|
|
9251
|
+
" "
|
|
9252
|
+
)
|
|
9253
|
+
});
|
|
8006
9254
|
}
|
|
8007
9255
|
}
|
|
8008
9256
|
files.push({
|
|
@@ -8014,7 +9262,7 @@ function generateSnapshot(configInput, options) {
|
|
|
8014
9262
|
layer: layer2,
|
|
8015
9263
|
kind,
|
|
8016
9264
|
language,
|
|
8017
|
-
inclusionMode
|
|
9265
|
+
inclusionMode,
|
|
8018
9266
|
isLarge,
|
|
8019
9267
|
isPreviewTruncated,
|
|
8020
9268
|
originalLines: lineCount,
|
|
@@ -8026,6 +9274,58 @@ function generateSnapshot(configInput, options) {
|
|
|
8026
9274
|
content: finalContent
|
|
8027
9275
|
});
|
|
8028
9276
|
}
|
|
9277
|
+
if (changedScope !== null && changedPaths !== null && config.withDependents === true) {
|
|
9278
|
+
const keep = new Set(changedPaths);
|
|
9279
|
+
const resolver = buildImportResolver(files);
|
|
9280
|
+
for (const file of files) {
|
|
9281
|
+
if (!isSourceSummary(file.summary)) continue;
|
|
9282
|
+
if (importsChangedFile(file.relativePath, file.summary.imports, changedPaths, resolver)) {
|
|
9283
|
+
keep.add(file.relativePath);
|
|
9284
|
+
}
|
|
9285
|
+
}
|
|
9286
|
+
for (const file of files) {
|
|
9287
|
+
if (!keep.has(file.relativePath)) unchangedPaths.push(file.relativePath);
|
|
9288
|
+
}
|
|
9289
|
+
const kept = files.filter((file) => keep.has(file.relativePath));
|
|
9290
|
+
files.length = 0;
|
|
9291
|
+
files.push(...kept);
|
|
9292
|
+
totalLines = files.reduce((total, file) => total + file.lines, 0);
|
|
9293
|
+
totalSizeBytes = files.reduce((total, file) => total + file.sizeBytes, 0);
|
|
9294
|
+
largeFileCount = 0;
|
|
9295
|
+
for (const key of Object.keys(layerCounts)) delete layerCounts[key];
|
|
9296
|
+
for (const key of Object.keys(kindCounts)) delete kindCounts[key];
|
|
9297
|
+
for (const file of files) {
|
|
9298
|
+
bump(layerCounts, file.layer);
|
|
9299
|
+
bump(kindCounts, file.kind);
|
|
9300
|
+
if (file.isLarge) largeFileCount += 1;
|
|
9301
|
+
}
|
|
9302
|
+
retain(
|
|
9303
|
+
secretFindings,
|
|
9304
|
+
(finding) => finding.path === void 0 || keep.has(finding.path)
|
|
9305
|
+
);
|
|
9306
|
+
retain(encodingNotes, (note) => keep.has(note.path));
|
|
9307
|
+
retain(oversizeSummaryPaths, (path17) => keep.has(path17));
|
|
9308
|
+
retain(previewSummaryPaths, (path17) => keep.has(path17));
|
|
9309
|
+
retain(fileWarnings, (warning) => keep.has(warning.path));
|
|
9310
|
+
for (const path17 of [...monolithBodies.keys()]) {
|
|
9311
|
+
if (!keep.has(path17)) monolithBodies.delete(path17);
|
|
9312
|
+
}
|
|
9313
|
+
}
|
|
9314
|
+
if (changedScope !== null) {
|
|
9315
|
+
for (const relativePath of unchangedPaths) {
|
|
9316
|
+
skipped.push({
|
|
9317
|
+
path: relativePath,
|
|
9318
|
+
reason: "unchanged",
|
|
9319
|
+
detail: `not ${changedScope.detail}`
|
|
9320
|
+
});
|
|
9321
|
+
}
|
|
9322
|
+
}
|
|
9323
|
+
for (const warning of fileWarnings) warnings.push(warning.message);
|
|
9324
|
+
if (changedScope !== null && files.length === 0) {
|
|
9325
|
+
warnings.push(
|
|
9326
|
+
`No file is ${changedScope.detail}, so the snapshot is empty. Check the ref, or run without --changed-since to scan the whole repository.`
|
|
9327
|
+
);
|
|
9328
|
+
}
|
|
8029
9329
|
const encodingBuckets = /* @__PURE__ */ new Map();
|
|
8030
9330
|
for (const note of encodingNotes) {
|
|
8031
9331
|
const bucket = encodingBuckets.get(note.encoding) ?? { paths: [], invalidBytes: 0 };
|
|
@@ -8076,13 +9376,8 @@ function generateSnapshot(configInput, options) {
|
|
|
8076
9376
|
);
|
|
8077
9377
|
}
|
|
8078
9378
|
const parts = partitionByBudget(files, config, fingerprint);
|
|
8079
|
-
const maxEffectiveBudget = Math.max(
|
|
8080
|
-
config.maxPartBytes,
|
|
8081
|
-
config.budgets.default,
|
|
8082
|
-
...Object.values(config.budgets.byLayer)
|
|
8083
|
-
);
|
|
8084
9379
|
const truncatedPreviewCount = files.filter((file) => file.isPreviewTruncated).length;
|
|
8085
|
-
const validationErrors = verifyInMemory(files, parts
|
|
9380
|
+
const validationErrors = verifyInMemory(files, parts);
|
|
8086
9381
|
const totalContentBytes = files.reduce((total, file) => total + file.includedBytes, 0);
|
|
8087
9382
|
const maskName = (value) => maskPathText(value, config.secretRules);
|
|
8088
9383
|
const manifest = {
|
|
@@ -8102,12 +9397,26 @@ function generateSnapshot(configInput, options) {
|
|
|
8102
9397
|
config: {
|
|
8103
9398
|
digest: configDigest(config),
|
|
8104
9399
|
maxPartBytes: config.maxPartBytes,
|
|
9400
|
+
// The ceiling the user named, in their own unit. `maxPartBytes` above is what the
|
|
9401
|
+
// partitioner enforced; both are recorded so the manifest can explain the gap.
|
|
9402
|
+
...config.maxPartTokens === void 0 ? {} : { maxPartTokens: config.maxPartTokens },
|
|
8105
9403
|
largeFileBytes: config.largeFileBytes,
|
|
8106
9404
|
previewHeadLines: config.previewHeadLines,
|
|
8107
9405
|
previewTailLines: config.previewTailLines,
|
|
8108
9406
|
includeMonolith: config.includeMonolith,
|
|
9407
|
+
// A ceiling that decides whether `full-codebase.*` is written at all, so it belongs
|
|
9408
|
+
// in the record of what the run enforced. Omitting it left `--max-monolith-bytes`
|
|
9409
|
+
// invisible in the manifest that is supposed to describe the run.
|
|
9410
|
+
maxMonolithBytes: config.maxMonolithBytes ?? null,
|
|
8109
9411
|
includeSensitiveFiles: config.includeSensitiveFiles,
|
|
8110
9412
|
discovery: config.discovery ?? "auto",
|
|
9413
|
+
// A changed-files scope decides which files are in the snapshot, so the manifest that
|
|
9414
|
+
// describes the run has to name it.
|
|
9415
|
+
...config.changedSince === void 0 ? {} : { changedSince: config.changedSince },
|
|
9416
|
+
...config.staged === true ? { staged: true } : {},
|
|
9417
|
+
...config.withDependents === true ? { withDependents: true } : {},
|
|
9418
|
+
...config.skeleton === true ? { skeleton: true } : {},
|
|
9419
|
+
...config.sarif === true ? { sarif: true } : {},
|
|
8111
9420
|
artifacts: { ...config.artifacts },
|
|
8112
9421
|
include: [...config.include].map(maskName),
|
|
8113
9422
|
preset: config.preset,
|
|
@@ -8139,6 +9448,9 @@ function generateSnapshot(configInput, options) {
|
|
|
8139
9448
|
skippedFiles: skipped.length,
|
|
8140
9449
|
totalSourceBytes: totalSizeBytes,
|
|
8141
9450
|
totalContentBytes,
|
|
9451
|
+
// The parts are what a model is handed, so the estimate is over the parts rather than
|
|
9452
|
+
// over the sources: previews and the byte budget both shrink the number.
|
|
9453
|
+
estimatedTokens: parts.reduce((total, part) => total + part.metadata.estimatedTokens, 0),
|
|
8142
9454
|
partCount: parts.length,
|
|
8143
9455
|
warningCount: 0,
|
|
8144
9456
|
redactedLines: 0
|
|
@@ -8177,7 +9489,9 @@ function generateSnapshot(configInput, options) {
|
|
|
8177
9489
|
);
|
|
8178
9490
|
manifest.totals.warningCount = manifest.warnings.length;
|
|
8179
9491
|
}
|
|
8180
|
-
for (const message of artifactWarnings(config.artifacts))
|
|
9492
|
+
for (const message of artifactWarnings(config.artifacts, config.sarif === true)) {
|
|
9493
|
+
manifest.warnings.push(message);
|
|
9494
|
+
}
|
|
8181
9495
|
manifest.totals.warningCount = manifest.warnings.length;
|
|
8182
9496
|
let projectTree = null;
|
|
8183
9497
|
if (config.artifacts.projectTree) {
|
|
@@ -8190,6 +9504,13 @@ function generateSnapshot(configInput, options) {
|
|
|
8190
9504
|
manifest.totals.warningCount = manifest.warnings.length;
|
|
8191
9505
|
}
|
|
8192
9506
|
}
|
|
9507
|
+
const sarifText = config.sarif === true ? generateSarifReport(manifest, secretFindings, config.secretRules, GENERATOR_VERSION) : null;
|
|
9508
|
+
if (sarifText !== null) {
|
|
9509
|
+
manifest.artifacts.sarif = {
|
|
9510
|
+
byteSize: Buffer.byteLength(sarifText, "utf8"),
|
|
9511
|
+
sha256: sha256Hex2(sarifText)
|
|
9512
|
+
};
|
|
9513
|
+
}
|
|
8193
9514
|
let monolithJson = null;
|
|
8194
9515
|
let monolithMarkdown = null;
|
|
8195
9516
|
if (config.includeMonolith) {
|
|
@@ -8240,17 +9561,17 @@ function generateSnapshot(configInput, options) {
|
|
|
8240
9561
|
const lockKeys = Array.from(/* @__PURE__ */ new Set([tmpDir, outDir])).sort();
|
|
8241
9562
|
const releases = lockKeys.map((key) => acquireRunLock(key));
|
|
8242
9563
|
const releaseLock = () => {
|
|
8243
|
-
for (const release of releases.reverse()) release();
|
|
9564
|
+
for (const release of [...releases].reverse()) release();
|
|
8244
9565
|
};
|
|
8245
9566
|
let stagingDir = null;
|
|
8246
9567
|
try {
|
|
8247
9568
|
ensureParentDirectory(outDir, "outDir");
|
|
8248
|
-
|
|
9569
|
+
fs14.mkdirSync(tmpDir, { recursive: true });
|
|
8249
9570
|
cleanStaleRunDirs(tmpDir);
|
|
8250
|
-
const snapshotId =
|
|
9571
|
+
const snapshotId = crypto2.randomUUID();
|
|
8251
9572
|
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
8252
|
-
stagingDir =
|
|
8253
|
-
|
|
9573
|
+
stagingDir = path16.join(tmpDir, `${RUN_DIR_PREFIX}${process.pid}-${snapshotId.slice(0, 8)}`);
|
|
9574
|
+
fs14.mkdirSync(stagingDir);
|
|
8254
9575
|
writeOwnershipMarker(stagingDir, {
|
|
8255
9576
|
generator: GENERATOR_NAME,
|
|
8256
9577
|
version: GENERATOR_VERSION,
|
|
@@ -8260,41 +9581,44 @@ function generateSnapshot(configInput, options) {
|
|
|
8260
9581
|
id: snapshotId,
|
|
8261
9582
|
pid: process.pid
|
|
8262
9583
|
});
|
|
8263
|
-
const partsDir =
|
|
8264
|
-
const reportsDir =
|
|
8265
|
-
if (artifacts.parts)
|
|
8266
|
-
if (artifacts.navigation || artifacts.projectTree || artifacts.audit) {
|
|
8267
|
-
|
|
9584
|
+
const partsDir = path16.join(stagingDir, "parts");
|
|
9585
|
+
const reportsDir = path16.join(stagingDir, "reports");
|
|
9586
|
+
if (artifacts.parts) fs14.mkdirSync(partsDir, { recursive: true });
|
|
9587
|
+
if (artifacts.navigation || artifacts.projectTree || artifacts.audit || config.sarif === true) {
|
|
9588
|
+
fs14.mkdirSync(reportsDir, { recursive: true });
|
|
8268
9589
|
}
|
|
8269
9590
|
if (artifacts.parts) {
|
|
8270
9591
|
for (const part of parts) {
|
|
8271
|
-
|
|
9592
|
+
fs14.writeFileSync(path16.join(partsDir, part.filename), part.markdown, "utf8");
|
|
8272
9593
|
}
|
|
8273
9594
|
}
|
|
8274
9595
|
if (projectTree !== null) {
|
|
8275
|
-
|
|
9596
|
+
fs14.writeFileSync(path16.join(reportsDir, "project-tree.md"), projectTree, "utf8");
|
|
8276
9597
|
}
|
|
8277
9598
|
if (artifacts.navigation) {
|
|
8278
|
-
|
|
9599
|
+
fs14.writeFileSync(path16.join(reportsDir, "navigation.md"), generateNavigation(manifest), "utf8");
|
|
8279
9600
|
}
|
|
8280
9601
|
if (artifacts.audit) {
|
|
8281
|
-
|
|
8282
|
-
|
|
9602
|
+
fs14.writeFileSync(
|
|
9603
|
+
path16.join(reportsDir, "audit.md"),
|
|
8283
9604
|
generateAuditReport(manifest, secretFindings, config.secretRules),
|
|
8284
9605
|
"utf8"
|
|
8285
9606
|
);
|
|
8286
9607
|
}
|
|
9608
|
+
if (sarifText !== null) {
|
|
9609
|
+
fs14.writeFileSync(path16.join(reportsDir, "audit.sarif"), sarifText, "utf8");
|
|
9610
|
+
}
|
|
8287
9611
|
if (artifacts.readme) {
|
|
8288
|
-
|
|
9612
|
+
fs14.writeFileSync(path16.join(stagingDir, "README.md"), generateReadme(manifest, artifacts), "utf8");
|
|
8289
9613
|
}
|
|
8290
9614
|
if (artifacts.manifest) {
|
|
8291
|
-
|
|
9615
|
+
fs14.writeFileSync(path16.join(stagingDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf8");
|
|
8292
9616
|
}
|
|
8293
9617
|
if (monolithJson !== null) {
|
|
8294
|
-
|
|
9618
|
+
fs14.writeFileSync(path16.join(stagingDir, "full-codebase.json"), monolithJson, "utf8");
|
|
8295
9619
|
}
|
|
8296
9620
|
if (monolithMarkdown !== null) {
|
|
8297
|
-
|
|
9621
|
+
fs14.writeFileSync(path16.join(stagingDir, "full-codebase.md"), monolithMarkdown, "utf8");
|
|
8298
9622
|
}
|
|
8299
9623
|
writeOwnershipMarker(stagingDir, markerPayload(stagingDir, "complete", snapshotId, createdAt));
|
|
8300
9624
|
swapIntoPlace(stagingDir, outDir);
|
|
@@ -8302,31 +9626,41 @@ function generateSnapshot(configInput, options) {
|
|
|
8302
9626
|
const warningsBeforeHook = manifest.warnings.length;
|
|
8303
9627
|
const pendingHook = runAfterGenerate(config, manifest);
|
|
8304
9628
|
const shipHookState = () => {
|
|
9629
|
+
if (!markerBelongsTo(outDir, snapshotId)) return;
|
|
8305
9630
|
if (artifacts.manifest && manifest.warnings.length !== warningsBeforeHook) {
|
|
8306
|
-
writeManifestAtomically(
|
|
9631
|
+
writeManifestAtomically(path16.join(outDir, "manifest.json"), manifest);
|
|
8307
9632
|
}
|
|
8308
9633
|
writeOwnershipMarker(outDir, markerPayload(outDir, "complete", snapshotId, createdAt));
|
|
8309
9634
|
};
|
|
8310
9635
|
shipHookState();
|
|
8311
9636
|
if (pendingHook) {
|
|
8312
|
-
pendingHook.
|
|
8313
|
-
|
|
8314
|
-
|
|
8315
|
-
|
|
8316
|
-
|
|
8317
|
-
|
|
8318
|
-
|
|
8319
|
-
|
|
8320
|
-
|
|
8321
|
-
|
|
9637
|
+
pendingHook.then(
|
|
9638
|
+
() => {
|
|
9639
|
+
try {
|
|
9640
|
+
shipHookState();
|
|
9641
|
+
} catch {
|
|
9642
|
+
}
|
|
9643
|
+
},
|
|
9644
|
+
(err) => {
|
|
9645
|
+
const message = maskPathText(
|
|
9646
|
+
err instanceof Error ? err.message : String(err),
|
|
9647
|
+
config.secretRules
|
|
9648
|
+
);
|
|
9649
|
+
manifest.warnings.push(`hooks.afterGenerate failed: ${message}`);
|
|
9650
|
+
manifest.totals.warningCount = manifest.warnings.length;
|
|
9651
|
+
try {
|
|
9652
|
+
shipHookState();
|
|
9653
|
+
} catch {
|
|
9654
|
+
}
|
|
8322
9655
|
}
|
|
8323
|
-
|
|
9656
|
+
);
|
|
9657
|
+
pendingHooks.set(outDir, pendingHook);
|
|
8324
9658
|
}
|
|
8325
9659
|
return manifest;
|
|
8326
9660
|
} catch (err) {
|
|
8327
9661
|
if (stagingDir !== null) {
|
|
8328
9662
|
try {
|
|
8329
|
-
|
|
9663
|
+
fs14.rmSync(stagingDir, { recursive: true, force: true });
|
|
8330
9664
|
} catch {
|
|
8331
9665
|
}
|
|
8332
9666
|
}
|
|
@@ -8336,10 +9670,17 @@ function generateSnapshot(configInput, options) {
|
|
|
8336
9670
|
releaseLock();
|
|
8337
9671
|
}
|
|
8338
9672
|
}
|
|
9673
|
+
var pendingHooks = /* @__PURE__ */ new Map();
|
|
8339
9674
|
async function generateSnapshotAsync(configInput, options) {
|
|
8340
|
-
|
|
9675
|
+
const manifest = generateSnapshot(configInput, options);
|
|
9676
|
+
const config = isResolvedConfig(configInput) ? normalizeResolvedConfig(configInput) : resolveConfig(configInput ?? null);
|
|
9677
|
+
const { outDir } = resolveSafeOutputDirs(config);
|
|
9678
|
+
const pending = pendingHooks.get(outDir);
|
|
9679
|
+
if (pending !== void 0) {
|
|
9680
|
+
pendingHooks.delete(outDir);
|
|
9681
|
+
await pending.catch(() => void 0);
|
|
9682
|
+
}
|
|
9683
|
+
return manifest;
|
|
8341
9684
|
}
|
|
8342
9685
|
|
|
8343
|
-
export { ARTIFACT_NAMES, CHECK_INTERVAL_MS, DEFAULT_ANSWERS, DEFAULT_SECRET_PATTERNS, FAILURE_INTERVAL_MS, GENERATOR_NAME, GENERATOR_VERSION, LANGUAGE_PROFILES, MIN_MAX_PART_BYTES, PACKAGE_LATEST_URL, PRESETS, PromptBack, PromptCancelled, REQUEST_TIMEOUT_MS, SCHEMA_VERSION, anchorForPath, appendGitignoreEntries, assertReplaceable, assertUsableConfigInput, cacheDirectory, cacheFilePath, checkForUpdate, collectFiles, compareManifests, countInvalidUtf8Bytes, countLines, decodeText, defaultIO, defineConfig, describeManifestShapeProblem, detectLanguage, detectPreset, displayWidth, escapeControls, explainPath, fenceForContent, fenceForText, fetchLatestVersion, findConfigFile, firstRunMessage, formatNumber, formatUpdateNotice, generateProjectTree, generateSnapshot, generateSnapshotAsync, getPreset, gitignorePath, globToRegExp, hasPriorSnapshot, installKind, isBinaryBuffer, isBinaryExtension, isBinaryFile, isDiscoveryMode, isEffectivelyEmpty, isInteractive, isNewer, isResolvedConfig, isSensitivePath, listPresetNames, loadConfigFile, loadConfigFileDetailed, loadManifest, markResolvedConfig, matchRule, matchesExclude, normalizeResolvedConfig, parseByteSize, partitionFiles, planFirstRun, profileForPath, renderConfig, renderDiffText, renderExplainText, renumberParts, resolveConfig, resolveSafeOutputDirs, runInit, scanAndRedactSecrets, shouldCheck, stripJsonComments, summarizeJson, summarizeMarkdown, summarizeTypeScriptSource, summarizeWithProfile, supportsTypeScriptImports, truncatePreview, truncateToBytes, unknownPresetMessage, updateCommand, validateSnapshot, wrapLines };
|
|
8344
|
-
//# sourceMappingURL=chunk-KUVFTJHK.js.map
|
|
8345
|
-
//# sourceMappingURL=chunk-KUVFTJHK.js.map
|
|
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 };
|