@ankurhalder/codebase-snapshot 4.0.0 → 4.0.2

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.cjs CHANGED
@@ -2218,11 +2218,15 @@ function isSensitivePath(relativePath) {
2218
2218
  }
2219
2219
  return matchesSensitiveList(posixPath, base);
2220
2220
  }
2221
- function findSecretInPath(relativePath) {
2221
+ function pathScanRules(extraRules) {
2222
+ const fallback = extraRules ?? [];
2223
+ return [...DEFAULT_SECRET_PATTERNS, ...fallback];
2224
+ }
2225
+ function findSecretInPath(relativePath, extraRules) {
2222
2226
  const posixPath = relativePath.replace(/\\/g, "/");
2223
2227
  const spans = [];
2224
2228
  const rules = [];
2225
- for (const { rule, pattern } of DEFAULT_SECRET_PATTERNS) {
2229
+ for (const { rule, pattern } of pathScanRules(extraRules)) {
2226
2230
  const scan = new RegExp(pattern.source, `${pattern.flags.replace(/[gy]/g, "")}g`);
2227
2231
  let match;
2228
2232
  while ((match = scan.exec(posixPath)) !== null) {
@@ -2230,24 +2234,39 @@ function findSecretInPath(relativePath) {
2230
2234
  scan.lastIndex += 1;
2231
2235
  continue;
2232
2236
  }
2233
- spans.push({ start: match.index, end: match.index + match[0].length, rule });
2237
+ spans.push({ start: match.index, end: match.index + match[0].length });
2234
2238
  if (!rules.includes(rule)) rules.push(rule);
2235
2239
  }
2236
2240
  }
2237
2241
  if (spans.length === 0) return null;
2238
2242
  spans.sort((a, b) => a.start - b.start || a.end - b.end);
2243
+ const merged = [];
2244
+ for (const span of spans) {
2245
+ const last = merged[merged.length - 1];
2246
+ if (last && span.start <= last.end) last.end = Math.max(last.end, span.end);
2247
+ else merged.push({ start: span.start, end: span.end });
2248
+ }
2239
2249
  let out = "";
2240
2250
  let cursor = 0;
2241
- for (const span of spans) {
2242
- if (span.end <= cursor) continue;
2243
- const start = Math.max(span.start, cursor);
2244
- out += posixPath.slice(cursor, start);
2251
+ for (const span of merged) {
2252
+ out += posixPath.slice(cursor, span.start);
2245
2253
  out += REDACTION_MARKER;
2246
- cursor = Math.min(span.end, posixPath.length);
2254
+ cursor = span.end;
2247
2255
  }
2248
2256
  out += posixPath.slice(cursor);
2249
2257
  return { rules, masked: out };
2250
2258
  }
2259
+ function maskSkipPaths(records, extraRules) {
2260
+ return records.map((record) => {
2261
+ const hit = findSecretInPath(record.path, extraRules);
2262
+ const detail = record.detail ? findSecretInPath(record.detail, extraRules) : null;
2263
+ return {
2264
+ ...record,
2265
+ path: hit ? hit.masked : record.path,
2266
+ ...detail ? { detail: detail.masked } : {}
2267
+ };
2268
+ });
2269
+ }
2251
2270
  function globToRegExp(glob) {
2252
2271
  const normalized = glob.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/+$/, "");
2253
2272
  const anchored = normalized.startsWith("/");
@@ -2478,6 +2497,7 @@ function collectFiles(cwd, options = {}) {
2478
2497
  const includePatterns = options.includePatterns ?? [];
2479
2498
  const excludePatterns = options.excludePatterns ?? [];
2480
2499
  const includeSensitiveFiles = options.includeSensitiveFiles ?? false;
2500
+ const pathRules = options.secretRules;
2481
2501
  const isToolOwned = makeToolOwnedDecider(cwd);
2482
2502
  let candidates;
2483
2503
  const skipped = [];
@@ -2513,7 +2533,7 @@ function collectFiles(cwd, options = {}) {
2513
2533
  }
2514
2534
  }
2515
2535
  } catch {
2516
- const walked = walkDirectory(cwd, cwd, /* @__PURE__ */ new Set(), true);
2536
+ const walked = walkDirectory(cwd, cwd, rootRealFor(cwd), /* @__PURE__ */ new Set(), true);
2517
2537
  candidates = walked.files;
2518
2538
  skipped.push(...walked.skipped);
2519
2539
  }
@@ -2551,7 +2571,7 @@ function collectFiles(cwd, options = {}) {
2551
2571
  });
2552
2572
  return false;
2553
2573
  }
2554
- const secret = findSecretInPath(file);
2574
+ const secret = findSecretInPath(file, pathRules);
2555
2575
  if (secret) {
2556
2576
  skipped.push({
2557
2577
  path: secret.masked,
@@ -2562,30 +2582,29 @@ function collectFiles(cwd, options = {}) {
2562
2582
  }
2563
2583
  return true;
2564
2584
  }).sort();
2565
- return { files, skipped };
2585
+ return { files, skipped: maskSkipPaths(skipped, pathRules) };
2566
2586
  }
2567
- function walkDirectory(dir, baseDir, visited, isRoot = false) {
2568
- const results = { files: [], skipped: [] };
2569
- let realDir;
2587
+ function rootRealFor(root) {
2570
2588
  try {
2571
- realDir = fs9__namespace.realpathSync(dir);
2589
+ return fs9__namespace.realpathSync(root);
2572
2590
  } catch {
2573
- realDir = dir;
2574
- }
2575
- if (visited.has(realDir)) return results;
2576
- visited.add(realDir);
2577
- let rootReal;
2578
- try {
2579
- rootReal = fs9__namespace.realpathSync(baseDir);
2580
- } catch {
2581
- rootReal = baseDir;
2591
+ return root;
2582
2592
  }
2593
+ }
2594
+ function walkDirectory(dir, baseDir, rootReal, visited, isRoot = false) {
2595
+ const results = { files: [], skipped: [] };
2596
+ if (visited.has(dir)) return results;
2597
+ visited.add(dir);
2583
2598
  const ignoredByGitignore = isRoot ? readGitignoreMatcher(baseDir) : () => false;
2584
2599
  let entries;
2585
2600
  try {
2586
2601
  entries = fs9__namespace.readdirSync(dir, { withFileTypes: true });
2587
2602
  } catch {
2588
- results.skipped.push({ path: dir, reason: "read-error", detail: "directory unreadable" });
2603
+ results.skipped.push({
2604
+ path: path10__namespace.relative(baseDir, dir).replace(/\\/g, "/") || ".",
2605
+ reason: "read-error",
2606
+ detail: "directory unreadable"
2607
+ });
2589
2608
  return results;
2590
2609
  }
2591
2610
  for (const entry of entries) {
@@ -2664,7 +2683,7 @@ function walkDirectory(dir, baseDir, visited, isRoot = false) {
2664
2683
  isFile = true;
2665
2684
  }
2666
2685
  if (isDirectory) {
2667
- const nested = walkDirectory(fullPath, baseDir, visited);
2686
+ const nested = walkDirectory(fullPath, baseDir, rootReal, visited);
2668
2687
  results.files.push(...nested.files);
2669
2688
  results.skipped.push(...nested.skipped);
2670
2689
  } else if (isFile) {
@@ -2935,6 +2954,9 @@ async function loadConfigFileDetailed(cwd) {
2935
2954
  }
2936
2955
  async function loadConfigFile(cwd) {
2937
2956
  const loaded = await loadConfigFileDetailed(cwd);
2957
+ if (loaded.errors.length > 0) {
2958
+ throw new Error(`The config file could not be read: ${loaded.errors.join("; ")}`);
2959
+ }
2938
2960
  return loaded.config;
2939
2961
  }
2940
2962
  var MIN_MAX_PART_BYTES = 4096;
@@ -3363,7 +3385,8 @@ function defineConfig(config) {
3363
3385
  }
3364
3386
 
3365
3387
  // src/generator/audit.ts
3366
- function generateAuditReport(manifest, secretFindings) {
3388
+ function generateAuditReport(manifest, secretFindings, secretRules) {
3389
+ const mask = (value) => findSecretInPath(value, secretRules)?.masked ?? value;
3367
3390
  const lines = [
3368
3391
  "# Codebase Health & Security Audit",
3369
3392
  "",
@@ -3398,7 +3421,7 @@ function generateAuditReport(manifest, secretFindings) {
3398
3421
  "| :--- | :--- | :--- |"
3399
3422
  );
3400
3423
  for (const finding of secretFindings) {
3401
- lines.push(`| \`${finding.path ?? "unknown"}\` | ${finding.line} | \`${finding.rule}\` |`);
3424
+ lines.push(`| \`${mask(finding.path ?? "unknown")}\` | ${finding.line} | \`${finding.rule}\` |`);
3402
3425
  }
3403
3426
  lines.push("", "### Matches by rule", "", "| Rule | Matches |", "| :--- | :--- |");
3404
3427
  for (const [rule, count] of Array.from(byRule.entries()).sort((a, b) => b[1] - a[1])) {
@@ -3420,7 +3443,7 @@ function generateAuditReport(manifest, secretFindings) {
3420
3443
  for (const file of largeFiles) {
3421
3444
  const sizeKb = (file.sizeBytes / 1024).toFixed(1);
3422
3445
  const status = file.isPreviewTruncated ? "Truncated Head/Tail" : "Full";
3423
- lines.push(`| \`${file.relativePath}\` | ${sizeKb} KB | ${file.lines} | ${status} |`);
3446
+ lines.push(`| \`${mask(file.relativePath)}\` | ${sizeKb} KB | ${file.lines} | ${status} |`);
3424
3447
  }
3425
3448
  lines.push("");
3426
3449
  }
@@ -3462,7 +3485,7 @@ function generateAuditReport(manifest, secretFindings) {
3462
3485
  "| :--- | :--- | :--- |"
3463
3486
  );
3464
3487
  for (const skip of manifest.skipped) {
3465
- lines.push(`| \`${skip.path}\` | \`${skip.reason}\` | ${skip.detail ?? "-"} |`);
3488
+ lines.push(`| \`${mask(skip.path)}\` | \`${skip.reason}\` | ${skip.detail ?? "-"} |`);
3466
3489
  }
3467
3490
  lines.push("");
3468
3491
  }
@@ -4124,6 +4147,9 @@ function detectLanguage(filePath) {
4124
4147
  const ext = path10__namespace.posix.extname(base).toLowerCase();
4125
4148
  return LANGUAGE_BY_EXTENSION[ext] ?? "text";
4126
4149
  }
4150
+ function maskedPath(value, secretRules) {
4151
+ return findSecretInPath(value, secretRules)?.masked ?? value;
4152
+ }
4127
4153
  function emptyDrift() {
4128
4154
  return {
4129
4155
  missingInSnapshot: [],
@@ -4194,15 +4220,20 @@ function validateSnapshot(configInput) {
4194
4220
  const skippedSet = new Set(
4195
4221
  manifest.skipped.filter((entry) => entry && typeof entry.path === "string").map((entry) => entry.path)
4196
4222
  );
4223
+ const isSkippedOnDisk = (diskFile) => {
4224
+ if (skippedSet.has(diskFile)) return true;
4225
+ return skippedSet.has(maskedPath(diskFile, config.secretRules));
4226
+ };
4197
4227
  const collection = collectFiles(config.cwd, {
4198
4228
  includePatterns: config.include,
4199
4229
  excludePatterns: [...config.excludePatterns, ...config.presetExclude],
4200
- includeSensitiveFiles: config.includeSensitiveFiles
4230
+ includeSensitiveFiles: config.includeSensitiveFiles,
4231
+ secretRules: config.secretRules
4201
4232
  });
4202
4233
  for (const diskFile of collection.files) {
4203
4234
  const snapshotHash = manifestFileMap.get(diskFile);
4204
4235
  if (!snapshotHash) {
4205
- if (skippedSet.has(diskFile) && !wouldNowBeIncluded(config.cwd, diskFile)) continue;
4236
+ if (isSkippedOnDisk(diskFile) && !wouldNowBeIncluded(config.cwd, diskFile)) continue;
4206
4237
  drift.missingInSnapshot.push(diskFile);
4207
4238
  continue;
4208
4239
  }
@@ -4471,10 +4502,19 @@ function validateSnapshot(configInput) {
4471
4502
  `${drift.configMismatches.length} configuration mismatch(es) between the manifest and this run.`
4472
4503
  );
4473
4504
  }
4505
+ const maskedDrift = {
4506
+ ...drift,
4507
+ missingInSnapshot: drift.missingInSnapshot.map((value) => maskedPath(value, config.secretRules)),
4508
+ missingOnDisk: drift.missingOnDisk.map((value) => maskedPath(value, config.secretRules)),
4509
+ hashMismatches: drift.hashMismatches.map((mismatch) => ({
4510
+ ...mismatch,
4511
+ path: maskedPath(mismatch.path, config.secretRules)
4512
+ }))
4513
+ };
4474
4514
  return {
4475
4515
  valid: errors.length === 0,
4476
4516
  manifestPresent: true,
4477
- drift,
4517
+ drift: maskedDrift,
4478
4518
  errors,
4479
4519
  warnings
4480
4520
  };
@@ -4509,18 +4549,22 @@ function readManifest2(config) {
4509
4549
  function explainPath(config, target) {
4510
4550
  const relative4 = normalizeTarget(config.cwd, target);
4511
4551
  const absolute = path10__namespace.join(config.cwd, relative4);
4552
+ const maskedPath2 = findSecretInPath(relative4, config.secretRules)?.masked ?? relative4;
4512
4553
  const result = {
4513
- path: relative4,
4554
+ path: maskedPath2,
4514
4555
  exists: fs9__namespace.existsSync(absolute),
4515
4556
  included: false
4516
4557
  };
4517
4558
  const collection = collectFiles(config.cwd, {
4518
4559
  includePatterns: config.include,
4519
4560
  excludePatterns: [...config.excludePatterns, ...config.presetExclude],
4520
- includeSensitiveFiles: config.includeSensitiveFiles
4561
+ includeSensitiveFiles: config.includeSensitiveFiles,
4562
+ secretRules: config.secretRules
4521
4563
  });
4522
4564
  if (!collection.files.includes(relative4)) {
4523
- const record = collection.skipped.find((entry2) => entry2.path === relative4);
4565
+ const record = collection.skipped.find(
4566
+ (entry2) => entry2.path === relative4 || entry2.path === maskedPath2
4567
+ );
4524
4568
  if (record) {
4525
4569
  result.reason = record.reason;
4526
4570
  result.detail = record.detail;
@@ -4557,7 +4601,7 @@ function explainPath(config, target) {
4557
4601
  result.secrets = findings.slice(0, 20).map((finding) => ({ rule: finding.rule, line: finding.line }));
4558
4602
  }
4559
4603
  const manifest = readManifest2(config);
4560
- const entry = manifest?.files.find((file) => file.relativePath === relative4);
4604
+ const entry = Array.isArray(manifest?.files) ? manifest.files.find((file) => file.relativePath === relative4) : void 0;
4561
4605
  result.part = entry?.part ?? null;
4562
4606
  result.inclusionMode = entry?.inclusionMode;
4563
4607
  return result;
@@ -4662,12 +4706,13 @@ function compareManifests(before, after) {
4662
4706
  if (!right.has(filePath)) removed.push(filePath);
4663
4707
  }
4664
4708
  const sort = (values) => values.sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
4709
+ const mask = (values) => values.map((value) => findSecretInPath(value)?.masked ?? value);
4665
4710
  return {
4666
4711
  before: side(before),
4667
4712
  after: side(after),
4668
- added: sort(added),
4669
- removed: sort(removed),
4670
- changed: sort(changed),
4713
+ added: mask(sort(added)),
4714
+ removed: mask(sort(removed)),
4715
+ changed: mask(sort(changed)),
4671
4716
  unchanged
4672
4717
  };
4673
4718
  }
@@ -5392,7 +5437,7 @@ async function runInit(options) {
5392
5437
 
5393
5438
  // src/index.ts
5394
5439
  var GENERATOR_NAME = "@ankurhalder/codebase-snapshot";
5395
- var GENERATOR_VERSION = "4.0.0";
5440
+ var GENERATOR_VERSION = "4.0.2";
5396
5441
  var SCHEMA_VERSION = "3.0.0";
5397
5442
  function sha256Hex2(input) {
5398
5443
  return crypto__namespace.createHash("sha256").update(input).digest("hex");
@@ -5429,7 +5474,6 @@ function bump(counts, key) {
5429
5474
  }
5430
5475
  var RELATIVE_SPECIFIER = /^\.{1,2}\//;
5431
5476
  var IMPORT_CANDIDATE_SUFFIXES = [
5432
- "",
5433
5477
  ".ts",
5434
5478
  ".tsx",
5435
5479
  ".mts",
@@ -5443,23 +5487,59 @@ var IMPORT_CANDIDATE_SUFFIXES = [
5443
5487
  "/index.js",
5444
5488
  "/index.mjs"
5445
5489
  ];
5446
- function resolveSpecifier(fromFile, specifier, knownFiles) {
5490
+ var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
5491
+ ".ts",
5492
+ ".tsx",
5493
+ ".mts",
5494
+ ".cts",
5495
+ ".js",
5496
+ ".jsx",
5497
+ ".mjs",
5498
+ ".cjs"
5499
+ ]);
5500
+ function buildImportResolver(files) {
5501
+ const byPath = /* @__PURE__ */ new Map();
5502
+ const byPathFolded = /* @__PURE__ */ new Map();
5503
+ const byStem = /* @__PURE__ */ new Map();
5504
+ const byStemFolded = /* @__PURE__ */ new Map();
5505
+ const remember = (map, key, value) => {
5506
+ if (!map.has(key)) map.set(key, value);
5507
+ };
5508
+ for (const file of files) {
5509
+ const relativePath = file.relativePath;
5510
+ remember(byPath, relativePath, relativePath);
5511
+ remember(byPathFolded, relativePath.toLowerCase(), relativePath);
5512
+ const extension = path10__namespace.posix.extname(relativePath);
5513
+ if (!SOURCE_EXTENSIONS.has(extension)) continue;
5514
+ const stem = relativePath.slice(0, -extension.length);
5515
+ remember(byStem, stem, relativePath);
5516
+ remember(byStemFolded, stem.toLowerCase(), relativePath);
5517
+ if (path10__namespace.posix.basename(stem) === "index") {
5518
+ const directory = path10__namespace.posix.dirname(stem);
5519
+ remember(byStem, directory, relativePath);
5520
+ remember(byStemFolded, directory.toLowerCase(), relativePath);
5521
+ }
5522
+ }
5523
+ return { byPath, byPathFolded, byStem, byStemFolded };
5524
+ }
5525
+ function resolveSpecifier(fromFile, specifier, resolver) {
5447
5526
  if (!RELATIVE_SPECIFIER.test(specifier)) return null;
5448
- const baseDir = path10__namespace.posix.dirname(fromFile);
5449
- const joined = path10__namespace.posix.normalize(path10__namespace.posix.join(baseDir, specifier));
5450
- const candidate = joined.replace(/\.(?:js|mjs|cjs|jsx)$/, "");
5527
+ const joined = path10__namespace.posix.normalize(
5528
+ path10__namespace.posix.join(path10__namespace.posix.dirname(fromFile), specifier)
5529
+ );
5530
+ const exact = (key) => resolver.byPath.get(key) ?? resolver.byPathFolded.get(key.toLowerCase()) ?? null;
5531
+ const direct = exact(joined);
5532
+ if (direct) return direct;
5451
5533
  for (const suffix of IMPORT_CANDIDATE_SUFFIXES) {
5452
- const withSuffix = `${joined}${suffix}`;
5453
- if (knownFiles.has(withSuffix)) return withSuffix;
5534
+ const hit = exact(`${joined}${suffix}`);
5535
+ if (hit) return hit;
5454
5536
  }
5455
- const extensionSwap = new RegExp(`${candidate}\\.(?:ts|tsx|mts|cts|js|jsx|mjs|cjs)$`);
5456
- for (const file of knownFiles) {
5457
- if (extensionSwap.test(file)) return file;
5458
- }
5459
- return null;
5537
+ const stem = joined.replace(/\.(?:js|mjs|cjs|jsx)$/, "");
5538
+ const byStem = (key) => resolver.byStem.get(key) ?? resolver.byStemFolded.get(key.toLowerCase()) ?? null;
5539
+ return byStem(joined) ?? byStem(stem);
5460
5540
  }
5461
5541
  function buildIndexes(files) {
5462
- const knownFiles = new Set(files.map((file) => file.relativePath));
5542
+ const resolver = buildImportResolver(files);
5463
5543
  const importerCounts = /* @__PURE__ */ new Map();
5464
5544
  const byFile = files.filter((file) => isSourceSummary(file.summary)).map((file) => {
5465
5545
  const summary = file.summary;
@@ -5471,7 +5551,7 @@ function buildIndexes(files) {
5471
5551
  });
5472
5552
  for (const entry of byFile) {
5473
5553
  for (const specifier of entry.imports) {
5474
- const resolved = resolveSpecifier(entry.path, specifier, knownFiles);
5554
+ const resolved = resolveSpecifier(entry.path, specifier, resolver);
5475
5555
  if (resolved) importerCounts.set(resolved, (importerCounts.get(resolved) ?? 0) + 1);
5476
5556
  }
5477
5557
  }
@@ -5562,15 +5642,22 @@ function resolveSafeOutputDirs(config) {
5562
5642
  return { outDir, tmpDir };
5563
5643
  }
5564
5644
  var PLACEHOLDER_ENTRIES = /* @__PURE__ */ new Set([".gitkeep", ".gitignore", ".DS_Store"]);
5565
- var STAGING_ENTRIES = /* @__PURE__ */ new Set([
5566
- ".codebase-snapshot",
5567
- "manifest.json",
5568
- "README.md",
5569
- "parts",
5570
- "reports",
5571
- "full-codebase.json",
5572
- "full-codebase.md"
5573
- ]);
5645
+ var RUN_DIR_PREFIX = "run-";
5646
+ var STALE_RUN_MS = 60 * 60 * 1e3;
5647
+ function maskPathText(value, secretRules) {
5648
+ return findSecretInPath(value, secretRules)?.masked ?? value;
5649
+ }
5650
+ function maskSkipRecords(records, secretRules) {
5651
+ return maskSkipPaths(records, secretRules);
5652
+ }
5653
+ function isProcessAlive(pid) {
5654
+ try {
5655
+ process.kill(pid, 0);
5656
+ return true;
5657
+ } catch (err) {
5658
+ return err.code !== "ESRCH";
5659
+ }
5660
+ }
5574
5661
  var OWNERSHIP_MARKER = ".codebase-snapshot";
5575
5662
  var SUPPORTED_MARKER_SCHEMAS = /* @__PURE__ */ new Set([SCHEMA_VERSION]);
5576
5663
  var SNAPSHOT_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -5600,6 +5687,7 @@ function markerPayload(dir, state, id, createdAt) {
5600
5687
  createdAt,
5601
5688
  state,
5602
5689
  id,
5690
+ pid: process.pid,
5603
5691
  entries: listEntries(dir)
5604
5692
  };
5605
5693
  const manifestPath = path10__namespace.join(dir, "manifest.json");
@@ -5663,6 +5751,7 @@ function readOwnershipMarker(dir) {
5663
5751
  createdAt: typeof candidate.createdAt === "string" ? candidate.createdAt : "unknown",
5664
5752
  state: candidate.state,
5665
5753
  id: candidate.id,
5754
+ pid: typeof candidate.pid === "number" ? candidate.pid : void 0,
5666
5755
  manifestSha256: typeof candidate.manifestSha256 === "string" ? candidate.manifestSha256 : void 0,
5667
5756
  entries: Array.isArray(candidate.entries) && candidate.entries.every((entry) => typeof entry === "string") ? candidate.entries : void 0
5668
5757
  },
@@ -5770,7 +5859,7 @@ function describeEntries(entries, limit = 5) {
5770
5859
  const shown = entries.slice(0, limit).join(", ");
5771
5860
  return entries.length > limit ? `${shown}, and ${entries.length - limit} more` : shown;
5772
5861
  }
5773
- function assertReplaceable(target, label) {
5862
+ function assertReplaceable(target, label, secretRules) {
5774
5863
  if (!fs9__namespace.existsSync(target)) return;
5775
5864
  if (!fs9__namespace.statSync(target).isDirectory()) {
5776
5865
  throw new Error(
@@ -5779,34 +5868,89 @@ function assertReplaceable(target, label) {
5779
5868
  }
5780
5869
  const entries = fs9__namespace.readdirSync(target);
5781
5870
  if (entries.length === 0) return;
5782
- if (label === "outDir" && entries.every((name) => PLACEHOLDER_ENTRIES.has(name))) return;
5783
- const { marker: marker2, problem } = readOwnershipMarker(target);
5784
- if (label === "tmpDir") {
5785
- if (!marker2) {
5786
- throw new Error(
5787
- `Refusing to replace tmpDir ${target}: ${problem ?? "it does not carry this generator's marker"}. This generator only clears a staging directory it wrote. Point tmpDir at a new or empty directory, or remove ${target} yourself.`
5788
- );
5789
- }
5790
- const unexpected = entries.filter((name) => !STAGING_ENTRIES.has(name));
5791
- if (unexpected.length > 0) {
5792
- throw new Error(
5793
- `Refusing to replace tmpDir ${target}: it holds ${describeEntries(unexpected)}, which this generator did not write. Point tmpDir at a new or empty directory, or remove those files yourself.`
5794
- );
5795
- }
5796
- return;
5797
- }
5871
+ if (entries.every((name) => PLACEHOLDER_ENTRIES.has(name))) return;
5872
+ const { problem } = readOwnershipMarker(target);
5798
5873
  const verdict = assessSnapshotDirectory(target);
5799
5874
  if (verdict.owned) return;
5875
+ const shownForeign = verdict.foreignEntries.map((name) => maskPathText(name, secretRules));
5876
+ const shownEntries = entries.map((name) => maskPathText(name, secretRules));
5800
5877
  if (verdict.foreignEntries.length > 0) {
5801
5878
  throw new Error(
5802
- `Refusing to replace ${label} ${target}: it carries this generator's ownership marker, but these entries are not part of the snapshot it recorded: ${describeEntries(verdict.foreignEntries)}. Remove them yourself, or point ${label} at a new or empty directory.`
5879
+ `Refusing to replace ${label} ${target}: it carries this generator's ownership marker, but these entries are not part of the snapshot it recorded: ${describeEntries(shownForeign)}. Remove them yourself, or point ${label} at a new or empty directory.`
5803
5880
  );
5804
5881
  }
5805
5882
  const reason = verdict.problem ?? problem ?? "it does not look like a snapshot this generator wrote";
5806
5883
  throw new Error(
5807
- `Refusing to replace ${label} ${target}: ${reason} (found ${describeEntries(entries)}). Remove the directory yourself, or point ${label} at a new or empty directory.`
5884
+ `Refusing to replace ${label} ${target}: ${reason} (found ${describeEntries(shownEntries)}). Remove the directory yourself, or point ${label} at a new or empty directory.`
5808
5885
  );
5809
5886
  }
5887
+ function assertStagingRootUsable(tmpDir, secretRules) {
5888
+ if (!fs9__namespace.existsSync(tmpDir)) return;
5889
+ if (!fs9__namespace.statSync(tmpDir).isDirectory()) {
5890
+ throw new Error(`Refusing to write into tmpDir ${tmpDir}: it exists but is not a directory.`);
5891
+ }
5892
+ const entries = fs9__namespace.readdirSync(tmpDir);
5893
+ if (entries.length === 0) return;
5894
+ const foreign = entries.filter((name) => {
5895
+ const full = path10__namespace.join(tmpDir, name);
5896
+ if (!name.startsWith(RUN_DIR_PREFIX)) return true;
5897
+ let stats;
5898
+ try {
5899
+ stats = fs9__namespace.statSync(full);
5900
+ } catch {
5901
+ return true;
5902
+ }
5903
+ if (!stats.isDirectory()) return true;
5904
+ const { marker: marker2 } = readOwnershipMarker(full);
5905
+ if (marker2) return false;
5906
+ try {
5907
+ return fs9__namespace.readdirSync(full).length > 0;
5908
+ } catch {
5909
+ return true;
5910
+ }
5911
+ });
5912
+ if (foreign.length > 0) {
5913
+ const shown = describeEntries(foreign.map((name) => maskPathText(name, secretRules)));
5914
+ throw new Error(
5915
+ `Refusing to replace tmpDir ${tmpDir}: it holds ${shown}, which this generator did not write. Point tmpDir at a new or empty directory, or remove those files yourself.`
5916
+ );
5917
+ }
5918
+ }
5919
+ function cleanStaleRunDirs(tmpDir) {
5920
+ let entries;
5921
+ try {
5922
+ entries = fs9__namespace.readdirSync(tmpDir);
5923
+ } catch {
5924
+ return;
5925
+ }
5926
+ for (const name of entries) {
5927
+ if (!name.startsWith(RUN_DIR_PREFIX)) continue;
5928
+ const full = path10__namespace.join(tmpDir, name);
5929
+ const { marker: marker2 } = readOwnershipMarker(full);
5930
+ if (marker2) {
5931
+ const created = Date.parse(marker2.createdAt);
5932
+ const aged = !Number.isFinite(created) || Date.now() - created > STALE_RUN_MS;
5933
+ const abandoned = typeof marker2.pid === "number" && !isProcessAlive(marker2.pid);
5934
+ if (aged || abandoned) {
5935
+ try {
5936
+ fs9__namespace.rmSync(full, { recursive: true, force: true });
5937
+ } catch {
5938
+ }
5939
+ }
5940
+ continue;
5941
+ }
5942
+ try {
5943
+ if (fs9__namespace.readdirSync(full).length === 0) fs9__namespace.rmSync(full, { recursive: true, force: true });
5944
+ } catch {
5945
+ }
5946
+ }
5947
+ }
5948
+ function removeIfEmpty(dir) {
5949
+ try {
5950
+ if (fs9__namespace.readdirSync(dir).length === 0) fs9__namespace.rmSync(dir, { recursive: true, force: true });
5951
+ } catch {
5952
+ }
5953
+ }
5810
5954
  function ensureParentDirectory(target, label) {
5811
5955
  const parent = path10__namespace.dirname(target);
5812
5956
  if (fs9__namespace.existsSync(parent) && !fs9__namespace.statSync(parent).isDirectory()) {
@@ -5822,14 +5966,12 @@ function ensureParentDirectory(target, label) {
5822
5966
  );
5823
5967
  }
5824
5968
  }
5825
- function acquireRunLock(outDir) {
5826
- const lockPath = path10__namespace.join(
5827
- os__namespace.tmpdir(),
5828
- `codebase-snapshot-${sha256Hex2(outDir).slice(0, 16)}.lock`
5829
- );
5969
+ function acquireRunLock(stagingRoot) {
5970
+ const key = lockKeyFor(stagingRoot);
5971
+ const lockPath = path10__namespace.join(os__namespace.tmpdir(), `codebase-snapshot-${key}.lock`);
5830
5972
  const payload = `${JSON.stringify({
5831
5973
  pid: process.pid,
5832
- outDir,
5974
+ stagingRoot,
5833
5975
  startedAt: (/* @__PURE__ */ new Date()).toISOString()
5834
5976
  })}
5835
5977
  `;
@@ -5854,11 +5996,20 @@ function acquireRunLock(outDir) {
5854
5996
  continue;
5855
5997
  }
5856
5998
  throw new Error(
5857
- `Another generate run is already writing ${outDir}. Wait for it to finish, or delete ${lockPath} if no other run is active.`
5999
+ `Another generate run is already using the staging directory ${stagingRoot}. Wait for it to finish, or delete ${lockPath} if no other run is active.`
5858
6000
  );
5859
6001
  }
5860
6002
  }
5861
- throw new Error(`Could not acquire the generate lock for ${outDir}.`);
6003
+ throw new Error(`Could not acquire the generate lock for ${stagingRoot}.`);
6004
+ }
6005
+ function lockKeyFor(stagingRoot) {
6006
+ let key = stagingRoot;
6007
+ try {
6008
+ key = fs9__namespace.realpathSync(stagingRoot);
6009
+ } catch {
6010
+ }
6011
+ if (process.platform === "win32" || process.platform === "darwin") key = key.toLowerCase();
6012
+ return sha256Hex2(key).slice(0, 16);
5862
6013
  }
5863
6014
  function lockOwnerIsGone(lockPath) {
5864
6015
  let raw;
@@ -5894,18 +6045,32 @@ function writeManifestAtomically(manifestPath, manifest) {
5894
6045
  throw err;
5895
6046
  }
5896
6047
  }
5897
- function swapIntoPlace(tmpDir, outDir) {
6048
+ function swapIntoPlace(stagingDir, outDir) {
5898
6049
  ensureParentDirectory(outDir, "outDir");
6050
+ const deadline = Date.now() + 3e3;
6051
+ for (let attempt = 0; ; attempt += 1) {
6052
+ try {
6053
+ swapOnce(stagingDir, outDir);
6054
+ return;
6055
+ } catch (err) {
6056
+ const code = err.code;
6057
+ const racy = code === "ENOENT" || code === "ENOTEMPTY" || code === "EEXIST" || code === "EPERM";
6058
+ if (!racy || attempt >= 10 || Date.now() > deadline) throw err;
6059
+ sleepSync(50);
6060
+ }
6061
+ }
6062
+ }
6063
+ function swapOnce(stagingDir, outDir) {
5899
6064
  const backupDir = `${outDir}.backup-${process.pid}-${Date.now()}`;
5900
6065
  const hadPrevious = fs9__namespace.existsSync(outDir);
5901
6066
  if (hadPrevious) fs9__namespace.renameSync(outDir, backupDir);
5902
6067
  try {
5903
6068
  try {
5904
- fs9__namespace.renameSync(tmpDir, outDir);
6069
+ fs9__namespace.renameSync(stagingDir, outDir);
5905
6070
  } catch (err) {
5906
6071
  if (err.code === "EXDEV") {
5907
- fs9__namespace.cpSync(tmpDir, outDir, { recursive: true });
5908
- fs9__namespace.rmSync(tmpDir, { recursive: true, force: true });
6072
+ fs9__namespace.cpSync(stagingDir, outDir, { recursive: true });
6073
+ fs9__namespace.rmSync(stagingDir, { recursive: true, force: true });
5909
6074
  } else {
5910
6075
  throw err;
5911
6076
  }
@@ -5919,6 +6084,10 @@ function swapIntoPlace(tmpDir, outDir) {
5919
6084
  throw err;
5920
6085
  }
5921
6086
  }
6087
+ function sleepSync(milliseconds) {
6088
+ const buffer = new Int32Array(new SharedArrayBuffer(4));
6089
+ Atomics.wait(buffer, 0, 0, milliseconds);
6090
+ }
5922
6091
  function artifactWarnings(artifacts) {
5923
6092
  const messages = [];
5924
6093
  if (!artifacts.manifest) {
@@ -5981,11 +6150,12 @@ function generateSnapshot(configInput, options) {
5981
6150
  const collection = collectFiles(config.cwd, {
5982
6151
  includePatterns: config.include,
5983
6152
  excludePatterns: [...config.excludePatterns, ...config.presetExclude],
5984
- includeSensitiveFiles: config.includeSensitiveFiles
6153
+ includeSensitiveFiles: config.includeSensitiveFiles,
6154
+ secretRules: config.secretRules
5985
6155
  });
5986
6156
  const files = [];
5987
6157
  const monolithBodies = /* @__PURE__ */ new Map();
5988
- const skipped = [...collection.skipped];
6158
+ const skipped = maskSkipRecords([...collection.skipped], config.secretRules);
5989
6159
  const secretFindings = [];
5990
6160
  const warnings = [];
5991
6161
  const layerCounts = {};
@@ -6059,12 +6229,12 @@ function generateSnapshot(configInput, options) {
6059
6229
  summaryQuality = outcome?.quality;
6060
6230
  if (isSourceSummary(summary) && summary.parseErrors) {
6061
6231
  warnings.push(
6062
- `Parse errors in ${relativePath} (${summary.parseErrors}); its summary is incomplete.`
6232
+ `Parse errors in ${maskPathText(relativePath, config.secretRules)} (${summary.parseErrors}); its summary is incomplete.`
6063
6233
  );
6064
6234
  }
6065
6235
  } catch (err) {
6066
6236
  warnings.push(
6067
- `Summary skipped for ${relativePath}: ${err.message}`.replace(/\s+/g, " ")
6237
+ `Summary skipped for ${maskPathText(relativePath, config.secretRules)}: ${err.message}`.replace(/\s+/g, " ")
6068
6238
  );
6069
6239
  }
6070
6240
  files.push({
@@ -6108,6 +6278,7 @@ function generateSnapshot(configInput, options) {
6108
6278
  const truncatedPreviewCount = files.filter((file) => file.isPreviewTruncated).length;
6109
6279
  const validationErrors = verifyInMemory(files, parts, maxEffectiveBudget);
6110
6280
  const totalContentBytes = files.reduce((total, file) => total + file.includedBytes, 0);
6281
+ const maskName = (value) => maskPathText(value, config.secretRules);
6111
6282
  const manifest = {
6112
6283
  schemaVersion: SCHEMA_VERSION,
6113
6284
  generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -6128,13 +6299,13 @@ function generateSnapshot(configInput, options) {
6128
6299
  includeMonolith: config.includeMonolith,
6129
6300
  includeSensitiveFiles: config.includeSensitiveFiles,
6130
6301
  artifacts: { ...config.artifacts },
6131
- include: [...config.include],
6302
+ include: [...config.include].map(maskName),
6132
6303
  preset: config.preset,
6133
6304
  layersMode: config.layersMode,
6134
6305
  budgets: { default: config.budgets.default, byLayer: { ...config.budgets.byLayer } },
6135
- excludePatterns: [...config.excludePatterns],
6136
- layers: config.layers.map((layer2) => layer2.name),
6137
- kinds: config.kinds.map((kind) => kind.kind)
6306
+ excludePatterns: [...config.excludePatterns].map(maskName),
6307
+ layers: config.layers.map((layer2) => maskName(layer2.name)),
6308
+ kinds: config.kinds.map((kind) => maskName(kind.kind))
6138
6309
  },
6139
6310
  counts: {
6140
6311
  totalFiles: files.length,
@@ -6173,12 +6344,12 @@ function generateSnapshot(configInput, options) {
6173
6344
  const { content: _content, ...rest } = file;
6174
6345
  return rest;
6175
6346
  }),
6176
- skipped,
6347
+ skipped: maskSkipRecords(skipped, config.secretRules),
6177
6348
  warnings
6178
6349
  };
6179
6350
  manifest.warnings = [
6180
6351
  ...warnings,
6181
- ...secretFindings.map((finding) => `${finding.rule} at ${finding.path ?? "unknown"}:${finding.line}`)
6352
+ ...secretFindings.map((finding) => `${finding.rule} at ${maskPathText(finding.path ?? "unknown", config.secretRules)}:${finding.line}`)
6182
6353
  ];
6183
6354
  manifest.totals.warningCount = manifest.warnings.length;
6184
6355
  let monolithJson = null;
@@ -6224,31 +6395,33 @@ function generateSnapshot(configInput, options) {
6224
6395
  manifest.totals.warningCount = manifest.warnings.length;
6225
6396
  const { outDir, tmpDir } = resolveSafeOutputDirs(config);
6226
6397
  const { artifacts } = config;
6227
- assertReplaceable(tmpDir, "tmpDir");
6228
- assertReplaceable(outDir, "outDir");
6398
+ assertReplaceable(outDir, "outDir", config.secretRules);
6399
+ assertStagingRootUsable(tmpDir, config.secretRules);
6229
6400
  if (dryRun) {
6230
6401
  runAfterGenerate(config, manifest);
6231
6402
  return manifest;
6232
6403
  }
6233
- const releaseLock = acquireRunLock(outDir);
6234
- let stagingStarted = false;
6404
+ const releaseLock = acquireRunLock(tmpDir);
6405
+ let stagingDir = null;
6235
6406
  try {
6236
6407
  ensureParentDirectory(outDir, "outDir");
6237
- fs9__namespace.rmSync(tmpDir, { recursive: true, force: true });
6238
6408
  fs9__namespace.mkdirSync(tmpDir, { recursive: true });
6239
- stagingStarted = true;
6409
+ cleanStaleRunDirs(tmpDir);
6240
6410
  const snapshotId = crypto__namespace.randomUUID();
6241
6411
  const createdAt = (/* @__PURE__ */ new Date()).toISOString();
6242
- writeOwnershipMarker(tmpDir, {
6412
+ stagingDir = path10__namespace.join(tmpDir, `${RUN_DIR_PREFIX}${process.pid}-${snapshotId.slice(0, 8)}`);
6413
+ fs9__namespace.mkdirSync(stagingDir);
6414
+ writeOwnershipMarker(stagingDir, {
6243
6415
  generator: GENERATOR_NAME,
6244
6416
  version: GENERATOR_VERSION,
6245
6417
  schemaVersion: SCHEMA_VERSION,
6246
6418
  createdAt,
6247
6419
  state: "staging",
6248
- id: snapshotId
6420
+ id: snapshotId,
6421
+ pid: process.pid
6249
6422
  });
6250
- const partsDir = path10__namespace.join(tmpDir, "parts");
6251
- const reportsDir = path10__namespace.join(tmpDir, "reports");
6423
+ const partsDir = path10__namespace.join(stagingDir, "parts");
6424
+ const reportsDir = path10__namespace.join(stagingDir, "reports");
6252
6425
  if (artifacts.parts) fs9__namespace.mkdirSync(partsDir, { recursive: true });
6253
6426
  if (artifacts.navigation || artifacts.projectTree || artifacts.audit) {
6254
6427
  fs9__namespace.mkdirSync(reportsDir, { recursive: true });
@@ -6268,25 +6441,25 @@ function generateSnapshot(configInput, options) {
6268
6441
  if (artifacts.audit) {
6269
6442
  fs9__namespace.writeFileSync(
6270
6443
  path10__namespace.join(reportsDir, "audit.md"),
6271
- generateAuditReport(manifest, secretFindings),
6444
+ generateAuditReport(manifest, secretFindings, config.secretRules),
6272
6445
  "utf8"
6273
6446
  );
6274
6447
  }
6275
6448
  if (artifacts.readme) {
6276
- fs9__namespace.writeFileSync(path10__namespace.join(tmpDir, "README.md"), generateReadme(manifest), "utf8");
6449
+ fs9__namespace.writeFileSync(path10__namespace.join(stagingDir, "README.md"), generateReadme(manifest), "utf8");
6277
6450
  }
6278
6451
  if (artifacts.manifest) {
6279
- fs9__namespace.writeFileSync(path10__namespace.join(tmpDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf8");
6452
+ fs9__namespace.writeFileSync(path10__namespace.join(stagingDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf8");
6280
6453
  }
6281
6454
  if (monolithJson !== null) {
6282
- fs9__namespace.writeFileSync(path10__namespace.join(tmpDir, "full-codebase.json"), monolithJson, "utf8");
6455
+ fs9__namespace.writeFileSync(path10__namespace.join(stagingDir, "full-codebase.json"), monolithJson, "utf8");
6283
6456
  }
6284
6457
  if (monolithMarkdown !== null) {
6285
- fs9__namespace.writeFileSync(path10__namespace.join(tmpDir, "full-codebase.md"), monolithMarkdown, "utf8");
6458
+ fs9__namespace.writeFileSync(path10__namespace.join(stagingDir, "full-codebase.md"), monolithMarkdown, "utf8");
6286
6459
  }
6287
- writeOwnershipMarker(tmpDir, markerPayload(tmpDir, "complete", snapshotId, createdAt));
6288
- swapIntoPlace(tmpDir, outDir);
6289
- stagingStarted = false;
6460
+ writeOwnershipMarker(stagingDir, markerPayload(stagingDir, "complete", snapshotId, createdAt));
6461
+ swapIntoPlace(stagingDir, outDir);
6462
+ stagingDir = null;
6290
6463
  const warningsBeforeHook = manifest.warnings.length;
6291
6464
  runAfterGenerate(config, manifest);
6292
6465
  if (artifacts.manifest && manifest.warnings.length !== warningsBeforeHook) {
@@ -6295,14 +6468,15 @@ function generateSnapshot(configInput, options) {
6295
6468
  writeOwnershipMarker(outDir, markerPayload(outDir, "complete", snapshotId, createdAt));
6296
6469
  return manifest;
6297
6470
  } catch (err) {
6298
- if (stagingStarted) {
6471
+ if (stagingDir !== null) {
6299
6472
  try {
6300
- fs9__namespace.rmSync(tmpDir, { recursive: true, force: true });
6473
+ fs9__namespace.rmSync(stagingDir, { recursive: true, force: true });
6301
6474
  } catch {
6302
6475
  }
6303
6476
  }
6304
6477
  throw err;
6305
6478
  } finally {
6479
+ removeIfEmpty(tmpDir);
6306
6480
  releaseLock();
6307
6481
  }
6308
6482
  }