claude-nomad 0.60.0 → 0.62.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
@@ -189,8 +189,36 @@ var init_color = __esm({
189
189
  }
190
190
  });
191
191
 
192
+ // src/exit-codes.ts
193
+ var EXIT;
194
+ var init_exit_codes = __esm({
195
+ "src/exit-codes.ts"() {
196
+ "use strict";
197
+ EXIT = {
198
+ /** Completed successfully. Also returned when a held lock causes a no-op skip. */
199
+ SUCCESS: 0,
200
+ /** Unclassified failure; the default for any error not mapped to a more specific code. */
201
+ GENERIC_FAILURE: 1,
202
+ /** Bad argv: unknown subcommand, unknown flag, or malformed flag value. */
203
+ USAGE: 2,
204
+ /** A wedged repo state (e.g. an unresolved rebase) needing manual git resolution. */
205
+ CONFLICT: 4,
206
+ /** gitleaks confirmed a secret in the staged tree and the push was aborted. */
207
+ LEAK_BLOCKED: 5
208
+ };
209
+ }
210
+ });
211
+
192
212
  // src/utils.ts
193
213
  import { execFileSync } from "node:child_process";
214
+ function isProcessExit(err) {
215
+ if (typeof err !== "object" || err === null) return false;
216
+ try {
217
+ return err[PROCESS_EXIT_BRAND] === true;
218
+ } catch {
219
+ return false;
220
+ }
221
+ }
194
222
  function gitCaptureRaw(args, cwd) {
195
223
  return execFileSync("git", args, {
196
224
  cwd,
@@ -214,11 +242,12 @@ function gitOrFatal(args, context, cwd) {
214
242
  throw new NomadFatal(`${context} failed`);
215
243
  }
216
244
  }
217
- var log, ok, warn, fail, item, NomadFatal, die, gitStatusPorcelainZ;
245
+ var log, ok, warn, fail, item, NomadFatal, PROCESS_EXIT_BRAND, ProcessExit, die, gitStatusPorcelainZ;
218
246
  var init_utils = __esm({
219
247
  "src/utils.ts"() {
220
248
  "use strict";
221
249
  init_color();
250
+ init_exit_codes();
222
251
  log = (msg) => console.log(`${dim(infoGlyph)} ${msg}`);
223
252
  ok = (msg) => console.log(`${green(okGlyph)} ${msg}`);
224
253
  warn = (msg) => {
@@ -229,13 +258,25 @@ var init_utils = __esm({
229
258
  };
230
259
  item = (msg) => console.log(dim(` ${msg}`));
231
260
  NomadFatal = class extends Error {
232
- constructor(message) {
261
+ code;
262
+ constructor(message, opts = {}) {
233
263
  super(message);
234
264
  this.name = "NomadFatal";
265
+ this.code = opts.code ?? EXIT.GENERIC_FAILURE;
235
266
  }
236
267
  };
237
- die = (msg) => {
238
- throw new NomadFatal(msg);
268
+ PROCESS_EXIT_BRAND = /* @__PURE__ */ Symbol.for("claude-nomad.ProcessExit");
269
+ ProcessExit = class extends Error {
270
+ code;
271
+ [PROCESS_EXIT_BRAND] = true;
272
+ constructor(code) {
273
+ super(`exit:${String(code)}`);
274
+ this.name = "ProcessExit";
275
+ this.code = code;
276
+ }
277
+ };
278
+ die = (msg, opts = {}) => {
279
+ throw new NomadFatal(msg, opts);
239
280
  };
240
281
  gitStatusPorcelainZ = (cwd, opts = {}) => {
241
282
  const args = ["status", "--porcelain=v1", "-z"];
@@ -459,6 +500,9 @@ function repoHome() {
459
500
  function backupBase() {
460
501
  return join(home(), ".cache", "claude-nomad", "backup");
461
502
  }
503
+ function crashDir() {
504
+ return join(home(), ".cache", "claude-nomad", "crash");
505
+ }
462
506
  function manifestPath() {
463
507
  return join(home(), ".cache", "claude-nomad", `push-manifest-${encodeURIComponent(HOST)}.json`);
464
508
  }
@@ -957,7 +1001,7 @@ function wedgePreflight(wedge) {
957
1001
  }
958
1002
  function rebaseBeforePush(repo) {
959
1003
  const wedge = classifyWedge(repo);
960
- if (wedge !== null) throw new NomadFatal(wedgePreflight(wedge));
1004
+ if (wedge !== null) throw new NomadFatal(wedgePreflight(wedge), { code: EXIT.CONFLICT });
961
1005
  try {
962
1006
  execFileSync4("git", ["pull", "--rebase", "--autostash"], {
963
1007
  cwd: repo,
@@ -976,6 +1020,7 @@ var init_push_checks = __esm({
976
1020
  "use strict";
977
1021
  init_push_gitleaks_config();
978
1022
  init_commands_pull_wedge();
1023
+ init_exit_codes();
979
1024
  init_utils();
980
1025
  }
981
1026
  });
@@ -1030,7 +1075,7 @@ function scanStagedTree(repoDir, forwardStreams = false) {
1030
1075
  rmSync8(reportPath, { force: true });
1031
1076
  }
1032
1077
  }
1033
- function scanFile(filePath, forwardStreams = false) {
1078
+ function scanFile(filePath, forwardStreams = false, timeoutMs = GITLEAKS_SCAN_TIMEOUT_MS) {
1034
1079
  const cacheDir = join23(homedir3(), ".cache", "claude-nomad");
1035
1080
  mkdirSync3(cacheDir, { recursive: true });
1036
1081
  const reportPath = join23(cacheDir, `gitleaks-file-${nowTimestamp()}-${process.pid}.json`);
@@ -1046,7 +1091,7 @@ function scanFile(filePath, forwardStreams = false) {
1046
1091
  if (toml !== null) args.push("--config", toml);
1047
1092
  const opts = {
1048
1093
  stdio: ["ignore", "pipe", "pipe"],
1049
- timeout: GITLEAKS_SCAN_TIMEOUT_MS
1094
+ timeout: timeoutMs
1050
1095
  };
1051
1096
  try {
1052
1097
  execFileSync8("gitleaks", args, opts);
@@ -1166,6 +1211,7 @@ var init_push_gitleaks = __esm({
1166
1211
  "src/push-gitleaks.ts"() {
1167
1212
  "use strict";
1168
1213
  init_config();
1214
+ init_exit_codes();
1169
1215
  init_push_checks();
1170
1216
  init_push_gitleaks_scan();
1171
1217
  init_utils();
@@ -1686,12 +1732,12 @@ function* eachExtrasTarget(v, counts) {
1686
1732
  counts.unmapped++;
1687
1733
  continue;
1688
1734
  }
1689
- for (const dirname10 of dirnames) {
1690
- if (!whitelist.includes(dirname10)) {
1735
+ for (const dirname13 of dirnames) {
1736
+ if (!whitelist.includes(dirname13)) {
1691
1737
  counts.skipped++;
1692
1738
  continue;
1693
1739
  }
1694
- yield { logical, localRoot, dirname: dirname10 };
1740
+ yield { logical, localRoot, dirname: dirname13 };
1695
1741
  }
1696
1742
  }
1697
1743
  }
@@ -1729,8 +1775,8 @@ function copyExtrasFileSkipDiverged(src, dst) {
1729
1775
  }
1730
1776
  copyExtras(src, dst);
1731
1777
  }
1732
- function extrasDenySet(dirname10) {
1733
- return dirname10 === ".claude" ? CLAUDE_EXTRA_NEVER_SYNC : ALWAYS_NEVER_SYNC;
1778
+ function extrasDenySet(dirname13) {
1779
+ return dirname13 === ".claude" ? CLAUDE_EXTRA_NEVER_SYNC : ALWAYS_NEVER_SYNC;
1734
1780
  }
1735
1781
  function copyExtrasFiltered(src, dst, blockSet) {
1736
1782
  rmSync2(dst, { recursive: true, force: true });
@@ -2024,7 +2070,7 @@ function cmdAdopt(name, opts = {}) {
2024
2070
  } catch (err) {
2025
2071
  if (!(err instanceof NomadFatal)) throw err;
2026
2072
  fail(err.message);
2027
- process.exitCode = 1;
2073
+ process.exitCode = err.code;
2028
2074
  }
2029
2075
  }
2030
2076
 
@@ -2107,6 +2153,7 @@ function appendGitleaksIgnore(fingerprint, repo) {
2107
2153
  }
2108
2154
 
2109
2155
  // src/commands.allow.ts
2156
+ init_exit_codes();
2110
2157
  init_utils();
2111
2158
  function cmdAllow(fingerprints) {
2112
2159
  const repo = repoHome();
@@ -2115,7 +2162,7 @@ function cmdAllow(fingerprints) {
2115
2162
  if (!isValidFingerprint(fp)) {
2116
2163
  const shown = fp.replaceAll("\r", String.raw`\r`).replaceAll("\n", String.raw`\n`);
2117
2164
  fail(`invalid fingerprint: ${shown}`);
2118
- process.exit(1);
2165
+ process.exit(EXIT.USAGE);
2119
2166
  }
2120
2167
  }
2121
2168
  for (const fp of fingerprints) {
@@ -2324,6 +2371,7 @@ async function cmdCaptureSettings(opts) {
2324
2371
 
2325
2372
  // src/commands.clean.ts
2326
2373
  init_config();
2374
+ init_exit_codes();
2327
2375
  init_utils();
2328
2376
  import { existsSync as existsSync8, lstatSync as lstatSync6, readdirSync as readdirSync3, rmSync as rmSync5, statSync as statSync2 } from "node:fs";
2329
2377
  import { join as join10 } from "node:path";
@@ -2364,14 +2412,14 @@ function cmdClean(opts, backupBase2 = backupBase()) {
2364
2412
  const { dryRun, olderThan, keep } = opts;
2365
2413
  if (olderThan !== void 0 && keep !== void 0) {
2366
2414
  fail("--older-than and --keep are mutually exclusive");
2367
- process.exit(1);
2415
+ process.exit(EXIT.USAGE);
2368
2416
  }
2369
2417
  let olderThanMs = CLEAN_DEFAULT_OLDER_THAN_MS;
2370
2418
  if (olderThan !== void 0) {
2371
2419
  const parsed = parseDuration(olderThan);
2372
2420
  if (parsed === null) {
2373
2421
  fail(`invalid --older-than duration: ${olderThan} (expected e.g. 14d, 24h, 30m)`);
2374
- process.exit(1);
2422
+ process.exit(EXIT.USAGE);
2375
2423
  }
2376
2424
  olderThanMs = parsed;
2377
2425
  }
@@ -2558,8 +2606,8 @@ function cmdEject(opts = {}, roots = defaultEjectRoots()) {
2558
2606
  }
2559
2607
 
2560
2608
  // src/commands.doctor.ts
2561
- import { existsSync as existsSync34 } from "node:fs";
2562
- import { join as join40 } from "node:path";
2609
+ import { existsSync as existsSync36 } from "node:fs";
2610
+ import { join as join45 } from "node:path";
2563
2611
 
2564
2612
  // src/commands.doctor.checks.repo.ts
2565
2613
  init_color();
@@ -3406,20 +3454,117 @@ function reportCheckSchema(section2) {
3406
3454
 
3407
3455
  // src/commands.doctor.check-shared.ts
3408
3456
  init_color();
3409
- import { randomBytes } from "node:crypto";
3457
+ import { randomBytes as randomBytes3 } from "node:crypto";
3458
+ import { execFileSync as execFileSync11 } from "node:child_process";
3459
+ import { existsSync as existsSync22, mkdirSync as mkdirSync8, readdirSync as readdirSync11, rmSync as rmSync12 } from "node:fs";
3460
+ import { homedir as homedir6 } from "node:os";
3461
+ import { join as join29 } from "node:path";
3462
+
3463
+ // src/commands.doctor.check-shared.memory.ts
3464
+ init_color();
3410
3465
  import { execFileSync as execFileSync9 } from "node:child_process";
3411
- import { existsSync as existsSync22, mkdirSync as mkdirSync6, readdirSync as readdirSync11, rmSync as rmSync10 } from "node:fs";
3466
+ import { randomBytes } from "node:crypto";
3467
+ import { mkdirSync as mkdirSync4, rmSync as rmSync9, writeFileSync as writeFileSync4 } from "node:fs";
3412
3468
  import { homedir as homedir4 } from "node:os";
3413
- import { join as join27 } from "node:path";
3469
+ import { dirname as dirname3, join as join24 } from "node:path";
3470
+ init_config();
3471
+ init_push_gitleaks();
3472
+ init_utils_fs();
3473
+ var MEMORY_MD_PATH = /^shared\/projects\/([^/]+)\/memory\/[^/]+\.md$/;
3474
+ function buildMemoryScanTree(tmpRoot) {
3475
+ let paths;
3476
+ try {
3477
+ const out = execFileSync9(
3478
+ "git",
3479
+ ["-C", repoHome(), "ls-tree", "-r", "-z", "--name-only", "HEAD", "--", "shared/projects"],
3480
+ { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 1e4 }
3481
+ );
3482
+ paths = out.split("\0").filter((p) => p.length > 0);
3483
+ } catch {
3484
+ return 0;
3485
+ }
3486
+ const logicals = /* @__PURE__ */ new Set();
3487
+ for (const rel of paths) {
3488
+ const m = MEMORY_MD_PATH.exec(rel);
3489
+ if (m?.[1] === void 0) continue;
3490
+ let blob;
3491
+ try {
3492
+ blob = execFileSync9("git", ["-C", repoHome(), "cat-file", "blob", `HEAD:${rel}`], {
3493
+ stdio: ["ignore", "pipe", "ignore"],
3494
+ maxBuffer: 67108864,
3495
+ timeout: 1e4
3496
+ });
3497
+ } catch {
3498
+ continue;
3499
+ }
3500
+ const dest = join24(tmpRoot, rel);
3501
+ mkdirSync4(dirname3(dest), { recursive: true });
3502
+ writeFileSync4(dest, blob);
3503
+ logicals.add(m[1]);
3504
+ }
3505
+ return logicals.size;
3506
+ }
3507
+ function reportMemoryFindings(section2, findings) {
3508
+ for (const f of findings) {
3509
+ addItem(section2, `${yellow(warnGlyph)} ${f.RuleID} in ${f.File}`);
3510
+ }
3511
+ addItem(
3512
+ section2,
3513
+ ` ${dim("run `nomad push` and choose Redact in the recovery menu to scrub these")}`
3514
+ );
3515
+ }
3516
+ function reportCommittedMemory(section2) {
3517
+ let tmpRoot;
3518
+ try {
3519
+ const cacheDir = join24(homedir4(), ".cache", "claude-nomad");
3520
+ mkdirSync4(cacheDir, { recursive: true });
3521
+ const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes(4).toString("hex")}`;
3522
+ tmpRoot = join24(cacheDir, `check-shared-memory-tree-${stamp}`);
3523
+ mkdirSync4(tmpRoot, { recursive: true, mode: 448 });
3524
+ const staged = buildMemoryScanTree(tmpRoot);
3525
+ if (staged === 0) return;
3526
+ let findings;
3527
+ try {
3528
+ findings = scanStagedTree(tmpRoot);
3529
+ } catch (err) {
3530
+ addItem(
3531
+ section2,
3532
+ `${yellow(warnGlyph)} committed-memory scan skipped: ${err.message}`
3533
+ );
3534
+ return;
3535
+ }
3536
+ if (findings === null) {
3537
+ addItem(
3538
+ section2,
3539
+ `${yellow(warnGlyph)} committed-memory scan skipped: no parseable gitleaks report`
3540
+ );
3541
+ return;
3542
+ }
3543
+ if (findings.length === 0) return;
3544
+ reportMemoryFindings(section2, findings);
3545
+ } catch (err) {
3546
+ addItem(
3547
+ section2,
3548
+ `${yellow(warnGlyph)} committed-memory scan skipped: ${err.message}`
3549
+ );
3550
+ } finally {
3551
+ if (tmpRoot !== void 0) {
3552
+ try {
3553
+ rmSync9(tmpRoot, { recursive: true, force: true });
3554
+ } catch {
3555
+ }
3556
+ }
3557
+ }
3558
+ }
3414
3559
 
3415
3560
  // src/commands.doctor.check-shared.scan.ts
3416
3561
  init_color();
3417
- import { join as join24 } from "node:path";
3562
+ import { join as join25 } from "node:path";
3418
3563
  init_config();
3419
3564
  init_push_gitleaks();
3420
3565
  function scrubPath(logical, sid, logicalToEncoded) {
3421
3566
  const encoded = logicalToEncoded.get(logical) ?? logical;
3422
- return join24(claudeHome(), "projects", encoded, `${sid}.jsonl`);
3567
+ return join25(claudeHome(), "projects", encoded, `${sid}.jsonl`);
3423
3568
  }
3424
3569
  function reportSessionFindings(section2, bySession) {
3425
3570
  for (const [sid, counts] of bySession) {
@@ -3502,13 +3647,123 @@ function scanAndReport(section2, tmpRoot, staged, logicalToEncoded) {
3502
3647
  emitDescriptionLegend(section2, findings);
3503
3648
  }
3504
3649
 
3650
+ // src/commands.doctor.check-shared.skills.ts
3651
+ init_color();
3652
+ import { execFileSync as execFileSync10 } from "node:child_process";
3653
+ import { randomBytes as randomBytes2 } from "node:crypto";
3654
+ import { mkdirSync as mkdirSync5, rmSync as rmSync10, writeFileSync as writeFileSync5 } from "node:fs";
3655
+ import { homedir as homedir5 } from "node:os";
3656
+ import { dirname as dirname4, join as join26 } from "node:path";
3657
+ init_config();
3658
+ init_push_gitleaks();
3659
+ init_utils_fs();
3660
+ var SKILL_PATH = /^shared\/skills\/([^/]+)\/(.+)$/;
3661
+ function buildSkillScanTree(tmpRoot) {
3662
+ let paths;
3663
+ try {
3664
+ const out = execFileSync10(
3665
+ "git",
3666
+ ["-C", repoHome(), "ls-tree", "-r", "-z", "--name-only", "HEAD", "--", "shared/skills"],
3667
+ { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 1e4 }
3668
+ );
3669
+ paths = out.split("\0").filter((p) => p.length > 0);
3670
+ } catch {
3671
+ return { staged: 0, incomplete: false };
3672
+ }
3673
+ const names = /* @__PURE__ */ new Set();
3674
+ let incomplete = false;
3675
+ for (const rel of paths) {
3676
+ const m = SKILL_PATH.exec(rel);
3677
+ if (m?.[1] === void 0) continue;
3678
+ let blob;
3679
+ try {
3680
+ blob = execFileSync10("git", ["-C", repoHome(), "cat-file", "blob", `HEAD:${rel}`], {
3681
+ stdio: ["ignore", "pipe", "ignore"],
3682
+ maxBuffer: 67108864,
3683
+ timeout: 1e4
3684
+ });
3685
+ } catch {
3686
+ incomplete = true;
3687
+ continue;
3688
+ }
3689
+ const dest = join26(tmpRoot, rel);
3690
+ mkdirSync5(dirname4(dest), { recursive: true });
3691
+ writeFileSync5(dest, blob);
3692
+ names.add(m[1]);
3693
+ }
3694
+ return { staged: names.size, incomplete };
3695
+ }
3696
+ function reportSkillFindings(section2, findings) {
3697
+ for (const f of findings) {
3698
+ addItem(section2, `${yellow(warnGlyph)} ${f.RuleID} in ${f.File}`);
3699
+ }
3700
+ addItem(
3701
+ section2,
3702
+ ` ${dim("run `nomad push` and choose Redact in the recovery menu to scrub these")}`
3703
+ );
3704
+ addItem(
3705
+ section2,
3706
+ ` ${dim("a gsd-* skill is not auto-redactable; remove the secret from it by hand")}`
3707
+ );
3708
+ }
3709
+ function reportCommittedSkills(section2) {
3710
+ let tmpRoot;
3711
+ try {
3712
+ const cacheDir = join26(homedir5(), ".cache", "claude-nomad");
3713
+ mkdirSync5(cacheDir, { recursive: true });
3714
+ const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes2(4).toString("hex")}`;
3715
+ tmpRoot = join26(cacheDir, `check-shared-skills-tree-${stamp}`);
3716
+ mkdirSync5(tmpRoot, { recursive: true, mode: 448 });
3717
+ const { staged, incomplete } = buildSkillScanTree(tmpRoot);
3718
+ if (incomplete) {
3719
+ addItem(
3720
+ section2,
3721
+ `${yellow(warnGlyph)} committed-skills scan skipped: could not read every committed skill blob`
3722
+ );
3723
+ return;
3724
+ }
3725
+ if (staged === 0) return;
3726
+ let findings;
3727
+ try {
3728
+ findings = scanStagedTree(tmpRoot);
3729
+ } catch (err) {
3730
+ addItem(
3731
+ section2,
3732
+ `${yellow(warnGlyph)} committed-skills scan skipped: ${err.message}`
3733
+ );
3734
+ return;
3735
+ }
3736
+ if (findings === null) {
3737
+ addItem(
3738
+ section2,
3739
+ `${yellow(warnGlyph)} committed-skills scan skipped: no parseable gitleaks report`
3740
+ );
3741
+ return;
3742
+ }
3743
+ if (findings.length === 0) return;
3744
+ reportSkillFindings(section2, findings);
3745
+ } catch (err) {
3746
+ addItem(
3747
+ section2,
3748
+ `${yellow(warnGlyph)} committed-skills scan skipped: ${err.message}`
3749
+ );
3750
+ } finally {
3751
+ if (tmpRoot !== void 0) {
3752
+ try {
3753
+ rmSync10(tmpRoot, { recursive: true, force: true });
3754
+ } catch {
3755
+ }
3756
+ }
3757
+ }
3758
+ }
3759
+
3505
3760
  // src/commands.doctor.check-shared.ts
3506
3761
  init_config();
3507
3762
 
3508
3763
  // src/remap.ts
3509
3764
  init_config_sharedDirs_guard();
3510
- 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";
3511
- import { dirname as dirname4, join as join26, relative as relative5, sep as sep4 } from "node:path";
3765
+ import { cpSync as cpSync6, existsSync as existsSync21, lstatSync as lstatSync10, mkdirSync as mkdirSync7, readdirSync as readdirSync10, rmSync as rmSync11, statSync as statSync5 } from "node:fs";
3766
+ import { dirname as dirname6, join as join28, relative as relative5, sep as sep4 } from "node:path";
3512
3767
  init_config();
3513
3768
 
3514
3769
  // src/push-manifest.ts
@@ -3516,8 +3771,8 @@ init_config();
3516
3771
  init_push_gitleaks_config();
3517
3772
  init_utils_fs();
3518
3773
  import { createHash } from "node:crypto";
3519
- import { existsSync as existsSync20, mkdirSync as mkdirSync4, readdirSync as readdirSync9, readFileSync as readFileSync7, statSync as statSync4 } from "node:fs";
3520
- import { dirname as dirname3, join as join25 } from "node:path";
3774
+ import { existsSync as existsSync20, mkdirSync as mkdirSync6, readdirSync as readdirSync9, readFileSync as readFileSync7, statSync as statSync4 } from "node:fs";
3775
+ import { dirname as dirname5, join as join27 } from "node:path";
3521
3776
  function isChanged(prev, cur, hash) {
3522
3777
  if (prev === void 0) return true;
3523
3778
  if (prev.size !== cur.size) return true;
@@ -3558,7 +3813,7 @@ function hashFile(absPath) {
3558
3813
  }
3559
3814
  function enumerateDir(dir, results) {
3560
3815
  for (const entry of readdirSync9(dir)) {
3561
- const fullPath = join25(dir, entry);
3816
+ const fullPath = join27(dir, entry);
3562
3817
  const st = statSync4(fullPath);
3563
3818
  if (st.isDirectory()) {
3564
3819
  enumerateDir(fullPath, results);
@@ -3570,7 +3825,7 @@ function enumerateDir(dir, results) {
3570
3825
  function enumerateSourceFiles(projectDir) {
3571
3826
  const results = [];
3572
3827
  for (const entry of readdirSync9(projectDir)) {
3573
- const fullPath = join25(projectDir, entry);
3828
+ const fullPath = join27(projectDir, entry);
3574
3829
  const st = statSync4(fullPath);
3575
3830
  if (st.isDirectory()) {
3576
3831
  enumerateDir(fullPath, results);
@@ -3589,8 +3844,8 @@ function computeConfigHash() {
3589
3844
  const repo = repoHome();
3590
3845
  const parts = [
3591
3846
  fileIdentity(resolveTomlPath(repo)),
3592
- fileIdentity(join25(repo, ".gitleaks.overlay.toml")),
3593
- fileIdentity(join25(repo, ".gitleaksignore"))
3847
+ fileIdentity(join27(repo, ".gitleaks.overlay.toml")),
3848
+ fileIdentity(join27(repo, ".gitleaksignore"))
3594
3849
  ];
3595
3850
  return createHash("sha256").update(parts.join("\n")).digest("hex");
3596
3851
  }
@@ -3610,7 +3865,7 @@ function readManifest(path) {
3610
3865
  }
3611
3866
  }
3612
3867
  function writeManifest(path, manifest) {
3613
- mkdirSync4(dirname3(path), { recursive: true });
3868
+ mkdirSync6(dirname5(path), { recursive: true });
3614
3869
  writeJsonAtomic(path, manifest);
3615
3870
  }
3616
3871
  function buildManifest(files, scannerVersion, configHash) {
@@ -3624,9 +3879,9 @@ init_utils_json();
3624
3879
  var TMP_SUFFIX = ".nomad-tmp";
3625
3880
  function atomicMirror(src, dst, options) {
3626
3881
  const tmp = `${dst}${TMP_SUFFIX}`;
3627
- rmSync9(tmp, { recursive: true, force: true });
3882
+ rmSync11(tmp, { recursive: true, force: true });
3628
3883
  cpSync6(src, tmp, options);
3629
- rmSync9(dst, { recursive: true, force: true });
3884
+ rmSync11(dst, { recursive: true, force: true });
3630
3885
  renameAtomicRetry(tmp, dst);
3631
3886
  }
3632
3887
  function overlaySessionDir(src, dst) {
@@ -3634,7 +3889,7 @@ function overlaySessionDir(src, dst) {
3634
3889
  cpSyncGuarded(src, dst, void 0, "overlaySessionDir");
3635
3890
  }
3636
3891
  function copyFileAtomic(src, dst) {
3637
- mkdirSync5(dirname4(dst), { recursive: true });
3892
+ mkdirSync7(dirname6(dst), { recursive: true });
3638
3893
  const tmp = `${dst}.tmp.${process.pid}`;
3639
3894
  cpSync6(src, tmp, { force: true, preserveTimestamps: true });
3640
3895
  renameAtomicRetry(tmp, dst);
@@ -3652,12 +3907,12 @@ function applySelective(sel, localDir, repoDst) {
3652
3907
  const prefix = `${localDir}${sep4}`;
3653
3908
  for (const src of sel.changed) {
3654
3909
  if (!src.startsWith(prefix)) continue;
3655
- copyFileAtomic(src, join26(repoDst, relative5(localDir, src)));
3910
+ copyFileAtomic(src, join28(repoDst, relative5(localDir, src)));
3656
3911
  }
3657
3912
  for (const src of sel.deleted) {
3658
3913
  if (!src.startsWith(prefix)) continue;
3659
- const dst = join26(repoDst, relative5(localDir, src));
3660
- if (existsSync21(dst)) rmSync9(dst);
3914
+ const dst = join28(repoDst, relative5(localDir, src));
3915
+ if (existsSync21(dst)) rmSync11(dst);
3661
3916
  }
3662
3917
  }
3663
3918
  function copyDirJsonlOnly(src, dst) {
@@ -3686,16 +3941,16 @@ function remapPull(ts, opts = {}) {
3686
3941
  const wouldPull = [];
3687
3942
  const repo = repoHome();
3688
3943
  const claude = claudeHome();
3689
- const mapPath = join26(repo, "path-map.json");
3690
- const repoProjects = join26(repo, "shared", "projects");
3944
+ const mapPath = join28(repo, "path-map.json");
3945
+ const repoProjects = join28(repo, "shared", "projects");
3691
3946
  if (!existsSync21(mapPath) || !existsSync21(repoProjects)) {
3692
3947
  const text = "no path-map or repo projects dir; skipping session remap";
3693
3948
  emitPreview(opts.onPreview, { kind: "note", text }, text);
3694
3949
  return { unmapped: 0, pulled, wouldPull };
3695
3950
  }
3696
3951
  const map = readPathMap(mapPath);
3697
- const localProjects = join26(claude, "projects");
3698
- if (!dryRun) mkdirSync5(localProjects, { recursive: true });
3952
+ const localProjects = join28(claude, "projects");
3953
+ if (!dryRun) mkdirSync7(localProjects, { recursive: true });
3699
3954
  for (const [logical, hosts] of Object.entries(map.projects)) {
3700
3955
  assertSafeLogical(logical);
3701
3956
  const localPath = hosts[HOST];
@@ -3704,9 +3959,9 @@ function remapPull(ts, opts = {}) {
3704
3959
  continue;
3705
3960
  }
3706
3961
  assertSafeLocalRoot(localPath, logical);
3707
- const src = join26(repoProjects, logical);
3962
+ const src = join28(repoProjects, logical);
3708
3963
  if (!existsSync21(src)) continue;
3709
- const dst = join26(localProjects, encodePath(localPath));
3964
+ const dst = join28(localProjects, encodePath(localPath));
3710
3965
  if (dryRun) {
3711
3966
  wouldPull.push(logical);
3712
3967
  emitPreview(
@@ -3725,8 +3980,8 @@ function remapPull(ts, opts = {}) {
3725
3980
  function countLocalOnly(src, dst) {
3726
3981
  let count = 0;
3727
3982
  for (const name of readdirSync10(dst)) {
3728
- const dstPath = join26(dst, name);
3729
- const srcPath = join26(src, name);
3983
+ const dstPath = join28(dst, name);
3984
+ const srcPath = join28(src, name);
3730
3985
  if (lstatSync10(dstPath).isDirectory()) {
3731
3986
  count += countLocalOnly(srcPath, dstPath);
3732
3987
  } else if (lstatSync10(srcPath, { throwIfNoEntry: false }) === void 0) {
@@ -3738,20 +3993,20 @@ function countLocalOnly(src, dst) {
3738
3993
  function scanLocalOnly() {
3739
3994
  const repo = repoHome();
3740
3995
  const claude = claudeHome();
3741
- const mapPath = join26(repo, "path-map.json");
3742
- const repoProjects = join26(repo, "shared", "projects");
3996
+ const mapPath = join28(repo, "path-map.json");
3997
+ const repoProjects = join28(repo, "shared", "projects");
3743
3998
  if (!existsSync21(mapPath) || !existsSync21(repoProjects)) return 0;
3744
3999
  const map = readPathMap(mapPath);
3745
- const localProjects = join26(claude, "projects");
4000
+ const localProjects = join28(claude, "projects");
3746
4001
  let count = 0;
3747
4002
  for (const [logical, hosts] of Object.entries(map.projects)) {
3748
4003
  assertSafeLogical(logical);
3749
4004
  const localPath = hosts[HOST];
3750
4005
  if (!localPath || localPath === "TBD") continue;
3751
4006
  assertSafeLocalRoot(localPath, logical);
3752
- const dst = join26(localProjects, encodePath(localPath));
4007
+ const dst = join28(localProjects, encodePath(localPath));
3753
4008
  if (!existsSync21(dst)) continue;
3754
- count += countLocalOnly(join26(repoProjects, logical), dst);
4009
+ count += countLocalOnly(join28(repoProjects, logical), dst);
3755
4010
  }
3756
4011
  return count;
3757
4012
  }
@@ -3786,17 +4041,17 @@ function remapPush(ts, opts = {}) {
3786
4041
  const wouldPush = [];
3787
4042
  const repo = repoHome();
3788
4043
  const claude = claudeHome();
3789
- const mapPath = join26(repo, "path-map.json");
4044
+ const mapPath = join28(repo, "path-map.json");
3790
4045
  if (!existsSync21(mapPath)) {
3791
4046
  log("no path-map.json; skipping session export");
3792
4047
  return { unmapped: 0, collisions: 0, pushed, wouldPush };
3793
4048
  }
3794
4049
  const map = readPathMap(mapPath);
3795
- const localProjects = join26(claude, "projects");
3796
- const repoProjects = join26(repo, "shared", "projects");
4050
+ const localProjects = join28(claude, "projects");
4051
+ const repoProjects = join28(repo, "shared", "projects");
3797
4052
  const reverse = buildReverseMap(map);
3798
4053
  if (!existsSync21(localProjects)) return { unmapped, collisions: 0, pushed, wouldPush };
3799
- if (!dryRun) mkdirSync5(repoProjects, { recursive: true });
4054
+ if (!dryRun) mkdirSync7(repoProjects, { recursive: true });
3800
4055
  for (const dir of readdirSync10(localProjects)) {
3801
4056
  if (dir.endsWith(TMP_SUFFIX)) continue;
3802
4057
  const logical = reverse.get(dir);
@@ -3804,8 +4059,8 @@ function remapPush(ts, opts = {}) {
3804
4059
  unmapped++;
3805
4060
  continue;
3806
4061
  }
3807
- const localDir = join26(localProjects, dir);
3808
- const repoDst = join26(repoProjects, logical);
4062
+ const localDir = join28(localProjects, dir);
4063
+ const repoDst = join28(repoProjects, logical);
3809
4064
  if (skipForSelection(opts.selection, localDir)) continue;
3810
4065
  if (dryRun) {
3811
4066
  wouldPush.push(logical);
@@ -3829,7 +4084,7 @@ function buildScanTree(tmpRoot) {
3829
4084
  const logicalToEncoded = /* @__PURE__ */ new Map();
3830
4085
  let staged = 0;
3831
4086
  const repo = repoHome();
3832
- const mapPath = join27(repo, "path-map.json");
4087
+ const mapPath = join29(repo, "path-map.json");
3833
4088
  if (!existsSync22(mapPath)) return { logicalToEncoded, staged, malformed: false };
3834
4089
  let map;
3835
4090
  try {
@@ -3847,12 +4102,12 @@ function buildScanTree(tmpRoot) {
3847
4102
  if (!p || p === "TBD") continue;
3848
4103
  reverse.set(encodePath(p), logical);
3849
4104
  }
3850
- const localProjects = join27(claudeHome(), "projects");
4105
+ const localProjects = join29(claudeHome(), "projects");
3851
4106
  if (!existsSync22(localProjects)) return { logicalToEncoded, staged, malformed: false };
3852
4107
  for (const dir of readdirSync11(localProjects)) {
3853
4108
  const logical = reverse.get(dir);
3854
4109
  if (!logical) continue;
3855
- copyDirJsonlOnly(join27(localProjects, dir), join27(tmpRoot, "shared", "projects", logical));
4110
+ copyDirJsonlOnly(join29(localProjects, dir), join29(tmpRoot, "shared", "projects", logical));
3856
4111
  logicalToEncoded.set(logical, dir);
3857
4112
  staged++;
3858
4113
  }
@@ -3860,7 +4115,7 @@ function buildScanTree(tmpRoot) {
3860
4115
  }
3861
4116
  function probeGitleaksForScan() {
3862
4117
  try {
3863
- execFileSync9("gitleaks", ["version"], { stdio: ["ignore", "pipe", "pipe"] });
4118
+ execFileSync11("gitleaks", ["version"], { stdio: ["ignore", "pipe", "pipe"] });
3864
4119
  return "ok";
3865
4120
  } catch (err) {
3866
4121
  if (err.code === "ENOENT") return "missing";
@@ -3881,13 +4136,12 @@ function ensureGitleaksReady(section2, gitleaksReady) {
3881
4136
  }
3882
4137
  return true;
3883
4138
  }
3884
- function reportCheckShared(section2, gitleaksReady) {
3885
- if (!ensureGitleaksReady(section2, gitleaksReady)) return;
3886
- const cacheDir = join27(homedir4(), ".cache", "claude-nomad");
3887
- mkdirSync6(cacheDir, { recursive: true });
3888
- const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes(4).toString("hex")}`;
3889
- const reportPath = join27(cacheDir, `check-shared-${stamp}.json`);
3890
- const tmpRoot = join27(cacheDir, `check-shared-tree-${stamp}`);
4139
+ function runLocalPreviewScan(section2) {
4140
+ const cacheDir = join29(homedir6(), ".cache", "claude-nomad");
4141
+ mkdirSync8(cacheDir, { recursive: true });
4142
+ const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes3(4).toString("hex")}`;
4143
+ const reportPath = join29(cacheDir, `check-shared-${stamp}.json`);
4144
+ const tmpRoot = join29(cacheDir, `check-shared-tree-${stamp}`);
3891
4145
  try {
3892
4146
  const { logicalToEncoded, staged, malformed } = buildScanTree(tmpRoot);
3893
4147
  if (malformed) {
@@ -3901,15 +4155,27 @@ function reportCheckShared(section2, gitleaksReady) {
3901
4155
  }
3902
4156
  scanAndReport(section2, tmpRoot, staged, logicalToEncoded);
3903
4157
  } finally {
3904
- rmSync10(reportPath, { force: true });
3905
- rmSync10(tmpRoot, { recursive: true, force: true });
4158
+ rmSync12(reportPath, { force: true });
4159
+ rmSync12(tmpRoot, { recursive: true, force: true });
4160
+ }
4161
+ }
4162
+ function reportCheckShared(section2, gitleaksReady) {
4163
+ if (!ensureGitleaksReady(section2, gitleaksReady)) return;
4164
+ try {
4165
+ runLocalPreviewScan(section2);
4166
+ } catch (err) {
4167
+ addItem(section2, `${red(failGlyph)} shared scan failed: ${err.message}`);
4168
+ process.exitCode = 1;
4169
+ } finally {
4170
+ reportCommittedMemory(section2);
4171
+ reportCommittedSkills(section2);
3906
4172
  }
3907
4173
  }
3908
4174
 
3909
4175
  // src/commands.doctor.checks.hooks.scope.ts
3910
4176
  init_color();
3911
4177
  import { existsSync as existsSync23, readFileSync as readFileSync8, readdirSync as readdirSync12, realpathSync as realpathSync2 } from "node:fs";
3912
- import { dirname as dirname5, extname, join as join28 } from "node:path";
4178
+ import { dirname as dirname7, extname, join as join30 } from "node:path";
3913
4179
  init_config();
3914
4180
  function typeFromPackageJson(pkgPath) {
3915
4181
  try {
@@ -3929,11 +4195,11 @@ function effectiveType(hookPath) {
3929
4195
  } catch {
3930
4196
  return null;
3931
4197
  }
3932
- let dir = dirname5(real);
4198
+ let dir = dirname7(real);
3933
4199
  for (; ; ) {
3934
- const pkg = join28(dir, "package.json");
4200
+ const pkg = join30(dir, "package.json");
3935
4201
  if (existsSync23(pkg)) return typeFromPackageJson(pkg);
3936
- const parent = dirname5(dir);
4202
+ const parent = dirname7(dir);
3937
4203
  if (parent === dir) return "cjs";
3938
4204
  dir = parent;
3939
4205
  }
@@ -3974,7 +4240,7 @@ function safeRead(path) {
3974
4240
  }
3975
4241
  }
3976
4242
  function reportHookScopeCheck(section2) {
3977
- const hooksDir = join28(claudeHome(), "hooks");
4243
+ const hooksDir = join30(claudeHome(), "hooks");
3978
4244
  if (!existsSync23(hooksDir)) {
3979
4245
  addItem(section2, `${dim(infoGlyph)} no ~/.claude/hooks; skipping module-scope check`);
3980
4246
  return;
@@ -3982,7 +4248,7 @@ function reportHookScopeCheck(section2) {
3982
4248
  let anyWarn = false;
3983
4249
  for (const name of safeReaddir2(hooksDir)) {
3984
4250
  if (extname(name) !== ".js") continue;
3985
- const abs = join28(hooksDir, name);
4251
+ const abs = join30(hooksDir, name);
3986
4252
  const eff = effectiveType(abs);
3987
4253
  if (eff === null) continue;
3988
4254
  const src = safeRead(abs);
@@ -4003,7 +4269,7 @@ function reportHookScopeCheck(section2) {
4003
4269
  // src/commands.doctor.checks.hooks.ts
4004
4270
  init_color();
4005
4271
  import { existsSync as existsSync24 } from "node:fs";
4006
- import { join as join29 } from "node:path";
4272
+ import { join as join31 } from "node:path";
4007
4273
  init_config();
4008
4274
  function expandHome(token) {
4009
4275
  const h2 = home();
@@ -4059,7 +4325,7 @@ function checkEventGroups(section2, event, groups) {
4059
4325
  return anyFail;
4060
4326
  }
4061
4327
  function reportHooksTargetCheck(section2) {
4062
- const settingsPath = join29(claudeHome(), "settings.json");
4328
+ const settingsPath = join31(claudeHome(), "settings.json");
4063
4329
  if (!existsSync24(settingsPath)) {
4064
4330
  addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json; skipping hook target check`);
4065
4331
  return;
@@ -4084,11 +4350,11 @@ function reportHooksTargetCheck(section2) {
4084
4350
  // src/commands.doctor.checks.hooks.preserve-symlinks.ts
4085
4351
  init_color();
4086
4352
  import { existsSync as existsSync26, readFileSync as readFileSync9 } from "node:fs";
4087
- import { join as join31 } from "node:path";
4353
+ import { join as join33 } from "node:path";
4088
4354
 
4089
4355
  // src/commands.doctor.checks.hooks.preserve-symlinks.probe.ts
4090
4356
  import { closeSync as closeSync3, existsSync as existsSync25, openSync as openSync3, readSync, realpathSync as realpathSync3 } from "node:fs";
4091
- import { dirname as dirname6, join as join30, resolve as resolve2 } from "node:path";
4357
+ import { dirname as dirname8, join as join32, resolve as resolve2 } from "node:path";
4092
4358
  function suppressedRanges(src) {
4093
4359
  const ranges = [];
4094
4360
  const re = /\/\*[\s\S]*?\*\/|\/\/[^\n]*|'[^']*'|"[^"]*"|`[^`]*`/g;
@@ -4125,7 +4391,7 @@ function specifierIsMissing(specifier, baseDir) {
4125
4391
  if (existsSync25(base + ext)) return false;
4126
4392
  }
4127
4393
  for (const idx of ["index.js", "index.cjs", "index.mjs"]) {
4128
- if (existsSync25(join30(base, idx))) return false;
4394
+ if (existsSync25(join32(base, idx))) return false;
4129
4395
  }
4130
4396
  return true;
4131
4397
  }
@@ -4151,7 +4417,7 @@ function relativeRequireTargetsBroken(scriptPath) {
4151
4417
  }
4152
4418
  const specifiers = topRelativeSpecifiers(raw);
4153
4419
  if (specifiers.length === 0) return false;
4154
- const baseDir = dirname6(realPath);
4420
+ const baseDir = dirname8(realPath);
4155
4421
  for (const spec of specifiers) {
4156
4422
  if (specifierIsMissing(spec, baseDir)) return true;
4157
4423
  }
@@ -4180,7 +4446,7 @@ function commandTokens(command) {
4180
4446
  return tokens;
4181
4447
  }
4182
4448
  function readPathMapSafe() {
4183
- const mapPath = join31(repoHome(), "path-map.json");
4449
+ const mapPath = join33(repoHome(), "path-map.json");
4184
4450
  if (!existsSync26(mapPath)) return { projects: {} };
4185
4451
  try {
4186
4452
  return JSON.parse(readFileSync9(mapPath, "utf8"));
@@ -4255,7 +4521,7 @@ function checkEventForPreserveSymlinks(section2, event, groups, sharedLinkNames)
4255
4521
  return anyWarn;
4256
4522
  }
4257
4523
  function reportPreserveSymlinksCheck(section2) {
4258
- const settingsPath = join31(claudeHome(), "settings.json");
4524
+ const settingsPath = join33(claudeHome(), "settings.json");
4259
4525
  if (!existsSync26(settingsPath)) {
4260
4526
  addItem(
4261
4527
  section2,
@@ -4290,7 +4556,7 @@ function reportPreserveSymlinksCheck(section2) {
4290
4556
  // src/commands.doctor.checks.settings-drift.ts
4291
4557
  init_color();
4292
4558
  import { existsSync as existsSync27, readFileSync as readFileSync10 } from "node:fs";
4293
- import { join as join32 } from "node:path";
4559
+ import { join as join34 } from "node:path";
4294
4560
  init_config();
4295
4561
  init_utils_json();
4296
4562
  function diffMergedSettings(merged, settings) {
@@ -4308,7 +4574,7 @@ function tryReadJson(filePath) {
4308
4574
  }
4309
4575
  }
4310
4576
  function reportHooksBaseSelfCleanNote(section2) {
4311
- const basePath = join32(repoHome(), "shared", "settings.base.json");
4577
+ const basePath = join34(repoHome(), "shared", "settings.base.json");
4312
4578
  const base = tryReadJson(basePath);
4313
4579
  if (base === null) return;
4314
4580
  if (!baseHasGsdHookEntries(base)) return;
@@ -4321,9 +4587,9 @@ function reportSettingsDriftCheck(section2) {
4321
4587
  const claude = claudeHome();
4322
4588
  const repo = repoHome();
4323
4589
  const host = HOST;
4324
- const settingsPath = join32(claude, "settings.json");
4325
- const basePath = join32(repo, "shared", "settings.base.json");
4326
- const hostPath = join32(repo, "hosts", `${host}.json`);
4590
+ const settingsPath = join34(claude, "settings.json");
4591
+ const basePath = join34(repo, "shared", "settings.base.json");
4592
+ const hostPath = join34(repo, "hosts", `${host}.json`);
4327
4593
  if (!existsSync27(settingsPath)) {
4328
4594
  addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json; skipping merge-drift check`);
4329
4595
  return;
@@ -4505,7 +4771,7 @@ function reportNodeEngineCheck(section2) {
4505
4771
 
4506
4772
  // src/commands.doctor.checks.longpaths.ts
4507
4773
  init_color();
4508
- import { execFileSync as execFileSync10 } from "node:child_process";
4774
+ import { execFileSync as execFileSync12 } from "node:child_process";
4509
4775
  init_config();
4510
4776
  var PROBE_TIMEOUT_MS = 3e3;
4511
4777
  var LONGPATHS_REG_KEY = String.raw`HKLM\SYSTEM\CurrentControlSet\Control\FileSystem`;
@@ -4539,7 +4805,7 @@ function addLongpathsRow(section2, label, enabled2, remediation) {
4539
4805
  }
4540
4806
  addItem(section2, `${yellow(warnGlyph)} ${label}: not enabled (${remediation})`);
4541
4807
  }
4542
- function reportLongPathsCheck(section2, run = execFileSync10) {
4808
+ function reportLongPathsCheck(section2, run = execFileSync12) {
4543
4809
  if (process.platform !== "win32") return;
4544
4810
  addLongpathsRow(
4545
4811
  section2,
@@ -4561,15 +4827,15 @@ function reportSyncModality(section2) {
4561
4827
 
4562
4828
  // src/commands.doctor.checks.crlf.ts
4563
4829
  init_color();
4564
- import { execFileSync as execFileSync11 } from "node:child_process";
4830
+ import { execFileSync as execFileSync13 } from "node:child_process";
4565
4831
  import { existsSync as existsSync28, readFileSync as readFileSync13 } from "node:fs";
4566
- import { join as join33 } from "node:path";
4832
+ import { join as join35 } from "node:path";
4567
4833
  init_config();
4568
4834
  var PROBE_TIMEOUT_MS2 = 3e3;
4569
4835
  var GUARD_LINE = /^\*\s+-text\b/;
4570
4836
  function hasGitattributesGuard(repo) {
4571
4837
  try {
4572
- const path = join33(repo, ".gitattributes");
4838
+ const path = join35(repo, ".gitattributes");
4573
4839
  if (!existsSync28(path)) return false;
4574
4840
  const content = readFileSync13(path, "utf8");
4575
4841
  return content.split("\n").map((line) => line.trim()).some((line) => line !== "" && !line.startsWith("#") && GUARD_LINE.test(line));
@@ -4600,7 +4866,7 @@ function addExposedRow(section2, repo, verdict) {
4600
4866
  const remediation = `add a .gitattributes with a \`* -text\` line, or run \`git config core.autocrlf false\`, in ${repo}`;
4601
4867
  addItem(section2, `${yellow(warnGlyph)} CRLF guard: ${risk}; ${remediation}`);
4602
4868
  }
4603
- function reportCrlfGuardCheck(section2, run = execFileSync11) {
4869
+ function reportCrlfGuardCheck(section2, run = execFileSync13) {
4604
4870
  const repo = repoHome();
4605
4871
  if (hasGitattributesGuard(repo)) {
4606
4872
  addItem(section2, `${green(okGlyph)} CRLF guard: .gitattributes (* -text) present`);
@@ -4611,35 +4877,35 @@ function reportCrlfGuardCheck(section2, run = execFileSync11) {
4611
4877
 
4612
4878
  // src/spinner.ts
4613
4879
  init_color();
4614
- import { existsSync as existsSync32 } from "node:fs";
4880
+ import { existsSync as existsSync34 } from "node:fs";
4615
4881
  import { fileURLToPath as fileURLToPath4 } from "node:url";
4616
4882
  import { Worker } from "node:worker_threads";
4617
4883
 
4618
4884
  // src/commands.push.recovery.ts
4619
4885
  init_config();
4620
- import { readFileSync as readFileSync16, rmSync as rmSync12, writeFileSync as writeFileSync5 } from "node:fs";
4621
- import { join as join38 } from "node:path";
4886
+ import { readFileSync as readFileSync18, rmSync as rmSync15, writeFileSync as writeFileSync9 } from "node:fs";
4887
+ import { join as join43 } from "node:path";
4622
4888
  import { createInterface as createInterface2 } from "node:readline/promises";
4623
4889
 
4624
4890
  // src/commands.push.recovery.actions.ts
4625
4891
  init_config();
4626
- import { readFileSync as readFileSync15 } from "node:fs";
4627
- import { isAbsolute as isAbsolute2, resolve as resolve3, sep as sep6 } from "node:path";
4892
+ import { readFileSync as readFileSync17 } from "node:fs";
4893
+ import { isAbsolute as isAbsolute2, resolve as resolve3, sep as sep7 } from "node:path";
4628
4894
 
4629
4895
  // src/commands.push.recovery.redact.ts
4630
4896
  init_config();
4631
4897
  init_config_sharedDirs_guard();
4632
- import { cpSync as cpSync7, existsSync as existsSync31, mkdirSync as mkdirSync7, statSync as statSync8 } from "node:fs";
4633
- import { dirname as dirname8, join as join36, sep as sep5 } from "node:path";
4898
+ import { cpSync as cpSync8, existsSync as existsSync32, mkdirSync as mkdirSync10, statSync as statSync8 } from "node:fs";
4899
+ import { dirname as dirname10, join as join39, sep as sep5 } from "node:path";
4634
4900
 
4635
4901
  // src/commands.redact.ts
4636
4902
  init_config();
4637
4903
  import { existsSync as existsSync30, statSync as statSync7 } from "node:fs";
4638
- import { dirname as dirname7, join as join35 } from "node:path";
4904
+ import { dirname as dirname9, join as join37 } from "node:path";
4639
4905
 
4640
4906
  // src/commands.redact.subtree.ts
4641
- import { existsSync as existsSync29, lstatSync as lstatSync11, readFileSync as readFileSync14, readdirSync as readdirSync13, statSync as statSync6, writeFileSync as writeFileSync4 } from "node:fs";
4642
- import { join as join34 } from "node:path";
4907
+ import { existsSync as existsSync29, lstatSync as lstatSync11, readFileSync as readFileSync14, readdirSync as readdirSync13, statSync as statSync6, writeFileSync as writeFileSync6 } from "node:fs";
4908
+ import { join as join36 } from "node:path";
4643
4909
  init_utils_fs();
4644
4910
  init_utils();
4645
4911
  function collectFiles(dir, out) {
@@ -4647,7 +4913,7 @@ function collectFiles(dir, out) {
4647
4913
  const st = lstatSync11(dir);
4648
4914
  if (!st.isDirectory()) return;
4649
4915
  for (const entry of readdirSync13(dir)) {
4650
- const abs = join34(dir, entry);
4916
+ const abs = join36(dir, entry);
4651
4917
  const lst = lstatSync11(abs);
4652
4918
  if (lst.isSymbolicLink()) continue;
4653
4919
  if (lst.isDirectory()) {
@@ -4691,7 +4957,7 @@ function applySubtreeRedactions(mainPath, mainFindings, subtreeFiles, rule, ts,
4691
4957
  `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.`
4692
4958
  );
4693
4959
  }
4694
- writeFileSync4(filePath, after, "utf8");
4960
+ writeFileSync6(filePath, after, "utf8");
4695
4961
  }
4696
4962
  }
4697
4963
  return { total, dirty };
@@ -4699,10 +4965,10 @@ function applySubtreeRedactions(mainPath, mainFindings, subtreeFiles, rule, ts,
4699
4965
 
4700
4966
  // src/commands.pushed-history.ts
4701
4967
  init_utils();
4702
- import { execFileSync as execFileSync12 } from "node:child_process";
4968
+ import { execFileSync as execFileSync14 } from "node:child_process";
4703
4969
  function pushedRef(repo) {
4704
4970
  try {
4705
- const ref = execFileSync12("git", ["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"], {
4971
+ const ref = execFileSync14("git", ["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"], {
4706
4972
  cwd: repo,
4707
4973
  stdio: ["ignore", "pipe", "ignore"]
4708
4974
  }).toString().trim();
@@ -4715,7 +4981,7 @@ function sessionInPushedHistory(id, repo) {
4715
4981
  const ref = pushedRef(repo);
4716
4982
  if (ref === null) return false;
4717
4983
  try {
4718
- const out = execFileSync12(
4984
+ const out = execFileSync14(
4719
4985
  "git",
4720
4986
  [
4721
4987
  "log",
@@ -4747,19 +5013,20 @@ function warnIfSessionPushed(id, repo) {
4747
5013
 
4748
5014
  // src/commands.redact.ts
4749
5015
  init_push_gitleaks_scan();
5016
+ init_exit_codes();
4750
5017
  init_utils_fs();
4751
5018
  init_utils_json();
4752
5019
  init_utils();
4753
5020
  function resolveLiveTranscript(id) {
4754
5021
  try {
4755
- const mapPath = join35(repoHome(), "path-map.json");
5022
+ const mapPath = join37(repoHome(), "path-map.json");
4756
5023
  if (!existsSync30(mapPath)) return null;
4757
5024
  const projects = readJson(mapPath).projects;
4758
5025
  const claude = claudeHome();
4759
5026
  for (const hostMap of Object.values(projects)) {
4760
5027
  const abs = hostMap[HOST];
4761
5028
  if (abs === void 0) continue;
4762
- const live = join35(claude, "projects", encodePath(abs), `${id}.jsonl`);
5029
+ const live = join37(claude, "projects", encodePath(abs), `${id}.jsonl`);
4763
5030
  if (existsSync30(live)) return live;
4764
5031
  }
4765
5032
  return null;
@@ -4776,7 +5043,7 @@ function cmdRedact(opts, nowMs = Date.now, scan = scanFile) {
4776
5043
  const { id, rule, dryRun = false, findings: rawFindings } = opts;
4777
5044
  if (id.length === 0 || id.length > 128 || !/^[A-Za-z0-9_-]+$/.test(id)) {
4778
5045
  fail(`invalid session id: ${id}`);
4779
- process.exit(1);
5046
+ process.exit(EXIT.USAGE);
4780
5047
  }
4781
5048
  const repo = repoHome();
4782
5049
  const backup = backupBase();
@@ -4790,7 +5057,7 @@ function cmdRedact(opts, nowMs = Date.now, scan = scanFile) {
4790
5057
  process.exitCode = 1;
4791
5058
  return;
4792
5059
  }
4793
- const sessionDir = join35(dirname7(localPath), id);
5060
+ const sessionDir = join37(dirname9(localPath), id);
4794
5061
  const subtreeFiles = listSubtreeFiles(sessionDir);
4795
5062
  const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync7(p).mtimeMs);
4796
5063
  if (isRecentlyModified(subtreeMtime, nowMs())) {
@@ -4850,6 +5117,78 @@ init_utils();
4850
5117
 
4851
5118
  // src/commands.push.recovery.seams.ts
4852
5119
  init_push_gitleaks();
5120
+
5121
+ // src/commands.push.recovery.memory.ts
5122
+ init_config();
5123
+ init_config_sharedDirs_guard();
5124
+ import { cpSync as cpSync7, existsSync as existsSync31, mkdirSync as mkdirSync9, readFileSync as readFileSync15, writeFileSync as writeFileSync7 } from "node:fs";
5125
+ import { join as join38 } from "node:path";
5126
+ init_push_gitleaks_scan();
5127
+ init_utils_fs();
5128
+ init_utils_json();
5129
+ init_utils();
5130
+ var MEMORY_FINDING_PATH = /^shared\/projects\/([^/]+)\/memory\/([^/]+\.md)$/;
5131
+ var SAFE_MEMORY_FILENAME = /^[^/\\]+\.md$/;
5132
+ var MEMORY_DIR_PATH = /^shared\/projects\/[^/]+\/memory\//;
5133
+ function memoryFileFromFinding(f) {
5134
+ const m = MEMORY_FINDING_PATH.exec(f.File);
5135
+ if (m?.[1] === void 0 || m[2] === void 0) return null;
5136
+ return { logical: m[1], filename: m[2] };
5137
+ }
5138
+ function isMemoryFindingPath(f) {
5139
+ return MEMORY_DIR_PATH.test(f.File);
5140
+ }
5141
+ function resolveMemoryLocalPath(logical, filename, map) {
5142
+ assertSafeLogical(logical);
5143
+ if (!SAFE_MEMORY_FILENAME.test(filename) || filename.includes("..")) return null;
5144
+ const abs = map.projects[logical]?.[HOST];
5145
+ if (abs === void 0) return null;
5146
+ const localPath = join38(claudeHome(), "projects", encodePath(abs), "memory", filename);
5147
+ return existsSync31(localPath) ? localPath : null;
5148
+ }
5149
+ function preflightMemoryRedactable(f, map) {
5150
+ const parsed = memoryFileFromFinding(f);
5151
+ if (parsed === null) return "a finding is not a project-level memory file";
5152
+ const localPath = resolveMemoryLocalPath(parsed.logical, parsed.filename, map);
5153
+ if (localPath === null) {
5154
+ return `memory file ${parsed.logical}/memory/${parsed.filename}: local file not found or unmapped`;
5155
+ }
5156
+ return null;
5157
+ }
5158
+ function applyMemoryRedact(f, ts, map, scan = scanFile) {
5159
+ const refuse = (msg) => {
5160
+ log(msg);
5161
+ return false;
5162
+ };
5163
+ const parsed = memoryFileFromFinding(f);
5164
+ if (parsed === null) {
5165
+ return refuse("could not parse this finding as a project-level memory file; choose Skip.");
5166
+ }
5167
+ const { logical, filename } = parsed;
5168
+ const localPath = resolveMemoryLocalPath(logical, filename, map);
5169
+ if (localPath === null) {
5170
+ return refuse(
5171
+ `could not locate the local memory file for ${logical}/memory/${filename}; choose Skip.`
5172
+ );
5173
+ }
5174
+ const findings = scan(localPath);
5175
+ if (findings === null) {
5176
+ return refuse(`re-scan of ${logical}/memory/${filename} failed; choose Skip.`);
5177
+ }
5178
+ if (findings.length === 0) {
5179
+ return refuse(`nothing to redact in ${logical}/memory/${filename}; choose Skip.`);
5180
+ }
5181
+ backupBeforeWrite(localPath, ts);
5182
+ const before = readFileSync15(localPath, "utf8");
5183
+ const after = applyRedactions(before, findings);
5184
+ writeFileSync7(localPath, after, "utf8");
5185
+ const stagedMemoryDir = join38(repoHome(), "shared", "projects", logical, "memory");
5186
+ mkdirSync9(stagedMemoryDir, { recursive: true });
5187
+ cpSync7(localPath, join38(stagedMemoryDir, filename), { force: true });
5188
+ return true;
5189
+ }
5190
+
5191
+ // src/commands.push.recovery.seams.ts
4853
5192
  var MASK_LEAD = 4;
4854
5193
  var MASK_BODY = "************";
4855
5194
  var CONTEXT_WINDOW = 40;
@@ -4858,8 +5197,10 @@ function findingKey(f) {
4858
5197
  return `${f.File}:${f.StartLine}:${f.StartColumn}:${f.RuleID}`;
4859
5198
  }
4860
5199
  var VALID_SID = /^[A-Za-z0-9_-]+$/;
5200
+ var SUBTREE_PATH = /^shared\/projects\/[^/]+\/([^/]+)\/.+$/;
4861
5201
  function sessionIdFromFinding(f) {
4862
- const m = SESSION_PATH.exec(f.File) ?? /^shared\/projects\/[^/]+\/([^/]+)\//.exec(f.File);
5202
+ if (isMemoryFindingPath(f)) return null;
5203
+ const m = SESSION_PATH.exec(f.File) ?? SUBTREE_PATH.exec(f.File);
4863
5204
  if (m === null) return null;
4864
5205
  const sid = m[1];
4865
5206
  return VALID_SID.test(sid) ? sid : null;
@@ -4906,8 +5247,8 @@ function resolveStagedDir(localPath, map, claude, repo) {
4906
5247
  assertSafeLogical(logical);
4907
5248
  const abs = hostMap[HOST];
4908
5249
  if (abs === void 0) continue;
4909
- if (localPath.startsWith(join36(claude, "projects", encodePath(abs)) + sep5)) {
4910
- return join36(repo, "shared", "projects", logical);
5250
+ if (localPath.startsWith(join39(claude, "projects", encodePath(abs)) + sep5)) {
5251
+ return join39(repo, "shared", "projects", logical);
4911
5252
  }
4912
5253
  }
4913
5254
  return null;
@@ -4917,7 +5258,7 @@ function preflightRedactable(f, map, nowMs) {
4917
5258
  if (sid === null) return "a finding has no resolvable session id (not a session transcript)";
4918
5259
  const localPath = resolveLiveTranscript(sid);
4919
5260
  if (localPath === null) return `session ${sid}: local transcript not found`;
4920
- const sessionDir = join36(dirname8(localPath), sid);
5261
+ const sessionDir = join39(dirname10(localPath), sid);
4921
5262
  const subtreeFiles = listSubtreeFiles(sessionDir);
4922
5263
  const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync8(p).mtimeMs);
4923
5264
  if (isRecentlyModified(subtreeMtime, nowMs())) {
@@ -4947,7 +5288,7 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
4947
5288
  `could not locate the local transcript for session ${sid}; choose Skip or Drop session.`
4948
5289
  );
4949
5290
  }
4950
- const sessionDir = join36(dirname8(localPath), sid);
5291
+ const sessionDir = join39(dirname10(localPath), sid);
4951
5292
  const subtreeFiles = listSubtreeFiles(sessionDir);
4952
5293
  const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync8(p).mtimeMs);
4953
5294
  if (isRecentlyModified(subtreeMtime, nowMs())) {
@@ -4980,28 +5321,163 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
4980
5321
  `nothing to redact in the local transcript for session ${sid}; choose Skip or Drop session.`
4981
5322
  );
4982
5323
  }
4983
- mkdirSync7(stagedProjectDir, { recursive: true });
4984
- cpSync7(localPath, join36(stagedProjectDir, `${sid}.jsonl`), { force: true });
4985
- if (existsSync31(sessionDir)) {
4986
- cpSync7(sessionDir, join36(stagedProjectDir, sid), { force: true, recursive: true });
5324
+ mkdirSync10(stagedProjectDir, { recursive: true });
5325
+ cpSync8(localPath, join39(stagedProjectDir, `${sid}.jsonl`), { force: true });
5326
+ if (existsSync32(sessionDir)) {
5327
+ cpSync8(sessionDir, join39(stagedProjectDir, sid), { force: true, recursive: true });
4987
5328
  }
4988
5329
  return true;
4989
5330
  }
4990
5331
 
4991
5332
  // src/commands.push.recovery.drop.ts
4992
5333
  init_config();
4993
- import { rmSync as rmSync11 } from "node:fs";
4994
- import { join as join37 } from "node:path";
5334
+ import { rmSync as rmSync13 } from "node:fs";
5335
+ import { join as join40 } from "node:path";
4995
5336
  function dropSessionFromStaged(sid, map) {
4996
5337
  const logicals = Object.keys(map.projects);
4997
5338
  if (logicals.length === 0) return false;
4998
5339
  const repo = repoHome();
4999
5340
  for (const logical of logicals) {
5000
- const jsonl = join37(repo, "shared", "projects", logical, `${sid}.jsonl`);
5001
- const dir = join37(repo, "shared", "projects", logical, sid);
5002
- rmSync11(jsonl, { force: true });
5003
- rmSync11(dir, { recursive: true, force: true });
5341
+ const jsonl = join40(repo, "shared", "projects", logical, `${sid}.jsonl`);
5342
+ const dir = join40(repo, "shared", "projects", logical, sid);
5343
+ rmSync13(jsonl, { force: true });
5344
+ rmSync13(dir, { recursive: true, force: true });
5345
+ }
5346
+ return true;
5347
+ }
5348
+
5349
+ // src/commands.push.recovery.skills.ts
5350
+ init_config();
5351
+ import {
5352
+ cpSync as cpSync9,
5353
+ lstatSync as lstatSync13,
5354
+ mkdirSync as mkdirSync12,
5355
+ readFileSync as readFileSync16,
5356
+ realpathSync as realpathSync4,
5357
+ statSync as statSync9,
5358
+ writeFileSync as writeFileSync8
5359
+ } from "node:fs";
5360
+ import { dirname as dirname11, join as join42, sep as sep6 } from "node:path";
5361
+
5362
+ // src/skills-sync.ts
5363
+ init_config();
5364
+ import { existsSync as existsSync33, lstatSync as lstatSync12, mkdirSync as mkdirSync11, readdirSync as readdirSync14, rmSync as rmSync14 } from "node:fs";
5365
+ import { join as join41 } from "node:path";
5366
+ init_utils_fs();
5367
+ function isGsdOwned(name) {
5368
+ return name.startsWith(GSD_PREFIX);
5369
+ }
5370
+ function isSkillExcluded(name) {
5371
+ return isGsdOwned(name) || isDeniedName(ALWAYS_NEVER_SYNC, name);
5372
+ }
5373
+ function copySkillsPush(src, dst) {
5374
+ const srcNames = readdirSync14(src, { encoding: "utf8" });
5375
+ const blockSet = /* @__PURE__ */ new Set([
5376
+ ...srcNames.filter((n) => isGsdOwned(n)),
5377
+ ...ALWAYS_NEVER_SYNC
5378
+ ]);
5379
+ copyExtrasFiltered(src, dst, blockSet);
5380
+ }
5381
+ function copySkillsPull(src, dst) {
5382
+ copyExtrasFilteredPreservingBy(src, dst, isSkillExcluded);
5383
+ }
5384
+ function syncSkillsPull(ts) {
5385
+ const sharedSkills = join41(repoHome(), "shared", "skills");
5386
+ if (!existsSync33(sharedSkills)) return;
5387
+ const localSkills = join41(claudeHome(), "skills");
5388
+ const dstStat = lstatSync12(localSkills, { throwIfNoEntry: false });
5389
+ if (dstStat?.isSymbolicLink() === true) {
5390
+ backupBeforeWrite(localSkills, ts);
5391
+ rmSync14(localSkills, { recursive: true, force: true });
5392
+ mkdirSync11(localSkills, { recursive: true });
5393
+ }
5394
+ copySkillsPull(sharedSkills, localSkills);
5395
+ }
5396
+ function syncSkillsPush() {
5397
+ const localSkills = join41(claudeHome(), "skills");
5398
+ const stat = lstatSync12(localSkills, { throwIfNoEntry: false });
5399
+ if (stat === void 0) return;
5400
+ if (stat.isSymbolicLink()) return;
5401
+ const sharedSkills = join41(repoHome(), "shared", "skills");
5402
+ copySkillsPush(localSkills, sharedSkills);
5403
+ }
5404
+
5405
+ // src/commands.push.recovery.skills.ts
5406
+ init_push_gitleaks_scan();
5407
+ init_utils_fs();
5408
+ init_utils();
5409
+ var SKILL_FINDING_PATH = /^shared\/skills\/([^/]+)\/(.+)$/;
5410
+ var SAFE_SKILL_NAME = /^[A-Za-z0-9._-]+$/;
5411
+ var SKILL_DIR_PATH = /^shared\/skills\/[^/]+\//;
5412
+ function skillFileFromFinding(f) {
5413
+ const m = SKILL_FINDING_PATH.exec(f.File);
5414
+ if (m?.[1] === void 0 || m[2] === void 0) return null;
5415
+ return { name: m[1], relPath: m[2] };
5416
+ }
5417
+ function isSkillFindingPath(f) {
5418
+ return SKILL_DIR_PATH.test(f.File);
5419
+ }
5420
+ function isSafeRelPath(relPath) {
5421
+ if (relPath.length === 0 || relPath.startsWith("/") || relPath.includes("\\")) return false;
5422
+ const segments = relPath.split("/");
5423
+ return segments.every((s) => s.length > 0 && s !== "." && s !== "..");
5424
+ }
5425
+ function resolveSkillLocalPath(name, relPath) {
5426
+ if (!SAFE_SKILL_NAME.test(name) || name === "." || name === "..") return null;
5427
+ if (isGsdOwned(name)) return null;
5428
+ if (!isSafeRelPath(relPath)) return null;
5429
+ const skillsRoot = join42(claudeHome(), "skills");
5430
+ const skillRoot = join42(skillsRoot, name);
5431
+ const localPath = join42(skillRoot, ...relPath.split("/"));
5432
+ if (localPath !== skillRoot && !localPath.startsWith(skillRoot + sep6)) return null;
5433
+ try {
5434
+ if (lstatSync13(localPath).isSymbolicLink()) return null;
5435
+ const realLocal = realpathSync4(localPath);
5436
+ const realRoot = realpathSync4(skillsRoot);
5437
+ if (!realLocal.startsWith(realRoot + sep6)) return null;
5438
+ if (!statSync9(realLocal).isFile()) return null;
5439
+ } catch {
5440
+ return null;
5441
+ }
5442
+ return localPath;
5443
+ }
5444
+ function preflightSkillRedactable(f) {
5445
+ const parsed = skillFileFromFinding(f);
5446
+ if (parsed === null) return "a finding is not a skill file";
5447
+ const localPath = resolveSkillLocalPath(parsed.name, parsed.relPath);
5448
+ if (localPath === null) {
5449
+ return `skill file ${parsed.name}/${parsed.relPath}: local file not found or unresolvable`;
5004
5450
  }
5451
+ return null;
5452
+ }
5453
+ function applySkillRedact(f, ts, scan = scanFile) {
5454
+ const refuse = (msg) => {
5455
+ log(msg);
5456
+ return false;
5457
+ };
5458
+ const parsed = skillFileFromFinding(f);
5459
+ if (parsed === null) {
5460
+ return refuse("could not parse this finding as a skill file; choose Skip.");
5461
+ }
5462
+ const { name, relPath } = parsed;
5463
+ const localPath = resolveSkillLocalPath(name, relPath);
5464
+ if (localPath === null) {
5465
+ return refuse(`could not locate the local skill file for ${name}/${relPath}; choose Skip.`);
5466
+ }
5467
+ const findings = scan(localPath);
5468
+ if (findings === null) {
5469
+ return refuse(`re-scan of ${name}/${relPath} failed; choose Skip.`);
5470
+ }
5471
+ if (findings.length === 0) {
5472
+ return refuse(`nothing to redact in ${name}/${relPath}; choose Skip.`);
5473
+ }
5474
+ backupBeforeWrite(localPath, ts);
5475
+ const before = readFileSync16(localPath, "utf8");
5476
+ const after = applyRedactions(before, findings);
5477
+ writeFileSync8(localPath, after, "utf8");
5478
+ const dest = join42(repoHome(), "shared", "skills", name, ...relPath.split("/"));
5479
+ mkdirSync12(dirname11(dest), { recursive: true });
5480
+ cpSync9(localPath, dest, { force: true });
5005
5481
  return true;
5006
5482
  }
5007
5483
 
@@ -5031,10 +5507,10 @@ function makeDefaultReadLine(repo) {
5031
5507
  try {
5032
5508
  const repoRoot = resolve3(repo);
5033
5509
  const target = resolve3(repoRoot, file);
5034
- if (isAbsolute2(file) || target !== repoRoot && !target.startsWith(repoRoot + sep6)) {
5510
+ if (isAbsolute2(file) || target !== repoRoot && !target.startsWith(repoRoot + sep7)) {
5035
5511
  return null;
5036
5512
  }
5037
- const content = readFileSync15(target, "utf8");
5513
+ const content = readFileSync17(target, "utf8");
5038
5514
  const lines = content.split(/\r?\n/);
5039
5515
  const idx = line - 1;
5040
5516
  if (idx < 0 || idx >= lines.length) return null;
@@ -5057,6 +5533,40 @@ Finding: ${f.RuleID} in ${f.File} line ${f.StartLine}` + (sid === null ? "" : `
5057
5533
  }
5058
5534
  return actions;
5059
5535
  }
5536
+ function dispatchMemory(f, action, ctx) {
5537
+ const parsed = memoryFileFromFinding(f);
5538
+ if (parsed === null) {
5539
+ log(`memory path not auto-redactable: ${f.File}; scrub it by hand or choose Skip`);
5540
+ return;
5541
+ }
5542
+ if (action === "drop") {
5543
+ log("memory files cannot be dropped; use Redact or Skip");
5544
+ return;
5545
+ }
5546
+ const memKey = `${parsed.logical}/${parsed.filename}`;
5547
+ if (ctx.redactedMemory.has(memKey)) return;
5548
+ if (applyMemoryRedact(f, ctx.ts, ctx.map, ctx.scan)) ctx.redactedMemory.add(memKey);
5549
+ }
5550
+ function dispatchSkill(f, action, ctx) {
5551
+ const parsed = skillFileFromFinding(f);
5552
+ if (parsed === null) return;
5553
+ if (action === "drop") {
5554
+ log("skill files cannot be dropped; use Redact or Skip");
5555
+ return;
5556
+ }
5557
+ const skillKey = `${parsed.name}/${parsed.relPath}`;
5558
+ if (ctx.redactedSkills.has(skillKey)) return;
5559
+ if (applySkillRedact(f, ctx.ts, ctx.scan)) ctx.redactedSkills.add(skillKey);
5560
+ }
5561
+ function dispatchNonSession(f, action, ctx) {
5562
+ if (isMemoryFindingPath(f)) {
5563
+ dispatchMemory(f, action, ctx);
5564
+ return;
5565
+ }
5566
+ if (isSkillFindingPath(f)) {
5567
+ dispatchSkill(f, action, ctx);
5568
+ }
5569
+ }
5060
5570
  function dispatchOne(f, ctx) {
5061
5571
  const action = ctx.actions.get(findingKey(f)) ?? "skip";
5062
5572
  if (action === "skip") return;
@@ -5066,7 +5576,10 @@ function dispatchOne(f, ctx) {
5066
5576
  applyAllow(f, ctx.repo);
5067
5577
  return;
5068
5578
  }
5069
- if (sid === null) return;
5579
+ if (sid === null) {
5580
+ dispatchNonSession(f, action, ctx);
5581
+ return;
5582
+ }
5070
5583
  if (action === "drop") {
5071
5584
  ctx.droppedSids.add(sid);
5072
5585
  if (ctx.drop(sid, ctx.map)) {
@@ -5091,20 +5604,58 @@ function dispatchActions(findings, actions, opts) {
5091
5604
  scan,
5092
5605
  drop,
5093
5606
  redactedSids: /* @__PURE__ */ new Set(),
5094
- droppedSids: /* @__PURE__ */ new Set()
5607
+ droppedSids: /* @__PURE__ */ new Set(),
5608
+ redactedMemory: /* @__PURE__ */ new Set(),
5609
+ redactedSkills: /* @__PURE__ */ new Set()
5095
5610
  };
5096
5611
  for (const f of findings) {
5097
5612
  dispatchOne(f, ctx);
5098
5613
  }
5099
5614
  }
5615
+ function redactAllDedupeKey(f) {
5616
+ const sid = sessionIdFromFinding(f);
5617
+ if (sid !== null) return sid;
5618
+ const memParsed = memoryFileFromFinding(f);
5619
+ if (memParsed !== null) return `${memParsed.logical}/${memParsed.filename}`;
5620
+ const skillParsed = skillFileFromFinding(f);
5621
+ if (skillParsed !== null) return `skill:${skillParsed.name}/${skillParsed.relPath}`;
5622
+ return findingKey(f);
5623
+ }
5624
+ function redactAllPreflightOne(f, map, nowMs) {
5625
+ if (sessionIdFromFinding(f) === null) {
5626
+ if (memoryFileFromFinding(f) !== null) return preflightMemoryRedactable(f, map);
5627
+ if (skillFileFromFinding(f) !== null) return preflightSkillRedactable(f);
5628
+ }
5629
+ return preflightRedactable(f, map, nowMs);
5630
+ }
5631
+ function redactAllOne(f, ts, map, nowMs, scan, dedupe) {
5632
+ const sid = sessionIdFromFinding(f);
5633
+ if (sid !== null) {
5634
+ if (dedupe.redactedSids.has(sid)) return;
5635
+ if (applyRedact(f, ts, map, nowMs, scan)) dedupe.redactedSids.add(sid);
5636
+ return;
5637
+ }
5638
+ const memParsed = memoryFileFromFinding(f);
5639
+ if (memParsed !== null) {
5640
+ const memKey = `${memParsed.logical}/${memParsed.filename}`;
5641
+ if (dedupe.redactedMemory.has(memKey)) return;
5642
+ if (applyMemoryRedact(f, ts, map, scan)) dedupe.redactedMemory.add(memKey);
5643
+ return;
5644
+ }
5645
+ const skillParsed = skillFileFromFinding(f);
5646
+ if (skillParsed === null) return;
5647
+ const skillKey = `${skillParsed.name}/${skillParsed.relPath}`;
5648
+ if (dedupe.redactedSkills.has(skillKey)) return;
5649
+ if (applySkillRedact(f, ts, scan)) dedupe.redactedSkills.add(skillKey);
5650
+ }
5100
5651
  function redactAllFindings(findings, ts, map, nowMs, scan = scanFile) {
5101
5652
  const refusals = [];
5102
5653
  const preflighted = /* @__PURE__ */ new Set();
5103
5654
  for (const f of findings) {
5104
- const dedupeKey = sessionIdFromFinding(f) ?? findingKey(f);
5655
+ const dedupeKey = redactAllDedupeKey(f);
5105
5656
  if (preflighted.has(dedupeKey)) continue;
5106
5657
  preflighted.add(dedupeKey);
5107
- const reason = preflightRedactable(f, map, nowMs);
5658
+ const reason = redactAllPreflightOne(f, map, nowMs);
5108
5659
  if (reason !== null) refusals.push(reason);
5109
5660
  }
5110
5661
  if (refusals.length > 0) {
@@ -5114,15 +5665,18 @@ function redactAllFindings(findings, ts, map, nowMs, scan = scanFile) {
5114
5665
  Re-run without --redact-all to triage these interactively (Drop session / Skip), or end any active session and retry.`
5115
5666
  );
5116
5667
  }
5117
- const redactedSids = /* @__PURE__ */ new Set();
5668
+ const dedupe = {
5669
+ redactedSids: /* @__PURE__ */ new Set(),
5670
+ redactedMemory: /* @__PURE__ */ new Set(),
5671
+ redactedSkills: /* @__PURE__ */ new Set()
5672
+ };
5118
5673
  for (const f of findings) {
5119
- const sid = sessionIdFromFinding(f);
5120
- if (sid === null || redactedSids.has(sid)) continue;
5121
- if (applyRedact(f, ts, map, nowMs, scan)) redactedSids.add(sid);
5674
+ redactAllOne(f, ts, map, nowMs, scan, dedupe);
5122
5675
  }
5123
5676
  }
5124
5677
 
5125
5678
  // src/commands.push.recovery.ts
5679
+ init_exit_codes();
5126
5680
  init_push_gitleaks_scan();
5127
5681
  init_push_gitleaks();
5128
5682
  init_utils();
@@ -5150,15 +5704,15 @@ function applyThenRescan(scanVerdict, repoHome2) {
5150
5704
  const next = scanVerdict(repoHome2);
5151
5705
  if (next.leak) {
5152
5706
  const { bySession, other } = partitionFindings(next.findings);
5153
- throw new NomadFatal(buildSessionAwareFatal(bySession, other));
5707
+ throw new NomadFatal(buildSessionAwareFatal(bySession, other), { code: EXIT.LEAK_BLOCKED });
5154
5708
  }
5155
5709
  return next;
5156
5710
  }
5157
5711
  function allowThenRescan(append, scanVerdict, repoHome2) {
5158
- const ignPath = join38(repoHome2, ".gitleaksignore");
5712
+ const ignPath = join43(repoHome2, ".gitleaksignore");
5159
5713
  let before;
5160
5714
  try {
5161
- before = readFileSync16(ignPath, "utf8");
5715
+ before = readFileSync18(ignPath, "utf8");
5162
5716
  } catch {
5163
5717
  before = null;
5164
5718
  }
@@ -5166,8 +5720,8 @@ function allowThenRescan(append, scanVerdict, repoHome2) {
5166
5720
  try {
5167
5721
  return applyThenRescan(scanVerdict, repoHome2);
5168
5722
  } catch (err) {
5169
- if (before === null) rmSync12(ignPath, { force: true });
5170
- else writeFileSync5(ignPath, before, "utf8");
5723
+ if (before === null) rmSync15(ignPath, { force: true });
5724
+ else writeFileSync9(ignPath, before, "utf8");
5171
5725
  throw err;
5172
5726
  }
5173
5727
  }
@@ -5217,7 +5771,9 @@ async function resolveLeakFindings(verdict, ts, map, deps = {}) {
5217
5771
  );
5218
5772
  }
5219
5773
  if (!isTTYCheck()) {
5220
- throw new NomadFatal(current.recovery ?? "gitleaks detected secrets");
5774
+ throw new NomadFatal(current.recovery ?? "gitleaks detected secrets", {
5775
+ code: EXIT.LEAK_BLOCKED
5776
+ });
5221
5777
  }
5222
5778
  const prompt = makePromptFn();
5223
5779
  printLegend();
@@ -5226,7 +5782,7 @@ async function resolveLeakFindings(verdict, ts, map, deps = {}) {
5226
5782
  if (hasUnresolved(actions)) {
5227
5783
  const unresolved = current.findings.filter((f) => actions.get(findingKey(f)) === "skip");
5228
5784
  const { bySession, other } = partitionFindings(unresolved);
5229
- throw new NomadFatal(buildSessionAwareFatal(bySession, other));
5785
+ throw new NomadFatal(buildSessionAwareFatal(bySession, other), { code: EXIT.LEAK_BLOCKED });
5230
5786
  }
5231
5787
  dispatchActions(current.findings, actions, { ts, map, nowMs, repo, scan });
5232
5788
  gitOrFatal(["add", "-A"], "git add", repo);
@@ -5254,7 +5810,7 @@ function writeAnimatedDone(out, label, ms, useTTY) {
5254
5810
  `);
5255
5811
  }
5256
5812
  function resolveWorkerPath(deps = {}) {
5257
- const check = deps.existsSyncFn ?? existsSync32;
5813
+ const check = deps.existsSyncFn ?? existsSync34;
5258
5814
  const base = deps.baseUrl ?? import.meta.url;
5259
5815
  const mjs = fileURLToPath4(new URL("./nomad.worker.mjs", base));
5260
5816
  if (check(mjs)) return mjs;
@@ -5320,9 +5876,9 @@ function withSpinner(label, fn, deps) {
5320
5876
 
5321
5877
  // src/commands.doctor.gitleaks-version.ts
5322
5878
  init_color();
5323
- import { execFileSync as execFileSync13 } from "node:child_process";
5324
- import { existsSync as existsSync33 } from "node:fs";
5325
- import { join as join39 } from "node:path";
5879
+ import { execFileSync as execFileSync15 } from "node:child_process";
5880
+ import { existsSync as existsSync35 } from "node:fs";
5881
+ import { join as join44 } from "node:path";
5326
5882
  init_config();
5327
5883
  var SEMVER_MAJOR_MINOR = /^(\d+)\.(\d+)\.\d+$/;
5328
5884
  var GITLEAKS_TIMEOUT_MS = 5e3;
@@ -5331,7 +5887,7 @@ function majorMinorOf(value) {
5331
5887
  return m === null ? null : [m[1], m[2]];
5332
5888
  }
5333
5889
  function readGitleaksVersion(run, tomlExists) {
5334
- const tomlPath = join39(repoHome(), ".gitleaks.toml");
5890
+ const tomlPath = join44(repoHome(), ".gitleaks.toml");
5335
5891
  const args = ["version"];
5336
5892
  if (tomlExists(tomlPath)) args.push("--config", tomlPath);
5337
5893
  try {
@@ -5343,7 +5899,7 @@ function readGitleaksVersion(run, tomlExists) {
5343
5899
  return null;
5344
5900
  }
5345
5901
  }
5346
- function reportGitleaksVersionCheck(section2, run = execFileSync13, tomlExists = existsSync33) {
5902
+ function reportGitleaksVersionCheck(section2, run = execFileSync15, tomlExists = existsSync35) {
5347
5903
  const raw = readGitleaksVersion(run, tomlExists);
5348
5904
  if (raw === null) return;
5349
5905
  const local = majorMinorOf(raw);
@@ -5363,7 +5919,7 @@ function reportGitleaksVersionCheck(section2, run = execFileSync13, tomlExists =
5363
5919
 
5364
5920
  // src/commands.doctor.checks.deps.ts
5365
5921
  init_color();
5366
- import { execFileSync as execFileSync14 } from "node:child_process";
5922
+ import { execFileSync as execFileSync16 } from "node:child_process";
5367
5923
  var VERSION_TOKEN = /(\d{1,9}\.\d{1,9}\.\d{1,9})/;
5368
5924
  var PROBE_TIMEOUT_MS3 = 3e3;
5369
5925
  var FETCHER_BASE = "HTTP fetcher";
@@ -5400,7 +5956,7 @@ function reportFetcherRow(section2, run) {
5400
5956
  );
5401
5957
  }
5402
5958
  }
5403
- function reportOptionalDeps(section2, run = execFileSync14) {
5959
+ function reportOptionalDeps(section2, run = execFileSync16) {
5404
5960
  const gh = probeOptionalDep("gh", run);
5405
5961
  if (gh.status === "present") {
5406
5962
  addItem(section2, `${green(okGlyph)} gh: ${gh.version ?? "present"}`);
@@ -5415,11 +5971,11 @@ function reportOptionalDeps(section2, run = execFileSync14) {
5415
5971
 
5416
5972
  // src/commands.doctor.actions-drift.ts
5417
5973
  init_color();
5418
- import { execFileSync as execFileSync16 } from "node:child_process";
5974
+ import { execFileSync as execFileSync18 } from "node:child_process";
5419
5975
  init_config();
5420
5976
 
5421
5977
  // src/gh-actions.ts
5422
- import { execFileSync as execFileSync15 } from "node:child_process";
5978
+ import { execFileSync as execFileSync17 } from "node:child_process";
5423
5979
  var GH_TIMEOUT_MS = 5e3;
5424
5980
  function parseGitHubRemote(remoteUrl) {
5425
5981
  const normalized = remoteUrl.trim().replace(/\/$/, "");
@@ -5427,7 +5983,7 @@ function parseGitHubRemote(remoteUrl) {
5427
5983
  if (m === null) return null;
5428
5984
  return { owner: m[1], repo: m[2] };
5429
5985
  }
5430
- function ghAuthStatus(run = execFileSync15) {
5986
+ function ghAuthStatus(run = execFileSync17) {
5431
5987
  try {
5432
5988
  run("gh", ["auth", "status"], {
5433
5989
  stdio: ["ignore", "ignore", "ignore"],
@@ -5441,7 +5997,7 @@ function ghAuthStatus(run = execFileSync15) {
5441
5997
  return "gh-probe-error";
5442
5998
  }
5443
5999
  }
5444
- function isRepoPrivate(ref, run = execFileSync15) {
6000
+ function isRepoPrivate(ref, run = execFileSync17) {
5445
6001
  const out = run("gh", ["repo", "view", `${ref.owner}/${ref.repo}`, "--json", "isPrivate"], {
5446
6002
  stdio: ["ignore", "pipe", "ignore"],
5447
6003
  timeout: GH_TIMEOUT_MS
@@ -5449,7 +6005,7 @@ function isRepoPrivate(ref, run = execFileSync15) {
5449
6005
  const parsed = JSON.parse(out);
5450
6006
  return parsed.isPrivate === true;
5451
6007
  }
5452
- function isActionsEnabled(ref, run = execFileSync15) {
6008
+ function isActionsEnabled(ref, run = execFileSync17) {
5453
6009
  const out = run(
5454
6010
  "gh",
5455
6011
  ["api", `repos/${ref.owner}/${ref.repo}/actions/permissions`, "--jq", ".enabled"],
@@ -5457,7 +6013,7 @@ function isActionsEnabled(ref, run = execFileSync15) {
5457
6013
  ).toString().trim();
5458
6014
  return out === "true";
5459
6015
  }
5460
- function disableActions(ref, run = execFileSync15) {
6016
+ function disableActions(ref, run = execFileSync17) {
5461
6017
  run(
5462
6018
  "gh",
5463
6019
  [
@@ -5471,7 +6027,7 @@ function disableActions(ref, run = execFileSync15) {
5471
6027
  { stdio: ["ignore", "ignore", "pipe"], timeout: GH_TIMEOUT_MS }
5472
6028
  );
5473
6029
  }
5474
- function readOriginRemote(cwd, run = execFileSync15) {
6030
+ function readOriginRemote(cwd, run = execFileSync17) {
5475
6031
  return run("git", ["remote", "get-url", "origin"], {
5476
6032
  cwd,
5477
6033
  stdio: ["ignore", "pipe", "ignore"]
@@ -5479,7 +6035,7 @@ function readOriginRemote(cwd, run = execFileSync15) {
5479
6035
  }
5480
6036
 
5481
6037
  // src/commands.doctor.actions-drift.ts
5482
- function reportActionsDrift(section2, run = execFileSync16) {
6038
+ function reportActionsDrift(section2, run = execFileSync18) {
5483
6039
  let remote;
5484
6040
  try {
5485
6041
  remote = readOriginRemote(repoHome(), run);
@@ -5569,8 +6125,8 @@ function gatherDoctorSections(opts) {
5569
6125
  reportLongPathsCheck(host);
5570
6126
  reportCrlfGuardCheck(host);
5571
6127
  const links = section("Shared links");
5572
- const mapPath = join40(repoHome(), "path-map.json");
5573
- const rawMap = existsSync34(mapPath) ? readJsonSafe(mapPath, mapPath, links) : null;
6128
+ const mapPath = join45(repoHome(), "path-map.json");
6129
+ const rawMap = existsSync36(mapPath) ? readJsonSafe(mapPath, mapPath, links) : null;
5574
6130
  const map = rawMap ?? { projects: {} };
5575
6131
  reportSharedLinks(links, map);
5576
6132
  reportDroppedNamesMigration(links);
@@ -5666,15 +6222,15 @@ function parseDoctorArgs(args) {
5666
6222
 
5667
6223
  // src/commands.drop-session.ts
5668
6224
  init_config();
5669
- import { execFileSync as execFileSync18 } from "node:child_process";
5670
- import { existsSync as existsSync36, readdirSync as readdirSync14, statSync as statSync9 } from "node:fs";
5671
- import { join as join42, relative as relative6 } from "node:path";
6225
+ import { execFileSync as execFileSync20 } from "node:child_process";
6226
+ import { existsSync as existsSync38, readdirSync as readdirSync15, statSync as statSync10 } from "node:fs";
6227
+ import { join as join47, relative as relative6 } from "node:path";
5672
6228
 
5673
6229
  // src/commands.drop-session.git.ts
5674
- import { execFileSync as execFileSync17 } from "node:child_process";
6230
+ import { execFileSync as execFileSync19 } from "node:child_process";
5675
6231
  function expandStagedDir(dirRel, repo) {
5676
6232
  try {
5677
- const out = execFileSync17("git", ["ls-files", "-z", "--", dirRel], {
6233
+ const out = execFileSync19("git", ["ls-files", "-z", "--", dirRel], {
5678
6234
  cwd: repo,
5679
6235
  stdio: ["ignore", "pipe", "pipe"]
5680
6236
  });
@@ -5686,7 +6242,7 @@ function expandStagedDir(dirRel, repo) {
5686
6242
  function isTrackedInHead(rel, repo) {
5687
6243
  try {
5688
6244
  const treePath = process.platform === "win32" ? rel.replaceAll("\\", "/") : rel;
5689
- execFileSync17("git", ["cat-file", "-e", `HEAD:${treePath}`], {
6245
+ execFileSync19("git", ["cat-file", "-e", `HEAD:${treePath}`], {
5690
6246
  cwd: repo,
5691
6247
  stdio: ["ignore", "pipe", "pipe"]
5692
6248
  });
@@ -5697,7 +6253,7 @@ function isTrackedInHead(rel, repo) {
5697
6253
  }
5698
6254
  function isInIndex(rel, repo) {
5699
6255
  try {
5700
- const out = execFileSync17("git", ["ls-files", "--", rel], {
6256
+ const out = execFileSync19("git", ["ls-files", "--", rel], {
5701
6257
  cwd: repo,
5702
6258
  stdio: ["ignore", "pipe", "pipe"]
5703
6259
  });
@@ -5711,8 +6267,8 @@ function isInIndex(rel, repo) {
5711
6267
  init_config();
5712
6268
  init_utils();
5713
6269
  init_utils_json();
5714
- import { existsSync as existsSync35 } from "node:fs";
5715
- import { join as join41 } from "node:path";
6270
+ import { existsSync as existsSync37 } from "node:fs";
6271
+ import { join as join46 } from "node:path";
5716
6272
  var SHARED_PROJECT_LOGICAL = /^shared\/projects\/([^/]+)\//;
5717
6273
  function reportScrubHint(id, matches) {
5718
6274
  const live = resolveLiveTranscript2(id, matches);
@@ -5728,8 +6284,8 @@ function reportScrubHint(id, matches) {
5728
6284
  }
5729
6285
  function resolveLiveTranscript2(id, matches) {
5730
6286
  try {
5731
- const mapPath = join41(repoHome(), "path-map.json");
5732
- if (!existsSync35(mapPath)) return null;
6287
+ const mapPath = join46(repoHome(), "path-map.json");
6288
+ if (!existsSync37(mapPath)) return null;
5733
6289
  const projects = readJson(mapPath).projects;
5734
6290
  const claude = claudeHome();
5735
6291
  for (const rel of matches) {
@@ -5737,8 +6293,8 @@ function resolveLiveTranscript2(id, matches) {
5737
6293
  if (logical === void 0) continue;
5738
6294
  const abs = projects[logical]?.[HOST];
5739
6295
  if (abs === void 0) continue;
5740
- const live = join41(claude, "projects", encodePath(abs), `${id}.jsonl`);
5741
- if (existsSync35(live)) return live;
6296
+ const live = join46(claude, "projects", encodePath(abs), `${id}.jsonl`);
6297
+ if (existsSync37(live)) return live;
5742
6298
  }
5743
6299
  return null;
5744
6300
  } catch {
@@ -5754,12 +6310,12 @@ function cmdDropSession(id) {
5754
6310
  process.exit(1);
5755
6311
  }
5756
6312
  const repo = repoHome();
5757
- if (!existsSync36(repo)) die(`repo not cloned at ${repo}`);
6313
+ if (!existsSync38(repo)) die(`repo not cloned at ${repo}`);
5758
6314
  const handle = acquireLock("drop-session");
5759
6315
  if (handle === null) process.exit(0);
5760
6316
  try {
5761
- const repoProjects = join42(repo, "shared", "projects");
5762
- if (!existsSync36(repoProjects)) {
6317
+ const repoProjects = join47(repo, "shared", "projects");
6318
+ if (!existsSync38(repoProjects)) {
5763
6319
  throw new NomadFatal(`no staged session matches ${id}`);
5764
6320
  }
5765
6321
  const matches = collectMatches(repoProjects, id, repo);
@@ -5774,20 +6330,20 @@ function cmdDropSession(id) {
5774
6330
  throw err;
5775
6331
  }
5776
6332
  fail(err.message);
5777
- process.exitCode = 1;
6333
+ process.exitCode = err.code;
5778
6334
  } finally {
5779
6335
  releaseLock(handle);
5780
6336
  }
5781
6337
  }
5782
6338
  function collectMatches(repoProjects, id, repo) {
5783
6339
  const matches = [];
5784
- for (const logical of readdirSync14(repoProjects)) {
5785
- const candidate = join42(repoProjects, logical, `${id}.jsonl`);
5786
- if (existsSync36(candidate)) {
6340
+ for (const logical of readdirSync15(repoProjects)) {
6341
+ const candidate = join47(repoProjects, logical, `${id}.jsonl`);
6342
+ if (existsSync38(candidate)) {
5787
6343
  matches.push(relative6(repo, candidate));
5788
6344
  }
5789
- const dir = join42(repoProjects, logical, id);
5790
- if (existsSync36(dir) && statSync9(dir).isDirectory()) {
6345
+ const dir = join47(repoProjects, logical, id);
6346
+ if (existsSync38(dir) && statSync10(dir).isDirectory()) {
5791
6347
  const dirRel = relative6(repo, dir);
5792
6348
  const staged = expandStagedDir(dirRel, repo);
5793
6349
  if (staged.length > 0) matches.push(...staged);
@@ -5803,12 +6359,12 @@ function unstageOne(rel, repo) {
5803
6359
  }
5804
6360
  try {
5805
6361
  if (isTrackedInHead(rel, repo)) {
5806
- execFileSync18("git", ["restore", "--staged", "--worktree", "--", rel], {
6362
+ execFileSync20("git", ["restore", "--staged", "--worktree", "--", rel], {
5807
6363
  cwd: repo,
5808
6364
  stdio: ["ignore", "pipe", "pipe"]
5809
6365
  });
5810
6366
  } else {
5811
- execFileSync18("git", ["rm", "--cached", "-f", "--", rel], {
6367
+ execFileSync20("git", ["rm", "--cached", "-f", "--", rel], {
5812
6368
  cwd: repo,
5813
6369
  stdio: ["ignore", "pipe", "pipe"]
5814
6370
  });
@@ -5822,8 +6378,8 @@ function unstageOne(rel, repo) {
5822
6378
  }
5823
6379
 
5824
6380
  // src/commands.pull.ts
5825
- import { existsSync as existsSync41, mkdirSync as mkdirSync10 } from "node:fs";
5826
- import { join as join48 } from "node:path";
6381
+ import { existsSync as existsSync42, mkdirSync as mkdirSync14 } from "node:fs";
6382
+ import { join as join52 } from "node:path";
5827
6383
 
5828
6384
  // src/commands.push.sections.ts
5829
6385
  init_color();
@@ -5931,18 +6487,18 @@ init_config();
5931
6487
 
5932
6488
  // src/extras-sync.ts
5933
6489
  init_config();
5934
- import { existsSync as existsSync38, statSync as statSync10 } from "node:fs";
5935
- import { join as join45 } from "node:path";
6490
+ import { existsSync as existsSync40, statSync as statSync11 } from "node:fs";
6491
+ import { join as join50 } from "node:path";
5936
6492
  init_utils();
5937
6493
  init_utils_json();
5938
6494
 
5939
6495
  // src/extras-sync.remap.ts
5940
6496
  init_config();
5941
- import { existsSync as existsSync37, mkdirSync as mkdirSync8, readdirSync as readdirSync15, readFileSync as readFileSync17, realpathSync as realpathSync4, rmSync as rmSync13 } from "node:fs";
5942
- import { dirname as dirname9, join as join44, sep as sep8 } from "node:path";
6497
+ import { existsSync as existsSync39, mkdirSync as mkdirSync13, readdirSync as readdirSync16, readFileSync as readFileSync19, realpathSync as realpathSync5, rmSync as rmSync16 } from "node:fs";
6498
+ import { dirname as dirname12, join as join49, sep as sep9 } from "node:path";
5943
6499
 
5944
6500
  // src/extras-sync.planning-diff.ts
5945
- import { join as join43, normalize as normalize2, sep as sep7 } from "node:path";
6501
+ import { join as join48, normalize as normalize2, sep as sep8 } from "node:path";
5946
6502
  init_utils();
5947
6503
  function processRecord(fields, i, changed, deleted) {
5948
6504
  const status = fields[i];
@@ -5994,15 +6550,15 @@ function planningDeleteTargets(opts) {
5994
6550
  const { deleted } = parsePlanningDiff(raw);
5995
6551
  const logicalPrefix = "shared/extras/" + logical + "/";
5996
6552
  const prefix = logicalPrefix + ".planning/";
5997
- const planningRoot = join43(localRoot, ".planning");
5998
- const planningRootBoundary = planningRoot + sep7;
6553
+ const planningRoot = join48(localRoot, ".planning");
6554
+ const planningRootBoundary = planningRoot + sep8;
5999
6555
  const targets = [];
6000
6556
  for (const repoPath of deleted) {
6001
6557
  if (!repoPath.startsWith(prefix)) {
6002
6558
  continue;
6003
6559
  }
6004
6560
  const remainder = repoPath.slice(logicalPrefix.length);
6005
- const candidate = join43(localRoot, remainder);
6561
+ const candidate = join48(localRoot, remainder);
6006
6562
  const resolved = normalize2(candidate);
6007
6563
  if (!resolved.startsWith(planningRootBoundary)) {
6008
6564
  throw new NomadFatal(
@@ -6023,7 +6579,7 @@ function runExtrasOp(v, dryRun, paths, backup, copy) {
6023
6579
  const would = [];
6024
6580
  for (const t of eachExtrasTarget(v, counts)) {
6025
6581
  const { src, dst } = paths(t);
6026
- if (!existsSync37(src)) continue;
6582
+ if (!existsSync39(src)) continue;
6027
6583
  const item2 = `${t.logical}/${t.dirname}`;
6028
6584
  if (dryRun) {
6029
6585
  would.push(item2);
@@ -6036,42 +6592,42 @@ function runExtrasOp(v, dryRun, paths, backup, copy) {
6036
6592
  return { ...counts, done, would };
6037
6593
  }
6038
6594
  function pruneEmptyAncestors(target, planningRoot) {
6039
- let dir = dirname9(target);
6040
- while (dir !== planningRoot && dir.startsWith(planningRoot + sep8)) {
6595
+ let dir = dirname12(target);
6596
+ while (dir !== planningRoot && dir.startsWith(planningRoot + sep9)) {
6041
6597
  try {
6042
- if (readdirSync15(dir).length > 0) break;
6043
- rmSync13(dir, { recursive: true, force: true });
6598
+ if (readdirSync16(dir).length > 0) break;
6599
+ rmSync16(dir, { recursive: true, force: true });
6044
6600
  } catch {
6045
6601
  break;
6046
6602
  }
6047
- dir = dirname9(dir);
6603
+ dir = dirname12(dir);
6048
6604
  }
6049
6605
  }
6050
6606
  function tryRealpath(dir) {
6051
6607
  try {
6052
- return realpathSync4(dir);
6608
+ return realpathSync5(dir);
6053
6609
  } catch {
6054
6610
  return void 0;
6055
6611
  }
6056
6612
  }
6057
6613
  function isInsidePlanningRoot(parentReal, rootReal) {
6058
- return parentReal === rootReal || parentReal.startsWith(rootReal + sep8);
6614
+ return parentReal === rootReal || parentReal.startsWith(rootReal + sep9);
6059
6615
  }
6060
6616
  function deletePlanningTarget(target, planningRoot, repoCounterpart) {
6061
- if (existsSync37(repoCounterpart)) return;
6062
- const parentReal = tryRealpath(dirname9(target));
6617
+ if (existsSync39(repoCounterpart)) return;
6618
+ const parentReal = tryRealpath(dirname12(target));
6063
6619
  if (parentReal === void 0) return;
6064
6620
  const rootReal = tryRealpath(planningRoot);
6065
6621
  if (rootReal === void 0) return;
6066
6622
  if (!isInsidePlanningRoot(parentReal, rootReal)) return;
6067
- rmSync13(target, { recursive: true, force: true });
6623
+ rmSync16(target, { recursive: true, force: true });
6068
6624
  pruneEmptyAncestors(target, planningRoot);
6069
6625
  }
6070
6626
  function localDivergesFromPreDelete(target, pre, repoRel, repo) {
6071
- if (!existsSync37(target)) return false;
6627
+ if (!existsSync39(target)) return false;
6072
6628
  try {
6073
6629
  const preBlob = gitCaptureBuffer(["show", `${pre}:${repoRel}`], repo);
6074
- return !readFileSync17(target).equals(preBlob);
6630
+ return !readFileSync19(target).equals(preBlob);
6075
6631
  } catch {
6076
6632
  return true;
6077
6633
  }
@@ -6082,11 +6638,11 @@ function planningDiffArgs(pre, post, logical) {
6082
6638
  function deletePairsFor(t, raw) {
6083
6639
  return planningDeleteTargets({ raw, logical: t.logical, localRoot: t.localRoot }).map(
6084
6640
  (target) => {
6085
- const relToLocal = target.slice(t.localRoot.length + sep8.length);
6641
+ const relToLocal = target.slice(t.localRoot.length + sep9.length);
6086
6642
  return {
6087
6643
  target,
6088
6644
  relToLocal,
6089
- repoRel: `shared/extras/${t.logical}/${relToLocal.split(sep8).join("/")}`
6645
+ repoRel: `shared/extras/${t.logical}/${relToLocal.split(sep9).join("/")}`
6090
6646
  };
6091
6647
  }
6092
6648
  );
@@ -6113,7 +6669,7 @@ function keptDeletePreview(v, prePostHeads, repo) {
6113
6669
  return kept;
6114
6670
  }
6115
6671
  function propagatePlanningDeletes(v, ts, prePostHeads, repo) {
6116
- const repoExtras = join44(repo, "shared", "extras");
6672
+ const repoExtras = join49(repo, "shared", "extras");
6117
6673
  for (const t of eachExtrasTarget(v, { unmapped: 0, skipped: 0 })) {
6118
6674
  if (t.dirname !== ".planning") continue;
6119
6675
  let raw;
@@ -6128,14 +6684,14 @@ function propagatePlanningDeletes(v, ts, prePostHeads, repo) {
6128
6684
  }
6129
6685
  const pairs = deletePairsFor(t, raw);
6130
6686
  if (pairs.length === 0) continue;
6131
- backupExtrasWrite(join44(t.localRoot, t.dirname), ts, t.localRoot);
6132
- const planningRoot = join44(t.localRoot, ".planning");
6687
+ backupExtrasWrite(join49(t.localRoot, t.dirname), ts, t.localRoot);
6688
+ const planningRoot = join49(t.localRoot, ".planning");
6133
6689
  for (const { target, relToLocal, repoRel } of pairs) {
6134
6690
  if (localDivergesFromPreDelete(target, prePostHeads.pre, repoRel, repo)) {
6135
6691
  warn(keptDeleteWarnLine(t.logical, relToLocal));
6136
6692
  continue;
6137
6693
  }
6138
- deletePlanningTarget(target, planningRoot, join44(repoExtras, t.logical, relToLocal));
6694
+ deletePlanningTarget(target, planningRoot, join49(repoExtras, t.logical, relToLocal));
6139
6695
  }
6140
6696
  }
6141
6697
  }
@@ -6144,14 +6700,14 @@ function remapExtrasPush(ts, opts = {}) {
6144
6700
  const v = loadValidatedExtras({ missingMsg: "no path-map.json; skipping extras push" });
6145
6701
  if (v === null) return { unmapped: 0, skipped: 0, pushed: [], wouldPush: [] };
6146
6702
  const repo = repoHome();
6147
- const repoExtras = join44(repo, "shared", "extras");
6148
- if (!dryRun) mkdirSync8(repoExtras, { recursive: true });
6703
+ const repoExtras = join49(repo, "shared", "extras");
6704
+ if (!dryRun) mkdirSync13(repoExtras, { recursive: true });
6149
6705
  const { unmapped, skipped, done, would } = runExtrasOp(
6150
6706
  v,
6151
6707
  dryRun,
6152
- ({ localRoot, logical, dirname: dirname10 }) => ({
6153
- src: join44(localRoot, dirname10),
6154
- dst: join44(repoExtras, logical, dirname10)
6708
+ ({ localRoot, logical, dirname: dirname13 }) => ({
6709
+ src: join49(localRoot, dirname13),
6710
+ dst: join49(repoExtras, logical, dirname13)
6155
6711
  }),
6156
6712
  (dst) => backupRepoWrite(dst, ts, repo),
6157
6713
  // Push copy routing per extra type:
@@ -6163,7 +6719,7 @@ function remapExtrasPush(ts, opts = {}) {
6163
6719
  // (enforceAllowList / blockSetFor in commands.push.allowlist.ts)
6164
6720
  // remains the hard security boundary.
6165
6721
  // All others: copyExtrasFiltered with per-extra denylist.
6166
- (src, dst, dirname10) => dirname10 === ".planning" ? copyExtrasOverlayFiltered(src, dst, extrasDenySet(dirname10)) : copyExtrasFiltered(src, dst, extrasDenySet(dirname10))
6722
+ (src, dst, dirname13) => dirname13 === ".planning" ? copyExtrasOverlayFiltered(src, dst, extrasDenySet(dirname13)) : copyExtrasFiltered(src, dst, extrasDenySet(dirname13))
6167
6723
  );
6168
6724
  return { unmapped, skipped, pushed: done, wouldPush: would };
6169
6725
  }
@@ -6179,9 +6735,9 @@ function remapExtrasPull(ts, opts = {}) {
6179
6735
  const { unmapped, skipped, done, would } = runExtrasOp(
6180
6736
  v,
6181
6737
  dryRun,
6182
- ({ localRoot, logical, dirname: dirname10 }) => ({
6183
- src: join44(repo, "shared", "extras", logical, dirname10),
6184
- dst: join44(localRoot, dirname10)
6738
+ ({ localRoot, logical, dirname: dirname13 }) => ({
6739
+ src: join49(repo, "shared", "extras", logical, dirname13),
6740
+ dst: join49(localRoot, dirname13)
6185
6741
  }),
6186
6742
  // Snapshot the host-side dst BEFORE the copy step touches it. Anchor on
6187
6743
  // localRoot so the backup tree mirrors the project layout.
@@ -6199,12 +6755,12 @@ function remapExtrasPull(ts, opts = {}) {
6199
6755
  // copyExtrasFileSkipDiverged keeps a locally-edited file rather than
6200
6756
  // clobbering it, so the divergence WARN's keep-local promise holds for
6201
6757
  // every extra, not just `.planning`.
6202
- (src, dst, dirname10) => {
6203
- if (dirname10 === ".claude")
6204
- return copyExtrasFilteredPreserving(src, dst, extrasDenySet(dirname10));
6205
- if (dirname10 === ".planning") {
6758
+ (src, dst, dirname13) => {
6759
+ if (dirname13 === ".claude")
6760
+ return copyExtrasFilteredPreserving(src, dst, extrasDenySet(dirname13));
6761
+ if (dirname13 === ".planning") {
6206
6762
  const divergedSet = new Set(listDivergingModified(dst, src));
6207
- return copyExtrasOverlaySkipDiverged(src, dst, extrasDenySet(dirname10), divergedSet);
6763
+ return copyExtrasOverlaySkipDiverged(src, dst, extrasDenySet(dirname13), divergedSet);
6208
6764
  }
6209
6765
  return copyExtrasFileSkipDiverged(src, dst);
6210
6766
  }
@@ -6228,22 +6784,22 @@ function divergenceCheckExtras(ts, prePostHeads) {
6228
6784
  const v = loadValidatedExtras({});
6229
6785
  if (v === null) return 0;
6230
6786
  const counts = { unmapped: 0, skipped: 0 };
6231
- const backupRoot = join45(backupBase(), ts, "extras");
6787
+ const backupRoot = join50(backupBase(), ts, "extras");
6232
6788
  const repo = repoHome();
6233
6789
  let divergedCount = 0;
6234
- for (const { logical, localRoot, dirname: dirname10 } of eachExtrasTarget(v, counts)) {
6235
- const local = join45(localRoot, dirname10);
6236
- const repoEntry = join45(repo, "shared", "extras", logical, dirname10);
6237
- if (!existsSync38(local) || !existsSync38(repoEntry)) continue;
6790
+ for (const { logical, localRoot, dirname: dirname13 } of eachExtrasTarget(v, counts)) {
6791
+ const local = join50(localRoot, dirname13);
6792
+ const repoEntry = join50(repo, "shared", "extras", logical, dirname13);
6793
+ if (!existsSync40(local) || !existsSync40(repoEntry)) continue;
6238
6794
  const modified = listDivergingModified(local, repoEntry);
6239
6795
  if (modified.length === 0) continue;
6240
6796
  divergedCount += modified.length;
6241
- const projectBackupRoot = join45(backupRoot, encodePath(localRoot));
6797
+ const projectBackupRoot = join50(backupRoot, encodePath(localRoot));
6242
6798
  warn(
6243
6799
  divergenceWarnLine({
6244
- dirname: dirname10,
6800
+ dirname: dirname13,
6245
6801
  logical,
6246
- isDir: statSync10(local).isDirectory(),
6802
+ isDir: statSync11(local).isDirectory(),
6247
6803
  count: modified.length,
6248
6804
  projectBackupRoot
6249
6805
  })
@@ -6258,53 +6814,10 @@ function divergenceCheckExtras(ts, prePostHeads) {
6258
6814
  return divergedCount;
6259
6815
  }
6260
6816
 
6261
- // src/skills-sync.ts
6262
- init_config();
6263
- import { existsSync as existsSync39, lstatSync as lstatSync12, mkdirSync as mkdirSync9, readdirSync as readdirSync16, rmSync as rmSync14 } from "node:fs";
6264
- import { join as join46 } from "node:path";
6265
- init_utils_fs();
6266
- function isGsdOwned(name) {
6267
- return name.startsWith(GSD_PREFIX);
6268
- }
6269
- function isSkillExcluded(name) {
6270
- return isGsdOwned(name) || isDeniedName(ALWAYS_NEVER_SYNC, name);
6271
- }
6272
- function copySkillsPush(src, dst) {
6273
- const srcNames = readdirSync16(src, { encoding: "utf8" });
6274
- const blockSet = /* @__PURE__ */ new Set([
6275
- ...srcNames.filter((n) => isGsdOwned(n)),
6276
- ...ALWAYS_NEVER_SYNC
6277
- ]);
6278
- copyExtrasFiltered(src, dst, blockSet);
6279
- }
6280
- function copySkillsPull(src, dst) {
6281
- copyExtrasFilteredPreservingBy(src, dst, isSkillExcluded);
6282
- }
6283
- function syncSkillsPull(ts) {
6284
- const sharedSkills = join46(repoHome(), "shared", "skills");
6285
- if (!existsSync39(sharedSkills)) return;
6286
- const localSkills = join46(claudeHome(), "skills");
6287
- const dstStat = lstatSync12(localSkills, { throwIfNoEntry: false });
6288
- if (dstStat?.isSymbolicLink() === true) {
6289
- backupBeforeWrite(localSkills, ts);
6290
- rmSync14(localSkills, { recursive: true, force: true });
6291
- mkdirSync9(localSkills, { recursive: true });
6292
- }
6293
- copySkillsPull(sharedSkills, localSkills);
6294
- }
6295
- function syncSkillsPush() {
6296
- const localSkills = join46(claudeHome(), "skills");
6297
- const stat = lstatSync12(localSkills, { throwIfNoEntry: false });
6298
- if (stat === void 0) return;
6299
- if (stat.isSymbolicLink()) return;
6300
- const sharedSkills = join46(repoHome(), "shared", "skills");
6301
- copySkillsPush(localSkills, sharedSkills);
6302
- }
6303
-
6304
6817
  // src/preview.ts
6305
6818
  init_config();
6306
- import { existsSync as existsSync40 } from "node:fs";
6307
- import { join as join47 } from "node:path";
6819
+ import { existsSync as existsSync41 } from "node:fs";
6820
+ import { join as join51 } from "node:path";
6308
6821
 
6309
6822
  // node_modules/diff/libesm/diff/base.js
6310
6823
  var Diff = class {
@@ -6590,7 +7103,7 @@ function diffJsonStrings(currentJsonText, newJsonText) {
6590
7103
  return lines.join("\n");
6591
7104
  }
6592
7105
  function readJsonOrNull(path) {
6593
- if (!existsSync40(path)) return null;
7106
+ if (!existsSync41(path)) return null;
6594
7107
  try {
6595
7108
  return readJson(path);
6596
7109
  } catch {
@@ -6604,12 +7117,12 @@ function previewSettings(basePath, hostPath, settingsPath) {
6604
7117
  }
6605
7118
  const notes = [];
6606
7119
  const hostOverrides = readJsonOrNull(hostPath);
6607
- if (hostOverrides === null && existsSync40(hostPath)) {
7120
+ if (hostOverrides === null && existsSync41(hostPath)) {
6608
7121
  notes.push(`malformed hosts/${HOST}.json; ignoring overrides`);
6609
7122
  }
6610
7123
  const merged = stripGsdHookEntries(deepMerge(base, hostOverrides ?? {}));
6611
7124
  const current = readJsonOrNull(settingsPath);
6612
- if (current === null && existsSync40(settingsPath)) {
7125
+ if (current === null && existsSync41(settingsPath)) {
6613
7126
  return { diff: "", notes: [...notes, "malformed; skipping diff"] };
6614
7127
  }
6615
7128
  const strippedCurrent = stripGsdHookEntries(current ?? {});
@@ -6651,9 +7164,9 @@ function computePreview(ts, map, verb = "pull") {
6651
7164
  onPreview: (e) => addItem(links, formatLinkRow(e))
6652
7165
  });
6653
7166
  const settingsResult = previewSettings(
6654
- join47(repo, "shared", "settings.base.json"),
6655
- join47(repo, "hosts", `${HOST}.json`),
6656
- join47(claude, "settings.json")
7167
+ join51(repo, "shared", "settings.base.json"),
7168
+ join51(repo, "hosts", `${HOST}.json`),
7169
+ join51(claude, "settings.json")
6657
7170
  );
6658
7171
  const settingsSection = buildSettingsSectionForPreview(settingsResult);
6659
7172
  const sessions = section("Sessions");
@@ -6668,7 +7181,7 @@ function computePreview(ts, map, verb = "pull") {
6668
7181
  const extras = section("Extras");
6669
7182
  let extrasSkipped = 0;
6670
7183
  let extrasUnmapped = 0;
6671
- if (existsSync40(join47(repo, "path-map.json")) && existsSync40(join47(repo, "shared", "extras"))) {
7184
+ if (existsSync41(join51(repo, "path-map.json")) && existsSync41(join51(repo, "shared", "extras"))) {
6672
7185
  const extrasResult = remapExtrasPull(ts, { dryRun: true });
6673
7186
  for (const entry of extrasResult.wouldPull) {
6674
7187
  addItem(extras, entry);
@@ -6693,9 +7206,9 @@ init_config();
6693
7206
  init_commands_pull_wedge();
6694
7207
  init_utils();
6695
7208
  init_utils_fs();
6696
- import { execFileSync as execFileSync19 } from "node:child_process";
7209
+ import { execFileSync as execFileSync21 } from "node:child_process";
6697
7210
  function gitCapture(args, cwd) {
6698
- return execFileSync19("git", args, {
7211
+ return execFileSync21("git", args, {
6699
7212
  cwd,
6700
7213
  stdio: ["ignore", "pipe", "pipe"],
6701
7214
  maxBuffer: 64 * 1024 * 1024
@@ -6814,6 +7327,7 @@ function recoverForceRemote(mode, repo) {
6814
7327
  }
6815
7328
 
6816
7329
  // src/commands.pull.ts
7330
+ init_exit_codes();
6817
7331
  init_utils();
6818
7332
  init_utils_fs();
6819
7333
  init_utils_json();
@@ -6862,7 +7376,7 @@ function handleWedge(repo, forceRemote) {
6862
7376
  if (forceRemote) {
6863
7377
  recoverUnmergedIndex(repo);
6864
7378
  } else {
6865
- die(unmergedIndexRunbookText("nomad pull"));
7379
+ die(unmergedIndexRunbookText("nomad pull"), { code: EXIT.CONFLICT });
6866
7380
  }
6867
7381
  return;
6868
7382
  }
@@ -6871,7 +7385,7 @@ function handleWedge(repo, forceRemote) {
6871
7385
  return;
6872
7386
  }
6873
7387
  const state = wedge === "rebase" ? "mid-rebase" : "mid-merge";
6874
- die(wedgeMarkerRunbookText(state));
7388
+ die(wedgeMarkerRunbookText(state), { code: EXIT.CONFLICT });
6875
7389
  }
6876
7390
  function runPullCore(opts = {}) {
6877
7391
  const dryRun = opts.dryRun === true;
@@ -6882,9 +7396,9 @@ function runPullCore(opts = {}) {
6882
7396
  const ts = freshBackupTs(backup);
6883
7397
  handleWedge(repo, forceRemote);
6884
7398
  if (!dryRun) {
6885
- const backupRoot = join48(backup, ts);
7399
+ const backupRoot = join52(backup, ts);
6886
7400
  try {
6887
- mkdirSync10(backupRoot, { recursive: true });
7401
+ mkdirSync14(backupRoot, { recursive: true });
6888
7402
  } catch (err) {
6889
7403
  die(`could not create backup dir: ${err.message}`);
6890
7404
  }
@@ -6897,8 +7411,8 @@ function runPullCore(opts = {}) {
6897
7411
  const prePostHeads = capturePrePostHeads(repo, () => {
6898
7412
  gitOrFatal(["pull", "--rebase", "--autostash"], "git pull --rebase", repo);
6899
7413
  });
6900
- const mapPath = join48(repo, "path-map.json");
6901
- const map = existsSync41(mapPath) ? readPathMap(mapPath) : { projects: {} };
7414
+ const mapPath = join52(repo, "path-map.json");
7415
+ const map = existsSync42(mapPath) ? readPathMap(mapPath) : { projects: {} };
6902
7416
  const divergedKeptLocal = divergenceCheckExtras(ts, dryRun ? prePostHeads : void 0);
6903
7417
  if (dryRun) {
6904
7418
  computePreview(ts, map, "pull");
@@ -6924,8 +7438,8 @@ function runPullCore(opts = {}) {
6924
7438
  }
6925
7439
  function cmdPull(opts = {}) {
6926
7440
  const repo = repoHome();
6927
- if (!existsSync41(repo)) die(`repo not cloned at ${repo}`);
6928
- if (!existsSync41(join48(repo, "shared", "settings.base.json"))) {
7441
+ if (!existsSync42(repo)) die(`repo not cloned at ${repo}`);
7442
+ if (!existsSync42(join52(repo, "shared", "settings.base.json"))) {
6929
7443
  die("repo not initialized; run 'nomad init' to scaffold");
6930
7444
  }
6931
7445
  const handle = acquireLock("pull");
@@ -6936,7 +7450,7 @@ function cmdPull(opts = {}) {
6936
7450
  } catch (err) {
6937
7451
  if (err instanceof NomadFatal) {
6938
7452
  fail(err.message);
6939
- process.exitCode = 1;
7453
+ process.exitCode = err.code;
6940
7454
  } else {
6941
7455
  throw err;
6942
7456
  }
@@ -6947,13 +7461,13 @@ function cmdPull(opts = {}) {
6947
7461
 
6948
7462
  // src/commands.push.ts
6949
7463
  init_config();
6950
- import { existsSync as existsSync45 } from "node:fs";
6951
- import { join as join53 } from "node:path";
7464
+ import { existsSync as existsSync46 } from "node:fs";
7465
+ import { join as join57 } from "node:path";
6952
7466
 
6953
7467
  // src/commands.push.selection.ts
6954
7468
  init_config();
6955
- import { existsSync as existsSync42, statSync as statSync11 } from "node:fs";
6956
- import { join as join49 } from "node:path";
7469
+ import { existsSync as existsSync43, statSync as statSync12 } from "node:fs";
7470
+ import { join as join53 } from "node:path";
6957
7471
  init_utils_json();
6958
7472
  function buildCurrentMap(map) {
6959
7473
  const current = {};
@@ -6962,10 +7476,10 @@ function buildCurrentMap(map) {
6962
7476
  for (const [, hostMap] of Object.entries(map.projects)) {
6963
7477
  const localPath = hostMap[HOST];
6964
7478
  if (!localPath) continue;
6965
- const localDir = join49(claude, "projects", encodePath(localPath));
6966
- if (!existsSync42(localDir)) continue;
7479
+ const localDir = join53(claude, "projects", encodePath(localPath));
7480
+ if (!existsSync43(localDir)) continue;
6967
7481
  for (const f of enumerateSourceFiles(localDir)) {
6968
- const st = statSync11(f);
7482
+ const st = statSync12(f);
6969
7483
  current[f] = { size: st.size, mtime: st.mtimeMs };
6970
7484
  }
6971
7485
  }
@@ -6994,7 +7508,7 @@ function computePushSelection(map, old, scannerVersion, configHash, fullScan) {
6994
7508
  };
6995
7509
  }
6996
7510
  function loadSelectionForPush(mapPath, old, scannerVersion, configHash, fullScan) {
6997
- const map = existsSync42(mapPath) ? readPathMap(mapPath) : null;
7511
+ const map = existsSync43(mapPath) ? readPathMap(mapPath) : null;
6998
7512
  const { selection, newManifest } = computePushSelection(
6999
7513
  map,
7000
7514
  old,
@@ -7096,14 +7610,14 @@ function enforceAllowList(statusPorcelain, map) {
7096
7610
 
7097
7611
  // src/commands.push.settings.ts
7098
7612
  init_config();
7099
- import { existsSync as existsSync43 } from "node:fs";
7100
- import { join as join50 } from "node:path";
7613
+ import { existsSync as existsSync44 } from "node:fs";
7614
+ import { join as join54 } from "node:path";
7101
7615
  init_utils();
7102
7616
  init_utils_fs();
7103
7617
  init_utils_json();
7104
7618
  function stripGsdHooksFromBase(repo, backup) {
7105
- const basePath = join50(repo, "shared", "settings.base.json");
7106
- if (!existsSync43(basePath)) return;
7619
+ const basePath = join54(repo, "shared", "settings.base.json");
7620
+ if (!existsSync44(basePath)) return;
7107
7621
  let base;
7108
7622
  try {
7109
7623
  base = readJson(basePath);
@@ -7117,14 +7631,14 @@ function stripGsdHooksFromBase(repo, backup) {
7117
7631
  writeJsonAtomic(basePath, stripped);
7118
7632
  }
7119
7633
  function reportSettingsAheadDrift(repo) {
7120
- const basePath = join50(repo, "shared", "settings.base.json");
7121
- if (!existsSync43(basePath)) return;
7122
- const settingsPath = join50(claudeHome(), "settings.json");
7123
- if (!existsSync43(settingsPath)) return;
7634
+ const basePath = join54(repo, "shared", "settings.base.json");
7635
+ if (!existsSync44(basePath)) return;
7636
+ const settingsPath = join54(claudeHome(), "settings.json");
7637
+ if (!existsSync44(settingsPath)) return;
7124
7638
  try {
7125
7639
  const base = readJson(basePath);
7126
- const hostPath = join50(repo, "hosts", `${HOST}.json`);
7127
- const overrides = existsSync43(hostPath) ? readJson(hostPath) : {};
7640
+ const hostPath = join54(repo, "hosts", `${HOST}.json`);
7641
+ const overrides = existsSync44(hostPath) ? readJson(hostPath) : {};
7128
7642
  const merged = deepMerge(base, overrides);
7129
7643
  const settings = readJson(settingsPath);
7130
7644
  const { ahead } = classifySettingsDrift(merged, settings);
@@ -7141,9 +7655,9 @@ function reportSettingsAheadDrift(repo) {
7141
7655
  // src/commands.push.guards.ts
7142
7656
  init_push_checks();
7143
7657
  init_utils();
7144
- import { join as join51, relative as relative7 } from "node:path";
7658
+ import { join as join55, relative as relative7 } from "node:path";
7145
7659
  function guardGitlinks(repo) {
7146
- const gitlinks = findGitlinks(join51(repo, "shared"));
7660
+ const gitlinks = findGitlinks(join55(repo, "shared"));
7147
7661
  if (gitlinks.length === 0) return;
7148
7662
  for (const p of gitlinks) {
7149
7663
  const rel = relative7(repo, p).replaceAll("\\", "/");
@@ -7175,7 +7689,7 @@ init_config();
7175
7689
 
7176
7690
  // src/push-global-config.ts
7177
7691
  init_config();
7178
- import { execFileSync as execFileSync20 } from "node:child_process";
7692
+ import { execFileSync as execFileSync22 } from "node:child_process";
7179
7693
  var STATUS_LABELS = {
7180
7694
  A: "add",
7181
7695
  M: "modify",
@@ -7215,7 +7729,7 @@ function isInScope(filePath, exactPrefixes, dirPrefixes) {
7215
7729
  }
7216
7730
  function collectGlobalConfigChanges(repoHome2, hostname2, opts) {
7217
7731
  const args = opts.staged ? ["diff", "--cached", "--name-status", "-z"] : ["diff", "HEAD", "--name-status", "-z"];
7218
- const raw = execFileSync20("git", args, {
7732
+ const raw = execFileSync22("git", args, {
7219
7733
  cwd: repoHome2,
7220
7734
  stdio: ["ignore", "pipe", "pipe"]
7221
7735
  }).toString();
@@ -7252,10 +7766,10 @@ init_push_leak_verdict();
7252
7766
  init_color();
7253
7767
  init_config();
7254
7768
  init_config_sharedDirs_guard();
7255
- import { randomBytes as randomBytes2 } from "node:crypto";
7256
- import { copyFileSync, existsSync as existsSync44, mkdirSync as mkdirSync11, readdirSync as readdirSync17, rmSync as rmSync15 } from "node:fs";
7257
- import { homedir as homedir5 } from "node:os";
7258
- import { join as join52, relative as relative8, sep as sep9 } from "node:path";
7769
+ import { randomBytes as randomBytes4 } from "node:crypto";
7770
+ import { copyFileSync, existsSync as existsSync45, mkdirSync as mkdirSync15, readdirSync as readdirSync17, rmSync as rmSync17 } from "node:fs";
7771
+ import { homedir as homedir7 } from "node:os";
7772
+ import { join as join56, relative as relative8, sep as sep10 } from "node:path";
7259
7773
  init_push_leak_verdict();
7260
7774
  init_push_gitleaks();
7261
7775
  init_utils_fs();
@@ -7263,11 +7777,11 @@ init_utils_json();
7263
7777
  var NOTHING_TO_SCAN_ROW = `${dim(infoGlyph)} nothing to scan, no leaks`;
7264
7778
  function stageSessionDir(localDir, dstDir, changed) {
7265
7779
  if (changed !== void 0) {
7266
- const prefix = `${localDir}${sep9}`;
7780
+ const prefix = `${localDir}${sep10}`;
7267
7781
  const matching = [...changed].filter((p) => p.startsWith(prefix));
7268
7782
  if (matching.length === 0) return false;
7269
7783
  for (const src of matching) {
7270
- copyFileAtomic(src, join52(dstDir, relative8(localDir, src)));
7784
+ copyFileAtomic(src, join56(dstDir, relative8(localDir, src)));
7271
7785
  }
7272
7786
  return true;
7273
7787
  }
@@ -7283,14 +7797,14 @@ function stageSessions(tmpRoot, map, changed) {
7283
7797
  if (!p || p === "TBD") continue;
7284
7798
  reverse.set(encodePath(p), logical);
7285
7799
  }
7286
- const localProjects = join52(claudeHome(), "projects");
7287
- if (!existsSync44(localProjects)) return 0;
7800
+ const localProjects = join56(claudeHome(), "projects");
7801
+ if (!existsSync45(localProjects)) return 0;
7288
7802
  let staged = 0;
7289
7803
  for (const dir of readdirSync17(localProjects)) {
7290
7804
  const logical = reverse.get(dir);
7291
7805
  if (!logical) continue;
7292
- const localDir = join52(localProjects, dir);
7293
- const dstDir = join52(tmpRoot, "shared", "projects", logical);
7806
+ const localDir = join56(localProjects, dir);
7807
+ const dstDir = join56(tmpRoot, "shared", "projects", logical);
7294
7808
  if (stageSessionDir(localDir, dstDir, changed)) staged++;
7295
7809
  }
7296
7810
  return staged;
@@ -7304,11 +7818,11 @@ function stageExtras(tmpRoot, map) {
7304
7818
  assertSafeLogical(logical);
7305
7819
  const localRoot = map.projects[logical]?.[HOST];
7306
7820
  if (!localRoot || localRoot === "TBD") continue;
7307
- for (const dirname10 of dirnames) {
7308
- if (!whitelist.includes(dirname10)) continue;
7309
- const src = join52(localRoot, dirname10);
7310
- if (!existsSync44(src)) continue;
7311
- const dst = join52(tmpRoot, "shared", "extras", logical, dirname10);
7821
+ for (const dirname13 of dirnames) {
7822
+ if (!whitelist.includes(dirname13)) continue;
7823
+ const src = join56(localRoot, dirname13);
7824
+ if (!existsSync45(src)) continue;
7825
+ const dst = join56(tmpRoot, "shared", "extras", logical, dirname13);
7312
7826
  copyExtras(src, dst);
7313
7827
  staged++;
7314
7828
  }
@@ -7316,19 +7830,19 @@ function stageExtras(tmpRoot, map) {
7316
7830
  return staged;
7317
7831
  }
7318
7832
  function previewPushLeaks(map, opts = {}) {
7319
- const cacheDir = join52(homedir5(), ".cache", "claude-nomad");
7320
- mkdirSync11(cacheDir, { recursive: true });
7321
- const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes2(4).toString("hex")}`;
7322
- const tmpRoot = join52(cacheDir, `push-preview-tree-${stamp}`);
7833
+ const cacheDir = join56(homedir7(), ".cache", "claude-nomad");
7834
+ mkdirSync15(cacheDir, { recursive: true });
7835
+ const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes4(4).toString("hex")}`;
7836
+ const tmpRoot = join56(cacheDir, `push-preview-tree-${stamp}`);
7323
7837
  try {
7324
7838
  const sessionCount = stageSessions(tmpRoot, map, opts.selection?.changed);
7325
7839
  const extrasCount = stageExtras(tmpRoot, map);
7326
7840
  if (sessionCount + extrasCount === 0) {
7327
7841
  return { leak: false, verdictRow: NOTHING_TO_SCAN_ROW, recovery: null, findings: [] };
7328
7842
  }
7329
- const ignoreFile = join52(repoHome(), ".gitleaksignore");
7330
- if (existsSync44(ignoreFile)) {
7331
- copyFileSync(ignoreFile, join52(tmpRoot, ".gitleaksignore"));
7843
+ const ignoreFile = join56(repoHome(), ".gitleaksignore");
7844
+ if (existsSync45(ignoreFile)) {
7845
+ copyFileSync(ignoreFile, join56(tmpRoot, ".gitleaksignore"));
7332
7846
  }
7333
7847
  let findings;
7334
7848
  try {
@@ -7341,7 +7855,7 @@ function previewPushLeaks(map, opts = {}) {
7341
7855
  }
7342
7856
  return verdictFromFindings(findings);
7343
7857
  } finally {
7344
- rmSync15(tmpRoot, { recursive: true, force: true });
7858
+ rmSync17(tmpRoot, { recursive: true, force: true });
7345
7859
  }
7346
7860
  }
7347
7861
 
@@ -7454,7 +7968,7 @@ async function runPushCore(opts = {}) {
7454
7968
  const scannerVersion = probeGitleaks();
7455
7969
  const configHash = computeConfigHash();
7456
7970
  const old = readManifest(manifestPath());
7457
- const mapPath = join53(repo, "path-map.json");
7971
+ const mapPath = join57(repo, "path-map.json");
7458
7972
  const { map, selection, newManifest } = loadSelectionForPush(
7459
7973
  mapPath,
7460
7974
  old,
@@ -7505,7 +8019,7 @@ async function cmdPush(opts = {}) {
7505
8019
  const allowRule = opts.allowRule;
7506
8020
  guardResolutionModeConflicts(dryRun, redactAll, allowAll, allowRule);
7507
8021
  const repo = repoHome();
7508
- if (!existsSync45(repo)) die(`repo not cloned at ${repo}`);
8022
+ if (!existsSync46(repo)) die(`repo not cloned at ${repo}`);
7509
8023
  const handle = acquireLock("push");
7510
8024
  if (handle === null) process.exit(0);
7511
8025
  try {
@@ -7513,7 +8027,7 @@ async function cmdPush(opts = {}) {
7513
8027
  } catch (err) {
7514
8028
  if (err instanceof NomadFatal) {
7515
8029
  fail(err.message);
7516
- process.exitCode = 1;
8030
+ process.exitCode = err.code;
7517
8031
  } else {
7518
8032
  throw err;
7519
8033
  }
@@ -7523,8 +8037,8 @@ async function cmdPush(opts = {}) {
7523
8037
  }
7524
8038
 
7525
8039
  // src/commands.sync.ts
7526
- import { existsSync as existsSync46 } from "node:fs";
7527
- import { join as join54 } from "node:path";
8040
+ import { existsSync as existsSync47 } from "node:fs";
8041
+ import { join as join58 } from "node:path";
7528
8042
  init_config();
7529
8043
  init_color();
7530
8044
  init_utils();
@@ -7639,7 +8153,7 @@ async function runSyncPushHalf() {
7639
8153
  return { ok: true, result };
7640
8154
  } catch (err) {
7641
8155
  if (err instanceof NomadFatal) {
7642
- process.exitCode = 1;
8156
+ process.exitCode = err.code;
7643
8157
  return { ok: false, message: err.message };
7644
8158
  }
7645
8159
  throw err;
@@ -7652,8 +8166,8 @@ async function runSyncWet(verbose) {
7652
8166
  }
7653
8167
  async function runSyncDryRun(repo, backup) {
7654
8168
  const ts = freshBackupTs(backup);
7655
- const mapPath = join54(repo, "path-map.json");
7656
- const map = existsSync46(mapPath) ? readPathMap(mapPath) : { projects: {} };
8169
+ const mapPath = join58(repo, "path-map.json");
8170
+ const map = existsSync47(mapPath) ? readPathMap(mapPath) : { projects: {} };
7657
8171
  computePreview(ts, map, "pull");
7658
8172
  log("push preview below is computed against pre-pull state (a real sync pushes after pull)");
7659
8173
  await runPushCore({ dryRun: true });
@@ -7663,8 +8177,8 @@ async function cmdSync(opts = {}) {
7663
8177
  const verbose = opts.verbose === true;
7664
8178
  const repo = repoHome();
7665
8179
  const backup = backupBase();
7666
- if (!existsSync46(repo)) die(`repo not cloned at ${repo}`);
7667
- if (!existsSync46(join54(repo, "shared", "settings.base.json"))) {
8180
+ if (!existsSync47(repo)) die(`repo not cloned at ${repo}`);
8181
+ if (!existsSync47(join58(repo, "shared", "settings.base.json"))) {
7668
8182
  die("repo not initialized; run 'nomad init' to scaffold");
7669
8183
  }
7670
8184
  const handle = acquireLock("sync");
@@ -7678,7 +8192,7 @@ async function cmdSync(opts = {}) {
7678
8192
  } catch (err) {
7679
8193
  if (err instanceof NomadFatal) {
7680
8194
  fail(err.message);
7681
- process.exitCode = 1;
8195
+ process.exitCode = err.code;
7682
8196
  } else {
7683
8197
  throw err;
7684
8198
  }
@@ -7688,9 +8202,9 @@ async function cmdSync(opts = {}) {
7688
8202
  }
7689
8203
 
7690
8204
  // src/commands.update.ts
7691
- import { execFileSync as execFileSync21 } from "node:child_process";
8205
+ import { execFileSync as execFileSync23 } from "node:child_process";
7692
8206
  init_utils();
7693
- function readInstalledVersion(run = execFileSync21) {
8207
+ function readInstalledVersion(run = execFileSync23) {
7694
8208
  const isWin = process.platform === "win32";
7695
8209
  try {
7696
8210
  return run(isWin ? "nomad.cmd" : "nomad", ["--version"], {
@@ -7701,7 +8215,7 @@ function readInstalledVersion(run = execFileSync21) {
7701
8215
  return null;
7702
8216
  }
7703
8217
  }
7704
- function cmdUpdate(currentVersion, run = execFileSync21) {
8218
+ function cmdUpdate(currentVersion, run = execFileSync23) {
7705
8219
  console.log(`Updating claude-nomad v${currentVersion}...`);
7706
8220
  const isWin = process.platform === "win32";
7707
8221
  try {
@@ -7735,26 +8249,191 @@ ${detail}` : "";
7735
8249
  // src/nomad.ts
7736
8250
  init_config();
7737
8251
 
8252
+ // src/crash-report.write.ts
8253
+ init_config();
8254
+ import { mkdirSync as mkdirSync16, readdirSync as readdirSync18, statSync as statSync13, unlinkSync as unlinkSync2, writeFileSync as writeFileSync11 } from "node:fs";
8255
+ import { join as join60 } from "node:path";
8256
+
8257
+ // src/crash-report.ts
8258
+ var CRASH_MAX_STACK_LINES = 50;
8259
+ var CRASH_MAX_ARGV = 32;
8260
+ var CRASH_MAX_ARGV_TOKEN_LENGTH = 200;
8261
+ var CRASH_MAX_REPORT_BYTES = 16384;
8262
+ var TRUNCATION_MARKER = "\n[... truncated ...]\n";
8263
+ function safeStringify(value) {
8264
+ try {
8265
+ const json = JSON.stringify(value);
8266
+ if (json !== void 0) return json;
8267
+ } catch {
8268
+ }
8269
+ try {
8270
+ return String(value);
8271
+ } catch {
8272
+ return "(unstringifiable thrown value)";
8273
+ }
8274
+ }
8275
+ function normalizeError(err) {
8276
+ try {
8277
+ if (err instanceof Error) {
8278
+ return { name: err.name, message: err.message, stack: err.stack };
8279
+ }
8280
+ } catch {
8281
+ return { name: "Error", message: "(unreadable Error)", stack: void 0 };
8282
+ }
8283
+ if (typeof err === "string") {
8284
+ return { name: "NonErrorThrow", message: err, stack: void 0 };
8285
+ }
8286
+ return { name: "NonErrorThrow", message: safeStringify(err), stack: void 0 };
8287
+ }
8288
+ function boundedStack(stack) {
8289
+ if (stack === void 0) return "(no stack available)";
8290
+ return stack.split("\n").slice(0, CRASH_MAX_STACK_LINES).join("\n");
8291
+ }
8292
+ function clampArgv(argv) {
8293
+ return argv.slice(0, CRASH_MAX_ARGV).map((tok) => {
8294
+ if (tok.length <= CRASH_MAX_ARGV_TOKEN_LENGTH) return tok;
8295
+ return tok.slice(0, CRASH_MAX_ARGV_TOKEN_LENGTH) + "...";
8296
+ });
8297
+ }
8298
+ function truncateToBytes(text, maxBytes) {
8299
+ if (Buffer.byteLength(text, "utf8") <= maxBytes) return text;
8300
+ const budget = Math.max(0, maxBytes - Buffer.byteLength(TRUNCATION_MARKER, "utf8"));
8301
+ let sliced = text.slice(0, budget);
8302
+ while (Buffer.byteLength(sliced, "utf8") > budget) {
8303
+ sliced = sliced.slice(0, -1);
8304
+ }
8305
+ return sliced + TRUNCATION_MARKER;
8306
+ }
8307
+ function scrubStructural(text, homeDir, hostLabel) {
8308
+ let out = homeDir.length > 0 ? text.split(homeDir).join("~") : text;
8309
+ if (hostLabel.length > 0) out = out.split(hostLabel).join("<host>");
8310
+ return out;
8311
+ }
8312
+ function buildCrashReport(input) {
8313
+ const { err, argv, version, platform: platform2, timestamp, homeDir, hostLabel } = input;
8314
+ const normalized = normalizeError(err);
8315
+ const command = clampArgv(argv).join(" ");
8316
+ const stack = boundedStack(normalized.stack);
8317
+ const composed = [
8318
+ "nomad crash report",
8319
+ `version: ${version}`,
8320
+ `command: ${command}`,
8321
+ `error: ${normalized.name}: ${normalized.message}`,
8322
+ "stack:",
8323
+ stack,
8324
+ `platform: ${platform2} (node ${process.version})`,
8325
+ `timestamp: ${timestamp}`
8326
+ ].join("\n");
8327
+ const scrubbed = scrubStructural(composed, homeDir, hostLabel);
8328
+ return truncateToBytes(scrubbed, CRASH_MAX_REPORT_BYTES);
8329
+ }
8330
+
8331
+ // src/crash-report.redact.ts
8332
+ import { mkdtempSync as mkdtempSync2, rmSync as rmSync18, writeFileSync as writeFileSync10 } from "node:fs";
8333
+ import { tmpdir as tmpdir2 } from "node:os";
8334
+ import { join as join59 } from "node:path";
8335
+ init_push_gitleaks_scan();
8336
+ var CRASH_SCAN_TIMEOUT_MS = 3e3;
8337
+ var SCAN_UNAVAILABLE_ADVISORY = "\n\n[gitleaks value-based scan unavailable; only structural redaction applied. Review before sharing this file publicly.]\n";
8338
+ function redactWithGitleaks(text, scan = scanFile) {
8339
+ let dir;
8340
+ try {
8341
+ dir = mkdtempSync2(join59(tmpdir2(), "nomad-crash-scan-"));
8342
+ const tmp = join59(dir, "crash.txt");
8343
+ writeFileSync10(tmp, text, { mode: 384 });
8344
+ const findings = scan(tmp, false, CRASH_SCAN_TIMEOUT_MS);
8345
+ if (findings === null) return text + SCAN_UNAVAILABLE_ADVISORY;
8346
+ return applyRedactions(text, findings);
8347
+ } catch {
8348
+ return text + SCAN_UNAVAILABLE_ADVISORY;
8349
+ } finally {
8350
+ if (dir !== void 0) {
8351
+ try {
8352
+ rmSync18(dir, { recursive: true, force: true });
8353
+ } catch {
8354
+ }
8355
+ }
8356
+ }
8357
+ }
8358
+
8359
+ // src/crash-report.write.ts
8360
+ init_utils_fs();
8361
+ init_utils();
8362
+ var CRASH_RETENTION_KEEP = 20;
8363
+ function listCrashFiles(dir = crashDir()) {
8364
+ try {
8365
+ return readdirSync18(dir).flatMap((name) => {
8366
+ try {
8367
+ return [{ name, mtimeMs: statSync13(join60(dir, name)).mtimeMs }];
8368
+ } catch {
8369
+ return [];
8370
+ }
8371
+ }).sort((a, b) => b.mtimeMs - a.mtimeMs);
8372
+ } catch {
8373
+ return [];
8374
+ }
8375
+ }
8376
+ function pruneCrashDir(dir, keep = CRASH_RETENTION_KEEP) {
8377
+ const files = listCrashFiles(dir);
8378
+ const targets = prunableByCount(files, keep);
8379
+ for (const name of targets) {
8380
+ try {
8381
+ unlinkSync2(join60(dir, name));
8382
+ } catch {
8383
+ }
8384
+ }
8385
+ }
8386
+ function writeCrashReport(text, dir = crashDir()) {
8387
+ mkdirSync16(dir, { recursive: true, mode: 448 });
8388
+ const path = join60(dir, `crash-${nowTimestamp()}-${process.pid}.txt`);
8389
+ writeFileSync11(path, text, { mode: 384 });
8390
+ pruneCrashDir(dir);
8391
+ return path;
8392
+ }
8393
+ function handleCrash(err, argv, opts) {
8394
+ const redact = opts.redact ?? redactWithGitleaks;
8395
+ const write = opts.write ?? writeCrashReport;
8396
+ const now = opts.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
8397
+ try {
8398
+ const report = buildCrashReport({
8399
+ err,
8400
+ argv,
8401
+ version: opts.version,
8402
+ platform: opts.platform,
8403
+ timestamp: now(),
8404
+ homeDir: home(),
8405
+ hostLabel: HOST
8406
+ });
8407
+ const redacted = redact(report);
8408
+ const path = write(redacted);
8409
+ fail("nomad hit an unexpected error. This looks like a bug, not something you did wrong.");
8410
+ item(`Crash report written to: ${path}`);
8411
+ item(`Please consider reporting it at: ${opts.issuesUrl}`);
8412
+ } catch {
8413
+ fail("nomad hit an unexpected error and could not write a crash report.");
8414
+ }
8415
+ }
8416
+
7738
8417
  // src/diff.ts
7739
8418
  init_config();
7740
- import { existsSync as existsSync47 } from "node:fs";
7741
- import { join as join55 } from "node:path";
8419
+ import { existsSync as existsSync48 } from "node:fs";
8420
+ import { join as join61 } from "node:path";
7742
8421
  init_utils();
7743
8422
  init_utils_fs();
7744
8423
  init_utils_json();
7745
8424
  function cmdDiff() {
7746
8425
  try {
7747
8426
  const repo = repoHome();
7748
- if (!existsSync47(repo)) die(`repo not cloned at ${repo}`);
8427
+ if (!existsSync48(repo)) die(`repo not cloned at ${repo}`);
7749
8428
  const ts = freshBackupTs(backupBase());
7750
- const mapPath = join55(repo, "path-map.json");
7751
- const map = existsSync47(mapPath) ? readPathMap(mapPath) : { projects: {} };
8429
+ const mapPath = join61(repo, "path-map.json");
8430
+ const map = existsSync48(mapPath) ? readPathMap(mapPath) : { projects: {} };
7752
8431
  divergenceCheckExtras(ts);
7753
8432
  computePreview(ts, map, "diff");
7754
8433
  } catch (err) {
7755
8434
  if (err instanceof NomadFatal) {
7756
8435
  fail(err.message);
7757
- process.exitCode = 1;
8436
+ process.exitCode = err.code;
7758
8437
  return;
7759
8438
  }
7760
8439
  throw err;
@@ -7763,19 +8442,19 @@ function cmdDiff() {
7763
8442
 
7764
8443
  // src/init.ts
7765
8444
  init_config();
7766
- import { existsSync as existsSync49, mkdirSync as mkdirSync12, writeFileSync as writeFileSync6 } from "node:fs";
7767
- import { join as join57 } from "node:path";
8445
+ import { existsSync as existsSync50, mkdirSync as mkdirSync17, writeFileSync as writeFileSync12 } from "node:fs";
8446
+ import { join as join63 } from "node:path";
7768
8447
 
7769
8448
  // src/init.gh-onboard.ts
7770
8449
  init_config();
7771
- import { execFileSync as execFileSync22 } from "node:child_process";
8450
+ import { execFileSync as execFileSync24 } from "node:child_process";
7772
8451
  init_utils();
7773
8452
  var DEFAULT_REPO_NAME = "claude-nomad-config";
7774
8453
  function isValidRepoName(name) {
7775
8454
  return /^[A-Za-z0-9._-]{1,100}$/.test(name);
7776
8455
  }
7777
8456
  var GH_NETWORK_TIMEOUT_MS = 3e4;
7778
- function ensureOriginRepo(repoName, run = execFileSync22) {
8457
+ function ensureOriginRepo(repoName, run = execFileSync24) {
7779
8458
  if (!isValidRepoName(repoName)) {
7780
8459
  die(
7781
8460
  `invalid repo name: ${JSON.stringify(repoName)}. Use only letters, digits, hyphens, underscores, and dots (1-100 chars).`
@@ -7846,33 +8525,33 @@ init_config();
7846
8525
  init_utils();
7847
8526
  init_utils_fs();
7848
8527
  init_utils_json();
7849
- import { copyFileSync as copyFileSync2, cpSync as cpSync8, existsSync as existsSync48, rmSync as rmSync16, statSync as statSync12 } from "node:fs";
7850
- import { join as join56 } from "node:path";
8528
+ import { copyFileSync as copyFileSync2, cpSync as cpSync10, existsSync as existsSync49, rmSync as rmSync19, statSync as statSync14 } from "node:fs";
8529
+ import { join as join62 } from "node:path";
7851
8530
  function snapshotIntoShared(map) {
7852
8531
  const repo = repoHome();
7853
8532
  const claude = claudeHome();
7854
8533
  for (const name of allSharedLinks(map)) {
7855
- const src = join56(claude, name);
7856
- if (!existsSync48(src)) continue;
7857
- const dst = join56(repo, "shared", name);
7858
- if (statSync12(src).isDirectory()) {
7859
- const gk = join56(dst, ".gitkeep");
7860
- if (existsSync48(gk)) rmSync16(gk);
7861
- cpSync8(src, dst, { recursive: true, force: false, errorOnExist: true });
8534
+ const src = join62(claude, name);
8535
+ if (!existsSync49(src)) continue;
8536
+ const dst = join62(repo, "shared", name);
8537
+ if (statSync14(src).isDirectory()) {
8538
+ const gk = join62(dst, ".gitkeep");
8539
+ if (existsSync49(gk)) rmSync19(gk);
8540
+ cpSync10(src, dst, { recursive: true, force: false, errorOnExist: true });
7862
8541
  } else {
7863
8542
  copyFileSync2(src, dst);
7864
8543
  }
7865
8544
  log(`snapshotted shared/${name} from ${src}`);
7866
8545
  }
7867
- const userSettings = join56(claude, "settings.json");
7868
- if (existsSync48(userSettings)) {
8546
+ const userSettings = join62(claude, "settings.json");
8547
+ if (existsSync49(userSettings)) {
7869
8548
  let parsed;
7870
8549
  try {
7871
8550
  parsed = readJson(userSettings);
7872
8551
  } catch (err) {
7873
8552
  return die(`malformed ${userSettings}: ${err.message}`);
7874
8553
  }
7875
- const hostFile = join56(repo, "hosts", `${HOST}.json`);
8554
+ const hostFile = join62(repo, "hosts", `${HOST}.json`);
7876
8555
  writeJsonAtomic(hostFile, parsed);
7877
8556
  log(`snapshotted hosts/${HOST}.json from ${userSettings}`);
7878
8557
  }
@@ -7886,14 +8565,14 @@ var GITATTRIBUTES = "# nomad: sync content is byte-managed, disable all line-end
7886
8565
  var SHARED_KEEP_DIRS = ["agents", "skills", "commands", "rules", "hooks"];
7887
8566
  function preflightConflict(repoHome2) {
7888
8567
  const candidates = [
7889
- join57(repoHome2, "shared", "settings.base.json"),
7890
- join57(repoHome2, "shared", "CLAUDE.md"),
7891
- join57(repoHome2, "path-map.json"),
7892
- join57(repoHome2, "hosts"),
7893
- join57(repoHome2, "shared")
8568
+ join63(repoHome2, "shared", "settings.base.json"),
8569
+ join63(repoHome2, "shared", "CLAUDE.md"),
8570
+ join63(repoHome2, "path-map.json"),
8571
+ join63(repoHome2, "hosts"),
8572
+ join63(repoHome2, "shared")
7894
8573
  ];
7895
8574
  for (const c of candidates) {
7896
- if (existsSync49(c)) return c;
8575
+ if (existsSync50(c)) return c;
7897
8576
  }
7898
8577
  return null;
7899
8578
  }
@@ -7905,33 +8584,33 @@ function cmdInit(opts = {}) {
7905
8584
  const keepActions = opts.keepActions === true;
7906
8585
  const repo = repoHome();
7907
8586
  const claude = claudeHome();
7908
- mkdirSync12(repo, { recursive: true });
8587
+ mkdirSync17(repo, { recursive: true });
7909
8588
  const conflict = preflightConflict(repo);
7910
8589
  if (conflict !== null) {
7911
8590
  die(`already initialized; refusing to clobber ${conflict}`);
7912
8591
  }
7913
8592
  ensureOriginRepo(opts.repoName ?? DEFAULT_REPO_NAME, opts.run);
7914
- mkdirSync12(join57(repo, "shared"), { recursive: true });
7915
- mkdirSync12(join57(repo, "hosts"), { recursive: true });
8593
+ mkdirSync17(join63(repo, "shared"), { recursive: true });
8594
+ mkdirSync17(join63(repo, "hosts"), { recursive: true });
7916
8595
  for (const name of SHARED_KEEP_DIRS) {
7917
- mkdirSync12(join57(repo, "shared", name), { recursive: true });
8596
+ mkdirSync17(join63(repo, "shared", name), { recursive: true });
7918
8597
  }
7919
- const userClaudeMd = join57(claude, "CLAUDE.md");
7920
- if (!snapshot || !existsSync49(userClaudeMd)) {
7921
- writeFileSync6(join57(repo, "shared", "CLAUDE.md"), SHARED_CLAUDE_MD);
8598
+ const userClaudeMd = join63(claude, "CLAUDE.md");
8599
+ if (!snapshot || !existsSync50(userClaudeMd)) {
8600
+ writeFileSync12(join63(repo, "shared", "CLAUDE.md"), SHARED_CLAUDE_MD);
7922
8601
  item("created shared/CLAUDE.md");
7923
8602
  }
7924
8603
  for (const name of SHARED_KEEP_DIRS) {
7925
- writeFileSync6(join57(repo, "shared", name, ".gitkeep"), "");
8604
+ writeFileSync12(join63(repo, "shared", name, ".gitkeep"), "");
7926
8605
  item(`created shared/${name}/.gitkeep`);
7927
8606
  }
7928
- writeFileSync6(join57(repo, "hosts", ".gitkeep"), "");
8607
+ writeFileSync12(join63(repo, "hosts", ".gitkeep"), "");
7929
8608
  item("created hosts/.gitkeep");
7930
- writeJsonAtomic(join57(repo, "shared", "settings.base.json"), {});
8609
+ writeJsonAtomic(join63(repo, "shared", "settings.base.json"), {});
7931
8610
  item("created shared/settings.base.json");
7932
- writeJsonAtomic(join57(repo, "path-map.json"), { projects: {} });
8611
+ writeJsonAtomic(join63(repo, "path-map.json"), { projects: {} });
7933
8612
  item("created path-map.json");
7934
- writeFileSync6(join57(repo, ".gitattributes"), GITATTRIBUTES);
8613
+ writeFileSync12(join63(repo, ".gitattributes"), GITATTRIBUTES);
7935
8614
  item("created .gitattributes");
7936
8615
  if (snapshot) {
7937
8616
  snapshotIntoShared({ projects: {} });
@@ -7997,21 +8676,21 @@ function maybeDisableRepoActions(repoHome2, run) {
7997
8676
  // src/init.prompt.ts
7998
8677
  init_config();
7999
8678
  init_utils();
8000
- import { existsSync as existsSync50, readdirSync as readdirSync18, statSync as statSync13 } from "node:fs";
8001
- import { join as join58 } from "node:path";
8679
+ import { existsSync as existsSync51, readdirSync as readdirSync19, statSync as statSync15 } from "node:fs";
8680
+ import { join as join64 } from "node:path";
8002
8681
  import { createInterface as createInterface3 } from "node:readline/promises";
8003
8682
  function nonEmptyExists(path) {
8004
- if (!existsSync50(path)) return false;
8683
+ if (!existsSync51(path)) return false;
8005
8684
  try {
8006
- if (statSync13(path).isDirectory()) return readdirSync18(path).length > 0;
8685
+ if (statSync15(path).isDirectory()) return readdirSync19(path).length > 0;
8007
8686
  return true;
8008
8687
  } catch {
8009
8688
  return false;
8010
8689
  }
8011
8690
  }
8012
8691
  function hasExistingClaudeConfig(claudeHome2) {
8013
- if (existsSync50(join58(claudeHome2, "settings.json"))) return true;
8014
- return SHARED_LINKS.some((name) => nonEmptyExists(join58(claudeHome2, name)));
8692
+ if (existsSync51(join64(claudeHome2, "settings.json"))) return true;
8693
+ return SHARED_LINKS.some((name) => nonEmptyExists(join64(claudeHome2, name)));
8015
8694
  }
8016
8695
  async function confirmSnapshotDefault(claudeHome2) {
8017
8696
  if (process.stdin.isTTY !== true || process.stdout.isTTY !== true) {
@@ -8318,7 +8997,7 @@ function parseSyncArgs(argv) {
8318
8997
  // package.json
8319
8998
  var package_default = {
8320
8999
  name: "claude-nomad",
8321
- version: "0.60.0",
9000
+ version: "0.62.0",
8322
9001
  type: "module",
8323
9002
  description: "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
8324
9003
  keywords: [
@@ -8401,7 +9080,7 @@ var package_default = {
8401
9080
  globals: "^17.6.0",
8402
9081
  husky: "^9.1.7",
8403
9082
  "lint-staged": "^17.0.5",
8404
- "markdownlint-cli2": "^0.23.0",
9083
+ "markdownlint-cli2": "^0.23.1",
8405
9084
  picocolors: "^1.1.1",
8406
9085
  prettier: "^3.8.3",
8407
9086
  typescript: "^6.0.3",
@@ -8559,15 +9238,15 @@ var DEFAULT_HELP = [
8559
9238
  init_config();
8560
9239
  init_utils();
8561
9240
  init_utils_json();
8562
- import { existsSync as existsSync51, readFileSync as readFileSync18, readdirSync as readdirSync19 } from "node:fs";
8563
- import { join as join59 } from "node:path";
9241
+ import { existsSync as existsSync52, readFileSync as readFileSync20, readdirSync as readdirSync20 } from "node:fs";
9242
+ import { join as join65 } from "node:path";
8564
9243
  function resumeCmd(sessionId) {
8565
9244
  if (!/^[A-Za-z0-9_-]+$/.test(sessionId) || sessionId.length > 128) {
8566
9245
  fail(`invalid session id: ${sessionId}`);
8567
9246
  process.exit(1);
8568
9247
  }
8569
- const projectsRoot = join59(claudeHome(), "projects");
8570
- if (!existsSync51(projectsRoot)) {
9248
+ const projectsRoot = join65(claudeHome(), "projects");
9249
+ if (!existsSync52(projectsRoot)) {
8571
9250
  fail(`${projectsRoot} does not exist`);
8572
9251
  process.exit(1);
8573
9252
  }
@@ -8581,8 +9260,8 @@ function resumeCmd(sessionId) {
8581
9260
  fail(`no cwd field found in ${jsonlPath}`);
8582
9261
  process.exit(1);
8583
9262
  }
8584
- const mapPath = join59(repoHome(), "path-map.json");
8585
- if (!existsSync51(mapPath)) {
9263
+ const mapPath = join65(repoHome(), "path-map.json");
9264
+ if (!existsSync52(mapPath)) {
8586
9265
  fail("path-map.json missing");
8587
9266
  process.exit(1);
8588
9267
  }
@@ -8604,14 +9283,14 @@ function resumeCmd(sessionId) {
8604
9283
  console.log(`cd ${shQuote(hit.localPath)} && claude --resume ${shQuote(sessionId)}`);
8605
9284
  }
8606
9285
  function findTranscriptPath(projectsRoot, sessionId) {
8607
- for (const dir of readdirSync19(projectsRoot)) {
8608
- const candidate = join59(projectsRoot, dir, `${sessionId}.jsonl`);
8609
- if (existsSync51(candidate)) return candidate;
9286
+ for (const dir of readdirSync20(projectsRoot)) {
9287
+ const candidate = join65(projectsRoot, dir, `${sessionId}.jsonl`);
9288
+ if (existsSync52(candidate)) return candidate;
8610
9289
  }
8611
9290
  return null;
8612
9291
  }
8613
9292
  function extractRecordedCwd(jsonlPath) {
8614
- for (const line of readFileSync18(jsonlPath, "utf8").split("\n")) {
9293
+ for (const line of readFileSync20(jsonlPath, "utf8").split("\n")) {
8615
9294
  if (!line.trim()) continue;
8616
9295
  try {
8617
9296
  const obj = JSON.parse(line);
@@ -8641,20 +9320,45 @@ function shQuote(s) {
8641
9320
 
8642
9321
  // src/nomad.ts
8643
9322
  init_utils();
9323
+ init_exit_codes();
9324
+ function handleTopLevelError(err) {
9325
+ if (isProcessExit(err)) throw err;
9326
+ if (err instanceof NomadFatal) {
9327
+ fail(err.message);
9328
+ process.exit(err.code);
9329
+ }
9330
+ const issuesUrl = package_default.bugs?.url ?? "https://github.com/funkadelic/claude-nomad/issues";
9331
+ handleCrash(err, process.argv, {
9332
+ version: package_default.version,
9333
+ platform: process.platform,
9334
+ issuesUrl
9335
+ });
9336
+ process.exit(EXIT.GENERIC_FAILURE);
9337
+ }
9338
+ process.on("uncaughtException", handleTopLevelError);
9339
+ process.on("unhandledRejection", handleTopLevelError);
8644
9340
  var h = home();
8645
9341
  if (!h) {
8646
9342
  fail(
8647
9343
  "could not determine home directory (HOME env unset and no uid mapping). Set HOME and retry."
8648
9344
  );
8649
- process.exit(1);
9345
+ process.exit(EXIT.GENERIC_FAILURE);
8650
9346
  }
8651
9347
  try {
9348
+ if (process.env.NOMAD_TEST_FORCE_CRASH) {
9349
+ throw new Error("forced test crash (NOMAD_TEST_FORCE_CRASH)");
9350
+ }
9351
+ if (process.env.NOMAD_TEST_FORCE_ASYNC_CRASH) {
9352
+ setImmediate(() => {
9353
+ void Promise.reject(new Error("forced test async crash (NOMAD_TEST_FORCE_ASYNC_CRASH)"));
9354
+ });
9355
+ }
8652
9356
  const cmd = process.argv[2];
8653
9357
  switch (cmd) {
8654
9358
  case "--version":
8655
9359
  if (process.argv.length !== 3) {
8656
9360
  console.error("usage: nomad --version (no extra arguments)");
8657
- process.exit(1);
9361
+ process.exit(EXIT.USAGE);
8658
9362
  }
8659
9363
  console.log(package_default.version);
8660
9364
  break;
@@ -8662,7 +9366,7 @@ try {
8662
9366
  const pullArgs = parsePullArgs(process.argv);
8663
9367
  if (pullArgs === null) {
8664
9368
  console.error("usage: nomad pull [--dry-run] [--force-remote]");
8665
- process.exit(1);
9369
+ process.exit(EXIT.USAGE);
8666
9370
  }
8667
9371
  cmdPull({ dryRun: pullArgs.dryRun, forceRemote: pullArgs.forceRemote });
8668
9372
  break;
@@ -8673,7 +9377,7 @@ try {
8673
9377
  console.error(
8674
9378
  "usage: nomad push [--dry-run] [--full-scan] [--redact-all] [--allow <rule>] [--allow-all]"
8675
9379
  );
8676
- process.exit(1);
9380
+ process.exit(EXIT.USAGE);
8677
9381
  }
8678
9382
  await cmdPush({
8679
9383
  dryRun: pushArgs.dryRun,
@@ -8688,7 +9392,7 @@ try {
8688
9392
  const syncArgs = parseSyncArgs(process.argv);
8689
9393
  if (syncArgs === null) {
8690
9394
  console.error("usage: nomad sync [--dry-run] [--verbose|--all|-v]");
8691
- process.exit(1);
9395
+ process.exit(EXIT.USAGE);
8692
9396
  }
8693
9397
  await cmdSync({ dryRun: syncArgs.dryRun, verbose: syncArgs.verbose });
8694
9398
  break;
@@ -8697,7 +9401,7 @@ try {
8697
9401
  const initArgs = parseInitArgs(process.argv);
8698
9402
  if (initArgs === null) {
8699
9403
  console.error("usage: nomad init [--snapshot] [--keep-actions] [--repo <name>]");
8700
- process.exit(1);
9404
+ process.exit(EXIT.USAGE);
8701
9405
  }
8702
9406
  const snapshot = isAlreadyInitialized(repoHome()) ? initArgs.snapshot : await resolveSnapshotChoice(initArgs.snapshot, claudeHome());
8703
9407
  cmdInit({
@@ -8710,14 +9414,14 @@ try {
8710
9414
  case "diff":
8711
9415
  if (process.argv.length > 3) {
8712
9416
  console.error("usage: nomad diff");
8713
- process.exit(1);
9417
+ process.exit(EXIT.USAGE);
8714
9418
  }
8715
9419
  cmdDiff();
8716
9420
  break;
8717
9421
  case "update": {
8718
9422
  if (process.argv.length !== 3) {
8719
9423
  console.error("usage: nomad update");
8720
- process.exit(1);
9424
+ process.exit(EXIT.USAGE);
8721
9425
  }
8722
9426
  cmdUpdate(package_default.version);
8723
9427
  break;
@@ -8727,7 +9431,7 @@ try {
8727
9431
  const sub = process.argv[4];
8728
9432
  if (typeof name !== "string" || name.length === 0 || name.startsWith("-") || sub !== void 0 && (sub !== "--dry-run" || process.argv.length !== 5)) {
8729
9433
  console.error("usage: nomad adopt <name> [--dry-run]");
8730
- process.exit(1);
9434
+ process.exit(EXIT.USAGE);
8731
9435
  }
8732
9436
  cmdAdopt(name, { dryRun: sub === "--dry-run" });
8733
9437
  break;
@@ -8736,7 +9440,7 @@ try {
8736
9440
  const ejectArgs = parseEjectArgs(process.argv);
8737
9441
  if (ejectArgs === null) {
8738
9442
  console.error("usage: nomad eject [--dry-run]");
8739
- process.exit(1);
9443
+ process.exit(EXIT.USAGE);
8740
9444
  }
8741
9445
  cmdEject({ dryRun: ejectArgs.dryRun });
8742
9446
  break;
@@ -8745,7 +9449,7 @@ try {
8745
9449
  const captureArgs = parseCaptureSettingsArgs(process.argv);
8746
9450
  if (captureArgs === null) {
8747
9451
  console.error("usage: nomad capture-settings [--host] [--dry-run] [--yes]");
8748
- process.exit(1);
9452
+ process.exit(EXIT.USAGE);
8749
9453
  }
8750
9454
  await cmdCaptureSettings({
8751
9455
  host: captureArgs.host,
@@ -8760,7 +9464,7 @@ try {
8760
9464
  console.error(
8761
9465
  "usage: nomad doctor [--check-shared] [--check-schema] [--check-remote] [--verbose|--all|-v] | --resume-cmd <session-id>"
8762
9466
  );
8763
- process.exit(1);
9467
+ process.exit(EXIT.USAGE);
8764
9468
  } else if (parsed.kind === "resume") {
8765
9469
  resumeCmd(parsed.id);
8766
9470
  } else {
@@ -8777,7 +9481,7 @@ try {
8777
9481
  const id = process.argv[3];
8778
9482
  if (process.argv.length !== 4 || typeof id !== "string" || !/^\w[\w-]{0,127}$/.test(id)) {
8779
9483
  console.error("usage: nomad drop-session <id>");
8780
- process.exit(1);
9484
+ process.exit(EXIT.USAGE);
8781
9485
  }
8782
9486
  cmdDropSession(id);
8783
9487
  break;
@@ -8786,7 +9490,7 @@ try {
8786
9490
  const redactArgs = parseRedactArgs(process.argv);
8787
9491
  if (redactArgs === null) {
8788
9492
  console.error("usage: nomad redact <session-id> [--rule <rule-id>] [--dry-run]");
8789
- process.exit(1);
9493
+ process.exit(EXIT.USAGE);
8790
9494
  }
8791
9495
  cmdRedact(redactArgs);
8792
9496
  break;
@@ -8795,7 +9499,7 @@ try {
8795
9499
  const allowArgs = parseAllowArgs(process.argv);
8796
9500
  if (allowArgs === null) {
8797
9501
  console.error("usage: nomad allow <fingerprint> [<fingerprint>...]");
8798
- process.exit(1);
9502
+ process.exit(EXIT.USAGE);
8799
9503
  }
8800
9504
  cmdAllow(allowArgs);
8801
9505
  break;
@@ -8804,19 +9508,15 @@ try {
8804
9508
  const cleanArgs = parseCleanArgs(process.argv);
8805
9509
  if (cleanArgs === null) {
8806
9510
  console.error("usage: nomad clean --backups [--dry-run] [--older-than <dur> | --keep <N>]");
8807
- process.exit(1);
9511
+ process.exit(EXIT.USAGE);
8808
9512
  }
8809
9513
  cmdClean(cleanArgs);
8810
9514
  break;
8811
9515
  }
8812
9516
  default:
8813
9517
  console.error(DEFAULT_HELP);
8814
- process.exit(1);
9518
+ process.exit(EXIT.USAGE);
8815
9519
  }
8816
9520
  } catch (err) {
8817
- if (err instanceof NomadFatal) {
8818
- fail(err.message);
8819
- process.exit(1);
8820
- }
8821
- throw err;
9521
+ handleTopLevelError(err);
8822
9522
  }