@ankurhalder/codebase-snapshot 2.2.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/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
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-CJqkePM4.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-CJqkePM4.cjs';
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-BdpT_4b8.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-BdpT_4b8.cjs';
3
3
 
4
4
  /**
5
5
  * Strips `//` and block comments the way JSONC does, without touching anything
@@ -142,6 +142,16 @@ declare function renumberParts(parts: PartitionedPart[], contentFingerprint?: st
142
142
  * contains a fenced block cannot break out of its own code block.
143
143
  */
144
144
  declare function fenceForContent(content: string): string;
145
+ /**
146
+ * A fence longer than any backtick run in `content`, never shorter than
147
+ * `minimum`.
148
+ *
149
+ * Every fenced block the generator writes goes through this, not just file
150
+ * bodies: a *filename* may contain a backtick run (it is a legal name), and a
151
+ * fixed three-backtick fence around the `## Files` list let such a name close
152
+ * the block and turn the following filenames into live document text.
153
+ */
154
+ declare function fenceForText(content: string, minimum: number): string;
145
155
  /**
146
156
  * Stable, injective anchor for a file path.
147
157
  *
@@ -238,6 +248,8 @@ declare function detectLanguage(filePath: string): string;
238
248
  *
239
249
  * - Replaces only the matched span, so unrelated code sharing the line survives
240
250
  * (the previous implementation discarded the whole line).
251
+ * - When a rule declares `valueGroup`, only that capture group is masked, so
252
+ * `dbPassword: "…"` keeps its key and stays parseable.
241
253
  * - Masks PEM private-key blocks as a unit; per-line rules can only see the
242
254
  * `BEGIN` header, which left every body line of a real key in the output.
243
255
  * - Records `{ rule, line, path }` only. No excerpt of the source is retained,
@@ -262,20 +274,6 @@ declare function scanAndRedactSecrets(content: string, rules: SecretRule[], file
262
274
  */
263
275
  declare function validateSnapshot(configInput: ResolvedSnapshotConfig | SnapshotConfig): ValidationResult;
264
276
 
265
- /**
266
- * Default secret rules.
267
- *
268
- * Design notes:
269
- * - Every quantifier is bounded. An unbounded `[A-Za-z0-9_-]{10,}` followed by
270
- * `\.` backtracks to the end of the line at every start position, which made
271
- * `jwt-token` quadratic (measured: 1.22 s for a 200 KB line). JWT segments are
272
- * never longer than a few KB, so bounding at 1024 keeps the match linear in
273
- * practice while still matching every realistic token.
274
- * - Rules are line-oriented; the whole-block private-key scan lives in
275
- * `redact.ts` because it spans lines.
276
- * - Names are matched case-insensitively only where a value is anchored by
277
- * quotes or a length, to keep false positives at zero on ordinary source.
278
- */
279
277
  declare const DEFAULT_SECRET_PATTERNS: SecretRule[];
280
278
 
281
279
  interface ExplainResult {
@@ -329,12 +327,7 @@ interface DiffSide {
329
327
  fileCount: number;
330
328
  partsCount: number;
331
329
  }
332
- /**
333
- * Compares two manifests file by file.
334
- *
335
- * The per-file hashes and the content fingerprint already make this exact, so
336
- * nothing is inferred: a path is added, removed or changed, never "maybe".
337
- */
330
+ /** Compares two manifests file by file. */
338
331
  declare function compareManifests(before: Manifest, after: Manifest): ManifestDiff;
339
332
  /** Loads a manifest from a snapshot directory or a `manifest.json` path. */
340
333
  declare function loadManifest(target: string): Manifest;
@@ -497,7 +490,7 @@ declare function matchesExclude(relativePath: string, pattern: string): boolean;
497
490
  declare function collectFiles(cwd: string, options?: CollectOptions): CollectResult;
498
491
 
499
492
  declare const GENERATOR_NAME = "@ankurhalder/codebase-snapshot";
500
- declare const GENERATOR_VERSION = "2.2.0";
493
+ declare const GENERATOR_VERSION = "4.0.0";
501
494
  declare const SCHEMA_VERSION = "3.0.0";
502
495
  interface GenerateOptions {
503
496
  /** Compute the full manifest but write nothing to disk. */
@@ -521,10 +514,11 @@ declare function resolveSafeOutputDirs(config: {
521
514
  /**
522
515
  * Refuses to replace a directory that this generator did not write.
523
516
  *
524
- * `swapIntoPlace` renames the previous output aside and deletes it, and the
525
- * scratch directory is deleted outright before every run. Path checks alone
526
- * cannot make that safe: `outDir: "src"` is a plausible typo, and it used to
527
- * delete the developer's entire source tree and exit 0.
517
+ * Path checks alone cannot make the swap and the staging wipe safe: `outDir:
518
+ * "src"` is a plausible typo, and a user directory that happens to contain a
519
+ * `.codebase-snapshot` file or a manifest-shaped JSON document is not a
520
+ * snapshot. `tmpDir` is held to the same standard: it is only cleared when it
521
+ * carries our marker and holds nothing but our artifacts.
528
522
  */
529
523
  declare function assertReplaceable(target: string, label: "outDir" | "tmpDir"): void;
530
524
  /**
@@ -538,4 +532,4 @@ declare function generateSnapshot(configInput?: SnapshotConfig | ResolvedSnapsho
538
532
  /** Promise-returning wrapper for consumers that prefer an async call site. */
539
533
  declare function generateSnapshotAsync(configInput?: SnapshotConfig | ResolvedSnapshotConfig | null, options?: GenerateOptions): Promise<Manifest>;
540
534
 
541
- 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, 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 };
535
+ 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 };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
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-CJqkePM4.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-CJqkePM4.js';
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-BdpT_4b8.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-BdpT_4b8.js';
3
3
 
4
4
  /**
5
5
  * Strips `//` and block comments the way JSONC does, without touching anything
@@ -142,6 +142,16 @@ declare function renumberParts(parts: PartitionedPart[], contentFingerprint?: st
142
142
  * contains a fenced block cannot break out of its own code block.
143
143
  */
144
144
  declare function fenceForContent(content: string): string;
145
+ /**
146
+ * A fence longer than any backtick run in `content`, never shorter than
147
+ * `minimum`.
148
+ *
149
+ * Every fenced block the generator writes goes through this, not just file
150
+ * bodies: a *filename* may contain a backtick run (it is a legal name), and a
151
+ * fixed three-backtick fence around the `## Files` list let such a name close
152
+ * the block and turn the following filenames into live document text.
153
+ */
154
+ declare function fenceForText(content: string, minimum: number): string;
145
155
  /**
146
156
  * Stable, injective anchor for a file path.
147
157
  *
@@ -238,6 +248,8 @@ declare function detectLanguage(filePath: string): string;
238
248
  *
239
249
  * - Replaces only the matched span, so unrelated code sharing the line survives
240
250
  * (the previous implementation discarded the whole line).
251
+ * - When a rule declares `valueGroup`, only that capture group is masked, so
252
+ * `dbPassword: "…"` keeps its key and stays parseable.
241
253
  * - Masks PEM private-key blocks as a unit; per-line rules can only see the
242
254
  * `BEGIN` header, which left every body line of a real key in the output.
243
255
  * - Records `{ rule, line, path }` only. No excerpt of the source is retained,
@@ -262,20 +274,6 @@ declare function scanAndRedactSecrets(content: string, rules: SecretRule[], file
262
274
  */
263
275
  declare function validateSnapshot(configInput: ResolvedSnapshotConfig | SnapshotConfig): ValidationResult;
264
276
 
265
- /**
266
- * Default secret rules.
267
- *
268
- * Design notes:
269
- * - Every quantifier is bounded. An unbounded `[A-Za-z0-9_-]{10,}` followed by
270
- * `\.` backtracks to the end of the line at every start position, which made
271
- * `jwt-token` quadratic (measured: 1.22 s for a 200 KB line). JWT segments are
272
- * never longer than a few KB, so bounding at 1024 keeps the match linear in
273
- * practice while still matching every realistic token.
274
- * - Rules are line-oriented; the whole-block private-key scan lives in
275
- * `redact.ts` because it spans lines.
276
- * - Names are matched case-insensitively only where a value is anchored by
277
- * quotes or a length, to keep false positives at zero on ordinary source.
278
- */
279
277
  declare const DEFAULT_SECRET_PATTERNS: SecretRule[];
280
278
 
281
279
  interface ExplainResult {
@@ -329,12 +327,7 @@ interface DiffSide {
329
327
  fileCount: number;
330
328
  partsCount: number;
331
329
  }
332
- /**
333
- * Compares two manifests file by file.
334
- *
335
- * The per-file hashes and the content fingerprint already make this exact, so
336
- * nothing is inferred: a path is added, removed or changed, never "maybe".
337
- */
330
+ /** Compares two manifests file by file. */
338
331
  declare function compareManifests(before: Manifest, after: Manifest): ManifestDiff;
339
332
  /** Loads a manifest from a snapshot directory or a `manifest.json` path. */
340
333
  declare function loadManifest(target: string): Manifest;
@@ -497,7 +490,7 @@ declare function matchesExclude(relativePath: string, pattern: string): boolean;
497
490
  declare function collectFiles(cwd: string, options?: CollectOptions): CollectResult;
498
491
 
499
492
  declare const GENERATOR_NAME = "@ankurhalder/codebase-snapshot";
500
- declare const GENERATOR_VERSION = "2.2.0";
493
+ declare const GENERATOR_VERSION = "4.0.0";
501
494
  declare const SCHEMA_VERSION = "3.0.0";
502
495
  interface GenerateOptions {
503
496
  /** Compute the full manifest but write nothing to disk. */
@@ -521,10 +514,11 @@ declare function resolveSafeOutputDirs(config: {
521
514
  /**
522
515
  * Refuses to replace a directory that this generator did not write.
523
516
  *
524
- * `swapIntoPlace` renames the previous output aside and deletes it, and the
525
- * scratch directory is deleted outright before every run. Path checks alone
526
- * cannot make that safe: `outDir: "src"` is a plausible typo, and it used to
527
- * delete the developer's entire source tree and exit 0.
517
+ * Path checks alone cannot make the swap and the staging wipe safe: `outDir:
518
+ * "src"` is a plausible typo, and a user directory that happens to contain a
519
+ * `.codebase-snapshot` file or a manifest-shaped JSON document is not a
520
+ * snapshot. `tmpDir` is held to the same standard: it is only cleared when it
521
+ * carries our marker and holds nothing but our artifacts.
528
522
  */
529
523
  declare function assertReplaceable(target: string, label: "outDir" | "tmpDir"): void;
530
524
  /**
@@ -538,4 +532,4 @@ declare function generateSnapshot(configInput?: SnapshotConfig | ResolvedSnapsho
538
532
  /** Promise-returning wrapper for consumers that prefer an async call site. */
539
533
  declare function generateSnapshotAsync(configInput?: SnapshotConfig | ResolvedSnapshotConfig | null, options?: GenerateOptions): Promise<Manifest>;
540
534
 
541
- 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, 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 };
535
+ 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 };
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, 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-5I564BGX.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, 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-FM4OOQPF.js';
2
2
  //# sourceMappingURL=index.js.map
3
3
  //# sourceMappingURL=index.js.map
@@ -44,7 +44,27 @@ interface MarkdownSummary {
44
44
  * as one.
45
45
  */
46
46
  type SummaryQuality = "ast" | "structural" | "heuristic";
47
- type SkipReason = "asset" | "binary" | "read-error" | "not-file" | "sensitive-file" | "symlink" | "default-ignore" | "excluded";
47
+ /**
48
+ * Why a discovered path was not emitted.
49
+ *
50
+ * Every reason below is produced by the scanner; nothing in the type is
51
+ * aspirational. `asset` was removed because no code path ever emitted it.
52
+ */
53
+ type SkipReason = "binary" | "read-error" | "not-file"
54
+ /** A credential-bearing name (`.env`, `*.pem`, `.npmrc`, …). */
55
+ | "sensitive-file"
56
+ /** The path itself matched a secret rule; the emitted path is span-masked. */
57
+ | "sensitive-path"
58
+ /** A directory link (never followed), or a link to neither file nor directory. */
59
+ | "symlink"
60
+ /** A file link whose target resolves outside the scanned root. */
61
+ | "outside-root"
62
+ /** Excluded by a `.gitignore` in a scan without git. */
63
+ | "gitignore"
64
+ /** Excluded by a built-in rule: the ignore list, a preset, or an output dir. */
65
+ | "default-ignore"
66
+ /** Excluded by the user's own `excludePatterns` or `include` allow-list. */
67
+ | "excluded";
48
68
  interface SkipRecord {
49
69
  path: string;
50
70
  reason: SkipReason;
@@ -103,12 +123,25 @@ interface PartMetadata {
103
123
  interface SecretRule {
104
124
  rule: string;
105
125
  pattern: RegExp;
126
+ /**
127
+ * Index of the capture group that holds the secret, when the rule has to match
128
+ * its surrounding context to identify one (`password: "…"`). Redaction then
129
+ * masks only that group, so the key and the rest of the line survive.
130
+ */
131
+ valueGroup?: number;
106
132
  }
107
- /** Shape accepted from JSON config files, where `pattern` can only be a string. */
108
- interface SecretRuleInput {
133
+ /**
134
+ * Shape accepted for `secrets.customPatterns`.
135
+ *
136
+ * A bare `RegExp` or string is accepted because it is the documented form; the
137
+ * object form exists to name a rule. `valueGroup` is only useful with the object
138
+ * form, since a bare pattern has no group to name.
139
+ */
140
+ type SecretRuleInput = {
109
141
  rule: string;
110
142
  pattern: RegExp | string;
111
- }
143
+ valueGroup?: number;
144
+ } | RegExp | string;
112
145
  /**
113
146
  * A detected secret. Deliberately carries no excerpt of the matched source:
114
147
  * the previous `snippet` field was the vector by which raw credentials were
@@ -44,7 +44,27 @@ interface MarkdownSummary {
44
44
  * as one.
45
45
  */
46
46
  type SummaryQuality = "ast" | "structural" | "heuristic";
47
- type SkipReason = "asset" | "binary" | "read-error" | "not-file" | "sensitive-file" | "symlink" | "default-ignore" | "excluded";
47
+ /**
48
+ * Why a discovered path was not emitted.
49
+ *
50
+ * Every reason below is produced by the scanner; nothing in the type is
51
+ * aspirational. `asset` was removed because no code path ever emitted it.
52
+ */
53
+ type SkipReason = "binary" | "read-error" | "not-file"
54
+ /** A credential-bearing name (`.env`, `*.pem`, `.npmrc`, …). */
55
+ | "sensitive-file"
56
+ /** The path itself matched a secret rule; the emitted path is span-masked. */
57
+ | "sensitive-path"
58
+ /** A directory link (never followed), or a link to neither file nor directory. */
59
+ | "symlink"
60
+ /** A file link whose target resolves outside the scanned root. */
61
+ | "outside-root"
62
+ /** Excluded by a `.gitignore` in a scan without git. */
63
+ | "gitignore"
64
+ /** Excluded by a built-in rule: the ignore list, a preset, or an output dir. */
65
+ | "default-ignore"
66
+ /** Excluded by the user's own `excludePatterns` or `include` allow-list. */
67
+ | "excluded";
48
68
  interface SkipRecord {
49
69
  path: string;
50
70
  reason: SkipReason;
@@ -103,12 +123,25 @@ interface PartMetadata {
103
123
  interface SecretRule {
104
124
  rule: string;
105
125
  pattern: RegExp;
126
+ /**
127
+ * Index of the capture group that holds the secret, when the rule has to match
128
+ * its surrounding context to identify one (`password: "…"`). Redaction then
129
+ * masks only that group, so the key and the rest of the line survive.
130
+ */
131
+ valueGroup?: number;
106
132
  }
107
- /** Shape accepted from JSON config files, where `pattern` can only be a string. */
108
- interface SecretRuleInput {
133
+ /**
134
+ * Shape accepted for `secrets.customPatterns`.
135
+ *
136
+ * A bare `RegExp` or string is accepted because it is the documented form; the
137
+ * object form exists to name a rule. `valueGroup` is only useful with the object
138
+ * form, since a bare pattern has no group to name.
139
+ */
140
+ type SecretRuleInput = {
109
141
  rule: string;
110
142
  pattern: RegExp | string;
111
- }
143
+ valueGroup?: number;
144
+ } | RegExp | string;
112
145
  /**
113
146
  * A detected secret. Deliberately carries no excerpt of the matched source:
114
147
  * the previous `snippet` field was the vector by which raw credentials were
package/docs/agent.md CHANGED
@@ -1,45 +1,63 @@
1
1
  # Using this with an AI agent
2
2
 
3
- The package ships two things that make it usable by an agent that has never heard of it:
4
- a **machine-readable contract** and a **bundled skill**.
3
+ The package ships two things that make it usable by an agent that has never heard of it: a
4
+ **machine-readable contract** and a **bundled skill**.
5
5
 
6
- Nothing here needs to be installed globally, and no prior knowledge of the tool is
7
- required.
6
+ Nothing needs to be installed globally and no prior knowledge of the tool is required.
8
7
 
9
8
  ## The three commands an agent needs
10
9
 
11
10
  ```bash
12
11
  # 1. Read the contract. This is the machine-readable replacement for --help.
13
- npx @ankurhalder/codebase-snapshot@latest describe --format json
12
+ npx @ankurhalder/codebase-snapshot describe --format json
14
13
 
15
14
  # 2. Write a commented config that lists every setting (non-interactive).
16
- npx @ankurhalder/codebase-snapshot@latest init --yes
15
+ npx @ankurhalder/codebase-snapshot init --yes
17
16
 
18
17
  # 3. Generate and verify.
19
- npx @ankurhalder/codebase-snapshot@latest generate
20
- npx @ankurhalder/codebase-snapshot@latest validate
18
+ npx @ankurhalder/codebase-snapshot generate
19
+ npx @ankurhalder/codebase-snapshot validate
21
20
  ```
22
21
 
23
- `describe` returns the invocation that works, an ordered `workflow` array, every command,
24
- every option with its default, the exit-code contract, all 25 presets, the artifact list,
25
- the config file names, the output layout, safety rules and a troubleshooting table — all
26
- as JSON on stdout.
22
+ The bare package name works; pin `@latest` or `@4` when the same version must run on every
23
+ machine.
27
24
 
28
- **Always pin the tag** (`@latest` or `@2`). A bare scoped spec can resolve to a bin-less
29
- placeholder manifest on some npm builds and exit 0 printing nothing; the pinned form
30
- cannot.
25
+ ## The contract
26
+
27
+ `describe --format json` returns the whole tool as data on stdout:
28
+
29
+ | Field | Contents |
30
+ | :--- | :--- |
31
+ | `invocation` | The supported one-shot and installed commands, and the bin names. |
32
+ | `workflow` | An ordered six-step procedure: verify → understand → configure → generate → verify output → consume. |
33
+ | `commands` | Every command with what it does, its positionals and its exit codes. |
34
+ | `options` | Every option with its type, default, which commands it applies to, and a summary. |
35
+ | `exitCodes` | `0` success, `1` drift or refused generation, `2` usage error. |
36
+ | `presets` | All 25, with priority, recommended budget and ecosystem ignores. |
37
+ | `artifacts` | Each artifact with its on/off default and what it contains. |
38
+ | `config` | Every config filename and every setting with its default. |
39
+ | `output` | The directory layout and how to read `manifest.json`. |
40
+ | `safety` | What is excluded or redacted, and which directories are refused. |
41
+ | `troubleshooting` | Symptom → cause → fix. |
42
+
43
+ It is generated from the same code the CLI runs — presets, artifact names and versions are
44
+ read directly — and a test compares its documented defaults against a real
45
+ `generate --dry-run` manifest. If a default changes, the test fails rather than the contract
46
+ silently misleading an agent.
47
+
48
+ `describe` with no `--format` renders the same content for a human.
31
49
 
32
50
  ## The skill
33
51
 
34
52
  ```bash
35
- npx @ankurhalder/codebase-snapshot@latest skill # print it (stdout)
36
- npx @ankurhalder/codebase-snapshot@latest skill --install # write it into the repo
53
+ npx @ankurhalder/codebase-snapshot skill # print it (stdout)
54
+ npx @ankurhalder/codebase-snapshot skill --install # write it into the repository
37
55
  ```
38
56
 
39
57
  Printing goes to stdout deliberately, so it can be redirected:
40
58
 
41
59
  ```bash
42
- npx @ankurhalder/codebase-snapshot@latest skill > SKILL.md
60
+ npx @ankurhalder/codebase-snapshot skill > SKILL.md
43
61
  ```
44
62
 
45
63
  `--install` writes the same skill to every location agents look at by default:
@@ -51,30 +69,52 @@ npx @ankurhalder/codebase-snapshot@latest skill > SKILL.md
51
69
  | `cursor` | `.cursor/rules/codebase-snapshot.mdc` | Cursor rules |
52
70
 
53
71
  ```bash
54
- skill --install --target claude # one location
55
- skill --install # all three (default)
72
+ npx @ankurhalder/codebase-snapshot skill --install --target claude # one location
73
+ npx @ankurhalder/codebase-snapshot skill --install # all three (default)
56
74
  ```
57
75
 
58
- The install is **idempotent and non-destructive**: re-running replaces only the block
59
- between `<!-- codebase-snapshot:skill:start -->` and
60
- `<!-- codebase-snapshot:skill:end -->`, so an `AGENTS.md` that already documents your
61
- conventions keeps them. `--format json` reports what was created, updated or unchanged.
76
+ The install is **idempotent and non-destructive**: re-running replaces only the block between
77
+ `<!-- codebase-snapshot:skill:start -->` and `<!-- codebase-snapshot:skill:end -->`, so an
78
+ `AGENTS.md` that already documents your conventions keeps them. `--format json` reports what
79
+ was created, updated or unchanged.
62
80
 
63
- `snap init` prints a reminder about this; it never installs the skill on its own.
81
+ `codebase-snapshot init` prints a reminder about this; it never installs the skill on its own.
64
82
 
65
83
  ## Why both a contract and a skill
66
84
 
67
- The contract is for **doing** — it is the data an agent needs to pick correct commands and
68
- options, and it is asserted against the real defaults in the test suite so it cannot drift
69
- into lying.
70
-
85
+ The contract is for **doing** — the data an agent needs to pick correct commands and options.
71
86
  The skill is for **knowing when to act** — it carries the trigger conditions, the ordered
72
- workflow, and the surprises (sensitive files excluded by default, secrets redacted, the
73
- output directory being tool-owned) that an agent cannot infer from a schema.
74
-
75
- ## Keeping the contract honest
76
-
77
- `describe` is generated from the same code the CLI runs — the preset list, artifact names,
78
- schema version and generator version are read directly, and a test compares its documented
79
- defaults against a real `generate --dry-run` manifest. If a default changes, the test fails
80
- rather than the contract silently misleading an agent.
87
+ workflow, and the surprises an agent cannot infer from a schema: sensitive files excluded by
88
+ default, secrets redacted, the output directory being tool-owned.
89
+
90
+ ## What an agent should avoid
91
+
92
+ - **Do not point `--out-dir` at source code.** It is refused, and the refusal is deliberate.
93
+ - **Do not add `--include-sensitive-files`** unless the user explicitly asked for credential
94
+ files; by default they are excluded and recorded as `sensitive-file` in `manifest.skipped[]`.
95
+ - **Do not work around redaction.** If a value is masked, ask the user rather than reading the
96
+ original file.
97
+ - **Do not assume exit 2 means the repository is broken.** It means the command was wrong.
98
+ Re-read the contract.
99
+ - **Do not treat a bare `npx` that prints nothing as a repository problem.** A stale global
100
+ install shadows `npx`; see [Troubleshooting](troubleshooting.md#the-command-does-nothing).
101
+
102
+ ## Verifying an agent can drive it
103
+
104
+ `tests/agent-simulation.test.js` runs the published instructions as an agent would: read
105
+ `--help`, discover `describe`, then execute the six workflow steps in order against an 8-file
106
+ Django fixture.
107
+
108
+ It asserts what the contract promises at each step: `--version` prints a version, every
109
+ published option states where it applies, `init --yes` writes a config naming **every**
110
+ setting the contract lists, `generate --format json` exits `0` with
111
+ `validation.status: "passed"` and the preset `python-django`, every fixture file is either in
112
+ `manifest.files[]` or explained in `manifest.skipped[]`, each `files[]` entry carries its
113
+ classification and hash, and `validate` then exits `0`. If an agent following the published
114
+ words would fail, that test fails.
115
+
116
+ When you parse the manifest, remember what is conditional: `manifest.files[]` carries
117
+ `relativePath`, `layer`, `kind`, `language`, `part`, `inclusionMode` and `hashSha256` for
118
+ every included file, but `summary` and `summaryQuality` only for a language the tool can
119
+ analyse. A `.gitignore` or plain-text entry has neither key, and a file whose summary was too
120
+ large for its part carries `summaryOmitted: true` instead of a `summary`.