claude-nomad 0.64.0 → 0.65.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/nomad.mjs CHANGED
@@ -42,13 +42,42 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
42
42
  ));
43
43
 
44
44
  // src/config.never-sync.ts
45
+ function stripTrailingDotsAndWhitespace(name) {
46
+ let end = name.length;
47
+ while (end > 0 && (name[end - 1] === "." || /\s/.test(name[end - 1]))) end -= 1;
48
+ return name.slice(0, end);
49
+ }
45
50
  function isSecretFileName(name) {
46
- return SECRET_FILE_PATTERNS.some((re) => re.test(name));
51
+ const stripped = stripTrailingDotsAndWhitespace(name);
52
+ return SECRET_FILE_PATTERNS.some((re) => re.test(stripped));
47
53
  }
48
54
  function isDeniedName(blockSet, name) {
49
- return blockSet.has(name) || blockSet.has(name.toLowerCase()) || isSecretFileName(name);
55
+ const stripped = stripTrailingDotsAndWhitespace(name);
56
+ return blockSet.has(name) || blockSet.has(name.toLowerCase()) || blockSet.has(stripped) || blockSet.has(stripped.toLowerCase()) || isSecretFileName(name);
57
+ }
58
+ function isClaudeExtraName(name) {
59
+ return stripTrailingDotsAndWhitespace(name).toLowerCase() === ".claude";
60
+ }
61
+ function isExtrasScoped(segments) {
62
+ return segments[0] === "shared" && segments[1] === "extras";
63
+ }
64
+ function blockSetFor(segments) {
65
+ if (!isExtrasScoped(segments)) return NEVER_SYNC;
66
+ return isClaudeExtraName(segments[3] ?? "") ? CLAUDE_EXTRA_NEVER_SYNC : ALWAYS_NEVER_SYNC;
67
+ }
68
+ function deniedSegmentFor(path) {
69
+ const segments = path.split("/");
70
+ const blockSet = blockSetFor(segments);
71
+ const scan = isExtrasScoped(segments) ? segments.slice(4) : segments;
72
+ for (const segment of scan) {
73
+ if (isDeniedName(blockSet, segment)) return segment;
74
+ }
75
+ return null;
76
+ }
77
+ function isNeverSync(path) {
78
+ return deniedSegmentFor(path) !== null;
50
79
  }
51
- var NEVER_SYNC, CLAUDE_EXTRA_NEVER_SYNC, SECRET_FILE_PATTERNS;
80
+ var NEVER_SYNC, CLAUDE_EXTRA_NEVER_SYNC, ALWAYS_NEVER_SYNC, SECRET_FILE_PATTERNS;
52
81
  var init_config_never_sync = __esm({
53
82
  "src/config.never-sync.ts"() {
54
83
  "use strict";
@@ -78,6 +107,13 @@ var init_config_never_sync = __esm({
78
107
  "sessions"
79
108
  ]);
80
109
  CLAUDE_EXTRA_NEVER_SYNC = /* @__PURE__ */ new Set([...NEVER_SYNC, "projects"]);
110
+ ALWAYS_NEVER_SYNC = /* @__PURE__ */ new Set([
111
+ ".claude.json",
112
+ ".credentials.json",
113
+ "settings.local.json",
114
+ "history.jsonl",
115
+ "stats-cache.json"
116
+ ]);
81
117
  SECRET_FILE_PATTERNS = [
82
118
  /^\.env(\..+)?$/i,
83
119
  // .env, .env.local, .env.production
@@ -302,14 +338,67 @@ function assertSafeLogical(logical) {
302
338
  );
303
339
  }
304
340
  }
341
+ function validateSharedDirEntry(entry) {
342
+ if (typeof entry !== "string") {
343
+ return { reason: "not-a-string", message: "not a string" };
344
+ }
345
+ if (!SAFE_SEGMENT.test(entry) || entry === "." || entry === "..") {
346
+ return {
347
+ reason: "not-a-segment",
348
+ message: 'not a single path segment (contains a path separator, an unsupported character, "." or "..")'
349
+ };
350
+ }
351
+ const addressed = stripTrailingDots(entry);
352
+ const named = classifyDeniedName(addressed);
353
+ if (named !== null) {
354
+ return addressed === entry ? named : {
355
+ reason: named.reason,
356
+ message: `a trailing-dot name shadowing ${named.message}`
357
+ };
358
+ }
359
+ if (entry.endsWith(".")) {
360
+ return {
361
+ reason: "win32-alias",
362
+ message: 'a trailing-dot name (git for Windows refuses to create or check out a path ending in ".")'
363
+ };
364
+ }
365
+ return null;
366
+ }
367
+ function stripTrailingDots(name) {
368
+ let end = name.length;
369
+ while (end > 0 && name[end - 1] === ".") end -= 1;
370
+ return name.slice(0, end);
371
+ }
372
+ function classifyDeniedName(name) {
373
+ const folded = name.toLowerCase();
374
+ if (NEVER_SYNC.has(name) || NEVER_SYNC.has(folded)) {
375
+ return { reason: "never-sync", message: "a never-sync name" };
376
+ }
377
+ if (RESERVED_SHARED.has(name) || RESERVED_SHARED_FOLDED.has(folded)) {
378
+ return { reason: "reserved", message: "a reserved shared/ name" };
379
+ }
380
+ const dot = folded.indexOf(".");
381
+ const stem = dot === -1 ? folded : folded.slice(0, dot);
382
+ if (WIN32_DEVICE_NAMES.has(stem)) {
383
+ return { reason: "reserved", message: "a reserved Windows device name" };
384
+ }
385
+ if (isSecretFileName(name)) {
386
+ return {
387
+ reason: "secret-shaped",
388
+ message: "a credential-shaped filename (.env, id_rsa, credentials, *.pem, *.key and similar)"
389
+ };
390
+ }
391
+ return null;
392
+ }
393
+ function mayJoinRefusedEntry(entry, reason, remediable) {
394
+ if (UNJOINABLE_REASONS.has(reason)) return false;
395
+ if (entry.endsWith(".")) return true;
396
+ return remediable.has(reason);
397
+ }
305
398
  function isValidSharedDir(entry) {
306
- if (typeof entry !== "string") return false;
307
- if (!SAFE_SEGMENT.test(entry) || entry === "." || entry === "..") return false;
308
- if (NEVER_SYNC.has(entry)) return false;
309
- if (RESERVED_SHARED.has(entry)) return false;
310
- return true;
399
+ return validateSharedDirEntry(entry) === null;
311
400
  }
312
- var SAFE_LOGICAL, SAFE_SEGMENT, RESERVED_SHARED;
401
+ var SAFE_LOGICAL, SAFE_SEGMENT, RESERVED_SHARED, RESERVED_SHARED_FOLDED, WIN32_DEVICE_NAMES, UNJOINABLE_REASONS;
313
402
  var init_config_sharedDirs_guard = __esm({
314
403
  "src/config.sharedDirs.guard.ts"() {
315
404
  "use strict";
@@ -331,6 +420,19 @@ var init_config_sharedDirs_guard = __esm({
331
420
  "extras",
332
421
  "projects"
333
422
  ]);
423
+ RESERVED_SHARED_FOLDED = new Set([...RESERVED_SHARED].map((name) => name.toLowerCase()));
424
+ WIN32_DEVICE_NAMES = /* @__PURE__ */ new Set([
425
+ "con",
426
+ "prn",
427
+ "aux",
428
+ "nul",
429
+ ...Array.from({ length: 9 }, (_unused, i) => `com${i + 1}`),
430
+ ...Array.from({ length: 9 }, (_unused, i) => `lpt${i + 1}`)
431
+ ]);
432
+ UNJOINABLE_REASONS = /* @__PURE__ */ new Set([
433
+ "not-a-string",
434
+ "not-a-segment"
435
+ ]);
334
436
  }
335
437
  });
336
438
 
@@ -541,20 +643,29 @@ function manifestPath() {
541
643
  function sharedBaselinePath() {
542
644
  return join(home(), ".cache", "claude-nomad", `shared-baseline-${encodeURIComponent(HOST)}.json`);
543
645
  }
544
- function allSharedLinks(map) {
646
+ function allSharedLinks(map, opts = {}) {
647
+ const emit = opts.quiet === true ? () => void 0 : warn;
648
+ const raw = map.sharedDirs;
649
+ if (raw !== void 0 && !Array.isArray(raw)) {
650
+ emit("sharedDirs in path-map.json is not an array; ignoring the whole field");
651
+ return [...SHARED_LINKS];
652
+ }
545
653
  const extras = [];
546
- for (const entry of map.sharedDirs ?? []) {
547
- if (isValidSharedDir(entry)) {
654
+ for (const entry of raw ?? []) {
655
+ const rejection = validateSharedDirEntry(entry);
656
+ if (rejection === null) {
548
657
  extras.push(entry);
549
658
  } else {
550
- warn(
551
- `sharedDirs entry ${JSON.stringify(entry)} is invalid (path separator, reserved name, or NEVER_SYNC); skipping`
552
- );
659
+ emit(`sharedDirs entry ${JSON.stringify(entry)} rejected: ${rejection.message}; skipping`);
553
660
  }
554
661
  }
555
662
  return [...SHARED_LINKS, ...extras];
556
663
  }
557
- var SETTINGS_SCHEMA_URL, NPM_REGISTRY_LATEST_URL, GITLEAKS_PINNED_VERSION, GITLEAKS_SCAN_TIMEOUT_MS, HOST, SHARED_LINKS, GSD_PREFIX, GSD_DROPPED_NAMES, SUPPORTED_EXTRAS, ALWAYS_NEVER_SYNC, PUSH_ALLOWED_STATIC;
664
+ function sharedDirEntries(map) {
665
+ const raw = map.sharedDirs;
666
+ return Array.isArray(raw) ? raw : [];
667
+ }
668
+ var SETTINGS_SCHEMA_URL, NPM_REGISTRY_LATEST_URL, GITLEAKS_PINNED_VERSION, GITLEAKS_SCAN_TIMEOUT_MS, HOST, SHARED_LINKS, GSD_PREFIX, GSD_DROPPED_NAMES, SUPPORTED_EXTRAS, PUSH_ALLOWED_STATIC;
558
669
  var init_config = __esm({
559
670
  "src/config.ts"() {
560
671
  "use strict";
@@ -571,13 +682,6 @@ var init_config = __esm({
571
682
  GSD_PREFIX = "gsd-";
572
683
  GSD_DROPPED_NAMES = ["hooks", "agents"];
573
684
  SUPPORTED_EXTRAS = [".planning", "CLAUDE.md", ".claude"];
574
- ALWAYS_NEVER_SYNC = /* @__PURE__ */ new Set([
575
- ".claude.json",
576
- ".credentials.json",
577
- "settings.local.json",
578
- "history.jsonl",
579
- "stats-cache.json"
580
- ]);
581
685
  PUSH_ALLOWED_STATIC = [
582
686
  "shared/CLAUDE.md",
583
687
  "shared/my-statusline.cjs",
@@ -809,13 +913,20 @@ function probeUnmergedIndex(repo) {
809
913
  }
810
914
  return raw.split("\0").some(Boolean) ? "unmerged" : "clean";
811
915
  }
812
- function unmergedIndexPresent(repo) {
813
- return probeUnmergedIndex(repo) === "unmerged";
916
+ function classifyWedgeWithProbe(repo) {
917
+ const mode = detectWedge(repo);
918
+ if (mode !== null) return { state: mode, probe: "clean" };
919
+ const probe = probeUnmergedIndex(repo);
920
+ return { state: probe === "unmerged" ? "unmerged-index" : null, probe };
814
921
  }
815
922
  function classifyWedge(repo) {
816
- const mode = detectWedge(repo);
817
- if (mode !== null) return mode;
818
- return unmergedIndexPresent(repo) ? "unmerged-index" : null;
923
+ return classifyWedgeWithProbe(repo).state;
924
+ }
925
+ function cleanRepoForceRemoteMessage(probe) {
926
+ if (probe === "error") {
927
+ return "could not determine whether the repo is wedged; continuing with a normal pull";
928
+ }
929
+ return "repo is clean, nothing to recover; continuing with a normal pull";
819
930
  }
820
931
  function unmergedIndexRunbookText(resumeCmd2) {
821
932
  return `repo has an unmerged index with no active rebase or merge in progress (torn-down rebase or merge left stage-2/3 entries behind).
@@ -1422,6 +1533,7 @@ var init_push_leak_verdict = __esm({
1422
1533
  // src/commands.adopt.ts
1423
1534
  init_config();
1424
1535
  init_config_sharedDirs_guard();
1536
+ init_exit_codes();
1425
1537
  import { cpSync as cpSync4, existsSync as existsSync5, lstatSync as lstatSync5, rmSync as rmSync4 } from "node:fs";
1426
1538
  import { join as join6 } from "node:path";
1427
1539
 
@@ -1892,7 +2004,7 @@ function copyExtrasFileSkipDiverged(src, dst) {
1892
2004
  copyExtras(src, dst);
1893
2005
  }
1894
2006
  function extrasDenySet(dirname14) {
1895
- return dirname14 === ".claude" ? CLAUDE_EXTRA_NEVER_SYNC : ALWAYS_NEVER_SYNC;
2007
+ return isClaudeExtraName(dirname14) ? CLAUDE_EXTRA_NEVER_SYNC : ALWAYS_NEVER_SYNC;
1896
2008
  }
1897
2009
  function copyExtrasFiltered(src, dst, blockSet) {
1898
2010
  rmSync2(dst, { recursive: true, force: true });
@@ -2004,7 +2116,7 @@ function applySharedLinks(ts, map, opts = {}) {
2004
2116
  const dryRun = opts.dryRun === true;
2005
2117
  const claude = claudeHome();
2006
2118
  const repo = repoHome();
2007
- const linkNames = allSharedLinks(map);
2119
+ const linkNames = allSharedLinks(map, { quiet: opts.quietNames === true });
2008
2120
  if (process.platform === "win32") {
2009
2121
  applySharedLinksWin32(linkNames, claude, repo, ts, dryRun, opts.onPreview);
2010
2122
  return;
@@ -2022,35 +2134,6 @@ function applySharedLinks(ts, map, opts = {}) {
2022
2134
  ensureSymlink(linkPath, target);
2023
2135
  }
2024
2136
  }
2025
- function syncSharedLinksPush(map) {
2026
- mirrorSharedNames(map, { adoptNew: true, overlay: false });
2027
- }
2028
- function stageLocalSharedEdits(map, ts) {
2029
- mirrorSharedNames(map, { adoptNew: false, overlay: true, backupTs: ts });
2030
- }
2031
- function mirrorOneSharedName(name, claude, repo, policy) {
2032
- const localPath = join5(claude, name);
2033
- const stat = lstatSync4(localPath, { throwIfNoEntry: false });
2034
- if (stat === void 0) return;
2035
- if (stat.isSymbolicLink()) return;
2036
- const target = join5(repo, "shared", name);
2037
- if (!policy.adoptNew && !existsSync4(target)) return;
2038
- if (policy.backupTs !== void 0) backupRepoWrite(target, policy.backupTs, repo);
2039
- if (policy.overlay && stat.isDirectory()) {
2040
- copyExtrasOverlayFiltered(localPath, target, ALWAYS_NEVER_SYNC);
2041
- } else {
2042
- copyExtrasFiltered(localPath, target, ALWAYS_NEVER_SYNC);
2043
- }
2044
- }
2045
- function mirrorSharedNames(map, policy) {
2046
- if (process.platform !== "win32") return;
2047
- if (map === null) return;
2048
- const claude = claudeHome();
2049
- const repo = repoHome();
2050
- for (const name of allSharedLinks(map)) {
2051
- mirrorOneSharedName(name, claude, repo, policy);
2052
- }
2053
- }
2054
2137
  function readExistingSettings(settingsPath) {
2055
2138
  if (!existsSync4(settingsPath)) return { existing: {}, present: false, malformed: false };
2056
2139
  try {
@@ -2133,7 +2216,7 @@ function readMapIfPresent(repoHome2) {
2133
2216
  return existsSync5(mapPath) ? readPathMap(mapPath) : { projects: {} };
2134
2217
  }
2135
2218
  function isConfiguredTarget(name, map) {
2136
- return SHARED_LINKS.includes(name) || (map.sharedDirs?.includes(name) ?? false);
2219
+ return SHARED_LINKS.includes(name) || sharedDirEntries(map).includes(name);
2137
2220
  }
2138
2221
  function isValidAdoptName(name) {
2139
2222
  if (SHARED_LINKS.includes(name)) return true;
@@ -2155,6 +2238,13 @@ function performAdoptMove(name, linkPath, sharedTarget, repo, backup) {
2155
2238
  }
2156
2239
  function cmdAdopt(name, opts = {}) {
2157
2240
  const dryRun = opts.dryRun === true;
2241
+ const rejection = validateSharedDirEntry(name);
2242
+ if (rejection !== null && rejection.reason === "secret-shaped") {
2243
+ throw new NomadFatal(
2244
+ `cannot adopt ${JSON.stringify(name)}: ${rejection.message}. If it is listed in sharedDirs in path-map.json, remove it there too.`,
2245
+ { code: EXIT.GENERIC_FAILURE }
2246
+ );
2247
+ }
2158
2248
  if (!isValidAdoptName(name)) {
2159
2249
  fail(`invalid name: ${JSON.stringify(name)}`);
2160
2250
  process.exit(1);
@@ -2568,6 +2658,7 @@ function cmdClean(opts, backupBase2 = backupBase()) {
2568
2658
 
2569
2659
  // src/commands.eject.ts
2570
2660
  init_config();
2661
+ init_config_sharedDirs_guard();
2571
2662
  init_utils();
2572
2663
  init_utils_fs();
2573
2664
  init_utils_json();
@@ -2705,6 +2796,19 @@ function materializeOneOrDie(name, linkPath, sharedRoot, done) {
2705
2796
  );
2706
2797
  }
2707
2798
  }
2799
+ var WIDENED_REASONS = /* @__PURE__ */ new Set([
2800
+ "never-sync",
2801
+ "reserved",
2802
+ "secret-shaped"
2803
+ ]);
2804
+ function ejectNames(map) {
2805
+ const alreadyMaterialized = sharedDirEntries(map).filter((entry) => {
2806
+ if (typeof entry !== "string") return false;
2807
+ const rejection = validateSharedDirEntry(entry);
2808
+ return rejection === null || mayJoinRefusedEntry(entry, rejection.reason, WIDENED_REASONS);
2809
+ });
2810
+ return [.../* @__PURE__ */ new Set([...allSharedLinks(map), ...alreadyMaterialized])];
2811
+ }
2708
2812
  function defaultEjectRoots() {
2709
2813
  return { claudeHome: claudeHome(), repoHome: repoHome() };
2710
2814
  }
@@ -2712,16 +2816,31 @@ function cmdEject(opts = {}, roots = defaultEjectRoots()) {
2712
2816
  const dryRun = opts.dryRun === true;
2713
2817
  const { claudeHome: claudeHome2, repoHome: repoHome2 } = roots;
2714
2818
  const map = readMapIfPresent2(repoHome2);
2715
- const names = allSharedLinks(map);
2819
+ const names = ejectNames(map);
2716
2820
  const classifications = /* @__PURE__ */ new Map();
2717
2821
  for (const name of names) {
2718
2822
  classifications.set(name, classifyName(join11(claudeHome2, name)));
2719
2823
  }
2824
+ const base = new Set(allSharedLinks(map, { quiet: true }));
2825
+ for (const name of names) {
2826
+ if (base.has(name)) continue;
2827
+ if (classifications.get(name) !== "materialize") continue;
2828
+ item(`processing rejected entry already present on this host: ${name}`);
2829
+ }
2720
2830
  const dangling = names.filter((n) => classifications.get(n) === "dangling");
2721
- if (dangling.length > 0) {
2831
+ const danglingBase = dangling.filter((n) => base.has(n));
2832
+ const danglingRefused = dangling.filter((n) => !base.has(n));
2833
+ if (danglingBase.length > 0) {
2722
2834
  fail(
2723
- `dangling symlink(s): ${dangling.join(", ")}. run \`nomad pull\` first to restore the missing target, then re-run \`nomad eject\``
2835
+ `dangling symlink(s): ${danglingBase.join(", ")}. run \`nomad pull\` first to restore the missing target, then re-run \`nomad eject\``
2724
2836
  );
2837
+ }
2838
+ if (danglingRefused.length > 0) {
2839
+ fail(
2840
+ `dangling symlink(s) for a refused name nomad cannot restore: ${danglingRefused.join(", ")}. recover the content by hand if you need it, then remove the dead link and re-run \`nomad eject\``
2841
+ );
2842
+ }
2843
+ if (dangling.length > 0) {
2725
2844
  process.exit(1);
2726
2845
  }
2727
2846
  const sharedRoot = resolveSharedRoot(repoHome2);
@@ -2745,7 +2864,7 @@ import { join as join45 } from "node:path";
2745
2864
  init_color();
2746
2865
  init_config();
2747
2866
  import { existsSync as existsSync11, lstatSync as lstatSync8, readdirSync as readdirSync4, statSync as statSync3 } from "node:fs";
2748
- import { basename as basename3, join as join13 } from "node:path";
2867
+ import { join as join13, win32 as win32Path } from "node:path";
2749
2868
 
2750
2869
  // src/commands.doctor.format.ts
2751
2870
  init_color();
@@ -2999,19 +3118,30 @@ function reportRepoState(section2) {
2999
3118
  function repoHasSharedSource(name) {
3000
3119
  return existsSync11(join13(repoHome(), "shared", name));
3001
3120
  }
3002
- function win32CopyOkRow(name) {
3003
- return { line: `${green(okGlyph)} ${name}: real copy (win32 copy-sync)`, fail: false };
3121
+ function win32CopyOkRow(name, exempt = 0) {
3122
+ const note = exempt > 0 ? dim(` (${exempt} never-synced path(s) not compared)`) : "";
3123
+ return { line: `${green(okGlyph)} ${name}: real copy (win32 copy-sync)${note}`, fail: false };
3124
+ }
3125
+ function relativeUnder(root, path) {
3126
+ const rel = win32Path.relative(root, path);
3127
+ const segments = rel.split(win32Path.sep);
3128
+ if (rel === "" || segments[0] === ".." || win32Path.isAbsolute(rel)) return null;
3129
+ return segments.join("/");
3004
3130
  }
3005
- function divergingBasename(line) {
3006
- return basename3(line.replace(/ \((?:local|repo) only\)$/, ""));
3131
+ function divergingRepoPath(line, name, local, shared) {
3132
+ const path = line.replace(/ \((?:local|repo) only\)$/, "");
3133
+ const rel = relativeUnder(local, path) ?? relativeUnder(shared, path);
3134
+ return rel === null ? `shared/${name}` : `shared/${name}/${rel}`;
3007
3135
  }
3008
3136
  function classifyWin32Copy(name, p) {
3009
3137
  const sharedPath = join13(repoHome(), "shared", name);
3010
3138
  if (!existsSync11(sharedPath)) return win32CopyOkRow(name);
3011
- const diverging = listDivergingFiles(p, sharedPath).filter(
3012
- (line) => !isDeniedName(ALWAYS_NEVER_SYNC, divergingBasename(line))
3139
+ const compared = listDivergingFiles(p, sharedPath);
3140
+ const diverging = compared.filter(
3141
+ (line) => deniedSegmentFor(divergingRepoPath(line, name, p, sharedPath)) === null
3013
3142
  );
3014
- if (diverging.length === 0) return win32CopyOkRow(name);
3143
+ const exempt = compared.length - diverging.length;
3144
+ if (diverging.length === 0) return win32CopyOkRow(name, exempt);
3015
3145
  return {
3016
3146
  line: `${yellow(warnGlyph)} ${name}: ${diverging.length} file(s) diverge from shared/${name}`,
3017
3147
  fail: false,
@@ -3066,7 +3196,7 @@ function classifySymlinkTarget(name, p) {
3066
3196
  }
3067
3197
  function reportSharedLinks(section2, map) {
3068
3198
  const claude = claudeHome();
3069
- for (const name of allSharedLinks(map)) {
3199
+ for (const name of allSharedLinks(map, { quiet: true })) {
3070
3200
  const p = join13(claude, name);
3071
3201
  const { line, fail: fail2, children } = classifySharedLink(name, p);
3072
3202
  addItem(section2, line);
@@ -3158,8 +3288,9 @@ function reportHostOverrides(section2, base, settings) {
3158
3288
  // src/commands.doctor.checks.pathmap.ts
3159
3289
  init_color();
3160
3290
  init_config();
3161
- import { existsSync as existsSync13, readdirSync as readdirSync6 } from "node:fs";
3162
- import { join as join15 } from "node:path";
3291
+ init_config_sharedDirs_guard();
3292
+ import { existsSync as existsSync13, lstatSync as lstatSync9, readdirSync as readdirSync6, realpathSync as realpathSync2 } from "node:fs";
3293
+ import { join as join15, sep as sep4 } from "node:path";
3163
3294
  init_utils_json();
3164
3295
  function reportMappedProjects(section2, map) {
3165
3296
  const mapped = Object.entries(map.projects).filter(([, hosts]) => hosts[HOST]);
@@ -3199,6 +3330,95 @@ function reportCurrentHostPathsMissing(section2, map) {
3199
3330
  }
3200
3331
  }
3201
3332
  }
3333
+ function hasSharedLeftover(entry) {
3334
+ try {
3335
+ return lstatSync9(join15(repoHome(), "shared", entry), { throwIfNoEntry: false }) !== void 0;
3336
+ } catch {
3337
+ return false;
3338
+ }
3339
+ }
3340
+ var PROBEABLE_REASONS = /* @__PURE__ */ new Set([
3341
+ "never-sync",
3342
+ "secret-shaped"
3343
+ ]);
3344
+ function classifyLocalLink(entry) {
3345
+ const linkPath = join15(claudeHome(), entry);
3346
+ let stat;
3347
+ try {
3348
+ stat = lstatSync9(linkPath, { throwIfNoEntry: false });
3349
+ } catch {
3350
+ return "absent";
3351
+ }
3352
+ if (stat?.isSymbolicLink() !== true) return "absent";
3353
+ let target;
3354
+ try {
3355
+ target = realpathSync2(linkPath);
3356
+ } catch {
3357
+ return "dangling";
3358
+ }
3359
+ let root;
3360
+ try {
3361
+ root = realpathSync2(join15(repoHome(), "shared"));
3362
+ } catch {
3363
+ return "foreign";
3364
+ }
3365
+ return target.startsWith(root + sep4) ? "managed" : "foreign";
3366
+ }
3367
+ function reportRejectedSharedDirs(section2, map) {
3368
+ const raw = map.sharedDirs;
3369
+ if (raw !== void 0 && !Array.isArray(raw)) {
3370
+ addItem(
3371
+ section2,
3372
+ `${yellow(warnGlyph)} path-map: sharedDirs is not an array (${typeof raw}); the whole field is ignored`
3373
+ );
3374
+ return;
3375
+ }
3376
+ const entries = Array.isArray(raw) ? raw : [];
3377
+ const probable = [];
3378
+ for (const entry of entries) {
3379
+ const rejection = validateSharedDirEntry(entry);
3380
+ if (rejection === null) continue;
3381
+ if (typeof entry === "string" && mayJoinRefusedEntry(entry, rejection.reason, PROBEABLE_REASONS)) {
3382
+ probable.push(entry);
3383
+ }
3384
+ addItem(
3385
+ section2,
3386
+ `${yellow(warnGlyph)} path-map: sharedDirs entry ${JSON.stringify(entry)} rejected: ${rejection.message}; skipping`
3387
+ );
3388
+ }
3389
+ reportRejectedLeftovers(section2, probable);
3390
+ }
3391
+ function reportRejectedLeftovers(section2, probable) {
3392
+ for (const entry of probable) {
3393
+ const link = classifyLocalLink(entry);
3394
+ if (link === "managed") {
3395
+ addItem(
3396
+ section2,
3397
+ `${yellow(warnGlyph)} path-map: entry ${JSON.stringify(entry)} is a symlink under ~/.claude/ pointing into shared/; copy the content out first (cp -RL), then remove both. nomad will not do it for you`
3398
+ );
3399
+ continue;
3400
+ }
3401
+ if (link === "dangling") {
3402
+ addItem(
3403
+ section2,
3404
+ `${yellow(warnGlyph)} path-map: entry ${JSON.stringify(entry)} is a DANGLING symlink under ~/.claude/ (its target does not resolve); do not delete anything under shared/ yet, since a copy there may be the only one left. nomad cannot restore this name, because the entry is refused: recover the content by hand if you need it, then remove the dead link`
3405
+ );
3406
+ continue;
3407
+ }
3408
+ if (link === "foreign") {
3409
+ addItem(
3410
+ section2,
3411
+ `${yellow(warnGlyph)} path-map: entry ${JSON.stringify(entry)} is a symlink under ~/.claude/ pointing OUTSIDE shared/; nomad does not manage it, so leave it alone and just drop the entry from sharedDirs`
3412
+ );
3413
+ }
3414
+ if (hasSharedLeftover(entry)) {
3415
+ addItem(
3416
+ section2,
3417
+ `${yellow(warnGlyph)} path-map: shared/ entry ${JSON.stringify(entry)} exists in the repo working tree; remove it by hand, nomad will not delete it`
3418
+ );
3419
+ }
3420
+ }
3421
+ }
3202
3422
  function reportPathCollisions(section2, map) {
3203
3423
  const seen = /* @__PURE__ */ new Map();
3204
3424
  let collisionCount = 0;
@@ -3230,6 +3450,7 @@ function reportPathMap(section2) {
3230
3450
  }
3231
3451
  const map = readJsonSafe(mapPath, mapPath, section2);
3232
3452
  if (map === null) return;
3453
+ reportRejectedSharedDirs(section2, map);
3233
3454
  const shapeError = validatePathMapShape(map);
3234
3455
  if (shapeError !== null) {
3235
3456
  addItem(section2, `${red(failGlyph)} ${shapeError}`);
@@ -3425,7 +3646,7 @@ function reportGitIdentity(section2) {
3425
3646
 
3426
3647
  // src/commands.doctor.checks.backups.ts
3427
3648
  init_color();
3428
- import { existsSync as existsSync18, lstatSync as lstatSync9, readdirSync as readdirSync8 } from "node:fs";
3649
+ import { existsSync as existsSync18, lstatSync as lstatSync10, readdirSync as readdirSync8 } from "node:fs";
3429
3650
  import { join as join21 } from "node:path";
3430
3651
  init_config();
3431
3652
  var TS_SHAPE2 = /^\d{8}-\d{6}(-\d+)?$/;
@@ -3443,7 +3664,7 @@ function dirSizeBytes(dir) {
3443
3664
  let bytes = 0;
3444
3665
  for (const entry of safeReaddir(dir)) {
3445
3666
  const full = join21(dir, entry);
3446
- const st = lstatSync9(full, { throwIfNoEntry: false });
3667
+ const st = lstatSync10(full, { throwIfNoEntry: false });
3447
3668
  if (!st) continue;
3448
3669
  if (st.isSymbolicLink()) continue;
3449
3670
  if (st.isDirectory()) bytes += dirSizeBytes(full);
@@ -3922,8 +4143,8 @@ init_config();
3922
4143
 
3923
4144
  // src/remap.ts
3924
4145
  init_config_sharedDirs_guard();
3925
- import { cpSync as cpSync6, existsSync as existsSync21, lstatSync as lstatSync10, mkdirSync as mkdirSync7, readdirSync as readdirSync10, rmSync as rmSync11, statSync as statSync5 } from "node:fs";
3926
- import { dirname as dirname6, join as join28, relative as relative5, sep as sep4 } from "node:path";
4146
+ import { cpSync as cpSync6, existsSync as existsSync21, lstatSync as lstatSync11, mkdirSync as mkdirSync7, readdirSync as readdirSync10, rmSync as rmSync11, statSync as statSync5 } from "node:fs";
4147
+ import { dirname as dirname6, join as join28, relative as relative5, sep as sep5 } from "node:path";
3927
4148
  init_config();
3928
4149
 
3929
4150
  // src/push-manifest.ts
@@ -4055,7 +4276,7 @@ function copyFileAtomic(src, dst) {
4055
4276
  renameAtomicRetry(tmp, dst);
4056
4277
  }
4057
4278
  function hasDeltaForDir(sel, localDir) {
4058
- const prefix = `${localDir}${sep4}`;
4279
+ const prefix = `${localDir}${sep5}`;
4059
4280
  for (const p of sel.changed) if (p.startsWith(prefix)) return true;
4060
4281
  for (const p of sel.deleted) if (p.startsWith(prefix)) return true;
4061
4282
  return false;
@@ -4064,7 +4285,7 @@ function skipForSelection(sel, localDir) {
4064
4285
  return sel !== void 0 && !hasDeltaForDir(sel, localDir);
4065
4286
  }
4066
4287
  function applySelective(sel, localDir, repoDst) {
4067
- const prefix = `${localDir}${sep4}`;
4288
+ const prefix = `${localDir}${sep5}`;
4068
4289
  for (const src of sel.changed) {
4069
4290
  if (!src.startsWith(prefix)) continue;
4070
4291
  copyFileAtomic(src, join28(repoDst, relative5(localDir, src)));
@@ -4082,7 +4303,7 @@ function copyDirJsonlOnly(src, dst) {
4082
4303
  filter: (srcPath) => {
4083
4304
  const rel = relative5(src, srcPath);
4084
4305
  if (rel === "") return true;
4085
- if (rel.split(sep4).length > 1) return true;
4306
+ if (rel.split(sep5).length > 1) return true;
4086
4307
  if (statSync5(srcPath).isDirectory()) return true;
4087
4308
  if (srcPath.endsWith(".jsonl")) return true;
4088
4309
  item(`skip ${rel}: extension not in allowlist`);
@@ -4142,9 +4363,9 @@ function countLocalOnly(src, dst) {
4142
4363
  for (const name of readdirSync10(dst)) {
4143
4364
  const dstPath = join28(dst, name);
4144
4365
  const srcPath = join28(src, name);
4145
- if (lstatSync10(dstPath).isDirectory()) {
4366
+ if (lstatSync11(dstPath).isDirectory()) {
4146
4367
  count += countLocalOnly(srcPath, dstPath);
4147
- } else if (lstatSync10(srcPath, { throwIfNoEntry: false }) === void 0) {
4368
+ } else if (lstatSync11(srcPath, { throwIfNoEntry: false }) === void 0) {
4148
4369
  count++;
4149
4370
  }
4150
4371
  }
@@ -4334,7 +4555,7 @@ function reportCheckShared(section2, gitleaksReady) {
4334
4555
 
4335
4556
  // src/commands.doctor.checks.hooks.scope.ts
4336
4557
  init_color();
4337
- import { existsSync as existsSync23, readFileSync as readFileSync8, readdirSync as readdirSync12, realpathSync as realpathSync2 } from "node:fs";
4558
+ import { existsSync as existsSync23, readFileSync as readFileSync8, readdirSync as readdirSync12, realpathSync as realpathSync3 } from "node:fs";
4338
4559
  import { dirname as dirname7, extname, join as join30 } from "node:path";
4339
4560
  init_config();
4340
4561
  function typeFromPackageJson(pkgPath) {
@@ -4351,7 +4572,7 @@ function effectiveType(hookPath) {
4351
4572
  if (ext === ".cjs") return "cjs";
4352
4573
  let real;
4353
4574
  try {
4354
- real = realpathSync2(hookPath);
4575
+ real = realpathSync3(hookPath);
4355
4576
  } catch {
4356
4577
  return null;
4357
4578
  }
@@ -4513,7 +4734,7 @@ import { existsSync as existsSync26, readFileSync as readFileSync9 } from "node:
4513
4734
  import { join as join33 } from "node:path";
4514
4735
 
4515
4736
  // src/commands.doctor.checks.hooks.preserve-symlinks.probe.ts
4516
- import { closeSync as closeSync3, existsSync as existsSync25, openSync as openSync3, readSync, realpathSync as realpathSync3 } from "node:fs";
4737
+ import { closeSync as closeSync3, existsSync as existsSync25, openSync as openSync3, readSync, realpathSync as realpathSync4 } from "node:fs";
4517
4738
  import { dirname as dirname8, join as join32, resolve as resolve2 } from "node:path";
4518
4739
  function suppressedRanges(src) {
4519
4740
  const ranges = [];
@@ -4558,7 +4779,7 @@ function specifierIsMissing(specifier, baseDir) {
4558
4779
  function relativeRequireTargetsBroken(scriptPath) {
4559
4780
  let realPath;
4560
4781
  try {
4561
- realPath = realpathSync3(scriptPath);
4782
+ realPath = realpathSync4(scriptPath);
4562
4783
  } catch {
4563
4784
  return false;
4564
4785
  }
@@ -4702,7 +4923,7 @@ function reportPreserveSymlinksCheck(section2) {
4702
4923
  return;
4703
4924
  }
4704
4925
  const map = readPathMapSafe();
4705
- const sharedLinkNames = allSharedLinks(map);
4926
+ const sharedLinkNames = allSharedLinks(map, { quiet: true });
4706
4927
  let anyWarn = false;
4707
4928
  for (const [event, groups] of Object.entries(hooks)) {
4708
4929
  if (!Array.isArray(groups)) continue;
@@ -5052,21 +5273,21 @@ import { createInterface as createInterface2 } from "node:readline/promises";
5052
5273
  // src/commands.push.recovery.actions.ts
5053
5274
  init_config();
5054
5275
  import { readFileSync as readFileSync17 } from "node:fs";
5055
- import { isAbsolute as isAbsolute2, resolve as resolve3, sep as sep8 } from "node:path";
5276
+ import { isAbsolute as isAbsolute2, resolve as resolve3, sep as sep9 } from "node:path";
5056
5277
 
5057
5278
  // src/commands.push.recovery.memory.ts
5058
5279
  init_config();
5059
5280
  init_config_sharedDirs_guard();
5060
5281
  import {
5061
5282
  cpSync as cpSync7,
5062
- lstatSync as lstatSync11,
5283
+ lstatSync as lstatSync12,
5063
5284
  mkdirSync as mkdirSync9,
5064
5285
  readFileSync as readFileSync14,
5065
- realpathSync as realpathSync4,
5286
+ realpathSync as realpathSync5,
5066
5287
  statSync as statSync6,
5067
5288
  writeFileSync as writeFileSync6
5068
5289
  } from "node:fs";
5069
- import { dirname as dirname9, join as join36, sep as sep5 } from "node:path";
5290
+ import { dirname as dirname9, join as join36, sep as sep6 } from "node:path";
5070
5291
  init_push_gitleaks_scan();
5071
5292
 
5072
5293
  // src/rel-path-guard.ts
@@ -5097,12 +5318,12 @@ function resolveMemoryLocalPath(logical, relPath, map) {
5097
5318
  if (abs === void 0) return null;
5098
5319
  const memoryRoot = join36(claudeHome(), "projects", encodePath(abs), "memory");
5099
5320
  const localPath = join36(memoryRoot, ...relPath.split("/"));
5100
- if (localPath !== memoryRoot && !localPath.startsWith(memoryRoot + sep5)) return null;
5321
+ if (localPath !== memoryRoot && !localPath.startsWith(memoryRoot + sep6)) return null;
5101
5322
  try {
5102
- if (lstatSync11(localPath).isSymbolicLink()) return null;
5103
- const realLocal = realpathSync4(localPath);
5104
- const realRoot = realpathSync4(memoryRoot);
5105
- if (!realLocal.startsWith(realRoot + sep5)) return null;
5323
+ if (lstatSync12(localPath).isSymbolicLink()) return null;
5324
+ const realLocal = realpathSync5(localPath);
5325
+ const realRoot = realpathSync5(memoryRoot);
5326
+ if (!realLocal.startsWith(realRoot + sep6)) return null;
5106
5327
  if (!statSync6(realLocal).isFile()) return null;
5107
5328
  } catch {
5108
5329
  return null;
@@ -5160,28 +5381,28 @@ function applyMemoryRedact(f, ts, map, scan = scanFile) {
5160
5381
  init_config();
5161
5382
  import {
5162
5383
  cpSync as cpSync8,
5163
- lstatSync as lstatSync13,
5384
+ lstatSync as lstatSync14,
5164
5385
  mkdirSync as mkdirSync11,
5165
5386
  readFileSync as readFileSync15,
5166
- realpathSync as realpathSync5,
5387
+ realpathSync as realpathSync6,
5167
5388
  statSync as statSync7,
5168
5389
  writeFileSync as writeFileSync7
5169
5390
  } from "node:fs";
5170
- import { dirname as dirname10, join as join38, sep as sep6 } from "node:path";
5391
+ import { dirname as dirname10, join as join38, sep as sep7 } from "node:path";
5171
5392
 
5172
5393
  // src/skills-sync.ts
5173
5394
  init_config();
5174
- import { existsSync as existsSync29, lstatSync as lstatSync12, mkdirSync as mkdirSync10, readdirSync as readdirSync13, rmSync as rmSync13 } from "node:fs";
5395
+ import { existsSync as existsSync29, lstatSync as lstatSync13, mkdirSync as mkdirSync10, readdirSync as readdirSync13, rmSync as rmSync13 } from "node:fs";
5175
5396
  import { join as join37 } from "node:path";
5176
5397
 
5177
5398
  // src/skills-sync.tracked.ts
5178
5399
  init_utils();
5179
- import { basename as basename4 } from "node:path";
5400
+ import { basename as basename3 } from "node:path";
5180
5401
  function trackedRootSkillsAt(ref, repo) {
5181
5402
  try {
5182
5403
  const raw = gitCaptureRaw(["ls-tree", "--name-only", "-z", ref, "--", "shared/skills/"], repo);
5183
5404
  const entries = raw.split("\0").filter((entry) => entry !== "");
5184
- return new Set(entries.map((entry) => basename4(entry)));
5405
+ return new Set(entries.map((entry) => basename3(entry)));
5185
5406
  } catch {
5186
5407
  return /* @__PURE__ */ new Set();
5187
5408
  }
@@ -5211,7 +5432,7 @@ function syncSkillsPull(ts, prePostHeads) {
5211
5432
  if (!existsSync29(sharedSkills)) return;
5212
5433
  const localSkills = join37(claudeHome(), "skills");
5213
5434
  backupBeforeWrite(localSkills, ts);
5214
- const dstStat = lstatSync12(localSkills, { throwIfNoEntry: false });
5435
+ const dstStat = lstatSync13(localSkills, { throwIfNoEntry: false });
5215
5436
  if (dstStat?.isSymbolicLink() === true) {
5216
5437
  rmSync13(localSkills, { recursive: true, force: true });
5217
5438
  mkdirSync10(localSkills, { recursive: true });
@@ -5221,7 +5442,7 @@ function syncSkillsPull(ts, prePostHeads) {
5221
5442
  }
5222
5443
  function syncSkillsPush() {
5223
5444
  const localSkills = join37(claudeHome(), "skills");
5224
- const stat = lstatSync12(localSkills, { throwIfNoEntry: false });
5445
+ const stat = lstatSync13(localSkills, { throwIfNoEntry: false });
5225
5446
  if (stat === void 0) return;
5226
5447
  if (stat.isSymbolicLink()) return;
5227
5448
  const sharedSkills = join37(repoHome(), "shared", "skills");
@@ -5250,12 +5471,12 @@ function resolveSkillLocalPath(name, relPath) {
5250
5471
  const skillsRoot = join38(claudeHome(), "skills");
5251
5472
  const skillRoot = join38(skillsRoot, name);
5252
5473
  const localPath = join38(skillRoot, ...relPath.split("/"));
5253
- if (localPath !== skillRoot && !localPath.startsWith(skillRoot + sep6)) return null;
5474
+ if (localPath !== skillRoot && !localPath.startsWith(skillRoot + sep7)) return null;
5254
5475
  try {
5255
- if (lstatSync13(localPath).isSymbolicLink()) return null;
5256
- const realLocal = realpathSync5(localPath);
5257
- const realRoot = realpathSync5(skillsRoot);
5258
- if (!realLocal.startsWith(realRoot + sep6)) return null;
5476
+ if (lstatSync14(localPath).isSymbolicLink()) return null;
5477
+ const realLocal = realpathSync6(localPath);
5478
+ const realRoot = realpathSync6(skillsRoot);
5479
+ if (!realLocal.startsWith(realRoot + sep7)) return null;
5259
5480
  if (!statSync7(realLocal).isFile()) return null;
5260
5481
  } catch {
5261
5482
  return null;
@@ -5380,7 +5601,7 @@ function applyDeferredAllows(findings, state, repo) {
5380
5601
  init_config();
5381
5602
  init_config_sharedDirs_guard();
5382
5603
  import { cpSync as cpSync9, existsSync as existsSync32, mkdirSync as mkdirSync12 } from "node:fs";
5383
- import { dirname as dirname12, join as join41, sep as sep7 } from "node:path";
5604
+ import { dirname as dirname12, join as join41, sep as sep8 } from "node:path";
5384
5605
 
5385
5606
  // src/commands.redact.ts
5386
5607
  init_config();
@@ -5388,17 +5609,17 @@ import { existsSync as existsSync31 } from "node:fs";
5388
5609
  import { dirname as dirname11, join as join40 } from "node:path";
5389
5610
 
5390
5611
  // src/commands.redact.subtree.ts
5391
- import { existsSync as existsSync30, lstatSync as lstatSync14, readFileSync as readFileSync16, readdirSync as readdirSync14, statSync as statSync8, writeFileSync as writeFileSync8 } from "node:fs";
5612
+ import { existsSync as existsSync30, lstatSync as lstatSync15, readFileSync as readFileSync16, readdirSync as readdirSync14, statSync as statSync8, writeFileSync as writeFileSync8 } from "node:fs";
5392
5613
  import { join as join39 } from "node:path";
5393
5614
  init_utils_fs();
5394
5615
  init_utils();
5395
5616
  function collectFiles(dir, out) {
5396
5617
  if (!existsSync30(dir)) return;
5397
- const st = lstatSync14(dir);
5618
+ const st = lstatSync15(dir);
5398
5619
  if (!st.isDirectory()) return;
5399
5620
  for (const entry of readdirSync14(dir)) {
5400
5621
  const abs = join39(dir, entry);
5401
- const lst = lstatSync14(abs);
5622
+ const lst = lstatSync15(abs);
5402
5623
  if (lst.isSymbolicLink()) continue;
5403
5624
  if (lst.isDirectory()) {
5404
5625
  collectFiles(abs, out);
@@ -5606,7 +5827,7 @@ function resolveStagedDir(localPath, map, claude, repo) {
5606
5827
  assertSafeLogical(logical);
5607
5828
  const abs = hostMap[HOST];
5608
5829
  if (abs === void 0) continue;
5609
- if (localPath.startsWith(join41(claude, "projects", encodePath(abs)) + sep7)) {
5830
+ if (localPath.startsWith(join41(claude, "projects", encodePath(abs)) + sep8)) {
5610
5831
  return join41(repo, "shared", "projects", logical);
5611
5832
  }
5612
5833
  }
@@ -5897,8 +6118,8 @@ function sessionSuffix(finding) {
5897
6118
  const sid = sessionIdFromFinding(finding);
5898
6119
  if (sid === null) return "";
5899
6120
  const rawBasename = finding.File.replace(/^.*\//, "");
5900
- const basename5 = rawBasename.endsWith(".jsonl") ? rawBasename.slice(0, -".jsonl".length) : rawBasename;
5901
- return basename5 === sid ? "" : ` (session: ${sid})`;
6121
+ const basename4 = rawBasename.endsWith(".jsonl") ? rawBasename.slice(0, -".jsonl".length) : rawBasename;
6122
+ return basename4 === sid ? "" : ` (session: ${sid})`;
5902
6123
  }
5903
6124
  function renderFindingBlock(finding, readLine, siblings = []) {
5904
6125
  const lines = [` file: ${finding.File}:${finding.StartLine}${sessionSuffix(finding)}`];
@@ -5970,7 +6191,7 @@ function makeDefaultReadLine(repo) {
5970
6191
  try {
5971
6192
  const repoRoot = resolve3(repo);
5972
6193
  const target = resolve3(repoRoot, file);
5973
- if (isAbsolute2(file) || target !== repoRoot && !target.startsWith(repoRoot + sep8)) {
6194
+ if (isAbsolute2(file) || target !== repoRoot && !target.startsWith(repoRoot + sep9)) {
5974
6195
  return null;
5975
6196
  }
5976
6197
  let lines = cache.get(target);
@@ -6567,6 +6788,20 @@ function buildVerdictSection(sections) {
6567
6788
  return summary;
6568
6789
  }
6569
6790
 
6791
+ // src/commands.doctor.banner.ts
6792
+ var NOMAD_BANNER = [
6793
+ " __",
6794
+ " ____ ____ ____ ___ ____ _ ____/ /",
6795
+ " / __ \\ / __ \\ / __ `__ \\ / __ `// __ /",
6796
+ " / / / // /_/ // / / / / // /_/ // /_/ /",
6797
+ String.raw`/_/ /_/ \____//_/ /_/ /_/ \__,_/ \__,_/`
6798
+ ];
6799
+ function printBanner(log2, stdout) {
6800
+ if (stdout.isTTY !== true) return;
6801
+ for (const line of NOMAD_BANNER) log2(line);
6802
+ log2("");
6803
+ }
6804
+
6570
6805
  // src/commands.doctor.compact.ts
6571
6806
  init_color();
6572
6807
  var ALWAYS_FULL = /* @__PURE__ */ new Set([
@@ -6585,18 +6820,27 @@ function isRepoStateLine(item2) {
6585
6820
  function isCopySyncModalityLine(item2) {
6586
6821
  return item2.includes("sync modality: copy-sync");
6587
6822
  }
6823
+ function isKeptEnvironmentRow(item2) {
6824
+ return isRepoStateLine(item2) || isCopySyncModalityLine(item2) || isProblem(item2);
6825
+ }
6826
+ function keepWithChildren(items, keep) {
6827
+ const out = [];
6828
+ let parentKept = false;
6829
+ for (const item2 of items) {
6830
+ if (isChild(item2)) {
6831
+ if (parentKept) out.push(item2);
6832
+ continue;
6833
+ }
6834
+ parentKept = keep(item2);
6835
+ if (parentKept) out.push(item2);
6836
+ }
6837
+ return out;
6838
+ }
6588
6839
  function compactSections(sections) {
6589
6840
  return sections.map((s) => {
6590
6841
  if (ALWAYS_FULL.has(s.header)) return s;
6591
- if (s.header === "Environment") {
6592
- return {
6593
- ...s,
6594
- items: s.items.filter(
6595
- (it) => isRepoStateLine(it) || isCopySyncModalityLine(it) || isProblem(it)
6596
- )
6597
- };
6598
- }
6599
- return { ...s, items: s.items.filter(isProblem) };
6842
+ const keep = s.header === "Environment" ? isKeptEnvironmentRow : isProblem;
6843
+ return { ...s, items: keepWithChildren(s.items, keep) };
6600
6844
  });
6601
6845
  }
6602
6846
 
@@ -6673,6 +6917,7 @@ function gatherDoctorSections(opts) {
6673
6917
  return [...body, buildVerdictSection(body)];
6674
6918
  }
6675
6919
  function cmdDoctor(opts = {}) {
6920
+ printBanner(console.log, process.stdout);
6676
6921
  const makeSpinner = opts.startSpinner ?? startSpinner;
6677
6922
  const sp = makeSpinner("Running checks");
6678
6923
  let report;
@@ -6980,11 +7225,11 @@ init_utils_json();
6980
7225
 
6981
7226
  // src/extras-sync.remap.ts
6982
7227
  init_config();
6983
- import { existsSync as existsSync38, mkdirSync as mkdirSync13, readdirSync as readdirSync16, readFileSync as readFileSync19, realpathSync as realpathSync6, rmSync as rmSync16 } from "node:fs";
6984
- import { dirname as dirname13, join as join49, sep as sep10 } from "node:path";
7228
+ import { existsSync as existsSync38, mkdirSync as mkdirSync13, readdirSync as readdirSync16, readFileSync as readFileSync19, realpathSync as realpathSync7, rmSync as rmSync16 } from "node:fs";
7229
+ import { dirname as dirname13, join as join49, sep as sep11 } from "node:path";
6985
7230
 
6986
7231
  // src/extras-sync.planning-diff.ts
6987
- import { join as join48, normalize as normalize2, sep as sep9 } from "node:path";
7232
+ import { join as join48, normalize as normalize2, sep as sep10 } from "node:path";
6988
7233
  init_utils();
6989
7234
  function processRecord(fields, i, changed, deleted) {
6990
7235
  const status = fields[i];
@@ -7037,7 +7282,7 @@ function planningDeleteTargets(opts) {
7037
7282
  const logicalPrefix = "shared/extras/" + logical + "/";
7038
7283
  const prefix = logicalPrefix + ".planning/";
7039
7284
  const planningRoot = join48(localRoot, ".planning");
7040
- const planningRootBoundary = planningRoot + sep9;
7285
+ const planningRootBoundary = planningRoot + sep10;
7041
7286
  const targets = [];
7042
7287
  for (const repoPath of deleted) {
7043
7288
  if (!repoPath.startsWith(prefix)) {
@@ -7079,7 +7324,7 @@ function runExtrasOp(v, dryRun, paths, backup, copy) {
7079
7324
  }
7080
7325
  function pruneEmptyAncestors(target, planningRoot) {
7081
7326
  let dir = dirname13(target);
7082
- while (dir !== planningRoot && dir.startsWith(planningRoot + sep10)) {
7327
+ while (dir !== planningRoot && dir.startsWith(planningRoot + sep11)) {
7083
7328
  try {
7084
7329
  if (readdirSync16(dir).length > 0) break;
7085
7330
  rmSync16(dir, { recursive: true, force: true });
@@ -7091,13 +7336,13 @@ function pruneEmptyAncestors(target, planningRoot) {
7091
7336
  }
7092
7337
  function tryRealpath(dir) {
7093
7338
  try {
7094
- return realpathSync6(dir);
7339
+ return realpathSync7(dir);
7095
7340
  } catch {
7096
7341
  return void 0;
7097
7342
  }
7098
7343
  }
7099
7344
  function isInsidePlanningRoot(parentReal, rootReal) {
7100
- return parentReal === rootReal || parentReal.startsWith(rootReal + sep10);
7345
+ return parentReal === rootReal || parentReal.startsWith(rootReal + sep11);
7101
7346
  }
7102
7347
  function deletePlanningTarget(target, planningRoot, repoCounterpart) {
7103
7348
  if (existsSync38(repoCounterpart)) return;
@@ -7124,11 +7369,11 @@ function planningDiffArgs(pre, post, logical) {
7124
7369
  function deletePairsFor(t, raw) {
7125
7370
  return planningDeleteTargets({ raw, logical: t.logical, localRoot: t.localRoot }).map(
7126
7371
  (target) => {
7127
- const relToLocal = target.slice(t.localRoot.length + sep10.length);
7372
+ const relToLocal = target.slice(t.localRoot.length + sep11.length);
7128
7373
  return {
7129
7374
  target,
7130
7375
  relToLocal,
7131
- repoRel: `shared/extras/${t.logical}/${relToLocal.split(sep10).join("/")}`
7376
+ repoRel: `shared/extras/${t.logical}/${relToLocal.split(sep11).join("/")}`
7132
7377
  };
7133
7378
  }
7134
7379
  );
@@ -7202,7 +7447,8 @@ function remapExtrasPush(ts, opts = {}) {
7202
7447
  // The filter prevents ALWAYS_NEVER_SYNC files from landing in the repo
7203
7448
  // working tree before the allow-list gate fires, eliminating the
7204
7449
  // "residue wedges repeat push" regression. The allow-list gate
7205
- // (enforceAllowList / blockSetFor in commands.push.allowlist.ts)
7450
+ // (enforceAllowList in commands.push.allowlist.ts, blockSetFor in
7451
+ // config.never-sync.ts)
7206
7452
  // remains the hard security boundary.
7207
7453
  // All others: copyExtrasFiltered with per-extra denylist.
7208
7454
  (src, dst, dirname14) => dirname14 === ".planning" ? copyExtrasOverlayFiltered(src, dst, extrasDenySet(dirname14)) : copyExtrasFiltered(src, dst, extrasDenySet(dirname14))
@@ -7302,8 +7548,8 @@ function divergenceCheckExtras(ts, prePostHeads) {
7302
7548
 
7303
7549
  // src/links.baseline.ts
7304
7550
  init_config();
7305
- import { lstatSync as lstatSync15, readdirSync as readdirSync17 } from "node:fs";
7306
- import { join as join51, relative as relative7, sep as sep11 } from "node:path";
7551
+ import { lstatSync as lstatSync16, readdirSync as readdirSync17 } from "node:fs";
7552
+ import { join as join51, relative as relative7, sep as sep12 } from "node:path";
7307
7553
  init_utils();
7308
7554
  var SHARED_BASELINE_KIND = "shared-links-baseline/1";
7309
7555
  var SHARED_BASELINE_CONFIG_HASH = "not-applicable";
@@ -7314,13 +7560,13 @@ function readSharedBaseline() {
7314
7560
  return parsed;
7315
7561
  }
7316
7562
  function baselineKey(claude, abs) {
7317
- return relative7(claude, abs).split(sep11).join("/");
7563
+ return relative7(claude, abs).split(sep12).join("/");
7318
7564
  }
7319
7565
  function addLocalPath(abs, claude, scan) {
7320
7566
  const key = baselineKey(claude, abs);
7321
7567
  let st;
7322
7568
  try {
7323
- st = lstatSync15(abs, { throwIfNoEntry: false });
7569
+ st = lstatSync16(abs, { throwIfNoEntry: false });
7324
7570
  } catch {
7325
7571
  scan.declined.push(key);
7326
7572
  return;
@@ -7353,10 +7599,10 @@ function collectSharedFiles(dir, claude, scan) {
7353
7599
  addLocalPath(abs, claude, scan);
7354
7600
  }
7355
7601
  }
7356
- function enumerateLocalSharedScan(map) {
7602
+ function enumerateLocalSharedScan(map, opts = {}) {
7357
7603
  const claude = claudeHome();
7358
7604
  const scan = { files: {}, declined: [] };
7359
- for (const name of allSharedLinks(map)) {
7605
+ for (const name of opts.linkNames ?? allSharedLinks(map, { quiet: opts.quiet === true })) {
7360
7606
  if (isDeniedName(ALWAYS_NEVER_SYNC, name)) {
7361
7607
  scan.declined.push(name);
7362
7608
  continue;
@@ -7365,13 +7611,13 @@ function enumerateLocalSharedScan(map) {
7365
7611
  }
7366
7612
  return scan;
7367
7613
  }
7368
- function enumerateLocalSharedFiles(map) {
7369
- return enumerateLocalSharedScan(map).files;
7614
+ function enumerateLocalSharedFiles(map, opts = {}) {
7615
+ return enumerateLocalSharedScan(map, opts).files;
7370
7616
  }
7371
- function buildSharedBaseline(map) {
7617
+ function buildSharedBaseline(map, opts = {}) {
7372
7618
  const claude = claudeHome();
7373
7619
  const files = {};
7374
- for (const [key, st] of Object.entries(enumerateLocalSharedFiles(map))) {
7620
+ for (const [key, st] of Object.entries(enumerateLocalSharedFiles(map, opts))) {
7375
7621
  try {
7376
7622
  files[key] = { size: st.size, mtime: st.mtime, hash: hashFile(join51(claude, key)) };
7377
7623
  } catch {
@@ -7379,27 +7625,69 @@ function buildSharedBaseline(map) {
7379
7625
  }
7380
7626
  return buildManifest(files, SHARED_BASELINE_KIND, SHARED_BASELINE_CONFIG_HASH);
7381
7627
  }
7382
- function writeSharedBaseline(map) {
7628
+ function writeSharedBaseline(map, opts = {}) {
7383
7629
  if (process.platform !== "win32") return;
7384
7630
  try {
7385
- writeManifest(sharedBaselinePath(), buildSharedBaseline(map));
7631
+ writeManifest(sharedBaselinePath(), buildSharedBaseline(map, opts));
7386
7632
  } catch (err) {
7387
7633
  warn(`could not record the shared-config baseline: ${err.message}`);
7388
7634
  }
7389
7635
  }
7390
7636
 
7391
7637
  // src/commands.pull.win32.ts
7392
- init_config();
7638
+ init_color();
7393
7639
  import { existsSync as existsSync44 } from "node:fs";
7394
7640
  import { join as join56 } from "node:path";
7395
7641
 
7396
- // src/git-probe.ts
7642
+ // src/commands.pull.recovery.git.ts
7643
+ init_utils();
7397
7644
  import { execFileSync as execFileSync21 } from "node:child_process";
7645
+ function gitCapture(args, cwd) {
7646
+ return execFileSync21("git", args, {
7647
+ cwd,
7648
+ stdio: ["ignore", "pipe", "pipe"],
7649
+ maxBuffer: 64 * 1024 * 1024
7650
+ }).toString().trim();
7651
+ }
7652
+ function parsePorcelainZ(raw) {
7653
+ const tracked = [];
7654
+ const untracked = [];
7655
+ if (!raw) return { tracked, untracked };
7656
+ const records = raw.split("\0");
7657
+ for (let i = 0; i < records.length; i++) {
7658
+ const record = records[i];
7659
+ if (record.length < 3) continue;
7660
+ const xy = record.slice(0, 2);
7661
+ const filePath = record.slice(3);
7662
+ if (xy === "??") {
7663
+ untracked.push(filePath);
7664
+ continue;
7665
+ }
7666
+ tracked.push(filePath);
7667
+ if (xy.startsWith("R") || xy.startsWith("C")) {
7668
+ const src = records[i + 1];
7669
+ if (src) {
7670
+ tracked.push(src);
7671
+ i++;
7672
+ }
7673
+ }
7674
+ }
7675
+ return { tracked, untracked };
7676
+ }
7677
+ function parseDirtyPaths(repo, opts = {}) {
7678
+ return parsePorcelainZ(gitStatusPorcelainZ(repo, opts));
7679
+ }
7680
+
7681
+ // src/commands.pull.win32.ts
7682
+ init_config();
7683
+
7684
+ // src/git-probe.ts
7685
+ import { execFileSync as execFileSync22 } from "node:child_process";
7398
7686
  var PROBE_TIMEOUT_MS4 = 1e4;
7399
7687
  var PROBE_MAX_BUFFER = 64 * 1024 * 1024;
7400
7688
  function gitProbe(args, repo) {
7401
7689
  try {
7402
- return execFileSync21("git", args, {
7690
+ return execFileSync22("git", args, {
7403
7691
  cwd: repo,
7404
7692
  stdio: ["ignore", "pipe", "pipe"],
7405
7693
  timeout: PROBE_TIMEOUT_MS4,
@@ -7410,37 +7698,10 @@ function gitProbe(args, repo) {
7410
7698
  }
7411
7699
  }
7412
7700
 
7413
- // src/links.captures.ts
7414
- init_config();
7415
- import { existsSync as existsSync40, lstatSync as lstatSync16 } from "node:fs";
7416
- import { join as join52 } from "node:path";
7417
- function planSharedLinkCaptures(map) {
7418
- if (process.platform !== "win32") return [];
7419
- if (map === null) return [];
7420
- const claude = claudeHome();
7421
- const repo = repoHome();
7422
- const plan = [];
7423
- for (const name of allSharedLinks(map)) {
7424
- const localPath = join52(claude, name);
7425
- let stat;
7426
- try {
7427
- stat = lstatSync16(localPath, { throwIfNoEntry: false });
7428
- } catch {
7429
- continue;
7430
- }
7431
- if (stat === void 0) continue;
7432
- if (stat.isSymbolicLink()) continue;
7433
- const repoPath = join52(repo, "shared", name);
7434
- if (!existsSync40(repoPath)) continue;
7435
- plan.push({ name, localPath, repoPath });
7436
- }
7437
- return plan;
7438
- }
7439
-
7440
7701
  // src/links.deletions.ts
7441
7702
  init_config();
7442
- import { existsSync as existsSync41, lstatSync as lstatSync17, rmSync as rmSync17 } from "node:fs";
7443
- import { join as join53, relative as relative8, resolve as resolve4, sep as sep12 } from "node:path";
7703
+ import { existsSync as existsSync40, lstatSync as lstatSync17, rmSync as rmSync17 } from "node:fs";
7704
+ import { join as join52, relative as relative8, resolve as resolve4, sep as sep13 } from "node:path";
7444
7705
  init_utils();
7445
7706
  init_utils_fs();
7446
7707
  function isUnknown(declined, key) {
@@ -7450,27 +7711,28 @@ function deletionFor(key, names, claude, sharedRoot) {
7450
7711
  const segments = key.split("/");
7451
7712
  const name = segments[0];
7452
7713
  if (!names.has(name)) return null;
7453
- if (!existsSync41(join53(claude, name))) return null;
7714
+ if (!existsSync40(join52(claude, name))) return null;
7454
7715
  if (segments.some((segment) => isDeniedName(ALWAYS_NEVER_SYNC, segment))) return null;
7455
7716
  const repoPath = resolve4(sharedRoot, key);
7456
- const rel = relative8(join53(sharedRoot, name), repoPath);
7457
- if (rel === "" || rel === ".." || rel.startsWith(`..${sep12}`)) return null;
7717
+ const rel = relative8(join52(sharedRoot, name), repoPath);
7718
+ if (rel === "" || rel === ".." || rel.startsWith(`..${sep13}`)) return null;
7458
7719
  if (segments.some((segment) => segment === "" || segment === "." || segment === ".."))
7459
7720
  return null;
7460
- if (!existsSync41(repoPath)) return null;
7461
- return { name, localPath: join53(claude, key), repoPath };
7721
+ if (!existsSync40(repoPath)) return null;
7722
+ return { kind: "deletion", name, localPath: join52(claude, key), repoPath };
7462
7723
  }
7463
- function planSharedLinkDeletions(map) {
7724
+ function planSharedLinkDeletions(map, opts = {}) {
7464
7725
  if (process.platform !== "win32") return [];
7465
7726
  if (map === null) return [];
7466
7727
  const baseline = readSharedBaseline();
7467
7728
  if (baseline === null) return [];
7468
7729
  const claude = claudeHome();
7469
- const sharedRoot = join53(repoHome(), "shared");
7470
- const scan = enumerateLocalSharedScan(map);
7730
+ const sharedRoot = join52(repoHome(), "shared");
7731
+ const linkNames = opts.linkNames ?? allSharedLinks(map);
7732
+ const scan = enumerateLocalSharedScan(map, { linkNames });
7471
7733
  const present = new Set(Object.keys(scan.files).map((key) => key.toLowerCase()));
7472
7734
  const declined = scan.declined.map((path) => path.toLowerCase());
7473
- const names = new Set(allSharedLinks(map));
7735
+ const names = new Set(linkNames);
7474
7736
  const plan = [];
7475
7737
  for (const key of Object.keys(baseline.files)) {
7476
7738
  const folded = key.toLowerCase();
@@ -7481,17 +7743,151 @@ function planSharedLinkDeletions(map) {
7481
7743
  }
7482
7744
  return plan;
7483
7745
  }
7484
- function applySharedLinkDeletions(map, ts) {
7746
+ function applySharedLinkDeletions(map, ts, opts = {}) {
7485
7747
  const repo = repoHome();
7486
- for (const entry of planSharedLinkDeletions(map)) {
7748
+ const removed = [];
7749
+ for (const entry of planSharedLinkDeletions(map, opts)) {
7487
7750
  try {
7488
7751
  if (!lstatSync17(entry.repoPath).isFile()) continue;
7489
7752
  backupRepoWrite(entry.repoPath, ts, repo);
7490
7753
  rmSync17(entry.repoPath, { force: true });
7754
+ removed.push(entry);
7491
7755
  } catch (err) {
7492
7756
  warn(`could not remove ${entry.repoPath}: ${err.message}`);
7493
7757
  }
7494
7758
  }
7759
+ return removed;
7760
+ }
7761
+
7762
+ // src/links.mirror.ts
7763
+ init_config();
7764
+ import { existsSync as existsSync41, lstatSync as lstatSync18, rmSync as rmSync18 } from "node:fs";
7765
+ import { join as join53 } from "node:path";
7766
+ init_utils();
7767
+ init_utils_fs();
7768
+ function emitMirror(onPreview, name, localPath, repoPath) {
7769
+ if (onPreview) {
7770
+ onPreview({ kind: "mirror", name, localPath, repoPath });
7771
+ } else {
7772
+ log(`would capture: ${localPath} -> ${repoPath}`);
7773
+ }
7774
+ }
7775
+ function emitMirrorWet(onPreview, name, localPath, repoPath) {
7776
+ if (onPreview) onPreview({ kind: "mirror", name, localPath, repoPath });
7777
+ }
7778
+ function notShared(policy, target) {
7779
+ return !policy.adoptNew && !existsSync41(target);
7780
+ }
7781
+ function mirrorOneSharedName(name, claude, repo, policy, opts) {
7782
+ const localPath = join53(claude, name);
7783
+ const target = join53(repo, "shared", name);
7784
+ let stat;
7785
+ try {
7786
+ stat = lstatSync18(localPath, { throwIfNoEntry: false });
7787
+ } catch (err) {
7788
+ if (notShared(policy, target)) return;
7789
+ warn(
7790
+ `${name} could not be read (${err.message}), so it was left out of shared/ this run. Check its permissions, or whether another program has it open`
7791
+ );
7792
+ return;
7793
+ }
7794
+ if (stat === void 0) return;
7795
+ if (stat.isSymbolicLink()) return;
7796
+ if (notShared(policy, target)) return;
7797
+ if (opts.dryRun === true) {
7798
+ emitMirror(opts.onPreview, name, localPath, target);
7799
+ return;
7800
+ }
7801
+ if (policy.backupTs !== void 0) backupRepoWrite(target, policy.backupTs, repo);
7802
+ if (policy.overlay && stat.isDirectory()) {
7803
+ copyExtrasOverlayFiltered(localPath, target, NEVER_SYNC);
7804
+ } else {
7805
+ copyExtrasFiltered(localPath, target, NEVER_SYNC);
7806
+ }
7807
+ emitMirrorWet(opts.onPreview, name, localPath, target);
7808
+ }
7809
+ function mirrorSharedNames(map, policy, opts = {}) {
7810
+ if (process.platform !== "win32") return;
7811
+ if (map === null) return;
7812
+ const claude = claudeHome();
7813
+ const repo = repoHome();
7814
+ const linkNames = opts.linkNames ?? allSharedLinks(map);
7815
+ for (const name of linkNames) {
7816
+ mirrorOneSharedName(name, claude, repo, policy, opts);
7817
+ }
7818
+ }
7819
+ function syncSharedLinksPush(map, opts = {}) {
7820
+ mirrorSharedNames(map, { adoptNew: true, overlay: false }, opts);
7821
+ }
7822
+ function stageLocalSharedEdits(map, ts, opts = {}) {
7823
+ mirrorSharedNames(map, { adoptNew: false, overlay: true, backupTs: ts }, opts);
7824
+ }
7825
+ function presentAt(abs) {
7826
+ try {
7827
+ return lstatSync18(abs, { throwIfNoEntry: false }) !== void 0;
7828
+ } catch {
7829
+ return true;
7830
+ }
7831
+ }
7832
+ function removeUntrackedDenied(repo, path, segment, ts) {
7833
+ const abs = join53(repo, path);
7834
+ const denied = `the path segment "${segment}" is on the never-sync list`;
7835
+ if (!presentAt(abs)) {
7836
+ warn(
7837
+ `nothing was removed for ${path}: ${denied}, but nothing is at that path now. Either it went away after git listed it, in which case there is nothing left to do, or its name did not survive the decode of git's output and the real file is still in the sync repo under a name nomad cannot address. Look for it in ${repo} with "git status --untracked-files=all -- shared/"`
7838
+ );
7839
+ return;
7840
+ }
7841
+ const snapshotted = existsSync41(abs);
7842
+ try {
7843
+ backupRepoWrite(abs, ts, repo);
7844
+ } catch (err) {
7845
+ warn(
7846
+ `could not snapshot ${abs} before removing it (${err.message}), so it was left in place: ${denied}, so remove it by hand`
7847
+ );
7848
+ return;
7849
+ }
7850
+ try {
7851
+ rmSync18(abs, { recursive: true, force: true });
7852
+ if (presentAt(abs)) {
7853
+ warn(`could not remove ${abs}: ${denied}, so remove it by hand`);
7854
+ return;
7855
+ }
7856
+ const where = snapshotted ? `. A copy was snapshotted under backup/${ts}/repo/ first` : "";
7857
+ warn(`removed ${path} from the sync repo working tree: ${denied}${where}`);
7858
+ } catch (err) {
7859
+ warn(`could not remove ${abs}: ${err.message}`);
7860
+ }
7861
+ }
7862
+ function reportTrackedDenied(repo, path, segment) {
7863
+ const inHead = gitProbe(["ls-tree", "--name-only", "HEAD", "--", path], repo);
7864
+ const denied = `the path segment "${segment}" is on the never-sync list`;
7865
+ if (inHead === null) {
7866
+ warn(
7867
+ `could not check ${path} against HEAD: ${denied}. Nothing was changed. Inspect it with "git status -- ${path}" and take it out of shared/ before committing`
7868
+ );
7869
+ return;
7870
+ }
7871
+ if (inHead.trim() === "") {
7872
+ warn(
7873
+ `${path} is staged and has no committed version: ${denied}. Nothing was changed. Run "git rm --cached -- ${path}" to take it out of the index; that leaves the file on disk but makes it untracked, which the next nomad pull removes from the sync repo working tree (snapshotting it into the backup cache first), so move it outside shared/ instead if you want to keep it. If it is the destination half of a staged rename, "git diff --cached --name-status" names the source, whose content IS committed, so restore that half with "git checkout HEAD -- <source>" rather than leaving its deletion staged`
7874
+ );
7875
+ return;
7876
+ }
7877
+ if (!existsSync41(join53(repo, path))) return;
7878
+ warn(
7879
+ `${path} is tracked and has changes against HEAD: ${denied}. Nothing was changed. Run "git checkout HEAD -- ${path}" to put the committed content back, or move the file outside shared/ if you want to keep it. Neither of those takes the committed copy out of the repo: "git rm -- ${path}" and a commit does that going forward, and if it holds a real secret, rotate it and rewrite history, because nomad only changes your local worktree and index and cannot scrub what a previous push already sent to the remote`
7880
+ );
7881
+ }
7882
+ function revertDeniedMirrorPaths(repo, status, ts) {
7883
+ for (const path of new Set(status.untracked)) {
7884
+ const segment = deniedSegmentFor(path);
7885
+ if (segment !== null) removeUntrackedDenied(repo, path, segment, ts);
7886
+ }
7887
+ for (const path of new Set(status.tracked)) {
7888
+ const segment = deniedSegmentFor(path);
7889
+ if (segment !== null) reportTrackedDenied(repo, path, segment);
7890
+ }
7495
7891
  }
7496
7892
 
7497
7893
  // src/preview.ts
@@ -7838,8 +8234,8 @@ function formatLinkRow(e) {
7838
8234
  if (e.kind === "copy") return `would copy ${e.from} -> ${e.to}`;
7839
8235
  return `${e.kind} ${e.from} -> ${e.to}`;
7840
8236
  }
7841
- function formatCaptureRow(c) {
7842
- return `would capture ${c.localPath} -> ${c.repoPath}`;
8237
+ function formatMirrorRow(e) {
8238
+ return `would capture ${e.localPath} -> ${e.repoPath}`;
7843
8239
  }
7844
8240
  function formatDeletionRow(d) {
7845
8241
  return `would remove ${d.repoPath} (gone from ${d.localPath})`;
@@ -7847,6 +8243,23 @@ function formatDeletionRow(d) {
7847
8243
  function formatSessionRow(e) {
7848
8244
  return e.kind === "overwrite" ? `overwrite ${e.dst} (from ${e.src})` : e.text;
7849
8245
  }
8246
+ function appendMirrorPlanRows(links, ts, map, plans) {
8247
+ if (plans) {
8248
+ for (const capture of plans.captures) addItem(links, formatMirrorRow(capture));
8249
+ for (const deletion of plans.deletions) addItem(links, formatDeletionRow(deletion));
8250
+ return plans.namesDerived;
8251
+ }
8252
+ const linkNames = allSharedLinks(map);
8253
+ stageLocalSharedEdits(map, ts, {
8254
+ dryRun: true,
8255
+ onPreview: (e) => addItem(links, formatMirrorRow(e)),
8256
+ linkNames
8257
+ });
8258
+ for (const deletion of planSharedLinkDeletions(map, { linkNames })) {
8259
+ addItem(links, formatDeletionRow(deletion));
8260
+ }
8261
+ return true;
8262
+ }
7850
8263
  function buildSettingsSectionForPreview(result) {
7851
8264
  const s = section("settings.json", true);
7852
8265
  if (result.diff !== "") {
@@ -7865,18 +8278,10 @@ function computePreview(ts, map, verb = "pull", plans) {
7865
8278
  console.log(`would pull on host=${HOST} (preview; nothing applied)`);
7866
8279
  console.log("");
7867
8280
  const links = section("Symlinks");
7868
- const { captures, deletions } = plans ?? {
7869
- captures: planSharedLinkCaptures(map),
7870
- deletions: planSharedLinkDeletions(map)
7871
- };
7872
- for (const capture of captures) {
7873
- addItem(links, formatCaptureRow(capture));
7874
- }
7875
- for (const deletion of deletions) {
7876
- addItem(links, formatDeletionRow(deletion));
7877
- }
8281
+ const namesDerived = appendMirrorPlanRows(links, ts, map, plans);
7878
8282
  applySharedLinks(ts, map, {
7879
8283
  dryRun: true,
8284
+ quietNames: namesDerived,
7880
8285
  onPreview: (e) => addItem(links, formatLinkRow(e))
7881
8286
  });
7882
8287
  const settingsResult = previewSettings(
@@ -7926,6 +8331,19 @@ function readMapForMirror(mapPath) {
7926
8331
  return null;
7927
8332
  }
7928
8333
  }
8334
+ function sharedDirsUnchanged(before, after) {
8335
+ return JSON.stringify(before) === JSON.stringify(after);
8336
+ }
8337
+ function namesAlreadyReported(namesDerived, derivedSharedDirs, map) {
8338
+ return namesDerived && sharedDirsUnchanged(derivedSharedDirs, map.sharedDirs);
8339
+ }
8340
+ function plansAgainst(plans, map) {
8341
+ if (plans === void 0) return void 0;
8342
+ return {
8343
+ ...plans,
8344
+ namesDerived: namesAlreadyReported(plans.namesDerived, plans.derivedSharedDirs, map)
8345
+ };
8346
+ }
7929
8347
  function untrackedUnderShared(repo) {
7930
8348
  const out = gitProbe(["ls-files", "--others", "--exclude-standard", "-z", "--", "shared/"], repo);
7931
8349
  if (out === null) return null;
@@ -7935,27 +8353,94 @@ function newlyUntracked(before, after) {
7935
8353
  if (before === null || after === null) return [];
7936
8354
  return [...after].filter((p) => !before.has(p));
7937
8355
  }
8356
+ function revertDeniedUnderShared(repo, ts) {
8357
+ const out = gitProbe(
8358
+ ["status", "--porcelain=v1", "-z", "--untracked-files=all", "--", "shared/"],
8359
+ repo
8360
+ );
8361
+ if (out === null) return;
8362
+ revertDeniedMirrorPaths(repo, parsePorcelainZ(out), ts);
8363
+ }
7938
8364
  function reconcileSharedLinksBeforePull(repo, ts) {
7939
- if (process.platform !== "win32") return [];
8365
+ if (process.platform !== "win32") {
8366
+ return { mirrored: [], events: [], namesDerived: false, derivedSharedDirs: void 0 };
8367
+ }
7940
8368
  const before = untrackedUnderShared(repo);
8369
+ const events = [];
8370
+ let linkNames = [];
8371
+ let namesDerived = false;
8372
+ let derivedSharedDirs;
7941
8373
  try {
7942
8374
  const map = readMapForMirror(join56(repo, "path-map.json"));
7943
- stageLocalSharedEdits(map, ts);
7944
- applySharedLinkDeletions(map, ts);
8375
+ if (map !== null) {
8376
+ linkNames = allSharedLinks(map);
8377
+ namesDerived = true;
8378
+ derivedSharedDirs = map.sharedDirs;
8379
+ }
8380
+ stageLocalSharedEdits(map, ts, { onPreview: (e) => events.push(e), linkNames });
8381
+ events.push(...applySharedLinkDeletions(map, ts, { linkNames }));
7945
8382
  } catch (err) {
7946
8383
  warn(`could not reconcile local shared edits before the pull: ${err.message}`);
7947
8384
  }
7948
- return newlyUntracked(before, untrackedUnderShared(repo));
8385
+ revertDeniedUnderShared(repo, ts);
8386
+ return {
8387
+ mirrored: newlyUntracked(before, untrackedUnderShared(repo)),
8388
+ events,
8389
+ namesDerived,
8390
+ derivedSharedDirs
8391
+ };
8392
+ }
8393
+ function buildMirrorSection(events, discard) {
8394
+ const s = section("Symlinks");
8395
+ for (const e of events) {
8396
+ addItem(
8397
+ s,
8398
+ e.kind === "deletion" ? `removed ${e.repoPath} (gone from ${e.localPath})` : `captured ${e.localPath} -> ${e.repoPath}`
8399
+ );
8400
+ }
8401
+ if (discard) {
8402
+ const names = discard.count === 1 ? "name was" : "names were";
8403
+ addItem(
8404
+ s,
8405
+ `${yellow(warnGlyph)} recovered from a wedged repo, ${discard.count} shared ${names} restored from the repo copy, previous host copies backed up to ${discard.backupPath}`
8406
+ );
8407
+ }
8408
+ return s;
7949
8409
  }
7950
- function planSharedReconcileBeforePull(repo) {
8410
+ function describeSkippedMirrorDiscard(repo, ts) {
8411
+ if (process.platform !== "win32") return null;
8412
+ try {
8413
+ const map = readMapForMirror(join56(repo, "path-map.json"));
8414
+ const linkNames = map !== null ? allSharedLinks(map, { quiet: true }) : void 0;
8415
+ const captures = [];
8416
+ stageLocalSharedEdits(map, ts, {
8417
+ dryRun: true,
8418
+ onPreview: (e) => captures.push(e),
8419
+ linkNames
8420
+ });
8421
+ if (captures.length === 0) return null;
8422
+ return { count: captures.length, backupPath: join56(backupBase(), ts) };
8423
+ } catch {
8424
+ return null;
8425
+ }
8426
+ }
8427
+ function planSharedReconcileBeforePull(repo, ts) {
7951
8428
  const map = readMapForMirror(join56(repo, "path-map.json"));
7952
- return { captures: planSharedLinkCaptures(map), deletions: planSharedLinkDeletions(map) };
8429
+ const captures = [];
8430
+ const linkNames = process.platform === "win32" && map !== null ? allSharedLinks(map) : void 0;
8431
+ stageLocalSharedEdits(map, ts, { dryRun: true, onPreview: (e) => captures.push(e), linkNames });
8432
+ return {
8433
+ captures,
8434
+ deletions: planSharedLinkDeletions(map, { linkNames }),
8435
+ namesDerived: linkNames !== void 0,
8436
+ derivedSharedDirs: map?.sharedDirs
8437
+ };
7953
8438
  }
7954
8439
 
7955
8440
  // src/commands.pull.collision.ts
7956
8441
  init_config();
7957
8442
  init_exit_codes();
7958
- import { existsSync as existsSync45, lstatSync as lstatSync18, readFileSync as readFileSync20, rmSync as rmSync18 } from "node:fs";
8443
+ import { existsSync as existsSync45, lstatSync as lstatSync19, readFileSync as readFileSync20, rmSync as rmSync19 } from "node:fs";
7959
8444
  import { join as join57 } from "node:path";
7960
8445
  init_utils();
7961
8446
  var SHARED_PREFIX = /^shared\//;
@@ -8022,9 +8507,9 @@ function removeMirroredCopies(repo, repoRelPaths) {
8022
8507
  for (const rel of repoRelPaths.filter(isContainedMirrorPath)) {
8023
8508
  const abs = join57(repo, rel);
8024
8509
  try {
8025
- if (lstatSync18(abs, { throwIfNoEntry: false })?.isFile() !== true) continue;
8510
+ if (lstatSync19(abs, { throwIfNoEntry: false })?.isFile() !== true) continue;
8026
8511
  if (!matchesLocalOriginal(repo, rel)) continue;
8027
- rmSync18(abs, { force: true });
8512
+ rmSync19(abs, { force: true });
8028
8513
  } catch (err) {
8029
8514
  warn(`could not remove nomad's copy at ${abs}: ${err.message}`);
8030
8515
  }
@@ -8051,47 +8536,6 @@ init_commands_pull_wedge();
8051
8536
  init_config();
8052
8537
  init_utils();
8053
8538
  init_utils_fs();
8054
-
8055
- // src/commands.pull.recovery.git.ts
8056
- init_utils();
8057
- import { execFileSync as execFileSync22 } from "node:child_process";
8058
- function gitCapture(args, cwd) {
8059
- return execFileSync22("git", args, {
8060
- cwd,
8061
- stdio: ["ignore", "pipe", "pipe"],
8062
- maxBuffer: 64 * 1024 * 1024
8063
- }).toString().trim();
8064
- }
8065
- function parsePorcelainZ(raw) {
8066
- const tracked = [];
8067
- const untracked = [];
8068
- if (!raw) return { tracked, untracked };
8069
- const records = raw.split("\0");
8070
- for (let i = 0; i < records.length; i++) {
8071
- const record = records[i];
8072
- if (record.length < 3) continue;
8073
- const xy = record.slice(0, 2);
8074
- const filePath = record.slice(3);
8075
- if (xy === "??") {
8076
- untracked.push(filePath);
8077
- continue;
8078
- }
8079
- tracked.push(filePath);
8080
- if (xy.startsWith("R") || xy.startsWith("C")) {
8081
- const src = records[i + 1];
8082
- if (src) {
8083
- tracked.push(src);
8084
- i++;
8085
- }
8086
- }
8087
- }
8088
- return { tracked, untracked };
8089
- }
8090
- function parseDirtyPaths(repo, opts = {}) {
8091
- return parsePorcelainZ(gitStatusPorcelainZ(repo, opts));
8092
- }
8093
-
8094
- // src/commands.pull.recovery.ts
8095
8539
  function isSyncedConfig(path) {
8096
8540
  return PUSH_ALLOWED_STATIC.some(
8097
8541
  (entry) => entry.endsWith("/") ? path.startsWith(entry) : path === entry
@@ -8207,9 +8651,9 @@ function capturePrePostHeads(repo, rebase) {
8207
8651
  if (pre === void 0 || post === void 0) return void 0;
8208
8652
  return { pre, post };
8209
8653
  }
8210
- function buildWetPullSections(ts, map, prePostHeads) {
8211
- applySharedLinks(ts, map);
8212
- writeSharedBaseline(map);
8654
+ function buildWetPullSections(ts, map, prePostHeads, namesDerived = false) {
8655
+ applySharedLinks(ts, map, { quietNames: namesDerived });
8656
+ writeSharedBaseline(map, { quiet: true });
8213
8657
  const { label } = regenerateSettings(ts);
8214
8658
  syncSkillsPull(ts, prePostHeads);
8215
8659
  const remapResult = withSpinner("Syncing sessions", () => remapPull(ts));
@@ -8232,22 +8676,24 @@ function buildWetPullSections(ts, map, prePostHeads) {
8232
8676
  };
8233
8677
  }
8234
8678
  function handleWedge(repo, forceRemote) {
8235
- const wedge = classifyWedge(repo);
8236
- if (wedge === null) return;
8679
+ const { state: wedge, probe } = classifyWedgeWithProbe(repo);
8680
+ if (wedge === null) {
8681
+ if (forceRemote) log(cleanRepoForceRemoteMessage(probe));
8682
+ return false;
8683
+ }
8237
8684
  if (wedge === "unmerged-index") {
8238
8685
  if (forceRemote) {
8239
8686
  recoverUnmergedIndex(repo);
8240
- } else {
8241
- die(unmergedIndexRunbookText("nomad pull"), { code: EXIT.CONFLICT });
8687
+ return false;
8242
8688
  }
8243
- return;
8689
+ return die(unmergedIndexRunbookText("nomad pull"), { code: EXIT.CONFLICT });
8244
8690
  }
8245
8691
  if (forceRemote) {
8246
8692
  recoverForceRemote(wedge, repo);
8247
- return;
8693
+ return true;
8248
8694
  }
8249
8695
  const state = wedge === "rebase" ? "mid-rebase" : "mid-merge";
8250
- die(wedgeMarkerRunbookText(state), { code: EXIT.CONFLICT });
8696
+ return die(wedgeMarkerRunbookText(state), { code: EXIT.CONFLICT });
8251
8697
  }
8252
8698
  function runPullCore(opts = {}) {
8253
8699
  const dryRun = opts.dryRun === true;
@@ -8256,7 +8702,7 @@ function runPullCore(opts = {}) {
8256
8702
  const repo = repoHome();
8257
8703
  const backup = backupBase();
8258
8704
  const ts = freshBackupTs(backup);
8259
- handleWedge(repo, forceRemote);
8705
+ const recovered = handleWedge(repo, forceRemote);
8260
8706
  if (!dryRun) {
8261
8707
  const backupRoot = join58(backup, ts);
8262
8708
  try {
@@ -8270,8 +8716,13 @@ function runPullCore(opts = {}) {
8270
8716
  dryRun ? `pulling on host=${HOST} (backup=${ts}; dry-run)` : `pull on host=${HOST} (backup=${ts})`
8271
8717
  );
8272
8718
  }
8273
- const mirrored = !dryRun && !forceRemote ? reconcileSharedLinksBeforePull(repo, ts) : [];
8274
- const sharedPlans = dryRun ? planSharedReconcileBeforePull(repo) : void 0;
8719
+ const {
8720
+ mirrored,
8721
+ events: mirrorEvents,
8722
+ namesDerived,
8723
+ derivedSharedDirs
8724
+ } = !dryRun && !recovered ? reconcileSharedLinksBeforePull(repo, ts) : { mirrored: [], events: [], namesDerived: false, derivedSharedDirs: void 0 };
8725
+ const sharedPlans = dryRun ? planSharedReconcileBeforePull(repo, ts) : void 0;
8275
8726
  const prePostHeads = capturePrePostHeads(repo, () => {
8276
8727
  pullWithCollisionRunbook(repo, mirrored);
8277
8728
  });
@@ -8280,18 +8731,20 @@ function runPullCore(opts = {}) {
8280
8731
  const map = existsSync46(mapPath) ? readPathMap(mapPath) : { projects: {} };
8281
8732
  const divergedKeptLocal = divergenceCheckExtras(ts, dryRun ? prePostHeads : void 0);
8282
8733
  if (dryRun) {
8283
- computePreview(ts, map, "pull", sharedPlans);
8734
+ computePreview(ts, map, "pull", plansAgainst(sharedPlans, map));
8284
8735
  return { tag: "dry" };
8285
8736
  }
8737
+ const discardedMirror = recovered && !dryRun ? describeSkippedMirrorDiscard(repo, ts) : void 0;
8286
8738
  const { sections, localOnly, settingsLabel, unmapped, extrasSkipped } = buildWetPullSections(
8287
8739
  ts,
8288
8740
  map,
8289
- prePostHeads
8741
+ prePostHeads,
8742
+ namesAlreadyReported(namesDerived, derivedSharedDirs, map)
8290
8743
  );
8291
8744
  const incomingChanges = prePostHeads === void 0 ? true : prePostHeads.pre !== prePostHeads.post;
8292
8745
  return {
8293
8746
  tag: "wet",
8294
- sections,
8747
+ sections: [buildMirrorSection(mirrorEvents, discardedMirror), ...sections],
8295
8748
  localOnly,
8296
8749
  divergedKeptLocal,
8297
8750
  incomingChanges,
@@ -8399,19 +8852,6 @@ function isAllowed(path, allowed) {
8399
8852
  }
8400
8853
  return false;
8401
8854
  }
8402
- function blockSetFor(segments) {
8403
- if (segments[0] !== "shared" || segments[1] !== "extras") return NEVER_SYNC;
8404
- return segments[3] === ".claude" ? CLAUDE_EXTRA_NEVER_SYNC : ALWAYS_NEVER_SYNC;
8405
- }
8406
- function isNeverSync(path) {
8407
- const segments = path.split("/");
8408
- const blockSet = blockSetFor(segments);
8409
- const scan = segments[0] === "shared" && segments[1] === "extras" ? segments.slice(4) : segments;
8410
- for (const segment of scan) {
8411
- if (isDeniedName(blockSet, segment)) return true;
8412
- }
8413
- return false;
8414
- }
8415
8855
  var GSD_HOOKS_SUPPORT_FILES = /* @__PURE__ */ new Set(["managed-hooks-registry.cjs", "package.json"]);
8416
8856
  var GSD_HOOKS_SUPPORT_DIR = "lib";
8417
8857
  function isGsdDropped(path) {
@@ -8451,7 +8891,11 @@ function enforceAllowList(statusPorcelain, map) {
8451
8891
  ...Object.entries(map.extras ?? {}).flatMap(
8452
8892
  ([l, names]) => names.filter((n) => extrasWhitelist.includes(n)).flatMap((n) => [`shared/extras/${l}/${n}`, `shared/extras/${l}/${n}/`])
8453
8893
  ),
8454
- ...(map.sharedDirs ?? []).filter((d) => isValidSharedDir(d)).map((d) => `shared/${d}/`)
8894
+ // Predicate passed by reference, not wrapped in an arrow: `isValidSharedDir`
8895
+ // is a type guard, and an arrow returning its result is just a boolean, so
8896
+ // wrapping it would leave `d` as `unknown` and let a non-string reach the
8897
+ // template literal as "[object Object]".
8898
+ ...sharedDirEntries(map).filter(isValidSharedDir).map((d) => `shared/${d}/`)
8455
8899
  ];
8456
8900
  const neverSyncHits = [];
8457
8901
  const violations = [];
@@ -8632,9 +9076,9 @@ init_color();
8632
9076
  init_config();
8633
9077
  init_config_sharedDirs_guard();
8634
9078
  import { randomBytes as randomBytes4 } from "node:crypto";
8635
- import { copyFileSync, existsSync as existsSync49, lstatSync as lstatSync19, mkdirSync as mkdirSync15, readdirSync as readdirSync19, rmSync as rmSync19 } from "node:fs";
9079
+ import { copyFileSync, existsSync as existsSync49, lstatSync as lstatSync20, mkdirSync as mkdirSync15, readdirSync as readdirSync19, rmSync as rmSync20 } from "node:fs";
8636
9080
  import { homedir as homedir7 } from "node:os";
8637
- import { join as join62, relative as relative10, sep as sep13 } from "node:path";
9081
+ import { join as join62, relative as relative10, sep as sep14 } from "node:path";
8638
9082
  init_push_leak_verdict();
8639
9083
  init_push_gitleaks();
8640
9084
  init_utils_fs();
@@ -8642,7 +9086,7 @@ init_utils_json();
8642
9086
  var NOTHING_TO_SCAN_ROW = `${dim(infoGlyph)} nothing to scan, no leaks`;
8643
9087
  function stageSessionDir(localDir, dstDir, changed) {
8644
9088
  if (changed !== void 0) {
8645
- const prefix = `${localDir}${sep13}`;
9089
+ const prefix = `${localDir}${sep14}`;
8646
9090
  const matching = [...changed].filter((p) => p.startsWith(prefix));
8647
9091
  if (matching.length === 0) return false;
8648
9092
  for (const src of matching) {
@@ -8696,7 +9140,7 @@ function stageExtras(tmpRoot, map) {
8696
9140
  }
8697
9141
  function stageSkills(tmpRoot) {
8698
9142
  const localSkills = join62(claudeHome(), "skills");
8699
- const stat = lstatSync19(localSkills, { throwIfNoEntry: false });
9143
+ const stat = lstatSync20(localSkills, { throwIfNoEntry: false });
8700
9144
  if (stat === void 0 || stat.isSymbolicLink()) return 0;
8701
9145
  const names = readdirSync19(localSkills, { encoding: "utf8" }).filter((n) => !isSkillExcluded(n));
8702
9146
  if (names.length === 0) return 0;
@@ -8730,7 +9174,7 @@ function previewPushLeaks(map, opts = {}) {
8730
9174
  }
8731
9175
  return verdictFromFindings(findings);
8732
9176
  } finally {
8733
- rmSync19(tmpRoot, { recursive: true, force: true });
9177
+ rmSync20(tmpRoot, { recursive: true, force: true });
8734
9178
  }
8735
9179
  }
8736
9180
 
@@ -8859,7 +9303,8 @@ async function runPushCore(opts = {}) {
8859
9303
  const extras = withSpinner("Syncing extras", () => remapExtrasPush(ts, { dryRun }));
8860
9304
  if (!dryRun) {
8861
9305
  syncSkillsPush();
8862
- syncSharedLinksPush(map);
9306
+ const linkNames = map !== null ? allSharedLinks(map) : void 0;
9307
+ syncSharedLinksPush(map, { linkNames });
8863
9308
  stripGsdHooksFromBase(repo, backup);
8864
9309
  }
8865
9310
  const st = { dryRun, remap, extras, globalConfig: [] };
@@ -8982,7 +9427,14 @@ function buildSyncSummarySection(pull, pushOutcome) {
8982
9427
  for (const row2 of buildSkipAndCollisionRows(pull, result)) addItem(s, row2);
8983
9428
  return s;
8984
9429
  }
8985
- var SYNC_SECTION_ORDER = ["Settings", "Global config", "Sessions", "Extras", "Leak scan"];
9430
+ var SYNC_SECTION_ORDER = [
9431
+ "Symlinks",
9432
+ "Settings",
9433
+ "Global config",
9434
+ "Sessions",
9435
+ "Extras",
9436
+ "Leak scan"
9437
+ ];
8986
9438
  function isDroppedSummaryHeader(header) {
8987
9439
  return header === PULL_SUMMARY_HEADER || header === "Push summary";
8988
9440
  }
@@ -9211,7 +9663,7 @@ function buildCrashReport(input) {
9211
9663
  }
9212
9664
 
9213
9665
  // src/crash-report.redact.ts
9214
- import { mkdtempSync as mkdtempSync3, rmSync as rmSync20, writeFileSync as writeFileSync10 } from "node:fs";
9666
+ import { mkdtempSync as mkdtempSync3, rmSync as rmSync21, writeFileSync as writeFileSync10 } from "node:fs";
9215
9667
  import { tmpdir as tmpdir2 } from "node:os";
9216
9668
  import { join as join65 } from "node:path";
9217
9669
  init_push_gitleaks_scan();
@@ -9231,7 +9683,7 @@ function redactWithGitleaks(text, scan = scanFile) {
9231
9683
  } finally {
9232
9684
  if (dir !== void 0) {
9233
9685
  try {
9234
- rmSync20(dir, { recursive: true, force: true });
9686
+ rmSync21(dir, { recursive: true, force: true });
9235
9687
  } catch {
9236
9688
  }
9237
9689
  }
@@ -9409,7 +9861,7 @@ init_config();
9409
9861
  init_utils();
9410
9862
  init_utils_fs();
9411
9863
  init_utils_json();
9412
- import { copyFileSync as copyFileSync2, cpSync as cpSync10, existsSync as existsSync53, rmSync as rmSync21, statSync as statSync13 } from "node:fs";
9864
+ import { copyFileSync as copyFileSync2, cpSync as cpSync10, existsSync as existsSync53, rmSync as rmSync22, statSync as statSync13 } from "node:fs";
9413
9865
  import { join as join68 } from "node:path";
9414
9866
  function snapshotIntoShared(map) {
9415
9867
  const repo = repoHome();
@@ -9420,7 +9872,7 @@ function snapshotIntoShared(map) {
9420
9872
  const dst = join68(repo, "shared", name);
9421
9873
  if (statSync13(src).isDirectory()) {
9422
9874
  const gk = join68(dst, ".gitkeep");
9423
- if (existsSync53(gk)) rmSync21(gk);
9875
+ if (existsSync53(gk)) rmSync22(gk);
9424
9876
  cpSync10(src, dst, { recursive: true, force: false, errorOnExist: true });
9425
9877
  } else {
9426
9878
  copyFileSync2(src, dst);
@@ -9881,7 +10333,7 @@ function parseSyncArgs(argv) {
9881
10333
  // package.json
9882
10334
  var package_default = {
9883
10335
  name: "claude-nomad",
9884
- version: "0.64.0",
10336
+ version: "0.65.0",
9885
10337
  type: "module",
9886
10338
  description: "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
9887
10339
  keywords: [
@@ -9992,7 +10444,8 @@ var DEFAULT_HELP = [
9992
10444
  cont("abort the in-progress rebase/merge, park stranded commits on"),
9993
10445
  cont("nomad/stranded-<ts>, reset to origin/main, and re-pull. Refuses"),
9994
10446
  cont("if stranded or dirty tracked changes touch synced config (shared/,"),
9995
- cont("hosts/, path-map.json). Cannot combine with --dry-run."),
10447
+ cont("hosts/, path-map.json). Cannot combine with --dry-run. On a clean"),
10448
+ cont("repo, reports nothing to recover and pulls normally."),
9996
10449
  "",
9997
10450
  row(" push", "Rebase, run safety checks (gitleaks, gitlinks, allow-list), commit, push."),
9998
10451
  row(" --dry-run", "Run pre-checks (rebase, gitleaks probe, gitlink scan) and preview"),