claude-nomad 0.56.0 → 0.56.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/nomad.mjs +589 -410
  3. package/package.json +1 -1
package/dist/nomad.mjs CHANGED
@@ -198,6 +198,13 @@ function gitCaptureRaw(args, cwd) {
198
198
  maxBuffer: 64 * 1024 * 1024
199
199
  }).toString();
200
200
  }
201
+ function gitCaptureBuffer(args, cwd) {
202
+ return execFileSync("git", args, {
203
+ cwd,
204
+ stdio: ["ignore", "pipe", "pipe"],
205
+ maxBuffer: 64 * 1024 * 1024
206
+ });
207
+ }
201
208
  function gitOrFatal(args, context, cwd) {
202
209
  try {
203
210
  execFileSync("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
@@ -1041,22 +1048,38 @@ function otherFindingHint(f) {
1041
1048
  }
1042
1049
  return " Review with: git diff --cached, then unstage manually.";
1043
1050
  }
1044
- function buildSessionAwareFatal(bySession, other) {
1045
- if (bySession.size === 0) return LEGACY_FATAL;
1051
+ function renderOtherFindings(other) {
1046
1052
  const lines = [];
1047
- lines.push(
1048
- `gitleaks detected secrets in ${bySession.size} session transcript(s).`,
1049
- "nomad drop-session also clears each session's sibling subagent transcript directory."
1050
- );
1051
- for (const [sid, counts] of bySession) {
1052
- const summary = [...counts.entries()].map(([rule, n]) => `${rule} (${n})`).join(", ");
1053
- lines.push("", `Session ${sid}:`, ` ${summary}`, ` Recover with: nomad drop-session ${sid}`);
1053
+ for (const f of other) {
1054
+ lines.push(formatOtherFinding(f), otherFindingHint(f));
1054
1055
  }
1055
- if (other.length > 0) {
1056
- lines.push("", "Also found:");
1057
- for (const f of other) {
1058
- lines.push(formatOtherFinding(f), otherFindingHint(f));
1056
+ return lines;
1057
+ }
1058
+ function buildSessionAwareFatal(bySession, other) {
1059
+ if (bySession.size === 0 && other.length === 0) return LEGACY_FATAL;
1060
+ const lines = [];
1061
+ if (bySession.size > 0) {
1062
+ lines.push(
1063
+ `gitleaks detected secrets in ${bySession.size} session transcript(s).`,
1064
+ "nomad drop-session also clears each session's sibling subagent transcript directory."
1065
+ );
1066
+ for (const [sid, counts] of bySession) {
1067
+ const summary = [...counts.entries()].map(([rule, n]) => `${rule} (${n})`).join(", ");
1068
+ lines.push(
1069
+ "",
1070
+ `Session ${sid}:`,
1071
+ ` ${summary}`,
1072
+ ` Recover with: nomad drop-session ${sid}`
1073
+ );
1074
+ }
1075
+ if (other.length > 0) {
1076
+ lines.push("", "Also found:", ...renderOtherFindings(other));
1059
1077
  }
1078
+ } else {
1079
+ lines.push(
1080
+ `gitleaks detected secrets in ${other.length} location(s):`,
1081
+ ...renderOtherFindings(other)
1082
+ );
1060
1083
  }
1061
1084
  lines.push("", "After recovery, re-run nomad push.");
1062
1085
  return lines.join("\n");
@@ -1086,9 +1109,11 @@ __export(push_leak_verdict_exports, {
1086
1109
  verdictScanError: () => verdictScanError
1087
1110
  });
1088
1111
  function leakVerdictRow(findings) {
1089
- const { bySession } = partitionFindings(findings);
1090
- const n = bySession.size > 0 ? bySession.size : findings.length;
1091
- return failRow(`gitleaks detected secrets in ${n} session transcript(s)`);
1112
+ const { bySession, other } = partitionFindings(findings);
1113
+ if (bySession.size > 0) {
1114
+ return failRow(`gitleaks detected secrets in ${bySession.size} session transcript(s)`);
1115
+ }
1116
+ return failRow(`gitleaks detected secrets in ${other.length} location(s)`);
1092
1117
  }
1093
1118
  function leakFound(findings) {
1094
1119
  const { bySession, other } = partitionFindings(findings);
@@ -2092,8 +2117,8 @@ function cmdEject(opts = {}, roots = defaultEjectRoots()) {
2092
2117
  }
2093
2118
 
2094
2119
  // src/commands.doctor.ts
2095
- import { existsSync as existsSync31 } from "node:fs";
2096
- import { join as join37 } from "node:path";
2120
+ import { existsSync as existsSync32 } from "node:fs";
2121
+ import { join as join38 } from "node:path";
2097
2122
 
2098
2123
  // src/commands.doctor.checks.repo.ts
2099
2124
  init_color();
@@ -2533,28 +2558,42 @@ init_config();
2533
2558
  // src/extras-sync.diff.ts
2534
2559
  init_utils();
2535
2560
  import { execFileSync as execFileSync2 } from "node:child_process";
2536
- function labelDiffLine(line) {
2537
- const tab = line.indexOf(" ");
2538
- if (tab === -1) return line;
2539
- const status = line.slice(0, tab);
2540
- const path = line.slice(tab + 1);
2541
- if (status === "D") return `${path} (local only)`;
2542
- if (status === "A") return `${path} (repo only)`;
2543
- return path;
2561
+ import { relative as relative2 } from "node:path";
2562
+ function parseNameStatus(stdout) {
2563
+ const entries = [];
2564
+ for (const line of stdout.split("\n")) {
2565
+ if (line.length === 0) continue;
2566
+ const tab = line.indexOf(" ");
2567
+ if (tab === -1) continue;
2568
+ entries.push({ status: line.slice(0, tab), path: line.slice(tab + 1) });
2569
+ }
2570
+ return entries;
2571
+ }
2572
+ function labelEntry(entry) {
2573
+ if (entry.status === "D") return `${entry.path} (local only)`;
2574
+ if (entry.status === "A") return `${entry.path} (repo only)`;
2575
+ return entry.path;
2544
2576
  }
2545
2577
  function parseDiffOutput(stdout) {
2546
- return stdout.split("\n").filter((line) => line.length > 0).map(labelDiffLine);
2578
+ return parseNameStatus(stdout).map(labelEntry);
2547
2579
  }
2548
- function listDivergingFiles(a, b) {
2580
+ function parseModifiedPaths(stdout, a) {
2581
+ return parseNameStatus(stdout).filter((entry) => entry.status === "M").map((entry) => relative2(a, entry.path));
2582
+ }
2583
+ function runNameStatusDiff(a, b, parse) {
2549
2584
  try {
2550
- const stdout = execFileSync2("git", ["diff", "--no-index", "--name-status", a, b], {
2551
- stdio: ["ignore", "pipe", "pipe"]
2552
- }).toString();
2553
- return parseDiffOutput(stdout);
2585
+ const stdout = execFileSync2(
2586
+ "git",
2587
+ ["diff", "--no-index", "--no-renames", "--name-status", a, b],
2588
+ {
2589
+ stdio: ["ignore", "pipe", "pipe"]
2590
+ }
2591
+ ).toString();
2592
+ return parse(stdout);
2554
2593
  } catch (err) {
2555
2594
  const e = err;
2556
2595
  if (e.status === 1 && e.stdout !== void 0) {
2557
- return parseDiffOutput(e.stdout.toString());
2596
+ return parse(e.stdout.toString());
2558
2597
  }
2559
2598
  if (e.code === "ENOENT") {
2560
2599
  warn(`git not on PATH; divergence check skipped for ${a}`);
@@ -2564,6 +2603,12 @@ function listDivergingFiles(a, b) {
2564
2603
  return [];
2565
2604
  }
2566
2605
  }
2606
+ function listDivergingFiles(a, b) {
2607
+ return runNameStatusDiff(a, b, parseDiffOutput);
2608
+ }
2609
+ function listDivergingModified(a, b) {
2610
+ return runNameStatusDiff(a, b, (stdout) => parseModifiedPaths(stdout, a));
2611
+ }
2567
2612
 
2568
2613
  // src/commands.doctor.checks.skills.ts
2569
2614
  function stripSideIndicator(line) {
@@ -2573,15 +2618,15 @@ function stripSideIndicator(line) {
2573
2618
  }
2574
2619
  function isGsdDiffLine(line, localBase, sharedBase) {
2575
2620
  const bare = stripSideIndicator(line);
2576
- let relative7;
2621
+ let relative9;
2577
2622
  if (bare.startsWith(localBase + "/")) {
2578
- relative7 = bare.slice(localBase.length + 1);
2623
+ relative9 = bare.slice(localBase.length + 1);
2579
2624
  } else if (bare.startsWith(sharedBase + "/")) {
2580
- relative7 = bare.slice(sharedBase.length + 1);
2625
+ relative9 = bare.slice(sharedBase.length + 1);
2581
2626
  } else {
2582
- relative7 = bare;
2627
+ relative9 = bare;
2583
2628
  }
2584
- return relative7.split("/")[0].startsWith(GSD_PREFIX);
2629
+ return relative9.split("/")[0].startsWith(GSD_PREFIX);
2585
2630
  }
2586
2631
  function reportSkillsDivergence(section2) {
2587
2632
  const sharedSkills = join14(repoHome(), "shared", "skills");
@@ -2614,7 +2659,7 @@ init_color();
2614
2659
  init_config();
2615
2660
  import { execFileSync as execFileSync5 } from "node:child_process";
2616
2661
  import { existsSync as existsSync15 } from "node:fs";
2617
- import { join as join18, relative as relative2 } from "node:path";
2662
+ import { join as join18, relative as relative3 } from "node:path";
2618
2663
  init_commands_pull_wedge();
2619
2664
  init_push_checks();
2620
2665
  init_utils();
@@ -2641,7 +2686,7 @@ function reportGitlinks(section2) {
2641
2686
  if (existsSync15(sharedDir)) {
2642
2687
  const gitlinks = findGitlinks(sharedDir);
2643
2688
  for (const p of gitlinks) {
2644
- const rel = relative2(repo, p);
2689
+ const rel = relative3(repo, p);
2645
2690
  addItem(
2646
2691
  section2,
2647
2692
  `${red(failGlyph)} gitlink: ${blue(rel)} would push as submodule (run: rm -rf ${rel} or remove the nested repo)`
@@ -2918,9 +2963,9 @@ function reportCheckSchema(section2) {
2918
2963
  init_color();
2919
2964
  import { randomBytes } from "node:crypto";
2920
2965
  import { execFileSync as execFileSync9 } from "node:child_process";
2921
- import { existsSync as existsSync20, mkdirSync as mkdirSync6, readdirSync as readdirSync9, rmSync as rmSync8 } from "node:fs";
2966
+ import { existsSync as existsSync21, mkdirSync as mkdirSync6, readdirSync as readdirSync10, rmSync as rmSync9 } from "node:fs";
2922
2967
  import { homedir as homedir4 } from "node:os";
2923
- import { join as join25 } from "node:path";
2968
+ import { join as join26 } from "node:path";
2924
2969
 
2925
2970
  // src/commands.doctor.check-shared.scan.ts
2926
2971
  init_color();
@@ -3017,8 +3062,22 @@ init_config();
3017
3062
 
3018
3063
  // src/remap.ts
3019
3064
  init_config_sharedDirs_guard();
3020
- import { cpSync as cpSync4, existsSync as existsSync19, mkdirSync as mkdirSync5, readdirSync as readdirSync8, renameSync as renameSync3, rmSync as rmSync7, statSync as statSync5 } from "node:fs";
3021
- import { dirname as dirname4, join as join24, relative as relative3, sep as sep3 } from "node:path";
3065
+ import {
3066
+ cpSync as cpSync5,
3067
+ existsSync as existsSync20,
3068
+ lstatSync as lstatSync9,
3069
+ mkdirSync as mkdirSync5,
3070
+ readdirSync as readdirSync9,
3071
+ renameSync as renameSync3,
3072
+ rmSync as rmSync8,
3073
+ statSync as statSync5
3074
+ } from "node:fs";
3075
+ import { dirname as dirname4, join as join25, relative as relative5, sep as sep3 } from "node:path";
3076
+
3077
+ // src/extras-sync.core.ts
3078
+ init_config();
3079
+ import { cpSync as cpSync4, existsSync as existsSync18, lstatSync as lstatSync8, readdirSync as readdirSync7, readFileSync as readFileSync6, rmSync as rmSync7 } from "node:fs";
3080
+ import { basename, join as join23, relative as relative4 } from "node:path";
3022
3081
 
3023
3082
  // src/extras-sync.guards.ts
3024
3083
  init_utils();
@@ -3037,6 +3096,180 @@ function assertSafeLocalRoot(localRoot, logical) {
3037
3096
  }
3038
3097
  }
3039
3098
 
3099
+ // src/extras-sync.core.ts
3100
+ init_utils();
3101
+ init_utils_json();
3102
+ function loadValidatedExtras(opts) {
3103
+ const repo = repoHome();
3104
+ const mapPath = join23(repo, "path-map.json");
3105
+ const repoExtras = join23(repo, "shared", "extras");
3106
+ if (!existsSync18(mapPath) || opts.requireRepoExtras === true && !existsSync18(repoExtras)) {
3107
+ if (opts.missingMsg !== void 0) log(opts.missingMsg);
3108
+ return null;
3109
+ }
3110
+ const map = readPathMap(mapPath);
3111
+ const extrasMap = map.extras ?? {};
3112
+ if (Object.keys(extrasMap).length === 0) return null;
3113
+ for (const logical of Object.keys(extrasMap)) {
3114
+ assertSafeLogical(logical);
3115
+ const localRoot = map.projects[logical]?.[HOST];
3116
+ if (localRoot && localRoot !== "TBD") assertSafeLocalRoot(localRoot, logical);
3117
+ }
3118
+ return { map, extrasMap };
3119
+ }
3120
+ function* eachExtrasTarget(v, counts) {
3121
+ const whitelist = SUPPORTED_EXTRAS;
3122
+ for (const [logical, dirnames] of Object.entries(v.extrasMap)) {
3123
+ const localRoot = v.map.projects[logical]?.[HOST];
3124
+ if (!localRoot || localRoot === "TBD") {
3125
+ counts.unmapped++;
3126
+ continue;
3127
+ }
3128
+ for (const dirname10 of dirnames) {
3129
+ if (!whitelist.includes(dirname10)) {
3130
+ counts.skipped++;
3131
+ continue;
3132
+ }
3133
+ yield { logical, localRoot, dirname: dirname10 };
3134
+ }
3135
+ }
3136
+ }
3137
+ function stripCollidingDstSymlinks(src, dst, isExcluded) {
3138
+ if (!existsSync18(dst)) return;
3139
+ for (const name of readdirSync7(src)) {
3140
+ if (isExcluded(name)) continue;
3141
+ const dstPath = join23(dst, name);
3142
+ const dstStat = lstatSync8(dstPath, { throwIfNoEntry: false });
3143
+ if (dstStat === void 0) continue;
3144
+ if (dstStat.isSymbolicLink()) {
3145
+ rmSync7(dstPath, { recursive: true, force: true });
3146
+ } else if (dstStat.isDirectory() && lstatSync8(join23(src, name)).isDirectory()) {
3147
+ stripCollidingDstSymlinks(join23(src, name), dstPath, isExcluded);
3148
+ }
3149
+ }
3150
+ }
3151
+ function cpSyncGuarded(src, dst, filter, label) {
3152
+ try {
3153
+ cpSync4(src, dst, { recursive: true, force: true, verbatimSymlinks: true, filter });
3154
+ } catch (err) {
3155
+ const e = err;
3156
+ if (e.code === "EINVAL" || e.code === "ENOTEMPTY" || e.code === "ERR_FS_CP_NON_DIR_TO_DIR" || e.code === "ERR_FS_CP_DIR_TO_NON_DIR") {
3157
+ throw new NomadFatal(
3158
+ `${label}: type collision copying ${JSON.stringify(src)} -> ${JSON.stringify(dst)} (${e.path ?? "unknown path"}): a file/directory type changed upstream; run nomad pull --force-remote to recover`
3159
+ );
3160
+ }
3161
+ throw err;
3162
+ }
3163
+ }
3164
+ function copyExtrasOverlayFiltered(src, dst, blockSet) {
3165
+ stripCollidingDstSymlinks(src, dst, (name) => isDeniedName(blockSet, name));
3166
+ cpSyncGuarded(
3167
+ src,
3168
+ dst,
3169
+ (srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename(srcEntry)),
3170
+ "copyExtrasOverlayFiltered"
3171
+ );
3172
+ }
3173
+ function copyExtrasOverlaySkipDiverged(src, dst, blockSet, divergedSet) {
3174
+ stripCollidingDstSymlinks(src, dst, (name) => isDeniedName(blockSet, name));
3175
+ cpSyncGuarded(
3176
+ src,
3177
+ dst,
3178
+ (srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename(srcEntry)) && !divergedSet.has(relative4(src, srcEntry)),
3179
+ "copyExtrasOverlaySkipDiverged"
3180
+ );
3181
+ }
3182
+ function copyExtras(src, dst) {
3183
+ rmSync7(dst, { recursive: true, force: true });
3184
+ cpSync4(src, dst, { recursive: true, force: true, verbatimSymlinks: true });
3185
+ }
3186
+ function copyExtrasFileSkipDiverged(src, dst) {
3187
+ if (existsSync18(dst)) {
3188
+ let dstBuf;
3189
+ try {
3190
+ dstBuf = readFileSync6(dst);
3191
+ } catch {
3192
+ return;
3193
+ }
3194
+ if (!readFileSync6(src).equals(dstBuf)) return;
3195
+ }
3196
+ copyExtras(src, dst);
3197
+ }
3198
+ function extrasDenySet(dirname10) {
3199
+ return dirname10 === ".claude" ? CLAUDE_EXTRA_NEVER_SYNC : ALWAYS_NEVER_SYNC;
3200
+ }
3201
+ function copyExtrasFiltered(src, dst, blockSet) {
3202
+ rmSync7(dst, { recursive: true, force: true });
3203
+ cpSync4(src, dst, {
3204
+ recursive: true,
3205
+ force: true,
3206
+ verbatimSymlinks: true,
3207
+ filter: (srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename(srcEntry))
3208
+ });
3209
+ }
3210
+ function prunePreservingDenied(src, dst, blockSet) {
3211
+ for (const name of readdirSync7(dst)) {
3212
+ if (isDeniedName(blockSet, name)) continue;
3213
+ const dstPath = join23(dst, name);
3214
+ const srcStat = lstatSync8(join23(src, name), { throwIfNoEntry: false });
3215
+ if (srcStat === void 0) {
3216
+ rmSync7(dstPath, { recursive: true, force: true });
3217
+ continue;
3218
+ }
3219
+ const dstStat = lstatSync8(dstPath);
3220
+ if (srcStat.isDirectory() && dstStat.isDirectory()) {
3221
+ prunePreservingDenied(join23(src, name), dstPath, blockSet);
3222
+ } else if (srcStat.isDirectory() !== dstStat.isDirectory()) {
3223
+ rmSync7(dstPath, { recursive: true, force: true });
3224
+ }
3225
+ }
3226
+ }
3227
+ function copyExtrasFilteredPreserving(src, dst, blockSet) {
3228
+ const dstStat = lstatSync8(dst, { throwIfNoEntry: false });
3229
+ if (dstStat !== void 0) {
3230
+ if (dstStat.isDirectory()) prunePreservingDenied(src, dst, blockSet);
3231
+ else rmSync7(dst, { recursive: true, force: true });
3232
+ }
3233
+ stripCollidingDstSymlinks(src, dst, (name) => isDeniedName(blockSet, name));
3234
+ cpSync4(src, dst, {
3235
+ recursive: true,
3236
+ force: true,
3237
+ verbatimSymlinks: true,
3238
+ filter: (srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename(srcEntry))
3239
+ });
3240
+ }
3241
+ function prunePreservingBy(src, dst, isPreserved) {
3242
+ for (const name of readdirSync7(dst)) {
3243
+ if (isPreserved(name)) continue;
3244
+ const dstPath = join23(dst, name);
3245
+ const srcStat = lstatSync8(join23(src, name), { throwIfNoEntry: false });
3246
+ if (srcStat === void 0) {
3247
+ rmSync7(dstPath, { recursive: true, force: true });
3248
+ continue;
3249
+ }
3250
+ const dstStat = lstatSync8(dstPath);
3251
+ if (srcStat.isDirectory() && dstStat.isDirectory()) {
3252
+ prunePreservingBy(join23(src, name), dstPath, isPreserved);
3253
+ } else if (srcStat.isDirectory() !== dstStat.isDirectory()) {
3254
+ rmSync7(dstPath, { recursive: true, force: true });
3255
+ }
3256
+ }
3257
+ }
3258
+ function copyExtrasFilteredPreservingBy(src, dst, isPreserved) {
3259
+ const dstStat = lstatSync8(dst, { throwIfNoEntry: false });
3260
+ if (dstStat !== void 0) {
3261
+ if (dstStat.isDirectory()) prunePreservingBy(src, dst, isPreserved);
3262
+ else rmSync7(dst, { recursive: true, force: true });
3263
+ }
3264
+ stripCollidingDstSymlinks(src, dst, isPreserved);
3265
+ cpSync4(src, dst, {
3266
+ recursive: true,
3267
+ force: true,
3268
+ verbatimSymlinks: true,
3269
+ filter: (srcEntry) => srcEntry === src || !isPreserved(basename(srcEntry))
3270
+ });
3271
+ }
3272
+
3040
3273
  // src/remap.ts
3041
3274
  init_config();
3042
3275
 
@@ -3045,8 +3278,8 @@ init_config();
3045
3278
  init_push_gitleaks_config();
3046
3279
  init_utils_fs();
3047
3280
  import { createHash } from "node:crypto";
3048
- import { existsSync as existsSync18, mkdirSync as mkdirSync4, readdirSync as readdirSync7, readFileSync as readFileSync6, statSync as statSync4 } from "node:fs";
3049
- import { dirname as dirname3, join as join23 } from "node:path";
3281
+ import { existsSync as existsSync19, mkdirSync as mkdirSync4, readdirSync as readdirSync8, readFileSync as readFileSync7, statSync as statSync4 } from "node:fs";
3282
+ import { dirname as dirname3, join as join24 } from "node:path";
3050
3283
  function isChanged(prev, cur, hash) {
3051
3284
  if (prev === void 0) return true;
3052
3285
  if (prev.size !== cur.size) return true;
@@ -3083,11 +3316,11 @@ function shouldFullRescan(old, scannerVersion, configHash, forceFlag) {
3083
3316
  return false;
3084
3317
  }
3085
3318
  function hashFile(absPath) {
3086
- return createHash("sha256").update(readFileSync6(absPath)).digest("hex");
3319
+ return createHash("sha256").update(readFileSync7(absPath)).digest("hex");
3087
3320
  }
3088
3321
  function enumerateDir(dir, results) {
3089
- for (const entry of readdirSync7(dir)) {
3090
- const fullPath = join23(dir, entry);
3322
+ for (const entry of readdirSync8(dir)) {
3323
+ const fullPath = join24(dir, entry);
3091
3324
  const st = statSync4(fullPath);
3092
3325
  if (st.isDirectory()) {
3093
3326
  enumerateDir(fullPath, results);
@@ -3098,8 +3331,8 @@ function enumerateDir(dir, results) {
3098
3331
  }
3099
3332
  function enumerateSourceFiles(projectDir) {
3100
3333
  const results = [];
3101
- for (const entry of readdirSync7(projectDir)) {
3102
- const fullPath = join23(projectDir, entry);
3334
+ for (const entry of readdirSync8(projectDir)) {
3335
+ const fullPath = join24(projectDir, entry);
3103
3336
  const st = statSync4(fullPath);
3104
3337
  if (st.isDirectory()) {
3105
3338
  enumerateDir(fullPath, results);
@@ -3111,15 +3344,15 @@ function enumerateSourceFiles(projectDir) {
3111
3344
  }
3112
3345
  function fileIdentity(p) {
3113
3346
  if (p === null) return "none::absent";
3114
- if (!existsSync18(p)) return `${p}::absent`;
3115
- return `${p}::${createHash("sha256").update(readFileSync6(p)).digest("hex")}`;
3347
+ if (!existsSync19(p)) return `${p}::absent`;
3348
+ return `${p}::${createHash("sha256").update(readFileSync7(p)).digest("hex")}`;
3116
3349
  }
3117
3350
  function computeConfigHash() {
3118
3351
  const repo = repoHome();
3119
3352
  const parts = [
3120
3353
  fileIdentity(resolveTomlPath(repo)),
3121
- fileIdentity(join23(repo, ".gitleaks.overlay.toml")),
3122
- fileIdentity(join23(repo, ".gitleaksignore"))
3354
+ fileIdentity(join24(repo, ".gitleaks.overlay.toml")),
3355
+ fileIdentity(join24(repo, ".gitleaksignore"))
3123
3356
  ];
3124
3357
  return createHash("sha256").update(parts.join("\n")).digest("hex");
3125
3358
  }
@@ -3130,7 +3363,7 @@ function isManifestShape(raw) {
3130
3363
  }
3131
3364
  function readManifest(path) {
3132
3365
  try {
3133
- const raw = readFileSync6(path, "utf8");
3366
+ const raw = readFileSync7(path, "utf8");
3134
3367
  const parsed = JSON.parse(raw);
3135
3368
  if (!isManifestShape(parsed)) return null;
3136
3369
  return parsed;
@@ -3153,18 +3386,19 @@ init_utils_json();
3153
3386
  var TMP_SUFFIX = ".nomad-tmp";
3154
3387
  function atomicMirror(src, dst, options) {
3155
3388
  const tmp = `${dst}${TMP_SUFFIX}`;
3156
- rmSync7(tmp, { recursive: true, force: true });
3157
- cpSync4(src, tmp, options);
3158
- rmSync7(dst, { recursive: true, force: true });
3389
+ rmSync8(tmp, { recursive: true, force: true });
3390
+ cpSync5(src, tmp, options);
3391
+ rmSync8(dst, { recursive: true, force: true });
3159
3392
  renameSync3(tmp, dst);
3160
3393
  }
3161
- function copyDir(src, dst) {
3162
- atomicMirror(src, dst, { recursive: true, force: true });
3394
+ function overlaySessionDir(src, dst) {
3395
+ stripCollidingDstSymlinks(src, dst, () => false);
3396
+ cpSyncGuarded(src, dst, void 0, "overlaySessionDir");
3163
3397
  }
3164
3398
  function copyFileAtomic(src, dst) {
3165
3399
  mkdirSync5(dirname4(dst), { recursive: true });
3166
3400
  const tmp = `${dst}.tmp.${process.pid}`;
3167
- cpSync4(src, tmp, { force: true, preserveTimestamps: true });
3401
+ cpSync5(src, tmp, { force: true, preserveTimestamps: true });
3168
3402
  renameSync3(tmp, dst);
3169
3403
  }
3170
3404
  function hasDeltaForDir(sel, localDir) {
@@ -3180,12 +3414,12 @@ function applySelective(sel, localDir, repoDst) {
3180
3414
  const prefix = `${localDir}${sep3}`;
3181
3415
  for (const src of sel.changed) {
3182
3416
  if (!src.startsWith(prefix)) continue;
3183
- copyFileAtomic(src, join24(repoDst, relative3(localDir, src)));
3417
+ copyFileAtomic(src, join25(repoDst, relative5(localDir, src)));
3184
3418
  }
3185
3419
  for (const src of sel.deleted) {
3186
3420
  if (!src.startsWith(prefix)) continue;
3187
- const dst = join24(repoDst, relative3(localDir, src));
3188
- if (existsSync19(dst)) rmSync7(dst);
3421
+ const dst = join25(repoDst, relative5(localDir, src));
3422
+ if (existsSync20(dst)) rmSync8(dst);
3189
3423
  }
3190
3424
  }
3191
3425
  function copyDirJsonlOnly(src, dst) {
@@ -3193,7 +3427,7 @@ function copyDirJsonlOnly(src, dst) {
3193
3427
  recursive: true,
3194
3428
  force: true,
3195
3429
  filter: (srcPath) => {
3196
- const rel = relative3(src, srcPath);
3430
+ const rel = relative5(src, srcPath);
3197
3431
  if (rel === "") return true;
3198
3432
  if (rel.split(sep3).length > 1) return true;
3199
3433
  if (statSync5(srcPath).isDirectory()) return true;
@@ -3214,15 +3448,15 @@ function remapPull(ts, opts = {}) {
3214
3448
  const wouldPull = [];
3215
3449
  const repo = repoHome();
3216
3450
  const claude = claudeHome();
3217
- const mapPath = join24(repo, "path-map.json");
3218
- const repoProjects = join24(repo, "shared", "projects");
3219
- if (!existsSync19(mapPath) || !existsSync19(repoProjects)) {
3451
+ const mapPath = join25(repo, "path-map.json");
3452
+ const repoProjects = join25(repo, "shared", "projects");
3453
+ if (!existsSync20(mapPath) || !existsSync20(repoProjects)) {
3220
3454
  const text = "no path-map or repo projects dir; skipping session remap";
3221
3455
  emitPreview(opts.onPreview, { kind: "note", text }, text);
3222
3456
  return { unmapped: 0, pulled, wouldPull };
3223
3457
  }
3224
3458
  const map = readPathMap(mapPath);
3225
- const localProjects = join24(claude, "projects");
3459
+ const localProjects = join25(claude, "projects");
3226
3460
  if (!dryRun) mkdirSync5(localProjects, { recursive: true });
3227
3461
  for (const [logical, hosts] of Object.entries(map.projects)) {
3228
3462
  assertSafeLogical(logical);
@@ -3232,9 +3466,9 @@ function remapPull(ts, opts = {}) {
3232
3466
  continue;
3233
3467
  }
3234
3468
  assertSafeLocalRoot(localPath, logical);
3235
- const src = join24(repoProjects, logical);
3236
- if (!existsSync19(src)) continue;
3237
- const dst = join24(localProjects, encodePath(localPath));
3469
+ const src = join25(repoProjects, logical);
3470
+ if (!existsSync20(src)) continue;
3471
+ const dst = join25(localProjects, encodePath(localPath));
3238
3472
  if (dryRun) {
3239
3473
  wouldPull.push(logical);
3240
3474
  emitPreview(
@@ -3245,11 +3479,44 @@ function remapPull(ts, opts = {}) {
3245
3479
  continue;
3246
3480
  }
3247
3481
  backupBeforeWrite(dst, ts);
3248
- copyDir(src, dst);
3482
+ overlaySessionDir(src, dst);
3249
3483
  pulled.push(logical);
3250
3484
  }
3251
3485
  return { unmapped, pulled, wouldPull };
3252
3486
  }
3487
+ function countLocalOnly(src, dst) {
3488
+ let count = 0;
3489
+ for (const name of readdirSync9(dst)) {
3490
+ const dstPath = join25(dst, name);
3491
+ const srcPath = join25(src, name);
3492
+ if (lstatSync9(dstPath).isDirectory()) {
3493
+ count += countLocalOnly(srcPath, dstPath);
3494
+ } else if (lstatSync9(srcPath, { throwIfNoEntry: false }) === void 0) {
3495
+ count++;
3496
+ }
3497
+ }
3498
+ return count;
3499
+ }
3500
+ function scanLocalOnly() {
3501
+ const repo = repoHome();
3502
+ const claude = claudeHome();
3503
+ const mapPath = join25(repo, "path-map.json");
3504
+ const repoProjects = join25(repo, "shared", "projects");
3505
+ if (!existsSync20(mapPath) || !existsSync20(repoProjects)) return 0;
3506
+ const map = readPathMap(mapPath);
3507
+ const localProjects = join25(claude, "projects");
3508
+ let count = 0;
3509
+ for (const [logical, hosts] of Object.entries(map.projects)) {
3510
+ assertSafeLogical(logical);
3511
+ const localPath = hosts[HOST];
3512
+ if (!localPath || localPath === "TBD") continue;
3513
+ assertSafeLocalRoot(localPath, logical);
3514
+ const dst = join25(localProjects, encodePath(localPath));
3515
+ if (!existsSync20(dst)) continue;
3516
+ count += countLocalOnly(join25(repoProjects, logical), dst);
3517
+ }
3518
+ return count;
3519
+ }
3253
3520
  function buildReverseMap(map) {
3254
3521
  const reverse = /* @__PURE__ */ new Map();
3255
3522
  const encodedPaths = /* @__PURE__ */ new Map();
@@ -3281,26 +3548,26 @@ function remapPush(ts, opts = {}) {
3281
3548
  const wouldPush = [];
3282
3549
  const repo = repoHome();
3283
3550
  const claude = claudeHome();
3284
- const mapPath = join24(repo, "path-map.json");
3285
- if (!existsSync19(mapPath)) {
3551
+ const mapPath = join25(repo, "path-map.json");
3552
+ if (!existsSync20(mapPath)) {
3286
3553
  log("no path-map.json; skipping session export");
3287
3554
  return { unmapped: 0, collisions: 0, pushed, wouldPush };
3288
3555
  }
3289
3556
  const map = readPathMap(mapPath);
3290
- const localProjects = join24(claude, "projects");
3291
- const repoProjects = join24(repo, "shared", "projects");
3557
+ const localProjects = join25(claude, "projects");
3558
+ const repoProjects = join25(repo, "shared", "projects");
3292
3559
  const reverse = buildReverseMap(map);
3293
- if (!existsSync19(localProjects)) return { unmapped, collisions: 0, pushed, wouldPush };
3560
+ if (!existsSync20(localProjects)) return { unmapped, collisions: 0, pushed, wouldPush };
3294
3561
  if (!dryRun) mkdirSync5(repoProjects, { recursive: true });
3295
- for (const dir of readdirSync8(localProjects)) {
3562
+ for (const dir of readdirSync9(localProjects)) {
3296
3563
  if (dir.endsWith(TMP_SUFFIX)) continue;
3297
3564
  const logical = reverse.get(dir);
3298
3565
  if (!logical) {
3299
3566
  unmapped++;
3300
3567
  continue;
3301
3568
  }
3302
- const localDir = join24(localProjects, dir);
3303
- const repoDst = join24(repoProjects, logical);
3569
+ const localDir = join25(localProjects, dir);
3570
+ const repoDst = join25(repoProjects, logical);
3304
3571
  if (skipForSelection(opts.selection, localDir)) continue;
3305
3572
  if (dryRun) {
3306
3573
  wouldPush.push(logical);
@@ -3324,8 +3591,8 @@ function buildScanTree(tmpRoot) {
3324
3591
  const logicalToEncoded = /* @__PURE__ */ new Map();
3325
3592
  let staged = 0;
3326
3593
  const repo = repoHome();
3327
- const mapPath = join25(repo, "path-map.json");
3328
- if (!existsSync20(mapPath)) return { logicalToEncoded, staged, malformed: false };
3594
+ const mapPath = join26(repo, "path-map.json");
3595
+ if (!existsSync21(mapPath)) return { logicalToEncoded, staged, malformed: false };
3329
3596
  let map;
3330
3597
  try {
3331
3598
  map = readJson(mapPath);
@@ -3342,12 +3609,12 @@ function buildScanTree(tmpRoot) {
3342
3609
  if (!p || p === "TBD") continue;
3343
3610
  reverse.set(encodePath(p), logical);
3344
3611
  }
3345
- const localProjects = join25(claudeHome(), "projects");
3346
- if (!existsSync20(localProjects)) return { logicalToEncoded, staged, malformed: false };
3347
- for (const dir of readdirSync9(localProjects)) {
3612
+ const localProjects = join26(claudeHome(), "projects");
3613
+ if (!existsSync21(localProjects)) return { logicalToEncoded, staged, malformed: false };
3614
+ for (const dir of readdirSync10(localProjects)) {
3348
3615
  const logical = reverse.get(dir);
3349
3616
  if (!logical) continue;
3350
- copyDirJsonlOnly(join25(localProjects, dir), join25(tmpRoot, "shared", "projects", logical));
3617
+ copyDirJsonlOnly(join26(localProjects, dir), join26(tmpRoot, "shared", "projects", logical));
3351
3618
  logicalToEncoded.set(logical, dir);
3352
3619
  staged++;
3353
3620
  }
@@ -3378,11 +3645,11 @@ function ensureGitleaksReady(section2, gitleaksReady) {
3378
3645
  }
3379
3646
  function reportCheckShared(section2, gitleaksReady) {
3380
3647
  if (!ensureGitleaksReady(section2, gitleaksReady)) return;
3381
- const cacheDir = join25(homedir4(), ".cache", "claude-nomad");
3648
+ const cacheDir = join26(homedir4(), ".cache", "claude-nomad");
3382
3649
  mkdirSync6(cacheDir, { recursive: true });
3383
3650
  const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes(4).toString("hex")}`;
3384
- const reportPath = join25(cacheDir, `check-shared-${stamp}.json`);
3385
- const tmpRoot = join25(cacheDir, `check-shared-tree-${stamp}`);
3651
+ const reportPath = join26(cacheDir, `check-shared-${stamp}.json`);
3652
+ const tmpRoot = join26(cacheDir, `check-shared-tree-${stamp}`);
3386
3653
  try {
3387
3654
  const { logicalToEncoded, staged, malformed } = buildScanTree(tmpRoot);
3388
3655
  if (malformed) {
@@ -3396,19 +3663,19 @@ function reportCheckShared(section2, gitleaksReady) {
3396
3663
  }
3397
3664
  scanAndReport(section2, tmpRoot, staged, logicalToEncoded);
3398
3665
  } finally {
3399
- rmSync8(reportPath, { force: true });
3400
- rmSync8(tmpRoot, { recursive: true, force: true });
3666
+ rmSync9(reportPath, { force: true });
3667
+ rmSync9(tmpRoot, { recursive: true, force: true });
3401
3668
  }
3402
3669
  }
3403
3670
 
3404
3671
  // src/commands.doctor.checks.hooks.scope.ts
3405
3672
  init_color();
3406
- import { existsSync as existsSync21, readFileSync as readFileSync7, readdirSync as readdirSync10, realpathSync as realpathSync2 } from "node:fs";
3407
- import { dirname as dirname5, extname, join as join26 } from "node:path";
3673
+ import { existsSync as existsSync22, readFileSync as readFileSync8, readdirSync as readdirSync11, realpathSync as realpathSync2 } from "node:fs";
3674
+ import { dirname as dirname5, extname, join as join27 } from "node:path";
3408
3675
  init_config();
3409
3676
  function typeFromPackageJson(pkgPath) {
3410
3677
  try {
3411
- const parsed = JSON.parse(readFileSync7(pkgPath, "utf8"));
3678
+ const parsed = JSON.parse(readFileSync8(pkgPath, "utf8"));
3412
3679
  return parsed.type === "module" ? "esm" : "cjs";
3413
3680
  } catch {
3414
3681
  return "cjs";
@@ -3426,8 +3693,8 @@ function effectiveType(hookPath) {
3426
3693
  }
3427
3694
  let dir = dirname5(real);
3428
3695
  for (; ; ) {
3429
- const pkg = join26(dir, "package.json");
3430
- if (existsSync21(pkg)) return typeFromPackageJson(pkg);
3696
+ const pkg = join27(dir, "package.json");
3697
+ if (existsSync22(pkg)) return typeFromPackageJson(pkg);
3431
3698
  const parent = dirname5(dir);
3432
3699
  if (parent === dir) return "cjs";
3433
3700
  dir = parent;
@@ -3456,28 +3723,28 @@ function remedy(family) {
3456
3723
  }
3457
3724
  function safeReaddir2(dir) {
3458
3725
  try {
3459
- return readdirSync10(dir);
3726
+ return readdirSync11(dir);
3460
3727
  } catch {
3461
3728
  return [];
3462
3729
  }
3463
3730
  }
3464
3731
  function safeRead(path) {
3465
3732
  try {
3466
- return readFileSync7(path, "utf8");
3733
+ return readFileSync8(path, "utf8");
3467
3734
  } catch {
3468
3735
  return null;
3469
3736
  }
3470
3737
  }
3471
3738
  function reportHookScopeCheck(section2) {
3472
- const hooksDir = join26(claudeHome(), "hooks");
3473
- if (!existsSync21(hooksDir)) {
3739
+ const hooksDir = join27(claudeHome(), "hooks");
3740
+ if (!existsSync22(hooksDir)) {
3474
3741
  addItem(section2, `${dim(infoGlyph)} no ~/.claude/hooks; skipping module-scope check`);
3475
3742
  return;
3476
3743
  }
3477
3744
  let anyWarn = false;
3478
3745
  for (const name of safeReaddir2(hooksDir)) {
3479
3746
  if (extname(name) !== ".js") continue;
3480
- const abs = join26(hooksDir, name);
3747
+ const abs = join27(hooksDir, name);
3481
3748
  const eff = effectiveType(abs);
3482
3749
  if (eff === null) continue;
3483
3750
  const src = safeRead(abs);
@@ -3497,8 +3764,8 @@ function reportHookScopeCheck(section2) {
3497
3764
 
3498
3765
  // src/commands.doctor.checks.hooks.ts
3499
3766
  init_color();
3500
- import { existsSync as existsSync22 } from "node:fs";
3501
- import { join as join27 } from "node:path";
3767
+ import { existsSync as existsSync23 } from "node:fs";
3768
+ import { join as join28 } from "node:path";
3502
3769
  init_config();
3503
3770
  function expandHome(token) {
3504
3771
  const h2 = home();
@@ -3541,7 +3808,7 @@ function checkEventGroups(section2, event, groups) {
3541
3808
  for (const group of groups) {
3542
3809
  for (const cmd of commandsFromGroup(group)) {
3543
3810
  for (const resolved of claudePathsIn(cmd)) {
3544
- if (existsSync22(resolved)) continue;
3811
+ if (existsSync23(resolved)) continue;
3545
3812
  addItem(
3546
3813
  section2,
3547
3814
  `${red(failGlyph)} hooks/${event}: command target missing: ${resolved} (run nomad pull)`
@@ -3554,8 +3821,8 @@ function checkEventGroups(section2, event, groups) {
3554
3821
  return anyFail;
3555
3822
  }
3556
3823
  function reportHooksTargetCheck(section2) {
3557
- const settingsPath = join27(claudeHome(), "settings.json");
3558
- if (!existsSync22(settingsPath)) {
3824
+ const settingsPath = join28(claudeHome(), "settings.json");
3825
+ if (!existsSync23(settingsPath)) {
3559
3826
  addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json; skipping hook target check`);
3560
3827
  return;
3561
3828
  }
@@ -3578,12 +3845,12 @@ function reportHooksTargetCheck(section2) {
3578
3845
 
3579
3846
  // src/commands.doctor.checks.hooks.preserve-symlinks.ts
3580
3847
  init_color();
3581
- import { existsSync as existsSync24, readFileSync as readFileSync8 } from "node:fs";
3582
- import { join as join29 } from "node:path";
3848
+ import { existsSync as existsSync25, readFileSync as readFileSync9 } from "node:fs";
3849
+ import { join as join30 } from "node:path";
3583
3850
 
3584
3851
  // src/commands.doctor.checks.hooks.preserve-symlinks.probe.ts
3585
- import { closeSync as closeSync3, existsSync as existsSync23, openSync as openSync3, readSync, realpathSync as realpathSync3 } from "node:fs";
3586
- import { dirname as dirname6, join as join28, resolve as resolve2 } from "node:path";
3852
+ import { closeSync as closeSync3, existsSync as existsSync24, openSync as openSync3, readSync, realpathSync as realpathSync3 } from "node:fs";
3853
+ import { dirname as dirname6, join as join29, resolve as resolve2 } from "node:path";
3587
3854
  function suppressedRanges(src) {
3588
3855
  const ranges = [];
3589
3856
  const re = /\/\*[\s\S]*?\*\/|\/\/[^\n]*|'[^']*'|"[^"]*"|`[^`]*`/g;
@@ -3615,12 +3882,12 @@ function topRelativeSpecifiers(src) {
3615
3882
  }
3616
3883
  function specifierIsMissing(specifier, baseDir) {
3617
3884
  const base = resolve2(baseDir, specifier);
3618
- if (existsSync23(base)) return false;
3885
+ if (existsSync24(base)) return false;
3619
3886
  for (const ext of [".js", ".cjs", ".mjs"]) {
3620
- if (existsSync23(base + ext)) return false;
3887
+ if (existsSync24(base + ext)) return false;
3621
3888
  }
3622
3889
  for (const idx of ["index.js", "index.cjs", "index.mjs"]) {
3623
- if (existsSync23(join28(base, idx))) return false;
3890
+ if (existsSync24(join29(base, idx))) return false;
3624
3891
  }
3625
3892
  return true;
3626
3893
  }
@@ -3675,10 +3942,10 @@ function commandTokens(command) {
3675
3942
  return tokens;
3676
3943
  }
3677
3944
  function readPathMapSafe() {
3678
- const mapPath = join29(repoHome(), "path-map.json");
3679
- if (!existsSync24(mapPath)) return { projects: {} };
3945
+ const mapPath = join30(repoHome(), "path-map.json");
3946
+ if (!existsSync25(mapPath)) return { projects: {} };
3680
3947
  try {
3681
- return JSON.parse(readFileSync8(mapPath, "utf8"));
3948
+ return JSON.parse(readFileSync9(mapPath, "utf8"));
3682
3949
  } catch {
3683
3950
  return { projects: {} };
3684
3951
  }
@@ -3749,8 +4016,8 @@ function checkEventForPreserveSymlinks(section2, event, groups, sharedLinkNames)
3749
4016
  return anyWarn;
3750
4017
  }
3751
4018
  function reportPreserveSymlinksCheck(section2) {
3752
- const settingsPath = join29(claudeHome(), "settings.json");
3753
- if (!existsSync24(settingsPath)) {
4019
+ const settingsPath = join30(claudeHome(), "settings.json");
4020
+ if (!existsSync25(settingsPath)) {
3754
4021
  addItem(
3755
4022
  section2,
3756
4023
  `${dim(infoGlyph)} no ~/.claude/settings.json; skipping preserve-symlinks-main check`
@@ -3759,7 +4026,7 @@ function reportPreserveSymlinksCheck(section2) {
3759
4026
  }
3760
4027
  let settings;
3761
4028
  try {
3762
- settings = JSON.parse(readFileSync8(settingsPath, "utf8"));
4029
+ settings = JSON.parse(readFileSync9(settingsPath, "utf8"));
3763
4030
  } catch {
3764
4031
  return;
3765
4032
  }
@@ -3783,8 +4050,8 @@ function reportPreserveSymlinksCheck(section2) {
3783
4050
 
3784
4051
  // src/commands.doctor.checks.settings-drift.ts
3785
4052
  init_color();
3786
- import { existsSync as existsSync25, readFileSync as readFileSync9 } from "node:fs";
3787
- import { join as join30 } from "node:path";
4053
+ import { existsSync as existsSync26, readFileSync as readFileSync10 } from "node:fs";
4054
+ import { join as join31 } from "node:path";
3788
4055
  init_config();
3789
4056
  init_utils_json();
3790
4057
  function diffMergedSettings(merged, settings) {
@@ -3793,7 +4060,7 @@ function diffMergedSettings(merged, settings) {
3793
4060
  }
3794
4061
  function tryReadJson(filePath) {
3795
4062
  try {
3796
- const raw = readFileSync9(filePath, "utf8");
4063
+ const raw = readFileSync10(filePath, "utf8");
3797
4064
  const parsed = JSON.parse(raw);
3798
4065
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
3799
4066
  return parsed;
@@ -3802,7 +4069,7 @@ function tryReadJson(filePath) {
3802
4069
  }
3803
4070
  }
3804
4071
  function reportHooksBaseSelfCleanNote(section2) {
3805
- const basePath = join30(repoHome(), "shared", "settings.base.json");
4072
+ const basePath = join31(repoHome(), "shared", "settings.base.json");
3806
4073
  const base = tryReadJson(basePath);
3807
4074
  if (base === null) return;
3808
4075
  if (!baseHasGsdHookEntries(base)) return;
@@ -3815,14 +4082,14 @@ function reportSettingsDriftCheck(section2) {
3815
4082
  const claude = claudeHome();
3816
4083
  const repo = repoHome();
3817
4084
  const host = HOST;
3818
- const settingsPath = join30(claude, "settings.json");
3819
- const basePath = join30(repo, "shared", "settings.base.json");
3820
- const hostPath = join30(repo, "hosts", `${host}.json`);
3821
- if (!existsSync25(settingsPath)) {
4085
+ const settingsPath = join31(claude, "settings.json");
4086
+ const basePath = join31(repo, "shared", "settings.base.json");
4087
+ const hostPath = join31(repo, "hosts", `${host}.json`);
4088
+ if (!existsSync26(settingsPath)) {
3822
4089
  addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json; skipping merge-drift check`);
3823
4090
  return;
3824
4091
  }
3825
- if (!existsSync25(basePath)) {
4092
+ if (!existsSync26(basePath)) {
3826
4093
  addItem(
3827
4094
  section2,
3828
4095
  `${dim(infoGlyph)} shared/settings.base.json missing; skipping merge-drift check`
@@ -3841,7 +4108,7 @@ function reportSettingsDriftCheck(section2) {
3841
4108
  if (settings === null) {
3842
4109
  return;
3843
4110
  }
3844
- const hostExists = existsSync25(hostPath);
4111
+ const hostExists = existsSync26(hostPath);
3845
4112
  const hostObj = hostExists ? tryReadJson(hostPath) : null;
3846
4113
  if (hostExists && hostObj === null) {
3847
4114
  addItem(
@@ -3890,12 +4157,12 @@ init_config();
3890
4157
 
3891
4158
  // src/commands.doctor.engine.ts
3892
4159
  init_color();
3893
- import { readFileSync as readFileSync11 } from "node:fs";
4160
+ import { readFileSync as readFileSync12 } from "node:fs";
3894
4161
  import { fileURLToPath as fileURLToPath3 } from "node:url";
3895
4162
 
3896
4163
  // src/commands.doctor.version.ts
3897
4164
  init_color();
3898
- import { readFileSync as readFileSync10 } from "node:fs";
4165
+ import { readFileSync as readFileSync11 } from "node:fs";
3899
4166
  import { fileURLToPath as fileURLToPath2 } from "node:url";
3900
4167
  init_config();
3901
4168
  var STRICT_SEMVER = /^\d+\.\d+\.\d+$/;
@@ -3912,7 +4179,7 @@ function compareSemver(a, b) {
3912
4179
  function readLocalVersion() {
3913
4180
  try {
3914
4181
  const pkgPath = fileURLToPath2(new URL("../package.json", import.meta.url));
3915
- const parsed = JSON.parse(readFileSync10(pkgPath, "utf8"));
4182
+ const parsed = JSON.parse(readFileSync11(pkgPath, "utf8"));
3916
4183
  if (typeof parsed.version === "string" && parsed.version.length > 0) {
3917
4184
  return parsed.version;
3918
4185
  }
@@ -3971,7 +4238,7 @@ function parseMinVersion(spec) {
3971
4238
  function readEnginesNode() {
3972
4239
  try {
3973
4240
  const pkgPath = fileURLToPath3(new URL("../package.json", import.meta.url));
3974
- const parsed = JSON.parse(readFileSync11(pkgPath, "utf8"));
4241
+ const parsed = JSON.parse(readFileSync12(pkgPath, "utf8"));
3975
4242
  const node = parsed.engines?.node;
3976
4243
  if (typeof node === "string" && node.length > 0) return node;
3977
4244
  return null;
@@ -3999,44 +4266,44 @@ function reportNodeEngineCheck(section2) {
3999
4266
 
4000
4267
  // src/spinner.ts
4001
4268
  init_color();
4002
- import { existsSync as existsSync29 } from "node:fs";
4269
+ import { existsSync as existsSync30 } from "node:fs";
4003
4270
  import { fileURLToPath as fileURLToPath4 } from "node:url";
4004
4271
  import { Worker } from "node:worker_threads";
4005
4272
 
4006
4273
  // src/commands.push.recovery.ts
4007
4274
  init_config();
4008
- import { readFileSync as readFileSync14, rmSync as rmSync10, writeFileSync as writeFileSync5 } from "node:fs";
4009
- import { join as join35 } from "node:path";
4275
+ import { readFileSync as readFileSync15, rmSync as rmSync11, writeFileSync as writeFileSync5 } from "node:fs";
4276
+ import { join as join36 } from "node:path";
4010
4277
  import { createInterface as createInterface2 } from "node:readline/promises";
4011
4278
 
4012
4279
  // src/commands.push.recovery.actions.ts
4013
4280
  init_config();
4014
- import { readFileSync as readFileSync13 } from "node:fs";
4281
+ import { readFileSync as readFileSync14 } from "node:fs";
4015
4282
  import { isAbsolute as isAbsolute2, resolve as resolve3, sep as sep5 } from "node:path";
4016
4283
 
4017
4284
  // src/commands.push.recovery.redact.ts
4018
4285
  init_config();
4019
4286
  init_config_sharedDirs_guard();
4020
- import { cpSync as cpSync5, existsSync as existsSync28, mkdirSync as mkdirSync7, statSync as statSync8 } from "node:fs";
4021
- import { dirname as dirname8, join as join33, sep as sep4 } from "node:path";
4287
+ import { cpSync as cpSync6, existsSync as existsSync29, mkdirSync as mkdirSync7, statSync as statSync8 } from "node:fs";
4288
+ import { dirname as dirname8, join as join34, sep as sep4 } from "node:path";
4022
4289
 
4023
4290
  // src/commands.redact.ts
4024
4291
  init_config();
4025
- import { existsSync as existsSync27, statSync as statSync7 } from "node:fs";
4026
- import { dirname as dirname7, join as join32 } from "node:path";
4292
+ import { existsSync as existsSync28, statSync as statSync7 } from "node:fs";
4293
+ import { dirname as dirname7, join as join33 } from "node:path";
4027
4294
 
4028
4295
  // src/commands.redact.subtree.ts
4029
- import { existsSync as existsSync26, lstatSync as lstatSync8, readFileSync as readFileSync12, readdirSync as readdirSync11, statSync as statSync6, writeFileSync as writeFileSync4 } from "node:fs";
4030
- import { join as join31 } from "node:path";
4296
+ import { existsSync as existsSync27, lstatSync as lstatSync10, readFileSync as readFileSync13, readdirSync as readdirSync12, statSync as statSync6, writeFileSync as writeFileSync4 } from "node:fs";
4297
+ import { join as join32 } from "node:path";
4031
4298
  init_utils_fs();
4032
4299
  init_utils();
4033
4300
  function collectFiles(dir, out) {
4034
- if (!existsSync26(dir)) return;
4035
- const st = lstatSync8(dir);
4301
+ if (!existsSync27(dir)) return;
4302
+ const st = lstatSync10(dir);
4036
4303
  if (!st.isDirectory()) return;
4037
- for (const entry of readdirSync11(dir)) {
4038
- const abs = join31(dir, entry);
4039
- const lst = lstatSync8(abs);
4304
+ for (const entry of readdirSync12(dir)) {
4305
+ const abs = join32(dir, entry);
4306
+ const lst = lstatSync10(abs);
4040
4307
  if (lst.isSymbolicLink()) continue;
4041
4308
  if (lst.isDirectory()) {
4042
4309
  collectFiles(abs, out);
@@ -4072,7 +4339,7 @@ function applySubtreeRedactions(mainPath, mainFindings, subtreeFiles, rule, ts,
4072
4339
  if (!dryRun && total > 0) {
4073
4340
  for (const { path: filePath, findings } of dirty) {
4074
4341
  backupBeforeWrite(filePath, ts);
4075
- const before = readFileSync12(filePath, "utf8");
4342
+ const before = readFileSync13(filePath, "utf8");
4076
4343
  const after = applyRedactions(before, findings);
4077
4344
  if (after === before) {
4078
4345
  log(
@@ -4140,15 +4407,15 @@ init_utils_json();
4140
4407
  init_utils();
4141
4408
  function resolveLiveTranscript(id) {
4142
4409
  try {
4143
- const mapPath = join32(repoHome(), "path-map.json");
4144
- if (!existsSync27(mapPath)) return null;
4410
+ const mapPath = join33(repoHome(), "path-map.json");
4411
+ if (!existsSync28(mapPath)) return null;
4145
4412
  const projects = readJson(mapPath).projects;
4146
4413
  const claude = claudeHome();
4147
4414
  for (const hostMap of Object.values(projects)) {
4148
4415
  const abs = hostMap[HOST];
4149
4416
  if (abs === void 0) continue;
4150
- const live = join32(claude, "projects", encodePath(abs), `${id}.jsonl`);
4151
- if (existsSync27(live)) return live;
4417
+ const live = join33(claude, "projects", encodePath(abs), `${id}.jsonl`);
4418
+ if (existsSync28(live)) return live;
4152
4419
  }
4153
4420
  return null;
4154
4421
  } catch {
@@ -4168,17 +4435,17 @@ function cmdRedact(opts, nowMs = Date.now, scan = scanFile) {
4168
4435
  }
4169
4436
  const repo = repoHome();
4170
4437
  const backup = backupBase();
4171
- if (!existsSync27(repo)) die(`repo not cloned at ${repo}`);
4438
+ if (!existsSync28(repo)) die(`repo not cloned at ${repo}`);
4172
4439
  const handle = acquireLock("redact");
4173
4440
  if (handle === null) process.exit(0);
4174
4441
  try {
4175
4442
  const localPath = resolveLiveTranscript(id);
4176
- if (localPath === null || !existsSync27(localPath)) {
4443
+ if (localPath === null || !existsSync28(localPath)) {
4177
4444
  fail(`could not resolve local transcript for session ${id} on this host`);
4178
4445
  process.exitCode = 1;
4179
4446
  return;
4180
4447
  }
4181
- const sessionDir = join32(dirname7(localPath), id);
4448
+ const sessionDir = join33(dirname7(localPath), id);
4182
4449
  const subtreeFiles = listSubtreeFiles(sessionDir);
4183
4450
  const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync7(p).mtimeMs);
4184
4451
  if (isRecentlyModified(subtreeMtime, nowMs())) {
@@ -4294,8 +4561,8 @@ function resolveStagedDir(localPath, map, claude, repo) {
4294
4561
  assertSafeLogical(logical);
4295
4562
  const abs = hostMap[HOST];
4296
4563
  if (abs === void 0) continue;
4297
- if (localPath.startsWith(join33(claude, "projects", encodePath(abs)) + sep4)) {
4298
- return join33(repo, "shared", "projects", logical);
4564
+ if (localPath.startsWith(join34(claude, "projects", encodePath(abs)) + sep4)) {
4565
+ return join34(repo, "shared", "projects", logical);
4299
4566
  }
4300
4567
  }
4301
4568
  return null;
@@ -4305,7 +4572,7 @@ function preflightRedactable(f, map, nowMs) {
4305
4572
  if (sid === null) return "a finding has no resolvable session id (not a session transcript)";
4306
4573
  const localPath = resolveLiveTranscript(sid);
4307
4574
  if (localPath === null) return `session ${sid}: local transcript not found`;
4308
- const sessionDir = join33(dirname8(localPath), sid);
4575
+ const sessionDir = join34(dirname8(localPath), sid);
4309
4576
  const subtreeFiles = listSubtreeFiles(sessionDir);
4310
4577
  const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync8(p).mtimeMs);
4311
4578
  if (isRecentlyModified(subtreeMtime, nowMs())) {
@@ -4335,7 +4602,7 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
4335
4602
  `could not locate the local transcript for session ${sid}; choose Skip or Drop session.`
4336
4603
  );
4337
4604
  }
4338
- const sessionDir = join33(dirname8(localPath), sid);
4605
+ const sessionDir = join34(dirname8(localPath), sid);
4339
4606
  const subtreeFiles = listSubtreeFiles(sessionDir);
4340
4607
  const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync8(p).mtimeMs);
4341
4608
  if (isRecentlyModified(subtreeMtime, nowMs())) {
@@ -4369,26 +4636,26 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
4369
4636
  );
4370
4637
  }
4371
4638
  mkdirSync7(stagedProjectDir, { recursive: true });
4372
- cpSync5(localPath, join33(stagedProjectDir, `${sid}.jsonl`), { force: true });
4373
- if (existsSync28(sessionDir)) {
4374
- cpSync5(sessionDir, join33(stagedProjectDir, sid), { force: true, recursive: true });
4639
+ cpSync6(localPath, join34(stagedProjectDir, `${sid}.jsonl`), { force: true });
4640
+ if (existsSync29(sessionDir)) {
4641
+ cpSync6(sessionDir, join34(stagedProjectDir, sid), { force: true, recursive: true });
4375
4642
  }
4376
4643
  return true;
4377
4644
  }
4378
4645
 
4379
4646
  // src/commands.push.recovery.drop.ts
4380
4647
  init_config();
4381
- import { rmSync as rmSync9 } from "node:fs";
4382
- import { join as join34 } from "node:path";
4648
+ import { rmSync as rmSync10 } from "node:fs";
4649
+ import { join as join35 } from "node:path";
4383
4650
  function dropSessionFromStaged(sid, map) {
4384
4651
  const logicals = Object.keys(map.projects);
4385
4652
  if (logicals.length === 0) return false;
4386
4653
  const repo = repoHome();
4387
4654
  for (const logical of logicals) {
4388
- const jsonl = join34(repo, "shared", "projects", logical, `${sid}.jsonl`);
4389
- const dir = join34(repo, "shared", "projects", logical, sid);
4390
- rmSync9(jsonl, { force: true });
4391
- rmSync9(dir, { recursive: true, force: true });
4655
+ const jsonl = join35(repo, "shared", "projects", logical, `${sid}.jsonl`);
4656
+ const dir = join35(repo, "shared", "projects", logical, sid);
4657
+ rmSync10(jsonl, { force: true });
4658
+ rmSync10(dir, { recursive: true, force: true });
4392
4659
  }
4393
4660
  return true;
4394
4661
  }
@@ -4422,7 +4689,7 @@ function makeDefaultReadLine(repo) {
4422
4689
  if (isAbsolute2(file) || target !== repoRoot && !target.startsWith(repoRoot + sep5)) {
4423
4690
  return null;
4424
4691
  }
4425
- const content = readFileSync13(target, "utf8");
4692
+ const content = readFileSync14(target, "utf8");
4426
4693
  const lines = content.split(/\r?\n/);
4427
4694
  const idx = line - 1;
4428
4695
  if (idx < 0 || idx >= lines.length) return null;
@@ -4543,10 +4810,10 @@ function applyThenRescan(scanVerdict, repoHome2) {
4543
4810
  return next;
4544
4811
  }
4545
4812
  function allowThenRescan(append, scanVerdict, repoHome2) {
4546
- const ignPath = join35(repoHome2, ".gitleaksignore");
4813
+ const ignPath = join36(repoHome2, ".gitleaksignore");
4547
4814
  let before;
4548
4815
  try {
4549
- before = readFileSync14(ignPath, "utf8");
4816
+ before = readFileSync15(ignPath, "utf8");
4550
4817
  } catch {
4551
4818
  before = null;
4552
4819
  }
@@ -4554,7 +4821,7 @@ function allowThenRescan(append, scanVerdict, repoHome2) {
4554
4821
  try {
4555
4822
  return applyThenRescan(scanVerdict, repoHome2);
4556
4823
  } catch (err) {
4557
- if (before === null) rmSync10(ignPath, { force: true });
4824
+ if (before === null) rmSync11(ignPath, { force: true });
4558
4825
  else writeFileSync5(ignPath, before, "utf8");
4559
4826
  throw err;
4560
4827
  }
@@ -4642,7 +4909,7 @@ function writeAnimatedDone(out, label, ms, useTTY) {
4642
4909
  `);
4643
4910
  }
4644
4911
  function resolveWorkerPath(deps = {}) {
4645
- const check = deps.existsSyncFn ?? existsSync29;
4912
+ const check = deps.existsSyncFn ?? existsSync30;
4646
4913
  const base = deps.baseUrl ?? import.meta.url;
4647
4914
  const mjs = fileURLToPath4(new URL("./nomad.worker.mjs", base));
4648
4915
  if (check(mjs)) return mjs;
@@ -4709,8 +4976,8 @@ function withSpinner(label, fn, deps) {
4709
4976
  // src/commands.doctor.gitleaks-version.ts
4710
4977
  init_color();
4711
4978
  import { execFileSync as execFileSync11 } from "node:child_process";
4712
- import { existsSync as existsSync30 } from "node:fs";
4713
- import { join as join36 } from "node:path";
4979
+ import { existsSync as existsSync31 } from "node:fs";
4980
+ import { join as join37 } from "node:path";
4714
4981
  init_config();
4715
4982
  var SEMVER_MAJOR_MINOR = /^(\d+)\.(\d+)\.\d+$/;
4716
4983
  var GITLEAKS_TIMEOUT_MS = 5e3;
@@ -4719,7 +4986,7 @@ function majorMinorOf(value) {
4719
4986
  return m === null ? null : [m[1], m[2]];
4720
4987
  }
4721
4988
  function readGitleaksVersion(run, tomlExists) {
4722
- const tomlPath = join36(repoHome(), ".gitleaks.toml");
4989
+ const tomlPath = join37(repoHome(), ".gitleaks.toml");
4723
4990
  const args = ["version"];
4724
4991
  if (tomlExists(tomlPath)) args.push("--config", tomlPath);
4725
4992
  try {
@@ -4731,7 +4998,7 @@ function readGitleaksVersion(run, tomlExists) {
4731
4998
  return null;
4732
4999
  }
4733
5000
  }
4734
- function reportGitleaksVersionCheck(section2, run = execFileSync11, tomlExists = existsSync30) {
5001
+ function reportGitleaksVersionCheck(section2, run = execFileSync11, tomlExists = existsSync31) {
4735
5002
  const raw = readGitleaksVersion(run, tomlExists);
4736
5003
  if (raw === null) return;
4737
5004
  const local = majorMinorOf(raw);
@@ -4954,8 +5221,8 @@ function gatherDoctorSections(opts) {
4954
5221
  reportHostKeyAlignment(host);
4955
5222
  reportRepoState(host);
4956
5223
  const links = section("Shared links");
4957
- const mapPath = join37(repoHome(), "path-map.json");
4958
- const rawMap = existsSync31(mapPath) ? readJsonSafe(mapPath, mapPath, links) : null;
5224
+ const mapPath = join38(repoHome(), "path-map.json");
5225
+ const rawMap = existsSync32(mapPath) ? readJsonSafe(mapPath, mapPath, links) : null;
4959
5226
  const map = rawMap ?? { projects: {} };
4960
5227
  reportSharedLinks(links, map);
4961
5228
  reportDroppedNamesMigration(links);
@@ -5052,8 +5319,8 @@ function parseDoctorArgs(args) {
5052
5319
  // src/commands.drop-session.ts
5053
5320
  init_config();
5054
5321
  import { execFileSync as execFileSync16 } from "node:child_process";
5055
- import { existsSync as existsSync33, readdirSync as readdirSync12, statSync as statSync9 } from "node:fs";
5056
- import { join as join39, relative as relative4 } from "node:path";
5322
+ import { existsSync as existsSync34, readdirSync as readdirSync13, statSync as statSync9 } from "node:fs";
5323
+ import { join as join40, relative as relative6 } from "node:path";
5057
5324
 
5058
5325
  // src/commands.drop-session.git.ts
5059
5326
  import { execFileSync as execFileSync15 } from "node:child_process";
@@ -5095,8 +5362,8 @@ function isInIndex(rel, repo) {
5095
5362
  init_config();
5096
5363
  init_utils();
5097
5364
  init_utils_json();
5098
- import { existsSync as existsSync32 } from "node:fs";
5099
- import { join as join38 } from "node:path";
5365
+ import { existsSync as existsSync33 } from "node:fs";
5366
+ import { join as join39 } from "node:path";
5100
5367
  var SHARED_PROJECT_LOGICAL = /^shared\/projects\/([^/]+)\//;
5101
5368
  function reportScrubHint(id, matches) {
5102
5369
  const live = resolveLiveTranscript2(id, matches);
@@ -5112,8 +5379,8 @@ function reportScrubHint(id, matches) {
5112
5379
  }
5113
5380
  function resolveLiveTranscript2(id, matches) {
5114
5381
  try {
5115
- const mapPath = join38(repoHome(), "path-map.json");
5116
- if (!existsSync32(mapPath)) return null;
5382
+ const mapPath = join39(repoHome(), "path-map.json");
5383
+ if (!existsSync33(mapPath)) return null;
5117
5384
  const projects = readJson(mapPath).projects;
5118
5385
  const claude = claudeHome();
5119
5386
  for (const rel of matches) {
@@ -5121,8 +5388,8 @@ function resolveLiveTranscript2(id, matches) {
5121
5388
  if (logical === void 0) continue;
5122
5389
  const abs = projects[logical]?.[HOST];
5123
5390
  if (abs === void 0) continue;
5124
- const live = join38(claude, "projects", encodePath(abs), `${id}.jsonl`);
5125
- if (existsSync32(live)) return live;
5391
+ const live = join39(claude, "projects", encodePath(abs), `${id}.jsonl`);
5392
+ if (existsSync33(live)) return live;
5126
5393
  }
5127
5394
  return null;
5128
5395
  } catch {
@@ -5138,12 +5405,12 @@ function cmdDropSession(id) {
5138
5405
  process.exit(1);
5139
5406
  }
5140
5407
  const repo = repoHome();
5141
- if (!existsSync33(repo)) die(`repo not cloned at ${repo}`);
5408
+ if (!existsSync34(repo)) die(`repo not cloned at ${repo}`);
5142
5409
  const handle = acquireLock("drop-session");
5143
5410
  if (handle === null) process.exit(0);
5144
5411
  try {
5145
- const repoProjects = join39(repo, "shared", "projects");
5146
- if (!existsSync33(repoProjects)) {
5412
+ const repoProjects = join40(repo, "shared", "projects");
5413
+ if (!existsSync34(repoProjects)) {
5147
5414
  throw new NomadFatal(`no staged session matches ${id}`);
5148
5415
  }
5149
5416
  const matches = collectMatches(repoProjects, id, repo);
@@ -5165,14 +5432,14 @@ function cmdDropSession(id) {
5165
5432
  }
5166
5433
  function collectMatches(repoProjects, id, repo) {
5167
5434
  const matches = [];
5168
- for (const logical of readdirSync12(repoProjects)) {
5169
- const candidate = join39(repoProjects, logical, `${id}.jsonl`);
5170
- if (existsSync33(candidate)) {
5171
- matches.push(relative4(repo, candidate));
5172
- }
5173
- const dir = join39(repoProjects, logical, id);
5174
- if (existsSync33(dir) && statSync9(dir).isDirectory()) {
5175
- const dirRel = relative4(repo, dir);
5435
+ for (const logical of readdirSync13(repoProjects)) {
5436
+ const candidate = join40(repoProjects, logical, `${id}.jsonl`);
5437
+ if (existsSync34(candidate)) {
5438
+ matches.push(relative6(repo, candidate));
5439
+ }
5440
+ const dir = join40(repoProjects, logical, id);
5441
+ if (existsSync34(dir) && statSync9(dir).isDirectory()) {
5442
+ const dirRel = relative6(repo, dir);
5176
5443
  const staged = expandStagedDir(dirRel, repo);
5177
5444
  if (staged.length > 0) matches.push(...staged);
5178
5445
  else matches.push(dirRel);
@@ -5214,7 +5481,7 @@ init_color();
5214
5481
 
5215
5482
  // src/summary.ts
5216
5483
  init_utils();
5217
- function summaryText(verb, unmapped, collisions = 0, extrasSkipped = 0) {
5484
+ function summaryText(verb, unmapped, collisions = 0, extrasSkipped = 0, localOnly = 0) {
5218
5485
  const extras = extrasSkipped > 0 ? `, ${extrasSkipped} extras skipped` : "";
5219
5486
  if (verb === "push") {
5220
5487
  if (unmapped === 0 && collisions === 0 && extrasSkipped === 0) {
@@ -5223,16 +5490,17 @@ function summaryText(verb, unmapped, collisions = 0, extrasSkipped = 0) {
5223
5490
  const base = `summary: ${unmapped} unmapped on push, ${collisions} collisions`;
5224
5491
  return { text: `${base}${extras} (run nomad doctor to list)`, clean: false };
5225
5492
  }
5226
- if (unmapped === 0 && extrasSkipped === 0) {
5493
+ if (unmapped === 0 && extrasSkipped === 0 && localOnly === 0) {
5227
5494
  return { text: "summary: clean", clean: true };
5228
5495
  }
5496
+ const localOnlyPhrase = localOnly > 0 ? `, ${localOnly} local-only present (push to reconcile)` : "";
5229
5497
  return {
5230
- text: `summary: ${unmapped} unmapped on ${verb}${extras} (run nomad doctor to list)`,
5498
+ text: `summary: ${unmapped} unmapped on ${verb}${extras} (run nomad doctor to list)${localOnlyPhrase}`,
5231
5499
  clean: false
5232
5500
  };
5233
5501
  }
5234
- function summaryRow(verb, unmapped, collisions = 0, extrasSkipped = 0) {
5235
- const { text } = summaryText(verb, unmapped, collisions, extrasSkipped);
5502
+ function summaryRow(verb, unmapped, collisions = 0, extrasSkipped = 0, localOnly = 0) {
5503
+ const { text } = summaryText(verb, unmapped, collisions, extrasSkipped, localOnly);
5236
5504
  return text.replace(/^summary: /, "");
5237
5505
  }
5238
5506
 
@@ -5246,11 +5514,17 @@ function buildSettingsSection(label) {
5246
5514
  addItem(s, `${green(okGlyph)} settings.json (base + ${label})`);
5247
5515
  return s;
5248
5516
  }
5249
- function buildSessionsSection(items, unmapped) {
5517
+ function buildSessionsSection(items, unmapped, localOnly = 0) {
5250
5518
  const s = section("Sessions");
5251
5519
  for (const logical of items) addItem(s, `${green(okGlyph)} ${logical}`);
5252
5520
  const skip = collapsedSkipRow(unmapped, "not in path-map (run nomad doctor to list)");
5253
5521
  if (skip !== null) addItem(s, skip);
5522
+ if (localOnly > 0) {
5523
+ addItem(
5524
+ s,
5525
+ `${yellow(warnGlyph)} ${localOnly} local-only present, not in repo (push to reconcile)`
5526
+ );
5527
+ }
5254
5528
  return s;
5255
5529
  }
5256
5530
  function buildExtrasSection(items, extrasSkipped) {
@@ -5304,167 +5578,12 @@ init_config();
5304
5578
  init_config();
5305
5579
  import { existsSync as existsSync36, statSync as statSync10 } from "node:fs";
5306
5580
  import { join as join43 } from "node:path";
5307
-
5308
- // src/extras-sync.core.ts
5309
- init_config();
5310
- import { cpSync as cpSync6, existsSync as existsSync34, lstatSync as lstatSync9, readdirSync as readdirSync13, rmSync as rmSync11 } from "node:fs";
5311
- import { basename, join as join40 } from "node:path";
5312
- init_utils();
5313
- init_utils_json();
5314
- function loadValidatedExtras(opts) {
5315
- const repo = repoHome();
5316
- const mapPath = join40(repo, "path-map.json");
5317
- const repoExtras = join40(repo, "shared", "extras");
5318
- if (!existsSync34(mapPath) || opts.requireRepoExtras === true && !existsSync34(repoExtras)) {
5319
- if (opts.missingMsg !== void 0) log(opts.missingMsg);
5320
- return null;
5321
- }
5322
- const map = readPathMap(mapPath);
5323
- const extrasMap = map.extras ?? {};
5324
- if (Object.keys(extrasMap).length === 0) return null;
5325
- for (const logical of Object.keys(extrasMap)) {
5326
- assertSafeLogical(logical);
5327
- const localRoot = map.projects[logical]?.[HOST];
5328
- if (localRoot && localRoot !== "TBD") assertSafeLocalRoot(localRoot, logical);
5329
- }
5330
- return { map, extrasMap };
5331
- }
5332
- function* eachExtrasTarget(v, counts) {
5333
- const whitelist = SUPPORTED_EXTRAS;
5334
- for (const [logical, dirnames] of Object.entries(v.extrasMap)) {
5335
- const localRoot = v.map.projects[logical]?.[HOST];
5336
- if (!localRoot || localRoot === "TBD") {
5337
- counts.unmapped++;
5338
- continue;
5339
- }
5340
- for (const dirname10 of dirnames) {
5341
- if (!whitelist.includes(dirname10)) {
5342
- counts.skipped++;
5343
- continue;
5344
- }
5345
- yield { logical, localRoot, dirname: dirname10 };
5346
- }
5347
- }
5348
- }
5349
- function stripCollidingDstSymlinks(src, dst, isExcluded) {
5350
- if (!existsSync34(dst)) return;
5351
- for (const name of readdirSync13(src)) {
5352
- if (isExcluded(name)) continue;
5353
- const dstPath = join40(dst, name);
5354
- const dstStat = lstatSync9(dstPath, { throwIfNoEntry: false });
5355
- if (dstStat === void 0) continue;
5356
- if (dstStat.isSymbolicLink()) {
5357
- rmSync11(dstPath, { recursive: true, force: true });
5358
- } else if (dstStat.isDirectory() && lstatSync9(join40(src, name)).isDirectory()) {
5359
- stripCollidingDstSymlinks(join40(src, name), dstPath, isExcluded);
5360
- }
5361
- }
5362
- }
5363
- function copyExtrasOverlayFiltered(src, dst, blockSet) {
5364
- stripCollidingDstSymlinks(src, dst, (name) => isDeniedName(blockSet, name));
5365
- try {
5366
- cpSync6(src, dst, {
5367
- recursive: true,
5368
- force: true,
5369
- verbatimSymlinks: true,
5370
- filter: (srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename(srcEntry))
5371
- });
5372
- } catch (err) {
5373
- const e = err;
5374
- if (e.code === "EINVAL" || e.code === "ENOTEMPTY" || e.code === "ERR_FS_CP_NON_DIR_TO_DIR" || e.code === "ERR_FS_CP_DIR_TO_NON_DIR") {
5375
- throw new NomadFatal(
5376
- `copyExtrasOverlayFiltered: type collision copying ${JSON.stringify(src)} -> ${JSON.stringify(dst)} (${e.path ?? "unknown path"}): a file/directory type changed upstream; run nomad pull --force-remote to recover`
5377
- );
5378
- }
5379
- throw err;
5380
- }
5381
- }
5382
- function copyExtras(src, dst) {
5383
- rmSync11(dst, { recursive: true, force: true });
5384
- cpSync6(src, dst, { recursive: true, force: true, verbatimSymlinks: true });
5385
- }
5386
- function extrasDenySet(dirname10) {
5387
- return dirname10 === ".claude" ? CLAUDE_EXTRA_NEVER_SYNC : ALWAYS_NEVER_SYNC;
5388
- }
5389
- function copyExtrasFiltered(src, dst, blockSet) {
5390
- rmSync11(dst, { recursive: true, force: true });
5391
- cpSync6(src, dst, {
5392
- recursive: true,
5393
- force: true,
5394
- verbatimSymlinks: true,
5395
- filter: (srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename(srcEntry))
5396
- });
5397
- }
5398
- function prunePreservingDenied(src, dst, blockSet) {
5399
- for (const name of readdirSync13(dst)) {
5400
- if (isDeniedName(blockSet, name)) continue;
5401
- const dstPath = join40(dst, name);
5402
- const srcStat = lstatSync9(join40(src, name), { throwIfNoEntry: false });
5403
- if (srcStat === void 0) {
5404
- rmSync11(dstPath, { recursive: true, force: true });
5405
- continue;
5406
- }
5407
- const dstStat = lstatSync9(dstPath);
5408
- if (srcStat.isDirectory() && dstStat.isDirectory()) {
5409
- prunePreservingDenied(join40(src, name), dstPath, blockSet);
5410
- } else if (srcStat.isDirectory() !== dstStat.isDirectory()) {
5411
- rmSync11(dstPath, { recursive: true, force: true });
5412
- }
5413
- }
5414
- }
5415
- function copyExtrasFilteredPreserving(src, dst, blockSet) {
5416
- const dstStat = lstatSync9(dst, { throwIfNoEntry: false });
5417
- if (dstStat !== void 0) {
5418
- if (dstStat.isDirectory()) prunePreservingDenied(src, dst, blockSet);
5419
- else rmSync11(dst, { recursive: true, force: true });
5420
- }
5421
- stripCollidingDstSymlinks(src, dst, (name) => isDeniedName(blockSet, name));
5422
- cpSync6(src, dst, {
5423
- recursive: true,
5424
- force: true,
5425
- verbatimSymlinks: true,
5426
- filter: (srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename(srcEntry))
5427
- });
5428
- }
5429
- function prunePreservingBy(src, dst, isPreserved) {
5430
- for (const name of readdirSync13(dst)) {
5431
- if (isPreserved(name)) continue;
5432
- const dstPath = join40(dst, name);
5433
- const srcStat = lstatSync9(join40(src, name), { throwIfNoEntry: false });
5434
- if (srcStat === void 0) {
5435
- rmSync11(dstPath, { recursive: true, force: true });
5436
- continue;
5437
- }
5438
- const dstStat = lstatSync9(dstPath);
5439
- if (srcStat.isDirectory() && dstStat.isDirectory()) {
5440
- prunePreservingBy(join40(src, name), dstPath, isPreserved);
5441
- } else if (srcStat.isDirectory() !== dstStat.isDirectory()) {
5442
- rmSync11(dstPath, { recursive: true, force: true });
5443
- }
5444
- }
5445
- }
5446
- function copyExtrasFilteredPreservingBy(src, dst, isPreserved) {
5447
- const dstStat = lstatSync9(dst, { throwIfNoEntry: false });
5448
- if (dstStat !== void 0) {
5449
- if (dstStat.isDirectory()) prunePreservingBy(src, dst, isPreserved);
5450
- else rmSync11(dst, { recursive: true, force: true });
5451
- }
5452
- stripCollidingDstSymlinks(src, dst, isPreserved);
5453
- cpSync6(src, dst, {
5454
- recursive: true,
5455
- force: true,
5456
- verbatimSymlinks: true,
5457
- filter: (srcEntry) => srcEntry === src || !isPreserved(basename(srcEntry))
5458
- });
5459
- }
5460
-
5461
- // src/extras-sync.ts
5462
5581
  init_utils();
5463
5582
  init_utils_json();
5464
5583
 
5465
5584
  // src/extras-sync.remap.ts
5466
5585
  init_config();
5467
- import { existsSync as existsSync35, mkdirSync as mkdirSync8, readdirSync as readdirSync14, realpathSync as realpathSync4, rmSync as rmSync12 } from "node:fs";
5586
+ import { existsSync as existsSync35, mkdirSync as mkdirSync8, readdirSync as readdirSync14, readFileSync as readFileSync16, realpathSync as realpathSync4, rmSync as rmSync12 } from "node:fs";
5468
5587
  import { dirname as dirname9, join as join42, sep as sep7 } from "node:path";
5469
5588
 
5470
5589
  // src/extras-sync.planning-diff.ts
@@ -5593,24 +5712,58 @@ function deletePlanningTarget(target, planningRoot, repoCounterpart) {
5593
5712
  rmSync12(target, { recursive: true, force: true });
5594
5713
  pruneEmptyAncestors(target, planningRoot);
5595
5714
  }
5715
+ function localDivergesFromPreDelete(target, pre, repoRel, repo) {
5716
+ if (!existsSync35(target)) return false;
5717
+ try {
5718
+ const preBlob = gitCaptureBuffer(["show", `${pre}:${repoRel}`], repo);
5719
+ return !readFileSync16(target).equals(preBlob);
5720
+ } catch {
5721
+ return true;
5722
+ }
5723
+ }
5724
+ function planningDiffArgs(pre, post, logical) {
5725
+ return ["diff", "--name-status", "-z", pre, post, "--", `shared/extras/${logical}/.planning/`];
5726
+ }
5727
+ function deletePairsFor(t, raw) {
5728
+ return planningDeleteTargets({ raw, logical: t.logical, localRoot: t.localRoot }).map(
5729
+ (target) => {
5730
+ const relToLocal = target.slice(t.localRoot.length + sep7.length);
5731
+ return {
5732
+ target,
5733
+ relToLocal,
5734
+ repoRel: `shared/extras/${t.logical}/${relToLocal.split(sep7).join("/")}`
5735
+ };
5736
+ }
5737
+ );
5738
+ }
5739
+ function keptDeleteWarnLine(logical, relToLocal) {
5740
+ return `keeping locally-edited ${relToLocal} in ${logical}: deleted upstream but changed locally (push to reconcile; your copy is backed up)`;
5741
+ }
5742
+ function keptDeletePreview(v, prePostHeads, repo) {
5743
+ const kept = [];
5744
+ for (const t of eachExtrasTarget(v, { unmapped: 0, skipped: 0 })) {
5745
+ if (t.dirname !== ".planning") continue;
5746
+ let raw;
5747
+ try {
5748
+ raw = gitCaptureRaw(planningDiffArgs(prePostHeads.pre, prePostHeads.post, t.logical), repo);
5749
+ } catch {
5750
+ continue;
5751
+ }
5752
+ for (const { target, relToLocal, repoRel } of deletePairsFor(t, raw)) {
5753
+ if (localDivergesFromPreDelete(target, prePostHeads.pre, repoRel, repo)) {
5754
+ kept.push({ logical: t.logical, relToLocal });
5755
+ }
5756
+ }
5757
+ }
5758
+ return kept;
5759
+ }
5596
5760
  function propagatePlanningDeletes(v, ts, prePostHeads, repo) {
5597
5761
  const repoExtras = join42(repo, "shared", "extras");
5598
5762
  for (const t of eachExtrasTarget(v, { unmapped: 0, skipped: 0 })) {
5599
5763
  if (t.dirname !== ".planning") continue;
5600
5764
  let raw;
5601
5765
  try {
5602
- raw = gitCaptureRaw(
5603
- [
5604
- "diff",
5605
- "--name-status",
5606
- "-z",
5607
- prePostHeads.pre,
5608
- prePostHeads.post,
5609
- "--",
5610
- `shared/extras/${t.logical}/.planning/`
5611
- ],
5612
- repo
5613
- );
5766
+ raw = gitCaptureRaw(planningDiffArgs(prePostHeads.pre, prePostHeads.post, t.logical), repo);
5614
5767
  } catch (err) {
5615
5768
  const e = err;
5616
5769
  if (e.stderr) process.stderr.write(e.stderr);
@@ -5618,12 +5771,15 @@ function propagatePlanningDeletes(v, ts, prePostHeads, repo) {
5618
5771
  `git diff failed while propagating .planning deletes for ${t.logical}; run nomad pull --force-remote to recover`
5619
5772
  );
5620
5773
  }
5621
- const targets = planningDeleteTargets({ raw, logical: t.logical, localRoot: t.localRoot });
5622
- if (targets.length === 0) continue;
5774
+ const pairs = deletePairsFor(t, raw);
5775
+ if (pairs.length === 0) continue;
5623
5776
  backupExtrasWrite(join42(t.localRoot, t.dirname), ts, t.localRoot);
5624
5777
  const planningRoot = join42(t.localRoot, ".planning");
5625
- for (const target of targets) {
5626
- const relToLocal = target.slice(t.localRoot.length + sep7.length);
5778
+ for (const { target, relToLocal, repoRel } of pairs) {
5779
+ if (localDivergesFromPreDelete(target, prePostHeads.pre, repoRel, repo)) {
5780
+ warn(keptDeleteWarnLine(t.logical, relToLocal));
5781
+ continue;
5782
+ }
5627
5783
  deletePlanningTarget(target, planningRoot, join42(repoExtras, t.logical, relToLocal));
5628
5784
  }
5629
5785
  }
@@ -5672,23 +5828,30 @@ function remapExtrasPull(ts, opts = {}) {
5672
5828
  src: join42(repo, "shared", "extras", logical, dirname10),
5673
5829
  dst: join42(localRoot, dirname10)
5674
5830
  }),
5675
- // Snapshot the host-side dst BEFORE copyExtras clobbers it. Anchor on
5831
+ // Snapshot the host-side dst BEFORE the copy step touches it. Anchor on
5676
5832
  // localRoot so the backup tree mirrors the project layout.
5677
5833
  (dst, localRoot) => backupExtrasWrite(dst, ts, localRoot),
5678
5834
  // Pull routing per extra type:
5679
5835
  // `.claude`: copyExtrasFilteredPreserving preserves host-local deny-set
5680
5836
  // files (e.g. settings.local.json) while mirror-pruning synced entries.
5681
- // `.planning`: copyExtrasOverlayFiltered (no rmSync; deny-set filtered)
5682
- // keeps local-only files; the delete pass below propagates upstream
5683
- // removals via the git-diff D set. The filter is defense-in-depth
5684
- // against a repo poisoned out-of-band.
5685
- // All others: copyExtras (exact mirror; rarely carry host-local files).
5837
+ // `.planning`: copyExtrasOverlaySkipDiverged (no rmSync; deny-set filtered)
5838
+ // keeps local-only files AND skips any file whose local copy diverges
5839
+ // from the repo copy (content hash differs), so a local hand-edit wins
5840
+ // on conflict; the delete pass below still propagates
5841
+ // upstream removals via the git-diff D set. The filter is defense-in-
5842
+ // depth against a repo poisoned out-of-band.
5843
+ // All others (a single root-level file, e.g. CLAUDE.md):
5844
+ // copyExtrasFileSkipDiverged keeps a locally-edited file rather than
5845
+ // clobbering it, so the divergence WARN's keep-local promise holds for
5846
+ // every extra, not just `.planning`.
5686
5847
  (src, dst, dirname10) => {
5687
5848
  if (dirname10 === ".claude")
5688
5849
  return copyExtrasFilteredPreserving(src, dst, extrasDenySet(dirname10));
5689
- if (dirname10 === ".planning")
5690
- return copyExtrasOverlayFiltered(src, dst, extrasDenySet(dirname10));
5691
- return copyExtras(src, dst);
5850
+ if (dirname10 === ".planning") {
5851
+ const divergedSet = new Set(listDivergingModified(dst, src));
5852
+ return copyExtrasOverlaySkipDiverged(src, dst, extrasDenySet(dirname10), divergedSet);
5853
+ }
5854
+ return copyExtrasFileSkipDiverged(src, dst);
5692
5855
  }
5693
5856
  );
5694
5857
  if (!dryRun && prePostHeads !== void 0) {
@@ -5703,11 +5866,10 @@ function divergenceWarnLine(o) {
5703
5866
  const name = o.isDir ? `${o.dirname}/` : o.dirname;
5704
5867
  const one = o.count === 1;
5705
5868
  const fileCount = one ? "1 file" : `${o.count} files`;
5706
- const them = one ? "it" : "them";
5707
5869
  const yours = one ? "your current file is" : "your current files are";
5708
- return `local ${kind} ${name} in repo ${o.logical} differs from the synced copy in ${fileCount}; the next pull step will overwrite ${them} with the synced version (${yours} backed up to ${o.projectBackupRoot}/)`;
5870
+ return `local ${kind} ${name} in repo ${o.logical} differs from the synced copy in ${fileCount}; the next pull step will keep your local copy (push to reconcile; ${yours} backed up to ${o.projectBackupRoot}/)`;
5709
5871
  }
5710
- function divergenceCheckExtras(ts) {
5872
+ function divergenceCheckExtras(ts, prePostHeads) {
5711
5873
  const v = loadValidatedExtras({});
5712
5874
  if (v === null) return;
5713
5875
  const counts = { unmapped: 0, skipped: 0 };
@@ -5717,25 +5879,30 @@ function divergenceCheckExtras(ts) {
5717
5879
  const local = join43(localRoot, dirname10);
5718
5880
  const repoEntry = join43(repo, "shared", "extras", logical, dirname10);
5719
5881
  if (!existsSync36(local) || !existsSync36(repoEntry)) continue;
5720
- const diff = listDivergingFiles(local, repoEntry);
5721
- if (diff.length === 0) continue;
5882
+ const modified = listDivergingModified(local, repoEntry);
5883
+ if (modified.length === 0) continue;
5722
5884
  const projectBackupRoot = join43(backupRoot, encodePath(localRoot));
5723
5885
  warn(
5724
5886
  divergenceWarnLine({
5725
5887
  dirname: dirname10,
5726
5888
  logical,
5727
5889
  isDir: statSync10(local).isDirectory(),
5728
- count: diff.length,
5890
+ count: modified.length,
5729
5891
  projectBackupRoot
5730
5892
  })
5731
5893
  );
5732
- for (const f of diff) warn(` ${f}`);
5894
+ for (const f of modified) warn(` ${f}`);
5895
+ }
5896
+ if (prePostHeads !== void 0) {
5897
+ for (const { logical, relToLocal } of keptDeletePreview(v, prePostHeads, repo)) {
5898
+ warn(keptDeleteWarnLine(logical, relToLocal));
5899
+ }
5733
5900
  }
5734
5901
  }
5735
5902
 
5736
5903
  // src/skills-sync.ts
5737
5904
  init_config();
5738
- import { existsSync as existsSync37, lstatSync as lstatSync10, mkdirSync as mkdirSync9, readdirSync as readdirSync15, rmSync as rmSync13 } from "node:fs";
5905
+ import { existsSync as existsSync37, lstatSync as lstatSync11, mkdirSync as mkdirSync9, readdirSync as readdirSync15, rmSync as rmSync13 } from "node:fs";
5739
5906
  import { join as join44 } from "node:path";
5740
5907
  init_utils_fs();
5741
5908
  function isGsdOwned(name) {
@@ -5759,7 +5926,7 @@ function syncSkillsPull(ts) {
5759
5926
  const sharedSkills = join44(repoHome(), "shared", "skills");
5760
5927
  if (!existsSync37(sharedSkills)) return;
5761
5928
  const localSkills = join44(claudeHome(), "skills");
5762
- const dstStat = lstatSync10(localSkills, { throwIfNoEntry: false });
5929
+ const dstStat = lstatSync11(localSkills, { throwIfNoEntry: false });
5763
5930
  if (dstStat?.isSymbolicLink() === true) {
5764
5931
  backupBeforeWrite(localSkills, ts);
5765
5932
  rmSync13(localSkills, { recursive: true, force: true });
@@ -5769,7 +5936,7 @@ function syncSkillsPull(ts) {
5769
5936
  }
5770
5937
  function syncSkillsPush() {
5771
5938
  const localSkills = join44(claudeHome(), "skills");
5772
- const stat = lstatSync10(localSkills, { throwIfNoEntry: false });
5939
+ const stat = lstatSync11(localSkills, { throwIfNoEntry: false });
5773
5940
  if (stat === void 0) return;
5774
5941
  if (stat.isSymbolicLink()) return;
5775
5942
  const sharedSkills = join44(repoHome(), "shared", "skills");
@@ -6135,10 +6302,14 @@ function computePreview(ts, map, verb = "pull") {
6135
6302
  dryRun: true,
6136
6303
  onPreview: (e) => addItem(sessions, formatSessionRow(e))
6137
6304
  });
6305
+ const localOnly = scanLocalOnly();
6306
+ if (localOnly > 0) {
6307
+ addItem(sessions, `${localOnly} local-only present, not in repo (push to reconcile)`);
6308
+ }
6138
6309
  const summary = section("Summary");
6139
- addItem(summary, summaryRow(verb, remapResult.unmapped));
6310
+ addItem(summary, summaryRow(verb, remapResult.unmapped, 0, 0, localOnly));
6140
6311
  renderTree([links, settingsSection, sessions, summary]);
6141
- return { unmapped: remapResult.unmapped, collisions: 0 };
6312
+ return { unmapped: remapResult.unmapped, collisions: 0, localOnly };
6142
6313
  }
6143
6314
 
6144
6315
  // src/commands.pull.ts
@@ -6293,14 +6464,21 @@ function applyWetPull(ts, map, prePostHeads) {
6293
6464
  syncSkillsPull(ts);
6294
6465
  const remapResult = withSpinner("Syncing sessions", () => remapPull(ts));
6295
6466
  const extrasResult = remapExtrasPull(ts, { prePostHeads });
6467
+ const localOnly = scanLocalOnly();
6296
6468
  const summary = section("Summary");
6297
6469
  addItem(
6298
6470
  summary,
6299
- summaryRow("pull", remapResult.unmapped + extrasResult.unmapped, 0, extrasResult.skipped)
6471
+ summaryRow(
6472
+ "pull",
6473
+ remapResult.unmapped + extrasResult.unmapped,
6474
+ 0,
6475
+ extrasResult.skipped,
6476
+ localOnly
6477
+ )
6300
6478
  );
6301
6479
  renderTree([
6302
6480
  buildSettingsSection(label),
6303
- buildSessionsSection(remapResult.pulled, remapResult.unmapped),
6481
+ buildSessionsSection(remapResult.pulled, remapResult.unmapped, localOnly),
6304
6482
  buildExtrasSection(extrasResult.pulled, extrasResult.skipped),
6305
6483
  summary
6306
6484
  ]);
@@ -6353,7 +6531,7 @@ function cmdPull(opts = {}) {
6353
6531
  });
6354
6532
  const mapPath = join46(repo, "path-map.json");
6355
6533
  const map = existsSync39(mapPath) ? readPathMap(mapPath) : { projects: {} };
6356
- divergenceCheckExtras(ts);
6534
+ divergenceCheckExtras(ts, dryRun ? prePostHeads : void 0);
6357
6535
  if (dryRun) {
6358
6536
  computePreview(ts, map, "pull");
6359
6537
  log("dry-run complete; no mutation");
@@ -6568,12 +6746,12 @@ function reportSettingsAheadDrift(repo) {
6568
6746
  // src/commands.push.guards.ts
6569
6747
  init_push_checks();
6570
6748
  init_utils();
6571
- import { join as join49, relative as relative5 } from "node:path";
6749
+ import { join as join49, relative as relative7 } from "node:path";
6572
6750
  function guardGitlinks(repo) {
6573
6751
  const gitlinks = findGitlinks(join49(repo, "shared"));
6574
6752
  if (gitlinks.length === 0) return;
6575
6753
  for (const p of gitlinks) {
6576
- const rel = relative5(repo, p);
6754
+ const rel = relative7(repo, p);
6577
6755
  fail(`gitlink: ${rel} would push as submodule (run: rm -rf ${rel} or remove the nested repo)`);
6578
6756
  }
6579
6757
  const noun = gitlinks.length === 1 ? "entry" : "entries";
@@ -6682,7 +6860,7 @@ init_config_sharedDirs_guard();
6682
6860
  import { randomBytes as randomBytes2 } from "node:crypto";
6683
6861
  import { copyFileSync, existsSync as existsSync42, mkdirSync as mkdirSync11, readdirSync as readdirSync16, rmSync as rmSync14 } from "node:fs";
6684
6862
  import { homedir as homedir5 } from "node:os";
6685
- import { join as join50, relative as relative6, sep as sep8 } from "node:path";
6863
+ import { join as join50, relative as relative8, sep as sep8 } from "node:path";
6686
6864
  init_push_leak_verdict();
6687
6865
  init_push_gitleaks();
6688
6866
  init_utils_fs();
@@ -6694,7 +6872,7 @@ function stageSessionDir(localDir, dstDir, changed) {
6694
6872
  const matching = [...changed].filter((p) => p.startsWith(prefix));
6695
6873
  if (matching.length === 0) return false;
6696
6874
  for (const src of matching) {
6697
- copyFileAtomic(src, join50(dstDir, relative6(localDir, src)));
6875
+ copyFileAtomic(src, join50(dstDir, relative8(localDir, src)));
6698
6876
  }
6699
6877
  return true;
6700
6878
  }
@@ -6933,6 +7111,7 @@ function cmdDiff() {
6933
7111
  const ts = freshBackupTs(backupBase());
6934
7112
  const mapPath = join52(repo, "path-map.json");
6935
7113
  const map = existsSync44(mapPath) ? readPathMap(mapPath) : { projects: {} };
7114
+ divergenceCheckExtras(ts);
6936
7115
  computePreview(ts, map, "diff");
6937
7116
  } catch (err) {
6938
7117
  if (err instanceof NomadFatal) {
@@ -7478,7 +7657,7 @@ function parsePushArgs(argv) {
7478
7657
  // package.json
7479
7658
  var package_default = {
7480
7659
  name: "claude-nomad",
7481
- version: "0.56.0",
7660
+ version: "0.56.2",
7482
7661
  type: "module",
7483
7662
  description: "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
7484
7663
  keywords: [
@@ -7703,7 +7882,7 @@ var DEFAULT_HELP = [
7703
7882
  init_config();
7704
7883
  init_utils();
7705
7884
  init_utils_json();
7706
- import { existsSync as existsSync48, readFileSync as readFileSync15, readdirSync as readdirSync18 } from "node:fs";
7885
+ import { existsSync as existsSync48, readFileSync as readFileSync17, readdirSync as readdirSync18 } from "node:fs";
7707
7886
  import { join as join56 } from "node:path";
7708
7887
  function resumeCmd(sessionId) {
7709
7888
  if (!/^[A-Za-z0-9_-]+$/.test(sessionId) || sessionId.length > 128) {
@@ -7755,7 +7934,7 @@ function findTranscriptPath(projectsRoot, sessionId) {
7755
7934
  return null;
7756
7935
  }
7757
7936
  function extractRecordedCwd(jsonlPath) {
7758
- for (const line of readFileSync15(jsonlPath, "utf8").split("\n")) {
7937
+ for (const line of readFileSync17(jsonlPath, "utf8").split("\n")) {
7759
7938
  if (!line.trim()) continue;
7760
7939
  try {
7761
7940
  const obj = JSON.parse(line);