claude-nomad 0.65.0 → 0.66.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
@@ -51,19 +51,34 @@ function isSecretFileName(name) {
51
51
  const stripped = stripTrailingDotsAndWhitespace(name);
52
52
  return SECRET_FILE_PATTERNS.some((re) => re.test(stripped));
53
53
  }
54
- function isDeniedName(blockSet, name) {
54
+ function matchDeniedName(blockSet, name) {
55
55
  const stripped = stripTrailingDotsAndWhitespace(name);
56
- return blockSet.has(name) || blockSet.has(name.toLowerCase()) || blockSet.has(stripped) || blockSet.has(stripped.toLowerCase()) || isSecretFileName(name);
56
+ for (const candidate of [name, name.toLowerCase(), stripped, stripped.toLowerCase()]) {
57
+ if (blockSet.has(candidate)) return { axis: "name", entry: candidate };
58
+ }
59
+ return isSecretFileName(name) ? { axis: "shape" } : null;
60
+ }
61
+ function isDeniedName(blockSet, name) {
62
+ return matchDeniedName(blockSet, name) !== null;
57
63
  }
58
64
  function isClaudeExtraName(name) {
59
65
  return stripTrailingDotsAndWhitespace(name).toLowerCase() === ".claude";
60
66
  }
67
+ function regionKey(segment) {
68
+ return stripTrailingDotsAndWhitespace(segment).toLowerCase();
69
+ }
61
70
  function isExtrasScoped(segments) {
62
- return segments[0] === "shared" && segments[1] === "extras";
71
+ return segments[0] === "shared" && segments.length > 1 && regionKey(segments[1]) === "extras";
72
+ }
73
+ function isSharedNameScoped(segments) {
74
+ return segments[0] === "shared" && segments.length > 1 && !UNFILTERED_SHARED_REGIONS.has(regionKey(segments[1]));
63
75
  }
64
76
  function blockSetFor(segments) {
65
- if (!isExtrasScoped(segments)) return NEVER_SYNC;
66
- return isClaudeExtraName(segments[3] ?? "") ? CLAUDE_EXTRA_NEVER_SYNC : ALWAYS_NEVER_SYNC;
77
+ if (isExtrasScoped(segments)) {
78
+ return isClaudeExtraName(segments[3] ?? "") ? CLAUDE_EXTRA_NEVER_SYNC : ALWAYS_NEVER_SYNC;
79
+ }
80
+ if (isSharedNameScoped(segments)) return ALWAYS_NEVER_SYNC;
81
+ return NEVER_SYNC;
67
82
  }
68
83
  function deniedSegmentFor(path) {
69
84
  const segments = path.split("/");
@@ -77,7 +92,7 @@ function deniedSegmentFor(path) {
77
92
  function isNeverSync(path) {
78
93
  return deniedSegmentFor(path) !== null;
79
94
  }
80
- var NEVER_SYNC, CLAUDE_EXTRA_NEVER_SYNC, ALWAYS_NEVER_SYNC, SECRET_FILE_PATTERNS;
95
+ var NEVER_SYNC, CLAUDE_EXTRA_NEVER_SYNC, ALWAYS_NEVER_SYNC, SECRET_FILE_PATTERNS, UNFILTERED_SHARED_REGIONS;
81
96
  var init_config_never_sync = __esm({
82
97
  "src/config.never-sync.ts"() {
83
98
  "use strict";
@@ -128,6 +143,7 @@ var init_config_never_sync = __esm({
128
143
  /^\.git-credentials$/i,
129
144
  /^credentials$/i
130
145
  ];
146
+ UNFILTERED_SHARED_REGIONS = /* @__PURE__ */ new Set(["extras", "projects"]);
131
147
  }
132
148
  });
133
149
 
@@ -787,7 +803,7 @@ import {
787
803
  cpSync as cpSync3,
788
804
  existsSync as existsSync3,
789
805
  fsyncSync,
790
- lstatSync as lstatSync3,
806
+ lstatSync as lstatSync4,
791
807
  mkdirSync,
792
808
  openSync,
793
809
  renameSync,
@@ -795,7 +811,7 @@ import {
795
811
  symlinkSync,
796
812
  writeFileSync
797
813
  } from "node:fs";
798
- import { dirname, join as join4, relative as relative2, sep as sep2 } from "node:path";
814
+ import { dirname, join as join5, relative as relative3, sep as sep2 } from "node:path";
799
815
  function isRetryableRenameCode(code) {
800
816
  return code === "EPERM" || code === "EBUSY";
801
817
  }
@@ -846,14 +862,14 @@ function nowTimestamp() {
846
862
  }
847
863
  function freshBackupTs(backupRoot) {
848
864
  const base = nowTimestamp();
849
- if (!existsSync3(join4(backupRoot, base))) return base;
865
+ if (!existsSync3(join5(backupRoot, base))) return base;
850
866
  let n = 1;
851
- while (existsSync3(join4(backupRoot, `${base}-${n}`))) n++;
867
+ while (existsSync3(join5(backupRoot, `${base}-${n}`))) n++;
852
868
  return `${base}-${n}`;
853
869
  }
854
870
  function ensureSymlink(linkPath, target) {
855
871
  if (existsSync3(linkPath)) {
856
- if (lstatSync3(linkPath).isSymbolicLink()) return;
872
+ if (lstatSync4(linkPath).isSymbolicLink()) return;
857
873
  die(`${linkPath} exists and is not a symlink. Move it aside first.`);
858
874
  }
859
875
  mkdirSync(dirname(linkPath), { recursive: true });
@@ -861,21 +877,22 @@ function ensureSymlink(linkPath, target) {
861
877
  log(`linked ${linkPath} -> ${target}`);
862
878
  }
863
879
  function backupUnder(absPath, anchor, destRoot) {
864
- if (!existsSync3(absPath)) return;
865
- const rel = relative2(anchor, absPath);
866
- if (rel === "" || rel === ".." || rel.startsWith(`..${sep2}`)) return;
867
- const dst = join4(destRoot, rel);
880
+ if (!existsSync3(absPath)) return false;
881
+ const rel = relative3(anchor, absPath);
882
+ if (rel === "" || rel === ".." || rel.startsWith(`..${sep2}`)) return false;
883
+ const dst = join5(destRoot, rel);
868
884
  mkdirSync(dirname(dst), { recursive: true });
869
885
  cpSync3(absPath, dst, { recursive: true, force: false, preserveTimestamps: true });
886
+ return true;
870
887
  }
871
888
  function backupBeforeWrite(absPath, ts) {
872
- backupUnder(absPath, claudeHome(), join4(backupBase(), ts));
889
+ return backupUnder(absPath, claudeHome(), join5(backupBase(), ts));
873
890
  }
874
891
  function backupRepoWrite(absPath, ts, repoHome2) {
875
- backupUnder(absPath, repoHome2, join4(backupBase(), ts, "repo"));
892
+ backupUnder(absPath, repoHome2, join5(backupBase(), ts, "repo"));
876
893
  }
877
894
  function backupExtrasWrite(absPath, ts, projectRoot) {
878
- backupUnder(absPath, projectRoot, join4(backupBase(), ts, "extras", encodePath(projectRoot)));
895
+ backupUnder(absPath, projectRoot, join5(backupBase(), ts, "extras", encodePath(projectRoot)));
879
896
  }
880
897
  var RENAME_RETRY_MAX_ATTEMPTS, RENAME_RETRY_BACKOFF_MS;
881
898
  var init_utils_fs = __esm({
@@ -891,12 +908,12 @@ var init_utils_fs = __esm({
891
908
 
892
909
  // src/commands.pull.wedge.ts
893
910
  import { execFileSync as execFileSync3 } from "node:child_process";
894
- import { existsSync as existsSync15 } from "node:fs";
895
- import { join as join17 } from "node:path";
911
+ import { existsSync as existsSync16 } from "node:fs";
912
+ import { join as join20 } from "node:path";
896
913
  function detectWedge(repo) {
897
- const g = join17(repo, ".git");
898
- if (existsSync15(join17(g, "rebase-merge")) || existsSync15(join17(g, "rebase-apply"))) return "rebase";
899
- if (existsSync15(join17(g, "MERGE_HEAD"))) return "merge";
914
+ const g = join20(repo, ".git");
915
+ if (existsSync16(join20(g, "rebase-merge")) || existsSync16(join20(g, "rebase-apply"))) return "rebase";
916
+ if (existsSync16(join20(g, "MERGE_HEAD"))) return "merge";
900
917
  return null;
901
918
  }
902
919
  function probeUnmergedIndex(repo) {
@@ -966,15 +983,15 @@ var init_commands_pull_wedge = __esm({
966
983
  });
967
984
 
968
985
  // src/push-gitleaks.config.ts
969
- import { existsSync as existsSync16, mkdtempSync, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
986
+ import { existsSync as existsSync17, mkdtempSync, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
970
987
  import { tmpdir } from "node:os";
971
- import { join as join18 } from "node:path";
988
+ import { join as join21 } from "node:path";
972
989
  import { fileURLToPath } from "node:url";
973
990
  function resolveTomlPath(repo = repoHome()) {
974
- const repoToml = join18(repo, ".gitleaks.toml");
975
- if (existsSync16(repoToml)) return repoToml;
991
+ const repoToml = join21(repo, ".gitleaks.toml");
992
+ if (existsSync17(repoToml)) return repoToml;
976
993
  const bundled = fileURLToPath(new URL("../.gitleaks.toml", import.meta.url));
977
- return existsSync16(bundled) ? bundled : null;
994
+ return existsSync17(bundled) ? bundled : null;
978
995
  }
979
996
  function assertOverlayAllowlistsScoped(overlayBody) {
980
997
  if (OVERLAY_INLINE_ALLOWLIST_RE.test(overlayBody)) {
@@ -1012,17 +1029,17 @@ function buildOverlayTempConfig(overlayBody, bundled) {
1012
1029
  path = ${JSON.stringify(bundled)}
1013
1030
 
1014
1031
  ${overlayBody}`;
1015
- const tempPath = mkdtempSync(join18(tmpdir(), "nomad-gitleaks-cfg-"));
1016
- const configPath = join18(tempPath, "config.toml");
1032
+ const tempPath = mkdtempSync(join21(tmpdir(), "nomad-gitleaks-cfg-"));
1033
+ const configPath = join21(tempPath, "config.toml");
1017
1034
  writeFileSync3(configPath, tempBody, { mode: 384, flag: "wx" });
1018
1035
  return { configPath, tempPath };
1019
1036
  }
1020
1037
  function resolveTomlConfig() {
1021
1038
  const repo = repoHome();
1022
- const overlayPath = join18(repo, ".gitleaks.overlay.toml");
1023
- const repoToml = join18(repo, ".gitleaks.toml");
1039
+ const overlayPath = join21(repo, ".gitleaks.overlay.toml");
1040
+ const repoToml = join21(repo, ".gitleaks.toml");
1024
1041
  const bundled = resolveTomlPath(repo);
1025
- if (!existsSync16(overlayPath)) {
1042
+ if (!existsSync17(overlayPath)) {
1026
1043
  return { path: bundled, tempPath: null };
1027
1044
  }
1028
1045
  if (bundled === repoToml) {
@@ -1108,9 +1125,9 @@ var init_autostash_guard = __esm({
1108
1125
 
1109
1126
  // src/push-checks.ts
1110
1127
  import { execFileSync as execFileSync4 } from "node:child_process";
1111
- import { readdirSync as readdirSync7, rmSync as rmSync7 } from "node:fs";
1128
+ import { readdirSync as readdirSync8, rmSync as rmSync7 } from "node:fs";
1112
1129
  import { homedir as homedir2, platform } from "node:os";
1113
- import { delimiter, join as join19 } from "node:path";
1130
+ import { delimiter, join as join22 } from "node:path";
1114
1131
  function gitleaksInstallHint() {
1115
1132
  const head = "gitleaks not on PATH (required for nomad push). Install:";
1116
1133
  const plat = platform();
@@ -1150,23 +1167,23 @@ function gitleaksInstallHint() {
1150
1167
  }
1151
1168
  function findGitlinks(dir) {
1152
1169
  const hits = [];
1153
- function walk(current) {
1170
+ function walk2(current) {
1154
1171
  let entries;
1155
1172
  try {
1156
- entries = readdirSync7(current, { withFileTypes: true });
1173
+ entries = readdirSync8(current, { withFileTypes: true });
1157
1174
  } catch {
1158
1175
  return;
1159
1176
  }
1160
1177
  for (const e of entries) {
1161
- const p = join19(current, e.name);
1178
+ const p = join22(current, e.name);
1162
1179
  if (e.name === ".git") {
1163
1180
  hits.push(p);
1164
1181
  continue;
1165
1182
  }
1166
- if (e.isDirectory()) walk(p);
1183
+ if (e.isDirectory()) walk2(p);
1167
1184
  }
1168
1185
  }
1169
- walk(dir);
1186
+ walk2(dir);
1170
1187
  return hits;
1171
1188
  }
1172
1189
  function probeGitleaks() {
@@ -1222,7 +1239,7 @@ var init_push_checks = __esm({
1222
1239
  import { execFileSync as execFileSync8 } from "node:child_process";
1223
1240
  import { mkdirSync as mkdirSync3, mkdtempSync as mkdtempSync2, readFileSync as readFileSync6, rmSync as rmSync8 } from "node:fs";
1224
1241
  import { homedir as homedir3 } from "node:os";
1225
- import { join as join23 } from "node:path";
1242
+ import { join as join26 } from "node:path";
1226
1243
  function toFinding(entry) {
1227
1244
  if (typeof entry !== "object" || entry === null) return null;
1228
1245
  const e = entry;
@@ -1270,9 +1287,9 @@ function resolveScanFailure(reportPath, e, forwardStreams) {
1270
1287
  return report;
1271
1288
  }
1272
1289
  function scanStagedTree(repoDir, forwardStreams = false) {
1273
- const cacheDir = join23(homedir3(), ".cache", "claude-nomad");
1290
+ const cacheDir = join26(homedir3(), ".cache", "claude-nomad");
1274
1291
  mkdirSync3(cacheDir, { recursive: true });
1275
- const reportPath = join23(cacheDir, `gitleaks-${nowTimestamp()}-${process.pid}.json`);
1292
+ const reportPath = join26(cacheDir, `gitleaks-${nowTimestamp()}-${process.pid}.json`);
1276
1293
  const { path: toml, tempPath } = resolveTomlConfig();
1277
1294
  const args = [
1278
1295
  "protect",
@@ -1300,14 +1317,14 @@ function scanStagedTree(repoDir, forwardStreams = false) {
1300
1317
  }
1301
1318
  }
1302
1319
  function scanFile(filePath, forwardStreams = false, timeoutMs = GITLEAKS_SCAN_TIMEOUT_MS) {
1303
- const cacheDir = join23(homedir3(), ".cache", "claude-nomad");
1320
+ const cacheDir = join26(homedir3(), ".cache", "claude-nomad");
1304
1321
  mkdirSync3(cacheDir, { recursive: true });
1305
1322
  const { path: toml, tempPath } = resolveTomlConfig();
1306
1323
  let reportDir;
1307
1324
  let reportPath;
1308
1325
  try {
1309
- reportDir = mkdtempSync2(join23(cacheDir, `gitleaks-file-${nowTimestamp()}-${process.pid}-`));
1310
- reportPath = join23(reportDir, "report.json");
1326
+ reportDir = mkdtempSync2(join26(cacheDir, `gitleaks-file-${nowTimestamp()}-${process.pid}-`));
1327
+ reportPath = join26(reportDir, "report.json");
1311
1328
  const args = [
1312
1329
  "detect",
1313
1330
  "--no-git",
@@ -1531,16 +1548,314 @@ var init_push_leak_verdict = __esm({
1531
1548
  });
1532
1549
 
1533
1550
  // src/commands.adopt.ts
1551
+ import { existsSync as existsSync5, lstatSync as lstatSync7 } from "node:fs";
1552
+ import { join as join8 } from "node:path";
1553
+
1554
+ // src/commands.adopt.recover.ts
1555
+ import { lstatSync as lstatSync6, rmSync as rmSync4 } from "node:fs";
1556
+ import { dirname as dirname2, join as join7, resolve as resolve2 } from "node:path";
1557
+
1558
+ // src/commands.adopt.scan.ts
1534
1559
  init_config();
1535
- init_config_sharedDirs_guard();
1536
1560
  init_exit_codes();
1537
- import { cpSync as cpSync4, existsSync as existsSync5, lstatSync as lstatSync5, rmSync as rmSync4 } from "node:fs";
1538
- import { join as join6 } from "node:path";
1561
+ init_utils();
1562
+ import { lstatSync, readdirSync } from "node:fs";
1563
+ import { join as join2, relative } from "node:path";
1564
+ function toForwardSlash(p) {
1565
+ return p.replaceAll("\\", "/");
1566
+ }
1567
+ function walk(root, dir, out) {
1568
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
1569
+ const match = matchDeniedName(ALWAYS_NEVER_SYNC, entry.name);
1570
+ if (match !== null) {
1571
+ out.push({
1572
+ path: toForwardSlash(relative(root, join2(dir, entry.name))),
1573
+ matched: match.axis === "name" ? match.entry : null
1574
+ });
1575
+ continue;
1576
+ }
1577
+ if (entry.isDirectory()) walk(root, join2(dir, entry.name), out);
1578
+ }
1579
+ }
1580
+ function scanDeniedEntries(root) {
1581
+ const stat = lstatSync(root, { throwIfNoEntry: false });
1582
+ if (!stat?.isDirectory()) return [];
1583
+ const out = [];
1584
+ walk(root, root, out);
1585
+ out.sort((a, b) => a.path.localeCompare(b.path));
1586
+ return out;
1587
+ }
1588
+ function scanOrFatal(name, root, state) {
1589
+ try {
1590
+ return scanDeniedEntries(root);
1591
+ } catch (err) {
1592
+ const message = err?.message;
1593
+ const text = typeof message === "string" ? message : String(err);
1594
+ throw new NomadFatal(
1595
+ `cannot adopt ${name}: could not scan ${root} for never-sync content (${text}). ${state()} Check that it is readable and not being written to, then run \`nomad adopt ${name}\` again.`,
1596
+ { code: EXIT.GENERIC_FAILURE }
1597
+ );
1598
+ }
1599
+ }
1600
+ function describeMatch(hit) {
1601
+ return hit.matched === null ? "matches a credential filename shape" : `matches the never-sync name "${hit.matched}"`;
1602
+ }
1603
+ function remedies(hits, root, name) {
1604
+ const subject = hits.length === 1 ? "that path" : "those paths";
1605
+ let out = ` Move ${subject} out of ${root} and run \`nomad adopt ${name}\` again.`;
1606
+ if (hits.some((hit) => hit.matched !== null)) {
1607
+ out += " A never-sync name is matched by spelling alone and never by content, so renaming a path listed above as a never-sync name clears the refusal just as well.";
1608
+ }
1609
+ if (hits.some((hit) => hit.matched === null)) {
1610
+ out += " A credential filename shape is matched by the extension or by the whole filename, so renaming a path listed above as a credential filename shape clears the refusal only if the new name falls outside that shape too.";
1611
+ }
1612
+ return out;
1613
+ }
1614
+ function deniedEntriesRefusal(name, root, hits, clauses) {
1615
+ const lines = hits.map((hit) => ` ${hit.path} (${describeMatch(hit)})`).join("\n");
1616
+ return new NomadFatal(
1617
+ `cannot adopt ${name}: ${clauses.found}:
1618
+ ${lines}
1619
+ ${clauses.state}${remedies(hits, root, name)}`,
1620
+ { code: EXIT.GENERIC_FAILURE }
1621
+ );
1622
+ }
1623
+ function refuseDeniedEntries(name, root) {
1624
+ const nothingChanged = "Nothing was changed.";
1625
+ const hits = scanOrFatal(name, root, () => nothingChanged);
1626
+ if (hits.length === 0) return;
1627
+ throw deniedEntriesRefusal(name, root, hits, {
1628
+ found: `${root} contains never-sync content`,
1629
+ state: nothingChanged
1630
+ });
1631
+ }
1632
+
1633
+ // src/commands.adopt.recover.ts
1634
+ init_config();
1635
+ init_exit_codes();
1636
+
1637
+ // src/extras-sync.core.ts
1638
+ init_config();
1639
+ import { cpSync as cpSync2, existsSync as existsSync2, lstatSync as lstatSync3, readdirSync as readdirSync3, readFileSync as readFileSync2, rmSync as rmSync2 } from "node:fs";
1640
+ import { basename as basename2, join as join4, relative as relative2 } from "node:path";
1641
+
1642
+ // src/extras-sync.collision.ts
1643
+ init_utils();
1644
+ import { cpSync, existsSync, lstatSync as lstatSync2, readdirSync as readdirSync2, rmSync } from "node:fs";
1645
+ import { basename, join as join3 } from "node:path";
1646
+ function quoted(p) {
1647
+ return `"${p}"`;
1648
+ }
1649
+ function stripCollidingDstSymlinks(src, dst, isExcluded) {
1650
+ if (!existsSync(dst)) return;
1651
+ for (const name of readdirSync2(src)) {
1652
+ if (isExcluded(name)) continue;
1653
+ const dstPath = join3(dst, name);
1654
+ const dstStat = lstatSync2(dstPath, { throwIfNoEntry: false });
1655
+ if (dstStat === void 0) continue;
1656
+ if (dstStat.isSymbolicLink()) {
1657
+ rmSync(dstPath, { recursive: true, force: true });
1658
+ } else if (dstStat.isDirectory() && lstatSync2(join3(src, name)).isDirectory()) {
1659
+ stripCollidingDstSymlinks(join3(src, name), dstPath, isExcluded);
1660
+ }
1661
+ }
1662
+ }
1663
+ function cpSyncGuarded(src, dst, filter, label) {
1664
+ try {
1665
+ cpSync(src, dst, { recursive: true, force: true, verbatimSymlinks: true, filter });
1666
+ } catch (err) {
1667
+ const e = err;
1668
+ if (e.code === "EINVAL" || e.code === "ENOTEMPTY" || e.code === "ERR_FS_CP_NON_DIR_TO_DIR" || e.code === "ERR_FS_CP_DIR_TO_NON_DIR" || process.platform === "win32" && e.errno === 145) {
1669
+ throw new NomadFatal(
1670
+ `${label}: type collision copying ${quoted(src)} -> ${quoted(dst)} (${e.path ?? "unknown path"}): a file/directory type changed upstream; run nomad pull --force-remote to recover`
1671
+ );
1672
+ }
1673
+ throw err;
1674
+ }
1675
+ }
1676
+ function prunePreservingBy(src, dst, isPreserved, isRootPreserved) {
1677
+ for (const name of readdirSync2(dst)) {
1678
+ if (isPreserved(name) || isRootPreserved?.(name) === true) continue;
1679
+ const dstPath = join3(dst, name);
1680
+ const srcStat = lstatSync2(join3(src, name), { throwIfNoEntry: false });
1681
+ if (srcStat === void 0) {
1682
+ rmSync(dstPath, { recursive: true, force: true });
1683
+ continue;
1684
+ }
1685
+ const dstStat = lstatSync2(dstPath);
1686
+ if (srcStat.isDirectory() && dstStat.isDirectory()) {
1687
+ prunePreservingBy(join3(src, name), dstPath, isPreserved);
1688
+ } else if (srcStat.isDirectory() !== dstStat.isDirectory()) {
1689
+ rmSync(dstPath, { recursive: true, force: true });
1690
+ }
1691
+ }
1692
+ }
1693
+ function copyExtrasFilteredPreservingBy(src, dst, isPreserved, isRootPreserved) {
1694
+ const dstStat = lstatSync2(dst, { throwIfNoEntry: false });
1695
+ if (dstStat !== void 0) {
1696
+ if (dstStat.isDirectory()) {
1697
+ const srcStat = lstatSync2(src, { throwIfNoEntry: false });
1698
+ if (srcStat !== void 0 && !srcStat.isDirectory()) {
1699
+ throw new NomadFatal(
1700
+ `copyExtrasFilteredPreservingBy: type mismatch copying ${quoted(src)} -> ${quoted(dst)}: the repo entry is a file but the local entry is a directory; run nomad pull --force-remote to recover`
1701
+ );
1702
+ }
1703
+ prunePreservingBy(src, dst, isPreserved, isRootPreserved);
1704
+ } else {
1705
+ rmSync(dst, { recursive: true, force: true });
1706
+ }
1707
+ }
1708
+ stripCollidingDstSymlinks(src, dst, isPreserved);
1709
+ cpSync(src, dst, {
1710
+ recursive: true,
1711
+ force: true,
1712
+ verbatimSymlinks: true,
1713
+ filter: (srcEntry) => srcEntry === src || !isPreserved(basename(srcEntry))
1714
+ });
1715
+ }
1716
+
1717
+ // src/extras-sync.guards.ts
1718
+ init_utils();
1719
+ init_config_sharedDirs_guard();
1720
+ import { isAbsolute, normalize, sep } from "node:path";
1721
+ function assertSafeLocalRoot(localRoot, logical) {
1722
+ if (!isAbsolute(localRoot)) {
1723
+ throw new NomadFatal(
1724
+ `invalid localRoot for ${logical} in path-map.json: ${JSON.stringify(localRoot)} (must be absolute)`
1725
+ );
1726
+ }
1727
+ const canonical = process.platform === "win32" ? localRoot.split("/").join(sep) : localRoot;
1728
+ if (canonical !== normalize(canonical)) {
1729
+ throw new NomadFatal(
1730
+ `invalid localRoot for ${logical} in path-map.json: ${JSON.stringify(localRoot)} (must be already-normalized; no '..' or redundant segments)`
1731
+ );
1732
+ }
1733
+ }
1734
+
1735
+ // src/extras-sync.core.ts
1736
+ init_utils();
1737
+ init_utils_json();
1738
+ function loadValidatedExtras(opts) {
1739
+ const repo = repoHome();
1740
+ const mapPath = join4(repo, "path-map.json");
1741
+ const repoExtras = join4(repo, "shared", "extras");
1742
+ if (!existsSync2(mapPath) || opts.requireRepoExtras === true && !existsSync2(repoExtras)) {
1743
+ if (opts.missingMsg !== void 0) log(opts.missingMsg);
1744
+ return null;
1745
+ }
1746
+ const map = readPathMap(mapPath);
1747
+ const extrasMap = map.extras ?? {};
1748
+ if (Object.keys(extrasMap).length === 0) return null;
1749
+ for (const logical of Object.keys(extrasMap)) {
1750
+ assertSafeLogical(logical);
1751
+ const localRoot = map.projects[logical]?.[HOST];
1752
+ if (localRoot && localRoot !== "TBD") assertSafeLocalRoot(localRoot, logical);
1753
+ }
1754
+ return { map, extrasMap };
1755
+ }
1756
+ function* eachExtrasTarget(v, counts) {
1757
+ const whitelist = SUPPORTED_EXTRAS;
1758
+ for (const [logical, dirnames] of Object.entries(v.extrasMap)) {
1759
+ const localRoot = v.map.projects[logical]?.[HOST];
1760
+ if (!localRoot || localRoot === "TBD") {
1761
+ counts.unmapped++;
1762
+ continue;
1763
+ }
1764
+ for (const dirname15 of dirnames) {
1765
+ if (!whitelist.includes(dirname15)) {
1766
+ counts.skipped++;
1767
+ continue;
1768
+ }
1769
+ yield { logical, localRoot, dirname: dirname15 };
1770
+ }
1771
+ }
1772
+ }
1773
+ function copyExtrasOverlayFiltered(src, dst, blockSet) {
1774
+ const dstStat = lstatSync3(dst, { throwIfNoEntry: false });
1775
+ if (dstStat !== void 0 && !dstStat.isDirectory()) {
1776
+ rmSync2(dst, { recursive: true, force: true });
1777
+ }
1778
+ stripCollidingDstSymlinks(src, dst, (name) => isDeniedName(blockSet, name));
1779
+ cpSyncGuarded(
1780
+ src,
1781
+ dst,
1782
+ (srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename2(srcEntry)),
1783
+ "copyExtrasOverlayFiltered"
1784
+ );
1785
+ }
1786
+ function copyExtrasOverlaySkipDiverged(src, dst, blockSet, divergedSet) {
1787
+ stripCollidingDstSymlinks(src, dst, (name) => isDeniedName(blockSet, name));
1788
+ cpSyncGuarded(
1789
+ src,
1790
+ dst,
1791
+ (srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename2(srcEntry)) && !divergedSet.has(relative2(src, srcEntry)),
1792
+ "copyExtrasOverlaySkipDiverged"
1793
+ );
1794
+ }
1795
+ function copyExtras(src, dst) {
1796
+ rmSync2(dst, { recursive: true, force: true });
1797
+ cpSync2(src, dst, { recursive: true, force: true, verbatimSymlinks: true });
1798
+ }
1799
+ function copyExtrasFileSkipDiverged(src, dst) {
1800
+ if (existsSync2(dst)) {
1801
+ let dstBuf;
1802
+ try {
1803
+ dstBuf = readFileSync2(dst);
1804
+ } catch {
1805
+ return;
1806
+ }
1807
+ if (!readFileSync2(src).equals(dstBuf)) return;
1808
+ }
1809
+ copyExtras(src, dst);
1810
+ }
1811
+ function extrasDenySet(dirname15) {
1812
+ return isClaudeExtraName(dirname15) ? CLAUDE_EXTRA_NEVER_SYNC : ALWAYS_NEVER_SYNC;
1813
+ }
1814
+ function copyExtrasFiltered(src, dst, blockSet) {
1815
+ rmSync2(dst, { recursive: true, force: true });
1816
+ cpSync2(src, dst, {
1817
+ recursive: true,
1818
+ force: true,
1819
+ verbatimSymlinks: true,
1820
+ filter: (srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename2(srcEntry))
1821
+ });
1822
+ }
1823
+ function prunePreservingDenied(src, dst, blockSet) {
1824
+ for (const name of readdirSync3(dst)) {
1825
+ if (isDeniedName(blockSet, name)) continue;
1826
+ const dstPath = join4(dst, name);
1827
+ const srcStat = lstatSync3(join4(src, name), { throwIfNoEntry: false });
1828
+ if (srcStat === void 0) {
1829
+ rmSync2(dstPath, { recursive: true, force: true });
1830
+ continue;
1831
+ }
1832
+ const dstStat = lstatSync3(dstPath);
1833
+ if (srcStat.isDirectory() && dstStat.isDirectory()) {
1834
+ prunePreservingDenied(join4(src, name), dstPath, blockSet);
1835
+ } else if (srcStat.isDirectory() !== dstStat.isDirectory()) {
1836
+ rmSync2(dstPath, { recursive: true, force: true });
1837
+ }
1838
+ }
1839
+ }
1840
+ function copyExtrasFilteredPreserving(src, dst, blockSet) {
1841
+ const dstStat = lstatSync3(dst, { throwIfNoEntry: false });
1842
+ if (dstStat !== void 0) {
1843
+ if (dstStat.isDirectory()) prunePreservingDenied(src, dst, blockSet);
1844
+ else rmSync2(dst, { recursive: true, force: true });
1845
+ }
1846
+ stripCollidingDstSymlinks(src, dst, (name) => isDeniedName(blockSet, name));
1847
+ cpSync2(src, dst, {
1848
+ recursive: true,
1849
+ force: true,
1850
+ verbatimSymlinks: true,
1851
+ filter: (srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename2(srcEntry))
1852
+ });
1853
+ }
1539
1854
 
1540
1855
  // src/links.ts
1541
1856
  init_config();
1542
- import { existsSync as existsSync4, lstatSync as lstatSync4, rmSync as rmSync3 } from "node:fs";
1543
- import { join as join5 } from "node:path";
1857
+ import { existsSync as existsSync4, lstatSync as lstatSync5, rmSync as rmSync3 } from "node:fs";
1858
+ import { join as join6 } from "node:path";
1544
1859
 
1545
1860
  // src/hooks-filter.ts
1546
1861
  init_config();
@@ -1829,224 +2144,6 @@ function buildCaptureSubset(merged, settings, opts) {
1829
2144
  return out;
1830
2145
  }
1831
2146
 
1832
- // src/extras-sync.core.ts
1833
- init_config();
1834
- import { cpSync as cpSync2, existsSync as existsSync2, lstatSync as lstatSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, rmSync as rmSync2 } from "node:fs";
1835
- import { basename as basename2, join as join3, relative } from "node:path";
1836
-
1837
- // src/extras-sync.collision.ts
1838
- init_utils();
1839
- import { cpSync, existsSync, lstatSync, readdirSync, rmSync } from "node:fs";
1840
- import { basename, join as join2 } from "node:path";
1841
- function quoted(p) {
1842
- return `"${p}"`;
1843
- }
1844
- function stripCollidingDstSymlinks(src, dst, isExcluded) {
1845
- if (!existsSync(dst)) return;
1846
- for (const name of readdirSync(src)) {
1847
- if (isExcluded(name)) continue;
1848
- const dstPath = join2(dst, name);
1849
- const dstStat = lstatSync(dstPath, { throwIfNoEntry: false });
1850
- if (dstStat === void 0) continue;
1851
- if (dstStat.isSymbolicLink()) {
1852
- rmSync(dstPath, { recursive: true, force: true });
1853
- } else if (dstStat.isDirectory() && lstatSync(join2(src, name)).isDirectory()) {
1854
- stripCollidingDstSymlinks(join2(src, name), dstPath, isExcluded);
1855
- }
1856
- }
1857
- }
1858
- function cpSyncGuarded(src, dst, filter, label) {
1859
- try {
1860
- cpSync(src, dst, { recursive: true, force: true, verbatimSymlinks: true, filter });
1861
- } catch (err) {
1862
- const e = err;
1863
- if (e.code === "EINVAL" || e.code === "ENOTEMPTY" || e.code === "ERR_FS_CP_NON_DIR_TO_DIR" || e.code === "ERR_FS_CP_DIR_TO_NON_DIR" || process.platform === "win32" && e.errno === 145) {
1864
- throw new NomadFatal(
1865
- `${label}: type collision copying ${quoted(src)} -> ${quoted(dst)} (${e.path ?? "unknown path"}): a file/directory type changed upstream; run nomad pull --force-remote to recover`
1866
- );
1867
- }
1868
- throw err;
1869
- }
1870
- }
1871
- function prunePreservingBy(src, dst, isPreserved, isRootPreserved) {
1872
- for (const name of readdirSync(dst)) {
1873
- if (isPreserved(name) || isRootPreserved?.(name) === true) continue;
1874
- const dstPath = join2(dst, name);
1875
- const srcStat = lstatSync(join2(src, name), { throwIfNoEntry: false });
1876
- if (srcStat === void 0) {
1877
- rmSync(dstPath, { recursive: true, force: true });
1878
- continue;
1879
- }
1880
- const dstStat = lstatSync(dstPath);
1881
- if (srcStat.isDirectory() && dstStat.isDirectory()) {
1882
- prunePreservingBy(join2(src, name), dstPath, isPreserved);
1883
- } else if (srcStat.isDirectory() !== dstStat.isDirectory()) {
1884
- rmSync(dstPath, { recursive: true, force: true });
1885
- }
1886
- }
1887
- }
1888
- function copyExtrasFilteredPreservingBy(src, dst, isPreserved, isRootPreserved) {
1889
- const dstStat = lstatSync(dst, { throwIfNoEntry: false });
1890
- if (dstStat !== void 0) {
1891
- if (dstStat.isDirectory()) {
1892
- const srcStat = lstatSync(src, { throwIfNoEntry: false });
1893
- if (srcStat !== void 0 && !srcStat.isDirectory()) {
1894
- throw new NomadFatal(
1895
- `copyExtrasFilteredPreservingBy: type mismatch copying ${quoted(src)} -> ${quoted(dst)}: the repo entry is a file but the local entry is a directory; run nomad pull --force-remote to recover`
1896
- );
1897
- }
1898
- prunePreservingBy(src, dst, isPreserved, isRootPreserved);
1899
- } else {
1900
- rmSync(dst, { recursive: true, force: true });
1901
- }
1902
- }
1903
- stripCollidingDstSymlinks(src, dst, isPreserved);
1904
- cpSync(src, dst, {
1905
- recursive: true,
1906
- force: true,
1907
- verbatimSymlinks: true,
1908
- filter: (srcEntry) => srcEntry === src || !isPreserved(basename(srcEntry))
1909
- });
1910
- }
1911
-
1912
- // src/extras-sync.guards.ts
1913
- init_utils();
1914
- init_config_sharedDirs_guard();
1915
- import { isAbsolute, normalize, sep } from "node:path";
1916
- function assertSafeLocalRoot(localRoot, logical) {
1917
- if (!isAbsolute(localRoot)) {
1918
- throw new NomadFatal(
1919
- `invalid localRoot for ${logical} in path-map.json: ${JSON.stringify(localRoot)} (must be absolute)`
1920
- );
1921
- }
1922
- const canonical = process.platform === "win32" ? localRoot.split("/").join(sep) : localRoot;
1923
- if (canonical !== normalize(canonical)) {
1924
- throw new NomadFatal(
1925
- `invalid localRoot for ${logical} in path-map.json: ${JSON.stringify(localRoot)} (must be already-normalized; no '..' or redundant segments)`
1926
- );
1927
- }
1928
- }
1929
-
1930
- // src/extras-sync.core.ts
1931
- init_utils();
1932
- init_utils_json();
1933
- function loadValidatedExtras(opts) {
1934
- const repo = repoHome();
1935
- const mapPath = join3(repo, "path-map.json");
1936
- const repoExtras = join3(repo, "shared", "extras");
1937
- if (!existsSync2(mapPath) || opts.requireRepoExtras === true && !existsSync2(repoExtras)) {
1938
- if (opts.missingMsg !== void 0) log(opts.missingMsg);
1939
- return null;
1940
- }
1941
- const map = readPathMap(mapPath);
1942
- const extrasMap = map.extras ?? {};
1943
- if (Object.keys(extrasMap).length === 0) return null;
1944
- for (const logical of Object.keys(extrasMap)) {
1945
- assertSafeLogical(logical);
1946
- const localRoot = map.projects[logical]?.[HOST];
1947
- if (localRoot && localRoot !== "TBD") assertSafeLocalRoot(localRoot, logical);
1948
- }
1949
- return { map, extrasMap };
1950
- }
1951
- function* eachExtrasTarget(v, counts) {
1952
- const whitelist = SUPPORTED_EXTRAS;
1953
- for (const [logical, dirnames] of Object.entries(v.extrasMap)) {
1954
- const localRoot = v.map.projects[logical]?.[HOST];
1955
- if (!localRoot || localRoot === "TBD") {
1956
- counts.unmapped++;
1957
- continue;
1958
- }
1959
- for (const dirname14 of dirnames) {
1960
- if (!whitelist.includes(dirname14)) {
1961
- counts.skipped++;
1962
- continue;
1963
- }
1964
- yield { logical, localRoot, dirname: dirname14 };
1965
- }
1966
- }
1967
- }
1968
- function copyExtrasOverlayFiltered(src, dst, blockSet) {
1969
- const dstStat = lstatSync2(dst, { throwIfNoEntry: false });
1970
- if (dstStat !== void 0 && !dstStat.isDirectory()) {
1971
- rmSync2(dst, { recursive: true, force: true });
1972
- }
1973
- stripCollidingDstSymlinks(src, dst, (name) => isDeniedName(blockSet, name));
1974
- cpSyncGuarded(
1975
- src,
1976
- dst,
1977
- (srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename2(srcEntry)),
1978
- "copyExtrasOverlayFiltered"
1979
- );
1980
- }
1981
- function copyExtrasOverlaySkipDiverged(src, dst, blockSet, divergedSet) {
1982
- stripCollidingDstSymlinks(src, dst, (name) => isDeniedName(blockSet, name));
1983
- cpSyncGuarded(
1984
- src,
1985
- dst,
1986
- (srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename2(srcEntry)) && !divergedSet.has(relative(src, srcEntry)),
1987
- "copyExtrasOverlaySkipDiverged"
1988
- );
1989
- }
1990
- function copyExtras(src, dst) {
1991
- rmSync2(dst, { recursive: true, force: true });
1992
- cpSync2(src, dst, { recursive: true, force: true, verbatimSymlinks: true });
1993
- }
1994
- function copyExtrasFileSkipDiverged(src, dst) {
1995
- if (existsSync2(dst)) {
1996
- let dstBuf;
1997
- try {
1998
- dstBuf = readFileSync2(dst);
1999
- } catch {
2000
- return;
2001
- }
2002
- if (!readFileSync2(src).equals(dstBuf)) return;
2003
- }
2004
- copyExtras(src, dst);
2005
- }
2006
- function extrasDenySet(dirname14) {
2007
- return isClaudeExtraName(dirname14) ? CLAUDE_EXTRA_NEVER_SYNC : ALWAYS_NEVER_SYNC;
2008
- }
2009
- function copyExtrasFiltered(src, dst, blockSet) {
2010
- rmSync2(dst, { recursive: true, force: true });
2011
- cpSync2(src, dst, {
2012
- recursive: true,
2013
- force: true,
2014
- verbatimSymlinks: true,
2015
- filter: (srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename2(srcEntry))
2016
- });
2017
- }
2018
- function prunePreservingDenied(src, dst, blockSet) {
2019
- for (const name of readdirSync2(dst)) {
2020
- if (isDeniedName(blockSet, name)) continue;
2021
- const dstPath = join3(dst, name);
2022
- const srcStat = lstatSync2(join3(src, name), { throwIfNoEntry: false });
2023
- if (srcStat === void 0) {
2024
- rmSync2(dstPath, { recursive: true, force: true });
2025
- continue;
2026
- }
2027
- const dstStat = lstatSync2(dstPath);
2028
- if (srcStat.isDirectory() && dstStat.isDirectory()) {
2029
- prunePreservingDenied(join3(src, name), dstPath, blockSet);
2030
- } else if (srcStat.isDirectory() !== dstStat.isDirectory()) {
2031
- rmSync2(dstPath, { recursive: true, force: true });
2032
- }
2033
- }
2034
- }
2035
- function copyExtrasFilteredPreserving(src, dst, blockSet) {
2036
- const dstStat = lstatSync2(dst, { throwIfNoEntry: false });
2037
- if (dstStat !== void 0) {
2038
- if (dstStat.isDirectory()) prunePreservingDenied(src, dst, blockSet);
2039
- else rmSync2(dst, { recursive: true, force: true });
2040
- }
2041
- stripCollidingDstSymlinks(src, dst, (name) => isDeniedName(blockSet, name));
2042
- cpSync2(src, dst, {
2043
- recursive: true,
2044
- force: true,
2045
- verbatimSymlinks: true,
2046
- filter: (srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename2(srcEntry))
2047
- });
2048
- }
2049
-
2050
2147
  // src/links.ts
2051
2148
  init_utils();
2052
2149
  init_utils_fs();
@@ -2073,43 +2170,78 @@ function emitCopy(onPreview, from, to) {
2073
2170
  }
2074
2171
  }
2075
2172
  function isAlreadySymlink(linkPath) {
2076
- return existsSync4(linkPath) && lstatSync4(linkPath).isSymbolicLink();
2173
+ return existsSync4(linkPath) && lstatSync5(linkPath).isSymbolicLink();
2077
2174
  }
2078
2175
  function runAutoMovePasses(linkNames, claude, repo, ts, dryRun, onPreview) {
2079
2176
  for (const name of linkNames) {
2080
- const linkPath = join5(claude, name);
2081
- const target = join5(repo, "shared", name);
2177
+ const linkPath = join6(claude, name);
2178
+ const target = join6(repo, "shared", name);
2082
2179
  if (!existsSync4(linkPath)) continue;
2083
- if (lstatSync4(linkPath).isSymbolicLink()) continue;
2180
+ if (lstatSync5(linkPath).isSymbolicLink()) continue;
2084
2181
  if (!existsSync4(target)) continue;
2085
2182
  if (dryRun) {
2086
2183
  emitAutoMove(onPreview, linkPath, ts, name);
2087
2184
  continue;
2088
2185
  }
2089
- backupBeforeWrite(linkPath, ts);
2090
- rmSync3(linkPath, { recursive: true, force: true });
2186
+ backupBeforeWrite(linkPath, ts);
2187
+ rmSync3(linkPath, { recursive: true, force: true });
2188
+ }
2189
+ }
2190
+ function copySharedLinkPull(src, dst) {
2191
+ copyExtrasFilteredPreservingBy(src, dst, (name) => isDeniedName(ALWAYS_NEVER_SYNC, name));
2192
+ }
2193
+ function stillOccupied(abs) {
2194
+ try {
2195
+ return lstatSync5(abs, { throwIfNoEntry: false }) !== void 0;
2196
+ } catch {
2197
+ return true;
2198
+ }
2199
+ }
2200
+ function snapshotBeforeWin32Copy(linkPath, ts) {
2201
+ try {
2202
+ return backupBeforeWrite(linkPath, ts) ? "snapshotted" : "nothing-to-snapshot";
2203
+ } catch (err) {
2204
+ warn(
2205
+ `could not snapshot ${linkPath} before updating it (${err.message}), so it was left as it is. The rest of the pull continues`
2206
+ );
2207
+ return "failed";
2208
+ }
2209
+ }
2210
+ function warnWin32ApplyFailed(linkPath, ts, err, snapshotted) {
2211
+ const state = stillOccupied(linkPath) ? "it may be unchanged, or partly updated" : "nothing is at that path now";
2212
+ const recover = snapshotted ? ` A copy of what it held before this pull is under backup/${ts}/.` : "";
2213
+ warn(
2214
+ `${linkPath} could not be updated (${err.message}), so ${state}.${recover} The rest of the pull continues. Check its permissions, or whether another program has it open, then run 'nomad pull' again to update it`
2215
+ );
2216
+ }
2217
+ function applyOneSharedLinkWin32(target, linkPath, ts) {
2218
+ let snapshotted = false;
2219
+ try {
2220
+ const stat = lstatSync5(linkPath, { throwIfNoEntry: false });
2221
+ if (stat !== void 0) {
2222
+ const snapshot = snapshotBeforeWin32Copy(linkPath, ts);
2223
+ if (snapshot === "failed") return;
2224
+ snapshotted = snapshot === "snapshotted";
2225
+ if (stat.isSymbolicLink()) {
2226
+ rmSync3(linkPath, { recursive: true, force: true });
2227
+ }
2228
+ }
2229
+ copySharedLinkPull(target, linkPath);
2230
+ } catch (err) {
2231
+ if (err instanceof NomadFatal) throw err;
2232
+ warnWin32ApplyFailed(linkPath, ts, err, snapshotted);
2091
2233
  }
2092
2234
  }
2093
- function copySharedLinkPull(src, dst) {
2094
- copyExtrasFilteredPreservingBy(src, dst, (name) => isDeniedName(ALWAYS_NEVER_SYNC, name));
2095
- }
2096
2235
  function applySharedLinksWin32(linkNames, claude, repo, ts, dryRun, onPreview) {
2097
2236
  for (const name of linkNames) {
2098
- const target = join5(repo, "shared", name);
2237
+ const target = join6(repo, "shared", name);
2099
2238
  if (!existsSync4(target)) continue;
2100
- const linkPath = join5(claude, name);
2239
+ const linkPath = join6(claude, name);
2101
2240
  if (dryRun) {
2102
2241
  emitCopy(onPreview, linkPath, target);
2103
2242
  continue;
2104
2243
  }
2105
- const stat = lstatSync4(linkPath, { throwIfNoEntry: false });
2106
- if (stat !== void 0) {
2107
- backupBeforeWrite(linkPath, ts);
2108
- if (stat.isSymbolicLink()) {
2109
- rmSync3(linkPath, { recursive: true, force: true });
2110
- }
2111
- }
2112
- copySharedLinkPull(target, linkPath);
2244
+ applyOneSharedLinkWin32(target, linkPath, ts);
2113
2245
  }
2114
2246
  }
2115
2247
  function applySharedLinks(ts, map, opts = {}) {
@@ -2123,9 +2255,9 @@ function applySharedLinks(ts, map, opts = {}) {
2123
2255
  }
2124
2256
  runAutoMovePasses(linkNames, claude, repo, ts, dryRun, opts.onPreview);
2125
2257
  for (const name of linkNames) {
2126
- const target = join5(repo, "shared", name);
2258
+ const target = join6(repo, "shared", name);
2127
2259
  if (!existsSync4(target)) continue;
2128
- const linkPath = join5(claude, name);
2260
+ const linkPath = join6(claude, name);
2129
2261
  if (isAlreadySymlink(linkPath)) continue;
2130
2262
  if (dryRun) {
2131
2263
  emitCreate(opts.onPreview, linkPath, target);
@@ -2167,8 +2299,8 @@ function regenerateSettings(ts, opts = {}) {
2167
2299
  const suppressDriftWarn = opts.suppressDriftWarn === true;
2168
2300
  const repo = repoHome();
2169
2301
  const claude = claudeHome();
2170
- const basePath = join5(repo, "shared", "settings.base.json");
2171
- const hostPath = join5(repo, "hosts", `${HOST}.json`);
2302
+ const basePath = join6(repo, "shared", "settings.base.json");
2303
+ const hostPath = join6(repo, "hosts", `${HOST}.json`);
2172
2304
  if (!existsSync4(basePath)) {
2173
2305
  die("repo not initialized; run 'nomad init' to scaffold");
2174
2306
  }
@@ -2176,7 +2308,7 @@ function regenerateSettings(ts, opts = {}) {
2176
2308
  const hasOverrides = existsSync4(hostPath);
2177
2309
  const overrides = hasOverrides ? readJson(hostPath) : {};
2178
2310
  const merged = deepMerge(base, overrides);
2179
- const settingsPath = join5(claude, "settings.json");
2311
+ const settingsPath = join6(claude, "settings.json");
2180
2312
  const { existing, present, malformed } = readExistingSettings(settingsPath);
2181
2313
  if (!suppressDriftWarn && present) {
2182
2314
  if (malformed) {
@@ -2198,21 +2330,140 @@ function regenerateSettings(ts, opts = {}) {
2198
2330
  return { label: overrideLabel };
2199
2331
  }
2200
2332
 
2201
- // src/commands.adopt.ts
2333
+ // src/commands.adopt.recover.ts
2202
2334
  init_utils();
2203
- init_utils_fs();
2204
- init_utils_json();
2205
- var ADOPT_PUSH_HINT = "run `nomad push` to share with other hosts";
2335
+ function errorText(err) {
2336
+ const message = err?.message;
2337
+ return typeof message === "string" ? message : String(err);
2338
+ }
2206
2339
  function lexists(p) {
2207
2340
  try {
2208
- lstatSync5(p);
2341
+ lstatSync6(p);
2209
2342
  return true;
2210
2343
  } catch {
2211
2344
  return false;
2212
2345
  }
2213
2346
  }
2347
+ function isDirectChildOf(root, abs) {
2348
+ return dirname2(resolve2(abs)) === resolve2(root);
2349
+ }
2350
+ function clearPartialCopy(linkPath) {
2351
+ return clearIfDirectChild(claudeHome(), linkPath);
2352
+ }
2353
+ function clearPartialShared(sharedTarget, repo) {
2354
+ return clearIfDirectChild(join7(repo, "shared"), sharedTarget);
2355
+ }
2356
+ function clearIfDirectChild(root, target) {
2357
+ const resolved = resolve2(target);
2358
+ if (!isDirectChildOf(root, resolved)) return false;
2359
+ try {
2360
+ rmSync4(resolved, { recursive: true, force: true });
2361
+ } catch {
2362
+ return false;
2363
+ }
2364
+ return !lexists(resolved);
2365
+ }
2366
+ function stageOrReport(stage) {
2367
+ try {
2368
+ stage();
2369
+ return "";
2370
+ } catch (err) {
2371
+ return ` Staging it failed too (${errorText(err)}), so it is in the repo but not staged.`;
2372
+ }
2373
+ }
2374
+ function describeLeftoverAt(name, linkPath, sourceRemoved) {
2375
+ if (!sourceRemoved) {
2376
+ return ` The original is still at ${linkPath}, because removing it failed too (warned above), so this host keeps a complete local copy and a later \`nomad push\` publishes it.`;
2377
+ }
2378
+ if (clearPartialCopy(linkPath)) return "";
2379
+ return ` A partial copy may still be at that path, so do NOT run \`nomad push\` or \`nomad sync\` yet: either one copies that path back over shared/${name}, and \`nomad sync\` pushes in the same run. Pull first, and check it does not warn about ${name} again, because a pull that cannot read the path warns and still exits 0.`;
2380
+ }
2381
+ function restoreWin32LocalCopy(name, linkPath, sharedTarget, ts, stage, opts) {
2382
+ try {
2383
+ copySharedLinkPull(sharedTarget, linkPath);
2384
+ } catch (err) {
2385
+ if (err instanceof NomadFatal) {
2386
+ const fatalStageFailure = stageOrReport(stage);
2387
+ if (fatalStageFailure !== "") warn(`${name}:${fatalStageFailure}`);
2388
+ throw err;
2389
+ }
2390
+ const leftover = describeLeftoverAt(name, linkPath, opts.sourceRemoved);
2391
+ const stageFailure = stageOrReport(stage);
2392
+ const staged = stageFailure === "" ? " and staged." : `.${stageFailure}`;
2393
+ const recover = opts.snapshotted ? ` A copy of what it held before is under backup/${ts}/.` : "";
2394
+ throw new NomadFatal(
2395
+ `adopted ${name} into shared/${name}, but could not restore the local copy at ${linkPath} (${errorText(err)}). The content is safe in the repo${staged}${recover} Check its permissions, or whether another program has it open, then run \`nomad pull\` to recreate the local copy before your next \`nomad push\`.${leftover}`,
2396
+ { code: EXIT.GENERIC_FAILURE }
2397
+ );
2398
+ }
2399
+ }
2400
+ function describePartialShared(name, linkPath) {
2401
+ if (process.platform === "win32") {
2402
+ return ` A partial shared/${name} may still be in the repo. Remove it before re-running: adopt reports any name whose shared/${name} already exists as adopted and does nothing, and the \`nomad pull\` it suggests would copy that fragment over ${linkPath}, which is still whole.`;
2403
+ }
2404
+ return ` A partial shared/${name} may still be in the repo; remove it first, because adopt refuses to run while it is there.`;
2405
+ }
2406
+ function copyIntoSharedOrFatal(name, linkPath, sharedTarget, repo) {
2407
+ try {
2408
+ copyExtrasFiltered(linkPath, sharedTarget, ALWAYS_NEVER_SYNC);
2409
+ } catch (err) {
2410
+ const leftover = clearPartialShared(sharedTarget, repo) ? "" : describePartialShared(name, linkPath);
2411
+ throw new NomadFatal(
2412
+ `could not copy ${linkPath} into shared/${name} (${errorText(err)}). Nothing was removed from ${linkPath}. Check its permissions, or whether another program has it open, then run \`nomad adopt ${name}\` again.${leftover}`,
2413
+ { code: EXIT.GENERIC_FAILURE }
2414
+ );
2415
+ }
2416
+ }
2417
+ function refuseLateDeniedEntries(name, linkPath, sharedTarget, repo, opts) {
2418
+ const snapshot = opts.snapshotted ? ` A plain snapshot of ${linkPath} taken before the copy is under backup/${opts.ts}/; it is unfiltered, so any never-sync entry under that path is in it too.` : "";
2419
+ const stateNow = () => clearPartialShared(sharedTarget, repo) ? `shared/${name} was cleared, and nothing was removed from ${linkPath}.${snapshot}` : `Nothing was removed from ${linkPath}.${describePartialShared(name, linkPath)}${snapshot}`;
2420
+ const late = scanOrFatal(name, linkPath, stateNow);
2421
+ if (late.length === 0) return;
2422
+ throw deniedEntriesRefusal(name, linkPath, late, {
2423
+ found: `never-sync content appeared under ${linkPath} after the preflight scan`,
2424
+ state: stateNow()
2425
+ });
2426
+ }
2427
+ function removeAdoptSource(linkPath) {
2428
+ const resolved = resolve2(linkPath);
2429
+ if (!isDirectChildOf(claudeHome(), resolved)) {
2430
+ return { ok: false, message: "it is not a direct child of the configured Claude home" };
2431
+ }
2432
+ try {
2433
+ rmSync4(resolved, { recursive: true, force: true });
2434
+ } catch (err) {
2435
+ return { ok: false, message: errorText(err) };
2436
+ }
2437
+ if (lexists(resolved)) {
2438
+ return { ok: false, message: "the delete was accepted but the entry is still there" };
2439
+ }
2440
+ return { ok: true };
2441
+ }
2442
+ function reportSourceRemovalFailure(name, linkPath, message, stage) {
2443
+ if (process.platform === "win32") {
2444
+ warn(
2445
+ `${name}: could not remove ${linkPath} (${message}); refreshing it from shared/${name} instead, which leaves the same result`
2446
+ );
2447
+ return;
2448
+ }
2449
+ const stageFailure = stageOrReport(stage);
2450
+ const staged = stageFailure === "" ? " and staged." : `.${stageFailure}`;
2451
+ throw new NomadFatal(
2452
+ `adopted ${name} into shared/${name}, but could not remove the original at ${linkPath} (${message}). It is a real directory where a symlink belongs, so nothing links to shared/${name} yet. The content is safe in the repo${staged} Check its permissions, or whether another program has it open, then run \`nomad pull\`, which backs that directory up and replaces it with the symlink. Remove it by hand only if the same problem blocks the pull.`,
2453
+ { code: EXIT.GENERIC_FAILURE }
2454
+ );
2455
+ }
2456
+
2457
+ // src/commands.adopt.ts
2458
+ init_config();
2459
+ init_config_sharedDirs_guard();
2460
+ init_exit_codes();
2461
+ init_utils();
2462
+ init_utils_fs();
2463
+ init_utils_json();
2464
+ var ADOPT_PUSH_HINT = "run `nomad push` to share with other hosts";
2214
2465
  function readMapIfPresent(repoHome2) {
2215
- const mapPath = join6(repoHome2, "path-map.json");
2466
+ const mapPath = join8(repoHome2, "path-map.json");
2216
2467
  return existsSync5(mapPath) ? readPathMap(mapPath) : { projects: {} };
2217
2468
  }
2218
2469
  function isConfiguredTarget(name, map) {
@@ -2222,18 +2473,46 @@ function isValidAdoptName(name) {
2222
2473
  if (SHARED_LINKS.includes(name)) return true;
2223
2474
  return isValidSharedDir(name);
2224
2475
  }
2476
+ function reportWin32AlreadyAdopted(name, sharedTarget) {
2477
+ if (process.platform !== "win32" || !lexists(sharedTarget)) return false;
2478
+ log(`${name}: already adopted (win32 copy-sync); run \`nomad pull\` to refresh the local copy`);
2479
+ return true;
2480
+ }
2481
+ function adoptStopsEarly(name, linkPath, sharedTarget) {
2482
+ if (!existsSync5(linkPath)) {
2483
+ if (reportWin32AlreadyAdopted(name, sharedTarget)) return true;
2484
+ log(`${name}: nothing to adopt (not present in ~/.claude/)`);
2485
+ return true;
2486
+ }
2487
+ if (lstatSync7(linkPath).isSymbolicLink()) {
2488
+ log(`${name}: already adopted (already a symlink)`);
2489
+ return true;
2490
+ }
2491
+ if (reportWin32AlreadyAdopted(name, sharedTarget)) return true;
2492
+ if (lexists(sharedTarget)) {
2493
+ fail(`${name}: shared/${name} already exists; would clobber. Remove it first.`);
2494
+ process.exit(1);
2495
+ }
2496
+ return false;
2497
+ }
2225
2498
  function performAdoptMove(name, linkPath, sharedTarget, repo, backup) {
2226
2499
  const ts = freshBackupTs(backup);
2227
- backupBeforeWrite(linkPath, ts);
2228
- cpSync4(linkPath, sharedTarget, { recursive: true, force: true, preserveTimestamps: true });
2229
- rmSync4(linkPath, { recursive: true, force: true });
2500
+ const snapshotted = backupBeforeWrite(linkPath, ts);
2501
+ const rel = join8("shared", name);
2502
+ const stage = () => gitOrFatal(["add", "--", rel], `git add shared/${name}`, repo);
2503
+ copyIntoSharedOrFatal(name, linkPath, sharedTarget, repo);
2504
+ refuseLateDeniedEntries(name, linkPath, sharedTarget, repo, { snapshotted, ts });
2505
+ const removal = removeAdoptSource(linkPath);
2506
+ if (!removal.ok) reportSourceRemovalFailure(name, linkPath, removal.message, stage);
2230
2507
  if (process.platform === "win32") {
2231
- copySharedLinkPull(sharedTarget, linkPath);
2508
+ restoreWin32LocalCopy(name, linkPath, sharedTarget, ts, stage, {
2509
+ snapshotted,
2510
+ sourceRemoved: removal.ok
2511
+ });
2232
2512
  } else {
2233
2513
  ensureSymlink(linkPath, sharedTarget);
2234
2514
  }
2235
- const rel = join6("shared", name);
2236
- gitOrFatal(["add", "--", rel], `git add shared/${name}`, repo);
2515
+ stage();
2237
2516
  log(`adopted ${name}; ${ADOPT_PUSH_HINT}`);
2238
2517
  }
2239
2518
  function cmdAdopt(name, opts = {}) {
@@ -2259,24 +2538,10 @@ function cmdAdopt(name, opts = {}) {
2259
2538
  );
2260
2539
  process.exit(1);
2261
2540
  }
2262
- const linkPath = join6(claude, name);
2263
- const sharedTarget = join6(repo, "shared", name);
2264
- if (!existsSync5(linkPath)) {
2265
- log(`${name}: nothing to adopt (not present in ~/.claude/)`);
2266
- return;
2267
- }
2268
- if (lstatSync5(linkPath).isSymbolicLink()) {
2269
- log(`${name}: already adopted (already a symlink)`);
2270
- return;
2271
- }
2272
- if (process.platform === "win32" && lexists(sharedTarget)) {
2273
- log(`${name}: already adopted (win32 copy-sync); run \`nomad pull\` to refresh the local copy`);
2274
- return;
2275
- }
2276
- if (lexists(sharedTarget)) {
2277
- fail(`${name}: shared/${name} already exists; would clobber. Remove it first.`);
2278
- process.exit(1);
2279
- }
2541
+ const linkPath = join8(claude, name);
2542
+ const sharedTarget = join8(repo, "shared", name);
2543
+ if (adoptStopsEarly(name, linkPath, sharedTarget)) return;
2544
+ refuseDeniedEntries(name, linkPath);
2280
2545
  if (dryRun) {
2281
2546
  const ts = freshBackupTs(backup);
2282
2547
  log(`would backup: ${linkPath} -> backup/${ts}/${name}`);
@@ -2302,7 +2567,7 @@ import { existsSync as existsSync6 } from "node:fs";
2302
2567
 
2303
2568
  // src/commands.redact.core.ts
2304
2569
  import { appendFileSync, readFileSync as readFileSync3 } from "node:fs";
2305
- import { join as join7 } from "node:path";
2570
+ import { join as join9 } from "node:path";
2306
2571
  function collectMatchIntervals(content, findings) {
2307
2572
  const intervals = [];
2308
2573
  for (const f of findings) {
@@ -2360,7 +2625,7 @@ function isAlreadyPresent(line, lines) {
2360
2625
  function appendGitleaksIgnore(fingerprint, repo) {
2361
2626
  const sanitized = fingerprint.replace(/[\r\n]/g, "").trim();
2362
2627
  if (sanitized.length === 0) return;
2363
- const ignPath = join7(repo, ".gitleaksignore");
2628
+ const ignPath = join9(repo, ".gitleaksignore");
2364
2629
  let raw;
2365
2630
  try {
2366
2631
  raw = readFileSync3(ignPath, "utf8");
@@ -2397,7 +2662,7 @@ function cmdAllow(fingerprints) {
2397
2662
  // src/commands.capture-settings.ts
2398
2663
  init_config();
2399
2664
  import { existsSync as existsSync7 } from "node:fs";
2400
- import { join as join9 } from "node:path";
2665
+ import { join as join11 } from "node:path";
2401
2666
  import { createInterface } from "node:readline/promises";
2402
2667
  init_utils_fs();
2403
2668
  init_utils_json();
@@ -2406,13 +2671,13 @@ init_utils_json();
2406
2671
  init_config();
2407
2672
  init_utils();
2408
2673
  import { closeSync as closeSync2, mkdirSync as mkdirSync2, openSync as openSync2, readFileSync as readFileSync4, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
2409
- import { dirname as dirname2, join as join8 } from "node:path";
2674
+ import { dirname as dirname3, join as join10 } from "node:path";
2410
2675
  function lockFilePath() {
2411
- return join8(home(), ".cache", "claude-nomad", "nomad.lock");
2676
+ return join10(home(), ".cache", "claude-nomad", "nomad.lock");
2412
2677
  }
2413
2678
  function acquireLock(verb) {
2414
2679
  const lp = lockFilePath();
2415
- mkdirSync2(dirname2(lp), { recursive: true });
2680
+ mkdirSync2(dirname3(lp), { recursive: true });
2416
2681
  try {
2417
2682
  const fd = openSync2(lp, "wx");
2418
2683
  try {
@@ -2534,7 +2799,7 @@ async function confirmCapture(destLabel, keys) {
2534
2799
  }
2535
2800
  }
2536
2801
  function resolveCaptureDestination(repo, useHost) {
2537
- const destPath = useHost ? join9(repo, "hosts", `${HOST}.json`) : join9(repo, "shared", "settings.base.json");
2802
+ const destPath = useHost ? join11(repo, "hosts", `${HOST}.json`) : join11(repo, "shared", "settings.base.json");
2538
2803
  const existing = existsSync7(destPath) ? readJson(destPath) : {};
2539
2804
  return { destPath, existing };
2540
2805
  }
@@ -2546,17 +2811,17 @@ async function cmdCaptureSettings(opts) {
2546
2811
  if (handle === null) process.exit(0);
2547
2812
  try {
2548
2813
  const claude = claudeHome();
2549
- const basePath = join9(repo, "shared", "settings.base.json");
2814
+ const basePath = join11(repo, "shared", "settings.base.json");
2550
2815
  if (!existsSync7(basePath)) {
2551
2816
  die("repo not initialized; run 'nomad init' to scaffold");
2552
2817
  }
2553
- const settingsPath = join9(claude, "settings.json");
2818
+ const settingsPath = join11(claude, "settings.json");
2554
2819
  if (!existsSync7(settingsPath)) {
2555
2820
  log("no ~/.claude/settings.json found; nothing to capture");
2556
2821
  return;
2557
2822
  }
2558
2823
  const base = readJson(basePath);
2559
- const hostPath = join9(repo, "hosts", `${HOST}.json`);
2824
+ const hostPath = join11(repo, "hosts", `${HOST}.json`);
2560
2825
  const overrides = existsSync7(hostPath) ? readJson(hostPath) : {};
2561
2826
  const merged = deepMerge(base, overrides);
2562
2827
  const settings = readJson(settingsPath);
@@ -2595,8 +2860,8 @@ async function cmdCaptureSettings(opts) {
2595
2860
  init_config();
2596
2861
  init_exit_codes();
2597
2862
  init_utils();
2598
- import { existsSync as existsSync8, lstatSync as lstatSync6, readdirSync as readdirSync3, rmSync as rmSync5, statSync as statSync2 } from "node:fs";
2599
- import { join as join10 } from "node:path";
2863
+ import { existsSync as existsSync8, lstatSync as lstatSync8, readdirSync as readdirSync4, rmSync as rmSync5, statSync as statSync2 } from "node:fs";
2864
+ import { join as join12 } from "node:path";
2600
2865
  var TS_SHAPE = /^\d{8}-\d{6}(-\d+)?$/;
2601
2866
  var DURATION_RE = /^(\d+)([dhm])$/;
2602
2867
  var UNIT_MS = { d: 864e5, h: 36e5, m: 6e4 };
@@ -2611,7 +2876,7 @@ function parseDuration(s) {
2611
2876
  }
2612
2877
  function listBackupDirs(backupBase2) {
2613
2878
  if (!existsSync8(backupBase2)) return [];
2614
- return readdirSync3(backupBase2).filter(isTsDir).map((name) => ({ name, mtimeMs: statSync2(join10(backupBase2, name)).mtimeMs })).sort((a, b) => b.mtimeMs - a.mtimeMs);
2879
+ return readdirSync4(backupBase2).filter(isTsDir).map((name) => ({ name, mtimeMs: statSync2(join12(backupBase2, name)).mtimeMs })).sort((a, b) => b.mtimeMs - a.mtimeMs);
2615
2880
  }
2616
2881
  function prunableByAge(dirs, olderThanMs, nowMs) {
2617
2882
  return dirs.filter((d) => nowMs - d.mtimeMs > olderThanMs).map((d) => d.name);
@@ -2621,8 +2886,8 @@ function prunableByCount(dirs, keep) {
2621
2886
  }
2622
2887
  function safeDelete(backupBase2, name) {
2623
2888
  if (!isTsDir(name)) return;
2624
- const full = join10(backupBase2, name);
2625
- const st = lstatSync6(full, { throwIfNoEntry: false });
2889
+ const full = join12(backupBase2, name);
2890
+ const st = lstatSync8(full, { throwIfNoEntry: false });
2626
2891
  if (!st || st.isSymbolicLink() || !st.isDirectory()) return;
2627
2892
  rmSync5(full, { recursive: true, force: true });
2628
2893
  }
@@ -2662,8 +2927,8 @@ init_config_sharedDirs_guard();
2662
2927
  init_utils();
2663
2928
  init_utils_fs();
2664
2929
  init_utils_json();
2665
- import { cpSync as cpSync5, existsSync as existsSync9, lstatSync as lstatSync7, realpathSync, rmSync as rmSync6 } from "node:fs";
2666
- import { join as join11, sep as sep3 } from "node:path";
2930
+ import { cpSync as cpSync4, existsSync as existsSync9, lstatSync as lstatSync9, realpathSync, rmSync as rmSync6 } from "node:fs";
2931
+ import { join as join13, sep as sep3 } from "node:path";
2667
2932
  function ejectChecklist() {
2668
2933
  return [
2669
2934
  "Manual steps remaining to finish leaving claude-nomad on this host:",
@@ -2679,28 +2944,28 @@ function errMessage(err) {
2679
2944
  }
2680
2945
  function lexists2(p) {
2681
2946
  try {
2682
- lstatSync7(p);
2947
+ lstatSync9(p);
2683
2948
  return true;
2684
2949
  } catch {
2685
2950
  return false;
2686
2951
  }
2687
2952
  }
2688
2953
  function readMapIfPresent2(repoHome2) {
2689
- const mapPath = join11(repoHome2, "path-map.json");
2954
+ const mapPath = join13(repoHome2, "path-map.json");
2690
2955
  return existsSync9(mapPath) ? readPathMap(mapPath) : { projects: {} };
2691
2956
  }
2692
2957
  function classifyName(linkPath) {
2693
2958
  if (!lexists2(linkPath)) return "absent";
2694
- if (!lstatSync7(linkPath).isSymbolicLink()) return "skip-real";
2959
+ if (!lstatSync9(linkPath).isSymbolicLink()) return "skip-real";
2695
2960
  if (!existsSync9(linkPath)) return "dangling";
2696
2961
  return "materialize";
2697
2962
  }
2698
2963
  function resolveSharedRoot(repoHome2) {
2699
2964
  try {
2700
- return realpathSync(join11(repoHome2, "shared"));
2965
+ return realpathSync(join13(repoHome2, "shared"));
2701
2966
  } catch {
2702
2967
  return die(
2703
- `cannot resolve ${join11(repoHome2, "shared")} (repo checkout incomplete). run \`nomad pull\` first, then re-run \`nomad eject\``
2968
+ `cannot resolve ${join13(repoHome2, "shared")} (repo checkout incomplete). run \`nomad pull\` first, then re-run \`nomad eject\``
2704
2969
  );
2705
2970
  }
2706
2971
  }
@@ -2716,7 +2981,7 @@ function materializeOne(name, linkPath, sharedRoot) {
2716
2981
  const tmp = `${linkPath}.eject.tmp.${process.pid}.${Date.now()}`;
2717
2982
  try {
2718
2983
  rmSync6(tmp, { recursive: true, force: true });
2719
- cpSync5(target, tmp, {
2984
+ cpSync4(target, tmp, {
2720
2985
  recursive: true,
2721
2986
  force: true,
2722
2987
  dereference: true,
@@ -2740,7 +3005,7 @@ function skipRealMessage(name) {
2740
3005
  function previewDryRun(names, classifications, claudeHome2, sharedRoot) {
2741
3006
  for (const name of names) {
2742
3007
  const cls = classifications.get(name);
2743
- const linkPath = join11(claudeHome2, name);
3008
+ const linkPath = join13(claudeHome2, name);
2744
3009
  if (cls === "absent") {
2745
3010
  item(`skipped (absent): ${name}`);
2746
3011
  } else if (cls === "skip-real") {
@@ -2770,7 +3035,7 @@ function runLiveEject(names, classifications, claudeHome2, sharedRoot) {
2770
3035
  let skipped = 0;
2771
3036
  for (const name of names) {
2772
3037
  const cls = classifications.get(name);
2773
- const linkPath = join11(claudeHome2, name);
3038
+ const linkPath = join13(claudeHome2, name);
2774
3039
  if (cls === "absent") {
2775
3040
  item(`skipped (absent): ${name}`);
2776
3041
  skipped++;
@@ -2819,7 +3084,7 @@ function cmdEject(opts = {}, roots = defaultEjectRoots()) {
2819
3084
  const names = ejectNames(map);
2820
3085
  const classifications = /* @__PURE__ */ new Map();
2821
3086
  for (const name of names) {
2822
- classifications.set(name, classifyName(join11(claudeHome2, name)));
3087
+ classifications.set(name, classifyName(join13(claudeHome2, name)));
2823
3088
  }
2824
3089
  const base = new Set(allSharedLinks(map, { quiet: true }));
2825
3090
  for (const name of names) {
@@ -2857,14 +3122,14 @@ function cmdEject(opts = {}, roots = defaultEjectRoots()) {
2857
3122
  }
2858
3123
 
2859
3124
  // src/commands.doctor.ts
2860
- import { existsSync as existsSync35 } from "node:fs";
2861
- import { join as join45 } from "node:path";
3125
+ import { existsSync as existsSync36 } from "node:fs";
3126
+ import { join as join48 } from "node:path";
2862
3127
 
2863
3128
  // src/commands.doctor.checks.repo.ts
2864
3129
  init_color();
2865
3130
  init_config();
2866
- import { existsSync as existsSync11, lstatSync as lstatSync8, readdirSync as readdirSync4, statSync as statSync3 } from "node:fs";
2867
- import { join as join13, win32 as win32Path } from "node:path";
3131
+ import { existsSync as existsSync12, lstatSync as lstatSync10, readdirSync as readdirSync5, statSync as statSync3 } from "node:fs";
3132
+ import { join as join16 } from "node:path";
2868
3133
 
2869
3134
  // src/commands.doctor.format.ts
2870
3135
  init_color();
@@ -2939,10 +3204,16 @@ function readJsonSafe(path, label, section2) {
2939
3204
  }
2940
3205
  }
2941
3206
 
3207
+ // src/commands.doctor.checks.repo.win32.ts
3208
+ init_color();
3209
+ init_config();
3210
+ import { existsSync as existsSync10 } from "node:fs";
3211
+ import { join as join14, win32 as win32Path } from "node:path";
3212
+
2942
3213
  // src/extras-sync.diff.ts
2943
3214
  init_utils();
2944
3215
  import { execFileSync as execFileSync2 } from "node:child_process";
2945
- import { relative as relative3 } from "node:path";
3216
+ import { relative as relative4 } from "node:path";
2946
3217
  var NAME_STATUS_DIFF_TIMEOUT_MS = 3e3;
2947
3218
  function parseNameStatus(stdout) {
2948
3219
  const fields = stdout.split("\0").filter((f) => f.length > 0);
@@ -2961,7 +3232,7 @@ function parseDiffOutput(stdout) {
2961
3232
  return parseNameStatus(stdout).map(labelEntry);
2962
3233
  }
2963
3234
  function parseModifiedPaths(stdout, a) {
2964
- return parseNameStatus(stdout).filter((entry) => entry.status === "M").map((entry) => relative3(a, entry.path));
3235
+ return parseNameStatus(stdout).filter((entry) => entry.status === "M").map((entry) => relative4(a, entry.path));
2965
3236
  }
2966
3237
  function runNameStatusDiff(a, b, parse) {
2967
3238
  try {
@@ -2994,18 +3265,56 @@ function listDivergingModified(a, b) {
2994
3265
  return runNameStatusDiff(a, b, (stdout) => parseModifiedPaths(stdout, a));
2995
3266
  }
2996
3267
 
3268
+ // src/commands.doctor.checks.repo.win32.ts
3269
+ function win32CopyOkRow(name, exempt = 0) {
3270
+ const note = exempt > 0 ? dim(` (${exempt} never-synced path(s) not compared)`) : "";
3271
+ return { line: `${green(okGlyph)} ${name}: real copy (win32 copy-sync)${note}`, fail: false };
3272
+ }
3273
+ function win32CopyUnpublishedRow(name) {
3274
+ return {
3275
+ line: `${dim(infoGlyph)} ${name}: real local copy, not published (run \`nomad adopt ${name}\` to share it)`,
3276
+ fail: false
3277
+ };
3278
+ }
3279
+ function relativeUnder(root, path) {
3280
+ const rel = win32Path.relative(root, path);
3281
+ const segments = rel.split(win32Path.sep);
3282
+ if (rel === "" || segments[0] === ".." || win32Path.isAbsolute(rel)) return null;
3283
+ return segments.join("/");
3284
+ }
3285
+ function divergingRepoPath(line, name, local, shared) {
3286
+ const path = line.replace(/ \((?:local|repo) only\)$/, "");
3287
+ const rel = relativeUnder(local, path) ?? relativeUnder(shared, path);
3288
+ return rel === null ? `shared/${name}` : `shared/${name}/${rel}`;
3289
+ }
3290
+ function classifyWin32Copy(name, p) {
3291
+ const sharedPath = join14(repoHome(), "shared", name);
3292
+ if (!existsSync10(sharedPath)) return win32CopyUnpublishedRow(name);
3293
+ const compared = listDivergingFiles(p, sharedPath);
3294
+ const diverging = compared.filter(
3295
+ (line) => deniedSegmentFor(divergingRepoPath(line, name, p, sharedPath)) === null
3296
+ );
3297
+ const exempt = compared.length - diverging.length;
3298
+ if (diverging.length === 0) return win32CopyOkRow(name, exempt);
3299
+ return {
3300
+ line: `${yellow(warnGlyph)} ${name}: ${diverging.length} file(s) diverge from shared/${name}`,
3301
+ fail: false,
3302
+ children: diverging
3303
+ };
3304
+ }
3305
+
2997
3306
  // src/init.classify.ts
2998
3307
  init_config();
2999
3308
  init_utils_json();
3000
- import { existsSync as existsSync10 } from "node:fs";
3001
- import { join as join12 } from "node:path";
3309
+ import { existsSync as existsSync11 } from "node:fs";
3310
+ import { join as join15 } from "node:path";
3002
3311
  function classifyRepoState(repoHome2, host) {
3003
- const basePath = join12(repoHome2, "shared", "settings.base.json");
3004
- const mapPath = join12(repoHome2, "path-map.json");
3005
- const hostPath = join12(repoHome2, "hosts", `${host}.json`);
3006
- const hasBase = existsSync10(basePath);
3007
- const hasMap = existsSync10(mapPath);
3008
- const hasHost = existsSync10(hostPath);
3312
+ const basePath = join15(repoHome2, "shared", "settings.base.json");
3313
+ const mapPath = join15(repoHome2, "path-map.json");
3314
+ const hostPath = join15(repoHome2, "hosts", `${host}.json`);
3315
+ const hasBase = existsSync11(basePath);
3316
+ const hasMap = existsSync11(mapPath);
3317
+ const hasHost = existsSync11(hostPath);
3009
3318
  let mapEntryCount = 0;
3010
3319
  if (hasMap) {
3011
3320
  try {
@@ -3020,11 +3329,11 @@ function classifyRepoState(repoHome2, host) {
3020
3329
  return "partial";
3021
3330
  }
3022
3331
  function reasonForPartial(repoHome2, host) {
3023
- const basePath = join12(repoHome2, "shared", "settings.base.json");
3024
- const mapPath = join12(repoHome2, "path-map.json");
3025
- const hostPath = join12(repoHome2, "hosts", `${host}.json`);
3026
- if (!existsSync10(basePath)) return "- shared/settings.base.json missing";
3027
- if (!existsSync10(mapPath)) return "- path-map.json missing";
3332
+ const basePath = join15(repoHome2, "shared", "settings.base.json");
3333
+ const mapPath = join15(repoHome2, "path-map.json");
3334
+ const hostPath = join15(repoHome2, "hosts", `${host}.json`);
3335
+ if (!existsSync11(basePath)) return "- shared/settings.base.json missing";
3336
+ if (!existsSync11(mapPath)) return "- path-map.json missing";
3028
3337
  let mapEntryCount;
3029
3338
  try {
3030
3339
  const map = readJson(mapPath);
@@ -3033,7 +3342,7 @@ function reasonForPartial(repoHome2, host) {
3033
3342
  mapEntryCount = 0;
3034
3343
  }
3035
3344
  if (mapEntryCount === 0) return "- path-map.json.projects has no entries";
3036
- if (!existsSync10(hostPath)) return `- hosts/${host}.json missing`;
3345
+ if (!existsSync11(hostPath)) return `- hosts/${host}.json missing`;
3037
3346
  return "- partial state (unknown gap)";
3038
3347
  }
3039
3348
 
@@ -3050,15 +3359,15 @@ function reportHostAndPaths(section2) {
3050
3359
  if (isOverrideActive()) {
3051
3360
  addItem(section2, `${dim(infoGlyph)} NOMAD_REPO: ${blue(repo)}`);
3052
3361
  }
3053
- addItem(section2, `${existsSync11(repo) ? green(okGlyph) : yellow(warnGlyph)} repo: ${blue(repo)}`);
3362
+ addItem(section2, `${existsSync12(repo) ? green(okGlyph) : yellow(warnGlyph)} repo: ${blue(repo)}`);
3054
3363
  addItem(
3055
3364
  section2,
3056
- `${existsSync11(claude) ? green(okGlyph) : yellow(warnGlyph)} claude home: ${blue(claude)}`
3365
+ `${existsSync12(claude) ? green(okGlyph) : yellow(warnGlyph)} claude home: ${blue(claude)}`
3057
3366
  );
3058
3367
  }
3059
3368
  function pathMapHostKeys() {
3060
- const mapPath = join13(repoHome(), "path-map.json");
3061
- if (!existsSync11(mapPath)) return /* @__PURE__ */ new Set();
3369
+ const mapPath = join16(repoHome(), "path-map.json");
3370
+ if (!existsSync12(mapPath)) return /* @__PURE__ */ new Set();
3062
3371
  let raw;
3063
3372
  try {
3064
3373
  raw = readJson(mapPath);
@@ -3075,7 +3384,7 @@ function pathMapHostKeys() {
3075
3384
  function hostOverrideLabels() {
3076
3385
  let entries;
3077
3386
  try {
3078
- entries = readdirSync4(join13(repoHome(), "hosts"));
3387
+ entries = readdirSync5(join16(repoHome(), "hosts"));
3079
3388
  } catch {
3080
3389
  return /* @__PURE__ */ new Set();
3081
3390
  }
@@ -3116,42 +3425,12 @@ function reportRepoState(section2) {
3116
3425
  }
3117
3426
  }
3118
3427
  function repoHasSharedSource(name) {
3119
- return existsSync11(join13(repoHome(), "shared", name));
3120
- }
3121
- function win32CopyOkRow(name, exempt = 0) {
3122
- const note = exempt > 0 ? dim(` (${exempt} never-synced path(s) not compared)`) : "";
3123
- return { line: `${green(okGlyph)} ${name}: real copy (win32 copy-sync)${note}`, fail: false };
3124
- }
3125
- function relativeUnder(root, path) {
3126
- const rel = win32Path.relative(root, path);
3127
- const segments = rel.split(win32Path.sep);
3128
- if (rel === "" || segments[0] === ".." || win32Path.isAbsolute(rel)) return null;
3129
- return segments.join("/");
3130
- }
3131
- function divergingRepoPath(line, name, local, shared) {
3132
- const path = line.replace(/ \((?:local|repo) only\)$/, "");
3133
- const rel = relativeUnder(local, path) ?? relativeUnder(shared, path);
3134
- return rel === null ? `shared/${name}` : `shared/${name}/${rel}`;
3135
- }
3136
- function classifyWin32Copy(name, p) {
3137
- const sharedPath = join13(repoHome(), "shared", name);
3138
- if (!existsSync11(sharedPath)) return win32CopyOkRow(name);
3139
- const compared = listDivergingFiles(p, sharedPath);
3140
- const diverging = compared.filter(
3141
- (line) => deniedSegmentFor(divergingRepoPath(line, name, p, sharedPath)) === null
3142
- );
3143
- const exempt = compared.length - diverging.length;
3144
- if (diverging.length === 0) return win32CopyOkRow(name, exempt);
3145
- return {
3146
- line: `${yellow(warnGlyph)} ${name}: ${diverging.length} file(s) diverge from shared/${name}`,
3147
- fail: false,
3148
- children: diverging
3149
- };
3428
+ return existsSync12(join16(repoHome(), "shared", name));
3150
3429
  }
3151
3430
  function classifySharedLink(name, p) {
3152
3431
  let stat;
3153
3432
  try {
3154
- stat = lstatSync8(p);
3433
+ stat = lstatSync10(p);
3155
3434
  } catch (err) {
3156
3435
  const code = err.code;
3157
3436
  if (code === "ENOENT") {
@@ -3197,7 +3476,7 @@ function classifySymlinkTarget(name, p) {
3197
3476
  function reportSharedLinks(section2, map) {
3198
3477
  const claude = claudeHome();
3199
3478
  for (const name of allSharedLinks(map, { quiet: true })) {
3200
- const p = join13(claude, name);
3479
+ const p = join16(claude, name);
3201
3480
  const { line, fail: fail2, children } = classifySharedLink(name, p);
3202
3481
  addItem(section2, line);
3203
3482
  if (fail2) process.exitCode = 1;
@@ -3209,10 +3488,10 @@ function reportSharedLinks(section2, map) {
3209
3488
  function reportDroppedNamesMigration(section2) {
3210
3489
  const claude = claudeHome();
3211
3490
  for (const name of GSD_DROPPED_NAMES) {
3212
- const p = join13(claude, name);
3491
+ const p = join16(claude, name);
3213
3492
  let stat;
3214
3493
  try {
3215
- stat = lstatSync8(p);
3494
+ stat = lstatSync10(p);
3216
3495
  } catch {
3217
3496
  continue;
3218
3497
  }
@@ -3227,11 +3506,11 @@ function reportDroppedNamesMigration(section2) {
3227
3506
  // src/commands.doctor.checks.settings.ts
3228
3507
  init_color();
3229
3508
  init_config();
3230
- import { existsSync as existsSync12, readdirSync as readdirSync5 } from "node:fs";
3231
- import { join as join14 } from "node:path";
3509
+ import { existsSync as existsSync13, readdirSync as readdirSync6 } from "node:fs";
3510
+ import { join as join17 } from "node:path";
3232
3511
  function loadBaseSettings(section2) {
3233
- const basePath = join14(repoHome(), "shared", "settings.base.json");
3234
- if (!existsSync12(basePath)) {
3512
+ const basePath = join17(repoHome(), "shared", "settings.base.json");
3513
+ if (!existsSync13(basePath)) {
3235
3514
  addItem(section2, `${red(failGlyph)} shared/settings.base.json missing at ${blue(basePath)}`);
3236
3515
  process.exitCode = 1;
3237
3516
  return null;
@@ -3239,8 +3518,8 @@ function loadBaseSettings(section2) {
3239
3518
  return readJsonSafe(basePath, basePath, section2);
3240
3519
  }
3241
3520
  function loadAndReportSettings(section2) {
3242
- const settingsPath = join14(claudeHome(), "settings.json");
3243
- if (!existsSync12(settingsPath)) return null;
3521
+ const settingsPath = join17(claudeHome(), "settings.json");
3522
+ if (!existsSync13(settingsPath)) return null;
3244
3523
  const settings = readJsonSafe(settingsPath, settingsPath, section2);
3245
3524
  if (settings === null) return null;
3246
3525
  const unknownKeys = Object.keys(settings).filter((k) => !KNOWN_SETTINGS_KEYS.has(k));
@@ -3256,13 +3535,13 @@ function loadAndReportSettings(section2) {
3256
3535
  }
3257
3536
  function reportHostOverrides(section2, base, settings) {
3258
3537
  const repo = repoHome();
3259
- const hostFile = join14(repo, "hosts", `${HOST}.json`);
3538
+ const hostFile = join17(repo, "hosts", `${HOST}.json`);
3260
3539
  let drift = [];
3261
3540
  if (base !== null && settings !== null) {
3262
3541
  const baseKeys = new Set(Object.keys(base));
3263
3542
  drift = Object.keys(settings).filter((k) => !baseKeys.has(k));
3264
3543
  }
3265
- if (existsSync12(hostFile)) {
3544
+ if (existsSync13(hostFile)) {
3266
3545
  if (readJsonSafe(hostFile, hostFile, section2) !== null) {
3267
3546
  addItem(section2, `${green(okGlyph)} host overrides: ${blue(hostFile)}`);
3268
3547
  }
@@ -3271,9 +3550,9 @@ function reportHostOverrides(section2, base, settings) {
3271
3550
  section2,
3272
3551
  `${red(failGlyph)} no hosts/${HOST}.json AND settings.json has unbased keys ${JSON.stringify(drift)}`
3273
3552
  );
3274
- const hostsDir = join14(repo, "hosts");
3275
- if (existsSync12(hostsDir)) {
3276
- const cands = readdirSync5(hostsDir).filter((f) => f.endsWith(".json"));
3553
+ const hostsDir = join17(repo, "hosts");
3554
+ if (existsSync13(hostsDir)) {
3555
+ const cands = readdirSync6(hostsDir).filter((f) => f.endsWith(".json"));
3277
3556
  if (cands.length > 0) addItem(section2, `${dim(infoGlyph)} candidates: ${cands.join(", ")}`);
3278
3557
  }
3279
3558
  process.exitCode = 1;
@@ -3289,8 +3568,8 @@ function reportHostOverrides(section2, base, settings) {
3289
3568
  init_color();
3290
3569
  init_config();
3291
3570
  init_config_sharedDirs_guard();
3292
- import { existsSync as existsSync13, lstatSync as lstatSync9, readdirSync as readdirSync6, realpathSync as realpathSync2 } from "node:fs";
3293
- import { join as join15, sep as sep4 } from "node:path";
3571
+ import { existsSync as existsSync14, lstatSync as lstatSync11, readdirSync as readdirSync7, realpathSync as realpathSync2 } from "node:fs";
3572
+ import { join as join18, sep as sep4 } from "node:path";
3294
3573
  init_utils_json();
3295
3574
  function reportMappedProjects(section2, map) {
3296
3575
  const mapped = Object.entries(map.projects).filter(([, hosts]) => hosts[HOST]);
@@ -3300,11 +3579,11 @@ function reportMappedProjects(section2, map) {
3300
3579
  }
3301
3580
  }
3302
3581
  function reportUnmappedProjects(section2, map) {
3303
- const localProjects = join15(claudeHome(), "projects");
3304
- if (!existsSync13(localProjects)) return;
3582
+ const localProjects = join18(claudeHome(), "projects");
3583
+ if (!existsSync14(localProjects)) return;
3305
3584
  let localDirs;
3306
3585
  try {
3307
- localDirs = readdirSync6(localProjects);
3586
+ localDirs = readdirSync7(localProjects);
3308
3587
  } catch {
3309
3588
  return;
3310
3589
  }
@@ -3322,7 +3601,7 @@ function reportCurrentHostPathsMissing(section2, map) {
3322
3601
  for (const [name, hosts] of Object.entries(map.projects)) {
3323
3602
  const abspath = hosts[HOST];
3324
3603
  if (!abspath || abspath === "TBD") continue;
3325
- if (!existsSync13(abspath)) {
3604
+ if (!existsSync14(abspath)) {
3326
3605
  addItem(
3327
3606
  section2,
3328
3607
  `${yellow(warnGlyph)} path-map: ${name} local path missing on ${HOST}: ${blue(abspath)}`
@@ -3332,7 +3611,7 @@ function reportCurrentHostPathsMissing(section2, map) {
3332
3611
  }
3333
3612
  function hasSharedLeftover(entry) {
3334
3613
  try {
3335
- return lstatSync9(join15(repoHome(), "shared", entry), { throwIfNoEntry: false }) !== void 0;
3614
+ return lstatSync11(join18(repoHome(), "shared", entry), { throwIfNoEntry: false }) !== void 0;
3336
3615
  } catch {
3337
3616
  return false;
3338
3617
  }
@@ -3342,10 +3621,10 @@ var PROBEABLE_REASONS = /* @__PURE__ */ new Set([
3342
3621
  "secret-shaped"
3343
3622
  ]);
3344
3623
  function classifyLocalLink(entry) {
3345
- const linkPath = join15(claudeHome(), entry);
3624
+ const linkPath = join18(claudeHome(), entry);
3346
3625
  let stat;
3347
3626
  try {
3348
- stat = lstatSync9(linkPath, { throwIfNoEntry: false });
3627
+ stat = lstatSync11(linkPath, { throwIfNoEntry: false });
3349
3628
  } catch {
3350
3629
  return "absent";
3351
3630
  }
@@ -3358,7 +3637,7 @@ function classifyLocalLink(entry) {
3358
3637
  }
3359
3638
  let root;
3360
3639
  try {
3361
- root = realpathSync2(join15(repoHome(), "shared"));
3640
+ root = realpathSync2(join18(repoHome(), "shared"));
3362
3641
  } catch {
3363
3642
  return "foreign";
3364
3643
  }
@@ -3442,8 +3721,8 @@ function reportPathCollisions(section2, map) {
3442
3721
  else addItem(section2, `${green(okGlyph)} path-encoding: no collisions`);
3443
3722
  }
3444
3723
  function reportPathMap(section2) {
3445
- const mapPath = join15(repoHome(), "path-map.json");
3446
- if (!existsSync13(mapPath)) {
3724
+ const mapPath = join18(repoHome(), "path-map.json");
3725
+ if (!existsSync14(mapPath)) {
3447
3726
  addItem(section2, `${red(failGlyph)} path-map.json missing at ${blue(mapPath)}`);
3448
3727
  process.exitCode = 1;
3449
3728
  return;
@@ -3473,8 +3752,8 @@ function reportNeverSync(section2) {
3473
3752
 
3474
3753
  // src/commands.doctor.checks.skills.ts
3475
3754
  init_color();
3476
- import { existsSync as existsSync14 } from "node:fs";
3477
- import { join as join16 } from "node:path";
3755
+ import { existsSync as existsSync15 } from "node:fs";
3756
+ import { join as join19 } from "node:path";
3478
3757
  init_config();
3479
3758
  function stripSideIndicator(line) {
3480
3759
  if (line.endsWith(" (local only)")) return line.slice(0, -" (local only)".length);
@@ -3483,24 +3762,24 @@ function stripSideIndicator(line) {
3483
3762
  }
3484
3763
  function isGsdDiffLine(line, localBase, sharedBase) {
3485
3764
  const bare = stripSideIndicator(line);
3486
- let relative11;
3765
+ let relative12;
3487
3766
  if (bare.startsWith(localBase + "/")) {
3488
- relative11 = bare.slice(localBase.length + 1);
3767
+ relative12 = bare.slice(localBase.length + 1);
3489
3768
  } else if (bare.startsWith(sharedBase + "/")) {
3490
- relative11 = bare.slice(sharedBase.length + 1);
3769
+ relative12 = bare.slice(sharedBase.length + 1);
3491
3770
  } else {
3492
- relative11 = bare;
3771
+ relative12 = bare;
3493
3772
  }
3494
- return relative11.split("/")[0].startsWith(GSD_PREFIX);
3773
+ return relative12.split("/")[0].startsWith(GSD_PREFIX);
3495
3774
  }
3496
3775
  function reportSkillsDivergence(section2) {
3497
- const sharedSkills = join16(repoHome(), "shared", "skills");
3498
- const localSkills = join16(claudeHome(), "skills");
3499
- if (!existsSync14(sharedSkills)) {
3776
+ const sharedSkills = join19(repoHome(), "shared", "skills");
3777
+ const localSkills = join19(claudeHome(), "skills");
3778
+ if (!existsSync15(sharedSkills)) {
3500
3779
  addItem(section2, `${dim(infoGlyph)} skills: no shared/skills/ to compare`);
3501
3780
  return;
3502
3781
  }
3503
- if (!existsSync14(localSkills)) {
3782
+ if (!existsSync15(localSkills)) {
3504
3783
  addItem(section2, `${dim(infoGlyph)} skills: no local skills/ to compare`);
3505
3784
  return;
3506
3785
  }
@@ -3523,8 +3802,8 @@ function reportSkillsDivergence(section2) {
3523
3802
  init_color();
3524
3803
  init_config();
3525
3804
  import { execFileSync as execFileSync5 } from "node:child_process";
3526
- import { existsSync as existsSync17 } from "node:fs";
3527
- import { join as join20, relative as relative4 } from "node:path";
3805
+ import { existsSync as existsSync18 } from "node:fs";
3806
+ import { join as join23, relative as relative5 } from "node:path";
3528
3807
  init_commands_pull_wedge();
3529
3808
  init_push_checks();
3530
3809
  init_utils();
@@ -3547,11 +3826,11 @@ function reportGitleaksProbe(section2) {
3547
3826
  }
3548
3827
  function reportGitlinks(section2) {
3549
3828
  const repo = repoHome();
3550
- const sharedDir = join20(repo, "shared");
3551
- if (existsSync17(sharedDir)) {
3829
+ const sharedDir = join23(repo, "shared");
3830
+ if (existsSync18(sharedDir)) {
3552
3831
  const gitlinks = findGitlinks(sharedDir);
3553
3832
  for (const p of gitlinks) {
3554
- const rel = relative4(repo, p).replaceAll("\\", "/");
3833
+ const rel = relative5(repo, p).replaceAll("\\", "/");
3555
3834
  addItem(
3556
3835
  section2,
3557
3836
  `${red(failGlyph)} gitlink: ${blue(rel)} would push as submodule (run: rm -rf ${rel} or remove the nested repo)`
@@ -3646,13 +3925,13 @@ function reportGitIdentity(section2) {
3646
3925
 
3647
3926
  // src/commands.doctor.checks.backups.ts
3648
3927
  init_color();
3649
- import { existsSync as existsSync18, lstatSync as lstatSync10, readdirSync as readdirSync8 } from "node:fs";
3650
- import { join as join21 } from "node:path";
3928
+ import { existsSync as existsSync19, lstatSync as lstatSync12, readdirSync as readdirSync9 } from "node:fs";
3929
+ import { join as join24 } from "node:path";
3651
3930
  init_config();
3652
3931
  var TS_SHAPE2 = /^\d{8}-\d{6}(-\d+)?$/;
3653
3932
  function safeReaddir(dir) {
3654
3933
  try {
3655
- return readdirSync8(dir);
3934
+ return readdirSync9(dir);
3656
3935
  } catch {
3657
3936
  return [];
3658
3937
  }
@@ -3663,8 +3942,8 @@ var BYTES_PER_MB = 1024 * 1024;
3663
3942
  function dirSizeBytes(dir) {
3664
3943
  let bytes = 0;
3665
3944
  for (const entry of safeReaddir(dir)) {
3666
- const full = join21(dir, entry);
3667
- const st = lstatSync10(full, { throwIfNoEntry: false });
3945
+ const full = join24(dir, entry);
3946
+ const st = lstatSync12(full, { throwIfNoEntry: false });
3668
3947
  if (!st) continue;
3669
3948
  if (st.isSymbolicLink()) continue;
3670
3949
  if (st.isDirectory()) bytes += dirSizeBytes(full);
@@ -3674,11 +3953,11 @@ function dirSizeBytes(dir) {
3674
3953
  }
3675
3954
  function totalSizeMb(backupBase2, dirs) {
3676
3955
  let bytes = 0;
3677
- for (const name of dirs) bytes += dirSizeBytes(join21(backupBase2, name));
3956
+ for (const name of dirs) bytes += dirSizeBytes(join24(backupBase2, name));
3678
3957
  return bytes / BYTES_PER_MB;
3679
3958
  }
3680
3959
  function reportBackupsCheck(section2, backupBase2 = backupBase()) {
3681
- if (!existsSync18(backupBase2)) return;
3960
+ if (!existsSync19(backupBase2)) return;
3682
3961
  const dirs = safeReaddir(backupBase2).filter((n) => TS_SHAPE2.test(n));
3683
3962
  const count = dirs.length;
3684
3963
  const sizeMb = totalSizeMb(backupBase2, dirs);
@@ -3759,8 +4038,8 @@ function reportCheckRemote(section2) {
3759
4038
 
3760
4039
  // src/commands.doctor.check-schema.ts
3761
4040
  init_color();
3762
- import { existsSync as existsSync19 } from "node:fs";
3763
- import { join as join22 } from "node:path";
4041
+ import { existsSync as existsSync20 } from "node:fs";
4042
+ import { join as join25 } from "node:path";
3764
4043
  init_config();
3765
4044
 
3766
4045
  // src/http-fetch.ts
@@ -3797,8 +4076,8 @@ function fetchSchemaKeys() {
3797
4076
  }
3798
4077
  }
3799
4078
  function reportCheckSchema(section2) {
3800
- const settingsPath = join22(claudeHome(), "settings.json");
3801
- if (!existsSync19(settingsPath)) {
4079
+ const settingsPath = join25(claudeHome(), "settings.json");
4080
+ if (!existsSync20(settingsPath)) {
3802
4081
  addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json to check`);
3803
4082
  return;
3804
4083
  }
@@ -3828,9 +4107,9 @@ function reportCheckSchema(section2) {
3828
4107
  init_color();
3829
4108
  import { randomBytes as randomBytes3 } from "node:crypto";
3830
4109
  import { execFileSync as execFileSync11 } from "node:child_process";
3831
- import { existsSync as existsSync22, mkdirSync as mkdirSync8, readdirSync as readdirSync11, rmSync as rmSync12 } from "node:fs";
4110
+ import { existsSync as existsSync23, mkdirSync as mkdirSync8, readdirSync as readdirSync12, rmSync as rmSync12 } from "node:fs";
3832
4111
  import { homedir as homedir6 } from "node:os";
3833
- import { join as join29 } from "node:path";
4112
+ import { join as join32 } from "node:path";
3834
4113
 
3835
4114
  // src/commands.doctor.check-shared.memory.ts
3836
4115
  init_color();
@@ -3838,7 +4117,7 @@ import { execFileSync as execFileSync9 } from "node:child_process";
3838
4117
  import { randomBytes } from "node:crypto";
3839
4118
  import { mkdirSync as mkdirSync4, rmSync as rmSync9, writeFileSync as writeFileSync4 } from "node:fs";
3840
4119
  import { homedir as homedir4 } from "node:os";
3841
- import { dirname as dirname3, join as join24 } from "node:path";
4120
+ import { dirname as dirname4, join as join27 } from "node:path";
3842
4121
  init_config();
3843
4122
  init_push_gitleaks();
3844
4123
  init_utils_fs();
@@ -3871,8 +4150,8 @@ function buildMemoryScanTree(tmpRoot) {
3871
4150
  incomplete = true;
3872
4151
  continue;
3873
4152
  }
3874
- const dest = join24(tmpRoot, rel);
3875
- mkdirSync4(dirname3(dest), { recursive: true });
4153
+ const dest = join27(tmpRoot, rel);
4154
+ mkdirSync4(dirname4(dest), { recursive: true });
3876
4155
  writeFileSync4(dest, blob);
3877
4156
  logicals.add(m[1]);
3878
4157
  }
@@ -3890,10 +4169,10 @@ function reportMemoryFindings(section2, findings) {
3890
4169
  function reportCommittedMemory(section2) {
3891
4170
  let tmpRoot;
3892
4171
  try {
3893
- const cacheDir = join24(homedir4(), ".cache", "claude-nomad");
4172
+ const cacheDir = join27(homedir4(), ".cache", "claude-nomad");
3894
4173
  mkdirSync4(cacheDir, { recursive: true });
3895
4174
  const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes(4).toString("hex")}`;
3896
- tmpRoot = join24(cacheDir, `check-shared-memory-tree-${stamp}`);
4175
+ tmpRoot = join27(cacheDir, `check-shared-memory-tree-${stamp}`);
3897
4176
  mkdirSync4(tmpRoot, { recursive: true, mode: 448 });
3898
4177
  const { staged, incomplete } = buildMemoryScanTree(tmpRoot);
3899
4178
  if (incomplete) {
@@ -3940,12 +4219,12 @@ function reportCommittedMemory(section2) {
3940
4219
 
3941
4220
  // src/commands.doctor.check-shared.scan.ts
3942
4221
  init_color();
3943
- import { join as join25 } from "node:path";
4222
+ import { join as join28 } from "node:path";
3944
4223
  init_config();
3945
4224
  init_push_gitleaks();
3946
4225
  function scrubPath(logical, sid, logicalToEncoded) {
3947
4226
  const encoded = logicalToEncoded.get(logical) ?? logical;
3948
- return join25(claudeHome(), "projects", encoded, `${sid}.jsonl`);
4227
+ return join28(claudeHome(), "projects", encoded, `${sid}.jsonl`);
3949
4228
  }
3950
4229
  function reportSessionFindings(section2, bySession) {
3951
4230
  for (const [sid, counts] of bySession) {
@@ -4034,7 +4313,7 @@ import { execFileSync as execFileSync10 } from "node:child_process";
4034
4313
  import { randomBytes as randomBytes2 } from "node:crypto";
4035
4314
  import { mkdirSync as mkdirSync5, rmSync as rmSync10, writeFileSync as writeFileSync5 } from "node:fs";
4036
4315
  import { homedir as homedir5 } from "node:os";
4037
- import { dirname as dirname4, join as join26 } from "node:path";
4316
+ import { dirname as dirname5, join as join29 } from "node:path";
4038
4317
  init_config();
4039
4318
  init_push_gitleaks();
4040
4319
  init_utils_fs();
@@ -4067,8 +4346,8 @@ function buildSkillScanTree(tmpRoot) {
4067
4346
  incomplete = true;
4068
4347
  continue;
4069
4348
  }
4070
- const dest = join26(tmpRoot, rel);
4071
- mkdirSync5(dirname4(dest), { recursive: true });
4349
+ const dest = join29(tmpRoot, rel);
4350
+ mkdirSync5(dirname5(dest), { recursive: true });
4072
4351
  writeFileSync5(dest, blob);
4073
4352
  names.add(m[1]);
4074
4353
  }
@@ -4090,10 +4369,10 @@ function reportSkillFindings(section2, findings) {
4090
4369
  function reportCommittedSkills(section2) {
4091
4370
  let tmpRoot;
4092
4371
  try {
4093
- const cacheDir = join26(homedir5(), ".cache", "claude-nomad");
4372
+ const cacheDir = join29(homedir5(), ".cache", "claude-nomad");
4094
4373
  mkdirSync5(cacheDir, { recursive: true });
4095
4374
  const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes2(4).toString("hex")}`;
4096
- tmpRoot = join26(cacheDir, `check-shared-skills-tree-${stamp}`);
4375
+ tmpRoot = join29(cacheDir, `check-shared-skills-tree-${stamp}`);
4097
4376
  mkdirSync5(tmpRoot, { recursive: true, mode: 448 });
4098
4377
  const { staged, incomplete } = buildSkillScanTree(tmpRoot);
4099
4378
  if (incomplete) {
@@ -4143,8 +4422,8 @@ init_config();
4143
4422
 
4144
4423
  // src/remap.ts
4145
4424
  init_config_sharedDirs_guard();
4146
- import { cpSync as cpSync6, existsSync as existsSync21, lstatSync as lstatSync11, mkdirSync as mkdirSync7, readdirSync as readdirSync10, rmSync as rmSync11, statSync as statSync5 } from "node:fs";
4147
- import { dirname as dirname6, join as join28, relative as relative5, sep as sep5 } from "node:path";
4425
+ import { cpSync as cpSync5, existsSync as existsSync22, lstatSync as lstatSync13, mkdirSync as mkdirSync7, readdirSync as readdirSync11, rmSync as rmSync11, statSync as statSync5 } from "node:fs";
4426
+ import { dirname as dirname7, join as join31, relative as relative6, sep as sep5 } from "node:path";
4148
4427
  init_config();
4149
4428
 
4150
4429
  // src/push-manifest.ts
@@ -4152,8 +4431,8 @@ init_config();
4152
4431
  init_push_gitleaks_config();
4153
4432
  init_utils_fs();
4154
4433
  import { createHash } from "node:crypto";
4155
- import { existsSync as existsSync20, mkdirSync as mkdirSync6, readdirSync as readdirSync9, readFileSync as readFileSync7, statSync as statSync4 } from "node:fs";
4156
- import { dirname as dirname5, join as join27 } from "node:path";
4434
+ import { existsSync as existsSync21, mkdirSync as mkdirSync6, readdirSync as readdirSync10, readFileSync as readFileSync7, statSync as statSync4 } from "node:fs";
4435
+ import { dirname as dirname6, join as join30 } from "node:path";
4157
4436
  function isChanged(prev, cur, hash) {
4158
4437
  if (prev === void 0) return true;
4159
4438
  if (prev.size !== cur.size) return true;
@@ -4193,8 +4472,8 @@ function hashFile(absPath) {
4193
4472
  return createHash("sha256").update(readFileSync7(absPath)).digest("hex");
4194
4473
  }
4195
4474
  function enumerateDir(dir, results) {
4196
- for (const entry of readdirSync9(dir)) {
4197
- const fullPath = join27(dir, entry);
4475
+ for (const entry of readdirSync10(dir)) {
4476
+ const fullPath = join30(dir, entry);
4198
4477
  const st = statSync4(fullPath);
4199
4478
  if (st.isDirectory()) {
4200
4479
  enumerateDir(fullPath, results);
@@ -4205,8 +4484,8 @@ function enumerateDir(dir, results) {
4205
4484
  }
4206
4485
  function enumerateSourceFiles(projectDir) {
4207
4486
  const results = [];
4208
- for (const entry of readdirSync9(projectDir)) {
4209
- const fullPath = join27(projectDir, entry);
4487
+ for (const entry of readdirSync10(projectDir)) {
4488
+ const fullPath = join30(projectDir, entry);
4210
4489
  const st = statSync4(fullPath);
4211
4490
  if (st.isDirectory()) {
4212
4491
  enumerateDir(fullPath, results);
@@ -4218,15 +4497,15 @@ function enumerateSourceFiles(projectDir) {
4218
4497
  }
4219
4498
  function fileIdentity(p) {
4220
4499
  if (p === null) return "none::absent";
4221
- if (!existsSync20(p)) return `${p}::absent`;
4500
+ if (!existsSync21(p)) return `${p}::absent`;
4222
4501
  return `${p}::${createHash("sha256").update(readFileSync7(p)).digest("hex")}`;
4223
4502
  }
4224
4503
  function computeConfigHash() {
4225
4504
  const repo = repoHome();
4226
4505
  const parts = [
4227
4506
  fileIdentity(resolveTomlPath(repo)),
4228
- fileIdentity(join27(repo, ".gitleaks.overlay.toml")),
4229
- fileIdentity(join27(repo, ".gitleaksignore"))
4507
+ fileIdentity(join30(repo, ".gitleaks.overlay.toml")),
4508
+ fileIdentity(join30(repo, ".gitleaksignore"))
4230
4509
  ];
4231
4510
  return createHash("sha256").update(parts.join("\n")).digest("hex");
4232
4511
  }
@@ -4246,7 +4525,7 @@ function readManifest(path) {
4246
4525
  }
4247
4526
  }
4248
4527
  function writeManifest(path, manifest) {
4249
- mkdirSync6(dirname5(path), { recursive: true });
4528
+ mkdirSync6(dirname6(path), { recursive: true });
4250
4529
  writeJsonAtomic(path, manifest);
4251
4530
  }
4252
4531
  function buildManifest(files, scannerVersion, configHash) {
@@ -4261,7 +4540,7 @@ var TMP_SUFFIX = ".nomad-tmp";
4261
4540
  function atomicMirror(src, dst, options) {
4262
4541
  const tmp = `${dst}${TMP_SUFFIX}`;
4263
4542
  rmSync11(tmp, { recursive: true, force: true });
4264
- cpSync6(src, tmp, options);
4543
+ cpSync5(src, tmp, options);
4265
4544
  rmSync11(dst, { recursive: true, force: true });
4266
4545
  renameAtomicRetry(tmp, dst);
4267
4546
  }
@@ -4270,9 +4549,9 @@ function overlaySessionDir(src, dst) {
4270
4549
  cpSyncGuarded(src, dst, void 0, "overlaySessionDir");
4271
4550
  }
4272
4551
  function copyFileAtomic(src, dst) {
4273
- mkdirSync7(dirname6(dst), { recursive: true });
4552
+ mkdirSync7(dirname7(dst), { recursive: true });
4274
4553
  const tmp = `${dst}.tmp.${process.pid}`;
4275
- cpSync6(src, tmp, { force: true, preserveTimestamps: true });
4554
+ cpSync5(src, tmp, { force: true, preserveTimestamps: true });
4276
4555
  renameAtomicRetry(tmp, dst);
4277
4556
  }
4278
4557
  function hasDeltaForDir(sel, localDir) {
@@ -4288,12 +4567,12 @@ function applySelective(sel, localDir, repoDst) {
4288
4567
  const prefix = `${localDir}${sep5}`;
4289
4568
  for (const src of sel.changed) {
4290
4569
  if (!src.startsWith(prefix)) continue;
4291
- copyFileAtomic(src, join28(repoDst, relative5(localDir, src)));
4570
+ copyFileAtomic(src, join31(repoDst, relative6(localDir, src)));
4292
4571
  }
4293
4572
  for (const src of sel.deleted) {
4294
4573
  if (!src.startsWith(prefix)) continue;
4295
- const dst = join28(repoDst, relative5(localDir, src));
4296
- if (existsSync21(dst)) rmSync11(dst);
4574
+ const dst = join31(repoDst, relative6(localDir, src));
4575
+ if (existsSync22(dst)) rmSync11(dst);
4297
4576
  }
4298
4577
  }
4299
4578
  function copyDirJsonlOnly(src, dst) {
@@ -4301,7 +4580,7 @@ function copyDirJsonlOnly(src, dst) {
4301
4580
  recursive: true,
4302
4581
  force: true,
4303
4582
  filter: (srcPath) => {
4304
- const rel = relative5(src, srcPath);
4583
+ const rel = relative6(src, srcPath);
4305
4584
  if (rel === "") return true;
4306
4585
  if (rel.split(sep5).length > 1) return true;
4307
4586
  if (statSync5(srcPath).isDirectory()) return true;
@@ -4322,15 +4601,15 @@ function remapPull(ts, opts = {}) {
4322
4601
  const wouldPull = [];
4323
4602
  const repo = repoHome();
4324
4603
  const claude = claudeHome();
4325
- const mapPath = join28(repo, "path-map.json");
4326
- const repoProjects = join28(repo, "shared", "projects");
4327
- if (!existsSync21(mapPath) || !existsSync21(repoProjects)) {
4604
+ const mapPath = join31(repo, "path-map.json");
4605
+ const repoProjects = join31(repo, "shared", "projects");
4606
+ if (!existsSync22(mapPath) || !existsSync22(repoProjects)) {
4328
4607
  const text = "no path-map or repo projects dir; skipping session remap";
4329
4608
  emitPreview(opts.onPreview, { kind: "note", text }, text);
4330
4609
  return { unmapped: 0, pulled, wouldPull };
4331
4610
  }
4332
4611
  const map = readPathMap(mapPath);
4333
- const localProjects = join28(claude, "projects");
4612
+ const localProjects = join31(claude, "projects");
4334
4613
  if (!dryRun) mkdirSync7(localProjects, { recursive: true });
4335
4614
  for (const [logical, hosts] of Object.entries(map.projects)) {
4336
4615
  assertSafeLogical(logical);
@@ -4340,9 +4619,9 @@ function remapPull(ts, opts = {}) {
4340
4619
  continue;
4341
4620
  }
4342
4621
  assertSafeLocalRoot(localPath, logical);
4343
- const src = join28(repoProjects, logical);
4344
- if (!existsSync21(src)) continue;
4345
- const dst = join28(localProjects, encodePath(localPath));
4622
+ const src = join31(repoProjects, logical);
4623
+ if (!existsSync22(src)) continue;
4624
+ const dst = join31(localProjects, encodePath(localPath));
4346
4625
  if (dryRun) {
4347
4626
  wouldPull.push(logical);
4348
4627
  emitPreview(
@@ -4360,12 +4639,12 @@ function remapPull(ts, opts = {}) {
4360
4639
  }
4361
4640
  function countLocalOnly(src, dst) {
4362
4641
  let count = 0;
4363
- for (const name of readdirSync10(dst)) {
4364
- const dstPath = join28(dst, name);
4365
- const srcPath = join28(src, name);
4366
- if (lstatSync11(dstPath).isDirectory()) {
4642
+ for (const name of readdirSync11(dst)) {
4643
+ const dstPath = join31(dst, name);
4644
+ const srcPath = join31(src, name);
4645
+ if (lstatSync13(dstPath).isDirectory()) {
4367
4646
  count += countLocalOnly(srcPath, dstPath);
4368
- } else if (lstatSync11(srcPath, { throwIfNoEntry: false }) === void 0) {
4647
+ } else if (lstatSync13(srcPath, { throwIfNoEntry: false }) === void 0) {
4369
4648
  count++;
4370
4649
  }
4371
4650
  }
@@ -4374,20 +4653,20 @@ function countLocalOnly(src, dst) {
4374
4653
  function scanLocalOnly() {
4375
4654
  const repo = repoHome();
4376
4655
  const claude = claudeHome();
4377
- const mapPath = join28(repo, "path-map.json");
4378
- const repoProjects = join28(repo, "shared", "projects");
4379
- if (!existsSync21(mapPath) || !existsSync21(repoProjects)) return 0;
4656
+ const mapPath = join31(repo, "path-map.json");
4657
+ const repoProjects = join31(repo, "shared", "projects");
4658
+ if (!existsSync22(mapPath) || !existsSync22(repoProjects)) return 0;
4380
4659
  const map = readPathMap(mapPath);
4381
- const localProjects = join28(claude, "projects");
4660
+ const localProjects = join31(claude, "projects");
4382
4661
  let count = 0;
4383
4662
  for (const [logical, hosts] of Object.entries(map.projects)) {
4384
4663
  assertSafeLogical(logical);
4385
4664
  const localPath = hosts[HOST];
4386
4665
  if (!localPath || localPath === "TBD") continue;
4387
4666
  assertSafeLocalRoot(localPath, logical);
4388
- const dst = join28(localProjects, encodePath(localPath));
4389
- if (!existsSync21(dst)) continue;
4390
- count += countLocalOnly(join28(repoProjects, logical), dst);
4667
+ const dst = join31(localProjects, encodePath(localPath));
4668
+ if (!existsSync22(dst)) continue;
4669
+ count += countLocalOnly(join31(repoProjects, logical), dst);
4391
4670
  }
4392
4671
  return count;
4393
4672
  }
@@ -4422,26 +4701,26 @@ function remapPush(ts, opts = {}) {
4422
4701
  const wouldPush = [];
4423
4702
  const repo = repoHome();
4424
4703
  const claude = claudeHome();
4425
- const mapPath = join28(repo, "path-map.json");
4426
- if (!existsSync21(mapPath)) {
4704
+ const mapPath = join31(repo, "path-map.json");
4705
+ if (!existsSync22(mapPath)) {
4427
4706
  log("no path-map.json; skipping session export");
4428
4707
  return { unmapped: 0, collisions: 0, pushed, wouldPush };
4429
4708
  }
4430
4709
  const map = readPathMap(mapPath);
4431
- const localProjects = join28(claude, "projects");
4432
- const repoProjects = join28(repo, "shared", "projects");
4710
+ const localProjects = join31(claude, "projects");
4711
+ const repoProjects = join31(repo, "shared", "projects");
4433
4712
  const reverse = buildReverseMap(map);
4434
- if (!existsSync21(localProjects)) return { unmapped, collisions: 0, pushed, wouldPush };
4713
+ if (!existsSync22(localProjects)) return { unmapped, collisions: 0, pushed, wouldPush };
4435
4714
  if (!dryRun) mkdirSync7(repoProjects, { recursive: true });
4436
- for (const dir of readdirSync10(localProjects)) {
4715
+ for (const dir of readdirSync11(localProjects)) {
4437
4716
  if (dir.endsWith(TMP_SUFFIX)) continue;
4438
4717
  const logical = reverse.get(dir);
4439
4718
  if (!logical) {
4440
4719
  unmapped++;
4441
4720
  continue;
4442
4721
  }
4443
- const localDir = join28(localProjects, dir);
4444
- const repoDst = join28(repoProjects, logical);
4722
+ const localDir = join31(localProjects, dir);
4723
+ const repoDst = join31(repoProjects, logical);
4445
4724
  if (skipForSelection(opts.selection, localDir)) continue;
4446
4725
  if (dryRun) {
4447
4726
  wouldPush.push(logical);
@@ -4465,8 +4744,8 @@ function buildScanTree(tmpRoot) {
4465
4744
  const logicalToEncoded = /* @__PURE__ */ new Map();
4466
4745
  let staged = 0;
4467
4746
  const repo = repoHome();
4468
- const mapPath = join29(repo, "path-map.json");
4469
- if (!existsSync22(mapPath)) return { logicalToEncoded, staged, malformed: false };
4747
+ const mapPath = join32(repo, "path-map.json");
4748
+ if (!existsSync23(mapPath)) return { logicalToEncoded, staged, malformed: false };
4470
4749
  let map;
4471
4750
  try {
4472
4751
  map = readJson(mapPath);
@@ -4483,12 +4762,12 @@ function buildScanTree(tmpRoot) {
4483
4762
  if (!p || p === "TBD") continue;
4484
4763
  reverse.set(encodePath(p), logical);
4485
4764
  }
4486
- const localProjects = join29(claudeHome(), "projects");
4487
- if (!existsSync22(localProjects)) return { logicalToEncoded, staged, malformed: false };
4488
- for (const dir of readdirSync11(localProjects)) {
4765
+ const localProjects = join32(claudeHome(), "projects");
4766
+ if (!existsSync23(localProjects)) return { logicalToEncoded, staged, malformed: false };
4767
+ for (const dir of readdirSync12(localProjects)) {
4489
4768
  const logical = reverse.get(dir);
4490
4769
  if (!logical) continue;
4491
- copyDirJsonlOnly(join29(localProjects, dir), join29(tmpRoot, "shared", "projects", logical));
4770
+ copyDirJsonlOnly(join32(localProjects, dir), join32(tmpRoot, "shared", "projects", logical));
4492
4771
  logicalToEncoded.set(logical, dir);
4493
4772
  staged++;
4494
4773
  }
@@ -4518,11 +4797,11 @@ function ensureGitleaksReady(section2, gitleaksReady) {
4518
4797
  return true;
4519
4798
  }
4520
4799
  function runLocalPreviewScan(section2) {
4521
- const cacheDir = join29(homedir6(), ".cache", "claude-nomad");
4800
+ const cacheDir = join32(homedir6(), ".cache", "claude-nomad");
4522
4801
  mkdirSync8(cacheDir, { recursive: true });
4523
4802
  const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes3(4).toString("hex")}`;
4524
- const reportPath = join29(cacheDir, `check-shared-${stamp}.json`);
4525
- const tmpRoot = join29(cacheDir, `check-shared-tree-${stamp}`);
4803
+ const reportPath = join32(cacheDir, `check-shared-${stamp}.json`);
4804
+ const tmpRoot = join32(cacheDir, `check-shared-tree-${stamp}`);
4526
4805
  try {
4527
4806
  const { logicalToEncoded, staged, malformed } = buildScanTree(tmpRoot);
4528
4807
  if (malformed) {
@@ -4555,8 +4834,8 @@ function reportCheckShared(section2, gitleaksReady) {
4555
4834
 
4556
4835
  // src/commands.doctor.checks.hooks.scope.ts
4557
4836
  init_color();
4558
- import { existsSync as existsSync23, readFileSync as readFileSync8, readdirSync as readdirSync12, realpathSync as realpathSync3 } from "node:fs";
4559
- import { dirname as dirname7, extname, join as join30 } from "node:path";
4837
+ import { existsSync as existsSync24, readFileSync as readFileSync8, readdirSync as readdirSync13, realpathSync as realpathSync3 } from "node:fs";
4838
+ import { dirname as dirname8, extname, join as join33 } from "node:path";
4560
4839
  init_config();
4561
4840
  function typeFromPackageJson(pkgPath) {
4562
4841
  try {
@@ -4576,11 +4855,11 @@ function effectiveType(hookPath) {
4576
4855
  } catch {
4577
4856
  return null;
4578
4857
  }
4579
- let dir = dirname7(real);
4858
+ let dir = dirname8(real);
4580
4859
  for (; ; ) {
4581
- const pkg = join30(dir, "package.json");
4582
- if (existsSync23(pkg)) return typeFromPackageJson(pkg);
4583
- const parent = dirname7(dir);
4860
+ const pkg = join33(dir, "package.json");
4861
+ if (existsSync24(pkg)) return typeFromPackageJson(pkg);
4862
+ const parent = dirname8(dir);
4584
4863
  if (parent === dir) return "cjs";
4585
4864
  dir = parent;
4586
4865
  }
@@ -4608,7 +4887,7 @@ function remedy(family) {
4608
4887
  }
4609
4888
  function safeReaddir2(dir) {
4610
4889
  try {
4611
- return readdirSync12(dir);
4890
+ return readdirSync13(dir);
4612
4891
  } catch {
4613
4892
  return [];
4614
4893
  }
@@ -4621,15 +4900,15 @@ function safeRead(path) {
4621
4900
  }
4622
4901
  }
4623
4902
  function reportHookScopeCheck(section2) {
4624
- const hooksDir = join30(claudeHome(), "hooks");
4625
- if (!existsSync23(hooksDir)) {
4903
+ const hooksDir = join33(claudeHome(), "hooks");
4904
+ if (!existsSync24(hooksDir)) {
4626
4905
  addItem(section2, `${dim(infoGlyph)} no ~/.claude/hooks; skipping module-scope check`);
4627
4906
  return;
4628
4907
  }
4629
4908
  let anyWarn = false;
4630
4909
  for (const name of safeReaddir2(hooksDir)) {
4631
4910
  if (extname(name) !== ".js") continue;
4632
- const abs = join30(hooksDir, name);
4911
+ const abs = join33(hooksDir, name);
4633
4912
  const eff = effectiveType(abs);
4634
4913
  if (eff === null) continue;
4635
4914
  const src = safeRead(abs);
@@ -4649,8 +4928,8 @@ function reportHookScopeCheck(section2) {
4649
4928
 
4650
4929
  // src/commands.doctor.checks.hooks.ts
4651
4930
  init_color();
4652
- import { existsSync as existsSync24 } from "node:fs";
4653
- import { join as join31 } from "node:path";
4931
+ import { existsSync as existsSync25 } from "node:fs";
4932
+ import { join as join34 } from "node:path";
4654
4933
  init_config();
4655
4934
  function expandHome(token) {
4656
4935
  const h2 = home();
@@ -4693,7 +4972,7 @@ function checkEventGroups(section2, event, groups) {
4693
4972
  for (const group of groups) {
4694
4973
  for (const cmd of commandsFromGroup(group)) {
4695
4974
  for (const resolved of claudePathsIn(cmd)) {
4696
- if (existsSync24(resolved)) continue;
4975
+ if (existsSync25(resolved)) continue;
4697
4976
  addItem(
4698
4977
  section2,
4699
4978
  `${red(failGlyph)} hooks/${event}: command target missing: ${resolved} (run nomad pull)`
@@ -4706,8 +4985,8 @@ function checkEventGroups(section2, event, groups) {
4706
4985
  return anyFail;
4707
4986
  }
4708
4987
  function reportHooksTargetCheck(section2) {
4709
- const settingsPath = join31(claudeHome(), "settings.json");
4710
- if (!existsSync24(settingsPath)) {
4988
+ const settingsPath = join34(claudeHome(), "settings.json");
4989
+ if (!existsSync25(settingsPath)) {
4711
4990
  addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json; skipping hook target check`);
4712
4991
  return;
4713
4992
  }
@@ -4730,12 +5009,12 @@ function reportHooksTargetCheck(section2) {
4730
5009
 
4731
5010
  // src/commands.doctor.checks.hooks.preserve-symlinks.ts
4732
5011
  init_color();
4733
- import { existsSync as existsSync26, readFileSync as readFileSync9 } from "node:fs";
4734
- import { join as join33 } from "node:path";
5012
+ import { existsSync as existsSync27, readFileSync as readFileSync9 } from "node:fs";
5013
+ import { join as join36 } from "node:path";
4735
5014
 
4736
5015
  // src/commands.doctor.checks.hooks.preserve-symlinks.probe.ts
4737
- import { closeSync as closeSync3, existsSync as existsSync25, openSync as openSync3, readSync, realpathSync as realpathSync4 } from "node:fs";
4738
- import { dirname as dirname8, join as join32, resolve as resolve2 } from "node:path";
5016
+ import { closeSync as closeSync3, existsSync as existsSync26, openSync as openSync3, readSync, realpathSync as realpathSync4 } from "node:fs";
5017
+ import { dirname as dirname9, join as join35, resolve as resolve3 } from "node:path";
4739
5018
  function suppressedRanges(src) {
4740
5019
  const ranges = [];
4741
5020
  const re = /\/\*[\s\S]*?\*\/|\/\/[^\n]*|'[^']*'|"[^"]*"|`[^`]*`/g;
@@ -4766,13 +5045,13 @@ function topRelativeSpecifiers(src) {
4766
5045
  return specifiers;
4767
5046
  }
4768
5047
  function specifierIsMissing(specifier, baseDir) {
4769
- const base = resolve2(baseDir, specifier);
4770
- if (existsSync25(base)) return false;
5048
+ const base = resolve3(baseDir, specifier);
5049
+ if (existsSync26(base)) return false;
4771
5050
  for (const ext of [".js", ".cjs", ".mjs"]) {
4772
- if (existsSync25(base + ext)) return false;
5051
+ if (existsSync26(base + ext)) return false;
4773
5052
  }
4774
5053
  for (const idx of ["index.js", "index.cjs", "index.mjs"]) {
4775
- if (existsSync25(join32(base, idx))) return false;
5054
+ if (existsSync26(join35(base, idx))) return false;
4776
5055
  }
4777
5056
  return true;
4778
5057
  }
@@ -4798,7 +5077,7 @@ function relativeRequireTargetsBroken(scriptPath) {
4798
5077
  }
4799
5078
  const specifiers = topRelativeSpecifiers(raw);
4800
5079
  if (specifiers.length === 0) return false;
4801
- const baseDir = dirname8(realPath);
5080
+ const baseDir = dirname9(realPath);
4802
5081
  for (const spec of specifiers) {
4803
5082
  if (specifierIsMissing(spec, baseDir)) return true;
4804
5083
  }
@@ -4827,8 +5106,8 @@ function commandTokens(command) {
4827
5106
  return tokens;
4828
5107
  }
4829
5108
  function readPathMapSafe() {
4830
- const mapPath = join33(repoHome(), "path-map.json");
4831
- if (!existsSync26(mapPath)) return { projects: {} };
5109
+ const mapPath = join36(repoHome(), "path-map.json");
5110
+ if (!existsSync27(mapPath)) return { projects: {} };
4832
5111
  try {
4833
5112
  return JSON.parse(readFileSync9(mapPath, "utf8"));
4834
5113
  } catch {
@@ -4902,8 +5181,8 @@ function checkEventForPreserveSymlinks(section2, event, groups, sharedLinkNames)
4902
5181
  return anyWarn;
4903
5182
  }
4904
5183
  function reportPreserveSymlinksCheck(section2) {
4905
- const settingsPath = join33(claudeHome(), "settings.json");
4906
- if (!existsSync26(settingsPath)) {
5184
+ const settingsPath = join36(claudeHome(), "settings.json");
5185
+ if (!existsSync27(settingsPath)) {
4907
5186
  addItem(
4908
5187
  section2,
4909
5188
  `${dim(infoGlyph)} no ~/.claude/settings.json; skipping preserve-symlinks-main check`
@@ -4936,8 +5215,8 @@ function reportPreserveSymlinksCheck(section2) {
4936
5215
 
4937
5216
  // src/commands.doctor.checks.settings-drift.ts
4938
5217
  init_color();
4939
- import { existsSync as existsSync27, readFileSync as readFileSync10 } from "node:fs";
4940
- import { join as join34 } from "node:path";
5218
+ import { existsSync as existsSync28, readFileSync as readFileSync10 } from "node:fs";
5219
+ import { join as join37 } from "node:path";
4941
5220
  init_config();
4942
5221
  init_utils_json();
4943
5222
  function diffMergedSettings(merged, settings) {
@@ -4955,7 +5234,7 @@ function tryReadJson(filePath) {
4955
5234
  }
4956
5235
  }
4957
5236
  function reportHooksBaseSelfCleanNote(section2) {
4958
- const basePath = join34(repoHome(), "shared", "settings.base.json");
5237
+ const basePath = join37(repoHome(), "shared", "settings.base.json");
4959
5238
  const base = tryReadJson(basePath);
4960
5239
  if (base === null) return;
4961
5240
  if (!baseHasGsdHookEntries(base)) return;
@@ -4968,14 +5247,14 @@ function reportSettingsDriftCheck(section2) {
4968
5247
  const claude = claudeHome();
4969
5248
  const repo = repoHome();
4970
5249
  const host = HOST;
4971
- const settingsPath = join34(claude, "settings.json");
4972
- const basePath = join34(repo, "shared", "settings.base.json");
4973
- const hostPath = join34(repo, "hosts", `${host}.json`);
4974
- if (!existsSync27(settingsPath)) {
5250
+ const settingsPath = join37(claude, "settings.json");
5251
+ const basePath = join37(repo, "shared", "settings.base.json");
5252
+ const hostPath = join37(repo, "hosts", `${host}.json`);
5253
+ if (!existsSync28(settingsPath)) {
4975
5254
  addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json; skipping merge-drift check`);
4976
5255
  return;
4977
5256
  }
4978
- if (!existsSync27(basePath)) {
5257
+ if (!existsSync28(basePath)) {
4979
5258
  addItem(
4980
5259
  section2,
4981
5260
  `${dim(infoGlyph)} shared/settings.base.json missing; skipping merge-drift check`
@@ -4994,7 +5273,7 @@ function reportSettingsDriftCheck(section2) {
4994
5273
  if (settings === null) {
4995
5274
  return;
4996
5275
  }
4997
- const hostExists = existsSync27(hostPath);
5276
+ const hostExists = existsSync28(hostPath);
4998
5277
  const hostObj = hostExists ? tryReadJson(hostPath) : null;
4999
5278
  if (hostExists && hostObj === null) {
5000
5279
  addItem(
@@ -5211,15 +5490,15 @@ function reportSyncModality(section2) {
5211
5490
  // src/commands.doctor.checks.crlf.ts
5212
5491
  init_color();
5213
5492
  import { execFileSync as execFileSync13 } from "node:child_process";
5214
- import { existsSync as existsSync28, readFileSync as readFileSync13 } from "node:fs";
5215
- import { join as join35 } from "node:path";
5493
+ import { existsSync as existsSync29, readFileSync as readFileSync13 } from "node:fs";
5494
+ import { join as join38 } from "node:path";
5216
5495
  init_config();
5217
5496
  var PROBE_TIMEOUT_MS2 = 3e3;
5218
5497
  var GUARD_LINE = /^\*\s+-text\b/;
5219
5498
  function hasGitattributesGuard(repo) {
5220
5499
  try {
5221
- const path = join35(repo, ".gitattributes");
5222
- if (!existsSync28(path)) return false;
5500
+ const path = join38(repo, ".gitattributes");
5501
+ if (!existsSync29(path)) return false;
5223
5502
  const content = readFileSync13(path, "utf8");
5224
5503
  return content.split("\n").map((line) => line.trim()).some((line) => line !== "" && !line.startsWith("#") && GUARD_LINE.test(line));
5225
5504
  } catch {
@@ -5260,34 +5539,34 @@ function reportCrlfGuardCheck(section2, run = execFileSync13) {
5260
5539
 
5261
5540
  // src/spinner.ts
5262
5541
  init_color();
5263
- import { existsSync as existsSync33 } from "node:fs";
5542
+ import { existsSync as existsSync34 } from "node:fs";
5264
5543
  import { fileURLToPath as fileURLToPath4 } from "node:url";
5265
5544
  import { Worker } from "node:worker_threads";
5266
5545
 
5267
5546
  // src/commands.push.recovery.ts
5268
5547
  init_config();
5269
5548
  import { readFileSync as readFileSync18, rmSync as rmSync15, writeFileSync as writeFileSync9 } from "node:fs";
5270
- import { join as join43 } from "node:path";
5549
+ import { join as join46 } from "node:path";
5271
5550
  import { createInterface as createInterface2 } from "node:readline/promises";
5272
5551
 
5273
5552
  // src/commands.push.recovery.actions.ts
5274
5553
  init_config();
5275
5554
  import { readFileSync as readFileSync17 } from "node:fs";
5276
- import { isAbsolute as isAbsolute2, resolve as resolve3, sep as sep9 } from "node:path";
5555
+ import { isAbsolute as isAbsolute2, resolve as resolve4, sep as sep9 } from "node:path";
5277
5556
 
5278
5557
  // src/commands.push.recovery.memory.ts
5279
5558
  init_config();
5280
5559
  init_config_sharedDirs_guard();
5281
5560
  import {
5282
- cpSync as cpSync7,
5283
- lstatSync as lstatSync12,
5561
+ cpSync as cpSync6,
5562
+ lstatSync as lstatSync14,
5284
5563
  mkdirSync as mkdirSync9,
5285
5564
  readFileSync as readFileSync14,
5286
5565
  realpathSync as realpathSync5,
5287
5566
  statSync as statSync6,
5288
5567
  writeFileSync as writeFileSync6
5289
5568
  } from "node:fs";
5290
- import { dirname as dirname9, join as join36, sep as sep6 } from "node:path";
5569
+ import { dirname as dirname10, join as join39, sep as sep6 } from "node:path";
5291
5570
  init_push_gitleaks_scan();
5292
5571
 
5293
5572
  // src/rel-path-guard.ts
@@ -5316,11 +5595,11 @@ function resolveMemoryLocalPath(logical, relPath, map) {
5316
5595
  if (!isSafeRelPath(relPath)) return null;
5317
5596
  const abs = map.projects[logical]?.[HOST];
5318
5597
  if (abs === void 0) return null;
5319
- const memoryRoot = join36(claudeHome(), "projects", encodePath(abs), "memory");
5320
- const localPath = join36(memoryRoot, ...relPath.split("/"));
5598
+ const memoryRoot = join39(claudeHome(), "projects", encodePath(abs), "memory");
5599
+ const localPath = join39(memoryRoot, ...relPath.split("/"));
5321
5600
  if (localPath !== memoryRoot && !localPath.startsWith(memoryRoot + sep6)) return null;
5322
5601
  try {
5323
- if (lstatSync12(localPath).isSymbolicLink()) return null;
5602
+ if (lstatSync14(localPath).isSymbolicLink()) return null;
5324
5603
  const realLocal = realpathSync5(localPath);
5325
5604
  const realRoot = realpathSync5(memoryRoot);
5326
5605
  if (!realLocal.startsWith(realRoot + sep6)) return null;
@@ -5371,29 +5650,29 @@ function applyMemoryRedact(f, ts, map, scan = scanFile) {
5371
5650
  );
5372
5651
  }
5373
5652
  writeFileSync6(localPath, after, "utf8");
5374
- const dest = join36(repoHome(), "shared", "projects", logical, "memory", ...relPath.split("/"));
5375
- mkdirSync9(dirname9(dest), { recursive: true });
5376
- cpSync7(localPath, dest, { force: true });
5653
+ const dest = join39(repoHome(), "shared", "projects", logical, "memory", ...relPath.split("/"));
5654
+ mkdirSync9(dirname10(dest), { recursive: true });
5655
+ cpSync6(localPath, dest, { force: true });
5377
5656
  return true;
5378
5657
  }
5379
5658
 
5380
5659
  // src/commands.push.recovery.skills.ts
5381
5660
  init_config();
5382
5661
  import {
5383
- cpSync as cpSync8,
5384
- lstatSync as lstatSync14,
5662
+ cpSync as cpSync7,
5663
+ lstatSync as lstatSync16,
5385
5664
  mkdirSync as mkdirSync11,
5386
5665
  readFileSync as readFileSync15,
5387
5666
  realpathSync as realpathSync6,
5388
5667
  statSync as statSync7,
5389
5668
  writeFileSync as writeFileSync7
5390
5669
  } from "node:fs";
5391
- import { dirname as dirname10, join as join38, sep as sep7 } from "node:path";
5670
+ import { dirname as dirname11, join as join41, sep as sep7 } from "node:path";
5392
5671
 
5393
5672
  // src/skills-sync.ts
5394
5673
  init_config();
5395
- import { existsSync as existsSync29, lstatSync as lstatSync13, mkdirSync as mkdirSync10, readdirSync as readdirSync13, rmSync as rmSync13 } from "node:fs";
5396
- import { join as join37 } from "node:path";
5674
+ import { existsSync as existsSync30, lstatSync as lstatSync15, mkdirSync as mkdirSync10, readdirSync as readdirSync14, rmSync as rmSync13 } from "node:fs";
5675
+ import { join as join40 } from "node:path";
5397
5676
 
5398
5677
  // src/skills-sync.tracked.ts
5399
5678
  init_utils();
@@ -5417,7 +5696,7 @@ function isSkillExcluded(name) {
5417
5696
  return isGsdOwned(name) || isDeniedName(ALWAYS_NEVER_SYNC, name);
5418
5697
  }
5419
5698
  function copySkillsPush(src, dst) {
5420
- const srcNames = readdirSync13(src, { encoding: "utf8" });
5699
+ const srcNames = readdirSync14(src, { encoding: "utf8" });
5421
5700
  const blockSet = /* @__PURE__ */ new Set([
5422
5701
  ...srcNames.filter((n) => isGsdOwned(n)),
5423
5702
  ...ALWAYS_NEVER_SYNC
@@ -5428,11 +5707,11 @@ function copySkillsPull(src, dst, isRootPreserved) {
5428
5707
  copyExtrasFilteredPreservingBy(src, dst, isSkillExcluded, isRootPreserved);
5429
5708
  }
5430
5709
  function syncSkillsPull(ts, prePostHeads) {
5431
- const sharedSkills = join37(repoHome(), "shared", "skills");
5432
- if (!existsSync29(sharedSkills)) return;
5433
- const localSkills = join37(claudeHome(), "skills");
5710
+ const sharedSkills = join40(repoHome(), "shared", "skills");
5711
+ if (!existsSync30(sharedSkills)) return;
5712
+ const localSkills = join40(claudeHome(), "skills");
5434
5713
  backupBeforeWrite(localSkills, ts);
5435
- const dstStat = lstatSync13(localSkills, { throwIfNoEntry: false });
5714
+ const dstStat = lstatSync15(localSkills, { throwIfNoEntry: false });
5436
5715
  if (dstStat?.isSymbolicLink() === true) {
5437
5716
  rmSync13(localSkills, { recursive: true, force: true });
5438
5717
  mkdirSync10(localSkills, { recursive: true });
@@ -5441,11 +5720,11 @@ function syncSkillsPull(ts, prePostHeads) {
5441
5720
  copySkillsPull(sharedSkills, localSkills, (name) => !tracked.has(name));
5442
5721
  }
5443
5722
  function syncSkillsPush() {
5444
- const localSkills = join37(claudeHome(), "skills");
5445
- const stat = lstatSync13(localSkills, { throwIfNoEntry: false });
5723
+ const localSkills = join40(claudeHome(), "skills");
5724
+ const stat = lstatSync15(localSkills, { throwIfNoEntry: false });
5446
5725
  if (stat === void 0) return;
5447
5726
  if (stat.isSymbolicLink()) return;
5448
- const sharedSkills = join37(repoHome(), "shared", "skills");
5727
+ const sharedSkills = join40(repoHome(), "shared", "skills");
5449
5728
  copySkillsPush(localSkills, sharedSkills);
5450
5729
  }
5451
5730
 
@@ -5468,12 +5747,12 @@ function resolveSkillLocalPath(name, relPath) {
5468
5747
  if (!SAFE_SKILL_NAME.test(name) || name === "." || name === "..") return null;
5469
5748
  if (isGsdOwned(name)) return null;
5470
5749
  if (!isSafeRelPath(relPath)) return null;
5471
- const skillsRoot = join38(claudeHome(), "skills");
5472
- const skillRoot = join38(skillsRoot, name);
5473
- const localPath = join38(skillRoot, ...relPath.split("/"));
5750
+ const skillsRoot = join41(claudeHome(), "skills");
5751
+ const skillRoot = join41(skillsRoot, name);
5752
+ const localPath = join41(skillRoot, ...relPath.split("/"));
5474
5753
  if (localPath !== skillRoot && !localPath.startsWith(skillRoot + sep7)) return null;
5475
5754
  try {
5476
- if (lstatSync14(localPath).isSymbolicLink()) return null;
5755
+ if (lstatSync16(localPath).isSymbolicLink()) return null;
5477
5756
  const realLocal = realpathSync6(localPath);
5478
5757
  const realRoot = realpathSync6(skillsRoot);
5479
5758
  if (!realLocal.startsWith(realRoot + sep7)) return null;
@@ -5522,9 +5801,9 @@ function applySkillRedact(f, ts, scan = scanFile) {
5522
5801
  );
5523
5802
  }
5524
5803
  writeFileSync7(localPath, after, "utf8");
5525
- const dest = join38(repoHome(), "shared", "skills", name, ...relPath.split("/"));
5526
- mkdirSync11(dirname10(dest), { recursive: true });
5527
- cpSync8(localPath, dest, { force: true });
5804
+ const dest = join41(repoHome(), "shared", "skills", name, ...relPath.split("/"));
5805
+ mkdirSync11(dirname11(dest), { recursive: true });
5806
+ cpSync7(localPath, dest, { force: true });
5528
5807
  return true;
5529
5808
  }
5530
5809
 
@@ -5600,26 +5879,26 @@ function applyDeferredAllows(findings, state, repo) {
5600
5879
  // src/commands.push.recovery.redact.ts
5601
5880
  init_config();
5602
5881
  init_config_sharedDirs_guard();
5603
- import { cpSync as cpSync9, existsSync as existsSync32, mkdirSync as mkdirSync12 } from "node:fs";
5604
- import { dirname as dirname12, join as join41, sep as sep8 } from "node:path";
5882
+ import { cpSync as cpSync8, existsSync as existsSync33, mkdirSync as mkdirSync12 } from "node:fs";
5883
+ import { dirname as dirname13, join as join44, sep as sep8 } from "node:path";
5605
5884
 
5606
5885
  // src/commands.redact.ts
5607
5886
  init_config();
5608
- import { existsSync as existsSync31 } from "node:fs";
5609
- import { dirname as dirname11, join as join40 } from "node:path";
5887
+ import { existsSync as existsSync32 } from "node:fs";
5888
+ import { dirname as dirname12, join as join43 } from "node:path";
5610
5889
 
5611
5890
  // src/commands.redact.subtree.ts
5612
- import { existsSync as existsSync30, lstatSync as lstatSync15, readFileSync as readFileSync16, readdirSync as readdirSync14, statSync as statSync8, writeFileSync as writeFileSync8 } from "node:fs";
5613
- import { join as join39 } from "node:path";
5891
+ import { existsSync as existsSync31, lstatSync as lstatSync17, readFileSync as readFileSync16, readdirSync as readdirSync15, statSync as statSync8, writeFileSync as writeFileSync8 } from "node:fs";
5892
+ import { join as join42 } from "node:path";
5614
5893
  init_utils_fs();
5615
5894
  init_utils();
5616
5895
  function collectFiles(dir, out) {
5617
- if (!existsSync30(dir)) return;
5618
- const st = lstatSync15(dir);
5896
+ if (!existsSync31(dir)) return;
5897
+ const st = lstatSync17(dir);
5619
5898
  if (!st.isDirectory()) return;
5620
- for (const entry of readdirSync14(dir)) {
5621
- const abs = join39(dir, entry);
5622
- const lst = lstatSync15(abs);
5899
+ for (const entry of readdirSync15(dir)) {
5900
+ const abs = join42(dir, entry);
5901
+ const lst = lstatSync17(abs);
5623
5902
  if (lst.isSymbolicLink()) continue;
5624
5903
  if (lst.isDirectory()) {
5625
5904
  collectFiles(abs, out);
@@ -5728,15 +6007,15 @@ init_utils_json();
5728
6007
  init_utils();
5729
6008
  function resolveLiveTranscript(id) {
5730
6009
  try {
5731
- const mapPath = join40(repoHome(), "path-map.json");
5732
- if (!existsSync31(mapPath)) return null;
6010
+ const mapPath = join43(repoHome(), "path-map.json");
6011
+ if (!existsSync32(mapPath)) return null;
5733
6012
  const projects = readJson(mapPath).projects;
5734
6013
  const claude = claudeHome();
5735
6014
  for (const hostMap of Object.values(projects)) {
5736
6015
  const abs = hostMap[HOST];
5737
6016
  if (abs === void 0) continue;
5738
- const live = join40(claude, "projects", encodePath(abs), `${id}.jsonl`);
5739
- if (existsSync31(live)) return live;
6017
+ const live = join43(claude, "projects", encodePath(abs), `${id}.jsonl`);
6018
+ if (existsSync32(live)) return live;
5740
6019
  }
5741
6020
  return null;
5742
6021
  } catch {
@@ -5756,17 +6035,17 @@ function cmdRedact(opts, nowMs = Date.now, scan = scanFile) {
5756
6035
  }
5757
6036
  const repo = repoHome();
5758
6037
  const backup = backupBase();
5759
- if (!existsSync31(repo)) die(`repo not cloned at ${repo}`);
6038
+ if (!existsSync32(repo)) die(`repo not cloned at ${repo}`);
5760
6039
  const handle = acquireLock("redact");
5761
6040
  if (handle === null) process.exit(0);
5762
6041
  try {
5763
6042
  const localPath = resolveLiveTranscript(id);
5764
- if (localPath === null || !existsSync31(localPath)) {
6043
+ if (localPath === null || !existsSync32(localPath)) {
5765
6044
  fail(`could not resolve local transcript for session ${id} on this host`);
5766
6045
  process.exitCode = 1;
5767
6046
  return;
5768
6047
  }
5769
- const sessionDir = join40(dirname11(localPath), id);
6048
+ const sessionDir = join43(dirname12(localPath), id);
5770
6049
  const subtreeFiles = listSubtreeFiles(sessionDir);
5771
6050
  if (isSubtreeActive(localPath, subtreeFiles, nowMs())) {
5772
6051
  log(
@@ -5827,8 +6106,8 @@ function resolveStagedDir(localPath, map, claude, repo) {
5827
6106
  assertSafeLogical(logical);
5828
6107
  const abs = hostMap[HOST];
5829
6108
  if (abs === void 0) continue;
5830
- if (localPath.startsWith(join41(claude, "projects", encodePath(abs)) + sep8)) {
5831
- return join41(repo, "shared", "projects", logical);
6109
+ if (localPath.startsWith(join44(claude, "projects", encodePath(abs)) + sep8)) {
6110
+ return join44(repo, "shared", "projects", logical);
5832
6111
  }
5833
6112
  }
5834
6113
  return null;
@@ -5838,7 +6117,7 @@ function preflightRedactable(f, map, nowMs) {
5838
6117
  if (sid === null) return "a finding has no resolvable session id (not a session transcript)";
5839
6118
  const localPath = resolveLiveTranscript(sid);
5840
6119
  if (localPath === null) return `session ${sid}: local transcript not found`;
5841
- const sessionDir = join41(dirname12(localPath), sid);
6120
+ const sessionDir = join44(dirname13(localPath), sid);
5842
6121
  const subtreeFiles = listSubtreeFiles(sessionDir);
5843
6122
  if (isSubtreeActive(localPath, subtreeFiles, nowMs())) {
5844
6123
  return `session ${sid}: looks active (modified within the last 5 minutes)`;
@@ -5867,7 +6146,7 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
5867
6146
  `could not locate the local transcript for session ${sid}; choose Skip or Drop session.`
5868
6147
  );
5869
6148
  }
5870
- const sessionDir = join41(dirname12(localPath), sid);
6149
+ const sessionDir = join44(dirname13(localPath), sid);
5871
6150
  const subtreeFiles = listSubtreeFiles(sessionDir);
5872
6151
  if (isSubtreeActive(localPath, subtreeFiles, nowMs())) {
5873
6152
  return refuse(
@@ -5900,9 +6179,9 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
5900
6179
  );
5901
6180
  }
5902
6181
  mkdirSync12(stagedProjectDir, { recursive: true });
5903
- cpSync9(localPath, join41(stagedProjectDir, `${sid}.jsonl`), { force: true });
5904
- if (existsSync32(sessionDir)) {
5905
- cpSync9(sessionDir, join41(stagedProjectDir, sid), { force: true, recursive: true });
6182
+ cpSync8(localPath, join44(stagedProjectDir, `${sid}.jsonl`), { force: true });
6183
+ if (existsSync33(sessionDir)) {
6184
+ cpSync8(sessionDir, join44(stagedProjectDir, sid), { force: true, recursive: true });
5906
6185
  }
5907
6186
  return true;
5908
6187
  }
@@ -5910,14 +6189,14 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
5910
6189
  // src/commands.push.recovery.drop.ts
5911
6190
  init_config();
5912
6191
  import { rmSync as rmSync14 } from "node:fs";
5913
- import { join as join42 } from "node:path";
6192
+ import { join as join45 } from "node:path";
5914
6193
  function dropSessionFromStaged(sid, map) {
5915
6194
  const logicals = Object.keys(map.projects);
5916
6195
  if (logicals.length === 0) return false;
5917
6196
  const repo = repoHome();
5918
6197
  for (const logical of logicals) {
5919
- const jsonl = join42(repo, "shared", "projects", logical, `${sid}.jsonl`);
5920
- const dir = join42(repo, "shared", "projects", logical, sid);
6198
+ const jsonl = join45(repo, "shared", "projects", logical, `${sid}.jsonl`);
6199
+ const dir = join45(repo, "shared", "projects", logical, sid);
5921
6200
  rmSync14(jsonl, { force: true });
5922
6201
  rmSync14(dir, { recursive: true, force: true });
5923
6202
  }
@@ -6189,8 +6468,8 @@ function makeDefaultReadLine(repo) {
6189
6468
  const cache = /* @__PURE__ */ new Map();
6190
6469
  return (file, line) => {
6191
6470
  try {
6192
- const repoRoot = resolve3(repo);
6193
- const target = resolve3(repoRoot, file);
6471
+ const repoRoot = resolve4(repo);
6472
+ const target = resolve4(repoRoot, file);
6194
6473
  if (isAbsolute2(file) || target !== repoRoot && !target.startsWith(repoRoot + sep9)) {
6195
6474
  return null;
6196
6475
  }
@@ -6402,7 +6681,7 @@ function applyThenRescan(scanVerdict, repoHome2) {
6402
6681
  return next;
6403
6682
  }
6404
6683
  function allowThenRescan(append, scanVerdict, repoHome2) {
6405
- const ignPath = join43(repoHome2, ".gitleaksignore");
6684
+ const ignPath = join46(repoHome2, ".gitleaksignore");
6406
6685
  let before;
6407
6686
  try {
6408
6687
  before = readFileSync18(ignPath, "utf8");
@@ -6508,7 +6787,7 @@ function writeAnimatedDone(out, label, ms, useTTY) {
6508
6787
  `);
6509
6788
  }
6510
6789
  function resolveWorkerPath(deps = {}) {
6511
- const check = deps.existsSyncFn ?? existsSync33;
6790
+ const check = deps.existsSyncFn ?? existsSync34;
6512
6791
  const base = deps.baseUrl ?? import.meta.url;
6513
6792
  const mjs = fileURLToPath4(new URL("./nomad.worker.mjs", base));
6514
6793
  if (check(mjs)) return mjs;
@@ -6575,8 +6854,8 @@ function withSpinner(label, fn, deps) {
6575
6854
  // src/commands.doctor.gitleaks-version.ts
6576
6855
  init_color();
6577
6856
  import { execFileSync as execFileSync15 } from "node:child_process";
6578
- import { existsSync as existsSync34 } from "node:fs";
6579
- import { join as join44 } from "node:path";
6857
+ import { existsSync as existsSync35 } from "node:fs";
6858
+ import { join as join47 } from "node:path";
6580
6859
  init_config();
6581
6860
  var SEMVER_MAJOR_MINOR = /^(\d+)\.(\d+)\.\d+$/;
6582
6861
  var GITLEAKS_TIMEOUT_MS = 5e3;
@@ -6585,7 +6864,7 @@ function majorMinorOf(value) {
6585
6864
  return m === null ? null : [m[1], m[2]];
6586
6865
  }
6587
6866
  function readGitleaksVersion(run, tomlExists) {
6588
- const tomlPath = join44(repoHome(), ".gitleaks.toml");
6867
+ const tomlPath = join47(repoHome(), ".gitleaks.toml");
6589
6868
  const args = ["version"];
6590
6869
  if (tomlExists(tomlPath)) args.push("--config", tomlPath);
6591
6870
  try {
@@ -6597,7 +6876,7 @@ function readGitleaksVersion(run, tomlExists) {
6597
6876
  return null;
6598
6877
  }
6599
6878
  }
6600
- function reportGitleaksVersionCheck(section2, run = execFileSync15, tomlExists = existsSync34) {
6879
+ function reportGitleaksVersionCheck(section2, run = execFileSync15, tomlExists = existsSync35) {
6601
6880
  const raw = readGitleaksVersion(run, tomlExists);
6602
6881
  if (raw === null) return;
6603
6882
  const local = majorMinorOf(raw);
@@ -6854,8 +7133,8 @@ function gatherDoctorSections(opts) {
6854
7133
  reportLongPathsCheck(host);
6855
7134
  reportCrlfGuardCheck(host);
6856
7135
  const links = section("Shared links");
6857
- const mapPath = join45(repoHome(), "path-map.json");
6858
- const rawMap = existsSync35(mapPath) ? readJsonSafe(mapPath, mapPath, links) : null;
7136
+ const mapPath = join48(repoHome(), "path-map.json");
7137
+ const rawMap = existsSync36(mapPath) ? readJsonSafe(mapPath, mapPath, links) : null;
6859
7138
  const map = rawMap ?? { projects: {} };
6860
7139
  reportSharedLinks(links, map);
6861
7140
  reportDroppedNamesMigration(links);
@@ -6953,8 +7232,8 @@ function parseDoctorArgs(args) {
6953
7232
  // src/commands.drop-session.ts
6954
7233
  init_config();
6955
7234
  import { execFileSync as execFileSync20 } from "node:child_process";
6956
- import { existsSync as existsSync37, readdirSync as readdirSync15, statSync as statSync9 } from "node:fs";
6957
- import { join as join47, relative as relative6 } from "node:path";
7235
+ import { existsSync as existsSync38, readdirSync as readdirSync16, statSync as statSync9 } from "node:fs";
7236
+ import { join as join50, relative as relative7 } from "node:path";
6958
7237
 
6959
7238
  // src/commands.drop-session.git.ts
6960
7239
  import { execFileSync as execFileSync19 } from "node:child_process";
@@ -6997,8 +7276,8 @@ function isInIndex(rel, repo) {
6997
7276
  init_config();
6998
7277
  init_utils();
6999
7278
  init_utils_json();
7000
- import { existsSync as existsSync36 } from "node:fs";
7001
- import { join as join46 } from "node:path";
7279
+ import { existsSync as existsSync37 } from "node:fs";
7280
+ import { join as join49 } from "node:path";
7002
7281
  var SHARED_PROJECT_LOGICAL = /^shared\/projects\/([^/]+)\//;
7003
7282
  function reportScrubHint(id, matches) {
7004
7283
  const live = resolveLiveTranscript2(id, matches);
@@ -7014,8 +7293,8 @@ function reportScrubHint(id, matches) {
7014
7293
  }
7015
7294
  function resolveLiveTranscript2(id, matches) {
7016
7295
  try {
7017
- const mapPath = join46(repoHome(), "path-map.json");
7018
- if (!existsSync36(mapPath)) return null;
7296
+ const mapPath = join49(repoHome(), "path-map.json");
7297
+ if (!existsSync37(mapPath)) return null;
7019
7298
  const projects = readJson(mapPath).projects;
7020
7299
  const claude = claudeHome();
7021
7300
  for (const rel of matches) {
@@ -7023,8 +7302,8 @@ function resolveLiveTranscript2(id, matches) {
7023
7302
  if (logical === void 0) continue;
7024
7303
  const abs = projects[logical]?.[HOST];
7025
7304
  if (abs === void 0) continue;
7026
- const live = join46(claude, "projects", encodePath(abs), `${id}.jsonl`);
7027
- if (existsSync36(live)) return live;
7305
+ const live = join49(claude, "projects", encodePath(abs), `${id}.jsonl`);
7306
+ if (existsSync37(live)) return live;
7028
7307
  }
7029
7308
  return null;
7030
7309
  } catch {
@@ -7040,12 +7319,12 @@ function cmdDropSession(id) {
7040
7319
  process.exit(1);
7041
7320
  }
7042
7321
  const repo = repoHome();
7043
- if (!existsSync37(repo)) die(`repo not cloned at ${repo}`);
7322
+ if (!existsSync38(repo)) die(`repo not cloned at ${repo}`);
7044
7323
  const handle = acquireLock("drop-session");
7045
7324
  if (handle === null) process.exit(0);
7046
7325
  try {
7047
- const repoProjects = join47(repo, "shared", "projects");
7048
- if (!existsSync37(repoProjects)) {
7326
+ const repoProjects = join50(repo, "shared", "projects");
7327
+ if (!existsSync38(repoProjects)) {
7049
7328
  throw new NomadFatal(`no staged session matches ${id}`);
7050
7329
  }
7051
7330
  const matches = collectMatches(repoProjects, id, repo);
@@ -7067,14 +7346,14 @@ function cmdDropSession(id) {
7067
7346
  }
7068
7347
  function collectMatches(repoProjects, id, repo) {
7069
7348
  const matches = [];
7070
- for (const logical of readdirSync15(repoProjects)) {
7071
- const candidate = join47(repoProjects, logical, `${id}.jsonl`);
7072
- if (existsSync37(candidate)) {
7073
- matches.push(relative6(repo, candidate));
7074
- }
7075
- const dir = join47(repoProjects, logical, id);
7076
- if (existsSync37(dir) && statSync9(dir).isDirectory()) {
7077
- const dirRel = relative6(repo, dir);
7349
+ for (const logical of readdirSync16(repoProjects)) {
7350
+ const candidate = join50(repoProjects, logical, `${id}.jsonl`);
7351
+ if (existsSync38(candidate)) {
7352
+ matches.push(relative7(repo, candidate));
7353
+ }
7354
+ const dir = join50(repoProjects, logical, id);
7355
+ if (existsSync38(dir) && statSync9(dir).isDirectory()) {
7356
+ const dirRel = relative7(repo, dir);
7078
7357
  const staged = expandStagedDir(dirRel, repo);
7079
7358
  if (staged.length > 0) matches.push(...staged);
7080
7359
  else matches.push(dirRel);
@@ -7109,8 +7388,8 @@ function unstageOne(rel, repo) {
7109
7388
 
7110
7389
  // src/commands.pull.ts
7111
7390
  init_autostash_guard();
7112
- import { existsSync as existsSync46, mkdirSync as mkdirSync14 } from "node:fs";
7113
- import { join as join58 } from "node:path";
7391
+ import { existsSync as existsSync47, mkdirSync as mkdirSync14 } from "node:fs";
7392
+ import { join as join61 } from "node:path";
7114
7393
 
7115
7394
  // src/commands.push.sections.ts
7116
7395
  init_color();
@@ -7218,18 +7497,18 @@ init_config();
7218
7497
 
7219
7498
  // src/extras-sync.ts
7220
7499
  init_config();
7221
- import { existsSync as existsSync39, statSync as statSync10 } from "node:fs";
7222
- import { join as join50 } from "node:path";
7500
+ import { existsSync as existsSync40, statSync as statSync10 } from "node:fs";
7501
+ import { join as join53 } from "node:path";
7223
7502
  init_utils();
7224
7503
  init_utils_json();
7225
7504
 
7226
7505
  // src/extras-sync.remap.ts
7227
7506
  init_config();
7228
- import { existsSync as existsSync38, mkdirSync as mkdirSync13, readdirSync as readdirSync16, readFileSync as readFileSync19, realpathSync as realpathSync7, rmSync as rmSync16 } from "node:fs";
7229
- import { dirname as dirname13, join as join49, sep as sep11 } from "node:path";
7507
+ import { existsSync as existsSync39, mkdirSync as mkdirSync13, readdirSync as readdirSync17, readFileSync as readFileSync19, realpathSync as realpathSync7, rmSync as rmSync16 } from "node:fs";
7508
+ import { dirname as dirname14, join as join52, sep as sep11 } from "node:path";
7230
7509
 
7231
7510
  // src/extras-sync.planning-diff.ts
7232
- import { join as join48, normalize as normalize2, sep as sep10 } from "node:path";
7511
+ import { join as join51, normalize as normalize2, sep as sep10 } from "node:path";
7233
7512
  init_utils();
7234
7513
  function processRecord(fields, i, changed, deleted) {
7235
7514
  const status = fields[i];
@@ -7281,7 +7560,7 @@ function planningDeleteTargets(opts) {
7281
7560
  const { deleted } = parsePlanningDiff(raw);
7282
7561
  const logicalPrefix = "shared/extras/" + logical + "/";
7283
7562
  const prefix = logicalPrefix + ".planning/";
7284
- const planningRoot = join48(localRoot, ".planning");
7563
+ const planningRoot = join51(localRoot, ".planning");
7285
7564
  const planningRootBoundary = planningRoot + sep10;
7286
7565
  const targets = [];
7287
7566
  for (const repoPath of deleted) {
@@ -7289,7 +7568,7 @@ function planningDeleteTargets(opts) {
7289
7568
  continue;
7290
7569
  }
7291
7570
  const remainder = repoPath.slice(logicalPrefix.length);
7292
- const candidate = join48(localRoot, remainder);
7571
+ const candidate = join51(localRoot, remainder);
7293
7572
  const resolved = normalize2(candidate);
7294
7573
  if (!resolved.startsWith(planningRootBoundary)) {
7295
7574
  throw new NomadFatal(
@@ -7310,7 +7589,7 @@ function runExtrasOp(v, dryRun, paths, backup, copy) {
7310
7589
  const would = [];
7311
7590
  for (const t of eachExtrasTarget(v, counts)) {
7312
7591
  const { src, dst } = paths(t);
7313
- if (!existsSync38(src)) continue;
7592
+ if (!existsSync39(src)) continue;
7314
7593
  const item2 = `${t.logical}/${t.dirname}`;
7315
7594
  if (dryRun) {
7316
7595
  would.push(item2);
@@ -7323,15 +7602,15 @@ function runExtrasOp(v, dryRun, paths, backup, copy) {
7323
7602
  return { ...counts, done, would };
7324
7603
  }
7325
7604
  function pruneEmptyAncestors(target, planningRoot) {
7326
- let dir = dirname13(target);
7605
+ let dir = dirname14(target);
7327
7606
  while (dir !== planningRoot && dir.startsWith(planningRoot + sep11)) {
7328
7607
  try {
7329
- if (readdirSync16(dir).length > 0) break;
7608
+ if (readdirSync17(dir).length > 0) break;
7330
7609
  rmSync16(dir, { recursive: true, force: true });
7331
7610
  } catch {
7332
7611
  break;
7333
7612
  }
7334
- dir = dirname13(dir);
7613
+ dir = dirname14(dir);
7335
7614
  }
7336
7615
  }
7337
7616
  function tryRealpath(dir) {
@@ -7345,8 +7624,8 @@ function isInsidePlanningRoot(parentReal, rootReal) {
7345
7624
  return parentReal === rootReal || parentReal.startsWith(rootReal + sep11);
7346
7625
  }
7347
7626
  function deletePlanningTarget(target, planningRoot, repoCounterpart) {
7348
- if (existsSync38(repoCounterpart)) return;
7349
- const parentReal = tryRealpath(dirname13(target));
7627
+ if (existsSync39(repoCounterpart)) return;
7628
+ const parentReal = tryRealpath(dirname14(target));
7350
7629
  if (parentReal === void 0) return;
7351
7630
  const rootReal = tryRealpath(planningRoot);
7352
7631
  if (rootReal === void 0) return;
@@ -7355,7 +7634,7 @@ function deletePlanningTarget(target, planningRoot, repoCounterpart) {
7355
7634
  pruneEmptyAncestors(target, planningRoot);
7356
7635
  }
7357
7636
  function localDivergesFromPreDelete(target, pre, repoRel, repo) {
7358
- if (!existsSync38(target)) return false;
7637
+ if (!existsSync39(target)) return false;
7359
7638
  try {
7360
7639
  const preBlob = gitCaptureBuffer(["show", `${pre}:${repoRel}`], repo);
7361
7640
  return !readFileSync19(target).equals(preBlob);
@@ -7400,7 +7679,7 @@ function keptDeletePreview(v, prePostHeads, repo) {
7400
7679
  return kept;
7401
7680
  }
7402
7681
  function propagatePlanningDeletes(v, ts, prePostHeads, repo) {
7403
- const repoExtras = join49(repo, "shared", "extras");
7682
+ const repoExtras = join52(repo, "shared", "extras");
7404
7683
  for (const t of eachExtrasTarget(v, { unmapped: 0, skipped: 0 })) {
7405
7684
  if (t.dirname !== ".planning") continue;
7406
7685
  let raw;
@@ -7415,14 +7694,14 @@ function propagatePlanningDeletes(v, ts, prePostHeads, repo) {
7415
7694
  }
7416
7695
  const pairs = deletePairsFor(t, raw);
7417
7696
  if (pairs.length === 0) continue;
7418
- backupExtrasWrite(join49(t.localRoot, t.dirname), ts, t.localRoot);
7419
- const planningRoot = join49(t.localRoot, ".planning");
7697
+ backupExtrasWrite(join52(t.localRoot, t.dirname), ts, t.localRoot);
7698
+ const planningRoot = join52(t.localRoot, ".planning");
7420
7699
  for (const { target, relToLocal, repoRel } of pairs) {
7421
7700
  if (localDivergesFromPreDelete(target, prePostHeads.pre, repoRel, repo)) {
7422
7701
  warn(keptDeleteWarnLine(t.logical, relToLocal));
7423
7702
  continue;
7424
7703
  }
7425
- deletePlanningTarget(target, planningRoot, join49(repoExtras, t.logical, relToLocal));
7704
+ deletePlanningTarget(target, planningRoot, join52(repoExtras, t.logical, relToLocal));
7426
7705
  }
7427
7706
  }
7428
7707
  }
@@ -7431,14 +7710,14 @@ function remapExtrasPush(ts, opts = {}) {
7431
7710
  const v = loadValidatedExtras({ missingMsg: "no path-map.json; skipping extras push" });
7432
7711
  if (v === null) return { unmapped: 0, skipped: 0, pushed: [], wouldPush: [] };
7433
7712
  const repo = repoHome();
7434
- const repoExtras = join49(repo, "shared", "extras");
7713
+ const repoExtras = join52(repo, "shared", "extras");
7435
7714
  if (!dryRun) mkdirSync13(repoExtras, { recursive: true });
7436
7715
  const { unmapped, skipped, done, would } = runExtrasOp(
7437
7716
  v,
7438
7717
  dryRun,
7439
- ({ localRoot, logical, dirname: dirname14 }) => ({
7440
- src: join49(localRoot, dirname14),
7441
- dst: join49(repoExtras, logical, dirname14)
7718
+ ({ localRoot, logical, dirname: dirname15 }) => ({
7719
+ src: join52(localRoot, dirname15),
7720
+ dst: join52(repoExtras, logical, dirname15)
7442
7721
  }),
7443
7722
  (dst) => backupRepoWrite(dst, ts, repo),
7444
7723
  // Push copy routing per extra type:
@@ -7451,7 +7730,7 @@ function remapExtrasPush(ts, opts = {}) {
7451
7730
  // config.never-sync.ts)
7452
7731
  // remains the hard security boundary.
7453
7732
  // All others: copyExtrasFiltered with per-extra denylist.
7454
- (src, dst, dirname14) => dirname14 === ".planning" ? copyExtrasOverlayFiltered(src, dst, extrasDenySet(dirname14)) : copyExtrasFiltered(src, dst, extrasDenySet(dirname14))
7733
+ (src, dst, dirname15) => dirname15 === ".planning" ? copyExtrasOverlayFiltered(src, dst, extrasDenySet(dirname15)) : copyExtrasFiltered(src, dst, extrasDenySet(dirname15))
7455
7734
  );
7456
7735
  return { unmapped, skipped, pushed: done, wouldPush: would };
7457
7736
  }
@@ -7467,9 +7746,9 @@ function remapExtrasPull(ts, opts = {}) {
7467
7746
  const { unmapped, skipped, done, would } = runExtrasOp(
7468
7747
  v,
7469
7748
  dryRun,
7470
- ({ localRoot, logical, dirname: dirname14 }) => ({
7471
- src: join49(repo, "shared", "extras", logical, dirname14),
7472
- dst: join49(localRoot, dirname14)
7749
+ ({ localRoot, logical, dirname: dirname15 }) => ({
7750
+ src: join52(repo, "shared", "extras", logical, dirname15),
7751
+ dst: join52(localRoot, dirname15)
7473
7752
  }),
7474
7753
  // Snapshot the host-side dst BEFORE the copy step touches it. Anchor on
7475
7754
  // localRoot so the backup tree mirrors the project layout.
@@ -7487,12 +7766,12 @@ function remapExtrasPull(ts, opts = {}) {
7487
7766
  // copyExtrasFileSkipDiverged keeps a locally-edited file rather than
7488
7767
  // clobbering it, so the divergence WARN's keep-local promise holds for
7489
7768
  // every extra, not just `.planning`.
7490
- (src, dst, dirname14) => {
7491
- if (dirname14 === ".claude")
7492
- return copyExtrasFilteredPreserving(src, dst, extrasDenySet(dirname14));
7493
- if (dirname14 === ".planning") {
7769
+ (src, dst, dirname15) => {
7770
+ if (dirname15 === ".claude")
7771
+ return copyExtrasFilteredPreserving(src, dst, extrasDenySet(dirname15));
7772
+ if (dirname15 === ".planning") {
7494
7773
  const divergedSet = new Set(listDivergingModified(dst, src));
7495
- return copyExtrasOverlaySkipDiverged(src, dst, extrasDenySet(dirname14), divergedSet);
7774
+ return copyExtrasOverlaySkipDiverged(src, dst, extrasDenySet(dirname15), divergedSet);
7496
7775
  }
7497
7776
  return copyExtrasFileSkipDiverged(src, dst);
7498
7777
  }
@@ -7516,20 +7795,20 @@ function divergenceCheckExtras(ts, prePostHeads) {
7516
7795
  const v = loadValidatedExtras({});
7517
7796
  if (v === null) return 0;
7518
7797
  const counts = { unmapped: 0, skipped: 0 };
7519
- const backupRoot = join50(backupBase(), ts, "extras");
7798
+ const backupRoot = join53(backupBase(), ts, "extras");
7520
7799
  const repo = repoHome();
7521
7800
  let divergedCount = 0;
7522
- for (const { logical, localRoot, dirname: dirname14 } of eachExtrasTarget(v, counts)) {
7523
- const local = join50(localRoot, dirname14);
7524
- const repoEntry = join50(repo, "shared", "extras", logical, dirname14);
7525
- if (!existsSync39(local) || !existsSync39(repoEntry)) continue;
7801
+ for (const { logical, localRoot, dirname: dirname15 } of eachExtrasTarget(v, counts)) {
7802
+ const local = join53(localRoot, dirname15);
7803
+ const repoEntry = join53(repo, "shared", "extras", logical, dirname15);
7804
+ if (!existsSync40(local) || !existsSync40(repoEntry)) continue;
7526
7805
  const modified = listDivergingModified(local, repoEntry);
7527
7806
  if (modified.length === 0) continue;
7528
7807
  divergedCount += modified.length;
7529
- const projectBackupRoot = join50(backupRoot, encodePath(localRoot));
7808
+ const projectBackupRoot = join53(backupRoot, encodePath(localRoot));
7530
7809
  warn(
7531
7810
  divergenceWarnLine({
7532
- dirname: dirname14,
7811
+ dirname: dirname15,
7533
7812
  logical,
7534
7813
  isDir: statSync10(local).isDirectory(),
7535
7814
  count: modified.length,
@@ -7548,8 +7827,8 @@ function divergenceCheckExtras(ts, prePostHeads) {
7548
7827
 
7549
7828
  // src/links.baseline.ts
7550
7829
  init_config();
7551
- import { lstatSync as lstatSync16, readdirSync as readdirSync17 } from "node:fs";
7552
- import { join as join51, relative as relative7, sep as sep12 } from "node:path";
7830
+ import { lstatSync as lstatSync18, readdirSync as readdirSync18 } from "node:fs";
7831
+ import { join as join54, relative as relative8, sep as sep12 } from "node:path";
7553
7832
  init_utils();
7554
7833
  var SHARED_BASELINE_KIND = "shared-links-baseline/1";
7555
7834
  var SHARED_BASELINE_CONFIG_HASH = "not-applicable";
@@ -7560,13 +7839,13 @@ function readSharedBaseline() {
7560
7839
  return parsed;
7561
7840
  }
7562
7841
  function baselineKey(claude, abs) {
7563
- return relative7(claude, abs).split(sep12).join("/");
7842
+ return relative8(claude, abs).split(sep12).join("/");
7564
7843
  }
7565
7844
  function addLocalPath(abs, claude, scan) {
7566
7845
  const key = baselineKey(claude, abs);
7567
7846
  let st;
7568
7847
  try {
7569
- st = lstatSync16(abs, { throwIfNoEntry: false });
7848
+ st = lstatSync18(abs, { throwIfNoEntry: false });
7570
7849
  } catch {
7571
7850
  scan.declined.push(key);
7572
7851
  return;
@@ -7585,13 +7864,13 @@ function addLocalPath(abs, claude, scan) {
7585
7864
  function collectSharedFiles(dir, claude, scan) {
7586
7865
  let entries;
7587
7866
  try {
7588
- entries = readdirSync17(dir);
7867
+ entries = readdirSync18(dir);
7589
7868
  } catch {
7590
7869
  scan.declined.push(baselineKey(claude, dir));
7591
7870
  return;
7592
7871
  }
7593
7872
  for (const entry of entries) {
7594
- const abs = join51(dir, entry);
7873
+ const abs = join54(dir, entry);
7595
7874
  if (isDeniedName(ALWAYS_NEVER_SYNC, entry)) {
7596
7875
  scan.declined.push(baselineKey(claude, abs));
7597
7876
  continue;
@@ -7607,7 +7886,7 @@ function enumerateLocalSharedScan(map, opts = {}) {
7607
7886
  scan.declined.push(name);
7608
7887
  continue;
7609
7888
  }
7610
- addLocalPath(join51(claude, name), claude, scan);
7889
+ addLocalPath(join54(claude, name), claude, scan);
7611
7890
  }
7612
7891
  return scan;
7613
7892
  }
@@ -7619,7 +7898,7 @@ function buildSharedBaseline(map, opts = {}) {
7619
7898
  const files = {};
7620
7899
  for (const [key, st] of Object.entries(enumerateLocalSharedFiles(map, opts))) {
7621
7900
  try {
7622
- files[key] = { size: st.size, mtime: st.mtime, hash: hashFile(join51(claude, key)) };
7901
+ files[key] = { size: st.size, mtime: st.mtime, hash: hashFile(join54(claude, key)) };
7623
7902
  } catch {
7624
7903
  }
7625
7904
  }
@@ -7636,8 +7915,8 @@ function writeSharedBaseline(map, opts = {}) {
7636
7915
 
7637
7916
  // src/commands.pull.win32.ts
7638
7917
  init_color();
7639
- import { existsSync as existsSync44 } from "node:fs";
7640
- import { join as join56 } from "node:path";
7918
+ import { existsSync as existsSync45 } from "node:fs";
7919
+ import { join as join59 } from "node:path";
7641
7920
 
7642
7921
  // src/commands.pull.recovery.git.ts
7643
7922
  init_utils();
@@ -7700,8 +7979,8 @@ function gitProbe(args, repo) {
7700
7979
 
7701
7980
  // src/links.deletions.ts
7702
7981
  init_config();
7703
- import { existsSync as existsSync40, lstatSync as lstatSync17, rmSync as rmSync17 } from "node:fs";
7704
- import { join as join52, relative as relative8, resolve as resolve4, sep as sep13 } from "node:path";
7982
+ import { existsSync as existsSync41, lstatSync as lstatSync19, rmSync as rmSync17 } from "node:fs";
7983
+ import { join as join55, relative as relative9, resolve as resolve5, sep as sep13 } from "node:path";
7705
7984
  init_utils();
7706
7985
  init_utils_fs();
7707
7986
  function isUnknown(declined, key) {
@@ -7711,15 +7990,15 @@ function deletionFor(key, names, claude, sharedRoot) {
7711
7990
  const segments = key.split("/");
7712
7991
  const name = segments[0];
7713
7992
  if (!names.has(name)) return null;
7714
- if (!existsSync40(join52(claude, name))) return null;
7993
+ if (!existsSync41(join55(claude, name))) return null;
7715
7994
  if (segments.some((segment) => isDeniedName(ALWAYS_NEVER_SYNC, segment))) return null;
7716
- const repoPath = resolve4(sharedRoot, key);
7717
- const rel = relative8(join52(sharedRoot, name), repoPath);
7995
+ const repoPath = resolve5(sharedRoot, key);
7996
+ const rel = relative9(join55(sharedRoot, name), repoPath);
7718
7997
  if (rel === "" || rel === ".." || rel.startsWith(`..${sep13}`)) return null;
7719
7998
  if (segments.some((segment) => segment === "" || segment === "." || segment === ".."))
7720
7999
  return null;
7721
- if (!existsSync40(repoPath)) return null;
7722
- return { kind: "deletion", name, localPath: join52(claude, key), repoPath };
8000
+ if (!existsSync41(repoPath)) return null;
8001
+ return { kind: "deletion", name, localPath: join55(claude, key), repoPath };
7723
8002
  }
7724
8003
  function planSharedLinkDeletions(map, opts = {}) {
7725
8004
  if (process.platform !== "win32") return [];
@@ -7727,7 +8006,7 @@ function planSharedLinkDeletions(map, opts = {}) {
7727
8006
  const baseline = readSharedBaseline();
7728
8007
  if (baseline === null) return [];
7729
8008
  const claude = claudeHome();
7730
- const sharedRoot = join52(repoHome(), "shared");
8009
+ const sharedRoot = join55(repoHome(), "shared");
7731
8010
  const linkNames = opts.linkNames ?? allSharedLinks(map);
7732
8011
  const scan = enumerateLocalSharedScan(map, { linkNames });
7733
8012
  const present = new Set(Object.keys(scan.files).map((key) => key.toLowerCase()));
@@ -7748,7 +8027,7 @@ function applySharedLinkDeletions(map, ts, opts = {}) {
7748
8027
  const removed = [];
7749
8028
  for (const entry of planSharedLinkDeletions(map, opts)) {
7750
8029
  try {
7751
- if (!lstatSync17(entry.repoPath).isFile()) continue;
8030
+ if (!lstatSync19(entry.repoPath).isFile()) continue;
7752
8031
  backupRepoWrite(entry.repoPath, ts, repo);
7753
8032
  rmSync17(entry.repoPath, { force: true });
7754
8033
  removed.push(entry);
@@ -7761,8 +8040,8 @@ function applySharedLinkDeletions(map, ts, opts = {}) {
7761
8040
 
7762
8041
  // src/links.mirror.ts
7763
8042
  init_config();
7764
- import { existsSync as existsSync41, lstatSync as lstatSync18, rmSync as rmSync18 } from "node:fs";
7765
- import { join as join53 } from "node:path";
8043
+ import { existsSync as existsSync42, lstatSync as lstatSync20, rmSync as rmSync18 } from "node:fs";
8044
+ import { join as join56 } from "node:path";
7766
8045
  init_utils();
7767
8046
  init_utils_fs();
7768
8047
  function emitMirror(onPreview, name, localPath, repoPath) {
@@ -7776,16 +8055,22 @@ function emitMirrorWet(onPreview, name, localPath, repoPath) {
7776
8055
  if (onPreview) onPreview({ kind: "mirror", name, localPath, repoPath });
7777
8056
  }
7778
8057
  function notShared(policy, target) {
7779
- return !policy.adoptNew && !existsSync41(target);
8058
+ return !policy.adoptNew && !existsSync42(target);
7780
8059
  }
7781
8060
  function mirrorOneSharedName(name, claude, repo, policy, opts) {
7782
- const localPath = join53(claude, name);
7783
- const target = join53(repo, "shared", name);
8061
+ const localPath = join56(claude, name);
8062
+ const target = join56(repo, "shared", name);
7784
8063
  let stat;
7785
8064
  try {
7786
- stat = lstatSync18(localPath, { throwIfNoEntry: false });
8065
+ stat = lstatSync20(localPath, { throwIfNoEntry: false });
7787
8066
  } catch (err) {
7788
8067
  if (notShared(policy, target)) return;
8068
+ if (opts.dryRun === true) {
8069
+ warn(
8070
+ `${name} could not be read (${err.message}), so nothing was captured for it and nothing was written. A pull that captures shared edits would skip it too. Check its permissions, or whether another program has it open`
8071
+ );
8072
+ return;
8073
+ }
7789
8074
  warn(
7790
8075
  `${name} could not be read (${err.message}), so it was left out of shared/ this run. Check its permissions, or whether another program has it open`
7791
8076
  );
@@ -7800,9 +8085,9 @@ function mirrorOneSharedName(name, claude, repo, policy, opts) {
7800
8085
  }
7801
8086
  if (policy.backupTs !== void 0) backupRepoWrite(target, policy.backupTs, repo);
7802
8087
  if (policy.overlay && stat.isDirectory()) {
7803
- copyExtrasOverlayFiltered(localPath, target, NEVER_SYNC);
8088
+ copyExtrasOverlayFiltered(localPath, target, ALWAYS_NEVER_SYNC);
7804
8089
  } else {
7805
- copyExtrasFiltered(localPath, target, NEVER_SYNC);
8090
+ copyExtrasFiltered(localPath, target, ALWAYS_NEVER_SYNC);
7806
8091
  }
7807
8092
  emitMirrorWet(opts.onPreview, name, localPath, target);
7808
8093
  }
@@ -7817,20 +8102,20 @@ function mirrorSharedNames(map, policy, opts = {}) {
7817
8102
  }
7818
8103
  }
7819
8104
  function syncSharedLinksPush(map, opts = {}) {
7820
- mirrorSharedNames(map, { adoptNew: true, overlay: false }, opts);
8105
+ mirrorSharedNames(map, { adoptNew: false, overlay: false }, opts);
7821
8106
  }
7822
8107
  function stageLocalSharedEdits(map, ts, opts = {}) {
7823
8108
  mirrorSharedNames(map, { adoptNew: false, overlay: true, backupTs: ts }, opts);
7824
8109
  }
7825
8110
  function presentAt(abs) {
7826
8111
  try {
7827
- return lstatSync18(abs, { throwIfNoEntry: false }) !== void 0;
8112
+ return lstatSync20(abs, { throwIfNoEntry: false }) !== void 0;
7828
8113
  } catch {
7829
8114
  return true;
7830
8115
  }
7831
8116
  }
7832
8117
  function removeUntrackedDenied(repo, path, segment, ts) {
7833
- const abs = join53(repo, path);
8118
+ const abs = join56(repo, path);
7834
8119
  const denied = `the path segment "${segment}" is on the never-sync list`;
7835
8120
  if (!presentAt(abs)) {
7836
8121
  warn(
@@ -7838,7 +8123,7 @@ function removeUntrackedDenied(repo, path, segment, ts) {
7838
8123
  );
7839
8124
  return;
7840
8125
  }
7841
- const snapshotted = existsSync41(abs);
8126
+ const snapshotted = existsSync42(abs);
7842
8127
  try {
7843
8128
  backupRepoWrite(abs, ts, repo);
7844
8129
  } catch (err) {
@@ -7874,7 +8159,7 @@ function reportTrackedDenied(repo, path, segment) {
7874
8159
  );
7875
8160
  return;
7876
8161
  }
7877
- if (!existsSync41(join53(repo, path))) return;
8162
+ if (!existsSync42(join56(repo, path))) return;
7878
8163
  warn(
7879
8164
  `${path} is tracked and has changes against HEAD: ${denied}. Nothing was changed. Run "git checkout HEAD -- ${path}" to put the committed content back, or move the file outside shared/ if you want to keep it. Neither of those takes the committed copy out of the repo: "git rm -- ${path}" and a commit does that going forward, and if it holds a real secret, rotate it and rewrite history, because nomad only changes your local worktree and index and cannot scrub what a previous push already sent to the remote`
7880
8165
  );
@@ -7892,8 +8177,8 @@ function revertDeniedMirrorPaths(repo, status, ts) {
7892
8177
 
7893
8178
  // src/preview.ts
7894
8179
  init_config();
7895
- import { existsSync as existsSync43 } from "node:fs";
7896
- import { join as join55 } from "node:path";
8180
+ import { existsSync as existsSync44 } from "node:fs";
8181
+ import { join as join58 } from "node:path";
7897
8182
 
7898
8183
  // node_modules/diff/libesm/diff/base.js
7899
8184
  var Diff = class {
@@ -8168,16 +8453,16 @@ function diffLinesToUnified(oldStr, newStr) {
8168
8453
 
8169
8454
  // src/preview.skills.ts
8170
8455
  init_config();
8171
- import { existsSync as existsSync42, readdirSync as readdirSync18 } from "node:fs";
8172
- import { join as join54 } from "node:path";
8456
+ import { existsSync as existsSync43, readdirSync as readdirSync19 } from "node:fs";
8457
+ import { join as join57 } from "node:path";
8173
8458
  function buildSkillsPreviewSection() {
8174
8459
  const s = section("Skills");
8175
- const sharedSkills = join54(repoHome(), "shared", "skills");
8176
- if (!existsSync42(sharedSkills)) return s;
8177
- const sharedNames = readdirSync18(sharedSkills, { encoding: "utf8" }).filter((name) => !isSkillExcluded(name)).sort((a, b) => a.localeCompare(b, "en"));
8460
+ const sharedSkills = join57(repoHome(), "shared", "skills");
8461
+ if (!existsSync43(sharedSkills)) return s;
8462
+ const sharedNames = readdirSync19(sharedSkills, { encoding: "utf8" }).filter((name) => !isSkillExcluded(name)).sort((a, b) => a.localeCompare(b, "en"));
8178
8463
  for (const name of sharedNames) addItem(s, name);
8179
- const localSkills = join54(claudeHome(), "skills");
8180
- const localOnly = existsSync42(localSkills) ? readdirSync18(localSkills, { encoding: "utf8" }).filter(
8464
+ const localSkills = join57(claudeHome(), "skills");
8465
+ const localOnly = existsSync43(localSkills) ? readdirSync19(localSkills, { encoding: "utf8" }).filter(
8181
8466
  (name) => !isSkillExcluded(name) && !sharedNames.includes(name)
8182
8467
  ).length : 0;
8183
8468
  if (localOnly > 0) {
@@ -8199,7 +8484,7 @@ function diffJsonStrings(currentJsonText, newJsonText) {
8199
8484
  return lines.join("\n");
8200
8485
  }
8201
8486
  function readJsonOrNull(path) {
8202
- if (!existsSync43(path)) return null;
8487
+ if (!existsSync44(path)) return null;
8203
8488
  try {
8204
8489
  return readJson(path);
8205
8490
  } catch {
@@ -8213,12 +8498,12 @@ function previewSettings(basePath, hostPath, settingsPath) {
8213
8498
  }
8214
8499
  const notes = [];
8215
8500
  const hostOverrides = readJsonOrNull(hostPath);
8216
- if (hostOverrides === null && existsSync43(hostPath)) {
8501
+ if (hostOverrides === null && existsSync44(hostPath)) {
8217
8502
  notes.push(`malformed hosts/${HOST}.json; ignoring overrides`);
8218
8503
  }
8219
8504
  const merged = stripGsdHookEntries(deepMerge(base, hostOverrides ?? {}));
8220
8505
  const current = readJsonOrNull(settingsPath);
8221
- if (current === null && existsSync43(settingsPath)) {
8506
+ if (current === null && existsSync44(settingsPath)) {
8222
8507
  return { diff: "", notes: [...notes, "malformed; skipping diff"] };
8223
8508
  }
8224
8509
  const strippedCurrent = stripGsdHookEntries(current ?? {});
@@ -8285,9 +8570,9 @@ function computePreview(ts, map, verb = "pull", plans) {
8285
8570
  onPreview: (e) => addItem(links, formatLinkRow(e))
8286
8571
  });
8287
8572
  const settingsResult = previewSettings(
8288
- join55(repo, "shared", "settings.base.json"),
8289
- join55(repo, "hosts", `${HOST}.json`),
8290
- join55(claude, "settings.json")
8573
+ join58(repo, "shared", "settings.base.json"),
8574
+ join58(repo, "hosts", `${HOST}.json`),
8575
+ join58(claude, "settings.json")
8291
8576
  );
8292
8577
  const settingsSection = buildSettingsSectionForPreview(settingsResult);
8293
8578
  const sessions = section("Sessions");
@@ -8303,7 +8588,7 @@ function computePreview(ts, map, verb = "pull", plans) {
8303
8588
  const extras = section("Extras");
8304
8589
  let extrasSkipped = 0;
8305
8590
  let extrasUnmapped = 0;
8306
- if (existsSync43(join55(repo, "path-map.json")) && existsSync43(join55(repo, "shared", "extras"))) {
8591
+ if (existsSync44(join58(repo, "path-map.json")) && existsSync44(join58(repo, "shared", "extras"))) {
8307
8592
  const extrasResult = remapExtrasPull(ts, { dryRun: true });
8308
8593
  for (const entry of extrasResult.wouldPull) {
8309
8594
  addItem(extras, entry);
@@ -8324,7 +8609,7 @@ function computePreview(ts, map, verb = "pull", plans) {
8324
8609
  init_utils();
8325
8610
  init_utils_json();
8326
8611
  function readMapForMirror(mapPath) {
8327
- if (!existsSync44(mapPath)) return { projects: {} };
8612
+ if (!existsSync45(mapPath)) return { projects: {} };
8328
8613
  try {
8329
8614
  return readPathMap(mapPath);
8330
8615
  } catch {
@@ -8371,7 +8656,7 @@ function reconcileSharedLinksBeforePull(repo, ts) {
8371
8656
  let namesDerived = false;
8372
8657
  let derivedSharedDirs;
8373
8658
  try {
8374
- const map = readMapForMirror(join56(repo, "path-map.json"));
8659
+ const map = readMapForMirror(join59(repo, "path-map.json"));
8375
8660
  if (map !== null) {
8376
8661
  linkNames = allSharedLinks(map);
8377
8662
  namesDerived = true;
@@ -8410,7 +8695,7 @@ function buildMirrorSection(events, discard) {
8410
8695
  function describeSkippedMirrorDiscard(repo, ts) {
8411
8696
  if (process.platform !== "win32") return null;
8412
8697
  try {
8413
- const map = readMapForMirror(join56(repo, "path-map.json"));
8698
+ const map = readMapForMirror(join59(repo, "path-map.json"));
8414
8699
  const linkNames = map !== null ? allSharedLinks(map, { quiet: true }) : void 0;
8415
8700
  const captures = [];
8416
8701
  stageLocalSharedEdits(map, ts, {
@@ -8419,13 +8704,13 @@ function describeSkippedMirrorDiscard(repo, ts) {
8419
8704
  linkNames
8420
8705
  });
8421
8706
  if (captures.length === 0) return null;
8422
- return { count: captures.length, backupPath: join56(backupBase(), ts) };
8707
+ return { count: captures.length, backupPath: join59(backupBase(), ts) };
8423
8708
  } catch {
8424
8709
  return null;
8425
8710
  }
8426
8711
  }
8427
8712
  function planSharedReconcileBeforePull(repo, ts) {
8428
- const map = readMapForMirror(join56(repo, "path-map.json"));
8713
+ const map = readMapForMirror(join59(repo, "path-map.json"));
8429
8714
  const captures = [];
8430
8715
  const linkNames = process.platform === "win32" && map !== null ? allSharedLinks(map) : void 0;
8431
8716
  stageLocalSharedEdits(map, ts, { dryRun: true, onPreview: (e) => captures.push(e), linkNames });
@@ -8440,8 +8725,8 @@ function planSharedReconcileBeforePull(repo, ts) {
8440
8725
  // src/commands.pull.collision.ts
8441
8726
  init_config();
8442
8727
  init_exit_codes();
8443
- import { existsSync as existsSync45, lstatSync as lstatSync19, readFileSync as readFileSync20, rmSync as rmSync19 } from "node:fs";
8444
- import { join as join57 } from "node:path";
8728
+ import { existsSync as existsSync46, lstatSync as lstatSync21, readFileSync as readFileSync20, rmSync as rmSync19 } from "node:fs";
8729
+ import { join as join60 } from "node:path";
8445
8730
  init_utils();
8446
8731
  var SHARED_PREFIX = /^shared\//;
8447
8732
  var EXTENSION = /(\.[^.]+)?$/;
@@ -8497,24 +8782,24 @@ function isContainedMirrorPath(rel) {
8497
8782
  }
8498
8783
  function matchesLocalOriginal(repo, rel) {
8499
8784
  try {
8500
- const local = join57(claudeHome(), rel.replace(SHARED_PREFIX, ""));
8501
- return readFileSync20(join57(repo, rel)).equals(readFileSync20(local));
8785
+ const local = join60(claudeHome(), rel.replace(SHARED_PREFIX, ""));
8786
+ return readFileSync20(join60(repo, rel)).equals(readFileSync20(local));
8502
8787
  } catch {
8503
8788
  return false;
8504
8789
  }
8505
8790
  }
8506
8791
  function removeMirroredCopies(repo, repoRelPaths) {
8507
8792
  for (const rel of repoRelPaths.filter(isContainedMirrorPath)) {
8508
- const abs = join57(repo, rel);
8793
+ const abs = join60(repo, rel);
8509
8794
  try {
8510
- if (lstatSync19(abs, { throwIfNoEntry: false })?.isFile() !== true) continue;
8795
+ if (lstatSync21(abs, { throwIfNoEntry: false })?.isFile() !== true) continue;
8511
8796
  if (!matchesLocalOriginal(repo, rel)) continue;
8512
8797
  rmSync19(abs, { force: true });
8513
8798
  } catch (err) {
8514
8799
  warn(`could not remove nomad's copy at ${abs}: ${err.message}`);
8515
8800
  }
8516
8801
  }
8517
- return repoRelPaths.every((rel) => !existsSync45(join57(repo, rel)));
8802
+ return repoRelPaths.every((rel) => !existsSync46(join60(repo, rel)));
8518
8803
  }
8519
8804
  function pullWithCollisionRunbook(repo, mirrored) {
8520
8805
  try {
@@ -8596,7 +8881,27 @@ function recoverForceRemote(mode, repo) {
8596
8881
  const { synced } = classifyTouched(allTouched);
8597
8882
  if (synced.length > 0) {
8598
8883
  die(
8599
- "force-remote refused: stranded or dirty tracked changes touch synced config.\nAt-risk paths:\n" + synced.map((p) => ` ${p}`).join("\n") + "\nCopy or cherry-pick those changes out before retrying."
8884
+ "force-remote refused: the sync repo's synced config differs from origin/main.\nDiffering paths:\n" + // Quoted for display only: `-z` hands back raw names, and a path
8885
+ // carrying a newline or a terminal control sequence would otherwise
8886
+ // split or rewrite the recovery steps printed below it. The
8887
+ // classification above uses the raw value.
8888
+ synced.map((p) => ` ${JSON.stringify(p)}`).join("\n") + `
8889
+ The comparison runs both ways, so a path can be listed because origin/main is ahead of
8890
+ you, with nothing of yours at risk on it.
8891
+
8892
+ Manual recovery. Step 1 is not optional: this refusal happens BEFORE recovery parks your
8893
+ local commits, so nothing has been saved for you yet and step 3 discards whatever is not
8894
+ on another ref.
8895
+ 1. git branch nomad-rescue HEAD (keeps every local commit)
8896
+ 2. copy any uncommitted work above OUT of the repo (it exists nowhere else)
8897
+ 3. git reset --hard origin/main (this is what clears the refusal)
8898
+ 4. nomad pull
8899
+
8900
+ Copying or moving the files alone does not clear it: git still reports a moved file as a
8901
+ deletion, and a committed change is still ahead of origin/main. The in-progress rebase or
8902
+ merge has ALREADY been aborted, so re-running 'nomad pull --force-remote' now behaves as
8903
+ an ordinary pull.
8904
+ (see FAQ: "Every pull fails with unmerged files")`
8600
8905
  );
8601
8906
  }
8602
8907
  const branchName = freshStrandedBranch(repo);
@@ -8704,7 +9009,7 @@ function runPullCore(opts = {}) {
8704
9009
  const ts = freshBackupTs(backup);
8705
9010
  const recovered = handleWedge(repo, forceRemote);
8706
9011
  if (!dryRun) {
8707
- const backupRoot = join58(backup, ts);
9012
+ const backupRoot = join61(backup, ts);
8708
9013
  try {
8709
9014
  mkdirSync14(backupRoot, { recursive: true });
8710
9015
  } catch (err) {
@@ -8727,8 +9032,8 @@ function runPullCore(opts = {}) {
8727
9032
  pullWithCollisionRunbook(repo, mirrored);
8728
9033
  });
8729
9034
  assertNoAutostashConflict(repo, "nomad pull");
8730
- const mapPath = join58(repo, "path-map.json");
8731
- const map = existsSync46(mapPath) ? readPathMap(mapPath) : { projects: {} };
9035
+ const mapPath = join61(repo, "path-map.json");
9036
+ const map = existsSync47(mapPath) ? readPathMap(mapPath) : { projects: {} };
8732
9037
  const divergedKeptLocal = divergenceCheckExtras(ts, dryRun ? prePostHeads : void 0);
8733
9038
  if (dryRun) {
8734
9039
  computePreview(ts, map, "pull", plansAgainst(sharedPlans, map));
@@ -8755,8 +9060,8 @@ function runPullCore(opts = {}) {
8755
9060
  }
8756
9061
  function cmdPull(opts = {}) {
8757
9062
  const repo = repoHome();
8758
- if (!existsSync46(repo)) die(`repo not cloned at ${repo}`);
8759
- if (!existsSync46(join58(repo, "shared", "settings.base.json"))) {
9063
+ if (!existsSync47(repo)) die(`repo not cloned at ${repo}`);
9064
+ if (!existsSync47(join61(repo, "shared", "settings.base.json"))) {
8760
9065
  die("repo not initialized; run 'nomad init' to scaffold");
8761
9066
  }
8762
9067
  const handle = acquireLock("pull");
@@ -8779,13 +9084,13 @@ function cmdPull(opts = {}) {
8779
9084
 
8780
9085
  // src/commands.push.ts
8781
9086
  init_config();
8782
- import { existsSync as existsSync50 } from "node:fs";
8783
- import { join as join63 } from "node:path";
9087
+ import { existsSync as existsSync51 } from "node:fs";
9088
+ import { join as join66 } from "node:path";
8784
9089
 
8785
9090
  // src/commands.push.selection.ts
8786
9091
  init_config();
8787
- import { existsSync as existsSync47, statSync as statSync11 } from "node:fs";
8788
- import { join as join59 } from "node:path";
9092
+ import { existsSync as existsSync48, statSync as statSync11 } from "node:fs";
9093
+ import { join as join62 } from "node:path";
8789
9094
  init_utils_json();
8790
9095
  function buildCurrentMap(map) {
8791
9096
  const current = {};
@@ -8794,8 +9099,8 @@ function buildCurrentMap(map) {
8794
9099
  for (const [, hostMap] of Object.entries(map.projects)) {
8795
9100
  const localPath = hostMap[HOST];
8796
9101
  if (!localPath) continue;
8797
- const localDir = join59(claude, "projects", encodePath(localPath));
8798
- if (!existsSync47(localDir)) continue;
9102
+ const localDir = join62(claude, "projects", encodePath(localPath));
9103
+ if (!existsSync48(localDir)) continue;
8799
9104
  for (const f of enumerateSourceFiles(localDir)) {
8800
9105
  const st = statSync11(f);
8801
9106
  current[f] = { size: st.size, mtime: st.mtimeMs };
@@ -8826,7 +9131,7 @@ function computePushSelection(map, old, scannerVersion, configHash, fullScan) {
8826
9131
  };
8827
9132
  }
8828
9133
  function loadSelectionForPush(mapPath, old, scannerVersion, configHash, fullScan) {
8829
- const map = existsSync47(mapPath) ? readPathMap(mapPath) : null;
9134
+ const map = existsSync48(mapPath) ? readPathMap(mapPath) : null;
8830
9135
  const { selection, newManifest } = computePushSelection(
8831
9136
  map,
8832
9137
  old,
@@ -8919,14 +9224,14 @@ function enforceAllowList(statusPorcelain, map) {
8919
9224
 
8920
9225
  // src/commands.push.settings.ts
8921
9226
  init_config();
8922
- import { existsSync as existsSync48 } from "node:fs";
8923
- import { join as join60 } from "node:path";
9227
+ import { existsSync as existsSync49 } from "node:fs";
9228
+ import { join as join63 } from "node:path";
8924
9229
  init_utils();
8925
9230
  init_utils_fs();
8926
9231
  init_utils_json();
8927
9232
  function stripGsdHooksFromBase(repo, backup) {
8928
- const basePath = join60(repo, "shared", "settings.base.json");
8929
- if (!existsSync48(basePath)) return;
9233
+ const basePath = join63(repo, "shared", "settings.base.json");
9234
+ if (!existsSync49(basePath)) return;
8930
9235
  let base;
8931
9236
  try {
8932
9237
  base = readJson(basePath);
@@ -8940,14 +9245,14 @@ function stripGsdHooksFromBase(repo, backup) {
8940
9245
  writeJsonAtomic(basePath, stripped);
8941
9246
  }
8942
9247
  function reportSettingsAheadDrift(repo) {
8943
- const basePath = join60(repo, "shared", "settings.base.json");
8944
- if (!existsSync48(basePath)) return;
8945
- const settingsPath = join60(claudeHome(), "settings.json");
8946
- if (!existsSync48(settingsPath)) return;
9248
+ const basePath = join63(repo, "shared", "settings.base.json");
9249
+ if (!existsSync49(basePath)) return;
9250
+ const settingsPath = join63(claudeHome(), "settings.json");
9251
+ if (!existsSync49(settingsPath)) return;
8947
9252
  try {
8948
9253
  const base = readJson(basePath);
8949
- const hostPath = join60(repo, "hosts", `${HOST}.json`);
8950
- const overrides = existsSync48(hostPath) ? readJson(hostPath) : {};
9254
+ const hostPath = join63(repo, "hosts", `${HOST}.json`);
9255
+ const overrides = existsSync49(hostPath) ? readJson(hostPath) : {};
8951
9256
  const merged = deepMerge(base, overrides);
8952
9257
  const settings = readJson(settingsPath);
8953
9258
  const { ahead } = classifySettingsDrift(merged, settings);
@@ -8964,12 +9269,12 @@ function reportSettingsAheadDrift(repo) {
8964
9269
  // src/commands.push.guards.ts
8965
9270
  init_push_checks();
8966
9271
  init_utils();
8967
- import { join as join61, relative as relative9 } from "node:path";
9272
+ import { join as join64, relative as relative10 } from "node:path";
8968
9273
  function guardGitlinks(repo) {
8969
- const gitlinks = findGitlinks(join61(repo, "shared"));
9274
+ const gitlinks = findGitlinks(join64(repo, "shared"));
8970
9275
  if (gitlinks.length === 0) return;
8971
9276
  for (const p of gitlinks) {
8972
- const rel = relative9(repo, p).replaceAll("\\", "/");
9277
+ const rel = relative10(repo, p).replaceAll("\\", "/");
8973
9278
  fail(`gitlink: ${rel} would push as submodule (run: rm -rf ${rel} or remove the nested repo)`);
8974
9279
  }
8975
9280
  const noun = gitlinks.length === 1 ? "entry" : "entries";
@@ -9076,9 +9381,9 @@ init_color();
9076
9381
  init_config();
9077
9382
  init_config_sharedDirs_guard();
9078
9383
  import { randomBytes as randomBytes4 } from "node:crypto";
9079
- import { copyFileSync, existsSync as existsSync49, lstatSync as lstatSync20, mkdirSync as mkdirSync15, readdirSync as readdirSync19, rmSync as rmSync20 } from "node:fs";
9384
+ import { copyFileSync, existsSync as existsSync50, lstatSync as lstatSync22, mkdirSync as mkdirSync15, readdirSync as readdirSync20, rmSync as rmSync20 } from "node:fs";
9080
9385
  import { homedir as homedir7 } from "node:os";
9081
- import { join as join62, relative as relative10, sep as sep14 } from "node:path";
9386
+ import { join as join65, relative as relative11, sep as sep14 } from "node:path";
9082
9387
  init_push_leak_verdict();
9083
9388
  init_push_gitleaks();
9084
9389
  init_utils_fs();
@@ -9090,7 +9395,7 @@ function stageSessionDir(localDir, dstDir, changed) {
9090
9395
  const matching = [...changed].filter((p) => p.startsWith(prefix));
9091
9396
  if (matching.length === 0) return false;
9092
9397
  for (const src of matching) {
9093
- copyFileAtomic(src, join62(dstDir, relative10(localDir, src)));
9398
+ copyFileAtomic(src, join65(dstDir, relative11(localDir, src)));
9094
9399
  }
9095
9400
  return true;
9096
9401
  }
@@ -9106,14 +9411,14 @@ function stageSessions(tmpRoot, map, changed) {
9106
9411
  if (!p || p === "TBD") continue;
9107
9412
  reverse.set(encodePath(p), logical);
9108
9413
  }
9109
- const localProjects = join62(claudeHome(), "projects");
9110
- if (!existsSync49(localProjects)) return 0;
9414
+ const localProjects = join65(claudeHome(), "projects");
9415
+ if (!existsSync50(localProjects)) return 0;
9111
9416
  let staged = 0;
9112
- for (const dir of readdirSync19(localProjects)) {
9417
+ for (const dir of readdirSync20(localProjects)) {
9113
9418
  const logical = reverse.get(dir);
9114
9419
  if (!logical) continue;
9115
- const localDir = join62(localProjects, dir);
9116
- const dstDir = join62(tmpRoot, "shared", "projects", logical);
9420
+ const localDir = join65(localProjects, dir);
9421
+ const dstDir = join65(tmpRoot, "shared", "projects", logical);
9117
9422
  if (stageSessionDir(localDir, dstDir, changed)) staged++;
9118
9423
  }
9119
9424
  return staged;
@@ -9127,11 +9432,11 @@ function stageExtras(tmpRoot, map) {
9127
9432
  assertSafeLogical(logical);
9128
9433
  const localRoot = map.projects[logical]?.[HOST];
9129
9434
  if (!localRoot || localRoot === "TBD") continue;
9130
- for (const dirname14 of dirnames) {
9131
- if (!whitelist.includes(dirname14)) continue;
9132
- const src = join62(localRoot, dirname14);
9133
- if (!existsSync49(src)) continue;
9134
- const dst = join62(tmpRoot, "shared", "extras", logical, dirname14);
9435
+ for (const dirname15 of dirnames) {
9436
+ if (!whitelist.includes(dirname15)) continue;
9437
+ const src = join65(localRoot, dirname15);
9438
+ if (!existsSync50(src)) continue;
9439
+ const dst = join65(tmpRoot, "shared", "extras", logical, dirname15);
9135
9440
  copyExtras(src, dst);
9136
9441
  staged++;
9137
9442
  }
@@ -9139,19 +9444,19 @@ function stageExtras(tmpRoot, map) {
9139
9444
  return staged;
9140
9445
  }
9141
9446
  function stageSkills(tmpRoot) {
9142
- const localSkills = join62(claudeHome(), "skills");
9143
- const stat = lstatSync20(localSkills, { throwIfNoEntry: false });
9447
+ const localSkills = join65(claudeHome(), "skills");
9448
+ const stat = lstatSync22(localSkills, { throwIfNoEntry: false });
9144
9449
  if (stat === void 0 || stat.isSymbolicLink()) return 0;
9145
- const names = readdirSync19(localSkills, { encoding: "utf8" }).filter((n) => !isSkillExcluded(n));
9450
+ const names = readdirSync20(localSkills, { encoding: "utf8" }).filter((n) => !isSkillExcluded(n));
9146
9451
  if (names.length === 0) return 0;
9147
- copySkillsPush(localSkills, join62(tmpRoot, "shared", "skills"));
9452
+ copySkillsPush(localSkills, join65(tmpRoot, "shared", "skills"));
9148
9453
  return names.length;
9149
9454
  }
9150
9455
  function previewPushLeaks(map, opts = {}) {
9151
- const cacheDir = join62(homedir7(), ".cache", "claude-nomad");
9456
+ const cacheDir = join65(homedir7(), ".cache", "claude-nomad");
9152
9457
  mkdirSync15(cacheDir, { recursive: true });
9153
9458
  const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes4(4).toString("hex")}`;
9154
- const tmpRoot = join62(cacheDir, `push-preview-tree-${stamp}`);
9459
+ const tmpRoot = join65(cacheDir, `push-preview-tree-${stamp}`);
9155
9460
  try {
9156
9461
  const sessionCount = stageSessions(tmpRoot, map, opts.selection?.changed);
9157
9462
  const extrasCount = stageExtras(tmpRoot, map);
@@ -9159,9 +9464,9 @@ function previewPushLeaks(map, opts = {}) {
9159
9464
  if (sessionCount + extrasCount + skillCount === 0) {
9160
9465
  return { leak: false, verdictRow: NOTHING_TO_SCAN_ROW, recovery: null, findings: [] };
9161
9466
  }
9162
- const ignoreFile = join62(repoHome(), ".gitleaksignore");
9163
- if (existsSync49(ignoreFile)) {
9164
- copyFileSync(ignoreFile, join62(tmpRoot, ".gitleaksignore"));
9467
+ const ignoreFile = join65(repoHome(), ".gitleaksignore");
9468
+ if (existsSync50(ignoreFile)) {
9469
+ copyFileSync(ignoreFile, join65(tmpRoot, ".gitleaksignore"));
9165
9470
  }
9166
9471
  let findings;
9167
9472
  try {
@@ -9289,7 +9594,7 @@ async function runPushCore(opts = {}) {
9289
9594
  const scannerVersion = probeGitleaks();
9290
9595
  const configHash = computeConfigHash();
9291
9596
  const old = readManifest(manifestPath());
9292
- const mapPath = join63(repo, "path-map.json");
9597
+ const mapPath = join66(repo, "path-map.json");
9293
9598
  const { map, selection, newManifest } = loadSelectionForPush(
9294
9599
  mapPath,
9295
9600
  old,
@@ -9342,7 +9647,7 @@ async function cmdPush(opts = {}) {
9342
9647
  opts.allowRule
9343
9648
  );
9344
9649
  const repo = repoHome();
9345
- if (!existsSync50(repo)) die(`repo not cloned at ${repo}`);
9650
+ if (!existsSync51(repo)) die(`repo not cloned at ${repo}`);
9346
9651
  const handle = acquireLock("push");
9347
9652
  if (handle === null) process.exit(0);
9348
9653
  try {
@@ -9360,8 +9665,8 @@ async function cmdPush(opts = {}) {
9360
9665
  }
9361
9666
 
9362
9667
  // src/commands.sync.ts
9363
- import { existsSync as existsSync51 } from "node:fs";
9364
- import { join as join64 } from "node:path";
9668
+ import { existsSync as existsSync52 } from "node:fs";
9669
+ import { join as join67 } from "node:path";
9365
9670
  init_config();
9366
9671
  init_color();
9367
9672
  init_utils();
@@ -9502,8 +9807,8 @@ async function cmdSync(opts = {}) {
9502
9807
  const dryRun = opts.dryRun === true;
9503
9808
  const verbose = opts.verbose === true;
9504
9809
  const repo = repoHome();
9505
- if (!existsSync51(repo)) die(`repo not cloned at ${repo}`);
9506
- if (!existsSync51(join64(repo, "shared", "settings.base.json"))) {
9810
+ if (!existsSync52(repo)) die(`repo not cloned at ${repo}`);
9811
+ if (!existsSync52(join67(repo, "shared", "settings.base.json"))) {
9507
9812
  die("repo not initialized; run 'nomad init' to scaffold");
9508
9813
  }
9509
9814
  const handle = acquireLock("sync");
@@ -9579,8 +9884,8 @@ init_config();
9579
9884
  // src/crash-report.write.ts
9580
9885
  init_config();
9581
9886
  import { randomBytes as randomBytes5 } from "node:crypto";
9582
- import { mkdirSync as mkdirSync16, readdirSync as readdirSync20, statSync as statSync12, unlinkSync as unlinkSync2, writeFileSync as writeFileSync11 } from "node:fs";
9583
- import { join as join66 } from "node:path";
9887
+ import { mkdirSync as mkdirSync16, readdirSync as readdirSync21, statSync as statSync12, unlinkSync as unlinkSync2, writeFileSync as writeFileSync11 } from "node:fs";
9888
+ import { join as join69 } from "node:path";
9584
9889
 
9585
9890
  // src/crash-report.ts
9586
9891
  var CRASH_MAX_STACK_LINES = 50;
@@ -9665,15 +9970,15 @@ function buildCrashReport(input) {
9665
9970
  // src/crash-report.redact.ts
9666
9971
  import { mkdtempSync as mkdtempSync3, rmSync as rmSync21, writeFileSync as writeFileSync10 } from "node:fs";
9667
9972
  import { tmpdir as tmpdir2 } from "node:os";
9668
- import { join as join65 } from "node:path";
9973
+ import { join as join68 } from "node:path";
9669
9974
  init_push_gitleaks_scan();
9670
9975
  var CRASH_SCAN_TIMEOUT_MS = 3e3;
9671
9976
  var SCAN_UNAVAILABLE_ADVISORY = "\n\n[gitleaks value-based scan unavailable; only structural redaction applied. Review before sharing this file publicly.]\n";
9672
9977
  function redactWithGitleaks(text, scan = scanFile) {
9673
9978
  let dir;
9674
9979
  try {
9675
- dir = mkdtempSync3(join65(tmpdir2(), "nomad-crash-scan-"));
9676
- const tmp = join65(dir, "crash.txt");
9980
+ dir = mkdtempSync3(join68(tmpdir2(), "nomad-crash-scan-"));
9981
+ const tmp = join68(dir, "crash.txt");
9677
9982
  writeFileSync10(tmp, text, { mode: 384 });
9678
9983
  const findings = scan(tmp, false, CRASH_SCAN_TIMEOUT_MS);
9679
9984
  if (findings === null) return text + SCAN_UNAVAILABLE_ADVISORY;
@@ -9696,9 +10001,9 @@ init_utils();
9696
10001
  var CRASH_RETENTION_KEEP = 20;
9697
10002
  function listCrashFiles(dir = crashDir()) {
9698
10003
  try {
9699
- return readdirSync20(dir).flatMap((name) => {
10004
+ return readdirSync21(dir).flatMap((name) => {
9700
10005
  try {
9701
- return [{ name, mtimeMs: statSync12(join66(dir, name)).mtimeMs }];
10006
+ return [{ name, mtimeMs: statSync12(join69(dir, name)).mtimeMs }];
9702
10007
  } catch {
9703
10008
  return [];
9704
10009
  }
@@ -9712,7 +10017,7 @@ function pruneCrashDir(dir, keep = CRASH_RETENTION_KEEP) {
9712
10017
  const targets = prunableByCount(files, keep);
9713
10018
  for (const name of targets) {
9714
10019
  try {
9715
- unlinkSync2(join66(dir, name));
10020
+ unlinkSync2(join69(dir, name));
9716
10021
  } catch {
9717
10022
  }
9718
10023
  }
@@ -9720,7 +10025,7 @@ function pruneCrashDir(dir, keep = CRASH_RETENTION_KEEP) {
9720
10025
  function writeCrashReport(text, dir = crashDir()) {
9721
10026
  mkdirSync16(dir, { recursive: true, mode: 448 });
9722
10027
  const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes5(4).toString("hex")}`;
9723
- const path = join66(dir, `crash-${stamp}.txt`);
10028
+ const path = join69(dir, `crash-${stamp}.txt`);
9724
10029
  writeFileSync11(path, text, { mode: 384 });
9725
10030
  pruneCrashDir(dir);
9726
10031
  return path;
@@ -9752,18 +10057,18 @@ function handleCrash(err, argv, opts) {
9752
10057
 
9753
10058
  // src/diff.ts
9754
10059
  init_config();
9755
- import { existsSync as existsSync52 } from "node:fs";
9756
- import { join as join67 } from "node:path";
10060
+ import { existsSync as existsSync53 } from "node:fs";
10061
+ import { join as join70 } from "node:path";
9757
10062
  init_utils();
9758
10063
  init_utils_fs();
9759
10064
  init_utils_json();
9760
10065
  function cmdDiff() {
9761
10066
  try {
9762
10067
  const repo = repoHome();
9763
- if (!existsSync52(repo)) die(`repo not cloned at ${repo}`);
10068
+ if (!existsSync53(repo)) die(`repo not cloned at ${repo}`);
9764
10069
  const ts = freshBackupTs(backupBase());
9765
- const mapPath = join67(repo, "path-map.json");
9766
- const map = existsSync52(mapPath) ? readPathMap(mapPath) : { projects: {} };
10070
+ const mapPath = join70(repo, "path-map.json");
10071
+ const map = existsSync53(mapPath) ? readPathMap(mapPath) : { projects: {} };
9767
10072
  divergenceCheckExtras(ts);
9768
10073
  computePreview(ts, map, "diff");
9769
10074
  } catch (err) {
@@ -9778,8 +10083,8 @@ function cmdDiff() {
9778
10083
 
9779
10084
  // src/init.ts
9780
10085
  init_config();
9781
- import { existsSync as existsSync54, mkdirSync as mkdirSync17, writeFileSync as writeFileSync12 } from "node:fs";
9782
- import { join as join69 } from "node:path";
10086
+ import { existsSync as existsSync55, mkdirSync as mkdirSync17, writeFileSync as writeFileSync12 } from "node:fs";
10087
+ import { join as join72 } from "node:path";
9783
10088
 
9784
10089
  // src/init.gh-onboard.ts
9785
10090
  init_config();
@@ -9861,33 +10166,33 @@ init_config();
9861
10166
  init_utils();
9862
10167
  init_utils_fs();
9863
10168
  init_utils_json();
9864
- import { copyFileSync as copyFileSync2, cpSync as cpSync10, existsSync as existsSync53, rmSync as rmSync22, statSync as statSync13 } from "node:fs";
9865
- import { join as join68 } from "node:path";
10169
+ import { copyFileSync as copyFileSync2, cpSync as cpSync9, existsSync as existsSync54, rmSync as rmSync22, statSync as statSync13 } from "node:fs";
10170
+ import { join as join71 } from "node:path";
9866
10171
  function snapshotIntoShared(map) {
9867
10172
  const repo = repoHome();
9868
10173
  const claude = claudeHome();
9869
10174
  for (const name of allSharedLinks(map)) {
9870
- const src = join68(claude, name);
9871
- if (!existsSync53(src)) continue;
9872
- const dst = join68(repo, "shared", name);
10175
+ const src = join71(claude, name);
10176
+ if (!existsSync54(src)) continue;
10177
+ const dst = join71(repo, "shared", name);
9873
10178
  if (statSync13(src).isDirectory()) {
9874
- const gk = join68(dst, ".gitkeep");
9875
- if (existsSync53(gk)) rmSync22(gk);
9876
- cpSync10(src, dst, { recursive: true, force: false, errorOnExist: true });
10179
+ const gk = join71(dst, ".gitkeep");
10180
+ if (existsSync54(gk)) rmSync22(gk);
10181
+ cpSync9(src, dst, { recursive: true, force: false, errorOnExist: true });
9877
10182
  } else {
9878
10183
  copyFileSync2(src, dst);
9879
10184
  }
9880
10185
  log(`snapshotted shared/${name} from ${src}`);
9881
10186
  }
9882
- const userSettings = join68(claude, "settings.json");
9883
- if (existsSync53(userSettings)) {
10187
+ const userSettings = join71(claude, "settings.json");
10188
+ if (existsSync54(userSettings)) {
9884
10189
  let parsed;
9885
10190
  try {
9886
10191
  parsed = readJson(userSettings);
9887
10192
  } catch (err) {
9888
10193
  return die(`malformed ${userSettings}: ${err.message}`);
9889
10194
  }
9890
- const hostFile = join68(repo, "hosts", `${HOST}.json`);
10195
+ const hostFile = join71(repo, "hosts", `${HOST}.json`);
9891
10196
  writeJsonAtomic(hostFile, parsed);
9892
10197
  log(`snapshotted hosts/${HOST}.json from ${userSettings}`);
9893
10198
  }
@@ -9901,14 +10206,14 @@ var GITATTRIBUTES = "# nomad: sync content is byte-managed, disable all line-end
9901
10206
  var SHARED_KEEP_DIRS = ["agents", "skills", "commands", "rules", "hooks"];
9902
10207
  function preflightConflict(repoHome2) {
9903
10208
  const candidates = [
9904
- join69(repoHome2, "shared", "settings.base.json"),
9905
- join69(repoHome2, "shared", "CLAUDE.md"),
9906
- join69(repoHome2, "path-map.json"),
9907
- join69(repoHome2, "hosts"),
9908
- join69(repoHome2, "shared")
10209
+ join72(repoHome2, "shared", "settings.base.json"),
10210
+ join72(repoHome2, "shared", "CLAUDE.md"),
10211
+ join72(repoHome2, "path-map.json"),
10212
+ join72(repoHome2, "hosts"),
10213
+ join72(repoHome2, "shared")
9909
10214
  ];
9910
10215
  for (const c of candidates) {
9911
- if (existsSync54(c)) return c;
10216
+ if (existsSync55(c)) return c;
9912
10217
  }
9913
10218
  return null;
9914
10219
  }
@@ -9926,27 +10231,27 @@ function cmdInit(opts = {}) {
9926
10231
  die(`already initialized; refusing to clobber ${conflict}`);
9927
10232
  }
9928
10233
  ensureOriginRepo(opts.repoName ?? DEFAULT_REPO_NAME, opts.run);
9929
- mkdirSync17(join69(repo, "shared"), { recursive: true });
9930
- mkdirSync17(join69(repo, "hosts"), { recursive: true });
10234
+ mkdirSync17(join72(repo, "shared"), { recursive: true });
10235
+ mkdirSync17(join72(repo, "hosts"), { recursive: true });
9931
10236
  for (const name of SHARED_KEEP_DIRS) {
9932
- mkdirSync17(join69(repo, "shared", name), { recursive: true });
10237
+ mkdirSync17(join72(repo, "shared", name), { recursive: true });
9933
10238
  }
9934
- const userClaudeMd = join69(claude, "CLAUDE.md");
9935
- if (!snapshot || !existsSync54(userClaudeMd)) {
9936
- writeFileSync12(join69(repo, "shared", "CLAUDE.md"), SHARED_CLAUDE_MD);
10239
+ const userClaudeMd = join72(claude, "CLAUDE.md");
10240
+ if (!snapshot || !existsSync55(userClaudeMd)) {
10241
+ writeFileSync12(join72(repo, "shared", "CLAUDE.md"), SHARED_CLAUDE_MD);
9937
10242
  item("created shared/CLAUDE.md");
9938
10243
  }
9939
10244
  for (const name of SHARED_KEEP_DIRS) {
9940
- writeFileSync12(join69(repo, "shared", name, ".gitkeep"), "");
10245
+ writeFileSync12(join72(repo, "shared", name, ".gitkeep"), "");
9941
10246
  item(`created shared/${name}/.gitkeep`);
9942
10247
  }
9943
- writeFileSync12(join69(repo, "hosts", ".gitkeep"), "");
10248
+ writeFileSync12(join72(repo, "hosts", ".gitkeep"), "");
9944
10249
  item("created hosts/.gitkeep");
9945
- writeJsonAtomic(join69(repo, "shared", "settings.base.json"), {});
10250
+ writeJsonAtomic(join72(repo, "shared", "settings.base.json"), {});
9946
10251
  item("created shared/settings.base.json");
9947
- writeJsonAtomic(join69(repo, "path-map.json"), { projects: {} });
10252
+ writeJsonAtomic(join72(repo, "path-map.json"), { projects: {} });
9948
10253
  item("created path-map.json");
9949
- writeFileSync12(join69(repo, ".gitattributes"), GITATTRIBUTES);
10254
+ writeFileSync12(join72(repo, ".gitattributes"), GITATTRIBUTES);
9950
10255
  item("created .gitattributes");
9951
10256
  if (snapshot) {
9952
10257
  snapshotIntoShared({ projects: {} });
@@ -10012,21 +10317,21 @@ function maybeDisableRepoActions(repoHome2, run) {
10012
10317
  // src/init.prompt.ts
10013
10318
  init_config();
10014
10319
  init_utils();
10015
- import { existsSync as existsSync55, readdirSync as readdirSync21, statSync as statSync14 } from "node:fs";
10016
- import { join as join70 } from "node:path";
10320
+ import { existsSync as existsSync56, readdirSync as readdirSync22, statSync as statSync14 } from "node:fs";
10321
+ import { join as join73 } from "node:path";
10017
10322
  import { createInterface as createInterface3 } from "node:readline/promises";
10018
10323
  function nonEmptyExists(path) {
10019
- if (!existsSync55(path)) return false;
10324
+ if (!existsSync56(path)) return false;
10020
10325
  try {
10021
- if (statSync14(path).isDirectory()) return readdirSync21(path).length > 0;
10326
+ if (statSync14(path).isDirectory()) return readdirSync22(path).length > 0;
10022
10327
  return true;
10023
10328
  } catch {
10024
10329
  return false;
10025
10330
  }
10026
10331
  }
10027
10332
  function hasExistingClaudeConfig(claudeHome2) {
10028
- if (existsSync55(join70(claudeHome2, "settings.json"))) return true;
10029
- return SHARED_LINKS.some((name) => nonEmptyExists(join70(claudeHome2, name)));
10333
+ if (existsSync56(join73(claudeHome2, "settings.json"))) return true;
10334
+ return SHARED_LINKS.some((name) => nonEmptyExists(join73(claudeHome2, name)));
10030
10335
  }
10031
10336
  async function confirmSnapshotDefault(claudeHome2) {
10032
10337
  if (process.stdin.isTTY !== true || process.stdout.isTTY !== true) {
@@ -10333,7 +10638,7 @@ function parseSyncArgs(argv) {
10333
10638
  // package.json
10334
10639
  var package_default = {
10335
10640
  name: "claude-nomad",
10336
- version: "0.65.0",
10641
+ version: "0.66.0",
10337
10642
  type: "module",
10338
10643
  description: "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
10339
10644
  keywords: [
@@ -10578,15 +10883,15 @@ var DEFAULT_HELP = [
10578
10883
  init_config();
10579
10884
  init_utils();
10580
10885
  init_utils_json();
10581
- import { existsSync as existsSync56, readFileSync as readFileSync21, readdirSync as readdirSync22 } from "node:fs";
10582
- import { join as join71 } from "node:path";
10886
+ import { existsSync as existsSync57, readFileSync as readFileSync21, readdirSync as readdirSync23 } from "node:fs";
10887
+ import { join as join74 } from "node:path";
10583
10888
  function resumeCmd(sessionId) {
10584
10889
  if (!/^[A-Za-z0-9_-]+$/.test(sessionId) || sessionId.length > 128) {
10585
10890
  fail(`invalid session id: ${sessionId}`);
10586
10891
  process.exit(1);
10587
10892
  }
10588
- const projectsRoot = join71(claudeHome(), "projects");
10589
- if (!existsSync56(projectsRoot)) {
10893
+ const projectsRoot = join74(claudeHome(), "projects");
10894
+ if (!existsSync57(projectsRoot)) {
10590
10895
  fail(`${projectsRoot} does not exist`);
10591
10896
  process.exit(1);
10592
10897
  }
@@ -10600,8 +10905,8 @@ function resumeCmd(sessionId) {
10600
10905
  fail(`no cwd field found in ${jsonlPath}`);
10601
10906
  process.exit(1);
10602
10907
  }
10603
- const mapPath = join71(repoHome(), "path-map.json");
10604
- if (!existsSync56(mapPath)) {
10908
+ const mapPath = join74(repoHome(), "path-map.json");
10909
+ if (!existsSync57(mapPath)) {
10605
10910
  fail("path-map.json missing");
10606
10911
  process.exit(1);
10607
10912
  }
@@ -10623,9 +10928,9 @@ function resumeCmd(sessionId) {
10623
10928
  console.log(`cd ${shQuote(hit.localPath)} && claude --resume ${shQuote(sessionId)}`);
10624
10929
  }
10625
10930
  function findTranscriptPath(projectsRoot, sessionId) {
10626
- for (const dir of readdirSync22(projectsRoot)) {
10627
- const candidate = join71(projectsRoot, dir, `${sessionId}.jsonl`);
10628
- if (existsSync56(candidate)) return candidate;
10931
+ for (const dir of readdirSync23(projectsRoot)) {
10932
+ const candidate = join74(projectsRoot, dir, `${sessionId}.jsonl`);
10933
+ if (existsSync57(candidate)) return candidate;
10629
10934
  }
10630
10935
  return null;
10631
10936
  }