claude-nomad 0.59.0 → 0.61.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
@@ -1353,6 +1353,75 @@ function stripGsdHookEntries(settings) {
1353
1353
  }
1354
1354
  return out;
1355
1355
  }
1356
+ function keepMatcherEntry(entry) {
1357
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)) return null;
1358
+ const entryObj = entry;
1359
+ if (!Array.isArray(entryObj.hooks)) return null;
1360
+ const innerHooks = entryObj.hooks;
1361
+ const kept = innerHooks.filter((h2) => {
1362
+ if (h2 === null || typeof h2 !== "object" || Array.isArray(h2)) return false;
1363
+ const hookObj = h2;
1364
+ const cmd = hookObj.command;
1365
+ return isGsdHookEntry(typeof cmd === "string" ? cmd : "");
1366
+ });
1367
+ if (kept.length === 0) return null;
1368
+ return { ...entryObj, hooks: kept };
1369
+ }
1370
+ function keepEventMatchers(matchers) {
1371
+ if (!Array.isArray(matchers)) return null;
1372
+ const kept = [];
1373
+ for (const entry of matchers) {
1374
+ const result = keepMatcherEntry(entry);
1375
+ if (result !== null) kept.push(result);
1376
+ }
1377
+ return kept.length === 0 ? null : kept;
1378
+ }
1379
+ function keepGsdHookEntries(settings) {
1380
+ const hooksVal = settings.hooks;
1381
+ if (hooksVal === null || typeof hooksVal !== "object" || Array.isArray(hooksVal)) return {};
1382
+ const hooksObj = hooksVal;
1383
+ const keptHooks = {};
1384
+ for (const [event, matchers] of Object.entries(hooksObj)) {
1385
+ const kept = keepEventMatchers(matchers);
1386
+ if (kept !== null) keptHooks[event] = kept;
1387
+ }
1388
+ return Object.keys(keptHooks).length === 0 ? {} : { hooks: keptHooks };
1389
+ }
1390
+ function asPlainObject(value) {
1391
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return null;
1392
+ return value;
1393
+ }
1394
+ function canonicalMatcherKey(m) {
1395
+ const obj = asPlainObject(m);
1396
+ if (obj === null) return JSON.stringify(m);
1397
+ return JSON.stringify(
1398
+ Object.keys(obj).sort((a, b) => a.localeCompare(b)).map((k) => [k, obj[k]])
1399
+ );
1400
+ }
1401
+ function unionMatcherArrays(baseMatchers, gsdMatchers) {
1402
+ const seen = new Set(baseMatchers.map(canonicalMatcherKey));
1403
+ const merged = [...baseMatchers];
1404
+ for (const m of gsdMatchers) {
1405
+ const key = canonicalMatcherKey(m);
1406
+ if (!seen.has(key)) {
1407
+ merged.push(m);
1408
+ seen.add(key);
1409
+ }
1410
+ }
1411
+ return merged;
1412
+ }
1413
+ function graftGsdHookEntries(base, gsdOnly) {
1414
+ const gsdHooks = asPlainObject(gsdOnly.hooks);
1415
+ if (gsdHooks === null || Object.keys(gsdHooks).length === 0) return base;
1416
+ const baseHooks = asPlainObject(base.hooks);
1417
+ const mergedHooks = baseHooks ? { ...baseHooks } : {};
1418
+ for (const [event, gsdMatchers] of Object.entries(gsdHooks)) {
1419
+ if (!Array.isArray(gsdMatchers)) continue;
1420
+ const baseMatchers = mergedHooks[event];
1421
+ mergedHooks[event] = Array.isArray(baseMatchers) ? unionMatcherArrays(baseMatchers, gsdMatchers) : gsdMatchers;
1422
+ }
1423
+ return { ...base, hooks: mergedHooks };
1424
+ }
1356
1425
  function matcherHasGsdEntry(entry) {
1357
1426
  if (entry === null || typeof entry !== "object" || Array.isArray(entry)) return false;
1358
1427
  const entryObj = entry;
@@ -1617,12 +1686,12 @@ function* eachExtrasTarget(v, counts) {
1617
1686
  counts.unmapped++;
1618
1687
  continue;
1619
1688
  }
1620
- for (const dirname10 of dirnames) {
1621
- if (!whitelist.includes(dirname10)) {
1689
+ for (const dirname11 of dirnames) {
1690
+ if (!whitelist.includes(dirname11)) {
1622
1691
  counts.skipped++;
1623
1692
  continue;
1624
1693
  }
1625
- yield { logical, localRoot, dirname: dirname10 };
1694
+ yield { logical, localRoot, dirname: dirname11 };
1626
1695
  }
1627
1696
  }
1628
1697
  }
@@ -1660,8 +1729,8 @@ function copyExtrasFileSkipDiverged(src, dst) {
1660
1729
  }
1661
1730
  copyExtras(src, dst);
1662
1731
  }
1663
- function extrasDenySet(dirname10) {
1664
- return dirname10 === ".claude" ? CLAUDE_EXTRA_NEVER_SYNC : ALWAYS_NEVER_SYNC;
1732
+ function extrasDenySet(dirname11) {
1733
+ return dirname11 === ".claude" ? CLAUDE_EXTRA_NEVER_SYNC : ALWAYS_NEVER_SYNC;
1665
1734
  }
1666
1735
  function copyExtrasFiltered(src, dst, blockSet) {
1667
1736
  rmSync2(dst, { recursive: true, force: true });
@@ -1804,6 +1873,34 @@ function syncSharedLinksPush(map) {
1804
1873
  copyExtrasFiltered(localPath, join5(repo, "shared", name), ALWAYS_NEVER_SYNC);
1805
1874
  }
1806
1875
  }
1876
+ function readExistingSettings(settingsPath) {
1877
+ if (!existsSync4(settingsPath)) return { existing: {}, present: false, malformed: false };
1878
+ try {
1879
+ const parsed = readJson(settingsPath);
1880
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
1881
+ return { existing: {}, present: true, malformed: true };
1882
+ }
1883
+ return { existing: parsed, present: true, malformed: false };
1884
+ } catch {
1885
+ return { existing: {}, present: true, malformed: true };
1886
+ }
1887
+ }
1888
+ function emitDriftWarnings(merged, existing) {
1889
+ const drift = classifySettingsDrift(merged, existing);
1890
+ if (drift.behind.length > 0) {
1891
+ const { phrase, pronoun } = describeSettings(drift.behind);
1892
+ warn(
1893
+ `your settings.json is missing ${phrase} that the synced copy has; run 'nomad pull' to restore ${pronoun}.`
1894
+ );
1895
+ }
1896
+ const { promotable } = partitionByCaptureExclusion(drift.ahead);
1897
+ if (promotable.length > 0) {
1898
+ const { phrase, pronoun, verb } = describeSettings(promotable);
1899
+ warn(
1900
+ `your settings.json has ${phrase} that ${verb} not yet synced; run 'nomad capture-settings' to save ${pronoun} to the repo before the next pull overwrites ${pronoun}.`
1901
+ );
1902
+ }
1903
+ }
1807
1904
  function regenerateSettings(ts, opts = {}) {
1808
1905
  const dryRun = opts.dryRun === true;
1809
1906
  const suppressDriftWarn = opts.suppressDriftWarn === true;
@@ -1819,25 +1916,12 @@ function regenerateSettings(ts, opts = {}) {
1819
1916
  const overrides = hasOverrides ? readJson(hostPath) : {};
1820
1917
  const merged = deepMerge(base, overrides);
1821
1918
  const settingsPath = join5(claude, "settings.json");
1822
- if (!suppressDriftWarn && existsSync4(settingsPath)) {
1823
- try {
1824
- const existing = readJson(settingsPath);
1825
- const drift = classifySettingsDrift(merged, existing);
1826
- if (drift.behind.length > 0) {
1827
- const { phrase, pronoun } = describeSettings(drift.behind);
1828
- warn(
1829
- `your settings.json is missing ${phrase} that the synced copy has; run 'nomad pull' to restore ${pronoun}.`
1830
- );
1831
- }
1832
- const { promotable } = partitionByCaptureExclusion(drift.ahead);
1833
- if (promotable.length > 0) {
1834
- const { phrase, pronoun, verb } = describeSettings(promotable);
1835
- warn(
1836
- `your settings.json has ${phrase} that ${verb} not yet synced; run 'nomad capture-settings' to save ${pronoun} to the repo before the next pull overwrites ${pronoun}.`
1837
- );
1838
- }
1839
- } catch {
1919
+ const { existing, present, malformed } = readExistingSettings(settingsPath);
1920
+ if (!suppressDriftWarn && present) {
1921
+ if (malformed) {
1840
1922
  warn("existing settings.json is malformed; skipping drift-check and regenerating.");
1923
+ } else {
1924
+ emitDriftWarnings(merged, existing);
1841
1925
  }
1842
1926
  }
1843
1927
  const overrideLabel = hasOverrides ? `${HOST}.json` : "no host overrides";
@@ -1846,7 +1930,10 @@ function regenerateSettings(ts, opts = {}) {
1846
1930
  return { label: overrideLabel };
1847
1931
  }
1848
1932
  backupBeforeWrite(settingsPath, ts);
1849
- writeJsonAtomic(settingsPath, stripGsdHookEntries(merged));
1933
+ writeJsonAtomic(
1934
+ settingsPath,
1935
+ graftGsdHookEntries(stripGsdHookEntries(merged), keepGsdHookEntries(existing))
1936
+ );
1850
1937
  return { label: overrideLabel };
1851
1938
  }
1852
1939
 
@@ -2471,8 +2558,8 @@ function cmdEject(opts = {}, roots = defaultEjectRoots()) {
2471
2558
  }
2472
2559
 
2473
2560
  // src/commands.doctor.ts
2474
- import { existsSync as existsSync34 } from "node:fs";
2475
- import { join as join40 } from "node:path";
2561
+ import { existsSync as existsSync35 } from "node:fs";
2562
+ import { join as join42 } from "node:path";
2476
2563
 
2477
2564
  // src/commands.doctor.checks.repo.ts
2478
2565
  init_color();
@@ -3319,20 +3406,117 @@ function reportCheckSchema(section2) {
3319
3406
 
3320
3407
  // src/commands.doctor.check-shared.ts
3321
3408
  init_color();
3322
- import { randomBytes } from "node:crypto";
3409
+ import { randomBytes as randomBytes2 } from "node:crypto";
3410
+ import { execFileSync as execFileSync10 } from "node:child_process";
3411
+ import { existsSync as existsSync22, mkdirSync as mkdirSync7, readdirSync as readdirSync11, rmSync as rmSync11 } from "node:fs";
3412
+ import { homedir as homedir5 } from "node:os";
3413
+ import { join as join28 } from "node:path";
3414
+
3415
+ // src/commands.doctor.check-shared.memory.ts
3416
+ init_color();
3323
3417
  import { execFileSync as execFileSync9 } from "node:child_process";
3324
- import { existsSync as existsSync22, mkdirSync as mkdirSync6, readdirSync as readdirSync11, rmSync as rmSync10 } from "node:fs";
3418
+ import { randomBytes } from "node:crypto";
3419
+ import { mkdirSync as mkdirSync4, rmSync as rmSync9, writeFileSync as writeFileSync4 } from "node:fs";
3325
3420
  import { homedir as homedir4 } from "node:os";
3326
- import { join as join27 } from "node:path";
3421
+ import { dirname as dirname3, join as join24 } from "node:path";
3422
+ init_config();
3423
+ init_push_gitleaks();
3424
+ init_utils_fs();
3425
+ var MEMORY_MD_PATH = /^shared\/projects\/([^/]+)\/memory\/[^/]+\.md$/;
3426
+ function buildMemoryScanTree(tmpRoot) {
3427
+ let paths;
3428
+ try {
3429
+ const out = execFileSync9(
3430
+ "git",
3431
+ ["-C", repoHome(), "ls-tree", "-r", "-z", "--name-only", "HEAD", "--", "shared/projects"],
3432
+ { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 1e4 }
3433
+ );
3434
+ paths = out.split("\0").filter((p) => p.length > 0);
3435
+ } catch {
3436
+ return 0;
3437
+ }
3438
+ const logicals = /* @__PURE__ */ new Set();
3439
+ for (const rel of paths) {
3440
+ const m = MEMORY_MD_PATH.exec(rel);
3441
+ if (m?.[1] === void 0) continue;
3442
+ let blob;
3443
+ try {
3444
+ blob = execFileSync9("git", ["-C", repoHome(), "cat-file", "blob", `HEAD:${rel}`], {
3445
+ stdio: ["ignore", "pipe", "ignore"],
3446
+ maxBuffer: 67108864,
3447
+ timeout: 1e4
3448
+ });
3449
+ } catch {
3450
+ continue;
3451
+ }
3452
+ const dest = join24(tmpRoot, rel);
3453
+ mkdirSync4(dirname3(dest), { recursive: true });
3454
+ writeFileSync4(dest, blob);
3455
+ logicals.add(m[1]);
3456
+ }
3457
+ return logicals.size;
3458
+ }
3459
+ function reportMemoryFindings(section2, findings) {
3460
+ for (const f of findings) {
3461
+ addItem(section2, `${yellow(warnGlyph)} ${f.RuleID} in ${f.File}`);
3462
+ }
3463
+ addItem(
3464
+ section2,
3465
+ ` ${dim("run `nomad push` and choose Redact in the recovery menu to scrub these")}`
3466
+ );
3467
+ }
3468
+ function reportCommittedMemory(section2) {
3469
+ let tmpRoot;
3470
+ try {
3471
+ const cacheDir = join24(homedir4(), ".cache", "claude-nomad");
3472
+ mkdirSync4(cacheDir, { recursive: true });
3473
+ const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes(4).toString("hex")}`;
3474
+ tmpRoot = join24(cacheDir, `check-shared-memory-tree-${stamp}`);
3475
+ mkdirSync4(tmpRoot, { recursive: true, mode: 448 });
3476
+ const staged = buildMemoryScanTree(tmpRoot);
3477
+ if (staged === 0) return;
3478
+ let findings;
3479
+ try {
3480
+ findings = scanStagedTree(tmpRoot);
3481
+ } catch (err) {
3482
+ addItem(
3483
+ section2,
3484
+ `${yellow(warnGlyph)} committed-memory scan skipped: ${err.message}`
3485
+ );
3486
+ return;
3487
+ }
3488
+ if (findings === null) {
3489
+ addItem(
3490
+ section2,
3491
+ `${yellow(warnGlyph)} committed-memory scan skipped: no parseable gitleaks report`
3492
+ );
3493
+ return;
3494
+ }
3495
+ if (findings.length === 0) return;
3496
+ reportMemoryFindings(section2, findings);
3497
+ } catch (err) {
3498
+ addItem(
3499
+ section2,
3500
+ `${yellow(warnGlyph)} committed-memory scan skipped: ${err.message}`
3501
+ );
3502
+ } finally {
3503
+ if (tmpRoot !== void 0) {
3504
+ try {
3505
+ rmSync9(tmpRoot, { recursive: true, force: true });
3506
+ } catch {
3507
+ }
3508
+ }
3509
+ }
3510
+ }
3327
3511
 
3328
3512
  // src/commands.doctor.check-shared.scan.ts
3329
3513
  init_color();
3330
- import { join as join24 } from "node:path";
3514
+ import { join as join25 } from "node:path";
3331
3515
  init_config();
3332
3516
  init_push_gitleaks();
3333
3517
  function scrubPath(logical, sid, logicalToEncoded) {
3334
3518
  const encoded = logicalToEncoded.get(logical) ?? logical;
3335
- return join24(claudeHome(), "projects", encoded, `${sid}.jsonl`);
3519
+ return join25(claudeHome(), "projects", encoded, `${sid}.jsonl`);
3336
3520
  }
3337
3521
  function reportSessionFindings(section2, bySession) {
3338
3522
  for (const [sid, counts] of bySession) {
@@ -3420,8 +3604,8 @@ init_config();
3420
3604
 
3421
3605
  // src/remap.ts
3422
3606
  init_config_sharedDirs_guard();
3423
- import { cpSync as cpSync6, existsSync as existsSync21, lstatSync as lstatSync10, mkdirSync as mkdirSync5, readdirSync as readdirSync10, rmSync as rmSync9, statSync as statSync5 } from "node:fs";
3424
- import { dirname as dirname4, join as join26, relative as relative5, sep as sep4 } from "node:path";
3607
+ import { cpSync as cpSync6, existsSync as existsSync21, lstatSync as lstatSync10, mkdirSync as mkdirSync6, readdirSync as readdirSync10, rmSync as rmSync10, statSync as statSync5 } from "node:fs";
3608
+ import { dirname as dirname5, join as join27, relative as relative5, sep as sep4 } from "node:path";
3425
3609
  init_config();
3426
3610
 
3427
3611
  // src/push-manifest.ts
@@ -3429,8 +3613,8 @@ init_config();
3429
3613
  init_push_gitleaks_config();
3430
3614
  init_utils_fs();
3431
3615
  import { createHash } from "node:crypto";
3432
- import { existsSync as existsSync20, mkdirSync as mkdirSync4, readdirSync as readdirSync9, readFileSync as readFileSync7, statSync as statSync4 } from "node:fs";
3433
- import { dirname as dirname3, join as join25 } from "node:path";
3616
+ import { existsSync as existsSync20, mkdirSync as mkdirSync5, readdirSync as readdirSync9, readFileSync as readFileSync7, statSync as statSync4 } from "node:fs";
3617
+ import { dirname as dirname4, join as join26 } from "node:path";
3434
3618
  function isChanged(prev, cur, hash) {
3435
3619
  if (prev === void 0) return true;
3436
3620
  if (prev.size !== cur.size) return true;
@@ -3471,7 +3655,7 @@ function hashFile(absPath) {
3471
3655
  }
3472
3656
  function enumerateDir(dir, results) {
3473
3657
  for (const entry of readdirSync9(dir)) {
3474
- const fullPath = join25(dir, entry);
3658
+ const fullPath = join26(dir, entry);
3475
3659
  const st = statSync4(fullPath);
3476
3660
  if (st.isDirectory()) {
3477
3661
  enumerateDir(fullPath, results);
@@ -3483,7 +3667,7 @@ function enumerateDir(dir, results) {
3483
3667
  function enumerateSourceFiles(projectDir) {
3484
3668
  const results = [];
3485
3669
  for (const entry of readdirSync9(projectDir)) {
3486
- const fullPath = join25(projectDir, entry);
3670
+ const fullPath = join26(projectDir, entry);
3487
3671
  const st = statSync4(fullPath);
3488
3672
  if (st.isDirectory()) {
3489
3673
  enumerateDir(fullPath, results);
@@ -3502,8 +3686,8 @@ function computeConfigHash() {
3502
3686
  const repo = repoHome();
3503
3687
  const parts = [
3504
3688
  fileIdentity(resolveTomlPath(repo)),
3505
- fileIdentity(join25(repo, ".gitleaks.overlay.toml")),
3506
- fileIdentity(join25(repo, ".gitleaksignore"))
3689
+ fileIdentity(join26(repo, ".gitleaks.overlay.toml")),
3690
+ fileIdentity(join26(repo, ".gitleaksignore"))
3507
3691
  ];
3508
3692
  return createHash("sha256").update(parts.join("\n")).digest("hex");
3509
3693
  }
@@ -3523,7 +3707,7 @@ function readManifest(path) {
3523
3707
  }
3524
3708
  }
3525
3709
  function writeManifest(path, manifest) {
3526
- mkdirSync4(dirname3(path), { recursive: true });
3710
+ mkdirSync5(dirname4(path), { recursive: true });
3527
3711
  writeJsonAtomic(path, manifest);
3528
3712
  }
3529
3713
  function buildManifest(files, scannerVersion, configHash) {
@@ -3537,9 +3721,9 @@ init_utils_json();
3537
3721
  var TMP_SUFFIX = ".nomad-tmp";
3538
3722
  function atomicMirror(src, dst, options) {
3539
3723
  const tmp = `${dst}${TMP_SUFFIX}`;
3540
- rmSync9(tmp, { recursive: true, force: true });
3724
+ rmSync10(tmp, { recursive: true, force: true });
3541
3725
  cpSync6(src, tmp, options);
3542
- rmSync9(dst, { recursive: true, force: true });
3726
+ rmSync10(dst, { recursive: true, force: true });
3543
3727
  renameAtomicRetry(tmp, dst);
3544
3728
  }
3545
3729
  function overlaySessionDir(src, dst) {
@@ -3547,7 +3731,7 @@ function overlaySessionDir(src, dst) {
3547
3731
  cpSyncGuarded(src, dst, void 0, "overlaySessionDir");
3548
3732
  }
3549
3733
  function copyFileAtomic(src, dst) {
3550
- mkdirSync5(dirname4(dst), { recursive: true });
3734
+ mkdirSync6(dirname5(dst), { recursive: true });
3551
3735
  const tmp = `${dst}.tmp.${process.pid}`;
3552
3736
  cpSync6(src, tmp, { force: true, preserveTimestamps: true });
3553
3737
  renameAtomicRetry(tmp, dst);
@@ -3565,12 +3749,12 @@ function applySelective(sel, localDir, repoDst) {
3565
3749
  const prefix = `${localDir}${sep4}`;
3566
3750
  for (const src of sel.changed) {
3567
3751
  if (!src.startsWith(prefix)) continue;
3568
- copyFileAtomic(src, join26(repoDst, relative5(localDir, src)));
3752
+ copyFileAtomic(src, join27(repoDst, relative5(localDir, src)));
3569
3753
  }
3570
3754
  for (const src of sel.deleted) {
3571
3755
  if (!src.startsWith(prefix)) continue;
3572
- const dst = join26(repoDst, relative5(localDir, src));
3573
- if (existsSync21(dst)) rmSync9(dst);
3756
+ const dst = join27(repoDst, relative5(localDir, src));
3757
+ if (existsSync21(dst)) rmSync10(dst);
3574
3758
  }
3575
3759
  }
3576
3760
  function copyDirJsonlOnly(src, dst) {
@@ -3599,16 +3783,16 @@ function remapPull(ts, opts = {}) {
3599
3783
  const wouldPull = [];
3600
3784
  const repo = repoHome();
3601
3785
  const claude = claudeHome();
3602
- const mapPath = join26(repo, "path-map.json");
3603
- const repoProjects = join26(repo, "shared", "projects");
3786
+ const mapPath = join27(repo, "path-map.json");
3787
+ const repoProjects = join27(repo, "shared", "projects");
3604
3788
  if (!existsSync21(mapPath) || !existsSync21(repoProjects)) {
3605
3789
  const text = "no path-map or repo projects dir; skipping session remap";
3606
3790
  emitPreview(opts.onPreview, { kind: "note", text }, text);
3607
3791
  return { unmapped: 0, pulled, wouldPull };
3608
3792
  }
3609
3793
  const map = readPathMap(mapPath);
3610
- const localProjects = join26(claude, "projects");
3611
- if (!dryRun) mkdirSync5(localProjects, { recursive: true });
3794
+ const localProjects = join27(claude, "projects");
3795
+ if (!dryRun) mkdirSync6(localProjects, { recursive: true });
3612
3796
  for (const [logical, hosts] of Object.entries(map.projects)) {
3613
3797
  assertSafeLogical(logical);
3614
3798
  const localPath = hosts[HOST];
@@ -3617,9 +3801,9 @@ function remapPull(ts, opts = {}) {
3617
3801
  continue;
3618
3802
  }
3619
3803
  assertSafeLocalRoot(localPath, logical);
3620
- const src = join26(repoProjects, logical);
3804
+ const src = join27(repoProjects, logical);
3621
3805
  if (!existsSync21(src)) continue;
3622
- const dst = join26(localProjects, encodePath(localPath));
3806
+ const dst = join27(localProjects, encodePath(localPath));
3623
3807
  if (dryRun) {
3624
3808
  wouldPull.push(logical);
3625
3809
  emitPreview(
@@ -3638,8 +3822,8 @@ function remapPull(ts, opts = {}) {
3638
3822
  function countLocalOnly(src, dst) {
3639
3823
  let count = 0;
3640
3824
  for (const name of readdirSync10(dst)) {
3641
- const dstPath = join26(dst, name);
3642
- const srcPath = join26(src, name);
3825
+ const dstPath = join27(dst, name);
3826
+ const srcPath = join27(src, name);
3643
3827
  if (lstatSync10(dstPath).isDirectory()) {
3644
3828
  count += countLocalOnly(srcPath, dstPath);
3645
3829
  } else if (lstatSync10(srcPath, { throwIfNoEntry: false }) === void 0) {
@@ -3651,20 +3835,20 @@ function countLocalOnly(src, dst) {
3651
3835
  function scanLocalOnly() {
3652
3836
  const repo = repoHome();
3653
3837
  const claude = claudeHome();
3654
- const mapPath = join26(repo, "path-map.json");
3655
- const repoProjects = join26(repo, "shared", "projects");
3838
+ const mapPath = join27(repo, "path-map.json");
3839
+ const repoProjects = join27(repo, "shared", "projects");
3656
3840
  if (!existsSync21(mapPath) || !existsSync21(repoProjects)) return 0;
3657
3841
  const map = readPathMap(mapPath);
3658
- const localProjects = join26(claude, "projects");
3842
+ const localProjects = join27(claude, "projects");
3659
3843
  let count = 0;
3660
3844
  for (const [logical, hosts] of Object.entries(map.projects)) {
3661
3845
  assertSafeLogical(logical);
3662
3846
  const localPath = hosts[HOST];
3663
3847
  if (!localPath || localPath === "TBD") continue;
3664
3848
  assertSafeLocalRoot(localPath, logical);
3665
- const dst = join26(localProjects, encodePath(localPath));
3849
+ const dst = join27(localProjects, encodePath(localPath));
3666
3850
  if (!existsSync21(dst)) continue;
3667
- count += countLocalOnly(join26(repoProjects, logical), dst);
3851
+ count += countLocalOnly(join27(repoProjects, logical), dst);
3668
3852
  }
3669
3853
  return count;
3670
3854
  }
@@ -3699,17 +3883,17 @@ function remapPush(ts, opts = {}) {
3699
3883
  const wouldPush = [];
3700
3884
  const repo = repoHome();
3701
3885
  const claude = claudeHome();
3702
- const mapPath = join26(repo, "path-map.json");
3886
+ const mapPath = join27(repo, "path-map.json");
3703
3887
  if (!existsSync21(mapPath)) {
3704
3888
  log("no path-map.json; skipping session export");
3705
3889
  return { unmapped: 0, collisions: 0, pushed, wouldPush };
3706
3890
  }
3707
3891
  const map = readPathMap(mapPath);
3708
- const localProjects = join26(claude, "projects");
3709
- const repoProjects = join26(repo, "shared", "projects");
3892
+ const localProjects = join27(claude, "projects");
3893
+ const repoProjects = join27(repo, "shared", "projects");
3710
3894
  const reverse = buildReverseMap(map);
3711
3895
  if (!existsSync21(localProjects)) return { unmapped, collisions: 0, pushed, wouldPush };
3712
- if (!dryRun) mkdirSync5(repoProjects, { recursive: true });
3896
+ if (!dryRun) mkdirSync6(repoProjects, { recursive: true });
3713
3897
  for (const dir of readdirSync10(localProjects)) {
3714
3898
  if (dir.endsWith(TMP_SUFFIX)) continue;
3715
3899
  const logical = reverse.get(dir);
@@ -3717,8 +3901,8 @@ function remapPush(ts, opts = {}) {
3717
3901
  unmapped++;
3718
3902
  continue;
3719
3903
  }
3720
- const localDir = join26(localProjects, dir);
3721
- const repoDst = join26(repoProjects, logical);
3904
+ const localDir = join27(localProjects, dir);
3905
+ const repoDst = join27(repoProjects, logical);
3722
3906
  if (skipForSelection(opts.selection, localDir)) continue;
3723
3907
  if (dryRun) {
3724
3908
  wouldPush.push(logical);
@@ -3742,7 +3926,7 @@ function buildScanTree(tmpRoot) {
3742
3926
  const logicalToEncoded = /* @__PURE__ */ new Map();
3743
3927
  let staged = 0;
3744
3928
  const repo = repoHome();
3745
- const mapPath = join27(repo, "path-map.json");
3929
+ const mapPath = join28(repo, "path-map.json");
3746
3930
  if (!existsSync22(mapPath)) return { logicalToEncoded, staged, malformed: false };
3747
3931
  let map;
3748
3932
  try {
@@ -3760,12 +3944,12 @@ function buildScanTree(tmpRoot) {
3760
3944
  if (!p || p === "TBD") continue;
3761
3945
  reverse.set(encodePath(p), logical);
3762
3946
  }
3763
- const localProjects = join27(claudeHome(), "projects");
3947
+ const localProjects = join28(claudeHome(), "projects");
3764
3948
  if (!existsSync22(localProjects)) return { logicalToEncoded, staged, malformed: false };
3765
3949
  for (const dir of readdirSync11(localProjects)) {
3766
3950
  const logical = reverse.get(dir);
3767
3951
  if (!logical) continue;
3768
- copyDirJsonlOnly(join27(localProjects, dir), join27(tmpRoot, "shared", "projects", logical));
3952
+ copyDirJsonlOnly(join28(localProjects, dir), join28(tmpRoot, "shared", "projects", logical));
3769
3953
  logicalToEncoded.set(logical, dir);
3770
3954
  staged++;
3771
3955
  }
@@ -3773,7 +3957,7 @@ function buildScanTree(tmpRoot) {
3773
3957
  }
3774
3958
  function probeGitleaksForScan() {
3775
3959
  try {
3776
- execFileSync9("gitleaks", ["version"], { stdio: ["ignore", "pipe", "pipe"] });
3960
+ execFileSync10("gitleaks", ["version"], { stdio: ["ignore", "pipe", "pipe"] });
3777
3961
  return "ok";
3778
3962
  } catch (err) {
3779
3963
  if (err.code === "ENOENT") return "missing";
@@ -3794,13 +3978,12 @@ function ensureGitleaksReady(section2, gitleaksReady) {
3794
3978
  }
3795
3979
  return true;
3796
3980
  }
3797
- function reportCheckShared(section2, gitleaksReady) {
3798
- if (!ensureGitleaksReady(section2, gitleaksReady)) return;
3799
- const cacheDir = join27(homedir4(), ".cache", "claude-nomad");
3800
- mkdirSync6(cacheDir, { recursive: true });
3801
- const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes(4).toString("hex")}`;
3802
- const reportPath = join27(cacheDir, `check-shared-${stamp}.json`);
3803
- const tmpRoot = join27(cacheDir, `check-shared-tree-${stamp}`);
3981
+ function runLocalPreviewScan(section2) {
3982
+ const cacheDir = join28(homedir5(), ".cache", "claude-nomad");
3983
+ mkdirSync7(cacheDir, { recursive: true });
3984
+ const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes2(4).toString("hex")}`;
3985
+ const reportPath = join28(cacheDir, `check-shared-${stamp}.json`);
3986
+ const tmpRoot = join28(cacheDir, `check-shared-tree-${stamp}`);
3804
3987
  try {
3805
3988
  const { logicalToEncoded, staged, malformed } = buildScanTree(tmpRoot);
3806
3989
  if (malformed) {
@@ -3814,15 +3997,26 @@ function reportCheckShared(section2, gitleaksReady) {
3814
3997
  }
3815
3998
  scanAndReport(section2, tmpRoot, staged, logicalToEncoded);
3816
3999
  } finally {
3817
- rmSync10(reportPath, { force: true });
3818
- rmSync10(tmpRoot, { recursive: true, force: true });
4000
+ rmSync11(reportPath, { force: true });
4001
+ rmSync11(tmpRoot, { recursive: true, force: true });
4002
+ }
4003
+ }
4004
+ function reportCheckShared(section2, gitleaksReady) {
4005
+ if (!ensureGitleaksReady(section2, gitleaksReady)) return;
4006
+ try {
4007
+ runLocalPreviewScan(section2);
4008
+ } catch (err) {
4009
+ addItem(section2, `${red(failGlyph)} shared scan failed: ${err.message}`);
4010
+ process.exitCode = 1;
4011
+ } finally {
4012
+ reportCommittedMemory(section2);
3819
4013
  }
3820
4014
  }
3821
4015
 
3822
4016
  // src/commands.doctor.checks.hooks.scope.ts
3823
4017
  init_color();
3824
4018
  import { existsSync as existsSync23, readFileSync as readFileSync8, readdirSync as readdirSync12, realpathSync as realpathSync2 } from "node:fs";
3825
- import { dirname as dirname5, extname, join as join28 } from "node:path";
4019
+ import { dirname as dirname6, extname, join as join29 } from "node:path";
3826
4020
  init_config();
3827
4021
  function typeFromPackageJson(pkgPath) {
3828
4022
  try {
@@ -3842,11 +4036,11 @@ function effectiveType(hookPath) {
3842
4036
  } catch {
3843
4037
  return null;
3844
4038
  }
3845
- let dir = dirname5(real);
4039
+ let dir = dirname6(real);
3846
4040
  for (; ; ) {
3847
- const pkg = join28(dir, "package.json");
4041
+ const pkg = join29(dir, "package.json");
3848
4042
  if (existsSync23(pkg)) return typeFromPackageJson(pkg);
3849
- const parent = dirname5(dir);
4043
+ const parent = dirname6(dir);
3850
4044
  if (parent === dir) return "cjs";
3851
4045
  dir = parent;
3852
4046
  }
@@ -3887,7 +4081,7 @@ function safeRead(path) {
3887
4081
  }
3888
4082
  }
3889
4083
  function reportHookScopeCheck(section2) {
3890
- const hooksDir = join28(claudeHome(), "hooks");
4084
+ const hooksDir = join29(claudeHome(), "hooks");
3891
4085
  if (!existsSync23(hooksDir)) {
3892
4086
  addItem(section2, `${dim(infoGlyph)} no ~/.claude/hooks; skipping module-scope check`);
3893
4087
  return;
@@ -3895,7 +4089,7 @@ function reportHookScopeCheck(section2) {
3895
4089
  let anyWarn = false;
3896
4090
  for (const name of safeReaddir2(hooksDir)) {
3897
4091
  if (extname(name) !== ".js") continue;
3898
- const abs = join28(hooksDir, name);
4092
+ const abs = join29(hooksDir, name);
3899
4093
  const eff = effectiveType(abs);
3900
4094
  if (eff === null) continue;
3901
4095
  const src = safeRead(abs);
@@ -3916,7 +4110,7 @@ function reportHookScopeCheck(section2) {
3916
4110
  // src/commands.doctor.checks.hooks.ts
3917
4111
  init_color();
3918
4112
  import { existsSync as existsSync24 } from "node:fs";
3919
- import { join as join29 } from "node:path";
4113
+ import { join as join30 } from "node:path";
3920
4114
  init_config();
3921
4115
  function expandHome(token) {
3922
4116
  const h2 = home();
@@ -3972,7 +4166,7 @@ function checkEventGroups(section2, event, groups) {
3972
4166
  return anyFail;
3973
4167
  }
3974
4168
  function reportHooksTargetCheck(section2) {
3975
- const settingsPath = join29(claudeHome(), "settings.json");
4169
+ const settingsPath = join30(claudeHome(), "settings.json");
3976
4170
  if (!existsSync24(settingsPath)) {
3977
4171
  addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json; skipping hook target check`);
3978
4172
  return;
@@ -3997,11 +4191,11 @@ function reportHooksTargetCheck(section2) {
3997
4191
  // src/commands.doctor.checks.hooks.preserve-symlinks.ts
3998
4192
  init_color();
3999
4193
  import { existsSync as existsSync26, readFileSync as readFileSync9 } from "node:fs";
4000
- import { join as join31 } from "node:path";
4194
+ import { join as join32 } from "node:path";
4001
4195
 
4002
4196
  // src/commands.doctor.checks.hooks.preserve-symlinks.probe.ts
4003
4197
  import { closeSync as closeSync3, existsSync as existsSync25, openSync as openSync3, readSync, realpathSync as realpathSync3 } from "node:fs";
4004
- import { dirname as dirname6, join as join30, resolve as resolve2 } from "node:path";
4198
+ import { dirname as dirname7, join as join31, resolve as resolve2 } from "node:path";
4005
4199
  function suppressedRanges(src) {
4006
4200
  const ranges = [];
4007
4201
  const re = /\/\*[\s\S]*?\*\/|\/\/[^\n]*|'[^']*'|"[^"]*"|`[^`]*`/g;
@@ -4038,7 +4232,7 @@ function specifierIsMissing(specifier, baseDir) {
4038
4232
  if (existsSync25(base + ext)) return false;
4039
4233
  }
4040
4234
  for (const idx of ["index.js", "index.cjs", "index.mjs"]) {
4041
- if (existsSync25(join30(base, idx))) return false;
4235
+ if (existsSync25(join31(base, idx))) return false;
4042
4236
  }
4043
4237
  return true;
4044
4238
  }
@@ -4064,7 +4258,7 @@ function relativeRequireTargetsBroken(scriptPath) {
4064
4258
  }
4065
4259
  const specifiers = topRelativeSpecifiers(raw);
4066
4260
  if (specifiers.length === 0) return false;
4067
- const baseDir = dirname6(realPath);
4261
+ const baseDir = dirname7(realPath);
4068
4262
  for (const spec of specifiers) {
4069
4263
  if (specifierIsMissing(spec, baseDir)) return true;
4070
4264
  }
@@ -4093,7 +4287,7 @@ function commandTokens(command) {
4093
4287
  return tokens;
4094
4288
  }
4095
4289
  function readPathMapSafe() {
4096
- const mapPath = join31(repoHome(), "path-map.json");
4290
+ const mapPath = join32(repoHome(), "path-map.json");
4097
4291
  if (!existsSync26(mapPath)) return { projects: {} };
4098
4292
  try {
4099
4293
  return JSON.parse(readFileSync9(mapPath, "utf8"));
@@ -4168,7 +4362,7 @@ function checkEventForPreserveSymlinks(section2, event, groups, sharedLinkNames)
4168
4362
  return anyWarn;
4169
4363
  }
4170
4364
  function reportPreserveSymlinksCheck(section2) {
4171
- const settingsPath = join31(claudeHome(), "settings.json");
4365
+ const settingsPath = join32(claudeHome(), "settings.json");
4172
4366
  if (!existsSync26(settingsPath)) {
4173
4367
  addItem(
4174
4368
  section2,
@@ -4203,7 +4397,7 @@ function reportPreserveSymlinksCheck(section2) {
4203
4397
  // src/commands.doctor.checks.settings-drift.ts
4204
4398
  init_color();
4205
4399
  import { existsSync as existsSync27, readFileSync as readFileSync10 } from "node:fs";
4206
- import { join as join32 } from "node:path";
4400
+ import { join as join33 } from "node:path";
4207
4401
  init_config();
4208
4402
  init_utils_json();
4209
4403
  function diffMergedSettings(merged, settings) {
@@ -4221,7 +4415,7 @@ function tryReadJson(filePath) {
4221
4415
  }
4222
4416
  }
4223
4417
  function reportHooksBaseSelfCleanNote(section2) {
4224
- const basePath = join32(repoHome(), "shared", "settings.base.json");
4418
+ const basePath = join33(repoHome(), "shared", "settings.base.json");
4225
4419
  const base = tryReadJson(basePath);
4226
4420
  if (base === null) return;
4227
4421
  if (!baseHasGsdHookEntries(base)) return;
@@ -4234,9 +4428,9 @@ function reportSettingsDriftCheck(section2) {
4234
4428
  const claude = claudeHome();
4235
4429
  const repo = repoHome();
4236
4430
  const host = HOST;
4237
- const settingsPath = join32(claude, "settings.json");
4238
- const basePath = join32(repo, "shared", "settings.base.json");
4239
- const hostPath = join32(repo, "hosts", `${host}.json`);
4431
+ const settingsPath = join33(claude, "settings.json");
4432
+ const basePath = join33(repo, "shared", "settings.base.json");
4433
+ const hostPath = join33(repo, "hosts", `${host}.json`);
4240
4434
  if (!existsSync27(settingsPath)) {
4241
4435
  addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json; skipping merge-drift check`);
4242
4436
  return;
@@ -4418,7 +4612,7 @@ function reportNodeEngineCheck(section2) {
4418
4612
 
4419
4613
  // src/commands.doctor.checks.longpaths.ts
4420
4614
  init_color();
4421
- import { execFileSync as execFileSync10 } from "node:child_process";
4615
+ import { execFileSync as execFileSync11 } from "node:child_process";
4422
4616
  init_config();
4423
4617
  var PROBE_TIMEOUT_MS = 3e3;
4424
4618
  var LONGPATHS_REG_KEY = String.raw`HKLM\SYSTEM\CurrentControlSet\Control\FileSystem`;
@@ -4452,7 +4646,7 @@ function addLongpathsRow(section2, label, enabled2, remediation) {
4452
4646
  }
4453
4647
  addItem(section2, `${yellow(warnGlyph)} ${label}: not enabled (${remediation})`);
4454
4648
  }
4455
- function reportLongPathsCheck(section2, run = execFileSync10) {
4649
+ function reportLongPathsCheck(section2, run = execFileSync11) {
4456
4650
  if (process.platform !== "win32") return;
4457
4651
  addLongpathsRow(
4458
4652
  section2,
@@ -4474,15 +4668,15 @@ function reportSyncModality(section2) {
4474
4668
 
4475
4669
  // src/commands.doctor.checks.crlf.ts
4476
4670
  init_color();
4477
- import { execFileSync as execFileSync11 } from "node:child_process";
4671
+ import { execFileSync as execFileSync12 } from "node:child_process";
4478
4672
  import { existsSync as existsSync28, readFileSync as readFileSync13 } from "node:fs";
4479
- import { join as join33 } from "node:path";
4673
+ import { join as join34 } from "node:path";
4480
4674
  init_config();
4481
4675
  var PROBE_TIMEOUT_MS2 = 3e3;
4482
4676
  var GUARD_LINE = /^\*\s+-text\b/;
4483
4677
  function hasGitattributesGuard(repo) {
4484
4678
  try {
4485
- const path = join33(repo, ".gitattributes");
4679
+ const path = join34(repo, ".gitattributes");
4486
4680
  if (!existsSync28(path)) return false;
4487
4681
  const content = readFileSync13(path, "utf8");
4488
4682
  return content.split("\n").map((line) => line.trim()).some((line) => line !== "" && !line.startsWith("#") && GUARD_LINE.test(line));
@@ -4513,7 +4707,7 @@ function addExposedRow(section2, repo, verdict) {
4513
4707
  const remediation = `add a .gitattributes with a \`* -text\` line, or run \`git config core.autocrlf false\`, in ${repo}`;
4514
4708
  addItem(section2, `${yellow(warnGlyph)} CRLF guard: ${risk}; ${remediation}`);
4515
4709
  }
4516
- function reportCrlfGuardCheck(section2, run = execFileSync11) {
4710
+ function reportCrlfGuardCheck(section2, run = execFileSync12) {
4517
4711
  const repo = repoHome();
4518
4712
  if (hasGitattributesGuard(repo)) {
4519
4713
  addItem(section2, `${green(okGlyph)} CRLF guard: .gitattributes (* -text) present`);
@@ -4524,35 +4718,35 @@ function reportCrlfGuardCheck(section2, run = execFileSync11) {
4524
4718
 
4525
4719
  // src/spinner.ts
4526
4720
  init_color();
4527
- import { existsSync as existsSync32 } from "node:fs";
4721
+ import { existsSync as existsSync33 } from "node:fs";
4528
4722
  import { fileURLToPath as fileURLToPath4 } from "node:url";
4529
4723
  import { Worker } from "node:worker_threads";
4530
4724
 
4531
4725
  // src/commands.push.recovery.ts
4532
4726
  init_config();
4533
- import { readFileSync as readFileSync16, rmSync as rmSync12, writeFileSync as writeFileSync5 } from "node:fs";
4534
- import { join as join38 } from "node:path";
4727
+ import { readFileSync as readFileSync17, rmSync as rmSync13, writeFileSync as writeFileSync7 } from "node:fs";
4728
+ import { join as join40 } from "node:path";
4535
4729
  import { createInterface as createInterface2 } from "node:readline/promises";
4536
4730
 
4537
4731
  // src/commands.push.recovery.actions.ts
4538
4732
  init_config();
4539
- import { readFileSync as readFileSync15 } from "node:fs";
4733
+ import { readFileSync as readFileSync16 } from "node:fs";
4540
4734
  import { isAbsolute as isAbsolute2, resolve as resolve3, sep as sep6 } from "node:path";
4541
4735
 
4542
4736
  // src/commands.push.recovery.redact.ts
4543
4737
  init_config();
4544
4738
  init_config_sharedDirs_guard();
4545
- import { cpSync as cpSync7, existsSync as existsSync31, mkdirSync as mkdirSync7, statSync as statSync8 } from "node:fs";
4546
- import { dirname as dirname8, join as join36, sep as sep5 } from "node:path";
4739
+ import { cpSync as cpSync8, existsSync as existsSync32, mkdirSync as mkdirSync9, statSync as statSync8 } from "node:fs";
4740
+ import { dirname as dirname9, join as join38, sep as sep5 } from "node:path";
4547
4741
 
4548
4742
  // src/commands.redact.ts
4549
4743
  init_config();
4550
4744
  import { existsSync as existsSync30, statSync as statSync7 } from "node:fs";
4551
- import { dirname as dirname7, join as join35 } from "node:path";
4745
+ import { dirname as dirname8, join as join36 } from "node:path";
4552
4746
 
4553
4747
  // src/commands.redact.subtree.ts
4554
- import { existsSync as existsSync29, lstatSync as lstatSync11, readFileSync as readFileSync14, readdirSync as readdirSync13, statSync as statSync6, writeFileSync as writeFileSync4 } from "node:fs";
4555
- import { join as join34 } from "node:path";
4748
+ import { existsSync as existsSync29, lstatSync as lstatSync11, readFileSync as readFileSync14, readdirSync as readdirSync13, statSync as statSync6, writeFileSync as writeFileSync5 } from "node:fs";
4749
+ import { join as join35 } from "node:path";
4556
4750
  init_utils_fs();
4557
4751
  init_utils();
4558
4752
  function collectFiles(dir, out) {
@@ -4560,7 +4754,7 @@ function collectFiles(dir, out) {
4560
4754
  const st = lstatSync11(dir);
4561
4755
  if (!st.isDirectory()) return;
4562
4756
  for (const entry of readdirSync13(dir)) {
4563
- const abs = join34(dir, entry);
4757
+ const abs = join35(dir, entry);
4564
4758
  const lst = lstatSync11(abs);
4565
4759
  if (lst.isSymbolicLink()) continue;
4566
4760
  if (lst.isDirectory()) {
@@ -4604,7 +4798,7 @@ function applySubtreeRedactions(mainPath, mainFindings, subtreeFiles, rule, ts,
4604
4798
  `warning: no redaction applied to ${filePath}: finding match values were not located in the file. Inspect it manually; the push re-scan still blocks a real leak.`
4605
4799
  );
4606
4800
  }
4607
- writeFileSync4(filePath, after, "utf8");
4801
+ writeFileSync5(filePath, after, "utf8");
4608
4802
  }
4609
4803
  }
4610
4804
  return { total, dirty };
@@ -4612,10 +4806,10 @@ function applySubtreeRedactions(mainPath, mainFindings, subtreeFiles, rule, ts,
4612
4806
 
4613
4807
  // src/commands.pushed-history.ts
4614
4808
  init_utils();
4615
- import { execFileSync as execFileSync12 } from "node:child_process";
4809
+ import { execFileSync as execFileSync13 } from "node:child_process";
4616
4810
  function pushedRef(repo) {
4617
4811
  try {
4618
- const ref = execFileSync12("git", ["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"], {
4812
+ const ref = execFileSync13("git", ["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"], {
4619
4813
  cwd: repo,
4620
4814
  stdio: ["ignore", "pipe", "ignore"]
4621
4815
  }).toString().trim();
@@ -4628,7 +4822,7 @@ function sessionInPushedHistory(id, repo) {
4628
4822
  const ref = pushedRef(repo);
4629
4823
  if (ref === null) return false;
4630
4824
  try {
4631
- const out = execFileSync12(
4825
+ const out = execFileSync13(
4632
4826
  "git",
4633
4827
  [
4634
4828
  "log",
@@ -4665,14 +4859,14 @@ init_utils_json();
4665
4859
  init_utils();
4666
4860
  function resolveLiveTranscript(id) {
4667
4861
  try {
4668
- const mapPath = join35(repoHome(), "path-map.json");
4862
+ const mapPath = join36(repoHome(), "path-map.json");
4669
4863
  if (!existsSync30(mapPath)) return null;
4670
4864
  const projects = readJson(mapPath).projects;
4671
4865
  const claude = claudeHome();
4672
4866
  for (const hostMap of Object.values(projects)) {
4673
4867
  const abs = hostMap[HOST];
4674
4868
  if (abs === void 0) continue;
4675
- const live = join35(claude, "projects", encodePath(abs), `${id}.jsonl`);
4869
+ const live = join36(claude, "projects", encodePath(abs), `${id}.jsonl`);
4676
4870
  if (existsSync30(live)) return live;
4677
4871
  }
4678
4872
  return null;
@@ -4703,7 +4897,7 @@ function cmdRedact(opts, nowMs = Date.now, scan = scanFile) {
4703
4897
  process.exitCode = 1;
4704
4898
  return;
4705
4899
  }
4706
- const sessionDir = join35(dirname7(localPath), id);
4900
+ const sessionDir = join36(dirname8(localPath), id);
4707
4901
  const subtreeFiles = listSubtreeFiles(sessionDir);
4708
4902
  const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync7(p).mtimeMs);
4709
4903
  if (isRecentlyModified(subtreeMtime, nowMs())) {
@@ -4763,6 +4957,78 @@ init_utils();
4763
4957
 
4764
4958
  // src/commands.push.recovery.seams.ts
4765
4959
  init_push_gitleaks();
4960
+
4961
+ // src/commands.push.recovery.memory.ts
4962
+ init_config();
4963
+ init_config_sharedDirs_guard();
4964
+ import { cpSync as cpSync7, existsSync as existsSync31, mkdirSync as mkdirSync8, readFileSync as readFileSync15, writeFileSync as writeFileSync6 } from "node:fs";
4965
+ import { join as join37 } from "node:path";
4966
+ init_push_gitleaks_scan();
4967
+ init_utils_fs();
4968
+ init_utils_json();
4969
+ init_utils();
4970
+ var MEMORY_FINDING_PATH = /^shared\/projects\/([^/]+)\/memory\/([^/]+\.md)$/;
4971
+ var SAFE_MEMORY_FILENAME = /^[^/\\]+\.md$/;
4972
+ var MEMORY_DIR_PATH = /^shared\/projects\/[^/]+\/memory\//;
4973
+ function memoryFileFromFinding(f) {
4974
+ const m = MEMORY_FINDING_PATH.exec(f.File);
4975
+ if (m?.[1] === void 0 || m[2] === void 0) return null;
4976
+ return { logical: m[1], filename: m[2] };
4977
+ }
4978
+ function isMemoryFindingPath(f) {
4979
+ return MEMORY_DIR_PATH.test(f.File);
4980
+ }
4981
+ function resolveMemoryLocalPath(logical, filename, map) {
4982
+ assertSafeLogical(logical);
4983
+ if (!SAFE_MEMORY_FILENAME.test(filename) || filename.includes("..")) return null;
4984
+ const abs = map.projects[logical]?.[HOST];
4985
+ if (abs === void 0) return null;
4986
+ const localPath = join37(claudeHome(), "projects", encodePath(abs), "memory", filename);
4987
+ return existsSync31(localPath) ? localPath : null;
4988
+ }
4989
+ function preflightMemoryRedactable(f, map) {
4990
+ const parsed = memoryFileFromFinding(f);
4991
+ if (parsed === null) return "a finding is not a project-level memory file";
4992
+ const localPath = resolveMemoryLocalPath(parsed.logical, parsed.filename, map);
4993
+ if (localPath === null) {
4994
+ return `memory file ${parsed.logical}/memory/${parsed.filename}: local file not found or unmapped`;
4995
+ }
4996
+ return null;
4997
+ }
4998
+ function applyMemoryRedact(f, ts, map, scan = scanFile) {
4999
+ const refuse = (msg) => {
5000
+ log(msg);
5001
+ return false;
5002
+ };
5003
+ const parsed = memoryFileFromFinding(f);
5004
+ if (parsed === null) {
5005
+ return refuse("could not parse this finding as a project-level memory file; choose Skip.");
5006
+ }
5007
+ const { logical, filename } = parsed;
5008
+ const localPath = resolveMemoryLocalPath(logical, filename, map);
5009
+ if (localPath === null) {
5010
+ return refuse(
5011
+ `could not locate the local memory file for ${logical}/memory/${filename}; choose Skip.`
5012
+ );
5013
+ }
5014
+ const findings = scan(localPath);
5015
+ if (findings === null) {
5016
+ return refuse(`re-scan of ${logical}/memory/${filename} failed; choose Skip.`);
5017
+ }
5018
+ if (findings.length === 0) {
5019
+ return refuse(`nothing to redact in ${logical}/memory/${filename}; choose Skip.`);
5020
+ }
5021
+ backupBeforeWrite(localPath, ts);
5022
+ const before = readFileSync15(localPath, "utf8");
5023
+ const after = applyRedactions(before, findings);
5024
+ writeFileSync6(localPath, after, "utf8");
5025
+ const stagedMemoryDir = join37(repoHome(), "shared", "projects", logical, "memory");
5026
+ mkdirSync8(stagedMemoryDir, { recursive: true });
5027
+ cpSync7(localPath, join37(stagedMemoryDir, filename), { force: true });
5028
+ return true;
5029
+ }
5030
+
5031
+ // src/commands.push.recovery.seams.ts
4766
5032
  var MASK_LEAD = 4;
4767
5033
  var MASK_BODY = "************";
4768
5034
  var CONTEXT_WINDOW = 40;
@@ -4771,8 +5037,10 @@ function findingKey(f) {
4771
5037
  return `${f.File}:${f.StartLine}:${f.StartColumn}:${f.RuleID}`;
4772
5038
  }
4773
5039
  var VALID_SID = /^[A-Za-z0-9_-]+$/;
5040
+ var SUBTREE_PATH = /^shared\/projects\/[^/]+\/([^/]+)\/.+$/;
4774
5041
  function sessionIdFromFinding(f) {
4775
- const m = SESSION_PATH.exec(f.File) ?? /^shared\/projects\/[^/]+\/([^/]+)\//.exec(f.File);
5042
+ if (isMemoryFindingPath(f)) return null;
5043
+ const m = SESSION_PATH.exec(f.File) ?? SUBTREE_PATH.exec(f.File);
4776
5044
  if (m === null) return null;
4777
5045
  const sid = m[1];
4778
5046
  return VALID_SID.test(sid) ? sid : null;
@@ -4819,8 +5087,8 @@ function resolveStagedDir(localPath, map, claude, repo) {
4819
5087
  assertSafeLogical(logical);
4820
5088
  const abs = hostMap[HOST];
4821
5089
  if (abs === void 0) continue;
4822
- if (localPath.startsWith(join36(claude, "projects", encodePath(abs)) + sep5)) {
4823
- return join36(repo, "shared", "projects", logical);
5090
+ if (localPath.startsWith(join38(claude, "projects", encodePath(abs)) + sep5)) {
5091
+ return join38(repo, "shared", "projects", logical);
4824
5092
  }
4825
5093
  }
4826
5094
  return null;
@@ -4830,7 +5098,7 @@ function preflightRedactable(f, map, nowMs) {
4830
5098
  if (sid === null) return "a finding has no resolvable session id (not a session transcript)";
4831
5099
  const localPath = resolveLiveTranscript(sid);
4832
5100
  if (localPath === null) return `session ${sid}: local transcript not found`;
4833
- const sessionDir = join36(dirname8(localPath), sid);
5101
+ const sessionDir = join38(dirname9(localPath), sid);
4834
5102
  const subtreeFiles = listSubtreeFiles(sessionDir);
4835
5103
  const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync8(p).mtimeMs);
4836
5104
  if (isRecentlyModified(subtreeMtime, nowMs())) {
@@ -4860,7 +5128,7 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
4860
5128
  `could not locate the local transcript for session ${sid}; choose Skip or Drop session.`
4861
5129
  );
4862
5130
  }
4863
- const sessionDir = join36(dirname8(localPath), sid);
5131
+ const sessionDir = join38(dirname9(localPath), sid);
4864
5132
  const subtreeFiles = listSubtreeFiles(sessionDir);
4865
5133
  const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync8(p).mtimeMs);
4866
5134
  if (isRecentlyModified(subtreeMtime, nowMs())) {
@@ -4893,27 +5161,27 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
4893
5161
  `nothing to redact in the local transcript for session ${sid}; choose Skip or Drop session.`
4894
5162
  );
4895
5163
  }
4896
- mkdirSync7(stagedProjectDir, { recursive: true });
4897
- cpSync7(localPath, join36(stagedProjectDir, `${sid}.jsonl`), { force: true });
4898
- if (existsSync31(sessionDir)) {
4899
- cpSync7(sessionDir, join36(stagedProjectDir, sid), { force: true, recursive: true });
5164
+ mkdirSync9(stagedProjectDir, { recursive: true });
5165
+ cpSync8(localPath, join38(stagedProjectDir, `${sid}.jsonl`), { force: true });
5166
+ if (existsSync32(sessionDir)) {
5167
+ cpSync8(sessionDir, join38(stagedProjectDir, sid), { force: true, recursive: true });
4900
5168
  }
4901
5169
  return true;
4902
5170
  }
4903
5171
 
4904
5172
  // src/commands.push.recovery.drop.ts
4905
5173
  init_config();
4906
- import { rmSync as rmSync11 } from "node:fs";
4907
- import { join as join37 } from "node:path";
5174
+ import { rmSync as rmSync12 } from "node:fs";
5175
+ import { join as join39 } from "node:path";
4908
5176
  function dropSessionFromStaged(sid, map) {
4909
5177
  const logicals = Object.keys(map.projects);
4910
5178
  if (logicals.length === 0) return false;
4911
5179
  const repo = repoHome();
4912
5180
  for (const logical of logicals) {
4913
- const jsonl = join37(repo, "shared", "projects", logical, `${sid}.jsonl`);
4914
- const dir = join37(repo, "shared", "projects", logical, sid);
4915
- rmSync11(jsonl, { force: true });
4916
- rmSync11(dir, { recursive: true, force: true });
5181
+ const jsonl = join39(repo, "shared", "projects", logical, `${sid}.jsonl`);
5182
+ const dir = join39(repo, "shared", "projects", logical, sid);
5183
+ rmSync12(jsonl, { force: true });
5184
+ rmSync12(dir, { recursive: true, force: true });
4917
5185
  }
4918
5186
  return true;
4919
5187
  }
@@ -4947,7 +5215,7 @@ function makeDefaultReadLine(repo) {
4947
5215
  if (isAbsolute2(file) || target !== repoRoot && !target.startsWith(repoRoot + sep6)) {
4948
5216
  return null;
4949
5217
  }
4950
- const content = readFileSync15(target, "utf8");
5218
+ const content = readFileSync16(target, "utf8");
4951
5219
  const lines = content.split(/\r?\n/);
4952
5220
  const idx = line - 1;
4953
5221
  if (idx < 0 || idx >= lines.length) return null;
@@ -4970,6 +5238,22 @@ Finding: ${f.RuleID} in ${f.File} line ${f.StartLine}` + (sid === null ? "" : `
4970
5238
  }
4971
5239
  return actions;
4972
5240
  }
5241
+ function dispatchMemory(f, action, ctx) {
5242
+ const parsed = memoryFileFromFinding(f);
5243
+ if (parsed === null) {
5244
+ if (isMemoryFindingPath(f)) {
5245
+ log(`memory path not auto-redactable: ${f.File}; scrub it by hand or choose Skip`);
5246
+ }
5247
+ return;
5248
+ }
5249
+ if (action === "drop") {
5250
+ log("memory files cannot be dropped; use Redact or Skip");
5251
+ return;
5252
+ }
5253
+ const memKey = `${parsed.logical}/${parsed.filename}`;
5254
+ if (ctx.redactedMemory.has(memKey)) return;
5255
+ if (applyMemoryRedact(f, ctx.ts, ctx.map, ctx.scan)) ctx.redactedMemory.add(memKey);
5256
+ }
4973
5257
  function dispatchOne(f, ctx) {
4974
5258
  const action = ctx.actions.get(findingKey(f)) ?? "skip";
4975
5259
  if (action === "skip") return;
@@ -4979,7 +5263,10 @@ function dispatchOne(f, ctx) {
4979
5263
  applyAllow(f, ctx.repo);
4980
5264
  return;
4981
5265
  }
4982
- if (sid === null) return;
5266
+ if (sid === null) {
5267
+ dispatchMemory(f, action, ctx);
5268
+ return;
5269
+ }
4983
5270
  if (action === "drop") {
4984
5271
  ctx.droppedSids.add(sid);
4985
5272
  if (ctx.drop(sid, ctx.map)) {
@@ -5004,20 +5291,46 @@ function dispatchActions(findings, actions, opts) {
5004
5291
  scan,
5005
5292
  drop,
5006
5293
  redactedSids: /* @__PURE__ */ new Set(),
5007
- droppedSids: /* @__PURE__ */ new Set()
5294
+ droppedSids: /* @__PURE__ */ new Set(),
5295
+ redactedMemory: /* @__PURE__ */ new Set()
5008
5296
  };
5009
5297
  for (const f of findings) {
5010
5298
  dispatchOne(f, ctx);
5011
5299
  }
5012
5300
  }
5301
+ function redactAllDedupeKey(f) {
5302
+ const sid = sessionIdFromFinding(f);
5303
+ if (sid !== null) return sid;
5304
+ const parsed = memoryFileFromFinding(f);
5305
+ return parsed !== null ? `${parsed.logical}/${parsed.filename}` : findingKey(f);
5306
+ }
5307
+ function redactAllPreflightOne(f, map, nowMs) {
5308
+ if (sessionIdFromFinding(f) === null && memoryFileFromFinding(f) !== null) {
5309
+ return preflightMemoryRedactable(f, map);
5310
+ }
5311
+ return preflightRedactable(f, map, nowMs);
5312
+ }
5313
+ function redactAllOne(f, ts, map, nowMs, scan, redactedSids, redactedMemory) {
5314
+ const sid = sessionIdFromFinding(f);
5315
+ if (sid !== null) {
5316
+ if (redactedSids.has(sid)) return;
5317
+ if (applyRedact(f, ts, map, nowMs, scan)) redactedSids.add(sid);
5318
+ return;
5319
+ }
5320
+ const parsed = memoryFileFromFinding(f);
5321
+ if (parsed === null) return;
5322
+ const memKey = `${parsed.logical}/${parsed.filename}`;
5323
+ if (redactedMemory.has(memKey)) return;
5324
+ if (applyMemoryRedact(f, ts, map, scan)) redactedMemory.add(memKey);
5325
+ }
5013
5326
  function redactAllFindings(findings, ts, map, nowMs, scan = scanFile) {
5014
5327
  const refusals = [];
5015
5328
  const preflighted = /* @__PURE__ */ new Set();
5016
5329
  for (const f of findings) {
5017
- const dedupeKey = sessionIdFromFinding(f) ?? findingKey(f);
5330
+ const dedupeKey = redactAllDedupeKey(f);
5018
5331
  if (preflighted.has(dedupeKey)) continue;
5019
5332
  preflighted.add(dedupeKey);
5020
- const reason = preflightRedactable(f, map, nowMs);
5333
+ const reason = redactAllPreflightOne(f, map, nowMs);
5021
5334
  if (reason !== null) refusals.push(reason);
5022
5335
  }
5023
5336
  if (refusals.length > 0) {
@@ -5028,10 +5341,9 @@ function redactAllFindings(findings, ts, map, nowMs, scan = scanFile) {
5028
5341
  );
5029
5342
  }
5030
5343
  const redactedSids = /* @__PURE__ */ new Set();
5344
+ const redactedMemory = /* @__PURE__ */ new Set();
5031
5345
  for (const f of findings) {
5032
- const sid = sessionIdFromFinding(f);
5033
- if (sid === null || redactedSids.has(sid)) continue;
5034
- if (applyRedact(f, ts, map, nowMs, scan)) redactedSids.add(sid);
5346
+ redactAllOne(f, ts, map, nowMs, scan, redactedSids, redactedMemory);
5035
5347
  }
5036
5348
  }
5037
5349
 
@@ -5068,10 +5380,10 @@ function applyThenRescan(scanVerdict, repoHome2) {
5068
5380
  return next;
5069
5381
  }
5070
5382
  function allowThenRescan(append, scanVerdict, repoHome2) {
5071
- const ignPath = join38(repoHome2, ".gitleaksignore");
5383
+ const ignPath = join40(repoHome2, ".gitleaksignore");
5072
5384
  let before;
5073
5385
  try {
5074
- before = readFileSync16(ignPath, "utf8");
5386
+ before = readFileSync17(ignPath, "utf8");
5075
5387
  } catch {
5076
5388
  before = null;
5077
5389
  }
@@ -5079,8 +5391,8 @@ function allowThenRescan(append, scanVerdict, repoHome2) {
5079
5391
  try {
5080
5392
  return applyThenRescan(scanVerdict, repoHome2);
5081
5393
  } catch (err) {
5082
- if (before === null) rmSync12(ignPath, { force: true });
5083
- else writeFileSync5(ignPath, before, "utf8");
5394
+ if (before === null) rmSync13(ignPath, { force: true });
5395
+ else writeFileSync7(ignPath, before, "utf8");
5084
5396
  throw err;
5085
5397
  }
5086
5398
  }
@@ -5167,7 +5479,7 @@ function writeAnimatedDone(out, label, ms, useTTY) {
5167
5479
  `);
5168
5480
  }
5169
5481
  function resolveWorkerPath(deps = {}) {
5170
- const check = deps.existsSyncFn ?? existsSync32;
5482
+ const check = deps.existsSyncFn ?? existsSync33;
5171
5483
  const base = deps.baseUrl ?? import.meta.url;
5172
5484
  const mjs = fileURLToPath4(new URL("./nomad.worker.mjs", base));
5173
5485
  if (check(mjs)) return mjs;
@@ -5233,9 +5545,9 @@ function withSpinner(label, fn, deps) {
5233
5545
 
5234
5546
  // src/commands.doctor.gitleaks-version.ts
5235
5547
  init_color();
5236
- import { execFileSync as execFileSync13 } from "node:child_process";
5237
- import { existsSync as existsSync33 } from "node:fs";
5238
- import { join as join39 } from "node:path";
5548
+ import { execFileSync as execFileSync14 } from "node:child_process";
5549
+ import { existsSync as existsSync34 } from "node:fs";
5550
+ import { join as join41 } from "node:path";
5239
5551
  init_config();
5240
5552
  var SEMVER_MAJOR_MINOR = /^(\d+)\.(\d+)\.\d+$/;
5241
5553
  var GITLEAKS_TIMEOUT_MS = 5e3;
@@ -5244,7 +5556,7 @@ function majorMinorOf(value) {
5244
5556
  return m === null ? null : [m[1], m[2]];
5245
5557
  }
5246
5558
  function readGitleaksVersion(run, tomlExists) {
5247
- const tomlPath = join39(repoHome(), ".gitleaks.toml");
5559
+ const tomlPath = join41(repoHome(), ".gitleaks.toml");
5248
5560
  const args = ["version"];
5249
5561
  if (tomlExists(tomlPath)) args.push("--config", tomlPath);
5250
5562
  try {
@@ -5256,7 +5568,7 @@ function readGitleaksVersion(run, tomlExists) {
5256
5568
  return null;
5257
5569
  }
5258
5570
  }
5259
- function reportGitleaksVersionCheck(section2, run = execFileSync13, tomlExists = existsSync33) {
5571
+ function reportGitleaksVersionCheck(section2, run = execFileSync14, tomlExists = existsSync34) {
5260
5572
  const raw = readGitleaksVersion(run, tomlExists);
5261
5573
  if (raw === null) return;
5262
5574
  const local = majorMinorOf(raw);
@@ -5276,7 +5588,7 @@ function reportGitleaksVersionCheck(section2, run = execFileSync13, tomlExists =
5276
5588
 
5277
5589
  // src/commands.doctor.checks.deps.ts
5278
5590
  init_color();
5279
- import { execFileSync as execFileSync14 } from "node:child_process";
5591
+ import { execFileSync as execFileSync15 } from "node:child_process";
5280
5592
  var VERSION_TOKEN = /(\d{1,9}\.\d{1,9}\.\d{1,9})/;
5281
5593
  var PROBE_TIMEOUT_MS3 = 3e3;
5282
5594
  var FETCHER_BASE = "HTTP fetcher";
@@ -5313,7 +5625,7 @@ function reportFetcherRow(section2, run) {
5313
5625
  );
5314
5626
  }
5315
5627
  }
5316
- function reportOptionalDeps(section2, run = execFileSync14) {
5628
+ function reportOptionalDeps(section2, run = execFileSync15) {
5317
5629
  const gh = probeOptionalDep("gh", run);
5318
5630
  if (gh.status === "present") {
5319
5631
  addItem(section2, `${green(okGlyph)} gh: ${gh.version ?? "present"}`);
@@ -5328,11 +5640,11 @@ function reportOptionalDeps(section2, run = execFileSync14) {
5328
5640
 
5329
5641
  // src/commands.doctor.actions-drift.ts
5330
5642
  init_color();
5331
- import { execFileSync as execFileSync16 } from "node:child_process";
5643
+ import { execFileSync as execFileSync17 } from "node:child_process";
5332
5644
  init_config();
5333
5645
 
5334
5646
  // src/gh-actions.ts
5335
- import { execFileSync as execFileSync15 } from "node:child_process";
5647
+ import { execFileSync as execFileSync16 } from "node:child_process";
5336
5648
  var GH_TIMEOUT_MS = 5e3;
5337
5649
  function parseGitHubRemote(remoteUrl) {
5338
5650
  const normalized = remoteUrl.trim().replace(/\/$/, "");
@@ -5340,7 +5652,7 @@ function parseGitHubRemote(remoteUrl) {
5340
5652
  if (m === null) return null;
5341
5653
  return { owner: m[1], repo: m[2] };
5342
5654
  }
5343
- function ghAuthStatus(run = execFileSync15) {
5655
+ function ghAuthStatus(run = execFileSync16) {
5344
5656
  try {
5345
5657
  run("gh", ["auth", "status"], {
5346
5658
  stdio: ["ignore", "ignore", "ignore"],
@@ -5354,7 +5666,7 @@ function ghAuthStatus(run = execFileSync15) {
5354
5666
  return "gh-probe-error";
5355
5667
  }
5356
5668
  }
5357
- function isRepoPrivate(ref, run = execFileSync15) {
5669
+ function isRepoPrivate(ref, run = execFileSync16) {
5358
5670
  const out = run("gh", ["repo", "view", `${ref.owner}/${ref.repo}`, "--json", "isPrivate"], {
5359
5671
  stdio: ["ignore", "pipe", "ignore"],
5360
5672
  timeout: GH_TIMEOUT_MS
@@ -5362,7 +5674,7 @@ function isRepoPrivate(ref, run = execFileSync15) {
5362
5674
  const parsed = JSON.parse(out);
5363
5675
  return parsed.isPrivate === true;
5364
5676
  }
5365
- function isActionsEnabled(ref, run = execFileSync15) {
5677
+ function isActionsEnabled(ref, run = execFileSync16) {
5366
5678
  const out = run(
5367
5679
  "gh",
5368
5680
  ["api", `repos/${ref.owner}/${ref.repo}/actions/permissions`, "--jq", ".enabled"],
@@ -5370,7 +5682,7 @@ function isActionsEnabled(ref, run = execFileSync15) {
5370
5682
  ).toString().trim();
5371
5683
  return out === "true";
5372
5684
  }
5373
- function disableActions(ref, run = execFileSync15) {
5685
+ function disableActions(ref, run = execFileSync16) {
5374
5686
  run(
5375
5687
  "gh",
5376
5688
  [
@@ -5384,7 +5696,7 @@ function disableActions(ref, run = execFileSync15) {
5384
5696
  { stdio: ["ignore", "ignore", "pipe"], timeout: GH_TIMEOUT_MS }
5385
5697
  );
5386
5698
  }
5387
- function readOriginRemote(cwd, run = execFileSync15) {
5699
+ function readOriginRemote(cwd, run = execFileSync16) {
5388
5700
  return run("git", ["remote", "get-url", "origin"], {
5389
5701
  cwd,
5390
5702
  stdio: ["ignore", "pipe", "ignore"]
@@ -5392,7 +5704,7 @@ function readOriginRemote(cwd, run = execFileSync15) {
5392
5704
  }
5393
5705
 
5394
5706
  // src/commands.doctor.actions-drift.ts
5395
- function reportActionsDrift(section2, run = execFileSync16) {
5707
+ function reportActionsDrift(section2, run = execFileSync17) {
5396
5708
  let remote;
5397
5709
  try {
5398
5710
  remote = readOriginRemote(repoHome(), run);
@@ -5482,8 +5794,8 @@ function gatherDoctorSections(opts) {
5482
5794
  reportLongPathsCheck(host);
5483
5795
  reportCrlfGuardCheck(host);
5484
5796
  const links = section("Shared links");
5485
- const mapPath = join40(repoHome(), "path-map.json");
5486
- const rawMap = existsSync34(mapPath) ? readJsonSafe(mapPath, mapPath, links) : null;
5797
+ const mapPath = join42(repoHome(), "path-map.json");
5798
+ const rawMap = existsSync35(mapPath) ? readJsonSafe(mapPath, mapPath, links) : null;
5487
5799
  const map = rawMap ?? { projects: {} };
5488
5800
  reportSharedLinks(links, map);
5489
5801
  reportDroppedNamesMigration(links);
@@ -5579,15 +5891,15 @@ function parseDoctorArgs(args) {
5579
5891
 
5580
5892
  // src/commands.drop-session.ts
5581
5893
  init_config();
5582
- import { execFileSync as execFileSync18 } from "node:child_process";
5583
- import { existsSync as existsSync36, readdirSync as readdirSync14, statSync as statSync9 } from "node:fs";
5584
- import { join as join42, relative as relative6 } from "node:path";
5894
+ import { execFileSync as execFileSync19 } from "node:child_process";
5895
+ import { existsSync as existsSync37, readdirSync as readdirSync14, statSync as statSync9 } from "node:fs";
5896
+ import { join as join44, relative as relative6 } from "node:path";
5585
5897
 
5586
5898
  // src/commands.drop-session.git.ts
5587
- import { execFileSync as execFileSync17 } from "node:child_process";
5899
+ import { execFileSync as execFileSync18 } from "node:child_process";
5588
5900
  function expandStagedDir(dirRel, repo) {
5589
5901
  try {
5590
- const out = execFileSync17("git", ["ls-files", "-z", "--", dirRel], {
5902
+ const out = execFileSync18("git", ["ls-files", "-z", "--", dirRel], {
5591
5903
  cwd: repo,
5592
5904
  stdio: ["ignore", "pipe", "pipe"]
5593
5905
  });
@@ -5599,7 +5911,7 @@ function expandStagedDir(dirRel, repo) {
5599
5911
  function isTrackedInHead(rel, repo) {
5600
5912
  try {
5601
5913
  const treePath = process.platform === "win32" ? rel.replaceAll("\\", "/") : rel;
5602
- execFileSync17("git", ["cat-file", "-e", `HEAD:${treePath}`], {
5914
+ execFileSync18("git", ["cat-file", "-e", `HEAD:${treePath}`], {
5603
5915
  cwd: repo,
5604
5916
  stdio: ["ignore", "pipe", "pipe"]
5605
5917
  });
@@ -5610,7 +5922,7 @@ function isTrackedInHead(rel, repo) {
5610
5922
  }
5611
5923
  function isInIndex(rel, repo) {
5612
5924
  try {
5613
- const out = execFileSync17("git", ["ls-files", "--", rel], {
5925
+ const out = execFileSync18("git", ["ls-files", "--", rel], {
5614
5926
  cwd: repo,
5615
5927
  stdio: ["ignore", "pipe", "pipe"]
5616
5928
  });
@@ -5624,8 +5936,8 @@ function isInIndex(rel, repo) {
5624
5936
  init_config();
5625
5937
  init_utils();
5626
5938
  init_utils_json();
5627
- import { existsSync as existsSync35 } from "node:fs";
5628
- import { join as join41 } from "node:path";
5939
+ import { existsSync as existsSync36 } from "node:fs";
5940
+ import { join as join43 } from "node:path";
5629
5941
  var SHARED_PROJECT_LOGICAL = /^shared\/projects\/([^/]+)\//;
5630
5942
  function reportScrubHint(id, matches) {
5631
5943
  const live = resolveLiveTranscript2(id, matches);
@@ -5641,8 +5953,8 @@ function reportScrubHint(id, matches) {
5641
5953
  }
5642
5954
  function resolveLiveTranscript2(id, matches) {
5643
5955
  try {
5644
- const mapPath = join41(repoHome(), "path-map.json");
5645
- if (!existsSync35(mapPath)) return null;
5956
+ const mapPath = join43(repoHome(), "path-map.json");
5957
+ if (!existsSync36(mapPath)) return null;
5646
5958
  const projects = readJson(mapPath).projects;
5647
5959
  const claude = claudeHome();
5648
5960
  for (const rel of matches) {
@@ -5650,8 +5962,8 @@ function resolveLiveTranscript2(id, matches) {
5650
5962
  if (logical === void 0) continue;
5651
5963
  const abs = projects[logical]?.[HOST];
5652
5964
  if (abs === void 0) continue;
5653
- const live = join41(claude, "projects", encodePath(abs), `${id}.jsonl`);
5654
- if (existsSync35(live)) return live;
5965
+ const live = join43(claude, "projects", encodePath(abs), `${id}.jsonl`);
5966
+ if (existsSync36(live)) return live;
5655
5967
  }
5656
5968
  return null;
5657
5969
  } catch {
@@ -5667,12 +5979,12 @@ function cmdDropSession(id) {
5667
5979
  process.exit(1);
5668
5980
  }
5669
5981
  const repo = repoHome();
5670
- if (!existsSync36(repo)) die(`repo not cloned at ${repo}`);
5982
+ if (!existsSync37(repo)) die(`repo not cloned at ${repo}`);
5671
5983
  const handle = acquireLock("drop-session");
5672
5984
  if (handle === null) process.exit(0);
5673
5985
  try {
5674
- const repoProjects = join42(repo, "shared", "projects");
5675
- if (!existsSync36(repoProjects)) {
5986
+ const repoProjects = join44(repo, "shared", "projects");
5987
+ if (!existsSync37(repoProjects)) {
5676
5988
  throw new NomadFatal(`no staged session matches ${id}`);
5677
5989
  }
5678
5990
  const matches = collectMatches(repoProjects, id, repo);
@@ -5695,12 +6007,12 @@ function cmdDropSession(id) {
5695
6007
  function collectMatches(repoProjects, id, repo) {
5696
6008
  const matches = [];
5697
6009
  for (const logical of readdirSync14(repoProjects)) {
5698
- const candidate = join42(repoProjects, logical, `${id}.jsonl`);
5699
- if (existsSync36(candidate)) {
6010
+ const candidate = join44(repoProjects, logical, `${id}.jsonl`);
6011
+ if (existsSync37(candidate)) {
5700
6012
  matches.push(relative6(repo, candidate));
5701
6013
  }
5702
- const dir = join42(repoProjects, logical, id);
5703
- if (existsSync36(dir) && statSync9(dir).isDirectory()) {
6014
+ const dir = join44(repoProjects, logical, id);
6015
+ if (existsSync37(dir) && statSync9(dir).isDirectory()) {
5704
6016
  const dirRel = relative6(repo, dir);
5705
6017
  const staged = expandStagedDir(dirRel, repo);
5706
6018
  if (staged.length > 0) matches.push(...staged);
@@ -5716,12 +6028,12 @@ function unstageOne(rel, repo) {
5716
6028
  }
5717
6029
  try {
5718
6030
  if (isTrackedInHead(rel, repo)) {
5719
- execFileSync18("git", ["restore", "--staged", "--worktree", "--", rel], {
6031
+ execFileSync19("git", ["restore", "--staged", "--worktree", "--", rel], {
5720
6032
  cwd: repo,
5721
6033
  stdio: ["ignore", "pipe", "pipe"]
5722
6034
  });
5723
6035
  } else {
5724
- execFileSync18("git", ["rm", "--cached", "-f", "--", rel], {
6036
+ execFileSync19("git", ["rm", "--cached", "-f", "--", rel], {
5725
6037
  cwd: repo,
5726
6038
  stdio: ["ignore", "pipe", "pipe"]
5727
6039
  });
@@ -5735,8 +6047,8 @@ function unstageOne(rel, repo) {
5735
6047
  }
5736
6048
 
5737
6049
  // src/commands.pull.ts
5738
- import { existsSync as existsSync41, mkdirSync as mkdirSync10 } from "node:fs";
5739
- import { join as join48 } from "node:path";
6050
+ import { existsSync as existsSync42, mkdirSync as mkdirSync12 } from "node:fs";
6051
+ import { join as join50 } from "node:path";
5740
6052
 
5741
6053
  // src/commands.push.sections.ts
5742
6054
  init_color();
@@ -5818,19 +6130,25 @@ function summarySection(st) {
5818
6130
  addItem(s, summaryRow("push", unmapped, st.remap.collisions, st.extras.skipped));
5819
6131
  return s;
5820
6132
  }
5821
- function renderPushTree(st, verdict) {
6133
+ function buildPushTreeSections(st, verdict) {
5822
6134
  const leakScan = section("Leak scan");
5823
6135
  addItem(leakScan, verdict.verdictRow);
5824
- renderTree([...syncedSections(st), leakScan, summarySection(st)]);
6136
+ return [...syncedSections(st), leakScan, summarySection(st)];
5825
6137
  }
5826
- function renderNoScanTree(st, opts = {}) {
6138
+ function renderPushTree(st, verdict) {
6139
+ renderTree(buildPushTreeSections(st, verdict));
6140
+ }
6141
+ function buildNoScanSections(st, opts = {}) {
5827
6142
  const sections = [];
5828
6143
  if (opts.noMapHint === true) {
5829
6144
  const pathMap = section("Path map");
5830
6145
  addItem(pathMap, `${dim(infoGlyph)} no path-map.json (nothing to preview)`);
5831
6146
  sections.push(pathMap);
5832
6147
  }
5833
- renderTree([...sections, ...syncedSections(st), summarySection(st)]);
6148
+ return [...sections, ...syncedSections(st), summarySection(st)];
6149
+ }
6150
+ function renderNoScanTree(st, opts = {}) {
6151
+ renderTree(buildNoScanSections(st, opts));
5834
6152
  }
5835
6153
 
5836
6154
  // src/commands.pull.ts
@@ -5838,18 +6156,18 @@ init_config();
5838
6156
 
5839
6157
  // src/extras-sync.ts
5840
6158
  init_config();
5841
- import { existsSync as existsSync38, statSync as statSync10 } from "node:fs";
5842
- import { join as join45 } from "node:path";
6159
+ import { existsSync as existsSync39, statSync as statSync10 } from "node:fs";
6160
+ import { join as join47 } from "node:path";
5843
6161
  init_utils();
5844
6162
  init_utils_json();
5845
6163
 
5846
6164
  // src/extras-sync.remap.ts
5847
6165
  init_config();
5848
- import { existsSync as existsSync37, mkdirSync as mkdirSync8, readdirSync as readdirSync15, readFileSync as readFileSync17, realpathSync as realpathSync4, rmSync as rmSync13 } from "node:fs";
5849
- import { dirname as dirname9, join as join44, sep as sep8 } from "node:path";
6166
+ import { existsSync as existsSync38, mkdirSync as mkdirSync10, readdirSync as readdirSync15, readFileSync as readFileSync18, realpathSync as realpathSync4, rmSync as rmSync14 } from "node:fs";
6167
+ import { dirname as dirname10, join as join46, sep as sep8 } from "node:path";
5850
6168
 
5851
6169
  // src/extras-sync.planning-diff.ts
5852
- import { join as join43, normalize as normalize2, sep as sep7 } from "node:path";
6170
+ import { join as join45, normalize as normalize2, sep as sep7 } from "node:path";
5853
6171
  init_utils();
5854
6172
  function processRecord(fields, i, changed, deleted) {
5855
6173
  const status = fields[i];
@@ -5901,7 +6219,7 @@ function planningDeleteTargets(opts) {
5901
6219
  const { deleted } = parsePlanningDiff(raw);
5902
6220
  const logicalPrefix = "shared/extras/" + logical + "/";
5903
6221
  const prefix = logicalPrefix + ".planning/";
5904
- const planningRoot = join43(localRoot, ".planning");
6222
+ const planningRoot = join45(localRoot, ".planning");
5905
6223
  const planningRootBoundary = planningRoot + sep7;
5906
6224
  const targets = [];
5907
6225
  for (const repoPath of deleted) {
@@ -5909,7 +6227,7 @@ function planningDeleteTargets(opts) {
5909
6227
  continue;
5910
6228
  }
5911
6229
  const remainder = repoPath.slice(logicalPrefix.length);
5912
- const candidate = join43(localRoot, remainder);
6230
+ const candidate = join45(localRoot, remainder);
5913
6231
  const resolved = normalize2(candidate);
5914
6232
  if (!resolved.startsWith(planningRootBoundary)) {
5915
6233
  throw new NomadFatal(
@@ -5930,7 +6248,7 @@ function runExtrasOp(v, dryRun, paths, backup, copy) {
5930
6248
  const would = [];
5931
6249
  for (const t of eachExtrasTarget(v, counts)) {
5932
6250
  const { src, dst } = paths(t);
5933
- if (!existsSync37(src)) continue;
6251
+ if (!existsSync38(src)) continue;
5934
6252
  const item2 = `${t.logical}/${t.dirname}`;
5935
6253
  if (dryRun) {
5936
6254
  would.push(item2);
@@ -5943,15 +6261,15 @@ function runExtrasOp(v, dryRun, paths, backup, copy) {
5943
6261
  return { ...counts, done, would };
5944
6262
  }
5945
6263
  function pruneEmptyAncestors(target, planningRoot) {
5946
- let dir = dirname9(target);
6264
+ let dir = dirname10(target);
5947
6265
  while (dir !== planningRoot && dir.startsWith(planningRoot + sep8)) {
5948
6266
  try {
5949
6267
  if (readdirSync15(dir).length > 0) break;
5950
- rmSync13(dir, { recursive: true, force: true });
6268
+ rmSync14(dir, { recursive: true, force: true });
5951
6269
  } catch {
5952
6270
  break;
5953
6271
  }
5954
- dir = dirname9(dir);
6272
+ dir = dirname10(dir);
5955
6273
  }
5956
6274
  }
5957
6275
  function tryRealpath(dir) {
@@ -5965,20 +6283,20 @@ function isInsidePlanningRoot(parentReal, rootReal) {
5965
6283
  return parentReal === rootReal || parentReal.startsWith(rootReal + sep8);
5966
6284
  }
5967
6285
  function deletePlanningTarget(target, planningRoot, repoCounterpart) {
5968
- if (existsSync37(repoCounterpart)) return;
5969
- const parentReal = tryRealpath(dirname9(target));
6286
+ if (existsSync38(repoCounterpart)) return;
6287
+ const parentReal = tryRealpath(dirname10(target));
5970
6288
  if (parentReal === void 0) return;
5971
6289
  const rootReal = tryRealpath(planningRoot);
5972
6290
  if (rootReal === void 0) return;
5973
6291
  if (!isInsidePlanningRoot(parentReal, rootReal)) return;
5974
- rmSync13(target, { recursive: true, force: true });
6292
+ rmSync14(target, { recursive: true, force: true });
5975
6293
  pruneEmptyAncestors(target, planningRoot);
5976
6294
  }
5977
6295
  function localDivergesFromPreDelete(target, pre, repoRel, repo) {
5978
- if (!existsSync37(target)) return false;
6296
+ if (!existsSync38(target)) return false;
5979
6297
  try {
5980
6298
  const preBlob = gitCaptureBuffer(["show", `${pre}:${repoRel}`], repo);
5981
- return !readFileSync17(target).equals(preBlob);
6299
+ return !readFileSync18(target).equals(preBlob);
5982
6300
  } catch {
5983
6301
  return true;
5984
6302
  }
@@ -6020,7 +6338,7 @@ function keptDeletePreview(v, prePostHeads, repo) {
6020
6338
  return kept;
6021
6339
  }
6022
6340
  function propagatePlanningDeletes(v, ts, prePostHeads, repo) {
6023
- const repoExtras = join44(repo, "shared", "extras");
6341
+ const repoExtras = join46(repo, "shared", "extras");
6024
6342
  for (const t of eachExtrasTarget(v, { unmapped: 0, skipped: 0 })) {
6025
6343
  if (t.dirname !== ".planning") continue;
6026
6344
  let raw;
@@ -6035,14 +6353,14 @@ function propagatePlanningDeletes(v, ts, prePostHeads, repo) {
6035
6353
  }
6036
6354
  const pairs = deletePairsFor(t, raw);
6037
6355
  if (pairs.length === 0) continue;
6038
- backupExtrasWrite(join44(t.localRoot, t.dirname), ts, t.localRoot);
6039
- const planningRoot = join44(t.localRoot, ".planning");
6356
+ backupExtrasWrite(join46(t.localRoot, t.dirname), ts, t.localRoot);
6357
+ const planningRoot = join46(t.localRoot, ".planning");
6040
6358
  for (const { target, relToLocal, repoRel } of pairs) {
6041
6359
  if (localDivergesFromPreDelete(target, prePostHeads.pre, repoRel, repo)) {
6042
6360
  warn(keptDeleteWarnLine(t.logical, relToLocal));
6043
6361
  continue;
6044
6362
  }
6045
- deletePlanningTarget(target, planningRoot, join44(repoExtras, t.logical, relToLocal));
6363
+ deletePlanningTarget(target, planningRoot, join46(repoExtras, t.logical, relToLocal));
6046
6364
  }
6047
6365
  }
6048
6366
  }
@@ -6051,14 +6369,14 @@ function remapExtrasPush(ts, opts = {}) {
6051
6369
  const v = loadValidatedExtras({ missingMsg: "no path-map.json; skipping extras push" });
6052
6370
  if (v === null) return { unmapped: 0, skipped: 0, pushed: [], wouldPush: [] };
6053
6371
  const repo = repoHome();
6054
- const repoExtras = join44(repo, "shared", "extras");
6055
- if (!dryRun) mkdirSync8(repoExtras, { recursive: true });
6372
+ const repoExtras = join46(repo, "shared", "extras");
6373
+ if (!dryRun) mkdirSync10(repoExtras, { recursive: true });
6056
6374
  const { unmapped, skipped, done, would } = runExtrasOp(
6057
6375
  v,
6058
6376
  dryRun,
6059
- ({ localRoot, logical, dirname: dirname10 }) => ({
6060
- src: join44(localRoot, dirname10),
6061
- dst: join44(repoExtras, logical, dirname10)
6377
+ ({ localRoot, logical, dirname: dirname11 }) => ({
6378
+ src: join46(localRoot, dirname11),
6379
+ dst: join46(repoExtras, logical, dirname11)
6062
6380
  }),
6063
6381
  (dst) => backupRepoWrite(dst, ts, repo),
6064
6382
  // Push copy routing per extra type:
@@ -6070,7 +6388,7 @@ function remapExtrasPush(ts, opts = {}) {
6070
6388
  // (enforceAllowList / blockSetFor in commands.push.allowlist.ts)
6071
6389
  // remains the hard security boundary.
6072
6390
  // All others: copyExtrasFiltered with per-extra denylist.
6073
- (src, dst, dirname10) => dirname10 === ".planning" ? copyExtrasOverlayFiltered(src, dst, extrasDenySet(dirname10)) : copyExtrasFiltered(src, dst, extrasDenySet(dirname10))
6391
+ (src, dst, dirname11) => dirname11 === ".planning" ? copyExtrasOverlayFiltered(src, dst, extrasDenySet(dirname11)) : copyExtrasFiltered(src, dst, extrasDenySet(dirname11))
6074
6392
  );
6075
6393
  return { unmapped, skipped, pushed: done, wouldPush: would };
6076
6394
  }
@@ -6086,9 +6404,9 @@ function remapExtrasPull(ts, opts = {}) {
6086
6404
  const { unmapped, skipped, done, would } = runExtrasOp(
6087
6405
  v,
6088
6406
  dryRun,
6089
- ({ localRoot, logical, dirname: dirname10 }) => ({
6090
- src: join44(repo, "shared", "extras", logical, dirname10),
6091
- dst: join44(localRoot, dirname10)
6407
+ ({ localRoot, logical, dirname: dirname11 }) => ({
6408
+ src: join46(repo, "shared", "extras", logical, dirname11),
6409
+ dst: join46(localRoot, dirname11)
6092
6410
  }),
6093
6411
  // Snapshot the host-side dst BEFORE the copy step touches it. Anchor on
6094
6412
  // localRoot so the backup tree mirrors the project layout.
@@ -6106,12 +6424,12 @@ function remapExtrasPull(ts, opts = {}) {
6106
6424
  // copyExtrasFileSkipDiverged keeps a locally-edited file rather than
6107
6425
  // clobbering it, so the divergence WARN's keep-local promise holds for
6108
6426
  // every extra, not just `.planning`.
6109
- (src, dst, dirname10) => {
6110
- if (dirname10 === ".claude")
6111
- return copyExtrasFilteredPreserving(src, dst, extrasDenySet(dirname10));
6112
- if (dirname10 === ".planning") {
6427
+ (src, dst, dirname11) => {
6428
+ if (dirname11 === ".claude")
6429
+ return copyExtrasFilteredPreserving(src, dst, extrasDenySet(dirname11));
6430
+ if (dirname11 === ".planning") {
6113
6431
  const divergedSet = new Set(listDivergingModified(dst, src));
6114
- return copyExtrasOverlaySkipDiverged(src, dst, extrasDenySet(dirname10), divergedSet);
6432
+ return copyExtrasOverlaySkipDiverged(src, dst, extrasDenySet(dirname11), divergedSet);
6115
6433
  }
6116
6434
  return copyExtrasFileSkipDiverged(src, dst);
6117
6435
  }
@@ -6135,20 +6453,20 @@ function divergenceCheckExtras(ts, prePostHeads) {
6135
6453
  const v = loadValidatedExtras({});
6136
6454
  if (v === null) return 0;
6137
6455
  const counts = { unmapped: 0, skipped: 0 };
6138
- const backupRoot = join45(backupBase(), ts, "extras");
6456
+ const backupRoot = join47(backupBase(), ts, "extras");
6139
6457
  const repo = repoHome();
6140
6458
  let divergedCount = 0;
6141
- for (const { logical, localRoot, dirname: dirname10 } of eachExtrasTarget(v, counts)) {
6142
- const local = join45(localRoot, dirname10);
6143
- const repoEntry = join45(repo, "shared", "extras", logical, dirname10);
6144
- if (!existsSync38(local) || !existsSync38(repoEntry)) continue;
6459
+ for (const { logical, localRoot, dirname: dirname11 } of eachExtrasTarget(v, counts)) {
6460
+ const local = join47(localRoot, dirname11);
6461
+ const repoEntry = join47(repo, "shared", "extras", logical, dirname11);
6462
+ if (!existsSync39(local) || !existsSync39(repoEntry)) continue;
6145
6463
  const modified = listDivergingModified(local, repoEntry);
6146
6464
  if (modified.length === 0) continue;
6147
6465
  divergedCount += modified.length;
6148
- const projectBackupRoot = join45(backupRoot, encodePath(localRoot));
6466
+ const projectBackupRoot = join47(backupRoot, encodePath(localRoot));
6149
6467
  warn(
6150
6468
  divergenceWarnLine({
6151
- dirname: dirname10,
6469
+ dirname: dirname11,
6152
6470
  logical,
6153
6471
  isDir: statSync10(local).isDirectory(),
6154
6472
  count: modified.length,
@@ -6167,8 +6485,8 @@ function divergenceCheckExtras(ts, prePostHeads) {
6167
6485
 
6168
6486
  // src/skills-sync.ts
6169
6487
  init_config();
6170
- import { existsSync as existsSync39, lstatSync as lstatSync12, mkdirSync as mkdirSync9, readdirSync as readdirSync16, rmSync as rmSync14 } from "node:fs";
6171
- import { join as join46 } from "node:path";
6488
+ import { existsSync as existsSync40, lstatSync as lstatSync12, mkdirSync as mkdirSync11, readdirSync as readdirSync16, rmSync as rmSync15 } from "node:fs";
6489
+ import { join as join48 } from "node:path";
6172
6490
  init_utils_fs();
6173
6491
  function isGsdOwned(name) {
6174
6492
  return name.startsWith(GSD_PREFIX);
@@ -6188,30 +6506,30 @@ function copySkillsPull(src, dst) {
6188
6506
  copyExtrasFilteredPreservingBy(src, dst, isSkillExcluded);
6189
6507
  }
6190
6508
  function syncSkillsPull(ts) {
6191
- const sharedSkills = join46(repoHome(), "shared", "skills");
6192
- if (!existsSync39(sharedSkills)) return;
6193
- const localSkills = join46(claudeHome(), "skills");
6509
+ const sharedSkills = join48(repoHome(), "shared", "skills");
6510
+ if (!existsSync40(sharedSkills)) return;
6511
+ const localSkills = join48(claudeHome(), "skills");
6194
6512
  const dstStat = lstatSync12(localSkills, { throwIfNoEntry: false });
6195
6513
  if (dstStat?.isSymbolicLink() === true) {
6196
6514
  backupBeforeWrite(localSkills, ts);
6197
- rmSync14(localSkills, { recursive: true, force: true });
6198
- mkdirSync9(localSkills, { recursive: true });
6515
+ rmSync15(localSkills, { recursive: true, force: true });
6516
+ mkdirSync11(localSkills, { recursive: true });
6199
6517
  }
6200
6518
  copySkillsPull(sharedSkills, localSkills);
6201
6519
  }
6202
6520
  function syncSkillsPush() {
6203
- const localSkills = join46(claudeHome(), "skills");
6521
+ const localSkills = join48(claudeHome(), "skills");
6204
6522
  const stat = lstatSync12(localSkills, { throwIfNoEntry: false });
6205
6523
  if (stat === void 0) return;
6206
6524
  if (stat.isSymbolicLink()) return;
6207
- const sharedSkills = join46(repoHome(), "shared", "skills");
6525
+ const sharedSkills = join48(repoHome(), "shared", "skills");
6208
6526
  copySkillsPush(localSkills, sharedSkills);
6209
6527
  }
6210
6528
 
6211
6529
  // src/preview.ts
6212
6530
  init_config();
6213
- import { existsSync as existsSync40 } from "node:fs";
6214
- import { join as join47 } from "node:path";
6531
+ import { existsSync as existsSync41 } from "node:fs";
6532
+ import { join as join49 } from "node:path";
6215
6533
 
6216
6534
  // node_modules/diff/libesm/diff/base.js
6217
6535
  var Diff = class {
@@ -6497,7 +6815,7 @@ function diffJsonStrings(currentJsonText, newJsonText) {
6497
6815
  return lines.join("\n");
6498
6816
  }
6499
6817
  function readJsonOrNull(path) {
6500
- if (!existsSync40(path)) return null;
6818
+ if (!existsSync41(path)) return null;
6501
6819
  try {
6502
6820
  return readJson(path);
6503
6821
  } catch {
@@ -6511,12 +6829,12 @@ function previewSettings(basePath, hostPath, settingsPath) {
6511
6829
  }
6512
6830
  const notes = [];
6513
6831
  const hostOverrides = readJsonOrNull(hostPath);
6514
- if (hostOverrides === null && existsSync40(hostPath)) {
6832
+ if (hostOverrides === null && existsSync41(hostPath)) {
6515
6833
  notes.push(`malformed hosts/${HOST}.json; ignoring overrides`);
6516
6834
  }
6517
6835
  const merged = stripGsdHookEntries(deepMerge(base, hostOverrides ?? {}));
6518
6836
  const current = readJsonOrNull(settingsPath);
6519
- if (current === null && existsSync40(settingsPath)) {
6837
+ if (current === null && existsSync41(settingsPath)) {
6520
6838
  return { diff: "", notes: [...notes, "malformed; skipping diff"] };
6521
6839
  }
6522
6840
  const strippedCurrent = stripGsdHookEntries(current ?? {});
@@ -6558,9 +6876,9 @@ function computePreview(ts, map, verb = "pull") {
6558
6876
  onPreview: (e) => addItem(links, formatLinkRow(e))
6559
6877
  });
6560
6878
  const settingsResult = previewSettings(
6561
- join47(repo, "shared", "settings.base.json"),
6562
- join47(repo, "hosts", `${HOST}.json`),
6563
- join47(claude, "settings.json")
6879
+ join49(repo, "shared", "settings.base.json"),
6880
+ join49(repo, "hosts", `${HOST}.json`),
6881
+ join49(claude, "settings.json")
6564
6882
  );
6565
6883
  const settingsSection = buildSettingsSectionForPreview(settingsResult);
6566
6884
  const sessions = section("Sessions");
@@ -6575,7 +6893,7 @@ function computePreview(ts, map, verb = "pull") {
6575
6893
  const extras = section("Extras");
6576
6894
  let extrasSkipped = 0;
6577
6895
  let extrasUnmapped = 0;
6578
- if (existsSync40(join47(repo, "path-map.json")) && existsSync40(join47(repo, "shared", "extras"))) {
6896
+ if (existsSync41(join49(repo, "path-map.json")) && existsSync41(join49(repo, "shared", "extras"))) {
6579
6897
  const extrasResult = remapExtrasPull(ts, { dryRun: true });
6580
6898
  for (const entry of extrasResult.wouldPull) {
6581
6899
  addItem(extras, entry);
@@ -6600,9 +6918,9 @@ init_config();
6600
6918
  init_commands_pull_wedge();
6601
6919
  init_utils();
6602
6920
  init_utils_fs();
6603
- import { execFileSync as execFileSync19 } from "node:child_process";
6921
+ import { execFileSync as execFileSync20 } from "node:child_process";
6604
6922
  function gitCapture(args, cwd) {
6605
- return execFileSync19("git", args, {
6923
+ return execFileSync20("git", args, {
6606
6924
  cwd,
6607
6925
  stdio: ["ignore", "pipe", "pipe"],
6608
6926
  maxBuffer: 64 * 1024 * 1024
@@ -6746,17 +7064,9 @@ function buildWetPullSections(ts, map, prePostHeads) {
6746
7064
  const remapResult = withSpinner("Syncing sessions", () => remapPull(ts));
6747
7065
  const extrasResult = remapExtrasPull(ts, { prePostHeads });
6748
7066
  const localOnly = scanLocalOnly();
7067
+ const unmapped = remapResult.unmapped + extrasResult.unmapped;
6749
7068
  const summary = section(PULL_SUMMARY_HEADER);
6750
- addItem(
6751
- summary,
6752
- summaryRow(
6753
- "pull",
6754
- remapResult.unmapped + extrasResult.unmapped,
6755
- 0,
6756
- extrasResult.skipped,
6757
- localOnly
6758
- )
6759
- );
7069
+ addItem(summary, summaryRow("pull", unmapped, 0, extrasResult.skipped, localOnly));
6760
7070
  return {
6761
7071
  sections: [
6762
7072
  buildSettingsSection(label),
@@ -6764,7 +7074,10 @@ function buildWetPullSections(ts, map, prePostHeads) {
6764
7074
  buildExtrasSection(extrasResult.pulled, extrasResult.skipped),
6765
7075
  summary
6766
7076
  ],
6767
- localOnly
7077
+ localOnly,
7078
+ settingsLabel: label,
7079
+ unmapped,
7080
+ extrasSkipped: extrasResult.skipped
6768
7081
  };
6769
7082
  }
6770
7083
  function handleWedge(repo, forceRemote) {
@@ -6788,39 +7101,56 @@ function handleWedge(repo, forceRemote) {
6788
7101
  function runPullCore(opts = {}) {
6789
7102
  const dryRun = opts.dryRun === true;
6790
7103
  const forceRemote = opts.forceRemote === true;
7104
+ const compose = opts.compose === true;
6791
7105
  const repo = repoHome();
6792
7106
  const backup = backupBase();
6793
7107
  const ts = freshBackupTs(backup);
6794
7108
  handleWedge(repo, forceRemote);
6795
7109
  if (!dryRun) {
6796
- const backupRoot = join48(backup, ts);
7110
+ const backupRoot = join50(backup, ts);
6797
7111
  try {
6798
- mkdirSync10(backupRoot, { recursive: true });
7112
+ mkdirSync12(backupRoot, { recursive: true });
6799
7113
  } catch (err) {
6800
7114
  die(`could not create backup dir: ${err.message}`);
6801
7115
  }
6802
7116
  }
6803
- log(
6804
- dryRun ? `pulling on host=${HOST} (backup=${ts}; dry-run)` : `pull on host=${HOST} (backup=${ts})`
6805
- );
7117
+ if (!compose) {
7118
+ log(
7119
+ dryRun ? `pulling on host=${HOST} (backup=${ts}; dry-run)` : `pull on host=${HOST} (backup=${ts})`
7120
+ );
7121
+ }
6806
7122
  const prePostHeads = capturePrePostHeads(repo, () => {
6807
7123
  gitOrFatal(["pull", "--rebase", "--autostash"], "git pull --rebase", repo);
6808
7124
  });
6809
- const mapPath = join48(repo, "path-map.json");
6810
- const map = existsSync41(mapPath) ? readPathMap(mapPath) : { projects: {} };
7125
+ const mapPath = join50(repo, "path-map.json");
7126
+ const map = existsSync42(mapPath) ? readPathMap(mapPath) : { projects: {} };
6811
7127
  const divergedKeptLocal = divergenceCheckExtras(ts, dryRun ? prePostHeads : void 0);
6812
7128
  if (dryRun) {
6813
7129
  computePreview(ts, map, "pull");
6814
7130
  log("dry-run complete; no mutation");
6815
7131
  return { tag: "dry" };
6816
7132
  }
6817
- const { sections, localOnly } = buildWetPullSections(ts, map, prePostHeads);
6818
- return { tag: "wet", sections, localOnly, divergedKeptLocal };
7133
+ const { sections, localOnly, settingsLabel, unmapped, extrasSkipped } = buildWetPullSections(
7134
+ ts,
7135
+ map,
7136
+ prePostHeads
7137
+ );
7138
+ const incomingChanges = prePostHeads === void 0 ? true : prePostHeads.pre !== prePostHeads.post;
7139
+ return {
7140
+ tag: "wet",
7141
+ sections,
7142
+ localOnly,
7143
+ divergedKeptLocal,
7144
+ incomingChanges,
7145
+ settingsLabel,
7146
+ unmapped,
7147
+ extrasSkipped
7148
+ };
6819
7149
  }
6820
7150
  function cmdPull(opts = {}) {
6821
7151
  const repo = repoHome();
6822
- if (!existsSync41(repo)) die(`repo not cloned at ${repo}`);
6823
- if (!existsSync41(join48(repo, "shared", "settings.base.json"))) {
7152
+ if (!existsSync42(repo)) die(`repo not cloned at ${repo}`);
7153
+ if (!existsSync42(join50(repo, "shared", "settings.base.json"))) {
6824
7154
  die("repo not initialized; run 'nomad init' to scaffold");
6825
7155
  }
6826
7156
  const handle = acquireLock("pull");
@@ -6842,13 +7172,13 @@ function cmdPull(opts = {}) {
6842
7172
 
6843
7173
  // src/commands.push.ts
6844
7174
  init_config();
6845
- import { existsSync as existsSync45 } from "node:fs";
6846
- import { join as join53 } from "node:path";
7175
+ import { existsSync as existsSync46 } from "node:fs";
7176
+ import { join as join55 } from "node:path";
6847
7177
 
6848
7178
  // src/commands.push.selection.ts
6849
7179
  init_config();
6850
- import { existsSync as existsSync42, statSync as statSync11 } from "node:fs";
6851
- import { join as join49 } from "node:path";
7180
+ import { existsSync as existsSync43, statSync as statSync11 } from "node:fs";
7181
+ import { join as join51 } from "node:path";
6852
7182
  init_utils_json();
6853
7183
  function buildCurrentMap(map) {
6854
7184
  const current = {};
@@ -6857,8 +7187,8 @@ function buildCurrentMap(map) {
6857
7187
  for (const [, hostMap] of Object.entries(map.projects)) {
6858
7188
  const localPath = hostMap[HOST];
6859
7189
  if (!localPath) continue;
6860
- const localDir = join49(claude, "projects", encodePath(localPath));
6861
- if (!existsSync42(localDir)) continue;
7190
+ const localDir = join51(claude, "projects", encodePath(localPath));
7191
+ if (!existsSync43(localDir)) continue;
6862
7192
  for (const f of enumerateSourceFiles(localDir)) {
6863
7193
  const st = statSync11(f);
6864
7194
  current[f] = { size: st.size, mtime: st.mtimeMs };
@@ -6889,7 +7219,7 @@ function computePushSelection(map, old, scannerVersion, configHash, fullScan) {
6889
7219
  };
6890
7220
  }
6891
7221
  function loadSelectionForPush(mapPath, old, scannerVersion, configHash, fullScan) {
6892
- const map = existsSync42(mapPath) ? readPathMap(mapPath) : null;
7222
+ const map = existsSync43(mapPath) ? readPathMap(mapPath) : null;
6893
7223
  const { selection, newManifest } = computePushSelection(
6894
7224
  map,
6895
7225
  old,
@@ -6991,14 +7321,14 @@ function enforceAllowList(statusPorcelain, map) {
6991
7321
 
6992
7322
  // src/commands.push.settings.ts
6993
7323
  init_config();
6994
- import { existsSync as existsSync43 } from "node:fs";
6995
- import { join as join50 } from "node:path";
7324
+ import { existsSync as existsSync44 } from "node:fs";
7325
+ import { join as join52 } from "node:path";
6996
7326
  init_utils();
6997
7327
  init_utils_fs();
6998
7328
  init_utils_json();
6999
7329
  function stripGsdHooksFromBase(repo, backup) {
7000
- const basePath = join50(repo, "shared", "settings.base.json");
7001
- if (!existsSync43(basePath)) return;
7330
+ const basePath = join52(repo, "shared", "settings.base.json");
7331
+ if (!existsSync44(basePath)) return;
7002
7332
  let base;
7003
7333
  try {
7004
7334
  base = readJson(basePath);
@@ -7012,14 +7342,14 @@ function stripGsdHooksFromBase(repo, backup) {
7012
7342
  writeJsonAtomic(basePath, stripped);
7013
7343
  }
7014
7344
  function reportSettingsAheadDrift(repo) {
7015
- const basePath = join50(repo, "shared", "settings.base.json");
7016
- if (!existsSync43(basePath)) return;
7017
- const settingsPath = join50(claudeHome(), "settings.json");
7018
- if (!existsSync43(settingsPath)) return;
7345
+ const basePath = join52(repo, "shared", "settings.base.json");
7346
+ if (!existsSync44(basePath)) return;
7347
+ const settingsPath = join52(claudeHome(), "settings.json");
7348
+ if (!existsSync44(settingsPath)) return;
7019
7349
  try {
7020
7350
  const base = readJson(basePath);
7021
- const hostPath = join50(repo, "hosts", `${HOST}.json`);
7022
- const overrides = existsSync43(hostPath) ? readJson(hostPath) : {};
7351
+ const hostPath = join52(repo, "hosts", `${HOST}.json`);
7352
+ const overrides = existsSync44(hostPath) ? readJson(hostPath) : {};
7023
7353
  const merged = deepMerge(base, overrides);
7024
7354
  const settings = readJson(settingsPath);
7025
7355
  const { ahead } = classifySettingsDrift(merged, settings);
@@ -7036,9 +7366,9 @@ function reportSettingsAheadDrift(repo) {
7036
7366
  // src/commands.push.guards.ts
7037
7367
  init_push_checks();
7038
7368
  init_utils();
7039
- import { join as join51, relative as relative7 } from "node:path";
7369
+ import { join as join53, relative as relative7 } from "node:path";
7040
7370
  function guardGitlinks(repo) {
7041
- const gitlinks = findGitlinks(join51(repo, "shared"));
7371
+ const gitlinks = findGitlinks(join53(repo, "shared"));
7042
7372
  if (gitlinks.length === 0) return;
7043
7373
  for (const p of gitlinks) {
7044
7374
  const rel = relative7(repo, p).replaceAll("\\", "/");
@@ -7070,7 +7400,7 @@ init_config();
7070
7400
 
7071
7401
  // src/push-global-config.ts
7072
7402
  init_config();
7073
- import { execFileSync as execFileSync20 } from "node:child_process";
7403
+ import { execFileSync as execFileSync21 } from "node:child_process";
7074
7404
  var STATUS_LABELS = {
7075
7405
  A: "add",
7076
7406
  M: "modify",
@@ -7110,7 +7440,7 @@ function isInScope(filePath, exactPrefixes, dirPrefixes) {
7110
7440
  }
7111
7441
  function collectGlobalConfigChanges(repoHome2, hostname2, opts) {
7112
7442
  const args = opts.staged ? ["diff", "--cached", "--name-status", "-z"] : ["diff", "HEAD", "--name-status", "-z"];
7113
- const raw = execFileSync20("git", args, {
7443
+ const raw = execFileSync21("git", args, {
7114
7444
  cwd: repoHome2,
7115
7445
  stdio: ["ignore", "pipe", "pipe"]
7116
7446
  }).toString();
@@ -7147,10 +7477,10 @@ init_push_leak_verdict();
7147
7477
  init_color();
7148
7478
  init_config();
7149
7479
  init_config_sharedDirs_guard();
7150
- import { randomBytes as randomBytes2 } from "node:crypto";
7151
- import { copyFileSync, existsSync as existsSync44, mkdirSync as mkdirSync11, readdirSync as readdirSync17, rmSync as rmSync15 } from "node:fs";
7152
- import { homedir as homedir5 } from "node:os";
7153
- import { join as join52, relative as relative8, sep as sep9 } from "node:path";
7480
+ import { randomBytes as randomBytes3 } from "node:crypto";
7481
+ import { copyFileSync, existsSync as existsSync45, mkdirSync as mkdirSync13, readdirSync as readdirSync17, rmSync as rmSync16 } from "node:fs";
7482
+ import { homedir as homedir6 } from "node:os";
7483
+ import { join as join54, relative as relative8, sep as sep9 } from "node:path";
7154
7484
  init_push_leak_verdict();
7155
7485
  init_push_gitleaks();
7156
7486
  init_utils_fs();
@@ -7162,7 +7492,7 @@ function stageSessionDir(localDir, dstDir, changed) {
7162
7492
  const matching = [...changed].filter((p) => p.startsWith(prefix));
7163
7493
  if (matching.length === 0) return false;
7164
7494
  for (const src of matching) {
7165
- copyFileAtomic(src, join52(dstDir, relative8(localDir, src)));
7495
+ copyFileAtomic(src, join54(dstDir, relative8(localDir, src)));
7166
7496
  }
7167
7497
  return true;
7168
7498
  }
@@ -7178,14 +7508,14 @@ function stageSessions(tmpRoot, map, changed) {
7178
7508
  if (!p || p === "TBD") continue;
7179
7509
  reverse.set(encodePath(p), logical);
7180
7510
  }
7181
- const localProjects = join52(claudeHome(), "projects");
7182
- if (!existsSync44(localProjects)) return 0;
7511
+ const localProjects = join54(claudeHome(), "projects");
7512
+ if (!existsSync45(localProjects)) return 0;
7183
7513
  let staged = 0;
7184
7514
  for (const dir of readdirSync17(localProjects)) {
7185
7515
  const logical = reverse.get(dir);
7186
7516
  if (!logical) continue;
7187
- const localDir = join52(localProjects, dir);
7188
- const dstDir = join52(tmpRoot, "shared", "projects", logical);
7517
+ const localDir = join54(localProjects, dir);
7518
+ const dstDir = join54(tmpRoot, "shared", "projects", logical);
7189
7519
  if (stageSessionDir(localDir, dstDir, changed)) staged++;
7190
7520
  }
7191
7521
  return staged;
@@ -7199,11 +7529,11 @@ function stageExtras(tmpRoot, map) {
7199
7529
  assertSafeLogical(logical);
7200
7530
  const localRoot = map.projects[logical]?.[HOST];
7201
7531
  if (!localRoot || localRoot === "TBD") continue;
7202
- for (const dirname10 of dirnames) {
7203
- if (!whitelist.includes(dirname10)) continue;
7204
- const src = join52(localRoot, dirname10);
7205
- if (!existsSync44(src)) continue;
7206
- const dst = join52(tmpRoot, "shared", "extras", logical, dirname10);
7532
+ for (const dirname11 of dirnames) {
7533
+ if (!whitelist.includes(dirname11)) continue;
7534
+ const src = join54(localRoot, dirname11);
7535
+ if (!existsSync45(src)) continue;
7536
+ const dst = join54(tmpRoot, "shared", "extras", logical, dirname11);
7207
7537
  copyExtras(src, dst);
7208
7538
  staged++;
7209
7539
  }
@@ -7211,19 +7541,19 @@ function stageExtras(tmpRoot, map) {
7211
7541
  return staged;
7212
7542
  }
7213
7543
  function previewPushLeaks(map, opts = {}) {
7214
- const cacheDir = join52(homedir5(), ".cache", "claude-nomad");
7215
- mkdirSync11(cacheDir, { recursive: true });
7216
- const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes2(4).toString("hex")}`;
7217
- const tmpRoot = join52(cacheDir, `push-preview-tree-${stamp}`);
7544
+ const cacheDir = join54(homedir6(), ".cache", "claude-nomad");
7545
+ mkdirSync13(cacheDir, { recursive: true });
7546
+ const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes3(4).toString("hex")}`;
7547
+ const tmpRoot = join54(cacheDir, `push-preview-tree-${stamp}`);
7218
7548
  try {
7219
7549
  const sessionCount = stageSessions(tmpRoot, map, opts.selection?.changed);
7220
7550
  const extrasCount = stageExtras(tmpRoot, map);
7221
7551
  if (sessionCount + extrasCount === 0) {
7222
7552
  return { leak: false, verdictRow: NOTHING_TO_SCAN_ROW, recovery: null, findings: [] };
7223
7553
  }
7224
- const ignoreFile = join52(repoHome(), ".gitleaksignore");
7225
- if (existsSync44(ignoreFile)) {
7226
- copyFileSync(ignoreFile, join52(tmpRoot, ".gitleaksignore"));
7554
+ const ignoreFile = join54(repoHome(), ".gitleaksignore");
7555
+ if (existsSync45(ignoreFile)) {
7556
+ copyFileSync(ignoreFile, join54(tmpRoot, ".gitleaksignore"));
7227
7557
  }
7228
7558
  let findings;
7229
7559
  try {
@@ -7236,13 +7566,13 @@ function previewPushLeaks(map, opts = {}) {
7236
7566
  }
7237
7567
  return verdictFromFindings(findings);
7238
7568
  } finally {
7239
- rmSync15(tmpRoot, { recursive: true, force: true });
7569
+ rmSync16(tmpRoot, { recursive: true, force: true });
7240
7570
  }
7241
7571
  }
7242
7572
 
7243
7573
  // src/commands.push.steps.ts
7244
7574
  init_utils();
7245
- async function commitAndPush(st, ts, map, resolution, repo, newManifest) {
7575
+ async function commitAndPush(st, ts, map, resolution, repo, newManifest, render) {
7246
7576
  gitOrFatal(["add", "-A"], "git add", repo);
7247
7577
  const staged = parsePorcelainZ2(gitStatusPorcelainZ(repo));
7248
7578
  const toDrop = staged.filter((p) => isGsdDropped(p));
@@ -7250,13 +7580,18 @@ async function commitAndPush(st, ts, map, resolution, repo, newManifest) {
7250
7580
  gitOrFatal(["restore", "--staged", "--", ...toDrop], "git restore --staged", repo);
7251
7581
  }
7252
7582
  if (staged.length === toDrop.length) {
7253
- log("nothing to commit");
7254
- renderNoScanTree(st);
7255
- return "nothing";
7583
+ const sections2 = buildNoScanSections(st);
7584
+ if (render) {
7585
+ log("nothing to commit");
7586
+ renderTree(sections2);
7587
+ }
7588
+ return { outcome: "nothing", sections: sections2 };
7256
7589
  }
7257
7590
  st.globalConfig = collectGlobalConfigChanges(repo, HOST, { staged: true });
7258
7591
  let verdict = withSpinner("Scanning for secrets", () => scanPushVerdict(repo));
7592
+ const hadLeak = verdict.leak;
7259
7593
  if (verdict.leak) {
7594
+ if (!render) log("push (leak recovery)");
7260
7595
  renderPushTree(st, verdict);
7261
7596
  verdict = await resolveLeakFindings(verdict, ts, map, resolution);
7262
7597
  }
@@ -7267,8 +7602,13 @@ async function commitAndPush(st, ts, map, resolution, repo, newManifest) {
7267
7602
  } catch (err) {
7268
7603
  warn(`could not write push manifest (next push will full-rescan): ${String(err)}`);
7269
7604
  }
7270
- renderPushTree(st, verdict);
7271
- return "pushed";
7605
+ if (hadLeak) {
7606
+ renderPushTree(st, verdict);
7607
+ return { outcome: "pushed", sections: [] };
7608
+ }
7609
+ const sections = buildPushTreeSections(st, verdict);
7610
+ if (render) renderTree(sections);
7611
+ return { outcome: "pushed", sections };
7272
7612
  }
7273
7613
  function runDryRunPreview(st, map, repo, selection) {
7274
7614
  st.globalConfig = collectGlobalConfigChanges(repo, HOST, { staged: false });
@@ -7285,20 +7625,61 @@ function runDryRunPreview(st, map, repo, selection) {
7285
7625
  init_push_checks();
7286
7626
  init_utils();
7287
7627
  init_utils_fs();
7628
+ function aheadOfUpstream(repo) {
7629
+ try {
7630
+ const raw = gitCaptureRaw(["rev-list", "--count", "@{u}..HEAD"], repo);
7631
+ return Number.parseInt(raw.trim(), 10) > 0;
7632
+ } catch {
7633
+ return false;
7634
+ }
7635
+ }
7636
+ function emptyStatusResult(st, compose, repo) {
7637
+ if (compose) {
7638
+ return {
7639
+ tag: "nothing",
7640
+ sections: buildNoScanSections(st),
7641
+ aheadOfOrigin: aheadOfUpstream(repo),
7642
+ globalConfigCount: st.globalConfig.length,
7643
+ collisions: st.remap.collisions
7644
+ };
7645
+ }
7646
+ log("nothing to commit");
7647
+ renderNoScanTree(st);
7648
+ return { tag: "nothing" };
7649
+ }
7650
+ function toPushCoreResult(outcome, sections, compose, repo, st) {
7651
+ if (!compose) return { tag: outcome };
7652
+ const globalConfigCount = st.globalConfig.length;
7653
+ const collisions = st.remap.collisions;
7654
+ if (outcome === "nothing") {
7655
+ return {
7656
+ tag: "nothing",
7657
+ sections,
7658
+ aheadOfOrigin: aheadOfUpstream(repo),
7659
+ globalConfigCount,
7660
+ collisions
7661
+ };
7662
+ }
7663
+ return { tag: "pushed", sections, globalConfigCount, collisions };
7664
+ }
7288
7665
  async function runPushCore(opts = {}) {
7289
7666
  const dryRun = opts.dryRun === true;
7290
7667
  const redactAll = opts.redactAll === true;
7291
7668
  const allowAll = opts.allowAll === true;
7292
7669
  const allowRule = opts.allowRule;
7293
7670
  const fullScan = opts.fullScan === true;
7671
+ const compose = opts.compose === true;
7672
+ const render = !compose;
7294
7673
  const repo = repoHome();
7295
7674
  const backup = backupBase();
7296
- console.log(dryRun ? `push on host=${HOST} (dry-run)` : `push on host=${HOST}`);
7675
+ if (!compose) {
7676
+ console.log(dryRun ? `push on host=${HOST} (dry-run)` : `push on host=${HOST}`);
7677
+ }
7297
7678
  reportSettingsAheadDrift(repo);
7298
7679
  const scannerVersion = probeGitleaks();
7299
7680
  const configHash = computeConfigHash();
7300
7681
  const old = readManifest(manifestPath());
7301
- const mapPath = join53(repo, "path-map.json");
7682
+ const mapPath = join55(repo, "path-map.json");
7302
7683
  const { map, selection, newManifest } = loadSelectionForPush(
7303
7684
  mapPath,
7304
7685
  old,
@@ -7318,11 +7699,7 @@ async function runPushCore(opts = {}) {
7318
7699
  const st = { dryRun, remap, extras, globalConfig: [] };
7319
7700
  guardGitlinks(repo);
7320
7701
  const status = gitStatusPorcelainZ(repo, { untrackedAll: true });
7321
- if (!dryRun && !status) {
7322
- log("nothing to commit");
7323
- renderNoScanTree(st);
7324
- return { tag: "nothing" };
7325
- }
7702
+ if (!dryRun && !status) return emptyStatusResult(st, compose, repo);
7326
7703
  if (map === null) {
7327
7704
  if (dryRun) {
7328
7705
  runDryRunPreview(st, null, repo, selection);
@@ -7335,15 +7712,16 @@ async function runPushCore(opts = {}) {
7335
7712
  runDryRunPreview(st, map, repo, selection);
7336
7713
  return { tag: "dry" };
7337
7714
  }
7338
- const outcome = await commitAndPush(
7715
+ const { outcome, sections } = await commitAndPush(
7339
7716
  st,
7340
7717
  ts,
7341
7718
  map,
7342
7719
  { redactAll, allowAll, allowRule },
7343
7720
  repo,
7344
- newManifest
7721
+ newManifest,
7722
+ render
7345
7723
  );
7346
- return outcome === "nothing" ? { tag: "nothing" } : { tag: "pushed" };
7724
+ return toPushCoreResult(outcome, sections, compose, repo, st);
7347
7725
  }
7348
7726
  async function cmdPush(opts = {}) {
7349
7727
  const dryRun = opts.dryRun === true;
@@ -7352,7 +7730,7 @@ async function cmdPush(opts = {}) {
7352
7730
  const allowRule = opts.allowRule;
7353
7731
  guardResolutionModeConflicts(dryRun, redactAll, allowAll, allowRule);
7354
7732
  const repo = repoHome();
7355
- if (!existsSync45(repo)) die(`repo not cloned at ${repo}`);
7733
+ if (!existsSync46(repo)) die(`repo not cloned at ${repo}`);
7356
7734
  const handle = acquireLock("push");
7357
7735
  if (handle === null) process.exit(0);
7358
7736
  try {
@@ -7370,36 +7748,59 @@ async function cmdPush(opts = {}) {
7370
7748
  }
7371
7749
 
7372
7750
  // src/commands.sync.ts
7373
- import { existsSync as existsSync46 } from "node:fs";
7374
- import { join as join54 } from "node:path";
7751
+ import { existsSync as existsSync47 } from "node:fs";
7752
+ import { join as join56 } from "node:path";
7375
7753
  init_config();
7754
+ init_color();
7376
7755
  init_utils();
7377
7756
  init_utils_fs();
7378
7757
  init_utils_json();
7379
- function pullHasNoSyncedItems(sections) {
7380
- return sections.every(
7381
- (s) => s.header === "Settings" || s.header === PULL_SUMMARY_HEADER || s.items.length === 0
7382
- );
7383
- }
7384
7758
  function isNoopSync(pull, pushOutcome) {
7385
7759
  if (!pushOutcome.ok) return false;
7386
7760
  if (pull.localOnly !== 0 || pull.divergedKeptLocal !== 0) return false;
7387
7761
  if (pushOutcome.result.tag !== "nothing") return false;
7388
- return pullHasNoSyncedItems(pull.sections);
7762
+ if (pushOutcome.result.aheadOfOrigin === true) return false;
7763
+ return !pull.incomingChanges;
7389
7764
  }
7390
- function pullPhrase(pull) {
7391
- const summary = pull.sections.find((s) => s.header === PULL_SUMMARY_HEADER);
7392
- return summary?.items[0] ?? "applied";
7765
+ function buildPullSummaryRow(pull) {
7766
+ const applied = pull.incomingChanges ? "upstream changes applied" : "no upstream changes";
7767
+ return `pull: ${applied}; settings regenerated (base + ${pull.settingsLabel})`;
7393
7768
  }
7394
- function reconciledNotes(pull) {
7395
- const notes = [];
7769
+ function countNoun(n, singular, plural) {
7770
+ return `${n} ${n === 1 ? singular : plural}`;
7771
+ }
7772
+ function pushedParenParts(pull, globalConfigCount) {
7773
+ const parts = [];
7774
+ if (pull.localOnly > 0) {
7775
+ parts.push(countNoun(pull.localOnly, "local-only session", "local-only sessions"));
7776
+ }
7396
7777
  if (pull.divergedKeptLocal > 0) {
7397
- notes.push(`${pull.divergedKeptLocal} diverged files kept local and pushed`);
7778
+ parts.push(countNoun(pull.divergedKeptLocal, "diverged extras file", "diverged extras files"));
7398
7779
  }
7399
- if (pull.localOnly > 0) {
7400
- notes.push(`${pull.localOnly} local-only sessions pushed`);
7780
+ if (globalConfigCount > 0) {
7781
+ parts.push(countNoun(globalConfigCount, "config file", "config files"));
7401
7782
  }
7402
- return notes;
7783
+ return parts;
7784
+ }
7785
+ function buildPushSummaryRow(pull, result) {
7786
+ if (result.tag !== "pushed") return "push: nothing to push";
7787
+ const parts = pushedParenParts(pull, result.globalConfigCount ?? 0);
7788
+ return parts.length > 0 ? `push: pushed (${parts.join(", ")})` : "push: pushed";
7789
+ }
7790
+ function buildSkipAndCollisionRows(pull, result) {
7791
+ const rows = [];
7792
+ if (pull.unmapped > 0) {
7793
+ rows.push(`${dim(infoGlyph)} ${pull.unmapped} not in path-map (run nomad doctor to list)`);
7794
+ }
7795
+ if (pull.extrasSkipped > 0) {
7796
+ rows.push(`${dim(infoGlyph)} ${pull.extrasSkipped} extras skipped`);
7797
+ }
7798
+ const collisions = result.tag === "dry" ? void 0 : result.collisions;
7799
+ if (collisions !== void 0 && collisions > 0) {
7800
+ const phrase = countNoun(collisions, "collision", "collisions");
7801
+ rows.push(`${yellow(warnGlyph)} ${phrase} (run nomad doctor to list)`);
7802
+ }
7803
+ return rows;
7403
7804
  }
7404
7805
  function buildSyncSummarySection(pull, pushOutcome) {
7405
7806
  const s = section("Sync summary");
@@ -7407,22 +7808,59 @@ function buildSyncSummarySection(pull, pushOutcome) {
7407
7808
  addItem(s, `pull: applied, push: failed (${pushOutcome.message})`);
7408
7809
  return s;
7409
7810
  }
7410
- addItem(s, `pull: ${pullPhrase(pull)}`);
7411
- addItem(s, `push: ${pushOutcome.result.tag === "pushed" ? "pushed" : "nothing to push"}`);
7412
- for (const note of reconciledNotes(pull)) addItem(s, note);
7811
+ const { result } = pushOutcome;
7812
+ addItem(s, buildPullSummaryRow(pull));
7813
+ addItem(s, buildPushSummaryRow(pull, result));
7814
+ if (result.tag === "nothing" && result.aheadOfOrigin === true) {
7815
+ addItem(s, "sync repo has unpushed commits");
7816
+ }
7817
+ for (const row2 of buildSkipAndCollisionRows(pull, result)) addItem(s, row2);
7413
7818
  return s;
7414
7819
  }
7415
- function renderWetSync(pull, pushOutcome) {
7820
+ var SYNC_SECTION_ORDER = ["Settings", "Global config", "Sessions", "Extras", "Leak scan"];
7821
+ function isDroppedSummaryHeader(header) {
7822
+ return header === PULL_SUMMARY_HEADER || header === "Push summary";
7823
+ }
7824
+ function mergeSyncSections(pullSections, pushSections) {
7825
+ const byHeader = new Map(
7826
+ SYNC_SECTION_ORDER.map((header) => [header, section(header)])
7827
+ );
7828
+ for (const s of [...pullSections, ...pushSections]) {
7829
+ if (isDroppedSummaryHeader(s.header)) continue;
7830
+ let target = byHeader.get(s.header);
7831
+ if (target === void 0) {
7832
+ target = section(s.header);
7833
+ byHeader.set(s.header, target);
7834
+ }
7835
+ for (const item2 of s.items) {
7836
+ if (!target.items.includes(item2)) addItem(target, item2);
7837
+ }
7838
+ }
7839
+ return [...byHeader.values()].filter((s) => s.items.length > 0);
7840
+ }
7841
+ function pushSectionsOf(pushOutcome) {
7842
+ if (!pushOutcome.ok) return [];
7843
+ const result = pushOutcome.result;
7844
+ if (result.tag === "dry") return [];
7845
+ return result.sections ?? [];
7846
+ }
7847
+ function renderWetSync(pull, pushOutcome, verbose) {
7416
7848
  if (isNoopSync(pull, pushOutcome)) {
7417
7849
  ok("already in sync");
7418
7850
  return;
7419
7851
  }
7420
- renderTree(pull.sections);
7421
- renderTree([buildSyncSummarySection(pull, pushOutcome)]);
7852
+ log(`sync on host=${HOST}`);
7853
+ const summary = buildSyncSummarySection(pull, pushOutcome);
7854
+ if (!verbose) {
7855
+ renderTree([summary]);
7856
+ return;
7857
+ }
7858
+ const merged = mergeSyncSections(pull.sections, pushSectionsOf(pushOutcome));
7859
+ renderTree([...merged, summary]);
7422
7860
  }
7423
7861
  async function runSyncPushHalf() {
7424
7862
  try {
7425
- const result = await runPushCore();
7863
+ const result = await runPushCore({ compose: true });
7426
7864
  return { ok: true, result };
7427
7865
  } catch (err) {
7428
7866
  if (err instanceof NomadFatal) {
@@ -7432,25 +7870,26 @@ async function runSyncPushHalf() {
7432
7870
  throw err;
7433
7871
  }
7434
7872
  }
7435
- async function runSyncWet() {
7436
- const pull = runPullCore();
7873
+ async function runSyncWet(verbose) {
7874
+ const pull = runPullCore({ compose: true });
7437
7875
  const pushOutcome = await runSyncPushHalf();
7438
- renderWetSync(pull, pushOutcome);
7876
+ renderWetSync(pull, pushOutcome, verbose);
7439
7877
  }
7440
7878
  async function runSyncDryRun(repo, backup) {
7441
7879
  const ts = freshBackupTs(backup);
7442
- const mapPath = join54(repo, "path-map.json");
7443
- const map = existsSync46(mapPath) ? readPathMap(mapPath) : { projects: {} };
7880
+ const mapPath = join56(repo, "path-map.json");
7881
+ const map = existsSync47(mapPath) ? readPathMap(mapPath) : { projects: {} };
7444
7882
  computePreview(ts, map, "pull");
7445
7883
  log("push preview below is computed against pre-pull state (a real sync pushes after pull)");
7446
7884
  await runPushCore({ dryRun: true });
7447
7885
  }
7448
7886
  async function cmdSync(opts = {}) {
7449
7887
  const dryRun = opts.dryRun === true;
7888
+ const verbose = opts.verbose === true;
7450
7889
  const repo = repoHome();
7451
7890
  const backup = backupBase();
7452
- if (!existsSync46(repo)) die(`repo not cloned at ${repo}`);
7453
- if (!existsSync46(join54(repo, "shared", "settings.base.json"))) {
7891
+ if (!existsSync47(repo)) die(`repo not cloned at ${repo}`);
7892
+ if (!existsSync47(join56(repo, "shared", "settings.base.json"))) {
7454
7893
  die("repo not initialized; run 'nomad init' to scaffold");
7455
7894
  }
7456
7895
  const handle = acquireLock("sync");
@@ -7459,7 +7898,7 @@ async function cmdSync(opts = {}) {
7459
7898
  if (dryRun) {
7460
7899
  await runSyncDryRun(repo, backup);
7461
7900
  } else {
7462
- await runSyncWet();
7901
+ await runSyncWet(verbose);
7463
7902
  }
7464
7903
  } catch (err) {
7465
7904
  if (err instanceof NomadFatal) {
@@ -7474,9 +7913,9 @@ async function cmdSync(opts = {}) {
7474
7913
  }
7475
7914
 
7476
7915
  // src/commands.update.ts
7477
- import { execFileSync as execFileSync21 } from "node:child_process";
7916
+ import { execFileSync as execFileSync22 } from "node:child_process";
7478
7917
  init_utils();
7479
- function readInstalledVersion(run = execFileSync21) {
7918
+ function readInstalledVersion(run = execFileSync22) {
7480
7919
  const isWin = process.platform === "win32";
7481
7920
  try {
7482
7921
  return run(isWin ? "nomad.cmd" : "nomad", ["--version"], {
@@ -7487,7 +7926,7 @@ function readInstalledVersion(run = execFileSync21) {
7487
7926
  return null;
7488
7927
  }
7489
7928
  }
7490
- function cmdUpdate(currentVersion, run = execFileSync21) {
7929
+ function cmdUpdate(currentVersion, run = execFileSync22) {
7491
7930
  console.log(`Updating claude-nomad v${currentVersion}...`);
7492
7931
  const isWin = process.platform === "win32";
7493
7932
  try {
@@ -7523,18 +7962,18 @@ init_config();
7523
7962
 
7524
7963
  // src/diff.ts
7525
7964
  init_config();
7526
- import { existsSync as existsSync47 } from "node:fs";
7527
- import { join as join55 } from "node:path";
7965
+ import { existsSync as existsSync48 } from "node:fs";
7966
+ import { join as join57 } from "node:path";
7528
7967
  init_utils();
7529
7968
  init_utils_fs();
7530
7969
  init_utils_json();
7531
7970
  function cmdDiff() {
7532
7971
  try {
7533
7972
  const repo = repoHome();
7534
- if (!existsSync47(repo)) die(`repo not cloned at ${repo}`);
7973
+ if (!existsSync48(repo)) die(`repo not cloned at ${repo}`);
7535
7974
  const ts = freshBackupTs(backupBase());
7536
- const mapPath = join55(repo, "path-map.json");
7537
- const map = existsSync47(mapPath) ? readPathMap(mapPath) : { projects: {} };
7975
+ const mapPath = join57(repo, "path-map.json");
7976
+ const map = existsSync48(mapPath) ? readPathMap(mapPath) : { projects: {} };
7538
7977
  divergenceCheckExtras(ts);
7539
7978
  computePreview(ts, map, "diff");
7540
7979
  } catch (err) {
@@ -7549,19 +7988,19 @@ function cmdDiff() {
7549
7988
 
7550
7989
  // src/init.ts
7551
7990
  init_config();
7552
- import { existsSync as existsSync49, mkdirSync as mkdirSync12, writeFileSync as writeFileSync6 } from "node:fs";
7553
- import { join as join57 } from "node:path";
7991
+ import { existsSync as existsSync50, mkdirSync as mkdirSync14, writeFileSync as writeFileSync8 } from "node:fs";
7992
+ import { join as join59 } from "node:path";
7554
7993
 
7555
7994
  // src/init.gh-onboard.ts
7556
7995
  init_config();
7557
- import { execFileSync as execFileSync22 } from "node:child_process";
7996
+ import { execFileSync as execFileSync23 } from "node:child_process";
7558
7997
  init_utils();
7559
7998
  var DEFAULT_REPO_NAME = "claude-nomad-config";
7560
7999
  function isValidRepoName(name) {
7561
8000
  return /^[A-Za-z0-9._-]{1,100}$/.test(name);
7562
8001
  }
7563
8002
  var GH_NETWORK_TIMEOUT_MS = 3e4;
7564
- function ensureOriginRepo(repoName, run = execFileSync22) {
8003
+ function ensureOriginRepo(repoName, run = execFileSync23) {
7565
8004
  if (!isValidRepoName(repoName)) {
7566
8005
  die(
7567
8006
  `invalid repo name: ${JSON.stringify(repoName)}. Use only letters, digits, hyphens, underscores, and dots (1-100 chars).`
@@ -7632,33 +8071,33 @@ init_config();
7632
8071
  init_utils();
7633
8072
  init_utils_fs();
7634
8073
  init_utils_json();
7635
- import { copyFileSync as copyFileSync2, cpSync as cpSync8, existsSync as existsSync48, rmSync as rmSync16, statSync as statSync12 } from "node:fs";
7636
- import { join as join56 } from "node:path";
8074
+ import { copyFileSync as copyFileSync2, cpSync as cpSync9, existsSync as existsSync49, rmSync as rmSync17, statSync as statSync12 } from "node:fs";
8075
+ import { join as join58 } from "node:path";
7637
8076
  function snapshotIntoShared(map) {
7638
8077
  const repo = repoHome();
7639
8078
  const claude = claudeHome();
7640
8079
  for (const name of allSharedLinks(map)) {
7641
- const src = join56(claude, name);
7642
- if (!existsSync48(src)) continue;
7643
- const dst = join56(repo, "shared", name);
8080
+ const src = join58(claude, name);
8081
+ if (!existsSync49(src)) continue;
8082
+ const dst = join58(repo, "shared", name);
7644
8083
  if (statSync12(src).isDirectory()) {
7645
- const gk = join56(dst, ".gitkeep");
7646
- if (existsSync48(gk)) rmSync16(gk);
7647
- cpSync8(src, dst, { recursive: true, force: false, errorOnExist: true });
8084
+ const gk = join58(dst, ".gitkeep");
8085
+ if (existsSync49(gk)) rmSync17(gk);
8086
+ cpSync9(src, dst, { recursive: true, force: false, errorOnExist: true });
7648
8087
  } else {
7649
8088
  copyFileSync2(src, dst);
7650
8089
  }
7651
8090
  log(`snapshotted shared/${name} from ${src}`);
7652
8091
  }
7653
- const userSettings = join56(claude, "settings.json");
7654
- if (existsSync48(userSettings)) {
8092
+ const userSettings = join58(claude, "settings.json");
8093
+ if (existsSync49(userSettings)) {
7655
8094
  let parsed;
7656
8095
  try {
7657
8096
  parsed = readJson(userSettings);
7658
8097
  } catch (err) {
7659
8098
  return die(`malformed ${userSettings}: ${err.message}`);
7660
8099
  }
7661
- const hostFile = join56(repo, "hosts", `${HOST}.json`);
8100
+ const hostFile = join58(repo, "hosts", `${HOST}.json`);
7662
8101
  writeJsonAtomic(hostFile, parsed);
7663
8102
  log(`snapshotted hosts/${HOST}.json from ${userSettings}`);
7664
8103
  }
@@ -7672,14 +8111,14 @@ var GITATTRIBUTES = "# nomad: sync content is byte-managed, disable all line-end
7672
8111
  var SHARED_KEEP_DIRS = ["agents", "skills", "commands", "rules", "hooks"];
7673
8112
  function preflightConflict(repoHome2) {
7674
8113
  const candidates = [
7675
- join57(repoHome2, "shared", "settings.base.json"),
7676
- join57(repoHome2, "shared", "CLAUDE.md"),
7677
- join57(repoHome2, "path-map.json"),
7678
- join57(repoHome2, "hosts"),
7679
- join57(repoHome2, "shared")
8114
+ join59(repoHome2, "shared", "settings.base.json"),
8115
+ join59(repoHome2, "shared", "CLAUDE.md"),
8116
+ join59(repoHome2, "path-map.json"),
8117
+ join59(repoHome2, "hosts"),
8118
+ join59(repoHome2, "shared")
7680
8119
  ];
7681
8120
  for (const c of candidates) {
7682
- if (existsSync49(c)) return c;
8121
+ if (existsSync50(c)) return c;
7683
8122
  }
7684
8123
  return null;
7685
8124
  }
@@ -7691,33 +8130,33 @@ function cmdInit(opts = {}) {
7691
8130
  const keepActions = opts.keepActions === true;
7692
8131
  const repo = repoHome();
7693
8132
  const claude = claudeHome();
7694
- mkdirSync12(repo, { recursive: true });
8133
+ mkdirSync14(repo, { recursive: true });
7695
8134
  const conflict = preflightConflict(repo);
7696
8135
  if (conflict !== null) {
7697
8136
  die(`already initialized; refusing to clobber ${conflict}`);
7698
8137
  }
7699
8138
  ensureOriginRepo(opts.repoName ?? DEFAULT_REPO_NAME, opts.run);
7700
- mkdirSync12(join57(repo, "shared"), { recursive: true });
7701
- mkdirSync12(join57(repo, "hosts"), { recursive: true });
8139
+ mkdirSync14(join59(repo, "shared"), { recursive: true });
8140
+ mkdirSync14(join59(repo, "hosts"), { recursive: true });
7702
8141
  for (const name of SHARED_KEEP_DIRS) {
7703
- mkdirSync12(join57(repo, "shared", name), { recursive: true });
8142
+ mkdirSync14(join59(repo, "shared", name), { recursive: true });
7704
8143
  }
7705
- const userClaudeMd = join57(claude, "CLAUDE.md");
7706
- if (!snapshot || !existsSync49(userClaudeMd)) {
7707
- writeFileSync6(join57(repo, "shared", "CLAUDE.md"), SHARED_CLAUDE_MD);
8144
+ const userClaudeMd = join59(claude, "CLAUDE.md");
8145
+ if (!snapshot || !existsSync50(userClaudeMd)) {
8146
+ writeFileSync8(join59(repo, "shared", "CLAUDE.md"), SHARED_CLAUDE_MD);
7708
8147
  item("created shared/CLAUDE.md");
7709
8148
  }
7710
8149
  for (const name of SHARED_KEEP_DIRS) {
7711
- writeFileSync6(join57(repo, "shared", name, ".gitkeep"), "");
8150
+ writeFileSync8(join59(repo, "shared", name, ".gitkeep"), "");
7712
8151
  item(`created shared/${name}/.gitkeep`);
7713
8152
  }
7714
- writeFileSync6(join57(repo, "hosts", ".gitkeep"), "");
8153
+ writeFileSync8(join59(repo, "hosts", ".gitkeep"), "");
7715
8154
  item("created hosts/.gitkeep");
7716
- writeJsonAtomic(join57(repo, "shared", "settings.base.json"), {});
8155
+ writeJsonAtomic(join59(repo, "shared", "settings.base.json"), {});
7717
8156
  item("created shared/settings.base.json");
7718
- writeJsonAtomic(join57(repo, "path-map.json"), { projects: {} });
8157
+ writeJsonAtomic(join59(repo, "path-map.json"), { projects: {} });
7719
8158
  item("created path-map.json");
7720
- writeFileSync6(join57(repo, ".gitattributes"), GITATTRIBUTES);
8159
+ writeFileSync8(join59(repo, ".gitattributes"), GITATTRIBUTES);
7721
8160
  item("created .gitattributes");
7722
8161
  if (snapshot) {
7723
8162
  snapshotIntoShared({ projects: {} });
@@ -7783,11 +8222,11 @@ function maybeDisableRepoActions(repoHome2, run) {
7783
8222
  // src/init.prompt.ts
7784
8223
  init_config();
7785
8224
  init_utils();
7786
- import { existsSync as existsSync50, readdirSync as readdirSync18, statSync as statSync13 } from "node:fs";
7787
- import { join as join58 } from "node:path";
8225
+ import { existsSync as existsSync51, readdirSync as readdirSync18, statSync as statSync13 } from "node:fs";
8226
+ import { join as join60 } from "node:path";
7788
8227
  import { createInterface as createInterface3 } from "node:readline/promises";
7789
8228
  function nonEmptyExists(path) {
7790
- if (!existsSync50(path)) return false;
8229
+ if (!existsSync51(path)) return false;
7791
8230
  try {
7792
8231
  if (statSync13(path).isDirectory()) return readdirSync18(path).length > 0;
7793
8232
  return true;
@@ -7796,8 +8235,8 @@ function nonEmptyExists(path) {
7796
8235
  }
7797
8236
  }
7798
8237
  function hasExistingClaudeConfig(claudeHome2) {
7799
- if (existsSync50(join58(claudeHome2, "settings.json"))) return true;
7800
- return SHARED_LINKS.some((name) => nonEmptyExists(join58(claudeHome2, name)));
8238
+ if (existsSync51(join60(claudeHome2, "settings.json"))) return true;
8239
+ return SHARED_LINKS.some((name) => nonEmptyExists(join60(claudeHome2, name)));
7801
8240
  }
7802
8241
  async function confirmSnapshotDefault(claudeHome2) {
7803
8242
  if (process.stdin.isTTY !== true || process.stdout.isTTY !== true) {
@@ -8084,24 +8523,27 @@ function parsePushArgs(argv) {
8084
8523
  // src/nomad.dispatch.sync.ts
8085
8524
  function parseSyncArgs(argv) {
8086
8525
  let dryRun = false;
8526
+ let verbose = false;
8087
8527
  let i = 3;
8088
8528
  while (i < argv.length) {
8089
8529
  const token = argv[i];
8090
8530
  if (token === "--dry-run") {
8091
8531
  if (dryRun) return null;
8092
8532
  dryRun = true;
8533
+ } else if (token === "--verbose" || token === "--all" || token === "-v") {
8534
+ verbose = true;
8093
8535
  } else {
8094
8536
  return null;
8095
8537
  }
8096
8538
  i++;
8097
8539
  }
8098
- return { dryRun };
8540
+ return { dryRun, verbose };
8099
8541
  }
8100
8542
 
8101
8543
  // package.json
8102
8544
  var package_default = {
8103
8545
  name: "claude-nomad",
8104
- version: "0.59.0",
8546
+ version: "0.61.0",
8105
8547
  type: "module",
8106
8548
  description: "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
8107
8549
  keywords: [
@@ -8236,10 +8678,15 @@ var DEFAULT_HELP = [
8236
8678
  cont("Mutually exclusive with --redact-all/--allow. Cannot combine with --dry-run."),
8237
8679
  "",
8238
8680
  row(" sync", "Pull (retain-merge), then push, under one lock. One-shot; hides ordering."),
8681
+ cont("Compact by default: prints only the Sync summary."),
8239
8682
  row(" --dry-run", "Stack the pull preview then the push preview; mutates nothing."),
8240
8683
  cont("Fails fast on a pull-half error; a wedged repo hints at nomad pull --force-remote"),
8241
8684
  cont("(sync itself takes no --force-remote). Mid-push leak recovery behaves exactly"),
8242
8685
  cont("like nomad push."),
8686
+ row(
8687
+ " --verbose, --all, -v",
8688
+ "Show the full merged tree; default output is the Sync summary only."
8689
+ ),
8243
8690
  "",
8244
8691
  row(" diff", "Offline preview of what `pull` would change against local repo state."),
8245
8692
  cont("No git pull, no lock acquired."),
@@ -8337,15 +8784,15 @@ var DEFAULT_HELP = [
8337
8784
  init_config();
8338
8785
  init_utils();
8339
8786
  init_utils_json();
8340
- import { existsSync as existsSync51, readFileSync as readFileSync18, readdirSync as readdirSync19 } from "node:fs";
8341
- import { join as join59 } from "node:path";
8787
+ import { existsSync as existsSync52, readFileSync as readFileSync19, readdirSync as readdirSync19 } from "node:fs";
8788
+ import { join as join61 } from "node:path";
8342
8789
  function resumeCmd(sessionId) {
8343
8790
  if (!/^[A-Za-z0-9_-]+$/.test(sessionId) || sessionId.length > 128) {
8344
8791
  fail(`invalid session id: ${sessionId}`);
8345
8792
  process.exit(1);
8346
8793
  }
8347
- const projectsRoot = join59(claudeHome(), "projects");
8348
- if (!existsSync51(projectsRoot)) {
8794
+ const projectsRoot = join61(claudeHome(), "projects");
8795
+ if (!existsSync52(projectsRoot)) {
8349
8796
  fail(`${projectsRoot} does not exist`);
8350
8797
  process.exit(1);
8351
8798
  }
@@ -8359,8 +8806,8 @@ function resumeCmd(sessionId) {
8359
8806
  fail(`no cwd field found in ${jsonlPath}`);
8360
8807
  process.exit(1);
8361
8808
  }
8362
- const mapPath = join59(repoHome(), "path-map.json");
8363
- if (!existsSync51(mapPath)) {
8809
+ const mapPath = join61(repoHome(), "path-map.json");
8810
+ if (!existsSync52(mapPath)) {
8364
8811
  fail("path-map.json missing");
8365
8812
  process.exit(1);
8366
8813
  }
@@ -8383,13 +8830,13 @@ function resumeCmd(sessionId) {
8383
8830
  }
8384
8831
  function findTranscriptPath(projectsRoot, sessionId) {
8385
8832
  for (const dir of readdirSync19(projectsRoot)) {
8386
- const candidate = join59(projectsRoot, dir, `${sessionId}.jsonl`);
8387
- if (existsSync51(candidate)) return candidate;
8833
+ const candidate = join61(projectsRoot, dir, `${sessionId}.jsonl`);
8834
+ if (existsSync52(candidate)) return candidate;
8388
8835
  }
8389
8836
  return null;
8390
8837
  }
8391
8838
  function extractRecordedCwd(jsonlPath) {
8392
- for (const line of readFileSync18(jsonlPath, "utf8").split("\n")) {
8839
+ for (const line of readFileSync19(jsonlPath, "utf8").split("\n")) {
8393
8840
  if (!line.trim()) continue;
8394
8841
  try {
8395
8842
  const obj = JSON.parse(line);
@@ -8465,10 +8912,10 @@ try {
8465
8912
  case "sync": {
8466
8913
  const syncArgs = parseSyncArgs(process.argv);
8467
8914
  if (syncArgs === null) {
8468
- console.error("usage: nomad sync [--dry-run]");
8915
+ console.error("usage: nomad sync [--dry-run] [--verbose|--all|-v]");
8469
8916
  process.exit(1);
8470
8917
  }
8471
- await cmdSync({ dryRun: syncArgs.dryRun });
8918
+ await cmdSync({ dryRun: syncArgs.dryRun, verbose: syncArgs.verbose });
8472
8919
  break;
8473
8920
  }
8474
8921
  case "init": {