@ankurhalder/codebase-snapshot 2.1.0 → 2.1.1

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/bin.js CHANGED
@@ -52,7 +52,13 @@ function compareManifests(before, after) {
52
52
  };
53
53
  }
54
54
  function loadManifest(target) {
55
- const manifestPath = fs.statSync(target).isDirectory() ? path.join(target, "manifest.json") : target;
55
+ let isDirectory;
56
+ try {
57
+ isDirectory = fs.statSync(target).isDirectory();
58
+ } catch {
59
+ throw new Error(`No snapshot found at ${target}: the path does not exist.`);
60
+ }
61
+ const manifestPath = isDirectory ? path.join(target, "manifest.json") : target;
56
62
  let raw;
57
63
  try {
58
64
  raw = fs.readFileSync(manifestPath, "utf8");
@@ -68,6 +74,9 @@ function loadManifest(target) {
68
74
  if (!parsed || !Array.isArray(parsed.files)) {
69
75
  throw new Error(`${manifestPath} is not a snapshot manifest (no "files" array).`);
70
76
  }
77
+ if (!parsed.counts || typeof parsed.counts !== "object") {
78
+ throw new Error(`${manifestPath} is not a snapshot manifest (no "counts" object).`);
79
+ }
71
80
  return parsed;
72
81
  }
73
82
  function renderDiffText(diff) {
@@ -948,11 +957,12 @@ import * as fs3 from "fs";
948
957
  import * as path3 from "path";
949
958
  function getGitInfo(cwd, fallbackBranch = "main") {
950
959
  try {
951
- const branch = childProcess.execSync("git rev-parse --abbrev-ref HEAD", {
960
+ const reported = childProcess.execSync("git rev-parse --abbrev-ref HEAD", {
952
961
  cwd,
953
962
  stdio: ["ignore", "pipe", "ignore"],
954
963
  encoding: "utf8"
955
- }).trim() || fallbackBranch;
964
+ }).trim();
965
+ const branch = !reported || reported === "HEAD" ? fallbackBranch : reported;
956
966
  const commit = childProcess.execSync("git rev-parse HEAD", {
957
967
  cwd,
958
968
  stdio: ["ignore", "pipe", "ignore"],
@@ -1573,6 +1583,9 @@ async function loadConfigFileDetailed(cwd) {
1573
1583
  if (snapshotKey && typeof snapshotKey === "object") {
1574
1584
  return { config: snapshotKey, file: pkgJsonPath, warnings, errors };
1575
1585
  }
1586
+ if (snapshotKey !== void 0) {
1587
+ warnings.push(`${pkgJsonPath}: the "snapshot" key must be an object; it was ignored.`);
1588
+ }
1576
1589
  } catch (err) {
1577
1590
  const message = err.message;
1578
1591
  warnings.push(message);
@@ -4295,6 +4308,7 @@ function validateSnapshot(configInput) {
4295
4308
  drift.missingOnDisk.push(snapshotFile);
4296
4309
  }
4297
4310
  }
4311
+ drift.missingOnDisk = [...new Set(drift.missingOnDisk)];
4298
4312
  const maxEffectiveBudget = Math.max(
4299
4313
  config.maxPartBytes,
4300
4314
  config.budgets.default,
@@ -4554,7 +4568,7 @@ function validateSnapshot(configInput) {
4554
4568
 
4555
4569
  // src/index.ts
4556
4570
  var GENERATOR_NAME = "@ankurhalder/codebase-snapshot";
4557
- var GENERATOR_VERSION = "2.1.0";
4571
+ var GENERATOR_VERSION = "2.1.1";
4558
4572
  var SCHEMA_VERSION = "3.0.0";
4559
4573
  function sha256Hex2(input) {
4560
4574
  return crypto3.createHash("sha256").update(input).digest("hex");
@@ -4798,7 +4812,9 @@ function artifactWarnings(artifacts) {
4798
4812
  );
4799
4813
  }
4800
4814
  if (ARTIFACT_NAMES.every((name) => artifacts[name] === false)) {
4801
- messages.push("every artifact is disabled: the snapshot directory will be empty.");
4815
+ messages.push(
4816
+ "every artifact is disabled: only the .codebase-snapshot ownership marker will be written."
4817
+ );
4802
4818
  }
4803
4819
  return messages;
4804
4820
  }
@@ -5253,9 +5269,32 @@ function parseCliArgs(argv) {
5253
5269
  strict: true
5254
5270
  });
5255
5271
  } catch (err) {
5256
- throw new UsageError(err.message);
5272
+ const message = err.message;
5273
+ const ambiguous = /^Option '(--[^']+)' argument is ambiguous/i.exec(message);
5274
+ if (ambiguous) {
5275
+ throw new UsageError(
5276
+ `${ambiguous[1]} received a value that looks like another flag (for example a negative number). Use the "--option=value" form, and note that sizes must be positive.`
5277
+ );
5278
+ }
5279
+ throw new UsageError(message);
5257
5280
  }
5258
5281
  const values = parsed.values;
5282
+ for (const [name, spec] of Object.entries(CLI_OPTIONS)) {
5283
+ if (spec.type !== "string") continue;
5284
+ const raw = values[name];
5285
+ if (raw !== void 0 && String(raw).trim() === "") {
5286
+ throw new UsageError(`--${name} requires a value (received an empty string).`);
5287
+ }
5288
+ }
5289
+ if (values.cwd !== void 0) {
5290
+ const target = String(values.cwd);
5291
+ if (!fs10.existsSync(target)) {
5292
+ throw new UsageError(`--cwd directory does not exist: ${target}`);
5293
+ }
5294
+ if (!fs10.statSync(target).isDirectory()) {
5295
+ throw new UsageError(`--cwd is not a directory: ${target}`);
5296
+ }
5297
+ }
5259
5298
  if (values.help) {
5260
5299
  process.stdout.write(`${USAGE}
5261
5300
  `);
@@ -5473,6 +5512,9 @@ function renderValidateText(result) {
5473
5512
  for (const missing of result.drift.missingOnDisk.slice(0, 20)) {
5474
5513
  warn(` - deleted: ${missing}`);
5475
5514
  }
5515
+ for (const mismatch of result.drift.totalsMismatches.slice(0, 20)) {
5516
+ warn(` - counts: ${mismatch}`);
5517
+ }
5476
5518
  }
5477
5519
  for (const warning of result.warnings) warn(` \u26A0 ${warning}`);
5478
5520
  }
@@ -5603,9 +5645,23 @@ ${USAGE}
5603
5645
  info(
5604
5646
  ` - Total size: ${(manifest.counts.totalSizeBytes / (1024 * 1024)).toFixed(2)} MB`
5605
5647
  );
5606
- info(` - Parts written: ${manifest.counts.partsCount} under ${resolved.outDir}/parts/`);
5607
- info(` - Reports: ${resolved.outDir}/reports/ (project-tree, navigation, audit)`);
5608
- info(` - Manifest: ${resolved.outDir}/manifest.json`);
5648
+ const { artifacts } = resolved;
5649
+ if (artifacts.parts) {
5650
+ info(` - Parts written: ${manifest.counts.partsCount} under ${resolved.outDir}/parts/`);
5651
+ }
5652
+ const reports = [
5653
+ artifacts.projectTree && "project-tree",
5654
+ artifacts.navigation && "navigation",
5655
+ artifacts.audit && "audit"
5656
+ ].filter(Boolean);
5657
+ if (reports.length > 0) {
5658
+ info(` - Reports: ${resolved.outDir}/reports/ (${reports.join(", ")})`);
5659
+ }
5660
+ if (artifacts.manifest) info(` - Manifest: ${resolved.outDir}/manifest.json`);
5661
+ if (artifacts.monolith) info(` - Monolith: ${resolved.outDir}/full-codebase.{json,md}`);
5662
+ if (reports.length === 0 && !artifacts.parts && !artifacts.manifest && !artifacts.monolith) {
5663
+ info(` - Artifacts: all disabled (only the ownership marker was written)`);
5664
+ }
5609
5665
  info(` - Fingerprint: ${manifest.fingerprint}`);
5610
5666
  info(` - Integrity: ${manifest.validation.status}`);
5611
5667
  if (manifest.warnings.length > 0) {
@@ -5622,6 +5678,14 @@ ${USAGE}
5622
5678
  }
5623
5679
  return manifest.validation.status === "passed" ? 0 : 1;
5624
5680
  } catch (err) {
5681
+ const code = err.code;
5682
+ if (code === "EPERM" || code === "EACCES") {
5683
+ warn(
5684
+ `\u274C Cannot write the snapshot: permission denied under ${resolved.outDir}. Check the directory's permissions or choose another --out-dir.`
5685
+ );
5686
+ warn(` (${err.message})`);
5687
+ return 1;
5688
+ }
5625
5689
  warn(`\u274C Failed to generate snapshot: ${err.message}`);
5626
5690
  return 1;
5627
5691
  }
@@ -1678,11 +1678,12 @@ function detectPreset(cwd, forced) {
1678
1678
  }
1679
1679
  function getGitInfo(cwd, fallbackBranch = "main") {
1680
1680
  try {
1681
- const branch = childProcess.execSync("git rev-parse --abbrev-ref HEAD", {
1681
+ const reported = childProcess.execSync("git rev-parse --abbrev-ref HEAD", {
1682
1682
  cwd,
1683
1683
  stdio: ["ignore", "pipe", "ignore"],
1684
1684
  encoding: "utf8"
1685
- }).trim() || fallbackBranch;
1685
+ }).trim();
1686
+ const branch = !reported || reported === "HEAD" ? fallbackBranch : reported;
1686
1687
  const commit = childProcess.execSync("git rev-parse HEAD", {
1687
1688
  cwd,
1688
1689
  stdio: ["ignore", "pipe", "ignore"],
@@ -2303,6 +2304,9 @@ async function loadConfigFileDetailed(cwd) {
2303
2304
  if (snapshotKey && typeof snapshotKey === "object") {
2304
2305
  return { config: snapshotKey, file: pkgJsonPath, warnings, errors };
2305
2306
  }
2307
+ if (snapshotKey !== void 0) {
2308
+ warnings.push(`${pkgJsonPath}: the "snapshot" key must be an object; it was ignored.`);
2309
+ }
2306
2310
  } catch (err) {
2307
2311
  const message = err.message;
2308
2312
  warnings.push(message);
@@ -3417,6 +3421,7 @@ function validateSnapshot(configInput) {
3417
3421
  drift.missingOnDisk.push(snapshotFile);
3418
3422
  }
3419
3423
  }
3424
+ drift.missingOnDisk = [...new Set(drift.missingOnDisk)];
3420
3425
  const maxEffectiveBudget = Math.max(
3421
3426
  config.maxPartBytes,
3422
3427
  config.budgets.default,
@@ -3828,7 +3833,13 @@ function compareManifests(before, after) {
3828
3833
  };
3829
3834
  }
3830
3835
  function loadManifest(target) {
3831
- const manifestPath = fs9.statSync(target).isDirectory() ? path10.join(target, "manifest.json") : target;
3836
+ let isDirectory;
3837
+ try {
3838
+ isDirectory = fs9.statSync(target).isDirectory();
3839
+ } catch {
3840
+ throw new Error(`No snapshot found at ${target}: the path does not exist.`);
3841
+ }
3842
+ const manifestPath = isDirectory ? path10.join(target, "manifest.json") : target;
3832
3843
  let raw;
3833
3844
  try {
3834
3845
  raw = fs9.readFileSync(manifestPath, "utf8");
@@ -3844,6 +3855,9 @@ function loadManifest(target) {
3844
3855
  if (!parsed || !Array.isArray(parsed.files)) {
3845
3856
  throw new Error(`${manifestPath} is not a snapshot manifest (no "files" array).`);
3846
3857
  }
3858
+ if (!parsed.counts || typeof parsed.counts !== "object") {
3859
+ throw new Error(`${manifestPath} is not a snapshot manifest (no "counts" object).`);
3860
+ }
3847
3861
  return parsed;
3848
3862
  }
3849
3863
  function renderDiffText(diff) {
@@ -4514,7 +4528,7 @@ async function runInit(options) {
4514
4528
 
4515
4529
  // src/index.ts
4516
4530
  var GENERATOR_NAME = "@ankurhalder/codebase-snapshot";
4517
- var GENERATOR_VERSION = "2.1.0";
4531
+ var GENERATOR_VERSION = "2.1.1";
4518
4532
  var SCHEMA_VERSION = "3.0.0";
4519
4533
  function sha256Hex2(input) {
4520
4534
  return crypto.createHash("sha256").update(input).digest("hex");
@@ -4758,7 +4772,9 @@ function artifactWarnings(artifacts) {
4758
4772
  );
4759
4773
  }
4760
4774
  if (ARTIFACT_NAMES.every((name) => artifacts[name] === false)) {
4761
- messages.push("every artifact is disabled: the snapshot directory will be empty.");
4775
+ messages.push(
4776
+ "every artifact is disabled: only the .codebase-snapshot ownership marker will be written."
4777
+ );
4762
4778
  }
4763
4779
  return messages;
4764
4780
  }
@@ -5089,5 +5105,5 @@ async function generateSnapshotAsync(configInput, options) {
5089
5105
  }
5090
5106
 
5091
5107
  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, unknownPresetMessage, validateSnapshot };
5092
- //# sourceMappingURL=chunk-QEIOXPMU.js.map
5093
- //# sourceMappingURL=chunk-QEIOXPMU.js.map
5108
+ //# sourceMappingURL=chunk-SUOJL5EX.js.map
5109
+ //# sourceMappingURL=chunk-SUOJL5EX.js.map
package/dist/cli.cjs CHANGED
@@ -81,7 +81,13 @@ function compareManifests(before, after) {
81
81
  };
82
82
  }
83
83
  function loadManifest(target) {
84
- const manifestPath = fs9__namespace.statSync(target).isDirectory() ? path10__namespace.join(target, "manifest.json") : target;
84
+ let isDirectory;
85
+ try {
86
+ isDirectory = fs9__namespace.statSync(target).isDirectory();
87
+ } catch {
88
+ throw new Error(`No snapshot found at ${target}: the path does not exist.`);
89
+ }
90
+ const manifestPath = isDirectory ? path10__namespace.join(target, "manifest.json") : target;
85
91
  let raw;
86
92
  try {
87
93
  raw = fs9__namespace.readFileSync(manifestPath, "utf8");
@@ -97,6 +103,9 @@ function loadManifest(target) {
97
103
  if (!parsed || !Array.isArray(parsed.files)) {
98
104
  throw new Error(`${manifestPath} is not a snapshot manifest (no "files" array).`);
99
105
  }
106
+ if (!parsed.counts || typeof parsed.counts !== "object") {
107
+ throw new Error(`${manifestPath} is not a snapshot manifest (no "counts" object).`);
108
+ }
100
109
  return parsed;
101
110
  }
102
111
  function renderDiffText(diff) {
@@ -959,11 +968,12 @@ function detectPreset(cwd, forced) {
959
968
  }
960
969
  function getGitInfo(cwd, fallbackBranch = "main") {
961
970
  try {
962
- const branch = childProcess__namespace.execSync("git rev-parse --abbrev-ref HEAD", {
971
+ const reported = childProcess__namespace.execSync("git rev-parse --abbrev-ref HEAD", {
963
972
  cwd,
964
973
  stdio: ["ignore", "pipe", "ignore"],
965
974
  encoding: "utf8"
966
- }).trim() || fallbackBranch;
975
+ }).trim();
976
+ const branch = !reported || reported === "HEAD" ? fallbackBranch : reported;
967
977
  const commit = childProcess__namespace.execSync("git rev-parse HEAD", {
968
978
  cwd,
969
979
  stdio: ["ignore", "pipe", "ignore"],
@@ -1584,6 +1594,9 @@ async function loadConfigFileDetailed(cwd) {
1584
1594
  if (snapshotKey && typeof snapshotKey === "object") {
1585
1595
  return { config: snapshotKey, file: pkgJsonPath, warnings, errors };
1586
1596
  }
1597
+ if (snapshotKey !== void 0) {
1598
+ warnings.push(`${pkgJsonPath}: the "snapshot" key must be an object; it was ignored.`);
1599
+ }
1587
1600
  } catch (err) {
1588
1601
  const message = err.message;
1589
1602
  warnings.push(message);
@@ -4274,6 +4287,7 @@ function validateSnapshot(configInput) {
4274
4287
  drift.missingOnDisk.push(snapshotFile);
4275
4288
  }
4276
4289
  }
4290
+ drift.missingOnDisk = [...new Set(drift.missingOnDisk)];
4277
4291
  const maxEffectiveBudget = Math.max(
4278
4292
  config.maxPartBytes,
4279
4293
  config.budgets.default,
@@ -4533,7 +4547,7 @@ function validateSnapshot(configInput) {
4533
4547
 
4534
4548
  // src/index.ts
4535
4549
  var GENERATOR_NAME = "@ankurhalder/codebase-snapshot";
4536
- var GENERATOR_VERSION = "2.1.0";
4550
+ var GENERATOR_VERSION = "2.1.1";
4537
4551
  var SCHEMA_VERSION = "3.0.0";
4538
4552
  function sha256Hex2(input) {
4539
4553
  return crypto__namespace.createHash("sha256").update(input).digest("hex");
@@ -4777,7 +4791,9 @@ function artifactWarnings(artifacts) {
4777
4791
  );
4778
4792
  }
4779
4793
  if (ARTIFACT_NAMES.every((name) => artifacts[name] === false)) {
4780
- messages.push("every artifact is disabled: the snapshot directory will be empty.");
4794
+ messages.push(
4795
+ "every artifact is disabled: only the .codebase-snapshot ownership marker will be written."
4796
+ );
4781
4797
  }
4782
4798
  return messages;
4783
4799
  }
@@ -5244,9 +5260,32 @@ function parseCliArgs(argv) {
5244
5260
  strict: true
5245
5261
  });
5246
5262
  } catch (err) {
5247
- throw new UsageError(err.message);
5263
+ const message = err.message;
5264
+ const ambiguous = /^Option '(--[^']+)' argument is ambiguous/i.exec(message);
5265
+ if (ambiguous) {
5266
+ throw new UsageError(
5267
+ `${ambiguous[1]} received a value that looks like another flag (for example a negative number). Use the "--option=value" form, and note that sizes must be positive.`
5268
+ );
5269
+ }
5270
+ throw new UsageError(message);
5248
5271
  }
5249
5272
  const values = parsed.values;
5273
+ for (const [name, spec] of Object.entries(CLI_OPTIONS)) {
5274
+ if (spec.type !== "string") continue;
5275
+ const raw = values[name];
5276
+ if (raw !== void 0 && String(raw).trim() === "") {
5277
+ throw new UsageError(`--${name} requires a value (received an empty string).`);
5278
+ }
5279
+ }
5280
+ if (values.cwd !== void 0) {
5281
+ const target = String(values.cwd);
5282
+ if (!fs9__namespace.existsSync(target)) {
5283
+ throw new UsageError(`--cwd directory does not exist: ${target}`);
5284
+ }
5285
+ if (!fs9__namespace.statSync(target).isDirectory()) {
5286
+ throw new UsageError(`--cwd is not a directory: ${target}`);
5287
+ }
5288
+ }
5250
5289
  if (values.help) {
5251
5290
  process.stdout.write(`${USAGE}
5252
5291
  `);
@@ -5464,6 +5503,9 @@ function renderValidateText(result) {
5464
5503
  for (const missing of result.drift.missingOnDisk.slice(0, 20)) {
5465
5504
  warn(` - deleted: ${missing}`);
5466
5505
  }
5506
+ for (const mismatch of result.drift.totalsMismatches.slice(0, 20)) {
5507
+ warn(` - counts: ${mismatch}`);
5508
+ }
5467
5509
  }
5468
5510
  for (const warning of result.warnings) warn(` \u26A0 ${warning}`);
5469
5511
  }
@@ -5594,9 +5636,23 @@ ${USAGE}
5594
5636
  info(
5595
5637
  ` - Total size: ${(manifest.counts.totalSizeBytes / (1024 * 1024)).toFixed(2)} MB`
5596
5638
  );
5597
- info(` - Parts written: ${manifest.counts.partsCount} under ${resolved.outDir}/parts/`);
5598
- info(` - Reports: ${resolved.outDir}/reports/ (project-tree, navigation, audit)`);
5599
- info(` - Manifest: ${resolved.outDir}/manifest.json`);
5639
+ const { artifacts } = resolved;
5640
+ if (artifacts.parts) {
5641
+ info(` - Parts written: ${manifest.counts.partsCount} under ${resolved.outDir}/parts/`);
5642
+ }
5643
+ const reports = [
5644
+ artifacts.projectTree && "project-tree",
5645
+ artifacts.navigation && "navigation",
5646
+ artifacts.audit && "audit"
5647
+ ].filter(Boolean);
5648
+ if (reports.length > 0) {
5649
+ info(` - Reports: ${resolved.outDir}/reports/ (${reports.join(", ")})`);
5650
+ }
5651
+ if (artifacts.manifest) info(` - Manifest: ${resolved.outDir}/manifest.json`);
5652
+ if (artifacts.monolith) info(` - Monolith: ${resolved.outDir}/full-codebase.{json,md}`);
5653
+ if (reports.length === 0 && !artifacts.parts && !artifacts.manifest && !artifacts.monolith) {
5654
+ info(` - Artifacts: all disabled (only the ownership marker was written)`);
5655
+ }
5600
5656
  info(` - Fingerprint: ${manifest.fingerprint}`);
5601
5657
  info(` - Integrity: ${manifest.validation.status}`);
5602
5658
  if (manifest.warnings.length > 0) {
@@ -5613,6 +5669,14 @@ ${USAGE}
5613
5669
  }
5614
5670
  return manifest.validation.status === "passed" ? 0 : 1;
5615
5671
  } catch (err) {
5672
+ const code = err.code;
5673
+ if (code === "EPERM" || code === "EACCES") {
5674
+ warn(
5675
+ `\u274C Cannot write the snapshot: permission denied under ${resolved.outDir}. Check the directory's permissions or choose another --out-dir.`
5676
+ );
5677
+ warn(` (${err.message})`);
5678
+ return 1;
5679
+ }
5616
5680
  warn(`\u274C Failed to generate snapshot: ${err.message}`);
5617
5681
  return 1;
5618
5682
  }
package/dist/cli.js CHANGED
@@ -1,4 +1,4 @@
1
- import { parseByteSize, GENERATOR_VERSION, ARTIFACT_NAMES, getPreset, unknownPresetMessage, MIN_MAX_PART_BYTES, loadConfigFileDetailed, resolveConfig, validateSnapshot, explainPath, compareManifests, loadManifest, renderDiffText, generateSnapshot, GENERATOR_NAME, runInit, PromptCancelled, renderExplainText } from './chunk-QEIOXPMU.js';
1
+ import { parseByteSize, GENERATOR_VERSION, ARTIFACT_NAMES, getPreset, unknownPresetMessage, MIN_MAX_PART_BYTES, loadConfigFileDetailed, resolveConfig, validateSnapshot, explainPath, compareManifests, loadManifest, renderDiffText, generateSnapshot, GENERATOR_NAME, runInit, PromptCancelled, renderExplainText } from './chunk-SUOJL5EX.js';
2
2
  import * as fs from 'fs';
3
3
  import * as path from 'path';
4
4
  import { parseArgs } from 'util';
@@ -142,9 +142,32 @@ function parseCliArgs(argv) {
142
142
  strict: true
143
143
  });
144
144
  } catch (err) {
145
- throw new UsageError(err.message);
145
+ const message = err.message;
146
+ const ambiguous = /^Option '(--[^']+)' argument is ambiguous/i.exec(message);
147
+ if (ambiguous) {
148
+ throw new UsageError(
149
+ `${ambiguous[1]} received a value that looks like another flag (for example a negative number). Use the "--option=value" form, and note that sizes must be positive.`
150
+ );
151
+ }
152
+ throw new UsageError(message);
146
153
  }
147
154
  const values = parsed.values;
155
+ for (const [name, spec] of Object.entries(CLI_OPTIONS)) {
156
+ if (spec.type !== "string") continue;
157
+ const raw = values[name];
158
+ if (raw !== void 0 && String(raw).trim() === "") {
159
+ throw new UsageError(`--${name} requires a value (received an empty string).`);
160
+ }
161
+ }
162
+ if (values.cwd !== void 0) {
163
+ const target = String(values.cwd);
164
+ if (!fs.existsSync(target)) {
165
+ throw new UsageError(`--cwd directory does not exist: ${target}`);
166
+ }
167
+ if (!fs.statSync(target).isDirectory()) {
168
+ throw new UsageError(`--cwd is not a directory: ${target}`);
169
+ }
170
+ }
148
171
  if (values.help) {
149
172
  process.stdout.write(`${USAGE}
150
173
  `);
@@ -362,6 +385,9 @@ function renderValidateText(result) {
362
385
  for (const missing of result.drift.missingOnDisk.slice(0, 20)) {
363
386
  warn(` - deleted: ${missing}`);
364
387
  }
388
+ for (const mismatch of result.drift.totalsMismatches.slice(0, 20)) {
389
+ warn(` - counts: ${mismatch}`);
390
+ }
365
391
  }
366
392
  for (const warning of result.warnings) warn(` \u26A0 ${warning}`);
367
393
  }
@@ -492,9 +518,23 @@ ${USAGE}
492
518
  info(
493
519
  ` - Total size: ${(manifest.counts.totalSizeBytes / (1024 * 1024)).toFixed(2)} MB`
494
520
  );
495
- info(` - Parts written: ${manifest.counts.partsCount} under ${resolved.outDir}/parts/`);
496
- info(` - Reports: ${resolved.outDir}/reports/ (project-tree, navigation, audit)`);
497
- info(` - Manifest: ${resolved.outDir}/manifest.json`);
521
+ const { artifacts } = resolved;
522
+ if (artifacts.parts) {
523
+ info(` - Parts written: ${manifest.counts.partsCount} under ${resolved.outDir}/parts/`);
524
+ }
525
+ const reports = [
526
+ artifacts.projectTree && "project-tree",
527
+ artifacts.navigation && "navigation",
528
+ artifacts.audit && "audit"
529
+ ].filter(Boolean);
530
+ if (reports.length > 0) {
531
+ info(` - Reports: ${resolved.outDir}/reports/ (${reports.join(", ")})`);
532
+ }
533
+ if (artifacts.manifest) info(` - Manifest: ${resolved.outDir}/manifest.json`);
534
+ if (artifacts.monolith) info(` - Monolith: ${resolved.outDir}/full-codebase.{json,md}`);
535
+ if (reports.length === 0 && !artifacts.parts && !artifacts.manifest && !artifacts.monolith) {
536
+ info(` - Artifacts: all disabled (only the ownership marker was written)`);
537
+ }
498
538
  info(` - Fingerprint: ${manifest.fingerprint}`);
499
539
  info(` - Integrity: ${manifest.validation.status}`);
500
540
  if (manifest.warnings.length > 0) {
@@ -511,6 +551,14 @@ ${USAGE}
511
551
  }
512
552
  return manifest.validation.status === "passed" ? 0 : 1;
513
553
  } catch (err) {
554
+ const code = err.code;
555
+ if (code === "EPERM" || code === "EACCES") {
556
+ warn(
557
+ `\u274C Cannot write the snapshot: permission denied under ${resolved.outDir}. Check the directory's permissions or choose another --out-dir.`
558
+ );
559
+ warn(` (${err.message})`);
560
+ return 1;
561
+ }
514
562
  warn(`\u274C Failed to generate snapshot: ${err.message}`);
515
563
  return 1;
516
564
  }
package/dist/index.cjs CHANGED
@@ -1707,11 +1707,12 @@ function detectPreset(cwd, forced) {
1707
1707
  }
1708
1708
  function getGitInfo(cwd, fallbackBranch = "main") {
1709
1709
  try {
1710
- const branch = childProcess__namespace.execSync("git rev-parse --abbrev-ref HEAD", {
1710
+ const reported = childProcess__namespace.execSync("git rev-parse --abbrev-ref HEAD", {
1711
1711
  cwd,
1712
1712
  stdio: ["ignore", "pipe", "ignore"],
1713
1713
  encoding: "utf8"
1714
- }).trim() || fallbackBranch;
1714
+ }).trim();
1715
+ const branch = !reported || reported === "HEAD" ? fallbackBranch : reported;
1715
1716
  const commit = childProcess__namespace.execSync("git rev-parse HEAD", {
1716
1717
  cwd,
1717
1718
  stdio: ["ignore", "pipe", "ignore"],
@@ -2332,6 +2333,9 @@ async function loadConfigFileDetailed(cwd) {
2332
2333
  if (snapshotKey && typeof snapshotKey === "object") {
2333
2334
  return { config: snapshotKey, file: pkgJsonPath, warnings, errors };
2334
2335
  }
2336
+ if (snapshotKey !== void 0) {
2337
+ warnings.push(`${pkgJsonPath}: the "snapshot" key must be an object; it was ignored.`);
2338
+ }
2335
2339
  } catch (err) {
2336
2340
  const message = err.message;
2337
2341
  warnings.push(message);
@@ -3446,6 +3450,7 @@ function validateSnapshot(configInput) {
3446
3450
  drift.missingOnDisk.push(snapshotFile);
3447
3451
  }
3448
3452
  }
3453
+ drift.missingOnDisk = [...new Set(drift.missingOnDisk)];
3449
3454
  const maxEffectiveBudget = Math.max(
3450
3455
  config.maxPartBytes,
3451
3456
  config.budgets.default,
@@ -3857,7 +3862,13 @@ function compareManifests(before, after) {
3857
3862
  };
3858
3863
  }
3859
3864
  function loadManifest(target) {
3860
- const manifestPath = fs9__namespace.statSync(target).isDirectory() ? path10__namespace.join(target, "manifest.json") : target;
3865
+ let isDirectory;
3866
+ try {
3867
+ isDirectory = fs9__namespace.statSync(target).isDirectory();
3868
+ } catch {
3869
+ throw new Error(`No snapshot found at ${target}: the path does not exist.`);
3870
+ }
3871
+ const manifestPath = isDirectory ? path10__namespace.join(target, "manifest.json") : target;
3861
3872
  let raw;
3862
3873
  try {
3863
3874
  raw = fs9__namespace.readFileSync(manifestPath, "utf8");
@@ -3873,6 +3884,9 @@ function loadManifest(target) {
3873
3884
  if (!parsed || !Array.isArray(parsed.files)) {
3874
3885
  throw new Error(`${manifestPath} is not a snapshot manifest (no "files" array).`);
3875
3886
  }
3887
+ if (!parsed.counts || typeof parsed.counts !== "object") {
3888
+ throw new Error(`${manifestPath} is not a snapshot manifest (no "counts" object).`);
3889
+ }
3876
3890
  return parsed;
3877
3891
  }
3878
3892
  function renderDiffText(diff) {
@@ -4543,7 +4557,7 @@ async function runInit(options) {
4543
4557
 
4544
4558
  // src/index.ts
4545
4559
  var GENERATOR_NAME = "@ankurhalder/codebase-snapshot";
4546
- var GENERATOR_VERSION = "2.1.0";
4560
+ var GENERATOR_VERSION = "2.1.1";
4547
4561
  var SCHEMA_VERSION = "3.0.0";
4548
4562
  function sha256Hex2(input) {
4549
4563
  return crypto__namespace.createHash("sha256").update(input).digest("hex");
@@ -4787,7 +4801,9 @@ function artifactWarnings(artifacts) {
4787
4801
  );
4788
4802
  }
4789
4803
  if (ARTIFACT_NAMES.every((name) => artifacts[name] === false)) {
4790
- messages.push("every artifact is disabled: the snapshot directory will be empty.");
4804
+ messages.push(
4805
+ "every artifact is disabled: only the .codebase-snapshot ownership marker will be written."
4806
+ );
4791
4807
  }
4792
4808
  return messages;
4793
4809
  }
package/dist/index.d.cts CHANGED
@@ -497,7 +497,7 @@ declare function matchesExclude(relativePath: string, pattern: string): boolean;
497
497
  declare function collectFiles(cwd: string, options?: CollectOptions): CollectResult;
498
498
 
499
499
  declare const GENERATOR_NAME = "@ankurhalder/codebase-snapshot";
500
- declare const GENERATOR_VERSION = "2.1.0";
500
+ declare const GENERATOR_VERSION = "2.1.1";
501
501
  declare const SCHEMA_VERSION = "3.0.0";
502
502
  interface GenerateOptions {
503
503
  /** Compute the full manifest but write nothing to disk. */
package/dist/index.d.ts CHANGED
@@ -497,7 +497,7 @@ declare function matchesExclude(relativePath: string, pattern: string): boolean;
497
497
  declare function collectFiles(cwd: string, options?: CollectOptions): CollectResult;
498
498
 
499
499
  declare const GENERATOR_NAME = "@ankurhalder/codebase-snapshot";
500
- declare const GENERATOR_VERSION = "2.1.0";
500
+ declare const GENERATOR_VERSION = "2.1.1";
501
501
  declare const SCHEMA_VERSION = "3.0.0";
502
502
  interface GenerateOptions {
503
503
  /** Compute the full manifest but write nothing to disk. */
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-QEIOXPMU.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, 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-SUOJL5EX.js';
2
2
  //# sourceMappingURL=index.js.map
3
3
  //# sourceMappingURL=index.js.map
package/docs/install.md CHANGED
@@ -12,6 +12,20 @@ Requires **Node.js 18 or newer**.
12
12
  npx @ankurhalder/codebase-snapshot generate
13
13
  ```
14
14
 
15
+ If that exits with **no output and no error** instead of generating, your npm has
16
+ resolved the bare spec to a placeholder manifest that carries no executable — npm creates
17
+ a `0.0.0-stage` placeholder version during staged publishing, and some npm builds pick it
18
+ when no version is given. Pin the version or the tag and it works:
19
+
20
+ ```bash
21
+ npx @ankurhalder/codebase-snapshot@latest generate
22
+ npx @ankurhalder/codebase-snapshot@2 generate
23
+ ```
24
+
25
+ `npx -p @ankurhalder/codebase-snapshot codebase-snapshot generate`, `pnpm dlx`,
26
+ `yarn dlx` and `bunx` are unaffected. A normal `npm install -D` followed by `npx snap`
27
+ is unaffected too, and is the more reproducible choice for a project.
28
+
15
29
  Equivalent one-liners for other clients:
16
30
 
17
31
  ```bash
@@ -68,7 +82,7 @@ if the generator version is reproducible.
68
82
 
69
83
  ```bash
70
84
  npx @ankurhalder/codebase-snapshot@2 generate
71
- pnpm dlx @ankurhalder/codebase-snapshot@2.1.0 generate
85
+ pnpm dlx @ankurhalder/codebase-snapshot@2.1.1 generate
72
86
  ```
73
87
 
74
88
  ## Verified clients
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ankurhalder/codebase-snapshot",
3
- "version": "2.1.0",
3
+ "version": "2.1.1",
4
4
  "description": "Production-grade LLM codebase snapshot generator, auditor, and AST partitioner",
5
5
  "keywords": [
6
6
  "codebase",