@ankurhalder/codebase-snapshot 3.0.0 → 4.0.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 +161 -310
- package/dist/bin.js +1939 -570
- package/dist/{chunk-5B4UVQLR.js → chunk-FM4OOQPF.js} +1558 -390
- package/dist/cli.cjs +1907 -537
- package/dist/cli.d.cts +3 -1
- package/dist/cli.d.ts +3 -1
- package/dist/cli.js +265 -64
- package/dist/index.cjs +1557 -387
- package/dist/index.d.cts +22 -28
- package/dist/index.d.ts +22 -28
- package/dist/index.js +1 -1
- package/dist/{types-CJqkePM4.d.cts → types-BdpT_4b8.d.cts} +37 -4
- package/dist/{types-CJqkePM4.d.ts → types-BdpT_4b8.d.ts} +37 -4
- package/docs/agent.md +77 -37
- package/docs/cli.md +315 -0
- package/docs/configuration.md +254 -226
- package/docs/install.md +114 -86
- package/docs/output.md +213 -0
- package/docs/troubleshooting.md +277 -0
- package/package.json +7 -4
package/docs/configuration.md
CHANGED
|
@@ -1,126 +1,146 @@
|
|
|
1
1
|
# Configuration reference
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Every setting, every default, and what each one changes. For commands and options see the
|
|
4
|
+
[CLI reference](cli.md).
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
npx @ankurhalder/codebase-snapshot init # writes a starter config
|
|
7
|
-
```
|
|
6
|
+
Nothing here is required: with no config at all the tool runs on the defaults below.
|
|
8
7
|
|
|
9
|
-
|
|
8
|
+
## Contents
|
|
10
9
|
|
|
11
|
-
|
|
10
|
+
- [Config files](#config-files)
|
|
11
|
+
- [Scoping: which files get in](#scoping-which-files-get-in)
|
|
12
|
+
- [Project presets](#project-presets)
|
|
13
|
+
- [Classification: layers and kinds](#classification-layers-and-kinds)
|
|
14
|
+
- [Sizing: byte budgets](#sizing-byte-budgets)
|
|
15
|
+
- [Artifacts: what gets written](#artifacts-what-gets-written)
|
|
16
|
+
- [Secrets: redaction](#secrets-redaction)
|
|
17
|
+
- [Output directories](#output-directories)
|
|
18
|
+
- [Hooks](#hooks)
|
|
19
|
+
- [Complete example](#complete-example)
|
|
20
|
+
- [Interactive setup](#interactive-setup)
|
|
12
21
|
|
|
13
|
-
|
|
22
|
+
## Config files
|
|
14
23
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
## Scoping: which files get in
|
|
24
|
+
The first match wins:
|
|
18
25
|
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
26
|
+
```
|
|
27
|
+
snapshot.config.ts
|
|
28
|
+
snapshot.config.js
|
|
29
|
+
snapshot.config.mjs
|
|
30
|
+
snapshot.config.cjs
|
|
31
|
+
snapshot.config.json
|
|
32
|
+
.snapshotrc / .snapshotrc.json
|
|
33
|
+
package.json → "snapshot"
|
|
24
34
|
```
|
|
25
35
|
|
|
26
|
-
`
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
but not `docsite/c.md`.
|
|
36
|
+
`snapshot.config.json` accepts comments. `package.json`'s `"snapshot"` key is read only when
|
|
37
|
+
no dedicated config file exists, and it must be an object (a wrong type is a warning, since
|
|
38
|
+
other tools use that key too).
|
|
30
39
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
`
|
|
40
|
+
The extension decides how the file is loaded. `.cjs` is always CommonJS and must use
|
|
41
|
+
`module.exports`; `.mjs` and `.ts` are always ESM and must use `export default`. `.js` follows
|
|
42
|
+
the package's `type` field — `module` means ESM, `commonjs` means CommonJS; with no `type`
|
|
43
|
+
Node decides by detecting the syntax, so `export default` works on current Node but is not
|
|
44
|
+
portable to versions without detection. Getting it wrong is the `Could not load …` error
|
|
45
|
+
above, not a silent fallback.
|
|
34
46
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
includeSensitiveFiles: false, // .env, *.pem, .npmrc, .ssh/, .tfstate …
|
|
38
|
-
};
|
|
39
|
-
```
|
|
47
|
+
A config file that exists but cannot be parsed is a **hard error** (exit `1`, nothing
|
|
48
|
+
written). Silently ignoring it would produce a snapshot with none of your settings.
|
|
40
49
|
|
|
41
|
-
|
|
50
|
+
A `.ts` config needs Node ≥ 22.6 **and** an ES-module project, because Node applies the
|
|
51
|
+
package's `type` field to a `.ts` file. `codebase-snapshot init` picks the right filename for
|
|
52
|
+
the project — `.ts` when both conditions hold, `.mjs` otherwise — and writes a plain object
|
|
53
|
+
with no imports so it loads even when the package is only used through `npx`.
|
|
54
|
+
|
|
55
|
+
CLI flags override the config for a single run (`--max-part-bytes`, `--out-dir`, …).
|
|
56
|
+
`--no-config` goes further and ignores every config file, so the run uses the built-in
|
|
57
|
+
defaults and the project's detected preset. A code config is executed when it is loaded, so
|
|
58
|
+
`--no-config` is also the way to snapshot a repository whose config you do not want to run —
|
|
59
|
+
see [the trust boundary](../SECURITY.md#trust-boundary).
|
|
60
|
+
|
|
61
|
+
## Scoping: which files get in
|
|
42
62
|
|
|
43
|
-
|
|
44
|
-
recommended part budget. Detection reads the repository **once** and the first
|
|
45
|
-
matching preset wins, ordered by specificity — so a Next.js app inside a pnpm
|
|
46
|
-
workspace is still a Next.js project.
|
|
47
|
-
|
|
48
|
-
| Preset | Detected from |
|
|
49
|
-
| :--- | :--- |
|
|
50
|
-
| `nextjs` | `next.config.*`, or `next` |
|
|
51
|
-
| `nuxt` | `nuxt.config.*`, or `nuxt` |
|
|
52
|
-
| `sveltekit` | `svelte.config.*`, `@sveltejs/kit` |
|
|
53
|
-
| `remix` | `@remix-run/*` |
|
|
54
|
-
| `astro` | `astro.config.*`, `astro` |
|
|
55
|
-
| `angular` | `angular.json`, `@angular/core` |
|
|
56
|
-
| `solid` | `solid-js` |
|
|
57
|
-
| `vue-vite` | `vue` + vite |
|
|
58
|
-
| `react-vite` | `react` + vite |
|
|
59
|
-
| `nest` | `@nestjs/core` |
|
|
60
|
-
| `node-express` | `express`, `fastify`, `koa` |
|
|
61
|
-
| `node-lib` | any `package.json` |
|
|
62
|
-
| `python-django` | `manage.py`, or django in the manifest |
|
|
63
|
-
| `python-fastapi` | fastapi / flask in the manifest |
|
|
64
|
-
| `python-lib` | `pyproject.toml`, `setup.py`, `.py` files |
|
|
65
|
-
| `go` | `go.mod` |
|
|
66
|
-
| `rust` | `Cargo.toml` |
|
|
67
|
-
| `java-spring` | `pom.xml`, `build.gradle` |
|
|
68
|
-
| `dotnet` | `*.csproj`, `*.sln` |
|
|
69
|
-
| `php-laravel` | `artisan`, `composer.json` |
|
|
70
|
-
| `ruby-rails` | `Gemfile`, `config.ru` |
|
|
71
|
-
| `monorepo` | `pnpm-workspace.yaml`, `workspaces`, `nx.json`, `turbo.json` |
|
|
72
|
-
| `infra` | `*.tf`, `Chart.yaml`, `docker-compose.yml` |
|
|
73
|
-
| `docs` | mostly Markdown, no `package.json` |
|
|
74
|
-
| `generic` | anything else |
|
|
75
|
-
|
|
76
|
-
Force one when detection is wrong:
|
|
77
|
-
|
|
78
|
-
```javascript
|
|
63
|
+
```js
|
|
79
64
|
export default {
|
|
80
|
-
|
|
65
|
+
include: [], // allow-list; empty means "everything"
|
|
66
|
+
excludePatterns: [], // extra excludes, applied on top of the preset's
|
|
81
67
|
};
|
|
82
68
|
```
|
|
83
69
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
recorded in `manifest.config.preset`, so changing it shows up as config drift.
|
|
70
|
+
- `include` is an allow-list of globs. When non-empty, only matching files are considered.
|
|
71
|
+
- `excludePatterns` removes files everywhere. Presets contribute their own ecosystem ignores
|
|
72
|
+
on top; those are not removable, only addable to.
|
|
88
73
|
|
|
89
|
-
|
|
74
|
+
Built-in ignores always apply: `.git`, `node_modules`, `.next`, `dist`, `build`, lockfiles,
|
|
75
|
+
minified assets and binaries. Sensitive files are handled separately (see
|
|
76
|
+
[Secrets](#secrets-redaction)).
|
|
90
77
|
|
|
91
|
-
|
|
78
|
+
Every path the scanner discovers is recorded in `manifest.skipped[]` with the reason and the
|
|
79
|
+
rule that matched — nothing it sees is dropped silently, including a `.gitignore`d path in a
|
|
80
|
+
repository where git does the filtering. See the [`skipped[]`
|
|
81
|
+
reasons](output.md#skipped). To debug one path:
|
|
92
82
|
|
|
93
83
|
```bash
|
|
94
|
-
|
|
84
|
+
npx codebase-snapshot explain src/some/file.ts
|
|
95
85
|
```
|
|
96
86
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
|
106
|
-
|
|
|
107
|
-
|
|
|
108
|
-
|
|
|
109
|
-
|
|
|
110
|
-
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
87
|
+
## Project presets
|
|
88
|
+
|
|
89
|
+
Detection reads the repository **once** and picks the highest-priority match, so a Next.js
|
|
90
|
+
app inside a pnpm workspace is still `nextjs`, not `monorepo`. `peerDependencies` and
|
|
91
|
+
`optionalDependencies` count as dependencies. A bare `package.json` ranks below any
|
|
92
|
+
definitive language manifest, so a Go or Rust repository with a `package.json` for tooling is
|
|
93
|
+
not mistaken for a Node library.
|
|
94
|
+
|
|
95
|
+
| Preset | Priority | Detected from | Budget | Ecosystem ignores |
|
|
96
|
+
| :--- | ---: | :--- | :--- | :--- |
|
|
97
|
+
| `nextjs` | 100 | `next.config.*`, or `next` | 300k | `.svelte-kit`, `.astro`, `.vite`, `.output` |
|
|
98
|
+
| `nuxt` | 98 | `nuxt.config.*`, or `nuxt` | 200k | `.nuxt`, `.output` |
|
|
99
|
+
| `sveltekit` | 96 | `svelte.config.*`, `@sveltejs/kit` | 200k | `.svelte-kit`, `.vercel` |
|
|
100
|
+
| `remix` | 94 | `@remix-run/*` | 200k | `.cache`, `public/build` |
|
|
101
|
+
| `astro` | 92 | `astro.config.*`, `astro` | 150k | `.astro`, `dist` |
|
|
102
|
+
| `angular` | 90 | `angular.json`, `@angular/core` | 200k | `.angular`, `dist` |
|
|
103
|
+
| `solid` | 88 | `solid-js`, `solid-start` | 120k | `.solid`, `dist` |
|
|
104
|
+
| `vue-vite` | 86 | `vue` + `vite`/`vite.config.*` | 120k | `.vite`, `dist` |
|
|
105
|
+
| `react-vite` | 84 | `react` + `vite`/`vite.config.*` | 120k | `.vite`, `dist` |
|
|
106
|
+
| `nest` | 80 | `@nestjs/core` | 200k | `dist`, `node_modules` |
|
|
107
|
+
| `node-express` | 78 | `express`, `fastify`, `koa`, `@hapi/hapi` | 200k | `dist` |
|
|
108
|
+
| `monorepo` | 70 | `pnpm-workspace.yaml`, `nx.json`, `turbo.json`, `lerna.json`, `"workspaces"` | 200k | `.turbo`, `.nx` |
|
|
109
|
+
| `python-django` | 56 | `manage.py`, django in `pyproject.toml`/`requirements.txt` | 150k | `__pycache__`, `.venv`, `venv`, `.mypy_cache`, `.pytest_cache`, `.tox`, `.eggs`, `staticfiles`, `media` |
|
|
110
|
+
| `python-fastapi` | 54 | fastapi or flask in `pyproject.toml`/`requirements.txt` | 150k | `__pycache__`, `.venv`, `venv`, `.mypy_cache`, `.pytest_cache`, `.tox`, `.eggs` |
|
|
111
|
+
| `go` | 50 | `go.mod` | 200k | `vendor`, `testdata` |
|
|
112
|
+
| `rust` | 48 | `Cargo.toml` | 200k | `target` |
|
|
113
|
+
| `java-spring` | 46 | `pom.xml`, `build.gradle`, `build.gradle.kts` | 200k | `.gradle`, `out`, `target` |
|
|
114
|
+
| `node-lib` | 45 | any `package.json` | 120k | `dist` |
|
|
115
|
+
| `dotnet` | 44 | `*.csproj`, `*.sln`, `global.json` | 200k | `bin`, `obj`, `.vs` |
|
|
116
|
+
| `php-laravel` | 42 | `artisan`, laravel in `composer.json`, `composer.json` | 150k | `vendor`, `storage`, `bootstrap/cache`, `public/build` |
|
|
117
|
+
| `ruby-rails` | 40 | `Gemfile`, `config.ru`, `Rakefile` | 150k | `.bundle`, `log`, `tmp`, `vendor/bundle`, `public/assets` |
|
|
118
|
+
| `python-lib` | 30 | `pyproject.toml`, `setup.py`, `requirements.txt`, `.py` files | 120k | `__pycache__`, `.venv`, `venv`, `.mypy_cache`, `.pytest_cache`, `.tox`, `.eggs` |
|
|
119
|
+
| `infra` | 24 | `*.tf`, `Chart.yaml`, `kustomization.yaml`, `docker-compose.yml` | 100k | `.terraform`, `.terragrunt-cache` |
|
|
120
|
+
| `docs` | 22 | ≥ 3 markdown files and no `package.json` | 100k | `.docusaurus`, `.vitepress/dist`, `site` |
|
|
121
|
+
| `generic` | 0 | anything else | 120k | — |
|
|
122
|
+
|
|
123
|
+
A preset contributes layers, kinds, ignores and a recommended budget — what `init` writes as
|
|
124
|
+
`maxPartBytes`. Force one when detection is wrong:
|
|
125
|
+
|
|
126
|
+
```js
|
|
127
|
+
export default { preset: "react-vite" };
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
An unknown name is an error listing every valid preset, never a silent fallback. The resolved
|
|
131
|
+
preset is recorded in `manifest.config.preset`, so changing it shows up as config drift.
|
|
132
|
+
|
|
116
133
|
## Classification: layers and kinds
|
|
117
134
|
|
|
118
|
-
|
|
135
|
+
Layers group files (roughly directories), kinds label their role. Both are first-match-wins
|
|
136
|
+
with `other` as the fallback, and both accept a glob string, a `RegExp`, or a function.
|
|
137
|
+
|
|
138
|
+
```js
|
|
119
139
|
export default {
|
|
120
|
-
layersMode: "merge",
|
|
140
|
+
layersMode: "merge", // "merge" (default) or "replace"
|
|
121
141
|
layers: [
|
|
122
|
-
{ name: "
|
|
123
|
-
{ name: "
|
|
142
|
+
{ name: "api", match: "src/api/**" },
|
|
143
|
+
{ name: "ui", match: /\.(tsx|vue|svelte)$/ },
|
|
124
144
|
],
|
|
125
145
|
kinds: [
|
|
126
146
|
{ kind: "route", match: /(^|\/)route\.tsx?$/ },
|
|
@@ -129,115 +149,124 @@ export default {
|
|
|
129
149
|
};
|
|
130
150
|
```
|
|
131
151
|
|
|
132
|
-
|
|
133
|
-
|
|
152
|
+
A layer rule is `{ name, match }` and a kind rule is `{ kind, match }`. The label key is
|
|
153
|
+
different in each, and a row that gets it wrong is rejected rather than silently ignored, so
|
|
154
|
+
copy the shapes above.
|
|
134
155
|
|
|
135
|
-
`layersMode: "merge"`
|
|
136
|
-
|
|
156
|
+
- `layersMode: "merge"` keeps the preset's rules and lets yours win by being evaluated first.
|
|
157
|
+
- `layersMode: "replace"` discards the preset's rules entirely.
|
|
137
158
|
|
|
138
|
-
|
|
139
|
-
and `generic` is the fallback. There are 25 of them — see the table above. A framework
|
|
140
|
-
that depends on `vite` still resolves to `react-vite` / `vue-vite` because those detectors
|
|
141
|
-
outrank `node-lib`, and a Next.js app inside a pnpm workspace is still `nextjs`, not
|
|
142
|
-
`monorepo`.
|
|
159
|
+
Classification drives per-layer budgets and the `Layers:` breakdown in `report`.
|
|
143
160
|
|
|
144
161
|
## Sizing: byte budgets
|
|
145
162
|
|
|
146
|
-
```
|
|
163
|
+
```js
|
|
147
164
|
export default {
|
|
148
|
-
maxPartBytes: "
|
|
149
|
-
largeFileBytes: "200k",
|
|
165
|
+
maxPartBytes: "300k", // hard ceiling per part; default 307200, minimum 4096
|
|
166
|
+
largeFileBytes: "200k", // above this a file becomes a head/tail preview
|
|
150
167
|
previewHeadLines: 80,
|
|
151
168
|
previewTailLines: 40,
|
|
152
|
-
|
|
153
|
-
budgets: { // optional per-layer overrides
|
|
169
|
+
budgets: { // optional per-layer overrides
|
|
154
170
|
default: "120k",
|
|
155
171
|
byLayer: { docs: 40_000, core: 300_000 },
|
|
156
172
|
},
|
|
157
173
|
};
|
|
158
174
|
```
|
|
159
175
|
|
|
160
|
-
|
|
161
|
-
|
|
176
|
+
Sizes accept bytes (`307200`), `"120k"`, `"1.5mb"`, and spaces (`"120 k"`). Zero and negative
|
|
177
|
+
values are errors; so is anything below 4096 — a part budget that small cannot hold a header,
|
|
178
|
+
let alone code.
|
|
162
179
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
## Artifacts: what gets written
|
|
180
|
+
The budget is a hard ceiling: a part never exceeds it. A single file larger than the budget is
|
|
181
|
+
not split mid-file — it becomes a head/tail preview, marked `isPreviewTruncated`.
|
|
167
182
|
|
|
168
|
-
|
|
169
|
-
`artifacts` in config, or `--skip-artifacts` / `--only-artifacts` on the CLI.
|
|
183
|
+
`budgets.byLayer` lets expensive layers get more room than generated docs, for example.
|
|
170
184
|
|
|
171
|
-
|
|
172
|
-
| :--- | :--- | :--- |
|
|
173
|
-
| `readme` | on | `README.md` |
|
|
174
|
-
| `manifest` | on | `manifest.json` — **off breaks `validate` and `report`** |
|
|
175
|
-
| `parts` | on | `parts/part-XXX.md` |
|
|
176
|
-
| `navigation` | on | `reports/navigation.md` — dangles if `parts` is off |
|
|
177
|
-
| `projectTree` | on | `reports/project-tree.md` |
|
|
178
|
-
| `audit` | on | `reports/audit.md` |
|
|
179
|
-
| `monolith` | off | `full-codebase.{json,md}` |
|
|
185
|
+
## Artifacts: what gets written
|
|
180
186
|
|
|
181
|
-
```
|
|
187
|
+
```js
|
|
182
188
|
export default {
|
|
183
|
-
artifacts: {
|
|
184
|
-
|
|
185
|
-
|
|
189
|
+
artifacts: {
|
|
190
|
+
readme: true, // snapshot/README.md
|
|
191
|
+
manifest: true, // snapshot/manifest.json
|
|
192
|
+
parts: true, // snapshot/parts/*.md
|
|
193
|
+
navigation: true, // snapshot/reports/navigation.md
|
|
194
|
+
projectTree: true, // snapshot/reports/project-tree.md
|
|
195
|
+
audit: true, // snapshot/reports/audit.md
|
|
196
|
+
monolith: false, // full-codebase.json + full-codebase.md
|
|
197
|
+
},
|
|
186
198
|
};
|
|
187
199
|
```
|
|
188
200
|
|
|
189
|
-
|
|
190
|
-
|
|
201
|
+
Everything except `monolith` is on by default; `monolith` follows `includeMonolith`. Turn
|
|
202
|
+
artifacts off with the config, `--skip-artifacts a,b`, or `--only-artifacts a,b` (the two
|
|
203
|
+
cannot be combined).
|
|
204
|
+
|
|
205
|
+
Two combinations warn because they produce a snapshot that contradicts itself: `manifest` off
|
|
206
|
+
means `validate` and `report` cannot inspect the snapshot, and `parts` off with `navigation`
|
|
207
|
+
on leaves links pointing at files that were never written. Both are allowed.
|
|
191
208
|
|
|
192
|
-
The
|
|
193
|
-
file even when a part had to truncate it.
|
|
209
|
+
The `.codebase-snapshot` ownership marker is always written, whatever the selection.
|
|
194
210
|
|
|
195
|
-
|
|
211
|
+
See [Snapshot output](output.md) for the layout and the manifest schema.
|
|
196
212
|
|
|
197
|
-
|
|
198
|
-
OpenAI/Anthropic, npm, PyPI, SendGrid, Twilio, DigitalOcean, Azure, database
|
|
199
|
-
URIs, basic-auth URLs and `.env`-style assignments.
|
|
213
|
+
## Secrets: redaction
|
|
200
214
|
|
|
201
|
-
```
|
|
215
|
+
```js
|
|
202
216
|
export default {
|
|
203
217
|
secrets: {
|
|
204
218
|
customPatterns: [
|
|
219
|
+
{ rule: "github-pat", pattern: /ghp_[A-Za-z0-9]{36}/ },
|
|
205
220
|
{ rule: "internal-token", pattern: /CORP-[A-Z0-9]{32}/ },
|
|
206
|
-
{ rule: "from-json", pattern: "ACME_[a-z0-9]{10}" }, // strings allowed
|
|
207
221
|
],
|
|
208
222
|
disableDefaultPatterns: false,
|
|
209
223
|
},
|
|
224
|
+
includeSensitiveFiles: false, // .env, *.pem, .npmrc, .ssh/, .tfstate …
|
|
210
225
|
};
|
|
211
226
|
```
|
|
212
227
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
228
|
+
Each entry is `{ rule, pattern }`, where `rule` is the name that appears in the audit and
|
|
229
|
+
`pattern` is a `RegExp`, or a string in a `.json` config. A bare `RegExp` in the array works
|
|
230
|
+
too — it comes out as `custom-1`, `custom-2`, … in the findings — but naming the rule is what
|
|
231
|
+
makes an audit entry mean something.
|
|
232
|
+
|
|
233
|
+
- Credential-bearing **files** are skipped by default and listed in `skipped[]` with reason
|
|
234
|
+
`sensitive-file`. `includeSensitiveFiles: true` emits them.
|
|
235
|
+
- Secret-shaped **strings** inside ordinary files are redacted in the emitted parts. Only the
|
|
236
|
+
matched span is masked, never the whole line, and no excerpt of a matched line reaches any
|
|
237
|
+
artifact. Multi-line PEM keys are masked as a block.
|
|
238
|
+
- `customPatterns` add rules; `disableDefaultPatterns` removes the built-in set — it does not
|
|
239
|
+
disable redaction itself.
|
|
216
240
|
|
|
217
|
-
|
|
241
|
+
Findings are summarised in `reports/audit.md` and in `report`.
|
|
218
242
|
|
|
219
|
-
|
|
243
|
+
## Output directories
|
|
244
|
+
|
|
245
|
+
```js
|
|
220
246
|
export default {
|
|
221
247
|
outDir: "snapshot",
|
|
222
248
|
tmpDir: "snapshot.tmp",
|
|
223
|
-
branch: "main", // fallback when git cannot report one
|
|
224
249
|
};
|
|
225
250
|
```
|
|
226
251
|
|
|
227
|
-
`outDir` may not be the working directory, an ancestor, or the filesystem root,
|
|
228
|
-
|
|
229
|
-
snapshot survives until the new one is
|
|
252
|
+
`outDir` may not be the working directory, an ancestor, or the filesystem root, and
|
|
253
|
+
`outDir`/`tmpDir` may not contain one another — the message prints the resolved paths. Writes
|
|
254
|
+
are atomic: the previous snapshot survives until the new one is complete.
|
|
255
|
+
|
|
256
|
+
Both directories are also **refused if they already exist and this generator did not write
|
|
257
|
+
them**, so `outDir: "src"` cannot replace your source tree. Ownership is proved by the
|
|
258
|
+
`.codebase-snapshot` marker and nothing else: a JSON object naming this generator, with a
|
|
259
|
+
schema version this build understands, a per-snapshot `id`, and the list of entries it wrote.
|
|
260
|
+
A file with that name but no valid marker, or a directory holding a manifest from another
|
|
261
|
+
tool, is refused. The proof is never a filename, so an existing `docs/README.md` is safe.
|
|
230
262
|
|
|
231
|
-
|
|
232
|
-
not
|
|
233
|
-
|
|
234
|
-
parseable `manifest.json` for older output), never by filenames, so an existing
|
|
235
|
-
`docs/README.md` is never mistaken for a previous snapshot. An empty directory,
|
|
236
|
-
or one holding only `.gitkeep`/`.gitignore`/`.DS_Store`, is always accepted.
|
|
263
|
+
Files your hook writes into the output directory are recorded in the marker's entry list, so
|
|
264
|
+
they do not block later runs; a file you add by hand afterwards does, because it is not in the
|
|
265
|
+
snapshot the marker describes.
|
|
237
266
|
|
|
238
267
|
## Hooks
|
|
239
268
|
|
|
240
|
-
```
|
|
269
|
+
```js
|
|
241
270
|
export default {
|
|
242
271
|
hooks: {
|
|
243
272
|
afterGenerate: (manifest) => {
|
|
@@ -247,94 +276,93 @@ export default {
|
|
|
247
276
|
};
|
|
248
277
|
```
|
|
249
278
|
|
|
250
|
-
A throwing hook
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
A hook may write its own files into the output directory. That is safe across
|
|
254
|
-
reruns: ownership of the directory is established by a `.codebase-snapshot`
|
|
255
|
-
marker written before any artifact, not by the set of filenames present, so
|
|
256
|
-
custom artifacts never look like someone else's data.
|
|
279
|
+
A throwing hook is downgraded to a warning on the manifest — it never loses a snapshot that
|
|
280
|
+
was already written.
|
|
257
281
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
not the target repo. Use the absolute paths on `manifest` (or resolve against
|
|
261
|
-
`outDir` yourself) when passing `--cwd`.
|
|
282
|
+
A hook may write its own files into the output directory; that is safe across reruns because
|
|
283
|
+
ownership is established by the marker rather than by the filenames present.
|
|
262
284
|
|
|
263
|
-
|
|
285
|
+
A hook runs in the **process** working directory, so with `--cwd elsewhere` a relative path
|
|
286
|
+
resolves against your shell, not the target repository. Use the absolute paths on `manifest`
|
|
287
|
+
(or resolve against `outDir` yourself) when passing `--cwd`.
|
|
264
288
|
|
|
265
|
-
|
|
289
|
+
## Complete example
|
|
266
290
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
| `generate` *(default)* | Write the snapshot |
|
|
270
|
-
| `validate`, `check` | Re-hash sources and parts, compare config, detect drift |
|
|
271
|
-
| `report` | Print metrics, layer distribution and drift status |
|
|
272
|
-
| `explain <path>` | Show how one path would be treated |
|
|
273
|
-
| `diff <a> <b>` | Compare two snapshots |
|
|
274
|
-
| `init` | Write a starter config |
|
|
291
|
+
Every setting at its built-in default. Nothing here has to be written — the tool runs on these
|
|
292
|
+
values with no config at all.
|
|
275
293
|
|
|
276
|
-
|
|
294
|
+
```js
|
|
295
|
+
export default {
|
|
296
|
+
// ── Project ──────────────────────────────────────────────
|
|
297
|
+
preset: "detected", // "detected" auto-detects; or name one of the 25
|
|
298
|
+
branch: "main", // recorded when git cannot report one
|
|
277
299
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
src/app/page.tsx
|
|
282
|
-
|
|
283
|
-
included yes
|
|
284
|
-
layer app (rule (p) => p.startsWith("src/app/") || ...)
|
|
285
|
-
kind component (rule (p) => { const base = p.split("/").pop() ...)
|
|
286
|
-
language tsx
|
|
287
|
-
size 48 B, 1 line(s)
|
|
288
|
-
part part-001.md
|
|
289
|
-
inclusion full
|
|
290
|
-
secrets none
|
|
291
|
-
```
|
|
300
|
+
// ── Output ───────────────────────────────────────────────
|
|
301
|
+
outDir: "snapshot",
|
|
302
|
+
tmpDir: "snapshot.tmp",
|
|
292
303
|
|
|
293
|
-
|
|
304
|
+
// ── Size ─────────────────────────────────────────────────
|
|
305
|
+
maxPartBytes: "300k",
|
|
306
|
+
largeFileBytes: "200k",
|
|
307
|
+
previewHeadLines: 80,
|
|
308
|
+
previewTailLines: 40,
|
|
309
|
+
budgets: { default: "300k", byLayer: {} },
|
|
310
|
+
|
|
311
|
+
// ── Scope ────────────────────────────────────────────────
|
|
312
|
+
include: [],
|
|
313
|
+
excludePatterns: [],
|
|
314
|
+
|
|
315
|
+
// ── Artifacts ────────────────────────────────────────────
|
|
316
|
+
artifacts: {
|
|
317
|
+
readme: true,
|
|
318
|
+
manifest: true,
|
|
319
|
+
parts: true,
|
|
320
|
+
navigation: true,
|
|
321
|
+
projectTree: true,
|
|
322
|
+
audit: true,
|
|
323
|
+
monolith: false,
|
|
324
|
+
},
|
|
294
325
|
|
|
295
|
-
|
|
296
|
-
|
|
326
|
+
// ── Safety ───────────────────────────────────────────────
|
|
327
|
+
includeSensitiveFiles: false,
|
|
328
|
+
secrets: { customPatterns: [], disableDefaultPatterns: false },
|
|
297
329
|
|
|
298
|
-
|
|
330
|
+
// ── Classification ───────────────────────────────────────
|
|
331
|
+
layersMode: "merge",
|
|
332
|
+
layers: [],
|
|
333
|
+
kinds: [],
|
|
299
334
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
335
|
+
// ── Advanced ─────────────────────────────────────────────
|
|
336
|
+
includeMonolith: false,
|
|
337
|
+
maxMonolithBytes: null,
|
|
338
|
+
hooks: {},
|
|
339
|
+
};
|
|
303
340
|
```
|
|
304
341
|
|
|
305
|
-
|
|
306
|
-
same data as an object.
|
|
307
|
-
|
|
308
|
-
## `diff`
|
|
342
|
+
Deleting any line is safe — the defaults are real, and each one is what the comment says.
|
|
309
343
|
|
|
310
|
-
|
|
311
|
-
|
|
344
|
+
This is **not** byte-identical to a generated file. `codebase-snapshot init` starts from these
|
|
345
|
+
values and substitutes two things: `preset` becomes the detected name (`node-lib` for a plain
|
|
346
|
+
npm package), and `maxPartBytes` with `budgets.default` become that preset's
|
|
347
|
+
`recommendedBudget` (`"120k"` in the same project). `generate` on its own applies no
|
|
348
|
+
recommended budget — without a config it uses `--max-part-bytes`, default 307200 — so the
|
|
349
|
+
preset's figure only takes effect once `init` has written it into a config.
|
|
312
350
|
|
|
313
|
-
|
|
314
|
-
after 2026-11-03T11:30:00.000Z 820 file(s), 4 part(s)
|
|
351
|
+
## Interactive setup
|
|
315
352
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
~ 3 changed
|
|
319
|
-
= 808 unchanged
|
|
353
|
+
```bash
|
|
354
|
+
npx codebase-snapshot init
|
|
320
355
|
```
|
|
321
356
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
## `--watch`
|
|
357
|
+
Walks every tunable parameter, showing the detected value as the default. Every prompt has a
|
|
358
|
+
flag, so the same setup works non-interactively:
|
|
325
359
|
|
|
326
360
|
```bash
|
|
327
|
-
npx
|
|
361
|
+
npx codebase-snapshot init --yes
|
|
362
|
+
npx codebase-snapshot init --preset go --max-part-bytes 200k \
|
|
363
|
+
--include "cmd/**,internal/**" --exclude "**/*_test.go" --yes
|
|
328
364
|
```
|
|
329
365
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
---
|
|
334
|
-
|
|
335
|
-
# Drift
|
|
336
|
-
|
|
337
|
-
`validate` compares five things: source hashes on disk, the emitted parts (size
|
|
338
|
-
and SHA-256), the manifest's own bookkeeping, the recorded configuration
|
|
339
|
-
(budgets, includes, excludes, artifacts, layers mode), and git state. Changing a
|
|
340
|
-
config value without re-generating is reported as drift, not silently accepted.
|
|
366
|
+
`--minimal` writes only what differs from the defaults. `--dry-run` prints the config and
|
|
367
|
+
writes nothing. `--force` overwrites an existing config; without it, an existing config is
|
|
368
|
+
left alone and the command exits `1`.
|