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