codebase-snapshot 0.9.1 → 0.9.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +219 -0
- package/README.md +141 -127
- package/dist/bin.js +2617 -492
- package/dist/chunk-4AHHSNGG.js +2577 -0
- package/dist/{chunk-HT7J3NWT.js → chunk-B336MMNC.js} +599 -379
- package/dist/cli.cjs +2703 -600
- package/dist/cli.d.cts +11 -1
- package/dist/cli.d.ts +11 -1
- package/dist/cli.js +71 -754
- package/dist/index.cjs +587 -377
- package/dist/index.d.cts +111 -5
- package/dist/index.d.ts +111 -5
- package/dist/index.js +1 -1
- package/dist/mcp.cjs +9398 -0
- package/dist/mcp.d.cts +399 -0
- package/dist/mcp.d.ts +399 -0
- package/dist/mcp.js +4 -0
- package/docs/agent.md +36 -0
- package/docs/cli.md +95 -14
- package/docs/codebase-token-budget.md +98 -0
- package/docs/configuration.md +12 -2
- package/docs/how-to-feed-a-repo-to-claude.md +4 -2
- package/docs/install.md +39 -11
- package/docs/mcp.md +149 -0
- package/docs/stale-context-is-your-bug.md +118 -0
- package/docs/troubleshooting.md +13 -0
- package/docs/vs-repomix.md +4 -2
- package/package.json +29 -14
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import * as crypto from 'crypto';
|
|
2
2
|
import { createHash } from 'crypto';
|
|
3
3
|
import { AsyncLocalStorage } from 'async_hooks';
|
|
4
|
-
import * as
|
|
4
|
+
import * as fs11 from 'fs';
|
|
5
5
|
import * as os from 'os';
|
|
6
|
-
import * as
|
|
6
|
+
import * as path12 from 'path';
|
|
7
7
|
import ts from 'typescript';
|
|
8
8
|
import { createRequire } from 'module';
|
|
9
9
|
import { pathToFileURL } from 'url';
|
|
@@ -234,9 +234,9 @@ var JAVA_METHOD = /^\s+(?:public|private|protected)\s+(?:static\s+)?(?:final\s+)
|
|
|
234
234
|
var jvmProfile = {
|
|
235
235
|
id: "jvm",
|
|
236
236
|
extensions: [".java", ".kt", ".kts", ".scala"],
|
|
237
|
-
summarize(
|
|
237
|
+
summarize(path13, content) {
|
|
238
238
|
const summary = emptySummary();
|
|
239
|
-
const lower =
|
|
239
|
+
const lower = path13.toLowerCase();
|
|
240
240
|
const isJava = lower.endsWith(".java");
|
|
241
241
|
const isKotlin = lower.endsWith(".kt") || lower.endsWith(".kts");
|
|
242
242
|
eachLine(content, (line) => {
|
|
@@ -689,16 +689,16 @@ function summarizeTypeScriptSource(relativePath, content) {
|
|
|
689
689
|
// src/ast/profiles/sfc.ts
|
|
690
690
|
var SCRIPT_BLOCK = /<script[^>]*>([\s\S]*?)<\/script>/gi;
|
|
691
691
|
var ASTRO_FRONTMATTER = /^\uFEFF?---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*(?=\r?\n|$)/;
|
|
692
|
-
function componentName(
|
|
693
|
-
const base =
|
|
692
|
+
function componentName(path13) {
|
|
693
|
+
const base = path13.split("/").pop() ?? path13;
|
|
694
694
|
return base.replace(/\.[^.]+$/, "");
|
|
695
695
|
}
|
|
696
696
|
var sfcProfile = {
|
|
697
697
|
id: "sfc",
|
|
698
698
|
extensions: [".vue", ".svelte", ".astro"],
|
|
699
|
-
summarize(
|
|
699
|
+
summarize(path13, content) {
|
|
700
700
|
let script = "";
|
|
701
|
-
if (
|
|
701
|
+
if (path13.toLowerCase().endsWith(".astro")) {
|
|
702
702
|
const frontmatter = ASTRO_FRONTMATTER.exec(content);
|
|
703
703
|
if (frontmatter) script += `${frontmatter[1]}
|
|
704
704
|
`;
|
|
@@ -707,7 +707,7 @@ var sfcProfile = {
|
|
|
707
707
|
script += `${match[1]}
|
|
708
708
|
`;
|
|
709
709
|
}
|
|
710
|
-
const name = componentName(
|
|
710
|
+
const name = componentName(path13);
|
|
711
711
|
if (!script.trim()) {
|
|
712
712
|
const summary2 = emptySummary();
|
|
713
713
|
if (name) {
|
|
@@ -741,15 +741,15 @@ for (const profile of LANGUAGE_PROFILES) {
|
|
|
741
741
|
BY_EXTENSION.set(extension.toLowerCase(), profile);
|
|
742
742
|
}
|
|
743
743
|
}
|
|
744
|
-
function profileForPath(
|
|
745
|
-
const lower =
|
|
744
|
+
function profileForPath(path13) {
|
|
745
|
+
const lower = path13.toLowerCase();
|
|
746
746
|
const dot = lower.lastIndexOf(".");
|
|
747
747
|
if (dot === -1) return null;
|
|
748
748
|
return BY_EXTENSION.get(lower.slice(dot)) ?? null;
|
|
749
749
|
}
|
|
750
|
-
function summarizeWithProfile(
|
|
751
|
-
const profile = profileForPath(
|
|
752
|
-
return profile ? profile.summarize(
|
|
750
|
+
function summarizeWithProfile(path13, content) {
|
|
751
|
+
const profile = profileForPath(path13);
|
|
752
|
+
return profile ? profile.summarize(path13, content) : null;
|
|
753
753
|
}
|
|
754
754
|
|
|
755
755
|
// src/ast/structured.ts
|
|
@@ -920,15 +920,15 @@ var SKIPPED_WALK_DIRS = /* @__PURE__ */ new Set([
|
|
|
920
920
|
]);
|
|
921
921
|
function readSmallFile(fullPath) {
|
|
922
922
|
try {
|
|
923
|
-
const stats =
|
|
923
|
+
const stats = fs11.statSync(fullPath);
|
|
924
924
|
if (!stats.isFile() || stats.size > MANIFEST_MAX_BYTES) return null;
|
|
925
|
-
return
|
|
925
|
+
return fs11.readFileSync(fullPath, "utf8");
|
|
926
926
|
} catch {
|
|
927
927
|
return null;
|
|
928
928
|
}
|
|
929
929
|
}
|
|
930
930
|
function readManifest(cwd, name) {
|
|
931
|
-
return readSmallFile(
|
|
931
|
+
return readSmallFile(path12.join(cwd, name));
|
|
932
932
|
}
|
|
933
933
|
function dependenciesFromPackageJson(raw) {
|
|
934
934
|
const names = /* @__PURE__ */ new Set();
|
|
@@ -988,7 +988,7 @@ function pnpmWorkspacePatterns(raw) {
|
|
|
988
988
|
function childDirectories(cwd, parent) {
|
|
989
989
|
let entries;
|
|
990
990
|
try {
|
|
991
|
-
entries =
|
|
991
|
+
entries = fs11.readdirSync(path12.join(cwd, parent), { withFileTypes: true });
|
|
992
992
|
} catch {
|
|
993
993
|
return [];
|
|
994
994
|
}
|
|
@@ -1023,13 +1023,13 @@ function workspacePackageDirs(cwd, patterns) {
|
|
|
1023
1023
|
function scanWorkspacePackages(cwd, patterns) {
|
|
1024
1024
|
const files = /* @__PURE__ */ new Set();
|
|
1025
1025
|
const dependencies = /* @__PURE__ */ new Set();
|
|
1026
|
-
for (const
|
|
1027
|
-
const full =
|
|
1026
|
+
for (const relative7 of workspacePackageDirs(cwd, patterns)) {
|
|
1027
|
+
const full = path12.join(cwd, relative7);
|
|
1028
1028
|
try {
|
|
1029
|
-
for (const entry of
|
|
1029
|
+
for (const entry of fs11.readdirSync(full, { withFileTypes: true })) files.add(entry.name);
|
|
1030
1030
|
} catch {
|
|
1031
1031
|
}
|
|
1032
|
-
const raw = readSmallFile(
|
|
1032
|
+
const raw = readSmallFile(path12.join(full, "package.json"));
|
|
1033
1033
|
if (raw !== null) {
|
|
1034
1034
|
for (const name of dependenciesFromPackageJson(raw)) dependencies.add(name);
|
|
1035
1035
|
}
|
|
@@ -1046,7 +1046,7 @@ function countMarkdownFiles(cwd) {
|
|
|
1046
1046
|
directoriesSeen += 1;
|
|
1047
1047
|
let entries;
|
|
1048
1048
|
try {
|
|
1049
|
-
entries =
|
|
1049
|
+
entries = fs11.readdirSync(current.dir, { withFileTypes: true });
|
|
1050
1050
|
} catch {
|
|
1051
1051
|
continue;
|
|
1052
1052
|
}
|
|
@@ -1057,7 +1057,7 @@ function countMarkdownFiles(cwd) {
|
|
|
1057
1057
|
}
|
|
1058
1058
|
if (!entry.isDirectory() || current.depth >= 2) continue;
|
|
1059
1059
|
if (entry.name.startsWith(".") || SKIPPED_WALK_DIRS.has(entry.name)) continue;
|
|
1060
|
-
queue.push({ dir:
|
|
1060
|
+
queue.push({ dir: path12.join(current.dir, entry.name), depth: current.depth + 1 });
|
|
1061
1061
|
}
|
|
1062
1062
|
}
|
|
1063
1063
|
return count;
|
|
@@ -1066,7 +1066,7 @@ function buildPresetContext(cwd) {
|
|
|
1066
1066
|
const files = /* @__PURE__ */ new Set();
|
|
1067
1067
|
const dirs = /* @__PURE__ */ new Set();
|
|
1068
1068
|
try {
|
|
1069
|
-
for (const entry of
|
|
1069
|
+
for (const entry of fs11.readdirSync(cwd, { withFileTypes: true })) {
|
|
1070
1070
|
if (entry.isDirectory()) dirs.add(entry.name);
|
|
1071
1071
|
else if (entry.isFile()) files.add(entry.name);
|
|
1072
1072
|
}
|
|
@@ -1083,7 +1083,7 @@ function buildPresetContext(cwd) {
|
|
|
1083
1083
|
const isWorkspace = files.has("pnpm-workspace.yaml") || files.has("nx.json") || files.has("turbo.json") || files.has("lerna.json") || packageJson !== void 0 && packageJson.includes('"workspaces"') || declaredWorkspaces.length > 0;
|
|
1084
1084
|
let workspace = { files: /* @__PURE__ */ new Set(), dependencies: /* @__PURE__ */ new Set() };
|
|
1085
1085
|
if (isWorkspace) {
|
|
1086
|
-
const pnpmRaw = files.has("pnpm-workspace.yaml") ? readSmallFile(
|
|
1086
|
+
const pnpmRaw = files.has("pnpm-workspace.yaml") ? readSmallFile(path12.join(cwd, "pnpm-workspace.yaml")) : null;
|
|
1087
1087
|
const declared = pnpmRaw === null ? [] : pnpmWorkspacePatterns(pnpmRaw);
|
|
1088
1088
|
const patterns = declared.length > 0 ? declared : declaredWorkspaces.length > 0 ? declaredWorkspaces : DEFAULT_WORKSPACE_PATTERNS;
|
|
1089
1089
|
workspace = scanWorkspacePackages(cwd, patterns);
|
|
@@ -1101,25 +1101,41 @@ function buildPresetContext(cwd) {
|
|
|
1101
1101
|
markdownCount: files.has("package.json") ? 0 : countMarkdownFiles(cwd)
|
|
1102
1102
|
};
|
|
1103
1103
|
}
|
|
1104
|
-
|
|
1105
|
-
|
|
1104
|
+
|
|
1105
|
+
// src/presets/evidence.ts
|
|
1106
|
+
function fileEvidence(context, name) {
|
|
1107
|
+
return context.files.has(name) ? name : null;
|
|
1106
1108
|
}
|
|
1107
|
-
function
|
|
1109
|
+
function fileStartingWithEvidence(context, prefix) {
|
|
1108
1110
|
for (const file of context.files) {
|
|
1109
|
-
if (file.startsWith(prefix)) return
|
|
1111
|
+
if (file.startsWith(prefix)) return file;
|
|
1110
1112
|
}
|
|
1111
1113
|
for (const file of context.workspaceFiles) {
|
|
1112
|
-
if (file.startsWith(prefix)) return
|
|
1114
|
+
if (file.startsWith(prefix)) return file;
|
|
1113
1115
|
}
|
|
1114
|
-
return
|
|
1116
|
+
return null;
|
|
1117
|
+
}
|
|
1118
|
+
function dependencyEvidence(context, ...names) {
|
|
1119
|
+
for (const name of names) {
|
|
1120
|
+
if (context.dependencies.has(name)) return `depends on ${name}`;
|
|
1121
|
+
}
|
|
1122
|
+
return null;
|
|
1115
1123
|
}
|
|
1116
|
-
function
|
|
1124
|
+
function manifestEvidence(context, manifest, needle) {
|
|
1117
1125
|
const content = context.manifests[manifest];
|
|
1118
|
-
if (!content) return
|
|
1119
|
-
|
|
1126
|
+
if (!content) return null;
|
|
1127
|
+
if (!content.toLowerCase().includes(needle.toLowerCase())) return null;
|
|
1128
|
+
return `${needle} in ${manifest}`;
|
|
1120
1129
|
}
|
|
1121
|
-
function
|
|
1122
|
-
|
|
1130
|
+
function suffixEvidence(context, suffix) {
|
|
1131
|
+
for (const file of context.files) {
|
|
1132
|
+
if (file.endsWith(suffix)) return file;
|
|
1133
|
+
}
|
|
1134
|
+
return null;
|
|
1135
|
+
}
|
|
1136
|
+
function markdownEvidence(context) {
|
|
1137
|
+
if (context.markdownCount < 3 || context.files.has("package.json")) return null;
|
|
1138
|
+
return `${context.markdownCount} markdown files, no package.json`;
|
|
1123
1139
|
}
|
|
1124
1140
|
|
|
1125
1141
|
// src/presets/helpers.ts
|
|
@@ -1327,7 +1343,7 @@ var JS_PRESETS = [
|
|
|
1327
1343
|
{
|
|
1328
1344
|
name: "nextjs",
|
|
1329
1345
|
priority: 100,
|
|
1330
|
-
detect: (context) =>
|
|
1346
|
+
detect: (context) => fileStartingWithEvidence(context, "next.config") ?? dependencyEvidence(context, "next"),
|
|
1331
1347
|
layers: NEXTJS_LAYERS,
|
|
1332
1348
|
kinds: NEXTJS_KINDS,
|
|
1333
1349
|
exclude: NEXT_LIKE_EXCLUDES,
|
|
@@ -1336,7 +1352,7 @@ var JS_PRESETS = [
|
|
|
1336
1352
|
{
|
|
1337
1353
|
name: "nuxt",
|
|
1338
1354
|
priority: 98,
|
|
1339
|
-
detect: (context) =>
|
|
1355
|
+
detect: (context) => fileStartingWithEvidence(context, "nuxt.config") ?? dependencyEvidence(context, "nuxt", "nuxt3"),
|
|
1340
1356
|
layers: [
|
|
1341
1357
|
layer("pages", "pages/", "src/pages/"),
|
|
1342
1358
|
layer("components", "components/", "src/components/"),
|
|
@@ -1360,7 +1376,7 @@ var JS_PRESETS = [
|
|
|
1360
1376
|
{
|
|
1361
1377
|
name: "sveltekit",
|
|
1362
1378
|
priority: 96,
|
|
1363
|
-
detect: (context) =>
|
|
1379
|
+
detect: (context) => fileStartingWithEvidence(context, "svelte.config") ?? dependencyEvidence(context, "@sveltejs/kit", "svelte"),
|
|
1364
1380
|
layers: [
|
|
1365
1381
|
layer("routes", "src/routes/", "routes/"),
|
|
1366
1382
|
layer("lib", "src/lib/", "lib/"),
|
|
@@ -1381,7 +1397,7 @@ var JS_PRESETS = [
|
|
|
1381
1397
|
{
|
|
1382
1398
|
name: "remix",
|
|
1383
1399
|
priority: 94,
|
|
1384
|
-
detect: (context) =>
|
|
1400
|
+
detect: (context) => dependencyEvidence(context, "@remix-run/react", "@remix-run/node", "@remix-run/dev"),
|
|
1385
1401
|
layers: [
|
|
1386
1402
|
layer("routes", "app/routes/", "routes/"),
|
|
1387
1403
|
layer("components", "app/components/"),
|
|
@@ -1395,7 +1411,7 @@ var JS_PRESETS = [
|
|
|
1395
1411
|
{
|
|
1396
1412
|
name: "astro",
|
|
1397
1413
|
priority: 92,
|
|
1398
|
-
detect: (context) =>
|
|
1414
|
+
detect: (context) => fileStartingWithEvidence(context, "astro.config") ?? dependencyEvidence(context, "astro"),
|
|
1399
1415
|
layers: [
|
|
1400
1416
|
layer("pages", "src/pages/"),
|
|
1401
1417
|
layer("layouts", "src/layouts/"),
|
|
@@ -1410,7 +1426,7 @@ var JS_PRESETS = [
|
|
|
1410
1426
|
{
|
|
1411
1427
|
name: "angular",
|
|
1412
1428
|
priority: 90,
|
|
1413
|
-
detect: (context) =>
|
|
1429
|
+
detect: (context) => fileEvidence(context, "angular.json") ?? dependencyEvidence(context, "@angular/core"),
|
|
1414
1430
|
layers: [
|
|
1415
1431
|
layer("app", "src/app/"),
|
|
1416
1432
|
layer("features", "src/app/features/"),
|
|
@@ -1434,7 +1450,7 @@ var JS_PRESETS = [
|
|
|
1434
1450
|
{
|
|
1435
1451
|
name: "solid",
|
|
1436
1452
|
priority: 88,
|
|
1437
|
-
detect: (context) =>
|
|
1453
|
+
detect: (context) => dependencyEvidence(context, "solid-js", "solid-start"),
|
|
1438
1454
|
layers: [
|
|
1439
1455
|
layer("pages", "src/routes/", "src/pages/"),
|
|
1440
1456
|
layer("components", "src/components/"),
|
|
@@ -1447,7 +1463,15 @@ var JS_PRESETS = [
|
|
|
1447
1463
|
{
|
|
1448
1464
|
name: "vue-vite",
|
|
1449
1465
|
priority: 86,
|
|
1450
|
-
|
|
1466
|
+
// `vue` is the decisive signal; `vite` (or its config file) only says the app is
|
|
1467
|
+
// built with Vite rather than shipped pre-compiled, so it is required but not what
|
|
1468
|
+
// gets reported.
|
|
1469
|
+
detect: (context) => {
|
|
1470
|
+
const framework = dependencyEvidence(context, "vue");
|
|
1471
|
+
if (framework === null) return null;
|
|
1472
|
+
const build = dependencyEvidence(context, "vite") ?? fileStartingWithEvidence(context, "vite.config");
|
|
1473
|
+
return build === null ? null : framework;
|
|
1474
|
+
},
|
|
1451
1475
|
layers: [
|
|
1452
1476
|
layer("pages", "src/pages/", "src/views/"),
|
|
1453
1477
|
layer("components", "src/components/"),
|
|
@@ -1472,7 +1496,12 @@ var JS_PRESETS = [
|
|
|
1472
1496
|
{
|
|
1473
1497
|
name: "react-vite",
|
|
1474
1498
|
priority: 84,
|
|
1475
|
-
detect: (context) =>
|
|
1499
|
+
detect: (context) => {
|
|
1500
|
+
const framework = dependencyEvidence(context, "react");
|
|
1501
|
+
if (framework === null) return null;
|
|
1502
|
+
const build = dependencyEvidence(context, "vite") ?? fileStartingWithEvidence(context, "vite.config");
|
|
1503
|
+
return build === null ? null : framework;
|
|
1504
|
+
},
|
|
1476
1505
|
layers: VITE_LAYERS,
|
|
1477
1506
|
kinds: VITE_KINDS,
|
|
1478
1507
|
exclude: [".vite", "dist"],
|
|
@@ -1481,7 +1510,7 @@ var JS_PRESETS = [
|
|
|
1481
1510
|
{
|
|
1482
1511
|
name: "nest",
|
|
1483
1512
|
priority: 80,
|
|
1484
|
-
detect: (context) =>
|
|
1513
|
+
detect: (context) => dependencyEvidence(context, "@nestjs/core"),
|
|
1485
1514
|
layers: [
|
|
1486
1515
|
layer("modules", "src/modules/", "modules/"),
|
|
1487
1516
|
layer("controllers", "src/controllers/", "controllers/"),
|
|
@@ -1507,7 +1536,7 @@ var JS_PRESETS = [
|
|
|
1507
1536
|
{
|
|
1508
1537
|
name: "node-express",
|
|
1509
1538
|
priority: 78,
|
|
1510
|
-
detect: (context) =>
|
|
1539
|
+
detect: (context) => dependencyEvidence(context, "express", "fastify", "koa", "@hapi/hapi"),
|
|
1511
1540
|
layers: NODE_LAYERS,
|
|
1512
1541
|
kinds: NODE_KINDS,
|
|
1513
1542
|
exclude: ["dist"],
|
|
@@ -1518,7 +1547,7 @@ var JS_PRESETS = [
|
|
|
1518
1547
|
// A bare package.json is often just tooling around a non-JS project, so it
|
|
1519
1548
|
// ranks below every definitive language manifest.
|
|
1520
1549
|
priority: 45,
|
|
1521
|
-
detect: (context) =>
|
|
1550
|
+
detect: (context) => fileEvidence(context, "package.json"),
|
|
1522
1551
|
layers: [
|
|
1523
1552
|
layer("src", "src/"),
|
|
1524
1553
|
layer("lib", "lib/"),
|
|
@@ -1532,13 +1561,12 @@ var JS_PRESETS = [
|
|
|
1532
1561
|
];
|
|
1533
1562
|
|
|
1534
1563
|
// src/presets/lang-presets.ts
|
|
1535
|
-
var anyFileEndingWith = (context, suffix) => Array.from(context.files).some((file) => file.endsWith(suffix));
|
|
1536
1564
|
var DIR = (name) => `${name}/`;
|
|
1537
1565
|
var LANGUAGE_PRESETS = [
|
|
1538
1566
|
{
|
|
1539
1567
|
name: "python-django",
|
|
1540
1568
|
priority: 56,
|
|
1541
|
-
detect: (context) =>
|
|
1569
|
+
detect: (context) => fileEvidence(context, "manage.py") ?? manifestEvidence(context, "pyproject.toml", "django") ?? manifestEvidence(context, "requirements.txt", "django"),
|
|
1542
1570
|
layers: [
|
|
1543
1571
|
layer("views", DIR("views"), DIR("app/views"), DIR("apps/views")),
|
|
1544
1572
|
layer("models", DIR("models"), DIR("app/models"), DIR("apps/models")),
|
|
@@ -1564,7 +1592,7 @@ var LANGUAGE_PRESETS = [
|
|
|
1564
1592
|
{
|
|
1565
1593
|
name: "python-fastapi",
|
|
1566
1594
|
priority: 54,
|
|
1567
|
-
detect: (context) =>
|
|
1595
|
+
detect: (context) => manifestEvidence(context, "requirements.txt", "fastapi") ?? manifestEvidence(context, "requirements.txt", "flask") ?? manifestEvidence(context, "pyproject.toml", "fastapi") ?? manifestEvidence(context, "pyproject.toml", "flask"),
|
|
1568
1596
|
layers: [
|
|
1569
1597
|
layer("routers", DIR("routers"), DIR("routes"), DIR("api")),
|
|
1570
1598
|
layer("models", DIR("models"), DIR("db")),
|
|
@@ -1588,7 +1616,7 @@ var LANGUAGE_PRESETS = [
|
|
|
1588
1616
|
name: "python-lib",
|
|
1589
1617
|
// Below node-lib: a Node repo with a stray .py file is still a Node repo.
|
|
1590
1618
|
priority: 30,
|
|
1591
|
-
detect: (context) =>
|
|
1619
|
+
detect: (context) => fileEvidence(context, "pyproject.toml") ?? fileEvidence(context, "setup.py") ?? fileEvidence(context, "requirements.txt") ?? suffixEvidence(context, ".py"),
|
|
1592
1620
|
layers: [
|
|
1593
1621
|
layer("package", DIR("src"), DIR("lib")),
|
|
1594
1622
|
layer("tests", DIR("tests"), DIR("test")),
|
|
@@ -1601,7 +1629,7 @@ var LANGUAGE_PRESETS = [
|
|
|
1601
1629
|
{
|
|
1602
1630
|
name: "go",
|
|
1603
1631
|
priority: 50,
|
|
1604
|
-
detect: (context) =>
|
|
1632
|
+
detect: (context) => fileEvidence(context, "go.mod"),
|
|
1605
1633
|
layers: [
|
|
1606
1634
|
layer("cmd", "cmd/"),
|
|
1607
1635
|
layer("internal", "internal/"),
|
|
@@ -1625,7 +1653,7 @@ var LANGUAGE_PRESETS = [
|
|
|
1625
1653
|
{
|
|
1626
1654
|
name: "rust",
|
|
1627
1655
|
priority: 48,
|
|
1628
|
-
detect: (context) =>
|
|
1656
|
+
detect: (context) => fileEvidence(context, "Cargo.toml"),
|
|
1629
1657
|
layers: [
|
|
1630
1658
|
layer("src", "src/"),
|
|
1631
1659
|
layer("crates", "crates/"),
|
|
@@ -1641,7 +1669,7 @@ var LANGUAGE_PRESETS = [
|
|
|
1641
1669
|
{
|
|
1642
1670
|
name: "java-spring",
|
|
1643
1671
|
priority: 46,
|
|
1644
|
-
detect: (context) =>
|
|
1672
|
+
detect: (context) => fileEvidence(context, "pom.xml") ?? fileEvidence(context, "build.gradle") ?? fileEvidence(context, "build.gradle.kts"),
|
|
1645
1673
|
layers: [
|
|
1646
1674
|
layer("api", "src/main/java/"),
|
|
1647
1675
|
layer("test", "src/test/java/"),
|
|
@@ -1664,7 +1692,7 @@ var LANGUAGE_PRESETS = [
|
|
|
1664
1692
|
{
|
|
1665
1693
|
name: "dotnet",
|
|
1666
1694
|
priority: 44,
|
|
1667
|
-
detect: (context) =>
|
|
1695
|
+
detect: (context) => suffixEvidence(context, ".csproj") ?? suffixEvidence(context, ".sln") ?? fileEvidence(context, "global.json"),
|
|
1668
1696
|
layers: [
|
|
1669
1697
|
layer("web", "src/", "Web/"),
|
|
1670
1698
|
layer("domain", "Domain/", "Core/"),
|
|
@@ -1687,7 +1715,7 @@ var LANGUAGE_PRESETS = [
|
|
|
1687
1715
|
{
|
|
1688
1716
|
name: "php-laravel",
|
|
1689
1717
|
priority: 42,
|
|
1690
|
-
detect: (context) =>
|
|
1718
|
+
detect: (context) => fileEvidence(context, "artisan") ?? manifestEvidence(context, "composer.json", "laravel") ?? fileEvidence(context, "composer.json"),
|
|
1691
1719
|
layers: [
|
|
1692
1720
|
layer("app", "app/"),
|
|
1693
1721
|
layer("routes", "routes/"),
|
|
@@ -1713,7 +1741,7 @@ var LANGUAGE_PRESETS = [
|
|
|
1713
1741
|
{
|
|
1714
1742
|
name: "ruby-rails",
|
|
1715
1743
|
priority: 40,
|
|
1716
|
-
detect: (context) =>
|
|
1744
|
+
detect: (context) => fileEvidence(context, "Gemfile") ?? fileEvidence(context, "config.ru") ?? fileEvidence(context, "Rakefile"),
|
|
1717
1745
|
layers: [
|
|
1718
1746
|
layer("app", "app/"),
|
|
1719
1747
|
layer("controllers", "app/controllers/"),
|
|
@@ -1786,7 +1814,7 @@ var SHAPE_PRESETS = [
|
|
|
1786
1814
|
{
|
|
1787
1815
|
name: "monorepo",
|
|
1788
1816
|
priority: 70,
|
|
1789
|
-
detect: (context) =>
|
|
1817
|
+
detect: (context) => fileEvidence(context, "pnpm-workspace.yaml") ?? fileEvidence(context, "nx.json") ?? fileEvidence(context, "turbo.json") ?? fileEvidence(context, "lerna.json") ?? manifestEvidence(context, "package.json", '"workspaces"'),
|
|
1790
1818
|
layers: [
|
|
1791
1819
|
layer("apps", "apps/"),
|
|
1792
1820
|
layer("packages", "packages/"),
|
|
@@ -1800,7 +1828,7 @@ var SHAPE_PRESETS = [
|
|
|
1800
1828
|
{
|
|
1801
1829
|
name: "infra",
|
|
1802
1830
|
priority: 24,
|
|
1803
|
-
detect: (context) =>
|
|
1831
|
+
detect: (context) => suffixEvidence(context, ".tf") ?? fileEvidence(context, "Chart.yaml") ?? fileEvidence(context, "kustomization.yaml") ?? fileEvidence(context, "docker-compose.yml") ?? fileEvidence(context, "docker-compose.yaml"),
|
|
1804
1832
|
layers: [
|
|
1805
1833
|
layer("modules", "modules/"),
|
|
1806
1834
|
layer("environments", "env/", "environments/"),
|
|
@@ -1822,7 +1850,7 @@ var SHAPE_PRESETS = [
|
|
|
1822
1850
|
// up to two directories down (bounded in the context builder). Counting
|
|
1823
1851
|
// only the root turned every real documentation repository — README.md plus
|
|
1824
1852
|
// the pages under `docs/` — into `generic`.
|
|
1825
|
-
detect: (context) =>
|
|
1853
|
+
detect: (context) => markdownEvidence(context),
|
|
1826
1854
|
layers: [
|
|
1827
1855
|
layer("content", "content/", "docs/"),
|
|
1828
1856
|
layer("assets", "assets/", "static/", "public/"),
|
|
@@ -1835,7 +1863,10 @@ var SHAPE_PRESETS = [
|
|
|
1835
1863
|
{
|
|
1836
1864
|
name: "generic",
|
|
1837
1865
|
priority: 0,
|
|
1838
|
-
|
|
1866
|
+
// No detector: this is the fallback, and `detectPresetFromContext` reaches it only
|
|
1867
|
+
// when every other preset reported no evidence. `fallback: true` is what lets the
|
|
1868
|
+
// wizard tell "I recognised this" apart from "I gave up".
|
|
1869
|
+
fallback: true,
|
|
1839
1870
|
layers: DEFAULT_LAYERS,
|
|
1840
1871
|
kinds: DEFAULT_KINDS,
|
|
1841
1872
|
exclude: [],
|
|
@@ -1867,20 +1898,27 @@ function detectPresetFromContext(context, forced) {
|
|
|
1867
1898
|
if (forced !== void 0) {
|
|
1868
1899
|
const preset = BY_NAME.get(forced);
|
|
1869
1900
|
if (!preset) throw new Error(unknownPresetMessage(forced));
|
|
1870
|
-
return toDetected(preset);
|
|
1901
|
+
return toDetected(preset, null);
|
|
1871
1902
|
}
|
|
1872
1903
|
for (const preset of PRESETS) {
|
|
1873
|
-
|
|
1904
|
+
const reason = preset.detect?.(context) ?? null;
|
|
1905
|
+
if (reason !== null) return toDetected(preset, reason);
|
|
1874
1906
|
}
|
|
1875
|
-
|
|
1907
|
+
const fallback = PRESETS.find((preset) => preset.fallback === true);
|
|
1908
|
+
if (fallback === void 0) {
|
|
1909
|
+
throw new Error("No fallback preset is defined. Exactly one preset must set `fallback: true`.");
|
|
1910
|
+
}
|
|
1911
|
+
return toDetected(fallback, null, true);
|
|
1876
1912
|
}
|
|
1877
|
-
function toDetected(preset) {
|
|
1913
|
+
function toDetected(preset, reason, fallback = false) {
|
|
1878
1914
|
return {
|
|
1879
1915
|
presetName: preset.name,
|
|
1880
1916
|
layers: preset.layers,
|
|
1881
1917
|
kinds: preset.kinds,
|
|
1882
1918
|
exclude: preset.exclude ? [...preset.exclude] : [],
|
|
1883
|
-
recommendedBudget: preset.recommendedBudget ?? null
|
|
1919
|
+
recommendedBudget: preset.recommendedBudget ?? null,
|
|
1920
|
+
reason,
|
|
1921
|
+
fallback
|
|
1884
1922
|
};
|
|
1885
1923
|
}
|
|
1886
1924
|
function detectPreset(cwd, forced) {
|
|
@@ -2320,14 +2358,14 @@ var PRESET_IGNORES = new Set(
|
|
|
2320
2358
|
);
|
|
2321
2359
|
function readLinkTarget(linkPath, fallback) {
|
|
2322
2360
|
try {
|
|
2323
|
-
return
|
|
2361
|
+
return fs11.readlinkSync(linkPath);
|
|
2324
2362
|
} catch {
|
|
2325
2363
|
return fallback;
|
|
2326
2364
|
}
|
|
2327
2365
|
}
|
|
2328
2366
|
function isInside(root, candidate) {
|
|
2329
|
-
const
|
|
2330
|
-
return
|
|
2367
|
+
const relative7 = path12.relative(root, candidate);
|
|
2368
|
+
return relative7 === "" || !relative7.startsWith(`..${path12.sep}`) && relative7 !== ".." && !path12.isAbsolute(relative7);
|
|
2331
2369
|
}
|
|
2332
2370
|
function getGitInfo(cwd, fallbackBranch = "main") {
|
|
2333
2371
|
try {
|
|
@@ -2532,10 +2570,10 @@ function trimUnescapedTrailingSpaces(line) {
|
|
|
2532
2570
|
return line.slice(0, end);
|
|
2533
2571
|
}
|
|
2534
2572
|
function readGitignoreRules(dir) {
|
|
2535
|
-
const gitignorePath2 =
|
|
2573
|
+
const gitignorePath2 = path12.join(dir, ".gitignore");
|
|
2536
2574
|
let raw;
|
|
2537
2575
|
try {
|
|
2538
|
-
raw =
|
|
2576
|
+
raw = fs11.readFileSync(gitignorePath2, "utf8");
|
|
2539
2577
|
} catch {
|
|
2540
2578
|
return [];
|
|
2541
2579
|
}
|
|
@@ -2662,7 +2700,7 @@ function isToolOwnedDirName(name) {
|
|
|
2662
2700
|
}
|
|
2663
2701
|
function hasSnapshotMarker(target) {
|
|
2664
2702
|
try {
|
|
2665
|
-
return
|
|
2703
|
+
return fs11.statSync(target).isDirectory() && fs11.existsSync(path12.join(target, ".codebase-snapshot"));
|
|
2666
2704
|
} catch {
|
|
2667
2705
|
return false;
|
|
2668
2706
|
}
|
|
@@ -2676,7 +2714,7 @@ function makeToolOwnedDecider(cwd, toolOwnedPaths) {
|
|
|
2676
2714
|
const prefix = parts.slice(0, depth).join("/");
|
|
2677
2715
|
let hasMarker = markers.get(prefix);
|
|
2678
2716
|
if (hasMarker === void 0) {
|
|
2679
|
-
hasMarker =
|
|
2717
|
+
hasMarker = fs11.existsSync(path12.join(cwd, prefix, ".codebase-snapshot"));
|
|
2680
2718
|
markers.set(prefix, hasMarker);
|
|
2681
2719
|
}
|
|
2682
2720
|
if (hasMarker) return true;
|
|
@@ -2691,7 +2729,7 @@ function makeLinkProbe() {
|
|
|
2691
2729
|
let known = cache.get(absolutePath);
|
|
2692
2730
|
if (known === void 0) {
|
|
2693
2731
|
try {
|
|
2694
|
-
known =
|
|
2732
|
+
known = fs11.lstatSync(absolutePath).isSymbolicLink();
|
|
2695
2733
|
} catch {
|
|
2696
2734
|
known = false;
|
|
2697
2735
|
}
|
|
@@ -2704,7 +2742,7 @@ function dropLinkedDirectories(cwd, candidates, skipped) {
|
|
|
2704
2742
|
const isLink = makeLinkProbe();
|
|
2705
2743
|
let rootReal = cwd;
|
|
2706
2744
|
try {
|
|
2707
|
-
rootReal =
|
|
2745
|
+
rootReal = fs11.realpathSync(cwd);
|
|
2708
2746
|
} catch {
|
|
2709
2747
|
}
|
|
2710
2748
|
const reported = /* @__PURE__ */ new Set();
|
|
@@ -2714,7 +2752,7 @@ function dropLinkedDirectories(cwd, candidates, skipped) {
|
|
|
2714
2752
|
let linkedDir = null;
|
|
2715
2753
|
for (let depth = 1; depth <= parts.length; depth += 1) {
|
|
2716
2754
|
const prefix = parts.slice(0, depth).join("/");
|
|
2717
|
-
if (isLink(
|
|
2755
|
+
if (isLink(path12.join(cwd, prefix))) {
|
|
2718
2756
|
linkedDir = prefix;
|
|
2719
2757
|
break;
|
|
2720
2758
|
}
|
|
@@ -2725,10 +2763,10 @@ function dropLinkedDirectories(cwd, candidates, skipped) {
|
|
|
2725
2763
|
}
|
|
2726
2764
|
if (reported.has(linkedDir)) continue;
|
|
2727
2765
|
reported.add(linkedDir);
|
|
2728
|
-
const absolute =
|
|
2766
|
+
const absolute = path12.join(cwd, linkedDir);
|
|
2729
2767
|
let targetReal = absolute;
|
|
2730
2768
|
try {
|
|
2731
|
-
targetReal =
|
|
2769
|
+
targetReal = fs11.realpathSync(absolute);
|
|
2732
2770
|
} catch {
|
|
2733
2771
|
}
|
|
2734
2772
|
const target = readLinkTarget(absolute, targetReal);
|
|
@@ -2805,7 +2843,7 @@ function collectFiles(cwd, options = {}) {
|
|
|
2805
2843
|
"Discovery mode is `git`, but git could not list this directory's files. Run inside a working repository, or pass --discovery walk (or auto) to scan the tree directly."
|
|
2806
2844
|
);
|
|
2807
2845
|
}
|
|
2808
|
-
if (
|
|
2846
|
+
if (fs11.existsSync(path12.join(cwd, ".git"))) {
|
|
2809
2847
|
throw new Error(
|
|
2810
2848
|
"This directory is a git repository, but git could not list its files. A plain walk honours .gitignore files, but git also applies .git/info/exclude and your global excludes file, includes files that are tracked although a .gitignore now matches them, and treats a submodule as a single entry. Fix the repository (git status should work), run outside git, or pass --discovery walk to accept those differences."
|
|
2811
2849
|
);
|
|
@@ -2864,7 +2902,7 @@ function collectFiles(cwd, options = {}) {
|
|
|
2864
2902
|
}
|
|
2865
2903
|
function rootRealFor(root) {
|
|
2866
2904
|
try {
|
|
2867
|
-
return
|
|
2905
|
+
return fs11.realpathSync(root);
|
|
2868
2906
|
} catch {
|
|
2869
2907
|
return root;
|
|
2870
2908
|
}
|
|
@@ -2872,18 +2910,18 @@ function rootRealFor(root) {
|
|
|
2872
2910
|
function walkDirectory(dir, baseDir, rootReal, visited, reservedPaths = /* @__PURE__ */ new Set(), parentGitignoreLayers = []) {
|
|
2873
2911
|
const results = { files: [], skipped: [] };
|
|
2874
2912
|
const stack = [];
|
|
2875
|
-
const relativeToBase = (target) =>
|
|
2913
|
+
const relativeToBase = (target) => path12.relative(baseDir, target).replace(/\\/g, "/") || ".";
|
|
2876
2914
|
const enter = (current, layers) => {
|
|
2877
2915
|
if (visited.has(current)) return false;
|
|
2878
2916
|
visited.add(current);
|
|
2879
2917
|
const localRules = readGitignoreRules(current);
|
|
2880
2918
|
const gitignoreLayers = localRules.length === 0 ? layers : [
|
|
2881
2919
|
...layers,
|
|
2882
|
-
{ prefix:
|
|
2920
|
+
{ prefix: path12.relative(baseDir, current).replace(/\\/g, "/"), rules: localRules }
|
|
2883
2921
|
];
|
|
2884
2922
|
let entries;
|
|
2885
2923
|
try {
|
|
2886
|
-
entries =
|
|
2924
|
+
entries = fs11.readdirSync(current, { withFileTypes: true });
|
|
2887
2925
|
} catch {
|
|
2888
2926
|
results.skipped.push({
|
|
2889
2927
|
path: relativeToBase(current),
|
|
@@ -2905,8 +2943,8 @@ function walkDirectory(dir, baseDir, rootReal, visited, reservedPaths = /* @__PU
|
|
|
2905
2943
|
}
|
|
2906
2944
|
const entry = frame.entries[frame.index];
|
|
2907
2945
|
frame.index += 1;
|
|
2908
|
-
const fullPath =
|
|
2909
|
-
const relPath =
|
|
2946
|
+
const fullPath = path12.join(frame.dir, entry.name);
|
|
2947
|
+
const relPath = path12.relative(baseDir, fullPath).replace(/\\/g, "/");
|
|
2910
2948
|
if (isToolOwnedDirName(entry.name) && reservedPaths.has(relPath)) continue;
|
|
2911
2949
|
if (DEFAULT_IGNORED_DIRS.includes(entry.name)) {
|
|
2912
2950
|
results.skipped.push({
|
|
@@ -2939,14 +2977,14 @@ function walkDirectory(dir, baseDir, rootReal, visited, reservedPaths = /* @__PU
|
|
|
2939
2977
|
if (entry.isSymbolicLink()) {
|
|
2940
2978
|
let targetReal;
|
|
2941
2979
|
try {
|
|
2942
|
-
targetReal =
|
|
2980
|
+
targetReal = fs11.realpathSync(fullPath);
|
|
2943
2981
|
} catch {
|
|
2944
2982
|
results.skipped.push({ path: relPath, reason: "read-error", detail: "broken symlink" });
|
|
2945
2983
|
continue;
|
|
2946
2984
|
}
|
|
2947
2985
|
let stats;
|
|
2948
2986
|
try {
|
|
2949
|
-
stats =
|
|
2987
|
+
stats = fs11.statSync(fullPath);
|
|
2950
2988
|
} catch {
|
|
2951
2989
|
results.skipped.push({ path: relPath, reason: "read-error", detail: "unreadable symlink" });
|
|
2952
2990
|
continue;
|
|
@@ -2999,6 +3037,20 @@ var CONFIG_FILENAMES = [
|
|
|
2999
3037
|
".snapshotrc.json",
|
|
3000
3038
|
".snapshotrc"
|
|
3001
3039
|
];
|
|
3040
|
+
function findConfigFile(cwd) {
|
|
3041
|
+
for (const filename of CONFIG_FILENAMES) {
|
|
3042
|
+
const fullPath = path12.join(cwd, filename);
|
|
3043
|
+
if (fs11.existsSync(fullPath)) return fullPath;
|
|
3044
|
+
}
|
|
3045
|
+
const pkgJsonPath = path12.join(cwd, "package.json");
|
|
3046
|
+
if (!fs11.existsSync(pkgJsonPath)) return null;
|
|
3047
|
+
try {
|
|
3048
|
+
const parsed = JSON.parse(fs11.readFileSync(pkgJsonPath, "utf8"));
|
|
3049
|
+
return parsed.snapshot === void 0 ? null : pkgJsonPath;
|
|
3050
|
+
} catch {
|
|
3051
|
+
return null;
|
|
3052
|
+
}
|
|
3053
|
+
}
|
|
3002
3054
|
var RESOLVED_CONFIG_BRAND = /* @__PURE__ */ Symbol.for(
|
|
3003
3055
|
"codebase-snapshot/resolved-config"
|
|
3004
3056
|
);
|
|
@@ -3244,11 +3296,11 @@ async function loadConfigFileDetailed(cwd) {
|
|
|
3244
3296
|
const warnings = [];
|
|
3245
3297
|
const errors = [];
|
|
3246
3298
|
for (const filename of CONFIG_FILENAMES) {
|
|
3247
|
-
const fullPath =
|
|
3248
|
-
if (!
|
|
3299
|
+
const fullPath = path12.join(cwd, filename);
|
|
3300
|
+
if (!fs11.existsSync(fullPath)) continue;
|
|
3249
3301
|
if (filename.endsWith(".json") || filename === ".snapshotrc") {
|
|
3250
3302
|
try {
|
|
3251
|
-
const config = parseJsonish(
|
|
3303
|
+
const config = parseJsonish(fs11.readFileSync(fullPath, "utf8"), fullPath);
|
|
3252
3304
|
if (config === null || typeof config !== "object" || Array.isArray(config)) {
|
|
3253
3305
|
throw new Error(
|
|
3254
3306
|
`${fullPath} does not hold a configuration object (received ${describeValue2(config)}).`
|
|
@@ -3282,10 +3334,10 @@ async function loadConfigFileDetailed(cwd) {
|
|
|
3282
3334
|
continue;
|
|
3283
3335
|
}
|
|
3284
3336
|
}
|
|
3285
|
-
const pkgJsonPath =
|
|
3286
|
-
if (
|
|
3337
|
+
const pkgJsonPath = path12.join(cwd, "package.json");
|
|
3338
|
+
if (fs11.existsSync(pkgJsonPath)) {
|
|
3287
3339
|
try {
|
|
3288
|
-
const parsed = parseJsonish(
|
|
3340
|
+
const parsed = parseJsonish(fs11.readFileSync(pkgJsonPath, "utf8"), pkgJsonPath);
|
|
3289
3341
|
const snapshotKey = parsed.snapshot;
|
|
3290
3342
|
if (snapshotKey && typeof snapshotKey === "object" && !Array.isArray(snapshotKey)) {
|
|
3291
3343
|
return { config: snapshotKey, file: pkgJsonPath, warnings, errors };
|
|
@@ -3308,7 +3360,7 @@ async function loadConfigFile(cwd) {
|
|
|
3308
3360
|
const loaded = await loadConfigFileDetailed(cwd);
|
|
3309
3361
|
if (loaded.config && loaded.file && typeof loaded.config === "object") {
|
|
3310
3362
|
Object.defineProperty(loaded.config, CONFIG_DIR_SYMBOL, {
|
|
3311
|
-
value:
|
|
3363
|
+
value: path12.dirname(loaded.file),
|
|
3312
3364
|
enumerable: false,
|
|
3313
3365
|
configurable: true
|
|
3314
3366
|
});
|
|
@@ -3467,12 +3519,12 @@ function resolveKindRules(value) {
|
|
|
3467
3519
|
function outputExcludePatterns(cwd, outDir, tmpDir) {
|
|
3468
3520
|
const patterns = [];
|
|
3469
3521
|
for (const dir of [outDir, tmpDir]) {
|
|
3470
|
-
const absolute =
|
|
3471
|
-
const
|
|
3472
|
-
if (!
|
|
3522
|
+
const absolute = path12.resolve(cwd, dir);
|
|
3523
|
+
const relative7 = path12.relative(cwd, absolute);
|
|
3524
|
+
if (!relative7 || relative7 === ".." || relative7.startsWith(`..${path12.sep}`) || path12.isAbsolute(relative7)) {
|
|
3473
3525
|
continue;
|
|
3474
3526
|
}
|
|
3475
|
-
patterns.push(`${
|
|
3527
|
+
patterns.push(`${relative7.replace(/\\/g, "/")}/**`);
|
|
3476
3528
|
}
|
|
3477
3529
|
return patterns;
|
|
3478
3530
|
}
|
|
@@ -3588,11 +3640,11 @@ function resolveCandidateCwd(userConfig, cliOptions, baseDir) {
|
|
|
3588
3640
|
);
|
|
3589
3641
|
}
|
|
3590
3642
|
const candidate = cliOptions?.cwd || userConfig?.cwd || baseDir || process.cwd();
|
|
3591
|
-
const resolved =
|
|
3592
|
-
if (!
|
|
3643
|
+
const resolved = path12.resolve(candidate);
|
|
3644
|
+
if (!fs11.existsSync(resolved)) {
|
|
3593
3645
|
throw new Error(`Working directory does not exist: ${resolved}`);
|
|
3594
3646
|
}
|
|
3595
|
-
if (!
|
|
3647
|
+
if (!fs11.statSync(resolved).isDirectory()) {
|
|
3596
3648
|
throw new Error(`Working directory is not a directory: ${resolved}`);
|
|
3597
3649
|
}
|
|
3598
3650
|
return resolved;
|
|
@@ -4616,7 +4668,7 @@ var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
4616
4668
|
".so"
|
|
4617
4669
|
]);
|
|
4618
4670
|
function isBinaryExtension(filePath) {
|
|
4619
|
-
const ext =
|
|
4671
|
+
const ext = path12.extname(filePath).toLowerCase();
|
|
4620
4672
|
return BINARY_EXTENSIONS.has(ext);
|
|
4621
4673
|
}
|
|
4622
4674
|
function isBinaryBuffer(buffer) {
|
|
@@ -4786,13 +4838,13 @@ function isBinaryFile(absolutePath) {
|
|
|
4786
4838
|
if (isBinaryExtension(absolutePath)) return true;
|
|
4787
4839
|
let fd;
|
|
4788
4840
|
try {
|
|
4789
|
-
fd =
|
|
4841
|
+
fd = fs11.openSync(absolutePath, "r");
|
|
4790
4842
|
const buffer = Buffer.alloc(CHUNK);
|
|
4791
4843
|
let sample;
|
|
4792
4844
|
let position = 0;
|
|
4793
4845
|
while (position < BINARY_SCAN_BYTES) {
|
|
4794
4846
|
const wanted = Math.min(CHUNK, BINARY_SCAN_BYTES - position);
|
|
4795
|
-
const bytesRead =
|
|
4847
|
+
const bytesRead = fs11.readSync(fd, buffer, 0, wanted, position);
|
|
4796
4848
|
if (bytesRead === 0) return false;
|
|
4797
4849
|
const chunk = buffer.subarray(0, bytesRead);
|
|
4798
4850
|
if (sample === void 0) {
|
|
@@ -4810,7 +4862,7 @@ function isBinaryFile(absolutePath) {
|
|
|
4810
4862
|
} finally {
|
|
4811
4863
|
if (fd !== void 0) {
|
|
4812
4864
|
try {
|
|
4813
|
-
|
|
4865
|
+
fs11.closeSync(fd);
|
|
4814
4866
|
} catch {
|
|
4815
4867
|
}
|
|
4816
4868
|
}
|
|
@@ -4870,10 +4922,10 @@ var LANGUAGE_BY_BASENAME = {
|
|
|
4870
4922
|
};
|
|
4871
4923
|
function detectLanguage(filePath) {
|
|
4872
4924
|
const normalized = filePath.replace(/\\/g, "/");
|
|
4873
|
-
const base =
|
|
4925
|
+
const base = path12.posix.basename(normalized);
|
|
4874
4926
|
if (LANGUAGE_BY_BASENAME[base]) return LANGUAGE_BY_BASENAME[base];
|
|
4875
4927
|
if (base === ".env" || base.startsWith(".env.")) return "dotenv";
|
|
4876
|
-
const ext =
|
|
4928
|
+
const ext = path12.posix.extname(base).toLowerCase();
|
|
4877
4929
|
return LANGUAGE_BY_EXTENSION[ext] ?? "text";
|
|
4878
4930
|
}
|
|
4879
4931
|
function maskedPath(value, secretRules) {
|
|
@@ -4881,33 +4933,33 @@ function maskedPath(value, secretRules) {
|
|
|
4881
4933
|
}
|
|
4882
4934
|
function confineToDirectory(directory, filename) {
|
|
4883
4935
|
if (filename.includes("\0")) return null;
|
|
4884
|
-
if (
|
|
4885
|
-
const base =
|
|
4886
|
-
const resolved =
|
|
4936
|
+
if (path12.isAbsolute(filename)) return null;
|
|
4937
|
+
const base = path12.resolve(directory);
|
|
4938
|
+
const resolved = path12.resolve(base, filename);
|
|
4887
4939
|
const caseInsensitive = process.platform === "win32" || process.platform === "darwin";
|
|
4888
|
-
const prefix = caseInsensitive ? `${base}${
|
|
4940
|
+
const prefix = caseInsensitive ? `${base}${path12.sep}`.toLowerCase() : `${base}${path12.sep}`;
|
|
4889
4941
|
const candidate = caseInsensitive ? resolved.toLowerCase() : resolved;
|
|
4890
4942
|
return candidate.startsWith(prefix) ? resolved : null;
|
|
4891
4943
|
}
|
|
4892
4944
|
function findStagingDirectory(config) {
|
|
4893
|
-
const tmpDir =
|
|
4945
|
+
const tmpDir = path12.resolve(config.cwd, config.tmpDir);
|
|
4894
4946
|
let entries;
|
|
4895
4947
|
try {
|
|
4896
|
-
entries =
|
|
4948
|
+
entries = fs11.readdirSync(tmpDir);
|
|
4897
4949
|
} catch {
|
|
4898
4950
|
return null;
|
|
4899
4951
|
}
|
|
4900
4952
|
const runs = entries.filter((name) => name.startsWith("run-")).sort();
|
|
4901
4953
|
const newest = runs[runs.length - 1];
|
|
4902
|
-
return newest ?
|
|
4954
|
+
return newest ? path12.join(tmpDir, newest) : null;
|
|
4903
4955
|
}
|
|
4904
4956
|
function describeMissingManifest(config, manifestPath) {
|
|
4905
|
-
const outDir =
|
|
4906
|
-
const markerPath =
|
|
4957
|
+
const outDir = path12.resolve(config.cwd, config.outDir);
|
|
4958
|
+
const markerPath = path12.join(outDir, ".codebase-snapshot");
|
|
4907
4959
|
let marker2 = null;
|
|
4908
4960
|
try {
|
|
4909
|
-
if (
|
|
4910
|
-
const parsed = JSON.parse(
|
|
4961
|
+
if (fs11.existsSync(markerPath)) {
|
|
4962
|
+
const parsed = JSON.parse(fs11.readFileSync(markerPath, "utf8"));
|
|
4911
4963
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
4912
4964
|
marker2 = parsed;
|
|
4913
4965
|
}
|
|
@@ -4943,14 +4995,14 @@ function sha256Hex(buffer) {
|
|
|
4943
4995
|
}
|
|
4944
4996
|
var VERIFY_SNIFF_MAX_BYTES = 8 * 1024 * 1024;
|
|
4945
4997
|
function wouldNowBeIncluded(cwd, relativePath, reason) {
|
|
4946
|
-
const absolutePath =
|
|
4998
|
+
const absolutePath = path12.join(cwd, relativePath);
|
|
4947
4999
|
try {
|
|
4948
|
-
const stats =
|
|
5000
|
+
const stats = fs11.statSync(absolutePath);
|
|
4949
5001
|
if (!stats.isFile()) return false;
|
|
4950
5002
|
if (isBinaryFile(absolutePath)) return false;
|
|
4951
5003
|
if (reason !== "binary") return true;
|
|
4952
5004
|
if (stats.size > VERIFY_SNIFF_MAX_BYTES) return false;
|
|
4953
|
-
return !decodeText(
|
|
5005
|
+
return !decodeText(fs11.readFileSync(absolutePath)).binary;
|
|
4954
5006
|
} catch {
|
|
4955
5007
|
return false;
|
|
4956
5008
|
}
|
|
@@ -5008,8 +5060,8 @@ function describeManifestShapeProblem(value) {
|
|
|
5008
5060
|
function validateSnapshot(configInput) {
|
|
5009
5061
|
assertUsableConfigInput(configInput);
|
|
5010
5062
|
const config = isResolvedConfig(configInput) ? normalizeResolvedConfig(configInput) : resolveConfig(configInput);
|
|
5011
|
-
const manifestPath =
|
|
5012
|
-
if (!
|
|
5063
|
+
const manifestPath = path12.resolve(config.cwd, config.outDir, "manifest.json");
|
|
5064
|
+
if (!fs11.existsSync(manifestPath)) {
|
|
5013
5065
|
return {
|
|
5014
5066
|
valid: false,
|
|
5015
5067
|
manifestPresent: false,
|
|
@@ -5020,7 +5072,7 @@ function validateSnapshot(configInput) {
|
|
|
5020
5072
|
}
|
|
5021
5073
|
let manifest;
|
|
5022
5074
|
try {
|
|
5023
|
-
manifest = JSON.parse(
|
|
5075
|
+
manifest = JSON.parse(fs11.readFileSync(manifestPath, "utf8"));
|
|
5024
5076
|
} catch (err) {
|
|
5025
5077
|
return {
|
|
5026
5078
|
valid: false,
|
|
@@ -5042,7 +5094,7 @@ function validateSnapshot(configInput) {
|
|
|
5042
5094
|
}
|
|
5043
5095
|
const drift = emptyDrift();
|
|
5044
5096
|
const warnings = [];
|
|
5045
|
-
const snapshotDir =
|
|
5097
|
+
const snapshotDir = path12.resolve(config.cwd, config.outDir);
|
|
5046
5098
|
const manifestFileMap = /* @__PURE__ */ new Map();
|
|
5047
5099
|
for (const file of manifest.files) {
|
|
5048
5100
|
if (file && typeof file.relativePath === "string") {
|
|
@@ -5076,7 +5128,7 @@ function validateSnapshot(configInput) {
|
|
|
5076
5128
|
}
|
|
5077
5129
|
let diskHash;
|
|
5078
5130
|
try {
|
|
5079
|
-
diskHash = sha256Hex(
|
|
5131
|
+
diskHash = sha256Hex(fs11.readFileSync(path12.join(config.cwd, diskFile)));
|
|
5080
5132
|
} catch (err) {
|
|
5081
5133
|
if (err.code === "ENOENT") {
|
|
5082
5134
|
drift.missingOnDisk.push(diskFile);
|
|
@@ -5100,7 +5152,7 @@ function validateSnapshot(configInput) {
|
|
|
5100
5152
|
}
|
|
5101
5153
|
let statFailure = null;
|
|
5102
5154
|
try {
|
|
5103
|
-
|
|
5155
|
+
fs11.statSync(confined);
|
|
5104
5156
|
} catch (err) {
|
|
5105
5157
|
statFailure = err;
|
|
5106
5158
|
}
|
|
@@ -5120,7 +5172,7 @@ function validateSnapshot(configInput) {
|
|
|
5120
5172
|
...Object.values(config.budgets.byLayer)
|
|
5121
5173
|
);
|
|
5122
5174
|
const partNames = /* @__PURE__ */ new Set();
|
|
5123
|
-
const partsDir =
|
|
5175
|
+
const partsDir = path12.join(snapshotDir, "parts");
|
|
5124
5176
|
for (const part of manifest.parts) {
|
|
5125
5177
|
if (!part || typeof part.filename !== "string") continue;
|
|
5126
5178
|
partNames.add(part.filename);
|
|
@@ -5135,7 +5187,7 @@ function validateSnapshot(configInput) {
|
|
|
5135
5187
|
}
|
|
5136
5188
|
let buffer;
|
|
5137
5189
|
try {
|
|
5138
|
-
buffer =
|
|
5190
|
+
buffer = fs11.readFileSync(partPath);
|
|
5139
5191
|
} catch {
|
|
5140
5192
|
drift.partIssues.push({ part: part.filename, issue: "missing from snapshot/parts" });
|
|
5141
5193
|
continue;
|
|
@@ -5349,9 +5401,9 @@ function validateSnapshot(configInput) {
|
|
|
5349
5401
|
["full-codebase.md", manifest.artifacts?.fullMarkdown]
|
|
5350
5402
|
]) {
|
|
5351
5403
|
if (!artifact) continue;
|
|
5352
|
-
const artifactPath =
|
|
5404
|
+
const artifactPath = path12.join(snapshotDir, key);
|
|
5353
5405
|
try {
|
|
5354
|
-
const buffer =
|
|
5406
|
+
const buffer = fs11.readFileSync(artifactPath);
|
|
5355
5407
|
if (sha256Hex(buffer) !== artifact.sha256 || buffer.length !== artifact.byteSize) {
|
|
5356
5408
|
drift.partIssues.push({ part: key, issue: "artifact does not match its recorded digest" });
|
|
5357
5409
|
}
|
|
@@ -5421,7 +5473,7 @@ function validateSnapshot(configInput) {
|
|
|
5421
5473
|
|
|
5422
5474
|
// src/version.ts
|
|
5423
5475
|
var GENERATOR_NAME = "codebase-snapshot";
|
|
5424
|
-
var GENERATOR_VERSION = "0.9.
|
|
5476
|
+
var GENERATOR_VERSION = "0.9.5";
|
|
5425
5477
|
var LEGACY_GENERATOR_NAMES = ["@ankurhalder/codebase-snapshot"];
|
|
5426
5478
|
function isOurGeneratorName(name) {
|
|
5427
5479
|
if (typeof name !== "string") return false;
|
|
@@ -5436,42 +5488,42 @@ function describeMatcher(matcher) {
|
|
|
5436
5488
|
return String(matcher);
|
|
5437
5489
|
}
|
|
5438
5490
|
function normalizeTarget(cwd, target) {
|
|
5439
|
-
const absolute =
|
|
5440
|
-
const
|
|
5441
|
-
if (
|
|
5442
|
-
return
|
|
5491
|
+
const absolute = path12.resolve(cwd, target);
|
|
5492
|
+
const relative7 = path12.relative(cwd, absolute);
|
|
5493
|
+
if (relative7.startsWith("..") || path12.isAbsolute(relative7)) return target;
|
|
5494
|
+
return relative7.split(path12.sep).join("/");
|
|
5443
5495
|
}
|
|
5444
5496
|
function findRuleMatch(rules, relativePath) {
|
|
5445
5497
|
return rules.find(
|
|
5446
5498
|
(rule) => matchRule(relativePath, rule.match)
|
|
5447
5499
|
);
|
|
5448
5500
|
}
|
|
5449
|
-
function findRecordedAncestor(skipped,
|
|
5501
|
+
function findRecordedAncestor(skipped, relative7, maskedPath2) {
|
|
5450
5502
|
let nearest;
|
|
5451
5503
|
for (const record of skipped) {
|
|
5452
5504
|
const ancestorPath = record.path;
|
|
5453
5505
|
if (ancestorPath.length === 0) continue;
|
|
5454
|
-
const isAncestor =
|
|
5506
|
+
const isAncestor = relative7.startsWith(`${ancestorPath}/`) || maskedPath2.startsWith(`${ancestorPath}/`);
|
|
5455
5507
|
if (!isAncestor) continue;
|
|
5456
5508
|
if (!nearest || ancestorPath.length > nearest.path.length) nearest = record;
|
|
5457
5509
|
}
|
|
5458
5510
|
return nearest;
|
|
5459
5511
|
}
|
|
5460
5512
|
function readManifest2(config) {
|
|
5461
|
-
const manifestPath =
|
|
5513
|
+
const manifestPath = path12.resolve(config.cwd, config.outDir, "manifest.json");
|
|
5462
5514
|
try {
|
|
5463
|
-
return JSON.parse(
|
|
5515
|
+
return JSON.parse(fs11.readFileSync(manifestPath, "utf8"));
|
|
5464
5516
|
} catch {
|
|
5465
5517
|
return null;
|
|
5466
5518
|
}
|
|
5467
5519
|
}
|
|
5468
5520
|
function explainPath(config, target) {
|
|
5469
|
-
const
|
|
5470
|
-
const absolute =
|
|
5471
|
-
const maskedPath2 = findSecretInPath(
|
|
5521
|
+
const relative7 = normalizeTarget(config.cwd, target);
|
|
5522
|
+
const absolute = path12.join(config.cwd, relative7);
|
|
5523
|
+
const maskedPath2 = findSecretInPath(relative7, config.secretRules)?.masked ?? relative7;
|
|
5472
5524
|
const result = {
|
|
5473
5525
|
path: maskedPath2,
|
|
5474
|
-
exists:
|
|
5526
|
+
exists: fs11.existsSync(absolute),
|
|
5475
5527
|
included: false
|
|
5476
5528
|
};
|
|
5477
5529
|
const collection = collectFiles(config.cwd, {
|
|
@@ -5482,12 +5534,12 @@ function explainPath(config, target) {
|
|
|
5482
5534
|
// Same exclusion `generate` applies, so `explain` never says "not-discovered" about a file
|
|
5483
5535
|
// that is only missing because the walker dropped the tool's own output directory.
|
|
5484
5536
|
toolOwnedPaths: [config.outDir, config.tmpDir].map(
|
|
5485
|
-
(directory) =>
|
|
5486
|
-
).filter((
|
|
5537
|
+
(directory) => path12.relative(config.cwd, path12.resolve(config.cwd, directory)).replace(/\\/g, "/")
|
|
5538
|
+
).filter((relative8) => relative8.length > 0 && !relative8.startsWith(".."))
|
|
5487
5539
|
});
|
|
5488
|
-
if (!collection.files.includes(
|
|
5540
|
+
if (!collection.files.includes(relative7)) {
|
|
5489
5541
|
const record = collection.skipped.find(
|
|
5490
|
-
(entry2) => entry2.path ===
|
|
5542
|
+
(entry2) => entry2.path === relative7 || entry2.path === maskedPath2
|
|
5491
5543
|
);
|
|
5492
5544
|
if (record) {
|
|
5493
5545
|
result.reason = record.reason;
|
|
@@ -5496,7 +5548,7 @@ function explainPath(config, target) {
|
|
|
5496
5548
|
result.reason = "missing";
|
|
5497
5549
|
result.detail = "no such file or directory, relative to the configured cwd";
|
|
5498
5550
|
} else {
|
|
5499
|
-
const ancestor = findRecordedAncestor(collection.skipped,
|
|
5551
|
+
const ancestor = findRecordedAncestor(collection.skipped, relative7, maskedPath2);
|
|
5500
5552
|
if (ancestor) {
|
|
5501
5553
|
result.reason = ancestor.reason;
|
|
5502
5554
|
result.detail = ancestor.detail ? `${ancestor.detail} \u2014 inherited from the skipped directory \`${ancestor.path}\`` : `inside the skipped directory \`${ancestor.path}\``;
|
|
@@ -5514,15 +5566,15 @@ function explainPath(config, target) {
|
|
|
5514
5566
|
result.detail = "binary content; generate skips it and records `binary` in manifest.skipped[]";
|
|
5515
5567
|
return result;
|
|
5516
5568
|
}
|
|
5517
|
-
result.layer = classifyLayer(
|
|
5518
|
-
result.kind = classifyKind(
|
|
5519
|
-
result.language = detectLanguage(
|
|
5520
|
-
const layerRule = findRuleMatch(config.layers,
|
|
5569
|
+
result.layer = classifyLayer(relative7, config.layers);
|
|
5570
|
+
result.kind = classifyKind(relative7, config.kinds);
|
|
5571
|
+
result.language = detectLanguage(relative7);
|
|
5572
|
+
const layerRule = findRuleMatch(config.layers, relative7);
|
|
5521
5573
|
if (layerRule) result.layerMatch = describeMatcher(layerRule.match);
|
|
5522
|
-
const kindRule = findRuleMatch(config.kinds,
|
|
5574
|
+
const kindRule = findRuleMatch(config.kinds, relative7);
|
|
5523
5575
|
if (kindRule) result.kindMatch = describeMatcher(kindRule.match);
|
|
5524
|
-
if (
|
|
5525
|
-
const buffer =
|
|
5576
|
+
if (fs11.existsSync(absolute) && fs11.statSync(absolute).isFile()) {
|
|
5577
|
+
const buffer = fs11.readFileSync(absolute);
|
|
5526
5578
|
const decoded = decodeText(buffer);
|
|
5527
5579
|
if (decoded.binary) {
|
|
5528
5580
|
result.included = false;
|
|
@@ -5534,12 +5586,12 @@ function explainPath(config, target) {
|
|
|
5534
5586
|
result.bytes = buffer.length;
|
|
5535
5587
|
result.lines = countLines(text2);
|
|
5536
5588
|
if (decoded.encoding !== "utf-8") result.encoding = decoded.encoding;
|
|
5537
|
-
const { findings } = scanAndRedactSecrets(text2, config.secretRules,
|
|
5589
|
+
const { findings } = scanAndRedactSecrets(text2, config.secretRules, relative7);
|
|
5538
5590
|
result.secretCount = findings.length;
|
|
5539
5591
|
result.secrets = findings.slice(0, 20).map((finding) => ({ rules: finding.rules, line: finding.line }));
|
|
5540
5592
|
}
|
|
5541
5593
|
const manifest = readManifest2(config);
|
|
5542
|
-
const entry = Array.isArray(manifest?.files) ? manifest.files.find((file) => file.relativePath ===
|
|
5594
|
+
const entry = Array.isArray(manifest?.files) ? manifest.files.find((file) => file.relativePath === relative7) : void 0;
|
|
5543
5595
|
result.part = entry?.part ?? null;
|
|
5544
5596
|
result.inclusionMode = entry?.inclusionMode;
|
|
5545
5597
|
return result;
|
|
@@ -5770,14 +5822,14 @@ function compareManifests(before, after) {
|
|
|
5770
5822
|
function loadManifest(target) {
|
|
5771
5823
|
let isDirectory;
|
|
5772
5824
|
try {
|
|
5773
|
-
isDirectory =
|
|
5825
|
+
isDirectory = fs11.statSync(target).isDirectory();
|
|
5774
5826
|
} catch {
|
|
5775
5827
|
throw new Error(`No snapshot found at ${target}: the path does not exist.`);
|
|
5776
5828
|
}
|
|
5777
|
-
const manifestPath = isDirectory ?
|
|
5829
|
+
const manifestPath = isDirectory ? path12.join(target, "manifest.json") : target;
|
|
5778
5830
|
let raw;
|
|
5779
5831
|
try {
|
|
5780
|
-
raw =
|
|
5832
|
+
raw = fs11.readFileSync(manifestPath, "utf8");
|
|
5781
5833
|
} catch {
|
|
5782
5834
|
throw new Error(`Could not read a manifest at ${manifestPath}.`);
|
|
5783
5835
|
}
|
|
@@ -5834,15 +5886,15 @@ function renderDiffText(diff) {
|
|
|
5834
5886
|
function appendGitignoreEntries(cwd, entries) {
|
|
5835
5887
|
const wanted = [...new Set(entries)].filter((entry) => entry.trim().length > 0);
|
|
5836
5888
|
if (wanted.length === 0) return { added: [], note: null };
|
|
5837
|
-
const ignorePath =
|
|
5889
|
+
const ignorePath = path12.join(cwd, ".gitignore");
|
|
5838
5890
|
const manual = `Add ${wanted.map((entry) => `"${entry}"`).join(" and ")} to your .gitignore yourself.`;
|
|
5839
5891
|
let existing = "";
|
|
5840
5892
|
try {
|
|
5841
|
-
if (
|
|
5842
|
-
if (
|
|
5893
|
+
if (fs11.existsSync(ignorePath)) {
|
|
5894
|
+
if (fs11.statSync(ignorePath).isDirectory()) {
|
|
5843
5895
|
return { added: [], note: `${ignorePath} is a directory, so it was not updated. ${manual}` };
|
|
5844
5896
|
}
|
|
5845
|
-
existing =
|
|
5897
|
+
existing = fs11.readFileSync(ignorePath, "utf8");
|
|
5846
5898
|
}
|
|
5847
5899
|
} catch (err) {
|
|
5848
5900
|
return {
|
|
@@ -5855,7 +5907,7 @@ function appendGitignoreEntries(cwd, entries) {
|
|
|
5855
5907
|
if (added.length === 0) return { added: [], note: null };
|
|
5856
5908
|
const prefix = existing.length === 0 || existing.endsWith("\n") ? "" : "\n";
|
|
5857
5909
|
try {
|
|
5858
|
-
|
|
5910
|
+
fs11.writeFileSync(ignorePath, `${existing}${prefix}${added.join("\n")}
|
|
5859
5911
|
`, "utf8");
|
|
5860
5912
|
} catch (err) {
|
|
5861
5913
|
return { added: [], note: `${err.message}. ${manual}` };
|
|
@@ -5863,7 +5915,7 @@ function appendGitignoreEntries(cwd, entries) {
|
|
|
5863
5915
|
return { added, note: null };
|
|
5864
5916
|
}
|
|
5865
5917
|
function gitignorePath(cwd, filePath) {
|
|
5866
|
-
return
|
|
5918
|
+
return path12.relative(cwd, filePath).split(path12.sep).join("/");
|
|
5867
5919
|
}
|
|
5868
5920
|
|
|
5869
5921
|
// src/wizard/config-template.ts
|
|
@@ -5997,6 +6049,12 @@ var PromptCancelled = class extends Error {
|
|
|
5997
6049
|
this.name = "PromptCancelled";
|
|
5998
6050
|
}
|
|
5999
6051
|
};
|
|
6052
|
+
var PromptBack = class extends Error {
|
|
6053
|
+
constructor() {
|
|
6054
|
+
super("Prompt back");
|
|
6055
|
+
this.name = "PromptBack";
|
|
6056
|
+
}
|
|
6057
|
+
};
|
|
6000
6058
|
function defaultIO() {
|
|
6001
6059
|
return { input: process.stdin, output: process.stdout };
|
|
6002
6060
|
}
|
|
@@ -6020,35 +6078,40 @@ function choiceLine(choice, active) {
|
|
|
6020
6078
|
const hint = choice.hint ? ` ${choice.hint}` : "";
|
|
6021
6079
|
return ` ${marker(active)} ${choice.label}${hint}`;
|
|
6022
6080
|
}
|
|
6023
|
-
function
|
|
6081
|
+
function listFooter(base, allowBack) {
|
|
6082
|
+
return allowBack ? `${base} \xB7 \u2190 back` : base;
|
|
6083
|
+
}
|
|
6084
|
+
function renderSelect(message, choices, index, allowBack = false) {
|
|
6024
6085
|
const lines = [`\u25C6 ${message}`, ""];
|
|
6025
6086
|
choices.forEach((choice, position) => lines.push(choiceLine(choice, position === index)));
|
|
6026
|
-
lines.push("", "
|
|
6087
|
+
lines.push("", ` ${listFooter("\u2191\u2193 move \xB7 enter select", allowBack)}`);
|
|
6027
6088
|
return lines.join("\n");
|
|
6028
6089
|
}
|
|
6029
|
-
function renderMultiSelect(message, choices, index, selected) {
|
|
6090
|
+
function renderMultiSelect(message, choices, index, selected, allowBack = false) {
|
|
6030
6091
|
const lines = [`\u25C6 ${message}`, ""];
|
|
6031
6092
|
choices.forEach((choice, position) => {
|
|
6032
6093
|
const box = selected.has(position) ? "\u25FC" : "\u25FB";
|
|
6033
6094
|
const hint = choice.hint ? ` ${choice.hint}` : "";
|
|
6034
6095
|
lines.push(` ${marker(position === index)} ${box} ${choice.label}${hint}`);
|
|
6035
6096
|
});
|
|
6036
|
-
lines.push("", "
|
|
6097
|
+
lines.push("", ` ${listFooter("\u2191\u2193 move \xB7 space toggle \xB7 enter confirm", allowBack)}`);
|
|
6037
6098
|
return lines.join("\n");
|
|
6038
6099
|
}
|
|
6039
|
-
function renderConfirm(message, value) {
|
|
6100
|
+
function renderConfirm(message, value, allowBack = false) {
|
|
6101
|
+
const footer = allowBack ? "\u2190\u2192 or y/n \xB7 enter confirm \xB7 b back" : "\u2190\u2192 or y/n \xB7 enter confirm";
|
|
6040
6102
|
return [
|
|
6041
6103
|
`\u25C6 ${message}`,
|
|
6042
6104
|
"",
|
|
6043
6105
|
` ${marker(value)} Yes`,
|
|
6044
6106
|
` ${marker(!value)} No`,
|
|
6045
6107
|
"",
|
|
6046
|
-
|
|
6108
|
+
` ${footer}`
|
|
6047
6109
|
].join("\n");
|
|
6048
6110
|
}
|
|
6049
|
-
function renderText(message, value, error) {
|
|
6111
|
+
function renderText(message, value, error, allowBack = false) {
|
|
6050
6112
|
const lines = [`\u25C6 ${message}`, "", ` ${value || "\u258F"}`];
|
|
6051
6113
|
if (error) lines.push("", ` \u2716 ${error}`);
|
|
6114
|
+
if (allowBack) lines.push("", " enter confirm \xB7 \u2190 back");
|
|
6052
6115
|
return lines.join("\n");
|
|
6053
6116
|
}
|
|
6054
6117
|
function keyReader(input) {
|
|
@@ -6097,14 +6160,18 @@ var Frame = class {
|
|
|
6097
6160
|
function isCancel(key) {
|
|
6098
6161
|
return key.ctrl === true && key.name === "c" || key.ctrl === true && key.name === "d";
|
|
6099
6162
|
}
|
|
6163
|
+
function isListBack(key, allowBack) {
|
|
6164
|
+
return allowBack && (key.name === "left" || key.name === "b");
|
|
6165
|
+
}
|
|
6100
6166
|
async function select(io, message, choices, options = {}) {
|
|
6101
6167
|
if (choices.length === 0) throw new Error("select() requires at least one choice.");
|
|
6168
|
+
const allowBack = options.allowBack === true;
|
|
6102
6169
|
const fallbackIndex = Math.max(
|
|
6103
6170
|
0,
|
|
6104
6171
|
choices.findIndex((choice) => choice.value === options.defaultValue)
|
|
6105
6172
|
);
|
|
6106
6173
|
if (!isInteractive(io)) {
|
|
6107
|
-
io.output.write(`${renderSelect(message, choices, fallbackIndex)}
|
|
6174
|
+
io.output.write(`${renderSelect(message, choices, fallbackIndex, allowBack)}
|
|
6108
6175
|
`);
|
|
6109
6176
|
return choices[fallbackIndex].value;
|
|
6110
6177
|
}
|
|
@@ -6113,9 +6180,10 @@ async function select(io, message, choices, options = {}) {
|
|
|
6113
6180
|
let index = fallbackIndex;
|
|
6114
6181
|
try {
|
|
6115
6182
|
for (; ; ) {
|
|
6116
|
-
frame.draw(renderSelect(message, choices, index));
|
|
6183
|
+
frame.draw(renderSelect(message, choices, index, allowBack));
|
|
6117
6184
|
const key = await reader.next();
|
|
6118
6185
|
if (isCancel(key)) throw new PromptCancelled();
|
|
6186
|
+
if (isListBack(key, allowBack)) throw new PromptBack();
|
|
6119
6187
|
if (key.name === "up") index = moveSelection(index, -1, choices.length);
|
|
6120
6188
|
else if (key.name === "down") index = moveSelection(index, 1, choices.length);
|
|
6121
6189
|
else if (key.name === "return" || key.name === "enter") {
|
|
@@ -6124,18 +6192,22 @@ async function select(io, message, choices, options = {}) {
|
|
|
6124
6192
|
} else if (key.name === "j") index = moveSelection(index, 1, choices.length);
|
|
6125
6193
|
else if (key.name === "k") index = moveSelection(index, -1, choices.length);
|
|
6126
6194
|
}
|
|
6195
|
+
} catch (error) {
|
|
6196
|
+
frame.erase();
|
|
6197
|
+
throw error;
|
|
6127
6198
|
} finally {
|
|
6128
6199
|
reader.close();
|
|
6129
6200
|
}
|
|
6130
6201
|
}
|
|
6131
6202
|
async function multiSelect(io, message, choices, options = {}) {
|
|
6203
|
+
const allowBack = options.allowBack === true;
|
|
6132
6204
|
const selected = /* @__PURE__ */ new Set();
|
|
6133
6205
|
choices.forEach((choice, position) => {
|
|
6134
6206
|
if (options.defaults?.includes(choice.value)) selected.add(position);
|
|
6135
6207
|
});
|
|
6136
6208
|
const result = () => choices.filter((_, position) => selected.has(position)).map((choice) => choice.value);
|
|
6137
6209
|
if (!isInteractive(io)) {
|
|
6138
|
-
io.output.write(`${renderMultiSelect(message, choices, 0, selected)}
|
|
6210
|
+
io.output.write(`${renderMultiSelect(message, choices, 0, selected, allowBack)}
|
|
6139
6211
|
`);
|
|
6140
6212
|
return result();
|
|
6141
6213
|
}
|
|
@@ -6144,9 +6216,10 @@ async function multiSelect(io, message, choices, options = {}) {
|
|
|
6144
6216
|
let index = 0;
|
|
6145
6217
|
try {
|
|
6146
6218
|
for (; ; ) {
|
|
6147
|
-
frame.draw(renderMultiSelect(message, choices, index, selected));
|
|
6219
|
+
frame.draw(renderMultiSelect(message, choices, index, selected, allowBack));
|
|
6148
6220
|
const key = await reader.next();
|
|
6149
6221
|
if (isCancel(key)) throw new PromptCancelled();
|
|
6222
|
+
if (isListBack(key, allowBack)) throw new PromptBack();
|
|
6150
6223
|
if (key.name === "up") index = moveSelection(index, -1, choices.length);
|
|
6151
6224
|
else if (key.name === "down") index = moveSelection(index, 1, choices.length);
|
|
6152
6225
|
else if (key.name === "space") {
|
|
@@ -6161,14 +6234,18 @@ async function multiSelect(io, message, choices, options = {}) {
|
|
|
6161
6234
|
return result();
|
|
6162
6235
|
}
|
|
6163
6236
|
}
|
|
6237
|
+
} catch (error) {
|
|
6238
|
+
frame.erase();
|
|
6239
|
+
throw error;
|
|
6164
6240
|
} finally {
|
|
6165
6241
|
reader.close();
|
|
6166
6242
|
}
|
|
6167
6243
|
}
|
|
6168
6244
|
async function confirm(io, message, options = {}) {
|
|
6245
|
+
const allowBack = options.allowBack === true;
|
|
6169
6246
|
let value = options.defaultValue ?? true;
|
|
6170
6247
|
if (!isInteractive(io)) {
|
|
6171
|
-
io.output.write(`${renderConfirm(message, value)}
|
|
6248
|
+
io.output.write(`${renderConfirm(message, value, allowBack)}
|
|
6172
6249
|
`);
|
|
6173
6250
|
return value;
|
|
6174
6251
|
}
|
|
@@ -6176,9 +6253,10 @@ async function confirm(io, message, options = {}) {
|
|
|
6176
6253
|
const reader = keyReader(io.input);
|
|
6177
6254
|
try {
|
|
6178
6255
|
for (; ; ) {
|
|
6179
|
-
frame.draw(renderConfirm(message, value));
|
|
6256
|
+
frame.draw(renderConfirm(message, value, allowBack));
|
|
6180
6257
|
const key = await reader.next();
|
|
6181
6258
|
if (isCancel(key)) throw new PromptCancelled();
|
|
6259
|
+
if (allowBack && key.name === "b") throw new PromptBack();
|
|
6182
6260
|
if (key.name === "up" || key.name === "left") value = true;
|
|
6183
6261
|
else if (key.name === "down" || key.name === "right") value = false;
|
|
6184
6262
|
else if (key.name === "y") value = true;
|
|
@@ -6188,14 +6266,18 @@ async function confirm(io, message, options = {}) {
|
|
|
6188
6266
|
return value;
|
|
6189
6267
|
}
|
|
6190
6268
|
}
|
|
6269
|
+
} catch (error) {
|
|
6270
|
+
frame.erase();
|
|
6271
|
+
throw error;
|
|
6191
6272
|
} finally {
|
|
6192
6273
|
reader.close();
|
|
6193
6274
|
}
|
|
6194
6275
|
}
|
|
6195
6276
|
async function text(io, message, options = {}) {
|
|
6277
|
+
const allowBack = options.allowBack === true;
|
|
6196
6278
|
const fallback = options.defaultValue ?? "";
|
|
6197
6279
|
if (!isInteractive(io)) {
|
|
6198
|
-
io.output.write(`${renderText(message, fallback)}
|
|
6280
|
+
io.output.write(`${renderText(message, fallback, void 0, allowBack)}
|
|
6199
6281
|
`);
|
|
6200
6282
|
return fallback;
|
|
6201
6283
|
}
|
|
@@ -6205,9 +6287,10 @@ async function text(io, message, options = {}) {
|
|
|
6205
6287
|
let error;
|
|
6206
6288
|
try {
|
|
6207
6289
|
for (; ; ) {
|
|
6208
|
-
frame.draw(renderText(message, value, error));
|
|
6290
|
+
frame.draw(renderText(message, value, error, allowBack));
|
|
6209
6291
|
const key = await reader.next();
|
|
6210
6292
|
if (isCancel(key)) throw new PromptCancelled();
|
|
6293
|
+
if (allowBack && key.name === "left") throw new PromptBack();
|
|
6211
6294
|
error = void 0;
|
|
6212
6295
|
if (key.name === "return" || key.name === "enter") {
|
|
6213
6296
|
const problem = options.validate ? options.validate(value) : null;
|
|
@@ -6228,6 +6311,9 @@ async function text(io, message, options = {}) {
|
|
|
6228
6311
|
}
|
|
6229
6312
|
if (_isPrintable(key)) value += _keyText(key);
|
|
6230
6313
|
}
|
|
6314
|
+
} catch (error_) {
|
|
6315
|
+
frame.erase();
|
|
6316
|
+
throw error_;
|
|
6231
6317
|
} finally {
|
|
6232
6318
|
reader.close();
|
|
6233
6319
|
}
|
|
@@ -6251,10 +6337,10 @@ var CONFIG_CANDIDATES = [
|
|
|
6251
6337
|
];
|
|
6252
6338
|
function chooseConfigFileName(cwd) {
|
|
6253
6339
|
if (!supportsTypeScriptImports()) return "snapshot.config.mjs";
|
|
6254
|
-
const pkgPath =
|
|
6255
|
-
if (!
|
|
6340
|
+
const pkgPath = path12.join(cwd, "package.json");
|
|
6341
|
+
if (!fs11.existsSync(pkgPath)) return "snapshot.config.mjs";
|
|
6256
6342
|
try {
|
|
6257
|
-
const parsed = JSON.parse(
|
|
6343
|
+
const parsed = JSON.parse(fs11.readFileSync(pkgPath, "utf8"));
|
|
6258
6344
|
return parsed.type === "module" ? "snapshot.config.ts" : "snapshot.config.mjs";
|
|
6259
6345
|
} catch {
|
|
6260
6346
|
return "snapshot.config.mjs";
|
|
@@ -6268,7 +6354,7 @@ var SIZE_CHOICES = [
|
|
|
6268
6354
|
];
|
|
6269
6355
|
function packageJsonHasConfig(cwd) {
|
|
6270
6356
|
try {
|
|
6271
|
-
const parsed = JSON.parse(
|
|
6357
|
+
const parsed = JSON.parse(fs11.readFileSync(path12.join(cwd, "package.json"), "utf8"));
|
|
6272
6358
|
return typeof parsed.snapshot === "object" && parsed.snapshot !== null && !Array.isArray(parsed.snapshot);
|
|
6273
6359
|
} catch {
|
|
6274
6360
|
return false;
|
|
@@ -6289,7 +6375,7 @@ function artifactAnswers(options) {
|
|
|
6289
6375
|
}
|
|
6290
6376
|
function topLevelDirs(cwd) {
|
|
6291
6377
|
try {
|
|
6292
|
-
return
|
|
6378
|
+
return fs11.readdirSync(cwd, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules").map((entry) => entry.name).sort();
|
|
6293
6379
|
} catch {
|
|
6294
6380
|
return [];
|
|
6295
6381
|
}
|
|
@@ -6302,7 +6388,7 @@ function describeJsonValue(value) {
|
|
|
6302
6388
|
}
|
|
6303
6389
|
function writeFileOrExplain(target, content) {
|
|
6304
6390
|
try {
|
|
6305
|
-
|
|
6391
|
+
fs11.writeFileSync(target, content, "utf8");
|
|
6306
6392
|
} catch (err) {
|
|
6307
6393
|
const code = err.code;
|
|
6308
6394
|
const reason = code === "EACCES" || code === "EPERM" || code === "EROFS" ? `permission denied (${code})` : code === "EISDIR" ? "it is a directory" : `${err.message}`;
|
|
@@ -6313,7 +6399,7 @@ function canWriteModuleConfig(name, cwd) {
|
|
|
6313
6399
|
if (name.endsWith(".mjs") || name.endsWith(".ts")) return true;
|
|
6314
6400
|
if (!name.endsWith(".js")) return false;
|
|
6315
6401
|
try {
|
|
6316
|
-
const parsed = JSON.parse(
|
|
6402
|
+
const parsed = JSON.parse(fs11.readFileSync(path12.join(cwd, "package.json"), "utf8"));
|
|
6317
6403
|
return parsed.type === "module";
|
|
6318
6404
|
} catch {
|
|
6319
6405
|
return false;
|
|
@@ -6348,19 +6434,19 @@ function serializePackageJson(raw, value) {
|
|
|
6348
6434
|
return `${text2}${raw.endsWith("\n") ? crlf ? "\r\n" : "\n" : ""}`;
|
|
6349
6435
|
}
|
|
6350
6436
|
function updatePackageJson(cwd, notes) {
|
|
6351
|
-
const packagePath =
|
|
6437
|
+
const packagePath = path12.join(cwd, "package.json");
|
|
6352
6438
|
const unchanged = {
|
|
6353
6439
|
scripts: [],
|
|
6354
6440
|
dependency: { name: GENERATOR_NAME, range: null, added: false }
|
|
6355
6441
|
};
|
|
6356
|
-
if (!
|
|
6442
|
+
if (!fs11.existsSync(packagePath)) {
|
|
6357
6443
|
notes.push("No package.json found, so npm scripts and the devDependency were not added.");
|
|
6358
6444
|
return unchanged;
|
|
6359
6445
|
}
|
|
6360
6446
|
let raw;
|
|
6361
6447
|
let parsed;
|
|
6362
6448
|
try {
|
|
6363
|
-
raw =
|
|
6449
|
+
raw = fs11.readFileSync(packagePath, "utf8");
|
|
6364
6450
|
parsed = JSON.parse(raw);
|
|
6365
6451
|
} catch {
|
|
6366
6452
|
notes.push("Could not parse package.json; npm scripts and the devDependency were not added.");
|
|
@@ -6423,16 +6509,18 @@ function updatePackageJson(cwd, notes) {
|
|
|
6423
6509
|
return { scripts: added, dependency };
|
|
6424
6510
|
}
|
|
6425
6511
|
var GITIGNORE_ENTRIES = ["snapshot/", "snapshot.tmp/"];
|
|
6426
|
-
function resolvePresetChoice(
|
|
6512
|
+
function resolvePresetChoice(detectedName, presetName) {
|
|
6427
6513
|
return {
|
|
6428
6514
|
value: presetName,
|
|
6429
6515
|
label: presetName,
|
|
6430
|
-
hint: presetName ===
|
|
6516
|
+
hint: presetName === detectedName ? "detected" : void 0
|
|
6431
6517
|
};
|
|
6432
6518
|
}
|
|
6433
6519
|
async function runInit(options) {
|
|
6434
6520
|
const io = options.io ?? defaultIO();
|
|
6435
6521
|
const detected = detectPreset(options.cwd, options.preset);
|
|
6522
|
+
let reason = detected.reason;
|
|
6523
|
+
let fallback = detected.fallback;
|
|
6436
6524
|
const notes = [];
|
|
6437
6525
|
const answers = {
|
|
6438
6526
|
preset: detected.presetName,
|
|
@@ -6454,73 +6542,37 @@ async function runInit(options) {
|
|
|
6454
6542
|
if (interactive) {
|
|
6455
6543
|
io.output.write("\n codebase-snapshot\n");
|
|
6456
6544
|
io.output.write(" Turn this repository into an LLM-ready, redacted, byte-bounded snapshot.\n\n");
|
|
6457
|
-
|
|
6545
|
+
if (fallback) {
|
|
6546
|
+
io.output.write(" \u26A0 Could not tell what kind of project this is.\n");
|
|
6547
|
+
io.output.write(" No package.json, go.mod, Cargo.toml, pyproject.toml, Gemfile,\n");
|
|
6548
|
+
io.output.write(" composer.json, pom.xml, *.csproj, next.config.*, vite.config.*, *.tf\n");
|
|
6549
|
+
io.output.write(" or workspace marker was found here.\n\n");
|
|
6550
|
+
} else {
|
|
6551
|
+
io.output.write(` Detected ${detected.presetName}
|
|
6458
6552
|
`);
|
|
6459
|
-
|
|
6553
|
+
io.output.write(` Because ${reason}
|
|
6460
6554
|
|
|
6461
6555
|
`);
|
|
6462
|
-
const useDetected = await confirm(io, `Set up for ${detected.presetName}?`, {
|
|
6463
|
-
defaultValue: true
|
|
6464
|
-
});
|
|
6465
|
-
if (!useDetected) {
|
|
6466
|
-
answers.preset = await select(
|
|
6467
|
-
io,
|
|
6468
|
-
"What kind of project is this?",
|
|
6469
|
-
listPresetNames().map((name) => resolvePresetChoice(answers, name))
|
|
6470
|
-
);
|
|
6471
|
-
const chosen = detectPreset(options.cwd, answers.preset);
|
|
6472
|
-
answers.preset = chosen.presetName;
|
|
6473
|
-
}
|
|
6474
|
-
const scope = await select(
|
|
6475
|
-
io,
|
|
6476
|
-
"What should go into the snapshot?",
|
|
6477
|
-
[
|
|
6478
|
-
{ value: "all", label: "Everything the scanner finds", hint: "recommended \xB7 respects .gitignore" },
|
|
6479
|
-
{ value: "folders", label: "Only specific folders\u2026" },
|
|
6480
|
-
{ value: "no-tests", label: "Everything, but skip tests" },
|
|
6481
|
-
{ value: "custom", label: "Let me write glob patterns" }
|
|
6482
|
-
],
|
|
6483
|
-
{ defaultValue: "all" }
|
|
6484
|
-
);
|
|
6485
|
-
if (scope === "folders") {
|
|
6486
|
-
const dirs = topLevelDirs(options.cwd).slice(0, 40);
|
|
6487
|
-
if (dirs.length === 0) {
|
|
6488
|
-
notes.push("No top-level directories found; the snapshot will include everything.");
|
|
6489
|
-
} else {
|
|
6490
|
-
const chosen = await multiSelect(
|
|
6491
|
-
io,
|
|
6492
|
-
"Select folders",
|
|
6493
|
-
dirs.map((name) => ({ value: name, label: `${name}/` })),
|
|
6494
|
-
{ defaults: dirs }
|
|
6495
|
-
);
|
|
6496
|
-
answers.include = chosen.map((name) => `${name}/**`);
|
|
6497
|
-
}
|
|
6498
|
-
} else if (scope === "no-tests") {
|
|
6499
|
-
answers.excludePatterns = [
|
|
6500
|
-
...answers.excludePatterns,
|
|
6501
|
-
"**/*.test.*",
|
|
6502
|
-
"**/*.spec.*",
|
|
6503
|
-
"tests",
|
|
6504
|
-
"test"
|
|
6505
|
-
];
|
|
6506
|
-
} else if (scope === "custom") {
|
|
6507
|
-
const raw = await text(io, "Glob patterns to include (comma separated)", {
|
|
6508
|
-
defaultValue: ""
|
|
6509
|
-
});
|
|
6510
|
-
answers.include = raw.split(",").map((value) => value.trim()).filter(Boolean);
|
|
6511
|
-
}
|
|
6512
|
-
const sizeChoice = await select(io, "How large should each part be?", SIZE_CHOICES, {
|
|
6513
|
-
defaultValue: "120k"
|
|
6514
|
-
});
|
|
6515
|
-
if (sizeChoice === "custom") {
|
|
6516
|
-
const raw = await text(io, "Part size (e.g. 150k, 1.5mb, 900000)", {
|
|
6517
|
-
defaultValue: String(answers.maxPartBytes),
|
|
6518
|
-
validate: (value) => parseByteSize(value) === void 0 ? "Not a valid size." : null
|
|
6519
|
-
});
|
|
6520
|
-
answers.maxPartBytes = parseByteSize(raw) ?? answers.maxPartBytes;
|
|
6521
|
-
} else {
|
|
6522
|
-
answers.maxPartBytes = sizeChoice;
|
|
6523
6556
|
}
|
|
6557
|
+
io.output.write(` Directory ${options.cwd}
|
|
6558
|
+
|
|
6559
|
+
`);
|
|
6560
|
+
const paceChoices = fallback ? [
|
|
6561
|
+
{ value: "choose", label: "Pick the closest preset", hint: "then generate with the defaults" },
|
|
6562
|
+
{ value: "recommended", label: "Use the defaults", hint: "generic \xB7 everything the scanner finds" },
|
|
6563
|
+
{ value: "walk", label: "Go through each setting", hint: "every question starts on its default" }
|
|
6564
|
+
] : [
|
|
6565
|
+
{
|
|
6566
|
+
value: "recommended",
|
|
6567
|
+
label: "Use the recommended setup",
|
|
6568
|
+
hint: `${detected.presetName} \xB7 config \xB7 npm scripts \xB7 install \xB7 generate`
|
|
6569
|
+
},
|
|
6570
|
+
{ value: "walk", label: "Go through each setting", hint: "every question starts on its default" },
|
|
6571
|
+
{ value: "choose", label: "Pick a different preset", hint: "everything else stays at the defaults" }
|
|
6572
|
+
];
|
|
6573
|
+
const baseInclude = [...answers.include];
|
|
6574
|
+
const baseExclude = [...answers.excludePatterns];
|
|
6575
|
+
let lastScope = "all";
|
|
6524
6576
|
const artifactChoices = [
|
|
6525
6577
|
{ value: "readme", label: "README.md", hint: "orientation page" },
|
|
6526
6578
|
{ value: "manifest", label: "manifest.json", hint: "required by validate and report" },
|
|
@@ -6530,41 +6582,163 @@ async function runInit(options) {
|
|
|
6530
6582
|
{ value: "audit", label: "reports/audit.md", hint: "security and health findings" },
|
|
6531
6583
|
{ value: "monolith", label: "full-codebase.json + .md", hint: "roughly doubles output" }
|
|
6532
6584
|
];
|
|
6533
|
-
const
|
|
6534
|
-
|
|
6535
|
-
|
|
6536
|
-
|
|
6537
|
-
|
|
6538
|
-
|
|
6539
|
-
|
|
6540
|
-
|
|
6541
|
-
|
|
6542
|
-
|
|
6543
|
-
|
|
6544
|
-
|
|
6545
|
-
|
|
6546
|
-
|
|
6547
|
-
|
|
6548
|
-
|
|
6585
|
+
const scopeStep = async () => {
|
|
6586
|
+
for (; ; ) {
|
|
6587
|
+
const scope = await select(
|
|
6588
|
+
io,
|
|
6589
|
+
"What should go into the snapshot?",
|
|
6590
|
+
[
|
|
6591
|
+
{ value: "all", label: "Everything the scanner finds", hint: "recommended \xB7 respects .gitignore" },
|
|
6592
|
+
{ value: "folders", label: "Only specific folders\u2026" },
|
|
6593
|
+
{ value: "no-tests", label: "Everything, but skip tests" },
|
|
6594
|
+
{ value: "custom", label: "Let me write glob patterns" }
|
|
6595
|
+
],
|
|
6596
|
+
{ defaultValue: lastScope, allowBack: true }
|
|
6597
|
+
);
|
|
6598
|
+
lastScope = scope;
|
|
6599
|
+
answers.include = [...baseInclude];
|
|
6600
|
+
answers.excludePatterns = [...baseExclude];
|
|
6601
|
+
try {
|
|
6602
|
+
if (scope === "folders") {
|
|
6603
|
+
const dirs = topLevelDirs(options.cwd).slice(0, 40);
|
|
6604
|
+
if (dirs.length === 0) {
|
|
6605
|
+
notes.push("No top-level directories found; the snapshot will include everything.");
|
|
6606
|
+
} else {
|
|
6607
|
+
const chosen = await multiSelect(
|
|
6608
|
+
io,
|
|
6609
|
+
"Select folders",
|
|
6610
|
+
dirs.map((name) => ({ value: name, label: `${name}/` })),
|
|
6611
|
+
{ defaults: dirs, allowBack: true }
|
|
6612
|
+
);
|
|
6613
|
+
answers.include = chosen.map((name) => `${name}/**`);
|
|
6614
|
+
}
|
|
6615
|
+
} else if (scope === "no-tests") {
|
|
6616
|
+
answers.excludePatterns = [...baseExclude, "**/*.test.*", "**/*.spec.*", "tests", "test"];
|
|
6617
|
+
} else if (scope === "custom") {
|
|
6618
|
+
const raw = await text(io, "Glob patterns to include (comma separated)", {
|
|
6619
|
+
defaultValue: baseInclude.join(", "),
|
|
6620
|
+
allowBack: true
|
|
6621
|
+
});
|
|
6622
|
+
answers.include = raw.split(",").map((value) => value.trim()).filter(Boolean);
|
|
6623
|
+
}
|
|
6624
|
+
return;
|
|
6625
|
+
} catch (error) {
|
|
6626
|
+
if (error instanceof PromptBack) continue;
|
|
6627
|
+
throw error;
|
|
6628
|
+
}
|
|
6629
|
+
}
|
|
6630
|
+
};
|
|
6631
|
+
const sizeStep = async () => {
|
|
6632
|
+
for (; ; ) {
|
|
6633
|
+
const currentBudget = String(answers.maxPartBytes);
|
|
6634
|
+
const sizeChoice = await select(io, "How large should each part be?", SIZE_CHOICES, {
|
|
6635
|
+
defaultValue: SIZE_CHOICES.some((choice) => choice.value === currentBudget) ? currentBudget : "custom",
|
|
6636
|
+
allowBack: true
|
|
6637
|
+
});
|
|
6638
|
+
if (sizeChoice !== "custom") {
|
|
6639
|
+
answers.maxPartBytes = sizeChoice;
|
|
6640
|
+
return;
|
|
6641
|
+
}
|
|
6642
|
+
try {
|
|
6643
|
+
const raw = await text(io, "Part size (e.g. 150k, 1.5mb, 900000)", {
|
|
6644
|
+
defaultValue: currentBudget,
|
|
6645
|
+
validate: (value) => parseByteSize(value) === void 0 ? "Not a valid size." : null,
|
|
6646
|
+
allowBack: true
|
|
6647
|
+
});
|
|
6648
|
+
answers.maxPartBytes = parseByteSize(raw) ?? answers.maxPartBytes;
|
|
6649
|
+
return;
|
|
6650
|
+
} catch (error) {
|
|
6651
|
+
if (error instanceof PromptBack) continue;
|
|
6652
|
+
throw error;
|
|
6653
|
+
}
|
|
6654
|
+
}
|
|
6655
|
+
};
|
|
6656
|
+
const artifactsStep = async () => {
|
|
6657
|
+
const enabled = ARTIFACT_NAMES.filter((name) => answers.artifacts[name]);
|
|
6658
|
+
const selectedArtifacts = await multiSelect(
|
|
6659
|
+
io,
|
|
6660
|
+
"Which artifacts should be generated?",
|
|
6661
|
+
artifactChoices,
|
|
6662
|
+
{ defaults: enabled, allowBack: true }
|
|
6663
|
+
);
|
|
6664
|
+
for (const name of ARTIFACT_NAMES) answers.artifacts[name] = selectedArtifacts.includes(name);
|
|
6665
|
+
};
|
|
6666
|
+
const credentialsStep = async () => {
|
|
6667
|
+
answers.includeSensitiveFiles = await confirm(
|
|
6668
|
+
io,
|
|
6669
|
+
"Include credential files (.env, *.pem, *.npmrc, *.ssh/)?",
|
|
6670
|
+
{ defaultValue: answers.includeSensitiveFiles, allowBack: true }
|
|
6671
|
+
);
|
|
6672
|
+
};
|
|
6673
|
+
const reviewStep = async () => {
|
|
6674
|
+
const enabledCount = ARTIFACT_NAMES.filter((name) => answers.artifacts[name]).length;
|
|
6675
|
+
io.output.write("\n Review\n\n");
|
|
6676
|
+
io.output.write(` project ${answers.preset}
|
|
6549
6677
|
`);
|
|
6550
|
-
|
|
6678
|
+
io.output.write(` include ${answers.include.length ? answers.include.join(", ") : "everything"}
|
|
6551
6679
|
`);
|
|
6552
|
-
|
|
6680
|
+
io.output.write(` part size ${String(answers.maxPartBytes)}
|
|
6553
6681
|
`);
|
|
6554
|
-
|
|
6682
|
+
io.output.write(` artifacts ${enabledCount} of ${ARTIFACT_NAMES.length}
|
|
6555
6683
|
`);
|
|
6556
|
-
|
|
6684
|
+
io.output.write(` credentials ${answers.includeSensitiveFiles ? "included" : "skipped"}
|
|
6557
6685
|
|
|
6558
6686
|
`);
|
|
6559
|
-
|
|
6560
|
-
|
|
6561
|
-
|
|
6562
|
-
|
|
6687
|
+
io.output.write(" Will write snapshot.config with your answers, plus every other setting\n");
|
|
6688
|
+
io.output.write(" at its default \u2014 so nothing is hidden behind the docs.\n\n");
|
|
6689
|
+
const proceed = await confirm(io, "Write this and continue?", {
|
|
6690
|
+
defaultValue: true,
|
|
6691
|
+
allowBack: true
|
|
6692
|
+
});
|
|
6693
|
+
if (!proceed) throw new PromptBack();
|
|
6694
|
+
};
|
|
6695
|
+
const walk = async () => {
|
|
6696
|
+
const steps = [scopeStep, sizeStep, artifactsStep, credentialsStep, reviewStep];
|
|
6697
|
+
let index = 0;
|
|
6698
|
+
while (index < steps.length) {
|
|
6699
|
+
try {
|
|
6700
|
+
await steps[index]();
|
|
6701
|
+
index += 1;
|
|
6702
|
+
} catch (error) {
|
|
6703
|
+
if (!(error instanceof PromptBack)) throw error;
|
|
6704
|
+
if (index === 0) return false;
|
|
6705
|
+
index -= 1;
|
|
6706
|
+
io.output.write("\n");
|
|
6707
|
+
}
|
|
6708
|
+
}
|
|
6709
|
+
return true;
|
|
6710
|
+
};
|
|
6711
|
+
for (; ; ) {
|
|
6712
|
+
const pace = await select(io, "How should I set this up?", paceChoices, {
|
|
6713
|
+
defaultValue: fallback ? "choose" : "recommended"
|
|
6714
|
+
});
|
|
6715
|
+
if (pace === "choose") {
|
|
6716
|
+
try {
|
|
6717
|
+
answers.preset = await select(
|
|
6718
|
+
io,
|
|
6719
|
+
"What kind of project is this?",
|
|
6720
|
+
listPresetNames().map(
|
|
6721
|
+
(name) => resolvePresetChoice(fallback ? null : detected.presetName, name)
|
|
6722
|
+
),
|
|
6723
|
+
{ defaultValue: answers.preset, allowBack: true }
|
|
6724
|
+
);
|
|
6725
|
+
} catch (error) {
|
|
6726
|
+
if (error instanceof PromptBack) continue;
|
|
6727
|
+
throw error;
|
|
6728
|
+
}
|
|
6729
|
+
const chosen = detectPreset(options.cwd, answers.preset);
|
|
6730
|
+
answers.preset = chosen.presetName;
|
|
6731
|
+
reason = null;
|
|
6732
|
+
fallback = false;
|
|
6733
|
+
}
|
|
6734
|
+
if (pace === "walk" && !await walk()) continue;
|
|
6735
|
+
break;
|
|
6736
|
+
}
|
|
6563
6737
|
}
|
|
6564
6738
|
const configText = renderConfig(answers);
|
|
6565
6739
|
const preferredName = chooseConfigFileName(options.cwd);
|
|
6566
6740
|
const presentConfigs = CONFIG_CANDIDATES.filter(
|
|
6567
|
-
(name) =>
|
|
6741
|
+
(name) => fs11.existsSync(path12.join(options.cwd, name))
|
|
6568
6742
|
);
|
|
6569
6743
|
const present = presentConfigs[0] ?? null;
|
|
6570
6744
|
if (presentConfigs.length > 1) {
|
|
@@ -6573,7 +6747,7 @@ async function runInit(options) {
|
|
|
6573
6747
|
);
|
|
6574
6748
|
}
|
|
6575
6749
|
const configName = present !== null && options.force ? present : preferredName;
|
|
6576
|
-
const configPath =
|
|
6750
|
+
const configPath = path12.join(options.cwd, configName);
|
|
6577
6751
|
const reused = present !== null && options.force !== true && options.reuseConfig === true;
|
|
6578
6752
|
if (reused) notes.push(`Kept the existing ${present} instead of writing a new one.`);
|
|
6579
6753
|
if (options.dryRun) {
|
|
@@ -6583,6 +6757,8 @@ async function runInit(options) {
|
|
|
6583
6757
|
written: false,
|
|
6584
6758
|
reused,
|
|
6585
6759
|
preset: answers.preset,
|
|
6760
|
+
reason,
|
|
6761
|
+
fallback,
|
|
6586
6762
|
scripts: [],
|
|
6587
6763
|
gitignoreEntries: [],
|
|
6588
6764
|
dependency: { name: GENERATOR_NAME, range: `^${GENERATOR_VERSION}`, added: false },
|
|
@@ -6604,7 +6780,7 @@ async function runInit(options) {
|
|
|
6604
6780
|
'package.json already defines a "snapshot" config. Remove that key, or re-run with --force to write a config file that takes precedence over it.'
|
|
6605
6781
|
);
|
|
6606
6782
|
}
|
|
6607
|
-
if (
|
|
6783
|
+
if (fs11.existsSync(configPath) && !options.force && !reused) {
|
|
6608
6784
|
throw new Error(`${configName} already exists. Re-run with --force to replace it.`);
|
|
6609
6785
|
}
|
|
6610
6786
|
if (!reused) writeFileOrExplain(configPath, configText);
|
|
@@ -6617,12 +6793,56 @@ async function runInit(options) {
|
|
|
6617
6793
|
written: true,
|
|
6618
6794
|
reused,
|
|
6619
6795
|
preset: answers.preset,
|
|
6796
|
+
reason,
|
|
6797
|
+
fallback,
|
|
6620
6798
|
scripts,
|
|
6621
6799
|
gitignoreEntries: ignore.added,
|
|
6622
6800
|
dependency,
|
|
6623
6801
|
notes
|
|
6624
6802
|
};
|
|
6625
6803
|
}
|
|
6804
|
+
function describeTarget(cwd) {
|
|
6805
|
+
const relative7 = path12.relative(process.cwd(), cwd);
|
|
6806
|
+
if (relative7 === "") return "this directory";
|
|
6807
|
+
if (relative7.startsWith("..") || path12.isAbsolute(relative7)) return cwd;
|
|
6808
|
+
return relative7;
|
|
6809
|
+
}
|
|
6810
|
+
function firstRunMessage(reason, cwd) {
|
|
6811
|
+
const where = describeTarget(cwd);
|
|
6812
|
+
if (reason === "empty") {
|
|
6813
|
+
return `Nothing to snapshot: ${where} has no files. Run this inside the repository you want to snapshot, or pass --cwd.`;
|
|
6814
|
+
}
|
|
6815
|
+
return `No configuration found in ${where} and this session is not interactive, so nothing was generated.
|
|
6816
|
+
Choose one:
|
|
6817
|
+
${GENERATOR_NAME} --autopilot detect the project, set it up and generate, no questions
|
|
6818
|
+
${GENERATOR_NAME} init --yes write a config from the detected defaults, then generate
|
|
6819
|
+
${GENERATOR_NAME} init write a config interactively`;
|
|
6820
|
+
}
|
|
6821
|
+
function planFirstRun(facts) {
|
|
6822
|
+
if (facts.emptyTarget) return { action: "refuse", exitCode: 1, reason: "empty" };
|
|
6823
|
+
if (facts.autopilot) return { action: "setup", interactive: false };
|
|
6824
|
+
if (facts.hasConfig || facts.hasPriorSnapshot) return { action: "generate" };
|
|
6825
|
+
if (facts.interactive) return { action: "setup", interactive: true };
|
|
6826
|
+
return { action: "refuse", exitCode: 2, reason: "undecided" };
|
|
6827
|
+
}
|
|
6828
|
+
var NOT_CONTENT = /* @__PURE__ */ new Set([".git", ".hg", ".svn", ".bzr", ".DS_Store", "Thumbs.db"]);
|
|
6829
|
+
function isEffectivelyEmpty(cwd) {
|
|
6830
|
+
try {
|
|
6831
|
+
return fs11.readdirSync(cwd).every((entry) => NOT_CONTENT.has(entry));
|
|
6832
|
+
} catch {
|
|
6833
|
+
return false;
|
|
6834
|
+
}
|
|
6835
|
+
}
|
|
6836
|
+
function hasPriorSnapshot(cwd, outDir) {
|
|
6837
|
+
let parsed;
|
|
6838
|
+
try {
|
|
6839
|
+
parsed = JSON.parse(fs11.readFileSync(path12.join(cwd, outDir, ".codebase-snapshot"), "utf8"));
|
|
6840
|
+
} catch {
|
|
6841
|
+
return false;
|
|
6842
|
+
}
|
|
6843
|
+
if (parsed === null || typeof parsed !== "object") return false;
|
|
6844
|
+
return isOurGeneratorName(parsed.generator);
|
|
6845
|
+
}
|
|
6626
6846
|
|
|
6627
6847
|
// src/index.ts
|
|
6628
6848
|
var SCHEMA_VERSION = "3.0.0";
|
|
@@ -6704,13 +6924,13 @@ function buildImportResolver(files) {
|
|
|
6704
6924
|
const relativePath = file.relativePath;
|
|
6705
6925
|
remember(byPath, relativePath, relativePath);
|
|
6706
6926
|
remember(byPathFolded, relativePath.toLowerCase(), relativePath);
|
|
6707
|
-
const extension =
|
|
6927
|
+
const extension = path12.posix.extname(relativePath);
|
|
6708
6928
|
if (!SOURCE_EXTENSIONS.has(extension)) continue;
|
|
6709
6929
|
const stem = relativePath.slice(0, -extension.length);
|
|
6710
6930
|
remember(byStem, stem, relativePath);
|
|
6711
6931
|
remember(byStemFolded, stem.toLowerCase(), relativePath);
|
|
6712
|
-
if (
|
|
6713
|
-
const directory =
|
|
6932
|
+
if (path12.posix.basename(stem) === "index") {
|
|
6933
|
+
const directory = path12.posix.dirname(stem);
|
|
6714
6934
|
remember(byStem, directory, relativePath);
|
|
6715
6935
|
remember(byStemFolded, directory.toLowerCase(), relativePath);
|
|
6716
6936
|
}
|
|
@@ -6719,8 +6939,8 @@ function buildImportResolver(files) {
|
|
|
6719
6939
|
}
|
|
6720
6940
|
function resolveSpecifier(fromFile, specifier, resolver) {
|
|
6721
6941
|
if (!RELATIVE_SPECIFIER.test(specifier)) return null;
|
|
6722
|
-
const joined =
|
|
6723
|
-
|
|
6942
|
+
const joined = path12.posix.normalize(
|
|
6943
|
+
path12.posix.join(path12.posix.dirname(fromFile), specifier)
|
|
6724
6944
|
);
|
|
6725
6945
|
const exact = (key) => resolver.byPath.get(key) ?? resolver.byPathFolded.get(key.toLowerCase()) ?? null;
|
|
6726
6946
|
const direct = exact(joined);
|
|
@@ -6813,11 +7033,11 @@ function verifyInMemory(files, parts, maxPartBytes) {
|
|
|
6813
7033
|
return errors;
|
|
6814
7034
|
}
|
|
6815
7035
|
function resolveSafeOutputDirs(config) {
|
|
6816
|
-
const cwd =
|
|
6817
|
-
const outDir =
|
|
6818
|
-
const tmpDir =
|
|
6819
|
-
const root =
|
|
6820
|
-
const contains = (parent, child) => child === parent || child.startsWith(`${parent}${
|
|
7036
|
+
const cwd = path12.resolve(config.cwd);
|
|
7037
|
+
const outDir = path12.resolve(cwd, config.outDir);
|
|
7038
|
+
const tmpDir = path12.resolve(cwd, config.tmpDir);
|
|
7039
|
+
const root = path12.parse(cwd).root;
|
|
7040
|
+
const contains = (parent, child) => child === parent || child.startsWith(`${parent}${path12.sep}`);
|
|
6821
7041
|
const reject = (reason) => {
|
|
6822
7042
|
throw new Error(
|
|
6823
7043
|
`Refusing to generate: ${reason}. outDir=${outDir} tmpDir=${tmpDir} cwd=${cwd}`
|
|
@@ -6873,14 +7093,14 @@ function listEntries(dir) {
|
|
|
6873
7093
|
const walk = (absolute, prefix) => {
|
|
6874
7094
|
let children;
|
|
6875
7095
|
try {
|
|
6876
|
-
children =
|
|
7096
|
+
children = fs11.readdirSync(absolute, { withFileTypes: true });
|
|
6877
7097
|
} catch {
|
|
6878
7098
|
return;
|
|
6879
7099
|
}
|
|
6880
7100
|
for (const child of children) {
|
|
6881
|
-
const
|
|
6882
|
-
entries.push(
|
|
6883
|
-
if (child.isDirectory()) walk(
|
|
7101
|
+
const relative7 = prefix === "" ? child.name : `${prefix}/${child.name}`;
|
|
7102
|
+
entries.push(relative7);
|
|
7103
|
+
if (child.isDirectory()) walk(path12.join(absolute, child.name), relative7);
|
|
6884
7104
|
}
|
|
6885
7105
|
};
|
|
6886
7106
|
walk(dir, "");
|
|
@@ -6897,28 +7117,28 @@ function markerPayload(dir, state, id, createdAt) {
|
|
|
6897
7117
|
pid: process.pid,
|
|
6898
7118
|
entries: listEntries(dir)
|
|
6899
7119
|
};
|
|
6900
|
-
const manifestPath =
|
|
6901
|
-
if (
|
|
6902
|
-
marker2.manifestSha256 = sha256Hex2(
|
|
7120
|
+
const manifestPath = path12.join(dir, "manifest.json");
|
|
7121
|
+
if (fs11.existsSync(manifestPath)) {
|
|
7122
|
+
marker2.manifestSha256 = sha256Hex2(fs11.readFileSync(manifestPath));
|
|
6903
7123
|
}
|
|
6904
7124
|
return marker2;
|
|
6905
7125
|
}
|
|
6906
7126
|
function writeOwnershipMarker(dir, marker2) {
|
|
6907
|
-
|
|
6908
|
-
|
|
7127
|
+
fs11.writeFileSync(
|
|
7128
|
+
path12.join(dir, OWNERSHIP_MARKER),
|
|
6909
7129
|
`${JSON.stringify(marker2, null, 2)}
|
|
6910
7130
|
`,
|
|
6911
7131
|
"utf8"
|
|
6912
7132
|
);
|
|
6913
7133
|
}
|
|
6914
7134
|
function readOwnershipMarker(dir) {
|
|
6915
|
-
const markerPath =
|
|
6916
|
-
if (!
|
|
7135
|
+
const markerPath = path12.join(dir, OWNERSHIP_MARKER);
|
|
7136
|
+
if (!fs11.existsSync(markerPath)) {
|
|
6917
7137
|
return { marker: null, problem: "it holds no .codebase-snapshot marker" };
|
|
6918
7138
|
}
|
|
6919
7139
|
let raw;
|
|
6920
7140
|
try {
|
|
6921
|
-
raw =
|
|
7141
|
+
raw = fs11.readFileSync(markerPath, "utf8");
|
|
6922
7142
|
} catch {
|
|
6923
7143
|
return { marker: null, problem: "its .codebase-snapshot marker could not be read" };
|
|
6924
7144
|
}
|
|
@@ -6986,11 +7206,11 @@ function assessSnapshotDirectory(dir) {
|
|
|
6986
7206
|
marker: marker2
|
|
6987
7207
|
};
|
|
6988
7208
|
}
|
|
6989
|
-
const manifestPath =
|
|
6990
|
-
if (
|
|
7209
|
+
const manifestPath = path12.join(dir, "manifest.json");
|
|
7210
|
+
if (fs11.existsSync(manifestPath)) {
|
|
6991
7211
|
let raw;
|
|
6992
7212
|
try {
|
|
6993
|
-
raw =
|
|
7213
|
+
raw = fs11.readFileSync(manifestPath, "utf8");
|
|
6994
7214
|
} catch {
|
|
6995
7215
|
return {
|
|
6996
7216
|
owned: false,
|
|
@@ -7072,13 +7292,13 @@ function containsNoFiles(dir) {
|
|
|
7072
7292
|
const current = pending.pop();
|
|
7073
7293
|
let entries;
|
|
7074
7294
|
try {
|
|
7075
|
-
entries =
|
|
7295
|
+
entries = fs11.readdirSync(current, { withFileTypes: true });
|
|
7076
7296
|
} catch {
|
|
7077
7297
|
return false;
|
|
7078
7298
|
}
|
|
7079
7299
|
for (const entry of entries) {
|
|
7080
7300
|
if (entry.isDirectory()) {
|
|
7081
|
-
pending.push(
|
|
7301
|
+
pending.push(path12.join(current, entry.name));
|
|
7082
7302
|
} else if (!IGNORABLE_ENTRIES.has(entry.name)) {
|
|
7083
7303
|
return false;
|
|
7084
7304
|
}
|
|
@@ -7087,13 +7307,13 @@ function containsNoFiles(dir) {
|
|
|
7087
7307
|
return true;
|
|
7088
7308
|
}
|
|
7089
7309
|
function assertReplaceable(target, label, secretRules) {
|
|
7090
|
-
if (!
|
|
7091
|
-
if (!
|
|
7310
|
+
if (!fs11.existsSync(target)) return;
|
|
7311
|
+
if (!fs11.statSync(target).isDirectory()) {
|
|
7092
7312
|
throw new Error(
|
|
7093
7313
|
`Refusing to write into ${label} ${target}: it exists but is not a directory.`
|
|
7094
7314
|
);
|
|
7095
7315
|
}
|
|
7096
|
-
const entries =
|
|
7316
|
+
const entries = fs11.readdirSync(target);
|
|
7097
7317
|
if (entries.length === 0) return;
|
|
7098
7318
|
if (entries.every((name) => IGNORABLE_ENTRIES.has(name))) return;
|
|
7099
7319
|
if (containsNoFiles(target)) return;
|
|
@@ -7113,18 +7333,18 @@ function assertReplaceable(target, label, secretRules) {
|
|
|
7113
7333
|
);
|
|
7114
7334
|
}
|
|
7115
7335
|
function assertStagingRootUsable(tmpDir, secretRules) {
|
|
7116
|
-
if (!
|
|
7117
|
-
if (!
|
|
7336
|
+
if (!fs11.existsSync(tmpDir)) return;
|
|
7337
|
+
if (!fs11.statSync(tmpDir).isDirectory()) {
|
|
7118
7338
|
throw new Error(`Refusing to write into tmpDir ${tmpDir}: it exists but is not a directory.`);
|
|
7119
7339
|
}
|
|
7120
|
-
const entries =
|
|
7340
|
+
const entries = fs11.readdirSync(tmpDir);
|
|
7121
7341
|
if (entries.length === 0) return;
|
|
7122
7342
|
const foreign = entries.filter((name) => {
|
|
7123
|
-
const full =
|
|
7343
|
+
const full = path12.join(tmpDir, name);
|
|
7124
7344
|
if (!name.startsWith(RUN_DIR_PREFIX)) return true;
|
|
7125
7345
|
let stats;
|
|
7126
7346
|
try {
|
|
7127
|
-
stats =
|
|
7347
|
+
stats = fs11.statSync(full);
|
|
7128
7348
|
} catch {
|
|
7129
7349
|
return true;
|
|
7130
7350
|
}
|
|
@@ -7132,7 +7352,7 @@ function assertStagingRootUsable(tmpDir, secretRules) {
|
|
|
7132
7352
|
const { marker: marker2 } = readOwnershipMarker(full);
|
|
7133
7353
|
if (marker2) return false;
|
|
7134
7354
|
try {
|
|
7135
|
-
return
|
|
7355
|
+
return fs11.readdirSync(full).length > 0;
|
|
7136
7356
|
} catch {
|
|
7137
7357
|
return true;
|
|
7138
7358
|
}
|
|
@@ -7147,13 +7367,13 @@ function assertStagingRootUsable(tmpDir, secretRules) {
|
|
|
7147
7367
|
function cleanStaleRunDirs(tmpDir) {
|
|
7148
7368
|
let entries;
|
|
7149
7369
|
try {
|
|
7150
|
-
entries =
|
|
7370
|
+
entries = fs11.readdirSync(tmpDir);
|
|
7151
7371
|
} catch {
|
|
7152
7372
|
return;
|
|
7153
7373
|
}
|
|
7154
7374
|
for (const name of entries) {
|
|
7155
7375
|
if (!name.startsWith(RUN_DIR_PREFIX)) continue;
|
|
7156
|
-
const full =
|
|
7376
|
+
const full = path12.join(tmpDir, name);
|
|
7157
7377
|
const { marker: marker2 } = readOwnershipMarker(full);
|
|
7158
7378
|
if (marker2) {
|
|
7159
7379
|
const created = Date.parse(marker2.createdAt);
|
|
@@ -7161,33 +7381,33 @@ function cleanStaleRunDirs(tmpDir) {
|
|
|
7161
7381
|
const abandoned = typeof marker2.pid === "number" && !isProcessAlive(marker2.pid);
|
|
7162
7382
|
if (aged || abandoned) {
|
|
7163
7383
|
try {
|
|
7164
|
-
|
|
7384
|
+
fs11.rmSync(full, { recursive: true, force: true });
|
|
7165
7385
|
} catch {
|
|
7166
7386
|
}
|
|
7167
7387
|
}
|
|
7168
7388
|
continue;
|
|
7169
7389
|
}
|
|
7170
7390
|
try {
|
|
7171
|
-
if (
|
|
7391
|
+
if (fs11.readdirSync(full).length === 0) fs11.rmSync(full, { recursive: true, force: true });
|
|
7172
7392
|
} catch {
|
|
7173
7393
|
}
|
|
7174
7394
|
}
|
|
7175
7395
|
}
|
|
7176
7396
|
function removeIfEmpty(dir) {
|
|
7177
7397
|
try {
|
|
7178
|
-
if (
|
|
7398
|
+
if (fs11.readdirSync(dir).length === 0) fs11.rmSync(dir, { recursive: true, force: true });
|
|
7179
7399
|
} catch {
|
|
7180
7400
|
}
|
|
7181
7401
|
}
|
|
7182
7402
|
function ensureParentDirectory(target, label) {
|
|
7183
|
-
const parent =
|
|
7184
|
-
if (
|
|
7403
|
+
const parent = path12.dirname(target);
|
|
7404
|
+
if (fs11.existsSync(parent) && !fs11.statSync(parent).isDirectory()) {
|
|
7185
7405
|
throw new Error(
|
|
7186
7406
|
`Refusing to create ${label} ${target}: ${parent} exists but is not a directory.`
|
|
7187
7407
|
);
|
|
7188
7408
|
}
|
|
7189
7409
|
try {
|
|
7190
|
-
|
|
7410
|
+
fs11.mkdirSync(parent, { recursive: true });
|
|
7191
7411
|
} catch (err) {
|
|
7192
7412
|
throw new Error(
|
|
7193
7413
|
`Refusing to create ${label} ${target}: its parent ${parent} could not be created (${err.message}).`
|
|
@@ -7196,7 +7416,7 @@ function ensureParentDirectory(target, label) {
|
|
|
7196
7416
|
}
|
|
7197
7417
|
function acquireRunLock(stagingRoot) {
|
|
7198
7418
|
const key = lockKeyFor(stagingRoot);
|
|
7199
|
-
const lockPath =
|
|
7419
|
+
const lockPath = path12.join(os.tmpdir(), `codebase-snapshot-${key}.lock`);
|
|
7200
7420
|
const payload = `${JSON.stringify({
|
|
7201
7421
|
pid: process.pid,
|
|
7202
7422
|
stagingRoot,
|
|
@@ -7205,22 +7425,22 @@ function acquireRunLock(stagingRoot) {
|
|
|
7205
7425
|
`;
|
|
7206
7426
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
7207
7427
|
try {
|
|
7208
|
-
const handle =
|
|
7428
|
+
const handle = fs11.openSync(lockPath, "wx");
|
|
7209
7429
|
try {
|
|
7210
|
-
|
|
7430
|
+
fs11.writeSync(handle, payload);
|
|
7211
7431
|
} finally {
|
|
7212
|
-
|
|
7432
|
+
fs11.closeSync(handle);
|
|
7213
7433
|
}
|
|
7214
7434
|
return () => {
|
|
7215
7435
|
try {
|
|
7216
|
-
|
|
7436
|
+
fs11.rmSync(lockPath, { force: true });
|
|
7217
7437
|
} catch {
|
|
7218
7438
|
}
|
|
7219
7439
|
};
|
|
7220
7440
|
} catch (err) {
|
|
7221
7441
|
if (err.code !== "EEXIST") throw err;
|
|
7222
7442
|
if (attempt === 0 && lockOwnerIsGone(lockPath)) {
|
|
7223
|
-
|
|
7443
|
+
fs11.rmSync(lockPath, { force: true });
|
|
7224
7444
|
continue;
|
|
7225
7445
|
}
|
|
7226
7446
|
throw new Error(
|
|
@@ -7233,7 +7453,7 @@ function acquireRunLock(stagingRoot) {
|
|
|
7233
7453
|
function lockKeyFor(stagingRoot) {
|
|
7234
7454
|
let key = stagingRoot;
|
|
7235
7455
|
try {
|
|
7236
|
-
key =
|
|
7456
|
+
key = fs11.realpathSync(stagingRoot);
|
|
7237
7457
|
} catch {
|
|
7238
7458
|
}
|
|
7239
7459
|
if (process.platform === "win32" || process.platform === "darwin") key = key.toLowerCase();
|
|
@@ -7242,7 +7462,7 @@ function lockKeyFor(stagingRoot) {
|
|
|
7242
7462
|
function lockOwnerIsGone(lockPath) {
|
|
7243
7463
|
let raw;
|
|
7244
7464
|
try {
|
|
7245
|
-
raw =
|
|
7465
|
+
raw = fs11.readFileSync(lockPath, "utf8");
|
|
7246
7466
|
} catch {
|
|
7247
7467
|
return false;
|
|
7248
7468
|
}
|
|
@@ -7262,12 +7482,12 @@ function lockOwnerIsGone(lockPath) {
|
|
|
7262
7482
|
}
|
|
7263
7483
|
function writeManifestAtomically(manifestPath, manifest) {
|
|
7264
7484
|
const tempPath = `${manifestPath}.${process.pid}.tmp`;
|
|
7265
|
-
|
|
7485
|
+
fs11.writeFileSync(tempPath, JSON.stringify(manifest, null, 2), "utf8");
|
|
7266
7486
|
try {
|
|
7267
|
-
|
|
7487
|
+
fs11.renameSync(tempPath, manifestPath);
|
|
7268
7488
|
} catch (err) {
|
|
7269
7489
|
try {
|
|
7270
|
-
|
|
7490
|
+
fs11.rmSync(tempPath, { force: true });
|
|
7271
7491
|
} catch {
|
|
7272
7492
|
}
|
|
7273
7493
|
throw err;
|
|
@@ -7290,31 +7510,31 @@ function swapIntoPlace(stagingDir, outDir) {
|
|
|
7290
7510
|
}
|
|
7291
7511
|
function swapOnce(stagingDir, outDir) {
|
|
7292
7512
|
const backupDir = `${outDir}.backup-${process.pid}-${Date.now()}`;
|
|
7293
|
-
const hadPrevious =
|
|
7294
|
-
if (hadPrevious)
|
|
7513
|
+
const hadPrevious = fs11.existsSync(outDir);
|
|
7514
|
+
if (hadPrevious) fs11.renameSync(outDir, backupDir);
|
|
7295
7515
|
try {
|
|
7296
7516
|
try {
|
|
7297
|
-
|
|
7517
|
+
fs11.renameSync(stagingDir, outDir);
|
|
7298
7518
|
} catch (err) {
|
|
7299
7519
|
if (err.code !== "EXDEV") throw err;
|
|
7300
7520
|
const partialDir = `${outDir}.partial-${process.pid}-${Date.now()}`;
|
|
7301
7521
|
try {
|
|
7302
|
-
|
|
7303
|
-
|
|
7304
|
-
|
|
7522
|
+
fs11.cpSync(stagingDir, partialDir, { recursive: true });
|
|
7523
|
+
fs11.renameSync(partialDir, outDir);
|
|
7524
|
+
fs11.rmSync(stagingDir, { recursive: true, force: true });
|
|
7305
7525
|
} catch (copyErr) {
|
|
7306
7526
|
try {
|
|
7307
|
-
|
|
7527
|
+
fs11.rmSync(partialDir, { recursive: true, force: true });
|
|
7308
7528
|
} catch {
|
|
7309
7529
|
}
|
|
7310
7530
|
throw copyErr;
|
|
7311
7531
|
}
|
|
7312
7532
|
}
|
|
7313
|
-
if (hadPrevious)
|
|
7533
|
+
if (hadPrevious) fs11.rmSync(backupDir, { recursive: true, force: true });
|
|
7314
7534
|
} catch (err) {
|
|
7315
|
-
if (hadPrevious &&
|
|
7316
|
-
if (
|
|
7317
|
-
|
|
7535
|
+
if (hadPrevious && fs11.existsSync(backupDir)) {
|
|
7536
|
+
if (fs11.existsSync(outDir)) fs11.rmSync(outDir, { recursive: true, force: true });
|
|
7537
|
+
fs11.renameSync(backupDir, outDir);
|
|
7318
7538
|
}
|
|
7319
7539
|
throw err;
|
|
7320
7540
|
}
|
|
@@ -7409,8 +7629,8 @@ function generateSnapshot(configInput, options) {
|
|
|
7409
7629
|
// first run cannot have marked them yet, so the name alone keeps a run from recording its own
|
|
7410
7630
|
// output — but only at these exact paths, not at every directory that shares the name.
|
|
7411
7631
|
toolOwnedPaths: [config.outDir, config.tmpDir].map(
|
|
7412
|
-
(directory) =>
|
|
7413
|
-
).filter((
|
|
7632
|
+
(directory) => path12.relative(config.cwd, path12.resolve(config.cwd, directory)).replace(/\\/g, "/")
|
|
7633
|
+
).filter((relative7) => relative7.length > 0 && !relative7.startsWith(".."))
|
|
7414
7634
|
});
|
|
7415
7635
|
const files = [];
|
|
7416
7636
|
const monolithBodies = /* @__PURE__ */ new Map();
|
|
@@ -7430,12 +7650,12 @@ function generateSnapshot(configInput, options) {
|
|
|
7430
7650
|
let totalSizeBytes = 0;
|
|
7431
7651
|
let largeFileCount = 0;
|
|
7432
7652
|
for (const relativePath of collection.files) {
|
|
7433
|
-
const absolutePath =
|
|
7653
|
+
const absolutePath = path12.join(config.cwd, relativePath);
|
|
7434
7654
|
let stats;
|
|
7435
7655
|
try {
|
|
7436
|
-
stats =
|
|
7656
|
+
stats = fs11.statSync(absolutePath);
|
|
7437
7657
|
if (!stats.isFile()) {
|
|
7438
|
-
const nestedRepository = stats.isDirectory() &&
|
|
7658
|
+
const nestedRepository = stats.isDirectory() && fs11.existsSync(path12.join(absolutePath, ".git"));
|
|
7439
7659
|
skipped.push(
|
|
7440
7660
|
nestedRepository ? {
|
|
7441
7661
|
path: relativePath,
|
|
@@ -7455,7 +7675,7 @@ function generateSnapshot(configInput, options) {
|
|
|
7455
7675
|
}
|
|
7456
7676
|
let buffer;
|
|
7457
7677
|
try {
|
|
7458
|
-
buffer =
|
|
7678
|
+
buffer = fs11.readFileSync(absolutePath);
|
|
7459
7679
|
} catch {
|
|
7460
7680
|
skipped.push({ path: relativePath, reason: "read-error" });
|
|
7461
7681
|
continue;
|
|
@@ -7785,12 +8005,12 @@ function generateSnapshot(configInput, options) {
|
|
|
7785
8005
|
let stagingDir = null;
|
|
7786
8006
|
try {
|
|
7787
8007
|
ensureParentDirectory(outDir, "outDir");
|
|
7788
|
-
|
|
8008
|
+
fs11.mkdirSync(tmpDir, { recursive: true });
|
|
7789
8009
|
cleanStaleRunDirs(tmpDir);
|
|
7790
8010
|
const snapshotId = crypto.randomUUID();
|
|
7791
8011
|
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
7792
|
-
stagingDir =
|
|
7793
|
-
|
|
8012
|
+
stagingDir = path12.join(tmpDir, `${RUN_DIR_PREFIX}${process.pid}-${snapshotId.slice(0, 8)}`);
|
|
8013
|
+
fs11.mkdirSync(stagingDir);
|
|
7794
8014
|
writeOwnershipMarker(stagingDir, {
|
|
7795
8015
|
generator: GENERATOR_NAME,
|
|
7796
8016
|
version: GENERATOR_VERSION,
|
|
@@ -7800,41 +8020,41 @@ function generateSnapshot(configInput, options) {
|
|
|
7800
8020
|
id: snapshotId,
|
|
7801
8021
|
pid: process.pid
|
|
7802
8022
|
});
|
|
7803
|
-
const partsDir =
|
|
7804
|
-
const reportsDir =
|
|
7805
|
-
if (artifacts.parts)
|
|
8023
|
+
const partsDir = path12.join(stagingDir, "parts");
|
|
8024
|
+
const reportsDir = path12.join(stagingDir, "reports");
|
|
8025
|
+
if (artifacts.parts) fs11.mkdirSync(partsDir, { recursive: true });
|
|
7806
8026
|
if (artifacts.navigation || artifacts.projectTree || artifacts.audit) {
|
|
7807
|
-
|
|
8027
|
+
fs11.mkdirSync(reportsDir, { recursive: true });
|
|
7808
8028
|
}
|
|
7809
8029
|
if (artifacts.parts) {
|
|
7810
8030
|
for (const part of parts) {
|
|
7811
|
-
|
|
8031
|
+
fs11.writeFileSync(path12.join(partsDir, part.filename), part.markdown, "utf8");
|
|
7812
8032
|
}
|
|
7813
8033
|
}
|
|
7814
8034
|
if (projectTree !== null) {
|
|
7815
|
-
|
|
8035
|
+
fs11.writeFileSync(path12.join(reportsDir, "project-tree.md"), projectTree, "utf8");
|
|
7816
8036
|
}
|
|
7817
8037
|
if (artifacts.navigation) {
|
|
7818
|
-
|
|
8038
|
+
fs11.writeFileSync(path12.join(reportsDir, "navigation.md"), generateNavigation(manifest), "utf8");
|
|
7819
8039
|
}
|
|
7820
8040
|
if (artifacts.audit) {
|
|
7821
|
-
|
|
7822
|
-
|
|
8041
|
+
fs11.writeFileSync(
|
|
8042
|
+
path12.join(reportsDir, "audit.md"),
|
|
7823
8043
|
generateAuditReport(manifest, secretFindings, config.secretRules),
|
|
7824
8044
|
"utf8"
|
|
7825
8045
|
);
|
|
7826
8046
|
}
|
|
7827
8047
|
if (artifacts.readme) {
|
|
7828
|
-
|
|
8048
|
+
fs11.writeFileSync(path12.join(stagingDir, "README.md"), generateReadme(manifest, artifacts), "utf8");
|
|
7829
8049
|
}
|
|
7830
8050
|
if (artifacts.manifest) {
|
|
7831
|
-
|
|
8051
|
+
fs11.writeFileSync(path12.join(stagingDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf8");
|
|
7832
8052
|
}
|
|
7833
8053
|
if (monolithJson !== null) {
|
|
7834
|
-
|
|
8054
|
+
fs11.writeFileSync(path12.join(stagingDir, "full-codebase.json"), monolithJson, "utf8");
|
|
7835
8055
|
}
|
|
7836
8056
|
if (monolithMarkdown !== null) {
|
|
7837
|
-
|
|
8057
|
+
fs11.writeFileSync(path12.join(stagingDir, "full-codebase.md"), monolithMarkdown, "utf8");
|
|
7838
8058
|
}
|
|
7839
8059
|
writeOwnershipMarker(stagingDir, markerPayload(stagingDir, "complete", snapshotId, createdAt));
|
|
7840
8060
|
swapIntoPlace(stagingDir, outDir);
|
|
@@ -7843,7 +8063,7 @@ function generateSnapshot(configInput, options) {
|
|
|
7843
8063
|
const pendingHook = runAfterGenerate(config, manifest);
|
|
7844
8064
|
const shipHookState = () => {
|
|
7845
8065
|
if (artifacts.manifest && manifest.warnings.length !== warningsBeforeHook) {
|
|
7846
|
-
writeManifestAtomically(
|
|
8066
|
+
writeManifestAtomically(path12.join(outDir, "manifest.json"), manifest);
|
|
7847
8067
|
}
|
|
7848
8068
|
writeOwnershipMarker(outDir, markerPayload(outDir, "complete", snapshotId, createdAt));
|
|
7849
8069
|
};
|
|
@@ -7866,7 +8086,7 @@ function generateSnapshot(configInput, options) {
|
|
|
7866
8086
|
} catch (err) {
|
|
7867
8087
|
if (stagingDir !== null) {
|
|
7868
8088
|
try {
|
|
7869
|
-
|
|
8089
|
+
fs11.rmSync(stagingDir, { recursive: true, force: true });
|
|
7870
8090
|
} catch {
|
|
7871
8091
|
}
|
|
7872
8092
|
}
|
|
@@ -7880,6 +8100,6 @@ async function generateSnapshotAsync(configInput, options) {
|
|
|
7880
8100
|
return generateSnapshot(configInput, options);
|
|
7881
8101
|
}
|
|
7882
8102
|
|
|
7883
|
-
export { ARTIFACT_NAMES, DEFAULT_ANSWERS, DEFAULT_SECRET_PATTERNS, GENERATOR_NAME, GENERATOR_VERSION, LANGUAGE_PROFILES, MIN_MAX_PART_BYTES, PRESETS, PromptCancelled, SCHEMA_VERSION, anchorForPath, appendGitignoreEntries, assertReplaceable, assertUsableConfigInput, collectFiles, compareManifests, countInvalidUtf8Bytes, countLines, decodeText, defineConfig, describeManifestShapeProblem, detectLanguage, detectPreset, escapeControls, explainPath, fenceForContent, fenceForText, formatNumber, generateProjectTree, generateSnapshot, generateSnapshotAsync, getPreset, gitignorePath, globToRegExp, isBinaryBuffer, isBinaryExtension, isBinaryFile, isDiscoveryMode, isInteractive, isResolvedConfig, isSensitivePath, listPresetNames, loadConfigFile, loadConfigFileDetailed, loadManifest, markResolvedConfig, matchRule, matchesExclude, normalizeResolvedConfig, parseByteSize, partitionFiles, profileForPath, renderConfig, renderDiffText, renderExplainText, renumberParts, resolveConfig, resolveSafeOutputDirs, runInit, scanAndRedactSecrets, stripJsonComments, summarizeJson, summarizeMarkdown, summarizeTypeScriptSource, summarizeWithProfile, supportsTypeScriptImports, truncatePreview, truncateToBytes, unknownPresetMessage, validateSnapshot };
|
|
7884
|
-
//# sourceMappingURL=chunk-
|
|
7885
|
-
//# sourceMappingURL=chunk-
|
|
8103
|
+
export { ARTIFACT_NAMES, DEFAULT_ANSWERS, DEFAULT_SECRET_PATTERNS, GENERATOR_NAME, GENERATOR_VERSION, LANGUAGE_PROFILES, MIN_MAX_PART_BYTES, PRESETS, PromptCancelled, SCHEMA_VERSION, anchorForPath, appendGitignoreEntries, assertReplaceable, assertUsableConfigInput, collectFiles, compareManifests, countInvalidUtf8Bytes, countLines, decodeText, defaultIO, defineConfig, describeManifestShapeProblem, detectLanguage, detectPreset, escapeControls, explainPath, fenceForContent, fenceForText, findConfigFile, firstRunMessage, formatNumber, generateProjectTree, generateSnapshot, generateSnapshotAsync, getPreset, gitignorePath, globToRegExp, hasPriorSnapshot, isBinaryBuffer, isBinaryExtension, isBinaryFile, isDiscoveryMode, isEffectivelyEmpty, isInteractive, isResolvedConfig, isSensitivePath, listPresetNames, loadConfigFile, loadConfigFileDetailed, loadManifest, markResolvedConfig, matchRule, matchesExclude, normalizeResolvedConfig, parseByteSize, partitionFiles, planFirstRun, profileForPath, renderConfig, renderDiffText, renderExplainText, renumberParts, resolveConfig, resolveSafeOutputDirs, runInit, scanAndRedactSecrets, stripJsonComments, summarizeJson, summarizeMarkdown, summarizeTypeScriptSource, summarizeWithProfile, supportsTypeScriptImports, truncatePreview, truncateToBytes, unknownPresetMessage, validateSnapshot };
|
|
8104
|
+
//# sourceMappingURL=chunk-B336MMNC.js.map
|
|
8105
|
+
//# sourceMappingURL=chunk-B336MMNC.js.map
|