codebase-snapshot 0.9.1 → 0.9.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/mcp.d.ts ADDED
@@ -0,0 +1,399 @@
1
+ import { Readable, Writable } from 'node:stream';
2
+
3
+ /**
4
+ * JSON-RPC 2.0 envelopes and the Model Context Protocol revisions this server speaks.
5
+ *
6
+ * Two protocol eras coexist and this file is the only place that knows which revision belongs
7
+ * to which era. The legacy era (`2025-11-25` and earlier) opens every connection with an
8
+ * `initialize` handshake. The modern era (`2026-07-28`) removed the handshake: every request
9
+ * carries the protocol version and client capabilities in `_meta`, `server/discover` is
10
+ * mandatory, every result carries `resultType`, and `ping` no longer exists.
11
+ *
12
+ * The constants are read by the negotiation in `era.ts`, by the dispatch in `server.ts` and by
13
+ * the `describe` contract, so a revision cannot be added to one of them and forgotten in the
14
+ * others.
15
+ */
16
+ /** A JSON object as it appears on the wire: the shape every `params`/`result` takes. */
17
+ type JsonObject = Record<string, unknown>;
18
+ type JsonRpcId = string | number;
19
+ interface JsonRpcError {
20
+ code: number;
21
+ message: string;
22
+ data?: unknown;
23
+ }
24
+ interface JsonRpcSuccessResponse {
25
+ jsonrpc: "2.0";
26
+ id: JsonRpcId | null;
27
+ result: JsonObject;
28
+ }
29
+ interface JsonRpcErrorResponse {
30
+ jsonrpc: "2.0";
31
+ id: JsonRpcId | null;
32
+ error: JsonRpcError;
33
+ }
34
+ type JsonRpcResponse = JsonRpcSuccessResponse | JsonRpcErrorResponse;
35
+ /**
36
+ * Revisions that open with the `initialize` handshake, newest first.
37
+ *
38
+ * The server echoes the requested revision when it is in this list and otherwise answers with
39
+ * the newest entry: the legacy lifecycle says a server that cannot speak the requested version
40
+ * responds with one it does speak, and the client decides whether to continue.
41
+ */
42
+ declare const LEGACY_PROTOCOL_VERSIONS: readonly string[];
43
+ /** Revisions that carry version and capabilities on every request, newest first. */
44
+ declare const MODERN_PROTOCOL_VERSIONS: readonly string[];
45
+ /** Everything advertised in `server/discover` and in an unsupported-version error. */
46
+ declare const SUPPORTED_PROTOCOL_VERSIONS: readonly string[];
47
+ /**
48
+ * JSON-RPC error codes, plus the one the modern era added.
49
+ *
50
+ * `-32022` is not a generic bad-params error: a client that receives it knows to read
51
+ * `data.supported` and retry with a mutually supported revision. Reporting it as `-32602`
52
+ * would leave a modern client with no way to recover.
53
+ */
54
+ declare const ERROR_CODES: {
55
+ readonly parseError: -32700;
56
+ readonly invalidRequest: -32600;
57
+ readonly methodNotFound: -32601;
58
+ readonly invalidParams: -32602;
59
+ readonly internalError: -32603;
60
+ readonly unsupportedProtocolVersion: -32022;
61
+ };
62
+ /** Method names, kept as constants so a typo is a compile error rather than a `-32601`. */
63
+ declare const METHOD: {
64
+ readonly initialize: "initialize";
65
+ readonly initialized: "notifications/initialized";
66
+ readonly discover: "server/discover";
67
+ readonly ping: "ping";
68
+ readonly toolsList: "tools/list";
69
+ readonly toolsCall: "tools/call";
70
+ readonly resourcesList: "resources/list";
71
+ readonly resourcesRead: "resources/read";
72
+ readonly resourceTemplatesList: "resources/templates/list";
73
+ readonly promptsList: "prompts/list";
74
+ readonly promptsGet: "prompts/get";
75
+ readonly cancelled: "notifications/cancelled";
76
+ };
77
+ /**
78
+ * A JSON Schema subset, as far as this server needs it.
79
+ *
80
+ * Deliberately not `unknown`: the tool table is authored by hand, and the compiler catching a
81
+ * misspelled keyword is worth more than accepting every dialect feature we never emit.
82
+ */
83
+ interface JsonSchema {
84
+ type?: "object" | "string" | "number" | "integer" | "boolean" | "array";
85
+ description?: string;
86
+ properties?: Record<string, JsonSchema>;
87
+ required?: string[];
88
+ items?: JsonSchema;
89
+ enum?: readonly string[];
90
+ default?: string | number | boolean;
91
+ additionalProperties?: boolean;
92
+ minimum?: number;
93
+ maximum?: number;
94
+ minLength?: number;
95
+ maxLength?: number;
96
+ }
97
+ /** An `inputSchema` root: MCP requires a top-level object schema. */
98
+ interface InputSchema extends JsonSchema {
99
+ type: "object";
100
+ properties: Record<string, JsonSchema>;
101
+ }
102
+
103
+ type ProtocolEra = "legacy" | "modern";
104
+ /** Per-connection state. The modern era has no session, so this stays empty until legacy init. */
105
+ interface SessionState {
106
+ /** Which era the connection committed to, or null before the first decisive request. */
107
+ era: ProtocolEra | null;
108
+ /** The revision in force. Null in legacy until `initialize` has been negotiated. */
109
+ protocolVersion: string | null;
110
+ /** True once `initialize` has been answered, which is the legacy lifecycle's gate. */
111
+ ready: boolean;
112
+ }
113
+ declare function createSession(): SessionState;
114
+ /** The modern request metadata, when a request carries it at all. */
115
+ interface ModernMeta {
116
+ version: string;
117
+ clientCapabilities: JsonObject;
118
+ clientInfo: JsonObject | null;
119
+ }
120
+ type MetaInspection = {
121
+ kind: "absent";
122
+ } | {
123
+ kind: "incomplete";
124
+ missing: string;
125
+ } | {
126
+ kind: "present";
127
+ meta: ModernMeta;
128
+ };
129
+ /**
130
+ * Reads `_meta` from a request.
131
+ *
132
+ * `absent` and `incomplete` are different answers: a request with no `_meta` at all is a legacy
133
+ * client, while a request with `_meta` that lacks a required key is a modern client making a
134
+ * mistake — and telling those two apart is what keeps the legacy era from being rejected for a
135
+ * field it never had.
136
+ */
137
+ declare function inspectModernMeta(params: JsonObject | undefined): MetaInspection;
138
+ type EraDecision = {
139
+ kind: "era";
140
+ era: ProtocolEra;
141
+ version: string | null;
142
+ } | {
143
+ kind: "error";
144
+ code: number;
145
+ message: string;
146
+ data?: unknown;
147
+ };
148
+ /**
149
+ * Decides which era a request belongs to, and whether its version is acceptable.
150
+ *
151
+ * Once a connection commits to an era it stays committed. A legacy client that suddenly sends
152
+ * modern `_meta` is confused rather than clever, and silently switching would answer half a
153
+ * session in each shape.
154
+ */
155
+ declare function classifyRequest(method: string, params: JsonObject | undefined, session: SessionState): EraDecision;
156
+ interface LegacyNegotiation {
157
+ version: string;
158
+ /** False when the client asked for something we cannot speak and was negotiated down. */
159
+ matched: boolean;
160
+ }
161
+ /**
162
+ * Legacy version negotiation: echo a supported request, otherwise answer with our best.
163
+ *
164
+ * A client that asked for a modern revision here gets the newest *legacy* revision, not an error:
165
+ * under this lifecycle the server's answer is the offer, and the client decides.
166
+ */
167
+ declare function negotiateLegacyVersion(requested: unknown): LegacyNegotiation;
168
+
169
+ /**
170
+ * The capability records the MCP server serves.
171
+ *
172
+ * These are the shapes the protocol layer deals in, kept apart from the handlers that fill them so
173
+ * the server can be tested with a fixed set of stubs and the tool table can be built without a
174
+ * running transport.
175
+ */
176
+
177
+ /** Everything a handler may touch. Deliberately small: no transport, no session, no protocol. */
178
+ interface ToolContext {
179
+ /** Repository the server was launched for; every relative path resolves against it. */
180
+ cwd: string;
181
+ /** Absolute directories a `root` argument is allowed to resolve into. */
182
+ roots: string[];
183
+ /** Writes one line to stderr. Never stdout, which carries protocol messages only. */
184
+ log: (line: string) => void;
185
+ }
186
+ /**
187
+ * Client-facing hints.
188
+ *
189
+ * `readOnlyHint` is the one that changes behaviour rather than appearance: clients use it to
190
+ * auto-approve a call. Six of the seven tools never touch the filesystem, so they are marked
191
+ * read-only and only `snapshot_generate` prompts.
192
+ */
193
+ interface ToolAnnotations {
194
+ readOnlyHint: boolean;
195
+ destructiveHint: boolean;
196
+ idempotentHint: boolean;
197
+ openWorldHint: boolean;
198
+ }
199
+ /** What a handler returns. The protocol layer turns this into a `CallToolResult`. */
200
+ interface ToolOutcome {
201
+ /** The text block a model reads. Keep it short: bulk content belongs in a windowed read. */
202
+ text: string;
203
+ /** Typed mirror of `text`, declared by the tool's `outputSchema`. */
204
+ structured?: JsonObject;
205
+ /** True for a tool-level failure. Reported inside the result, not as a JSON-RPC error. */
206
+ isError?: boolean;
207
+ }
208
+ interface McpTool {
209
+ name: string;
210
+ title: string;
211
+ description: string;
212
+ inputSchema: InputSchema;
213
+ outputSchema?: InputSchema;
214
+ annotations: ToolAnnotations;
215
+ handler(args: JsonObject, context: ToolContext): Promise<ToolOutcome>;
216
+ }
217
+ /** One addressable resource, as `resources/list` reports it. */
218
+ interface McpResourceDescriptor {
219
+ uri: string;
220
+ name: string;
221
+ title?: string;
222
+ description: string;
223
+ mimeType: string;
224
+ }
225
+ interface McpResourceContents {
226
+ uri: string;
227
+ mimeType: string;
228
+ text: string;
229
+ }
230
+ /** A parameterised resource family, as `resources/templates/list` reports it. */
231
+ interface McpResourceTemplate {
232
+ uriTemplate: string;
233
+ name: string;
234
+ title?: string;
235
+ description: string;
236
+ mimeType: string;
237
+ }
238
+ /**
239
+ * Resources are computed per call rather than declared once.
240
+ *
241
+ * The list depends on what is on disk — a snapshot that has not been generated has manifest
242
+ * resources that do not exist yet — so a static table would advertise resources whose read then
243
+ * fails, which is worse than an honest short list.
244
+ */
245
+ interface ResourceProvider {
246
+ list(context: ToolContext): Promise<McpResourceDescriptor[]>;
247
+ templates(): McpResourceTemplate[];
248
+ /** Resolves one URI. Returns null when this server does not own it. */
249
+ read(uri: string, context: ToolContext): Promise<McpResourceContents | null>;
250
+ }
251
+ interface PromptArgument {
252
+ name: string;
253
+ description: string;
254
+ required: boolean;
255
+ }
256
+ interface PromptMessage {
257
+ role: "user" | "assistant";
258
+ content: {
259
+ type: "text";
260
+ text: string;
261
+ };
262
+ }
263
+ interface McpPrompt {
264
+ name: string;
265
+ title?: string;
266
+ description: string;
267
+ arguments: PromptArgument[];
268
+ build(args: JsonObject): {
269
+ description: string;
270
+ messages: PromptMessage[];
271
+ };
272
+ }
273
+ /** Identity reported at handshake and in every modern result's `_meta`. */
274
+ interface McpServerInfo {
275
+ name: string;
276
+ title: string;
277
+ version: string;
278
+ websiteUrl: string;
279
+ }
280
+
281
+ interface McpServerOptions {
282
+ info: McpServerInfo;
283
+ instructions: string;
284
+ tools: McpTool[];
285
+ resources: ResourceProvider;
286
+ prompts: McpPrompt[];
287
+ context: ToolContext;
288
+ }
289
+ interface McpServer {
290
+ /** Handles one already-parsed message. Returns null when no response is due. */
291
+ handle(message: unknown): Promise<JsonRpcResponse | null>;
292
+ /** Exposed for tests and for `mcp --list`, which prints the negotiation table. */
293
+ readonly session: SessionState;
294
+ }
295
+ declare function createMcpServer(options: McpServerOptions): McpServer;
296
+
297
+ /** Every tool this server exposes, in the order a first-time caller should read them. */
298
+ declare function buildTools(): McpTool[];
299
+
300
+ declare function buildPrompts(): McpPrompt[];
301
+
302
+ declare function buildResourceProvider(): ResourceProvider;
303
+
304
+ interface ServerOptions {
305
+ /** Repository the tools operate on. Also the only directory they may read. */
306
+ cwd: string;
307
+ /** Additional directories callers may name as `root`. Defaults to `cwd` alone. */
308
+ roots?: string[];
309
+ /** Where operational notes go. Never stdout, which carries protocol messages only. */
310
+ log?: (line: string) => void;
311
+ }
312
+ /** Builds a server with every capability this package implements. */
313
+ declare function createServer(options: ServerOptions): McpServer;
314
+ /**
315
+ * Serves MCP over stdio until the client closes the stream.
316
+ *
317
+ * Writes are serialised through one promise chain: two tool calls finishing together must not
318
+ * interleave half-written lines, and a single response can be large. Reading is not serialised —
319
+ * a slow tool must not stop a cancellation notification from being read — and the process waits
320
+ * for every accepted request to answer before it resolves.
321
+ */
322
+ declare function runStdioServer(options: ServerOptions): Promise<number>;
323
+
324
+ /**
325
+ * The stdio transport: one JSON-RPC message per line, UTF-8, nothing else on the stream.
326
+ *
327
+ * MCP's stdio binding is newline-delimited JSON, not LSP's `Content-Length` framing, and the
328
+ * server MUST NOT write anything to stdout that is not a valid message. `JSON.stringify` escapes
329
+ * newlines inside strings, so serialising a message can never produce a second line — the writer
330
+ * deliberately does not pretty-print, because `JSON.stringify(value, null, 2)` would.
331
+ *
332
+ * Framing is separated from parsing on purpose: this module hands raw lines to its caller and the
333
+ * server decides what a bad line means (a `-32700` response), which keeps the line splitting
334
+ * testable without a running server.
335
+ */
336
+
337
+ /**
338
+ * Largest single message this transport will buffer.
339
+ *
340
+ * A line with no newline grows the buffer until the process runs out of memory, so an oversized
341
+ * message is discarded rather than accepted: the snapshot itself is never this large, and a
342
+ * client that streams more than this is not sending a message.
343
+ */
344
+ declare const MAX_LINE_BYTES: number;
345
+ interface MessageWriter {
346
+ /** Serialises one message onto the stream. Rejects if the stream reports a write error. */
347
+ write(value: unknown): Promise<void>;
348
+ }
349
+ /**
350
+ * Creates a writer that emits exactly one line per message.
351
+ *
352
+ * Writes are awaited through the stream's callback rather than fire-and-forget: a tool result can
353
+ * be tens of kilobytes, and queueing several without backpressure interleaves them with the next
354
+ * response on a slow pipe.
355
+ */
356
+ declare function createWriter(output: Writable): MessageWriter;
357
+ interface LineReaderOptions {
358
+ /** Called for every complete, non-empty line, with the trailing CR already removed. */
359
+ onLine: (line: string) => void;
360
+ /** Called when one line exceeds {@link MAX_LINE_BYTES}; the line is discarded. */
361
+ onOverflow?: (bytes: number) => void;
362
+ }
363
+ /**
364
+ * Reads newline-delimited lines from `input` until it ends.
365
+ *
366
+ * Three details are what make this work against real clients rather than against our own tests:
367
+ * a chunk boundary can fall anywhere inside a line (including inside a multi-byte character,
368
+ * which `setEncoding` handles); a Windows client may terminate lines with CRLF; and the final
369
+ * line may arrive without a newline before the stream ends.
370
+ *
371
+ * Returns a function that removes the listeners, so a caller can shut a transport down without
372
+ * racing a late `end` event.
373
+ */
374
+ declare function createLineReader(input: Readable, options: LineReaderOptions): () => void;
375
+
376
+ /**
377
+ * The instructions an MCP client shows its model at handshake.
378
+ *
379
+ * This text is the cheapest documentation the project has: it arrives before the first tool call,
380
+ * in the model's own context, on every client that supports the field. It is therefore about
381
+ * *order* rather than features — an agent that reads a part end to end has already lost the
382
+ * budget this tool exists to protect.
383
+ */
384
+ declare const SERVER_INSTRUCTIONS: string;
385
+
386
+ /**
387
+ * A JSON Schema checker for tool arguments, sized to the schemas this server publishes.
388
+ *
389
+ * The alternative was Ajv, which is the right answer for arbitrary schemas and the wrong one for
390
+ * a package whose single dependency is a compiler. Every schema here is a flat object of scalar
391
+ * properties, so the checker is small, readable and — unlike a general-purpose validator — it can
392
+ * reject unknown properties by default, which is what turns an agent's typo (`maxPartByte`) into a
393
+ * named error instead of a silently ignored option.
394
+ */
395
+
396
+ /** Human-readable problems, each prefixed with the argument path that caused it. */
397
+ declare function validateArguments(schema: InputSchema, value: unknown): string[];
398
+
399
+ export { ERROR_CODES, type JsonRpcResponse, type JsonSchema, LEGACY_PROTOCOL_VERSIONS, type LineReaderOptions, MAX_LINE_BYTES, METHOD, MODERN_PROTOCOL_VERSIONS, type McpPrompt, type McpResourceContents, type McpResourceDescriptor, type McpResourceTemplate, type McpServer, type McpServerInfo, type McpServerOptions, type McpTool, type MessageWriter, type ProtocolEra, type ResourceProvider, SERVER_INSTRUCTIONS, SUPPORTED_PROTOCOL_VERSIONS, type ServerOptions, type SessionState, type ToolAnnotations, type ToolContext, type ToolOutcome, buildPrompts, buildResourceProvider, buildTools, classifyRequest, createLineReader, createMcpServer, createServer, createSession, createWriter, inspectModernMeta, negotiateLegacyVersion, runStdioServer, validateArguments };
package/dist/mcp.js ADDED
@@ -0,0 +1,4 @@
1
+ export { ERROR_CODES, LEGACY_PROTOCOL_VERSIONS, MAX_LINE_BYTES, METHOD, MODERN_PROTOCOL_VERSIONS, SERVER_INSTRUCTIONS, SUPPORTED_PROTOCOL_VERSIONS, buildPrompts, buildResourceProvider, buildTools, classifyRequest, createLineReader, createMcpServer, createServer, createSession, createWriter, inspectModernMeta, negotiateLegacyVersion, runStdioServer, validateArguments } from './chunk-EWZYK3VX.js';
2
+ import './chunk-FCWD4LY7.js';
3
+ //# sourceMappingURL=mcp.js.map
4
+ //# sourceMappingURL=mcp.js.map
package/docs/agent.md CHANGED
@@ -22,6 +22,26 @@ npx codebase-snapshot validate
22
22
  The bare package name works; pin an exact version (`npx codebase-snapshot --version` prints the current one) when the same version must run on every
23
23
  machine.
24
24
 
25
+ ### One command instead of three
26
+
27
+ ```bash
28
+ npx codebase-snapshot --autopilot
29
+ ```
30
+
31
+ Sets the project up — config, npm scripts, devDependency, `.gitignore`, install — and generates,
32
+ with no prompts. It keeps an existing config, and `--no-install` avoids touching the lockfile.
33
+ `setup` is the same operation as a command word.
34
+
35
+ ### Do not rely on the bare command
36
+
37
+ ```bash
38
+ npx codebase-snapshot # ❌ in an unconfigured project this exits 2
39
+ ```
40
+
41
+ An agent has no terminal, so the guided first run cannot ask its question and refuses instead of
42
+ generating with settings nobody chose. That exit `2` is the tool being honest, not broken: say
43
+ which setup you want — `--autopilot`, `init --yes`, or `generate` on the documented defaults.
44
+
25
45
  ## The contract
26
46
 
27
47
  `describe --format json` returns the whole tool as data on stdout:
@@ -93,6 +113,22 @@ added.
93
113
  `codebase-snapshot init` does none of this: it writes the config, the npm scripts, the
94
114
  devDependency and the `.gitignore` entries for the snapshot directory, and nothing else.
95
115
 
116
+ ## When the client speaks MCP
117
+
118
+ `codebase-snapshot mcp` serves the repository to a Model Context Protocol client over stdio: seven
119
+ tools, `snapshot://` resources and two prompts. Prefer it over shelling out when the agent already
120
+ has an MCP connection, for two reasons that are properties of the design rather than of the wiring:
121
+
122
+ - **Reads come from the snapshot, not the working tree.** `snapshot_read` extracts a file's section
123
+ from the emitted part, so a file the snapshot excluded cannot be read back and a redacted value
124
+ stays redacted.
125
+ - **Staleness is a first-class question.** `snapshot_validate` answers "has the code moved since
126
+ this was built", which an agent with only a filesystem tool has no way to ask.
127
+
128
+ The tools are listed in [the MCP guide](mcp.md) and in the `mcp` block of
129
+ `describe --format json`. A `root` argument may name a sub-directory of the directory the server was
130
+ started for, and nothing outside it.
131
+
96
132
  ## Why both a contract and a skill
97
133
 
98
134
  The contract is for **doing** — the data an agent needs to pick correct commands and options.
package/docs/cli.md CHANGED
@@ -4,12 +4,53 @@
4
4
  npx codebase-snapshot <command> [options]
5
5
  ```
6
6
 
7
- The package installs one executable, **`codebase-snapshot`**, with a short alias **`snap`**.
8
- Both are the same file. After `npx codebase-snapshot generate` the command
9
- becomes available as `npx codebase-snapshot` for the rest of that session; after
10
- `npm install -D` use `npx codebase-snapshot` or `npx snap` from the project.
7
+ The package installs one executable under two names: **`codebase-snapshot`** (canonical, and the
8
+ one to script against) and **`snapshot`** (the short command a global install puts on your
9
+ `PATH`). Both are the same file. From a project dependency use `npx codebase-snapshot` or
10
+ `npx snapshot` — both resolve the local copy first.
11
11
 
12
- Running with no command is the same as `generate`.
12
+ Running with no command is the guided first run: in a project with no configuration it asks
13
+ once, sets the project up and generates; in a configured project it is the same as `generate`.
14
+ Naming the command (`codebase-snapshot generate`) always means "do exactly this", on the
15
+ documented defaults. `--autopilot` sets the project up first without asking.
16
+
17
+ ```bash
18
+ snapshot # global install: guided first run, then generate
19
+ npx codebase-snapshot # the same, from a project dependency
20
+ npx codebase-snapshot --autopilot # the same with no questions
21
+ ```
22
+
23
+ ## The first run
24
+
25
+ ```bash
26
+ snapshot # global install
27
+ npx codebase-snapshot # from a project dependency
28
+ ```
29
+
30
+ In a project with no configuration the tool reports what it recognised **and what it keyed on**,
31
+ then asks one question:
32
+
33
+ ```
34
+ codebase-snapshot
35
+
36
+ Detected nextjs
37
+ Because next.config.ts
38
+ Directory /Users/you/app
39
+
40
+ ◆ How should I set this up?
41
+ ❯ Use the recommended setup nextjs · config · npm scripts · install · generate
42
+ Go through each setting every question starts on its default
43
+ Pick a different preset everything else stays at the defaults
44
+ ```
45
+
46
+ The first entry is the whole setup in one keystroke. The second walks through the settings, and
47
+ every prompt starts on its answer, so pressing `enter` through the lot is the recommended setup
48
+ spelled out. When nothing matched, the tool says so, names the manifests it looked for, and asks
49
+ which preset is closest — the one case where it needs an answer it cannot find itself.
50
+
51
+ A project that already has a config, or one this tool generated before, skips all of it and just
52
+ generates. `--autopilot` does the same work with no questions at all, and reports each decision
53
+ as it makes it.
13
54
 
14
55
  ## Contents
15
56
 
@@ -17,6 +58,7 @@ Running with no command is the same as `generate`.
17
58
  - [`generate`](#generate)
18
59
  - [`validate` / `check`](#validate--check)
19
60
  - [`report`](#report)
61
+ - [`setup`](#setup)
20
62
  - [`init`](#init)
21
63
  - [`explain <path>`](#explain-path)
22
64
  - [`diff <before> <after>`](#diff-before-after)
@@ -35,8 +77,15 @@ Running with no command is the same as `generate`.
35
77
  codebase-snapshot generate [options]
36
78
  ```
37
79
 
38
- Scans the repository and writes the snapshot to `outDir` (default `./snapshot`). This is the
39
- default command, so `codebase-snapshot` alone does the same thing.
80
+ Scans the repository and writes the snapshot to `outDir` (default `./snapshot`).
81
+
82
+ Naming the command is the deterministic form: it runs on the documented defaults and writes nothing
83
+ but the snapshot. Without a command word an unconfigured project gets the
84
+ [guided first run](#the-first-run) first; `--autopilot` asks for that setup from an explicit
85
+ `generate` too, and never prompts.
86
+
87
+ A first run neither form can resolve — no config, no previous snapshot, no `--autopilot`, no
88
+ terminal — exits `2` and names the alternatives. It does not quietly generate on defaults.
40
89
 
41
90
  Detection is automatic: a preset is chosen from the 25 in the
42
91
  [preset table](configuration.md#project-presets), which supplies layers, kinds and ecosystem
@@ -107,7 +156,8 @@ is `validate`, which re-reads the repository and exits `1` on drift.
107
156
  codebase-snapshot setup
108
157
  ```
109
158
 
110
- The whole first run in one command, with defaults and no prompts:
159
+ The whole first run in one command, with defaults and no prompts. This is the same implementation
160
+ `--autopilot` uses, spelled as a command word:
111
161
 
112
162
  1. Detects the project preset.
113
163
  2. Writes `snapshot.config.ts` (or `.mjs`) — **only when the repository has none**. An existing
@@ -124,7 +174,7 @@ The whole first run in one command, with defaults and no prompts:
124
174
  🚀 Setup for: /path/to/repo
125
175
  ✅ Created snapshot.config.ts (preset: nextjs)
126
176
  Added npm scripts: snapshot, snapshot:validate
127
- Added devDependency: codebase-snapshot ^0.9.1
177
+ Added devDependency: codebase-snapshot ^0.9.4
128
178
  Added to .gitignore: snapshot/, snapshot.tmp/
129
179
  ⏳ Installing dependencies…
130
180
  ✅ Installed dependencies (npm install, 2.1s)
@@ -161,7 +211,7 @@ It also has three side effects on files you own, and it says so when it does the
161
211
  ```
162
212
  ✅ Created snapshot.config.ts (preset: node-lib)
163
213
  Added npm scripts: snapshot, snapshot:validate
164
- Added devDependency: codebase-snapshot ^0.9.1
214
+ Added devDependency: codebase-snapshot ^0.9.4
165
215
  Added to .gitignore: snapshot/, snapshot.tmp/
166
216
  ```
167
217
 
@@ -311,6 +361,25 @@ unchanged, plus the ignore entries it added.
311
361
 
312
362
  Printing goes to **stdout** so it can be redirected: `codebase-snapshot skill > SKILL.md`.
313
363
 
364
+ ### `mcp`
365
+
366
+ ```bash
367
+ codebase-snapshot mcp # serve the current directory over stdio
368
+ codebase-snapshot mcp --cwd <path> # serve another repository
369
+ ```
370
+
371
+ Serves the repository to a Model Context Protocol client over stdio: seven tools, `snapshot://`
372
+ resources and two prompts. Stdout carries JSON-RPC messages and nothing else; every note goes to
373
+ stderr. It speaks both protocol eras — the legacy `initialize` handshake and the stateless
374
+ `2026-07-28` revision — and answers whichever the client starts with.
375
+
376
+ `--cwd` is the only directory it will read. A tool's `root` argument can name a sub-directory of it
377
+ and nothing outside, symlinks included. It never installs packages and never writes a config file.
378
+
379
+ There is no `--format` here, because stdout *is* the protocol stream. The full tool list, resource
380
+ URIs and protocol versions appear in [the MCP server guide](mcp.md) and in
381
+ `describe --format json` under `mcp`.
382
+
314
383
  ## Options
315
384
 
316
385
  An option a command does not accept is **rejected**, not ignored: exit `2` and a message
@@ -328,9 +397,11 @@ narrowest cases of the same rule.
328
397
  | `--include-sensitive-files` | flag | off | generate, validate, report, explain, init | Emit `.env`, `*.pem`, `.npmrc` and similar. Off by default; they are reported as skipped. |
329
398
  | `--skip-artifacts <names>` | list | unset | generate, validate, report, explain, init | Comma-separated artifacts to omit. |
330
399
  | `--only-artifacts <names>` | list | unset | generate, validate, report, explain, init | Write only these. Cannot be combined with `--skip-artifacts`. |
331
- | `--format <text\|json>` | enum | `text` | generate, validate, report, explain, init, diff, describe, skill | `json` writes a pure JSON payload to stdout. Every command honours it, and `describe` reports the same eight-command list. |
332
- | `--dry-run` | flag | off | generate, init | Compute everything, print it, write nothing. |
400
+ | `--format <text\|json>` | enum | `text` | generate, validate, report, explain, init, setup, diff, describe, skill | `json` writes a pure JSON payload to stdout. Every command honours it, and `describe` reports the same nine-command list. |
401
+ | `--dry-run` | flag | off | generate, init, setup | Compute everything, print it, write nothing. |
333
402
  | `--watch` | flag | off | generate | Regenerate on every change until interrupted. |
403
+ | `--autopilot` | flag | off | generate | Set the project up without asking — config, `.gitignore`, npm scripts, devDependency, install — then generate. Never overwrites an existing config; refuses an empty directory (exit `1`); contradicts `--no-config` (exit `2`). |
404
+ | `--no-install` | flag | off | setup, generate `--autopilot` | Skip the dependency install. Anywhere else it is a usage error, because it would change nothing. |
334
405
  | `--no-config` | flag | off | generate, validate, report, explain, diff | Ignore every `snapshot.config.*` file and run on the built-in defaults. `diff` never loads a config anyway and accepts the flag for consistency. |
335
406
  | `--discovery <mode>` | enum | `auto` | generate, validate, explain | How files are found: `auto` lists through git when git works and refuses when a `.git` directory cannot be listed; `git` requires git; `walk` scans the tree and reads `.gitignore` files directly. The escape hatch for a broken `.git`. |
336
407
  | `--preset <name>` | name | detected | *init only* | Force a preset instead of detecting one. |
@@ -341,7 +412,7 @@ narrowest cases of the same rule.
341
412
  | `--install` | flag | off | *skill only* | Write the skill into the repository instead of printing it. |
342
413
  | `--target <name>` | enum | `all` | *skill only* | `claude`, `cursor` or `all`. |
343
414
  | `-h`, `--help` | flag | — | every command | Print usage. |
344
- | `-v`, `--version` | flag | — | every command | Print the version on stdout: a bare `v`-prefixed semver such as `v4.0.0`, with no package name. |
415
+ | `-v`, `--version` | flag | — | every command | Print the version on stdout: a bare `v`-prefixed semver (for example `v1.2.3`), with no package name. |
345
416
 
346
417
  The artifact and size options apply to `validate`, `report` and `explain` as well as
347
418
  `generate` because those commands re-read the snapshot: pass the selection you generated
@@ -373,7 +444,7 @@ reason to use it, and the positional form is the documented one.
373
444
  | :--- | :--- |
374
445
  | `0` | Success. For `validate`/`check`, no drift. |
375
446
  | `1` | Drift detected, generation refused or failed (nothing was written), or the state the command needed was not there. |
376
- | `2` | Usage error: unknown flag or command, an option the command does not accept, bad value, wrong argument count. |
447
+ | `2` | Usage error: unknown flag or command, an option the command does not accept, bad value, wrong argument count. Also a first run that needs an answer and has no terminal — run `--autopilot`, `init --yes`, or name the command. |
377
448
 
378
449
  `1` covers the cases where the tool did the right thing but the state is not what you wanted
379
450
  — drift, a refused output directory, an unparseable config. `2` means the command itself was