claude-nomad 0.44.1 → 0.45.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/.gitleaks.toml CHANGED
@@ -39,6 +39,27 @@ paths = [
39
39
  ]
40
40
  condition = "AND"
41
41
 
42
+ # Path-scoped: SSH public-key fingerprints in git signature-verification
43
+ # output (`git log --show-signature`, `git verify-commit`) land in session
44
+ # transcripts as `Good "git" signature for <signer> with ED25519 key
45
+ # SHA256:<43-char base64>`. The fingerprint is a hash of a PUBLIC key, not a
46
+ # credential, but its 43-char base64 body trips generic-api-key. Anchoring on
47
+ # the surrounding `with <keytype> key SHA256:` structure (via
48
+ # regexTarget = "line") keeps this from whitelisting a bare token: a real
49
+ # secret is never preceded by an SSH key-type label and the SHA256: scheme
50
+ # prefix. `condition = "AND"` plus the session-jsonl path scope double-locks
51
+ # it to synced transcripts.
52
+ [[allowlists]]
53
+ description = "claude-nomad: SSH public-key fingerprints (with <keytype> key SHA256:<b64>) in git signature output in synced session transcripts"
54
+ regexTarget = "line"
55
+ regexes = [
56
+ '''with (?:ED25519|ED25519-SK|ECDSA|ECDSA-SK|RSA|DSA) key SHA256:[A-Za-z0-9+/]{43}''',
57
+ ]
58
+ paths = [
59
+ '''^shared/projects/[^/]+/.*\.jsonl$''',
60
+ ]
61
+ condition = "AND"
62
+
42
63
  # Path-scoped: SonarCloud issue-listing tool output (`gh`/sonar API dumps of
43
64
  # the form `key: <20-char id>` immediately followed by `rule: <lang>:S<n>`)
44
65
  # lands in session transcripts during PR reviews. The issue key is an opaque
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.45.0](https://github.com/funkadelic/claude-nomad/compare/v0.44.1...v0.45.0) (2026-06-07)
4
+
5
+
6
+ ### Added
7
+
8
+ * **doctor:** warn when settings.json drifts from the base+host merge ([#259](https://github.com/funkadelic/claude-nomad/issues/259)) ([6401732](https://github.com/funkadelic/claude-nomad/commit/6401732199aa9f883de78b15b323ec4dfecf8d3f))
9
+
10
+
11
+ ### Changed
12
+
13
+ * **gitleaks:** allowlist SSH key fingerprints in signature output ([#257](https://github.com/funkadelic/claude-nomad/issues/257)) ([ca310aa](https://github.com/funkadelic/claude-nomad/commit/ca310aa7f3a23b152ed149224400d60332ada037))
14
+
15
+
16
+ ### Testing
17
+
18
+ * kill mutation survivors from the full Stryker sweep ([#260](https://github.com/funkadelic/claude-nomad/issues/260)) ([314e315](https://github.com/funkadelic/claude-nomad/commit/314e31587cb97e0fdf5b410587bb0ca64a2e46d5))
19
+
3
20
  ## [0.44.1](https://github.com/funkadelic/claude-nomad/compare/v0.44.0...v0.44.1) (2026-06-06)
4
21
 
5
22
 
package/README.md CHANGED
@@ -37,7 +37,9 @@ survives different file paths and your secrets never ride along.
37
37
  `--dry-run` on pull and push prints the plan without writing anything.
38
38
  - **One command tells you what is wrong.** `nomad doctor` is a read-only health check: wedged sync
39
39
  repo, broken hook references, hooks that would crash on session start because of a missing
40
- `--preserve-symlinks-main` flag, version drift, oversized backup cache, each with a fix hint.
40
+ `--preserve-symlinks-main` flag, version drift, oversized backup cache, and settings drift (warns
41
+ when `~/.claude/settings.json` no longer matches the base+host merge nomad would write, the
42
+ silent-clobber case, with `nomad pull` as the fix), each with a fix hint.
41
43
  - **Self-healing sync.** Every overwrite is backed up first, and `nomad pull --force-remote`
42
44
  recovers a sync repo stuck mid-rebase while parking your stranded work on a branch, refusing
43
45
  entirely if shared config is at risk.
package/dist/nomad.mjs CHANGED
@@ -1400,8 +1400,8 @@ function cmdEject(opts = {}, roots = defaultEjectRoots()) {
1400
1400
  }
1401
1401
 
1402
1402
  // src/commands.doctor.ts
1403
- import { existsSync as existsSync22 } from "node:fs";
1404
- import { join as join26 } from "node:path";
1403
+ import { existsSync as existsSync23 } from "node:fs";
1404
+ import { join as join27 } from "node:path";
1405
1405
 
1406
1406
  // src/commands.doctor.checks.repo.ts
1407
1407
  init_color();
@@ -2686,17 +2686,150 @@ function reportPreserveSymlinksCheck(section2) {
2686
2686
  }
2687
2687
  }
2688
2688
 
2689
+ // src/commands.doctor.checks.settings-drift.ts
2690
+ init_color();
2691
+ import { existsSync as existsSync21, readFileSync as readFileSync7 } from "node:fs";
2692
+ import { join as join25 } from "node:path";
2693
+ init_config();
2694
+ init_utils_json();
2695
+ function arraysEqual(a, b) {
2696
+ if (a.length !== b.length) return false;
2697
+ for (let i = 0; i < a.length; i++) {
2698
+ if (!deepEqual(a[i], b[i])) return false;
2699
+ }
2700
+ return true;
2701
+ }
2702
+ function objectsEqual(a, b) {
2703
+ const aKeys = Object.keys(a);
2704
+ const bKeys = Object.keys(b);
2705
+ if (aKeys.length !== bKeys.length) return false;
2706
+ for (const k of aKeys) {
2707
+ if (!Object.hasOwn(b, k)) return false;
2708
+ if (!deepEqual(a[k], b[k])) return false;
2709
+ }
2710
+ return true;
2711
+ }
2712
+ function deepEqual(a, b) {
2713
+ if (a === b) return true;
2714
+ if (a === null || b === null) return false;
2715
+ if (Array.isArray(a) && Array.isArray(b)) return arraysEqual(a, b);
2716
+ if (Array.isArray(a) || Array.isArray(b)) return false;
2717
+ if (typeof a === "object" && typeof b === "object") {
2718
+ return objectsEqual(a, b);
2719
+ }
2720
+ return false;
2721
+ }
2722
+ function diffMergedSettings(merged, settings) {
2723
+ const missing = [];
2724
+ const changed = [];
2725
+ const extra = [];
2726
+ const settingsKeys = new Set(Object.keys(settings));
2727
+ for (const key of Object.keys(merged)) {
2728
+ if (!settingsKeys.has(key)) {
2729
+ missing.push(key);
2730
+ } else if (!deepEqual(merged[key], settings[key])) {
2731
+ changed.push(key);
2732
+ }
2733
+ }
2734
+ const mergedKeys = new Set(Object.keys(merged));
2735
+ for (const key of Object.keys(settings)) {
2736
+ if (!mergedKeys.has(key)) extra.push(key);
2737
+ }
2738
+ const collator = (a, b) => a.localeCompare(b, "en");
2739
+ return {
2740
+ missing: missing.toSorted(collator),
2741
+ changed: changed.toSorted(collator),
2742
+ extra: extra.toSorted(collator)
2743
+ };
2744
+ }
2745
+ function tryReadJson(filePath) {
2746
+ try {
2747
+ const raw = readFileSync7(filePath, "utf8");
2748
+ const parsed = JSON.parse(raw);
2749
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
2750
+ return parsed;
2751
+ } catch {
2752
+ return null;
2753
+ }
2754
+ }
2755
+ function reportSettingsDriftCheck(section2) {
2756
+ const claude = claudeHome();
2757
+ const repo = repoHome();
2758
+ const host = HOST;
2759
+ const settingsPath = join25(claude, "settings.json");
2760
+ const basePath = join25(repo, "shared", "settings.base.json");
2761
+ const hostPath = join25(repo, "hosts", `${host}.json`);
2762
+ if (!existsSync21(settingsPath)) {
2763
+ addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json; skipping merge-drift check`);
2764
+ return;
2765
+ }
2766
+ if (!existsSync21(basePath)) {
2767
+ addItem(
2768
+ section2,
2769
+ `${dim(infoGlyph)} shared/settings.base.json missing; skipping merge-drift check`
2770
+ );
2771
+ return;
2772
+ }
2773
+ const base = tryReadJson(basePath);
2774
+ if (base === null) {
2775
+ addItem(
2776
+ section2,
2777
+ `${dim(infoGlyph)} shared/settings.base.json unparseable; skipping merge-drift check`
2778
+ );
2779
+ return;
2780
+ }
2781
+ const settings = tryReadJson(settingsPath);
2782
+ if (settings === null) {
2783
+ return;
2784
+ }
2785
+ const hostExists = existsSync21(hostPath);
2786
+ const hostObj = hostExists ? tryReadJson(hostPath) : null;
2787
+ if (hostExists && hostObj === null) {
2788
+ addItem(
2789
+ section2,
2790
+ `${yellow(warnGlyph)} hosts/${host}.json unparseable; 'nomad pull' will fail (fix the host file)`
2791
+ );
2792
+ return;
2793
+ }
2794
+ const merged = deepMerge(base, hostObj ?? {});
2795
+ const { missing, changed, extra } = diffMergedSettings(merged, settings);
2796
+ emitDriftRows(section2, missing, changed, extra, host, hostExists);
2797
+ }
2798
+ function emitDriftRows(section2, missing, changed, extra, host, hostFileExists) {
2799
+ if (missing.length > 0) {
2800
+ addItem(
2801
+ section2,
2802
+ `${yellow(warnGlyph)} settings.json drift: merged keys missing locally: ${missing.join(", ")} (external writer clobbered settings.json; run 'nomad pull')`
2803
+ );
2804
+ }
2805
+ if (changed.length > 0) {
2806
+ addItem(
2807
+ section2,
2808
+ `${yellow(warnGlyph)} settings.json drift: merged keys with changed values: ${changed.join(", ")} (run 'nomad pull')`
2809
+ );
2810
+ }
2811
+ if (extra.length > 0 && hostFileExists) {
2812
+ addItem(
2813
+ section2,
2814
+ `${dim(infoGlyph)} settings.json has ${extra.length} local-only key(s) not in base+host merge: ${extra.join(", ")} (promotion candidates for shared/settings.base.json or hosts/${host}.json)`
2815
+ );
2816
+ }
2817
+ if (missing.length === 0 && changed.length === 0 && extra.length === 0) {
2818
+ addItem(section2, `${green(okGlyph)} settings.json matches base+host merge`);
2819
+ }
2820
+ }
2821
+
2689
2822
  // src/commands.doctor.ts
2690
2823
  init_config();
2691
2824
 
2692
2825
  // src/commands.doctor.engine.ts
2693
2826
  init_color();
2694
- import { readFileSync as readFileSync8 } from "node:fs";
2827
+ import { readFileSync as readFileSync9 } from "node:fs";
2695
2828
  import { fileURLToPath as fileURLToPath3 } from "node:url";
2696
2829
 
2697
2830
  // src/commands.doctor.version.ts
2698
2831
  init_color();
2699
- import { readFileSync as readFileSync7 } from "node:fs";
2832
+ import { readFileSync as readFileSync8 } from "node:fs";
2700
2833
  import { fileURLToPath as fileURLToPath2 } from "node:url";
2701
2834
  init_config();
2702
2835
  var STRICT_SEMVER = /^\d+\.\d+\.\d+$/;
@@ -2713,7 +2846,7 @@ function compareSemver(a, b) {
2713
2846
  function readLocalVersion() {
2714
2847
  try {
2715
2848
  const pkgPath = fileURLToPath2(new URL("../package.json", import.meta.url));
2716
- const parsed = JSON.parse(readFileSync7(pkgPath, "utf8"));
2849
+ const parsed = JSON.parse(readFileSync8(pkgPath, "utf8"));
2717
2850
  if (typeof parsed.version === "string" && parsed.version.length > 0) {
2718
2851
  return parsed.version;
2719
2852
  }
@@ -2772,7 +2905,7 @@ function parseMinVersion(spec) {
2772
2905
  function readEnginesNode() {
2773
2906
  try {
2774
2907
  const pkgPath = fileURLToPath3(new URL("../package.json", import.meta.url));
2775
- const parsed = JSON.parse(readFileSync8(pkgPath, "utf8"));
2908
+ const parsed = JSON.parse(readFileSync9(pkgPath, "utf8"));
2776
2909
  const node = parsed.engines?.node;
2777
2910
  if (typeof node === "string" && node.length > 0) return node;
2778
2911
  return null;
@@ -2801,8 +2934,8 @@ function reportNodeEngineCheck(section2) {
2801
2934
  // src/commands.doctor.gitleaks-version.ts
2802
2935
  init_color();
2803
2936
  import { execFileSync as execFileSync7 } from "node:child_process";
2804
- import { existsSync as existsSync21 } from "node:fs";
2805
- import { join as join25 } from "node:path";
2937
+ import { existsSync as existsSync22 } from "node:fs";
2938
+ import { join as join26 } from "node:path";
2806
2939
  init_config();
2807
2940
  var SEMVER_MAJOR_MINOR = /^(\d+)\.(\d+)\.\d+$/;
2808
2941
  var GITLEAKS_TIMEOUT_MS = 5e3;
@@ -2811,7 +2944,7 @@ function majorMinorOf(value) {
2811
2944
  return m === null ? null : [m[1], m[2]];
2812
2945
  }
2813
2946
  function readGitleaksVersion(run, tomlExists) {
2814
- const tomlPath = join25(repoHome(), ".gitleaks.toml");
2947
+ const tomlPath = join26(repoHome(), ".gitleaks.toml");
2815
2948
  const args = ["version"];
2816
2949
  if (tomlExists(tomlPath)) args.push("--config", tomlPath);
2817
2950
  try {
@@ -2823,7 +2956,7 @@ function readGitleaksVersion(run, tomlExists) {
2823
2956
  return null;
2824
2957
  }
2825
2958
  }
2826
- function reportGitleaksVersionCheck(section2, run = execFileSync7, tomlExists = existsSync21) {
2959
+ function reportGitleaksVersionCheck(section2, run = execFileSync7, tomlExists = existsSync22) {
2827
2960
  const raw = readGitleaksVersion(run, tomlExists);
2828
2961
  if (raw === null) return;
2829
2962
  const local = majorMinorOf(raw);
@@ -3023,8 +3156,8 @@ function cmdDoctor(opts = {}) {
3023
3156
  reportHostAndPaths(host);
3024
3157
  reportRepoState(host);
3025
3158
  const links = section("Shared links");
3026
- const mapPath = join26(repoHome(), "path-map.json");
3027
- const rawMap = existsSync22(mapPath) ? readJsonSafe(mapPath, mapPath, links) : null;
3159
+ const mapPath = join27(repoHome(), "path-map.json");
3160
+ const rawMap = existsSync23(mapPath) ? readJsonSafe(mapPath, mapPath, links) : null;
3028
3161
  const map = rawMap ?? { projects: {} };
3029
3162
  reportSharedLinks(links, map);
3030
3163
  const hooksScan = section("Hook targets");
@@ -3035,6 +3168,7 @@ function cmdDoctor(opts = {}) {
3035
3168
  const base = loadBaseSettings(settings);
3036
3169
  const parsedSettings = loadAndReportSettings(settings);
3037
3170
  reportHostOverrides(settings, base, parsedSettings);
3171
+ reportSettingsDriftCheck(settings);
3038
3172
  const pathMap = section("Path map");
3039
3173
  reportPathMap(pathMap);
3040
3174
  const neverSync = section("Never-sync");
@@ -3078,8 +3212,8 @@ function cmdDoctor(opts = {}) {
3078
3212
  // src/commands.drop-session.ts
3079
3213
  init_config();
3080
3214
  import { execFileSync as execFileSync12 } from "node:child_process";
3081
- import { existsSync as existsSync24, readdirSync as readdirSync9, statSync as statSync5 } from "node:fs";
3082
- import { join as join29, relative as relative4 } from "node:path";
3215
+ import { existsSync as existsSync25, readdirSync as readdirSync9, statSync as statSync5 } from "node:fs";
3216
+ import { join as join30, relative as relative4 } from "node:path";
3083
3217
 
3084
3218
  // src/commands.drop-session.git.ts
3085
3219
  import { execFileSync as execFileSync11 } from "node:child_process";
@@ -3121,8 +3255,8 @@ function isInIndex(rel, repo) {
3121
3255
  init_config();
3122
3256
  init_utils();
3123
3257
  init_utils_json();
3124
- import { existsSync as existsSync23 } from "node:fs";
3125
- import { join as join27 } from "node:path";
3258
+ import { existsSync as existsSync24 } from "node:fs";
3259
+ import { join as join28 } from "node:path";
3126
3260
  var SHARED_PROJECT_LOGICAL = /^shared\/projects\/([^/]+)\//;
3127
3261
  function reportScrubHint(id, matches) {
3128
3262
  const live = resolveLiveTranscript(id, matches);
@@ -3138,8 +3272,8 @@ function reportScrubHint(id, matches) {
3138
3272
  }
3139
3273
  function resolveLiveTranscript(id, matches) {
3140
3274
  try {
3141
- const mapPath = join27(repoHome(), "path-map.json");
3142
- if (!existsSync23(mapPath)) return null;
3275
+ const mapPath = join28(repoHome(), "path-map.json");
3276
+ if (!existsSync24(mapPath)) return null;
3143
3277
  const projects = readJson(mapPath).projects;
3144
3278
  const claude = claudeHome();
3145
3279
  for (const rel of matches) {
@@ -3147,8 +3281,8 @@ function resolveLiveTranscript(id, matches) {
3147
3281
  if (logical === void 0) continue;
3148
3282
  const abs = projects[logical]?.[HOST];
3149
3283
  if (abs === void 0) continue;
3150
- const live = join27(claude, "projects", encodePath(abs), `${id}.jsonl`);
3151
- if (existsSync23(live)) return live;
3284
+ const live = join28(claude, "projects", encodePath(abs), `${id}.jsonl`);
3285
+ if (existsSync24(live)) return live;
3152
3286
  }
3153
3287
  return null;
3154
3288
  } catch {
@@ -3162,10 +3296,10 @@ init_utils();
3162
3296
  // src/utils.lockfile.ts
3163
3297
  init_config();
3164
3298
  init_utils();
3165
- import { closeSync as closeSync3, mkdirSync as mkdirSync5, openSync as openSync3, readFileSync as readFileSync9, unlinkSync, writeFileSync as writeFileSync3 } from "node:fs";
3166
- import { dirname as dirname4, join as join28 } from "node:path";
3299
+ import { closeSync as closeSync3, mkdirSync as mkdirSync5, openSync as openSync3, readFileSync as readFileSync10, unlinkSync, writeFileSync as writeFileSync3 } from "node:fs";
3300
+ import { dirname as dirname4, join as join29 } from "node:path";
3167
3301
  function lockFilePath() {
3168
- return join28(home(), ".cache", "claude-nomad", "nomad.lock");
3302
+ return join29(home(), ".cache", "claude-nomad", "nomad.lock");
3169
3303
  }
3170
3304
  function acquireLock(verb) {
3171
3305
  const lp = lockFilePath();
@@ -3208,7 +3342,7 @@ function releaseLock(handle) {
3208
3342
  function unlinkIfSamePid(expectedPidStr, lp) {
3209
3343
  let current;
3210
3344
  try {
3211
- current = readFileSync9(lp, "utf8").trim();
3345
+ current = readFileSync10(lp, "utf8").trim();
3212
3346
  } catch {
3213
3347
  return false;
3214
3348
  }
@@ -3223,7 +3357,7 @@ function unlinkIfSamePid(expectedPidStr, lp) {
3223
3357
  function checkStaleAndRetry(verb, lp) {
3224
3358
  let pidStr;
3225
3359
  try {
3226
- pidStr = readFileSync9(lp, "utf8").trim();
3360
+ pidStr = readFileSync10(lp, "utf8").trim();
3227
3361
  } catch {
3228
3362
  pidStr = "";
3229
3363
  }
@@ -3279,12 +3413,12 @@ function cmdDropSession(id) {
3279
3413
  process.exit(1);
3280
3414
  }
3281
3415
  const repo = repoHome();
3282
- if (!existsSync24(repo)) die(`repo not cloned at ${repo}`);
3416
+ if (!existsSync25(repo)) die(`repo not cloned at ${repo}`);
3283
3417
  const handle = acquireLock("drop-session");
3284
3418
  if (handle === null) process.exit(0);
3285
3419
  try {
3286
- const repoProjects = join29(repo, "shared", "projects");
3287
- if (!existsSync24(repoProjects)) {
3420
+ const repoProjects = join30(repo, "shared", "projects");
3421
+ if (!existsSync25(repoProjects)) {
3288
3422
  throw new NomadFatal(`no staged session matches ${id}`);
3289
3423
  }
3290
3424
  const matches = collectMatches(repoProjects, id, repo);
@@ -3306,12 +3440,12 @@ function cmdDropSession(id) {
3306
3440
  function collectMatches(repoProjects, id, repo) {
3307
3441
  const matches = [];
3308
3442
  for (const logical of readdirSync9(repoProjects)) {
3309
- const candidate = join29(repoProjects, logical, `${id}.jsonl`);
3310
- if (existsSync24(candidate)) {
3443
+ const candidate = join30(repoProjects, logical, `${id}.jsonl`);
3444
+ if (existsSync25(candidate)) {
3311
3445
  matches.push(relative4(repo, candidate));
3312
3446
  }
3313
- const dir = join29(repoProjects, logical, id);
3314
- if (existsSync24(dir) && statSync5(dir).isDirectory()) {
3447
+ const dir = join30(repoProjects, logical, id);
3448
+ if (existsSync25(dir) && statSync5(dir).isDirectory()) {
3315
3449
  const dirRel = relative4(repo, dir);
3316
3450
  const staged = expandStagedDir(dirRel, repo);
3317
3451
  if (staged.length > 0) matches.push(...staged);
@@ -3347,19 +3481,19 @@ function unstageOne(rel, repo) {
3347
3481
 
3348
3482
  // src/commands.redact.ts
3349
3483
  init_config();
3350
- import { existsSync as existsSync26, statSync as statSync7 } from "node:fs";
3351
- import { dirname as dirname5, join as join31 } from "node:path";
3484
+ import { existsSync as existsSync27, statSync as statSync7 } from "node:fs";
3485
+ import { dirname as dirname5, join as join32 } from "node:path";
3352
3486
 
3353
3487
  // src/commands.redact.subtree.ts
3354
- import { existsSync as existsSync25, lstatSync as lstatSync7, readFileSync as readFileSync10, readdirSync as readdirSync10, statSync as statSync6, writeFileSync as writeFileSync4 } from "node:fs";
3355
- import { join as join30 } from "node:path";
3488
+ import { existsSync as existsSync26, lstatSync as lstatSync7, readFileSync as readFileSync11, readdirSync as readdirSync10, statSync as statSync6, writeFileSync as writeFileSync4 } from "node:fs";
3489
+ import { join as join31 } from "node:path";
3356
3490
  init_utils_fs();
3357
3491
  function collectFiles(dir, out) {
3358
- if (!existsSync25(dir)) return;
3492
+ if (!existsSync26(dir)) return;
3359
3493
  const st = lstatSync7(dir);
3360
3494
  if (!st.isDirectory()) return;
3361
3495
  for (const entry of readdirSync10(dir)) {
3362
- const abs = join30(dir, entry);
3496
+ const abs = join31(dir, entry);
3363
3497
  const lst = lstatSync7(abs);
3364
3498
  if (lst.isSymbolicLink()) continue;
3365
3499
  if (lst.isDirectory()) {
@@ -3396,7 +3530,7 @@ function applySubtreeRedactions(mainPath, mainFindings, subtreeFiles, rule, ts,
3396
3530
  if (!dryRun && total > 0) {
3397
3531
  for (const { path: filePath, findings } of dirty) {
3398
3532
  backupBeforeWrite(filePath, ts);
3399
- writeFileSync4(filePath, applyRedactions(readFileSync10(filePath, "utf8"), findings), "utf8");
3533
+ writeFileSync4(filePath, applyRedactions(readFileSync11(filePath, "utf8"), findings), "utf8");
3400
3534
  }
3401
3535
  }
3402
3536
  return { total, dirty };
@@ -3409,15 +3543,15 @@ init_utils_json();
3409
3543
  init_utils();
3410
3544
  function resolveLiveTranscript2(id) {
3411
3545
  try {
3412
- const mapPath = join31(repoHome(), "path-map.json");
3413
- if (!existsSync26(mapPath)) return null;
3546
+ const mapPath = join32(repoHome(), "path-map.json");
3547
+ if (!existsSync27(mapPath)) return null;
3414
3548
  const projects = readJson(mapPath).projects;
3415
3549
  const claude = claudeHome();
3416
3550
  for (const hostMap of Object.values(projects)) {
3417
3551
  const abs = hostMap[HOST];
3418
3552
  if (abs === void 0) continue;
3419
- const live = join31(claude, "projects", encodePath(abs), `${id}.jsonl`);
3420
- if (existsSync26(live)) return live;
3553
+ const live = join32(claude, "projects", encodePath(abs), `${id}.jsonl`);
3554
+ if (existsSync27(live)) return live;
3421
3555
  }
3422
3556
  return null;
3423
3557
  } catch {
@@ -3437,17 +3571,17 @@ function cmdRedact(opts, nowMs = Date.now, scan = scanFile) {
3437
3571
  }
3438
3572
  const repo = repoHome();
3439
3573
  const backup = backupBase();
3440
- if (!existsSync26(repo)) die(`repo not cloned at ${repo}`);
3574
+ if (!existsSync27(repo)) die(`repo not cloned at ${repo}`);
3441
3575
  const handle = acquireLock("redact");
3442
3576
  if (handle === null) process.exit(0);
3443
3577
  try {
3444
3578
  const localPath = resolveLiveTranscript2(id);
3445
- if (localPath === null || !existsSync26(localPath)) {
3579
+ if (localPath === null || !existsSync27(localPath)) {
3446
3580
  fail(`could not resolve local transcript for session ${id} on this host`);
3447
3581
  process.exitCode = 1;
3448
3582
  return;
3449
3583
  }
3450
- const sessionDir = join31(dirname5(localPath), id);
3584
+ const sessionDir = join32(dirname5(localPath), id);
3451
3585
  const subtreeFiles = listSubtreeFiles(sessionDir);
3452
3586
  const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync7(p).mtimeMs);
3453
3587
  if (isRecentlyModified(subtreeMtime, nowMs())) {
@@ -3500,8 +3634,8 @@ ${lines}`);
3500
3634
  }
3501
3635
 
3502
3636
  // src/commands.pull.ts
3503
- import { existsSync as existsSync34, mkdirSync as mkdirSync8 } from "node:fs";
3504
- import { join as join40 } from "node:path";
3637
+ import { existsSync as existsSync35, mkdirSync as mkdirSync8 } from "node:fs";
3638
+ import { join as join41 } from "node:path";
3505
3639
 
3506
3640
  // src/commands.push.sections.ts
3507
3641
  init_color();
@@ -3589,8 +3723,8 @@ init_config();
3589
3723
 
3590
3724
  // src/extras-sync.ts
3591
3725
  init_config();
3592
- import { existsSync as existsSync29 } from "node:fs";
3593
- import { join as join34 } from "node:path";
3726
+ import { existsSync as existsSync30 } from "node:fs";
3727
+ import { join as join35 } from "node:path";
3594
3728
 
3595
3729
  // src/extras-sync.diff.ts
3596
3730
  init_utils();
@@ -3629,8 +3763,8 @@ function listDivergingFiles(a, b) {
3629
3763
 
3630
3764
  // src/extras-sync.core.ts
3631
3765
  init_config();
3632
- import { cpSync as cpSync5, existsSync as existsSync27, rmSync as rmSync8 } from "node:fs";
3633
- import { join as join32 } from "node:path";
3766
+ import { cpSync as cpSync5, existsSync as existsSync28, rmSync as rmSync8 } from "node:fs";
3767
+ import { join as join33 } from "node:path";
3634
3768
 
3635
3769
  // src/extras-sync.guards.ts
3636
3770
  init_utils();
@@ -3654,9 +3788,9 @@ init_utils();
3654
3788
  init_utils_json();
3655
3789
  function loadValidatedExtras(opts) {
3656
3790
  const repo = repoHome();
3657
- const mapPath = join32(repo, "path-map.json");
3658
- const repoExtras = join32(repo, "shared", "extras");
3659
- if (!existsSync27(mapPath) || opts.requireRepoExtras === true && !existsSync27(repoExtras)) {
3791
+ const mapPath = join33(repo, "path-map.json");
3792
+ const repoExtras = join33(repo, "shared", "extras");
3793
+ if (!existsSync28(mapPath) || opts.requireRepoExtras === true && !existsSync28(repoExtras)) {
3660
3794
  if (opts.missingMsg !== void 0) log(opts.missingMsg);
3661
3795
  return null;
3662
3796
  }
@@ -3698,8 +3832,8 @@ init_utils_json();
3698
3832
 
3699
3833
  // src/extras-sync.remap.ts
3700
3834
  init_config();
3701
- import { existsSync as existsSync28, mkdirSync as mkdirSync6 } from "node:fs";
3702
- import { join as join33 } from "node:path";
3835
+ import { existsSync as existsSync29, mkdirSync as mkdirSync6 } from "node:fs";
3836
+ import { join as join34 } from "node:path";
3703
3837
  init_utils_fs();
3704
3838
  function runExtrasOp(v, dryRun, paths, backup) {
3705
3839
  const counts = { unmapped: 0, skipped: 0 };
@@ -3707,7 +3841,7 @@ function runExtrasOp(v, dryRun, paths, backup) {
3707
3841
  const would = [];
3708
3842
  for (const t of eachExtrasTarget(v, counts)) {
3709
3843
  const { src, dst } = paths(t);
3710
- if (!existsSync28(src)) continue;
3844
+ if (!existsSync29(src)) continue;
3711
3845
  const item2 = `${t.logical}/${t.dirname}`;
3712
3846
  if (dryRun) {
3713
3847
  would.push(item2);
@@ -3724,14 +3858,14 @@ function remapExtrasPush(ts, opts = {}) {
3724
3858
  const v = loadValidatedExtras({ missingMsg: "no path-map.json; skipping extras push" });
3725
3859
  if (v === null) return { unmapped: 0, skipped: 0, pushed: [], wouldPush: [] };
3726
3860
  const repo = repoHome();
3727
- const repoExtras = join33(repo, "shared", "extras");
3861
+ const repoExtras = join34(repo, "shared", "extras");
3728
3862
  if (!dryRun) mkdirSync6(repoExtras, { recursive: true });
3729
3863
  const { unmapped, skipped, done, would } = runExtrasOp(
3730
3864
  v,
3731
3865
  dryRun,
3732
3866
  ({ localRoot, logical, dirname: dirname7 }) => ({
3733
- src: join33(localRoot, dirname7),
3734
- dst: join33(repoExtras, logical, dirname7)
3867
+ src: join34(localRoot, dirname7),
3868
+ dst: join34(repoExtras, logical, dirname7)
3735
3869
  }),
3736
3870
  (dst) => backupRepoWrite(dst, ts, repo)
3737
3871
  );
@@ -3744,13 +3878,13 @@ function remapExtrasPull(ts, opts = {}) {
3744
3878
  missingMsg: "no path-map or repo extras dir; skipping extras remap"
3745
3879
  });
3746
3880
  if (v === null) return { unmapped: 0, skipped: 0, pulled: [], wouldPull: [] };
3747
- const repoExtras = join33(repoHome(), "shared", "extras");
3881
+ const repoExtras = join34(repoHome(), "shared", "extras");
3748
3882
  const { unmapped, skipped, done, would } = runExtrasOp(
3749
3883
  v,
3750
3884
  dryRun,
3751
3885
  ({ localRoot, logical, dirname: dirname7 }) => ({
3752
- src: join33(repoExtras, logical, dirname7),
3753
- dst: join33(localRoot, dirname7)
3886
+ src: join34(repoExtras, logical, dirname7),
3887
+ dst: join34(localRoot, dirname7)
3754
3888
  }),
3755
3889
  // Snapshot the host-side dst BEFORE copyExtras clobbers it. Anchor on
3756
3890
  // localRoot so the backup tree mirrors the project layout.
@@ -3764,15 +3898,15 @@ function divergenceCheckExtras(ts) {
3764
3898
  const v = loadValidatedExtras({});
3765
3899
  if (v === null) return;
3766
3900
  const counts = { unmapped: 0, skipped: 0 };
3767
- const backupRoot = join34(backupBase(), ts, "extras");
3901
+ const backupRoot = join35(backupBase(), ts, "extras");
3768
3902
  const repo = repoHome();
3769
3903
  for (const { logical, localRoot, dirname: dirname7 } of eachExtrasTarget(v, counts)) {
3770
- const local = join34(localRoot, dirname7);
3771
- const repoEntry = join34(repo, "shared", "extras", logical, dirname7);
3772
- if (!existsSync29(local) || !existsSync29(repoEntry)) continue;
3904
+ const local = join35(localRoot, dirname7);
3905
+ const repoEntry = join35(repo, "shared", "extras", logical, dirname7);
3906
+ if (!existsSync30(local) || !existsSync30(repoEntry)) continue;
3773
3907
  const diff = listDivergingFiles(local, repoEntry);
3774
3908
  if (diff.length === 0) continue;
3775
- const projectBackupRoot = join34(backupRoot, encodePath(localRoot));
3909
+ const projectBackupRoot = join35(backupRoot, encodePath(localRoot));
3776
3910
  warn(
3777
3911
  `local ${dirname7} for ${logical} diverges from origin in ${diff.length} file(s); next remapExtrasPull will overwrite them (backups at ${projectBackupRoot}/)`
3778
3912
  );
@@ -3785,8 +3919,8 @@ init_config();
3785
3919
  init_utils();
3786
3920
  init_utils_fs();
3787
3921
  init_utils_json();
3788
- import { existsSync as existsSync30, lstatSync as lstatSync8, rmSync as rmSync9 } from "node:fs";
3789
- import { join as join35 } from "node:path";
3922
+ import { existsSync as existsSync31, lstatSync as lstatSync8, rmSync as rmSync9 } from "node:fs";
3923
+ import { join as join36 } from "node:path";
3790
3924
  function emitAutoMove(onPreview, linkPath, ts, name) {
3791
3925
  if (onPreview) {
3792
3926
  onPreview({ kind: "auto-move", from: linkPath, to: `backup/${ts}/${name}` });
@@ -3807,11 +3941,11 @@ function applySharedLinks(ts, map, opts = {}) {
3807
3941
  const repo = repoHome();
3808
3942
  const linkNames = allSharedLinks(map);
3809
3943
  for (const name of linkNames) {
3810
- const linkPath = join35(claude, name);
3811
- const target = join35(repo, "shared", name);
3812
- if (!existsSync30(linkPath)) continue;
3944
+ const linkPath = join36(claude, name);
3945
+ const target = join36(repo, "shared", name);
3946
+ if (!existsSync31(linkPath)) continue;
3813
3947
  if (lstatSync8(linkPath).isSymbolicLink()) continue;
3814
- if (!existsSync30(target)) continue;
3948
+ if (!existsSync31(target)) continue;
3815
3949
  if (dryRun) {
3816
3950
  emitAutoMove(opts.onPreview, linkPath, ts, name);
3817
3951
  continue;
@@ -3820,30 +3954,30 @@ function applySharedLinks(ts, map, opts = {}) {
3820
3954
  rmSync9(linkPath, { recursive: true, force: true });
3821
3955
  }
3822
3956
  for (const name of linkNames) {
3823
- const target = join35(repo, "shared", name);
3824
- if (!existsSync30(target)) continue;
3957
+ const target = join36(repo, "shared", name);
3958
+ if (!existsSync31(target)) continue;
3825
3959
  if (dryRun) {
3826
- emitCreate(opts.onPreview, join35(claude, name), target);
3960
+ emitCreate(opts.onPreview, join36(claude, name), target);
3827
3961
  continue;
3828
3962
  }
3829
- ensureSymlink(join35(claude, name), target);
3963
+ ensureSymlink(join36(claude, name), target);
3830
3964
  }
3831
3965
  }
3832
3966
  function regenerateSettings(ts, opts = {}) {
3833
3967
  const dryRun = opts.dryRun === true;
3834
3968
  const repo = repoHome();
3835
3969
  const claude = claudeHome();
3836
- const basePath = join35(repo, "shared", "settings.base.json");
3837
- const hostPath = join35(repo, "hosts", `${HOST}.json`);
3838
- if (!existsSync30(basePath)) {
3970
+ const basePath = join36(repo, "shared", "settings.base.json");
3971
+ const hostPath = join36(repo, "hosts", `${HOST}.json`);
3972
+ if (!existsSync31(basePath)) {
3839
3973
  die("repo not initialized; run 'nomad init' to scaffold");
3840
3974
  }
3841
3975
  const base = readJson(basePath);
3842
- const hasOverrides = existsSync30(hostPath);
3976
+ const hasOverrides = existsSync31(hostPath);
3843
3977
  const overrides = hasOverrides ? readJson(hostPath) : {};
3844
3978
  const merged = deepMerge(base, overrides);
3845
- const settingsPath = join35(claude, "settings.json");
3846
- if (!hasOverrides && existsSync30(settingsPath)) {
3979
+ const settingsPath = join36(claude, "settings.json");
3980
+ if (!hasOverrides && existsSync31(settingsPath)) {
3847
3981
  try {
3848
3982
  const existing = readJson(settingsPath);
3849
3983
  const baseKeys = new Set(Object.keys(base));
@@ -3869,8 +4003,8 @@ function regenerateSettings(ts, opts = {}) {
3869
4003
 
3870
4004
  // src/preview.ts
3871
4005
  init_config();
3872
- import { existsSync as existsSync31 } from "node:fs";
3873
- import { join as join36 } from "node:path";
4006
+ import { existsSync as existsSync32 } from "node:fs";
4007
+ import { join as join37 } from "node:path";
3874
4008
 
3875
4009
  // node_modules/diff/libesm/diff/base.js
3876
4010
  var Diff = class {
@@ -4156,7 +4290,7 @@ function diffJsonStrings(currentJsonText, newJsonText) {
4156
4290
  return lines.join("\n");
4157
4291
  }
4158
4292
  function readJsonOrNull(path) {
4159
- if (!existsSync31(path)) return null;
4293
+ if (!existsSync32(path)) return null;
4160
4294
  try {
4161
4295
  return readJson(path);
4162
4296
  } catch {
@@ -4170,12 +4304,12 @@ function previewSettings(basePath, hostPath, settingsPath) {
4170
4304
  }
4171
4305
  const notes = [];
4172
4306
  const hostOverrides = readJsonOrNull(hostPath);
4173
- if (hostOverrides === null && existsSync31(hostPath)) {
4307
+ if (hostOverrides === null && existsSync32(hostPath)) {
4174
4308
  notes.push(`malformed hosts/${HOST}.json; ignoring overrides`);
4175
4309
  }
4176
4310
  const merged = deepMerge(base, hostOverrides ?? {});
4177
4311
  const current = readJsonOrNull(settingsPath);
4178
- if (current === null && existsSync31(settingsPath)) {
4312
+ if (current === null && existsSync32(settingsPath)) {
4179
4313
  return { diff: "", notes: [...notes, "malformed; skipping diff"] };
4180
4314
  }
4181
4315
  const rawEqual = JSON.stringify(current ?? {}, null, 2) === JSON.stringify(merged, null, 2);
@@ -4215,9 +4349,9 @@ function computePreview(ts, map, verb = "pull") {
4215
4349
  onPreview: (e) => addItem(links, formatLinkRow(e))
4216
4350
  });
4217
4351
  const settingsResult = previewSettings(
4218
- join36(repo, "shared", "settings.base.json"),
4219
- join36(repo, "hosts", `${HOST}.json`),
4220
- join36(claude, "settings.json")
4352
+ join37(repo, "shared", "settings.base.json"),
4353
+ join37(repo, "hosts", `${HOST}.json`),
4354
+ join37(claude, "settings.json")
4221
4355
  );
4222
4356
  const settingsSection = buildSettingsSectionForPreview(settingsResult);
4223
4357
  const sessions = section("Sessions");
@@ -4233,21 +4367,21 @@ function computePreview(ts, map, verb = "pull") {
4233
4367
 
4234
4368
  // src/spinner.ts
4235
4369
  init_color();
4236
- import { existsSync as existsSync33 } from "node:fs";
4370
+ import { existsSync as existsSync34 } from "node:fs";
4237
4371
  import { fileURLToPath as fileURLToPath4 } from "node:url";
4238
4372
  import { Worker } from "node:worker_threads";
4239
4373
 
4240
4374
  // src/commands.push.recovery.ts
4241
4375
  init_config();
4242
- import { readFileSync as readFileSync11, rmSync as rmSync11, writeFileSync as writeFileSync5 } from "node:fs";
4243
- import { join as join39 } from "node:path";
4376
+ import { readFileSync as readFileSync12, rmSync as rmSync11, writeFileSync as writeFileSync5 } from "node:fs";
4377
+ import { join as join40 } from "node:path";
4244
4378
  import { createInterface } from "node:readline/promises";
4245
4379
 
4246
4380
  // src/commands.push.recovery.redact.ts
4247
4381
  init_config();
4248
4382
  init_config_sharedDirs_guard();
4249
- import { cpSync as cpSync6, existsSync as existsSync32, mkdirSync as mkdirSync7, statSync as statSync8 } from "node:fs";
4250
- import { dirname as dirname6, join as join37, sep as sep3 } from "node:path";
4383
+ import { cpSync as cpSync6, existsSync as existsSync33, mkdirSync as mkdirSync7, statSync as statSync8 } from "node:fs";
4384
+ import { dirname as dirname6, join as join38, sep as sep3 } from "node:path";
4251
4385
  init_push_gitleaks_scan();
4252
4386
  init_utils_json();
4253
4387
  init_utils();
@@ -4278,8 +4412,8 @@ function resolveStagedDir(localPath, map, claude, repo) {
4278
4412
  assertSafeLogical(logical);
4279
4413
  const abs = hostMap[HOST];
4280
4414
  if (abs === void 0) continue;
4281
- if (localPath.startsWith(join37(claude, "projects", encodePath(abs)) + sep3)) {
4282
- return join37(repo, "shared", "projects", logical);
4415
+ if (localPath.startsWith(join38(claude, "projects", encodePath(abs)) + sep3)) {
4416
+ return join38(repo, "shared", "projects", logical);
4283
4417
  }
4284
4418
  }
4285
4419
  return null;
@@ -4303,7 +4437,7 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
4303
4437
  `could not locate the local transcript for session ${sid}; choose Skip or Drop session.`
4304
4438
  );
4305
4439
  }
4306
- const sessionDir = join37(dirname6(localPath), sid);
4440
+ const sessionDir = join38(dirname6(localPath), sid);
4307
4441
  const subtreeFiles = listSubtreeFiles(sessionDir);
4308
4442
  const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync8(p).mtimeMs);
4309
4443
  if (isRecentlyModified(subtreeMtime, nowMs())) {
@@ -4337,9 +4471,9 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
4337
4471
  );
4338
4472
  }
4339
4473
  mkdirSync7(stagedProjectDir, { recursive: true });
4340
- cpSync6(localPath, join37(stagedProjectDir, `${sid}.jsonl`), { force: true });
4341
- if (existsSync32(sessionDir)) {
4342
- cpSync6(sessionDir, join37(stagedProjectDir, sid), { force: true, recursive: true });
4474
+ cpSync6(localPath, join38(stagedProjectDir, `${sid}.jsonl`), { force: true });
4475
+ if (existsSync33(sessionDir)) {
4476
+ cpSync6(sessionDir, join38(stagedProjectDir, sid), { force: true, recursive: true });
4343
4477
  }
4344
4478
  return true;
4345
4479
  }
@@ -4347,14 +4481,14 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
4347
4481
  // src/commands.push.recovery.drop.ts
4348
4482
  init_config();
4349
4483
  import { rmSync as rmSync10 } from "node:fs";
4350
- import { join as join38 } from "node:path";
4484
+ import { join as join39 } from "node:path";
4351
4485
  function dropSessionFromStaged(sid, map) {
4352
4486
  const logicals = Object.keys(map.projects);
4353
4487
  if (logicals.length === 0) return false;
4354
4488
  const repo = repoHome();
4355
4489
  for (const logical of logicals) {
4356
- const jsonl = join38(repo, "shared", "projects", logical, `${sid}.jsonl`);
4357
- const dir = join38(repo, "shared", "projects", logical, sid);
4490
+ const jsonl = join39(repo, "shared", "projects", logical, `${sid}.jsonl`);
4491
+ const dir = join39(repo, "shared", "projects", logical, sid);
4358
4492
  rmSync10(jsonl, { force: true });
4359
4493
  rmSync10(dir, { recursive: true, force: true });
4360
4494
  }
@@ -4474,10 +4608,10 @@ function applyThenRescan(scanVerdict, repoHome2) {
4474
4608
  return next;
4475
4609
  }
4476
4610
  function allowThenRescan(append, scanVerdict, repoHome2) {
4477
- const ignPath = join39(repoHome2, ".gitleaksignore");
4611
+ const ignPath = join40(repoHome2, ".gitleaksignore");
4478
4612
  let before;
4479
4613
  try {
4480
- before = readFileSync11(ignPath, "utf8");
4614
+ before = readFileSync12(ignPath, "utf8");
4481
4615
  } catch {
4482
4616
  before = null;
4483
4617
  }
@@ -4573,7 +4707,7 @@ function writeAnimatedDone(out, label, ms, useTTY) {
4573
4707
  `);
4574
4708
  }
4575
4709
  function resolveWorkerPath(deps = {}) {
4576
- const check = deps.existsSyncFn ?? existsSync33;
4710
+ const check = deps.existsSyncFn ?? existsSync34;
4577
4711
  const base = deps.baseUrl ?? import.meta.url;
4578
4712
  const mjs = fileURLToPath4(new URL("./nomad.worker.mjs", base));
4579
4713
  if (check(mjs)) return mjs;
@@ -4781,8 +4915,8 @@ function cmdPull(opts = {}) {
4781
4915
  const forceRemote = opts.forceRemote === true;
4782
4916
  const repo = repoHome();
4783
4917
  const backup = backupBase();
4784
- if (!existsSync34(repo)) die(`repo not cloned at ${repo}`);
4785
- if (!existsSync34(join40(repo, "shared", "settings.base.json"))) {
4918
+ if (!existsSync35(repo)) die(`repo not cloned at ${repo}`);
4919
+ if (!existsSync35(join41(repo, "shared", "settings.base.json"))) {
4786
4920
  die("repo not initialized; run 'nomad init' to scaffold");
4787
4921
  }
4788
4922
  const handle = acquireLock("pull");
@@ -4791,7 +4925,7 @@ function cmdPull(opts = {}) {
4791
4925
  const ts = freshBackupTs(backup);
4792
4926
  handleWedge(repo, forceRemote);
4793
4927
  if (!dryRun) {
4794
- const backupRoot = join40(backup, ts);
4928
+ const backupRoot = join41(backup, ts);
4795
4929
  try {
4796
4930
  mkdirSync8(backupRoot, { recursive: true });
4797
4931
  } catch (err) {
@@ -4802,8 +4936,8 @@ function cmdPull(opts = {}) {
4802
4936
  dryRun ? `pulling on host=${HOST} (backup=${ts}; dry-run)` : `pull on host=${HOST} (backup=${ts})`
4803
4937
  );
4804
4938
  gitOrFatal(["pull", "--rebase", "--autostash"], "git pull --rebase", repo);
4805
- const mapPath = join40(repo, "path-map.json");
4806
- const map = existsSync34(mapPath) ? readPathMap(mapPath) : { projects: {} };
4939
+ const mapPath = join41(repo, "path-map.json");
4940
+ const map = existsSync35(mapPath) ? readPathMap(mapPath) : { projects: {} };
4807
4941
  divergenceCheckExtras(ts);
4808
4942
  if (dryRun) {
4809
4943
  computePreview(ts, map, "pull");
@@ -4825,8 +4959,8 @@ function cmdPull(opts = {}) {
4825
4959
 
4826
4960
  // src/commands.push.ts
4827
4961
  init_config();
4828
- import { existsSync as existsSync36 } from "node:fs";
4829
- import { join as join42, relative as relative5 } from "node:path";
4962
+ import { existsSync as existsSync37 } from "node:fs";
4963
+ import { join as join43, relative as relative5 } from "node:path";
4830
4964
 
4831
4965
  // src/commands.push.allowlist.ts
4832
4966
  init_config();
@@ -4906,9 +5040,9 @@ init_color();
4906
5040
  init_config();
4907
5041
  init_config_sharedDirs_guard();
4908
5042
  import { randomBytes as randomBytes2 } from "node:crypto";
4909
- import { copyFileSync, existsSync as existsSync35, mkdirSync as mkdirSync9, readdirSync as readdirSync11, rmSync as rmSync12 } from "node:fs";
5043
+ import { copyFileSync, existsSync as existsSync36, mkdirSync as mkdirSync9, readdirSync as readdirSync11, rmSync as rmSync12 } from "node:fs";
4910
5044
  import { homedir as homedir5 } from "node:os";
4911
- import { join as join41 } from "node:path";
5045
+ import { join as join42 } from "node:path";
4912
5046
  init_push_leak_verdict();
4913
5047
  init_push_gitleaks();
4914
5048
  init_utils_fs();
@@ -4923,13 +5057,13 @@ function stageSessions(tmpRoot, map) {
4923
5057
  if (!p || p === "TBD") continue;
4924
5058
  reverse.set(encodePath(p), logical);
4925
5059
  }
4926
- const localProjects = join41(claudeHome(), "projects");
4927
- if (!existsSync35(localProjects)) return 0;
5060
+ const localProjects = join42(claudeHome(), "projects");
5061
+ if (!existsSync36(localProjects)) return 0;
4928
5062
  let staged = 0;
4929
5063
  for (const dir of readdirSync11(localProjects)) {
4930
5064
  const logical = reverse.get(dir);
4931
5065
  if (!logical) continue;
4932
- copyDirJsonlOnly(join41(localProjects, dir), join41(tmpRoot, "shared", "projects", logical));
5066
+ copyDirJsonlOnly(join42(localProjects, dir), join42(tmpRoot, "shared", "projects", logical));
4933
5067
  staged++;
4934
5068
  }
4935
5069
  return staged;
@@ -4945,9 +5079,9 @@ function stageExtras(tmpRoot, map) {
4945
5079
  if (!localRoot || localRoot === "TBD") continue;
4946
5080
  for (const dirname7 of dirnames) {
4947
5081
  if (!whitelist.includes(dirname7)) continue;
4948
- const src = join41(localRoot, dirname7);
4949
- if (!existsSync35(src)) continue;
4950
- const dst = join41(tmpRoot, "shared", "extras", logical, dirname7);
5082
+ const src = join42(localRoot, dirname7);
5083
+ if (!existsSync36(src)) continue;
5084
+ const dst = join42(tmpRoot, "shared", "extras", logical, dirname7);
4951
5085
  copyExtras(src, dst);
4952
5086
  staged++;
4953
5087
  }
@@ -4955,19 +5089,19 @@ function stageExtras(tmpRoot, map) {
4955
5089
  return staged;
4956
5090
  }
4957
5091
  function previewPushLeaks(map) {
4958
- const cacheDir = join41(homedir5(), ".cache", "claude-nomad");
5092
+ const cacheDir = join42(homedir5(), ".cache", "claude-nomad");
4959
5093
  mkdirSync9(cacheDir, { recursive: true });
4960
5094
  const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes2(4).toString("hex")}`;
4961
- const tmpRoot = join41(cacheDir, `push-preview-tree-${stamp}`);
5095
+ const tmpRoot = join42(cacheDir, `push-preview-tree-${stamp}`);
4962
5096
  try {
4963
5097
  const sessionCount = stageSessions(tmpRoot, map);
4964
5098
  const extrasCount = stageExtras(tmpRoot, map);
4965
5099
  if (sessionCount + extrasCount === 0) {
4966
5100
  return { leak: false, verdictRow: NOTHING_TO_SCAN_ROW, recovery: null, findings: [] };
4967
5101
  }
4968
- const ignoreFile = join41(repoHome(), ".gitleaksignore");
4969
- if (existsSync35(ignoreFile)) {
4970
- copyFileSync(ignoreFile, join41(tmpRoot, ".gitleaksignore"));
5102
+ const ignoreFile = join42(repoHome(), ".gitleaksignore");
5103
+ if (existsSync36(ignoreFile)) {
5104
+ copyFileSync(ignoreFile, join42(tmpRoot, ".gitleaksignore"));
4971
5105
  }
4972
5106
  let findings;
4973
5107
  try {
@@ -4989,7 +5123,7 @@ init_utils();
4989
5123
  init_utils_fs();
4990
5124
  init_utils_json();
4991
5125
  function guardGitlinks(repo) {
4992
- const gitlinks = findGitlinks(join42(repo, "shared"));
5126
+ const gitlinks = findGitlinks(join43(repo, "shared"));
4993
5127
  if (gitlinks.length === 0) return;
4994
5128
  for (const p of gitlinks) {
4995
5129
  const rel = relative5(repo, p);
@@ -5043,7 +5177,7 @@ async function cmdPush(opts = {}) {
5043
5177
  guardResolutionModeConflicts(dryRun, redactAll, allowAll, allowRule);
5044
5178
  const repo = repoHome();
5045
5179
  const backup = backupBase();
5046
- if (!existsSync36(repo)) die(`repo not cloned at ${repo}`);
5180
+ if (!existsSync37(repo)) die(`repo not cloned at ${repo}`);
5047
5181
  const handle = acquireLock("push");
5048
5182
  if (handle === null) process.exit(0);
5049
5183
  try {
@@ -5061,8 +5195,8 @@ async function cmdPush(opts = {}) {
5061
5195
  renderNoScanTree(st);
5062
5196
  return;
5063
5197
  }
5064
- const mapPath = join42(repo, "path-map.json");
5065
- if (!existsSync36(mapPath)) {
5198
+ const mapPath = join43(repo, "path-map.json");
5199
+ if (!existsSync37(mapPath)) {
5066
5200
  if (dryRun) return runDryRunPreview(st, null);
5067
5201
  die("path-map.json missing, cannot enforce push allow-list");
5068
5202
  }
@@ -5102,18 +5236,18 @@ init_config();
5102
5236
 
5103
5237
  // src/diff.ts
5104
5238
  init_config();
5105
- import { existsSync as existsSync37 } from "node:fs";
5106
- import { join as join43 } from "node:path";
5239
+ import { existsSync as existsSync38 } from "node:fs";
5240
+ import { join as join44 } from "node:path";
5107
5241
  init_utils();
5108
5242
  init_utils_fs();
5109
5243
  init_utils_json();
5110
5244
  function cmdDiff() {
5111
5245
  try {
5112
5246
  const repo = repoHome();
5113
- if (!existsSync37(repo)) die(`repo not cloned at ${repo}`);
5247
+ if (!existsSync38(repo)) die(`repo not cloned at ${repo}`);
5114
5248
  const ts = freshBackupTs(backupBase());
5115
- const mapPath = join43(repo, "path-map.json");
5116
- const map = existsSync37(mapPath) ? readPathMap(mapPath) : { projects: {} };
5249
+ const mapPath = join44(repo, "path-map.json");
5250
+ const map = existsSync38(mapPath) ? readPathMap(mapPath) : { projects: {} };
5117
5251
  computePreview(ts, map, "diff");
5118
5252
  } catch (err) {
5119
5253
  if (err instanceof NomadFatal) {
@@ -5127,8 +5261,8 @@ function cmdDiff() {
5127
5261
 
5128
5262
  // src/init.ts
5129
5263
  init_config();
5130
- import { existsSync as existsSync39, mkdirSync as mkdirSync10, writeFileSync as writeFileSync6 } from "node:fs";
5131
- import { join as join45 } from "node:path";
5264
+ import { existsSync as existsSync40, mkdirSync as mkdirSync10, writeFileSync as writeFileSync6 } from "node:fs";
5265
+ import { join as join46 } from "node:path";
5132
5266
 
5133
5267
  // src/init.gh-onboard.ts
5134
5268
  init_config();
@@ -5210,33 +5344,33 @@ init_config();
5210
5344
  init_utils();
5211
5345
  init_utils_fs();
5212
5346
  init_utils_json();
5213
- import { copyFileSync as copyFileSync2, cpSync as cpSync7, existsSync as existsSync38, rmSync as rmSync13, statSync as statSync9 } from "node:fs";
5214
- import { join as join44 } from "node:path";
5347
+ import { copyFileSync as copyFileSync2, cpSync as cpSync7, existsSync as existsSync39, rmSync as rmSync13, statSync as statSync9 } from "node:fs";
5348
+ import { join as join45 } from "node:path";
5215
5349
  function snapshotIntoShared(map) {
5216
5350
  const repo = repoHome();
5217
5351
  const claude = claudeHome();
5218
5352
  for (const name of allSharedLinks(map)) {
5219
- const src = join44(claude, name);
5220
- if (!existsSync38(src)) continue;
5221
- const dst = join44(repo, "shared", name);
5353
+ const src = join45(claude, name);
5354
+ if (!existsSync39(src)) continue;
5355
+ const dst = join45(repo, "shared", name);
5222
5356
  if (statSync9(src).isDirectory()) {
5223
- const gk = join44(dst, ".gitkeep");
5224
- if (existsSync38(gk)) rmSync13(gk);
5357
+ const gk = join45(dst, ".gitkeep");
5358
+ if (existsSync39(gk)) rmSync13(gk);
5225
5359
  cpSync7(src, dst, { recursive: true, force: false, errorOnExist: true });
5226
5360
  } else {
5227
5361
  copyFileSync2(src, dst);
5228
5362
  }
5229
5363
  log(`snapshotted shared/${name} from ${src}`);
5230
5364
  }
5231
- const userSettings = join44(claude, "settings.json");
5232
- if (existsSync38(userSettings)) {
5365
+ const userSettings = join45(claude, "settings.json");
5366
+ if (existsSync39(userSettings)) {
5233
5367
  let parsed;
5234
5368
  try {
5235
5369
  parsed = readJson(userSettings);
5236
5370
  } catch (err) {
5237
5371
  return die(`malformed ${userSettings}: ${err.message}`);
5238
5372
  }
5239
- const hostFile = join44(repo, "hosts", `${HOST}.json`);
5373
+ const hostFile = join45(repo, "hosts", `${HOST}.json`);
5240
5374
  writeJsonAtomic(hostFile, parsed);
5241
5375
  log(`snapshotted hosts/${HOST}.json from ${userSettings}`);
5242
5376
  }
@@ -5249,14 +5383,14 @@ var SHARED_CLAUDE_MD = "<!-- claude-nomad shared CLAUDE.md; symlinked into ~/.cl
5249
5383
  var SHARED_KEEP_DIRS = ["agents", "skills", "commands", "rules", "hooks"];
5250
5384
  function preflightConflict(repoHome2) {
5251
5385
  const candidates = [
5252
- join45(repoHome2, "shared", "settings.base.json"),
5253
- join45(repoHome2, "shared", "CLAUDE.md"),
5254
- join45(repoHome2, "path-map.json"),
5255
- join45(repoHome2, "hosts"),
5256
- join45(repoHome2, "shared")
5386
+ join46(repoHome2, "shared", "settings.base.json"),
5387
+ join46(repoHome2, "shared", "CLAUDE.md"),
5388
+ join46(repoHome2, "path-map.json"),
5389
+ join46(repoHome2, "hosts"),
5390
+ join46(repoHome2, "shared")
5257
5391
  ];
5258
5392
  for (const c of candidates) {
5259
- if (existsSync39(c)) return c;
5393
+ if (existsSync40(c)) return c;
5260
5394
  }
5261
5395
  return null;
5262
5396
  }
@@ -5271,25 +5405,25 @@ function cmdInit(opts = {}) {
5271
5405
  die(`already initialized; refusing to clobber ${conflict}`);
5272
5406
  }
5273
5407
  ensureOriginRepo(opts.repoName ?? DEFAULT_REPO_NAME, opts.run);
5274
- mkdirSync10(join45(repo, "shared"), { recursive: true });
5275
- mkdirSync10(join45(repo, "hosts"), { recursive: true });
5408
+ mkdirSync10(join46(repo, "shared"), { recursive: true });
5409
+ mkdirSync10(join46(repo, "hosts"), { recursive: true });
5276
5410
  for (const name of SHARED_KEEP_DIRS) {
5277
- mkdirSync10(join45(repo, "shared", name), { recursive: true });
5411
+ mkdirSync10(join46(repo, "shared", name), { recursive: true });
5278
5412
  }
5279
- const userClaudeMd = join45(claude, "CLAUDE.md");
5280
- if (!snapshot || !existsSync39(userClaudeMd)) {
5281
- writeFileSync6(join45(repo, "shared", "CLAUDE.md"), SHARED_CLAUDE_MD);
5413
+ const userClaudeMd = join46(claude, "CLAUDE.md");
5414
+ if (!snapshot || !existsSync40(userClaudeMd)) {
5415
+ writeFileSync6(join46(repo, "shared", "CLAUDE.md"), SHARED_CLAUDE_MD);
5282
5416
  item("created shared/CLAUDE.md");
5283
5417
  }
5284
5418
  for (const name of SHARED_KEEP_DIRS) {
5285
- writeFileSync6(join45(repo, "shared", name, ".gitkeep"), "");
5419
+ writeFileSync6(join46(repo, "shared", name, ".gitkeep"), "");
5286
5420
  item(`created shared/${name}/.gitkeep`);
5287
5421
  }
5288
- writeFileSync6(join45(repo, "hosts", ".gitkeep"), "");
5422
+ writeFileSync6(join46(repo, "hosts", ".gitkeep"), "");
5289
5423
  item("created hosts/.gitkeep");
5290
- writeJsonAtomic(join45(repo, "shared", "settings.base.json"), {});
5424
+ writeJsonAtomic(join46(repo, "shared", "settings.base.json"), {});
5291
5425
  item("created shared/settings.base.json");
5292
- writeJsonAtomic(join45(repo, "path-map.json"), { projects: {} });
5426
+ writeJsonAtomic(join46(repo, "path-map.json"), { projects: {} });
5293
5427
  item("created path-map.json");
5294
5428
  if (snapshot) {
5295
5429
  snapshotIntoShared({ projects: {} });
@@ -5590,7 +5724,7 @@ function parsePushArgs(argv) {
5590
5724
  // package.json
5591
5725
  var package_default = {
5592
5726
  name: "claude-nomad",
5593
- version: "0.44.1",
5727
+ version: "0.45.0",
5594
5728
  type: "module",
5595
5729
  description: "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
5596
5730
  keywords: [
@@ -5792,15 +5926,15 @@ var DEFAULT_HELP = [
5792
5926
  init_config();
5793
5927
  init_utils();
5794
5928
  init_utils_json();
5795
- import { existsSync as existsSync40, readFileSync as readFileSync12, readdirSync as readdirSync12 } from "node:fs";
5796
- import { join as join46 } from "node:path";
5929
+ import { existsSync as existsSync41, readFileSync as readFileSync13, readdirSync as readdirSync12 } from "node:fs";
5930
+ import { join as join47 } from "node:path";
5797
5931
  function resumeCmd(sessionId) {
5798
5932
  if (!/^[A-Za-z0-9_-]+$/.test(sessionId) || sessionId.length > 128) {
5799
5933
  fail(`invalid session id: ${sessionId}`);
5800
5934
  process.exit(1);
5801
5935
  }
5802
- const projectsRoot = join46(claudeHome(), "projects");
5803
- if (!existsSync40(projectsRoot)) {
5936
+ const projectsRoot = join47(claudeHome(), "projects");
5937
+ if (!existsSync41(projectsRoot)) {
5804
5938
  fail(`${projectsRoot} does not exist`);
5805
5939
  process.exit(1);
5806
5940
  }
@@ -5814,8 +5948,8 @@ function resumeCmd(sessionId) {
5814
5948
  fail(`no cwd field found in ${jsonlPath}`);
5815
5949
  process.exit(1);
5816
5950
  }
5817
- const mapPath = join46(repoHome(), "path-map.json");
5818
- if (!existsSync40(mapPath)) {
5951
+ const mapPath = join47(repoHome(), "path-map.json");
5952
+ if (!existsSync41(mapPath)) {
5819
5953
  fail("path-map.json missing");
5820
5954
  process.exit(1);
5821
5955
  }
@@ -5838,13 +5972,13 @@ function resumeCmd(sessionId) {
5838
5972
  }
5839
5973
  function findTranscriptPath(projectsRoot, sessionId) {
5840
5974
  for (const dir of readdirSync12(projectsRoot)) {
5841
- const candidate = join46(projectsRoot, dir, `${sessionId}.jsonl`);
5842
- if (existsSync40(candidate)) return candidate;
5975
+ const candidate = join47(projectsRoot, dir, `${sessionId}.jsonl`);
5976
+ if (existsSync41(candidate)) return candidate;
5843
5977
  }
5844
5978
  return null;
5845
5979
  }
5846
5980
  function extractRecordedCwd(jsonlPath) {
5847
- for (const line of readFileSync12(jsonlPath, "utf8").split("\n")) {
5981
+ for (const line of readFileSync13(jsonlPath, "utf8").split("\n")) {
5848
5982
  if (!line.trim()) continue;
5849
5983
  try {
5850
5984
  const obj = JSON.parse(line);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-nomad",
3
- "version": "0.44.1",
3
+ "version": "0.45.0",
4
4
  "type": "module",
5
5
  "description": "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
6
6
  "keywords": [