@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.
@@ -2188,11 +2188,19 @@ function isSensitivePath(relativePath) {
2188
2188
  }
2189
2189
  return matchesSensitiveList(posixPath, base);
2190
2190
  }
2191
- function findSecretInPath(relativePath) {
2191
+ var activeSecretRules = null;
2192
+ function setActiveSecretRules(rules) {
2193
+ activeSecretRules = rules === null ? null : [...rules];
2194
+ }
2195
+ function pathScanRules(extraRules) {
2196
+ const fallback = extraRules ?? activeSecretRules ?? [];
2197
+ return [...DEFAULT_SECRET_PATTERNS, ...fallback];
2198
+ }
2199
+ function findSecretInPath(relativePath, extraRules) {
2192
2200
  const posixPath = relativePath.replace(/\\/g, "/");
2193
2201
  const spans = [];
2194
2202
  const rules = [];
2195
- for (const { rule, pattern } of DEFAULT_SECRET_PATTERNS) {
2203
+ for (const { rule, pattern } of pathScanRules(extraRules)) {
2196
2204
  const scan = new RegExp(pattern.source, `${pattern.flags.replace(/[gy]/g, "")}g`);
2197
2205
  let match;
2198
2206
  while ((match = scan.exec(posixPath)) !== null) {
@@ -2200,24 +2208,34 @@ function findSecretInPath(relativePath) {
2200
2208
  scan.lastIndex += 1;
2201
2209
  continue;
2202
2210
  }
2203
- spans.push({ start: match.index, end: match.index + match[0].length, rule });
2211
+ spans.push({ start: match.index, end: match.index + match[0].length });
2204
2212
  if (!rules.includes(rule)) rules.push(rule);
2205
2213
  }
2206
2214
  }
2207
2215
  if (spans.length === 0) return null;
2208
2216
  spans.sort((a, b) => a.start - b.start || a.end - b.end);
2217
+ const merged = [];
2218
+ for (const span of spans) {
2219
+ const last = merged[merged.length - 1];
2220
+ if (last && span.start <= last.end) last.end = Math.max(last.end, span.end);
2221
+ else merged.push({ start: span.start, end: span.end });
2222
+ }
2209
2223
  let out = "";
2210
2224
  let cursor = 0;
2211
- for (const span of spans) {
2212
- if (span.end <= cursor) continue;
2213
- const start = Math.max(span.start, cursor);
2214
- out += posixPath.slice(cursor, start);
2225
+ for (const span of merged) {
2226
+ out += posixPath.slice(cursor, span.start);
2215
2227
  out += REDACTION_MARKER;
2216
- cursor = Math.min(span.end, posixPath.length);
2228
+ cursor = span.end;
2217
2229
  }
2218
2230
  out += posixPath.slice(cursor);
2219
2231
  return { rules, masked: out };
2220
2232
  }
2233
+ function maskSkipPaths(records, extraRules) {
2234
+ return records.map((record) => {
2235
+ const hit = findSecretInPath(record.path, extraRules);
2236
+ return hit ? { ...record, path: hit.masked } : record;
2237
+ });
2238
+ }
2221
2239
  function globToRegExp(glob) {
2222
2240
  const normalized = glob.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/+$/, "");
2223
2241
  const anchored = normalized.startsWith("/");
@@ -2448,6 +2466,7 @@ function collectFiles(cwd, options = {}) {
2448
2466
  const includePatterns = options.includePatterns ?? [];
2449
2467
  const excludePatterns = options.excludePatterns ?? [];
2450
2468
  const includeSensitiveFiles = options.includeSensitiveFiles ?? false;
2469
+ const pathRules = options.secretRules;
2451
2470
  const isToolOwned = makeToolOwnedDecider(cwd);
2452
2471
  let candidates;
2453
2472
  const skipped = [];
@@ -2483,7 +2502,7 @@ function collectFiles(cwd, options = {}) {
2483
2502
  }
2484
2503
  }
2485
2504
  } catch {
2486
- const walked = walkDirectory(cwd, cwd, /* @__PURE__ */ new Set(), true);
2505
+ const walked = walkDirectory(cwd, cwd, rootRealFor(cwd), /* @__PURE__ */ new Set(), true);
2487
2506
  candidates = walked.files;
2488
2507
  skipped.push(...walked.skipped);
2489
2508
  }
@@ -2521,7 +2540,7 @@ function collectFiles(cwd, options = {}) {
2521
2540
  });
2522
2541
  return false;
2523
2542
  }
2524
- const secret = findSecretInPath(file);
2543
+ const secret = findSecretInPath(file, pathRules);
2525
2544
  if (secret) {
2526
2545
  skipped.push({
2527
2546
  path: secret.masked,
@@ -2532,30 +2551,29 @@ function collectFiles(cwd, options = {}) {
2532
2551
  }
2533
2552
  return true;
2534
2553
  }).sort();
2535
- return { files, skipped };
2554
+ return { files, skipped: maskSkipPaths(skipped, pathRules) };
2536
2555
  }
2537
- function walkDirectory(dir, baseDir, visited, isRoot = false) {
2538
- const results = { files: [], skipped: [] };
2539
- let realDir;
2540
- try {
2541
- realDir = fs9.realpathSync(dir);
2542
- } catch {
2543
- realDir = dir;
2544
- }
2545
- if (visited.has(realDir)) return results;
2546
- visited.add(realDir);
2547
- let rootReal;
2556
+ function rootRealFor(root) {
2548
2557
  try {
2549
- rootReal = fs9.realpathSync(baseDir);
2558
+ return fs9.realpathSync(root);
2550
2559
  } catch {
2551
- rootReal = baseDir;
2560
+ return root;
2552
2561
  }
2562
+ }
2563
+ function walkDirectory(dir, baseDir, rootReal, visited, isRoot = false) {
2564
+ const results = { files: [], skipped: [] };
2565
+ if (visited.has(dir)) return results;
2566
+ visited.add(dir);
2553
2567
  const ignoredByGitignore = isRoot ? readGitignoreMatcher(baseDir) : () => false;
2554
2568
  let entries;
2555
2569
  try {
2556
2570
  entries = fs9.readdirSync(dir, { withFileTypes: true });
2557
2571
  } catch {
2558
- results.skipped.push({ path: dir, reason: "read-error", detail: "directory unreadable" });
2572
+ results.skipped.push({
2573
+ path: path10.relative(baseDir, dir).replace(/\\/g, "/") || ".",
2574
+ reason: "read-error",
2575
+ detail: "directory unreadable"
2576
+ });
2559
2577
  return results;
2560
2578
  }
2561
2579
  for (const entry of entries) {
@@ -2634,7 +2652,7 @@ function walkDirectory(dir, baseDir, visited, isRoot = false) {
2634
2652
  isFile = true;
2635
2653
  }
2636
2654
  if (isDirectory) {
2637
- const nested = walkDirectory(fullPath, baseDir, visited);
2655
+ const nested = walkDirectory(fullPath, baseDir, rootReal, visited);
2638
2656
  results.files.push(...nested.files);
2639
2657
  results.skipped.push(...nested.skipped);
2640
2658
  } else if (isFile) {
@@ -3226,6 +3244,7 @@ function resolveConfig(userConfig, cliOptions, baseDir) {
3226
3244
  ...aliasedRules ?? []
3227
3245
  ]);
3228
3246
  const secretRules = disableDefaultPatterns ? compiledCustomRules : [...compileSecretRules(DEFAULT_SECRET_PATTERNS), ...compiledCustomRules];
3247
+ setActiveSecretRules(secretRules);
3229
3248
  const cliMonolith = cliOptions?.includeMonolith;
3230
3249
  const includeMonolith = cliMonolith ?? strictBoolean(userConfig?.includeMonolith, false, "`includeMonolith`");
3231
3250
  const artifacts = resolveArtifacts(
@@ -3333,7 +3352,8 @@ function defineConfig(config) {
3333
3352
  }
3334
3353
 
3335
3354
  // src/generator/audit.ts
3336
- function generateAuditReport(manifest, secretFindings) {
3355
+ function generateAuditReport(manifest, secretFindings, secretRules) {
3356
+ const mask = (value) => findSecretInPath(value, secretRules)?.masked ?? value;
3337
3357
  const lines = [
3338
3358
  "# Codebase Health & Security Audit",
3339
3359
  "",
@@ -3368,7 +3388,7 @@ function generateAuditReport(manifest, secretFindings) {
3368
3388
  "| :--- | :--- | :--- |"
3369
3389
  );
3370
3390
  for (const finding of secretFindings) {
3371
- lines.push(`| \`${finding.path ?? "unknown"}\` | ${finding.line} | \`${finding.rule}\` |`);
3391
+ lines.push(`| \`${mask(finding.path ?? "unknown")}\` | ${finding.line} | \`${finding.rule}\` |`);
3372
3392
  }
3373
3393
  lines.push("", "### Matches by rule", "", "| Rule | Matches |", "| :--- | :--- |");
3374
3394
  for (const [rule, count] of Array.from(byRule.entries()).sort((a, b) => b[1] - a[1])) {
@@ -3390,7 +3410,7 @@ function generateAuditReport(manifest, secretFindings) {
3390
3410
  for (const file of largeFiles) {
3391
3411
  const sizeKb = (file.sizeBytes / 1024).toFixed(1);
3392
3412
  const status = file.isPreviewTruncated ? "Truncated Head/Tail" : "Full";
3393
- lines.push(`| \`${file.relativePath}\` | ${sizeKb} KB | ${file.lines} | ${status} |`);
3413
+ lines.push(`| \`${mask(file.relativePath)}\` | ${sizeKb} KB | ${file.lines} | ${status} |`);
3394
3414
  }
3395
3415
  lines.push("");
3396
3416
  }
@@ -3432,7 +3452,7 @@ function generateAuditReport(manifest, secretFindings) {
3432
3452
  "| :--- | :--- | :--- |"
3433
3453
  );
3434
3454
  for (const skip of manifest.skipped) {
3435
- lines.push(`| \`${skip.path}\` | \`${skip.reason}\` | ${skip.detail ?? "-"} |`);
3455
+ lines.push(`| \`${mask(skip.path)}\` | \`${skip.reason}\` | ${skip.detail ?? "-"} |`);
3436
3456
  }
3437
3457
  lines.push("");
3438
3458
  }
@@ -4094,6 +4114,9 @@ function detectLanguage(filePath) {
4094
4114
  const ext = path10.posix.extname(base).toLowerCase();
4095
4115
  return LANGUAGE_BY_EXTENSION[ext] ?? "text";
4096
4116
  }
4117
+ function maskedPath(value, secretRules) {
4118
+ return findSecretInPath(value, secretRules)?.masked ?? value;
4119
+ }
4097
4120
  function emptyDrift() {
4098
4121
  return {
4099
4122
  missingInSnapshot: [],
@@ -4164,15 +4187,20 @@ function validateSnapshot(configInput) {
4164
4187
  const skippedSet = new Set(
4165
4188
  manifest.skipped.filter((entry) => entry && typeof entry.path === "string").map((entry) => entry.path)
4166
4189
  );
4190
+ const isSkippedOnDisk = (diskFile) => {
4191
+ if (skippedSet.has(diskFile)) return true;
4192
+ return skippedSet.has(maskedPath(diskFile, config.secretRules));
4193
+ };
4167
4194
  const collection = collectFiles(config.cwd, {
4168
4195
  includePatterns: config.include,
4169
4196
  excludePatterns: [...config.excludePatterns, ...config.presetExclude],
4170
- includeSensitiveFiles: config.includeSensitiveFiles
4197
+ includeSensitiveFiles: config.includeSensitiveFiles,
4198
+ secretRules: config.secretRules
4171
4199
  });
4172
4200
  for (const diskFile of collection.files) {
4173
4201
  const snapshotHash = manifestFileMap.get(diskFile);
4174
4202
  if (!snapshotHash) {
4175
- if (skippedSet.has(diskFile) && !wouldNowBeIncluded(config.cwd, diskFile)) continue;
4203
+ if (isSkippedOnDisk(diskFile) && !wouldNowBeIncluded(config.cwd, diskFile)) continue;
4176
4204
  drift.missingInSnapshot.push(diskFile);
4177
4205
  continue;
4178
4206
  }
@@ -4441,10 +4469,19 @@ function validateSnapshot(configInput) {
4441
4469
  `${drift.configMismatches.length} configuration mismatch(es) between the manifest and this run.`
4442
4470
  );
4443
4471
  }
4472
+ const maskedDrift = {
4473
+ ...drift,
4474
+ missingInSnapshot: drift.missingInSnapshot.map((value) => maskedPath(value, config.secretRules)),
4475
+ missingOnDisk: drift.missingOnDisk.map((value) => maskedPath(value, config.secretRules)),
4476
+ hashMismatches: drift.hashMismatches.map((mismatch) => ({
4477
+ ...mismatch,
4478
+ path: maskedPath(mismatch.path, config.secretRules)
4479
+ }))
4480
+ };
4444
4481
  return {
4445
4482
  valid: errors.length === 0,
4446
4483
  manifestPresent: true,
4447
- drift,
4484
+ drift: maskedDrift,
4448
4485
  errors,
4449
4486
  warnings
4450
4487
  };
@@ -4479,18 +4516,22 @@ function readManifest2(config) {
4479
4516
  function explainPath(config, target) {
4480
4517
  const relative4 = normalizeTarget(config.cwd, target);
4481
4518
  const absolute = path10.join(config.cwd, relative4);
4519
+ const maskedPath2 = findSecretInPath(relative4, config.secretRules)?.masked ?? relative4;
4482
4520
  const result = {
4483
- path: relative4,
4521
+ path: maskedPath2,
4484
4522
  exists: fs9.existsSync(absolute),
4485
4523
  included: false
4486
4524
  };
4487
4525
  const collection = collectFiles(config.cwd, {
4488
4526
  includePatterns: config.include,
4489
4527
  excludePatterns: [...config.excludePatterns, ...config.presetExclude],
4490
- includeSensitiveFiles: config.includeSensitiveFiles
4528
+ includeSensitiveFiles: config.includeSensitiveFiles,
4529
+ secretRules: config.secretRules
4491
4530
  });
4492
4531
  if (!collection.files.includes(relative4)) {
4493
- const record = collection.skipped.find((entry2) => entry2.path === relative4);
4532
+ const record = collection.skipped.find(
4533
+ (entry2) => entry2.path === relative4 || entry2.path === maskedPath2
4534
+ );
4494
4535
  if (record) {
4495
4536
  result.reason = record.reason;
4496
4537
  result.detail = record.detail;
@@ -4527,7 +4568,7 @@ function explainPath(config, target) {
4527
4568
  result.secrets = findings.slice(0, 20).map((finding) => ({ rule: finding.rule, line: finding.line }));
4528
4569
  }
4529
4570
  const manifest = readManifest2(config);
4530
- const entry = manifest?.files.find((file) => file.relativePath === relative4);
4571
+ const entry = Array.isArray(manifest?.files) ? manifest.files.find((file) => file.relativePath === relative4) : void 0;
4531
4572
  result.part = entry?.part ?? null;
4532
4573
  result.inclusionMode = entry?.inclusionMode;
4533
4574
  return result;
@@ -4632,12 +4673,13 @@ function compareManifests(before, after) {
4632
4673
  if (!right.has(filePath)) removed.push(filePath);
4633
4674
  }
4634
4675
  const sort = (values) => values.sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
4676
+ const mask = (values) => values.map((value) => findSecretInPath(value)?.masked ?? value);
4635
4677
  return {
4636
4678
  before: side(before),
4637
4679
  after: side(after),
4638
- added: sort(added),
4639
- removed: sort(removed),
4640
- changed: sort(changed),
4680
+ added: mask(sort(added)),
4681
+ removed: mask(sort(removed)),
4682
+ changed: mask(sort(changed)),
4641
4683
  unchanged
4642
4684
  };
4643
4685
  }
@@ -5362,7 +5404,7 @@ async function runInit(options) {
5362
5404
 
5363
5405
  // src/index.ts
5364
5406
  var GENERATOR_NAME = "@ankurhalder/codebase-snapshot";
5365
- var GENERATOR_VERSION = "4.0.0";
5407
+ var GENERATOR_VERSION = "4.0.1";
5366
5408
  var SCHEMA_VERSION = "3.0.0";
5367
5409
  function sha256Hex2(input) {
5368
5410
  return crypto.createHash("sha256").update(input).digest("hex");
@@ -5399,7 +5441,6 @@ function bump(counts, key) {
5399
5441
  }
5400
5442
  var RELATIVE_SPECIFIER = /^\.{1,2}\//;
5401
5443
  var IMPORT_CANDIDATE_SUFFIXES = [
5402
- "",
5403
5444
  ".ts",
5404
5445
  ".tsx",
5405
5446
  ".mts",
@@ -5413,23 +5454,59 @@ var IMPORT_CANDIDATE_SUFFIXES = [
5413
5454
  "/index.js",
5414
5455
  "/index.mjs"
5415
5456
  ];
5416
- function resolveSpecifier(fromFile, specifier, knownFiles) {
5457
+ var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
5458
+ ".ts",
5459
+ ".tsx",
5460
+ ".mts",
5461
+ ".cts",
5462
+ ".js",
5463
+ ".jsx",
5464
+ ".mjs",
5465
+ ".cjs"
5466
+ ]);
5467
+ function buildImportResolver(files) {
5468
+ const byPath = /* @__PURE__ */ new Map();
5469
+ const byPathFolded = /* @__PURE__ */ new Map();
5470
+ const byStem = /* @__PURE__ */ new Map();
5471
+ const byStemFolded = /* @__PURE__ */ new Map();
5472
+ const remember = (map, key, value) => {
5473
+ if (!map.has(key)) map.set(key, value);
5474
+ };
5475
+ for (const file of files) {
5476
+ const relativePath = file.relativePath;
5477
+ remember(byPath, relativePath, relativePath);
5478
+ remember(byPathFolded, relativePath.toLowerCase(), relativePath);
5479
+ const extension = path10.posix.extname(relativePath);
5480
+ if (!SOURCE_EXTENSIONS.has(extension)) continue;
5481
+ const stem = relativePath.slice(0, -extension.length);
5482
+ remember(byStem, stem, relativePath);
5483
+ remember(byStemFolded, stem.toLowerCase(), relativePath);
5484
+ if (path10.posix.basename(stem) === "index") {
5485
+ const directory = path10.posix.dirname(stem);
5486
+ remember(byStem, directory, relativePath);
5487
+ remember(byStemFolded, directory.toLowerCase(), relativePath);
5488
+ }
5489
+ }
5490
+ return { byPath, byPathFolded, byStem, byStemFolded };
5491
+ }
5492
+ function resolveSpecifier(fromFile, specifier, resolver) {
5417
5493
  if (!RELATIVE_SPECIFIER.test(specifier)) return null;
5418
- const baseDir = path10.posix.dirname(fromFile);
5419
- const joined = path10.posix.normalize(path10.posix.join(baseDir, specifier));
5420
- const candidate = joined.replace(/\.(?:js|mjs|cjs|jsx)$/, "");
5494
+ const joined = path10.posix.normalize(
5495
+ path10.posix.join(path10.posix.dirname(fromFile), specifier)
5496
+ );
5497
+ const exact = (key) => resolver.byPath.get(key) ?? resolver.byPathFolded.get(key.toLowerCase()) ?? null;
5498
+ const direct = exact(joined);
5499
+ if (direct) return direct;
5421
5500
  for (const suffix of IMPORT_CANDIDATE_SUFFIXES) {
5422
- const withSuffix = `${joined}${suffix}`;
5423
- if (knownFiles.has(withSuffix)) return withSuffix;
5424
- }
5425
- const extensionSwap = new RegExp(`${candidate}\\.(?:ts|tsx|mts|cts|js|jsx|mjs|cjs)$`);
5426
- for (const file of knownFiles) {
5427
- if (extensionSwap.test(file)) return file;
5501
+ const hit = exact(`${joined}${suffix}`);
5502
+ if (hit) return hit;
5428
5503
  }
5429
- return null;
5504
+ const stem = joined.replace(/\.(?:js|mjs|cjs|jsx)$/, "");
5505
+ const byStem = (key) => resolver.byStem.get(key) ?? resolver.byStemFolded.get(key.toLowerCase()) ?? null;
5506
+ return byStem(joined) ?? byStem(stem);
5430
5507
  }
5431
5508
  function buildIndexes(files) {
5432
- const knownFiles = new Set(files.map((file) => file.relativePath));
5509
+ const resolver = buildImportResolver(files);
5433
5510
  const importerCounts = /* @__PURE__ */ new Map();
5434
5511
  const byFile = files.filter((file) => isSourceSummary(file.summary)).map((file) => {
5435
5512
  const summary = file.summary;
@@ -5441,7 +5518,7 @@ function buildIndexes(files) {
5441
5518
  });
5442
5519
  for (const entry of byFile) {
5443
5520
  for (const specifier of entry.imports) {
5444
- const resolved = resolveSpecifier(entry.path, specifier, knownFiles);
5521
+ const resolved = resolveSpecifier(entry.path, specifier, resolver);
5445
5522
  if (resolved) importerCounts.set(resolved, (importerCounts.get(resolved) ?? 0) + 1);
5446
5523
  }
5447
5524
  }
@@ -5532,15 +5609,22 @@ function resolveSafeOutputDirs(config) {
5532
5609
  return { outDir, tmpDir };
5533
5610
  }
5534
5611
  var PLACEHOLDER_ENTRIES = /* @__PURE__ */ new Set([".gitkeep", ".gitignore", ".DS_Store"]);
5535
- var STAGING_ENTRIES = /* @__PURE__ */ new Set([
5536
- ".codebase-snapshot",
5537
- "manifest.json",
5538
- "README.md",
5539
- "parts",
5540
- "reports",
5541
- "full-codebase.json",
5542
- "full-codebase.md"
5543
- ]);
5612
+ var RUN_DIR_PREFIX = "run-";
5613
+ var STALE_RUN_MS = 60 * 60 * 1e3;
5614
+ function maskPathText(value, secretRules) {
5615
+ return findSecretInPath(value, secretRules)?.masked ?? value;
5616
+ }
5617
+ function maskSkipRecords(records, secretRules) {
5618
+ return maskSkipPaths(records, secretRules);
5619
+ }
5620
+ function isProcessAlive(pid) {
5621
+ try {
5622
+ process.kill(pid, 0);
5623
+ return true;
5624
+ } catch (err) {
5625
+ return err.code !== "ESRCH";
5626
+ }
5627
+ }
5544
5628
  var OWNERSHIP_MARKER = ".codebase-snapshot";
5545
5629
  var SUPPORTED_MARKER_SCHEMAS = /* @__PURE__ */ new Set([SCHEMA_VERSION]);
5546
5630
  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;
@@ -5570,6 +5654,7 @@ function markerPayload(dir, state, id, createdAt) {
5570
5654
  createdAt,
5571
5655
  state,
5572
5656
  id,
5657
+ pid: process.pid,
5573
5658
  entries: listEntries(dir)
5574
5659
  };
5575
5660
  const manifestPath = path10.join(dir, "manifest.json");
@@ -5633,6 +5718,7 @@ function readOwnershipMarker(dir) {
5633
5718
  createdAt: typeof candidate.createdAt === "string" ? candidate.createdAt : "unknown",
5634
5719
  state: candidate.state,
5635
5720
  id: candidate.id,
5721
+ pid: typeof candidate.pid === "number" ? candidate.pid : void 0,
5636
5722
  manifestSha256: typeof candidate.manifestSha256 === "string" ? candidate.manifestSha256 : void 0,
5637
5723
  entries: Array.isArray(candidate.entries) && candidate.entries.every((entry) => typeof entry === "string") ? candidate.entries : void 0
5638
5724
  },
@@ -5740,7 +5826,7 @@ function describeEntries(entries, limit = 5) {
5740
5826
  const shown = entries.slice(0, limit).join(", ");
5741
5827
  return entries.length > limit ? `${shown}, and ${entries.length - limit} more` : shown;
5742
5828
  }
5743
- function assertReplaceable(target, label) {
5829
+ function assertReplaceable(target, label, secretRules) {
5744
5830
  if (!fs9.existsSync(target)) return;
5745
5831
  if (!fs9.statSync(target).isDirectory()) {
5746
5832
  throw new Error(
@@ -5749,34 +5835,89 @@ function assertReplaceable(target, label) {
5749
5835
  }
5750
5836
  const entries = fs9.readdirSync(target);
5751
5837
  if (entries.length === 0) return;
5752
- if (label === "outDir" && entries.every((name) => PLACEHOLDER_ENTRIES.has(name))) return;
5753
- const { marker: marker2, problem } = readOwnershipMarker(target);
5754
- if (label === "tmpDir") {
5755
- if (!marker2) {
5756
- throw new Error(
5757
- `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.`
5758
- );
5759
- }
5760
- const unexpected = entries.filter((name) => !STAGING_ENTRIES.has(name));
5761
- if (unexpected.length > 0) {
5762
- throw new Error(
5763
- `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.`
5764
- );
5765
- }
5766
- return;
5767
- }
5838
+ if (entries.every((name) => PLACEHOLDER_ENTRIES.has(name))) return;
5839
+ const { problem } = readOwnershipMarker(target);
5768
5840
  const verdict = assessSnapshotDirectory(target);
5769
5841
  if (verdict.owned) return;
5842
+ const shownForeign = verdict.foreignEntries.map((name) => maskPathText(name, secretRules));
5843
+ const shownEntries = entries.map((name) => maskPathText(name, secretRules));
5770
5844
  if (verdict.foreignEntries.length > 0) {
5771
5845
  throw new Error(
5772
- `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.`
5846
+ `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.`
5773
5847
  );
5774
5848
  }
5775
5849
  const reason = verdict.problem ?? problem ?? "it does not look like a snapshot this generator wrote";
5776
5850
  throw new Error(
5777
- `Refusing to replace ${label} ${target}: ${reason} (found ${describeEntries(entries)}). Remove the directory yourself, or point ${label} at a new or empty directory.`
5851
+ `Refusing to replace ${label} ${target}: ${reason} (found ${describeEntries(shownEntries)}). Remove the directory yourself, or point ${label} at a new or empty directory.`
5778
5852
  );
5779
5853
  }
5854
+ function assertStagingRootUsable(tmpDir, secretRules) {
5855
+ if (!fs9.existsSync(tmpDir)) return;
5856
+ if (!fs9.statSync(tmpDir).isDirectory()) {
5857
+ throw new Error(`Refusing to write into tmpDir ${tmpDir}: it exists but is not a directory.`);
5858
+ }
5859
+ const entries = fs9.readdirSync(tmpDir);
5860
+ if (entries.length === 0) return;
5861
+ const foreign = entries.filter((name) => {
5862
+ const full = path10.join(tmpDir, name);
5863
+ if (!name.startsWith(RUN_DIR_PREFIX)) return true;
5864
+ let stats;
5865
+ try {
5866
+ stats = fs9.statSync(full);
5867
+ } catch {
5868
+ return true;
5869
+ }
5870
+ if (!stats.isDirectory()) return true;
5871
+ const { marker: marker2 } = readOwnershipMarker(full);
5872
+ if (marker2) return false;
5873
+ try {
5874
+ return fs9.readdirSync(full).length > 0;
5875
+ } catch {
5876
+ return true;
5877
+ }
5878
+ });
5879
+ if (foreign.length > 0) {
5880
+ const shown = describeEntries(foreign.map((name) => maskPathText(name, secretRules)));
5881
+ throw new Error(
5882
+ `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.`
5883
+ );
5884
+ }
5885
+ }
5886
+ function cleanStaleRunDirs(tmpDir) {
5887
+ let entries;
5888
+ try {
5889
+ entries = fs9.readdirSync(tmpDir);
5890
+ } catch {
5891
+ return;
5892
+ }
5893
+ for (const name of entries) {
5894
+ if (!name.startsWith(RUN_DIR_PREFIX)) continue;
5895
+ const full = path10.join(tmpDir, name);
5896
+ const { marker: marker2 } = readOwnershipMarker(full);
5897
+ if (marker2) {
5898
+ const created = Date.parse(marker2.createdAt);
5899
+ const aged = !Number.isFinite(created) || Date.now() - created > STALE_RUN_MS;
5900
+ const abandoned = typeof marker2.pid === "number" && !isProcessAlive(marker2.pid);
5901
+ if (aged || abandoned) {
5902
+ try {
5903
+ fs9.rmSync(full, { recursive: true, force: true });
5904
+ } catch {
5905
+ }
5906
+ }
5907
+ continue;
5908
+ }
5909
+ try {
5910
+ if (fs9.readdirSync(full).length === 0) fs9.rmSync(full, { recursive: true, force: true });
5911
+ } catch {
5912
+ }
5913
+ }
5914
+ }
5915
+ function removeIfEmpty(dir) {
5916
+ try {
5917
+ if (fs9.readdirSync(dir).length === 0) fs9.rmSync(dir, { recursive: true, force: true });
5918
+ } catch {
5919
+ }
5920
+ }
5780
5921
  function ensureParentDirectory(target, label) {
5781
5922
  const parent = path10.dirname(target);
5782
5923
  if (fs9.existsSync(parent) && !fs9.statSync(parent).isDirectory()) {
@@ -5792,14 +5933,12 @@ function ensureParentDirectory(target, label) {
5792
5933
  );
5793
5934
  }
5794
5935
  }
5795
- function acquireRunLock(outDir) {
5796
- const lockPath = path10.join(
5797
- os.tmpdir(),
5798
- `codebase-snapshot-${sha256Hex2(outDir).slice(0, 16)}.lock`
5799
- );
5936
+ function acquireRunLock(stagingRoot) {
5937
+ const key = lockKeyFor(stagingRoot);
5938
+ const lockPath = path10.join(os.tmpdir(), `codebase-snapshot-${key}.lock`);
5800
5939
  const payload = `${JSON.stringify({
5801
5940
  pid: process.pid,
5802
- outDir,
5941
+ stagingRoot,
5803
5942
  startedAt: (/* @__PURE__ */ new Date()).toISOString()
5804
5943
  })}
5805
5944
  `;
@@ -5824,11 +5963,20 @@ function acquireRunLock(outDir) {
5824
5963
  continue;
5825
5964
  }
5826
5965
  throw new Error(
5827
- `Another generate run is already writing ${outDir}. Wait for it to finish, or delete ${lockPath} if no other run is active.`
5966
+ `Another generate run is already using the staging directory ${stagingRoot}. Wait for it to finish, or delete ${lockPath} if no other run is active.`
5828
5967
  );
5829
5968
  }
5830
5969
  }
5831
- throw new Error(`Could not acquire the generate lock for ${outDir}.`);
5970
+ throw new Error(`Could not acquire the generate lock for ${stagingRoot}.`);
5971
+ }
5972
+ function lockKeyFor(stagingRoot) {
5973
+ let key = stagingRoot;
5974
+ try {
5975
+ key = fs9.realpathSync(stagingRoot);
5976
+ } catch {
5977
+ }
5978
+ if (process.platform === "win32" || process.platform === "darwin") key = key.toLowerCase();
5979
+ return sha256Hex2(key).slice(0, 16);
5832
5980
  }
5833
5981
  function lockOwnerIsGone(lockPath) {
5834
5982
  let raw;
@@ -5864,18 +6012,32 @@ function writeManifestAtomically(manifestPath, manifest) {
5864
6012
  throw err;
5865
6013
  }
5866
6014
  }
5867
- function swapIntoPlace(tmpDir, outDir) {
6015
+ function swapIntoPlace(stagingDir, outDir) {
5868
6016
  ensureParentDirectory(outDir, "outDir");
6017
+ const deadline = Date.now() + 3e3;
6018
+ for (let attempt = 0; ; attempt += 1) {
6019
+ try {
6020
+ swapOnce(stagingDir, outDir);
6021
+ return;
6022
+ } catch (err) {
6023
+ const code = err.code;
6024
+ const racy = code === "ENOENT" || code === "ENOTEMPTY" || code === "EEXIST" || code === "EPERM";
6025
+ if (!racy || attempt >= 10 || Date.now() > deadline) throw err;
6026
+ sleepSync(50);
6027
+ }
6028
+ }
6029
+ }
6030
+ function swapOnce(stagingDir, outDir) {
5869
6031
  const backupDir = `${outDir}.backup-${process.pid}-${Date.now()}`;
5870
6032
  const hadPrevious = fs9.existsSync(outDir);
5871
6033
  if (hadPrevious) fs9.renameSync(outDir, backupDir);
5872
6034
  try {
5873
6035
  try {
5874
- fs9.renameSync(tmpDir, outDir);
6036
+ fs9.renameSync(stagingDir, outDir);
5875
6037
  } catch (err) {
5876
6038
  if (err.code === "EXDEV") {
5877
- fs9.cpSync(tmpDir, outDir, { recursive: true });
5878
- fs9.rmSync(tmpDir, { recursive: true, force: true });
6039
+ fs9.cpSync(stagingDir, outDir, { recursive: true });
6040
+ fs9.rmSync(stagingDir, { recursive: true, force: true });
5879
6041
  } else {
5880
6042
  throw err;
5881
6043
  }
@@ -5889,6 +6051,10 @@ function swapIntoPlace(tmpDir, outDir) {
5889
6051
  throw err;
5890
6052
  }
5891
6053
  }
6054
+ function sleepSync(milliseconds) {
6055
+ const buffer = new Int32Array(new SharedArrayBuffer(4));
6056
+ Atomics.wait(buffer, 0, 0, milliseconds);
6057
+ }
5892
6058
  function artifactWarnings(artifacts) {
5893
6059
  const messages = [];
5894
6060
  if (!artifacts.manifest) {
@@ -5951,11 +6117,12 @@ function generateSnapshot(configInput, options) {
5951
6117
  const collection = collectFiles(config.cwd, {
5952
6118
  includePatterns: config.include,
5953
6119
  excludePatterns: [...config.excludePatterns, ...config.presetExclude],
5954
- includeSensitiveFiles: config.includeSensitiveFiles
6120
+ includeSensitiveFiles: config.includeSensitiveFiles,
6121
+ secretRules: config.secretRules
5955
6122
  });
5956
6123
  const files = [];
5957
6124
  const monolithBodies = /* @__PURE__ */ new Map();
5958
- const skipped = [...collection.skipped];
6125
+ const skipped = maskSkipRecords([...collection.skipped], config.secretRules);
5959
6126
  const secretFindings = [];
5960
6127
  const warnings = [];
5961
6128
  const layerCounts = {};
@@ -6029,12 +6196,12 @@ function generateSnapshot(configInput, options) {
6029
6196
  summaryQuality = outcome?.quality;
6030
6197
  if (isSourceSummary(summary) && summary.parseErrors) {
6031
6198
  warnings.push(
6032
- `Parse errors in ${relativePath} (${summary.parseErrors}); its summary is incomplete.`
6199
+ `Parse errors in ${maskPathText(relativePath, config.secretRules)} (${summary.parseErrors}); its summary is incomplete.`
6033
6200
  );
6034
6201
  }
6035
6202
  } catch (err) {
6036
6203
  warnings.push(
6037
- `Summary skipped for ${relativePath}: ${err.message}`.replace(/\s+/g, " ")
6204
+ `Summary skipped for ${maskPathText(relativePath, config.secretRules)}: ${err.message}`.replace(/\s+/g, " ")
6038
6205
  );
6039
6206
  }
6040
6207
  files.push({
@@ -6143,12 +6310,12 @@ function generateSnapshot(configInput, options) {
6143
6310
  const { content: _content, ...rest } = file;
6144
6311
  return rest;
6145
6312
  }),
6146
- skipped,
6313
+ skipped: maskSkipRecords(skipped, config.secretRules),
6147
6314
  warnings
6148
6315
  };
6149
6316
  manifest.warnings = [
6150
6317
  ...warnings,
6151
- ...secretFindings.map((finding) => `${finding.rule} at ${finding.path ?? "unknown"}:${finding.line}`)
6318
+ ...secretFindings.map((finding) => `${finding.rule} at ${maskPathText(finding.path ?? "unknown", config.secretRules)}:${finding.line}`)
6152
6319
  ];
6153
6320
  manifest.totals.warningCount = manifest.warnings.length;
6154
6321
  let monolithJson = null;
@@ -6194,31 +6361,33 @@ function generateSnapshot(configInput, options) {
6194
6361
  manifest.totals.warningCount = manifest.warnings.length;
6195
6362
  const { outDir, tmpDir } = resolveSafeOutputDirs(config);
6196
6363
  const { artifacts } = config;
6197
- assertReplaceable(tmpDir, "tmpDir");
6198
- assertReplaceable(outDir, "outDir");
6364
+ assertReplaceable(outDir, "outDir", config.secretRules);
6365
+ assertStagingRootUsable(tmpDir, config.secretRules);
6199
6366
  if (dryRun) {
6200
6367
  runAfterGenerate(config, manifest);
6201
6368
  return manifest;
6202
6369
  }
6203
- const releaseLock = acquireRunLock(outDir);
6204
- let stagingStarted = false;
6370
+ const releaseLock = acquireRunLock(tmpDir);
6371
+ let stagingDir = null;
6205
6372
  try {
6206
6373
  ensureParentDirectory(outDir, "outDir");
6207
- fs9.rmSync(tmpDir, { recursive: true, force: true });
6208
6374
  fs9.mkdirSync(tmpDir, { recursive: true });
6209
- stagingStarted = true;
6375
+ cleanStaleRunDirs(tmpDir);
6210
6376
  const snapshotId = crypto.randomUUID();
6211
6377
  const createdAt = (/* @__PURE__ */ new Date()).toISOString();
6212
- writeOwnershipMarker(tmpDir, {
6378
+ stagingDir = path10.join(tmpDir, `${RUN_DIR_PREFIX}${process.pid}-${snapshotId.slice(0, 8)}`);
6379
+ fs9.mkdirSync(stagingDir);
6380
+ writeOwnershipMarker(stagingDir, {
6213
6381
  generator: GENERATOR_NAME,
6214
6382
  version: GENERATOR_VERSION,
6215
6383
  schemaVersion: SCHEMA_VERSION,
6216
6384
  createdAt,
6217
6385
  state: "staging",
6218
- id: snapshotId
6386
+ id: snapshotId,
6387
+ pid: process.pid
6219
6388
  });
6220
- const partsDir = path10.join(tmpDir, "parts");
6221
- const reportsDir = path10.join(tmpDir, "reports");
6389
+ const partsDir = path10.join(stagingDir, "parts");
6390
+ const reportsDir = path10.join(stagingDir, "reports");
6222
6391
  if (artifacts.parts) fs9.mkdirSync(partsDir, { recursive: true });
6223
6392
  if (artifacts.navigation || artifacts.projectTree || artifacts.audit) {
6224
6393
  fs9.mkdirSync(reportsDir, { recursive: true });
@@ -6238,25 +6407,25 @@ function generateSnapshot(configInput, options) {
6238
6407
  if (artifacts.audit) {
6239
6408
  fs9.writeFileSync(
6240
6409
  path10.join(reportsDir, "audit.md"),
6241
- generateAuditReport(manifest, secretFindings),
6410
+ generateAuditReport(manifest, secretFindings, config.secretRules),
6242
6411
  "utf8"
6243
6412
  );
6244
6413
  }
6245
6414
  if (artifacts.readme) {
6246
- fs9.writeFileSync(path10.join(tmpDir, "README.md"), generateReadme(manifest), "utf8");
6415
+ fs9.writeFileSync(path10.join(stagingDir, "README.md"), generateReadme(manifest), "utf8");
6247
6416
  }
6248
6417
  if (artifacts.manifest) {
6249
- fs9.writeFileSync(path10.join(tmpDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf8");
6418
+ fs9.writeFileSync(path10.join(stagingDir, "manifest.json"), JSON.stringify(manifest, null, 2), "utf8");
6250
6419
  }
6251
6420
  if (monolithJson !== null) {
6252
- fs9.writeFileSync(path10.join(tmpDir, "full-codebase.json"), monolithJson, "utf8");
6421
+ fs9.writeFileSync(path10.join(stagingDir, "full-codebase.json"), monolithJson, "utf8");
6253
6422
  }
6254
6423
  if (monolithMarkdown !== null) {
6255
- fs9.writeFileSync(path10.join(tmpDir, "full-codebase.md"), monolithMarkdown, "utf8");
6424
+ fs9.writeFileSync(path10.join(stagingDir, "full-codebase.md"), monolithMarkdown, "utf8");
6256
6425
  }
6257
- writeOwnershipMarker(tmpDir, markerPayload(tmpDir, "complete", snapshotId, createdAt));
6258
- swapIntoPlace(tmpDir, outDir);
6259
- stagingStarted = false;
6426
+ writeOwnershipMarker(stagingDir, markerPayload(stagingDir, "complete", snapshotId, createdAt));
6427
+ swapIntoPlace(stagingDir, outDir);
6428
+ stagingDir = null;
6260
6429
  const warningsBeforeHook = manifest.warnings.length;
6261
6430
  runAfterGenerate(config, manifest);
6262
6431
  if (artifacts.manifest && manifest.warnings.length !== warningsBeforeHook) {
@@ -6265,14 +6434,15 @@ function generateSnapshot(configInput, options) {
6265
6434
  writeOwnershipMarker(outDir, markerPayload(outDir, "complete", snapshotId, createdAt));
6266
6435
  return manifest;
6267
6436
  } catch (err) {
6268
- if (stagingStarted) {
6437
+ if (stagingDir !== null) {
6269
6438
  try {
6270
- fs9.rmSync(tmpDir, { recursive: true, force: true });
6439
+ fs9.rmSync(stagingDir, { recursive: true, force: true });
6271
6440
  } catch {
6272
6441
  }
6273
6442
  }
6274
6443
  throw err;
6275
6444
  } finally {
6445
+ removeIfEmpty(tmpDir);
6276
6446
  releaseLock();
6277
6447
  }
6278
6448
  }
@@ -6281,5 +6451,5 @@ async function generateSnapshotAsync(configInput, options) {
6281
6451
  }
6282
6452
 
6283
6453
  export { ARTIFACT_NAMES, DEFAULT_ANSWERS, DEFAULT_SECRET_PATTERNS, GENERATOR_NAME, GENERATOR_VERSION, LANGUAGE_PROFILES, MIN_MAX_PART_BYTES, PRESETS, PromptCancelled, SCHEMA_VERSION, anchorForPath, assertReplaceable, assertUsableConfigInput, collectFiles, compareManifests, countLines, defineConfig, detectLanguage, detectPreset, explainPath, fenceForContent, fenceForText, generateProjectTree, generateSnapshot, generateSnapshotAsync, getPreset, globToRegExp, isBinaryBuffer, isBinaryExtension, isBinaryFile, isInteractive, isResolvedConfig, isSensitivePath, listPresetNames, loadConfigFile, loadConfigFileDetailed, loadManifest, markResolvedConfig, matchRule, matchesExclude, normalizeResolvedConfig, parseByteSize, partitionFiles, profileForPath, renderConfig, renderDiffText, renderExplainText, renderMinimalConfig, renumberParts, resolveConfig, resolveSafeOutputDirs, runInit, scanAndRedactSecrets, stripJsonComments, summarizeJson, summarizeMarkdown, summarizeTypeScriptSource, summarizeWithProfile, supportsTypeScriptImports, truncatePreview, truncateToBytes, unknownPresetMessage, validateSnapshot };
6284
- //# sourceMappingURL=chunk-FM4OOQPF.js.map
6285
- //# sourceMappingURL=chunk-FM4OOQPF.js.map
6454
+ //# sourceMappingURL=chunk-RGIMMEAW.js.map
6455
+ //# sourceMappingURL=chunk-RGIMMEAW.js.map