codebase-snapshot 0.0.0-stage → 0.9.0
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 +1242 -0
- package/LICENSE +21 -0
- package/README.md +276 -2
- package/SECURITY.md +110 -0
- package/dist/bin.js +9840 -0
- package/dist/chunk-FJ4RQYY2.js +7885 -0
- package/dist/cli.cjs +9822 -0
- package/dist/cli.d.cts +124 -0
- package/dist/cli.d.ts +124 -0
- package/dist/cli.js +1931 -0
- package/dist/index.cjs +7973 -0
- package/dist/index.d.cts +710 -0
- package/dist/index.d.ts +710 -0
- package/dist/index.js +3 -0
- package/dist/types-Ca4PmkiQ.d.cts +491 -0
- package/dist/types-Ca4PmkiQ.d.ts +491 -0
- package/docs/agent.md +135 -0
- package/docs/cli.md +437 -0
- package/docs/configuration.md +424 -0
- package/docs/how-to-context-budget.md +90 -0
- package/docs/how-to-feed-a-repo-to-claude.md +122 -0
- package/docs/install.md +196 -0
- package/docs/output.md +264 -0
- package/docs/troubleshooting.md +384 -0
- package/docs/vs-code2prompt.md +66 -0
- package/docs/vs-gitingest.md +69 -0
- package/docs/vs-repomix.md +83 -0
- package/package.json +112 -4
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
type Mode = "generate" | "validate" | "report";
|
|
2
|
+
type OutputFormat = "text" | "json";
|
|
3
|
+
/**
|
|
4
|
+
* How files are discovered.
|
|
5
|
+
*
|
|
6
|
+
* `auto` lists files through git when git can do it and walks the tree otherwise; `git` and `walk`
|
|
7
|
+
* force one path. It is part of the manifest's recorded settings because it decides which files a
|
|
8
|
+
* snapshot contains — the same repository scanned both ways is two different snapshots.
|
|
9
|
+
*/
|
|
10
|
+
type DiscoveryMode = "auto" | "git" | "walk";
|
|
11
|
+
/**
|
|
12
|
+
* How a file's bytes became text.
|
|
13
|
+
*
|
|
14
|
+
* Absent from a `FileEntry` means plain UTF-8, which is the overwhelmingly common case. The other
|
|
15
|
+
* values are recorded because they change what the artifact actually contains: `utf-16le`/`utf-16be`
|
|
16
|
+
* files were transcoded, and `utf-8-lossy` files carry U+FFFD where their undecodable bytes were.
|
|
17
|
+
*/
|
|
18
|
+
type TextEncoding = "utf-8" | "utf-8-lossy" | "utf-16le" | "utf-16be";
|
|
19
|
+
/**
|
|
20
|
+
* How much of a file made it into the snapshot.
|
|
21
|
+
* `full` – the complete file body was emitted.
|
|
22
|
+
* `preview` – the body was truncated (by lines and/or bytes) before emission.
|
|
23
|
+
*/
|
|
24
|
+
type InclusionMode = "full" | "preview";
|
|
25
|
+
interface SourceSummary {
|
|
26
|
+
directives: string[];
|
|
27
|
+
imports: string[];
|
|
28
|
+
exports: string[];
|
|
29
|
+
/** Module specifiers re-exported wholesale via `export * from "..."`. */
|
|
30
|
+
reExports: string[];
|
|
31
|
+
functions: string[];
|
|
32
|
+
classes: string[];
|
|
33
|
+
interfaces: string[];
|
|
34
|
+
types: string[];
|
|
35
|
+
enums: string[];
|
|
36
|
+
variables: string[];
|
|
37
|
+
reactComponents: string[];
|
|
38
|
+
hooks: string[];
|
|
39
|
+
routeHandlers: string[];
|
|
40
|
+
/** Number of parser diagnostics, present only when the file has syntax errors. */
|
|
41
|
+
parseErrors?: number;
|
|
42
|
+
}
|
|
43
|
+
interface JsonSummary {
|
|
44
|
+
shape: string;
|
|
45
|
+
itemCount: number | null;
|
|
46
|
+
keys: string[];
|
|
47
|
+
sampleKeys: string[];
|
|
48
|
+
}
|
|
49
|
+
interface MarkdownSummary {
|
|
50
|
+
headings: string[];
|
|
51
|
+
frontmatterKeys: string[];
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* How a summary was produced.
|
|
55
|
+
*
|
|
56
|
+
* `ast` — a real parser (TypeScript compiler; the script block of a
|
|
57
|
+
* Vue/Svelte/Astro component).
|
|
58
|
+
* `structural` — a real grammar for a data format (JSON, Markdown).
|
|
59
|
+
* `heuristic` — line patterns. Useful, but not a parse, and never presented
|
|
60
|
+
* as one.
|
|
61
|
+
*/
|
|
62
|
+
type SummaryQuality = "ast" | "structural" | "heuristic";
|
|
63
|
+
/**
|
|
64
|
+
* Why a discovered path was not emitted.
|
|
65
|
+
*
|
|
66
|
+
* Every reason below is produced by the scanner; nothing in the type is
|
|
67
|
+
* aspirational. `asset` was removed because no code path ever emitted it.
|
|
68
|
+
*/
|
|
69
|
+
type SkipReason = "binary" | "read-error" | "not-file"
|
|
70
|
+
/** A credential-bearing name (`.env`, `*.pem`, `.npmrc`, …). */
|
|
71
|
+
| "sensitive-file"
|
|
72
|
+
/** The path itself matched a secret rule; the emitted path is span-masked. */
|
|
73
|
+
| "sensitive-path"
|
|
74
|
+
/** A directory link (never followed), or a link to neither file nor directory. */
|
|
75
|
+
| "symlink"
|
|
76
|
+
/** A file link whose target resolves outside the scanned root. */
|
|
77
|
+
| "outside-root"
|
|
78
|
+
/** Excluded by a `.gitignore` in a scan without git. */
|
|
79
|
+
| "gitignore"
|
|
80
|
+
/** Excluded by a built-in rule: the ignore list, a preset, or an output dir. */
|
|
81
|
+
| "default-ignore"
|
|
82
|
+
/** Excluded by the user's own `excludePatterns` or `include` allow-list. */
|
|
83
|
+
| "excluded"
|
|
84
|
+
/**
|
|
85
|
+
* A directory that belongs to another repository: a nested checkout or a submodule.
|
|
86
|
+
*
|
|
87
|
+
* A submodule is a single gitlink entry, so `stat` reports a directory where a file was
|
|
88
|
+
* expected — which used to be recorded as the generic `not-file`, implying the path was a
|
|
89
|
+
* socket or a FIFO rather than a repository whose contents are simply somebody else's.
|
|
90
|
+
*/
|
|
91
|
+
| "nested-repository";
|
|
92
|
+
interface SkipRecord {
|
|
93
|
+
/** Path as it may be shown in an artifact: secret spans are masked. */
|
|
94
|
+
path: string;
|
|
95
|
+
reason: SkipReason;
|
|
96
|
+
detail?: string;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* One scanned file. Field names are canonical and unique by design:
|
|
100
|
+
* earlier releases carried `path`/`relativePath`, `sizeBytes`/`bytes` and
|
|
101
|
+
* `hashSha256`/`sha256` aliases that could only ever agree.
|
|
102
|
+
*/
|
|
103
|
+
interface FileEntry {
|
|
104
|
+
relativePath: string;
|
|
105
|
+
sizeBytes: number;
|
|
106
|
+
lines: number;
|
|
107
|
+
hashSha256: string;
|
|
108
|
+
part: string;
|
|
109
|
+
layer: string;
|
|
110
|
+
kind: string;
|
|
111
|
+
language: string;
|
|
112
|
+
inclusionMode: InclusionMode;
|
|
113
|
+
isLarge: boolean;
|
|
114
|
+
isPreviewTruncated: boolean;
|
|
115
|
+
/** Line count of the file on disk. */
|
|
116
|
+
originalLines: number;
|
|
117
|
+
/** Lines actually emitted into the snapshot. */
|
|
118
|
+
includedLines: number;
|
|
119
|
+
/** UTF-8 bytes of file content actually emitted into the snapshot. */
|
|
120
|
+
includedBytes: number;
|
|
121
|
+
summary?: SourceSummary | JsonSummary | MarkdownSummary;
|
|
122
|
+
/** How the summary was produced. Absent when there is no summary. */
|
|
123
|
+
summaryQuality?: SummaryQuality;
|
|
124
|
+
/** True when the summary alone was too large for the part budget and was dropped. */
|
|
125
|
+
summaryOmitted?: boolean;
|
|
126
|
+
/**
|
|
127
|
+
* How the file's bytes were decoded. Absent means plain UTF-8. Present when the file was
|
|
128
|
+
* transcoded from UTF-16, or when it was not valid UTF-8 and lost bytes to U+FFFD.
|
|
129
|
+
*/
|
|
130
|
+
encoding?: TextEncoding;
|
|
131
|
+
/** Emitted body after redaction/truncation. Never serialized into the manifest. */
|
|
132
|
+
content?: string;
|
|
133
|
+
}
|
|
134
|
+
interface PartFileEntry {
|
|
135
|
+
relativePath: string;
|
|
136
|
+
sourceBytes: number;
|
|
137
|
+
includedBytes: number;
|
|
138
|
+
lines: number;
|
|
139
|
+
layer: string;
|
|
140
|
+
kind: string;
|
|
141
|
+
}
|
|
142
|
+
interface PartMetadata {
|
|
143
|
+
index: number;
|
|
144
|
+
filename: string;
|
|
145
|
+
/** Exact UTF-8 byte length of the emitted part file. */
|
|
146
|
+
byteSize: number;
|
|
147
|
+
sha256: string;
|
|
148
|
+
lines: number;
|
|
149
|
+
fileCount: number;
|
|
150
|
+
layersCovered: string[];
|
|
151
|
+
includedFiles: PartFileEntry[];
|
|
152
|
+
}
|
|
153
|
+
interface SecretRule {
|
|
154
|
+
rule: string;
|
|
155
|
+
pattern: RegExp;
|
|
156
|
+
/**
|
|
157
|
+
* Index of the capture group that holds the secret, when the rule has to match
|
|
158
|
+
* its surrounding context to identify one (`password: "…"`). Redaction then
|
|
159
|
+
* masks only that group, so the key and the rest of the line survive.
|
|
160
|
+
*/
|
|
161
|
+
valueGroup?: number;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Shape accepted for `secrets.customPatterns`.
|
|
165
|
+
*
|
|
166
|
+
* A bare `RegExp` or string is accepted because it is the documented form; the
|
|
167
|
+
* object form exists to name a rule. `valueGroup` is only useful with the object
|
|
168
|
+
* form, since a bare pattern has no group to name.
|
|
169
|
+
*/
|
|
170
|
+
type SecretRuleInput = {
|
|
171
|
+
rule: string;
|
|
172
|
+
pattern: RegExp | string;
|
|
173
|
+
valueGroup?: number;
|
|
174
|
+
} | RegExp | string;
|
|
175
|
+
/**
|
|
176
|
+
* A detected secret. Deliberately carries no excerpt of the matched source:
|
|
177
|
+
* the previous `snippet` field was the vector by which raw credentials were
|
|
178
|
+
* written into `reports/audit.md`.
|
|
179
|
+
*/
|
|
180
|
+
interface SecretFinding {
|
|
181
|
+
/**
|
|
182
|
+
* Every rule that matched this line, in rule-table order.
|
|
183
|
+
*
|
|
184
|
+
* One finding is one *line*, not one (line, rule) pair. A single line can match two rules — an
|
|
185
|
+
* unquoted `AUTH_TOKEN=<value>` assignment matches both `credential-assignment-unquoted` and
|
|
186
|
+
* `env-secret-assignment` — and reporting it twice doubled the figure a user is asked to explain,
|
|
187
|
+
* in the CLI summary and in `reports/audit.md` alike.
|
|
188
|
+
*/
|
|
189
|
+
rules: string[];
|
|
190
|
+
line: number;
|
|
191
|
+
path?: string;
|
|
192
|
+
}
|
|
193
|
+
interface ManifestTotals {
|
|
194
|
+
candidateFiles: number;
|
|
195
|
+
includedFiles: number;
|
|
196
|
+
fullFiles: number;
|
|
197
|
+
previewFiles: number;
|
|
198
|
+
skippedFiles: number;
|
|
199
|
+
totalSourceBytes: number;
|
|
200
|
+
totalContentBytes: number;
|
|
201
|
+
partCount: number;
|
|
202
|
+
warningCount: number;
|
|
203
|
+
/**
|
|
204
|
+
* Lines whose values were masked, counted once per line.
|
|
205
|
+
*
|
|
206
|
+
* Separate from `warningCount` because they are not the same event: a warning says the snapshot
|
|
207
|
+
* may surprise you, while a redaction says the tool did its job. Folding them together made a
|
|
208
|
+
* clean repository report `Warnings: 156` and left the summary's own slice of ten looking like
|
|
209
|
+
* a truncated warning list.
|
|
210
|
+
*/
|
|
211
|
+
redactedLines: number;
|
|
212
|
+
}
|
|
213
|
+
interface ManifestArtifact {
|
|
214
|
+
byteSize: number;
|
|
215
|
+
sha256: string;
|
|
216
|
+
}
|
|
217
|
+
interface ManifestIndexes {
|
|
218
|
+
routes: Array<{
|
|
219
|
+
path: string;
|
|
220
|
+
methods: string[];
|
|
221
|
+
part: string;
|
|
222
|
+
}>;
|
|
223
|
+
components: Array<{
|
|
224
|
+
path: string;
|
|
225
|
+
symbols: string[];
|
|
226
|
+
part: string;
|
|
227
|
+
layer: string;
|
|
228
|
+
}>;
|
|
229
|
+
handlers: Array<{
|
|
230
|
+
path: string;
|
|
231
|
+
exports: string[];
|
|
232
|
+
part: string;
|
|
233
|
+
}>;
|
|
234
|
+
features: Array<{
|
|
235
|
+
name: string;
|
|
236
|
+
files: number;
|
|
237
|
+
}>;
|
|
238
|
+
imports: {
|
|
239
|
+
byFile: Array<{
|
|
240
|
+
path: string;
|
|
241
|
+
imports: string[];
|
|
242
|
+
exports: string[];
|
|
243
|
+
}>;
|
|
244
|
+
centralFiles: Array<{
|
|
245
|
+
path: string;
|
|
246
|
+
importedByCount: number;
|
|
247
|
+
}>;
|
|
248
|
+
};
|
|
249
|
+
directives: Array<{
|
|
250
|
+
path: string;
|
|
251
|
+
directives: string[];
|
|
252
|
+
}>;
|
|
253
|
+
}
|
|
254
|
+
/** Effective configuration recorded so `validate` can detect config drift. */
|
|
255
|
+
interface ManifestConfigSnapshot {
|
|
256
|
+
/** Digest of the effective settings; see `configDigest`. */
|
|
257
|
+
digest?: string;
|
|
258
|
+
maxPartBytes: number;
|
|
259
|
+
largeFileBytes: number;
|
|
260
|
+
previewHeadLines: number;
|
|
261
|
+
previewTailLines: number;
|
|
262
|
+
includeMonolith: boolean;
|
|
263
|
+
includeSensitiveFiles: boolean;
|
|
264
|
+
/** How files were discovered; `undefined` on manifests written before this was recorded. */
|
|
265
|
+
discovery?: DiscoveryMode;
|
|
266
|
+
artifacts: ResolvedArtifacts;
|
|
267
|
+
include: string[];
|
|
268
|
+
preset: string;
|
|
269
|
+
layersMode: "merge" | "replace";
|
|
270
|
+
budgets: ResolvedBudgets;
|
|
271
|
+
excludePatterns: string[];
|
|
272
|
+
layers: string[];
|
|
273
|
+
kinds: string[];
|
|
274
|
+
}
|
|
275
|
+
interface Manifest {
|
|
276
|
+
schemaVersion: string;
|
|
277
|
+
generatedAt: string;
|
|
278
|
+
/**
|
|
279
|
+
* Present only in the manifest embedded in `full-codebase.json`.
|
|
280
|
+
*
|
|
281
|
+
* That file cannot record its own sha256, so its embedded copy leaves `artifacts.fullJson` null
|
|
282
|
+
* and points at `manifest.json` for the real digest instead of writing a number that describes
|
|
283
|
+
* different bytes. The shipped manifest never carries this field.
|
|
284
|
+
*/
|
|
285
|
+
selfReference?: string;
|
|
286
|
+
generator: {
|
|
287
|
+
name: string;
|
|
288
|
+
version: string;
|
|
289
|
+
};
|
|
290
|
+
git: {
|
|
291
|
+
branch: string;
|
|
292
|
+
commit: string;
|
|
293
|
+
isDirty: boolean;
|
|
294
|
+
};
|
|
295
|
+
budgets: {
|
|
296
|
+
maxPartBytes: number;
|
|
297
|
+
largeFileBytes: number;
|
|
298
|
+
};
|
|
299
|
+
config: ManifestConfigSnapshot;
|
|
300
|
+
counts: {
|
|
301
|
+
totalFiles: number;
|
|
302
|
+
totalSizeBytes: number;
|
|
303
|
+
totalLines: number;
|
|
304
|
+
partsCount: number;
|
|
305
|
+
skippedCount: number;
|
|
306
|
+
largeFileCount: number;
|
|
307
|
+
truncatedPreviewCount: number;
|
|
308
|
+
};
|
|
309
|
+
totals: ManifestTotals;
|
|
310
|
+
fingerprint: string;
|
|
311
|
+
validation: {
|
|
312
|
+
status: "passed" | "failed";
|
|
313
|
+
errors: string[];
|
|
314
|
+
};
|
|
315
|
+
artifacts: {
|
|
316
|
+
fullJson: ManifestArtifact | null;
|
|
317
|
+
fullMarkdown: ManifestArtifact | null;
|
|
318
|
+
};
|
|
319
|
+
layers: Record<string, number>;
|
|
320
|
+
kinds: Record<string, number>;
|
|
321
|
+
indexes: ManifestIndexes;
|
|
322
|
+
parts: PartMetadata[];
|
|
323
|
+
files: FileEntry[];
|
|
324
|
+
skipped: SkipRecord[];
|
|
325
|
+
warnings: string[];
|
|
326
|
+
}
|
|
327
|
+
interface LayerRule {
|
|
328
|
+
name: string;
|
|
329
|
+
match: string | RegExp | ((filePath: string) => boolean);
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Which snapshot artifacts get written.
|
|
333
|
+
*
|
|
334
|
+
* Everything a developer opens is produced by default; each entry exists only so
|
|
335
|
+
* a single artifact can be switched off without disabling the rest. `monolith`
|
|
336
|
+
* is the one exception, because it duplicates the entire payload: it follows
|
|
337
|
+
* `includeMonolith` (off by default) unless set here explicitly.
|
|
338
|
+
*/
|
|
339
|
+
interface ArtifactSelection {
|
|
340
|
+
/** `README.md` — the orientation page for humans and models. */
|
|
341
|
+
readme?: boolean;
|
|
342
|
+
/** `manifest.json` — the registry `validate` and `report` read. */
|
|
343
|
+
manifest?: boolean;
|
|
344
|
+
/** `parts/part-XXX.md` — the partitioned code payload. */
|
|
345
|
+
parts?: boolean;
|
|
346
|
+
/** `reports/navigation.md` — file-to-part lookup index. */
|
|
347
|
+
navigation?: boolean;
|
|
348
|
+
/** `reports/project-tree.md` — ASCII repository tree. */
|
|
349
|
+
projectTree?: boolean;
|
|
350
|
+
/** `reports/audit.md` — security, layer, kind and skip findings. */
|
|
351
|
+
audit?: boolean;
|
|
352
|
+
/** `full-codebase.{json,md}` — one file per format, the whole payload again. */
|
|
353
|
+
monolith?: boolean;
|
|
354
|
+
}
|
|
355
|
+
/** Every artifact decision made concrete. */
|
|
356
|
+
type ResolvedArtifacts = Required<ArtifactSelection>;
|
|
357
|
+
interface KindRule {
|
|
358
|
+
kind: string;
|
|
359
|
+
match: string | RegExp | ((filePath: string) => boolean);
|
|
360
|
+
}
|
|
361
|
+
/** A byte budget: either raw bytes or a human-readable size such as `"120k"`. */
|
|
362
|
+
type ByteSize = number | string;
|
|
363
|
+
/** Per-layer part budgets. Unlisted layers fall back to `default`. */
|
|
364
|
+
interface BudgetConfig {
|
|
365
|
+
default?: ByteSize;
|
|
366
|
+
byLayer?: Record<string, ByteSize>;
|
|
367
|
+
}
|
|
368
|
+
/** Every budget resolved to concrete byte counts. */
|
|
369
|
+
interface ResolvedBudgets {
|
|
370
|
+
default: number;
|
|
371
|
+
byLayer: Record<string, number>;
|
|
372
|
+
}
|
|
373
|
+
/** Lifecycle hooks. Not serialized into the manifest (functions are not data). */
|
|
374
|
+
interface HooksConfig {
|
|
375
|
+
/** Runs once after a successful generate, with the manifest that was written. */
|
|
376
|
+
afterGenerate?: (manifest: Manifest) => void;
|
|
377
|
+
}
|
|
378
|
+
interface SnapshotConfig {
|
|
379
|
+
cwd?: string;
|
|
380
|
+
branch?: string;
|
|
381
|
+
outDir?: string;
|
|
382
|
+
tmpDir?: string;
|
|
383
|
+
/** Raw bytes, or a human-readable size (`"120k"`, `"1.5mb"`). */
|
|
384
|
+
maxPartBytes?: ByteSize;
|
|
385
|
+
largeFileBytes?: ByteSize;
|
|
386
|
+
previewHeadLines?: number;
|
|
387
|
+
previewTailLines?: number;
|
|
388
|
+
includeMonolith?: boolean;
|
|
389
|
+
maxMonolithBytes?: ByteSize | null;
|
|
390
|
+
/** Opt in to emitting `.env`/`*.pem`/`.npmrc`/credential files. Default false. */
|
|
391
|
+
includeSensitiveFiles?: boolean;
|
|
392
|
+
/** Allow-list of globs. When non-empty, only matching files are included. */
|
|
393
|
+
include?: string[];
|
|
394
|
+
/** Force a preset by name instead of auto-detecting one. */
|
|
395
|
+
preset?: string;
|
|
396
|
+
/** Switch individual snapshot artifacts off. Everything is on by default. */
|
|
397
|
+
artifacts?: ArtifactSelection;
|
|
398
|
+
/**
|
|
399
|
+
* How user `layers`/`kinds` interact with the detected preset (default
|
|
400
|
+
* `"merge"`): user rules are evaluated first, then the preset fills the gaps.
|
|
401
|
+
* `"replace"` discards the preset entirely.
|
|
402
|
+
*/
|
|
403
|
+
layersMode?: "merge" | "replace";
|
|
404
|
+
/** Per-layer part budgets. */
|
|
405
|
+
budgets?: BudgetConfig;
|
|
406
|
+
hooks?: HooksConfig;
|
|
407
|
+
layers?: LayerRule[];
|
|
408
|
+
kinds?: KindRule[];
|
|
409
|
+
excludePatterns?: string[];
|
|
410
|
+
secrets?: {
|
|
411
|
+
customPatterns?: SecretRuleInput[];
|
|
412
|
+
disableDefaultPatterns?: boolean;
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
interface ResolvedSnapshotConfig {
|
|
416
|
+
cwd: string;
|
|
417
|
+
branch: string;
|
|
418
|
+
outDir: string;
|
|
419
|
+
tmpDir: string;
|
|
420
|
+
maxPartBytes: number;
|
|
421
|
+
largeFileBytes: number;
|
|
422
|
+
previewHeadLines: number;
|
|
423
|
+
previewTailLines: number;
|
|
424
|
+
includeMonolith: boolean;
|
|
425
|
+
maxMonolithBytes: number | null;
|
|
426
|
+
includeSensitiveFiles: boolean;
|
|
427
|
+
/**
|
|
428
|
+
* How files are discovered; `auto` picks git when it can list files and walks the tree otherwise.
|
|
429
|
+
* Optional because a resolved config can arrive from an older caller that never named it — every
|
|
430
|
+
* reader treats `undefined` as `auto`, so the effective mode is never guessed twice.
|
|
431
|
+
*/
|
|
432
|
+
discovery?: DiscoveryMode;
|
|
433
|
+
/** Empty means "no allow-list"; every discovered file is a candidate. */
|
|
434
|
+
include: string[];
|
|
435
|
+
/** The preset that was resolved (detected or forced). */
|
|
436
|
+
preset: string;
|
|
437
|
+
/** Ecosystem ignores contributed by the preset, merged with `excludePatterns`. */
|
|
438
|
+
presetExclude: string[];
|
|
439
|
+
artifacts: ResolvedArtifacts;
|
|
440
|
+
layersMode: "merge" | "replace";
|
|
441
|
+
budgets: ResolvedBudgets;
|
|
442
|
+
hooks: HooksConfig;
|
|
443
|
+
layers: LayerRule[];
|
|
444
|
+
kinds: KindRule[];
|
|
445
|
+
excludePatterns: string[];
|
|
446
|
+
secretRules: SecretRule[];
|
|
447
|
+
}
|
|
448
|
+
interface CliOptions {
|
|
449
|
+
mode: Mode;
|
|
450
|
+
format: OutputFormat;
|
|
451
|
+
dryRun: boolean;
|
|
452
|
+
cwd?: string;
|
|
453
|
+
outDir?: string;
|
|
454
|
+
includeMonolith?: boolean;
|
|
455
|
+
includeSensitiveFiles?: boolean;
|
|
456
|
+
maxMonolithBytes?: number | null;
|
|
457
|
+
maxPartBytes?: number;
|
|
458
|
+
largeFileBytes?: number;
|
|
459
|
+
/** Artifact names to suppress for this invocation (`--skip-artifacts`). */
|
|
460
|
+
skipArtifacts?: string[];
|
|
461
|
+
}
|
|
462
|
+
interface ValidationDrift {
|
|
463
|
+
missingInSnapshot: string[];
|
|
464
|
+
missingOnDisk: string[];
|
|
465
|
+
/** Files that exist but could not be read, so their content could not be verified. */
|
|
466
|
+
unreadable: string[];
|
|
467
|
+
hashMismatches: Array<{
|
|
468
|
+
path: string;
|
|
469
|
+
diskHash: string;
|
|
470
|
+
snapshotHash: string;
|
|
471
|
+
}>;
|
|
472
|
+
/** Emitted `parts/*.md` that are missing or no longer match their recorded hash. */
|
|
473
|
+
partIssues: Array<{
|
|
474
|
+
part: string;
|
|
475
|
+
issue: string;
|
|
476
|
+
}>;
|
|
477
|
+
/** `counts`/`totals`/`budgets` that disagree with the rest of the manifest. */
|
|
478
|
+
totalsMismatches: string[];
|
|
479
|
+
/** Effective-configuration differences between the manifest and the current run. */
|
|
480
|
+
configMismatches: string[];
|
|
481
|
+
}
|
|
482
|
+
interface ValidationResult {
|
|
483
|
+
valid: boolean;
|
|
484
|
+
manifestPresent: boolean;
|
|
485
|
+
drift: ValidationDrift;
|
|
486
|
+
errors: string[];
|
|
487
|
+
/** Non-fatal observations (e.g. branch moved on). */
|
|
488
|
+
warnings: string[];
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
export type { ArtifactSelection as A, ByteSize as B, CliOptions as C, DiscoveryMode as D, FileEntry as F, HooksConfig as H, InclusionMode as I, JsonSummary as J, KindRule as K, LayerRule as L, MarkdownSummary as M, OutputFormat as O, PartMetadata as P, ResolvedSnapshotConfig as R, SecretRule as S, TextEncoding as T, ValidationResult as V, SkipRecord as a, SourceSummary as b, SnapshotConfig as c, SecretFinding as d, Manifest as e, BudgetConfig as f, ManifestArtifact as g, ManifestConfigSnapshot as h, ManifestIndexes as i, ManifestTotals as j, Mode as k, PartFileEntry as l, ResolvedArtifacts as m, ResolvedBudgets as n, SecretRuleInput as o, SkipReason as p, SummaryQuality as q, ValidationDrift as r };
|
package/docs/agent.md
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Using this with an AI agent
|
|
2
|
+
|
|
3
|
+
The package ships two things that make it usable by an agent that has never heard of it: a
|
|
4
|
+
**machine-readable contract** and a **bundled skill**.
|
|
5
|
+
|
|
6
|
+
Nothing needs to be installed globally and no prior knowledge of the tool is required.
|
|
7
|
+
|
|
8
|
+
## The three commands an agent needs
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
# 1. Read the contract. This is the machine-readable replacement for --help.
|
|
12
|
+
npx codebase-snapshot describe --format json
|
|
13
|
+
|
|
14
|
+
# 2. Write a commented config that lists every setting (non-interactive).
|
|
15
|
+
npx codebase-snapshot init --yes
|
|
16
|
+
|
|
17
|
+
# 3. Generate and verify.
|
|
18
|
+
npx codebase-snapshot generate
|
|
19
|
+
npx codebase-snapshot validate
|
|
20
|
+
```
|
|
21
|
+
|
|
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
|
+
machine.
|
|
24
|
+
|
|
25
|
+
## The contract
|
|
26
|
+
|
|
27
|
+
`describe --format json` returns the whole tool as data on stdout:
|
|
28
|
+
|
|
29
|
+
| Field | Contents |
|
|
30
|
+
| :--- | :--- |
|
|
31
|
+
| `invocation` | The supported one-shot and installed commands, and the bin names. |
|
|
32
|
+
| `workflow` | An ordered six-step procedure: verify → understand → configure → generate → verify output → consume. |
|
|
33
|
+
| `commands` | Every command with what it does, its positionals and its exit codes. |
|
|
34
|
+
| `options` | Every option with its type, default, which commands it applies to, and a summary. |
|
|
35
|
+
| `exitCodes` | `0` success, `1` drift or refused generation, `2` usage error. |
|
|
36
|
+
| `presets` | All 25, with priority, recommended budget and ecosystem ignores. |
|
|
37
|
+
| `artifacts` | Each artifact with its on/off default and what it contains. |
|
|
38
|
+
| `config` | Every config filename and every setting with its default. |
|
|
39
|
+
| `output` | The directory layout and how to read `manifest.json`. |
|
|
40
|
+
| `safety` | What is excluded or redacted, and which directories are refused. |
|
|
41
|
+
| `troubleshooting` | Symptom → cause → fix. |
|
|
42
|
+
|
|
43
|
+
It is generated from the same code the CLI runs — presets, artifact names and versions are
|
|
44
|
+
read directly — and a test compares its documented defaults against a real
|
|
45
|
+
`generate --dry-run` manifest. If a default changes, the test fails rather than the contract
|
|
46
|
+
silently misleading an agent.
|
|
47
|
+
|
|
48
|
+
`describe` with no `--format` renders the same content for a human.
|
|
49
|
+
|
|
50
|
+
## The skill
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npx codebase-snapshot skill # print it (stdout)
|
|
54
|
+
npx codebase-snapshot skill --install # write it into the repository
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Printing goes to stdout deliberately, so it can be redirected:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
npx codebase-snapshot skill > SKILL.md
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`--install` writes the same skill into the registries agent tools read by name:
|
|
64
|
+
|
|
65
|
+
| Target | Path | Read by |
|
|
66
|
+
| :--- | :--- | :--- |
|
|
67
|
+
| `claude` | `.claude/skills/codebase-snapshot/SKILL.md` | Claude Code auto-discovery |
|
|
68
|
+
| `cursor` | `.cursor/rules/codebase-snapshot.mdc` | Cursor rules |
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
npx codebase-snapshot skill --install --target claude # one registry
|
|
72
|
+
npx codebase-snapshot skill --install # both (default)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Both are **registries keyed by skill name**: the path carries `codebase-snapshot`, so the file
|
|
76
|
+
cannot be mistaken for your project's own documentation, and two skills cannot collide.
|
|
77
|
+
|
|
78
|
+
**It never writes `AGENTS.md`.** That file is the repository's own agent contract — build
|
|
79
|
+
commands, conventions, house rules — and an install that creates or edits it puts a tool's
|
|
80
|
+
marketing where the project's rules belong. If you want the skill in there, print it and paste
|
|
81
|
+
it, so the diff and the commit are yours:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
npx codebase-snapshot skill >> AGENTS.md
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The install is **idempotent**, and whatever it *creates* is appended to `.gitignore` in the
|
|
88
|
+
same run — these are a tool's files written into your repository, and `git status` should not
|
|
89
|
+
turn that into a decision about what to commit. A file that already existed is never ignored.
|
|
90
|
+
`--format json` reports what was created, updated or unchanged, and which ignore entries were
|
|
91
|
+
added.
|
|
92
|
+
|
|
93
|
+
`codebase-snapshot init` does none of this: it writes the config, the npm scripts, the
|
|
94
|
+
devDependency and the `.gitignore` entries for the snapshot directory, and nothing else.
|
|
95
|
+
|
|
96
|
+
## Why both a contract and a skill
|
|
97
|
+
|
|
98
|
+
The contract is for **doing** — the data an agent needs to pick correct commands and options.
|
|
99
|
+
The skill is for **knowing when to act** — it carries the trigger conditions, the ordered
|
|
100
|
+
workflow, and the surprises an agent cannot infer from a schema: sensitive files excluded by
|
|
101
|
+
default, secrets redacted, the output directory being tool-owned.
|
|
102
|
+
|
|
103
|
+
## What an agent should avoid
|
|
104
|
+
|
|
105
|
+
- **Do not point `--out-dir` at source code.** It is refused, and the refusal is deliberate.
|
|
106
|
+
- **Do not add `--include-sensitive-files`** unless the user explicitly asked for credential
|
|
107
|
+
files; by default they are excluded and recorded as `sensitive-file` in `manifest.skipped[]`.
|
|
108
|
+
- **Do not work around redaction.** If a value is masked, ask the user rather than reading the
|
|
109
|
+
original file.
|
|
110
|
+
- **Do not assume exit 2 means the repository is broken.** It means the command was wrong.
|
|
111
|
+
Re-read the contract.
|
|
112
|
+
- **Do not treat a bare `npx` that prints nothing as a repository problem.** A stale global
|
|
113
|
+
install shadows `npx`; see [Troubleshooting](troubleshooting.md#the-command-does-nothing).
|
|
114
|
+
|
|
115
|
+
## Verifying an agent can drive it
|
|
116
|
+
|
|
117
|
+
`tests/agent-simulation.test.js` runs the published instructions as an agent would: read
|
|
118
|
+
`--help`, discover `describe`, then execute the six workflow steps in order against an 8-file
|
|
119
|
+
Django fixture.
|
|
120
|
+
|
|
121
|
+
It asserts what the contract promises at each step: `--version` prints a version, every
|
|
122
|
+
published option states where it applies, `init --yes` writes a config naming **every**
|
|
123
|
+
setting the contract lists, `generate --format json` exits `0` with
|
|
124
|
+
`validation.status: "passed"` and the preset `python-django`, every fixture file is either in
|
|
125
|
+
`manifest.files[]` or explained in `manifest.skipped[]`, each `files[]` entry carries its
|
|
126
|
+
classification and hash, and `validate` then exits `0`. If an agent following the published
|
|
127
|
+
words would fail, that test fails.
|
|
128
|
+
|
|
129
|
+
When you parse the manifest, remember what is conditional: `manifest.files[]` carries
|
|
130
|
+
`relativePath`, `layer`, `kind`, `language`, `part`, `inclusionMode` and `hashSha256` for
|
|
131
|
+
every included file, but `summary` and `summaryQuality` only for a language the tool can
|
|
132
|
+
analyse. A `.gitignore` or plain-text entry has neither key, and a file whose summary was too
|
|
133
|
+
large for its part carries `summaryOmitted: true` instead of a `summary`. `encoding` appears only
|
|
134
|
+
when a file was not plain UTF-8: `utf-16le`/`utf-16be` were transcoded, and `utf-8-lossy` means
|
|
135
|
+
the body you are reading contains `U+FFFD` where undecodable bytes were.
|