@ankurhalder/codebase-snapshot 4.0.7 → 4.1.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/README.md +4 -1
- package/dist/bin.js +1279 -268
- package/dist/{chunk-VRJTXNJV.js → chunk-6SMZR5EM.js} +998 -193
- package/dist/cli.cjs +1286 -268
- package/dist/cli.d.cts +60 -2
- package/dist/cli.d.ts +60 -2
- package/dist/cli.js +288 -79
- package/dist/index.cjs +997 -190
- package/dist/index.d.cts +163 -54
- package/dist/index.d.ts +163 -54
- package/dist/index.js +1 -1
- package/dist/{types-x7aogSer.d.cts → types-BMP676ER.d.cts} +41 -2
- package/dist/{types-x7aogSer.d.ts → types-BMP676ER.d.ts} +41 -2
- package/docs/agent.md +3 -1
- package/docs/cli.md +34 -3
- package/docs/configuration.md +29 -4
- package/docs/install.md +5 -3
- package/docs/output.md +22 -9
- package/docs/troubleshooting.md +60 -3
- package/package.json +1 -1
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
type Mode = "generate" | "validate" | "report";
|
|
2
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";
|
|
3
19
|
/**
|
|
4
20
|
* How much of a file made it into the snapshot.
|
|
5
21
|
* `full` – the complete file body was emitted.
|
|
@@ -64,7 +80,15 @@ type SkipReason = "binary" | "read-error" | "not-file"
|
|
|
64
80
|
/** Excluded by a built-in rule: the ignore list, a preset, or an output dir. */
|
|
65
81
|
| "default-ignore"
|
|
66
82
|
/** Excluded by the user's own `excludePatterns` or `include` allow-list. */
|
|
67
|
-
| "excluded"
|
|
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";
|
|
68
92
|
interface SkipRecord {
|
|
69
93
|
/** Path as it may be shown in an artifact: secret spans are masked. */
|
|
70
94
|
path: string;
|
|
@@ -99,6 +123,11 @@ interface FileEntry {
|
|
|
99
123
|
summaryQuality?: SummaryQuality;
|
|
100
124
|
/** True when the summary alone was too large for the part budget and was dropped. */
|
|
101
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;
|
|
102
131
|
/** Emitted body after redaction/truncation. Never serialized into the manifest. */
|
|
103
132
|
content?: string;
|
|
104
133
|
}
|
|
@@ -215,6 +244,8 @@ interface ManifestConfigSnapshot {
|
|
|
215
244
|
previewTailLines: number;
|
|
216
245
|
includeMonolith: boolean;
|
|
217
246
|
includeSensitiveFiles: boolean;
|
|
247
|
+
/** How files were discovered; `undefined` on manifests written before this was recorded. */
|
|
248
|
+
discovery?: DiscoveryMode;
|
|
218
249
|
artifacts: ResolvedArtifacts;
|
|
219
250
|
include: string[];
|
|
220
251
|
preset: string;
|
|
@@ -368,6 +399,12 @@ interface ResolvedSnapshotConfig {
|
|
|
368
399
|
includeMonolith: boolean;
|
|
369
400
|
maxMonolithBytes: number | null;
|
|
370
401
|
includeSensitiveFiles: boolean;
|
|
402
|
+
/**
|
|
403
|
+
* How files are discovered; `auto` picks git when it can list files and walks the tree otherwise.
|
|
404
|
+
* Optional because a resolved config can arrive from an older caller that never named it — every
|
|
405
|
+
* reader treats `undefined` as `auto`, so the effective mode is never guessed twice.
|
|
406
|
+
*/
|
|
407
|
+
discovery?: DiscoveryMode;
|
|
371
408
|
/** Empty means "no allow-list"; every discovered file is a candidate. */
|
|
372
409
|
include: string[];
|
|
373
410
|
/** The preset that was resolved (detected or forced). */
|
|
@@ -400,6 +437,8 @@ interface CliOptions {
|
|
|
400
437
|
interface ValidationDrift {
|
|
401
438
|
missingInSnapshot: string[];
|
|
402
439
|
missingOnDisk: string[];
|
|
440
|
+
/** Files that exist but could not be read, so their content could not be verified. */
|
|
441
|
+
unreadable: string[];
|
|
403
442
|
hashMismatches: Array<{
|
|
404
443
|
path: string;
|
|
405
444
|
diskHash: string;
|
|
@@ -424,4 +463,4 @@ interface ValidationResult {
|
|
|
424
463
|
warnings: string[];
|
|
425
464
|
}
|
|
426
465
|
|
|
427
|
-
export type { ArtifactSelection as A, ByteSize as B, CliOptions as C, 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,
|
|
466
|
+
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
CHANGED
|
@@ -117,4 +117,6 @@ When you parse the manifest, remember what is conditional: `manifest.files[]` ca
|
|
|
117
117
|
`relativePath`, `layer`, `kind`, `language`, `part`, `inclusionMode` and `hashSha256` for
|
|
118
118
|
every included file, but `summary` and `summaryQuality` only for a language the tool can
|
|
119
119
|
analyse. A `.gitignore` or plain-text entry has neither key, and a file whose summary was too
|
|
120
|
-
large for its part carries `summaryOmitted: true` instead of a `summary`.
|
|
120
|
+
large for its part carries `summaryOmitted: true` instead of a `summary`. `encoding` appears only
|
|
121
|
+
when a file was not plain UTF-8: `utf-16le`/`utf-16be` were transcoded, and `utf-8-lossy` means
|
|
122
|
+
the body you are reading contains `U+FFFD` where undecodable bytes were.
|
package/docs/cli.md
CHANGED
|
@@ -175,6 +175,7 @@ With `--format json` the same answer is one object on stdout, with only the keys
|
|
|
175
175
|
| `layer`, `kind`, `language` | The classification, present only when `included` is `true`. |
|
|
176
176
|
| `layerMatch`, `kindMatch` | Which rule chose the layer or kind. A glob is printed as written (`src/api/**`); a `RegExp` as its literal form (`/\.tsx$/i`); a function matcher, which is what the presets use, as `function (custom predicate)`. Absent when no rule matched and the `other` fallback was used. |
|
|
177
177
|
| `bytes`, `lines` | Size of the file on disk. |
|
|
178
|
+
| `encoding` | Present only when the file is not plain UTF-8 — `utf-16le`, `utf-16be` or `utf-8-lossy`. |
|
|
178
179
|
| `secretCount`, `secrets` | How many secret rules fire, and the first 20 as `{ rule, line }` — never the matched text. |
|
|
179
180
|
| `part` | The part filename from the last written manifest, or `null` when it is not in that snapshot. |
|
|
180
181
|
| `inclusionMode` | `full` or `preview`, when a part entry exists. |
|
|
@@ -193,6 +194,20 @@ Compares two snapshots and lists added, removed and changed files. Either argume
|
|
|
193
194
|
snapshot directory or a `manifest.json`. Useful in review: "what changed in the context we
|
|
194
195
|
hand to the model?"
|
|
195
196
|
|
|
197
|
+
Comparison is per file (`relativePath` + `sha256`), so on its own it cannot see a run that
|
|
198
|
+
repacked the *same* sources differently. `diff` therefore also compares the two manifests'
|
|
199
|
+
`generator.version`, `config.digest`, content fingerprint, part count and layer totals, and
|
|
200
|
+
prints a `payload/configuration differs:` section when the file set is identical but the
|
|
201
|
+
emitted payload — or the configuration that produced it — is not. That covers `--large-file-bytes`
|
|
202
|
+
(a preview becomes a full body), the preview head/tail counts, redaction rules, artifact
|
|
203
|
+
selection and per-layer budgets, none of which move a source hash. The per-file lists and the
|
|
204
|
+
JSON fields `added` / `removed` / `changed` / `unchanged` are unchanged; the extra data is
|
|
205
|
+
under `payload` in `--format json`.
|
|
206
|
+
|
|
207
|
+
Manifests written before 4.0.5 have no `config.digest`. In that case `diff` compares the
|
|
208
|
+
content fingerprint and the part count only, and prints `configuration not compared: a
|
|
209
|
+
manifest has no config digest (written before 4.0.5).` rather than implying the settings match.
|
|
210
|
+
|
|
196
211
|
### `describe`
|
|
197
212
|
|
|
198
213
|
```bash
|
|
@@ -242,13 +257,14 @@ narrowest cases of the same rule.
|
|
|
242
257
|
| `--format <text\|json>` | enum | `text` | every command | `json` writes a pure JSON payload to stdout. |
|
|
243
258
|
| `--dry-run` | flag | off | generate, init | Compute everything, print it, write nothing. |
|
|
244
259
|
| `--watch` | flag | off | generate | Regenerate on every change until interrupted. |
|
|
245
|
-
| `--no-config` | flag | off | generate, validate, report, explain | Ignore every `snapshot.config.*` file and run on the built-in defaults. |
|
|
260
|
+
| `--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. |
|
|
261
|
+
| `--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`. |
|
|
246
262
|
| `--preset <name>` | name | detected | *init only* | Force a preset instead of detecting one. |
|
|
247
263
|
| `--include <globs>` | list | unset | *init only* | Allow-list recorded in the generated config. |
|
|
248
264
|
| `--exclude <globs>` | list | unset | *init only* | Exclude globs recorded in the generated config. |
|
|
249
265
|
| `--yes` | flag | off | *init only* | Never prompt; accept every default. |
|
|
250
266
|
| `--minimal` | flag | off | *init only* | Write only settings that differ from the defaults. |
|
|
251
|
-
| `--force` | flag | off | *init only* |
|
|
267
|
+
| `--force` | flag | off | *init only* | Replace an existing `.ts`/`.js`/`.mjs` config in place. A JSON-family config is refused, naming the file to remove. |
|
|
252
268
|
| `--install` | flag | off | *skill only* | Write the skill into the repository instead of printing it. |
|
|
253
269
|
| `--target <name>` | enum | `all` | *skill only* | `claude`, `agents`, `cursor` or `all`. |
|
|
254
270
|
| `-h`, `--help` | flag | — | every command | Print usage. |
|
|
@@ -263,6 +279,21 @@ config mismatch.
|
|
|
263
279
|
Every option also accepts the `--option=value` form. That form is required for negative
|
|
264
280
|
numbers, which otherwise look like flags.
|
|
265
281
|
|
|
282
|
+
### `--mode`
|
|
283
|
+
|
|
284
|
+
`--mode <command>` names the command instead of passing it positionally, which is handy when
|
|
285
|
+
a wrapper builds the argument list. `--mode validate` and `codebase-snapshot validate` are the
|
|
286
|
+
same run. It accepts the nine command names, and it is deliberately strict: asking for two
|
|
287
|
+
different commands is an error rather than a silent winner.
|
|
288
|
+
|
|
289
|
+
```
|
|
290
|
+
❌ --mode validate contradicts the command "generate". Pass one or the other.
|
|
291
|
+
❌ Unknown --mode "bogus". Expected one of: generate, validate, check, report, init, explain, diff, describe, skill.
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
`--mode` is not listed in `describe`'s option table or `--help`, on purpose: an agent has no
|
|
295
|
+
reason to use it, and the positional form is the documented one.
|
|
296
|
+
|
|
266
297
|
## Exit codes
|
|
267
298
|
|
|
268
299
|
| Code | Meaning |
|
|
@@ -297,7 +328,7 @@ npx @ankurhalder/codebase-snapshot generate --out-dir .context --max-part-bytes
|
|
|
297
328
|
npx @ankurhalder/codebase-snapshot generate --only-artifacts parts,manifest
|
|
298
329
|
|
|
299
330
|
# One file per part, with everything else left alone
|
|
300
|
-
npx @ankurhalder/codebase-snapshot
|
|
331
|
+
npx @ankurhalder/codebase-snapshot generate --only-artifacts parts
|
|
301
332
|
|
|
302
333
|
# Why is this file missing?
|
|
303
334
|
npx @ankurhalder/codebase-snapshot explain src/generated/schema.ts
|
package/docs/configuration.md
CHANGED
|
@@ -64,12 +64,26 @@ see [the trust boundary](../SECURITY.md#trust-boundary).
|
|
|
64
64
|
export default {
|
|
65
65
|
include: [], // allow-list; empty means "everything"
|
|
66
66
|
excludePatterns: [], // extra excludes, applied on top of the preset's
|
|
67
|
+
discovery: "auto", // "auto" | "git" | "walk" — how files are found
|
|
67
68
|
};
|
|
68
69
|
```
|
|
69
70
|
|
|
70
71
|
- `include` is an allow-list of globs. When non-empty, only matching files are considered.
|
|
72
|
+
- `exclude` is accepted as an alias of `excludePatterns` and means the same thing; the resolved
|
|
73
|
+
config always records it under `excludePatterns`.
|
|
71
74
|
- `excludePatterns` removes files everywhere. Presets contribute their own ecosystem ignores
|
|
72
75
|
on top; those are not removable, only addable to.
|
|
76
|
+
- `discovery` chooses how the file list is produced, and is recorded in `manifest.config`, so
|
|
77
|
+
changing it shows up as config drift:
|
|
78
|
+
- `"auto"` (default) — list through `git ls-files` when git can do it; if a `.git` directory
|
|
79
|
+
exists but git cannot list the files, refuse rather than silently switch methods.
|
|
80
|
+
- `"git"` — require git; a directory without a usable repository is an error.
|
|
81
|
+
- `"walk"` — scan the tree and read `.gitignore` files directly (nested ones included), still
|
|
82
|
+
skipping credential files. Use it when a `.git` directory cannot be listed. It cannot
|
|
83
|
+
reproduce git's own ignore sources (`.git/info/exclude`, your global `core.excludesFile`),
|
|
84
|
+
git's inclusion of files that are tracked although a `.gitignore` now matches them, or its
|
|
85
|
+
treatment of a submodule as a single entry. The same setting is available per run as
|
|
86
|
+
[`--discovery`](cli.md#options).
|
|
73
87
|
|
|
74
88
|
Built-in ignores always apply: `.git`, `node_modules`, `.next`, `dist`, `build`, lockfiles,
|
|
75
89
|
minified assets and binaries. Sensitive files are handled separately (see
|
|
@@ -270,14 +284,17 @@ snapshot the marker describes.
|
|
|
270
284
|
export default {
|
|
271
285
|
hooks: {
|
|
272
286
|
afterGenerate: (manifest) => {
|
|
273
|
-
// runs after a
|
|
287
|
+
// runs after a real generate; a dry run does not call it
|
|
274
288
|
},
|
|
275
289
|
},
|
|
276
290
|
};
|
|
277
291
|
```
|
|
278
292
|
|
|
279
293
|
A throwing hook is downgraded to a warning on the manifest — it never loses a snapshot that
|
|
280
|
-
was already written.
|
|
294
|
+
was already written. A hook may return a promise: it is not awaited (this API is synchronous),
|
|
295
|
+
and a rejection is recorded as a warning on the re-shipped manifest and marker. Anything the
|
|
296
|
+
hook writes to stdout is redirected to stderr for the duration of the call, so `--format json`
|
|
297
|
+
stays parseable.
|
|
281
298
|
|
|
282
299
|
A hook may write its own files into the output directory; that is safe across reruns because
|
|
283
300
|
ownership is established by the marker rather than by the filenames present.
|
|
@@ -311,6 +328,7 @@ export default {
|
|
|
311
328
|
// ── Scope ────────────────────────────────────────────────
|
|
312
329
|
include: [],
|
|
313
330
|
excludePatterns: [],
|
|
331
|
+
discovery: "auto", // "auto" | "git" | "walk"
|
|
314
332
|
|
|
315
333
|
// ── Artifacts ────────────────────────────────────────────
|
|
316
334
|
artifacts: {
|
|
@@ -364,5 +382,12 @@ npx codebase-snapshot init --preset go --max-part-bytes 200k \
|
|
|
364
382
|
```
|
|
365
383
|
|
|
366
384
|
`--minimal` writes only what differs from the defaults. `--dry-run` prints the config and
|
|
367
|
-
writes nothing. `--force`
|
|
368
|
-
left alone and the command exits `1`.
|
|
385
|
+
writes nothing. `--force` replaces an existing ES-module config (`.ts`, `.js`, `.mjs`) in
|
|
386
|
+
place; without it, an existing config is left alone and the command exits `1`.
|
|
387
|
+
|
|
388
|
+
It will not replace a JSON-family config — `snapshot.config.json`, `.snapshotrc`,
|
|
389
|
+
`.snapshotrc.json`, or the `"snapshot"` key in `package.json` — because this wizard writes an
|
|
390
|
+
ES-module file and a new file beside the old one would silently shadow it. Those cases exit `1`
|
|
391
|
+
with a message naming the file to delete or rename. Note that this means `--force` writing
|
|
392
|
+
`snapshot.config.ts` does shadow a `"snapshot"` key in `package.json`, since a dedicated config
|
|
393
|
+
file always wins.
|
package/docs/install.md
CHANGED
|
@@ -53,13 +53,15 @@ most reproducible option:
|
|
|
53
53
|
```json
|
|
54
54
|
{
|
|
55
55
|
"scripts": {
|
|
56
|
-
"snapshot": "codebase-snapshot generate",
|
|
57
|
-
"snapshot:validate": "codebase-snapshot validate"
|
|
56
|
+
"snapshot": "npx @ankurhalder/codebase-snapshot generate",
|
|
57
|
+
"snapshot:validate": "npx @ankurhalder/codebase-snapshot validate"
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
`codebase-snapshot init` writes these entries for you
|
|
62
|
+
`codebase-snapshot init` writes these entries for you, in exactly this `npx` form: the bare
|
|
63
|
+
`codebase-snapshot` command only exists on `PATH` inside a client runner or a script that has
|
|
64
|
+
already resolved the package, and `init` does not assume the install has happened yet.
|
|
63
65
|
|
|
64
66
|
## One-off runners for other clients
|
|
65
67
|
|
package/docs/output.md
CHANGED
|
@@ -129,9 +129,18 @@ values — the hash is the whole 64-character digest, never elided.
|
|
|
129
129
|
- Only `isPreviewTruncated` tells you content is missing; `isLarge` alone does not.
|
|
130
130
|
- `hashSha256` is the hash of the source file on disk — `validate` recomputes it to detect
|
|
131
131
|
drift. It is the full 64-character digest.
|
|
132
|
+
- `encoding` is present **only** when the file is not plain UTF-8; its absence means clean UTF-8.
|
|
133
|
+
`utf-16le` and `utf-16be` mean the file was transcoded to UTF-8 before it was emitted, so a
|
|
134
|
+
UTF-16 source file is readable in the parts instead of vanishing as "binary". `utf-8-lossy`
|
|
135
|
+
means the file is not valid UTF-8 at all: its undecodable byte spans became `U+FFFD`, the count
|
|
136
|
+
is reported in `warnings`, and the file is kept rather than dropped. The hash still covers the
|
|
137
|
+
raw bytes, so a lossy file validates as current — `encoding` is how you learn that the body you
|
|
138
|
+
are reading is not byte-exact.
|
|
132
139
|
- `summary` and `summaryQuality` are **conditional**. Both keys are present for a language
|
|
133
140
|
with an analyser and both are absent for a shapeless one — a `.gitignore`, a `.txt`. Treat a
|
|
134
|
-
missing `summary` as "no structural information", not as an empty one.
|
|
141
|
+
missing `summary` as "no structural information", not as an empty one. A file larger than
|
|
142
|
+
2 MB gets no summary at all: parsing it costs more memory than the file itself, and its body is
|
|
143
|
+
a head/tail preview at that size anyway. `manifest.warnings` names those files.
|
|
135
144
|
- `summary` has one of three shapes. Code files get the thirteen keys above (`directives`,
|
|
136
145
|
`imports`, `exports`, `reExports`, `functions`, `classes`, `interfaces`, `types`, `enums`,
|
|
137
146
|
`variables`, `reactComponents`, `hooks`, `routeHandlers`), plus `parseErrors` when the parser
|
|
@@ -160,12 +169,13 @@ Reasons, and what each one means:
|
|
|
160
169
|
| `default-ignore` | A built-in rule caught it, the preset's ecosystem ignores did (`node_modules`, `__pycache__`, `target`, …), or it sat inside an output directory. |
|
|
161
170
|
| `sensitive-file` | Its name is credential-bearing (`.env`, `*.pem`, `.npmrc`, …). |
|
|
162
171
|
| `sensitive-path` | The path itself matched a secret rule. The file is skipped and the path is masked in every artifact. |
|
|
163
|
-
| `binary` | It is not text. |
|
|
172
|
+
| `binary` | It is not text: it holds a NUL byte outside a UTF-16 stream, or more than 1% control characters (a 4 KB blob of `0x07` is valid UTF-8 but is not text). `detail` carries the size and, when there is one to give, the reason: the control-byte count (`Size: 13 B; 5 control byte(s)`), or the offset of a NUL found past the 8 MB head scan (`Size: 9000010 B; NUL byte at offset 9000000`). A NUL inside the head scan is caught by the cheap extension-and-prefix check, so its `detail` is the size alone. |
|
|
164
173
|
| `symlink` | It is a link, and links are not followed. A directory link names its target in `detail`. |
|
|
165
174
|
| `outside-root` | It is a link whose target resolves outside the scanned root. |
|
|
166
175
|
| `gitignore` | A `.gitignore` excluded it, in a scan that is not reading git's index. |
|
|
167
176
|
| `read-error` | It could not be read. |
|
|
168
|
-
| `not-file` | It is a directory, socket or FIFO. |
|
|
177
|
+
| `not-file` | It is a directory, socket or FIFO. A directory that belongs to another repository is reported as `nested-repository` instead. |
|
|
178
|
+
| `nested-repository` | It is a nested checkout or a submodule: a directory whose contents are tracked by a different repository, so they are not part of this codebase. |
|
|
169
179
|
|
|
170
180
|
Every path the scanner discovers is recorded — nothing is dropped silently, in either discovery
|
|
171
181
|
mode. Inside a git repository the candidate list comes from git, which applies `.gitignore`
|
|
@@ -191,14 +201,17 @@ example). See [Troubleshooting](troubleshooting.md#a-file-is-missing-from-the-sn
|
|
|
191
201
|
replacement is complete.
|
|
192
202
|
- **The tool refuses to replace a directory it did not write.** Pointing `--out-dir` at
|
|
193
203
|
`src/` is refused with exit `1` and the directory is left untouched — a mistyped output
|
|
194
|
-
path cannot delete source code. Empty directories, directories
|
|
195
|
-
`.
|
|
196
|
-
|
|
204
|
+
path cannot delete source code. Empty directories, directories that hold nothing but
|
|
205
|
+
`.DS_Store`, and directories holding a complete snapshot this build wrote are accepted.
|
|
206
|
+
A `.gitkeep` or `.gitignore` inside the directory is not accepted, because the generator never
|
|
207
|
+
writes either into an output directory: a directory that holds one is somebody's data, so the
|
|
208
|
+
run refuses instead of deleting it.
|
|
197
209
|
- **Ownership is the marker, not a filename.** `.codebase-snapshot` is a JSON record carrying
|
|
198
210
|
this generator's name and `version`, the marker's `schemaVersion`, a `createdAt` timestamp,
|
|
199
|
-
a per-snapshot `id`, the run `state` (`staging` while it is being written, `complete`
|
|
200
|
-
is whole), the `manifestSha256` digest of the manifest it shipped, and `entries` — the
|
|
201
|
-
inventory of everything it wrote.
|
|
211
|
+
a `pid`, a per-snapshot `id`, the run `state` (`staging` while it is being written, `complete`
|
|
212
|
+
once it is whole), the `manifestSha256` digest of the manifest it shipped, and `entries` — the
|
|
213
|
+
inventory of everything it wrote. `pid` is what lets a later run tell a crashed run's staging
|
|
214
|
+
directory and lock from a live one. A zero-byte marker, a marker that is not JSON, a marker
|
|
202
215
|
written by 3.0.0 (no `id`), a bare `manifest.json`, and a directory with files you added
|
|
203
216
|
after the run are all refused.
|
|
204
217
|
See [Troubleshooting](troubleshooting.md#refusing-to-replace-outdir-).
|
package/docs/troubleshooting.md
CHANGED
|
@@ -83,8 +83,13 @@ and the digest of the manifest it shipped. A file with the right name is not eno
|
|
|
83
83
|
| carries our marker but you added files | `it carries this generator's ownership marker, but these entries are not part of the snapshot it recorded: custom.txt. Remove them yourself, or point outDir at a new or empty directory.` |
|
|
84
84
|
|
|
85
85
|
**Fix** — point `--out-dir` somewhere new, or delete the directory yourself. Do not delete
|
|
86
|
-
your source. Accepted without complaint: a missing directory, an empty directory, one
|
|
87
|
-
|
|
86
|
+
your source. Accepted without complaint: a missing directory, an empty directory, one holding
|
|
87
|
+
nothing but `.DS_Store`, and a complete snapshot this build wrote.
|
|
88
|
+
|
|
89
|
+
`.gitkeep` and `.gitignore` are **not** accepted, even in a directory that holds nothing else.
|
|
90
|
+
Both are files you create — the generator never writes either into an output directory — so a
|
|
91
|
+
directory holding one is somebody's data and is refused rather than replaced. Move the file out,
|
|
92
|
+
or point `--out-dir` somewhere new.
|
|
88
93
|
|
|
89
94
|
A snapshot written by 3.0.0 or earlier is **not** upgraded in place: its marker has three keys
|
|
90
95
|
and no `id`, so the new build cannot prove it wrote the directory and refuses. Delete the old
|
|
@@ -113,6 +118,44 @@ settings and still report success.
|
|
|
113
118
|
loadable on your Node version: a `.ts` config needs Node ≥ 22.6 **and** an ES-module project.
|
|
114
119
|
`codebase-snapshot init` picks the right filename for the project automatically.
|
|
115
120
|
|
|
121
|
+
### `This directory is a git repository, but git could not list its files.`
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
❌ Failed to generate snapshot: This directory is a git repository, but git could not list its files. A plain walk honours .gitignore files, but git also applies .git/info/exclude and your global excludes file, includes files that are tracked although a .gitignore now matches them, and treats a submodule as a single entry. Fix the repository (git status should work), run outside git, or pass --discovery walk to accept those differences.
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
`git` is present and a `.git` directory exists, but it cannot list the files — an empty or
|
|
128
|
+
half-initialised `.git`, a permissions problem, or a damaged index. The tool refuses rather
|
|
129
|
+
than silently choosing between two discovery methods: which files are in the snapshot would
|
|
130
|
+
otherwise depend on whether git happened to work.
|
|
131
|
+
|
|
132
|
+
**Fix** — make `git status` work in that directory (repair or re-clone the repository), generate
|
|
133
|
+
from a copy that has no `.git` directory, or pass `--discovery walk` to scan the tree anyway.
|
|
134
|
+
The walk reads every `.gitignore` it meets, nested ones included, and still skips credential
|
|
135
|
+
files; what it cannot reproduce is git's own ignore sources (`.git/info/exclude` and your global
|
|
136
|
+
`core.excludesFile`), git's inclusion of files that are tracked although a `.gitignore` now
|
|
137
|
+
matches them, and its treatment of a submodule as one entry instead of a tree.
|
|
138
|
+
|
|
139
|
+
`--discovery git` says the opposite — require git — so it refuses in a directory that has no
|
|
140
|
+
usable repository:
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
❌ Failed to generate snapshot: Discovery mode is `git`, but git could not list this directory's files. Run inside a working repository, or pass --discovery walk (or auto) to scan the tree directly.
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### `Another generate run is already using the staging directory …`
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
❌ Failed to generate snapshot: Another generate run is already using the staging directory /repo/snapshot.tmp. Wait for it to finish, or delete /repo/codebase-snapshot-1a2b3c4d5e6f7890.lock if no other run is active.
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
A second `generate` against the same repository started while the first still held the staging
|
|
153
|
+
directory — two terminals, or a CI job and a developer at once. The lock is advisory and on
|
|
154
|
+
disk, so a run that was killed cannot release it.
|
|
155
|
+
|
|
156
|
+
**Fix** — wait for the other run, then retry. If no other run is active, the named lock file is
|
|
157
|
+
stale: delete it, and the next run proceeds.
|
|
158
|
+
|
|
116
159
|
### `Refusing to generate: …`
|
|
117
160
|
|
|
118
161
|
```
|
|
@@ -195,7 +238,7 @@ npx codebase-snapshot explain src/generated/schema.ts
|
|
|
195
238
|
| :--- | :--- | :--- |
|
|
196
239
|
| `sensitive-file` | `.env`, `*.pem`, `.npmrc`, `.ssh/`, `.tfstate`, … | `--include-sensitive-files` if you really want it. |
|
|
197
240
|
| `sensitive-path` | The path itself matched a secret rule, so the file is skipped and its path is masked in every artifact. | Rename the file; a path that looks like a key is treated as one. |
|
|
198
|
-
| `binary` | Not text. | Nothing to do. |
|
|
241
|
+
| `binary` | Not text: it contains a NUL byte outside a UTF-16 stream, or is more than 1% control characters. The `detail` names the file size and the reason — including the offset of the first NUL when that byte sits past the 8 MB head scan. | Nothing to do. A UTF-16 file is *not* binary — it is transcoded and reported with `encoding`, not skipped. |
|
|
199
242
|
| `default-ignore` | A built-in rule, the preset's ecosystem ignores (`node_modules`, `__pycache__`, `target`, `vendor`, …), or the output directory. | A preset's ignores are not configurable; a custom `excludePatterns` can only remove more. |
|
|
200
243
|
| `excluded` | Your own `excludePatterns`, or a path outside a non-empty `include` allow-list. | Remove the pattern, or widen `include`. |
|
|
201
244
|
| `symlink` | Links are not followed (a directory link names its target in `detail`). | Snapshot the target. |
|
|
@@ -203,6 +246,7 @@ npx codebase-snapshot explain src/generated/schema.ts
|
|
|
203
246
|
| `gitignore` | A `.gitignore` excluded it, in a scan that is not reading git's index. | Remove the entry, or add the path to `include`. |
|
|
204
247
|
| `read-error` | Unreadable file. | Check permissions. |
|
|
205
248
|
| `not-file` | A directory, socket, FIFO. | Expected. |
|
|
249
|
+
| `nested-repository` | A nested checkout or submodule — a directory tracked by a different repository. | Expected; snapshot that repository separately if you need its code. |
|
|
206
250
|
|
|
207
251
|
Inside a git repository the candidate list comes from git, which applies `.gitignore` before
|
|
208
252
|
the scanner runs; those ignored paths are listed separately and recorded too, so a `.gitignore`d
|
|
@@ -238,6 +282,19 @@ under a `- Warnings: N` line in the summary, and none of them stops a run:
|
|
|
238
282
|
- `every artifact is disabled: only the .codebase-snapshot ownership marker will be written.`
|
|
239
283
|
- A `hooks.afterGenerate` error is downgraded to a warning: the snapshot it was called for is
|
|
240
284
|
already written and is not lost.
|
|
285
|
+
- `N file(s) exceed 2 MB and were not parsed for a structural summary: …` — the named files are
|
|
286
|
+
emitted as head/tail previews without an `imports`/`exports` summary. Parsing them costs several
|
|
287
|
+
times their own size in memory, which is how a single 50 MB source file used to reach 3.3 GB of
|
|
288
|
+
resident memory. Nothing to fix; raise the part budget if the preview is too small.
|
|
289
|
+
- `N file(s) are UTF-16LE and were transcoded to UTF-8: …` — those files were UTF-16 and are
|
|
290
|
+
now ordinary text in the snapshot. They are listed in `manifest.files[]` with
|
|
291
|
+
`encoding: "utf-16le"`, or `"utf-16be"`. Nothing to fix; the warning exists because the bytes
|
|
292
|
+
in the artifact are not the bytes on disk.
|
|
293
|
+
- `N file(s) are not valid UTF-8; M undecodable byte(s) became U+FFFD: …` — the named files are
|
|
294
|
+
not UTF-8 at all (Latin-1 is the usual cause). They are kept, with `encoding: "utf-8-lossy"`,
|
|
295
|
+
but the undecodable spans are substituted, so **do not hand the emitted body to a model as if
|
|
296
|
+
it were the source**. Convert the file to UTF-8 (`iconv -f WINDOWS-1252 -t UTF-8 file > file.tmp`)
|
|
297
|
+
and regenerate. The snapshot still validates as current, because the hash covers the raw bytes.
|
|
241
298
|
|
|
242
299
|
`validate` produces its own warnings, printed with a `⚠ ` prefix and never written to the
|
|
243
300
|
manifest, because they describe the state of the repository rather than the snapshot:
|