claude-nomad 0.53.3 → 0.55.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
@@ -614,19 +614,19 @@ var init_utils_fs = __esm({
614
614
  });
615
615
 
616
616
  // src/commands.pull.wedge.ts
617
- import { execFileSync as execFileSync2 } from "node:child_process";
618
- import { existsSync as existsSync12 } from "node:fs";
619
- import { join as join14 } from "node:path";
617
+ import { execFileSync as execFileSync3 } from "node:child_process";
618
+ import { existsSync as existsSync13 } from "node:fs";
619
+ import { join as join15 } from "node:path";
620
620
  function detectWedge(repo) {
621
- const g = join14(repo, ".git");
622
- if (existsSync12(join14(g, "rebase-merge")) || existsSync12(join14(g, "rebase-apply"))) return "rebase";
623
- if (existsSync12(join14(g, "MERGE_HEAD"))) return "merge";
621
+ const g = join15(repo, ".git");
622
+ if (existsSync13(join15(g, "rebase-merge")) || existsSync13(join15(g, "rebase-apply"))) return "rebase";
623
+ if (existsSync13(join15(g, "MERGE_HEAD"))) return "merge";
624
624
  return null;
625
625
  }
626
626
  function unmergedIndexPresent(repo) {
627
627
  let raw;
628
628
  try {
629
- raw = execFileSync2("git", ["diff", "--diff-filter=U", "--name-only", "-z"], {
629
+ raw = execFileSync3("git", ["diff", "--diff-filter=U", "--name-only", "-z"], {
630
630
  cwd: repo,
631
631
  stdio: ["ignore", "pipe", "pipe"],
632
632
  maxBuffer: 64 * 1024 * 1024
@@ -660,7 +660,7 @@ function wedgeMarkerRunbookText(state) {
660
660
  function orphanedAutostashPresent(repo) {
661
661
  let raw;
662
662
  try {
663
- raw = execFileSync2("git", ["stash", "list"], {
663
+ raw = execFileSync3("git", ["stash", "list"], {
664
664
  cwd: repo,
665
665
  stdio: ["ignore", "pipe", "pipe"],
666
666
  maxBuffer: 64 * 1024 * 1024
@@ -677,15 +677,15 @@ var init_commands_pull_wedge = __esm({
677
677
  });
678
678
 
679
679
  // src/push-gitleaks.config.ts
680
- import { existsSync as existsSync13, mkdtempSync, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
680
+ import { existsSync as existsSync14, mkdtempSync, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
681
681
  import { tmpdir } from "node:os";
682
- import { join as join15 } from "node:path";
682
+ import { join as join16 } from "node:path";
683
683
  import { fileURLToPath } from "node:url";
684
684
  function resolveTomlPath(repo = repoHome()) {
685
- const repoToml = join15(repo, ".gitleaks.toml");
686
- if (existsSync13(repoToml)) return repoToml;
685
+ const repoToml = join16(repo, ".gitleaks.toml");
686
+ if (existsSync14(repoToml)) return repoToml;
687
687
  const bundled = fileURLToPath(new URL("../.gitleaks.toml", import.meta.url));
688
- return existsSync13(bundled) ? bundled : null;
688
+ return existsSync14(bundled) ? bundled : null;
689
689
  }
690
690
  function assertOverlayAllowlistsScoped(overlayBody) {
691
691
  if (OVERLAY_INLINE_ALLOWLIST_RE.test(overlayBody)) {
@@ -723,17 +723,17 @@ function buildOverlayTempConfig(overlayBody, bundled) {
723
723
  path = ${JSON.stringify(bundled)}
724
724
 
725
725
  ${overlayBody}`;
726
- const tempPath = mkdtempSync(join15(tmpdir(), "nomad-gitleaks-cfg-"));
727
- const configPath = join15(tempPath, "config.toml");
726
+ const tempPath = mkdtempSync(join16(tmpdir(), "nomad-gitleaks-cfg-"));
727
+ const configPath = join16(tempPath, "config.toml");
728
728
  writeFileSync3(configPath, tempBody, { mode: 384, flag: "wx" });
729
729
  return { configPath, tempPath };
730
730
  }
731
731
  function resolveTomlConfig() {
732
732
  const repo = repoHome();
733
- const overlayPath = join15(repo, ".gitleaks.overlay.toml");
734
- const repoToml = join15(repo, ".gitleaks.toml");
733
+ const overlayPath = join16(repo, ".gitleaks.overlay.toml");
734
+ const repoToml = join16(repo, ".gitleaks.toml");
735
735
  const bundled = resolveTomlPath(repo);
736
- if (!existsSync13(overlayPath)) {
736
+ if (!existsSync14(overlayPath)) {
737
737
  return { path: bundled, tempPath: null };
738
738
  }
739
739
  if (bundled === repoToml) {
@@ -779,10 +779,10 @@ var init_push_gitleaks_config = __esm({
779
779
  });
780
780
 
781
781
  // src/push-checks.ts
782
- import { execFileSync as execFileSync3 } from "node:child_process";
783
- import { readdirSync as readdirSync4, rmSync as rmSync5 } from "node:fs";
782
+ import { execFileSync as execFileSync4 } from "node:child_process";
783
+ import { readdirSync as readdirSync5, rmSync as rmSync5 } from "node:fs";
784
784
  import { homedir as homedir2, platform } from "node:os";
785
- import { join as join16 } from "node:path";
785
+ import { join as join17 } from "node:path";
786
786
  function gitleaksInstallHint() {
787
787
  const head = "gitleaks not on PATH (required for nomad push). Install:";
788
788
  const plat = platform();
@@ -820,12 +820,12 @@ function findGitlinks(dir) {
820
820
  function walk(current) {
821
821
  let entries;
822
822
  try {
823
- entries = readdirSync4(current, { withFileTypes: true });
823
+ entries = readdirSync5(current, { withFileTypes: true });
824
824
  } catch {
825
825
  return;
826
826
  }
827
827
  for (const e of entries) {
828
- const p = join16(current, e.name);
828
+ const p = join17(current, e.name);
829
829
  if (e.name === ".git") {
830
830
  hits.push(p);
831
831
  continue;
@@ -841,7 +841,7 @@ function probeGitleaks() {
841
841
  const args = ["version"];
842
842
  if (toml !== null) args.push("--config", toml);
843
843
  try {
844
- return execFileSync3("gitleaks", args, { stdio: ["ignore", "pipe", "pipe"] }).toString().trim();
844
+ return execFileSync4("gitleaks", args, { stdio: ["ignore", "pipe", "pipe"] }).toString().trim();
845
845
  } catch (err) {
846
846
  const e = err;
847
847
  if (e.code === "ENOENT") throw new NomadFatal(gitleaksInstallHint());
@@ -861,7 +861,7 @@ function rebaseBeforePush(repo) {
861
861
  const wedge = classifyWedge(repo);
862
862
  if (wedge !== null) throw new NomadFatal(wedgePreflight(wedge));
863
863
  try {
864
- execFileSync3("git", ["pull", "--rebase", "--autostash"], {
864
+ execFileSync4("git", ["pull", "--rebase", "--autostash"], {
865
865
  cwd: repo,
866
866
  stdio: ["ignore", "pipe", "pipe"]
867
867
  });
@@ -883,10 +883,10 @@ var init_push_checks = __esm({
883
883
  });
884
884
 
885
885
  // src/push-gitleaks.scan.ts
886
- import { execFileSync as execFileSync6 } from "node:child_process";
886
+ import { execFileSync as execFileSync8 } from "node:child_process";
887
887
  import { mkdirSync as mkdirSync3, readFileSync as readFileSync5, rmSync as rmSync6 } from "node:fs";
888
888
  import { homedir as homedir3 } from "node:os";
889
- import { join as join20 } from "node:path";
889
+ import { join as join21 } from "node:path";
890
890
  function readGitleaksReport(reportPath) {
891
891
  try {
892
892
  const raw = readFileSync5(reportPath, "utf8");
@@ -898,9 +898,9 @@ function readGitleaksReport(reportPath) {
898
898
  }
899
899
  }
900
900
  function scanStagedTree(repoDir, forwardStreams = false) {
901
- const cacheDir = join20(homedir3(), ".cache", "claude-nomad");
901
+ const cacheDir = join21(homedir3(), ".cache", "claude-nomad");
902
902
  mkdirSync3(cacheDir, { recursive: true });
903
- const reportPath = join20(cacheDir, `gitleaks-${nowTimestamp()}-${process.pid}.json`);
903
+ const reportPath = join21(cacheDir, `gitleaks-${nowTimestamp()}-${process.pid}.json`);
904
904
  const { path: toml, tempPath } = resolveTomlConfig();
905
905
  const args = [
906
906
  "protect",
@@ -913,9 +913,9 @@ function scanStagedTree(repoDir, forwardStreams = false) {
913
913
  if (toml !== null) args.push("--config", toml);
914
914
  const opts = { cwd: repoDir, stdio: ["ignore", "pipe", "pipe"] };
915
915
  try {
916
- execFileSync6("git", ["init", "-q"], opts);
917
- execFileSync6("git", ["add", "-A"], opts);
918
- execFileSync6("gitleaks", args, opts);
916
+ execFileSync8("git", ["init", "-q"], opts);
917
+ execFileSync8("git", ["add", "-A"], opts);
918
+ execFileSync8("gitleaks", args, opts);
919
919
  return [];
920
920
  } catch (err) {
921
921
  const e = err;
@@ -932,9 +932,9 @@ function scanStagedTree(repoDir, forwardStreams = false) {
932
932
  }
933
933
  }
934
934
  function scanFile(filePath, forwardStreams = false) {
935
- const cacheDir = join20(homedir3(), ".cache", "claude-nomad");
935
+ const cacheDir = join21(homedir3(), ".cache", "claude-nomad");
936
936
  mkdirSync3(cacheDir, { recursive: true });
937
- const reportPath = join20(cacheDir, `gitleaks-file-${nowTimestamp()}-${process.pid}.json`);
937
+ const reportPath = join21(cacheDir, `gitleaks-file-${nowTimestamp()}-${process.pid}.json`);
938
938
  const { path: toml, tempPath } = resolveTomlConfig();
939
939
  const args = [
940
940
  "detect",
@@ -947,7 +947,7 @@ function scanFile(filePath, forwardStreams = false) {
947
947
  if (toml !== null) args.push("--config", toml);
948
948
  const opts = { stdio: ["ignore", "pipe", "pipe"] };
949
949
  try {
950
- execFileSync6("gitleaks", args, opts);
950
+ execFileSync8("gitleaks", args, opts);
951
951
  return [];
952
952
  } catch (err) {
953
953
  const e = err;
@@ -1505,6 +1505,14 @@ function partitionByCaptureExclusion(keys) {
1505
1505
  }
1506
1506
  return { promotable, excluded };
1507
1507
  }
1508
+ function describeSettings(keys) {
1509
+ const one = keys.length === 1;
1510
+ return {
1511
+ phrase: `${keys.length} ${one ? "setting" : "settings"} (${keys.join(", ")})`,
1512
+ pronoun: one ? "it" : "them",
1513
+ verb: one ? "is" : "are"
1514
+ };
1515
+ }
1508
1516
  var BIN_NODE_RE = /^"?(?:[A-Za-z]:)?[\\/](?:.*[\\/])?bin[\\/]node"?$/;
1509
1517
  var LEADING_BIN_NODE_RE = /^"?(?:[A-Za-z]:)?[\\/](?:[^"\s]*[\\/])?bin[\\/]node"?(?=\s)/;
1510
1518
  function normalizeNodePathsDeep(value) {
@@ -1614,14 +1622,16 @@ function regenerateSettings(ts, opts = {}) {
1614
1622
  const existing = readJson(settingsPath);
1615
1623
  const drift = classifySettingsDrift(merged, existing);
1616
1624
  if (drift.behind.length > 0) {
1625
+ const { phrase, pronoun } = describeSettings(drift.behind);
1617
1626
  warn(
1618
- `existing settings.json is missing merged keys ${JSON.stringify(drift.behind)}. Run 'nomad pull' to restore them.`
1627
+ `your settings.json is missing ${phrase} that the synced copy has; run 'nomad pull' to restore ${pronoun}.`
1619
1628
  );
1620
1629
  }
1621
1630
  const { promotable } = partitionByCaptureExclusion(drift.ahead);
1622
1631
  if (promotable.length > 0) {
1632
+ const { phrase, pronoun, verb } = describeSettings(promotable);
1623
1633
  warn(
1624
- `existing settings.json has local-only keys ${JSON.stringify(promotable)}. Run 'nomad capture-settings' to promote them into the repo before they are overwritten.`
1634
+ `your settings.json has ${phrase} that ${verb} not yet synced; run 'nomad capture-settings' to save ${pronoun} to the repo before the next pull overwrites ${pronoun}.`
1625
1635
  );
1626
1636
  }
1627
1637
  } catch {
@@ -2063,13 +2073,13 @@ function cmdEject(opts = {}, roots = defaultEjectRoots()) {
2063
2073
  }
2064
2074
 
2065
2075
  // src/commands.doctor.ts
2066
- import { existsSync as existsSync29 } from "node:fs";
2067
- import { join as join35 } from "node:path";
2076
+ import { existsSync as existsSync30 } from "node:fs";
2077
+ import { join as join36 } from "node:path";
2068
2078
 
2069
2079
  // src/commands.doctor.checks.repo.ts
2070
2080
  init_color();
2071
2081
  init_config();
2072
- import { existsSync as existsSync9, lstatSync as lstatSync6, statSync as statSync3 } from "node:fs";
2082
+ import { existsSync as existsSync9, lstatSync as lstatSync6, readdirSync as readdirSync2, statSync as statSync3 } from "node:fs";
2073
2083
  import { join as join11 } from "node:path";
2074
2084
 
2075
2085
  // src/commands.doctor.format.ts
@@ -2189,6 +2199,7 @@ function reasonForPartial(repoHome2, host) {
2189
2199
  }
2190
2200
 
2191
2201
  // src/commands.doctor.checks.repo.ts
2202
+ init_utils_json();
2192
2203
  function isOverrideActive() {
2193
2204
  return Boolean(process.env.NOMAD_REPO);
2194
2205
  }
@@ -2206,6 +2217,46 @@ function reportHostAndPaths(section2) {
2206
2217
  `${existsSync9(claude) ? green(okGlyph) : yellow(warnGlyph)} claude home: ${blue(claude)}`
2207
2218
  );
2208
2219
  }
2220
+ function pathMapHostKeys() {
2221
+ const mapPath = join11(repoHome(), "path-map.json");
2222
+ if (!existsSync9(mapPath)) return /* @__PURE__ */ new Set();
2223
+ let raw;
2224
+ try {
2225
+ raw = readJson(mapPath);
2226
+ } catch {
2227
+ return /* @__PURE__ */ new Set();
2228
+ }
2229
+ if (validatePathMapShape(raw) !== null) return /* @__PURE__ */ new Set();
2230
+ const keys = /* @__PURE__ */ new Set();
2231
+ for (const hosts of Object.values(raw.projects)) {
2232
+ for (const key of Object.keys(hosts)) keys.add(key);
2233
+ }
2234
+ return keys;
2235
+ }
2236
+ function hostOverrideLabels() {
2237
+ let entries;
2238
+ try {
2239
+ entries = readdirSync2(join11(repoHome(), "hosts"));
2240
+ } catch {
2241
+ return /* @__PURE__ */ new Set();
2242
+ }
2243
+ const labels = /* @__PURE__ */ new Set();
2244
+ for (const entry of entries) {
2245
+ if (entry.endsWith(".json")) labels.add(entry.slice(0, -".json".length));
2246
+ }
2247
+ return labels;
2248
+ }
2249
+ function reportHostKeyAlignment(section2) {
2250
+ if (process.env.NOMAD_HOST) return;
2251
+ const overrideLabels = hostOverrideLabels();
2252
+ const mapKeys = pathMapHostKeys();
2253
+ if (overrideLabels.has(HOST) || mapKeys.has(HOST)) return;
2254
+ if (overrideLabels.size === 0 && mapKeys.size === 0) return;
2255
+ addItem(
2256
+ section2,
2257
+ `${yellow(warnGlyph)} NOMAD_HOST unset: this repo configures other hosts, but the hostname key ${cyan(HOST)} matches no hosts/${HOST}.json or path-map entry; set NOMAD_HOST to the label this host should use so per-host settings and session sync line up`
2258
+ );
2259
+ }
2209
2260
  function reportRepoState(section2) {
2210
2261
  const repo = repoHome();
2211
2262
  const state = classifyRepoState(repo, HOST);
@@ -2301,7 +2352,7 @@ function reportDroppedNamesMigration(section2) {
2301
2352
  // src/commands.doctor.checks.settings.ts
2302
2353
  init_color();
2303
2354
  init_config();
2304
- import { existsSync as existsSync10, readdirSync as readdirSync2 } from "node:fs";
2355
+ import { existsSync as existsSync10, readdirSync as readdirSync3 } from "node:fs";
2305
2356
  import { join as join12 } from "node:path";
2306
2357
  function loadBaseSettings(section2) {
2307
2358
  const basePath = join12(repoHome(), "shared", "settings.base.json");
@@ -2347,7 +2398,7 @@ function reportHostOverrides(section2, base, settings) {
2347
2398
  );
2348
2399
  const hostsDir = join12(repo, "hosts");
2349
2400
  if (existsSync10(hostsDir)) {
2350
- const cands = readdirSync2(hostsDir).filter((f) => f.endsWith(".json"));
2401
+ const cands = readdirSync3(hostsDir).filter((f) => f.endsWith(".json"));
2351
2402
  if (cands.length > 0) addItem(section2, `${dim(infoGlyph)} candidates: ${cands.join(", ")}`);
2352
2403
  }
2353
2404
  process.exitCode = 1;
@@ -2362,7 +2413,7 @@ function reportHostOverrides(section2, base, settings) {
2362
2413
  // src/commands.doctor.checks.pathmap.ts
2363
2414
  init_color();
2364
2415
  init_config();
2365
- import { existsSync as existsSync11, readdirSync as readdirSync3 } from "node:fs";
2416
+ import { existsSync as existsSync11, readdirSync as readdirSync4 } from "node:fs";
2366
2417
  import { join as join13 } from "node:path";
2367
2418
  init_utils_json();
2368
2419
  function reportMappedProjects(section2, map) {
@@ -2377,7 +2428,7 @@ function reportUnmappedProjects(section2, map) {
2377
2428
  if (!existsSync11(localProjects)) return;
2378
2429
  let localDirs;
2379
2430
  try {
2380
- localDirs = readdirSync3(localProjects);
2431
+ localDirs = readdirSync4(localProjects);
2381
2432
  } catch {
2382
2433
  return;
2383
2434
  }
@@ -2391,6 +2442,18 @@ function reportUnmappedProjects(section2, map) {
2391
2442
  addChildItem(section2, dim(dir));
2392
2443
  }
2393
2444
  }
2445
+ function reportCurrentHostPathsMissing(section2, map) {
2446
+ for (const [name, hosts] of Object.entries(map.projects)) {
2447
+ const abspath = hosts[HOST];
2448
+ if (!abspath || abspath === "TBD") continue;
2449
+ if (!existsSync11(abspath)) {
2450
+ addItem(
2451
+ section2,
2452
+ `${yellow(warnGlyph)} path-map: ${name} local path missing on ${HOST}: ${blue(abspath)}`
2453
+ );
2454
+ }
2455
+ }
2456
+ }
2394
2457
  function reportPathCollisions(section2, map) {
2395
2458
  const seen = /* @__PURE__ */ new Map();
2396
2459
  let collisionCount = 0;
@@ -2429,6 +2492,7 @@ function reportPathMap(section2) {
2429
2492
  return;
2430
2493
  }
2431
2494
  reportMappedProjects(section2, map);
2495
+ reportCurrentHostPathsMissing(section2, map);
2432
2496
  reportUnmappedProjects(section2, map);
2433
2497
  reportPathCollisions(section2, map);
2434
2498
  }
@@ -2441,18 +2505,103 @@ function reportNeverSync(section2) {
2441
2505
  );
2442
2506
  }
2443
2507
 
2508
+ // src/commands.doctor.checks.skills.ts
2509
+ init_color();
2510
+ import { existsSync as existsSync12 } from "node:fs";
2511
+ import { join as join14 } from "node:path";
2512
+ init_config();
2513
+
2514
+ // src/extras-sync.diff.ts
2515
+ init_utils();
2516
+ import { execFileSync as execFileSync2 } from "node:child_process";
2517
+ function labelDiffLine(line) {
2518
+ const tab = line.indexOf(" ");
2519
+ if (tab === -1) return line;
2520
+ const status = line.slice(0, tab);
2521
+ const path = line.slice(tab + 1);
2522
+ if (status === "D") return `${path} (local only)`;
2523
+ if (status === "A") return `${path} (repo only)`;
2524
+ return path;
2525
+ }
2526
+ function parseDiffOutput(stdout) {
2527
+ return stdout.split("\n").filter((line) => line.length > 0).map(labelDiffLine);
2528
+ }
2529
+ function listDivergingFiles(a, b) {
2530
+ try {
2531
+ const stdout = execFileSync2("git", ["diff", "--no-index", "--name-status", a, b], {
2532
+ stdio: ["ignore", "pipe", "pipe"]
2533
+ }).toString();
2534
+ return parseDiffOutput(stdout);
2535
+ } catch (err) {
2536
+ const e = err;
2537
+ if (e.status === 1 && e.stdout !== void 0) {
2538
+ return parseDiffOutput(e.stdout.toString());
2539
+ }
2540
+ if (e.code === "ENOENT") {
2541
+ warn(`git not on PATH; divergence check skipped for ${a}`);
2542
+ return [];
2543
+ }
2544
+ warn(`divergence check failed for ${a}: ${e.message ?? String(err)}`);
2545
+ return [];
2546
+ }
2547
+ }
2548
+
2549
+ // src/commands.doctor.checks.skills.ts
2550
+ function stripSideIndicator(line) {
2551
+ if (line.endsWith(" (local only)")) return line.slice(0, -" (local only)".length);
2552
+ if (line.endsWith(" (repo only)")) return line.slice(0, -" (repo only)".length);
2553
+ return line;
2554
+ }
2555
+ function isGsdDiffLine(line, localBase, sharedBase) {
2556
+ const bare = stripSideIndicator(line);
2557
+ let relative6;
2558
+ if (bare.startsWith(localBase + "/")) {
2559
+ relative6 = bare.slice(localBase.length + 1);
2560
+ } else if (bare.startsWith(sharedBase + "/")) {
2561
+ relative6 = bare.slice(sharedBase.length + 1);
2562
+ } else {
2563
+ relative6 = bare;
2564
+ }
2565
+ return relative6.split("/")[0].startsWith(GSD_PREFIX);
2566
+ }
2567
+ function reportSkillsDivergence(section2) {
2568
+ const sharedSkills = join14(repoHome(), "shared", "skills");
2569
+ const localSkills = join14(claudeHome(), "skills");
2570
+ if (!existsSync12(sharedSkills)) {
2571
+ addItem(section2, `${dim(infoGlyph)} skills: no shared/skills/ to compare`);
2572
+ return;
2573
+ }
2574
+ if (!existsSync12(localSkills)) {
2575
+ addItem(section2, `${dim(infoGlyph)} skills: no local skills/ to compare`);
2576
+ return;
2577
+ }
2578
+ const diff = listDivergingFiles(localSkills, sharedSkills);
2579
+ const relevant = diff.filter((line) => !isGsdDiffLine(line, localSkills, sharedSkills));
2580
+ if (relevant.length === 0) {
2581
+ addItem(section2, `${green(okGlyph)} skills: in sync with shared/skills/`);
2582
+ return;
2583
+ }
2584
+ addItem(
2585
+ section2,
2586
+ `${yellow(warnGlyph)} skills: ${relevant.length} file(s) diverge from shared/skills/`
2587
+ );
2588
+ for (const f of relevant) {
2589
+ addChildItem(section2, f);
2590
+ }
2591
+ }
2592
+
2444
2593
  // src/commands.doctor.checks.git-state.ts
2445
2594
  init_color();
2446
2595
  init_config();
2447
- import { execFileSync as execFileSync4 } from "node:child_process";
2448
- import { existsSync as existsSync14 } from "node:fs";
2449
- import { join as join17, relative as relative2 } from "node:path";
2596
+ import { execFileSync as execFileSync5 } from "node:child_process";
2597
+ import { existsSync as existsSync15 } from "node:fs";
2598
+ import { join as join18, relative as relative2 } from "node:path";
2450
2599
  init_commands_pull_wedge();
2451
2600
  init_push_checks();
2452
2601
  init_utils();
2453
2602
  function reportGitleaksProbe(section2) {
2454
2603
  try {
2455
- execFileSync4("gitleaks", ["version"], { stdio: ["ignore", "pipe", "pipe"] });
2604
+ execFileSync5("gitleaks", ["version"], { stdio: ["ignore", "pipe", "pipe"] });
2456
2605
  return true;
2457
2606
  } catch (err) {
2458
2607
  if (err.code === "ENOENT") {
@@ -2469,8 +2618,8 @@ function reportGitleaksProbe(section2) {
2469
2618
  }
2470
2619
  function reportGitlinks(section2) {
2471
2620
  const repo = repoHome();
2472
- const sharedDir = join17(repo, "shared");
2473
- if (existsSync14(sharedDir)) {
2621
+ const sharedDir = join18(repo, "shared");
2622
+ if (existsSync15(sharedDir)) {
2474
2623
  const gitlinks = findGitlinks(sharedDir);
2475
2624
  for (const p of gitlinks) {
2476
2625
  const rel = relative2(repo, p);
@@ -2488,7 +2637,7 @@ function reportGitlinks(section2) {
2488
2637
  }
2489
2638
  function reportRemote(section2) {
2490
2639
  try {
2491
- const url = execFileSync4("git", ["remote", "get-url", "origin"], {
2640
+ const url = execFileSync5("git", ["remote", "get-url", "origin"], {
2492
2641
  cwd: repoHome(),
2493
2642
  stdio: ["ignore", "pipe", "pipe"]
2494
2643
  }).toString().trim();
@@ -2540,16 +2689,41 @@ function reportOrphanedAutostash(sec) {
2540
2689
  } catch {
2541
2690
  }
2542
2691
  }
2692
+ function reportGitIdentity(section2) {
2693
+ const repo = repoHome();
2694
+ const missing = [];
2695
+ for (const field of ["user.name", "user.email"]) {
2696
+ try {
2697
+ const val = execFileSync5("git", ["config", field], {
2698
+ cwd: repo,
2699
+ stdio: ["ignore", "pipe", "pipe"]
2700
+ }).toString().trim();
2701
+ if (!val) missing.push(field);
2702
+ } catch (err) {
2703
+ if (err.status === 1) missing.push(field);
2704
+ else return;
2705
+ }
2706
+ }
2707
+ if (missing.length > 0) {
2708
+ const hint = missing.map((f) => `git config ${f} "..."`).join(" / ");
2709
+ addItem(
2710
+ section2,
2711
+ `${yellow(warnGlyph)} git identity: ${missing.join(", ")} not set (run: ${hint})`
2712
+ );
2713
+ } else {
2714
+ addItem(section2, `${green(okGlyph)} git identity: user.name and user.email configured`);
2715
+ }
2716
+ }
2543
2717
 
2544
2718
  // src/commands.doctor.checks.backups.ts
2545
2719
  init_color();
2546
- import { existsSync as existsSync15, lstatSync as lstatSync7, readdirSync as readdirSync5 } from "node:fs";
2547
- import { join as join18 } from "node:path";
2720
+ import { existsSync as existsSync16, lstatSync as lstatSync7, readdirSync as readdirSync6 } from "node:fs";
2721
+ import { join as join19 } from "node:path";
2548
2722
  init_config();
2549
2723
  var TS_SHAPE2 = /^\d{8}-\d{6}(-\d+)?$/;
2550
2724
  function safeReaddir(dir) {
2551
2725
  try {
2552
- return readdirSync5(dir);
2726
+ return readdirSync6(dir);
2553
2727
  } catch {
2554
2728
  return [];
2555
2729
  }
@@ -2560,7 +2734,7 @@ var BYTES_PER_MB = 1024 * 1024;
2560
2734
  function dirSizeBytes(dir) {
2561
2735
  let bytes = 0;
2562
2736
  for (const entry of safeReaddir(dir)) {
2563
- const full = join18(dir, entry);
2737
+ const full = join19(dir, entry);
2564
2738
  const st = lstatSync7(full, { throwIfNoEntry: false });
2565
2739
  if (!st) continue;
2566
2740
  if (st.isSymbolicLink()) continue;
@@ -2571,11 +2745,11 @@ function dirSizeBytes(dir) {
2571
2745
  }
2572
2746
  function totalSizeMb(backupBase2, dirs) {
2573
2747
  let bytes = 0;
2574
- for (const name of dirs) bytes += dirSizeBytes(join18(backupBase2, name));
2748
+ for (const name of dirs) bytes += dirSizeBytes(join19(backupBase2, name));
2575
2749
  return bytes / BYTES_PER_MB;
2576
2750
  }
2577
2751
  function reportBackupsCheck(section2, backupBase2 = backupBase()) {
2578
- if (!existsSync15(backupBase2)) return;
2752
+ if (!existsSync16(backupBase2)) return;
2579
2753
  const dirs = safeReaddir(backupBase2).filter((n) => TS_SHAPE2.test(n));
2580
2754
  const count = dirs.length;
2581
2755
  const sizeMb = totalSizeMb(backupBase2, dirs);
@@ -2587,16 +2761,83 @@ function reportBackupsCheck(section2, backupBase2 = backupBase()) {
2587
2761
  }
2588
2762
  }
2589
2763
 
2764
+ // src/commands.doctor.check-remote.ts
2765
+ init_color();
2766
+ import { execFileSync as execFileSync6 } from "node:child_process";
2767
+ init_config();
2768
+ init_utils_json();
2769
+ var GIT_REMOTE_TIMEOUT_MS = 3e3;
2770
+ function readRemotePathMap(sec, repo) {
2771
+ let rawJson;
2772
+ try {
2773
+ rawJson = execFileSync6("git", ["show", "origin/main:path-map.json"], {
2774
+ cwd: repo,
2775
+ stdio: ["ignore", "pipe", "pipe"],
2776
+ timeout: GIT_REMOTE_TIMEOUT_MS
2777
+ }).toString();
2778
+ } catch {
2779
+ addItem(
2780
+ sec,
2781
+ `${yellow(warnGlyph)} remote check skipped (could not read path-map.json from origin/main)`
2782
+ );
2783
+ return false;
2784
+ }
2785
+ let map;
2786
+ try {
2787
+ map = JSON.parse(rawJson);
2788
+ } catch {
2789
+ addItem(sec, `${yellow(warnGlyph)} remote: path-map.json at origin/main is malformed JSON`);
2790
+ return false;
2791
+ }
2792
+ const shapeError = validatePathMapShape(map);
2793
+ if (shapeError !== null) {
2794
+ addItem(
2795
+ sec,
2796
+ `${yellow(warnGlyph)} remote: path-map.json at origin/main has invalid shape: ${shapeError}`
2797
+ );
2798
+ return false;
2799
+ }
2800
+ return true;
2801
+ }
2802
+ function reportCheckRemote(section2) {
2803
+ const repo = repoHome();
2804
+ let names;
2805
+ try {
2806
+ const out = execFileSync6("git", ["ls-tree", "--name-only", "origin/main"], {
2807
+ cwd: repo,
2808
+ stdio: ["ignore", "pipe", "pipe"],
2809
+ timeout: GIT_REMOTE_TIMEOUT_MS
2810
+ }).toString().trim();
2811
+ names = out.split("\n").filter(Boolean);
2812
+ } catch {
2813
+ addItem(
2814
+ section2,
2815
+ `${yellow(warnGlyph)} remote check skipped (origin/main unavailable or git error)`
2816
+ );
2817
+ return;
2818
+ }
2819
+ if (!names.includes("shared")) {
2820
+ addItem(section2, `${yellow(warnGlyph)} remote: shared/ not found in origin/main`);
2821
+ return;
2822
+ }
2823
+ if (!names.includes("path-map.json")) {
2824
+ addItem(section2, `${yellow(warnGlyph)} remote: path-map.json not found in origin/main`);
2825
+ return;
2826
+ }
2827
+ if (!readRemotePathMap(section2, repo)) return;
2828
+ addItem(section2, `${green(okGlyph)} remote: origin/main has shared/ and a valid path-map.json`);
2829
+ }
2830
+
2590
2831
  // src/commands.doctor.check-schema.ts
2591
2832
  init_color();
2592
- import { existsSync as existsSync16 } from "node:fs";
2593
- import { join as join19 } from "node:path";
2833
+ import { existsSync as existsSync17 } from "node:fs";
2834
+ import { join as join20 } from "node:path";
2594
2835
  init_config();
2595
2836
 
2596
2837
  // src/http-fetch.ts
2597
- import { execFileSync as execFileSync5 } from "node:child_process";
2838
+ import { execFileSync as execFileSync7 } from "node:child_process";
2598
2839
  var FETCH_TIMEOUT_MS = 3e3;
2599
- function fetchUrl(url, run = execFileSync5) {
2840
+ function fetchUrl(url, run = execFileSync7) {
2600
2841
  try {
2601
2842
  return run("curl", ["-fsSL", "-m", "3", url], {
2602
2843
  stdio: ["ignore", "pipe", "pipe"],
@@ -2627,8 +2868,8 @@ function fetchSchemaKeys() {
2627
2868
  }
2628
2869
  }
2629
2870
  function reportCheckSchema(section2) {
2630
- const settingsPath = join19(claudeHome(), "settings.json");
2631
- if (!existsSync16(settingsPath)) {
2871
+ const settingsPath = join20(claudeHome(), "settings.json");
2872
+ if (!existsSync17(settingsPath)) {
2632
2873
  addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json to check`);
2633
2874
  return;
2634
2875
  }
@@ -2657,19 +2898,19 @@ function reportCheckSchema(section2) {
2657
2898
  // src/commands.doctor.check-shared.ts
2658
2899
  init_color();
2659
2900
  import { randomBytes } from "node:crypto";
2660
- import { execFileSync as execFileSync7 } from "node:child_process";
2661
- import { existsSync as existsSync18, mkdirSync as mkdirSync5, readdirSync as readdirSync7, rmSync as rmSync8 } from "node:fs";
2901
+ import { execFileSync as execFileSync9 } from "node:child_process";
2902
+ import { existsSync as existsSync19, mkdirSync as mkdirSync5, readdirSync as readdirSync8, rmSync as rmSync8 } from "node:fs";
2662
2903
  import { homedir as homedir4 } from "node:os";
2663
- import { join as join23 } from "node:path";
2904
+ import { join as join24 } from "node:path";
2664
2905
 
2665
2906
  // src/commands.doctor.check-shared.scan.ts
2666
2907
  init_color();
2667
- import { join as join21 } from "node:path";
2908
+ import { join as join22 } from "node:path";
2668
2909
  init_config();
2669
2910
  init_push_gitleaks();
2670
2911
  function scrubPath(logical, sid, logicalToEncoded) {
2671
2912
  const encoded = logicalToEncoded.get(logical) ?? logical;
2672
- return join21(claudeHome(), "projects", encoded, `${sid}.jsonl`);
2913
+ return join22(claudeHome(), "projects", encoded, `${sid}.jsonl`);
2673
2914
  }
2674
2915
  function reportSessionFindings(section2, bySession) {
2675
2916
  for (const [sid, counts] of bySession) {
@@ -2757,8 +2998,8 @@ init_config();
2757
2998
 
2758
2999
  // src/remap.ts
2759
3000
  init_config_sharedDirs_guard();
2760
- import { cpSync as cpSync4, existsSync as existsSync17, mkdirSync as mkdirSync4, readdirSync as readdirSync6, renameSync as renameSync3, rmSync as rmSync7, statSync as statSync4 } from "node:fs";
2761
- import { join as join22, relative as relative3, sep as sep3 } from "node:path";
3001
+ 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";
3002
+ import { join as join23, relative as relative3, sep as sep3 } from "node:path";
2762
3003
 
2763
3004
  // src/extras-sync.guards.ts
2764
3005
  init_utils();
@@ -2819,15 +3060,15 @@ function remapPull(ts, opts = {}) {
2819
3060
  const wouldPull = [];
2820
3061
  const repo = repoHome();
2821
3062
  const claude = claudeHome();
2822
- const mapPath = join22(repo, "path-map.json");
2823
- const repoProjects = join22(repo, "shared", "projects");
2824
- if (!existsSync17(mapPath) || !existsSync17(repoProjects)) {
3063
+ const mapPath = join23(repo, "path-map.json");
3064
+ const repoProjects = join23(repo, "shared", "projects");
3065
+ if (!existsSync18(mapPath) || !existsSync18(repoProjects)) {
2825
3066
  const text = "no path-map or repo projects dir; skipping session remap";
2826
3067
  emitPreview(opts.onPreview, { kind: "note", text }, text);
2827
3068
  return { unmapped: 0, pulled, wouldPull };
2828
3069
  }
2829
3070
  const map = readPathMap(mapPath);
2830
- const localProjects = join22(claude, "projects");
3071
+ const localProjects = join23(claude, "projects");
2831
3072
  if (!dryRun) mkdirSync4(localProjects, { recursive: true });
2832
3073
  for (const [logical, hosts] of Object.entries(map.projects)) {
2833
3074
  assertSafeLogical(logical);
@@ -2837,9 +3078,9 @@ function remapPull(ts, opts = {}) {
2837
3078
  continue;
2838
3079
  }
2839
3080
  assertSafeLocalRoot(localPath, logical);
2840
- const src = join22(repoProjects, logical);
2841
- if (!existsSync17(src)) continue;
2842
- const dst = join22(localProjects, encodePath(localPath));
3081
+ const src = join23(repoProjects, logical);
3082
+ if (!existsSync18(src)) continue;
3083
+ const dst = join23(localProjects, encodePath(localPath));
2843
3084
  if (dryRun) {
2844
3085
  wouldPull.push(logical);
2845
3086
  emitPreview(
@@ -2886,31 +3127,31 @@ function remapPush(ts, opts = {}) {
2886
3127
  const wouldPush = [];
2887
3128
  const repo = repoHome();
2888
3129
  const claude = claudeHome();
2889
- const mapPath = join22(repo, "path-map.json");
2890
- if (!existsSync17(mapPath)) {
3130
+ const mapPath = join23(repo, "path-map.json");
3131
+ if (!existsSync18(mapPath)) {
2891
3132
  log("no path-map.json; skipping session export");
2892
3133
  return { unmapped: 0, collisions: 0, pushed, wouldPush };
2893
3134
  }
2894
3135
  const map = readPathMap(mapPath);
2895
- const localProjects = join22(claude, "projects");
2896
- const repoProjects = join22(repo, "shared", "projects");
3136
+ const localProjects = join23(claude, "projects");
3137
+ const repoProjects = join23(repo, "shared", "projects");
2897
3138
  const reverse = buildReverseMap(map);
2898
- if (!existsSync17(localProjects)) return { unmapped, collisions: 0, pushed, wouldPush };
3139
+ if (!existsSync18(localProjects)) return { unmapped, collisions: 0, pushed, wouldPush };
2899
3140
  if (!dryRun) mkdirSync4(repoProjects, { recursive: true });
2900
- for (const dir of readdirSync6(localProjects)) {
3141
+ for (const dir of readdirSync7(localProjects)) {
2901
3142
  if (dir.endsWith(TMP_SUFFIX)) continue;
2902
3143
  const logical = reverse.get(dir);
2903
3144
  if (!logical) {
2904
3145
  unmapped++;
2905
3146
  continue;
2906
3147
  }
2907
- const repoDst = join22(repoProjects, logical);
3148
+ const repoDst = join23(repoProjects, logical);
2908
3149
  if (dryRun) {
2909
3150
  wouldPush.push(logical);
2910
3151
  continue;
2911
3152
  }
2912
3153
  backupRepoWrite(repoDst, ts, repo);
2913
- copyDirJsonlOnly(join22(localProjects, dir), repoDst);
3154
+ copyDirJsonlOnly(join23(localProjects, dir), repoDst);
2914
3155
  pushed.push(logical);
2915
3156
  }
2916
3157
  return { unmapped, collisions: 0, pushed, wouldPush };
@@ -2923,8 +3164,8 @@ function buildScanTree(tmpRoot) {
2923
3164
  const logicalToEncoded = /* @__PURE__ */ new Map();
2924
3165
  let staged = 0;
2925
3166
  const repo = repoHome();
2926
- const mapPath = join23(repo, "path-map.json");
2927
- if (!existsSync18(mapPath)) return { logicalToEncoded, staged, malformed: false };
3167
+ const mapPath = join24(repo, "path-map.json");
3168
+ if (!existsSync19(mapPath)) return { logicalToEncoded, staged, malformed: false };
2928
3169
  let map;
2929
3170
  try {
2930
3171
  map = readJson(mapPath);
@@ -2941,12 +3182,12 @@ function buildScanTree(tmpRoot) {
2941
3182
  if (!p || p === "TBD") continue;
2942
3183
  reverse.set(encodePath(p), logical);
2943
3184
  }
2944
- const localProjects = join23(claudeHome(), "projects");
2945
- if (!existsSync18(localProjects)) return { logicalToEncoded, staged, malformed: false };
2946
- for (const dir of readdirSync7(localProjects)) {
3185
+ const localProjects = join24(claudeHome(), "projects");
3186
+ if (!existsSync19(localProjects)) return { logicalToEncoded, staged, malformed: false };
3187
+ for (const dir of readdirSync8(localProjects)) {
2947
3188
  const logical = reverse.get(dir);
2948
3189
  if (!logical) continue;
2949
- copyDirJsonlOnly(join23(localProjects, dir), join23(tmpRoot, "shared", "projects", logical));
3190
+ copyDirJsonlOnly(join24(localProjects, dir), join24(tmpRoot, "shared", "projects", logical));
2950
3191
  logicalToEncoded.set(logical, dir);
2951
3192
  staged++;
2952
3193
  }
@@ -2954,7 +3195,7 @@ function buildScanTree(tmpRoot) {
2954
3195
  }
2955
3196
  function probeGitleaksForScan() {
2956
3197
  try {
2957
- execFileSync7("gitleaks", ["version"], { stdio: ["ignore", "pipe", "pipe"] });
3198
+ execFileSync9("gitleaks", ["version"], { stdio: ["ignore", "pipe", "pipe"] });
2958
3199
  return "ok";
2959
3200
  } catch (err) {
2960
3201
  if (err.code === "ENOENT") return "missing";
@@ -2977,11 +3218,11 @@ function ensureGitleaksReady(section2, gitleaksReady) {
2977
3218
  }
2978
3219
  function reportCheckShared(section2, gitleaksReady) {
2979
3220
  if (!ensureGitleaksReady(section2, gitleaksReady)) return;
2980
- const cacheDir = join23(homedir4(), ".cache", "claude-nomad");
3221
+ const cacheDir = join24(homedir4(), ".cache", "claude-nomad");
2981
3222
  mkdirSync5(cacheDir, { recursive: true });
2982
3223
  const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes(4).toString("hex")}`;
2983
- const reportPath = join23(cacheDir, `check-shared-${stamp}.json`);
2984
- const tmpRoot = join23(cacheDir, `check-shared-tree-${stamp}`);
3224
+ const reportPath = join24(cacheDir, `check-shared-${stamp}.json`);
3225
+ const tmpRoot = join24(cacheDir, `check-shared-tree-${stamp}`);
2985
3226
  try {
2986
3227
  const { logicalToEncoded, staged, malformed } = buildScanTree(tmpRoot);
2987
3228
  if (malformed) {
@@ -3002,8 +3243,8 @@ function reportCheckShared(section2, gitleaksReady) {
3002
3243
 
3003
3244
  // src/commands.doctor.checks.hooks.scope.ts
3004
3245
  init_color();
3005
- import { existsSync as existsSync19, readFileSync as readFileSync6, readdirSync as readdirSync8, realpathSync as realpathSync2 } from "node:fs";
3006
- import { dirname as dirname3, extname, join as join24 } from "node:path";
3246
+ import { existsSync as existsSync20, readFileSync as readFileSync6, readdirSync as readdirSync9, realpathSync as realpathSync2 } from "node:fs";
3247
+ import { dirname as dirname3, extname, join as join25 } from "node:path";
3007
3248
  init_config();
3008
3249
  function typeFromPackageJson(pkgPath) {
3009
3250
  try {
@@ -3025,8 +3266,8 @@ function effectiveType(hookPath) {
3025
3266
  }
3026
3267
  let dir = dirname3(real);
3027
3268
  for (; ; ) {
3028
- const pkg = join24(dir, "package.json");
3029
- if (existsSync19(pkg)) return typeFromPackageJson(pkg);
3269
+ const pkg = join25(dir, "package.json");
3270
+ if (existsSync20(pkg)) return typeFromPackageJson(pkg);
3030
3271
  const parent = dirname3(dir);
3031
3272
  if (parent === dir) return "cjs";
3032
3273
  dir = parent;
@@ -3055,7 +3296,7 @@ function remedy(family) {
3055
3296
  }
3056
3297
  function safeReaddir2(dir) {
3057
3298
  try {
3058
- return readdirSync8(dir);
3299
+ return readdirSync9(dir);
3059
3300
  } catch {
3060
3301
  return [];
3061
3302
  }
@@ -3068,15 +3309,15 @@ function safeRead(path) {
3068
3309
  }
3069
3310
  }
3070
3311
  function reportHookScopeCheck(section2) {
3071
- const hooksDir = join24(claudeHome(), "hooks");
3072
- if (!existsSync19(hooksDir)) {
3312
+ const hooksDir = join25(claudeHome(), "hooks");
3313
+ if (!existsSync20(hooksDir)) {
3073
3314
  addItem(section2, `${dim(infoGlyph)} no ~/.claude/hooks; skipping module-scope check`);
3074
3315
  return;
3075
3316
  }
3076
3317
  let anyWarn = false;
3077
3318
  for (const name of safeReaddir2(hooksDir)) {
3078
3319
  if (extname(name) !== ".js") continue;
3079
- const abs = join24(hooksDir, name);
3320
+ const abs = join25(hooksDir, name);
3080
3321
  const eff = effectiveType(abs);
3081
3322
  if (eff === null) continue;
3082
3323
  const src = safeRead(abs);
@@ -3096,8 +3337,8 @@ function reportHookScopeCheck(section2) {
3096
3337
 
3097
3338
  // src/commands.doctor.checks.hooks.ts
3098
3339
  init_color();
3099
- import { existsSync as existsSync20 } from "node:fs";
3100
- import { join as join25 } from "node:path";
3340
+ import { existsSync as existsSync21 } from "node:fs";
3341
+ import { join as join26 } from "node:path";
3101
3342
  init_config();
3102
3343
  function expandHome(token) {
3103
3344
  const h2 = home();
@@ -3136,7 +3377,7 @@ function checkEventGroups(section2, event, groups) {
3136
3377
  for (const group of groups) {
3137
3378
  for (const cmd of commandsFromGroup(group)) {
3138
3379
  for (const resolved of claudePathsIn(cmd)) {
3139
- if (existsSync20(resolved)) continue;
3380
+ if (existsSync21(resolved)) continue;
3140
3381
  addItem(
3141
3382
  section2,
3142
3383
  `${red(failGlyph)} hooks/${event}: command target missing: ${resolved} (run nomad pull)`
@@ -3149,8 +3390,8 @@ function checkEventGroups(section2, event, groups) {
3149
3390
  return anyFail;
3150
3391
  }
3151
3392
  function reportHooksTargetCheck(section2) {
3152
- const settingsPath = join25(claudeHome(), "settings.json");
3153
- if (!existsSync20(settingsPath)) {
3393
+ const settingsPath = join26(claudeHome(), "settings.json");
3394
+ if (!existsSync21(settingsPath)) {
3154
3395
  addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json; skipping hook target check`);
3155
3396
  return;
3156
3397
  }
@@ -3173,12 +3414,12 @@ function reportHooksTargetCheck(section2) {
3173
3414
 
3174
3415
  // src/commands.doctor.checks.hooks.preserve-symlinks.ts
3175
3416
  init_color();
3176
- import { existsSync as existsSync22, readFileSync as readFileSync7 } from "node:fs";
3177
- import { join as join27 } from "node:path";
3417
+ import { existsSync as existsSync23, readFileSync as readFileSync7 } from "node:fs";
3418
+ import { join as join28 } from "node:path";
3178
3419
 
3179
3420
  // src/commands.doctor.checks.hooks.preserve-symlinks.probe.ts
3180
- import { closeSync as closeSync3, existsSync as existsSync21, openSync as openSync3, readSync, realpathSync as realpathSync3 } from "node:fs";
3181
- import { dirname as dirname4, join as join26, resolve as resolve2 } from "node:path";
3421
+ import { closeSync as closeSync3, existsSync as existsSync22, openSync as openSync3, readSync, realpathSync as realpathSync3 } from "node:fs";
3422
+ import { dirname as dirname4, join as join27, resolve as resolve2 } from "node:path";
3182
3423
  function suppressedRanges(src) {
3183
3424
  const ranges = [];
3184
3425
  const re = /\/\*[\s\S]*?\*\/|\/\/[^\n]*|'[^']*'|"[^"]*"|`[^`]*`/g;
@@ -3210,12 +3451,12 @@ function topRelativeSpecifiers(src) {
3210
3451
  }
3211
3452
  function specifierIsMissing(specifier, baseDir) {
3212
3453
  const base = resolve2(baseDir, specifier);
3213
- if (existsSync21(base)) return false;
3454
+ if (existsSync22(base)) return false;
3214
3455
  for (const ext of [".js", ".cjs", ".mjs"]) {
3215
- if (existsSync21(base + ext)) return false;
3456
+ if (existsSync22(base + ext)) return false;
3216
3457
  }
3217
3458
  for (const idx of ["index.js", "index.cjs", "index.mjs"]) {
3218
- if (existsSync21(join26(base, idx))) return false;
3459
+ if (existsSync22(join27(base, idx))) return false;
3219
3460
  }
3220
3461
  return true;
3221
3462
  }
@@ -3270,8 +3511,8 @@ function commandTokens(command) {
3270
3511
  return tokens;
3271
3512
  }
3272
3513
  function readPathMapSafe() {
3273
- const mapPath = join27(repoHome(), "path-map.json");
3274
- if (!existsSync22(mapPath)) return { projects: {} };
3514
+ const mapPath = join28(repoHome(), "path-map.json");
3515
+ if (!existsSync23(mapPath)) return { projects: {} };
3275
3516
  try {
3276
3517
  return JSON.parse(readFileSync7(mapPath, "utf8"));
3277
3518
  } catch {
@@ -3344,8 +3585,8 @@ function checkEventForPreserveSymlinks(section2, event, groups, sharedLinkNames)
3344
3585
  return anyWarn;
3345
3586
  }
3346
3587
  function reportPreserveSymlinksCheck(section2) {
3347
- const settingsPath = join27(claudeHome(), "settings.json");
3348
- if (!existsSync22(settingsPath)) {
3588
+ const settingsPath = join28(claudeHome(), "settings.json");
3589
+ if (!existsSync23(settingsPath)) {
3349
3590
  addItem(
3350
3591
  section2,
3351
3592
  `${dim(infoGlyph)} no ~/.claude/settings.json; skipping preserve-symlinks-main check`
@@ -3378,8 +3619,8 @@ function reportPreserveSymlinksCheck(section2) {
3378
3619
 
3379
3620
  // src/commands.doctor.checks.settings-drift.ts
3380
3621
  init_color();
3381
- import { existsSync as existsSync23, readFileSync as readFileSync8 } from "node:fs";
3382
- import { join as join28 } from "node:path";
3622
+ import { existsSync as existsSync24, readFileSync as readFileSync8 } from "node:fs";
3623
+ import { join as join29 } from "node:path";
3383
3624
  init_config();
3384
3625
  init_utils_json();
3385
3626
  function diffMergedSettings(merged, settings) {
@@ -3397,7 +3638,7 @@ function tryReadJson(filePath) {
3397
3638
  }
3398
3639
  }
3399
3640
  function reportHooksBaseSelfCleanNote(section2) {
3400
- const basePath = join28(repoHome(), "shared", "settings.base.json");
3641
+ const basePath = join29(repoHome(), "shared", "settings.base.json");
3401
3642
  const base = tryReadJson(basePath);
3402
3643
  if (base === null) return;
3403
3644
  if (!baseHasGsdHookEntries(base)) return;
@@ -3410,14 +3651,14 @@ function reportSettingsDriftCheck(section2) {
3410
3651
  const claude = claudeHome();
3411
3652
  const repo = repoHome();
3412
3653
  const host = HOST;
3413
- const settingsPath = join28(claude, "settings.json");
3414
- const basePath = join28(repo, "shared", "settings.base.json");
3415
- const hostPath = join28(repo, "hosts", `${host}.json`);
3416
- if (!existsSync23(settingsPath)) {
3654
+ const settingsPath = join29(claude, "settings.json");
3655
+ const basePath = join29(repo, "shared", "settings.base.json");
3656
+ const hostPath = join29(repo, "hosts", `${host}.json`);
3657
+ if (!existsSync24(settingsPath)) {
3417
3658
  addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json; skipping merge-drift check`);
3418
3659
  return;
3419
3660
  }
3420
- if (!existsSync23(basePath)) {
3661
+ if (!existsSync24(basePath)) {
3421
3662
  addItem(
3422
3663
  section2,
3423
3664
  `${dim(infoGlyph)} shared/settings.base.json missing; skipping merge-drift check`
@@ -3436,7 +3677,7 @@ function reportSettingsDriftCheck(section2) {
3436
3677
  if (settings === null) {
3437
3678
  return;
3438
3679
  }
3439
- const hostExists = existsSync23(hostPath);
3680
+ const hostExists = existsSync24(hostPath);
3440
3681
  const hostObj = hostExists ? tryReadJson(hostPath) : null;
3441
3682
  if (hostExists && hostObj === null) {
3442
3683
  addItem(
@@ -3594,14 +3835,14 @@ function reportNodeEngineCheck(section2) {
3594
3835
 
3595
3836
  // src/spinner.ts
3596
3837
  init_color();
3597
- import { existsSync as existsSync27 } from "node:fs";
3838
+ import { existsSync as existsSync28 } from "node:fs";
3598
3839
  import { fileURLToPath as fileURLToPath4 } from "node:url";
3599
3840
  import { Worker } from "node:worker_threads";
3600
3841
 
3601
3842
  // src/commands.push.recovery.ts
3602
3843
  init_config();
3603
3844
  import { readFileSync as readFileSync13, rmSync as rmSync10, writeFileSync as writeFileSync5 } from "node:fs";
3604
- import { join as join33 } from "node:path";
3845
+ import { join as join34 } from "node:path";
3605
3846
  import { createInterface as createInterface2 } from "node:readline/promises";
3606
3847
 
3607
3848
  // src/commands.push.recovery.actions.ts
@@ -3612,25 +3853,25 @@ import { isAbsolute as isAbsolute2, resolve as resolve3, sep as sep5 } from "nod
3612
3853
  // src/commands.push.recovery.redact.ts
3613
3854
  init_config();
3614
3855
  init_config_sharedDirs_guard();
3615
- import { cpSync as cpSync5, existsSync as existsSync26, mkdirSync as mkdirSync6, statSync as statSync7 } from "node:fs";
3616
- import { dirname as dirname6, join as join31, sep as sep4 } from "node:path";
3856
+ import { cpSync as cpSync5, existsSync as existsSync27, mkdirSync as mkdirSync6, statSync as statSync7 } from "node:fs";
3857
+ import { dirname as dirname6, join as join32, sep as sep4 } from "node:path";
3617
3858
 
3618
3859
  // src/commands.redact.ts
3619
3860
  init_config();
3620
- import { existsSync as existsSync25, statSync as statSync6 } from "node:fs";
3621
- import { dirname as dirname5, join as join30 } from "node:path";
3861
+ import { existsSync as existsSync26, statSync as statSync6 } from "node:fs";
3862
+ import { dirname as dirname5, join as join31 } from "node:path";
3622
3863
 
3623
3864
  // src/commands.redact.subtree.ts
3624
- import { existsSync as existsSync24, lstatSync as lstatSync8, readFileSync as readFileSync11, readdirSync as readdirSync9, statSync as statSync5, writeFileSync as writeFileSync4 } from "node:fs";
3625
- import { join as join29 } from "node:path";
3865
+ import { existsSync as existsSync25, lstatSync as lstatSync8, readFileSync as readFileSync11, readdirSync as readdirSync10, statSync as statSync5, writeFileSync as writeFileSync4 } from "node:fs";
3866
+ import { join as join30 } from "node:path";
3626
3867
  init_utils_fs();
3627
3868
  init_utils();
3628
3869
  function collectFiles(dir, out) {
3629
- if (!existsSync24(dir)) return;
3870
+ if (!existsSync25(dir)) return;
3630
3871
  const st = lstatSync8(dir);
3631
3872
  if (!st.isDirectory()) return;
3632
- for (const entry of readdirSync9(dir)) {
3633
- const abs = join29(dir, entry);
3873
+ for (const entry of readdirSync10(dir)) {
3874
+ const abs = join30(dir, entry);
3634
3875
  const lst = lstatSync8(abs);
3635
3876
  if (lst.isSymbolicLink()) continue;
3636
3877
  if (lst.isDirectory()) {
@@ -3682,10 +3923,10 @@ function applySubtreeRedactions(mainPath, mainFindings, subtreeFiles, rule, ts,
3682
3923
 
3683
3924
  // src/commands.pushed-history.ts
3684
3925
  init_utils();
3685
- import { execFileSync as execFileSync8 } from "node:child_process";
3926
+ import { execFileSync as execFileSync10 } from "node:child_process";
3686
3927
  function pushedRef(repo) {
3687
3928
  try {
3688
- const ref = execFileSync8("git", ["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"], {
3929
+ const ref = execFileSync10("git", ["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"], {
3689
3930
  cwd: repo,
3690
3931
  stdio: ["ignore", "pipe", "ignore"]
3691
3932
  }).toString().trim();
@@ -3698,7 +3939,7 @@ function sessionInPushedHistory(id, repo) {
3698
3939
  const ref = pushedRef(repo);
3699
3940
  if (ref === null) return false;
3700
3941
  try {
3701
- const out = execFileSync8(
3942
+ const out = execFileSync10(
3702
3943
  "git",
3703
3944
  [
3704
3945
  "log",
@@ -3735,15 +3976,15 @@ init_utils_json();
3735
3976
  init_utils();
3736
3977
  function resolveLiveTranscript(id) {
3737
3978
  try {
3738
- const mapPath = join30(repoHome(), "path-map.json");
3739
- if (!existsSync25(mapPath)) return null;
3979
+ const mapPath = join31(repoHome(), "path-map.json");
3980
+ if (!existsSync26(mapPath)) return null;
3740
3981
  const projects = readJson(mapPath).projects;
3741
3982
  const claude = claudeHome();
3742
3983
  for (const hostMap of Object.values(projects)) {
3743
3984
  const abs = hostMap[HOST];
3744
3985
  if (abs === void 0) continue;
3745
- const live = join30(claude, "projects", encodePath(abs), `${id}.jsonl`);
3746
- if (existsSync25(live)) return live;
3986
+ const live = join31(claude, "projects", encodePath(abs), `${id}.jsonl`);
3987
+ if (existsSync26(live)) return live;
3747
3988
  }
3748
3989
  return null;
3749
3990
  } catch {
@@ -3763,17 +4004,17 @@ function cmdRedact(opts, nowMs = Date.now, scan = scanFile) {
3763
4004
  }
3764
4005
  const repo = repoHome();
3765
4006
  const backup = backupBase();
3766
- if (!existsSync25(repo)) die(`repo not cloned at ${repo}`);
4007
+ if (!existsSync26(repo)) die(`repo not cloned at ${repo}`);
3767
4008
  const handle = acquireLock("redact");
3768
4009
  if (handle === null) process.exit(0);
3769
4010
  try {
3770
4011
  const localPath = resolveLiveTranscript(id);
3771
- if (localPath === null || !existsSync25(localPath)) {
4012
+ if (localPath === null || !existsSync26(localPath)) {
3772
4013
  fail(`could not resolve local transcript for session ${id} on this host`);
3773
4014
  process.exitCode = 1;
3774
4015
  return;
3775
4016
  }
3776
- const sessionDir = join30(dirname5(localPath), id);
4017
+ const sessionDir = join31(dirname5(localPath), id);
3777
4018
  const subtreeFiles = listSubtreeFiles(sessionDir);
3778
4019
  const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync6(p).mtimeMs);
3779
4020
  if (isRecentlyModified(subtreeMtime, nowMs())) {
@@ -3889,8 +4130,8 @@ function resolveStagedDir(localPath, map, claude, repo) {
3889
4130
  assertSafeLogical(logical);
3890
4131
  const abs = hostMap[HOST];
3891
4132
  if (abs === void 0) continue;
3892
- if (localPath.startsWith(join31(claude, "projects", encodePath(abs)) + sep4)) {
3893
- return join31(repo, "shared", "projects", logical);
4133
+ if (localPath.startsWith(join32(claude, "projects", encodePath(abs)) + sep4)) {
4134
+ return join32(repo, "shared", "projects", logical);
3894
4135
  }
3895
4136
  }
3896
4137
  return null;
@@ -3900,7 +4141,7 @@ function preflightRedactable(f, map, nowMs) {
3900
4141
  if (sid === null) return "a finding has no resolvable session id (not a session transcript)";
3901
4142
  const localPath = resolveLiveTranscript(sid);
3902
4143
  if (localPath === null) return `session ${sid}: local transcript not found`;
3903
- const sessionDir = join31(dirname6(localPath), sid);
4144
+ const sessionDir = join32(dirname6(localPath), sid);
3904
4145
  const subtreeFiles = listSubtreeFiles(sessionDir);
3905
4146
  const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync7(p).mtimeMs);
3906
4147
  if (isRecentlyModified(subtreeMtime, nowMs())) {
@@ -3930,7 +4171,7 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
3930
4171
  `could not locate the local transcript for session ${sid}; choose Skip or Drop session.`
3931
4172
  );
3932
4173
  }
3933
- const sessionDir = join31(dirname6(localPath), sid);
4174
+ const sessionDir = join32(dirname6(localPath), sid);
3934
4175
  const subtreeFiles = listSubtreeFiles(sessionDir);
3935
4176
  const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync7(p).mtimeMs);
3936
4177
  if (isRecentlyModified(subtreeMtime, nowMs())) {
@@ -3964,9 +4205,9 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
3964
4205
  );
3965
4206
  }
3966
4207
  mkdirSync6(stagedProjectDir, { recursive: true });
3967
- cpSync5(localPath, join31(stagedProjectDir, `${sid}.jsonl`), { force: true });
3968
- if (existsSync26(sessionDir)) {
3969
- cpSync5(sessionDir, join31(stagedProjectDir, sid), { force: true, recursive: true });
4208
+ cpSync5(localPath, join32(stagedProjectDir, `${sid}.jsonl`), { force: true });
4209
+ if (existsSync27(sessionDir)) {
4210
+ cpSync5(sessionDir, join32(stagedProjectDir, sid), { force: true, recursive: true });
3970
4211
  }
3971
4212
  return true;
3972
4213
  }
@@ -3974,14 +4215,14 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
3974
4215
  // src/commands.push.recovery.drop.ts
3975
4216
  init_config();
3976
4217
  import { rmSync as rmSync9 } from "node:fs";
3977
- import { join as join32 } from "node:path";
4218
+ import { join as join33 } from "node:path";
3978
4219
  function dropSessionFromStaged(sid, map) {
3979
4220
  const logicals = Object.keys(map.projects);
3980
4221
  if (logicals.length === 0) return false;
3981
4222
  const repo = repoHome();
3982
4223
  for (const logical of logicals) {
3983
- const jsonl = join32(repo, "shared", "projects", logical, `${sid}.jsonl`);
3984
- const dir = join32(repo, "shared", "projects", logical, sid);
4224
+ const jsonl = join33(repo, "shared", "projects", logical, `${sid}.jsonl`);
4225
+ const dir = join33(repo, "shared", "projects", logical, sid);
3985
4226
  rmSync9(jsonl, { force: true });
3986
4227
  rmSync9(dir, { recursive: true, force: true });
3987
4228
  }
@@ -4138,7 +4379,7 @@ function applyThenRescan(scanVerdict, repoHome2) {
4138
4379
  return next;
4139
4380
  }
4140
4381
  function allowThenRescan(append, scanVerdict, repoHome2) {
4141
- const ignPath = join33(repoHome2, ".gitleaksignore");
4382
+ const ignPath = join34(repoHome2, ".gitleaksignore");
4142
4383
  let before;
4143
4384
  try {
4144
4385
  before = readFileSync13(ignPath, "utf8");
@@ -4237,7 +4478,7 @@ function writeAnimatedDone(out, label, ms, useTTY) {
4237
4478
  `);
4238
4479
  }
4239
4480
  function resolveWorkerPath(deps = {}) {
4240
- const check = deps.existsSyncFn ?? existsSync27;
4481
+ const check = deps.existsSyncFn ?? existsSync28;
4241
4482
  const base = deps.baseUrl ?? import.meta.url;
4242
4483
  const mjs = fileURLToPath4(new URL("./nomad.worker.mjs", base));
4243
4484
  if (check(mjs)) return mjs;
@@ -4303,9 +4544,9 @@ function withSpinner(label, fn, deps) {
4303
4544
 
4304
4545
  // src/commands.doctor.gitleaks-version.ts
4305
4546
  init_color();
4306
- import { execFileSync as execFileSync9 } from "node:child_process";
4307
- import { existsSync as existsSync28 } from "node:fs";
4308
- import { join as join34 } from "node:path";
4547
+ import { execFileSync as execFileSync11 } from "node:child_process";
4548
+ import { existsSync as existsSync29 } from "node:fs";
4549
+ import { join as join35 } from "node:path";
4309
4550
  init_config();
4310
4551
  var SEMVER_MAJOR_MINOR = /^(\d+)\.(\d+)\.\d+$/;
4311
4552
  var GITLEAKS_TIMEOUT_MS = 5e3;
@@ -4314,7 +4555,7 @@ function majorMinorOf(value) {
4314
4555
  return m === null ? null : [m[1], m[2]];
4315
4556
  }
4316
4557
  function readGitleaksVersion(run, tomlExists) {
4317
- const tomlPath = join34(repoHome(), ".gitleaks.toml");
4558
+ const tomlPath = join35(repoHome(), ".gitleaks.toml");
4318
4559
  const args = ["version"];
4319
4560
  if (tomlExists(tomlPath)) args.push("--config", tomlPath);
4320
4561
  try {
@@ -4326,7 +4567,7 @@ function readGitleaksVersion(run, tomlExists) {
4326
4567
  return null;
4327
4568
  }
4328
4569
  }
4329
- function reportGitleaksVersionCheck(section2, run = execFileSync9, tomlExists = existsSync28) {
4570
+ function reportGitleaksVersionCheck(section2, run = execFileSync11, tomlExists = existsSync29) {
4330
4571
  const raw = readGitleaksVersion(run, tomlExists);
4331
4572
  if (raw === null) return;
4332
4573
  const local = majorMinorOf(raw);
@@ -4346,7 +4587,7 @@ function reportGitleaksVersionCheck(section2, run = execFileSync9, tomlExists =
4346
4587
 
4347
4588
  // src/commands.doctor.checks.deps.ts
4348
4589
  init_color();
4349
- import { execFileSync as execFileSync10 } from "node:child_process";
4590
+ import { execFileSync as execFileSync12 } from "node:child_process";
4350
4591
  var VERSION_TOKEN = /(\d{1,9}\.\d{1,9}\.\d{1,9})/;
4351
4592
  var PROBE_TIMEOUT_MS = 3e3;
4352
4593
  var FETCHER_BASE = "HTTP fetcher";
@@ -4383,14 +4624,14 @@ function reportFetcherRow(section2, run) {
4383
4624
  );
4384
4625
  }
4385
4626
  }
4386
- function reportOptionalDeps(section2, run = execFileSync10) {
4627
+ function reportOptionalDeps(section2, run = execFileSync12) {
4387
4628
  const gh = probeOptionalDep("gh", run);
4388
4629
  if (gh.status === "present") {
4389
4630
  addItem(section2, `${green(okGlyph)} gh: ${gh.version ?? "present"}`);
4390
4631
  } else {
4391
4632
  addItem(
4392
4633
  section2,
4393
- `${yellow(warnGlyph)} gh: not installed (optional; needed for nomad init Actions auto-disable + the Actions-drift check)`
4634
+ `${yellow(warnGlyph)} gh: not installed (optional; nomad init needs it to create the sync repo and auto-disable Actions, and the Actions-drift check uses it)`
4394
4635
  );
4395
4636
  }
4396
4637
  reportFetcherRow(section2, run);
@@ -4398,11 +4639,11 @@ function reportOptionalDeps(section2, run = execFileSync10) {
4398
4639
 
4399
4640
  // src/commands.doctor.actions-drift.ts
4400
4641
  init_color();
4401
- import { execFileSync as execFileSync12 } from "node:child_process";
4642
+ import { execFileSync as execFileSync14 } from "node:child_process";
4402
4643
  init_config();
4403
4644
 
4404
4645
  // src/gh-actions.ts
4405
- import { execFileSync as execFileSync11 } from "node:child_process";
4646
+ import { execFileSync as execFileSync13 } from "node:child_process";
4406
4647
  var GH_TIMEOUT_MS = 5e3;
4407
4648
  function parseGitHubRemote(remoteUrl) {
4408
4649
  const normalized = remoteUrl.trim().replace(/\/$/, "");
@@ -4410,7 +4651,7 @@ function parseGitHubRemote(remoteUrl) {
4410
4651
  if (m === null) return null;
4411
4652
  return { owner: m[1], repo: m[2] };
4412
4653
  }
4413
- function ghAuthStatus(run = execFileSync11) {
4654
+ function ghAuthStatus(run = execFileSync13) {
4414
4655
  try {
4415
4656
  run("gh", ["auth", "status"], {
4416
4657
  stdio: ["ignore", "ignore", "ignore"],
@@ -4424,7 +4665,7 @@ function ghAuthStatus(run = execFileSync11) {
4424
4665
  return "gh-probe-error";
4425
4666
  }
4426
4667
  }
4427
- function isRepoPrivate(ref, run = execFileSync11) {
4668
+ function isRepoPrivate(ref, run = execFileSync13) {
4428
4669
  const out = run("gh", ["repo", "view", `${ref.owner}/${ref.repo}`, "--json", "isPrivate"], {
4429
4670
  stdio: ["ignore", "pipe", "ignore"],
4430
4671
  timeout: GH_TIMEOUT_MS
@@ -4432,7 +4673,7 @@ function isRepoPrivate(ref, run = execFileSync11) {
4432
4673
  const parsed = JSON.parse(out);
4433
4674
  return parsed.isPrivate === true;
4434
4675
  }
4435
- function isActionsEnabled(ref, run = execFileSync11) {
4676
+ function isActionsEnabled(ref, run = execFileSync13) {
4436
4677
  const out = run(
4437
4678
  "gh",
4438
4679
  ["api", `repos/${ref.owner}/${ref.repo}/actions/permissions`, "--jq", ".enabled"],
@@ -4440,7 +4681,7 @@ function isActionsEnabled(ref, run = execFileSync11) {
4440
4681
  ).toString().trim();
4441
4682
  return out === "true";
4442
4683
  }
4443
- function disableActions(ref, run = execFileSync11) {
4684
+ function disableActions(ref, run = execFileSync13) {
4444
4685
  run(
4445
4686
  "gh",
4446
4687
  [
@@ -4454,7 +4695,7 @@ function disableActions(ref, run = execFileSync11) {
4454
4695
  { stdio: ["ignore", "ignore", "pipe"], timeout: GH_TIMEOUT_MS }
4455
4696
  );
4456
4697
  }
4457
- function readOriginRemote(cwd, run = execFileSync11) {
4698
+ function readOriginRemote(cwd, run = execFileSync13) {
4458
4699
  return run("git", ["remote", "get-url", "origin"], {
4459
4700
  cwd,
4460
4701
  stdio: ["ignore", "pipe", "ignore"]
@@ -4462,7 +4703,7 @@ function readOriginRemote(cwd, run = execFileSync11) {
4462
4703
  }
4463
4704
 
4464
4705
  // src/commands.doctor.actions-drift.ts
4465
- function reportActionsDrift(section2, run = execFileSync12) {
4706
+ function reportActionsDrift(section2, run = execFileSync14) {
4466
4707
  let remote;
4467
4708
  try {
4468
4709
  remote = readOriginRemote(repoHome(), run);
@@ -4519,7 +4760,13 @@ function buildVerdictSection(sections) {
4519
4760
 
4520
4761
  // src/commands.doctor.compact.ts
4521
4762
  init_color();
4522
- var ALWAYS_FULL = /* @__PURE__ */ new Set(["Nomad Version", "Summary", "Shared scan", "Schema scan"]);
4763
+ var ALWAYS_FULL = /* @__PURE__ */ new Set([
4764
+ "Nomad Version",
4765
+ "Summary",
4766
+ "Shared scan",
4767
+ "Schema scan",
4768
+ "Remote check"
4769
+ ]);
4523
4770
  function isProblem(item2) {
4524
4771
  return item2.includes(failGlyph) || item2.includes(warnGlyph);
4525
4772
  }
@@ -4540,10 +4787,11 @@ function compactSections(sections) {
4540
4787
  function gatherDoctorSections(opts) {
4541
4788
  const host = section("Environment");
4542
4789
  reportHostAndPaths(host);
4790
+ reportHostKeyAlignment(host);
4543
4791
  reportRepoState(host);
4544
4792
  const links = section("Shared links");
4545
- const mapPath = join35(repoHome(), "path-map.json");
4546
- const rawMap = existsSync29(mapPath) ? readJsonSafe(mapPath, mapPath, links) : null;
4793
+ const mapPath = join36(repoHome(), "path-map.json");
4794
+ const rawMap = existsSync30(mapPath) ? readJsonSafe(mapPath, mapPath, links) : null;
4547
4795
  const map = rawMap ?? { projects: {} };
4548
4796
  reportSharedLinks(links, map);
4549
4797
  reportDroppedNamesMigration(links);
@@ -4561,10 +4809,13 @@ function gatherDoctorSections(opts) {
4561
4809
  reportPathMap(pathMap);
4562
4810
  const neverSync = section("Never-sync");
4563
4811
  reportNeverSync(neverSync);
4812
+ const skills = section("Skills");
4813
+ reportSkillsDivergence(skills);
4564
4814
  const repository = section("Repository");
4565
4815
  const gitleaksReady = reportGitleaksProbe(repository);
4566
4816
  reportGitlinks(repository);
4567
4817
  reportRemote(repository);
4818
+ reportGitIdentity(repository);
4568
4819
  reportRebaseClean(repository);
4569
4820
  reportRebaseState(repository);
4570
4821
  reportOrphanedAutostash(repository);
@@ -4581,6 +4832,8 @@ function gatherDoctorSections(opts) {
4581
4832
  if (opts.checkShared === true) reportCheckShared(sharedScan, gitleaksReady);
4582
4833
  const schemaScan = section("Schema scan");
4583
4834
  if (opts.checkSchema === true) reportCheckSchema(schemaScan);
4835
+ const remoteCheck = section("Remote check");
4836
+ if (opts.checkRemote === true) reportCheckRemote(remoteCheck);
4584
4837
  const body = [
4585
4838
  nomadVersion,
4586
4839
  depVersions,
@@ -4590,10 +4843,12 @@ function gatherDoctorSections(opts) {
4590
4843
  settings,
4591
4844
  pathMap,
4592
4845
  neverSync,
4846
+ skills,
4593
4847
  repository,
4594
4848
  housekeeping,
4595
4849
  sharedScan,
4596
- schemaScan
4850
+ schemaScan,
4851
+ remoteCheck
4597
4852
  ];
4598
4853
  return [...body, buildVerdictSection(body)];
4599
4854
  }
@@ -4618,27 +4873,29 @@ function parseDoctorArgs(args) {
4618
4873
  }
4619
4874
  let checkShared = false;
4620
4875
  let checkSchema = false;
4876
+ let checkRemote = false;
4621
4877
  let verbose = false;
4622
4878
  for (const arg of args) {
4623
4879
  if (arg === "--check-shared") checkShared = true;
4624
4880
  else if (arg === "--check-schema") checkSchema = true;
4881
+ else if (arg === "--check-remote") checkRemote = true;
4625
4882
  else if (arg === "--verbose" || arg === "--all" || arg === "-v") verbose = true;
4626
4883
  else return { kind: "error" };
4627
4884
  }
4628
- return { kind: "run", checkShared, checkSchema, verbose };
4885
+ return { kind: "run", checkShared, checkSchema, checkRemote, verbose };
4629
4886
  }
4630
4887
 
4631
4888
  // src/commands.drop-session.ts
4632
4889
  init_config();
4633
- import { execFileSync as execFileSync14 } from "node:child_process";
4634
- import { existsSync as existsSync31, readdirSync as readdirSync10, statSync as statSync8 } from "node:fs";
4635
- import { join as join37, relative as relative4 } from "node:path";
4890
+ import { execFileSync as execFileSync16 } from "node:child_process";
4891
+ import { existsSync as existsSync32, readdirSync as readdirSync11, statSync as statSync8 } from "node:fs";
4892
+ import { join as join38, relative as relative4 } from "node:path";
4636
4893
 
4637
4894
  // src/commands.drop-session.git.ts
4638
- import { execFileSync as execFileSync13 } from "node:child_process";
4895
+ import { execFileSync as execFileSync15 } from "node:child_process";
4639
4896
  function expandStagedDir(dirRel, repo) {
4640
4897
  try {
4641
- const out = execFileSync13("git", ["ls-files", "-z", "--", dirRel], {
4898
+ const out = execFileSync15("git", ["ls-files", "-z", "--", dirRel], {
4642
4899
  cwd: repo,
4643
4900
  stdio: ["ignore", "pipe", "pipe"]
4644
4901
  });
@@ -4649,7 +4906,7 @@ function expandStagedDir(dirRel, repo) {
4649
4906
  }
4650
4907
  function isTrackedInHead(rel, repo) {
4651
4908
  try {
4652
- execFileSync13("git", ["cat-file", "-e", `HEAD:${rel}`], {
4909
+ execFileSync15("git", ["cat-file", "-e", `HEAD:${rel}`], {
4653
4910
  cwd: repo,
4654
4911
  stdio: ["ignore", "pipe", "pipe"]
4655
4912
  });
@@ -4660,7 +4917,7 @@ function isTrackedInHead(rel, repo) {
4660
4917
  }
4661
4918
  function isInIndex(rel, repo) {
4662
4919
  try {
4663
- const out = execFileSync13("git", ["ls-files", "--", rel], {
4920
+ const out = execFileSync15("git", ["ls-files", "--", rel], {
4664
4921
  cwd: repo,
4665
4922
  stdio: ["ignore", "pipe", "pipe"]
4666
4923
  });
@@ -4674,8 +4931,8 @@ function isInIndex(rel, repo) {
4674
4931
  init_config();
4675
4932
  init_utils();
4676
4933
  init_utils_json();
4677
- import { existsSync as existsSync30 } from "node:fs";
4678
- import { join as join36 } from "node:path";
4934
+ import { existsSync as existsSync31 } from "node:fs";
4935
+ import { join as join37 } from "node:path";
4679
4936
  var SHARED_PROJECT_LOGICAL = /^shared\/projects\/([^/]+)\//;
4680
4937
  function reportScrubHint(id, matches) {
4681
4938
  const live = resolveLiveTranscript2(id, matches);
@@ -4691,8 +4948,8 @@ function reportScrubHint(id, matches) {
4691
4948
  }
4692
4949
  function resolveLiveTranscript2(id, matches) {
4693
4950
  try {
4694
- const mapPath = join36(repoHome(), "path-map.json");
4695
- if (!existsSync30(mapPath)) return null;
4951
+ const mapPath = join37(repoHome(), "path-map.json");
4952
+ if (!existsSync31(mapPath)) return null;
4696
4953
  const projects = readJson(mapPath).projects;
4697
4954
  const claude = claudeHome();
4698
4955
  for (const rel of matches) {
@@ -4700,8 +4957,8 @@ function resolveLiveTranscript2(id, matches) {
4700
4957
  if (logical === void 0) continue;
4701
4958
  const abs = projects[logical]?.[HOST];
4702
4959
  if (abs === void 0) continue;
4703
- const live = join36(claude, "projects", encodePath(abs), `${id}.jsonl`);
4704
- if (existsSync30(live)) return live;
4960
+ const live = join37(claude, "projects", encodePath(abs), `${id}.jsonl`);
4961
+ if (existsSync31(live)) return live;
4705
4962
  }
4706
4963
  return null;
4707
4964
  } catch {
@@ -4717,12 +4974,12 @@ function cmdDropSession(id) {
4717
4974
  process.exit(1);
4718
4975
  }
4719
4976
  const repo = repoHome();
4720
- if (!existsSync31(repo)) die(`repo not cloned at ${repo}`);
4977
+ if (!existsSync32(repo)) die(`repo not cloned at ${repo}`);
4721
4978
  const handle = acquireLock("drop-session");
4722
4979
  if (handle === null) process.exit(0);
4723
4980
  try {
4724
- const repoProjects = join37(repo, "shared", "projects");
4725
- if (!existsSync31(repoProjects)) {
4981
+ const repoProjects = join38(repo, "shared", "projects");
4982
+ if (!existsSync32(repoProjects)) {
4726
4983
  throw new NomadFatal(`no staged session matches ${id}`);
4727
4984
  }
4728
4985
  const matches = collectMatches(repoProjects, id, repo);
@@ -4744,13 +5001,13 @@ function cmdDropSession(id) {
4744
5001
  }
4745
5002
  function collectMatches(repoProjects, id, repo) {
4746
5003
  const matches = [];
4747
- for (const logical of readdirSync10(repoProjects)) {
4748
- const candidate = join37(repoProjects, logical, `${id}.jsonl`);
4749
- if (existsSync31(candidate)) {
5004
+ for (const logical of readdirSync11(repoProjects)) {
5005
+ const candidate = join38(repoProjects, logical, `${id}.jsonl`);
5006
+ if (existsSync32(candidate)) {
4750
5007
  matches.push(relative4(repo, candidate));
4751
5008
  }
4752
- const dir = join37(repoProjects, logical, id);
4753
- if (existsSync31(dir) && statSync8(dir).isDirectory()) {
5009
+ const dir = join38(repoProjects, logical, id);
5010
+ if (existsSync32(dir) && statSync8(dir).isDirectory()) {
4754
5011
  const dirRel = relative4(repo, dir);
4755
5012
  const staged = expandStagedDir(dirRel, repo);
4756
5013
  if (staged.length > 0) matches.push(...staged);
@@ -4766,12 +5023,12 @@ function unstageOne(rel, repo) {
4766
5023
  }
4767
5024
  try {
4768
5025
  if (isTrackedInHead(rel, repo)) {
4769
- execFileSync14("git", ["restore", "--staged", "--worktree", "--", rel], {
5026
+ execFileSync16("git", ["restore", "--staged", "--worktree", "--", rel], {
4770
5027
  cwd: repo,
4771
5028
  stdio: ["ignore", "pipe", "pipe"]
4772
5029
  });
4773
5030
  } else {
4774
- execFileSync14("git", ["rm", "--cached", "-f", "--", rel], {
5031
+ execFileSync16("git", ["rm", "--cached", "-f", "--", rel], {
4775
5032
  cwd: repo,
4776
5033
  stdio: ["ignore", "pipe", "pipe"]
4777
5034
  });
@@ -4785,8 +5042,8 @@ function unstageOne(rel, repo) {
4785
5042
  }
4786
5043
 
4787
5044
  // src/commands.pull.ts
4788
- import { existsSync as existsSync37, mkdirSync as mkdirSync9 } from "node:fs";
4789
- import { join as join44 } from "node:path";
5045
+ import { existsSync as existsSync38, mkdirSync as mkdirSync9 } from "node:fs";
5046
+ import { join as join45 } from "node:path";
4790
5047
 
4791
5048
  // src/commands.push.sections.ts
4792
5049
  init_color();
@@ -4881,55 +5138,20 @@ init_config();
4881
5138
 
4882
5139
  // src/extras-sync.ts
4883
5140
  init_config();
4884
- import { existsSync as existsSync34 } from "node:fs";
4885
- import { join as join41 } from "node:path";
4886
-
4887
- // src/extras-sync.diff.ts
4888
- init_utils();
4889
- import { execFileSync as execFileSync15 } from "node:child_process";
4890
- function labelDiffLine(line) {
4891
- const tab = line.indexOf(" ");
4892
- if (tab === -1) return line;
4893
- const status = line.slice(0, tab);
4894
- const path = line.slice(tab + 1);
4895
- if (status === "D") return `${path} (local only)`;
4896
- if (status === "A") return `${path} (repo only)`;
4897
- return path;
4898
- }
4899
- function parseDiffOutput(stdout) {
4900
- return stdout.split("\n").filter((line) => line.length > 0).map(labelDiffLine);
4901
- }
4902
- function listDivergingFiles(a, b) {
4903
- try {
4904
- const stdout = execFileSync15("git", ["diff", "--no-index", "--name-status", a, b], {
4905
- stdio: ["ignore", "pipe", "pipe"]
4906
- }).toString();
4907
- return parseDiffOutput(stdout);
4908
- } catch (err) {
4909
- const e = err;
4910
- if (e.status === 1 && e.stdout !== void 0) {
4911
- return parseDiffOutput(e.stdout.toString());
4912
- }
4913
- if (e.code === "ENOENT") {
4914
- warn(`git not on PATH; divergence check skipped for ${a}`);
4915
- return [];
4916
- }
4917
- warn(`divergence check failed for ${a}: ${e.message ?? String(err)}`);
4918
- return [];
4919
- }
4920
- }
5141
+ import { existsSync as existsSync35, statSync as statSync9 } from "node:fs";
5142
+ import { join as join42 } from "node:path";
4921
5143
 
4922
5144
  // src/extras-sync.core.ts
4923
5145
  init_config();
4924
- import { cpSync as cpSync6, existsSync as existsSync32, lstatSync as lstatSync9, readdirSync as readdirSync11, rmSync as rmSync11 } from "node:fs";
4925
- import { basename, join as join38 } from "node:path";
5146
+ import { cpSync as cpSync6, existsSync as existsSync33, lstatSync as lstatSync9, readdirSync as readdirSync12, rmSync as rmSync11 } from "node:fs";
5147
+ import { basename, join as join39 } from "node:path";
4926
5148
  init_utils();
4927
5149
  init_utils_json();
4928
5150
  function loadValidatedExtras(opts) {
4929
5151
  const repo = repoHome();
4930
- const mapPath = join38(repo, "path-map.json");
4931
- const repoExtras = join38(repo, "shared", "extras");
4932
- if (!existsSync32(mapPath) || opts.requireRepoExtras === true && !existsSync32(repoExtras)) {
5152
+ const mapPath = join39(repo, "path-map.json");
5153
+ const repoExtras = join39(repo, "shared", "extras");
5154
+ if (!existsSync33(mapPath) || opts.requireRepoExtras === true && !existsSync33(repoExtras)) {
4933
5155
  if (opts.missingMsg !== void 0) log(opts.missingMsg);
4934
5156
  return null;
4935
5157
  }
@@ -4961,16 +5183,16 @@ function* eachExtrasTarget(v, counts) {
4961
5183
  }
4962
5184
  }
4963
5185
  function stripCollidingDstSymlinks(src, dst, isExcluded) {
4964
- if (!existsSync32(dst)) return;
4965
- for (const name of readdirSync11(src)) {
5186
+ if (!existsSync33(dst)) return;
5187
+ for (const name of readdirSync12(src)) {
4966
5188
  if (isExcluded(name)) continue;
4967
- const dstPath = join38(dst, name);
5189
+ const dstPath = join39(dst, name);
4968
5190
  const dstStat = lstatSync9(dstPath, { throwIfNoEntry: false });
4969
5191
  if (dstStat === void 0) continue;
4970
5192
  if (dstStat.isSymbolicLink()) {
4971
5193
  rmSync11(dstPath, { recursive: true, force: true });
4972
- } else if (dstStat.isDirectory() && lstatSync9(join38(src, name)).isDirectory()) {
4973
- stripCollidingDstSymlinks(join38(src, name), dstPath, isExcluded);
5194
+ } else if (dstStat.isDirectory() && lstatSync9(join39(src, name)).isDirectory()) {
5195
+ stripCollidingDstSymlinks(join39(src, name), dstPath, isExcluded);
4974
5196
  }
4975
5197
  }
4976
5198
  }
@@ -5010,17 +5232,17 @@ function copyExtrasFiltered(src, dst, blockSet) {
5010
5232
  });
5011
5233
  }
5012
5234
  function prunePreservingDenied(src, dst, blockSet) {
5013
- for (const name of readdirSync11(dst)) {
5235
+ for (const name of readdirSync12(dst)) {
5014
5236
  if (isDeniedName(blockSet, name)) continue;
5015
- const dstPath = join38(dst, name);
5016
- const srcStat = lstatSync9(join38(src, name), { throwIfNoEntry: false });
5237
+ const dstPath = join39(dst, name);
5238
+ const srcStat = lstatSync9(join39(src, name), { throwIfNoEntry: false });
5017
5239
  if (srcStat === void 0) {
5018
5240
  rmSync11(dstPath, { recursive: true, force: true });
5019
5241
  continue;
5020
5242
  }
5021
5243
  const dstStat = lstatSync9(dstPath);
5022
5244
  if (srcStat.isDirectory() && dstStat.isDirectory()) {
5023
- prunePreservingDenied(join38(src, name), dstPath, blockSet);
5245
+ prunePreservingDenied(join39(src, name), dstPath, blockSet);
5024
5246
  } else if (srcStat.isDirectory() !== dstStat.isDirectory()) {
5025
5247
  rmSync11(dstPath, { recursive: true, force: true });
5026
5248
  }
@@ -5041,17 +5263,17 @@ function copyExtrasFilteredPreserving(src, dst, blockSet) {
5041
5263
  });
5042
5264
  }
5043
5265
  function prunePreservingBy(src, dst, isPreserved) {
5044
- for (const name of readdirSync11(dst)) {
5266
+ for (const name of readdirSync12(dst)) {
5045
5267
  if (isPreserved(name)) continue;
5046
- const dstPath = join38(dst, name);
5047
- const srcStat = lstatSync9(join38(src, name), { throwIfNoEntry: false });
5268
+ const dstPath = join39(dst, name);
5269
+ const srcStat = lstatSync9(join39(src, name), { throwIfNoEntry: false });
5048
5270
  if (srcStat === void 0) {
5049
5271
  rmSync11(dstPath, { recursive: true, force: true });
5050
5272
  continue;
5051
5273
  }
5052
5274
  const dstStat = lstatSync9(dstPath);
5053
5275
  if (srcStat.isDirectory() && dstStat.isDirectory()) {
5054
- prunePreservingBy(join38(src, name), dstPath, isPreserved);
5276
+ prunePreservingBy(join39(src, name), dstPath, isPreserved);
5055
5277
  } else if (srcStat.isDirectory() !== dstStat.isDirectory()) {
5056
5278
  rmSync11(dstPath, { recursive: true, force: true });
5057
5279
  }
@@ -5078,11 +5300,11 @@ init_utils_json();
5078
5300
 
5079
5301
  // src/extras-sync.remap.ts
5080
5302
  init_config();
5081
- import { existsSync as existsSync33, mkdirSync as mkdirSync7, readdirSync as readdirSync12, realpathSync as realpathSync4, rmSync as rmSync12 } from "node:fs";
5082
- import { dirname as dirname7, join as join40, sep as sep7 } from "node:path";
5303
+ import { existsSync as existsSync34, mkdirSync as mkdirSync7, readdirSync as readdirSync13, realpathSync as realpathSync4, rmSync as rmSync12 } from "node:fs";
5304
+ import { dirname as dirname7, join as join41, sep as sep7 } from "node:path";
5083
5305
 
5084
5306
  // src/extras-sync.planning-diff.ts
5085
- import { join as join39, normalize as normalize2, sep as sep6 } from "node:path";
5307
+ import { join as join40, normalize as normalize2, sep as sep6 } from "node:path";
5086
5308
  init_utils();
5087
5309
  function processRecord(fields, i, changed, deleted) {
5088
5310
  const status = fields[i];
@@ -5134,7 +5356,7 @@ function planningDeleteTargets(opts) {
5134
5356
  const { deleted } = parsePlanningDiff(raw);
5135
5357
  const logicalPrefix = "shared/extras/" + logical + "/";
5136
5358
  const prefix = logicalPrefix + ".planning/";
5137
- const planningRoot = join39(localRoot, ".planning");
5359
+ const planningRoot = join40(localRoot, ".planning");
5138
5360
  const planningRootBoundary = planningRoot + sep6;
5139
5361
  const targets = [];
5140
5362
  for (const repoPath of deleted) {
@@ -5142,7 +5364,7 @@ function planningDeleteTargets(opts) {
5142
5364
  continue;
5143
5365
  }
5144
5366
  const remainder = repoPath.slice(logicalPrefix.length);
5145
- const candidate = join39(localRoot, remainder);
5367
+ const candidate = join40(localRoot, remainder);
5146
5368
  const resolved = normalize2(candidate);
5147
5369
  if (!resolved.startsWith(planningRootBoundary)) {
5148
5370
  throw new NomadFatal(
@@ -5163,7 +5385,7 @@ function runExtrasOp(v, dryRun, paths, backup, copy) {
5163
5385
  const would = [];
5164
5386
  for (const t of eachExtrasTarget(v, counts)) {
5165
5387
  const { src, dst } = paths(t);
5166
- if (!existsSync33(src)) continue;
5388
+ if (!existsSync34(src)) continue;
5167
5389
  const item2 = `${t.logical}/${t.dirname}`;
5168
5390
  if (dryRun) {
5169
5391
  would.push(item2);
@@ -5179,7 +5401,7 @@ function pruneEmptyAncestors(target, planningRoot) {
5179
5401
  let dir = dirname7(target);
5180
5402
  while (dir !== planningRoot && dir.startsWith(planningRoot + sep7)) {
5181
5403
  try {
5182
- if (readdirSync12(dir).length > 0) break;
5404
+ if (readdirSync13(dir).length > 0) break;
5183
5405
  rmSync12(dir, { recursive: true, force: true });
5184
5406
  } catch {
5185
5407
  break;
@@ -5198,7 +5420,7 @@ function isInsidePlanningRoot(parentReal, rootReal) {
5198
5420
  return parentReal === rootReal || parentReal.startsWith(rootReal + sep7);
5199
5421
  }
5200
5422
  function deletePlanningTarget(target, planningRoot, repoCounterpart) {
5201
- if (existsSync33(repoCounterpart)) return;
5423
+ if (existsSync34(repoCounterpart)) return;
5202
5424
  const parentReal = tryRealpath(dirname7(target));
5203
5425
  if (parentReal === void 0) return;
5204
5426
  const rootReal = tryRealpath(planningRoot);
@@ -5208,7 +5430,7 @@ function deletePlanningTarget(target, planningRoot, repoCounterpart) {
5208
5430
  pruneEmptyAncestors(target, planningRoot);
5209
5431
  }
5210
5432
  function propagatePlanningDeletes(v, ts, prePostHeads, repo) {
5211
- const repoExtras = join40(repo, "shared", "extras");
5433
+ const repoExtras = join41(repo, "shared", "extras");
5212
5434
  for (const t of eachExtrasTarget(v, { unmapped: 0, skipped: 0 })) {
5213
5435
  if (t.dirname !== ".planning") continue;
5214
5436
  let raw;
@@ -5234,11 +5456,11 @@ function propagatePlanningDeletes(v, ts, prePostHeads, repo) {
5234
5456
  }
5235
5457
  const targets = planningDeleteTargets({ raw, logical: t.logical, localRoot: t.localRoot });
5236
5458
  if (targets.length === 0) continue;
5237
- backupExtrasWrite(join40(t.localRoot, t.dirname), ts, t.localRoot);
5238
- const planningRoot = join40(t.localRoot, ".planning");
5459
+ backupExtrasWrite(join41(t.localRoot, t.dirname), ts, t.localRoot);
5460
+ const planningRoot = join41(t.localRoot, ".planning");
5239
5461
  for (const target of targets) {
5240
5462
  const relToLocal = target.slice(t.localRoot.length + sep7.length);
5241
- deletePlanningTarget(target, planningRoot, join40(repoExtras, t.logical, relToLocal));
5463
+ deletePlanningTarget(target, planningRoot, join41(repoExtras, t.logical, relToLocal));
5242
5464
  }
5243
5465
  }
5244
5466
  }
@@ -5247,14 +5469,14 @@ function remapExtrasPush(ts, opts = {}) {
5247
5469
  const v = loadValidatedExtras({ missingMsg: "no path-map.json; skipping extras push" });
5248
5470
  if (v === null) return { unmapped: 0, skipped: 0, pushed: [], wouldPush: [] };
5249
5471
  const repo = repoHome();
5250
- const repoExtras = join40(repo, "shared", "extras");
5472
+ const repoExtras = join41(repo, "shared", "extras");
5251
5473
  if (!dryRun) mkdirSync7(repoExtras, { recursive: true });
5252
5474
  const { unmapped, skipped, done, would } = runExtrasOp(
5253
5475
  v,
5254
5476
  dryRun,
5255
5477
  ({ localRoot, logical, dirname: dirname8 }) => ({
5256
- src: join40(localRoot, dirname8),
5257
- dst: join40(repoExtras, logical, dirname8)
5478
+ src: join41(localRoot, dirname8),
5479
+ dst: join41(repoExtras, logical, dirname8)
5258
5480
  }),
5259
5481
  (dst) => backupRepoWrite(dst, ts, repo),
5260
5482
  // Push copy routing per extra type:
@@ -5283,8 +5505,8 @@ function remapExtrasPull(ts, opts = {}) {
5283
5505
  v,
5284
5506
  dryRun,
5285
5507
  ({ localRoot, logical, dirname: dirname8 }) => ({
5286
- src: join40(repo, "shared", "extras", logical, dirname8),
5287
- dst: join40(localRoot, dirname8)
5508
+ src: join41(repo, "shared", "extras", logical, dirname8),
5509
+ dst: join41(localRoot, dirname8)
5288
5510
  }),
5289
5511
  // Snapshot the host-side dst BEFORE copyExtras clobbers it. Anchor on
5290
5512
  // localRoot so the backup tree mirrors the project layout.
@@ -5312,21 +5534,36 @@ function remapExtrasPull(ts, opts = {}) {
5312
5534
  }
5313
5535
 
5314
5536
  // src/extras-sync.ts
5537
+ function divergenceWarnLine(o) {
5538
+ const kind = o.isDir ? "folder" : "file";
5539
+ const name = o.isDir ? `${o.dirname}/` : o.dirname;
5540
+ const one = o.count === 1;
5541
+ const fileCount = one ? "1 file" : `${o.count} files`;
5542
+ const them = one ? "it" : "them";
5543
+ const yours = one ? "your current file is" : "your current files are";
5544
+ return `local ${kind} ${name} in repo ${o.logical} differs from the synced copy in ${fileCount}; the next pull step will overwrite ${them} with the synced version (${yours} backed up to ${o.projectBackupRoot}/)`;
5545
+ }
5315
5546
  function divergenceCheckExtras(ts) {
5316
5547
  const v = loadValidatedExtras({});
5317
5548
  if (v === null) return;
5318
5549
  const counts = { unmapped: 0, skipped: 0 };
5319
- const backupRoot = join41(backupBase(), ts, "extras");
5550
+ const backupRoot = join42(backupBase(), ts, "extras");
5320
5551
  const repo = repoHome();
5321
5552
  for (const { logical, localRoot, dirname: dirname8 } of eachExtrasTarget(v, counts)) {
5322
- const local = join41(localRoot, dirname8);
5323
- const repoEntry = join41(repo, "shared", "extras", logical, dirname8);
5324
- if (!existsSync34(local) || !existsSync34(repoEntry)) continue;
5553
+ const local = join42(localRoot, dirname8);
5554
+ const repoEntry = join42(repo, "shared", "extras", logical, dirname8);
5555
+ if (!existsSync35(local) || !existsSync35(repoEntry)) continue;
5325
5556
  const diff = listDivergingFiles(local, repoEntry);
5326
5557
  if (diff.length === 0) continue;
5327
- const projectBackupRoot = join41(backupRoot, encodePath(localRoot));
5558
+ const projectBackupRoot = join42(backupRoot, encodePath(localRoot));
5328
5559
  warn(
5329
- `local ${dirname8} for ${logical} diverges from origin in ${diff.length} file(s); next remapExtrasPull will merge changes (.planning overlays, .claude/.CLAUDE.md mirror; backups at ${projectBackupRoot}/)`
5560
+ divergenceWarnLine({
5561
+ dirname: dirname8,
5562
+ logical,
5563
+ isDir: statSync9(local).isDirectory(),
5564
+ count: diff.length,
5565
+ projectBackupRoot
5566
+ })
5330
5567
  );
5331
5568
  for (const f of diff) warn(` ${f}`);
5332
5569
  }
@@ -5334,8 +5571,8 @@ function divergenceCheckExtras(ts) {
5334
5571
 
5335
5572
  // src/skills-sync.ts
5336
5573
  init_config();
5337
- import { existsSync as existsSync35, lstatSync as lstatSync10, mkdirSync as mkdirSync8, readdirSync as readdirSync13, rmSync as rmSync13 } from "node:fs";
5338
- import { join as join42 } from "node:path";
5574
+ import { existsSync as existsSync36, lstatSync as lstatSync10, mkdirSync as mkdirSync8, readdirSync as readdirSync14, rmSync as rmSync13 } from "node:fs";
5575
+ import { join as join43 } from "node:path";
5339
5576
  init_utils_fs();
5340
5577
  function isGsdOwned(name) {
5341
5578
  return name.startsWith(GSD_PREFIX);
@@ -5344,7 +5581,7 @@ function isSkillExcluded(name) {
5344
5581
  return isGsdOwned(name) || isDeniedName(ALWAYS_NEVER_SYNC, name);
5345
5582
  }
5346
5583
  function copySkillsPush(src, dst) {
5347
- const srcNames = readdirSync13(src, { encoding: "utf8" });
5584
+ const srcNames = readdirSync14(src, { encoding: "utf8" });
5348
5585
  const blockSet = /* @__PURE__ */ new Set([
5349
5586
  ...srcNames.filter((n) => isGsdOwned(n)),
5350
5587
  ...ALWAYS_NEVER_SYNC
@@ -5355,9 +5592,9 @@ function copySkillsPull(src, dst) {
5355
5592
  copyExtrasFilteredPreservingBy(src, dst, isSkillExcluded);
5356
5593
  }
5357
5594
  function syncSkillsPull(ts) {
5358
- const sharedSkills = join42(repoHome(), "shared", "skills");
5359
- if (!existsSync35(sharedSkills)) return;
5360
- const localSkills = join42(claudeHome(), "skills");
5595
+ const sharedSkills = join43(repoHome(), "shared", "skills");
5596
+ if (!existsSync36(sharedSkills)) return;
5597
+ const localSkills = join43(claudeHome(), "skills");
5361
5598
  const dstStat = lstatSync10(localSkills, { throwIfNoEntry: false });
5362
5599
  if (dstStat?.isSymbolicLink() === true) {
5363
5600
  backupBeforeWrite(localSkills, ts);
@@ -5367,18 +5604,18 @@ function syncSkillsPull(ts) {
5367
5604
  copySkillsPull(sharedSkills, localSkills);
5368
5605
  }
5369
5606
  function syncSkillsPush() {
5370
- const localSkills = join42(claudeHome(), "skills");
5607
+ const localSkills = join43(claudeHome(), "skills");
5371
5608
  const stat = lstatSync10(localSkills, { throwIfNoEntry: false });
5372
5609
  if (stat === void 0) return;
5373
5610
  if (stat.isSymbolicLink()) return;
5374
- const sharedSkills = join42(repoHome(), "shared", "skills");
5611
+ const sharedSkills = join43(repoHome(), "shared", "skills");
5375
5612
  copySkillsPush(localSkills, sharedSkills);
5376
5613
  }
5377
5614
 
5378
5615
  // src/preview.ts
5379
5616
  init_config();
5380
- import { existsSync as existsSync36 } from "node:fs";
5381
- import { join as join43 } from "node:path";
5617
+ import { existsSync as existsSync37 } from "node:fs";
5618
+ import { join as join44 } from "node:path";
5382
5619
 
5383
5620
  // node_modules/diff/libesm/diff/base.js
5384
5621
  var Diff = class {
@@ -5664,7 +5901,7 @@ function diffJsonStrings(currentJsonText, newJsonText) {
5664
5901
  return lines.join("\n");
5665
5902
  }
5666
5903
  function readJsonOrNull(path) {
5667
- if (!existsSync36(path)) return null;
5904
+ if (!existsSync37(path)) return null;
5668
5905
  try {
5669
5906
  return readJson(path);
5670
5907
  } catch {
@@ -5678,12 +5915,12 @@ function previewSettings(basePath, hostPath, settingsPath) {
5678
5915
  }
5679
5916
  const notes = [];
5680
5917
  const hostOverrides = readJsonOrNull(hostPath);
5681
- if (hostOverrides === null && existsSync36(hostPath)) {
5918
+ if (hostOverrides === null && existsSync37(hostPath)) {
5682
5919
  notes.push(`malformed hosts/${HOST}.json; ignoring overrides`);
5683
5920
  }
5684
5921
  const merged = stripGsdHookEntries(deepMerge(base, hostOverrides ?? {}));
5685
5922
  const current = readJsonOrNull(settingsPath);
5686
- if (current === null && existsSync36(settingsPath)) {
5923
+ if (current === null && existsSync37(settingsPath)) {
5687
5924
  return { diff: "", notes: [...notes, "malformed; skipping diff"] };
5688
5925
  }
5689
5926
  const strippedCurrent = stripGsdHookEntries(current ?? {});
@@ -5724,9 +5961,9 @@ function computePreview(ts, map, verb = "pull") {
5724
5961
  onPreview: (e) => addItem(links, formatLinkRow(e))
5725
5962
  });
5726
5963
  const settingsResult = previewSettings(
5727
- join43(repo, "shared", "settings.base.json"),
5728
- join43(repo, "hosts", `${HOST}.json`),
5729
- join43(claude, "settings.json")
5964
+ join44(repo, "shared", "settings.base.json"),
5965
+ join44(repo, "hosts", `${HOST}.json`),
5966
+ join44(claude, "settings.json")
5730
5967
  );
5731
5968
  const settingsSection = buildSettingsSectionForPreview(settingsResult);
5732
5969
  const sessions = section("Sessions");
@@ -5748,9 +5985,9 @@ init_config();
5748
5985
  init_commands_pull_wedge();
5749
5986
  init_utils();
5750
5987
  init_utils_fs();
5751
- import { execFileSync as execFileSync16 } from "node:child_process";
5988
+ import { execFileSync as execFileSync17 } from "node:child_process";
5752
5989
  function gitCapture(args, cwd) {
5753
- return execFileSync16("git", args, {
5990
+ return execFileSync17("git", args, {
5754
5991
  cwd,
5755
5992
  stdio: ["ignore", "pipe", "pipe"],
5756
5993
  maxBuffer: 64 * 1024 * 1024
@@ -5927,8 +6164,8 @@ function cmdPull(opts = {}) {
5927
6164
  const forceRemote = opts.forceRemote === true;
5928
6165
  const repo = repoHome();
5929
6166
  const backup = backupBase();
5930
- if (!existsSync37(repo)) die(`repo not cloned at ${repo}`);
5931
- if (!existsSync37(join44(repo, "shared", "settings.base.json"))) {
6167
+ if (!existsSync38(repo)) die(`repo not cloned at ${repo}`);
6168
+ if (!existsSync38(join45(repo, "shared", "settings.base.json"))) {
5932
6169
  die("repo not initialized; run 'nomad init' to scaffold");
5933
6170
  }
5934
6171
  const handle = acquireLock("pull");
@@ -5937,7 +6174,7 @@ function cmdPull(opts = {}) {
5937
6174
  const ts = freshBackupTs(backup);
5938
6175
  handleWedge(repo, forceRemote);
5939
6176
  if (!dryRun) {
5940
- const backupRoot = join44(backup, ts);
6177
+ const backupRoot = join45(backup, ts);
5941
6178
  try {
5942
6179
  mkdirSync9(backupRoot, { recursive: true });
5943
6180
  } catch (err) {
@@ -5950,8 +6187,8 @@ function cmdPull(opts = {}) {
5950
6187
  const prePostHeads = capturePrePostHeads(repo, () => {
5951
6188
  gitOrFatal(["pull", "--rebase", "--autostash"], "git pull --rebase", repo);
5952
6189
  });
5953
- const mapPath = join44(repo, "path-map.json");
5954
- const map = existsSync37(mapPath) ? readPathMap(mapPath) : { projects: {} };
6190
+ const mapPath = join45(repo, "path-map.json");
6191
+ const map = existsSync38(mapPath) ? readPathMap(mapPath) : { projects: {} };
5955
6192
  divergenceCheckExtras(ts);
5956
6193
  if (dryRun) {
5957
6194
  computePreview(ts, map, "pull");
@@ -5973,8 +6210,8 @@ function cmdPull(opts = {}) {
5973
6210
 
5974
6211
  // src/commands.push.ts
5975
6212
  init_config();
5976
- import { existsSync as existsSync39 } from "node:fs";
5977
- import { join as join46, relative as relative5 } from "node:path";
6213
+ import { existsSync as existsSync40 } from "node:fs";
6214
+ import { join as join47, relative as relative5 } from "node:path";
5978
6215
 
5979
6216
  // src/commands.push.allowlist.ts
5980
6217
  init_config();
@@ -6067,7 +6304,7 @@ function enforceAllowList(statusPorcelain, map) {
6067
6304
 
6068
6305
  // src/push-global-config.ts
6069
6306
  init_config();
6070
- import { execFileSync as execFileSync17 } from "node:child_process";
6307
+ import { execFileSync as execFileSync18 } from "node:child_process";
6071
6308
  var STATUS_LABELS = {
6072
6309
  A: "add",
6073
6310
  M: "modify",
@@ -6107,7 +6344,7 @@ function isInScope(filePath, exactPrefixes, dirPrefixes) {
6107
6344
  }
6108
6345
  function collectGlobalConfigChanges(repoHome2, hostname2, opts) {
6109
6346
  const args = opts.staged ? ["diff", "--cached", "--name-status", "-z"] : ["diff", "HEAD", "--name-status", "-z"];
6110
- const raw = execFileSync17("git", args, {
6347
+ const raw = execFileSync18("git", args, {
6111
6348
  cwd: repoHome2,
6112
6349
  stdio: ["ignore", "pipe", "pipe"]
6113
6350
  }).toString();
@@ -6146,9 +6383,9 @@ init_color();
6146
6383
  init_config();
6147
6384
  init_config_sharedDirs_guard();
6148
6385
  import { randomBytes as randomBytes2 } from "node:crypto";
6149
- import { copyFileSync, existsSync as existsSync38, mkdirSync as mkdirSync10, readdirSync as readdirSync14, rmSync as rmSync14 } from "node:fs";
6386
+ import { copyFileSync, existsSync as existsSync39, mkdirSync as mkdirSync10, readdirSync as readdirSync15, rmSync as rmSync14 } from "node:fs";
6150
6387
  import { homedir as homedir5 } from "node:os";
6151
- import { join as join45 } from "node:path";
6388
+ import { join as join46 } from "node:path";
6152
6389
  init_push_leak_verdict();
6153
6390
  init_push_gitleaks();
6154
6391
  init_utils_fs();
@@ -6163,13 +6400,13 @@ function stageSessions(tmpRoot, map) {
6163
6400
  if (!p || p === "TBD") continue;
6164
6401
  reverse.set(encodePath(p), logical);
6165
6402
  }
6166
- const localProjects = join45(claudeHome(), "projects");
6167
- if (!existsSync38(localProjects)) return 0;
6403
+ const localProjects = join46(claudeHome(), "projects");
6404
+ if (!existsSync39(localProjects)) return 0;
6168
6405
  let staged = 0;
6169
- for (const dir of readdirSync14(localProjects)) {
6406
+ for (const dir of readdirSync15(localProjects)) {
6170
6407
  const logical = reverse.get(dir);
6171
6408
  if (!logical) continue;
6172
- copyDirJsonlOnly(join45(localProjects, dir), join45(tmpRoot, "shared", "projects", logical));
6409
+ copyDirJsonlOnly(join46(localProjects, dir), join46(tmpRoot, "shared", "projects", logical));
6173
6410
  staged++;
6174
6411
  }
6175
6412
  return staged;
@@ -6185,9 +6422,9 @@ function stageExtras(tmpRoot, map) {
6185
6422
  if (!localRoot || localRoot === "TBD") continue;
6186
6423
  for (const dirname8 of dirnames) {
6187
6424
  if (!whitelist.includes(dirname8)) continue;
6188
- const src = join45(localRoot, dirname8);
6189
- if (!existsSync38(src)) continue;
6190
- const dst = join45(tmpRoot, "shared", "extras", logical, dirname8);
6425
+ const src = join46(localRoot, dirname8);
6426
+ if (!existsSync39(src)) continue;
6427
+ const dst = join46(tmpRoot, "shared", "extras", logical, dirname8);
6191
6428
  copyExtras(src, dst);
6192
6429
  staged++;
6193
6430
  }
@@ -6195,19 +6432,19 @@ function stageExtras(tmpRoot, map) {
6195
6432
  return staged;
6196
6433
  }
6197
6434
  function previewPushLeaks(map) {
6198
- const cacheDir = join45(homedir5(), ".cache", "claude-nomad");
6435
+ const cacheDir = join46(homedir5(), ".cache", "claude-nomad");
6199
6436
  mkdirSync10(cacheDir, { recursive: true });
6200
6437
  const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes2(4).toString("hex")}`;
6201
- const tmpRoot = join45(cacheDir, `push-preview-tree-${stamp}`);
6438
+ const tmpRoot = join46(cacheDir, `push-preview-tree-${stamp}`);
6202
6439
  try {
6203
6440
  const sessionCount = stageSessions(tmpRoot, map);
6204
6441
  const extrasCount = stageExtras(tmpRoot, map);
6205
6442
  if (sessionCount + extrasCount === 0) {
6206
6443
  return { leak: false, verdictRow: NOTHING_TO_SCAN_ROW, recovery: null, findings: [] };
6207
6444
  }
6208
- const ignoreFile = join45(repoHome(), ".gitleaksignore");
6209
- if (existsSync38(ignoreFile)) {
6210
- copyFileSync(ignoreFile, join45(tmpRoot, ".gitleaksignore"));
6445
+ const ignoreFile = join46(repoHome(), ".gitleaksignore");
6446
+ if (existsSync39(ignoreFile)) {
6447
+ copyFileSync(ignoreFile, join46(tmpRoot, ".gitleaksignore"));
6211
6448
  }
6212
6449
  let findings;
6213
6450
  try {
@@ -6229,8 +6466,8 @@ init_utils();
6229
6466
  init_utils_fs();
6230
6467
  init_utils_json();
6231
6468
  function stripGsdHooksFromBase(repo, backup) {
6232
- const basePath = join46(repo, "shared", "settings.base.json");
6233
- if (!existsSync39(basePath)) return;
6469
+ const basePath = join47(repo, "shared", "settings.base.json");
6470
+ if (!existsSync40(basePath)) return;
6234
6471
  let base;
6235
6472
  try {
6236
6473
  base = readJson(basePath);
@@ -6244,28 +6481,28 @@ function stripGsdHooksFromBase(repo, backup) {
6244
6481
  writeJsonAtomic(basePath, stripped);
6245
6482
  }
6246
6483
  function reportSettingsAheadDrift(repo) {
6247
- const basePath = join46(repo, "shared", "settings.base.json");
6248
- if (!existsSync39(basePath)) return;
6249
- const settingsPath = join46(claudeHome(), "settings.json");
6250
- if (!existsSync39(settingsPath)) return;
6484
+ const basePath = join47(repo, "shared", "settings.base.json");
6485
+ if (!existsSync40(basePath)) return;
6486
+ const settingsPath = join47(claudeHome(), "settings.json");
6487
+ if (!existsSync40(settingsPath)) return;
6251
6488
  try {
6252
6489
  const base = readJson(basePath);
6253
- const hostPath = join46(repo, "hosts", `${HOST}.json`);
6254
- const overrides = existsSync39(hostPath) ? readJson(hostPath) : {};
6490
+ const hostPath = join47(repo, "hosts", `${HOST}.json`);
6491
+ const overrides = existsSync40(hostPath) ? readJson(hostPath) : {};
6255
6492
  const merged = deepMerge(base, overrides);
6256
6493
  const settings = readJson(settingsPath);
6257
6494
  const { ahead } = classifySettingsDrift(merged, settings);
6258
6495
  const { promotable } = partitionByCaptureExclusion(ahead);
6259
6496
  if (promotable.length === 0) return;
6260
- const keys = JSON.stringify(promotable);
6497
+ const { phrase, pronoun, verb } = describeSettings(promotable);
6261
6498
  warn(
6262
- `settings.json has local-only keys ${keys} not in the repo. Run 'nomad capture-settings' to promote them (or 'nomad capture-settings --host' for host-specific values).`
6499
+ `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).`
6263
6500
  );
6264
6501
  } catch {
6265
6502
  }
6266
6503
  }
6267
6504
  function guardGitlinks(repo) {
6268
- const gitlinks = findGitlinks(join46(repo, "shared"));
6505
+ const gitlinks = findGitlinks(join47(repo, "shared"));
6269
6506
  if (gitlinks.length === 0) return;
6270
6507
  for (const p of gitlinks) {
6271
6508
  const rel = relative5(repo, p);
@@ -6331,7 +6568,7 @@ async function cmdPush(opts = {}) {
6331
6568
  guardResolutionModeConflicts(dryRun, redactAll, allowAll, allowRule);
6332
6569
  const repo = repoHome();
6333
6570
  const backup = backupBase();
6334
- if (!existsSync39(repo)) die(`repo not cloned at ${repo}`);
6571
+ if (!existsSync40(repo)) die(`repo not cloned at ${repo}`);
6335
6572
  const handle = acquireLock("push");
6336
6573
  if (handle === null) process.exit(0);
6337
6574
  try {
@@ -6354,8 +6591,8 @@ async function cmdPush(opts = {}) {
6354
6591
  renderNoScanTree(st);
6355
6592
  return;
6356
6593
  }
6357
- const mapPath = join46(repo, "path-map.json");
6358
- if (!existsSync39(mapPath)) {
6594
+ const mapPath = join47(repo, "path-map.json");
6595
+ if (!existsSync40(mapPath)) {
6359
6596
  if (dryRun) return runDryRunPreview(st, null, repo);
6360
6597
  die("path-map.json missing, cannot enforce push allow-list");
6361
6598
  }
@@ -6376,25 +6613,35 @@ async function cmdPush(opts = {}) {
6376
6613
  }
6377
6614
 
6378
6615
  // src/commands.update.ts
6379
- import { execFileSync as execFileSync18 } from "node:child_process";
6616
+ import { execFileSync as execFileSync19 } from "node:child_process";
6380
6617
  init_utils();
6381
- function readInstalledVersion(run = execFileSync18) {
6618
+ function readInstalledVersion(run = execFileSync19) {
6382
6619
  try {
6383
6620
  return run("nomad", ["--version"], { encoding: "utf8" }).toString().trim() || null;
6384
6621
  } catch {
6385
6622
  return null;
6386
6623
  }
6387
6624
  }
6388
- function cmdUpdate(run = execFileSync18) {
6389
- console.log("Updating claude-nomad CLI via npm...");
6625
+ function cmdUpdate(currentVersion, run = execFileSync19) {
6626
+ console.log(`Updating claude-nomad v${currentVersion}...`);
6390
6627
  try {
6391
- run("npm", ["update", "-g", "claude-nomad"], { stdio: "inherit" });
6628
+ run("npm", ["update", "-g", "claude-nomad"], {
6629
+ encoding: "utf8",
6630
+ stdio: ["ignore", "pipe", "pipe"],
6631
+ // Now that output is captured rather than inherited, execFileSync buffers
6632
+ // it; lift the default 1 MiB ceiling so a noisy-but-successful npm run
6633
+ // (deprecation/funding spam) cannot throw ENOBUFS.
6634
+ maxBuffer: 64 * 1024 * 1024
6635
+ });
6392
6636
  } catch (err) {
6393
6637
  const e = err;
6394
6638
  if (e.code === "ENOENT") {
6395
6639
  throw new NomadFatal("npm not found on PATH; install Node.js/npm and retry.");
6396
6640
  }
6397
- throw new NomadFatal(`npm update -g claude-nomad failed: ${e.message}`);
6641
+ const detail = String(e.stderr ?? "").trim();
6642
+ const suffix = detail ? `
6643
+ ${detail}` : "";
6644
+ throw new NomadFatal(`npm update -g claude-nomad failed: ${e.message}${suffix}`);
6398
6645
  }
6399
6646
  const version = readInstalledVersion(run);
6400
6647
  if (version) {
@@ -6409,18 +6656,18 @@ init_config();
6409
6656
 
6410
6657
  // src/diff.ts
6411
6658
  init_config();
6412
- import { existsSync as existsSync40 } from "node:fs";
6413
- import { join as join47 } from "node:path";
6659
+ import { existsSync as existsSync41 } from "node:fs";
6660
+ import { join as join48 } from "node:path";
6414
6661
  init_utils();
6415
6662
  init_utils_fs();
6416
6663
  init_utils_json();
6417
6664
  function cmdDiff() {
6418
6665
  try {
6419
6666
  const repo = repoHome();
6420
- if (!existsSync40(repo)) die(`repo not cloned at ${repo}`);
6667
+ if (!existsSync41(repo)) die(`repo not cloned at ${repo}`);
6421
6668
  const ts = freshBackupTs(backupBase());
6422
- const mapPath = join47(repo, "path-map.json");
6423
- const map = existsSync40(mapPath) ? readPathMap(mapPath) : { projects: {} };
6669
+ const mapPath = join48(repo, "path-map.json");
6670
+ const map = existsSync41(mapPath) ? readPathMap(mapPath) : { projects: {} };
6424
6671
  computePreview(ts, map, "diff");
6425
6672
  } catch (err) {
6426
6673
  if (err instanceof NomadFatal) {
@@ -6434,19 +6681,19 @@ function cmdDiff() {
6434
6681
 
6435
6682
  // src/init.ts
6436
6683
  init_config();
6437
- import { existsSync as existsSync42, mkdirSync as mkdirSync11, writeFileSync as writeFileSync6 } from "node:fs";
6438
- import { join as join49 } from "node:path";
6684
+ import { existsSync as existsSync43, mkdirSync as mkdirSync11, writeFileSync as writeFileSync6 } from "node:fs";
6685
+ import { join as join50 } from "node:path";
6439
6686
 
6440
6687
  // src/init.gh-onboard.ts
6441
6688
  init_config();
6442
- import { execFileSync as execFileSync19 } from "node:child_process";
6689
+ import { execFileSync as execFileSync20 } from "node:child_process";
6443
6690
  init_utils();
6444
6691
  var DEFAULT_REPO_NAME = "claude-nomad-config";
6445
6692
  function isValidRepoName(name) {
6446
6693
  return /^[A-Za-z0-9._-]{1,100}$/.test(name);
6447
6694
  }
6448
6695
  var GH_NETWORK_TIMEOUT_MS = 3e4;
6449
- function ensureOriginRepo(repoName, run = execFileSync19) {
6696
+ function ensureOriginRepo(repoName, run = execFileSync20) {
6450
6697
  if (!isValidRepoName(repoName)) {
6451
6698
  die(
6452
6699
  `invalid repo name: ${JSON.stringify(repoName)}. Use only letters, digits, hyphens, underscores, and dots (1-100 chars).`
@@ -6517,33 +6764,33 @@ init_config();
6517
6764
  init_utils();
6518
6765
  init_utils_fs();
6519
6766
  init_utils_json();
6520
- import { copyFileSync as copyFileSync2, cpSync as cpSync7, existsSync as existsSync41, rmSync as rmSync15, statSync as statSync9 } from "node:fs";
6521
- import { join as join48 } from "node:path";
6767
+ import { copyFileSync as copyFileSync2, cpSync as cpSync7, existsSync as existsSync42, rmSync as rmSync15, statSync as statSync10 } from "node:fs";
6768
+ import { join as join49 } from "node:path";
6522
6769
  function snapshotIntoShared(map) {
6523
6770
  const repo = repoHome();
6524
6771
  const claude = claudeHome();
6525
6772
  for (const name of allSharedLinks(map)) {
6526
- const src = join48(claude, name);
6527
- if (!existsSync41(src)) continue;
6528
- const dst = join48(repo, "shared", name);
6529
- if (statSync9(src).isDirectory()) {
6530
- const gk = join48(dst, ".gitkeep");
6531
- if (existsSync41(gk)) rmSync15(gk);
6773
+ const src = join49(claude, name);
6774
+ if (!existsSync42(src)) continue;
6775
+ const dst = join49(repo, "shared", name);
6776
+ if (statSync10(src).isDirectory()) {
6777
+ const gk = join49(dst, ".gitkeep");
6778
+ if (existsSync42(gk)) rmSync15(gk);
6532
6779
  cpSync7(src, dst, { recursive: true, force: false, errorOnExist: true });
6533
6780
  } else {
6534
6781
  copyFileSync2(src, dst);
6535
6782
  }
6536
6783
  log(`snapshotted shared/${name} from ${src}`);
6537
6784
  }
6538
- const userSettings = join48(claude, "settings.json");
6539
- if (existsSync41(userSettings)) {
6785
+ const userSettings = join49(claude, "settings.json");
6786
+ if (existsSync42(userSettings)) {
6540
6787
  let parsed;
6541
6788
  try {
6542
6789
  parsed = readJson(userSettings);
6543
6790
  } catch (err) {
6544
6791
  return die(`malformed ${userSettings}: ${err.message}`);
6545
6792
  }
6546
- const hostFile = join48(repo, "hosts", `${HOST}.json`);
6793
+ const hostFile = join49(repo, "hosts", `${HOST}.json`);
6547
6794
  writeJsonAtomic(hostFile, parsed);
6548
6795
  log(`snapshotted hosts/${HOST}.json from ${userSettings}`);
6549
6796
  }
@@ -6556,17 +6803,20 @@ var SHARED_CLAUDE_MD = "<!-- claude-nomad shared CLAUDE.md; symlinked into ~/.cl
6556
6803
  var SHARED_KEEP_DIRS = ["agents", "skills", "commands", "rules", "hooks"];
6557
6804
  function preflightConflict(repoHome2) {
6558
6805
  const candidates = [
6559
- join49(repoHome2, "shared", "settings.base.json"),
6560
- join49(repoHome2, "shared", "CLAUDE.md"),
6561
- join49(repoHome2, "path-map.json"),
6562
- join49(repoHome2, "hosts"),
6563
- join49(repoHome2, "shared")
6806
+ join50(repoHome2, "shared", "settings.base.json"),
6807
+ join50(repoHome2, "shared", "CLAUDE.md"),
6808
+ join50(repoHome2, "path-map.json"),
6809
+ join50(repoHome2, "hosts"),
6810
+ join50(repoHome2, "shared")
6564
6811
  ];
6565
6812
  for (const c of candidates) {
6566
- if (existsSync42(c)) return c;
6813
+ if (existsSync43(c)) return c;
6567
6814
  }
6568
6815
  return null;
6569
6816
  }
6817
+ function isAlreadyInitialized(repoHome2) {
6818
+ return preflightConflict(repoHome2) !== null;
6819
+ }
6570
6820
  function cmdInit(opts = {}) {
6571
6821
  const snapshot = opts.snapshot === true;
6572
6822
  const keepActions = opts.keepActions === true;
@@ -6578,25 +6828,25 @@ function cmdInit(opts = {}) {
6578
6828
  die(`already initialized; refusing to clobber ${conflict}`);
6579
6829
  }
6580
6830
  ensureOriginRepo(opts.repoName ?? DEFAULT_REPO_NAME, opts.run);
6581
- mkdirSync11(join49(repo, "shared"), { recursive: true });
6582
- mkdirSync11(join49(repo, "hosts"), { recursive: true });
6831
+ mkdirSync11(join50(repo, "shared"), { recursive: true });
6832
+ mkdirSync11(join50(repo, "hosts"), { recursive: true });
6583
6833
  for (const name of SHARED_KEEP_DIRS) {
6584
- mkdirSync11(join49(repo, "shared", name), { recursive: true });
6834
+ mkdirSync11(join50(repo, "shared", name), { recursive: true });
6585
6835
  }
6586
- const userClaudeMd = join49(claude, "CLAUDE.md");
6587
- if (!snapshot || !existsSync42(userClaudeMd)) {
6588
- writeFileSync6(join49(repo, "shared", "CLAUDE.md"), SHARED_CLAUDE_MD);
6836
+ const userClaudeMd = join50(claude, "CLAUDE.md");
6837
+ if (!snapshot || !existsSync43(userClaudeMd)) {
6838
+ writeFileSync6(join50(repo, "shared", "CLAUDE.md"), SHARED_CLAUDE_MD);
6589
6839
  item("created shared/CLAUDE.md");
6590
6840
  }
6591
6841
  for (const name of SHARED_KEEP_DIRS) {
6592
- writeFileSync6(join49(repo, "shared", name, ".gitkeep"), "");
6842
+ writeFileSync6(join50(repo, "shared", name, ".gitkeep"), "");
6593
6843
  item(`created shared/${name}/.gitkeep`);
6594
6844
  }
6595
- writeFileSync6(join49(repo, "hosts", ".gitkeep"), "");
6845
+ writeFileSync6(join50(repo, "hosts", ".gitkeep"), "");
6596
6846
  item("created hosts/.gitkeep");
6597
- writeJsonAtomic(join49(repo, "shared", "settings.base.json"), {});
6847
+ writeJsonAtomic(join50(repo, "shared", "settings.base.json"), {});
6598
6848
  item("created shared/settings.base.json");
6599
- writeJsonAtomic(join49(repo, "path-map.json"), { projects: {} });
6849
+ writeJsonAtomic(join50(repo, "path-map.json"), { projects: {} });
6600
6850
  item("created path-map.json");
6601
6851
  if (snapshot) {
6602
6852
  snapshotIntoShared({ projects: {} });
@@ -6659,6 +6909,47 @@ function maybeDisableRepoActions(repoHome2, run) {
6659
6909
  }
6660
6910
  }
6661
6911
 
6912
+ // src/init.prompt.ts
6913
+ init_config();
6914
+ init_utils();
6915
+ import { existsSync as existsSync44, readdirSync as readdirSync16, statSync as statSync11 } from "node:fs";
6916
+ import { join as join51 } from "node:path";
6917
+ import { createInterface as createInterface3 } from "node:readline/promises";
6918
+ function nonEmptyExists(path) {
6919
+ if (!existsSync44(path)) return false;
6920
+ try {
6921
+ if (statSync11(path).isDirectory()) return readdirSync16(path).length > 0;
6922
+ return true;
6923
+ } catch {
6924
+ return false;
6925
+ }
6926
+ }
6927
+ function hasExistingClaudeConfig(claudeHome2) {
6928
+ if (existsSync44(join51(claudeHome2, "settings.json"))) return true;
6929
+ return SHARED_LINKS.some((name) => nonEmptyExists(join51(claudeHome2, name)));
6930
+ }
6931
+ async function confirmSnapshotDefault(claudeHome2) {
6932
+ if (process.stdin.isTTY !== true || process.stdout.isTTY !== true) {
6933
+ log(
6934
+ `tip: ${claudeHome2} already has config; re-run 'nomad init --snapshot' to seed the repo from it.`
6935
+ );
6936
+ return false;
6937
+ }
6938
+ log(`Found existing config in ${claudeHome2}.`);
6939
+ const rl = createInterface3({ input: process.stdin, output: process.stdout });
6940
+ try {
6941
+ const answer = await rl.question("Seed the new repo from it? [Y/n] ");
6942
+ return !/^n(o)?$/i.test(answer.trim());
6943
+ } finally {
6944
+ rl.close();
6945
+ }
6946
+ }
6947
+ async function resolveSnapshotChoice(flagSnapshot, claudeHome2, confirm = confirmSnapshotDefault) {
6948
+ if (flagSnapshot) return true;
6949
+ if (!hasExistingClaudeConfig(claudeHome2)) return false;
6950
+ return confirm(claudeHome2);
6951
+ }
6952
+
6662
6953
  // src/nomad.dispatch.helpers.ts
6663
6954
  var REJECT = { ok: false, advance: 0 };
6664
6955
  function applyBool(seen, set) {
@@ -6918,7 +7209,7 @@ function parsePushArgs(argv) {
6918
7209
  // package.json
6919
7210
  var package_default = {
6920
7211
  name: "claude-nomad",
6921
- version: "0.53.3",
7212
+ version: "0.55.0",
6922
7213
  type: "module",
6923
7214
  description: "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
6924
7215
  keywords: [
@@ -7050,7 +7341,8 @@ var DEFAULT_HELP = [
7050
7341
  " init",
7051
7342
  "Create a private GitHub repo via gh (if none exists), scaffold shared/, hosts/, path-map."
7052
7343
  ),
7053
- row(" --snapshot", "Overlay the current ~/.claude/ into shared/ as the initial seed."),
7344
+ row(" --snapshot", "Seed shared/ from the current ~/.claude/. Without this flag, init"),
7345
+ cont("offers the same seed interactively when it detects existing config."),
7054
7346
  row(" --keep-actions", "Skip auto-disabling GitHub Actions on the private repo."),
7055
7347
  row(
7056
7348
  " --repo <name>",
@@ -7064,6 +7356,8 @@ var DEFAULT_HELP = [
7064
7356
  cont("`nomad push` would stage (a temp copy, never the live dir)."),
7065
7357
  row(" --check-schema", "Flag settings.json keys absent from the live published"),
7066
7358
  cont("Claude Code settings schema (needs network; degrades offline)."),
7359
+ row(" --check-remote", "Verify origin/main has shared/ and a valid path-map.json"),
7360
+ cont("(reads locally-cached remote ref; skips with a warning if unavailable)."),
7067
7361
  row(" --resume-cmd <id>", "Print `cd <abspath> && claude --resume <id>` for a session id"),
7068
7362
  cont("from ~/.claude/projects/."),
7069
7363
  "",
@@ -7126,7 +7420,8 @@ var DEFAULT_HELP = [
7126
7420
  "",
7127
7421
  row(" --version", "Print the installed CLI version as bare semver to stdout; exits 0."),
7128
7422
  "",
7129
- "Run `nomad doctor` to validate your setup. Edit shared/ or hosts/<HOST>.json",
7423
+ "You edit the sync repo (~/claude-nomad/); nomad regenerates ~/.claude/ on every",
7424
+ "pull. Run `nomad doctor` to validate your setup. Edit shared/ or hosts/<HOST>.json",
7130
7425
  "in the repo, never ~/.claude/settings.json directly (it is regenerated on",
7131
7426
  "every pull)."
7132
7427
  ].join("\n");
@@ -7135,15 +7430,15 @@ var DEFAULT_HELP = [
7135
7430
  init_config();
7136
7431
  init_utils();
7137
7432
  init_utils_json();
7138
- import { existsSync as existsSync43, readFileSync as readFileSync14, readdirSync as readdirSync15 } from "node:fs";
7139
- import { join as join50 } from "node:path";
7433
+ import { existsSync as existsSync45, readFileSync as readFileSync14, readdirSync as readdirSync17 } from "node:fs";
7434
+ import { join as join52 } from "node:path";
7140
7435
  function resumeCmd(sessionId) {
7141
7436
  if (!/^[A-Za-z0-9_-]+$/.test(sessionId) || sessionId.length > 128) {
7142
7437
  fail(`invalid session id: ${sessionId}`);
7143
7438
  process.exit(1);
7144
7439
  }
7145
- const projectsRoot = join50(claudeHome(), "projects");
7146
- if (!existsSync43(projectsRoot)) {
7440
+ const projectsRoot = join52(claudeHome(), "projects");
7441
+ if (!existsSync45(projectsRoot)) {
7147
7442
  fail(`${projectsRoot} does not exist`);
7148
7443
  process.exit(1);
7149
7444
  }
@@ -7157,8 +7452,8 @@ function resumeCmd(sessionId) {
7157
7452
  fail(`no cwd field found in ${jsonlPath}`);
7158
7453
  process.exit(1);
7159
7454
  }
7160
- const mapPath = join50(repoHome(), "path-map.json");
7161
- if (!existsSync43(mapPath)) {
7455
+ const mapPath = join52(repoHome(), "path-map.json");
7456
+ if (!existsSync45(mapPath)) {
7162
7457
  fail("path-map.json missing");
7163
7458
  process.exit(1);
7164
7459
  }
@@ -7180,9 +7475,9 @@ function resumeCmd(sessionId) {
7180
7475
  console.log(`cd ${shQuote(hit.localPath)} && claude --resume ${shQuote(sessionId)}`);
7181
7476
  }
7182
7477
  function findTranscriptPath(projectsRoot, sessionId) {
7183
- for (const dir of readdirSync15(projectsRoot)) {
7184
- const candidate = join50(projectsRoot, dir, `${sessionId}.jsonl`);
7185
- if (existsSync43(candidate)) return candidate;
7478
+ for (const dir of readdirSync17(projectsRoot)) {
7479
+ const candidate = join52(projectsRoot, dir, `${sessionId}.jsonl`);
7480
+ if (existsSync45(candidate)) return candidate;
7186
7481
  }
7187
7482
  return null;
7188
7483
  }
@@ -7265,8 +7560,9 @@ try {
7265
7560
  console.error("usage: nomad init [--snapshot] [--keep-actions] [--repo <name>]");
7266
7561
  process.exit(1);
7267
7562
  }
7563
+ const snapshot = isAlreadyInitialized(repoHome()) ? initArgs.snapshot : await resolveSnapshotChoice(initArgs.snapshot, claudeHome());
7268
7564
  cmdInit({
7269
- snapshot: initArgs.snapshot,
7565
+ snapshot,
7270
7566
  keepActions: initArgs.keepActions,
7271
7567
  repoName: initArgs.repoName
7272
7568
  });
@@ -7284,7 +7580,7 @@ try {
7284
7580
  console.error("usage: nomad update");
7285
7581
  process.exit(1);
7286
7582
  }
7287
- cmdUpdate();
7583
+ cmdUpdate(package_default.version);
7288
7584
  break;
7289
7585
  }
7290
7586
  case "adopt": {
@@ -7323,7 +7619,7 @@ try {
7323
7619
  const parsed = parseDoctorArgs(process.argv.slice(3));
7324
7620
  if (parsed.kind === "error") {
7325
7621
  console.error(
7326
- "usage: nomad doctor [--check-shared] [--check-schema] [--verbose|--all|-v] | --resume-cmd <session-id>"
7622
+ "usage: nomad doctor [--check-shared] [--check-schema] [--check-remote] [--verbose|--all|-v] | --resume-cmd <session-id>"
7327
7623
  );
7328
7624
  process.exit(1);
7329
7625
  } else if (parsed.kind === "resume") {
@@ -7332,6 +7628,7 @@ try {
7332
7628
  cmdDoctor({
7333
7629
  checkShared: parsed.checkShared,
7334
7630
  checkSchema: parsed.checkSchema,
7631
+ checkRemote: parsed.checkRemote,
7335
7632
  verbose: parsed.verbose
7336
7633
  });
7337
7634
  }