@ankurhalder/codebase-snapshot 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -1,10 +1,11 @@
1
1
  'use strict';
2
2
 
3
- var fs8 = require('fs');
4
- var path9 = require('path');
3
+ var fs9 = require('fs');
4
+ var path10 = require('path');
5
5
  var util = require('util');
6
6
  var url = require('url');
7
7
  var childProcess = require('child_process');
8
+ var readline = require('readline');
8
9
  var crypto = require('crypto');
9
10
  var ts = require('typescript');
10
11
 
@@ -28,9 +29,10 @@ function _interopNamespace(e) {
28
29
  return Object.freeze(n);
29
30
  }
30
31
 
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 childProcess__namespace = /*#__PURE__*/_interopNamespace(childProcess);
35
+ var readline__namespace = /*#__PURE__*/_interopNamespace(readline);
34
36
  var crypto__namespace = /*#__PURE__*/_interopNamespace(crypto);
35
37
  var ts__default = /*#__PURE__*/_interopDefault(ts);
36
38
 
@@ -79,10 +81,10 @@ function compareManifests(before, after) {
79
81
  };
80
82
  }
81
83
  function loadManifest(target) {
82
- const manifestPath = fs8__namespace.statSync(target).isDirectory() ? path9__namespace.join(target, "manifest.json") : target;
84
+ const manifestPath = fs9__namespace.statSync(target).isDirectory() ? path10__namespace.join(target, "manifest.json") : target;
83
85
  let raw;
84
86
  try {
85
- raw = fs8__namespace.readFileSync(manifestPath, "utf8");
87
+ raw = fs9__namespace.readFileSync(manifestPath, "utf8");
86
88
  } catch {
87
89
  throw new Error(`Could not read a manifest at ${manifestPath}.`);
88
90
  }
@@ -117,6 +119,138 @@ function renderDiffText(diff) {
117
119
  section("changed:", diff.changed);
118
120
  return lines.join("\n");
119
121
  }
122
+ var MANIFEST_FILES = [
123
+ "package.json",
124
+ "pyproject.toml",
125
+ "requirements.txt",
126
+ "setup.py",
127
+ "go.mod",
128
+ "Cargo.toml",
129
+ "pom.xml",
130
+ "build.gradle",
131
+ "build.gradle.kts",
132
+ "composer.json",
133
+ "Gemfile"
134
+ ];
135
+ var MANIFEST_MAX_BYTES = 1e5;
136
+ function readManifest(cwd, name) {
137
+ const full = path10__namespace.join(cwd, name);
138
+ try {
139
+ const stats = fs9__namespace.statSync(full);
140
+ if (!stats.isFile() || stats.size > MANIFEST_MAX_BYTES) return null;
141
+ return fs9__namespace.readFileSync(full, "utf8");
142
+ } catch {
143
+ return null;
144
+ }
145
+ }
146
+ function collectDependencies(manifests) {
147
+ const names = /* @__PURE__ */ new Set();
148
+ const raw = manifests["package.json"];
149
+ if (!raw) return names;
150
+ try {
151
+ const parsed = JSON.parse(raw);
152
+ for (const section of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
153
+ const block = parsed[section];
154
+ if (block && typeof block === "object") {
155
+ for (const name of Object.keys(block)) names.add(name);
156
+ }
157
+ }
158
+ } catch {
159
+ }
160
+ return names;
161
+ }
162
+ function buildPresetContext(cwd) {
163
+ const files = /* @__PURE__ */ new Set();
164
+ const dirs = /* @__PURE__ */ new Set();
165
+ try {
166
+ for (const entry of fs9__namespace.readdirSync(cwd, { withFileTypes: true })) {
167
+ if (entry.isDirectory()) dirs.add(entry.name);
168
+ else if (entry.isFile()) files.add(entry.name);
169
+ }
170
+ } catch {
171
+ }
172
+ const manifests = {};
173
+ for (const name of MANIFEST_FILES) {
174
+ const content = readManifest(cwd, name);
175
+ if (content !== null) manifests[name] = content;
176
+ }
177
+ return {
178
+ cwd,
179
+ files,
180
+ dirs,
181
+ dependencies: collectDependencies(manifests),
182
+ manifests
183
+ };
184
+ }
185
+ function hasFile(context, name) {
186
+ return context.files.has(name);
187
+ }
188
+ function hasFileStartingWith(context, prefix) {
189
+ for (const file of context.files) {
190
+ if (file.startsWith(prefix)) return true;
191
+ }
192
+ return false;
193
+ }
194
+ function manifestContains(context, manifest, needle) {
195
+ const content = context.manifests[manifest];
196
+ if (!content) return false;
197
+ return content.toLowerCase().includes(needle.toLowerCase());
198
+ }
199
+ function hasDependency(context, ...names) {
200
+ return names.some((name) => context.dependencies.has(name));
201
+ }
202
+
203
+ // src/presets/helpers.ts
204
+ function layer(name, ...prefixes) {
205
+ return {
206
+ name,
207
+ match: (filePath) => prefixes.some((prefix) => filePath.startsWith(prefix))
208
+ };
209
+ }
210
+ function kindOf(name, ...needles) {
211
+ return {
212
+ kind: name,
213
+ match: (filePath) => needles.some((needle) => filePath.includes(needle))
214
+ };
215
+ }
216
+ function suffixKind(name, ...suffixes) {
217
+ return {
218
+ kind: name,
219
+ match: (filePath) => suffixes.some((suffix) => filePath.endsWith(suffix))
220
+ };
221
+ }
222
+ function commonLayers() {
223
+ return [
224
+ layer("scripts", "scripts/", "tools/"),
225
+ layer("docs", "docs/", "documentation/"),
226
+ {
227
+ name: "config",
228
+ match: (filePath) => filePath.startsWith(".github/") || filePath.startsWith(".husky/") || filePath.startsWith(".vscode/") || filePath.startsWith(".") || filePath.includes("config")
229
+ },
230
+ { name: "root", match: (filePath) => !filePath.includes("/") }
231
+ ];
232
+ }
233
+ function commonKinds() {
234
+ return [
235
+ {
236
+ kind: "test",
237
+ match: (filePath) => {
238
+ const lower = filePath.toLowerCase();
239
+ return lower.includes(".test.") || lower.includes(".spec.") || lower.startsWith("test/") || lower.startsWith("tests/") || lower.includes("_test.") || lower.endsWith("_test.go") || lower.includes("__tests__");
240
+ }
241
+ },
242
+ suffixKind("doc", ".md", ".mdx", ".rst", ".txt"),
243
+ suffixKind("data", ".json", ".yaml", ".yml", ".csv", ".toml"),
244
+ {
245
+ kind: "script",
246
+ match: (filePath) => filePath.startsWith("scripts/") || /\.(sh|ps1|bat)$/i.test(filePath)
247
+ },
248
+ {
249
+ kind: "config",
250
+ match: (filePath) => filePath.startsWith(".") || filePath.includes("config") || filePath === "package.json" || filePath === "tsconfig.json"
251
+ }
252
+ ];
253
+ }
120
254
 
121
255
  // src/presets/nextjs.ts
122
256
  var NEXTJS_LAYERS = [
@@ -173,6 +307,47 @@ var NEXTJS_KINDS = [
173
307
  }
174
308
  ];
175
309
 
310
+ // src/presets/node.ts
311
+ var NODE_LAYERS = [
312
+ { name: "core", match: (p) => p.startsWith("src/core/") || p.startsWith("core/") },
313
+ { name: "protocols", match: (p) => p.startsWith("src/protocols/") || p.startsWith("protocols/") },
314
+ { name: "providers", match: (p) => p.startsWith("src/providers/") || p.startsWith("providers/") },
315
+ { name: "security", match: (p) => p.startsWith("src/security/") || p.startsWith("security/") },
316
+ { name: "db", match: (p) => p.startsWith("src/db/") || p.startsWith("db/") || p.startsWith("prisma/") },
317
+ { name: "api", match: (p) => p.startsWith("src/api/") || p.startsWith("api/") || p.startsWith("routes/") },
318
+ { name: "services", match: (p) => p.startsWith("src/services/") || p.startsWith("services/") },
319
+ { name: "controllers", match: (p) => p.startsWith("src/controllers/") || p.startsWith("controllers/") },
320
+ { name: "scripts", match: (p) => p.startsWith("scripts/") },
321
+ { name: "docs", match: (p) => p.startsWith("docs/") },
322
+ {
323
+ name: "config",
324
+ match: (p) => p.startsWith(".github/") || p.startsWith(".husky/") || p.startsWith(".") || p.includes("config")
325
+ },
326
+ { name: "root", match: (p) => !p.includes("/") }
327
+ ];
328
+ var NODE_KINDS = [
329
+ { kind: "test", match: (p) => /\.(test|spec)\.[jt]sx?$/.test(p.toLowerCase()) },
330
+ { kind: "route", match: (p) => p.includes("/routes/") || p.includes("route.") || p.includes("/api/") },
331
+ { kind: "controller", match: (p) => p.includes("controller") },
332
+ { kind: "service", match: (p) => p.includes("service") },
333
+ { kind: "provider", match: (p) => p.includes("provider") },
334
+ { kind: "model", match: (p) => p.includes("model") || p.includes("entity") },
335
+ { kind: "schema", match: (p) => p.includes("schema") || p.includes("zod") || p.includes("dto") },
336
+ { kind: "type", match: (p) => p.endsWith(".d.ts") || p.includes("types.") || p.includes("type.") },
337
+ { kind: "security", match: (p) => p.includes("auth") || p.includes("security") || p.includes("guard") },
338
+ { kind: "db", match: (p) => p.includes("db") || p.includes("database") || p.includes("migration") },
339
+ { kind: "data", match: (p) => p.endsWith(".json") || p.endsWith(".csv") },
340
+ { kind: "doc", match: (p) => p.endsWith(".md") || p.endsWith(".mdx") },
341
+ { kind: "script", match: (p) => p.startsWith("scripts/") || p.endsWith(".sh") || p.endsWith(".ps1") },
342
+ {
343
+ kind: "config",
344
+ match: (p) => {
345
+ const base = p.split("/").pop() || "";
346
+ return ["package.json", "tsconfig.json", "Dockerfile"].includes(base) || base.startsWith(".");
347
+ }
348
+ }
349
+ ];
350
+
176
351
  // src/presets/vite.ts
177
352
  var VITE_LAYERS = [
178
353
  { name: "pages", match: (p) => p.startsWith("src/pages/") || p.startsWith("src/views/") },
@@ -220,44 +395,419 @@ var VITE_KINDS = [
220
395
  }
221
396
  ];
222
397
 
223
- // src/presets/node.ts
224
- var NODE_LAYERS = [
225
- { name: "core", match: (p) => p.startsWith("src/core/") || p.startsWith("core/") },
226
- { name: "protocols", match: (p) => p.startsWith("src/protocols/") || p.startsWith("protocols/") },
227
- { name: "providers", match: (p) => p.startsWith("src/providers/") || p.startsWith("providers/") },
228
- { name: "security", match: (p) => p.startsWith("src/security/") || p.startsWith("security/") },
229
- { name: "db", match: (p) => p.startsWith("src/db/") || p.startsWith("db/") || p.startsWith("prisma/") },
230
- { name: "api", match: (p) => p.startsWith("src/api/") || p.startsWith("api/") || p.startsWith("routes/") },
231
- { name: "services", match: (p) => p.startsWith("src/services/") || p.startsWith("services/") },
232
- { name: "controllers", match: (p) => p.startsWith("src/controllers/") || p.startsWith("controllers/") },
233
- { name: "scripts", match: (p) => p.startsWith("scripts/") },
234
- { name: "docs", match: (p) => p.startsWith("docs/") },
398
+ // src/presets/js-presets.ts
399
+ var NEXT_LIKE_EXCLUDES = [".svelte-kit", ".astro", ".vite", ".output"];
400
+ var JS_PRESETS = [
235
401
  {
236
- name: "config",
237
- match: (p) => p.startsWith(".github/") || p.startsWith(".husky/") || p.startsWith(".") || p.includes("config")
402
+ name: "nextjs",
403
+ priority: 100,
404
+ detect: (context) => hasFileStartingWith(context, "next.config") || hasDependency(context, "next"),
405
+ layers: NEXTJS_LAYERS,
406
+ kinds: NEXTJS_KINDS,
407
+ exclude: NEXT_LIKE_EXCLUDES,
408
+ recommendedBudget: "300k"
238
409
  },
239
- { name: "root", match: (p) => !p.includes("/") }
410
+ {
411
+ name: "nuxt",
412
+ priority: 98,
413
+ detect: (context) => hasFileStartingWith(context, "nuxt.config") || hasDependency(context, "nuxt", "nuxt3"),
414
+ layers: [
415
+ layer("pages", "pages/", "src/pages/"),
416
+ layer("components", "components/", "src/components/"),
417
+ layer("composables", "composables/", "src/composables/"),
418
+ layer("server", "server/", "src/server/", "api/"),
419
+ layer("stores", "stores/", "src/stores/"),
420
+ layer("layouts", "layouts/", "src/layouts/"),
421
+ ...commonLayers()
422
+ ],
423
+ kinds: [
424
+ kindOf("api-route", "server/api/", "server/routes/"),
425
+ suffixKind("component", ".vue"),
426
+ kindOf("composable", "composables/"),
427
+ kindOf("store", "stores/", "store/"),
428
+ kindOf("layout", "layouts/"),
429
+ ...commonKinds()
430
+ ],
431
+ exclude: [".nuxt", ".output"],
432
+ recommendedBudget: "200k"
433
+ },
434
+ {
435
+ name: "sveltekit",
436
+ priority: 96,
437
+ detect: (context) => hasFileStartingWith(context, "svelte.config") || hasDependency(context, "@sveltejs/kit", "svelte"),
438
+ layers: [
439
+ layer("routes", "src/routes/", "routes/"),
440
+ layer("lib", "src/lib/", "lib/"),
441
+ layer("components", "src/lib/components/", "src/components/"),
442
+ layer("stores", "src/lib/stores/", "src/stores/"),
443
+ ...commonLayers()
444
+ ],
445
+ kinds: [
446
+ kindOf("route", "+page.", "+server.", "+layout."),
447
+ suffixKind("component", ".svelte"),
448
+ kindOf("store", "stores/"),
449
+ kindOf("params", "params/"),
450
+ ...commonKinds()
451
+ ],
452
+ exclude: [".svelte-kit", ".vercel"],
453
+ recommendedBudget: "200k"
454
+ },
455
+ {
456
+ name: "remix",
457
+ priority: 94,
458
+ detect: (context) => hasDependency(context, "@remix-run/react", "@remix-run/node", "@remix-run/dev"),
459
+ layers: [
460
+ layer("routes", "app/routes/", "routes/"),
461
+ layer("components", "app/components/"),
462
+ layer("lib", "app/lib/", "app/utils/"),
463
+ ...commonLayers()
464
+ ],
465
+ kinds: [kindOf("route", "routes/"), ...commonKinds()],
466
+ exclude: [".cache", "public/build"],
467
+ recommendedBudget: "200k"
468
+ },
469
+ {
470
+ name: "astro",
471
+ priority: 92,
472
+ detect: (context) => hasFileStartingWith(context, "astro.config") || hasDependency(context, "astro"),
473
+ layers: [
474
+ layer("pages", "src/pages/"),
475
+ layer("layouts", "src/layouts/"),
476
+ layer("components", "src/components/"),
477
+ layer("content", "src/content/"),
478
+ ...commonLayers()
479
+ ],
480
+ kinds: [suffixKind("page", ".astro"), kindOf("layout", "layouts/"), ...commonKinds()],
481
+ exclude: [".astro", "dist"],
482
+ recommendedBudget: "150k"
483
+ },
484
+ {
485
+ name: "angular",
486
+ priority: 90,
487
+ detect: (context) => hasFile(context, "angular.json") || hasDependency(context, "@angular/core"),
488
+ layers: [
489
+ layer("app", "src/app/"),
490
+ layer("features", "src/app/features/"),
491
+ layer("shared", "src/app/shared/"),
492
+ layer("core", "src/app/core/"),
493
+ layer("assets", "src/assets/"),
494
+ ...commonLayers()
495
+ ],
496
+ kinds: [
497
+ suffixKind("component", ".component.ts"),
498
+ suffixKind("service", ".service.ts"),
499
+ suffixKind("module", ".module.ts"),
500
+ suffixKind("guard", ".guard.ts"),
501
+ suffixKind("pipe", ".pipe.ts"),
502
+ suffixKind("directive", ".directive.ts"),
503
+ ...commonKinds()
504
+ ],
505
+ exclude: [".angular", "dist"],
506
+ recommendedBudget: "200k"
507
+ },
508
+ {
509
+ name: "solid",
510
+ priority: 88,
511
+ detect: (context) => hasDependency(context, "solid-js", "solid-start"),
512
+ layers: [
513
+ layer("pages", "src/routes/", "src/pages/"),
514
+ layer("components", "src/components/"),
515
+ ...commonLayers()
516
+ ],
517
+ kinds: [kindOf("route", "routes/"), ...commonKinds()],
518
+ exclude: [".solid", "dist"],
519
+ recommendedBudget: "120k"
520
+ },
521
+ {
522
+ name: "vue-vite",
523
+ priority: 86,
524
+ detect: (context) => hasDependency(context, "vue") && (hasDependency(context, "vite") || hasFileStartingWith(context, "vite.config")),
525
+ layers: [
526
+ layer("pages", "src/pages/", "src/views/"),
527
+ layer("components", "src/components/"),
528
+ layer("composables", "src/composables/", "src/hooks/"),
529
+ layer("stores", "src/stores/", "src/store/"),
530
+ layer("services", "src/services/", "src/api/"),
531
+ layer("shared", "src/shared/", "src/common/"),
532
+ layer("styles", "src/styles/", "src/assets/"),
533
+ ...commonLayers()
534
+ ],
535
+ kinds: [
536
+ suffixKind("component", ".vue"),
537
+ kindOf("composable", "composables/", "use"),
538
+ kindOf("store", "stores/", "store/"),
539
+ kindOf("service", "services/", "api/"),
540
+ suffixKind("style", ".css", ".scss", ".sass", ".less"),
541
+ ...commonKinds()
542
+ ],
543
+ exclude: [".vite", "dist"],
544
+ recommendedBudget: "120k"
545
+ },
546
+ {
547
+ name: "react-vite",
548
+ priority: 84,
549
+ detect: (context) => hasDependency(context, "react") && (hasDependency(context, "vite") || hasFileStartingWith(context, "vite.config")),
550
+ layers: VITE_LAYERS,
551
+ kinds: VITE_KINDS,
552
+ exclude: [".vite", "dist"],
553
+ recommendedBudget: "120k"
554
+ },
555
+ {
556
+ name: "nest",
557
+ priority: 80,
558
+ detect: (context) => hasDependency(context, "@nestjs/core"),
559
+ layers: [
560
+ layer("modules", "src/modules/", "modules/"),
561
+ layer("controllers", "src/controllers/", "controllers/"),
562
+ layer("providers", "src/providers/", "providers/"),
563
+ layer("core", "src/core/"),
564
+ layer("security", "src/security/", "src/auth/"),
565
+ layer("db", "src/db/", "src/database/", "prisma/"),
566
+ ...commonLayers()
567
+ ],
568
+ kinds: [
569
+ suffixKind("controller", ".controller.ts"),
570
+ suffixKind("service", ".service.ts"),
571
+ suffixKind("module", ".module.ts"),
572
+ suffixKind("guard", ".guard.ts"),
573
+ suffixKind("dto", ".dto.ts"),
574
+ suffixKind("entity", ".entity.ts"),
575
+ kindOf("migration", "migrations/"),
576
+ ...commonKinds()
577
+ ],
578
+ exclude: ["dist", "node_modules"],
579
+ recommendedBudget: "200k"
580
+ },
581
+ {
582
+ name: "node-express",
583
+ priority: 78,
584
+ detect: (context) => hasDependency(context, "express", "fastify", "koa", "@hapi/hapi"),
585
+ layers: NODE_LAYERS,
586
+ kinds: NODE_KINDS,
587
+ exclude: ["dist"],
588
+ recommendedBudget: "200k"
589
+ },
590
+ {
591
+ name: "node-lib",
592
+ // A bare package.json is often just tooling around a non-JS project, so it
593
+ // ranks below every definitive language manifest.
594
+ priority: 45,
595
+ detect: (context) => hasFile(context, "package.json"),
596
+ layers: [
597
+ layer("src", "src/"),
598
+ layer("lib", "lib/"),
599
+ layer("test", "test/", "tests/", "__tests__/"),
600
+ ...commonLayers()
601
+ ],
602
+ kinds: [suffixKind("type", ".d.ts"), ...commonKinds()],
603
+ exclude: ["dist"],
604
+ recommendedBudget: "120k"
605
+ }
240
606
  ];
241
- var NODE_KINDS = [
242
- { kind: "test", match: (p) => /\.(test|spec)\.[jt]sx?$/.test(p.toLowerCase()) },
243
- { kind: "route", match: (p) => p.includes("/routes/") || p.includes("route.") || p.includes("/api/") },
244
- { kind: "controller", match: (p) => p.includes("controller") },
245
- { kind: "service", match: (p) => p.includes("service") },
246
- { kind: "provider", match: (p) => p.includes("provider") },
247
- { kind: "model", match: (p) => p.includes("model") || p.includes("entity") },
248
- { kind: "schema", match: (p) => p.includes("schema") || p.includes("zod") || p.includes("dto") },
249
- { kind: "type", match: (p) => p.endsWith(".d.ts") || p.includes("types.") || p.includes("type.") },
250
- { kind: "security", match: (p) => p.includes("auth") || p.includes("security") || p.includes("guard") },
251
- { kind: "db", match: (p) => p.includes("db") || p.includes("database") || p.includes("migration") },
252
- { kind: "data", match: (p) => p.endsWith(".json") || p.endsWith(".csv") },
253
- { kind: "doc", match: (p) => p.endsWith(".md") || p.endsWith(".mdx") },
254
- { kind: "script", match: (p) => p.startsWith("scripts/") || p.endsWith(".sh") || p.endsWith(".ps1") },
607
+
608
+ // src/presets/lang-presets.ts
609
+ var anyFileEndingWith = (context, suffix) => Array.from(context.files).some((file) => file.endsWith(suffix));
610
+ var DIR = (name) => `${name}/`;
611
+ var LANGUAGE_PRESETS = [
255
612
  {
256
- kind: "config",
257
- match: (p) => {
258
- const base = p.split("/").pop() || "";
259
- return ["package.json", "tsconfig.json", "Dockerfile"].includes(base) || base.startsWith(".");
260
- }
613
+ name: "python-django",
614
+ priority: 56,
615
+ detect: (context) => hasFile(context, "manage.py") || manifestContains(context, "pyproject.toml", "django") || manifestContains(context, "requirements.txt", "django"),
616
+ layers: [
617
+ layer("views", DIR("views"), DIR("app/views"), DIR("apps/views")),
618
+ layer("models", DIR("models"), DIR("app/models"), DIR("apps/models")),
619
+ layer("serializers", DIR("serializers")),
620
+ layer("urls", "urls.py"),
621
+ layer("migrations", DIR("migrations")),
622
+ layer("settings", "settings.py", "settings/"),
623
+ layer("templates", DIR("templates")),
624
+ ...commonLayers()
625
+ ],
626
+ kinds: [
627
+ suffixKind("model", "models.py"),
628
+ suffixKind("view", "views.py"),
629
+ suffixKind("serializer", "serializers.py"),
630
+ suffixKind("urlconf", "urls.py"),
631
+ kindOf("migration", "migrations/"),
632
+ suffixKind("settings", "settings.py"),
633
+ ...commonKinds()
634
+ ],
635
+ exclude: ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache", ".tox", ".eggs", "staticfiles", "media"],
636
+ recommendedBudget: "150k"
637
+ },
638
+ {
639
+ name: "python-fastapi",
640
+ priority: 54,
641
+ detect: (context) => manifestContains(context, "requirements.txt", "fastapi") || manifestContains(context, "requirements.txt", "flask") || manifestContains(context, "pyproject.toml", "fastapi") || manifestContains(context, "pyproject.toml", "flask"),
642
+ layers: [
643
+ layer("routers", DIR("routers"), DIR("routes"), DIR("api")),
644
+ layer("models", DIR("models"), DIR("db")),
645
+ layer("schemas", DIR("schemas")),
646
+ layer("services", DIR("services")),
647
+ layer("core", DIR("core")),
648
+ layer("tests", DIR("tests")),
649
+ ...commonLayers()
650
+ ],
651
+ kinds: [
652
+ kindOf("router", "routers/", "routes/", "api/"),
653
+ kindOf("model", "models/"),
654
+ kindOf("schema", "schemas/"),
655
+ kindOf("service", "services/"),
656
+ ...commonKinds()
657
+ ],
658
+ exclude: ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache", ".tox", ".eggs"],
659
+ recommendedBudget: "150k"
660
+ },
661
+ {
662
+ name: "python-lib",
663
+ // Below node-lib: a Node repo with a stray .py file is still a Node repo.
664
+ priority: 30,
665
+ detect: (context) => hasFile(context, "pyproject.toml") || hasFile(context, "setup.py") || hasFile(context, "requirements.txt") || anyFileEndingWith(context, ".py"),
666
+ layers: [
667
+ layer("package", DIR("src"), DIR("lib")),
668
+ layer("tests", DIR("tests"), DIR("test")),
669
+ ...commonLayers()
670
+ ],
671
+ kinds: [suffixKind("module", ".py"), ...commonKinds()],
672
+ exclude: ["__pycache__", ".venv", "venv", ".mypy_cache", ".pytest_cache", ".tox", ".eggs"],
673
+ recommendedBudget: "120k"
674
+ },
675
+ {
676
+ name: "go",
677
+ priority: 50,
678
+ detect: (context) => hasFile(context, "go.mod"),
679
+ layers: [
680
+ layer("cmd", "cmd/"),
681
+ layer("internal", "internal/"),
682
+ layer("pkg", "pkg/"),
683
+ layer("api", "api/", "proto/"),
684
+ layer("migrations", "migrations/"),
685
+ ...commonLayers()
686
+ ],
687
+ kinds: [
688
+ kindOf("main", "cmd/"),
689
+ kindOf("handler", "handler", "server", "http"),
690
+ kindOf("service", "service"),
691
+ kindOf("repository", "repository", "store", "repo"),
692
+ kindOf("model", "model", "entity"),
693
+ suffixKind("test", "_test.go"),
694
+ ...commonKinds()
695
+ ],
696
+ exclude: ["vendor", "testdata"],
697
+ recommendedBudget: "200k"
698
+ },
699
+ {
700
+ name: "rust",
701
+ priority: 48,
702
+ detect: (context) => hasFile(context, "Cargo.toml"),
703
+ layers: [
704
+ layer("src", "src/"),
705
+ layer("crates", "crates/"),
706
+ layer("bins", "src/bin/"),
707
+ layer("tests", "tests/"),
708
+ layer("benches", "benches/"),
709
+ ...commonLayers()
710
+ ],
711
+ kinds: [suffixKind("module", ".rs"), suffixKind("test", "_test.rs"), ...commonKinds()],
712
+ exclude: ["target"],
713
+ recommendedBudget: "200k"
714
+ },
715
+ {
716
+ name: "java-spring",
717
+ priority: 46,
718
+ detect: (context) => hasFile(context, "pom.xml") || hasFile(context, "build.gradle") || hasFile(context, "build.gradle.kts"),
719
+ layers: [
720
+ layer("api", "src/main/java/"),
721
+ layer("test", "src/test/java/"),
722
+ layer("resources", "src/main/resources/"),
723
+ layer("migrations", "src/main/resources/db/"),
724
+ ...commonLayers()
725
+ ],
726
+ kinds: [
727
+ kindOf("controller", "controller"),
728
+ kindOf("service", "service"),
729
+ kindOf("repository", "repository", "dao"),
730
+ kindOf("entity", "entity", "model"),
731
+ kindOf("dto", "dto", "request", "response"),
732
+ kindOf("migration", "migration", "db/migration"),
733
+ ...commonKinds()
734
+ ],
735
+ exclude: [".gradle", "out", "target"],
736
+ recommendedBudget: "200k"
737
+ },
738
+ {
739
+ name: "dotnet",
740
+ priority: 44,
741
+ detect: (context) => anyFileEndingWith(context, ".csproj") || anyFileEndingWith(context, ".sln") || hasFile(context, "global.json"),
742
+ layers: [
743
+ layer("web", "src/", "Web/"),
744
+ layer("domain", "Domain/", "Core/"),
745
+ layer("infrastructure", "Infrastructure/", "Data/"),
746
+ layer("tests", "Tests/", "test/"),
747
+ ...commonLayers()
748
+ ],
749
+ kinds: [
750
+ kindOf("controller", "Controller"),
751
+ kindOf("service", "Service"),
752
+ kindOf("repository", "Repository"),
753
+ kindOf("model", "Model", "Entity"),
754
+ kindOf("dto", "Dto", "Request", "Response"),
755
+ suffixKind("test", "Tests.cs"),
756
+ ...commonKinds()
757
+ ],
758
+ exclude: ["bin", "obj", ".vs"],
759
+ recommendedBudget: "200k"
760
+ },
761
+ {
762
+ name: "php-laravel",
763
+ priority: 42,
764
+ detect: (context) => hasFile(context, "artisan") || manifestContains(context, "composer.json", "laravel") || hasFile(context, "composer.json"),
765
+ layers: [
766
+ layer("app", "app/"),
767
+ layer("routes", "routes/"),
768
+ layer("http", "app/Http/"),
769
+ layer("models", "app/Models/"),
770
+ layer("providers", "app/Providers/"),
771
+ layer("database", "database/"),
772
+ layer("tests", "tests/"),
773
+ ...commonLayers()
774
+ ],
775
+ kinds: [
776
+ kindOf("controller", "Http/Controllers/"),
777
+ kindOf("model", "Models/"),
778
+ kindOf("migration", "migrations/"),
779
+ kindOf("middleware", "Middleware/"),
780
+ kindOf("request", "Requests/"),
781
+ kindOf("provider", "Providers/"),
782
+ ...commonKinds()
783
+ ],
784
+ exclude: ["vendor", "storage", "bootstrap/cache", "public/build"],
785
+ recommendedBudget: "150k"
786
+ },
787
+ {
788
+ name: "ruby-rails",
789
+ priority: 40,
790
+ detect: (context) => hasFile(context, "Gemfile") || hasFile(context, "config.ru") || hasFile(context, "Rakefile"),
791
+ layers: [
792
+ layer("app", "app/"),
793
+ layer("controllers", "app/controllers/"),
794
+ layer("models", "app/models/"),
795
+ layer("config", "config/"),
796
+ layer("db", "db/"),
797
+ layer("spec", "spec/", "test/"),
798
+ ...commonLayers()
799
+ ],
800
+ kinds: [
801
+ kindOf("controller", "controllers/"),
802
+ kindOf("model", "models/"),
803
+ kindOf("migration", "migrate/"),
804
+ kindOf("job", "jobs/"),
805
+ kindOf("mailer", "mailers/"),
806
+ suffixKind("test", "_spec.rb", "_test.rb"),
807
+ ...commonKinds()
808
+ ],
809
+ exclude: [".bundle", "log", "tmp", "vendor/bundle", "public/assets"],
810
+ recommendedBudget: "150k"
261
811
  }
262
812
  ];
263
813
 
@@ -303,50 +853,110 @@ var DEFAULT_KINDS = [
303
853
  }
304
854
  ];
305
855
 
306
- // src/presets/index.ts
307
- function detectPreset(cwd) {
308
- 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"));
309
- 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"));
310
- const pkgJsonPath = path9__namespace.join(cwd, "package.json");
311
- let pkgDependencies = {};
312
- if (fs8__namespace.existsSync(pkgJsonPath)) {
313
- try {
314
- const parsed = JSON.parse(fs8__namespace.readFileSync(pkgJsonPath, "utf8"));
315
- pkgDependencies = {
316
- ...parsed.dependencies || {},
317
- ...parsed.devDependencies || {}
318
- };
319
- } catch {
320
- pkgDependencies = {};
321
- }
322
- }
323
- if (hasNextConfig || pkgDependencies["next"]) {
324
- return {
325
- presetName: "nextjs",
326
- layers: NEXTJS_LAYERS,
327
- kinds: NEXTJS_KINDS
328
- };
856
+ // src/presets/shape-presets.ts
857
+ var SHAPE_PRESETS = [
858
+ {
859
+ name: "monorepo",
860
+ priority: 70,
861
+ detect: (context) => hasFile(context, "pnpm-workspace.yaml") || hasFile(context, "nx.json") || hasFile(context, "turbo.json") || hasFile(context, "lerna.json") || manifestContains(context, "package.json", '"workspaces"'),
862
+ layers: [
863
+ layer("apps", "apps/"),
864
+ layer("packages", "packages/"),
865
+ layer("tooling", "tooling/", "tools/", "scripts/"),
866
+ ...commonLayers()
867
+ ],
868
+ kinds: commonKinds(),
869
+ exclude: [".turbo", ".nx"],
870
+ recommendedBudget: "200k"
871
+ },
872
+ {
873
+ name: "infra",
874
+ priority: 24,
875
+ 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"),
876
+ layers: [
877
+ layer("modules", "modules/"),
878
+ layer("environments", "env/", "environments/"),
879
+ layer("manifests", "k8s/", "helm/", "charts/", "deploy/"),
880
+ ...commonLayers()
881
+ ],
882
+ kinds: [
883
+ { kind: "terraform", match: (filePath) => filePath.endsWith(".tf") },
884
+ { kind: "kubernetes", match: (filePath) => /\.(ya?ml)$/i.test(filePath) && /k8s|helm|charts|deploy/.test(filePath) },
885
+ ...commonKinds()
886
+ ],
887
+ exclude: [".terraform", ".terragrunt-cache"],
888
+ recommendedBudget: "100k"
889
+ },
890
+ {
891
+ name: "docs",
892
+ priority: 22,
893
+ detect: (context) => {
894
+ const markdown = Array.from(context.files).filter((file) => /\.mdx?$/i.test(file)).length;
895
+ return markdown >= 3 && !hasFile(context, "package.json");
896
+ },
897
+ layers: [
898
+ layer("content", "content/", "docs/"),
899
+ layer("assets", "assets/", "static/", "public/"),
900
+ ...commonLayers()
901
+ ],
902
+ kinds: commonKinds(),
903
+ exclude: [".docusaurus", ".vitepress/dist", "site"],
904
+ recommendedBudget: "100k"
905
+ },
906
+ {
907
+ name: "generic",
908
+ priority: 0,
909
+ detect: () => true,
910
+ layers: DEFAULT_LAYERS,
911
+ kinds: DEFAULT_KINDS,
912
+ exclude: [],
913
+ recommendedBudget: "120k"
329
914
  }
330
- if (hasViteConfig || pkgDependencies["vite"]) {
331
- return {
332
- presetName: "vite",
333
- layers: VITE_LAYERS,
334
- kinds: VITE_KINDS
335
- };
915
+ ];
916
+
917
+ // src/presets/registry.ts
918
+ function compare(a, b) {
919
+ if (b.priority !== a.priority) return b.priority - a.priority;
920
+ return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
921
+ }
922
+ var PRESETS = [
923
+ ...JS_PRESETS,
924
+ ...LANGUAGE_PRESETS,
925
+ ...SHAPE_PRESETS
926
+ ].sort(compare);
927
+ var BY_NAME = new Map(PRESETS.map((preset) => [preset.name, preset]));
928
+ function listPresetNames() {
929
+ return PRESETS.map((preset) => preset.name);
930
+ }
931
+ function getPreset(name) {
932
+ return BY_NAME.get(name);
933
+ }
934
+ function unknownPresetMessage(name) {
935
+ return `Unknown preset "${name}". Expected one of: ${listPresetNames().join(", ")}.`;
936
+ }
937
+ function detectPresetFromContext(context, forced) {
938
+ if (forced !== void 0) {
939
+ const preset = BY_NAME.get(forced);
940
+ if (!preset) throw new Error(unknownPresetMessage(forced));
941
+ return toDetected(preset);
336
942
  }
337
- 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"))) {
338
- return {
339
- presetName: "node",
340
- layers: NODE_LAYERS,
341
- kinds: NODE_KINDS
342
- };
943
+ for (const preset of PRESETS) {
944
+ if (preset.detect(context)) return toDetected(preset);
343
945
  }
946
+ return toDetected(PRESETS[PRESETS.length - 1]);
947
+ }
948
+ function toDetected(preset) {
344
949
  return {
345
- presetName: "default",
346
- layers: DEFAULT_LAYERS,
347
- kinds: DEFAULT_KINDS
950
+ presetName: preset.name,
951
+ layers: preset.layers,
952
+ kinds: preset.kinds,
953
+ exclude: preset.exclude ? [...preset.exclude] : [],
954
+ recommendedBudget: preset.recommendedBudget ?? null
348
955
  };
349
956
  }
957
+ function detectPreset(cwd, forced) {
958
+ return detectPresetFromContext(buildPresetContext(cwd), forced);
959
+ }
350
960
  function getGitInfo(cwd, fallbackBranch = "main") {
351
961
  try {
352
962
  const branch = childProcess__namespace.execSync("git rev-parse --abbrev-ref HEAD", {
@@ -481,10 +1091,10 @@ function matchesExclude(relativePath, pattern) {
481
1091
  return posixPath === cleaned || posixPath.startsWith(`${cleaned}/`) || posixPath.includes(`/${cleaned}/`) || posixPath.endsWith(`/${cleaned}`);
482
1092
  }
483
1093
  function readGitignoreMatcher(dir) {
484
- const gitignorePath = path9__namespace.join(dir, ".gitignore");
1094
+ const gitignorePath = path10__namespace.join(dir, ".gitignore");
485
1095
  let raw;
486
1096
  try {
487
- raw = fs8__namespace.readFileSync(gitignorePath, "utf8");
1097
+ raw = fs9__namespace.readFileSync(gitignorePath, "utf8");
488
1098
  } catch {
489
1099
  return () => false;
490
1100
  }
@@ -587,7 +1197,7 @@ function walkDirectory(dir, baseDir, visited) {
587
1197
  const results = { files: [], skipped: [] };
588
1198
  let realDir;
589
1199
  try {
590
- realDir = fs8__namespace.realpathSync(dir);
1200
+ realDir = fs9__namespace.realpathSync(dir);
591
1201
  } catch {
592
1202
  realDir = dir;
593
1203
  }
@@ -596,21 +1206,21 @@ function walkDirectory(dir, baseDir, visited) {
596
1206
  const ignoredByGitignore = dir === baseDir ? readGitignoreMatcher(baseDir) : () => false;
597
1207
  let entries;
598
1208
  try {
599
- entries = fs8__namespace.readdirSync(dir, { withFileTypes: true });
1209
+ entries = fs9__namespace.readdirSync(dir, { withFileTypes: true });
600
1210
  } catch {
601
1211
  results.skipped.push({ path: dir, reason: "read-error", detail: "directory unreadable" });
602
1212
  return results;
603
1213
  }
604
1214
  for (const entry of entries) {
605
1215
  if (DEFAULT_IGNORED_DIRS.includes(entry.name)) continue;
606
- const fullPath = path9__namespace.join(dir, entry.name);
607
- const relPath = path9__namespace.relative(baseDir, fullPath).replace(/\\/g, "/");
1216
+ const fullPath = path10__namespace.join(dir, entry.name);
1217
+ const relPath = path10__namespace.relative(baseDir, fullPath).replace(/\\/g, "/");
608
1218
  if (ignoredByGitignore(relPath)) continue;
609
1219
  let isDirectory = entry.isDirectory();
610
1220
  let isFile = entry.isFile();
611
1221
  if (entry.isSymbolicLink()) {
612
1222
  try {
613
- const stats = fs8__namespace.statSync(fullPath);
1223
+ const stats = fs9__namespace.statSync(fullPath);
614
1224
  isDirectory = stats.isDirectory();
615
1225
  isFile = stats.isFile();
616
1226
  if (!isDirectory && !isFile) {
@@ -875,6 +1485,8 @@ function normalizeResolvedConfig(config) {
875
1485
  config.includeMonolith === true
876
1486
  ),
877
1487
  include: resolveInclude(config.include),
1488
+ preset: typeof config.preset === "string" ? config.preset : "generic",
1489
+ presetExclude: Array.isArray(config.presetExclude) ? config.presetExclude : [],
878
1490
  layersMode: config.layersMode === "replace" ? "replace" : "merge",
879
1491
  budgets: config.budgets && typeof config.budgets === "object" ? config.budgets : { default: config.maxPartBytes, byLayer: {} },
880
1492
  hooks: resolveHooks(config.hooks),
@@ -922,18 +1534,22 @@ function parseJsonish(raw, file) {
922
1534
  }
923
1535
  async function loadConfigFileDetailed(cwd) {
924
1536
  const warnings = [];
1537
+ const errors = [];
925
1538
  for (const filename of CONFIG_FILENAMES) {
926
- const fullPath = path9__namespace.join(cwd, filename);
927
- if (!fs8__namespace.existsSync(fullPath)) continue;
1539
+ const fullPath = path10__namespace.join(cwd, filename);
1540
+ if (!fs9__namespace.existsSync(fullPath)) continue;
928
1541
  if (filename.endsWith(".json") || filename === ".snapshotrc") {
929
1542
  try {
930
1543
  return {
931
- config: parseJsonish(fs8__namespace.readFileSync(fullPath, "utf8"), fullPath),
1544
+ config: parseJsonish(fs9__namespace.readFileSync(fullPath, "utf8"), fullPath),
932
1545
  file: fullPath,
933
- warnings
1546
+ warnings,
1547
+ errors
934
1548
  };
935
1549
  } catch (err) {
936
- warnings.push(err.message);
1550
+ const message = err.message;
1551
+ warnings.push(message);
1552
+ errors.push(message);
937
1553
  continue;
938
1554
  }
939
1555
  }
@@ -947,28 +1563,34 @@ async function loadConfigFileDetailed(cwd) {
947
1563
  const imported = await import(url.pathToFileURL(fullPath).href);
948
1564
  const config = imported.default ?? imported;
949
1565
  if (!config || typeof config !== "object") {
950
- warnings.push(`${fullPath} does not export a configuration object.`);
1566
+ const message = `${fullPath} does not export a configuration object.`;
1567
+ warnings.push(message);
1568
+ errors.push(message);
951
1569
  continue;
952
1570
  }
953
- return { config, file: fullPath, warnings };
1571
+ return { config, file: fullPath, warnings, errors };
954
1572
  } catch (err) {
955
- warnings.push(`Could not load ${fullPath}: ${err.message}`);
1573
+ const message = `Could not load ${fullPath}: ${err.message}`;
1574
+ warnings.push(message);
1575
+ errors.push(message);
956
1576
  continue;
957
1577
  }
958
1578
  }
959
- const pkgJsonPath = path9__namespace.join(cwd, "package.json");
960
- if (fs8__namespace.existsSync(pkgJsonPath)) {
1579
+ const pkgJsonPath = path10__namespace.join(cwd, "package.json");
1580
+ if (fs9__namespace.existsSync(pkgJsonPath)) {
961
1581
  try {
962
- const parsed = parseJsonish(fs8__namespace.readFileSync(pkgJsonPath, "utf8"), pkgJsonPath);
1582
+ const parsed = parseJsonish(fs9__namespace.readFileSync(pkgJsonPath, "utf8"), pkgJsonPath);
963
1583
  const snapshotKey = parsed.snapshot;
964
1584
  if (snapshotKey && typeof snapshotKey === "object") {
965
- return { config: snapshotKey, file: pkgJsonPath, warnings };
1585
+ return { config: snapshotKey, file: pkgJsonPath, warnings, errors };
966
1586
  }
967
1587
  } catch (err) {
968
- warnings.push(err.message);
1588
+ const message = err.message;
1589
+ warnings.push(message);
1590
+ errors.push(message);
969
1591
  }
970
1592
  }
971
- return { config: null, file: null, warnings };
1593
+ return { config: null, file: null, warnings, errors };
972
1594
  }
973
1595
  var MIN_MAX_PART_BYTES = 4096;
974
1596
  var ARTIFACT_NAMES = [
@@ -1063,7 +1685,9 @@ function boundedBytes(value, fallback, minimum, label) {
1063
1685
  `${label} must be a byte count such as 120000, "120k" or "1.5mb" (received ${describeBytes(value)}).`
1064
1686
  );
1065
1687
  }
1066
- if (parsed <= 0) return fallback;
1688
+ if (parsed <= 0) {
1689
+ throw new Error(`${label} must be a positive byte count (received ${describeBytes(value)}).`);
1690
+ }
1067
1691
  if (parsed < minimum) {
1068
1692
  throw new Error(`${label} must be at least ${minimum} bytes (received ${describeBytes(value)}).`);
1069
1693
  }
@@ -1103,12 +1727,12 @@ function resolveBudgets(budgets, fallback) {
1103
1727
  if (typeof budgets.byLayer !== "object" || budgets.byLayer === null) {
1104
1728
  throw new Error("`budgets.byLayer` must be an object of layer name to byte size.");
1105
1729
  }
1106
- for (const [layer, value] of Object.entries(budgets.byLayer)) {
1107
- byLayer[layer] = boundedBytes(
1730
+ for (const [layer2, value] of Object.entries(budgets.byLayer)) {
1731
+ byLayer[layer2] = boundedBytes(
1108
1732
  value,
1109
1733
  fallback,
1110
1734
  MIN_MAX_PART_BYTES,
1111
- `budgets.byLayer["${layer}"]`
1735
+ `budgets.byLayer["${layer2}"]`
1112
1736
  );
1113
1737
  }
1114
1738
  }
@@ -1129,18 +1753,22 @@ function resolveHooks(hooks) {
1129
1753
  }
1130
1754
  function resolveCandidateCwd(userConfig, cliOptions, baseDir) {
1131
1755
  const candidate = cliOptions?.cwd || userConfig?.cwd || baseDir || process.cwd();
1132
- const resolved = path9__namespace.resolve(candidate);
1133
- if (!fs8__namespace.existsSync(resolved)) {
1756
+ const resolved = path10__namespace.resolve(candidate);
1757
+ if (!fs9__namespace.existsSync(resolved)) {
1134
1758
  throw new Error(`Working directory does not exist: ${resolved}`);
1135
1759
  }
1136
- if (!fs8__namespace.statSync(resolved).isDirectory()) {
1760
+ if (!fs9__namespace.statSync(resolved).isDirectory()) {
1137
1761
  throw new Error(`Working directory is not a directory: ${resolved}`);
1138
1762
  }
1139
1763
  return resolved;
1140
1764
  }
1141
1765
  function resolveConfig(userConfig, cliOptions, baseDir) {
1142
1766
  const cwd = resolveCandidateCwd(userConfig ?? null, cliOptions, baseDir);
1143
- const preset = detectPreset(cwd);
1767
+ const forcedPreset = userConfig?.preset;
1768
+ if (forcedPreset !== void 0 && !getPreset(forcedPreset)) {
1769
+ throw new Error(unknownPresetMessage(forcedPreset));
1770
+ }
1771
+ const preset = detectPreset(cwd, forcedPreset);
1144
1772
  const layersMode = resolveLayersMode(userConfig?.layersMode);
1145
1773
  const layers = userConfig?.layers && userConfig.layers.length > 0 ? layersMode === "replace" ? userConfig.layers : [...userConfig.layers, ...preset.layers] : preset.layers;
1146
1774
  const kinds = userConfig?.kinds && userConfig.kinds.length > 0 ? layersMode === "replace" ? userConfig.kinds : [...userConfig.kinds, ...preset.kinds] : preset.kinds;
@@ -1176,6 +1804,8 @@ function resolveConfig(userConfig, cliOptions, baseDir) {
1176
1804
  ),
1177
1805
  includeSensitiveFiles: cliOptions?.includeSensitiveFiles ?? userConfig?.includeSensitiveFiles ?? false,
1178
1806
  include: resolveInclude(userConfig?.include),
1807
+ preset: preset.presetName,
1808
+ presetExclude: [...preset.exclude],
1179
1809
  artifacts,
1180
1810
  layersMode,
1181
1811
  budgets,
@@ -1210,8 +1840,8 @@ function matchRule(filePath, matcher) {
1210
1840
  return false;
1211
1841
  }
1212
1842
  function classifyLayer(filePath, layers) {
1213
- for (const layer of layers) {
1214
- if (matchRule(filePath, layer.match)) return layer.name;
1843
+ for (const layer2 of layers) {
1844
+ if (matchRule(filePath, layer2.match)) return layer2.name;
1215
1845
  }
1216
1846
  return "other";
1217
1847
  }
@@ -1315,10 +1945,10 @@ var LANGUAGE_BY_BASENAME = {
1315
1945
  };
1316
1946
  function detectLanguage(filePath) {
1317
1947
  const normalized = filePath.replace(/\\/g, "/");
1318
- const base = path9__namespace.posix.basename(normalized);
1948
+ const base = path10__namespace.posix.basename(normalized);
1319
1949
  if (LANGUAGE_BY_BASENAME[base]) return LANGUAGE_BY_BASENAME[base];
1320
1950
  if (base === ".env" || base.startsWith(".env.")) return "dotenv";
1321
- const ext = path9__namespace.posix.extname(base).toLowerCase();
1951
+ const ext = path10__namespace.posix.extname(base).toLowerCase();
1322
1952
  return LANGUAGE_BY_EXTENSION[ext] ?? "text";
1323
1953
  }
1324
1954
 
@@ -1333,35 +1963,35 @@ function describeMatcher(matcher) {
1333
1963
  return String(matcher);
1334
1964
  }
1335
1965
  function normalizeTarget(cwd, target) {
1336
- const absolute = path9__namespace.resolve(cwd, target);
1337
- const relative4 = path9__namespace.relative(cwd, absolute);
1338
- if (relative4.startsWith("..") || path9__namespace.isAbsolute(relative4)) return target;
1339
- return relative4.split(path9__namespace.sep).join("/");
1966
+ const absolute = path10__namespace.resolve(cwd, target);
1967
+ const relative4 = path10__namespace.relative(cwd, absolute);
1968
+ if (relative4.startsWith("..") || path10__namespace.isAbsolute(relative4)) return target;
1969
+ return relative4.split(path10__namespace.sep).join("/");
1340
1970
  }
1341
1971
  function findRuleMatch(rules, relativePath) {
1342
1972
  return rules.find(
1343
1973
  (rule) => matchRule(relativePath, rule.match)
1344
1974
  );
1345
1975
  }
1346
- function readManifest(config) {
1347
- const manifestPath = path9__namespace.join(config.cwd, config.outDir, "manifest.json");
1976
+ function readManifest2(config) {
1977
+ const manifestPath = path10__namespace.resolve(config.cwd, config.outDir, "manifest.json");
1348
1978
  try {
1349
- return JSON.parse(fs8__namespace.readFileSync(manifestPath, "utf8"));
1979
+ return JSON.parse(fs9__namespace.readFileSync(manifestPath, "utf8"));
1350
1980
  } catch {
1351
1981
  return null;
1352
1982
  }
1353
1983
  }
1354
1984
  function explainPath(config, target) {
1355
1985
  const relative4 = normalizeTarget(config.cwd, target);
1356
- const absolute = path9__namespace.join(config.cwd, relative4);
1986
+ const absolute = path10__namespace.join(config.cwd, relative4);
1357
1987
  const result = {
1358
1988
  path: relative4,
1359
- exists: fs8__namespace.existsSync(absolute),
1989
+ exists: fs9__namespace.existsSync(absolute),
1360
1990
  included: false
1361
1991
  };
1362
1992
  const collection = collectFiles(config.cwd, {
1363
1993
  includePatterns: config.include,
1364
- excludePatterns: config.excludePatterns,
1994
+ excludePatterns: [...config.excludePatterns, ...config.presetExclude],
1365
1995
  includeSensitiveFiles: config.includeSensitiveFiles
1366
1996
  });
1367
1997
  if (!collection.files.includes(relative4)) {
@@ -1386,16 +2016,16 @@ function explainPath(config, target) {
1386
2016
  if (layerRule) result.layerMatch = describeMatcher(layerRule.match);
1387
2017
  const kindRule = findRuleMatch(config.kinds, relative4);
1388
2018
  if (kindRule) result.kindMatch = describeMatcher(kindRule.match);
1389
- if (fs8__namespace.existsSync(absolute) && fs8__namespace.statSync(absolute).isFile()) {
1390
- const buffer = fs8__namespace.readFileSync(absolute);
1391
- const text = buffer.toString("utf8");
2019
+ if (fs9__namespace.existsSync(absolute) && fs9__namespace.statSync(absolute).isFile()) {
2020
+ const buffer = fs9__namespace.readFileSync(absolute);
2021
+ const text2 = buffer.toString("utf8");
1392
2022
  result.bytes = buffer.length;
1393
- result.lines = countLines(text);
1394
- const { findings } = scanAndRedactSecrets(text, config.secretRules, relative4);
2023
+ result.lines = countLines(text2);
2024
+ const { findings } = scanAndRedactSecrets(text2, config.secretRules, relative4);
1395
2025
  result.secretCount = findings.length;
1396
2026
  result.secrets = findings.slice(0, 20).map((finding) => ({ rule: finding.rule, line: finding.line }));
1397
2027
  }
1398
- const manifest = readManifest(config);
2028
+ const manifest = readManifest2(config);
1399
2029
  const entry = manifest?.files.find((file) => file.relativePath === relative4);
1400
2030
  result.part = entry?.part ?? null;
1401
2031
  result.inclusionMode = entry?.inclusionMode;
@@ -1435,128 +2065,1133 @@ function renderExplainText(result) {
1435
2065
  return lines.join("\n");
1436
2066
  }
1437
2067
 
1438
- // src/ast/structured.ts
1439
- function stripJsonComments2(content) {
1440
- const text = content.replace(/^\uFEFF/, "");
1441
- let out = "";
1442
- let inString = false;
1443
- let inLineComment = false;
1444
- let inBlockComment = false;
1445
- for (let i = 0; i < text.length; i++) {
1446
- const char = text[i];
1447
- const next = text[i + 1];
1448
- if (inLineComment) {
1449
- if (char === "\n") {
1450
- inLineComment = false;
1451
- out += char;
1452
- }
1453
- continue;
1454
- }
1455
- if (inBlockComment) {
1456
- if (char === "*" && next === "/") {
1457
- inBlockComment = false;
1458
- i += 1;
1459
- }
1460
- continue;
1461
- }
1462
- if (inString) {
1463
- out += char;
1464
- if (char === "\\") {
1465
- if (next !== void 0) {
1466
- out += next;
1467
- i += 1;
1468
- }
1469
- } else if (char === '"') {
1470
- inString = false;
1471
- }
1472
- continue;
1473
- }
1474
- if (char === '"') {
1475
- inString = true;
1476
- out += char;
1477
- continue;
1478
- }
1479
- if (char === "/" && next === "/") {
1480
- inLineComment = true;
1481
- i += 1;
1482
- continue;
1483
- }
1484
- if (char === "/" && next === "*") {
1485
- inBlockComment = true;
1486
- i += 1;
1487
- continue;
1488
- }
1489
- out += char;
1490
- }
1491
- return out;
2068
+ // src/wizard/config-template.ts
2069
+ var DEFAULT_ANSWERS = {
2070
+ preset: "generic",
2071
+ maxPartBytes: "300k",
2072
+ largeFileBytes: "200k",
2073
+ artifacts: {
2074
+ readme: true,
2075
+ manifest: true,
2076
+ parts: true,
2077
+ navigation: true,
2078
+ projectTree: true,
2079
+ audit: true,
2080
+ monolith: false
2081
+ },
2082
+ includeSensitiveFiles: false,
2083
+ outDir: "snapshot"
2084
+ };
2085
+ function list(values) {
2086
+ if (values.length === 0) return "[]";
2087
+ return `[${values.map((value) => JSON.stringify(value)).join(", ")}]`;
1492
2088
  }
1493
- function summarizeJson(content) {
1494
- try {
1495
- const parsed = JSON.parse(stripJsonComments2(content));
1496
- if (Array.isArray(parsed)) {
1497
- const sample = parsed[0];
1498
- const sampleKeys = sample && typeof sample === "object" && !Array.isArray(sample) ? Object.keys(sample).slice(0, 10).sort() : [];
1499
- return {
1500
- shape: "array",
1501
- itemCount: parsed.length,
1502
- keys: [],
1503
- sampleKeys
1504
- };
2089
+ function size(value) {
2090
+ return typeof value === "string" ? JSON.stringify(value) : String(value);
2091
+ }
2092
+ var PRESET_NAMES = [
2093
+ "nextjs \xB7 react-vite \xB7 vue-vite \xB7 nuxt \xB7 sveltekit \xB7 angular \xB7 astro",
2094
+ "remix \xB7 solid \xB7 nest \xB7 node-express \xB7 node-lib",
2095
+ "python-django \xB7 python-fastapi \xB7 python-lib \xB7 go \xB7 rust",
2096
+ "java-spring \xB7 dotnet \xB7 php-laravel \xB7 ruby-rails",
2097
+ "monorepo \xB7 docs \xB7 infra \xB7 generic"
2098
+ ];
2099
+ function renderConfig(answers) {
2100
+ const { artifacts } = answers;
2101
+ return `import { defineConfig } from "@ankurhalder/codebase-snapshot";
2102
+
2103
+ export default defineConfig({
2104
+ // \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
2105
+ // Which project shape to use. Detected from the repo when omitted.
2106
+ // ${PRESET_NAMES.join("\n // ")}
2107
+ preset: ${JSON.stringify(answers.preset)},
2108
+
2109
+ // Recorded in the manifest when git cannot report a branch.
2110
+ branch: "main",
2111
+
2112
+ // \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
2113
+ // Where the snapshot is written.
2114
+ outDir: ${JSON.stringify(answers.outDir)},
2115
+ // Scratch directory; the new snapshot is renamed into place atomically.
2116
+ tmpDir: "snapshot.tmp",
2117
+
2118
+ // \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
2119
+ // Hard ceiling per part. "300k", 307200 and "1.5mb" are all accepted.
2120
+ maxPartBytes: ${size(answers.maxPartBytes)},
2121
+ // Files above this size get a head/tail preview instead of the full body.
2122
+ largeFileBytes: ${size(answers.largeFileBytes)},
2123
+ previewHeadLines: 80,
2124
+ previewTailLines: 40,
2125
+ // Different ceilings per layer. Unlisted layers use \`default\`.
2126
+ budgets: {
2127
+ default: ${size(answers.maxPartBytes)},
2128
+ byLayer: {},
2129
+ },
2130
+
2131
+ // \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
2132
+ // Allow-list. Empty means "everything that is not ignored".
2133
+ // Example: ["src/**", "packages/*/src/**"]
2134
+ include: ${list(answers.include)},
2135
+ // Globs or path segments to drop. Example: ["**/*.test.ts", "docs"]
2136
+ excludePatterns: ${list(answers.excludePatterns)},
2137
+
2138
+ // \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
2139
+ // "merge" = the preset's rules plus yours (yours win). "replace" = yours only.
2140
+ layersMode: "merge",
2141
+ // [{ name: "billing", match: "src/features/billing/**" }]
2142
+ layers: [],
2143
+ // [{ kind: "route", match: "src/app/api/**" }]
2144
+ kinds: [],
2145
+
2146
+ // \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
2147
+ // .env, *.pem, .npmrc, .ssh/ and friends are skipped unless this is true.
2148
+ includeSensitiveFiles: ${answers.includeSensitiveFiles},
2149
+ secrets: {
2150
+ // [{ rule: "internal-token", pattern: /CORP-[A-Z0-9]{32}/ }]
2151
+ customPatterns: [],
2152
+ // Set true to drop the 21 built-in rules.
2153
+ disableDefaultPatterns: false,
2154
+ },
2155
+
2156
+ // \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
2157
+ artifacts: {
2158
+ readme: ${artifacts.readme}, // README.md
2159
+ manifest: ${artifacts.manifest}, // manifest.json \u2014 required by validate and report
2160
+ parts: ${artifacts.parts}, // parts/part-XXX.md
2161
+ navigation: ${artifacts.navigation}, // reports/navigation.md
2162
+ projectTree: ${artifacts.projectTree}, // reports/project-tree.md
2163
+ audit: ${artifacts.audit}, // reports/audit.md
2164
+ monolith: ${artifacts.monolith}, // full-codebase.json + .md (roughly doubles output)
2165
+ },
2166
+ // Skip the monolith above this size. null = no limit.
2167
+ maxMonolithBytes: null,
2168
+
2169
+ // \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
2170
+ // afterGenerate: (manifest) => { /* write your own artifact */ }
2171
+ hooks: {},
2172
+ });
2173
+ `;
2174
+ }
2175
+ function renderMinimalConfig(answers) {
2176
+ const lines = [];
2177
+ if (answers.preset !== DEFAULT_ANSWERS.preset) lines.push(` preset: ${JSON.stringify(answers.preset)},`);
2178
+ if (answers.maxPartBytes !== DEFAULT_ANSWERS.maxPartBytes) {
2179
+ lines.push(` maxPartBytes: ${size(answers.maxPartBytes)},`);
2180
+ }
2181
+ if (answers.largeFileBytes !== DEFAULT_ANSWERS.largeFileBytes) {
2182
+ lines.push(` largeFileBytes: ${size(answers.largeFileBytes)},`);
2183
+ }
2184
+ if (answers.include.length > 0) lines.push(` include: ${list(answers.include)},`);
2185
+ if (answers.excludePatterns.length > 0) {
2186
+ lines.push(` excludePatterns: ${list(answers.excludePatterns)},`);
2187
+ }
2188
+ if (answers.includeSensitiveFiles) lines.push(" includeSensitiveFiles: true,");
2189
+ if (answers.outDir !== DEFAULT_ANSWERS.outDir) lines.push(` outDir: ${JSON.stringify(answers.outDir)},`);
2190
+ const changed = Object.keys(answers.artifacts).filter(
2191
+ (name) => answers.artifacts[name] !== DEFAULT_ANSWERS.artifacts[name]
2192
+ );
2193
+ if (changed.length > 0) {
2194
+ const entries = changed.map((name) => ` ${name}: ${answers.artifacts[name]}`).join(",\n");
2195
+ lines.push(` artifacts: {
2196
+ ${entries},
2197
+ },`);
2198
+ }
2199
+ if (lines.length === 0) {
2200
+ return `import { defineConfig } from "@ankurhalder/codebase-snapshot";
2201
+
2202
+ export default defineConfig({});
2203
+ `;
2204
+ }
2205
+ return `import { defineConfig } from "@ankurhalder/codebase-snapshot";
2206
+
2207
+ export default defineConfig({
2208
+ ${lines.join("\n")}
2209
+ });
2210
+ `;
2211
+ }
2212
+ var PromptCancelled = class extends Error {
2213
+ constructor() {
2214
+ super("Prompt cancelled");
2215
+ this.name = "PromptCancelled";
2216
+ }
2217
+ };
2218
+ function defaultIO() {
2219
+ return { input: process.stdin, output: process.stdout };
2220
+ }
2221
+ function isInteractive(io) {
2222
+ return Boolean(io.input.isTTY && io.output.isTTY);
2223
+ }
2224
+ function moveSelection(current, delta, count) {
2225
+ if (count <= 0) return 0;
2226
+ return ((current + delta) % count + count) % count;
2227
+ }
2228
+ function toggleIndex(selected, index) {
2229
+ const next = new Set(selected);
2230
+ if (next.has(index)) next.delete(index);
2231
+ else next.add(index);
2232
+ return next;
2233
+ }
2234
+ function marker(active) {
2235
+ return active ? "\u276F" : " ";
2236
+ }
2237
+ function choiceLine(choice, active) {
2238
+ const hint = choice.hint ? ` ${choice.hint}` : "";
2239
+ return ` ${marker(active)} ${choice.label}${hint}`;
2240
+ }
2241
+ function renderSelect(message, choices, index) {
2242
+ const lines = [`\u25C6 ${message}`, ""];
2243
+ choices.forEach((choice, position) => lines.push(choiceLine(choice, position === index)));
2244
+ lines.push("", " \u2191\u2193 move \xB7 enter select");
2245
+ return lines.join("\n");
2246
+ }
2247
+ function renderMultiSelect(message, choices, index, selected) {
2248
+ const lines = [`\u25C6 ${message}`, ""];
2249
+ choices.forEach((choice, position) => {
2250
+ const box = selected.has(position) ? "\u25FC" : "\u25FB";
2251
+ const hint = choice.hint ? ` ${choice.hint}` : "";
2252
+ lines.push(` ${marker(position === index)} ${box} ${choice.label}${hint}`);
2253
+ });
2254
+ lines.push("", " \u2191\u2193 move \xB7 space toggle \xB7 enter confirm");
2255
+ return lines.join("\n");
2256
+ }
2257
+ function renderConfirm(message, value) {
2258
+ return [
2259
+ `\u25C6 ${message}`,
2260
+ "",
2261
+ ` ${marker(value)} Yes`,
2262
+ ` ${marker(!value)} No`,
2263
+ "",
2264
+ " \u2190\u2192 or y/n \xB7 enter confirm"
2265
+ ].join("\n");
2266
+ }
2267
+ function renderText(message, value, error) {
2268
+ const lines = [`\u25C6 ${message}`, "", ` ${value || "\u258F"}`];
2269
+ if (error) lines.push("", ` \u2716 ${error}`);
2270
+ return lines.join("\n");
2271
+ }
2272
+ function keyReader(input) {
2273
+ const queue = [];
2274
+ const waiters = [];
2275
+ const wasRaw = input.isRaw;
2276
+ const onKey = (_text, key) => {
2277
+ const waiter = waiters.shift();
2278
+ if (waiter) waiter(key);
2279
+ else queue.push(key);
2280
+ };
2281
+ readline__namespace.emitKeypressEvents(input);
2282
+ if (input.isTTY) input.setRawMode(true);
2283
+ input.resume();
2284
+ input.on("keypress", onKey);
2285
+ return {
2286
+ next() {
2287
+ const queued = queue.shift();
2288
+ if (queued) return Promise.resolve(queued);
2289
+ return new Promise((resolve6) => waiters.push(resolve6));
2290
+ },
2291
+ close() {
2292
+ input.off("keypress", onKey);
2293
+ if (input.isTTY) input.setRawMode(wasRaw ?? false);
2294
+ input.pause();
1505
2295
  }
1506
- if (parsed && typeof parsed === "object") {
1507
- const keys = Object.keys(parsed).sort();
1508
- return {
1509
- shape: "object",
1510
- itemCount: keys.length,
1511
- keys: keys.slice(0, 30),
1512
- sampleKeys: []
1513
- };
2296
+ };
2297
+ }
2298
+ var Frame = class {
2299
+ constructor(output) {
2300
+ this.output = output;
2301
+ }
2302
+ output;
2303
+ drawn = 0;
2304
+ draw(text2) {
2305
+ this.erase();
2306
+ this.output.write(`${text2}
2307
+ `);
2308
+ this.drawn = text2.split("\n").length;
2309
+ }
2310
+ erase() {
2311
+ if (this.drawn > 0) this.output.write(`\x1B[${this.drawn}A\x1B[0J`);
2312
+ this.drawn = 0;
2313
+ }
2314
+ };
2315
+ function isCancel(key) {
2316
+ return key.ctrl === true && key.name === "c" || key.ctrl === true && key.name === "d";
2317
+ }
2318
+ async function select(io, message, choices, options = {}) {
2319
+ if (choices.length === 0) throw new Error("select() requires at least one choice.");
2320
+ const fallbackIndex = Math.max(
2321
+ 0,
2322
+ choices.findIndex((choice) => choice.value === options.defaultValue)
2323
+ );
2324
+ if (!isInteractive(io)) {
2325
+ io.output.write(`${renderSelect(message, choices, fallbackIndex)}
2326
+ `);
2327
+ return choices[fallbackIndex].value;
2328
+ }
2329
+ const frame = new Frame(io.output);
2330
+ const reader = keyReader(io.input);
2331
+ let index = fallbackIndex;
2332
+ try {
2333
+ for (; ; ) {
2334
+ frame.draw(renderSelect(message, choices, index));
2335
+ const key = await reader.next();
2336
+ if (isCancel(key)) throw new PromptCancelled();
2337
+ if (key.name === "up") index = moveSelection(index, -1, choices.length);
2338
+ else if (key.name === "down") index = moveSelection(index, 1, choices.length);
2339
+ else if (key.name === "return" || key.name === "enter") {
2340
+ frame.erase();
2341
+ return choices[index].value;
2342
+ } else if (key.name === "j") index = moveSelection(index, 1, choices.length);
2343
+ else if (key.name === "k") index = moveSelection(index, -1, choices.length);
1514
2344
  }
1515
- return {
1516
- shape: typeof parsed,
1517
- itemCount: null,
1518
- keys: [],
1519
- sampleKeys: []
1520
- };
2345
+ } finally {
2346
+ reader.close();
2347
+ }
2348
+ }
2349
+ async function multiSelect(io, message, choices, options = {}) {
2350
+ const selected = /* @__PURE__ */ new Set();
2351
+ choices.forEach((choice, position) => {
2352
+ if (options.defaults?.includes(choice.value)) selected.add(position);
2353
+ });
2354
+ const result = () => choices.filter((_, position) => selected.has(position)).map((choice) => choice.value);
2355
+ if (!isInteractive(io)) {
2356
+ io.output.write(`${renderMultiSelect(message, choices, 0, selected)}
2357
+ `);
2358
+ return result();
2359
+ }
2360
+ const frame = new Frame(io.output);
2361
+ const reader = keyReader(io.input);
2362
+ let index = 0;
2363
+ try {
2364
+ for (; ; ) {
2365
+ frame.draw(renderMultiSelect(message, choices, index, selected));
2366
+ const key = await reader.next();
2367
+ if (isCancel(key)) throw new PromptCancelled();
2368
+ if (key.name === "up") index = moveSelection(index, -1, choices.length);
2369
+ else if (key.name === "down") index = moveSelection(index, 1, choices.length);
2370
+ else if (key.name === "space") {
2371
+ const next = toggleIndex(selected, index);
2372
+ selected.clear();
2373
+ for (const position of next) selected.add(position);
2374
+ } else if (key.name === "a") {
2375
+ if (selected.size === choices.length) selected.clear();
2376
+ else choices.forEach((_, position) => selected.add(position));
2377
+ } else if (key.name === "return" || key.name === "enter") {
2378
+ frame.erase();
2379
+ return result();
2380
+ }
2381
+ }
2382
+ } finally {
2383
+ reader.close();
2384
+ }
2385
+ }
2386
+ async function confirm(io, message, options = {}) {
2387
+ let value = options.defaultValue ?? true;
2388
+ if (!isInteractive(io)) {
2389
+ io.output.write(`${renderConfirm(message, value)}
2390
+ `);
2391
+ return value;
2392
+ }
2393
+ const frame = new Frame(io.output);
2394
+ const reader = keyReader(io.input);
2395
+ try {
2396
+ for (; ; ) {
2397
+ frame.draw(renderConfirm(message, value));
2398
+ const key = await reader.next();
2399
+ if (isCancel(key)) throw new PromptCancelled();
2400
+ if (key.name === "up" || key.name === "left") value = true;
2401
+ else if (key.name === "down" || key.name === "right") value = false;
2402
+ else if (key.name === "y") value = true;
2403
+ else if (key.name === "n") value = false;
2404
+ else if (key.name === "return" || key.name === "enter") {
2405
+ frame.erase();
2406
+ return value;
2407
+ }
2408
+ }
2409
+ } finally {
2410
+ reader.close();
2411
+ }
2412
+ }
2413
+ async function text(io, message, options = {}) {
2414
+ const fallback = options.defaultValue ?? "";
2415
+ if (!isInteractive(io)) {
2416
+ io.output.write(`${renderText(message, fallback)}
2417
+ `);
2418
+ return fallback;
2419
+ }
2420
+ const frame = new Frame(io.output);
2421
+ const reader = keyReader(io.input);
2422
+ let value = fallback;
2423
+ let error;
2424
+ try {
2425
+ for (; ; ) {
2426
+ frame.draw(renderText(message, value, error));
2427
+ const key = await reader.next();
2428
+ if (isCancel(key)) throw new PromptCancelled();
2429
+ error = void 0;
2430
+ if (key.name === "return" || key.name === "enter") {
2431
+ const problem = options.validate ? options.validate(value) : null;
2432
+ if (problem) {
2433
+ error = problem;
2434
+ continue;
2435
+ }
2436
+ frame.erase();
2437
+ return value;
2438
+ }
2439
+ if (key.name === "backspace") {
2440
+ value = value.slice(0, -1);
2441
+ continue;
2442
+ }
2443
+ if (key.name === "escape") {
2444
+ value = "";
2445
+ continue;
2446
+ }
2447
+ if (_isPrintable(key)) value += _keyText(key);
2448
+ }
2449
+ } finally {
2450
+ reader.close();
2451
+ }
2452
+ }
2453
+ function _isPrintable(key) {
2454
+ return typeof key.sequence === "string" && key.sequence.length === 1 && key.sequence >= " ";
2455
+ }
2456
+ function _keyText(key) {
2457
+ return key.sequence ?? "";
2458
+ }
2459
+
2460
+ // src/wizard/init.ts
2461
+ var CONFIG_CANDIDATES = [
2462
+ "snapshot.config.ts",
2463
+ "snapshot.config.js",
2464
+ "snapshot.config.mjs",
2465
+ "snapshot.config.cjs",
2466
+ "snapshot.config.json",
2467
+ ".snapshotrc.json",
2468
+ ".snapshotrc"
2469
+ ];
2470
+ var SIZE_CHOICES = [
2471
+ { value: "60k", label: "Small", hint: "60 KB \xB7 ~15k tokens \xB7 tight context windows" },
2472
+ { value: "120k", label: "Medium", hint: "120 KB \xB7 ~30k tokens \xB7 recommended" },
2473
+ { value: "300k", label: "Large", hint: "300 KB \xB7 ~75k tokens \xB7 the default" },
2474
+ { value: "custom", label: "Custom\u2026", hint: "type a size like 150k or 1.5mb" }
2475
+ ];
2476
+ function existingConfig(cwd) {
2477
+ for (const name of CONFIG_CANDIDATES) {
2478
+ if (fs9__namespace.existsSync(path10__namespace.join(cwd, name))) return name;
2479
+ }
2480
+ return null;
2481
+ }
2482
+ function artifactAnswers(options) {
2483
+ const artifacts = { ...DEFAULT_ANSWERS.artifacts };
2484
+ for (const name of options.skipArtifacts ?? []) {
2485
+ if (ARTIFACT_NAMES.includes(name)) {
2486
+ artifacts[name] = false;
2487
+ }
2488
+ }
2489
+ if (options.onlyArtifacts && options.onlyArtifacts.length > 0) {
2490
+ const keep = new Set(options.onlyArtifacts);
2491
+ for (const name of ARTIFACT_NAMES) artifacts[name] = keep.has(name);
2492
+ }
2493
+ return artifacts;
2494
+ }
2495
+ function topLevelDirs(cwd) {
2496
+ try {
2497
+ return fs9__namespace.readdirSync(cwd, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules").map((entry) => entry.name).sort();
1521
2498
  } catch {
2499
+ return [];
2500
+ }
2501
+ }
2502
+ function addScripts(cwd, notes) {
2503
+ const packagePath = path10__namespace.join(cwd, "package.json");
2504
+ if (!fs9__namespace.existsSync(packagePath)) {
2505
+ notes.push("No package.json found, so npm scripts were not added.");
2506
+ return [];
2507
+ }
2508
+ let parsed;
2509
+ try {
2510
+ parsed = JSON.parse(fs9__namespace.readFileSync(packagePath, "utf8"));
2511
+ } catch {
2512
+ notes.push("Could not parse package.json; npm scripts were not added.");
2513
+ return [];
2514
+ }
2515
+ const scripts = parsed.scripts && typeof parsed.scripts === "object" ? parsed.scripts : {};
2516
+ const added = [];
2517
+ if (!scripts.snapshot) {
2518
+ scripts.snapshot = "codebase-snapshot generate";
2519
+ added.push("snapshot");
2520
+ }
2521
+ if (!scripts["snapshot:validate"]) {
2522
+ scripts["snapshot:validate"] = "codebase-snapshot validate";
2523
+ added.push("snapshot:validate");
2524
+ }
2525
+ if (added.length === 0) return [];
2526
+ parsed.scripts = scripts;
2527
+ fs9__namespace.writeFileSync(packagePath, `${JSON.stringify(parsed, null, 2)}
2528
+ `, "utf8");
2529
+ return added;
2530
+ }
2531
+ function addGitignore(cwd) {
2532
+ const ignorePath = path10__namespace.join(cwd, ".gitignore");
2533
+ const existing = fs9__namespace.existsSync(ignorePath) ? fs9__namespace.readFileSync(ignorePath, "utf8") : "";
2534
+ const wanted = ["snapshot/", "snapshot.tmp/"];
2535
+ const added = wanted.filter((entry) => !existing.split(/\r?\n/).includes(entry));
2536
+ if (added.length === 0) return [];
2537
+ const prefix = existing.length === 0 || existing.endsWith("\n") ? "" : "\n";
2538
+ fs9__namespace.writeFileSync(ignorePath, `${existing}${prefix}${added.join("\n")}
2539
+ `, "utf8");
2540
+ return added;
2541
+ }
2542
+ function resolvePresetChoice(answers, presetName) {
2543
+ return {
2544
+ value: presetName,
2545
+ label: presetName,
2546
+ hint: presetName === answers.preset ? "detected" : void 0
2547
+ };
2548
+ }
2549
+ async function runInit(options) {
2550
+ const io = options.io ?? defaultIO();
2551
+ const detected = detectPreset(options.cwd, options.preset);
2552
+ const notes = [];
2553
+ const answers = {
2554
+ preset: detected.presetName,
2555
+ maxPartBytes: options.maxPartBytes ?? detected.recommendedBudget ?? DEFAULT_ANSWERS.maxPartBytes,
2556
+ largeFileBytes: DEFAULT_ANSWERS.largeFileBytes,
2557
+ include: options.include ?? [],
2558
+ excludePatterns: options.exclude ?? [],
2559
+ artifacts: artifactAnswers(options),
2560
+ includeSensitiveFiles: options.includeSensitiveFiles ?? DEFAULT_ANSWERS.includeSensitiveFiles,
2561
+ outDir: options.outDir ?? DEFAULT_ANSWERS.outDir
2562
+ };
2563
+ const interactive = isInteractive(io) && options.yes !== true;
2564
+ if (interactive) {
2565
+ io.output.write("\n @ankurhalder/codebase-snapshot\n");
2566
+ io.output.write(" Turn this repository into an LLM-ready, redacted, byte-bounded snapshot.\n\n");
2567
+ io.output.write(` Detected ${detected.presetName}
2568
+ `);
2569
+ io.output.write(` Directory ${options.cwd}
2570
+
2571
+ `);
2572
+ const useDetected = await confirm(io, `Set up for ${detected.presetName}?`, {
2573
+ defaultValue: true
2574
+ });
2575
+ if (!useDetected) {
2576
+ answers.preset = await select(
2577
+ io,
2578
+ "What kind of project is this?",
2579
+ listPresetNames().map((name) => resolvePresetChoice(answers, name))
2580
+ );
2581
+ const chosen = detectPreset(options.cwd, answers.preset);
2582
+ answers.preset = chosen.presetName;
2583
+ }
2584
+ const scope = await select(
2585
+ io,
2586
+ "What should go into the snapshot?",
2587
+ [
2588
+ { value: "all", label: "Everything the scanner finds", hint: "recommended \xB7 respects .gitignore" },
2589
+ { value: "folders", label: "Only specific folders\u2026" },
2590
+ { value: "no-tests", label: "Everything, but skip tests" },
2591
+ { value: "custom", label: "Let me write glob patterns" }
2592
+ ],
2593
+ { defaultValue: "all" }
2594
+ );
2595
+ if (scope === "folders") {
2596
+ const dirs = topLevelDirs(options.cwd).slice(0, 40);
2597
+ if (dirs.length === 0) {
2598
+ notes.push("No top-level directories found; the snapshot will include everything.");
2599
+ } else {
2600
+ const chosen = await multiSelect(
2601
+ io,
2602
+ "Select folders",
2603
+ dirs.map((name) => ({ value: name, label: `${name}/` })),
2604
+ { defaults: dirs }
2605
+ );
2606
+ answers.include = chosen.map((name) => `${name}/**`);
2607
+ }
2608
+ } else if (scope === "no-tests") {
2609
+ answers.excludePatterns = [
2610
+ ...answers.excludePatterns,
2611
+ "**/*.test.*",
2612
+ "**/*.spec.*",
2613
+ "tests",
2614
+ "test"
2615
+ ];
2616
+ } else if (scope === "custom") {
2617
+ const raw = await text(io, "Glob patterns to include (comma separated)", {
2618
+ defaultValue: ""
2619
+ });
2620
+ answers.include = raw.split(",").map((value) => value.trim()).filter(Boolean);
2621
+ }
2622
+ const sizeChoice = await select(io, "How large should each part be?", SIZE_CHOICES, {
2623
+ defaultValue: "120k"
2624
+ });
2625
+ if (sizeChoice === "custom") {
2626
+ const raw = await text(io, "Part size (e.g. 150k, 1.5mb, 900000)", {
2627
+ defaultValue: String(answers.maxPartBytes),
2628
+ validate: (value) => parseByteSize(value) === void 0 ? "Not a valid size." : null
2629
+ });
2630
+ answers.maxPartBytes = parseByteSize(raw) ?? answers.maxPartBytes;
2631
+ } else {
2632
+ answers.maxPartBytes = sizeChoice;
2633
+ }
2634
+ const artifactChoices = [
2635
+ { value: "readme", label: "README.md", hint: "orientation page" },
2636
+ { value: "manifest", label: "manifest.json", hint: "required by validate and report" },
2637
+ { value: "parts", label: "parts/", hint: "the code payload" },
2638
+ { value: "navigation", label: "reports/navigation.md", hint: "file \u2192 part index" },
2639
+ { value: "projectTree", label: "reports/project-tree.md", hint: "ASCII tree" },
2640
+ { value: "audit", label: "reports/audit.md", hint: "security and health findings" },
2641
+ { value: "monolith", label: "full-codebase.json + .md", hint: "roughly doubles output" }
2642
+ ];
2643
+ const presetEnabled = ARTIFACT_NAMES.filter((name) => answers.artifacts[name]);
2644
+ const selectedArtifacts = await multiSelect(
2645
+ io,
2646
+ "Which artifacts should be generated?",
2647
+ artifactChoices,
2648
+ { defaults: presetEnabled }
2649
+ );
2650
+ for (const name of ARTIFACT_NAMES) answers.artifacts[name] = selectedArtifacts.includes(name);
2651
+ const enabledCount = ARTIFACT_NAMES.filter((name) => answers.artifacts[name]).length;
2652
+ answers.includeSensitiveFiles = await confirm(
2653
+ io,
2654
+ "Include credential files (.env, *.pem, .npmrc, .ssh/)?",
2655
+ { defaultValue: false }
2656
+ );
2657
+ io.output.write("\n Review\n\n");
2658
+ io.output.write(` project ${answers.preset}
2659
+ `);
2660
+ io.output.write(` include ${answers.include.length ? answers.include.join(", ") : "everything"}
2661
+ `);
2662
+ io.output.write(` part size ${String(answers.maxPartBytes)}
2663
+ `);
2664
+ io.output.write(` artifacts ${enabledCount} of ${ARTIFACT_NAMES.length}
2665
+ `);
2666
+ io.output.write(` credentials ${answers.includeSensitiveFiles ? "included" : "skipped"}
2667
+
2668
+ `);
2669
+ io.output.write(" Will write snapshot.config with your answers, plus every other setting\n");
2670
+ io.output.write(" at its default \u2014 so nothing is hidden behind the docs.\n\n");
2671
+ const proceed = await confirm(io, "Write this and continue?", { defaultValue: true });
2672
+ if (!proceed) throw new PromptCancelled();
2673
+ }
2674
+ const configText = options.minimal ? renderMinimalConfig(answers) : renderConfig(answers);
2675
+ const configName = supportsTypeScriptImports() ? "snapshot.config.ts" : "snapshot.config.mjs";
2676
+ const configPath = path10__namespace.join(options.cwd, configName);
2677
+ if (options.dryRun) {
1522
2678
  return {
1523
- shape: "invalid-json",
1524
- itemCount: null,
1525
- keys: [],
1526
- sampleKeys: []
2679
+ configPath,
2680
+ configText,
2681
+ written: false,
2682
+ preset: answers.preset,
2683
+ scripts: [],
2684
+ gitignoreEntries: [],
2685
+ notes
1527
2686
  };
1528
2687
  }
1529
- }
1530
- function summarizeMarkdown(content) {
1531
- const headings = [];
1532
- const frontmatterKeys = [];
1533
- const lines = content.replace(/^\uFEFF/, "").split("\n");
1534
- let inFrontmatter = false;
1535
- for (let index = 0; index < lines.length; index++) {
1536
- const line = lines[index];
1537
- if (index === 0 && line.trim() === "---") {
1538
- inFrontmatter = true;
1539
- continue;
1540
- }
1541
- if (inFrontmatter) {
1542
- if (line.trim() === "---") {
1543
- inFrontmatter = false;
1544
- continue;
2688
+ const present = existingConfig(options.cwd);
2689
+ if (present && present !== configName && !options.force) {
2690
+ throw new Error(
2691
+ `A configuration already exists (${present}). Re-run with --force to replace it.`
2692
+ );
2693
+ }
2694
+ if (fs9__namespace.existsSync(configPath) && !options.force) {
2695
+ throw new Error(`${configName} already exists. Re-run with --force to replace it.`);
2696
+ }
2697
+ fs9__namespace.writeFileSync(configPath, configText, "utf8");
2698
+ const scripts = addScripts(options.cwd, notes);
2699
+ const gitignoreEntries = addGitignore(options.cwd);
2700
+ return {
2701
+ configPath,
2702
+ configText,
2703
+ written: true,
2704
+ preset: answers.preset,
2705
+ scripts,
2706
+ gitignoreEntries,
2707
+ notes
2708
+ };
2709
+ }
2710
+
2711
+ // src/ast/profiles/shared.ts
2712
+ var MAX_SUMMARY_CHARS = 1e6;
2713
+ function emptySummary() {
2714
+ return {
2715
+ directives: [],
2716
+ imports: [],
2717
+ exports: [],
2718
+ reExports: [],
2719
+ functions: [],
2720
+ classes: [],
2721
+ interfaces: [],
2722
+ types: [],
2723
+ enums: [],
2724
+ variables: [],
2725
+ reactComponents: [],
2726
+ hooks: [],
2727
+ routeHandlers: []
2728
+ };
2729
+ }
2730
+ function dedupeSorted(values) {
2731
+ return Array.from(new Set(values.filter(Boolean))).sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
2732
+ }
2733
+ function finalizeSummary(summary) {
2734
+ return {
2735
+ directives: dedupeSorted(summary.directives),
2736
+ imports: dedupeSorted(summary.imports),
2737
+ exports: dedupeSorted(summary.exports),
2738
+ reExports: dedupeSorted(summary.reExports),
2739
+ functions: dedupeSorted(summary.functions),
2740
+ classes: dedupeSorted(summary.classes),
2741
+ interfaces: dedupeSorted(summary.interfaces),
2742
+ types: dedupeSorted(summary.types),
2743
+ enums: dedupeSorted(summary.enums),
2744
+ variables: dedupeSorted(summary.variables),
2745
+ reactComponents: dedupeSorted(summary.reactComponents),
2746
+ hooks: dedupeSorted(summary.hooks),
2747
+ routeHandlers: dedupeSorted(summary.routeHandlers)
2748
+ };
2749
+ }
2750
+ function eachLine(content, visit) {
2751
+ const text2 = content.length > MAX_SUMMARY_CHARS ? content.slice(0, MAX_SUMMARY_CHARS) : content;
2752
+ for (const line of text2.split(/\r?\n/)) visit(line);
2753
+ }
2754
+ function isTopLevel(line) {
2755
+ return !/^[\s]/.test(line);
2756
+ }
2757
+ function isPublicName(name) {
2758
+ return name.length > 0 && !name.startsWith("_") && /^[A-Za-z]/.test(name);
2759
+ }
2760
+ function isCapitalizedName(name) {
2761
+ return /^[A-Z]/.test(name);
2762
+ }
2763
+
2764
+ // src/ast/profiles/cfamily.ts
2765
+ var INCLUDE = /^#\s*include\s*[<"]([^>"]+)[>"]/;
2766
+ var DEFINE = /^#\s*define\s+([A-Za-z_]\w*)/;
2767
+ var USING = /^using\s+(?:namespace\s+)?([\w:]+)\s*;/;
2768
+ var TYPE_DECL = /^(?:typedef\s+)?(?:struct|union|class|enum)\s+([A-Za-z_]\w*)/;
2769
+ var TYPEDEF = /^typedef\b[^;{}]*?\b([A-Za-z_]\w*)\s*;/;
2770
+ var FUNCTION = /^[A-Za-z_][\w \t*&:<>,]*?\b([A-Za-z_]\w*)\s*\([^;]*\)\s*\{?\s*$/;
2771
+ var NOT_A_FUNCTION = /^(?:if|for|while|switch|return|else|do|case|sizeof|catch|throw)\b/;
2772
+ var cfamilyProfile = {
2773
+ id: "cfamily",
2774
+ extensions: [".c", ".h", ".cc", ".cpp", ".cxx", ".hpp", ".hxx", ".m", ".mm"],
2775
+ summarize(_path, content) {
2776
+ const summary = emptySummary();
2777
+ eachLine(content, (line) => {
2778
+ const trimmed = line.trim();
2779
+ if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("/*") || trimmed.startsWith("*")) {
2780
+ return;
2781
+ }
2782
+ const included = INCLUDE.exec(trimmed);
2783
+ if (included) {
2784
+ summary.imports.push(included[1]);
2785
+ return;
2786
+ }
2787
+ const used = USING.exec(trimmed);
2788
+ if (used) {
2789
+ summary.imports.push(used[1]);
2790
+ return;
2791
+ }
2792
+ const macro = DEFINE.exec(trimmed);
2793
+ if (macro) {
2794
+ summary.variables.push(macro[1]);
2795
+ return;
2796
+ }
2797
+ if (!isTopLevel(line)) return;
2798
+ const declared = TYPE_DECL.exec(line);
2799
+ if (declared) {
2800
+ const kind = /^typedef\s+enum/.test(line) || /^enum\b/.test(trimmed) ? "enum" : "type";
2801
+ if (kind === "enum") summary.enums.push(declared[1]);
2802
+ else summary.classes.push(declared[1]);
2803
+ summary.exports.push(declared[1]);
2804
+ return;
2805
+ }
2806
+ const aliased = TYPEDEF.exec(line);
2807
+ if (aliased) {
2808
+ summary.types.push(aliased[1]);
2809
+ summary.exports.push(aliased[1]);
2810
+ return;
2811
+ }
2812
+ if (NOT_A_FUNCTION.test(trimmed)) return;
2813
+ const fn = FUNCTION.exec(line);
2814
+ if (fn) {
2815
+ summary.functions.push(fn[1]);
2816
+ summary.exports.push(fn[1]);
2817
+ }
2818
+ });
2819
+ return { summary: finalizeSummary(summary), quality: "heuristic" };
2820
+ }
2821
+ };
2822
+
2823
+ // src/ast/profiles/dotnet.ts
2824
+ var USING2 = /^using\s+([\w.]+)\s*;/;
2825
+ 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*)/;
2826
+ 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*[<(]/;
2827
+ var dotnetProfile = {
2828
+ id: "dotnet",
2829
+ extensions: [".cs"],
2830
+ summarize(_path, content) {
2831
+ const summary = emptySummary();
2832
+ eachLine(content, (line) => {
2833
+ const trimmed = line.trim();
2834
+ if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("/*") || trimmed.startsWith("*")) {
2835
+ return;
2836
+ }
2837
+ const used = USING2.exec(trimmed);
2838
+ if (used) {
2839
+ summary.imports.push(used[1]);
2840
+ return;
2841
+ }
2842
+ const declaration = TYPE_DECL2.exec(line);
2843
+ if (declaration) {
2844
+ const kind = declaration[1];
2845
+ const name = declaration[2];
2846
+ if (kind === "interface") summary.interfaces.push(name);
2847
+ else if (kind === "enum") summary.enums.push(name);
2848
+ else summary.classes.push(name);
2849
+ if (/^\s*public\b/.test(line)) summary.exports.push(name);
2850
+ return;
2851
+ }
2852
+ const method = METHOD.exec(line);
2853
+ if (method) {
2854
+ summary.functions.push(method[1]);
2855
+ if (/^\s*public\b/.test(line)) summary.exports.push(method[1]);
2856
+ }
2857
+ });
2858
+ return { summary: finalizeSummary(summary), quality: "heuristic" };
2859
+ }
2860
+ };
2861
+
2862
+ // src/ast/profiles/go.ts
2863
+ var FUNC = /^func\s+(?:\([^)]*\)\s*)?([A-Za-z_]\w*)/;
2864
+ var TYPE = /^type\s+([A-Za-z_]\w*)\s+(.*)$/;
2865
+ var VAR_CONST = /^(?:var|const)\s+([A-Za-z_]\w*)/;
2866
+ var IMPORT_SINGLE = /^import\s+(?:[\w.]+\s+)?"([^"]+)"/;
2867
+ var IMPORT_OPEN = /^import\s*\(/;
2868
+ var IMPORT_ENTRY = /^\s*(?:[\w.]+\s+)?"([^"]+)"/;
2869
+ var goProfile = {
2870
+ id: "go",
2871
+ extensions: [".go"],
2872
+ summarize(_path, content) {
2873
+ const summary = emptySummary();
2874
+ let inImportBlock = false;
2875
+ eachLine(content, (line) => {
2876
+ const trimmed = line.trim();
2877
+ if (!trimmed || trimmed.startsWith("//")) return;
2878
+ if (inImportBlock) {
2879
+ if (trimmed.startsWith(")")) {
2880
+ inImportBlock = false;
2881
+ return;
2882
+ }
2883
+ const entry = IMPORT_ENTRY.exec(line);
2884
+ if (entry) summary.imports.push(entry[1]);
2885
+ return;
2886
+ }
2887
+ if (IMPORT_OPEN.test(trimmed)) {
2888
+ inImportBlock = true;
2889
+ return;
2890
+ }
2891
+ const single = IMPORT_SINGLE.exec(trimmed);
2892
+ if (single) {
2893
+ summary.imports.push(single[1]);
2894
+ return;
2895
+ }
2896
+ if (!isTopLevel(line)) return;
2897
+ const method = FUNC.exec(line);
2898
+ if (method) {
2899
+ const name = method[1];
2900
+ summary.functions.push(name);
2901
+ if (isCapitalizedName(name)) summary.exports.push(name);
2902
+ return;
2903
+ }
2904
+ const declared = TYPE.exec(line);
2905
+ if (declared) {
2906
+ const name = declared[1];
2907
+ const body = declared[2].trim();
2908
+ if (body.startsWith("struct")) summary.classes.push(name);
2909
+ else if (body.startsWith("interface")) summary.interfaces.push(name);
2910
+ else summary.types.push(name);
2911
+ if (isCapitalizedName(name)) summary.exports.push(name);
2912
+ return;
2913
+ }
2914
+ const value = VAR_CONST.exec(line);
2915
+ if (value) {
2916
+ summary.variables.push(value[1]);
2917
+ if (isCapitalizedName(value[1])) summary.exports.push(value[1]);
2918
+ }
2919
+ });
2920
+ return { summary: finalizeSummary(summary), quality: "heuristic" };
2921
+ }
2922
+ };
2923
+
2924
+ // src/ast/profiles/jvm.ts
2925
+ var IMPORT = /^import\s+(?:static\s+)?([\w.]+?)(?:\.\*)?;?\s*$/;
2926
+ 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*)/;
2927
+ 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*)/;
2928
+ var SCALA_DEF = /^\s*def\s+([A-Za-z_]\w*)/;
2929
+ var JAVA_METHOD = /^\s+(?:public|private|protected)\s+(?:static\s+)?(?:final\s+)?(?:synchronized\s+)?(?:abstract\s+)?[\w<>[\],.?]+\s+([A-Za-z_]\w*)\s*\(/;
2930
+ var jvmProfile = {
2931
+ id: "jvm",
2932
+ extensions: [".java", ".kt", ".kts", ".scala"],
2933
+ summarize(path12, content) {
2934
+ const summary = emptySummary();
2935
+ const lower = path12.toLowerCase();
2936
+ const isJava = lower.endsWith(".java");
2937
+ const isKotlin = lower.endsWith(".kt") || lower.endsWith(".kts");
2938
+ eachLine(content, (line) => {
2939
+ const trimmed = line.trim();
2940
+ if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("*") || trimmed.startsWith("/*")) {
2941
+ return;
2942
+ }
2943
+ const imported = IMPORT.exec(trimmed);
2944
+ if (imported) {
2945
+ summary.imports.push(imported[1]);
2946
+ return;
2947
+ }
2948
+ const declaration = TYPE_DECL3.exec(line);
2949
+ if (declaration) {
2950
+ const kind = declaration[1];
2951
+ const name = declaration[2];
2952
+ if (kind === "interface" || kind === "trait") summary.interfaces.push(name);
2953
+ else if (kind === "enum") summary.enums.push(name);
2954
+ else summary.classes.push(name);
2955
+ const hidden = /^\s*(?:private|protected|internal)\b/.test(line);
2956
+ if (isJava ? /^\s*public\b/.test(line) : !hidden) summary.exports.push(name);
2957
+ return;
2958
+ }
2959
+ const kotlinFunction = KOTLIN_FUN.exec(line);
2960
+ if (kotlinFunction && isKotlin) {
2961
+ summary.functions.push(kotlinFunction[1]);
2962
+ const hidden = /^\s*(?:private|protected|internal)\b/.test(line);
2963
+ if (!hidden) summary.exports.push(kotlinFunction[1]);
2964
+ return;
2965
+ }
2966
+ const scalaFunction = SCALA_DEF.exec(line);
2967
+ if (scalaFunction && !isJava && !isKotlin) {
2968
+ summary.functions.push(scalaFunction[1]);
2969
+ const hidden = /^\s*(?:private|protected)\b/.test(line);
2970
+ if (!hidden) summary.exports.push(scalaFunction[1]);
2971
+ return;
2972
+ }
2973
+ if (isJava) {
2974
+ const method = JAVA_METHOD.exec(line);
2975
+ if (method) {
2976
+ summary.functions.push(method[1]);
2977
+ if (/^\s*public\b/.test(line)) summary.exports.push(method[1]);
2978
+ }
2979
+ }
2980
+ });
2981
+ return { summary: finalizeSummary(summary), quality: "heuristic" };
2982
+ }
2983
+ };
2984
+
2985
+ // src/ast/profiles/php.ts
2986
+ var USE = /^use\s+([\w\\]+)\s*(?:as\s+\w+)?;/;
2987
+ var DECLARE = /^(?:abstract\s+|final\s+|readonly\s+)*(class|interface|trait|enum)\s+([A-Za-z_]\w*)/;
2988
+ var FUNCTION2 = /^(?:public\s+|protected\s+|private\s+|static\s+|final\s+|abstract\s+)*function\s+([A-Za-z_]\w*)/;
2989
+ var phpProfile = {
2990
+ id: "php",
2991
+ extensions: [".php"],
2992
+ summarize(_path, content) {
2993
+ const summary = emptySummary();
2994
+ eachLine(content, (line) => {
2995
+ const trimmed = line.trim();
2996
+ if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("#") || trimmed.startsWith("/*")) {
2997
+ return;
2998
+ }
2999
+ const used = USE.exec(trimmed);
3000
+ if (used) {
3001
+ summary.imports.push(used[1].replace(/\\\\/g, "\\"));
3002
+ return;
3003
+ }
3004
+ const declaration = DECLARE.exec(trimmed);
3005
+ if (declaration) {
3006
+ const kind = declaration[1];
3007
+ const name = declaration[2];
3008
+ if (kind === "interface" || kind === "trait") summary.interfaces.push(name);
3009
+ else if (kind === "enum") summary.enums.push(name);
3010
+ else summary.classes.push(name);
3011
+ summary.exports.push(name);
3012
+ return;
3013
+ }
3014
+ const fn = FUNCTION2.exec(trimmed);
3015
+ if (fn) {
3016
+ summary.functions.push(fn[1]);
3017
+ if (!/^private\b/.test(trimmed)) summary.exports.push(fn[1]);
3018
+ }
3019
+ });
3020
+ return { summary: finalizeSummary(summary), quality: "heuristic" };
3021
+ }
3022
+ };
3023
+
3024
+ // src/ast/profiles/python.ts
3025
+ var IMPORT2 = /^import\s+(.+)$/;
3026
+ var FROM_IMPORT = /^from\s+([\w.]+)\s+import\s+(.+)$/;
3027
+ var CLASS = /^class\s+([A-Za-z_]\w*)/;
3028
+ var DEF = /^(?:async\s+)?def\s+([A-Za-z_]\w*)/;
3029
+ var CONSTANT = /^([A-Z_][A-Z0-9_]*)\s*(?::[^=]+)?=/;
3030
+ var ALL_DUNDER = /^__all__\s*=\s*\[([^\]]*)\]/;
3031
+ var ROUTE_DECORATOR = /^\s*@[\w.]*\b(?:get|post|put|patch|delete|head|options|route)\s*\(/i;
3032
+ var pythonProfile = {
3033
+ id: "python",
3034
+ extensions: [".py", ".pyi"],
3035
+ summarize(_path, content) {
3036
+ const summary = emptySummary();
3037
+ let routePending = false;
3038
+ eachLine(content, (line) => {
3039
+ const trimmed = line.trim();
3040
+ if (!trimmed || trimmed.startsWith("#")) return;
3041
+ if (ROUTE_DECORATOR.test(line)) {
3042
+ routePending = true;
3043
+ return;
3044
+ }
3045
+ const from = FROM_IMPORT.exec(trimmed);
3046
+ if (from) {
3047
+ summary.imports.push(from[1]);
3048
+ routePending = false;
3049
+ return;
3050
+ }
3051
+ const imported = IMPORT2.exec(trimmed);
3052
+ if (imported) {
3053
+ for (const part of imported[1].split(",")) {
3054
+ const name = part.trim().split(/\s+as\s+/)[0].trim();
3055
+ if (name) summary.imports.push(name);
3056
+ }
3057
+ routePending = false;
3058
+ return;
3059
+ }
3060
+ if (isTopLevel(line)) {
3061
+ const klass = CLASS.exec(line);
3062
+ if (klass) {
3063
+ summary.classes.push(klass[1]);
3064
+ if (isPublicName(klass[1])) summary.exports.push(klass[1]);
3065
+ routePending = false;
3066
+ return;
3067
+ }
3068
+ const definition = DEF.exec(line);
3069
+ if (definition) {
3070
+ const name = definition[1];
3071
+ summary.functions.push(name);
3072
+ if (routePending) {
3073
+ summary.routeHandlers.push(name);
3074
+ routePending = false;
3075
+ }
3076
+ if (isPublicName(name)) summary.exports.push(name);
3077
+ return;
3078
+ }
3079
+ const all = ALL_DUNDER.exec(trimmed);
3080
+ if (all) {
3081
+ for (const entry of all[1].split(",")) {
3082
+ const name = entry.trim().replace(/['"]/g, "").trim();
3083
+ if (name) summary.exports.push(name);
3084
+ }
3085
+ return;
3086
+ }
3087
+ const constant = CONSTANT.exec(line);
3088
+ if (constant) summary.variables.push(constant[1]);
3089
+ }
3090
+ });
3091
+ return { summary: finalizeSummary(summary), quality: "heuristic" };
3092
+ }
3093
+ };
3094
+
3095
+ // src/ast/profiles/ruby.ts
3096
+ var REQUIRE = /^require(?:_relative)?\s+["']([^"']+)["']/;
3097
+ var CLASS_OR_MODULE = /^(?:class|module)\s+([A-Z][\w:]*)/;
3098
+ var METHOD2 = /^def\s+(?:self\.)?([A-Za-z_]\w*[?!=]?)/;
3099
+ var rubyProfile = {
3100
+ id: "ruby",
3101
+ extensions: [".rb", ".rake", ".gemspec"],
3102
+ summarize(_path, content) {
3103
+ const summary = emptySummary();
3104
+ eachLine(content, (line) => {
3105
+ const trimmed = line.trim();
3106
+ if (!trimmed || trimmed.startsWith("#")) return;
3107
+ const required = REQUIRE.exec(trimmed);
3108
+ if (required) {
3109
+ summary.imports.push(required[1]);
3110
+ return;
1545
3111
  }
1546
- const match = /^([a-zA-Z0-9_-]+):/.exec(line);
1547
- if (match) frontmatterKeys.push(match[1]);
1548
- continue;
1549
- }
1550
- const headingMatch = /^(#{1,6})\s+(.+)$/.exec(line.trim());
1551
- if (headingMatch) {
1552
- headings.push(`${headingMatch[1]} ${headingMatch[2].trim()}`);
1553
- }
3112
+ if (!isTopLevel(line)) return;
3113
+ const declaration = CLASS_OR_MODULE.exec(line);
3114
+ if (declaration) {
3115
+ summary.classes.push(declaration[1]);
3116
+ summary.exports.push(declaration[1]);
3117
+ return;
3118
+ }
3119
+ const method = METHOD2.exec(line);
3120
+ if (method) {
3121
+ summary.functions.push(method[1]);
3122
+ summary.exports.push(method[1]);
3123
+ }
3124
+ });
3125
+ return { summary: finalizeSummary(summary), quality: "heuristic" };
1554
3126
  }
1555
- return {
1556
- headings: headings.slice(0, 40),
1557
- frontmatterKeys: Array.from(new Set(frontmatterKeys)).sort()
1558
- };
1559
- }
3127
+ };
3128
+
3129
+ // src/ast/profiles/rust.ts
3130
+ var USE2 = /^use\s+([^;]+);/;
3131
+ var FN = /^(pub(?:\([^)]*\))?\s+)?(?:const\s+)?(?:async\s+)?(?:unsafe\s+)?fn\s+([A-Za-z_]\w*)/;
3132
+ var STRUCT = /^(pub(?:\([^)]*\))?\s+)?struct\s+([A-Za-z_]\w*)/;
3133
+ var TRAIT = /^(pub(?:\([^)]*\))?\s+)?(?:unsafe\s+)?trait\s+([A-Za-z_]\w*)/;
3134
+ var ENUM = /^(pub(?:\([^)]*\))?\s+)?enum\s+([A-Za-z_]\w*)/;
3135
+ var TYPE2 = /^(pub(?:\([^)]*\))?\s+)?type\s+([A-Za-z_]\w*)/;
3136
+ var MOD = /^(pub(?:\([^)]*\))?\s+)?mod\s+([A-Za-z_]\w*)/;
3137
+ var IMPL = /^impl(?:<[^>]*>)?\s+(?:[\w:<>]+\s+for\s+)?([A-Za-z_]\w*)/;
3138
+ var rustProfile = {
3139
+ id: "rust",
3140
+ extensions: [".rs"],
3141
+ summarize(_path, content) {
3142
+ const summary = emptySummary();
3143
+ eachLine(content, (line) => {
3144
+ const trimmed = line.trim();
3145
+ if (!trimmed || trimmed.startsWith("//")) return;
3146
+ const used = USE2.exec(trimmed);
3147
+ if (used) {
3148
+ summary.imports.push(used[1].trim());
3149
+ return;
3150
+ }
3151
+ if (!isTopLevel(line)) return;
3152
+ const isPublic = /^pub\b/.test(trimmed);
3153
+ const fn = FN.exec(line);
3154
+ if (fn) {
3155
+ summary.functions.push(fn[2]);
3156
+ if (isPublic) summary.exports.push(fn[2]);
3157
+ return;
3158
+ }
3159
+ const struct = STRUCT.exec(line);
3160
+ if (struct) {
3161
+ summary.classes.push(struct[2]);
3162
+ if (isPublic) summary.exports.push(struct[2]);
3163
+ return;
3164
+ }
3165
+ const trait = TRAIT.exec(line);
3166
+ if (trait) {
3167
+ summary.interfaces.push(trait[2]);
3168
+ if (isPublic) summary.exports.push(trait[2]);
3169
+ return;
3170
+ }
3171
+ const enumeration = ENUM.exec(line);
3172
+ if (enumeration) {
3173
+ summary.enums.push(enumeration[2]);
3174
+ if (isPublic) summary.exports.push(enumeration[2]);
3175
+ return;
3176
+ }
3177
+ const alias = TYPE2.exec(line);
3178
+ if (alias) {
3179
+ summary.types.push(alias[2]);
3180
+ if (isPublic) summary.exports.push(alias[2]);
3181
+ return;
3182
+ }
3183
+ const module = MOD.exec(line);
3184
+ if (module) {
3185
+ summary.variables.push(module[2]);
3186
+ if (isPublic) summary.exports.push(module[2]);
3187
+ return;
3188
+ }
3189
+ const implementation = IMPL.exec(line);
3190
+ if (implementation) summary.classes.push(implementation[1]);
3191
+ });
3192
+ return { summary: finalizeSummary(summary), quality: "heuristic" };
3193
+ }
3194
+ };
1560
3195
  var ROUTE_HANDLER_NAMES = /* @__PURE__ */ new Set([
1561
3196
  "GET",
1562
3197
  "POST",
@@ -1596,9 +3231,9 @@ function summarizeTypeScriptSource(relativePath, content) {
1596
3231
  if (parseErrors > 0) summary.parseErrors = parseErrors;
1597
3232
  for (const statement of sourceFile.statements) {
1598
3233
  if (ts__default.default.isExpressionStatement(statement) && ts__default.default.isStringLiteral(statement.expression)) {
1599
- const text = statement.expression.text;
1600
- if (text === "use client" || text === "use server") {
1601
- summary.directives.push(text);
3234
+ const text2 = statement.expression.text;
3235
+ if (text2 === "use client" || text2 === "use server") {
3236
+ summary.directives.push(text2);
1602
3237
  }
1603
3238
  continue;
1604
3239
  }
@@ -1698,23 +3333,212 @@ function summarizeTypeScriptSource(relativePath, content) {
1698
3333
  stack.push(children[index]);
1699
3334
  }
1700
3335
  }
1701
- const dedupeSorted = (values) => Array.from(new Set(values)).sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
1702
- summary.directives = dedupeSorted(summary.directives);
1703
- summary.imports = dedupeSorted(summary.imports);
1704
- summary.exports = dedupeSorted(summary.exports);
1705
- summary.reExports = dedupeSorted(summary.reExports);
1706
- summary.functions = dedupeSorted(summary.functions);
1707
- summary.classes = dedupeSorted(summary.classes);
1708
- summary.interfaces = dedupeSorted(summary.interfaces);
1709
- summary.types = dedupeSorted(summary.types);
1710
- summary.enums = dedupeSorted(summary.enums);
1711
- summary.variables = dedupeSorted(summary.variables);
1712
- summary.reactComponents = dedupeSorted(summary.reactComponents);
1713
- summary.hooks = dedupeSorted(summary.hooks);
1714
- summary.routeHandlers = dedupeSorted(summary.routeHandlers);
3336
+ const dedupeSorted2 = (values) => Array.from(new Set(values)).sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
3337
+ summary.directives = dedupeSorted2(summary.directives);
3338
+ summary.imports = dedupeSorted2(summary.imports);
3339
+ summary.exports = dedupeSorted2(summary.exports);
3340
+ summary.reExports = dedupeSorted2(summary.reExports);
3341
+ summary.functions = dedupeSorted2(summary.functions);
3342
+ summary.classes = dedupeSorted2(summary.classes);
3343
+ summary.interfaces = dedupeSorted2(summary.interfaces);
3344
+ summary.types = dedupeSorted2(summary.types);
3345
+ summary.enums = dedupeSorted2(summary.enums);
3346
+ summary.variables = dedupeSorted2(summary.variables);
3347
+ summary.reactComponents = dedupeSorted2(summary.reactComponents);
3348
+ summary.hooks = dedupeSorted2(summary.hooks);
3349
+ summary.routeHandlers = dedupeSorted2(summary.routeHandlers);
1715
3350
  return summary;
1716
3351
  }
1717
3352
 
3353
+ // src/ast/profiles/sfc.ts
3354
+ var SCRIPT_BLOCK = /<script[^>]*>([\s\S]*?)<\/script>/gi;
3355
+ var ASTRO_FRONTMATTER = /^\uFEFF?---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*(?=\r?\n|$)/;
3356
+ function componentName(path12) {
3357
+ const base = path12.split("/").pop() ?? path12;
3358
+ return base.replace(/\.[^.]+$/, "");
3359
+ }
3360
+ var sfcProfile = {
3361
+ id: "sfc",
3362
+ extensions: [".vue", ".svelte", ".astro"],
3363
+ summarize(path12, content) {
3364
+ let script = "";
3365
+ if (path12.toLowerCase().endsWith(".astro")) {
3366
+ const frontmatter = ASTRO_FRONTMATTER.exec(content);
3367
+ if (frontmatter) script += `${frontmatter[1]}
3368
+ `;
3369
+ }
3370
+ for (const match of content.matchAll(SCRIPT_BLOCK)) {
3371
+ script += `${match[1]}
3372
+ `;
3373
+ }
3374
+ const name = componentName(path12);
3375
+ if (!script.trim()) {
3376
+ const summary2 = emptySummary();
3377
+ if (name) {
3378
+ summary2.exports.push(name);
3379
+ summary2.reactComponents.push(name);
3380
+ }
3381
+ return { summary: finalizeSummary(summary2), quality: "heuristic" };
3382
+ }
3383
+ const summary = summarizeTypeScriptSource(`${name}.ts`, script);
3384
+ summary.reactComponents.push(name);
3385
+ summary.exports.push(name);
3386
+ return { summary, quality: "ast" };
3387
+ }
3388
+ };
3389
+
3390
+ // src/ast/profiles/index.ts
3391
+ var LANGUAGE_PROFILES = [
3392
+ pythonProfile,
3393
+ goProfile,
3394
+ rustProfile,
3395
+ jvmProfile,
3396
+ dotnetProfile,
3397
+ phpProfile,
3398
+ rubyProfile,
3399
+ cfamilyProfile,
3400
+ sfcProfile
3401
+ ];
3402
+ var BY_EXTENSION = /* @__PURE__ */ new Map();
3403
+ for (const profile of LANGUAGE_PROFILES) {
3404
+ for (const extension of profile.extensions) {
3405
+ BY_EXTENSION.set(extension.toLowerCase(), profile);
3406
+ }
3407
+ }
3408
+ function profileForPath(path12) {
3409
+ const lower = path12.toLowerCase();
3410
+ const dot = lower.lastIndexOf(".");
3411
+ if (dot === -1) return null;
3412
+ return BY_EXTENSION.get(lower.slice(dot)) ?? null;
3413
+ }
3414
+ function summarizeWithProfile(path12, content) {
3415
+ const profile = profileForPath(path12);
3416
+ return profile ? profile.summarize(path12, content) : null;
3417
+ }
3418
+
3419
+ // src/ast/structured.ts
3420
+ function stripJsonComments2(content) {
3421
+ const text2 = content.replace(/^\uFEFF/, "");
3422
+ let out = "";
3423
+ let inString = false;
3424
+ let inLineComment = false;
3425
+ let inBlockComment = false;
3426
+ for (let i = 0; i < text2.length; i++) {
3427
+ const char = text2[i];
3428
+ const next = text2[i + 1];
3429
+ if (inLineComment) {
3430
+ if (char === "\n") {
3431
+ inLineComment = false;
3432
+ out += char;
3433
+ }
3434
+ continue;
3435
+ }
3436
+ if (inBlockComment) {
3437
+ if (char === "*" && next === "/") {
3438
+ inBlockComment = false;
3439
+ i += 1;
3440
+ }
3441
+ continue;
3442
+ }
3443
+ if (inString) {
3444
+ out += char;
3445
+ if (char === "\\") {
3446
+ if (next !== void 0) {
3447
+ out += next;
3448
+ i += 1;
3449
+ }
3450
+ } else if (char === '"') {
3451
+ inString = false;
3452
+ }
3453
+ continue;
3454
+ }
3455
+ if (char === '"') {
3456
+ inString = true;
3457
+ out += char;
3458
+ continue;
3459
+ }
3460
+ if (char === "/" && next === "/") {
3461
+ inLineComment = true;
3462
+ i += 1;
3463
+ continue;
3464
+ }
3465
+ if (char === "/" && next === "*") {
3466
+ inBlockComment = true;
3467
+ i += 1;
3468
+ continue;
3469
+ }
3470
+ out += char;
3471
+ }
3472
+ return out;
3473
+ }
3474
+ function summarizeJson(content) {
3475
+ try {
3476
+ const parsed = JSON.parse(stripJsonComments2(content));
3477
+ if (Array.isArray(parsed)) {
3478
+ const sample = parsed[0];
3479
+ const sampleKeys = sample && typeof sample === "object" && !Array.isArray(sample) ? Object.keys(sample).slice(0, 10).sort() : [];
3480
+ return {
3481
+ shape: "array",
3482
+ itemCount: parsed.length,
3483
+ keys: [],
3484
+ sampleKeys
3485
+ };
3486
+ }
3487
+ if (parsed && typeof parsed === "object") {
3488
+ const keys = Object.keys(parsed).sort();
3489
+ return {
3490
+ shape: "object",
3491
+ itemCount: keys.length,
3492
+ keys: keys.slice(0, 30),
3493
+ sampleKeys: []
3494
+ };
3495
+ }
3496
+ return {
3497
+ shape: typeof parsed,
3498
+ itemCount: null,
3499
+ keys: [],
3500
+ sampleKeys: []
3501
+ };
3502
+ } catch {
3503
+ return {
3504
+ shape: "invalid-json",
3505
+ itemCount: null,
3506
+ keys: [],
3507
+ sampleKeys: []
3508
+ };
3509
+ }
3510
+ }
3511
+ function summarizeMarkdown(content) {
3512
+ const headings = [];
3513
+ const frontmatterKeys = [];
3514
+ const lines = content.replace(/^\uFEFF/, "").split("\n");
3515
+ let inFrontmatter = false;
3516
+ for (let index = 0; index < lines.length; index++) {
3517
+ const line = lines[index];
3518
+ if (index === 0 && line.trim() === "---") {
3519
+ inFrontmatter = true;
3520
+ continue;
3521
+ }
3522
+ if (inFrontmatter) {
3523
+ if (line.trim() === "---") {
3524
+ inFrontmatter = false;
3525
+ continue;
3526
+ }
3527
+ const match = /^([a-zA-Z0-9_-]+):/.exec(line);
3528
+ if (match) frontmatterKeys.push(match[1]);
3529
+ continue;
3530
+ }
3531
+ const headingMatch = /^(#{1,6})\s+(.+)$/.exec(line.trim());
3532
+ if (headingMatch) {
3533
+ headings.push(`${headingMatch[1]} ${headingMatch[2].trim()}`);
3534
+ }
3535
+ }
3536
+ return {
3537
+ headings: headings.slice(0, 40),
3538
+ frontmatterKeys: Array.from(new Set(frontmatterKeys)).sort()
3539
+ };
3540
+ }
3541
+
1718
3542
  // src/generator/audit.ts
1719
3543
  function generateAuditReport(manifest, secretFindings) {
1720
3544
  const lines = [
@@ -1780,11 +3604,11 @@ function generateAuditReport(manifest, secretFindings) {
1780
3604
  "| :--- | :--- | :--- |"
1781
3605
  );
1782
3606
  const totalFiles = manifest.counts.totalFiles || 1;
1783
- for (const [layer, count] of Object.entries(manifest.layers).sort(
3607
+ for (const [layer2, count] of Object.entries(manifest.layers).sort(
1784
3608
  (a, b) => b[1] - a[1] || (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0)
1785
3609
  )) {
1786
3610
  const pct = (count / totalFiles * 100).toFixed(1);
1787
- lines.push(`| \`${layer}\` | ${count} | ${pct}% |`);
3611
+ lines.push(`| \`${layer2}\` | ${count} | ${pct}% |`);
1788
3612
  }
1789
3613
  lines.push("");
1790
3614
  lines.push(
@@ -1850,21 +3674,21 @@ function partitionFiles(files, maxPartBytes, contentFingerprint) {
1850
3674
  section.byteSize = Buffer.byteLength(section.text, "utf8");
1851
3675
  continue;
1852
3676
  }
1853
- const marker = "\n... [part truncated: cannot fit budget] ...\n";
1854
- const target = Math.max(0, budget - Buffer.byteLength(marker, "utf8"));
3677
+ const marker2 = "\n... [part truncated: cannot fit budget] ...\n";
3678
+ const target = Math.max(0, budget - Buffer.byteLength(marker2, "utf8"));
1855
3679
  const clipped = truncateToBytes(rendered[oversized].markdown, target);
1856
- rendered[oversized] = rehashPart(rendered[oversized], clipped.content + marker);
3680
+ rendered[oversized] = rehashPart(rendered[oversized], clipped.content + marker2);
1857
3681
  return rendered;
1858
3682
  }
1859
3683
  return enforceBudget(renderGroups(groups, contentFingerprint), budget);
1860
3684
  }
1861
3685
  function enforceBudget(parts, budget) {
1862
- const marker = "\n... [part truncated: cannot fit budget] ...\n";
1863
- const target = Math.max(0, budget - Buffer.byteLength(marker, "utf8"));
3686
+ const marker2 = "\n... [part truncated: cannot fit budget] ...\n";
3687
+ const target = Math.max(0, budget - Buffer.byteLength(marker2, "utf8"));
1864
3688
  return parts.map((part) => {
1865
3689
  if (part.metadata.byteSize <= budget) return part;
1866
3690
  const clipped = truncateToBytes(part.markdown, target);
1867
- return rehashPart(part, clipped.content + marker);
3691
+ return rehashPart(part, clipped.content + marker2);
1868
3692
  });
1869
3693
  }
1870
3694
  function renumberParts(parts, contentFingerprint) {
@@ -1872,10 +3696,10 @@ function renumberParts(parts, contentFingerprint) {
1872
3696
  if (total === 0) return [];
1873
3697
  const zeroPad = (index) => String(index).padStart(Math.max(3, String(total).length), "0");
1874
3698
  const nameFor = (position) => `part-${zeroPad(position)}.md`;
1875
- const marker = "## Contents\n";
3699
+ const marker2 = "## Contents\n";
1876
3700
  return parts.map((part, index) => {
1877
- const markerIndex = part.markdown.indexOf(marker);
1878
- const body = markerIndex === -1 ? "" : part.markdown.slice(markerIndex + marker.length);
3701
+ const markerIndex = part.markdown.indexOf(marker2);
3702
+ const body = markerIndex === -1 ? "" : part.markdown.slice(markerIndex + marker2.length);
1879
3703
  const included = part.metadata.includedFiles;
1880
3704
  if (markerIndex === -1) {
1881
3705
  throw new Error(`${part.filename} has no "## Contents" section to re-number.`);
@@ -1933,9 +3757,9 @@ function topEntries(counts, limit) {
1933
3757
  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(", ");
1934
3758
  }
1935
3759
  function prepareSection(file, sectionBudget) {
1936
- let text = renderFileSection(file);
1937
- let byteSize = Buffer.byteLength(text, "utf8");
1938
- if (byteSize <= sectionBudget) return { file, text, byteSize };
3760
+ let text2 = renderFileSection(file);
3761
+ let byteSize = Buffer.byteLength(text2, "utf8");
3762
+ if (byteSize <= sectionBudget) return { file, text: text2, byteSize };
1939
3763
  const shrinkBody = () => {
1940
3764
  const content = file.content ?? "";
1941
3765
  const overhead = byteSize - Buffer.byteLength(content, "utf8");
@@ -1944,24 +3768,24 @@ function prepareSection(file, sectionBudget) {
1944
3768
  const truncated = truncateToBytes(content, allowed);
1945
3769
  if (!truncated.isTruncated) return;
1946
3770
  applyTruncatedContent(file, truncated.content);
1947
- text = renderFileSection(file);
1948
- byteSize = Buffer.byteLength(text, "utf8");
3771
+ text2 = renderFileSection(file);
3772
+ byteSize = Buffer.byteLength(text2, "utf8");
1949
3773
  };
1950
3774
  shrinkBody();
1951
3775
  if (byteSize > sectionBudget && file.summary) {
1952
3776
  file.summary = void 0;
1953
3777
  file.summaryOmitted = true;
1954
- text = renderFileSection(file);
1955
- byteSize = Buffer.byteLength(text, "utf8");
3778
+ text2 = renderFileSection(file);
3779
+ byteSize = Buffer.byteLength(text2, "utf8");
1956
3780
  shrinkBody();
1957
3781
  }
1958
3782
  if (byteSize > sectionBudget) {
1959
3783
  const target = sectionBudget - Buffer.byteLength(SECTION_TRUNCATION_NOTICE, "utf8");
1960
- const truncated = truncateToBytes(text, Math.max(0, target));
1961
- text = truncated.content + SECTION_TRUNCATION_NOTICE;
1962
- byteSize = Buffer.byteLength(text, "utf8");
3784
+ const truncated = truncateToBytes(text2, Math.max(0, target));
3785
+ text2 = truncated.content + SECTION_TRUNCATION_NOTICE;
3786
+ byteSize = Buffer.byteLength(text2, "utf8");
1963
3787
  }
1964
- return { file, text, byteSize };
3788
+ return { file, text: text2, byteSize };
1965
3789
  }
1966
3790
  function applyTruncatedContent(file, content) {
1967
3791
  file.content = content;
@@ -2083,6 +3907,7 @@ function renderFileSection(file) {
2083
3907
  `- Source Lines: ${file.originalLines}`,
2084
3908
  `- Included Lines: ${file.includedLines}`,
2085
3909
  `- SHA-256: ${file.hashSha256}`,
3910
+ ...file.summaryQuality ? [`- Summary Quality: ${file.summaryQuality}`] : [],
2086
3911
  ...renderSummary(file.summary),
2087
3912
  ...file.summaryOmitted ? ["- Summary: (omitted: exceeds the part budget)"] : [],
2088
3913
  "",
@@ -2330,7 +4155,7 @@ var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
2330
4155
  ".so"
2331
4156
  ]);
2332
4157
  function isBinaryExtension(filePath) {
2333
- const ext = path9__namespace.extname(filePath).toLowerCase();
4158
+ const ext = path10__namespace.extname(filePath).toLowerCase();
2334
4159
  return BINARY_EXTENSIONS.has(ext);
2335
4160
  }
2336
4161
  function isBinaryBuffer(buffer) {
@@ -2344,9 +4169,9 @@ function isBinaryFile(absolutePath) {
2344
4169
  if (isBinaryExtension(absolutePath)) return true;
2345
4170
  let fd;
2346
4171
  try {
2347
- fd = fs8__namespace.openSync(absolutePath, "r");
4172
+ fd = fs9__namespace.openSync(absolutePath, "r");
2348
4173
  const buffer = Buffer.alloc(8192);
2349
- const bytesRead = fs8__namespace.readSync(fd, buffer, 0, 8192, 0);
4174
+ const bytesRead = fs9__namespace.readSync(fd, buffer, 0, 8192, 0);
2350
4175
  if (bytesRead === 0) return false;
2351
4176
  return isBinaryBuffer(buffer.subarray(0, bytesRead));
2352
4177
  } catch {
@@ -2354,7 +4179,7 @@ function isBinaryFile(absolutePath) {
2354
4179
  } finally {
2355
4180
  if (fd !== void 0) {
2356
4181
  try {
2357
- fs8__namespace.closeSync(fd);
4182
+ fs9__namespace.closeSync(fd);
2358
4183
  } catch {
2359
4184
  }
2360
4185
  }
@@ -2376,8 +4201,8 @@ function sha256Hex(buffer) {
2376
4201
  function validateSnapshot(configInput) {
2377
4202
  assertUsableConfigInput(configInput);
2378
4203
  const config = isResolvedConfig(configInput) ? normalizeResolvedConfig(configInput) : resolveConfig(configInput);
2379
- const manifestPath = path9__namespace.join(config.cwd, config.outDir, "manifest.json");
2380
- if (!fs8__namespace.existsSync(manifestPath)) {
4204
+ const manifestPath = path10__namespace.resolve(config.cwd, config.outDir, "manifest.json");
4205
+ if (!fs9__namespace.existsSync(manifestPath)) {
2381
4206
  return {
2382
4207
  valid: false,
2383
4208
  manifestPresent: false,
@@ -2388,7 +4213,7 @@ function validateSnapshot(configInput) {
2388
4213
  }
2389
4214
  let manifest;
2390
4215
  try {
2391
- manifest = JSON.parse(fs8__namespace.readFileSync(manifestPath, "utf8"));
4216
+ manifest = JSON.parse(fs9__namespace.readFileSync(manifestPath, "utf8"));
2392
4217
  } catch (err) {
2393
4218
  return {
2394
4219
  valid: false,
@@ -2411,7 +4236,7 @@ function validateSnapshot(configInput) {
2411
4236
  }
2412
4237
  const drift = emptyDrift();
2413
4238
  const warnings = [];
2414
- const snapshotDir = path9__namespace.join(config.cwd, config.outDir);
4239
+ const snapshotDir = path10__namespace.resolve(config.cwd, config.outDir);
2415
4240
  const manifestFileMap = /* @__PURE__ */ new Map();
2416
4241
  for (const file of manifest.files) {
2417
4242
  if (file && typeof file.relativePath === "string") {
@@ -2423,7 +4248,7 @@ function validateSnapshot(configInput) {
2423
4248
  );
2424
4249
  const collection = collectFiles(config.cwd, {
2425
4250
  includePatterns: config.include,
2426
- excludePatterns: config.excludePatterns,
4251
+ excludePatterns: [...config.excludePatterns, ...config.presetExclude],
2427
4252
  includeSensitiveFiles: config.includeSensitiveFiles
2428
4253
  });
2429
4254
  for (const diskFile of collection.files) {
@@ -2435,7 +4260,7 @@ function validateSnapshot(configInput) {
2435
4260
  }
2436
4261
  let diskHash;
2437
4262
  try {
2438
- diskHash = sha256Hex(fs8__namespace.readFileSync(path9__namespace.join(config.cwd, diskFile)));
4263
+ diskHash = sha256Hex(fs9__namespace.readFileSync(path10__namespace.join(config.cwd, diskFile)));
2439
4264
  } catch {
2440
4265
  drift.missingOnDisk.push(diskFile);
2441
4266
  continue;
@@ -2445,7 +4270,7 @@ function validateSnapshot(configInput) {
2445
4270
  }
2446
4271
  }
2447
4272
  for (const snapshotFile of manifestFileMap.keys()) {
2448
- if (!fs8__namespace.existsSync(path9__namespace.join(config.cwd, snapshotFile))) {
4273
+ if (!fs9__namespace.existsSync(path10__namespace.join(config.cwd, snapshotFile))) {
2449
4274
  drift.missingOnDisk.push(snapshotFile);
2450
4275
  }
2451
4276
  }
@@ -2459,10 +4284,10 @@ function validateSnapshot(configInput) {
2459
4284
  if (!part || typeof part.filename !== "string") continue;
2460
4285
  partNames.add(part.filename);
2461
4286
  if (!config.artifacts.parts) continue;
2462
- const partPath = path9__namespace.join(snapshotDir, "parts", part.filename);
4287
+ const partPath = path10__namespace.join(snapshotDir, "parts", part.filename);
2463
4288
  let buffer;
2464
4289
  try {
2465
- buffer = fs8__namespace.readFileSync(partPath);
4290
+ buffer = fs9__namespace.readFileSync(partPath);
2466
4291
  } catch {
2467
4292
  drift.partIssues.push({ part: part.filename, issue: "missing from snapshot/parts" });
2468
4293
  continue;
@@ -2549,6 +4374,11 @@ function validateSnapshot(configInput) {
2549
4374
  }
2550
4375
  }
2551
4376
  }
4377
+ if (recorded?.preset !== void 0 && recorded.preset !== config.preset) {
4378
+ drift.configMismatches.push(
4379
+ `preset: manifest ${recorded.preset} vs current ${config.preset}`
4380
+ );
4381
+ }
2552
4382
  if (recorded?.layersMode !== void 0 && recorded.layersMode !== config.layersMode) {
2553
4383
  drift.configMismatches.push(
2554
4384
  `layersMode: manifest ${recorded.layersMode} vs current ${config.layersMode}`
@@ -2566,10 +4396,10 @@ function validateSnapshot(configInput) {
2566
4396
  ...Object.keys(recordedByLayer),
2567
4397
  ...Object.keys(config.budgets.byLayer)
2568
4398
  ]);
2569
- for (const layer of Array.from(layerNames).sort()) {
2570
- if (recordedByLayer[layer] !== config.budgets.byLayer[layer]) {
4399
+ for (const layer2 of Array.from(layerNames).sort()) {
4400
+ if (recordedByLayer[layer2] !== config.budgets.byLayer[layer2]) {
2571
4401
  drift.configMismatches.push(
2572
- `budgets.byLayer.${layer}: manifest ${recordedByLayer[layer]} vs current ${config.budgets.byLayer[layer]}`
4402
+ `budgets.byLayer.${layer2}: manifest ${recordedByLayer[layer2]} vs current ${config.budgets.byLayer[layer2]}`
2573
4403
  );
2574
4404
  }
2575
4405
  }
@@ -2652,9 +4482,9 @@ function validateSnapshot(configInput) {
2652
4482
  ["full-codebase.md", manifest.artifacts?.fullMarkdown]
2653
4483
  ]) {
2654
4484
  if (!artifact) continue;
2655
- const artifactPath = path9__namespace.join(snapshotDir, key);
4485
+ const artifactPath = path10__namespace.join(snapshotDir, key);
2656
4486
  try {
2657
- const buffer = fs8__namespace.readFileSync(artifactPath);
4487
+ const buffer = fs9__namespace.readFileSync(artifactPath);
2658
4488
  if (sha256Hex(buffer) !== artifact.sha256 || buffer.length !== artifact.byteSize) {
2659
4489
  drift.partIssues.push({ part: key, issue: "artifact does not match its recorded digest" });
2660
4490
  }
@@ -2703,12 +4533,12 @@ function validateSnapshot(configInput) {
2703
4533
 
2704
4534
  // src/index.ts
2705
4535
  var GENERATOR_NAME = "@ankurhalder/codebase-snapshot";
2706
- var GENERATOR_VERSION = "2.0.0";
4536
+ var GENERATOR_VERSION = "2.1.0";
2707
4537
  var SCHEMA_VERSION = "3.0.0";
2708
4538
  function sha256Hex2(input) {
2709
4539
  return crypto__namespace.createHash("sha256").update(input).digest("hex");
2710
4540
  }
2711
- var SUMMARY_EXTENSIONS = [
4541
+ var TS_EXTENSIONS = [
2712
4542
  ".ts",
2713
4543
  ".mts",
2714
4544
  ".cts",
@@ -2716,24 +4546,21 @@ var SUMMARY_EXTENSIONS = [
2716
4546
  ".js",
2717
4547
  ".mjs",
2718
4548
  ".cjs",
2719
- ".jsx",
2720
- ".json",
2721
- ".jsonc",
2722
- ".md",
2723
- ".mdx"
4549
+ ".jsx"
2724
4550
  ];
2725
4551
  function summarizeFile(relativePath, redactedContent) {
2726
4552
  const lower = relativePath.toLowerCase();
2727
- if (!SUMMARY_EXTENSIONS.some((extension) => lower.endsWith(extension))) {
2728
- return void 0;
2729
- }
2730
4553
  if (lower.endsWith(".json") || lower.endsWith(".jsonc")) {
2731
- return summarizeJson(redactedContent);
4554
+ return { summary: summarizeJson(redactedContent), quality: "structural" };
2732
4555
  }
2733
4556
  if (lower.endsWith(".md") || lower.endsWith(".mdx")) {
2734
- return summarizeMarkdown(redactedContent);
4557
+ return { summary: summarizeMarkdown(redactedContent), quality: "structural" };
2735
4558
  }
2736
- return summarizeTypeScriptSource(relativePath, redactedContent);
4559
+ if (TS_EXTENSIONS.some((extension) => lower.endsWith(extension))) {
4560
+ return { summary: summarizeTypeScriptSource(relativePath, redactedContent), quality: "ast" };
4561
+ }
4562
+ const profiled = summarizeWithProfile(relativePath, redactedContent);
4563
+ return profiled ? { summary: profiled.summary, quality: profiled.quality } : void 0;
2737
4564
  }
2738
4565
  function isSourceSummary(summary) {
2739
4566
  return Boolean(summary && typeof summary === "object" && "imports" in summary);
@@ -2759,8 +4586,8 @@ var IMPORT_CANDIDATE_SUFFIXES = [
2759
4586
  ];
2760
4587
  function resolveSpecifier(fromFile, specifier, knownFiles) {
2761
4588
  if (!RELATIVE_SPECIFIER.test(specifier)) return null;
2762
- const baseDir = path9__namespace.posix.dirname(fromFile);
2763
- const joined = path9__namespace.posix.normalize(path9__namespace.posix.join(baseDir, specifier));
4589
+ const baseDir = path10__namespace.posix.dirname(fromFile);
4590
+ const joined = path10__namespace.posix.normalize(path10__namespace.posix.join(baseDir, specifier));
2764
4591
  const candidate = joined.replace(/\.(?:js|mjs|cjs|jsx)$/, "");
2765
4592
  for (const suffix of IMPORT_CANDIDATE_SUFFIXES) {
2766
4593
  const withSuffix = `${joined}${suffix}`;
@@ -2852,11 +4679,11 @@ function verifyInMemory(files, parts, maxPartBytes) {
2852
4679
  return errors;
2853
4680
  }
2854
4681
  function resolveSafeOutputDirs(config) {
2855
- const cwd = path9__namespace.resolve(config.cwd);
2856
- const outDir = path9__namespace.resolve(cwd, config.outDir);
2857
- const tmpDir = path9__namespace.resolve(cwd, config.tmpDir);
2858
- const root = path9__namespace.parse(cwd).root;
2859
- const contains = (parent, child) => child === parent || child.startsWith(`${parent}${path9__namespace.sep}`);
4682
+ const cwd = path10__namespace.resolve(config.cwd);
4683
+ const outDir = path10__namespace.resolve(cwd, config.outDir);
4684
+ const tmpDir = path10__namespace.resolve(cwd, config.tmpDir);
4685
+ const root = path10__namespace.parse(cwd).root;
4686
+ const contains = (parent, child) => child === parent || child.startsWith(`${parent}${path10__namespace.sep}`);
2860
4687
  const reject = (reason) => {
2861
4688
  throw new Error(
2862
4689
  `Refusing to generate: ${reason}. outDir=${outDir} tmpDir=${tmpDir} cwd=${cwd}`
@@ -2875,26 +4702,66 @@ function resolveSafeOutputDirs(config) {
2875
4702
  if (contains(tmpDir, outDir)) reject("outDir is inside tmpDir");
2876
4703
  return { outDir, tmpDir };
2877
4704
  }
4705
+ var PLACEHOLDER_ENTRIES = /* @__PURE__ */ new Set([".gitkeep", ".gitignore", ".DS_Store"]);
4706
+ var OWNERSHIP_MARKER = ".codebase-snapshot";
4707
+ function writeOwnershipMarker(dir) {
4708
+ const marker2 = {
4709
+ generator: GENERATOR_NAME,
4710
+ version: GENERATOR_VERSION,
4711
+ schemaVersion: SCHEMA_VERSION
4712
+ };
4713
+ fs9__namespace.writeFileSync(path10__namespace.join(dir, OWNERSHIP_MARKER), `${JSON.stringify(marker2, null, 2)}
4714
+ `, "utf8");
4715
+ }
4716
+ function isOwnOutput(dir) {
4717
+ if (fs9__namespace.existsSync(path10__namespace.join(dir, OWNERSHIP_MARKER))) return true;
4718
+ const manifestPath = path10__namespace.join(dir, "manifest.json");
4719
+ if (!fs9__namespace.existsSync(manifestPath)) return false;
4720
+ try {
4721
+ const parsed = JSON.parse(fs9__namespace.readFileSync(manifestPath, "utf8"));
4722
+ const generator = parsed.generator;
4723
+ return typeof parsed.schemaVersion === "string" && Array.isArray(parsed.files) && typeof generator === "object" && generator !== null && generator.name === GENERATOR_NAME;
4724
+ } catch {
4725
+ return false;
4726
+ }
4727
+ }
4728
+ function assertReplaceable(target, label) {
4729
+ if (!fs9__namespace.existsSync(target)) return;
4730
+ if (!fs9__namespace.statSync(target).isDirectory()) {
4731
+ throw new Error(
4732
+ `Refusing to write into ${label} ${target}: it exists but is not a directory.`
4733
+ );
4734
+ }
4735
+ const entries = fs9__namespace.readdirSync(target);
4736
+ const meaningful = entries.filter((name) => !PLACEHOLDER_ENTRIES.has(name));
4737
+ if (meaningful.length === 0) return;
4738
+ if (isOwnOutput(target)) return;
4739
+ const preview = meaningful.slice(0, 5).join(", ");
4740
+ const more = meaningful.length > 5 ? `, and ${meaningful.length - 5} more` : "";
4741
+ throw new Error(
4742
+ `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.`
4743
+ );
4744
+ }
2878
4745
  function swapIntoPlace(tmpDir, outDir) {
2879
4746
  const backupDir = `${outDir}.backup-${process.pid}-${Date.now()}`;
2880
- const hadPrevious = fs8__namespace.existsSync(outDir);
2881
- if (hadPrevious) fs8__namespace.renameSync(outDir, backupDir);
4747
+ const hadPrevious = fs9__namespace.existsSync(outDir);
4748
+ if (hadPrevious) fs9__namespace.renameSync(outDir, backupDir);
2882
4749
  try {
2883
4750
  try {
2884
- fs8__namespace.renameSync(tmpDir, outDir);
4751
+ fs9__namespace.renameSync(tmpDir, outDir);
2885
4752
  } catch (err) {
2886
4753
  if (err.code === "EXDEV") {
2887
- fs8__namespace.cpSync(tmpDir, outDir, { recursive: true });
2888
- fs8__namespace.rmSync(tmpDir, { recursive: true, force: true });
4754
+ fs9__namespace.cpSync(tmpDir, outDir, { recursive: true });
4755
+ fs9__namespace.rmSync(tmpDir, { recursive: true, force: true });
2889
4756
  } else {
2890
4757
  throw err;
2891
4758
  }
2892
4759
  }
2893
- if (hadPrevious) fs8__namespace.rmSync(backupDir, { recursive: true, force: true });
4760
+ if (hadPrevious) fs9__namespace.rmSync(backupDir, { recursive: true, force: true });
2894
4761
  } catch (err) {
2895
- if (hadPrevious && fs8__namespace.existsSync(backupDir)) {
2896
- if (fs8__namespace.existsSync(outDir)) fs8__namespace.rmSync(outDir, { recursive: true, force: true });
2897
- fs8__namespace.renameSync(backupDir, outDir);
4762
+ if (hadPrevious && fs9__namespace.existsSync(backupDir)) {
4763
+ if (fs9__namespace.existsSync(outDir)) fs9__namespace.rmSync(outDir, { recursive: true, force: true });
4764
+ fs9__namespace.renameSync(backupDir, outDir);
2898
4765
  }
2899
4766
  throw err;
2900
4767
  }
@@ -2958,7 +4825,7 @@ function generateSnapshot(configInput, options) {
2958
4825
  const gitInfo = getGitInfo(config.cwd, config.branch);
2959
4826
  const collection = collectFiles(config.cwd, {
2960
4827
  includePatterns: config.include,
2961
- excludePatterns: config.excludePatterns,
4828
+ excludePatterns: [...config.excludePatterns, ...config.presetExclude],
2962
4829
  includeSensitiveFiles: config.includeSensitiveFiles
2963
4830
  });
2964
4831
  const files = [];
@@ -2971,10 +4838,10 @@ function generateSnapshot(configInput, options) {
2971
4838
  let totalSizeBytes = 0;
2972
4839
  let largeFileCount = 0;
2973
4840
  for (const relativePath of collection.files) {
2974
- const absolutePath = path9__namespace.join(config.cwd, relativePath);
4841
+ const absolutePath = path10__namespace.join(config.cwd, relativePath);
2975
4842
  let stats;
2976
4843
  try {
2977
- stats = fs8__namespace.statSync(absolutePath);
4844
+ stats = fs9__namespace.statSync(absolutePath);
2978
4845
  if (!stats.isFile()) {
2979
4846
  skipped.push({ path: relativePath, reason: "not-file" });
2980
4847
  continue;
@@ -2989,7 +4856,7 @@ function generateSnapshot(configInput, options) {
2989
4856
  }
2990
4857
  let buffer;
2991
4858
  try {
2992
- buffer = fs8__namespace.readFileSync(absolutePath);
4859
+ buffer = fs9__namespace.readFileSync(absolutePath);
2993
4860
  } catch {
2994
4861
  skipped.push({ path: relativePath, reason: "read-error" });
2995
4862
  continue;
@@ -2999,10 +4866,10 @@ function generateSnapshot(configInput, options) {
2999
4866
  const lineCount = countLines(rawContent);
3000
4867
  totalLines += lineCount;
3001
4868
  totalSizeBytes += stats.size;
3002
- const layer = classifyLayer(relativePath, config.layers);
4869
+ const layer2 = classifyLayer(relativePath, config.layers);
3003
4870
  const kind = classifyKind(relativePath, config.kinds);
3004
4871
  const language = detectLanguage(relativePath);
3005
- bump(layerCounts, layer);
4872
+ bump(layerCounts, layer2);
3006
4873
  bump(kindCounts, kind);
3007
4874
  const { redacted, findings } = scanAndRedactSecrets(
3008
4875
  rawContent,
@@ -3026,8 +4893,11 @@ function generateSnapshot(configInput, options) {
3026
4893
  includedLines = preview.includedLines;
3027
4894
  }
3028
4895
  let summary;
4896
+ let summaryQuality;
3029
4897
  try {
3030
- summary = summarizeFile(relativePath, redacted);
4898
+ const outcome = summarizeFile(relativePath, redacted);
4899
+ summary = outcome?.summary;
4900
+ summaryQuality = outcome?.quality;
3031
4901
  if (isSourceSummary(summary) && summary.parseErrors) {
3032
4902
  warnings.push(
3033
4903
  `Parse errors in ${relativePath} (${summary.parseErrors}); its summary is incomplete.`
@@ -3044,7 +4914,7 @@ function generateSnapshot(configInput, options) {
3044
4914
  lines: lineCount,
3045
4915
  hashSha256,
3046
4916
  part: "",
3047
- layer,
4917
+ layer: layer2,
3048
4918
  kind,
3049
4919
  language,
3050
4920
  inclusionMode: isPreviewTruncated ? "preview" : "full",
@@ -3054,6 +4924,7 @@ function generateSnapshot(configInput, options) {
3054
4924
  includedLines,
3055
4925
  includedBytes: Buffer.byteLength(finalContent, "utf8"),
3056
4926
  summary,
4927
+ summaryQuality,
3057
4928
  content: finalContent
3058
4929
  });
3059
4930
  }
@@ -3094,10 +4965,11 @@ function generateSnapshot(configInput, options) {
3094
4965
  includeSensitiveFiles: config.includeSensitiveFiles,
3095
4966
  artifacts: { ...config.artifacts },
3096
4967
  include: [...config.include],
4968
+ preset: config.preset,
3097
4969
  layersMode: config.layersMode,
3098
4970
  budgets: { default: config.budgets.default, byLayer: { ...config.budgets.byLayer } },
3099
4971
  excludePatterns: [...config.excludePatterns],
3100
- layers: config.layers.map((layer) => layer.name),
4972
+ layers: config.layers.map((layer2) => layer2.name),
3101
4973
  kinds: config.kinds.map((kind) => kind.kind)
3102
4974
  },
3103
4975
  counts: {
@@ -3184,45 +5056,48 @@ function generateSnapshot(configInput, options) {
3184
5056
  return manifest;
3185
5057
  }
3186
5058
  const { outDir, tmpDir } = resolveSafeOutputDirs(config);
5059
+ assertReplaceable(tmpDir, "tmpDir");
5060
+ assertReplaceable(outDir, "outDir");
3187
5061
  const { artifacts } = config;
3188
- fs8__namespace.rmSync(tmpDir, { recursive: true, force: true });
3189
- fs8__namespace.mkdirSync(tmpDir, { recursive: true });
3190
- const partsDir = path9__namespace.join(tmpDir, "parts");
3191
- const reportsDir = path9__namespace.join(tmpDir, "reports");
3192
- if (artifacts.parts) fs8__namespace.mkdirSync(partsDir, { recursive: true });
5062
+ fs9__namespace.rmSync(tmpDir, { recursive: true, force: true });
5063
+ fs9__namespace.mkdirSync(tmpDir, { recursive: true });
5064
+ writeOwnershipMarker(tmpDir);
5065
+ const partsDir = path10__namespace.join(tmpDir, "parts");
5066
+ const reportsDir = path10__namespace.join(tmpDir, "reports");
5067
+ if (artifacts.parts) fs9__namespace.mkdirSync(partsDir, { recursive: true });
3193
5068
  if (artifacts.navigation || artifacts.projectTree || artifacts.audit) {
3194
- fs8__namespace.mkdirSync(reportsDir, { recursive: true });
5069
+ fs9__namespace.mkdirSync(reportsDir, { recursive: true });
3195
5070
  }
3196
5071
  if (artifacts.parts) {
3197
5072
  for (const part of parts) {
3198
- fs8__namespace.writeFileSync(path9__namespace.join(partsDir, part.filename), part.markdown, "utf8");
5073
+ fs9__namespace.writeFileSync(path10__namespace.join(partsDir, part.filename), part.markdown, "utf8");
3199
5074
  }
3200
5075
  }
3201
5076
  if (artifacts.projectTree) {
3202
5077
  const projectTree = generateProjectTree(files.map((file) => file.relativePath));
3203
- fs8__namespace.writeFileSync(path9__namespace.join(reportsDir, "project-tree.md"), projectTree, "utf8");
5078
+ fs9__namespace.writeFileSync(path10__namespace.join(reportsDir, "project-tree.md"), projectTree, "utf8");
3204
5079
  }
3205
5080
  if (artifacts.navigation) {
3206
- fs8__namespace.writeFileSync(path9__namespace.join(reportsDir, "navigation.md"), generateNavigation(manifest), "utf8");
5081
+ fs9__namespace.writeFileSync(path10__namespace.join(reportsDir, "navigation.md"), generateNavigation(manifest), "utf8");
3207
5082
  }
3208
5083
  if (artifacts.audit) {
3209
- fs8__namespace.writeFileSync(
3210
- path9__namespace.join(reportsDir, "audit.md"),
5084
+ fs9__namespace.writeFileSync(
5085
+ path10__namespace.join(reportsDir, "audit.md"),
3211
5086
  generateAuditReport(manifest, secretFindings),
3212
5087
  "utf8"
3213
5088
  );
3214
5089
  }
3215
5090
  if (artifacts.readme) {
3216
- fs8__namespace.writeFileSync(path9__namespace.join(tmpDir, "README.md"), generateReadme(manifest), "utf8");
5091
+ fs9__namespace.writeFileSync(path10__namespace.join(tmpDir, "README.md"), generateReadme(manifest), "utf8");
3217
5092
  }
3218
5093
  if (artifacts.manifest) {
3219
- fs8__namespace.writeFileSync(path9__namespace.join(tmpDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf8");
5094
+ fs9__namespace.writeFileSync(path10__namespace.join(tmpDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf8");
3220
5095
  }
3221
5096
  if (monolithJson !== null) {
3222
- fs8__namespace.writeFileSync(path9__namespace.join(tmpDir, "full-codebase.json"), monolithJson, "utf8");
5097
+ fs9__namespace.writeFileSync(path10__namespace.join(tmpDir, "full-codebase.json"), monolithJson, "utf8");
3223
5098
  }
3224
5099
  if (monolithMarkdown !== null) {
3225
- fs8__namespace.writeFileSync(path9__namespace.join(tmpDir, "full-codebase.md"), monolithMarkdown, "utf8");
5100
+ fs9__namespace.writeFileSync(path10__namespace.join(tmpDir, "full-codebase.md"), monolithMarkdown, "utf8");
3226
5101
  }
3227
5102
  swapIntoPlace(tmpDir, outDir);
3228
5103
  runAfterGenerate(config, manifest);
@@ -3262,6 +5137,12 @@ var CLI_OPTIONS = {
3262
5137
  mode: { type: "string" },
3263
5138
  "dry-run": { type: "boolean" },
3264
5139
  watch: { type: "boolean" },
5140
+ preset: { type: "string" },
5141
+ include: { type: "string" },
5142
+ exclude: { type: "string" },
5143
+ yes: { type: "boolean" },
5144
+ minimal: { type: "boolean" },
5145
+ force: { type: "boolean" },
3265
5146
  help: { type: "boolean", short: "h" },
3266
5147
  version: { type: "boolean", short: "v" }
3267
5148
  };
@@ -3299,6 +5180,14 @@ Options:
3299
5180
  --format <text|json> CLI output format (default: text)
3300
5181
  --dry-run Compute the snapshot without writing anything
3301
5182
  --watch Re-generate whenever the repository changes
5183
+
5184
+ init only:
5185
+ --preset <name> Force a project preset instead of detecting one
5186
+ --include <globs> Comma-separated allow-list
5187
+ --exclude <globs> Comma-separated exclude patterns
5188
+ --minimal Write only the settings that differ from defaults
5189
+ --yes Accept every default without prompting
5190
+ --force Overwrite an existing config
3302
5191
  -h, --help Display this help message
3303
5192
  -v, --version Display version
3304
5193
 
@@ -3423,13 +5312,38 @@ function parseCliArgs(argv) {
3423
5312
  } else if (rawSkip !== void 0) {
3424
5313
  skipArtifacts = parseArtifactList(String(rawSkip), "--skip-artifacts");
3425
5314
  }
5315
+ if (command !== "init") {
5316
+ const initOnly = [];
5317
+ if (values.preset !== void 0) initOnly.push("--preset");
5318
+ if (values.include !== void 0) initOnly.push("--include");
5319
+ if (values.exclude !== void 0) initOnly.push("--exclude");
5320
+ if (values.yes === true) initOnly.push("--yes");
5321
+ if (values.minimal === true) initOnly.push("--minimal");
5322
+ if (values.force === true) initOnly.push("--force");
5323
+ if (initOnly.length > 0) {
5324
+ throw new UsageError(
5325
+ `${initOnly.join(", ")} ${initOnly.length === 1 ? "is" : "are"} only valid with \`init\`. To scope \`${command}\`, set include/excludePatterns/preset in snapshot.config.ts (or run \`snap init\` to generate one).`
5326
+ );
5327
+ }
5328
+ }
5329
+ if (command === "init" && values.preset !== void 0) {
5330
+ const requested = String(values.preset);
5331
+ if (!getPreset(requested)) throw new UsageError(unknownPresetMessage(requested));
5332
+ }
3426
5333
  return {
3427
5334
  command,
3428
5335
  args,
3429
5336
  format: rawFormat,
3430
5337
  dryRun: values["dry-run"] === true,
3431
5338
  watch: values.watch === true,
3432
- cwd: values.cwd === void 0 ? process.cwd() : path9__namespace.resolve(String(values.cwd)),
5339
+ preset: values.preset === void 0 ? void 0 : String(values.preset),
5340
+ maxPartBytesRaw: values["max-part-bytes"] === void 0 ? void 0 : String(values["max-part-bytes"]),
5341
+ include: values.include === void 0 ? void 0 : String(values.include),
5342
+ exclude: values.exclude === void 0 ? void 0 : String(values.exclude),
5343
+ yes: values.yes === true,
5344
+ minimal: values.minimal === true,
5345
+ force: values.force === true,
5346
+ cwd: values.cwd === void 0 ? process.cwd() : path10__namespace.resolve(String(values.cwd)),
3433
5347
  outDir: values["out-dir"] === void 0 ? void 0 : String(values["out-dir"]),
3434
5348
  includeMonolith: values["include-monolith"] === true ? true : void 0,
3435
5349
  includeSensitiveFiles: values["include-sensitive-files"] === true ? true : void 0,
@@ -3469,32 +5383,65 @@ function emit(format, payload, textRenderer) {
3469
5383
  }
3470
5384
  textRenderer();
3471
5385
  }
3472
- function runInit(cwd) {
3473
- const targetName = supportsTypeScriptImports() ? "snapshot.config.ts" : "snapshot.config.mjs";
3474
- const targetPath = path9__namespace.join(cwd, targetName);
3475
- if (fs8__namespace.existsSync(targetPath)) {
3476
- warn(`\u274C ${targetName} already exists in ${cwd}.`);
5386
+ function splitList(value) {
5387
+ if (value === void 0) return void 0;
5388
+ return value.split(",").map((entry) => entry.trim()).filter(Boolean);
5389
+ }
5390
+ function describeManifestShapeProblem(manifest) {
5391
+ if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) {
5392
+ return "not a JSON object";
5393
+ }
5394
+ const candidate = manifest;
5395
+ const counts = candidate.counts;
5396
+ if (!counts || typeof counts !== "object") return "missing `counts`";
5397
+ for (const key of ["totalFiles", "totalLines", "totalSizeBytes", "partsCount"]) {
5398
+ if (typeof counts[key] !== "number") return `missing numeric \`counts.${key}\``;
5399
+ }
5400
+ if (!candidate.layers || typeof candidate.layers !== "object") return "missing `layers`";
5401
+ if (!Array.isArray(candidate.files)) return "missing `files` array";
5402
+ return null;
5403
+ }
5404
+ async function runInitCommand(invocation) {
5405
+ try {
5406
+ const result = await runInit({
5407
+ cwd: invocation.cwd,
5408
+ preset: invocation.preset,
5409
+ maxPartBytes: invocation.maxPartBytesRaw ?? invocation.maxPartBytes,
5410
+ include: splitList(invocation.include),
5411
+ exclude: splitList(invocation.exclude),
5412
+ skipArtifacts: invocation.skipArtifacts,
5413
+ includeSensitiveFiles: invocation.includeSensitiveFiles,
5414
+ yes: invocation.yes,
5415
+ minimal: invocation.minimal,
5416
+ force: invocation.force,
5417
+ dryRun: invocation.dryRun
5418
+ });
5419
+ if (invocation.format === "json") {
5420
+ process.stdout.write(`${JSON.stringify(result, null, 2)}
5421
+ `);
5422
+ return 0;
5423
+ }
5424
+ if (!result.written) {
5425
+ info(result.configText);
5426
+ return 0;
5427
+ }
5428
+ info(`\u2705 Created ${path10__namespace.basename(result.configPath)} (preset: ${result.preset})`);
5429
+ if (result.scripts.length > 0) info(` Added npm scripts: ${result.scripts.join(", ")}`);
5430
+ if (result.gitignoreEntries.length > 0) {
5431
+ info(` Added to .gitignore: ${result.gitignoreEntries.join(", ")}`);
5432
+ }
5433
+ for (const note of result.notes) info(` \u26A0 ${note}`);
5434
+ info("");
5435
+ info(" Next: npm run snapshot");
5436
+ return 0;
5437
+ } catch (err) {
5438
+ if (err instanceof PromptCancelled) {
5439
+ warn("\u274C Cancelled.");
5440
+ return 1;
5441
+ }
5442
+ warn(`\u274C ${err.message}`);
3477
5443
  return 1;
3478
5444
  }
3479
- const importPath = JSON.stringify(GENERATOR_NAME);
3480
- const starter = `import { defineConfig } from ${importPath};
3481
-
3482
- export default defineConfig({
3483
- branch: "main",
3484
- outDir: "snapshot",
3485
- maxPartBytes: 300 * 1024,
3486
- largeFileBytes: 200 * 1024,
3487
- excludePatterns: [],
3488
- });
3489
- `;
3490
- fs8__namespace.writeFileSync(targetPath, starter, "utf8");
3491
- info(`\u2705 Created ${targetName} in ${cwd}`);
3492
- if (targetName.endsWith(".mjs")) {
3493
- info(
3494
- " (a .ts config needs Node >= 22.6 to import natively; this Node writes a .mjs instead)"
3495
- );
3496
- }
3497
- return 0;
3498
5445
  }
3499
5446
  function renderValidateText(result) {
3500
5447
  if (result.valid) {
@@ -3536,11 +5483,15 @@ ${USAGE}
3536
5483
  throw err;
3537
5484
  }
3538
5485
  if (invocation.command === "init") {
3539
- return runInit(invocation.cwd);
5486
+ return await runInitCommand(invocation);
3540
5487
  }
3541
5488
  const loaded = await loadConfigFileDetailed(invocation.cwd);
3542
5489
  for (const warning of loaded.warnings) warn(`\u26A0 Config: ${warning}`);
3543
- if (loaded.file) info(`\u2699 Using config ${path9__namespace.relative(process.cwd(), loaded.file) || loaded.file}`);
5490
+ if (loaded.errors.length > 0) {
5491
+ warn("\u274C The config file could not be read. Fix it, or delete it to use the defaults.");
5492
+ return 1;
5493
+ }
5494
+ if (loaded.file) info(`\u2699 Using config ${path10__namespace.relative(process.cwd(), loaded.file) || loaded.file}`);
3544
5495
  const resolved = resolveConfig(
3545
5496
  loaded.config,
3546
5497
  {
@@ -3564,18 +5515,23 @@ ${USAGE}
3564
5515
  return result.valid ? 0 : 1;
3565
5516
  }
3566
5517
  if (invocation.command === "report") {
3567
- const manifestPath = path9__namespace.join(resolved.cwd, resolved.outDir, "manifest.json");
3568
- if (!fs8__namespace.existsSync(manifestPath)) {
5518
+ const manifestPath = path10__namespace.resolve(resolved.cwd, resolved.outDir, "manifest.json");
5519
+ if (!fs9__namespace.existsSync(manifestPath)) {
3569
5520
  warn(`\u274C Manifest not found at ${manifestPath}. Run generate first.`);
3570
5521
  return 1;
3571
5522
  }
3572
5523
  let manifest;
3573
5524
  try {
3574
- manifest = JSON.parse(fs8__namespace.readFileSync(manifestPath, "utf8"));
5525
+ manifest = JSON.parse(fs9__namespace.readFileSync(manifestPath, "utf8"));
3575
5526
  } catch (err) {
3576
5527
  warn(`\u274C Could not read ${manifestPath}: ${err.message}`);
3577
5528
  return 1;
3578
5529
  }
5530
+ const shapeError = describeManifestShapeProblem(manifest);
5531
+ if (shapeError) {
5532
+ warn(`\u274C ${manifestPath} is not a snapshot manifest: ${shapeError}`);
5533
+ return 1;
5534
+ }
3579
5535
  const validation = validateSnapshot(resolved);
3580
5536
  emit(invocation.format, { manifest, validation }, () => {
3581
5537
  info(`
@@ -3592,8 +5548,8 @@ ${USAGE}
3592
5548
  info(` - Integrity: ${validation.valid ? "\u2705 Clean (no drift)" : "\u274C Drift detected"}`);
3593
5549
  info(` - Warnings: ${manifest.warnings?.length ?? 0}`);
3594
5550
  info("\n Layers:");
3595
- for (const [layer, count] of Object.entries(manifest.layers)) {
3596
- info(` - ${layer.padEnd(16)}: ${count} files`);
5551
+ for (const [layer2, count] of Object.entries(manifest.layers)) {
5552
+ info(` - ${layer2.padEnd(16)}: ${count} files`);
3597
5553
  }
3598
5554
  });
3599
5555
  return validation.valid ? 0 : 1;
@@ -3662,10 +5618,10 @@ ${USAGE}
3662
5618
  }
3663
5619
  }
3664
5620
  async function runWatchLoop(invocation, resolved) {
3665
- const outDir = path9__namespace.resolve(resolved.cwd, resolved.outDir);
3666
- const tmpDir = path9__namespace.resolve(resolved.cwd, resolved.tmpDir);
3667
- const outName = path9__namespace.basename(outDir).toLowerCase();
3668
- const tmpName = path9__namespace.basename(tmpDir).toLowerCase();
5621
+ const outDir = path10__namespace.resolve(resolved.cwd, resolved.outDir);
5622
+ const tmpDir = path10__namespace.resolve(resolved.cwd, resolved.tmpDir);
5623
+ const outName = path10__namespace.basename(outDir).toLowerCase();
5624
+ const tmpName = path10__namespace.basename(tmpDir).toLowerCase();
3669
5625
  const isGenerated = (candidate) => candidate.toLowerCase().split(/[\\/]/).some(
3670
5626
  (segment) => segment === outName || segment === tmpName || segment.startsWith(`${outName}.backup-`)
3671
5627
  );
@@ -3689,19 +5645,19 @@ async function runWatchLoop(invocation, resolved) {
3689
5645
  info("\u{1F440} Watching for changes. Press Ctrl+C to stop.");
3690
5646
  regenerate();
3691
5647
  let timer = null;
3692
- const watcher = fs8__namespace.watch(resolved.cwd, { recursive: true }, (_event, filename) => {
5648
+ const watcher = fs9__namespace.watch(resolved.cwd, { recursive: true }, (_event, filename) => {
3693
5649
  if (filename) {
3694
- const changed = path9__namespace.resolve(resolved.cwd, filename.toString());
5650
+ const changed = path10__namespace.resolve(resolved.cwd, filename.toString());
3695
5651
  if (isGenerated(changed)) return;
3696
5652
  }
3697
5653
  if (timer) clearTimeout(timer);
3698
5654
  timer = setTimeout(regenerate, 250);
3699
5655
  });
3700
- await new Promise((resolve5) => {
5656
+ await new Promise((resolve6) => {
3701
5657
  const stop = () => {
3702
5658
  if (timer) clearTimeout(timer);
3703
5659
  watcher.close();
3704
- resolve5();
5660
+ resolve6();
3705
5661
  };
3706
5662
  process.once("SIGINT", stop);
3707
5663
  process.once("SIGTERM", stop);