codebase-snapshot 0.9.1 → 0.9.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  var crypto = require('crypto');
4
4
  var async_hooks = require('async_hooks');
5
- var fs10 = require('fs');
5
+ var fs11 = require('fs');
6
6
  var os = require('os');
7
- var path11 = require('path');
7
+ var path12 = require('path');
8
8
  var ts = require('typescript');
9
9
  var module$1 = require('module');
10
10
  var url = require('url');
@@ -32,9 +32,9 @@ function _interopNamespace(e) {
32
32
  }
33
33
 
34
34
  var crypto__namespace = /*#__PURE__*/_interopNamespace(crypto);
35
- var fs10__namespace = /*#__PURE__*/_interopNamespace(fs10);
35
+ var fs11__namespace = /*#__PURE__*/_interopNamespace(fs11);
36
36
  var os__namespace = /*#__PURE__*/_interopNamespace(os);
37
- var path11__namespace = /*#__PURE__*/_interopNamespace(path11);
37
+ var path12__namespace = /*#__PURE__*/_interopNamespace(path12);
38
38
  var ts__default = /*#__PURE__*/_interopDefault(ts);
39
39
  var childProcess__namespace = /*#__PURE__*/_interopNamespace(childProcess);
40
40
  var readline__namespace = /*#__PURE__*/_interopNamespace(readline);
@@ -263,9 +263,9 @@ var JAVA_METHOD = /^\s+(?:public|private|protected)\s+(?:static\s+)?(?:final\s+)
263
263
  var jvmProfile = {
264
264
  id: "jvm",
265
265
  extensions: [".java", ".kt", ".kts", ".scala"],
266
- summarize(path12, content) {
266
+ summarize(path13, content) {
267
267
  const summary = emptySummary();
268
- const lower = path12.toLowerCase();
268
+ const lower = path13.toLowerCase();
269
269
  const isJava = lower.endsWith(".java");
270
270
  const isKotlin = lower.endsWith(".kt") || lower.endsWith(".kts");
271
271
  eachLine(content, (line) => {
@@ -718,16 +718,16 @@ function summarizeTypeScriptSource(relativePath, content) {
718
718
  // src/ast/profiles/sfc.ts
719
719
  var SCRIPT_BLOCK = /<script[^>]*>([\s\S]*?)<\/script>/gi;
720
720
  var ASTRO_FRONTMATTER = /^\uFEFF?---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*(?=\r?\n|$)/;
721
- function componentName(path12) {
722
- const base = path12.split("/").pop() ?? path12;
721
+ function componentName(path13) {
722
+ const base = path13.split("/").pop() ?? path13;
723
723
  return base.replace(/\.[^.]+$/, "");
724
724
  }
725
725
  var sfcProfile = {
726
726
  id: "sfc",
727
727
  extensions: [".vue", ".svelte", ".astro"],
728
- summarize(path12, content) {
728
+ summarize(path13, content) {
729
729
  let script = "";
730
- if (path12.toLowerCase().endsWith(".astro")) {
730
+ if (path13.toLowerCase().endsWith(".astro")) {
731
731
  const frontmatter = ASTRO_FRONTMATTER.exec(content);
732
732
  if (frontmatter) script += `${frontmatter[1]}
733
733
  `;
@@ -736,7 +736,7 @@ var sfcProfile = {
736
736
  script += `${match[1]}
737
737
  `;
738
738
  }
739
- const name = componentName(path12);
739
+ const name = componentName(path13);
740
740
  if (!script.trim()) {
741
741
  const summary2 = emptySummary();
742
742
  if (name) {
@@ -770,15 +770,15 @@ for (const profile of LANGUAGE_PROFILES) {
770
770
  BY_EXTENSION.set(extension.toLowerCase(), profile);
771
771
  }
772
772
  }
773
- function profileForPath(path12) {
774
- const lower = path12.toLowerCase();
773
+ function profileForPath(path13) {
774
+ const lower = path13.toLowerCase();
775
775
  const dot = lower.lastIndexOf(".");
776
776
  if (dot === -1) return null;
777
777
  return BY_EXTENSION.get(lower.slice(dot)) ?? null;
778
778
  }
779
- function summarizeWithProfile(path12, content) {
780
- const profile = profileForPath(path12);
781
- return profile ? profile.summarize(path12, content) : null;
779
+ function summarizeWithProfile(path13, content) {
780
+ const profile = profileForPath(path13);
781
+ return profile ? profile.summarize(path13, content) : null;
782
782
  }
783
783
 
784
784
  // src/ast/structured.ts
@@ -949,15 +949,15 @@ var SKIPPED_WALK_DIRS = /* @__PURE__ */ new Set([
949
949
  ]);
950
950
  function readSmallFile(fullPath) {
951
951
  try {
952
- const stats = fs10__namespace.statSync(fullPath);
952
+ const stats = fs11__namespace.statSync(fullPath);
953
953
  if (!stats.isFile() || stats.size > MANIFEST_MAX_BYTES) return null;
954
- return fs10__namespace.readFileSync(fullPath, "utf8");
954
+ return fs11__namespace.readFileSync(fullPath, "utf8");
955
955
  } catch {
956
956
  return null;
957
957
  }
958
958
  }
959
959
  function readManifest(cwd, name) {
960
- return readSmallFile(path11__namespace.join(cwd, name));
960
+ return readSmallFile(path12__namespace.join(cwd, name));
961
961
  }
962
962
  function dependenciesFromPackageJson(raw) {
963
963
  const names = /* @__PURE__ */ new Set();
@@ -1017,7 +1017,7 @@ function pnpmWorkspacePatterns(raw) {
1017
1017
  function childDirectories(cwd, parent) {
1018
1018
  let entries;
1019
1019
  try {
1020
- entries = fs10__namespace.readdirSync(path11__namespace.join(cwd, parent), { withFileTypes: true });
1020
+ entries = fs11__namespace.readdirSync(path12__namespace.join(cwd, parent), { withFileTypes: true });
1021
1021
  } catch {
1022
1022
  return [];
1023
1023
  }
@@ -1052,13 +1052,13 @@ function workspacePackageDirs(cwd, patterns) {
1052
1052
  function scanWorkspacePackages(cwd, patterns) {
1053
1053
  const files = /* @__PURE__ */ new Set();
1054
1054
  const dependencies = /* @__PURE__ */ new Set();
1055
- for (const relative6 of workspacePackageDirs(cwd, patterns)) {
1056
- const full = path11__namespace.join(cwd, relative6);
1055
+ for (const relative7 of workspacePackageDirs(cwd, patterns)) {
1056
+ const full = path12__namespace.join(cwd, relative7);
1057
1057
  try {
1058
- for (const entry of fs10__namespace.readdirSync(full, { withFileTypes: true })) files.add(entry.name);
1058
+ for (const entry of fs11__namespace.readdirSync(full, { withFileTypes: true })) files.add(entry.name);
1059
1059
  } catch {
1060
1060
  }
1061
- const raw = readSmallFile(path11__namespace.join(full, "package.json"));
1061
+ const raw = readSmallFile(path12__namespace.join(full, "package.json"));
1062
1062
  if (raw !== null) {
1063
1063
  for (const name of dependenciesFromPackageJson(raw)) dependencies.add(name);
1064
1064
  }
@@ -1075,7 +1075,7 @@ function countMarkdownFiles(cwd) {
1075
1075
  directoriesSeen += 1;
1076
1076
  let entries;
1077
1077
  try {
1078
- entries = fs10__namespace.readdirSync(current.dir, { withFileTypes: true });
1078
+ entries = fs11__namespace.readdirSync(current.dir, { withFileTypes: true });
1079
1079
  } catch {
1080
1080
  continue;
1081
1081
  }
@@ -1086,7 +1086,7 @@ function countMarkdownFiles(cwd) {
1086
1086
  }
1087
1087
  if (!entry.isDirectory() || current.depth >= 2) continue;
1088
1088
  if (entry.name.startsWith(".") || SKIPPED_WALK_DIRS.has(entry.name)) continue;
1089
- queue.push({ dir: path11__namespace.join(current.dir, entry.name), depth: current.depth + 1 });
1089
+ queue.push({ dir: path12__namespace.join(current.dir, entry.name), depth: current.depth + 1 });
1090
1090
  }
1091
1091
  }
1092
1092
  return count;
@@ -1095,7 +1095,7 @@ function buildPresetContext(cwd) {
1095
1095
  const files = /* @__PURE__ */ new Set();
1096
1096
  const dirs = /* @__PURE__ */ new Set();
1097
1097
  try {
1098
- for (const entry of fs10__namespace.readdirSync(cwd, { withFileTypes: true })) {
1098
+ for (const entry of fs11__namespace.readdirSync(cwd, { withFileTypes: true })) {
1099
1099
  if (entry.isDirectory()) dirs.add(entry.name);
1100
1100
  else if (entry.isFile()) files.add(entry.name);
1101
1101
  }
@@ -1112,7 +1112,7 @@ function buildPresetContext(cwd) {
1112
1112
  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;
1113
1113
  let workspace = { files: /* @__PURE__ */ new Set(), dependencies: /* @__PURE__ */ new Set() };
1114
1114
  if (isWorkspace) {
1115
- const pnpmRaw = files.has("pnpm-workspace.yaml") ? readSmallFile(path11__namespace.join(cwd, "pnpm-workspace.yaml")) : null;
1115
+ const pnpmRaw = files.has("pnpm-workspace.yaml") ? readSmallFile(path12__namespace.join(cwd, "pnpm-workspace.yaml")) : null;
1116
1116
  const declared = pnpmRaw === null ? [] : pnpmWorkspacePatterns(pnpmRaw);
1117
1117
  const patterns = declared.length > 0 ? declared : declaredWorkspaces.length > 0 ? declaredWorkspaces : DEFAULT_WORKSPACE_PATTERNS;
1118
1118
  workspace = scanWorkspacePackages(cwd, patterns);
@@ -1130,25 +1130,41 @@ function buildPresetContext(cwd) {
1130
1130
  markdownCount: files.has("package.json") ? 0 : countMarkdownFiles(cwd)
1131
1131
  };
1132
1132
  }
1133
- function hasFile(context, name) {
1134
- return context.files.has(name);
1133
+
1134
+ // src/presets/evidence.ts
1135
+ function fileEvidence(context, name) {
1136
+ return context.files.has(name) ? name : null;
1135
1137
  }
1136
- function hasFileStartingWith(context, prefix) {
1138
+ function fileStartingWithEvidence(context, prefix) {
1137
1139
  for (const file of context.files) {
1138
- if (file.startsWith(prefix)) return true;
1140
+ if (file.startsWith(prefix)) return file;
1139
1141
  }
1140
1142
  for (const file of context.workspaceFiles) {
1141
- if (file.startsWith(prefix)) return true;
1143
+ if (file.startsWith(prefix)) return file;
1142
1144
  }
1143
- return false;
1145
+ return null;
1146
+ }
1147
+ function dependencyEvidence(context, ...names) {
1148
+ for (const name of names) {
1149
+ if (context.dependencies.has(name)) return `depends on ${name}`;
1150
+ }
1151
+ return null;
1144
1152
  }
1145
- function manifestContains(context, manifest, needle) {
1153
+ function manifestEvidence(context, manifest, needle) {
1146
1154
  const content = context.manifests[manifest];
1147
- if (!content) return false;
1148
- return content.toLowerCase().includes(needle.toLowerCase());
1155
+ if (!content) return null;
1156
+ if (!content.toLowerCase().includes(needle.toLowerCase())) return null;
1157
+ return `${needle} in ${manifest}`;
1158
+ }
1159
+ function suffixEvidence(context, suffix) {
1160
+ for (const file of context.files) {
1161
+ if (file.endsWith(suffix)) return file;
1162
+ }
1163
+ return null;
1149
1164
  }
1150
- function hasDependency(context, ...names) {
1151
- return names.some((name) => context.dependencies.has(name));
1165
+ function markdownEvidence(context) {
1166
+ if (context.markdownCount < 3 || context.files.has("package.json")) return null;
1167
+ return `${context.markdownCount} markdown files, no package.json`;
1152
1168
  }
1153
1169
 
1154
1170
  // src/presets/helpers.ts
@@ -1356,7 +1372,7 @@ var JS_PRESETS = [
1356
1372
  {
1357
1373
  name: "nextjs",
1358
1374
  priority: 100,
1359
- detect: (context) => hasFileStartingWith(context, "next.config") || hasDependency(context, "next"),
1375
+ detect: (context) => fileStartingWithEvidence(context, "next.config") ?? dependencyEvidence(context, "next"),
1360
1376
  layers: NEXTJS_LAYERS,
1361
1377
  kinds: NEXTJS_KINDS,
1362
1378
  exclude: NEXT_LIKE_EXCLUDES,
@@ -1365,7 +1381,7 @@ var JS_PRESETS = [
1365
1381
  {
1366
1382
  name: "nuxt",
1367
1383
  priority: 98,
1368
- detect: (context) => hasFileStartingWith(context, "nuxt.config") || hasDependency(context, "nuxt", "nuxt3"),
1384
+ detect: (context) => fileStartingWithEvidence(context, "nuxt.config") ?? dependencyEvidence(context, "nuxt", "nuxt3"),
1369
1385
  layers: [
1370
1386
  layer("pages", "pages/", "src/pages/"),
1371
1387
  layer("components", "components/", "src/components/"),
@@ -1389,7 +1405,7 @@ var JS_PRESETS = [
1389
1405
  {
1390
1406
  name: "sveltekit",
1391
1407
  priority: 96,
1392
- detect: (context) => hasFileStartingWith(context, "svelte.config") || hasDependency(context, "@sveltejs/kit", "svelte"),
1408
+ detect: (context) => fileStartingWithEvidence(context, "svelte.config") ?? dependencyEvidence(context, "@sveltejs/kit", "svelte"),
1393
1409
  layers: [
1394
1410
  layer("routes", "src/routes/", "routes/"),
1395
1411
  layer("lib", "src/lib/", "lib/"),
@@ -1410,7 +1426,7 @@ var JS_PRESETS = [
1410
1426
  {
1411
1427
  name: "remix",
1412
1428
  priority: 94,
1413
- detect: (context) => hasDependency(context, "@remix-run/react", "@remix-run/node", "@remix-run/dev"),
1429
+ detect: (context) => dependencyEvidence(context, "@remix-run/react", "@remix-run/node", "@remix-run/dev"),
1414
1430
  layers: [
1415
1431
  layer("routes", "app/routes/", "routes/"),
1416
1432
  layer("components", "app/components/"),
@@ -1424,7 +1440,7 @@ var JS_PRESETS = [
1424
1440
  {
1425
1441
  name: "astro",
1426
1442
  priority: 92,
1427
- detect: (context) => hasFileStartingWith(context, "astro.config") || hasDependency(context, "astro"),
1443
+ detect: (context) => fileStartingWithEvidence(context, "astro.config") ?? dependencyEvidence(context, "astro"),
1428
1444
  layers: [
1429
1445
  layer("pages", "src/pages/"),
1430
1446
  layer("layouts", "src/layouts/"),
@@ -1439,7 +1455,7 @@ var JS_PRESETS = [
1439
1455
  {
1440
1456
  name: "angular",
1441
1457
  priority: 90,
1442
- detect: (context) => hasFile(context, "angular.json") || hasDependency(context, "@angular/core"),
1458
+ detect: (context) => fileEvidence(context, "angular.json") ?? dependencyEvidence(context, "@angular/core"),
1443
1459
  layers: [
1444
1460
  layer("app", "src/app/"),
1445
1461
  layer("features", "src/app/features/"),
@@ -1463,7 +1479,7 @@ var JS_PRESETS = [
1463
1479
  {
1464
1480
  name: "solid",
1465
1481
  priority: 88,
1466
- detect: (context) => hasDependency(context, "solid-js", "solid-start"),
1482
+ detect: (context) => dependencyEvidence(context, "solid-js", "solid-start"),
1467
1483
  layers: [
1468
1484
  layer("pages", "src/routes/", "src/pages/"),
1469
1485
  layer("components", "src/components/"),
@@ -1476,7 +1492,15 @@ var JS_PRESETS = [
1476
1492
  {
1477
1493
  name: "vue-vite",
1478
1494
  priority: 86,
1479
- detect: (context) => hasDependency(context, "vue") && (hasDependency(context, "vite") || hasFileStartingWith(context, "vite.config")),
1495
+ // `vue` is the decisive signal; `vite` (or its config file) only says the app is
1496
+ // built with Vite rather than shipped pre-compiled, so it is required but not what
1497
+ // gets reported.
1498
+ detect: (context) => {
1499
+ const framework = dependencyEvidence(context, "vue");
1500
+ if (framework === null) return null;
1501
+ const build = dependencyEvidence(context, "vite") ?? fileStartingWithEvidence(context, "vite.config");
1502
+ return build === null ? null : framework;
1503
+ },
1480
1504
  layers: [
1481
1505
  layer("pages", "src/pages/", "src/views/"),
1482
1506
  layer("components", "src/components/"),
@@ -1501,7 +1525,12 @@ var JS_PRESETS = [
1501
1525
  {
1502
1526
  name: "react-vite",
1503
1527
  priority: 84,
1504
- detect: (context) => hasDependency(context, "react") && (hasDependency(context, "vite") || hasFileStartingWith(context, "vite.config")),
1528
+ detect: (context) => {
1529
+ const framework = dependencyEvidence(context, "react");
1530
+ if (framework === null) return null;
1531
+ const build = dependencyEvidence(context, "vite") ?? fileStartingWithEvidence(context, "vite.config");
1532
+ return build === null ? null : framework;
1533
+ },
1505
1534
  layers: VITE_LAYERS,
1506
1535
  kinds: VITE_KINDS,
1507
1536
  exclude: [".vite", "dist"],
@@ -1510,7 +1539,7 @@ var JS_PRESETS = [
1510
1539
  {
1511
1540
  name: "nest",
1512
1541
  priority: 80,
1513
- detect: (context) => hasDependency(context, "@nestjs/core"),
1542
+ detect: (context) => dependencyEvidence(context, "@nestjs/core"),
1514
1543
  layers: [
1515
1544
  layer("modules", "src/modules/", "modules/"),
1516
1545
  layer("controllers", "src/controllers/", "controllers/"),
@@ -1536,7 +1565,7 @@ var JS_PRESETS = [
1536
1565
  {
1537
1566
  name: "node-express",
1538
1567
  priority: 78,
1539
- detect: (context) => hasDependency(context, "express", "fastify", "koa", "@hapi/hapi"),
1568
+ detect: (context) => dependencyEvidence(context, "express", "fastify", "koa", "@hapi/hapi"),
1540
1569
  layers: NODE_LAYERS,
1541
1570
  kinds: NODE_KINDS,
1542
1571
  exclude: ["dist"],
@@ -1547,7 +1576,7 @@ var JS_PRESETS = [
1547
1576
  // A bare package.json is often just tooling around a non-JS project, so it
1548
1577
  // ranks below every definitive language manifest.
1549
1578
  priority: 45,
1550
- detect: (context) => hasFile(context, "package.json"),
1579
+ detect: (context) => fileEvidence(context, "package.json"),
1551
1580
  layers: [
1552
1581
  layer("src", "src/"),
1553
1582
  layer("lib", "lib/"),
@@ -1561,13 +1590,12 @@ var JS_PRESETS = [
1561
1590
  ];
1562
1591
 
1563
1592
  // src/presets/lang-presets.ts
1564
- var anyFileEndingWith = (context, suffix) => Array.from(context.files).some((file) => file.endsWith(suffix));
1565
1593
  var DIR = (name) => `${name}/`;
1566
1594
  var LANGUAGE_PRESETS = [
1567
1595
  {
1568
1596
  name: "python-django",
1569
1597
  priority: 56,
1570
- detect: (context) => hasFile(context, "manage.py") || manifestContains(context, "pyproject.toml", "django") || manifestContains(context, "requirements.txt", "django"),
1598
+ detect: (context) => fileEvidence(context, "manage.py") ?? manifestEvidence(context, "pyproject.toml", "django") ?? manifestEvidence(context, "requirements.txt", "django"),
1571
1599
  layers: [
1572
1600
  layer("views", DIR("views"), DIR("app/views"), DIR("apps/views")),
1573
1601
  layer("models", DIR("models"), DIR("app/models"), DIR("apps/models")),
@@ -1593,7 +1621,7 @@ var LANGUAGE_PRESETS = [
1593
1621
  {
1594
1622
  name: "python-fastapi",
1595
1623
  priority: 54,
1596
- detect: (context) => manifestContains(context, "requirements.txt", "fastapi") || manifestContains(context, "requirements.txt", "flask") || manifestContains(context, "pyproject.toml", "fastapi") || manifestContains(context, "pyproject.toml", "flask"),
1624
+ detect: (context) => manifestEvidence(context, "requirements.txt", "fastapi") ?? manifestEvidence(context, "requirements.txt", "flask") ?? manifestEvidence(context, "pyproject.toml", "fastapi") ?? manifestEvidence(context, "pyproject.toml", "flask"),
1597
1625
  layers: [
1598
1626
  layer("routers", DIR("routers"), DIR("routes"), DIR("api")),
1599
1627
  layer("models", DIR("models"), DIR("db")),
@@ -1617,7 +1645,7 @@ var LANGUAGE_PRESETS = [
1617
1645
  name: "python-lib",
1618
1646
  // Below node-lib: a Node repo with a stray .py file is still a Node repo.
1619
1647
  priority: 30,
1620
- detect: (context) => hasFile(context, "pyproject.toml") || hasFile(context, "setup.py") || hasFile(context, "requirements.txt") || anyFileEndingWith(context, ".py"),
1648
+ detect: (context) => fileEvidence(context, "pyproject.toml") ?? fileEvidence(context, "setup.py") ?? fileEvidence(context, "requirements.txt") ?? suffixEvidence(context, ".py"),
1621
1649
  layers: [
1622
1650
  layer("package", DIR("src"), DIR("lib")),
1623
1651
  layer("tests", DIR("tests"), DIR("test")),
@@ -1630,7 +1658,7 @@ var LANGUAGE_PRESETS = [
1630
1658
  {
1631
1659
  name: "go",
1632
1660
  priority: 50,
1633
- detect: (context) => hasFile(context, "go.mod"),
1661
+ detect: (context) => fileEvidence(context, "go.mod"),
1634
1662
  layers: [
1635
1663
  layer("cmd", "cmd/"),
1636
1664
  layer("internal", "internal/"),
@@ -1654,7 +1682,7 @@ var LANGUAGE_PRESETS = [
1654
1682
  {
1655
1683
  name: "rust",
1656
1684
  priority: 48,
1657
- detect: (context) => hasFile(context, "Cargo.toml"),
1685
+ detect: (context) => fileEvidence(context, "Cargo.toml"),
1658
1686
  layers: [
1659
1687
  layer("src", "src/"),
1660
1688
  layer("crates", "crates/"),
@@ -1670,7 +1698,7 @@ var LANGUAGE_PRESETS = [
1670
1698
  {
1671
1699
  name: "java-spring",
1672
1700
  priority: 46,
1673
- detect: (context) => hasFile(context, "pom.xml") || hasFile(context, "build.gradle") || hasFile(context, "build.gradle.kts"),
1701
+ detect: (context) => fileEvidence(context, "pom.xml") ?? fileEvidence(context, "build.gradle") ?? fileEvidence(context, "build.gradle.kts"),
1674
1702
  layers: [
1675
1703
  layer("api", "src/main/java/"),
1676
1704
  layer("test", "src/test/java/"),
@@ -1693,7 +1721,7 @@ var LANGUAGE_PRESETS = [
1693
1721
  {
1694
1722
  name: "dotnet",
1695
1723
  priority: 44,
1696
- detect: (context) => anyFileEndingWith(context, ".csproj") || anyFileEndingWith(context, ".sln") || hasFile(context, "global.json"),
1724
+ detect: (context) => suffixEvidence(context, ".csproj") ?? suffixEvidence(context, ".sln") ?? fileEvidence(context, "global.json"),
1697
1725
  layers: [
1698
1726
  layer("web", "src/", "Web/"),
1699
1727
  layer("domain", "Domain/", "Core/"),
@@ -1716,7 +1744,7 @@ var LANGUAGE_PRESETS = [
1716
1744
  {
1717
1745
  name: "php-laravel",
1718
1746
  priority: 42,
1719
- detect: (context) => hasFile(context, "artisan") || manifestContains(context, "composer.json", "laravel") || hasFile(context, "composer.json"),
1747
+ detect: (context) => fileEvidence(context, "artisan") ?? manifestEvidence(context, "composer.json", "laravel") ?? fileEvidence(context, "composer.json"),
1720
1748
  layers: [
1721
1749
  layer("app", "app/"),
1722
1750
  layer("routes", "routes/"),
@@ -1742,7 +1770,7 @@ var LANGUAGE_PRESETS = [
1742
1770
  {
1743
1771
  name: "ruby-rails",
1744
1772
  priority: 40,
1745
- detect: (context) => hasFile(context, "Gemfile") || hasFile(context, "config.ru") || hasFile(context, "Rakefile"),
1773
+ detect: (context) => fileEvidence(context, "Gemfile") ?? fileEvidence(context, "config.ru") ?? fileEvidence(context, "Rakefile"),
1746
1774
  layers: [
1747
1775
  layer("app", "app/"),
1748
1776
  layer("controllers", "app/controllers/"),
@@ -1815,7 +1843,7 @@ var SHAPE_PRESETS = [
1815
1843
  {
1816
1844
  name: "monorepo",
1817
1845
  priority: 70,
1818
- detect: (context) => hasFile(context, "pnpm-workspace.yaml") || hasFile(context, "nx.json") || hasFile(context, "turbo.json") || hasFile(context, "lerna.json") || manifestContains(context, "package.json", '"workspaces"'),
1846
+ detect: (context) => fileEvidence(context, "pnpm-workspace.yaml") ?? fileEvidence(context, "nx.json") ?? fileEvidence(context, "turbo.json") ?? fileEvidence(context, "lerna.json") ?? manifestEvidence(context, "package.json", '"workspaces"'),
1819
1847
  layers: [
1820
1848
  layer("apps", "apps/"),
1821
1849
  layer("packages", "packages/"),
@@ -1829,7 +1857,7 @@ var SHAPE_PRESETS = [
1829
1857
  {
1830
1858
  name: "infra",
1831
1859
  priority: 24,
1832
- 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"),
1860
+ detect: (context) => suffixEvidence(context, ".tf") ?? fileEvidence(context, "Chart.yaml") ?? fileEvidence(context, "kustomization.yaml") ?? fileEvidence(context, "docker-compose.yml") ?? fileEvidence(context, "docker-compose.yaml"),
1833
1861
  layers: [
1834
1862
  layer("modules", "modules/"),
1835
1863
  layer("environments", "env/", "environments/"),
@@ -1851,7 +1879,7 @@ var SHAPE_PRESETS = [
1851
1879
  // up to two directories down (bounded in the context builder). Counting
1852
1880
  // only the root turned every real documentation repository — README.md plus
1853
1881
  // the pages under `docs/` — into `generic`.
1854
- detect: (context) => context.markdownCount >= 3 && !hasFile(context, "package.json"),
1882
+ detect: (context) => markdownEvidence(context),
1855
1883
  layers: [
1856
1884
  layer("content", "content/", "docs/"),
1857
1885
  layer("assets", "assets/", "static/", "public/"),
@@ -1864,7 +1892,10 @@ var SHAPE_PRESETS = [
1864
1892
  {
1865
1893
  name: "generic",
1866
1894
  priority: 0,
1867
- detect: () => true,
1895
+ // No detector: this is the fallback, and `detectPresetFromContext` reaches it only
1896
+ // when every other preset reported no evidence. `fallback: true` is what lets the
1897
+ // wizard tell "I recognised this" apart from "I gave up".
1898
+ fallback: true,
1868
1899
  layers: DEFAULT_LAYERS,
1869
1900
  kinds: DEFAULT_KINDS,
1870
1901
  exclude: [],
@@ -1896,20 +1927,27 @@ function detectPresetFromContext(context, forced) {
1896
1927
  if (forced !== void 0) {
1897
1928
  const preset = BY_NAME.get(forced);
1898
1929
  if (!preset) throw new Error(unknownPresetMessage(forced));
1899
- return toDetected(preset);
1930
+ return toDetected(preset, null);
1900
1931
  }
1901
1932
  for (const preset of PRESETS) {
1902
- if (preset.detect(context)) return toDetected(preset);
1933
+ const reason = preset.detect?.(context) ?? null;
1934
+ if (reason !== null) return toDetected(preset, reason);
1935
+ }
1936
+ const fallback = PRESETS.find((preset) => preset.fallback === true);
1937
+ if (fallback === void 0) {
1938
+ throw new Error("No fallback preset is defined. Exactly one preset must set `fallback: true`.");
1903
1939
  }
1904
- return toDetected(PRESETS[PRESETS.length - 1]);
1940
+ return toDetected(fallback, null, true);
1905
1941
  }
1906
- function toDetected(preset) {
1942
+ function toDetected(preset, reason, fallback = false) {
1907
1943
  return {
1908
1944
  presetName: preset.name,
1909
1945
  layers: preset.layers,
1910
1946
  kinds: preset.kinds,
1911
1947
  exclude: preset.exclude ? [...preset.exclude] : [],
1912
- recommendedBudget: preset.recommendedBudget ?? null
1948
+ recommendedBudget: preset.recommendedBudget ?? null,
1949
+ reason,
1950
+ fallback
1913
1951
  };
1914
1952
  }
1915
1953
  function detectPreset(cwd, forced) {
@@ -2349,14 +2387,14 @@ var PRESET_IGNORES = new Set(
2349
2387
  );
2350
2388
  function readLinkTarget(linkPath, fallback) {
2351
2389
  try {
2352
- return fs10__namespace.readlinkSync(linkPath);
2390
+ return fs11__namespace.readlinkSync(linkPath);
2353
2391
  } catch {
2354
2392
  return fallback;
2355
2393
  }
2356
2394
  }
2357
2395
  function isInside(root, candidate) {
2358
- const relative6 = path11__namespace.relative(root, candidate);
2359
- return relative6 === "" || !relative6.startsWith(`..${path11__namespace.sep}`) && relative6 !== ".." && !path11__namespace.isAbsolute(relative6);
2396
+ const relative7 = path12__namespace.relative(root, candidate);
2397
+ return relative7 === "" || !relative7.startsWith(`..${path12__namespace.sep}`) && relative7 !== ".." && !path12__namespace.isAbsolute(relative7);
2360
2398
  }
2361
2399
  function getGitInfo(cwd, fallbackBranch = "main") {
2362
2400
  try {
@@ -2561,10 +2599,10 @@ function trimUnescapedTrailingSpaces(line) {
2561
2599
  return line.slice(0, end);
2562
2600
  }
2563
2601
  function readGitignoreRules(dir) {
2564
- const gitignorePath = path11__namespace.join(dir, ".gitignore");
2602
+ const gitignorePath = path12__namespace.join(dir, ".gitignore");
2565
2603
  let raw;
2566
2604
  try {
2567
- raw = fs10__namespace.readFileSync(gitignorePath, "utf8");
2605
+ raw = fs11__namespace.readFileSync(gitignorePath, "utf8");
2568
2606
  } catch {
2569
2607
  return [];
2570
2608
  }
@@ -2691,7 +2729,7 @@ function isToolOwnedDirName(name) {
2691
2729
  }
2692
2730
  function hasSnapshotMarker(target) {
2693
2731
  try {
2694
- return fs10__namespace.statSync(target).isDirectory() && fs10__namespace.existsSync(path11__namespace.join(target, ".codebase-snapshot"));
2732
+ return fs11__namespace.statSync(target).isDirectory() && fs11__namespace.existsSync(path12__namespace.join(target, ".codebase-snapshot"));
2695
2733
  } catch {
2696
2734
  return false;
2697
2735
  }
@@ -2705,7 +2743,7 @@ function makeToolOwnedDecider(cwd, toolOwnedPaths) {
2705
2743
  const prefix = parts.slice(0, depth).join("/");
2706
2744
  let hasMarker = markers.get(prefix);
2707
2745
  if (hasMarker === void 0) {
2708
- hasMarker = fs10__namespace.existsSync(path11__namespace.join(cwd, prefix, ".codebase-snapshot"));
2746
+ hasMarker = fs11__namespace.existsSync(path12__namespace.join(cwd, prefix, ".codebase-snapshot"));
2709
2747
  markers.set(prefix, hasMarker);
2710
2748
  }
2711
2749
  if (hasMarker) return true;
@@ -2720,7 +2758,7 @@ function makeLinkProbe() {
2720
2758
  let known = cache.get(absolutePath);
2721
2759
  if (known === void 0) {
2722
2760
  try {
2723
- known = fs10__namespace.lstatSync(absolutePath).isSymbolicLink();
2761
+ known = fs11__namespace.lstatSync(absolutePath).isSymbolicLink();
2724
2762
  } catch {
2725
2763
  known = false;
2726
2764
  }
@@ -2733,7 +2771,7 @@ function dropLinkedDirectories(cwd, candidates, skipped) {
2733
2771
  const isLink = makeLinkProbe();
2734
2772
  let rootReal = cwd;
2735
2773
  try {
2736
- rootReal = fs10__namespace.realpathSync(cwd);
2774
+ rootReal = fs11__namespace.realpathSync(cwd);
2737
2775
  } catch {
2738
2776
  }
2739
2777
  const reported = /* @__PURE__ */ new Set();
@@ -2743,7 +2781,7 @@ function dropLinkedDirectories(cwd, candidates, skipped) {
2743
2781
  let linkedDir = null;
2744
2782
  for (let depth = 1; depth <= parts.length; depth += 1) {
2745
2783
  const prefix = parts.slice(0, depth).join("/");
2746
- if (isLink(path11__namespace.join(cwd, prefix))) {
2784
+ if (isLink(path12__namespace.join(cwd, prefix))) {
2747
2785
  linkedDir = prefix;
2748
2786
  break;
2749
2787
  }
@@ -2754,10 +2792,10 @@ function dropLinkedDirectories(cwd, candidates, skipped) {
2754
2792
  }
2755
2793
  if (reported.has(linkedDir)) continue;
2756
2794
  reported.add(linkedDir);
2757
- const absolute = path11__namespace.join(cwd, linkedDir);
2795
+ const absolute = path12__namespace.join(cwd, linkedDir);
2758
2796
  let targetReal = absolute;
2759
2797
  try {
2760
- targetReal = fs10__namespace.realpathSync(absolute);
2798
+ targetReal = fs11__namespace.realpathSync(absolute);
2761
2799
  } catch {
2762
2800
  }
2763
2801
  const target = readLinkTarget(absolute, targetReal);
@@ -2834,7 +2872,7 @@ function collectFiles(cwd, options = {}) {
2834
2872
  "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."
2835
2873
  );
2836
2874
  }
2837
- if (fs10__namespace.existsSync(path11__namespace.join(cwd, ".git"))) {
2875
+ if (fs11__namespace.existsSync(path12__namespace.join(cwd, ".git"))) {
2838
2876
  throw new Error(
2839
2877
  "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."
2840
2878
  );
@@ -2893,7 +2931,7 @@ function collectFiles(cwd, options = {}) {
2893
2931
  }
2894
2932
  function rootRealFor(root) {
2895
2933
  try {
2896
- return fs10__namespace.realpathSync(root);
2934
+ return fs11__namespace.realpathSync(root);
2897
2935
  } catch {
2898
2936
  return root;
2899
2937
  }
@@ -2901,18 +2939,18 @@ function rootRealFor(root) {
2901
2939
  function walkDirectory(dir, baseDir, rootReal, visited, reservedPaths = /* @__PURE__ */ new Set(), parentGitignoreLayers = []) {
2902
2940
  const results = { files: [], skipped: [] };
2903
2941
  const stack = [];
2904
- const relativeToBase = (target) => path11__namespace.relative(baseDir, target).replace(/\\/g, "/") || ".";
2942
+ const relativeToBase = (target) => path12__namespace.relative(baseDir, target).replace(/\\/g, "/") || ".";
2905
2943
  const enter = (current, layers) => {
2906
2944
  if (visited.has(current)) return false;
2907
2945
  visited.add(current);
2908
2946
  const localRules = readGitignoreRules(current);
2909
2947
  const gitignoreLayers = localRules.length === 0 ? layers : [
2910
2948
  ...layers,
2911
- { prefix: path11__namespace.relative(baseDir, current).replace(/\\/g, "/"), rules: localRules }
2949
+ { prefix: path12__namespace.relative(baseDir, current).replace(/\\/g, "/"), rules: localRules }
2912
2950
  ];
2913
2951
  let entries;
2914
2952
  try {
2915
- entries = fs10__namespace.readdirSync(current, { withFileTypes: true });
2953
+ entries = fs11__namespace.readdirSync(current, { withFileTypes: true });
2916
2954
  } catch {
2917
2955
  results.skipped.push({
2918
2956
  path: relativeToBase(current),
@@ -2934,8 +2972,8 @@ function walkDirectory(dir, baseDir, rootReal, visited, reservedPaths = /* @__PU
2934
2972
  }
2935
2973
  const entry = frame.entries[frame.index];
2936
2974
  frame.index += 1;
2937
- const fullPath = path11__namespace.join(frame.dir, entry.name);
2938
- const relPath = path11__namespace.relative(baseDir, fullPath).replace(/\\/g, "/");
2975
+ const fullPath = path12__namespace.join(frame.dir, entry.name);
2976
+ const relPath = path12__namespace.relative(baseDir, fullPath).replace(/\\/g, "/");
2939
2977
  if (isToolOwnedDirName(entry.name) && reservedPaths.has(relPath)) continue;
2940
2978
  if (DEFAULT_IGNORED_DIRS.includes(entry.name)) {
2941
2979
  results.skipped.push({
@@ -2968,14 +3006,14 @@ function walkDirectory(dir, baseDir, rootReal, visited, reservedPaths = /* @__PU
2968
3006
  if (entry.isSymbolicLink()) {
2969
3007
  let targetReal;
2970
3008
  try {
2971
- targetReal = fs10__namespace.realpathSync(fullPath);
3009
+ targetReal = fs11__namespace.realpathSync(fullPath);
2972
3010
  } catch {
2973
3011
  results.skipped.push({ path: relPath, reason: "read-error", detail: "broken symlink" });
2974
3012
  continue;
2975
3013
  }
2976
3014
  let stats;
2977
3015
  try {
2978
- stats = fs10__namespace.statSync(fullPath);
3016
+ stats = fs11__namespace.statSync(fullPath);
2979
3017
  } catch {
2980
3018
  results.skipped.push({ path: relPath, reason: "read-error", detail: "unreadable symlink" });
2981
3019
  continue;
@@ -3273,11 +3311,11 @@ async function loadConfigFileDetailed(cwd) {
3273
3311
  const warnings = [];
3274
3312
  const errors = [];
3275
3313
  for (const filename of CONFIG_FILENAMES) {
3276
- const fullPath = path11__namespace.join(cwd, filename);
3277
- if (!fs10__namespace.existsSync(fullPath)) continue;
3314
+ const fullPath = path12__namespace.join(cwd, filename);
3315
+ if (!fs11__namespace.existsSync(fullPath)) continue;
3278
3316
  if (filename.endsWith(".json") || filename === ".snapshotrc") {
3279
3317
  try {
3280
- const config = parseJsonish(fs10__namespace.readFileSync(fullPath, "utf8"), fullPath);
3318
+ const config = parseJsonish(fs11__namespace.readFileSync(fullPath, "utf8"), fullPath);
3281
3319
  if (config === null || typeof config !== "object" || Array.isArray(config)) {
3282
3320
  throw new Error(
3283
3321
  `${fullPath} does not hold a configuration object (received ${describeValue2(config)}).`
@@ -3311,10 +3349,10 @@ async function loadConfigFileDetailed(cwd) {
3311
3349
  continue;
3312
3350
  }
3313
3351
  }
3314
- const pkgJsonPath = path11__namespace.join(cwd, "package.json");
3315
- if (fs10__namespace.existsSync(pkgJsonPath)) {
3352
+ const pkgJsonPath = path12__namespace.join(cwd, "package.json");
3353
+ if (fs11__namespace.existsSync(pkgJsonPath)) {
3316
3354
  try {
3317
- const parsed = parseJsonish(fs10__namespace.readFileSync(pkgJsonPath, "utf8"), pkgJsonPath);
3355
+ const parsed = parseJsonish(fs11__namespace.readFileSync(pkgJsonPath, "utf8"), pkgJsonPath);
3318
3356
  const snapshotKey = parsed.snapshot;
3319
3357
  if (snapshotKey && typeof snapshotKey === "object" && !Array.isArray(snapshotKey)) {
3320
3358
  return { config: snapshotKey, file: pkgJsonPath, warnings, errors };
@@ -3337,7 +3375,7 @@ async function loadConfigFile(cwd) {
3337
3375
  const loaded = await loadConfigFileDetailed(cwd);
3338
3376
  if (loaded.config && loaded.file && typeof loaded.config === "object") {
3339
3377
  Object.defineProperty(loaded.config, CONFIG_DIR_SYMBOL, {
3340
- value: path11__namespace.dirname(loaded.file),
3378
+ value: path12__namespace.dirname(loaded.file),
3341
3379
  enumerable: false,
3342
3380
  configurable: true
3343
3381
  });
@@ -3496,12 +3534,12 @@ function resolveKindRules(value) {
3496
3534
  function outputExcludePatterns(cwd, outDir, tmpDir) {
3497
3535
  const patterns = [];
3498
3536
  for (const dir of [outDir, tmpDir]) {
3499
- const absolute = path11__namespace.resolve(cwd, dir);
3500
- const relative6 = path11__namespace.relative(cwd, absolute);
3501
- if (!relative6 || relative6 === ".." || relative6.startsWith(`..${path11__namespace.sep}`) || path11__namespace.isAbsolute(relative6)) {
3537
+ const absolute = path12__namespace.resolve(cwd, dir);
3538
+ const relative7 = path12__namespace.relative(cwd, absolute);
3539
+ if (!relative7 || relative7 === ".." || relative7.startsWith(`..${path12__namespace.sep}`) || path12__namespace.isAbsolute(relative7)) {
3502
3540
  continue;
3503
3541
  }
3504
- patterns.push(`${relative6.replace(/\\/g, "/")}/**`);
3542
+ patterns.push(`${relative7.replace(/\\/g, "/")}/**`);
3505
3543
  }
3506
3544
  return patterns;
3507
3545
  }
@@ -3617,11 +3655,11 @@ function resolveCandidateCwd(userConfig, cliOptions, baseDir) {
3617
3655
  );
3618
3656
  }
3619
3657
  const candidate = cliOptions?.cwd || userConfig?.cwd || baseDir || process.cwd();
3620
- const resolved = path11__namespace.resolve(candidate);
3621
- if (!fs10__namespace.existsSync(resolved)) {
3658
+ const resolved = path12__namespace.resolve(candidate);
3659
+ if (!fs11__namespace.existsSync(resolved)) {
3622
3660
  throw new Error(`Working directory does not exist: ${resolved}`);
3623
3661
  }
3624
- if (!fs10__namespace.statSync(resolved).isDirectory()) {
3662
+ if (!fs11__namespace.statSync(resolved).isDirectory()) {
3625
3663
  throw new Error(`Working directory is not a directory: ${resolved}`);
3626
3664
  }
3627
3665
  return resolved;
@@ -4645,7 +4683,7 @@ var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
4645
4683
  ".so"
4646
4684
  ]);
4647
4685
  function isBinaryExtension(filePath) {
4648
- const ext = path11__namespace.extname(filePath).toLowerCase();
4686
+ const ext = path12__namespace.extname(filePath).toLowerCase();
4649
4687
  return BINARY_EXTENSIONS.has(ext);
4650
4688
  }
4651
4689
  function isBinaryBuffer(buffer) {
@@ -4815,13 +4853,13 @@ function isBinaryFile(absolutePath) {
4815
4853
  if (isBinaryExtension(absolutePath)) return true;
4816
4854
  let fd;
4817
4855
  try {
4818
- fd = fs10__namespace.openSync(absolutePath, "r");
4856
+ fd = fs11__namespace.openSync(absolutePath, "r");
4819
4857
  const buffer = Buffer.alloc(CHUNK);
4820
4858
  let sample;
4821
4859
  let position = 0;
4822
4860
  while (position < BINARY_SCAN_BYTES) {
4823
4861
  const wanted = Math.min(CHUNK, BINARY_SCAN_BYTES - position);
4824
- const bytesRead = fs10__namespace.readSync(fd, buffer, 0, wanted, position);
4862
+ const bytesRead = fs11__namespace.readSync(fd, buffer, 0, wanted, position);
4825
4863
  if (bytesRead === 0) return false;
4826
4864
  const chunk = buffer.subarray(0, bytesRead);
4827
4865
  if (sample === void 0) {
@@ -4839,7 +4877,7 @@ function isBinaryFile(absolutePath) {
4839
4877
  } finally {
4840
4878
  if (fd !== void 0) {
4841
4879
  try {
4842
- fs10__namespace.closeSync(fd);
4880
+ fs11__namespace.closeSync(fd);
4843
4881
  } catch {
4844
4882
  }
4845
4883
  }
@@ -4899,10 +4937,10 @@ var LANGUAGE_BY_BASENAME = {
4899
4937
  };
4900
4938
  function detectLanguage(filePath) {
4901
4939
  const normalized = filePath.replace(/\\/g, "/");
4902
- const base = path11__namespace.posix.basename(normalized);
4940
+ const base = path12__namespace.posix.basename(normalized);
4903
4941
  if (LANGUAGE_BY_BASENAME[base]) return LANGUAGE_BY_BASENAME[base];
4904
4942
  if (base === ".env" || base.startsWith(".env.")) return "dotenv";
4905
- const ext = path11__namespace.posix.extname(base).toLowerCase();
4943
+ const ext = path12__namespace.posix.extname(base).toLowerCase();
4906
4944
  return LANGUAGE_BY_EXTENSION[ext] ?? "text";
4907
4945
  }
4908
4946
  function maskedPath(value, secretRules) {
@@ -4910,33 +4948,33 @@ function maskedPath(value, secretRules) {
4910
4948
  }
4911
4949
  function confineToDirectory(directory, filename) {
4912
4950
  if (filename.includes("\0")) return null;
4913
- if (path11__namespace.isAbsolute(filename)) return null;
4914
- const base = path11__namespace.resolve(directory);
4915
- const resolved = path11__namespace.resolve(base, filename);
4951
+ if (path12__namespace.isAbsolute(filename)) return null;
4952
+ const base = path12__namespace.resolve(directory);
4953
+ const resolved = path12__namespace.resolve(base, filename);
4916
4954
  const caseInsensitive = process.platform === "win32" || process.platform === "darwin";
4917
- const prefix = caseInsensitive ? `${base}${path11__namespace.sep}`.toLowerCase() : `${base}${path11__namespace.sep}`;
4955
+ const prefix = caseInsensitive ? `${base}${path12__namespace.sep}`.toLowerCase() : `${base}${path12__namespace.sep}`;
4918
4956
  const candidate = caseInsensitive ? resolved.toLowerCase() : resolved;
4919
4957
  return candidate.startsWith(prefix) ? resolved : null;
4920
4958
  }
4921
4959
  function findStagingDirectory(config) {
4922
- const tmpDir = path11__namespace.resolve(config.cwd, config.tmpDir);
4960
+ const tmpDir = path12__namespace.resolve(config.cwd, config.tmpDir);
4923
4961
  let entries;
4924
4962
  try {
4925
- entries = fs10__namespace.readdirSync(tmpDir);
4963
+ entries = fs11__namespace.readdirSync(tmpDir);
4926
4964
  } catch {
4927
4965
  return null;
4928
4966
  }
4929
4967
  const runs = entries.filter((name) => name.startsWith("run-")).sort();
4930
4968
  const newest = runs[runs.length - 1];
4931
- return newest ? path11__namespace.join(tmpDir, newest) : null;
4969
+ return newest ? path12__namespace.join(tmpDir, newest) : null;
4932
4970
  }
4933
4971
  function describeMissingManifest(config, manifestPath) {
4934
- const outDir = path11__namespace.resolve(config.cwd, config.outDir);
4935
- const markerPath = path11__namespace.join(outDir, ".codebase-snapshot");
4972
+ const outDir = path12__namespace.resolve(config.cwd, config.outDir);
4973
+ const markerPath = path12__namespace.join(outDir, ".codebase-snapshot");
4936
4974
  let marker2 = null;
4937
4975
  try {
4938
- if (fs10__namespace.existsSync(markerPath)) {
4939
- const parsed = JSON.parse(fs10__namespace.readFileSync(markerPath, "utf8"));
4976
+ if (fs11__namespace.existsSync(markerPath)) {
4977
+ const parsed = JSON.parse(fs11__namespace.readFileSync(markerPath, "utf8"));
4940
4978
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
4941
4979
  marker2 = parsed;
4942
4980
  }
@@ -4972,14 +5010,14 @@ function sha256Hex(buffer) {
4972
5010
  }
4973
5011
  var VERIFY_SNIFF_MAX_BYTES = 8 * 1024 * 1024;
4974
5012
  function wouldNowBeIncluded(cwd, relativePath, reason) {
4975
- const absolutePath = path11__namespace.join(cwd, relativePath);
5013
+ const absolutePath = path12__namespace.join(cwd, relativePath);
4976
5014
  try {
4977
- const stats = fs10__namespace.statSync(absolutePath);
5015
+ const stats = fs11__namespace.statSync(absolutePath);
4978
5016
  if (!stats.isFile()) return false;
4979
5017
  if (isBinaryFile(absolutePath)) return false;
4980
5018
  if (reason !== "binary") return true;
4981
5019
  if (stats.size > VERIFY_SNIFF_MAX_BYTES) return false;
4982
- return !decodeText(fs10__namespace.readFileSync(absolutePath)).binary;
5020
+ return !decodeText(fs11__namespace.readFileSync(absolutePath)).binary;
4983
5021
  } catch {
4984
5022
  return false;
4985
5023
  }
@@ -5037,8 +5075,8 @@ function describeManifestShapeProblem(value) {
5037
5075
  function validateSnapshot(configInput) {
5038
5076
  assertUsableConfigInput(configInput);
5039
5077
  const config = isResolvedConfig(configInput) ? normalizeResolvedConfig(configInput) : resolveConfig(configInput);
5040
- const manifestPath = path11__namespace.resolve(config.cwd, config.outDir, "manifest.json");
5041
- if (!fs10__namespace.existsSync(manifestPath)) {
5078
+ const manifestPath = path12__namespace.resolve(config.cwd, config.outDir, "manifest.json");
5079
+ if (!fs11__namespace.existsSync(manifestPath)) {
5042
5080
  return {
5043
5081
  valid: false,
5044
5082
  manifestPresent: false,
@@ -5049,7 +5087,7 @@ function validateSnapshot(configInput) {
5049
5087
  }
5050
5088
  let manifest;
5051
5089
  try {
5052
- manifest = JSON.parse(fs10__namespace.readFileSync(manifestPath, "utf8"));
5090
+ manifest = JSON.parse(fs11__namespace.readFileSync(manifestPath, "utf8"));
5053
5091
  } catch (err) {
5054
5092
  return {
5055
5093
  valid: false,
@@ -5071,7 +5109,7 @@ function validateSnapshot(configInput) {
5071
5109
  }
5072
5110
  const drift = emptyDrift();
5073
5111
  const warnings = [];
5074
- const snapshotDir = path11__namespace.resolve(config.cwd, config.outDir);
5112
+ const snapshotDir = path12__namespace.resolve(config.cwd, config.outDir);
5075
5113
  const manifestFileMap = /* @__PURE__ */ new Map();
5076
5114
  for (const file of manifest.files) {
5077
5115
  if (file && typeof file.relativePath === "string") {
@@ -5105,7 +5143,7 @@ function validateSnapshot(configInput) {
5105
5143
  }
5106
5144
  let diskHash;
5107
5145
  try {
5108
- diskHash = sha256Hex(fs10__namespace.readFileSync(path11__namespace.join(config.cwd, diskFile)));
5146
+ diskHash = sha256Hex(fs11__namespace.readFileSync(path12__namespace.join(config.cwd, diskFile)));
5109
5147
  } catch (err) {
5110
5148
  if (err.code === "ENOENT") {
5111
5149
  drift.missingOnDisk.push(diskFile);
@@ -5129,7 +5167,7 @@ function validateSnapshot(configInput) {
5129
5167
  }
5130
5168
  let statFailure = null;
5131
5169
  try {
5132
- fs10__namespace.statSync(confined);
5170
+ fs11__namespace.statSync(confined);
5133
5171
  } catch (err) {
5134
5172
  statFailure = err;
5135
5173
  }
@@ -5149,7 +5187,7 @@ function validateSnapshot(configInput) {
5149
5187
  ...Object.values(config.budgets.byLayer)
5150
5188
  );
5151
5189
  const partNames = /* @__PURE__ */ new Set();
5152
- const partsDir = path11__namespace.join(snapshotDir, "parts");
5190
+ const partsDir = path12__namespace.join(snapshotDir, "parts");
5153
5191
  for (const part of manifest.parts) {
5154
5192
  if (!part || typeof part.filename !== "string") continue;
5155
5193
  partNames.add(part.filename);
@@ -5164,7 +5202,7 @@ function validateSnapshot(configInput) {
5164
5202
  }
5165
5203
  let buffer;
5166
5204
  try {
5167
- buffer = fs10__namespace.readFileSync(partPath);
5205
+ buffer = fs11__namespace.readFileSync(partPath);
5168
5206
  } catch {
5169
5207
  drift.partIssues.push({ part: part.filename, issue: "missing from snapshot/parts" });
5170
5208
  continue;
@@ -5378,9 +5416,9 @@ function validateSnapshot(configInput) {
5378
5416
  ["full-codebase.md", manifest.artifacts?.fullMarkdown]
5379
5417
  ]) {
5380
5418
  if (!artifact) continue;
5381
- const artifactPath = path11__namespace.join(snapshotDir, key);
5419
+ const artifactPath = path12__namespace.join(snapshotDir, key);
5382
5420
  try {
5383
- const buffer = fs10__namespace.readFileSync(artifactPath);
5421
+ const buffer = fs11__namespace.readFileSync(artifactPath);
5384
5422
  if (sha256Hex(buffer) !== artifact.sha256 || buffer.length !== artifact.byteSize) {
5385
5423
  drift.partIssues.push({ part: key, issue: "artifact does not match its recorded digest" });
5386
5424
  }
@@ -5450,7 +5488,7 @@ function validateSnapshot(configInput) {
5450
5488
 
5451
5489
  // src/version.ts
5452
5490
  var GENERATOR_NAME = "codebase-snapshot";
5453
- var GENERATOR_VERSION = "0.9.1";
5491
+ var GENERATOR_VERSION = "0.9.5";
5454
5492
  var LEGACY_GENERATOR_NAMES = ["@ankurhalder/codebase-snapshot"];
5455
5493
  function isOurGeneratorName(name) {
5456
5494
  if (typeof name !== "string") return false;
@@ -5465,42 +5503,42 @@ function describeMatcher(matcher) {
5465
5503
  return String(matcher);
5466
5504
  }
5467
5505
  function normalizeTarget(cwd, target) {
5468
- const absolute = path11__namespace.resolve(cwd, target);
5469
- const relative6 = path11__namespace.relative(cwd, absolute);
5470
- if (relative6.startsWith("..") || path11__namespace.isAbsolute(relative6)) return target;
5471
- return relative6.split(path11__namespace.sep).join("/");
5506
+ const absolute = path12__namespace.resolve(cwd, target);
5507
+ const relative7 = path12__namespace.relative(cwd, absolute);
5508
+ if (relative7.startsWith("..") || path12__namespace.isAbsolute(relative7)) return target;
5509
+ return relative7.split(path12__namespace.sep).join("/");
5472
5510
  }
5473
5511
  function findRuleMatch(rules, relativePath) {
5474
5512
  return rules.find(
5475
5513
  (rule) => matchRule(relativePath, rule.match)
5476
5514
  );
5477
5515
  }
5478
- function findRecordedAncestor(skipped, relative6, maskedPath2) {
5516
+ function findRecordedAncestor(skipped, relative7, maskedPath2) {
5479
5517
  let nearest;
5480
5518
  for (const record of skipped) {
5481
5519
  const ancestorPath = record.path;
5482
5520
  if (ancestorPath.length === 0) continue;
5483
- const isAncestor = relative6.startsWith(`${ancestorPath}/`) || maskedPath2.startsWith(`${ancestorPath}/`);
5521
+ const isAncestor = relative7.startsWith(`${ancestorPath}/`) || maskedPath2.startsWith(`${ancestorPath}/`);
5484
5522
  if (!isAncestor) continue;
5485
5523
  if (!nearest || ancestorPath.length > nearest.path.length) nearest = record;
5486
5524
  }
5487
5525
  return nearest;
5488
5526
  }
5489
5527
  function readManifest2(config) {
5490
- const manifestPath = path11__namespace.resolve(config.cwd, config.outDir, "manifest.json");
5528
+ const manifestPath = path12__namespace.resolve(config.cwd, config.outDir, "manifest.json");
5491
5529
  try {
5492
- return JSON.parse(fs10__namespace.readFileSync(manifestPath, "utf8"));
5530
+ return JSON.parse(fs11__namespace.readFileSync(manifestPath, "utf8"));
5493
5531
  } catch {
5494
5532
  return null;
5495
5533
  }
5496
5534
  }
5497
5535
  function explainPath(config, target) {
5498
- const relative6 = normalizeTarget(config.cwd, target);
5499
- const absolute = path11__namespace.join(config.cwd, relative6);
5500
- const maskedPath2 = findSecretInPath(relative6, config.secretRules)?.masked ?? relative6;
5536
+ const relative7 = normalizeTarget(config.cwd, target);
5537
+ const absolute = path12__namespace.join(config.cwd, relative7);
5538
+ const maskedPath2 = findSecretInPath(relative7, config.secretRules)?.masked ?? relative7;
5501
5539
  const result = {
5502
5540
  path: maskedPath2,
5503
- exists: fs10__namespace.existsSync(absolute),
5541
+ exists: fs11__namespace.existsSync(absolute),
5504
5542
  included: false
5505
5543
  };
5506
5544
  const collection = collectFiles(config.cwd, {
@@ -5511,12 +5549,12 @@ function explainPath(config, target) {
5511
5549
  // Same exclusion `generate` applies, so `explain` never says "not-discovered" about a file
5512
5550
  // that is only missing because the walker dropped the tool's own output directory.
5513
5551
  toolOwnedPaths: [config.outDir, config.tmpDir].map(
5514
- (directory) => path11__namespace.relative(config.cwd, path11__namespace.resolve(config.cwd, directory)).replace(/\\/g, "/")
5515
- ).filter((relative7) => relative7.length > 0 && !relative7.startsWith(".."))
5552
+ (directory) => path12__namespace.relative(config.cwd, path12__namespace.resolve(config.cwd, directory)).replace(/\\/g, "/")
5553
+ ).filter((relative8) => relative8.length > 0 && !relative8.startsWith(".."))
5516
5554
  });
5517
- if (!collection.files.includes(relative6)) {
5555
+ if (!collection.files.includes(relative7)) {
5518
5556
  const record = collection.skipped.find(
5519
- (entry2) => entry2.path === relative6 || entry2.path === maskedPath2
5557
+ (entry2) => entry2.path === relative7 || entry2.path === maskedPath2
5520
5558
  );
5521
5559
  if (record) {
5522
5560
  result.reason = record.reason;
@@ -5525,7 +5563,7 @@ function explainPath(config, target) {
5525
5563
  result.reason = "missing";
5526
5564
  result.detail = "no such file or directory, relative to the configured cwd";
5527
5565
  } else {
5528
- const ancestor = findRecordedAncestor(collection.skipped, relative6, maskedPath2);
5566
+ const ancestor = findRecordedAncestor(collection.skipped, relative7, maskedPath2);
5529
5567
  if (ancestor) {
5530
5568
  result.reason = ancestor.reason;
5531
5569
  result.detail = ancestor.detail ? `${ancestor.detail} \u2014 inherited from the skipped directory \`${ancestor.path}\`` : `inside the skipped directory \`${ancestor.path}\``;
@@ -5543,15 +5581,15 @@ function explainPath(config, target) {
5543
5581
  result.detail = "binary content; generate skips it and records `binary` in manifest.skipped[]";
5544
5582
  return result;
5545
5583
  }
5546
- result.layer = classifyLayer(relative6, config.layers);
5547
- result.kind = classifyKind(relative6, config.kinds);
5548
- result.language = detectLanguage(relative6);
5549
- const layerRule = findRuleMatch(config.layers, relative6);
5584
+ result.layer = classifyLayer(relative7, config.layers);
5585
+ result.kind = classifyKind(relative7, config.kinds);
5586
+ result.language = detectLanguage(relative7);
5587
+ const layerRule = findRuleMatch(config.layers, relative7);
5550
5588
  if (layerRule) result.layerMatch = describeMatcher(layerRule.match);
5551
- const kindRule = findRuleMatch(config.kinds, relative6);
5589
+ const kindRule = findRuleMatch(config.kinds, relative7);
5552
5590
  if (kindRule) result.kindMatch = describeMatcher(kindRule.match);
5553
- if (fs10__namespace.existsSync(absolute) && fs10__namespace.statSync(absolute).isFile()) {
5554
- const buffer = fs10__namespace.readFileSync(absolute);
5591
+ if (fs11__namespace.existsSync(absolute) && fs11__namespace.statSync(absolute).isFile()) {
5592
+ const buffer = fs11__namespace.readFileSync(absolute);
5555
5593
  const decoded = decodeText(buffer);
5556
5594
  if (decoded.binary) {
5557
5595
  result.included = false;
@@ -5563,12 +5601,12 @@ function explainPath(config, target) {
5563
5601
  result.bytes = buffer.length;
5564
5602
  result.lines = countLines(text2);
5565
5603
  if (decoded.encoding !== "utf-8") result.encoding = decoded.encoding;
5566
- const { findings } = scanAndRedactSecrets(text2, config.secretRules, relative6);
5604
+ const { findings } = scanAndRedactSecrets(text2, config.secretRules, relative7);
5567
5605
  result.secretCount = findings.length;
5568
5606
  result.secrets = findings.slice(0, 20).map((finding) => ({ rules: finding.rules, line: finding.line }));
5569
5607
  }
5570
5608
  const manifest = readManifest2(config);
5571
- const entry = Array.isArray(manifest?.files) ? manifest.files.find((file) => file.relativePath === relative6) : void 0;
5609
+ const entry = Array.isArray(manifest?.files) ? manifest.files.find((file) => file.relativePath === relative7) : void 0;
5572
5610
  result.part = entry?.part ?? null;
5573
5611
  result.inclusionMode = entry?.inclusionMode;
5574
5612
  return result;
@@ -5799,14 +5837,14 @@ function compareManifests(before, after) {
5799
5837
  function loadManifest(target) {
5800
5838
  let isDirectory;
5801
5839
  try {
5802
- isDirectory = fs10__namespace.statSync(target).isDirectory();
5840
+ isDirectory = fs11__namespace.statSync(target).isDirectory();
5803
5841
  } catch {
5804
5842
  throw new Error(`No snapshot found at ${target}: the path does not exist.`);
5805
5843
  }
5806
- const manifestPath = isDirectory ? path11__namespace.join(target, "manifest.json") : target;
5844
+ const manifestPath = isDirectory ? path12__namespace.join(target, "manifest.json") : target;
5807
5845
  let raw;
5808
5846
  try {
5809
- raw = fs10__namespace.readFileSync(manifestPath, "utf8");
5847
+ raw = fs11__namespace.readFileSync(manifestPath, "utf8");
5810
5848
  } catch {
5811
5849
  throw new Error(`Could not read a manifest at ${manifestPath}.`);
5812
5850
  }
@@ -5863,15 +5901,15 @@ function renderDiffText(diff) {
5863
5901
  function appendGitignoreEntries(cwd, entries) {
5864
5902
  const wanted = [...new Set(entries)].filter((entry) => entry.trim().length > 0);
5865
5903
  if (wanted.length === 0) return { added: [], note: null };
5866
- const ignorePath = path11__namespace.join(cwd, ".gitignore");
5904
+ const ignorePath = path12__namespace.join(cwd, ".gitignore");
5867
5905
  const manual = `Add ${wanted.map((entry) => `"${entry}"`).join(" and ")} to your .gitignore yourself.`;
5868
5906
  let existing = "";
5869
5907
  try {
5870
- if (fs10__namespace.existsSync(ignorePath)) {
5871
- if (fs10__namespace.statSync(ignorePath).isDirectory()) {
5908
+ if (fs11__namespace.existsSync(ignorePath)) {
5909
+ if (fs11__namespace.statSync(ignorePath).isDirectory()) {
5872
5910
  return { added: [], note: `${ignorePath} is a directory, so it was not updated. ${manual}` };
5873
5911
  }
5874
- existing = fs10__namespace.readFileSync(ignorePath, "utf8");
5912
+ existing = fs11__namespace.readFileSync(ignorePath, "utf8");
5875
5913
  }
5876
5914
  } catch (err) {
5877
5915
  return {
@@ -5884,7 +5922,7 @@ function appendGitignoreEntries(cwd, entries) {
5884
5922
  if (added.length === 0) return { added: [], note: null };
5885
5923
  const prefix = existing.length === 0 || existing.endsWith("\n") ? "" : "\n";
5886
5924
  try {
5887
- fs10__namespace.writeFileSync(ignorePath, `${existing}${prefix}${added.join("\n")}
5925
+ fs11__namespace.writeFileSync(ignorePath, `${existing}${prefix}${added.join("\n")}
5888
5926
  `, "utf8");
5889
5927
  } catch (err) {
5890
5928
  return { added: [], note: `${err.message}. ${manual}` };
@@ -6023,6 +6061,12 @@ var PromptCancelled = class extends Error {
6023
6061
  this.name = "PromptCancelled";
6024
6062
  }
6025
6063
  };
6064
+ var PromptBack = class extends Error {
6065
+ constructor() {
6066
+ super("Prompt back");
6067
+ this.name = "PromptBack";
6068
+ }
6069
+ };
6026
6070
  function defaultIO() {
6027
6071
  return { input: process.stdin, output: process.stdout };
6028
6072
  }
@@ -6046,35 +6090,40 @@ function choiceLine(choice, active) {
6046
6090
  const hint = choice.hint ? ` ${choice.hint}` : "";
6047
6091
  return ` ${marker(active)} ${choice.label}${hint}`;
6048
6092
  }
6049
- function renderSelect(message, choices, index) {
6093
+ function listFooter(base, allowBack) {
6094
+ return allowBack ? `${base} \xB7 \u2190 back` : base;
6095
+ }
6096
+ function renderSelect(message, choices, index, allowBack = false) {
6050
6097
  const lines = [`\u25C6 ${message}`, ""];
6051
6098
  choices.forEach((choice, position) => lines.push(choiceLine(choice, position === index)));
6052
- lines.push("", " \u2191\u2193 move \xB7 enter select");
6099
+ lines.push("", ` ${listFooter("\u2191\u2193 move \xB7 enter select", allowBack)}`);
6053
6100
  return lines.join("\n");
6054
6101
  }
6055
- function renderMultiSelect(message, choices, index, selected) {
6102
+ function renderMultiSelect(message, choices, index, selected, allowBack = false) {
6056
6103
  const lines = [`\u25C6 ${message}`, ""];
6057
6104
  choices.forEach((choice, position) => {
6058
6105
  const box = selected.has(position) ? "\u25FC" : "\u25FB";
6059
6106
  const hint = choice.hint ? ` ${choice.hint}` : "";
6060
6107
  lines.push(` ${marker(position === index)} ${box} ${choice.label}${hint}`);
6061
6108
  });
6062
- lines.push("", " \u2191\u2193 move \xB7 space toggle \xB7 enter confirm");
6109
+ lines.push("", ` ${listFooter("\u2191\u2193 move \xB7 space toggle \xB7 enter confirm", allowBack)}`);
6063
6110
  return lines.join("\n");
6064
6111
  }
6065
- function renderConfirm(message, value) {
6112
+ function renderConfirm(message, value, allowBack = false) {
6113
+ const footer = allowBack ? "\u2190\u2192 or y/n \xB7 enter confirm \xB7 b back" : "\u2190\u2192 or y/n \xB7 enter confirm";
6066
6114
  return [
6067
6115
  `\u25C6 ${message}`,
6068
6116
  "",
6069
6117
  ` ${marker(value)} Yes`,
6070
6118
  ` ${marker(!value)} No`,
6071
6119
  "",
6072
- " \u2190\u2192 or y/n \xB7 enter confirm"
6120
+ ` ${footer}`
6073
6121
  ].join("\n");
6074
6122
  }
6075
- function renderText(message, value, error) {
6123
+ function renderText(message, value, error, allowBack = false) {
6076
6124
  const lines = [`\u25C6 ${message}`, "", ` ${value || "\u258F"}`];
6077
6125
  if (error) lines.push("", ` \u2716 ${error}`);
6126
+ if (allowBack) lines.push("", " enter confirm \xB7 \u2190 back");
6078
6127
  return lines.join("\n");
6079
6128
  }
6080
6129
  function keyReader(input) {
@@ -6123,14 +6172,18 @@ var Frame = class {
6123
6172
  function isCancel(key) {
6124
6173
  return key.ctrl === true && key.name === "c" || key.ctrl === true && key.name === "d";
6125
6174
  }
6175
+ function isListBack(key, allowBack) {
6176
+ return allowBack && (key.name === "left" || key.name === "b");
6177
+ }
6126
6178
  async function select(io, message, choices, options = {}) {
6127
6179
  if (choices.length === 0) throw new Error("select() requires at least one choice.");
6180
+ const allowBack = options.allowBack === true;
6128
6181
  const fallbackIndex = Math.max(
6129
6182
  0,
6130
6183
  choices.findIndex((choice) => choice.value === options.defaultValue)
6131
6184
  );
6132
6185
  if (!isInteractive(io)) {
6133
- io.output.write(`${renderSelect(message, choices, fallbackIndex)}
6186
+ io.output.write(`${renderSelect(message, choices, fallbackIndex, allowBack)}
6134
6187
  `);
6135
6188
  return choices[fallbackIndex].value;
6136
6189
  }
@@ -6139,9 +6192,10 @@ async function select(io, message, choices, options = {}) {
6139
6192
  let index = fallbackIndex;
6140
6193
  try {
6141
6194
  for (; ; ) {
6142
- frame.draw(renderSelect(message, choices, index));
6195
+ frame.draw(renderSelect(message, choices, index, allowBack));
6143
6196
  const key = await reader.next();
6144
6197
  if (isCancel(key)) throw new PromptCancelled();
6198
+ if (isListBack(key, allowBack)) throw new PromptBack();
6145
6199
  if (key.name === "up") index = moveSelection(index, -1, choices.length);
6146
6200
  else if (key.name === "down") index = moveSelection(index, 1, choices.length);
6147
6201
  else if (key.name === "return" || key.name === "enter") {
@@ -6150,18 +6204,22 @@ async function select(io, message, choices, options = {}) {
6150
6204
  } else if (key.name === "j") index = moveSelection(index, 1, choices.length);
6151
6205
  else if (key.name === "k") index = moveSelection(index, -1, choices.length);
6152
6206
  }
6207
+ } catch (error) {
6208
+ frame.erase();
6209
+ throw error;
6153
6210
  } finally {
6154
6211
  reader.close();
6155
6212
  }
6156
6213
  }
6157
6214
  async function multiSelect(io, message, choices, options = {}) {
6215
+ const allowBack = options.allowBack === true;
6158
6216
  const selected = /* @__PURE__ */ new Set();
6159
6217
  choices.forEach((choice, position) => {
6160
6218
  if (options.defaults?.includes(choice.value)) selected.add(position);
6161
6219
  });
6162
6220
  const result = () => choices.filter((_, position) => selected.has(position)).map((choice) => choice.value);
6163
6221
  if (!isInteractive(io)) {
6164
- io.output.write(`${renderMultiSelect(message, choices, 0, selected)}
6222
+ io.output.write(`${renderMultiSelect(message, choices, 0, selected, allowBack)}
6165
6223
  `);
6166
6224
  return result();
6167
6225
  }
@@ -6170,9 +6228,10 @@ async function multiSelect(io, message, choices, options = {}) {
6170
6228
  let index = 0;
6171
6229
  try {
6172
6230
  for (; ; ) {
6173
- frame.draw(renderMultiSelect(message, choices, index, selected));
6231
+ frame.draw(renderMultiSelect(message, choices, index, selected, allowBack));
6174
6232
  const key = await reader.next();
6175
6233
  if (isCancel(key)) throw new PromptCancelled();
6234
+ if (isListBack(key, allowBack)) throw new PromptBack();
6176
6235
  if (key.name === "up") index = moveSelection(index, -1, choices.length);
6177
6236
  else if (key.name === "down") index = moveSelection(index, 1, choices.length);
6178
6237
  else if (key.name === "space") {
@@ -6187,14 +6246,18 @@ async function multiSelect(io, message, choices, options = {}) {
6187
6246
  return result();
6188
6247
  }
6189
6248
  }
6249
+ } catch (error) {
6250
+ frame.erase();
6251
+ throw error;
6190
6252
  } finally {
6191
6253
  reader.close();
6192
6254
  }
6193
6255
  }
6194
6256
  async function confirm(io, message, options = {}) {
6257
+ const allowBack = options.allowBack === true;
6195
6258
  let value = options.defaultValue ?? true;
6196
6259
  if (!isInteractive(io)) {
6197
- io.output.write(`${renderConfirm(message, value)}
6260
+ io.output.write(`${renderConfirm(message, value, allowBack)}
6198
6261
  `);
6199
6262
  return value;
6200
6263
  }
@@ -6202,9 +6265,10 @@ async function confirm(io, message, options = {}) {
6202
6265
  const reader = keyReader(io.input);
6203
6266
  try {
6204
6267
  for (; ; ) {
6205
- frame.draw(renderConfirm(message, value));
6268
+ frame.draw(renderConfirm(message, value, allowBack));
6206
6269
  const key = await reader.next();
6207
6270
  if (isCancel(key)) throw new PromptCancelled();
6271
+ if (allowBack && key.name === "b") throw new PromptBack();
6208
6272
  if (key.name === "up" || key.name === "left") value = true;
6209
6273
  else if (key.name === "down" || key.name === "right") value = false;
6210
6274
  else if (key.name === "y") value = true;
@@ -6214,14 +6278,18 @@ async function confirm(io, message, options = {}) {
6214
6278
  return value;
6215
6279
  }
6216
6280
  }
6281
+ } catch (error) {
6282
+ frame.erase();
6283
+ throw error;
6217
6284
  } finally {
6218
6285
  reader.close();
6219
6286
  }
6220
6287
  }
6221
6288
  async function text(io, message, options = {}) {
6289
+ const allowBack = options.allowBack === true;
6222
6290
  const fallback = options.defaultValue ?? "";
6223
6291
  if (!isInteractive(io)) {
6224
- io.output.write(`${renderText(message, fallback)}
6292
+ io.output.write(`${renderText(message, fallback, void 0, allowBack)}
6225
6293
  `);
6226
6294
  return fallback;
6227
6295
  }
@@ -6231,9 +6299,10 @@ async function text(io, message, options = {}) {
6231
6299
  let error;
6232
6300
  try {
6233
6301
  for (; ; ) {
6234
- frame.draw(renderText(message, value, error));
6302
+ frame.draw(renderText(message, value, error, allowBack));
6235
6303
  const key = await reader.next();
6236
6304
  if (isCancel(key)) throw new PromptCancelled();
6305
+ if (allowBack && key.name === "left") throw new PromptBack();
6237
6306
  error = void 0;
6238
6307
  if (key.name === "return" || key.name === "enter") {
6239
6308
  const problem = options.validate ? options.validate(value) : null;
@@ -6254,6 +6323,9 @@ async function text(io, message, options = {}) {
6254
6323
  }
6255
6324
  if (_isPrintable(key)) value += _keyText(key);
6256
6325
  }
6326
+ } catch (error_) {
6327
+ frame.erase();
6328
+ throw error_;
6257
6329
  } finally {
6258
6330
  reader.close();
6259
6331
  }
@@ -6277,10 +6349,10 @@ var CONFIG_CANDIDATES = [
6277
6349
  ];
6278
6350
  function chooseConfigFileName(cwd) {
6279
6351
  if (!supportsTypeScriptImports()) return "snapshot.config.mjs";
6280
- const pkgPath = path11__namespace.join(cwd, "package.json");
6281
- if (!fs10__namespace.existsSync(pkgPath)) return "snapshot.config.mjs";
6352
+ const pkgPath = path12__namespace.join(cwd, "package.json");
6353
+ if (!fs11__namespace.existsSync(pkgPath)) return "snapshot.config.mjs";
6282
6354
  try {
6283
- const parsed = JSON.parse(fs10__namespace.readFileSync(pkgPath, "utf8"));
6355
+ const parsed = JSON.parse(fs11__namespace.readFileSync(pkgPath, "utf8"));
6284
6356
  return parsed.type === "module" ? "snapshot.config.ts" : "snapshot.config.mjs";
6285
6357
  } catch {
6286
6358
  return "snapshot.config.mjs";
@@ -6294,7 +6366,7 @@ var SIZE_CHOICES = [
6294
6366
  ];
6295
6367
  function packageJsonHasConfig(cwd) {
6296
6368
  try {
6297
- const parsed = JSON.parse(fs10__namespace.readFileSync(path11__namespace.join(cwd, "package.json"), "utf8"));
6369
+ const parsed = JSON.parse(fs11__namespace.readFileSync(path12__namespace.join(cwd, "package.json"), "utf8"));
6298
6370
  return typeof parsed.snapshot === "object" && parsed.snapshot !== null && !Array.isArray(parsed.snapshot);
6299
6371
  } catch {
6300
6372
  return false;
@@ -6315,7 +6387,7 @@ function artifactAnswers(options) {
6315
6387
  }
6316
6388
  function topLevelDirs(cwd) {
6317
6389
  try {
6318
- return fs10__namespace.readdirSync(cwd, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules").map((entry) => entry.name).sort();
6390
+ return fs11__namespace.readdirSync(cwd, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules").map((entry) => entry.name).sort();
6319
6391
  } catch {
6320
6392
  return [];
6321
6393
  }
@@ -6328,7 +6400,7 @@ function describeJsonValue(value) {
6328
6400
  }
6329
6401
  function writeFileOrExplain(target, content) {
6330
6402
  try {
6331
- fs10__namespace.writeFileSync(target, content, "utf8");
6403
+ fs11__namespace.writeFileSync(target, content, "utf8");
6332
6404
  } catch (err) {
6333
6405
  const code = err.code;
6334
6406
  const reason = code === "EACCES" || code === "EPERM" || code === "EROFS" ? `permission denied (${code})` : code === "EISDIR" ? "it is a directory" : `${err.message}`;
@@ -6339,7 +6411,7 @@ function canWriteModuleConfig(name, cwd) {
6339
6411
  if (name.endsWith(".mjs") || name.endsWith(".ts")) return true;
6340
6412
  if (!name.endsWith(".js")) return false;
6341
6413
  try {
6342
- const parsed = JSON.parse(fs10__namespace.readFileSync(path11__namespace.join(cwd, "package.json"), "utf8"));
6414
+ const parsed = JSON.parse(fs11__namespace.readFileSync(path12__namespace.join(cwd, "package.json"), "utf8"));
6343
6415
  return parsed.type === "module";
6344
6416
  } catch {
6345
6417
  return false;
@@ -6374,19 +6446,19 @@ function serializePackageJson(raw, value) {
6374
6446
  return `${text2}${raw.endsWith("\n") ? crlf ? "\r\n" : "\n" : ""}`;
6375
6447
  }
6376
6448
  function updatePackageJson(cwd, notes) {
6377
- const packagePath = path11__namespace.join(cwd, "package.json");
6449
+ const packagePath = path12__namespace.join(cwd, "package.json");
6378
6450
  const unchanged = {
6379
6451
  scripts: [],
6380
6452
  dependency: { name: GENERATOR_NAME, range: null, added: false }
6381
6453
  };
6382
- if (!fs10__namespace.existsSync(packagePath)) {
6454
+ if (!fs11__namespace.existsSync(packagePath)) {
6383
6455
  notes.push("No package.json found, so npm scripts and the devDependency were not added.");
6384
6456
  return unchanged;
6385
6457
  }
6386
6458
  let raw;
6387
6459
  let parsed;
6388
6460
  try {
6389
- raw = fs10__namespace.readFileSync(packagePath, "utf8");
6461
+ raw = fs11__namespace.readFileSync(packagePath, "utf8");
6390
6462
  parsed = JSON.parse(raw);
6391
6463
  } catch {
6392
6464
  notes.push("Could not parse package.json; npm scripts and the devDependency were not added.");
@@ -6449,16 +6521,18 @@ function updatePackageJson(cwd, notes) {
6449
6521
  return { scripts: added, dependency };
6450
6522
  }
6451
6523
  var GITIGNORE_ENTRIES = ["snapshot/", "snapshot.tmp/"];
6452
- function resolvePresetChoice(answers, presetName) {
6524
+ function resolvePresetChoice(detectedName, presetName) {
6453
6525
  return {
6454
6526
  value: presetName,
6455
6527
  label: presetName,
6456
- hint: presetName === answers.preset ? "detected" : void 0
6528
+ hint: presetName === detectedName ? "detected" : void 0
6457
6529
  };
6458
6530
  }
6459
6531
  async function runInit(options) {
6460
6532
  const io = options.io ?? defaultIO();
6461
6533
  const detected = detectPreset(options.cwd, options.preset);
6534
+ let reason = detected.reason;
6535
+ let fallback = detected.fallback;
6462
6536
  const notes = [];
6463
6537
  const answers = {
6464
6538
  preset: detected.presetName,
@@ -6480,73 +6554,37 @@ async function runInit(options) {
6480
6554
  if (interactive) {
6481
6555
  io.output.write("\n codebase-snapshot\n");
6482
6556
  io.output.write(" Turn this repository into an LLM-ready, redacted, byte-bounded snapshot.\n\n");
6483
- io.output.write(` Detected ${detected.presetName}
6557
+ if (fallback) {
6558
+ io.output.write(" \u26A0 Could not tell what kind of project this is.\n");
6559
+ io.output.write(" No package.json, go.mod, Cargo.toml, pyproject.toml, Gemfile,\n");
6560
+ io.output.write(" composer.json, pom.xml, *.csproj, next.config.*, vite.config.*, *.tf\n");
6561
+ io.output.write(" or workspace marker was found here.\n\n");
6562
+ } else {
6563
+ io.output.write(` Detected ${detected.presetName}
6484
6564
  `);
6485
- io.output.write(` Directory ${options.cwd}
6565
+ io.output.write(` Because ${reason}
6486
6566
 
6487
6567
  `);
6488
- const useDetected = await confirm(io, `Set up for ${detected.presetName}?`, {
6489
- defaultValue: true
6490
- });
6491
- if (!useDetected) {
6492
- answers.preset = await select(
6493
- io,
6494
- "What kind of project is this?",
6495
- listPresetNames().map((name) => resolvePresetChoice(answers, name))
6496
- );
6497
- const chosen = detectPreset(options.cwd, answers.preset);
6498
- answers.preset = chosen.presetName;
6499
- }
6500
- const scope = await select(
6501
- io,
6502
- "What should go into the snapshot?",
6503
- [
6504
- { value: "all", label: "Everything the scanner finds", hint: "recommended \xB7 respects .gitignore" },
6505
- { value: "folders", label: "Only specific folders\u2026" },
6506
- { value: "no-tests", label: "Everything, but skip tests" },
6507
- { value: "custom", label: "Let me write glob patterns" }
6508
- ],
6509
- { defaultValue: "all" }
6510
- );
6511
- if (scope === "folders") {
6512
- const dirs = topLevelDirs(options.cwd).slice(0, 40);
6513
- if (dirs.length === 0) {
6514
- notes.push("No top-level directories found; the snapshot will include everything.");
6515
- } else {
6516
- const chosen = await multiSelect(
6517
- io,
6518
- "Select folders",
6519
- dirs.map((name) => ({ value: name, label: `${name}/` })),
6520
- { defaults: dirs }
6521
- );
6522
- answers.include = chosen.map((name) => `${name}/**`);
6523
- }
6524
- } else if (scope === "no-tests") {
6525
- answers.excludePatterns = [
6526
- ...answers.excludePatterns,
6527
- "**/*.test.*",
6528
- "**/*.spec.*",
6529
- "tests",
6530
- "test"
6531
- ];
6532
- } else if (scope === "custom") {
6533
- const raw = await text(io, "Glob patterns to include (comma separated)", {
6534
- defaultValue: ""
6535
- });
6536
- answers.include = raw.split(",").map((value) => value.trim()).filter(Boolean);
6537
- }
6538
- const sizeChoice = await select(io, "How large should each part be?", SIZE_CHOICES, {
6539
- defaultValue: "120k"
6540
- });
6541
- if (sizeChoice === "custom") {
6542
- const raw = await text(io, "Part size (e.g. 150k, 1.5mb, 900000)", {
6543
- defaultValue: String(answers.maxPartBytes),
6544
- validate: (value) => parseByteSize(value) === void 0 ? "Not a valid size." : null
6545
- });
6546
- answers.maxPartBytes = parseByteSize(raw) ?? answers.maxPartBytes;
6547
- } else {
6548
- answers.maxPartBytes = sizeChoice;
6549
6568
  }
6569
+ io.output.write(` Directory ${options.cwd}
6570
+
6571
+ `);
6572
+ const paceChoices = fallback ? [
6573
+ { value: "choose", label: "Pick the closest preset", hint: "then generate with the defaults" },
6574
+ { value: "recommended", label: "Use the defaults", hint: "generic \xB7 everything the scanner finds" },
6575
+ { value: "walk", label: "Go through each setting", hint: "every question starts on its default" }
6576
+ ] : [
6577
+ {
6578
+ value: "recommended",
6579
+ label: "Use the recommended setup",
6580
+ hint: `${detected.presetName} \xB7 config \xB7 npm scripts \xB7 install \xB7 generate`
6581
+ },
6582
+ { value: "walk", label: "Go through each setting", hint: "every question starts on its default" },
6583
+ { value: "choose", label: "Pick a different preset", hint: "everything else stays at the defaults" }
6584
+ ];
6585
+ const baseInclude = [...answers.include];
6586
+ const baseExclude = [...answers.excludePatterns];
6587
+ let lastScope = "all";
6550
6588
  const artifactChoices = [
6551
6589
  { value: "readme", label: "README.md", hint: "orientation page" },
6552
6590
  { value: "manifest", label: "manifest.json", hint: "required by validate and report" },
@@ -6556,41 +6594,163 @@ async function runInit(options) {
6556
6594
  { value: "audit", label: "reports/audit.md", hint: "security and health findings" },
6557
6595
  { value: "monolith", label: "full-codebase.json + .md", hint: "roughly doubles output" }
6558
6596
  ];
6559
- const presetEnabled = ARTIFACT_NAMES.filter((name) => answers.artifacts[name]);
6560
- const selectedArtifacts = await multiSelect(
6561
- io,
6562
- "Which artifacts should be generated?",
6563
- artifactChoices,
6564
- { defaults: presetEnabled }
6565
- );
6566
- for (const name of ARTIFACT_NAMES) answers.artifacts[name] = selectedArtifacts.includes(name);
6567
- const enabledCount = ARTIFACT_NAMES.filter((name) => answers.artifacts[name]).length;
6568
- answers.includeSensitiveFiles = await confirm(
6569
- io,
6570
- "Include credential files (.env, *.pem, .npmrc, .ssh/)?",
6571
- { defaultValue: false }
6572
- );
6573
- io.output.write("\n Review\n\n");
6574
- io.output.write(` project ${answers.preset}
6597
+ const scopeStep = async () => {
6598
+ for (; ; ) {
6599
+ const scope = await select(
6600
+ io,
6601
+ "What should go into the snapshot?",
6602
+ [
6603
+ { value: "all", label: "Everything the scanner finds", hint: "recommended \xB7 respects .gitignore" },
6604
+ { value: "folders", label: "Only specific folders\u2026" },
6605
+ { value: "no-tests", label: "Everything, but skip tests" },
6606
+ { value: "custom", label: "Let me write glob patterns" }
6607
+ ],
6608
+ { defaultValue: lastScope, allowBack: true }
6609
+ );
6610
+ lastScope = scope;
6611
+ answers.include = [...baseInclude];
6612
+ answers.excludePatterns = [...baseExclude];
6613
+ try {
6614
+ if (scope === "folders") {
6615
+ const dirs = topLevelDirs(options.cwd).slice(0, 40);
6616
+ if (dirs.length === 0) {
6617
+ notes.push("No top-level directories found; the snapshot will include everything.");
6618
+ } else {
6619
+ const chosen = await multiSelect(
6620
+ io,
6621
+ "Select folders",
6622
+ dirs.map((name) => ({ value: name, label: `${name}/` })),
6623
+ { defaults: dirs, allowBack: true }
6624
+ );
6625
+ answers.include = chosen.map((name) => `${name}/**`);
6626
+ }
6627
+ } else if (scope === "no-tests") {
6628
+ answers.excludePatterns = [...baseExclude, "**/*.test.*", "**/*.spec.*", "tests", "test"];
6629
+ } else if (scope === "custom") {
6630
+ const raw = await text(io, "Glob patterns to include (comma separated)", {
6631
+ defaultValue: baseInclude.join(", "),
6632
+ allowBack: true
6633
+ });
6634
+ answers.include = raw.split(",").map((value) => value.trim()).filter(Boolean);
6635
+ }
6636
+ return;
6637
+ } catch (error) {
6638
+ if (error instanceof PromptBack) continue;
6639
+ throw error;
6640
+ }
6641
+ }
6642
+ };
6643
+ const sizeStep = async () => {
6644
+ for (; ; ) {
6645
+ const currentBudget = String(answers.maxPartBytes);
6646
+ const sizeChoice = await select(io, "How large should each part be?", SIZE_CHOICES, {
6647
+ defaultValue: SIZE_CHOICES.some((choice) => choice.value === currentBudget) ? currentBudget : "custom",
6648
+ allowBack: true
6649
+ });
6650
+ if (sizeChoice !== "custom") {
6651
+ answers.maxPartBytes = sizeChoice;
6652
+ return;
6653
+ }
6654
+ try {
6655
+ const raw = await text(io, "Part size (e.g. 150k, 1.5mb, 900000)", {
6656
+ defaultValue: currentBudget,
6657
+ validate: (value) => parseByteSize(value) === void 0 ? "Not a valid size." : null,
6658
+ allowBack: true
6659
+ });
6660
+ answers.maxPartBytes = parseByteSize(raw) ?? answers.maxPartBytes;
6661
+ return;
6662
+ } catch (error) {
6663
+ if (error instanceof PromptBack) continue;
6664
+ throw error;
6665
+ }
6666
+ }
6667
+ };
6668
+ const artifactsStep = async () => {
6669
+ const enabled = ARTIFACT_NAMES.filter((name) => answers.artifacts[name]);
6670
+ const selectedArtifacts = await multiSelect(
6671
+ io,
6672
+ "Which artifacts should be generated?",
6673
+ artifactChoices,
6674
+ { defaults: enabled, allowBack: true }
6675
+ );
6676
+ for (const name of ARTIFACT_NAMES) answers.artifacts[name] = selectedArtifacts.includes(name);
6677
+ };
6678
+ const credentialsStep = async () => {
6679
+ answers.includeSensitiveFiles = await confirm(
6680
+ io,
6681
+ "Include credential files (.env, *.pem, *.npmrc, *.ssh/)?",
6682
+ { defaultValue: answers.includeSensitiveFiles, allowBack: true }
6683
+ );
6684
+ };
6685
+ const reviewStep = async () => {
6686
+ const enabledCount = ARTIFACT_NAMES.filter((name) => answers.artifacts[name]).length;
6687
+ io.output.write("\n Review\n\n");
6688
+ io.output.write(` project ${answers.preset}
6575
6689
  `);
6576
- io.output.write(` include ${answers.include.length ? answers.include.join(", ") : "everything"}
6690
+ io.output.write(` include ${answers.include.length ? answers.include.join(", ") : "everything"}
6577
6691
  `);
6578
- io.output.write(` part size ${String(answers.maxPartBytes)}
6692
+ io.output.write(` part size ${String(answers.maxPartBytes)}
6579
6693
  `);
6580
- io.output.write(` artifacts ${enabledCount} of ${ARTIFACT_NAMES.length}
6694
+ io.output.write(` artifacts ${enabledCount} of ${ARTIFACT_NAMES.length}
6581
6695
  `);
6582
- io.output.write(` credentials ${answers.includeSensitiveFiles ? "included" : "skipped"}
6696
+ io.output.write(` credentials ${answers.includeSensitiveFiles ? "included" : "skipped"}
6583
6697
 
6584
6698
  `);
6585
- io.output.write(" Will write snapshot.config with your answers, plus every other setting\n");
6586
- io.output.write(" at its default \u2014 so nothing is hidden behind the docs.\n\n");
6587
- const proceed = await confirm(io, "Write this and continue?", { defaultValue: true });
6588
- if (!proceed) throw new PromptCancelled();
6699
+ io.output.write(" Will write snapshot.config with your answers, plus every other setting\n");
6700
+ io.output.write(" at its default \u2014 so nothing is hidden behind the docs.\n\n");
6701
+ const proceed = await confirm(io, "Write this and continue?", {
6702
+ defaultValue: true,
6703
+ allowBack: true
6704
+ });
6705
+ if (!proceed) throw new PromptBack();
6706
+ };
6707
+ const walk = async () => {
6708
+ const steps = [scopeStep, sizeStep, artifactsStep, credentialsStep, reviewStep];
6709
+ let index = 0;
6710
+ while (index < steps.length) {
6711
+ try {
6712
+ await steps[index]();
6713
+ index += 1;
6714
+ } catch (error) {
6715
+ if (!(error instanceof PromptBack)) throw error;
6716
+ if (index === 0) return false;
6717
+ index -= 1;
6718
+ io.output.write("\n");
6719
+ }
6720
+ }
6721
+ return true;
6722
+ };
6723
+ for (; ; ) {
6724
+ const pace = await select(io, "How should I set this up?", paceChoices, {
6725
+ defaultValue: fallback ? "choose" : "recommended"
6726
+ });
6727
+ if (pace === "choose") {
6728
+ try {
6729
+ answers.preset = await select(
6730
+ io,
6731
+ "What kind of project is this?",
6732
+ listPresetNames().map(
6733
+ (name) => resolvePresetChoice(fallback ? null : detected.presetName, name)
6734
+ ),
6735
+ { defaultValue: answers.preset, allowBack: true }
6736
+ );
6737
+ } catch (error) {
6738
+ if (error instanceof PromptBack) continue;
6739
+ throw error;
6740
+ }
6741
+ const chosen = detectPreset(options.cwd, answers.preset);
6742
+ answers.preset = chosen.presetName;
6743
+ reason = null;
6744
+ fallback = false;
6745
+ }
6746
+ if (pace === "walk" && !await walk()) continue;
6747
+ break;
6748
+ }
6589
6749
  }
6590
6750
  const configText = renderConfig(answers);
6591
6751
  const preferredName = chooseConfigFileName(options.cwd);
6592
6752
  const presentConfigs = CONFIG_CANDIDATES.filter(
6593
- (name) => fs10__namespace.existsSync(path11__namespace.join(options.cwd, name))
6753
+ (name) => fs11__namespace.existsSync(path12__namespace.join(options.cwd, name))
6594
6754
  );
6595
6755
  const present = presentConfigs[0] ?? null;
6596
6756
  if (presentConfigs.length > 1) {
@@ -6599,7 +6759,7 @@ async function runInit(options) {
6599
6759
  );
6600
6760
  }
6601
6761
  const configName = present !== null && options.force ? present : preferredName;
6602
- const configPath = path11__namespace.join(options.cwd, configName);
6762
+ const configPath = path12__namespace.join(options.cwd, configName);
6603
6763
  const reused = present !== null && options.force !== true && options.reuseConfig === true;
6604
6764
  if (reused) notes.push(`Kept the existing ${present} instead of writing a new one.`);
6605
6765
  if (options.dryRun) {
@@ -6609,6 +6769,8 @@ async function runInit(options) {
6609
6769
  written: false,
6610
6770
  reused,
6611
6771
  preset: answers.preset,
6772
+ reason,
6773
+ fallback,
6612
6774
  scripts: [],
6613
6775
  gitignoreEntries: [],
6614
6776
  dependency: { name: GENERATOR_NAME, range: `^${GENERATOR_VERSION}`, added: false },
@@ -6630,7 +6792,7 @@ async function runInit(options) {
6630
6792
  '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.'
6631
6793
  );
6632
6794
  }
6633
- if (fs10__namespace.existsSync(configPath) && !options.force && !reused) {
6795
+ if (fs11__namespace.existsSync(configPath) && !options.force && !reused) {
6634
6796
  throw new Error(`${configName} already exists. Re-run with --force to replace it.`);
6635
6797
  }
6636
6798
  if (!reused) writeFileOrExplain(configPath, configText);
@@ -6643,12 +6805,56 @@ async function runInit(options) {
6643
6805
  written: true,
6644
6806
  reused,
6645
6807
  preset: answers.preset,
6808
+ reason,
6809
+ fallback,
6646
6810
  scripts,
6647
6811
  gitignoreEntries: ignore.added,
6648
6812
  dependency,
6649
6813
  notes
6650
6814
  };
6651
6815
  }
6816
+ function describeTarget(cwd) {
6817
+ const relative7 = path12__namespace.relative(process.cwd(), cwd);
6818
+ if (relative7 === "") return "this directory";
6819
+ if (relative7.startsWith("..") || path12__namespace.isAbsolute(relative7)) return cwd;
6820
+ return relative7;
6821
+ }
6822
+ function firstRunMessage(reason, cwd) {
6823
+ const where = describeTarget(cwd);
6824
+ if (reason === "empty") {
6825
+ return `Nothing to snapshot: ${where} has no files. Run this inside the repository you want to snapshot, or pass --cwd.`;
6826
+ }
6827
+ return `No configuration found in ${where} and this session is not interactive, so nothing was generated.
6828
+ Choose one:
6829
+ ${GENERATOR_NAME} --autopilot detect the project, set it up and generate, no questions
6830
+ ${GENERATOR_NAME} init --yes write a config from the detected defaults, then generate
6831
+ ${GENERATOR_NAME} init write a config interactively`;
6832
+ }
6833
+ function planFirstRun(facts) {
6834
+ if (facts.emptyTarget) return { action: "refuse", exitCode: 1, reason: "empty" };
6835
+ if (facts.autopilot) return { action: "setup", interactive: false };
6836
+ if (facts.hasConfig || facts.hasPriorSnapshot) return { action: "generate" };
6837
+ if (facts.interactive) return { action: "setup", interactive: true };
6838
+ return { action: "refuse", exitCode: 2, reason: "undecided" };
6839
+ }
6840
+ var NOT_CONTENT = /* @__PURE__ */ new Set([".git", ".hg", ".svn", ".bzr", ".DS_Store", "Thumbs.db"]);
6841
+ function isEffectivelyEmpty(cwd) {
6842
+ try {
6843
+ return fs11__namespace.readdirSync(cwd).every((entry) => NOT_CONTENT.has(entry));
6844
+ } catch {
6845
+ return false;
6846
+ }
6847
+ }
6848
+ function hasPriorSnapshot(cwd, outDir) {
6849
+ let parsed;
6850
+ try {
6851
+ parsed = JSON.parse(fs11__namespace.readFileSync(path12__namespace.join(cwd, outDir, ".codebase-snapshot"), "utf8"));
6852
+ } catch {
6853
+ return false;
6854
+ }
6855
+ if (parsed === null || typeof parsed !== "object") return false;
6856
+ return isOurGeneratorName(parsed.generator);
6857
+ }
6652
6858
 
6653
6859
  // src/index.ts
6654
6860
  var SCHEMA_VERSION = "3.0.0";
@@ -6730,13 +6936,13 @@ function buildImportResolver(files) {
6730
6936
  const relativePath = file.relativePath;
6731
6937
  remember(byPath, relativePath, relativePath);
6732
6938
  remember(byPathFolded, relativePath.toLowerCase(), relativePath);
6733
- const extension = path11__namespace.posix.extname(relativePath);
6939
+ const extension = path12__namespace.posix.extname(relativePath);
6734
6940
  if (!SOURCE_EXTENSIONS.has(extension)) continue;
6735
6941
  const stem = relativePath.slice(0, -extension.length);
6736
6942
  remember(byStem, stem, relativePath);
6737
6943
  remember(byStemFolded, stem.toLowerCase(), relativePath);
6738
- if (path11__namespace.posix.basename(stem) === "index") {
6739
- const directory = path11__namespace.posix.dirname(stem);
6944
+ if (path12__namespace.posix.basename(stem) === "index") {
6945
+ const directory = path12__namespace.posix.dirname(stem);
6740
6946
  remember(byStem, directory, relativePath);
6741
6947
  remember(byStemFolded, directory.toLowerCase(), relativePath);
6742
6948
  }
@@ -6745,8 +6951,8 @@ function buildImportResolver(files) {
6745
6951
  }
6746
6952
  function resolveSpecifier(fromFile, specifier, resolver) {
6747
6953
  if (!RELATIVE_SPECIFIER.test(specifier)) return null;
6748
- const joined = path11__namespace.posix.normalize(
6749
- path11__namespace.posix.join(path11__namespace.posix.dirname(fromFile), specifier)
6954
+ const joined = path12__namespace.posix.normalize(
6955
+ path12__namespace.posix.join(path12__namespace.posix.dirname(fromFile), specifier)
6750
6956
  );
6751
6957
  const exact = (key) => resolver.byPath.get(key) ?? resolver.byPathFolded.get(key.toLowerCase()) ?? null;
6752
6958
  const direct = exact(joined);
@@ -6839,11 +7045,11 @@ function verifyInMemory(files, parts, maxPartBytes) {
6839
7045
  return errors;
6840
7046
  }
6841
7047
  function resolveSafeOutputDirs(config) {
6842
- const cwd = path11__namespace.resolve(config.cwd);
6843
- const outDir = path11__namespace.resolve(cwd, config.outDir);
6844
- const tmpDir = path11__namespace.resolve(cwd, config.tmpDir);
6845
- const root = path11__namespace.parse(cwd).root;
6846
- const contains = (parent, child) => child === parent || child.startsWith(`${parent}${path11__namespace.sep}`);
7048
+ const cwd = path12__namespace.resolve(config.cwd);
7049
+ const outDir = path12__namespace.resolve(cwd, config.outDir);
7050
+ const tmpDir = path12__namespace.resolve(cwd, config.tmpDir);
7051
+ const root = path12__namespace.parse(cwd).root;
7052
+ const contains = (parent, child) => child === parent || child.startsWith(`${parent}${path12__namespace.sep}`);
6847
7053
  const reject = (reason) => {
6848
7054
  throw new Error(
6849
7055
  `Refusing to generate: ${reason}. outDir=${outDir} tmpDir=${tmpDir} cwd=${cwd}`
@@ -6899,14 +7105,14 @@ function listEntries(dir) {
6899
7105
  const walk = (absolute, prefix) => {
6900
7106
  let children;
6901
7107
  try {
6902
- children = fs10__namespace.readdirSync(absolute, { withFileTypes: true });
7108
+ children = fs11__namespace.readdirSync(absolute, { withFileTypes: true });
6903
7109
  } catch {
6904
7110
  return;
6905
7111
  }
6906
7112
  for (const child of children) {
6907
- const relative6 = prefix === "" ? child.name : `${prefix}/${child.name}`;
6908
- entries.push(relative6);
6909
- if (child.isDirectory()) walk(path11__namespace.join(absolute, child.name), relative6);
7113
+ const relative7 = prefix === "" ? child.name : `${prefix}/${child.name}`;
7114
+ entries.push(relative7);
7115
+ if (child.isDirectory()) walk(path12__namespace.join(absolute, child.name), relative7);
6910
7116
  }
6911
7117
  };
6912
7118
  walk(dir, "");
@@ -6923,28 +7129,28 @@ function markerPayload(dir, state, id, createdAt) {
6923
7129
  pid: process.pid,
6924
7130
  entries: listEntries(dir)
6925
7131
  };
6926
- const manifestPath = path11__namespace.join(dir, "manifest.json");
6927
- if (fs10__namespace.existsSync(manifestPath)) {
6928
- marker2.manifestSha256 = sha256Hex2(fs10__namespace.readFileSync(manifestPath));
7132
+ const manifestPath = path12__namespace.join(dir, "manifest.json");
7133
+ if (fs11__namespace.existsSync(manifestPath)) {
7134
+ marker2.manifestSha256 = sha256Hex2(fs11__namespace.readFileSync(manifestPath));
6929
7135
  }
6930
7136
  return marker2;
6931
7137
  }
6932
7138
  function writeOwnershipMarker(dir, marker2) {
6933
- fs10__namespace.writeFileSync(
6934
- path11__namespace.join(dir, OWNERSHIP_MARKER),
7139
+ fs11__namespace.writeFileSync(
7140
+ path12__namespace.join(dir, OWNERSHIP_MARKER),
6935
7141
  `${JSON.stringify(marker2, null, 2)}
6936
7142
  `,
6937
7143
  "utf8"
6938
7144
  );
6939
7145
  }
6940
7146
  function readOwnershipMarker(dir) {
6941
- const markerPath = path11__namespace.join(dir, OWNERSHIP_MARKER);
6942
- if (!fs10__namespace.existsSync(markerPath)) {
7147
+ const markerPath = path12__namespace.join(dir, OWNERSHIP_MARKER);
7148
+ if (!fs11__namespace.existsSync(markerPath)) {
6943
7149
  return { marker: null, problem: "it holds no .codebase-snapshot marker" };
6944
7150
  }
6945
7151
  let raw;
6946
7152
  try {
6947
- raw = fs10__namespace.readFileSync(markerPath, "utf8");
7153
+ raw = fs11__namespace.readFileSync(markerPath, "utf8");
6948
7154
  } catch {
6949
7155
  return { marker: null, problem: "its .codebase-snapshot marker could not be read" };
6950
7156
  }
@@ -7012,11 +7218,11 @@ function assessSnapshotDirectory(dir) {
7012
7218
  marker: marker2
7013
7219
  };
7014
7220
  }
7015
- const manifestPath = path11__namespace.join(dir, "manifest.json");
7016
- if (fs10__namespace.existsSync(manifestPath)) {
7221
+ const manifestPath = path12__namespace.join(dir, "manifest.json");
7222
+ if (fs11__namespace.existsSync(manifestPath)) {
7017
7223
  let raw;
7018
7224
  try {
7019
- raw = fs10__namespace.readFileSync(manifestPath, "utf8");
7225
+ raw = fs11__namespace.readFileSync(manifestPath, "utf8");
7020
7226
  } catch {
7021
7227
  return {
7022
7228
  owned: false,
@@ -7098,13 +7304,13 @@ function containsNoFiles(dir) {
7098
7304
  const current = pending.pop();
7099
7305
  let entries;
7100
7306
  try {
7101
- entries = fs10__namespace.readdirSync(current, { withFileTypes: true });
7307
+ entries = fs11__namespace.readdirSync(current, { withFileTypes: true });
7102
7308
  } catch {
7103
7309
  return false;
7104
7310
  }
7105
7311
  for (const entry of entries) {
7106
7312
  if (entry.isDirectory()) {
7107
- pending.push(path11__namespace.join(current, entry.name));
7313
+ pending.push(path12__namespace.join(current, entry.name));
7108
7314
  } else if (!IGNORABLE_ENTRIES.has(entry.name)) {
7109
7315
  return false;
7110
7316
  }
@@ -7113,13 +7319,13 @@ function containsNoFiles(dir) {
7113
7319
  return true;
7114
7320
  }
7115
7321
  function assertReplaceable(target, label, secretRules) {
7116
- if (!fs10__namespace.existsSync(target)) return;
7117
- if (!fs10__namespace.statSync(target).isDirectory()) {
7322
+ if (!fs11__namespace.existsSync(target)) return;
7323
+ if (!fs11__namespace.statSync(target).isDirectory()) {
7118
7324
  throw new Error(
7119
7325
  `Refusing to write into ${label} ${target}: it exists but is not a directory.`
7120
7326
  );
7121
7327
  }
7122
- const entries = fs10__namespace.readdirSync(target);
7328
+ const entries = fs11__namespace.readdirSync(target);
7123
7329
  if (entries.length === 0) return;
7124
7330
  if (entries.every((name) => IGNORABLE_ENTRIES.has(name))) return;
7125
7331
  if (containsNoFiles(target)) return;
@@ -7139,18 +7345,18 @@ function assertReplaceable(target, label, secretRules) {
7139
7345
  );
7140
7346
  }
7141
7347
  function assertStagingRootUsable(tmpDir, secretRules) {
7142
- if (!fs10__namespace.existsSync(tmpDir)) return;
7143
- if (!fs10__namespace.statSync(tmpDir).isDirectory()) {
7348
+ if (!fs11__namespace.existsSync(tmpDir)) return;
7349
+ if (!fs11__namespace.statSync(tmpDir).isDirectory()) {
7144
7350
  throw new Error(`Refusing to write into tmpDir ${tmpDir}: it exists but is not a directory.`);
7145
7351
  }
7146
- const entries = fs10__namespace.readdirSync(tmpDir);
7352
+ const entries = fs11__namespace.readdirSync(tmpDir);
7147
7353
  if (entries.length === 0) return;
7148
7354
  const foreign = entries.filter((name) => {
7149
- const full = path11__namespace.join(tmpDir, name);
7355
+ const full = path12__namespace.join(tmpDir, name);
7150
7356
  if (!name.startsWith(RUN_DIR_PREFIX)) return true;
7151
7357
  let stats;
7152
7358
  try {
7153
- stats = fs10__namespace.statSync(full);
7359
+ stats = fs11__namespace.statSync(full);
7154
7360
  } catch {
7155
7361
  return true;
7156
7362
  }
@@ -7158,7 +7364,7 @@ function assertStagingRootUsable(tmpDir, secretRules) {
7158
7364
  const { marker: marker2 } = readOwnershipMarker(full);
7159
7365
  if (marker2) return false;
7160
7366
  try {
7161
- return fs10__namespace.readdirSync(full).length > 0;
7367
+ return fs11__namespace.readdirSync(full).length > 0;
7162
7368
  } catch {
7163
7369
  return true;
7164
7370
  }
@@ -7173,13 +7379,13 @@ function assertStagingRootUsable(tmpDir, secretRules) {
7173
7379
  function cleanStaleRunDirs(tmpDir) {
7174
7380
  let entries;
7175
7381
  try {
7176
- entries = fs10__namespace.readdirSync(tmpDir);
7382
+ entries = fs11__namespace.readdirSync(tmpDir);
7177
7383
  } catch {
7178
7384
  return;
7179
7385
  }
7180
7386
  for (const name of entries) {
7181
7387
  if (!name.startsWith(RUN_DIR_PREFIX)) continue;
7182
- const full = path11__namespace.join(tmpDir, name);
7388
+ const full = path12__namespace.join(tmpDir, name);
7183
7389
  const { marker: marker2 } = readOwnershipMarker(full);
7184
7390
  if (marker2) {
7185
7391
  const created = Date.parse(marker2.createdAt);
@@ -7187,33 +7393,33 @@ function cleanStaleRunDirs(tmpDir) {
7187
7393
  const abandoned = typeof marker2.pid === "number" && !isProcessAlive(marker2.pid);
7188
7394
  if (aged || abandoned) {
7189
7395
  try {
7190
- fs10__namespace.rmSync(full, { recursive: true, force: true });
7396
+ fs11__namespace.rmSync(full, { recursive: true, force: true });
7191
7397
  } catch {
7192
7398
  }
7193
7399
  }
7194
7400
  continue;
7195
7401
  }
7196
7402
  try {
7197
- if (fs10__namespace.readdirSync(full).length === 0) fs10__namespace.rmSync(full, { recursive: true, force: true });
7403
+ if (fs11__namespace.readdirSync(full).length === 0) fs11__namespace.rmSync(full, { recursive: true, force: true });
7198
7404
  } catch {
7199
7405
  }
7200
7406
  }
7201
7407
  }
7202
7408
  function removeIfEmpty(dir) {
7203
7409
  try {
7204
- if (fs10__namespace.readdirSync(dir).length === 0) fs10__namespace.rmSync(dir, { recursive: true, force: true });
7410
+ if (fs11__namespace.readdirSync(dir).length === 0) fs11__namespace.rmSync(dir, { recursive: true, force: true });
7205
7411
  } catch {
7206
7412
  }
7207
7413
  }
7208
7414
  function ensureParentDirectory(target, label) {
7209
- const parent = path11__namespace.dirname(target);
7210
- if (fs10__namespace.existsSync(parent) && !fs10__namespace.statSync(parent).isDirectory()) {
7415
+ const parent = path12__namespace.dirname(target);
7416
+ if (fs11__namespace.existsSync(parent) && !fs11__namespace.statSync(parent).isDirectory()) {
7211
7417
  throw new Error(
7212
7418
  `Refusing to create ${label} ${target}: ${parent} exists but is not a directory.`
7213
7419
  );
7214
7420
  }
7215
7421
  try {
7216
- fs10__namespace.mkdirSync(parent, { recursive: true });
7422
+ fs11__namespace.mkdirSync(parent, { recursive: true });
7217
7423
  } catch (err) {
7218
7424
  throw new Error(
7219
7425
  `Refusing to create ${label} ${target}: its parent ${parent} could not be created (${err.message}).`
@@ -7222,7 +7428,7 @@ function ensureParentDirectory(target, label) {
7222
7428
  }
7223
7429
  function acquireRunLock(stagingRoot) {
7224
7430
  const key = lockKeyFor(stagingRoot);
7225
- const lockPath = path11__namespace.join(os__namespace.tmpdir(), `codebase-snapshot-${key}.lock`);
7431
+ const lockPath = path12__namespace.join(os__namespace.tmpdir(), `codebase-snapshot-${key}.lock`);
7226
7432
  const payload = `${JSON.stringify({
7227
7433
  pid: process.pid,
7228
7434
  stagingRoot,
@@ -7231,22 +7437,22 @@ function acquireRunLock(stagingRoot) {
7231
7437
  `;
7232
7438
  for (let attempt = 0; attempt < 2; attempt += 1) {
7233
7439
  try {
7234
- const handle = fs10__namespace.openSync(lockPath, "wx");
7440
+ const handle = fs11__namespace.openSync(lockPath, "wx");
7235
7441
  try {
7236
- fs10__namespace.writeSync(handle, payload);
7442
+ fs11__namespace.writeSync(handle, payload);
7237
7443
  } finally {
7238
- fs10__namespace.closeSync(handle);
7444
+ fs11__namespace.closeSync(handle);
7239
7445
  }
7240
7446
  return () => {
7241
7447
  try {
7242
- fs10__namespace.rmSync(lockPath, { force: true });
7448
+ fs11__namespace.rmSync(lockPath, { force: true });
7243
7449
  } catch {
7244
7450
  }
7245
7451
  };
7246
7452
  } catch (err) {
7247
7453
  if (err.code !== "EEXIST") throw err;
7248
7454
  if (attempt === 0 && lockOwnerIsGone(lockPath)) {
7249
- fs10__namespace.rmSync(lockPath, { force: true });
7455
+ fs11__namespace.rmSync(lockPath, { force: true });
7250
7456
  continue;
7251
7457
  }
7252
7458
  throw new Error(
@@ -7259,7 +7465,7 @@ function acquireRunLock(stagingRoot) {
7259
7465
  function lockKeyFor(stagingRoot) {
7260
7466
  let key = stagingRoot;
7261
7467
  try {
7262
- key = fs10__namespace.realpathSync(stagingRoot);
7468
+ key = fs11__namespace.realpathSync(stagingRoot);
7263
7469
  } catch {
7264
7470
  }
7265
7471
  if (process.platform === "win32" || process.platform === "darwin") key = key.toLowerCase();
@@ -7268,7 +7474,7 @@ function lockKeyFor(stagingRoot) {
7268
7474
  function lockOwnerIsGone(lockPath) {
7269
7475
  let raw;
7270
7476
  try {
7271
- raw = fs10__namespace.readFileSync(lockPath, "utf8");
7477
+ raw = fs11__namespace.readFileSync(lockPath, "utf8");
7272
7478
  } catch {
7273
7479
  return false;
7274
7480
  }
@@ -7288,12 +7494,12 @@ function lockOwnerIsGone(lockPath) {
7288
7494
  }
7289
7495
  function writeManifestAtomically(manifestPath, manifest) {
7290
7496
  const tempPath = `${manifestPath}.${process.pid}.tmp`;
7291
- fs10__namespace.writeFileSync(tempPath, JSON.stringify(manifest, null, 2), "utf8");
7497
+ fs11__namespace.writeFileSync(tempPath, JSON.stringify(manifest, null, 2), "utf8");
7292
7498
  try {
7293
- fs10__namespace.renameSync(tempPath, manifestPath);
7499
+ fs11__namespace.renameSync(tempPath, manifestPath);
7294
7500
  } catch (err) {
7295
7501
  try {
7296
- fs10__namespace.rmSync(tempPath, { force: true });
7502
+ fs11__namespace.rmSync(tempPath, { force: true });
7297
7503
  } catch {
7298
7504
  }
7299
7505
  throw err;
@@ -7316,31 +7522,31 @@ function swapIntoPlace(stagingDir, outDir) {
7316
7522
  }
7317
7523
  function swapOnce(stagingDir, outDir) {
7318
7524
  const backupDir = `${outDir}.backup-${process.pid}-${Date.now()}`;
7319
- const hadPrevious = fs10__namespace.existsSync(outDir);
7320
- if (hadPrevious) fs10__namespace.renameSync(outDir, backupDir);
7525
+ const hadPrevious = fs11__namespace.existsSync(outDir);
7526
+ if (hadPrevious) fs11__namespace.renameSync(outDir, backupDir);
7321
7527
  try {
7322
7528
  try {
7323
- fs10__namespace.renameSync(stagingDir, outDir);
7529
+ fs11__namespace.renameSync(stagingDir, outDir);
7324
7530
  } catch (err) {
7325
7531
  if (err.code !== "EXDEV") throw err;
7326
7532
  const partialDir = `${outDir}.partial-${process.pid}-${Date.now()}`;
7327
7533
  try {
7328
- fs10__namespace.cpSync(stagingDir, partialDir, { recursive: true });
7329
- fs10__namespace.renameSync(partialDir, outDir);
7330
- fs10__namespace.rmSync(stagingDir, { recursive: true, force: true });
7534
+ fs11__namespace.cpSync(stagingDir, partialDir, { recursive: true });
7535
+ fs11__namespace.renameSync(partialDir, outDir);
7536
+ fs11__namespace.rmSync(stagingDir, { recursive: true, force: true });
7331
7537
  } catch (copyErr) {
7332
7538
  try {
7333
- fs10__namespace.rmSync(partialDir, { recursive: true, force: true });
7539
+ fs11__namespace.rmSync(partialDir, { recursive: true, force: true });
7334
7540
  } catch {
7335
7541
  }
7336
7542
  throw copyErr;
7337
7543
  }
7338
7544
  }
7339
- if (hadPrevious) fs10__namespace.rmSync(backupDir, { recursive: true, force: true });
7545
+ if (hadPrevious) fs11__namespace.rmSync(backupDir, { recursive: true, force: true });
7340
7546
  } catch (err) {
7341
- if (hadPrevious && fs10__namespace.existsSync(backupDir)) {
7342
- if (fs10__namespace.existsSync(outDir)) fs10__namespace.rmSync(outDir, { recursive: true, force: true });
7343
- fs10__namespace.renameSync(backupDir, outDir);
7547
+ if (hadPrevious && fs11__namespace.existsSync(backupDir)) {
7548
+ if (fs11__namespace.existsSync(outDir)) fs11__namespace.rmSync(outDir, { recursive: true, force: true });
7549
+ fs11__namespace.renameSync(backupDir, outDir);
7344
7550
  }
7345
7551
  throw err;
7346
7552
  }
@@ -7435,8 +7641,8 @@ function generateSnapshot(configInput, options) {
7435
7641
  // first run cannot have marked them yet, so the name alone keeps a run from recording its own
7436
7642
  // output — but only at these exact paths, not at every directory that shares the name.
7437
7643
  toolOwnedPaths: [config.outDir, config.tmpDir].map(
7438
- (directory) => path11__namespace.relative(config.cwd, path11__namespace.resolve(config.cwd, directory)).replace(/\\/g, "/")
7439
- ).filter((relative6) => relative6.length > 0 && !relative6.startsWith(".."))
7644
+ (directory) => path12__namespace.relative(config.cwd, path12__namespace.resolve(config.cwd, directory)).replace(/\\/g, "/")
7645
+ ).filter((relative7) => relative7.length > 0 && !relative7.startsWith(".."))
7440
7646
  });
7441
7647
  const files = [];
7442
7648
  const monolithBodies = /* @__PURE__ */ new Map();
@@ -7456,12 +7662,12 @@ function generateSnapshot(configInput, options) {
7456
7662
  let totalSizeBytes = 0;
7457
7663
  let largeFileCount = 0;
7458
7664
  for (const relativePath of collection.files) {
7459
- const absolutePath = path11__namespace.join(config.cwd, relativePath);
7665
+ const absolutePath = path12__namespace.join(config.cwd, relativePath);
7460
7666
  let stats;
7461
7667
  try {
7462
- stats = fs10__namespace.statSync(absolutePath);
7668
+ stats = fs11__namespace.statSync(absolutePath);
7463
7669
  if (!stats.isFile()) {
7464
- const nestedRepository = stats.isDirectory() && fs10__namespace.existsSync(path11__namespace.join(absolutePath, ".git"));
7670
+ const nestedRepository = stats.isDirectory() && fs11__namespace.existsSync(path12__namespace.join(absolutePath, ".git"));
7465
7671
  skipped.push(
7466
7672
  nestedRepository ? {
7467
7673
  path: relativePath,
@@ -7481,7 +7687,7 @@ function generateSnapshot(configInput, options) {
7481
7687
  }
7482
7688
  let buffer;
7483
7689
  try {
7484
- buffer = fs10__namespace.readFileSync(absolutePath);
7690
+ buffer = fs11__namespace.readFileSync(absolutePath);
7485
7691
  } catch {
7486
7692
  skipped.push({ path: relativePath, reason: "read-error" });
7487
7693
  continue;
@@ -7811,12 +8017,12 @@ function generateSnapshot(configInput, options) {
7811
8017
  let stagingDir = null;
7812
8018
  try {
7813
8019
  ensureParentDirectory(outDir, "outDir");
7814
- fs10__namespace.mkdirSync(tmpDir, { recursive: true });
8020
+ fs11__namespace.mkdirSync(tmpDir, { recursive: true });
7815
8021
  cleanStaleRunDirs(tmpDir);
7816
8022
  const snapshotId = crypto__namespace.randomUUID();
7817
8023
  const createdAt = (/* @__PURE__ */ new Date()).toISOString();
7818
- stagingDir = path11__namespace.join(tmpDir, `${RUN_DIR_PREFIX}${process.pid}-${snapshotId.slice(0, 8)}`);
7819
- fs10__namespace.mkdirSync(stagingDir);
8024
+ stagingDir = path12__namespace.join(tmpDir, `${RUN_DIR_PREFIX}${process.pid}-${snapshotId.slice(0, 8)}`);
8025
+ fs11__namespace.mkdirSync(stagingDir);
7820
8026
  writeOwnershipMarker(stagingDir, {
7821
8027
  generator: GENERATOR_NAME,
7822
8028
  version: GENERATOR_VERSION,
@@ -7826,41 +8032,41 @@ function generateSnapshot(configInput, options) {
7826
8032
  id: snapshotId,
7827
8033
  pid: process.pid
7828
8034
  });
7829
- const partsDir = path11__namespace.join(stagingDir, "parts");
7830
- const reportsDir = path11__namespace.join(stagingDir, "reports");
7831
- if (artifacts.parts) fs10__namespace.mkdirSync(partsDir, { recursive: true });
8035
+ const partsDir = path12__namespace.join(stagingDir, "parts");
8036
+ const reportsDir = path12__namespace.join(stagingDir, "reports");
8037
+ if (artifacts.parts) fs11__namespace.mkdirSync(partsDir, { recursive: true });
7832
8038
  if (artifacts.navigation || artifacts.projectTree || artifacts.audit) {
7833
- fs10__namespace.mkdirSync(reportsDir, { recursive: true });
8039
+ fs11__namespace.mkdirSync(reportsDir, { recursive: true });
7834
8040
  }
7835
8041
  if (artifacts.parts) {
7836
8042
  for (const part of parts) {
7837
- fs10__namespace.writeFileSync(path11__namespace.join(partsDir, part.filename), part.markdown, "utf8");
8043
+ fs11__namespace.writeFileSync(path12__namespace.join(partsDir, part.filename), part.markdown, "utf8");
7838
8044
  }
7839
8045
  }
7840
8046
  if (projectTree !== null) {
7841
- fs10__namespace.writeFileSync(path11__namespace.join(reportsDir, "project-tree.md"), projectTree, "utf8");
8047
+ fs11__namespace.writeFileSync(path12__namespace.join(reportsDir, "project-tree.md"), projectTree, "utf8");
7842
8048
  }
7843
8049
  if (artifacts.navigation) {
7844
- fs10__namespace.writeFileSync(path11__namespace.join(reportsDir, "navigation.md"), generateNavigation(manifest), "utf8");
8050
+ fs11__namespace.writeFileSync(path12__namespace.join(reportsDir, "navigation.md"), generateNavigation(manifest), "utf8");
7845
8051
  }
7846
8052
  if (artifacts.audit) {
7847
- fs10__namespace.writeFileSync(
7848
- path11__namespace.join(reportsDir, "audit.md"),
8053
+ fs11__namespace.writeFileSync(
8054
+ path12__namespace.join(reportsDir, "audit.md"),
7849
8055
  generateAuditReport(manifest, secretFindings, config.secretRules),
7850
8056
  "utf8"
7851
8057
  );
7852
8058
  }
7853
8059
  if (artifacts.readme) {
7854
- fs10__namespace.writeFileSync(path11__namespace.join(stagingDir, "README.md"), generateReadme(manifest, artifacts), "utf8");
8060
+ fs11__namespace.writeFileSync(path12__namespace.join(stagingDir, "README.md"), generateReadme(manifest, artifacts), "utf8");
7855
8061
  }
7856
8062
  if (artifacts.manifest) {
7857
- fs10__namespace.writeFileSync(path11__namespace.join(stagingDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf8");
8063
+ fs11__namespace.writeFileSync(path12__namespace.join(stagingDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf8");
7858
8064
  }
7859
8065
  if (monolithJson !== null) {
7860
- fs10__namespace.writeFileSync(path11__namespace.join(stagingDir, "full-codebase.json"), monolithJson, "utf8");
8066
+ fs11__namespace.writeFileSync(path12__namespace.join(stagingDir, "full-codebase.json"), monolithJson, "utf8");
7861
8067
  }
7862
8068
  if (monolithMarkdown !== null) {
7863
- fs10__namespace.writeFileSync(path11__namespace.join(stagingDir, "full-codebase.md"), monolithMarkdown, "utf8");
8069
+ fs11__namespace.writeFileSync(path12__namespace.join(stagingDir, "full-codebase.md"), monolithMarkdown, "utf8");
7864
8070
  }
7865
8071
  writeOwnershipMarker(stagingDir, markerPayload(stagingDir, "complete", snapshotId, createdAt));
7866
8072
  swapIntoPlace(stagingDir, outDir);
@@ -7869,7 +8075,7 @@ function generateSnapshot(configInput, options) {
7869
8075
  const pendingHook = runAfterGenerate(config, manifest);
7870
8076
  const shipHookState = () => {
7871
8077
  if (artifacts.manifest && manifest.warnings.length !== warningsBeforeHook) {
7872
- writeManifestAtomically(path11__namespace.join(outDir, "manifest.json"), manifest);
8078
+ writeManifestAtomically(path12__namespace.join(outDir, "manifest.json"), manifest);
7873
8079
  }
7874
8080
  writeOwnershipMarker(outDir, markerPayload(outDir, "complete", snapshotId, createdAt));
7875
8081
  };
@@ -7892,7 +8098,7 @@ function generateSnapshot(configInput, options) {
7892
8098
  } catch (err) {
7893
8099
  if (stagingDir !== null) {
7894
8100
  try {
7895
- fs10__namespace.rmSync(stagingDir, { recursive: true, force: true });
8101
+ fs11__namespace.rmSync(stagingDir, { recursive: true, force: true });
7896
8102
  } catch {
7897
8103
  }
7898
8104
  }
@@ -7930,14 +8136,17 @@ exports.detectPreset = detectPreset;
7930
8136
  exports.explainPath = explainPath;
7931
8137
  exports.fenceForContent = fenceForContent;
7932
8138
  exports.fenceForText = fenceForText;
8139
+ exports.firstRunMessage = firstRunMessage;
7933
8140
  exports.generateProjectTree = generateProjectTree;
7934
8141
  exports.generateSnapshot = generateSnapshot;
7935
8142
  exports.generateSnapshotAsync = generateSnapshotAsync;
7936
8143
  exports.getPreset = getPreset;
7937
8144
  exports.globToRegExp = globToRegExp;
8145
+ exports.hasPriorSnapshot = hasPriorSnapshot;
7938
8146
  exports.isBinaryBuffer = isBinaryBuffer;
7939
8147
  exports.isBinaryExtension = isBinaryExtension;
7940
8148
  exports.isBinaryFile = isBinaryFile;
8149
+ exports.isEffectivelyEmpty = isEffectivelyEmpty;
7941
8150
  exports.isInteractive = isInteractive;
7942
8151
  exports.isResolvedConfig = isResolvedConfig;
7943
8152
  exports.isSensitivePath = isSensitivePath;
@@ -7951,6 +8160,7 @@ exports.matchesExclude = matchesExclude;
7951
8160
  exports.normalizeResolvedConfig = normalizeResolvedConfig;
7952
8161
  exports.parseByteSize = parseByteSize;
7953
8162
  exports.partitionFiles = partitionFiles;
8163
+ exports.planFirstRun = planFirstRun;
7954
8164
  exports.profileForPath = profileForPath;
7955
8165
  exports.renderConfig = renderConfig;
7956
8166
  exports.renderDiffText = renderDiffText;