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
package/docs/install.md
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# Installation
|
|
2
|
+
|
|
3
|
+
`codebase-snapshot` is published to the public npm registry, so it installs with
|
|
4
|
+
**any** npm-compatible client. There is no second registry to add and nothing to configure.
|
|
5
|
+
|
|
6
|
+
Requires **Node.js 18 or newer**.
|
|
7
|
+
|
|
8
|
+
## One command
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npx codebase-snapshot setup
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Detects the project, writes the config (an existing one is kept), adds the npm scripts and the
|
|
15
|
+
devDependency, installs them with the package manager your lockfile names, and generates the first
|
|
16
|
+
snapshot. `--no-install` skips the install; `--dry-run` writes nothing. The sections below are the
|
|
17
|
+
same steps taken one at a time.
|
|
18
|
+
## One-off use
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npx codebase-snapshot generate
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
This is the shortest path and needs no install. The command is also available as
|
|
25
|
+
`codebase-snapshot` and, once installed, as the short alias `snap`.
|
|
26
|
+
|
|
27
|
+
Pin the version when you need the same build on every machine:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npx codebase-snapshot@latest generate
|
|
31
|
+
npx codebase-snapshot@0.9.0 generate
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**Why the bare name works.** npm resolves which binary to run from the package's `bin`
|
|
35
|
+
entries. This package declares `codebase-snapshot` and `snap` pointing at the *same file*, so
|
|
36
|
+
npm treats them as aliases and can always resolve one. Keep them aliases: two *different*
|
|
37
|
+
executables under a scoped package name would make the bare form unresolvable.
|
|
38
|
+
|
|
39
|
+
If a command exits with no output at all, a stale global install is the usual cause — see
|
|
40
|
+
[Troubleshooting](#troubleshooting).
|
|
41
|
+
|
|
42
|
+
## Add to a project
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npm install -D codebase-snapshot
|
|
46
|
+
yarn add -D codebase-snapshot # yarn classic and Berry
|
|
47
|
+
pnpm add -D codebase-snapshot
|
|
48
|
+
bun add -d codebase-snapshot
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Then run it through your client so the binary resolves from the project:
|
|
52
|
+
|
|
53
|
+
| Client | Run |
|
|
54
|
+
| :--- | :--- |
|
|
55
|
+
| npm | `npx codebase-snapshot generate` |
|
|
56
|
+
| yarn (classic and Berry) | `yarn codebase-snapshot generate` |
|
|
57
|
+
| pnpm | `pnpm exec codebase-snapshot generate` |
|
|
58
|
+
| bun | `bun run codebase-snapshot generate` |
|
|
59
|
+
|
|
60
|
+
`snap` works everywhere `codebase-snapshot` does. Adding it to `package.json` scripts is the
|
|
61
|
+
most reproducible option:
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"scripts": {
|
|
66
|
+
"snapshot": "npx codebase-snapshot generate",
|
|
67
|
+
"snapshot:validate": "npx codebase-snapshot validate"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`codebase-snapshot init` writes these entries for you, in exactly this `npx` form: the bare
|
|
73
|
+
`codebase-snapshot` command only exists on `PATH` inside a client runner or a script that has
|
|
74
|
+
already resolved the package, and `init` does not assume the install has happened yet.
|
|
75
|
+
|
|
76
|
+
## One-off runners for other clients
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
yarn dlx -p codebase-snapshot codebase-snapshot generate # yarn Berry
|
|
80
|
+
pnpm dlx codebase-snapshot generate
|
|
81
|
+
bunx codebase-snapshot generate
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Agent skill
|
|
85
|
+
|
|
86
|
+
To make a repository discoverable by an AI agent — so it finds this tool without being told
|
|
87
|
+
it exists — run:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
npx codebase-snapshot skill --install
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
That writes `.claude/skills/codebase-snapshot/SKILL.md` and
|
|
94
|
+
`.cursor/rules/codebase-snapshot.mdc`, and adds both to `.gitignore` so the install leaves
|
|
95
|
+
nothing untracked. It does not touch `AGENTS.md`: that file is your project's own agent
|
|
96
|
+
contract. To put the skill there, print it and paste it —
|
|
97
|
+
`npx codebase-snapshot skill >> AGENTS.md`. See
|
|
98
|
+
[Using this with an AI agent](agent.md).
|
|
99
|
+
|
|
100
|
+
## Verified clients
|
|
101
|
+
|
|
102
|
+
Every client below installs the published package and runs it successfully; the release check
|
|
103
|
+
enforces this on every pull request, so a change that breaks one fails the build rather than
|
|
104
|
+
a user's terminal.
|
|
105
|
+
|
|
106
|
+
| Client | Run installed binary with |
|
|
107
|
+
| :--- | :--- |
|
|
108
|
+
| npm 10–11 | `npx codebase-snapshot` |
|
|
109
|
+
| yarn classic 1.22 | `yarn codebase-snapshot` |
|
|
110
|
+
| yarn Berry 4.6 (`nodeLinker: pnp`) | `yarn codebase-snapshot` |
|
|
111
|
+
| pnpm 10 | `pnpm exec codebase-snapshot` |
|
|
112
|
+
| bun 1.4 | `bun run codebase-snapshot` |
|
|
113
|
+
|
|
114
|
+
Yarn Berry's default **strict PnP** works: there is no `node_modules` directory, and the
|
|
115
|
+
runtime `typescript` dependency still resolves through yarn's `.pnp.cjs` loader.
|
|
116
|
+
|
|
117
|
+
## Before scanning a repository you do not own
|
|
118
|
+
|
|
119
|
+
A `snapshot.config.ts`, `.js`, `.mjs` or `.cjs` in the target repository is **imported and
|
|
120
|
+
executed** when the tool runs there — that is what lets a config express a `RegExp` or a
|
|
121
|
+
`match` function. Pointing `--cwd` at a checkout you do not trust therefore runs its code.
|
|
122
|
+
`--no-config` ignores every config file and uses the built-in defaults plus the detected
|
|
123
|
+
preset, and JSON configs are parsed rather than executed. See
|
|
124
|
+
[the trust boundary](../SECURITY.md#trust-boundary).
|
|
125
|
+
|
|
126
|
+
## Global install
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
npm install -g codebase-snapshot
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Convenient, but prefer pinning per project: snapshots are only reproducible if the generator
|
|
133
|
+
version is. A global install also shadows `npx` for this package — npm will run your global
|
|
134
|
+
copy instead of the published one, which is confusing when the two differ. That is exactly
|
|
135
|
+
the situation described under [Troubleshooting](#troubleshooting).
|
|
136
|
+
|
|
137
|
+
## Workspaces
|
|
138
|
+
|
|
139
|
+
The binary resolves from the workspace it is installed in. In a monorepo, install it in the
|
|
140
|
+
package that runs the snapshot (usually the root), or use a one-off runner
|
|
141
|
+
(`npx` / `pnpm dlx` / `yarn dlx` / `bunx`) which works from any subdirectory.
|
|
142
|
+
|
|
143
|
+
## Upgrading
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
npm install -D codebase-snapshot@latest
|
|
147
|
+
npx codebase-snapshot generate # rewrite the snapshot with the new version
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
`manifest.generator.version` records which version produced a snapshot. Re-run `generate`
|
|
151
|
+
after an upgrade so the snapshot, and the version it records, match the build you are now
|
|
152
|
+
running.
|
|
153
|
+
|
|
154
|
+
A snapshot written by **3.0.0 or earlier** is refused rather than replaced: its
|
|
155
|
+
`.codebase-snapshot` marker has no per-snapshot id, so the current build cannot prove it wrote
|
|
156
|
+
the directory. Delete the old snapshot directory and generate again.
|
|
157
|
+
|
|
158
|
+
See [CHANGELOG](../CHANGELOG.md) for breaking changes — 3.0.0 removed nothing, but it briefly
|
|
159
|
+
did in error; 4.0.0 restored the `snap` alias.
|
|
160
|
+
|
|
161
|
+
## Uninstalling
|
|
162
|
+
|
|
163
|
+
Standard for your client (`npm uninstall`, `yarn remove`, `pnpm remove`, `bun remove`).
|
|
164
|
+
Snapshots already written to the output directory are ordinary files and are left alone.
|
|
165
|
+
If you installed globally, `npm rm -g codebase-snapshot` also removes the
|
|
166
|
+
shim that shadows `npx`.
|
|
167
|
+
|
|
168
|
+
## Troubleshooting
|
|
169
|
+
|
|
170
|
+
**`codebase-snapshot: command not found`** — when installed locally (not globally) the binary
|
|
171
|
+
lives in `node_modules/.bin` and is only on `PATH` inside a package script or a client
|
|
172
|
+
runner. Use `npx codebase-snapshot`, `pnpm exec codebase-snapshot`,
|
|
173
|
+
`yarn codebase-snapshot` or `bun run codebase-snapshot`.
|
|
174
|
+
|
|
175
|
+
**A command exits 0 and prints nothing** — almost always a stale **global** install
|
|
176
|
+
shadowing `npx`. Check and remove it:
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
npm ls -g --depth=0 | grep codebase-snapshot
|
|
180
|
+
npm rm -g codebase-snapshot
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Then re-run. If the package is not installed globally, clear the npx cache
|
|
184
|
+
(`npm cache clean --force`) and retry.
|
|
185
|
+
|
|
186
|
+
**`Cannot find module 'typescript'`** — the package declares `typescript` as a runtime
|
|
187
|
+
dependency for AST analysis. If your client prunes it (some `--ignore-scripts` CI setups do),
|
|
188
|
+
rerun a clean install or pin `typescript@^5` yourself.
|
|
189
|
+
|
|
190
|
+
**`yarn codebase-snapshot` prints extra lines / breaks a script** — yarn classic wraps
|
|
191
|
+
everything: it echoes `yarn run 1.22.x`, the resolved command, your output, then
|
|
192
|
+
`Done in 0.37s.`. For anything that parses the output, call the binary directly
|
|
193
|
+
(`./node_modules/.bin/codebase-snapshot` or `npx codebase-snapshot`).
|
|
194
|
+
|
|
195
|
+
More symptoms — refusals, config errors, drift, missing files — are in
|
|
196
|
+
[Troubleshooting](troubleshooting.md).
|
package/docs/output.md
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
# Snapshot output
|
|
2
|
+
|
|
3
|
+
A run writes one directory (default `./snapshot`) that is meant to be handed to a model as
|
|
4
|
+
context. It is self-describing: `manifest.json` indexes everything the parts contain and
|
|
5
|
+
explains everything they omit.
|
|
6
|
+
|
|
7
|
+
## Layout
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
snapshot/
|
|
11
|
+
├── .codebase-snapshot ownership marker (see Safety)
|
|
12
|
+
├── README.md human summary of this snapshot
|
|
13
|
+
├── manifest.json machine index: files, parts, config, hashes, skips
|
|
14
|
+
├── parts/
|
|
15
|
+
│ ├── part-001.md the code, grouped and size-budgeted
|
|
16
|
+
│ ├── part-002.md
|
|
17
|
+
│ └── …
|
|
18
|
+
└── reports/
|
|
19
|
+
├── project-tree.md directory tree of included files
|
|
20
|
+
├── navigation.md links between layers, kinds and parts
|
|
21
|
+
└── audit.md secrets, large files, parse diagnostics, git state
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
With `--include-monolith` you also get `full-codebase.json` and `full-codebase.md` — the
|
|
25
|
+
whole repository in one file. They are off by default because they grow without bound;
|
|
26
|
+
`--max-monolith-bytes` skips them when they would exceed a size you set.
|
|
27
|
+
|
|
28
|
+
`full-codebase.json` embeds its own `files[]` array, and its `includedBytes` / `includedLines`
|
|
29
|
+
describe the monolith's raw content — the file as it was read — so they differ from the same
|
|
30
|
+
fields in `manifest.json` for the same file. `manifest.json` reports what the part emitted
|
|
31
|
+
(CRLF normalised, the trailing newline not counted); the monolith reports the content it
|
|
32
|
+
embedded. Compare a file's fields within one artifact, not across the two.
|
|
33
|
+
|
|
34
|
+
The manifest embedded in `full-codebase.json` is otherwise identical to `manifest.json` with one
|
|
35
|
+
impossible field: a file cannot record its own sha256, so `artifacts.fullJson` is `null` in the
|
|
36
|
+
embedded copy and a `selfReference` sentence points at `manifest.json`, which carries the real
|
|
37
|
+
`byteSize` and `sha256` for that file.
|
|
38
|
+
|
|
39
|
+
Every artifact can be turned off. `--skip-artifacts parts` omits the parts,
|
|
40
|
+
`--only-artifacts manifest` writes nothing but the manifest, and `config.artifacts` does the
|
|
41
|
+
same persistently. The ownership marker is always written.
|
|
42
|
+
|
|
43
|
+
## Parts
|
|
44
|
+
|
|
45
|
+
Each `part-NNN.md` holds whole files under the byte budget (`maxPartBytes`, default 300 KB).
|
|
46
|
+
A file larger than the budget is not split mid-file: it is truncated and carries an omission
|
|
47
|
+
marker naming the budget that cut it (`... [347 lines omitted: part budget 1887 B] ...`). A file
|
|
48
|
+
above `largeFileBytes` is emitted as a head/tail excerpt instead, with the same marker. Each file
|
|
49
|
+
section is tagged with its layer, kind, language and a structural summary (imports, exports,
|
|
50
|
+
classes, functions) so a reader can navigate without opening every file.
|
|
51
|
+
|
|
52
|
+
Parts are packed in a stable order, so the same input produces the same content — which is
|
|
53
|
+
what makes `manifest.fingerprint` meaningful.
|
|
54
|
+
|
|
55
|
+
Two runs over unchanged input are **not** byte-identical everywhere, and the difference is
|
|
56
|
+
deliberate bookkeeping rather than content. `manifest.json`, the snapshot's `README.md`,
|
|
57
|
+
`reports/audit.md` and `.codebase-snapshot` differ on every run — they carry a timestamp, and the
|
|
58
|
+
marker also carries a fresh run id and pid. `parts/*.md`, `reports/navigation.md` and
|
|
59
|
+
`reports/project-tree.md` are **byte-identical** across runs, and `manifest.fingerprint` is stable.
|
|
60
|
+
So: diff the parts, the navigation index or the tree to see whether the *content* changed, and
|
|
61
|
+
diff the manifest only when you need to know that a run happened.
|
|
62
|
+
|
|
63
|
+
## Manifest
|
|
64
|
+
|
|
65
|
+
`manifest.json` is the index and the contract between runs. Top-level keys:
|
|
66
|
+
|
|
67
|
+
| Key | Contents |
|
|
68
|
+
| :--- | :--- |
|
|
69
|
+
| `schemaVersion` | Manifest format version (`3.0.0`). |
|
|
70
|
+
| `generatedAt` | ISO timestamp of the run. |
|
|
71
|
+
| `generator` | `{ name, version }` of the tool that wrote it. |
|
|
72
|
+
| `git` | `{ branch, commit, isDirty }`. `branch` falls back to the configured branch on a detached HEAD and outside a repository; `commit` is `"unknown"` and `isDirty` is `false` when git cannot report. A `false` there means "not known to be dirty", not "clean tree" — check `commit` to tell the two apart. |
|
|
73
|
+
| `budgets` | The effective byte limits for the run: `{ maxPartBytes, largeFileBytes }`. The per-layer overrides live at `config.budgets`, which is `{ default, byLayer }`. |
|
|
74
|
+
| `config` | The resolved configuration actually used (preset, artifacts, sizing, scoping). |
|
|
75
|
+
| `counts` | `{ totalFiles, totalSizeBytes, totalLines, partsCount, skippedCount, largeFileCount, truncatedPreviewCount }`. |
|
|
76
|
+
| `totals` | Bookkeeping: `{ candidateFiles, includedFiles, fullFiles, previewFiles, skippedFiles, totalSourceBytes, totalContentBytes, partCount, warningCount, redactedLines }`. `redactedLines` counts the lines whose values were masked — one per line, not one per matching rule — and is what the summary's `Redacted:` line prints. |
|
|
77
|
+
| `fingerprint` | Stable hash of the snapshot content. Two runs over unchanged input produce the same value. |
|
|
78
|
+
| `validation` | `{ status: "passed" \| "failed", errors: [] }` for the run's own integrity check. |
|
|
79
|
+
| `artifacts` | The optional monolith files: `{ fullJson, fullMarkdown }`. Each value is `null` when that file was not written, otherwise `{ byteSize, sha256 }`. The object itself is always present. |
|
|
80
|
+
| `layers`, `kinds` | File counts per layer and per kind. |
|
|
81
|
+
| `indexes` | Cross-references: `routes`, `components`, `handlers`, `features`, `imports`, `directives`. |
|
|
82
|
+
| `parts` | One entry per part (below). |
|
|
83
|
+
| `files` | One entry per included file (below). |
|
|
84
|
+
| `skipped` | One entry per excluded file (below). |
|
|
85
|
+
| `warnings` | Non-fatal observations that may change how you read the snapshot — a disabled artifact, a file too large to parse, a transcoded file, a throwing `hooks.afterGenerate`. Masked values are **not** warnings: they are counted in `totals.redactedLines` and listed per line in `reports/audit.md`. |
|
|
86
|
+
|
|
87
|
+
### `parts[]`
|
|
88
|
+
|
|
89
|
+
```json
|
|
90
|
+
{
|
|
91
|
+
"index": 1,
|
|
92
|
+
"filename": "part-001.md",
|
|
93
|
+
"byteSize": 1432,
|
|
94
|
+
"sha256": "3472b9edcd504403f2416606e4222f326ec207d7916bf928ade4ec7de1406d2d",
|
|
95
|
+
"lines": 91,
|
|
96
|
+
"fileCount": 2,
|
|
97
|
+
"layersCovered": ["root", "src"],
|
|
98
|
+
"includedFiles": [
|
|
99
|
+
{ "relativePath": "package.json", "sourceBytes": 35, "includedBytes": 35, "lines": 1, "layer": "root", "kind": "data" },
|
|
100
|
+
{ "relativePath": "src/math.ts", "sourceBytes": 63, "includedBytes": 63, "lines": 3, "layer": "src", "kind": "other" }
|
|
101
|
+
]
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`byteSize` and `sha256` are what `validate` checks against the file on disk. Both are full
|
|
106
|
+
values — the hash is the whole 64-character digest, never elided.
|
|
107
|
+
|
|
108
|
+
### `files[]`
|
|
109
|
+
|
|
110
|
+
```json
|
|
111
|
+
{
|
|
112
|
+
"relativePath": "src/math.ts",
|
|
113
|
+
"sizeBytes": 63,
|
|
114
|
+
"lines": 3,
|
|
115
|
+
"hashSha256": "aa229260647049a42a0446f48f1b34f19657921b7a0a27a41c0b2a294a25c919",
|
|
116
|
+
"part": "part-001.md",
|
|
117
|
+
"layer": "src",
|
|
118
|
+
"kind": "other",
|
|
119
|
+
"language": "typescript",
|
|
120
|
+
"inclusionMode": "full",
|
|
121
|
+
"isLarge": false,
|
|
122
|
+
"isPreviewTruncated": false,
|
|
123
|
+
"originalLines": 3,
|
|
124
|
+
"includedLines": 3,
|
|
125
|
+
"includedBytes": 63,
|
|
126
|
+
"summary": {
|
|
127
|
+
"directives": [], "imports": [], "exports": ["add"], "reExports": [],
|
|
128
|
+
"functions": ["add"], "classes": [], "interfaces": [], "types": [],
|
|
129
|
+
"enums": [], "variables": [], "reactComponents": [], "hooks": [],
|
|
130
|
+
"routeHandlers": []
|
|
131
|
+
},
|
|
132
|
+
"summaryQuality": "ast"
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
- `isLarge` is a fact about the file: it is bigger than `largeFileBytes` (default 200 KB).
|
|
137
|
+
A large file is emitted as a head/tail excerpt rather than inlined whole.
|
|
138
|
+
- `isPreviewTruncated` is a fact about what was written: the body in this snapshot is **not**
|
|
139
|
+
the whole file, because lines were dropped.
|
|
140
|
+
- `includedLines` counts content lines after CRLF normalisation and without a trailing empty
|
|
141
|
+
element, which is what the fenced block holds. An empty file therefore records `0` while the
|
|
142
|
+
fence shows one empty line, and a file ending in a blank line records one fewer than a reader
|
|
143
|
+
counts — the number describes content, not rendering.
|
|
144
|
+
- `inclusionMode` is `preview` exactly when `isPreviewTruncated` is `true`, and there are two
|
|
145
|
+
routes to it:
|
|
146
|
+
- **Over `largeFileBytes`** — the head/tail excerpt drops the middle, so
|
|
147
|
+
`isLarge: true`, `isPreviewTruncated: true`. If the head and tail happen to cover every
|
|
148
|
+
line, nothing is dropped and the entry stays `isLarge: true`, `inclusionMode: "full"`.
|
|
149
|
+
- **Under `largeFileBytes` but too big for its part** — the partitioner byte-truncates the
|
|
150
|
+
section to keep the part inside its budget, so `isLarge: false`,
|
|
151
|
+
`isPreviewTruncated: true`. This is the one that surprises people: the file is not "large"
|
|
152
|
+
by the configured threshold, yet its body is incomplete.
|
|
153
|
+
- Only `isPreviewTruncated` tells you content is missing; `isLarge` alone does not.
|
|
154
|
+
- `hashSha256` is the hash of the source file on disk — `validate` recomputes it to detect
|
|
155
|
+
drift. It is the full 64-character digest.
|
|
156
|
+
- `encoding` is present **only** when the file is not plain UTF-8; its absence means clean UTF-8.
|
|
157
|
+
`utf-16le` and `utf-16be` mean the file was transcoded to UTF-8 before it was emitted, so a
|
|
158
|
+
UTF-16 source file is readable in the parts instead of vanishing as "binary". `utf-8-lossy`
|
|
159
|
+
means the file is not valid UTF-8 at all: its undecodable byte spans became `U+FFFD`, the count
|
|
160
|
+
is reported in `warnings`, and the file is kept rather than dropped. The hash still covers the
|
|
161
|
+
raw bytes, so a lossy file validates as current — `encoding` is how you learn that the body you
|
|
162
|
+
are reading is not byte-exact.
|
|
163
|
+
- `summary` and `summaryQuality` are **conditional**. Both keys are present for a language
|
|
164
|
+
with an analyser and both are absent for a shapeless one — a `.gitignore`, a `.txt`. Treat a
|
|
165
|
+
missing `summary` as "no structural information", not as an empty one. A file larger than
|
|
166
|
+
2 MB gets no summary at all: parsing it costs more memory than the file itself, and its body is
|
|
167
|
+
a head/tail preview at that size anyway. `manifest.warnings` names those files.
|
|
168
|
+
- `summary` has one of three shapes. Code files get the thirteen keys above (`directives`,
|
|
169
|
+
`imports`, `exports`, `reExports`, `functions`, `classes`, `interfaces`, `types`, `enums`,
|
|
170
|
+
`variables`, `reactComponents`, `hooks`, `routeHandlers`), plus `parseErrors` when the parser
|
|
171
|
+
reported syntax errors. JSON gets `{ shape, itemCount, keys, sampleKeys }` and Markdown gets
|
|
172
|
+
`{ headings, frontmatterKeys }` — a shape description rather than a code summary.
|
|
173
|
+
- `summaryOmitted: true` means the file's summary was dropped because the summary alone would
|
|
174
|
+
not fit the part budget. The entry then has `summaryQuality` but no `summary`, and the
|
|
175
|
+
file's body is still emitted in full. It is only ever present when `true`.
|
|
176
|
+
- `summaryQuality` is `ast` (parsed by the TypeScript compiler), `structural` (JSON or
|
|
177
|
+
Markdown shape) or `heuristic` (pattern-matched). It tells you how much to trust the
|
|
178
|
+
summary: `heuristic` means a line scan, not a parse.
|
|
179
|
+
- The summary block is written **as-is** into the part, so a control or bidi character that
|
|
180
|
+
appears in a source file can appear inside the fenced JSON too — the same reason the file
|
|
181
|
+
body is emitted raw. Names are escaped where a path is *displayed* (part headers, the tree,
|
|
182
|
+
navigation, audit, `explain`), but the two raw regions — the summary fence and the body —
|
|
183
|
+
reproduce the repository's bytes. Treat the whole part as untrusted data, not just the body.
|
|
184
|
+
|
|
185
|
+
Querying this array is usually faster and cheaper than re-reading the parts.
|
|
186
|
+
|
|
187
|
+
### `skipped[]`
|
|
188
|
+
|
|
189
|
+
```json
|
|
190
|
+
{ "path": ".env", "reason": "sensitive-file", "detail": "credential-bearing file; enable includeSensitiveFiles to emit it" }
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Reasons, and what each one means:
|
|
194
|
+
|
|
195
|
+
| `reason` | The file was dropped because |
|
|
196
|
+
| :--- | :--- |
|
|
197
|
+
| `excluded` | It matched your own `excludePatterns`, or fell outside a non-empty `include` allow-list. |
|
|
198
|
+
| `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. |
|
|
199
|
+
| `sensitive-file` | Its name is credential-bearing (`.env`, `*.pem`, `.npmrc`, …). |
|
|
200
|
+
| `sensitive-path` | The path itself matched a secret rule. The file is skipped and the path is masked in every artifact. |
|
|
201
|
+
| `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. |
|
|
202
|
+
| `symlink` | It is a link, and links are not followed. A directory link names its target in `detail`. |
|
|
203
|
+
| `outside-root` | It is a link whose target resolves outside the scanned root. |
|
|
204
|
+
| `gitignore` | A `.gitignore` excluded it, in a scan that is not reading git's index. |
|
|
205
|
+
| `read-error` | It could not be read. |
|
|
206
|
+
| `not-file` | It is a directory, socket or FIFO. A directory that belongs to another repository is reported as `nested-repository` instead. |
|
|
207
|
+
| `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. |
|
|
208
|
+
|
|
209
|
+
Every path the scanner discovers is recorded — nothing is dropped silently, in either discovery
|
|
210
|
+
mode. Inside a git repository the candidate list comes from git, which applies `.gitignore`
|
|
211
|
+
before the scanner runs; the ignored paths are then listed separately and recorded here too, so
|
|
212
|
+
a `.gitignore`d file or directory appears in `skipped[]` with reason `gitignore` whether or not
|
|
213
|
+
git is present.
|
|
214
|
+
|
|
215
|
+
`explain` reports two further reasons that never appear here, because they describe a path
|
|
216
|
+
rather than a decision about a candidate file: `missing` (nothing exists at that path) and
|
|
217
|
+
`not-discovered` (the path exists but was never a candidate — inside `node_modules`, for
|
|
218
|
+
example). See [Troubleshooting](troubleshooting.md#a-file-is-missing-from-the-snapshot).
|
|
219
|
+
|
|
220
|
+
**Ignored trees are recorded at two granularities**, which is why `explain` can answer
|
|
221
|
+
`not-discovered` for a directory you can see in `skipped[]`. A *built-in* ignore
|
|
222
|
+
(`node_modules`, `dist`, `.next`, …) is recorded once, as the directory itself, and its contents
|
|
223
|
+
are never enumerated — `explain node_modules/pkg/i.js` inherits that record and says
|
|
224
|
+
`default-ignore`. A *preset ecosystem* ignore (`__pycache__`, `target`, `vendor`, …) is recorded
|
|
225
|
+
per file, so `explain app/__pycache__` — the directory — finds no record of its own and answers
|
|
226
|
+
`not-discovered`, while `explain app/__pycache__/mod.pyc` says `default-ignore`. Neither is an
|
|
227
|
+
omission: the directory and its files are all accounted for, just not at the same level.
|
|
228
|
+
|
|
229
|
+
## Safety
|
|
230
|
+
|
|
231
|
+
- **Sensitive files are excluded by default.** `.env`, `*.pem`, `*.key`, `.npmrc`, `.netrc`,
|
|
232
|
+
`.git-credentials`, `id_rsa`, `.kube/config`, `*.tfstate` and similar are skipped and
|
|
233
|
+
listed in `skipped[]`. `--include-sensitive-files` opts in.
|
|
234
|
+
- **Secret-shaped strings are redacted** inside ordinary source files. Only the matched span
|
|
235
|
+
is masked, never the whole line, and no excerpt of a matched line is written to any
|
|
236
|
+
artifact.
|
|
237
|
+
- **The output directory is owned by the tool.** Each run writes `.codebase-snapshot` into it
|
|
238
|
+
first, then swaps the new snapshot in atomically. A previous snapshot survives until the
|
|
239
|
+
replacement is complete.
|
|
240
|
+
- **The tool refuses to replace a directory it did not write.** Pointing `--out-dir` at
|
|
241
|
+
`src/` is refused with exit `1` and the directory is left untouched — a mistyped output
|
|
242
|
+
path cannot delete source code. Empty directories, directories that hold nothing but
|
|
243
|
+
`.DS_Store`, and directories holding a complete snapshot this build wrote are accepted.
|
|
244
|
+
A `.gitkeep` or `.gitignore` inside the directory is not accepted, because the generator never
|
|
245
|
+
writes either into an output directory: a directory that holds one is somebody's data, so the
|
|
246
|
+
run refuses instead of deleting it.
|
|
247
|
+
- **Ownership is the marker, not a filename.** `.codebase-snapshot` is a JSON record carrying
|
|
248
|
+
this generator's name and `version`, the marker's `schemaVersion`, a `createdAt` timestamp,
|
|
249
|
+
a `pid`, a per-snapshot `id`, the run `state` (`staging` while it is being written, `complete`
|
|
250
|
+
once it is whole), the `manifestSha256` digest of the manifest it shipped, and `entries` — the
|
|
251
|
+
inventory of everything it wrote. `pid` is what lets a later run tell a crashed run's staging
|
|
252
|
+
directory and lock from a live one. A zero-byte marker, a marker that is not JSON, a marker
|
|
253
|
+
written by 3.0.0 (no `id`), a bare `manifest.json`, and a directory with files you added
|
|
254
|
+
after the run are all refused.
|
|
255
|
+
See [Troubleshooting](troubleshooting.md#refusing-to-replace-outdir-).
|
|
256
|
+
|
|
257
|
+
The marker also makes hook-written artifacts safe: the entries a `hooks.afterGenerate` adds to
|
|
258
|
+
the output are recorded in the marker, so they do not make the next run refuse the directory.
|
|
259
|
+
|
|
260
|
+
## Committing the snapshot
|
|
261
|
+
|
|
262
|
+
Usually you do not. Add the output directory to `.gitignore` — `codebase-snapshot init` does
|
|
263
|
+
it for you. If you do commit snapshots (for example, to diff context across releases),
|
|
264
|
+
`manifest.json` is the file that matters: it is compact and `diff` can compare two of them.
|