@ankurhalder/codebase-snapshot 2.0.0 → 2.1.1

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,6 +1055,47 @@ var NEXTJS_KINDS = [
370
1055
  }
371
1056
  ];
372
1057
 
1058
+ // src/presets/node.ts
1059
+ var NODE_LAYERS = [
1060
+ { name: "core", match: (p) => p.startsWith("src/core/") || p.startsWith("core/") },
1061
+ { name: "protocols", match: (p) => p.startsWith("src/protocols/") || p.startsWith("protocols/") },
1062
+ { name: "providers", match: (p) => p.startsWith("src/providers/") || p.startsWith("providers/") },
1063
+ { name: "security", match: (p) => p.startsWith("src/security/") || p.startsWith("security/") },
1064
+ { name: "db", match: (p) => p.startsWith("src/db/") || p.startsWith("db/") || p.startsWith("prisma/") },
1065
+ { name: "api", match: (p) => p.startsWith("src/api/") || p.startsWith("api/") || p.startsWith("routes/") },
1066
+ { name: "services", match: (p) => p.startsWith("src/services/") || p.startsWith("services/") },
1067
+ { name: "controllers", match: (p) => p.startsWith("src/controllers/") || p.startsWith("controllers/") },
1068
+ { name: "scripts", match: (p) => p.startsWith("scripts/") },
1069
+ { name: "docs", match: (p) => p.startsWith("docs/") },
1070
+ {
1071
+ name: "config",
1072
+ match: (p) => p.startsWith(".github/") || p.startsWith(".husky/") || p.startsWith(".") || p.includes("config")
1073
+ },
1074
+ { name: "root", match: (p) => !p.includes("/") }
1075
+ ];
1076
+ var NODE_KINDS = [
1077
+ { kind: "test", match: (p) => /\.(test|spec)\.[jt]sx?$/.test(p.toLowerCase()) },
1078
+ { kind: "route", match: (p) => p.includes("/routes/") || p.includes("route.") || p.includes("/api/") },
1079
+ { kind: "controller", match: (p) => p.includes("controller") },
1080
+ { kind: "service", match: (p) => p.includes("service") },
1081
+ { kind: "provider", match: (p) => p.includes("provider") },
1082
+ { kind: "model", match: (p) => p.includes("model") || p.includes("entity") },
1083
+ { kind: "schema", match: (p) => p.includes("schema") || p.includes("zod") || p.includes("dto") },
1084
+ { kind: "type", match: (p) => p.endsWith(".d.ts") || p.includes("types.") || p.includes("type.") },
1085
+ { kind: "security", match: (p) => p.includes("auth") || p.includes("security") || p.includes("guard") },
1086
+ { kind: "db", match: (p) => p.includes("db") || p.includes("database") || p.includes("migration") },
1087
+ { kind: "data", match: (p) => p.endsWith(".json") || p.endsWith(".csv") },
1088
+ { kind: "doc", match: (p) => p.endsWith(".md") || p.endsWith(".mdx") },
1089
+ { kind: "script", match: (p) => p.startsWith("scripts/") || p.endsWith(".sh") || p.endsWith(".ps1") },
1090
+ {
1091
+ kind: "config",
1092
+ match: (p) => {
1093
+ const base = p.split("/").pop() || "";
1094
+ return ["package.json", "tsconfig.json", "Dockerfile"].includes(base) || base.startsWith(".");
1095
+ }
1096
+ }
1097
+ ];
1098
+
373
1099
  // src/presets/vite.ts
374
1100
  var VITE_LAYERS = [
375
1101
  { name: "pages", match: (p) => p.startsWith("src/pages/") || p.startsWith("src/views/") },
@@ -417,44 +1143,419 @@ var VITE_KINDS = [
417
1143
  }
418
1144
  ];
419
1145
 
420
- // src/presets/node.ts
421
- var NODE_LAYERS = [
422
- { name: "core", match: (p) => p.startsWith("src/core/") || p.startsWith("core/") },
423
- { name: "protocols", match: (p) => p.startsWith("src/protocols/") || p.startsWith("protocols/") },
424
- { name: "providers", match: (p) => p.startsWith("src/providers/") || p.startsWith("providers/") },
425
- { name: "security", match: (p) => p.startsWith("src/security/") || p.startsWith("security/") },
426
- { name: "db", match: (p) => p.startsWith("src/db/") || p.startsWith("db/") || p.startsWith("prisma/") },
427
- { name: "api", match: (p) => p.startsWith("src/api/") || p.startsWith("api/") || p.startsWith("routes/") },
428
- { name: "services", match: (p) => p.startsWith("src/services/") || p.startsWith("services/") },
429
- { name: "controllers", match: (p) => p.startsWith("src/controllers/") || p.startsWith("controllers/") },
430
- { name: "scripts", match: (p) => p.startsWith("scripts/") },
431
- { name: "docs", match: (p) => p.startsWith("docs/") },
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
+ },
432
1294
  {
433
- name: "config",
434
- match: (p) => p.startsWith(".github/") || p.startsWith(".husky/") || p.startsWith(".") || p.includes("config")
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"
435
1302
  },
436
- { name: "root", match: (p) => !p.includes("/") }
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
+ }
437
1354
  ];
438
- var NODE_KINDS = [
439
- { kind: "test", match: (p) => /\.(test|spec)\.[jt]sx?$/.test(p.toLowerCase()) },
440
- { kind: "route", match: (p) => p.includes("/routes/") || p.includes("route.") || p.includes("/api/") },
441
- { kind: "controller", match: (p) => p.includes("controller") },
442
- { kind: "service", match: (p) => p.includes("service") },
443
- { kind: "provider", match: (p) => p.includes("provider") },
444
- { kind: "model", match: (p) => p.includes("model") || p.includes("entity") },
445
- { kind: "schema", match: (p) => p.includes("schema") || p.includes("zod") || p.includes("dto") },
446
- { kind: "type", match: (p) => p.endsWith(".d.ts") || p.includes("types.") || p.includes("type.") },
447
- { kind: "security", match: (p) => p.includes("auth") || p.includes("security") || p.includes("guard") },
448
- { kind: "db", match: (p) => p.includes("db") || p.includes("database") || p.includes("migration") },
449
- { kind: "data", match: (p) => p.endsWith(".json") || p.endsWith(".csv") },
450
- { kind: "doc", match: (p) => p.endsWith(".md") || p.endsWith(".mdx") },
451
- { kind: "script", match: (p) => p.startsWith("scripts/") || p.endsWith(".sh") || p.endsWith(".ps1") },
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 = [
452
1360
  {
453
- kind: "config",
454
- match: (p) => {
455
- const base = p.split("/").pop() || "";
456
- return ["package.json", "tsconfig.json", "Dockerfile"].includes(base) || base.startsWith(".");
457
- }
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"
458
1559
  }
459
1560
  ];
460
1561
 
@@ -500,57 +1601,118 @@ 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
- const branch = childProcess__namespace.execSync("git rev-parse --abbrev-ref HEAD", {
1710
+ const reported = childProcess__namespace.execSync("git rev-parse --abbrev-ref HEAD", {
550
1711
  cwd,
551
1712
  stdio: ["ignore", "pipe", "ignore"],
552
1713
  encoding: "utf8"
553
- }).trim() || fallbackBranch;
1714
+ }).trim();
1715
+ const branch = !reported || reported === "HEAD" ? fallbackBranch : reported;
554
1716
  const commit = childProcess__namespace.execSync("git rev-parse HEAD", {
555
1717
  cwd,
556
1718
  stdio: ["ignore", "pipe", "ignore"],
@@ -678,10 +1840,10 @@ function matchesExclude(relativePath, pattern) {
678
1840
  return posixPath === cleaned || posixPath.startsWith(`${cleaned}/`) || posixPath.includes(`/${cleaned}/`) || posixPath.endsWith(`/${cleaned}`);
679
1841
  }
680
1842
  function readGitignoreMatcher(dir) {
681
- const gitignorePath = path9__namespace.join(dir, ".gitignore");
1843
+ const gitignorePath = path10__namespace.join(dir, ".gitignore");
682
1844
  let raw;
683
1845
  try {
684
- raw = fs8__namespace.readFileSync(gitignorePath, "utf8");
1846
+ raw = fs9__namespace.readFileSync(gitignorePath, "utf8");
685
1847
  } catch {
686
1848
  return () => false;
687
1849
  }
@@ -784,7 +1946,7 @@ function walkDirectory(dir, baseDir, visited) {
784
1946
  const results = { files: [], skipped: [] };
785
1947
  let realDir;
786
1948
  try {
787
- realDir = fs8__namespace.realpathSync(dir);
1949
+ realDir = fs9__namespace.realpathSync(dir);
788
1950
  } catch {
789
1951
  realDir = dir;
790
1952
  }
@@ -793,21 +1955,21 @@ function walkDirectory(dir, baseDir, visited) {
793
1955
  const ignoredByGitignore = dir === baseDir ? readGitignoreMatcher(baseDir) : () => false;
794
1956
  let entries;
795
1957
  try {
796
- entries = fs8__namespace.readdirSync(dir, { withFileTypes: true });
1958
+ entries = fs9__namespace.readdirSync(dir, { withFileTypes: true });
797
1959
  } catch {
798
1960
  results.skipped.push({ path: dir, reason: "read-error", detail: "directory unreadable" });
799
1961
  return results;
800
1962
  }
801
1963
  for (const entry of entries) {
802
1964
  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, "/");
1965
+ const fullPath = path10__namespace.join(dir, entry.name);
1966
+ const relPath = path10__namespace.relative(baseDir, fullPath).replace(/\\/g, "/");
805
1967
  if (ignoredByGitignore(relPath)) continue;
806
1968
  let isDirectory = entry.isDirectory();
807
1969
  let isFile = entry.isFile();
808
1970
  if (entry.isSymbolicLink()) {
809
1971
  try {
810
- const stats = fs8__namespace.statSync(fullPath);
1972
+ const stats = fs9__namespace.statSync(fullPath);
811
1973
  isDirectory = stats.isDirectory();
812
1974
  isFile = stats.isFile();
813
1975
  if (!isDirectory && !isFile) {
@@ -1072,6 +2234,8 @@ function normalizeResolvedConfig(config) {
1072
2234
  config.includeMonolith === true
1073
2235
  ),
1074
2236
  include: resolveInclude(config.include),
2237
+ preset: typeof config.preset === "string" ? config.preset : "generic",
2238
+ presetExclude: Array.isArray(config.presetExclude) ? config.presetExclude : [],
1075
2239
  layersMode: config.layersMode === "replace" ? "replace" : "merge",
1076
2240
  budgets: config.budgets && typeof config.budgets === "object" ? config.budgets : { default: config.maxPartBytes, byLayer: {} },
1077
2241
  hooks: resolveHooks(config.hooks),
@@ -1119,18 +2283,22 @@ function parseJsonish(raw, file) {
1119
2283
  }
1120
2284
  async function loadConfigFileDetailed(cwd) {
1121
2285
  const warnings = [];
2286
+ const errors = [];
1122
2287
  for (const filename of CONFIG_FILENAMES) {
1123
- const fullPath = path9__namespace.join(cwd, filename);
1124
- if (!fs8__namespace.existsSync(fullPath)) continue;
2288
+ const fullPath = path10__namespace.join(cwd, filename);
2289
+ if (!fs9__namespace.existsSync(fullPath)) continue;
1125
2290
  if (filename.endsWith(".json") || filename === ".snapshotrc") {
1126
2291
  try {
1127
2292
  return {
1128
- config: parseJsonish(fs8__namespace.readFileSync(fullPath, "utf8"), fullPath),
2293
+ config: parseJsonish(fs9__namespace.readFileSync(fullPath, "utf8"), fullPath),
1129
2294
  file: fullPath,
1130
- warnings
2295
+ warnings,
2296
+ errors
1131
2297
  };
1132
2298
  } catch (err) {
1133
- warnings.push(err.message);
2299
+ const message = err.message;
2300
+ warnings.push(message);
2301
+ errors.push(message);
1134
2302
  continue;
1135
2303
  }
1136
2304
  }
@@ -1144,28 +2312,37 @@ async function loadConfigFileDetailed(cwd) {
1144
2312
  const imported = await import(url.pathToFileURL(fullPath).href);
1145
2313
  const config = imported.default ?? imported;
1146
2314
  if (!config || typeof config !== "object") {
1147
- warnings.push(`${fullPath} does not export a configuration object.`);
2315
+ const message = `${fullPath} does not export a configuration object.`;
2316
+ warnings.push(message);
2317
+ errors.push(message);
1148
2318
  continue;
1149
2319
  }
1150
- return { config, file: fullPath, warnings };
2320
+ return { config, file: fullPath, warnings, errors };
1151
2321
  } catch (err) {
1152
- warnings.push(`Could not load ${fullPath}: ${err.message}`);
2322
+ const message = `Could not load ${fullPath}: ${err.message}`;
2323
+ warnings.push(message);
2324
+ errors.push(message);
1153
2325
  continue;
1154
2326
  }
1155
2327
  }
1156
- const pkgJsonPath = path9__namespace.join(cwd, "package.json");
1157
- if (fs8__namespace.existsSync(pkgJsonPath)) {
2328
+ const pkgJsonPath = path10__namespace.join(cwd, "package.json");
2329
+ if (fs9__namespace.existsSync(pkgJsonPath)) {
1158
2330
  try {
1159
- const parsed = parseJsonish(fs8__namespace.readFileSync(pkgJsonPath, "utf8"), pkgJsonPath);
2331
+ const parsed = parseJsonish(fs9__namespace.readFileSync(pkgJsonPath, "utf8"), pkgJsonPath);
1160
2332
  const snapshotKey = parsed.snapshot;
1161
2333
  if (snapshotKey && typeof snapshotKey === "object") {
1162
- return { config: snapshotKey, file: pkgJsonPath, warnings };
2334
+ return { config: snapshotKey, file: pkgJsonPath, warnings, errors };
2335
+ }
2336
+ if (snapshotKey !== void 0) {
2337
+ warnings.push(`${pkgJsonPath}: the "snapshot" key must be an object; it was ignored.`);
1163
2338
  }
1164
2339
  } catch (err) {
1165
- warnings.push(err.message);
2340
+ const message = err.message;
2341
+ warnings.push(message);
2342
+ errors.push(message);
1166
2343
  }
1167
2344
  }
1168
- return { config: null, file: null, warnings };
2345
+ return { config: null, file: null, warnings, errors };
1169
2346
  }
1170
2347
  async function loadConfigFile(cwd) {
1171
2348
  const loaded = await loadConfigFileDetailed(cwd);
@@ -1264,7 +2441,9 @@ function boundedBytes(value, fallback, minimum, label) {
1264
2441
  `${label} must be a byte count such as 120000, "120k" or "1.5mb" (received ${describeBytes(value)}).`
1265
2442
  );
1266
2443
  }
1267
- if (parsed <= 0) return fallback;
2444
+ if (parsed <= 0) {
2445
+ throw new Error(`${label} must be a positive byte count (received ${describeBytes(value)}).`);
2446
+ }
1268
2447
  if (parsed < minimum) {
1269
2448
  throw new Error(`${label} must be at least ${minimum} bytes (received ${describeBytes(value)}).`);
1270
2449
  }
@@ -1304,12 +2483,12 @@ function resolveBudgets(budgets, fallback) {
1304
2483
  if (typeof budgets.byLayer !== "object" || budgets.byLayer === null) {
1305
2484
  throw new Error("`budgets.byLayer` must be an object of layer name to byte size.");
1306
2485
  }
1307
- for (const [layer, value] of Object.entries(budgets.byLayer)) {
1308
- byLayer[layer] = boundedBytes(
2486
+ for (const [layer2, value] of Object.entries(budgets.byLayer)) {
2487
+ byLayer[layer2] = boundedBytes(
1309
2488
  value,
1310
2489
  fallback,
1311
2490
  MIN_MAX_PART_BYTES,
1312
- `budgets.byLayer["${layer}"]`
2491
+ `budgets.byLayer["${layer2}"]`
1313
2492
  );
1314
2493
  }
1315
2494
  }
@@ -1330,18 +2509,22 @@ function resolveHooks(hooks) {
1330
2509
  }
1331
2510
  function resolveCandidateCwd(userConfig, cliOptions, baseDir) {
1332
2511
  const candidate = cliOptions?.cwd || userConfig?.cwd || baseDir || process.cwd();
1333
- const resolved = path9__namespace.resolve(candidate);
1334
- if (!fs8__namespace.existsSync(resolved)) {
2512
+ const resolved = path10__namespace.resolve(candidate);
2513
+ if (!fs9__namespace.existsSync(resolved)) {
1335
2514
  throw new Error(`Working directory does not exist: ${resolved}`);
1336
2515
  }
1337
- if (!fs8__namespace.statSync(resolved).isDirectory()) {
2516
+ if (!fs9__namespace.statSync(resolved).isDirectory()) {
1338
2517
  throw new Error(`Working directory is not a directory: ${resolved}`);
1339
2518
  }
1340
2519
  return resolved;
1341
2520
  }
1342
2521
  function resolveConfig(userConfig, cliOptions, baseDir) {
1343
2522
  const cwd = resolveCandidateCwd(userConfig ?? null, cliOptions, baseDir);
1344
- const preset = detectPreset(cwd);
2523
+ const forcedPreset = userConfig?.preset;
2524
+ if (forcedPreset !== void 0 && !getPreset(forcedPreset)) {
2525
+ throw new Error(unknownPresetMessage(forcedPreset));
2526
+ }
2527
+ const preset = detectPreset(cwd, forcedPreset);
1345
2528
  const layersMode = resolveLayersMode(userConfig?.layersMode);
1346
2529
  const layers = userConfig?.layers && userConfig.layers.length > 0 ? layersMode === "replace" ? userConfig.layers : [...userConfig.layers, ...preset.layers] : preset.layers;
1347
2530
  const kinds = userConfig?.kinds && userConfig.kinds.length > 0 ? layersMode === "replace" ? userConfig.kinds : [...userConfig.kinds, ...preset.kinds] : preset.kinds;
@@ -1377,6 +2560,8 @@ function resolveConfig(userConfig, cliOptions, baseDir) {
1377
2560
  ),
1378
2561
  includeSensitiveFiles: cliOptions?.includeSensitiveFiles ?? userConfig?.includeSensitiveFiles ?? false,
1379
2562
  include: resolveInclude(userConfig?.include),
2563
+ preset: preset.presetName,
2564
+ presetExclude: [...preset.exclude],
1380
2565
  artifacts,
1381
2566
  layersMode,
1382
2567
  budgets,
@@ -1411,8 +2596,8 @@ function matchRule(filePath, matcher) {
1411
2596
  return false;
1412
2597
  }
1413
2598
  function classifyLayer(filePath, layers) {
1414
- for (const layer of layers) {
1415
- if (matchRule(filePath, layer.match)) return layer.name;
2599
+ for (const layer2 of layers) {
2600
+ if (matchRule(filePath, layer2.match)) return layer2.name;
1416
2601
  }
1417
2602
  return "other";
1418
2603
  }
@@ -1493,11 +2678,11 @@ function generateAuditReport(manifest, secretFindings) {
1493
2678
  "| :--- | :--- | :--- |"
1494
2679
  );
1495
2680
  const totalFiles = manifest.counts.totalFiles || 1;
1496
- for (const [layer, count] of Object.entries(manifest.layers).sort(
2681
+ for (const [layer2, count] of Object.entries(manifest.layers).sort(
1497
2682
  (a, b) => b[1] - a[1] || (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0)
1498
2683
  )) {
1499
2684
  const pct = (count / totalFiles * 100).toFixed(1);
1500
- lines.push(`| \`${layer}\` | ${count} | ${pct}% |`);
2685
+ lines.push(`| \`${layer2}\` | ${count} | ${pct}% |`);
1501
2686
  }
1502
2687
  lines.push("");
1503
2688
  lines.push(
@@ -1605,21 +2790,21 @@ function partitionFiles(files, maxPartBytes, contentFingerprint) {
1605
2790
  section.byteSize = Buffer.byteLength(section.text, "utf8");
1606
2791
  continue;
1607
2792
  }
1608
- const marker = "\n... [part truncated: cannot fit budget] ...\n";
1609
- const target = Math.max(0, budget - Buffer.byteLength(marker, "utf8"));
2793
+ const marker2 = "\n... [part truncated: cannot fit budget] ...\n";
2794
+ const target = Math.max(0, budget - Buffer.byteLength(marker2, "utf8"));
1610
2795
  const clipped = truncateToBytes(rendered[oversized].markdown, target);
1611
- rendered[oversized] = rehashPart(rendered[oversized], clipped.content + marker);
2796
+ rendered[oversized] = rehashPart(rendered[oversized], clipped.content + marker2);
1612
2797
  return rendered;
1613
2798
  }
1614
2799
  return enforceBudget(renderGroups(groups, contentFingerprint), budget);
1615
2800
  }
1616
2801
  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"));
2802
+ const marker2 = "\n... [part truncated: cannot fit budget] ...\n";
2803
+ const target = Math.max(0, budget - Buffer.byteLength(marker2, "utf8"));
1619
2804
  return parts.map((part) => {
1620
2805
  if (part.metadata.byteSize <= budget) return part;
1621
2806
  const clipped = truncateToBytes(part.markdown, target);
1622
- return rehashPart(part, clipped.content + marker);
2807
+ return rehashPart(part, clipped.content + marker2);
1623
2808
  });
1624
2809
  }
1625
2810
  function renumberParts(parts, contentFingerprint) {
@@ -1627,10 +2812,10 @@ function renumberParts(parts, contentFingerprint) {
1627
2812
  if (total === 0) return [];
1628
2813
  const zeroPad = (index) => String(index).padStart(Math.max(3, String(total).length), "0");
1629
2814
  const nameFor = (position) => `part-${zeroPad(position)}.md`;
1630
- const marker = "## Contents\n";
2815
+ const marker2 = "## Contents\n";
1631
2816
  return parts.map((part, index) => {
1632
- const markerIndex = part.markdown.indexOf(marker);
1633
- const body = markerIndex === -1 ? "" : part.markdown.slice(markerIndex + marker.length);
2817
+ const markerIndex = part.markdown.indexOf(marker2);
2818
+ const body = markerIndex === -1 ? "" : part.markdown.slice(markerIndex + marker2.length);
1634
2819
  const included = part.metadata.includedFiles;
1635
2820
  if (markerIndex === -1) {
1636
2821
  throw new Error(`${part.filename} has no "## Contents" section to re-number.`);
@@ -1688,9 +2873,9 @@ function topEntries(counts, limit) {
1688
2873
  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
2874
  }
1690
2875
  function prepareSection(file, sectionBudget) {
1691
- let text = renderFileSection(file);
1692
- let byteSize = Buffer.byteLength(text, "utf8");
1693
- if (byteSize <= sectionBudget) return { file, text, byteSize };
2876
+ let text2 = renderFileSection(file);
2877
+ let byteSize = Buffer.byteLength(text2, "utf8");
2878
+ if (byteSize <= sectionBudget) return { file, text: text2, byteSize };
1694
2879
  const shrinkBody = () => {
1695
2880
  const content = file.content ?? "";
1696
2881
  const overhead = byteSize - Buffer.byteLength(content, "utf8");
@@ -1699,24 +2884,24 @@ function prepareSection(file, sectionBudget) {
1699
2884
  const truncated = truncateToBytes(content, allowed);
1700
2885
  if (!truncated.isTruncated) return;
1701
2886
  applyTruncatedContent(file, truncated.content);
1702
- text = renderFileSection(file);
1703
- byteSize = Buffer.byteLength(text, "utf8");
2887
+ text2 = renderFileSection(file);
2888
+ byteSize = Buffer.byteLength(text2, "utf8");
1704
2889
  };
1705
2890
  shrinkBody();
1706
2891
  if (byteSize > sectionBudget && file.summary) {
1707
2892
  file.summary = void 0;
1708
2893
  file.summaryOmitted = true;
1709
- text = renderFileSection(file);
1710
- byteSize = Buffer.byteLength(text, "utf8");
2894
+ text2 = renderFileSection(file);
2895
+ byteSize = Buffer.byteLength(text2, "utf8");
1711
2896
  shrinkBody();
1712
2897
  }
1713
2898
  if (byteSize > sectionBudget) {
1714
2899
  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");
2900
+ const truncated = truncateToBytes(text2, Math.max(0, target));
2901
+ text2 = truncated.content + SECTION_TRUNCATION_NOTICE;
2902
+ byteSize = Buffer.byteLength(text2, "utf8");
1718
2903
  }
1719
- return { file, text, byteSize };
2904
+ return { file, text: text2, byteSize };
1720
2905
  }
1721
2906
  function applyTruncatedContent(file, content) {
1722
2907
  file.content = content;
@@ -1838,6 +3023,7 @@ function renderFileSection(file) {
1838
3023
  `- Source Lines: ${file.originalLines}`,
1839
3024
  `- Included Lines: ${file.includedLines}`,
1840
3025
  `- SHA-256: ${file.hashSha256}`,
3026
+ ...file.summaryQuality ? [`- Summary Quality: ${file.summaryQuality}`] : [],
1841
3027
  ...renderSummary(file.summary),
1842
3028
  ...file.summaryOmitted ? ["- Summary: (omitted: exceeds the part budget)"] : [],
1843
3029
  "",
@@ -2085,7 +3271,7 @@ var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
2085
3271
  ".so"
2086
3272
  ]);
2087
3273
  function isBinaryExtension(filePath) {
2088
- const ext = path9__namespace.extname(filePath).toLowerCase();
3274
+ const ext = path10__namespace.extname(filePath).toLowerCase();
2089
3275
  return BINARY_EXTENSIONS.has(ext);
2090
3276
  }
2091
3277
  function isBinaryBuffer(buffer) {
@@ -2099,9 +3285,9 @@ function isBinaryFile(absolutePath) {
2099
3285
  if (isBinaryExtension(absolutePath)) return true;
2100
3286
  let fd;
2101
3287
  try {
2102
- fd = fs8__namespace.openSync(absolutePath, "r");
3288
+ fd = fs9__namespace.openSync(absolutePath, "r");
2103
3289
  const buffer = Buffer.alloc(8192);
2104
- const bytesRead = fs8__namespace.readSync(fd, buffer, 0, 8192, 0);
3290
+ const bytesRead = fs9__namespace.readSync(fd, buffer, 0, 8192, 0);
2105
3291
  if (bytesRead === 0) return false;
2106
3292
  return isBinaryBuffer(buffer.subarray(0, bytesRead));
2107
3293
  } catch {
@@ -2109,7 +3295,7 @@ function isBinaryFile(absolutePath) {
2109
3295
  } finally {
2110
3296
  if (fd !== void 0) {
2111
3297
  try {
2112
- fs8__namespace.closeSync(fd);
3298
+ fs9__namespace.closeSync(fd);
2113
3299
  } catch {
2114
3300
  }
2115
3301
  }
@@ -2169,10 +3355,10 @@ var LANGUAGE_BY_BASENAME = {
2169
3355
  };
2170
3356
  function detectLanguage(filePath) {
2171
3357
  const normalized = filePath.replace(/\\/g, "/");
2172
- const base = path9__namespace.posix.basename(normalized);
3358
+ const base = path10__namespace.posix.basename(normalized);
2173
3359
  if (LANGUAGE_BY_BASENAME[base]) return LANGUAGE_BY_BASENAME[base];
2174
3360
  if (base === ".env" || base.startsWith(".env.")) return "dotenv";
2175
- const ext = path9__namespace.posix.extname(base).toLowerCase();
3361
+ const ext = path10__namespace.posix.extname(base).toLowerCase();
2176
3362
  return LANGUAGE_BY_EXTENSION[ext] ?? "text";
2177
3363
  }
2178
3364
  function emptyDrift() {
@@ -2191,8 +3377,8 @@ function sha256Hex(buffer) {
2191
3377
  function validateSnapshot(configInput) {
2192
3378
  assertUsableConfigInput(configInput);
2193
3379
  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)) {
3380
+ const manifestPath = path10__namespace.resolve(config.cwd, config.outDir, "manifest.json");
3381
+ if (!fs9__namespace.existsSync(manifestPath)) {
2196
3382
  return {
2197
3383
  valid: false,
2198
3384
  manifestPresent: false,
@@ -2203,7 +3389,7 @@ function validateSnapshot(configInput) {
2203
3389
  }
2204
3390
  let manifest;
2205
3391
  try {
2206
- manifest = JSON.parse(fs8__namespace.readFileSync(manifestPath, "utf8"));
3392
+ manifest = JSON.parse(fs9__namespace.readFileSync(manifestPath, "utf8"));
2207
3393
  } catch (err) {
2208
3394
  return {
2209
3395
  valid: false,
@@ -2226,7 +3412,7 @@ function validateSnapshot(configInput) {
2226
3412
  }
2227
3413
  const drift = emptyDrift();
2228
3414
  const warnings = [];
2229
- const snapshotDir = path9__namespace.join(config.cwd, config.outDir);
3415
+ const snapshotDir = path10__namespace.resolve(config.cwd, config.outDir);
2230
3416
  const manifestFileMap = /* @__PURE__ */ new Map();
2231
3417
  for (const file of manifest.files) {
2232
3418
  if (file && typeof file.relativePath === "string") {
@@ -2238,7 +3424,7 @@ function validateSnapshot(configInput) {
2238
3424
  );
2239
3425
  const collection = collectFiles(config.cwd, {
2240
3426
  includePatterns: config.include,
2241
- excludePatterns: config.excludePatterns,
3427
+ excludePatterns: [...config.excludePatterns, ...config.presetExclude],
2242
3428
  includeSensitiveFiles: config.includeSensitiveFiles
2243
3429
  });
2244
3430
  for (const diskFile of collection.files) {
@@ -2250,7 +3436,7 @@ function validateSnapshot(configInput) {
2250
3436
  }
2251
3437
  let diskHash;
2252
3438
  try {
2253
- diskHash = sha256Hex(fs8__namespace.readFileSync(path9__namespace.join(config.cwd, diskFile)));
3439
+ diskHash = sha256Hex(fs9__namespace.readFileSync(path10__namespace.join(config.cwd, diskFile)));
2254
3440
  } catch {
2255
3441
  drift.missingOnDisk.push(diskFile);
2256
3442
  continue;
@@ -2260,10 +3446,11 @@ function validateSnapshot(configInput) {
2260
3446
  }
2261
3447
  }
2262
3448
  for (const snapshotFile of manifestFileMap.keys()) {
2263
- if (!fs8__namespace.existsSync(path9__namespace.join(config.cwd, snapshotFile))) {
3449
+ if (!fs9__namespace.existsSync(path10__namespace.join(config.cwd, snapshotFile))) {
2264
3450
  drift.missingOnDisk.push(snapshotFile);
2265
3451
  }
2266
3452
  }
3453
+ drift.missingOnDisk = [...new Set(drift.missingOnDisk)];
2267
3454
  const maxEffectiveBudget = Math.max(
2268
3455
  config.maxPartBytes,
2269
3456
  config.budgets.default,
@@ -2274,10 +3461,10 @@ function validateSnapshot(configInput) {
2274
3461
  if (!part || typeof part.filename !== "string") continue;
2275
3462
  partNames.add(part.filename);
2276
3463
  if (!config.artifacts.parts) continue;
2277
- const partPath = path9__namespace.join(snapshotDir, "parts", part.filename);
3464
+ const partPath = path10__namespace.join(snapshotDir, "parts", part.filename);
2278
3465
  let buffer;
2279
3466
  try {
2280
- buffer = fs8__namespace.readFileSync(partPath);
3467
+ buffer = fs9__namespace.readFileSync(partPath);
2281
3468
  } catch {
2282
3469
  drift.partIssues.push({ part: part.filename, issue: "missing from snapshot/parts" });
2283
3470
  continue;
@@ -2364,6 +3551,11 @@ function validateSnapshot(configInput) {
2364
3551
  }
2365
3552
  }
2366
3553
  }
3554
+ if (recorded?.preset !== void 0 && recorded.preset !== config.preset) {
3555
+ drift.configMismatches.push(
3556
+ `preset: manifest ${recorded.preset} vs current ${config.preset}`
3557
+ );
3558
+ }
2367
3559
  if (recorded?.layersMode !== void 0 && recorded.layersMode !== config.layersMode) {
2368
3560
  drift.configMismatches.push(
2369
3561
  `layersMode: manifest ${recorded.layersMode} vs current ${config.layersMode}`
@@ -2381,10 +3573,10 @@ function validateSnapshot(configInput) {
2381
3573
  ...Object.keys(recordedByLayer),
2382
3574
  ...Object.keys(config.budgets.byLayer)
2383
3575
  ]);
2384
- for (const layer of Array.from(layerNames).sort()) {
2385
- if (recordedByLayer[layer] !== config.budgets.byLayer[layer]) {
3576
+ for (const layer2 of Array.from(layerNames).sort()) {
3577
+ if (recordedByLayer[layer2] !== config.budgets.byLayer[layer2]) {
2386
3578
  drift.configMismatches.push(
2387
- `budgets.byLayer.${layer}: manifest ${recordedByLayer[layer]} vs current ${config.budgets.byLayer[layer]}`
3579
+ `budgets.byLayer.${layer2}: manifest ${recordedByLayer[layer2]} vs current ${config.budgets.byLayer[layer2]}`
2388
3580
  );
2389
3581
  }
2390
3582
  }
@@ -2467,9 +3659,9 @@ function validateSnapshot(configInput) {
2467
3659
  ["full-codebase.md", manifest.artifacts?.fullMarkdown]
2468
3660
  ]) {
2469
3661
  if (!artifact) continue;
2470
- const artifactPath = path9__namespace.join(snapshotDir, key);
3662
+ const artifactPath = path10__namespace.join(snapshotDir, key);
2471
3663
  try {
2472
- const buffer = fs8__namespace.readFileSync(artifactPath);
3664
+ const buffer = fs9__namespace.readFileSync(artifactPath);
2473
3665
  if (sha256Hex(buffer) !== artifact.sha256 || buffer.length !== artifact.byteSize) {
2474
3666
  drift.partIssues.push({ part: key, issue: "artifact does not match its recorded digest" });
2475
3667
  }
@@ -2525,35 +3717,35 @@ function describeMatcher(matcher) {
2525
3717
  return String(matcher);
2526
3718
  }
2527
3719
  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("/");
3720
+ const absolute = path10__namespace.resolve(cwd, target);
3721
+ const relative3 = path10__namespace.relative(cwd, absolute);
3722
+ if (relative3.startsWith("..") || path10__namespace.isAbsolute(relative3)) return target;
3723
+ return relative3.split(path10__namespace.sep).join("/");
2532
3724
  }
2533
3725
  function findRuleMatch(rules, relativePath) {
2534
3726
  return rules.find(
2535
3727
  (rule) => matchRule(relativePath, rule.match)
2536
3728
  );
2537
3729
  }
2538
- function readManifest(config) {
2539
- const manifestPath = path9__namespace.join(config.cwd, config.outDir, "manifest.json");
3730
+ function readManifest2(config) {
3731
+ const manifestPath = path10__namespace.resolve(config.cwd, config.outDir, "manifest.json");
2540
3732
  try {
2541
- return JSON.parse(fs8__namespace.readFileSync(manifestPath, "utf8"));
3733
+ return JSON.parse(fs9__namespace.readFileSync(manifestPath, "utf8"));
2542
3734
  } catch {
2543
3735
  return null;
2544
3736
  }
2545
3737
  }
2546
3738
  function explainPath(config, target) {
2547
3739
  const relative3 = normalizeTarget(config.cwd, target);
2548
- const absolute = path9__namespace.join(config.cwd, relative3);
3740
+ const absolute = path10__namespace.join(config.cwd, relative3);
2549
3741
  const result = {
2550
3742
  path: relative3,
2551
- exists: fs8__namespace.existsSync(absolute),
3743
+ exists: fs9__namespace.existsSync(absolute),
2552
3744
  included: false
2553
3745
  };
2554
3746
  const collection = collectFiles(config.cwd, {
2555
3747
  includePatterns: config.include,
2556
- excludePatterns: config.excludePatterns,
3748
+ excludePatterns: [...config.excludePatterns, ...config.presetExclude],
2557
3749
  includeSensitiveFiles: config.includeSensitiveFiles
2558
3750
  });
2559
3751
  if (!collection.files.includes(relative3)) {
@@ -2578,16 +3770,16 @@ function explainPath(config, target) {
2578
3770
  if (layerRule) result.layerMatch = describeMatcher(layerRule.match);
2579
3771
  const kindRule = findRuleMatch(config.kinds, relative3);
2580
3772
  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");
3773
+ if (fs9__namespace.existsSync(absolute) && fs9__namespace.statSync(absolute).isFile()) {
3774
+ const buffer = fs9__namespace.readFileSync(absolute);
3775
+ const text2 = buffer.toString("utf8");
2584
3776
  result.bytes = buffer.length;
2585
- result.lines = countLines(text);
2586
- const { findings } = scanAndRedactSecrets(text, config.secretRules, relative3);
3777
+ result.lines = countLines(text2);
3778
+ const { findings } = scanAndRedactSecrets(text2, config.secretRules, relative3);
2587
3779
  result.secretCount = findings.length;
2588
3780
  result.secrets = findings.slice(0, 20).map((finding) => ({ rule: finding.rule, line: finding.line }));
2589
3781
  }
2590
- const manifest = readManifest(config);
3782
+ const manifest = readManifest2(config);
2591
3783
  const entry = manifest?.files.find((file) => file.relativePath === relative3);
2592
3784
  result.part = entry?.part ?? null;
2593
3785
  result.inclusionMode = entry?.inclusionMode;
@@ -2670,10 +3862,16 @@ function compareManifests(before, after) {
2670
3862
  };
2671
3863
  }
2672
3864
  function loadManifest(target) {
2673
- const manifestPath = fs8__namespace.statSync(target).isDirectory() ? path9__namespace.join(target, "manifest.json") : target;
3865
+ let isDirectory;
3866
+ try {
3867
+ isDirectory = fs9__namespace.statSync(target).isDirectory();
3868
+ } catch {
3869
+ throw new Error(`No snapshot found at ${target}: the path does not exist.`);
3870
+ }
3871
+ const manifestPath = isDirectory ? path10__namespace.join(target, "manifest.json") : target;
2674
3872
  let raw;
2675
3873
  try {
2676
- raw = fs8__namespace.readFileSync(manifestPath, "utf8");
3874
+ raw = fs9__namespace.readFileSync(manifestPath, "utf8");
2677
3875
  } catch {
2678
3876
  throw new Error(`Could not read a manifest at ${manifestPath}.`);
2679
3877
  }
@@ -2686,6 +3884,9 @@ function loadManifest(target) {
2686
3884
  if (!parsed || !Array.isArray(parsed.files)) {
2687
3885
  throw new Error(`${manifestPath} is not a snapshot manifest (no "files" array).`);
2688
3886
  }
3887
+ if (!parsed.counts || typeof parsed.counts !== "object") {
3888
+ throw new Error(`${manifestPath} is not a snapshot manifest (no "counts" object).`);
3889
+ }
2689
3890
  return parsed;
2690
3891
  }
2691
3892
  function renderDiffText(diff) {
@@ -2709,14 +3910,659 @@ function renderDiffText(diff) {
2709
3910
  return lines.join("\n");
2710
3911
  }
2711
3912
 
3913
+ // src/wizard/config-template.ts
3914
+ var DEFAULT_ANSWERS = {
3915
+ preset: "generic",
3916
+ maxPartBytes: "300k",
3917
+ largeFileBytes: "200k",
3918
+ include: [],
3919
+ excludePatterns: [],
3920
+ artifacts: {
3921
+ readme: true,
3922
+ manifest: true,
3923
+ parts: true,
3924
+ navigation: true,
3925
+ projectTree: true,
3926
+ audit: true,
3927
+ monolith: false
3928
+ },
3929
+ includeSensitiveFiles: false,
3930
+ outDir: "snapshot"
3931
+ };
3932
+ function list(values) {
3933
+ if (values.length === 0) return "[]";
3934
+ return `[${values.map((value) => JSON.stringify(value)).join(", ")}]`;
3935
+ }
3936
+ function size(value) {
3937
+ return typeof value === "string" ? JSON.stringify(value) : String(value);
3938
+ }
3939
+ var PRESET_NAMES = [
3940
+ "nextjs \xB7 react-vite \xB7 vue-vite \xB7 nuxt \xB7 sveltekit \xB7 angular \xB7 astro",
3941
+ "remix \xB7 solid \xB7 nest \xB7 node-express \xB7 node-lib",
3942
+ "python-django \xB7 python-fastapi \xB7 python-lib \xB7 go \xB7 rust",
3943
+ "java-spring \xB7 dotnet \xB7 php-laravel \xB7 ruby-rails",
3944
+ "monorepo \xB7 docs \xB7 infra \xB7 generic"
3945
+ ];
3946
+ function renderConfig(answers) {
3947
+ const { artifacts } = answers;
3948
+ return `import { defineConfig } from "@ankurhalder/codebase-snapshot";
3949
+
3950
+ export default defineConfig({
3951
+ // \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
3952
+ // Which project shape to use. Detected from the repo when omitted.
3953
+ // ${PRESET_NAMES.join("\n // ")}
3954
+ preset: ${JSON.stringify(answers.preset)},
3955
+
3956
+ // Recorded in the manifest when git cannot report a branch.
3957
+ branch: "main",
3958
+
3959
+ // \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
3960
+ // Where the snapshot is written.
3961
+ outDir: ${JSON.stringify(answers.outDir)},
3962
+ // Scratch directory; the new snapshot is renamed into place atomically.
3963
+ tmpDir: "snapshot.tmp",
3964
+
3965
+ // \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
3966
+ // Hard ceiling per part. "300k", 307200 and "1.5mb" are all accepted.
3967
+ maxPartBytes: ${size(answers.maxPartBytes)},
3968
+ // Files above this size get a head/tail preview instead of the full body.
3969
+ largeFileBytes: ${size(answers.largeFileBytes)},
3970
+ previewHeadLines: 80,
3971
+ previewTailLines: 40,
3972
+ // Different ceilings per layer. Unlisted layers use \`default\`.
3973
+ budgets: {
3974
+ default: ${size(answers.maxPartBytes)},
3975
+ byLayer: {},
3976
+ },
3977
+
3978
+ // \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
3979
+ // Allow-list. Empty means "everything that is not ignored".
3980
+ // Example: ["src/**", "packages/*/src/**"]
3981
+ include: ${list(answers.include)},
3982
+ // Globs or path segments to drop. Example: ["**/*.test.ts", "docs"]
3983
+ excludePatterns: ${list(answers.excludePatterns)},
3984
+
3985
+ // \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
3986
+ // "merge" = the preset's rules plus yours (yours win). "replace" = yours only.
3987
+ layersMode: "merge",
3988
+ // [{ name: "billing", match: "src/features/billing/**" }]
3989
+ layers: [],
3990
+ // [{ kind: "route", match: "src/app/api/**" }]
3991
+ kinds: [],
3992
+
3993
+ // \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
3994
+ // .env, *.pem, .npmrc, .ssh/ and friends are skipped unless this is true.
3995
+ includeSensitiveFiles: ${answers.includeSensitiveFiles},
3996
+ secrets: {
3997
+ // [{ rule: "internal-token", pattern: /CORP-[A-Z0-9]{32}/ }]
3998
+ customPatterns: [],
3999
+ // Set true to drop the 21 built-in rules.
4000
+ disableDefaultPatterns: false,
4001
+ },
4002
+
4003
+ // \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
4004
+ artifacts: {
4005
+ readme: ${artifacts.readme}, // README.md
4006
+ manifest: ${artifacts.manifest}, // manifest.json \u2014 required by validate and report
4007
+ parts: ${artifacts.parts}, // parts/part-XXX.md
4008
+ navigation: ${artifacts.navigation}, // reports/navigation.md
4009
+ projectTree: ${artifacts.projectTree}, // reports/project-tree.md
4010
+ audit: ${artifacts.audit}, // reports/audit.md
4011
+ monolith: ${artifacts.monolith}, // full-codebase.json + .md (roughly doubles output)
4012
+ },
4013
+ // Skip the monolith above this size. null = no limit.
4014
+ maxMonolithBytes: null,
4015
+
4016
+ // \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
4017
+ // afterGenerate: (manifest) => { /* write your own artifact */ }
4018
+ hooks: {},
4019
+ });
4020
+ `;
4021
+ }
4022
+ function renderMinimalConfig(answers) {
4023
+ const lines = [];
4024
+ if (answers.preset !== DEFAULT_ANSWERS.preset) lines.push(` preset: ${JSON.stringify(answers.preset)},`);
4025
+ if (answers.maxPartBytes !== DEFAULT_ANSWERS.maxPartBytes) {
4026
+ lines.push(` maxPartBytes: ${size(answers.maxPartBytes)},`);
4027
+ }
4028
+ if (answers.largeFileBytes !== DEFAULT_ANSWERS.largeFileBytes) {
4029
+ lines.push(` largeFileBytes: ${size(answers.largeFileBytes)},`);
4030
+ }
4031
+ if (answers.include.length > 0) lines.push(` include: ${list(answers.include)},`);
4032
+ if (answers.excludePatterns.length > 0) {
4033
+ lines.push(` excludePatterns: ${list(answers.excludePatterns)},`);
4034
+ }
4035
+ if (answers.includeSensitiveFiles) lines.push(" includeSensitiveFiles: true,");
4036
+ if (answers.outDir !== DEFAULT_ANSWERS.outDir) lines.push(` outDir: ${JSON.stringify(answers.outDir)},`);
4037
+ const changed = Object.keys(answers.artifacts).filter(
4038
+ (name) => answers.artifacts[name] !== DEFAULT_ANSWERS.artifacts[name]
4039
+ );
4040
+ if (changed.length > 0) {
4041
+ const entries = changed.map((name) => ` ${name}: ${answers.artifacts[name]}`).join(",\n");
4042
+ lines.push(` artifacts: {
4043
+ ${entries},
4044
+ },`);
4045
+ }
4046
+ if (lines.length === 0) {
4047
+ return `import { defineConfig } from "@ankurhalder/codebase-snapshot";
4048
+
4049
+ export default defineConfig({});
4050
+ `;
4051
+ }
4052
+ return `import { defineConfig } from "@ankurhalder/codebase-snapshot";
4053
+
4054
+ export default defineConfig({
4055
+ ${lines.join("\n")}
4056
+ });
4057
+ `;
4058
+ }
4059
+ var PromptCancelled = class extends Error {
4060
+ constructor() {
4061
+ super("Prompt cancelled");
4062
+ this.name = "PromptCancelled";
4063
+ }
4064
+ };
4065
+ function defaultIO() {
4066
+ return { input: process.stdin, output: process.stdout };
4067
+ }
4068
+ function isInteractive(io) {
4069
+ return Boolean(io.input.isTTY && io.output.isTTY);
4070
+ }
4071
+ function moveSelection(current, delta, count) {
4072
+ if (count <= 0) return 0;
4073
+ return ((current + delta) % count + count) % count;
4074
+ }
4075
+ function toggleIndex(selected, index) {
4076
+ const next = new Set(selected);
4077
+ if (next.has(index)) next.delete(index);
4078
+ else next.add(index);
4079
+ return next;
4080
+ }
4081
+ function marker(active) {
4082
+ return active ? "\u276F" : " ";
4083
+ }
4084
+ function choiceLine(choice, active) {
4085
+ const hint = choice.hint ? ` ${choice.hint}` : "";
4086
+ return ` ${marker(active)} ${choice.label}${hint}`;
4087
+ }
4088
+ function renderSelect(message, choices, index) {
4089
+ const lines = [`\u25C6 ${message}`, ""];
4090
+ choices.forEach((choice, position) => lines.push(choiceLine(choice, position === index)));
4091
+ lines.push("", " \u2191\u2193 move \xB7 enter select");
4092
+ return lines.join("\n");
4093
+ }
4094
+ function renderMultiSelect(message, choices, index, selected) {
4095
+ const lines = [`\u25C6 ${message}`, ""];
4096
+ choices.forEach((choice, position) => {
4097
+ const box = selected.has(position) ? "\u25FC" : "\u25FB";
4098
+ const hint = choice.hint ? ` ${choice.hint}` : "";
4099
+ lines.push(` ${marker(position === index)} ${box} ${choice.label}${hint}`);
4100
+ });
4101
+ lines.push("", " \u2191\u2193 move \xB7 space toggle \xB7 enter confirm");
4102
+ return lines.join("\n");
4103
+ }
4104
+ function renderConfirm(message, value) {
4105
+ return [
4106
+ `\u25C6 ${message}`,
4107
+ "",
4108
+ ` ${marker(value)} Yes`,
4109
+ ` ${marker(!value)} No`,
4110
+ "",
4111
+ " \u2190\u2192 or y/n \xB7 enter confirm"
4112
+ ].join("\n");
4113
+ }
4114
+ function renderText(message, value, error) {
4115
+ const lines = [`\u25C6 ${message}`, "", ` ${value || "\u258F"}`];
4116
+ if (error) lines.push("", ` \u2716 ${error}`);
4117
+ return lines.join("\n");
4118
+ }
4119
+ function keyReader(input) {
4120
+ const queue = [];
4121
+ const waiters = [];
4122
+ const wasRaw = input.isRaw;
4123
+ const onKey = (_text, key) => {
4124
+ const waiter = waiters.shift();
4125
+ if (waiter) waiter(key);
4126
+ else queue.push(key);
4127
+ };
4128
+ readline__namespace.emitKeypressEvents(input);
4129
+ if (input.isTTY) input.setRawMode(true);
4130
+ input.resume();
4131
+ input.on("keypress", onKey);
4132
+ return {
4133
+ next() {
4134
+ const queued = queue.shift();
4135
+ if (queued) return Promise.resolve(queued);
4136
+ return new Promise((resolve5) => waiters.push(resolve5));
4137
+ },
4138
+ close() {
4139
+ input.off("keypress", onKey);
4140
+ if (input.isTTY) input.setRawMode(wasRaw ?? false);
4141
+ input.pause();
4142
+ }
4143
+ };
4144
+ }
4145
+ var Frame = class {
4146
+ constructor(output) {
4147
+ this.output = output;
4148
+ }
4149
+ output;
4150
+ drawn = 0;
4151
+ draw(text2) {
4152
+ this.erase();
4153
+ this.output.write(`${text2}
4154
+ `);
4155
+ this.drawn = text2.split("\n").length;
4156
+ }
4157
+ erase() {
4158
+ if (this.drawn > 0) this.output.write(`\x1B[${this.drawn}A\x1B[0J`);
4159
+ this.drawn = 0;
4160
+ }
4161
+ };
4162
+ function isCancel(key) {
4163
+ return key.ctrl === true && key.name === "c" || key.ctrl === true && key.name === "d";
4164
+ }
4165
+ async function select(io, message, choices, options = {}) {
4166
+ if (choices.length === 0) throw new Error("select() requires at least one choice.");
4167
+ const fallbackIndex = Math.max(
4168
+ 0,
4169
+ choices.findIndex((choice) => choice.value === options.defaultValue)
4170
+ );
4171
+ if (!isInteractive(io)) {
4172
+ io.output.write(`${renderSelect(message, choices, fallbackIndex)}
4173
+ `);
4174
+ return choices[fallbackIndex].value;
4175
+ }
4176
+ const frame = new Frame(io.output);
4177
+ const reader = keyReader(io.input);
4178
+ let index = fallbackIndex;
4179
+ try {
4180
+ for (; ; ) {
4181
+ frame.draw(renderSelect(message, choices, index));
4182
+ const key = await reader.next();
4183
+ if (isCancel(key)) throw new PromptCancelled();
4184
+ if (key.name === "up") index = moveSelection(index, -1, choices.length);
4185
+ else if (key.name === "down") index = moveSelection(index, 1, choices.length);
4186
+ else if (key.name === "return" || key.name === "enter") {
4187
+ frame.erase();
4188
+ return choices[index].value;
4189
+ } else if (key.name === "j") index = moveSelection(index, 1, choices.length);
4190
+ else if (key.name === "k") index = moveSelection(index, -1, choices.length);
4191
+ }
4192
+ } finally {
4193
+ reader.close();
4194
+ }
4195
+ }
4196
+ async function multiSelect(io, message, choices, options = {}) {
4197
+ const selected = /* @__PURE__ */ new Set();
4198
+ choices.forEach((choice, position) => {
4199
+ if (options.defaults?.includes(choice.value)) selected.add(position);
4200
+ });
4201
+ const result = () => choices.filter((_, position) => selected.has(position)).map((choice) => choice.value);
4202
+ if (!isInteractive(io)) {
4203
+ io.output.write(`${renderMultiSelect(message, choices, 0, selected)}
4204
+ `);
4205
+ return result();
4206
+ }
4207
+ const frame = new Frame(io.output);
4208
+ const reader = keyReader(io.input);
4209
+ let index = 0;
4210
+ try {
4211
+ for (; ; ) {
4212
+ frame.draw(renderMultiSelect(message, choices, index, selected));
4213
+ const key = await reader.next();
4214
+ if (isCancel(key)) throw new PromptCancelled();
4215
+ if (key.name === "up") index = moveSelection(index, -1, choices.length);
4216
+ else if (key.name === "down") index = moveSelection(index, 1, choices.length);
4217
+ else if (key.name === "space") {
4218
+ const next = toggleIndex(selected, index);
4219
+ selected.clear();
4220
+ for (const position of next) selected.add(position);
4221
+ } else if (key.name === "a") {
4222
+ if (selected.size === choices.length) selected.clear();
4223
+ else choices.forEach((_, position) => selected.add(position));
4224
+ } else if (key.name === "return" || key.name === "enter") {
4225
+ frame.erase();
4226
+ return result();
4227
+ }
4228
+ }
4229
+ } finally {
4230
+ reader.close();
4231
+ }
4232
+ }
4233
+ async function confirm(io, message, options = {}) {
4234
+ let value = options.defaultValue ?? true;
4235
+ if (!isInteractive(io)) {
4236
+ io.output.write(`${renderConfirm(message, value)}
4237
+ `);
4238
+ return value;
4239
+ }
4240
+ const frame = new Frame(io.output);
4241
+ const reader = keyReader(io.input);
4242
+ try {
4243
+ for (; ; ) {
4244
+ frame.draw(renderConfirm(message, value));
4245
+ const key = await reader.next();
4246
+ if (isCancel(key)) throw new PromptCancelled();
4247
+ if (key.name === "up" || key.name === "left") value = true;
4248
+ else if (key.name === "down" || key.name === "right") value = false;
4249
+ else if (key.name === "y") value = true;
4250
+ else if (key.name === "n") value = false;
4251
+ else if (key.name === "return" || key.name === "enter") {
4252
+ frame.erase();
4253
+ return value;
4254
+ }
4255
+ }
4256
+ } finally {
4257
+ reader.close();
4258
+ }
4259
+ }
4260
+ async function text(io, message, options = {}) {
4261
+ const fallback = options.defaultValue ?? "";
4262
+ if (!isInteractive(io)) {
4263
+ io.output.write(`${renderText(message, fallback)}
4264
+ `);
4265
+ return fallback;
4266
+ }
4267
+ const frame = new Frame(io.output);
4268
+ const reader = keyReader(io.input);
4269
+ let value = fallback;
4270
+ let error;
4271
+ try {
4272
+ for (; ; ) {
4273
+ frame.draw(renderText(message, value, error));
4274
+ const key = await reader.next();
4275
+ if (isCancel(key)) throw new PromptCancelled();
4276
+ error = void 0;
4277
+ if (key.name === "return" || key.name === "enter") {
4278
+ const problem = options.validate ? options.validate(value) : null;
4279
+ if (problem) {
4280
+ error = problem;
4281
+ continue;
4282
+ }
4283
+ frame.erase();
4284
+ return value;
4285
+ }
4286
+ if (key.name === "backspace") {
4287
+ value = value.slice(0, -1);
4288
+ continue;
4289
+ }
4290
+ if (key.name === "escape") {
4291
+ value = "";
4292
+ continue;
4293
+ }
4294
+ if (_isPrintable(key)) value += _keyText(key);
4295
+ }
4296
+ } finally {
4297
+ reader.close();
4298
+ }
4299
+ }
4300
+ function _isPrintable(key) {
4301
+ return typeof key.sequence === "string" && key.sequence.length === 1 && key.sequence >= " ";
4302
+ }
4303
+ function _keyText(key) {
4304
+ return key.sequence ?? "";
4305
+ }
4306
+
4307
+ // src/wizard/init.ts
4308
+ var CONFIG_CANDIDATES = [
4309
+ "snapshot.config.ts",
4310
+ "snapshot.config.js",
4311
+ "snapshot.config.mjs",
4312
+ "snapshot.config.cjs",
4313
+ "snapshot.config.json",
4314
+ ".snapshotrc.json",
4315
+ ".snapshotrc"
4316
+ ];
4317
+ var SIZE_CHOICES = [
4318
+ { value: "60k", label: "Small", hint: "60 KB \xB7 ~15k tokens \xB7 tight context windows" },
4319
+ { value: "120k", label: "Medium", hint: "120 KB \xB7 ~30k tokens \xB7 recommended" },
4320
+ { value: "300k", label: "Large", hint: "300 KB \xB7 ~75k tokens \xB7 the default" },
4321
+ { value: "custom", label: "Custom\u2026", hint: "type a size like 150k or 1.5mb" }
4322
+ ];
4323
+ function existingConfig(cwd) {
4324
+ for (const name of CONFIG_CANDIDATES) {
4325
+ if (fs9__namespace.existsSync(path10__namespace.join(cwd, name))) return name;
4326
+ }
4327
+ return null;
4328
+ }
4329
+ function artifactAnswers(options) {
4330
+ const artifacts = { ...DEFAULT_ANSWERS.artifacts };
4331
+ for (const name of options.skipArtifacts ?? []) {
4332
+ if (ARTIFACT_NAMES.includes(name)) {
4333
+ artifacts[name] = false;
4334
+ }
4335
+ }
4336
+ if (options.onlyArtifacts && options.onlyArtifacts.length > 0) {
4337
+ const keep = new Set(options.onlyArtifacts);
4338
+ for (const name of ARTIFACT_NAMES) artifacts[name] = keep.has(name);
4339
+ }
4340
+ return artifacts;
4341
+ }
4342
+ function topLevelDirs(cwd) {
4343
+ try {
4344
+ return fs9__namespace.readdirSync(cwd, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules").map((entry) => entry.name).sort();
4345
+ } catch {
4346
+ return [];
4347
+ }
4348
+ }
4349
+ function addScripts(cwd, notes) {
4350
+ const packagePath = path10__namespace.join(cwd, "package.json");
4351
+ if (!fs9__namespace.existsSync(packagePath)) {
4352
+ notes.push("No package.json found, so npm scripts were not added.");
4353
+ return [];
4354
+ }
4355
+ let parsed;
4356
+ try {
4357
+ parsed = JSON.parse(fs9__namespace.readFileSync(packagePath, "utf8"));
4358
+ } catch {
4359
+ notes.push("Could not parse package.json; npm scripts were not added.");
4360
+ return [];
4361
+ }
4362
+ const scripts = parsed.scripts && typeof parsed.scripts === "object" ? parsed.scripts : {};
4363
+ const added = [];
4364
+ if (!scripts.snapshot) {
4365
+ scripts.snapshot = "codebase-snapshot generate";
4366
+ added.push("snapshot");
4367
+ }
4368
+ if (!scripts["snapshot:validate"]) {
4369
+ scripts["snapshot:validate"] = "codebase-snapshot validate";
4370
+ added.push("snapshot:validate");
4371
+ }
4372
+ if (added.length === 0) return [];
4373
+ parsed.scripts = scripts;
4374
+ fs9__namespace.writeFileSync(packagePath, `${JSON.stringify(parsed, null, 2)}
4375
+ `, "utf8");
4376
+ return added;
4377
+ }
4378
+ function addGitignore(cwd) {
4379
+ const ignorePath = path10__namespace.join(cwd, ".gitignore");
4380
+ const existing = fs9__namespace.existsSync(ignorePath) ? fs9__namespace.readFileSync(ignorePath, "utf8") : "";
4381
+ const wanted = ["snapshot/", "snapshot.tmp/"];
4382
+ const added = wanted.filter((entry) => !existing.split(/\r?\n/).includes(entry));
4383
+ if (added.length === 0) return [];
4384
+ const prefix = existing.length === 0 || existing.endsWith("\n") ? "" : "\n";
4385
+ fs9__namespace.writeFileSync(ignorePath, `${existing}${prefix}${added.join("\n")}
4386
+ `, "utf8");
4387
+ return added;
4388
+ }
4389
+ function resolvePresetChoice(answers, presetName) {
4390
+ return {
4391
+ value: presetName,
4392
+ label: presetName,
4393
+ hint: presetName === answers.preset ? "detected" : void 0
4394
+ };
4395
+ }
4396
+ async function runInit(options) {
4397
+ const io = options.io ?? defaultIO();
4398
+ const detected = detectPreset(options.cwd, options.preset);
4399
+ const notes = [];
4400
+ const answers = {
4401
+ preset: detected.presetName,
4402
+ maxPartBytes: options.maxPartBytes ?? detected.recommendedBudget ?? DEFAULT_ANSWERS.maxPartBytes,
4403
+ largeFileBytes: DEFAULT_ANSWERS.largeFileBytes,
4404
+ include: options.include ?? [],
4405
+ excludePatterns: options.exclude ?? [],
4406
+ artifacts: artifactAnswers(options),
4407
+ includeSensitiveFiles: options.includeSensitiveFiles ?? DEFAULT_ANSWERS.includeSensitiveFiles,
4408
+ outDir: options.outDir ?? DEFAULT_ANSWERS.outDir
4409
+ };
4410
+ const interactive = isInteractive(io) && options.yes !== true;
4411
+ if (interactive) {
4412
+ io.output.write("\n @ankurhalder/codebase-snapshot\n");
4413
+ io.output.write(" Turn this repository into an LLM-ready, redacted, byte-bounded snapshot.\n\n");
4414
+ io.output.write(` Detected ${detected.presetName}
4415
+ `);
4416
+ io.output.write(` Directory ${options.cwd}
4417
+
4418
+ `);
4419
+ const useDetected = await confirm(io, `Set up for ${detected.presetName}?`, {
4420
+ defaultValue: true
4421
+ });
4422
+ if (!useDetected) {
4423
+ answers.preset = await select(
4424
+ io,
4425
+ "What kind of project is this?",
4426
+ listPresetNames().map((name) => resolvePresetChoice(answers, name))
4427
+ );
4428
+ const chosen = detectPreset(options.cwd, answers.preset);
4429
+ answers.preset = chosen.presetName;
4430
+ }
4431
+ const scope = await select(
4432
+ io,
4433
+ "What should go into the snapshot?",
4434
+ [
4435
+ { value: "all", label: "Everything the scanner finds", hint: "recommended \xB7 respects .gitignore" },
4436
+ { value: "folders", label: "Only specific folders\u2026" },
4437
+ { value: "no-tests", label: "Everything, but skip tests" },
4438
+ { value: "custom", label: "Let me write glob patterns" }
4439
+ ],
4440
+ { defaultValue: "all" }
4441
+ );
4442
+ if (scope === "folders") {
4443
+ const dirs = topLevelDirs(options.cwd).slice(0, 40);
4444
+ if (dirs.length === 0) {
4445
+ notes.push("No top-level directories found; the snapshot will include everything.");
4446
+ } else {
4447
+ const chosen = await multiSelect(
4448
+ io,
4449
+ "Select folders",
4450
+ dirs.map((name) => ({ value: name, label: `${name}/` })),
4451
+ { defaults: dirs }
4452
+ );
4453
+ answers.include = chosen.map((name) => `${name}/**`);
4454
+ }
4455
+ } else if (scope === "no-tests") {
4456
+ answers.excludePatterns = [
4457
+ ...answers.excludePatterns,
4458
+ "**/*.test.*",
4459
+ "**/*.spec.*",
4460
+ "tests",
4461
+ "test"
4462
+ ];
4463
+ } else if (scope === "custom") {
4464
+ const raw = await text(io, "Glob patterns to include (comma separated)", {
4465
+ defaultValue: ""
4466
+ });
4467
+ answers.include = raw.split(",").map((value) => value.trim()).filter(Boolean);
4468
+ }
4469
+ const sizeChoice = await select(io, "How large should each part be?", SIZE_CHOICES, {
4470
+ defaultValue: "120k"
4471
+ });
4472
+ if (sizeChoice === "custom") {
4473
+ const raw = await text(io, "Part size (e.g. 150k, 1.5mb, 900000)", {
4474
+ defaultValue: String(answers.maxPartBytes),
4475
+ validate: (value) => parseByteSize(value) === void 0 ? "Not a valid size." : null
4476
+ });
4477
+ answers.maxPartBytes = parseByteSize(raw) ?? answers.maxPartBytes;
4478
+ } else {
4479
+ answers.maxPartBytes = sizeChoice;
4480
+ }
4481
+ const artifactChoices = [
4482
+ { value: "readme", label: "README.md", hint: "orientation page" },
4483
+ { value: "manifest", label: "manifest.json", hint: "required by validate and report" },
4484
+ { value: "parts", label: "parts/", hint: "the code payload" },
4485
+ { value: "navigation", label: "reports/navigation.md", hint: "file \u2192 part index" },
4486
+ { value: "projectTree", label: "reports/project-tree.md", hint: "ASCII tree" },
4487
+ { value: "audit", label: "reports/audit.md", hint: "security and health findings" },
4488
+ { value: "monolith", label: "full-codebase.json + .md", hint: "roughly doubles output" }
4489
+ ];
4490
+ const presetEnabled = ARTIFACT_NAMES.filter((name) => answers.artifacts[name]);
4491
+ const selectedArtifacts = await multiSelect(
4492
+ io,
4493
+ "Which artifacts should be generated?",
4494
+ artifactChoices,
4495
+ { defaults: presetEnabled }
4496
+ );
4497
+ for (const name of ARTIFACT_NAMES) answers.artifacts[name] = selectedArtifacts.includes(name);
4498
+ const enabledCount = ARTIFACT_NAMES.filter((name) => answers.artifacts[name]).length;
4499
+ answers.includeSensitiveFiles = await confirm(
4500
+ io,
4501
+ "Include credential files (.env, *.pem, .npmrc, .ssh/)?",
4502
+ { defaultValue: false }
4503
+ );
4504
+ io.output.write("\n Review\n\n");
4505
+ io.output.write(` project ${answers.preset}
4506
+ `);
4507
+ io.output.write(` include ${answers.include.length ? answers.include.join(", ") : "everything"}
4508
+ `);
4509
+ io.output.write(` part size ${String(answers.maxPartBytes)}
4510
+ `);
4511
+ io.output.write(` artifacts ${enabledCount} of ${ARTIFACT_NAMES.length}
4512
+ `);
4513
+ io.output.write(` credentials ${answers.includeSensitiveFiles ? "included" : "skipped"}
4514
+
4515
+ `);
4516
+ io.output.write(" Will write snapshot.config with your answers, plus every other setting\n");
4517
+ io.output.write(" at its default \u2014 so nothing is hidden behind the docs.\n\n");
4518
+ const proceed = await confirm(io, "Write this and continue?", { defaultValue: true });
4519
+ if (!proceed) throw new PromptCancelled();
4520
+ }
4521
+ const configText = options.minimal ? renderMinimalConfig(answers) : renderConfig(answers);
4522
+ const configName = supportsTypeScriptImports() ? "snapshot.config.ts" : "snapshot.config.mjs";
4523
+ const configPath = path10__namespace.join(options.cwd, configName);
4524
+ if (options.dryRun) {
4525
+ return {
4526
+ configPath,
4527
+ configText,
4528
+ written: false,
4529
+ preset: answers.preset,
4530
+ scripts: [],
4531
+ gitignoreEntries: [],
4532
+ notes
4533
+ };
4534
+ }
4535
+ const present = existingConfig(options.cwd);
4536
+ if (present && present !== configName && !options.force) {
4537
+ throw new Error(
4538
+ `A configuration already exists (${present}). Re-run with --force to replace it.`
4539
+ );
4540
+ }
4541
+ if (fs9__namespace.existsSync(configPath) && !options.force) {
4542
+ throw new Error(`${configName} already exists. Re-run with --force to replace it.`);
4543
+ }
4544
+ fs9__namespace.writeFileSync(configPath, configText, "utf8");
4545
+ const scripts = addScripts(options.cwd, notes);
4546
+ const gitignoreEntries = addGitignore(options.cwd);
4547
+ return {
4548
+ configPath,
4549
+ configText,
4550
+ written: true,
4551
+ preset: answers.preset,
4552
+ scripts,
4553
+ gitignoreEntries,
4554
+ notes
4555
+ };
4556
+ }
4557
+
2712
4558
  // src/index.ts
2713
4559
  var GENERATOR_NAME = "@ankurhalder/codebase-snapshot";
2714
- var GENERATOR_VERSION = "2.0.0";
4560
+ var GENERATOR_VERSION = "2.1.1";
2715
4561
  var SCHEMA_VERSION = "3.0.0";
2716
4562
  function sha256Hex2(input) {
2717
4563
  return crypto__namespace.createHash("sha256").update(input).digest("hex");
2718
4564
  }
2719
- var SUMMARY_EXTENSIONS = [
4565
+ var TS_EXTENSIONS = [
2720
4566
  ".ts",
2721
4567
  ".mts",
2722
4568
  ".cts",
@@ -2724,24 +4570,21 @@ var SUMMARY_EXTENSIONS = [
2724
4570
  ".js",
2725
4571
  ".mjs",
2726
4572
  ".cjs",
2727
- ".jsx",
2728
- ".json",
2729
- ".jsonc",
2730
- ".md",
2731
- ".mdx"
4573
+ ".jsx"
2732
4574
  ];
2733
4575
  function summarizeFile(relativePath, redactedContent) {
2734
4576
  const lower = relativePath.toLowerCase();
2735
- if (!SUMMARY_EXTENSIONS.some((extension) => lower.endsWith(extension))) {
2736
- return void 0;
2737
- }
2738
4577
  if (lower.endsWith(".json") || lower.endsWith(".jsonc")) {
2739
- return summarizeJson(redactedContent);
4578
+ return { summary: summarizeJson(redactedContent), quality: "structural" };
2740
4579
  }
2741
4580
  if (lower.endsWith(".md") || lower.endsWith(".mdx")) {
2742
- return summarizeMarkdown(redactedContent);
4581
+ return { summary: summarizeMarkdown(redactedContent), quality: "structural" };
4582
+ }
4583
+ if (TS_EXTENSIONS.some((extension) => lower.endsWith(extension))) {
4584
+ return { summary: summarizeTypeScriptSource(relativePath, redactedContent), quality: "ast" };
2743
4585
  }
2744
- return summarizeTypeScriptSource(relativePath, redactedContent);
4586
+ const profiled = summarizeWithProfile(relativePath, redactedContent);
4587
+ return profiled ? { summary: profiled.summary, quality: profiled.quality } : void 0;
2745
4588
  }
2746
4589
  function isSourceSummary(summary) {
2747
4590
  return Boolean(summary && typeof summary === "object" && "imports" in summary);
@@ -2767,8 +4610,8 @@ var IMPORT_CANDIDATE_SUFFIXES = [
2767
4610
  ];
2768
4611
  function resolveSpecifier(fromFile, specifier, knownFiles) {
2769
4612
  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));
4613
+ const baseDir = path10__namespace.posix.dirname(fromFile);
4614
+ const joined = path10__namespace.posix.normalize(path10__namespace.posix.join(baseDir, specifier));
2772
4615
  const candidate = joined.replace(/\.(?:js|mjs|cjs|jsx)$/, "");
2773
4616
  for (const suffix of IMPORT_CANDIDATE_SUFFIXES) {
2774
4617
  const withSuffix = `${joined}${suffix}`;
@@ -2860,11 +4703,11 @@ function verifyInMemory(files, parts, maxPartBytes) {
2860
4703
  return errors;
2861
4704
  }
2862
4705
  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}`);
4706
+ const cwd = path10__namespace.resolve(config.cwd);
4707
+ const outDir = path10__namespace.resolve(cwd, config.outDir);
4708
+ const tmpDir = path10__namespace.resolve(cwd, config.tmpDir);
4709
+ const root = path10__namespace.parse(cwd).root;
4710
+ const contains = (parent, child) => child === parent || child.startsWith(`${parent}${path10__namespace.sep}`);
2868
4711
  const reject = (reason) => {
2869
4712
  throw new Error(
2870
4713
  `Refusing to generate: ${reason}. outDir=${outDir} tmpDir=${tmpDir} cwd=${cwd}`
@@ -2883,26 +4726,66 @@ function resolveSafeOutputDirs(config) {
2883
4726
  if (contains(tmpDir, outDir)) reject("outDir is inside tmpDir");
2884
4727
  return { outDir, tmpDir };
2885
4728
  }
4729
+ var PLACEHOLDER_ENTRIES = /* @__PURE__ */ new Set([".gitkeep", ".gitignore", ".DS_Store"]);
4730
+ var OWNERSHIP_MARKER = ".codebase-snapshot";
4731
+ function writeOwnershipMarker(dir) {
4732
+ const marker2 = {
4733
+ generator: GENERATOR_NAME,
4734
+ version: GENERATOR_VERSION,
4735
+ schemaVersion: SCHEMA_VERSION
4736
+ };
4737
+ fs9__namespace.writeFileSync(path10__namespace.join(dir, OWNERSHIP_MARKER), `${JSON.stringify(marker2, null, 2)}
4738
+ `, "utf8");
4739
+ }
4740
+ function isOwnOutput(dir) {
4741
+ if (fs9__namespace.existsSync(path10__namespace.join(dir, OWNERSHIP_MARKER))) return true;
4742
+ const manifestPath = path10__namespace.join(dir, "manifest.json");
4743
+ if (!fs9__namespace.existsSync(manifestPath)) return false;
4744
+ try {
4745
+ const parsed = JSON.parse(fs9__namespace.readFileSync(manifestPath, "utf8"));
4746
+ const generator = parsed.generator;
4747
+ return typeof parsed.schemaVersion === "string" && Array.isArray(parsed.files) && typeof generator === "object" && generator !== null && generator.name === GENERATOR_NAME;
4748
+ } catch {
4749
+ return false;
4750
+ }
4751
+ }
4752
+ function assertReplaceable(target, label) {
4753
+ if (!fs9__namespace.existsSync(target)) return;
4754
+ if (!fs9__namespace.statSync(target).isDirectory()) {
4755
+ throw new Error(
4756
+ `Refusing to write into ${label} ${target}: it exists but is not a directory.`
4757
+ );
4758
+ }
4759
+ const entries = fs9__namespace.readdirSync(target);
4760
+ const meaningful = entries.filter((name) => !PLACEHOLDER_ENTRIES.has(name));
4761
+ if (meaningful.length === 0) return;
4762
+ if (isOwnOutput(target)) return;
4763
+ const preview = meaningful.slice(0, 5).join(", ");
4764
+ const more = meaningful.length > 5 ? `, and ${meaningful.length - 5} more` : "";
4765
+ throw new Error(
4766
+ `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.`
4767
+ );
4768
+ }
2886
4769
  function swapIntoPlace(tmpDir, outDir) {
2887
4770
  const backupDir = `${outDir}.backup-${process.pid}-${Date.now()}`;
2888
- const hadPrevious = fs8__namespace.existsSync(outDir);
2889
- if (hadPrevious) fs8__namespace.renameSync(outDir, backupDir);
4771
+ const hadPrevious = fs9__namespace.existsSync(outDir);
4772
+ if (hadPrevious) fs9__namespace.renameSync(outDir, backupDir);
2890
4773
  try {
2891
4774
  try {
2892
- fs8__namespace.renameSync(tmpDir, outDir);
4775
+ fs9__namespace.renameSync(tmpDir, outDir);
2893
4776
  } catch (err) {
2894
4777
  if (err.code === "EXDEV") {
2895
- fs8__namespace.cpSync(tmpDir, outDir, { recursive: true });
2896
- fs8__namespace.rmSync(tmpDir, { recursive: true, force: true });
4778
+ fs9__namespace.cpSync(tmpDir, outDir, { recursive: true });
4779
+ fs9__namespace.rmSync(tmpDir, { recursive: true, force: true });
2897
4780
  } else {
2898
4781
  throw err;
2899
4782
  }
2900
4783
  }
2901
- if (hadPrevious) fs8__namespace.rmSync(backupDir, { recursive: true, force: true });
4784
+ if (hadPrevious) fs9__namespace.rmSync(backupDir, { recursive: true, force: true });
2902
4785
  } 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);
4786
+ if (hadPrevious && fs9__namespace.existsSync(backupDir)) {
4787
+ if (fs9__namespace.existsSync(outDir)) fs9__namespace.rmSync(outDir, { recursive: true, force: true });
4788
+ fs9__namespace.renameSync(backupDir, outDir);
2906
4789
  }
2907
4790
  throw err;
2908
4791
  }
@@ -2918,7 +4801,9 @@ function artifactWarnings(artifacts) {
2918
4801
  );
2919
4802
  }
2920
4803
  if (ARTIFACT_NAMES.every((name) => artifacts[name] === false)) {
2921
- messages.push("every artifact is disabled: the snapshot directory will be empty.");
4804
+ messages.push(
4805
+ "every artifact is disabled: only the .codebase-snapshot ownership marker will be written."
4806
+ );
2922
4807
  }
2923
4808
  return messages;
2924
4809
  }
@@ -2966,7 +4851,7 @@ function generateSnapshot(configInput, options) {
2966
4851
  const gitInfo = getGitInfo(config.cwd, config.branch);
2967
4852
  const collection = collectFiles(config.cwd, {
2968
4853
  includePatterns: config.include,
2969
- excludePatterns: config.excludePatterns,
4854
+ excludePatterns: [...config.excludePatterns, ...config.presetExclude],
2970
4855
  includeSensitiveFiles: config.includeSensitiveFiles
2971
4856
  });
2972
4857
  const files = [];
@@ -2979,10 +4864,10 @@ function generateSnapshot(configInput, options) {
2979
4864
  let totalSizeBytes = 0;
2980
4865
  let largeFileCount = 0;
2981
4866
  for (const relativePath of collection.files) {
2982
- const absolutePath = path9__namespace.join(config.cwd, relativePath);
4867
+ const absolutePath = path10__namespace.join(config.cwd, relativePath);
2983
4868
  let stats;
2984
4869
  try {
2985
- stats = fs8__namespace.statSync(absolutePath);
4870
+ stats = fs9__namespace.statSync(absolutePath);
2986
4871
  if (!stats.isFile()) {
2987
4872
  skipped.push({ path: relativePath, reason: "not-file" });
2988
4873
  continue;
@@ -2997,7 +4882,7 @@ function generateSnapshot(configInput, options) {
2997
4882
  }
2998
4883
  let buffer;
2999
4884
  try {
3000
- buffer = fs8__namespace.readFileSync(absolutePath);
4885
+ buffer = fs9__namespace.readFileSync(absolutePath);
3001
4886
  } catch {
3002
4887
  skipped.push({ path: relativePath, reason: "read-error" });
3003
4888
  continue;
@@ -3007,10 +4892,10 @@ function generateSnapshot(configInput, options) {
3007
4892
  const lineCount = countLines(rawContent);
3008
4893
  totalLines += lineCount;
3009
4894
  totalSizeBytes += stats.size;
3010
- const layer = classifyLayer(relativePath, config.layers);
4895
+ const layer2 = classifyLayer(relativePath, config.layers);
3011
4896
  const kind = classifyKind(relativePath, config.kinds);
3012
4897
  const language = detectLanguage(relativePath);
3013
- bump(layerCounts, layer);
4898
+ bump(layerCounts, layer2);
3014
4899
  bump(kindCounts, kind);
3015
4900
  const { redacted, findings } = scanAndRedactSecrets(
3016
4901
  rawContent,
@@ -3034,8 +4919,11 @@ function generateSnapshot(configInput, options) {
3034
4919
  includedLines = preview.includedLines;
3035
4920
  }
3036
4921
  let summary;
4922
+ let summaryQuality;
3037
4923
  try {
3038
- summary = summarizeFile(relativePath, redacted);
4924
+ const outcome = summarizeFile(relativePath, redacted);
4925
+ summary = outcome?.summary;
4926
+ summaryQuality = outcome?.quality;
3039
4927
  if (isSourceSummary(summary) && summary.parseErrors) {
3040
4928
  warnings.push(
3041
4929
  `Parse errors in ${relativePath} (${summary.parseErrors}); its summary is incomplete.`
@@ -3052,7 +4940,7 @@ function generateSnapshot(configInput, options) {
3052
4940
  lines: lineCount,
3053
4941
  hashSha256,
3054
4942
  part: "",
3055
- layer,
4943
+ layer: layer2,
3056
4944
  kind,
3057
4945
  language,
3058
4946
  inclusionMode: isPreviewTruncated ? "preview" : "full",
@@ -3062,6 +4950,7 @@ function generateSnapshot(configInput, options) {
3062
4950
  includedLines,
3063
4951
  includedBytes: Buffer.byteLength(finalContent, "utf8"),
3064
4952
  summary,
4953
+ summaryQuality,
3065
4954
  content: finalContent
3066
4955
  });
3067
4956
  }
@@ -3102,10 +4991,11 @@ function generateSnapshot(configInput, options) {
3102
4991
  includeSensitiveFiles: config.includeSensitiveFiles,
3103
4992
  artifacts: { ...config.artifacts },
3104
4993
  include: [...config.include],
4994
+ preset: config.preset,
3105
4995
  layersMode: config.layersMode,
3106
4996
  budgets: { default: config.budgets.default, byLayer: { ...config.budgets.byLayer } },
3107
4997
  excludePatterns: [...config.excludePatterns],
3108
- layers: config.layers.map((layer) => layer.name),
4998
+ layers: config.layers.map((layer2) => layer2.name),
3109
4999
  kinds: config.kinds.map((kind) => kind.kind)
3110
5000
  },
3111
5001
  counts: {
@@ -3192,45 +5082,48 @@ function generateSnapshot(configInput, options) {
3192
5082
  return manifest;
3193
5083
  }
3194
5084
  const { outDir, tmpDir } = resolveSafeOutputDirs(config);
5085
+ assertReplaceable(tmpDir, "tmpDir");
5086
+ assertReplaceable(outDir, "outDir");
3195
5087
  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 });
5088
+ fs9__namespace.rmSync(tmpDir, { recursive: true, force: true });
5089
+ fs9__namespace.mkdirSync(tmpDir, { recursive: true });
5090
+ writeOwnershipMarker(tmpDir);
5091
+ const partsDir = path10__namespace.join(tmpDir, "parts");
5092
+ const reportsDir = path10__namespace.join(tmpDir, "reports");
5093
+ if (artifacts.parts) fs9__namespace.mkdirSync(partsDir, { recursive: true });
3201
5094
  if (artifacts.navigation || artifacts.projectTree || artifacts.audit) {
3202
- fs8__namespace.mkdirSync(reportsDir, { recursive: true });
5095
+ fs9__namespace.mkdirSync(reportsDir, { recursive: true });
3203
5096
  }
3204
5097
  if (artifacts.parts) {
3205
5098
  for (const part of parts) {
3206
- fs8__namespace.writeFileSync(path9__namespace.join(partsDir, part.filename), part.markdown, "utf8");
5099
+ fs9__namespace.writeFileSync(path10__namespace.join(partsDir, part.filename), part.markdown, "utf8");
3207
5100
  }
3208
5101
  }
3209
5102
  if (artifacts.projectTree) {
3210
5103
  const projectTree = generateProjectTree(files.map((file) => file.relativePath));
3211
- fs8__namespace.writeFileSync(path9__namespace.join(reportsDir, "project-tree.md"), projectTree, "utf8");
5104
+ fs9__namespace.writeFileSync(path10__namespace.join(reportsDir, "project-tree.md"), projectTree, "utf8");
3212
5105
  }
3213
5106
  if (artifacts.navigation) {
3214
- fs8__namespace.writeFileSync(path9__namespace.join(reportsDir, "navigation.md"), generateNavigation(manifest), "utf8");
5107
+ fs9__namespace.writeFileSync(path10__namespace.join(reportsDir, "navigation.md"), generateNavigation(manifest), "utf8");
3215
5108
  }
3216
5109
  if (artifacts.audit) {
3217
- fs8__namespace.writeFileSync(
3218
- path9__namespace.join(reportsDir, "audit.md"),
5110
+ fs9__namespace.writeFileSync(
5111
+ path10__namespace.join(reportsDir, "audit.md"),
3219
5112
  generateAuditReport(manifest, secretFindings),
3220
5113
  "utf8"
3221
5114
  );
3222
5115
  }
3223
5116
  if (artifacts.readme) {
3224
- fs8__namespace.writeFileSync(path9__namespace.join(tmpDir, "README.md"), generateReadme(manifest), "utf8");
5117
+ fs9__namespace.writeFileSync(path10__namespace.join(tmpDir, "README.md"), generateReadme(manifest), "utf8");
3225
5118
  }
3226
5119
  if (artifacts.manifest) {
3227
- fs8__namespace.writeFileSync(path9__namespace.join(tmpDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf8");
5120
+ fs9__namespace.writeFileSync(path10__namespace.join(tmpDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf8");
3228
5121
  }
3229
5122
  if (monolithJson !== null) {
3230
- fs8__namespace.writeFileSync(path9__namespace.join(tmpDir, "full-codebase.json"), monolithJson, "utf8");
5123
+ fs9__namespace.writeFileSync(path10__namespace.join(tmpDir, "full-codebase.json"), monolithJson, "utf8");
3231
5124
  }
3232
5125
  if (monolithMarkdown !== null) {
3233
- fs8__namespace.writeFileSync(path9__namespace.join(tmpDir, "full-codebase.md"), monolithMarkdown, "utf8");
5126
+ fs9__namespace.writeFileSync(path10__namespace.join(tmpDir, "full-codebase.md"), monolithMarkdown, "utf8");
3234
5127
  }
3235
5128
  swapIntoPlace(tmpDir, outDir);
3236
5129
  runAfterGenerate(config, manifest);
@@ -3241,12 +5134,17 @@ async function generateSnapshotAsync(configInput, options) {
3241
5134
  }
3242
5135
 
3243
5136
  exports.ARTIFACT_NAMES = ARTIFACT_NAMES;
5137
+ exports.DEFAULT_ANSWERS = DEFAULT_ANSWERS;
3244
5138
  exports.DEFAULT_SECRET_PATTERNS = DEFAULT_SECRET_PATTERNS;
3245
5139
  exports.GENERATOR_NAME = GENERATOR_NAME;
3246
5140
  exports.GENERATOR_VERSION = GENERATOR_VERSION;
5141
+ exports.LANGUAGE_PROFILES = LANGUAGE_PROFILES;
3247
5142
  exports.MIN_MAX_PART_BYTES = MIN_MAX_PART_BYTES;
5143
+ exports.PRESETS = PRESETS;
5144
+ exports.PromptCancelled = PromptCancelled;
3248
5145
  exports.SCHEMA_VERSION = SCHEMA_VERSION;
3249
5146
  exports.anchorForPath = anchorForPath;
5147
+ exports.assertReplaceable = assertReplaceable;
3250
5148
  exports.assertUsableConfigInput = assertUsableConfigInput;
3251
5149
  exports.collectFiles = collectFiles;
3252
5150
  exports.compareManifests = compareManifests;
@@ -3259,12 +5157,15 @@ exports.fenceForContent = fenceForContent;
3259
5157
  exports.generateProjectTree = generateProjectTree;
3260
5158
  exports.generateSnapshot = generateSnapshot;
3261
5159
  exports.generateSnapshotAsync = generateSnapshotAsync;
5160
+ exports.getPreset = getPreset;
3262
5161
  exports.globToRegExp = globToRegExp;
3263
5162
  exports.isBinaryBuffer = isBinaryBuffer;
3264
5163
  exports.isBinaryExtension = isBinaryExtension;
3265
5164
  exports.isBinaryFile = isBinaryFile;
5165
+ exports.isInteractive = isInteractive;
3266
5166
  exports.isResolvedConfig = isResolvedConfig;
3267
5167
  exports.isSensitivePath = isSensitivePath;
5168
+ exports.listPresetNames = listPresetNames;
3268
5169
  exports.loadConfigFile = loadConfigFile;
3269
5170
  exports.loadConfigFileDetailed = loadConfigFileDetailed;
3270
5171
  exports.loadManifest = loadManifest;
@@ -3274,16 +5175,21 @@ exports.matchesExclude = matchesExclude;
3274
5175
  exports.normalizeResolvedConfig = normalizeResolvedConfig;
3275
5176
  exports.parseByteSize = parseByteSize;
3276
5177
  exports.partitionFiles = partitionFiles;
5178
+ exports.profileForPath = profileForPath;
5179
+ exports.renderConfig = renderConfig;
3277
5180
  exports.renderDiffText = renderDiffText;
3278
5181
  exports.renderExplainText = renderExplainText;
5182
+ exports.renderMinimalConfig = renderMinimalConfig;
3279
5183
  exports.renumberParts = renumberParts;
3280
5184
  exports.resolveConfig = resolveConfig;
3281
5185
  exports.resolveSafeOutputDirs = resolveSafeOutputDirs;
5186
+ exports.runInit = runInit;
3282
5187
  exports.scanAndRedactSecrets = scanAndRedactSecrets;
3283
5188
  exports.stripJsonComments = stripJsonComments;
3284
5189
  exports.summarizeJson = summarizeJson;
3285
5190
  exports.summarizeMarkdown = summarizeMarkdown;
3286
5191
  exports.summarizeTypeScriptSource = summarizeTypeScriptSource;
5192
+ exports.summarizeWithProfile = summarizeWithProfile;
3287
5193
  exports.supportsTypeScriptImports = supportsTypeScriptImports;
3288
5194
  exports.truncatePreview = truncatePreview;
3289
5195
  exports.truncateToBytes = truncateToBytes;