codebase-snapshot 0.9.9 → 0.9.14
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 +873 -0
- package/README.md +3 -3
- package/dist/bin.js +6343 -3897
- package/dist/{chunk-KUVFTJHK.js → chunk-DUZK5VL2.js} +3036 -1049
- package/dist/{chunk-HQBEQ67K.js → chunk-J4W74N4Y.js} +496 -114
- package/dist/cli.cjs +6282 -3859
- package/dist/cli.d.cts +282 -3
- package/dist/cli.d.ts +282 -3
- package/dist/cli.js +198 -87
- package/dist/generate-worker-D_mUjgXw.d.cts +73 -0
- package/dist/generate-worker-D_mUjgXw.d.ts +73 -0
- package/dist/index.cjs +2981 -1057
- package/dist/index.d.cts +337 -25
- package/dist/index.d.ts +337 -25
- package/dist/index.js +1 -3
- package/dist/mcp.cjs +6426 -5065
- package/dist/mcp.d.cts +136 -68
- package/dist/mcp.d.ts +136 -68
- package/dist/mcp.js +2 -4
- package/dist/{types-Ca4PmkiQ.d.cts → types-DIX6zWSt.d.cts} +130 -2
- package/dist/{types-Ca4PmkiQ.d.ts → types-DIX6zWSt.d.ts} +130 -2
- package/docs/agent.md +1 -1
- package/docs/cli.md +26 -6
- package/docs/configuration.md +48 -9
- package/docs/how-to-feed-a-repo-to-claude.md +61 -0
- package/docs/mcp.md +36 -10
- package/docs/output.md +49 -4
- package/docs/stale-context-is-your-bug.md +13 -0
- package/docs/vs-repomix.md +1 -1
- package/package.json +5 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { GENERATOR_VERSION, MIN_MAX_PART_BYTES, SCHEMA_VERSION, GENERATOR_NAME, ARTIFACT_NAMES, PRESETS, generateSnapshot, validateSnapshot, explainPath, renderExplainText, describeManifestShapeProblem, loadConfigFileDetailed, parseByteSize, resolveConfig } from './chunk-
|
|
1
|
+
import { GENERATOR_VERSION, anchorForPath, displayPath, MIN_MAX_PART_BYTES, SCHEMA_VERSION, GENERATOR_NAME, ARTIFACT_NAMES, PRESETS, generateSnapshot, generateInWorker, validateSnapshot, buildImportResolver, resolveSpecifier, truncateToBytes, explainPath, renderExplainText, describeManifestShapeProblem, loadConfigFileDetailed, parseByteSize, resolveConfig } from './chunk-DUZK5VL2.js';
|
|
2
2
|
import * as fs from 'fs';
|
|
3
|
-
import * as
|
|
3
|
+
import * as path from 'path';
|
|
4
4
|
|
|
5
5
|
// src/mcp/protocol.ts
|
|
6
6
|
var LEGACY_PROTOCOL_VERSIONS = ["2025-11-25", "2025-06-18"];
|
|
@@ -520,6 +520,8 @@ function createMcpServer(options) {
|
|
|
520
520
|
// src/mcp/part.ts
|
|
521
521
|
var FILE_HEADING = "### FILE: ";
|
|
522
522
|
var CONTENT_MARKERS = /* @__PURE__ */ new Set(["#### Content", "#### Bounded Preview"]);
|
|
523
|
+
var ANCHOR_LINE = /^<a id="([^"]+)"><\/a>$/;
|
|
524
|
+
var INCLUSION_MODES = /* @__PURE__ */ new Set(["full", "preview", "signatures"]);
|
|
523
525
|
function parsePartSections(partText) {
|
|
524
526
|
const lines = partText.split("\n");
|
|
525
527
|
const sections = [];
|
|
@@ -530,16 +532,26 @@ function parsePartSections(partText) {
|
|
|
530
532
|
continue;
|
|
531
533
|
}
|
|
532
534
|
const heading = lines[index].slice(FILE_HEADING.length);
|
|
535
|
+
const anchorMatch = index > 0 ? ANCHOR_LINE.exec(lines[index - 1]) : null;
|
|
533
536
|
let cursor = index + 1;
|
|
534
537
|
let inclusion = null;
|
|
535
|
-
let sha256 = null;
|
|
536
538
|
let includedLines = null;
|
|
537
539
|
while (cursor < lines.length && !CONTENT_MARKERS.has(lines[cursor])) {
|
|
538
540
|
const line = lines[cursor];
|
|
539
541
|
if (line.startsWith(FILE_HEADING)) break;
|
|
540
|
-
if (line.startsWith("-
|
|
541
|
-
|
|
542
|
-
|
|
542
|
+
if (line.startsWith("- Meta: ")) {
|
|
543
|
+
for (const field of line.slice("- Meta: ".length).split("|")) {
|
|
544
|
+
const value = field.trim();
|
|
545
|
+
if (INCLUSION_MODES.has(value)) inclusion = value;
|
|
546
|
+
}
|
|
547
|
+
const counts = /(-?\d+) of (-?\d+) lines|(\d+) lines/.exec(line);
|
|
548
|
+
if (counts) {
|
|
549
|
+
const emitted = counts[1] ?? counts[3];
|
|
550
|
+
includedLines = Number.isFinite(Number(emitted)) ? Number(emitted) : null;
|
|
551
|
+
}
|
|
552
|
+
} else if (line.startsWith("- Inclusion: ")) {
|
|
553
|
+
inclusion = line.slice("- Inclusion: ".length).trim();
|
|
554
|
+
} else if (line.startsWith("- Included Lines: ")) {
|
|
543
555
|
const value = Number(line.slice("- Included Lines: ".length).trim());
|
|
544
556
|
includedLines = Number.isFinite(value) ? value : null;
|
|
545
557
|
}
|
|
@@ -563,47 +575,49 @@ function parsePartSections(partText) {
|
|
|
563
575
|
body.push(lines[bodyLine]);
|
|
564
576
|
bodyLine += 1;
|
|
565
577
|
}
|
|
566
|
-
sections.push({ path: heading, body: body.join("\n"), inclusion,
|
|
578
|
+
sections.push({ path: heading, anchor: anchorMatch?.[1] ?? null, body: body.join("\n"), inclusion, includedLines });
|
|
567
579
|
index = bodyLine + 1;
|
|
568
580
|
}
|
|
569
581
|
return sections;
|
|
570
582
|
}
|
|
571
|
-
function findSection(sections, relativePath
|
|
572
|
-
const
|
|
583
|
+
function findSection(sections, relativePath) {
|
|
584
|
+
const anchor = anchorForPath(relativePath);
|
|
585
|
+
const byAnchor = sections.find((section) => section.anchor === anchor);
|
|
586
|
+
if (byAnchor) return byAnchor;
|
|
587
|
+
const rendered = displayPath(relativePath);
|
|
588
|
+
const samePath = (section) => section.path === relativePath || section.path === rendered;
|
|
589
|
+
const exact = sections.find(samePath);
|
|
573
590
|
if (exact) return exact;
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
(section) => section.path.toLowerCase() === relativePath.toLowerCase()
|
|
580
|
-
);
|
|
581
|
-
if (byPath) return byPath;
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
const folded = sections.filter((section) => section.path.toLowerCase() === relativePath.toLowerCase());
|
|
591
|
+
const foldedPath = rendered.toLowerCase();
|
|
592
|
+
const folded = sections.filter((section) => {
|
|
593
|
+
const path4 = section.path.toLowerCase();
|
|
594
|
+
return path4 === relativePath.toLowerCase() || path4 === foldedPath;
|
|
595
|
+
});
|
|
585
596
|
return folded.length === 1 ? folded[0] : null;
|
|
586
597
|
}
|
|
587
598
|
function windowText(body, fromLine, lineCount) {
|
|
588
599
|
const lines = body.split("\n");
|
|
589
|
-
const total = lines.length;
|
|
590
|
-
const
|
|
600
|
+
const total = body.length === 0 ? 0 : lines.length;
|
|
601
|
+
const requested = Math.max(1, Math.trunc(fromLine));
|
|
602
|
+
const pastEnd = total > 0 && requested > total;
|
|
603
|
+
const start = pastEnd ? requested : Math.min(requested, Math.max(1, total));
|
|
591
604
|
const end = Math.min(total, start + Math.max(1, Math.trunc(lineCount)) - 1);
|
|
592
|
-
const slice = lines.slice(start - 1, end);
|
|
605
|
+
const slice = pastEnd ? [] : lines.slice(start - 1, Math.max(start - 1, end));
|
|
593
606
|
return {
|
|
594
607
|
text: slice.join("\n"),
|
|
595
608
|
fromLine: start,
|
|
596
609
|
returnedLines: slice.length,
|
|
597
610
|
totalLines: total,
|
|
598
|
-
truncated: end < total,
|
|
599
|
-
nextFromLine: end < total ? end + 1 : null
|
|
611
|
+
truncated: !pastEnd && end < total,
|
|
612
|
+
nextFromLine: !pastEnd && end < total ? end + 1 : null,
|
|
613
|
+
pastEnd
|
|
600
614
|
};
|
|
601
615
|
}
|
|
602
616
|
function foldForComparison(value) {
|
|
603
617
|
return process.platform === "win32" || process.platform === "darwin" ? value.toLowerCase() : value;
|
|
604
618
|
}
|
|
605
619
|
function canonicalPath(value) {
|
|
606
|
-
const absolute =
|
|
620
|
+
const absolute = path.resolve(value);
|
|
607
621
|
try {
|
|
608
622
|
return fs.realpathSync(absolute);
|
|
609
623
|
} catch {
|
|
@@ -613,14 +627,14 @@ function canonicalPath(value) {
|
|
|
613
627
|
function isInside(parent, child) {
|
|
614
628
|
const foldedParent = foldForComparison(parent);
|
|
615
629
|
const foldedChild = foldForComparison(child);
|
|
616
|
-
return foldedChild === foldedParent || foldedChild.startsWith(`${foldedParent}${
|
|
630
|
+
return foldedChild === foldedParent || foldedChild.startsWith(`${foldedParent}${path.sep}`);
|
|
617
631
|
}
|
|
618
632
|
function resolveRoot(requested, context) {
|
|
619
633
|
if (requested !== void 0 && (typeof requested !== "string" || requested.trim().length === 0)) {
|
|
620
634
|
return { ok: false, message: '"root" must be a non-empty string when provided.' };
|
|
621
635
|
}
|
|
622
636
|
const wanted = typeof requested === "string" ? requested.trim() : context.cwd;
|
|
623
|
-
const absolute =
|
|
637
|
+
const absolute = path.resolve(context.cwd, wanted);
|
|
624
638
|
let stats;
|
|
625
639
|
try {
|
|
626
640
|
stats = fs.statSync(absolute);
|
|
@@ -659,26 +673,59 @@ async function openWorkspace(root, overrides = {}) {
|
|
|
659
673
|
}
|
|
660
674
|
cliOptions.maxPartBytes = parsed;
|
|
661
675
|
}
|
|
676
|
+
if (overrides.maxPartTokens !== void 0) {
|
|
677
|
+
const parsed = parseByteSize(overrides.maxPartTokens);
|
|
678
|
+
if (parsed === void 0) {
|
|
679
|
+
return {
|
|
680
|
+
ok: false,
|
|
681
|
+
message: '"maxPartTokens" must be a token count, for example 32000 or "32k".'
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
cliOptions.maxPartTokens = parsed;
|
|
685
|
+
}
|
|
662
686
|
if (overrides.outDir !== void 0) {
|
|
663
687
|
if (typeof overrides.outDir !== "string" || overrides.outDir.trim().length === 0) {
|
|
664
688
|
return { ok: false, message: '"outDir" must be a non-empty string.' };
|
|
665
689
|
}
|
|
666
690
|
cliOptions.outDir = overrides.outDir.trim();
|
|
667
691
|
}
|
|
692
|
+
if (overrides.changedSince !== void 0) {
|
|
693
|
+
if (typeof overrides.changedSince !== "string" || overrides.changedSince.trim().length === 0) {
|
|
694
|
+
return {
|
|
695
|
+
ok: false,
|
|
696
|
+
message: '"changedSince" must be a git ref such as "main" or "HEAD~3".'
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
cliOptions.changedSince = overrides.changedSince.trim();
|
|
700
|
+
}
|
|
701
|
+
if (overrides.skeleton === true) cliOptions.skeleton = true;
|
|
702
|
+
if (overrides.sarif === true) cliOptions.sarif = true;
|
|
703
|
+
if (overrides.staged === true) cliOptions.staged = true;
|
|
704
|
+
if (overrides.withDependents === true) cliOptions.withDependents = true;
|
|
668
705
|
try {
|
|
669
706
|
const config = resolveConfig(loaded.config, cliOptions, root);
|
|
670
|
-
return { ok: true, workspace: { root, config, configFile: loaded.file } };
|
|
707
|
+
return { ok: true, workspace: { root, config, configFile: loaded.file, cliOptions } };
|
|
671
708
|
} catch (error) {
|
|
672
709
|
return { ok: false, message: error instanceof Error ? error.message : String(error) };
|
|
673
710
|
}
|
|
674
711
|
}
|
|
675
712
|
var manifestCache = /* @__PURE__ */ new Map();
|
|
676
713
|
function manifestPathFor(workspace) {
|
|
677
|
-
return
|
|
714
|
+
return path.resolve(workspace.root, workspace.config.outDir, "manifest.json");
|
|
715
|
+
}
|
|
716
|
+
function partPathFor(workspace, filename) {
|
|
717
|
+
if (filename.length === 0 || filename.includes("\0")) return null;
|
|
718
|
+
const partsDir = path.resolve(workspace.root, workspace.config.outDir, "parts");
|
|
719
|
+
const resolved = path.resolve(partsDir, filename);
|
|
720
|
+
const relative3 = path.relative(partsDir, resolved);
|
|
721
|
+
if (relative3.length === 0 || relative3.includes(path.sep) || relative3.includes("/") || relative3.includes("\\") || path.isAbsolute(relative3)) {
|
|
722
|
+
return null;
|
|
723
|
+
}
|
|
724
|
+
return resolved;
|
|
678
725
|
}
|
|
679
726
|
function readManifest(workspace) {
|
|
680
727
|
const file = manifestPathFor(workspace);
|
|
681
|
-
const shown =
|
|
728
|
+
const shown = path.relative(workspace.root, file).split(path.sep).join("/") || file;
|
|
682
729
|
let stats;
|
|
683
730
|
try {
|
|
684
731
|
stats = fs.statSync(file);
|
|
@@ -741,6 +788,49 @@ var ROOT_PROPERTY = {
|
|
|
741
788
|
type: "string",
|
|
742
789
|
description: "Repository root. Defaults to the directory this server was started in."
|
|
743
790
|
};
|
|
791
|
+
var OUT_DIR_PROPERTY = {
|
|
792
|
+
type: "string",
|
|
793
|
+
description: "Snapshot directory, relative to the root. Defaults to the project configuration's outDir."
|
|
794
|
+
};
|
|
795
|
+
var outDirOverride = (args) => ({ outDir: args.outDir });
|
|
796
|
+
var SCOPE_PROPERTIES = {
|
|
797
|
+
maxPartTokens: {
|
|
798
|
+
type: "string",
|
|
799
|
+
description: 'Token ceiling per part, for example 32000 or "32k". Narrower than maxPartBytes; it converts at four bytes per token.'
|
|
800
|
+
},
|
|
801
|
+
skeleton: {
|
|
802
|
+
type: "boolean",
|
|
803
|
+
description: "Replace every function body with `{ /* ... */ }`, keeping signatures, imports and exports. Applies to TypeScript, JavaScript and JSX."
|
|
804
|
+
},
|
|
805
|
+
sarif: {
|
|
806
|
+
type: "boolean",
|
|
807
|
+
description: "Also write reports/audit.sarif beside reports/audit.md, for CI code scanning."
|
|
808
|
+
},
|
|
809
|
+
changedSince: {
|
|
810
|
+
type: "string",
|
|
811
|
+
description: 'Only files changed since this git ref, for example "main" or "HEAD~3".'
|
|
812
|
+
},
|
|
813
|
+
staged: {
|
|
814
|
+
type: "boolean",
|
|
815
|
+
description: "Only files staged in the git index."
|
|
816
|
+
},
|
|
817
|
+
withDependents: {
|
|
818
|
+
type: "boolean",
|
|
819
|
+
description: "With staged or changedSince, also include the files that import the changed ones (one hop)."
|
|
820
|
+
}
|
|
821
|
+
};
|
|
822
|
+
function workspaceOverrides(args) {
|
|
823
|
+
return {
|
|
824
|
+
outDir: args.outDir,
|
|
825
|
+
maxPartBytes: args.maxPartBytes,
|
|
826
|
+
maxPartTokens: args.maxPartTokens,
|
|
827
|
+
skeleton: args.skeleton,
|
|
828
|
+
sarif: args.sarif,
|
|
829
|
+
changedSince: args.changedSince,
|
|
830
|
+
staged: args.staged,
|
|
831
|
+
withDependents: args.withDependents
|
|
832
|
+
};
|
|
833
|
+
}
|
|
744
834
|
function failure(message) {
|
|
745
835
|
return { text: message, isError: true };
|
|
746
836
|
}
|
|
@@ -753,7 +843,7 @@ function formatBytes(bytes) {
|
|
|
753
843
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
754
844
|
}
|
|
755
845
|
function relativeFor(workspace, absolute) {
|
|
756
|
-
return
|
|
846
|
+
return path.relative(workspace.root, absolute).split(path.sep).join("/");
|
|
757
847
|
}
|
|
758
848
|
function capped(values, limit = 20) {
|
|
759
849
|
return { count: values.length, sample: values.slice(0, limit) };
|
|
@@ -771,19 +861,50 @@ function summarizeDrift(drift) {
|
|
|
771
861
|
}
|
|
772
862
|
function toRelativePath(root, value) {
|
|
773
863
|
const cleaned = value.trim();
|
|
774
|
-
if (cleaned.length === 0) return null;
|
|
775
|
-
|
|
776
|
-
|
|
864
|
+
if (cleaned.length === 0 || cleaned.includes("\0")) return null;
|
|
865
|
+
const resolvedRoot = path.resolve(root);
|
|
866
|
+
const absolute = path.resolve(resolvedRoot, cleaned);
|
|
867
|
+
const relative3 = path.relative(resolvedRoot, absolute);
|
|
868
|
+
if (relative3 === "" || relative3 === ".." || relative3.startsWith(`..${path.sep}`) || path.isAbsolute(relative3)) {
|
|
869
|
+
return null;
|
|
777
870
|
}
|
|
778
|
-
|
|
779
|
-
if (relative3.startsWith("..") || path2.isAbsolute(relative3)) return null;
|
|
780
|
-
return relative3.split(path2.sep).join("/");
|
|
871
|
+
return relative3.split(path.sep).join("/");
|
|
781
872
|
}
|
|
782
873
|
function symbolsOf(entry) {
|
|
783
874
|
const summary = entry.summary;
|
|
784
|
-
if (!summary || typeof summary !== "object"
|
|
785
|
-
const
|
|
786
|
-
|
|
875
|
+
if (!summary || typeof summary !== "object") return [];
|
|
876
|
+
const names = [];
|
|
877
|
+
const collect = (value) => {
|
|
878
|
+
if (!Array.isArray(value)) return;
|
|
879
|
+
for (const item of value) {
|
|
880
|
+
if (typeof item === "string") names.push(item);
|
|
881
|
+
}
|
|
882
|
+
};
|
|
883
|
+
const record = summary;
|
|
884
|
+
for (const field of [
|
|
885
|
+
"exports",
|
|
886
|
+
"reactComponents",
|
|
887
|
+
"routeHandlers",
|
|
888
|
+
"functions",
|
|
889
|
+
"classes",
|
|
890
|
+
"interfaces",
|
|
891
|
+
"types",
|
|
892
|
+
"enums",
|
|
893
|
+
"hooks",
|
|
894
|
+
"headings",
|
|
895
|
+
// The rest of the same records. `variables` is where a `const`, a Python constant, a Go
|
|
896
|
+
// `var`, a Rust `mod`, a C `#define` and a Terraform `variable` block all land, so leaving it
|
|
897
|
+
// out made `snapshot_locate` miss most of what a non-JavaScript file declares; `directives`
|
|
898
|
+
// carries `"use client"`-style markers, and `frontmatterKeys`/`keys` are the headings and
|
|
899
|
+
// top-level names of a Markdown or JSON file.
|
|
900
|
+
"variables",
|
|
901
|
+
"directives",
|
|
902
|
+
"frontmatterKeys",
|
|
903
|
+
"keys"
|
|
904
|
+
]) {
|
|
905
|
+
collect(record[field]);
|
|
906
|
+
}
|
|
907
|
+
return names;
|
|
787
908
|
}
|
|
788
909
|
async function prepare(args, context, overrides = {}) {
|
|
789
910
|
const rootOutcome = resolveRoot(args.root, context);
|
|
@@ -798,7 +919,7 @@ var statusTool = {
|
|
|
798
919
|
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.",
|
|
799
920
|
inputSchema: {
|
|
800
921
|
type: "object",
|
|
801
|
-
properties: { root: ROOT_PROPERTY },
|
|
922
|
+
properties: { root: ROOT_PROPERTY, outDir: OUT_DIR_PROPERTY },
|
|
802
923
|
additionalProperties: false
|
|
803
924
|
},
|
|
804
925
|
outputSchema: {
|
|
@@ -819,7 +940,7 @@ var statusTool = {
|
|
|
819
940
|
},
|
|
820
941
|
annotations: READ_ONLY,
|
|
821
942
|
async handler(args, context) {
|
|
822
|
-
const prepared = await prepare(args, context);
|
|
943
|
+
const prepared = await prepare(args, context, outDirOverride(args));
|
|
823
944
|
if (!prepared.ok) return prepared.outcome;
|
|
824
945
|
const workspace = prepared.workspace;
|
|
825
946
|
const read = readManifest(workspace);
|
|
@@ -878,10 +999,12 @@ var planTool = {
|
|
|
878
999
|
type: "object",
|
|
879
1000
|
properties: {
|
|
880
1001
|
root: ROOT_PROPERTY,
|
|
1002
|
+
outDir: OUT_DIR_PROPERTY,
|
|
881
1003
|
maxPartBytes: {
|
|
882
1004
|
type: "string",
|
|
883
1005
|
description: 'Byte budget per part, for example 307200 or "300k". Defaults to the project configuration.'
|
|
884
|
-
}
|
|
1006
|
+
},
|
|
1007
|
+
...SCOPE_PROPERTIES
|
|
885
1008
|
},
|
|
886
1009
|
additionalProperties: false
|
|
887
1010
|
},
|
|
@@ -898,7 +1021,7 @@ var planTool = {
|
|
|
898
1021
|
},
|
|
899
1022
|
annotations: READ_ONLY,
|
|
900
1023
|
async handler(args, context) {
|
|
901
|
-
const prepared = await prepare(args, context,
|
|
1024
|
+
const prepared = await prepare(args, context, workspaceOverrides(args));
|
|
902
1025
|
if (!prepared.ok) return prepared.outcome;
|
|
903
1026
|
const workspace = prepared.workspace;
|
|
904
1027
|
let manifest;
|
|
@@ -952,14 +1075,12 @@ var generateTool = {
|
|
|
952
1075
|
type: "object",
|
|
953
1076
|
properties: {
|
|
954
1077
|
root: ROOT_PROPERTY,
|
|
1078
|
+
outDir: OUT_DIR_PROPERTY,
|
|
955
1079
|
maxPartBytes: {
|
|
956
1080
|
type: "string",
|
|
957
1081
|
description: 'Byte budget per part, for example 307200 or "300k". Defaults to the project configuration.'
|
|
958
1082
|
},
|
|
959
|
-
|
|
960
|
-
type: "string",
|
|
961
|
-
description: "Output directory relative to the root. Defaults to the project configuration."
|
|
962
|
-
}
|
|
1083
|
+
...SCOPE_PROPERTIES
|
|
963
1084
|
},
|
|
964
1085
|
additionalProperties: false
|
|
965
1086
|
},
|
|
@@ -975,41 +1096,35 @@ var generateTool = {
|
|
|
975
1096
|
},
|
|
976
1097
|
annotations: WRITES,
|
|
977
1098
|
async handler(args, context) {
|
|
978
|
-
const prepared = await prepare(args, context,
|
|
979
|
-
maxPartBytes: args.maxPartBytes,
|
|
980
|
-
outDir: args.outDir
|
|
981
|
-
});
|
|
1099
|
+
const prepared = await prepare(args, context, workspaceOverrides(args));
|
|
982
1100
|
if (!prepared.ok) return prepared.outcome;
|
|
983
1101
|
const workspace = prepared.workspace;
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
} catch (error) {
|
|
988
|
-
return failure(error instanceof Error ? error.message : String(error));
|
|
989
|
-
}
|
|
1102
|
+
const result = await generateInWorker(workspace.root, workspace.cliOptions);
|
|
1103
|
+
if (!result.ok) return failure(result.message);
|
|
1104
|
+
const summary = result.summary;
|
|
990
1105
|
forgetManifest(manifestPathFor(workspace));
|
|
991
|
-
const written = relativeFor(workspace,
|
|
1106
|
+
const written = relativeFor(workspace, path.resolve(workspace.root, workspace.config.outDir));
|
|
992
1107
|
const lines = [
|
|
993
1108
|
`Wrote ${written}/`,
|
|
994
|
-
`Files: ${
|
|
995
|
-
`Parts: ${
|
|
996
|
-
`Fingerprint: ${
|
|
1109
|
+
`Files: ${summary.totalFiles} included, ${summary.skippedCount} skipped, ${plural(summary.partsCount, "part")}`,
|
|
1110
|
+
`Parts: ${summary.parts.map((part) => `${part.filename} (${formatBytes(part.byteSize)})`).join(", ")}`,
|
|
1111
|
+
`Fingerprint: ${summary.fingerprint}`
|
|
997
1112
|
];
|
|
998
|
-
if (
|
|
999
|
-
lines.push(`Warnings (${
|
|
1000
|
-
for (const warning of
|
|
1113
|
+
if (summary.warnings.length > 0) {
|
|
1114
|
+
lines.push(`Warnings (${summary.warnings.length}):`);
|
|
1115
|
+
for (const warning of summary.warnings.slice(0, 5)) lines.push(` - ${warning}`);
|
|
1001
1116
|
}
|
|
1002
1117
|
return {
|
|
1003
1118
|
text: lines.join("\n"),
|
|
1004
1119
|
structured: {
|
|
1005
|
-
fingerprint:
|
|
1006
|
-
files:
|
|
1007
|
-
skipped:
|
|
1008
|
-
parts:
|
|
1009
|
-
emittedBytes:
|
|
1120
|
+
fingerprint: summary.fingerprint,
|
|
1121
|
+
files: summary.totalFiles,
|
|
1122
|
+
skipped: summary.skippedCount,
|
|
1123
|
+
parts: summary.partsCount,
|
|
1124
|
+
emittedBytes: summary.totalContentBytes,
|
|
1010
1125
|
outDir: written,
|
|
1011
|
-
partFiles:
|
|
1012
|
-
warningCount:
|
|
1126
|
+
partFiles: summary.parts.map((part) => part.filename),
|
|
1127
|
+
warningCount: summary.warnings.length
|
|
1013
1128
|
}
|
|
1014
1129
|
};
|
|
1015
1130
|
}
|
|
@@ -1020,7 +1135,7 @@ var validateTool = {
|
|
|
1020
1135
|
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.",
|
|
1021
1136
|
inputSchema: {
|
|
1022
1137
|
type: "object",
|
|
1023
|
-
properties: { root: ROOT_PROPERTY },
|
|
1138
|
+
properties: { root: ROOT_PROPERTY, outDir: OUT_DIR_PROPERTY, ...SCOPE_PROPERTIES },
|
|
1024
1139
|
additionalProperties: false
|
|
1025
1140
|
},
|
|
1026
1141
|
outputSchema: {
|
|
@@ -1032,7 +1147,7 @@ var validateTool = {
|
|
|
1032
1147
|
},
|
|
1033
1148
|
annotations: READ_ONLY,
|
|
1034
1149
|
async handler(args, context) {
|
|
1035
|
-
const prepared = await prepare(args, context);
|
|
1150
|
+
const prepared = await prepare(args, context, workspaceOverrides(args));
|
|
1036
1151
|
if (!prepared.ok) return prepared.outcome;
|
|
1037
1152
|
const workspace = prepared.workspace;
|
|
1038
1153
|
let result;
|
|
@@ -1099,7 +1214,8 @@ var locateTool = {
|
|
|
1099
1214
|
default: 20,
|
|
1100
1215
|
description: "Maximum matches to return."
|
|
1101
1216
|
},
|
|
1102
|
-
root: ROOT_PROPERTY
|
|
1217
|
+
root: ROOT_PROPERTY,
|
|
1218
|
+
outDir: OUT_DIR_PROPERTY
|
|
1103
1219
|
},
|
|
1104
1220
|
required: ["query"],
|
|
1105
1221
|
additionalProperties: false
|
|
@@ -1110,7 +1226,7 @@ var locateTool = {
|
|
|
1110
1226
|
},
|
|
1111
1227
|
annotations: READ_ONLY,
|
|
1112
1228
|
async handler(args, context) {
|
|
1113
|
-
const prepared = await prepare(args, context);
|
|
1229
|
+
const prepared = await prepare(args, context, outDirOverride(args));
|
|
1114
1230
|
if (!prepared.ok) return prepared.outcome;
|
|
1115
1231
|
const workspace = prepared.workspace;
|
|
1116
1232
|
const read = readManifest(workspace);
|
|
@@ -1145,12 +1261,13 @@ var locateTool = {
|
|
|
1145
1261
|
continue;
|
|
1146
1262
|
}
|
|
1147
1263
|
}
|
|
1148
|
-
if (wants("layer") && entry.layer.toLowerCase()
|
|
1149
|
-
else if (wants("kind") && entry.kind.toLowerCase()
|
|
1264
|
+
if (wants("layer") && entry.layer.toLowerCase().includes(needle)) add(entry, "layer");
|
|
1265
|
+
else if (wants("kind") && entry.kind.toLowerCase().includes(needle)) add(entry, "kind");
|
|
1150
1266
|
}
|
|
1151
1267
|
if (wants("route")) {
|
|
1152
1268
|
for (const route of manifest.indexes.routes) {
|
|
1153
|
-
|
|
1269
|
+
const method = route.methods.find((name) => name.toLowerCase().includes(needle));
|
|
1270
|
+
if (!route.path.toLowerCase().includes(needle) && method === void 0) continue;
|
|
1154
1271
|
if (matches.some((match) => match.path === route.path)) continue;
|
|
1155
1272
|
matches.push({
|
|
1156
1273
|
path: route.path,
|
|
@@ -1185,6 +1302,127 @@ var locateTool = {
|
|
|
1185
1302
|
};
|
|
1186
1303
|
}
|
|
1187
1304
|
};
|
|
1305
|
+
var dependentsTool = {
|
|
1306
|
+
name: "snapshot_dependents",
|
|
1307
|
+
title: "Follow the import graph",
|
|
1308
|
+
description: 'For one file in the snapshot: what it imports, and which files import it. Answers "what breaks if I change this?" without reading the parts.',
|
|
1309
|
+
inputSchema: {
|
|
1310
|
+
type: "object",
|
|
1311
|
+
properties: {
|
|
1312
|
+
path: {
|
|
1313
|
+
type: "string",
|
|
1314
|
+
description: "Snapshot-relative path, exactly as snapshot_locate or manifest.files[] reports it."
|
|
1315
|
+
},
|
|
1316
|
+
direction: {
|
|
1317
|
+
type: "string",
|
|
1318
|
+
enum: ["both", "imports", "imported-by"],
|
|
1319
|
+
default: "both",
|
|
1320
|
+
description: "Which edges to return."
|
|
1321
|
+
},
|
|
1322
|
+
limit: {
|
|
1323
|
+
type: "integer",
|
|
1324
|
+
minimum: 1,
|
|
1325
|
+
maximum: 200,
|
|
1326
|
+
default: 50,
|
|
1327
|
+
description: "Maximum entries per direction."
|
|
1328
|
+
},
|
|
1329
|
+
root: ROOT_PROPERTY,
|
|
1330
|
+
outDir: OUT_DIR_PROPERTY
|
|
1331
|
+
},
|
|
1332
|
+
required: ["path"],
|
|
1333
|
+
additionalProperties: false
|
|
1334
|
+
},
|
|
1335
|
+
outputSchema: {
|
|
1336
|
+
type: "object",
|
|
1337
|
+
properties: {
|
|
1338
|
+
path: { type: "string" },
|
|
1339
|
+
imports: { type: "array" },
|
|
1340
|
+
importedBy: { type: "array" },
|
|
1341
|
+
unresolved: { type: "array" }
|
|
1342
|
+
}
|
|
1343
|
+
},
|
|
1344
|
+
annotations: READ_ONLY,
|
|
1345
|
+
async handler(args, context) {
|
|
1346
|
+
const prepared = await prepare(args, context, outDirOverride(args));
|
|
1347
|
+
if (!prepared.ok) return prepared.outcome;
|
|
1348
|
+
const read = readManifest(prepared.workspace);
|
|
1349
|
+
if (!read.ok) return failure(read.message);
|
|
1350
|
+
const manifest = read.manifest;
|
|
1351
|
+
const requested = String(args.path ?? "").trim().replace(/\\/g, "/");
|
|
1352
|
+
if (requested.length === 0) return failure('"path" must not be empty.');
|
|
1353
|
+
const filesByPath = new Map(manifest.files.map((file) => [file.relativePath, file]));
|
|
1354
|
+
const entry = filesByPath.get(requested) ?? manifest.files.find((file) => file.relativePath.toLowerCase() === requested.toLowerCase());
|
|
1355
|
+
if (!entry) {
|
|
1356
|
+
return failure(
|
|
1357
|
+
`${requested} is not in the snapshot. Use snapshot_locate to find its path, or snapshot_explain to see whether it was excluded.`
|
|
1358
|
+
);
|
|
1359
|
+
}
|
|
1360
|
+
const direction = args.direction === "imports" || args.direction === "imported-by" ? String(args.direction) : "both";
|
|
1361
|
+
const limit = typeof args.limit === "number" && args.limit > 0 ? Math.floor(args.limit) : 50;
|
|
1362
|
+
const resolver = buildImportResolver(manifest.files);
|
|
1363
|
+
const imports = [];
|
|
1364
|
+
const unresolved = [];
|
|
1365
|
+
if (direction !== "imported-by") {
|
|
1366
|
+
const own = manifest.indexes.imports.byFile.find((file) => file.path === entry.relativePath);
|
|
1367
|
+
for (const specifier of own?.imports ?? []) {
|
|
1368
|
+
const resolved = resolveSpecifier(entry.relativePath, specifier, resolver);
|
|
1369
|
+
if (resolved === null) {
|
|
1370
|
+
unresolved.push(specifier);
|
|
1371
|
+
continue;
|
|
1372
|
+
}
|
|
1373
|
+
const target = filesByPath.get(resolved);
|
|
1374
|
+
if (target) imports.push({ path: resolved, specifier, part: target.part });
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
const importedBy = [];
|
|
1378
|
+
if (direction !== "imports") {
|
|
1379
|
+
for (const candidate of manifest.indexes.imports.byFile) {
|
|
1380
|
+
if (candidate.path === entry.relativePath) continue;
|
|
1381
|
+
const edge = candidate.imports.some(
|
|
1382
|
+
(specifier) => resolveSpecifier(candidate.path, specifier, resolver) === entry.relativePath
|
|
1383
|
+
);
|
|
1384
|
+
if (!edge) continue;
|
|
1385
|
+
const source = filesByPath.get(candidate.path);
|
|
1386
|
+
importedBy.push({ path: candidate.path, part: source?.part ?? "" });
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
const shownImports = imports.slice(0, limit);
|
|
1390
|
+
const shownImportedBy = importedBy.slice(0, limit);
|
|
1391
|
+
const lines = [
|
|
1392
|
+
`${entry.relativePath} \u2014 ${importedBy.length} importer(s), ${imports.length} import(s), ${unresolved.length} outside the snapshot.`
|
|
1393
|
+
];
|
|
1394
|
+
if (direction !== "imports") {
|
|
1395
|
+
lines.push("", "Imported by:");
|
|
1396
|
+
if (shownImportedBy.length === 0) lines.push(" (nothing in the snapshot imports it)");
|
|
1397
|
+
for (const item of shownImportedBy) lines.push(` ${item.path} [${item.part}]`);
|
|
1398
|
+
if (importedBy.length > shownImportedBy.length) {
|
|
1399
|
+
lines.push(` \u2026 ${importedBy.length - shownImportedBy.length} more; raise \`limit\`.`);
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
if (direction !== "imported-by") {
|
|
1403
|
+
lines.push("", "Imports:");
|
|
1404
|
+
if (shownImports.length === 0) lines.push(" (none recorded for this file)");
|
|
1405
|
+
for (const item of shownImports) {
|
|
1406
|
+
lines.push(` ${item.path} (from "${item.specifier}") [${item.part}]`);
|
|
1407
|
+
}
|
|
1408
|
+
if (imports.length > shownImports.length) {
|
|
1409
|
+
lines.push(` \u2026 ${imports.length - shownImports.length} more; raise \`limit\`.`);
|
|
1410
|
+
}
|
|
1411
|
+
if (unresolved.length > 0) {
|
|
1412
|
+
lines.push("", `Outside the snapshot (${unresolved.length}): ${unresolved.join(", ")}`);
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
return {
|
|
1416
|
+
text: lines.join("\n"),
|
|
1417
|
+
structured: {
|
|
1418
|
+
path: entry.relativePath,
|
|
1419
|
+
imports: shownImports,
|
|
1420
|
+
importedBy: shownImportedBy,
|
|
1421
|
+
unresolved
|
|
1422
|
+
}
|
|
1423
|
+
};
|
|
1424
|
+
}
|
|
1425
|
+
};
|
|
1188
1426
|
var readTool = {
|
|
1189
1427
|
name: "snapshot_read",
|
|
1190
1428
|
title: "Read a file from the snapshot",
|
|
@@ -1209,7 +1447,8 @@ var readTool = {
|
|
|
1209
1447
|
default: 200,
|
|
1210
1448
|
description: "How many lines to return."
|
|
1211
1449
|
},
|
|
1212
|
-
root: ROOT_PROPERTY
|
|
1450
|
+
root: ROOT_PROPERTY,
|
|
1451
|
+
outDir: OUT_DIR_PROPERTY
|
|
1213
1452
|
},
|
|
1214
1453
|
required: ["path"],
|
|
1215
1454
|
additionalProperties: false
|
|
@@ -1227,7 +1466,7 @@ var readTool = {
|
|
|
1227
1466
|
},
|
|
1228
1467
|
annotations: READ_ONLY,
|
|
1229
1468
|
async handler(args, context) {
|
|
1230
|
-
const prepared = await prepare(args, context);
|
|
1469
|
+
const prepared = await prepare(args, context, outDirOverride(args));
|
|
1231
1470
|
if (!prepared.ok) return prepared.outcome;
|
|
1232
1471
|
const workspace = prepared.workspace;
|
|
1233
1472
|
if (typeof args.path !== "string") return failure('"path" must be a string.');
|
|
@@ -1253,7 +1492,12 @@ var readTool = {
|
|
|
1253
1492
|
`${relativePath} is not in the snapshot. Call snapshot_locate to find the path, or snapshot_explain to learn why it was excluded.`
|
|
1254
1493
|
);
|
|
1255
1494
|
}
|
|
1256
|
-
const partPath =
|
|
1495
|
+
const partPath = partPathFor(workspace, entry.part);
|
|
1496
|
+
if (partPath === null) {
|
|
1497
|
+
return failure(
|
|
1498
|
+
`${entry.part} is not a part of this snapshot: its name leaves ${workspace.config.outDir}/parts. Run snapshot_generate to rebuild it.`
|
|
1499
|
+
);
|
|
1500
|
+
}
|
|
1257
1501
|
let partText;
|
|
1258
1502
|
try {
|
|
1259
1503
|
partText = fs.readFileSync(partPath, "utf8");
|
|
@@ -1262,7 +1506,7 @@ var readTool = {
|
|
|
1262
1506
|
`${entry.part} could not be read, so the snapshot is damaged. Run snapshot_generate to rebuild it.`
|
|
1263
1507
|
);
|
|
1264
1508
|
}
|
|
1265
|
-
const section = findSection(parsePartSections(partText), entry.relativePath
|
|
1509
|
+
const section = findSection(parsePartSections(partText), entry.relativePath);
|
|
1266
1510
|
if (!section) {
|
|
1267
1511
|
return failure(
|
|
1268
1512
|
`${relativePath} is recorded in ${entry.part} but its section could not be located there. Run snapshot_generate to rebuild the snapshot.`
|
|
@@ -1274,16 +1518,28 @@ var readTool = {
|
|
|
1274
1518
|
let body = window.text;
|
|
1275
1519
|
let truncatedByBytes = false;
|
|
1276
1520
|
if (Buffer.byteLength(body, "utf8") > MAX_RESULT_BYTES) {
|
|
1277
|
-
|
|
1521
|
+
const clipped = truncateToBytes(body, MAX_RESULT_BYTES);
|
|
1522
|
+
body = clipped.content;
|
|
1278
1523
|
const lastBreak = body.lastIndexOf("\n");
|
|
1279
1524
|
body = lastBreak > 0 ? body.slice(0, lastBreak) : body;
|
|
1280
1525
|
truncatedByBytes = true;
|
|
1281
1526
|
}
|
|
1282
1527
|
const returnedLines = body.length === 0 ? 0 : body.split("\n").length;
|
|
1283
|
-
const moreRemain = window.truncated || truncatedByBytes;
|
|
1528
|
+
const moreRemain = !window.pastEnd && (window.truncated || truncatedByBytes);
|
|
1284
1529
|
const header = [
|
|
1285
1530
|
`${entry.relativePath} \u2014 ${entry.part}, ${entry.layer}/${entry.kind}, ${entry.language}`,
|
|
1286
|
-
|
|
1531
|
+
// The line range is the answer to "where am I in this file", and it is computed from the
|
|
1532
|
+
// body the window was taken from, so it is just as meaningful for a preview as for a full
|
|
1533
|
+
// file. It used to be replaced — not augmented — by the preview note, so a 40-line window
|
|
1534
|
+
// into a 300-line preview came back with no range at all and the caller could not tell
|
|
1535
|
+
// where the window ended. Both facts are stated now, and `signatures` is no longer
|
|
1536
|
+
// silently treated as a full file.
|
|
1537
|
+
...section.inclusion === "preview" ? [
|
|
1538
|
+
"Bounded preview: the snapshot holds this file's head and tail only, so these line numbers count the preview, not the source."
|
|
1539
|
+
] : section.inclusion === "signatures" ? [
|
|
1540
|
+
"Signatures: function and method bodies were replaced with `{ /* ... */ }` when the snapshot was generated."
|
|
1541
|
+
] : [],
|
|
1542
|
+
window.totalLines === 0 ? "This file is empty." : window.pastEnd ? `This file has ${window.totalLines} line(s); fromLine=${window.fromLine} is past its end.` : `Lines ${window.fromLine}\u2013${window.fromLine + returnedLines - 1} of ${window.totalLines}.`
|
|
1287
1543
|
];
|
|
1288
1544
|
const lines = [...header, "", body];
|
|
1289
1545
|
if (moreRemain) {
|
|
@@ -1320,7 +1576,9 @@ var explainTool = {
|
|
|
1320
1576
|
type: "string",
|
|
1321
1577
|
description: "Path relative to the repository root. Directories are explained as a whole."
|
|
1322
1578
|
},
|
|
1323
|
-
root: ROOT_PROPERTY
|
|
1579
|
+
root: ROOT_PROPERTY,
|
|
1580
|
+
outDir: OUT_DIR_PROPERTY,
|
|
1581
|
+
...SCOPE_PROPERTIES
|
|
1324
1582
|
},
|
|
1325
1583
|
required: ["path"],
|
|
1326
1584
|
additionalProperties: false
|
|
@@ -1335,7 +1593,7 @@ var explainTool = {
|
|
|
1335
1593
|
},
|
|
1336
1594
|
annotations: READ_ONLY,
|
|
1337
1595
|
async handler(args, context) {
|
|
1338
|
-
const prepared = await prepare(args, context);
|
|
1596
|
+
const prepared = await prepare(args, context, workspaceOverrides(args));
|
|
1339
1597
|
if (!prepared.ok) return prepared.outcome;
|
|
1340
1598
|
const workspace = prepared.workspace;
|
|
1341
1599
|
if (typeof args.path !== "string") return failure('"path" must be a string.');
|
|
@@ -1375,6 +1633,7 @@ function buildTools() {
|
|
|
1375
1633
|
generateTool,
|
|
1376
1634
|
validateTool,
|
|
1377
1635
|
locateTool,
|
|
1636
|
+
dependentsTool,
|
|
1378
1637
|
readTool,
|
|
1379
1638
|
explainTool
|
|
1380
1639
|
];
|
|
@@ -1446,9 +1705,15 @@ var RESOURCE_TEMPLATE_URIS = ["snapshot://parts/{index}"];
|
|
|
1446
1705
|
|
|
1447
1706
|
// src/agent/option-scope.ts
|
|
1448
1707
|
var VALIDATE_OPTIONS = [
|
|
1708
|
+
"sarif",
|
|
1709
|
+
"skeleton",
|
|
1710
|
+
"changed-since",
|
|
1711
|
+
"staged",
|
|
1712
|
+
"with-dependents",
|
|
1449
1713
|
"cwd",
|
|
1450
1714
|
"out-dir",
|
|
1451
1715
|
"max-part-bytes",
|
|
1716
|
+
"max-part-tokens",
|
|
1452
1717
|
"large-file-bytes",
|
|
1453
1718
|
"max-monolith-bytes",
|
|
1454
1719
|
"include-monolith",
|
|
@@ -1465,9 +1730,15 @@ var VALIDATE_OPTIONS = [
|
|
|
1465
1730
|
];
|
|
1466
1731
|
var COMMAND_OPTION_SCOPE = {
|
|
1467
1732
|
generate: [
|
|
1733
|
+
"sarif",
|
|
1734
|
+
"skeleton",
|
|
1735
|
+
"changed-since",
|
|
1736
|
+
"staged",
|
|
1737
|
+
"with-dependents",
|
|
1468
1738
|
"cwd",
|
|
1469
1739
|
"out-dir",
|
|
1470
1740
|
"max-part-bytes",
|
|
1741
|
+
"max-part-tokens",
|
|
1471
1742
|
"large-file-bytes",
|
|
1472
1743
|
"max-monolith-bytes",
|
|
1473
1744
|
"include-monolith",
|
|
@@ -1505,6 +1776,12 @@ var COMMAND_OPTION_SCOPE = {
|
|
|
1505
1776
|
"version"
|
|
1506
1777
|
],
|
|
1507
1778
|
explain: [
|
|
1779
|
+
// The scope decides what `explain` says about a file, dependents included: without these,
|
|
1780
|
+
// a file that is in the snapshot only because it imports a changed one was explained as
|
|
1781
|
+
// `included: false, reason: "unchanged"`.
|
|
1782
|
+
"changed-since",
|
|
1783
|
+
"staged",
|
|
1784
|
+
"with-dependents",
|
|
1508
1785
|
"cwd",
|
|
1509
1786
|
"out-dir",
|
|
1510
1787
|
"max-part-bytes",
|
|
@@ -1525,6 +1802,7 @@ var COMMAND_OPTION_SCOPE = {
|
|
|
1525
1802
|
diff: ["format", "mode", "no-config", "no-update-check", "help", "version"],
|
|
1526
1803
|
describe: ["format", "mode", "help", "version"],
|
|
1527
1804
|
init: [
|
|
1805
|
+
"install-hook",
|
|
1528
1806
|
"cwd",
|
|
1529
1807
|
"out-dir",
|
|
1530
1808
|
"max-part-bytes",
|
|
@@ -1547,13 +1825,29 @@ var COMMAND_OPTION_SCOPE = {
|
|
|
1547
1825
|
// `mcp` reads the repository, so it takes --cwd. It has no --format: stdout carries JSON-RPC
|
|
1548
1826
|
// messages, and there is no second shape to choose, so offering the flag would be a lie.
|
|
1549
1827
|
mcp: ["cwd", "mode", "help", "version"],
|
|
1828
|
+
// `setup` generates through the `generate` command, so every generation setting it can
|
|
1829
|
+
// honour is in scope. Rejecting them made the one-command path unable to express a walk
|
|
1830
|
+
// scan, a monolith or a different large-file threshold; the alternative was to run
|
|
1831
|
+
// `setup` and then edit the config it had just written.
|
|
1550
1832
|
setup: [
|
|
1833
|
+
"install-hook",
|
|
1551
1834
|
"cwd",
|
|
1552
1835
|
"out-dir",
|
|
1553
1836
|
"max-part-bytes",
|
|
1837
|
+
// Persisted into the config `setup` writes, then forwarded to the generation it runs — so
|
|
1838
|
+
// the snapshot it produces validates against the config beside it. Accepting a setting here
|
|
1839
|
+
// without writing it down made `setup --large-file-bytes 100k` finish with a snapshot its
|
|
1840
|
+
// own `validate` called drifted, on the first run of a brand-new project.
|
|
1841
|
+
"max-part-tokens",
|
|
1842
|
+
"skeleton",
|
|
1843
|
+
"sarif",
|
|
1844
|
+
"large-file-bytes",
|
|
1845
|
+
"max-monolith-bytes",
|
|
1846
|
+
"include-monolith",
|
|
1554
1847
|
"include-sensitive-files",
|
|
1555
1848
|
"skip-artifacts",
|
|
1556
1849
|
"only-artifacts",
|
|
1850
|
+
"discovery",
|
|
1557
1851
|
"preset",
|
|
1558
1852
|
"include",
|
|
1559
1853
|
"exclude",
|
|
@@ -1839,6 +2133,13 @@ function buildAgentContract() {
|
|
|
1839
2133
|
appliesTo: optionAppliesTo("max-part-bytes"),
|
|
1840
2134
|
summary: 'Byte budget per markdown part. Accepts "120k" / "1.5mb". Minimum 4096.'
|
|
1841
2135
|
},
|
|
2136
|
+
{
|
|
2137
|
+
flag: "--max-part-tokens",
|
|
2138
|
+
type: "string",
|
|
2139
|
+
default: "unset",
|
|
2140
|
+
appliesTo: optionAppliesTo("max-part-tokens"),
|
|
2141
|
+
summary: "Ceiling on a part expressed in tokens. Narrows --max-part-bytes instead of replacing it; the manifest records both units. Minimum 1024."
|
|
2142
|
+
},
|
|
1842
2143
|
{
|
|
1843
2144
|
flag: "--large-file-bytes",
|
|
1844
2145
|
type: "string",
|
|
@@ -1888,6 +2189,41 @@ function buildAgentContract() {
|
|
|
1888
2189
|
appliesTo: optionAppliesTo("only-artifacts"),
|
|
1889
2190
|
summary: "Write only these artifacts. Cannot be combined with --skip-artifacts."
|
|
1890
2191
|
},
|
|
2192
|
+
{
|
|
2193
|
+
flag: "--skeleton",
|
|
2194
|
+
type: "boolean",
|
|
2195
|
+
default: false,
|
|
2196
|
+
appliesTo: optionAppliesTo("skeleton"),
|
|
2197
|
+
summary: 'Replace function and method bodies with `{ /* ... */ }` in TypeScript/TSX files, keeping signatures and comments. The part records inclusionMode "signatures" for each file it touched.'
|
|
2198
|
+
},
|
|
2199
|
+
{
|
|
2200
|
+
flag: "--sarif",
|
|
2201
|
+
type: "boolean",
|
|
2202
|
+
default: false,
|
|
2203
|
+
appliesTo: optionAppliesTo("sarif"),
|
|
2204
|
+
summary: "Also write reports/audit.sarif (SARIF 2.1.0) so a CI security gate can read the redaction findings. The matched value is never included."
|
|
2205
|
+
},
|
|
2206
|
+
{
|
|
2207
|
+
flag: "--changed-since",
|
|
2208
|
+
type: "string",
|
|
2209
|
+
default: "unset",
|
|
2210
|
+
appliesTo: optionAppliesTo("changed-since"),
|
|
2211
|
+
summary: 'Snapshot only the files that differ from this git ref, plus untracked ones. Everything else is recorded in skipped[] with reason "unchanged". Requires a git checkout.'
|
|
2212
|
+
},
|
|
2213
|
+
{
|
|
2214
|
+
flag: "--staged",
|
|
2215
|
+
type: "boolean",
|
|
2216
|
+
default: false,
|
|
2217
|
+
appliesTo: optionAppliesTo("staged"),
|
|
2218
|
+
summary: "Snapshot only the files staged in the index. The pre-commit counterpart of --changed-since."
|
|
2219
|
+
},
|
|
2220
|
+
{
|
|
2221
|
+
flag: "--with-dependents",
|
|
2222
|
+
type: "boolean",
|
|
2223
|
+
default: false,
|
|
2224
|
+
appliesTo: optionAppliesTo("with-dependents"),
|
|
2225
|
+
summary: "Widen a changed-files scope by one hop: files that import a changed file are included too. Only meaningful with --changed-since or --staged."
|
|
2226
|
+
},
|
|
1891
2227
|
{
|
|
1892
2228
|
flag: "--format",
|
|
1893
2229
|
type: "string",
|
|
@@ -1995,6 +2331,13 @@ function buildAgentContract() {
|
|
|
1995
2331
|
appliesTo: optionAppliesTo("no-install"),
|
|
1996
2332
|
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)."
|
|
1997
2333
|
},
|
|
2334
|
+
{
|
|
2335
|
+
flag: "--install-hook",
|
|
2336
|
+
type: "boolean",
|
|
2337
|
+
default: false,
|
|
2338
|
+
appliesTo: optionAppliesTo("install-hook"),
|
|
2339
|
+
summary: "After writing the config, install a pre-commit hook that runs `validate`. An existing hook this tool did not write is left alone."
|
|
2340
|
+
},
|
|
1998
2341
|
{
|
|
1999
2342
|
flag: "--no-update-check",
|
|
2000
2343
|
type: "boolean",
|
|
@@ -2031,6 +2374,12 @@ function buildAgentContract() {
|
|
|
2031
2374
|
{ key: "branch", type: "string", default: "main", summary: "Branch recorded when git cannot report one." },
|
|
2032
2375
|
{ key: "preset", type: "string", default: "detected", summary: "Force a preset. Unknown names are an error." },
|
|
2033
2376
|
{ key: "maxPartBytes", type: "number | string", default: 307200, summary: "Per-part byte budget." },
|
|
2377
|
+
{ key: "maxPartTokens", type: "number | string | null", default: null, summary: "Per-part token ceiling; narrows maxPartBytes. Minimum 1024." },
|
|
2378
|
+
{ key: "skeleton", type: "boolean", default: false, summary: "Collapse function bodies to `{ /* ... */ }` in TS, JS, TSX and JSX files." },
|
|
2379
|
+
{ key: "sarif", type: "boolean", default: false, summary: "Also write reports/audit.sarif." },
|
|
2380
|
+
{ key: "changedSince", type: "string | null", default: null, summary: "Snapshot only files that differ from this git ref." },
|
|
2381
|
+
{ key: "staged", type: "boolean", default: false, summary: "Snapshot only files staged in the index." },
|
|
2382
|
+
{ key: "withDependents", type: "boolean", default: false, summary: "With a changed-files scope, also include one hop of importers." },
|
|
2034
2383
|
{ key: "largeFileBytes", type: "number | string", default: 204800, summary: "Preview threshold for large files." },
|
|
2035
2384
|
{ key: "previewHeadLines", type: "number", default: 80, summary: "Lines kept from the head of a large file." },
|
|
2036
2385
|
{ key: "previewTailLines", type: "number", default: 40, summary: "Lines kept from the tail of a large file." },
|
|
@@ -2213,14 +2562,19 @@ do not assume the repository is broken.
|
|
|
2213
2562
|
matches the files on disk. Exit 1 means drift \u2014 re-run step 5.
|
|
2214
2563
|
|
|
2215
2564
|
7. **Read the output in this order:**
|
|
2216
|
-
- \`snapshot/parts/part-*.md\` \u2014 the code itself
|
|
2217
|
-
language and, when
|
|
2218
|
-
classes, functions). A
|
|
2565
|
+
- \`snapshot/parts/part-*.md\` \u2014 the code itself. Each file carries one \`- Meta:\` line
|
|
2566
|
+
(layer/kind, language, inclusion, size) and, only when the body is **not** the whole
|
|
2567
|
+
file, a structural summary (imports, exports, classes, functions). A full file has no
|
|
2568
|
+
summary line, because the summary would repeat the code three lines below it \u2014 read
|
|
2569
|
+
\`manifest.json\` for it. A summary dropped to fit the budget appears as a
|
|
2570
|
+
\`Summary: (omitted \u2026)\` line.
|
|
2219
2571
|
- \`snapshot/manifest.json\` \u2014 the index. \`files[]\` has \`relativePath\`, \`layer\`,
|
|
2220
|
-
\`kind\`, \`language\`, \`part\`, \`inclusionMode\` and
|
|
2221
|
-
when the file was not plain UTF-8. \`summary\` is
|
|
2222
|
-
languages with no structural summary, and
|
|
2223
|
-
to fit the part budget \u2014 never assume it
|
|
2572
|
+
\`kind\`, \`language\`, \`part\`, \`inclusionMode\`, \`hashSha256\` and the structural
|
|
2573
|
+
\`summary\`, plus \`encoding\` when the file was not plain UTF-8. \`summary\` is
|
|
2574
|
+
**optional**: it is absent for languages with no structural summary, and
|
|
2575
|
+
\`summaryOmitted: true\` marks one dropped to fit the part budget \u2014 never assume it
|
|
2576
|
+
exists. The per-file \`hashSha256\` is only here and in the manifest; it is not printed
|
|
2577
|
+
into the parts.
|
|
2224
2578
|
\`skipped[]\` records every excluded file **and why**. Prefer querying this over
|
|
2225
2579
|
re-reading the parts.
|
|
2226
2580
|
- \`snapshot/reports/audit.md\` \u2014 secrets found, large files, parse diagnostics, git
|
|
@@ -2274,7 +2628,7 @@ diff, and the commit, are then yours.
|
|
|
2274
2628
|
## When the client speaks MCP
|
|
2275
2629
|
|
|
2276
2630
|
If the agent runs inside an MCP client, \`npx ${PACKAGE_NAME2}@latest mcp\` serves this repository
|
|
2277
|
-
as
|
|
2631
|
+
as eight tools over stdio instead of shelling out. Its reads come from the snapshot rather than
|
|
2278
2632
|
the working tree, so excluded files and redacted values never reach the context. Use
|
|
2279
2633
|
\`snapshot_locate\` before \`snapshot_read\`, and \`snapshot_validate\` before trusting a snapshot
|
|
2280
2634
|
from an earlier session.
|
|
@@ -2394,7 +2748,7 @@ function buildResourceProvider() {
|
|
|
2394
2748
|
const report = REPORT_URIS.get(uri);
|
|
2395
2749
|
if (report) {
|
|
2396
2750
|
const text = readFileIfPresent(
|
|
2397
|
-
|
|
2751
|
+
path.resolve(workspace.root, workspace.config.outDir, "reports", report.file)
|
|
2398
2752
|
);
|
|
2399
2753
|
if (text === null) return null;
|
|
2400
2754
|
return { uri, mimeType: "text/markdown", text: capUtf8(text, MAX_RESOURCE_BYTES).text };
|
|
@@ -2412,9 +2766,9 @@ function buildResourceProvider() {
|
|
|
2412
2766
|
const index = Number(partMatch[1]);
|
|
2413
2767
|
const part = read.manifest.parts.find((candidate) => candidate.index === index);
|
|
2414
2768
|
if (!part) return null;
|
|
2415
|
-
const
|
|
2416
|
-
|
|
2417
|
-
);
|
|
2769
|
+
const partPath = partPathFor(workspace, part.filename);
|
|
2770
|
+
if (partPath === null) return null;
|
|
2771
|
+
const text = readFileIfPresent(partPath);
|
|
2418
2772
|
if (text === null) return null;
|
|
2419
2773
|
return { uri, mimeType: "text/markdown", text: capUtf8(text, MAX_RESOURCE_BYTES).text };
|
|
2420
2774
|
}
|
|
@@ -2431,7 +2785,8 @@ var SERVER_INSTRUCTIONS = [
|
|
|
2431
2785
|
"1. snapshot_status \u2014 is there a snapshot here, and has the tree moved since it was built?",
|
|
2432
2786
|
"2. snapshot_locate \u2014 find which part holds the code you need.",
|
|
2433
2787
|
"3. snapshot_read \u2014 read a window of one file. Never read a part end to end.",
|
|
2434
|
-
"4.
|
|
2788
|
+
"4. snapshot_dependents \u2014 before changing a file, see what imports it.",
|
|
2789
|
+
"5. snapshot_validate \u2014 before trusting context that came from an earlier conversation.",
|
|
2435
2790
|
"",
|
|
2436
2791
|
"Rules that matter:",
|
|
2437
2792
|
"- Parts are budgeted, not small: a 12-part snapshot is hundreds of kilobytes. Use snapshot_locate first.",
|
|
@@ -2457,6 +2812,7 @@ function createWriter(output) {
|
|
|
2457
2812
|
}
|
|
2458
2813
|
function createLineReader(input, options) {
|
|
2459
2814
|
let buffer = "";
|
|
2815
|
+
let bufferBytes = 0;
|
|
2460
2816
|
let discarding = false;
|
|
2461
2817
|
let discardedBytes = 0;
|
|
2462
2818
|
const deliver = (line) => {
|
|
@@ -2469,43 +2825,71 @@ function createLineReader(input, options) {
|
|
|
2469
2825
|
if (trimmed.trim().length === 0) return;
|
|
2470
2826
|
options.onLine(trimmed);
|
|
2471
2827
|
};
|
|
2828
|
+
const discardBuffered = () => {
|
|
2829
|
+
discarding = true;
|
|
2830
|
+
discardedBytes += bufferBytes;
|
|
2831
|
+
buffer = "";
|
|
2832
|
+
bufferBytes = 0;
|
|
2833
|
+
options.onOverflow?.(discardedBytes);
|
|
2834
|
+
};
|
|
2472
2835
|
const drain = () => {
|
|
2473
2836
|
let index = buffer.indexOf("\n");
|
|
2474
2837
|
while (index >= 0) {
|
|
2475
2838
|
const line = buffer.slice(0, index);
|
|
2476
2839
|
buffer = buffer.slice(index + 1);
|
|
2477
|
-
|
|
2840
|
+
if (discarding) {
|
|
2841
|
+
deliver(line);
|
|
2842
|
+
} else {
|
|
2843
|
+
const lineBytes = Buffer.byteLength(line, "utf8");
|
|
2844
|
+
if (lineBytes > MAX_LINE_BYTES) options.onOverflow?.(lineBytes);
|
|
2845
|
+
else deliver(line);
|
|
2846
|
+
}
|
|
2478
2847
|
index = buffer.indexOf("\n");
|
|
2479
2848
|
}
|
|
2849
|
+
bufferBytes = Buffer.byteLength(buffer, "utf8");
|
|
2850
|
+
if (!discarding && bufferBytes > MAX_LINE_BYTES) discardBuffered();
|
|
2480
2851
|
};
|
|
2481
2852
|
const onData = (chunk) => {
|
|
2853
|
+
const chunkBytes = Buffer.byteLength(chunk, "utf8");
|
|
2482
2854
|
if (discarding) {
|
|
2483
2855
|
const index = chunk.indexOf("\n");
|
|
2484
2856
|
if (index < 0) {
|
|
2485
|
-
discardedBytes +=
|
|
2857
|
+
discardedBytes += chunkBytes;
|
|
2486
2858
|
return;
|
|
2487
2859
|
}
|
|
2488
2860
|
buffer = chunk.slice(index + 1);
|
|
2861
|
+
bufferBytes = Buffer.byteLength(buffer, "utf8");
|
|
2489
2862
|
deliver("");
|
|
2490
2863
|
drain();
|
|
2491
2864
|
return;
|
|
2492
2865
|
}
|
|
2493
|
-
|
|
2494
|
-
if (buffer.length > MAX_LINE_BYTES && buffer.indexOf("\n") < 0) {
|
|
2866
|
+
if (chunk.indexOf("\n") < 0 && bufferBytes + chunkBytes > MAX_LINE_BYTES) {
|
|
2495
2867
|
discarding = true;
|
|
2496
|
-
discardedBytes =
|
|
2868
|
+
discardedBytes = bufferBytes + chunkBytes;
|
|
2497
2869
|
buffer = "";
|
|
2870
|
+
bufferBytes = 0;
|
|
2498
2871
|
options.onOverflow?.(discardedBytes);
|
|
2499
2872
|
return;
|
|
2500
2873
|
}
|
|
2874
|
+
buffer += chunk;
|
|
2501
2875
|
drain();
|
|
2502
2876
|
};
|
|
2503
2877
|
const onEnd = () => {
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2878
|
+
const rest = buffer;
|
|
2879
|
+
const restBytes = bufferBytes || Buffer.byteLength(rest, "utf8");
|
|
2880
|
+
buffer = "";
|
|
2881
|
+
bufferBytes = 0;
|
|
2882
|
+
if (rest.length === 0) return;
|
|
2883
|
+
if (discarding) {
|
|
2884
|
+
discarding = false;
|
|
2885
|
+
discardedBytes = 0;
|
|
2886
|
+
return;
|
|
2887
|
+
}
|
|
2888
|
+
if (restBytes > MAX_LINE_BYTES) {
|
|
2889
|
+
options.onOverflow?.(restBytes);
|
|
2890
|
+
return;
|
|
2508
2891
|
}
|
|
2892
|
+
deliver(rest);
|
|
2509
2893
|
};
|
|
2510
2894
|
input.setEncoding("utf8");
|
|
2511
2895
|
input.on("data", onData);
|
|
@@ -2598,6 +2982,4 @@ async function runStdioServer(options) {
|
|
|
2598
2982
|
return code;
|
|
2599
2983
|
}
|
|
2600
2984
|
|
|
2601
|
-
export { COMMAND_OPTION_SCOPE, ERROR_CODES, LEGACY_PROTOCOL_VERSIONS, MAX_LINE_BYTES, METHOD, MODERN_PROTOCOL_VERSIONS, SERVER_INSTRUCTIONS, SKILL_NAME, SUPPORTED_PROTOCOL_VERSIONS, buildAgentContract, buildPrompts, buildResourceProvider, buildTools, classifyRequest, coerceNumericStrings, commandsForOption, createLineReader, createMcpServer, createServer, createSession, createWriter, inspectModernMeta, negotiateLegacyVersion, renderContractText, renderSkill, runStdioServer, validateArguments };
|
|
2602
|
-
//# sourceMappingURL=chunk-HQBEQ67K.js.map
|
|
2603
|
-
//# sourceMappingURL=chunk-HQBEQ67K.js.map
|
|
2985
|
+
export { COMMAND_OPTION_SCOPE, ERROR_CODES, LEGACY_PROTOCOL_VERSIONS, MAX_LINE_BYTES, METHOD, MODERN_PROTOCOL_VERSIONS, SERVER_INSTRUCTIONS, SKILL_NAME, SUPPORTED_PROTOCOL_VERSIONS, buildAgentContract, buildPrompts, buildResourceProvider, buildTools, classifyRequest, coerceNumericStrings, commandsForOption, createLineReader, createMcpServer, createServer, createSession, createWriter, findSection, inspectModernMeta, negotiateLegacyVersion, parsePartSections, renderContractText, renderSkill, runStdioServer, validateArguments, windowText };
|