claude-nomad 0.55.1 → 0.56.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/nomad.mjs CHANGED
@@ -410,6 +410,9 @@ function repoHome() {
410
410
  function backupBase() {
411
411
  return join(home(), ".cache", "claude-nomad", "backup");
412
412
  }
413
+ function manifestPath() {
414
+ return join(home(), ".cache", "claude-nomad", `push-manifest-${encodeURIComponent(HOST)}.json`);
415
+ }
413
416
  function allSharedLinks(map) {
414
417
  const extras = [];
415
418
  for (const entry of map.sharedDirs ?? []) {
@@ -2089,8 +2092,8 @@ function cmdEject(opts = {}, roots = defaultEjectRoots()) {
2089
2092
  }
2090
2093
 
2091
2094
  // src/commands.doctor.ts
2092
- import { existsSync as existsSync30 } from "node:fs";
2093
- import { join as join36 } from "node:path";
2095
+ import { existsSync as existsSync31 } from "node:fs";
2096
+ import { join as join37 } from "node:path";
2094
2097
 
2095
2098
  // src/commands.doctor.checks.repo.ts
2096
2099
  init_color();
@@ -2570,15 +2573,15 @@ function stripSideIndicator(line) {
2570
2573
  }
2571
2574
  function isGsdDiffLine(line, localBase, sharedBase) {
2572
2575
  const bare = stripSideIndicator(line);
2573
- let relative6;
2576
+ let relative7;
2574
2577
  if (bare.startsWith(localBase + "/")) {
2575
- relative6 = bare.slice(localBase.length + 1);
2578
+ relative7 = bare.slice(localBase.length + 1);
2576
2579
  } else if (bare.startsWith(sharedBase + "/")) {
2577
- relative6 = bare.slice(sharedBase.length + 1);
2580
+ relative7 = bare.slice(sharedBase.length + 1);
2578
2581
  } else {
2579
- relative6 = bare;
2582
+ relative7 = bare;
2580
2583
  }
2581
- return relative6.split("/")[0].startsWith(GSD_PREFIX);
2584
+ return relative7.split("/")[0].startsWith(GSD_PREFIX);
2582
2585
  }
2583
2586
  function reportSkillsDivergence(section2) {
2584
2587
  const sharedSkills = join14(repoHome(), "shared", "skills");
@@ -2915,9 +2918,9 @@ function reportCheckSchema(section2) {
2915
2918
  init_color();
2916
2919
  import { randomBytes } from "node:crypto";
2917
2920
  import { execFileSync as execFileSync9 } from "node:child_process";
2918
- import { existsSync as existsSync19, mkdirSync as mkdirSync5, readdirSync as readdirSync8, rmSync as rmSync8 } from "node:fs";
2921
+ import { existsSync as existsSync20, mkdirSync as mkdirSync6, readdirSync as readdirSync9, rmSync as rmSync8 } from "node:fs";
2919
2922
  import { homedir as homedir4 } from "node:os";
2920
- import { join as join24 } from "node:path";
2923
+ import { join as join25 } from "node:path";
2921
2924
 
2922
2925
  // src/commands.doctor.check-shared.scan.ts
2923
2926
  init_color();
@@ -3014,8 +3017,8 @@ init_config();
3014
3017
 
3015
3018
  // src/remap.ts
3016
3019
  init_config_sharedDirs_guard();
3017
- import { cpSync as cpSync4, existsSync as existsSync18, mkdirSync as mkdirSync4, readdirSync as readdirSync7, renameSync as renameSync3, rmSync as rmSync7, statSync as statSync4 } from "node:fs";
3018
- import { join as join23, relative as relative3, sep as sep3 } from "node:path";
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";
3019
3022
 
3020
3023
  // src/extras-sync.guards.ts
3021
3024
  init_utils();
@@ -3036,6 +3039,114 @@ function assertSafeLocalRoot(localRoot, logical) {
3036
3039
 
3037
3040
  // src/remap.ts
3038
3041
  init_config();
3042
+
3043
+ // src/push-manifest.ts
3044
+ init_config();
3045
+ init_push_gitleaks_config();
3046
+ init_utils_fs();
3047
+ 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";
3050
+ function isChanged(prev, cur, hash) {
3051
+ if (prev === void 0) return true;
3052
+ if (prev.size !== cur.size) return true;
3053
+ if (prev.mtime === cur.mtime) return false;
3054
+ return prev.hash !== hash();
3055
+ }
3056
+ function diffManifest(old, current, hashFor) {
3057
+ const changed = /* @__PURE__ */ new Set();
3058
+ const deleted = [];
3059
+ if (old === null) {
3060
+ for (const key of Object.keys(current)) {
3061
+ changed.add(key);
3062
+ }
3063
+ return { changed, deleted };
3064
+ }
3065
+ for (const [key, cur] of Object.entries(current)) {
3066
+ const prev = old.files[key];
3067
+ if (isChanged(prev, cur, () => hashFor(key))) {
3068
+ changed.add(key);
3069
+ }
3070
+ }
3071
+ for (const key of Object.keys(old.files)) {
3072
+ if (!(key in current)) {
3073
+ deleted.push(key);
3074
+ }
3075
+ }
3076
+ return { changed, deleted };
3077
+ }
3078
+ function shouldFullRescan(old, scannerVersion, configHash, forceFlag) {
3079
+ if (forceFlag) return true;
3080
+ if (old === null) return true;
3081
+ if (old.scannerVersion !== scannerVersion) return true;
3082
+ if (old.configHash !== configHash) return true;
3083
+ return false;
3084
+ }
3085
+ function hashFile(absPath) {
3086
+ return createHash("sha256").update(readFileSync6(absPath)).digest("hex");
3087
+ }
3088
+ function enumerateDir(dir, results) {
3089
+ for (const entry of readdirSync7(dir)) {
3090
+ const fullPath = join23(dir, entry);
3091
+ const st = statSync4(fullPath);
3092
+ if (st.isDirectory()) {
3093
+ enumerateDir(fullPath, results);
3094
+ } else {
3095
+ results.push(fullPath);
3096
+ }
3097
+ }
3098
+ }
3099
+ function enumerateSourceFiles(projectDir) {
3100
+ const results = [];
3101
+ for (const entry of readdirSync7(projectDir)) {
3102
+ const fullPath = join23(projectDir, entry);
3103
+ const st = statSync4(fullPath);
3104
+ if (st.isDirectory()) {
3105
+ enumerateDir(fullPath, results);
3106
+ } else if (fullPath.endsWith(".jsonl")) {
3107
+ results.push(fullPath);
3108
+ }
3109
+ }
3110
+ return results;
3111
+ }
3112
+ function fileIdentity(p) {
3113
+ if (p === null) return "none::absent";
3114
+ if (!existsSync18(p)) return `${p}::absent`;
3115
+ return `${p}::${createHash("sha256").update(readFileSync6(p)).digest("hex")}`;
3116
+ }
3117
+ function computeConfigHash() {
3118
+ const repo = repoHome();
3119
+ const parts = [
3120
+ fileIdentity(resolveTomlPath(repo)),
3121
+ fileIdentity(join23(repo, ".gitleaks.overlay.toml")),
3122
+ fileIdentity(join23(repo, ".gitleaksignore"))
3123
+ ];
3124
+ return createHash("sha256").update(parts.join("\n")).digest("hex");
3125
+ }
3126
+ function isManifestShape(raw) {
3127
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return false;
3128
+ const m = raw;
3129
+ return m.schema === 1 && typeof m.scannerVersion === "string" && typeof m.configHash === "string" && m.files !== null && typeof m.files === "object" && !Array.isArray(m.files);
3130
+ }
3131
+ function readManifest(path) {
3132
+ try {
3133
+ const raw = readFileSync6(path, "utf8");
3134
+ const parsed = JSON.parse(raw);
3135
+ if (!isManifestShape(parsed)) return null;
3136
+ return parsed;
3137
+ } catch {
3138
+ return null;
3139
+ }
3140
+ }
3141
+ function writeManifest(path, manifest) {
3142
+ mkdirSync4(dirname3(path), { recursive: true });
3143
+ writeJsonAtomic(path, manifest);
3144
+ }
3145
+ function buildManifest(files, scannerVersion, configHash) {
3146
+ return { schema: 1, scannerVersion, configHash, files };
3147
+ }
3148
+
3149
+ // src/remap.ts
3039
3150
  init_utils();
3040
3151
  init_utils_fs();
3041
3152
  init_utils_json();
@@ -3050,6 +3161,33 @@ function atomicMirror(src, dst, options) {
3050
3161
  function copyDir(src, dst) {
3051
3162
  atomicMirror(src, dst, { recursive: true, force: true });
3052
3163
  }
3164
+ function copyFileAtomic(src, dst) {
3165
+ mkdirSync5(dirname4(dst), { recursive: true });
3166
+ const tmp = `${dst}.tmp.${process.pid}`;
3167
+ cpSync4(src, tmp, { force: true, preserveTimestamps: true });
3168
+ renameSync3(tmp, dst);
3169
+ }
3170
+ function hasDeltaForDir(sel, localDir) {
3171
+ const prefix = `${localDir}${sep3}`;
3172
+ for (const p of sel.changed) if (p.startsWith(prefix)) return true;
3173
+ for (const p of sel.deleted) if (p.startsWith(prefix)) return true;
3174
+ return false;
3175
+ }
3176
+ function skipForSelection(sel, localDir) {
3177
+ return sel !== void 0 && !hasDeltaForDir(sel, localDir);
3178
+ }
3179
+ function applySelective(sel, localDir, repoDst) {
3180
+ const prefix = `${localDir}${sep3}`;
3181
+ for (const src of sel.changed) {
3182
+ if (!src.startsWith(prefix)) continue;
3183
+ copyFileAtomic(src, join24(repoDst, relative3(localDir, src)));
3184
+ }
3185
+ for (const src of sel.deleted) {
3186
+ if (!src.startsWith(prefix)) continue;
3187
+ const dst = join24(repoDst, relative3(localDir, src));
3188
+ if (existsSync19(dst)) rmSync7(dst);
3189
+ }
3190
+ }
3053
3191
  function copyDirJsonlOnly(src, dst) {
3054
3192
  atomicMirror(src, dst, {
3055
3193
  recursive: true,
@@ -3058,7 +3196,7 @@ function copyDirJsonlOnly(src, dst) {
3058
3196
  const rel = relative3(src, srcPath);
3059
3197
  if (rel === "") return true;
3060
3198
  if (rel.split(sep3).length > 1) return true;
3061
- if (statSync4(srcPath).isDirectory()) return true;
3199
+ if (statSync5(srcPath).isDirectory()) return true;
3062
3200
  if (srcPath.endsWith(".jsonl")) return true;
3063
3201
  item(`skip ${rel}: extension not in allowlist`);
3064
3202
  return false;
@@ -3076,16 +3214,16 @@ function remapPull(ts, opts = {}) {
3076
3214
  const wouldPull = [];
3077
3215
  const repo = repoHome();
3078
3216
  const claude = claudeHome();
3079
- const mapPath = join23(repo, "path-map.json");
3080
- const repoProjects = join23(repo, "shared", "projects");
3081
- if (!existsSync18(mapPath) || !existsSync18(repoProjects)) {
3217
+ const mapPath = join24(repo, "path-map.json");
3218
+ const repoProjects = join24(repo, "shared", "projects");
3219
+ if (!existsSync19(mapPath) || !existsSync19(repoProjects)) {
3082
3220
  const text = "no path-map or repo projects dir; skipping session remap";
3083
3221
  emitPreview(opts.onPreview, { kind: "note", text }, text);
3084
3222
  return { unmapped: 0, pulled, wouldPull };
3085
3223
  }
3086
3224
  const map = readPathMap(mapPath);
3087
- const localProjects = join23(claude, "projects");
3088
- if (!dryRun) mkdirSync4(localProjects, { recursive: true });
3225
+ const localProjects = join24(claude, "projects");
3226
+ if (!dryRun) mkdirSync5(localProjects, { recursive: true });
3089
3227
  for (const [logical, hosts] of Object.entries(map.projects)) {
3090
3228
  assertSafeLogical(logical);
3091
3229
  const localPath = hosts[HOST];
@@ -3094,9 +3232,9 @@ function remapPull(ts, opts = {}) {
3094
3232
  continue;
3095
3233
  }
3096
3234
  assertSafeLocalRoot(localPath, logical);
3097
- const src = join23(repoProjects, logical);
3098
- if (!existsSync18(src)) continue;
3099
- const dst = join23(localProjects, encodePath(localPath));
3235
+ const src = join24(repoProjects, logical);
3236
+ if (!existsSync19(src)) continue;
3237
+ const dst = join24(localProjects, encodePath(localPath));
3100
3238
  if (dryRun) {
3101
3239
  wouldPull.push(logical);
3102
3240
  emitPreview(
@@ -3143,31 +3281,37 @@ function remapPush(ts, opts = {}) {
3143
3281
  const wouldPush = [];
3144
3282
  const repo = repoHome();
3145
3283
  const claude = claudeHome();
3146
- const mapPath = join23(repo, "path-map.json");
3147
- if (!existsSync18(mapPath)) {
3284
+ const mapPath = join24(repo, "path-map.json");
3285
+ if (!existsSync19(mapPath)) {
3148
3286
  log("no path-map.json; skipping session export");
3149
3287
  return { unmapped: 0, collisions: 0, pushed, wouldPush };
3150
3288
  }
3151
3289
  const map = readPathMap(mapPath);
3152
- const localProjects = join23(claude, "projects");
3153
- const repoProjects = join23(repo, "shared", "projects");
3290
+ const localProjects = join24(claude, "projects");
3291
+ const repoProjects = join24(repo, "shared", "projects");
3154
3292
  const reverse = buildReverseMap(map);
3155
- if (!existsSync18(localProjects)) return { unmapped, collisions: 0, pushed, wouldPush };
3156
- if (!dryRun) mkdirSync4(repoProjects, { recursive: true });
3157
- for (const dir of readdirSync7(localProjects)) {
3293
+ if (!existsSync19(localProjects)) return { unmapped, collisions: 0, pushed, wouldPush };
3294
+ if (!dryRun) mkdirSync5(repoProjects, { recursive: true });
3295
+ for (const dir of readdirSync8(localProjects)) {
3158
3296
  if (dir.endsWith(TMP_SUFFIX)) continue;
3159
3297
  const logical = reverse.get(dir);
3160
3298
  if (!logical) {
3161
3299
  unmapped++;
3162
3300
  continue;
3163
3301
  }
3164
- const repoDst = join23(repoProjects, logical);
3302
+ const localDir = join24(localProjects, dir);
3303
+ const repoDst = join24(repoProjects, logical);
3304
+ if (skipForSelection(opts.selection, localDir)) continue;
3165
3305
  if (dryRun) {
3166
3306
  wouldPush.push(logical);
3167
3307
  continue;
3168
3308
  }
3169
3309
  backupRepoWrite(repoDst, ts, repo);
3170
- copyDirJsonlOnly(join23(localProjects, dir), repoDst);
3310
+ if (opts.selection) {
3311
+ applySelective(opts.selection, localDir, repoDst);
3312
+ } else {
3313
+ copyDirJsonlOnly(localDir, repoDst);
3314
+ }
3171
3315
  pushed.push(logical);
3172
3316
  }
3173
3317
  return { unmapped, collisions: 0, pushed, wouldPush };
@@ -3180,8 +3324,8 @@ function buildScanTree(tmpRoot) {
3180
3324
  const logicalToEncoded = /* @__PURE__ */ new Map();
3181
3325
  let staged = 0;
3182
3326
  const repo = repoHome();
3183
- const mapPath = join24(repo, "path-map.json");
3184
- if (!existsSync19(mapPath)) return { logicalToEncoded, staged, malformed: false };
3327
+ const mapPath = join25(repo, "path-map.json");
3328
+ if (!existsSync20(mapPath)) return { logicalToEncoded, staged, malformed: false };
3185
3329
  let map;
3186
3330
  try {
3187
3331
  map = readJson(mapPath);
@@ -3198,12 +3342,12 @@ function buildScanTree(tmpRoot) {
3198
3342
  if (!p || p === "TBD") continue;
3199
3343
  reverse.set(encodePath(p), logical);
3200
3344
  }
3201
- const localProjects = join24(claudeHome(), "projects");
3202
- if (!existsSync19(localProjects)) return { logicalToEncoded, staged, malformed: false };
3203
- for (const dir of readdirSync8(localProjects)) {
3345
+ const localProjects = join25(claudeHome(), "projects");
3346
+ if (!existsSync20(localProjects)) return { logicalToEncoded, staged, malformed: false };
3347
+ for (const dir of readdirSync9(localProjects)) {
3204
3348
  const logical = reverse.get(dir);
3205
3349
  if (!logical) continue;
3206
- copyDirJsonlOnly(join24(localProjects, dir), join24(tmpRoot, "shared", "projects", logical));
3350
+ copyDirJsonlOnly(join25(localProjects, dir), join25(tmpRoot, "shared", "projects", logical));
3207
3351
  logicalToEncoded.set(logical, dir);
3208
3352
  staged++;
3209
3353
  }
@@ -3234,11 +3378,11 @@ function ensureGitleaksReady(section2, gitleaksReady) {
3234
3378
  }
3235
3379
  function reportCheckShared(section2, gitleaksReady) {
3236
3380
  if (!ensureGitleaksReady(section2, gitleaksReady)) return;
3237
- const cacheDir = join24(homedir4(), ".cache", "claude-nomad");
3238
- mkdirSync5(cacheDir, { recursive: true });
3381
+ const cacheDir = join25(homedir4(), ".cache", "claude-nomad");
3382
+ mkdirSync6(cacheDir, { recursive: true });
3239
3383
  const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes(4).toString("hex")}`;
3240
- const reportPath = join24(cacheDir, `check-shared-${stamp}.json`);
3241
- const tmpRoot = join24(cacheDir, `check-shared-tree-${stamp}`);
3384
+ const reportPath = join25(cacheDir, `check-shared-${stamp}.json`);
3385
+ const tmpRoot = join25(cacheDir, `check-shared-tree-${stamp}`);
3242
3386
  try {
3243
3387
  const { logicalToEncoded, staged, malformed } = buildScanTree(tmpRoot);
3244
3388
  if (malformed) {
@@ -3259,12 +3403,12 @@ function reportCheckShared(section2, gitleaksReady) {
3259
3403
 
3260
3404
  // src/commands.doctor.checks.hooks.scope.ts
3261
3405
  init_color();
3262
- import { existsSync as existsSync20, readFileSync as readFileSync6, readdirSync as readdirSync9, realpathSync as realpathSync2 } from "node:fs";
3263
- import { dirname as dirname3, extname, join as join25 } from "node:path";
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";
3264
3408
  init_config();
3265
3409
  function typeFromPackageJson(pkgPath) {
3266
3410
  try {
3267
- const parsed = JSON.parse(readFileSync6(pkgPath, "utf8"));
3411
+ const parsed = JSON.parse(readFileSync7(pkgPath, "utf8"));
3268
3412
  return parsed.type === "module" ? "esm" : "cjs";
3269
3413
  } catch {
3270
3414
  return "cjs";
@@ -3280,11 +3424,11 @@ function effectiveType(hookPath) {
3280
3424
  } catch {
3281
3425
  return null;
3282
3426
  }
3283
- let dir = dirname3(real);
3427
+ let dir = dirname5(real);
3284
3428
  for (; ; ) {
3285
- const pkg = join25(dir, "package.json");
3286
- if (existsSync20(pkg)) return typeFromPackageJson(pkg);
3287
- const parent = dirname3(dir);
3429
+ const pkg = join26(dir, "package.json");
3430
+ if (existsSync21(pkg)) return typeFromPackageJson(pkg);
3431
+ const parent = dirname5(dir);
3288
3432
  if (parent === dir) return "cjs";
3289
3433
  dir = parent;
3290
3434
  }
@@ -3312,28 +3456,28 @@ function remedy(family) {
3312
3456
  }
3313
3457
  function safeReaddir2(dir) {
3314
3458
  try {
3315
- return readdirSync9(dir);
3459
+ return readdirSync10(dir);
3316
3460
  } catch {
3317
3461
  return [];
3318
3462
  }
3319
3463
  }
3320
3464
  function safeRead(path) {
3321
3465
  try {
3322
- return readFileSync6(path, "utf8");
3466
+ return readFileSync7(path, "utf8");
3323
3467
  } catch {
3324
3468
  return null;
3325
3469
  }
3326
3470
  }
3327
3471
  function reportHookScopeCheck(section2) {
3328
- const hooksDir = join25(claudeHome(), "hooks");
3329
- if (!existsSync20(hooksDir)) {
3472
+ const hooksDir = join26(claudeHome(), "hooks");
3473
+ if (!existsSync21(hooksDir)) {
3330
3474
  addItem(section2, `${dim(infoGlyph)} no ~/.claude/hooks; skipping module-scope check`);
3331
3475
  return;
3332
3476
  }
3333
3477
  let anyWarn = false;
3334
3478
  for (const name of safeReaddir2(hooksDir)) {
3335
3479
  if (extname(name) !== ".js") continue;
3336
- const abs = join25(hooksDir, name);
3480
+ const abs = join26(hooksDir, name);
3337
3481
  const eff = effectiveType(abs);
3338
3482
  if (eff === null) continue;
3339
3483
  const src = safeRead(abs);
@@ -3353,8 +3497,8 @@ function reportHookScopeCheck(section2) {
3353
3497
 
3354
3498
  // src/commands.doctor.checks.hooks.ts
3355
3499
  init_color();
3356
- import { existsSync as existsSync21 } from "node:fs";
3357
- import { join as join26 } from "node:path";
3500
+ import { existsSync as existsSync22 } from "node:fs";
3501
+ import { join as join27 } from "node:path";
3358
3502
  init_config();
3359
3503
  function expandHome(token) {
3360
3504
  const h2 = home();
@@ -3397,7 +3541,7 @@ function checkEventGroups(section2, event, groups) {
3397
3541
  for (const group of groups) {
3398
3542
  for (const cmd of commandsFromGroup(group)) {
3399
3543
  for (const resolved of claudePathsIn(cmd)) {
3400
- if (existsSync21(resolved)) continue;
3544
+ if (existsSync22(resolved)) continue;
3401
3545
  addItem(
3402
3546
  section2,
3403
3547
  `${red(failGlyph)} hooks/${event}: command target missing: ${resolved} (run nomad pull)`
@@ -3410,8 +3554,8 @@ function checkEventGroups(section2, event, groups) {
3410
3554
  return anyFail;
3411
3555
  }
3412
3556
  function reportHooksTargetCheck(section2) {
3413
- const settingsPath = join26(claudeHome(), "settings.json");
3414
- if (!existsSync21(settingsPath)) {
3557
+ const settingsPath = join27(claudeHome(), "settings.json");
3558
+ if (!existsSync22(settingsPath)) {
3415
3559
  addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json; skipping hook target check`);
3416
3560
  return;
3417
3561
  }
@@ -3434,12 +3578,12 @@ function reportHooksTargetCheck(section2) {
3434
3578
 
3435
3579
  // src/commands.doctor.checks.hooks.preserve-symlinks.ts
3436
3580
  init_color();
3437
- import { existsSync as existsSync23, readFileSync as readFileSync7 } from "node:fs";
3438
- import { join as join28 } from "node:path";
3581
+ import { existsSync as existsSync24, readFileSync as readFileSync8 } from "node:fs";
3582
+ import { join as join29 } from "node:path";
3439
3583
 
3440
3584
  // src/commands.doctor.checks.hooks.preserve-symlinks.probe.ts
3441
- import { closeSync as closeSync3, existsSync as existsSync22, openSync as openSync3, readSync, realpathSync as realpathSync3 } from "node:fs";
3442
- import { dirname as dirname4, join as join27, resolve as resolve2 } from "node:path";
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";
3443
3587
  function suppressedRanges(src) {
3444
3588
  const ranges = [];
3445
3589
  const re = /\/\*[\s\S]*?\*\/|\/\/[^\n]*|'[^']*'|"[^"]*"|`[^`]*`/g;
@@ -3471,12 +3615,12 @@ function topRelativeSpecifiers(src) {
3471
3615
  }
3472
3616
  function specifierIsMissing(specifier, baseDir) {
3473
3617
  const base = resolve2(baseDir, specifier);
3474
- if (existsSync22(base)) return false;
3618
+ if (existsSync23(base)) return false;
3475
3619
  for (const ext of [".js", ".cjs", ".mjs"]) {
3476
- if (existsSync22(base + ext)) return false;
3620
+ if (existsSync23(base + ext)) return false;
3477
3621
  }
3478
3622
  for (const idx of ["index.js", "index.cjs", "index.mjs"]) {
3479
- if (existsSync22(join27(base, idx))) return false;
3623
+ if (existsSync23(join28(base, idx))) return false;
3480
3624
  }
3481
3625
  return true;
3482
3626
  }
@@ -3502,7 +3646,7 @@ function relativeRequireTargetsBroken(scriptPath) {
3502
3646
  }
3503
3647
  const specifiers = topRelativeSpecifiers(raw);
3504
3648
  if (specifiers.length === 0) return false;
3505
- const baseDir = dirname4(realPath);
3649
+ const baseDir = dirname6(realPath);
3506
3650
  for (const spec of specifiers) {
3507
3651
  if (specifierIsMissing(spec, baseDir)) return true;
3508
3652
  }
@@ -3531,10 +3675,10 @@ function commandTokens(command) {
3531
3675
  return tokens;
3532
3676
  }
3533
3677
  function readPathMapSafe() {
3534
- const mapPath = join28(repoHome(), "path-map.json");
3535
- if (!existsSync23(mapPath)) return { projects: {} };
3678
+ const mapPath = join29(repoHome(), "path-map.json");
3679
+ if (!existsSync24(mapPath)) return { projects: {} };
3536
3680
  try {
3537
- return JSON.parse(readFileSync7(mapPath, "utf8"));
3681
+ return JSON.parse(readFileSync8(mapPath, "utf8"));
3538
3682
  } catch {
3539
3683
  return { projects: {} };
3540
3684
  }
@@ -3605,8 +3749,8 @@ function checkEventForPreserveSymlinks(section2, event, groups, sharedLinkNames)
3605
3749
  return anyWarn;
3606
3750
  }
3607
3751
  function reportPreserveSymlinksCheck(section2) {
3608
- const settingsPath = join28(claudeHome(), "settings.json");
3609
- if (!existsSync23(settingsPath)) {
3752
+ const settingsPath = join29(claudeHome(), "settings.json");
3753
+ if (!existsSync24(settingsPath)) {
3610
3754
  addItem(
3611
3755
  section2,
3612
3756
  `${dim(infoGlyph)} no ~/.claude/settings.json; skipping preserve-symlinks-main check`
@@ -3615,7 +3759,7 @@ function reportPreserveSymlinksCheck(section2) {
3615
3759
  }
3616
3760
  let settings;
3617
3761
  try {
3618
- settings = JSON.parse(readFileSync7(settingsPath, "utf8"));
3762
+ settings = JSON.parse(readFileSync8(settingsPath, "utf8"));
3619
3763
  } catch {
3620
3764
  return;
3621
3765
  }
@@ -3639,8 +3783,8 @@ function reportPreserveSymlinksCheck(section2) {
3639
3783
 
3640
3784
  // src/commands.doctor.checks.settings-drift.ts
3641
3785
  init_color();
3642
- import { existsSync as existsSync24, readFileSync as readFileSync8 } from "node:fs";
3643
- import { join as join29 } from "node:path";
3786
+ import { existsSync as existsSync25, readFileSync as readFileSync9 } from "node:fs";
3787
+ import { join as join30 } from "node:path";
3644
3788
  init_config();
3645
3789
  init_utils_json();
3646
3790
  function diffMergedSettings(merged, settings) {
@@ -3649,7 +3793,7 @@ function diffMergedSettings(merged, settings) {
3649
3793
  }
3650
3794
  function tryReadJson(filePath) {
3651
3795
  try {
3652
- const raw = readFileSync8(filePath, "utf8");
3796
+ const raw = readFileSync9(filePath, "utf8");
3653
3797
  const parsed = JSON.parse(raw);
3654
3798
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
3655
3799
  return parsed;
@@ -3658,7 +3802,7 @@ function tryReadJson(filePath) {
3658
3802
  }
3659
3803
  }
3660
3804
  function reportHooksBaseSelfCleanNote(section2) {
3661
- const basePath = join29(repoHome(), "shared", "settings.base.json");
3805
+ const basePath = join30(repoHome(), "shared", "settings.base.json");
3662
3806
  const base = tryReadJson(basePath);
3663
3807
  if (base === null) return;
3664
3808
  if (!baseHasGsdHookEntries(base)) return;
@@ -3671,14 +3815,14 @@ function reportSettingsDriftCheck(section2) {
3671
3815
  const claude = claudeHome();
3672
3816
  const repo = repoHome();
3673
3817
  const host = HOST;
3674
- const settingsPath = join29(claude, "settings.json");
3675
- const basePath = join29(repo, "shared", "settings.base.json");
3676
- const hostPath = join29(repo, "hosts", `${host}.json`);
3677
- if (!existsSync24(settingsPath)) {
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)) {
3678
3822
  addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json; skipping merge-drift check`);
3679
3823
  return;
3680
3824
  }
3681
- if (!existsSync24(basePath)) {
3825
+ if (!existsSync25(basePath)) {
3682
3826
  addItem(
3683
3827
  section2,
3684
3828
  `${dim(infoGlyph)} shared/settings.base.json missing; skipping merge-drift check`
@@ -3697,7 +3841,7 @@ function reportSettingsDriftCheck(section2) {
3697
3841
  if (settings === null) {
3698
3842
  return;
3699
3843
  }
3700
- const hostExists = existsSync24(hostPath);
3844
+ const hostExists = existsSync25(hostPath);
3701
3845
  const hostObj = hostExists ? tryReadJson(hostPath) : null;
3702
3846
  if (hostExists && hostObj === null) {
3703
3847
  addItem(
@@ -3746,12 +3890,12 @@ init_config();
3746
3890
 
3747
3891
  // src/commands.doctor.engine.ts
3748
3892
  init_color();
3749
- import { readFileSync as readFileSync10 } from "node:fs";
3893
+ import { readFileSync as readFileSync11 } from "node:fs";
3750
3894
  import { fileURLToPath as fileURLToPath3 } from "node:url";
3751
3895
 
3752
3896
  // src/commands.doctor.version.ts
3753
3897
  init_color();
3754
- import { readFileSync as readFileSync9 } from "node:fs";
3898
+ import { readFileSync as readFileSync10 } from "node:fs";
3755
3899
  import { fileURLToPath as fileURLToPath2 } from "node:url";
3756
3900
  init_config();
3757
3901
  var STRICT_SEMVER = /^\d+\.\d+\.\d+$/;
@@ -3768,7 +3912,7 @@ function compareSemver(a, b) {
3768
3912
  function readLocalVersion() {
3769
3913
  try {
3770
3914
  const pkgPath = fileURLToPath2(new URL("../package.json", import.meta.url));
3771
- const parsed = JSON.parse(readFileSync9(pkgPath, "utf8"));
3915
+ const parsed = JSON.parse(readFileSync10(pkgPath, "utf8"));
3772
3916
  if (typeof parsed.version === "string" && parsed.version.length > 0) {
3773
3917
  return parsed.version;
3774
3918
  }
@@ -3827,7 +3971,7 @@ function parseMinVersion(spec) {
3827
3971
  function readEnginesNode() {
3828
3972
  try {
3829
3973
  const pkgPath = fileURLToPath3(new URL("../package.json", import.meta.url));
3830
- const parsed = JSON.parse(readFileSync10(pkgPath, "utf8"));
3974
+ const parsed = JSON.parse(readFileSync11(pkgPath, "utf8"));
3831
3975
  const node = parsed.engines?.node;
3832
3976
  if (typeof node === "string" && node.length > 0) return node;
3833
3977
  return null;
@@ -3855,43 +3999,43 @@ function reportNodeEngineCheck(section2) {
3855
3999
 
3856
4000
  // src/spinner.ts
3857
4001
  init_color();
3858
- import { existsSync as existsSync28 } from "node:fs";
4002
+ import { existsSync as existsSync29 } from "node:fs";
3859
4003
  import { fileURLToPath as fileURLToPath4 } from "node:url";
3860
4004
  import { Worker } from "node:worker_threads";
3861
4005
 
3862
4006
  // src/commands.push.recovery.ts
3863
4007
  init_config();
3864
- import { readFileSync as readFileSync13, rmSync as rmSync10, writeFileSync as writeFileSync5 } from "node:fs";
3865
- import { join as join34 } from "node:path";
4008
+ import { readFileSync as readFileSync14, rmSync as rmSync10, writeFileSync as writeFileSync5 } from "node:fs";
4009
+ import { join as join35 } from "node:path";
3866
4010
  import { createInterface as createInterface2 } from "node:readline/promises";
3867
4011
 
3868
4012
  // src/commands.push.recovery.actions.ts
3869
4013
  init_config();
3870
- import { readFileSync as readFileSync12 } from "node:fs";
4014
+ import { readFileSync as readFileSync13 } from "node:fs";
3871
4015
  import { isAbsolute as isAbsolute2, resolve as resolve3, sep as sep5 } from "node:path";
3872
4016
 
3873
4017
  // src/commands.push.recovery.redact.ts
3874
4018
  init_config();
3875
4019
  init_config_sharedDirs_guard();
3876
- import { cpSync as cpSync5, existsSync as existsSync27, mkdirSync as mkdirSync6, statSync as statSync7 } from "node:fs";
3877
- import { dirname as dirname6, join as join32, sep as sep4 } from "node:path";
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";
3878
4022
 
3879
4023
  // src/commands.redact.ts
3880
4024
  init_config();
3881
- import { existsSync as existsSync26, statSync as statSync6 } from "node:fs";
3882
- import { dirname as dirname5, join as join31 } from "node:path";
4025
+ import { existsSync as existsSync27, statSync as statSync7 } from "node:fs";
4026
+ import { dirname as dirname7, join as join32 } from "node:path";
3883
4027
 
3884
4028
  // src/commands.redact.subtree.ts
3885
- import { existsSync as existsSync25, lstatSync as lstatSync8, readFileSync as readFileSync11, readdirSync as readdirSync10, statSync as statSync5, writeFileSync as writeFileSync4 } from "node:fs";
3886
- import { join as join30 } from "node:path";
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";
3887
4031
  init_utils_fs();
3888
4032
  init_utils();
3889
4033
  function collectFiles(dir, out) {
3890
- if (!existsSync25(dir)) return;
4034
+ if (!existsSync26(dir)) return;
3891
4035
  const st = lstatSync8(dir);
3892
4036
  if (!st.isDirectory()) return;
3893
- for (const entry of readdirSync10(dir)) {
3894
- const abs = join30(dir, entry);
4037
+ for (const entry of readdirSync11(dir)) {
4038
+ const abs = join31(dir, entry);
3895
4039
  const lst = lstatSync8(abs);
3896
4040
  if (lst.isSymbolicLink()) continue;
3897
4041
  if (lst.isDirectory()) {
@@ -3906,7 +4050,7 @@ function listSubtreeFiles(sessionDir) {
3906
4050
  collectFiles(sessionDir, out);
3907
4051
  return out.sort((a, b) => a.localeCompare(b));
3908
4052
  }
3909
- function newestSubtreeMtimeMs(mainPath, subtreeFiles, statMtime = (p) => statSync5(p).mtimeMs) {
4053
+ function newestSubtreeMtimeMs(mainPath, subtreeFiles, statMtime = (p) => statSync6(p).mtimeMs) {
3910
4054
  let newest = statMtime(mainPath);
3911
4055
  for (const filePath of subtreeFiles) {
3912
4056
  const t = statMtime(filePath);
@@ -3928,7 +4072,7 @@ function applySubtreeRedactions(mainPath, mainFindings, subtreeFiles, rule, ts,
3928
4072
  if (!dryRun && total > 0) {
3929
4073
  for (const { path: filePath, findings } of dirty) {
3930
4074
  backupBeforeWrite(filePath, ts);
3931
- const before = readFileSync11(filePath, "utf8");
4075
+ const before = readFileSync12(filePath, "utf8");
3932
4076
  const after = applyRedactions(before, findings);
3933
4077
  if (after === before) {
3934
4078
  log(
@@ -3996,15 +4140,15 @@ init_utils_json();
3996
4140
  init_utils();
3997
4141
  function resolveLiveTranscript(id) {
3998
4142
  try {
3999
- const mapPath = join31(repoHome(), "path-map.json");
4000
- if (!existsSync26(mapPath)) return null;
4143
+ const mapPath = join32(repoHome(), "path-map.json");
4144
+ if (!existsSync27(mapPath)) return null;
4001
4145
  const projects = readJson(mapPath).projects;
4002
4146
  const claude = claudeHome();
4003
4147
  for (const hostMap of Object.values(projects)) {
4004
4148
  const abs = hostMap[HOST];
4005
4149
  if (abs === void 0) continue;
4006
- const live = join31(claude, "projects", encodePath(abs), `${id}.jsonl`);
4007
- if (existsSync26(live)) return live;
4150
+ const live = join32(claude, "projects", encodePath(abs), `${id}.jsonl`);
4151
+ if (existsSync27(live)) return live;
4008
4152
  }
4009
4153
  return null;
4010
4154
  } catch {
@@ -4024,19 +4168,19 @@ function cmdRedact(opts, nowMs = Date.now, scan = scanFile) {
4024
4168
  }
4025
4169
  const repo = repoHome();
4026
4170
  const backup = backupBase();
4027
- if (!existsSync26(repo)) die(`repo not cloned at ${repo}`);
4171
+ if (!existsSync27(repo)) die(`repo not cloned at ${repo}`);
4028
4172
  const handle = acquireLock("redact");
4029
4173
  if (handle === null) process.exit(0);
4030
4174
  try {
4031
4175
  const localPath = resolveLiveTranscript(id);
4032
- if (localPath === null || !existsSync26(localPath)) {
4176
+ if (localPath === null || !existsSync27(localPath)) {
4033
4177
  fail(`could not resolve local transcript for session ${id} on this host`);
4034
4178
  process.exitCode = 1;
4035
4179
  return;
4036
4180
  }
4037
- const sessionDir = join31(dirname5(localPath), id);
4181
+ const sessionDir = join32(dirname7(localPath), id);
4038
4182
  const subtreeFiles = listSubtreeFiles(sessionDir);
4039
- const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync6(p).mtimeMs);
4183
+ const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync7(p).mtimeMs);
4040
4184
  if (isRecentlyModified(subtreeMtime, nowMs())) {
4041
4185
  log(
4042
4186
  `session ${id} was modified recently and may be active.
@@ -4150,8 +4294,8 @@ function resolveStagedDir(localPath, map, claude, repo) {
4150
4294
  assertSafeLogical(logical);
4151
4295
  const abs = hostMap[HOST];
4152
4296
  if (abs === void 0) continue;
4153
- if (localPath.startsWith(join32(claude, "projects", encodePath(abs)) + sep4)) {
4154
- return join32(repo, "shared", "projects", logical);
4297
+ if (localPath.startsWith(join33(claude, "projects", encodePath(abs)) + sep4)) {
4298
+ return join33(repo, "shared", "projects", logical);
4155
4299
  }
4156
4300
  }
4157
4301
  return null;
@@ -4161,9 +4305,9 @@ function preflightRedactable(f, map, nowMs) {
4161
4305
  if (sid === null) return "a finding has no resolvable session id (not a session transcript)";
4162
4306
  const localPath = resolveLiveTranscript(sid);
4163
4307
  if (localPath === null) return `session ${sid}: local transcript not found`;
4164
- const sessionDir = join32(dirname6(localPath), sid);
4308
+ const sessionDir = join33(dirname8(localPath), sid);
4165
4309
  const subtreeFiles = listSubtreeFiles(sessionDir);
4166
- const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync7(p).mtimeMs);
4310
+ const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync8(p).mtimeMs);
4167
4311
  if (isRecentlyModified(subtreeMtime, nowMs())) {
4168
4312
  return `session ${sid}: looks active (modified within the last 5 minutes)`;
4169
4313
  }
@@ -4191,9 +4335,9 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
4191
4335
  `could not locate the local transcript for session ${sid}; choose Skip or Drop session.`
4192
4336
  );
4193
4337
  }
4194
- const sessionDir = join32(dirname6(localPath), sid);
4338
+ const sessionDir = join33(dirname8(localPath), sid);
4195
4339
  const subtreeFiles = listSubtreeFiles(sessionDir);
4196
- const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync7(p).mtimeMs);
4340
+ const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync8(p).mtimeMs);
4197
4341
  if (isRecentlyModified(subtreeMtime, nowMs())) {
4198
4342
  return refuse(
4199
4343
  `session ${sid} looks active (modified within the last 5 minutes); refusing to redact, no changes made.
@@ -4224,10 +4368,10 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
4224
4368
  `nothing to redact in the local transcript for session ${sid}; choose Skip or Drop session.`
4225
4369
  );
4226
4370
  }
4227
- mkdirSync6(stagedProjectDir, { recursive: true });
4228
- cpSync5(localPath, join32(stagedProjectDir, `${sid}.jsonl`), { force: true });
4229
- if (existsSync27(sessionDir)) {
4230
- cpSync5(sessionDir, join32(stagedProjectDir, sid), { force: true, recursive: true });
4371
+ 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 });
4231
4375
  }
4232
4376
  return true;
4233
4377
  }
@@ -4235,14 +4379,14 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
4235
4379
  // src/commands.push.recovery.drop.ts
4236
4380
  init_config();
4237
4381
  import { rmSync as rmSync9 } from "node:fs";
4238
- import { join as join33 } from "node:path";
4382
+ import { join as join34 } from "node:path";
4239
4383
  function dropSessionFromStaged(sid, map) {
4240
4384
  const logicals = Object.keys(map.projects);
4241
4385
  if (logicals.length === 0) return false;
4242
4386
  const repo = repoHome();
4243
4387
  for (const logical of logicals) {
4244
- const jsonl = join33(repo, "shared", "projects", logical, `${sid}.jsonl`);
4245
- const dir = join33(repo, "shared", "projects", logical, sid);
4388
+ const jsonl = join34(repo, "shared", "projects", logical, `${sid}.jsonl`);
4389
+ const dir = join34(repo, "shared", "projects", logical, sid);
4246
4390
  rmSync9(jsonl, { force: true });
4247
4391
  rmSync9(dir, { recursive: true, force: true });
4248
4392
  }
@@ -4278,7 +4422,7 @@ function makeDefaultReadLine(repo) {
4278
4422
  if (isAbsolute2(file) || target !== repoRoot && !target.startsWith(repoRoot + sep5)) {
4279
4423
  return null;
4280
4424
  }
4281
- const content = readFileSync12(target, "utf8");
4425
+ const content = readFileSync13(target, "utf8");
4282
4426
  const lines = content.split(/\r?\n/);
4283
4427
  const idx = line - 1;
4284
4428
  if (idx < 0 || idx >= lines.length) return null;
@@ -4399,10 +4543,10 @@ function applyThenRescan(scanVerdict, repoHome2) {
4399
4543
  return next;
4400
4544
  }
4401
4545
  function allowThenRescan(append, scanVerdict, repoHome2) {
4402
- const ignPath = join34(repoHome2, ".gitleaksignore");
4546
+ const ignPath = join35(repoHome2, ".gitleaksignore");
4403
4547
  let before;
4404
4548
  try {
4405
- before = readFileSync13(ignPath, "utf8");
4549
+ before = readFileSync14(ignPath, "utf8");
4406
4550
  } catch {
4407
4551
  before = null;
4408
4552
  }
@@ -4498,7 +4642,7 @@ function writeAnimatedDone(out, label, ms, useTTY) {
4498
4642
  `);
4499
4643
  }
4500
4644
  function resolveWorkerPath(deps = {}) {
4501
- const check = deps.existsSyncFn ?? existsSync28;
4645
+ const check = deps.existsSyncFn ?? existsSync29;
4502
4646
  const base = deps.baseUrl ?? import.meta.url;
4503
4647
  const mjs = fileURLToPath4(new URL("./nomad.worker.mjs", base));
4504
4648
  if (check(mjs)) return mjs;
@@ -4565,8 +4709,8 @@ function withSpinner(label, fn, deps) {
4565
4709
  // src/commands.doctor.gitleaks-version.ts
4566
4710
  init_color();
4567
4711
  import { execFileSync as execFileSync11 } from "node:child_process";
4568
- import { existsSync as existsSync29 } from "node:fs";
4569
- import { join as join35 } from "node:path";
4712
+ import { existsSync as existsSync30 } from "node:fs";
4713
+ import { join as join36 } from "node:path";
4570
4714
  init_config();
4571
4715
  var SEMVER_MAJOR_MINOR = /^(\d+)\.(\d+)\.\d+$/;
4572
4716
  var GITLEAKS_TIMEOUT_MS = 5e3;
@@ -4575,7 +4719,7 @@ function majorMinorOf(value) {
4575
4719
  return m === null ? null : [m[1], m[2]];
4576
4720
  }
4577
4721
  function readGitleaksVersion(run, tomlExists) {
4578
- const tomlPath = join35(repoHome(), ".gitleaks.toml");
4722
+ const tomlPath = join36(repoHome(), ".gitleaks.toml");
4579
4723
  const args = ["version"];
4580
4724
  if (tomlExists(tomlPath)) args.push("--config", tomlPath);
4581
4725
  try {
@@ -4587,7 +4731,7 @@ function readGitleaksVersion(run, tomlExists) {
4587
4731
  return null;
4588
4732
  }
4589
4733
  }
4590
- function reportGitleaksVersionCheck(section2, run = execFileSync11, tomlExists = existsSync29) {
4734
+ function reportGitleaksVersionCheck(section2, run = execFileSync11, tomlExists = existsSync30) {
4591
4735
  const raw = readGitleaksVersion(run, tomlExists);
4592
4736
  if (raw === null) return;
4593
4737
  const local = majorMinorOf(raw);
@@ -4810,8 +4954,8 @@ function gatherDoctorSections(opts) {
4810
4954
  reportHostKeyAlignment(host);
4811
4955
  reportRepoState(host);
4812
4956
  const links = section("Shared links");
4813
- const mapPath = join36(repoHome(), "path-map.json");
4814
- const rawMap = existsSync30(mapPath) ? readJsonSafe(mapPath, mapPath, links) : null;
4957
+ const mapPath = join37(repoHome(), "path-map.json");
4958
+ const rawMap = existsSync31(mapPath) ? readJsonSafe(mapPath, mapPath, links) : null;
4815
4959
  const map = rawMap ?? { projects: {} };
4816
4960
  reportSharedLinks(links, map);
4817
4961
  reportDroppedNamesMigration(links);
@@ -4908,8 +5052,8 @@ function parseDoctorArgs(args) {
4908
5052
  // src/commands.drop-session.ts
4909
5053
  init_config();
4910
5054
  import { execFileSync as execFileSync16 } from "node:child_process";
4911
- import { existsSync as existsSync32, readdirSync as readdirSync11, statSync as statSync8 } from "node:fs";
4912
- import { join as join38, relative as relative4 } from "node:path";
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";
4913
5057
 
4914
5058
  // src/commands.drop-session.git.ts
4915
5059
  import { execFileSync as execFileSync15 } from "node:child_process";
@@ -4951,8 +5095,8 @@ function isInIndex(rel, repo) {
4951
5095
  init_config();
4952
5096
  init_utils();
4953
5097
  init_utils_json();
4954
- import { existsSync as existsSync31 } from "node:fs";
4955
- import { join as join37 } from "node:path";
5098
+ import { existsSync as existsSync32 } from "node:fs";
5099
+ import { join as join38 } from "node:path";
4956
5100
  var SHARED_PROJECT_LOGICAL = /^shared\/projects\/([^/]+)\//;
4957
5101
  function reportScrubHint(id, matches) {
4958
5102
  const live = resolveLiveTranscript2(id, matches);
@@ -4968,8 +5112,8 @@ function reportScrubHint(id, matches) {
4968
5112
  }
4969
5113
  function resolveLiveTranscript2(id, matches) {
4970
5114
  try {
4971
- const mapPath = join37(repoHome(), "path-map.json");
4972
- if (!existsSync31(mapPath)) return null;
5115
+ const mapPath = join38(repoHome(), "path-map.json");
5116
+ if (!existsSync32(mapPath)) return null;
4973
5117
  const projects = readJson(mapPath).projects;
4974
5118
  const claude = claudeHome();
4975
5119
  for (const rel of matches) {
@@ -4977,8 +5121,8 @@ function resolveLiveTranscript2(id, matches) {
4977
5121
  if (logical === void 0) continue;
4978
5122
  const abs = projects[logical]?.[HOST];
4979
5123
  if (abs === void 0) continue;
4980
- const live = join37(claude, "projects", encodePath(abs), `${id}.jsonl`);
4981
- if (existsSync31(live)) return live;
5124
+ const live = join38(claude, "projects", encodePath(abs), `${id}.jsonl`);
5125
+ if (existsSync32(live)) return live;
4982
5126
  }
4983
5127
  return null;
4984
5128
  } catch {
@@ -4994,12 +5138,12 @@ function cmdDropSession(id) {
4994
5138
  process.exit(1);
4995
5139
  }
4996
5140
  const repo = repoHome();
4997
- if (!existsSync32(repo)) die(`repo not cloned at ${repo}`);
5141
+ if (!existsSync33(repo)) die(`repo not cloned at ${repo}`);
4998
5142
  const handle = acquireLock("drop-session");
4999
5143
  if (handle === null) process.exit(0);
5000
5144
  try {
5001
- const repoProjects = join38(repo, "shared", "projects");
5002
- if (!existsSync32(repoProjects)) {
5145
+ const repoProjects = join39(repo, "shared", "projects");
5146
+ if (!existsSync33(repoProjects)) {
5003
5147
  throw new NomadFatal(`no staged session matches ${id}`);
5004
5148
  }
5005
5149
  const matches = collectMatches(repoProjects, id, repo);
@@ -5021,13 +5165,13 @@ function cmdDropSession(id) {
5021
5165
  }
5022
5166
  function collectMatches(repoProjects, id, repo) {
5023
5167
  const matches = [];
5024
- for (const logical of readdirSync11(repoProjects)) {
5025
- const candidate = join38(repoProjects, logical, `${id}.jsonl`);
5026
- if (existsSync32(candidate)) {
5168
+ for (const logical of readdirSync12(repoProjects)) {
5169
+ const candidate = join39(repoProjects, logical, `${id}.jsonl`);
5170
+ if (existsSync33(candidate)) {
5027
5171
  matches.push(relative4(repo, candidate));
5028
5172
  }
5029
- const dir = join38(repoProjects, logical, id);
5030
- if (existsSync32(dir) && statSync8(dir).isDirectory()) {
5173
+ const dir = join39(repoProjects, logical, id);
5174
+ if (existsSync33(dir) && statSync9(dir).isDirectory()) {
5031
5175
  const dirRel = relative4(repo, dir);
5032
5176
  const staged = expandStagedDir(dirRel, repo);
5033
5177
  if (staged.length > 0) matches.push(...staged);
@@ -5062,8 +5206,8 @@ function unstageOne(rel, repo) {
5062
5206
  }
5063
5207
 
5064
5208
  // src/commands.pull.ts
5065
- import { existsSync as existsSync38, mkdirSync as mkdirSync9 } from "node:fs";
5066
- import { join as join45 } from "node:path";
5209
+ import { existsSync as existsSync39, mkdirSync as mkdirSync10 } from "node:fs";
5210
+ import { join as join46 } from "node:path";
5067
5211
 
5068
5212
  // src/commands.push.sections.ts
5069
5213
  init_color();
@@ -5158,20 +5302,20 @@ init_config();
5158
5302
 
5159
5303
  // src/extras-sync.ts
5160
5304
  init_config();
5161
- import { existsSync as existsSync35, statSync as statSync9 } from "node:fs";
5162
- import { join as join42 } from "node:path";
5305
+ import { existsSync as existsSync36, statSync as statSync10 } from "node:fs";
5306
+ import { join as join43 } from "node:path";
5163
5307
 
5164
5308
  // src/extras-sync.core.ts
5165
5309
  init_config();
5166
- import { cpSync as cpSync6, existsSync as existsSync33, lstatSync as lstatSync9, readdirSync as readdirSync12, rmSync as rmSync11 } from "node:fs";
5167
- import { basename, join as join39 } from "node:path";
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";
5168
5312
  init_utils();
5169
5313
  init_utils_json();
5170
5314
  function loadValidatedExtras(opts) {
5171
5315
  const repo = repoHome();
5172
- const mapPath = join39(repo, "path-map.json");
5173
- const repoExtras = join39(repo, "shared", "extras");
5174
- if (!existsSync33(mapPath) || opts.requireRepoExtras === true && !existsSync33(repoExtras)) {
5316
+ const mapPath = join40(repo, "path-map.json");
5317
+ const repoExtras = join40(repo, "shared", "extras");
5318
+ if (!existsSync34(mapPath) || opts.requireRepoExtras === true && !existsSync34(repoExtras)) {
5175
5319
  if (opts.missingMsg !== void 0) log(opts.missingMsg);
5176
5320
  return null;
5177
5321
  }
@@ -5193,26 +5337,26 @@ function* eachExtrasTarget(v, counts) {
5193
5337
  counts.unmapped++;
5194
5338
  continue;
5195
5339
  }
5196
- for (const dirname8 of dirnames) {
5197
- if (!whitelist.includes(dirname8)) {
5340
+ for (const dirname10 of dirnames) {
5341
+ if (!whitelist.includes(dirname10)) {
5198
5342
  counts.skipped++;
5199
5343
  continue;
5200
5344
  }
5201
- yield { logical, localRoot, dirname: dirname8 };
5345
+ yield { logical, localRoot, dirname: dirname10 };
5202
5346
  }
5203
5347
  }
5204
5348
  }
5205
5349
  function stripCollidingDstSymlinks(src, dst, isExcluded) {
5206
- if (!existsSync33(dst)) return;
5207
- for (const name of readdirSync12(src)) {
5350
+ if (!existsSync34(dst)) return;
5351
+ for (const name of readdirSync13(src)) {
5208
5352
  if (isExcluded(name)) continue;
5209
- const dstPath = join39(dst, name);
5353
+ const dstPath = join40(dst, name);
5210
5354
  const dstStat = lstatSync9(dstPath, { throwIfNoEntry: false });
5211
5355
  if (dstStat === void 0) continue;
5212
5356
  if (dstStat.isSymbolicLink()) {
5213
5357
  rmSync11(dstPath, { recursive: true, force: true });
5214
- } else if (dstStat.isDirectory() && lstatSync9(join39(src, name)).isDirectory()) {
5215
- stripCollidingDstSymlinks(join39(src, name), dstPath, isExcluded);
5358
+ } else if (dstStat.isDirectory() && lstatSync9(join40(src, name)).isDirectory()) {
5359
+ stripCollidingDstSymlinks(join40(src, name), dstPath, isExcluded);
5216
5360
  }
5217
5361
  }
5218
5362
  }
@@ -5239,8 +5383,8 @@ function copyExtras(src, dst) {
5239
5383
  rmSync11(dst, { recursive: true, force: true });
5240
5384
  cpSync6(src, dst, { recursive: true, force: true, verbatimSymlinks: true });
5241
5385
  }
5242
- function extrasDenySet(dirname8) {
5243
- return dirname8 === ".claude" ? CLAUDE_EXTRA_NEVER_SYNC : ALWAYS_NEVER_SYNC;
5386
+ function extrasDenySet(dirname10) {
5387
+ return dirname10 === ".claude" ? CLAUDE_EXTRA_NEVER_SYNC : ALWAYS_NEVER_SYNC;
5244
5388
  }
5245
5389
  function copyExtrasFiltered(src, dst, blockSet) {
5246
5390
  rmSync11(dst, { recursive: true, force: true });
@@ -5252,17 +5396,17 @@ function copyExtrasFiltered(src, dst, blockSet) {
5252
5396
  });
5253
5397
  }
5254
5398
  function prunePreservingDenied(src, dst, blockSet) {
5255
- for (const name of readdirSync12(dst)) {
5399
+ for (const name of readdirSync13(dst)) {
5256
5400
  if (isDeniedName(blockSet, name)) continue;
5257
- const dstPath = join39(dst, name);
5258
- const srcStat = lstatSync9(join39(src, name), { throwIfNoEntry: false });
5401
+ const dstPath = join40(dst, name);
5402
+ const srcStat = lstatSync9(join40(src, name), { throwIfNoEntry: false });
5259
5403
  if (srcStat === void 0) {
5260
5404
  rmSync11(dstPath, { recursive: true, force: true });
5261
5405
  continue;
5262
5406
  }
5263
5407
  const dstStat = lstatSync9(dstPath);
5264
5408
  if (srcStat.isDirectory() && dstStat.isDirectory()) {
5265
- prunePreservingDenied(join39(src, name), dstPath, blockSet);
5409
+ prunePreservingDenied(join40(src, name), dstPath, blockSet);
5266
5410
  } else if (srcStat.isDirectory() !== dstStat.isDirectory()) {
5267
5411
  rmSync11(dstPath, { recursive: true, force: true });
5268
5412
  }
@@ -5283,17 +5427,17 @@ function copyExtrasFilteredPreserving(src, dst, blockSet) {
5283
5427
  });
5284
5428
  }
5285
5429
  function prunePreservingBy(src, dst, isPreserved) {
5286
- for (const name of readdirSync12(dst)) {
5430
+ for (const name of readdirSync13(dst)) {
5287
5431
  if (isPreserved(name)) continue;
5288
- const dstPath = join39(dst, name);
5289
- const srcStat = lstatSync9(join39(src, name), { throwIfNoEntry: false });
5432
+ const dstPath = join40(dst, name);
5433
+ const srcStat = lstatSync9(join40(src, name), { throwIfNoEntry: false });
5290
5434
  if (srcStat === void 0) {
5291
5435
  rmSync11(dstPath, { recursive: true, force: true });
5292
5436
  continue;
5293
5437
  }
5294
5438
  const dstStat = lstatSync9(dstPath);
5295
5439
  if (srcStat.isDirectory() && dstStat.isDirectory()) {
5296
- prunePreservingBy(join39(src, name), dstPath, isPreserved);
5440
+ prunePreservingBy(join40(src, name), dstPath, isPreserved);
5297
5441
  } else if (srcStat.isDirectory() !== dstStat.isDirectory()) {
5298
5442
  rmSync11(dstPath, { recursive: true, force: true });
5299
5443
  }
@@ -5320,11 +5464,11 @@ init_utils_json();
5320
5464
 
5321
5465
  // src/extras-sync.remap.ts
5322
5466
  init_config();
5323
- import { existsSync as existsSync34, mkdirSync as mkdirSync7, readdirSync as readdirSync13, realpathSync as realpathSync4, rmSync as rmSync12 } from "node:fs";
5324
- import { dirname as dirname7, join as join41, sep as sep7 } from "node:path";
5467
+ import { existsSync as existsSync35, mkdirSync as mkdirSync8, readdirSync as readdirSync14, realpathSync as realpathSync4, rmSync as rmSync12 } from "node:fs";
5468
+ import { dirname as dirname9, join as join42, sep as sep7 } from "node:path";
5325
5469
 
5326
5470
  // src/extras-sync.planning-diff.ts
5327
- import { join as join40, normalize as normalize2, sep as sep6 } from "node:path";
5471
+ import { join as join41, normalize as normalize2, sep as sep6 } from "node:path";
5328
5472
  init_utils();
5329
5473
  function processRecord(fields, i, changed, deleted) {
5330
5474
  const status = fields[i];
@@ -5376,7 +5520,7 @@ function planningDeleteTargets(opts) {
5376
5520
  const { deleted } = parsePlanningDiff(raw);
5377
5521
  const logicalPrefix = "shared/extras/" + logical + "/";
5378
5522
  const prefix = logicalPrefix + ".planning/";
5379
- const planningRoot = join40(localRoot, ".planning");
5523
+ const planningRoot = join41(localRoot, ".planning");
5380
5524
  const planningRootBoundary = planningRoot + sep6;
5381
5525
  const targets = [];
5382
5526
  for (const repoPath of deleted) {
@@ -5384,7 +5528,7 @@ function planningDeleteTargets(opts) {
5384
5528
  continue;
5385
5529
  }
5386
5530
  const remainder = repoPath.slice(logicalPrefix.length);
5387
- const candidate = join40(localRoot, remainder);
5531
+ const candidate = join41(localRoot, remainder);
5388
5532
  const resolved = normalize2(candidate);
5389
5533
  if (!resolved.startsWith(planningRootBoundary)) {
5390
5534
  throw new NomadFatal(
@@ -5405,7 +5549,7 @@ function runExtrasOp(v, dryRun, paths, backup, copy) {
5405
5549
  const would = [];
5406
5550
  for (const t of eachExtrasTarget(v, counts)) {
5407
5551
  const { src, dst } = paths(t);
5408
- if (!existsSync34(src)) continue;
5552
+ if (!existsSync35(src)) continue;
5409
5553
  const item2 = `${t.logical}/${t.dirname}`;
5410
5554
  if (dryRun) {
5411
5555
  would.push(item2);
@@ -5418,15 +5562,15 @@ function runExtrasOp(v, dryRun, paths, backup, copy) {
5418
5562
  return { ...counts, done, would };
5419
5563
  }
5420
5564
  function pruneEmptyAncestors(target, planningRoot) {
5421
- let dir = dirname7(target);
5565
+ let dir = dirname9(target);
5422
5566
  while (dir !== planningRoot && dir.startsWith(planningRoot + sep7)) {
5423
5567
  try {
5424
- if (readdirSync13(dir).length > 0) break;
5568
+ if (readdirSync14(dir).length > 0) break;
5425
5569
  rmSync12(dir, { recursive: true, force: true });
5426
5570
  } catch {
5427
5571
  break;
5428
5572
  }
5429
- dir = dirname7(dir);
5573
+ dir = dirname9(dir);
5430
5574
  }
5431
5575
  }
5432
5576
  function tryRealpath(dir) {
@@ -5440,8 +5584,8 @@ function isInsidePlanningRoot(parentReal, rootReal) {
5440
5584
  return parentReal === rootReal || parentReal.startsWith(rootReal + sep7);
5441
5585
  }
5442
5586
  function deletePlanningTarget(target, planningRoot, repoCounterpart) {
5443
- if (existsSync34(repoCounterpart)) return;
5444
- const parentReal = tryRealpath(dirname7(target));
5587
+ if (existsSync35(repoCounterpart)) return;
5588
+ const parentReal = tryRealpath(dirname9(target));
5445
5589
  if (parentReal === void 0) return;
5446
5590
  const rootReal = tryRealpath(planningRoot);
5447
5591
  if (rootReal === void 0) return;
@@ -5450,7 +5594,7 @@ function deletePlanningTarget(target, planningRoot, repoCounterpart) {
5450
5594
  pruneEmptyAncestors(target, planningRoot);
5451
5595
  }
5452
5596
  function propagatePlanningDeletes(v, ts, prePostHeads, repo) {
5453
- const repoExtras = join41(repo, "shared", "extras");
5597
+ const repoExtras = join42(repo, "shared", "extras");
5454
5598
  for (const t of eachExtrasTarget(v, { unmapped: 0, skipped: 0 })) {
5455
5599
  if (t.dirname !== ".planning") continue;
5456
5600
  let raw;
@@ -5476,11 +5620,11 @@ function propagatePlanningDeletes(v, ts, prePostHeads, repo) {
5476
5620
  }
5477
5621
  const targets = planningDeleteTargets({ raw, logical: t.logical, localRoot: t.localRoot });
5478
5622
  if (targets.length === 0) continue;
5479
- backupExtrasWrite(join41(t.localRoot, t.dirname), ts, t.localRoot);
5480
- const planningRoot = join41(t.localRoot, ".planning");
5623
+ backupExtrasWrite(join42(t.localRoot, t.dirname), ts, t.localRoot);
5624
+ const planningRoot = join42(t.localRoot, ".planning");
5481
5625
  for (const target of targets) {
5482
5626
  const relToLocal = target.slice(t.localRoot.length + sep7.length);
5483
- deletePlanningTarget(target, planningRoot, join41(repoExtras, t.logical, relToLocal));
5627
+ deletePlanningTarget(target, planningRoot, join42(repoExtras, t.logical, relToLocal));
5484
5628
  }
5485
5629
  }
5486
5630
  }
@@ -5489,14 +5633,14 @@ function remapExtrasPush(ts, opts = {}) {
5489
5633
  const v = loadValidatedExtras({ missingMsg: "no path-map.json; skipping extras push" });
5490
5634
  if (v === null) return { unmapped: 0, skipped: 0, pushed: [], wouldPush: [] };
5491
5635
  const repo = repoHome();
5492
- const repoExtras = join41(repo, "shared", "extras");
5493
- if (!dryRun) mkdirSync7(repoExtras, { recursive: true });
5636
+ const repoExtras = join42(repo, "shared", "extras");
5637
+ if (!dryRun) mkdirSync8(repoExtras, { recursive: true });
5494
5638
  const { unmapped, skipped, done, would } = runExtrasOp(
5495
5639
  v,
5496
5640
  dryRun,
5497
- ({ localRoot, logical, dirname: dirname8 }) => ({
5498
- src: join41(localRoot, dirname8),
5499
- dst: join41(repoExtras, logical, dirname8)
5641
+ ({ localRoot, logical, dirname: dirname10 }) => ({
5642
+ src: join42(localRoot, dirname10),
5643
+ dst: join42(repoExtras, logical, dirname10)
5500
5644
  }),
5501
5645
  (dst) => backupRepoWrite(dst, ts, repo),
5502
5646
  // Push copy routing per extra type:
@@ -5508,7 +5652,7 @@ function remapExtrasPush(ts, opts = {}) {
5508
5652
  // (enforceAllowList / blockSetFor in commands.push.allowlist.ts)
5509
5653
  // remains the hard security boundary.
5510
5654
  // All others: copyExtrasFiltered with per-extra denylist.
5511
- (src, dst, dirname8) => dirname8 === ".planning" ? copyExtrasOverlayFiltered(src, dst, extrasDenySet(dirname8)) : copyExtrasFiltered(src, dst, extrasDenySet(dirname8))
5655
+ (src, dst, dirname10) => dirname10 === ".planning" ? copyExtrasOverlayFiltered(src, dst, extrasDenySet(dirname10)) : copyExtrasFiltered(src, dst, extrasDenySet(dirname10))
5512
5656
  );
5513
5657
  return { unmapped, skipped, pushed: done, wouldPush: would };
5514
5658
  }
@@ -5524,9 +5668,9 @@ function remapExtrasPull(ts, opts = {}) {
5524
5668
  const { unmapped, skipped, done, would } = runExtrasOp(
5525
5669
  v,
5526
5670
  dryRun,
5527
- ({ localRoot, logical, dirname: dirname8 }) => ({
5528
- src: join41(repo, "shared", "extras", logical, dirname8),
5529
- dst: join41(localRoot, dirname8)
5671
+ ({ localRoot, logical, dirname: dirname10 }) => ({
5672
+ src: join42(repo, "shared", "extras", logical, dirname10),
5673
+ dst: join42(localRoot, dirname10)
5530
5674
  }),
5531
5675
  // Snapshot the host-side dst BEFORE copyExtras clobbers it. Anchor on
5532
5676
  // localRoot so the backup tree mirrors the project layout.
@@ -5539,11 +5683,11 @@ function remapExtrasPull(ts, opts = {}) {
5539
5683
  // removals via the git-diff D set. The filter is defense-in-depth
5540
5684
  // against a repo poisoned out-of-band.
5541
5685
  // All others: copyExtras (exact mirror; rarely carry host-local files).
5542
- (src, dst, dirname8) => {
5543
- if (dirname8 === ".claude")
5544
- return copyExtrasFilteredPreserving(src, dst, extrasDenySet(dirname8));
5545
- if (dirname8 === ".planning")
5546
- return copyExtrasOverlayFiltered(src, dst, extrasDenySet(dirname8));
5686
+ (src, dst, dirname10) => {
5687
+ if (dirname10 === ".claude")
5688
+ return copyExtrasFilteredPreserving(src, dst, extrasDenySet(dirname10));
5689
+ if (dirname10 === ".planning")
5690
+ return copyExtrasOverlayFiltered(src, dst, extrasDenySet(dirname10));
5547
5691
  return copyExtras(src, dst);
5548
5692
  }
5549
5693
  );
@@ -5567,20 +5711,20 @@ function divergenceCheckExtras(ts) {
5567
5711
  const v = loadValidatedExtras({});
5568
5712
  if (v === null) return;
5569
5713
  const counts = { unmapped: 0, skipped: 0 };
5570
- const backupRoot = join42(backupBase(), ts, "extras");
5714
+ const backupRoot = join43(backupBase(), ts, "extras");
5571
5715
  const repo = repoHome();
5572
- for (const { logical, localRoot, dirname: dirname8 } of eachExtrasTarget(v, counts)) {
5573
- const local = join42(localRoot, dirname8);
5574
- const repoEntry = join42(repo, "shared", "extras", logical, dirname8);
5575
- if (!existsSync35(local) || !existsSync35(repoEntry)) continue;
5716
+ for (const { logical, localRoot, dirname: dirname10 } of eachExtrasTarget(v, counts)) {
5717
+ const local = join43(localRoot, dirname10);
5718
+ const repoEntry = join43(repo, "shared", "extras", logical, dirname10);
5719
+ if (!existsSync36(local) || !existsSync36(repoEntry)) continue;
5576
5720
  const diff = listDivergingFiles(local, repoEntry);
5577
5721
  if (diff.length === 0) continue;
5578
- const projectBackupRoot = join42(backupRoot, encodePath(localRoot));
5722
+ const projectBackupRoot = join43(backupRoot, encodePath(localRoot));
5579
5723
  warn(
5580
5724
  divergenceWarnLine({
5581
- dirname: dirname8,
5725
+ dirname: dirname10,
5582
5726
  logical,
5583
- isDir: statSync9(local).isDirectory(),
5727
+ isDir: statSync10(local).isDirectory(),
5584
5728
  count: diff.length,
5585
5729
  projectBackupRoot
5586
5730
  })
@@ -5591,8 +5735,8 @@ function divergenceCheckExtras(ts) {
5591
5735
 
5592
5736
  // src/skills-sync.ts
5593
5737
  init_config();
5594
- import { existsSync as existsSync36, lstatSync as lstatSync10, mkdirSync as mkdirSync8, readdirSync as readdirSync14, rmSync as rmSync13 } from "node:fs";
5595
- import { join as join43 } from "node:path";
5738
+ import { existsSync as existsSync37, lstatSync as lstatSync10, mkdirSync as mkdirSync9, readdirSync as readdirSync15, rmSync as rmSync13 } from "node:fs";
5739
+ import { join as join44 } from "node:path";
5596
5740
  init_utils_fs();
5597
5741
  function isGsdOwned(name) {
5598
5742
  return name.startsWith(GSD_PREFIX);
@@ -5601,7 +5745,7 @@ function isSkillExcluded(name) {
5601
5745
  return isGsdOwned(name) || isDeniedName(ALWAYS_NEVER_SYNC, name);
5602
5746
  }
5603
5747
  function copySkillsPush(src, dst) {
5604
- const srcNames = readdirSync14(src, { encoding: "utf8" });
5748
+ const srcNames = readdirSync15(src, { encoding: "utf8" });
5605
5749
  const blockSet = /* @__PURE__ */ new Set([
5606
5750
  ...srcNames.filter((n) => isGsdOwned(n)),
5607
5751
  ...ALWAYS_NEVER_SYNC
@@ -5612,30 +5756,30 @@ function copySkillsPull(src, dst) {
5612
5756
  copyExtrasFilteredPreservingBy(src, dst, isSkillExcluded);
5613
5757
  }
5614
5758
  function syncSkillsPull(ts) {
5615
- const sharedSkills = join43(repoHome(), "shared", "skills");
5616
- if (!existsSync36(sharedSkills)) return;
5617
- const localSkills = join43(claudeHome(), "skills");
5759
+ const sharedSkills = join44(repoHome(), "shared", "skills");
5760
+ if (!existsSync37(sharedSkills)) return;
5761
+ const localSkills = join44(claudeHome(), "skills");
5618
5762
  const dstStat = lstatSync10(localSkills, { throwIfNoEntry: false });
5619
5763
  if (dstStat?.isSymbolicLink() === true) {
5620
5764
  backupBeforeWrite(localSkills, ts);
5621
5765
  rmSync13(localSkills, { recursive: true, force: true });
5622
- mkdirSync8(localSkills, { recursive: true });
5766
+ mkdirSync9(localSkills, { recursive: true });
5623
5767
  }
5624
5768
  copySkillsPull(sharedSkills, localSkills);
5625
5769
  }
5626
5770
  function syncSkillsPush() {
5627
- const localSkills = join43(claudeHome(), "skills");
5771
+ const localSkills = join44(claudeHome(), "skills");
5628
5772
  const stat = lstatSync10(localSkills, { throwIfNoEntry: false });
5629
5773
  if (stat === void 0) return;
5630
5774
  if (stat.isSymbolicLink()) return;
5631
- const sharedSkills = join43(repoHome(), "shared", "skills");
5775
+ const sharedSkills = join44(repoHome(), "shared", "skills");
5632
5776
  copySkillsPush(localSkills, sharedSkills);
5633
5777
  }
5634
5778
 
5635
5779
  // src/preview.ts
5636
5780
  init_config();
5637
- import { existsSync as existsSync37 } from "node:fs";
5638
- import { join as join44 } from "node:path";
5781
+ import { existsSync as existsSync38 } from "node:fs";
5782
+ import { join as join45 } from "node:path";
5639
5783
 
5640
5784
  // node_modules/diff/libesm/diff/base.js
5641
5785
  var Diff = class {
@@ -5921,7 +6065,7 @@ function diffJsonStrings(currentJsonText, newJsonText) {
5921
6065
  return lines.join("\n");
5922
6066
  }
5923
6067
  function readJsonOrNull(path) {
5924
- if (!existsSync37(path)) return null;
6068
+ if (!existsSync38(path)) return null;
5925
6069
  try {
5926
6070
  return readJson(path);
5927
6071
  } catch {
@@ -5935,12 +6079,12 @@ function previewSettings(basePath, hostPath, settingsPath) {
5935
6079
  }
5936
6080
  const notes = [];
5937
6081
  const hostOverrides = readJsonOrNull(hostPath);
5938
- if (hostOverrides === null && existsSync37(hostPath)) {
6082
+ if (hostOverrides === null && existsSync38(hostPath)) {
5939
6083
  notes.push(`malformed hosts/${HOST}.json; ignoring overrides`);
5940
6084
  }
5941
6085
  const merged = stripGsdHookEntries(deepMerge(base, hostOverrides ?? {}));
5942
6086
  const current = readJsonOrNull(settingsPath);
5943
- if (current === null && existsSync37(settingsPath)) {
6087
+ if (current === null && existsSync38(settingsPath)) {
5944
6088
  return { diff: "", notes: [...notes, "malformed; skipping diff"] };
5945
6089
  }
5946
6090
  const strippedCurrent = stripGsdHookEntries(current ?? {});
@@ -5981,9 +6125,9 @@ function computePreview(ts, map, verb = "pull") {
5981
6125
  onPreview: (e) => addItem(links, formatLinkRow(e))
5982
6126
  });
5983
6127
  const settingsResult = previewSettings(
5984
- join44(repo, "shared", "settings.base.json"),
5985
- join44(repo, "hosts", `${HOST}.json`),
5986
- join44(claude, "settings.json")
6128
+ join45(repo, "shared", "settings.base.json"),
6129
+ join45(repo, "hosts", `${HOST}.json`),
6130
+ join45(claude, "settings.json")
5987
6131
  );
5988
6132
  const settingsSection = buildSettingsSectionForPreview(settingsResult);
5989
6133
  const sessions = section("Sessions");
@@ -6184,8 +6328,8 @@ function cmdPull(opts = {}) {
6184
6328
  const forceRemote = opts.forceRemote === true;
6185
6329
  const repo = repoHome();
6186
6330
  const backup = backupBase();
6187
- if (!existsSync38(repo)) die(`repo not cloned at ${repo}`);
6188
- if (!existsSync38(join45(repo, "shared", "settings.base.json"))) {
6331
+ if (!existsSync39(repo)) die(`repo not cloned at ${repo}`);
6332
+ if (!existsSync39(join46(repo, "shared", "settings.base.json"))) {
6189
6333
  die("repo not initialized; run 'nomad init' to scaffold");
6190
6334
  }
6191
6335
  const handle = acquireLock("pull");
@@ -6194,9 +6338,9 @@ function cmdPull(opts = {}) {
6194
6338
  const ts = freshBackupTs(backup);
6195
6339
  handleWedge(repo, forceRemote);
6196
6340
  if (!dryRun) {
6197
- const backupRoot = join45(backup, ts);
6341
+ const backupRoot = join46(backup, ts);
6198
6342
  try {
6199
- mkdirSync9(backupRoot, { recursive: true });
6343
+ mkdirSync10(backupRoot, { recursive: true });
6200
6344
  } catch (err) {
6201
6345
  die(`could not create backup dir: ${err.message}`);
6202
6346
  }
@@ -6207,8 +6351,8 @@ function cmdPull(opts = {}) {
6207
6351
  const prePostHeads = capturePrePostHeads(repo, () => {
6208
6352
  gitOrFatal(["pull", "--rebase", "--autostash"], "git pull --rebase", repo);
6209
6353
  });
6210
- const mapPath = join45(repo, "path-map.json");
6211
- const map = existsSync38(mapPath) ? readPathMap(mapPath) : { projects: {} };
6354
+ const mapPath = join46(repo, "path-map.json");
6355
+ const map = existsSync39(mapPath) ? readPathMap(mapPath) : { projects: {} };
6212
6356
  divergenceCheckExtras(ts);
6213
6357
  if (dryRun) {
6214
6358
  computePreview(ts, map, "pull");
@@ -6230,8 +6374,63 @@ function cmdPull(opts = {}) {
6230
6374
 
6231
6375
  // src/commands.push.ts
6232
6376
  init_config();
6233
- import { existsSync as existsSync40 } from "node:fs";
6234
- import { join as join47, relative as relative5 } from "node:path";
6377
+ import { existsSync as existsSync43 } from "node:fs";
6378
+ import { join as join51 } from "node:path";
6379
+
6380
+ // src/commands.push.selection.ts
6381
+ init_config();
6382
+ import { existsSync as existsSync40, statSync as statSync11 } from "node:fs";
6383
+ import { join as join47 } from "node:path";
6384
+ init_utils_json();
6385
+ function buildCurrentMap(map) {
6386
+ const current = {};
6387
+ if (map === null) return current;
6388
+ const claude = claudeHome();
6389
+ for (const [, hostMap] of Object.entries(map.projects)) {
6390
+ const localPath = hostMap[HOST];
6391
+ if (!localPath) continue;
6392
+ const localDir = join47(claude, "projects", encodePath(localPath));
6393
+ if (!existsSync40(localDir)) continue;
6394
+ for (const f of enumerateSourceFiles(localDir)) {
6395
+ const st = statSync11(f);
6396
+ current[f] = { size: st.size, mtime: st.mtimeMs };
6397
+ }
6398
+ }
6399
+ return current;
6400
+ }
6401
+ function computePushSelection(map, old, scannerVersion, configHash, fullScan) {
6402
+ const current = buildCurrentMap(map);
6403
+ const fullRescan = shouldFullRescan(old, scannerVersion, configHash, fullScan);
6404
+ const hashCache = /* @__PURE__ */ new Map();
6405
+ const cachedHash = (p) => {
6406
+ const hit = hashCache.get(p);
6407
+ if (hit !== void 0) return hit;
6408
+ const h2 = hashFile(p);
6409
+ hashCache.set(p, h2);
6410
+ return h2;
6411
+ };
6412
+ const delta = fullRescan ? { changed: new Set(Object.keys(current)), deleted: [] } : diffManifest(old, current, cachedHash);
6413
+ const files = {};
6414
+ for (const [key, meta] of Object.entries(current)) {
6415
+ const hash = delta.changed.has(key) ? cachedHash(key) : old.files[key].hash;
6416
+ files[key] = { size: meta.size, mtime: meta.mtime, hash };
6417
+ }
6418
+ return {
6419
+ selection: fullRescan ? void 0 : delta,
6420
+ newManifest: buildManifest(files, scannerVersion, configHash)
6421
+ };
6422
+ }
6423
+ function loadSelectionForPush(mapPath, old, scannerVersion, configHash, fullScan) {
6424
+ const map = existsSync40(mapPath) ? readPathMap(mapPath) : null;
6425
+ const { selection, newManifest } = computePushSelection(
6426
+ map,
6427
+ old,
6428
+ scannerVersion,
6429
+ configHash,
6430
+ fullScan
6431
+ );
6432
+ return { map, selection, newManifest };
6433
+ }
6235
6434
 
6236
6435
  // src/commands.push.allowlist.ts
6237
6436
  init_config();
@@ -6322,6 +6521,85 @@ function enforceAllowList(statusPorcelain, map) {
6322
6521
  throw new NomadFatal("push allow-list violations");
6323
6522
  }
6324
6523
 
6524
+ // src/commands.push.settings.ts
6525
+ init_config();
6526
+ import { existsSync as existsSync41 } from "node:fs";
6527
+ import { join as join48 } from "node:path";
6528
+ init_utils();
6529
+ init_utils_fs();
6530
+ init_utils_json();
6531
+ function stripGsdHooksFromBase(repo, backup) {
6532
+ const basePath = join48(repo, "shared", "settings.base.json");
6533
+ if (!existsSync41(basePath)) return;
6534
+ let base;
6535
+ try {
6536
+ base = readJson(basePath);
6537
+ } catch {
6538
+ return;
6539
+ }
6540
+ if (!baseHasGsdHookEntries(base)) return;
6541
+ const stripped = stripGsdHookEntries(base);
6542
+ const ts = freshBackupTs(backup);
6543
+ backupRepoWrite(basePath, ts, repo);
6544
+ writeJsonAtomic(basePath, stripped);
6545
+ }
6546
+ function reportSettingsAheadDrift(repo) {
6547
+ const basePath = join48(repo, "shared", "settings.base.json");
6548
+ if (!existsSync41(basePath)) return;
6549
+ const settingsPath = join48(claudeHome(), "settings.json");
6550
+ if (!existsSync41(settingsPath)) return;
6551
+ try {
6552
+ const base = readJson(basePath);
6553
+ const hostPath = join48(repo, "hosts", `${HOST}.json`);
6554
+ const overrides = existsSync41(hostPath) ? readJson(hostPath) : {};
6555
+ const merged = deepMerge(base, overrides);
6556
+ const settings = readJson(settingsPath);
6557
+ const { ahead } = classifySettingsDrift(merged, settings);
6558
+ const { promotable } = partitionByCaptureExclusion(ahead);
6559
+ if (promotable.length === 0) return;
6560
+ const { phrase, pronoun, verb } = describeSettings(promotable);
6561
+ warn(
6562
+ `your settings.json has ${phrase} that ${verb} not yet in the repo; run 'nomad capture-settings' to save ${pronoun} (or 'nomad capture-settings --host' for host-specific values).`
6563
+ );
6564
+ } catch {
6565
+ }
6566
+ }
6567
+
6568
+ // src/commands.push.guards.ts
6569
+ init_push_checks();
6570
+ init_utils();
6571
+ import { join as join49, relative as relative5 } from "node:path";
6572
+ function guardGitlinks(repo) {
6573
+ const gitlinks = findGitlinks(join49(repo, "shared"));
6574
+ if (gitlinks.length === 0) return;
6575
+ for (const p of gitlinks) {
6576
+ const rel = relative5(repo, p);
6577
+ fail(`gitlink: ${rel} would push as submodule (run: rm -rf ${rel} or remove the nested repo)`);
6578
+ }
6579
+ const noun = gitlinks.length === 1 ? "entry" : "entries";
6580
+ throw new NomadFatal(
6581
+ `gitlink trap: ${gitlinks.length} nested .git ${noun} in shared/; remove before retry`
6582
+ );
6583
+ }
6584
+ function guardResolutionModeConflicts(dryRun, redactAll, allowAll, allowRule) {
6585
+ const hasAllow = allowAll || allowRule !== void 0;
6586
+ const wantsResolution = redactAll || hasAllow;
6587
+ if (redactAll && hasAllow) {
6588
+ die("--redact-all, --allow-all, and --allow are mutually exclusive resolution modes");
6589
+ }
6590
+ if (allowAll && allowRule !== void 0) {
6591
+ die("--redact-all, --allow-all, and --allow are mutually exclusive resolution modes");
6592
+ }
6593
+ if (dryRun && wantsResolution) {
6594
+ die(
6595
+ "--redact-all, --allow-all, and --allow cannot be combined with --dry-run (dry-run resolves nothing)"
6596
+ );
6597
+ }
6598
+ }
6599
+
6600
+ // src/commands.push.steps.ts
6601
+ init_config();
6602
+
6325
6603
  // src/push-global-config.ts
6326
6604
  init_config();
6327
6605
  import { execFileSync as execFileSync18 } from "node:child_process";
@@ -6394,24 +6672,36 @@ function collectGlobalConfigChanges(repoHome2, hostname2, opts) {
6394
6672
  return changes;
6395
6673
  }
6396
6674
 
6397
- // src/commands.push.ts
6675
+ // src/commands.push.steps.ts
6398
6676
  init_push_leak_verdict();
6399
- init_push_checks();
6400
6677
 
6401
6678
  // src/push-preview.ts
6402
6679
  init_color();
6403
6680
  init_config();
6404
6681
  init_config_sharedDirs_guard();
6405
6682
  import { randomBytes as randomBytes2 } from "node:crypto";
6406
- import { copyFileSync, existsSync as existsSync39, mkdirSync as mkdirSync10, readdirSync as readdirSync15, rmSync as rmSync14 } from "node:fs";
6683
+ import { copyFileSync, existsSync as existsSync42, mkdirSync as mkdirSync11, readdirSync as readdirSync16, rmSync as rmSync14 } from "node:fs";
6407
6684
  import { homedir as homedir5 } from "node:os";
6408
- import { join as join46 } from "node:path";
6685
+ import { join as join50, relative as relative6, sep as sep8 } from "node:path";
6409
6686
  init_push_leak_verdict();
6410
6687
  init_push_gitleaks();
6411
6688
  init_utils_fs();
6412
6689
  init_utils_json();
6413
6690
  var NOTHING_TO_SCAN_ROW = `${dim(infoGlyph)} nothing to scan, no leaks`;
6414
- function stageSessions(tmpRoot, map) {
6691
+ function stageSessionDir(localDir, dstDir, changed) {
6692
+ if (changed !== void 0) {
6693
+ const prefix = `${localDir}${sep8}`;
6694
+ const matching = [...changed].filter((p) => p.startsWith(prefix));
6695
+ if (matching.length === 0) return false;
6696
+ for (const src of matching) {
6697
+ copyFileAtomic(src, join50(dstDir, relative6(localDir, src)));
6698
+ }
6699
+ return true;
6700
+ }
6701
+ copyDirJsonlOnly(localDir, dstDir);
6702
+ return true;
6703
+ }
6704
+ function stageSessions(tmpRoot, map, changed) {
6415
6705
  if (typeof map.projects !== "object" || map.projects === null) return 0;
6416
6706
  const reverse = /* @__PURE__ */ new Map();
6417
6707
  for (const [logical, hosts] of Object.entries(map.projects)) {
@@ -6420,14 +6710,15 @@ function stageSessions(tmpRoot, map) {
6420
6710
  if (!p || p === "TBD") continue;
6421
6711
  reverse.set(encodePath(p), logical);
6422
6712
  }
6423
- const localProjects = join46(claudeHome(), "projects");
6424
- if (!existsSync39(localProjects)) return 0;
6713
+ const localProjects = join50(claudeHome(), "projects");
6714
+ if (!existsSync42(localProjects)) return 0;
6425
6715
  let staged = 0;
6426
- for (const dir of readdirSync15(localProjects)) {
6716
+ for (const dir of readdirSync16(localProjects)) {
6427
6717
  const logical = reverse.get(dir);
6428
6718
  if (!logical) continue;
6429
- copyDirJsonlOnly(join46(localProjects, dir), join46(tmpRoot, "shared", "projects", logical));
6430
- staged++;
6719
+ const localDir = join50(localProjects, dir);
6720
+ const dstDir = join50(tmpRoot, "shared", "projects", logical);
6721
+ if (stageSessionDir(localDir, dstDir, changed)) staged++;
6431
6722
  }
6432
6723
  return staged;
6433
6724
  }
@@ -6440,31 +6731,31 @@ function stageExtras(tmpRoot, map) {
6440
6731
  assertSafeLogical(logical);
6441
6732
  const localRoot = map.projects[logical]?.[HOST];
6442
6733
  if (!localRoot || localRoot === "TBD") continue;
6443
- for (const dirname8 of dirnames) {
6444
- if (!whitelist.includes(dirname8)) continue;
6445
- const src = join46(localRoot, dirname8);
6446
- if (!existsSync39(src)) continue;
6447
- const dst = join46(tmpRoot, "shared", "extras", logical, dirname8);
6734
+ for (const dirname10 of dirnames) {
6735
+ if (!whitelist.includes(dirname10)) continue;
6736
+ const src = join50(localRoot, dirname10);
6737
+ if (!existsSync42(src)) continue;
6738
+ const dst = join50(tmpRoot, "shared", "extras", logical, dirname10);
6448
6739
  copyExtras(src, dst);
6449
6740
  staged++;
6450
6741
  }
6451
6742
  }
6452
6743
  return staged;
6453
6744
  }
6454
- function previewPushLeaks(map) {
6455
- const cacheDir = join46(homedir5(), ".cache", "claude-nomad");
6456
- mkdirSync10(cacheDir, { recursive: true });
6745
+ function previewPushLeaks(map, opts = {}) {
6746
+ const cacheDir = join50(homedir5(), ".cache", "claude-nomad");
6747
+ mkdirSync11(cacheDir, { recursive: true });
6457
6748
  const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes2(4).toString("hex")}`;
6458
- const tmpRoot = join46(cacheDir, `push-preview-tree-${stamp}`);
6749
+ const tmpRoot = join50(cacheDir, `push-preview-tree-${stamp}`);
6459
6750
  try {
6460
- const sessionCount = stageSessions(tmpRoot, map);
6751
+ const sessionCount = stageSessions(tmpRoot, map, opts.selection?.changed);
6461
6752
  const extrasCount = stageExtras(tmpRoot, map);
6462
6753
  if (sessionCount + extrasCount === 0) {
6463
6754
  return { leak: false, verdictRow: NOTHING_TO_SCAN_ROW, recovery: null, findings: [] };
6464
6755
  }
6465
- const ignoreFile = join46(repoHome(), ".gitleaksignore");
6466
- if (existsSync39(ignoreFile)) {
6467
- copyFileSync(ignoreFile, join46(tmpRoot, ".gitleaksignore"));
6756
+ const ignoreFile = join50(repoHome(), ".gitleaksignore");
6757
+ if (existsSync42(ignoreFile)) {
6758
+ copyFileSync(ignoreFile, join50(tmpRoot, ".gitleaksignore"));
6468
6759
  }
6469
6760
  let findings;
6470
6761
  try {
@@ -6481,59 +6772,9 @@ function previewPushLeaks(map) {
6481
6772
  }
6482
6773
  }
6483
6774
 
6484
- // src/commands.push.ts
6775
+ // src/commands.push.steps.ts
6485
6776
  init_utils();
6486
- init_utils_fs();
6487
- init_utils_json();
6488
- function stripGsdHooksFromBase(repo, backup) {
6489
- const basePath = join47(repo, "shared", "settings.base.json");
6490
- if (!existsSync40(basePath)) return;
6491
- let base;
6492
- try {
6493
- base = readJson(basePath);
6494
- } catch {
6495
- return;
6496
- }
6497
- if (!baseHasGsdHookEntries(base)) return;
6498
- const stripped = stripGsdHookEntries(base);
6499
- const ts = freshBackupTs(backup);
6500
- backupRepoWrite(basePath, ts, repo);
6501
- writeJsonAtomic(basePath, stripped);
6502
- }
6503
- function reportSettingsAheadDrift(repo) {
6504
- const basePath = join47(repo, "shared", "settings.base.json");
6505
- if (!existsSync40(basePath)) return;
6506
- const settingsPath = join47(claudeHome(), "settings.json");
6507
- if (!existsSync40(settingsPath)) return;
6508
- try {
6509
- const base = readJson(basePath);
6510
- const hostPath = join47(repo, "hosts", `${HOST}.json`);
6511
- const overrides = existsSync40(hostPath) ? readJson(hostPath) : {};
6512
- const merged = deepMerge(base, overrides);
6513
- const settings = readJson(settingsPath);
6514
- const { ahead } = classifySettingsDrift(merged, settings);
6515
- const { promotable } = partitionByCaptureExclusion(ahead);
6516
- if (promotable.length === 0) return;
6517
- const { phrase, pronoun, verb } = describeSettings(promotable);
6518
- warn(
6519
- `your settings.json has ${phrase} that ${verb} not yet in the repo; run 'nomad capture-settings' to save ${pronoun} (or 'nomad capture-settings --host' for host-specific values).`
6520
- );
6521
- } catch {
6522
- }
6523
- }
6524
- function guardGitlinks(repo) {
6525
- const gitlinks = findGitlinks(join47(repo, "shared"));
6526
- if (gitlinks.length === 0) return;
6527
- for (const p of gitlinks) {
6528
- const rel = relative5(repo, p);
6529
- fail(`gitlink: ${rel} would push as submodule (run: rm -rf ${rel} or remove the nested repo)`);
6530
- }
6531
- const noun = gitlinks.length === 1 ? "entry" : "entries";
6532
- throw new NomadFatal(
6533
- `gitlink trap: ${gitlinks.length} nested .git ${noun} in shared/; remove before retry`
6534
- );
6535
- }
6536
- async function commitAndPush(st, ts, map, redactAll, allowAll, allowRule, repo) {
6777
+ async function commitAndPush(st, ts, map, resolution, repo, newManifest) {
6537
6778
  gitOrFatal(["add", "-A"], "git add", repo);
6538
6779
  const staged = parsePorcelainZ2(gitStatusPorcelainZ(repo));
6539
6780
  const toDrop = staged.filter((p) => isGsdDropped(p));
@@ -6549,55 +6790,61 @@ async function commitAndPush(st, ts, map, redactAll, allowAll, allowRule, repo)
6549
6790
  let verdict = withSpinner("Scanning for secrets", () => scanPushVerdict(repo));
6550
6791
  if (verdict.leak) {
6551
6792
  renderPushTree(st, verdict);
6552
- verdict = await resolveLeakFindings(verdict, ts, map, { redactAll, allowAll, allowRule });
6793
+ verdict = await resolveLeakFindings(verdict, ts, map, resolution);
6553
6794
  }
6554
6795
  gitOrFatal(["commit", "-m", `chore: sync from ${HOST}`], "git commit", repo);
6555
6796
  withSpinner("Pushing", () => gitOrFatal(["push"], "git push", repo));
6797
+ try {
6798
+ writeManifest(manifestPath(), newManifest);
6799
+ } catch (err) {
6800
+ warn(`could not write push manifest (next push will full-rescan): ${String(err)}`);
6801
+ }
6556
6802
  renderPushTree(st, verdict);
6557
6803
  }
6558
- function runDryRunPreview(st, map, repo) {
6804
+ function runDryRunPreview(st, map, repo, selection) {
6559
6805
  st.globalConfig = collectGlobalConfigChanges(repo, HOST, { staged: false });
6560
6806
  if (map === null) {
6561
6807
  renderNoScanTree(st, { noMapHint: true });
6562
6808
  return;
6563
6809
  }
6564
- const verdict = withSpinner("Scanning for secrets", () => previewPushLeaks(map));
6810
+ const verdict = withSpinner("Scanning for secrets", () => previewPushLeaks(map, { selection }));
6565
6811
  renderPushTree(st, verdict);
6566
6812
  if (verdict.recovery !== null) fail(verdict.recovery);
6567
6813
  }
6568
- function guardResolutionModeConflicts(dryRun, redactAll, allowAll, allowRule) {
6569
- const hasAllow = allowAll || allowRule !== void 0;
6570
- const wantsResolution = redactAll || hasAllow;
6571
- if (redactAll && hasAllow) {
6572
- die("--redact-all, --allow-all, and --allow are mutually exclusive resolution modes");
6573
- }
6574
- if (allowAll && allowRule !== void 0) {
6575
- die("--redact-all, --allow-all, and --allow are mutually exclusive resolution modes");
6576
- }
6577
- if (dryRun && wantsResolution) {
6578
- die(
6579
- "--redact-all, --allow-all, and --allow cannot be combined with --dry-run (dry-run resolves nothing)"
6580
- );
6581
- }
6582
- }
6814
+
6815
+ // src/commands.push.ts
6816
+ init_push_checks();
6817
+ init_utils();
6818
+ init_utils_fs();
6583
6819
  async function cmdPush(opts = {}) {
6584
6820
  const dryRun = opts.dryRun === true;
6585
6821
  const redactAll = opts.redactAll === true;
6586
6822
  const allowAll = opts.allowAll === true;
6587
6823
  const allowRule = opts.allowRule;
6824
+ const fullScan = opts.fullScan === true;
6588
6825
  guardResolutionModeConflicts(dryRun, redactAll, allowAll, allowRule);
6589
6826
  const repo = repoHome();
6590
6827
  const backup = backupBase();
6591
- if (!existsSync40(repo)) die(`repo not cloned at ${repo}`);
6828
+ if (!existsSync43(repo)) die(`repo not cloned at ${repo}`);
6592
6829
  const handle = acquireLock("push");
6593
6830
  if (handle === null) process.exit(0);
6594
6831
  try {
6595
6832
  console.log(dryRun ? `push on host=${HOST} (dry-run)` : `push on host=${HOST}`);
6596
6833
  reportSettingsAheadDrift(repo);
6597
- probeGitleaks();
6834
+ const scannerVersion = probeGitleaks();
6835
+ const configHash = computeConfigHash();
6836
+ const old = readManifest(manifestPath());
6837
+ const mapPath = join51(repo, "path-map.json");
6838
+ const { map, selection, newManifest } = loadSelectionForPush(
6839
+ mapPath,
6840
+ old,
6841
+ scannerVersion,
6842
+ configHash,
6843
+ fullScan
6844
+ );
6598
6845
  withSpinner("Rebasing onto origin", () => rebaseBeforePush(repo));
6599
6846
  const ts = freshBackupTs(backup);
6600
- const remap = withSpinner("Syncing sessions", () => remapPush(ts, { dryRun }));
6847
+ const remap = withSpinner("Syncing sessions", () => remapPush(ts, { dryRun, selection }));
6601
6848
  const extras = withSpinner("Syncing extras", () => remapExtrasPush(ts, { dryRun }));
6602
6849
  if (!dryRun) {
6603
6850
  syncSkillsPush();
@@ -6611,15 +6858,13 @@ async function cmdPush(opts = {}) {
6611
6858
  renderNoScanTree(st);
6612
6859
  return;
6613
6860
  }
6614
- const mapPath = join47(repo, "path-map.json");
6615
- if (!existsSync40(mapPath)) {
6616
- if (dryRun) return runDryRunPreview(st, null, repo);
6617
- die("path-map.json missing, cannot enforce push allow-list");
6861
+ if (map === null) {
6862
+ if (dryRun) return runDryRunPreview(st, null, repo, selection);
6863
+ return die("path-map.json missing, cannot enforce push allow-list");
6618
6864
  }
6619
- const map = readPathMap(mapPath);
6620
6865
  if (status) enforceAllowList(status, map);
6621
- if (dryRun) return runDryRunPreview(st, map, repo);
6622
- await commitAndPush(st, ts, map, redactAll, allowAll, allowRule, repo);
6866
+ if (dryRun) return runDryRunPreview(st, map, repo, selection);
6867
+ await commitAndPush(st, ts, map, { redactAll, allowAll, allowRule }, repo, newManifest);
6623
6868
  } catch (err) {
6624
6869
  if (err instanceof NomadFatal) {
6625
6870
  fail(err.message);
@@ -6676,18 +6921,18 @@ init_config();
6676
6921
 
6677
6922
  // src/diff.ts
6678
6923
  init_config();
6679
- import { existsSync as existsSync41 } from "node:fs";
6680
- import { join as join48 } from "node:path";
6924
+ import { existsSync as existsSync44 } from "node:fs";
6925
+ import { join as join52 } from "node:path";
6681
6926
  init_utils();
6682
6927
  init_utils_fs();
6683
6928
  init_utils_json();
6684
6929
  function cmdDiff() {
6685
6930
  try {
6686
6931
  const repo = repoHome();
6687
- if (!existsSync41(repo)) die(`repo not cloned at ${repo}`);
6932
+ if (!existsSync44(repo)) die(`repo not cloned at ${repo}`);
6688
6933
  const ts = freshBackupTs(backupBase());
6689
- const mapPath = join48(repo, "path-map.json");
6690
- const map = existsSync41(mapPath) ? readPathMap(mapPath) : { projects: {} };
6934
+ const mapPath = join52(repo, "path-map.json");
6935
+ const map = existsSync44(mapPath) ? readPathMap(mapPath) : { projects: {} };
6691
6936
  computePreview(ts, map, "diff");
6692
6937
  } catch (err) {
6693
6938
  if (err instanceof NomadFatal) {
@@ -6701,8 +6946,8 @@ function cmdDiff() {
6701
6946
 
6702
6947
  // src/init.ts
6703
6948
  init_config();
6704
- import { existsSync as existsSync43, mkdirSync as mkdirSync11, writeFileSync as writeFileSync6 } from "node:fs";
6705
- import { join as join50 } from "node:path";
6949
+ import { existsSync as existsSync46, mkdirSync as mkdirSync12, writeFileSync as writeFileSync6 } from "node:fs";
6950
+ import { join as join54 } from "node:path";
6706
6951
 
6707
6952
  // src/init.gh-onboard.ts
6708
6953
  init_config();
@@ -6784,33 +7029,33 @@ init_config();
6784
7029
  init_utils();
6785
7030
  init_utils_fs();
6786
7031
  init_utils_json();
6787
- import { copyFileSync as copyFileSync2, cpSync as cpSync7, existsSync as existsSync42, rmSync as rmSync15, statSync as statSync10 } from "node:fs";
6788
- import { join as join49 } from "node:path";
7032
+ import { copyFileSync as copyFileSync2, cpSync as cpSync7, existsSync as existsSync45, rmSync as rmSync15, statSync as statSync12 } from "node:fs";
7033
+ import { join as join53 } from "node:path";
6789
7034
  function snapshotIntoShared(map) {
6790
7035
  const repo = repoHome();
6791
7036
  const claude = claudeHome();
6792
7037
  for (const name of allSharedLinks(map)) {
6793
- const src = join49(claude, name);
6794
- if (!existsSync42(src)) continue;
6795
- const dst = join49(repo, "shared", name);
6796
- if (statSync10(src).isDirectory()) {
6797
- const gk = join49(dst, ".gitkeep");
6798
- if (existsSync42(gk)) rmSync15(gk);
7038
+ const src = join53(claude, name);
7039
+ if (!existsSync45(src)) continue;
7040
+ const dst = join53(repo, "shared", name);
7041
+ if (statSync12(src).isDirectory()) {
7042
+ const gk = join53(dst, ".gitkeep");
7043
+ if (existsSync45(gk)) rmSync15(gk);
6799
7044
  cpSync7(src, dst, { recursive: true, force: false, errorOnExist: true });
6800
7045
  } else {
6801
7046
  copyFileSync2(src, dst);
6802
7047
  }
6803
7048
  log(`snapshotted shared/${name} from ${src}`);
6804
7049
  }
6805
- const userSettings = join49(claude, "settings.json");
6806
- if (existsSync42(userSettings)) {
7050
+ const userSettings = join53(claude, "settings.json");
7051
+ if (existsSync45(userSettings)) {
6807
7052
  let parsed;
6808
7053
  try {
6809
7054
  parsed = readJson(userSettings);
6810
7055
  } catch (err) {
6811
7056
  return die(`malformed ${userSettings}: ${err.message}`);
6812
7057
  }
6813
- const hostFile = join49(repo, "hosts", `${HOST}.json`);
7058
+ const hostFile = join53(repo, "hosts", `${HOST}.json`);
6814
7059
  writeJsonAtomic(hostFile, parsed);
6815
7060
  log(`snapshotted hosts/${HOST}.json from ${userSettings}`);
6816
7061
  }
@@ -6823,14 +7068,14 @@ var SHARED_CLAUDE_MD = "<!-- claude-nomad shared CLAUDE.md; symlinked into ~/.cl
6823
7068
  var SHARED_KEEP_DIRS = ["agents", "skills", "commands", "rules", "hooks"];
6824
7069
  function preflightConflict(repoHome2) {
6825
7070
  const candidates = [
6826
- join50(repoHome2, "shared", "settings.base.json"),
6827
- join50(repoHome2, "shared", "CLAUDE.md"),
6828
- join50(repoHome2, "path-map.json"),
6829
- join50(repoHome2, "hosts"),
6830
- join50(repoHome2, "shared")
7071
+ join54(repoHome2, "shared", "settings.base.json"),
7072
+ join54(repoHome2, "shared", "CLAUDE.md"),
7073
+ join54(repoHome2, "path-map.json"),
7074
+ join54(repoHome2, "hosts"),
7075
+ join54(repoHome2, "shared")
6831
7076
  ];
6832
7077
  for (const c of candidates) {
6833
- if (existsSync43(c)) return c;
7078
+ if (existsSync46(c)) return c;
6834
7079
  }
6835
7080
  return null;
6836
7081
  }
@@ -6842,31 +7087,31 @@ function cmdInit(opts = {}) {
6842
7087
  const keepActions = opts.keepActions === true;
6843
7088
  const repo = repoHome();
6844
7089
  const claude = claudeHome();
6845
- mkdirSync11(repo, { recursive: true });
7090
+ mkdirSync12(repo, { recursive: true });
6846
7091
  const conflict = preflightConflict(repo);
6847
7092
  if (conflict !== null) {
6848
7093
  die(`already initialized; refusing to clobber ${conflict}`);
6849
7094
  }
6850
7095
  ensureOriginRepo(opts.repoName ?? DEFAULT_REPO_NAME, opts.run);
6851
- mkdirSync11(join50(repo, "shared"), { recursive: true });
6852
- mkdirSync11(join50(repo, "hosts"), { recursive: true });
7096
+ mkdirSync12(join54(repo, "shared"), { recursive: true });
7097
+ mkdirSync12(join54(repo, "hosts"), { recursive: true });
6853
7098
  for (const name of SHARED_KEEP_DIRS) {
6854
- mkdirSync11(join50(repo, "shared", name), { recursive: true });
7099
+ mkdirSync12(join54(repo, "shared", name), { recursive: true });
6855
7100
  }
6856
- const userClaudeMd = join50(claude, "CLAUDE.md");
6857
- if (!snapshot || !existsSync43(userClaudeMd)) {
6858
- writeFileSync6(join50(repo, "shared", "CLAUDE.md"), SHARED_CLAUDE_MD);
7101
+ const userClaudeMd = join54(claude, "CLAUDE.md");
7102
+ if (!snapshot || !existsSync46(userClaudeMd)) {
7103
+ writeFileSync6(join54(repo, "shared", "CLAUDE.md"), SHARED_CLAUDE_MD);
6859
7104
  item("created shared/CLAUDE.md");
6860
7105
  }
6861
7106
  for (const name of SHARED_KEEP_DIRS) {
6862
- writeFileSync6(join50(repo, "shared", name, ".gitkeep"), "");
7107
+ writeFileSync6(join54(repo, "shared", name, ".gitkeep"), "");
6863
7108
  item(`created shared/${name}/.gitkeep`);
6864
7109
  }
6865
- writeFileSync6(join50(repo, "hosts", ".gitkeep"), "");
7110
+ writeFileSync6(join54(repo, "hosts", ".gitkeep"), "");
6866
7111
  item("created hosts/.gitkeep");
6867
- writeJsonAtomic(join50(repo, "shared", "settings.base.json"), {});
7112
+ writeJsonAtomic(join54(repo, "shared", "settings.base.json"), {});
6868
7113
  item("created shared/settings.base.json");
6869
- writeJsonAtomic(join50(repo, "path-map.json"), { projects: {} });
7114
+ writeJsonAtomic(join54(repo, "path-map.json"), { projects: {} });
6870
7115
  item("created path-map.json");
6871
7116
  if (snapshot) {
6872
7117
  snapshotIntoShared({ projects: {} });
@@ -6932,21 +7177,21 @@ function maybeDisableRepoActions(repoHome2, run) {
6932
7177
  // src/init.prompt.ts
6933
7178
  init_config();
6934
7179
  init_utils();
6935
- import { existsSync as existsSync44, readdirSync as readdirSync16, statSync as statSync11 } from "node:fs";
6936
- import { join as join51 } from "node:path";
7180
+ import { existsSync as existsSync47, readdirSync as readdirSync17, statSync as statSync13 } from "node:fs";
7181
+ import { join as join55 } from "node:path";
6937
7182
  import { createInterface as createInterface3 } from "node:readline/promises";
6938
7183
  function nonEmptyExists(path) {
6939
- if (!existsSync44(path)) return false;
7184
+ if (!existsSync47(path)) return false;
6940
7185
  try {
6941
- if (statSync11(path).isDirectory()) return readdirSync16(path).length > 0;
7186
+ if (statSync13(path).isDirectory()) return readdirSync17(path).length > 0;
6942
7187
  return true;
6943
7188
  } catch {
6944
7189
  return false;
6945
7190
  }
6946
7191
  }
6947
7192
  function hasExistingClaudeConfig(claudeHome2) {
6948
- if (existsSync44(join51(claudeHome2, "settings.json"))) return true;
6949
- return SHARED_LINKS.some((name) => nonEmptyExists(join51(claudeHome2, name)));
7193
+ if (existsSync47(join55(claudeHome2, "settings.json"))) return true;
7194
+ return SHARED_LINKS.some((name) => nonEmptyExists(join55(claudeHome2, name)));
6950
7195
  }
6951
7196
  async function confirmSnapshotDefault(claudeHome2) {
6952
7197
  if (process.stdin.isTTY !== true || process.stdout.isTTY !== true) {
@@ -7196,6 +7441,8 @@ function applyPushToken(argv, i, st) {
7196
7441
  return applyBool(st.allowAll, () => st.allowAll = true);
7197
7442
  case "--allow":
7198
7443
  return applyAllow2(argv, i, st);
7444
+ case "--full-scan":
7445
+ return applyBool(st.fullScan, () => st.fullScan = true);
7199
7446
  default:
7200
7447
  return REJECT;
7201
7448
  }
@@ -7205,7 +7452,8 @@ function parsePushArgs(argv) {
7205
7452
  dryRun: false,
7206
7453
  redactAll: false,
7207
7454
  allowAll: false,
7208
- allowRule: void 0
7455
+ allowRule: void 0,
7456
+ fullScan: false
7209
7457
  };
7210
7458
  let i = 3;
7211
7459
  while (i < argv.length) {
@@ -7222,14 +7470,15 @@ function parsePushArgs(argv) {
7222
7470
  dryRun: st.dryRun,
7223
7471
  redactAll: st.redactAll,
7224
7472
  allowAll: st.allowAll,
7225
- allowRule: st.allowRule
7473
+ allowRule: st.allowRule,
7474
+ fullScan: st.fullScan
7226
7475
  };
7227
7476
  }
7228
7477
 
7229
7478
  // package.json
7230
7479
  var package_default = {
7231
7480
  name: "claude-nomad",
7232
- version: "0.55.1",
7481
+ version: "0.56.0",
7233
7482
  type: "module",
7234
7483
  description: "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
7235
7484
  keywords: [
@@ -7337,6 +7586,10 @@ var DEFAULT_HELP = [
7337
7586
  row(" push", "Rebase, run safety checks (gitleaks, gitlinks, allow-list), commit, push."),
7338
7587
  row(" --dry-run", "Run pre-checks (rebase, gitleaks probe, gitlink scan) and preview"),
7339
7588
  cont("remap, without staging or pushing."),
7589
+ row(" --full-scan", "Ignore the per-host push manifest and rescan all transcripts."),
7590
+ cont("On a successful non-dry-run push, rewrite the manifest from that full"),
7591
+ cont("rescan. Use after a gitleaks upgrade or when in doubt. Composes freely"),
7592
+ cont("with --dry-run and all resolution modes."),
7340
7593
  row(" --redact-all", "Redact all findings non-interactively (backup, no prompt); no TTY"),
7341
7594
  cont("required. Does not auto-Allow. Mutually exclusive with --allow*."),
7342
7595
  cont("Cannot combine with --dry-run."),
@@ -7450,15 +7703,15 @@ var DEFAULT_HELP = [
7450
7703
  init_config();
7451
7704
  init_utils();
7452
7705
  init_utils_json();
7453
- import { existsSync as existsSync45, readFileSync as readFileSync14, readdirSync as readdirSync17 } from "node:fs";
7454
- import { join as join52 } from "node:path";
7706
+ import { existsSync as existsSync48, readFileSync as readFileSync15, readdirSync as readdirSync18 } from "node:fs";
7707
+ import { join as join56 } from "node:path";
7455
7708
  function resumeCmd(sessionId) {
7456
7709
  if (!/^[A-Za-z0-9_-]+$/.test(sessionId) || sessionId.length > 128) {
7457
7710
  fail(`invalid session id: ${sessionId}`);
7458
7711
  process.exit(1);
7459
7712
  }
7460
- const projectsRoot = join52(claudeHome(), "projects");
7461
- if (!existsSync45(projectsRoot)) {
7713
+ const projectsRoot = join56(claudeHome(), "projects");
7714
+ if (!existsSync48(projectsRoot)) {
7462
7715
  fail(`${projectsRoot} does not exist`);
7463
7716
  process.exit(1);
7464
7717
  }
@@ -7472,8 +7725,8 @@ function resumeCmd(sessionId) {
7472
7725
  fail(`no cwd field found in ${jsonlPath}`);
7473
7726
  process.exit(1);
7474
7727
  }
7475
- const mapPath = join52(repoHome(), "path-map.json");
7476
- if (!existsSync45(mapPath)) {
7728
+ const mapPath = join56(repoHome(), "path-map.json");
7729
+ if (!existsSync48(mapPath)) {
7477
7730
  fail("path-map.json missing");
7478
7731
  process.exit(1);
7479
7732
  }
@@ -7495,14 +7748,14 @@ function resumeCmd(sessionId) {
7495
7748
  console.log(`cd ${shQuote(hit.localPath)} && claude --resume ${shQuote(sessionId)}`);
7496
7749
  }
7497
7750
  function findTranscriptPath(projectsRoot, sessionId) {
7498
- for (const dir of readdirSync17(projectsRoot)) {
7499
- const candidate = join52(projectsRoot, dir, `${sessionId}.jsonl`);
7500
- if (existsSync45(candidate)) return candidate;
7751
+ for (const dir of readdirSync18(projectsRoot)) {
7752
+ const candidate = join56(projectsRoot, dir, `${sessionId}.jsonl`);
7753
+ if (existsSync48(candidate)) return candidate;
7501
7754
  }
7502
7755
  return null;
7503
7756
  }
7504
7757
  function extractRecordedCwd(jsonlPath) {
7505
- for (const line of readFileSync14(jsonlPath, "utf8").split("\n")) {
7758
+ for (const line of readFileSync15(jsonlPath, "utf8").split("\n")) {
7506
7759
  if (!line.trim()) continue;
7507
7760
  try {
7508
7761
  const obj = JSON.parse(line);
@@ -7562,7 +7815,7 @@ try {
7562
7815
  const pushArgs = parsePushArgs(process.argv);
7563
7816
  if (pushArgs === null) {
7564
7817
  console.error(
7565
- "usage: nomad push [--dry-run] [--redact-all] [--allow <rule>] [--allow-all]"
7818
+ "usage: nomad push [--dry-run] [--full-scan] [--redact-all] [--allow <rule>] [--allow-all]"
7566
7819
  );
7567
7820
  process.exit(1);
7568
7821
  }
@@ -7570,7 +7823,8 @@ try {
7570
7823
  dryRun: pushArgs.dryRun,
7571
7824
  redactAll: pushArgs.redactAll,
7572
7825
  allowAll: pushArgs.allowAll,
7573
- allowRule: pushArgs.allowRule
7826
+ allowRule: pushArgs.allowRule,
7827
+ fullScan: pushArgs.fullScan
7574
7828
  });
7575
7829
  break;
7576
7830
  }