claude-nomad 0.56.1 → 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 +16 -0
  2. package/dist/nomad.mjs +555 -394
  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"] });
@@ -2110,8 +2117,8 @@ function cmdEject(opts = {}, roots = defaultEjectRoots()) {
2110
2117
  }
2111
2118
 
2112
2119
  // src/commands.doctor.ts
2113
- import { existsSync as existsSync31 } from "node:fs";
2114
- import { join as join37 } from "node:path";
2120
+ import { existsSync as existsSync32 } from "node:fs";
2121
+ import { join as join38 } from "node:path";
2115
2122
 
2116
2123
  // src/commands.doctor.checks.repo.ts
2117
2124
  init_color();
@@ -2551,28 +2558,42 @@ init_config();
2551
2558
  // src/extras-sync.diff.ts
2552
2559
  init_utils();
2553
2560
  import { execFileSync as execFileSync2 } from "node:child_process";
2554
- function labelDiffLine(line) {
2555
- const tab = line.indexOf(" ");
2556
- if (tab === -1) return line;
2557
- const status = line.slice(0, tab);
2558
- const path = line.slice(tab + 1);
2559
- if (status === "D") return `${path} (local only)`;
2560
- if (status === "A") return `${path} (repo only)`;
2561
- 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;
2562
2576
  }
2563
2577
  function parseDiffOutput(stdout) {
2564
- return stdout.split("\n").filter((line) => line.length > 0).map(labelDiffLine);
2578
+ return parseNameStatus(stdout).map(labelEntry);
2565
2579
  }
2566
- 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) {
2567
2584
  try {
2568
- const stdout = execFileSync2("git", ["diff", "--no-index", "--name-status", a, b], {
2569
- stdio: ["ignore", "pipe", "pipe"]
2570
- }).toString();
2571
- 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);
2572
2593
  } catch (err) {
2573
2594
  const e = err;
2574
2595
  if (e.status === 1 && e.stdout !== void 0) {
2575
- return parseDiffOutput(e.stdout.toString());
2596
+ return parse(e.stdout.toString());
2576
2597
  }
2577
2598
  if (e.code === "ENOENT") {
2578
2599
  warn(`git not on PATH; divergence check skipped for ${a}`);
@@ -2582,6 +2603,12 @@ function listDivergingFiles(a, b) {
2582
2603
  return [];
2583
2604
  }
2584
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
+ }
2585
2612
 
2586
2613
  // src/commands.doctor.checks.skills.ts
2587
2614
  function stripSideIndicator(line) {
@@ -2591,15 +2618,15 @@ function stripSideIndicator(line) {
2591
2618
  }
2592
2619
  function isGsdDiffLine(line, localBase, sharedBase) {
2593
2620
  const bare = stripSideIndicator(line);
2594
- let relative7;
2621
+ let relative9;
2595
2622
  if (bare.startsWith(localBase + "/")) {
2596
- relative7 = bare.slice(localBase.length + 1);
2623
+ relative9 = bare.slice(localBase.length + 1);
2597
2624
  } else if (bare.startsWith(sharedBase + "/")) {
2598
- relative7 = bare.slice(sharedBase.length + 1);
2625
+ relative9 = bare.slice(sharedBase.length + 1);
2599
2626
  } else {
2600
- relative7 = bare;
2627
+ relative9 = bare;
2601
2628
  }
2602
- return relative7.split("/")[0].startsWith(GSD_PREFIX);
2629
+ return relative9.split("/")[0].startsWith(GSD_PREFIX);
2603
2630
  }
2604
2631
  function reportSkillsDivergence(section2) {
2605
2632
  const sharedSkills = join14(repoHome(), "shared", "skills");
@@ -2632,7 +2659,7 @@ init_color();
2632
2659
  init_config();
2633
2660
  import { execFileSync as execFileSync5 } from "node:child_process";
2634
2661
  import { existsSync as existsSync15 } from "node:fs";
2635
- import { join as join18, relative as relative2 } from "node:path";
2662
+ import { join as join18, relative as relative3 } from "node:path";
2636
2663
  init_commands_pull_wedge();
2637
2664
  init_push_checks();
2638
2665
  init_utils();
@@ -2659,7 +2686,7 @@ function reportGitlinks(section2) {
2659
2686
  if (existsSync15(sharedDir)) {
2660
2687
  const gitlinks = findGitlinks(sharedDir);
2661
2688
  for (const p of gitlinks) {
2662
- const rel = relative2(repo, p);
2689
+ const rel = relative3(repo, p);
2663
2690
  addItem(
2664
2691
  section2,
2665
2692
  `${red(failGlyph)} gitlink: ${blue(rel)} would push as submodule (run: rm -rf ${rel} or remove the nested repo)`
@@ -2936,9 +2963,9 @@ function reportCheckSchema(section2) {
2936
2963
  init_color();
2937
2964
  import { randomBytes } from "node:crypto";
2938
2965
  import { execFileSync as execFileSync9 } from "node:child_process";
2939
- 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";
2940
2967
  import { homedir as homedir4 } from "node:os";
2941
- import { join as join25 } from "node:path";
2968
+ import { join as join26 } from "node:path";
2942
2969
 
2943
2970
  // src/commands.doctor.check-shared.scan.ts
2944
2971
  init_color();
@@ -3035,8 +3062,22 @@ init_config();
3035
3062
 
3036
3063
  // src/remap.ts
3037
3064
  init_config_sharedDirs_guard();
3038
- 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";
3039
- 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";
3040
3081
 
3041
3082
  // src/extras-sync.guards.ts
3042
3083
  init_utils();
@@ -3055,6 +3096,180 @@ function assertSafeLocalRoot(localRoot, logical) {
3055
3096
  }
3056
3097
  }
3057
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
+
3058
3273
  // src/remap.ts
3059
3274
  init_config();
3060
3275
 
@@ -3063,8 +3278,8 @@ init_config();
3063
3278
  init_push_gitleaks_config();
3064
3279
  init_utils_fs();
3065
3280
  import { createHash } from "node:crypto";
3066
- import { existsSync as existsSync18, mkdirSync as mkdirSync4, readdirSync as readdirSync7, readFileSync as readFileSync6, statSync as statSync4 } from "node:fs";
3067
- 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";
3068
3283
  function isChanged(prev, cur, hash) {
3069
3284
  if (prev === void 0) return true;
3070
3285
  if (prev.size !== cur.size) return true;
@@ -3101,11 +3316,11 @@ function shouldFullRescan(old, scannerVersion, configHash, forceFlag) {
3101
3316
  return false;
3102
3317
  }
3103
3318
  function hashFile(absPath) {
3104
- return createHash("sha256").update(readFileSync6(absPath)).digest("hex");
3319
+ return createHash("sha256").update(readFileSync7(absPath)).digest("hex");
3105
3320
  }
3106
3321
  function enumerateDir(dir, results) {
3107
- for (const entry of readdirSync7(dir)) {
3108
- const fullPath = join23(dir, entry);
3322
+ for (const entry of readdirSync8(dir)) {
3323
+ const fullPath = join24(dir, entry);
3109
3324
  const st = statSync4(fullPath);
3110
3325
  if (st.isDirectory()) {
3111
3326
  enumerateDir(fullPath, results);
@@ -3116,8 +3331,8 @@ function enumerateDir(dir, results) {
3116
3331
  }
3117
3332
  function enumerateSourceFiles(projectDir) {
3118
3333
  const results = [];
3119
- for (const entry of readdirSync7(projectDir)) {
3120
- const fullPath = join23(projectDir, entry);
3334
+ for (const entry of readdirSync8(projectDir)) {
3335
+ const fullPath = join24(projectDir, entry);
3121
3336
  const st = statSync4(fullPath);
3122
3337
  if (st.isDirectory()) {
3123
3338
  enumerateDir(fullPath, results);
@@ -3129,15 +3344,15 @@ function enumerateSourceFiles(projectDir) {
3129
3344
  }
3130
3345
  function fileIdentity(p) {
3131
3346
  if (p === null) return "none::absent";
3132
- if (!existsSync18(p)) return `${p}::absent`;
3133
- 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")}`;
3134
3349
  }
3135
3350
  function computeConfigHash() {
3136
3351
  const repo = repoHome();
3137
3352
  const parts = [
3138
3353
  fileIdentity(resolveTomlPath(repo)),
3139
- fileIdentity(join23(repo, ".gitleaks.overlay.toml")),
3140
- fileIdentity(join23(repo, ".gitleaksignore"))
3354
+ fileIdentity(join24(repo, ".gitleaks.overlay.toml")),
3355
+ fileIdentity(join24(repo, ".gitleaksignore"))
3141
3356
  ];
3142
3357
  return createHash("sha256").update(parts.join("\n")).digest("hex");
3143
3358
  }
@@ -3148,7 +3363,7 @@ function isManifestShape(raw) {
3148
3363
  }
3149
3364
  function readManifest(path) {
3150
3365
  try {
3151
- const raw = readFileSync6(path, "utf8");
3366
+ const raw = readFileSync7(path, "utf8");
3152
3367
  const parsed = JSON.parse(raw);
3153
3368
  if (!isManifestShape(parsed)) return null;
3154
3369
  return parsed;
@@ -3171,18 +3386,19 @@ init_utils_json();
3171
3386
  var TMP_SUFFIX = ".nomad-tmp";
3172
3387
  function atomicMirror(src, dst, options) {
3173
3388
  const tmp = `${dst}${TMP_SUFFIX}`;
3174
- rmSync7(tmp, { recursive: true, force: true });
3175
- cpSync4(src, tmp, options);
3176
- 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 });
3177
3392
  renameSync3(tmp, dst);
3178
3393
  }
3179
- function copyDir(src, dst) {
3180
- atomicMirror(src, dst, { recursive: true, force: true });
3394
+ function overlaySessionDir(src, dst) {
3395
+ stripCollidingDstSymlinks(src, dst, () => false);
3396
+ cpSyncGuarded(src, dst, void 0, "overlaySessionDir");
3181
3397
  }
3182
3398
  function copyFileAtomic(src, dst) {
3183
3399
  mkdirSync5(dirname4(dst), { recursive: true });
3184
3400
  const tmp = `${dst}.tmp.${process.pid}`;
3185
- cpSync4(src, tmp, { force: true, preserveTimestamps: true });
3401
+ cpSync5(src, tmp, { force: true, preserveTimestamps: true });
3186
3402
  renameSync3(tmp, dst);
3187
3403
  }
3188
3404
  function hasDeltaForDir(sel, localDir) {
@@ -3198,12 +3414,12 @@ function applySelective(sel, localDir, repoDst) {
3198
3414
  const prefix = `${localDir}${sep3}`;
3199
3415
  for (const src of sel.changed) {
3200
3416
  if (!src.startsWith(prefix)) continue;
3201
- copyFileAtomic(src, join24(repoDst, relative3(localDir, src)));
3417
+ copyFileAtomic(src, join25(repoDst, relative5(localDir, src)));
3202
3418
  }
3203
3419
  for (const src of sel.deleted) {
3204
3420
  if (!src.startsWith(prefix)) continue;
3205
- const dst = join24(repoDst, relative3(localDir, src));
3206
- if (existsSync19(dst)) rmSync7(dst);
3421
+ const dst = join25(repoDst, relative5(localDir, src));
3422
+ if (existsSync20(dst)) rmSync8(dst);
3207
3423
  }
3208
3424
  }
3209
3425
  function copyDirJsonlOnly(src, dst) {
@@ -3211,7 +3427,7 @@ function copyDirJsonlOnly(src, dst) {
3211
3427
  recursive: true,
3212
3428
  force: true,
3213
3429
  filter: (srcPath) => {
3214
- const rel = relative3(src, srcPath);
3430
+ const rel = relative5(src, srcPath);
3215
3431
  if (rel === "") return true;
3216
3432
  if (rel.split(sep3).length > 1) return true;
3217
3433
  if (statSync5(srcPath).isDirectory()) return true;
@@ -3232,15 +3448,15 @@ function remapPull(ts, opts = {}) {
3232
3448
  const wouldPull = [];
3233
3449
  const repo = repoHome();
3234
3450
  const claude = claudeHome();
3235
- const mapPath = join24(repo, "path-map.json");
3236
- const repoProjects = join24(repo, "shared", "projects");
3237
- 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)) {
3238
3454
  const text = "no path-map or repo projects dir; skipping session remap";
3239
3455
  emitPreview(opts.onPreview, { kind: "note", text }, text);
3240
3456
  return { unmapped: 0, pulled, wouldPull };
3241
3457
  }
3242
3458
  const map = readPathMap(mapPath);
3243
- const localProjects = join24(claude, "projects");
3459
+ const localProjects = join25(claude, "projects");
3244
3460
  if (!dryRun) mkdirSync5(localProjects, { recursive: true });
3245
3461
  for (const [logical, hosts] of Object.entries(map.projects)) {
3246
3462
  assertSafeLogical(logical);
@@ -3250,9 +3466,9 @@ function remapPull(ts, opts = {}) {
3250
3466
  continue;
3251
3467
  }
3252
3468
  assertSafeLocalRoot(localPath, logical);
3253
- const src = join24(repoProjects, logical);
3254
- if (!existsSync19(src)) continue;
3255
- const dst = join24(localProjects, encodePath(localPath));
3469
+ const src = join25(repoProjects, logical);
3470
+ if (!existsSync20(src)) continue;
3471
+ const dst = join25(localProjects, encodePath(localPath));
3256
3472
  if (dryRun) {
3257
3473
  wouldPull.push(logical);
3258
3474
  emitPreview(
@@ -3263,11 +3479,44 @@ function remapPull(ts, opts = {}) {
3263
3479
  continue;
3264
3480
  }
3265
3481
  backupBeforeWrite(dst, ts);
3266
- copyDir(src, dst);
3482
+ overlaySessionDir(src, dst);
3267
3483
  pulled.push(logical);
3268
3484
  }
3269
3485
  return { unmapped, pulled, wouldPull };
3270
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
+ }
3271
3520
  function buildReverseMap(map) {
3272
3521
  const reverse = /* @__PURE__ */ new Map();
3273
3522
  const encodedPaths = /* @__PURE__ */ new Map();
@@ -3299,26 +3548,26 @@ function remapPush(ts, opts = {}) {
3299
3548
  const wouldPush = [];
3300
3549
  const repo = repoHome();
3301
3550
  const claude = claudeHome();
3302
- const mapPath = join24(repo, "path-map.json");
3303
- if (!existsSync19(mapPath)) {
3551
+ const mapPath = join25(repo, "path-map.json");
3552
+ if (!existsSync20(mapPath)) {
3304
3553
  log("no path-map.json; skipping session export");
3305
3554
  return { unmapped: 0, collisions: 0, pushed, wouldPush };
3306
3555
  }
3307
3556
  const map = readPathMap(mapPath);
3308
- const localProjects = join24(claude, "projects");
3309
- const repoProjects = join24(repo, "shared", "projects");
3557
+ const localProjects = join25(claude, "projects");
3558
+ const repoProjects = join25(repo, "shared", "projects");
3310
3559
  const reverse = buildReverseMap(map);
3311
- if (!existsSync19(localProjects)) return { unmapped, collisions: 0, pushed, wouldPush };
3560
+ if (!existsSync20(localProjects)) return { unmapped, collisions: 0, pushed, wouldPush };
3312
3561
  if (!dryRun) mkdirSync5(repoProjects, { recursive: true });
3313
- for (const dir of readdirSync8(localProjects)) {
3562
+ for (const dir of readdirSync9(localProjects)) {
3314
3563
  if (dir.endsWith(TMP_SUFFIX)) continue;
3315
3564
  const logical = reverse.get(dir);
3316
3565
  if (!logical) {
3317
3566
  unmapped++;
3318
3567
  continue;
3319
3568
  }
3320
- const localDir = join24(localProjects, dir);
3321
- const repoDst = join24(repoProjects, logical);
3569
+ const localDir = join25(localProjects, dir);
3570
+ const repoDst = join25(repoProjects, logical);
3322
3571
  if (skipForSelection(opts.selection, localDir)) continue;
3323
3572
  if (dryRun) {
3324
3573
  wouldPush.push(logical);
@@ -3342,8 +3591,8 @@ function buildScanTree(tmpRoot) {
3342
3591
  const logicalToEncoded = /* @__PURE__ */ new Map();
3343
3592
  let staged = 0;
3344
3593
  const repo = repoHome();
3345
- const mapPath = join25(repo, "path-map.json");
3346
- 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 };
3347
3596
  let map;
3348
3597
  try {
3349
3598
  map = readJson(mapPath);
@@ -3360,12 +3609,12 @@ function buildScanTree(tmpRoot) {
3360
3609
  if (!p || p === "TBD") continue;
3361
3610
  reverse.set(encodePath(p), logical);
3362
3611
  }
3363
- const localProjects = join25(claudeHome(), "projects");
3364
- if (!existsSync20(localProjects)) return { logicalToEncoded, staged, malformed: false };
3365
- 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)) {
3366
3615
  const logical = reverse.get(dir);
3367
3616
  if (!logical) continue;
3368
- copyDirJsonlOnly(join25(localProjects, dir), join25(tmpRoot, "shared", "projects", logical));
3617
+ copyDirJsonlOnly(join26(localProjects, dir), join26(tmpRoot, "shared", "projects", logical));
3369
3618
  logicalToEncoded.set(logical, dir);
3370
3619
  staged++;
3371
3620
  }
@@ -3396,11 +3645,11 @@ function ensureGitleaksReady(section2, gitleaksReady) {
3396
3645
  }
3397
3646
  function reportCheckShared(section2, gitleaksReady) {
3398
3647
  if (!ensureGitleaksReady(section2, gitleaksReady)) return;
3399
- const cacheDir = join25(homedir4(), ".cache", "claude-nomad");
3648
+ const cacheDir = join26(homedir4(), ".cache", "claude-nomad");
3400
3649
  mkdirSync6(cacheDir, { recursive: true });
3401
3650
  const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes(4).toString("hex")}`;
3402
- const reportPath = join25(cacheDir, `check-shared-${stamp}.json`);
3403
- 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}`);
3404
3653
  try {
3405
3654
  const { logicalToEncoded, staged, malformed } = buildScanTree(tmpRoot);
3406
3655
  if (malformed) {
@@ -3414,19 +3663,19 @@ function reportCheckShared(section2, gitleaksReady) {
3414
3663
  }
3415
3664
  scanAndReport(section2, tmpRoot, staged, logicalToEncoded);
3416
3665
  } finally {
3417
- rmSync8(reportPath, { force: true });
3418
- rmSync8(tmpRoot, { recursive: true, force: true });
3666
+ rmSync9(reportPath, { force: true });
3667
+ rmSync9(tmpRoot, { recursive: true, force: true });
3419
3668
  }
3420
3669
  }
3421
3670
 
3422
3671
  // src/commands.doctor.checks.hooks.scope.ts
3423
3672
  init_color();
3424
- import { existsSync as existsSync21, readFileSync as readFileSync7, readdirSync as readdirSync10, realpathSync as realpathSync2 } from "node:fs";
3425
- 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";
3426
3675
  init_config();
3427
3676
  function typeFromPackageJson(pkgPath) {
3428
3677
  try {
3429
- const parsed = JSON.parse(readFileSync7(pkgPath, "utf8"));
3678
+ const parsed = JSON.parse(readFileSync8(pkgPath, "utf8"));
3430
3679
  return parsed.type === "module" ? "esm" : "cjs";
3431
3680
  } catch {
3432
3681
  return "cjs";
@@ -3444,8 +3693,8 @@ function effectiveType(hookPath) {
3444
3693
  }
3445
3694
  let dir = dirname5(real);
3446
3695
  for (; ; ) {
3447
- const pkg = join26(dir, "package.json");
3448
- if (existsSync21(pkg)) return typeFromPackageJson(pkg);
3696
+ const pkg = join27(dir, "package.json");
3697
+ if (existsSync22(pkg)) return typeFromPackageJson(pkg);
3449
3698
  const parent = dirname5(dir);
3450
3699
  if (parent === dir) return "cjs";
3451
3700
  dir = parent;
@@ -3474,28 +3723,28 @@ function remedy(family) {
3474
3723
  }
3475
3724
  function safeReaddir2(dir) {
3476
3725
  try {
3477
- return readdirSync10(dir);
3726
+ return readdirSync11(dir);
3478
3727
  } catch {
3479
3728
  return [];
3480
3729
  }
3481
3730
  }
3482
3731
  function safeRead(path) {
3483
3732
  try {
3484
- return readFileSync7(path, "utf8");
3733
+ return readFileSync8(path, "utf8");
3485
3734
  } catch {
3486
3735
  return null;
3487
3736
  }
3488
3737
  }
3489
3738
  function reportHookScopeCheck(section2) {
3490
- const hooksDir = join26(claudeHome(), "hooks");
3491
- if (!existsSync21(hooksDir)) {
3739
+ const hooksDir = join27(claudeHome(), "hooks");
3740
+ if (!existsSync22(hooksDir)) {
3492
3741
  addItem(section2, `${dim(infoGlyph)} no ~/.claude/hooks; skipping module-scope check`);
3493
3742
  return;
3494
3743
  }
3495
3744
  let anyWarn = false;
3496
3745
  for (const name of safeReaddir2(hooksDir)) {
3497
3746
  if (extname(name) !== ".js") continue;
3498
- const abs = join26(hooksDir, name);
3747
+ const abs = join27(hooksDir, name);
3499
3748
  const eff = effectiveType(abs);
3500
3749
  if (eff === null) continue;
3501
3750
  const src = safeRead(abs);
@@ -3515,8 +3764,8 @@ function reportHookScopeCheck(section2) {
3515
3764
 
3516
3765
  // src/commands.doctor.checks.hooks.ts
3517
3766
  init_color();
3518
- import { existsSync as existsSync22 } from "node:fs";
3519
- import { join as join27 } from "node:path";
3767
+ import { existsSync as existsSync23 } from "node:fs";
3768
+ import { join as join28 } from "node:path";
3520
3769
  init_config();
3521
3770
  function expandHome(token) {
3522
3771
  const h2 = home();
@@ -3559,7 +3808,7 @@ function checkEventGroups(section2, event, groups) {
3559
3808
  for (const group of groups) {
3560
3809
  for (const cmd of commandsFromGroup(group)) {
3561
3810
  for (const resolved of claudePathsIn(cmd)) {
3562
- if (existsSync22(resolved)) continue;
3811
+ if (existsSync23(resolved)) continue;
3563
3812
  addItem(
3564
3813
  section2,
3565
3814
  `${red(failGlyph)} hooks/${event}: command target missing: ${resolved} (run nomad pull)`
@@ -3572,8 +3821,8 @@ function checkEventGroups(section2, event, groups) {
3572
3821
  return anyFail;
3573
3822
  }
3574
3823
  function reportHooksTargetCheck(section2) {
3575
- const settingsPath = join27(claudeHome(), "settings.json");
3576
- if (!existsSync22(settingsPath)) {
3824
+ const settingsPath = join28(claudeHome(), "settings.json");
3825
+ if (!existsSync23(settingsPath)) {
3577
3826
  addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json; skipping hook target check`);
3578
3827
  return;
3579
3828
  }
@@ -3596,12 +3845,12 @@ function reportHooksTargetCheck(section2) {
3596
3845
 
3597
3846
  // src/commands.doctor.checks.hooks.preserve-symlinks.ts
3598
3847
  init_color();
3599
- import { existsSync as existsSync24, readFileSync as readFileSync8 } from "node:fs";
3600
- 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";
3601
3850
 
3602
3851
  // src/commands.doctor.checks.hooks.preserve-symlinks.probe.ts
3603
- import { closeSync as closeSync3, existsSync as existsSync23, openSync as openSync3, readSync, realpathSync as realpathSync3 } from "node:fs";
3604
- 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";
3605
3854
  function suppressedRanges(src) {
3606
3855
  const ranges = [];
3607
3856
  const re = /\/\*[\s\S]*?\*\/|\/\/[^\n]*|'[^']*'|"[^"]*"|`[^`]*`/g;
@@ -3633,12 +3882,12 @@ function topRelativeSpecifiers(src) {
3633
3882
  }
3634
3883
  function specifierIsMissing(specifier, baseDir) {
3635
3884
  const base = resolve2(baseDir, specifier);
3636
- if (existsSync23(base)) return false;
3885
+ if (existsSync24(base)) return false;
3637
3886
  for (const ext of [".js", ".cjs", ".mjs"]) {
3638
- if (existsSync23(base + ext)) return false;
3887
+ if (existsSync24(base + ext)) return false;
3639
3888
  }
3640
3889
  for (const idx of ["index.js", "index.cjs", "index.mjs"]) {
3641
- if (existsSync23(join28(base, idx))) return false;
3890
+ if (existsSync24(join29(base, idx))) return false;
3642
3891
  }
3643
3892
  return true;
3644
3893
  }
@@ -3693,10 +3942,10 @@ function commandTokens(command) {
3693
3942
  return tokens;
3694
3943
  }
3695
3944
  function readPathMapSafe() {
3696
- const mapPath = join29(repoHome(), "path-map.json");
3697
- if (!existsSync24(mapPath)) return { projects: {} };
3945
+ const mapPath = join30(repoHome(), "path-map.json");
3946
+ if (!existsSync25(mapPath)) return { projects: {} };
3698
3947
  try {
3699
- return JSON.parse(readFileSync8(mapPath, "utf8"));
3948
+ return JSON.parse(readFileSync9(mapPath, "utf8"));
3700
3949
  } catch {
3701
3950
  return { projects: {} };
3702
3951
  }
@@ -3767,8 +4016,8 @@ function checkEventForPreserveSymlinks(section2, event, groups, sharedLinkNames)
3767
4016
  return anyWarn;
3768
4017
  }
3769
4018
  function reportPreserveSymlinksCheck(section2) {
3770
- const settingsPath = join29(claudeHome(), "settings.json");
3771
- if (!existsSync24(settingsPath)) {
4019
+ const settingsPath = join30(claudeHome(), "settings.json");
4020
+ if (!existsSync25(settingsPath)) {
3772
4021
  addItem(
3773
4022
  section2,
3774
4023
  `${dim(infoGlyph)} no ~/.claude/settings.json; skipping preserve-symlinks-main check`
@@ -3777,7 +4026,7 @@ function reportPreserveSymlinksCheck(section2) {
3777
4026
  }
3778
4027
  let settings;
3779
4028
  try {
3780
- settings = JSON.parse(readFileSync8(settingsPath, "utf8"));
4029
+ settings = JSON.parse(readFileSync9(settingsPath, "utf8"));
3781
4030
  } catch {
3782
4031
  return;
3783
4032
  }
@@ -3801,8 +4050,8 @@ function reportPreserveSymlinksCheck(section2) {
3801
4050
 
3802
4051
  // src/commands.doctor.checks.settings-drift.ts
3803
4052
  init_color();
3804
- import { existsSync as existsSync25, readFileSync as readFileSync9 } from "node:fs";
3805
- 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";
3806
4055
  init_config();
3807
4056
  init_utils_json();
3808
4057
  function diffMergedSettings(merged, settings) {
@@ -3811,7 +4060,7 @@ function diffMergedSettings(merged, settings) {
3811
4060
  }
3812
4061
  function tryReadJson(filePath) {
3813
4062
  try {
3814
- const raw = readFileSync9(filePath, "utf8");
4063
+ const raw = readFileSync10(filePath, "utf8");
3815
4064
  const parsed = JSON.parse(raw);
3816
4065
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
3817
4066
  return parsed;
@@ -3820,7 +4069,7 @@ function tryReadJson(filePath) {
3820
4069
  }
3821
4070
  }
3822
4071
  function reportHooksBaseSelfCleanNote(section2) {
3823
- const basePath = join30(repoHome(), "shared", "settings.base.json");
4072
+ const basePath = join31(repoHome(), "shared", "settings.base.json");
3824
4073
  const base = tryReadJson(basePath);
3825
4074
  if (base === null) return;
3826
4075
  if (!baseHasGsdHookEntries(base)) return;
@@ -3833,14 +4082,14 @@ function reportSettingsDriftCheck(section2) {
3833
4082
  const claude = claudeHome();
3834
4083
  const repo = repoHome();
3835
4084
  const host = HOST;
3836
- const settingsPath = join30(claude, "settings.json");
3837
- const basePath = join30(repo, "shared", "settings.base.json");
3838
- const hostPath = join30(repo, "hosts", `${host}.json`);
3839
- 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)) {
3840
4089
  addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json; skipping merge-drift check`);
3841
4090
  return;
3842
4091
  }
3843
- if (!existsSync25(basePath)) {
4092
+ if (!existsSync26(basePath)) {
3844
4093
  addItem(
3845
4094
  section2,
3846
4095
  `${dim(infoGlyph)} shared/settings.base.json missing; skipping merge-drift check`
@@ -3859,7 +4108,7 @@ function reportSettingsDriftCheck(section2) {
3859
4108
  if (settings === null) {
3860
4109
  return;
3861
4110
  }
3862
- const hostExists = existsSync25(hostPath);
4111
+ const hostExists = existsSync26(hostPath);
3863
4112
  const hostObj = hostExists ? tryReadJson(hostPath) : null;
3864
4113
  if (hostExists && hostObj === null) {
3865
4114
  addItem(
@@ -3908,12 +4157,12 @@ init_config();
3908
4157
 
3909
4158
  // src/commands.doctor.engine.ts
3910
4159
  init_color();
3911
- import { readFileSync as readFileSync11 } from "node:fs";
4160
+ import { readFileSync as readFileSync12 } from "node:fs";
3912
4161
  import { fileURLToPath as fileURLToPath3 } from "node:url";
3913
4162
 
3914
4163
  // src/commands.doctor.version.ts
3915
4164
  init_color();
3916
- import { readFileSync as readFileSync10 } from "node:fs";
4165
+ import { readFileSync as readFileSync11 } from "node:fs";
3917
4166
  import { fileURLToPath as fileURLToPath2 } from "node:url";
3918
4167
  init_config();
3919
4168
  var STRICT_SEMVER = /^\d+\.\d+\.\d+$/;
@@ -3930,7 +4179,7 @@ function compareSemver(a, b) {
3930
4179
  function readLocalVersion() {
3931
4180
  try {
3932
4181
  const pkgPath = fileURLToPath2(new URL("../package.json", import.meta.url));
3933
- const parsed = JSON.parse(readFileSync10(pkgPath, "utf8"));
4182
+ const parsed = JSON.parse(readFileSync11(pkgPath, "utf8"));
3934
4183
  if (typeof parsed.version === "string" && parsed.version.length > 0) {
3935
4184
  return parsed.version;
3936
4185
  }
@@ -3989,7 +4238,7 @@ function parseMinVersion(spec) {
3989
4238
  function readEnginesNode() {
3990
4239
  try {
3991
4240
  const pkgPath = fileURLToPath3(new URL("../package.json", import.meta.url));
3992
- const parsed = JSON.parse(readFileSync11(pkgPath, "utf8"));
4241
+ const parsed = JSON.parse(readFileSync12(pkgPath, "utf8"));
3993
4242
  const node = parsed.engines?.node;
3994
4243
  if (typeof node === "string" && node.length > 0) return node;
3995
4244
  return null;
@@ -4017,44 +4266,44 @@ function reportNodeEngineCheck(section2) {
4017
4266
 
4018
4267
  // src/spinner.ts
4019
4268
  init_color();
4020
- import { existsSync as existsSync29 } from "node:fs";
4269
+ import { existsSync as existsSync30 } from "node:fs";
4021
4270
  import { fileURLToPath as fileURLToPath4 } from "node:url";
4022
4271
  import { Worker } from "node:worker_threads";
4023
4272
 
4024
4273
  // src/commands.push.recovery.ts
4025
4274
  init_config();
4026
- import { readFileSync as readFileSync14, rmSync as rmSync10, writeFileSync as writeFileSync5 } from "node:fs";
4027
- 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";
4028
4277
  import { createInterface as createInterface2 } from "node:readline/promises";
4029
4278
 
4030
4279
  // src/commands.push.recovery.actions.ts
4031
4280
  init_config();
4032
- import { readFileSync as readFileSync13 } from "node:fs";
4281
+ import { readFileSync as readFileSync14 } from "node:fs";
4033
4282
  import { isAbsolute as isAbsolute2, resolve as resolve3, sep as sep5 } from "node:path";
4034
4283
 
4035
4284
  // src/commands.push.recovery.redact.ts
4036
4285
  init_config();
4037
4286
  init_config_sharedDirs_guard();
4038
- import { cpSync as cpSync5, existsSync as existsSync28, mkdirSync as mkdirSync7, statSync as statSync8 } from "node:fs";
4039
- 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";
4040
4289
 
4041
4290
  // src/commands.redact.ts
4042
4291
  init_config();
4043
- import { existsSync as existsSync27, statSync as statSync7 } from "node:fs";
4044
- 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";
4045
4294
 
4046
4295
  // src/commands.redact.subtree.ts
4047
- import { existsSync as existsSync26, lstatSync as lstatSync8, readFileSync as readFileSync12, readdirSync as readdirSync11, statSync as statSync6, writeFileSync as writeFileSync4 } from "node:fs";
4048
- 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";
4049
4298
  init_utils_fs();
4050
4299
  init_utils();
4051
4300
  function collectFiles(dir, out) {
4052
- if (!existsSync26(dir)) return;
4053
- const st = lstatSync8(dir);
4301
+ if (!existsSync27(dir)) return;
4302
+ const st = lstatSync10(dir);
4054
4303
  if (!st.isDirectory()) return;
4055
- for (const entry of readdirSync11(dir)) {
4056
- const abs = join31(dir, entry);
4057
- const lst = lstatSync8(abs);
4304
+ for (const entry of readdirSync12(dir)) {
4305
+ const abs = join32(dir, entry);
4306
+ const lst = lstatSync10(abs);
4058
4307
  if (lst.isSymbolicLink()) continue;
4059
4308
  if (lst.isDirectory()) {
4060
4309
  collectFiles(abs, out);
@@ -4090,7 +4339,7 @@ function applySubtreeRedactions(mainPath, mainFindings, subtreeFiles, rule, ts,
4090
4339
  if (!dryRun && total > 0) {
4091
4340
  for (const { path: filePath, findings } of dirty) {
4092
4341
  backupBeforeWrite(filePath, ts);
4093
- const before = readFileSync12(filePath, "utf8");
4342
+ const before = readFileSync13(filePath, "utf8");
4094
4343
  const after = applyRedactions(before, findings);
4095
4344
  if (after === before) {
4096
4345
  log(
@@ -4158,15 +4407,15 @@ init_utils_json();
4158
4407
  init_utils();
4159
4408
  function resolveLiveTranscript(id) {
4160
4409
  try {
4161
- const mapPath = join32(repoHome(), "path-map.json");
4162
- if (!existsSync27(mapPath)) return null;
4410
+ const mapPath = join33(repoHome(), "path-map.json");
4411
+ if (!existsSync28(mapPath)) return null;
4163
4412
  const projects = readJson(mapPath).projects;
4164
4413
  const claude = claudeHome();
4165
4414
  for (const hostMap of Object.values(projects)) {
4166
4415
  const abs = hostMap[HOST];
4167
4416
  if (abs === void 0) continue;
4168
- const live = join32(claude, "projects", encodePath(abs), `${id}.jsonl`);
4169
- if (existsSync27(live)) return live;
4417
+ const live = join33(claude, "projects", encodePath(abs), `${id}.jsonl`);
4418
+ if (existsSync28(live)) return live;
4170
4419
  }
4171
4420
  return null;
4172
4421
  } catch {
@@ -4186,17 +4435,17 @@ function cmdRedact(opts, nowMs = Date.now, scan = scanFile) {
4186
4435
  }
4187
4436
  const repo = repoHome();
4188
4437
  const backup = backupBase();
4189
- if (!existsSync27(repo)) die(`repo not cloned at ${repo}`);
4438
+ if (!existsSync28(repo)) die(`repo not cloned at ${repo}`);
4190
4439
  const handle = acquireLock("redact");
4191
4440
  if (handle === null) process.exit(0);
4192
4441
  try {
4193
4442
  const localPath = resolveLiveTranscript(id);
4194
- if (localPath === null || !existsSync27(localPath)) {
4443
+ if (localPath === null || !existsSync28(localPath)) {
4195
4444
  fail(`could not resolve local transcript for session ${id} on this host`);
4196
4445
  process.exitCode = 1;
4197
4446
  return;
4198
4447
  }
4199
- const sessionDir = join32(dirname7(localPath), id);
4448
+ const sessionDir = join33(dirname7(localPath), id);
4200
4449
  const subtreeFiles = listSubtreeFiles(sessionDir);
4201
4450
  const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync7(p).mtimeMs);
4202
4451
  if (isRecentlyModified(subtreeMtime, nowMs())) {
@@ -4312,8 +4561,8 @@ function resolveStagedDir(localPath, map, claude, repo) {
4312
4561
  assertSafeLogical(logical);
4313
4562
  const abs = hostMap[HOST];
4314
4563
  if (abs === void 0) continue;
4315
- if (localPath.startsWith(join33(claude, "projects", encodePath(abs)) + sep4)) {
4316
- return join33(repo, "shared", "projects", logical);
4564
+ if (localPath.startsWith(join34(claude, "projects", encodePath(abs)) + sep4)) {
4565
+ return join34(repo, "shared", "projects", logical);
4317
4566
  }
4318
4567
  }
4319
4568
  return null;
@@ -4323,7 +4572,7 @@ function preflightRedactable(f, map, nowMs) {
4323
4572
  if (sid === null) return "a finding has no resolvable session id (not a session transcript)";
4324
4573
  const localPath = resolveLiveTranscript(sid);
4325
4574
  if (localPath === null) return `session ${sid}: local transcript not found`;
4326
- const sessionDir = join33(dirname8(localPath), sid);
4575
+ const sessionDir = join34(dirname8(localPath), sid);
4327
4576
  const subtreeFiles = listSubtreeFiles(sessionDir);
4328
4577
  const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync8(p).mtimeMs);
4329
4578
  if (isRecentlyModified(subtreeMtime, nowMs())) {
@@ -4353,7 +4602,7 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
4353
4602
  `could not locate the local transcript for session ${sid}; choose Skip or Drop session.`
4354
4603
  );
4355
4604
  }
4356
- const sessionDir = join33(dirname8(localPath), sid);
4605
+ const sessionDir = join34(dirname8(localPath), sid);
4357
4606
  const subtreeFiles = listSubtreeFiles(sessionDir);
4358
4607
  const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync8(p).mtimeMs);
4359
4608
  if (isRecentlyModified(subtreeMtime, nowMs())) {
@@ -4387,26 +4636,26 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
4387
4636
  );
4388
4637
  }
4389
4638
  mkdirSync7(stagedProjectDir, { recursive: true });
4390
- cpSync5(localPath, join33(stagedProjectDir, `${sid}.jsonl`), { force: true });
4391
- if (existsSync28(sessionDir)) {
4392
- 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 });
4393
4642
  }
4394
4643
  return true;
4395
4644
  }
4396
4645
 
4397
4646
  // src/commands.push.recovery.drop.ts
4398
4647
  init_config();
4399
- import { rmSync as rmSync9 } from "node:fs";
4400
- import { join as join34 } from "node:path";
4648
+ import { rmSync as rmSync10 } from "node:fs";
4649
+ import { join as join35 } from "node:path";
4401
4650
  function dropSessionFromStaged(sid, map) {
4402
4651
  const logicals = Object.keys(map.projects);
4403
4652
  if (logicals.length === 0) return false;
4404
4653
  const repo = repoHome();
4405
4654
  for (const logical of logicals) {
4406
- const jsonl = join34(repo, "shared", "projects", logical, `${sid}.jsonl`);
4407
- const dir = join34(repo, "shared", "projects", logical, sid);
4408
- rmSync9(jsonl, { force: true });
4409
- 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 });
4410
4659
  }
4411
4660
  return true;
4412
4661
  }
@@ -4440,7 +4689,7 @@ function makeDefaultReadLine(repo) {
4440
4689
  if (isAbsolute2(file) || target !== repoRoot && !target.startsWith(repoRoot + sep5)) {
4441
4690
  return null;
4442
4691
  }
4443
- const content = readFileSync13(target, "utf8");
4692
+ const content = readFileSync14(target, "utf8");
4444
4693
  const lines = content.split(/\r?\n/);
4445
4694
  const idx = line - 1;
4446
4695
  if (idx < 0 || idx >= lines.length) return null;
@@ -4561,10 +4810,10 @@ function applyThenRescan(scanVerdict, repoHome2) {
4561
4810
  return next;
4562
4811
  }
4563
4812
  function allowThenRescan(append, scanVerdict, repoHome2) {
4564
- const ignPath = join35(repoHome2, ".gitleaksignore");
4813
+ const ignPath = join36(repoHome2, ".gitleaksignore");
4565
4814
  let before;
4566
4815
  try {
4567
- before = readFileSync14(ignPath, "utf8");
4816
+ before = readFileSync15(ignPath, "utf8");
4568
4817
  } catch {
4569
4818
  before = null;
4570
4819
  }
@@ -4572,7 +4821,7 @@ function allowThenRescan(append, scanVerdict, repoHome2) {
4572
4821
  try {
4573
4822
  return applyThenRescan(scanVerdict, repoHome2);
4574
4823
  } catch (err) {
4575
- if (before === null) rmSync10(ignPath, { force: true });
4824
+ if (before === null) rmSync11(ignPath, { force: true });
4576
4825
  else writeFileSync5(ignPath, before, "utf8");
4577
4826
  throw err;
4578
4827
  }
@@ -4660,7 +4909,7 @@ function writeAnimatedDone(out, label, ms, useTTY) {
4660
4909
  `);
4661
4910
  }
4662
4911
  function resolveWorkerPath(deps = {}) {
4663
- const check = deps.existsSyncFn ?? existsSync29;
4912
+ const check = deps.existsSyncFn ?? existsSync30;
4664
4913
  const base = deps.baseUrl ?? import.meta.url;
4665
4914
  const mjs = fileURLToPath4(new URL("./nomad.worker.mjs", base));
4666
4915
  if (check(mjs)) return mjs;
@@ -4727,8 +4976,8 @@ function withSpinner(label, fn, deps) {
4727
4976
  // src/commands.doctor.gitleaks-version.ts
4728
4977
  init_color();
4729
4978
  import { execFileSync as execFileSync11 } from "node:child_process";
4730
- import { existsSync as existsSync30 } from "node:fs";
4731
- import { join as join36 } from "node:path";
4979
+ import { existsSync as existsSync31 } from "node:fs";
4980
+ import { join as join37 } from "node:path";
4732
4981
  init_config();
4733
4982
  var SEMVER_MAJOR_MINOR = /^(\d+)\.(\d+)\.\d+$/;
4734
4983
  var GITLEAKS_TIMEOUT_MS = 5e3;
@@ -4737,7 +4986,7 @@ function majorMinorOf(value) {
4737
4986
  return m === null ? null : [m[1], m[2]];
4738
4987
  }
4739
4988
  function readGitleaksVersion(run, tomlExists) {
4740
- const tomlPath = join36(repoHome(), ".gitleaks.toml");
4989
+ const tomlPath = join37(repoHome(), ".gitleaks.toml");
4741
4990
  const args = ["version"];
4742
4991
  if (tomlExists(tomlPath)) args.push("--config", tomlPath);
4743
4992
  try {
@@ -4749,7 +4998,7 @@ function readGitleaksVersion(run, tomlExists) {
4749
4998
  return null;
4750
4999
  }
4751
5000
  }
4752
- function reportGitleaksVersionCheck(section2, run = execFileSync11, tomlExists = existsSync30) {
5001
+ function reportGitleaksVersionCheck(section2, run = execFileSync11, tomlExists = existsSync31) {
4753
5002
  const raw = readGitleaksVersion(run, tomlExists);
4754
5003
  if (raw === null) return;
4755
5004
  const local = majorMinorOf(raw);
@@ -4972,8 +5221,8 @@ function gatherDoctorSections(opts) {
4972
5221
  reportHostKeyAlignment(host);
4973
5222
  reportRepoState(host);
4974
5223
  const links = section("Shared links");
4975
- const mapPath = join37(repoHome(), "path-map.json");
4976
- 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;
4977
5226
  const map = rawMap ?? { projects: {} };
4978
5227
  reportSharedLinks(links, map);
4979
5228
  reportDroppedNamesMigration(links);
@@ -5070,8 +5319,8 @@ function parseDoctorArgs(args) {
5070
5319
  // src/commands.drop-session.ts
5071
5320
  init_config();
5072
5321
  import { execFileSync as execFileSync16 } from "node:child_process";
5073
- import { existsSync as existsSync33, readdirSync as readdirSync12, statSync as statSync9 } from "node:fs";
5074
- 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";
5075
5324
 
5076
5325
  // src/commands.drop-session.git.ts
5077
5326
  import { execFileSync as execFileSync15 } from "node:child_process";
@@ -5113,8 +5362,8 @@ function isInIndex(rel, repo) {
5113
5362
  init_config();
5114
5363
  init_utils();
5115
5364
  init_utils_json();
5116
- import { existsSync as existsSync32 } from "node:fs";
5117
- import { join as join38 } from "node:path";
5365
+ import { existsSync as existsSync33 } from "node:fs";
5366
+ import { join as join39 } from "node:path";
5118
5367
  var SHARED_PROJECT_LOGICAL = /^shared\/projects\/([^/]+)\//;
5119
5368
  function reportScrubHint(id, matches) {
5120
5369
  const live = resolveLiveTranscript2(id, matches);
@@ -5130,8 +5379,8 @@ function reportScrubHint(id, matches) {
5130
5379
  }
5131
5380
  function resolveLiveTranscript2(id, matches) {
5132
5381
  try {
5133
- const mapPath = join38(repoHome(), "path-map.json");
5134
- if (!existsSync32(mapPath)) return null;
5382
+ const mapPath = join39(repoHome(), "path-map.json");
5383
+ if (!existsSync33(mapPath)) return null;
5135
5384
  const projects = readJson(mapPath).projects;
5136
5385
  const claude = claudeHome();
5137
5386
  for (const rel of matches) {
@@ -5139,8 +5388,8 @@ function resolveLiveTranscript2(id, matches) {
5139
5388
  if (logical === void 0) continue;
5140
5389
  const abs = projects[logical]?.[HOST];
5141
5390
  if (abs === void 0) continue;
5142
- const live = join38(claude, "projects", encodePath(abs), `${id}.jsonl`);
5143
- if (existsSync32(live)) return live;
5391
+ const live = join39(claude, "projects", encodePath(abs), `${id}.jsonl`);
5392
+ if (existsSync33(live)) return live;
5144
5393
  }
5145
5394
  return null;
5146
5395
  } catch {
@@ -5156,12 +5405,12 @@ function cmdDropSession(id) {
5156
5405
  process.exit(1);
5157
5406
  }
5158
5407
  const repo = repoHome();
5159
- if (!existsSync33(repo)) die(`repo not cloned at ${repo}`);
5408
+ if (!existsSync34(repo)) die(`repo not cloned at ${repo}`);
5160
5409
  const handle = acquireLock("drop-session");
5161
5410
  if (handle === null) process.exit(0);
5162
5411
  try {
5163
- const repoProjects = join39(repo, "shared", "projects");
5164
- if (!existsSync33(repoProjects)) {
5412
+ const repoProjects = join40(repo, "shared", "projects");
5413
+ if (!existsSync34(repoProjects)) {
5165
5414
  throw new NomadFatal(`no staged session matches ${id}`);
5166
5415
  }
5167
5416
  const matches = collectMatches(repoProjects, id, repo);
@@ -5183,14 +5432,14 @@ function cmdDropSession(id) {
5183
5432
  }
5184
5433
  function collectMatches(repoProjects, id, repo) {
5185
5434
  const matches = [];
5186
- for (const logical of readdirSync12(repoProjects)) {
5187
- const candidate = join39(repoProjects, logical, `${id}.jsonl`);
5188
- if (existsSync33(candidate)) {
5189
- matches.push(relative4(repo, candidate));
5190
- }
5191
- const dir = join39(repoProjects, logical, id);
5192
- if (existsSync33(dir) && statSync9(dir).isDirectory()) {
5193
- 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);
5194
5443
  const staged = expandStagedDir(dirRel, repo);
5195
5444
  if (staged.length > 0) matches.push(...staged);
5196
5445
  else matches.push(dirRel);
@@ -5232,7 +5481,7 @@ init_color();
5232
5481
 
5233
5482
  // src/summary.ts
5234
5483
  init_utils();
5235
- function summaryText(verb, unmapped, collisions = 0, extrasSkipped = 0) {
5484
+ function summaryText(verb, unmapped, collisions = 0, extrasSkipped = 0, localOnly = 0) {
5236
5485
  const extras = extrasSkipped > 0 ? `, ${extrasSkipped} extras skipped` : "";
5237
5486
  if (verb === "push") {
5238
5487
  if (unmapped === 0 && collisions === 0 && extrasSkipped === 0) {
@@ -5241,16 +5490,17 @@ function summaryText(verb, unmapped, collisions = 0, extrasSkipped = 0) {
5241
5490
  const base = `summary: ${unmapped} unmapped on push, ${collisions} collisions`;
5242
5491
  return { text: `${base}${extras} (run nomad doctor to list)`, clean: false };
5243
5492
  }
5244
- if (unmapped === 0 && extrasSkipped === 0) {
5493
+ if (unmapped === 0 && extrasSkipped === 0 && localOnly === 0) {
5245
5494
  return { text: "summary: clean", clean: true };
5246
5495
  }
5496
+ const localOnlyPhrase = localOnly > 0 ? `, ${localOnly} local-only present (push to reconcile)` : "";
5247
5497
  return {
5248
- 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}`,
5249
5499
  clean: false
5250
5500
  };
5251
5501
  }
5252
- function summaryRow(verb, unmapped, collisions = 0, extrasSkipped = 0) {
5253
- 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);
5254
5504
  return text.replace(/^summary: /, "");
5255
5505
  }
5256
5506
 
@@ -5264,11 +5514,17 @@ function buildSettingsSection(label) {
5264
5514
  addItem(s, `${green(okGlyph)} settings.json (base + ${label})`);
5265
5515
  return s;
5266
5516
  }
5267
- function buildSessionsSection(items, unmapped) {
5517
+ function buildSessionsSection(items, unmapped, localOnly = 0) {
5268
5518
  const s = section("Sessions");
5269
5519
  for (const logical of items) addItem(s, `${green(okGlyph)} ${logical}`);
5270
5520
  const skip = collapsedSkipRow(unmapped, "not in path-map (run nomad doctor to list)");
5271
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
+ }
5272
5528
  return s;
5273
5529
  }
5274
5530
  function buildExtrasSection(items, extrasSkipped) {
@@ -5322,167 +5578,12 @@ init_config();
5322
5578
  init_config();
5323
5579
  import { existsSync as existsSync36, statSync as statSync10 } from "node:fs";
5324
5580
  import { join as join43 } from "node:path";
5325
-
5326
- // src/extras-sync.core.ts
5327
- init_config();
5328
- import { cpSync as cpSync6, existsSync as existsSync34, lstatSync as lstatSync9, readdirSync as readdirSync13, rmSync as rmSync11 } from "node:fs";
5329
- import { basename, join as join40 } from "node:path";
5330
- init_utils();
5331
- init_utils_json();
5332
- function loadValidatedExtras(opts) {
5333
- const repo = repoHome();
5334
- const mapPath = join40(repo, "path-map.json");
5335
- const repoExtras = join40(repo, "shared", "extras");
5336
- if (!existsSync34(mapPath) || opts.requireRepoExtras === true && !existsSync34(repoExtras)) {
5337
- if (opts.missingMsg !== void 0) log(opts.missingMsg);
5338
- return null;
5339
- }
5340
- const map = readPathMap(mapPath);
5341
- const extrasMap = map.extras ?? {};
5342
- if (Object.keys(extrasMap).length === 0) return null;
5343
- for (const logical of Object.keys(extrasMap)) {
5344
- assertSafeLogical(logical);
5345
- const localRoot = map.projects[logical]?.[HOST];
5346
- if (localRoot && localRoot !== "TBD") assertSafeLocalRoot(localRoot, logical);
5347
- }
5348
- return { map, extrasMap };
5349
- }
5350
- function* eachExtrasTarget(v, counts) {
5351
- const whitelist = SUPPORTED_EXTRAS;
5352
- for (const [logical, dirnames] of Object.entries(v.extrasMap)) {
5353
- const localRoot = v.map.projects[logical]?.[HOST];
5354
- if (!localRoot || localRoot === "TBD") {
5355
- counts.unmapped++;
5356
- continue;
5357
- }
5358
- for (const dirname10 of dirnames) {
5359
- if (!whitelist.includes(dirname10)) {
5360
- counts.skipped++;
5361
- continue;
5362
- }
5363
- yield { logical, localRoot, dirname: dirname10 };
5364
- }
5365
- }
5366
- }
5367
- function stripCollidingDstSymlinks(src, dst, isExcluded) {
5368
- if (!existsSync34(dst)) return;
5369
- for (const name of readdirSync13(src)) {
5370
- if (isExcluded(name)) continue;
5371
- const dstPath = join40(dst, name);
5372
- const dstStat = lstatSync9(dstPath, { throwIfNoEntry: false });
5373
- if (dstStat === void 0) continue;
5374
- if (dstStat.isSymbolicLink()) {
5375
- rmSync11(dstPath, { recursive: true, force: true });
5376
- } else if (dstStat.isDirectory() && lstatSync9(join40(src, name)).isDirectory()) {
5377
- stripCollidingDstSymlinks(join40(src, name), dstPath, isExcluded);
5378
- }
5379
- }
5380
- }
5381
- function copyExtrasOverlayFiltered(src, dst, blockSet) {
5382
- stripCollidingDstSymlinks(src, dst, (name) => isDeniedName(blockSet, name));
5383
- try {
5384
- cpSync6(src, dst, {
5385
- recursive: true,
5386
- force: true,
5387
- verbatimSymlinks: true,
5388
- filter: (srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename(srcEntry))
5389
- });
5390
- } catch (err) {
5391
- const e = err;
5392
- 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") {
5393
- throw new NomadFatal(
5394
- `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`
5395
- );
5396
- }
5397
- throw err;
5398
- }
5399
- }
5400
- function copyExtras(src, dst) {
5401
- rmSync11(dst, { recursive: true, force: true });
5402
- cpSync6(src, dst, { recursive: true, force: true, verbatimSymlinks: true });
5403
- }
5404
- function extrasDenySet(dirname10) {
5405
- return dirname10 === ".claude" ? CLAUDE_EXTRA_NEVER_SYNC : ALWAYS_NEVER_SYNC;
5406
- }
5407
- function copyExtrasFiltered(src, dst, blockSet) {
5408
- rmSync11(dst, { recursive: true, force: true });
5409
- cpSync6(src, dst, {
5410
- recursive: true,
5411
- force: true,
5412
- verbatimSymlinks: true,
5413
- filter: (srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename(srcEntry))
5414
- });
5415
- }
5416
- function prunePreservingDenied(src, dst, blockSet) {
5417
- for (const name of readdirSync13(dst)) {
5418
- if (isDeniedName(blockSet, name)) continue;
5419
- const dstPath = join40(dst, name);
5420
- const srcStat = lstatSync9(join40(src, name), { throwIfNoEntry: false });
5421
- if (srcStat === void 0) {
5422
- rmSync11(dstPath, { recursive: true, force: true });
5423
- continue;
5424
- }
5425
- const dstStat = lstatSync9(dstPath);
5426
- if (srcStat.isDirectory() && dstStat.isDirectory()) {
5427
- prunePreservingDenied(join40(src, name), dstPath, blockSet);
5428
- } else if (srcStat.isDirectory() !== dstStat.isDirectory()) {
5429
- rmSync11(dstPath, { recursive: true, force: true });
5430
- }
5431
- }
5432
- }
5433
- function copyExtrasFilteredPreserving(src, dst, blockSet) {
5434
- const dstStat = lstatSync9(dst, { throwIfNoEntry: false });
5435
- if (dstStat !== void 0) {
5436
- if (dstStat.isDirectory()) prunePreservingDenied(src, dst, blockSet);
5437
- else rmSync11(dst, { recursive: true, force: true });
5438
- }
5439
- stripCollidingDstSymlinks(src, dst, (name) => isDeniedName(blockSet, name));
5440
- cpSync6(src, dst, {
5441
- recursive: true,
5442
- force: true,
5443
- verbatimSymlinks: true,
5444
- filter: (srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename(srcEntry))
5445
- });
5446
- }
5447
- function prunePreservingBy(src, dst, isPreserved) {
5448
- for (const name of readdirSync13(dst)) {
5449
- if (isPreserved(name)) continue;
5450
- const dstPath = join40(dst, name);
5451
- const srcStat = lstatSync9(join40(src, name), { throwIfNoEntry: false });
5452
- if (srcStat === void 0) {
5453
- rmSync11(dstPath, { recursive: true, force: true });
5454
- continue;
5455
- }
5456
- const dstStat = lstatSync9(dstPath);
5457
- if (srcStat.isDirectory() && dstStat.isDirectory()) {
5458
- prunePreservingBy(join40(src, name), dstPath, isPreserved);
5459
- } else if (srcStat.isDirectory() !== dstStat.isDirectory()) {
5460
- rmSync11(dstPath, { recursive: true, force: true });
5461
- }
5462
- }
5463
- }
5464
- function copyExtrasFilteredPreservingBy(src, dst, isPreserved) {
5465
- const dstStat = lstatSync9(dst, { throwIfNoEntry: false });
5466
- if (dstStat !== void 0) {
5467
- if (dstStat.isDirectory()) prunePreservingBy(src, dst, isPreserved);
5468
- else rmSync11(dst, { recursive: true, force: true });
5469
- }
5470
- stripCollidingDstSymlinks(src, dst, isPreserved);
5471
- cpSync6(src, dst, {
5472
- recursive: true,
5473
- force: true,
5474
- verbatimSymlinks: true,
5475
- filter: (srcEntry) => srcEntry === src || !isPreserved(basename(srcEntry))
5476
- });
5477
- }
5478
-
5479
- // src/extras-sync.ts
5480
5581
  init_utils();
5481
5582
  init_utils_json();
5482
5583
 
5483
5584
  // src/extras-sync.remap.ts
5484
5585
  init_config();
5485
- 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";
5486
5587
  import { dirname as dirname9, join as join42, sep as sep7 } from "node:path";
5487
5588
 
5488
5589
  // src/extras-sync.planning-diff.ts
@@ -5611,24 +5712,58 @@ function deletePlanningTarget(target, planningRoot, repoCounterpart) {
5611
5712
  rmSync12(target, { recursive: true, force: true });
5612
5713
  pruneEmptyAncestors(target, planningRoot);
5613
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
+ }
5614
5760
  function propagatePlanningDeletes(v, ts, prePostHeads, repo) {
5615
5761
  const repoExtras = join42(repo, "shared", "extras");
5616
5762
  for (const t of eachExtrasTarget(v, { unmapped: 0, skipped: 0 })) {
5617
5763
  if (t.dirname !== ".planning") continue;
5618
5764
  let raw;
5619
5765
  try {
5620
- raw = gitCaptureRaw(
5621
- [
5622
- "diff",
5623
- "--name-status",
5624
- "-z",
5625
- prePostHeads.pre,
5626
- prePostHeads.post,
5627
- "--",
5628
- `shared/extras/${t.logical}/.planning/`
5629
- ],
5630
- repo
5631
- );
5766
+ raw = gitCaptureRaw(planningDiffArgs(prePostHeads.pre, prePostHeads.post, t.logical), repo);
5632
5767
  } catch (err) {
5633
5768
  const e = err;
5634
5769
  if (e.stderr) process.stderr.write(e.stderr);
@@ -5636,12 +5771,15 @@ function propagatePlanningDeletes(v, ts, prePostHeads, repo) {
5636
5771
  `git diff failed while propagating .planning deletes for ${t.logical}; run nomad pull --force-remote to recover`
5637
5772
  );
5638
5773
  }
5639
- const targets = planningDeleteTargets({ raw, logical: t.logical, localRoot: t.localRoot });
5640
- if (targets.length === 0) continue;
5774
+ const pairs = deletePairsFor(t, raw);
5775
+ if (pairs.length === 0) continue;
5641
5776
  backupExtrasWrite(join42(t.localRoot, t.dirname), ts, t.localRoot);
5642
5777
  const planningRoot = join42(t.localRoot, ".planning");
5643
- for (const target of targets) {
5644
- 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
+ }
5645
5783
  deletePlanningTarget(target, planningRoot, join42(repoExtras, t.logical, relToLocal));
5646
5784
  }
5647
5785
  }
@@ -5690,23 +5828,30 @@ function remapExtrasPull(ts, opts = {}) {
5690
5828
  src: join42(repo, "shared", "extras", logical, dirname10),
5691
5829
  dst: join42(localRoot, dirname10)
5692
5830
  }),
5693
- // 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
5694
5832
  // localRoot so the backup tree mirrors the project layout.
5695
5833
  (dst, localRoot) => backupExtrasWrite(dst, ts, localRoot),
5696
5834
  // Pull routing per extra type:
5697
5835
  // `.claude`: copyExtrasFilteredPreserving preserves host-local deny-set
5698
5836
  // files (e.g. settings.local.json) while mirror-pruning synced entries.
5699
- // `.planning`: copyExtrasOverlayFiltered (no rmSync; deny-set filtered)
5700
- // keeps local-only files; the delete pass below propagates upstream
5701
- // removals via the git-diff D set. The filter is defense-in-depth
5702
- // against a repo poisoned out-of-band.
5703
- // 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`.
5704
5847
  (src, dst, dirname10) => {
5705
5848
  if (dirname10 === ".claude")
5706
5849
  return copyExtrasFilteredPreserving(src, dst, extrasDenySet(dirname10));
5707
- if (dirname10 === ".planning")
5708
- return copyExtrasOverlayFiltered(src, dst, extrasDenySet(dirname10));
5709
- 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);
5710
5855
  }
5711
5856
  );
5712
5857
  if (!dryRun && prePostHeads !== void 0) {
@@ -5721,11 +5866,10 @@ function divergenceWarnLine(o) {
5721
5866
  const name = o.isDir ? `${o.dirname}/` : o.dirname;
5722
5867
  const one = o.count === 1;
5723
5868
  const fileCount = one ? "1 file" : `${o.count} files`;
5724
- const them = one ? "it" : "them";
5725
5869
  const yours = one ? "your current file is" : "your current files are";
5726
- 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}/)`;
5727
5871
  }
5728
- function divergenceCheckExtras(ts) {
5872
+ function divergenceCheckExtras(ts, prePostHeads) {
5729
5873
  const v = loadValidatedExtras({});
5730
5874
  if (v === null) return;
5731
5875
  const counts = { unmapped: 0, skipped: 0 };
@@ -5735,25 +5879,30 @@ function divergenceCheckExtras(ts) {
5735
5879
  const local = join43(localRoot, dirname10);
5736
5880
  const repoEntry = join43(repo, "shared", "extras", logical, dirname10);
5737
5881
  if (!existsSync36(local) || !existsSync36(repoEntry)) continue;
5738
- const diff = listDivergingFiles(local, repoEntry);
5739
- if (diff.length === 0) continue;
5882
+ const modified = listDivergingModified(local, repoEntry);
5883
+ if (modified.length === 0) continue;
5740
5884
  const projectBackupRoot = join43(backupRoot, encodePath(localRoot));
5741
5885
  warn(
5742
5886
  divergenceWarnLine({
5743
5887
  dirname: dirname10,
5744
5888
  logical,
5745
5889
  isDir: statSync10(local).isDirectory(),
5746
- count: diff.length,
5890
+ count: modified.length,
5747
5891
  projectBackupRoot
5748
5892
  })
5749
5893
  );
5750
- 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
+ }
5751
5900
  }
5752
5901
  }
5753
5902
 
5754
5903
  // src/skills-sync.ts
5755
5904
  init_config();
5756
- 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";
5757
5906
  import { join as join44 } from "node:path";
5758
5907
  init_utils_fs();
5759
5908
  function isGsdOwned(name) {
@@ -5777,7 +5926,7 @@ function syncSkillsPull(ts) {
5777
5926
  const sharedSkills = join44(repoHome(), "shared", "skills");
5778
5927
  if (!existsSync37(sharedSkills)) return;
5779
5928
  const localSkills = join44(claudeHome(), "skills");
5780
- const dstStat = lstatSync10(localSkills, { throwIfNoEntry: false });
5929
+ const dstStat = lstatSync11(localSkills, { throwIfNoEntry: false });
5781
5930
  if (dstStat?.isSymbolicLink() === true) {
5782
5931
  backupBeforeWrite(localSkills, ts);
5783
5932
  rmSync13(localSkills, { recursive: true, force: true });
@@ -5787,7 +5936,7 @@ function syncSkillsPull(ts) {
5787
5936
  }
5788
5937
  function syncSkillsPush() {
5789
5938
  const localSkills = join44(claudeHome(), "skills");
5790
- const stat = lstatSync10(localSkills, { throwIfNoEntry: false });
5939
+ const stat = lstatSync11(localSkills, { throwIfNoEntry: false });
5791
5940
  if (stat === void 0) return;
5792
5941
  if (stat.isSymbolicLink()) return;
5793
5942
  const sharedSkills = join44(repoHome(), "shared", "skills");
@@ -6153,10 +6302,14 @@ function computePreview(ts, map, verb = "pull") {
6153
6302
  dryRun: true,
6154
6303
  onPreview: (e) => addItem(sessions, formatSessionRow(e))
6155
6304
  });
6305
+ const localOnly = scanLocalOnly();
6306
+ if (localOnly > 0) {
6307
+ addItem(sessions, `${localOnly} local-only present, not in repo (push to reconcile)`);
6308
+ }
6156
6309
  const summary = section("Summary");
6157
- addItem(summary, summaryRow(verb, remapResult.unmapped));
6310
+ addItem(summary, summaryRow(verb, remapResult.unmapped, 0, 0, localOnly));
6158
6311
  renderTree([links, settingsSection, sessions, summary]);
6159
- return { unmapped: remapResult.unmapped, collisions: 0 };
6312
+ return { unmapped: remapResult.unmapped, collisions: 0, localOnly };
6160
6313
  }
6161
6314
 
6162
6315
  // src/commands.pull.ts
@@ -6311,14 +6464,21 @@ function applyWetPull(ts, map, prePostHeads) {
6311
6464
  syncSkillsPull(ts);
6312
6465
  const remapResult = withSpinner("Syncing sessions", () => remapPull(ts));
6313
6466
  const extrasResult = remapExtrasPull(ts, { prePostHeads });
6467
+ const localOnly = scanLocalOnly();
6314
6468
  const summary = section("Summary");
6315
6469
  addItem(
6316
6470
  summary,
6317
- 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
+ )
6318
6478
  );
6319
6479
  renderTree([
6320
6480
  buildSettingsSection(label),
6321
- buildSessionsSection(remapResult.pulled, remapResult.unmapped),
6481
+ buildSessionsSection(remapResult.pulled, remapResult.unmapped, localOnly),
6322
6482
  buildExtrasSection(extrasResult.pulled, extrasResult.skipped),
6323
6483
  summary
6324
6484
  ]);
@@ -6371,7 +6531,7 @@ function cmdPull(opts = {}) {
6371
6531
  });
6372
6532
  const mapPath = join46(repo, "path-map.json");
6373
6533
  const map = existsSync39(mapPath) ? readPathMap(mapPath) : { projects: {} };
6374
- divergenceCheckExtras(ts);
6534
+ divergenceCheckExtras(ts, dryRun ? prePostHeads : void 0);
6375
6535
  if (dryRun) {
6376
6536
  computePreview(ts, map, "pull");
6377
6537
  log("dry-run complete; no mutation");
@@ -6586,12 +6746,12 @@ function reportSettingsAheadDrift(repo) {
6586
6746
  // src/commands.push.guards.ts
6587
6747
  init_push_checks();
6588
6748
  init_utils();
6589
- import { join as join49, relative as relative5 } from "node:path";
6749
+ import { join as join49, relative as relative7 } from "node:path";
6590
6750
  function guardGitlinks(repo) {
6591
6751
  const gitlinks = findGitlinks(join49(repo, "shared"));
6592
6752
  if (gitlinks.length === 0) return;
6593
6753
  for (const p of gitlinks) {
6594
- const rel = relative5(repo, p);
6754
+ const rel = relative7(repo, p);
6595
6755
  fail(`gitlink: ${rel} would push as submodule (run: rm -rf ${rel} or remove the nested repo)`);
6596
6756
  }
6597
6757
  const noun = gitlinks.length === 1 ? "entry" : "entries";
@@ -6700,7 +6860,7 @@ init_config_sharedDirs_guard();
6700
6860
  import { randomBytes as randomBytes2 } from "node:crypto";
6701
6861
  import { copyFileSync, existsSync as existsSync42, mkdirSync as mkdirSync11, readdirSync as readdirSync16, rmSync as rmSync14 } from "node:fs";
6702
6862
  import { homedir as homedir5 } from "node:os";
6703
- 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";
6704
6864
  init_push_leak_verdict();
6705
6865
  init_push_gitleaks();
6706
6866
  init_utils_fs();
@@ -6712,7 +6872,7 @@ function stageSessionDir(localDir, dstDir, changed) {
6712
6872
  const matching = [...changed].filter((p) => p.startsWith(prefix));
6713
6873
  if (matching.length === 0) return false;
6714
6874
  for (const src of matching) {
6715
- copyFileAtomic(src, join50(dstDir, relative6(localDir, src)));
6875
+ copyFileAtomic(src, join50(dstDir, relative8(localDir, src)));
6716
6876
  }
6717
6877
  return true;
6718
6878
  }
@@ -6951,6 +7111,7 @@ function cmdDiff() {
6951
7111
  const ts = freshBackupTs(backupBase());
6952
7112
  const mapPath = join52(repo, "path-map.json");
6953
7113
  const map = existsSync44(mapPath) ? readPathMap(mapPath) : { projects: {} };
7114
+ divergenceCheckExtras(ts);
6954
7115
  computePreview(ts, map, "diff");
6955
7116
  } catch (err) {
6956
7117
  if (err instanceof NomadFatal) {
@@ -7496,7 +7657,7 @@ function parsePushArgs(argv) {
7496
7657
  // package.json
7497
7658
  var package_default = {
7498
7659
  name: "claude-nomad",
7499
- version: "0.56.1",
7660
+ version: "0.56.2",
7500
7661
  type: "module",
7501
7662
  description: "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
7502
7663
  keywords: [
@@ -7721,7 +7882,7 @@ var DEFAULT_HELP = [
7721
7882
  init_config();
7722
7883
  init_utils();
7723
7884
  init_utils_json();
7724
- 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";
7725
7886
  import { join as join56 } from "node:path";
7726
7887
  function resumeCmd(sessionId) {
7727
7888
  if (!/^[A-Za-z0-9_-]+$/.test(sessionId) || sessionId.length > 128) {
@@ -7773,7 +7934,7 @@ function findTranscriptPath(projectsRoot, sessionId) {
7773
7934
  return null;
7774
7935
  }
7775
7936
  function extractRecordedCwd(jsonlPath) {
7776
- for (const line of readFileSync15(jsonlPath, "utf8").split("\n")) {
7937
+ for (const line of readFileSync17(jsonlPath, "utf8").split("\n")) {
7777
7938
  if (!line.trim()) continue;
7778
7939
  try {
7779
7940
  const obj = JSON.parse(line);