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.js
CHANGED
|
@@ -1,750 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { buildAgentContract, runStdioServer, COMMAND_OPTION_SCOPE, commandsForOption, SKILL_NAME, renderSkill, renderContractText } from './chunk-EWZYK3VX.js';
|
|
2
|
+
import { loadConfigFileDetailed, resolveConfig, validateSnapshot, describeManifestShapeProblem, explainPath, compareManifests, loadManifest, renderDiffText, generateSnapshot, formatNumber, GENERATOR_VERSION, isDiscoveryMode, ARTIFACT_NAMES, getPreset, unknownPresetMessage, GENERATOR_NAME, runInit, PromptCancelled, planFirstRun, isEffectivelyEmpty, isInteractive, defaultIO, hasPriorSnapshot, DEFAULT_ANSWERS, findConfigFile, firstRunMessage, parseByteSize, gitignorePath, appendGitignoreEntries, escapeControls, MIN_MAX_PART_BYTES, renderExplainText } from './chunk-FCWD4LY7.js';
|
|
2
3
|
import { spawnSync } from 'child_process';
|
|
3
4
|
import * as fs from 'fs';
|
|
4
5
|
import * as path2 from 'path';
|
|
5
6
|
import { parseArgs } from 'util';
|
|
6
7
|
|
|
7
|
-
// src/agent/option-scope.ts
|
|
8
|
-
var VALIDATE_OPTIONS = [
|
|
9
|
-
"cwd",
|
|
10
|
-
"out-dir",
|
|
11
|
-
"max-part-bytes",
|
|
12
|
-
"large-file-bytes",
|
|
13
|
-
"max-monolith-bytes",
|
|
14
|
-
"include-monolith",
|
|
15
|
-
"include-sensitive-files",
|
|
16
|
-
"skip-artifacts",
|
|
17
|
-
"only-artifacts",
|
|
18
|
-
"no-config",
|
|
19
|
-
"discovery",
|
|
20
|
-
"format",
|
|
21
|
-
"mode",
|
|
22
|
-
"help",
|
|
23
|
-
"version"
|
|
24
|
-
];
|
|
25
|
-
var COMMAND_OPTION_SCOPE = {
|
|
26
|
-
generate: [
|
|
27
|
-
"cwd",
|
|
28
|
-
"out-dir",
|
|
29
|
-
"max-part-bytes",
|
|
30
|
-
"large-file-bytes",
|
|
31
|
-
"max-monolith-bytes",
|
|
32
|
-
"include-monolith",
|
|
33
|
-
"include-sensitive-files",
|
|
34
|
-
"skip-artifacts",
|
|
35
|
-
"only-artifacts",
|
|
36
|
-
"no-config",
|
|
37
|
-
"discovery",
|
|
38
|
-
"format",
|
|
39
|
-
"mode",
|
|
40
|
-
"dry-run",
|
|
41
|
-
"watch",
|
|
42
|
-
"help",
|
|
43
|
-
"version"
|
|
44
|
-
],
|
|
45
|
-
validate: VALIDATE_OPTIONS,
|
|
46
|
-
report: [
|
|
47
|
-
"cwd",
|
|
48
|
-
"out-dir",
|
|
49
|
-
"max-part-bytes",
|
|
50
|
-
"large-file-bytes",
|
|
51
|
-
"max-monolith-bytes",
|
|
52
|
-
"include-monolith",
|
|
53
|
-
"include-sensitive-files",
|
|
54
|
-
"skip-artifacts",
|
|
55
|
-
"only-artifacts",
|
|
56
|
-
"no-config",
|
|
57
|
-
"format",
|
|
58
|
-
"mode",
|
|
59
|
-
"help",
|
|
60
|
-
"version"
|
|
61
|
-
],
|
|
62
|
-
explain: [
|
|
63
|
-
"cwd",
|
|
64
|
-
"out-dir",
|
|
65
|
-
"max-part-bytes",
|
|
66
|
-
"large-file-bytes",
|
|
67
|
-
"max-monolith-bytes",
|
|
68
|
-
"include-monolith",
|
|
69
|
-
"include-sensitive-files",
|
|
70
|
-
"skip-artifacts",
|
|
71
|
-
"only-artifacts",
|
|
72
|
-
"no-config",
|
|
73
|
-
"discovery",
|
|
74
|
-
"format",
|
|
75
|
-
"mode",
|
|
76
|
-
"help",
|
|
77
|
-
"version"
|
|
78
|
-
],
|
|
79
|
-
diff: ["format", "mode", "no-config", "help", "version"],
|
|
80
|
-
describe: ["format", "mode", "help", "version"],
|
|
81
|
-
init: [
|
|
82
|
-
"cwd",
|
|
83
|
-
"out-dir",
|
|
84
|
-
"max-part-bytes",
|
|
85
|
-
"include-sensitive-files",
|
|
86
|
-
"skip-artifacts",
|
|
87
|
-
"only-artifacts",
|
|
88
|
-
"preset",
|
|
89
|
-
"include",
|
|
90
|
-
"exclude",
|
|
91
|
-
"yes",
|
|
92
|
-
"force",
|
|
93
|
-
"dry-run",
|
|
94
|
-
"format",
|
|
95
|
-
"mode",
|
|
96
|
-
"help",
|
|
97
|
-
"version"
|
|
98
|
-
],
|
|
99
|
-
skill: ["cwd", "format", "mode", "install", "target", "help", "version"],
|
|
100
|
-
setup: [
|
|
101
|
-
"cwd",
|
|
102
|
-
"out-dir",
|
|
103
|
-
"max-part-bytes",
|
|
104
|
-
"include-sensitive-files",
|
|
105
|
-
"skip-artifacts",
|
|
106
|
-
"only-artifacts",
|
|
107
|
-
"preset",
|
|
108
|
-
"include",
|
|
109
|
-
"exclude",
|
|
110
|
-
"yes",
|
|
111
|
-
"force",
|
|
112
|
-
"no-install",
|
|
113
|
-
"dry-run",
|
|
114
|
-
"format",
|
|
115
|
-
"mode",
|
|
116
|
-
"help",
|
|
117
|
-
"version"
|
|
118
|
-
]
|
|
119
|
-
};
|
|
120
|
-
var COMMAND_ORDER = [
|
|
121
|
-
"generate",
|
|
122
|
-
"validate",
|
|
123
|
-
"report",
|
|
124
|
-
"explain",
|
|
125
|
-
"init",
|
|
126
|
-
"setup",
|
|
127
|
-
"diff",
|
|
128
|
-
"describe",
|
|
129
|
-
"skill"
|
|
130
|
-
];
|
|
131
|
-
function commandsForOption(option) {
|
|
132
|
-
return COMMAND_ORDER.filter((command) => COMMAND_OPTION_SCOPE[command].includes(option));
|
|
133
|
-
}
|
|
134
|
-
function optionAppliesTo(option) {
|
|
135
|
-
return commandsForOption(option).join("/");
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// src/agent/contract.ts
|
|
139
|
-
var CONTRACT_VERSION = "1.0.0";
|
|
140
|
-
var PACKAGE_NAME = "codebase-snapshot";
|
|
141
|
-
var MAJOR_VERSION = GENERATOR_VERSION.split(".")[0];
|
|
142
|
-
var ARTIFACT_SUMMARY = {
|
|
143
|
-
readme: "README.md describing the snapshot at the output root.",
|
|
144
|
-
manifest: "manifest.json: machine-readable index of every file, part, hash and config.",
|
|
145
|
-
parts: "parts/*.md: the packed, budget-limited markdown parts that carry the code.",
|
|
146
|
-
navigation: "reports/navigation.md: links between parts and layers.",
|
|
147
|
-
projectTree: "reports/project-tree.md: the directory tree of included files.",
|
|
148
|
-
audit: "reports/audit.md: secret findings, large files, parse diagnostics, git state.",
|
|
149
|
-
monolith: "full-codebase.json and full-codebase.md: the whole repository in one file (off by default)."
|
|
150
|
-
};
|
|
151
|
-
var ARTIFACT_DEFAULTS = {
|
|
152
|
-
readme: true,
|
|
153
|
-
manifest: true,
|
|
154
|
-
parts: true,
|
|
155
|
-
navigation: true,
|
|
156
|
-
projectTree: true,
|
|
157
|
-
audit: true,
|
|
158
|
-
monolith: false
|
|
159
|
-
};
|
|
160
|
-
function renderContractText(contract) {
|
|
161
|
-
const lines = [];
|
|
162
|
-
lines.push(`${contract.package} v${contract.version}`);
|
|
163
|
-
lines.push(contract.summary);
|
|
164
|
-
lines.push("");
|
|
165
|
-
lines.push(`Node : ${contract.requires.node}`);
|
|
166
|
-
lines.push(`Schema : ${contract.schemaVersion} (contract ${contract.contractVersion})`);
|
|
167
|
-
lines.push(`One-shot : ${contract.invocation.oneShot} <command>`);
|
|
168
|
-
lines.push(`Installed : ${contract.invocation.installed} <command>`);
|
|
169
|
-
lines.push("");
|
|
170
|
-
lines.push("Workflow:");
|
|
171
|
-
for (const step of contract.workflow) {
|
|
172
|
-
lines.push(` ${step.step}. ${step.id}`);
|
|
173
|
-
lines.push(` run : ${step.run}`);
|
|
174
|
-
lines.push(` expect : ${step.expect}`);
|
|
175
|
-
if (step.notes) lines.push(` note : ${step.notes}`);
|
|
176
|
-
}
|
|
177
|
-
lines.push("");
|
|
178
|
-
lines.push("Commands:");
|
|
179
|
-
for (const command of contract.commands) {
|
|
180
|
-
const args = command.positionals.length > 0 ? ` ${command.positionals.join(" ")}` : "";
|
|
181
|
-
lines.push(` ${(command.name + args).padEnd(26)} ${command.summary}`);
|
|
182
|
-
}
|
|
183
|
-
lines.push("");
|
|
184
|
-
lines.push("Exit codes:");
|
|
185
|
-
for (const [code, meaning] of Object.entries(contract.exitCodes)) {
|
|
186
|
-
lines.push(` ${code} ${meaning}`);
|
|
187
|
-
}
|
|
188
|
-
lines.push("");
|
|
189
|
-
lines.push(`Presets (${contract.presets.length}): ${contract.presets.map((p) => p.name).join(", ")}`);
|
|
190
|
-
lines.push("");
|
|
191
|
-
lines.push("Artifacts:");
|
|
192
|
-
for (const artifact of contract.artifacts) {
|
|
193
|
-
lines.push(` ${artifact.name.padEnd(12)} ${artifact.default ? "on " : "off"} ${artifact.summary}`);
|
|
194
|
-
}
|
|
195
|
-
lines.push("");
|
|
196
|
-
lines.push("Options:");
|
|
197
|
-
for (const option of contract.options) {
|
|
198
|
-
const fallback = option.default === void 0 ? "" : ` [${JSON.stringify(option.default)}]`;
|
|
199
|
-
lines.push(` ${option.flag.padEnd(24)} ${option.appliesTo.padEnd(32)}${fallback}`);
|
|
200
|
-
lines.push(` ${"".padEnd(24)} ${option.summary}`);
|
|
201
|
-
}
|
|
202
|
-
lines.push("");
|
|
203
|
-
lines.push("Safety:");
|
|
204
|
-
for (const note of contract.safety) lines.push(` - ${note}`);
|
|
205
|
-
lines.push("");
|
|
206
|
-
lines.push("Troubleshooting:");
|
|
207
|
-
for (const item of contract.troubleshooting) {
|
|
208
|
-
lines.push(` ${item.symptom}`);
|
|
209
|
-
lines.push(` cause: ${item.cause}`);
|
|
210
|
-
lines.push(` fix : ${item.fix}`);
|
|
211
|
-
}
|
|
212
|
-
lines.push("");
|
|
213
|
-
lines.push("This is the human rendering. Agents should use: describe --format json");
|
|
214
|
-
return lines.join("\n");
|
|
215
|
-
}
|
|
216
|
-
function buildAgentContract() {
|
|
217
|
-
return {
|
|
218
|
-
generator: GENERATOR_NAME,
|
|
219
|
-
package: PACKAGE_NAME,
|
|
220
|
-
version: GENERATOR_VERSION,
|
|
221
|
-
schemaVersion: SCHEMA_VERSION,
|
|
222
|
-
contractVersion: CONTRACT_VERSION,
|
|
223
|
-
summary: "Turns a repository into an LLM-ready snapshot: code packed into size-budgeted markdown parts, plus a manifest, a project tree, navigation and a secret audit. Auto-detects the project shape (framework, language, monorepo, docs, infra).",
|
|
224
|
-
requires: { node: ">=18.0.0", dependencies: ["typescript"] },
|
|
225
|
-
invocation: {
|
|
226
|
-
oneShot: `npx ${PACKAGE_NAME}@latest`,
|
|
227
|
-
installed: "npx codebase-snapshot",
|
|
228
|
-
bins: ["codebase-snapshot", "snap"],
|
|
229
|
-
notes: [
|
|
230
|
-
`The bare package name also works: npx ${PACKAGE_NAME} <command>.`,
|
|
231
|
-
"Pin an exact version \u2014 for example `codebase-snapshot@0.9.0` \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.",
|
|
232
|
-
"Both bin names point at the same file. npm resolves a package by its bin entries, so they must stay aliases of one executable.",
|
|
233
|
-
"Human progress goes to stderr. With --format json the payload on stdout is pure JSON.",
|
|
234
|
-
"A failing run also writes pure JSON to stdout: { error: { message, exitCode } }. The exit code and the stderr line are unchanged."
|
|
235
|
-
]
|
|
236
|
-
},
|
|
237
|
-
workflow: [
|
|
238
|
-
{
|
|
239
|
-
step: 1,
|
|
240
|
-
id: "verify",
|
|
241
|
-
run: `npx ${PACKAGE_NAME}@latest --version`,
|
|
242
|
-
// The requirement is the major version; the exact build is context. A
|
|
243
|
-
// floating tag may print 4.0.7 today and 4.0.9 next week, and an agent
|
|
244
|
-
// must not conclude that a working tool is broken because of a patch.
|
|
245
|
-
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)`,
|
|
246
|
-
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\`.`
|
|
247
|
-
},
|
|
248
|
-
{
|
|
249
|
-
step: 2,
|
|
250
|
-
id: "understand",
|
|
251
|
-
run: `npx ${PACKAGE_NAME}@latest describe --format json`,
|
|
252
|
-
expect: "the full contract as JSON: commands, options, presets, exit codes",
|
|
253
|
-
notes: "Read this before acting if you have no prior knowledge of the tool."
|
|
254
|
-
},
|
|
255
|
-
{
|
|
256
|
-
step: 3,
|
|
257
|
-
id: "configure",
|
|
258
|
-
run: `npx ${PACKAGE_NAME}@latest init --yes`,
|
|
259
|
-
expect: 'writes a commented config file listing every setting at its current value: snapshot.config.ts only when the running Node can strip types (>= 22.6) and the project is an ES module (`"type": "module"` in package.json); snapshot.config.mjs otherwise',
|
|
260
|
-
notes: "Non-interactive. The detected preset is chosen automatically; override with --preset <name>. Skip this step to run on built-in defaults."
|
|
261
|
-
},
|
|
262
|
-
{
|
|
263
|
-
step: 4,
|
|
264
|
-
id: "generate",
|
|
265
|
-
run: `npx ${PACKAGE_NAME}@latest generate`,
|
|
266
|
-
expect: "exit 0, and ./snapshot/ written with parts, reports and manifest.json"
|
|
267
|
-
},
|
|
268
|
-
{
|
|
269
|
-
step: 5,
|
|
270
|
-
id: "verify-output",
|
|
271
|
-
run: `npx ${PACKAGE_NAME}@latest validate`,
|
|
272
|
-
expect: "exit 0 means the snapshot matches the files on disk",
|
|
273
|
-
notes: "Exit 1 means drift or a damaged snapshot; re-run generate."
|
|
274
|
-
},
|
|
275
|
-
{
|
|
276
|
-
step: 6,
|
|
277
|
-
id: "consume",
|
|
278
|
-
run: "read ./snapshot/parts/*.md, then ./snapshot/manifest.json, then ./snapshot/reports/audit.md",
|
|
279
|
-
expect: "the code, the index of what was included, and the risk findings",
|
|
280
|
-
notes: "manifest.json.files[] lists every included file with layer, kind, language, part and summary. manifest.skipped[] explains every file that was left out and why."
|
|
281
|
-
}
|
|
282
|
-
],
|
|
283
|
-
commands: [
|
|
284
|
-
{
|
|
285
|
-
name: "generate",
|
|
286
|
-
aliases: ["default"],
|
|
287
|
-
summary: "Write the snapshot. This is the command you normally want.",
|
|
288
|
-
positionals: [],
|
|
289
|
-
exits: [0, 1]
|
|
290
|
-
},
|
|
291
|
-
{
|
|
292
|
-
name: "validate",
|
|
293
|
-
aliases: ["check"],
|
|
294
|
-
summary: "Compare the snapshot on disk with the repository; exit 1 on drift. `check` is an alias of `validate`.",
|
|
295
|
-
positionals: [],
|
|
296
|
-
exits: [0, 1]
|
|
297
|
-
},
|
|
298
|
-
{
|
|
299
|
-
name: "report",
|
|
300
|
-
aliases: [],
|
|
301
|
-
summary: "Print snapshot health, layer metrics and the secret audit.",
|
|
302
|
-
positionals: [],
|
|
303
|
-
exits: [0, 1]
|
|
304
|
-
},
|
|
305
|
-
{
|
|
306
|
-
name: "setup",
|
|
307
|
-
aliases: [],
|
|
308
|
-
summary: "Everything a repository needs in one command, with defaults: write the config when there is none (an existing one is kept), add the npm scripts, devDependency and .gitignore entries, install dependencies with the project's package manager, then generate the snapshot. --no-install skips the install; --dry-run writes nothing.",
|
|
309
|
-
positionals: [],
|
|
310
|
-
exits: [0, 1, 2]
|
|
311
|
-
},
|
|
312
|
-
{
|
|
313
|
-
name: "init",
|
|
314
|
-
aliases: [],
|
|
315
|
-
summary: "Write a commented config file (snapshot.config.ts only when Node can strip types and the project is an ES module; snapshot.config.mjs otherwise), add the snapshot scripts and the devDependency, and ignore the snapshot directories. Nothing else is written. Interactive, or --yes for CI.",
|
|
316
|
-
positionals: [],
|
|
317
|
-
exits: [0, 1, 2]
|
|
318
|
-
},
|
|
319
|
-
{
|
|
320
|
-
name: "explain",
|
|
321
|
-
aliases: [],
|
|
322
|
-
summary: "Explain how one path would be treated: layer, kind, part, skip reason. Always exits 0 when it can report; read `included` and `reason` instead of the exit code.",
|
|
323
|
-
positionals: ["<path>"],
|
|
324
|
-
exits: [0]
|
|
325
|
-
},
|
|
326
|
-
{
|
|
327
|
-
name: "diff",
|
|
328
|
-
aliases: [],
|
|
329
|
-
summary: "Compare two snapshots (directories or manifest.json files).",
|
|
330
|
-
positionals: ["<before>", "<after>"],
|
|
331
|
-
exits: [0, 1]
|
|
332
|
-
},
|
|
333
|
-
{
|
|
334
|
-
name: "describe",
|
|
335
|
-
aliases: [],
|
|
336
|
-
summary: "Print this contract. --format json for machines, text for humans.",
|
|
337
|
-
positionals: [],
|
|
338
|
-
exits: [0]
|
|
339
|
-
},
|
|
340
|
-
{
|
|
341
|
-
name: "skill",
|
|
342
|
-
aliases: [],
|
|
343
|
-
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.",
|
|
344
|
-
positionals: [],
|
|
345
|
-
exits: [0, 1, 2]
|
|
346
|
-
}
|
|
347
|
-
],
|
|
348
|
-
exitCodes: {
|
|
349
|
-
"0": "success (and validate: no drift)",
|
|
350
|
-
"1": "drift detected, or generation refused/failed (nothing was written)",
|
|
351
|
-
"2": "usage error: unknown flag/command, bad value, wrong argument count"
|
|
352
|
-
},
|
|
353
|
-
options: [
|
|
354
|
-
{
|
|
355
|
-
flag: "--out-dir",
|
|
356
|
-
type: "string",
|
|
357
|
-
default: "snapshot",
|
|
358
|
-
appliesTo: optionAppliesTo("out-dir"),
|
|
359
|
-
summary: "Where the snapshot is written. Must not be the repo root or a directory the tool did not create."
|
|
360
|
-
},
|
|
361
|
-
{
|
|
362
|
-
flag: "--max-part-bytes",
|
|
363
|
-
type: "string",
|
|
364
|
-
default: 307200,
|
|
365
|
-
appliesTo: optionAppliesTo("max-part-bytes"),
|
|
366
|
-
summary: 'Byte budget per markdown part. Accepts "120k" / "1.5mb". Minimum 4096.'
|
|
367
|
-
},
|
|
368
|
-
{
|
|
369
|
-
flag: "--large-file-bytes",
|
|
370
|
-
type: "string",
|
|
371
|
-
default: 204800,
|
|
372
|
-
appliesTo: optionAppliesTo("large-file-bytes"),
|
|
373
|
-
summary: "Above this size a file gets a head/tail preview instead of the full body."
|
|
374
|
-
},
|
|
375
|
-
{
|
|
376
|
-
flag: "--max-monolith-bytes",
|
|
377
|
-
type: "string",
|
|
378
|
-
default: "unset",
|
|
379
|
-
appliesTo: optionAppliesTo("max-monolith-bytes"),
|
|
380
|
-
summary: "Skip the monolith artifacts when they would exceed this size."
|
|
381
|
-
},
|
|
382
|
-
{
|
|
383
|
-
flag: "--include-monolith",
|
|
384
|
-
type: "boolean",
|
|
385
|
-
default: false,
|
|
386
|
-
appliesTo: optionAppliesTo("include-monolith"),
|
|
387
|
-
summary: "Also write full-codebase.json and full-codebase.md."
|
|
388
|
-
},
|
|
389
|
-
{
|
|
390
|
-
flag: "--include-sensitive-files",
|
|
391
|
-
type: "boolean",
|
|
392
|
-
default: false,
|
|
393
|
-
appliesTo: optionAppliesTo("include-sensitive-files"),
|
|
394
|
-
summary: "Also emit .env / *.pem / .npmrc style files. Off by default; they are recorded as skipped."
|
|
395
|
-
},
|
|
396
|
-
{
|
|
397
|
-
flag: "--discovery",
|
|
398
|
-
type: "string",
|
|
399
|
-
default: "auto",
|
|
400
|
-
appliesTo: optionAppliesTo("discovery"),
|
|
401
|
-
summary: "How files are discovered: auto (git when usable), git (require git), walk (read the root .gitignore directly; the escape hatch for a .git that cannot be listed)."
|
|
402
|
-
},
|
|
403
|
-
{
|
|
404
|
-
flag: "--skip-artifacts",
|
|
405
|
-
type: "string",
|
|
406
|
-
default: "unset",
|
|
407
|
-
appliesTo: optionAppliesTo("skip-artifacts"),
|
|
408
|
-
summary: "Comma-separated artifacts to omit (readme, manifest, parts, navigation, projectTree, audit, monolith)."
|
|
409
|
-
},
|
|
410
|
-
{
|
|
411
|
-
flag: "--only-artifacts",
|
|
412
|
-
type: "string",
|
|
413
|
-
default: "unset",
|
|
414
|
-
appliesTo: optionAppliesTo("only-artifacts"),
|
|
415
|
-
summary: "Write only these artifacts. Cannot be combined with --skip-artifacts."
|
|
416
|
-
},
|
|
417
|
-
{
|
|
418
|
-
flag: "--format",
|
|
419
|
-
type: "string",
|
|
420
|
-
default: "text",
|
|
421
|
-
// `optionAppliesTo` always returns the explicit slash list, so this is the same scoped form
|
|
422
|
-
// as every other option — no prose sentinel a machine consumer could misread.
|
|
423
|
-
appliesTo: optionAppliesTo("format"),
|
|
424
|
-
summary: 'Output format: "text" or "json". JSON goes to stdout, progress to stderr.'
|
|
425
|
-
},
|
|
426
|
-
{
|
|
427
|
-
flag: "--mode",
|
|
428
|
-
type: "string",
|
|
429
|
-
default: "generate",
|
|
430
|
-
appliesTo: optionAppliesTo("mode"),
|
|
431
|
-
summary: "Select the command when no command word is given. Values: generate, validate, check, report, init, setup, explain, diff, describe, skill. A command word and --mode must name the same command; a contradiction is a usage error (exit 2)."
|
|
432
|
-
},
|
|
433
|
-
{
|
|
434
|
-
flag: "--cwd",
|
|
435
|
-
type: "string",
|
|
436
|
-
default: "process.cwd()",
|
|
437
|
-
appliesTo: optionAppliesTo("cwd"),
|
|
438
|
-
summary: "Repository to operate on. Must be an existing directory."
|
|
439
|
-
},
|
|
440
|
-
{
|
|
441
|
-
flag: "--dry-run",
|
|
442
|
-
type: "boolean",
|
|
443
|
-
default: false,
|
|
444
|
-
appliesTo: optionAppliesTo("dry-run"),
|
|
445
|
-
summary: "Compute everything and print the result without writing."
|
|
446
|
-
},
|
|
447
|
-
{
|
|
448
|
-
flag: "--watch",
|
|
449
|
-
type: "boolean",
|
|
450
|
-
default: false,
|
|
451
|
-
appliesTo: optionAppliesTo("watch"),
|
|
452
|
-
summary: "Regenerate on every change until interrupted."
|
|
453
|
-
},
|
|
454
|
-
{
|
|
455
|
-
flag: "--no-config",
|
|
456
|
-
type: "boolean",
|
|
457
|
-
default: false,
|
|
458
|
-
appliesTo: optionAppliesTo("no-config"),
|
|
459
|
-
summary: "Ignore the config file and run on the built-in defaults."
|
|
460
|
-
},
|
|
461
|
-
{
|
|
462
|
-
flag: "--preset",
|
|
463
|
-
type: "string",
|
|
464
|
-
default: "detected",
|
|
465
|
-
appliesTo: optionAppliesTo("preset"),
|
|
466
|
-
summary: "Force a project preset instead of detecting one. Unknown names are a usage error."
|
|
467
|
-
},
|
|
468
|
-
{
|
|
469
|
-
flag: "--include",
|
|
470
|
-
type: "string",
|
|
471
|
-
default: "unset",
|
|
472
|
-
appliesTo: optionAppliesTo("include"),
|
|
473
|
-
summary: "Comma-separated allow-list of globs to record in the generated config."
|
|
474
|
-
},
|
|
475
|
-
{
|
|
476
|
-
flag: "--exclude",
|
|
477
|
-
type: "string",
|
|
478
|
-
default: "unset",
|
|
479
|
-
appliesTo: optionAppliesTo("exclude"),
|
|
480
|
-
summary: "Comma-separated globs to record in the generated config."
|
|
481
|
-
},
|
|
482
|
-
{
|
|
483
|
-
flag: "--yes",
|
|
484
|
-
type: "boolean",
|
|
485
|
-
default: false,
|
|
486
|
-
appliesTo: optionAppliesTo("yes"),
|
|
487
|
-
summary: "Accept every default without prompting. Required in a non-TTY."
|
|
488
|
-
},
|
|
489
|
-
{
|
|
490
|
-
flag: "--force",
|
|
491
|
-
type: "boolean",
|
|
492
|
-
default: false,
|
|
493
|
-
appliesTo: optionAppliesTo("force"),
|
|
494
|
-
summary: "Overwrite an existing config file."
|
|
495
|
-
},
|
|
496
|
-
{
|
|
497
|
-
flag: "--install",
|
|
498
|
-
type: "boolean",
|
|
499
|
-
default: false,
|
|
500
|
-
appliesTo: optionAppliesTo("install"),
|
|
501
|
-
summary: "Write the skill into the repository instead of printing it."
|
|
502
|
-
},
|
|
503
|
-
{
|
|
504
|
-
flag: "--target",
|
|
505
|
-
type: "string",
|
|
506
|
-
default: "all",
|
|
507
|
-
appliesTo: optionAppliesTo("target"),
|
|
508
|
-
summary: 'Where to install: "claude", "cursor" or "all".'
|
|
509
|
-
},
|
|
510
|
-
{
|
|
511
|
-
flag: "--no-install",
|
|
512
|
-
type: "boolean",
|
|
513
|
-
default: false,
|
|
514
|
-
appliesTo: optionAppliesTo("no-install"),
|
|
515
|
-
summary: "setup only: skip installing the dependency before generating (CI, offline, or a lockfile you do not want a package manager to touch)."
|
|
516
|
-
}
|
|
517
|
-
],
|
|
518
|
-
presets: PRESETS.map((preset) => ({
|
|
519
|
-
name: preset.name,
|
|
520
|
-
priority: preset.priority,
|
|
521
|
-
recommendedBudget: preset.recommendedBudget ?? "300k",
|
|
522
|
-
excludes: preset.exclude ? [...preset.exclude] : []
|
|
523
|
-
})),
|
|
524
|
-
artifacts: ARTIFACT_NAMES.map((name) => ({
|
|
525
|
-
name,
|
|
526
|
-
default: ARTIFACT_DEFAULTS[name] ?? true,
|
|
527
|
-
summary: ARTIFACT_SUMMARY[name] ?? name
|
|
528
|
-
})),
|
|
529
|
-
config: {
|
|
530
|
-
fileNames: [
|
|
531
|
-
"snapshot.config.ts",
|
|
532
|
-
"snapshot.config.js",
|
|
533
|
-
"snapshot.config.mjs",
|
|
534
|
-
"snapshot.config.cjs",
|
|
535
|
-
"snapshot.config.json",
|
|
536
|
-
".snapshotrc.json",
|
|
537
|
-
".snapshotrc",
|
|
538
|
-
'package.json \u2192 "snapshot" key'
|
|
539
|
-
],
|
|
540
|
-
settings: [
|
|
541
|
-
{ key: "outDir", type: "string", default: "snapshot", summary: "Snapshot output directory." },
|
|
542
|
-
{ key: "tmpDir", type: "string", default: "snapshot.tmp", summary: "Scratch directory used for the atomic swap." },
|
|
543
|
-
{ key: "branch", type: "string", default: "main", summary: "Branch recorded when git cannot report one." },
|
|
544
|
-
{ key: "preset", type: "string", default: "detected", summary: "Force a preset. Unknown names are an error." },
|
|
545
|
-
{ key: "maxPartBytes", type: "number | string", default: 307200, summary: "Per-part byte budget." },
|
|
546
|
-
{ key: "largeFileBytes", type: "number | string", default: 204800, summary: "Preview threshold for large files." },
|
|
547
|
-
{ key: "previewHeadLines", type: "number", default: 80, summary: "Lines kept from the head of a large file." },
|
|
548
|
-
{ key: "previewTailLines", type: "number", default: 40, summary: "Lines kept from the tail of a large file." },
|
|
549
|
-
{ key: "includeMonolith", type: "boolean", default: false, summary: "Also write the single-file monolith." },
|
|
550
|
-
{ key: "maxMonolithBytes", type: "number | string | null", default: null, summary: "Skip the monolith above this size." },
|
|
551
|
-
{ key: "includeSensitiveFiles", type: "boolean", default: false, summary: "Emit .env / *.pem / credential files." },
|
|
552
|
-
{ key: "include", type: "string[]", default: "[]", summary: "Allow-list of globs; empty means everything." },
|
|
553
|
-
{ key: "excludePatterns", type: "string[]", default: "[]", summary: "Extra exclude globs." },
|
|
554
|
-
{ key: "discovery", type: '"auto" | "git" | "walk"', default: "auto", summary: "How files are discovered; `walk` scans the tree and reads .gitignore directly when a .git directory cannot be listed." },
|
|
555
|
-
{ key: "artifacts", type: "object", default: "all on except monolith", summary: "Per-artifact opt-out." },
|
|
556
|
-
{ key: "budgets", type: "object", default: "{ default, byLayer }", summary: "Per-layer byte budgets." },
|
|
557
|
-
{ key: "layersMode", type: '"merge" | "replace"', default: "merge", summary: "Whether custom layers extend or replace the preset's." },
|
|
558
|
-
{ key: "layers", type: "array", default: "preset layers", summary: "Custom layer rules (glob or RegExp)." },
|
|
559
|
-
{ key: "kinds", type: "array", default: "preset kinds", summary: "Custom kind rules." },
|
|
560
|
-
{ key: "secrets", type: "object", default: "{}", summary: "Custom secret patterns, or disable the defaults." },
|
|
561
|
-
{ key: "hooks", type: "object", default: "{}", summary: "afterGenerate(manifest) runs after a successful generate." }
|
|
562
|
-
]
|
|
563
|
-
},
|
|
564
|
-
output: {
|
|
565
|
-
layout: [
|
|
566
|
-
"<outDir>/README.md",
|
|
567
|
-
"<outDir>/manifest.json",
|
|
568
|
-
"<outDir>/parts/part-NNN.md",
|
|
569
|
-
"<outDir>/reports/project-tree.md",
|
|
570
|
-
"<outDir>/reports/navigation.md",
|
|
571
|
-
"<outDir>/reports/audit.md",
|
|
572
|
-
"<outDir>/.codebase-snapshot (ownership marker, always written)"
|
|
573
|
-
],
|
|
574
|
-
notes: [
|
|
575
|
-
"Parts are ordered and budget-limited; a single file larger than the budget becomes a head/tail preview.",
|
|
576
|
-
"manifest.json.files[] carries relativePath, layer, kind, language, part, inclusionMode and hash; `summary` is conditional \u2014 it is absent for languages with no structural summary, and `summaryOmitted: true` marks a summary dropped to fit the part budget; encoding appears when the file was not plain UTF-8.",
|
|
577
|
-
"manifest.skipped[] carries the path and the exact reason it was excluded.",
|
|
578
|
-
`The output directory is replaced atomically; ${MIN_MAX_PART_BYTES} bytes is the smallest legal budget.`
|
|
579
|
-
]
|
|
580
|
-
},
|
|
581
|
-
safety: [
|
|
582
|
-
"Sensitive files (.env, *.pem, .npmrc, .ssh/, .tfstate) are excluded by default and listed in manifest.skipped[].",
|
|
583
|
-
"Secret-shaped strings in ordinary source files are redacted in the emitted parts.",
|
|
584
|
-
"The tool refuses to replace an outDir that it did not create, so a mistyped outDir cannot delete source code.",
|
|
585
|
-
"Nothing is ever written above the repository root; --out-dir may not be the root or an ancestor."
|
|
586
|
-
],
|
|
587
|
-
troubleshooting: [
|
|
588
|
-
{
|
|
589
|
-
symptom: "Command prints nothing and exits 0",
|
|
590
|
-
cause: "A stale global install of this package (often a leftover `npm link`) is shadowing npx: npm runs the global binary instead of installing the published one.",
|
|
591
|
-
fix: "Check `npm ls -g --depth=0` for codebase-snapshot and remove it with `npm rm -g codebase-snapshot`. If it is not installed globally, clear the npx cache with `npm cache clean --force` and retry."
|
|
592
|
-
},
|
|
593
|
-
{
|
|
594
|
-
symptom: "Exit 1 with 'Refusing to replace outDir'",
|
|
595
|
-
cause: "The outDir exists and contains files this tool did not write.",
|
|
596
|
-
fix: "Choose a new/empty --out-dir, or delete the existing directory yourself."
|
|
597
|
-
},
|
|
598
|
-
{
|
|
599
|
-
symptom: "Exit 1 with 'The config file could not be read'",
|
|
600
|
-
cause: "snapshot.config.* exists but does not parse.",
|
|
601
|
-
fix: "Fix the syntax, or delete the config to use the defaults."
|
|
602
|
-
},
|
|
603
|
-
{
|
|
604
|
-
symptom: "Exit 1 from validate",
|
|
605
|
-
cause: "The snapshot no longer matches the files on disk.",
|
|
606
|
-
fix: "Re-run generate."
|
|
607
|
-
},
|
|
608
|
-
{
|
|
609
|
-
symptom: "A directory you expected is missing from the snapshot",
|
|
610
|
-
cause: "It is excluded by the preset, by default ignores, or by your own globs.",
|
|
611
|
-
fix: "Run `explain <path>`; manifest.skipped[] also records the reason for every file."
|
|
612
|
-
}
|
|
613
|
-
]
|
|
614
|
-
};
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
// src/agent/skill.ts
|
|
618
|
-
var PACKAGE_NAME2 = "codebase-snapshot";
|
|
619
|
-
var MAJOR_VERSION2 = GENERATOR_VERSION.split(".")[0];
|
|
620
|
-
var SKILL_NAME = "codebase-snapshot";
|
|
621
|
-
function renderSkill() {
|
|
622
|
-
return `---
|
|
623
|
-
name: ${SKILL_NAME}
|
|
624
|
-
description: Create an LLM-ready snapshot of a repository (size-budgeted markdown parts, a JSON manifest, project tree, navigation and a secret audit) when a whole codebase has to be read, summarized or handed to a model that cannot clone it. Use when asked to snapshot a codebase, prepare repository context, or analyze a project's structure.
|
|
625
|
-
version: ${GENERATOR_VERSION}
|
|
626
|
-
---
|
|
627
|
-
|
|
628
|
-
# codebase-snapshot
|
|
629
|
-
|
|
630
|
-
Turns a repository into an LLM-ready snapshot under \`./snapshot/\`: code packed into
|
|
631
|
-
size-budgeted markdown parts, plus a manifest that indexes every file, a project tree, a
|
|
632
|
-
navigation index and a secret audit.
|
|
633
|
-
|
|
634
|
-
Use it when you need to understand a whole repository, or produce repository context for
|
|
635
|
-
another model, and reading files one by one would not fit in context.
|
|
636
|
-
|
|
637
|
-
## Before you start
|
|
638
|
-
|
|
639
|
-
This tool is a Node CLI. It needs **Node.js 18 or newer**. You do not need to install it
|
|
640
|
-
globally, and you do not need to clone anything.
|
|
641
|
-
|
|
642
|
-
**Pin a version** \u2014 \`npx ${PACKAGE_NAME2}@${GENERATOR_VERSION} <command>\` \u2014 when you
|
|
643
|
-
need the same version on every machine. The bare package name works too: this package ships
|
|
644
|
-
one executable file, published under two names (\`codebase-snapshot\` and \`snap\`), which is what
|
|
645
|
-
lets npm pick it. If a command exits 0 printing nothing,
|
|
646
|
-
the executable was not resolved \u2014 clear the npm cache and retry; do not assume the repository
|
|
647
|
-
is broken.
|
|
648
|
-
|
|
649
|
-
## Step by step
|
|
650
|
-
|
|
651
|
-
1. **Verify the tool.** Run \`npx ${PACKAGE_NAME2}@latest --version\`. Expect \`v${MAJOR_VERSION2}\` or
|
|
652
|
-
newer on stdout \u2014 compare the major version, not the patch, because the
|
|
653
|
-
\`@latest\` tag can lag this release. If what it prints is older, pin the exact
|
|
654
|
-
release (\`@${GENERATOR_VERSION}\`) instead of retrying. Empty output means you hit the
|
|
655
|
-
bare-spec problem above.
|
|
656
|
-
|
|
657
|
-
2. **Read the contract, not the prose.** Run
|
|
658
|
-
\`npx ${PACKAGE_NAME2}@latest describe --format json\`. It returns the full machine
|
|
659
|
-
contract: every command, every option with its default, the exit-code meanings, the
|
|
660
|
-
25 project presets, the artifact list, the config settings and a troubleshooting
|
|
661
|
-
table. Read it before you make decisions; do not guess option names.
|
|
662
|
-
|
|
663
|
-
3. **Look before you leap (optional but cheap).** To see how a specific file or directory
|
|
664
|
-
would be treated, run \`npx ${PACKAGE_NAME2}@latest explain <path>\`. It reports the
|
|
665
|
-
layer, kind, language, target part, and the exact reason a path would be skipped.
|
|
666
|
-
\`--format json\` if you want to parse it.
|
|
667
|
-
|
|
668
|
-
3b. **One command, if the user just wants it working.** \`npx ${PACKAGE_NAME2}@latest setup\`
|
|
669
|
-
does steps 4\u20136 in one go with defaults: it writes the config (an existing one is kept),
|
|
670
|
-
adds the npm scripts and the devDependency, installs with the project's package manager,
|
|
671
|
-
and generates. Use \`--no-install\` when the machine is offline or the user does not want
|
|
672
|
-
their lockfile touched. Prefer the explicit steps below when you need to control any of
|
|
673
|
-
those, or when something fails and you want to see which part.
|
|
674
|
-
|
|
675
|
-
4. **Configure (recommended).** Run \`npx ${PACKAGE_NAME2}@latest init --yes\`. It detects
|
|
676
|
-
the project shape, then writes a commented config file listing **every** setting at its
|
|
677
|
-
current value. The filename depends on the project: \`snapshot.config.ts\` only when
|
|
678
|
-
the running Node can strip types (Node >= 22.6) **and** the project is an ES module
|
|
679
|
-
(\`"type": "module"\` in package.json); otherwise \`snapshot.config.mjs\`. Pass
|
|
680
|
-
\`--preset <name>\` only if detection is wrong; the preset names are in the contract.
|
|
681
|
-
This step is optional: without a config the tool runs on the same defaults.
|
|
682
|
-
|
|
683
|
-
5. **Generate.** Run \`npx ${PACKAGE_NAME2}@latest generate\`. Expect exit 0 and a
|
|
684
|
-
\`./snapshot/\` directory. If you need a different location use \`--out-dir <path>\`; if
|
|
685
|
-
you need smaller or larger parts use \`--max-part-bytes 120k\`.
|
|
686
|
-
|
|
687
|
-
6. **Verify.** Run \`npx ${PACKAGE_NAME2}@latest validate\`. Exit 0 means the snapshot
|
|
688
|
-
matches the files on disk. Exit 1 means drift \u2014 re-run step 5.
|
|
689
|
-
|
|
690
|
-
7. **Read the output in this order:**
|
|
691
|
-
- \`snapshot/parts/part-*.md\` \u2014 the code itself, each file tagged with its layer, kind,
|
|
692
|
-
language and, when it fits the budget, a structural summary (imports, exports,
|
|
693
|
-
classes, functions). A dropped summary appears as a \`Summary: (omitted \u2026)\` line.
|
|
694
|
-
- \`snapshot/manifest.json\` \u2014 the index. \`files[]\` has \`relativePath\`, \`layer\`,
|
|
695
|
-
\`kind\`, \`language\`, \`part\`, \`inclusionMode\` and \`hashSha256\`, plus \`encoding\`
|
|
696
|
-
when the file was not plain UTF-8. \`summary\` is **optional**: it is absent for
|
|
697
|
-
languages with no structural summary, and \`summaryOmitted: true\` marks one dropped
|
|
698
|
-
to fit the part budget \u2014 never assume it exists.
|
|
699
|
-
\`skipped[]\` records every excluded file **and why**. Prefer querying this over
|
|
700
|
-
re-reading the parts.
|
|
701
|
-
- \`snapshot/reports/audit.md\` \u2014 secrets found, large files, parse diagnostics, git
|
|
702
|
-
state. Read this before you quote code back to a user.
|
|
703
|
-
- \`snapshot/reports/project-tree.md\` and \`navigation.md\` \u2014 structure and cross-links.
|
|
704
|
-
|
|
705
|
-
## Project types
|
|
706
|
-
|
|
707
|
-
Detection is automatic and covers Next.js, Nuxt, SvelteKit, Remix, Astro, Angular, Solid,
|
|
708
|
-
Vue+React with Vite, Nest, Express/Fastify/Koa, any Node library, monorepos, Django,
|
|
709
|
-
FastAPI/Flask, plain Python, Go, Rust, Java/Spring, .NET, PHP/Laravel, Ruby/Rails,
|
|
710
|
-
Terraform/Docker infra, docs-only repositories, and a generic fallback. It sets sensible
|
|
711
|
-
layers, kinds, ecosystem ignore patterns and a recommended part budget. For non-TypeScript
|
|
712
|
-
languages it extracts imports, exports, classes and functions structurally; Vue, Svelte and
|
|
713
|
-
Astro script blocks go through the real TypeScript compiler. You do not need to configure
|
|
714
|
-
any of this to get a good first snapshot.
|
|
715
|
-
|
|
716
|
-
## Things that will surprise you
|
|
717
|
-
|
|
718
|
-
- **Sensitive files are excluded by default** (\`.env\`, \`*.pem\`, \`.npmrc\`, \`.ssh/\`,
|
|
719
|
-
\`.tfstate\`) and are listed in \`manifest.skipped[]\`. Add \`--include-sensitive-files\` only
|
|
720
|
-
if the user asks for them explicitly.
|
|
721
|
-
- **Secret-shaped strings inside normal source files are redacted** in the emitted parts.
|
|
722
|
-
If the user needs the raw value, say so rather than working around it.
|
|
723
|
-
- **The output directory is owned by the tool.** It is replaced atomically, and the tool
|
|
724
|
-
refuses to replace a directory it did not create. Never point \`--out-dir\` at source
|
|
725
|
-
code; if you see \`Refusing to replace outDir\`, choose a new directory rather than
|
|
726
|
-
deleting anything.
|
|
727
|
-
- **Exit 2 is a usage error**, not a failure of the repository. Re-read the contract
|
|
728
|
-
before retrying.
|
|
729
|
-
- **\`--format json\` keeps stdout pure.** Progress goes to stderr, so \`stdout\` is safe to
|
|
730
|
-
pipe into a JSON parser.
|
|
731
|
-
|
|
732
|
-
## Making this available to other agents
|
|
733
|
-
|
|
734
|
-
Run \`npx ${PACKAGE_NAME2}@latest skill --install\`. It writes this skill into the two
|
|
735
|
-
registries agent tools read by name \u2014 \`.claude/skills/${SKILL_NAME}/SKILL.md\` and
|
|
736
|
-
\`.cursor/rules/${SKILL_NAME}.mdc\` \u2014 and appends exactly those files to \`.gitignore\`, so the
|
|
737
|
-
install leaves nothing untracked. It is idempotent, and it refuses a target that resolves
|
|
738
|
-
outside the repository, so a linked \`.claude/\` cannot be used to write elsewhere.
|
|
739
|
-
|
|
740
|
-
It never writes \`AGENTS.md\`. That file is the repository's own agent contract \u2014 build
|
|
741
|
-
commands, conventions, house rules \u2014 and this tool has no business editing it. If you want
|
|
742
|
-
this skill in there, run \`npx ${PACKAGE_NAME2}@latest skill\` and paste the text yourself: the
|
|
743
|
-
diff, and the commit, are then yours.
|
|
744
|
-
`;
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
// src/agent/install-skill.ts
|
|
748
8
|
var SKILL_TARGETS = ["claude", "cursor", "all"];
|
|
749
9
|
var REFUSED = "Refusing to install the agent skill";
|
|
750
10
|
var LEGACY_AGENTS_MARKER = "<!-- codebase-snapshot:skill:start -->";
|
|
@@ -845,7 +105,8 @@ var COMMAND_ALIASES = /* @__PURE__ */ new Map([
|
|
|
845
105
|
["explain", "explain"],
|
|
846
106
|
["diff", "diff"],
|
|
847
107
|
["describe", "describe"],
|
|
848
|
-
["skill", "skill"]
|
|
108
|
+
["skill", "skill"],
|
|
109
|
+
["mcp", "mcp"]
|
|
849
110
|
]);
|
|
850
111
|
var COMMAND_ARITY = {
|
|
851
112
|
generate: 0,
|
|
@@ -856,7 +117,8 @@ var COMMAND_ARITY = {
|
|
|
856
117
|
explain: 1,
|
|
857
118
|
diff: 2,
|
|
858
119
|
describe: 0,
|
|
859
|
-
skill: 0
|
|
120
|
+
skill: 0,
|
|
121
|
+
mcp: 0
|
|
860
122
|
};
|
|
861
123
|
var CLI_OPTIONS = {
|
|
862
124
|
cwd: { type: "string" },
|
|
@@ -879,6 +141,7 @@ var CLI_OPTIONS = {
|
|
|
879
141
|
exclude: { type: "string" },
|
|
880
142
|
yes: { type: "boolean" },
|
|
881
143
|
force: { type: "boolean" },
|
|
144
|
+
autopilot: { type: "boolean" },
|
|
882
145
|
install: { type: "boolean" },
|
|
883
146
|
"no-install": { type: "boolean" },
|
|
884
147
|
target: { type: "string" },
|
|
@@ -894,6 +157,14 @@ Production-grade LLM codebase snapshot generator, auditor, and AST partitioner
|
|
|
894
157
|
Usage:
|
|
895
158
|
codebase-snapshot [command] [options]
|
|
896
159
|
|
|
160
|
+
First run:
|
|
161
|
+
codebase-snapshot No config yet: asks once, sets the project up, generates.
|
|
162
|
+
A configured project just generates. It never falls back
|
|
163
|
+
to defaults it was not given.
|
|
164
|
+
codebase-snapshot --autopilot The same without questions: detect the preset, write the
|
|
165
|
+
config, add the npm scripts and devDependency, install,
|
|
166
|
+
then generate.
|
|
167
|
+
|
|
897
168
|
Commands:
|
|
898
169
|
setup Everything at once: config, scripts, dependency, install, generate
|
|
899
170
|
generate (default) Generate a complete partitioned codebase snapshot
|
|
@@ -904,6 +175,7 @@ Commands:
|
|
|
904
175
|
diff <a> <b> Compare two snapshots (directories or manifest.json files)
|
|
905
176
|
describe Print the machine-readable contract (--format json for agents)
|
|
906
177
|
skill Print the bundled agent skill; --install writes it into the repo
|
|
178
|
+
mcp Serve this repository to an MCP client over stdio
|
|
907
179
|
|
|
908
180
|
Options:
|
|
909
181
|
--cwd <path> Target repository directory (default: current directory)
|
|
@@ -926,6 +198,12 @@ Options:
|
|
|
926
198
|
auto: git when usable. git: require git. walk: read
|
|
927
199
|
.gitignore directly (escape hatch for a broken .git)
|
|
928
200
|
|
|
201
|
+
generate only:
|
|
202
|
+
--autopilot Set the project up without asking, then generate: write the
|
|
203
|
+
config and .gitignore, add the npm scripts and devDependency,
|
|
204
|
+
and install with the project's own package manager.
|
|
205
|
+
--no-install With --autopilot: skip the dependency install.
|
|
206
|
+
|
|
929
207
|
init only:
|
|
930
208
|
--preset <name> Force a project preset instead of detecting one
|
|
931
209
|
--include <globs> Comma-separated allow-list
|
|
@@ -1140,8 +418,17 @@ function parseCliArgs(argv) {
|
|
|
1140
418
|
`--target must be one of: ${SKILL_TARGETS.join(", ")} (received "${String(values.target)}").`
|
|
1141
419
|
);
|
|
1142
420
|
}
|
|
421
|
+
if (values["no-install"] === true && command === "generate" && values.autopilot !== true) {
|
|
422
|
+
throw new UsageError("--no-install only applies to `setup`, or to `generate --autopilot`.");
|
|
423
|
+
}
|
|
424
|
+
if (values.autopilot === true && values["no-config"] === true) {
|
|
425
|
+
throw new UsageError(
|
|
426
|
+
"--autopilot sets a project up and writes a config; --no-config ignores config files. Pass one or the other."
|
|
427
|
+
);
|
|
428
|
+
}
|
|
1143
429
|
return {
|
|
1144
430
|
command,
|
|
431
|
+
explicitCommand: firstPositional !== void 0 || explicitMode !== void 0,
|
|
1145
432
|
args,
|
|
1146
433
|
format: rawFormat,
|
|
1147
434
|
dryRun: values["dry-run"] === true,
|
|
@@ -1153,6 +440,7 @@ function parseCliArgs(argv) {
|
|
|
1153
440
|
exclude: values.exclude === void 0 ? void 0 : String(values.exclude),
|
|
1154
441
|
yes: values.yes === true,
|
|
1155
442
|
force: values.force === true,
|
|
443
|
+
autopilot: values.autopilot === true,
|
|
1156
444
|
install: values.install === true,
|
|
1157
445
|
noInstall: values["no-install"] === true,
|
|
1158
446
|
target: values.target === void 0 ? void 0 : String(values.target),
|
|
@@ -1233,6 +521,9 @@ function nextStepHint(cwd, scriptsAddedNow) {
|
|
|
1233
521
|
const installed = fs.existsSync(bin) || fs.existsSync(`${bin}.cmd`);
|
|
1234
522
|
return installed ? "npm run snapshot" : "npm install && npm run snapshot";
|
|
1235
523
|
}
|
|
524
|
+
function presetSummary(init) {
|
|
525
|
+
return init.reason === null ? `preset: ${init.preset}` : `preset: ${init.preset}, because ${init.reason}`;
|
|
526
|
+
}
|
|
1236
527
|
async function runInitCommand(invocation) {
|
|
1237
528
|
try {
|
|
1238
529
|
const result = await runInit({
|
|
@@ -1257,7 +548,7 @@ async function runInitCommand(invocation) {
|
|
|
1257
548
|
info(result.configText);
|
|
1258
549
|
return 0;
|
|
1259
550
|
}
|
|
1260
|
-
info(`\u2705 Created ${path2.basename(result.configPath)} (
|
|
551
|
+
info(`\u2705 Created ${path2.basename(result.configPath)} (${presetSummary(result)})`);
|
|
1261
552
|
if (result.scripts.length > 0) info(` Added npm scripts: ${result.scripts.join(", ")}`);
|
|
1262
553
|
if (result.dependency.added && result.dependency.range !== null) {
|
|
1263
554
|
info(` Added devDependency: ${result.dependency.name} ${result.dependency.range}`);
|
|
@@ -1328,8 +619,9 @@ async function captureStdout(fn) {
|
|
|
1328
619
|
process.stdout.write = original;
|
|
1329
620
|
}
|
|
1330
621
|
}
|
|
1331
|
-
async function runSetupCommand(invocation) {
|
|
622
|
+
async function runSetupCommand(invocation, options = {}) {
|
|
1332
623
|
try {
|
|
624
|
+
const interactive = options.interactive === true;
|
|
1333
625
|
const init = await runInit({
|
|
1334
626
|
cwd: invocation.cwd,
|
|
1335
627
|
preset: invocation.preset,
|
|
@@ -1339,7 +631,7 @@ async function runSetupCommand(invocation) {
|
|
|
1339
631
|
exclude: splitList(invocation.exclude),
|
|
1340
632
|
skipArtifacts: invocation.skipArtifacts,
|
|
1341
633
|
includeSensitiveFiles: invocation.includeSensitiveFiles,
|
|
1342
|
-
yes:
|
|
634
|
+
yes: !interactive,
|
|
1343
635
|
force: invocation.force,
|
|
1344
636
|
reuseConfig: true,
|
|
1345
637
|
dryRun: invocation.dryRun
|
|
@@ -1376,11 +668,11 @@ async function runSetupCommand(invocation) {
|
|
|
1376
668
|
return 0;
|
|
1377
669
|
}
|
|
1378
670
|
if (invocation.format !== "json") {
|
|
1379
|
-
info(`\u{1F680} Setup for: ${invocation.cwd}`);
|
|
671
|
+
if (!interactive) info(`\u{1F680} Setup for: ${invocation.cwd}`);
|
|
1380
672
|
if (init.reused) {
|
|
1381
|
-
info(`\u267B Keeping ${path2.basename(init.configPath)} (
|
|
673
|
+
info(`\u267B Keeping ${path2.basename(init.configPath)} (${presetSummary(init)})`);
|
|
1382
674
|
} else {
|
|
1383
|
-
info(`\u2705 Created ${path2.basename(init.configPath)} (
|
|
675
|
+
info(`\u2705 Created ${path2.basename(init.configPath)} (${presetSummary(init)})`);
|
|
1384
676
|
}
|
|
1385
677
|
if (init.scripts.length > 0) info(` Added npm scripts: ${init.scripts.join(", ")}`);
|
|
1386
678
|
if (init.dependency.added && init.dependency.range !== null) {
|
|
@@ -1409,6 +701,7 @@ async function runSetupCommand(invocation) {
|
|
|
1409
701
|
if ((invocation.skipArtifacts ?? []).length > 0) {
|
|
1410
702
|
generateArgs.push("--skip-artifacts", (invocation.skipArtifacts ?? []).join(","));
|
|
1411
703
|
}
|
|
704
|
+
if (invocation.watch) generateArgs.push("--watch");
|
|
1412
705
|
if (invocation.format === "json") {
|
|
1413
706
|
const generated = await captureStdout(() => main(generateArgs));
|
|
1414
707
|
let manifest = null;
|
|
@@ -1448,6 +741,24 @@ async function runSetupCommand(invocation) {
|
|
|
1448
741
|
return fail(invocation.format, err.message);
|
|
1449
742
|
}
|
|
1450
743
|
}
|
|
744
|
+
async function firstRun(invocation) {
|
|
745
|
+
const plan = planFirstRun({
|
|
746
|
+
hasConfig: findConfigFile(invocation.cwd) !== null,
|
|
747
|
+
hasPriorSnapshot: hasPriorSnapshot(invocation.cwd, invocation.outDir ?? DEFAULT_ANSWERS.outDir),
|
|
748
|
+
// A machine must never be asked a question, and `--format json` is how a machine says so.
|
|
749
|
+
interactive: invocation.format === "text" && isInteractive(defaultIO()),
|
|
750
|
+
autopilot: invocation.autopilot,
|
|
751
|
+
emptyTarget: isEffectivelyEmpty(invocation.cwd)
|
|
752
|
+
});
|
|
753
|
+
if (plan.action === "generate") return null;
|
|
754
|
+
if (plan.action === "refuse") {
|
|
755
|
+
return fail(invocation.format, firstRunMessage(plan.reason, invocation.cwd), plan.exitCode);
|
|
756
|
+
}
|
|
757
|
+
info(
|
|
758
|
+
plan.interactive ? "\u2699 This project has no snapshot configuration yet." : "\u2699 --autopilot: setting this project up before generating."
|
|
759
|
+
);
|
|
760
|
+
return await runSetupCommand(invocation, { interactive: plan.interactive });
|
|
761
|
+
}
|
|
1451
762
|
function runSkillCommand(invocation) {
|
|
1452
763
|
if (!invocation.install) {
|
|
1453
764
|
if (invocation.format === "json") {
|
|
@@ -1564,9 +875,16 @@ ${USAGE}
|
|
|
1564
875
|
if (invocation.command === "skill") {
|
|
1565
876
|
return runSkillCommand(invocation);
|
|
1566
877
|
}
|
|
878
|
+
if (invocation.command === "mcp") {
|
|
879
|
+
return await runStdioServer({ cwd: path2.resolve(invocation.cwd) });
|
|
880
|
+
}
|
|
1567
881
|
if (invocation.command === "setup") {
|
|
1568
882
|
return runSetupCommand(invocation);
|
|
1569
883
|
}
|
|
884
|
+
if (invocation.command === "generate" && !invocation.noConfig && (!invocation.explicitCommand || invocation.autopilot)) {
|
|
885
|
+
const decided = await firstRun(invocation);
|
|
886
|
+
if (decided !== null) return decided;
|
|
887
|
+
}
|
|
1570
888
|
const skipConfig = invocation.noConfig || invocation.command === "diff";
|
|
1571
889
|
const loaded = skipConfig ? { config: null, warnings: [], errors: [], file: null } : await loadConfigFileDetailed(invocation.cwd);
|
|
1572
890
|
if (invocation.noConfig) info("\u2699 --no-config: using built-in defaults, config file ignored");
|