@ankurhalder/codebase-snapshot 4.0.6 → 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/dist/index.d.cts CHANGED
@@ -1,5 +1,70 @@
1
- import { J as JsonSummary, M as MarkdownSummary, S as SourceSummary, R as ResolvedSnapshotConfig, a as SnapshotConfig, B as ByteSize, C as CliOptions, F as FileEntry, P as PartMetadata, L as LayerRule, K as KindRule, b as SecretRule, c as SecretFinding, V as ValidationResult, d as Manifest, A as ArtifactSelection, e as SkipRecord } from './types-x7aogSer.cjs';
2
- export { f as BudgetConfig, H as HooksConfig, I as InclusionMode, g as ManifestArtifact, h as ManifestConfigSnapshot, i as ManifestIndexes, j as ManifestTotals, k as Mode, O as OutputFormat, l as PartFileEntry, m as ResolvedArtifacts, n as ResolvedBudgets, o as SecretRuleInput, p as SkipReason, q as SummaryQuality, r as ValidationDrift } from './types-x7aogSer.cjs';
1
+ import { S as SecretRule, D as DiscoveryMode, a as SkipRecord, J as JsonSummary, M as MarkdownSummary, b as SourceSummary, R as ResolvedSnapshotConfig, c as SnapshotConfig, B as ByteSize, C as CliOptions, F as FileEntry, P as PartMetadata, L as LayerRule, K as KindRule, T as TextEncoding, d as SecretFinding, V as ValidationResult, e as Manifest, A as ArtifactSelection } from './types-BMP676ER.cjs';
2
+ export { f as BudgetConfig, H as HooksConfig, I as InclusionMode, g as ManifestArtifact, h as ManifestConfigSnapshot, i as ManifestIndexes, j as ManifestTotals, k as Mode, O as OutputFormat, l as PartFileEntry, m as ResolvedArtifacts, n as ResolvedBudgets, o as SecretRuleInput, p as SkipReason, q as SummaryQuality, r as ValidationDrift } from './types-BMP676ER.cjs';
3
+
4
+ interface CollectOptions {
5
+ /**
6
+ * When non-empty, only files matching at least one pattern are candidates.
7
+ * Applied before `excludePatterns`, which then narrows the result further.
8
+ */
9
+ includePatterns?: string[];
10
+ excludePatterns?: string[];
11
+ /**
12
+ * Emit credential-bearing files (`.env`, `*.pem`, `.npmrc`, `id_rsa`, …).
13
+ * Default `false`: they are skipped with `reason: "sensitive-file"`.
14
+ */
15
+ includeSensitiveFiles?: boolean;
16
+ /**
17
+ * Secret rules used to scan *paths* (defaults are always applied). Pass
18
+ * `config.secretRules` so `customPatterns` also protect file names.
19
+ */
20
+ secretRules?: readonly SecretRule[];
21
+ /**
22
+ * Top-level directory names this run is about to write — `outDir` and `tmpDir`.
23
+ *
24
+ * They are excluded from the scan without a `skipped[]` record because that is this tool's own
25
+ * bookkeeping and a first run cannot have written a marker into them yet. A directory with one
26
+ * of these *names* that carries no marker is still excluded when it is one of these paths, but
27
+ * is an ordinary directory when it is not: dropping it silently was the bug.
28
+ */
29
+ toolOwnedPaths?: readonly string[];
30
+ /**
31
+ * How candidates are discovered: `auto` (git when usable, refusal when the
32
+ * directory claims to be a repository but git cannot list it), `git` (require
33
+ * git), or `walk` (walk the tree, reading the root `.gitignore` directly).
34
+ * Defaults to the configured mode, then `auto`.
35
+ */
36
+ discovery?: DiscoveryMode;
37
+ }
38
+ interface CollectResult {
39
+ files: string[];
40
+ skipped: SkipRecord[];
41
+ }
42
+ declare function isSensitivePath(relativePath: string): boolean;
43
+ /**
44
+ * Translates a glob (`**`, `*`, `?`, `{a,b}`, `[abc]`) into a RegExp. A pattern
45
+ * without a leading `/` matches at any depth, mirroring `.gitignore` semantics.
46
+ *
47
+ * Brace groups and character classes are supported because they are what people
48
+ * actually write: `*.{ts,tsx}` previously matched nothing at all, since `{` and
49
+ * `}` were escaped as literals.
50
+ */
51
+ declare function globToRegExp(glob: string): RegExp;
52
+ /**
53
+ * Path exclusion with proper segment semantics.
54
+ *
55
+ * A plain (non-glob) pattern matches a whole path segment sequence, so
56
+ * `"test"` matches `test/a.ts` and `src/test/b.ts` but **not** `contest/entry.ts`
57
+ * (the previous implementation was a raw `String.includes`).
58
+ */
59
+ declare function matchesExclude(relativePath: string, pattern: string): boolean;
60
+ /**
61
+ * Collects candidate file paths.
62
+ *
63
+ * Uses `git ls-files -z` when git is available: `-z` is required because the
64
+ * default output C-quotes paths containing non-ASCII characters, which silently
65
+ * mangled `src/café.ts` into a path that never existed and dropped the file.
66
+ */
67
+ declare function collectFiles(cwd: string, options?: CollectOptions): CollectResult;
3
68
 
4
69
  /**
5
70
  * Strips `//` and block comments the way JSONC does, without touching anything
@@ -190,6 +255,10 @@ declare function truncatePreview(content: string, headLines: number, tailLines:
190
255
  * Built once per run: the previous implementation re-read `package.json` inside
191
256
  * every detector, which was both slow and incapable of seeing anything outside
192
257
  * the JavaScript ecosystem.
258
+ *
259
+ * Detection stays bounded: the root is read once, and a workspace root (and only
260
+ * a workspace root) additionally looks one level into its packages so a Next.js
261
+ * app in `apps/web` is still a Next.js repository — see {@link isWorkspace}.
193
262
  */
194
263
  interface PresetContext {
195
264
  cwd: string;
@@ -197,10 +266,26 @@ interface PresetContext {
197
266
  files: ReadonlySet<string>;
198
267
  /** Top-level directory names. */
199
268
  dirs: ReadonlySet<string>;
200
- /** `dependencies` + `devDependencies` + `peerDependencies` names. */
269
+ /** `dependencies` + `devDependencies` + `peerDependencies` names, root and workspace. */
201
270
  dependencies: ReadonlySet<string>;
202
271
  /** Small snapshots of well-known manifests, for keyword detection. */
203
272
  manifests: Readonly<Record<string, string>>;
273
+ /** True when the root declares a workspace (`pnpm-workspace.yaml`, `workspaces`, nx/turbo/lerna). */
274
+ isWorkspace: boolean;
275
+ /**
276
+ * Top-level entry names of the workspace's packages, deduplicated.
277
+ *
278
+ * Flattened on purpose: a detector only needs "does some package carry a
279
+ * `next.config.*`/`vite.config.*` marker", not which package it was.
280
+ */
281
+ workspaceFiles: ReadonlySet<string>;
282
+ /**
283
+ * `.md`/`.mdx` files at the root and up to two directories down.
284
+ *
285
+ * Only counted when the root has no `package.json` (the `docs` preset's other
286
+ * condition), so ordinary repositories pay nothing for it.
287
+ */
288
+ markdownCount: number;
204
289
  }
205
290
 
206
291
  interface PresetDefinition {
@@ -239,6 +324,33 @@ declare function detectPreset(cwd: string, forced?: string): DetectedPreset;
239
324
 
240
325
  declare function isBinaryExtension(filePath: string): boolean;
241
326
  declare function isBinaryBuffer(buffer: Buffer): boolean;
327
+ interface DecodedText {
328
+ /** Decoded body. Empty when `binary` is true. */
329
+ text: string;
330
+ encoding: TextEncoding;
331
+ /** Byte count that could not be decoded as UTF-8. Zero for well-formed input. */
332
+ invalidBytes: number;
333
+ /** True when the bytes are not text: a NUL, or a payload full of control characters. */
334
+ binary: boolean;
335
+ /** Human-readable reason the bytes were rejected, when `binary` is true. */
336
+ binaryReason?: string;
337
+ }
338
+ /**
339
+ * Counts the bytes that are not part of a well-formed UTF-8 sequence.
340
+ *
341
+ * `buffer.toString("utf8")` silently substitutes U+FFFD for every bad sequence, which is how a
342
+ * Latin-1 source file used to reach the snapshot as `caf\uFFFD` with no warning anywhere. This
343
+ * gives the caller the number it needs to say so.
344
+ */
345
+ declare function countInvalidUtf8Bytes(buffer: Buffer): number;
346
+ /**
347
+ * Decodes a file buffer to text without guessing silently.
348
+ *
349
+ * Order matters. A UTF-16 file is mostly NUL bytes, and NUL is perfectly valid UTF-8, so asking
350
+ * "is this valid UTF-8?" first would answer yes and hand the caller a string full of NULs. The
351
+ * BOM check and the UTF-16 shape test therefore run before the UTF-8 validity test.
352
+ */
353
+ declare function decodeText(buffer: Buffer): DecodedText;
242
354
  declare function isBinaryFile(absolutePath: string): boolean;
243
355
 
244
356
  declare function detectLanguage(filePath: string): string;
@@ -296,6 +408,8 @@ interface ExplainResult {
296
408
  inclusionMode?: string;
297
409
  lines?: number;
298
410
  bytes?: number;
411
+ /** Present when the file is not plain UTF-8, so an explanation cannot quietly lie about it. */
412
+ encoding?: TextEncoding;
299
413
  secretCount?: number;
300
414
  secrets?: Array<{
301
415
  rule: string;
@@ -320,6 +434,50 @@ interface ManifestDiff {
320
434
  removed: string[];
321
435
  changed: string[];
322
436
  unchanged: number;
437
+ /** What differs beyond the per-file list: the configuration and the emitted payload. */
438
+ payload: PayloadDiff;
439
+ }
440
+ /**
441
+ * A difference the per-file list cannot express.
442
+ *
443
+ * Per-file comparison is `relativePath + hashSha256`, so a run that changes only
444
+ * the *emitted* payload — `--large-file-bytes` turning a large preview into its
445
+ * full body, different preview head/tail counts, different redaction rules,
446
+ * different artifact selection — used to report "0 changed, N unchanged" and
447
+ * nothing else. The manifests already record the effective-settings digest, the
448
+ * content fingerprint and the part count, so this block says plainly when the
449
+ * source files are identical but the snapshot handed to the model is not.
450
+ */
451
+ interface PayloadDiff {
452
+ /** No source file was added, removed or changed. */
453
+ sourceFilesIdentical: boolean;
454
+ /** The emitted payload, or the configuration that produced it, differs. */
455
+ differs: boolean;
456
+ /** The generator or the effective settings differ, independent of any source edit. */
457
+ configurationDiffers: boolean;
458
+ generator: {
459
+ before: string;
460
+ after: string;
461
+ };
462
+ /** `null` when the manifest predates the digest (written before 4.0.5). */
463
+ configDigest: {
464
+ before: string | null;
465
+ after: string | null;
466
+ };
467
+ fingerprint: {
468
+ before: string;
469
+ after: string;
470
+ };
471
+ partsCount: {
472
+ before: number;
473
+ after: number;
474
+ };
475
+ layers: {
476
+ before: Record<string, number>;
477
+ after: Record<string, number>;
478
+ };
479
+ /** Human-readable statements, rendered by the terminal form. */
480
+ reasons: string[];
323
481
  }
324
482
  interface DiffSide {
325
483
  generatedAt: string;
@@ -445,57 +603,8 @@ declare function profileForPath(path: string): LanguageProfile | null;
445
603
  */
446
604
  declare function summarizeWithProfile(path: string, content: string): HeuristicResult | null;
447
605
 
448
- interface CollectOptions {
449
- /**
450
- * When non-empty, only files matching at least one pattern are candidates.
451
- * Applied before `excludePatterns`, which then narrows the result further.
452
- */
453
- includePatterns?: string[];
454
- excludePatterns?: string[];
455
- /**
456
- * Emit credential-bearing files (`.env`, `*.pem`, `.npmrc`, `id_rsa`, …).
457
- * Default `false`: they are skipped with `reason: "sensitive-file"`.
458
- */
459
- includeSensitiveFiles?: boolean;
460
- /**
461
- * Secret rules used to scan *paths* (defaults are always applied). Pass
462
- * `config.secretRules` so `customPatterns` also protect file names.
463
- */
464
- secretRules?: readonly SecretRule[];
465
- }
466
- interface CollectResult {
467
- files: string[];
468
- skipped: SkipRecord[];
469
- }
470
- declare function isSensitivePath(relativePath: string): boolean;
471
- /**
472
- * Translates a glob (`**`, `*`, `?`, `{a,b}`, `[abc]`) into a RegExp. A pattern
473
- * without a leading `/` matches at any depth, mirroring `.gitignore` semantics.
474
- *
475
- * Brace groups and character classes are supported because they are what people
476
- * actually write: `*.{ts,tsx}` previously matched nothing at all, since `{` and
477
- * `}` were escaped as literals.
478
- */
479
- declare function globToRegExp(glob: string): RegExp;
480
- /**
481
- * Path exclusion with proper segment semantics.
482
- *
483
- * A plain (non-glob) pattern matches a whole path segment sequence, so
484
- * `"test"` matches `test/a.ts` and `src/test/b.ts` but **not** `contest/entry.ts`
485
- * (the previous implementation was a raw `String.includes`).
486
- */
487
- declare function matchesExclude(relativePath: string, pattern: string): boolean;
488
- /**
489
- * Collects candidate file paths.
490
- *
491
- * Uses `git ls-files -z` when git is available: `-z` is required because the
492
- * default output C-quotes paths containing non-ASCII characters, which silently
493
- * mangled `src/café.ts` into a path that never existed and dropped the file.
494
- */
495
- declare function collectFiles(cwd: string, options?: CollectOptions): CollectResult;
496
-
497
606
  declare const GENERATOR_NAME = "@ankurhalder/codebase-snapshot";
498
- declare const GENERATOR_VERSION = "4.0.6";
607
+ declare const GENERATOR_VERSION = "4.1.0";
499
608
  declare const SCHEMA_VERSION = "3.0.0";
500
609
  interface GenerateOptions {
501
610
  /** Compute the full manifest but write nothing to disk. */
@@ -537,4 +646,4 @@ declare function generateSnapshot(configInput?: SnapshotConfig | ResolvedSnapsho
537
646
  /** Promise-returning wrapper for consumers that prefer an async call site. */
538
647
  declare function generateSnapshotAsync(configInput?: SnapshotConfig | ResolvedSnapshotConfig | null, options?: GenerateOptions): Promise<Manifest>;
539
648
 
540
- export { ARTIFACT_NAMES, ArtifactSelection, ByteSize, CliOptions, DEFAULT_ANSWERS, DEFAULT_SECRET_PATTERNS, type ExplainResult, FileEntry, GENERATOR_NAME, GENERATOR_VERSION, type GenerateOptions, type HeuristicResult, type InitOptions, type InitResult, JsonSummary, KindRule, LANGUAGE_PROFILES, type LanguageProfile, LayerRule, MIN_MAX_PART_BYTES, Manifest, type ManifestDiff, MarkdownSummary, PRESETS, PartMetadata, PromptCancelled, ResolvedSnapshotConfig, SCHEMA_VERSION, SecretFinding, SecretRule, SkipRecord, SnapshotConfig, SourceSummary, ValidationResult, type WizardAnswers, anchorForPath, assertReplaceable, assertUsableConfigInput, collectFiles, compareManifests, countLines, defineConfig, detectLanguage, detectPreset, explainPath, fenceForContent, fenceForText, generateProjectTree, generateSnapshot, generateSnapshotAsync, getPreset, globToRegExp, isBinaryBuffer, isBinaryExtension, isBinaryFile, isInteractive, isResolvedConfig, isSensitivePath, listPresetNames, loadConfigFile, loadConfigFileDetailed, loadManifest, markResolvedConfig, matchRule, matchesExclude, normalizeResolvedConfig, parseByteSize, partitionFiles, profileForPath, renderConfig, renderDiffText, renderExplainText, renderMinimalConfig, renumberParts, resolveConfig, resolveSafeOutputDirs, runInit, scanAndRedactSecrets, stripJsonComments, summarizeJson, summarizeMarkdown, summarizeTypeScriptSource, summarizeWithProfile, supportsTypeScriptImports, truncatePreview, truncateToBytes, validateSnapshot };
649
+ export { ARTIFACT_NAMES, ArtifactSelection, ByteSize, CliOptions, DEFAULT_ANSWERS, DEFAULT_SECRET_PATTERNS, DiscoveryMode, type ExplainResult, FileEntry, GENERATOR_NAME, GENERATOR_VERSION, type GenerateOptions, type HeuristicResult, type InitOptions, type InitResult, JsonSummary, KindRule, LANGUAGE_PROFILES, type LanguageProfile, LayerRule, MIN_MAX_PART_BYTES, Manifest, type ManifestDiff, MarkdownSummary, PRESETS, PartMetadata, PromptCancelled, ResolvedSnapshotConfig, SCHEMA_VERSION, SecretFinding, SecretRule, SkipRecord, SnapshotConfig, SourceSummary, TextEncoding, ValidationResult, type WizardAnswers, anchorForPath, assertReplaceable, assertUsableConfigInput, collectFiles, compareManifests, countInvalidUtf8Bytes, countLines, decodeText, defineConfig, detectLanguage, detectPreset, explainPath, fenceForContent, fenceForText, generateProjectTree, generateSnapshot, generateSnapshotAsync, getPreset, globToRegExp, isBinaryBuffer, isBinaryExtension, isBinaryFile, isInteractive, isResolvedConfig, isSensitivePath, listPresetNames, loadConfigFile, loadConfigFileDetailed, loadManifest, markResolvedConfig, matchRule, matchesExclude, normalizeResolvedConfig, parseByteSize, partitionFiles, profileForPath, renderConfig, renderDiffText, renderExplainText, renderMinimalConfig, renumberParts, resolveConfig, resolveSafeOutputDirs, runInit, scanAndRedactSecrets, stripJsonComments, summarizeJson, summarizeMarkdown, summarizeTypeScriptSource, summarizeWithProfile, supportsTypeScriptImports, truncatePreview, truncateToBytes, validateSnapshot };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,70 @@
1
- import { J as JsonSummary, M as MarkdownSummary, S as SourceSummary, R as ResolvedSnapshotConfig, a as SnapshotConfig, B as ByteSize, C as CliOptions, F as FileEntry, P as PartMetadata, L as LayerRule, K as KindRule, b as SecretRule, c as SecretFinding, V as ValidationResult, d as Manifest, A as ArtifactSelection, e as SkipRecord } from './types-x7aogSer.js';
2
- export { f as BudgetConfig, H as HooksConfig, I as InclusionMode, g as ManifestArtifact, h as ManifestConfigSnapshot, i as ManifestIndexes, j as ManifestTotals, k as Mode, O as OutputFormat, l as PartFileEntry, m as ResolvedArtifacts, n as ResolvedBudgets, o as SecretRuleInput, p as SkipReason, q as SummaryQuality, r as ValidationDrift } from './types-x7aogSer.js';
1
+ import { S as SecretRule, D as DiscoveryMode, a as SkipRecord, J as JsonSummary, M as MarkdownSummary, b as SourceSummary, R as ResolvedSnapshotConfig, c as SnapshotConfig, B as ByteSize, C as CliOptions, F as FileEntry, P as PartMetadata, L as LayerRule, K as KindRule, T as TextEncoding, d as SecretFinding, V as ValidationResult, e as Manifest, A as ArtifactSelection } from './types-BMP676ER.js';
2
+ export { f as BudgetConfig, H as HooksConfig, I as InclusionMode, g as ManifestArtifact, h as ManifestConfigSnapshot, i as ManifestIndexes, j as ManifestTotals, k as Mode, O as OutputFormat, l as PartFileEntry, m as ResolvedArtifacts, n as ResolvedBudgets, o as SecretRuleInput, p as SkipReason, q as SummaryQuality, r as ValidationDrift } from './types-BMP676ER.js';
3
+
4
+ interface CollectOptions {
5
+ /**
6
+ * When non-empty, only files matching at least one pattern are candidates.
7
+ * Applied before `excludePatterns`, which then narrows the result further.
8
+ */
9
+ includePatterns?: string[];
10
+ excludePatterns?: string[];
11
+ /**
12
+ * Emit credential-bearing files (`.env`, `*.pem`, `.npmrc`, `id_rsa`, …).
13
+ * Default `false`: they are skipped with `reason: "sensitive-file"`.
14
+ */
15
+ includeSensitiveFiles?: boolean;
16
+ /**
17
+ * Secret rules used to scan *paths* (defaults are always applied). Pass
18
+ * `config.secretRules` so `customPatterns` also protect file names.
19
+ */
20
+ secretRules?: readonly SecretRule[];
21
+ /**
22
+ * Top-level directory names this run is about to write — `outDir` and `tmpDir`.
23
+ *
24
+ * They are excluded from the scan without a `skipped[]` record because that is this tool's own
25
+ * bookkeeping and a first run cannot have written a marker into them yet. A directory with one
26
+ * of these *names* that carries no marker is still excluded when it is one of these paths, but
27
+ * is an ordinary directory when it is not: dropping it silently was the bug.
28
+ */
29
+ toolOwnedPaths?: readonly string[];
30
+ /**
31
+ * How candidates are discovered: `auto` (git when usable, refusal when the
32
+ * directory claims to be a repository but git cannot list it), `git` (require
33
+ * git), or `walk` (walk the tree, reading the root `.gitignore` directly).
34
+ * Defaults to the configured mode, then `auto`.
35
+ */
36
+ discovery?: DiscoveryMode;
37
+ }
38
+ interface CollectResult {
39
+ files: string[];
40
+ skipped: SkipRecord[];
41
+ }
42
+ declare function isSensitivePath(relativePath: string): boolean;
43
+ /**
44
+ * Translates a glob (`**`, `*`, `?`, `{a,b}`, `[abc]`) into a RegExp. A pattern
45
+ * without a leading `/` matches at any depth, mirroring `.gitignore` semantics.
46
+ *
47
+ * Brace groups and character classes are supported because they are what people
48
+ * actually write: `*.{ts,tsx}` previously matched nothing at all, since `{` and
49
+ * `}` were escaped as literals.
50
+ */
51
+ declare function globToRegExp(glob: string): RegExp;
52
+ /**
53
+ * Path exclusion with proper segment semantics.
54
+ *
55
+ * A plain (non-glob) pattern matches a whole path segment sequence, so
56
+ * `"test"` matches `test/a.ts` and `src/test/b.ts` but **not** `contest/entry.ts`
57
+ * (the previous implementation was a raw `String.includes`).
58
+ */
59
+ declare function matchesExclude(relativePath: string, pattern: string): boolean;
60
+ /**
61
+ * Collects candidate file paths.
62
+ *
63
+ * Uses `git ls-files -z` when git is available: `-z` is required because the
64
+ * default output C-quotes paths containing non-ASCII characters, which silently
65
+ * mangled `src/café.ts` into a path that never existed and dropped the file.
66
+ */
67
+ declare function collectFiles(cwd: string, options?: CollectOptions): CollectResult;
3
68
 
4
69
  /**
5
70
  * Strips `//` and block comments the way JSONC does, without touching anything
@@ -190,6 +255,10 @@ declare function truncatePreview(content: string, headLines: number, tailLines:
190
255
  * Built once per run: the previous implementation re-read `package.json` inside
191
256
  * every detector, which was both slow and incapable of seeing anything outside
192
257
  * the JavaScript ecosystem.
258
+ *
259
+ * Detection stays bounded: the root is read once, and a workspace root (and only
260
+ * a workspace root) additionally looks one level into its packages so a Next.js
261
+ * app in `apps/web` is still a Next.js repository — see {@link isWorkspace}.
193
262
  */
194
263
  interface PresetContext {
195
264
  cwd: string;
@@ -197,10 +266,26 @@ interface PresetContext {
197
266
  files: ReadonlySet<string>;
198
267
  /** Top-level directory names. */
199
268
  dirs: ReadonlySet<string>;
200
- /** `dependencies` + `devDependencies` + `peerDependencies` names. */
269
+ /** `dependencies` + `devDependencies` + `peerDependencies` names, root and workspace. */
201
270
  dependencies: ReadonlySet<string>;
202
271
  /** Small snapshots of well-known manifests, for keyword detection. */
203
272
  manifests: Readonly<Record<string, string>>;
273
+ /** True when the root declares a workspace (`pnpm-workspace.yaml`, `workspaces`, nx/turbo/lerna). */
274
+ isWorkspace: boolean;
275
+ /**
276
+ * Top-level entry names of the workspace's packages, deduplicated.
277
+ *
278
+ * Flattened on purpose: a detector only needs "does some package carry a
279
+ * `next.config.*`/`vite.config.*` marker", not which package it was.
280
+ */
281
+ workspaceFiles: ReadonlySet<string>;
282
+ /**
283
+ * `.md`/`.mdx` files at the root and up to two directories down.
284
+ *
285
+ * Only counted when the root has no `package.json` (the `docs` preset's other
286
+ * condition), so ordinary repositories pay nothing for it.
287
+ */
288
+ markdownCount: number;
204
289
  }
205
290
 
206
291
  interface PresetDefinition {
@@ -239,6 +324,33 @@ declare function detectPreset(cwd: string, forced?: string): DetectedPreset;
239
324
 
240
325
  declare function isBinaryExtension(filePath: string): boolean;
241
326
  declare function isBinaryBuffer(buffer: Buffer): boolean;
327
+ interface DecodedText {
328
+ /** Decoded body. Empty when `binary` is true. */
329
+ text: string;
330
+ encoding: TextEncoding;
331
+ /** Byte count that could not be decoded as UTF-8. Zero for well-formed input. */
332
+ invalidBytes: number;
333
+ /** True when the bytes are not text: a NUL, or a payload full of control characters. */
334
+ binary: boolean;
335
+ /** Human-readable reason the bytes were rejected, when `binary` is true. */
336
+ binaryReason?: string;
337
+ }
338
+ /**
339
+ * Counts the bytes that are not part of a well-formed UTF-8 sequence.
340
+ *
341
+ * `buffer.toString("utf8")` silently substitutes U+FFFD for every bad sequence, which is how a
342
+ * Latin-1 source file used to reach the snapshot as `caf\uFFFD` with no warning anywhere. This
343
+ * gives the caller the number it needs to say so.
344
+ */
345
+ declare function countInvalidUtf8Bytes(buffer: Buffer): number;
346
+ /**
347
+ * Decodes a file buffer to text without guessing silently.
348
+ *
349
+ * Order matters. A UTF-16 file is mostly NUL bytes, and NUL is perfectly valid UTF-8, so asking
350
+ * "is this valid UTF-8?" first would answer yes and hand the caller a string full of NULs. The
351
+ * BOM check and the UTF-16 shape test therefore run before the UTF-8 validity test.
352
+ */
353
+ declare function decodeText(buffer: Buffer): DecodedText;
242
354
  declare function isBinaryFile(absolutePath: string): boolean;
243
355
 
244
356
  declare function detectLanguage(filePath: string): string;
@@ -296,6 +408,8 @@ interface ExplainResult {
296
408
  inclusionMode?: string;
297
409
  lines?: number;
298
410
  bytes?: number;
411
+ /** Present when the file is not plain UTF-8, so an explanation cannot quietly lie about it. */
412
+ encoding?: TextEncoding;
299
413
  secretCount?: number;
300
414
  secrets?: Array<{
301
415
  rule: string;
@@ -320,6 +434,50 @@ interface ManifestDiff {
320
434
  removed: string[];
321
435
  changed: string[];
322
436
  unchanged: number;
437
+ /** What differs beyond the per-file list: the configuration and the emitted payload. */
438
+ payload: PayloadDiff;
439
+ }
440
+ /**
441
+ * A difference the per-file list cannot express.
442
+ *
443
+ * Per-file comparison is `relativePath + hashSha256`, so a run that changes only
444
+ * the *emitted* payload — `--large-file-bytes` turning a large preview into its
445
+ * full body, different preview head/tail counts, different redaction rules,
446
+ * different artifact selection — used to report "0 changed, N unchanged" and
447
+ * nothing else. The manifests already record the effective-settings digest, the
448
+ * content fingerprint and the part count, so this block says plainly when the
449
+ * source files are identical but the snapshot handed to the model is not.
450
+ */
451
+ interface PayloadDiff {
452
+ /** No source file was added, removed or changed. */
453
+ sourceFilesIdentical: boolean;
454
+ /** The emitted payload, or the configuration that produced it, differs. */
455
+ differs: boolean;
456
+ /** The generator or the effective settings differ, independent of any source edit. */
457
+ configurationDiffers: boolean;
458
+ generator: {
459
+ before: string;
460
+ after: string;
461
+ };
462
+ /** `null` when the manifest predates the digest (written before 4.0.5). */
463
+ configDigest: {
464
+ before: string | null;
465
+ after: string | null;
466
+ };
467
+ fingerprint: {
468
+ before: string;
469
+ after: string;
470
+ };
471
+ partsCount: {
472
+ before: number;
473
+ after: number;
474
+ };
475
+ layers: {
476
+ before: Record<string, number>;
477
+ after: Record<string, number>;
478
+ };
479
+ /** Human-readable statements, rendered by the terminal form. */
480
+ reasons: string[];
323
481
  }
324
482
  interface DiffSide {
325
483
  generatedAt: string;
@@ -445,57 +603,8 @@ declare function profileForPath(path: string): LanguageProfile | null;
445
603
  */
446
604
  declare function summarizeWithProfile(path: string, content: string): HeuristicResult | null;
447
605
 
448
- interface CollectOptions {
449
- /**
450
- * When non-empty, only files matching at least one pattern are candidates.
451
- * Applied before `excludePatterns`, which then narrows the result further.
452
- */
453
- includePatterns?: string[];
454
- excludePatterns?: string[];
455
- /**
456
- * Emit credential-bearing files (`.env`, `*.pem`, `.npmrc`, `id_rsa`, …).
457
- * Default `false`: they are skipped with `reason: "sensitive-file"`.
458
- */
459
- includeSensitiveFiles?: boolean;
460
- /**
461
- * Secret rules used to scan *paths* (defaults are always applied). Pass
462
- * `config.secretRules` so `customPatterns` also protect file names.
463
- */
464
- secretRules?: readonly SecretRule[];
465
- }
466
- interface CollectResult {
467
- files: string[];
468
- skipped: SkipRecord[];
469
- }
470
- declare function isSensitivePath(relativePath: string): boolean;
471
- /**
472
- * Translates a glob (`**`, `*`, `?`, `{a,b}`, `[abc]`) into a RegExp. A pattern
473
- * without a leading `/` matches at any depth, mirroring `.gitignore` semantics.
474
- *
475
- * Brace groups and character classes are supported because they are what people
476
- * actually write: `*.{ts,tsx}` previously matched nothing at all, since `{` and
477
- * `}` were escaped as literals.
478
- */
479
- declare function globToRegExp(glob: string): RegExp;
480
- /**
481
- * Path exclusion with proper segment semantics.
482
- *
483
- * A plain (non-glob) pattern matches a whole path segment sequence, so
484
- * `"test"` matches `test/a.ts` and `src/test/b.ts` but **not** `contest/entry.ts`
485
- * (the previous implementation was a raw `String.includes`).
486
- */
487
- declare function matchesExclude(relativePath: string, pattern: string): boolean;
488
- /**
489
- * Collects candidate file paths.
490
- *
491
- * Uses `git ls-files -z` when git is available: `-z` is required because the
492
- * default output C-quotes paths containing non-ASCII characters, which silently
493
- * mangled `src/café.ts` into a path that never existed and dropped the file.
494
- */
495
- declare function collectFiles(cwd: string, options?: CollectOptions): CollectResult;
496
-
497
606
  declare const GENERATOR_NAME = "@ankurhalder/codebase-snapshot";
498
- declare const GENERATOR_VERSION = "4.0.6";
607
+ declare const GENERATOR_VERSION = "4.1.0";
499
608
  declare const SCHEMA_VERSION = "3.0.0";
500
609
  interface GenerateOptions {
501
610
  /** Compute the full manifest but write nothing to disk. */
@@ -537,4 +646,4 @@ declare function generateSnapshot(configInput?: SnapshotConfig | ResolvedSnapsho
537
646
  /** Promise-returning wrapper for consumers that prefer an async call site. */
538
647
  declare function generateSnapshotAsync(configInput?: SnapshotConfig | ResolvedSnapshotConfig | null, options?: GenerateOptions): Promise<Manifest>;
539
648
 
540
- export { ARTIFACT_NAMES, ArtifactSelection, ByteSize, CliOptions, DEFAULT_ANSWERS, DEFAULT_SECRET_PATTERNS, type ExplainResult, FileEntry, GENERATOR_NAME, GENERATOR_VERSION, type GenerateOptions, type HeuristicResult, type InitOptions, type InitResult, JsonSummary, KindRule, LANGUAGE_PROFILES, type LanguageProfile, LayerRule, MIN_MAX_PART_BYTES, Manifest, type ManifestDiff, MarkdownSummary, PRESETS, PartMetadata, PromptCancelled, ResolvedSnapshotConfig, SCHEMA_VERSION, SecretFinding, SecretRule, SkipRecord, SnapshotConfig, SourceSummary, ValidationResult, type WizardAnswers, anchorForPath, assertReplaceable, assertUsableConfigInput, collectFiles, compareManifests, countLines, defineConfig, detectLanguage, detectPreset, explainPath, fenceForContent, fenceForText, generateProjectTree, generateSnapshot, generateSnapshotAsync, getPreset, globToRegExp, isBinaryBuffer, isBinaryExtension, isBinaryFile, isInteractive, isResolvedConfig, isSensitivePath, listPresetNames, loadConfigFile, loadConfigFileDetailed, loadManifest, markResolvedConfig, matchRule, matchesExclude, normalizeResolvedConfig, parseByteSize, partitionFiles, profileForPath, renderConfig, renderDiffText, renderExplainText, renderMinimalConfig, renumberParts, resolveConfig, resolveSafeOutputDirs, runInit, scanAndRedactSecrets, stripJsonComments, summarizeJson, summarizeMarkdown, summarizeTypeScriptSource, summarizeWithProfile, supportsTypeScriptImports, truncatePreview, truncateToBytes, validateSnapshot };
649
+ export { ARTIFACT_NAMES, ArtifactSelection, ByteSize, CliOptions, DEFAULT_ANSWERS, DEFAULT_SECRET_PATTERNS, DiscoveryMode, type ExplainResult, FileEntry, GENERATOR_NAME, GENERATOR_VERSION, type GenerateOptions, type HeuristicResult, type InitOptions, type InitResult, JsonSummary, KindRule, LANGUAGE_PROFILES, type LanguageProfile, LayerRule, MIN_MAX_PART_BYTES, Manifest, type ManifestDiff, MarkdownSummary, PRESETS, PartMetadata, PromptCancelled, ResolvedSnapshotConfig, SCHEMA_VERSION, SecretFinding, SecretRule, SkipRecord, SnapshotConfig, SourceSummary, TextEncoding, ValidationResult, type WizardAnswers, anchorForPath, assertReplaceable, assertUsableConfigInput, collectFiles, compareManifests, countInvalidUtf8Bytes, countLines, decodeText, defineConfig, detectLanguage, detectPreset, explainPath, fenceForContent, fenceForText, generateProjectTree, generateSnapshot, generateSnapshotAsync, getPreset, globToRegExp, isBinaryBuffer, isBinaryExtension, isBinaryFile, isInteractive, isResolvedConfig, isSensitivePath, listPresetNames, loadConfigFile, loadConfigFileDetailed, loadManifest, markResolvedConfig, matchRule, matchesExclude, normalizeResolvedConfig, parseByteSize, partitionFiles, profileForPath, renderConfig, renderDiffText, renderExplainText, renderMinimalConfig, renumberParts, resolveConfig, resolveSafeOutputDirs, runInit, scanAndRedactSecrets, stripJsonComments, summarizeJson, summarizeMarkdown, summarizeTypeScriptSource, summarizeWithProfile, supportsTypeScriptImports, truncatePreview, truncateToBytes, validateSnapshot };
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- export { ARTIFACT_NAMES, DEFAULT_ANSWERS, DEFAULT_SECRET_PATTERNS, GENERATOR_NAME, GENERATOR_VERSION, LANGUAGE_PROFILES, MIN_MAX_PART_BYTES, PRESETS, PromptCancelled, SCHEMA_VERSION, anchorForPath, assertReplaceable, assertUsableConfigInput, collectFiles, compareManifests, countLines, defineConfig, detectLanguage, detectPreset, explainPath, fenceForContent, fenceForText, generateProjectTree, generateSnapshot, generateSnapshotAsync, getPreset, globToRegExp, isBinaryBuffer, isBinaryExtension, isBinaryFile, isInteractive, isResolvedConfig, isSensitivePath, listPresetNames, loadConfigFile, loadConfigFileDetailed, loadManifest, markResolvedConfig, matchRule, matchesExclude, normalizeResolvedConfig, parseByteSize, partitionFiles, profileForPath, renderConfig, renderDiffText, renderExplainText, renderMinimalConfig, renumberParts, resolveConfig, resolveSafeOutputDirs, runInit, scanAndRedactSecrets, stripJsonComments, summarizeJson, summarizeMarkdown, summarizeTypeScriptSource, summarizeWithProfile, supportsTypeScriptImports, truncatePreview, truncateToBytes, validateSnapshot } from './chunk-GAY7W5MK.js';
1
+ export { ARTIFACT_NAMES, DEFAULT_ANSWERS, DEFAULT_SECRET_PATTERNS, GENERATOR_NAME, GENERATOR_VERSION, LANGUAGE_PROFILES, MIN_MAX_PART_BYTES, PRESETS, PromptCancelled, SCHEMA_VERSION, anchorForPath, assertReplaceable, assertUsableConfigInput, collectFiles, compareManifests, countInvalidUtf8Bytes, countLines, decodeText, defineConfig, detectLanguage, detectPreset, explainPath, fenceForContent, fenceForText, generateProjectTree, generateSnapshot, generateSnapshotAsync, getPreset, globToRegExp, isBinaryBuffer, isBinaryExtension, isBinaryFile, isInteractive, isResolvedConfig, isSensitivePath, listPresetNames, loadConfigFile, loadConfigFileDetailed, loadManifest, markResolvedConfig, matchRule, matchesExclude, normalizeResolvedConfig, parseByteSize, partitionFiles, profileForPath, renderConfig, renderDiffText, renderExplainText, renderMinimalConfig, renumberParts, resolveConfig, resolveSafeOutputDirs, runInit, scanAndRedactSecrets, stripJsonComments, summarizeJson, summarizeMarkdown, summarizeTypeScriptSource, summarizeWithProfile, supportsTypeScriptImports, truncatePreview, truncateToBytes, validateSnapshot } from './chunk-6SMZR5EM.js';
2
2
  //# sourceMappingURL=index.js.map
3
3
  //# sourceMappingURL=index.js.map
@@ -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, SourceSummary as S, ValidationResult as V, SnapshotConfig as a, SecretRule as b, SecretFinding as c, Manifest as d, SkipRecord 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 };
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 };