claude-nomad 0.65.0 → 0.67.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,15 +803,16 @@ 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,
810
+ rmdirSync,
794
811
  statSync,
795
812
  symlinkSync,
796
813
  writeFileSync
797
814
  } from "node:fs";
798
- import { dirname, join as join4, relative as relative2, sep as sep2 } from "node:path";
815
+ import { dirname, join as join5, relative as relative3, sep as sep2 } from "node:path";
799
816
  function isRetryableRenameCode(code) {
800
817
  return code === "EPERM" || code === "EBUSY";
801
818
  }
@@ -846,14 +863,20 @@ function nowTimestamp() {
846
863
  }
847
864
  function freshBackupTs(backupRoot) {
848
865
  const base = nowTimestamp();
849
- if (!existsSync3(join4(backupRoot, base))) return base;
866
+ if (!existsSync3(join5(backupRoot, base))) return base;
850
867
  let n = 1;
851
- while (existsSync3(join4(backupRoot, `${base}-${n}`))) n++;
868
+ while (existsSync3(join5(backupRoot, `${base}-${n}`))) n++;
852
869
  return `${base}-${n}`;
853
870
  }
871
+ function discardEmptyBackupDir(backupRoot) {
872
+ try {
873
+ rmdirSync(backupRoot);
874
+ } catch {
875
+ }
876
+ }
854
877
  function ensureSymlink(linkPath, target) {
855
878
  if (existsSync3(linkPath)) {
856
- if (lstatSync3(linkPath).isSymbolicLink()) return;
879
+ if (lstatSync4(linkPath).isSymbolicLink()) return;
857
880
  die(`${linkPath} exists and is not a symlink. Move it aside first.`);
858
881
  }
859
882
  mkdirSync(dirname(linkPath), { recursive: true });
@@ -861,21 +884,22 @@ function ensureSymlink(linkPath, target) {
861
884
  log(`linked ${linkPath} -> ${target}`);
862
885
  }
863
886
  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);
887
+ if (!existsSync3(absPath)) return false;
888
+ const rel = relative3(anchor, absPath);
889
+ if (rel === "" || rel === ".." || rel.startsWith(`..${sep2}`)) return false;
890
+ const dst = join5(destRoot, rel);
868
891
  mkdirSync(dirname(dst), { recursive: true });
869
892
  cpSync3(absPath, dst, { recursive: true, force: false, preserveTimestamps: true });
893
+ return true;
870
894
  }
871
895
  function backupBeforeWrite(absPath, ts) {
872
- backupUnder(absPath, claudeHome(), join4(backupBase(), ts));
896
+ return backupUnder(absPath, claudeHome(), join5(backupBase(), ts));
873
897
  }
874
898
  function backupRepoWrite(absPath, ts, repoHome2) {
875
- backupUnder(absPath, repoHome2, join4(backupBase(), ts, "repo"));
899
+ backupUnder(absPath, repoHome2, join5(backupBase(), ts, "repo"));
876
900
  }
877
901
  function backupExtrasWrite(absPath, ts, projectRoot) {
878
- backupUnder(absPath, projectRoot, join4(backupBase(), ts, "extras", encodePath(projectRoot)));
902
+ backupUnder(absPath, projectRoot, join5(backupBase(), ts, "extras", encodePath(projectRoot)));
879
903
  }
880
904
  var RENAME_RETRY_MAX_ATTEMPTS, RENAME_RETRY_BACKOFF_MS;
881
905
  var init_utils_fs = __esm({
@@ -891,12 +915,12 @@ var init_utils_fs = __esm({
891
915
 
892
916
  // src/commands.pull.wedge.ts
893
917
  import { execFileSync as execFileSync3 } from "node:child_process";
894
- import { existsSync as existsSync15 } from "node:fs";
895
- import { join as join17 } from "node:path";
918
+ import { existsSync as existsSync16 } from "node:fs";
919
+ import { join as join20 } from "node:path";
896
920
  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";
921
+ const g = join20(repo, ".git");
922
+ if (existsSync16(join20(g, "rebase-merge")) || existsSync16(join20(g, "rebase-apply"))) return "rebase";
923
+ if (existsSync16(join20(g, "MERGE_HEAD"))) return "merge";
900
924
  return null;
901
925
  }
902
926
  function probeUnmergedIndex(repo) {
@@ -966,15 +990,15 @@ var init_commands_pull_wedge = __esm({
966
990
  });
967
991
 
968
992
  // src/push-gitleaks.config.ts
969
- import { existsSync as existsSync16, mkdtempSync, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
993
+ import { existsSync as existsSync17, mkdtempSync, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
970
994
  import { tmpdir } from "node:os";
971
- import { join as join18 } from "node:path";
995
+ import { join as join21 } from "node:path";
972
996
  import { fileURLToPath } from "node:url";
973
997
  function resolveTomlPath(repo = repoHome()) {
974
- const repoToml = join18(repo, ".gitleaks.toml");
975
- if (existsSync16(repoToml)) return repoToml;
998
+ const repoToml = join21(repo, ".gitleaks.toml");
999
+ if (existsSync17(repoToml)) return repoToml;
976
1000
  const bundled = fileURLToPath(new URL("../.gitleaks.toml", import.meta.url));
977
- return existsSync16(bundled) ? bundled : null;
1001
+ return existsSync17(bundled) ? bundled : null;
978
1002
  }
979
1003
  function assertOverlayAllowlistsScoped(overlayBody) {
980
1004
  if (OVERLAY_INLINE_ALLOWLIST_RE.test(overlayBody)) {
@@ -1012,17 +1036,17 @@ function buildOverlayTempConfig(overlayBody, bundled) {
1012
1036
  path = ${JSON.stringify(bundled)}
1013
1037
 
1014
1038
  ${overlayBody}`;
1015
- const tempPath = mkdtempSync(join18(tmpdir(), "nomad-gitleaks-cfg-"));
1016
- const configPath = join18(tempPath, "config.toml");
1039
+ const tempPath = mkdtempSync(join21(tmpdir(), "nomad-gitleaks-cfg-"));
1040
+ const configPath = join21(tempPath, "config.toml");
1017
1041
  writeFileSync3(configPath, tempBody, { mode: 384, flag: "wx" });
1018
1042
  return { configPath, tempPath };
1019
1043
  }
1020
1044
  function resolveTomlConfig() {
1021
1045
  const repo = repoHome();
1022
- const overlayPath = join18(repo, ".gitleaks.overlay.toml");
1023
- const repoToml = join18(repo, ".gitleaks.toml");
1046
+ const overlayPath = join21(repo, ".gitleaks.overlay.toml");
1047
+ const repoToml = join21(repo, ".gitleaks.toml");
1024
1048
  const bundled = resolveTomlPath(repo);
1025
- if (!existsSync16(overlayPath)) {
1049
+ if (!existsSync17(overlayPath)) {
1026
1050
  return { path: bundled, tempPath: null };
1027
1051
  }
1028
1052
  if (bundled === repoToml) {
@@ -1108,9 +1132,9 @@ var init_autostash_guard = __esm({
1108
1132
 
1109
1133
  // src/push-checks.ts
1110
1134
  import { execFileSync as execFileSync4 } from "node:child_process";
1111
- import { readdirSync as readdirSync7, rmSync as rmSync7 } from "node:fs";
1135
+ import { readdirSync as readdirSync8, rmSync as rmSync7 } from "node:fs";
1112
1136
  import { homedir as homedir2, platform } from "node:os";
1113
- import { delimiter, join as join19 } from "node:path";
1137
+ import { delimiter, join as join22 } from "node:path";
1114
1138
  function gitleaksInstallHint() {
1115
1139
  const head = "gitleaks not on PATH (required for nomad push). Install:";
1116
1140
  const plat = platform();
@@ -1150,23 +1174,23 @@ function gitleaksInstallHint() {
1150
1174
  }
1151
1175
  function findGitlinks(dir) {
1152
1176
  const hits = [];
1153
- function walk(current) {
1177
+ function walk2(current) {
1154
1178
  let entries;
1155
1179
  try {
1156
- entries = readdirSync7(current, { withFileTypes: true });
1180
+ entries = readdirSync8(current, { withFileTypes: true });
1157
1181
  } catch {
1158
1182
  return;
1159
1183
  }
1160
1184
  for (const e of entries) {
1161
- const p = join19(current, e.name);
1185
+ const p = join22(current, e.name);
1162
1186
  if (e.name === ".git") {
1163
1187
  hits.push(p);
1164
1188
  continue;
1165
1189
  }
1166
- if (e.isDirectory()) walk(p);
1190
+ if (e.isDirectory()) walk2(p);
1167
1191
  }
1168
1192
  }
1169
- walk(dir);
1193
+ walk2(dir);
1170
1194
  return hits;
1171
1195
  }
1172
1196
  function probeGitleaks() {
@@ -1222,7 +1246,7 @@ var init_push_checks = __esm({
1222
1246
  import { execFileSync as execFileSync8 } from "node:child_process";
1223
1247
  import { mkdirSync as mkdirSync3, mkdtempSync as mkdtempSync2, readFileSync as readFileSync6, rmSync as rmSync8 } from "node:fs";
1224
1248
  import { homedir as homedir3 } from "node:os";
1225
- import { join as join23 } from "node:path";
1249
+ import { join as join26 } from "node:path";
1226
1250
  function toFinding(entry) {
1227
1251
  if (typeof entry !== "object" || entry === null) return null;
1228
1252
  const e = entry;
@@ -1270,9 +1294,9 @@ function resolveScanFailure(reportPath, e, forwardStreams) {
1270
1294
  return report;
1271
1295
  }
1272
1296
  function scanStagedTree(repoDir, forwardStreams = false) {
1273
- const cacheDir = join23(homedir3(), ".cache", "claude-nomad");
1297
+ const cacheDir = join26(homedir3(), ".cache", "claude-nomad");
1274
1298
  mkdirSync3(cacheDir, { recursive: true });
1275
- const reportPath = join23(cacheDir, `gitleaks-${nowTimestamp()}-${process.pid}.json`);
1299
+ const reportPath = join26(cacheDir, `gitleaks-${nowTimestamp()}-${process.pid}.json`);
1276
1300
  const { path: toml, tempPath } = resolveTomlConfig();
1277
1301
  const args = [
1278
1302
  "protect",
@@ -1300,14 +1324,14 @@ function scanStagedTree(repoDir, forwardStreams = false) {
1300
1324
  }
1301
1325
  }
1302
1326
  function scanFile(filePath, forwardStreams = false, timeoutMs = GITLEAKS_SCAN_TIMEOUT_MS) {
1303
- const cacheDir = join23(homedir3(), ".cache", "claude-nomad");
1327
+ const cacheDir = join26(homedir3(), ".cache", "claude-nomad");
1304
1328
  mkdirSync3(cacheDir, { recursive: true });
1305
1329
  const { path: toml, tempPath } = resolveTomlConfig();
1306
1330
  let reportDir;
1307
1331
  let reportPath;
1308
1332
  try {
1309
- reportDir = mkdtempSync2(join23(cacheDir, `gitleaks-file-${nowTimestamp()}-${process.pid}-`));
1310
- reportPath = join23(reportDir, "report.json");
1333
+ reportDir = mkdtempSync2(join26(cacheDir, `gitleaks-file-${nowTimestamp()}-${process.pid}-`));
1334
+ reportPath = join26(reportDir, "report.json");
1311
1335
  const args = [
1312
1336
  "detect",
1313
1337
  "--no-git",
@@ -1531,16 +1555,314 @@ var init_push_leak_verdict = __esm({
1531
1555
  });
1532
1556
 
1533
1557
  // src/commands.adopt.ts
1558
+ import { existsSync as existsSync5, lstatSync as lstatSync7 } from "node:fs";
1559
+ import { join as join9 } from "node:path";
1560
+
1561
+ // src/commands.adopt.recover.ts
1562
+ import { lstatSync as lstatSync6, rmSync as rmSync4 } from "node:fs";
1563
+ import { dirname as dirname2, join as join7, resolve as resolve2 } from "node:path";
1564
+
1565
+ // src/commands.adopt.scan.ts
1534
1566
  init_config();
1535
- init_config_sharedDirs_guard();
1536
1567
  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";
1568
+ init_utils();
1569
+ import { lstatSync, readdirSync } from "node:fs";
1570
+ import { join as join2, relative } from "node:path";
1571
+ function toForwardSlash(p) {
1572
+ return p.replaceAll("\\", "/");
1573
+ }
1574
+ function walk(root, dir, out) {
1575
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
1576
+ const match = matchDeniedName(ALWAYS_NEVER_SYNC, entry.name);
1577
+ if (match !== null) {
1578
+ out.push({
1579
+ path: toForwardSlash(relative(root, join2(dir, entry.name))),
1580
+ matched: match.axis === "name" ? match.entry : null
1581
+ });
1582
+ continue;
1583
+ }
1584
+ if (entry.isDirectory()) walk(root, join2(dir, entry.name), out);
1585
+ }
1586
+ }
1587
+ function scanDeniedEntries(root) {
1588
+ const stat = lstatSync(root, { throwIfNoEntry: false });
1589
+ if (!stat?.isDirectory()) return [];
1590
+ const out = [];
1591
+ walk(root, root, out);
1592
+ out.sort((a, b) => a.path.localeCompare(b.path));
1593
+ return out;
1594
+ }
1595
+ function scanOrFatal(name, root, state) {
1596
+ try {
1597
+ return scanDeniedEntries(root);
1598
+ } catch (err) {
1599
+ const message = err?.message;
1600
+ const text = typeof message === "string" ? message : String(err);
1601
+ throw new NomadFatal(
1602
+ `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.`,
1603
+ { code: EXIT.GENERIC_FAILURE }
1604
+ );
1605
+ }
1606
+ }
1607
+ function describeMatch(hit) {
1608
+ return hit.matched === null ? "matches a credential filename shape" : `matches the never-sync name "${hit.matched}"`;
1609
+ }
1610
+ function remedies(hits, root, name) {
1611
+ const subject = hits.length === 1 ? "that path" : "those paths";
1612
+ let out = ` Move ${subject} out of ${root} and run \`nomad adopt ${name}\` again.`;
1613
+ if (hits.some((hit) => hit.matched !== null)) {
1614
+ 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.";
1615
+ }
1616
+ if (hits.some((hit) => hit.matched === null)) {
1617
+ 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.";
1618
+ }
1619
+ return out;
1620
+ }
1621
+ function deniedEntriesRefusal(name, root, hits, clauses) {
1622
+ const lines = hits.map((hit) => ` ${hit.path} (${describeMatch(hit)})`).join("\n");
1623
+ return new NomadFatal(
1624
+ `cannot adopt ${name}: ${clauses.found}:
1625
+ ${lines}
1626
+ ${clauses.state}${remedies(hits, root, name)}`,
1627
+ { code: EXIT.GENERIC_FAILURE }
1628
+ );
1629
+ }
1630
+ function refuseDeniedEntries(name, root) {
1631
+ const nothingChanged = "Nothing was changed.";
1632
+ const hits = scanOrFatal(name, root, () => nothingChanged);
1633
+ if (hits.length === 0) return;
1634
+ throw deniedEntriesRefusal(name, root, hits, {
1635
+ found: `${root} contains never-sync content`,
1636
+ state: nothingChanged
1637
+ });
1638
+ }
1639
+
1640
+ // src/commands.adopt.recover.ts
1641
+ init_config();
1642
+ init_exit_codes();
1643
+
1644
+ // src/extras-sync.core.ts
1645
+ init_config();
1646
+ import { cpSync as cpSync2, existsSync as existsSync2, lstatSync as lstatSync3, readdirSync as readdirSync3, readFileSync as readFileSync2, rmSync as rmSync2 } from "node:fs";
1647
+ import { basename as basename2, join as join4, relative as relative2 } from "node:path";
1648
+
1649
+ // src/extras-sync.collision.ts
1650
+ init_utils();
1651
+ import { cpSync, existsSync, lstatSync as lstatSync2, readdirSync as readdirSync2, rmSync } from "node:fs";
1652
+ import { basename, join as join3 } from "node:path";
1653
+ function quoted(p) {
1654
+ return `"${p}"`;
1655
+ }
1656
+ function stripCollidingDstSymlinks(src, dst, isExcluded) {
1657
+ if (!existsSync(dst)) return;
1658
+ for (const name of readdirSync2(src)) {
1659
+ if (isExcluded(name)) continue;
1660
+ const dstPath = join3(dst, name);
1661
+ const dstStat = lstatSync2(dstPath, { throwIfNoEntry: false });
1662
+ if (dstStat === void 0) continue;
1663
+ if (dstStat.isSymbolicLink()) {
1664
+ rmSync(dstPath, { recursive: true, force: true });
1665
+ } else if (dstStat.isDirectory() && lstatSync2(join3(src, name)).isDirectory()) {
1666
+ stripCollidingDstSymlinks(join3(src, name), dstPath, isExcluded);
1667
+ }
1668
+ }
1669
+ }
1670
+ function cpSyncGuarded(src, dst, filter, label) {
1671
+ try {
1672
+ cpSync(src, dst, { recursive: true, force: true, verbatimSymlinks: true, filter });
1673
+ } catch (err) {
1674
+ const e = err;
1675
+ 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) {
1676
+ throw new NomadFatal(
1677
+ `${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`
1678
+ );
1679
+ }
1680
+ throw err;
1681
+ }
1682
+ }
1683
+ function prunePreservingBy(src, dst, isPreserved, isRootPreserved) {
1684
+ for (const name of readdirSync2(dst)) {
1685
+ if (isPreserved(name) || isRootPreserved?.(name) === true) continue;
1686
+ const dstPath = join3(dst, name);
1687
+ const srcStat = lstatSync2(join3(src, name), { throwIfNoEntry: false });
1688
+ if (srcStat === void 0) {
1689
+ rmSync(dstPath, { recursive: true, force: true });
1690
+ continue;
1691
+ }
1692
+ const dstStat = lstatSync2(dstPath);
1693
+ if (srcStat.isDirectory() && dstStat.isDirectory()) {
1694
+ prunePreservingBy(join3(src, name), dstPath, isPreserved);
1695
+ } else if (srcStat.isDirectory() !== dstStat.isDirectory()) {
1696
+ rmSync(dstPath, { recursive: true, force: true });
1697
+ }
1698
+ }
1699
+ }
1700
+ function copyExtrasFilteredPreservingBy(src, dst, isPreserved, isRootPreserved) {
1701
+ const dstStat = lstatSync2(dst, { throwIfNoEntry: false });
1702
+ if (dstStat !== void 0) {
1703
+ if (dstStat.isDirectory()) {
1704
+ const srcStat = lstatSync2(src, { throwIfNoEntry: false });
1705
+ if (srcStat !== void 0 && !srcStat.isDirectory()) {
1706
+ throw new NomadFatal(
1707
+ `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`
1708
+ );
1709
+ }
1710
+ prunePreservingBy(src, dst, isPreserved, isRootPreserved);
1711
+ } else {
1712
+ rmSync(dst, { recursive: true, force: true });
1713
+ }
1714
+ }
1715
+ stripCollidingDstSymlinks(src, dst, isPreserved);
1716
+ cpSync(src, dst, {
1717
+ recursive: true,
1718
+ force: true,
1719
+ verbatimSymlinks: true,
1720
+ filter: (srcEntry) => srcEntry === src || !isPreserved(basename(srcEntry))
1721
+ });
1722
+ }
1723
+
1724
+ // src/extras-sync.guards.ts
1725
+ init_utils();
1726
+ init_config_sharedDirs_guard();
1727
+ import { isAbsolute, normalize, sep } from "node:path";
1728
+ function assertSafeLocalRoot(localRoot, logical) {
1729
+ if (!isAbsolute(localRoot)) {
1730
+ throw new NomadFatal(
1731
+ `invalid localRoot for ${logical} in path-map.json: ${JSON.stringify(localRoot)} (must be absolute)`
1732
+ );
1733
+ }
1734
+ const canonical = process.platform === "win32" ? localRoot.split("/").join(sep) : localRoot;
1735
+ if (canonical !== normalize(canonical)) {
1736
+ throw new NomadFatal(
1737
+ `invalid localRoot for ${logical} in path-map.json: ${JSON.stringify(localRoot)} (must be already-normalized; no '..' or redundant segments)`
1738
+ );
1739
+ }
1740
+ }
1741
+
1742
+ // src/extras-sync.core.ts
1743
+ init_utils();
1744
+ init_utils_json();
1745
+ function loadValidatedExtras(opts) {
1746
+ const repo = repoHome();
1747
+ const mapPath = join4(repo, "path-map.json");
1748
+ const repoExtras = join4(repo, "shared", "extras");
1749
+ if (!existsSync2(mapPath) || opts.requireRepoExtras === true && !existsSync2(repoExtras)) {
1750
+ if (opts.missingMsg !== void 0) log(opts.missingMsg);
1751
+ return null;
1752
+ }
1753
+ const map = readPathMap(mapPath);
1754
+ const extrasMap = map.extras ?? {};
1755
+ if (Object.keys(extrasMap).length === 0) return null;
1756
+ for (const logical of Object.keys(extrasMap)) {
1757
+ assertSafeLogical(logical);
1758
+ const localRoot = map.projects[logical]?.[HOST];
1759
+ if (localRoot && localRoot !== "TBD") assertSafeLocalRoot(localRoot, logical);
1760
+ }
1761
+ return { map, extrasMap };
1762
+ }
1763
+ function* eachExtrasTarget(v, counts) {
1764
+ const whitelist = SUPPORTED_EXTRAS;
1765
+ for (const [logical, dirnames] of Object.entries(v.extrasMap)) {
1766
+ const localRoot = v.map.projects[logical]?.[HOST];
1767
+ if (!localRoot || localRoot === "TBD") {
1768
+ counts.unmapped++;
1769
+ continue;
1770
+ }
1771
+ for (const dirname15 of dirnames) {
1772
+ if (!whitelist.includes(dirname15)) {
1773
+ counts.skipped++;
1774
+ continue;
1775
+ }
1776
+ yield { logical, localRoot, dirname: dirname15 };
1777
+ }
1778
+ }
1779
+ }
1780
+ function copyExtrasOverlayFiltered(src, dst, blockSet) {
1781
+ const dstStat = lstatSync3(dst, { throwIfNoEntry: false });
1782
+ if (dstStat !== void 0 && !dstStat.isDirectory()) {
1783
+ rmSync2(dst, { recursive: true, force: true });
1784
+ }
1785
+ stripCollidingDstSymlinks(src, dst, (name) => isDeniedName(blockSet, name));
1786
+ cpSyncGuarded(
1787
+ src,
1788
+ dst,
1789
+ (srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename2(srcEntry)),
1790
+ "copyExtrasOverlayFiltered"
1791
+ );
1792
+ }
1793
+ function copyExtrasOverlaySkipDiverged(src, dst, blockSet, divergedSet) {
1794
+ stripCollidingDstSymlinks(src, dst, (name) => isDeniedName(blockSet, name));
1795
+ cpSyncGuarded(
1796
+ src,
1797
+ dst,
1798
+ (srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename2(srcEntry)) && !divergedSet.has(relative2(src, srcEntry)),
1799
+ "copyExtrasOverlaySkipDiverged"
1800
+ );
1801
+ }
1802
+ function copyExtras(src, dst) {
1803
+ rmSync2(dst, { recursive: true, force: true });
1804
+ cpSync2(src, dst, { recursive: true, force: true, verbatimSymlinks: true });
1805
+ }
1806
+ function copyExtrasFileSkipDiverged(src, dst) {
1807
+ if (existsSync2(dst)) {
1808
+ let dstBuf;
1809
+ try {
1810
+ dstBuf = readFileSync2(dst);
1811
+ } catch {
1812
+ return;
1813
+ }
1814
+ if (!readFileSync2(src).equals(dstBuf)) return;
1815
+ }
1816
+ copyExtras(src, dst);
1817
+ }
1818
+ function extrasDenySet(dirname15) {
1819
+ return isClaudeExtraName(dirname15) ? CLAUDE_EXTRA_NEVER_SYNC : ALWAYS_NEVER_SYNC;
1820
+ }
1821
+ function copyExtrasFiltered(src, dst, blockSet) {
1822
+ rmSync2(dst, { recursive: true, force: true });
1823
+ cpSync2(src, dst, {
1824
+ recursive: true,
1825
+ force: true,
1826
+ verbatimSymlinks: true,
1827
+ filter: (srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename2(srcEntry))
1828
+ });
1829
+ }
1830
+ function prunePreservingDenied(src, dst, blockSet) {
1831
+ for (const name of readdirSync3(dst)) {
1832
+ if (isDeniedName(blockSet, name)) continue;
1833
+ const dstPath = join4(dst, name);
1834
+ const srcStat = lstatSync3(join4(src, name), { throwIfNoEntry: false });
1835
+ if (srcStat === void 0) {
1836
+ rmSync2(dstPath, { recursive: true, force: true });
1837
+ continue;
1838
+ }
1839
+ const dstStat = lstatSync3(dstPath);
1840
+ if (srcStat.isDirectory() && dstStat.isDirectory()) {
1841
+ prunePreservingDenied(join4(src, name), dstPath, blockSet);
1842
+ } else if (srcStat.isDirectory() !== dstStat.isDirectory()) {
1843
+ rmSync2(dstPath, { recursive: true, force: true });
1844
+ }
1845
+ }
1846
+ }
1847
+ function copyExtrasFilteredPreserving(src, dst, blockSet) {
1848
+ const dstStat = lstatSync3(dst, { throwIfNoEntry: false });
1849
+ if (dstStat !== void 0) {
1850
+ if (dstStat.isDirectory()) prunePreservingDenied(src, dst, blockSet);
1851
+ else rmSync2(dst, { recursive: true, force: true });
1852
+ }
1853
+ stripCollidingDstSymlinks(src, dst, (name) => isDeniedName(blockSet, name));
1854
+ cpSync2(src, dst, {
1855
+ recursive: true,
1856
+ force: true,
1857
+ verbatimSymlinks: true,
1858
+ filter: (srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename2(srcEntry))
1859
+ });
1860
+ }
1539
1861
 
1540
1862
  // src/links.ts
1541
1863
  init_config();
1542
- import { existsSync as existsSync4, lstatSync as lstatSync4, rmSync as rmSync3 } from "node:fs";
1543
- import { join as join5 } from "node:path";
1864
+ import { existsSync as existsSync4, lstatSync as lstatSync5, rmSync as rmSync3 } from "node:fs";
1865
+ import { join as join6 } from "node:path";
1544
1866
 
1545
1867
  // src/hooks-filter.ts
1546
1868
  init_config();
@@ -1829,225 +2151,7 @@ function buildCaptureSubset(merged, settings, opts) {
1829
2151
  return out;
1830
2152
  }
1831
2153
 
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
- // src/links.ts
2154
+ // src/links.ts
2051
2155
  init_utils();
2052
2156
  init_utils_fs();
2053
2157
  init_utils_json();
@@ -2073,14 +2177,14 @@ function emitCopy(onPreview, from, to) {
2073
2177
  }
2074
2178
  }
2075
2179
  function isAlreadySymlink(linkPath) {
2076
- return existsSync4(linkPath) && lstatSync4(linkPath).isSymbolicLink();
2180
+ return existsSync4(linkPath) && lstatSync5(linkPath).isSymbolicLink();
2077
2181
  }
2078
2182
  function runAutoMovePasses(linkNames, claude, repo, ts, dryRun, onPreview) {
2079
2183
  for (const name of linkNames) {
2080
- const linkPath = join5(claude, name);
2081
- const target = join5(repo, "shared", name);
2184
+ const linkPath = join6(claude, name);
2185
+ const target = join6(repo, "shared", name);
2082
2186
  if (!existsSync4(linkPath)) continue;
2083
- if (lstatSync4(linkPath).isSymbolicLink()) continue;
2187
+ if (lstatSync5(linkPath).isSymbolicLink()) continue;
2084
2188
  if (!existsSync4(target)) continue;
2085
2189
  if (dryRun) {
2086
2190
  emitAutoMove(onPreview, linkPath, ts, name);
@@ -2093,23 +2197,58 @@ function runAutoMovePasses(linkNames, claude, repo, ts, dryRun, onPreview) {
2093
2197
  function copySharedLinkPull(src, dst) {
2094
2198
  copyExtrasFilteredPreservingBy(src, dst, (name) => isDeniedName(ALWAYS_NEVER_SYNC, name));
2095
2199
  }
2200
+ function stillOccupied(abs) {
2201
+ try {
2202
+ return lstatSync5(abs, { throwIfNoEntry: false }) !== void 0;
2203
+ } catch {
2204
+ return true;
2205
+ }
2206
+ }
2207
+ function snapshotBeforeWin32Copy(linkPath, ts) {
2208
+ try {
2209
+ return backupBeforeWrite(linkPath, ts) ? "snapshotted" : "nothing-to-snapshot";
2210
+ } catch (err) {
2211
+ warn(
2212
+ `could not snapshot ${linkPath} before updating it (${err.message}), so it was left as it is. The rest of the pull continues`
2213
+ );
2214
+ return "failed";
2215
+ }
2216
+ }
2217
+ function warnWin32ApplyFailed(linkPath, ts, err, snapshotted) {
2218
+ const state = stillOccupied(linkPath) ? "it may be unchanged, or partly updated" : "nothing is at that path now";
2219
+ const recover = snapshotted ? ` A copy of what it held before this pull is under backup/${ts}/.` : "";
2220
+ warn(
2221
+ `${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`
2222
+ );
2223
+ }
2224
+ function applyOneSharedLinkWin32(target, linkPath, ts) {
2225
+ let snapshotted = false;
2226
+ try {
2227
+ const stat = lstatSync5(linkPath, { throwIfNoEntry: false });
2228
+ if (stat !== void 0) {
2229
+ const snapshot = snapshotBeforeWin32Copy(linkPath, ts);
2230
+ if (snapshot === "failed") return;
2231
+ snapshotted = snapshot === "snapshotted";
2232
+ if (stat.isSymbolicLink()) {
2233
+ rmSync3(linkPath, { recursive: true, force: true });
2234
+ }
2235
+ }
2236
+ copySharedLinkPull(target, linkPath);
2237
+ } catch (err) {
2238
+ if (err instanceof NomadFatal) throw err;
2239
+ warnWin32ApplyFailed(linkPath, ts, err, snapshotted);
2240
+ }
2241
+ }
2096
2242
  function applySharedLinksWin32(linkNames, claude, repo, ts, dryRun, onPreview) {
2097
2243
  for (const name of linkNames) {
2098
- const target = join5(repo, "shared", name);
2244
+ const target = join6(repo, "shared", name);
2099
2245
  if (!existsSync4(target)) continue;
2100
- const linkPath = join5(claude, name);
2246
+ const linkPath = join6(claude, name);
2101
2247
  if (dryRun) {
2102
2248
  emitCopy(onPreview, linkPath, target);
2103
2249
  continue;
2104
2250
  }
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);
2251
+ applyOneSharedLinkWin32(target, linkPath, ts);
2113
2252
  }
2114
2253
  }
2115
2254
  function applySharedLinks(ts, map, opts = {}) {
@@ -2123,9 +2262,9 @@ function applySharedLinks(ts, map, opts = {}) {
2123
2262
  }
2124
2263
  runAutoMovePasses(linkNames, claude, repo, ts, dryRun, opts.onPreview);
2125
2264
  for (const name of linkNames) {
2126
- const target = join5(repo, "shared", name);
2265
+ const target = join6(repo, "shared", name);
2127
2266
  if (!existsSync4(target)) continue;
2128
- const linkPath = join5(claude, name);
2267
+ const linkPath = join6(claude, name);
2129
2268
  if (isAlreadySymlink(linkPath)) continue;
2130
2269
  if (dryRun) {
2131
2270
  emitCreate(opts.onPreview, linkPath, target);
@@ -2167,8 +2306,8 @@ function regenerateSettings(ts, opts = {}) {
2167
2306
  const suppressDriftWarn = opts.suppressDriftWarn === true;
2168
2307
  const repo = repoHome();
2169
2308
  const claude = claudeHome();
2170
- const basePath = join5(repo, "shared", "settings.base.json");
2171
- const hostPath = join5(repo, "hosts", `${HOST}.json`);
2309
+ const basePath = join6(repo, "shared", "settings.base.json");
2310
+ const hostPath = join6(repo, "hosts", `${HOST}.json`);
2172
2311
  if (!existsSync4(basePath)) {
2173
2312
  die("repo not initialized; run 'nomad init' to scaffold");
2174
2313
  }
@@ -2176,7 +2315,7 @@ function regenerateSettings(ts, opts = {}) {
2176
2315
  const hasOverrides = existsSync4(hostPath);
2177
2316
  const overrides = hasOverrides ? readJson(hostPath) : {};
2178
2317
  const merged = deepMerge(base, overrides);
2179
- const settingsPath = join5(claude, "settings.json");
2318
+ const settingsPath = join6(claude, "settings.json");
2180
2319
  const { existing, present, malformed } = readExistingSettings(settingsPath);
2181
2320
  if (!suppressDriftWarn && present) {
2182
2321
  if (malformed) {
@@ -2198,56 +2337,318 @@ function regenerateSettings(ts, opts = {}) {
2198
2337
  return { label: overrideLabel };
2199
2338
  }
2200
2339
 
2201
- // src/commands.adopt.ts
2340
+ // src/commands.adopt.recover.ts
2202
2341
  init_utils();
2203
- init_utils_fs();
2204
- init_utils_json();
2205
- var ADOPT_PUSH_HINT = "run `nomad push` to share with other hosts";
2342
+ function errorText(err) {
2343
+ const message = err?.message;
2344
+ return typeof message === "string" ? message : String(err);
2345
+ }
2206
2346
  function lexists(p) {
2207
2347
  try {
2208
- lstatSync5(p);
2348
+ lstatSync6(p);
2209
2349
  return true;
2210
2350
  } catch {
2211
2351
  return false;
2212
2352
  }
2213
2353
  }
2214
- function readMapIfPresent(repoHome2) {
2215
- const mapPath = join6(repoHome2, "path-map.json");
2216
- return existsSync5(mapPath) ? readPathMap(mapPath) : { projects: {} };
2354
+ function isDirectChildOf(root, abs) {
2355
+ return dirname2(resolve2(abs)) === resolve2(root);
2217
2356
  }
2218
- function isConfiguredTarget(name, map) {
2219
- return SHARED_LINKS.includes(name) || sharedDirEntries(map).includes(name);
2357
+ function clearPartialCopy(linkPath) {
2358
+ return clearIfDirectChild(claudeHome(), linkPath);
2220
2359
  }
2221
- function isValidAdoptName(name) {
2222
- if (SHARED_LINKS.includes(name)) return true;
2223
- return isValidSharedDir(name);
2360
+ function clearPartialShared(sharedTarget, repo) {
2361
+ return clearIfDirectChild(join7(repo, "shared"), sharedTarget);
2224
2362
  }
2225
- function performAdoptMove(name, linkPath, sharedTarget, repo, backup) {
2226
- 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 });
2230
- if (process.platform === "win32") {
2231
- copySharedLinkPull(sharedTarget, linkPath);
2232
- } else {
2233
- ensureSymlink(linkPath, sharedTarget);
2363
+ function clearIfDirectChild(root, target) {
2364
+ const resolved = resolve2(target);
2365
+ if (!isDirectChildOf(root, resolved)) return false;
2366
+ try {
2367
+ rmSync4(resolved, { recursive: true, force: true });
2368
+ } catch {
2369
+ return false;
2234
2370
  }
2235
- const rel = join6("shared", name);
2236
- gitOrFatal(["add", "--", rel], `git add shared/${name}`, repo);
2237
- log(`adopted ${name}; ${ADOPT_PUSH_HINT}`);
2371
+ return !lexists(resolved);
2238
2372
  }
2239
- function cmdAdopt(name, opts = {}) {
2240
- const dryRun = opts.dryRun === true;
2241
- const rejection = validateSharedDirEntry(name);
2242
- if (rejection !== null && rejection.reason === "secret-shaped") {
2373
+ function stageOrReport(stage) {
2374
+ try {
2375
+ stage();
2376
+ return "";
2377
+ } catch (err) {
2378
+ return ` Staging it failed too (${errorText(err)}), so it is in the repo but not staged.`;
2379
+ }
2380
+ }
2381
+ function describeLeftoverAt(name, linkPath, sourceRemoved) {
2382
+ if (!sourceRemoved) {
2383
+ 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.`;
2384
+ }
2385
+ if (clearPartialCopy(linkPath)) return "";
2386
+ 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.`;
2387
+ }
2388
+ function restoreWin32LocalCopy(name, linkPath, sharedTarget, ts, stage, opts) {
2389
+ try {
2390
+ copySharedLinkPull(sharedTarget, linkPath);
2391
+ } catch (err) {
2392
+ if (err instanceof NomadFatal) {
2393
+ const fatalStageFailure = stageOrReport(stage);
2394
+ if (fatalStageFailure !== "") warn(`${name}:${fatalStageFailure}`);
2395
+ throw err;
2396
+ }
2397
+ const leftover = describeLeftoverAt(name, linkPath, opts.sourceRemoved);
2398
+ const stageFailure = stageOrReport(stage);
2399
+ const staged = stageFailure === "" ? " and staged." : `.${stageFailure}`;
2400
+ const recover = opts.snapshotted ? ` A copy of what it held before is under backup/${ts}/.` : "";
2243
2401
  throw new NomadFatal(
2244
- `cannot adopt ${JSON.stringify(name)}: ${rejection.message}. If it is listed in sharedDirs in path-map.json, remove it there too.`,
2402
+ `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}`,
2245
2403
  { code: EXIT.GENERIC_FAILURE }
2246
2404
  );
2247
2405
  }
2248
- if (!isValidAdoptName(name)) {
2249
- fail(`invalid name: ${JSON.stringify(name)}`);
2250
- process.exit(1);
2406
+ }
2407
+ function describePartialShared(name, linkPath) {
2408
+ if (process.platform === "win32") {
2409
+ 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.`;
2410
+ }
2411
+ return ` A partial shared/${name} may still be in the repo; remove it first, because adopt refuses to run while it is there.`;
2412
+ }
2413
+ function copyIntoSharedOrFatal(name, linkPath, sharedTarget, repo) {
2414
+ try {
2415
+ copyExtrasFiltered(linkPath, sharedTarget, ALWAYS_NEVER_SYNC);
2416
+ } catch (err) {
2417
+ const leftover = clearPartialShared(sharedTarget, repo) ? "" : describePartialShared(name, linkPath);
2418
+ throw new NomadFatal(
2419
+ `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}`,
2420
+ { code: EXIT.GENERIC_FAILURE }
2421
+ );
2422
+ }
2423
+ }
2424
+ function refuseLateDeniedEntries(name, linkPath, sharedTarget, repo, opts) {
2425
+ 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.` : "";
2426
+ 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}`;
2427
+ const late = scanOrFatal(name, linkPath, stateNow);
2428
+ if (late.length === 0) return;
2429
+ throw deniedEntriesRefusal(name, linkPath, late, {
2430
+ found: `never-sync content appeared under ${linkPath} after the preflight scan`,
2431
+ state: stateNow()
2432
+ });
2433
+ }
2434
+ function removeAdoptSource(linkPath) {
2435
+ const resolved = resolve2(linkPath);
2436
+ if (!isDirectChildOf(claudeHome(), resolved)) {
2437
+ return { ok: false, message: "it is not a direct child of the configured Claude home" };
2438
+ }
2439
+ try {
2440
+ rmSync4(resolved, { recursive: true, force: true });
2441
+ } catch (err) {
2442
+ return { ok: false, message: errorText(err) };
2443
+ }
2444
+ if (lexists(resolved)) {
2445
+ return { ok: false, message: "the delete was accepted but the entry is still there" };
2446
+ }
2447
+ return { ok: true };
2448
+ }
2449
+ function reportSourceRemovalFailure(name, linkPath, message, stage) {
2450
+ if (process.platform === "win32") {
2451
+ warn(
2452
+ `${name}: could not remove ${linkPath} (${message}); refreshing it from shared/${name} instead, which leaves the same result`
2453
+ );
2454
+ return;
2455
+ }
2456
+ const stageFailure = stageOrReport(stage);
2457
+ const staged = stageFailure === "" ? " and staged." : `.${stageFailure}`;
2458
+ throw new NomadFatal(
2459
+ `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.`,
2460
+ { code: EXIT.GENERIC_FAILURE }
2461
+ );
2462
+ }
2463
+
2464
+ // src/commands.adopt.ts
2465
+ init_config();
2466
+ init_config_sharedDirs_guard();
2467
+ init_exit_codes();
2468
+ init_utils();
2469
+ init_utils_fs();
2470
+
2471
+ // src/utils.lockfile.ts
2472
+ init_config();
2473
+ init_utils();
2474
+ import { closeSync as closeSync2, mkdirSync as mkdirSync2, openSync as openSync2, readFileSync as readFileSync3, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
2475
+ import { dirname as dirname3, join as join8 } from "node:path";
2476
+ function lockFilePath() {
2477
+ return join8(home(), ".cache", "claude-nomad", "nomad.lock");
2478
+ }
2479
+ function acquireLock(verb) {
2480
+ const lp = lockFilePath();
2481
+ mkdirSync2(dirname3(lp), { recursive: true });
2482
+ try {
2483
+ const fd = openSync2(lp, "wx");
2484
+ try {
2485
+ writeFileSync2(fd, String(process.pid));
2486
+ } catch (writeErr) {
2487
+ try {
2488
+ closeSync2(fd);
2489
+ } catch {
2490
+ }
2491
+ try {
2492
+ unlinkSync(lp);
2493
+ } catch {
2494
+ }
2495
+ throw writeErr;
2496
+ }
2497
+ return { fd, path: lp };
2498
+ } catch (err) {
2499
+ const code = err.code;
2500
+ if (code !== "EEXIST") throw err;
2501
+ return checkStaleAndRetry(verb, lp);
2502
+ }
2503
+ }
2504
+ function releaseLock(handle) {
2505
+ if (handle === null) return;
2506
+ const lp = handle.path;
2507
+ try {
2508
+ closeSync2(handle.fd);
2509
+ } catch {
2510
+ }
2511
+ try {
2512
+ unlinkSync(lp);
2513
+ } catch (err) {
2514
+ if (err.code !== "ENOENT") throw err;
2515
+ }
2516
+ }
2517
+ function unlinkIfSamePid(expectedPidStr, lp) {
2518
+ let current;
2519
+ try {
2520
+ current = readFileSync3(lp, "utf8").trim();
2521
+ } catch {
2522
+ return false;
2523
+ }
2524
+ if (current !== expectedPidStr) return false;
2525
+ try {
2526
+ unlinkSync(lp);
2527
+ return true;
2528
+ } catch {
2529
+ return false;
2530
+ }
2531
+ }
2532
+ function checkStaleAndRetry(verb, lp) {
2533
+ let pidStr;
2534
+ try {
2535
+ pidStr = readFileSync3(lp, "utf8").trim();
2536
+ } catch {
2537
+ pidStr = "";
2538
+ }
2539
+ const pid = Number.parseInt(pidStr, 10);
2540
+ if (!Number.isFinite(pid) || pid <= 0) {
2541
+ if (unlinkIfSamePid(pidStr, lp)) return retryOnce(verb, lp);
2542
+ warn(`another nomad ${verb} running, skipping`);
2543
+ return null;
2544
+ }
2545
+ try {
2546
+ process.kill(pid, 0);
2547
+ warn(`another nomad ${verb} running, skipping`);
2548
+ return null;
2549
+ } catch (err) {
2550
+ const code = err.code;
2551
+ if (code === "ESRCH") {
2552
+ if (unlinkIfSamePid(pidStr, lp)) return retryOnce(verb, lp);
2553
+ warn(`another nomad ${verb} running, skipping`);
2554
+ return null;
2555
+ }
2556
+ warn(`another nomad ${verb} running, skipping`);
2557
+ return null;
2558
+ }
2559
+ }
2560
+ function retryOnce(verb, lp) {
2561
+ try {
2562
+ const fd = openSync2(lp, "wx");
2563
+ try {
2564
+ writeFileSync2(fd, String(process.pid));
2565
+ } catch {
2566
+ try {
2567
+ closeSync2(fd);
2568
+ } catch {
2569
+ }
2570
+ try {
2571
+ unlinkSync(lp);
2572
+ } catch {
2573
+ }
2574
+ warn(`another nomad ${verb} running, skipping`);
2575
+ return null;
2576
+ }
2577
+ return { fd, path: lp };
2578
+ } catch {
2579
+ warn(`another nomad ${verb} running, skipping`);
2580
+ return null;
2581
+ }
2582
+ }
2583
+
2584
+ // src/commands.adopt.ts
2585
+ init_utils_json();
2586
+ var ADOPT_PUSH_HINT = "run `nomad push` to share with other hosts";
2587
+ function readMapIfPresent(repoHome2) {
2588
+ const mapPath = join9(repoHome2, "path-map.json");
2589
+ return existsSync5(mapPath) ? readPathMap(mapPath) : { projects: {} };
2590
+ }
2591
+ function isConfiguredTarget(name, map) {
2592
+ return SHARED_LINKS.includes(name) || sharedDirEntries(map).includes(name);
2593
+ }
2594
+ function isValidAdoptName(name) {
2595
+ if (SHARED_LINKS.includes(name)) return true;
2596
+ return isValidSharedDir(name);
2597
+ }
2598
+ function reportWin32AlreadyAdopted(name, sharedTarget) {
2599
+ if (process.platform !== "win32" || !lexists(sharedTarget)) return false;
2600
+ log(`${name}: already adopted (win32 copy-sync); run \`nomad pull\` to refresh the local copy`);
2601
+ return true;
2602
+ }
2603
+ function adoptStopsEarly(name, linkPath, sharedTarget) {
2604
+ if (!existsSync5(linkPath)) {
2605
+ if (reportWin32AlreadyAdopted(name, sharedTarget)) return true;
2606
+ log(`${name}: nothing to adopt (not present in ~/.claude/)`);
2607
+ return true;
2608
+ }
2609
+ if (lstatSync7(linkPath).isSymbolicLink()) {
2610
+ log(`${name}: already adopted (already a symlink)`);
2611
+ return true;
2612
+ }
2613
+ if (reportWin32AlreadyAdopted(name, sharedTarget)) return true;
2614
+ if (lexists(sharedTarget)) {
2615
+ fail(`${name}: shared/${name} already exists; would clobber. Remove it first.`);
2616
+ process.exit(1);
2617
+ }
2618
+ return false;
2619
+ }
2620
+ function performAdoptMove(name, linkPath, sharedTarget, repo, backup) {
2621
+ const ts = freshBackupTs(backup);
2622
+ const snapshotted = backupBeforeWrite(linkPath, ts);
2623
+ const rel = join9("shared", name);
2624
+ const stage = () => gitOrFatal(["add", "--", rel], `git add shared/${name}`, repo);
2625
+ copyIntoSharedOrFatal(name, linkPath, sharedTarget, repo);
2626
+ refuseLateDeniedEntries(name, linkPath, sharedTarget, repo, { snapshotted, ts });
2627
+ const removal = removeAdoptSource(linkPath);
2628
+ if (!removal.ok) reportSourceRemovalFailure(name, linkPath, removal.message, stage);
2629
+ if (process.platform === "win32") {
2630
+ restoreWin32LocalCopy(name, linkPath, sharedTarget, ts, stage, {
2631
+ snapshotted,
2632
+ sourceRemoved: removal.ok
2633
+ });
2634
+ } else {
2635
+ ensureSymlink(linkPath, sharedTarget);
2636
+ }
2637
+ stage();
2638
+ log(`adopted ${name}; ${ADOPT_PUSH_HINT}`);
2639
+ }
2640
+ function cmdAdopt(name, opts = {}) {
2641
+ const dryRun = opts.dryRun === true;
2642
+ const rejection = validateSharedDirEntry(name);
2643
+ if (rejection !== null && rejection.reason === "secret-shaped") {
2644
+ throw new NomadFatal(
2645
+ `cannot adopt ${JSON.stringify(name)}: ${rejection.message}. If it is listed in sharedDirs in path-map.json, remove it there too.`,
2646
+ { code: EXIT.GENERIC_FAILURE }
2647
+ );
2648
+ }
2649
+ if (!isValidAdoptName(name)) {
2650
+ fail(`invalid name: ${JSON.stringify(name)}`);
2651
+ process.exit(1);
2251
2652
  }
2252
2653
  const repo = repoHome();
2253
2654
  const claude = claudeHome();
@@ -2259,24 +2660,23 @@ function cmdAdopt(name, opts = {}) {
2259
2660
  );
2260
2661
  process.exit(1);
2261
2662
  }
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`);
2663
+ const linkPath = join9(claude, name);
2664
+ const sharedTarget = join9(repo, "shared", name);
2665
+ if (dryRun) {
2666
+ adoptPreflightAndMove(name, linkPath, sharedTarget, repo, backup, true);
2274
2667
  return;
2275
2668
  }
2276
- if (lexists(sharedTarget)) {
2277
- fail(`${name}: shared/${name} already exists; would clobber. Remove it first.`);
2278
- process.exit(1);
2669
+ const handle = acquireLock("adopt");
2670
+ if (handle === null) process.exit(EXIT.SUCCESS);
2671
+ try {
2672
+ adoptPreflightAndMove(name, linkPath, sharedTarget, repo, backup, false);
2673
+ } finally {
2674
+ releaseLock(handle);
2279
2675
  }
2676
+ }
2677
+ function adoptPreflightAndMove(name, linkPath, sharedTarget, repo, backup, dryRun) {
2678
+ if (adoptStopsEarly(name, linkPath, sharedTarget)) return;
2679
+ refuseDeniedEntries(name, linkPath);
2280
2680
  if (dryRun) {
2281
2681
  const ts = freshBackupTs(backup);
2282
2682
  log(`would backup: ${linkPath} -> backup/${ts}/${name}`);
@@ -2301,8 +2701,8 @@ init_config();
2301
2701
  import { existsSync as existsSync6 } from "node:fs";
2302
2702
 
2303
2703
  // src/commands.redact.core.ts
2304
- import { appendFileSync, readFileSync as readFileSync3 } from "node:fs";
2305
- import { join as join7 } from "node:path";
2704
+ import { appendFileSync, readFileSync as readFileSync4 } from "node:fs";
2705
+ import { join as join10 } from "node:path";
2306
2706
  function collectMatchIntervals(content, findings) {
2307
2707
  const intervals = [];
2308
2708
  for (const f of findings) {
@@ -2340,182 +2740,67 @@ function applyRedactions(content, findings) {
2340
2740
  const merged = mergeIntervals(raw);
2341
2741
  let result = content;
2342
2742
  for (let i = merged.length - 1; i >= 0; i--) {
2343
- const { start, end, ruleId } = merged[i];
2344
- result = result.slice(0, start) + `[REDACTED:${ruleId}]` + result.slice(end);
2345
- }
2346
- return result;
2347
- }
2348
- function formatFingerprint(fingerprint) {
2349
- return fingerprint.replace(/[\r\n]/g, "") + "\n";
2350
- }
2351
- function isRecentlyModified(mtimeMs, nowMs, thresholdMs = 5 * 60 * 1e3) {
2352
- return nowMs - mtimeMs < thresholdMs;
2353
- }
2354
- function isValidFingerprint(fingerprint) {
2355
- return fingerprint.trim().length > 0 && fingerprint.length <= 512 && /^[^\r\n]+$/.test(fingerprint);
2356
- }
2357
- function isAlreadyPresent(line, lines) {
2358
- return lines.includes(line);
2359
- }
2360
- function appendGitleaksIgnore(fingerprint, repo) {
2361
- const sanitized = fingerprint.replace(/[\r\n]/g, "").trim();
2362
- if (sanitized.length === 0) return;
2363
- const ignPath = join7(repo, ".gitleaksignore");
2364
- let raw;
2365
- try {
2366
- raw = readFileSync3(ignPath, "utf8");
2367
- } catch {
2368
- raw = "";
2369
- }
2370
- const existing = raw.split("\n").filter((l) => l.length > 0);
2371
- if (isAlreadyPresent(sanitized, existing)) return;
2372
- const needsLeadingNewline = raw.length > 0 && !raw.endsWith("\n");
2373
- const prefix = needsLeadingNewline ? "\n" : "";
2374
- appendFileSync(ignPath, prefix + formatFingerprint(sanitized), "utf8");
2375
- }
2376
-
2377
- // src/commands.allow.ts
2378
- init_exit_codes();
2379
- init_utils();
2380
- function cmdAllow(fingerprints) {
2381
- const repo = repoHome();
2382
- if (!existsSync6(repo)) die(`repo not cloned at ${repo}`);
2383
- for (const fp of fingerprints) {
2384
- if (!isValidFingerprint(fp)) {
2385
- const shown = fp.replaceAll("\r", String.raw`\r`).replaceAll("\n", String.raw`\n`);
2386
- fail(`invalid fingerprint: ${shown}`);
2387
- process.exit(EXIT.USAGE);
2388
- }
2389
- }
2390
- for (const fp of fingerprints) {
2391
- appendGitleaksIgnore(fp, repo);
2392
- item(`allowed: ${fp}`);
2393
- }
2394
- log(`allowed ${fingerprints.length} fingerprint(s)`);
2395
- }
2396
-
2397
- // src/commands.capture-settings.ts
2398
- init_config();
2399
- import { existsSync as existsSync7 } from "node:fs";
2400
- import { join as join9 } from "node:path";
2401
- import { createInterface } from "node:readline/promises";
2402
- init_utils_fs();
2403
- init_utils_json();
2404
-
2405
- // src/utils.lockfile.ts
2406
- init_config();
2407
- init_utils();
2408
- 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";
2410
- function lockFilePath() {
2411
- return join8(home(), ".cache", "claude-nomad", "nomad.lock");
2412
- }
2413
- function acquireLock(verb) {
2414
- const lp = lockFilePath();
2415
- mkdirSync2(dirname2(lp), { recursive: true });
2416
- try {
2417
- const fd = openSync2(lp, "wx");
2418
- try {
2419
- writeFileSync2(fd, String(process.pid));
2420
- } catch (writeErr) {
2421
- try {
2422
- closeSync2(fd);
2423
- } catch {
2424
- }
2425
- try {
2426
- unlinkSync(lp);
2427
- } catch {
2428
- }
2429
- throw writeErr;
2430
- }
2431
- return { fd, path: lp };
2432
- } catch (err) {
2433
- const code = err.code;
2434
- if (code !== "EEXIST") throw err;
2435
- return checkStaleAndRetry(verb, lp);
2436
- }
2437
- }
2438
- function releaseLock(handle) {
2439
- if (handle === null) return;
2440
- const lp = handle.path;
2441
- try {
2442
- closeSync2(handle.fd);
2443
- } catch {
2444
- }
2445
- try {
2446
- unlinkSync(lp);
2447
- } catch (err) {
2448
- if (err.code !== "ENOENT") throw err;
2449
- }
2450
- }
2451
- function unlinkIfSamePid(expectedPidStr, lp) {
2452
- let current;
2453
- try {
2454
- current = readFileSync4(lp, "utf8").trim();
2455
- } catch {
2456
- return false;
2457
- }
2458
- if (current !== expectedPidStr) return false;
2459
- try {
2460
- unlinkSync(lp);
2461
- return true;
2462
- } catch {
2463
- return false;
2743
+ const { start, end, ruleId } = merged[i];
2744
+ result = result.slice(0, start) + `[REDACTED:${ruleId}]` + result.slice(end);
2464
2745
  }
2746
+ return result;
2465
2747
  }
2466
- function checkStaleAndRetry(verb, lp) {
2467
- let pidStr;
2748
+ function formatFingerprint(fingerprint) {
2749
+ return fingerprint.replace(/[\r\n]/g, "") + "\n";
2750
+ }
2751
+ function isRecentlyModified(mtimeMs, nowMs, thresholdMs = 5 * 60 * 1e3) {
2752
+ return nowMs - mtimeMs < thresholdMs;
2753
+ }
2754
+ function isValidFingerprint(fingerprint) {
2755
+ return fingerprint.trim().length > 0 && fingerprint.length <= 512 && /^[^\r\n]+$/.test(fingerprint);
2756
+ }
2757
+ function isAlreadyPresent(line, lines) {
2758
+ return lines.includes(line);
2759
+ }
2760
+ function appendGitleaksIgnore(fingerprint, repo) {
2761
+ const sanitized = fingerprint.replace(/[\r\n]/g, "").trim();
2762
+ if (sanitized.length === 0) return;
2763
+ const ignPath = join10(repo, ".gitleaksignore");
2764
+ let raw;
2468
2765
  try {
2469
- pidStr = readFileSync4(lp, "utf8").trim();
2766
+ raw = readFileSync4(ignPath, "utf8");
2470
2767
  } catch {
2471
- pidStr = "";
2472
- }
2473
- const pid = Number.parseInt(pidStr, 10);
2474
- if (!Number.isFinite(pid) || pid <= 0) {
2475
- if (unlinkIfSamePid(pidStr, lp)) return retryOnce(verb, lp);
2476
- warn(`another nomad ${verb} running, skipping`);
2477
- return null;
2478
- }
2479
- try {
2480
- process.kill(pid, 0);
2481
- warn(`another nomad ${verb} running, skipping`);
2482
- return null;
2483
- } catch (err) {
2484
- const code = err.code;
2485
- if (code === "ESRCH") {
2486
- if (unlinkIfSamePid(pidStr, lp)) return retryOnce(verb, lp);
2487
- warn(`another nomad ${verb} running, skipping`);
2488
- return null;
2489
- }
2490
- warn(`another nomad ${verb} running, skipping`);
2491
- return null;
2768
+ raw = "";
2492
2769
  }
2770
+ const existing = raw.split("\n").filter((l) => l.length > 0);
2771
+ if (isAlreadyPresent(sanitized, existing)) return;
2772
+ const needsLeadingNewline = raw.length > 0 && !raw.endsWith("\n");
2773
+ const prefix = needsLeadingNewline ? "\n" : "";
2774
+ appendFileSync(ignPath, prefix + formatFingerprint(sanitized), "utf8");
2493
2775
  }
2494
- function retryOnce(verb, lp) {
2495
- try {
2496
- const fd = openSync2(lp, "wx");
2497
- try {
2498
- writeFileSync2(fd, String(process.pid));
2499
- } catch {
2500
- try {
2501
- closeSync2(fd);
2502
- } catch {
2503
- }
2504
- try {
2505
- unlinkSync(lp);
2506
- } catch {
2507
- }
2508
- warn(`another nomad ${verb} running, skipping`);
2509
- return null;
2776
+
2777
+ // src/commands.allow.ts
2778
+ init_exit_codes();
2779
+ init_utils();
2780
+ function cmdAllow(fingerprints) {
2781
+ const repo = repoHome();
2782
+ if (!existsSync6(repo)) die(`repo not cloned at ${repo}`);
2783
+ for (const fp of fingerprints) {
2784
+ if (!isValidFingerprint(fp)) {
2785
+ const shown = fp.replaceAll("\r", String.raw`\r`).replaceAll("\n", String.raw`\n`);
2786
+ fail(`invalid fingerprint: ${shown}`);
2787
+ process.exit(EXIT.USAGE);
2510
2788
  }
2511
- return { fd, path: lp };
2512
- } catch {
2513
- warn(`another nomad ${verb} running, skipping`);
2514
- return null;
2515
2789
  }
2790
+ for (const fp of fingerprints) {
2791
+ appendGitleaksIgnore(fp, repo);
2792
+ item(`allowed: ${fp}`);
2793
+ }
2794
+ log(`allowed ${fingerprints.length} fingerprint(s)`);
2516
2795
  }
2517
2796
 
2518
2797
  // src/commands.capture-settings.ts
2798
+ init_config();
2799
+ import { existsSync as existsSync7 } from "node:fs";
2800
+ import { join as join11 } from "node:path";
2801
+ import { createInterface } from "node:readline/promises";
2802
+ init_utils_fs();
2803
+ init_utils_json();
2519
2804
  init_utils();
2520
2805
  async function confirmCapture(destLabel, keys) {
2521
2806
  if (process.stdin.isTTY !== true || process.stdout.isTTY !== true) {
@@ -2534,7 +2819,7 @@ async function confirmCapture(destLabel, keys) {
2534
2819
  }
2535
2820
  }
2536
2821
  function resolveCaptureDestination(repo, useHost) {
2537
- const destPath = useHost ? join9(repo, "hosts", `${HOST}.json`) : join9(repo, "shared", "settings.base.json");
2822
+ const destPath = useHost ? join11(repo, "hosts", `${HOST}.json`) : join11(repo, "shared", "settings.base.json");
2538
2823
  const existing = existsSync7(destPath) ? readJson(destPath) : {};
2539
2824
  return { destPath, existing };
2540
2825
  }
@@ -2546,17 +2831,17 @@ async function cmdCaptureSettings(opts) {
2546
2831
  if (handle === null) process.exit(0);
2547
2832
  try {
2548
2833
  const claude = claudeHome();
2549
- const basePath = join9(repo, "shared", "settings.base.json");
2834
+ const basePath = join11(repo, "shared", "settings.base.json");
2550
2835
  if (!existsSync7(basePath)) {
2551
2836
  die("repo not initialized; run 'nomad init' to scaffold");
2552
2837
  }
2553
- const settingsPath = join9(claude, "settings.json");
2838
+ const settingsPath = join11(claude, "settings.json");
2554
2839
  if (!existsSync7(settingsPath)) {
2555
2840
  log("no ~/.claude/settings.json found; nothing to capture");
2556
2841
  return;
2557
2842
  }
2558
2843
  const base = readJson(basePath);
2559
- const hostPath = join9(repo, "hosts", `${HOST}.json`);
2844
+ const hostPath = join11(repo, "hosts", `${HOST}.json`);
2560
2845
  const overrides = existsSync7(hostPath) ? readJson(hostPath) : {};
2561
2846
  const merged = deepMerge(base, overrides);
2562
2847
  const settings = readJson(settingsPath);
@@ -2595,12 +2880,15 @@ async function cmdCaptureSettings(opts) {
2595
2880
  init_config();
2596
2881
  init_exit_codes();
2597
2882
  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";
2883
+ import { existsSync as existsSync8, lstatSync as lstatSync8, readdirSync as readdirSync4, rmSync as rmSync5, statSync as statSync2 } from "node:fs";
2884
+ import { join as join12 } from "node:path";
2600
2885
  var TS_SHAPE = /^\d{8}-\d{6}(-\d+)?$/;
2601
2886
  var DURATION_RE = /^(\d+)([dhm])$/;
2602
2887
  var UNIT_MS = { d: 864e5, h: 36e5, m: 6e4 };
2603
2888
  var CLEAN_DEFAULT_OLDER_THAN_MS = 14 * 24 * 60 * 60 * 1e3;
2889
+ function nameOf(d) {
2890
+ return d.name;
2891
+ }
2604
2892
  function isTsDir(name) {
2605
2893
  return TS_SHAPE.test(name);
2606
2894
  }
@@ -2611,7 +2899,20 @@ function parseDuration(s) {
2611
2899
  }
2612
2900
  function listBackupDirs(backupBase2) {
2613
2901
  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);
2902
+ return readdirSync4(backupBase2).filter(isTsDir).map((name) => ({ name, mtimeMs: statSync2(join12(backupBase2, name)).mtimeMs })).sort((a, b) => b.mtimeMs - a.mtimeMs);
2903
+ }
2904
+ function holdsNoContent(dir) {
2905
+ let entries;
2906
+ try {
2907
+ entries = readdirSync4(dir, { withFileTypes: true });
2908
+ } catch {
2909
+ return false;
2910
+ }
2911
+ for (const entry of entries) {
2912
+ if (!entry.isDirectory()) return false;
2913
+ if (!holdsNoContent(join12(dir, entry.name))) return false;
2914
+ }
2915
+ return true;
2615
2916
  }
2616
2917
  function prunableByAge(dirs, olderThanMs, nowMs) {
2617
2918
  return dirs.filter((d) => nowMs - d.mtimeMs > olderThanMs).map((d) => d.name);
@@ -2621,8 +2922,8 @@ function prunableByCount(dirs, keep) {
2621
2922
  }
2622
2923
  function safeDelete(backupBase2, name) {
2623
2924
  if (!isTsDir(name)) return;
2624
- const full = join10(backupBase2, name);
2625
- const st = lstatSync6(full, { throwIfNoEntry: false });
2925
+ const full = join12(backupBase2, name);
2926
+ const st = lstatSync8(full, { throwIfNoEntry: false });
2626
2927
  if (!st || st.isSymbolicLink() || !st.isDirectory()) return;
2627
2928
  rmSync5(full, { recursive: true, force: true });
2628
2929
  }
@@ -2645,8 +2946,30 @@ function cmdClean(opts, backupBase2 = backupBase()) {
2645
2946
  }
2646
2947
  olderThanMs = parsed;
2647
2948
  }
2949
+ if (dryRun) {
2950
+ pruneBackups({ dryRun, olderThanMs, keep }, backupBase2);
2951
+ return;
2952
+ }
2953
+ const handle = acquireLock("clean");
2954
+ if (handle === null) process.exit(EXIT.SUCCESS);
2955
+ try {
2956
+ pruneBackups({ dryRun, olderThanMs, keep }, backupBase2);
2957
+ } finally {
2958
+ releaseLock(handle);
2959
+ }
2960
+ }
2961
+ function pruneBackups(opts, backupBase2) {
2962
+ const { dryRun, olderThanMs, keep } = opts;
2648
2963
  const dirs = listBackupDirs(backupBase2);
2649
- const targets = resolveTargets(dirs, olderThanMs, keep);
2964
+ const empty = new Set(dirs.filter((d) => holdsNoContent(join12(backupBase2, d.name))).map(nameOf));
2965
+ const aged = new Set(
2966
+ resolveTargets(
2967
+ dirs.filter((d) => !empty.has(d.name)),
2968
+ olderThanMs,
2969
+ keep
2970
+ )
2971
+ );
2972
+ const targets = dirs.filter((d) => empty.has(d.name) || aged.has(d.name)).map(nameOf);
2650
2973
  if (dryRun) {
2651
2974
  for (const name of targets) item(name);
2652
2975
  log(`dry-run: ${targets.length} backup(s) would be removed`);
@@ -2662,8 +2985,8 @@ init_config_sharedDirs_guard();
2662
2985
  init_utils();
2663
2986
  init_utils_fs();
2664
2987
  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";
2988
+ import { cpSync as cpSync4, existsSync as existsSync9, lstatSync as lstatSync9, realpathSync, rmSync as rmSync6 } from "node:fs";
2989
+ import { join as join13, sep as sep3 } from "node:path";
2667
2990
  function ejectChecklist() {
2668
2991
  return [
2669
2992
  "Manual steps remaining to finish leaving claude-nomad on this host:",
@@ -2679,28 +3002,28 @@ function errMessage(err) {
2679
3002
  }
2680
3003
  function lexists2(p) {
2681
3004
  try {
2682
- lstatSync7(p);
3005
+ lstatSync9(p);
2683
3006
  return true;
2684
3007
  } catch {
2685
3008
  return false;
2686
3009
  }
2687
3010
  }
2688
3011
  function readMapIfPresent2(repoHome2) {
2689
- const mapPath = join11(repoHome2, "path-map.json");
3012
+ const mapPath = join13(repoHome2, "path-map.json");
2690
3013
  return existsSync9(mapPath) ? readPathMap(mapPath) : { projects: {} };
2691
3014
  }
2692
3015
  function classifyName(linkPath) {
2693
3016
  if (!lexists2(linkPath)) return "absent";
2694
- if (!lstatSync7(linkPath).isSymbolicLink()) return "skip-real";
3017
+ if (!lstatSync9(linkPath).isSymbolicLink()) return "skip-real";
2695
3018
  if (!existsSync9(linkPath)) return "dangling";
2696
3019
  return "materialize";
2697
3020
  }
2698
3021
  function resolveSharedRoot(repoHome2) {
2699
3022
  try {
2700
- return realpathSync(join11(repoHome2, "shared"));
3023
+ return realpathSync(join13(repoHome2, "shared"));
2701
3024
  } catch {
2702
3025
  return die(
2703
- `cannot resolve ${join11(repoHome2, "shared")} (repo checkout incomplete). run \`nomad pull\` first, then re-run \`nomad eject\``
3026
+ `cannot resolve ${join13(repoHome2, "shared")} (repo checkout incomplete). run \`nomad pull\` first, then re-run \`nomad eject\``
2704
3027
  );
2705
3028
  }
2706
3029
  }
@@ -2716,7 +3039,7 @@ function materializeOne(name, linkPath, sharedRoot) {
2716
3039
  const tmp = `${linkPath}.eject.tmp.${process.pid}.${Date.now()}`;
2717
3040
  try {
2718
3041
  rmSync6(tmp, { recursive: true, force: true });
2719
- cpSync5(target, tmp, {
3042
+ cpSync4(target, tmp, {
2720
3043
  recursive: true,
2721
3044
  force: true,
2722
3045
  dereference: true,
@@ -2740,7 +3063,7 @@ function skipRealMessage(name) {
2740
3063
  function previewDryRun(names, classifications, claudeHome2, sharedRoot) {
2741
3064
  for (const name of names) {
2742
3065
  const cls = classifications.get(name);
2743
- const linkPath = join11(claudeHome2, name);
3066
+ const linkPath = join13(claudeHome2, name);
2744
3067
  if (cls === "absent") {
2745
3068
  item(`skipped (absent): ${name}`);
2746
3069
  } else if (cls === "skip-real") {
@@ -2770,7 +3093,7 @@ function runLiveEject(names, classifications, claudeHome2, sharedRoot) {
2770
3093
  let skipped = 0;
2771
3094
  for (const name of names) {
2772
3095
  const cls = classifications.get(name);
2773
- const linkPath = join11(claudeHome2, name);
3096
+ const linkPath = join13(claudeHome2, name);
2774
3097
  if (cls === "absent") {
2775
3098
  item(`skipped (absent): ${name}`);
2776
3099
  skipped++;
@@ -2819,7 +3142,7 @@ function cmdEject(opts = {}, roots = defaultEjectRoots()) {
2819
3142
  const names = ejectNames(map);
2820
3143
  const classifications = /* @__PURE__ */ new Map();
2821
3144
  for (const name of names) {
2822
- classifications.set(name, classifyName(join11(claudeHome2, name)));
3145
+ classifications.set(name, classifyName(join13(claudeHome2, name)));
2823
3146
  }
2824
3147
  const base = new Set(allSharedLinks(map, { quiet: true }));
2825
3148
  for (const name of names) {
@@ -2857,14 +3180,14 @@ function cmdEject(opts = {}, roots = defaultEjectRoots()) {
2857
3180
  }
2858
3181
 
2859
3182
  // src/commands.doctor.ts
2860
- import { existsSync as existsSync35 } from "node:fs";
2861
- import { join as join45 } from "node:path";
3183
+ import { existsSync as existsSync36 } from "node:fs";
3184
+ import { join as join48 } from "node:path";
2862
3185
 
2863
3186
  // src/commands.doctor.checks.repo.ts
2864
3187
  init_color();
2865
3188
  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";
3189
+ import { existsSync as existsSync12, lstatSync as lstatSync10, readdirSync as readdirSync5, statSync as statSync3 } from "node:fs";
3190
+ import { join as join16 } from "node:path";
2868
3191
 
2869
3192
  // src/commands.doctor.format.ts
2870
3193
  init_color();
@@ -2939,10 +3262,16 @@ function readJsonSafe(path, label, section2) {
2939
3262
  }
2940
3263
  }
2941
3264
 
3265
+ // src/commands.doctor.checks.repo.win32.ts
3266
+ init_color();
3267
+ init_config();
3268
+ import { existsSync as existsSync10 } from "node:fs";
3269
+ import { join as join14, win32 as win32Path } from "node:path";
3270
+
2942
3271
  // src/extras-sync.diff.ts
2943
3272
  init_utils();
2944
3273
  import { execFileSync as execFileSync2 } from "node:child_process";
2945
- import { relative as relative3 } from "node:path";
3274
+ import { relative as relative4 } from "node:path";
2946
3275
  var NAME_STATUS_DIFF_TIMEOUT_MS = 3e3;
2947
3276
  function parseNameStatus(stdout) {
2948
3277
  const fields = stdout.split("\0").filter((f) => f.length > 0);
@@ -2961,7 +3290,7 @@ function parseDiffOutput(stdout) {
2961
3290
  return parseNameStatus(stdout).map(labelEntry);
2962
3291
  }
2963
3292
  function parseModifiedPaths(stdout, a) {
2964
- return parseNameStatus(stdout).filter((entry) => entry.status === "M").map((entry) => relative3(a, entry.path));
3293
+ return parseNameStatus(stdout).filter((entry) => entry.status === "M").map((entry) => relative4(a, entry.path));
2965
3294
  }
2966
3295
  function runNameStatusDiff(a, b, parse) {
2967
3296
  try {
@@ -2994,18 +3323,56 @@ function listDivergingModified(a, b) {
2994
3323
  return runNameStatusDiff(a, b, (stdout) => parseModifiedPaths(stdout, a));
2995
3324
  }
2996
3325
 
3326
+ // src/commands.doctor.checks.repo.win32.ts
3327
+ function win32CopyOkRow(name, exempt = 0) {
3328
+ const note = exempt > 0 ? dim(` (${exempt} never-synced path(s) not compared)`) : "";
3329
+ return { line: `${green(okGlyph)} ${name}: real copy (win32 copy-sync)${note}`, fail: false };
3330
+ }
3331
+ function win32CopyUnpublishedRow(name) {
3332
+ return {
3333
+ line: `${dim(infoGlyph)} ${name}: real local copy, not published (run \`nomad adopt ${name}\` to share it)`,
3334
+ fail: false
3335
+ };
3336
+ }
3337
+ function relativeUnder(root, path) {
3338
+ const rel = win32Path.relative(root, path);
3339
+ const segments = rel.split(win32Path.sep);
3340
+ if (rel === "" || segments[0] === ".." || win32Path.isAbsolute(rel)) return null;
3341
+ return segments.join("/");
3342
+ }
3343
+ function divergingRepoPath(line, name, local, shared) {
3344
+ const path = line.replace(/ \((?:local|repo) only\)$/, "");
3345
+ const rel = relativeUnder(local, path) ?? relativeUnder(shared, path);
3346
+ return rel === null ? `shared/${name}` : `shared/${name}/${rel}`;
3347
+ }
3348
+ function classifyWin32Copy(name, p) {
3349
+ const sharedPath = join14(repoHome(), "shared", name);
3350
+ if (!existsSync10(sharedPath)) return win32CopyUnpublishedRow(name);
3351
+ const compared = listDivergingFiles(p, sharedPath);
3352
+ const diverging = compared.filter(
3353
+ (line) => deniedSegmentFor(divergingRepoPath(line, name, p, sharedPath)) === null
3354
+ );
3355
+ const exempt = compared.length - diverging.length;
3356
+ if (diverging.length === 0) return win32CopyOkRow(name, exempt);
3357
+ return {
3358
+ line: `${yellow(warnGlyph)} ${name}: ${diverging.length} file(s) diverge from shared/${name}`,
3359
+ fail: false,
3360
+ children: diverging
3361
+ };
3362
+ }
3363
+
2997
3364
  // src/init.classify.ts
2998
3365
  init_config();
2999
3366
  init_utils_json();
3000
- import { existsSync as existsSync10 } from "node:fs";
3001
- import { join as join12 } from "node:path";
3367
+ import { existsSync as existsSync11 } from "node:fs";
3368
+ import { join as join15 } from "node:path";
3002
3369
  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);
3370
+ const basePath = join15(repoHome2, "shared", "settings.base.json");
3371
+ const mapPath = join15(repoHome2, "path-map.json");
3372
+ const hostPath = join15(repoHome2, "hosts", `${host}.json`);
3373
+ const hasBase = existsSync11(basePath);
3374
+ const hasMap = existsSync11(mapPath);
3375
+ const hasHost = existsSync11(hostPath);
3009
3376
  let mapEntryCount = 0;
3010
3377
  if (hasMap) {
3011
3378
  try {
@@ -3020,11 +3387,11 @@ function classifyRepoState(repoHome2, host) {
3020
3387
  return "partial";
3021
3388
  }
3022
3389
  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";
3390
+ const basePath = join15(repoHome2, "shared", "settings.base.json");
3391
+ const mapPath = join15(repoHome2, "path-map.json");
3392
+ const hostPath = join15(repoHome2, "hosts", `${host}.json`);
3393
+ if (!existsSync11(basePath)) return "- shared/settings.base.json missing";
3394
+ if (!existsSync11(mapPath)) return "- path-map.json missing";
3028
3395
  let mapEntryCount;
3029
3396
  try {
3030
3397
  const map = readJson(mapPath);
@@ -3033,7 +3400,7 @@ function reasonForPartial(repoHome2, host) {
3033
3400
  mapEntryCount = 0;
3034
3401
  }
3035
3402
  if (mapEntryCount === 0) return "- path-map.json.projects has no entries";
3036
- if (!existsSync10(hostPath)) return `- hosts/${host}.json missing`;
3403
+ if (!existsSync11(hostPath)) return `- hosts/${host}.json missing`;
3037
3404
  return "- partial state (unknown gap)";
3038
3405
  }
3039
3406
 
@@ -3050,15 +3417,15 @@ function reportHostAndPaths(section2) {
3050
3417
  if (isOverrideActive()) {
3051
3418
  addItem(section2, `${dim(infoGlyph)} NOMAD_REPO: ${blue(repo)}`);
3052
3419
  }
3053
- addItem(section2, `${existsSync11(repo) ? green(okGlyph) : yellow(warnGlyph)} repo: ${blue(repo)}`);
3420
+ addItem(section2, `${existsSync12(repo) ? green(okGlyph) : yellow(warnGlyph)} repo: ${blue(repo)}`);
3054
3421
  addItem(
3055
3422
  section2,
3056
- `${existsSync11(claude) ? green(okGlyph) : yellow(warnGlyph)} claude home: ${blue(claude)}`
3423
+ `${existsSync12(claude) ? green(okGlyph) : yellow(warnGlyph)} claude home: ${blue(claude)}`
3057
3424
  );
3058
3425
  }
3059
3426
  function pathMapHostKeys() {
3060
- const mapPath = join13(repoHome(), "path-map.json");
3061
- if (!existsSync11(mapPath)) return /* @__PURE__ */ new Set();
3427
+ const mapPath = join16(repoHome(), "path-map.json");
3428
+ if (!existsSync12(mapPath)) return /* @__PURE__ */ new Set();
3062
3429
  let raw;
3063
3430
  try {
3064
3431
  raw = readJson(mapPath);
@@ -3075,7 +3442,7 @@ function pathMapHostKeys() {
3075
3442
  function hostOverrideLabels() {
3076
3443
  let entries;
3077
3444
  try {
3078
- entries = readdirSync4(join13(repoHome(), "hosts"));
3445
+ entries = readdirSync5(join16(repoHome(), "hosts"));
3079
3446
  } catch {
3080
3447
  return /* @__PURE__ */ new Set();
3081
3448
  }
@@ -3116,42 +3483,12 @@ function reportRepoState(section2) {
3116
3483
  }
3117
3484
  }
3118
3485
  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
- };
3486
+ return existsSync12(join16(repoHome(), "shared", name));
3150
3487
  }
3151
3488
  function classifySharedLink(name, p) {
3152
3489
  let stat;
3153
3490
  try {
3154
- stat = lstatSync8(p);
3491
+ stat = lstatSync10(p);
3155
3492
  } catch (err) {
3156
3493
  const code = err.code;
3157
3494
  if (code === "ENOENT") {
@@ -3197,7 +3534,7 @@ function classifySymlinkTarget(name, p) {
3197
3534
  function reportSharedLinks(section2, map) {
3198
3535
  const claude = claudeHome();
3199
3536
  for (const name of allSharedLinks(map, { quiet: true })) {
3200
- const p = join13(claude, name);
3537
+ const p = join16(claude, name);
3201
3538
  const { line, fail: fail2, children } = classifySharedLink(name, p);
3202
3539
  addItem(section2, line);
3203
3540
  if (fail2) process.exitCode = 1;
@@ -3209,10 +3546,10 @@ function reportSharedLinks(section2, map) {
3209
3546
  function reportDroppedNamesMigration(section2) {
3210
3547
  const claude = claudeHome();
3211
3548
  for (const name of GSD_DROPPED_NAMES) {
3212
- const p = join13(claude, name);
3549
+ const p = join16(claude, name);
3213
3550
  let stat;
3214
3551
  try {
3215
- stat = lstatSync8(p);
3552
+ stat = lstatSync10(p);
3216
3553
  } catch {
3217
3554
  continue;
3218
3555
  }
@@ -3227,11 +3564,11 @@ function reportDroppedNamesMigration(section2) {
3227
3564
  // src/commands.doctor.checks.settings.ts
3228
3565
  init_color();
3229
3566
  init_config();
3230
- import { existsSync as existsSync12, readdirSync as readdirSync5 } from "node:fs";
3231
- import { join as join14 } from "node:path";
3567
+ import { existsSync as existsSync13, readdirSync as readdirSync6 } from "node:fs";
3568
+ import { join as join17 } from "node:path";
3232
3569
  function loadBaseSettings(section2) {
3233
- const basePath = join14(repoHome(), "shared", "settings.base.json");
3234
- if (!existsSync12(basePath)) {
3570
+ const basePath = join17(repoHome(), "shared", "settings.base.json");
3571
+ if (!existsSync13(basePath)) {
3235
3572
  addItem(section2, `${red(failGlyph)} shared/settings.base.json missing at ${blue(basePath)}`);
3236
3573
  process.exitCode = 1;
3237
3574
  return null;
@@ -3239,8 +3576,8 @@ function loadBaseSettings(section2) {
3239
3576
  return readJsonSafe(basePath, basePath, section2);
3240
3577
  }
3241
3578
  function loadAndReportSettings(section2) {
3242
- const settingsPath = join14(claudeHome(), "settings.json");
3243
- if (!existsSync12(settingsPath)) return null;
3579
+ const settingsPath = join17(claudeHome(), "settings.json");
3580
+ if (!existsSync13(settingsPath)) return null;
3244
3581
  const settings = readJsonSafe(settingsPath, settingsPath, section2);
3245
3582
  if (settings === null) return null;
3246
3583
  const unknownKeys = Object.keys(settings).filter((k) => !KNOWN_SETTINGS_KEYS.has(k));
@@ -3256,13 +3593,13 @@ function loadAndReportSettings(section2) {
3256
3593
  }
3257
3594
  function reportHostOverrides(section2, base, settings) {
3258
3595
  const repo = repoHome();
3259
- const hostFile = join14(repo, "hosts", `${HOST}.json`);
3596
+ const hostFile = join17(repo, "hosts", `${HOST}.json`);
3260
3597
  let drift = [];
3261
3598
  if (base !== null && settings !== null) {
3262
3599
  const baseKeys = new Set(Object.keys(base));
3263
3600
  drift = Object.keys(settings).filter((k) => !baseKeys.has(k));
3264
3601
  }
3265
- if (existsSync12(hostFile)) {
3602
+ if (existsSync13(hostFile)) {
3266
3603
  if (readJsonSafe(hostFile, hostFile, section2) !== null) {
3267
3604
  addItem(section2, `${green(okGlyph)} host overrides: ${blue(hostFile)}`);
3268
3605
  }
@@ -3271,9 +3608,9 @@ function reportHostOverrides(section2, base, settings) {
3271
3608
  section2,
3272
3609
  `${red(failGlyph)} no hosts/${HOST}.json AND settings.json has unbased keys ${JSON.stringify(drift)}`
3273
3610
  );
3274
- const hostsDir = join14(repo, "hosts");
3275
- if (existsSync12(hostsDir)) {
3276
- const cands = readdirSync5(hostsDir).filter((f) => f.endsWith(".json"));
3611
+ const hostsDir = join17(repo, "hosts");
3612
+ if (existsSync13(hostsDir)) {
3613
+ const cands = readdirSync6(hostsDir).filter((f) => f.endsWith(".json"));
3277
3614
  if (cands.length > 0) addItem(section2, `${dim(infoGlyph)} candidates: ${cands.join(", ")}`);
3278
3615
  }
3279
3616
  process.exitCode = 1;
@@ -3289,8 +3626,8 @@ function reportHostOverrides(section2, base, settings) {
3289
3626
  init_color();
3290
3627
  init_config();
3291
3628
  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";
3629
+ import { existsSync as existsSync14, lstatSync as lstatSync11, readdirSync as readdirSync7, realpathSync as realpathSync2 } from "node:fs";
3630
+ import { join as join18, sep as sep4 } from "node:path";
3294
3631
  init_utils_json();
3295
3632
  function reportMappedProjects(section2, map) {
3296
3633
  const mapped = Object.entries(map.projects).filter(([, hosts]) => hosts[HOST]);
@@ -3300,11 +3637,11 @@ function reportMappedProjects(section2, map) {
3300
3637
  }
3301
3638
  }
3302
3639
  function reportUnmappedProjects(section2, map) {
3303
- const localProjects = join15(claudeHome(), "projects");
3304
- if (!existsSync13(localProjects)) return;
3640
+ const localProjects = join18(claudeHome(), "projects");
3641
+ if (!existsSync14(localProjects)) return;
3305
3642
  let localDirs;
3306
3643
  try {
3307
- localDirs = readdirSync6(localProjects);
3644
+ localDirs = readdirSync7(localProjects);
3308
3645
  } catch {
3309
3646
  return;
3310
3647
  }
@@ -3322,7 +3659,7 @@ function reportCurrentHostPathsMissing(section2, map) {
3322
3659
  for (const [name, hosts] of Object.entries(map.projects)) {
3323
3660
  const abspath = hosts[HOST];
3324
3661
  if (!abspath || abspath === "TBD") continue;
3325
- if (!existsSync13(abspath)) {
3662
+ if (!existsSync14(abspath)) {
3326
3663
  addItem(
3327
3664
  section2,
3328
3665
  `${yellow(warnGlyph)} path-map: ${name} local path missing on ${HOST}: ${blue(abspath)}`
@@ -3332,7 +3669,7 @@ function reportCurrentHostPathsMissing(section2, map) {
3332
3669
  }
3333
3670
  function hasSharedLeftover(entry) {
3334
3671
  try {
3335
- return lstatSync9(join15(repoHome(), "shared", entry), { throwIfNoEntry: false }) !== void 0;
3672
+ return lstatSync11(join18(repoHome(), "shared", entry), { throwIfNoEntry: false }) !== void 0;
3336
3673
  } catch {
3337
3674
  return false;
3338
3675
  }
@@ -3342,10 +3679,10 @@ var PROBEABLE_REASONS = /* @__PURE__ */ new Set([
3342
3679
  "secret-shaped"
3343
3680
  ]);
3344
3681
  function classifyLocalLink(entry) {
3345
- const linkPath = join15(claudeHome(), entry);
3682
+ const linkPath = join18(claudeHome(), entry);
3346
3683
  let stat;
3347
3684
  try {
3348
- stat = lstatSync9(linkPath, { throwIfNoEntry: false });
3685
+ stat = lstatSync11(linkPath, { throwIfNoEntry: false });
3349
3686
  } catch {
3350
3687
  return "absent";
3351
3688
  }
@@ -3358,7 +3695,7 @@ function classifyLocalLink(entry) {
3358
3695
  }
3359
3696
  let root;
3360
3697
  try {
3361
- root = realpathSync2(join15(repoHome(), "shared"));
3698
+ root = realpathSync2(join18(repoHome(), "shared"));
3362
3699
  } catch {
3363
3700
  return "foreign";
3364
3701
  }
@@ -3442,8 +3779,8 @@ function reportPathCollisions(section2, map) {
3442
3779
  else addItem(section2, `${green(okGlyph)} path-encoding: no collisions`);
3443
3780
  }
3444
3781
  function reportPathMap(section2) {
3445
- const mapPath = join15(repoHome(), "path-map.json");
3446
- if (!existsSync13(mapPath)) {
3782
+ const mapPath = join18(repoHome(), "path-map.json");
3783
+ if (!existsSync14(mapPath)) {
3447
3784
  addItem(section2, `${red(failGlyph)} path-map.json missing at ${blue(mapPath)}`);
3448
3785
  process.exitCode = 1;
3449
3786
  return;
@@ -3473,8 +3810,8 @@ function reportNeverSync(section2) {
3473
3810
 
3474
3811
  // src/commands.doctor.checks.skills.ts
3475
3812
  init_color();
3476
- import { existsSync as existsSync14 } from "node:fs";
3477
- import { join as join16 } from "node:path";
3813
+ import { existsSync as existsSync15 } from "node:fs";
3814
+ import { join as join19 } from "node:path";
3478
3815
  init_config();
3479
3816
  function stripSideIndicator(line) {
3480
3817
  if (line.endsWith(" (local only)")) return line.slice(0, -" (local only)".length);
@@ -3483,24 +3820,24 @@ function stripSideIndicator(line) {
3483
3820
  }
3484
3821
  function isGsdDiffLine(line, localBase, sharedBase) {
3485
3822
  const bare = stripSideIndicator(line);
3486
- let relative11;
3823
+ let relative12;
3487
3824
  if (bare.startsWith(localBase + "/")) {
3488
- relative11 = bare.slice(localBase.length + 1);
3825
+ relative12 = bare.slice(localBase.length + 1);
3489
3826
  } else if (bare.startsWith(sharedBase + "/")) {
3490
- relative11 = bare.slice(sharedBase.length + 1);
3827
+ relative12 = bare.slice(sharedBase.length + 1);
3491
3828
  } else {
3492
- relative11 = bare;
3829
+ relative12 = bare;
3493
3830
  }
3494
- return relative11.split("/")[0].startsWith(GSD_PREFIX);
3831
+ return relative12.split("/")[0].startsWith(GSD_PREFIX);
3495
3832
  }
3496
3833
  function reportSkillsDivergence(section2) {
3497
- const sharedSkills = join16(repoHome(), "shared", "skills");
3498
- const localSkills = join16(claudeHome(), "skills");
3499
- if (!existsSync14(sharedSkills)) {
3834
+ const sharedSkills = join19(repoHome(), "shared", "skills");
3835
+ const localSkills = join19(claudeHome(), "skills");
3836
+ if (!existsSync15(sharedSkills)) {
3500
3837
  addItem(section2, `${dim(infoGlyph)} skills: no shared/skills/ to compare`);
3501
3838
  return;
3502
3839
  }
3503
- if (!existsSync14(localSkills)) {
3840
+ if (!existsSync15(localSkills)) {
3504
3841
  addItem(section2, `${dim(infoGlyph)} skills: no local skills/ to compare`);
3505
3842
  return;
3506
3843
  }
@@ -3523,8 +3860,8 @@ function reportSkillsDivergence(section2) {
3523
3860
  init_color();
3524
3861
  init_config();
3525
3862
  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";
3863
+ import { existsSync as existsSync18 } from "node:fs";
3864
+ import { join as join23, relative as relative5 } from "node:path";
3528
3865
  init_commands_pull_wedge();
3529
3866
  init_push_checks();
3530
3867
  init_utils();
@@ -3547,11 +3884,11 @@ function reportGitleaksProbe(section2) {
3547
3884
  }
3548
3885
  function reportGitlinks(section2) {
3549
3886
  const repo = repoHome();
3550
- const sharedDir = join20(repo, "shared");
3551
- if (existsSync17(sharedDir)) {
3887
+ const sharedDir = join23(repo, "shared");
3888
+ if (existsSync18(sharedDir)) {
3552
3889
  const gitlinks = findGitlinks(sharedDir);
3553
3890
  for (const p of gitlinks) {
3554
- const rel = relative4(repo, p).replaceAll("\\", "/");
3891
+ const rel = relative5(repo, p).replaceAll("\\", "/");
3555
3892
  addItem(
3556
3893
  section2,
3557
3894
  `${red(failGlyph)} gitlink: ${blue(rel)} would push as submodule (run: rm -rf ${rel} or remove the nested repo)`
@@ -3646,13 +3983,13 @@ function reportGitIdentity(section2) {
3646
3983
 
3647
3984
  // src/commands.doctor.checks.backups.ts
3648
3985
  init_color();
3649
- import { existsSync as existsSync18, lstatSync as lstatSync10, readdirSync as readdirSync8 } from "node:fs";
3650
- import { join as join21 } from "node:path";
3986
+ import { existsSync as existsSync19, lstatSync as lstatSync12, readdirSync as readdirSync9 } from "node:fs";
3987
+ import { join as join24 } from "node:path";
3651
3988
  init_config();
3652
3989
  var TS_SHAPE2 = /^\d{8}-\d{6}(-\d+)?$/;
3653
3990
  function safeReaddir(dir) {
3654
3991
  try {
3655
- return readdirSync8(dir);
3992
+ return readdirSync9(dir);
3656
3993
  } catch {
3657
3994
  return [];
3658
3995
  }
@@ -3663,8 +4000,8 @@ var BYTES_PER_MB = 1024 * 1024;
3663
4000
  function dirSizeBytes(dir) {
3664
4001
  let bytes = 0;
3665
4002
  for (const entry of safeReaddir(dir)) {
3666
- const full = join21(dir, entry);
3667
- const st = lstatSync10(full, { throwIfNoEntry: false });
4003
+ const full = join24(dir, entry);
4004
+ const st = lstatSync12(full, { throwIfNoEntry: false });
3668
4005
  if (!st) continue;
3669
4006
  if (st.isSymbolicLink()) continue;
3670
4007
  if (st.isDirectory()) bytes += dirSizeBytes(full);
@@ -3674,18 +4011,18 @@ function dirSizeBytes(dir) {
3674
4011
  }
3675
4012
  function totalSizeMb(backupBase2, dirs) {
3676
4013
  let bytes = 0;
3677
- for (const name of dirs) bytes += dirSizeBytes(join21(backupBase2, name));
4014
+ for (const name of dirs) bytes += dirSizeBytes(join24(backupBase2, name));
3678
4015
  return bytes / BYTES_PER_MB;
3679
4016
  }
3680
4017
  function reportBackupsCheck(section2, backupBase2 = backupBase()) {
3681
- if (!existsSync18(backupBase2)) return;
4018
+ if (!existsSync19(backupBase2)) return;
3682
4019
  const dirs = safeReaddir(backupBase2).filter((n) => TS_SHAPE2.test(n));
3683
4020
  const count = dirs.length;
3684
4021
  const sizeMb = totalSizeMb(backupBase2, dirs);
3685
4022
  if (count > DOCTOR_BACKUP_COUNT_WARN || sizeMb > DOCTOR_BACKUP_SIZE_WARN_MB) {
3686
4023
  addItem(
3687
4024
  section2,
3688
- `${yellow(warnGlyph)} backups: ${count} dirs / ${sizeMb.toFixed(1)} MB (run 'nomad clean --backups --keep <N>'; bare --backups only prunes dirs older than 14d)`
4025
+ `${yellow(warnGlyph)} backups: ${count} dirs / ${sizeMb.toFixed(1)} MB (run 'nomad clean --backups --keep <N>'; bare --backups prunes empty dirs plus those older than 14d)`
3689
4026
  );
3690
4027
  }
3691
4028
  }
@@ -3759,8 +4096,8 @@ function reportCheckRemote(section2) {
3759
4096
 
3760
4097
  // src/commands.doctor.check-schema.ts
3761
4098
  init_color();
3762
- import { existsSync as existsSync19 } from "node:fs";
3763
- import { join as join22 } from "node:path";
4099
+ import { existsSync as existsSync20 } from "node:fs";
4100
+ import { join as join25 } from "node:path";
3764
4101
  init_config();
3765
4102
 
3766
4103
  // src/http-fetch.ts
@@ -3797,8 +4134,8 @@ function fetchSchemaKeys() {
3797
4134
  }
3798
4135
  }
3799
4136
  function reportCheckSchema(section2) {
3800
- const settingsPath = join22(claudeHome(), "settings.json");
3801
- if (!existsSync19(settingsPath)) {
4137
+ const settingsPath = join25(claudeHome(), "settings.json");
4138
+ if (!existsSync20(settingsPath)) {
3802
4139
  addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json to check`);
3803
4140
  return;
3804
4141
  }
@@ -3828,9 +4165,9 @@ function reportCheckSchema(section2) {
3828
4165
  init_color();
3829
4166
  import { randomBytes as randomBytes3 } from "node:crypto";
3830
4167
  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";
4168
+ import { existsSync as existsSync23, mkdirSync as mkdirSync8, readdirSync as readdirSync12, rmSync as rmSync12 } from "node:fs";
3832
4169
  import { homedir as homedir6 } from "node:os";
3833
- import { join as join29 } from "node:path";
4170
+ import { join as join32 } from "node:path";
3834
4171
 
3835
4172
  // src/commands.doctor.check-shared.memory.ts
3836
4173
  init_color();
@@ -3838,7 +4175,7 @@ import { execFileSync as execFileSync9 } from "node:child_process";
3838
4175
  import { randomBytes } from "node:crypto";
3839
4176
  import { mkdirSync as mkdirSync4, rmSync as rmSync9, writeFileSync as writeFileSync4 } from "node:fs";
3840
4177
  import { homedir as homedir4 } from "node:os";
3841
- import { dirname as dirname3, join as join24 } from "node:path";
4178
+ import { dirname as dirname4, join as join27 } from "node:path";
3842
4179
  init_config();
3843
4180
  init_push_gitleaks();
3844
4181
  init_utils_fs();
@@ -3871,8 +4208,8 @@ function buildMemoryScanTree(tmpRoot) {
3871
4208
  incomplete = true;
3872
4209
  continue;
3873
4210
  }
3874
- const dest = join24(tmpRoot, rel);
3875
- mkdirSync4(dirname3(dest), { recursive: true });
4211
+ const dest = join27(tmpRoot, rel);
4212
+ mkdirSync4(dirname4(dest), { recursive: true });
3876
4213
  writeFileSync4(dest, blob);
3877
4214
  logicals.add(m[1]);
3878
4215
  }
@@ -3890,10 +4227,10 @@ function reportMemoryFindings(section2, findings) {
3890
4227
  function reportCommittedMemory(section2) {
3891
4228
  let tmpRoot;
3892
4229
  try {
3893
- const cacheDir = join24(homedir4(), ".cache", "claude-nomad");
4230
+ const cacheDir = join27(homedir4(), ".cache", "claude-nomad");
3894
4231
  mkdirSync4(cacheDir, { recursive: true });
3895
4232
  const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes(4).toString("hex")}`;
3896
- tmpRoot = join24(cacheDir, `check-shared-memory-tree-${stamp}`);
4233
+ tmpRoot = join27(cacheDir, `check-shared-memory-tree-${stamp}`);
3897
4234
  mkdirSync4(tmpRoot, { recursive: true, mode: 448 });
3898
4235
  const { staged, incomplete } = buildMemoryScanTree(tmpRoot);
3899
4236
  if (incomplete) {
@@ -3940,12 +4277,12 @@ function reportCommittedMemory(section2) {
3940
4277
 
3941
4278
  // src/commands.doctor.check-shared.scan.ts
3942
4279
  init_color();
3943
- import { join as join25 } from "node:path";
4280
+ import { join as join28 } from "node:path";
3944
4281
  init_config();
3945
4282
  init_push_gitleaks();
3946
4283
  function scrubPath(logical, sid, logicalToEncoded) {
3947
4284
  const encoded = logicalToEncoded.get(logical) ?? logical;
3948
- return join25(claudeHome(), "projects", encoded, `${sid}.jsonl`);
4285
+ return join28(claudeHome(), "projects", encoded, `${sid}.jsonl`);
3949
4286
  }
3950
4287
  function reportSessionFindings(section2, bySession) {
3951
4288
  for (const [sid, counts] of bySession) {
@@ -4034,7 +4371,7 @@ import { execFileSync as execFileSync10 } from "node:child_process";
4034
4371
  import { randomBytes as randomBytes2 } from "node:crypto";
4035
4372
  import { mkdirSync as mkdirSync5, rmSync as rmSync10, writeFileSync as writeFileSync5 } from "node:fs";
4036
4373
  import { homedir as homedir5 } from "node:os";
4037
- import { dirname as dirname4, join as join26 } from "node:path";
4374
+ import { dirname as dirname5, join as join29 } from "node:path";
4038
4375
  init_config();
4039
4376
  init_push_gitleaks();
4040
4377
  init_utils_fs();
@@ -4067,8 +4404,8 @@ function buildSkillScanTree(tmpRoot) {
4067
4404
  incomplete = true;
4068
4405
  continue;
4069
4406
  }
4070
- const dest = join26(tmpRoot, rel);
4071
- mkdirSync5(dirname4(dest), { recursive: true });
4407
+ const dest = join29(tmpRoot, rel);
4408
+ mkdirSync5(dirname5(dest), { recursive: true });
4072
4409
  writeFileSync5(dest, blob);
4073
4410
  names.add(m[1]);
4074
4411
  }
@@ -4090,10 +4427,10 @@ function reportSkillFindings(section2, findings) {
4090
4427
  function reportCommittedSkills(section2) {
4091
4428
  let tmpRoot;
4092
4429
  try {
4093
- const cacheDir = join26(homedir5(), ".cache", "claude-nomad");
4430
+ const cacheDir = join29(homedir5(), ".cache", "claude-nomad");
4094
4431
  mkdirSync5(cacheDir, { recursive: true });
4095
4432
  const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes2(4).toString("hex")}`;
4096
- tmpRoot = join26(cacheDir, `check-shared-skills-tree-${stamp}`);
4433
+ tmpRoot = join29(cacheDir, `check-shared-skills-tree-${stamp}`);
4097
4434
  mkdirSync5(tmpRoot, { recursive: true, mode: 448 });
4098
4435
  const { staged, incomplete } = buildSkillScanTree(tmpRoot);
4099
4436
  if (incomplete) {
@@ -4143,8 +4480,8 @@ init_config();
4143
4480
 
4144
4481
  // src/remap.ts
4145
4482
  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";
4483
+ 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";
4484
+ import { dirname as dirname7, join as join31, relative as relative6, sep as sep5 } from "node:path";
4148
4485
  init_config();
4149
4486
 
4150
4487
  // src/push-manifest.ts
@@ -4152,8 +4489,8 @@ init_config();
4152
4489
  init_push_gitleaks_config();
4153
4490
  init_utils_fs();
4154
4491
  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";
4492
+ import { existsSync as existsSync21, mkdirSync as mkdirSync6, readdirSync as readdirSync10, readFileSync as readFileSync7, statSync as statSync4 } from "node:fs";
4493
+ import { dirname as dirname6, join as join30 } from "node:path";
4157
4494
  function isChanged(prev, cur, hash) {
4158
4495
  if (prev === void 0) return true;
4159
4496
  if (prev.size !== cur.size) return true;
@@ -4193,8 +4530,8 @@ function hashFile(absPath) {
4193
4530
  return createHash("sha256").update(readFileSync7(absPath)).digest("hex");
4194
4531
  }
4195
4532
  function enumerateDir(dir, results) {
4196
- for (const entry of readdirSync9(dir)) {
4197
- const fullPath = join27(dir, entry);
4533
+ for (const entry of readdirSync10(dir)) {
4534
+ const fullPath = join30(dir, entry);
4198
4535
  const st = statSync4(fullPath);
4199
4536
  if (st.isDirectory()) {
4200
4537
  enumerateDir(fullPath, results);
@@ -4205,8 +4542,8 @@ function enumerateDir(dir, results) {
4205
4542
  }
4206
4543
  function enumerateSourceFiles(projectDir) {
4207
4544
  const results = [];
4208
- for (const entry of readdirSync9(projectDir)) {
4209
- const fullPath = join27(projectDir, entry);
4545
+ for (const entry of readdirSync10(projectDir)) {
4546
+ const fullPath = join30(projectDir, entry);
4210
4547
  const st = statSync4(fullPath);
4211
4548
  if (st.isDirectory()) {
4212
4549
  enumerateDir(fullPath, results);
@@ -4218,15 +4555,15 @@ function enumerateSourceFiles(projectDir) {
4218
4555
  }
4219
4556
  function fileIdentity(p) {
4220
4557
  if (p === null) return "none::absent";
4221
- if (!existsSync20(p)) return `${p}::absent`;
4558
+ if (!existsSync21(p)) return `${p}::absent`;
4222
4559
  return `${p}::${createHash("sha256").update(readFileSync7(p)).digest("hex")}`;
4223
4560
  }
4224
4561
  function computeConfigHash() {
4225
4562
  const repo = repoHome();
4226
4563
  const parts = [
4227
4564
  fileIdentity(resolveTomlPath(repo)),
4228
- fileIdentity(join27(repo, ".gitleaks.overlay.toml")),
4229
- fileIdentity(join27(repo, ".gitleaksignore"))
4565
+ fileIdentity(join30(repo, ".gitleaks.overlay.toml")),
4566
+ fileIdentity(join30(repo, ".gitleaksignore"))
4230
4567
  ];
4231
4568
  return createHash("sha256").update(parts.join("\n")).digest("hex");
4232
4569
  }
@@ -4246,7 +4583,7 @@ function readManifest(path) {
4246
4583
  }
4247
4584
  }
4248
4585
  function writeManifest(path, manifest) {
4249
- mkdirSync6(dirname5(path), { recursive: true });
4586
+ mkdirSync6(dirname6(path), { recursive: true });
4250
4587
  writeJsonAtomic(path, manifest);
4251
4588
  }
4252
4589
  function buildManifest(files, scannerVersion, configHash) {
@@ -4261,7 +4598,7 @@ var TMP_SUFFIX = ".nomad-tmp";
4261
4598
  function atomicMirror(src, dst, options) {
4262
4599
  const tmp = `${dst}${TMP_SUFFIX}`;
4263
4600
  rmSync11(tmp, { recursive: true, force: true });
4264
- cpSync6(src, tmp, options);
4601
+ cpSync5(src, tmp, options);
4265
4602
  rmSync11(dst, { recursive: true, force: true });
4266
4603
  renameAtomicRetry(tmp, dst);
4267
4604
  }
@@ -4270,9 +4607,9 @@ function overlaySessionDir(src, dst) {
4270
4607
  cpSyncGuarded(src, dst, void 0, "overlaySessionDir");
4271
4608
  }
4272
4609
  function copyFileAtomic(src, dst) {
4273
- mkdirSync7(dirname6(dst), { recursive: true });
4610
+ mkdirSync7(dirname7(dst), { recursive: true });
4274
4611
  const tmp = `${dst}.tmp.${process.pid}`;
4275
- cpSync6(src, tmp, { force: true, preserveTimestamps: true });
4612
+ cpSync5(src, tmp, { force: true, preserveTimestamps: true });
4276
4613
  renameAtomicRetry(tmp, dst);
4277
4614
  }
4278
4615
  function hasDeltaForDir(sel, localDir) {
@@ -4288,12 +4625,12 @@ function applySelective(sel, localDir, repoDst) {
4288
4625
  const prefix = `${localDir}${sep5}`;
4289
4626
  for (const src of sel.changed) {
4290
4627
  if (!src.startsWith(prefix)) continue;
4291
- copyFileAtomic(src, join28(repoDst, relative5(localDir, src)));
4628
+ copyFileAtomic(src, join31(repoDst, relative6(localDir, src)));
4292
4629
  }
4293
4630
  for (const src of sel.deleted) {
4294
4631
  if (!src.startsWith(prefix)) continue;
4295
- const dst = join28(repoDst, relative5(localDir, src));
4296
- if (existsSync21(dst)) rmSync11(dst);
4632
+ const dst = join31(repoDst, relative6(localDir, src));
4633
+ if (existsSync22(dst)) rmSync11(dst);
4297
4634
  }
4298
4635
  }
4299
4636
  function copyDirJsonlOnly(src, dst) {
@@ -4301,7 +4638,7 @@ function copyDirJsonlOnly(src, dst) {
4301
4638
  recursive: true,
4302
4639
  force: true,
4303
4640
  filter: (srcPath) => {
4304
- const rel = relative5(src, srcPath);
4641
+ const rel = relative6(src, srcPath);
4305
4642
  if (rel === "") return true;
4306
4643
  if (rel.split(sep5).length > 1) return true;
4307
4644
  if (statSync5(srcPath).isDirectory()) return true;
@@ -4322,15 +4659,15 @@ function remapPull(ts, opts = {}) {
4322
4659
  const wouldPull = [];
4323
4660
  const repo = repoHome();
4324
4661
  const claude = claudeHome();
4325
- const mapPath = join28(repo, "path-map.json");
4326
- const repoProjects = join28(repo, "shared", "projects");
4327
- if (!existsSync21(mapPath) || !existsSync21(repoProjects)) {
4662
+ const mapPath = join31(repo, "path-map.json");
4663
+ const repoProjects = join31(repo, "shared", "projects");
4664
+ if (!existsSync22(mapPath) || !existsSync22(repoProjects)) {
4328
4665
  const text = "no path-map or repo projects dir; skipping session remap";
4329
4666
  emitPreview(opts.onPreview, { kind: "note", text }, text);
4330
4667
  return { unmapped: 0, pulled, wouldPull };
4331
4668
  }
4332
4669
  const map = readPathMap(mapPath);
4333
- const localProjects = join28(claude, "projects");
4670
+ const localProjects = join31(claude, "projects");
4334
4671
  if (!dryRun) mkdirSync7(localProjects, { recursive: true });
4335
4672
  for (const [logical, hosts] of Object.entries(map.projects)) {
4336
4673
  assertSafeLogical(logical);
@@ -4340,9 +4677,9 @@ function remapPull(ts, opts = {}) {
4340
4677
  continue;
4341
4678
  }
4342
4679
  assertSafeLocalRoot(localPath, logical);
4343
- const src = join28(repoProjects, logical);
4344
- if (!existsSync21(src)) continue;
4345
- const dst = join28(localProjects, encodePath(localPath));
4680
+ const src = join31(repoProjects, logical);
4681
+ if (!existsSync22(src)) continue;
4682
+ const dst = join31(localProjects, encodePath(localPath));
4346
4683
  if (dryRun) {
4347
4684
  wouldPull.push(logical);
4348
4685
  emitPreview(
@@ -4360,12 +4697,12 @@ function remapPull(ts, opts = {}) {
4360
4697
  }
4361
4698
  function countLocalOnly(src, dst) {
4362
4699
  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()) {
4700
+ for (const name of readdirSync11(dst)) {
4701
+ const dstPath = join31(dst, name);
4702
+ const srcPath = join31(src, name);
4703
+ if (lstatSync13(dstPath).isDirectory()) {
4367
4704
  count += countLocalOnly(srcPath, dstPath);
4368
- } else if (lstatSync11(srcPath, { throwIfNoEntry: false }) === void 0) {
4705
+ } else if (lstatSync13(srcPath, { throwIfNoEntry: false }) === void 0) {
4369
4706
  count++;
4370
4707
  }
4371
4708
  }
@@ -4374,20 +4711,20 @@ function countLocalOnly(src, dst) {
4374
4711
  function scanLocalOnly() {
4375
4712
  const repo = repoHome();
4376
4713
  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;
4714
+ const mapPath = join31(repo, "path-map.json");
4715
+ const repoProjects = join31(repo, "shared", "projects");
4716
+ if (!existsSync22(mapPath) || !existsSync22(repoProjects)) return 0;
4380
4717
  const map = readPathMap(mapPath);
4381
- const localProjects = join28(claude, "projects");
4718
+ const localProjects = join31(claude, "projects");
4382
4719
  let count = 0;
4383
4720
  for (const [logical, hosts] of Object.entries(map.projects)) {
4384
4721
  assertSafeLogical(logical);
4385
4722
  const localPath = hosts[HOST];
4386
4723
  if (!localPath || localPath === "TBD") continue;
4387
4724
  assertSafeLocalRoot(localPath, logical);
4388
- const dst = join28(localProjects, encodePath(localPath));
4389
- if (!existsSync21(dst)) continue;
4390
- count += countLocalOnly(join28(repoProjects, logical), dst);
4725
+ const dst = join31(localProjects, encodePath(localPath));
4726
+ if (!existsSync22(dst)) continue;
4727
+ count += countLocalOnly(join31(repoProjects, logical), dst);
4391
4728
  }
4392
4729
  return count;
4393
4730
  }
@@ -4422,26 +4759,26 @@ function remapPush(ts, opts = {}) {
4422
4759
  const wouldPush = [];
4423
4760
  const repo = repoHome();
4424
4761
  const claude = claudeHome();
4425
- const mapPath = join28(repo, "path-map.json");
4426
- if (!existsSync21(mapPath)) {
4762
+ const mapPath = join31(repo, "path-map.json");
4763
+ if (!existsSync22(mapPath)) {
4427
4764
  log("no path-map.json; skipping session export");
4428
4765
  return { unmapped: 0, collisions: 0, pushed, wouldPush };
4429
4766
  }
4430
4767
  const map = readPathMap(mapPath);
4431
- const localProjects = join28(claude, "projects");
4432
- const repoProjects = join28(repo, "shared", "projects");
4768
+ const localProjects = join31(claude, "projects");
4769
+ const repoProjects = join31(repo, "shared", "projects");
4433
4770
  const reverse = buildReverseMap(map);
4434
- if (!existsSync21(localProjects)) return { unmapped, collisions: 0, pushed, wouldPush };
4771
+ if (!existsSync22(localProjects)) return { unmapped, collisions: 0, pushed, wouldPush };
4435
4772
  if (!dryRun) mkdirSync7(repoProjects, { recursive: true });
4436
- for (const dir of readdirSync10(localProjects)) {
4773
+ for (const dir of readdirSync11(localProjects)) {
4437
4774
  if (dir.endsWith(TMP_SUFFIX)) continue;
4438
4775
  const logical = reverse.get(dir);
4439
4776
  if (!logical) {
4440
4777
  unmapped++;
4441
4778
  continue;
4442
4779
  }
4443
- const localDir = join28(localProjects, dir);
4444
- const repoDst = join28(repoProjects, logical);
4780
+ const localDir = join31(localProjects, dir);
4781
+ const repoDst = join31(repoProjects, logical);
4445
4782
  if (skipForSelection(opts.selection, localDir)) continue;
4446
4783
  if (dryRun) {
4447
4784
  wouldPush.push(logical);
@@ -4465,8 +4802,8 @@ function buildScanTree(tmpRoot) {
4465
4802
  const logicalToEncoded = /* @__PURE__ */ new Map();
4466
4803
  let staged = 0;
4467
4804
  const repo = repoHome();
4468
- const mapPath = join29(repo, "path-map.json");
4469
- if (!existsSync22(mapPath)) return { logicalToEncoded, staged, malformed: false };
4805
+ const mapPath = join32(repo, "path-map.json");
4806
+ if (!existsSync23(mapPath)) return { logicalToEncoded, staged, malformed: false };
4470
4807
  let map;
4471
4808
  try {
4472
4809
  map = readJson(mapPath);
@@ -4483,12 +4820,12 @@ function buildScanTree(tmpRoot) {
4483
4820
  if (!p || p === "TBD") continue;
4484
4821
  reverse.set(encodePath(p), logical);
4485
4822
  }
4486
- const localProjects = join29(claudeHome(), "projects");
4487
- if (!existsSync22(localProjects)) return { logicalToEncoded, staged, malformed: false };
4488
- for (const dir of readdirSync11(localProjects)) {
4823
+ const localProjects = join32(claudeHome(), "projects");
4824
+ if (!existsSync23(localProjects)) return { logicalToEncoded, staged, malformed: false };
4825
+ for (const dir of readdirSync12(localProjects)) {
4489
4826
  const logical = reverse.get(dir);
4490
4827
  if (!logical) continue;
4491
- copyDirJsonlOnly(join29(localProjects, dir), join29(tmpRoot, "shared", "projects", logical));
4828
+ copyDirJsonlOnly(join32(localProjects, dir), join32(tmpRoot, "shared", "projects", logical));
4492
4829
  logicalToEncoded.set(logical, dir);
4493
4830
  staged++;
4494
4831
  }
@@ -4518,11 +4855,11 @@ function ensureGitleaksReady(section2, gitleaksReady) {
4518
4855
  return true;
4519
4856
  }
4520
4857
  function runLocalPreviewScan(section2) {
4521
- const cacheDir = join29(homedir6(), ".cache", "claude-nomad");
4858
+ const cacheDir = join32(homedir6(), ".cache", "claude-nomad");
4522
4859
  mkdirSync8(cacheDir, { recursive: true });
4523
4860
  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}`);
4861
+ const reportPath = join32(cacheDir, `check-shared-${stamp}.json`);
4862
+ const tmpRoot = join32(cacheDir, `check-shared-tree-${stamp}`);
4526
4863
  try {
4527
4864
  const { logicalToEncoded, staged, malformed } = buildScanTree(tmpRoot);
4528
4865
  if (malformed) {
@@ -4555,8 +4892,8 @@ function reportCheckShared(section2, gitleaksReady) {
4555
4892
 
4556
4893
  // src/commands.doctor.checks.hooks.scope.ts
4557
4894
  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";
4895
+ import { existsSync as existsSync24, readFileSync as readFileSync8, readdirSync as readdirSync13, realpathSync as realpathSync3 } from "node:fs";
4896
+ import { dirname as dirname8, extname, join as join33 } from "node:path";
4560
4897
  init_config();
4561
4898
  function typeFromPackageJson(pkgPath) {
4562
4899
  try {
@@ -4576,11 +4913,11 @@ function effectiveType(hookPath) {
4576
4913
  } catch {
4577
4914
  return null;
4578
4915
  }
4579
- let dir = dirname7(real);
4916
+ let dir = dirname8(real);
4580
4917
  for (; ; ) {
4581
- const pkg = join30(dir, "package.json");
4582
- if (existsSync23(pkg)) return typeFromPackageJson(pkg);
4583
- const parent = dirname7(dir);
4918
+ const pkg = join33(dir, "package.json");
4919
+ if (existsSync24(pkg)) return typeFromPackageJson(pkg);
4920
+ const parent = dirname8(dir);
4584
4921
  if (parent === dir) return "cjs";
4585
4922
  dir = parent;
4586
4923
  }
@@ -4608,7 +4945,7 @@ function remedy(family) {
4608
4945
  }
4609
4946
  function safeReaddir2(dir) {
4610
4947
  try {
4611
- return readdirSync12(dir);
4948
+ return readdirSync13(dir);
4612
4949
  } catch {
4613
4950
  return [];
4614
4951
  }
@@ -4621,15 +4958,15 @@ function safeRead(path) {
4621
4958
  }
4622
4959
  }
4623
4960
  function reportHookScopeCheck(section2) {
4624
- const hooksDir = join30(claudeHome(), "hooks");
4625
- if (!existsSync23(hooksDir)) {
4961
+ const hooksDir = join33(claudeHome(), "hooks");
4962
+ if (!existsSync24(hooksDir)) {
4626
4963
  addItem(section2, `${dim(infoGlyph)} no ~/.claude/hooks; skipping module-scope check`);
4627
4964
  return;
4628
4965
  }
4629
4966
  let anyWarn = false;
4630
4967
  for (const name of safeReaddir2(hooksDir)) {
4631
4968
  if (extname(name) !== ".js") continue;
4632
- const abs = join30(hooksDir, name);
4969
+ const abs = join33(hooksDir, name);
4633
4970
  const eff = effectiveType(abs);
4634
4971
  if (eff === null) continue;
4635
4972
  const src = safeRead(abs);
@@ -4649,8 +4986,8 @@ function reportHookScopeCheck(section2) {
4649
4986
 
4650
4987
  // src/commands.doctor.checks.hooks.ts
4651
4988
  init_color();
4652
- import { existsSync as existsSync24 } from "node:fs";
4653
- import { join as join31 } from "node:path";
4989
+ import { existsSync as existsSync25 } from "node:fs";
4990
+ import { join as join34 } from "node:path";
4654
4991
  init_config();
4655
4992
  function expandHome(token) {
4656
4993
  const h2 = home();
@@ -4693,7 +5030,7 @@ function checkEventGroups(section2, event, groups) {
4693
5030
  for (const group of groups) {
4694
5031
  for (const cmd of commandsFromGroup(group)) {
4695
5032
  for (const resolved of claudePathsIn(cmd)) {
4696
- if (existsSync24(resolved)) continue;
5033
+ if (existsSync25(resolved)) continue;
4697
5034
  addItem(
4698
5035
  section2,
4699
5036
  `${red(failGlyph)} hooks/${event}: command target missing: ${resolved} (run nomad pull)`
@@ -4706,8 +5043,8 @@ function checkEventGroups(section2, event, groups) {
4706
5043
  return anyFail;
4707
5044
  }
4708
5045
  function reportHooksTargetCheck(section2) {
4709
- const settingsPath = join31(claudeHome(), "settings.json");
4710
- if (!existsSync24(settingsPath)) {
5046
+ const settingsPath = join34(claudeHome(), "settings.json");
5047
+ if (!existsSync25(settingsPath)) {
4711
5048
  addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json; skipping hook target check`);
4712
5049
  return;
4713
5050
  }
@@ -4730,12 +5067,12 @@ function reportHooksTargetCheck(section2) {
4730
5067
 
4731
5068
  // src/commands.doctor.checks.hooks.preserve-symlinks.ts
4732
5069
  init_color();
4733
- import { existsSync as existsSync26, readFileSync as readFileSync9 } from "node:fs";
4734
- import { join as join33 } from "node:path";
5070
+ import { existsSync as existsSync27, readFileSync as readFileSync9 } from "node:fs";
5071
+ import { join as join36 } from "node:path";
4735
5072
 
4736
5073
  // 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";
5074
+ import { closeSync as closeSync3, existsSync as existsSync26, openSync as openSync3, readSync, realpathSync as realpathSync4 } from "node:fs";
5075
+ import { dirname as dirname9, join as join35, resolve as resolve3 } from "node:path";
4739
5076
  function suppressedRanges(src) {
4740
5077
  const ranges = [];
4741
5078
  const re = /\/\*[\s\S]*?\*\/|\/\/[^\n]*|'[^']*'|"[^"]*"|`[^`]*`/g;
@@ -4766,13 +5103,13 @@ function topRelativeSpecifiers(src) {
4766
5103
  return specifiers;
4767
5104
  }
4768
5105
  function specifierIsMissing(specifier, baseDir) {
4769
- const base = resolve2(baseDir, specifier);
4770
- if (existsSync25(base)) return false;
5106
+ const base = resolve3(baseDir, specifier);
5107
+ if (existsSync26(base)) return false;
4771
5108
  for (const ext of [".js", ".cjs", ".mjs"]) {
4772
- if (existsSync25(base + ext)) return false;
5109
+ if (existsSync26(base + ext)) return false;
4773
5110
  }
4774
5111
  for (const idx of ["index.js", "index.cjs", "index.mjs"]) {
4775
- if (existsSync25(join32(base, idx))) return false;
5112
+ if (existsSync26(join35(base, idx))) return false;
4776
5113
  }
4777
5114
  return true;
4778
5115
  }
@@ -4798,7 +5135,7 @@ function relativeRequireTargetsBroken(scriptPath) {
4798
5135
  }
4799
5136
  const specifiers = topRelativeSpecifiers(raw);
4800
5137
  if (specifiers.length === 0) return false;
4801
- const baseDir = dirname8(realPath);
5138
+ const baseDir = dirname9(realPath);
4802
5139
  for (const spec of specifiers) {
4803
5140
  if (specifierIsMissing(spec, baseDir)) return true;
4804
5141
  }
@@ -4827,8 +5164,8 @@ function commandTokens(command) {
4827
5164
  return tokens;
4828
5165
  }
4829
5166
  function readPathMapSafe() {
4830
- const mapPath = join33(repoHome(), "path-map.json");
4831
- if (!existsSync26(mapPath)) return { projects: {} };
5167
+ const mapPath = join36(repoHome(), "path-map.json");
5168
+ if (!existsSync27(mapPath)) return { projects: {} };
4832
5169
  try {
4833
5170
  return JSON.parse(readFileSync9(mapPath, "utf8"));
4834
5171
  } catch {
@@ -4902,8 +5239,8 @@ function checkEventForPreserveSymlinks(section2, event, groups, sharedLinkNames)
4902
5239
  return anyWarn;
4903
5240
  }
4904
5241
  function reportPreserveSymlinksCheck(section2) {
4905
- const settingsPath = join33(claudeHome(), "settings.json");
4906
- if (!existsSync26(settingsPath)) {
5242
+ const settingsPath = join36(claudeHome(), "settings.json");
5243
+ if (!existsSync27(settingsPath)) {
4907
5244
  addItem(
4908
5245
  section2,
4909
5246
  `${dim(infoGlyph)} no ~/.claude/settings.json; skipping preserve-symlinks-main check`
@@ -4936,8 +5273,8 @@ function reportPreserveSymlinksCheck(section2) {
4936
5273
 
4937
5274
  // src/commands.doctor.checks.settings-drift.ts
4938
5275
  init_color();
4939
- import { existsSync as existsSync27, readFileSync as readFileSync10 } from "node:fs";
4940
- import { join as join34 } from "node:path";
5276
+ import { existsSync as existsSync28, readFileSync as readFileSync10 } from "node:fs";
5277
+ import { join as join37 } from "node:path";
4941
5278
  init_config();
4942
5279
  init_utils_json();
4943
5280
  function diffMergedSettings(merged, settings) {
@@ -4955,7 +5292,7 @@ function tryReadJson(filePath) {
4955
5292
  }
4956
5293
  }
4957
5294
  function reportHooksBaseSelfCleanNote(section2) {
4958
- const basePath = join34(repoHome(), "shared", "settings.base.json");
5295
+ const basePath = join37(repoHome(), "shared", "settings.base.json");
4959
5296
  const base = tryReadJson(basePath);
4960
5297
  if (base === null) return;
4961
5298
  if (!baseHasGsdHookEntries(base)) return;
@@ -4968,14 +5305,14 @@ function reportSettingsDriftCheck(section2) {
4968
5305
  const claude = claudeHome();
4969
5306
  const repo = repoHome();
4970
5307
  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)) {
5308
+ const settingsPath = join37(claude, "settings.json");
5309
+ const basePath = join37(repo, "shared", "settings.base.json");
5310
+ const hostPath = join37(repo, "hosts", `${host}.json`);
5311
+ if (!existsSync28(settingsPath)) {
4975
5312
  addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json; skipping merge-drift check`);
4976
5313
  return;
4977
5314
  }
4978
- if (!existsSync27(basePath)) {
5315
+ if (!existsSync28(basePath)) {
4979
5316
  addItem(
4980
5317
  section2,
4981
5318
  `${dim(infoGlyph)} shared/settings.base.json missing; skipping merge-drift check`
@@ -4994,7 +5331,7 @@ function reportSettingsDriftCheck(section2) {
4994
5331
  if (settings === null) {
4995
5332
  return;
4996
5333
  }
4997
- const hostExists = existsSync27(hostPath);
5334
+ const hostExists = existsSync28(hostPath);
4998
5335
  const hostObj = hostExists ? tryReadJson(hostPath) : null;
4999
5336
  if (hostExists && hostObj === null) {
5000
5337
  addItem(
@@ -5211,15 +5548,15 @@ function reportSyncModality(section2) {
5211
5548
  // src/commands.doctor.checks.crlf.ts
5212
5549
  init_color();
5213
5550
  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";
5551
+ import { existsSync as existsSync29, readFileSync as readFileSync13 } from "node:fs";
5552
+ import { join as join38 } from "node:path";
5216
5553
  init_config();
5217
5554
  var PROBE_TIMEOUT_MS2 = 3e3;
5218
5555
  var GUARD_LINE = /^\*\s+-text\b/;
5219
5556
  function hasGitattributesGuard(repo) {
5220
5557
  try {
5221
- const path = join35(repo, ".gitattributes");
5222
- if (!existsSync28(path)) return false;
5558
+ const path = join38(repo, ".gitattributes");
5559
+ if (!existsSync29(path)) return false;
5223
5560
  const content = readFileSync13(path, "utf8");
5224
5561
  return content.split("\n").map((line) => line.trim()).some((line) => line !== "" && !line.startsWith("#") && GUARD_LINE.test(line));
5225
5562
  } catch {
@@ -5260,34 +5597,34 @@ function reportCrlfGuardCheck(section2, run = execFileSync13) {
5260
5597
 
5261
5598
  // src/spinner.ts
5262
5599
  init_color();
5263
- import { existsSync as existsSync33 } from "node:fs";
5600
+ import { existsSync as existsSync34 } from "node:fs";
5264
5601
  import { fileURLToPath as fileURLToPath4 } from "node:url";
5265
5602
  import { Worker } from "node:worker_threads";
5266
5603
 
5267
5604
  // src/commands.push.recovery.ts
5268
5605
  init_config();
5269
5606
  import { readFileSync as readFileSync18, rmSync as rmSync15, writeFileSync as writeFileSync9 } from "node:fs";
5270
- import { join as join43 } from "node:path";
5607
+ import { join as join46 } from "node:path";
5271
5608
  import { createInterface as createInterface2 } from "node:readline/promises";
5272
5609
 
5273
5610
  // src/commands.push.recovery.actions.ts
5274
5611
  init_config();
5275
5612
  import { readFileSync as readFileSync17 } from "node:fs";
5276
- import { isAbsolute as isAbsolute2, resolve as resolve3, sep as sep9 } from "node:path";
5613
+ import { isAbsolute as isAbsolute2, resolve as resolve4, sep as sep9 } from "node:path";
5277
5614
 
5278
5615
  // src/commands.push.recovery.memory.ts
5279
5616
  init_config();
5280
5617
  init_config_sharedDirs_guard();
5281
5618
  import {
5282
- cpSync as cpSync7,
5283
- lstatSync as lstatSync12,
5619
+ cpSync as cpSync6,
5620
+ lstatSync as lstatSync14,
5284
5621
  mkdirSync as mkdirSync9,
5285
5622
  readFileSync as readFileSync14,
5286
5623
  realpathSync as realpathSync5,
5287
5624
  statSync as statSync6,
5288
5625
  writeFileSync as writeFileSync6
5289
5626
  } from "node:fs";
5290
- import { dirname as dirname9, join as join36, sep as sep6 } from "node:path";
5627
+ import { dirname as dirname10, join as join39, sep as sep6 } from "node:path";
5291
5628
  init_push_gitleaks_scan();
5292
5629
 
5293
5630
  // src/rel-path-guard.ts
@@ -5316,11 +5653,11 @@ function resolveMemoryLocalPath(logical, relPath, map) {
5316
5653
  if (!isSafeRelPath(relPath)) return null;
5317
5654
  const abs = map.projects[logical]?.[HOST];
5318
5655
  if (abs === void 0) return null;
5319
- const memoryRoot = join36(claudeHome(), "projects", encodePath(abs), "memory");
5320
- const localPath = join36(memoryRoot, ...relPath.split("/"));
5656
+ const memoryRoot = join39(claudeHome(), "projects", encodePath(abs), "memory");
5657
+ const localPath = join39(memoryRoot, ...relPath.split("/"));
5321
5658
  if (localPath !== memoryRoot && !localPath.startsWith(memoryRoot + sep6)) return null;
5322
5659
  try {
5323
- if (lstatSync12(localPath).isSymbolicLink()) return null;
5660
+ if (lstatSync14(localPath).isSymbolicLink()) return null;
5324
5661
  const realLocal = realpathSync5(localPath);
5325
5662
  const realRoot = realpathSync5(memoryRoot);
5326
5663
  if (!realLocal.startsWith(realRoot + sep6)) return null;
@@ -5371,29 +5708,29 @@ function applyMemoryRedact(f, ts, map, scan = scanFile) {
5371
5708
  );
5372
5709
  }
5373
5710
  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 });
5711
+ const dest = join39(repoHome(), "shared", "projects", logical, "memory", ...relPath.split("/"));
5712
+ mkdirSync9(dirname10(dest), { recursive: true });
5713
+ cpSync6(localPath, dest, { force: true });
5377
5714
  return true;
5378
5715
  }
5379
5716
 
5380
5717
  // src/commands.push.recovery.skills.ts
5381
5718
  init_config();
5382
5719
  import {
5383
- cpSync as cpSync8,
5384
- lstatSync as lstatSync14,
5720
+ cpSync as cpSync7,
5721
+ lstatSync as lstatSync16,
5385
5722
  mkdirSync as mkdirSync11,
5386
5723
  readFileSync as readFileSync15,
5387
5724
  realpathSync as realpathSync6,
5388
5725
  statSync as statSync7,
5389
5726
  writeFileSync as writeFileSync7
5390
5727
  } from "node:fs";
5391
- import { dirname as dirname10, join as join38, sep as sep7 } from "node:path";
5728
+ import { dirname as dirname11, join as join41, sep as sep7 } from "node:path";
5392
5729
 
5393
5730
  // src/skills-sync.ts
5394
5731
  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";
5732
+ import { existsSync as existsSync30, lstatSync as lstatSync15, mkdirSync as mkdirSync10, readdirSync as readdirSync14, rmSync as rmSync13 } from "node:fs";
5733
+ import { join as join40 } from "node:path";
5397
5734
 
5398
5735
  // src/skills-sync.tracked.ts
5399
5736
  init_utils();
@@ -5417,7 +5754,7 @@ function isSkillExcluded(name) {
5417
5754
  return isGsdOwned(name) || isDeniedName(ALWAYS_NEVER_SYNC, name);
5418
5755
  }
5419
5756
  function copySkillsPush(src, dst) {
5420
- const srcNames = readdirSync13(src, { encoding: "utf8" });
5757
+ const srcNames = readdirSync14(src, { encoding: "utf8" });
5421
5758
  const blockSet = /* @__PURE__ */ new Set([
5422
5759
  ...srcNames.filter((n) => isGsdOwned(n)),
5423
5760
  ...ALWAYS_NEVER_SYNC
@@ -5428,11 +5765,11 @@ function copySkillsPull(src, dst, isRootPreserved) {
5428
5765
  copyExtrasFilteredPreservingBy(src, dst, isSkillExcluded, isRootPreserved);
5429
5766
  }
5430
5767
  function syncSkillsPull(ts, prePostHeads) {
5431
- const sharedSkills = join37(repoHome(), "shared", "skills");
5432
- if (!existsSync29(sharedSkills)) return;
5433
- const localSkills = join37(claudeHome(), "skills");
5768
+ const sharedSkills = join40(repoHome(), "shared", "skills");
5769
+ if (!existsSync30(sharedSkills)) return;
5770
+ const localSkills = join40(claudeHome(), "skills");
5434
5771
  backupBeforeWrite(localSkills, ts);
5435
- const dstStat = lstatSync13(localSkills, { throwIfNoEntry: false });
5772
+ const dstStat = lstatSync15(localSkills, { throwIfNoEntry: false });
5436
5773
  if (dstStat?.isSymbolicLink() === true) {
5437
5774
  rmSync13(localSkills, { recursive: true, force: true });
5438
5775
  mkdirSync10(localSkills, { recursive: true });
@@ -5441,11 +5778,11 @@ function syncSkillsPull(ts, prePostHeads) {
5441
5778
  copySkillsPull(sharedSkills, localSkills, (name) => !tracked.has(name));
5442
5779
  }
5443
5780
  function syncSkillsPush() {
5444
- const localSkills = join37(claudeHome(), "skills");
5445
- const stat = lstatSync13(localSkills, { throwIfNoEntry: false });
5781
+ const localSkills = join40(claudeHome(), "skills");
5782
+ const stat = lstatSync15(localSkills, { throwIfNoEntry: false });
5446
5783
  if (stat === void 0) return;
5447
5784
  if (stat.isSymbolicLink()) return;
5448
- const sharedSkills = join37(repoHome(), "shared", "skills");
5785
+ const sharedSkills = join40(repoHome(), "shared", "skills");
5449
5786
  copySkillsPush(localSkills, sharedSkills);
5450
5787
  }
5451
5788
 
@@ -5468,12 +5805,12 @@ function resolveSkillLocalPath(name, relPath) {
5468
5805
  if (!SAFE_SKILL_NAME.test(name) || name === "." || name === "..") return null;
5469
5806
  if (isGsdOwned(name)) return null;
5470
5807
  if (!isSafeRelPath(relPath)) return null;
5471
- const skillsRoot = join38(claudeHome(), "skills");
5472
- const skillRoot = join38(skillsRoot, name);
5473
- const localPath = join38(skillRoot, ...relPath.split("/"));
5808
+ const skillsRoot = join41(claudeHome(), "skills");
5809
+ const skillRoot = join41(skillsRoot, name);
5810
+ const localPath = join41(skillRoot, ...relPath.split("/"));
5474
5811
  if (localPath !== skillRoot && !localPath.startsWith(skillRoot + sep7)) return null;
5475
5812
  try {
5476
- if (lstatSync14(localPath).isSymbolicLink()) return null;
5813
+ if (lstatSync16(localPath).isSymbolicLink()) return null;
5477
5814
  const realLocal = realpathSync6(localPath);
5478
5815
  const realRoot = realpathSync6(skillsRoot);
5479
5816
  if (!realLocal.startsWith(realRoot + sep7)) return null;
@@ -5522,9 +5859,9 @@ function applySkillRedact(f, ts, scan = scanFile) {
5522
5859
  );
5523
5860
  }
5524
5861
  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 });
5862
+ const dest = join41(repoHome(), "shared", "skills", name, ...relPath.split("/"));
5863
+ mkdirSync11(dirname11(dest), { recursive: true });
5864
+ cpSync7(localPath, dest, { force: true });
5528
5865
  return true;
5529
5866
  }
5530
5867
 
@@ -5600,26 +5937,26 @@ function applyDeferredAllows(findings, state, repo) {
5600
5937
  // src/commands.push.recovery.redact.ts
5601
5938
  init_config();
5602
5939
  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";
5940
+ import { cpSync as cpSync8, existsSync as existsSync33, mkdirSync as mkdirSync12 } from "node:fs";
5941
+ import { dirname as dirname13, join as join44, sep as sep8 } from "node:path";
5605
5942
 
5606
5943
  // src/commands.redact.ts
5607
5944
  init_config();
5608
- import { existsSync as existsSync31 } from "node:fs";
5609
- import { dirname as dirname11, join as join40 } from "node:path";
5945
+ import { existsSync as existsSync32 } from "node:fs";
5946
+ import { dirname as dirname12, join as join43 } from "node:path";
5610
5947
 
5611
5948
  // 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";
5949
+ import { existsSync as existsSync31, lstatSync as lstatSync17, readFileSync as readFileSync16, readdirSync as readdirSync15, statSync as statSync8, writeFileSync as writeFileSync8 } from "node:fs";
5950
+ import { join as join42 } from "node:path";
5614
5951
  init_utils_fs();
5615
5952
  init_utils();
5616
5953
  function collectFiles(dir, out) {
5617
- if (!existsSync30(dir)) return;
5618
- const st = lstatSync15(dir);
5954
+ if (!existsSync31(dir)) return;
5955
+ const st = lstatSync17(dir);
5619
5956
  if (!st.isDirectory()) return;
5620
- for (const entry of readdirSync14(dir)) {
5621
- const abs = join39(dir, entry);
5622
- const lst = lstatSync15(abs);
5957
+ for (const entry of readdirSync15(dir)) {
5958
+ const abs = join42(dir, entry);
5959
+ const lst = lstatSync17(abs);
5623
5960
  if (lst.isSymbolicLink()) continue;
5624
5961
  if (lst.isDirectory()) {
5625
5962
  collectFiles(abs, out);
@@ -5728,15 +6065,15 @@ init_utils_json();
5728
6065
  init_utils();
5729
6066
  function resolveLiveTranscript(id) {
5730
6067
  try {
5731
- const mapPath = join40(repoHome(), "path-map.json");
5732
- if (!existsSync31(mapPath)) return null;
6068
+ const mapPath = join43(repoHome(), "path-map.json");
6069
+ if (!existsSync32(mapPath)) return null;
5733
6070
  const projects = readJson(mapPath).projects;
5734
6071
  const claude = claudeHome();
5735
6072
  for (const hostMap of Object.values(projects)) {
5736
6073
  const abs = hostMap[HOST];
5737
6074
  if (abs === void 0) continue;
5738
- const live = join40(claude, "projects", encodePath(abs), `${id}.jsonl`);
5739
- if (existsSync31(live)) return live;
6075
+ const live = join43(claude, "projects", encodePath(abs), `${id}.jsonl`);
6076
+ if (existsSync32(live)) return live;
5740
6077
  }
5741
6078
  return null;
5742
6079
  } catch {
@@ -5756,17 +6093,17 @@ function cmdRedact(opts, nowMs = Date.now, scan = scanFile) {
5756
6093
  }
5757
6094
  const repo = repoHome();
5758
6095
  const backup = backupBase();
5759
- if (!existsSync31(repo)) die(`repo not cloned at ${repo}`);
6096
+ if (!existsSync32(repo)) die(`repo not cloned at ${repo}`);
5760
6097
  const handle = acquireLock("redact");
5761
6098
  if (handle === null) process.exit(0);
5762
6099
  try {
5763
6100
  const localPath = resolveLiveTranscript(id);
5764
- if (localPath === null || !existsSync31(localPath)) {
6101
+ if (localPath === null || !existsSync32(localPath)) {
5765
6102
  fail(`could not resolve local transcript for session ${id} on this host`);
5766
6103
  process.exitCode = 1;
5767
6104
  return;
5768
6105
  }
5769
- const sessionDir = join40(dirname11(localPath), id);
6106
+ const sessionDir = join43(dirname12(localPath), id);
5770
6107
  const subtreeFiles = listSubtreeFiles(sessionDir);
5771
6108
  if (isSubtreeActive(localPath, subtreeFiles, nowMs())) {
5772
6109
  log(
@@ -5827,8 +6164,8 @@ function resolveStagedDir(localPath, map, claude, repo) {
5827
6164
  assertSafeLogical(logical);
5828
6165
  const abs = hostMap[HOST];
5829
6166
  if (abs === void 0) continue;
5830
- if (localPath.startsWith(join41(claude, "projects", encodePath(abs)) + sep8)) {
5831
- return join41(repo, "shared", "projects", logical);
6167
+ if (localPath.startsWith(join44(claude, "projects", encodePath(abs)) + sep8)) {
6168
+ return join44(repo, "shared", "projects", logical);
5832
6169
  }
5833
6170
  }
5834
6171
  return null;
@@ -5838,7 +6175,7 @@ function preflightRedactable(f, map, nowMs) {
5838
6175
  if (sid === null) return "a finding has no resolvable session id (not a session transcript)";
5839
6176
  const localPath = resolveLiveTranscript(sid);
5840
6177
  if (localPath === null) return `session ${sid}: local transcript not found`;
5841
- const sessionDir = join41(dirname12(localPath), sid);
6178
+ const sessionDir = join44(dirname13(localPath), sid);
5842
6179
  const subtreeFiles = listSubtreeFiles(sessionDir);
5843
6180
  if (isSubtreeActive(localPath, subtreeFiles, nowMs())) {
5844
6181
  return `session ${sid}: looks active (modified within the last 5 minutes)`;
@@ -5867,7 +6204,7 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
5867
6204
  `could not locate the local transcript for session ${sid}; choose Skip or Drop session.`
5868
6205
  );
5869
6206
  }
5870
- const sessionDir = join41(dirname12(localPath), sid);
6207
+ const sessionDir = join44(dirname13(localPath), sid);
5871
6208
  const subtreeFiles = listSubtreeFiles(sessionDir);
5872
6209
  if (isSubtreeActive(localPath, subtreeFiles, nowMs())) {
5873
6210
  return refuse(
@@ -5900,9 +6237,9 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
5900
6237
  );
5901
6238
  }
5902
6239
  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 });
6240
+ cpSync8(localPath, join44(stagedProjectDir, `${sid}.jsonl`), { force: true });
6241
+ if (existsSync33(sessionDir)) {
6242
+ cpSync8(sessionDir, join44(stagedProjectDir, sid), { force: true, recursive: true });
5906
6243
  }
5907
6244
  return true;
5908
6245
  }
@@ -5910,14 +6247,14 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
5910
6247
  // src/commands.push.recovery.drop.ts
5911
6248
  init_config();
5912
6249
  import { rmSync as rmSync14 } from "node:fs";
5913
- import { join as join42 } from "node:path";
6250
+ import { join as join45 } from "node:path";
5914
6251
  function dropSessionFromStaged(sid, map) {
5915
6252
  const logicals = Object.keys(map.projects);
5916
6253
  if (logicals.length === 0) return false;
5917
6254
  const repo = repoHome();
5918
6255
  for (const logical of logicals) {
5919
- const jsonl = join42(repo, "shared", "projects", logical, `${sid}.jsonl`);
5920
- const dir = join42(repo, "shared", "projects", logical, sid);
6256
+ const jsonl = join45(repo, "shared", "projects", logical, `${sid}.jsonl`);
6257
+ const dir = join45(repo, "shared", "projects", logical, sid);
5921
6258
  rmSync14(jsonl, { force: true });
5922
6259
  rmSync14(dir, { recursive: true, force: true });
5923
6260
  }
@@ -6189,8 +6526,8 @@ function makeDefaultReadLine(repo) {
6189
6526
  const cache = /* @__PURE__ */ new Map();
6190
6527
  return (file, line) => {
6191
6528
  try {
6192
- const repoRoot = resolve3(repo);
6193
- const target = resolve3(repoRoot, file);
6529
+ const repoRoot = resolve4(repo);
6530
+ const target = resolve4(repoRoot, file);
6194
6531
  if (isAbsolute2(file) || target !== repoRoot && !target.startsWith(repoRoot + sep9)) {
6195
6532
  return null;
6196
6533
  }
@@ -6402,7 +6739,7 @@ function applyThenRescan(scanVerdict, repoHome2) {
6402
6739
  return next;
6403
6740
  }
6404
6741
  function allowThenRescan(append, scanVerdict, repoHome2) {
6405
- const ignPath = join43(repoHome2, ".gitleaksignore");
6742
+ const ignPath = join46(repoHome2, ".gitleaksignore");
6406
6743
  let before;
6407
6744
  try {
6408
6745
  before = readFileSync18(ignPath, "utf8");
@@ -6508,7 +6845,7 @@ function writeAnimatedDone(out, label, ms, useTTY) {
6508
6845
  `);
6509
6846
  }
6510
6847
  function resolveWorkerPath(deps = {}) {
6511
- const check = deps.existsSyncFn ?? existsSync33;
6848
+ const check = deps.existsSyncFn ?? existsSync34;
6512
6849
  const base = deps.baseUrl ?? import.meta.url;
6513
6850
  const mjs = fileURLToPath4(new URL("./nomad.worker.mjs", base));
6514
6851
  if (check(mjs)) return mjs;
@@ -6575,8 +6912,8 @@ function withSpinner(label, fn, deps) {
6575
6912
  // src/commands.doctor.gitleaks-version.ts
6576
6913
  init_color();
6577
6914
  import { execFileSync as execFileSync15 } from "node:child_process";
6578
- import { existsSync as existsSync34 } from "node:fs";
6579
- import { join as join44 } from "node:path";
6915
+ import { existsSync as existsSync35 } from "node:fs";
6916
+ import { join as join47 } from "node:path";
6580
6917
  init_config();
6581
6918
  var SEMVER_MAJOR_MINOR = /^(\d+)\.(\d+)\.\d+$/;
6582
6919
  var GITLEAKS_TIMEOUT_MS = 5e3;
@@ -6585,7 +6922,7 @@ function majorMinorOf(value) {
6585
6922
  return m === null ? null : [m[1], m[2]];
6586
6923
  }
6587
6924
  function readGitleaksVersion(run, tomlExists) {
6588
- const tomlPath = join44(repoHome(), ".gitleaks.toml");
6925
+ const tomlPath = join47(repoHome(), ".gitleaks.toml");
6589
6926
  const args = ["version"];
6590
6927
  if (tomlExists(tomlPath)) args.push("--config", tomlPath);
6591
6928
  try {
@@ -6597,7 +6934,7 @@ function readGitleaksVersion(run, tomlExists) {
6597
6934
  return null;
6598
6935
  }
6599
6936
  }
6600
- function reportGitleaksVersionCheck(section2, run = execFileSync15, tomlExists = existsSync34) {
6937
+ function reportGitleaksVersionCheck(section2, run = execFileSync15, tomlExists = existsSync35) {
6601
6938
  const raw = readGitleaksVersion(run, tomlExists);
6602
6939
  if (raw === null) return;
6603
6940
  const local = majorMinorOf(raw);
@@ -6854,8 +7191,8 @@ function gatherDoctorSections(opts) {
6854
7191
  reportLongPathsCheck(host);
6855
7192
  reportCrlfGuardCheck(host);
6856
7193
  const links = section("Shared links");
6857
- const mapPath = join45(repoHome(), "path-map.json");
6858
- const rawMap = existsSync35(mapPath) ? readJsonSafe(mapPath, mapPath, links) : null;
7194
+ const mapPath = join48(repoHome(), "path-map.json");
7195
+ const rawMap = existsSync36(mapPath) ? readJsonSafe(mapPath, mapPath, links) : null;
6859
7196
  const map = rawMap ?? { projects: {} };
6860
7197
  reportSharedLinks(links, map);
6861
7198
  reportDroppedNamesMigration(links);
@@ -6953,8 +7290,8 @@ function parseDoctorArgs(args) {
6953
7290
  // src/commands.drop-session.ts
6954
7291
  init_config();
6955
7292
  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";
7293
+ import { existsSync as existsSync38, readdirSync as readdirSync16, statSync as statSync9 } from "node:fs";
7294
+ import { join as join50, relative as relative7 } from "node:path";
6958
7295
 
6959
7296
  // src/commands.drop-session.git.ts
6960
7297
  import { execFileSync as execFileSync19 } from "node:child_process";
@@ -6997,8 +7334,8 @@ function isInIndex(rel, repo) {
6997
7334
  init_config();
6998
7335
  init_utils();
6999
7336
  init_utils_json();
7000
- import { existsSync as existsSync36 } from "node:fs";
7001
- import { join as join46 } from "node:path";
7337
+ import { existsSync as existsSync37 } from "node:fs";
7338
+ import { join as join49 } from "node:path";
7002
7339
  var SHARED_PROJECT_LOGICAL = /^shared\/projects\/([^/]+)\//;
7003
7340
  function reportScrubHint(id, matches) {
7004
7341
  const live = resolveLiveTranscript2(id, matches);
@@ -7014,8 +7351,8 @@ function reportScrubHint(id, matches) {
7014
7351
  }
7015
7352
  function resolveLiveTranscript2(id, matches) {
7016
7353
  try {
7017
- const mapPath = join46(repoHome(), "path-map.json");
7018
- if (!existsSync36(mapPath)) return null;
7354
+ const mapPath = join49(repoHome(), "path-map.json");
7355
+ if (!existsSync37(mapPath)) return null;
7019
7356
  const projects = readJson(mapPath).projects;
7020
7357
  const claude = claudeHome();
7021
7358
  for (const rel of matches) {
@@ -7023,8 +7360,8 @@ function resolveLiveTranscript2(id, matches) {
7023
7360
  if (logical === void 0) continue;
7024
7361
  const abs = projects[logical]?.[HOST];
7025
7362
  if (abs === void 0) continue;
7026
- const live = join46(claude, "projects", encodePath(abs), `${id}.jsonl`);
7027
- if (existsSync36(live)) return live;
7363
+ const live = join49(claude, "projects", encodePath(abs), `${id}.jsonl`);
7364
+ if (existsSync37(live)) return live;
7028
7365
  }
7029
7366
  return null;
7030
7367
  } catch {
@@ -7040,12 +7377,12 @@ function cmdDropSession(id) {
7040
7377
  process.exit(1);
7041
7378
  }
7042
7379
  const repo = repoHome();
7043
- if (!existsSync37(repo)) die(`repo not cloned at ${repo}`);
7380
+ if (!existsSync38(repo)) die(`repo not cloned at ${repo}`);
7044
7381
  const handle = acquireLock("drop-session");
7045
7382
  if (handle === null) process.exit(0);
7046
7383
  try {
7047
- const repoProjects = join47(repo, "shared", "projects");
7048
- if (!existsSync37(repoProjects)) {
7384
+ const repoProjects = join50(repo, "shared", "projects");
7385
+ if (!existsSync38(repoProjects)) {
7049
7386
  throw new NomadFatal(`no staged session matches ${id}`);
7050
7387
  }
7051
7388
  const matches = collectMatches(repoProjects, id, repo);
@@ -7067,14 +7404,14 @@ function cmdDropSession(id) {
7067
7404
  }
7068
7405
  function collectMatches(repoProjects, id, repo) {
7069
7406
  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);
7407
+ for (const logical of readdirSync16(repoProjects)) {
7408
+ const candidate = join50(repoProjects, logical, `${id}.jsonl`);
7409
+ if (existsSync38(candidate)) {
7410
+ matches.push(relative7(repo, candidate));
7411
+ }
7412
+ const dir = join50(repoProjects, logical, id);
7413
+ if (existsSync38(dir) && statSync9(dir).isDirectory()) {
7414
+ const dirRel = relative7(repo, dir);
7078
7415
  const staged = expandStagedDir(dirRel, repo);
7079
7416
  if (staged.length > 0) matches.push(...staged);
7080
7417
  else matches.push(dirRel);
@@ -7109,8 +7446,8 @@ function unstageOne(rel, repo) {
7109
7446
 
7110
7447
  // src/commands.pull.ts
7111
7448
  init_autostash_guard();
7112
- import { existsSync as existsSync46, mkdirSync as mkdirSync14 } from "node:fs";
7113
- import { join as join58 } from "node:path";
7449
+ import { existsSync as existsSync47, mkdirSync as mkdirSync14 } from "node:fs";
7450
+ import { join as join61 } from "node:path";
7114
7451
 
7115
7452
  // src/commands.push.sections.ts
7116
7453
  init_color();
@@ -7218,18 +7555,18 @@ init_config();
7218
7555
 
7219
7556
  // src/extras-sync.ts
7220
7557
  init_config();
7221
- import { existsSync as existsSync39, statSync as statSync10 } from "node:fs";
7222
- import { join as join50 } from "node:path";
7558
+ import { existsSync as existsSync40, statSync as statSync10 } from "node:fs";
7559
+ import { join as join53 } from "node:path";
7223
7560
  init_utils();
7224
7561
  init_utils_json();
7225
7562
 
7226
7563
  // src/extras-sync.remap.ts
7227
7564
  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";
7565
+ import { existsSync as existsSync39, mkdirSync as mkdirSync13, readdirSync as readdirSync17, readFileSync as readFileSync19, realpathSync as realpathSync7, rmSync as rmSync16 } from "node:fs";
7566
+ import { dirname as dirname14, join as join52, sep as sep11 } from "node:path";
7230
7567
 
7231
7568
  // src/extras-sync.planning-diff.ts
7232
- import { join as join48, normalize as normalize2, sep as sep10 } from "node:path";
7569
+ import { join as join51, normalize as normalize2, sep as sep10 } from "node:path";
7233
7570
  init_utils();
7234
7571
  function processRecord(fields, i, changed, deleted) {
7235
7572
  const status = fields[i];
@@ -7281,7 +7618,7 @@ function planningDeleteTargets(opts) {
7281
7618
  const { deleted } = parsePlanningDiff(raw);
7282
7619
  const logicalPrefix = "shared/extras/" + logical + "/";
7283
7620
  const prefix = logicalPrefix + ".planning/";
7284
- const planningRoot = join48(localRoot, ".planning");
7621
+ const planningRoot = join51(localRoot, ".planning");
7285
7622
  const planningRootBoundary = planningRoot + sep10;
7286
7623
  const targets = [];
7287
7624
  for (const repoPath of deleted) {
@@ -7289,7 +7626,7 @@ function planningDeleteTargets(opts) {
7289
7626
  continue;
7290
7627
  }
7291
7628
  const remainder = repoPath.slice(logicalPrefix.length);
7292
- const candidate = join48(localRoot, remainder);
7629
+ const candidate = join51(localRoot, remainder);
7293
7630
  const resolved = normalize2(candidate);
7294
7631
  if (!resolved.startsWith(planningRootBoundary)) {
7295
7632
  throw new NomadFatal(
@@ -7310,7 +7647,7 @@ function runExtrasOp(v, dryRun, paths, backup, copy) {
7310
7647
  const would = [];
7311
7648
  for (const t of eachExtrasTarget(v, counts)) {
7312
7649
  const { src, dst } = paths(t);
7313
- if (!existsSync38(src)) continue;
7650
+ if (!existsSync39(src)) continue;
7314
7651
  const item2 = `${t.logical}/${t.dirname}`;
7315
7652
  if (dryRun) {
7316
7653
  would.push(item2);
@@ -7323,15 +7660,15 @@ function runExtrasOp(v, dryRun, paths, backup, copy) {
7323
7660
  return { ...counts, done, would };
7324
7661
  }
7325
7662
  function pruneEmptyAncestors(target, planningRoot) {
7326
- let dir = dirname13(target);
7663
+ let dir = dirname14(target);
7327
7664
  while (dir !== planningRoot && dir.startsWith(planningRoot + sep11)) {
7328
7665
  try {
7329
- if (readdirSync16(dir).length > 0) break;
7666
+ if (readdirSync17(dir).length > 0) break;
7330
7667
  rmSync16(dir, { recursive: true, force: true });
7331
7668
  } catch {
7332
7669
  break;
7333
7670
  }
7334
- dir = dirname13(dir);
7671
+ dir = dirname14(dir);
7335
7672
  }
7336
7673
  }
7337
7674
  function tryRealpath(dir) {
@@ -7345,8 +7682,8 @@ function isInsidePlanningRoot(parentReal, rootReal) {
7345
7682
  return parentReal === rootReal || parentReal.startsWith(rootReal + sep11);
7346
7683
  }
7347
7684
  function deletePlanningTarget(target, planningRoot, repoCounterpart) {
7348
- if (existsSync38(repoCounterpart)) return;
7349
- const parentReal = tryRealpath(dirname13(target));
7685
+ if (existsSync39(repoCounterpart)) return;
7686
+ const parentReal = tryRealpath(dirname14(target));
7350
7687
  if (parentReal === void 0) return;
7351
7688
  const rootReal = tryRealpath(planningRoot);
7352
7689
  if (rootReal === void 0) return;
@@ -7355,7 +7692,7 @@ function deletePlanningTarget(target, planningRoot, repoCounterpart) {
7355
7692
  pruneEmptyAncestors(target, planningRoot);
7356
7693
  }
7357
7694
  function localDivergesFromPreDelete(target, pre, repoRel, repo) {
7358
- if (!existsSync38(target)) return false;
7695
+ if (!existsSync39(target)) return false;
7359
7696
  try {
7360
7697
  const preBlob = gitCaptureBuffer(["show", `${pre}:${repoRel}`], repo);
7361
7698
  return !readFileSync19(target).equals(preBlob);
@@ -7400,7 +7737,7 @@ function keptDeletePreview(v, prePostHeads, repo) {
7400
7737
  return kept;
7401
7738
  }
7402
7739
  function propagatePlanningDeletes(v, ts, prePostHeads, repo) {
7403
- const repoExtras = join49(repo, "shared", "extras");
7740
+ const repoExtras = join52(repo, "shared", "extras");
7404
7741
  for (const t of eachExtrasTarget(v, { unmapped: 0, skipped: 0 })) {
7405
7742
  if (t.dirname !== ".planning") continue;
7406
7743
  let raw;
@@ -7415,14 +7752,14 @@ function propagatePlanningDeletes(v, ts, prePostHeads, repo) {
7415
7752
  }
7416
7753
  const pairs = deletePairsFor(t, raw);
7417
7754
  if (pairs.length === 0) continue;
7418
- backupExtrasWrite(join49(t.localRoot, t.dirname), ts, t.localRoot);
7419
- const planningRoot = join49(t.localRoot, ".planning");
7755
+ backupExtrasWrite(join52(t.localRoot, t.dirname), ts, t.localRoot);
7756
+ const planningRoot = join52(t.localRoot, ".planning");
7420
7757
  for (const { target, relToLocal, repoRel } of pairs) {
7421
7758
  if (localDivergesFromPreDelete(target, prePostHeads.pre, repoRel, repo)) {
7422
7759
  warn(keptDeleteWarnLine(t.logical, relToLocal));
7423
7760
  continue;
7424
7761
  }
7425
- deletePlanningTarget(target, planningRoot, join49(repoExtras, t.logical, relToLocal));
7762
+ deletePlanningTarget(target, planningRoot, join52(repoExtras, t.logical, relToLocal));
7426
7763
  }
7427
7764
  }
7428
7765
  }
@@ -7431,14 +7768,14 @@ function remapExtrasPush(ts, opts = {}) {
7431
7768
  const v = loadValidatedExtras({ missingMsg: "no path-map.json; skipping extras push" });
7432
7769
  if (v === null) return { unmapped: 0, skipped: 0, pushed: [], wouldPush: [] };
7433
7770
  const repo = repoHome();
7434
- const repoExtras = join49(repo, "shared", "extras");
7771
+ const repoExtras = join52(repo, "shared", "extras");
7435
7772
  if (!dryRun) mkdirSync13(repoExtras, { recursive: true });
7436
7773
  const { unmapped, skipped, done, would } = runExtrasOp(
7437
7774
  v,
7438
7775
  dryRun,
7439
- ({ localRoot, logical, dirname: dirname14 }) => ({
7440
- src: join49(localRoot, dirname14),
7441
- dst: join49(repoExtras, logical, dirname14)
7776
+ ({ localRoot, logical, dirname: dirname15 }) => ({
7777
+ src: join52(localRoot, dirname15),
7778
+ dst: join52(repoExtras, logical, dirname15)
7442
7779
  }),
7443
7780
  (dst) => backupRepoWrite(dst, ts, repo),
7444
7781
  // Push copy routing per extra type:
@@ -7451,7 +7788,7 @@ function remapExtrasPush(ts, opts = {}) {
7451
7788
  // config.never-sync.ts)
7452
7789
  // remains the hard security boundary.
7453
7790
  // All others: copyExtrasFiltered with per-extra denylist.
7454
- (src, dst, dirname14) => dirname14 === ".planning" ? copyExtrasOverlayFiltered(src, dst, extrasDenySet(dirname14)) : copyExtrasFiltered(src, dst, extrasDenySet(dirname14))
7791
+ (src, dst, dirname15) => dirname15 === ".planning" ? copyExtrasOverlayFiltered(src, dst, extrasDenySet(dirname15)) : copyExtrasFiltered(src, dst, extrasDenySet(dirname15))
7455
7792
  );
7456
7793
  return { unmapped, skipped, pushed: done, wouldPush: would };
7457
7794
  }
@@ -7467,9 +7804,9 @@ function remapExtrasPull(ts, opts = {}) {
7467
7804
  const { unmapped, skipped, done, would } = runExtrasOp(
7468
7805
  v,
7469
7806
  dryRun,
7470
- ({ localRoot, logical, dirname: dirname14 }) => ({
7471
- src: join49(repo, "shared", "extras", logical, dirname14),
7472
- dst: join49(localRoot, dirname14)
7807
+ ({ localRoot, logical, dirname: dirname15 }) => ({
7808
+ src: join52(repo, "shared", "extras", logical, dirname15),
7809
+ dst: join52(localRoot, dirname15)
7473
7810
  }),
7474
7811
  // Snapshot the host-side dst BEFORE the copy step touches it. Anchor on
7475
7812
  // localRoot so the backup tree mirrors the project layout.
@@ -7487,12 +7824,12 @@ function remapExtrasPull(ts, opts = {}) {
7487
7824
  // copyExtrasFileSkipDiverged keeps a locally-edited file rather than
7488
7825
  // clobbering it, so the divergence WARN's keep-local promise holds for
7489
7826
  // every extra, not just `.planning`.
7490
- (src, dst, dirname14) => {
7491
- if (dirname14 === ".claude")
7492
- return copyExtrasFilteredPreserving(src, dst, extrasDenySet(dirname14));
7493
- if (dirname14 === ".planning") {
7827
+ (src, dst, dirname15) => {
7828
+ if (dirname15 === ".claude")
7829
+ return copyExtrasFilteredPreserving(src, dst, extrasDenySet(dirname15));
7830
+ if (dirname15 === ".planning") {
7494
7831
  const divergedSet = new Set(listDivergingModified(dst, src));
7495
- return copyExtrasOverlaySkipDiverged(src, dst, extrasDenySet(dirname14), divergedSet);
7832
+ return copyExtrasOverlaySkipDiverged(src, dst, extrasDenySet(dirname15), divergedSet);
7496
7833
  }
7497
7834
  return copyExtrasFileSkipDiverged(src, dst);
7498
7835
  }
@@ -7516,20 +7853,20 @@ function divergenceCheckExtras(ts, prePostHeads) {
7516
7853
  const v = loadValidatedExtras({});
7517
7854
  if (v === null) return 0;
7518
7855
  const counts = { unmapped: 0, skipped: 0 };
7519
- const backupRoot = join50(backupBase(), ts, "extras");
7856
+ const backupRoot = join53(backupBase(), ts, "extras");
7520
7857
  const repo = repoHome();
7521
7858
  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;
7859
+ for (const { logical, localRoot, dirname: dirname15 } of eachExtrasTarget(v, counts)) {
7860
+ const local = join53(localRoot, dirname15);
7861
+ const repoEntry = join53(repo, "shared", "extras", logical, dirname15);
7862
+ if (!existsSync40(local) || !existsSync40(repoEntry)) continue;
7526
7863
  const modified = listDivergingModified(local, repoEntry);
7527
7864
  if (modified.length === 0) continue;
7528
7865
  divergedCount += modified.length;
7529
- const projectBackupRoot = join50(backupRoot, encodePath(localRoot));
7866
+ const projectBackupRoot = join53(backupRoot, encodePath(localRoot));
7530
7867
  warn(
7531
7868
  divergenceWarnLine({
7532
- dirname: dirname14,
7869
+ dirname: dirname15,
7533
7870
  logical,
7534
7871
  isDir: statSync10(local).isDirectory(),
7535
7872
  count: modified.length,
@@ -7548,8 +7885,8 @@ function divergenceCheckExtras(ts, prePostHeads) {
7548
7885
 
7549
7886
  // src/links.baseline.ts
7550
7887
  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";
7888
+ import { lstatSync as lstatSync18, readdirSync as readdirSync18 } from "node:fs";
7889
+ import { join as join54, relative as relative8, sep as sep12 } from "node:path";
7553
7890
  init_utils();
7554
7891
  var SHARED_BASELINE_KIND = "shared-links-baseline/1";
7555
7892
  var SHARED_BASELINE_CONFIG_HASH = "not-applicable";
@@ -7560,13 +7897,13 @@ function readSharedBaseline() {
7560
7897
  return parsed;
7561
7898
  }
7562
7899
  function baselineKey(claude, abs) {
7563
- return relative7(claude, abs).split(sep12).join("/");
7900
+ return relative8(claude, abs).split(sep12).join("/");
7564
7901
  }
7565
7902
  function addLocalPath(abs, claude, scan) {
7566
7903
  const key = baselineKey(claude, abs);
7567
7904
  let st;
7568
7905
  try {
7569
- st = lstatSync16(abs, { throwIfNoEntry: false });
7906
+ st = lstatSync18(abs, { throwIfNoEntry: false });
7570
7907
  } catch {
7571
7908
  scan.declined.push(key);
7572
7909
  return;
@@ -7585,13 +7922,13 @@ function addLocalPath(abs, claude, scan) {
7585
7922
  function collectSharedFiles(dir, claude, scan) {
7586
7923
  let entries;
7587
7924
  try {
7588
- entries = readdirSync17(dir);
7925
+ entries = readdirSync18(dir);
7589
7926
  } catch {
7590
7927
  scan.declined.push(baselineKey(claude, dir));
7591
7928
  return;
7592
7929
  }
7593
7930
  for (const entry of entries) {
7594
- const abs = join51(dir, entry);
7931
+ const abs = join54(dir, entry);
7595
7932
  if (isDeniedName(ALWAYS_NEVER_SYNC, entry)) {
7596
7933
  scan.declined.push(baselineKey(claude, abs));
7597
7934
  continue;
@@ -7607,7 +7944,7 @@ function enumerateLocalSharedScan(map, opts = {}) {
7607
7944
  scan.declined.push(name);
7608
7945
  continue;
7609
7946
  }
7610
- addLocalPath(join51(claude, name), claude, scan);
7947
+ addLocalPath(join54(claude, name), claude, scan);
7611
7948
  }
7612
7949
  return scan;
7613
7950
  }
@@ -7619,7 +7956,7 @@ function buildSharedBaseline(map, opts = {}) {
7619
7956
  const files = {};
7620
7957
  for (const [key, st] of Object.entries(enumerateLocalSharedFiles(map, opts))) {
7621
7958
  try {
7622
- files[key] = { size: st.size, mtime: st.mtime, hash: hashFile(join51(claude, key)) };
7959
+ files[key] = { size: st.size, mtime: st.mtime, hash: hashFile(join54(claude, key)) };
7623
7960
  } catch {
7624
7961
  }
7625
7962
  }
@@ -7636,8 +7973,8 @@ function writeSharedBaseline(map, opts = {}) {
7636
7973
 
7637
7974
  // src/commands.pull.win32.ts
7638
7975
  init_color();
7639
- import { existsSync as existsSync44 } from "node:fs";
7640
- import { join as join56 } from "node:path";
7976
+ import { existsSync as existsSync45 } from "node:fs";
7977
+ import { join as join59 } from "node:path";
7641
7978
 
7642
7979
  // src/commands.pull.recovery.git.ts
7643
7980
  init_utils();
@@ -7700,8 +8037,8 @@ function gitProbe(args, repo) {
7700
8037
 
7701
8038
  // src/links.deletions.ts
7702
8039
  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";
8040
+ import { existsSync as existsSync41, lstatSync as lstatSync19, rmSync as rmSync17 } from "node:fs";
8041
+ import { join as join55, relative as relative9, resolve as resolve5, sep as sep13 } from "node:path";
7705
8042
  init_utils();
7706
8043
  init_utils_fs();
7707
8044
  function isUnknown(declined, key) {
@@ -7711,15 +8048,15 @@ function deletionFor(key, names, claude, sharedRoot) {
7711
8048
  const segments = key.split("/");
7712
8049
  const name = segments[0];
7713
8050
  if (!names.has(name)) return null;
7714
- if (!existsSync40(join52(claude, name))) return null;
8051
+ if (!existsSync41(join55(claude, name))) return null;
7715
8052
  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);
8053
+ const repoPath = resolve5(sharedRoot, key);
8054
+ const rel = relative9(join55(sharedRoot, name), repoPath);
7718
8055
  if (rel === "" || rel === ".." || rel.startsWith(`..${sep13}`)) return null;
7719
8056
  if (segments.some((segment) => segment === "" || segment === "." || segment === ".."))
7720
8057
  return null;
7721
- if (!existsSync40(repoPath)) return null;
7722
- return { kind: "deletion", name, localPath: join52(claude, key), repoPath };
8058
+ if (!existsSync41(repoPath)) return null;
8059
+ return { kind: "deletion", name, localPath: join55(claude, key), repoPath };
7723
8060
  }
7724
8061
  function planSharedLinkDeletions(map, opts = {}) {
7725
8062
  if (process.platform !== "win32") return [];
@@ -7727,7 +8064,7 @@ function planSharedLinkDeletions(map, opts = {}) {
7727
8064
  const baseline = readSharedBaseline();
7728
8065
  if (baseline === null) return [];
7729
8066
  const claude = claudeHome();
7730
- const sharedRoot = join52(repoHome(), "shared");
8067
+ const sharedRoot = join55(repoHome(), "shared");
7731
8068
  const linkNames = opts.linkNames ?? allSharedLinks(map);
7732
8069
  const scan = enumerateLocalSharedScan(map, { linkNames });
7733
8070
  const present = new Set(Object.keys(scan.files).map((key) => key.toLowerCase()));
@@ -7748,7 +8085,7 @@ function applySharedLinkDeletions(map, ts, opts = {}) {
7748
8085
  const removed = [];
7749
8086
  for (const entry of planSharedLinkDeletions(map, opts)) {
7750
8087
  try {
7751
- if (!lstatSync17(entry.repoPath).isFile()) continue;
8088
+ if (!lstatSync19(entry.repoPath).isFile()) continue;
7752
8089
  backupRepoWrite(entry.repoPath, ts, repo);
7753
8090
  rmSync17(entry.repoPath, { force: true });
7754
8091
  removed.push(entry);
@@ -7761,8 +8098,8 @@ function applySharedLinkDeletions(map, ts, opts = {}) {
7761
8098
 
7762
8099
  // src/links.mirror.ts
7763
8100
  init_config();
7764
- import { existsSync as existsSync41, lstatSync as lstatSync18, rmSync as rmSync18 } from "node:fs";
7765
- import { join as join53 } from "node:path";
8101
+ import { existsSync as existsSync42, lstatSync as lstatSync20, rmSync as rmSync18 } from "node:fs";
8102
+ import { join as join56 } from "node:path";
7766
8103
  init_utils();
7767
8104
  init_utils_fs();
7768
8105
  function emitMirror(onPreview, name, localPath, repoPath) {
@@ -7776,16 +8113,22 @@ function emitMirrorWet(onPreview, name, localPath, repoPath) {
7776
8113
  if (onPreview) onPreview({ kind: "mirror", name, localPath, repoPath });
7777
8114
  }
7778
8115
  function notShared(policy, target) {
7779
- return !policy.adoptNew && !existsSync41(target);
8116
+ return !policy.adoptNew && !existsSync42(target);
7780
8117
  }
7781
8118
  function mirrorOneSharedName(name, claude, repo, policy, opts) {
7782
- const localPath = join53(claude, name);
7783
- const target = join53(repo, "shared", name);
8119
+ const localPath = join56(claude, name);
8120
+ const target = join56(repo, "shared", name);
7784
8121
  let stat;
7785
8122
  try {
7786
- stat = lstatSync18(localPath, { throwIfNoEntry: false });
8123
+ stat = lstatSync20(localPath, { throwIfNoEntry: false });
7787
8124
  } catch (err) {
7788
8125
  if (notShared(policy, target)) return;
8126
+ if (opts.dryRun === true) {
8127
+ warn(
8128
+ `${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`
8129
+ );
8130
+ return;
8131
+ }
7789
8132
  warn(
7790
8133
  `${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
8134
  );
@@ -7800,9 +8143,9 @@ function mirrorOneSharedName(name, claude, repo, policy, opts) {
7800
8143
  }
7801
8144
  if (policy.backupTs !== void 0) backupRepoWrite(target, policy.backupTs, repo);
7802
8145
  if (policy.overlay && stat.isDirectory()) {
7803
- copyExtrasOverlayFiltered(localPath, target, NEVER_SYNC);
8146
+ copyExtrasOverlayFiltered(localPath, target, ALWAYS_NEVER_SYNC);
7804
8147
  } else {
7805
- copyExtrasFiltered(localPath, target, NEVER_SYNC);
8148
+ copyExtrasFiltered(localPath, target, ALWAYS_NEVER_SYNC);
7806
8149
  }
7807
8150
  emitMirrorWet(opts.onPreview, name, localPath, target);
7808
8151
  }
@@ -7817,20 +8160,20 @@ function mirrorSharedNames(map, policy, opts = {}) {
7817
8160
  }
7818
8161
  }
7819
8162
  function syncSharedLinksPush(map, opts = {}) {
7820
- mirrorSharedNames(map, { adoptNew: true, overlay: false }, opts);
8163
+ mirrorSharedNames(map, { adoptNew: false, overlay: false }, opts);
7821
8164
  }
7822
8165
  function stageLocalSharedEdits(map, ts, opts = {}) {
7823
8166
  mirrorSharedNames(map, { adoptNew: false, overlay: true, backupTs: ts }, opts);
7824
8167
  }
7825
8168
  function presentAt(abs) {
7826
8169
  try {
7827
- return lstatSync18(abs, { throwIfNoEntry: false }) !== void 0;
8170
+ return lstatSync20(abs, { throwIfNoEntry: false }) !== void 0;
7828
8171
  } catch {
7829
8172
  return true;
7830
8173
  }
7831
8174
  }
7832
8175
  function removeUntrackedDenied(repo, path, segment, ts) {
7833
- const abs = join53(repo, path);
8176
+ const abs = join56(repo, path);
7834
8177
  const denied = `the path segment "${segment}" is on the never-sync list`;
7835
8178
  if (!presentAt(abs)) {
7836
8179
  warn(
@@ -7838,7 +8181,7 @@ function removeUntrackedDenied(repo, path, segment, ts) {
7838
8181
  );
7839
8182
  return;
7840
8183
  }
7841
- const snapshotted = existsSync41(abs);
8184
+ const snapshotted = existsSync42(abs);
7842
8185
  try {
7843
8186
  backupRepoWrite(abs, ts, repo);
7844
8187
  } catch (err) {
@@ -7874,7 +8217,7 @@ function reportTrackedDenied(repo, path, segment) {
7874
8217
  );
7875
8218
  return;
7876
8219
  }
7877
- if (!existsSync41(join53(repo, path))) return;
8220
+ if (!existsSync42(join56(repo, path))) return;
7878
8221
  warn(
7879
8222
  `${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
8223
  );
@@ -7892,8 +8235,8 @@ function revertDeniedMirrorPaths(repo, status, ts) {
7892
8235
 
7893
8236
  // src/preview.ts
7894
8237
  init_config();
7895
- import { existsSync as existsSync43 } from "node:fs";
7896
- import { join as join55 } from "node:path";
8238
+ import { existsSync as existsSync44 } from "node:fs";
8239
+ import { join as join58 } from "node:path";
7897
8240
 
7898
8241
  // node_modules/diff/libesm/diff/base.js
7899
8242
  var Diff = class {
@@ -8168,16 +8511,16 @@ function diffLinesToUnified(oldStr, newStr) {
8168
8511
 
8169
8512
  // src/preview.skills.ts
8170
8513
  init_config();
8171
- import { existsSync as existsSync42, readdirSync as readdirSync18 } from "node:fs";
8172
- import { join as join54 } from "node:path";
8514
+ import { existsSync as existsSync43, readdirSync as readdirSync19 } from "node:fs";
8515
+ import { join as join57 } from "node:path";
8173
8516
  function buildSkillsPreviewSection() {
8174
8517
  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"));
8518
+ const sharedSkills = join57(repoHome(), "shared", "skills");
8519
+ if (!existsSync43(sharedSkills)) return s;
8520
+ const sharedNames = readdirSync19(sharedSkills, { encoding: "utf8" }).filter((name) => !isSkillExcluded(name)).sort((a, b) => a.localeCompare(b, "en"));
8178
8521
  for (const name of sharedNames) addItem(s, name);
8179
- const localSkills = join54(claudeHome(), "skills");
8180
- const localOnly = existsSync42(localSkills) ? readdirSync18(localSkills, { encoding: "utf8" }).filter(
8522
+ const localSkills = join57(claudeHome(), "skills");
8523
+ const localOnly = existsSync43(localSkills) ? readdirSync19(localSkills, { encoding: "utf8" }).filter(
8181
8524
  (name) => !isSkillExcluded(name) && !sharedNames.includes(name)
8182
8525
  ).length : 0;
8183
8526
  if (localOnly > 0) {
@@ -8199,7 +8542,7 @@ function diffJsonStrings(currentJsonText, newJsonText) {
8199
8542
  return lines.join("\n");
8200
8543
  }
8201
8544
  function readJsonOrNull(path) {
8202
- if (!existsSync43(path)) return null;
8545
+ if (!existsSync44(path)) return null;
8203
8546
  try {
8204
8547
  return readJson(path);
8205
8548
  } catch {
@@ -8213,12 +8556,12 @@ function previewSettings(basePath, hostPath, settingsPath) {
8213
8556
  }
8214
8557
  const notes = [];
8215
8558
  const hostOverrides = readJsonOrNull(hostPath);
8216
- if (hostOverrides === null && existsSync43(hostPath)) {
8559
+ if (hostOverrides === null && existsSync44(hostPath)) {
8217
8560
  notes.push(`malformed hosts/${HOST}.json; ignoring overrides`);
8218
8561
  }
8219
8562
  const merged = stripGsdHookEntries(deepMerge(base, hostOverrides ?? {}));
8220
8563
  const current = readJsonOrNull(settingsPath);
8221
- if (current === null && existsSync43(settingsPath)) {
8564
+ if (current === null && existsSync44(settingsPath)) {
8222
8565
  return { diff: "", notes: [...notes, "malformed; skipping diff"] };
8223
8566
  }
8224
8567
  const strippedCurrent = stripGsdHookEntries(current ?? {});
@@ -8285,9 +8628,9 @@ function computePreview(ts, map, verb = "pull", plans) {
8285
8628
  onPreview: (e) => addItem(links, formatLinkRow(e))
8286
8629
  });
8287
8630
  const settingsResult = previewSettings(
8288
- join55(repo, "shared", "settings.base.json"),
8289
- join55(repo, "hosts", `${HOST}.json`),
8290
- join55(claude, "settings.json")
8631
+ join58(repo, "shared", "settings.base.json"),
8632
+ join58(repo, "hosts", `${HOST}.json`),
8633
+ join58(claude, "settings.json")
8291
8634
  );
8292
8635
  const settingsSection = buildSettingsSectionForPreview(settingsResult);
8293
8636
  const sessions = section("Sessions");
@@ -8303,7 +8646,7 @@ function computePreview(ts, map, verb = "pull", plans) {
8303
8646
  const extras = section("Extras");
8304
8647
  let extrasSkipped = 0;
8305
8648
  let extrasUnmapped = 0;
8306
- if (existsSync43(join55(repo, "path-map.json")) && existsSync43(join55(repo, "shared", "extras"))) {
8649
+ if (existsSync44(join58(repo, "path-map.json")) && existsSync44(join58(repo, "shared", "extras"))) {
8307
8650
  const extrasResult = remapExtrasPull(ts, { dryRun: true });
8308
8651
  for (const entry of extrasResult.wouldPull) {
8309
8652
  addItem(extras, entry);
@@ -8324,7 +8667,7 @@ function computePreview(ts, map, verb = "pull", plans) {
8324
8667
  init_utils();
8325
8668
  init_utils_json();
8326
8669
  function readMapForMirror(mapPath) {
8327
- if (!existsSync44(mapPath)) return { projects: {} };
8670
+ if (!existsSync45(mapPath)) return { projects: {} };
8328
8671
  try {
8329
8672
  return readPathMap(mapPath);
8330
8673
  } catch {
@@ -8371,7 +8714,7 @@ function reconcileSharedLinksBeforePull(repo, ts) {
8371
8714
  let namesDerived = false;
8372
8715
  let derivedSharedDirs;
8373
8716
  try {
8374
- const map = readMapForMirror(join56(repo, "path-map.json"));
8717
+ const map = readMapForMirror(join59(repo, "path-map.json"));
8375
8718
  if (map !== null) {
8376
8719
  linkNames = allSharedLinks(map);
8377
8720
  namesDerived = true;
@@ -8410,7 +8753,7 @@ function buildMirrorSection(events, discard) {
8410
8753
  function describeSkippedMirrorDiscard(repo, ts) {
8411
8754
  if (process.platform !== "win32") return null;
8412
8755
  try {
8413
- const map = readMapForMirror(join56(repo, "path-map.json"));
8756
+ const map = readMapForMirror(join59(repo, "path-map.json"));
8414
8757
  const linkNames = map !== null ? allSharedLinks(map, { quiet: true }) : void 0;
8415
8758
  const captures = [];
8416
8759
  stageLocalSharedEdits(map, ts, {
@@ -8419,13 +8762,13 @@ function describeSkippedMirrorDiscard(repo, ts) {
8419
8762
  linkNames
8420
8763
  });
8421
8764
  if (captures.length === 0) return null;
8422
- return { count: captures.length, backupPath: join56(backupBase(), ts) };
8765
+ return { count: captures.length, backupPath: join59(backupBase(), ts) };
8423
8766
  } catch {
8424
8767
  return null;
8425
8768
  }
8426
8769
  }
8427
8770
  function planSharedReconcileBeforePull(repo, ts) {
8428
- const map = readMapForMirror(join56(repo, "path-map.json"));
8771
+ const map = readMapForMirror(join59(repo, "path-map.json"));
8429
8772
  const captures = [];
8430
8773
  const linkNames = process.platform === "win32" && map !== null ? allSharedLinks(map) : void 0;
8431
8774
  stageLocalSharedEdits(map, ts, { dryRun: true, onPreview: (e) => captures.push(e), linkNames });
@@ -8440,8 +8783,8 @@ function planSharedReconcileBeforePull(repo, ts) {
8440
8783
  // src/commands.pull.collision.ts
8441
8784
  init_config();
8442
8785
  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";
8786
+ import { existsSync as existsSync46, lstatSync as lstatSync21, readFileSync as readFileSync20, rmSync as rmSync19 } from "node:fs";
8787
+ import { join as join60 } from "node:path";
8445
8788
  init_utils();
8446
8789
  var SHARED_PREFIX = /^shared\//;
8447
8790
  var EXTENSION = /(\.[^.]+)?$/;
@@ -8497,24 +8840,24 @@ function isContainedMirrorPath(rel) {
8497
8840
  }
8498
8841
  function matchesLocalOriginal(repo, rel) {
8499
8842
  try {
8500
- const local = join57(claudeHome(), rel.replace(SHARED_PREFIX, ""));
8501
- return readFileSync20(join57(repo, rel)).equals(readFileSync20(local));
8843
+ const local = join60(claudeHome(), rel.replace(SHARED_PREFIX, ""));
8844
+ return readFileSync20(join60(repo, rel)).equals(readFileSync20(local));
8502
8845
  } catch {
8503
8846
  return false;
8504
8847
  }
8505
8848
  }
8506
8849
  function removeMirroredCopies(repo, repoRelPaths) {
8507
8850
  for (const rel of repoRelPaths.filter(isContainedMirrorPath)) {
8508
- const abs = join57(repo, rel);
8851
+ const abs = join60(repo, rel);
8509
8852
  try {
8510
- if (lstatSync19(abs, { throwIfNoEntry: false })?.isFile() !== true) continue;
8853
+ if (lstatSync21(abs, { throwIfNoEntry: false })?.isFile() !== true) continue;
8511
8854
  if (!matchesLocalOriginal(repo, rel)) continue;
8512
8855
  rmSync19(abs, { force: true });
8513
8856
  } catch (err) {
8514
8857
  warn(`could not remove nomad's copy at ${abs}: ${err.message}`);
8515
8858
  }
8516
8859
  }
8517
- return repoRelPaths.every((rel) => !existsSync45(join57(repo, rel)));
8860
+ return repoRelPaths.every((rel) => !existsSync46(join60(repo, rel)));
8518
8861
  }
8519
8862
  function pullWithCollisionRunbook(repo, mirrored) {
8520
8863
  try {
@@ -8596,7 +8939,27 @@ function recoverForceRemote(mode, repo) {
8596
8939
  const { synced } = classifyTouched(allTouched);
8597
8940
  if (synced.length > 0) {
8598
8941
  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."
8942
+ "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
8943
+ // carrying a newline or a terminal control sequence would otherwise
8944
+ // split or rewrite the recovery steps printed below it. The
8945
+ // classification above uses the raw value.
8946
+ synced.map((p) => ` ${JSON.stringify(p)}`).join("\n") + `
8947
+ The comparison runs both ways, so a path can be listed because origin/main is ahead of
8948
+ you, with nothing of yours at risk on it.
8949
+
8950
+ Manual recovery. Step 1 is not optional: this refusal happens BEFORE recovery parks your
8951
+ local commits, so nothing has been saved for you yet and step 3 discards whatever is not
8952
+ on another ref.
8953
+ 1. git branch nomad-rescue HEAD (keeps every local commit)
8954
+ 2. copy any uncommitted work above OUT of the repo (it exists nowhere else)
8955
+ 3. git reset --hard origin/main (this is what clears the refusal)
8956
+ 4. nomad pull
8957
+
8958
+ Copying or moving the files alone does not clear it: git still reports a moved file as a
8959
+ deletion, and a committed change is still ahead of origin/main. The in-progress rebase or
8960
+ merge has ALREADY been aborted, so re-running 'nomad pull --force-remote' now behaves as
8961
+ an ordinary pull.
8962
+ (see FAQ: "Every pull fails with unmerged files")`
8600
8963
  );
8601
8964
  }
8602
8965
  const branchName = freshStrandedBranch(repo);
@@ -8696,15 +9059,23 @@ function handleWedge(repo, forceRemote) {
8696
9059
  return die(wedgeMarkerRunbookText(state), { code: EXIT.CONFLICT });
8697
9060
  }
8698
9061
  function runPullCore(opts = {}) {
9062
+ const backup = backupBase();
9063
+ const ts = freshBackupTs(backup);
9064
+ try {
9065
+ return runPullWithBackupTs(ts, opts);
9066
+ } finally {
9067
+ discardEmptyBackupDir(join61(backup, ts));
9068
+ }
9069
+ }
9070
+ function runPullWithBackupTs(ts, opts) {
8699
9071
  const dryRun = opts.dryRun === true;
8700
9072
  const forceRemote = opts.forceRemote === true;
8701
9073
  const compose = opts.compose === true;
8702
9074
  const repo = repoHome();
8703
9075
  const backup = backupBase();
8704
- const ts = freshBackupTs(backup);
8705
9076
  const recovered = handleWedge(repo, forceRemote);
8706
9077
  if (!dryRun) {
8707
- const backupRoot = join58(backup, ts);
9078
+ const backupRoot = join61(backup, ts);
8708
9079
  try {
8709
9080
  mkdirSync14(backupRoot, { recursive: true });
8710
9081
  } catch (err) {
@@ -8727,8 +9098,8 @@ function runPullCore(opts = {}) {
8727
9098
  pullWithCollisionRunbook(repo, mirrored);
8728
9099
  });
8729
9100
  assertNoAutostashConflict(repo, "nomad pull");
8730
- const mapPath = join58(repo, "path-map.json");
8731
- const map = existsSync46(mapPath) ? readPathMap(mapPath) : { projects: {} };
9101
+ const mapPath = join61(repo, "path-map.json");
9102
+ const map = existsSync47(mapPath) ? readPathMap(mapPath) : { projects: {} };
8732
9103
  const divergedKeptLocal = divergenceCheckExtras(ts, dryRun ? prePostHeads : void 0);
8733
9104
  if (dryRun) {
8734
9105
  computePreview(ts, map, "pull", plansAgainst(sharedPlans, map));
@@ -8755,8 +9126,8 @@ function runPullCore(opts = {}) {
8755
9126
  }
8756
9127
  function cmdPull(opts = {}) {
8757
9128
  const repo = repoHome();
8758
- if (!existsSync46(repo)) die(`repo not cloned at ${repo}`);
8759
- if (!existsSync46(join58(repo, "shared", "settings.base.json"))) {
9129
+ if (!existsSync47(repo)) die(`repo not cloned at ${repo}`);
9130
+ if (!existsSync47(join61(repo, "shared", "settings.base.json"))) {
8760
9131
  die("repo not initialized; run 'nomad init' to scaffold");
8761
9132
  }
8762
9133
  const handle = acquireLock("pull");
@@ -8779,13 +9150,13 @@ function cmdPull(opts = {}) {
8779
9150
 
8780
9151
  // src/commands.push.ts
8781
9152
  init_config();
8782
- import { existsSync as existsSync50 } from "node:fs";
8783
- import { join as join63 } from "node:path";
9153
+ import { existsSync as existsSync51 } from "node:fs";
9154
+ import { join as join66 } from "node:path";
8784
9155
 
8785
9156
  // src/commands.push.selection.ts
8786
9157
  init_config();
8787
- import { existsSync as existsSync47, statSync as statSync11 } from "node:fs";
8788
- import { join as join59 } from "node:path";
9158
+ import { existsSync as existsSync48, statSync as statSync11 } from "node:fs";
9159
+ import { join as join62 } from "node:path";
8789
9160
  init_utils_json();
8790
9161
  function buildCurrentMap(map) {
8791
9162
  const current = {};
@@ -8794,8 +9165,8 @@ function buildCurrentMap(map) {
8794
9165
  for (const [, hostMap] of Object.entries(map.projects)) {
8795
9166
  const localPath = hostMap[HOST];
8796
9167
  if (!localPath) continue;
8797
- const localDir = join59(claude, "projects", encodePath(localPath));
8798
- if (!existsSync47(localDir)) continue;
9168
+ const localDir = join62(claude, "projects", encodePath(localPath));
9169
+ if (!existsSync48(localDir)) continue;
8799
9170
  for (const f of enumerateSourceFiles(localDir)) {
8800
9171
  const st = statSync11(f);
8801
9172
  current[f] = { size: st.size, mtime: st.mtimeMs };
@@ -8826,7 +9197,7 @@ function computePushSelection(map, old, scannerVersion, configHash, fullScan) {
8826
9197
  };
8827
9198
  }
8828
9199
  function loadSelectionForPush(mapPath, old, scannerVersion, configHash, fullScan) {
8829
- const map = existsSync47(mapPath) ? readPathMap(mapPath) : null;
9200
+ const map = existsSync48(mapPath) ? readPathMap(mapPath) : null;
8830
9201
  const { selection, newManifest } = computePushSelection(
8831
9202
  map,
8832
9203
  old,
@@ -8919,14 +9290,14 @@ function enforceAllowList(statusPorcelain, map) {
8919
9290
 
8920
9291
  // src/commands.push.settings.ts
8921
9292
  init_config();
8922
- import { existsSync as existsSync48 } from "node:fs";
8923
- import { join as join60 } from "node:path";
9293
+ import { existsSync as existsSync49 } from "node:fs";
9294
+ import { join as join63 } from "node:path";
8924
9295
  init_utils();
8925
9296
  init_utils_fs();
8926
9297
  init_utils_json();
8927
9298
  function stripGsdHooksFromBase(repo, backup) {
8928
- const basePath = join60(repo, "shared", "settings.base.json");
8929
- if (!existsSync48(basePath)) return;
9299
+ const basePath = join63(repo, "shared", "settings.base.json");
9300
+ if (!existsSync49(basePath)) return;
8930
9301
  let base;
8931
9302
  try {
8932
9303
  base = readJson(basePath);
@@ -8940,14 +9311,14 @@ function stripGsdHooksFromBase(repo, backup) {
8940
9311
  writeJsonAtomic(basePath, stripped);
8941
9312
  }
8942
9313
  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;
9314
+ const basePath = join63(repo, "shared", "settings.base.json");
9315
+ if (!existsSync49(basePath)) return;
9316
+ const settingsPath = join63(claudeHome(), "settings.json");
9317
+ if (!existsSync49(settingsPath)) return;
8947
9318
  try {
8948
9319
  const base = readJson(basePath);
8949
- const hostPath = join60(repo, "hosts", `${HOST}.json`);
8950
- const overrides = existsSync48(hostPath) ? readJson(hostPath) : {};
9320
+ const hostPath = join63(repo, "hosts", `${HOST}.json`);
9321
+ const overrides = existsSync49(hostPath) ? readJson(hostPath) : {};
8951
9322
  const merged = deepMerge(base, overrides);
8952
9323
  const settings = readJson(settingsPath);
8953
9324
  const { ahead } = classifySettingsDrift(merged, settings);
@@ -8964,12 +9335,12 @@ function reportSettingsAheadDrift(repo) {
8964
9335
  // src/commands.push.guards.ts
8965
9336
  init_push_checks();
8966
9337
  init_utils();
8967
- import { join as join61, relative as relative9 } from "node:path";
9338
+ import { join as join64, relative as relative10 } from "node:path";
8968
9339
  function guardGitlinks(repo) {
8969
- const gitlinks = findGitlinks(join61(repo, "shared"));
9340
+ const gitlinks = findGitlinks(join64(repo, "shared"));
8970
9341
  if (gitlinks.length === 0) return;
8971
9342
  for (const p of gitlinks) {
8972
- const rel = relative9(repo, p).replaceAll("\\", "/");
9343
+ const rel = relative10(repo, p).replaceAll("\\", "/");
8973
9344
  fail(`gitlink: ${rel} would push as submodule (run: rm -rf ${rel} or remove the nested repo)`);
8974
9345
  }
8975
9346
  const noun = gitlinks.length === 1 ? "entry" : "entries";
@@ -9076,9 +9447,9 @@ init_color();
9076
9447
  init_config();
9077
9448
  init_config_sharedDirs_guard();
9078
9449
  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";
9450
+ import { copyFileSync, existsSync as existsSync50, lstatSync as lstatSync22, mkdirSync as mkdirSync15, readdirSync as readdirSync20, rmSync as rmSync20 } from "node:fs";
9080
9451
  import { homedir as homedir7 } from "node:os";
9081
- import { join as join62, relative as relative10, sep as sep14 } from "node:path";
9452
+ import { join as join65, relative as relative11, sep as sep14 } from "node:path";
9082
9453
  init_push_leak_verdict();
9083
9454
  init_push_gitleaks();
9084
9455
  init_utils_fs();
@@ -9090,7 +9461,7 @@ function stageSessionDir(localDir, dstDir, changed) {
9090
9461
  const matching = [...changed].filter((p) => p.startsWith(prefix));
9091
9462
  if (matching.length === 0) return false;
9092
9463
  for (const src of matching) {
9093
- copyFileAtomic(src, join62(dstDir, relative10(localDir, src)));
9464
+ copyFileAtomic(src, join65(dstDir, relative11(localDir, src)));
9094
9465
  }
9095
9466
  return true;
9096
9467
  }
@@ -9106,14 +9477,14 @@ function stageSessions(tmpRoot, map, changed) {
9106
9477
  if (!p || p === "TBD") continue;
9107
9478
  reverse.set(encodePath(p), logical);
9108
9479
  }
9109
- const localProjects = join62(claudeHome(), "projects");
9110
- if (!existsSync49(localProjects)) return 0;
9480
+ const localProjects = join65(claudeHome(), "projects");
9481
+ if (!existsSync50(localProjects)) return 0;
9111
9482
  let staged = 0;
9112
- for (const dir of readdirSync19(localProjects)) {
9483
+ for (const dir of readdirSync20(localProjects)) {
9113
9484
  const logical = reverse.get(dir);
9114
9485
  if (!logical) continue;
9115
- const localDir = join62(localProjects, dir);
9116
- const dstDir = join62(tmpRoot, "shared", "projects", logical);
9486
+ const localDir = join65(localProjects, dir);
9487
+ const dstDir = join65(tmpRoot, "shared", "projects", logical);
9117
9488
  if (stageSessionDir(localDir, dstDir, changed)) staged++;
9118
9489
  }
9119
9490
  return staged;
@@ -9127,11 +9498,11 @@ function stageExtras(tmpRoot, map) {
9127
9498
  assertSafeLogical(logical);
9128
9499
  const localRoot = map.projects[logical]?.[HOST];
9129
9500
  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);
9501
+ for (const dirname15 of dirnames) {
9502
+ if (!whitelist.includes(dirname15)) continue;
9503
+ const src = join65(localRoot, dirname15);
9504
+ if (!existsSync50(src)) continue;
9505
+ const dst = join65(tmpRoot, "shared", "extras", logical, dirname15);
9135
9506
  copyExtras(src, dst);
9136
9507
  staged++;
9137
9508
  }
@@ -9139,19 +9510,19 @@ function stageExtras(tmpRoot, map) {
9139
9510
  return staged;
9140
9511
  }
9141
9512
  function stageSkills(tmpRoot) {
9142
- const localSkills = join62(claudeHome(), "skills");
9143
- const stat = lstatSync20(localSkills, { throwIfNoEntry: false });
9513
+ const localSkills = join65(claudeHome(), "skills");
9514
+ const stat = lstatSync22(localSkills, { throwIfNoEntry: false });
9144
9515
  if (stat === void 0 || stat.isSymbolicLink()) return 0;
9145
- const names = readdirSync19(localSkills, { encoding: "utf8" }).filter((n) => !isSkillExcluded(n));
9516
+ const names = readdirSync20(localSkills, { encoding: "utf8" }).filter((n) => !isSkillExcluded(n));
9146
9517
  if (names.length === 0) return 0;
9147
- copySkillsPush(localSkills, join62(tmpRoot, "shared", "skills"));
9518
+ copySkillsPush(localSkills, join65(tmpRoot, "shared", "skills"));
9148
9519
  return names.length;
9149
9520
  }
9150
9521
  function previewPushLeaks(map, opts = {}) {
9151
- const cacheDir = join62(homedir7(), ".cache", "claude-nomad");
9522
+ const cacheDir = join65(homedir7(), ".cache", "claude-nomad");
9152
9523
  mkdirSync15(cacheDir, { recursive: true });
9153
9524
  const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes4(4).toString("hex")}`;
9154
- const tmpRoot = join62(cacheDir, `push-preview-tree-${stamp}`);
9525
+ const tmpRoot = join65(cacheDir, `push-preview-tree-${stamp}`);
9155
9526
  try {
9156
9527
  const sessionCount = stageSessions(tmpRoot, map, opts.selection?.changed);
9157
9528
  const extrasCount = stageExtras(tmpRoot, map);
@@ -9159,9 +9530,9 @@ function previewPushLeaks(map, opts = {}) {
9159
9530
  if (sessionCount + extrasCount + skillCount === 0) {
9160
9531
  return { leak: false, verdictRow: NOTHING_TO_SCAN_ROW, recovery: null, findings: [] };
9161
9532
  }
9162
- const ignoreFile = join62(repoHome(), ".gitleaksignore");
9163
- if (existsSync49(ignoreFile)) {
9164
- copyFileSync(ignoreFile, join62(tmpRoot, ".gitleaksignore"));
9533
+ const ignoreFile = join65(repoHome(), ".gitleaksignore");
9534
+ if (existsSync50(ignoreFile)) {
9535
+ copyFileSync(ignoreFile, join65(tmpRoot, ".gitleaksignore"));
9165
9536
  }
9166
9537
  let findings;
9167
9538
  try {
@@ -9289,7 +9660,7 @@ async function runPushCore(opts = {}) {
9289
9660
  const scannerVersion = probeGitleaks();
9290
9661
  const configHash = computeConfigHash();
9291
9662
  const old = readManifest(manifestPath());
9292
- const mapPath = join63(repo, "path-map.json");
9663
+ const mapPath = join66(repo, "path-map.json");
9293
9664
  const { map, selection, newManifest } = loadSelectionForPush(
9294
9665
  mapPath,
9295
9666
  old,
@@ -9342,7 +9713,7 @@ async function cmdPush(opts = {}) {
9342
9713
  opts.allowRule
9343
9714
  );
9344
9715
  const repo = repoHome();
9345
- if (!existsSync50(repo)) die(`repo not cloned at ${repo}`);
9716
+ if (!existsSync51(repo)) die(`repo not cloned at ${repo}`);
9346
9717
  const handle = acquireLock("push");
9347
9718
  if (handle === null) process.exit(0);
9348
9719
  try {
@@ -9360,8 +9731,8 @@ async function cmdPush(opts = {}) {
9360
9731
  }
9361
9732
 
9362
9733
  // src/commands.sync.ts
9363
- import { existsSync as existsSync51 } from "node:fs";
9364
- import { join as join64 } from "node:path";
9734
+ import { existsSync as existsSync52 } from "node:fs";
9735
+ import { join as join67 } from "node:path";
9365
9736
  init_config();
9366
9737
  init_color();
9367
9738
  init_utils();
@@ -9502,8 +9873,8 @@ async function cmdSync(opts = {}) {
9502
9873
  const dryRun = opts.dryRun === true;
9503
9874
  const verbose = opts.verbose === true;
9504
9875
  const repo = repoHome();
9505
- if (!existsSync51(repo)) die(`repo not cloned at ${repo}`);
9506
- if (!existsSync51(join64(repo, "shared", "settings.base.json"))) {
9876
+ if (!existsSync52(repo)) die(`repo not cloned at ${repo}`);
9877
+ if (!existsSync52(join67(repo, "shared", "settings.base.json"))) {
9507
9878
  die("repo not initialized; run 'nomad init' to scaffold");
9508
9879
  }
9509
9880
  const handle = acquireLock("sync");
@@ -9579,8 +9950,8 @@ init_config();
9579
9950
  // src/crash-report.write.ts
9580
9951
  init_config();
9581
9952
  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";
9953
+ import { mkdirSync as mkdirSync16, readdirSync as readdirSync21, statSync as statSync12, unlinkSync as unlinkSync2, writeFileSync as writeFileSync11 } from "node:fs";
9954
+ import { join as join69 } from "node:path";
9584
9955
 
9585
9956
  // src/crash-report.ts
9586
9957
  var CRASH_MAX_STACK_LINES = 50;
@@ -9665,15 +10036,15 @@ function buildCrashReport(input) {
9665
10036
  // src/crash-report.redact.ts
9666
10037
  import { mkdtempSync as mkdtempSync3, rmSync as rmSync21, writeFileSync as writeFileSync10 } from "node:fs";
9667
10038
  import { tmpdir as tmpdir2 } from "node:os";
9668
- import { join as join65 } from "node:path";
10039
+ import { join as join68 } from "node:path";
9669
10040
  init_push_gitleaks_scan();
9670
10041
  var CRASH_SCAN_TIMEOUT_MS = 3e3;
9671
10042
  var SCAN_UNAVAILABLE_ADVISORY = "\n\n[gitleaks value-based scan unavailable; only structural redaction applied. Review before sharing this file publicly.]\n";
9672
10043
  function redactWithGitleaks(text, scan = scanFile) {
9673
10044
  let dir;
9674
10045
  try {
9675
- dir = mkdtempSync3(join65(tmpdir2(), "nomad-crash-scan-"));
9676
- const tmp = join65(dir, "crash.txt");
10046
+ dir = mkdtempSync3(join68(tmpdir2(), "nomad-crash-scan-"));
10047
+ const tmp = join68(dir, "crash.txt");
9677
10048
  writeFileSync10(tmp, text, { mode: 384 });
9678
10049
  const findings = scan(tmp, false, CRASH_SCAN_TIMEOUT_MS);
9679
10050
  if (findings === null) return text + SCAN_UNAVAILABLE_ADVISORY;
@@ -9696,9 +10067,9 @@ init_utils();
9696
10067
  var CRASH_RETENTION_KEEP = 20;
9697
10068
  function listCrashFiles(dir = crashDir()) {
9698
10069
  try {
9699
- return readdirSync20(dir).flatMap((name) => {
10070
+ return readdirSync21(dir).flatMap((name) => {
9700
10071
  try {
9701
- return [{ name, mtimeMs: statSync12(join66(dir, name)).mtimeMs }];
10072
+ return [{ name, mtimeMs: statSync12(join69(dir, name)).mtimeMs }];
9702
10073
  } catch {
9703
10074
  return [];
9704
10075
  }
@@ -9712,7 +10083,7 @@ function pruneCrashDir(dir, keep = CRASH_RETENTION_KEEP) {
9712
10083
  const targets = prunableByCount(files, keep);
9713
10084
  for (const name of targets) {
9714
10085
  try {
9715
- unlinkSync2(join66(dir, name));
10086
+ unlinkSync2(join69(dir, name));
9716
10087
  } catch {
9717
10088
  }
9718
10089
  }
@@ -9720,7 +10091,7 @@ function pruneCrashDir(dir, keep = CRASH_RETENTION_KEEP) {
9720
10091
  function writeCrashReport(text, dir = crashDir()) {
9721
10092
  mkdirSync16(dir, { recursive: true, mode: 448 });
9722
10093
  const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes5(4).toString("hex")}`;
9723
- const path = join66(dir, `crash-${stamp}.txt`);
10094
+ const path = join69(dir, `crash-${stamp}.txt`);
9724
10095
  writeFileSync11(path, text, { mode: 384 });
9725
10096
  pruneCrashDir(dir);
9726
10097
  return path;
@@ -9752,18 +10123,18 @@ function handleCrash(err, argv, opts) {
9752
10123
 
9753
10124
  // src/diff.ts
9754
10125
  init_config();
9755
- import { existsSync as existsSync52 } from "node:fs";
9756
- import { join as join67 } from "node:path";
10126
+ import { existsSync as existsSync53 } from "node:fs";
10127
+ import { join as join70 } from "node:path";
9757
10128
  init_utils();
9758
10129
  init_utils_fs();
9759
10130
  init_utils_json();
9760
10131
  function cmdDiff() {
9761
10132
  try {
9762
10133
  const repo = repoHome();
9763
- if (!existsSync52(repo)) die(`repo not cloned at ${repo}`);
10134
+ if (!existsSync53(repo)) die(`repo not cloned at ${repo}`);
9764
10135
  const ts = freshBackupTs(backupBase());
9765
- const mapPath = join67(repo, "path-map.json");
9766
- const map = existsSync52(mapPath) ? readPathMap(mapPath) : { projects: {} };
10136
+ const mapPath = join70(repo, "path-map.json");
10137
+ const map = existsSync53(mapPath) ? readPathMap(mapPath) : { projects: {} };
9767
10138
  divergenceCheckExtras(ts);
9768
10139
  computePreview(ts, map, "diff");
9769
10140
  } catch (err) {
@@ -9778,8 +10149,8 @@ function cmdDiff() {
9778
10149
 
9779
10150
  // src/init.ts
9780
10151
  init_config();
9781
- import { existsSync as existsSync54, mkdirSync as mkdirSync17, writeFileSync as writeFileSync12 } from "node:fs";
9782
- import { join as join69 } from "node:path";
10152
+ import { existsSync as existsSync55, mkdirSync as mkdirSync17, writeFileSync as writeFileSync12 } from "node:fs";
10153
+ import { join as join72 } from "node:path";
9783
10154
 
9784
10155
  // src/init.gh-onboard.ts
9785
10156
  init_config();
@@ -9861,33 +10232,33 @@ init_config();
9861
10232
  init_utils();
9862
10233
  init_utils_fs();
9863
10234
  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";
10235
+ import { copyFileSync as copyFileSync2, cpSync as cpSync9, existsSync as existsSync54, rmSync as rmSync22, statSync as statSync13 } from "node:fs";
10236
+ import { join as join71 } from "node:path";
9866
10237
  function snapshotIntoShared(map) {
9867
10238
  const repo = repoHome();
9868
10239
  const claude = claudeHome();
9869
10240
  for (const name of allSharedLinks(map)) {
9870
- const src = join68(claude, name);
9871
- if (!existsSync53(src)) continue;
9872
- const dst = join68(repo, "shared", name);
10241
+ const src = join71(claude, name);
10242
+ if (!existsSync54(src)) continue;
10243
+ const dst = join71(repo, "shared", name);
9873
10244
  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 });
10245
+ const gk = join71(dst, ".gitkeep");
10246
+ if (existsSync54(gk)) rmSync22(gk);
10247
+ cpSync9(src, dst, { recursive: true, force: false, errorOnExist: true });
9877
10248
  } else {
9878
10249
  copyFileSync2(src, dst);
9879
10250
  }
9880
10251
  log(`snapshotted shared/${name} from ${src}`);
9881
10252
  }
9882
- const userSettings = join68(claude, "settings.json");
9883
- if (existsSync53(userSettings)) {
10253
+ const userSettings = join71(claude, "settings.json");
10254
+ if (existsSync54(userSettings)) {
9884
10255
  let parsed;
9885
10256
  try {
9886
10257
  parsed = readJson(userSettings);
9887
10258
  } catch (err) {
9888
10259
  return die(`malformed ${userSettings}: ${err.message}`);
9889
10260
  }
9890
- const hostFile = join68(repo, "hosts", `${HOST}.json`);
10261
+ const hostFile = join71(repo, "hosts", `${HOST}.json`);
9891
10262
  writeJsonAtomic(hostFile, parsed);
9892
10263
  log(`snapshotted hosts/${HOST}.json from ${userSettings}`);
9893
10264
  }
@@ -9901,14 +10272,14 @@ var GITATTRIBUTES = "# nomad: sync content is byte-managed, disable all line-end
9901
10272
  var SHARED_KEEP_DIRS = ["agents", "skills", "commands", "rules", "hooks"];
9902
10273
  function preflightConflict(repoHome2) {
9903
10274
  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")
10275
+ join72(repoHome2, "shared", "settings.base.json"),
10276
+ join72(repoHome2, "shared", "CLAUDE.md"),
10277
+ join72(repoHome2, "path-map.json"),
10278
+ join72(repoHome2, "hosts"),
10279
+ join72(repoHome2, "shared")
9909
10280
  ];
9910
10281
  for (const c of candidates) {
9911
- if (existsSync54(c)) return c;
10282
+ if (existsSync55(c)) return c;
9912
10283
  }
9913
10284
  return null;
9914
10285
  }
@@ -9926,27 +10297,27 @@ function cmdInit(opts = {}) {
9926
10297
  die(`already initialized; refusing to clobber ${conflict}`);
9927
10298
  }
9928
10299
  ensureOriginRepo(opts.repoName ?? DEFAULT_REPO_NAME, opts.run);
9929
- mkdirSync17(join69(repo, "shared"), { recursive: true });
9930
- mkdirSync17(join69(repo, "hosts"), { recursive: true });
10300
+ mkdirSync17(join72(repo, "shared"), { recursive: true });
10301
+ mkdirSync17(join72(repo, "hosts"), { recursive: true });
9931
10302
  for (const name of SHARED_KEEP_DIRS) {
9932
- mkdirSync17(join69(repo, "shared", name), { recursive: true });
10303
+ mkdirSync17(join72(repo, "shared", name), { recursive: true });
9933
10304
  }
9934
- const userClaudeMd = join69(claude, "CLAUDE.md");
9935
- if (!snapshot || !existsSync54(userClaudeMd)) {
9936
- writeFileSync12(join69(repo, "shared", "CLAUDE.md"), SHARED_CLAUDE_MD);
10305
+ const userClaudeMd = join72(claude, "CLAUDE.md");
10306
+ if (!snapshot || !existsSync55(userClaudeMd)) {
10307
+ writeFileSync12(join72(repo, "shared", "CLAUDE.md"), SHARED_CLAUDE_MD);
9937
10308
  item("created shared/CLAUDE.md");
9938
10309
  }
9939
10310
  for (const name of SHARED_KEEP_DIRS) {
9940
- writeFileSync12(join69(repo, "shared", name, ".gitkeep"), "");
10311
+ writeFileSync12(join72(repo, "shared", name, ".gitkeep"), "");
9941
10312
  item(`created shared/${name}/.gitkeep`);
9942
10313
  }
9943
- writeFileSync12(join69(repo, "hosts", ".gitkeep"), "");
10314
+ writeFileSync12(join72(repo, "hosts", ".gitkeep"), "");
9944
10315
  item("created hosts/.gitkeep");
9945
- writeJsonAtomic(join69(repo, "shared", "settings.base.json"), {});
10316
+ writeJsonAtomic(join72(repo, "shared", "settings.base.json"), {});
9946
10317
  item("created shared/settings.base.json");
9947
- writeJsonAtomic(join69(repo, "path-map.json"), { projects: {} });
10318
+ writeJsonAtomic(join72(repo, "path-map.json"), { projects: {} });
9948
10319
  item("created path-map.json");
9949
- writeFileSync12(join69(repo, ".gitattributes"), GITATTRIBUTES);
10320
+ writeFileSync12(join72(repo, ".gitattributes"), GITATTRIBUTES);
9950
10321
  item("created .gitattributes");
9951
10322
  if (snapshot) {
9952
10323
  snapshotIntoShared({ projects: {} });
@@ -10012,21 +10383,21 @@ function maybeDisableRepoActions(repoHome2, run) {
10012
10383
  // src/init.prompt.ts
10013
10384
  init_config();
10014
10385
  init_utils();
10015
- import { existsSync as existsSync55, readdirSync as readdirSync21, statSync as statSync14 } from "node:fs";
10016
- import { join as join70 } from "node:path";
10386
+ import { existsSync as existsSync56, readdirSync as readdirSync22, statSync as statSync14 } from "node:fs";
10387
+ import { join as join73 } from "node:path";
10017
10388
  import { createInterface as createInterface3 } from "node:readline/promises";
10018
10389
  function nonEmptyExists(path) {
10019
- if (!existsSync55(path)) return false;
10390
+ if (!existsSync56(path)) return false;
10020
10391
  try {
10021
- if (statSync14(path).isDirectory()) return readdirSync21(path).length > 0;
10392
+ if (statSync14(path).isDirectory()) return readdirSync22(path).length > 0;
10022
10393
  return true;
10023
10394
  } catch {
10024
10395
  return false;
10025
10396
  }
10026
10397
  }
10027
10398
  function hasExistingClaudeConfig(claudeHome2) {
10028
- if (existsSync55(join70(claudeHome2, "settings.json"))) return true;
10029
- return SHARED_LINKS.some((name) => nonEmptyExists(join70(claudeHome2, name)));
10399
+ if (existsSync56(join73(claudeHome2, "settings.json"))) return true;
10400
+ return SHARED_LINKS.some((name) => nonEmptyExists(join73(claudeHome2, name)));
10030
10401
  }
10031
10402
  async function confirmSnapshotDefault(claudeHome2) {
10032
10403
  if (process.stdin.isTTY !== true || process.stdout.isTTY !== true) {
@@ -10333,7 +10704,7 @@ function parseSyncArgs(argv) {
10333
10704
  // package.json
10334
10705
  var package_default = {
10335
10706
  name: "claude-nomad",
10336
- version: "0.65.0",
10707
+ version: "0.67.0",
10337
10708
  type: "module",
10338
10709
  description: "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
10339
10710
  keywords: [
@@ -10562,7 +10933,9 @@ var DEFAULT_HELP = [
10562
10933
  row(" --backups", "Required: confirm backup pruning is the intended target."),
10563
10934
  row(" --dry-run", "List the snapshots that would be removed without deleting."),
10564
10935
  row(" --older-than <dur>", "Delete snapshots older than a duration (e.g. 14d, 24h, 30m)."),
10565
- cont("Default when no retention flag is given: 14d."),
10936
+ cont("Default when no retention flag is given: 14d. A snapshot holding"),
10937
+ cont("nothing is removed in every mode, whatever its age, and never"),
10938
+ cont("uses up one of the --keep slots."),
10566
10939
  row(" --keep <N>", "Keep the N most-recent snapshots, delete the rest. Mutually"),
10567
10940
  cont("exclusive with --older-than."),
10568
10941
  "",
@@ -10578,15 +10951,15 @@ var DEFAULT_HELP = [
10578
10951
  init_config();
10579
10952
  init_utils();
10580
10953
  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";
10954
+ import { existsSync as existsSync57, readFileSync as readFileSync21, readdirSync as readdirSync23 } from "node:fs";
10955
+ import { join as join74 } from "node:path";
10583
10956
  function resumeCmd(sessionId) {
10584
10957
  if (!/^[A-Za-z0-9_-]+$/.test(sessionId) || sessionId.length > 128) {
10585
10958
  fail(`invalid session id: ${sessionId}`);
10586
10959
  process.exit(1);
10587
10960
  }
10588
- const projectsRoot = join71(claudeHome(), "projects");
10589
- if (!existsSync56(projectsRoot)) {
10961
+ const projectsRoot = join74(claudeHome(), "projects");
10962
+ if (!existsSync57(projectsRoot)) {
10590
10963
  fail(`${projectsRoot} does not exist`);
10591
10964
  process.exit(1);
10592
10965
  }
@@ -10600,8 +10973,8 @@ function resumeCmd(sessionId) {
10600
10973
  fail(`no cwd field found in ${jsonlPath}`);
10601
10974
  process.exit(1);
10602
10975
  }
10603
- const mapPath = join71(repoHome(), "path-map.json");
10604
- if (!existsSync56(mapPath)) {
10976
+ const mapPath = join74(repoHome(), "path-map.json");
10977
+ if (!existsSync57(mapPath)) {
10605
10978
  fail("path-map.json missing");
10606
10979
  process.exit(1);
10607
10980
  }
@@ -10623,9 +10996,9 @@ function resumeCmd(sessionId) {
10623
10996
  console.log(`cd ${shQuote(hit.localPath)} && claude --resume ${shQuote(sessionId)}`);
10624
10997
  }
10625
10998
  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;
10999
+ for (const dir of readdirSync23(projectsRoot)) {
11000
+ const candidate = join74(projectsRoot, dir, `${sessionId}.jsonl`);
11001
+ if (existsSync57(candidate)) return candidate;
10629
11002
  }
10630
11003
  return null;
10631
11004
  }