codebase-snapshot 0.9.0 → 0.9.4

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.
@@ -1,9 +1,9 @@
1
1
  import * as crypto from 'crypto';
2
2
  import { createHash } from 'crypto';
3
3
  import { AsyncLocalStorage } from 'async_hooks';
4
- import * as fs10 from 'fs';
4
+ import * as fs11 from 'fs';
5
5
  import * as os from 'os';
6
- import * as path11 from 'path';
6
+ import * as path12 from 'path';
7
7
  import ts from 'typescript';
8
8
  import { createRequire } from 'module';
9
9
  import { pathToFileURL } from 'url';
@@ -234,9 +234,9 @@ var JAVA_METHOD = /^\s+(?:public|private|protected)\s+(?:static\s+)?(?:final\s+)
234
234
  var jvmProfile = {
235
235
  id: "jvm",
236
236
  extensions: [".java", ".kt", ".kts", ".scala"],
237
- summarize(path12, content) {
237
+ summarize(path13, content) {
238
238
  const summary = emptySummary();
239
- const lower = path12.toLowerCase();
239
+ const lower = path13.toLowerCase();
240
240
  const isJava = lower.endsWith(".java");
241
241
  const isKotlin = lower.endsWith(".kt") || lower.endsWith(".kts");
242
242
  eachLine(content, (line) => {
@@ -689,16 +689,16 @@ function summarizeTypeScriptSource(relativePath, content) {
689
689
  // src/ast/profiles/sfc.ts
690
690
  var SCRIPT_BLOCK = /<script[^>]*>([\s\S]*?)<\/script>/gi;
691
691
  var ASTRO_FRONTMATTER = /^\uFEFF?---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*(?=\r?\n|$)/;
692
- function componentName(path12) {
693
- const base = path12.split("/").pop() ?? path12;
692
+ function componentName(path13) {
693
+ const base = path13.split("/").pop() ?? path13;
694
694
  return base.replace(/\.[^.]+$/, "");
695
695
  }
696
696
  var sfcProfile = {
697
697
  id: "sfc",
698
698
  extensions: [".vue", ".svelte", ".astro"],
699
- summarize(path12, content) {
699
+ summarize(path13, content) {
700
700
  let script = "";
701
- if (path12.toLowerCase().endsWith(".astro")) {
701
+ if (path13.toLowerCase().endsWith(".astro")) {
702
702
  const frontmatter = ASTRO_FRONTMATTER.exec(content);
703
703
  if (frontmatter) script += `${frontmatter[1]}
704
704
  `;
@@ -707,7 +707,7 @@ var sfcProfile = {
707
707
  script += `${match[1]}
708
708
  `;
709
709
  }
710
- const name = componentName(path12);
710
+ const name = componentName(path13);
711
711
  if (!script.trim()) {
712
712
  const summary2 = emptySummary();
713
713
  if (name) {
@@ -741,15 +741,15 @@ for (const profile of LANGUAGE_PROFILES) {
741
741
  BY_EXTENSION.set(extension.toLowerCase(), profile);
742
742
  }
743
743
  }
744
- function profileForPath(path12) {
745
- const lower = path12.toLowerCase();
744
+ function profileForPath(path13) {
745
+ const lower = path13.toLowerCase();
746
746
  const dot = lower.lastIndexOf(".");
747
747
  if (dot === -1) return null;
748
748
  return BY_EXTENSION.get(lower.slice(dot)) ?? null;
749
749
  }
750
- function summarizeWithProfile(path12, content) {
751
- const profile = profileForPath(path12);
752
- return profile ? profile.summarize(path12, content) : null;
750
+ function summarizeWithProfile(path13, content) {
751
+ const profile = profileForPath(path13);
752
+ return profile ? profile.summarize(path13, content) : null;
753
753
  }
754
754
 
755
755
  // src/ast/structured.ts
@@ -920,15 +920,15 @@ var SKIPPED_WALK_DIRS = /* @__PURE__ */ new Set([
920
920
  ]);
921
921
  function readSmallFile(fullPath) {
922
922
  try {
923
- const stats = fs10.statSync(fullPath);
923
+ const stats = fs11.statSync(fullPath);
924
924
  if (!stats.isFile() || stats.size > MANIFEST_MAX_BYTES) return null;
925
- return fs10.readFileSync(fullPath, "utf8");
925
+ return fs11.readFileSync(fullPath, "utf8");
926
926
  } catch {
927
927
  return null;
928
928
  }
929
929
  }
930
930
  function readManifest(cwd, name) {
931
- return readSmallFile(path11.join(cwd, name));
931
+ return readSmallFile(path12.join(cwd, name));
932
932
  }
933
933
  function dependenciesFromPackageJson(raw) {
934
934
  const names = /* @__PURE__ */ new Set();
@@ -988,7 +988,7 @@ function pnpmWorkspacePatterns(raw) {
988
988
  function childDirectories(cwd, parent) {
989
989
  let entries;
990
990
  try {
991
- entries = fs10.readdirSync(path11.join(cwd, parent), { withFileTypes: true });
991
+ entries = fs11.readdirSync(path12.join(cwd, parent), { withFileTypes: true });
992
992
  } catch {
993
993
  return [];
994
994
  }
@@ -1023,13 +1023,13 @@ function workspacePackageDirs(cwd, patterns) {
1023
1023
  function scanWorkspacePackages(cwd, patterns) {
1024
1024
  const files = /* @__PURE__ */ new Set();
1025
1025
  const dependencies = /* @__PURE__ */ new Set();
1026
- for (const relative6 of workspacePackageDirs(cwd, patterns)) {
1027
- const full = path11.join(cwd, relative6);
1026
+ for (const relative7 of workspacePackageDirs(cwd, patterns)) {
1027
+ const full = path12.join(cwd, relative7);
1028
1028
  try {
1029
- for (const entry of fs10.readdirSync(full, { withFileTypes: true })) files.add(entry.name);
1029
+ for (const entry of fs11.readdirSync(full, { withFileTypes: true })) files.add(entry.name);
1030
1030
  } catch {
1031
1031
  }
1032
- const raw = readSmallFile(path11.join(full, "package.json"));
1032
+ const raw = readSmallFile(path12.join(full, "package.json"));
1033
1033
  if (raw !== null) {
1034
1034
  for (const name of dependenciesFromPackageJson(raw)) dependencies.add(name);
1035
1035
  }
@@ -1046,7 +1046,7 @@ function countMarkdownFiles(cwd) {
1046
1046
  directoriesSeen += 1;
1047
1047
  let entries;
1048
1048
  try {
1049
- entries = fs10.readdirSync(current.dir, { withFileTypes: true });
1049
+ entries = fs11.readdirSync(current.dir, { withFileTypes: true });
1050
1050
  } catch {
1051
1051
  continue;
1052
1052
  }
@@ -1057,7 +1057,7 @@ function countMarkdownFiles(cwd) {
1057
1057
  }
1058
1058
  if (!entry.isDirectory() || current.depth >= 2) continue;
1059
1059
  if (entry.name.startsWith(".") || SKIPPED_WALK_DIRS.has(entry.name)) continue;
1060
- queue.push({ dir: path11.join(current.dir, entry.name), depth: current.depth + 1 });
1060
+ queue.push({ dir: path12.join(current.dir, entry.name), depth: current.depth + 1 });
1061
1061
  }
1062
1062
  }
1063
1063
  return count;
@@ -1066,7 +1066,7 @@ function buildPresetContext(cwd) {
1066
1066
  const files = /* @__PURE__ */ new Set();
1067
1067
  const dirs = /* @__PURE__ */ new Set();
1068
1068
  try {
1069
- for (const entry of fs10.readdirSync(cwd, { withFileTypes: true })) {
1069
+ for (const entry of fs11.readdirSync(cwd, { withFileTypes: true })) {
1070
1070
  if (entry.isDirectory()) dirs.add(entry.name);
1071
1071
  else if (entry.isFile()) files.add(entry.name);
1072
1072
  }
@@ -1083,7 +1083,7 @@ function buildPresetContext(cwd) {
1083
1083
  const isWorkspace = files.has("pnpm-workspace.yaml") || files.has("nx.json") || files.has("turbo.json") || files.has("lerna.json") || packageJson !== void 0 && packageJson.includes('"workspaces"') || declaredWorkspaces.length > 0;
1084
1084
  let workspace = { files: /* @__PURE__ */ new Set(), dependencies: /* @__PURE__ */ new Set() };
1085
1085
  if (isWorkspace) {
1086
- const pnpmRaw = files.has("pnpm-workspace.yaml") ? readSmallFile(path11.join(cwd, "pnpm-workspace.yaml")) : null;
1086
+ const pnpmRaw = files.has("pnpm-workspace.yaml") ? readSmallFile(path12.join(cwd, "pnpm-workspace.yaml")) : null;
1087
1087
  const declared = pnpmRaw === null ? [] : pnpmWorkspacePatterns(pnpmRaw);
1088
1088
  const patterns = declared.length > 0 ? declared : declaredWorkspaces.length > 0 ? declaredWorkspaces : DEFAULT_WORKSPACE_PATTERNS;
1089
1089
  workspace = scanWorkspacePackages(cwd, patterns);
@@ -1101,25 +1101,41 @@ function buildPresetContext(cwd) {
1101
1101
  markdownCount: files.has("package.json") ? 0 : countMarkdownFiles(cwd)
1102
1102
  };
1103
1103
  }
1104
- function hasFile(context, name) {
1105
- return context.files.has(name);
1104
+
1105
+ // src/presets/evidence.ts
1106
+ function fileEvidence(context, name) {
1107
+ return context.files.has(name) ? name : null;
1106
1108
  }
1107
- function hasFileStartingWith(context, prefix) {
1109
+ function fileStartingWithEvidence(context, prefix) {
1108
1110
  for (const file of context.files) {
1109
- if (file.startsWith(prefix)) return true;
1111
+ if (file.startsWith(prefix)) return file;
1110
1112
  }
1111
1113
  for (const file of context.workspaceFiles) {
1112
- if (file.startsWith(prefix)) return true;
1114
+ if (file.startsWith(prefix)) return file;
1113
1115
  }
1114
- return false;
1116
+ return null;
1115
1117
  }
1116
- function manifestContains(context, manifest, needle) {
1118
+ function dependencyEvidence(context, ...names) {
1119
+ for (const name of names) {
1120
+ if (context.dependencies.has(name)) return `depends on ${name}`;
1121
+ }
1122
+ return null;
1123
+ }
1124
+ function manifestEvidence(context, manifest, needle) {
1117
1125
  const content = context.manifests[manifest];
1118
- if (!content) return false;
1119
- return content.toLowerCase().includes(needle.toLowerCase());
1126
+ if (!content) return null;
1127
+ if (!content.toLowerCase().includes(needle.toLowerCase())) return null;
1128
+ return `${needle} in ${manifest}`;
1120
1129
  }
1121
- function hasDependency(context, ...names) {
1122
- return names.some((name) => context.dependencies.has(name));
1130
+ function suffixEvidence(context, suffix) {
1131
+ for (const file of context.files) {
1132
+ if (file.endsWith(suffix)) return file;
1133
+ }
1134
+ return null;
1135
+ }
1136
+ function markdownEvidence(context) {
1137
+ if (context.markdownCount < 3 || context.files.has("package.json")) return null;
1138
+ return `${context.markdownCount} markdown files, no package.json`;
1123
1139
  }
1124
1140
 
1125
1141
  // src/presets/helpers.ts
@@ -1327,7 +1343,7 @@ var JS_PRESETS = [
1327
1343
  {
1328
1344
  name: "nextjs",
1329
1345
  priority: 100,
1330
- detect: (context) => hasFileStartingWith(context, "next.config") || hasDependency(context, "next"),
1346
+ detect: (context) => fileStartingWithEvidence(context, "next.config") ?? dependencyEvidence(context, "next"),
1331
1347
  layers: NEXTJS_LAYERS,
1332
1348
  kinds: NEXTJS_KINDS,
1333
1349
  exclude: NEXT_LIKE_EXCLUDES,
@@ -1336,7 +1352,7 @@ var JS_PRESETS = [
1336
1352
  {
1337
1353
  name: "nuxt",
1338
1354
  priority: 98,
1339
- detect: (context) => hasFileStartingWith(context, "nuxt.config") || hasDependency(context, "nuxt", "nuxt3"),
1355
+ detect: (context) => fileStartingWithEvidence(context, "nuxt.config") ?? dependencyEvidence(context, "nuxt", "nuxt3"),
1340
1356
  layers: [
1341
1357
  layer("pages", "pages/", "src/pages/"),
1342
1358
  layer("components", "components/", "src/components/"),
@@ -1360,7 +1376,7 @@ var JS_PRESETS = [
1360
1376
  {
1361
1377
  name: "sveltekit",
1362
1378
  priority: 96,
1363
- detect: (context) => hasFileStartingWith(context, "svelte.config") || hasDependency(context, "@sveltejs/kit", "svelte"),
1379
+ detect: (context) => fileStartingWithEvidence(context, "svelte.config") ?? dependencyEvidence(context, "@sveltejs/kit", "svelte"),
1364
1380
  layers: [
1365
1381
  layer("routes", "src/routes/", "routes/"),
1366
1382
  layer("lib", "src/lib/", "lib/"),
@@ -1381,7 +1397,7 @@ var JS_PRESETS = [
1381
1397
  {
1382
1398
  name: "remix",
1383
1399
  priority: 94,
1384
- detect: (context) => hasDependency(context, "@remix-run/react", "@remix-run/node", "@remix-run/dev"),
1400
+ detect: (context) => dependencyEvidence(context, "@remix-run/react", "@remix-run/node", "@remix-run/dev"),
1385
1401
  layers: [
1386
1402
  layer("routes", "app/routes/", "routes/"),
1387
1403
  layer("components", "app/components/"),
@@ -1395,7 +1411,7 @@ var JS_PRESETS = [
1395
1411
  {
1396
1412
  name: "astro",
1397
1413
  priority: 92,
1398
- detect: (context) => hasFileStartingWith(context, "astro.config") || hasDependency(context, "astro"),
1414
+ detect: (context) => fileStartingWithEvidence(context, "astro.config") ?? dependencyEvidence(context, "astro"),
1399
1415
  layers: [
1400
1416
  layer("pages", "src/pages/"),
1401
1417
  layer("layouts", "src/layouts/"),
@@ -1410,7 +1426,7 @@ var JS_PRESETS = [
1410
1426
  {
1411
1427
  name: "angular",
1412
1428
  priority: 90,
1413
- detect: (context) => hasFile(context, "angular.json") || hasDependency(context, "@angular/core"),
1429
+ detect: (context) => fileEvidence(context, "angular.json") ?? dependencyEvidence(context, "@angular/core"),
1414
1430
  layers: [
1415
1431
  layer("app", "src/app/"),
1416
1432
  layer("features", "src/app/features/"),
@@ -1434,7 +1450,7 @@ var JS_PRESETS = [
1434
1450
  {
1435
1451
  name: "solid",
1436
1452
  priority: 88,
1437
- detect: (context) => hasDependency(context, "solid-js", "solid-start"),
1453
+ detect: (context) => dependencyEvidence(context, "solid-js", "solid-start"),
1438
1454
  layers: [
1439
1455
  layer("pages", "src/routes/", "src/pages/"),
1440
1456
  layer("components", "src/components/"),
@@ -1447,7 +1463,15 @@ var JS_PRESETS = [
1447
1463
  {
1448
1464
  name: "vue-vite",
1449
1465
  priority: 86,
1450
- detect: (context) => hasDependency(context, "vue") && (hasDependency(context, "vite") || hasFileStartingWith(context, "vite.config")),
1466
+ // `vue` is the decisive signal; `vite` (or its config file) only says the app is
1467
+ // built with Vite rather than shipped pre-compiled, so it is required but not what
1468
+ // gets reported.
1469
+ detect: (context) => {
1470
+ const framework = dependencyEvidence(context, "vue");
1471
+ if (framework === null) return null;
1472
+ const build = dependencyEvidence(context, "vite") ?? fileStartingWithEvidence(context, "vite.config");
1473
+ return build === null ? null : framework;
1474
+ },
1451
1475
  layers: [
1452
1476
  layer("pages", "src/pages/", "src/views/"),
1453
1477
  layer("components", "src/components/"),
@@ -1472,7 +1496,12 @@ var JS_PRESETS = [
1472
1496
  {
1473
1497
  name: "react-vite",
1474
1498
  priority: 84,
1475
- detect: (context) => hasDependency(context, "react") && (hasDependency(context, "vite") || hasFileStartingWith(context, "vite.config")),
1499
+ detect: (context) => {
1500
+ const framework = dependencyEvidence(context, "react");
1501
+ if (framework === null) return null;
1502
+ const build = dependencyEvidence(context, "vite") ?? fileStartingWithEvidence(context, "vite.config");
1503
+ return build === null ? null : framework;
1504
+ },
1476
1505
  layers: VITE_LAYERS,
1477
1506
  kinds: VITE_KINDS,
1478
1507
  exclude: [".vite", "dist"],
@@ -1481,7 +1510,7 @@ var JS_PRESETS = [
1481
1510
  {
1482
1511
  name: "nest",
1483
1512
  priority: 80,
1484
- detect: (context) => hasDependency(context, "@nestjs/core"),
1513
+ detect: (context) => dependencyEvidence(context, "@nestjs/core"),
1485
1514
  layers: [
1486
1515
  layer("modules", "src/modules/", "modules/"),
1487
1516
  layer("controllers", "src/controllers/", "controllers/"),
@@ -1507,7 +1536,7 @@ var JS_PRESETS = [
1507
1536
  {
1508
1537
  name: "node-express",
1509
1538
  priority: 78,
1510
- detect: (context) => hasDependency(context, "express", "fastify", "koa", "@hapi/hapi"),
1539
+ detect: (context) => dependencyEvidence(context, "express", "fastify", "koa", "@hapi/hapi"),
1511
1540
  layers: NODE_LAYERS,
1512
1541
  kinds: NODE_KINDS,
1513
1542
  exclude: ["dist"],
@@ -1518,7 +1547,7 @@ var JS_PRESETS = [
1518
1547
  // A bare package.json is often just tooling around a non-JS project, so it
1519
1548
  // ranks below every definitive language manifest.
1520
1549
  priority: 45,
1521
- detect: (context) => hasFile(context, "package.json"),
1550
+ detect: (context) => fileEvidence(context, "package.json"),
1522
1551
  layers: [
1523
1552
  layer("src", "src/"),
1524
1553
  layer("lib", "lib/"),
@@ -1532,13 +1561,12 @@ var JS_PRESETS = [
1532
1561
  ];
1533
1562
 
1534
1563
  // src/presets/lang-presets.ts
1535
- var anyFileEndingWith = (context, suffix) => Array.from(context.files).some((file) => file.endsWith(suffix));
1536
1564
  var DIR = (name) => `${name}/`;
1537
1565
  var LANGUAGE_PRESETS = [
1538
1566
  {
1539
1567
  name: "python-django",
1540
1568
  priority: 56,
1541
- detect: (context) => hasFile(context, "manage.py") || manifestContains(context, "pyproject.toml", "django") || manifestContains(context, "requirements.txt", "django"),
1569
+ detect: (context) => fileEvidence(context, "manage.py") ?? manifestEvidence(context, "pyproject.toml", "django") ?? manifestEvidence(context, "requirements.txt", "django"),
1542
1570
  layers: [
1543
1571
  layer("views", DIR("views"), DIR("app/views"), DIR("apps/views")),
1544
1572
  layer("models", DIR("models"), DIR("app/models"), DIR("apps/models")),
@@ -1564,7 +1592,7 @@ var LANGUAGE_PRESETS = [
1564
1592
  {
1565
1593
  name: "python-fastapi",
1566
1594
  priority: 54,
1567
- detect: (context) => manifestContains(context, "requirements.txt", "fastapi") || manifestContains(context, "requirements.txt", "flask") || manifestContains(context, "pyproject.toml", "fastapi") || manifestContains(context, "pyproject.toml", "flask"),
1595
+ detect: (context) => manifestEvidence(context, "requirements.txt", "fastapi") ?? manifestEvidence(context, "requirements.txt", "flask") ?? manifestEvidence(context, "pyproject.toml", "fastapi") ?? manifestEvidence(context, "pyproject.toml", "flask"),
1568
1596
  layers: [
1569
1597
  layer("routers", DIR("routers"), DIR("routes"), DIR("api")),
1570
1598
  layer("models", DIR("models"), DIR("db")),
@@ -1588,7 +1616,7 @@ var LANGUAGE_PRESETS = [
1588
1616
  name: "python-lib",
1589
1617
  // Below node-lib: a Node repo with a stray .py file is still a Node repo.
1590
1618
  priority: 30,
1591
- detect: (context) => hasFile(context, "pyproject.toml") || hasFile(context, "setup.py") || hasFile(context, "requirements.txt") || anyFileEndingWith(context, ".py"),
1619
+ detect: (context) => fileEvidence(context, "pyproject.toml") ?? fileEvidence(context, "setup.py") ?? fileEvidence(context, "requirements.txt") ?? suffixEvidence(context, ".py"),
1592
1620
  layers: [
1593
1621
  layer("package", DIR("src"), DIR("lib")),
1594
1622
  layer("tests", DIR("tests"), DIR("test")),
@@ -1601,7 +1629,7 @@ var LANGUAGE_PRESETS = [
1601
1629
  {
1602
1630
  name: "go",
1603
1631
  priority: 50,
1604
- detect: (context) => hasFile(context, "go.mod"),
1632
+ detect: (context) => fileEvidence(context, "go.mod"),
1605
1633
  layers: [
1606
1634
  layer("cmd", "cmd/"),
1607
1635
  layer("internal", "internal/"),
@@ -1625,7 +1653,7 @@ var LANGUAGE_PRESETS = [
1625
1653
  {
1626
1654
  name: "rust",
1627
1655
  priority: 48,
1628
- detect: (context) => hasFile(context, "Cargo.toml"),
1656
+ detect: (context) => fileEvidence(context, "Cargo.toml"),
1629
1657
  layers: [
1630
1658
  layer("src", "src/"),
1631
1659
  layer("crates", "crates/"),
@@ -1641,7 +1669,7 @@ var LANGUAGE_PRESETS = [
1641
1669
  {
1642
1670
  name: "java-spring",
1643
1671
  priority: 46,
1644
- detect: (context) => hasFile(context, "pom.xml") || hasFile(context, "build.gradle") || hasFile(context, "build.gradle.kts"),
1672
+ detect: (context) => fileEvidence(context, "pom.xml") ?? fileEvidence(context, "build.gradle") ?? fileEvidence(context, "build.gradle.kts"),
1645
1673
  layers: [
1646
1674
  layer("api", "src/main/java/"),
1647
1675
  layer("test", "src/test/java/"),
@@ -1664,7 +1692,7 @@ var LANGUAGE_PRESETS = [
1664
1692
  {
1665
1693
  name: "dotnet",
1666
1694
  priority: 44,
1667
- detect: (context) => anyFileEndingWith(context, ".csproj") || anyFileEndingWith(context, ".sln") || hasFile(context, "global.json"),
1695
+ detect: (context) => suffixEvidence(context, ".csproj") ?? suffixEvidence(context, ".sln") ?? fileEvidence(context, "global.json"),
1668
1696
  layers: [
1669
1697
  layer("web", "src/", "Web/"),
1670
1698
  layer("domain", "Domain/", "Core/"),
@@ -1687,7 +1715,7 @@ var LANGUAGE_PRESETS = [
1687
1715
  {
1688
1716
  name: "php-laravel",
1689
1717
  priority: 42,
1690
- detect: (context) => hasFile(context, "artisan") || manifestContains(context, "composer.json", "laravel") || hasFile(context, "composer.json"),
1718
+ detect: (context) => fileEvidence(context, "artisan") ?? manifestEvidence(context, "composer.json", "laravel") ?? fileEvidence(context, "composer.json"),
1691
1719
  layers: [
1692
1720
  layer("app", "app/"),
1693
1721
  layer("routes", "routes/"),
@@ -1713,7 +1741,7 @@ var LANGUAGE_PRESETS = [
1713
1741
  {
1714
1742
  name: "ruby-rails",
1715
1743
  priority: 40,
1716
- detect: (context) => hasFile(context, "Gemfile") || hasFile(context, "config.ru") || hasFile(context, "Rakefile"),
1744
+ detect: (context) => fileEvidence(context, "Gemfile") ?? fileEvidence(context, "config.ru") ?? fileEvidence(context, "Rakefile"),
1717
1745
  layers: [
1718
1746
  layer("app", "app/"),
1719
1747
  layer("controllers", "app/controllers/"),
@@ -1786,7 +1814,7 @@ var SHAPE_PRESETS = [
1786
1814
  {
1787
1815
  name: "monorepo",
1788
1816
  priority: 70,
1789
- detect: (context) => hasFile(context, "pnpm-workspace.yaml") || hasFile(context, "nx.json") || hasFile(context, "turbo.json") || hasFile(context, "lerna.json") || manifestContains(context, "package.json", '"workspaces"'),
1817
+ detect: (context) => fileEvidence(context, "pnpm-workspace.yaml") ?? fileEvidence(context, "nx.json") ?? fileEvidence(context, "turbo.json") ?? fileEvidence(context, "lerna.json") ?? manifestEvidence(context, "package.json", '"workspaces"'),
1790
1818
  layers: [
1791
1819
  layer("apps", "apps/"),
1792
1820
  layer("packages", "packages/"),
@@ -1800,7 +1828,7 @@ var SHAPE_PRESETS = [
1800
1828
  {
1801
1829
  name: "infra",
1802
1830
  priority: 24,
1803
- 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"),
1831
+ detect: (context) => suffixEvidence(context, ".tf") ?? fileEvidence(context, "Chart.yaml") ?? fileEvidence(context, "kustomization.yaml") ?? fileEvidence(context, "docker-compose.yml") ?? fileEvidence(context, "docker-compose.yaml"),
1804
1832
  layers: [
1805
1833
  layer("modules", "modules/"),
1806
1834
  layer("environments", "env/", "environments/"),
@@ -1822,7 +1850,7 @@ var SHAPE_PRESETS = [
1822
1850
  // up to two directories down (bounded in the context builder). Counting
1823
1851
  // only the root turned every real documentation repository — README.md plus
1824
1852
  // the pages under `docs/` — into `generic`.
1825
- detect: (context) => context.markdownCount >= 3 && !hasFile(context, "package.json"),
1853
+ detect: (context) => markdownEvidence(context),
1826
1854
  layers: [
1827
1855
  layer("content", "content/", "docs/"),
1828
1856
  layer("assets", "assets/", "static/", "public/"),
@@ -1835,7 +1863,10 @@ var SHAPE_PRESETS = [
1835
1863
  {
1836
1864
  name: "generic",
1837
1865
  priority: 0,
1838
- detect: () => true,
1866
+ // No detector: this is the fallback, and `detectPresetFromContext` reaches it only
1867
+ // when every other preset reported no evidence. `fallback: true` is what lets the
1868
+ // wizard tell "I recognised this" apart from "I gave up".
1869
+ fallback: true,
1839
1870
  layers: DEFAULT_LAYERS,
1840
1871
  kinds: DEFAULT_KINDS,
1841
1872
  exclude: [],
@@ -1867,20 +1898,27 @@ function detectPresetFromContext(context, forced) {
1867
1898
  if (forced !== void 0) {
1868
1899
  const preset = BY_NAME.get(forced);
1869
1900
  if (!preset) throw new Error(unknownPresetMessage(forced));
1870
- return toDetected(preset);
1901
+ return toDetected(preset, null);
1871
1902
  }
1872
1903
  for (const preset of PRESETS) {
1873
- if (preset.detect(context)) return toDetected(preset);
1904
+ const reason = preset.detect?.(context) ?? null;
1905
+ if (reason !== null) return toDetected(preset, reason);
1874
1906
  }
1875
- return toDetected(PRESETS[PRESETS.length - 1]);
1907
+ const fallback = PRESETS.find((preset) => preset.fallback === true);
1908
+ if (fallback === void 0) {
1909
+ throw new Error("No fallback preset is defined. Exactly one preset must set `fallback: true`.");
1910
+ }
1911
+ return toDetected(fallback, null, true);
1876
1912
  }
1877
- function toDetected(preset) {
1913
+ function toDetected(preset, reason, fallback = false) {
1878
1914
  return {
1879
1915
  presetName: preset.name,
1880
1916
  layers: preset.layers,
1881
1917
  kinds: preset.kinds,
1882
1918
  exclude: preset.exclude ? [...preset.exclude] : [],
1883
- recommendedBudget: preset.recommendedBudget ?? null
1919
+ recommendedBudget: preset.recommendedBudget ?? null,
1920
+ reason,
1921
+ fallback
1884
1922
  };
1885
1923
  }
1886
1924
  function detectPreset(cwd, forced) {
@@ -2320,14 +2358,14 @@ var PRESET_IGNORES = new Set(
2320
2358
  );
2321
2359
  function readLinkTarget(linkPath, fallback) {
2322
2360
  try {
2323
- return fs10.readlinkSync(linkPath);
2361
+ return fs11.readlinkSync(linkPath);
2324
2362
  } catch {
2325
2363
  return fallback;
2326
2364
  }
2327
2365
  }
2328
2366
  function isInside(root, candidate) {
2329
- const relative6 = path11.relative(root, candidate);
2330
- return relative6 === "" || !relative6.startsWith(`..${path11.sep}`) && relative6 !== ".." && !path11.isAbsolute(relative6);
2367
+ const relative7 = path12.relative(root, candidate);
2368
+ return relative7 === "" || !relative7.startsWith(`..${path12.sep}`) && relative7 !== ".." && !path12.isAbsolute(relative7);
2331
2369
  }
2332
2370
  function getGitInfo(cwd, fallbackBranch = "main") {
2333
2371
  try {
@@ -2532,10 +2570,10 @@ function trimUnescapedTrailingSpaces(line) {
2532
2570
  return line.slice(0, end);
2533
2571
  }
2534
2572
  function readGitignoreRules(dir) {
2535
- const gitignorePath2 = path11.join(dir, ".gitignore");
2573
+ const gitignorePath2 = path12.join(dir, ".gitignore");
2536
2574
  let raw;
2537
2575
  try {
2538
- raw = fs10.readFileSync(gitignorePath2, "utf8");
2576
+ raw = fs11.readFileSync(gitignorePath2, "utf8");
2539
2577
  } catch {
2540
2578
  return [];
2541
2579
  }
@@ -2662,7 +2700,7 @@ function isToolOwnedDirName(name) {
2662
2700
  }
2663
2701
  function hasSnapshotMarker(target) {
2664
2702
  try {
2665
- return fs10.statSync(target).isDirectory() && fs10.existsSync(path11.join(target, ".codebase-snapshot"));
2703
+ return fs11.statSync(target).isDirectory() && fs11.existsSync(path12.join(target, ".codebase-snapshot"));
2666
2704
  } catch {
2667
2705
  return false;
2668
2706
  }
@@ -2676,7 +2714,7 @@ function makeToolOwnedDecider(cwd, toolOwnedPaths) {
2676
2714
  const prefix = parts.slice(0, depth).join("/");
2677
2715
  let hasMarker = markers.get(prefix);
2678
2716
  if (hasMarker === void 0) {
2679
- hasMarker = fs10.existsSync(path11.join(cwd, prefix, ".codebase-snapshot"));
2717
+ hasMarker = fs11.existsSync(path12.join(cwd, prefix, ".codebase-snapshot"));
2680
2718
  markers.set(prefix, hasMarker);
2681
2719
  }
2682
2720
  if (hasMarker) return true;
@@ -2691,7 +2729,7 @@ function makeLinkProbe() {
2691
2729
  let known = cache.get(absolutePath);
2692
2730
  if (known === void 0) {
2693
2731
  try {
2694
- known = fs10.lstatSync(absolutePath).isSymbolicLink();
2732
+ known = fs11.lstatSync(absolutePath).isSymbolicLink();
2695
2733
  } catch {
2696
2734
  known = false;
2697
2735
  }
@@ -2704,7 +2742,7 @@ function dropLinkedDirectories(cwd, candidates, skipped) {
2704
2742
  const isLink = makeLinkProbe();
2705
2743
  let rootReal = cwd;
2706
2744
  try {
2707
- rootReal = fs10.realpathSync(cwd);
2745
+ rootReal = fs11.realpathSync(cwd);
2708
2746
  } catch {
2709
2747
  }
2710
2748
  const reported = /* @__PURE__ */ new Set();
@@ -2714,7 +2752,7 @@ function dropLinkedDirectories(cwd, candidates, skipped) {
2714
2752
  let linkedDir = null;
2715
2753
  for (let depth = 1; depth <= parts.length; depth += 1) {
2716
2754
  const prefix = parts.slice(0, depth).join("/");
2717
- if (isLink(path11.join(cwd, prefix))) {
2755
+ if (isLink(path12.join(cwd, prefix))) {
2718
2756
  linkedDir = prefix;
2719
2757
  break;
2720
2758
  }
@@ -2725,10 +2763,10 @@ function dropLinkedDirectories(cwd, candidates, skipped) {
2725
2763
  }
2726
2764
  if (reported.has(linkedDir)) continue;
2727
2765
  reported.add(linkedDir);
2728
- const absolute = path11.join(cwd, linkedDir);
2766
+ const absolute = path12.join(cwd, linkedDir);
2729
2767
  let targetReal = absolute;
2730
2768
  try {
2731
- targetReal = fs10.realpathSync(absolute);
2769
+ targetReal = fs11.realpathSync(absolute);
2732
2770
  } catch {
2733
2771
  }
2734
2772
  const target = readLinkTarget(absolute, targetReal);
@@ -2805,7 +2843,7 @@ function collectFiles(cwd, options = {}) {
2805
2843
  "Discovery mode is `git`, but git could not list this directory's files. Run inside a working repository, or pass --discovery walk (or auto) to scan the tree directly."
2806
2844
  );
2807
2845
  }
2808
- if (fs10.existsSync(path11.join(cwd, ".git"))) {
2846
+ if (fs11.existsSync(path12.join(cwd, ".git"))) {
2809
2847
  throw new Error(
2810
2848
  "This directory is a git repository, but git could not list its files. A plain walk honours .gitignore files, but git also applies .git/info/exclude and your global excludes file, includes files that are tracked although a .gitignore now matches them, and treats a submodule as a single entry. Fix the repository (git status should work), run outside git, or pass --discovery walk to accept those differences."
2811
2849
  );
@@ -2864,7 +2902,7 @@ function collectFiles(cwd, options = {}) {
2864
2902
  }
2865
2903
  function rootRealFor(root) {
2866
2904
  try {
2867
- return fs10.realpathSync(root);
2905
+ return fs11.realpathSync(root);
2868
2906
  } catch {
2869
2907
  return root;
2870
2908
  }
@@ -2872,18 +2910,18 @@ function rootRealFor(root) {
2872
2910
  function walkDirectory(dir, baseDir, rootReal, visited, reservedPaths = /* @__PURE__ */ new Set(), parentGitignoreLayers = []) {
2873
2911
  const results = { files: [], skipped: [] };
2874
2912
  const stack = [];
2875
- const relativeToBase = (target) => path11.relative(baseDir, target).replace(/\\/g, "/") || ".";
2913
+ const relativeToBase = (target) => path12.relative(baseDir, target).replace(/\\/g, "/") || ".";
2876
2914
  const enter = (current, layers) => {
2877
2915
  if (visited.has(current)) return false;
2878
2916
  visited.add(current);
2879
2917
  const localRules = readGitignoreRules(current);
2880
2918
  const gitignoreLayers = localRules.length === 0 ? layers : [
2881
2919
  ...layers,
2882
- { prefix: path11.relative(baseDir, current).replace(/\\/g, "/"), rules: localRules }
2920
+ { prefix: path12.relative(baseDir, current).replace(/\\/g, "/"), rules: localRules }
2883
2921
  ];
2884
2922
  let entries;
2885
2923
  try {
2886
- entries = fs10.readdirSync(current, { withFileTypes: true });
2924
+ entries = fs11.readdirSync(current, { withFileTypes: true });
2887
2925
  } catch {
2888
2926
  results.skipped.push({
2889
2927
  path: relativeToBase(current),
@@ -2905,8 +2943,8 @@ function walkDirectory(dir, baseDir, rootReal, visited, reservedPaths = /* @__PU
2905
2943
  }
2906
2944
  const entry = frame.entries[frame.index];
2907
2945
  frame.index += 1;
2908
- const fullPath = path11.join(frame.dir, entry.name);
2909
- const relPath = path11.relative(baseDir, fullPath).replace(/\\/g, "/");
2946
+ const fullPath = path12.join(frame.dir, entry.name);
2947
+ const relPath = path12.relative(baseDir, fullPath).replace(/\\/g, "/");
2910
2948
  if (isToolOwnedDirName(entry.name) && reservedPaths.has(relPath)) continue;
2911
2949
  if (DEFAULT_IGNORED_DIRS.includes(entry.name)) {
2912
2950
  results.skipped.push({
@@ -2939,14 +2977,14 @@ function walkDirectory(dir, baseDir, rootReal, visited, reservedPaths = /* @__PU
2939
2977
  if (entry.isSymbolicLink()) {
2940
2978
  let targetReal;
2941
2979
  try {
2942
- targetReal = fs10.realpathSync(fullPath);
2980
+ targetReal = fs11.realpathSync(fullPath);
2943
2981
  } catch {
2944
2982
  results.skipped.push({ path: relPath, reason: "read-error", detail: "broken symlink" });
2945
2983
  continue;
2946
2984
  }
2947
2985
  let stats;
2948
2986
  try {
2949
- stats = fs10.statSync(fullPath);
2987
+ stats = fs11.statSync(fullPath);
2950
2988
  } catch {
2951
2989
  results.skipped.push({ path: relPath, reason: "read-error", detail: "unreadable symlink" });
2952
2990
  continue;
@@ -2999,6 +3037,20 @@ var CONFIG_FILENAMES = [
2999
3037
  ".snapshotrc.json",
3000
3038
  ".snapshotrc"
3001
3039
  ];
3040
+ function findConfigFile(cwd) {
3041
+ for (const filename of CONFIG_FILENAMES) {
3042
+ const fullPath = path12.join(cwd, filename);
3043
+ if (fs11.existsSync(fullPath)) return fullPath;
3044
+ }
3045
+ const pkgJsonPath = path12.join(cwd, "package.json");
3046
+ if (!fs11.existsSync(pkgJsonPath)) return null;
3047
+ try {
3048
+ const parsed = JSON.parse(fs11.readFileSync(pkgJsonPath, "utf8"));
3049
+ return parsed.snapshot === void 0 ? null : pkgJsonPath;
3050
+ } catch {
3051
+ return null;
3052
+ }
3053
+ }
3002
3054
  var RESOLVED_CONFIG_BRAND = /* @__PURE__ */ Symbol.for(
3003
3055
  "codebase-snapshot/resolved-config"
3004
3056
  );
@@ -3244,11 +3296,11 @@ async function loadConfigFileDetailed(cwd) {
3244
3296
  const warnings = [];
3245
3297
  const errors = [];
3246
3298
  for (const filename of CONFIG_FILENAMES) {
3247
- const fullPath = path11.join(cwd, filename);
3248
- if (!fs10.existsSync(fullPath)) continue;
3299
+ const fullPath = path12.join(cwd, filename);
3300
+ if (!fs11.existsSync(fullPath)) continue;
3249
3301
  if (filename.endsWith(".json") || filename === ".snapshotrc") {
3250
3302
  try {
3251
- const config = parseJsonish(fs10.readFileSync(fullPath, "utf8"), fullPath);
3303
+ const config = parseJsonish(fs11.readFileSync(fullPath, "utf8"), fullPath);
3252
3304
  if (config === null || typeof config !== "object" || Array.isArray(config)) {
3253
3305
  throw new Error(
3254
3306
  `${fullPath} does not hold a configuration object (received ${describeValue2(config)}).`
@@ -3282,10 +3334,10 @@ async function loadConfigFileDetailed(cwd) {
3282
3334
  continue;
3283
3335
  }
3284
3336
  }
3285
- const pkgJsonPath = path11.join(cwd, "package.json");
3286
- if (fs10.existsSync(pkgJsonPath)) {
3337
+ const pkgJsonPath = path12.join(cwd, "package.json");
3338
+ if (fs11.existsSync(pkgJsonPath)) {
3287
3339
  try {
3288
- const parsed = parseJsonish(fs10.readFileSync(pkgJsonPath, "utf8"), pkgJsonPath);
3340
+ const parsed = parseJsonish(fs11.readFileSync(pkgJsonPath, "utf8"), pkgJsonPath);
3289
3341
  const snapshotKey = parsed.snapshot;
3290
3342
  if (snapshotKey && typeof snapshotKey === "object" && !Array.isArray(snapshotKey)) {
3291
3343
  return { config: snapshotKey, file: pkgJsonPath, warnings, errors };
@@ -3308,7 +3360,7 @@ async function loadConfigFile(cwd) {
3308
3360
  const loaded = await loadConfigFileDetailed(cwd);
3309
3361
  if (loaded.config && loaded.file && typeof loaded.config === "object") {
3310
3362
  Object.defineProperty(loaded.config, CONFIG_DIR_SYMBOL, {
3311
- value: path11.dirname(loaded.file),
3363
+ value: path12.dirname(loaded.file),
3312
3364
  enumerable: false,
3313
3365
  configurable: true
3314
3366
  });
@@ -3467,12 +3519,12 @@ function resolveKindRules(value) {
3467
3519
  function outputExcludePatterns(cwd, outDir, tmpDir) {
3468
3520
  const patterns = [];
3469
3521
  for (const dir of [outDir, tmpDir]) {
3470
- const absolute = path11.resolve(cwd, dir);
3471
- const relative6 = path11.relative(cwd, absolute);
3472
- if (!relative6 || relative6 === ".." || relative6.startsWith(`..${path11.sep}`) || path11.isAbsolute(relative6)) {
3522
+ const absolute = path12.resolve(cwd, dir);
3523
+ const relative7 = path12.relative(cwd, absolute);
3524
+ if (!relative7 || relative7 === ".." || relative7.startsWith(`..${path12.sep}`) || path12.isAbsolute(relative7)) {
3473
3525
  continue;
3474
3526
  }
3475
- patterns.push(`${relative6.replace(/\\/g, "/")}/**`);
3527
+ patterns.push(`${relative7.replace(/\\/g, "/")}/**`);
3476
3528
  }
3477
3529
  return patterns;
3478
3530
  }
@@ -3588,11 +3640,11 @@ function resolveCandidateCwd(userConfig, cliOptions, baseDir) {
3588
3640
  );
3589
3641
  }
3590
3642
  const candidate = cliOptions?.cwd || userConfig?.cwd || baseDir || process.cwd();
3591
- const resolved = path11.resolve(candidate);
3592
- if (!fs10.existsSync(resolved)) {
3643
+ const resolved = path12.resolve(candidate);
3644
+ if (!fs11.existsSync(resolved)) {
3593
3645
  throw new Error(`Working directory does not exist: ${resolved}`);
3594
3646
  }
3595
- if (!fs10.statSync(resolved).isDirectory()) {
3647
+ if (!fs11.statSync(resolved).isDirectory()) {
3596
3648
  throw new Error(`Working directory is not a directory: ${resolved}`);
3597
3649
  }
3598
3650
  return resolved;
@@ -4616,7 +4668,7 @@ var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
4616
4668
  ".so"
4617
4669
  ]);
4618
4670
  function isBinaryExtension(filePath) {
4619
- const ext = path11.extname(filePath).toLowerCase();
4671
+ const ext = path12.extname(filePath).toLowerCase();
4620
4672
  return BINARY_EXTENSIONS.has(ext);
4621
4673
  }
4622
4674
  function isBinaryBuffer(buffer) {
@@ -4786,13 +4838,13 @@ function isBinaryFile(absolutePath) {
4786
4838
  if (isBinaryExtension(absolutePath)) return true;
4787
4839
  let fd;
4788
4840
  try {
4789
- fd = fs10.openSync(absolutePath, "r");
4841
+ fd = fs11.openSync(absolutePath, "r");
4790
4842
  const buffer = Buffer.alloc(CHUNK);
4791
4843
  let sample;
4792
4844
  let position = 0;
4793
4845
  while (position < BINARY_SCAN_BYTES) {
4794
4846
  const wanted = Math.min(CHUNK, BINARY_SCAN_BYTES - position);
4795
- const bytesRead = fs10.readSync(fd, buffer, 0, wanted, position);
4847
+ const bytesRead = fs11.readSync(fd, buffer, 0, wanted, position);
4796
4848
  if (bytesRead === 0) return false;
4797
4849
  const chunk = buffer.subarray(0, bytesRead);
4798
4850
  if (sample === void 0) {
@@ -4810,7 +4862,7 @@ function isBinaryFile(absolutePath) {
4810
4862
  } finally {
4811
4863
  if (fd !== void 0) {
4812
4864
  try {
4813
- fs10.closeSync(fd);
4865
+ fs11.closeSync(fd);
4814
4866
  } catch {
4815
4867
  }
4816
4868
  }
@@ -4870,10 +4922,10 @@ var LANGUAGE_BY_BASENAME = {
4870
4922
  };
4871
4923
  function detectLanguage(filePath) {
4872
4924
  const normalized = filePath.replace(/\\/g, "/");
4873
- const base = path11.posix.basename(normalized);
4925
+ const base = path12.posix.basename(normalized);
4874
4926
  if (LANGUAGE_BY_BASENAME[base]) return LANGUAGE_BY_BASENAME[base];
4875
4927
  if (base === ".env" || base.startsWith(".env.")) return "dotenv";
4876
- const ext = path11.posix.extname(base).toLowerCase();
4928
+ const ext = path12.posix.extname(base).toLowerCase();
4877
4929
  return LANGUAGE_BY_EXTENSION[ext] ?? "text";
4878
4930
  }
4879
4931
  function maskedPath(value, secretRules) {
@@ -4881,33 +4933,33 @@ function maskedPath(value, secretRules) {
4881
4933
  }
4882
4934
  function confineToDirectory(directory, filename) {
4883
4935
  if (filename.includes("\0")) return null;
4884
- if (path11.isAbsolute(filename)) return null;
4885
- const base = path11.resolve(directory);
4886
- const resolved = path11.resolve(base, filename);
4936
+ if (path12.isAbsolute(filename)) return null;
4937
+ const base = path12.resolve(directory);
4938
+ const resolved = path12.resolve(base, filename);
4887
4939
  const caseInsensitive = process.platform === "win32" || process.platform === "darwin";
4888
- const prefix = caseInsensitive ? `${base}${path11.sep}`.toLowerCase() : `${base}${path11.sep}`;
4940
+ const prefix = caseInsensitive ? `${base}${path12.sep}`.toLowerCase() : `${base}${path12.sep}`;
4889
4941
  const candidate = caseInsensitive ? resolved.toLowerCase() : resolved;
4890
4942
  return candidate.startsWith(prefix) ? resolved : null;
4891
4943
  }
4892
4944
  function findStagingDirectory(config) {
4893
- const tmpDir = path11.resolve(config.cwd, config.tmpDir);
4945
+ const tmpDir = path12.resolve(config.cwd, config.tmpDir);
4894
4946
  let entries;
4895
4947
  try {
4896
- entries = fs10.readdirSync(tmpDir);
4948
+ entries = fs11.readdirSync(tmpDir);
4897
4949
  } catch {
4898
4950
  return null;
4899
4951
  }
4900
4952
  const runs = entries.filter((name) => name.startsWith("run-")).sort();
4901
4953
  const newest = runs[runs.length - 1];
4902
- return newest ? path11.join(tmpDir, newest) : null;
4954
+ return newest ? path12.join(tmpDir, newest) : null;
4903
4955
  }
4904
4956
  function describeMissingManifest(config, manifestPath) {
4905
- const outDir = path11.resolve(config.cwd, config.outDir);
4906
- const markerPath = path11.join(outDir, ".codebase-snapshot");
4957
+ const outDir = path12.resolve(config.cwd, config.outDir);
4958
+ const markerPath = path12.join(outDir, ".codebase-snapshot");
4907
4959
  let marker2 = null;
4908
4960
  try {
4909
- if (fs10.existsSync(markerPath)) {
4910
- const parsed = JSON.parse(fs10.readFileSync(markerPath, "utf8"));
4961
+ if (fs11.existsSync(markerPath)) {
4962
+ const parsed = JSON.parse(fs11.readFileSync(markerPath, "utf8"));
4911
4963
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
4912
4964
  marker2 = parsed;
4913
4965
  }
@@ -4943,14 +4995,14 @@ function sha256Hex(buffer) {
4943
4995
  }
4944
4996
  var VERIFY_SNIFF_MAX_BYTES = 8 * 1024 * 1024;
4945
4997
  function wouldNowBeIncluded(cwd, relativePath, reason) {
4946
- const absolutePath = path11.join(cwd, relativePath);
4998
+ const absolutePath = path12.join(cwd, relativePath);
4947
4999
  try {
4948
- const stats = fs10.statSync(absolutePath);
5000
+ const stats = fs11.statSync(absolutePath);
4949
5001
  if (!stats.isFile()) return false;
4950
5002
  if (isBinaryFile(absolutePath)) return false;
4951
5003
  if (reason !== "binary") return true;
4952
5004
  if (stats.size > VERIFY_SNIFF_MAX_BYTES) return false;
4953
- return !decodeText(fs10.readFileSync(absolutePath)).binary;
5005
+ return !decodeText(fs11.readFileSync(absolutePath)).binary;
4954
5006
  } catch {
4955
5007
  return false;
4956
5008
  }
@@ -5008,8 +5060,8 @@ function describeManifestShapeProblem(value) {
5008
5060
  function validateSnapshot(configInput) {
5009
5061
  assertUsableConfigInput(configInput);
5010
5062
  const config = isResolvedConfig(configInput) ? normalizeResolvedConfig(configInput) : resolveConfig(configInput);
5011
- const manifestPath = path11.resolve(config.cwd, config.outDir, "manifest.json");
5012
- if (!fs10.existsSync(manifestPath)) {
5063
+ const manifestPath = path12.resolve(config.cwd, config.outDir, "manifest.json");
5064
+ if (!fs11.existsSync(manifestPath)) {
5013
5065
  return {
5014
5066
  valid: false,
5015
5067
  manifestPresent: false,
@@ -5020,7 +5072,7 @@ function validateSnapshot(configInput) {
5020
5072
  }
5021
5073
  let manifest;
5022
5074
  try {
5023
- manifest = JSON.parse(fs10.readFileSync(manifestPath, "utf8"));
5075
+ manifest = JSON.parse(fs11.readFileSync(manifestPath, "utf8"));
5024
5076
  } catch (err) {
5025
5077
  return {
5026
5078
  valid: false,
@@ -5042,7 +5094,7 @@ function validateSnapshot(configInput) {
5042
5094
  }
5043
5095
  const drift = emptyDrift();
5044
5096
  const warnings = [];
5045
- const snapshotDir = path11.resolve(config.cwd, config.outDir);
5097
+ const snapshotDir = path12.resolve(config.cwd, config.outDir);
5046
5098
  const manifestFileMap = /* @__PURE__ */ new Map();
5047
5099
  for (const file of manifest.files) {
5048
5100
  if (file && typeof file.relativePath === "string") {
@@ -5076,7 +5128,7 @@ function validateSnapshot(configInput) {
5076
5128
  }
5077
5129
  let diskHash;
5078
5130
  try {
5079
- diskHash = sha256Hex(fs10.readFileSync(path11.join(config.cwd, diskFile)));
5131
+ diskHash = sha256Hex(fs11.readFileSync(path12.join(config.cwd, diskFile)));
5080
5132
  } catch (err) {
5081
5133
  if (err.code === "ENOENT") {
5082
5134
  drift.missingOnDisk.push(diskFile);
@@ -5100,7 +5152,7 @@ function validateSnapshot(configInput) {
5100
5152
  }
5101
5153
  let statFailure = null;
5102
5154
  try {
5103
- fs10.statSync(confined);
5155
+ fs11.statSync(confined);
5104
5156
  } catch (err) {
5105
5157
  statFailure = err;
5106
5158
  }
@@ -5120,7 +5172,7 @@ function validateSnapshot(configInput) {
5120
5172
  ...Object.values(config.budgets.byLayer)
5121
5173
  );
5122
5174
  const partNames = /* @__PURE__ */ new Set();
5123
- const partsDir = path11.join(snapshotDir, "parts");
5175
+ const partsDir = path12.join(snapshotDir, "parts");
5124
5176
  for (const part of manifest.parts) {
5125
5177
  if (!part || typeof part.filename !== "string") continue;
5126
5178
  partNames.add(part.filename);
@@ -5135,7 +5187,7 @@ function validateSnapshot(configInput) {
5135
5187
  }
5136
5188
  let buffer;
5137
5189
  try {
5138
- buffer = fs10.readFileSync(partPath);
5190
+ buffer = fs11.readFileSync(partPath);
5139
5191
  } catch {
5140
5192
  drift.partIssues.push({ part: part.filename, issue: "missing from snapshot/parts" });
5141
5193
  continue;
@@ -5349,9 +5401,9 @@ function validateSnapshot(configInput) {
5349
5401
  ["full-codebase.md", manifest.artifacts?.fullMarkdown]
5350
5402
  ]) {
5351
5403
  if (!artifact) continue;
5352
- const artifactPath = path11.join(snapshotDir, key);
5404
+ const artifactPath = path12.join(snapshotDir, key);
5353
5405
  try {
5354
- const buffer = fs10.readFileSync(artifactPath);
5406
+ const buffer = fs11.readFileSync(artifactPath);
5355
5407
  if (sha256Hex(buffer) !== artifact.sha256 || buffer.length !== artifact.byteSize) {
5356
5408
  drift.partIssues.push({ part: key, issue: "artifact does not match its recorded digest" });
5357
5409
  }
@@ -5421,7 +5473,7 @@ function validateSnapshot(configInput) {
5421
5473
 
5422
5474
  // src/version.ts
5423
5475
  var GENERATOR_NAME = "codebase-snapshot";
5424
- var GENERATOR_VERSION = "0.9.0";
5476
+ var GENERATOR_VERSION = "0.9.4";
5425
5477
  var LEGACY_GENERATOR_NAMES = ["@ankurhalder/codebase-snapshot"];
5426
5478
  function isOurGeneratorName(name) {
5427
5479
  if (typeof name !== "string") return false;
@@ -5436,42 +5488,42 @@ function describeMatcher(matcher) {
5436
5488
  return String(matcher);
5437
5489
  }
5438
5490
  function normalizeTarget(cwd, target) {
5439
- const absolute = path11.resolve(cwd, target);
5440
- const relative6 = path11.relative(cwd, absolute);
5441
- if (relative6.startsWith("..") || path11.isAbsolute(relative6)) return target;
5442
- return relative6.split(path11.sep).join("/");
5491
+ const absolute = path12.resolve(cwd, target);
5492
+ const relative7 = path12.relative(cwd, absolute);
5493
+ if (relative7.startsWith("..") || path12.isAbsolute(relative7)) return target;
5494
+ return relative7.split(path12.sep).join("/");
5443
5495
  }
5444
5496
  function findRuleMatch(rules, relativePath) {
5445
5497
  return rules.find(
5446
5498
  (rule) => matchRule(relativePath, rule.match)
5447
5499
  );
5448
5500
  }
5449
- function findRecordedAncestor(skipped, relative6, maskedPath2) {
5501
+ function findRecordedAncestor(skipped, relative7, maskedPath2) {
5450
5502
  let nearest;
5451
5503
  for (const record of skipped) {
5452
5504
  const ancestorPath = record.path;
5453
5505
  if (ancestorPath.length === 0) continue;
5454
- const isAncestor = relative6.startsWith(`${ancestorPath}/`) || maskedPath2.startsWith(`${ancestorPath}/`);
5506
+ const isAncestor = relative7.startsWith(`${ancestorPath}/`) || maskedPath2.startsWith(`${ancestorPath}/`);
5455
5507
  if (!isAncestor) continue;
5456
5508
  if (!nearest || ancestorPath.length > nearest.path.length) nearest = record;
5457
5509
  }
5458
5510
  return nearest;
5459
5511
  }
5460
5512
  function readManifest2(config) {
5461
- const manifestPath = path11.resolve(config.cwd, config.outDir, "manifest.json");
5513
+ const manifestPath = path12.resolve(config.cwd, config.outDir, "manifest.json");
5462
5514
  try {
5463
- return JSON.parse(fs10.readFileSync(manifestPath, "utf8"));
5515
+ return JSON.parse(fs11.readFileSync(manifestPath, "utf8"));
5464
5516
  } catch {
5465
5517
  return null;
5466
5518
  }
5467
5519
  }
5468
5520
  function explainPath(config, target) {
5469
- const relative6 = normalizeTarget(config.cwd, target);
5470
- const absolute = path11.join(config.cwd, relative6);
5471
- const maskedPath2 = findSecretInPath(relative6, config.secretRules)?.masked ?? relative6;
5521
+ const relative7 = normalizeTarget(config.cwd, target);
5522
+ const absolute = path12.join(config.cwd, relative7);
5523
+ const maskedPath2 = findSecretInPath(relative7, config.secretRules)?.masked ?? relative7;
5472
5524
  const result = {
5473
5525
  path: maskedPath2,
5474
- exists: fs10.existsSync(absolute),
5526
+ exists: fs11.existsSync(absolute),
5475
5527
  included: false
5476
5528
  };
5477
5529
  const collection = collectFiles(config.cwd, {
@@ -5482,12 +5534,12 @@ function explainPath(config, target) {
5482
5534
  // Same exclusion `generate` applies, so `explain` never says "not-discovered" about a file
5483
5535
  // that is only missing because the walker dropped the tool's own output directory.
5484
5536
  toolOwnedPaths: [config.outDir, config.tmpDir].map(
5485
- (directory) => path11.relative(config.cwd, path11.resolve(config.cwd, directory)).replace(/\\/g, "/")
5486
- ).filter((relative7) => relative7.length > 0 && !relative7.startsWith(".."))
5537
+ (directory) => path12.relative(config.cwd, path12.resolve(config.cwd, directory)).replace(/\\/g, "/")
5538
+ ).filter((relative8) => relative8.length > 0 && !relative8.startsWith(".."))
5487
5539
  });
5488
- if (!collection.files.includes(relative6)) {
5540
+ if (!collection.files.includes(relative7)) {
5489
5541
  const record = collection.skipped.find(
5490
- (entry2) => entry2.path === relative6 || entry2.path === maskedPath2
5542
+ (entry2) => entry2.path === relative7 || entry2.path === maskedPath2
5491
5543
  );
5492
5544
  if (record) {
5493
5545
  result.reason = record.reason;
@@ -5496,7 +5548,7 @@ function explainPath(config, target) {
5496
5548
  result.reason = "missing";
5497
5549
  result.detail = "no such file or directory, relative to the configured cwd";
5498
5550
  } else {
5499
- const ancestor = findRecordedAncestor(collection.skipped, relative6, maskedPath2);
5551
+ const ancestor = findRecordedAncestor(collection.skipped, relative7, maskedPath2);
5500
5552
  if (ancestor) {
5501
5553
  result.reason = ancestor.reason;
5502
5554
  result.detail = ancestor.detail ? `${ancestor.detail} \u2014 inherited from the skipped directory \`${ancestor.path}\`` : `inside the skipped directory \`${ancestor.path}\``;
@@ -5514,15 +5566,15 @@ function explainPath(config, target) {
5514
5566
  result.detail = "binary content; generate skips it and records `binary` in manifest.skipped[]";
5515
5567
  return result;
5516
5568
  }
5517
- result.layer = classifyLayer(relative6, config.layers);
5518
- result.kind = classifyKind(relative6, config.kinds);
5519
- result.language = detectLanguage(relative6);
5520
- const layerRule = findRuleMatch(config.layers, relative6);
5569
+ result.layer = classifyLayer(relative7, config.layers);
5570
+ result.kind = classifyKind(relative7, config.kinds);
5571
+ result.language = detectLanguage(relative7);
5572
+ const layerRule = findRuleMatch(config.layers, relative7);
5521
5573
  if (layerRule) result.layerMatch = describeMatcher(layerRule.match);
5522
- const kindRule = findRuleMatch(config.kinds, relative6);
5574
+ const kindRule = findRuleMatch(config.kinds, relative7);
5523
5575
  if (kindRule) result.kindMatch = describeMatcher(kindRule.match);
5524
- if (fs10.existsSync(absolute) && fs10.statSync(absolute).isFile()) {
5525
- const buffer = fs10.readFileSync(absolute);
5576
+ if (fs11.existsSync(absolute) && fs11.statSync(absolute).isFile()) {
5577
+ const buffer = fs11.readFileSync(absolute);
5526
5578
  const decoded = decodeText(buffer);
5527
5579
  if (decoded.binary) {
5528
5580
  result.included = false;
@@ -5534,12 +5586,12 @@ function explainPath(config, target) {
5534
5586
  result.bytes = buffer.length;
5535
5587
  result.lines = countLines(text2);
5536
5588
  if (decoded.encoding !== "utf-8") result.encoding = decoded.encoding;
5537
- const { findings } = scanAndRedactSecrets(text2, config.secretRules, relative6);
5589
+ const { findings } = scanAndRedactSecrets(text2, config.secretRules, relative7);
5538
5590
  result.secretCount = findings.length;
5539
5591
  result.secrets = findings.slice(0, 20).map((finding) => ({ rules: finding.rules, line: finding.line }));
5540
5592
  }
5541
5593
  const manifest = readManifest2(config);
5542
- const entry = Array.isArray(manifest?.files) ? manifest.files.find((file) => file.relativePath === relative6) : void 0;
5594
+ const entry = Array.isArray(manifest?.files) ? manifest.files.find((file) => file.relativePath === relative7) : void 0;
5543
5595
  result.part = entry?.part ?? null;
5544
5596
  result.inclusionMode = entry?.inclusionMode;
5545
5597
  return result;
@@ -5770,14 +5822,14 @@ function compareManifests(before, after) {
5770
5822
  function loadManifest(target) {
5771
5823
  let isDirectory;
5772
5824
  try {
5773
- isDirectory = fs10.statSync(target).isDirectory();
5825
+ isDirectory = fs11.statSync(target).isDirectory();
5774
5826
  } catch {
5775
5827
  throw new Error(`No snapshot found at ${target}: the path does not exist.`);
5776
5828
  }
5777
- const manifestPath = isDirectory ? path11.join(target, "manifest.json") : target;
5829
+ const manifestPath = isDirectory ? path12.join(target, "manifest.json") : target;
5778
5830
  let raw;
5779
5831
  try {
5780
- raw = fs10.readFileSync(manifestPath, "utf8");
5832
+ raw = fs11.readFileSync(manifestPath, "utf8");
5781
5833
  } catch {
5782
5834
  throw new Error(`Could not read a manifest at ${manifestPath}.`);
5783
5835
  }
@@ -5834,15 +5886,15 @@ function renderDiffText(diff) {
5834
5886
  function appendGitignoreEntries(cwd, entries) {
5835
5887
  const wanted = [...new Set(entries)].filter((entry) => entry.trim().length > 0);
5836
5888
  if (wanted.length === 0) return { added: [], note: null };
5837
- const ignorePath = path11.join(cwd, ".gitignore");
5889
+ const ignorePath = path12.join(cwd, ".gitignore");
5838
5890
  const manual = `Add ${wanted.map((entry) => `"${entry}"`).join(" and ")} to your .gitignore yourself.`;
5839
5891
  let existing = "";
5840
5892
  try {
5841
- if (fs10.existsSync(ignorePath)) {
5842
- if (fs10.statSync(ignorePath).isDirectory()) {
5893
+ if (fs11.existsSync(ignorePath)) {
5894
+ if (fs11.statSync(ignorePath).isDirectory()) {
5843
5895
  return { added: [], note: `${ignorePath} is a directory, so it was not updated. ${manual}` };
5844
5896
  }
5845
- existing = fs10.readFileSync(ignorePath, "utf8");
5897
+ existing = fs11.readFileSync(ignorePath, "utf8");
5846
5898
  }
5847
5899
  } catch (err) {
5848
5900
  return {
@@ -5855,7 +5907,7 @@ function appendGitignoreEntries(cwd, entries) {
5855
5907
  if (added.length === 0) return { added: [], note: null };
5856
5908
  const prefix = existing.length === 0 || existing.endsWith("\n") ? "" : "\n";
5857
5909
  try {
5858
- fs10.writeFileSync(ignorePath, `${existing}${prefix}${added.join("\n")}
5910
+ fs11.writeFileSync(ignorePath, `${existing}${prefix}${added.join("\n")}
5859
5911
  `, "utf8");
5860
5912
  } catch (err) {
5861
5913
  return { added: [], note: `${err.message}. ${manual}` };
@@ -5863,7 +5915,7 @@ function appendGitignoreEntries(cwd, entries) {
5863
5915
  return { added, note: null };
5864
5916
  }
5865
5917
  function gitignorePath(cwd, filePath) {
5866
- return path11.relative(cwd, filePath).split(path11.sep).join("/");
5918
+ return path12.relative(cwd, filePath).split(path12.sep).join("/");
5867
5919
  }
5868
5920
 
5869
5921
  // src/wizard/config-template.ts
@@ -6251,10 +6303,10 @@ var CONFIG_CANDIDATES = [
6251
6303
  ];
6252
6304
  function chooseConfigFileName(cwd) {
6253
6305
  if (!supportsTypeScriptImports()) return "snapshot.config.mjs";
6254
- const pkgPath = path11.join(cwd, "package.json");
6255
- if (!fs10.existsSync(pkgPath)) return "snapshot.config.mjs";
6306
+ const pkgPath = path12.join(cwd, "package.json");
6307
+ if (!fs11.existsSync(pkgPath)) return "snapshot.config.mjs";
6256
6308
  try {
6257
- const parsed = JSON.parse(fs10.readFileSync(pkgPath, "utf8"));
6309
+ const parsed = JSON.parse(fs11.readFileSync(pkgPath, "utf8"));
6258
6310
  return parsed.type === "module" ? "snapshot.config.ts" : "snapshot.config.mjs";
6259
6311
  } catch {
6260
6312
  return "snapshot.config.mjs";
@@ -6268,7 +6320,7 @@ var SIZE_CHOICES = [
6268
6320
  ];
6269
6321
  function packageJsonHasConfig(cwd) {
6270
6322
  try {
6271
- const parsed = JSON.parse(fs10.readFileSync(path11.join(cwd, "package.json"), "utf8"));
6323
+ const parsed = JSON.parse(fs11.readFileSync(path12.join(cwd, "package.json"), "utf8"));
6272
6324
  return typeof parsed.snapshot === "object" && parsed.snapshot !== null && !Array.isArray(parsed.snapshot);
6273
6325
  } catch {
6274
6326
  return false;
@@ -6289,7 +6341,7 @@ function artifactAnswers(options) {
6289
6341
  }
6290
6342
  function topLevelDirs(cwd) {
6291
6343
  try {
6292
- return fs10.readdirSync(cwd, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules").map((entry) => entry.name).sort();
6344
+ return fs11.readdirSync(cwd, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules").map((entry) => entry.name).sort();
6293
6345
  } catch {
6294
6346
  return [];
6295
6347
  }
@@ -6302,7 +6354,7 @@ function describeJsonValue(value) {
6302
6354
  }
6303
6355
  function writeFileOrExplain(target, content) {
6304
6356
  try {
6305
- fs10.writeFileSync(target, content, "utf8");
6357
+ fs11.writeFileSync(target, content, "utf8");
6306
6358
  } catch (err) {
6307
6359
  const code = err.code;
6308
6360
  const reason = code === "EACCES" || code === "EPERM" || code === "EROFS" ? `permission denied (${code})` : code === "EISDIR" ? "it is a directory" : `${err.message}`;
@@ -6313,7 +6365,7 @@ function canWriteModuleConfig(name, cwd) {
6313
6365
  if (name.endsWith(".mjs") || name.endsWith(".ts")) return true;
6314
6366
  if (!name.endsWith(".js")) return false;
6315
6367
  try {
6316
- const parsed = JSON.parse(fs10.readFileSync(path11.join(cwd, "package.json"), "utf8"));
6368
+ const parsed = JSON.parse(fs11.readFileSync(path12.join(cwd, "package.json"), "utf8"));
6317
6369
  return parsed.type === "module";
6318
6370
  } catch {
6319
6371
  return false;
@@ -6348,19 +6400,19 @@ function serializePackageJson(raw, value) {
6348
6400
  return `${text2}${raw.endsWith("\n") ? crlf ? "\r\n" : "\n" : ""}`;
6349
6401
  }
6350
6402
  function updatePackageJson(cwd, notes) {
6351
- const packagePath = path11.join(cwd, "package.json");
6403
+ const packagePath = path12.join(cwd, "package.json");
6352
6404
  const unchanged = {
6353
6405
  scripts: [],
6354
6406
  dependency: { name: GENERATOR_NAME, range: null, added: false }
6355
6407
  };
6356
- if (!fs10.existsSync(packagePath)) {
6408
+ if (!fs11.existsSync(packagePath)) {
6357
6409
  notes.push("No package.json found, so npm scripts and the devDependency were not added.");
6358
6410
  return unchanged;
6359
6411
  }
6360
6412
  let raw;
6361
6413
  let parsed;
6362
6414
  try {
6363
- raw = fs10.readFileSync(packagePath, "utf8");
6415
+ raw = fs11.readFileSync(packagePath, "utf8");
6364
6416
  parsed = JSON.parse(raw);
6365
6417
  } catch {
6366
6418
  notes.push("Could not parse package.json; npm scripts and the devDependency were not added.");
@@ -6423,16 +6475,18 @@ function updatePackageJson(cwd, notes) {
6423
6475
  return { scripts: added, dependency };
6424
6476
  }
6425
6477
  var GITIGNORE_ENTRIES = ["snapshot/", "snapshot.tmp/"];
6426
- function resolvePresetChoice(answers, presetName) {
6478
+ function resolvePresetChoice(detectedName, presetName) {
6427
6479
  return {
6428
6480
  value: presetName,
6429
6481
  label: presetName,
6430
- hint: presetName === answers.preset ? "detected" : void 0
6482
+ hint: presetName === detectedName ? "detected" : void 0
6431
6483
  };
6432
6484
  }
6433
6485
  async function runInit(options) {
6434
6486
  const io = options.io ?? defaultIO();
6435
6487
  const detected = detectPreset(options.cwd, options.preset);
6488
+ let reason = detected.reason;
6489
+ let fallback = detected.fallback;
6436
6490
  const notes = [];
6437
6491
  const answers = {
6438
6492
  preset: detected.presetName,
@@ -6454,117 +6508,148 @@ async function runInit(options) {
6454
6508
  if (interactive) {
6455
6509
  io.output.write("\n codebase-snapshot\n");
6456
6510
  io.output.write(" Turn this repository into an LLM-ready, redacted, byte-bounded snapshot.\n\n");
6457
- io.output.write(` Detected ${detected.presetName}
6511
+ if (fallback) {
6512
+ io.output.write(" \u26A0 Could not tell what kind of project this is.\n");
6513
+ io.output.write(" No package.json, go.mod, Cargo.toml, pyproject.toml, Gemfile,\n");
6514
+ io.output.write(" composer.json, pom.xml, *.csproj, next.config.*, vite.config.*, *.tf\n");
6515
+ io.output.write(" or workspace marker was found here.\n\n");
6516
+ } else {
6517
+ io.output.write(` Detected ${detected.presetName}
6518
+ `);
6519
+ io.output.write(` Because ${reason}
6520
+
6458
6521
  `);
6522
+ }
6459
6523
  io.output.write(` Directory ${options.cwd}
6460
6524
 
6461
6525
  `);
6462
- const useDetected = await confirm(io, `Set up for ${detected.presetName}?`, {
6463
- defaultValue: true
6526
+ const paceChoices = fallback ? [
6527
+ { value: "choose", label: "Pick the closest preset", hint: "then generate with the defaults" },
6528
+ { value: "recommended", label: "Use the defaults", hint: "generic \xB7 everything the scanner finds" },
6529
+ { value: "walk", label: "Go through each setting", hint: "every question starts on its default" }
6530
+ ] : [
6531
+ {
6532
+ value: "recommended",
6533
+ label: "Use the recommended setup",
6534
+ hint: `${detected.presetName} \xB7 config \xB7 npm scripts \xB7 install \xB7 generate`
6535
+ },
6536
+ { value: "walk", label: "Go through each setting", hint: "every question starts on its default" },
6537
+ { value: "choose", label: "Pick a different preset", hint: "everything else stays at the defaults" }
6538
+ ];
6539
+ const pace = await select(io, "How should I set this up?", paceChoices, {
6540
+ defaultValue: fallback ? "choose" : "recommended"
6464
6541
  });
6465
- if (!useDetected) {
6542
+ if (pace === "choose") {
6466
6543
  answers.preset = await select(
6467
6544
  io,
6468
6545
  "What kind of project is this?",
6469
- listPresetNames().map((name) => resolvePresetChoice(answers, name))
6546
+ listPresetNames().map(
6547
+ (name) => resolvePresetChoice(fallback ? null : detected.presetName, name)
6548
+ ),
6549
+ { defaultValue: answers.preset }
6470
6550
  );
6471
6551
  const chosen = detectPreset(options.cwd, answers.preset);
6472
6552
  answers.preset = chosen.presetName;
6553
+ reason = null;
6554
+ fallback = false;
6473
6555
  }
6474
- const scope = await select(
6475
- io,
6476
- "What should go into the snapshot?",
6477
- [
6478
- { value: "all", label: "Everything the scanner finds", hint: "recommended \xB7 respects .gitignore" },
6479
- { value: "folders", label: "Only specific folders\u2026" },
6480
- { value: "no-tests", label: "Everything, but skip tests" },
6481
- { value: "custom", label: "Let me write glob patterns" }
6482
- ],
6483
- { defaultValue: "all" }
6484
- );
6485
- if (scope === "folders") {
6486
- const dirs = topLevelDirs(options.cwd).slice(0, 40);
6487
- if (dirs.length === 0) {
6488
- notes.push("No top-level directories found; the snapshot will include everything.");
6556
+ if (pace === "walk") {
6557
+ const scope = await select(
6558
+ io,
6559
+ "What should go into the snapshot?",
6560
+ [
6561
+ { value: "all", label: "Everything the scanner finds", hint: "recommended \xB7 respects .gitignore" },
6562
+ { value: "folders", label: "Only specific folders\u2026" },
6563
+ { value: "no-tests", label: "Everything, but skip tests" },
6564
+ { value: "custom", label: "Let me write glob patterns" }
6565
+ ],
6566
+ { defaultValue: "all" }
6567
+ );
6568
+ if (scope === "folders") {
6569
+ const dirs = topLevelDirs(options.cwd).slice(0, 40);
6570
+ if (dirs.length === 0) {
6571
+ notes.push("No top-level directories found; the snapshot will include everything.");
6572
+ } else {
6573
+ const chosen = await multiSelect(
6574
+ io,
6575
+ "Select folders",
6576
+ dirs.map((name) => ({ value: name, label: `${name}/` })),
6577
+ { defaults: dirs }
6578
+ );
6579
+ answers.include = chosen.map((name) => `${name}/**`);
6580
+ }
6581
+ } else if (scope === "no-tests") {
6582
+ answers.excludePatterns = [
6583
+ ...answers.excludePatterns,
6584
+ "**/*.test.*",
6585
+ "**/*.spec.*",
6586
+ "tests",
6587
+ "test"
6588
+ ];
6589
+ } else if (scope === "custom") {
6590
+ const raw = await text(io, "Glob patterns to include (comma separated)", {
6591
+ defaultValue: ""
6592
+ });
6593
+ answers.include = raw.split(",").map((value) => value.trim()).filter(Boolean);
6594
+ }
6595
+ const currentBudget = String(answers.maxPartBytes);
6596
+ const sizeChoice = await select(io, "How large should each part be?", SIZE_CHOICES, {
6597
+ defaultValue: SIZE_CHOICES.some((choice) => choice.value === currentBudget) ? currentBudget : "120k"
6598
+ });
6599
+ if (sizeChoice === "custom") {
6600
+ const raw = await text(io, "Part size (e.g. 150k, 1.5mb, 900000)", {
6601
+ defaultValue: String(answers.maxPartBytes),
6602
+ validate: (value) => parseByteSize(value) === void 0 ? "Not a valid size." : null
6603
+ });
6604
+ answers.maxPartBytes = parseByteSize(raw) ?? answers.maxPartBytes;
6489
6605
  } else {
6490
- const chosen = await multiSelect(
6491
- io,
6492
- "Select folders",
6493
- dirs.map((name) => ({ value: name, label: `${name}/` })),
6494
- { defaults: dirs }
6495
- );
6496
- answers.include = chosen.map((name) => `${name}/**`);
6606
+ answers.maxPartBytes = sizeChoice;
6497
6607
  }
6498
- } else if (scope === "no-tests") {
6499
- answers.excludePatterns = [
6500
- ...answers.excludePatterns,
6501
- "**/*.test.*",
6502
- "**/*.spec.*",
6503
- "tests",
6504
- "test"
6608
+ const artifactChoices = [
6609
+ { value: "readme", label: "README.md", hint: "orientation page" },
6610
+ { value: "manifest", label: "manifest.json", hint: "required by validate and report" },
6611
+ { value: "parts", label: "parts/", hint: "the code payload" },
6612
+ { value: "navigation", label: "reports/navigation.md", hint: "file \u2192 part index" },
6613
+ { value: "projectTree", label: "reports/project-tree.md", hint: "ASCII tree" },
6614
+ { value: "audit", label: "reports/audit.md", hint: "security and health findings" },
6615
+ { value: "monolith", label: "full-codebase.json + .md", hint: "roughly doubles output" }
6505
6616
  ];
6506
- } else if (scope === "custom") {
6507
- const raw = await text(io, "Glob patterns to include (comma separated)", {
6508
- defaultValue: ""
6509
- });
6510
- answers.include = raw.split(",").map((value) => value.trim()).filter(Boolean);
6511
- }
6512
- const sizeChoice = await select(io, "How large should each part be?", SIZE_CHOICES, {
6513
- defaultValue: "120k"
6514
- });
6515
- if (sizeChoice === "custom") {
6516
- const raw = await text(io, "Part size (e.g. 150k, 1.5mb, 900000)", {
6517
- defaultValue: String(answers.maxPartBytes),
6518
- validate: (value) => parseByteSize(value) === void 0 ? "Not a valid size." : null
6519
- });
6520
- answers.maxPartBytes = parseByteSize(raw) ?? answers.maxPartBytes;
6521
- } else {
6522
- answers.maxPartBytes = sizeChoice;
6523
- }
6524
- const artifactChoices = [
6525
- { value: "readme", label: "README.md", hint: "orientation page" },
6526
- { value: "manifest", label: "manifest.json", hint: "required by validate and report" },
6527
- { value: "parts", label: "parts/", hint: "the code payload" },
6528
- { value: "navigation", label: "reports/navigation.md", hint: "file \u2192 part index" },
6529
- { value: "projectTree", label: "reports/project-tree.md", hint: "ASCII tree" },
6530
- { value: "audit", label: "reports/audit.md", hint: "security and health findings" },
6531
- { value: "monolith", label: "full-codebase.json + .md", hint: "roughly doubles output" }
6532
- ];
6533
- const presetEnabled = ARTIFACT_NAMES.filter((name) => answers.artifacts[name]);
6534
- const selectedArtifacts = await multiSelect(
6535
- io,
6536
- "Which artifacts should be generated?",
6537
- artifactChoices,
6538
- { defaults: presetEnabled }
6539
- );
6540
- for (const name of ARTIFACT_NAMES) answers.artifacts[name] = selectedArtifacts.includes(name);
6541
- const enabledCount = ARTIFACT_NAMES.filter((name) => answers.artifacts[name]).length;
6542
- answers.includeSensitiveFiles = await confirm(
6543
- io,
6544
- "Include credential files (.env, *.pem, .npmrc, .ssh/)?",
6545
- { defaultValue: false }
6546
- );
6547
- io.output.write("\n Review\n\n");
6548
- io.output.write(` project ${answers.preset}
6617
+ const presetEnabled = ARTIFACT_NAMES.filter((name) => answers.artifacts[name]);
6618
+ const selectedArtifacts = await multiSelect(
6619
+ io,
6620
+ "Which artifacts should be generated?",
6621
+ artifactChoices,
6622
+ { defaults: presetEnabled }
6623
+ );
6624
+ for (const name of ARTIFACT_NAMES) answers.artifacts[name] = selectedArtifacts.includes(name);
6625
+ const enabledCount = ARTIFACT_NAMES.filter((name) => answers.artifacts[name]).length;
6626
+ answers.includeSensitiveFiles = await confirm(
6627
+ io,
6628
+ "Include credential files (.env, *.pem, .npmrc, .ssh/)?",
6629
+ { defaultValue: false }
6630
+ );
6631
+ io.output.write("\n Review\n\n");
6632
+ io.output.write(` project ${answers.preset}
6549
6633
  `);
6550
- io.output.write(` include ${answers.include.length ? answers.include.join(", ") : "everything"}
6634
+ io.output.write(` include ${answers.include.length ? answers.include.join(", ") : "everything"}
6551
6635
  `);
6552
- io.output.write(` part size ${String(answers.maxPartBytes)}
6636
+ io.output.write(` part size ${String(answers.maxPartBytes)}
6553
6637
  `);
6554
- io.output.write(` artifacts ${enabledCount} of ${ARTIFACT_NAMES.length}
6638
+ io.output.write(` artifacts ${enabledCount} of ${ARTIFACT_NAMES.length}
6555
6639
  `);
6556
- io.output.write(` credentials ${answers.includeSensitiveFiles ? "included" : "skipped"}
6640
+ io.output.write(` credentials ${answers.includeSensitiveFiles ? "included" : "skipped"}
6557
6641
 
6558
6642
  `);
6559
- io.output.write(" Will write snapshot.config with your answers, plus every other setting\n");
6560
- io.output.write(" at its default \u2014 so nothing is hidden behind the docs.\n\n");
6561
- const proceed = await confirm(io, "Write this and continue?", { defaultValue: true });
6562
- if (!proceed) throw new PromptCancelled();
6643
+ io.output.write(" Will write snapshot.config with your answers, plus every other setting\n");
6644
+ io.output.write(" at its default \u2014 so nothing is hidden behind the docs.\n\n");
6645
+ const proceed = await confirm(io, "Write this and continue?", { defaultValue: true });
6646
+ if (!proceed) throw new PromptCancelled();
6647
+ }
6563
6648
  }
6564
6649
  const configText = renderConfig(answers);
6565
6650
  const preferredName = chooseConfigFileName(options.cwd);
6566
6651
  const presentConfigs = CONFIG_CANDIDATES.filter(
6567
- (name) => fs10.existsSync(path11.join(options.cwd, name))
6652
+ (name) => fs11.existsSync(path12.join(options.cwd, name))
6568
6653
  );
6569
6654
  const present = presentConfigs[0] ?? null;
6570
6655
  if (presentConfigs.length > 1) {
@@ -6573,7 +6658,7 @@ async function runInit(options) {
6573
6658
  );
6574
6659
  }
6575
6660
  const configName = present !== null && options.force ? present : preferredName;
6576
- const configPath = path11.join(options.cwd, configName);
6661
+ const configPath = path12.join(options.cwd, configName);
6577
6662
  const reused = present !== null && options.force !== true && options.reuseConfig === true;
6578
6663
  if (reused) notes.push(`Kept the existing ${present} instead of writing a new one.`);
6579
6664
  if (options.dryRun) {
@@ -6583,6 +6668,8 @@ async function runInit(options) {
6583
6668
  written: false,
6584
6669
  reused,
6585
6670
  preset: answers.preset,
6671
+ reason,
6672
+ fallback,
6586
6673
  scripts: [],
6587
6674
  gitignoreEntries: [],
6588
6675
  dependency: { name: GENERATOR_NAME, range: `^${GENERATOR_VERSION}`, added: false },
@@ -6604,7 +6691,7 @@ async function runInit(options) {
6604
6691
  'package.json already defines a "snapshot" config. Remove that key, or re-run with --force to write a config file that takes precedence over it.'
6605
6692
  );
6606
6693
  }
6607
- if (fs10.existsSync(configPath) && !options.force && !reused) {
6694
+ if (fs11.existsSync(configPath) && !options.force && !reused) {
6608
6695
  throw new Error(`${configName} already exists. Re-run with --force to replace it.`);
6609
6696
  }
6610
6697
  if (!reused) writeFileOrExplain(configPath, configText);
@@ -6617,12 +6704,56 @@ async function runInit(options) {
6617
6704
  written: true,
6618
6705
  reused,
6619
6706
  preset: answers.preset,
6707
+ reason,
6708
+ fallback,
6620
6709
  scripts,
6621
6710
  gitignoreEntries: ignore.added,
6622
6711
  dependency,
6623
6712
  notes
6624
6713
  };
6625
6714
  }
6715
+ function describeTarget(cwd) {
6716
+ const relative7 = path12.relative(process.cwd(), cwd);
6717
+ if (relative7 === "") return "this directory";
6718
+ if (relative7.startsWith("..") || path12.isAbsolute(relative7)) return cwd;
6719
+ return relative7;
6720
+ }
6721
+ function firstRunMessage(reason, cwd) {
6722
+ const where = describeTarget(cwd);
6723
+ if (reason === "empty") {
6724
+ return `Nothing to snapshot: ${where} has no files. Run this inside the repository you want to snapshot, or pass --cwd.`;
6725
+ }
6726
+ return `No configuration found in ${where} and this session is not interactive, so nothing was generated.
6727
+ Choose one:
6728
+ ${GENERATOR_NAME} --autopilot detect the project, set it up and generate, no questions
6729
+ ${GENERATOR_NAME} init --yes write a config from the detected defaults, then generate
6730
+ ${GENERATOR_NAME} init write a config interactively`;
6731
+ }
6732
+ function planFirstRun(facts) {
6733
+ if (facts.emptyTarget) return { action: "refuse", exitCode: 1, reason: "empty" };
6734
+ if (facts.autopilot) return { action: "setup", interactive: false };
6735
+ if (facts.hasConfig || facts.hasPriorSnapshot) return { action: "generate" };
6736
+ if (facts.interactive) return { action: "setup", interactive: true };
6737
+ return { action: "refuse", exitCode: 2, reason: "undecided" };
6738
+ }
6739
+ var NOT_CONTENT = /* @__PURE__ */ new Set([".git", ".hg", ".svn", ".bzr", ".DS_Store", "Thumbs.db"]);
6740
+ function isEffectivelyEmpty(cwd) {
6741
+ try {
6742
+ return fs11.readdirSync(cwd).every((entry) => NOT_CONTENT.has(entry));
6743
+ } catch {
6744
+ return false;
6745
+ }
6746
+ }
6747
+ function hasPriorSnapshot(cwd, outDir) {
6748
+ let parsed;
6749
+ try {
6750
+ parsed = JSON.parse(fs11.readFileSync(path12.join(cwd, outDir, ".codebase-snapshot"), "utf8"));
6751
+ } catch {
6752
+ return false;
6753
+ }
6754
+ if (parsed === null || typeof parsed !== "object") return false;
6755
+ return isOurGeneratorName(parsed.generator);
6756
+ }
6626
6757
 
6627
6758
  // src/index.ts
6628
6759
  var SCHEMA_VERSION = "3.0.0";
@@ -6704,13 +6835,13 @@ function buildImportResolver(files) {
6704
6835
  const relativePath = file.relativePath;
6705
6836
  remember(byPath, relativePath, relativePath);
6706
6837
  remember(byPathFolded, relativePath.toLowerCase(), relativePath);
6707
- const extension = path11.posix.extname(relativePath);
6838
+ const extension = path12.posix.extname(relativePath);
6708
6839
  if (!SOURCE_EXTENSIONS.has(extension)) continue;
6709
6840
  const stem = relativePath.slice(0, -extension.length);
6710
6841
  remember(byStem, stem, relativePath);
6711
6842
  remember(byStemFolded, stem.toLowerCase(), relativePath);
6712
- if (path11.posix.basename(stem) === "index") {
6713
- const directory = path11.posix.dirname(stem);
6843
+ if (path12.posix.basename(stem) === "index") {
6844
+ const directory = path12.posix.dirname(stem);
6714
6845
  remember(byStem, directory, relativePath);
6715
6846
  remember(byStemFolded, directory.toLowerCase(), relativePath);
6716
6847
  }
@@ -6719,8 +6850,8 @@ function buildImportResolver(files) {
6719
6850
  }
6720
6851
  function resolveSpecifier(fromFile, specifier, resolver) {
6721
6852
  if (!RELATIVE_SPECIFIER.test(specifier)) return null;
6722
- const joined = path11.posix.normalize(
6723
- path11.posix.join(path11.posix.dirname(fromFile), specifier)
6853
+ const joined = path12.posix.normalize(
6854
+ path12.posix.join(path12.posix.dirname(fromFile), specifier)
6724
6855
  );
6725
6856
  const exact = (key) => resolver.byPath.get(key) ?? resolver.byPathFolded.get(key.toLowerCase()) ?? null;
6726
6857
  const direct = exact(joined);
@@ -6813,11 +6944,11 @@ function verifyInMemory(files, parts, maxPartBytes) {
6813
6944
  return errors;
6814
6945
  }
6815
6946
  function resolveSafeOutputDirs(config) {
6816
- const cwd = path11.resolve(config.cwd);
6817
- const outDir = path11.resolve(cwd, config.outDir);
6818
- const tmpDir = path11.resolve(cwd, config.tmpDir);
6819
- const root = path11.parse(cwd).root;
6820
- const contains = (parent, child) => child === parent || child.startsWith(`${parent}${path11.sep}`);
6947
+ const cwd = path12.resolve(config.cwd);
6948
+ const outDir = path12.resolve(cwd, config.outDir);
6949
+ const tmpDir = path12.resolve(cwd, config.tmpDir);
6950
+ const root = path12.parse(cwd).root;
6951
+ const contains = (parent, child) => child === parent || child.startsWith(`${parent}${path12.sep}`);
6821
6952
  const reject = (reason) => {
6822
6953
  throw new Error(
6823
6954
  `Refusing to generate: ${reason}. outDir=${outDir} tmpDir=${tmpDir} cwd=${cwd}`
@@ -6873,14 +7004,14 @@ function listEntries(dir) {
6873
7004
  const walk = (absolute, prefix) => {
6874
7005
  let children;
6875
7006
  try {
6876
- children = fs10.readdirSync(absolute, { withFileTypes: true });
7007
+ children = fs11.readdirSync(absolute, { withFileTypes: true });
6877
7008
  } catch {
6878
7009
  return;
6879
7010
  }
6880
7011
  for (const child of children) {
6881
- const relative6 = prefix === "" ? child.name : `${prefix}/${child.name}`;
6882
- entries.push(relative6);
6883
- if (child.isDirectory()) walk(path11.join(absolute, child.name), relative6);
7012
+ const relative7 = prefix === "" ? child.name : `${prefix}/${child.name}`;
7013
+ entries.push(relative7);
7014
+ if (child.isDirectory()) walk(path12.join(absolute, child.name), relative7);
6884
7015
  }
6885
7016
  };
6886
7017
  walk(dir, "");
@@ -6897,28 +7028,28 @@ function markerPayload(dir, state, id, createdAt) {
6897
7028
  pid: process.pid,
6898
7029
  entries: listEntries(dir)
6899
7030
  };
6900
- const manifestPath = path11.join(dir, "manifest.json");
6901
- if (fs10.existsSync(manifestPath)) {
6902
- marker2.manifestSha256 = sha256Hex2(fs10.readFileSync(manifestPath));
7031
+ const manifestPath = path12.join(dir, "manifest.json");
7032
+ if (fs11.existsSync(manifestPath)) {
7033
+ marker2.manifestSha256 = sha256Hex2(fs11.readFileSync(manifestPath));
6903
7034
  }
6904
7035
  return marker2;
6905
7036
  }
6906
7037
  function writeOwnershipMarker(dir, marker2) {
6907
- fs10.writeFileSync(
6908
- path11.join(dir, OWNERSHIP_MARKER),
7038
+ fs11.writeFileSync(
7039
+ path12.join(dir, OWNERSHIP_MARKER),
6909
7040
  `${JSON.stringify(marker2, null, 2)}
6910
7041
  `,
6911
7042
  "utf8"
6912
7043
  );
6913
7044
  }
6914
7045
  function readOwnershipMarker(dir) {
6915
- const markerPath = path11.join(dir, OWNERSHIP_MARKER);
6916
- if (!fs10.existsSync(markerPath)) {
7046
+ const markerPath = path12.join(dir, OWNERSHIP_MARKER);
7047
+ if (!fs11.existsSync(markerPath)) {
6917
7048
  return { marker: null, problem: "it holds no .codebase-snapshot marker" };
6918
7049
  }
6919
7050
  let raw;
6920
7051
  try {
6921
- raw = fs10.readFileSync(markerPath, "utf8");
7052
+ raw = fs11.readFileSync(markerPath, "utf8");
6922
7053
  } catch {
6923
7054
  return { marker: null, problem: "its .codebase-snapshot marker could not be read" };
6924
7055
  }
@@ -6986,11 +7117,11 @@ function assessSnapshotDirectory(dir) {
6986
7117
  marker: marker2
6987
7118
  };
6988
7119
  }
6989
- const manifestPath = path11.join(dir, "manifest.json");
6990
- if (fs10.existsSync(manifestPath)) {
7120
+ const manifestPath = path12.join(dir, "manifest.json");
7121
+ if (fs11.existsSync(manifestPath)) {
6991
7122
  let raw;
6992
7123
  try {
6993
- raw = fs10.readFileSync(manifestPath, "utf8");
7124
+ raw = fs11.readFileSync(manifestPath, "utf8");
6994
7125
  } catch {
6995
7126
  return {
6996
7127
  owned: false,
@@ -7072,13 +7203,13 @@ function containsNoFiles(dir) {
7072
7203
  const current = pending.pop();
7073
7204
  let entries;
7074
7205
  try {
7075
- entries = fs10.readdirSync(current, { withFileTypes: true });
7206
+ entries = fs11.readdirSync(current, { withFileTypes: true });
7076
7207
  } catch {
7077
7208
  return false;
7078
7209
  }
7079
7210
  for (const entry of entries) {
7080
7211
  if (entry.isDirectory()) {
7081
- pending.push(path11.join(current, entry.name));
7212
+ pending.push(path12.join(current, entry.name));
7082
7213
  } else if (!IGNORABLE_ENTRIES.has(entry.name)) {
7083
7214
  return false;
7084
7215
  }
@@ -7087,13 +7218,13 @@ function containsNoFiles(dir) {
7087
7218
  return true;
7088
7219
  }
7089
7220
  function assertReplaceable(target, label, secretRules) {
7090
- if (!fs10.existsSync(target)) return;
7091
- if (!fs10.statSync(target).isDirectory()) {
7221
+ if (!fs11.existsSync(target)) return;
7222
+ if (!fs11.statSync(target).isDirectory()) {
7092
7223
  throw new Error(
7093
7224
  `Refusing to write into ${label} ${target}: it exists but is not a directory.`
7094
7225
  );
7095
7226
  }
7096
- const entries = fs10.readdirSync(target);
7227
+ const entries = fs11.readdirSync(target);
7097
7228
  if (entries.length === 0) return;
7098
7229
  if (entries.every((name) => IGNORABLE_ENTRIES.has(name))) return;
7099
7230
  if (containsNoFiles(target)) return;
@@ -7113,18 +7244,18 @@ function assertReplaceable(target, label, secretRules) {
7113
7244
  );
7114
7245
  }
7115
7246
  function assertStagingRootUsable(tmpDir, secretRules) {
7116
- if (!fs10.existsSync(tmpDir)) return;
7117
- if (!fs10.statSync(tmpDir).isDirectory()) {
7247
+ if (!fs11.existsSync(tmpDir)) return;
7248
+ if (!fs11.statSync(tmpDir).isDirectory()) {
7118
7249
  throw new Error(`Refusing to write into tmpDir ${tmpDir}: it exists but is not a directory.`);
7119
7250
  }
7120
- const entries = fs10.readdirSync(tmpDir);
7251
+ const entries = fs11.readdirSync(tmpDir);
7121
7252
  if (entries.length === 0) return;
7122
7253
  const foreign = entries.filter((name) => {
7123
- const full = path11.join(tmpDir, name);
7254
+ const full = path12.join(tmpDir, name);
7124
7255
  if (!name.startsWith(RUN_DIR_PREFIX)) return true;
7125
7256
  let stats;
7126
7257
  try {
7127
- stats = fs10.statSync(full);
7258
+ stats = fs11.statSync(full);
7128
7259
  } catch {
7129
7260
  return true;
7130
7261
  }
@@ -7132,7 +7263,7 @@ function assertStagingRootUsable(tmpDir, secretRules) {
7132
7263
  const { marker: marker2 } = readOwnershipMarker(full);
7133
7264
  if (marker2) return false;
7134
7265
  try {
7135
- return fs10.readdirSync(full).length > 0;
7266
+ return fs11.readdirSync(full).length > 0;
7136
7267
  } catch {
7137
7268
  return true;
7138
7269
  }
@@ -7147,13 +7278,13 @@ function assertStagingRootUsable(tmpDir, secretRules) {
7147
7278
  function cleanStaleRunDirs(tmpDir) {
7148
7279
  let entries;
7149
7280
  try {
7150
- entries = fs10.readdirSync(tmpDir);
7281
+ entries = fs11.readdirSync(tmpDir);
7151
7282
  } catch {
7152
7283
  return;
7153
7284
  }
7154
7285
  for (const name of entries) {
7155
7286
  if (!name.startsWith(RUN_DIR_PREFIX)) continue;
7156
- const full = path11.join(tmpDir, name);
7287
+ const full = path12.join(tmpDir, name);
7157
7288
  const { marker: marker2 } = readOwnershipMarker(full);
7158
7289
  if (marker2) {
7159
7290
  const created = Date.parse(marker2.createdAt);
@@ -7161,33 +7292,33 @@ function cleanStaleRunDirs(tmpDir) {
7161
7292
  const abandoned = typeof marker2.pid === "number" && !isProcessAlive(marker2.pid);
7162
7293
  if (aged || abandoned) {
7163
7294
  try {
7164
- fs10.rmSync(full, { recursive: true, force: true });
7295
+ fs11.rmSync(full, { recursive: true, force: true });
7165
7296
  } catch {
7166
7297
  }
7167
7298
  }
7168
7299
  continue;
7169
7300
  }
7170
7301
  try {
7171
- if (fs10.readdirSync(full).length === 0) fs10.rmSync(full, { recursive: true, force: true });
7302
+ if (fs11.readdirSync(full).length === 0) fs11.rmSync(full, { recursive: true, force: true });
7172
7303
  } catch {
7173
7304
  }
7174
7305
  }
7175
7306
  }
7176
7307
  function removeIfEmpty(dir) {
7177
7308
  try {
7178
- if (fs10.readdirSync(dir).length === 0) fs10.rmSync(dir, { recursive: true, force: true });
7309
+ if (fs11.readdirSync(dir).length === 0) fs11.rmSync(dir, { recursive: true, force: true });
7179
7310
  } catch {
7180
7311
  }
7181
7312
  }
7182
7313
  function ensureParentDirectory(target, label) {
7183
- const parent = path11.dirname(target);
7184
- if (fs10.existsSync(parent) && !fs10.statSync(parent).isDirectory()) {
7314
+ const parent = path12.dirname(target);
7315
+ if (fs11.existsSync(parent) && !fs11.statSync(parent).isDirectory()) {
7185
7316
  throw new Error(
7186
7317
  `Refusing to create ${label} ${target}: ${parent} exists but is not a directory.`
7187
7318
  );
7188
7319
  }
7189
7320
  try {
7190
- fs10.mkdirSync(parent, { recursive: true });
7321
+ fs11.mkdirSync(parent, { recursive: true });
7191
7322
  } catch (err) {
7192
7323
  throw new Error(
7193
7324
  `Refusing to create ${label} ${target}: its parent ${parent} could not be created (${err.message}).`
@@ -7196,7 +7327,7 @@ function ensureParentDirectory(target, label) {
7196
7327
  }
7197
7328
  function acquireRunLock(stagingRoot) {
7198
7329
  const key = lockKeyFor(stagingRoot);
7199
- const lockPath = path11.join(os.tmpdir(), `codebase-snapshot-${key}.lock`);
7330
+ const lockPath = path12.join(os.tmpdir(), `codebase-snapshot-${key}.lock`);
7200
7331
  const payload = `${JSON.stringify({
7201
7332
  pid: process.pid,
7202
7333
  stagingRoot,
@@ -7205,22 +7336,22 @@ function acquireRunLock(stagingRoot) {
7205
7336
  `;
7206
7337
  for (let attempt = 0; attempt < 2; attempt += 1) {
7207
7338
  try {
7208
- const handle = fs10.openSync(lockPath, "wx");
7339
+ const handle = fs11.openSync(lockPath, "wx");
7209
7340
  try {
7210
- fs10.writeSync(handle, payload);
7341
+ fs11.writeSync(handle, payload);
7211
7342
  } finally {
7212
- fs10.closeSync(handle);
7343
+ fs11.closeSync(handle);
7213
7344
  }
7214
7345
  return () => {
7215
7346
  try {
7216
- fs10.rmSync(lockPath, { force: true });
7347
+ fs11.rmSync(lockPath, { force: true });
7217
7348
  } catch {
7218
7349
  }
7219
7350
  };
7220
7351
  } catch (err) {
7221
7352
  if (err.code !== "EEXIST") throw err;
7222
7353
  if (attempt === 0 && lockOwnerIsGone(lockPath)) {
7223
- fs10.rmSync(lockPath, { force: true });
7354
+ fs11.rmSync(lockPath, { force: true });
7224
7355
  continue;
7225
7356
  }
7226
7357
  throw new Error(
@@ -7233,7 +7364,7 @@ function acquireRunLock(stagingRoot) {
7233
7364
  function lockKeyFor(stagingRoot) {
7234
7365
  let key = stagingRoot;
7235
7366
  try {
7236
- key = fs10.realpathSync(stagingRoot);
7367
+ key = fs11.realpathSync(stagingRoot);
7237
7368
  } catch {
7238
7369
  }
7239
7370
  if (process.platform === "win32" || process.platform === "darwin") key = key.toLowerCase();
@@ -7242,7 +7373,7 @@ function lockKeyFor(stagingRoot) {
7242
7373
  function lockOwnerIsGone(lockPath) {
7243
7374
  let raw;
7244
7375
  try {
7245
- raw = fs10.readFileSync(lockPath, "utf8");
7376
+ raw = fs11.readFileSync(lockPath, "utf8");
7246
7377
  } catch {
7247
7378
  return false;
7248
7379
  }
@@ -7262,12 +7393,12 @@ function lockOwnerIsGone(lockPath) {
7262
7393
  }
7263
7394
  function writeManifestAtomically(manifestPath, manifest) {
7264
7395
  const tempPath = `${manifestPath}.${process.pid}.tmp`;
7265
- fs10.writeFileSync(tempPath, JSON.stringify(manifest, null, 2), "utf8");
7396
+ fs11.writeFileSync(tempPath, JSON.stringify(manifest, null, 2), "utf8");
7266
7397
  try {
7267
- fs10.renameSync(tempPath, manifestPath);
7398
+ fs11.renameSync(tempPath, manifestPath);
7268
7399
  } catch (err) {
7269
7400
  try {
7270
- fs10.rmSync(tempPath, { force: true });
7401
+ fs11.rmSync(tempPath, { force: true });
7271
7402
  } catch {
7272
7403
  }
7273
7404
  throw err;
@@ -7290,31 +7421,31 @@ function swapIntoPlace(stagingDir, outDir) {
7290
7421
  }
7291
7422
  function swapOnce(stagingDir, outDir) {
7292
7423
  const backupDir = `${outDir}.backup-${process.pid}-${Date.now()}`;
7293
- const hadPrevious = fs10.existsSync(outDir);
7294
- if (hadPrevious) fs10.renameSync(outDir, backupDir);
7424
+ const hadPrevious = fs11.existsSync(outDir);
7425
+ if (hadPrevious) fs11.renameSync(outDir, backupDir);
7295
7426
  try {
7296
7427
  try {
7297
- fs10.renameSync(stagingDir, outDir);
7428
+ fs11.renameSync(stagingDir, outDir);
7298
7429
  } catch (err) {
7299
7430
  if (err.code !== "EXDEV") throw err;
7300
7431
  const partialDir = `${outDir}.partial-${process.pid}-${Date.now()}`;
7301
7432
  try {
7302
- fs10.cpSync(stagingDir, partialDir, { recursive: true });
7303
- fs10.renameSync(partialDir, outDir);
7304
- fs10.rmSync(stagingDir, { recursive: true, force: true });
7433
+ fs11.cpSync(stagingDir, partialDir, { recursive: true });
7434
+ fs11.renameSync(partialDir, outDir);
7435
+ fs11.rmSync(stagingDir, { recursive: true, force: true });
7305
7436
  } catch (copyErr) {
7306
7437
  try {
7307
- fs10.rmSync(partialDir, { recursive: true, force: true });
7438
+ fs11.rmSync(partialDir, { recursive: true, force: true });
7308
7439
  } catch {
7309
7440
  }
7310
7441
  throw copyErr;
7311
7442
  }
7312
7443
  }
7313
- if (hadPrevious) fs10.rmSync(backupDir, { recursive: true, force: true });
7444
+ if (hadPrevious) fs11.rmSync(backupDir, { recursive: true, force: true });
7314
7445
  } catch (err) {
7315
- if (hadPrevious && fs10.existsSync(backupDir)) {
7316
- if (fs10.existsSync(outDir)) fs10.rmSync(outDir, { recursive: true, force: true });
7317
- fs10.renameSync(backupDir, outDir);
7446
+ if (hadPrevious && fs11.existsSync(backupDir)) {
7447
+ if (fs11.existsSync(outDir)) fs11.rmSync(outDir, { recursive: true, force: true });
7448
+ fs11.renameSync(backupDir, outDir);
7318
7449
  }
7319
7450
  throw err;
7320
7451
  }
@@ -7409,8 +7540,8 @@ function generateSnapshot(configInput, options) {
7409
7540
  // first run cannot have marked them yet, so the name alone keeps a run from recording its own
7410
7541
  // output — but only at these exact paths, not at every directory that shares the name.
7411
7542
  toolOwnedPaths: [config.outDir, config.tmpDir].map(
7412
- (directory) => path11.relative(config.cwd, path11.resolve(config.cwd, directory)).replace(/\\/g, "/")
7413
- ).filter((relative6) => relative6.length > 0 && !relative6.startsWith(".."))
7543
+ (directory) => path12.relative(config.cwd, path12.resolve(config.cwd, directory)).replace(/\\/g, "/")
7544
+ ).filter((relative7) => relative7.length > 0 && !relative7.startsWith(".."))
7414
7545
  });
7415
7546
  const files = [];
7416
7547
  const monolithBodies = /* @__PURE__ */ new Map();
@@ -7430,12 +7561,12 @@ function generateSnapshot(configInput, options) {
7430
7561
  let totalSizeBytes = 0;
7431
7562
  let largeFileCount = 0;
7432
7563
  for (const relativePath of collection.files) {
7433
- const absolutePath = path11.join(config.cwd, relativePath);
7564
+ const absolutePath = path12.join(config.cwd, relativePath);
7434
7565
  let stats;
7435
7566
  try {
7436
- stats = fs10.statSync(absolutePath);
7567
+ stats = fs11.statSync(absolutePath);
7437
7568
  if (!stats.isFile()) {
7438
- const nestedRepository = stats.isDirectory() && fs10.existsSync(path11.join(absolutePath, ".git"));
7569
+ const nestedRepository = stats.isDirectory() && fs11.existsSync(path12.join(absolutePath, ".git"));
7439
7570
  skipped.push(
7440
7571
  nestedRepository ? {
7441
7572
  path: relativePath,
@@ -7455,7 +7586,7 @@ function generateSnapshot(configInput, options) {
7455
7586
  }
7456
7587
  let buffer;
7457
7588
  try {
7458
- buffer = fs10.readFileSync(absolutePath);
7589
+ buffer = fs11.readFileSync(absolutePath);
7459
7590
  } catch {
7460
7591
  skipped.push({ path: relativePath, reason: "read-error" });
7461
7592
  continue;
@@ -7785,12 +7916,12 @@ function generateSnapshot(configInput, options) {
7785
7916
  let stagingDir = null;
7786
7917
  try {
7787
7918
  ensureParentDirectory(outDir, "outDir");
7788
- fs10.mkdirSync(tmpDir, { recursive: true });
7919
+ fs11.mkdirSync(tmpDir, { recursive: true });
7789
7920
  cleanStaleRunDirs(tmpDir);
7790
7921
  const snapshotId = crypto.randomUUID();
7791
7922
  const createdAt = (/* @__PURE__ */ new Date()).toISOString();
7792
- stagingDir = path11.join(tmpDir, `${RUN_DIR_PREFIX}${process.pid}-${snapshotId.slice(0, 8)}`);
7793
- fs10.mkdirSync(stagingDir);
7923
+ stagingDir = path12.join(tmpDir, `${RUN_DIR_PREFIX}${process.pid}-${snapshotId.slice(0, 8)}`);
7924
+ fs11.mkdirSync(stagingDir);
7794
7925
  writeOwnershipMarker(stagingDir, {
7795
7926
  generator: GENERATOR_NAME,
7796
7927
  version: GENERATOR_VERSION,
@@ -7800,41 +7931,41 @@ function generateSnapshot(configInput, options) {
7800
7931
  id: snapshotId,
7801
7932
  pid: process.pid
7802
7933
  });
7803
- const partsDir = path11.join(stagingDir, "parts");
7804
- const reportsDir = path11.join(stagingDir, "reports");
7805
- if (artifacts.parts) fs10.mkdirSync(partsDir, { recursive: true });
7934
+ const partsDir = path12.join(stagingDir, "parts");
7935
+ const reportsDir = path12.join(stagingDir, "reports");
7936
+ if (artifacts.parts) fs11.mkdirSync(partsDir, { recursive: true });
7806
7937
  if (artifacts.navigation || artifacts.projectTree || artifacts.audit) {
7807
- fs10.mkdirSync(reportsDir, { recursive: true });
7938
+ fs11.mkdirSync(reportsDir, { recursive: true });
7808
7939
  }
7809
7940
  if (artifacts.parts) {
7810
7941
  for (const part of parts) {
7811
- fs10.writeFileSync(path11.join(partsDir, part.filename), part.markdown, "utf8");
7942
+ fs11.writeFileSync(path12.join(partsDir, part.filename), part.markdown, "utf8");
7812
7943
  }
7813
7944
  }
7814
7945
  if (projectTree !== null) {
7815
- fs10.writeFileSync(path11.join(reportsDir, "project-tree.md"), projectTree, "utf8");
7946
+ fs11.writeFileSync(path12.join(reportsDir, "project-tree.md"), projectTree, "utf8");
7816
7947
  }
7817
7948
  if (artifacts.navigation) {
7818
- fs10.writeFileSync(path11.join(reportsDir, "navigation.md"), generateNavigation(manifest), "utf8");
7949
+ fs11.writeFileSync(path12.join(reportsDir, "navigation.md"), generateNavigation(manifest), "utf8");
7819
7950
  }
7820
7951
  if (artifacts.audit) {
7821
- fs10.writeFileSync(
7822
- path11.join(reportsDir, "audit.md"),
7952
+ fs11.writeFileSync(
7953
+ path12.join(reportsDir, "audit.md"),
7823
7954
  generateAuditReport(manifest, secretFindings, config.secretRules),
7824
7955
  "utf8"
7825
7956
  );
7826
7957
  }
7827
7958
  if (artifacts.readme) {
7828
- fs10.writeFileSync(path11.join(stagingDir, "README.md"), generateReadme(manifest, artifacts), "utf8");
7959
+ fs11.writeFileSync(path12.join(stagingDir, "README.md"), generateReadme(manifest, artifacts), "utf8");
7829
7960
  }
7830
7961
  if (artifacts.manifest) {
7831
- fs10.writeFileSync(path11.join(stagingDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf8");
7962
+ fs11.writeFileSync(path12.join(stagingDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf8");
7832
7963
  }
7833
7964
  if (monolithJson !== null) {
7834
- fs10.writeFileSync(path11.join(stagingDir, "full-codebase.json"), monolithJson, "utf8");
7965
+ fs11.writeFileSync(path12.join(stagingDir, "full-codebase.json"), monolithJson, "utf8");
7835
7966
  }
7836
7967
  if (monolithMarkdown !== null) {
7837
- fs10.writeFileSync(path11.join(stagingDir, "full-codebase.md"), monolithMarkdown, "utf8");
7968
+ fs11.writeFileSync(path12.join(stagingDir, "full-codebase.md"), monolithMarkdown, "utf8");
7838
7969
  }
7839
7970
  writeOwnershipMarker(stagingDir, markerPayload(stagingDir, "complete", snapshotId, createdAt));
7840
7971
  swapIntoPlace(stagingDir, outDir);
@@ -7843,7 +7974,7 @@ function generateSnapshot(configInput, options) {
7843
7974
  const pendingHook = runAfterGenerate(config, manifest);
7844
7975
  const shipHookState = () => {
7845
7976
  if (artifacts.manifest && manifest.warnings.length !== warningsBeforeHook) {
7846
- writeManifestAtomically(path11.join(outDir, "manifest.json"), manifest);
7977
+ writeManifestAtomically(path12.join(outDir, "manifest.json"), manifest);
7847
7978
  }
7848
7979
  writeOwnershipMarker(outDir, markerPayload(outDir, "complete", snapshotId, createdAt));
7849
7980
  };
@@ -7866,7 +7997,7 @@ function generateSnapshot(configInput, options) {
7866
7997
  } catch (err) {
7867
7998
  if (stagingDir !== null) {
7868
7999
  try {
7869
- fs10.rmSync(stagingDir, { recursive: true, force: true });
8000
+ fs11.rmSync(stagingDir, { recursive: true, force: true });
7870
8001
  } catch {
7871
8002
  }
7872
8003
  }
@@ -7880,6 +8011,6 @@ async function generateSnapshotAsync(configInput, options) {
7880
8011
  return generateSnapshot(configInput, options);
7881
8012
  }
7882
8013
 
7883
- export { ARTIFACT_NAMES, DEFAULT_ANSWERS, DEFAULT_SECRET_PATTERNS, GENERATOR_NAME, GENERATOR_VERSION, LANGUAGE_PROFILES, MIN_MAX_PART_BYTES, PRESETS, PromptCancelled, SCHEMA_VERSION, anchorForPath, appendGitignoreEntries, assertReplaceable, assertUsableConfigInput, collectFiles, compareManifests, countInvalidUtf8Bytes, countLines, decodeText, defineConfig, describeManifestShapeProblem, detectLanguage, detectPreset, escapeControls, explainPath, fenceForContent, fenceForText, formatNumber, generateProjectTree, generateSnapshot, generateSnapshotAsync, getPreset, gitignorePath, globToRegExp, isBinaryBuffer, isBinaryExtension, isBinaryFile, isDiscoveryMode, isInteractive, isResolvedConfig, isSensitivePath, listPresetNames, loadConfigFile, loadConfigFileDetailed, loadManifest, markResolvedConfig, matchRule, matchesExclude, normalizeResolvedConfig, parseByteSize, partitionFiles, profileForPath, renderConfig, renderDiffText, renderExplainText, renumberParts, resolveConfig, resolveSafeOutputDirs, runInit, scanAndRedactSecrets, stripJsonComments, summarizeJson, summarizeMarkdown, summarizeTypeScriptSource, summarizeWithProfile, supportsTypeScriptImports, truncatePreview, truncateToBytes, unknownPresetMessage, validateSnapshot };
7884
- //# sourceMappingURL=chunk-FJ4RQYY2.js.map
7885
- //# sourceMappingURL=chunk-FJ4RQYY2.js.map
8014
+ export { ARTIFACT_NAMES, DEFAULT_ANSWERS, DEFAULT_SECRET_PATTERNS, GENERATOR_NAME, GENERATOR_VERSION, LANGUAGE_PROFILES, MIN_MAX_PART_BYTES, PRESETS, PromptCancelled, SCHEMA_VERSION, anchorForPath, appendGitignoreEntries, assertReplaceable, assertUsableConfigInput, collectFiles, compareManifests, countInvalidUtf8Bytes, countLines, decodeText, defaultIO, defineConfig, describeManifestShapeProblem, detectLanguage, detectPreset, escapeControls, explainPath, fenceForContent, fenceForText, findConfigFile, firstRunMessage, formatNumber, generateProjectTree, generateSnapshot, generateSnapshotAsync, getPreset, gitignorePath, globToRegExp, hasPriorSnapshot, isBinaryBuffer, isBinaryExtension, isBinaryFile, isDiscoveryMode, isEffectivelyEmpty, isInteractive, isResolvedConfig, isSensitivePath, listPresetNames, loadConfigFile, loadConfigFileDetailed, loadManifest, markResolvedConfig, matchRule, matchesExclude, normalizeResolvedConfig, parseByteSize, partitionFiles, planFirstRun, profileForPath, renderConfig, renderDiffText, renderExplainText, renumberParts, resolveConfig, resolveSafeOutputDirs, runInit, scanAndRedactSecrets, stripJsonComments, summarizeJson, summarizeMarkdown, summarizeTypeScriptSource, summarizeWithProfile, supportsTypeScriptImports, truncatePreview, truncateToBytes, unknownPresetMessage, validateSnapshot };
8015
+ //# sourceMappingURL=chunk-FCWD4LY7.js.map
8016
+ //# sourceMappingURL=chunk-FCWD4LY7.js.map