@ankurhalder/codebase-snapshot 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,11 +1,12 @@
1
1
  'use strict';
2
2
 
3
3
  var crypto = require('crypto');
4
- var fs8 = require('fs');
5
- var path9 = require('path');
4
+ var fs9 = require('fs');
5
+ var path10 = require('path');
6
6
  var ts = require('typescript');
7
7
  var url = require('url');
8
8
  var childProcess = require('child_process');
9
+ var readline = require('readline');
9
10
 
10
11
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
11
12
 
@@ -28,135 +29,498 @@ function _interopNamespace(e) {
28
29
  }
29
30
 
30
31
  var crypto__namespace = /*#__PURE__*/_interopNamespace(crypto);
31
- var fs8__namespace = /*#__PURE__*/_interopNamespace(fs8);
32
- var path9__namespace = /*#__PURE__*/_interopNamespace(path9);
32
+ var fs9__namespace = /*#__PURE__*/_interopNamespace(fs9);
33
+ var path10__namespace = /*#__PURE__*/_interopNamespace(path10);
33
34
  var ts__default = /*#__PURE__*/_interopDefault(ts);
34
35
  var childProcess__namespace = /*#__PURE__*/_interopNamespace(childProcess);
36
+ var readline__namespace = /*#__PURE__*/_interopNamespace(readline);
35
37
 
36
38
  // src/index.ts
37
39
 
38
- // src/ast/structured.ts
39
- function stripJsonComments(content) {
40
- const text = content.replace(/^\uFEFF/, "");
41
- let out = "";
42
- let inString = false;
43
- let inLineComment = false;
44
- let inBlockComment = false;
45
- for (let i = 0; i < text.length; i++) {
46
- const char = text[i];
47
- const next = text[i + 1];
48
- if (inLineComment) {
49
- if (char === "\n") {
50
- inLineComment = false;
51
- out += char;
40
+ // src/ast/profiles/shared.ts
41
+ var MAX_SUMMARY_CHARS = 1e6;
42
+ function emptySummary() {
43
+ return {
44
+ directives: [],
45
+ imports: [],
46
+ exports: [],
47
+ reExports: [],
48
+ functions: [],
49
+ classes: [],
50
+ interfaces: [],
51
+ types: [],
52
+ enums: [],
53
+ variables: [],
54
+ reactComponents: [],
55
+ hooks: [],
56
+ routeHandlers: []
57
+ };
58
+ }
59
+ function dedupeSorted(values) {
60
+ return Array.from(new Set(values.filter(Boolean))).sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
61
+ }
62
+ function finalizeSummary(summary) {
63
+ return {
64
+ directives: dedupeSorted(summary.directives),
65
+ imports: dedupeSorted(summary.imports),
66
+ exports: dedupeSorted(summary.exports),
67
+ reExports: dedupeSorted(summary.reExports),
68
+ functions: dedupeSorted(summary.functions),
69
+ classes: dedupeSorted(summary.classes),
70
+ interfaces: dedupeSorted(summary.interfaces),
71
+ types: dedupeSorted(summary.types),
72
+ enums: dedupeSorted(summary.enums),
73
+ variables: dedupeSorted(summary.variables),
74
+ reactComponents: dedupeSorted(summary.reactComponents),
75
+ hooks: dedupeSorted(summary.hooks),
76
+ routeHandlers: dedupeSorted(summary.routeHandlers)
77
+ };
78
+ }
79
+ function eachLine(content, visit) {
80
+ const text2 = content.length > MAX_SUMMARY_CHARS ? content.slice(0, MAX_SUMMARY_CHARS) : content;
81
+ for (const line of text2.split(/\r?\n/)) visit(line);
82
+ }
83
+ function isTopLevel(line) {
84
+ return !/^[\s]/.test(line);
85
+ }
86
+ function isPublicName(name) {
87
+ return name.length > 0 && !name.startsWith("_") && /^[A-Za-z]/.test(name);
88
+ }
89
+ function isCapitalizedName(name) {
90
+ return /^[A-Z]/.test(name);
91
+ }
92
+
93
+ // src/ast/profiles/cfamily.ts
94
+ var INCLUDE = /^#\s*include\s*[<"]([^>"]+)[>"]/;
95
+ var DEFINE = /^#\s*define\s+([A-Za-z_]\w*)/;
96
+ var USING = /^using\s+(?:namespace\s+)?([\w:]+)\s*;/;
97
+ var TYPE_DECL = /^(?:typedef\s+)?(?:struct|union|class|enum)\s+([A-Za-z_]\w*)/;
98
+ var TYPEDEF = /^typedef\b[^;{}]*?\b([A-Za-z_]\w*)\s*;/;
99
+ var FUNCTION = /^[A-Za-z_][\w \t*&:<>,]*?\b([A-Za-z_]\w*)\s*\([^;]*\)\s*\{?\s*$/;
100
+ var NOT_A_FUNCTION = /^(?:if|for|while|switch|return|else|do|case|sizeof|catch|throw)\b/;
101
+ var cfamilyProfile = {
102
+ id: "cfamily",
103
+ extensions: [".c", ".h", ".cc", ".cpp", ".cxx", ".hpp", ".hxx", ".m", ".mm"],
104
+ summarize(_path, content) {
105
+ const summary = emptySummary();
106
+ eachLine(content, (line) => {
107
+ const trimmed = line.trim();
108
+ if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("/*") || trimmed.startsWith("*")) {
109
+ return;
52
110
  }
53
- continue;
54
- }
55
- if (inBlockComment) {
56
- if (char === "*" && next === "/") {
57
- inBlockComment = false;
58
- i += 1;
111
+ const included = INCLUDE.exec(trimmed);
112
+ if (included) {
113
+ summary.imports.push(included[1]);
114
+ return;
59
115
  }
60
- continue;
61
- }
62
- if (inString) {
63
- out += char;
64
- if (char === "\\") {
65
- if (next !== void 0) {
66
- out += next;
67
- i += 1;
116
+ const used = USING.exec(trimmed);
117
+ if (used) {
118
+ summary.imports.push(used[1]);
119
+ return;
120
+ }
121
+ const macro = DEFINE.exec(trimmed);
122
+ if (macro) {
123
+ summary.variables.push(macro[1]);
124
+ return;
125
+ }
126
+ if (!isTopLevel(line)) return;
127
+ const declared = TYPE_DECL.exec(line);
128
+ if (declared) {
129
+ const kind = /^typedef\s+enum/.test(line) || /^enum\b/.test(trimmed) ? "enum" : "type";
130
+ if (kind === "enum") summary.enums.push(declared[1]);
131
+ else summary.classes.push(declared[1]);
132
+ summary.exports.push(declared[1]);
133
+ return;
134
+ }
135
+ const aliased = TYPEDEF.exec(line);
136
+ if (aliased) {
137
+ summary.types.push(aliased[1]);
138
+ summary.exports.push(aliased[1]);
139
+ return;
140
+ }
141
+ if (NOT_A_FUNCTION.test(trimmed)) return;
142
+ const fn = FUNCTION.exec(line);
143
+ if (fn) {
144
+ summary.functions.push(fn[1]);
145
+ summary.exports.push(fn[1]);
146
+ }
147
+ });
148
+ return { summary: finalizeSummary(summary), quality: "heuristic" };
149
+ }
150
+ };
151
+
152
+ // src/ast/profiles/dotnet.ts
153
+ var USING2 = /^using\s+([\w.]+)\s*;/;
154
+ 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*)/;
155
+ var METHOD = /^\s*(?:public|private|protected|internal)\s+(?:static\s+)?(?:async\s+)?(?:virtual\s+|override\s+|sealed\s+)?[\w<>[\],?]+\s+([A-Za-z_]\w*)\s*[<(]/;
156
+ var dotnetProfile = {
157
+ id: "dotnet",
158
+ extensions: [".cs"],
159
+ summarize(_path, content) {
160
+ const summary = emptySummary();
161
+ eachLine(content, (line) => {
162
+ const trimmed = line.trim();
163
+ if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("/*") || trimmed.startsWith("*")) {
164
+ return;
165
+ }
166
+ const used = USING2.exec(trimmed);
167
+ if (used) {
168
+ summary.imports.push(used[1]);
169
+ return;
170
+ }
171
+ const declaration = TYPE_DECL2.exec(line);
172
+ if (declaration) {
173
+ const kind = declaration[1];
174
+ const name = declaration[2];
175
+ if (kind === "interface") summary.interfaces.push(name);
176
+ else if (kind === "enum") summary.enums.push(name);
177
+ else summary.classes.push(name);
178
+ if (/^\s*public\b/.test(line)) summary.exports.push(name);
179
+ return;
180
+ }
181
+ const method = METHOD.exec(line);
182
+ if (method) {
183
+ summary.functions.push(method[1]);
184
+ if (/^\s*public\b/.test(line)) summary.exports.push(method[1]);
185
+ }
186
+ });
187
+ return { summary: finalizeSummary(summary), quality: "heuristic" };
188
+ }
189
+ };
190
+
191
+ // src/ast/profiles/go.ts
192
+ var FUNC = /^func\s+(?:\([^)]*\)\s*)?([A-Za-z_]\w*)/;
193
+ var TYPE = /^type\s+([A-Za-z_]\w*)\s+(.*)$/;
194
+ var VAR_CONST = /^(?:var|const)\s+([A-Za-z_]\w*)/;
195
+ var IMPORT_SINGLE = /^import\s+(?:[\w.]+\s+)?"([^"]+)"/;
196
+ var IMPORT_OPEN = /^import\s*\(/;
197
+ var IMPORT_ENTRY = /^\s*(?:[\w.]+\s+)?"([^"]+)"/;
198
+ var goProfile = {
199
+ id: "go",
200
+ extensions: [".go"],
201
+ summarize(_path, content) {
202
+ const summary = emptySummary();
203
+ let inImportBlock = false;
204
+ eachLine(content, (line) => {
205
+ const trimmed = line.trim();
206
+ if (!trimmed || trimmed.startsWith("//")) return;
207
+ if (inImportBlock) {
208
+ if (trimmed.startsWith(")")) {
209
+ inImportBlock = false;
210
+ return;
68
211
  }
69
- } else if (char === '"') {
70
- inString = false;
212
+ const entry = IMPORT_ENTRY.exec(line);
213
+ if (entry) summary.imports.push(entry[1]);
214
+ return;
71
215
  }
72
- continue;
73
- }
74
- if (char === '"') {
75
- inString = true;
76
- out += char;
77
- continue;
78
- }
79
- if (char === "/" && next === "/") {
80
- inLineComment = true;
81
- i += 1;
82
- continue;
83
- }
84
- if (char === "/" && next === "*") {
85
- inBlockComment = true;
86
- i += 1;
87
- continue;
88
- }
89
- out += char;
216
+ if (IMPORT_OPEN.test(trimmed)) {
217
+ inImportBlock = true;
218
+ return;
219
+ }
220
+ const single = IMPORT_SINGLE.exec(trimmed);
221
+ if (single) {
222
+ summary.imports.push(single[1]);
223
+ return;
224
+ }
225
+ if (!isTopLevel(line)) return;
226
+ const method = FUNC.exec(line);
227
+ if (method) {
228
+ const name = method[1];
229
+ summary.functions.push(name);
230
+ if (isCapitalizedName(name)) summary.exports.push(name);
231
+ return;
232
+ }
233
+ const declared = TYPE.exec(line);
234
+ if (declared) {
235
+ const name = declared[1];
236
+ const body = declared[2].trim();
237
+ if (body.startsWith("struct")) summary.classes.push(name);
238
+ else if (body.startsWith("interface")) summary.interfaces.push(name);
239
+ else summary.types.push(name);
240
+ if (isCapitalizedName(name)) summary.exports.push(name);
241
+ return;
242
+ }
243
+ const value = VAR_CONST.exec(line);
244
+ if (value) {
245
+ summary.variables.push(value[1]);
246
+ if (isCapitalizedName(value[1])) summary.exports.push(value[1]);
247
+ }
248
+ });
249
+ return { summary: finalizeSummary(summary), quality: "heuristic" };
90
250
  }
91
- return out;
92
- }
93
- function summarizeJson(content) {
94
- try {
95
- const parsed = JSON.parse(stripJsonComments(content));
96
- if (Array.isArray(parsed)) {
97
- const sample = parsed[0];
98
- const sampleKeys = sample && typeof sample === "object" && !Array.isArray(sample) ? Object.keys(sample).slice(0, 10).sort() : [];
99
- return {
100
- shape: "array",
101
- itemCount: parsed.length,
102
- keys: [],
103
- sampleKeys
104
- };
105
- }
106
- if (parsed && typeof parsed === "object") {
107
- const keys = Object.keys(parsed).sort();
108
- return {
109
- shape: "object",
110
- itemCount: keys.length,
111
- keys: keys.slice(0, 30),
112
- sampleKeys: []
113
- };
114
- }
115
- return {
116
- shape: typeof parsed,
117
- itemCount: null,
118
- keys: [],
119
- sampleKeys: []
120
- };
121
- } catch {
122
- return {
123
- shape: "invalid-json",
124
- itemCount: null,
125
- keys: [],
126
- sampleKeys: []
127
- };
251
+ };
252
+
253
+ // src/ast/profiles/jvm.ts
254
+ var IMPORT = /^import\s+(?:static\s+)?([\w.]+?)(?:\.\*)?;?\s*$/;
255
+ 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*)/;
256
+ 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*)/;
257
+ var SCALA_DEF = /^\s*def\s+([A-Za-z_]\w*)/;
258
+ var JAVA_METHOD = /^\s+(?:public|private|protected)\s+(?:static\s+)?(?:final\s+)?(?:synchronized\s+)?(?:abstract\s+)?[\w<>[\],.?]+\s+([A-Za-z_]\w*)\s*\(/;
259
+ var jvmProfile = {
260
+ id: "jvm",
261
+ extensions: [".java", ".kt", ".kts", ".scala"],
262
+ summarize(path11, content) {
263
+ const summary = emptySummary();
264
+ const lower = path11.toLowerCase();
265
+ const isJava = lower.endsWith(".java");
266
+ const isKotlin = lower.endsWith(".kt") || lower.endsWith(".kts");
267
+ eachLine(content, (line) => {
268
+ const trimmed = line.trim();
269
+ if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("*") || trimmed.startsWith("/*")) {
270
+ return;
271
+ }
272
+ const imported = IMPORT.exec(trimmed);
273
+ if (imported) {
274
+ summary.imports.push(imported[1]);
275
+ return;
276
+ }
277
+ const declaration = TYPE_DECL3.exec(line);
278
+ if (declaration) {
279
+ const kind = declaration[1];
280
+ const name = declaration[2];
281
+ if (kind === "interface" || kind === "trait") summary.interfaces.push(name);
282
+ else if (kind === "enum") summary.enums.push(name);
283
+ else summary.classes.push(name);
284
+ const hidden = /^\s*(?:private|protected|internal)\b/.test(line);
285
+ if (isJava ? /^\s*public\b/.test(line) : !hidden) summary.exports.push(name);
286
+ return;
287
+ }
288
+ const kotlinFunction = KOTLIN_FUN.exec(line);
289
+ if (kotlinFunction && isKotlin) {
290
+ summary.functions.push(kotlinFunction[1]);
291
+ const hidden = /^\s*(?:private|protected|internal)\b/.test(line);
292
+ if (!hidden) summary.exports.push(kotlinFunction[1]);
293
+ return;
294
+ }
295
+ const scalaFunction = SCALA_DEF.exec(line);
296
+ if (scalaFunction && !isJava && !isKotlin) {
297
+ summary.functions.push(scalaFunction[1]);
298
+ const hidden = /^\s*(?:private|protected)\b/.test(line);
299
+ if (!hidden) summary.exports.push(scalaFunction[1]);
300
+ return;
301
+ }
302
+ if (isJava) {
303
+ const method = JAVA_METHOD.exec(line);
304
+ if (method) {
305
+ summary.functions.push(method[1]);
306
+ if (/^\s*public\b/.test(line)) summary.exports.push(method[1]);
307
+ }
308
+ }
309
+ });
310
+ return { summary: finalizeSummary(summary), quality: "heuristic" };
128
311
  }
129
- }
130
- function summarizeMarkdown(content) {
131
- const headings = [];
132
- const frontmatterKeys = [];
133
- const lines = content.replace(/^\uFEFF/, "").split("\n");
134
- let inFrontmatter = false;
135
- for (let index = 0; index < lines.length; index++) {
136
- const line = lines[index];
137
- if (index === 0 && line.trim() === "---") {
138
- inFrontmatter = true;
139
- continue;
140
- }
141
- if (inFrontmatter) {
142
- if (line.trim() === "---") {
143
- inFrontmatter = false;
144
- continue;
312
+ };
313
+
314
+ // src/ast/profiles/php.ts
315
+ var USE = /^use\s+([\w\\]+)\s*(?:as\s+\w+)?;/;
316
+ var DECLARE = /^(?:abstract\s+|final\s+|readonly\s+)*(class|interface|trait|enum)\s+([A-Za-z_]\w*)/;
317
+ var FUNCTION2 = /^(?:public\s+|protected\s+|private\s+|static\s+|final\s+|abstract\s+)*function\s+([A-Za-z_]\w*)/;
318
+ var phpProfile = {
319
+ id: "php",
320
+ extensions: [".php"],
321
+ summarize(_path, content) {
322
+ const summary = emptySummary();
323
+ eachLine(content, (line) => {
324
+ const trimmed = line.trim();
325
+ if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("#") || trimmed.startsWith("/*")) {
326
+ return;
145
327
  }
146
- const match = /^([a-zA-Z0-9_-]+):/.exec(line);
147
- if (match) frontmatterKeys.push(match[1]);
148
- continue;
149
- }
150
- const headingMatch = /^(#{1,6})\s+(.+)$/.exec(line.trim());
151
- if (headingMatch) {
152
- headings.push(`${headingMatch[1]} ${headingMatch[2].trim()}`);
153
- }
328
+ const used = USE.exec(trimmed);
329
+ if (used) {
330
+ summary.imports.push(used[1].replace(/\\\\/g, "\\"));
331
+ return;
332
+ }
333
+ const declaration = DECLARE.exec(trimmed);
334
+ if (declaration) {
335
+ const kind = declaration[1];
336
+ const name = declaration[2];
337
+ if (kind === "interface" || kind === "trait") summary.interfaces.push(name);
338
+ else if (kind === "enum") summary.enums.push(name);
339
+ else summary.classes.push(name);
340
+ summary.exports.push(name);
341
+ return;
342
+ }
343
+ const fn = FUNCTION2.exec(trimmed);
344
+ if (fn) {
345
+ summary.functions.push(fn[1]);
346
+ if (!/^private\b/.test(trimmed)) summary.exports.push(fn[1]);
347
+ }
348
+ });
349
+ return { summary: finalizeSummary(summary), quality: "heuristic" };
154
350
  }
155
- return {
156
- headings: headings.slice(0, 40),
157
- frontmatterKeys: Array.from(new Set(frontmatterKeys)).sort()
158
- };
159
- }
351
+ };
352
+
353
+ // src/ast/profiles/python.ts
354
+ var IMPORT2 = /^import\s+(.+)$/;
355
+ var FROM_IMPORT = /^from\s+([\w.]+)\s+import\s+(.+)$/;
356
+ var CLASS = /^class\s+([A-Za-z_]\w*)/;
357
+ var DEF = /^(?:async\s+)?def\s+([A-Za-z_]\w*)/;
358
+ var CONSTANT = /^([A-Z_][A-Z0-9_]*)\s*(?::[^=]+)?=/;
359
+ var ALL_DUNDER = /^__all__\s*=\s*\[([^\]]*)\]/;
360
+ var ROUTE_DECORATOR = /^\s*@[\w.]*\b(?:get|post|put|patch|delete|head|options|route)\s*\(/i;
361
+ var pythonProfile = {
362
+ id: "python",
363
+ extensions: [".py", ".pyi"],
364
+ summarize(_path, content) {
365
+ const summary = emptySummary();
366
+ let routePending = false;
367
+ eachLine(content, (line) => {
368
+ const trimmed = line.trim();
369
+ if (!trimmed || trimmed.startsWith("#")) return;
370
+ if (ROUTE_DECORATOR.test(line)) {
371
+ routePending = true;
372
+ return;
373
+ }
374
+ const from = FROM_IMPORT.exec(trimmed);
375
+ if (from) {
376
+ summary.imports.push(from[1]);
377
+ routePending = false;
378
+ return;
379
+ }
380
+ const imported = IMPORT2.exec(trimmed);
381
+ if (imported) {
382
+ for (const part of imported[1].split(",")) {
383
+ const name = part.trim().split(/\s+as\s+/)[0].trim();
384
+ if (name) summary.imports.push(name);
385
+ }
386
+ routePending = false;
387
+ return;
388
+ }
389
+ if (isTopLevel(line)) {
390
+ const klass = CLASS.exec(line);
391
+ if (klass) {
392
+ summary.classes.push(klass[1]);
393
+ if (isPublicName(klass[1])) summary.exports.push(klass[1]);
394
+ routePending = false;
395
+ return;
396
+ }
397
+ const definition = DEF.exec(line);
398
+ if (definition) {
399
+ const name = definition[1];
400
+ summary.functions.push(name);
401
+ if (routePending) {
402
+ summary.routeHandlers.push(name);
403
+ routePending = false;
404
+ }
405
+ if (isPublicName(name)) summary.exports.push(name);
406
+ return;
407
+ }
408
+ const all = ALL_DUNDER.exec(trimmed);
409
+ if (all) {
410
+ for (const entry of all[1].split(",")) {
411
+ const name = entry.trim().replace(/['"]/g, "").trim();
412
+ if (name) summary.exports.push(name);
413
+ }
414
+ return;
415
+ }
416
+ const constant = CONSTANT.exec(line);
417
+ if (constant) summary.variables.push(constant[1]);
418
+ }
419
+ });
420
+ return { summary: finalizeSummary(summary), quality: "heuristic" };
421
+ }
422
+ };
423
+
424
+ // src/ast/profiles/ruby.ts
425
+ var REQUIRE = /^require(?:_relative)?\s+["']([^"']+)["']/;
426
+ var CLASS_OR_MODULE = /^(?:class|module)\s+([A-Z][\w:]*)/;
427
+ var METHOD2 = /^def\s+(?:self\.)?([A-Za-z_]\w*[?!=]?)/;
428
+ var rubyProfile = {
429
+ id: "ruby",
430
+ extensions: [".rb", ".rake", ".gemspec"],
431
+ summarize(_path, content) {
432
+ const summary = emptySummary();
433
+ eachLine(content, (line) => {
434
+ const trimmed = line.trim();
435
+ if (!trimmed || trimmed.startsWith("#")) return;
436
+ const required = REQUIRE.exec(trimmed);
437
+ if (required) {
438
+ summary.imports.push(required[1]);
439
+ return;
440
+ }
441
+ if (!isTopLevel(line)) return;
442
+ const declaration = CLASS_OR_MODULE.exec(line);
443
+ if (declaration) {
444
+ summary.classes.push(declaration[1]);
445
+ summary.exports.push(declaration[1]);
446
+ return;
447
+ }
448
+ const method = METHOD2.exec(line);
449
+ if (method) {
450
+ summary.functions.push(method[1]);
451
+ summary.exports.push(method[1]);
452
+ }
453
+ });
454
+ return { summary: finalizeSummary(summary), quality: "heuristic" };
455
+ }
456
+ };
457
+
458
+ // src/ast/profiles/rust.ts
459
+ var USE2 = /^use\s+([^;]+);/;
460
+ var FN = /^(pub(?:\([^)]*\))?\s+)?(?:const\s+)?(?:async\s+)?(?:unsafe\s+)?fn\s+([A-Za-z_]\w*)/;
461
+ var STRUCT = /^(pub(?:\([^)]*\))?\s+)?struct\s+([A-Za-z_]\w*)/;
462
+ var TRAIT = /^(pub(?:\([^)]*\))?\s+)?(?:unsafe\s+)?trait\s+([A-Za-z_]\w*)/;
463
+ var ENUM = /^(pub(?:\([^)]*\))?\s+)?enum\s+([A-Za-z_]\w*)/;
464
+ var TYPE2 = /^(pub(?:\([^)]*\))?\s+)?type\s+([A-Za-z_]\w*)/;
465
+ var MOD = /^(pub(?:\([^)]*\))?\s+)?mod\s+([A-Za-z_]\w*)/;
466
+ var IMPL = /^impl(?:<[^>]*>)?\s+(?:[\w:<>]+\s+for\s+)?([A-Za-z_]\w*)/;
467
+ var rustProfile = {
468
+ id: "rust",
469
+ extensions: [".rs"],
470
+ summarize(_path, content) {
471
+ const summary = emptySummary();
472
+ eachLine(content, (line) => {
473
+ const trimmed = line.trim();
474
+ if (!trimmed || trimmed.startsWith("//")) return;
475
+ const used = USE2.exec(trimmed);
476
+ if (used) {
477
+ summary.imports.push(used[1].trim());
478
+ return;
479
+ }
480
+ if (!isTopLevel(line)) return;
481
+ const isPublic = /^pub\b/.test(trimmed);
482
+ const fn = FN.exec(line);
483
+ if (fn) {
484
+ summary.functions.push(fn[2]);
485
+ if (isPublic) summary.exports.push(fn[2]);
486
+ return;
487
+ }
488
+ const struct = STRUCT.exec(line);
489
+ if (struct) {
490
+ summary.classes.push(struct[2]);
491
+ if (isPublic) summary.exports.push(struct[2]);
492
+ return;
493
+ }
494
+ const trait = TRAIT.exec(line);
495
+ if (trait) {
496
+ summary.interfaces.push(trait[2]);
497
+ if (isPublic) summary.exports.push(trait[2]);
498
+ return;
499
+ }
500
+ const enumeration = ENUM.exec(line);
501
+ if (enumeration) {
502
+ summary.enums.push(enumeration[2]);
503
+ if (isPublic) summary.exports.push(enumeration[2]);
504
+ return;
505
+ }
506
+ const alias = TYPE2.exec(line);
507
+ if (alias) {
508
+ summary.types.push(alias[2]);
509
+ if (isPublic) summary.exports.push(alias[2]);
510
+ return;
511
+ }
512
+ const module = MOD.exec(line);
513
+ if (module) {
514
+ summary.variables.push(module[2]);
515
+ if (isPublic) summary.exports.push(module[2]);
516
+ return;
517
+ }
518
+ const implementation = IMPL.exec(line);
519
+ if (implementation) summary.classes.push(implementation[1]);
520
+ });
521
+ return { summary: finalizeSummary(summary), quality: "heuristic" };
522
+ }
523
+ };
160
524
  var ROUTE_HANDLER_NAMES = /* @__PURE__ */ new Set([
161
525
  "GET",
162
526
  "POST",
@@ -196,9 +560,9 @@ function summarizeTypeScriptSource(relativePath, content) {
196
560
  if (parseErrors > 0) summary.parseErrors = parseErrors;
197
561
  for (const statement of sourceFile.statements) {
198
562
  if (ts__default.default.isExpressionStatement(statement) && ts__default.default.isStringLiteral(statement.expression)) {
199
- const text = statement.expression.text;
200
- if (text === "use client" || text === "use server") {
201
- summary.directives.push(text);
563
+ const text2 = statement.expression.text;
564
+ if (text2 === "use client" || text2 === "use server") {
565
+ summary.directives.push(text2);
202
566
  }
203
567
  continue;
204
568
  }
@@ -298,23 +662,344 @@ function summarizeTypeScriptSource(relativePath, content) {
298
662
  stack.push(children[index]);
299
663
  }
300
664
  }
301
- const dedupeSorted = (values) => Array.from(new Set(values)).sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
302
- summary.directives = dedupeSorted(summary.directives);
303
- summary.imports = dedupeSorted(summary.imports);
304
- summary.exports = dedupeSorted(summary.exports);
305
- summary.reExports = dedupeSorted(summary.reExports);
306
- summary.functions = dedupeSorted(summary.functions);
307
- summary.classes = dedupeSorted(summary.classes);
308
- summary.interfaces = dedupeSorted(summary.interfaces);
309
- summary.types = dedupeSorted(summary.types);
310
- summary.enums = dedupeSorted(summary.enums);
311
- summary.variables = dedupeSorted(summary.variables);
312
- summary.reactComponents = dedupeSorted(summary.reactComponents);
313
- summary.hooks = dedupeSorted(summary.hooks);
314
- summary.routeHandlers = dedupeSorted(summary.routeHandlers);
665
+ const dedupeSorted2 = (values) => Array.from(new Set(values)).sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
666
+ summary.directives = dedupeSorted2(summary.directives);
667
+ summary.imports = dedupeSorted2(summary.imports);
668
+ summary.exports = dedupeSorted2(summary.exports);
669
+ summary.reExports = dedupeSorted2(summary.reExports);
670
+ summary.functions = dedupeSorted2(summary.functions);
671
+ summary.classes = dedupeSorted2(summary.classes);
672
+ summary.interfaces = dedupeSorted2(summary.interfaces);
673
+ summary.types = dedupeSorted2(summary.types);
674
+ summary.enums = dedupeSorted2(summary.enums);
675
+ summary.variables = dedupeSorted2(summary.variables);
676
+ summary.reactComponents = dedupeSorted2(summary.reactComponents);
677
+ summary.hooks = dedupeSorted2(summary.hooks);
678
+ summary.routeHandlers = dedupeSorted2(summary.routeHandlers);
315
679
  return summary;
316
680
  }
317
681
 
682
+ // src/ast/profiles/sfc.ts
683
+ var SCRIPT_BLOCK = /<script[^>]*>([\s\S]*?)<\/script>/gi;
684
+ var ASTRO_FRONTMATTER = /^\uFEFF?---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*(?=\r?\n|$)/;
685
+ function componentName(path11) {
686
+ const base = path11.split("/").pop() ?? path11;
687
+ return base.replace(/\.[^.]+$/, "");
688
+ }
689
+ var sfcProfile = {
690
+ id: "sfc",
691
+ extensions: [".vue", ".svelte", ".astro"],
692
+ summarize(path11, content) {
693
+ let script = "";
694
+ if (path11.toLowerCase().endsWith(".astro")) {
695
+ const frontmatter = ASTRO_FRONTMATTER.exec(content);
696
+ if (frontmatter) script += `${frontmatter[1]}
697
+ `;
698
+ }
699
+ for (const match of content.matchAll(SCRIPT_BLOCK)) {
700
+ script += `${match[1]}
701
+ `;
702
+ }
703
+ const name = componentName(path11);
704
+ if (!script.trim()) {
705
+ const summary2 = emptySummary();
706
+ if (name) {
707
+ summary2.exports.push(name);
708
+ summary2.reactComponents.push(name);
709
+ }
710
+ return { summary: finalizeSummary(summary2), quality: "heuristic" };
711
+ }
712
+ const summary = summarizeTypeScriptSource(`${name}.ts`, script);
713
+ summary.reactComponents.push(name);
714
+ summary.exports.push(name);
715
+ return { summary, quality: "ast" };
716
+ }
717
+ };
718
+
719
+ // src/ast/profiles/index.ts
720
+ var LANGUAGE_PROFILES = [
721
+ pythonProfile,
722
+ goProfile,
723
+ rustProfile,
724
+ jvmProfile,
725
+ dotnetProfile,
726
+ phpProfile,
727
+ rubyProfile,
728
+ cfamilyProfile,
729
+ sfcProfile
730
+ ];
731
+ var BY_EXTENSION = /* @__PURE__ */ new Map();
732
+ for (const profile of LANGUAGE_PROFILES) {
733
+ for (const extension of profile.extensions) {
734
+ BY_EXTENSION.set(extension.toLowerCase(), profile);
735
+ }
736
+ }
737
+ function profileForPath(path11) {
738
+ const lower = path11.toLowerCase();
739
+ const dot = lower.lastIndexOf(".");
740
+ if (dot === -1) return null;
741
+ return BY_EXTENSION.get(lower.slice(dot)) ?? null;
742
+ }
743
+ function summarizeWithProfile(path11, content) {
744
+ const profile = profileForPath(path11);
745
+ return profile ? profile.summarize(path11, content) : null;
746
+ }
747
+
748
+ // src/ast/structured.ts
749
+ function stripJsonComments(content) {
750
+ const text2 = content.replace(/^\uFEFF/, "");
751
+ let out = "";
752
+ let inString = false;
753
+ let inLineComment = false;
754
+ let inBlockComment = false;
755
+ for (let i = 0; i < text2.length; i++) {
756
+ const char = text2[i];
757
+ const next = text2[i + 1];
758
+ if (inLineComment) {
759
+ if (char === "\n") {
760
+ inLineComment = false;
761
+ out += char;
762
+ }
763
+ continue;
764
+ }
765
+ if (inBlockComment) {
766
+ if (char === "*" && next === "/") {
767
+ inBlockComment = false;
768
+ i += 1;
769
+ }
770
+ continue;
771
+ }
772
+ if (inString) {
773
+ out += char;
774
+ if (char === "\\") {
775
+ if (next !== void 0) {
776
+ out += next;
777
+ i += 1;
778
+ }
779
+ } else if (char === '"') {
780
+ inString = false;
781
+ }
782
+ continue;
783
+ }
784
+ if (char === '"') {
785
+ inString = true;
786
+ out += char;
787
+ continue;
788
+ }
789
+ if (char === "/" && next === "/") {
790
+ inLineComment = true;
791
+ i += 1;
792
+ continue;
793
+ }
794
+ if (char === "/" && next === "*") {
795
+ inBlockComment = true;
796
+ i += 1;
797
+ continue;
798
+ }
799
+ out += char;
800
+ }
801
+ return out;
802
+ }
803
+ function summarizeJson(content) {
804
+ try {
805
+ const parsed = JSON.parse(stripJsonComments(content));
806
+ if (Array.isArray(parsed)) {
807
+ const sample = parsed[0];
808
+ const sampleKeys = sample && typeof sample === "object" && !Array.isArray(sample) ? Object.keys(sample).slice(0, 10).sort() : [];
809
+ return {
810
+ shape: "array",
811
+ itemCount: parsed.length,
812
+ keys: [],
813
+ sampleKeys
814
+ };
815
+ }
816
+ if (parsed && typeof parsed === "object") {
817
+ const keys = Object.keys(parsed).sort();
818
+ return {
819
+ shape: "object",
820
+ itemCount: keys.length,
821
+ keys: keys.slice(0, 30),
822
+ sampleKeys: []
823
+ };
824
+ }
825
+ return {
826
+ shape: typeof parsed,
827
+ itemCount: null,
828
+ keys: [],
829
+ sampleKeys: []
830
+ };
831
+ } catch {
832
+ return {
833
+ shape: "invalid-json",
834
+ itemCount: null,
835
+ keys: [],
836
+ sampleKeys: []
837
+ };
838
+ }
839
+ }
840
+ function summarizeMarkdown(content) {
841
+ const headings = [];
842
+ const frontmatterKeys = [];
843
+ const lines = content.replace(/^\uFEFF/, "").split("\n");
844
+ let inFrontmatter = false;
845
+ for (let index = 0; index < lines.length; index++) {
846
+ const line = lines[index];
847
+ if (index === 0 && line.trim() === "---") {
848
+ inFrontmatter = true;
849
+ continue;
850
+ }
851
+ if (inFrontmatter) {
852
+ if (line.trim() === "---") {
853
+ inFrontmatter = false;
854
+ continue;
855
+ }
856
+ const match = /^([a-zA-Z0-9_-]+):/.exec(line);
857
+ if (match) frontmatterKeys.push(match[1]);
858
+ continue;
859
+ }
860
+ const headingMatch = /^(#{1,6})\s+(.+)$/.exec(line.trim());
861
+ if (headingMatch) {
862
+ headings.push(`${headingMatch[1]} ${headingMatch[2].trim()}`);
863
+ }
864
+ }
865
+ return {
866
+ headings: headings.slice(0, 40),
867
+ frontmatterKeys: Array.from(new Set(frontmatterKeys)).sort()
868
+ };
869
+ }
870
+ var MANIFEST_FILES = [
871
+ "package.json",
872
+ "pyproject.toml",
873
+ "requirements.txt",
874
+ "setup.py",
875
+ "go.mod",
876
+ "Cargo.toml",
877
+ "pom.xml",
878
+ "build.gradle",
879
+ "build.gradle.kts",
880
+ "composer.json",
881
+ "Gemfile"
882
+ ];
883
+ var MANIFEST_MAX_BYTES = 1e5;
884
+ function readManifest(cwd, name) {
885
+ const full = path10__namespace.join(cwd, name);
886
+ try {
887
+ const stats = fs9__namespace.statSync(full);
888
+ if (!stats.isFile() || stats.size > MANIFEST_MAX_BYTES) return null;
889
+ return fs9__namespace.readFileSync(full, "utf8");
890
+ } catch {
891
+ return null;
892
+ }
893
+ }
894
+ function collectDependencies(manifests) {
895
+ const names = /* @__PURE__ */ new Set();
896
+ const raw = manifests["package.json"];
897
+ if (!raw) return names;
898
+ try {
899
+ const parsed = JSON.parse(raw);
900
+ for (const section of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
901
+ const block = parsed[section];
902
+ if (block && typeof block === "object") {
903
+ for (const name of Object.keys(block)) names.add(name);
904
+ }
905
+ }
906
+ } catch {
907
+ }
908
+ return names;
909
+ }
910
+ function buildPresetContext(cwd) {
911
+ const files = /* @__PURE__ */ new Set();
912
+ const dirs = /* @__PURE__ */ new Set();
913
+ try {
914
+ for (const entry of fs9__namespace.readdirSync(cwd, { withFileTypes: true })) {
915
+ if (entry.isDirectory()) dirs.add(entry.name);
916
+ else if (entry.isFile()) files.add(entry.name);
917
+ }
918
+ } catch {
919
+ }
920
+ const manifests = {};
921
+ for (const name of MANIFEST_FILES) {
922
+ const content = readManifest(cwd, name);
923
+ if (content !== null) manifests[name] = content;
924
+ }
925
+ return {
926
+ cwd,
927
+ files,
928
+ dirs,
929
+ dependencies: collectDependencies(manifests),
930
+ manifests
931
+ };
932
+ }
933
+ function hasFile(context, name) {
934
+ return context.files.has(name);
935
+ }
936
+ function hasFileStartingWith(context, prefix) {
937
+ for (const file of context.files) {
938
+ if (file.startsWith(prefix)) return true;
939
+ }
940
+ return false;
941
+ }
942
+ function manifestContains(context, manifest, needle) {
943
+ const content = context.manifests[manifest];
944
+ if (!content) return false;
945
+ return content.toLowerCase().includes(needle.toLowerCase());
946
+ }
947
+ function hasDependency(context, ...names) {
948
+ return names.some((name) => context.dependencies.has(name));
949
+ }
950
+
951
+ // src/presets/helpers.ts
952
+ function layer(name, ...prefixes) {
953
+ return {
954
+ name,
955
+ match: (filePath) => prefixes.some((prefix) => filePath.startsWith(prefix))
956
+ };
957
+ }
958
+ function kindOf(name, ...needles) {
959
+ return {
960
+ kind: name,
961
+ match: (filePath) => needles.some((needle) => filePath.includes(needle))
962
+ };
963
+ }
964
+ function suffixKind(name, ...suffixes) {
965
+ return {
966
+ kind: name,
967
+ match: (filePath) => suffixes.some((suffix) => filePath.endsWith(suffix))
968
+ };
969
+ }
970
+ function commonLayers() {
971
+ return [
972
+ layer("scripts", "scripts/", "tools/"),
973
+ layer("docs", "docs/", "documentation/"),
974
+ {
975
+ name: "config",
976
+ match: (filePath) => filePath.startsWith(".github/") || filePath.startsWith(".husky/") || filePath.startsWith(".vscode/") || filePath.startsWith(".") || filePath.includes("config")
977
+ },
978
+ { name: "root", match: (filePath) => !filePath.includes("/") }
979
+ ];
980
+ }
981
+ function commonKinds() {
982
+ return [
983
+ {
984
+ kind: "test",
985
+ match: (filePath) => {
986
+ const lower = filePath.toLowerCase();
987
+ return lower.includes(".test.") || lower.includes(".spec.") || lower.startsWith("test/") || lower.startsWith("tests/") || lower.includes("_test.") || lower.endsWith("_test.go") || lower.includes("__tests__");
988
+ }
989
+ },
990
+ suffixKind("doc", ".md", ".mdx", ".rst", ".txt"),
991
+ suffixKind("data", ".json", ".yaml", ".yml", ".csv", ".toml"),
992
+ {
993
+ kind: "script",
994
+ match: (filePath) => filePath.startsWith("scripts/") || /\.(sh|ps1|bat)$/i.test(filePath)
995
+ },
996
+ {
997
+ kind: "config",
998
+ match: (filePath) => filePath.startsWith(".") || filePath.includes("config") || filePath === "package.json" || filePath === "tsconfig.json"
999
+ }
1000
+ ];
1001
+ }
1002
+
318
1003
  // src/presets/nextjs.ts
319
1004
  var NEXTJS_LAYERS = [
320
1005
  { name: "app", match: (p) => p.startsWith("src/app/") || p.startsWith("app/") },
@@ -370,53 +1055,6 @@ var NEXTJS_KINDS = [
370
1055
  }
371
1056
  ];
372
1057
 
373
- // src/presets/vite.ts
374
- var VITE_LAYERS = [
375
- { name: "pages", match: (p) => p.startsWith("src/pages/") || p.startsWith("src/views/") },
376
- { name: "components", match: (p) => p.startsWith("src/components/") },
377
- { name: "hooks", match: (p) => p.startsWith("src/hooks/") },
378
- { name: "services", match: (p) => p.startsWith("src/services/") || p.startsWith("src/api/") },
379
- { name: "shared", match: (p) => p.startsWith("src/shared/") || p.startsWith("src/common/") },
380
- { name: "styles", match: (p) => p.startsWith("src/styles/") || p.startsWith("src/assets/") },
381
- { name: "scripts", match: (p) => p.startsWith("scripts/") },
382
- { name: "docs", match: (p) => p.startsWith("docs/") },
383
- {
384
- name: "config",
385
- match: (p) => p.startsWith(".github/") || p.startsWith(".husky/") || p.startsWith(".") || p.includes("config")
386
- },
387
- { name: "root", match: (p) => !p.includes("/") }
388
- ];
389
- var VITE_KINDS = [
390
- { kind: "test", match: (p) => /\.(test|spec)\.[jt]sx?$/.test(p.toLowerCase()) },
391
- {
392
- kind: "component",
393
- match: (p) => {
394
- const base = p.split("/").pop() || "";
395
- return p.includes("/components/") || base.endsWith(".tsx") || base.endsWith(".jsx") || base.endsWith(".vue");
396
- }
397
- },
398
- {
399
- kind: "hook",
400
- match: (p) => {
401
- const base = p.split("/").pop() || "";
402
- return p.includes("/hooks/") || /^use[A-Z]/.test(base);
403
- }
404
- },
405
- { kind: "service", match: (p) => p.includes("service") || p.includes("api") },
406
- { kind: "type", match: (p) => p.endsWith(".d.ts") || p.includes("types") },
407
- { kind: "style", match: (p) => p.endsWith(".css") || p.endsWith(".scss") },
408
- { kind: "data", match: (p) => p.endsWith(".json") || p.endsWith(".csv") },
409
- { kind: "doc", match: (p) => p.endsWith(".md") || p.endsWith(".mdx") },
410
- { kind: "script", match: (p) => p.startsWith("scripts/") || p.endsWith(".sh") || p.endsWith(".ps1") },
411
- {
412
- kind: "config",
413
- match: (p) => {
414
- const base = p.split("/").pop() || "";
415
- return ["package.json", "tsconfig.json", "vite.config.ts", "vite.config.js"].includes(base) || base.startsWith(".");
416
- }
417
- }
418
- ];
419
-
420
1058
  // src/presets/node.ts
421
1059
  var NODE_LAYERS = [
422
1060
  { name: "core", match: (p) => p.startsWith("src/core/") || p.startsWith("core/") },
@@ -453,11 +1091,474 @@ var NODE_KINDS = [
453
1091
  kind: "config",
454
1092
  match: (p) => {
455
1093
  const base = p.split("/").pop() || "";
456
- return ["package.json", "tsconfig.json", "Dockerfile"].includes(base) || base.startsWith(".");
1094
+ return ["package.json", "tsconfig.json", "Dockerfile"].includes(base) || base.startsWith(".");
1095
+ }
1096
+ }
1097
+ ];
1098
+
1099
+ // src/presets/vite.ts
1100
+ var VITE_LAYERS = [
1101
+ { name: "pages", match: (p) => p.startsWith("src/pages/") || p.startsWith("src/views/") },
1102
+ { name: "components", match: (p) => p.startsWith("src/components/") },
1103
+ { name: "hooks", match: (p) => p.startsWith("src/hooks/") },
1104
+ { name: "services", match: (p) => p.startsWith("src/services/") || p.startsWith("src/api/") },
1105
+ { name: "shared", match: (p) => p.startsWith("src/shared/") || p.startsWith("src/common/") },
1106
+ { name: "styles", match: (p) => p.startsWith("src/styles/") || p.startsWith("src/assets/") },
1107
+ { name: "scripts", match: (p) => p.startsWith("scripts/") },
1108
+ { name: "docs", match: (p) => p.startsWith("docs/") },
1109
+ {
1110
+ name: "config",
1111
+ match: (p) => p.startsWith(".github/") || p.startsWith(".husky/") || p.startsWith(".") || p.includes("config")
1112
+ },
1113
+ { name: "root", match: (p) => !p.includes("/") }
1114
+ ];
1115
+ var VITE_KINDS = [
1116
+ { kind: "test", match: (p) => /\.(test|spec)\.[jt]sx?$/.test(p.toLowerCase()) },
1117
+ {
1118
+ kind: "component",
1119
+ match: (p) => {
1120
+ const base = p.split("/").pop() || "";
1121
+ return p.includes("/components/") || base.endsWith(".tsx") || base.endsWith(".jsx") || base.endsWith(".vue");
1122
+ }
1123
+ },
1124
+ {
1125
+ kind: "hook",
1126
+ match: (p) => {
1127
+ const base = p.split("/").pop() || "";
1128
+ return p.includes("/hooks/") || /^use[A-Z]/.test(base);
1129
+ }
1130
+ },
1131
+ { kind: "service", match: (p) => p.includes("service") || p.includes("api") },
1132
+ { kind: "type", match: (p) => p.endsWith(".d.ts") || p.includes("types") },
1133
+ { kind: "style", match: (p) => p.endsWith(".css") || p.endsWith(".scss") },
1134
+ { kind: "data", match: (p) => p.endsWith(".json") || p.endsWith(".csv") },
1135
+ { kind: "doc", match: (p) => p.endsWith(".md") || p.endsWith(".mdx") },
1136
+ { kind: "script", match: (p) => p.startsWith("scripts/") || p.endsWith(".sh") || p.endsWith(".ps1") },
1137
+ {
1138
+ kind: "config",
1139
+ match: (p) => {
1140
+ const base = p.split("/").pop() || "";
1141
+ return ["package.json", "tsconfig.json", "vite.config.ts", "vite.config.js"].includes(base) || base.startsWith(".");
457
1142
  }
458
1143
  }
459
1144
  ];
460
1145
 
1146
+ // src/presets/js-presets.ts
1147
+ var NEXT_LIKE_EXCLUDES = [".svelte-kit", ".astro", ".vite", ".output"];
1148
+ var JS_PRESETS = [
1149
+ {
1150
+ name: "nextjs",
1151
+ priority: 100,
1152
+ detect: (context) => hasFileStartingWith(context, "next.config") || hasDependency(context, "next"),
1153
+ layers: NEXTJS_LAYERS,
1154
+ kinds: NEXTJS_KINDS,
1155
+ exclude: NEXT_LIKE_EXCLUDES,
1156
+ recommendedBudget: "300k"
1157
+ },
1158
+ {
1159
+ name: "nuxt",
1160
+ priority: 98,
1161
+ detect: (context) => hasFileStartingWith(context, "nuxt.config") || hasDependency(context, "nuxt", "nuxt3"),
1162
+ layers: [
1163
+ layer("pages", "pages/", "src/pages/"),
1164
+ layer("components", "components/", "src/components/"),
1165
+ layer("composables", "composables/", "src/composables/"),
1166
+ layer("server", "server/", "src/server/", "api/"),
1167
+ layer("stores", "stores/", "src/stores/"),
1168
+ layer("layouts", "layouts/", "src/layouts/"),
1169
+ ...commonLayers()
1170
+ ],
1171
+ kinds: [
1172
+ kindOf("api-route", "server/api/", "server/routes/"),
1173
+ suffixKind("component", ".vue"),
1174
+ kindOf("composable", "composables/"),
1175
+ kindOf("store", "stores/", "store/"),
1176
+ kindOf("layout", "layouts/"),
1177
+ ...commonKinds()
1178
+ ],
1179
+ exclude: [".nuxt", ".output"],
1180
+ recommendedBudget: "200k"
1181
+ },
1182
+ {
1183
+ name: "sveltekit",
1184
+ priority: 96,
1185
+ detect: (context) => hasFileStartingWith(context, "svelte.config") || hasDependency(context, "@sveltejs/kit", "svelte"),
1186
+ layers: [
1187
+ layer("routes", "src/routes/", "routes/"),
1188
+ layer("lib", "src/lib/", "lib/"),
1189
+ layer("components", "src/lib/components/", "src/components/"),
1190
+ layer("stores", "src/lib/stores/", "src/stores/"),
1191
+ ...commonLayers()
1192
+ ],
1193
+ kinds: [
1194
+ kindOf("route", "+page.", "+server.", "+layout."),
1195
+ suffixKind("component", ".svelte"),
1196
+ kindOf("store", "stores/"),
1197
+ kindOf("params", "params/"),
1198
+ ...commonKinds()
1199
+ ],
1200
+ exclude: [".svelte-kit", ".vercel"],
1201
+ recommendedBudget: "200k"
1202
+ },
1203
+ {
1204
+ name: "remix",
1205
+ priority: 94,
1206
+ detect: (context) => hasDependency(context, "@remix-run/react", "@remix-run/node", "@remix-run/dev"),
1207
+ layers: [
1208
+ layer("routes", "app/routes/", "routes/"),
1209
+ layer("components", "app/components/"),
1210
+ layer("lib", "app/lib/", "app/utils/"),
1211
+ ...commonLayers()
1212
+ ],
1213
+ kinds: [kindOf("route", "routes/"), ...commonKinds()],
1214
+ exclude: [".cache", "public/build"],
1215
+ recommendedBudget: "200k"
1216
+ },
1217
+ {
1218
+ name: "astro",
1219
+ priority: 92,
1220
+ detect: (context) => hasFileStartingWith(context, "astro.config") || hasDependency(context, "astro"),
1221
+ layers: [
1222
+ layer("pages", "src/pages/"),
1223
+ layer("layouts", "src/layouts/"),
1224
+ layer("components", "src/components/"),
1225
+ layer("content", "src/content/"),
1226
+ ...commonLayers()
1227
+ ],
1228
+ kinds: [suffixKind("page", ".astro"), kindOf("layout", "layouts/"), ...commonKinds()],
1229
+ exclude: [".astro", "dist"],
1230
+ recommendedBudget: "150k"
1231
+ },
1232
+ {
1233
+ name: "angular",
1234
+ priority: 90,
1235
+ detect: (context) => hasFile(context, "angular.json") || hasDependency(context, "@angular/core"),
1236
+ layers: [
1237
+ layer("app", "src/app/"),
1238
+ layer("features", "src/app/features/"),
1239
+ layer("shared", "src/app/shared/"),
1240
+ layer("core", "src/app/core/"),
1241
+ layer("assets", "src/assets/"),
1242
+ ...commonLayers()
1243
+ ],
1244
+ kinds: [
1245
+ suffixKind("component", ".component.ts"),
1246
+ suffixKind("service", ".service.ts"),
1247
+ suffixKind("module", ".module.ts"),
1248
+ suffixKind("guard", ".guard.ts"),
1249
+ suffixKind("pipe", ".pipe.ts"),
1250
+ suffixKind("directive", ".directive.ts"),
1251
+ ...commonKinds()
1252
+ ],
1253
+ exclude: [".angular", "dist"],
1254
+ recommendedBudget: "200k"
1255
+ },
1256
+ {
1257
+ name: "solid",
1258
+ priority: 88,
1259
+ detect: (context) => hasDependency(context, "solid-js", "solid-start"),
1260
+ layers: [
1261
+ layer("pages", "src/routes/", "src/pages/"),
1262
+ layer("components", "src/components/"),
1263
+ ...commonLayers()
1264
+ ],
1265
+ kinds: [kindOf("route", "routes/"), ...commonKinds()],
1266
+ exclude: [".solid", "dist"],
1267
+ recommendedBudget: "120k"
1268
+ },
1269
+ {
1270
+ name: "vue-vite",
1271
+ priority: 86,
1272
+ detect: (context) => hasDependency(context, "vue") && (hasDependency(context, "vite") || hasFileStartingWith(context, "vite.config")),
1273
+ layers: [
1274
+ layer("pages", "src/pages/", "src/views/"),
1275
+ layer("components", "src/components/"),
1276
+ layer("composables", "src/composables/", "src/hooks/"),
1277
+ layer("stores", "src/stores/", "src/store/"),
1278
+ layer("services", "src/services/", "src/api/"),
1279
+ layer("shared", "src/shared/", "src/common/"),
1280
+ layer("styles", "src/styles/", "src/assets/"),
1281
+ ...commonLayers()
1282
+ ],
1283
+ kinds: [
1284
+ suffixKind("component", ".vue"),
1285
+ kindOf("composable", "composables/", "use"),
1286
+ kindOf("store", "stores/", "store/"),
1287
+ kindOf("service", "services/", "api/"),
1288
+ suffixKind("style", ".css", ".scss", ".sass", ".less"),
1289
+ ...commonKinds()
1290
+ ],
1291
+ exclude: [".vite", "dist"],
1292
+ recommendedBudget: "120k"
1293
+ },
1294
+ {
1295
+ name: "react-vite",
1296
+ priority: 84,
1297
+ detect: (context) => hasDependency(context, "react") && (hasDependency(context, "vite") || hasFileStartingWith(context, "vite.config")),
1298
+ layers: VITE_LAYERS,
1299
+ kinds: VITE_KINDS,
1300
+ exclude: [".vite", "dist"],
1301
+ recommendedBudget: "120k"
1302
+ },
1303
+ {
1304
+ name: "nest",
1305
+ priority: 80,
1306
+ detect: (context) => hasDependency(context, "@nestjs/core"),
1307
+ layers: [
1308
+ layer("modules", "src/modules/", "modules/"),
1309
+ layer("controllers", "src/controllers/", "controllers/"),
1310
+ layer("providers", "src/providers/", "providers/"),
1311
+ layer("core", "src/core/"),
1312
+ layer("security", "src/security/", "src/auth/"),
1313
+ layer("db", "src/db/", "src/database/", "prisma/"),
1314
+ ...commonLayers()
1315
+ ],
1316
+ kinds: [
1317
+ suffixKind("controller", ".controller.ts"),
1318
+ suffixKind("service", ".service.ts"),
1319
+ suffixKind("module", ".module.ts"),
1320
+ suffixKind("guard", ".guard.ts"),
1321
+ suffixKind("dto", ".dto.ts"),
1322
+ suffixKind("entity", ".entity.ts"),
1323
+ kindOf("migration", "migrations/"),
1324
+ ...commonKinds()
1325
+ ],
1326
+ exclude: ["dist", "node_modules"],
1327
+ recommendedBudget: "200k"
1328
+ },
1329
+ {
1330
+ name: "node-express",
1331
+ priority: 78,
1332
+ detect: (context) => hasDependency(context, "express", "fastify", "koa", "@hapi/hapi"),
1333
+ layers: NODE_LAYERS,
1334
+ kinds: NODE_KINDS,
1335
+ exclude: ["dist"],
1336
+ recommendedBudget: "200k"
1337
+ },
1338
+ {
1339
+ name: "node-lib",
1340
+ // A bare package.json is often just tooling around a non-JS project, so it
1341
+ // ranks below every definitive language manifest.
1342
+ priority: 45,
1343
+ detect: (context) => hasFile(context, "package.json"),
1344
+ layers: [
1345
+ layer("src", "src/"),
1346
+ layer("lib", "lib/"),
1347
+ layer("test", "test/", "tests/", "__tests__/"),
1348
+ ...commonLayers()
1349
+ ],
1350
+ kinds: [suffixKind("type", ".d.ts"), ...commonKinds()],
1351
+ exclude: ["dist"],
1352
+ recommendedBudget: "120k"
1353
+ }
1354
+ ];
1355
+
1356
+ // src/presets/lang-presets.ts
1357
+ var anyFileEndingWith = (context, suffix) => Array.from(context.files).some((file) => file.endsWith(suffix));
1358
+ var DIR = (name) => `${name}/`;
1359
+ var LANGUAGE_PRESETS = [
1360
+ {
1361
+ name: "python-django",
1362
+ priority: 56,
1363
+ detect: (context) => hasFile(context, "manage.py") || manifestContains(context, "pyproject.toml", "django") || manifestContains(context, "requirements.txt", "django"),
1364
+ layers: [
1365
+ layer("views", DIR("views"), DIR("app/views"), DIR("apps/views")),
1366
+ layer("models", DIR("models"), DIR("app/models"), DIR("apps/models")),
1367
+ layer("serializers", DIR("serializers")),
1368
+ layer("urls", "urls.py"),
1369
+ layer("migrations", DIR("migrations")),
1370
+ layer("settings", "settings.py", "settings/"),
1371
+ layer("templates", DIR("templates")),
1372
+ ...commonLayers()
1373
+ ],
1374
+ kinds: [
1375
+ suffixKind("model", "models.py"),
1376
+ suffixKind("view", "views.py"),
1377
+ suffixKind("serializer", "serializers.py"),
1378
+ suffixKind("urlconf", "urls.py"),
1379
+ kindOf("migration", "migrations/"),
1380
+ suffixKind("settings", "settings.py"),
1381
+ ...commonKinds()
1382
+ ],
1383
+ exclude: ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache", ".tox", ".eggs", "staticfiles", "media"],
1384
+ recommendedBudget: "150k"
1385
+ },
1386
+ {
1387
+ name: "python-fastapi",
1388
+ priority: 54,
1389
+ detect: (context) => manifestContains(context, "requirements.txt", "fastapi") || manifestContains(context, "requirements.txt", "flask") || manifestContains(context, "pyproject.toml", "fastapi") || manifestContains(context, "pyproject.toml", "flask"),
1390
+ layers: [
1391
+ layer("routers", DIR("routers"), DIR("routes"), DIR("api")),
1392
+ layer("models", DIR("models"), DIR("db")),
1393
+ layer("schemas", DIR("schemas")),
1394
+ layer("services", DIR("services")),
1395
+ layer("core", DIR("core")),
1396
+ layer("tests", DIR("tests")),
1397
+ ...commonLayers()
1398
+ ],
1399
+ kinds: [
1400
+ kindOf("router", "routers/", "routes/", "api/"),
1401
+ kindOf("model", "models/"),
1402
+ kindOf("schema", "schemas/"),
1403
+ kindOf("service", "services/"),
1404
+ ...commonKinds()
1405
+ ],
1406
+ exclude: ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache", ".tox", ".eggs"],
1407
+ recommendedBudget: "150k"
1408
+ },
1409
+ {
1410
+ name: "python-lib",
1411
+ // Below node-lib: a Node repo with a stray .py file is still a Node repo.
1412
+ priority: 30,
1413
+ detect: (context) => hasFile(context, "pyproject.toml") || hasFile(context, "setup.py") || hasFile(context, "requirements.txt") || anyFileEndingWith(context, ".py"),
1414
+ layers: [
1415
+ layer("package", DIR("src"), DIR("lib")),
1416
+ layer("tests", DIR("tests"), DIR("test")),
1417
+ ...commonLayers()
1418
+ ],
1419
+ kinds: [suffixKind("module", ".py"), ...commonKinds()],
1420
+ exclude: ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache", ".tox", ".eggs"],
1421
+ recommendedBudget: "120k"
1422
+ },
1423
+ {
1424
+ name: "go",
1425
+ priority: 50,
1426
+ detect: (context) => hasFile(context, "go.mod"),
1427
+ layers: [
1428
+ layer("cmd", "cmd/"),
1429
+ layer("internal", "internal/"),
1430
+ layer("pkg", "pkg/"),
1431
+ layer("api", "api/", "proto/"),
1432
+ layer("migrations", "migrations/"),
1433
+ ...commonLayers()
1434
+ ],
1435
+ kinds: [
1436
+ kindOf("main", "cmd/"),
1437
+ kindOf("handler", "handler", "server", "http"),
1438
+ kindOf("service", "service"),
1439
+ kindOf("repository", "repository", "store", "repo"),
1440
+ kindOf("model", "model", "entity"),
1441
+ suffixKind("test", "_test.go"),
1442
+ ...commonKinds()
1443
+ ],
1444
+ exclude: ["vendor", "testdata"],
1445
+ recommendedBudget: "200k"
1446
+ },
1447
+ {
1448
+ name: "rust",
1449
+ priority: 48,
1450
+ detect: (context) => hasFile(context, "Cargo.toml"),
1451
+ layers: [
1452
+ layer("src", "src/"),
1453
+ layer("crates", "crates/"),
1454
+ layer("bins", "src/bin/"),
1455
+ layer("tests", "tests/"),
1456
+ layer("benches", "benches/"),
1457
+ ...commonLayers()
1458
+ ],
1459
+ kinds: [suffixKind("module", ".rs"), suffixKind("test", "_test.rs"), ...commonKinds()],
1460
+ exclude: ["target"],
1461
+ recommendedBudget: "200k"
1462
+ },
1463
+ {
1464
+ name: "java-spring",
1465
+ priority: 46,
1466
+ detect: (context) => hasFile(context, "pom.xml") || hasFile(context, "build.gradle") || hasFile(context, "build.gradle.kts"),
1467
+ layers: [
1468
+ layer("api", "src/main/java/"),
1469
+ layer("test", "src/test/java/"),
1470
+ layer("resources", "src/main/resources/"),
1471
+ layer("migrations", "src/main/resources/db/"),
1472
+ ...commonLayers()
1473
+ ],
1474
+ kinds: [
1475
+ kindOf("controller", "controller"),
1476
+ kindOf("service", "service"),
1477
+ kindOf("repository", "repository", "dao"),
1478
+ kindOf("entity", "entity", "model"),
1479
+ kindOf("dto", "dto", "request", "response"),
1480
+ kindOf("migration", "migration", "db/migration"),
1481
+ ...commonKinds()
1482
+ ],
1483
+ exclude: [".gradle", "out", "target"],
1484
+ recommendedBudget: "200k"
1485
+ },
1486
+ {
1487
+ name: "dotnet",
1488
+ priority: 44,
1489
+ detect: (context) => anyFileEndingWith(context, ".csproj") || anyFileEndingWith(context, ".sln") || hasFile(context, "global.json"),
1490
+ layers: [
1491
+ layer("web", "src/", "Web/"),
1492
+ layer("domain", "Domain/", "Core/"),
1493
+ layer("infrastructure", "Infrastructure/", "Data/"),
1494
+ layer("tests", "Tests/", "test/"),
1495
+ ...commonLayers()
1496
+ ],
1497
+ kinds: [
1498
+ kindOf("controller", "Controller"),
1499
+ kindOf("service", "Service"),
1500
+ kindOf("repository", "Repository"),
1501
+ kindOf("model", "Model", "Entity"),
1502
+ kindOf("dto", "Dto", "Request", "Response"),
1503
+ suffixKind("test", "Tests.cs"),
1504
+ ...commonKinds()
1505
+ ],
1506
+ exclude: ["bin", "obj", ".vs"],
1507
+ recommendedBudget: "200k"
1508
+ },
1509
+ {
1510
+ name: "php-laravel",
1511
+ priority: 42,
1512
+ detect: (context) => hasFile(context, "artisan") || manifestContains(context, "composer.json", "laravel") || hasFile(context, "composer.json"),
1513
+ layers: [
1514
+ layer("app", "app/"),
1515
+ layer("routes", "routes/"),
1516
+ layer("http", "app/Http/"),
1517
+ layer("models", "app/Models/"),
1518
+ layer("providers", "app/Providers/"),
1519
+ layer("database", "database/"),
1520
+ layer("tests", "tests/"),
1521
+ ...commonLayers()
1522
+ ],
1523
+ kinds: [
1524
+ kindOf("controller", "Http/Controllers/"),
1525
+ kindOf("model", "Models/"),
1526
+ kindOf("migration", "migrations/"),
1527
+ kindOf("middleware", "Middleware/"),
1528
+ kindOf("request", "Requests/"),
1529
+ kindOf("provider", "Providers/"),
1530
+ ...commonKinds()
1531
+ ],
1532
+ exclude: ["vendor", "storage", "bootstrap/cache", "public/build"],
1533
+ recommendedBudget: "150k"
1534
+ },
1535
+ {
1536
+ name: "ruby-rails",
1537
+ priority: 40,
1538
+ detect: (context) => hasFile(context, "Gemfile") || hasFile(context, "config.ru") || hasFile(context, "Rakefile"),
1539
+ layers: [
1540
+ layer("app", "app/"),
1541
+ layer("controllers", "app/controllers/"),
1542
+ layer("models", "app/models/"),
1543
+ layer("config", "config/"),
1544
+ layer("db", "db/"),
1545
+ layer("spec", "spec/", "test/"),
1546
+ ...commonLayers()
1547
+ ],
1548
+ kinds: [
1549
+ kindOf("controller", "controllers/"),
1550
+ kindOf("model", "models/"),
1551
+ kindOf("migration", "migrate/"),
1552
+ kindOf("job", "jobs/"),
1553
+ kindOf("mailer", "mailers/"),
1554
+ suffixKind("test", "_spec.rb", "_test.rb"),
1555
+ ...commonKinds()
1556
+ ],
1557
+ exclude: [".bundle", "log", "tmp", "vendor/bundle", "public/assets"],
1558
+ recommendedBudget: "150k"
1559
+ }
1560
+ ];
1561
+
461
1562
  // src/presets/default.ts
462
1563
  var DEFAULT_LAYERS = [
463
1564
  { name: "src", match: (p) => p.startsWith("src/") },
@@ -500,50 +1601,110 @@ var DEFAULT_KINDS = [
500
1601
  }
501
1602
  ];
502
1603
 
503
- // src/presets/index.ts
504
- function detectPreset(cwd) {
505
- const hasNextConfig = fs8__namespace.existsSync(path9__namespace.join(cwd, "next.config.ts")) || fs8__namespace.existsSync(path9__namespace.join(cwd, "next.config.js")) || fs8__namespace.existsSync(path9__namespace.join(cwd, "next.config.mjs"));
506
- const hasViteConfig = fs8__namespace.existsSync(path9__namespace.join(cwd, "vite.config.ts")) || fs8__namespace.existsSync(path9__namespace.join(cwd, "vite.config.js")) || fs8__namespace.existsSync(path9__namespace.join(cwd, "vite.config.mjs"));
507
- const pkgJsonPath = path9__namespace.join(cwd, "package.json");
508
- let pkgDependencies = {};
509
- if (fs8__namespace.existsSync(pkgJsonPath)) {
510
- try {
511
- const parsed = JSON.parse(fs8__namespace.readFileSync(pkgJsonPath, "utf8"));
512
- pkgDependencies = {
513
- ...parsed.dependencies || {},
514
- ...parsed.devDependencies || {}
515
- };
516
- } catch {
517
- pkgDependencies = {};
518
- }
519
- }
520
- if (hasNextConfig || pkgDependencies["next"]) {
521
- return {
522
- presetName: "nextjs",
523
- layers: NEXTJS_LAYERS,
524
- kinds: NEXTJS_KINDS
525
- };
1604
+ // src/presets/shape-presets.ts
1605
+ var SHAPE_PRESETS = [
1606
+ {
1607
+ name: "monorepo",
1608
+ priority: 70,
1609
+ detect: (context) => hasFile(context, "pnpm-workspace.yaml") || hasFile(context, "nx.json") || hasFile(context, "turbo.json") || hasFile(context, "lerna.json") || manifestContains(context, "package.json", '"workspaces"'),
1610
+ layers: [
1611
+ layer("apps", "apps/"),
1612
+ layer("packages", "packages/"),
1613
+ layer("tooling", "tooling/", "tools/", "scripts/"),
1614
+ ...commonLayers()
1615
+ ],
1616
+ kinds: commonKinds(),
1617
+ exclude: [".turbo", ".nx"],
1618
+ recommendedBudget: "200k"
1619
+ },
1620
+ {
1621
+ name: "infra",
1622
+ priority: 24,
1623
+ detect: (context) => Array.from(context.files).some((file) => file.endsWith(".tf")) || hasFile(context, "Chart.yaml") || hasFile(context, "kustomization.yaml") || hasFile(context, "docker-compose.yml") || hasFile(context, "docker-compose.yaml"),
1624
+ layers: [
1625
+ layer("modules", "modules/"),
1626
+ layer("environments", "env/", "environments/"),
1627
+ layer("manifests", "k8s/", "helm/", "charts/", "deploy/"),
1628
+ ...commonLayers()
1629
+ ],
1630
+ kinds: [
1631
+ { kind: "terraform", match: (filePath) => filePath.endsWith(".tf") },
1632
+ { kind: "kubernetes", match: (filePath) => /\.(ya?ml)$/i.test(filePath) && /k8s|helm|charts|deploy/.test(filePath) },
1633
+ ...commonKinds()
1634
+ ],
1635
+ exclude: [".terraform", ".terragrunt-cache"],
1636
+ recommendedBudget: "100k"
1637
+ },
1638
+ {
1639
+ name: "docs",
1640
+ priority: 22,
1641
+ detect: (context) => {
1642
+ const markdown = Array.from(context.files).filter((file) => /\.mdx?$/i.test(file)).length;
1643
+ return markdown >= 3 && !hasFile(context, "package.json");
1644
+ },
1645
+ layers: [
1646
+ layer("content", "content/", "docs/"),
1647
+ layer("assets", "assets/", "static/", "public/"),
1648
+ ...commonLayers()
1649
+ ],
1650
+ kinds: commonKinds(),
1651
+ exclude: [".docusaurus", ".vitepress/dist", "site"],
1652
+ recommendedBudget: "100k"
1653
+ },
1654
+ {
1655
+ name: "generic",
1656
+ priority: 0,
1657
+ detect: () => true,
1658
+ layers: DEFAULT_LAYERS,
1659
+ kinds: DEFAULT_KINDS,
1660
+ exclude: [],
1661
+ recommendedBudget: "120k"
526
1662
  }
527
- if (hasViteConfig || pkgDependencies["vite"]) {
528
- return {
529
- presetName: "vite",
530
- layers: VITE_LAYERS,
531
- kinds: VITE_KINDS
532
- };
1663
+ ];
1664
+
1665
+ // src/presets/registry.ts
1666
+ function compare(a, b) {
1667
+ if (b.priority !== a.priority) return b.priority - a.priority;
1668
+ return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
1669
+ }
1670
+ var PRESETS = [
1671
+ ...JS_PRESETS,
1672
+ ...LANGUAGE_PRESETS,
1673
+ ...SHAPE_PRESETS
1674
+ ].sort(compare);
1675
+ var BY_NAME = new Map(PRESETS.map((preset) => [preset.name, preset]));
1676
+ function listPresetNames() {
1677
+ return PRESETS.map((preset) => preset.name);
1678
+ }
1679
+ function getPreset(name) {
1680
+ return BY_NAME.get(name);
1681
+ }
1682
+ function unknownPresetMessage(name) {
1683
+ return `Unknown preset "${name}". Expected one of: ${listPresetNames().join(", ")}.`;
1684
+ }
1685
+ function detectPresetFromContext(context, forced) {
1686
+ if (forced !== void 0) {
1687
+ const preset = BY_NAME.get(forced);
1688
+ if (!preset) throw new Error(unknownPresetMessage(forced));
1689
+ return toDetected(preset);
533
1690
  }
534
- if (pkgDependencies["express"] || pkgDependencies["fastify"] || pkgDependencies["@nestjs/core"] || fs8__namespace.existsSync(path9__namespace.join(cwd, "src/core")) || fs8__namespace.existsSync(path9__namespace.join(cwd, "src/protocols"))) {
535
- return {
536
- presetName: "node",
537
- layers: NODE_LAYERS,
538
- kinds: NODE_KINDS
539
- };
1691
+ for (const preset of PRESETS) {
1692
+ if (preset.detect(context)) return toDetected(preset);
540
1693
  }
1694
+ return toDetected(PRESETS[PRESETS.length - 1]);
1695
+ }
1696
+ function toDetected(preset) {
541
1697
  return {
542
- presetName: "default",
543
- layers: DEFAULT_LAYERS,
544
- kinds: DEFAULT_KINDS
1698
+ presetName: preset.name,
1699
+ layers: preset.layers,
1700
+ kinds: preset.kinds,
1701
+ exclude: preset.exclude ? [...preset.exclude] : [],
1702
+ recommendedBudget: preset.recommendedBudget ?? null
545
1703
  };
546
1704
  }
1705
+ function detectPreset(cwd, forced) {
1706
+ return detectPresetFromContext(buildPresetContext(cwd), forced);
1707
+ }
547
1708
  function getGitInfo(cwd, fallbackBranch = "main") {
548
1709
  try {
549
1710
  const branch = childProcess__namespace.execSync("git rev-parse --abbrev-ref HEAD", {
@@ -678,10 +1839,10 @@ function matchesExclude(relativePath, pattern) {
678
1839
  return posixPath === cleaned || posixPath.startsWith(`${cleaned}/`) || posixPath.includes(`/${cleaned}/`) || posixPath.endsWith(`/${cleaned}`);
679
1840
  }
680
1841
  function readGitignoreMatcher(dir) {
681
- const gitignorePath = path9__namespace.join(dir, ".gitignore");
1842
+ const gitignorePath = path10__namespace.join(dir, ".gitignore");
682
1843
  let raw;
683
1844
  try {
684
- raw = fs8__namespace.readFileSync(gitignorePath, "utf8");
1845
+ raw = fs9__namespace.readFileSync(gitignorePath, "utf8");
685
1846
  } catch {
686
1847
  return () => false;
687
1848
  }
@@ -784,7 +1945,7 @@ function walkDirectory(dir, baseDir, visited) {
784
1945
  const results = { files: [], skipped: [] };
785
1946
  let realDir;
786
1947
  try {
787
- realDir = fs8__namespace.realpathSync(dir);
1948
+ realDir = fs9__namespace.realpathSync(dir);
788
1949
  } catch {
789
1950
  realDir = dir;
790
1951
  }
@@ -793,21 +1954,21 @@ function walkDirectory(dir, baseDir, visited) {
793
1954
  const ignoredByGitignore = dir === baseDir ? readGitignoreMatcher(baseDir) : () => false;
794
1955
  let entries;
795
1956
  try {
796
- entries = fs8__namespace.readdirSync(dir, { withFileTypes: true });
1957
+ entries = fs9__namespace.readdirSync(dir, { withFileTypes: true });
797
1958
  } catch {
798
1959
  results.skipped.push({ path: dir, reason: "read-error", detail: "directory unreadable" });
799
1960
  return results;
800
1961
  }
801
1962
  for (const entry of entries) {
802
1963
  if (DEFAULT_IGNORED_DIRS.includes(entry.name)) continue;
803
- const fullPath = path9__namespace.join(dir, entry.name);
804
- const relPath = path9__namespace.relative(baseDir, fullPath).replace(/\\/g, "/");
1964
+ const fullPath = path10__namespace.join(dir, entry.name);
1965
+ const relPath = path10__namespace.relative(baseDir, fullPath).replace(/\\/g, "/");
805
1966
  if (ignoredByGitignore(relPath)) continue;
806
1967
  let isDirectory = entry.isDirectory();
807
1968
  let isFile = entry.isFile();
808
1969
  if (entry.isSymbolicLink()) {
809
1970
  try {
810
- const stats = fs8__namespace.statSync(fullPath);
1971
+ const stats = fs9__namespace.statSync(fullPath);
811
1972
  isDirectory = stats.isDirectory();
812
1973
  isFile = stats.isFile();
813
1974
  if (!isDirectory && !isFile) {
@@ -1072,6 +2233,8 @@ function normalizeResolvedConfig(config) {
1072
2233
  config.includeMonolith === true
1073
2234
  ),
1074
2235
  include: resolveInclude(config.include),
2236
+ preset: typeof config.preset === "string" ? config.preset : "generic",
2237
+ presetExclude: Array.isArray(config.presetExclude) ? config.presetExclude : [],
1075
2238
  layersMode: config.layersMode === "replace" ? "replace" : "merge",
1076
2239
  budgets: config.budgets && typeof config.budgets === "object" ? config.budgets : { default: config.maxPartBytes, byLayer: {} },
1077
2240
  hooks: resolveHooks(config.hooks),
@@ -1119,18 +2282,22 @@ function parseJsonish(raw, file) {
1119
2282
  }
1120
2283
  async function loadConfigFileDetailed(cwd) {
1121
2284
  const warnings = [];
2285
+ const errors = [];
1122
2286
  for (const filename of CONFIG_FILENAMES) {
1123
- const fullPath = path9__namespace.join(cwd, filename);
1124
- if (!fs8__namespace.existsSync(fullPath)) continue;
2287
+ const fullPath = path10__namespace.join(cwd, filename);
2288
+ if (!fs9__namespace.existsSync(fullPath)) continue;
1125
2289
  if (filename.endsWith(".json") || filename === ".snapshotrc") {
1126
2290
  try {
1127
2291
  return {
1128
- config: parseJsonish(fs8__namespace.readFileSync(fullPath, "utf8"), fullPath),
2292
+ config: parseJsonish(fs9__namespace.readFileSync(fullPath, "utf8"), fullPath),
1129
2293
  file: fullPath,
1130
- warnings
2294
+ warnings,
2295
+ errors
1131
2296
  };
1132
2297
  } catch (err) {
1133
- warnings.push(err.message);
2298
+ const message = err.message;
2299
+ warnings.push(message);
2300
+ errors.push(message);
1134
2301
  continue;
1135
2302
  }
1136
2303
  }
@@ -1144,28 +2311,34 @@ async function loadConfigFileDetailed(cwd) {
1144
2311
  const imported = await import(url.pathToFileURL(fullPath).href);
1145
2312
  const config = imported.default ?? imported;
1146
2313
  if (!config || typeof config !== "object") {
1147
- warnings.push(`${fullPath} does not export a configuration object.`);
2314
+ const message = `${fullPath} does not export a configuration object.`;
2315
+ warnings.push(message);
2316
+ errors.push(message);
1148
2317
  continue;
1149
2318
  }
1150
- return { config, file: fullPath, warnings };
2319
+ return { config, file: fullPath, warnings, errors };
1151
2320
  } catch (err) {
1152
- warnings.push(`Could not load ${fullPath}: ${err.message}`);
2321
+ const message = `Could not load ${fullPath}: ${err.message}`;
2322
+ warnings.push(message);
2323
+ errors.push(message);
1153
2324
  continue;
1154
2325
  }
1155
2326
  }
1156
- const pkgJsonPath = path9__namespace.join(cwd, "package.json");
1157
- if (fs8__namespace.existsSync(pkgJsonPath)) {
2327
+ const pkgJsonPath = path10__namespace.join(cwd, "package.json");
2328
+ if (fs9__namespace.existsSync(pkgJsonPath)) {
1158
2329
  try {
1159
- const parsed = parseJsonish(fs8__namespace.readFileSync(pkgJsonPath, "utf8"), pkgJsonPath);
2330
+ const parsed = parseJsonish(fs9__namespace.readFileSync(pkgJsonPath, "utf8"), pkgJsonPath);
1160
2331
  const snapshotKey = parsed.snapshot;
1161
2332
  if (snapshotKey && typeof snapshotKey === "object") {
1162
- return { config: snapshotKey, file: pkgJsonPath, warnings };
2333
+ return { config: snapshotKey, file: pkgJsonPath, warnings, errors };
1163
2334
  }
1164
2335
  } catch (err) {
1165
- warnings.push(err.message);
2336
+ const message = err.message;
2337
+ warnings.push(message);
2338
+ errors.push(message);
1166
2339
  }
1167
2340
  }
1168
- return { config: null, file: null, warnings };
2341
+ return { config: null, file: null, warnings, errors };
1169
2342
  }
1170
2343
  async function loadConfigFile(cwd) {
1171
2344
  const loaded = await loadConfigFileDetailed(cwd);
@@ -1264,7 +2437,9 @@ function boundedBytes(value, fallback, minimum, label) {
1264
2437
  `${label} must be a byte count such as 120000, "120k" or "1.5mb" (received ${describeBytes(value)}).`
1265
2438
  );
1266
2439
  }
1267
- if (parsed <= 0) return fallback;
2440
+ if (parsed <= 0) {
2441
+ throw new Error(`${label} must be a positive byte count (received ${describeBytes(value)}).`);
2442
+ }
1268
2443
  if (parsed < minimum) {
1269
2444
  throw new Error(`${label} must be at least ${minimum} bytes (received ${describeBytes(value)}).`);
1270
2445
  }
@@ -1304,12 +2479,12 @@ function resolveBudgets(budgets, fallback) {
1304
2479
  if (typeof budgets.byLayer !== "object" || budgets.byLayer === null) {
1305
2480
  throw new Error("`budgets.byLayer` must be an object of layer name to byte size.");
1306
2481
  }
1307
- for (const [layer, value] of Object.entries(budgets.byLayer)) {
1308
- byLayer[layer] = boundedBytes(
2482
+ for (const [layer2, value] of Object.entries(budgets.byLayer)) {
2483
+ byLayer[layer2] = boundedBytes(
1309
2484
  value,
1310
2485
  fallback,
1311
2486
  MIN_MAX_PART_BYTES,
1312
- `budgets.byLayer["${layer}"]`
2487
+ `budgets.byLayer["${layer2}"]`
1313
2488
  );
1314
2489
  }
1315
2490
  }
@@ -1330,18 +2505,22 @@ function resolveHooks(hooks) {
1330
2505
  }
1331
2506
  function resolveCandidateCwd(userConfig, cliOptions, baseDir) {
1332
2507
  const candidate = cliOptions?.cwd || userConfig?.cwd || baseDir || process.cwd();
1333
- const resolved = path9__namespace.resolve(candidate);
1334
- if (!fs8__namespace.existsSync(resolved)) {
2508
+ const resolved = path10__namespace.resolve(candidate);
2509
+ if (!fs9__namespace.existsSync(resolved)) {
1335
2510
  throw new Error(`Working directory does not exist: ${resolved}`);
1336
2511
  }
1337
- if (!fs8__namespace.statSync(resolved).isDirectory()) {
2512
+ if (!fs9__namespace.statSync(resolved).isDirectory()) {
1338
2513
  throw new Error(`Working directory is not a directory: ${resolved}`);
1339
2514
  }
1340
2515
  return resolved;
1341
2516
  }
1342
2517
  function resolveConfig(userConfig, cliOptions, baseDir) {
1343
2518
  const cwd = resolveCandidateCwd(userConfig ?? null, cliOptions, baseDir);
1344
- const preset = detectPreset(cwd);
2519
+ const forcedPreset = userConfig?.preset;
2520
+ if (forcedPreset !== void 0 && !getPreset(forcedPreset)) {
2521
+ throw new Error(unknownPresetMessage(forcedPreset));
2522
+ }
2523
+ const preset = detectPreset(cwd, forcedPreset);
1345
2524
  const layersMode = resolveLayersMode(userConfig?.layersMode);
1346
2525
  const layers = userConfig?.layers && userConfig.layers.length > 0 ? layersMode === "replace" ? userConfig.layers : [...userConfig.layers, ...preset.layers] : preset.layers;
1347
2526
  const kinds = userConfig?.kinds && userConfig.kinds.length > 0 ? layersMode === "replace" ? userConfig.kinds : [...userConfig.kinds, ...preset.kinds] : preset.kinds;
@@ -1377,6 +2556,8 @@ function resolveConfig(userConfig, cliOptions, baseDir) {
1377
2556
  ),
1378
2557
  includeSensitiveFiles: cliOptions?.includeSensitiveFiles ?? userConfig?.includeSensitiveFiles ?? false,
1379
2558
  include: resolveInclude(userConfig?.include),
2559
+ preset: preset.presetName,
2560
+ presetExclude: [...preset.exclude],
1380
2561
  artifacts,
1381
2562
  layersMode,
1382
2563
  budgets,
@@ -1411,8 +2592,8 @@ function matchRule(filePath, matcher) {
1411
2592
  return false;
1412
2593
  }
1413
2594
  function classifyLayer(filePath, layers) {
1414
- for (const layer of layers) {
1415
- if (matchRule(filePath, layer.match)) return layer.name;
2595
+ for (const layer2 of layers) {
2596
+ if (matchRule(filePath, layer2.match)) return layer2.name;
1416
2597
  }
1417
2598
  return "other";
1418
2599
  }
@@ -1493,11 +2674,11 @@ function generateAuditReport(manifest, secretFindings) {
1493
2674
  "| :--- | :--- | :--- |"
1494
2675
  );
1495
2676
  const totalFiles = manifest.counts.totalFiles || 1;
1496
- for (const [layer, count] of Object.entries(manifest.layers).sort(
2677
+ for (const [layer2, count] of Object.entries(manifest.layers).sort(
1497
2678
  (a, b) => b[1] - a[1] || (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0)
1498
2679
  )) {
1499
2680
  const pct = (count / totalFiles * 100).toFixed(1);
1500
- lines.push(`| \`${layer}\` | ${count} | ${pct}% |`);
2681
+ lines.push(`| \`${layer2}\` | ${count} | ${pct}% |`);
1501
2682
  }
1502
2683
  lines.push("");
1503
2684
  lines.push(
@@ -1605,21 +2786,21 @@ function partitionFiles(files, maxPartBytes, contentFingerprint) {
1605
2786
  section.byteSize = Buffer.byteLength(section.text, "utf8");
1606
2787
  continue;
1607
2788
  }
1608
- const marker = "\n... [part truncated: cannot fit budget] ...\n";
1609
- const target = Math.max(0, budget - Buffer.byteLength(marker, "utf8"));
2789
+ const marker2 = "\n... [part truncated: cannot fit budget] ...\n";
2790
+ const target = Math.max(0, budget - Buffer.byteLength(marker2, "utf8"));
1610
2791
  const clipped = truncateToBytes(rendered[oversized].markdown, target);
1611
- rendered[oversized] = rehashPart(rendered[oversized], clipped.content + marker);
2792
+ rendered[oversized] = rehashPart(rendered[oversized], clipped.content + marker2);
1612
2793
  return rendered;
1613
2794
  }
1614
2795
  return enforceBudget(renderGroups(groups, contentFingerprint), budget);
1615
2796
  }
1616
2797
  function enforceBudget(parts, budget) {
1617
- const marker = "\n... [part truncated: cannot fit budget] ...\n";
1618
- const target = Math.max(0, budget - Buffer.byteLength(marker, "utf8"));
2798
+ const marker2 = "\n... [part truncated: cannot fit budget] ...\n";
2799
+ const target = Math.max(0, budget - Buffer.byteLength(marker2, "utf8"));
1619
2800
  return parts.map((part) => {
1620
2801
  if (part.metadata.byteSize <= budget) return part;
1621
2802
  const clipped = truncateToBytes(part.markdown, target);
1622
- return rehashPart(part, clipped.content + marker);
2803
+ return rehashPart(part, clipped.content + marker2);
1623
2804
  });
1624
2805
  }
1625
2806
  function renumberParts(parts, contentFingerprint) {
@@ -1627,10 +2808,10 @@ function renumberParts(parts, contentFingerprint) {
1627
2808
  if (total === 0) return [];
1628
2809
  const zeroPad = (index) => String(index).padStart(Math.max(3, String(total).length), "0");
1629
2810
  const nameFor = (position) => `part-${zeroPad(position)}.md`;
1630
- const marker = "## Contents\n";
2811
+ const marker2 = "## Contents\n";
1631
2812
  return parts.map((part, index) => {
1632
- const markerIndex = part.markdown.indexOf(marker);
1633
- const body = markerIndex === -1 ? "" : part.markdown.slice(markerIndex + marker.length);
2813
+ const markerIndex = part.markdown.indexOf(marker2);
2814
+ const body = markerIndex === -1 ? "" : part.markdown.slice(markerIndex + marker2.length);
1634
2815
  const included = part.metadata.includedFiles;
1635
2816
  if (markerIndex === -1) {
1636
2817
  throw new Error(`${part.filename} has no "## Contents" section to re-number.`);
@@ -1688,9 +2869,9 @@ function topEntries(counts, limit) {
1688
2869
  return Object.entries(counts).sort((a, b) => b[1] - a[1] || (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0)).slice(0, limit).map(([key, value]) => `${capMetadata(key)} (${value})`).join(", ");
1689
2870
  }
1690
2871
  function prepareSection(file, sectionBudget) {
1691
- let text = renderFileSection(file);
1692
- let byteSize = Buffer.byteLength(text, "utf8");
1693
- if (byteSize <= sectionBudget) return { file, text, byteSize };
2872
+ let text2 = renderFileSection(file);
2873
+ let byteSize = Buffer.byteLength(text2, "utf8");
2874
+ if (byteSize <= sectionBudget) return { file, text: text2, byteSize };
1694
2875
  const shrinkBody = () => {
1695
2876
  const content = file.content ?? "";
1696
2877
  const overhead = byteSize - Buffer.byteLength(content, "utf8");
@@ -1699,24 +2880,24 @@ function prepareSection(file, sectionBudget) {
1699
2880
  const truncated = truncateToBytes(content, allowed);
1700
2881
  if (!truncated.isTruncated) return;
1701
2882
  applyTruncatedContent(file, truncated.content);
1702
- text = renderFileSection(file);
1703
- byteSize = Buffer.byteLength(text, "utf8");
2883
+ text2 = renderFileSection(file);
2884
+ byteSize = Buffer.byteLength(text2, "utf8");
1704
2885
  };
1705
2886
  shrinkBody();
1706
2887
  if (byteSize > sectionBudget && file.summary) {
1707
2888
  file.summary = void 0;
1708
2889
  file.summaryOmitted = true;
1709
- text = renderFileSection(file);
1710
- byteSize = Buffer.byteLength(text, "utf8");
2890
+ text2 = renderFileSection(file);
2891
+ byteSize = Buffer.byteLength(text2, "utf8");
1711
2892
  shrinkBody();
1712
2893
  }
1713
2894
  if (byteSize > sectionBudget) {
1714
2895
  const target = sectionBudget - Buffer.byteLength(SECTION_TRUNCATION_NOTICE, "utf8");
1715
- const truncated = truncateToBytes(text, Math.max(0, target));
1716
- text = truncated.content + SECTION_TRUNCATION_NOTICE;
1717
- byteSize = Buffer.byteLength(text, "utf8");
2896
+ const truncated = truncateToBytes(text2, Math.max(0, target));
2897
+ text2 = truncated.content + SECTION_TRUNCATION_NOTICE;
2898
+ byteSize = Buffer.byteLength(text2, "utf8");
1718
2899
  }
1719
- return { file, text, byteSize };
2900
+ return { file, text: text2, byteSize };
1720
2901
  }
1721
2902
  function applyTruncatedContent(file, content) {
1722
2903
  file.content = content;
@@ -1838,6 +3019,7 @@ function renderFileSection(file) {
1838
3019
  `- Source Lines: ${file.originalLines}`,
1839
3020
  `- Included Lines: ${file.includedLines}`,
1840
3021
  `- SHA-256: ${file.hashSha256}`,
3022
+ ...file.summaryQuality ? [`- Summary Quality: ${file.summaryQuality}`] : [],
1841
3023
  ...renderSummary(file.summary),
1842
3024
  ...file.summaryOmitted ? ["- Summary: (omitted: exceeds the part budget)"] : [],
1843
3025
  "",
@@ -2085,7 +3267,7 @@ var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
2085
3267
  ".so"
2086
3268
  ]);
2087
3269
  function isBinaryExtension(filePath) {
2088
- const ext = path9__namespace.extname(filePath).toLowerCase();
3270
+ const ext = path10__namespace.extname(filePath).toLowerCase();
2089
3271
  return BINARY_EXTENSIONS.has(ext);
2090
3272
  }
2091
3273
  function isBinaryBuffer(buffer) {
@@ -2099,9 +3281,9 @@ function isBinaryFile(absolutePath) {
2099
3281
  if (isBinaryExtension(absolutePath)) return true;
2100
3282
  let fd;
2101
3283
  try {
2102
- fd = fs8__namespace.openSync(absolutePath, "r");
3284
+ fd = fs9__namespace.openSync(absolutePath, "r");
2103
3285
  const buffer = Buffer.alloc(8192);
2104
- const bytesRead = fs8__namespace.readSync(fd, buffer, 0, 8192, 0);
3286
+ const bytesRead = fs9__namespace.readSync(fd, buffer, 0, 8192, 0);
2105
3287
  if (bytesRead === 0) return false;
2106
3288
  return isBinaryBuffer(buffer.subarray(0, bytesRead));
2107
3289
  } catch {
@@ -2109,7 +3291,7 @@ function isBinaryFile(absolutePath) {
2109
3291
  } finally {
2110
3292
  if (fd !== void 0) {
2111
3293
  try {
2112
- fs8__namespace.closeSync(fd);
3294
+ fs9__namespace.closeSync(fd);
2113
3295
  } catch {
2114
3296
  }
2115
3297
  }
@@ -2169,10 +3351,10 @@ var LANGUAGE_BY_BASENAME = {
2169
3351
  };
2170
3352
  function detectLanguage(filePath) {
2171
3353
  const normalized = filePath.replace(/\\/g, "/");
2172
- const base = path9__namespace.posix.basename(normalized);
3354
+ const base = path10__namespace.posix.basename(normalized);
2173
3355
  if (LANGUAGE_BY_BASENAME[base]) return LANGUAGE_BY_BASENAME[base];
2174
3356
  if (base === ".env" || base.startsWith(".env.")) return "dotenv";
2175
- const ext = path9__namespace.posix.extname(base).toLowerCase();
3357
+ const ext = path10__namespace.posix.extname(base).toLowerCase();
2176
3358
  return LANGUAGE_BY_EXTENSION[ext] ?? "text";
2177
3359
  }
2178
3360
  function emptyDrift() {
@@ -2191,8 +3373,8 @@ function sha256Hex(buffer) {
2191
3373
  function validateSnapshot(configInput) {
2192
3374
  assertUsableConfigInput(configInput);
2193
3375
  const config = isResolvedConfig(configInput) ? normalizeResolvedConfig(configInput) : resolveConfig(configInput);
2194
- const manifestPath = path9__namespace.join(config.cwd, config.outDir, "manifest.json");
2195
- if (!fs8__namespace.existsSync(manifestPath)) {
3376
+ const manifestPath = path10__namespace.resolve(config.cwd, config.outDir, "manifest.json");
3377
+ if (!fs9__namespace.existsSync(manifestPath)) {
2196
3378
  return {
2197
3379
  valid: false,
2198
3380
  manifestPresent: false,
@@ -2203,7 +3385,7 @@ function validateSnapshot(configInput) {
2203
3385
  }
2204
3386
  let manifest;
2205
3387
  try {
2206
- manifest = JSON.parse(fs8__namespace.readFileSync(manifestPath, "utf8"));
3388
+ manifest = JSON.parse(fs9__namespace.readFileSync(manifestPath, "utf8"));
2207
3389
  } catch (err) {
2208
3390
  return {
2209
3391
  valid: false,
@@ -2226,7 +3408,7 @@ function validateSnapshot(configInput) {
2226
3408
  }
2227
3409
  const drift = emptyDrift();
2228
3410
  const warnings = [];
2229
- const snapshotDir = path9__namespace.join(config.cwd, config.outDir);
3411
+ const snapshotDir = path10__namespace.resolve(config.cwd, config.outDir);
2230
3412
  const manifestFileMap = /* @__PURE__ */ new Map();
2231
3413
  for (const file of manifest.files) {
2232
3414
  if (file && typeof file.relativePath === "string") {
@@ -2238,7 +3420,7 @@ function validateSnapshot(configInput) {
2238
3420
  );
2239
3421
  const collection = collectFiles(config.cwd, {
2240
3422
  includePatterns: config.include,
2241
- excludePatterns: config.excludePatterns,
3423
+ excludePatterns: [...config.excludePatterns, ...config.presetExclude],
2242
3424
  includeSensitiveFiles: config.includeSensitiveFiles
2243
3425
  });
2244
3426
  for (const diskFile of collection.files) {
@@ -2250,7 +3432,7 @@ function validateSnapshot(configInput) {
2250
3432
  }
2251
3433
  let diskHash;
2252
3434
  try {
2253
- diskHash = sha256Hex(fs8__namespace.readFileSync(path9__namespace.join(config.cwd, diskFile)));
3435
+ diskHash = sha256Hex(fs9__namespace.readFileSync(path10__namespace.join(config.cwd, diskFile)));
2254
3436
  } catch {
2255
3437
  drift.missingOnDisk.push(diskFile);
2256
3438
  continue;
@@ -2260,7 +3442,7 @@ function validateSnapshot(configInput) {
2260
3442
  }
2261
3443
  }
2262
3444
  for (const snapshotFile of manifestFileMap.keys()) {
2263
- if (!fs8__namespace.existsSync(path9__namespace.join(config.cwd, snapshotFile))) {
3445
+ if (!fs9__namespace.existsSync(path10__namespace.join(config.cwd, snapshotFile))) {
2264
3446
  drift.missingOnDisk.push(snapshotFile);
2265
3447
  }
2266
3448
  }
@@ -2274,10 +3456,10 @@ function validateSnapshot(configInput) {
2274
3456
  if (!part || typeof part.filename !== "string") continue;
2275
3457
  partNames.add(part.filename);
2276
3458
  if (!config.artifacts.parts) continue;
2277
- const partPath = path9__namespace.join(snapshotDir, "parts", part.filename);
3459
+ const partPath = path10__namespace.join(snapshotDir, "parts", part.filename);
2278
3460
  let buffer;
2279
3461
  try {
2280
- buffer = fs8__namespace.readFileSync(partPath);
3462
+ buffer = fs9__namespace.readFileSync(partPath);
2281
3463
  } catch {
2282
3464
  drift.partIssues.push({ part: part.filename, issue: "missing from snapshot/parts" });
2283
3465
  continue;
@@ -2364,6 +3546,11 @@ function validateSnapshot(configInput) {
2364
3546
  }
2365
3547
  }
2366
3548
  }
3549
+ if (recorded?.preset !== void 0 && recorded.preset !== config.preset) {
3550
+ drift.configMismatches.push(
3551
+ `preset: manifest ${recorded.preset} vs current ${config.preset}`
3552
+ );
3553
+ }
2367
3554
  if (recorded?.layersMode !== void 0 && recorded.layersMode !== config.layersMode) {
2368
3555
  drift.configMismatches.push(
2369
3556
  `layersMode: manifest ${recorded.layersMode} vs current ${config.layersMode}`
@@ -2381,10 +3568,10 @@ function validateSnapshot(configInput) {
2381
3568
  ...Object.keys(recordedByLayer),
2382
3569
  ...Object.keys(config.budgets.byLayer)
2383
3570
  ]);
2384
- for (const layer of Array.from(layerNames).sort()) {
2385
- if (recordedByLayer[layer] !== config.budgets.byLayer[layer]) {
3571
+ for (const layer2 of Array.from(layerNames).sort()) {
3572
+ if (recordedByLayer[layer2] !== config.budgets.byLayer[layer2]) {
2386
3573
  drift.configMismatches.push(
2387
- `budgets.byLayer.${layer}: manifest ${recordedByLayer[layer]} vs current ${config.budgets.byLayer[layer]}`
3574
+ `budgets.byLayer.${layer2}: manifest ${recordedByLayer[layer2]} vs current ${config.budgets.byLayer[layer2]}`
2388
3575
  );
2389
3576
  }
2390
3577
  }
@@ -2467,9 +3654,9 @@ function validateSnapshot(configInput) {
2467
3654
  ["full-codebase.md", manifest.artifacts?.fullMarkdown]
2468
3655
  ]) {
2469
3656
  if (!artifact) continue;
2470
- const artifactPath = path9__namespace.join(snapshotDir, key);
3657
+ const artifactPath = path10__namespace.join(snapshotDir, key);
2471
3658
  try {
2472
- const buffer = fs8__namespace.readFileSync(artifactPath);
3659
+ const buffer = fs9__namespace.readFileSync(artifactPath);
2473
3660
  if (sha256Hex(buffer) !== artifact.sha256 || buffer.length !== artifact.byteSize) {
2474
3661
  drift.partIssues.push({ part: key, issue: "artifact does not match its recorded digest" });
2475
3662
  }
@@ -2525,35 +3712,35 @@ function describeMatcher(matcher) {
2525
3712
  return String(matcher);
2526
3713
  }
2527
3714
  function normalizeTarget(cwd, target) {
2528
- const absolute = path9__namespace.resolve(cwd, target);
2529
- const relative3 = path9__namespace.relative(cwd, absolute);
2530
- if (relative3.startsWith("..") || path9__namespace.isAbsolute(relative3)) return target;
2531
- return relative3.split(path9__namespace.sep).join("/");
3715
+ const absolute = path10__namespace.resolve(cwd, target);
3716
+ const relative3 = path10__namespace.relative(cwd, absolute);
3717
+ if (relative3.startsWith("..") || path10__namespace.isAbsolute(relative3)) return target;
3718
+ return relative3.split(path10__namespace.sep).join("/");
2532
3719
  }
2533
3720
  function findRuleMatch(rules, relativePath) {
2534
3721
  return rules.find(
2535
3722
  (rule) => matchRule(relativePath, rule.match)
2536
3723
  );
2537
3724
  }
2538
- function readManifest(config) {
2539
- const manifestPath = path9__namespace.join(config.cwd, config.outDir, "manifest.json");
3725
+ function readManifest2(config) {
3726
+ const manifestPath = path10__namespace.resolve(config.cwd, config.outDir, "manifest.json");
2540
3727
  try {
2541
- return JSON.parse(fs8__namespace.readFileSync(manifestPath, "utf8"));
3728
+ return JSON.parse(fs9__namespace.readFileSync(manifestPath, "utf8"));
2542
3729
  } catch {
2543
3730
  return null;
2544
3731
  }
2545
3732
  }
2546
3733
  function explainPath(config, target) {
2547
3734
  const relative3 = normalizeTarget(config.cwd, target);
2548
- const absolute = path9__namespace.join(config.cwd, relative3);
3735
+ const absolute = path10__namespace.join(config.cwd, relative3);
2549
3736
  const result = {
2550
3737
  path: relative3,
2551
- exists: fs8__namespace.existsSync(absolute),
3738
+ exists: fs9__namespace.existsSync(absolute),
2552
3739
  included: false
2553
3740
  };
2554
3741
  const collection = collectFiles(config.cwd, {
2555
3742
  includePatterns: config.include,
2556
- excludePatterns: config.excludePatterns,
3743
+ excludePatterns: [...config.excludePatterns, ...config.presetExclude],
2557
3744
  includeSensitiveFiles: config.includeSensitiveFiles
2558
3745
  });
2559
3746
  if (!collection.files.includes(relative3)) {
@@ -2578,16 +3765,16 @@ function explainPath(config, target) {
2578
3765
  if (layerRule) result.layerMatch = describeMatcher(layerRule.match);
2579
3766
  const kindRule = findRuleMatch(config.kinds, relative3);
2580
3767
  if (kindRule) result.kindMatch = describeMatcher(kindRule.match);
2581
- if (fs8__namespace.existsSync(absolute) && fs8__namespace.statSync(absolute).isFile()) {
2582
- const buffer = fs8__namespace.readFileSync(absolute);
2583
- const text = buffer.toString("utf8");
3768
+ if (fs9__namespace.existsSync(absolute) && fs9__namespace.statSync(absolute).isFile()) {
3769
+ const buffer = fs9__namespace.readFileSync(absolute);
3770
+ const text2 = buffer.toString("utf8");
2584
3771
  result.bytes = buffer.length;
2585
- result.lines = countLines(text);
2586
- const { findings } = scanAndRedactSecrets(text, config.secretRules, relative3);
3772
+ result.lines = countLines(text2);
3773
+ const { findings } = scanAndRedactSecrets(text2, config.secretRules, relative3);
2587
3774
  result.secretCount = findings.length;
2588
3775
  result.secrets = findings.slice(0, 20).map((finding) => ({ rule: finding.rule, line: finding.line }));
2589
3776
  }
2590
- const manifest = readManifest(config);
3777
+ const manifest = readManifest2(config);
2591
3778
  const entry = manifest?.files.find((file) => file.relativePath === relative3);
2592
3779
  result.part = entry?.part ?? null;
2593
3780
  result.inclusionMode = entry?.inclusionMode;
@@ -2670,10 +3857,10 @@ function compareManifests(before, after) {
2670
3857
  };
2671
3858
  }
2672
3859
  function loadManifest(target) {
2673
- const manifestPath = fs8__namespace.statSync(target).isDirectory() ? path9__namespace.join(target, "manifest.json") : target;
3860
+ const manifestPath = fs9__namespace.statSync(target).isDirectory() ? path10__namespace.join(target, "manifest.json") : target;
2674
3861
  let raw;
2675
3862
  try {
2676
- raw = fs8__namespace.readFileSync(manifestPath, "utf8");
3863
+ raw = fs9__namespace.readFileSync(manifestPath, "utf8");
2677
3864
  } catch {
2678
3865
  throw new Error(`Could not read a manifest at ${manifestPath}.`);
2679
3866
  }
@@ -2709,14 +3896,659 @@ function renderDiffText(diff) {
2709
3896
  return lines.join("\n");
2710
3897
  }
2711
3898
 
3899
+ // src/wizard/config-template.ts
3900
+ var DEFAULT_ANSWERS = {
3901
+ preset: "generic",
3902
+ maxPartBytes: "300k",
3903
+ largeFileBytes: "200k",
3904
+ include: [],
3905
+ excludePatterns: [],
3906
+ artifacts: {
3907
+ readme: true,
3908
+ manifest: true,
3909
+ parts: true,
3910
+ navigation: true,
3911
+ projectTree: true,
3912
+ audit: true,
3913
+ monolith: false
3914
+ },
3915
+ includeSensitiveFiles: false,
3916
+ outDir: "snapshot"
3917
+ };
3918
+ function list(values) {
3919
+ if (values.length === 0) return "[]";
3920
+ return `[${values.map((value) => JSON.stringify(value)).join(", ")}]`;
3921
+ }
3922
+ function size(value) {
3923
+ return typeof value === "string" ? JSON.stringify(value) : String(value);
3924
+ }
3925
+ var PRESET_NAMES = [
3926
+ "nextjs \xB7 react-vite \xB7 vue-vite \xB7 nuxt \xB7 sveltekit \xB7 angular \xB7 astro",
3927
+ "remix \xB7 solid \xB7 nest \xB7 node-express \xB7 node-lib",
3928
+ "python-django \xB7 python-fastapi \xB7 python-lib \xB7 go \xB7 rust",
3929
+ "java-spring \xB7 dotnet \xB7 php-laravel \xB7 ruby-rails",
3930
+ "monorepo \xB7 docs \xB7 infra \xB7 generic"
3931
+ ];
3932
+ function renderConfig(answers) {
3933
+ const { artifacts } = answers;
3934
+ return `import { defineConfig } from "@ankurhalder/codebase-snapshot";
3935
+
3936
+ export default defineConfig({
3937
+ // \u2500\u2500 Project \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
3938
+ // Which project shape to use. Detected from the repo when omitted.
3939
+ // ${PRESET_NAMES.join("\n // ")}
3940
+ preset: ${JSON.stringify(answers.preset)},
3941
+
3942
+ // Recorded in the manifest when git cannot report a branch.
3943
+ branch: "main",
3944
+
3945
+ // \u2500\u2500 Output \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
3946
+ // Where the snapshot is written.
3947
+ outDir: ${JSON.stringify(answers.outDir)},
3948
+ // Scratch directory; the new snapshot is renamed into place atomically.
3949
+ tmpDir: "snapshot.tmp",
3950
+
3951
+ // \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
3952
+ // Hard ceiling per part. "300k", 307200 and "1.5mb" are all accepted.
3953
+ maxPartBytes: ${size(answers.maxPartBytes)},
3954
+ // Files above this size get a head/tail preview instead of the full body.
3955
+ largeFileBytes: ${size(answers.largeFileBytes)},
3956
+ previewHeadLines: 80,
3957
+ previewTailLines: 40,
3958
+ // Different ceilings per layer. Unlisted layers use \`default\`.
3959
+ budgets: {
3960
+ default: ${size(answers.maxPartBytes)},
3961
+ byLayer: {},
3962
+ },
3963
+
3964
+ // \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
3965
+ // Allow-list. Empty means "everything that is not ignored".
3966
+ // Example: ["src/**", "packages/*/src/**"]
3967
+ include: ${list(answers.include)},
3968
+ // Globs or path segments to drop. Example: ["**/*.test.ts", "docs"]
3969
+ excludePatterns: ${list(answers.excludePatterns)},
3970
+
3971
+ // \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
3972
+ // "merge" = the preset's rules plus yours (yours win). "replace" = yours only.
3973
+ layersMode: "merge",
3974
+ // [{ name: "billing", match: "src/features/billing/**" }]
3975
+ layers: [],
3976
+ // [{ kind: "route", match: "src/app/api/**" }]
3977
+ kinds: [],
3978
+
3979
+ // \u2500\u2500 Safety \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
3980
+ // .env, *.pem, .npmrc, .ssh/ and friends are skipped unless this is true.
3981
+ includeSensitiveFiles: ${answers.includeSensitiveFiles},
3982
+ secrets: {
3983
+ // [{ rule: "internal-token", pattern: /CORP-[A-Z0-9]{32}/ }]
3984
+ customPatterns: [],
3985
+ // Set true to drop the 21 built-in rules.
3986
+ disableDefaultPatterns: false,
3987
+ },
3988
+
3989
+ // \u2500\u2500 Artifacts \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
3990
+ artifacts: {
3991
+ readme: ${artifacts.readme}, // README.md
3992
+ manifest: ${artifacts.manifest}, // manifest.json \u2014 required by validate and report
3993
+ parts: ${artifacts.parts}, // parts/part-XXX.md
3994
+ navigation: ${artifacts.navigation}, // reports/navigation.md
3995
+ projectTree: ${artifacts.projectTree}, // reports/project-tree.md
3996
+ audit: ${artifacts.audit}, // reports/audit.md
3997
+ monolith: ${artifacts.monolith}, // full-codebase.json + .md (roughly doubles output)
3998
+ },
3999
+ // Skip the monolith above this size. null = no limit.
4000
+ maxMonolithBytes: null,
4001
+
4002
+ // \u2500\u2500 Advanced \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
4003
+ // afterGenerate: (manifest) => { /* write your own artifact */ }
4004
+ hooks: {},
4005
+ });
4006
+ `;
4007
+ }
4008
+ function renderMinimalConfig(answers) {
4009
+ const lines = [];
4010
+ if (answers.preset !== DEFAULT_ANSWERS.preset) lines.push(` preset: ${JSON.stringify(answers.preset)},`);
4011
+ if (answers.maxPartBytes !== DEFAULT_ANSWERS.maxPartBytes) {
4012
+ lines.push(` maxPartBytes: ${size(answers.maxPartBytes)},`);
4013
+ }
4014
+ if (answers.largeFileBytes !== DEFAULT_ANSWERS.largeFileBytes) {
4015
+ lines.push(` largeFileBytes: ${size(answers.largeFileBytes)},`);
4016
+ }
4017
+ if (answers.include.length > 0) lines.push(` include: ${list(answers.include)},`);
4018
+ if (answers.excludePatterns.length > 0) {
4019
+ lines.push(` excludePatterns: ${list(answers.excludePatterns)},`);
4020
+ }
4021
+ if (answers.includeSensitiveFiles) lines.push(" includeSensitiveFiles: true,");
4022
+ if (answers.outDir !== DEFAULT_ANSWERS.outDir) lines.push(` outDir: ${JSON.stringify(answers.outDir)},`);
4023
+ const changed = Object.keys(answers.artifacts).filter(
4024
+ (name) => answers.artifacts[name] !== DEFAULT_ANSWERS.artifacts[name]
4025
+ );
4026
+ if (changed.length > 0) {
4027
+ const entries = changed.map((name) => ` ${name}: ${answers.artifacts[name]}`).join(",\n");
4028
+ lines.push(` artifacts: {
4029
+ ${entries},
4030
+ },`);
4031
+ }
4032
+ if (lines.length === 0) {
4033
+ return `import { defineConfig } from "@ankurhalder/codebase-snapshot";
4034
+
4035
+ export default defineConfig({});
4036
+ `;
4037
+ }
4038
+ return `import { defineConfig } from "@ankurhalder/codebase-snapshot";
4039
+
4040
+ export default defineConfig({
4041
+ ${lines.join("\n")}
4042
+ });
4043
+ `;
4044
+ }
4045
+ var PromptCancelled = class extends Error {
4046
+ constructor() {
4047
+ super("Prompt cancelled");
4048
+ this.name = "PromptCancelled";
4049
+ }
4050
+ };
4051
+ function defaultIO() {
4052
+ return { input: process.stdin, output: process.stdout };
4053
+ }
4054
+ function isInteractive(io) {
4055
+ return Boolean(io.input.isTTY && io.output.isTTY);
4056
+ }
4057
+ function moveSelection(current, delta, count) {
4058
+ if (count <= 0) return 0;
4059
+ return ((current + delta) % count + count) % count;
4060
+ }
4061
+ function toggleIndex(selected, index) {
4062
+ const next = new Set(selected);
4063
+ if (next.has(index)) next.delete(index);
4064
+ else next.add(index);
4065
+ return next;
4066
+ }
4067
+ function marker(active) {
4068
+ return active ? "\u276F" : " ";
4069
+ }
4070
+ function choiceLine(choice, active) {
4071
+ const hint = choice.hint ? ` ${choice.hint}` : "";
4072
+ return ` ${marker(active)} ${choice.label}${hint}`;
4073
+ }
4074
+ function renderSelect(message, choices, index) {
4075
+ const lines = [`\u25C6 ${message}`, ""];
4076
+ choices.forEach((choice, position) => lines.push(choiceLine(choice, position === index)));
4077
+ lines.push("", " \u2191\u2193 move \xB7 enter select");
4078
+ return lines.join("\n");
4079
+ }
4080
+ function renderMultiSelect(message, choices, index, selected) {
4081
+ const lines = [`\u25C6 ${message}`, ""];
4082
+ choices.forEach((choice, position) => {
4083
+ const box = selected.has(position) ? "\u25FC" : "\u25FB";
4084
+ const hint = choice.hint ? ` ${choice.hint}` : "";
4085
+ lines.push(` ${marker(position === index)} ${box} ${choice.label}${hint}`);
4086
+ });
4087
+ lines.push("", " \u2191\u2193 move \xB7 space toggle \xB7 enter confirm");
4088
+ return lines.join("\n");
4089
+ }
4090
+ function renderConfirm(message, value) {
4091
+ return [
4092
+ `\u25C6 ${message}`,
4093
+ "",
4094
+ ` ${marker(value)} Yes`,
4095
+ ` ${marker(!value)} No`,
4096
+ "",
4097
+ " \u2190\u2192 or y/n \xB7 enter confirm"
4098
+ ].join("\n");
4099
+ }
4100
+ function renderText(message, value, error) {
4101
+ const lines = [`\u25C6 ${message}`, "", ` ${value || "\u258F"}`];
4102
+ if (error) lines.push("", ` \u2716 ${error}`);
4103
+ return lines.join("\n");
4104
+ }
4105
+ function keyReader(input) {
4106
+ const queue = [];
4107
+ const waiters = [];
4108
+ const wasRaw = input.isRaw;
4109
+ const onKey = (_text, key) => {
4110
+ const waiter = waiters.shift();
4111
+ if (waiter) waiter(key);
4112
+ else queue.push(key);
4113
+ };
4114
+ readline__namespace.emitKeypressEvents(input);
4115
+ if (input.isTTY) input.setRawMode(true);
4116
+ input.resume();
4117
+ input.on("keypress", onKey);
4118
+ return {
4119
+ next() {
4120
+ const queued = queue.shift();
4121
+ if (queued) return Promise.resolve(queued);
4122
+ return new Promise((resolve5) => waiters.push(resolve5));
4123
+ },
4124
+ close() {
4125
+ input.off("keypress", onKey);
4126
+ if (input.isTTY) input.setRawMode(wasRaw ?? false);
4127
+ input.pause();
4128
+ }
4129
+ };
4130
+ }
4131
+ var Frame = class {
4132
+ constructor(output) {
4133
+ this.output = output;
4134
+ }
4135
+ output;
4136
+ drawn = 0;
4137
+ draw(text2) {
4138
+ this.erase();
4139
+ this.output.write(`${text2}
4140
+ `);
4141
+ this.drawn = text2.split("\n").length;
4142
+ }
4143
+ erase() {
4144
+ if (this.drawn > 0) this.output.write(`\x1B[${this.drawn}A\x1B[0J`);
4145
+ this.drawn = 0;
4146
+ }
4147
+ };
4148
+ function isCancel(key) {
4149
+ return key.ctrl === true && key.name === "c" || key.ctrl === true && key.name === "d";
4150
+ }
4151
+ async function select(io, message, choices, options = {}) {
4152
+ if (choices.length === 0) throw new Error("select() requires at least one choice.");
4153
+ const fallbackIndex = Math.max(
4154
+ 0,
4155
+ choices.findIndex((choice) => choice.value === options.defaultValue)
4156
+ );
4157
+ if (!isInteractive(io)) {
4158
+ io.output.write(`${renderSelect(message, choices, fallbackIndex)}
4159
+ `);
4160
+ return choices[fallbackIndex].value;
4161
+ }
4162
+ const frame = new Frame(io.output);
4163
+ const reader = keyReader(io.input);
4164
+ let index = fallbackIndex;
4165
+ try {
4166
+ for (; ; ) {
4167
+ frame.draw(renderSelect(message, choices, index));
4168
+ const key = await reader.next();
4169
+ if (isCancel(key)) throw new PromptCancelled();
4170
+ if (key.name === "up") index = moveSelection(index, -1, choices.length);
4171
+ else if (key.name === "down") index = moveSelection(index, 1, choices.length);
4172
+ else if (key.name === "return" || key.name === "enter") {
4173
+ frame.erase();
4174
+ return choices[index].value;
4175
+ } else if (key.name === "j") index = moveSelection(index, 1, choices.length);
4176
+ else if (key.name === "k") index = moveSelection(index, -1, choices.length);
4177
+ }
4178
+ } finally {
4179
+ reader.close();
4180
+ }
4181
+ }
4182
+ async function multiSelect(io, message, choices, options = {}) {
4183
+ const selected = /* @__PURE__ */ new Set();
4184
+ choices.forEach((choice, position) => {
4185
+ if (options.defaults?.includes(choice.value)) selected.add(position);
4186
+ });
4187
+ const result = () => choices.filter((_, position) => selected.has(position)).map((choice) => choice.value);
4188
+ if (!isInteractive(io)) {
4189
+ io.output.write(`${renderMultiSelect(message, choices, 0, selected)}
4190
+ `);
4191
+ return result();
4192
+ }
4193
+ const frame = new Frame(io.output);
4194
+ const reader = keyReader(io.input);
4195
+ let index = 0;
4196
+ try {
4197
+ for (; ; ) {
4198
+ frame.draw(renderMultiSelect(message, choices, index, selected));
4199
+ const key = await reader.next();
4200
+ if (isCancel(key)) throw new PromptCancelled();
4201
+ if (key.name === "up") index = moveSelection(index, -1, choices.length);
4202
+ else if (key.name === "down") index = moveSelection(index, 1, choices.length);
4203
+ else if (key.name === "space") {
4204
+ const next = toggleIndex(selected, index);
4205
+ selected.clear();
4206
+ for (const position of next) selected.add(position);
4207
+ } else if (key.name === "a") {
4208
+ if (selected.size === choices.length) selected.clear();
4209
+ else choices.forEach((_, position) => selected.add(position));
4210
+ } else if (key.name === "return" || key.name === "enter") {
4211
+ frame.erase();
4212
+ return result();
4213
+ }
4214
+ }
4215
+ } finally {
4216
+ reader.close();
4217
+ }
4218
+ }
4219
+ async function confirm(io, message, options = {}) {
4220
+ let value = options.defaultValue ?? true;
4221
+ if (!isInteractive(io)) {
4222
+ io.output.write(`${renderConfirm(message, value)}
4223
+ `);
4224
+ return value;
4225
+ }
4226
+ const frame = new Frame(io.output);
4227
+ const reader = keyReader(io.input);
4228
+ try {
4229
+ for (; ; ) {
4230
+ frame.draw(renderConfirm(message, value));
4231
+ const key = await reader.next();
4232
+ if (isCancel(key)) throw new PromptCancelled();
4233
+ if (key.name === "up" || key.name === "left") value = true;
4234
+ else if (key.name === "down" || key.name === "right") value = false;
4235
+ else if (key.name === "y") value = true;
4236
+ else if (key.name === "n") value = false;
4237
+ else if (key.name === "return" || key.name === "enter") {
4238
+ frame.erase();
4239
+ return value;
4240
+ }
4241
+ }
4242
+ } finally {
4243
+ reader.close();
4244
+ }
4245
+ }
4246
+ async function text(io, message, options = {}) {
4247
+ const fallback = options.defaultValue ?? "";
4248
+ if (!isInteractive(io)) {
4249
+ io.output.write(`${renderText(message, fallback)}
4250
+ `);
4251
+ return fallback;
4252
+ }
4253
+ const frame = new Frame(io.output);
4254
+ const reader = keyReader(io.input);
4255
+ let value = fallback;
4256
+ let error;
4257
+ try {
4258
+ for (; ; ) {
4259
+ frame.draw(renderText(message, value, error));
4260
+ const key = await reader.next();
4261
+ if (isCancel(key)) throw new PromptCancelled();
4262
+ error = void 0;
4263
+ if (key.name === "return" || key.name === "enter") {
4264
+ const problem = options.validate ? options.validate(value) : null;
4265
+ if (problem) {
4266
+ error = problem;
4267
+ continue;
4268
+ }
4269
+ frame.erase();
4270
+ return value;
4271
+ }
4272
+ if (key.name === "backspace") {
4273
+ value = value.slice(0, -1);
4274
+ continue;
4275
+ }
4276
+ if (key.name === "escape") {
4277
+ value = "";
4278
+ continue;
4279
+ }
4280
+ if (_isPrintable(key)) value += _keyText(key);
4281
+ }
4282
+ } finally {
4283
+ reader.close();
4284
+ }
4285
+ }
4286
+ function _isPrintable(key) {
4287
+ return typeof key.sequence === "string" && key.sequence.length === 1 && key.sequence >= " ";
4288
+ }
4289
+ function _keyText(key) {
4290
+ return key.sequence ?? "";
4291
+ }
4292
+
4293
+ // src/wizard/init.ts
4294
+ var CONFIG_CANDIDATES = [
4295
+ "snapshot.config.ts",
4296
+ "snapshot.config.js",
4297
+ "snapshot.config.mjs",
4298
+ "snapshot.config.cjs",
4299
+ "snapshot.config.json",
4300
+ ".snapshotrc.json",
4301
+ ".snapshotrc"
4302
+ ];
4303
+ var SIZE_CHOICES = [
4304
+ { value: "60k", label: "Small", hint: "60 KB \xB7 ~15k tokens \xB7 tight context windows" },
4305
+ { value: "120k", label: "Medium", hint: "120 KB \xB7 ~30k tokens \xB7 recommended" },
4306
+ { value: "300k", label: "Large", hint: "300 KB \xB7 ~75k tokens \xB7 the default" },
4307
+ { value: "custom", label: "Custom\u2026", hint: "type a size like 150k or 1.5mb" }
4308
+ ];
4309
+ function existingConfig(cwd) {
4310
+ for (const name of CONFIG_CANDIDATES) {
4311
+ if (fs9__namespace.existsSync(path10__namespace.join(cwd, name))) return name;
4312
+ }
4313
+ return null;
4314
+ }
4315
+ function artifactAnswers(options) {
4316
+ const artifacts = { ...DEFAULT_ANSWERS.artifacts };
4317
+ for (const name of options.skipArtifacts ?? []) {
4318
+ if (ARTIFACT_NAMES.includes(name)) {
4319
+ artifacts[name] = false;
4320
+ }
4321
+ }
4322
+ if (options.onlyArtifacts && options.onlyArtifacts.length > 0) {
4323
+ const keep = new Set(options.onlyArtifacts);
4324
+ for (const name of ARTIFACT_NAMES) artifacts[name] = keep.has(name);
4325
+ }
4326
+ return artifacts;
4327
+ }
4328
+ function topLevelDirs(cwd) {
4329
+ try {
4330
+ return fs9__namespace.readdirSync(cwd, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules").map((entry) => entry.name).sort();
4331
+ } catch {
4332
+ return [];
4333
+ }
4334
+ }
4335
+ function addScripts(cwd, notes) {
4336
+ const packagePath = path10__namespace.join(cwd, "package.json");
4337
+ if (!fs9__namespace.existsSync(packagePath)) {
4338
+ notes.push("No package.json found, so npm scripts were not added.");
4339
+ return [];
4340
+ }
4341
+ let parsed;
4342
+ try {
4343
+ parsed = JSON.parse(fs9__namespace.readFileSync(packagePath, "utf8"));
4344
+ } catch {
4345
+ notes.push("Could not parse package.json; npm scripts were not added.");
4346
+ return [];
4347
+ }
4348
+ const scripts = parsed.scripts && typeof parsed.scripts === "object" ? parsed.scripts : {};
4349
+ const added = [];
4350
+ if (!scripts.snapshot) {
4351
+ scripts.snapshot = "codebase-snapshot generate";
4352
+ added.push("snapshot");
4353
+ }
4354
+ if (!scripts["snapshot:validate"]) {
4355
+ scripts["snapshot:validate"] = "codebase-snapshot validate";
4356
+ added.push("snapshot:validate");
4357
+ }
4358
+ if (added.length === 0) return [];
4359
+ parsed.scripts = scripts;
4360
+ fs9__namespace.writeFileSync(packagePath, `${JSON.stringify(parsed, null, 2)}
4361
+ `, "utf8");
4362
+ return added;
4363
+ }
4364
+ function addGitignore(cwd) {
4365
+ const ignorePath = path10__namespace.join(cwd, ".gitignore");
4366
+ const existing = fs9__namespace.existsSync(ignorePath) ? fs9__namespace.readFileSync(ignorePath, "utf8") : "";
4367
+ const wanted = ["snapshot/", "snapshot.tmp/"];
4368
+ const added = wanted.filter((entry) => !existing.split(/\r?\n/).includes(entry));
4369
+ if (added.length === 0) return [];
4370
+ const prefix = existing.length === 0 || existing.endsWith("\n") ? "" : "\n";
4371
+ fs9__namespace.writeFileSync(ignorePath, `${existing}${prefix}${added.join("\n")}
4372
+ `, "utf8");
4373
+ return added;
4374
+ }
4375
+ function resolvePresetChoice(answers, presetName) {
4376
+ return {
4377
+ value: presetName,
4378
+ label: presetName,
4379
+ hint: presetName === answers.preset ? "detected" : void 0
4380
+ };
4381
+ }
4382
+ async function runInit(options) {
4383
+ const io = options.io ?? defaultIO();
4384
+ const detected = detectPreset(options.cwd, options.preset);
4385
+ const notes = [];
4386
+ const answers = {
4387
+ preset: detected.presetName,
4388
+ maxPartBytes: options.maxPartBytes ?? detected.recommendedBudget ?? DEFAULT_ANSWERS.maxPartBytes,
4389
+ largeFileBytes: DEFAULT_ANSWERS.largeFileBytes,
4390
+ include: options.include ?? [],
4391
+ excludePatterns: options.exclude ?? [],
4392
+ artifacts: artifactAnswers(options),
4393
+ includeSensitiveFiles: options.includeSensitiveFiles ?? DEFAULT_ANSWERS.includeSensitiveFiles,
4394
+ outDir: options.outDir ?? DEFAULT_ANSWERS.outDir
4395
+ };
4396
+ const interactive = isInteractive(io) && options.yes !== true;
4397
+ if (interactive) {
4398
+ io.output.write("\n @ankurhalder/codebase-snapshot\n");
4399
+ io.output.write(" Turn this repository into an LLM-ready, redacted, byte-bounded snapshot.\n\n");
4400
+ io.output.write(` Detected ${detected.presetName}
4401
+ `);
4402
+ io.output.write(` Directory ${options.cwd}
4403
+
4404
+ `);
4405
+ const useDetected = await confirm(io, `Set up for ${detected.presetName}?`, {
4406
+ defaultValue: true
4407
+ });
4408
+ if (!useDetected) {
4409
+ answers.preset = await select(
4410
+ io,
4411
+ "What kind of project is this?",
4412
+ listPresetNames().map((name) => resolvePresetChoice(answers, name))
4413
+ );
4414
+ const chosen = detectPreset(options.cwd, answers.preset);
4415
+ answers.preset = chosen.presetName;
4416
+ }
4417
+ const scope = await select(
4418
+ io,
4419
+ "What should go into the snapshot?",
4420
+ [
4421
+ { value: "all", label: "Everything the scanner finds", hint: "recommended \xB7 respects .gitignore" },
4422
+ { value: "folders", label: "Only specific folders\u2026" },
4423
+ { value: "no-tests", label: "Everything, but skip tests" },
4424
+ { value: "custom", label: "Let me write glob patterns" }
4425
+ ],
4426
+ { defaultValue: "all" }
4427
+ );
4428
+ if (scope === "folders") {
4429
+ const dirs = topLevelDirs(options.cwd).slice(0, 40);
4430
+ if (dirs.length === 0) {
4431
+ notes.push("No top-level directories found; the snapshot will include everything.");
4432
+ } else {
4433
+ const chosen = await multiSelect(
4434
+ io,
4435
+ "Select folders",
4436
+ dirs.map((name) => ({ value: name, label: `${name}/` })),
4437
+ { defaults: dirs }
4438
+ );
4439
+ answers.include = chosen.map((name) => `${name}/**`);
4440
+ }
4441
+ } else if (scope === "no-tests") {
4442
+ answers.excludePatterns = [
4443
+ ...answers.excludePatterns,
4444
+ "**/*.test.*",
4445
+ "**/*.spec.*",
4446
+ "tests",
4447
+ "test"
4448
+ ];
4449
+ } else if (scope === "custom") {
4450
+ const raw = await text(io, "Glob patterns to include (comma separated)", {
4451
+ defaultValue: ""
4452
+ });
4453
+ answers.include = raw.split(",").map((value) => value.trim()).filter(Boolean);
4454
+ }
4455
+ const sizeChoice = await select(io, "How large should each part be?", SIZE_CHOICES, {
4456
+ defaultValue: "120k"
4457
+ });
4458
+ if (sizeChoice === "custom") {
4459
+ const raw = await text(io, "Part size (e.g. 150k, 1.5mb, 900000)", {
4460
+ defaultValue: String(answers.maxPartBytes),
4461
+ validate: (value) => parseByteSize(value) === void 0 ? "Not a valid size." : null
4462
+ });
4463
+ answers.maxPartBytes = parseByteSize(raw) ?? answers.maxPartBytes;
4464
+ } else {
4465
+ answers.maxPartBytes = sizeChoice;
4466
+ }
4467
+ const artifactChoices = [
4468
+ { value: "readme", label: "README.md", hint: "orientation page" },
4469
+ { value: "manifest", label: "manifest.json", hint: "required by validate and report" },
4470
+ { value: "parts", label: "parts/", hint: "the code payload" },
4471
+ { value: "navigation", label: "reports/navigation.md", hint: "file \u2192 part index" },
4472
+ { value: "projectTree", label: "reports/project-tree.md", hint: "ASCII tree" },
4473
+ { value: "audit", label: "reports/audit.md", hint: "security and health findings" },
4474
+ { value: "monolith", label: "full-codebase.json + .md", hint: "roughly doubles output" }
4475
+ ];
4476
+ const presetEnabled = ARTIFACT_NAMES.filter((name) => answers.artifacts[name]);
4477
+ const selectedArtifacts = await multiSelect(
4478
+ io,
4479
+ "Which artifacts should be generated?",
4480
+ artifactChoices,
4481
+ { defaults: presetEnabled }
4482
+ );
4483
+ for (const name of ARTIFACT_NAMES) answers.artifacts[name] = selectedArtifacts.includes(name);
4484
+ const enabledCount = ARTIFACT_NAMES.filter((name) => answers.artifacts[name]).length;
4485
+ answers.includeSensitiveFiles = await confirm(
4486
+ io,
4487
+ "Include credential files (.env, *.pem, .npmrc, .ssh/)?",
4488
+ { defaultValue: false }
4489
+ );
4490
+ io.output.write("\n Review\n\n");
4491
+ io.output.write(` project ${answers.preset}
4492
+ `);
4493
+ io.output.write(` include ${answers.include.length ? answers.include.join(", ") : "everything"}
4494
+ `);
4495
+ io.output.write(` part size ${String(answers.maxPartBytes)}
4496
+ `);
4497
+ io.output.write(` artifacts ${enabledCount} of ${ARTIFACT_NAMES.length}
4498
+ `);
4499
+ io.output.write(` credentials ${answers.includeSensitiveFiles ? "included" : "skipped"}
4500
+
4501
+ `);
4502
+ io.output.write(" Will write snapshot.config with your answers, plus every other setting\n");
4503
+ io.output.write(" at its default \u2014 so nothing is hidden behind the docs.\n\n");
4504
+ const proceed = await confirm(io, "Write this and continue?", { defaultValue: true });
4505
+ if (!proceed) throw new PromptCancelled();
4506
+ }
4507
+ const configText = options.minimal ? renderMinimalConfig(answers) : renderConfig(answers);
4508
+ const configName = supportsTypeScriptImports() ? "snapshot.config.ts" : "snapshot.config.mjs";
4509
+ const configPath = path10__namespace.join(options.cwd, configName);
4510
+ if (options.dryRun) {
4511
+ return {
4512
+ configPath,
4513
+ configText,
4514
+ written: false,
4515
+ preset: answers.preset,
4516
+ scripts: [],
4517
+ gitignoreEntries: [],
4518
+ notes
4519
+ };
4520
+ }
4521
+ const present = existingConfig(options.cwd);
4522
+ if (present && present !== configName && !options.force) {
4523
+ throw new Error(
4524
+ `A configuration already exists (${present}). Re-run with --force to replace it.`
4525
+ );
4526
+ }
4527
+ if (fs9__namespace.existsSync(configPath) && !options.force) {
4528
+ throw new Error(`${configName} already exists. Re-run with --force to replace it.`);
4529
+ }
4530
+ fs9__namespace.writeFileSync(configPath, configText, "utf8");
4531
+ const scripts = addScripts(options.cwd, notes);
4532
+ const gitignoreEntries = addGitignore(options.cwd);
4533
+ return {
4534
+ configPath,
4535
+ configText,
4536
+ written: true,
4537
+ preset: answers.preset,
4538
+ scripts,
4539
+ gitignoreEntries,
4540
+ notes
4541
+ };
4542
+ }
4543
+
2712
4544
  // src/index.ts
2713
4545
  var GENERATOR_NAME = "@ankurhalder/codebase-snapshot";
2714
- var GENERATOR_VERSION = "2.0.0";
4546
+ var GENERATOR_VERSION = "2.1.0";
2715
4547
  var SCHEMA_VERSION = "3.0.0";
2716
4548
  function sha256Hex2(input) {
2717
4549
  return crypto__namespace.createHash("sha256").update(input).digest("hex");
2718
4550
  }
2719
- var SUMMARY_EXTENSIONS = [
4551
+ var TS_EXTENSIONS = [
2720
4552
  ".ts",
2721
4553
  ".mts",
2722
4554
  ".cts",
@@ -2724,24 +4556,21 @@ var SUMMARY_EXTENSIONS = [
2724
4556
  ".js",
2725
4557
  ".mjs",
2726
4558
  ".cjs",
2727
- ".jsx",
2728
- ".json",
2729
- ".jsonc",
2730
- ".md",
2731
- ".mdx"
4559
+ ".jsx"
2732
4560
  ];
2733
4561
  function summarizeFile(relativePath, redactedContent) {
2734
4562
  const lower = relativePath.toLowerCase();
2735
- if (!SUMMARY_EXTENSIONS.some((extension) => lower.endsWith(extension))) {
2736
- return void 0;
2737
- }
2738
4563
  if (lower.endsWith(".json") || lower.endsWith(".jsonc")) {
2739
- return summarizeJson(redactedContent);
4564
+ return { summary: summarizeJson(redactedContent), quality: "structural" };
2740
4565
  }
2741
4566
  if (lower.endsWith(".md") || lower.endsWith(".mdx")) {
2742
- return summarizeMarkdown(redactedContent);
4567
+ return { summary: summarizeMarkdown(redactedContent), quality: "structural" };
4568
+ }
4569
+ if (TS_EXTENSIONS.some((extension) => lower.endsWith(extension))) {
4570
+ return { summary: summarizeTypeScriptSource(relativePath, redactedContent), quality: "ast" };
2743
4571
  }
2744
- return summarizeTypeScriptSource(relativePath, redactedContent);
4572
+ const profiled = summarizeWithProfile(relativePath, redactedContent);
4573
+ return profiled ? { summary: profiled.summary, quality: profiled.quality } : void 0;
2745
4574
  }
2746
4575
  function isSourceSummary(summary) {
2747
4576
  return Boolean(summary && typeof summary === "object" && "imports" in summary);
@@ -2767,8 +4596,8 @@ var IMPORT_CANDIDATE_SUFFIXES = [
2767
4596
  ];
2768
4597
  function resolveSpecifier(fromFile, specifier, knownFiles) {
2769
4598
  if (!RELATIVE_SPECIFIER.test(specifier)) return null;
2770
- const baseDir = path9__namespace.posix.dirname(fromFile);
2771
- const joined = path9__namespace.posix.normalize(path9__namespace.posix.join(baseDir, specifier));
4599
+ const baseDir = path10__namespace.posix.dirname(fromFile);
4600
+ const joined = path10__namespace.posix.normalize(path10__namespace.posix.join(baseDir, specifier));
2772
4601
  const candidate = joined.replace(/\.(?:js|mjs|cjs|jsx)$/, "");
2773
4602
  for (const suffix of IMPORT_CANDIDATE_SUFFIXES) {
2774
4603
  const withSuffix = `${joined}${suffix}`;
@@ -2860,11 +4689,11 @@ function verifyInMemory(files, parts, maxPartBytes) {
2860
4689
  return errors;
2861
4690
  }
2862
4691
  function resolveSafeOutputDirs(config) {
2863
- const cwd = path9__namespace.resolve(config.cwd);
2864
- const outDir = path9__namespace.resolve(cwd, config.outDir);
2865
- const tmpDir = path9__namespace.resolve(cwd, config.tmpDir);
2866
- const root = path9__namespace.parse(cwd).root;
2867
- const contains = (parent, child) => child === parent || child.startsWith(`${parent}${path9__namespace.sep}`);
4692
+ const cwd = path10__namespace.resolve(config.cwd);
4693
+ const outDir = path10__namespace.resolve(cwd, config.outDir);
4694
+ const tmpDir = path10__namespace.resolve(cwd, config.tmpDir);
4695
+ const root = path10__namespace.parse(cwd).root;
4696
+ const contains = (parent, child) => child === parent || child.startsWith(`${parent}${path10__namespace.sep}`);
2868
4697
  const reject = (reason) => {
2869
4698
  throw new Error(
2870
4699
  `Refusing to generate: ${reason}. outDir=${outDir} tmpDir=${tmpDir} cwd=${cwd}`
@@ -2883,26 +4712,66 @@ function resolveSafeOutputDirs(config) {
2883
4712
  if (contains(tmpDir, outDir)) reject("outDir is inside tmpDir");
2884
4713
  return { outDir, tmpDir };
2885
4714
  }
4715
+ var PLACEHOLDER_ENTRIES = /* @__PURE__ */ new Set([".gitkeep", ".gitignore", ".DS_Store"]);
4716
+ var OWNERSHIP_MARKER = ".codebase-snapshot";
4717
+ function writeOwnershipMarker(dir) {
4718
+ const marker2 = {
4719
+ generator: GENERATOR_NAME,
4720
+ version: GENERATOR_VERSION,
4721
+ schemaVersion: SCHEMA_VERSION
4722
+ };
4723
+ fs9__namespace.writeFileSync(path10__namespace.join(dir, OWNERSHIP_MARKER), `${JSON.stringify(marker2, null, 2)}
4724
+ `, "utf8");
4725
+ }
4726
+ function isOwnOutput(dir) {
4727
+ if (fs9__namespace.existsSync(path10__namespace.join(dir, OWNERSHIP_MARKER))) return true;
4728
+ const manifestPath = path10__namespace.join(dir, "manifest.json");
4729
+ if (!fs9__namespace.existsSync(manifestPath)) return false;
4730
+ try {
4731
+ const parsed = JSON.parse(fs9__namespace.readFileSync(manifestPath, "utf8"));
4732
+ const generator = parsed.generator;
4733
+ return typeof parsed.schemaVersion === "string" && Array.isArray(parsed.files) && typeof generator === "object" && generator !== null && generator.name === GENERATOR_NAME;
4734
+ } catch {
4735
+ return false;
4736
+ }
4737
+ }
4738
+ function assertReplaceable(target, label) {
4739
+ if (!fs9__namespace.existsSync(target)) return;
4740
+ if (!fs9__namespace.statSync(target).isDirectory()) {
4741
+ throw new Error(
4742
+ `Refusing to write into ${label} ${target}: it exists but is not a directory.`
4743
+ );
4744
+ }
4745
+ const entries = fs9__namespace.readdirSync(target);
4746
+ const meaningful = entries.filter((name) => !PLACEHOLDER_ENTRIES.has(name));
4747
+ if (meaningful.length === 0) return;
4748
+ if (isOwnOutput(target)) return;
4749
+ const preview = meaningful.slice(0, 5).join(", ");
4750
+ const more = meaningful.length > 5 ? `, and ${meaningful.length - 5} more` : "";
4751
+ throw new Error(
4752
+ `Refusing to replace ${label} ${target}: it exists, is not empty, and does not look like a snapshot this generator wrote (found ${preview}${more}). Point ${label} at a new or empty directory, or delete it yourself first.`
4753
+ );
4754
+ }
2886
4755
  function swapIntoPlace(tmpDir, outDir) {
2887
4756
  const backupDir = `${outDir}.backup-${process.pid}-${Date.now()}`;
2888
- const hadPrevious = fs8__namespace.existsSync(outDir);
2889
- if (hadPrevious) fs8__namespace.renameSync(outDir, backupDir);
4757
+ const hadPrevious = fs9__namespace.existsSync(outDir);
4758
+ if (hadPrevious) fs9__namespace.renameSync(outDir, backupDir);
2890
4759
  try {
2891
4760
  try {
2892
- fs8__namespace.renameSync(tmpDir, outDir);
4761
+ fs9__namespace.renameSync(tmpDir, outDir);
2893
4762
  } catch (err) {
2894
4763
  if (err.code === "EXDEV") {
2895
- fs8__namespace.cpSync(tmpDir, outDir, { recursive: true });
2896
- fs8__namespace.rmSync(tmpDir, { recursive: true, force: true });
4764
+ fs9__namespace.cpSync(tmpDir, outDir, { recursive: true });
4765
+ fs9__namespace.rmSync(tmpDir, { recursive: true, force: true });
2897
4766
  } else {
2898
4767
  throw err;
2899
4768
  }
2900
4769
  }
2901
- if (hadPrevious) fs8__namespace.rmSync(backupDir, { recursive: true, force: true });
4770
+ if (hadPrevious) fs9__namespace.rmSync(backupDir, { recursive: true, force: true });
2902
4771
  } catch (err) {
2903
- if (hadPrevious && fs8__namespace.existsSync(backupDir)) {
2904
- if (fs8__namespace.existsSync(outDir)) fs8__namespace.rmSync(outDir, { recursive: true, force: true });
2905
- fs8__namespace.renameSync(backupDir, outDir);
4772
+ if (hadPrevious && fs9__namespace.existsSync(backupDir)) {
4773
+ if (fs9__namespace.existsSync(outDir)) fs9__namespace.rmSync(outDir, { recursive: true, force: true });
4774
+ fs9__namespace.renameSync(backupDir, outDir);
2906
4775
  }
2907
4776
  throw err;
2908
4777
  }
@@ -2966,7 +4835,7 @@ function generateSnapshot(configInput, options) {
2966
4835
  const gitInfo = getGitInfo(config.cwd, config.branch);
2967
4836
  const collection = collectFiles(config.cwd, {
2968
4837
  includePatterns: config.include,
2969
- excludePatterns: config.excludePatterns,
4838
+ excludePatterns: [...config.excludePatterns, ...config.presetExclude],
2970
4839
  includeSensitiveFiles: config.includeSensitiveFiles
2971
4840
  });
2972
4841
  const files = [];
@@ -2979,10 +4848,10 @@ function generateSnapshot(configInput, options) {
2979
4848
  let totalSizeBytes = 0;
2980
4849
  let largeFileCount = 0;
2981
4850
  for (const relativePath of collection.files) {
2982
- const absolutePath = path9__namespace.join(config.cwd, relativePath);
4851
+ const absolutePath = path10__namespace.join(config.cwd, relativePath);
2983
4852
  let stats;
2984
4853
  try {
2985
- stats = fs8__namespace.statSync(absolutePath);
4854
+ stats = fs9__namespace.statSync(absolutePath);
2986
4855
  if (!stats.isFile()) {
2987
4856
  skipped.push({ path: relativePath, reason: "not-file" });
2988
4857
  continue;
@@ -2997,7 +4866,7 @@ function generateSnapshot(configInput, options) {
2997
4866
  }
2998
4867
  let buffer;
2999
4868
  try {
3000
- buffer = fs8__namespace.readFileSync(absolutePath);
4869
+ buffer = fs9__namespace.readFileSync(absolutePath);
3001
4870
  } catch {
3002
4871
  skipped.push({ path: relativePath, reason: "read-error" });
3003
4872
  continue;
@@ -3007,10 +4876,10 @@ function generateSnapshot(configInput, options) {
3007
4876
  const lineCount = countLines(rawContent);
3008
4877
  totalLines += lineCount;
3009
4878
  totalSizeBytes += stats.size;
3010
- const layer = classifyLayer(relativePath, config.layers);
4879
+ const layer2 = classifyLayer(relativePath, config.layers);
3011
4880
  const kind = classifyKind(relativePath, config.kinds);
3012
4881
  const language = detectLanguage(relativePath);
3013
- bump(layerCounts, layer);
4882
+ bump(layerCounts, layer2);
3014
4883
  bump(kindCounts, kind);
3015
4884
  const { redacted, findings } = scanAndRedactSecrets(
3016
4885
  rawContent,
@@ -3034,8 +4903,11 @@ function generateSnapshot(configInput, options) {
3034
4903
  includedLines = preview.includedLines;
3035
4904
  }
3036
4905
  let summary;
4906
+ let summaryQuality;
3037
4907
  try {
3038
- summary = summarizeFile(relativePath, redacted);
4908
+ const outcome = summarizeFile(relativePath, redacted);
4909
+ summary = outcome?.summary;
4910
+ summaryQuality = outcome?.quality;
3039
4911
  if (isSourceSummary(summary) && summary.parseErrors) {
3040
4912
  warnings.push(
3041
4913
  `Parse errors in ${relativePath} (${summary.parseErrors}); its summary is incomplete.`
@@ -3052,7 +4924,7 @@ function generateSnapshot(configInput, options) {
3052
4924
  lines: lineCount,
3053
4925
  hashSha256,
3054
4926
  part: "",
3055
- layer,
4927
+ layer: layer2,
3056
4928
  kind,
3057
4929
  language,
3058
4930
  inclusionMode: isPreviewTruncated ? "preview" : "full",
@@ -3062,6 +4934,7 @@ function generateSnapshot(configInput, options) {
3062
4934
  includedLines,
3063
4935
  includedBytes: Buffer.byteLength(finalContent, "utf8"),
3064
4936
  summary,
4937
+ summaryQuality,
3065
4938
  content: finalContent
3066
4939
  });
3067
4940
  }
@@ -3102,10 +4975,11 @@ function generateSnapshot(configInput, options) {
3102
4975
  includeSensitiveFiles: config.includeSensitiveFiles,
3103
4976
  artifacts: { ...config.artifacts },
3104
4977
  include: [...config.include],
4978
+ preset: config.preset,
3105
4979
  layersMode: config.layersMode,
3106
4980
  budgets: { default: config.budgets.default, byLayer: { ...config.budgets.byLayer } },
3107
4981
  excludePatterns: [...config.excludePatterns],
3108
- layers: config.layers.map((layer) => layer.name),
4982
+ layers: config.layers.map((layer2) => layer2.name),
3109
4983
  kinds: config.kinds.map((kind) => kind.kind)
3110
4984
  },
3111
4985
  counts: {
@@ -3192,45 +5066,48 @@ function generateSnapshot(configInput, options) {
3192
5066
  return manifest;
3193
5067
  }
3194
5068
  const { outDir, tmpDir } = resolveSafeOutputDirs(config);
5069
+ assertReplaceable(tmpDir, "tmpDir");
5070
+ assertReplaceable(outDir, "outDir");
3195
5071
  const { artifacts } = config;
3196
- fs8__namespace.rmSync(tmpDir, { recursive: true, force: true });
3197
- fs8__namespace.mkdirSync(tmpDir, { recursive: true });
3198
- const partsDir = path9__namespace.join(tmpDir, "parts");
3199
- const reportsDir = path9__namespace.join(tmpDir, "reports");
3200
- if (artifacts.parts) fs8__namespace.mkdirSync(partsDir, { recursive: true });
5072
+ fs9__namespace.rmSync(tmpDir, { recursive: true, force: true });
5073
+ fs9__namespace.mkdirSync(tmpDir, { recursive: true });
5074
+ writeOwnershipMarker(tmpDir);
5075
+ const partsDir = path10__namespace.join(tmpDir, "parts");
5076
+ const reportsDir = path10__namespace.join(tmpDir, "reports");
5077
+ if (artifacts.parts) fs9__namespace.mkdirSync(partsDir, { recursive: true });
3201
5078
  if (artifacts.navigation || artifacts.projectTree || artifacts.audit) {
3202
- fs8__namespace.mkdirSync(reportsDir, { recursive: true });
5079
+ fs9__namespace.mkdirSync(reportsDir, { recursive: true });
3203
5080
  }
3204
5081
  if (artifacts.parts) {
3205
5082
  for (const part of parts) {
3206
- fs8__namespace.writeFileSync(path9__namespace.join(partsDir, part.filename), part.markdown, "utf8");
5083
+ fs9__namespace.writeFileSync(path10__namespace.join(partsDir, part.filename), part.markdown, "utf8");
3207
5084
  }
3208
5085
  }
3209
5086
  if (artifacts.projectTree) {
3210
5087
  const projectTree = generateProjectTree(files.map((file) => file.relativePath));
3211
- fs8__namespace.writeFileSync(path9__namespace.join(reportsDir, "project-tree.md"), projectTree, "utf8");
5088
+ fs9__namespace.writeFileSync(path10__namespace.join(reportsDir, "project-tree.md"), projectTree, "utf8");
3212
5089
  }
3213
5090
  if (artifacts.navigation) {
3214
- fs8__namespace.writeFileSync(path9__namespace.join(reportsDir, "navigation.md"), generateNavigation(manifest), "utf8");
5091
+ fs9__namespace.writeFileSync(path10__namespace.join(reportsDir, "navigation.md"), generateNavigation(manifest), "utf8");
3215
5092
  }
3216
5093
  if (artifacts.audit) {
3217
- fs8__namespace.writeFileSync(
3218
- path9__namespace.join(reportsDir, "audit.md"),
5094
+ fs9__namespace.writeFileSync(
5095
+ path10__namespace.join(reportsDir, "audit.md"),
3219
5096
  generateAuditReport(manifest, secretFindings),
3220
5097
  "utf8"
3221
5098
  );
3222
5099
  }
3223
5100
  if (artifacts.readme) {
3224
- fs8__namespace.writeFileSync(path9__namespace.join(tmpDir, "README.md"), generateReadme(manifest), "utf8");
5101
+ fs9__namespace.writeFileSync(path10__namespace.join(tmpDir, "README.md"), generateReadme(manifest), "utf8");
3225
5102
  }
3226
5103
  if (artifacts.manifest) {
3227
- fs8__namespace.writeFileSync(path9__namespace.join(tmpDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf8");
5104
+ fs9__namespace.writeFileSync(path10__namespace.join(tmpDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf8");
3228
5105
  }
3229
5106
  if (monolithJson !== null) {
3230
- fs8__namespace.writeFileSync(path9__namespace.join(tmpDir, "full-codebase.json"), monolithJson, "utf8");
5107
+ fs9__namespace.writeFileSync(path10__namespace.join(tmpDir, "full-codebase.json"), monolithJson, "utf8");
3231
5108
  }
3232
5109
  if (monolithMarkdown !== null) {
3233
- fs8__namespace.writeFileSync(path9__namespace.join(tmpDir, "full-codebase.md"), monolithMarkdown, "utf8");
5110
+ fs9__namespace.writeFileSync(path10__namespace.join(tmpDir, "full-codebase.md"), monolithMarkdown, "utf8");
3234
5111
  }
3235
5112
  swapIntoPlace(tmpDir, outDir);
3236
5113
  runAfterGenerate(config, manifest);
@@ -3241,12 +5118,17 @@ async function generateSnapshotAsync(configInput, options) {
3241
5118
  }
3242
5119
 
3243
5120
  exports.ARTIFACT_NAMES = ARTIFACT_NAMES;
5121
+ exports.DEFAULT_ANSWERS = DEFAULT_ANSWERS;
3244
5122
  exports.DEFAULT_SECRET_PATTERNS = DEFAULT_SECRET_PATTERNS;
3245
5123
  exports.GENERATOR_NAME = GENERATOR_NAME;
3246
5124
  exports.GENERATOR_VERSION = GENERATOR_VERSION;
5125
+ exports.LANGUAGE_PROFILES = LANGUAGE_PROFILES;
3247
5126
  exports.MIN_MAX_PART_BYTES = MIN_MAX_PART_BYTES;
5127
+ exports.PRESETS = PRESETS;
5128
+ exports.PromptCancelled = PromptCancelled;
3248
5129
  exports.SCHEMA_VERSION = SCHEMA_VERSION;
3249
5130
  exports.anchorForPath = anchorForPath;
5131
+ exports.assertReplaceable = assertReplaceable;
3250
5132
  exports.assertUsableConfigInput = assertUsableConfigInput;
3251
5133
  exports.collectFiles = collectFiles;
3252
5134
  exports.compareManifests = compareManifests;
@@ -3259,12 +5141,15 @@ exports.fenceForContent = fenceForContent;
3259
5141
  exports.generateProjectTree = generateProjectTree;
3260
5142
  exports.generateSnapshot = generateSnapshot;
3261
5143
  exports.generateSnapshotAsync = generateSnapshotAsync;
5144
+ exports.getPreset = getPreset;
3262
5145
  exports.globToRegExp = globToRegExp;
3263
5146
  exports.isBinaryBuffer = isBinaryBuffer;
3264
5147
  exports.isBinaryExtension = isBinaryExtension;
3265
5148
  exports.isBinaryFile = isBinaryFile;
5149
+ exports.isInteractive = isInteractive;
3266
5150
  exports.isResolvedConfig = isResolvedConfig;
3267
5151
  exports.isSensitivePath = isSensitivePath;
5152
+ exports.listPresetNames = listPresetNames;
3268
5153
  exports.loadConfigFile = loadConfigFile;
3269
5154
  exports.loadConfigFileDetailed = loadConfigFileDetailed;
3270
5155
  exports.loadManifest = loadManifest;
@@ -3274,16 +5159,21 @@ exports.matchesExclude = matchesExclude;
3274
5159
  exports.normalizeResolvedConfig = normalizeResolvedConfig;
3275
5160
  exports.parseByteSize = parseByteSize;
3276
5161
  exports.partitionFiles = partitionFiles;
5162
+ exports.profileForPath = profileForPath;
5163
+ exports.renderConfig = renderConfig;
3277
5164
  exports.renderDiffText = renderDiffText;
3278
5165
  exports.renderExplainText = renderExplainText;
5166
+ exports.renderMinimalConfig = renderMinimalConfig;
3279
5167
  exports.renumberParts = renumberParts;
3280
5168
  exports.resolveConfig = resolveConfig;
3281
5169
  exports.resolveSafeOutputDirs = resolveSafeOutputDirs;
5170
+ exports.runInit = runInit;
3282
5171
  exports.scanAndRedactSecrets = scanAndRedactSecrets;
3283
5172
  exports.stripJsonComments = stripJsonComments;
3284
5173
  exports.summarizeJson = summarizeJson;
3285
5174
  exports.summarizeMarkdown = summarizeMarkdown;
3286
5175
  exports.summarizeTypeScriptSource = summarizeTypeScriptSource;
5176
+ exports.summarizeWithProfile = summarizeWithProfile;
3287
5177
  exports.supportsTypeScriptImports = supportsTypeScriptImports;
3288
5178
  exports.truncatePreview = truncatePreview;
3289
5179
  exports.truncateToBytes = truncateToBytes;