codebase-snapshot 0.9.0 → 0.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +229 -1
- package/README.md +141 -127
- package/SECURITY.md +6 -2
- package/dist/bin.js +2544 -507
- package/dist/chunk-EWZYK3VX.js +2577 -0
- package/dist/{chunk-FJ4RQYY2.js → chunk-FCWD4LY7.js} +493 -362
- package/dist/cli.cjs +2451 -436
- package/dist/cli.d.cts +11 -1
- package/dist/cli.d.ts +11 -1
- package/dist/cli.js +68 -750
- package/dist/index.cjs +481 -360
- 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 +85 -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 +32 -16
package/dist/cli.cjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var childProcess = require('child_process');
|
|
4
|
-
var
|
|
5
|
-
var
|
|
4
|
+
var fs11 = require('fs');
|
|
5
|
+
var path12 = require('path');
|
|
6
6
|
var util = require('util');
|
|
7
7
|
var crypto2 = require('crypto');
|
|
8
8
|
var module$1 = require('module');
|
|
@@ -33,8 +33,8 @@ function _interopNamespace(e) {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
var childProcess__namespace = /*#__PURE__*/_interopNamespace(childProcess);
|
|
36
|
-
var
|
|
37
|
-
var
|
|
36
|
+
var fs11__namespace = /*#__PURE__*/_interopNamespace(fs11);
|
|
37
|
+
var path12__namespace = /*#__PURE__*/_interopNamespace(path12);
|
|
38
38
|
var crypto2__namespace = /*#__PURE__*/_interopNamespace(crypto2);
|
|
39
39
|
var readline__namespace = /*#__PURE__*/_interopNamespace(readline);
|
|
40
40
|
var os__namespace = /*#__PURE__*/_interopNamespace(os);
|
|
@@ -87,15 +87,15 @@ var SKIPPED_WALK_DIRS = /* @__PURE__ */ new Set([
|
|
|
87
87
|
]);
|
|
88
88
|
function readSmallFile(fullPath) {
|
|
89
89
|
try {
|
|
90
|
-
const stats =
|
|
90
|
+
const stats = fs11__namespace.statSync(fullPath);
|
|
91
91
|
if (!stats.isFile() || stats.size > MANIFEST_MAX_BYTES) return null;
|
|
92
|
-
return
|
|
92
|
+
return fs11__namespace.readFileSync(fullPath, "utf8");
|
|
93
93
|
} catch {
|
|
94
94
|
return null;
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
function readManifest(cwd, name) {
|
|
98
|
-
return readSmallFile(
|
|
98
|
+
return readSmallFile(path12__namespace.join(cwd, name));
|
|
99
99
|
}
|
|
100
100
|
function dependenciesFromPackageJson(raw) {
|
|
101
101
|
const names = /* @__PURE__ */ new Set();
|
|
@@ -155,7 +155,7 @@ function pnpmWorkspacePatterns(raw) {
|
|
|
155
155
|
function childDirectories(cwd, parent) {
|
|
156
156
|
let entries;
|
|
157
157
|
try {
|
|
158
|
-
entries =
|
|
158
|
+
entries = fs11__namespace.readdirSync(path12__namespace.join(cwd, parent), { withFileTypes: true });
|
|
159
159
|
} catch {
|
|
160
160
|
return [];
|
|
161
161
|
}
|
|
@@ -190,13 +190,13 @@ function workspacePackageDirs(cwd, patterns) {
|
|
|
190
190
|
function scanWorkspacePackages(cwd, patterns) {
|
|
191
191
|
const files = /* @__PURE__ */ new Set();
|
|
192
192
|
const dependencies = /* @__PURE__ */ new Set();
|
|
193
|
-
for (const
|
|
194
|
-
const full =
|
|
193
|
+
for (const relative11 of workspacePackageDirs(cwd, patterns)) {
|
|
194
|
+
const full = path12__namespace.join(cwd, relative11);
|
|
195
195
|
try {
|
|
196
|
-
for (const entry of
|
|
196
|
+
for (const entry of fs11__namespace.readdirSync(full, { withFileTypes: true })) files.add(entry.name);
|
|
197
197
|
} catch {
|
|
198
198
|
}
|
|
199
|
-
const raw = readSmallFile(
|
|
199
|
+
const raw = readSmallFile(path12__namespace.join(full, "package.json"));
|
|
200
200
|
if (raw !== null) {
|
|
201
201
|
for (const name of dependenciesFromPackageJson(raw)) dependencies.add(name);
|
|
202
202
|
}
|
|
@@ -213,7 +213,7 @@ function countMarkdownFiles(cwd) {
|
|
|
213
213
|
directoriesSeen += 1;
|
|
214
214
|
let entries;
|
|
215
215
|
try {
|
|
216
|
-
entries =
|
|
216
|
+
entries = fs11__namespace.readdirSync(current.dir, { withFileTypes: true });
|
|
217
217
|
} catch {
|
|
218
218
|
continue;
|
|
219
219
|
}
|
|
@@ -224,7 +224,7 @@ function countMarkdownFiles(cwd) {
|
|
|
224
224
|
}
|
|
225
225
|
if (!entry.isDirectory() || current.depth >= 2) continue;
|
|
226
226
|
if (entry.name.startsWith(".") || SKIPPED_WALK_DIRS.has(entry.name)) continue;
|
|
227
|
-
queue.push({ dir:
|
|
227
|
+
queue.push({ dir: path12__namespace.join(current.dir, entry.name), depth: current.depth + 1 });
|
|
228
228
|
}
|
|
229
229
|
}
|
|
230
230
|
return count;
|
|
@@ -233,7 +233,7 @@ function buildPresetContext(cwd) {
|
|
|
233
233
|
const files = /* @__PURE__ */ new Set();
|
|
234
234
|
const dirs = /* @__PURE__ */ new Set();
|
|
235
235
|
try {
|
|
236
|
-
for (const entry of
|
|
236
|
+
for (const entry of fs11__namespace.readdirSync(cwd, { withFileTypes: true })) {
|
|
237
237
|
if (entry.isDirectory()) dirs.add(entry.name);
|
|
238
238
|
else if (entry.isFile()) files.add(entry.name);
|
|
239
239
|
}
|
|
@@ -250,7 +250,7 @@ function buildPresetContext(cwd) {
|
|
|
250
250
|
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;
|
|
251
251
|
let workspace = { files: /* @__PURE__ */ new Set(), dependencies: /* @__PURE__ */ new Set() };
|
|
252
252
|
if (isWorkspace) {
|
|
253
|
-
const pnpmRaw = files.has("pnpm-workspace.yaml") ? readSmallFile(
|
|
253
|
+
const pnpmRaw = files.has("pnpm-workspace.yaml") ? readSmallFile(path12__namespace.join(cwd, "pnpm-workspace.yaml")) : null;
|
|
254
254
|
const declared = pnpmRaw === null ? [] : pnpmWorkspacePatterns(pnpmRaw);
|
|
255
255
|
const patterns = declared.length > 0 ? declared : declaredWorkspaces.length > 0 ? declaredWorkspaces : DEFAULT_WORKSPACE_PATTERNS;
|
|
256
256
|
workspace = scanWorkspacePackages(cwd, patterns);
|
|
@@ -268,25 +268,41 @@ function buildPresetContext(cwd) {
|
|
|
268
268
|
markdownCount: files.has("package.json") ? 0 : countMarkdownFiles(cwd)
|
|
269
269
|
};
|
|
270
270
|
}
|
|
271
|
-
|
|
272
|
-
|
|
271
|
+
|
|
272
|
+
// src/presets/evidence.ts
|
|
273
|
+
function fileEvidence(context, name) {
|
|
274
|
+
return context.files.has(name) ? name : null;
|
|
273
275
|
}
|
|
274
|
-
function
|
|
276
|
+
function fileStartingWithEvidence(context, prefix) {
|
|
275
277
|
for (const file of context.files) {
|
|
276
|
-
if (file.startsWith(prefix)) return
|
|
278
|
+
if (file.startsWith(prefix)) return file;
|
|
277
279
|
}
|
|
278
280
|
for (const file of context.workspaceFiles) {
|
|
279
|
-
if (file.startsWith(prefix)) return
|
|
281
|
+
if (file.startsWith(prefix)) return file;
|
|
280
282
|
}
|
|
281
|
-
return
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
function dependencyEvidence(context, ...names) {
|
|
286
|
+
for (const name of names) {
|
|
287
|
+
if (context.dependencies.has(name)) return `depends on ${name}`;
|
|
288
|
+
}
|
|
289
|
+
return null;
|
|
282
290
|
}
|
|
283
|
-
function
|
|
291
|
+
function manifestEvidence(context, manifest, needle) {
|
|
284
292
|
const content = context.manifests[manifest];
|
|
285
|
-
if (!content) return
|
|
286
|
-
|
|
293
|
+
if (!content) return null;
|
|
294
|
+
if (!content.toLowerCase().includes(needle.toLowerCase())) return null;
|
|
295
|
+
return `${needle} in ${manifest}`;
|
|
296
|
+
}
|
|
297
|
+
function suffixEvidence(context, suffix) {
|
|
298
|
+
for (const file of context.files) {
|
|
299
|
+
if (file.endsWith(suffix)) return file;
|
|
300
|
+
}
|
|
301
|
+
return null;
|
|
287
302
|
}
|
|
288
|
-
function
|
|
289
|
-
|
|
303
|
+
function markdownEvidence(context) {
|
|
304
|
+
if (context.markdownCount < 3 || context.files.has("package.json")) return null;
|
|
305
|
+
return `${context.markdownCount} markdown files, no package.json`;
|
|
290
306
|
}
|
|
291
307
|
|
|
292
308
|
// src/presets/helpers.ts
|
|
@@ -494,7 +510,7 @@ var JS_PRESETS = [
|
|
|
494
510
|
{
|
|
495
511
|
name: "nextjs",
|
|
496
512
|
priority: 100,
|
|
497
|
-
detect: (context) =>
|
|
513
|
+
detect: (context) => fileStartingWithEvidence(context, "next.config") ?? dependencyEvidence(context, "next"),
|
|
498
514
|
layers: NEXTJS_LAYERS,
|
|
499
515
|
kinds: NEXTJS_KINDS,
|
|
500
516
|
exclude: NEXT_LIKE_EXCLUDES,
|
|
@@ -503,7 +519,7 @@ var JS_PRESETS = [
|
|
|
503
519
|
{
|
|
504
520
|
name: "nuxt",
|
|
505
521
|
priority: 98,
|
|
506
|
-
detect: (context) =>
|
|
522
|
+
detect: (context) => fileStartingWithEvidence(context, "nuxt.config") ?? dependencyEvidence(context, "nuxt", "nuxt3"),
|
|
507
523
|
layers: [
|
|
508
524
|
layer("pages", "pages/", "src/pages/"),
|
|
509
525
|
layer("components", "components/", "src/components/"),
|
|
@@ -527,7 +543,7 @@ var JS_PRESETS = [
|
|
|
527
543
|
{
|
|
528
544
|
name: "sveltekit",
|
|
529
545
|
priority: 96,
|
|
530
|
-
detect: (context) =>
|
|
546
|
+
detect: (context) => fileStartingWithEvidence(context, "svelte.config") ?? dependencyEvidence(context, "@sveltejs/kit", "svelte"),
|
|
531
547
|
layers: [
|
|
532
548
|
layer("routes", "src/routes/", "routes/"),
|
|
533
549
|
layer("lib", "src/lib/", "lib/"),
|
|
@@ -548,7 +564,7 @@ var JS_PRESETS = [
|
|
|
548
564
|
{
|
|
549
565
|
name: "remix",
|
|
550
566
|
priority: 94,
|
|
551
|
-
detect: (context) =>
|
|
567
|
+
detect: (context) => dependencyEvidence(context, "@remix-run/react", "@remix-run/node", "@remix-run/dev"),
|
|
552
568
|
layers: [
|
|
553
569
|
layer("routes", "app/routes/", "routes/"),
|
|
554
570
|
layer("components", "app/components/"),
|
|
@@ -562,7 +578,7 @@ var JS_PRESETS = [
|
|
|
562
578
|
{
|
|
563
579
|
name: "astro",
|
|
564
580
|
priority: 92,
|
|
565
|
-
detect: (context) =>
|
|
581
|
+
detect: (context) => fileStartingWithEvidence(context, "astro.config") ?? dependencyEvidence(context, "astro"),
|
|
566
582
|
layers: [
|
|
567
583
|
layer("pages", "src/pages/"),
|
|
568
584
|
layer("layouts", "src/layouts/"),
|
|
@@ -577,7 +593,7 @@ var JS_PRESETS = [
|
|
|
577
593
|
{
|
|
578
594
|
name: "angular",
|
|
579
595
|
priority: 90,
|
|
580
|
-
detect: (context) =>
|
|
596
|
+
detect: (context) => fileEvidence(context, "angular.json") ?? dependencyEvidence(context, "@angular/core"),
|
|
581
597
|
layers: [
|
|
582
598
|
layer("app", "src/app/"),
|
|
583
599
|
layer("features", "src/app/features/"),
|
|
@@ -601,7 +617,7 @@ var JS_PRESETS = [
|
|
|
601
617
|
{
|
|
602
618
|
name: "solid",
|
|
603
619
|
priority: 88,
|
|
604
|
-
detect: (context) =>
|
|
620
|
+
detect: (context) => dependencyEvidence(context, "solid-js", "solid-start"),
|
|
605
621
|
layers: [
|
|
606
622
|
layer("pages", "src/routes/", "src/pages/"),
|
|
607
623
|
layer("components", "src/components/"),
|
|
@@ -614,7 +630,15 @@ var JS_PRESETS = [
|
|
|
614
630
|
{
|
|
615
631
|
name: "vue-vite",
|
|
616
632
|
priority: 86,
|
|
617
|
-
|
|
633
|
+
// `vue` is the decisive signal; `vite` (or its config file) only says the app is
|
|
634
|
+
// built with Vite rather than shipped pre-compiled, so it is required but not what
|
|
635
|
+
// gets reported.
|
|
636
|
+
detect: (context) => {
|
|
637
|
+
const framework = dependencyEvidence(context, "vue");
|
|
638
|
+
if (framework === null) return null;
|
|
639
|
+
const build = dependencyEvidence(context, "vite") ?? fileStartingWithEvidence(context, "vite.config");
|
|
640
|
+
return build === null ? null : framework;
|
|
641
|
+
},
|
|
618
642
|
layers: [
|
|
619
643
|
layer("pages", "src/pages/", "src/views/"),
|
|
620
644
|
layer("components", "src/components/"),
|
|
@@ -639,7 +663,12 @@ var JS_PRESETS = [
|
|
|
639
663
|
{
|
|
640
664
|
name: "react-vite",
|
|
641
665
|
priority: 84,
|
|
642
|
-
detect: (context) =>
|
|
666
|
+
detect: (context) => {
|
|
667
|
+
const framework = dependencyEvidence(context, "react");
|
|
668
|
+
if (framework === null) return null;
|
|
669
|
+
const build = dependencyEvidence(context, "vite") ?? fileStartingWithEvidence(context, "vite.config");
|
|
670
|
+
return build === null ? null : framework;
|
|
671
|
+
},
|
|
643
672
|
layers: VITE_LAYERS,
|
|
644
673
|
kinds: VITE_KINDS,
|
|
645
674
|
exclude: [".vite", "dist"],
|
|
@@ -648,7 +677,7 @@ var JS_PRESETS = [
|
|
|
648
677
|
{
|
|
649
678
|
name: "nest",
|
|
650
679
|
priority: 80,
|
|
651
|
-
detect: (context) =>
|
|
680
|
+
detect: (context) => dependencyEvidence(context, "@nestjs/core"),
|
|
652
681
|
layers: [
|
|
653
682
|
layer("modules", "src/modules/", "modules/"),
|
|
654
683
|
layer("controllers", "src/controllers/", "controllers/"),
|
|
@@ -674,7 +703,7 @@ var JS_PRESETS = [
|
|
|
674
703
|
{
|
|
675
704
|
name: "node-express",
|
|
676
705
|
priority: 78,
|
|
677
|
-
detect: (context) =>
|
|
706
|
+
detect: (context) => dependencyEvidence(context, "express", "fastify", "koa", "@hapi/hapi"),
|
|
678
707
|
layers: NODE_LAYERS,
|
|
679
708
|
kinds: NODE_KINDS,
|
|
680
709
|
exclude: ["dist"],
|
|
@@ -685,7 +714,7 @@ var JS_PRESETS = [
|
|
|
685
714
|
// A bare package.json is often just tooling around a non-JS project, so it
|
|
686
715
|
// ranks below every definitive language manifest.
|
|
687
716
|
priority: 45,
|
|
688
|
-
detect: (context) =>
|
|
717
|
+
detect: (context) => fileEvidence(context, "package.json"),
|
|
689
718
|
layers: [
|
|
690
719
|
layer("src", "src/"),
|
|
691
720
|
layer("lib", "lib/"),
|
|
@@ -699,13 +728,12 @@ var JS_PRESETS = [
|
|
|
699
728
|
];
|
|
700
729
|
|
|
701
730
|
// src/presets/lang-presets.ts
|
|
702
|
-
var anyFileEndingWith = (context, suffix) => Array.from(context.files).some((file) => file.endsWith(suffix));
|
|
703
731
|
var DIR = (name) => `${name}/`;
|
|
704
732
|
var LANGUAGE_PRESETS = [
|
|
705
733
|
{
|
|
706
734
|
name: "python-django",
|
|
707
735
|
priority: 56,
|
|
708
|
-
detect: (context) =>
|
|
736
|
+
detect: (context) => fileEvidence(context, "manage.py") ?? manifestEvidence(context, "pyproject.toml", "django") ?? manifestEvidence(context, "requirements.txt", "django"),
|
|
709
737
|
layers: [
|
|
710
738
|
layer("views", DIR("views"), DIR("app/views"), DIR("apps/views")),
|
|
711
739
|
layer("models", DIR("models"), DIR("app/models"), DIR("apps/models")),
|
|
@@ -731,7 +759,7 @@ var LANGUAGE_PRESETS = [
|
|
|
731
759
|
{
|
|
732
760
|
name: "python-fastapi",
|
|
733
761
|
priority: 54,
|
|
734
|
-
detect: (context) =>
|
|
762
|
+
detect: (context) => manifestEvidence(context, "requirements.txt", "fastapi") ?? manifestEvidence(context, "requirements.txt", "flask") ?? manifestEvidence(context, "pyproject.toml", "fastapi") ?? manifestEvidence(context, "pyproject.toml", "flask"),
|
|
735
763
|
layers: [
|
|
736
764
|
layer("routers", DIR("routers"), DIR("routes"), DIR("api")),
|
|
737
765
|
layer("models", DIR("models"), DIR("db")),
|
|
@@ -755,7 +783,7 @@ var LANGUAGE_PRESETS = [
|
|
|
755
783
|
name: "python-lib",
|
|
756
784
|
// Below node-lib: a Node repo with a stray .py file is still a Node repo.
|
|
757
785
|
priority: 30,
|
|
758
|
-
detect: (context) =>
|
|
786
|
+
detect: (context) => fileEvidence(context, "pyproject.toml") ?? fileEvidence(context, "setup.py") ?? fileEvidence(context, "requirements.txt") ?? suffixEvidence(context, ".py"),
|
|
759
787
|
layers: [
|
|
760
788
|
layer("package", DIR("src"), DIR("lib")),
|
|
761
789
|
layer("tests", DIR("tests"), DIR("test")),
|
|
@@ -768,7 +796,7 @@ var LANGUAGE_PRESETS = [
|
|
|
768
796
|
{
|
|
769
797
|
name: "go",
|
|
770
798
|
priority: 50,
|
|
771
|
-
detect: (context) =>
|
|
799
|
+
detect: (context) => fileEvidence(context, "go.mod"),
|
|
772
800
|
layers: [
|
|
773
801
|
layer("cmd", "cmd/"),
|
|
774
802
|
layer("internal", "internal/"),
|
|
@@ -792,7 +820,7 @@ var LANGUAGE_PRESETS = [
|
|
|
792
820
|
{
|
|
793
821
|
name: "rust",
|
|
794
822
|
priority: 48,
|
|
795
|
-
detect: (context) =>
|
|
823
|
+
detect: (context) => fileEvidence(context, "Cargo.toml"),
|
|
796
824
|
layers: [
|
|
797
825
|
layer("src", "src/"),
|
|
798
826
|
layer("crates", "crates/"),
|
|
@@ -808,7 +836,7 @@ var LANGUAGE_PRESETS = [
|
|
|
808
836
|
{
|
|
809
837
|
name: "java-spring",
|
|
810
838
|
priority: 46,
|
|
811
|
-
detect: (context) =>
|
|
839
|
+
detect: (context) => fileEvidence(context, "pom.xml") ?? fileEvidence(context, "build.gradle") ?? fileEvidence(context, "build.gradle.kts"),
|
|
812
840
|
layers: [
|
|
813
841
|
layer("api", "src/main/java/"),
|
|
814
842
|
layer("test", "src/test/java/"),
|
|
@@ -831,7 +859,7 @@ var LANGUAGE_PRESETS = [
|
|
|
831
859
|
{
|
|
832
860
|
name: "dotnet",
|
|
833
861
|
priority: 44,
|
|
834
|
-
detect: (context) =>
|
|
862
|
+
detect: (context) => suffixEvidence(context, ".csproj") ?? suffixEvidence(context, ".sln") ?? fileEvidence(context, "global.json"),
|
|
835
863
|
layers: [
|
|
836
864
|
layer("web", "src/", "Web/"),
|
|
837
865
|
layer("domain", "Domain/", "Core/"),
|
|
@@ -854,7 +882,7 @@ var LANGUAGE_PRESETS = [
|
|
|
854
882
|
{
|
|
855
883
|
name: "php-laravel",
|
|
856
884
|
priority: 42,
|
|
857
|
-
detect: (context) =>
|
|
885
|
+
detect: (context) => fileEvidence(context, "artisan") ?? manifestEvidence(context, "composer.json", "laravel") ?? fileEvidence(context, "composer.json"),
|
|
858
886
|
layers: [
|
|
859
887
|
layer("app", "app/"),
|
|
860
888
|
layer("routes", "routes/"),
|
|
@@ -880,7 +908,7 @@ var LANGUAGE_PRESETS = [
|
|
|
880
908
|
{
|
|
881
909
|
name: "ruby-rails",
|
|
882
910
|
priority: 40,
|
|
883
|
-
detect: (context) =>
|
|
911
|
+
detect: (context) => fileEvidence(context, "Gemfile") ?? fileEvidence(context, "config.ru") ?? fileEvidence(context, "Rakefile"),
|
|
884
912
|
layers: [
|
|
885
913
|
layer("app", "app/"),
|
|
886
914
|
layer("controllers", "app/controllers/"),
|
|
@@ -953,7 +981,7 @@ var SHAPE_PRESETS = [
|
|
|
953
981
|
{
|
|
954
982
|
name: "monorepo",
|
|
955
983
|
priority: 70,
|
|
956
|
-
detect: (context) =>
|
|
984
|
+
detect: (context) => fileEvidence(context, "pnpm-workspace.yaml") ?? fileEvidence(context, "nx.json") ?? fileEvidence(context, "turbo.json") ?? fileEvidence(context, "lerna.json") ?? manifestEvidence(context, "package.json", '"workspaces"'),
|
|
957
985
|
layers: [
|
|
958
986
|
layer("apps", "apps/"),
|
|
959
987
|
layer("packages", "packages/"),
|
|
@@ -967,7 +995,7 @@ var SHAPE_PRESETS = [
|
|
|
967
995
|
{
|
|
968
996
|
name: "infra",
|
|
969
997
|
priority: 24,
|
|
970
|
-
detect: (context) =>
|
|
998
|
+
detect: (context) => suffixEvidence(context, ".tf") ?? fileEvidence(context, "Chart.yaml") ?? fileEvidence(context, "kustomization.yaml") ?? fileEvidence(context, "docker-compose.yml") ?? fileEvidence(context, "docker-compose.yaml"),
|
|
971
999
|
layers: [
|
|
972
1000
|
layer("modules", "modules/"),
|
|
973
1001
|
layer("environments", "env/", "environments/"),
|
|
@@ -989,7 +1017,7 @@ var SHAPE_PRESETS = [
|
|
|
989
1017
|
// up to two directories down (bounded in the context builder). Counting
|
|
990
1018
|
// only the root turned every real documentation repository — README.md plus
|
|
991
1019
|
// the pages under `docs/` — into `generic`.
|
|
992
|
-
detect: (context) =>
|
|
1020
|
+
detect: (context) => markdownEvidence(context),
|
|
993
1021
|
layers: [
|
|
994
1022
|
layer("content", "content/", "docs/"),
|
|
995
1023
|
layer("assets", "assets/", "static/", "public/"),
|
|
@@ -1002,7 +1030,10 @@ var SHAPE_PRESETS = [
|
|
|
1002
1030
|
{
|
|
1003
1031
|
name: "generic",
|
|
1004
1032
|
priority: 0,
|
|
1005
|
-
|
|
1033
|
+
// No detector: this is the fallback, and `detectPresetFromContext` reaches it only
|
|
1034
|
+
// when every other preset reported no evidence. `fallback: true` is what lets the
|
|
1035
|
+
// wizard tell "I recognised this" apart from "I gave up".
|
|
1036
|
+
fallback: true,
|
|
1006
1037
|
layers: DEFAULT_LAYERS,
|
|
1007
1038
|
kinds: DEFAULT_KINDS,
|
|
1008
1039
|
exclude: [],
|
|
@@ -1034,20 +1065,27 @@ function detectPresetFromContext(context, forced) {
|
|
|
1034
1065
|
if (forced !== void 0) {
|
|
1035
1066
|
const preset = BY_NAME.get(forced);
|
|
1036
1067
|
if (!preset) throw new Error(unknownPresetMessage(forced));
|
|
1037
|
-
return toDetected(preset);
|
|
1068
|
+
return toDetected(preset, null);
|
|
1038
1069
|
}
|
|
1039
1070
|
for (const preset of PRESETS) {
|
|
1040
|
-
|
|
1071
|
+
const reason = preset.detect?.(context) ?? null;
|
|
1072
|
+
if (reason !== null) return toDetected(preset, reason);
|
|
1073
|
+
}
|
|
1074
|
+
const fallback = PRESETS.find((preset) => preset.fallback === true);
|
|
1075
|
+
if (fallback === void 0) {
|
|
1076
|
+
throw new Error("No fallback preset is defined. Exactly one preset must set `fallback: true`.");
|
|
1041
1077
|
}
|
|
1042
|
-
return toDetected(
|
|
1078
|
+
return toDetected(fallback, null, true);
|
|
1043
1079
|
}
|
|
1044
|
-
function toDetected(preset) {
|
|
1080
|
+
function toDetected(preset, reason, fallback = false) {
|
|
1045
1081
|
return {
|
|
1046
1082
|
presetName: preset.name,
|
|
1047
1083
|
layers: preset.layers,
|
|
1048
1084
|
kinds: preset.kinds,
|
|
1049
1085
|
exclude: preset.exclude ? [...preset.exclude] : [],
|
|
1050
|
-
recommendedBudget: preset.recommendedBudget ?? null
|
|
1086
|
+
recommendedBudget: preset.recommendedBudget ?? null,
|
|
1087
|
+
reason,
|
|
1088
|
+
fallback
|
|
1051
1089
|
};
|
|
1052
1090
|
}
|
|
1053
1091
|
function detectPreset(cwd, forced) {
|
|
@@ -1487,14 +1525,14 @@ var PRESET_IGNORES = new Set(
|
|
|
1487
1525
|
);
|
|
1488
1526
|
function readLinkTarget(linkPath, fallback) {
|
|
1489
1527
|
try {
|
|
1490
|
-
return
|
|
1528
|
+
return fs11__namespace.readlinkSync(linkPath);
|
|
1491
1529
|
} catch {
|
|
1492
1530
|
return fallback;
|
|
1493
1531
|
}
|
|
1494
1532
|
}
|
|
1495
1533
|
function isInside(root, candidate) {
|
|
1496
|
-
const
|
|
1497
|
-
return
|
|
1534
|
+
const relative11 = path12__namespace.relative(root, candidate);
|
|
1535
|
+
return relative11 === "" || !relative11.startsWith(`..${path12__namespace.sep}`) && relative11 !== ".." && !path12__namespace.isAbsolute(relative11);
|
|
1498
1536
|
}
|
|
1499
1537
|
function getGitInfo(cwd, fallbackBranch = "main") {
|
|
1500
1538
|
try {
|
|
@@ -1699,10 +1737,10 @@ function trimUnescapedTrailingSpaces(line) {
|
|
|
1699
1737
|
return line.slice(0, end);
|
|
1700
1738
|
}
|
|
1701
1739
|
function readGitignoreRules(dir) {
|
|
1702
|
-
const gitignorePath2 =
|
|
1740
|
+
const gitignorePath2 = path12__namespace.join(dir, ".gitignore");
|
|
1703
1741
|
let raw;
|
|
1704
1742
|
try {
|
|
1705
|
-
raw =
|
|
1743
|
+
raw = fs11__namespace.readFileSync(gitignorePath2, "utf8");
|
|
1706
1744
|
} catch {
|
|
1707
1745
|
return [];
|
|
1708
1746
|
}
|
|
@@ -1829,7 +1867,7 @@ function isToolOwnedDirName(name) {
|
|
|
1829
1867
|
}
|
|
1830
1868
|
function hasSnapshotMarker(target) {
|
|
1831
1869
|
try {
|
|
1832
|
-
return
|
|
1870
|
+
return fs11__namespace.statSync(target).isDirectory() && fs11__namespace.existsSync(path12__namespace.join(target, ".codebase-snapshot"));
|
|
1833
1871
|
} catch {
|
|
1834
1872
|
return false;
|
|
1835
1873
|
}
|
|
@@ -1843,7 +1881,7 @@ function makeToolOwnedDecider(cwd, toolOwnedPaths) {
|
|
|
1843
1881
|
const prefix = parts.slice(0, depth).join("/");
|
|
1844
1882
|
let hasMarker = markers.get(prefix);
|
|
1845
1883
|
if (hasMarker === void 0) {
|
|
1846
|
-
hasMarker =
|
|
1884
|
+
hasMarker = fs11__namespace.existsSync(path12__namespace.join(cwd, prefix, ".codebase-snapshot"));
|
|
1847
1885
|
markers.set(prefix, hasMarker);
|
|
1848
1886
|
}
|
|
1849
1887
|
if (hasMarker) return true;
|
|
@@ -1858,7 +1896,7 @@ function makeLinkProbe() {
|
|
|
1858
1896
|
let known = cache.get(absolutePath);
|
|
1859
1897
|
if (known === void 0) {
|
|
1860
1898
|
try {
|
|
1861
|
-
known =
|
|
1899
|
+
known = fs11__namespace.lstatSync(absolutePath).isSymbolicLink();
|
|
1862
1900
|
} catch {
|
|
1863
1901
|
known = false;
|
|
1864
1902
|
}
|
|
@@ -1871,7 +1909,7 @@ function dropLinkedDirectories(cwd, candidates, skipped) {
|
|
|
1871
1909
|
const isLink = makeLinkProbe();
|
|
1872
1910
|
let rootReal = cwd;
|
|
1873
1911
|
try {
|
|
1874
|
-
rootReal =
|
|
1912
|
+
rootReal = fs11__namespace.realpathSync(cwd);
|
|
1875
1913
|
} catch {
|
|
1876
1914
|
}
|
|
1877
1915
|
const reported = /* @__PURE__ */ new Set();
|
|
@@ -1881,7 +1919,7 @@ function dropLinkedDirectories(cwd, candidates, skipped) {
|
|
|
1881
1919
|
let linkedDir = null;
|
|
1882
1920
|
for (let depth = 1; depth <= parts.length; depth += 1) {
|
|
1883
1921
|
const prefix = parts.slice(0, depth).join("/");
|
|
1884
|
-
if (isLink(
|
|
1922
|
+
if (isLink(path12__namespace.join(cwd, prefix))) {
|
|
1885
1923
|
linkedDir = prefix;
|
|
1886
1924
|
break;
|
|
1887
1925
|
}
|
|
@@ -1892,10 +1930,10 @@ function dropLinkedDirectories(cwd, candidates, skipped) {
|
|
|
1892
1930
|
}
|
|
1893
1931
|
if (reported.has(linkedDir)) continue;
|
|
1894
1932
|
reported.add(linkedDir);
|
|
1895
|
-
const absolute =
|
|
1933
|
+
const absolute = path12__namespace.join(cwd, linkedDir);
|
|
1896
1934
|
let targetReal = absolute;
|
|
1897
1935
|
try {
|
|
1898
|
-
targetReal =
|
|
1936
|
+
targetReal = fs11__namespace.realpathSync(absolute);
|
|
1899
1937
|
} catch {
|
|
1900
1938
|
}
|
|
1901
1939
|
const target = readLinkTarget(absolute, targetReal);
|
|
@@ -1972,7 +2010,7 @@ function collectFiles(cwd, options = {}) {
|
|
|
1972
2010
|
"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."
|
|
1973
2011
|
);
|
|
1974
2012
|
}
|
|
1975
|
-
if (
|
|
2013
|
+
if (fs11__namespace.existsSync(path12__namespace.join(cwd, ".git"))) {
|
|
1976
2014
|
throw new Error(
|
|
1977
2015
|
"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."
|
|
1978
2016
|
);
|
|
@@ -2031,7 +2069,7 @@ function collectFiles(cwd, options = {}) {
|
|
|
2031
2069
|
}
|
|
2032
2070
|
function rootRealFor(root) {
|
|
2033
2071
|
try {
|
|
2034
|
-
return
|
|
2072
|
+
return fs11__namespace.realpathSync(root);
|
|
2035
2073
|
} catch {
|
|
2036
2074
|
return root;
|
|
2037
2075
|
}
|
|
@@ -2039,18 +2077,18 @@ function rootRealFor(root) {
|
|
|
2039
2077
|
function walkDirectory(dir, baseDir, rootReal, visited, reservedPaths = /* @__PURE__ */ new Set(), parentGitignoreLayers = []) {
|
|
2040
2078
|
const results = { files: [], skipped: [] };
|
|
2041
2079
|
const stack = [];
|
|
2042
|
-
const relativeToBase = (target) =>
|
|
2080
|
+
const relativeToBase = (target) => path12__namespace.relative(baseDir, target).replace(/\\/g, "/") || ".";
|
|
2043
2081
|
const enter = (current, layers) => {
|
|
2044
2082
|
if (visited.has(current)) return false;
|
|
2045
2083
|
visited.add(current);
|
|
2046
2084
|
const localRules = readGitignoreRules(current);
|
|
2047
2085
|
const gitignoreLayers = localRules.length === 0 ? layers : [
|
|
2048
2086
|
...layers,
|
|
2049
|
-
{ prefix:
|
|
2087
|
+
{ prefix: path12__namespace.relative(baseDir, current).replace(/\\/g, "/"), rules: localRules }
|
|
2050
2088
|
];
|
|
2051
2089
|
let entries;
|
|
2052
2090
|
try {
|
|
2053
|
-
entries =
|
|
2091
|
+
entries = fs11__namespace.readdirSync(current, { withFileTypes: true });
|
|
2054
2092
|
} catch {
|
|
2055
2093
|
results.skipped.push({
|
|
2056
2094
|
path: relativeToBase(current),
|
|
@@ -2072,8 +2110,8 @@ function walkDirectory(dir, baseDir, rootReal, visited, reservedPaths = /* @__PU
|
|
|
2072
2110
|
}
|
|
2073
2111
|
const entry = frame.entries[frame.index];
|
|
2074
2112
|
frame.index += 1;
|
|
2075
|
-
const fullPath =
|
|
2076
|
-
const relPath =
|
|
2113
|
+
const fullPath = path12__namespace.join(frame.dir, entry.name);
|
|
2114
|
+
const relPath = path12__namespace.relative(baseDir, fullPath).replace(/\\/g, "/");
|
|
2077
2115
|
if (isToolOwnedDirName(entry.name) && reservedPaths.has(relPath)) continue;
|
|
2078
2116
|
if (DEFAULT_IGNORED_DIRS.includes(entry.name)) {
|
|
2079
2117
|
results.skipped.push({
|
|
@@ -2106,14 +2144,14 @@ function walkDirectory(dir, baseDir, rootReal, visited, reservedPaths = /* @__PU
|
|
|
2106
2144
|
if (entry.isSymbolicLink()) {
|
|
2107
2145
|
let targetReal;
|
|
2108
2146
|
try {
|
|
2109
|
-
targetReal =
|
|
2147
|
+
targetReal = fs11__namespace.realpathSync(fullPath);
|
|
2110
2148
|
} catch {
|
|
2111
2149
|
results.skipped.push({ path: relPath, reason: "read-error", detail: "broken symlink" });
|
|
2112
2150
|
continue;
|
|
2113
2151
|
}
|
|
2114
2152
|
let stats;
|
|
2115
2153
|
try {
|
|
2116
|
-
stats =
|
|
2154
|
+
stats = fs11__namespace.statSync(fullPath);
|
|
2117
2155
|
} catch {
|
|
2118
2156
|
results.skipped.push({ path: relPath, reason: "read-error", detail: "unreadable symlink" });
|
|
2119
2157
|
continue;
|
|
@@ -2164,6 +2202,20 @@ var CONFIG_FILENAMES = [
|
|
|
2164
2202
|
".snapshotrc.json",
|
|
2165
2203
|
".snapshotrc"
|
|
2166
2204
|
];
|
|
2205
|
+
function findConfigFile(cwd) {
|
|
2206
|
+
for (const filename of CONFIG_FILENAMES) {
|
|
2207
|
+
const fullPath = path12__namespace.join(cwd, filename);
|
|
2208
|
+
if (fs11__namespace.existsSync(fullPath)) return fullPath;
|
|
2209
|
+
}
|
|
2210
|
+
const pkgJsonPath = path12__namespace.join(cwd, "package.json");
|
|
2211
|
+
if (!fs11__namespace.existsSync(pkgJsonPath)) return null;
|
|
2212
|
+
try {
|
|
2213
|
+
const parsed = JSON.parse(fs11__namespace.readFileSync(pkgJsonPath, "utf8"));
|
|
2214
|
+
return parsed.snapshot === void 0 ? null : pkgJsonPath;
|
|
2215
|
+
} catch {
|
|
2216
|
+
return null;
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2167
2219
|
var RESOLVED_CONFIG_BRAND = /* @__PURE__ */ Symbol.for(
|
|
2168
2220
|
"codebase-snapshot/resolved-config"
|
|
2169
2221
|
);
|
|
@@ -2409,11 +2461,11 @@ async function loadConfigFileDetailed(cwd) {
|
|
|
2409
2461
|
const warnings = [];
|
|
2410
2462
|
const errors = [];
|
|
2411
2463
|
for (const filename of CONFIG_FILENAMES) {
|
|
2412
|
-
const fullPath =
|
|
2413
|
-
if (!
|
|
2464
|
+
const fullPath = path12__namespace.join(cwd, filename);
|
|
2465
|
+
if (!fs11__namespace.existsSync(fullPath)) continue;
|
|
2414
2466
|
if (filename.endsWith(".json") || filename === ".snapshotrc") {
|
|
2415
2467
|
try {
|
|
2416
|
-
const config = parseJsonish(
|
|
2468
|
+
const config = parseJsonish(fs11__namespace.readFileSync(fullPath, "utf8"), fullPath);
|
|
2417
2469
|
if (config === null || typeof config !== "object" || Array.isArray(config)) {
|
|
2418
2470
|
throw new Error(
|
|
2419
2471
|
`${fullPath} does not hold a configuration object (received ${describeValue2(config)}).`
|
|
@@ -2447,10 +2499,10 @@ async function loadConfigFileDetailed(cwd) {
|
|
|
2447
2499
|
continue;
|
|
2448
2500
|
}
|
|
2449
2501
|
}
|
|
2450
|
-
const pkgJsonPath =
|
|
2451
|
-
if (
|
|
2502
|
+
const pkgJsonPath = path12__namespace.join(cwd, "package.json");
|
|
2503
|
+
if (fs11__namespace.existsSync(pkgJsonPath)) {
|
|
2452
2504
|
try {
|
|
2453
|
-
const parsed = parseJsonish(
|
|
2505
|
+
const parsed = parseJsonish(fs11__namespace.readFileSync(pkgJsonPath, "utf8"), pkgJsonPath);
|
|
2454
2506
|
const snapshotKey = parsed.snapshot;
|
|
2455
2507
|
if (snapshotKey && typeof snapshotKey === "object" && !Array.isArray(snapshotKey)) {
|
|
2456
2508
|
return { config: snapshotKey, file: pkgJsonPath, warnings, errors };
|
|
@@ -2618,12 +2670,12 @@ function resolveKindRules(value) {
|
|
|
2618
2670
|
function outputExcludePatterns(cwd, outDir, tmpDir) {
|
|
2619
2671
|
const patterns = [];
|
|
2620
2672
|
for (const dir of [outDir, tmpDir]) {
|
|
2621
|
-
const absolute =
|
|
2622
|
-
const
|
|
2623
|
-
if (!
|
|
2673
|
+
const absolute = path12__namespace.resolve(cwd, dir);
|
|
2674
|
+
const relative11 = path12__namespace.relative(cwd, absolute);
|
|
2675
|
+
if (!relative11 || relative11 === ".." || relative11.startsWith(`..${path12__namespace.sep}`) || path12__namespace.isAbsolute(relative11)) {
|
|
2624
2676
|
continue;
|
|
2625
2677
|
}
|
|
2626
|
-
patterns.push(`${
|
|
2678
|
+
patterns.push(`${relative11.replace(/\\/g, "/")}/**`);
|
|
2627
2679
|
}
|
|
2628
2680
|
return patterns;
|
|
2629
2681
|
}
|
|
@@ -2739,11 +2791,11 @@ function resolveCandidateCwd(userConfig, cliOptions, baseDir) {
|
|
|
2739
2791
|
);
|
|
2740
2792
|
}
|
|
2741
2793
|
const candidate = cliOptions?.cwd || userConfig?.cwd || baseDir || process.cwd();
|
|
2742
|
-
const resolved =
|
|
2743
|
-
if (!
|
|
2794
|
+
const resolved = path12__namespace.resolve(candidate);
|
|
2795
|
+
if (!fs11__namespace.existsSync(resolved)) {
|
|
2744
2796
|
throw new Error(`Working directory does not exist: ${resolved}`);
|
|
2745
2797
|
}
|
|
2746
|
-
if (!
|
|
2798
|
+
if (!fs11__namespace.statSync(resolved).isDirectory()) {
|
|
2747
2799
|
throw new Error(`Working directory is not a directory: ${resolved}`);
|
|
2748
2800
|
}
|
|
2749
2801
|
return resolved;
|
|
@@ -2969,7 +3021,7 @@ var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
2969
3021
|
".so"
|
|
2970
3022
|
]);
|
|
2971
3023
|
function isBinaryExtension(filePath) {
|
|
2972
|
-
const ext =
|
|
3024
|
+
const ext = path12__namespace.extname(filePath).toLowerCase();
|
|
2973
3025
|
return BINARY_EXTENSIONS.has(ext);
|
|
2974
3026
|
}
|
|
2975
3027
|
function countControlBytes(buffer) {
|
|
@@ -3136,13 +3188,13 @@ function isBinaryFile(absolutePath) {
|
|
|
3136
3188
|
if (isBinaryExtension(absolutePath)) return true;
|
|
3137
3189
|
let fd;
|
|
3138
3190
|
try {
|
|
3139
|
-
fd =
|
|
3191
|
+
fd = fs11__namespace.openSync(absolutePath, "r");
|
|
3140
3192
|
const buffer = Buffer.alloc(CHUNK);
|
|
3141
3193
|
let sample;
|
|
3142
3194
|
let position = 0;
|
|
3143
3195
|
while (position < BINARY_SCAN_BYTES) {
|
|
3144
3196
|
const wanted = Math.min(CHUNK, BINARY_SCAN_BYTES - position);
|
|
3145
|
-
const bytesRead =
|
|
3197
|
+
const bytesRead = fs11__namespace.readSync(fd, buffer, 0, wanted, position);
|
|
3146
3198
|
if (bytesRead === 0) return false;
|
|
3147
3199
|
const chunk = buffer.subarray(0, bytesRead);
|
|
3148
3200
|
if (sample === void 0) {
|
|
@@ -3160,7 +3212,7 @@ function isBinaryFile(absolutePath) {
|
|
|
3160
3212
|
} finally {
|
|
3161
3213
|
if (fd !== void 0) {
|
|
3162
3214
|
try {
|
|
3163
|
-
|
|
3215
|
+
fs11__namespace.closeSync(fd);
|
|
3164
3216
|
} catch {
|
|
3165
3217
|
}
|
|
3166
3218
|
}
|
|
@@ -3173,33 +3225,33 @@ function maskedPath(value, secretRules) {
|
|
|
3173
3225
|
}
|
|
3174
3226
|
function confineToDirectory(directory, filename) {
|
|
3175
3227
|
if (filename.includes("\0")) return null;
|
|
3176
|
-
if (
|
|
3177
|
-
const base =
|
|
3178
|
-
const resolved =
|
|
3228
|
+
if (path12__namespace.isAbsolute(filename)) return null;
|
|
3229
|
+
const base = path12__namespace.resolve(directory);
|
|
3230
|
+
const resolved = path12__namespace.resolve(base, filename);
|
|
3179
3231
|
const caseInsensitive = process.platform === "win32" || process.platform === "darwin";
|
|
3180
|
-
const prefix = caseInsensitive ? `${base}${
|
|
3232
|
+
const prefix = caseInsensitive ? `${base}${path12__namespace.sep}`.toLowerCase() : `${base}${path12__namespace.sep}`;
|
|
3181
3233
|
const candidate = caseInsensitive ? resolved.toLowerCase() : resolved;
|
|
3182
3234
|
return candidate.startsWith(prefix) ? resolved : null;
|
|
3183
3235
|
}
|
|
3184
3236
|
function findStagingDirectory(config) {
|
|
3185
|
-
const tmpDir =
|
|
3237
|
+
const tmpDir = path12__namespace.resolve(config.cwd, config.tmpDir);
|
|
3186
3238
|
let entries;
|
|
3187
3239
|
try {
|
|
3188
|
-
entries =
|
|
3240
|
+
entries = fs11__namespace.readdirSync(tmpDir);
|
|
3189
3241
|
} catch {
|
|
3190
3242
|
return null;
|
|
3191
3243
|
}
|
|
3192
3244
|
const runs = entries.filter((name) => name.startsWith("run-")).sort();
|
|
3193
3245
|
const newest = runs[runs.length - 1];
|
|
3194
|
-
return newest ?
|
|
3246
|
+
return newest ? path12__namespace.join(tmpDir, newest) : null;
|
|
3195
3247
|
}
|
|
3196
3248
|
function describeMissingManifest(config, manifestPath) {
|
|
3197
|
-
const outDir =
|
|
3198
|
-
const markerPath =
|
|
3249
|
+
const outDir = path12__namespace.resolve(config.cwd, config.outDir);
|
|
3250
|
+
const markerPath = path12__namespace.join(outDir, ".codebase-snapshot");
|
|
3199
3251
|
let marker2 = null;
|
|
3200
3252
|
try {
|
|
3201
|
-
if (
|
|
3202
|
-
const parsed = JSON.parse(
|
|
3253
|
+
if (fs11__namespace.existsSync(markerPath)) {
|
|
3254
|
+
const parsed = JSON.parse(fs11__namespace.readFileSync(markerPath, "utf8"));
|
|
3203
3255
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
3204
3256
|
marker2 = parsed;
|
|
3205
3257
|
}
|
|
@@ -3235,14 +3287,14 @@ function sha256Hex(buffer) {
|
|
|
3235
3287
|
}
|
|
3236
3288
|
var VERIFY_SNIFF_MAX_BYTES = 8 * 1024 * 1024;
|
|
3237
3289
|
function wouldNowBeIncluded(cwd, relativePath, reason) {
|
|
3238
|
-
const absolutePath =
|
|
3290
|
+
const absolutePath = path12__namespace.join(cwd, relativePath);
|
|
3239
3291
|
try {
|
|
3240
|
-
const stats =
|
|
3292
|
+
const stats = fs11__namespace.statSync(absolutePath);
|
|
3241
3293
|
if (!stats.isFile()) return false;
|
|
3242
3294
|
if (isBinaryFile(absolutePath)) return false;
|
|
3243
3295
|
if (reason !== "binary") return true;
|
|
3244
3296
|
if (stats.size > VERIFY_SNIFF_MAX_BYTES) return false;
|
|
3245
|
-
return !decodeText(
|
|
3297
|
+
return !decodeText(fs11__namespace.readFileSync(absolutePath)).binary;
|
|
3246
3298
|
} catch {
|
|
3247
3299
|
return false;
|
|
3248
3300
|
}
|
|
@@ -3300,8 +3352,8 @@ function describeManifestShapeProblem(value) {
|
|
|
3300
3352
|
function validateSnapshot(configInput) {
|
|
3301
3353
|
assertUsableConfigInput(configInput);
|
|
3302
3354
|
const config = isResolvedConfig(configInput) ? normalizeResolvedConfig(configInput) : resolveConfig(configInput);
|
|
3303
|
-
const manifestPath =
|
|
3304
|
-
if (!
|
|
3355
|
+
const manifestPath = path12__namespace.resolve(config.cwd, config.outDir, "manifest.json");
|
|
3356
|
+
if (!fs11__namespace.existsSync(manifestPath)) {
|
|
3305
3357
|
return {
|
|
3306
3358
|
valid: false,
|
|
3307
3359
|
manifestPresent: false,
|
|
@@ -3312,7 +3364,7 @@ function validateSnapshot(configInput) {
|
|
|
3312
3364
|
}
|
|
3313
3365
|
let manifest;
|
|
3314
3366
|
try {
|
|
3315
|
-
manifest = JSON.parse(
|
|
3367
|
+
manifest = JSON.parse(fs11__namespace.readFileSync(manifestPath, "utf8"));
|
|
3316
3368
|
} catch (err) {
|
|
3317
3369
|
return {
|
|
3318
3370
|
valid: false,
|
|
@@ -3334,7 +3386,7 @@ function validateSnapshot(configInput) {
|
|
|
3334
3386
|
}
|
|
3335
3387
|
const drift = emptyDrift();
|
|
3336
3388
|
const warnings = [];
|
|
3337
|
-
const snapshotDir =
|
|
3389
|
+
const snapshotDir = path12__namespace.resolve(config.cwd, config.outDir);
|
|
3338
3390
|
const manifestFileMap = /* @__PURE__ */ new Map();
|
|
3339
3391
|
for (const file of manifest.files) {
|
|
3340
3392
|
if (file && typeof file.relativePath === "string") {
|
|
@@ -3368,7 +3420,7 @@ function validateSnapshot(configInput) {
|
|
|
3368
3420
|
}
|
|
3369
3421
|
let diskHash;
|
|
3370
3422
|
try {
|
|
3371
|
-
diskHash = sha256Hex(
|
|
3423
|
+
diskHash = sha256Hex(fs11__namespace.readFileSync(path12__namespace.join(config.cwd, diskFile)));
|
|
3372
3424
|
} catch (err) {
|
|
3373
3425
|
if (err.code === "ENOENT") {
|
|
3374
3426
|
drift.missingOnDisk.push(diskFile);
|
|
@@ -3392,7 +3444,7 @@ function validateSnapshot(configInput) {
|
|
|
3392
3444
|
}
|
|
3393
3445
|
let statFailure = null;
|
|
3394
3446
|
try {
|
|
3395
|
-
|
|
3447
|
+
fs11__namespace.statSync(confined);
|
|
3396
3448
|
} catch (err) {
|
|
3397
3449
|
statFailure = err;
|
|
3398
3450
|
}
|
|
@@ -3412,7 +3464,7 @@ function validateSnapshot(configInput) {
|
|
|
3412
3464
|
...Object.values(config.budgets.byLayer)
|
|
3413
3465
|
);
|
|
3414
3466
|
const partNames = /* @__PURE__ */ new Set();
|
|
3415
|
-
const partsDir =
|
|
3467
|
+
const partsDir = path12__namespace.join(snapshotDir, "parts");
|
|
3416
3468
|
for (const part of manifest.parts) {
|
|
3417
3469
|
if (!part || typeof part.filename !== "string") continue;
|
|
3418
3470
|
partNames.add(part.filename);
|
|
@@ -3427,7 +3479,7 @@ function validateSnapshot(configInput) {
|
|
|
3427
3479
|
}
|
|
3428
3480
|
let buffer;
|
|
3429
3481
|
try {
|
|
3430
|
-
buffer =
|
|
3482
|
+
buffer = fs11__namespace.readFileSync(partPath);
|
|
3431
3483
|
} catch {
|
|
3432
3484
|
drift.partIssues.push({ part: part.filename, issue: "missing from snapshot/parts" });
|
|
3433
3485
|
continue;
|
|
@@ -3641,9 +3693,9 @@ function validateSnapshot(configInput) {
|
|
|
3641
3693
|
["full-codebase.md", manifest.artifacts?.fullMarkdown]
|
|
3642
3694
|
]) {
|
|
3643
3695
|
if (!artifact) continue;
|
|
3644
|
-
const artifactPath =
|
|
3696
|
+
const artifactPath = path12__namespace.join(snapshotDir, key);
|
|
3645
3697
|
try {
|
|
3646
|
-
const buffer =
|
|
3698
|
+
const buffer = fs11__namespace.readFileSync(artifactPath);
|
|
3647
3699
|
if (sha256Hex(buffer) !== artifact.sha256 || buffer.length !== artifact.byteSize) {
|
|
3648
3700
|
drift.partIssues.push({ part: key, issue: "artifact does not match its recorded digest" });
|
|
3649
3701
|
}
|
|
@@ -3904,14 +3956,14 @@ function compareManifests(before, after) {
|
|
|
3904
3956
|
function loadManifest(target) {
|
|
3905
3957
|
let isDirectory;
|
|
3906
3958
|
try {
|
|
3907
|
-
isDirectory =
|
|
3959
|
+
isDirectory = fs11__namespace.statSync(target).isDirectory();
|
|
3908
3960
|
} catch {
|
|
3909
3961
|
throw new Error(`No snapshot found at ${target}: the path does not exist.`);
|
|
3910
3962
|
}
|
|
3911
|
-
const manifestPath = isDirectory ?
|
|
3963
|
+
const manifestPath = isDirectory ? path12__namespace.join(target, "manifest.json") : target;
|
|
3912
3964
|
let raw;
|
|
3913
3965
|
try {
|
|
3914
|
-
raw =
|
|
3966
|
+
raw = fs11__namespace.readFileSync(manifestPath, "utf8");
|
|
3915
3967
|
} catch {
|
|
3916
3968
|
throw new Error(`Could not read a manifest at ${manifestPath}.`);
|
|
3917
3969
|
}
|
|
@@ -4062,10 +4114,10 @@ var LANGUAGE_BY_BASENAME = {
|
|
|
4062
4114
|
};
|
|
4063
4115
|
function detectLanguage(filePath) {
|
|
4064
4116
|
const normalized = filePath.replace(/\\/g, "/");
|
|
4065
|
-
const base =
|
|
4117
|
+
const base = path12__namespace.posix.basename(normalized);
|
|
4066
4118
|
if (LANGUAGE_BY_BASENAME[base]) return LANGUAGE_BY_BASENAME[base];
|
|
4067
4119
|
if (base === ".env" || base.startsWith(".env.")) return "dotenv";
|
|
4068
|
-
const ext =
|
|
4120
|
+
const ext = path12__namespace.posix.extname(base).toLowerCase();
|
|
4069
4121
|
return LANGUAGE_BY_EXTENSION[ext] ?? "text";
|
|
4070
4122
|
}
|
|
4071
4123
|
|
|
@@ -4107,42 +4159,42 @@ function describeMatcher(matcher) {
|
|
|
4107
4159
|
return String(matcher);
|
|
4108
4160
|
}
|
|
4109
4161
|
function normalizeTarget(cwd, target) {
|
|
4110
|
-
const absolute =
|
|
4111
|
-
const
|
|
4112
|
-
if (
|
|
4113
|
-
return
|
|
4162
|
+
const absolute = path12__namespace.resolve(cwd, target);
|
|
4163
|
+
const relative11 = path12__namespace.relative(cwd, absolute);
|
|
4164
|
+
if (relative11.startsWith("..") || path12__namespace.isAbsolute(relative11)) return target;
|
|
4165
|
+
return relative11.split(path12__namespace.sep).join("/");
|
|
4114
4166
|
}
|
|
4115
4167
|
function findRuleMatch(rules, relativePath) {
|
|
4116
4168
|
return rules.find(
|
|
4117
4169
|
(rule) => matchRule(relativePath, rule.match)
|
|
4118
4170
|
);
|
|
4119
4171
|
}
|
|
4120
|
-
function findRecordedAncestor(skipped,
|
|
4172
|
+
function findRecordedAncestor(skipped, relative11, maskedPath2) {
|
|
4121
4173
|
let nearest;
|
|
4122
4174
|
for (const record of skipped) {
|
|
4123
4175
|
const ancestorPath = record.path;
|
|
4124
4176
|
if (ancestorPath.length === 0) continue;
|
|
4125
|
-
const isAncestor =
|
|
4177
|
+
const isAncestor = relative11.startsWith(`${ancestorPath}/`) || maskedPath2.startsWith(`${ancestorPath}/`);
|
|
4126
4178
|
if (!isAncestor) continue;
|
|
4127
4179
|
if (!nearest || ancestorPath.length > nearest.path.length) nearest = record;
|
|
4128
4180
|
}
|
|
4129
4181
|
return nearest;
|
|
4130
4182
|
}
|
|
4131
4183
|
function readManifest2(config) {
|
|
4132
|
-
const manifestPath =
|
|
4184
|
+
const manifestPath = path12__namespace.resolve(config.cwd, config.outDir, "manifest.json");
|
|
4133
4185
|
try {
|
|
4134
|
-
return JSON.parse(
|
|
4186
|
+
return JSON.parse(fs11__namespace.readFileSync(manifestPath, "utf8"));
|
|
4135
4187
|
} catch {
|
|
4136
4188
|
return null;
|
|
4137
4189
|
}
|
|
4138
4190
|
}
|
|
4139
4191
|
function explainPath(config, target) {
|
|
4140
|
-
const
|
|
4141
|
-
const absolute =
|
|
4142
|
-
const maskedPath2 = findSecretInPath(
|
|
4192
|
+
const relative11 = normalizeTarget(config.cwd, target);
|
|
4193
|
+
const absolute = path12__namespace.join(config.cwd, relative11);
|
|
4194
|
+
const maskedPath2 = findSecretInPath(relative11, config.secretRules)?.masked ?? relative11;
|
|
4143
4195
|
const result = {
|
|
4144
4196
|
path: maskedPath2,
|
|
4145
|
-
exists:
|
|
4197
|
+
exists: fs11__namespace.existsSync(absolute),
|
|
4146
4198
|
included: false
|
|
4147
4199
|
};
|
|
4148
4200
|
const collection = collectFiles(config.cwd, {
|
|
@@ -4153,12 +4205,12 @@ function explainPath(config, target) {
|
|
|
4153
4205
|
// Same exclusion `generate` applies, so `explain` never says "not-discovered" about a file
|
|
4154
4206
|
// that is only missing because the walker dropped the tool's own output directory.
|
|
4155
4207
|
toolOwnedPaths: [config.outDir, config.tmpDir].map(
|
|
4156
|
-
(directory) =>
|
|
4157
|
-
).filter((
|
|
4208
|
+
(directory) => path12__namespace.relative(config.cwd, path12__namespace.resolve(config.cwd, directory)).replace(/\\/g, "/")
|
|
4209
|
+
).filter((relative12) => relative12.length > 0 && !relative12.startsWith(".."))
|
|
4158
4210
|
});
|
|
4159
|
-
if (!collection.files.includes(
|
|
4211
|
+
if (!collection.files.includes(relative11)) {
|
|
4160
4212
|
const record = collection.skipped.find(
|
|
4161
|
-
(entry2) => entry2.path ===
|
|
4213
|
+
(entry2) => entry2.path === relative11 || entry2.path === maskedPath2
|
|
4162
4214
|
);
|
|
4163
4215
|
if (record) {
|
|
4164
4216
|
result.reason = record.reason;
|
|
@@ -4167,7 +4219,7 @@ function explainPath(config, target) {
|
|
|
4167
4219
|
result.reason = "missing";
|
|
4168
4220
|
result.detail = "no such file or directory, relative to the configured cwd";
|
|
4169
4221
|
} else {
|
|
4170
|
-
const ancestor = findRecordedAncestor(collection.skipped,
|
|
4222
|
+
const ancestor = findRecordedAncestor(collection.skipped, relative11, maskedPath2);
|
|
4171
4223
|
if (ancestor) {
|
|
4172
4224
|
result.reason = ancestor.reason;
|
|
4173
4225
|
result.detail = ancestor.detail ? `${ancestor.detail} \u2014 inherited from the skipped directory \`${ancestor.path}\`` : `inside the skipped directory \`${ancestor.path}\``;
|
|
@@ -4185,15 +4237,15 @@ function explainPath(config, target) {
|
|
|
4185
4237
|
result.detail = "binary content; generate skips it and records `binary` in manifest.skipped[]";
|
|
4186
4238
|
return result;
|
|
4187
4239
|
}
|
|
4188
|
-
result.layer = classifyLayer(
|
|
4189
|
-
result.kind = classifyKind(
|
|
4190
|
-
result.language = detectLanguage(
|
|
4191
|
-
const layerRule = findRuleMatch(config.layers,
|
|
4240
|
+
result.layer = classifyLayer(relative11, config.layers);
|
|
4241
|
+
result.kind = classifyKind(relative11, config.kinds);
|
|
4242
|
+
result.language = detectLanguage(relative11);
|
|
4243
|
+
const layerRule = findRuleMatch(config.layers, relative11);
|
|
4192
4244
|
if (layerRule) result.layerMatch = describeMatcher(layerRule.match);
|
|
4193
|
-
const kindRule = findRuleMatch(config.kinds,
|
|
4245
|
+
const kindRule = findRuleMatch(config.kinds, relative11);
|
|
4194
4246
|
if (kindRule) result.kindMatch = describeMatcher(kindRule.match);
|
|
4195
|
-
if (
|
|
4196
|
-
const buffer =
|
|
4247
|
+
if (fs11__namespace.existsSync(absolute) && fs11__namespace.statSync(absolute).isFile()) {
|
|
4248
|
+
const buffer = fs11__namespace.readFileSync(absolute);
|
|
4197
4249
|
const decoded = decodeText(buffer);
|
|
4198
4250
|
if (decoded.binary) {
|
|
4199
4251
|
result.included = false;
|
|
@@ -4205,12 +4257,12 @@ function explainPath(config, target) {
|
|
|
4205
4257
|
result.bytes = buffer.length;
|
|
4206
4258
|
result.lines = countLines(text2);
|
|
4207
4259
|
if (decoded.encoding !== "utf-8") result.encoding = decoded.encoding;
|
|
4208
|
-
const { findings } = scanAndRedactSecrets(text2, config.secretRules,
|
|
4260
|
+
const { findings } = scanAndRedactSecrets(text2, config.secretRules, relative11);
|
|
4209
4261
|
result.secretCount = findings.length;
|
|
4210
4262
|
result.secrets = findings.slice(0, 20).map((finding) => ({ rules: finding.rules, line: finding.line }));
|
|
4211
4263
|
}
|
|
4212
4264
|
const manifest = readManifest2(config);
|
|
4213
|
-
const entry = Array.isArray(manifest?.files) ? manifest.files.find((file) => file.relativePath ===
|
|
4265
|
+
const entry = Array.isArray(manifest?.files) ? manifest.files.find((file) => file.relativePath === relative11) : void 0;
|
|
4214
4266
|
result.part = entry?.part ?? null;
|
|
4215
4267
|
result.inclusionMode = entry?.inclusionMode;
|
|
4216
4268
|
return result;
|
|
@@ -4252,15 +4304,15 @@ function renderExplainText(result) {
|
|
|
4252
4304
|
function appendGitignoreEntries(cwd, entries) {
|
|
4253
4305
|
const wanted = [...new Set(entries)].filter((entry) => entry.trim().length > 0);
|
|
4254
4306
|
if (wanted.length === 0) return { added: [], note: null };
|
|
4255
|
-
const ignorePath =
|
|
4307
|
+
const ignorePath = path12__namespace.join(cwd, ".gitignore");
|
|
4256
4308
|
const manual = `Add ${wanted.map((entry) => `"${entry}"`).join(" and ")} to your .gitignore yourself.`;
|
|
4257
4309
|
let existing = "";
|
|
4258
4310
|
try {
|
|
4259
|
-
if (
|
|
4260
|
-
if (
|
|
4311
|
+
if (fs11__namespace.existsSync(ignorePath)) {
|
|
4312
|
+
if (fs11__namespace.statSync(ignorePath).isDirectory()) {
|
|
4261
4313
|
return { added: [], note: `${ignorePath} is a directory, so it was not updated. ${manual}` };
|
|
4262
4314
|
}
|
|
4263
|
-
existing =
|
|
4315
|
+
existing = fs11__namespace.readFileSync(ignorePath, "utf8");
|
|
4264
4316
|
}
|
|
4265
4317
|
} catch (err) {
|
|
4266
4318
|
return {
|
|
@@ -4273,7 +4325,7 @@ function appendGitignoreEntries(cwd, entries) {
|
|
|
4273
4325
|
if (added.length === 0) return { added: [], note: null };
|
|
4274
4326
|
const prefix = existing.length === 0 || existing.endsWith("\n") ? "" : "\n";
|
|
4275
4327
|
try {
|
|
4276
|
-
|
|
4328
|
+
fs11__namespace.writeFileSync(ignorePath, `${existing}${prefix}${added.join("\n")}
|
|
4277
4329
|
`, "utf8");
|
|
4278
4330
|
} catch (err) {
|
|
4279
4331
|
return { added: [], note: `${err.message}. ${manual}` };
|
|
@@ -4281,12 +4333,12 @@ function appendGitignoreEntries(cwd, entries) {
|
|
|
4281
4333
|
return { added, note: null };
|
|
4282
4334
|
}
|
|
4283
4335
|
function gitignorePath(cwd, filePath) {
|
|
4284
|
-
return
|
|
4336
|
+
return path12__namespace.relative(cwd, filePath).split(path12__namespace.sep).join("/");
|
|
4285
4337
|
}
|
|
4286
4338
|
|
|
4287
4339
|
// src/version.ts
|
|
4288
4340
|
var GENERATOR_NAME = "codebase-snapshot";
|
|
4289
|
-
var GENERATOR_VERSION = "0.9.
|
|
4341
|
+
var GENERATOR_VERSION = "0.9.4";
|
|
4290
4342
|
var LEGACY_GENERATOR_NAMES = ["@ankurhalder/codebase-snapshot"];
|
|
4291
4343
|
function isOurGeneratorName(name) {
|
|
4292
4344
|
if (typeof name !== "string") return false;
|
|
@@ -4491,7 +4543,7 @@ function keyReader(input) {
|
|
|
4491
4543
|
next() {
|
|
4492
4544
|
const queued = queue.shift();
|
|
4493
4545
|
if (queued) return Promise.resolve(queued);
|
|
4494
|
-
return new Promise((
|
|
4546
|
+
return new Promise((resolve9) => waiters.push(resolve9));
|
|
4495
4547
|
},
|
|
4496
4548
|
close() {
|
|
4497
4549
|
input.off("keypress", onKey);
|
|
@@ -4674,10 +4726,10 @@ var CONFIG_CANDIDATES = [
|
|
|
4674
4726
|
];
|
|
4675
4727
|
function chooseConfigFileName(cwd) {
|
|
4676
4728
|
if (!supportsTypeScriptImports()) return "snapshot.config.mjs";
|
|
4677
|
-
const pkgPath =
|
|
4678
|
-
if (!
|
|
4729
|
+
const pkgPath = path12__namespace.join(cwd, "package.json");
|
|
4730
|
+
if (!fs11__namespace.existsSync(pkgPath)) return "snapshot.config.mjs";
|
|
4679
4731
|
try {
|
|
4680
|
-
const parsed = JSON.parse(
|
|
4732
|
+
const parsed = JSON.parse(fs11__namespace.readFileSync(pkgPath, "utf8"));
|
|
4681
4733
|
return parsed.type === "module" ? "snapshot.config.ts" : "snapshot.config.mjs";
|
|
4682
4734
|
} catch {
|
|
4683
4735
|
return "snapshot.config.mjs";
|
|
@@ -4691,7 +4743,7 @@ var SIZE_CHOICES = [
|
|
|
4691
4743
|
];
|
|
4692
4744
|
function packageJsonHasConfig(cwd) {
|
|
4693
4745
|
try {
|
|
4694
|
-
const parsed = JSON.parse(
|
|
4746
|
+
const parsed = JSON.parse(fs11__namespace.readFileSync(path12__namespace.join(cwd, "package.json"), "utf8"));
|
|
4695
4747
|
return typeof parsed.snapshot === "object" && parsed.snapshot !== null && !Array.isArray(parsed.snapshot);
|
|
4696
4748
|
} catch {
|
|
4697
4749
|
return false;
|
|
@@ -4712,7 +4764,7 @@ function artifactAnswers(options) {
|
|
|
4712
4764
|
}
|
|
4713
4765
|
function topLevelDirs(cwd) {
|
|
4714
4766
|
try {
|
|
4715
|
-
return
|
|
4767
|
+
return fs11__namespace.readdirSync(cwd, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules").map((entry) => entry.name).sort();
|
|
4716
4768
|
} catch {
|
|
4717
4769
|
return [];
|
|
4718
4770
|
}
|
|
@@ -4725,7 +4777,7 @@ function describeJsonValue(value) {
|
|
|
4725
4777
|
}
|
|
4726
4778
|
function writeFileOrExplain(target, content) {
|
|
4727
4779
|
try {
|
|
4728
|
-
|
|
4780
|
+
fs11__namespace.writeFileSync(target, content, "utf8");
|
|
4729
4781
|
} catch (err) {
|
|
4730
4782
|
const code = err.code;
|
|
4731
4783
|
const reason = code === "EACCES" || code === "EPERM" || code === "EROFS" ? `permission denied (${code})` : code === "EISDIR" ? "it is a directory" : `${err.message}`;
|
|
@@ -4736,7 +4788,7 @@ function canWriteModuleConfig(name, cwd) {
|
|
|
4736
4788
|
if (name.endsWith(".mjs") || name.endsWith(".ts")) return true;
|
|
4737
4789
|
if (!name.endsWith(".js")) return false;
|
|
4738
4790
|
try {
|
|
4739
|
-
const parsed = JSON.parse(
|
|
4791
|
+
const parsed = JSON.parse(fs11__namespace.readFileSync(path12__namespace.join(cwd, "package.json"), "utf8"));
|
|
4740
4792
|
return parsed.type === "module";
|
|
4741
4793
|
} catch {
|
|
4742
4794
|
return false;
|
|
@@ -4771,19 +4823,19 @@ function serializePackageJson(raw, value) {
|
|
|
4771
4823
|
return `${text2}${raw.endsWith("\n") ? crlf ? "\r\n" : "\n" : ""}`;
|
|
4772
4824
|
}
|
|
4773
4825
|
function updatePackageJson(cwd, notes) {
|
|
4774
|
-
const packagePath =
|
|
4826
|
+
const packagePath = path12__namespace.join(cwd, "package.json");
|
|
4775
4827
|
const unchanged = {
|
|
4776
4828
|
scripts: [],
|
|
4777
4829
|
dependency: { name: GENERATOR_NAME, range: null, added: false }
|
|
4778
4830
|
};
|
|
4779
|
-
if (!
|
|
4831
|
+
if (!fs11__namespace.existsSync(packagePath)) {
|
|
4780
4832
|
notes.push("No package.json found, so npm scripts and the devDependency were not added.");
|
|
4781
4833
|
return unchanged;
|
|
4782
4834
|
}
|
|
4783
4835
|
let raw;
|
|
4784
4836
|
let parsed;
|
|
4785
4837
|
try {
|
|
4786
|
-
raw =
|
|
4838
|
+
raw = fs11__namespace.readFileSync(packagePath, "utf8");
|
|
4787
4839
|
parsed = JSON.parse(raw);
|
|
4788
4840
|
} catch {
|
|
4789
4841
|
notes.push("Could not parse package.json; npm scripts and the devDependency were not added.");
|
|
@@ -4846,16 +4898,18 @@ function updatePackageJson(cwd, notes) {
|
|
|
4846
4898
|
return { scripts: added, dependency };
|
|
4847
4899
|
}
|
|
4848
4900
|
var GITIGNORE_ENTRIES = ["snapshot/", "snapshot.tmp/"];
|
|
4849
|
-
function resolvePresetChoice(
|
|
4901
|
+
function resolvePresetChoice(detectedName, presetName) {
|
|
4850
4902
|
return {
|
|
4851
4903
|
value: presetName,
|
|
4852
4904
|
label: presetName,
|
|
4853
|
-
hint: presetName ===
|
|
4905
|
+
hint: presetName === detectedName ? "detected" : void 0
|
|
4854
4906
|
};
|
|
4855
4907
|
}
|
|
4856
4908
|
async function runInit(options) {
|
|
4857
4909
|
const io = options.io ?? defaultIO();
|
|
4858
4910
|
const detected = detectPreset(options.cwd, options.preset);
|
|
4911
|
+
let reason = detected.reason;
|
|
4912
|
+
let fallback = detected.fallback;
|
|
4859
4913
|
const notes = [];
|
|
4860
4914
|
const answers = {
|
|
4861
4915
|
preset: detected.presetName,
|
|
@@ -4877,117 +4931,148 @@ async function runInit(options) {
|
|
|
4877
4931
|
if (interactive) {
|
|
4878
4932
|
io.output.write("\n codebase-snapshot\n");
|
|
4879
4933
|
io.output.write(" Turn this repository into an LLM-ready, redacted, byte-bounded snapshot.\n\n");
|
|
4880
|
-
|
|
4934
|
+
if (fallback) {
|
|
4935
|
+
io.output.write(" \u26A0 Could not tell what kind of project this is.\n");
|
|
4936
|
+
io.output.write(" No package.json, go.mod, Cargo.toml, pyproject.toml, Gemfile,\n");
|
|
4937
|
+
io.output.write(" composer.json, pom.xml, *.csproj, next.config.*, vite.config.*, *.tf\n");
|
|
4938
|
+
io.output.write(" or workspace marker was found here.\n\n");
|
|
4939
|
+
} else {
|
|
4940
|
+
io.output.write(` Detected ${detected.presetName}
|
|
4941
|
+
`);
|
|
4942
|
+
io.output.write(` Because ${reason}
|
|
4943
|
+
|
|
4881
4944
|
`);
|
|
4945
|
+
}
|
|
4882
4946
|
io.output.write(` Directory ${options.cwd}
|
|
4883
4947
|
|
|
4884
4948
|
`);
|
|
4885
|
-
const
|
|
4886
|
-
|
|
4949
|
+
const paceChoices = fallback ? [
|
|
4950
|
+
{ value: "choose", label: "Pick the closest preset", hint: "then generate with the defaults" },
|
|
4951
|
+
{ value: "recommended", label: "Use the defaults", hint: "generic \xB7 everything the scanner finds" },
|
|
4952
|
+
{ value: "walk", label: "Go through each setting", hint: "every question starts on its default" }
|
|
4953
|
+
] : [
|
|
4954
|
+
{
|
|
4955
|
+
value: "recommended",
|
|
4956
|
+
label: "Use the recommended setup",
|
|
4957
|
+
hint: `${detected.presetName} \xB7 config \xB7 npm scripts \xB7 install \xB7 generate`
|
|
4958
|
+
},
|
|
4959
|
+
{ value: "walk", label: "Go through each setting", hint: "every question starts on its default" },
|
|
4960
|
+
{ value: "choose", label: "Pick a different preset", hint: "everything else stays at the defaults" }
|
|
4961
|
+
];
|
|
4962
|
+
const pace = await select(io, "How should I set this up?", paceChoices, {
|
|
4963
|
+
defaultValue: fallback ? "choose" : "recommended"
|
|
4887
4964
|
});
|
|
4888
|
-
if (
|
|
4965
|
+
if (pace === "choose") {
|
|
4889
4966
|
answers.preset = await select(
|
|
4890
4967
|
io,
|
|
4891
4968
|
"What kind of project is this?",
|
|
4892
|
-
listPresetNames().map(
|
|
4969
|
+
listPresetNames().map(
|
|
4970
|
+
(name) => resolvePresetChoice(fallback ? null : detected.presetName, name)
|
|
4971
|
+
),
|
|
4972
|
+
{ defaultValue: answers.preset }
|
|
4893
4973
|
);
|
|
4894
4974
|
const chosen = detectPreset(options.cwd, answers.preset);
|
|
4895
4975
|
answers.preset = chosen.presetName;
|
|
4976
|
+
reason = null;
|
|
4977
|
+
fallback = false;
|
|
4896
4978
|
}
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
4979
|
+
if (pace === "walk") {
|
|
4980
|
+
const scope = await select(
|
|
4981
|
+
io,
|
|
4982
|
+
"What should go into the snapshot?",
|
|
4983
|
+
[
|
|
4984
|
+
{ value: "all", label: "Everything the scanner finds", hint: "recommended \xB7 respects .gitignore" },
|
|
4985
|
+
{ value: "folders", label: "Only specific folders\u2026" },
|
|
4986
|
+
{ value: "no-tests", label: "Everything, but skip tests" },
|
|
4987
|
+
{ value: "custom", label: "Let me write glob patterns" }
|
|
4988
|
+
],
|
|
4989
|
+
{ defaultValue: "all" }
|
|
4990
|
+
);
|
|
4991
|
+
if (scope === "folders") {
|
|
4992
|
+
const dirs = topLevelDirs(options.cwd).slice(0, 40);
|
|
4993
|
+
if (dirs.length === 0) {
|
|
4994
|
+
notes.push("No top-level directories found; the snapshot will include everything.");
|
|
4995
|
+
} else {
|
|
4996
|
+
const chosen = await multiSelect(
|
|
4997
|
+
io,
|
|
4998
|
+
"Select folders",
|
|
4999
|
+
dirs.map((name) => ({ value: name, label: `${name}/` })),
|
|
5000
|
+
{ defaults: dirs }
|
|
5001
|
+
);
|
|
5002
|
+
answers.include = chosen.map((name) => `${name}/**`);
|
|
5003
|
+
}
|
|
5004
|
+
} else if (scope === "no-tests") {
|
|
5005
|
+
answers.excludePatterns = [
|
|
5006
|
+
...answers.excludePatterns,
|
|
5007
|
+
"**/*.test.*",
|
|
5008
|
+
"**/*.spec.*",
|
|
5009
|
+
"tests",
|
|
5010
|
+
"test"
|
|
5011
|
+
];
|
|
5012
|
+
} else if (scope === "custom") {
|
|
5013
|
+
const raw = await text(io, "Glob patterns to include (comma separated)", {
|
|
5014
|
+
defaultValue: ""
|
|
5015
|
+
});
|
|
5016
|
+
answers.include = raw.split(",").map((value) => value.trim()).filter(Boolean);
|
|
5017
|
+
}
|
|
5018
|
+
const currentBudget = String(answers.maxPartBytes);
|
|
5019
|
+
const sizeChoice = await select(io, "How large should each part be?", SIZE_CHOICES, {
|
|
5020
|
+
defaultValue: SIZE_CHOICES.some((choice) => choice.value === currentBudget) ? currentBudget : "120k"
|
|
5021
|
+
});
|
|
5022
|
+
if (sizeChoice === "custom") {
|
|
5023
|
+
const raw = await text(io, "Part size (e.g. 150k, 1.5mb, 900000)", {
|
|
5024
|
+
defaultValue: String(answers.maxPartBytes),
|
|
5025
|
+
validate: (value) => parseByteSize(value) === void 0 ? "Not a valid size." : null
|
|
5026
|
+
});
|
|
5027
|
+
answers.maxPartBytes = parseByteSize(raw) ?? answers.maxPartBytes;
|
|
4912
5028
|
} else {
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
...answers.excludePatterns,
|
|
4924
|
-
"**/*.test.*",
|
|
4925
|
-
"**/*.spec.*",
|
|
4926
|
-
"tests",
|
|
4927
|
-
"test"
|
|
5029
|
+
answers.maxPartBytes = sizeChoice;
|
|
5030
|
+
}
|
|
5031
|
+
const artifactChoices = [
|
|
5032
|
+
{ value: "readme", label: "README.md", hint: "orientation page" },
|
|
5033
|
+
{ value: "manifest", label: "manifest.json", hint: "required by validate and report" },
|
|
5034
|
+
{ value: "parts", label: "parts/", hint: "the code payload" },
|
|
5035
|
+
{ value: "navigation", label: "reports/navigation.md", hint: "file \u2192 part index" },
|
|
5036
|
+
{ value: "projectTree", label: "reports/project-tree.md", hint: "ASCII tree" },
|
|
5037
|
+
{ value: "audit", label: "reports/audit.md", hint: "security and health findings" },
|
|
5038
|
+
{ value: "monolith", label: "full-codebase.json + .md", hint: "roughly doubles output" }
|
|
4928
5039
|
];
|
|
4929
|
-
|
|
4930
|
-
const
|
|
4931
|
-
|
|
4932
|
-
|
|
4933
|
-
|
|
4934
|
-
|
|
4935
|
-
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
answers.maxPartBytes = sizeChoice;
|
|
4946
|
-
}
|
|
4947
|
-
const artifactChoices = [
|
|
4948
|
-
{ value: "readme", label: "README.md", hint: "orientation page" },
|
|
4949
|
-
{ value: "manifest", label: "manifest.json", hint: "required by validate and report" },
|
|
4950
|
-
{ value: "parts", label: "parts/", hint: "the code payload" },
|
|
4951
|
-
{ value: "navigation", label: "reports/navigation.md", hint: "file \u2192 part index" },
|
|
4952
|
-
{ value: "projectTree", label: "reports/project-tree.md", hint: "ASCII tree" },
|
|
4953
|
-
{ value: "audit", label: "reports/audit.md", hint: "security and health findings" },
|
|
4954
|
-
{ value: "monolith", label: "full-codebase.json + .md", hint: "roughly doubles output" }
|
|
4955
|
-
];
|
|
4956
|
-
const presetEnabled = ARTIFACT_NAMES.filter((name) => answers.artifacts[name]);
|
|
4957
|
-
const selectedArtifacts = await multiSelect(
|
|
4958
|
-
io,
|
|
4959
|
-
"Which artifacts should be generated?",
|
|
4960
|
-
artifactChoices,
|
|
4961
|
-
{ defaults: presetEnabled }
|
|
4962
|
-
);
|
|
4963
|
-
for (const name of ARTIFACT_NAMES) answers.artifacts[name] = selectedArtifacts.includes(name);
|
|
4964
|
-
const enabledCount = ARTIFACT_NAMES.filter((name) => answers.artifacts[name]).length;
|
|
4965
|
-
answers.includeSensitiveFiles = await confirm(
|
|
4966
|
-
io,
|
|
4967
|
-
"Include credential files (.env, *.pem, .npmrc, .ssh/)?",
|
|
4968
|
-
{ defaultValue: false }
|
|
4969
|
-
);
|
|
4970
|
-
io.output.write("\n Review\n\n");
|
|
4971
|
-
io.output.write(` project ${answers.preset}
|
|
5040
|
+
const presetEnabled = ARTIFACT_NAMES.filter((name) => answers.artifacts[name]);
|
|
5041
|
+
const selectedArtifacts = await multiSelect(
|
|
5042
|
+
io,
|
|
5043
|
+
"Which artifacts should be generated?",
|
|
5044
|
+
artifactChoices,
|
|
5045
|
+
{ defaults: presetEnabled }
|
|
5046
|
+
);
|
|
5047
|
+
for (const name of ARTIFACT_NAMES) answers.artifacts[name] = selectedArtifacts.includes(name);
|
|
5048
|
+
const enabledCount = ARTIFACT_NAMES.filter((name) => answers.artifacts[name]).length;
|
|
5049
|
+
answers.includeSensitiveFiles = await confirm(
|
|
5050
|
+
io,
|
|
5051
|
+
"Include credential files (.env, *.pem, .npmrc, .ssh/)?",
|
|
5052
|
+
{ defaultValue: false }
|
|
5053
|
+
);
|
|
5054
|
+
io.output.write("\n Review\n\n");
|
|
5055
|
+
io.output.write(` project ${answers.preset}
|
|
4972
5056
|
`);
|
|
4973
|
-
|
|
5057
|
+
io.output.write(` include ${answers.include.length ? answers.include.join(", ") : "everything"}
|
|
4974
5058
|
`);
|
|
4975
|
-
|
|
5059
|
+
io.output.write(` part size ${String(answers.maxPartBytes)}
|
|
4976
5060
|
`);
|
|
4977
|
-
|
|
5061
|
+
io.output.write(` artifacts ${enabledCount} of ${ARTIFACT_NAMES.length}
|
|
4978
5062
|
`);
|
|
4979
|
-
|
|
5063
|
+
io.output.write(` credentials ${answers.includeSensitiveFiles ? "included" : "skipped"}
|
|
4980
5064
|
|
|
4981
5065
|
`);
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
5066
|
+
io.output.write(" Will write snapshot.config with your answers, plus every other setting\n");
|
|
5067
|
+
io.output.write(" at its default \u2014 so nothing is hidden behind the docs.\n\n");
|
|
5068
|
+
const proceed = await confirm(io, "Write this and continue?", { defaultValue: true });
|
|
5069
|
+
if (!proceed) throw new PromptCancelled();
|
|
5070
|
+
}
|
|
4986
5071
|
}
|
|
4987
5072
|
const configText = renderConfig(answers);
|
|
4988
5073
|
const preferredName = chooseConfigFileName(options.cwd);
|
|
4989
5074
|
const presentConfigs = CONFIG_CANDIDATES.filter(
|
|
4990
|
-
(name) =>
|
|
5075
|
+
(name) => fs11__namespace.existsSync(path12__namespace.join(options.cwd, name))
|
|
4991
5076
|
);
|
|
4992
5077
|
const present = presentConfigs[0] ?? null;
|
|
4993
5078
|
if (presentConfigs.length > 1) {
|
|
@@ -4996,7 +5081,7 @@ async function runInit(options) {
|
|
|
4996
5081
|
);
|
|
4997
5082
|
}
|
|
4998
5083
|
const configName = present !== null && options.force ? present : preferredName;
|
|
4999
|
-
const configPath =
|
|
5084
|
+
const configPath = path12__namespace.join(options.cwd, configName);
|
|
5000
5085
|
const reused = present !== null && options.force !== true && options.reuseConfig === true;
|
|
5001
5086
|
if (reused) notes.push(`Kept the existing ${present} instead of writing a new one.`);
|
|
5002
5087
|
if (options.dryRun) {
|
|
@@ -5006,6 +5091,8 @@ async function runInit(options) {
|
|
|
5006
5091
|
written: false,
|
|
5007
5092
|
reused,
|
|
5008
5093
|
preset: answers.preset,
|
|
5094
|
+
reason,
|
|
5095
|
+
fallback,
|
|
5009
5096
|
scripts: [],
|
|
5010
5097
|
gitignoreEntries: [],
|
|
5011
5098
|
dependency: { name: GENERATOR_NAME, range: `^${GENERATOR_VERSION}`, added: false },
|
|
@@ -5027,7 +5114,7 @@ async function runInit(options) {
|
|
|
5027
5114
|
'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.'
|
|
5028
5115
|
);
|
|
5029
5116
|
}
|
|
5030
|
-
if (
|
|
5117
|
+
if (fs11__namespace.existsSync(configPath) && !options.force && !reused) {
|
|
5031
5118
|
throw new Error(`${configName} already exists. Re-run with --force to replace it.`);
|
|
5032
5119
|
}
|
|
5033
5120
|
if (!reused) writeFileOrExplain(configPath, configText);
|
|
@@ -5040,12 +5127,56 @@ async function runInit(options) {
|
|
|
5040
5127
|
written: true,
|
|
5041
5128
|
reused,
|
|
5042
5129
|
preset: answers.preset,
|
|
5130
|
+
reason,
|
|
5131
|
+
fallback,
|
|
5043
5132
|
scripts,
|
|
5044
5133
|
gitignoreEntries: ignore.added,
|
|
5045
5134
|
dependency,
|
|
5046
5135
|
notes
|
|
5047
5136
|
};
|
|
5048
5137
|
}
|
|
5138
|
+
function describeTarget(cwd) {
|
|
5139
|
+
const relative11 = path12__namespace.relative(process.cwd(), cwd);
|
|
5140
|
+
if (relative11 === "") return "this directory";
|
|
5141
|
+
if (relative11.startsWith("..") || path12__namespace.isAbsolute(relative11)) return cwd;
|
|
5142
|
+
return relative11;
|
|
5143
|
+
}
|
|
5144
|
+
function firstRunMessage(reason, cwd) {
|
|
5145
|
+
const where = describeTarget(cwd);
|
|
5146
|
+
if (reason === "empty") {
|
|
5147
|
+
return `Nothing to snapshot: ${where} has no files. Run this inside the repository you want to snapshot, or pass --cwd.`;
|
|
5148
|
+
}
|
|
5149
|
+
return `No configuration found in ${where} and this session is not interactive, so nothing was generated.
|
|
5150
|
+
Choose one:
|
|
5151
|
+
${GENERATOR_NAME} --autopilot detect the project, set it up and generate, no questions
|
|
5152
|
+
${GENERATOR_NAME} init --yes write a config from the detected defaults, then generate
|
|
5153
|
+
${GENERATOR_NAME} init write a config interactively`;
|
|
5154
|
+
}
|
|
5155
|
+
function planFirstRun(facts) {
|
|
5156
|
+
if (facts.emptyTarget) return { action: "refuse", exitCode: 1, reason: "empty" };
|
|
5157
|
+
if (facts.autopilot) return { action: "setup", interactive: false };
|
|
5158
|
+
if (facts.hasConfig || facts.hasPriorSnapshot) return { action: "generate" };
|
|
5159
|
+
if (facts.interactive) return { action: "setup", interactive: true };
|
|
5160
|
+
return { action: "refuse", exitCode: 2, reason: "undecided" };
|
|
5161
|
+
}
|
|
5162
|
+
var NOT_CONTENT = /* @__PURE__ */ new Set([".git", ".hg", ".svn", ".bzr", ".DS_Store", "Thumbs.db"]);
|
|
5163
|
+
function isEffectivelyEmpty(cwd) {
|
|
5164
|
+
try {
|
|
5165
|
+
return fs11__namespace.readdirSync(cwd).every((entry) => NOT_CONTENT.has(entry));
|
|
5166
|
+
} catch {
|
|
5167
|
+
return false;
|
|
5168
|
+
}
|
|
5169
|
+
}
|
|
5170
|
+
function hasPriorSnapshot(cwd, outDir) {
|
|
5171
|
+
let parsed;
|
|
5172
|
+
try {
|
|
5173
|
+
parsed = JSON.parse(fs11__namespace.readFileSync(path12__namespace.join(cwd, outDir, ".codebase-snapshot"), "utf8"));
|
|
5174
|
+
} catch {
|
|
5175
|
+
return false;
|
|
5176
|
+
}
|
|
5177
|
+
if (parsed === null || typeof parsed !== "object") return false;
|
|
5178
|
+
return isOurGeneratorName(parsed.generator);
|
|
5179
|
+
}
|
|
5049
5180
|
|
|
5050
5181
|
// src/ast/profiles/shared.ts
|
|
5051
5182
|
var MAX_SUMMARY_CHARS = 1e6;
|
|
@@ -5269,9 +5400,9 @@ var JAVA_METHOD = /^\s+(?:public|private|protected)\s+(?:static\s+)?(?:final\s+)
|
|
|
5269
5400
|
var jvmProfile = {
|
|
5270
5401
|
id: "jvm",
|
|
5271
5402
|
extensions: [".java", ".kt", ".kts", ".scala"],
|
|
5272
|
-
summarize(
|
|
5403
|
+
summarize(path18, content) {
|
|
5273
5404
|
const summary = emptySummary();
|
|
5274
|
-
const lower =
|
|
5405
|
+
const lower = path18.toLowerCase();
|
|
5275
5406
|
const isJava = lower.endsWith(".java");
|
|
5276
5407
|
const isKotlin = lower.endsWith(".kt") || lower.endsWith(".kts");
|
|
5277
5408
|
eachLine(content, (line) => {
|
|
@@ -5724,16 +5855,16 @@ function summarizeTypeScriptSource(relativePath, content) {
|
|
|
5724
5855
|
// src/ast/profiles/sfc.ts
|
|
5725
5856
|
var SCRIPT_BLOCK = /<script[^>]*>([\s\S]*?)<\/script>/gi;
|
|
5726
5857
|
var ASTRO_FRONTMATTER = /^\uFEFF?---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*(?=\r?\n|$)/;
|
|
5727
|
-
function componentName(
|
|
5728
|
-
const base =
|
|
5858
|
+
function componentName(path18) {
|
|
5859
|
+
const base = path18.split("/").pop() ?? path18;
|
|
5729
5860
|
return base.replace(/\.[^.]+$/, "");
|
|
5730
5861
|
}
|
|
5731
5862
|
var sfcProfile = {
|
|
5732
5863
|
id: "sfc",
|
|
5733
5864
|
extensions: [".vue", ".svelte", ".astro"],
|
|
5734
|
-
summarize(
|
|
5865
|
+
summarize(path18, content) {
|
|
5735
5866
|
let script = "";
|
|
5736
|
-
if (
|
|
5867
|
+
if (path18.toLowerCase().endsWith(".astro")) {
|
|
5737
5868
|
const frontmatter = ASTRO_FRONTMATTER.exec(content);
|
|
5738
5869
|
if (frontmatter) script += `${frontmatter[1]}
|
|
5739
5870
|
`;
|
|
@@ -5742,7 +5873,7 @@ var sfcProfile = {
|
|
|
5742
5873
|
script += `${match[1]}
|
|
5743
5874
|
`;
|
|
5744
5875
|
}
|
|
5745
|
-
const name = componentName(
|
|
5876
|
+
const name = componentName(path18);
|
|
5746
5877
|
if (!script.trim()) {
|
|
5747
5878
|
const summary2 = emptySummary();
|
|
5748
5879
|
if (name) {
|
|
@@ -5776,15 +5907,15 @@ for (const profile of LANGUAGE_PROFILES) {
|
|
|
5776
5907
|
BY_EXTENSION.set(extension.toLowerCase(), profile);
|
|
5777
5908
|
}
|
|
5778
5909
|
}
|
|
5779
|
-
function profileForPath(
|
|
5780
|
-
const lower =
|
|
5910
|
+
function profileForPath(path18) {
|
|
5911
|
+
const lower = path18.toLowerCase();
|
|
5781
5912
|
const dot = lower.lastIndexOf(".");
|
|
5782
5913
|
if (dot === -1) return null;
|
|
5783
5914
|
return BY_EXTENSION.get(lower.slice(dot)) ?? null;
|
|
5784
5915
|
}
|
|
5785
|
-
function summarizeWithProfile(
|
|
5786
|
-
const profile = profileForPath(
|
|
5787
|
-
return profile ? profile.summarize(
|
|
5916
|
+
function summarizeWithProfile(path18, content) {
|
|
5917
|
+
const profile = profileForPath(path18);
|
|
5918
|
+
return profile ? profile.summarize(path18, content) : null;
|
|
5788
5919
|
}
|
|
5789
5920
|
|
|
5790
5921
|
// src/ast/structured.ts
|
|
@@ -6710,13 +6841,13 @@ function buildImportResolver(files) {
|
|
|
6710
6841
|
const relativePath = file.relativePath;
|
|
6711
6842
|
remember(byPath, relativePath, relativePath);
|
|
6712
6843
|
remember(byPathFolded, relativePath.toLowerCase(), relativePath);
|
|
6713
|
-
const extension =
|
|
6844
|
+
const extension = path12__namespace.posix.extname(relativePath);
|
|
6714
6845
|
if (!SOURCE_EXTENSIONS.has(extension)) continue;
|
|
6715
6846
|
const stem = relativePath.slice(0, -extension.length);
|
|
6716
6847
|
remember(byStem, stem, relativePath);
|
|
6717
6848
|
remember(byStemFolded, stem.toLowerCase(), relativePath);
|
|
6718
|
-
if (
|
|
6719
|
-
const directory =
|
|
6849
|
+
if (path12__namespace.posix.basename(stem) === "index") {
|
|
6850
|
+
const directory = path12__namespace.posix.dirname(stem);
|
|
6720
6851
|
remember(byStem, directory, relativePath);
|
|
6721
6852
|
remember(byStemFolded, directory.toLowerCase(), relativePath);
|
|
6722
6853
|
}
|
|
@@ -6725,8 +6856,8 @@ function buildImportResolver(files) {
|
|
|
6725
6856
|
}
|
|
6726
6857
|
function resolveSpecifier(fromFile, specifier, resolver) {
|
|
6727
6858
|
if (!RELATIVE_SPECIFIER.test(specifier)) return null;
|
|
6728
|
-
const joined =
|
|
6729
|
-
|
|
6859
|
+
const joined = path12__namespace.posix.normalize(
|
|
6860
|
+
path12__namespace.posix.join(path12__namespace.posix.dirname(fromFile), specifier)
|
|
6730
6861
|
);
|
|
6731
6862
|
const exact = (key) => resolver.byPath.get(key) ?? resolver.byPathFolded.get(key.toLowerCase()) ?? null;
|
|
6732
6863
|
const direct = exact(joined);
|
|
@@ -6819,11 +6950,11 @@ function verifyInMemory(files, parts, maxPartBytes) {
|
|
|
6819
6950
|
return errors;
|
|
6820
6951
|
}
|
|
6821
6952
|
function resolveSafeOutputDirs(config) {
|
|
6822
|
-
const cwd =
|
|
6823
|
-
const outDir =
|
|
6824
|
-
const tmpDir =
|
|
6825
|
-
const root =
|
|
6826
|
-
const contains = (parent, child) => child === parent || child.startsWith(`${parent}${
|
|
6953
|
+
const cwd = path12__namespace.resolve(config.cwd);
|
|
6954
|
+
const outDir = path12__namespace.resolve(cwd, config.outDir);
|
|
6955
|
+
const tmpDir = path12__namespace.resolve(cwd, config.tmpDir);
|
|
6956
|
+
const root = path12__namespace.parse(cwd).root;
|
|
6957
|
+
const contains = (parent, child) => child === parent || child.startsWith(`${parent}${path12__namespace.sep}`);
|
|
6827
6958
|
const reject = (reason) => {
|
|
6828
6959
|
throw new Error(
|
|
6829
6960
|
`Refusing to generate: ${reason}. outDir=${outDir} tmpDir=${tmpDir} cwd=${cwd}`
|
|
@@ -6879,14 +7010,14 @@ function listEntries(dir) {
|
|
|
6879
7010
|
const walk = (absolute, prefix) => {
|
|
6880
7011
|
let children;
|
|
6881
7012
|
try {
|
|
6882
|
-
children =
|
|
7013
|
+
children = fs11__namespace.readdirSync(absolute, { withFileTypes: true });
|
|
6883
7014
|
} catch {
|
|
6884
7015
|
return;
|
|
6885
7016
|
}
|
|
6886
7017
|
for (const child of children) {
|
|
6887
|
-
const
|
|
6888
|
-
entries.push(
|
|
6889
|
-
if (child.isDirectory()) walk(
|
|
7018
|
+
const relative11 = prefix === "" ? child.name : `${prefix}/${child.name}`;
|
|
7019
|
+
entries.push(relative11);
|
|
7020
|
+
if (child.isDirectory()) walk(path12__namespace.join(absolute, child.name), relative11);
|
|
6890
7021
|
}
|
|
6891
7022
|
};
|
|
6892
7023
|
walk(dir, "");
|
|
@@ -6903,28 +7034,28 @@ function markerPayload(dir, state, id, createdAt) {
|
|
|
6903
7034
|
pid: process.pid,
|
|
6904
7035
|
entries: listEntries(dir)
|
|
6905
7036
|
};
|
|
6906
|
-
const manifestPath =
|
|
6907
|
-
if (
|
|
6908
|
-
marker2.manifestSha256 = sha256Hex2(
|
|
7037
|
+
const manifestPath = path12__namespace.join(dir, "manifest.json");
|
|
7038
|
+
if (fs11__namespace.existsSync(manifestPath)) {
|
|
7039
|
+
marker2.manifestSha256 = sha256Hex2(fs11__namespace.readFileSync(manifestPath));
|
|
6909
7040
|
}
|
|
6910
7041
|
return marker2;
|
|
6911
7042
|
}
|
|
6912
7043
|
function writeOwnershipMarker(dir, marker2) {
|
|
6913
|
-
|
|
6914
|
-
|
|
7044
|
+
fs11__namespace.writeFileSync(
|
|
7045
|
+
path12__namespace.join(dir, OWNERSHIP_MARKER),
|
|
6915
7046
|
`${JSON.stringify(marker2, null, 2)}
|
|
6916
7047
|
`,
|
|
6917
7048
|
"utf8"
|
|
6918
7049
|
);
|
|
6919
7050
|
}
|
|
6920
7051
|
function readOwnershipMarker(dir) {
|
|
6921
|
-
const markerPath =
|
|
6922
|
-
if (!
|
|
7052
|
+
const markerPath = path12__namespace.join(dir, OWNERSHIP_MARKER);
|
|
7053
|
+
if (!fs11__namespace.existsSync(markerPath)) {
|
|
6923
7054
|
return { marker: null, problem: "it holds no .codebase-snapshot marker" };
|
|
6924
7055
|
}
|
|
6925
7056
|
let raw;
|
|
6926
7057
|
try {
|
|
6927
|
-
raw =
|
|
7058
|
+
raw = fs11__namespace.readFileSync(markerPath, "utf8");
|
|
6928
7059
|
} catch {
|
|
6929
7060
|
return { marker: null, problem: "its .codebase-snapshot marker could not be read" };
|
|
6930
7061
|
}
|
|
@@ -6992,11 +7123,11 @@ function assessSnapshotDirectory(dir) {
|
|
|
6992
7123
|
marker: marker2
|
|
6993
7124
|
};
|
|
6994
7125
|
}
|
|
6995
|
-
const manifestPath =
|
|
6996
|
-
if (
|
|
7126
|
+
const manifestPath = path12__namespace.join(dir, "manifest.json");
|
|
7127
|
+
if (fs11__namespace.existsSync(manifestPath)) {
|
|
6997
7128
|
let raw;
|
|
6998
7129
|
try {
|
|
6999
|
-
raw =
|
|
7130
|
+
raw = fs11__namespace.readFileSync(manifestPath, "utf8");
|
|
7000
7131
|
} catch {
|
|
7001
7132
|
return {
|
|
7002
7133
|
owned: false,
|
|
@@ -7078,13 +7209,13 @@ function containsNoFiles(dir) {
|
|
|
7078
7209
|
const current = pending.pop();
|
|
7079
7210
|
let entries;
|
|
7080
7211
|
try {
|
|
7081
|
-
entries =
|
|
7212
|
+
entries = fs11__namespace.readdirSync(current, { withFileTypes: true });
|
|
7082
7213
|
} catch {
|
|
7083
7214
|
return false;
|
|
7084
7215
|
}
|
|
7085
7216
|
for (const entry of entries) {
|
|
7086
7217
|
if (entry.isDirectory()) {
|
|
7087
|
-
pending.push(
|
|
7218
|
+
pending.push(path12__namespace.join(current, entry.name));
|
|
7088
7219
|
} else if (!IGNORABLE_ENTRIES.has(entry.name)) {
|
|
7089
7220
|
return false;
|
|
7090
7221
|
}
|
|
@@ -7093,13 +7224,13 @@ function containsNoFiles(dir) {
|
|
|
7093
7224
|
return true;
|
|
7094
7225
|
}
|
|
7095
7226
|
function assertReplaceable(target, label, secretRules) {
|
|
7096
|
-
if (!
|
|
7097
|
-
if (!
|
|
7227
|
+
if (!fs11__namespace.existsSync(target)) return;
|
|
7228
|
+
if (!fs11__namespace.statSync(target).isDirectory()) {
|
|
7098
7229
|
throw new Error(
|
|
7099
7230
|
`Refusing to write into ${label} ${target}: it exists but is not a directory.`
|
|
7100
7231
|
);
|
|
7101
7232
|
}
|
|
7102
|
-
const entries =
|
|
7233
|
+
const entries = fs11__namespace.readdirSync(target);
|
|
7103
7234
|
if (entries.length === 0) return;
|
|
7104
7235
|
if (entries.every((name) => IGNORABLE_ENTRIES.has(name))) return;
|
|
7105
7236
|
if (containsNoFiles(target)) return;
|
|
@@ -7119,18 +7250,18 @@ function assertReplaceable(target, label, secretRules) {
|
|
|
7119
7250
|
);
|
|
7120
7251
|
}
|
|
7121
7252
|
function assertStagingRootUsable(tmpDir, secretRules) {
|
|
7122
|
-
if (!
|
|
7123
|
-
if (!
|
|
7253
|
+
if (!fs11__namespace.existsSync(tmpDir)) return;
|
|
7254
|
+
if (!fs11__namespace.statSync(tmpDir).isDirectory()) {
|
|
7124
7255
|
throw new Error(`Refusing to write into tmpDir ${tmpDir}: it exists but is not a directory.`);
|
|
7125
7256
|
}
|
|
7126
|
-
const entries =
|
|
7257
|
+
const entries = fs11__namespace.readdirSync(tmpDir);
|
|
7127
7258
|
if (entries.length === 0) return;
|
|
7128
7259
|
const foreign = entries.filter((name) => {
|
|
7129
|
-
const full =
|
|
7260
|
+
const full = path12__namespace.join(tmpDir, name);
|
|
7130
7261
|
if (!name.startsWith(RUN_DIR_PREFIX)) return true;
|
|
7131
7262
|
let stats;
|
|
7132
7263
|
try {
|
|
7133
|
-
stats =
|
|
7264
|
+
stats = fs11__namespace.statSync(full);
|
|
7134
7265
|
} catch {
|
|
7135
7266
|
return true;
|
|
7136
7267
|
}
|
|
@@ -7138,7 +7269,7 @@ function assertStagingRootUsable(tmpDir, secretRules) {
|
|
|
7138
7269
|
const { marker: marker2 } = readOwnershipMarker(full);
|
|
7139
7270
|
if (marker2) return false;
|
|
7140
7271
|
try {
|
|
7141
|
-
return
|
|
7272
|
+
return fs11__namespace.readdirSync(full).length > 0;
|
|
7142
7273
|
} catch {
|
|
7143
7274
|
return true;
|
|
7144
7275
|
}
|
|
@@ -7153,13 +7284,13 @@ function assertStagingRootUsable(tmpDir, secretRules) {
|
|
|
7153
7284
|
function cleanStaleRunDirs(tmpDir) {
|
|
7154
7285
|
let entries;
|
|
7155
7286
|
try {
|
|
7156
|
-
entries =
|
|
7287
|
+
entries = fs11__namespace.readdirSync(tmpDir);
|
|
7157
7288
|
} catch {
|
|
7158
7289
|
return;
|
|
7159
7290
|
}
|
|
7160
7291
|
for (const name of entries) {
|
|
7161
7292
|
if (!name.startsWith(RUN_DIR_PREFIX)) continue;
|
|
7162
|
-
const full =
|
|
7293
|
+
const full = path12__namespace.join(tmpDir, name);
|
|
7163
7294
|
const { marker: marker2 } = readOwnershipMarker(full);
|
|
7164
7295
|
if (marker2) {
|
|
7165
7296
|
const created = Date.parse(marker2.createdAt);
|
|
@@ -7167,33 +7298,33 @@ function cleanStaleRunDirs(tmpDir) {
|
|
|
7167
7298
|
const abandoned = typeof marker2.pid === "number" && !isProcessAlive(marker2.pid);
|
|
7168
7299
|
if (aged || abandoned) {
|
|
7169
7300
|
try {
|
|
7170
|
-
|
|
7301
|
+
fs11__namespace.rmSync(full, { recursive: true, force: true });
|
|
7171
7302
|
} catch {
|
|
7172
7303
|
}
|
|
7173
7304
|
}
|
|
7174
7305
|
continue;
|
|
7175
7306
|
}
|
|
7176
7307
|
try {
|
|
7177
|
-
if (
|
|
7308
|
+
if (fs11__namespace.readdirSync(full).length === 0) fs11__namespace.rmSync(full, { recursive: true, force: true });
|
|
7178
7309
|
} catch {
|
|
7179
7310
|
}
|
|
7180
7311
|
}
|
|
7181
7312
|
}
|
|
7182
7313
|
function removeIfEmpty(dir) {
|
|
7183
7314
|
try {
|
|
7184
|
-
if (
|
|
7315
|
+
if (fs11__namespace.readdirSync(dir).length === 0) fs11__namespace.rmSync(dir, { recursive: true, force: true });
|
|
7185
7316
|
} catch {
|
|
7186
7317
|
}
|
|
7187
7318
|
}
|
|
7188
7319
|
function ensureParentDirectory(target, label) {
|
|
7189
|
-
const parent =
|
|
7190
|
-
if (
|
|
7320
|
+
const parent = path12__namespace.dirname(target);
|
|
7321
|
+
if (fs11__namespace.existsSync(parent) && !fs11__namespace.statSync(parent).isDirectory()) {
|
|
7191
7322
|
throw new Error(
|
|
7192
7323
|
`Refusing to create ${label} ${target}: ${parent} exists but is not a directory.`
|
|
7193
7324
|
);
|
|
7194
7325
|
}
|
|
7195
7326
|
try {
|
|
7196
|
-
|
|
7327
|
+
fs11__namespace.mkdirSync(parent, { recursive: true });
|
|
7197
7328
|
} catch (err) {
|
|
7198
7329
|
throw new Error(
|
|
7199
7330
|
`Refusing to create ${label} ${target}: its parent ${parent} could not be created (${err.message}).`
|
|
@@ -7202,7 +7333,7 @@ function ensureParentDirectory(target, label) {
|
|
|
7202
7333
|
}
|
|
7203
7334
|
function acquireRunLock(stagingRoot) {
|
|
7204
7335
|
const key = lockKeyFor(stagingRoot);
|
|
7205
|
-
const lockPath =
|
|
7336
|
+
const lockPath = path12__namespace.join(os__namespace.tmpdir(), `codebase-snapshot-${key}.lock`);
|
|
7206
7337
|
const payload = `${JSON.stringify({
|
|
7207
7338
|
pid: process.pid,
|
|
7208
7339
|
stagingRoot,
|
|
@@ -7211,22 +7342,22 @@ function acquireRunLock(stagingRoot) {
|
|
|
7211
7342
|
`;
|
|
7212
7343
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
7213
7344
|
try {
|
|
7214
|
-
const handle =
|
|
7345
|
+
const handle = fs11__namespace.openSync(lockPath, "wx");
|
|
7215
7346
|
try {
|
|
7216
|
-
|
|
7347
|
+
fs11__namespace.writeSync(handle, payload);
|
|
7217
7348
|
} finally {
|
|
7218
|
-
|
|
7349
|
+
fs11__namespace.closeSync(handle);
|
|
7219
7350
|
}
|
|
7220
7351
|
return () => {
|
|
7221
7352
|
try {
|
|
7222
|
-
|
|
7353
|
+
fs11__namespace.rmSync(lockPath, { force: true });
|
|
7223
7354
|
} catch {
|
|
7224
7355
|
}
|
|
7225
7356
|
};
|
|
7226
7357
|
} catch (err) {
|
|
7227
7358
|
if (err.code !== "EEXIST") throw err;
|
|
7228
7359
|
if (attempt === 0 && lockOwnerIsGone(lockPath)) {
|
|
7229
|
-
|
|
7360
|
+
fs11__namespace.rmSync(lockPath, { force: true });
|
|
7230
7361
|
continue;
|
|
7231
7362
|
}
|
|
7232
7363
|
throw new Error(
|
|
@@ -7239,7 +7370,7 @@ function acquireRunLock(stagingRoot) {
|
|
|
7239
7370
|
function lockKeyFor(stagingRoot) {
|
|
7240
7371
|
let key = stagingRoot;
|
|
7241
7372
|
try {
|
|
7242
|
-
key =
|
|
7373
|
+
key = fs11__namespace.realpathSync(stagingRoot);
|
|
7243
7374
|
} catch {
|
|
7244
7375
|
}
|
|
7245
7376
|
if (process.platform === "win32" || process.platform === "darwin") key = key.toLowerCase();
|
|
@@ -7248,7 +7379,7 @@ function lockKeyFor(stagingRoot) {
|
|
|
7248
7379
|
function lockOwnerIsGone(lockPath) {
|
|
7249
7380
|
let raw;
|
|
7250
7381
|
try {
|
|
7251
|
-
raw =
|
|
7382
|
+
raw = fs11__namespace.readFileSync(lockPath, "utf8");
|
|
7252
7383
|
} catch {
|
|
7253
7384
|
return false;
|
|
7254
7385
|
}
|
|
@@ -7268,12 +7399,12 @@ function lockOwnerIsGone(lockPath) {
|
|
|
7268
7399
|
}
|
|
7269
7400
|
function writeManifestAtomically(manifestPath, manifest) {
|
|
7270
7401
|
const tempPath = `${manifestPath}.${process.pid}.tmp`;
|
|
7271
|
-
|
|
7402
|
+
fs11__namespace.writeFileSync(tempPath, JSON.stringify(manifest, null, 2), "utf8");
|
|
7272
7403
|
try {
|
|
7273
|
-
|
|
7404
|
+
fs11__namespace.renameSync(tempPath, manifestPath);
|
|
7274
7405
|
} catch (err) {
|
|
7275
7406
|
try {
|
|
7276
|
-
|
|
7407
|
+
fs11__namespace.rmSync(tempPath, { force: true });
|
|
7277
7408
|
} catch {
|
|
7278
7409
|
}
|
|
7279
7410
|
throw err;
|
|
@@ -7296,31 +7427,31 @@ function swapIntoPlace(stagingDir, outDir) {
|
|
|
7296
7427
|
}
|
|
7297
7428
|
function swapOnce(stagingDir, outDir) {
|
|
7298
7429
|
const backupDir = `${outDir}.backup-${process.pid}-${Date.now()}`;
|
|
7299
|
-
const hadPrevious =
|
|
7300
|
-
if (hadPrevious)
|
|
7430
|
+
const hadPrevious = fs11__namespace.existsSync(outDir);
|
|
7431
|
+
if (hadPrevious) fs11__namespace.renameSync(outDir, backupDir);
|
|
7301
7432
|
try {
|
|
7302
7433
|
try {
|
|
7303
|
-
|
|
7434
|
+
fs11__namespace.renameSync(stagingDir, outDir);
|
|
7304
7435
|
} catch (err) {
|
|
7305
7436
|
if (err.code !== "EXDEV") throw err;
|
|
7306
7437
|
const partialDir = `${outDir}.partial-${process.pid}-${Date.now()}`;
|
|
7307
7438
|
try {
|
|
7308
|
-
|
|
7309
|
-
|
|
7310
|
-
|
|
7439
|
+
fs11__namespace.cpSync(stagingDir, partialDir, { recursive: true });
|
|
7440
|
+
fs11__namespace.renameSync(partialDir, outDir);
|
|
7441
|
+
fs11__namespace.rmSync(stagingDir, { recursive: true, force: true });
|
|
7311
7442
|
} catch (copyErr) {
|
|
7312
7443
|
try {
|
|
7313
|
-
|
|
7444
|
+
fs11__namespace.rmSync(partialDir, { recursive: true, force: true });
|
|
7314
7445
|
} catch {
|
|
7315
7446
|
}
|
|
7316
7447
|
throw copyErr;
|
|
7317
7448
|
}
|
|
7318
7449
|
}
|
|
7319
|
-
if (hadPrevious)
|
|
7450
|
+
if (hadPrevious) fs11__namespace.rmSync(backupDir, { recursive: true, force: true });
|
|
7320
7451
|
} catch (err) {
|
|
7321
|
-
if (hadPrevious &&
|
|
7322
|
-
if (
|
|
7323
|
-
|
|
7452
|
+
if (hadPrevious && fs11__namespace.existsSync(backupDir)) {
|
|
7453
|
+
if (fs11__namespace.existsSync(outDir)) fs11__namespace.rmSync(outDir, { recursive: true, force: true });
|
|
7454
|
+
fs11__namespace.renameSync(backupDir, outDir);
|
|
7324
7455
|
}
|
|
7325
7456
|
throw err;
|
|
7326
7457
|
}
|
|
@@ -7415,8 +7546,8 @@ function generateSnapshot(configInput, options) {
|
|
|
7415
7546
|
// first run cannot have marked them yet, so the name alone keeps a run from recording its own
|
|
7416
7547
|
// output — but only at these exact paths, not at every directory that shares the name.
|
|
7417
7548
|
toolOwnedPaths: [config.outDir, config.tmpDir].map(
|
|
7418
|
-
(directory) =>
|
|
7419
|
-
).filter((
|
|
7549
|
+
(directory) => path12__namespace.relative(config.cwd, path12__namespace.resolve(config.cwd, directory)).replace(/\\/g, "/")
|
|
7550
|
+
).filter((relative11) => relative11.length > 0 && !relative11.startsWith(".."))
|
|
7420
7551
|
});
|
|
7421
7552
|
const files = [];
|
|
7422
7553
|
const monolithBodies = /* @__PURE__ */ new Map();
|
|
@@ -7436,12 +7567,12 @@ function generateSnapshot(configInput, options) {
|
|
|
7436
7567
|
let totalSizeBytes = 0;
|
|
7437
7568
|
let largeFileCount = 0;
|
|
7438
7569
|
for (const relativePath of collection.files) {
|
|
7439
|
-
const absolutePath =
|
|
7570
|
+
const absolutePath = path12__namespace.join(config.cwd, relativePath);
|
|
7440
7571
|
let stats;
|
|
7441
7572
|
try {
|
|
7442
|
-
stats =
|
|
7573
|
+
stats = fs11__namespace.statSync(absolutePath);
|
|
7443
7574
|
if (!stats.isFile()) {
|
|
7444
|
-
const nestedRepository = stats.isDirectory() &&
|
|
7575
|
+
const nestedRepository = stats.isDirectory() && fs11__namespace.existsSync(path12__namespace.join(absolutePath, ".git"));
|
|
7445
7576
|
skipped.push(
|
|
7446
7577
|
nestedRepository ? {
|
|
7447
7578
|
path: relativePath,
|
|
@@ -7461,7 +7592,7 @@ function generateSnapshot(configInput, options) {
|
|
|
7461
7592
|
}
|
|
7462
7593
|
let buffer;
|
|
7463
7594
|
try {
|
|
7464
|
-
buffer =
|
|
7595
|
+
buffer = fs11__namespace.readFileSync(absolutePath);
|
|
7465
7596
|
} catch {
|
|
7466
7597
|
skipped.push({ path: relativePath, reason: "read-error" });
|
|
7467
7598
|
continue;
|
|
@@ -7791,12 +7922,12 @@ function generateSnapshot(configInput, options) {
|
|
|
7791
7922
|
let stagingDir = null;
|
|
7792
7923
|
try {
|
|
7793
7924
|
ensureParentDirectory(outDir, "outDir");
|
|
7794
|
-
|
|
7925
|
+
fs11__namespace.mkdirSync(tmpDir, { recursive: true });
|
|
7795
7926
|
cleanStaleRunDirs(tmpDir);
|
|
7796
7927
|
const snapshotId = crypto2__namespace.randomUUID();
|
|
7797
7928
|
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
7798
|
-
stagingDir =
|
|
7799
|
-
|
|
7929
|
+
stagingDir = path12__namespace.join(tmpDir, `${RUN_DIR_PREFIX}${process.pid}-${snapshotId.slice(0, 8)}`);
|
|
7930
|
+
fs11__namespace.mkdirSync(stagingDir);
|
|
7800
7931
|
writeOwnershipMarker(stagingDir, {
|
|
7801
7932
|
generator: GENERATOR_NAME,
|
|
7802
7933
|
version: GENERATOR_VERSION,
|
|
@@ -7806,41 +7937,41 @@ function generateSnapshot(configInput, options) {
|
|
|
7806
7937
|
id: snapshotId,
|
|
7807
7938
|
pid: process.pid
|
|
7808
7939
|
});
|
|
7809
|
-
const partsDir =
|
|
7810
|
-
const reportsDir =
|
|
7811
|
-
if (artifacts.parts)
|
|
7940
|
+
const partsDir = path12__namespace.join(stagingDir, "parts");
|
|
7941
|
+
const reportsDir = path12__namespace.join(stagingDir, "reports");
|
|
7942
|
+
if (artifacts.parts) fs11__namespace.mkdirSync(partsDir, { recursive: true });
|
|
7812
7943
|
if (artifacts.navigation || artifacts.projectTree || artifacts.audit) {
|
|
7813
|
-
|
|
7944
|
+
fs11__namespace.mkdirSync(reportsDir, { recursive: true });
|
|
7814
7945
|
}
|
|
7815
7946
|
if (artifacts.parts) {
|
|
7816
7947
|
for (const part of parts) {
|
|
7817
|
-
|
|
7948
|
+
fs11__namespace.writeFileSync(path12__namespace.join(partsDir, part.filename), part.markdown, "utf8");
|
|
7818
7949
|
}
|
|
7819
7950
|
}
|
|
7820
7951
|
if (projectTree !== null) {
|
|
7821
|
-
|
|
7952
|
+
fs11__namespace.writeFileSync(path12__namespace.join(reportsDir, "project-tree.md"), projectTree, "utf8");
|
|
7822
7953
|
}
|
|
7823
7954
|
if (artifacts.navigation) {
|
|
7824
|
-
|
|
7955
|
+
fs11__namespace.writeFileSync(path12__namespace.join(reportsDir, "navigation.md"), generateNavigation(manifest), "utf8");
|
|
7825
7956
|
}
|
|
7826
7957
|
if (artifacts.audit) {
|
|
7827
|
-
|
|
7828
|
-
|
|
7958
|
+
fs11__namespace.writeFileSync(
|
|
7959
|
+
path12__namespace.join(reportsDir, "audit.md"),
|
|
7829
7960
|
generateAuditReport(manifest, secretFindings, config.secretRules),
|
|
7830
7961
|
"utf8"
|
|
7831
7962
|
);
|
|
7832
7963
|
}
|
|
7833
7964
|
if (artifacts.readme) {
|
|
7834
|
-
|
|
7965
|
+
fs11__namespace.writeFileSync(path12__namespace.join(stagingDir, "README.md"), generateReadme(manifest, artifacts), "utf8");
|
|
7835
7966
|
}
|
|
7836
7967
|
if (artifacts.manifest) {
|
|
7837
|
-
|
|
7968
|
+
fs11__namespace.writeFileSync(path12__namespace.join(stagingDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf8");
|
|
7838
7969
|
}
|
|
7839
7970
|
if (monolithJson !== null) {
|
|
7840
|
-
|
|
7971
|
+
fs11__namespace.writeFileSync(path12__namespace.join(stagingDir, "full-codebase.json"), monolithJson, "utf8");
|
|
7841
7972
|
}
|
|
7842
7973
|
if (monolithMarkdown !== null) {
|
|
7843
|
-
|
|
7974
|
+
fs11__namespace.writeFileSync(path12__namespace.join(stagingDir, "full-codebase.md"), monolithMarkdown, "utf8");
|
|
7844
7975
|
}
|
|
7845
7976
|
writeOwnershipMarker(stagingDir, markerPayload(stagingDir, "complete", snapshotId, createdAt));
|
|
7846
7977
|
swapIntoPlace(stagingDir, outDir);
|
|
@@ -7849,7 +7980,7 @@ function generateSnapshot(configInput, options) {
|
|
|
7849
7980
|
const pendingHook = runAfterGenerate(config, manifest);
|
|
7850
7981
|
const shipHookState = () => {
|
|
7851
7982
|
if (artifacts.manifest && manifest.warnings.length !== warningsBeforeHook) {
|
|
7852
|
-
writeManifestAtomically(
|
|
7983
|
+
writeManifestAtomically(path12__namespace.join(outDir, "manifest.json"), manifest);
|
|
7853
7984
|
}
|
|
7854
7985
|
writeOwnershipMarker(outDir, markerPayload(outDir, "complete", snapshotId, createdAt));
|
|
7855
7986
|
};
|
|
@@ -7872,7 +8003,7 @@ function generateSnapshot(configInput, options) {
|
|
|
7872
8003
|
} catch (err) {
|
|
7873
8004
|
if (stagingDir !== null) {
|
|
7874
8005
|
try {
|
|
7875
|
-
|
|
8006
|
+
fs11__namespace.rmSync(stagingDir, { recursive: true, force: true });
|
|
7876
8007
|
} catch {
|
|
7877
8008
|
}
|
|
7878
8009
|
}
|
|
@@ -7883,6 +8014,989 @@ function generateSnapshot(configInput, options) {
|
|
|
7883
8014
|
}
|
|
7884
8015
|
}
|
|
7885
8016
|
|
|
8017
|
+
// src/mcp/part.ts
|
|
8018
|
+
var FILE_HEADING = "### FILE: ";
|
|
8019
|
+
var CONTENT_MARKERS = /* @__PURE__ */ new Set(["#### Content", "#### Bounded Preview"]);
|
|
8020
|
+
function parsePartSections(partText) {
|
|
8021
|
+
const lines = partText.split("\n");
|
|
8022
|
+
const sections = [];
|
|
8023
|
+
let index = 0;
|
|
8024
|
+
while (index < lines.length) {
|
|
8025
|
+
if (!lines[index].startsWith(FILE_HEADING)) {
|
|
8026
|
+
index += 1;
|
|
8027
|
+
continue;
|
|
8028
|
+
}
|
|
8029
|
+
const heading = lines[index].slice(FILE_HEADING.length);
|
|
8030
|
+
let cursor = index + 1;
|
|
8031
|
+
let inclusion = null;
|
|
8032
|
+
let sha256 = null;
|
|
8033
|
+
let includedLines = null;
|
|
8034
|
+
while (cursor < lines.length && !CONTENT_MARKERS.has(lines[cursor])) {
|
|
8035
|
+
const line = lines[cursor];
|
|
8036
|
+
if (line.startsWith(FILE_HEADING)) break;
|
|
8037
|
+
if (line.startsWith("- Inclusion: ")) inclusion = line.slice("- Inclusion: ".length).trim();
|
|
8038
|
+
else if (line.startsWith("- SHA-256: ")) sha256 = line.slice("- SHA-256: ".length).trim();
|
|
8039
|
+
else if (line.startsWith("- Included Lines: ")) {
|
|
8040
|
+
const value = Number(line.slice("- Included Lines: ".length).trim());
|
|
8041
|
+
includedLines = Number.isFinite(value) ? value : null;
|
|
8042
|
+
}
|
|
8043
|
+
cursor += 1;
|
|
8044
|
+
}
|
|
8045
|
+
if (cursor >= lines.length || !CONTENT_MARKERS.has(lines[cursor])) {
|
|
8046
|
+
index = cursor + 1;
|
|
8047
|
+
continue;
|
|
8048
|
+
}
|
|
8049
|
+
let fenceLine = cursor + 1;
|
|
8050
|
+
while (fenceLine < lines.length && lines[fenceLine].trim().length === 0) fenceLine += 1;
|
|
8051
|
+
const fenceMatch = /^(`{4,})/.exec(lines[fenceLine] ?? "");
|
|
8052
|
+
if (!fenceMatch) {
|
|
8053
|
+
index = cursor + 1;
|
|
8054
|
+
continue;
|
|
8055
|
+
}
|
|
8056
|
+
const fence = fenceMatch[1];
|
|
8057
|
+
const body = [];
|
|
8058
|
+
let bodyLine = fenceLine + 1;
|
|
8059
|
+
while (bodyLine < lines.length && lines[bodyLine] !== fence) {
|
|
8060
|
+
body.push(lines[bodyLine]);
|
|
8061
|
+
bodyLine += 1;
|
|
8062
|
+
}
|
|
8063
|
+
sections.push({ path: heading, body: body.join("\n"), inclusion, sha256, includedLines });
|
|
8064
|
+
index = bodyLine + 1;
|
|
8065
|
+
}
|
|
8066
|
+
return sections;
|
|
8067
|
+
}
|
|
8068
|
+
function findSection(sections, relativePath, sha256) {
|
|
8069
|
+
const exact = sections.find((section) => section.path === relativePath);
|
|
8070
|
+
if (exact) return exact;
|
|
8071
|
+
if (sha256) {
|
|
8072
|
+
const byHash = sections.filter((section) => section.sha256 === sha256);
|
|
8073
|
+
if (byHash.length === 1) return byHash[0];
|
|
8074
|
+
if (byHash.length > 1) {
|
|
8075
|
+
const byPath = byHash.find(
|
|
8076
|
+
(section) => section.path.toLowerCase() === relativePath.toLowerCase()
|
|
8077
|
+
);
|
|
8078
|
+
if (byPath) return byPath;
|
|
8079
|
+
}
|
|
8080
|
+
}
|
|
8081
|
+
const folded = sections.filter((section) => section.path.toLowerCase() === relativePath.toLowerCase());
|
|
8082
|
+
return folded.length === 1 ? folded[0] : null;
|
|
8083
|
+
}
|
|
8084
|
+
function windowText(body, fromLine, lineCount) {
|
|
8085
|
+
const lines = body.split("\n");
|
|
8086
|
+
const total = lines.length;
|
|
8087
|
+
const start = Math.min(Math.max(1, Math.trunc(fromLine)), Math.max(1, total));
|
|
8088
|
+
const end = Math.min(total, start + Math.max(1, Math.trunc(lineCount)) - 1);
|
|
8089
|
+
const slice = lines.slice(start - 1, end);
|
|
8090
|
+
return {
|
|
8091
|
+
text: slice.join("\n"),
|
|
8092
|
+
fromLine: start,
|
|
8093
|
+
returnedLines: slice.length,
|
|
8094
|
+
totalLines: total,
|
|
8095
|
+
truncated: end < total,
|
|
8096
|
+
nextFromLine: end < total ? end + 1 : null
|
|
8097
|
+
};
|
|
8098
|
+
}
|
|
8099
|
+
function foldForComparison(value) {
|
|
8100
|
+
return process.platform === "win32" || process.platform === "darwin" ? value.toLowerCase() : value;
|
|
8101
|
+
}
|
|
8102
|
+
function canonicalPath(value) {
|
|
8103
|
+
const absolute = path12__namespace.resolve(value);
|
|
8104
|
+
try {
|
|
8105
|
+
return fs11__namespace.realpathSync(absolute);
|
|
8106
|
+
} catch {
|
|
8107
|
+
return absolute;
|
|
8108
|
+
}
|
|
8109
|
+
}
|
|
8110
|
+
function isInside2(parent, child) {
|
|
8111
|
+
const foldedParent = foldForComparison(parent);
|
|
8112
|
+
const foldedChild = foldForComparison(child);
|
|
8113
|
+
return foldedChild === foldedParent || foldedChild.startsWith(`${foldedParent}${path12__namespace.sep}`);
|
|
8114
|
+
}
|
|
8115
|
+
function resolveRoot(requested, context) {
|
|
8116
|
+
if (requested !== void 0 && (typeof requested !== "string" || requested.trim().length === 0)) {
|
|
8117
|
+
return { ok: false, message: '"root" must be a non-empty string when provided.' };
|
|
8118
|
+
}
|
|
8119
|
+
const wanted = typeof requested === "string" ? requested.trim() : context.cwd;
|
|
8120
|
+
const absolute = path12__namespace.resolve(context.cwd, wanted);
|
|
8121
|
+
let stats;
|
|
8122
|
+
try {
|
|
8123
|
+
stats = fs11__namespace.statSync(absolute);
|
|
8124
|
+
} catch {
|
|
8125
|
+
return { ok: false, message: `"${wanted}" does not exist, so it cannot be a repository root.` };
|
|
8126
|
+
}
|
|
8127
|
+
if (!stats.isDirectory()) {
|
|
8128
|
+
return { ok: false, message: `"${wanted}" is not a directory.` };
|
|
8129
|
+
}
|
|
8130
|
+
const resolved = canonicalPath(absolute);
|
|
8131
|
+
for (const allowed of context.roots) {
|
|
8132
|
+
if (isInside2(canonicalPath(allowed), resolved)) return { ok: true, root: resolved };
|
|
8133
|
+
}
|
|
8134
|
+
const shown = context.roots.join(", ");
|
|
8135
|
+
return {
|
|
8136
|
+
ok: false,
|
|
8137
|
+
message: `"${wanted}" resolves outside the directories this server may read (${shown}). Restart it with --root <path> to add a repository.`
|
|
8138
|
+
};
|
|
8139
|
+
}
|
|
8140
|
+
async function openWorkspace(root, overrides = {}) {
|
|
8141
|
+
const loaded = await loadConfigFileDetailed(root);
|
|
8142
|
+
if (loaded.errors.length > 0) {
|
|
8143
|
+
return {
|
|
8144
|
+
ok: false,
|
|
8145
|
+
message: `The config file could not be read: ${loaded.errors.join("; ")} Fix it, or delete it to use the defaults \u2014 this server will not silently ignore it.`
|
|
8146
|
+
};
|
|
8147
|
+
}
|
|
8148
|
+
const cliOptions = { cwd: root };
|
|
8149
|
+
if (overrides.maxPartBytes !== void 0) {
|
|
8150
|
+
const parsed = parseByteSize(overrides.maxPartBytes);
|
|
8151
|
+
if (parsed === void 0) {
|
|
8152
|
+
return {
|
|
8153
|
+
ok: false,
|
|
8154
|
+
message: '"maxPartBytes" must be a byte count, for example 307200 or "300k".'
|
|
8155
|
+
};
|
|
8156
|
+
}
|
|
8157
|
+
cliOptions.maxPartBytes = parsed;
|
|
8158
|
+
}
|
|
8159
|
+
if (overrides.outDir !== void 0) {
|
|
8160
|
+
if (typeof overrides.outDir !== "string" || overrides.outDir.trim().length === 0) {
|
|
8161
|
+
return { ok: false, message: '"outDir" must be a non-empty string.' };
|
|
8162
|
+
}
|
|
8163
|
+
cliOptions.outDir = overrides.outDir.trim();
|
|
8164
|
+
}
|
|
8165
|
+
try {
|
|
8166
|
+
const config = resolveConfig(loaded.config, cliOptions, root);
|
|
8167
|
+
return { ok: true, workspace: { root, config, configFile: loaded.file } };
|
|
8168
|
+
} catch (error) {
|
|
8169
|
+
return { ok: false, message: error instanceof Error ? error.message : String(error) };
|
|
8170
|
+
}
|
|
8171
|
+
}
|
|
8172
|
+
var manifestCache = /* @__PURE__ */ new Map();
|
|
8173
|
+
function manifestPathFor(workspace) {
|
|
8174
|
+
return path12__namespace.resolve(workspace.root, workspace.config.outDir, "manifest.json");
|
|
8175
|
+
}
|
|
8176
|
+
function readManifest3(workspace) {
|
|
8177
|
+
const file = manifestPathFor(workspace);
|
|
8178
|
+
const shown = path12__namespace.relative(workspace.root, file).split(path12__namespace.sep).join("/") || file;
|
|
8179
|
+
let stats;
|
|
8180
|
+
try {
|
|
8181
|
+
stats = fs11__namespace.statSync(file);
|
|
8182
|
+
} catch {
|
|
8183
|
+
return {
|
|
8184
|
+
ok: false,
|
|
8185
|
+
missing: true,
|
|
8186
|
+
message: `There is no snapshot manifest at ${shown}. Call snapshot_plan to preview what would be included, or snapshot_generate to write it.`
|
|
8187
|
+
};
|
|
8188
|
+
}
|
|
8189
|
+
const cached = manifestCache.get(file);
|
|
8190
|
+
if (cached && cached.mtimeMs === stats.mtimeMs && cached.size === stats.size) {
|
|
8191
|
+
return { ok: true, manifest: cached.manifest, file };
|
|
8192
|
+
}
|
|
8193
|
+
let raw;
|
|
8194
|
+
try {
|
|
8195
|
+
raw = fs11__namespace.readFileSync(file, "utf8");
|
|
8196
|
+
} catch (error) {
|
|
8197
|
+
return {
|
|
8198
|
+
ok: false,
|
|
8199
|
+
missing: false,
|
|
8200
|
+
message: `${shown} exists but could not be read: ${error instanceof Error ? error.message : String(error)}`
|
|
8201
|
+
};
|
|
8202
|
+
}
|
|
8203
|
+
let parsed;
|
|
8204
|
+
try {
|
|
8205
|
+
parsed = JSON.parse(raw);
|
|
8206
|
+
} catch {
|
|
8207
|
+
return { ok: false, missing: false, message: `${shown} is not valid JSON.` };
|
|
8208
|
+
}
|
|
8209
|
+
const problem = describeManifestShapeProblem(parsed);
|
|
8210
|
+
if (problem) {
|
|
8211
|
+
return { ok: false, missing: false, message: `${shown} is not a snapshot manifest: ${problem}` };
|
|
8212
|
+
}
|
|
8213
|
+
const manifest = parsed;
|
|
8214
|
+
manifestCache.set(file, { mtimeMs: stats.mtimeMs, size: stats.size, manifest });
|
|
8215
|
+
return { ok: true, manifest, file };
|
|
8216
|
+
}
|
|
8217
|
+
function forgetManifest(file) {
|
|
8218
|
+
manifestCache.delete(file);
|
|
8219
|
+
}
|
|
8220
|
+
|
|
8221
|
+
// src/mcp/tools.ts
|
|
8222
|
+
var READ_ONLY = {
|
|
8223
|
+
readOnlyHint: true,
|
|
8224
|
+
destructiveHint: false,
|
|
8225
|
+
idempotentHint: true,
|
|
8226
|
+
openWorldHint: false
|
|
8227
|
+
};
|
|
8228
|
+
var WRITES = {
|
|
8229
|
+
readOnlyHint: false,
|
|
8230
|
+
// It replaces the snapshot directory but never anything it did not write, so it is additive with
|
|
8231
|
+
// respect to the user's files.
|
|
8232
|
+
destructiveHint: false,
|
|
8233
|
+
idempotentHint: true,
|
|
8234
|
+
openWorldHint: false
|
|
8235
|
+
};
|
|
8236
|
+
var MAX_RESULT_BYTES = 64 * 1024;
|
|
8237
|
+
var ROOT_PROPERTY = {
|
|
8238
|
+
type: "string",
|
|
8239
|
+
description: "Repository root. Defaults to the directory this server was started in."
|
|
8240
|
+
};
|
|
8241
|
+
function failure(message) {
|
|
8242
|
+
return { text: message, isError: true };
|
|
8243
|
+
}
|
|
8244
|
+
function plural(count, word) {
|
|
8245
|
+
return `${count} ${word}${count === 1 ? "" : "s"}`;
|
|
8246
|
+
}
|
|
8247
|
+
function formatBytes(bytes) {
|
|
8248
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
8249
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} kB`;
|
|
8250
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
8251
|
+
}
|
|
8252
|
+
function relativeFor(workspace, absolute) {
|
|
8253
|
+
return path12__namespace.relative(workspace.root, absolute).split(path12__namespace.sep).join("/");
|
|
8254
|
+
}
|
|
8255
|
+
function capped(values, limit = 20) {
|
|
8256
|
+
return { count: values.length, sample: values.slice(0, limit) };
|
|
8257
|
+
}
|
|
8258
|
+
function summarizeDrift(drift) {
|
|
8259
|
+
return {
|
|
8260
|
+
missingInSnapshot: capped(drift.missingInSnapshot),
|
|
8261
|
+
missingOnDisk: capped(drift.missingOnDisk),
|
|
8262
|
+
unreadable: capped(drift.unreadable),
|
|
8263
|
+
hashMismatches: capped(drift.hashMismatches),
|
|
8264
|
+
partIssues: capped(drift.partIssues),
|
|
8265
|
+
totalsMismatches: capped(drift.totalsMismatches),
|
|
8266
|
+
configMismatches: capped(drift.configMismatches)
|
|
8267
|
+
};
|
|
8268
|
+
}
|
|
8269
|
+
function toRelativePath(root, value) {
|
|
8270
|
+
const cleaned = value.trim();
|
|
8271
|
+
if (cleaned.length === 0) return null;
|
|
8272
|
+
if (!path12__namespace.isAbsolute(cleaned)) {
|
|
8273
|
+
return cleaned.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
8274
|
+
}
|
|
8275
|
+
const relative11 = path12__namespace.relative(root, cleaned);
|
|
8276
|
+
if (relative11.startsWith("..") || path12__namespace.isAbsolute(relative11)) return null;
|
|
8277
|
+
return relative11.split(path12__namespace.sep).join("/");
|
|
8278
|
+
}
|
|
8279
|
+
function symbolsOf(entry) {
|
|
8280
|
+
const summary = entry.summary;
|
|
8281
|
+
if (!summary || typeof summary !== "object" || !("exports" in summary)) return [];
|
|
8282
|
+
const source = summary;
|
|
8283
|
+
return [...source.exports, ...source.reactComponents, ...source.routeHandlers];
|
|
8284
|
+
}
|
|
8285
|
+
async function prepare(args, context, overrides = {}) {
|
|
8286
|
+
const rootOutcome = resolveRoot(args.root, context);
|
|
8287
|
+
if (!rootOutcome.ok) return { ok: false, outcome: failure(rootOutcome.message) };
|
|
8288
|
+
const opened = await openWorkspace(rootOutcome.root, overrides);
|
|
8289
|
+
if (!opened.ok) return { ok: false, outcome: failure(opened.message) };
|
|
8290
|
+
return { ok: true, workspace: opened.workspace };
|
|
8291
|
+
}
|
|
8292
|
+
var statusTool = {
|
|
8293
|
+
name: "snapshot_status",
|
|
8294
|
+
title: "Snapshot status",
|
|
8295
|
+
description: "Report whether a snapshot exists for this repository, when it was built, and what it covers. Reads the manifest rather than the repository, so it is cheap. Call it before deciding whether a snapshot needs to be generated.",
|
|
8296
|
+
inputSchema: {
|
|
8297
|
+
type: "object",
|
|
8298
|
+
properties: { root: ROOT_PROPERTY },
|
|
8299
|
+
additionalProperties: false
|
|
8300
|
+
},
|
|
8301
|
+
outputSchema: {
|
|
8302
|
+
type: "object",
|
|
8303
|
+
properties: {
|
|
8304
|
+
present: { type: "boolean" },
|
|
8305
|
+
root: { type: "string" },
|
|
8306
|
+
outDir: { type: "string" },
|
|
8307
|
+
fingerprint: { type: "string" },
|
|
8308
|
+
generatedAt: { type: "string" },
|
|
8309
|
+
generatorVersion: { type: "string" },
|
|
8310
|
+
generatorVersionMatches: { type: "boolean" },
|
|
8311
|
+
includedFiles: { type: "integer" },
|
|
8312
|
+
skippedFiles: { type: "integer" },
|
|
8313
|
+
partCount: { type: "integer" },
|
|
8314
|
+
warningCount: { type: "integer" }
|
|
8315
|
+
}
|
|
8316
|
+
},
|
|
8317
|
+
annotations: READ_ONLY,
|
|
8318
|
+
async handler(args, context) {
|
|
8319
|
+
const prepared = await prepare(args, context);
|
|
8320
|
+
if (!prepared.ok) return prepared.outcome;
|
|
8321
|
+
const workspace = prepared.workspace;
|
|
8322
|
+
const read = readManifest3(workspace);
|
|
8323
|
+
if (!read.ok) {
|
|
8324
|
+
return {
|
|
8325
|
+
text: read.message,
|
|
8326
|
+
structured: {
|
|
8327
|
+
present: false,
|
|
8328
|
+
root: workspace.root,
|
|
8329
|
+
outDir: workspace.config.outDir,
|
|
8330
|
+
message: read.message
|
|
8331
|
+
}
|
|
8332
|
+
};
|
|
8333
|
+
}
|
|
8334
|
+
const manifest = read.manifest;
|
|
8335
|
+
const versionMatches = manifest.generator.version === GENERATOR_VERSION;
|
|
8336
|
+
const lines = [
|
|
8337
|
+
`Snapshot: ${workspace.config.outDir}/`,
|
|
8338
|
+
`Generated: ${manifest.generatedAt} by ${manifest.generator.name} ${manifest.generator.version}`,
|
|
8339
|
+
`Files: ${manifest.counts.totalFiles} included, ${manifest.counts.skippedCount} skipped, ${plural(manifest.counts.partsCount, "part")}`,
|
|
8340
|
+
`Content: ${formatBytes(manifest.counts.totalSizeBytes)} of source in ${formatBytes(manifest.totals.totalContentBytes)} emitted`,
|
|
8341
|
+
`Fingerprint: ${manifest.fingerprint}`,
|
|
8342
|
+
`Git: ${manifest.git.branch} @ ${manifest.git.commit}${manifest.git.isDirty ? " (dirty)" : " (clean)"}`
|
|
8343
|
+
];
|
|
8344
|
+
if (manifest.totals.warningCount > 0) lines.push(`Warnings: ${manifest.totals.warningCount}`);
|
|
8345
|
+
if (!versionMatches) {
|
|
8346
|
+
lines.push(
|
|
8347
|
+
`Note: written by ${manifest.generator.version}, this server is ${GENERATOR_VERSION}. Re-run snapshot_generate if the difference matters.`
|
|
8348
|
+
);
|
|
8349
|
+
}
|
|
8350
|
+
return {
|
|
8351
|
+
text: lines.join("\n"),
|
|
8352
|
+
structured: {
|
|
8353
|
+
present: true,
|
|
8354
|
+
root: workspace.root,
|
|
8355
|
+
outDir: workspace.config.outDir,
|
|
8356
|
+
fingerprint: manifest.fingerprint,
|
|
8357
|
+
generatedAt: manifest.generatedAt,
|
|
8358
|
+
generatorVersion: manifest.generator.version,
|
|
8359
|
+
generatorVersionMatches: versionMatches,
|
|
8360
|
+
includedFiles: manifest.counts.totalFiles,
|
|
8361
|
+
skippedFiles: manifest.counts.skippedCount,
|
|
8362
|
+
partCount: manifest.counts.partsCount,
|
|
8363
|
+
warningCount: manifest.totals.warningCount,
|
|
8364
|
+
preset: manifest.config.preset,
|
|
8365
|
+
maxPartBytes: manifest.budgets.maxPartBytes
|
|
8366
|
+
}
|
|
8367
|
+
};
|
|
8368
|
+
}
|
|
8369
|
+
};
|
|
8370
|
+
var planTool = {
|
|
8371
|
+
name: "snapshot_plan",
|
|
8372
|
+
title: "Preview a snapshot",
|
|
8373
|
+
description: "Preview a snapshot without writing anything: how many files and parts it would contain, and why every excluded file was excluded. Use it to choose a part budget before generating.",
|
|
8374
|
+
inputSchema: {
|
|
8375
|
+
type: "object",
|
|
8376
|
+
properties: {
|
|
8377
|
+
root: ROOT_PROPERTY,
|
|
8378
|
+
maxPartBytes: {
|
|
8379
|
+
type: "string",
|
|
8380
|
+
description: 'Byte budget per part, for example 307200 or "300k". Defaults to the project configuration.'
|
|
8381
|
+
}
|
|
8382
|
+
},
|
|
8383
|
+
additionalProperties: false
|
|
8384
|
+
},
|
|
8385
|
+
outputSchema: {
|
|
8386
|
+
type: "object",
|
|
8387
|
+
properties: {
|
|
8388
|
+
files: { type: "integer" },
|
|
8389
|
+
skipped: { type: "integer" },
|
|
8390
|
+
parts: { type: "integer" },
|
|
8391
|
+
sourceBytes: { type: "integer" },
|
|
8392
|
+
emittedBytes: { type: "integer" },
|
|
8393
|
+
preset: { type: "string" }
|
|
8394
|
+
}
|
|
8395
|
+
},
|
|
8396
|
+
annotations: READ_ONLY,
|
|
8397
|
+
async handler(args, context) {
|
|
8398
|
+
const prepared = await prepare(args, context, { maxPartBytes: args.maxPartBytes });
|
|
8399
|
+
if (!prepared.ok) return prepared.outcome;
|
|
8400
|
+
const workspace = prepared.workspace;
|
|
8401
|
+
let manifest;
|
|
8402
|
+
try {
|
|
8403
|
+
manifest = generateSnapshot(workspace.config, { dryRun: true });
|
|
8404
|
+
} catch (error) {
|
|
8405
|
+
return failure(error instanceof Error ? error.message : String(error));
|
|
8406
|
+
}
|
|
8407
|
+
const reasons = /* @__PURE__ */ new Map();
|
|
8408
|
+
for (const record of manifest.skipped) {
|
|
8409
|
+
reasons.set(record.reason, (reasons.get(record.reason) ?? 0) + 1);
|
|
8410
|
+
}
|
|
8411
|
+
const topReasons = [...reasons.entries()].sort((a, b) => b[1] - a[1] || (a[0] < b[0] ? -1 : 1)).slice(0, 8).map(([reason, count]) => ({ reason, count }));
|
|
8412
|
+
const lines = [
|
|
8413
|
+
"Dry run \u2014 nothing was written.",
|
|
8414
|
+
`Preset: ${manifest.config.preset}`,
|
|
8415
|
+
`Would include ${plural(manifest.counts.totalFiles, "file")} in ${plural(manifest.counts.partsCount, "part")} (${formatBytes(manifest.counts.totalSizeBytes)} of source, ${formatBytes(manifest.totals.totalContentBytes)} emitted).`,
|
|
8416
|
+
`Would skip ${plural(manifest.counts.skippedCount, "file")}.`
|
|
8417
|
+
];
|
|
8418
|
+
if (topReasons.length > 0) {
|
|
8419
|
+
lines.push("Top skip reasons:");
|
|
8420
|
+
for (const entry of topReasons) lines.push(` - ${entry.reason} (${entry.count})`);
|
|
8421
|
+
}
|
|
8422
|
+
if (manifest.warnings.length > 0) {
|
|
8423
|
+
lines.push(`Warnings (${manifest.warnings.length}):`);
|
|
8424
|
+
for (const warning of manifest.warnings.slice(0, 5)) lines.push(` - ${warning}`);
|
|
8425
|
+
}
|
|
8426
|
+
return {
|
|
8427
|
+
text: lines.join("\n"),
|
|
8428
|
+
structured: {
|
|
8429
|
+
files: manifest.counts.totalFiles,
|
|
8430
|
+
skipped: manifest.counts.skippedCount,
|
|
8431
|
+
parts: manifest.counts.partsCount,
|
|
8432
|
+
sourceBytes: manifest.counts.totalSizeBytes,
|
|
8433
|
+
emittedBytes: manifest.totals.totalContentBytes,
|
|
8434
|
+
preset: manifest.config.preset,
|
|
8435
|
+
maxPartBytes: manifest.budgets.maxPartBytes,
|
|
8436
|
+
layers: manifest.layers,
|
|
8437
|
+
skipReasons: topReasons,
|
|
8438
|
+
warnings: manifest.warnings.slice(0, 5),
|
|
8439
|
+
warningCount: manifest.warnings.length
|
|
8440
|
+
}
|
|
8441
|
+
};
|
|
8442
|
+
}
|
|
8443
|
+
};
|
|
8444
|
+
var generateTool = {
|
|
8445
|
+
name: "snapshot_generate",
|
|
8446
|
+
title: "Generate the snapshot",
|
|
8447
|
+
description: "Write the snapshot to its output directory and return its fingerprint, parts and counts. Replaces the previous snapshot in that directory. A large repository can take several seconds.",
|
|
8448
|
+
inputSchema: {
|
|
8449
|
+
type: "object",
|
|
8450
|
+
properties: {
|
|
8451
|
+
root: ROOT_PROPERTY,
|
|
8452
|
+
maxPartBytes: {
|
|
8453
|
+
type: "string",
|
|
8454
|
+
description: 'Byte budget per part, for example 307200 or "300k". Defaults to the project configuration.'
|
|
8455
|
+
},
|
|
8456
|
+
outDir: {
|
|
8457
|
+
type: "string",
|
|
8458
|
+
description: "Output directory relative to the root. Defaults to the project configuration."
|
|
8459
|
+
}
|
|
8460
|
+
},
|
|
8461
|
+
additionalProperties: false
|
|
8462
|
+
},
|
|
8463
|
+
outputSchema: {
|
|
8464
|
+
type: "object",
|
|
8465
|
+
properties: {
|
|
8466
|
+
fingerprint: { type: "string" },
|
|
8467
|
+
files: { type: "integer" },
|
|
8468
|
+
skipped: { type: "integer" },
|
|
8469
|
+
parts: { type: "integer" },
|
|
8470
|
+
emittedBytes: { type: "integer" }
|
|
8471
|
+
}
|
|
8472
|
+
},
|
|
8473
|
+
annotations: WRITES,
|
|
8474
|
+
async handler(args, context) {
|
|
8475
|
+
const prepared = await prepare(args, context, {
|
|
8476
|
+
maxPartBytes: args.maxPartBytes,
|
|
8477
|
+
outDir: args.outDir
|
|
8478
|
+
});
|
|
8479
|
+
if (!prepared.ok) return prepared.outcome;
|
|
8480
|
+
const workspace = prepared.workspace;
|
|
8481
|
+
let manifest;
|
|
8482
|
+
try {
|
|
8483
|
+
manifest = generateSnapshot(workspace.config);
|
|
8484
|
+
} catch (error) {
|
|
8485
|
+
return failure(error instanceof Error ? error.message : String(error));
|
|
8486
|
+
}
|
|
8487
|
+
forgetManifest(manifestPathFor(workspace));
|
|
8488
|
+
const written = relativeFor(workspace, path12__namespace.resolve(workspace.root, workspace.config.outDir));
|
|
8489
|
+
const lines = [
|
|
8490
|
+
`Wrote ${written}/`,
|
|
8491
|
+
`Files: ${manifest.counts.totalFiles} included, ${manifest.counts.skippedCount} skipped, ${plural(manifest.counts.partsCount, "part")}`,
|
|
8492
|
+
`Parts: ${manifest.parts.map((part) => `${part.filename} (${formatBytes(part.byteSize)})`).join(", ")}`,
|
|
8493
|
+
`Fingerprint: ${manifest.fingerprint}`
|
|
8494
|
+
];
|
|
8495
|
+
if (manifest.warnings.length > 0) {
|
|
8496
|
+
lines.push(`Warnings (${manifest.warnings.length}):`);
|
|
8497
|
+
for (const warning of manifest.warnings.slice(0, 5)) lines.push(` - ${warning}`);
|
|
8498
|
+
}
|
|
8499
|
+
return {
|
|
8500
|
+
text: lines.join("\n"),
|
|
8501
|
+
structured: {
|
|
8502
|
+
fingerprint: manifest.fingerprint,
|
|
8503
|
+
files: manifest.counts.totalFiles,
|
|
8504
|
+
skipped: manifest.counts.skippedCount,
|
|
8505
|
+
parts: manifest.counts.partsCount,
|
|
8506
|
+
emittedBytes: manifest.totals.totalContentBytes,
|
|
8507
|
+
outDir: written,
|
|
8508
|
+
partFiles: manifest.parts.map((part) => part.filename),
|
|
8509
|
+
warningCount: manifest.warnings.length
|
|
8510
|
+
}
|
|
8511
|
+
};
|
|
8512
|
+
}
|
|
8513
|
+
};
|
|
8514
|
+
var validateTool = {
|
|
8515
|
+
name: "snapshot_validate",
|
|
8516
|
+
title: "Validate the snapshot",
|
|
8517
|
+
description: "Re-read the repository and report whether the snapshot on disk still matches it. Use it before trusting context that came from an earlier session, or after changing the config.",
|
|
8518
|
+
inputSchema: {
|
|
8519
|
+
type: "object",
|
|
8520
|
+
properties: { root: ROOT_PROPERTY },
|
|
8521
|
+
additionalProperties: false
|
|
8522
|
+
},
|
|
8523
|
+
outputSchema: {
|
|
8524
|
+
type: "object",
|
|
8525
|
+
properties: {
|
|
8526
|
+
current: { type: "boolean" },
|
|
8527
|
+
manifestPresent: { type: "boolean" }
|
|
8528
|
+
}
|
|
8529
|
+
},
|
|
8530
|
+
annotations: READ_ONLY,
|
|
8531
|
+
async handler(args, context) {
|
|
8532
|
+
const prepared = await prepare(args, context);
|
|
8533
|
+
if (!prepared.ok) return prepared.outcome;
|
|
8534
|
+
const workspace = prepared.workspace;
|
|
8535
|
+
let result;
|
|
8536
|
+
try {
|
|
8537
|
+
result = validateSnapshot(workspace.config);
|
|
8538
|
+
} catch (error) {
|
|
8539
|
+
return failure(error instanceof Error ? error.message : String(error));
|
|
8540
|
+
}
|
|
8541
|
+
const lines = [];
|
|
8542
|
+
if (result.valid) {
|
|
8543
|
+
lines.push("The snapshot matches the repository.");
|
|
8544
|
+
} else {
|
|
8545
|
+
lines.push("The snapshot is out of date.");
|
|
8546
|
+
const drift = result.drift;
|
|
8547
|
+
const observations = [
|
|
8548
|
+
["file(s) changed on disk", drift.hashMismatches.length],
|
|
8549
|
+
["file(s) added since the snapshot", drift.missingInSnapshot.length],
|
|
8550
|
+
["file(s) gone since the snapshot", drift.missingOnDisk.length],
|
|
8551
|
+
["part file(s) damaged or missing", drift.partIssues.length],
|
|
8552
|
+
["setting(s) changed since the snapshot", drift.configMismatches.length],
|
|
8553
|
+
["internal count(s) disagreeing", drift.totalsMismatches.length]
|
|
8554
|
+
];
|
|
8555
|
+
for (const [label, count] of observations) {
|
|
8556
|
+
if (count > 0) lines.push(` - ${count} ${label}`);
|
|
8557
|
+
}
|
|
8558
|
+
for (const error of result.errors.slice(0, 5)) lines.push(` - ${error}`);
|
|
8559
|
+
lines.push("Run snapshot_generate to refresh it.");
|
|
8560
|
+
}
|
|
8561
|
+
for (const warning of result.warnings.slice(0, 5)) lines.push(`Note: ${warning}`);
|
|
8562
|
+
return {
|
|
8563
|
+
text: lines.join("\n"),
|
|
8564
|
+
structured: {
|
|
8565
|
+
current: result.valid,
|
|
8566
|
+
manifestPresent: result.manifestPresent,
|
|
8567
|
+
drift: summarizeDrift(result.drift),
|
|
8568
|
+
errors: result.errors.slice(0, 10),
|
|
8569
|
+
warnings: result.warnings.slice(0, 10)
|
|
8570
|
+
}
|
|
8571
|
+
// A stale snapshot is an answer, not a tool failure: the caller asked whether it is current.
|
|
8572
|
+
};
|
|
8573
|
+
}
|
|
8574
|
+
};
|
|
8575
|
+
var locateTool = {
|
|
8576
|
+
name: "snapshot_locate",
|
|
8577
|
+
title: "Locate code in the snapshot",
|
|
8578
|
+
description: "Find which part holds the code you need. Matches file paths, exported symbols, component names and routes from the snapshot's index, so one part can be read instead of all of them.",
|
|
8579
|
+
inputSchema: {
|
|
8580
|
+
type: "object",
|
|
8581
|
+
properties: {
|
|
8582
|
+
query: {
|
|
8583
|
+
type: "string",
|
|
8584
|
+
description: "Text to match, case-insensitively, against paths and exported symbols."
|
|
8585
|
+
},
|
|
8586
|
+
by: {
|
|
8587
|
+
type: "string",
|
|
8588
|
+
enum: ["auto", "path", "symbol", "route", "layer", "kind"],
|
|
8589
|
+
default: "auto",
|
|
8590
|
+
description: "Restrict what is matched. Defaults to every category."
|
|
8591
|
+
},
|
|
8592
|
+
limit: {
|
|
8593
|
+
type: "integer",
|
|
8594
|
+
minimum: 1,
|
|
8595
|
+
maximum: 100,
|
|
8596
|
+
default: 20,
|
|
8597
|
+
description: "Maximum matches to return."
|
|
8598
|
+
},
|
|
8599
|
+
root: ROOT_PROPERTY
|
|
8600
|
+
},
|
|
8601
|
+
required: ["query"],
|
|
8602
|
+
additionalProperties: false
|
|
8603
|
+
},
|
|
8604
|
+
outputSchema: {
|
|
8605
|
+
type: "object",
|
|
8606
|
+
properties: { total: { type: "integer" } }
|
|
8607
|
+
},
|
|
8608
|
+
annotations: READ_ONLY,
|
|
8609
|
+
async handler(args, context) {
|
|
8610
|
+
const prepared = await prepare(args, context);
|
|
8611
|
+
if (!prepared.ok) return prepared.outcome;
|
|
8612
|
+
const workspace = prepared.workspace;
|
|
8613
|
+
const read = readManifest3(workspace);
|
|
8614
|
+
if (!read.ok) return failure(read.message);
|
|
8615
|
+
const query = String(args.query ?? "").trim();
|
|
8616
|
+
if (query.length === 0) return failure('"query" must not be empty.');
|
|
8617
|
+
const needle = query.toLowerCase();
|
|
8618
|
+
const by = typeof args.by === "string" ? args.by : "auto";
|
|
8619
|
+
const limit = typeof args.limit === "number" ? args.limit : 20;
|
|
8620
|
+
const manifest = read.manifest;
|
|
8621
|
+
const matches = [];
|
|
8622
|
+
const add = (entry, matchedBy, detail) => {
|
|
8623
|
+
matches.push({
|
|
8624
|
+
path: entry.relativePath,
|
|
8625
|
+
part: entry.part,
|
|
8626
|
+
layer: entry.layer,
|
|
8627
|
+
kind: entry.kind,
|
|
8628
|
+
matchedBy,
|
|
8629
|
+
...detail ? { detail } : {}
|
|
8630
|
+
});
|
|
8631
|
+
};
|
|
8632
|
+
const wants = (category) => by === "auto" || by === category;
|
|
8633
|
+
for (const entry of manifest.files) {
|
|
8634
|
+
if (wants("path") && entry.relativePath.toLowerCase().includes(needle)) {
|
|
8635
|
+
add(entry, "path");
|
|
8636
|
+
continue;
|
|
8637
|
+
}
|
|
8638
|
+
if (wants("symbol")) {
|
|
8639
|
+
const symbol = symbolsOf(entry).find((name) => name.toLowerCase().includes(needle));
|
|
8640
|
+
if (symbol) {
|
|
8641
|
+
add(entry, "symbol", symbol);
|
|
8642
|
+
continue;
|
|
8643
|
+
}
|
|
8644
|
+
}
|
|
8645
|
+
if (wants("layer") && entry.layer.toLowerCase() === needle) add(entry, "layer");
|
|
8646
|
+
else if (wants("kind") && entry.kind.toLowerCase() === needle) add(entry, "kind");
|
|
8647
|
+
}
|
|
8648
|
+
if (wants("route")) {
|
|
8649
|
+
for (const route of manifest.indexes.routes) {
|
|
8650
|
+
if (!route.path.toLowerCase().includes(needle)) continue;
|
|
8651
|
+
if (matches.some((match) => match.path === route.path)) continue;
|
|
8652
|
+
matches.push({
|
|
8653
|
+
path: route.path,
|
|
8654
|
+
part: route.part,
|
|
8655
|
+
layer: "route",
|
|
8656
|
+
kind: "route",
|
|
8657
|
+
matchedBy: "route",
|
|
8658
|
+
detail: route.methods.join(", ")
|
|
8659
|
+
});
|
|
8660
|
+
}
|
|
8661
|
+
}
|
|
8662
|
+
if (matches.length === 0) {
|
|
8663
|
+
return {
|
|
8664
|
+
text: `Nothing in the snapshot matches "${query}". Try a shorter fragment, or snapshot_explain a path to see whether it is included at all.`,
|
|
8665
|
+
structured: { total: 0, matches: [] }
|
|
8666
|
+
};
|
|
8667
|
+
}
|
|
8668
|
+
const shown = matches.slice(0, limit);
|
|
8669
|
+
const lines = [
|
|
8670
|
+
`${matches.length} match${matches.length === 1 ? "" : "es"} for "${query}"` + (matches.length > shown.length ? ` (showing ${shown.length})` : "") + ":"
|
|
8671
|
+
];
|
|
8672
|
+
for (const match of shown) {
|
|
8673
|
+
const detail = match.detail ? ` \u2014 ${match.detail}` : "";
|
|
8674
|
+
lines.push(` ${match.path}${detail} [${match.part}, ${match.layer}/${match.kind}]`);
|
|
8675
|
+
}
|
|
8676
|
+
if (matches.length > shown.length) {
|
|
8677
|
+
lines.push("Raise `limit` to see more.");
|
|
8678
|
+
}
|
|
8679
|
+
return {
|
|
8680
|
+
text: lines.join("\n"),
|
|
8681
|
+
structured: { total: matches.length, returned: shown.length, matches: shown }
|
|
8682
|
+
};
|
|
8683
|
+
}
|
|
8684
|
+
};
|
|
8685
|
+
var readTool = {
|
|
8686
|
+
name: "snapshot_read",
|
|
8687
|
+
title: "Read a file from the snapshot",
|
|
8688
|
+
description: "Return a line window of one file as the snapshot contains it: redacted, and bounded to a preview when the source is large. Reports the next `fromLine` when more remains.",
|
|
8689
|
+
inputSchema: {
|
|
8690
|
+
type: "object",
|
|
8691
|
+
properties: {
|
|
8692
|
+
path: {
|
|
8693
|
+
type: "string",
|
|
8694
|
+
description: "File path relative to the repository root, as snapshot_locate reports it."
|
|
8695
|
+
},
|
|
8696
|
+
fromLine: {
|
|
8697
|
+
type: "integer",
|
|
8698
|
+
minimum: 1,
|
|
8699
|
+
default: 1,
|
|
8700
|
+
description: "First line to return, 1-based."
|
|
8701
|
+
},
|
|
8702
|
+
lines: {
|
|
8703
|
+
type: "integer",
|
|
8704
|
+
minimum: 1,
|
|
8705
|
+
maximum: 2e3,
|
|
8706
|
+
default: 200,
|
|
8707
|
+
description: "How many lines to return."
|
|
8708
|
+
},
|
|
8709
|
+
root: ROOT_PROPERTY
|
|
8710
|
+
},
|
|
8711
|
+
required: ["path"],
|
|
8712
|
+
additionalProperties: false
|
|
8713
|
+
},
|
|
8714
|
+
outputSchema: {
|
|
8715
|
+
type: "object",
|
|
8716
|
+
properties: {
|
|
8717
|
+
path: { type: "string" },
|
|
8718
|
+
part: { type: "string" },
|
|
8719
|
+
fromLine: { type: "integer" },
|
|
8720
|
+
returnedLines: { type: "integer" },
|
|
8721
|
+
totalLines: { type: "integer" },
|
|
8722
|
+
nextFromLine: { type: "integer" }
|
|
8723
|
+
}
|
|
8724
|
+
},
|
|
8725
|
+
annotations: READ_ONLY,
|
|
8726
|
+
async handler(args, context) {
|
|
8727
|
+
const prepared = await prepare(args, context);
|
|
8728
|
+
if (!prepared.ok) return prepared.outcome;
|
|
8729
|
+
const workspace = prepared.workspace;
|
|
8730
|
+
if (typeof args.path !== "string") return failure('"path" must be a string.');
|
|
8731
|
+
const relativePath = toRelativePath(workspace.root, args.path);
|
|
8732
|
+
if (!relativePath) {
|
|
8733
|
+
return failure(
|
|
8734
|
+
`"${args.path}" is outside ${workspace.root}. Paths are read relative to the repository root.`
|
|
8735
|
+
);
|
|
8736
|
+
}
|
|
8737
|
+
const read = readManifest3(workspace);
|
|
8738
|
+
if (!read.ok) return failure(read.message);
|
|
8739
|
+
const manifest = read.manifest;
|
|
8740
|
+
const entry = manifest.files.find((file) => file.relativePath === relativePath) ?? manifest.files.find((file) => file.relativePath.toLowerCase() === relativePath.toLowerCase());
|
|
8741
|
+
if (!entry) {
|
|
8742
|
+
const skipped = manifest.skipped.find((record) => record.path === relativePath);
|
|
8743
|
+
if (skipped) {
|
|
8744
|
+
const detail = skipped.detail ? ` (${skipped.detail})` : "";
|
|
8745
|
+
return failure(
|
|
8746
|
+
`${relativePath} is not in the snapshot: ${skipped.reason}${detail}. Sensitive and ignored files stay out unless they are included explicitly.`
|
|
8747
|
+
);
|
|
8748
|
+
}
|
|
8749
|
+
return failure(
|
|
8750
|
+
`${relativePath} is not in the snapshot. Call snapshot_locate to find the path, or snapshot_explain to learn why it was excluded.`
|
|
8751
|
+
);
|
|
8752
|
+
}
|
|
8753
|
+
const partPath = path12__namespace.resolve(workspace.root, workspace.config.outDir, "parts", entry.part);
|
|
8754
|
+
let partText;
|
|
8755
|
+
try {
|
|
8756
|
+
partText = fs11__namespace.readFileSync(partPath, "utf8");
|
|
8757
|
+
} catch {
|
|
8758
|
+
return failure(
|
|
8759
|
+
`${entry.part} could not be read, so the snapshot is damaged. Run snapshot_generate to rebuild it.`
|
|
8760
|
+
);
|
|
8761
|
+
}
|
|
8762
|
+
const section = findSection(parsePartSections(partText), entry.relativePath, entry.hashSha256);
|
|
8763
|
+
if (!section) {
|
|
8764
|
+
return failure(
|
|
8765
|
+
`${relativePath} is recorded in ${entry.part} but its section could not be located there. Run snapshot_generate to rebuild the snapshot.`
|
|
8766
|
+
);
|
|
8767
|
+
}
|
|
8768
|
+
const fromLine = typeof args.fromLine === "number" ? args.fromLine : 1;
|
|
8769
|
+
const lineCount = typeof args.lines === "number" ? args.lines : 200;
|
|
8770
|
+
const window = windowText(section.body, fromLine, lineCount);
|
|
8771
|
+
let body = window.text;
|
|
8772
|
+
let truncatedByBytes = false;
|
|
8773
|
+
if (Buffer.byteLength(body, "utf8") > MAX_RESULT_BYTES) {
|
|
8774
|
+
body = body.slice(0, MAX_RESULT_BYTES);
|
|
8775
|
+
const lastBreak = body.lastIndexOf("\n");
|
|
8776
|
+
body = lastBreak > 0 ? body.slice(0, lastBreak) : body;
|
|
8777
|
+
truncatedByBytes = true;
|
|
8778
|
+
}
|
|
8779
|
+
const returnedLines = body.length === 0 ? 0 : body.split("\n").length;
|
|
8780
|
+
const moreRemain = window.truncated || truncatedByBytes;
|
|
8781
|
+
const header = [
|
|
8782
|
+
`${entry.relativePath} \u2014 ${entry.part}, ${entry.layer}/${entry.kind}, ${entry.language}`,
|
|
8783
|
+
section.inclusion === "preview" ? "Bounded preview: this source file is large, so the snapshot holds only its head and tail." : `Lines ${window.fromLine}\u2013${window.fromLine + returnedLines - 1} of ${window.totalLines}.`
|
|
8784
|
+
];
|
|
8785
|
+
const lines = [...header, "", body];
|
|
8786
|
+
if (moreRemain) {
|
|
8787
|
+
const next = window.fromLine + returnedLines;
|
|
8788
|
+
lines.push("", `Truncated. Call snapshot_read with fromLine=${next} for the rest.`);
|
|
8789
|
+
}
|
|
8790
|
+
return {
|
|
8791
|
+
text: lines.join("\n"),
|
|
8792
|
+
structured: {
|
|
8793
|
+
path: entry.relativePath,
|
|
8794
|
+
part: entry.part,
|
|
8795
|
+
layer: entry.layer,
|
|
8796
|
+
kind: entry.kind,
|
|
8797
|
+
language: entry.language,
|
|
8798
|
+
inclusion: section.inclusion ?? entry.inclusionMode,
|
|
8799
|
+
fromLine: window.fromLine,
|
|
8800
|
+
returnedLines,
|
|
8801
|
+
totalLines: window.totalLines,
|
|
8802
|
+
truncated: moreRemain,
|
|
8803
|
+
nextFromLine: moreRemain ? window.fromLine + returnedLines : null,
|
|
8804
|
+
sha256: entry.hashSha256
|
|
8805
|
+
}
|
|
8806
|
+
};
|
|
8807
|
+
}
|
|
8808
|
+
};
|
|
8809
|
+
var explainTool = {
|
|
8810
|
+
name: "snapshot_explain",
|
|
8811
|
+
title: "Explain one path",
|
|
8812
|
+
description: 'Explain how the next snapshot would treat one path: its layer, kind, language and part, or the exact reason it is excluded. Answers "why is this file missing".',
|
|
8813
|
+
inputSchema: {
|
|
8814
|
+
type: "object",
|
|
8815
|
+
properties: {
|
|
8816
|
+
path: {
|
|
8817
|
+
type: "string",
|
|
8818
|
+
description: "Path relative to the repository root. Directories are explained as a whole."
|
|
8819
|
+
},
|
|
8820
|
+
root: ROOT_PROPERTY
|
|
8821
|
+
},
|
|
8822
|
+
required: ["path"],
|
|
8823
|
+
additionalProperties: false
|
|
8824
|
+
},
|
|
8825
|
+
outputSchema: {
|
|
8826
|
+
type: "object",
|
|
8827
|
+
properties: {
|
|
8828
|
+
path: { type: "string" },
|
|
8829
|
+
included: { type: "boolean" },
|
|
8830
|
+
reason: { type: "string" }
|
|
8831
|
+
}
|
|
8832
|
+
},
|
|
8833
|
+
annotations: READ_ONLY,
|
|
8834
|
+
async handler(args, context) {
|
|
8835
|
+
const prepared = await prepare(args, context);
|
|
8836
|
+
if (!prepared.ok) return prepared.outcome;
|
|
8837
|
+
const workspace = prepared.workspace;
|
|
8838
|
+
if (typeof args.path !== "string") return failure('"path" must be a string.');
|
|
8839
|
+
const relativePath = toRelativePath(workspace.root, args.path);
|
|
8840
|
+
if (!relativePath) {
|
|
8841
|
+
return failure(`"${args.path}" is outside ${workspace.root}.`);
|
|
8842
|
+
}
|
|
8843
|
+
let result;
|
|
8844
|
+
try {
|
|
8845
|
+
result = explainPath(workspace.config, relativePath);
|
|
8846
|
+
} catch (error) {
|
|
8847
|
+
return failure(error instanceof Error ? error.message : String(error));
|
|
8848
|
+
}
|
|
8849
|
+
return {
|
|
8850
|
+
text: renderExplainText(result),
|
|
8851
|
+
structured: {
|
|
8852
|
+
path: result.path,
|
|
8853
|
+
included: result.included,
|
|
8854
|
+
...result.reason ? { reason: result.reason } : {},
|
|
8855
|
+
...result.detail ? { detail: result.detail } : {},
|
|
8856
|
+
...result.layer ? { layer: result.layer } : {},
|
|
8857
|
+
...result.kind ? { kind: result.kind } : {},
|
|
8858
|
+
...result.language ? { language: result.language } : {},
|
|
8859
|
+
...result.part ? { part: result.part } : {},
|
|
8860
|
+
...result.inclusionMode ? { inclusion: result.inclusionMode } : {},
|
|
8861
|
+
...typeof result.lines === "number" ? { lines: result.lines } : {},
|
|
8862
|
+
...typeof result.bytes === "number" ? { bytes: result.bytes } : {},
|
|
8863
|
+
...typeof result.secretCount === "number" ? { secretCount: result.secretCount } : {}
|
|
8864
|
+
}
|
|
8865
|
+
};
|
|
8866
|
+
}
|
|
8867
|
+
};
|
|
8868
|
+
function buildTools() {
|
|
8869
|
+
return [
|
|
8870
|
+
statusTool,
|
|
8871
|
+
planTool,
|
|
8872
|
+
generateTool,
|
|
8873
|
+
validateTool,
|
|
8874
|
+
locateTool,
|
|
8875
|
+
readTool,
|
|
8876
|
+
explainTool
|
|
8877
|
+
];
|
|
8878
|
+
}
|
|
8879
|
+
|
|
8880
|
+
// src/mcp/prompts.ts
|
|
8881
|
+
var ORIENT = `You have a codebase-snapshot server for this repository. Build a map of it before reading any code.
|
|
8882
|
+
|
|
8883
|
+
1. Call snapshot_status. If no snapshot exists, call snapshot_plan to preview it, then snapshot_generate.
|
|
8884
|
+
2. Call snapshot_locate for the areas that matter. Search by exported symbol, route or path fragment rather than guessing filenames.
|
|
8885
|
+
3. Report back: the layers present, the entry points, and the files that matter here \u2014 cited as paths with their part.
|
|
8886
|
+
4. Read one file at a time with snapshot_read, one window at a time. Do not read a part end to end.`;
|
|
8887
|
+
var KEEP_CURRENT = `Keep the context you were given provably current while you work.
|
|
8888
|
+
|
|
8889
|
+
1. Before relying on a snapshot from an earlier session, call snapshot_validate.
|
|
8890
|
+
2. If it reports drift, call snapshot_generate once, then snapshot_validate again.
|
|
8891
|
+
3. After changes you make, call snapshot_validate and report what it said.
|
|
8892
|
+
4. Never call a snapshot current unless snapshot_validate said so in this session.`;
|
|
8893
|
+
function withFocus(base, focus) {
|
|
8894
|
+
return typeof focus === "string" && focus.trim().length > 0 ? `${base}
|
|
8895
|
+
|
|
8896
|
+
Focus for this pass: ${focus.trim()}` : base;
|
|
8897
|
+
}
|
|
8898
|
+
function buildPrompts() {
|
|
8899
|
+
return [
|
|
8900
|
+
{
|
|
8901
|
+
name: "orient",
|
|
8902
|
+
title: "Orient in this repository",
|
|
8903
|
+
description: "Map the repository from the snapshot before reading any of it.",
|
|
8904
|
+
arguments: [
|
|
8905
|
+
{
|
|
8906
|
+
name: "focus",
|
|
8907
|
+
description: 'Optional area to concentrate on, for example "authentication".',
|
|
8908
|
+
required: false
|
|
8909
|
+
}
|
|
8910
|
+
],
|
|
8911
|
+
build(args) {
|
|
8912
|
+
return {
|
|
8913
|
+
description: "Map the repository before reading it.",
|
|
8914
|
+
messages: [{ role: "user", content: { type: "text", text: withFocus(ORIENT, args.focus) } }]
|
|
8915
|
+
};
|
|
8916
|
+
}
|
|
8917
|
+
},
|
|
8918
|
+
{
|
|
8919
|
+
name: "keep-current",
|
|
8920
|
+
title: "Keep the snapshot current",
|
|
8921
|
+
description: "Validate before trusting earlier context, and again after making changes.",
|
|
8922
|
+
arguments: [],
|
|
8923
|
+
build() {
|
|
8924
|
+
return {
|
|
8925
|
+
description: "Keep the context current.",
|
|
8926
|
+
messages: [{ role: "user", content: { type: "text", text: KEEP_CURRENT } }]
|
|
8927
|
+
};
|
|
8928
|
+
}
|
|
8929
|
+
}
|
|
8930
|
+
];
|
|
8931
|
+
}
|
|
8932
|
+
|
|
8933
|
+
// src/mcp/protocol.ts
|
|
8934
|
+
var LEGACY_PROTOCOL_VERSIONS = ["2025-11-25", "2025-06-18"];
|
|
8935
|
+
var MODERN_PROTOCOL_VERSIONS = ["2026-07-28"];
|
|
8936
|
+
var SUPPORTED_PROTOCOL_VERSIONS = [
|
|
8937
|
+
...MODERN_PROTOCOL_VERSIONS,
|
|
8938
|
+
...LEGACY_PROTOCOL_VERSIONS
|
|
8939
|
+
];
|
|
8940
|
+
var DEFAULT_LEGACY_PROTOCOL_VERSION = LEGACY_PROTOCOL_VERSIONS[0];
|
|
8941
|
+
var ERROR_CODES = {
|
|
8942
|
+
parseError: -32700,
|
|
8943
|
+
invalidRequest: -32600,
|
|
8944
|
+
methodNotFound: -32601,
|
|
8945
|
+
invalidParams: -32602,
|
|
8946
|
+
internalError: -32603,
|
|
8947
|
+
unsupportedProtocolVersion: -32022
|
|
8948
|
+
};
|
|
8949
|
+
var METHOD3 = {
|
|
8950
|
+
initialize: "initialize",
|
|
8951
|
+
initialized: "notifications/initialized",
|
|
8952
|
+
discover: "server/discover",
|
|
8953
|
+
ping: "ping",
|
|
8954
|
+
toolsList: "tools/list",
|
|
8955
|
+
toolsCall: "tools/call",
|
|
8956
|
+
resourcesList: "resources/list",
|
|
8957
|
+
resourcesRead: "resources/read",
|
|
8958
|
+
resourceTemplatesList: "resources/templates/list",
|
|
8959
|
+
promptsList: "prompts/list",
|
|
8960
|
+
promptsGet: "prompts/get"};
|
|
8961
|
+
var META_KEYS = {
|
|
8962
|
+
protocolVersion: "io.modelcontextprotocol/protocolVersion",
|
|
8963
|
+
clientCapabilities: "io.modelcontextprotocol/clientCapabilities",
|
|
8964
|
+
clientInfo: "io.modelcontextprotocol/clientInfo",
|
|
8965
|
+
serverInfo: "io.modelcontextprotocol/serverInfo"};
|
|
8966
|
+
function isJsonObject(value) {
|
|
8967
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8968
|
+
}
|
|
8969
|
+
function serverInfoMeta(info2) {
|
|
8970
|
+
const value = { name: info2.name, version: info2.version };
|
|
8971
|
+
if (info2.title) value.title = info2.title;
|
|
8972
|
+
return { [META_KEYS.serverInfo]: value };
|
|
8973
|
+
}
|
|
8974
|
+
function successResponse(id, result, modern, info2) {
|
|
8975
|
+
if (!modern) return { jsonrpc: "2.0", id, result };
|
|
8976
|
+
const withMeta = {
|
|
8977
|
+
...result,
|
|
8978
|
+
resultType: "complete",
|
|
8979
|
+
_meta: { ...isJsonObject(result._meta) ? result._meta : {}, ...serverInfoMeta(info2) }
|
|
8980
|
+
};
|
|
8981
|
+
return { jsonrpc: "2.0", id, result: withMeta };
|
|
8982
|
+
}
|
|
8983
|
+
function errorResponse(id, code, message, data) {
|
|
8984
|
+
const error = { code, message };
|
|
8985
|
+
if (data !== void 0) error.data = data;
|
|
8986
|
+
return { jsonrpc: "2.0", id, error };
|
|
8987
|
+
}
|
|
8988
|
+
|
|
8989
|
+
// src/mcp/uris.ts
|
|
8990
|
+
var STATIC_RESOURCE_URIS = [
|
|
8991
|
+
"snapshot://manifest",
|
|
8992
|
+
"snapshot://contract",
|
|
8993
|
+
"snapshot://skill",
|
|
8994
|
+
"snapshot://reports/audit",
|
|
8995
|
+
"snapshot://reports/tree",
|
|
8996
|
+
"snapshot://reports/navigation"
|
|
8997
|
+
];
|
|
8998
|
+
var RESOURCE_TEMPLATE_URIS = ["snapshot://parts/{index}"];
|
|
8999
|
+
|
|
7886
9000
|
// src/agent/option-scope.ts
|
|
7887
9001
|
var VALIDATE_OPTIONS = [
|
|
7888
9002
|
"cwd",
|
|
@@ -7918,6 +9032,8 @@ var COMMAND_OPTION_SCOPE = {
|
|
|
7918
9032
|
"mode",
|
|
7919
9033
|
"dry-run",
|
|
7920
9034
|
"watch",
|
|
9035
|
+
"autopilot",
|
|
9036
|
+
"no-install",
|
|
7921
9037
|
"help",
|
|
7922
9038
|
"version"
|
|
7923
9039
|
],
|
|
@@ -7976,6 +9092,9 @@ var COMMAND_OPTION_SCOPE = {
|
|
|
7976
9092
|
"version"
|
|
7977
9093
|
],
|
|
7978
9094
|
skill: ["cwd", "format", "mode", "install", "target", "help", "version"],
|
|
9095
|
+
// `mcp` reads the repository, so it takes --cwd. It has no --format: stdout carries JSON-RPC
|
|
9096
|
+
// messages, and there is no second shape to choose, so offering the flag would be a lie.
|
|
9097
|
+
mcp: ["cwd", "mode", "help", "version"],
|
|
7979
9098
|
setup: [
|
|
7980
9099
|
"cwd",
|
|
7981
9100
|
"out-dir",
|
|
@@ -8005,7 +9124,8 @@ var COMMAND_ORDER = [
|
|
|
8005
9124
|
"setup",
|
|
8006
9125
|
"diff",
|
|
8007
9126
|
"describe",
|
|
8008
|
-
"skill"
|
|
9127
|
+
"skill",
|
|
9128
|
+
"mcp"
|
|
8009
9129
|
];
|
|
8010
9130
|
function commandsForOption(option) {
|
|
8011
9131
|
return COMMAND_ORDER.filter((command) => COMMAND_OPTION_SCOPE[command].includes(option));
|
|
@@ -8079,6 +9199,19 @@ function renderContractText(contract) {
|
|
|
8079
9199
|
lines.push(` ${"".padEnd(24)} ${option.summary}`);
|
|
8080
9200
|
}
|
|
8081
9201
|
lines.push("");
|
|
9202
|
+
lines.push("MCP:");
|
|
9203
|
+
lines.push(` command ${contract.mcp.command}`);
|
|
9204
|
+
lines.push(` transport ${contract.mcp.transport}`);
|
|
9205
|
+
lines.push(
|
|
9206
|
+
` versions modern ${contract.mcp.protocolVersions.modern.join(", ")} / legacy ${contract.mcp.protocolVersions.legacy.join(", ")}`
|
|
9207
|
+
);
|
|
9208
|
+
lines.push(
|
|
9209
|
+
` tools ${contract.mcp.tools.map((tool) => tool.readOnly ? tool.name : `${tool.name} (writes)`).join(", ")}`
|
|
9210
|
+
);
|
|
9211
|
+
lines.push(` prompts ${contract.mcp.prompts.map((prompt) => prompt.name).join(", ")}`);
|
|
9212
|
+
lines.push(` resources ${contract.mcp.resources.join(", ")}`);
|
|
9213
|
+
lines.push(` templates ${contract.mcp.resourceTemplates.join(", ")}`);
|
|
9214
|
+
lines.push("");
|
|
8082
9215
|
lines.push("Safety:");
|
|
8083
9216
|
for (const note of contract.safety) lines.push(` - ${note}`);
|
|
8084
9217
|
lines.push("");
|
|
@@ -8104,11 +9237,13 @@ function buildAgentContract() {
|
|
|
8104
9237
|
invocation: {
|
|
8105
9238
|
oneShot: `npx ${PACKAGE_NAME}@latest`,
|
|
8106
9239
|
installed: "npx codebase-snapshot",
|
|
8107
|
-
bins: ["codebase-snapshot", "
|
|
9240
|
+
bins: ["codebase-snapshot", "snapshot"],
|
|
8108
9241
|
notes: [
|
|
8109
9242
|
`The bare package name also works: npx ${PACKAGE_NAME} <command>.`,
|
|
8110
|
-
"
|
|
9243
|
+
"`codebase-snapshot` is the canonical name and the one to script against. `snapshot` is the short alias a global install provides; in a project that has the package installed, `npx snapshot` resolves the local bin first.",
|
|
9244
|
+
`Pin an exact version \u2014 for example \`npx ${PACKAGE_NAME}@${GENERATOR_VERSION} --version\` \u2014 when you need the same build on every machine. A major range is not enough before 1.0: npm's caret on a 0.x version pins the minor, and 0.x minors are breaking by convention.`,
|
|
8111
9245
|
"Both bin names point at the same file. npm resolves a package by its bin entries, so they must stay aliases of one executable.",
|
|
9246
|
+
"A bare run in a project with no configuration asks once, sets the project up and generates \u2014 but only at a terminal. With no TTY it exits 2 and names the alternatives, so an unattended caller must say what it wants: `generate` after `init --yes`, or `--autopilot`.",
|
|
8112
9247
|
"Human progress goes to stderr. With --format json the payload on stdout is pure JSON.",
|
|
8113
9248
|
"A failing run also writes pure JSON to stdout: { error: { message, exitCode } }. The exit code and the stderr line are unchanged."
|
|
8114
9249
|
]
|
|
@@ -8119,7 +9254,7 @@ function buildAgentContract() {
|
|
|
8119
9254
|
id: "verify",
|
|
8120
9255
|
run: `npx ${PACKAGE_NAME}@latest --version`,
|
|
8121
9256
|
// The requirement is the major version; the exact build is context. A
|
|
8122
|
-
// floating tag may print
|
|
9257
|
+
// floating tag may print a different patch today than next week, and an agent
|
|
8123
9258
|
// must not conclude that a working tool is broken because of a patch.
|
|
8124
9259
|
expect: `prints v${MAJOR_VERSION} or newer on stdout and exits 0 (this build is v${GENERATOR_VERSION}: compare the major version, not the patch)`,
|
|
8125
9260
|
notes: `If it prints an older major than this contract's, the @latest tag is behind the release: pin the exact version with \`npx ${PACKAGE_NAME}@${GENERATOR_VERSION} --version\` instead of retrying @latest. If it prints nothing, clear the npm cache and retry, or use \`npx -p <pkg> codebase-snapshot\`.`
|
|
@@ -8163,9 +9298,9 @@ function buildAgentContract() {
|
|
|
8163
9298
|
{
|
|
8164
9299
|
name: "generate",
|
|
8165
9300
|
aliases: ["default"],
|
|
8166
|
-
summary: "Write the snapshot. This is the command you normally want.",
|
|
9301
|
+
summary: "Write the snapshot. This is the command you normally want. In a project with no configuration, a terminal asks once and sets the project up first; without a terminal it exits 2 and names `--autopilot` and `init --yes`.",
|
|
8167
9302
|
positionals: [],
|
|
8168
|
-
exits: [0, 1]
|
|
9303
|
+
exits: [0, 1, 2]
|
|
8169
9304
|
},
|
|
8170
9305
|
{
|
|
8171
9306
|
name: "validate",
|
|
@@ -8222,6 +9357,13 @@ function buildAgentContract() {
|
|
|
8222
9357
|
summary: "Print the agent skill, or --install it into the tool registries this repository's agent reads (.claude/skills, .cursor/rules). Files it creates are added to .gitignore. It never writes AGENTS.md: that file belongs to the repository.",
|
|
8223
9358
|
positionals: [],
|
|
8224
9359
|
exits: [0, 1, 2]
|
|
9360
|
+
},
|
|
9361
|
+
{
|
|
9362
|
+
name: "mcp",
|
|
9363
|
+
aliases: [],
|
|
9364
|
+
summary: "Serve this repository to an MCP client over stdio. The tools, resources and protocol versions it offers are listed in this contract's `mcp` block.",
|
|
9365
|
+
positionals: [],
|
|
9366
|
+
exits: [0, 2]
|
|
8225
9367
|
}
|
|
8226
9368
|
],
|
|
8227
9369
|
exitCodes: {
|
|
@@ -8372,6 +9514,13 @@ function buildAgentContract() {
|
|
|
8372
9514
|
appliesTo: optionAppliesTo("force"),
|
|
8373
9515
|
summary: "Overwrite an existing config file."
|
|
8374
9516
|
},
|
|
9517
|
+
{
|
|
9518
|
+
flag: "--autopilot",
|
|
9519
|
+
type: "boolean",
|
|
9520
|
+
default: false,
|
|
9521
|
+
appliesTo: optionAppliesTo("autopilot"),
|
|
9522
|
+
summary: "Set the project up without asking, then generate: detect the preset, write the config and .gitignore, add the npm scripts and devDependency, install with the project's own package manager, generate. Never overwrites an existing config, and never prompts. Refuses an empty directory (exit 1); contradicts --no-config (exit 2)."
|
|
9523
|
+
},
|
|
8375
9524
|
{
|
|
8376
9525
|
flag: "--install",
|
|
8377
9526
|
type: "boolean",
|
|
@@ -8391,7 +9540,7 @@ function buildAgentContract() {
|
|
|
8391
9540
|
type: "boolean",
|
|
8392
9541
|
default: false,
|
|
8393
9542
|
appliesTo: optionAppliesTo("no-install"),
|
|
8394
|
-
summary: "
|
|
9543
|
+
summary: "Skip installing the dependency before generating: `setup`, or `generate --autopilot` (CI, offline, or a lockfile you do not want a package manager to touch)."
|
|
8395
9544
|
}
|
|
8396
9545
|
],
|
|
8397
9546
|
presets: PRESETS.map((preset) => ({
|
|
@@ -8463,6 +9612,36 @@ function buildAgentContract() {
|
|
|
8463
9612
|
"The tool refuses to replace an outDir that it did not create, so a mistyped outDir cannot delete source code.",
|
|
8464
9613
|
"Nothing is ever written above the repository root; --out-dir may not be the root or an ancestor."
|
|
8465
9614
|
],
|
|
9615
|
+
mcp: {
|
|
9616
|
+
command: `npx ${PACKAGE_NAME} mcp`,
|
|
9617
|
+
transport: "stdio",
|
|
9618
|
+
protocolVersions: {
|
|
9619
|
+
modern: [...MODERN_PROTOCOL_VERSIONS],
|
|
9620
|
+
legacy: [...LEGACY_PROTOCOL_VERSIONS],
|
|
9621
|
+
default: DEFAULT_LEGACY_PROTOCOL_VERSION
|
|
9622
|
+
},
|
|
9623
|
+
tools: buildTools().map((tool) => ({
|
|
9624
|
+
name: tool.name,
|
|
9625
|
+
title: tool.title,
|
|
9626
|
+
summary: tool.description,
|
|
9627
|
+
readOnly: tool.annotations.readOnlyHint,
|
|
9628
|
+
parameters: Object.keys(tool.inputSchema.properties),
|
|
9629
|
+
required: tool.inputSchema.required ?? []
|
|
9630
|
+
})),
|
|
9631
|
+
prompts: buildPrompts().map((prompt) => ({
|
|
9632
|
+
name: prompt.name,
|
|
9633
|
+
summary: prompt.description,
|
|
9634
|
+
arguments: prompt.arguments.map((argument) => argument.name)
|
|
9635
|
+
})),
|
|
9636
|
+
resources: [...STATIC_RESOURCE_URIS],
|
|
9637
|
+
resourceTemplates: [...RESOURCE_TEMPLATE_URIS],
|
|
9638
|
+
notes: [
|
|
9639
|
+
"stdout carries JSON-RPC messages only; every note and log line goes to stderr.",
|
|
9640
|
+
"The server speaks both protocol eras: the legacy `initialize` handshake and the stateless revision, which is detected from `server/discover` or from per-request `_meta`.",
|
|
9641
|
+
"Everything it can do is a subset of the CLI. It never installs packages and never writes a config file.",
|
|
9642
|
+
"A `root` argument may name a sub-directory of the server's --cwd, and nothing outside it is readable."
|
|
9643
|
+
]
|
|
9644
|
+
},
|
|
8466
9645
|
troubleshooting: [
|
|
8467
9646
|
{
|
|
8468
9647
|
symptom: "Command prints nothing and exits 0",
|
|
@@ -8520,10 +9699,11 @@ globally, and you do not need to clone anything.
|
|
|
8520
9699
|
|
|
8521
9700
|
**Pin a version** \u2014 \`npx ${PACKAGE_NAME2}@${GENERATOR_VERSION} <command>\` \u2014 when you
|
|
8522
9701
|
need the same version on every machine. The bare package name works too: this package ships
|
|
8523
|
-
one executable file, published under two names (\`codebase-snapshot\` and \`
|
|
8524
|
-
lets npm pick it.
|
|
8525
|
-
|
|
8526
|
-
|
|
9702
|
+
one executable file, published under two names (\`codebase-snapshot\` and \`snapshot\`), which is
|
|
9703
|
+
what lets npm pick it. A global install gives the short command \`snapshot\`; inside a project
|
|
9704
|
+
that has this package installed, \`npx snapshot\` resolves the local copy first. If a command
|
|
9705
|
+
exits 0 printing nothing, the executable was not resolved \u2014 clear the npm cache and retry;
|
|
9706
|
+
do not assume the repository is broken.
|
|
8527
9707
|
|
|
8528
9708
|
## Step by step
|
|
8529
9709
|
|
|
@@ -8548,8 +9728,11 @@ is broken.
|
|
|
8548
9728
|
does steps 4\u20136 in one go with defaults: it writes the config (an existing one is kept),
|
|
8549
9729
|
adds the npm scripts and the devDependency, installs with the project's package manager,
|
|
8550
9730
|
and generates. Use \`--no-install\` when the machine is offline or the user does not want
|
|
8551
|
-
their lockfile touched.
|
|
8552
|
-
|
|
9731
|
+
their lockfile touched. \`--autopilot\` does exactly the same thing from the default
|
|
9732
|
+
command (\`npx ${PACKAGE_NAME2}@latest --autopilot\`) and is the form to reach for when a
|
|
9733
|
+
single invocation must both set the project up and produce the snapshot. Prefer the
|
|
9734
|
+
explicit steps below when you need to control any of those, or when something fails and
|
|
9735
|
+
you want to see which part.
|
|
8553
9736
|
|
|
8554
9737
|
4. **Configure (recommended).** Run \`npx ${PACKAGE_NAME2}@latest init --yes\`. It detects
|
|
8555
9738
|
the project shape, then writes a commented config file listing **every** setting at its
|
|
@@ -8557,7 +9740,10 @@ is broken.
|
|
|
8557
9740
|
the running Node can strip types (Node >= 22.6) **and** the project is an ES module
|
|
8558
9741
|
(\`"type": "module"\` in package.json); otherwise \`snapshot.config.mjs\`. Pass
|
|
8559
9742
|
\`--preset <name>\` only if detection is wrong; the preset names are in the contract.
|
|
8560
|
-
|
|
9743
|
+
Skipping this step is safe **at a terminal**: the first \`generate\` asks once, can write
|
|
9744
|
+
the config for you and then generates. Unattended, a \`generate\` with no config exits 2 \u2014
|
|
9745
|
+
run this step, or \`--autopilot\`, or pass \`--no-config\` to run deliberately on the
|
|
9746
|
+
built-in defaults.
|
|
8561
9747
|
|
|
8562
9748
|
5. **Generate.** Run \`npx ${PACKAGE_NAME2}@latest generate\`. Expect exit 0 and a
|
|
8563
9749
|
\`./snapshot/\` directory. If you need a different location use \`--out-dir <path>\`; if
|
|
@@ -8605,6 +9791,10 @@ any of this to get a good first snapshot.
|
|
|
8605
9791
|
deleting anything.
|
|
8606
9792
|
- **Exit 2 is a usage error**, not a failure of the repository. Re-read the contract
|
|
8607
9793
|
before retrying.
|
|
9794
|
+
- **A first run refuses rather than guessing.** In a project with no config and no previous
|
|
9795
|
+
snapshot, \`generate\` without a terminal exits 2 instead of silently using defaults. That
|
|
9796
|
+
is deliberate: say which set-up you want (\`--autopilot\`, \`init --yes\`, or \`--no-config\`)
|
|
9797
|
+
rather than retrying the same command.
|
|
8608
9798
|
- **\`--format json\` keeps stdout pure.** Progress goes to stderr, so \`stdout\` is safe to
|
|
8609
9799
|
pipe into a JSON parser.
|
|
8610
9800
|
|
|
@@ -8620,6 +9810,14 @@ It never writes \`AGENTS.md\`. That file is the repository's own agent contract
|
|
|
8620
9810
|
commands, conventions, house rules \u2014 and this tool has no business editing it. If you want
|
|
8621
9811
|
this skill in there, run \`npx ${PACKAGE_NAME2}@latest skill\` and paste the text yourself: the
|
|
8622
9812
|
diff, and the commit, are then yours.
|
|
9813
|
+
|
|
9814
|
+
## When the client speaks MCP
|
|
9815
|
+
|
|
9816
|
+
If the agent runs inside an MCP client, \`npx ${PACKAGE_NAME2}@latest mcp\` serves this repository
|
|
9817
|
+
as seven tools over stdio instead of shelling out. Its reads come from the snapshot rather than
|
|
9818
|
+
the working tree, so excluded files and redacted values never reach the context. Use
|
|
9819
|
+
\`snapshot_locate\` before \`snapshot_read\`, and \`snapshot_validate\` before trusting a snapshot
|
|
9820
|
+
from an earlier session.
|
|
8623
9821
|
`;
|
|
8624
9822
|
}
|
|
8625
9823
|
|
|
@@ -8630,17 +9828,17 @@ var LEGACY_AGENTS_MARKER = "<!-- codebase-snapshot:skill:start -->";
|
|
|
8630
9828
|
function targetPath(cwd, target) {
|
|
8631
9829
|
switch (target) {
|
|
8632
9830
|
case "claude":
|
|
8633
|
-
return
|
|
9831
|
+
return path12__namespace.join(cwd, ".claude", "skills", SKILL_NAME, "SKILL.md");
|
|
8634
9832
|
case "cursor":
|
|
8635
|
-
return
|
|
9833
|
+
return path12__namespace.join(cwd, ".cursor", "rules", `${SKILL_NAME}.mdc`);
|
|
8636
9834
|
}
|
|
8637
9835
|
}
|
|
8638
9836
|
function assertWritableInsideRepository(cwd, filePath) {
|
|
8639
|
-
const root =
|
|
8640
|
-
const shown =
|
|
9837
|
+
const root = fs11__namespace.realpathSync(cwd);
|
|
9838
|
+
const shown = path12__namespace.relative(root, filePath) || filePath;
|
|
8641
9839
|
let stat = null;
|
|
8642
9840
|
try {
|
|
8643
|
-
stat =
|
|
9841
|
+
stat = fs11__namespace.lstatSync(filePath);
|
|
8644
9842
|
} catch {
|
|
8645
9843
|
stat = null;
|
|
8646
9844
|
}
|
|
@@ -8656,32 +9854,32 @@ function assertWritableInsideRepository(cwd, filePath) {
|
|
|
8656
9854
|
}
|
|
8657
9855
|
let existing = filePath;
|
|
8658
9856
|
const missing = [];
|
|
8659
|
-
while (!
|
|
8660
|
-
missing.unshift(
|
|
8661
|
-
const parent =
|
|
9857
|
+
while (!fs11__namespace.existsSync(existing)) {
|
|
9858
|
+
missing.unshift(path12__namespace.basename(existing));
|
|
9859
|
+
const parent = path12__namespace.dirname(existing);
|
|
8662
9860
|
if (parent === existing) break;
|
|
8663
9861
|
existing = parent;
|
|
8664
9862
|
}
|
|
8665
|
-
const realAncestor =
|
|
8666
|
-
const resolved = missing.length === 0 ? realAncestor :
|
|
8667
|
-
if (resolved !== root && !resolved.startsWith(`${root}${
|
|
9863
|
+
const realAncestor = fs11__namespace.realpathSync(existing);
|
|
9864
|
+
const resolved = missing.length === 0 ? realAncestor : path12__namespace.join(realAncestor, ...missing);
|
|
9865
|
+
if (resolved !== root && !resolved.startsWith(`${root}${path12__namespace.sep}`)) {
|
|
8668
9866
|
throw new Error(
|
|
8669
9867
|
`${REFUSED}: ${shown} resolves outside the repository (${resolved}). Move it back inside ${root}, or remove the link, and run again.`
|
|
8670
9868
|
);
|
|
8671
9869
|
}
|
|
8672
9870
|
}
|
|
8673
9871
|
function writeIfChanged(target, filePath, content) {
|
|
8674
|
-
const existing =
|
|
9872
|
+
const existing = fs11__namespace.existsSync(filePath) ? fs11__namespace.readFileSync(filePath, "utf8") : null;
|
|
8675
9873
|
if (existing === content) return { target, path: filePath, action: "unchanged" };
|
|
8676
|
-
|
|
8677
|
-
|
|
9874
|
+
fs11__namespace.mkdirSync(path12__namespace.dirname(filePath), { recursive: true });
|
|
9875
|
+
fs11__namespace.writeFileSync(filePath, content, "utf8");
|
|
8678
9876
|
return { target, path: filePath, action: existing === null ? "created" : "updated" };
|
|
8679
9877
|
}
|
|
8680
9878
|
function legacyAgentsNote(cwd) {
|
|
8681
|
-
const filePath =
|
|
9879
|
+
const filePath = path12__namespace.join(cwd, "AGENTS.md");
|
|
8682
9880
|
try {
|
|
8683
|
-
if (!
|
|
8684
|
-
if (!
|
|
9881
|
+
if (!fs11__namespace.existsSync(filePath)) return null;
|
|
9882
|
+
if (!fs11__namespace.readFileSync(filePath, "utf8").includes(LEGACY_AGENTS_MARKER)) return null;
|
|
8685
9883
|
} catch {
|
|
8686
9884
|
return null;
|
|
8687
9885
|
}
|
|
@@ -8693,9 +9891,9 @@ function installSkill(cwd, target = "all") {
|
|
|
8693
9891
|
const notes = [];
|
|
8694
9892
|
const wanted = target === "all" ? ["claude", "cursor"] : [target];
|
|
8695
9893
|
for (const item of wanted) assertWritableInsideRepository(cwd, targetPath(cwd, item));
|
|
8696
|
-
const createsSomething = wanted.some((item) => !
|
|
9894
|
+
const createsSomething = wanted.some((item) => !fs11__namespace.existsSync(targetPath(cwd, item)));
|
|
8697
9895
|
if (createsSomething) {
|
|
8698
|
-
assertWritableInsideRepository(cwd,
|
|
9896
|
+
assertWritableInsideRepository(cwd, path12__namespace.join(cwd, ".gitignore"));
|
|
8699
9897
|
}
|
|
8700
9898
|
for (const item of wanted) {
|
|
8701
9899
|
written.push(writeIfChanged(item, targetPath(cwd, item), skill));
|
|
@@ -8711,6 +9909,765 @@ function isSkillTarget(value) {
|
|
|
8711
9909
|
return SKILL_TARGETS.includes(value);
|
|
8712
9910
|
}
|
|
8713
9911
|
|
|
9912
|
+
// src/mcp/era.ts
|
|
9913
|
+
function createSession() {
|
|
9914
|
+
return { era: null, protocolVersion: null, ready: false };
|
|
9915
|
+
}
|
|
9916
|
+
function isLegacyVersion(version) {
|
|
9917
|
+
return LEGACY_PROTOCOL_VERSIONS.includes(version);
|
|
9918
|
+
}
|
|
9919
|
+
function isModernVersion(version) {
|
|
9920
|
+
return MODERN_PROTOCOL_VERSIONS.includes(version);
|
|
9921
|
+
}
|
|
9922
|
+
function inspectModernMeta(params) {
|
|
9923
|
+
const raw = params?._meta;
|
|
9924
|
+
if (!isJsonObject(raw)) return { kind: "absent" };
|
|
9925
|
+
const version = raw[META_KEYS.protocolVersion];
|
|
9926
|
+
if (typeof version !== "string" || version.length === 0) {
|
|
9927
|
+
return { kind: "incomplete", missing: META_KEYS.protocolVersion };
|
|
9928
|
+
}
|
|
9929
|
+
const capabilities2 = raw[META_KEYS.clientCapabilities];
|
|
9930
|
+
if (!isJsonObject(capabilities2)) {
|
|
9931
|
+
return { kind: "incomplete", missing: META_KEYS.clientCapabilities };
|
|
9932
|
+
}
|
|
9933
|
+
const info2 = raw[META_KEYS.clientInfo];
|
|
9934
|
+
return {
|
|
9935
|
+
kind: "present",
|
|
9936
|
+
meta: {
|
|
9937
|
+
version,
|
|
9938
|
+
clientCapabilities: capabilities2,
|
|
9939
|
+
clientInfo: isJsonObject(info2) ? info2 : null
|
|
9940
|
+
}
|
|
9941
|
+
};
|
|
9942
|
+
}
|
|
9943
|
+
function unsupportedVersionData(requested) {
|
|
9944
|
+
return { requested, supported: [...SUPPORTED_PROTOCOL_VERSIONS] };
|
|
9945
|
+
}
|
|
9946
|
+
function classifyRequest(method, params, session) {
|
|
9947
|
+
const inspection = inspectModernMeta(params);
|
|
9948
|
+
if (session.era === "modern") {
|
|
9949
|
+
if (inspection.kind === "absent") {
|
|
9950
|
+
return {
|
|
9951
|
+
kind: "error",
|
|
9952
|
+
code: ERROR_CODES.invalidRequest,
|
|
9953
|
+
message: `This request has no _meta. The modern era requires "${META_KEYS.protocolVersion}" and "${META_KEYS.clientCapabilities}" on every request.`
|
|
9954
|
+
};
|
|
9955
|
+
}
|
|
9956
|
+
if (inspection.kind === "incomplete") {
|
|
9957
|
+
return {
|
|
9958
|
+
kind: "error",
|
|
9959
|
+
code: ERROR_CODES.invalidParams,
|
|
9960
|
+
message: `The request _meta is missing the required key "${inspection.missing}".`
|
|
9961
|
+
};
|
|
9962
|
+
}
|
|
9963
|
+
if (!isModernVersion(inspection.meta.version)) {
|
|
9964
|
+
return {
|
|
9965
|
+
kind: "error",
|
|
9966
|
+
code: ERROR_CODES.unsupportedProtocolVersion,
|
|
9967
|
+
message: `Unsupported protocol version "${inspection.meta.version}".`,
|
|
9968
|
+
data: unsupportedVersionData(inspection.meta.version)
|
|
9969
|
+
};
|
|
9970
|
+
}
|
|
9971
|
+
return { kind: "era", era: "modern", version: inspection.meta.version };
|
|
9972
|
+
}
|
|
9973
|
+
if (session.era === "legacy") {
|
|
9974
|
+
return { kind: "era", era: "legacy", version: session.protocolVersion };
|
|
9975
|
+
}
|
|
9976
|
+
if (method === METHOD3.initialize) {
|
|
9977
|
+
return { kind: "era", era: "legacy", version: null };
|
|
9978
|
+
}
|
|
9979
|
+
if (inspection.kind === "present") {
|
|
9980
|
+
if (!isModernVersion(inspection.meta.version)) {
|
|
9981
|
+
return {
|
|
9982
|
+
kind: "error",
|
|
9983
|
+
code: ERROR_CODES.unsupportedProtocolVersion,
|
|
9984
|
+
message: `Unsupported protocol version "${inspection.meta.version}".`,
|
|
9985
|
+
data: unsupportedVersionData(inspection.meta.version)
|
|
9986
|
+
};
|
|
9987
|
+
}
|
|
9988
|
+
return { kind: "era", era: "modern", version: inspection.meta.version };
|
|
9989
|
+
}
|
|
9990
|
+
if (inspection.kind === "incomplete") {
|
|
9991
|
+
return {
|
|
9992
|
+
kind: "error",
|
|
9993
|
+
code: ERROR_CODES.invalidParams,
|
|
9994
|
+
message: `The request _meta is missing the required key "${inspection.missing}".`
|
|
9995
|
+
};
|
|
9996
|
+
}
|
|
9997
|
+
if (method === METHOD3.discover) {
|
|
9998
|
+
return { kind: "era", era: "modern", version: MODERN_PROTOCOL_VERSIONS[0] };
|
|
9999
|
+
}
|
|
10000
|
+
return { kind: "era", era: "legacy", version: null };
|
|
10001
|
+
}
|
|
10002
|
+
function negotiateLegacyVersion(requested) {
|
|
10003
|
+
if (typeof requested === "string" && isLegacyVersion(requested)) {
|
|
10004
|
+
return { version: requested, matched: true };
|
|
10005
|
+
}
|
|
10006
|
+
return { version: DEFAULT_LEGACY_PROTOCOL_VERSION, matched: false };
|
|
10007
|
+
}
|
|
10008
|
+
|
|
10009
|
+
// src/mcp/schema.ts
|
|
10010
|
+
function validateArguments(schema, value) {
|
|
10011
|
+
if (value === void 0) value = {};
|
|
10012
|
+
if (!isJsonObject(value)) return ["arguments must be a JSON object"];
|
|
10013
|
+
const problems = [];
|
|
10014
|
+
const known = new Set(Object.keys(schema.properties));
|
|
10015
|
+
for (const key of Object.keys(value)) {
|
|
10016
|
+
if (!known.has(key) && schema.additionalProperties !== true) {
|
|
10017
|
+
const allowed = [...known].sort().join(", ");
|
|
10018
|
+
problems.push(`unknown argument "${key}" (accepted: ${allowed || "none"})`);
|
|
10019
|
+
}
|
|
10020
|
+
}
|
|
10021
|
+
for (const required of schema.required ?? []) {
|
|
10022
|
+
if (value[required] === void 0) problems.push(`missing required argument "${required}"`);
|
|
10023
|
+
}
|
|
10024
|
+
for (const [key, propertySchema] of Object.entries(schema.properties)) {
|
|
10025
|
+
const argument = value[key];
|
|
10026
|
+
if (argument === void 0) continue;
|
|
10027
|
+
problems.push(...checkValue(key, propertySchema, argument));
|
|
10028
|
+
}
|
|
10029
|
+
return problems;
|
|
10030
|
+
}
|
|
10031
|
+
function checkValue(path18, schema, value) {
|
|
10032
|
+
if (schema.enum && !schema.enum.includes(value)) {
|
|
10033
|
+
return [`"${path18}" must be one of: ${schema.enum.join(", ")}`];
|
|
10034
|
+
}
|
|
10035
|
+
switch (schema.type) {
|
|
10036
|
+
case "string": {
|
|
10037
|
+
if (typeof value !== "string") return [`"${path18}" must be a string`];
|
|
10038
|
+
const problems = [];
|
|
10039
|
+
if (schema.minLength !== void 0 && value.length < schema.minLength) {
|
|
10040
|
+
problems.push(`"${path18}" must be at least ${schema.minLength} character(s)`);
|
|
10041
|
+
}
|
|
10042
|
+
if (schema.maxLength !== void 0 && value.length > schema.maxLength) {
|
|
10043
|
+
problems.push(`"${path18}" must be at most ${schema.maxLength} character(s)`);
|
|
10044
|
+
}
|
|
10045
|
+
return problems;
|
|
10046
|
+
}
|
|
10047
|
+
case "number":
|
|
10048
|
+
case "integer": {
|
|
10049
|
+
if (typeof value !== "number" || Number.isNaN(value)) {
|
|
10050
|
+
return [`"${path18}" must be a number`];
|
|
10051
|
+
}
|
|
10052
|
+
if (schema.type === "integer" && !Number.isInteger(value)) {
|
|
10053
|
+
return [`"${path18}" must be an integer`];
|
|
10054
|
+
}
|
|
10055
|
+
const problems = [];
|
|
10056
|
+
if (schema.minimum !== void 0 && value < schema.minimum) {
|
|
10057
|
+
problems.push(`"${path18}" must be >= ${schema.minimum}`);
|
|
10058
|
+
}
|
|
10059
|
+
if (schema.maximum !== void 0 && value > schema.maximum) {
|
|
10060
|
+
problems.push(`"${path18}" must be <= ${schema.maximum}`);
|
|
10061
|
+
}
|
|
10062
|
+
return problems;
|
|
10063
|
+
}
|
|
10064
|
+
case "boolean":
|
|
10065
|
+
return typeof value === "boolean" ? [] : [`"${path18}" must be a boolean`];
|
|
10066
|
+
case "array": {
|
|
10067
|
+
if (!Array.isArray(value)) return [`"${path18}" must be an array`];
|
|
10068
|
+
if (!schema.items) return [];
|
|
10069
|
+
const problems = [];
|
|
10070
|
+
value.forEach((item, index) => {
|
|
10071
|
+
problems.push(...checkValue(`${path18}[${index}]`, schema.items, item));
|
|
10072
|
+
});
|
|
10073
|
+
return problems;
|
|
10074
|
+
}
|
|
10075
|
+
case "object":
|
|
10076
|
+
return isJsonObject(value) ? [] : [`"${path18}" must be an object`];
|
|
10077
|
+
default:
|
|
10078
|
+
return [];
|
|
10079
|
+
}
|
|
10080
|
+
}
|
|
10081
|
+
function applyDefaults(schema, value) {
|
|
10082
|
+
const out = { ...value };
|
|
10083
|
+
for (const [key, propertySchema] of Object.entries(schema.properties)) {
|
|
10084
|
+
if (out[key] === void 0 && propertySchema.default !== void 0) {
|
|
10085
|
+
out[key] = propertySchema.default;
|
|
10086
|
+
}
|
|
10087
|
+
}
|
|
10088
|
+
return out;
|
|
10089
|
+
}
|
|
10090
|
+
|
|
10091
|
+
// src/mcp/server.ts
|
|
10092
|
+
function capabilities() {
|
|
10093
|
+
return {
|
|
10094
|
+
tools: { listChanged: false },
|
|
10095
|
+
resources: { subscribe: false, listChanged: false },
|
|
10096
|
+
prompts: { listChanged: false }
|
|
10097
|
+
};
|
|
10098
|
+
}
|
|
10099
|
+
function requestIdOf(message) {
|
|
10100
|
+
const raw = message.id;
|
|
10101
|
+
return typeof raw === "string" || typeof raw === "number" ? raw : null;
|
|
10102
|
+
}
|
|
10103
|
+
function createMcpServer(options) {
|
|
10104
|
+
const session = createSession();
|
|
10105
|
+
const toolsByName = new Map(options.tools.map((tool) => [tool.name, tool]));
|
|
10106
|
+
const promptsByName = new Map(options.prompts.map((prompt) => [prompt.name, prompt]));
|
|
10107
|
+
const identity = () => ({
|
|
10108
|
+
name: options.info.name,
|
|
10109
|
+
title: options.info.title,
|
|
10110
|
+
version: options.info.version,
|
|
10111
|
+
websiteUrl: options.info.websiteUrl
|
|
10112
|
+
});
|
|
10113
|
+
const ok = (id, result, modern) => successResponse(id, result, modern, options.info);
|
|
10114
|
+
function handleNotification(method) {
|
|
10115
|
+
if (method === METHOD3.initialized && session.era === "legacy") session.ready = true;
|
|
10116
|
+
}
|
|
10117
|
+
function initialize(id, params) {
|
|
10118
|
+
const negotiation = negotiateLegacyVersion(params?.protocolVersion);
|
|
10119
|
+
session.era = "legacy";
|
|
10120
|
+
session.protocolVersion = negotiation.version;
|
|
10121
|
+
session.ready = true;
|
|
10122
|
+
options.context.log(
|
|
10123
|
+
negotiation.matched ? `mcp: client initialized with protocol ${negotiation.version}` : `mcp: client asked for an unsupported version and was offered ${negotiation.version}`
|
|
10124
|
+
);
|
|
10125
|
+
return {
|
|
10126
|
+
jsonrpc: "2.0",
|
|
10127
|
+
id,
|
|
10128
|
+
result: {
|
|
10129
|
+
protocolVersion: negotiation.version,
|
|
10130
|
+
capabilities: capabilities(),
|
|
10131
|
+
serverInfo: identity(),
|
|
10132
|
+
instructions: options.instructions
|
|
10133
|
+
}
|
|
10134
|
+
};
|
|
10135
|
+
}
|
|
10136
|
+
function discover(id) {
|
|
10137
|
+
session.era = "modern";
|
|
10138
|
+
session.ready = true;
|
|
10139
|
+
return ok(
|
|
10140
|
+
id,
|
|
10141
|
+
{
|
|
10142
|
+
supportedVersions: [...SUPPORTED_PROTOCOL_VERSIONS],
|
|
10143
|
+
capabilities: capabilities(),
|
|
10144
|
+
instructions: options.instructions,
|
|
10145
|
+
// The capability set depends on which repository the server was started for, so the answer
|
|
10146
|
+
// may be cached only by the client that asked, and not at all after this response.
|
|
10147
|
+
cacheScope: "private",
|
|
10148
|
+
ttlMs: 0
|
|
10149
|
+
},
|
|
10150
|
+
true
|
|
10151
|
+
);
|
|
10152
|
+
}
|
|
10153
|
+
function listTools(id, modern) {
|
|
10154
|
+
const tools = options.tools.map((tool) => ({
|
|
10155
|
+
name: tool.name,
|
|
10156
|
+
title: tool.title,
|
|
10157
|
+
description: tool.description,
|
|
10158
|
+
inputSchema: tool.inputSchema,
|
|
10159
|
+
...tool.outputSchema ? { outputSchema: tool.outputSchema } : {},
|
|
10160
|
+
annotations: tool.annotations
|
|
10161
|
+
}));
|
|
10162
|
+
return ok(id, { tools }, modern);
|
|
10163
|
+
}
|
|
10164
|
+
async function callTool(id, modern, params) {
|
|
10165
|
+
const name = params?.name;
|
|
10166
|
+
if (typeof name !== "string") {
|
|
10167
|
+
return errorResponse(id, ERROR_CODES.invalidParams, 'tools/call requires a "name" string.');
|
|
10168
|
+
}
|
|
10169
|
+
const tool = toolsByName.get(name);
|
|
10170
|
+
if (!tool) {
|
|
10171
|
+
const known = [...toolsByName.keys()].sort().join(", ");
|
|
10172
|
+
return errorResponse(id, ERROR_CODES.invalidParams, `Unknown tool "${name}". Available: ${known}.`);
|
|
10173
|
+
}
|
|
10174
|
+
const rawArguments = params?.arguments ?? {};
|
|
10175
|
+
if (!isJsonObject(rawArguments)) {
|
|
10176
|
+
return errorResponse(id, ERROR_CODES.invalidParams, '"arguments" must be a JSON object.');
|
|
10177
|
+
}
|
|
10178
|
+
const problems = validateArguments(tool.inputSchema, rawArguments);
|
|
10179
|
+
if (problems.length > 0) {
|
|
10180
|
+
return ok(
|
|
10181
|
+
id,
|
|
10182
|
+
{
|
|
10183
|
+
content: [{ type: "text", text: `Invalid arguments for ${tool.name}:
|
|
10184
|
+
- ${problems.join("\n- ")}` }],
|
|
10185
|
+
isError: true
|
|
10186
|
+
},
|
|
10187
|
+
modern
|
|
10188
|
+
);
|
|
10189
|
+
}
|
|
10190
|
+
const args = applyDefaults(tool.inputSchema, rawArguments);
|
|
10191
|
+
let outcome;
|
|
10192
|
+
try {
|
|
10193
|
+
outcome = await tool.handler(args, options.context);
|
|
10194
|
+
} catch (error) {
|
|
10195
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
10196
|
+
options.context.log(`mcp: tool ${tool.name} failed: ${detail}`);
|
|
10197
|
+
return ok(
|
|
10198
|
+
id,
|
|
10199
|
+
{ content: [{ type: "text", text: `${tool.name} failed: ${detail}` }], isError: true },
|
|
10200
|
+
modern
|
|
10201
|
+
);
|
|
10202
|
+
}
|
|
10203
|
+
return ok(
|
|
10204
|
+
id,
|
|
10205
|
+
{
|
|
10206
|
+
content: [{ type: "text", text: outcome.text }],
|
|
10207
|
+
...outcome.structured ? { structuredContent: outcome.structured } : {},
|
|
10208
|
+
...outcome.isError ? { isError: true } : {}
|
|
10209
|
+
},
|
|
10210
|
+
modern
|
|
10211
|
+
);
|
|
10212
|
+
}
|
|
10213
|
+
async function listResources(id, modern) {
|
|
10214
|
+
const resources = await options.resources.list(options.context);
|
|
10215
|
+
return ok(id, { resources }, modern);
|
|
10216
|
+
}
|
|
10217
|
+
async function readResource(id, modern, params) {
|
|
10218
|
+
const uri = params?.uri;
|
|
10219
|
+
if (typeof uri !== "string") {
|
|
10220
|
+
return errorResponse(id, ERROR_CODES.invalidParams, 'resources/read requires a "uri" string.');
|
|
10221
|
+
}
|
|
10222
|
+
const contents = await options.resources.read(uri, options.context);
|
|
10223
|
+
if (!contents) {
|
|
10224
|
+
return errorResponse(id, ERROR_CODES.invalidParams, `Unknown resource "${uri}".`);
|
|
10225
|
+
}
|
|
10226
|
+
return ok(id, { contents: [contents] }, modern);
|
|
10227
|
+
}
|
|
10228
|
+
function listPrompts(id, modern) {
|
|
10229
|
+
const prompts = options.prompts.map((prompt) => ({
|
|
10230
|
+
name: prompt.name,
|
|
10231
|
+
...prompt.title ? { title: prompt.title } : {},
|
|
10232
|
+
description: prompt.description,
|
|
10233
|
+
arguments: prompt.arguments
|
|
10234
|
+
}));
|
|
10235
|
+
return ok(id, { prompts }, modern);
|
|
10236
|
+
}
|
|
10237
|
+
function getPrompt(id, modern, params) {
|
|
10238
|
+
const name = params?.name;
|
|
10239
|
+
if (typeof name !== "string") {
|
|
10240
|
+
return errorResponse(id, ERROR_CODES.invalidParams, 'prompts/get requires a "name" string.');
|
|
10241
|
+
}
|
|
10242
|
+
const prompt = promptsByName.get(name);
|
|
10243
|
+
if (!prompt) {
|
|
10244
|
+
const known = [...promptsByName.keys()].sort().join(", ");
|
|
10245
|
+
return errorResponse(id, ERROR_CODES.invalidParams, `Unknown prompt "${name}". Available: ${known}.`);
|
|
10246
|
+
}
|
|
10247
|
+
const rawArguments = params?.arguments ?? {};
|
|
10248
|
+
if (!isJsonObject(rawArguments)) {
|
|
10249
|
+
return errorResponse(id, ERROR_CODES.invalidParams, '"arguments" must be a JSON object.');
|
|
10250
|
+
}
|
|
10251
|
+
const built = prompt.build(rawArguments);
|
|
10252
|
+
const messages = built.messages.map((message) => ({
|
|
10253
|
+
role: message.role,
|
|
10254
|
+
content: { type: "text", text: message.content.text }
|
|
10255
|
+
}));
|
|
10256
|
+
return ok(id, { description: built.description, messages }, modern);
|
|
10257
|
+
}
|
|
10258
|
+
async function route(name, id, modern, params) {
|
|
10259
|
+
switch (name) {
|
|
10260
|
+
case METHOD3.initialize:
|
|
10261
|
+
return modern ? errorResponse(
|
|
10262
|
+
id,
|
|
10263
|
+
ERROR_CODES.methodNotFound,
|
|
10264
|
+
'"initialize" does not exist in this protocol revision; call "server/discover".'
|
|
10265
|
+
) : initialize(id, params);
|
|
10266
|
+
case METHOD3.discover:
|
|
10267
|
+
return discover(id);
|
|
10268
|
+
case METHOD3.ping:
|
|
10269
|
+
return modern ? errorResponse(
|
|
10270
|
+
id,
|
|
10271
|
+
ERROR_CODES.methodNotFound,
|
|
10272
|
+
'"ping" was removed in this protocol revision; call "server/discover".'
|
|
10273
|
+
) : ok(id, {}, modern);
|
|
10274
|
+
}
|
|
10275
|
+
if (!session.ready) {
|
|
10276
|
+
return errorResponse(
|
|
10277
|
+
id,
|
|
10278
|
+
ERROR_CODES.invalidRequest,
|
|
10279
|
+
'This connection has not been initialized. Send "initialize" first.'
|
|
10280
|
+
);
|
|
10281
|
+
}
|
|
10282
|
+
switch (name) {
|
|
10283
|
+
case METHOD3.toolsList:
|
|
10284
|
+
return listTools(id, modern);
|
|
10285
|
+
case METHOD3.toolsCall:
|
|
10286
|
+
return callTool(id, modern, params);
|
|
10287
|
+
case METHOD3.resourcesList:
|
|
10288
|
+
return listResources(id, modern);
|
|
10289
|
+
case METHOD3.resourcesRead:
|
|
10290
|
+
return readResource(id, modern, params);
|
|
10291
|
+
case METHOD3.resourceTemplatesList:
|
|
10292
|
+
return ok(id, { resourceTemplates: options.resources.templates() }, modern);
|
|
10293
|
+
case METHOD3.promptsList:
|
|
10294
|
+
return listPrompts(id, modern);
|
|
10295
|
+
case METHOD3.promptsGet:
|
|
10296
|
+
return getPrompt(id, modern, params);
|
|
10297
|
+
default:
|
|
10298
|
+
return errorResponse(id, ERROR_CODES.methodNotFound, `Unknown method "${name}".`);
|
|
10299
|
+
}
|
|
10300
|
+
}
|
|
10301
|
+
async function dispatch(message) {
|
|
10302
|
+
if (!isJsonObject(message)) {
|
|
10303
|
+
return errorResponse(null, ERROR_CODES.parseError, "Message is not a JSON object.");
|
|
10304
|
+
}
|
|
10305
|
+
const isRequest = Object.prototype.hasOwnProperty.call(message, "id");
|
|
10306
|
+
const method = message.method;
|
|
10307
|
+
const params = message.params;
|
|
10308
|
+
if (message.jsonrpc !== "2.0" && isRequest) {
|
|
10309
|
+
return errorResponse(requestIdOf(message), ERROR_CODES.invalidRequest, 'Message must carry "jsonrpc": "2.0".');
|
|
10310
|
+
}
|
|
10311
|
+
if (typeof method !== "string" || method.length === 0) {
|
|
10312
|
+
return isRequest ? errorResponse(requestIdOf(message), ERROR_CODES.invalidRequest, 'Message must carry a "method" string.') : null;
|
|
10313
|
+
}
|
|
10314
|
+
if (params !== void 0 && !isJsonObject(params)) {
|
|
10315
|
+
return isRequest ? errorResponse(requestIdOf(message), ERROR_CODES.invalidRequest, '"params" must be a JSON object when present.') : null;
|
|
10316
|
+
}
|
|
10317
|
+
const named = params;
|
|
10318
|
+
const decision = classifyRequest(method, named, session);
|
|
10319
|
+
const modern = decision.kind === "era" ? decision.era === "modern" : session.era === "modern";
|
|
10320
|
+
if (!isRequest) {
|
|
10321
|
+
if (decision.kind === "era" && session.era === null) {
|
|
10322
|
+
session.era = decision.era;
|
|
10323
|
+
session.protocolVersion = decision.version;
|
|
10324
|
+
}
|
|
10325
|
+
handleNotification(method);
|
|
10326
|
+
return null;
|
|
10327
|
+
}
|
|
10328
|
+
const id = requestIdOf(message);
|
|
10329
|
+
if (id === null) {
|
|
10330
|
+
return errorResponse(null, ERROR_CODES.invalidRequest, 'A request "id" must be a string or a number.');
|
|
10331
|
+
}
|
|
10332
|
+
if (decision.kind === "error") {
|
|
10333
|
+
return errorResponse(id, decision.code, decision.message, decision.data);
|
|
10334
|
+
}
|
|
10335
|
+
if (session.era === null) {
|
|
10336
|
+
session.era = decision.era;
|
|
10337
|
+
session.protocolVersion = decision.version;
|
|
10338
|
+
}
|
|
10339
|
+
return route(method, id, modern, named);
|
|
10340
|
+
}
|
|
10341
|
+
return {
|
|
10342
|
+
session,
|
|
10343
|
+
async handle(message) {
|
|
10344
|
+
try {
|
|
10345
|
+
return await dispatch(message);
|
|
10346
|
+
} catch (error) {
|
|
10347
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
10348
|
+
options.context.log(`mcp: internal error: ${detail}`);
|
|
10349
|
+
const id = isJsonObject(message) ? requestIdOf(message) : null;
|
|
10350
|
+
return errorResponse(id, ERROR_CODES.internalError, "Internal server error.");
|
|
10351
|
+
}
|
|
10352
|
+
}
|
|
10353
|
+
};
|
|
10354
|
+
}
|
|
10355
|
+
var MAX_RESOURCE_BYTES = 512 * 1024;
|
|
10356
|
+
var PART_URI = /^snapshot:\/\/parts\/(\d+)$/;
|
|
10357
|
+
var REPORT_URIS = /* @__PURE__ */ new Map([
|
|
10358
|
+
["snapshot://reports/audit", { file: "audit.md", title: "Secret audit and risk findings" }],
|
|
10359
|
+
["snapshot://reports/tree", { file: "project-tree.md", title: "Project tree" }],
|
|
10360
|
+
["snapshot://reports/navigation", { file: "navigation.md", title: "Navigation index" }]
|
|
10361
|
+
]);
|
|
10362
|
+
function formatBytes2(bytes) {
|
|
10363
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
10364
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} kB`;
|
|
10365
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
10366
|
+
}
|
|
10367
|
+
function truncationMarker(originalBytes) {
|
|
10368
|
+
return `
|
|
10369
|
+
|
|
10370
|
+
[truncated: ${formatBytes2(originalBytes)} total. Use the snapshot_read tool for a line window.]
|
|
10371
|
+
`;
|
|
10372
|
+
}
|
|
10373
|
+
function capUtf8(text2, limit) {
|
|
10374
|
+
const size2 = Buffer.byteLength(text2, "utf8");
|
|
10375
|
+
if (size2 <= limit) return { text: text2, truncated: false };
|
|
10376
|
+
const cut = Buffer.from(text2, "utf8").subarray(0, limit).toString("utf8").replace(/\uFFFD+$/, "");
|
|
10377
|
+
return { text: `${cut}${truncationMarker(size2)}`, truncated: true };
|
|
10378
|
+
}
|
|
10379
|
+
async function workspaceFor(context) {
|
|
10380
|
+
const root = resolveRoot(void 0, context);
|
|
10381
|
+
if (!root.ok) return { error: root.message };
|
|
10382
|
+
const opened = await openWorkspace(root.root);
|
|
10383
|
+
if (!opened.ok) return { error: opened.message };
|
|
10384
|
+
return opened.workspace;
|
|
10385
|
+
}
|
|
10386
|
+
function readFileIfPresent(file) {
|
|
10387
|
+
try {
|
|
10388
|
+
return fs11__namespace.readFileSync(file, "utf8");
|
|
10389
|
+
} catch {
|
|
10390
|
+
return null;
|
|
10391
|
+
}
|
|
10392
|
+
}
|
|
10393
|
+
function buildResourceProvider() {
|
|
10394
|
+
return {
|
|
10395
|
+
async list(context) {
|
|
10396
|
+
const resources = [
|
|
10397
|
+
{
|
|
10398
|
+
uri: "snapshot://manifest",
|
|
10399
|
+
name: "manifest",
|
|
10400
|
+
title: "Snapshot manifest",
|
|
10401
|
+
description: "Every file in the snapshot with its layer, kind, language, part and hash, plus the reason each excluded file was excluded.",
|
|
10402
|
+
mimeType: "application/json"
|
|
10403
|
+
},
|
|
10404
|
+
{
|
|
10405
|
+
uri: "snapshot://contract",
|
|
10406
|
+
name: "contract",
|
|
10407
|
+
title: "Tool contract",
|
|
10408
|
+
description: "The machine-readable description of the codebase-snapshot command line.",
|
|
10409
|
+
mimeType: "application/json"
|
|
10410
|
+
},
|
|
10411
|
+
{
|
|
10412
|
+
uri: "snapshot://skill",
|
|
10413
|
+
name: "skill",
|
|
10414
|
+
title: "Agent skill",
|
|
10415
|
+
description: "Usage guidance for an agent working with this tool.",
|
|
10416
|
+
mimeType: "text/markdown"
|
|
10417
|
+
}
|
|
10418
|
+
];
|
|
10419
|
+
for (const [uri, report] of REPORT_URIS) {
|
|
10420
|
+
resources.push({
|
|
10421
|
+
uri,
|
|
10422
|
+
name: report.file.replace(/\.md$/, ""),
|
|
10423
|
+
title: report.title,
|
|
10424
|
+
description: `${report.title} from the most recent snapshot.`,
|
|
10425
|
+
mimeType: "text/markdown"
|
|
10426
|
+
});
|
|
10427
|
+
}
|
|
10428
|
+
const workspace = await workspaceFor(context);
|
|
10429
|
+
if ("error" in workspace) return resources;
|
|
10430
|
+
const read = readManifest3(workspace);
|
|
10431
|
+
if (!read.ok) return resources;
|
|
10432
|
+
for (const part of read.manifest.parts) {
|
|
10433
|
+
resources.push({
|
|
10434
|
+
uri: `snapshot://parts/${part.index}`,
|
|
10435
|
+
name: `part-${String(part.index).padStart(3, "0")}`,
|
|
10436
|
+
title: `Part ${part.index} (${part.fileCount} files, ${formatBytes2(part.byteSize)})`,
|
|
10437
|
+
description: `Part ${part.index} of ${read.manifest.parts.length}: ${part.fileCount} file(s), ${part.lines} line(s), ${formatBytes2(part.byteSize)}.`,
|
|
10438
|
+
mimeType: "text/markdown"
|
|
10439
|
+
});
|
|
10440
|
+
}
|
|
10441
|
+
return resources;
|
|
10442
|
+
},
|
|
10443
|
+
templates() {
|
|
10444
|
+
return [
|
|
10445
|
+
{
|
|
10446
|
+
uriTemplate: "snapshot://parts/{index}",
|
|
10447
|
+
name: "part",
|
|
10448
|
+
title: "Snapshot part",
|
|
10449
|
+
description: "One budgeted part of the snapshot, by its 1-based index.",
|
|
10450
|
+
mimeType: "text/markdown"
|
|
10451
|
+
}
|
|
10452
|
+
];
|
|
10453
|
+
},
|
|
10454
|
+
async read(uri, context) {
|
|
10455
|
+
if (uri === "snapshot://contract") {
|
|
10456
|
+
const contract = buildAgentContract();
|
|
10457
|
+
return { uri, mimeType: "application/json", text: JSON.stringify(contract, null, 2) };
|
|
10458
|
+
}
|
|
10459
|
+
if (uri === "snapshot://skill") {
|
|
10460
|
+
return { uri, mimeType: "text/markdown", text: renderSkill() };
|
|
10461
|
+
}
|
|
10462
|
+
const workspace = await workspaceFor(context);
|
|
10463
|
+
if ("error" in workspace) return null;
|
|
10464
|
+
const report = REPORT_URIS.get(uri);
|
|
10465
|
+
if (report) {
|
|
10466
|
+
const text2 = readFileIfPresent(
|
|
10467
|
+
path12__namespace.resolve(workspace.root, workspace.config.outDir, "reports", report.file)
|
|
10468
|
+
);
|
|
10469
|
+
if (text2 === null) return null;
|
|
10470
|
+
return { uri, mimeType: "text/markdown", text: capUtf8(text2, MAX_RESOURCE_BYTES).text };
|
|
10471
|
+
}
|
|
10472
|
+
if (uri === "snapshot://manifest") {
|
|
10473
|
+
const read = readManifest3(workspace);
|
|
10474
|
+
if (!read.ok) return null;
|
|
10475
|
+
const text2 = readFileIfPresent(read.file) ?? JSON.stringify(read.manifest, null, 2);
|
|
10476
|
+
return { uri, mimeType: "application/json", text: capUtf8(text2, MAX_RESOURCE_BYTES).text };
|
|
10477
|
+
}
|
|
10478
|
+
const partMatch = PART_URI.exec(uri);
|
|
10479
|
+
if (partMatch) {
|
|
10480
|
+
const read = readManifest3(workspace);
|
|
10481
|
+
if (!read.ok) return null;
|
|
10482
|
+
const index = Number(partMatch[1]);
|
|
10483
|
+
const part = read.manifest.parts.find((candidate) => candidate.index === index);
|
|
10484
|
+
if (!part) return null;
|
|
10485
|
+
const text2 = readFileIfPresent(
|
|
10486
|
+
path12__namespace.resolve(workspace.root, workspace.config.outDir, "parts", part.filename)
|
|
10487
|
+
);
|
|
10488
|
+
if (text2 === null) return null;
|
|
10489
|
+
return { uri, mimeType: "text/markdown", text: capUtf8(text2, MAX_RESOURCE_BYTES).text };
|
|
10490
|
+
}
|
|
10491
|
+
return null;
|
|
10492
|
+
}
|
|
10493
|
+
};
|
|
10494
|
+
}
|
|
10495
|
+
|
|
10496
|
+
// src/mcp/instructions.ts
|
|
10497
|
+
var SERVER_INSTRUCTIONS = [
|
|
10498
|
+
"codebase-snapshot serves this repository as a size-budgeted snapshot, not as a directory walk.",
|
|
10499
|
+
"",
|
|
10500
|
+
"Work in this order:",
|
|
10501
|
+
"1. snapshot_status \u2014 is there a snapshot here, and has the tree moved since it was built?",
|
|
10502
|
+
"2. snapshot_locate \u2014 find which part holds the code you need.",
|
|
10503
|
+
"3. snapshot_read \u2014 read a window of one file. Never read a part end to end.",
|
|
10504
|
+
"4. snapshot_validate \u2014 before trusting context that came from an earlier conversation.",
|
|
10505
|
+
"",
|
|
10506
|
+
"Rules that matter:",
|
|
10507
|
+
"- Parts are budgeted, not small: a 12-part snapshot is hundreds of kilobytes. Use snapshot_locate first.",
|
|
10508
|
+
"- A file absent from the snapshot has a recorded reason. Ask snapshot_explain, do not assume.",
|
|
10509
|
+
"- snapshot_generate is the only tool that writes. It never installs packages and never writes config.",
|
|
10510
|
+
"- Paths are relative to the repository root the server was started for, and nothing outside it is readable."
|
|
10511
|
+
].join("\n");
|
|
10512
|
+
|
|
10513
|
+
// src/mcp/stdio.ts
|
|
10514
|
+
var MAX_LINE_BYTES = 32 * 1024 * 1024;
|
|
10515
|
+
function createWriter(output) {
|
|
10516
|
+
return {
|
|
10517
|
+
write(value) {
|
|
10518
|
+
return new Promise((resolve9, reject) => {
|
|
10519
|
+
output.write(`${JSON.stringify(value)}
|
|
10520
|
+
`, (error) => {
|
|
10521
|
+
if (error) reject(error);
|
|
10522
|
+
else resolve9();
|
|
10523
|
+
});
|
|
10524
|
+
});
|
|
10525
|
+
}
|
|
10526
|
+
};
|
|
10527
|
+
}
|
|
10528
|
+
function createLineReader(input, options) {
|
|
10529
|
+
let buffer = "";
|
|
10530
|
+
let discarding = false;
|
|
10531
|
+
let discardedBytes = 0;
|
|
10532
|
+
const deliver = (line) => {
|
|
10533
|
+
if (discarding) {
|
|
10534
|
+
discarding = false;
|
|
10535
|
+
discardedBytes = 0;
|
|
10536
|
+
return;
|
|
10537
|
+
}
|
|
10538
|
+
const trimmed = line.endsWith("\r") ? line.slice(0, -1) : line;
|
|
10539
|
+
if (trimmed.trim().length === 0) return;
|
|
10540
|
+
options.onLine(trimmed);
|
|
10541
|
+
};
|
|
10542
|
+
const drain = () => {
|
|
10543
|
+
let index = buffer.indexOf("\n");
|
|
10544
|
+
while (index >= 0) {
|
|
10545
|
+
const line = buffer.slice(0, index);
|
|
10546
|
+
buffer = buffer.slice(index + 1);
|
|
10547
|
+
deliver(line);
|
|
10548
|
+
index = buffer.indexOf("\n");
|
|
10549
|
+
}
|
|
10550
|
+
};
|
|
10551
|
+
const onData = (chunk) => {
|
|
10552
|
+
if (discarding) {
|
|
10553
|
+
const index = chunk.indexOf("\n");
|
|
10554
|
+
if (index < 0) {
|
|
10555
|
+
discardedBytes += chunk.length;
|
|
10556
|
+
return;
|
|
10557
|
+
}
|
|
10558
|
+
buffer = chunk.slice(index + 1);
|
|
10559
|
+
deliver("");
|
|
10560
|
+
drain();
|
|
10561
|
+
return;
|
|
10562
|
+
}
|
|
10563
|
+
buffer += chunk;
|
|
10564
|
+
if (buffer.length > MAX_LINE_BYTES && buffer.indexOf("\n") < 0) {
|
|
10565
|
+
discarding = true;
|
|
10566
|
+
discardedBytes = buffer.length;
|
|
10567
|
+
buffer = "";
|
|
10568
|
+
options.onOverflow?.(discardedBytes);
|
|
10569
|
+
return;
|
|
10570
|
+
}
|
|
10571
|
+
drain();
|
|
10572
|
+
};
|
|
10573
|
+
const onEnd = () => {
|
|
10574
|
+
if (buffer.length > 0) {
|
|
10575
|
+
const rest = buffer;
|
|
10576
|
+
buffer = "";
|
|
10577
|
+
deliver(rest);
|
|
10578
|
+
}
|
|
10579
|
+
};
|
|
10580
|
+
input.setEncoding("utf8");
|
|
10581
|
+
input.on("data", onData);
|
|
10582
|
+
input.on("end", onEnd);
|
|
10583
|
+
return () => {
|
|
10584
|
+
input.off("data", onData);
|
|
10585
|
+
input.off("end", onEnd);
|
|
10586
|
+
};
|
|
10587
|
+
}
|
|
10588
|
+
|
|
10589
|
+
// src/mcp/run.ts
|
|
10590
|
+
var PROJECT_URL = "https://ankurhalder.github.io/codebase-snapshot/";
|
|
10591
|
+
function createServer(options) {
|
|
10592
|
+
const cwd = options.cwd;
|
|
10593
|
+
const roots = options.roots && options.roots.length > 0 ? options.roots : [cwd];
|
|
10594
|
+
const context = {
|
|
10595
|
+
cwd,
|
|
10596
|
+
roots,
|
|
10597
|
+
log: options.log ?? ((line) => process.stderr.write(`${line}
|
|
10598
|
+
`))
|
|
10599
|
+
};
|
|
10600
|
+
return createMcpServer({
|
|
10601
|
+
info: {
|
|
10602
|
+
name: GENERATOR_NAME,
|
|
10603
|
+
title: "codebase-snapshot",
|
|
10604
|
+
version: GENERATOR_VERSION,
|
|
10605
|
+
websiteUrl: PROJECT_URL
|
|
10606
|
+
},
|
|
10607
|
+
instructions: SERVER_INSTRUCTIONS,
|
|
10608
|
+
tools: buildTools(),
|
|
10609
|
+
resources: buildResourceProvider(),
|
|
10610
|
+
prompts: buildPrompts(),
|
|
10611
|
+
context
|
|
10612
|
+
});
|
|
10613
|
+
}
|
|
10614
|
+
async function runStdioServer(options) {
|
|
10615
|
+
const log = options.log ?? ((line) => process.stderr.write(`${line}
|
|
10616
|
+
`));
|
|
10617
|
+
const server = createServer(options);
|
|
10618
|
+
const writer = createWriter(process.stdout);
|
|
10619
|
+
let writeChain = Promise.resolve();
|
|
10620
|
+
const send = (message) => {
|
|
10621
|
+
writeChain = writeChain.then(() => writer.write(message)).catch((error) => {
|
|
10622
|
+
log(`mcp: could not write a response: ${error instanceof Error ? error.message : String(error)}`);
|
|
10623
|
+
});
|
|
10624
|
+
return writeChain;
|
|
10625
|
+
};
|
|
10626
|
+
let inFlight = 0;
|
|
10627
|
+
let inputEnded = false;
|
|
10628
|
+
let resolveDone = () => void 0;
|
|
10629
|
+
const finished = new Promise((resolve9) => {
|
|
10630
|
+
resolveDone = resolve9;
|
|
10631
|
+
});
|
|
10632
|
+
const finishIfIdle = () => {
|
|
10633
|
+
if (inputEnded && inFlight === 0) resolveDone(0);
|
|
10634
|
+
};
|
|
10635
|
+
const handleLine = async (line) => {
|
|
10636
|
+
let message;
|
|
10637
|
+
try {
|
|
10638
|
+
message = JSON.parse(line);
|
|
10639
|
+
} catch {
|
|
10640
|
+
await send(errorResponse(null, ERROR_CODES.parseError, "Message is not valid JSON."));
|
|
10641
|
+
return;
|
|
10642
|
+
}
|
|
10643
|
+
const response = await server.handle(message);
|
|
10644
|
+
if (response) await send(response);
|
|
10645
|
+
};
|
|
10646
|
+
const stopReading = createLineReader(process.stdin, {
|
|
10647
|
+
onOverflow: (bytes) => log(`mcp: discarded an oversized message (${bytes} bytes)`),
|
|
10648
|
+
onLine: (line) => {
|
|
10649
|
+
inFlight += 1;
|
|
10650
|
+
void handleLine(line).finally(() => {
|
|
10651
|
+
inFlight -= 1;
|
|
10652
|
+
finishIfIdle();
|
|
10653
|
+
});
|
|
10654
|
+
}
|
|
10655
|
+
});
|
|
10656
|
+
const onEnd = () => {
|
|
10657
|
+
inputEnded = true;
|
|
10658
|
+
finishIfIdle();
|
|
10659
|
+
};
|
|
10660
|
+
process.stdin.on("end", onEnd);
|
|
10661
|
+
process.stdin.on("close", onEnd);
|
|
10662
|
+
log(`mcp: serving ${options.cwd} over stdio`);
|
|
10663
|
+
const code = await finished;
|
|
10664
|
+
stopReading();
|
|
10665
|
+
process.stdin.off("end", onEnd);
|
|
10666
|
+
process.stdin.off("close", onEnd);
|
|
10667
|
+
await writeChain;
|
|
10668
|
+
return code;
|
|
10669
|
+
}
|
|
10670
|
+
|
|
8714
10671
|
// src/cli.ts
|
|
8715
10672
|
var VERSION = GENERATOR_VERSION;
|
|
8716
10673
|
var INSTALL_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
@@ -8724,7 +10681,8 @@ var COMMAND_ALIASES = /* @__PURE__ */ new Map([
|
|
|
8724
10681
|
["explain", "explain"],
|
|
8725
10682
|
["diff", "diff"],
|
|
8726
10683
|
["describe", "describe"],
|
|
8727
|
-
["skill", "skill"]
|
|
10684
|
+
["skill", "skill"],
|
|
10685
|
+
["mcp", "mcp"]
|
|
8728
10686
|
]);
|
|
8729
10687
|
var COMMAND_ARITY = {
|
|
8730
10688
|
generate: 0,
|
|
@@ -8735,7 +10693,8 @@ var COMMAND_ARITY = {
|
|
|
8735
10693
|
explain: 1,
|
|
8736
10694
|
diff: 2,
|
|
8737
10695
|
describe: 0,
|
|
8738
|
-
skill: 0
|
|
10696
|
+
skill: 0,
|
|
10697
|
+
mcp: 0
|
|
8739
10698
|
};
|
|
8740
10699
|
var CLI_OPTIONS = {
|
|
8741
10700
|
cwd: { type: "string" },
|
|
@@ -8758,6 +10717,7 @@ var CLI_OPTIONS = {
|
|
|
8758
10717
|
exclude: { type: "string" },
|
|
8759
10718
|
yes: { type: "boolean" },
|
|
8760
10719
|
force: { type: "boolean" },
|
|
10720
|
+
autopilot: { type: "boolean" },
|
|
8761
10721
|
install: { type: "boolean" },
|
|
8762
10722
|
"no-install": { type: "boolean" },
|
|
8763
10723
|
target: { type: "string" },
|
|
@@ -8773,6 +10733,14 @@ Production-grade LLM codebase snapshot generator, auditor, and AST partitioner
|
|
|
8773
10733
|
Usage:
|
|
8774
10734
|
codebase-snapshot [command] [options]
|
|
8775
10735
|
|
|
10736
|
+
First run:
|
|
10737
|
+
codebase-snapshot No config yet: asks once, sets the project up, generates.
|
|
10738
|
+
A configured project just generates. It never falls back
|
|
10739
|
+
to defaults it was not given.
|
|
10740
|
+
codebase-snapshot --autopilot The same without questions: detect the preset, write the
|
|
10741
|
+
config, add the npm scripts and devDependency, install,
|
|
10742
|
+
then generate.
|
|
10743
|
+
|
|
8776
10744
|
Commands:
|
|
8777
10745
|
setup Everything at once: config, scripts, dependency, install, generate
|
|
8778
10746
|
generate (default) Generate a complete partitioned codebase snapshot
|
|
@@ -8783,6 +10751,7 @@ Commands:
|
|
|
8783
10751
|
diff <a> <b> Compare two snapshots (directories or manifest.json files)
|
|
8784
10752
|
describe Print the machine-readable contract (--format json for agents)
|
|
8785
10753
|
skill Print the bundled agent skill; --install writes it into the repo
|
|
10754
|
+
mcp Serve this repository to an MCP client over stdio
|
|
8786
10755
|
|
|
8787
10756
|
Options:
|
|
8788
10757
|
--cwd <path> Target repository directory (default: current directory)
|
|
@@ -8805,6 +10774,12 @@ Options:
|
|
|
8805
10774
|
auto: git when usable. git: require git. walk: read
|
|
8806
10775
|
.gitignore directly (escape hatch for a broken .git)
|
|
8807
10776
|
|
|
10777
|
+
generate only:
|
|
10778
|
+
--autopilot Set the project up without asking, then generate: write the
|
|
10779
|
+
config and .gitignore, add the npm scripts and devDependency,
|
|
10780
|
+
and install with the project's own package manager.
|
|
10781
|
+
--no-install With --autopilot: skip the dependency install.
|
|
10782
|
+
|
|
8808
10783
|
init only:
|
|
8809
10784
|
--preset <name> Force a project preset instead of detecting one
|
|
8810
10785
|
--include <globs> Comma-separated allow-list
|
|
@@ -8896,10 +10871,10 @@ function parseCliArgs(argv) {
|
|
|
8896
10871
|
}
|
|
8897
10872
|
if (values.cwd !== void 0) {
|
|
8898
10873
|
const target = String(values.cwd);
|
|
8899
|
-
if (!
|
|
10874
|
+
if (!fs11__namespace.existsSync(target)) {
|
|
8900
10875
|
throw new UsageError(`--cwd directory does not exist: ${target}`);
|
|
8901
10876
|
}
|
|
8902
|
-
if (!
|
|
10877
|
+
if (!fs11__namespace.statSync(target).isDirectory()) {
|
|
8903
10878
|
throw new UsageError(`--cwd is not a directory: ${target}`);
|
|
8904
10879
|
}
|
|
8905
10880
|
}
|
|
@@ -9019,8 +10994,17 @@ function parseCliArgs(argv) {
|
|
|
9019
10994
|
`--target must be one of: ${SKILL_TARGETS.join(", ")} (received "${String(values.target)}").`
|
|
9020
10995
|
);
|
|
9021
10996
|
}
|
|
10997
|
+
if (values["no-install"] === true && command === "generate" && values.autopilot !== true) {
|
|
10998
|
+
throw new UsageError("--no-install only applies to `setup`, or to `generate --autopilot`.");
|
|
10999
|
+
}
|
|
11000
|
+
if (values.autopilot === true && values["no-config"] === true) {
|
|
11001
|
+
throw new UsageError(
|
|
11002
|
+
"--autopilot sets a project up and writes a config; --no-config ignores config files. Pass one or the other."
|
|
11003
|
+
);
|
|
11004
|
+
}
|
|
9022
11005
|
return {
|
|
9023
11006
|
command,
|
|
11007
|
+
explicitCommand: firstPositional !== void 0 || explicitMode !== void 0,
|
|
9024
11008
|
args,
|
|
9025
11009
|
format: rawFormat,
|
|
9026
11010
|
dryRun: values["dry-run"] === true,
|
|
@@ -9032,10 +11016,11 @@ function parseCliArgs(argv) {
|
|
|
9032
11016
|
exclude: values.exclude === void 0 ? void 0 : String(values.exclude),
|
|
9033
11017
|
yes: values.yes === true,
|
|
9034
11018
|
force: values.force === true,
|
|
11019
|
+
autopilot: values.autopilot === true,
|
|
9035
11020
|
install: values.install === true,
|
|
9036
11021
|
noInstall: values["no-install"] === true,
|
|
9037
11022
|
target: values.target === void 0 ? void 0 : String(values.target),
|
|
9038
|
-
cwd: values.cwd === void 0 ? process.cwd() :
|
|
11023
|
+
cwd: values.cwd === void 0 ? process.cwd() : path12__namespace.resolve(String(values.cwd)),
|
|
9039
11024
|
outDir: values["out-dir"] === void 0 ? void 0 : String(values["out-dir"]),
|
|
9040
11025
|
includeMonolith: values["include-monolith"] === true || onlyNamesMonolith === true ? true : void 0,
|
|
9041
11026
|
includeSensitiveFiles: values["include-sensitive-files"] === true ? true : void 0,
|
|
@@ -9092,26 +11077,29 @@ function splitList(value) {
|
|
|
9092
11077
|
return value.split(",").map((entry) => entry.trim()).filter(Boolean);
|
|
9093
11078
|
}
|
|
9094
11079
|
function displayOutPath(cwd, outDir, ...segments) {
|
|
9095
|
-
const absolute =
|
|
9096
|
-
const
|
|
9097
|
-
if (
|
|
11080
|
+
const absolute = path12__namespace.resolve(cwd, outDir, ...segments);
|
|
11081
|
+
const relative11 = path12__namespace.relative(cwd, absolute);
|
|
11082
|
+
if (relative11 && !relative11.startsWith("..") && !path12__namespace.isAbsolute(relative11)) return relative11;
|
|
9098
11083
|
return absolute;
|
|
9099
11084
|
}
|
|
9100
11085
|
function nextStepHint(cwd, scriptsAddedNow) {
|
|
9101
11086
|
let hasScript = scriptsAddedNow;
|
|
9102
11087
|
if (!hasScript) {
|
|
9103
11088
|
try {
|
|
9104
|
-
const parsed = JSON.parse(
|
|
11089
|
+
const parsed = JSON.parse(fs11__namespace.readFileSync(path12__namespace.join(cwd, "package.json"), "utf8"));
|
|
9105
11090
|
hasScript = typeof parsed.scripts?.snapshot === "string";
|
|
9106
11091
|
} catch {
|
|
9107
11092
|
hasScript = false;
|
|
9108
11093
|
}
|
|
9109
11094
|
}
|
|
9110
11095
|
if (!hasScript) return `npx ${GENERATOR_NAME} generate`;
|
|
9111
|
-
const bin =
|
|
9112
|
-
const installed =
|
|
11096
|
+
const bin = path12__namespace.join(cwd, "node_modules", ".bin", "codebase-snapshot");
|
|
11097
|
+
const installed = fs11__namespace.existsSync(bin) || fs11__namespace.existsSync(`${bin}.cmd`);
|
|
9113
11098
|
return installed ? "npm run snapshot" : "npm install && npm run snapshot";
|
|
9114
11099
|
}
|
|
11100
|
+
function presetSummary(init) {
|
|
11101
|
+
return init.reason === null ? `preset: ${init.preset}` : `preset: ${init.preset}, because ${init.reason}`;
|
|
11102
|
+
}
|
|
9115
11103
|
async function runInitCommand(invocation) {
|
|
9116
11104
|
try {
|
|
9117
11105
|
const result = await runInit({
|
|
@@ -9136,7 +11124,7 @@ async function runInitCommand(invocation) {
|
|
|
9136
11124
|
info(result.configText);
|
|
9137
11125
|
return 0;
|
|
9138
11126
|
}
|
|
9139
|
-
info(`\u2705 Created ${
|
|
11127
|
+
info(`\u2705 Created ${path12__namespace.basename(result.configPath)} (${presetSummary(result)})`);
|
|
9140
11128
|
if (result.scripts.length > 0) info(` Added npm scripts: ${result.scripts.join(", ")}`);
|
|
9141
11129
|
if (result.dependency.added && result.dependency.range !== null) {
|
|
9142
11130
|
info(` Added devDependency: ${result.dependency.name} ${result.dependency.range}`);
|
|
@@ -9156,7 +11144,7 @@ async function runInitCommand(invocation) {
|
|
|
9156
11144
|
}
|
|
9157
11145
|
}
|
|
9158
11146
|
function detectPackageManager(cwd) {
|
|
9159
|
-
const has = (name) =>
|
|
11147
|
+
const has = (name) => fs11__namespace.existsSync(path12__namespace.join(cwd, name));
|
|
9160
11148
|
if (has("bun.lockb") || has("bun.lock")) return { name: "bun", command: "bun", args: ["install"] };
|
|
9161
11149
|
if (has("pnpm-lock.yaml")) return { name: "pnpm", command: "pnpm", args: ["install"] };
|
|
9162
11150
|
if (has("yarn.lock")) return { name: "yarn", command: "yarn", args: ["install"] };
|
|
@@ -9207,8 +11195,9 @@ async function captureStdout(fn) {
|
|
|
9207
11195
|
process.stdout.write = original;
|
|
9208
11196
|
}
|
|
9209
11197
|
}
|
|
9210
|
-
async function runSetupCommand(invocation) {
|
|
11198
|
+
async function runSetupCommand(invocation, options = {}) {
|
|
9211
11199
|
try {
|
|
11200
|
+
const interactive = options.interactive === true;
|
|
9212
11201
|
const init = await runInit({
|
|
9213
11202
|
cwd: invocation.cwd,
|
|
9214
11203
|
preset: invocation.preset,
|
|
@@ -9218,7 +11207,7 @@ async function runSetupCommand(invocation) {
|
|
|
9218
11207
|
exclude: splitList(invocation.exclude),
|
|
9219
11208
|
skipArtifacts: invocation.skipArtifacts,
|
|
9220
11209
|
includeSensitiveFiles: invocation.includeSensitiveFiles,
|
|
9221
|
-
yes:
|
|
11210
|
+
yes: !interactive,
|
|
9222
11211
|
force: invocation.force,
|
|
9223
11212
|
reuseConfig: true,
|
|
9224
11213
|
dryRun: invocation.dryRun
|
|
@@ -9245,7 +11234,7 @@ async function runSetupCommand(invocation) {
|
|
|
9245
11234
|
}
|
|
9246
11235
|
info("\n\u{1F9EA} Dry run \u2014 setup would:");
|
|
9247
11236
|
info(
|
|
9248
|
-
` 1. ${init.reused ? `keep ${
|
|
11237
|
+
` 1. ${init.reused ? `keep ${path12__namespace.basename(init.configPath)}` : `write ${path12__namespace.basename(init.configPath)} (preset: ${init.preset})`}`
|
|
9249
11238
|
);
|
|
9250
11239
|
info(" 2. add the npm scripts, the devDependency and the .gitignore entries");
|
|
9251
11240
|
if (manager !== null) info(` 3. install with ${manager}`);
|
|
@@ -9255,11 +11244,11 @@ async function runSetupCommand(invocation) {
|
|
|
9255
11244
|
return 0;
|
|
9256
11245
|
}
|
|
9257
11246
|
if (invocation.format !== "json") {
|
|
9258
|
-
info(`\u{1F680} Setup for: ${invocation.cwd}`);
|
|
11247
|
+
if (!interactive) info(`\u{1F680} Setup for: ${invocation.cwd}`);
|
|
9259
11248
|
if (init.reused) {
|
|
9260
|
-
info(`\u267B Keeping ${
|
|
11249
|
+
info(`\u267B Keeping ${path12__namespace.basename(init.configPath)} (${presetSummary(init)})`);
|
|
9261
11250
|
} else {
|
|
9262
|
-
info(`\u2705 Created ${
|
|
11251
|
+
info(`\u2705 Created ${path12__namespace.basename(init.configPath)} (${presetSummary(init)})`);
|
|
9263
11252
|
}
|
|
9264
11253
|
if (init.scripts.length > 0) info(` Added npm scripts: ${init.scripts.join(", ")}`);
|
|
9265
11254
|
if (init.dependency.added && init.dependency.range !== null) {
|
|
@@ -9288,6 +11277,7 @@ async function runSetupCommand(invocation) {
|
|
|
9288
11277
|
if ((invocation.skipArtifacts ?? []).length > 0) {
|
|
9289
11278
|
generateArgs.push("--skip-artifacts", (invocation.skipArtifacts ?? []).join(","));
|
|
9290
11279
|
}
|
|
11280
|
+
if (invocation.watch) generateArgs.push("--watch");
|
|
9291
11281
|
if (invocation.format === "json") {
|
|
9292
11282
|
const generated = await captureStdout(() => main(generateArgs));
|
|
9293
11283
|
let manifest = null;
|
|
@@ -9327,6 +11317,24 @@ async function runSetupCommand(invocation) {
|
|
|
9327
11317
|
return fail(invocation.format, err.message);
|
|
9328
11318
|
}
|
|
9329
11319
|
}
|
|
11320
|
+
async function firstRun(invocation) {
|
|
11321
|
+
const plan = planFirstRun({
|
|
11322
|
+
hasConfig: findConfigFile(invocation.cwd) !== null,
|
|
11323
|
+
hasPriorSnapshot: hasPriorSnapshot(invocation.cwd, invocation.outDir ?? DEFAULT_ANSWERS.outDir),
|
|
11324
|
+
// A machine must never be asked a question, and `--format json` is how a machine says so.
|
|
11325
|
+
interactive: invocation.format === "text" && isInteractive(defaultIO()),
|
|
11326
|
+
autopilot: invocation.autopilot,
|
|
11327
|
+
emptyTarget: isEffectivelyEmpty(invocation.cwd)
|
|
11328
|
+
});
|
|
11329
|
+
if (plan.action === "generate") return null;
|
|
11330
|
+
if (plan.action === "refuse") {
|
|
11331
|
+
return fail(invocation.format, firstRunMessage(plan.reason, invocation.cwd), plan.exitCode);
|
|
11332
|
+
}
|
|
11333
|
+
info(
|
|
11334
|
+
plan.interactive ? "\u2699 This project has no snapshot configuration yet." : "\u2699 --autopilot: setting this project up before generating."
|
|
11335
|
+
);
|
|
11336
|
+
return await runSetupCommand(invocation, { interactive: plan.interactive });
|
|
11337
|
+
}
|
|
9330
11338
|
function runSkillCommand(invocation) {
|
|
9331
11339
|
if (!invocation.install) {
|
|
9332
11340
|
if (invocation.format === "json") {
|
|
@@ -9348,7 +11356,7 @@ function runSkillCommand(invocation) {
|
|
|
9348
11356
|
}
|
|
9349
11357
|
info(`\u2705 Agent skill installed (${SKILL_NAME}):`);
|
|
9350
11358
|
for (const item of result.written) {
|
|
9351
|
-
const shown =
|
|
11359
|
+
const shown = path12__namespace.relative(invocation.cwd, item.path) || item.path;
|
|
9352
11360
|
info(` - ${item.action.padEnd(9)} ${shown}`);
|
|
9353
11361
|
}
|
|
9354
11362
|
if (result.gitignored.length > 0) {
|
|
@@ -9443,9 +11451,16 @@ ${USAGE}
|
|
|
9443
11451
|
if (invocation.command === "skill") {
|
|
9444
11452
|
return runSkillCommand(invocation);
|
|
9445
11453
|
}
|
|
11454
|
+
if (invocation.command === "mcp") {
|
|
11455
|
+
return await runStdioServer({ cwd: path12__namespace.resolve(invocation.cwd) });
|
|
11456
|
+
}
|
|
9446
11457
|
if (invocation.command === "setup") {
|
|
9447
11458
|
return runSetupCommand(invocation);
|
|
9448
11459
|
}
|
|
11460
|
+
if (invocation.command === "generate" && !invocation.noConfig && (!invocation.explicitCommand || invocation.autopilot)) {
|
|
11461
|
+
const decided = await firstRun(invocation);
|
|
11462
|
+
if (decided !== null) return decided;
|
|
11463
|
+
}
|
|
9449
11464
|
const skipConfig = invocation.noConfig || invocation.command === "diff";
|
|
9450
11465
|
const loaded = skipConfig ? { config: null, warnings: [], errors: [], file: null } : await loadConfigFileDetailed(invocation.cwd);
|
|
9451
11466
|
if (invocation.noConfig) info("\u2699 --no-config: using built-in defaults, config file ignored");
|
|
@@ -9454,7 +11469,7 @@ ${USAGE}
|
|
|
9454
11469
|
warn("\u274C The config file could not be read. Fix it, or delete it to use the defaults.");
|
|
9455
11470
|
return 1;
|
|
9456
11471
|
}
|
|
9457
|
-
if (loaded.file) info(`\u2699 Using config ${
|
|
11472
|
+
if (loaded.file) info(`\u2699 Using config ${path12__namespace.relative(process.cwd(), loaded.file) || loaded.file}`);
|
|
9458
11473
|
const resolved = resolveConfig(
|
|
9459
11474
|
loaded.config,
|
|
9460
11475
|
{
|
|
@@ -9476,10 +11491,10 @@ ${USAGE}
|
|
|
9476
11491
|
if (invocation.command === "validate") {
|
|
9477
11492
|
const result = validateSnapshot(resolved);
|
|
9478
11493
|
if (result.valid) {
|
|
9479
|
-
const manifestPath =
|
|
11494
|
+
const manifestPath = path12__namespace.resolve(resolved.cwd, resolved.outDir, "manifest.json");
|
|
9480
11495
|
let manifest;
|
|
9481
11496
|
try {
|
|
9482
|
-
manifest = JSON.parse(
|
|
11497
|
+
manifest = JSON.parse(fs11__namespace.readFileSync(manifestPath, "utf8"));
|
|
9483
11498
|
} catch {
|
|
9484
11499
|
manifest = void 0;
|
|
9485
11500
|
}
|
|
@@ -9492,13 +11507,13 @@ ${USAGE}
|
|
|
9492
11507
|
return result.valid ? 0 : 1;
|
|
9493
11508
|
}
|
|
9494
11509
|
if (invocation.command === "report") {
|
|
9495
|
-
const manifestPath =
|
|
9496
|
-
if (!
|
|
11510
|
+
const manifestPath = path12__namespace.resolve(resolved.cwd, resolved.outDir, "manifest.json");
|
|
11511
|
+
if (!fs11__namespace.existsSync(manifestPath)) {
|
|
9497
11512
|
return fail(invocation.format, `Manifest not found at ${manifestPath}. Run generate first.`);
|
|
9498
11513
|
}
|
|
9499
11514
|
let manifest;
|
|
9500
11515
|
try {
|
|
9501
|
-
manifest = JSON.parse(
|
|
11516
|
+
manifest = JSON.parse(fs11__namespace.readFileSync(manifestPath, "utf8"));
|
|
9502
11517
|
} catch (err) {
|
|
9503
11518
|
return fail(invocation.format, `Could not read ${manifestPath}: ${err.message}`);
|
|
9504
11519
|
}
|
|
@@ -9662,20 +11677,20 @@ function finishWatchRun(state, now) {
|
|
|
9662
11677
|
};
|
|
9663
11678
|
}
|
|
9664
11679
|
function comparablePath(value) {
|
|
9665
|
-
const absolute =
|
|
11680
|
+
const absolute = path12__namespace.resolve(value);
|
|
9666
11681
|
return process.platform === "win32" ? absolute.toLowerCase() : absolute;
|
|
9667
11682
|
}
|
|
9668
11683
|
function isSameOrInside(parent, candidate) {
|
|
9669
|
-
const
|
|
9670
|
-
return
|
|
11684
|
+
const relative11 = path12__namespace.relative(parent, candidate);
|
|
11685
|
+
return relative11 === "" || relative11 !== ".." && !relative11.startsWith(`..${path12__namespace.sep}`) && !path12__namespace.isAbsolute(relative11);
|
|
9671
11686
|
}
|
|
9672
11687
|
function isGeneratedWatchPath(candidate, paths) {
|
|
9673
|
-
const absolute = comparablePath(
|
|
9674
|
-
const outDir = comparablePath(
|
|
9675
|
-
const tmpDir = comparablePath(
|
|
11688
|
+
const absolute = comparablePath(path12__namespace.resolve(paths.cwd, candidate));
|
|
11689
|
+
const outDir = comparablePath(path12__namespace.resolve(paths.cwd, paths.outDir));
|
|
11690
|
+
const tmpDir = comparablePath(path12__namespace.resolve(paths.cwd, paths.tmpDir));
|
|
9676
11691
|
if (isSameOrInside(outDir, absolute) || isSameOrInside(tmpDir, absolute)) return true;
|
|
9677
|
-
const parent =
|
|
9678
|
-
if (parent ===
|
|
11692
|
+
const parent = path12__namespace.dirname(absolute);
|
|
11693
|
+
if (parent === path12__namespace.dirname(outDir) && path12__namespace.basename(absolute).startsWith(`${path12__namespace.basename(outDir)}.backup-`)) {
|
|
9679
11694
|
return true;
|
|
9680
11695
|
}
|
|
9681
11696
|
return absolute.split(/[\\/]+/).some((segment) => VCS_SEGMENTS.has(segment));
|
|
@@ -9692,8 +11707,8 @@ function decideWatchEvent(state, event, paths) {
|
|
|
9692
11707
|
return { state, action: "schedule" };
|
|
9693
11708
|
}
|
|
9694
11709
|
async function runWatchLoop(invocation, resolved) {
|
|
9695
|
-
const outDir =
|
|
9696
|
-
const tmpDir =
|
|
11710
|
+
const outDir = path12__namespace.resolve(resolved.cwd, resolved.outDir);
|
|
11711
|
+
const tmpDir = path12__namespace.resolve(resolved.cwd, resolved.tmpDir);
|
|
9697
11712
|
const paths = { cwd: resolved.cwd, outDir, tmpDir };
|
|
9698
11713
|
let state = initialWatchState();
|
|
9699
11714
|
let timer = null;
|
|
@@ -9758,11 +11773,11 @@ async function runWatchLoop(invocation, resolved) {
|
|
|
9758
11773
|
};
|
|
9759
11774
|
info("\u{1F440} Watching for changes. Press Ctrl+C to stop.");
|
|
9760
11775
|
void regenerate();
|
|
9761
|
-
const watcher =
|
|
11776
|
+
const watcher = fs11__namespace.watch(resolved.cwd, { recursive: true }, (_event, filename) => {
|
|
9762
11777
|
const decision = decideWatchEvent(
|
|
9763
11778
|
state,
|
|
9764
11779
|
{
|
|
9765
|
-
path: filename ?
|
|
11780
|
+
path: filename ? path12__namespace.resolve(resolved.cwd, filename.toString()) : null,
|
|
9766
11781
|
now: performance.now()
|
|
9767
11782
|
},
|
|
9768
11783
|
paths
|
|
@@ -9777,12 +11792,12 @@ async function runWatchLoop(invocation, resolved) {
|
|
|
9777
11792
|
}
|
|
9778
11793
|
if (decision.action === "schedule") schedule();
|
|
9779
11794
|
});
|
|
9780
|
-
await new Promise((
|
|
11795
|
+
await new Promise((resolve9) => {
|
|
9781
11796
|
const stop = () => {
|
|
9782
11797
|
stopped = true;
|
|
9783
11798
|
if (timer) clearTimeout(timer);
|
|
9784
11799
|
watcher.close();
|
|
9785
|
-
|
|
11800
|
+
resolve9();
|
|
9786
11801
|
};
|
|
9787
11802
|
process.once("SIGINT", stop);
|
|
9788
11803
|
process.once("SIGTERM", stop);
|
|
@@ -9790,7 +11805,7 @@ async function runWatchLoop(invocation, resolved) {
|
|
|
9790
11805
|
return 0;
|
|
9791
11806
|
}
|
|
9792
11807
|
var entryFile = process.argv[1] ?? "";
|
|
9793
|
-
var entryName =
|
|
11808
|
+
var entryName = path12__namespace.basename(entryFile).toLowerCase();
|
|
9794
11809
|
var invokedDirectly = entryName === "cli.js" || entryName === "cli.cjs";
|
|
9795
11810
|
if (invokedDirectly) {
|
|
9796
11811
|
void main().then(
|