codebase-snapshot 0.9.9 → 0.9.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +873 -0
- package/README.md +3 -3
- package/dist/bin.js +6343 -3897
- package/dist/{chunk-KUVFTJHK.js → chunk-DUZK5VL2.js} +3036 -1049
- package/dist/{chunk-HQBEQ67K.js → chunk-J4W74N4Y.js} +496 -114
- package/dist/cli.cjs +6282 -3859
- package/dist/cli.d.cts +282 -3
- package/dist/cli.d.ts +282 -3
- package/dist/cli.js +198 -87
- package/dist/generate-worker-D_mUjgXw.d.cts +73 -0
- package/dist/generate-worker-D_mUjgXw.d.ts +73 -0
- package/dist/index.cjs +2981 -1057
- package/dist/index.d.cts +337 -25
- package/dist/index.d.ts +337 -25
- package/dist/index.js +1 -3
- package/dist/mcp.cjs +6426 -5065
- package/dist/mcp.d.cts +136 -68
- package/dist/mcp.d.ts +136 -68
- package/dist/mcp.js +2 -4
- package/dist/{types-Ca4PmkiQ.d.cts → types-DIX6zWSt.d.cts} +130 -2
- package/dist/{types-Ca4PmkiQ.d.ts → types-DIX6zWSt.d.ts} +130 -2
- package/docs/agent.md +1 -1
- package/docs/cli.md +26 -6
- package/docs/configuration.md +48 -9
- package/docs/how-to-feed-a-repo-to-claude.md +61 -0
- package/docs/mcp.md +36 -10
- package/docs/output.md +49 -4
- package/docs/stale-context-is-your-bug.md +13 -0
- package/docs/vs-repomix.md +1 -1
- package/package.json +5 -3
|
@@ -21,7 +21,7 @@ type TextEncoding = "utf-8" | "utf-8-lossy" | "utf-16le" | "utf-16be";
|
|
|
21
21
|
* `full` – the complete file body was emitted.
|
|
22
22
|
* `preview` – the body was truncated (by lines and/or bytes) before emission.
|
|
23
23
|
*/
|
|
24
|
-
type InclusionMode = "full" | "preview";
|
|
24
|
+
type InclusionMode = "full" | "preview" | "signatures";
|
|
25
25
|
interface SourceSummary {
|
|
26
26
|
directives: string[];
|
|
27
27
|
imports: string[];
|
|
@@ -88,7 +88,15 @@ type SkipReason = "binary" | "read-error" | "not-file"
|
|
|
88
88
|
* expected — which used to be recorded as the generic `not-file`, implying the path was a
|
|
89
89
|
* socket or a FIFO rather than a repository whose contents are simply somebody else's.
|
|
90
90
|
*/
|
|
91
|
-
| "nested-repository"
|
|
91
|
+
| "nested-repository"
|
|
92
|
+
/**
|
|
93
|
+
* Not in the set that `--changed-since` or `--staged` asked for.
|
|
94
|
+
*
|
|
95
|
+
* Deliberately distinct from `excluded`, which means the user's own glob rejected the path.
|
|
96
|
+
* Here the file is perfectly includable and simply did not change; reporting it as
|
|
97
|
+
* `excluded` would send a reader looking for an exclude pattern they never wrote.
|
|
98
|
+
*/
|
|
99
|
+
| "unchanged";
|
|
92
100
|
interface SkipRecord {
|
|
93
101
|
/** Path as it may be shown in an artifact: secret spans are masked. */
|
|
94
102
|
path: string;
|
|
@@ -144,6 +152,23 @@ interface PartMetadata {
|
|
|
144
152
|
filename: string;
|
|
145
153
|
/** Exact UTF-8 byte length of the emitted part file. */
|
|
146
154
|
byteSize: number;
|
|
155
|
+
/**
|
|
156
|
+
* Estimated tokens in this part.
|
|
157
|
+
*
|
|
158
|
+
* `byteSize` answers "will this fit the byte budget"; this answers the question a caller
|
|
159
|
+
* actually has, which is "will this fit my context window". An estimate, and labelled as
|
|
160
|
+
* one: `estimateTokens` documents exactly what it counts.
|
|
161
|
+
*/
|
|
162
|
+
estimatedTokens: number;
|
|
163
|
+
/**
|
|
164
|
+
* The byte budget this part was partitioned against.
|
|
165
|
+
*
|
|
166
|
+
* With per-layer budgets a snapshot has several budgets at once, so "within budget"
|
|
167
|
+
* cannot be answered without knowing which one applied. Verification compared every
|
|
168
|
+
* part against the *largest* budget in the run, which passed a part that was over its
|
|
169
|
+
* own.
|
|
170
|
+
*/
|
|
171
|
+
budgetBytes: number;
|
|
147
172
|
sha256: string;
|
|
148
173
|
lines: number;
|
|
149
174
|
fileCount: number;
|
|
@@ -198,6 +223,14 @@ interface ManifestTotals {
|
|
|
198
223
|
skippedFiles: number;
|
|
199
224
|
totalSourceBytes: number;
|
|
200
225
|
totalContentBytes: number;
|
|
226
|
+
/**
|
|
227
|
+
* Estimated tokens in the emitted parts.
|
|
228
|
+
*
|
|
229
|
+
* Optional because a manifest written before this was recorded has no such key. The number
|
|
230
|
+
* is an estimate — see `estimateTokens` for the method — and it exists because the snapshot
|
|
231
|
+
* is pasted into a context window, which is measured in tokens rather than bytes.
|
|
232
|
+
*/
|
|
233
|
+
estimatedTokens?: number;
|
|
201
234
|
partCount: number;
|
|
202
235
|
warningCount: number;
|
|
203
236
|
/**
|
|
@@ -256,13 +289,34 @@ interface ManifestConfigSnapshot {
|
|
|
256
289
|
/** Digest of the effective settings; see `configDigest`. */
|
|
257
290
|
digest?: string;
|
|
258
291
|
maxPartBytes: number;
|
|
292
|
+
/**
|
|
293
|
+
* The token ceiling the run was given, or `undefined` when only bytes were named.
|
|
294
|
+
*
|
|
295
|
+
* Recorded so the manifest states the budget the user chose rather than only the byte
|
|
296
|
+
* value it was converted to.
|
|
297
|
+
*/
|
|
298
|
+
maxPartTokens?: number;
|
|
259
299
|
largeFileBytes: number;
|
|
260
300
|
previewHeadLines: number;
|
|
261
301
|
previewTailLines: number;
|
|
262
302
|
includeMonolith: boolean;
|
|
303
|
+
/**
|
|
304
|
+
* The ceiling above which the monolith is not written; `null` for no ceiling.
|
|
305
|
+
*
|
|
306
|
+
* Optional because a manifest written before this was recorded has no such key.
|
|
307
|
+
*/
|
|
308
|
+
maxMonolithBytes?: number | null;
|
|
263
309
|
includeSensitiveFiles: boolean;
|
|
264
310
|
/** How files were discovered; `undefined` on manifests written before this was recorded. */
|
|
265
311
|
discovery?: DiscoveryMode;
|
|
312
|
+
/** The changed-files scope the snapshot was taken over, when one was used. */
|
|
313
|
+
changedSince?: string;
|
|
314
|
+
staged?: boolean;
|
|
315
|
+
withDependents?: boolean;
|
|
316
|
+
/** True when bodies were replaced by signatures. */
|
|
317
|
+
skeleton?: boolean;
|
|
318
|
+
/** True when `reports/audit.sarif` was written. */
|
|
319
|
+
sarif?: boolean;
|
|
266
320
|
artifacts: ResolvedArtifacts;
|
|
267
321
|
include: string[];
|
|
268
322
|
preset: string;
|
|
@@ -315,6 +369,14 @@ interface Manifest {
|
|
|
315
369
|
artifacts: {
|
|
316
370
|
fullJson: ManifestArtifact | null;
|
|
317
371
|
fullMarkdown: ManifestArtifact | null;
|
|
372
|
+
/**
|
|
373
|
+
* `reports/audit.sarif`, when `--sarif` asked for it.
|
|
374
|
+
*
|
|
375
|
+
* Optional because most runs do not write one, and a manifest written before this
|
|
376
|
+
* existed has no such key at all — the two are indistinguishable to a reader, which is
|
|
377
|
+
* why the presence of the file is also recorded in `config`.
|
|
378
|
+
*/
|
|
379
|
+
sarif?: ManifestArtifact;
|
|
318
380
|
};
|
|
319
381
|
layers: Record<string, number>;
|
|
320
382
|
kinds: Record<string, number>;
|
|
@@ -382,6 +444,15 @@ interface SnapshotConfig {
|
|
|
382
444
|
tmpDir?: string;
|
|
383
445
|
/** Raw bytes, or a human-readable size (`"120k"`, `"1.5mb"`). */
|
|
384
446
|
maxPartBytes?: ByteSize;
|
|
447
|
+
/**
|
|
448
|
+
* A per-part ceiling in *tokens* rather than bytes.
|
|
449
|
+
*
|
|
450
|
+
* The snapshot is pasted into a context window, and a context window is measured in tokens;
|
|
451
|
+
* bytes are what the partitioner writes. Naming a token budget sets `maxPartBytes` to the
|
|
452
|
+
* byte count that corresponds to it, so the byte bound — the thing the partitioner actually
|
|
453
|
+
* enforces — never stops being true. The smaller of the two ceilings wins.
|
|
454
|
+
*/
|
|
455
|
+
maxPartTokens?: ByteSize;
|
|
385
456
|
largeFileBytes?: ByteSize;
|
|
386
457
|
previewHeadLines?: number;
|
|
387
458
|
previewTailLines?: number;
|
|
@@ -393,6 +464,31 @@ interface SnapshotConfig {
|
|
|
393
464
|
include?: string[];
|
|
394
465
|
/** Force a preset by name instead of auto-detecting one. */
|
|
395
466
|
preset?: string;
|
|
467
|
+
/**
|
|
468
|
+
* Only include files changed since this git ref.
|
|
469
|
+
*
|
|
470
|
+
* The pull-request case: the diff is small and the repository is not. Unchanged files are
|
|
471
|
+
* recorded in `skipped[]` with reason `unchanged`, so the manifest accounts for them.
|
|
472
|
+
*/
|
|
473
|
+
changedSince?: string;
|
|
474
|
+
/** Only include files staged for the next commit. */
|
|
475
|
+
staged?: boolean;
|
|
476
|
+
/**
|
|
477
|
+
* Emit signatures instead of bodies for TypeScript and TSX files.
|
|
478
|
+
*
|
|
479
|
+
* Matches `inclusionMode: "signatures"` on the entries it applies to, so a reader can tell a
|
|
480
|
+
* compressed file from a whole one without diffing it against the source.
|
|
481
|
+
*/
|
|
482
|
+
skeleton?: boolean;
|
|
483
|
+
/** Also write `reports/audit.sarif` so a CI security gate can read the findings. */
|
|
484
|
+
sarif?: boolean;
|
|
485
|
+
/**
|
|
486
|
+
* With `changedSince` or `staged`, also include the files that import a changed one.
|
|
487
|
+
*
|
|
488
|
+
* A one-hop closure: the file you changed and the files that reference it are the context
|
|
489
|
+
* needed to change it safely.
|
|
490
|
+
*/
|
|
491
|
+
withDependents?: boolean;
|
|
396
492
|
/** Switch individual snapshot artifacts off. Everything is on by default. */
|
|
397
493
|
artifacts?: ArtifactSelection;
|
|
398
494
|
/**
|
|
@@ -418,6 +514,8 @@ interface ResolvedSnapshotConfig {
|
|
|
418
514
|
outDir: string;
|
|
419
515
|
tmpDir: string;
|
|
420
516
|
maxPartBytes: number;
|
|
517
|
+
/** The token ceiling the user asked for, before it was turned into `maxPartBytes`. */
|
|
518
|
+
maxPartTokens?: number;
|
|
421
519
|
largeFileBytes: number;
|
|
422
520
|
previewHeadLines: number;
|
|
423
521
|
previewTailLines: number;
|
|
@@ -430,6 +528,21 @@ interface ResolvedSnapshotConfig {
|
|
|
430
528
|
* reader treats `undefined` as `auto`, so the effective mode is never guessed twice.
|
|
431
529
|
*/
|
|
432
530
|
discovery?: DiscoveryMode;
|
|
531
|
+
/** Only files changed since this git ref; `undefined` scans everything. */
|
|
532
|
+
changedSince?: string;
|
|
533
|
+
/** Only files staged for the next commit. */
|
|
534
|
+
staged?: boolean;
|
|
535
|
+
/** Emit signatures instead of bodies for TypeScript and TSX files. */
|
|
536
|
+
skeleton?: boolean;
|
|
537
|
+
/**
|
|
538
|
+
* Also write `reports/audit.sarif`, the findings in SARIF 2.1.0.
|
|
539
|
+
*
|
|
540
|
+
* For a CI security gate: GitHub Code Scanning reads SARIF, and a markdown report it cannot
|
|
541
|
+
* ingest is a finding nobody's build can act on.
|
|
542
|
+
*/
|
|
543
|
+
sarif?: boolean;
|
|
544
|
+
/** Also include the files that import a changed one. */
|
|
545
|
+
withDependents?: boolean;
|
|
433
546
|
/** Empty means "no allow-list"; every discovered file is a candidate. */
|
|
434
547
|
include: string[];
|
|
435
548
|
/** The preset that was resolved (detected or forced). */
|
|
@@ -455,9 +568,24 @@ interface CliOptions {
|
|
|
455
568
|
includeSensitiveFiles?: boolean;
|
|
456
569
|
maxMonolithBytes?: number | null;
|
|
457
570
|
maxPartBytes?: number;
|
|
571
|
+
maxPartTokens?: ByteSize | null;
|
|
458
572
|
largeFileBytes?: number;
|
|
459
573
|
/** Artifact names to suppress for this invocation (`--skip-artifacts`). */
|
|
460
574
|
skipArtifacts?: string[];
|
|
575
|
+
/**
|
|
576
|
+
* Artifact names to keep, with everything else off (`--only-artifacts`).
|
|
577
|
+
*
|
|
578
|
+
* Separate from the derived `skipArtifacts` because the two are not interchangeable: "only
|
|
579
|
+
* these" is a statement about the whole selection, and the suppression list cannot express it
|
|
580
|
+
* when the configuration has already turned one of the named artifacts off.
|
|
581
|
+
*/
|
|
582
|
+
onlyArtifacts?: string[];
|
|
583
|
+
discovery?: DiscoveryMode;
|
|
584
|
+
changedSince?: string;
|
|
585
|
+
staged?: boolean;
|
|
586
|
+
withDependents?: boolean;
|
|
587
|
+
skeleton?: boolean;
|
|
588
|
+
sarif?: boolean;
|
|
461
589
|
}
|
|
462
590
|
interface ValidationDrift {
|
|
463
591
|
missingInSnapshot: string[];
|
|
@@ -21,7 +21,7 @@ type TextEncoding = "utf-8" | "utf-8-lossy" | "utf-16le" | "utf-16be";
|
|
|
21
21
|
* `full` – the complete file body was emitted.
|
|
22
22
|
* `preview` – the body was truncated (by lines and/or bytes) before emission.
|
|
23
23
|
*/
|
|
24
|
-
type InclusionMode = "full" | "preview";
|
|
24
|
+
type InclusionMode = "full" | "preview" | "signatures";
|
|
25
25
|
interface SourceSummary {
|
|
26
26
|
directives: string[];
|
|
27
27
|
imports: string[];
|
|
@@ -88,7 +88,15 @@ type SkipReason = "binary" | "read-error" | "not-file"
|
|
|
88
88
|
* expected — which used to be recorded as the generic `not-file`, implying the path was a
|
|
89
89
|
* socket or a FIFO rather than a repository whose contents are simply somebody else's.
|
|
90
90
|
*/
|
|
91
|
-
| "nested-repository"
|
|
91
|
+
| "nested-repository"
|
|
92
|
+
/**
|
|
93
|
+
* Not in the set that `--changed-since` or `--staged` asked for.
|
|
94
|
+
*
|
|
95
|
+
* Deliberately distinct from `excluded`, which means the user's own glob rejected the path.
|
|
96
|
+
* Here the file is perfectly includable and simply did not change; reporting it as
|
|
97
|
+
* `excluded` would send a reader looking for an exclude pattern they never wrote.
|
|
98
|
+
*/
|
|
99
|
+
| "unchanged";
|
|
92
100
|
interface SkipRecord {
|
|
93
101
|
/** Path as it may be shown in an artifact: secret spans are masked. */
|
|
94
102
|
path: string;
|
|
@@ -144,6 +152,23 @@ interface PartMetadata {
|
|
|
144
152
|
filename: string;
|
|
145
153
|
/** Exact UTF-8 byte length of the emitted part file. */
|
|
146
154
|
byteSize: number;
|
|
155
|
+
/**
|
|
156
|
+
* Estimated tokens in this part.
|
|
157
|
+
*
|
|
158
|
+
* `byteSize` answers "will this fit the byte budget"; this answers the question a caller
|
|
159
|
+
* actually has, which is "will this fit my context window". An estimate, and labelled as
|
|
160
|
+
* one: `estimateTokens` documents exactly what it counts.
|
|
161
|
+
*/
|
|
162
|
+
estimatedTokens: number;
|
|
163
|
+
/**
|
|
164
|
+
* The byte budget this part was partitioned against.
|
|
165
|
+
*
|
|
166
|
+
* With per-layer budgets a snapshot has several budgets at once, so "within budget"
|
|
167
|
+
* cannot be answered without knowing which one applied. Verification compared every
|
|
168
|
+
* part against the *largest* budget in the run, which passed a part that was over its
|
|
169
|
+
* own.
|
|
170
|
+
*/
|
|
171
|
+
budgetBytes: number;
|
|
147
172
|
sha256: string;
|
|
148
173
|
lines: number;
|
|
149
174
|
fileCount: number;
|
|
@@ -198,6 +223,14 @@ interface ManifestTotals {
|
|
|
198
223
|
skippedFiles: number;
|
|
199
224
|
totalSourceBytes: number;
|
|
200
225
|
totalContentBytes: number;
|
|
226
|
+
/**
|
|
227
|
+
* Estimated tokens in the emitted parts.
|
|
228
|
+
*
|
|
229
|
+
* Optional because a manifest written before this was recorded has no such key. The number
|
|
230
|
+
* is an estimate — see `estimateTokens` for the method — and it exists because the snapshot
|
|
231
|
+
* is pasted into a context window, which is measured in tokens rather than bytes.
|
|
232
|
+
*/
|
|
233
|
+
estimatedTokens?: number;
|
|
201
234
|
partCount: number;
|
|
202
235
|
warningCount: number;
|
|
203
236
|
/**
|
|
@@ -256,13 +289,34 @@ interface ManifestConfigSnapshot {
|
|
|
256
289
|
/** Digest of the effective settings; see `configDigest`. */
|
|
257
290
|
digest?: string;
|
|
258
291
|
maxPartBytes: number;
|
|
292
|
+
/**
|
|
293
|
+
* The token ceiling the run was given, or `undefined` when only bytes were named.
|
|
294
|
+
*
|
|
295
|
+
* Recorded so the manifest states the budget the user chose rather than only the byte
|
|
296
|
+
* value it was converted to.
|
|
297
|
+
*/
|
|
298
|
+
maxPartTokens?: number;
|
|
259
299
|
largeFileBytes: number;
|
|
260
300
|
previewHeadLines: number;
|
|
261
301
|
previewTailLines: number;
|
|
262
302
|
includeMonolith: boolean;
|
|
303
|
+
/**
|
|
304
|
+
* The ceiling above which the monolith is not written; `null` for no ceiling.
|
|
305
|
+
*
|
|
306
|
+
* Optional because a manifest written before this was recorded has no such key.
|
|
307
|
+
*/
|
|
308
|
+
maxMonolithBytes?: number | null;
|
|
263
309
|
includeSensitiveFiles: boolean;
|
|
264
310
|
/** How files were discovered; `undefined` on manifests written before this was recorded. */
|
|
265
311
|
discovery?: DiscoveryMode;
|
|
312
|
+
/** The changed-files scope the snapshot was taken over, when one was used. */
|
|
313
|
+
changedSince?: string;
|
|
314
|
+
staged?: boolean;
|
|
315
|
+
withDependents?: boolean;
|
|
316
|
+
/** True when bodies were replaced by signatures. */
|
|
317
|
+
skeleton?: boolean;
|
|
318
|
+
/** True when `reports/audit.sarif` was written. */
|
|
319
|
+
sarif?: boolean;
|
|
266
320
|
artifacts: ResolvedArtifacts;
|
|
267
321
|
include: string[];
|
|
268
322
|
preset: string;
|
|
@@ -315,6 +369,14 @@ interface Manifest {
|
|
|
315
369
|
artifacts: {
|
|
316
370
|
fullJson: ManifestArtifact | null;
|
|
317
371
|
fullMarkdown: ManifestArtifact | null;
|
|
372
|
+
/**
|
|
373
|
+
* `reports/audit.sarif`, when `--sarif` asked for it.
|
|
374
|
+
*
|
|
375
|
+
* Optional because most runs do not write one, and a manifest written before this
|
|
376
|
+
* existed has no such key at all — the two are indistinguishable to a reader, which is
|
|
377
|
+
* why the presence of the file is also recorded in `config`.
|
|
378
|
+
*/
|
|
379
|
+
sarif?: ManifestArtifact;
|
|
318
380
|
};
|
|
319
381
|
layers: Record<string, number>;
|
|
320
382
|
kinds: Record<string, number>;
|
|
@@ -382,6 +444,15 @@ interface SnapshotConfig {
|
|
|
382
444
|
tmpDir?: string;
|
|
383
445
|
/** Raw bytes, or a human-readable size (`"120k"`, `"1.5mb"`). */
|
|
384
446
|
maxPartBytes?: ByteSize;
|
|
447
|
+
/**
|
|
448
|
+
* A per-part ceiling in *tokens* rather than bytes.
|
|
449
|
+
*
|
|
450
|
+
* The snapshot is pasted into a context window, and a context window is measured in tokens;
|
|
451
|
+
* bytes are what the partitioner writes. Naming a token budget sets `maxPartBytes` to the
|
|
452
|
+
* byte count that corresponds to it, so the byte bound — the thing the partitioner actually
|
|
453
|
+
* enforces — never stops being true. The smaller of the two ceilings wins.
|
|
454
|
+
*/
|
|
455
|
+
maxPartTokens?: ByteSize;
|
|
385
456
|
largeFileBytes?: ByteSize;
|
|
386
457
|
previewHeadLines?: number;
|
|
387
458
|
previewTailLines?: number;
|
|
@@ -393,6 +464,31 @@ interface SnapshotConfig {
|
|
|
393
464
|
include?: string[];
|
|
394
465
|
/** Force a preset by name instead of auto-detecting one. */
|
|
395
466
|
preset?: string;
|
|
467
|
+
/**
|
|
468
|
+
* Only include files changed since this git ref.
|
|
469
|
+
*
|
|
470
|
+
* The pull-request case: the diff is small and the repository is not. Unchanged files are
|
|
471
|
+
* recorded in `skipped[]` with reason `unchanged`, so the manifest accounts for them.
|
|
472
|
+
*/
|
|
473
|
+
changedSince?: string;
|
|
474
|
+
/** Only include files staged for the next commit. */
|
|
475
|
+
staged?: boolean;
|
|
476
|
+
/**
|
|
477
|
+
* Emit signatures instead of bodies for TypeScript and TSX files.
|
|
478
|
+
*
|
|
479
|
+
* Matches `inclusionMode: "signatures"` on the entries it applies to, so a reader can tell a
|
|
480
|
+
* compressed file from a whole one without diffing it against the source.
|
|
481
|
+
*/
|
|
482
|
+
skeleton?: boolean;
|
|
483
|
+
/** Also write `reports/audit.sarif` so a CI security gate can read the findings. */
|
|
484
|
+
sarif?: boolean;
|
|
485
|
+
/**
|
|
486
|
+
* With `changedSince` or `staged`, also include the files that import a changed one.
|
|
487
|
+
*
|
|
488
|
+
* A one-hop closure: the file you changed and the files that reference it are the context
|
|
489
|
+
* needed to change it safely.
|
|
490
|
+
*/
|
|
491
|
+
withDependents?: boolean;
|
|
396
492
|
/** Switch individual snapshot artifacts off. Everything is on by default. */
|
|
397
493
|
artifacts?: ArtifactSelection;
|
|
398
494
|
/**
|
|
@@ -418,6 +514,8 @@ interface ResolvedSnapshotConfig {
|
|
|
418
514
|
outDir: string;
|
|
419
515
|
tmpDir: string;
|
|
420
516
|
maxPartBytes: number;
|
|
517
|
+
/** The token ceiling the user asked for, before it was turned into `maxPartBytes`. */
|
|
518
|
+
maxPartTokens?: number;
|
|
421
519
|
largeFileBytes: number;
|
|
422
520
|
previewHeadLines: number;
|
|
423
521
|
previewTailLines: number;
|
|
@@ -430,6 +528,21 @@ interface ResolvedSnapshotConfig {
|
|
|
430
528
|
* reader treats `undefined` as `auto`, so the effective mode is never guessed twice.
|
|
431
529
|
*/
|
|
432
530
|
discovery?: DiscoveryMode;
|
|
531
|
+
/** Only files changed since this git ref; `undefined` scans everything. */
|
|
532
|
+
changedSince?: string;
|
|
533
|
+
/** Only files staged for the next commit. */
|
|
534
|
+
staged?: boolean;
|
|
535
|
+
/** Emit signatures instead of bodies for TypeScript and TSX files. */
|
|
536
|
+
skeleton?: boolean;
|
|
537
|
+
/**
|
|
538
|
+
* Also write `reports/audit.sarif`, the findings in SARIF 2.1.0.
|
|
539
|
+
*
|
|
540
|
+
* For a CI security gate: GitHub Code Scanning reads SARIF, and a markdown report it cannot
|
|
541
|
+
* ingest is a finding nobody's build can act on.
|
|
542
|
+
*/
|
|
543
|
+
sarif?: boolean;
|
|
544
|
+
/** Also include the files that import a changed one. */
|
|
545
|
+
withDependents?: boolean;
|
|
433
546
|
/** Empty means "no allow-list"; every discovered file is a candidate. */
|
|
434
547
|
include: string[];
|
|
435
548
|
/** The preset that was resolved (detected or forced). */
|
|
@@ -455,9 +568,24 @@ interface CliOptions {
|
|
|
455
568
|
includeSensitiveFiles?: boolean;
|
|
456
569
|
maxMonolithBytes?: number | null;
|
|
457
570
|
maxPartBytes?: number;
|
|
571
|
+
maxPartTokens?: ByteSize | null;
|
|
458
572
|
largeFileBytes?: number;
|
|
459
573
|
/** Artifact names to suppress for this invocation (`--skip-artifacts`). */
|
|
460
574
|
skipArtifacts?: string[];
|
|
575
|
+
/**
|
|
576
|
+
* Artifact names to keep, with everything else off (`--only-artifacts`).
|
|
577
|
+
*
|
|
578
|
+
* Separate from the derived `skipArtifacts` because the two are not interchangeable: "only
|
|
579
|
+
* these" is a statement about the whole selection, and the suppression list cannot express it
|
|
580
|
+
* when the configuration has already turned one of the named artifacts off.
|
|
581
|
+
*/
|
|
582
|
+
onlyArtifacts?: string[];
|
|
583
|
+
discovery?: DiscoveryMode;
|
|
584
|
+
changedSince?: string;
|
|
585
|
+
staged?: boolean;
|
|
586
|
+
withDependents?: boolean;
|
|
587
|
+
skeleton?: boolean;
|
|
588
|
+
sarif?: boolean;
|
|
461
589
|
}
|
|
462
590
|
interface ValidationDrift {
|
|
463
591
|
missingInSnapshot: string[];
|
package/docs/agent.md
CHANGED
|
@@ -115,7 +115,7 @@ devDependency and the `.gitignore` entries for the snapshot directory, and nothi
|
|
|
115
115
|
|
|
116
116
|
## When the client speaks MCP
|
|
117
117
|
|
|
118
|
-
`codebase-snapshot mcp` serves the repository to a Model Context Protocol client over stdio:
|
|
118
|
+
`codebase-snapshot mcp` serves the repository to a Model Context Protocol client over stdio: eight
|
|
119
119
|
tools, `snapshot://` resources and two prompts. Prefer it over shelling out when the agent already
|
|
120
120
|
has an MCP connection, for two reasons that are properties of the design rather than of the wiring:
|
|
121
121
|
|
package/docs/cli.md
CHANGED
|
@@ -174,7 +174,7 @@ The whole first run in one command, with defaults and no prompts. This is the sa
|
|
|
174
174
|
🚀 Setup for: /path/to/repo
|
|
175
175
|
✅ Created snapshot.config.ts (preset: nextjs)
|
|
176
176
|
Added npm scripts: snapshot, snapshot:validate
|
|
177
|
-
Added devDependency: codebase-snapshot ^0.9.
|
|
177
|
+
Added devDependency: codebase-snapshot ^0.9.14
|
|
178
178
|
Added to .gitignore: snapshot/, snapshot.tmp/
|
|
179
179
|
⏳ Installing dependencies…
|
|
180
180
|
✅ Installed dependencies (npm install, 2.1s)
|
|
@@ -192,7 +192,9 @@ The whole first run in one command, with defaults and no prompts. This is the sa
|
|
|
192
192
|
`generate` and `init`.
|
|
193
193
|
- `--format json` emits **one** document:
|
|
194
194
|
`{ "setup": { configPath, configReused, preset, scripts, dependency, gitignoreEntries, install, notes }, "manifest": { … } }`,
|
|
195
|
-
where `manifest` is the same object `generate --format json` writes.
|
|
195
|
+
where `manifest` is the same object `generate --format json` writes. If that generate fails, the
|
|
196
|
+
document is `{ "error": { … }, "setup": { … }, "manifest": null }` and the exit code is the
|
|
197
|
+
generate's: one document per invocation, a payload or an error, never a payload holding an error.
|
|
196
198
|
- If `outDir` holds files this tool did not write, `setup` still refuses to replace it (exit 1,
|
|
197
199
|
naming the entries) after writing the config — the guard is not weakened by the convenience.
|
|
198
200
|
### `init`
|
|
@@ -211,7 +213,7 @@ It also has three side effects on files you own, and it says so when it does the
|
|
|
211
213
|
```
|
|
212
214
|
✅ Created snapshot.config.ts (preset: node-lib)
|
|
213
215
|
Added npm scripts: snapshot, snapshot:validate
|
|
214
|
-
Added devDependency: codebase-snapshot ^0.9.
|
|
216
|
+
Added devDependency: codebase-snapshot ^0.9.14
|
|
215
217
|
Added to .gitignore: snapshot/, snapshot.tmp/
|
|
216
218
|
```
|
|
217
219
|
|
|
@@ -378,7 +380,7 @@ codebase-snapshot mcp # serve the current directory over stdio
|
|
|
378
380
|
codebase-snapshot mcp --cwd <path> # serve another repository
|
|
379
381
|
```
|
|
380
382
|
|
|
381
|
-
Serves the repository to a Model Context Protocol client over stdio:
|
|
383
|
+
Serves the repository to a Model Context Protocol client over stdio: eight tools, `snapshot://`
|
|
382
384
|
resources and two prompts. Stdout carries JSON-RPC messages and nothing else; every note goes to
|
|
383
385
|
stderr. It speaks both protocol eras — the legacy `initialize` handshake and the stateless
|
|
384
386
|
`2026-07-28` revision — and answers whichever the client starts with.
|
|
@@ -401,15 +403,22 @@ narrowest cases of the same rule.
|
|
|
401
403
|
| `--cwd <path>` | path | current directory | generate, validate, report, explain, init, skill | Repository to operate on. Must exist and be a directory. `diff` and `describe` take no `--cwd`. |
|
|
402
404
|
| `--out-dir <path>` | path | `snapshot` | generate, validate, report, explain, init | Where the snapshot is written or read. May be absolute. |
|
|
403
405
|
| `--max-part-bytes <n>` | size | `307200` (300 KB) | generate, validate, report, explain, init | Hard ceiling per markdown part. Accepts `120k`, `1.5mb`. Minimum 4096. |
|
|
406
|
+
| `--max-part-tokens <n>` | size | unset | generate, validate, setup | A per-part ceiling in **tokens**, for a context window rather than a disk. Narrows `--max-part-bytes` to the equivalent byte count (`tokens × 4`); the smaller of the two ceilings always wins, and both are recorded in the manifest. Accepts `32k`. Minimum 1024 — below that it is a usage error, not a value quietly raised to the floor. |
|
|
404
407
|
| `--large-file-bytes <n>` | size | `204800` (200 KB) | generate, validate, report, explain | Above this, a file becomes a head/tail preview. |
|
|
405
408
|
| `--max-monolith-bytes <n>` | size | unset | generate, validate, report, explain | Skip the single-file monolith when it would exceed this. |
|
|
406
409
|
| `--include-monolith` | flag | off | generate, validate, report, explain | Also write `full-codebase.json` and `full-codebase.md`. |
|
|
407
410
|
| `--include-sensitive-files` | flag | off | generate, validate, report, explain, init | Emit `.env`, `*.pem`, `.npmrc` and similar. Off by default; they are reported as skipped. |
|
|
408
411
|
| `--skip-artifacts <names>` | list | unset | generate, validate, report, explain, init | Comma-separated artifacts to omit. |
|
|
409
|
-
| `--only-artifacts <names>` | list | unset | generate, validate, report, explain, init | Write only these. Cannot be combined with `--skip-artifacts`. |
|
|
412
|
+
| `--only-artifacts <names>` | list | unset | generate, validate, report, explain, init | Write *only* these, and **turn on** any this names: a config with `artifacts: { audit: false }` still gets `reports/audit.md` from `--only-artifacts audit`, because "only these" is a statement about the whole selection rather than a suppression list. Cannot be combined with `--skip-artifacts`. |
|
|
410
413
|
| `--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. |
|
|
414
|
+
| `--sarif` | flag | off | generate, validate, setup | Also write `reports/audit.sarif`: the redaction findings as SARIF 2.1.0, for upload to GitHub Code Scanning. The artefact carries the path, the line and the rule — never the value. Works with `--skip-artifacts audit`, because a CI gate does not read markdown. Rule ids are fixed per rule name, so adding a custom rule never re-points an existing alert. |
|
|
415
|
+
| `--skeleton` | flag | off | generate, validate, setup | Emit signatures instead of bodies: each function or method body becomes `{ /* ... */ }` in place, so formatting and comments around it are untouched. Covers `.ts`, `.tsx`, `.mts`, `.cts`, `.js`, `.jsx`, `.mjs` and `.cjs`; `.d.ts` files are left alone because they are already declarations. The summary still describes the whole file, and affected entries carry `inclusionMode: "signatures"`. `--include-monolith` still writes the whole body, so the monolith's `inclusionMode: "full"` stays true. |
|
|
416
|
+
| `--changed-since <ref>` | git ref | unset | generate, validate, explain | Include only files changed since this ref, plus untracked ones. Every other file is recorded in `manifest.skipped[]` with reason `unchanged` and is never opened. `validate` re-resolves the scope: a skipped file that has changed since is reported as drift, and one that has not is left alone — and with `--with-dependents`, so is a file that now imports one that changed. `explain` answers with the same rule. A ref beginning with `-` is refused. |
|
|
417
|
+
| `--staged` | flag | off | generate, validate, explain | Include only files staged for the next commit. |
|
|
418
|
+
| `--with-dependents` | flag | off | generate, validate, explain | With either of the two above, also include the files that import a changed one — a one-hop closure, not a transitive walk. Needs a scope; alone it is a usage error. |
|
|
419
|
+
| `--install-hook` | flag | off | init, setup | Write `.git/hooks/pre-commit` so a commit is refused while the snapshot is out of date. The hook checks the `outDir` the config names, so it works for a project that does not use `snapshot/`. An existing hook is never replaced; the note says what to add to it instead. |
|
|
411
420
|
| `--dry-run` | flag | off | generate, init, setup | Compute everything, print it, write nothing. |
|
|
412
|
-
| `--watch` | flag | off | generate | Regenerate on every change until interrupted. |
|
|
421
|
+
| `--watch` | flag | off | generate | Regenerate on every change until interrupted. Redaction and parsing are cached per file across runs in the same process, keyed on the content hash, so a one-file edit regenerates from that one file instead of from the whole repository. The cache is memory only and is dropped when the process exits. |
|
|
413
422
|
| `--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`). |
|
|
414
423
|
| `--no-install` | flag | off | setup, generate `--autopilot` | Skip the dependency install. Anywhere else it is a usage error, because it would change nothing. |
|
|
415
424
|
| `--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. |
|
|
@@ -430,6 +439,17 @@ with and the comparison is like for like. `validate --skip-artifacts parts` on a
|
|
|
430
439
|
built without parts therefore agrees, while the same flag on a full snapshot reports a
|
|
431
440
|
config mismatch.
|
|
432
441
|
|
|
442
|
+
The same rule applies to the compression and scope options. `--skeleton`, `--sarif` and
|
|
443
|
+
`--max-part-tokens` are recorded in `manifest.config.digest`, so `validate` accepts them too —
|
|
444
|
+
without that, a snapshot generated with `--skeleton` could not be validated at all, and passing
|
|
445
|
+
the flag to `validate` was rejected as a flag that "does not apply to validate". `--changed-since`,
|
|
446
|
+
`--staged` and `--with-dependents` are accepted by `validate` and `explain` because they decide
|
|
447
|
+
which recorded `unchanged` skips are re-checked and what `explain` answers about one file.
|
|
448
|
+
|
|
449
|
+
`report` does not take any of them: it reads the manifest and prints what is there, so a
|
|
450
|
+
generation setting would change nothing. A flag that parses and does nothing is the silent
|
|
451
|
+
fallback this table exists to prevent.
|
|
452
|
+
|
|
433
453
|
Every option also accepts the `--option=value` form. That form is required for negative
|
|
434
454
|
numbers, which otherwise look like flags.
|
|
435
455
|
|
package/docs/configuration.md
CHANGED
|
@@ -188,6 +188,11 @@ Classification drives per-layer budgets and the `Layers:` breakdown in `report`.
|
|
|
188
188
|
```js
|
|
189
189
|
export default {
|
|
190
190
|
maxPartBytes: "300k", // hard ceiling per part; default 307200, minimum 4096
|
|
191
|
+
// maxPartTokens: 32000, // a per-part ceiling in tokens; narrows maxPartBytes (tokens x 4)
|
|
192
|
+
// changedSince: "main", // only files changed since this git ref
|
|
193
|
+
// staged: true, // only files staged for the next commit
|
|
194
|
+
// withDependents: true, // ...and the files that import them
|
|
195
|
+
// skeleton: true, // signatures instead of bodies (--skeleton); TS, JS, TSX and JSX
|
|
191
196
|
largeFileBytes: "200k", // above this a file becomes a head/tail preview
|
|
192
197
|
previewHeadLines: 80,
|
|
193
198
|
previewTailLines: 40,
|
|
@@ -224,8 +229,9 @@ export default {
|
|
|
224
229
|
```
|
|
225
230
|
|
|
226
231
|
Everything except `monolith` is on by default; `monolith` follows `includeMonolith`. Turn
|
|
227
|
-
artifacts off with the config
|
|
228
|
-
|
|
232
|
+
artifacts off with the config or `--skip-artifacts a,b`. `--only-artifacts a,b` is different: it
|
|
233
|
+
*writes only* those and turns each named artifact **on**, so `--only-artifacts audit` still produces
|
|
234
|
+
`reports/audit.md` in a project whose config says `audit: false`. The two flags cannot be combined.
|
|
229
235
|
|
|
230
236
|
Two combinations warn because they produce a snapshot that contradicts itself: `manifest` off
|
|
231
237
|
means `validate` and `report` cannot inspect the snapshot, and `parts` off with `navigation`
|
|
@@ -261,19 +267,52 @@ makes an audit entry mean something.
|
|
|
261
267
|
matched span is masked, never the whole line, and no excerpt of a matched line reaches any
|
|
262
268
|
artifact. Multi-line PEM keys are masked as a block.
|
|
263
269
|
- `customPatterns` add rules; `disableDefaultPatterns` removes the built-in set — it does not
|
|
264
|
-
disable redaction itself
|
|
270
|
+
disable redaction itself, and it applies to **paths** as well as to content, so a
|
|
271
|
+
credential-shaped file name is no longer skipped by a rule you switched off.
|
|
265
272
|
|
|
266
273
|
Redaction is **fail-closed**, so a placeholder is indistinguishable from a secret: a
|
|
267
274
|
`.env.example` whose line reads `JWT_SECRET=replace-with-base64url-48-bytes` is masked like a
|
|
268
275
|
live key would be, and a test fixture literal such as `process.env.X_SECRET = "test-value"` is
|
|
269
|
-
masked too.
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
secret by shape alone. That is the intended trade-off — the alternative is guessing which values
|
|
273
|
-
are throwaway — and it is worth knowing before you hand the snapshot to a model and find the
|
|
274
|
-
template less readable than the original. To narrow it, remove a rule by name with
|
|
276
|
+
masked too. That is the intended trade-off — the alternative is guessing which values are
|
|
277
|
+
throwaway — and it is worth knowing before you hand the snapshot to a model and find the template
|
|
278
|
+
less readable than the original. To narrow it, remove a rule by name with
|
|
275
279
|
`disableDefaultPatterns: true` plus your own `customPatterns`.
|
|
276
280
|
|
|
281
|
+
The one shape that is *not* masked is a value that is a single `${…}` reference and nothing else —
|
|
282
|
+
`password: "${DB_PASSWORD}"`, `` `${process.env.TOKEN}` `` — because that is an expression and
|
|
283
|
+
masking it rewrites a program (or an assertion message) rather than hiding a credential. Anything
|
|
284
|
+
inside the braces that is not a plain reference settles it the other way:
|
|
285
|
+
`password: "${DB_PASSWORD:-hunter2}"` is masked, because the shell and Compose idiom for a default
|
|
286
|
+
puts a **hardcoded credential** in the fallback position. So does a second interpolation, or any
|
|
287
|
+
quote inside the braces.
|
|
288
|
+
|
|
289
|
+
The same default is masked when it is not quoted, which is how it is usually written:
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
# .env, docker-compose.yml, a workflow, a shell script — all of these are masked
|
|
293
|
+
POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-MyProdPassword123!}
|
|
294
|
+
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-MyProdPassword123!}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
The fallback is what gets replaced; the variable name stays, exactly as `POSTGRES_PASSWORD:` does.
|
|
298
|
+
`${PORT:-3000}` is untouched: the name is not a credential name, and a port number is not one
|
|
299
|
+
either.
|
|
300
|
+
|
|
301
|
+
A credential assigned to a name inside a string is also masked — a connection string is a string:
|
|
302
|
+
|
|
303
|
+
```js
|
|
304
|
+
const dsn = "password=MySuperSecret123;host=db.internal";
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
What is left alone is a credential *name* in a literal, with nothing assigned to it:
|
|
308
|
+
`["awsSecret:", "apiKey:"]` is a list of keys, not two credentials, and a documentation line showing
|
|
309
|
+
`password: "…"` inside a code span is showing an example. The difference is whether the literal
|
|
310
|
+
closes between the name and the value.
|
|
311
|
+
|
|
312
|
+
`readonly` and `declare` are shell assignments in a shell file and declarations in a source file, and
|
|
313
|
+
the file decides: `readonly api_key=hunter2` in `.envrc`, `.sh`, `.zsh`, a YAML or an INI file is
|
|
314
|
+
masked, while `private readonly apiKey = someValue` in TypeScript is left alone.
|
|
315
|
+
|
|
277
316
|
Findings are summarised in `reports/audit.md` (one row per line, naming every rule that matched
|
|
278
317
|
it), counted in `manifest.totals.redactedLines`, and shown in the `generate` summary as a
|
|
279
318
|
`Redacted:` line separate from `Warnings:`.
|