codebase-snapshot 0.9.0 → 0.9.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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}`;
1149
1158
  }
1150
- function hasDependency(context, ...names) {
1151
- return names.some((name) => context.dependencies.has(name));
1159
+ function suffixEvidence(context, suffix) {
1160
+ for (const file of context.files) {
1161
+ if (file.endsWith(suffix)) return file;
1162
+ }
1163
+ return null;
1164
+ }
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.0";
5491
+ var GENERATOR_VERSION = "0.9.4";
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}` };
@@ -6277,10 +6315,10 @@ var CONFIG_CANDIDATES = [
6277
6315
  ];
6278
6316
  function chooseConfigFileName(cwd) {
6279
6317
  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";
6318
+ const pkgPath = path12__namespace.join(cwd, "package.json");
6319
+ if (!fs11__namespace.existsSync(pkgPath)) return "snapshot.config.mjs";
6282
6320
  try {
6283
- const parsed = JSON.parse(fs10__namespace.readFileSync(pkgPath, "utf8"));
6321
+ const parsed = JSON.parse(fs11__namespace.readFileSync(pkgPath, "utf8"));
6284
6322
  return parsed.type === "module" ? "snapshot.config.ts" : "snapshot.config.mjs";
6285
6323
  } catch {
6286
6324
  return "snapshot.config.mjs";
@@ -6294,7 +6332,7 @@ var SIZE_CHOICES = [
6294
6332
  ];
6295
6333
  function packageJsonHasConfig(cwd) {
6296
6334
  try {
6297
- const parsed = JSON.parse(fs10__namespace.readFileSync(path11__namespace.join(cwd, "package.json"), "utf8"));
6335
+ const parsed = JSON.parse(fs11__namespace.readFileSync(path12__namespace.join(cwd, "package.json"), "utf8"));
6298
6336
  return typeof parsed.snapshot === "object" && parsed.snapshot !== null && !Array.isArray(parsed.snapshot);
6299
6337
  } catch {
6300
6338
  return false;
@@ -6315,7 +6353,7 @@ function artifactAnswers(options) {
6315
6353
  }
6316
6354
  function topLevelDirs(cwd) {
6317
6355
  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();
6356
+ return fs11__namespace.readdirSync(cwd, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules").map((entry) => entry.name).sort();
6319
6357
  } catch {
6320
6358
  return [];
6321
6359
  }
@@ -6328,7 +6366,7 @@ function describeJsonValue(value) {
6328
6366
  }
6329
6367
  function writeFileOrExplain(target, content) {
6330
6368
  try {
6331
- fs10__namespace.writeFileSync(target, content, "utf8");
6369
+ fs11__namespace.writeFileSync(target, content, "utf8");
6332
6370
  } catch (err) {
6333
6371
  const code = err.code;
6334
6372
  const reason = code === "EACCES" || code === "EPERM" || code === "EROFS" ? `permission denied (${code})` : code === "EISDIR" ? "it is a directory" : `${err.message}`;
@@ -6339,7 +6377,7 @@ function canWriteModuleConfig(name, cwd) {
6339
6377
  if (name.endsWith(".mjs") || name.endsWith(".ts")) return true;
6340
6378
  if (!name.endsWith(".js")) return false;
6341
6379
  try {
6342
- const parsed = JSON.parse(fs10__namespace.readFileSync(path11__namespace.join(cwd, "package.json"), "utf8"));
6380
+ const parsed = JSON.parse(fs11__namespace.readFileSync(path12__namespace.join(cwd, "package.json"), "utf8"));
6343
6381
  return parsed.type === "module";
6344
6382
  } catch {
6345
6383
  return false;
@@ -6374,19 +6412,19 @@ function serializePackageJson(raw, value) {
6374
6412
  return `${text2}${raw.endsWith("\n") ? crlf ? "\r\n" : "\n" : ""}`;
6375
6413
  }
6376
6414
  function updatePackageJson(cwd, notes) {
6377
- const packagePath = path11__namespace.join(cwd, "package.json");
6415
+ const packagePath = path12__namespace.join(cwd, "package.json");
6378
6416
  const unchanged = {
6379
6417
  scripts: [],
6380
6418
  dependency: { name: GENERATOR_NAME, range: null, added: false }
6381
6419
  };
6382
- if (!fs10__namespace.existsSync(packagePath)) {
6420
+ if (!fs11__namespace.existsSync(packagePath)) {
6383
6421
  notes.push("No package.json found, so npm scripts and the devDependency were not added.");
6384
6422
  return unchanged;
6385
6423
  }
6386
6424
  let raw;
6387
6425
  let parsed;
6388
6426
  try {
6389
- raw = fs10__namespace.readFileSync(packagePath, "utf8");
6427
+ raw = fs11__namespace.readFileSync(packagePath, "utf8");
6390
6428
  parsed = JSON.parse(raw);
6391
6429
  } catch {
6392
6430
  notes.push("Could not parse package.json; npm scripts and the devDependency were not added.");
@@ -6449,16 +6487,18 @@ function updatePackageJson(cwd, notes) {
6449
6487
  return { scripts: added, dependency };
6450
6488
  }
6451
6489
  var GITIGNORE_ENTRIES = ["snapshot/", "snapshot.tmp/"];
6452
- function resolvePresetChoice(answers, presetName) {
6490
+ function resolvePresetChoice(detectedName, presetName) {
6453
6491
  return {
6454
6492
  value: presetName,
6455
6493
  label: presetName,
6456
- hint: presetName === answers.preset ? "detected" : void 0
6494
+ hint: presetName === detectedName ? "detected" : void 0
6457
6495
  };
6458
6496
  }
6459
6497
  async function runInit(options) {
6460
6498
  const io = options.io ?? defaultIO();
6461
6499
  const detected = detectPreset(options.cwd, options.preset);
6500
+ let reason = detected.reason;
6501
+ let fallback = detected.fallback;
6462
6502
  const notes = [];
6463
6503
  const answers = {
6464
6504
  preset: detected.presetName,
@@ -6480,117 +6520,148 @@ async function runInit(options) {
6480
6520
  if (interactive) {
6481
6521
  io.output.write("\n codebase-snapshot\n");
6482
6522
  io.output.write(" Turn this repository into an LLM-ready, redacted, byte-bounded snapshot.\n\n");
6483
- io.output.write(` Detected ${detected.presetName}
6523
+ if (fallback) {
6524
+ io.output.write(" \u26A0 Could not tell what kind of project this is.\n");
6525
+ io.output.write(" No package.json, go.mod, Cargo.toml, pyproject.toml, Gemfile,\n");
6526
+ io.output.write(" composer.json, pom.xml, *.csproj, next.config.*, vite.config.*, *.tf\n");
6527
+ io.output.write(" or workspace marker was found here.\n\n");
6528
+ } else {
6529
+ io.output.write(` Detected ${detected.presetName}
6530
+ `);
6531
+ io.output.write(` Because ${reason}
6532
+
6484
6533
  `);
6534
+ }
6485
6535
  io.output.write(` Directory ${options.cwd}
6486
6536
 
6487
6537
  `);
6488
- const useDetected = await confirm(io, `Set up for ${detected.presetName}?`, {
6489
- defaultValue: true
6538
+ const paceChoices = fallback ? [
6539
+ { value: "choose", label: "Pick the closest preset", hint: "then generate with the defaults" },
6540
+ { value: "recommended", label: "Use the defaults", hint: "generic \xB7 everything the scanner finds" },
6541
+ { value: "walk", label: "Go through each setting", hint: "every question starts on its default" }
6542
+ ] : [
6543
+ {
6544
+ value: "recommended",
6545
+ label: "Use the recommended setup",
6546
+ hint: `${detected.presetName} \xB7 config \xB7 npm scripts \xB7 install \xB7 generate`
6547
+ },
6548
+ { value: "walk", label: "Go through each setting", hint: "every question starts on its default" },
6549
+ { value: "choose", label: "Pick a different preset", hint: "everything else stays at the defaults" }
6550
+ ];
6551
+ const pace = await select(io, "How should I set this up?", paceChoices, {
6552
+ defaultValue: fallback ? "choose" : "recommended"
6490
6553
  });
6491
- if (!useDetected) {
6554
+ if (pace === "choose") {
6492
6555
  answers.preset = await select(
6493
6556
  io,
6494
6557
  "What kind of project is this?",
6495
- listPresetNames().map((name) => resolvePresetChoice(answers, name))
6558
+ listPresetNames().map(
6559
+ (name) => resolvePresetChoice(fallback ? null : detected.presetName, name)
6560
+ ),
6561
+ { defaultValue: answers.preset }
6496
6562
  );
6497
6563
  const chosen = detectPreset(options.cwd, answers.preset);
6498
6564
  answers.preset = chosen.presetName;
6565
+ reason = null;
6566
+ fallback = false;
6499
6567
  }
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.");
6568
+ if (pace === "walk") {
6569
+ const scope = await select(
6570
+ io,
6571
+ "What should go into the snapshot?",
6572
+ [
6573
+ { value: "all", label: "Everything the scanner finds", hint: "recommended \xB7 respects .gitignore" },
6574
+ { value: "folders", label: "Only specific folders\u2026" },
6575
+ { value: "no-tests", label: "Everything, but skip tests" },
6576
+ { value: "custom", label: "Let me write glob patterns" }
6577
+ ],
6578
+ { defaultValue: "all" }
6579
+ );
6580
+ if (scope === "folders") {
6581
+ const dirs = topLevelDirs(options.cwd).slice(0, 40);
6582
+ if (dirs.length === 0) {
6583
+ notes.push("No top-level directories found; the snapshot will include everything.");
6584
+ } else {
6585
+ const chosen = await multiSelect(
6586
+ io,
6587
+ "Select folders",
6588
+ dirs.map((name) => ({ value: name, label: `${name}/` })),
6589
+ { defaults: dirs }
6590
+ );
6591
+ answers.include = chosen.map((name) => `${name}/**`);
6592
+ }
6593
+ } else if (scope === "no-tests") {
6594
+ answers.excludePatterns = [
6595
+ ...answers.excludePatterns,
6596
+ "**/*.test.*",
6597
+ "**/*.spec.*",
6598
+ "tests",
6599
+ "test"
6600
+ ];
6601
+ } else if (scope === "custom") {
6602
+ const raw = await text(io, "Glob patterns to include (comma separated)", {
6603
+ defaultValue: ""
6604
+ });
6605
+ answers.include = raw.split(",").map((value) => value.trim()).filter(Boolean);
6606
+ }
6607
+ const currentBudget = String(answers.maxPartBytes);
6608
+ const sizeChoice = await select(io, "How large should each part be?", SIZE_CHOICES, {
6609
+ defaultValue: SIZE_CHOICES.some((choice) => choice.value === currentBudget) ? currentBudget : "120k"
6610
+ });
6611
+ if (sizeChoice === "custom") {
6612
+ const raw = await text(io, "Part size (e.g. 150k, 1.5mb, 900000)", {
6613
+ defaultValue: String(answers.maxPartBytes),
6614
+ validate: (value) => parseByteSize(value) === void 0 ? "Not a valid size." : null
6615
+ });
6616
+ answers.maxPartBytes = parseByteSize(raw) ?? answers.maxPartBytes;
6515
6617
  } 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}/**`);
6618
+ answers.maxPartBytes = sizeChoice;
6523
6619
  }
6524
- } else if (scope === "no-tests") {
6525
- answers.excludePatterns = [
6526
- ...answers.excludePatterns,
6527
- "**/*.test.*",
6528
- "**/*.spec.*",
6529
- "tests",
6530
- "test"
6620
+ const artifactChoices = [
6621
+ { value: "readme", label: "README.md", hint: "orientation page" },
6622
+ { value: "manifest", label: "manifest.json", hint: "required by validate and report" },
6623
+ { value: "parts", label: "parts/", hint: "the code payload" },
6624
+ { value: "navigation", label: "reports/navigation.md", hint: "file \u2192 part index" },
6625
+ { value: "projectTree", label: "reports/project-tree.md", hint: "ASCII tree" },
6626
+ { value: "audit", label: "reports/audit.md", hint: "security and health findings" },
6627
+ { value: "monolith", label: "full-codebase.json + .md", hint: "roughly doubles output" }
6531
6628
  ];
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
- }
6550
- const artifactChoices = [
6551
- { value: "readme", label: "README.md", hint: "orientation page" },
6552
- { value: "manifest", label: "manifest.json", hint: "required by validate and report" },
6553
- { value: "parts", label: "parts/", hint: "the code payload" },
6554
- { value: "navigation", label: "reports/navigation.md", hint: "file \u2192 part index" },
6555
- { value: "projectTree", label: "reports/project-tree.md", hint: "ASCII tree" },
6556
- { value: "audit", label: "reports/audit.md", hint: "security and health findings" },
6557
- { value: "monolith", label: "full-codebase.json + .md", hint: "roughly doubles output" }
6558
- ];
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}
6629
+ const presetEnabled = ARTIFACT_NAMES.filter((name) => answers.artifacts[name]);
6630
+ const selectedArtifacts = await multiSelect(
6631
+ io,
6632
+ "Which artifacts should be generated?",
6633
+ artifactChoices,
6634
+ { defaults: presetEnabled }
6635
+ );
6636
+ for (const name of ARTIFACT_NAMES) answers.artifacts[name] = selectedArtifacts.includes(name);
6637
+ const enabledCount = ARTIFACT_NAMES.filter((name) => answers.artifacts[name]).length;
6638
+ answers.includeSensitiveFiles = await confirm(
6639
+ io,
6640
+ "Include credential files (.env, *.pem, .npmrc, .ssh/)?",
6641
+ { defaultValue: false }
6642
+ );
6643
+ io.output.write("\n Review\n\n");
6644
+ io.output.write(` project ${answers.preset}
6575
6645
  `);
6576
- io.output.write(` include ${answers.include.length ? answers.include.join(", ") : "everything"}
6646
+ io.output.write(` include ${answers.include.length ? answers.include.join(", ") : "everything"}
6577
6647
  `);
6578
- io.output.write(` part size ${String(answers.maxPartBytes)}
6648
+ io.output.write(` part size ${String(answers.maxPartBytes)}
6579
6649
  `);
6580
- io.output.write(` artifacts ${enabledCount} of ${ARTIFACT_NAMES.length}
6650
+ io.output.write(` artifacts ${enabledCount} of ${ARTIFACT_NAMES.length}
6581
6651
  `);
6582
- io.output.write(` credentials ${answers.includeSensitiveFiles ? "included" : "skipped"}
6652
+ io.output.write(` credentials ${answers.includeSensitiveFiles ? "included" : "skipped"}
6583
6653
 
6584
6654
  `);
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();
6655
+ io.output.write(" Will write snapshot.config with your answers, plus every other setting\n");
6656
+ io.output.write(" at its default \u2014 so nothing is hidden behind the docs.\n\n");
6657
+ const proceed = await confirm(io, "Write this and continue?", { defaultValue: true });
6658
+ if (!proceed) throw new PromptCancelled();
6659
+ }
6589
6660
  }
6590
6661
  const configText = renderConfig(answers);
6591
6662
  const preferredName = chooseConfigFileName(options.cwd);
6592
6663
  const presentConfigs = CONFIG_CANDIDATES.filter(
6593
- (name) => fs10__namespace.existsSync(path11__namespace.join(options.cwd, name))
6664
+ (name) => fs11__namespace.existsSync(path12__namespace.join(options.cwd, name))
6594
6665
  );
6595
6666
  const present = presentConfigs[0] ?? null;
6596
6667
  if (presentConfigs.length > 1) {
@@ -6599,7 +6670,7 @@ async function runInit(options) {
6599
6670
  );
6600
6671
  }
6601
6672
  const configName = present !== null && options.force ? present : preferredName;
6602
- const configPath = path11__namespace.join(options.cwd, configName);
6673
+ const configPath = path12__namespace.join(options.cwd, configName);
6603
6674
  const reused = present !== null && options.force !== true && options.reuseConfig === true;
6604
6675
  if (reused) notes.push(`Kept the existing ${present} instead of writing a new one.`);
6605
6676
  if (options.dryRun) {
@@ -6609,6 +6680,8 @@ async function runInit(options) {
6609
6680
  written: false,
6610
6681
  reused,
6611
6682
  preset: answers.preset,
6683
+ reason,
6684
+ fallback,
6612
6685
  scripts: [],
6613
6686
  gitignoreEntries: [],
6614
6687
  dependency: { name: GENERATOR_NAME, range: `^${GENERATOR_VERSION}`, added: false },
@@ -6630,7 +6703,7 @@ async function runInit(options) {
6630
6703
  '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
6704
  );
6632
6705
  }
6633
- if (fs10__namespace.existsSync(configPath) && !options.force && !reused) {
6706
+ if (fs11__namespace.existsSync(configPath) && !options.force && !reused) {
6634
6707
  throw new Error(`${configName} already exists. Re-run with --force to replace it.`);
6635
6708
  }
6636
6709
  if (!reused) writeFileOrExplain(configPath, configText);
@@ -6643,12 +6716,56 @@ async function runInit(options) {
6643
6716
  written: true,
6644
6717
  reused,
6645
6718
  preset: answers.preset,
6719
+ reason,
6720
+ fallback,
6646
6721
  scripts,
6647
6722
  gitignoreEntries: ignore.added,
6648
6723
  dependency,
6649
6724
  notes
6650
6725
  };
6651
6726
  }
6727
+ function describeTarget(cwd) {
6728
+ const relative7 = path12__namespace.relative(process.cwd(), cwd);
6729
+ if (relative7 === "") return "this directory";
6730
+ if (relative7.startsWith("..") || path12__namespace.isAbsolute(relative7)) return cwd;
6731
+ return relative7;
6732
+ }
6733
+ function firstRunMessage(reason, cwd) {
6734
+ const where = describeTarget(cwd);
6735
+ if (reason === "empty") {
6736
+ return `Nothing to snapshot: ${where} has no files. Run this inside the repository you want to snapshot, or pass --cwd.`;
6737
+ }
6738
+ return `No configuration found in ${where} and this session is not interactive, so nothing was generated.
6739
+ Choose one:
6740
+ ${GENERATOR_NAME} --autopilot detect the project, set it up and generate, no questions
6741
+ ${GENERATOR_NAME} init --yes write a config from the detected defaults, then generate
6742
+ ${GENERATOR_NAME} init write a config interactively`;
6743
+ }
6744
+ function planFirstRun(facts) {
6745
+ if (facts.emptyTarget) return { action: "refuse", exitCode: 1, reason: "empty" };
6746
+ if (facts.autopilot) return { action: "setup", interactive: false };
6747
+ if (facts.hasConfig || facts.hasPriorSnapshot) return { action: "generate" };
6748
+ if (facts.interactive) return { action: "setup", interactive: true };
6749
+ return { action: "refuse", exitCode: 2, reason: "undecided" };
6750
+ }
6751
+ var NOT_CONTENT = /* @__PURE__ */ new Set([".git", ".hg", ".svn", ".bzr", ".DS_Store", "Thumbs.db"]);
6752
+ function isEffectivelyEmpty(cwd) {
6753
+ try {
6754
+ return fs11__namespace.readdirSync(cwd).every((entry) => NOT_CONTENT.has(entry));
6755
+ } catch {
6756
+ return false;
6757
+ }
6758
+ }
6759
+ function hasPriorSnapshot(cwd, outDir) {
6760
+ let parsed;
6761
+ try {
6762
+ parsed = JSON.parse(fs11__namespace.readFileSync(path12__namespace.join(cwd, outDir, ".codebase-snapshot"), "utf8"));
6763
+ } catch {
6764
+ return false;
6765
+ }
6766
+ if (parsed === null || typeof parsed !== "object") return false;
6767
+ return isOurGeneratorName(parsed.generator);
6768
+ }
6652
6769
 
6653
6770
  // src/index.ts
6654
6771
  var SCHEMA_VERSION = "3.0.0";
@@ -6730,13 +6847,13 @@ function buildImportResolver(files) {
6730
6847
  const relativePath = file.relativePath;
6731
6848
  remember(byPath, relativePath, relativePath);
6732
6849
  remember(byPathFolded, relativePath.toLowerCase(), relativePath);
6733
- const extension = path11__namespace.posix.extname(relativePath);
6850
+ const extension = path12__namespace.posix.extname(relativePath);
6734
6851
  if (!SOURCE_EXTENSIONS.has(extension)) continue;
6735
6852
  const stem = relativePath.slice(0, -extension.length);
6736
6853
  remember(byStem, stem, relativePath);
6737
6854
  remember(byStemFolded, stem.toLowerCase(), relativePath);
6738
- if (path11__namespace.posix.basename(stem) === "index") {
6739
- const directory = path11__namespace.posix.dirname(stem);
6855
+ if (path12__namespace.posix.basename(stem) === "index") {
6856
+ const directory = path12__namespace.posix.dirname(stem);
6740
6857
  remember(byStem, directory, relativePath);
6741
6858
  remember(byStemFolded, directory.toLowerCase(), relativePath);
6742
6859
  }
@@ -6745,8 +6862,8 @@ function buildImportResolver(files) {
6745
6862
  }
6746
6863
  function resolveSpecifier(fromFile, specifier, resolver) {
6747
6864
  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)
6865
+ const joined = path12__namespace.posix.normalize(
6866
+ path12__namespace.posix.join(path12__namespace.posix.dirname(fromFile), specifier)
6750
6867
  );
6751
6868
  const exact = (key) => resolver.byPath.get(key) ?? resolver.byPathFolded.get(key.toLowerCase()) ?? null;
6752
6869
  const direct = exact(joined);
@@ -6839,11 +6956,11 @@ function verifyInMemory(files, parts, maxPartBytes) {
6839
6956
  return errors;
6840
6957
  }
6841
6958
  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}`);
6959
+ const cwd = path12__namespace.resolve(config.cwd);
6960
+ const outDir = path12__namespace.resolve(cwd, config.outDir);
6961
+ const tmpDir = path12__namespace.resolve(cwd, config.tmpDir);
6962
+ const root = path12__namespace.parse(cwd).root;
6963
+ const contains = (parent, child) => child === parent || child.startsWith(`${parent}${path12__namespace.sep}`);
6847
6964
  const reject = (reason) => {
6848
6965
  throw new Error(
6849
6966
  `Refusing to generate: ${reason}. outDir=${outDir} tmpDir=${tmpDir} cwd=${cwd}`
@@ -6899,14 +7016,14 @@ function listEntries(dir) {
6899
7016
  const walk = (absolute, prefix) => {
6900
7017
  let children;
6901
7018
  try {
6902
- children = fs10__namespace.readdirSync(absolute, { withFileTypes: true });
7019
+ children = fs11__namespace.readdirSync(absolute, { withFileTypes: true });
6903
7020
  } catch {
6904
7021
  return;
6905
7022
  }
6906
7023
  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);
7024
+ const relative7 = prefix === "" ? child.name : `${prefix}/${child.name}`;
7025
+ entries.push(relative7);
7026
+ if (child.isDirectory()) walk(path12__namespace.join(absolute, child.name), relative7);
6910
7027
  }
6911
7028
  };
6912
7029
  walk(dir, "");
@@ -6923,28 +7040,28 @@ function markerPayload(dir, state, id, createdAt) {
6923
7040
  pid: process.pid,
6924
7041
  entries: listEntries(dir)
6925
7042
  };
6926
- const manifestPath = path11__namespace.join(dir, "manifest.json");
6927
- if (fs10__namespace.existsSync(manifestPath)) {
6928
- marker2.manifestSha256 = sha256Hex2(fs10__namespace.readFileSync(manifestPath));
7043
+ const manifestPath = path12__namespace.join(dir, "manifest.json");
7044
+ if (fs11__namespace.existsSync(manifestPath)) {
7045
+ marker2.manifestSha256 = sha256Hex2(fs11__namespace.readFileSync(manifestPath));
6929
7046
  }
6930
7047
  return marker2;
6931
7048
  }
6932
7049
  function writeOwnershipMarker(dir, marker2) {
6933
- fs10__namespace.writeFileSync(
6934
- path11__namespace.join(dir, OWNERSHIP_MARKER),
7050
+ fs11__namespace.writeFileSync(
7051
+ path12__namespace.join(dir, OWNERSHIP_MARKER),
6935
7052
  `${JSON.stringify(marker2, null, 2)}
6936
7053
  `,
6937
7054
  "utf8"
6938
7055
  );
6939
7056
  }
6940
7057
  function readOwnershipMarker(dir) {
6941
- const markerPath = path11__namespace.join(dir, OWNERSHIP_MARKER);
6942
- if (!fs10__namespace.existsSync(markerPath)) {
7058
+ const markerPath = path12__namespace.join(dir, OWNERSHIP_MARKER);
7059
+ if (!fs11__namespace.existsSync(markerPath)) {
6943
7060
  return { marker: null, problem: "it holds no .codebase-snapshot marker" };
6944
7061
  }
6945
7062
  let raw;
6946
7063
  try {
6947
- raw = fs10__namespace.readFileSync(markerPath, "utf8");
7064
+ raw = fs11__namespace.readFileSync(markerPath, "utf8");
6948
7065
  } catch {
6949
7066
  return { marker: null, problem: "its .codebase-snapshot marker could not be read" };
6950
7067
  }
@@ -7012,11 +7129,11 @@ function assessSnapshotDirectory(dir) {
7012
7129
  marker: marker2
7013
7130
  };
7014
7131
  }
7015
- const manifestPath = path11__namespace.join(dir, "manifest.json");
7016
- if (fs10__namespace.existsSync(manifestPath)) {
7132
+ const manifestPath = path12__namespace.join(dir, "manifest.json");
7133
+ if (fs11__namespace.existsSync(manifestPath)) {
7017
7134
  let raw;
7018
7135
  try {
7019
- raw = fs10__namespace.readFileSync(manifestPath, "utf8");
7136
+ raw = fs11__namespace.readFileSync(manifestPath, "utf8");
7020
7137
  } catch {
7021
7138
  return {
7022
7139
  owned: false,
@@ -7098,13 +7215,13 @@ function containsNoFiles(dir) {
7098
7215
  const current = pending.pop();
7099
7216
  let entries;
7100
7217
  try {
7101
- entries = fs10__namespace.readdirSync(current, { withFileTypes: true });
7218
+ entries = fs11__namespace.readdirSync(current, { withFileTypes: true });
7102
7219
  } catch {
7103
7220
  return false;
7104
7221
  }
7105
7222
  for (const entry of entries) {
7106
7223
  if (entry.isDirectory()) {
7107
- pending.push(path11__namespace.join(current, entry.name));
7224
+ pending.push(path12__namespace.join(current, entry.name));
7108
7225
  } else if (!IGNORABLE_ENTRIES.has(entry.name)) {
7109
7226
  return false;
7110
7227
  }
@@ -7113,13 +7230,13 @@ function containsNoFiles(dir) {
7113
7230
  return true;
7114
7231
  }
7115
7232
  function assertReplaceable(target, label, secretRules) {
7116
- if (!fs10__namespace.existsSync(target)) return;
7117
- if (!fs10__namespace.statSync(target).isDirectory()) {
7233
+ if (!fs11__namespace.existsSync(target)) return;
7234
+ if (!fs11__namespace.statSync(target).isDirectory()) {
7118
7235
  throw new Error(
7119
7236
  `Refusing to write into ${label} ${target}: it exists but is not a directory.`
7120
7237
  );
7121
7238
  }
7122
- const entries = fs10__namespace.readdirSync(target);
7239
+ const entries = fs11__namespace.readdirSync(target);
7123
7240
  if (entries.length === 0) return;
7124
7241
  if (entries.every((name) => IGNORABLE_ENTRIES.has(name))) return;
7125
7242
  if (containsNoFiles(target)) return;
@@ -7139,18 +7256,18 @@ function assertReplaceable(target, label, secretRules) {
7139
7256
  );
7140
7257
  }
7141
7258
  function assertStagingRootUsable(tmpDir, secretRules) {
7142
- if (!fs10__namespace.existsSync(tmpDir)) return;
7143
- if (!fs10__namespace.statSync(tmpDir).isDirectory()) {
7259
+ if (!fs11__namespace.existsSync(tmpDir)) return;
7260
+ if (!fs11__namespace.statSync(tmpDir).isDirectory()) {
7144
7261
  throw new Error(`Refusing to write into tmpDir ${tmpDir}: it exists but is not a directory.`);
7145
7262
  }
7146
- const entries = fs10__namespace.readdirSync(tmpDir);
7263
+ const entries = fs11__namespace.readdirSync(tmpDir);
7147
7264
  if (entries.length === 0) return;
7148
7265
  const foreign = entries.filter((name) => {
7149
- const full = path11__namespace.join(tmpDir, name);
7266
+ const full = path12__namespace.join(tmpDir, name);
7150
7267
  if (!name.startsWith(RUN_DIR_PREFIX)) return true;
7151
7268
  let stats;
7152
7269
  try {
7153
- stats = fs10__namespace.statSync(full);
7270
+ stats = fs11__namespace.statSync(full);
7154
7271
  } catch {
7155
7272
  return true;
7156
7273
  }
@@ -7158,7 +7275,7 @@ function assertStagingRootUsable(tmpDir, secretRules) {
7158
7275
  const { marker: marker2 } = readOwnershipMarker(full);
7159
7276
  if (marker2) return false;
7160
7277
  try {
7161
- return fs10__namespace.readdirSync(full).length > 0;
7278
+ return fs11__namespace.readdirSync(full).length > 0;
7162
7279
  } catch {
7163
7280
  return true;
7164
7281
  }
@@ -7173,13 +7290,13 @@ function assertStagingRootUsable(tmpDir, secretRules) {
7173
7290
  function cleanStaleRunDirs(tmpDir) {
7174
7291
  let entries;
7175
7292
  try {
7176
- entries = fs10__namespace.readdirSync(tmpDir);
7293
+ entries = fs11__namespace.readdirSync(tmpDir);
7177
7294
  } catch {
7178
7295
  return;
7179
7296
  }
7180
7297
  for (const name of entries) {
7181
7298
  if (!name.startsWith(RUN_DIR_PREFIX)) continue;
7182
- const full = path11__namespace.join(tmpDir, name);
7299
+ const full = path12__namespace.join(tmpDir, name);
7183
7300
  const { marker: marker2 } = readOwnershipMarker(full);
7184
7301
  if (marker2) {
7185
7302
  const created = Date.parse(marker2.createdAt);
@@ -7187,33 +7304,33 @@ function cleanStaleRunDirs(tmpDir) {
7187
7304
  const abandoned = typeof marker2.pid === "number" && !isProcessAlive(marker2.pid);
7188
7305
  if (aged || abandoned) {
7189
7306
  try {
7190
- fs10__namespace.rmSync(full, { recursive: true, force: true });
7307
+ fs11__namespace.rmSync(full, { recursive: true, force: true });
7191
7308
  } catch {
7192
7309
  }
7193
7310
  }
7194
7311
  continue;
7195
7312
  }
7196
7313
  try {
7197
- if (fs10__namespace.readdirSync(full).length === 0) fs10__namespace.rmSync(full, { recursive: true, force: true });
7314
+ if (fs11__namespace.readdirSync(full).length === 0) fs11__namespace.rmSync(full, { recursive: true, force: true });
7198
7315
  } catch {
7199
7316
  }
7200
7317
  }
7201
7318
  }
7202
7319
  function removeIfEmpty(dir) {
7203
7320
  try {
7204
- if (fs10__namespace.readdirSync(dir).length === 0) fs10__namespace.rmSync(dir, { recursive: true, force: true });
7321
+ if (fs11__namespace.readdirSync(dir).length === 0) fs11__namespace.rmSync(dir, { recursive: true, force: true });
7205
7322
  } catch {
7206
7323
  }
7207
7324
  }
7208
7325
  function ensureParentDirectory(target, label) {
7209
- const parent = path11__namespace.dirname(target);
7210
- if (fs10__namespace.existsSync(parent) && !fs10__namespace.statSync(parent).isDirectory()) {
7326
+ const parent = path12__namespace.dirname(target);
7327
+ if (fs11__namespace.existsSync(parent) && !fs11__namespace.statSync(parent).isDirectory()) {
7211
7328
  throw new Error(
7212
7329
  `Refusing to create ${label} ${target}: ${parent} exists but is not a directory.`
7213
7330
  );
7214
7331
  }
7215
7332
  try {
7216
- fs10__namespace.mkdirSync(parent, { recursive: true });
7333
+ fs11__namespace.mkdirSync(parent, { recursive: true });
7217
7334
  } catch (err) {
7218
7335
  throw new Error(
7219
7336
  `Refusing to create ${label} ${target}: its parent ${parent} could not be created (${err.message}).`
@@ -7222,7 +7339,7 @@ function ensureParentDirectory(target, label) {
7222
7339
  }
7223
7340
  function acquireRunLock(stagingRoot) {
7224
7341
  const key = lockKeyFor(stagingRoot);
7225
- const lockPath = path11__namespace.join(os__namespace.tmpdir(), `codebase-snapshot-${key}.lock`);
7342
+ const lockPath = path12__namespace.join(os__namespace.tmpdir(), `codebase-snapshot-${key}.lock`);
7226
7343
  const payload = `${JSON.stringify({
7227
7344
  pid: process.pid,
7228
7345
  stagingRoot,
@@ -7231,22 +7348,22 @@ function acquireRunLock(stagingRoot) {
7231
7348
  `;
7232
7349
  for (let attempt = 0; attempt < 2; attempt += 1) {
7233
7350
  try {
7234
- const handle = fs10__namespace.openSync(lockPath, "wx");
7351
+ const handle = fs11__namespace.openSync(lockPath, "wx");
7235
7352
  try {
7236
- fs10__namespace.writeSync(handle, payload);
7353
+ fs11__namespace.writeSync(handle, payload);
7237
7354
  } finally {
7238
- fs10__namespace.closeSync(handle);
7355
+ fs11__namespace.closeSync(handle);
7239
7356
  }
7240
7357
  return () => {
7241
7358
  try {
7242
- fs10__namespace.rmSync(lockPath, { force: true });
7359
+ fs11__namespace.rmSync(lockPath, { force: true });
7243
7360
  } catch {
7244
7361
  }
7245
7362
  };
7246
7363
  } catch (err) {
7247
7364
  if (err.code !== "EEXIST") throw err;
7248
7365
  if (attempt === 0 && lockOwnerIsGone(lockPath)) {
7249
- fs10__namespace.rmSync(lockPath, { force: true });
7366
+ fs11__namespace.rmSync(lockPath, { force: true });
7250
7367
  continue;
7251
7368
  }
7252
7369
  throw new Error(
@@ -7259,7 +7376,7 @@ function acquireRunLock(stagingRoot) {
7259
7376
  function lockKeyFor(stagingRoot) {
7260
7377
  let key = stagingRoot;
7261
7378
  try {
7262
- key = fs10__namespace.realpathSync(stagingRoot);
7379
+ key = fs11__namespace.realpathSync(stagingRoot);
7263
7380
  } catch {
7264
7381
  }
7265
7382
  if (process.platform === "win32" || process.platform === "darwin") key = key.toLowerCase();
@@ -7268,7 +7385,7 @@ function lockKeyFor(stagingRoot) {
7268
7385
  function lockOwnerIsGone(lockPath) {
7269
7386
  let raw;
7270
7387
  try {
7271
- raw = fs10__namespace.readFileSync(lockPath, "utf8");
7388
+ raw = fs11__namespace.readFileSync(lockPath, "utf8");
7272
7389
  } catch {
7273
7390
  return false;
7274
7391
  }
@@ -7288,12 +7405,12 @@ function lockOwnerIsGone(lockPath) {
7288
7405
  }
7289
7406
  function writeManifestAtomically(manifestPath, manifest) {
7290
7407
  const tempPath = `${manifestPath}.${process.pid}.tmp`;
7291
- fs10__namespace.writeFileSync(tempPath, JSON.stringify(manifest, null, 2), "utf8");
7408
+ fs11__namespace.writeFileSync(tempPath, JSON.stringify(manifest, null, 2), "utf8");
7292
7409
  try {
7293
- fs10__namespace.renameSync(tempPath, manifestPath);
7410
+ fs11__namespace.renameSync(tempPath, manifestPath);
7294
7411
  } catch (err) {
7295
7412
  try {
7296
- fs10__namespace.rmSync(tempPath, { force: true });
7413
+ fs11__namespace.rmSync(tempPath, { force: true });
7297
7414
  } catch {
7298
7415
  }
7299
7416
  throw err;
@@ -7316,31 +7433,31 @@ function swapIntoPlace(stagingDir, outDir) {
7316
7433
  }
7317
7434
  function swapOnce(stagingDir, outDir) {
7318
7435
  const backupDir = `${outDir}.backup-${process.pid}-${Date.now()}`;
7319
- const hadPrevious = fs10__namespace.existsSync(outDir);
7320
- if (hadPrevious) fs10__namespace.renameSync(outDir, backupDir);
7436
+ const hadPrevious = fs11__namespace.existsSync(outDir);
7437
+ if (hadPrevious) fs11__namespace.renameSync(outDir, backupDir);
7321
7438
  try {
7322
7439
  try {
7323
- fs10__namespace.renameSync(stagingDir, outDir);
7440
+ fs11__namespace.renameSync(stagingDir, outDir);
7324
7441
  } catch (err) {
7325
7442
  if (err.code !== "EXDEV") throw err;
7326
7443
  const partialDir = `${outDir}.partial-${process.pid}-${Date.now()}`;
7327
7444
  try {
7328
- fs10__namespace.cpSync(stagingDir, partialDir, { recursive: true });
7329
- fs10__namespace.renameSync(partialDir, outDir);
7330
- fs10__namespace.rmSync(stagingDir, { recursive: true, force: true });
7445
+ fs11__namespace.cpSync(stagingDir, partialDir, { recursive: true });
7446
+ fs11__namespace.renameSync(partialDir, outDir);
7447
+ fs11__namespace.rmSync(stagingDir, { recursive: true, force: true });
7331
7448
  } catch (copyErr) {
7332
7449
  try {
7333
- fs10__namespace.rmSync(partialDir, { recursive: true, force: true });
7450
+ fs11__namespace.rmSync(partialDir, { recursive: true, force: true });
7334
7451
  } catch {
7335
7452
  }
7336
7453
  throw copyErr;
7337
7454
  }
7338
7455
  }
7339
- if (hadPrevious) fs10__namespace.rmSync(backupDir, { recursive: true, force: true });
7456
+ if (hadPrevious) fs11__namespace.rmSync(backupDir, { recursive: true, force: true });
7340
7457
  } 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);
7458
+ if (hadPrevious && fs11__namespace.existsSync(backupDir)) {
7459
+ if (fs11__namespace.existsSync(outDir)) fs11__namespace.rmSync(outDir, { recursive: true, force: true });
7460
+ fs11__namespace.renameSync(backupDir, outDir);
7344
7461
  }
7345
7462
  throw err;
7346
7463
  }
@@ -7435,8 +7552,8 @@ function generateSnapshot(configInput, options) {
7435
7552
  // first run cannot have marked them yet, so the name alone keeps a run from recording its own
7436
7553
  // output — but only at these exact paths, not at every directory that shares the name.
7437
7554
  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(".."))
7555
+ (directory) => path12__namespace.relative(config.cwd, path12__namespace.resolve(config.cwd, directory)).replace(/\\/g, "/")
7556
+ ).filter((relative7) => relative7.length > 0 && !relative7.startsWith(".."))
7440
7557
  });
7441
7558
  const files = [];
7442
7559
  const monolithBodies = /* @__PURE__ */ new Map();
@@ -7456,12 +7573,12 @@ function generateSnapshot(configInput, options) {
7456
7573
  let totalSizeBytes = 0;
7457
7574
  let largeFileCount = 0;
7458
7575
  for (const relativePath of collection.files) {
7459
- const absolutePath = path11__namespace.join(config.cwd, relativePath);
7576
+ const absolutePath = path12__namespace.join(config.cwd, relativePath);
7460
7577
  let stats;
7461
7578
  try {
7462
- stats = fs10__namespace.statSync(absolutePath);
7579
+ stats = fs11__namespace.statSync(absolutePath);
7463
7580
  if (!stats.isFile()) {
7464
- const nestedRepository = stats.isDirectory() && fs10__namespace.existsSync(path11__namespace.join(absolutePath, ".git"));
7581
+ const nestedRepository = stats.isDirectory() && fs11__namespace.existsSync(path12__namespace.join(absolutePath, ".git"));
7465
7582
  skipped.push(
7466
7583
  nestedRepository ? {
7467
7584
  path: relativePath,
@@ -7481,7 +7598,7 @@ function generateSnapshot(configInput, options) {
7481
7598
  }
7482
7599
  let buffer;
7483
7600
  try {
7484
- buffer = fs10__namespace.readFileSync(absolutePath);
7601
+ buffer = fs11__namespace.readFileSync(absolutePath);
7485
7602
  } catch {
7486
7603
  skipped.push({ path: relativePath, reason: "read-error" });
7487
7604
  continue;
@@ -7811,12 +7928,12 @@ function generateSnapshot(configInput, options) {
7811
7928
  let stagingDir = null;
7812
7929
  try {
7813
7930
  ensureParentDirectory(outDir, "outDir");
7814
- fs10__namespace.mkdirSync(tmpDir, { recursive: true });
7931
+ fs11__namespace.mkdirSync(tmpDir, { recursive: true });
7815
7932
  cleanStaleRunDirs(tmpDir);
7816
7933
  const snapshotId = crypto__namespace.randomUUID();
7817
7934
  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);
7935
+ stagingDir = path12__namespace.join(tmpDir, `${RUN_DIR_PREFIX}${process.pid}-${snapshotId.slice(0, 8)}`);
7936
+ fs11__namespace.mkdirSync(stagingDir);
7820
7937
  writeOwnershipMarker(stagingDir, {
7821
7938
  generator: GENERATOR_NAME,
7822
7939
  version: GENERATOR_VERSION,
@@ -7826,41 +7943,41 @@ function generateSnapshot(configInput, options) {
7826
7943
  id: snapshotId,
7827
7944
  pid: process.pid
7828
7945
  });
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 });
7946
+ const partsDir = path12__namespace.join(stagingDir, "parts");
7947
+ const reportsDir = path12__namespace.join(stagingDir, "reports");
7948
+ if (artifacts.parts) fs11__namespace.mkdirSync(partsDir, { recursive: true });
7832
7949
  if (artifacts.navigation || artifacts.projectTree || artifacts.audit) {
7833
- fs10__namespace.mkdirSync(reportsDir, { recursive: true });
7950
+ fs11__namespace.mkdirSync(reportsDir, { recursive: true });
7834
7951
  }
7835
7952
  if (artifacts.parts) {
7836
7953
  for (const part of parts) {
7837
- fs10__namespace.writeFileSync(path11__namespace.join(partsDir, part.filename), part.markdown, "utf8");
7954
+ fs11__namespace.writeFileSync(path12__namespace.join(partsDir, part.filename), part.markdown, "utf8");
7838
7955
  }
7839
7956
  }
7840
7957
  if (projectTree !== null) {
7841
- fs10__namespace.writeFileSync(path11__namespace.join(reportsDir, "project-tree.md"), projectTree, "utf8");
7958
+ fs11__namespace.writeFileSync(path12__namespace.join(reportsDir, "project-tree.md"), projectTree, "utf8");
7842
7959
  }
7843
7960
  if (artifacts.navigation) {
7844
- fs10__namespace.writeFileSync(path11__namespace.join(reportsDir, "navigation.md"), generateNavigation(manifest), "utf8");
7961
+ fs11__namespace.writeFileSync(path12__namespace.join(reportsDir, "navigation.md"), generateNavigation(manifest), "utf8");
7845
7962
  }
7846
7963
  if (artifacts.audit) {
7847
- fs10__namespace.writeFileSync(
7848
- path11__namespace.join(reportsDir, "audit.md"),
7964
+ fs11__namespace.writeFileSync(
7965
+ path12__namespace.join(reportsDir, "audit.md"),
7849
7966
  generateAuditReport(manifest, secretFindings, config.secretRules),
7850
7967
  "utf8"
7851
7968
  );
7852
7969
  }
7853
7970
  if (artifacts.readme) {
7854
- fs10__namespace.writeFileSync(path11__namespace.join(stagingDir, "README.md"), generateReadme(manifest, artifacts), "utf8");
7971
+ fs11__namespace.writeFileSync(path12__namespace.join(stagingDir, "README.md"), generateReadme(manifest, artifacts), "utf8");
7855
7972
  }
7856
7973
  if (artifacts.manifest) {
7857
- fs10__namespace.writeFileSync(path11__namespace.join(stagingDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf8");
7974
+ fs11__namespace.writeFileSync(path12__namespace.join(stagingDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf8");
7858
7975
  }
7859
7976
  if (monolithJson !== null) {
7860
- fs10__namespace.writeFileSync(path11__namespace.join(stagingDir, "full-codebase.json"), monolithJson, "utf8");
7977
+ fs11__namespace.writeFileSync(path12__namespace.join(stagingDir, "full-codebase.json"), monolithJson, "utf8");
7861
7978
  }
7862
7979
  if (monolithMarkdown !== null) {
7863
- fs10__namespace.writeFileSync(path11__namespace.join(stagingDir, "full-codebase.md"), monolithMarkdown, "utf8");
7980
+ fs11__namespace.writeFileSync(path12__namespace.join(stagingDir, "full-codebase.md"), monolithMarkdown, "utf8");
7864
7981
  }
7865
7982
  writeOwnershipMarker(stagingDir, markerPayload(stagingDir, "complete", snapshotId, createdAt));
7866
7983
  swapIntoPlace(stagingDir, outDir);
@@ -7869,7 +7986,7 @@ function generateSnapshot(configInput, options) {
7869
7986
  const pendingHook = runAfterGenerate(config, manifest);
7870
7987
  const shipHookState = () => {
7871
7988
  if (artifacts.manifest && manifest.warnings.length !== warningsBeforeHook) {
7872
- writeManifestAtomically(path11__namespace.join(outDir, "manifest.json"), manifest);
7989
+ writeManifestAtomically(path12__namespace.join(outDir, "manifest.json"), manifest);
7873
7990
  }
7874
7991
  writeOwnershipMarker(outDir, markerPayload(outDir, "complete", snapshotId, createdAt));
7875
7992
  };
@@ -7892,7 +8009,7 @@ function generateSnapshot(configInput, options) {
7892
8009
  } catch (err) {
7893
8010
  if (stagingDir !== null) {
7894
8011
  try {
7895
- fs10__namespace.rmSync(stagingDir, { recursive: true, force: true });
8012
+ fs11__namespace.rmSync(stagingDir, { recursive: true, force: true });
7896
8013
  } catch {
7897
8014
  }
7898
8015
  }
@@ -7930,14 +8047,17 @@ exports.detectPreset = detectPreset;
7930
8047
  exports.explainPath = explainPath;
7931
8048
  exports.fenceForContent = fenceForContent;
7932
8049
  exports.fenceForText = fenceForText;
8050
+ exports.firstRunMessage = firstRunMessage;
7933
8051
  exports.generateProjectTree = generateProjectTree;
7934
8052
  exports.generateSnapshot = generateSnapshot;
7935
8053
  exports.generateSnapshotAsync = generateSnapshotAsync;
7936
8054
  exports.getPreset = getPreset;
7937
8055
  exports.globToRegExp = globToRegExp;
8056
+ exports.hasPriorSnapshot = hasPriorSnapshot;
7938
8057
  exports.isBinaryBuffer = isBinaryBuffer;
7939
8058
  exports.isBinaryExtension = isBinaryExtension;
7940
8059
  exports.isBinaryFile = isBinaryFile;
8060
+ exports.isEffectivelyEmpty = isEffectivelyEmpty;
7941
8061
  exports.isInteractive = isInteractive;
7942
8062
  exports.isResolvedConfig = isResolvedConfig;
7943
8063
  exports.isSensitivePath = isSensitivePath;
@@ -7951,6 +8071,7 @@ exports.matchesExclude = matchesExclude;
7951
8071
  exports.normalizeResolvedConfig = normalizeResolvedConfig;
7952
8072
  exports.parseByteSize = parseByteSize;
7953
8073
  exports.partitionFiles = partitionFiles;
8074
+ exports.planFirstRun = planFirstRun;
7954
8075
  exports.profileForPath = profileForPath;
7955
8076
  exports.renderConfig = renderConfig;
7956
8077
  exports.renderDiffText = renderDiffText;