claude-nomad 0.62.3 → 0.62.4

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/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.62.4](https://github.com/funkadelic/claude-nomad/compare/v0.62.3...v0.62.4) (2026-07-21)
4
+
5
+
6
+ ### Fixed
7
+
8
+ * **sync:** compute the dry-run pull preview after the fetch ([#451](https://github.com/funkadelic/claude-nomad/issues/451)) ([8118625](https://github.com/funkadelic/claude-nomad/commit/81186258896081a7b2a2585f40eed67bd3e5c6b6))
9
+
10
+
11
+ ### Changed
12
+
13
+ * **pull:** split commands.pull.recovery.ts on the force-remote seam ([#456](https://github.com/funkadelic/claude-nomad/issues/456)) ([1c1875b](https://github.com/funkadelic/claude-nomad/commit/1c1875b40e67512329e9a2d1215afa7667c29d1c))
14
+ * **push:** split redact-all batch from recovery actions ([#457](https://github.com/funkadelic/claude-nomad/issues/457)) ([b104472](https://github.com/funkadelic/claude-nomad/commit/b104472b87d3f847546ee938900bcaa4ea1abdd8))
15
+
16
+
17
+ ### Testing
18
+
19
+ * precompile the CLI once per vitest run ([#453](https://github.com/funkadelic/claude-nomad/issues/453)) ([e0b043b](https://github.com/funkadelic/claude-nomad/commit/e0b043b851ae9bd89da026199f70d723e213aed9))
20
+ * **pull:** reset process.exitCode in beforeEach as well as afterEach ([#450](https://github.com/funkadelic/claude-nomad/issues/450)) ([78943e7](https://github.com/funkadelic/claude-nomad/commit/78943e72a41d4a542a4d6528d995ae9f8a8452cc))
21
+
3
22
  ## [0.62.3](https://github.com/funkadelic/claude-nomad/compare/v0.62.2...v0.62.3) (2026-07-21)
4
23
 
5
24
 
package/dist/nomad.mjs CHANGED
@@ -5668,6 +5668,10 @@ function dispatchActions(findings, actions, opts) {
5668
5668
  dispatchOne(f, ctx);
5669
5669
  }
5670
5670
  }
5671
+
5672
+ // src/commands.push.recovery.redact-all.ts
5673
+ init_push_gitleaks_scan();
5674
+ init_utils();
5671
5675
  function redactAllDedupeKey(f) {
5672
5676
  const sid = sessionIdFromFinding(f);
5673
5677
  if (sid !== null) return sid;
@@ -7238,7 +7242,7 @@ function buildSettingsSectionForPreview(result) {
7238
7242
  function computePreview(ts, map, verb = "pull") {
7239
7243
  const repo = repoHome();
7240
7244
  const claude = claudeHome();
7241
- console.log(`would pull on host=${HOST} (dry-run; no mutation)`);
7245
+ console.log(`would pull on host=${HOST} (preview; nothing applied)`);
7242
7246
  console.log("");
7243
7247
  const links = section("Symlinks");
7244
7248
  applySharedLinks(ts, map, {
@@ -7286,10 +7290,11 @@ init_commands_pull_wedge();
7286
7290
 
7287
7291
  // src/commands.pull.recovery.ts
7288
7292
  init_config();
7289
- init_commands_pull_wedge();
7290
- init_exit_codes();
7291
7293
  init_utils();
7292
7294
  init_utils_fs();
7295
+
7296
+ // src/commands.pull.recovery.git.ts
7297
+ init_utils();
7293
7298
  import { execFileSync as execFileSync21 } from "node:child_process";
7294
7299
  function gitCapture(args, cwd) {
7295
7300
  return execFileSync21("git", args, {
@@ -7298,23 +7303,6 @@ function gitCapture(args, cwd) {
7298
7303
  maxBuffer: 64 * 1024 * 1024
7299
7304
  }).toString().trim();
7300
7305
  }
7301
- function isSyncedConfig(path) {
7302
- return PUSH_ALLOWED_STATIC.some(
7303
- (entry) => entry.endsWith("/") ? path.startsWith(entry) : path === entry
7304
- );
7305
- }
7306
- function classifyTouched(touched) {
7307
- const synced = [];
7308
- const toolSource = [];
7309
- for (const p of touched) {
7310
- if (isSyncedConfig(p)) {
7311
- synced.push(p);
7312
- } else {
7313
- toolSource.push(p);
7314
- }
7315
- }
7316
- return { synced, toolSource };
7317
- }
7318
7306
  function parsePorcelainZ(raw) {
7319
7307
  const tracked = [];
7320
7308
  const untracked = [];
@@ -7343,6 +7331,25 @@ function parsePorcelainZ(raw) {
7343
7331
  function parseDirtyPaths(repo, opts = {}) {
7344
7332
  return parsePorcelainZ(gitStatusPorcelainZ(repo, opts));
7345
7333
  }
7334
+
7335
+ // src/commands.pull.recovery.ts
7336
+ function isSyncedConfig(path) {
7337
+ return PUSH_ALLOWED_STATIC.some(
7338
+ (entry) => entry.endsWith("/") ? path.startsWith(entry) : path === entry
7339
+ );
7340
+ }
7341
+ function classifyTouched(touched) {
7342
+ const synced = [];
7343
+ const toolSource = [];
7344
+ for (const p of touched) {
7345
+ if (isSyncedConfig(p)) {
7346
+ synced.push(p);
7347
+ } else {
7348
+ toolSource.push(p);
7349
+ }
7350
+ }
7351
+ return { synced, toolSource };
7352
+ }
7346
7353
  function buildRecoverySummary(branchName, strandedLog, untracked) {
7347
7354
  const strandedLines = strandedLog.split("\n").filter(Boolean).map((l) => ` ${l}`).join("\n");
7348
7355
  const parts = [`parked stranded commits on ${branchName}`];
@@ -7367,26 +7374,6 @@ function freshStrandedBranch(repo) {
7367
7374
  while (exists(`${base}-${n}`)) n++;
7368
7375
  return `${base}-${n}`;
7369
7376
  }
7370
- function recoverUnmergedIndex(repo) {
7371
- const conflicted = new Set(
7372
- gitCapture(["diff", "--diff-filter=U", "--name-only", "-z"], repo).split("\0").filter(Boolean)
7373
- );
7374
- gitOrFatal(["reset", "--mixed", "HEAD"], "git reset --mixed HEAD", repo);
7375
- const { tracked, untracked } = parseDirtyPaths(repo, { untrackedAll: true });
7376
- const present = /* @__PURE__ */ new Set([...tracked, ...untracked]);
7377
- const residual = [...conflicted].filter((p) => present.has(p));
7378
- if (orphanedAutostashPresent(repo)) {
7379
- log(
7380
- 'orphaned autostash preserved in the stash list; run "git stash pop" to restore or "git stash drop" to discard it, then re-run "nomad pull"'
7381
- );
7382
- }
7383
- if (residual.length > 0) {
7384
- die(
7385
- "index cleared, but these files still carry unresolved conflict content from the torn-down rebase or merge:\n" + residual.map((p) => ` ${p}`).join("\n") + '\n\nThe repo is no longer wedged, so nothing else is blocked. Nothing was applied to ~/.claude/: pulling now would publish that content to your live config.\n\nResolve each file above (remove any <<<<<<< / ======= / >>>>>>> markers and keep the content you want; a file left untracked was deleted upstream, so keep or delete it deliberately), commit or checkout the result, then re-run "nomad pull".',
7386
- { code: EXIT.CONFLICT }
7387
- );
7388
- }
7389
- }
7390
7377
  function recoverForceRemote(mode, repo) {
7391
7378
  if (mode === "merge") {
7392
7379
  gitOrFatal(["merge", "--abort"], "git merge --abort", repo);
@@ -7416,6 +7403,31 @@ function recoverForceRemote(mode, repo) {
7416
7403
  log(buildRecoverySummary(branchName, strandedLog, untracked));
7417
7404
  }
7418
7405
 
7406
+ // src/commands.pull.recovery.unmerged.ts
7407
+ init_exit_codes();
7408
+ init_commands_pull_wedge();
7409
+ init_utils();
7410
+ function recoverUnmergedIndex(repo) {
7411
+ const conflicted = new Set(
7412
+ gitCapture(["diff", "--diff-filter=U", "--name-only", "-z"], repo).split("\0").filter(Boolean)
7413
+ );
7414
+ gitOrFatal(["reset", "--mixed", "HEAD"], "git reset --mixed HEAD", repo);
7415
+ const { tracked, untracked } = parseDirtyPaths(repo, { untrackedAll: true });
7416
+ const present = /* @__PURE__ */ new Set([...tracked, ...untracked]);
7417
+ const residual = [...conflicted].filter((p) => present.has(p));
7418
+ if (orphanedAutostashPresent(repo)) {
7419
+ log(
7420
+ 'orphaned autostash preserved in the stash list; run "git stash pop" to restore or "git stash drop" to discard it, then re-run "nomad pull"'
7421
+ );
7422
+ }
7423
+ if (residual.length > 0) {
7424
+ die(
7425
+ "index cleared, but these files still carry unresolved conflict content from the torn-down rebase or merge:\n" + residual.map((p) => ` ${p}`).join("\n") + '\n\nThe repo is no longer wedged, so nothing else is blocked. Nothing was applied to ~/.claude/: pulling now would publish that content to your live config.\n\nResolve each file above (remove any <<<<<<< / ======= / >>>>>>> markers and keep the content you want; a file left untracked was deleted upstream, so keep or delete it deliberately), commit or checkout the result, then re-run "nomad pull".',
7426
+ { code: EXIT.CONFLICT }
7427
+ );
7428
+ }
7429
+ }
7430
+
7419
7431
  // src/commands.pull.ts
7420
7432
  init_exit_codes();
7421
7433
  init_utils();
@@ -7507,7 +7519,6 @@ function runPullCore(opts = {}) {
7507
7519
  const divergedKeptLocal = divergenceCheckExtras(ts, dryRun ? prePostHeads : void 0);
7508
7520
  if (dryRun) {
7509
7521
  computePreview(ts, map, "pull");
7510
- log("dry-run complete; no mutation");
7511
7522
  return { tag: "dry" };
7512
7523
  }
7513
7524
  const { sections, localOnly, settingsLabel, unmapped, extrasSkipped } = buildWetPullSections(
@@ -7538,6 +7549,7 @@ function cmdPull(opts = {}) {
7538
7549
  try {
7539
7550
  const result = runPullCore(opts);
7540
7551
  if (result.tag === "wet") renderTree(result.sections);
7552
+ else log("dry-run complete; nothing applied to ~/.claude/");
7541
7553
  } catch (err) {
7542
7554
  if (err instanceof NomadFatal) {
7543
7555
  fail(err.message);
@@ -8134,8 +8146,6 @@ import { join as join59 } from "node:path";
8134
8146
  init_config();
8135
8147
  init_color();
8136
8148
  init_utils();
8137
- init_utils_fs();
8138
- init_utils_json();
8139
8149
  function isNoopSync(pull, pushOutcome) {
8140
8150
  if (!pushOutcome.ok) return false;
8141
8151
  if (pull.localOnly !== 0 || pull.divergedKeptLocal !== 0) return false;
@@ -8256,19 +8266,16 @@ async function runSyncWet(verbose) {
8256
8266
  const pushOutcome = await runSyncPushHalf();
8257
8267
  renderWetSync(pull, pushOutcome, verbose);
8258
8268
  }
8259
- async function runSyncDryRun(repo, backup) {
8260
- const ts = freshBackupTs(backup);
8261
- const mapPath = join59(repo, "path-map.json");
8262
- const map = existsSync48(mapPath) ? readPathMap(mapPath) : { projects: {} };
8263
- computePreview(ts, map, "pull");
8269
+ async function runSyncDryRun() {
8270
+ runPullCore({ dryRun: true });
8264
8271
  log("push preview below is computed against pre-pull state (a real sync pushes after pull)");
8265
8272
  await runPushCore({ dryRun: true });
8273
+ log("dry-run complete; nothing applied to ~/.claude/, nothing pushed");
8266
8274
  }
8267
8275
  async function cmdSync(opts = {}) {
8268
8276
  const dryRun = opts.dryRun === true;
8269
8277
  const verbose = opts.verbose === true;
8270
8278
  const repo = repoHome();
8271
- const backup = backupBase();
8272
8279
  if (!existsSync48(repo)) die(`repo not cloned at ${repo}`);
8273
8280
  if (!existsSync48(join59(repo, "shared", "settings.base.json"))) {
8274
8281
  die("repo not initialized; run 'nomad init' to scaffold");
@@ -8277,7 +8284,7 @@ async function cmdSync(opts = {}) {
8277
8284
  if (handle === null) process.exit(0);
8278
8285
  try {
8279
8286
  if (dryRun) {
8280
- await runSyncDryRun(repo, backup);
8287
+ await runSyncDryRun();
8281
8288
  } else {
8282
8289
  await runSyncWet(verbose);
8283
8290
  }
@@ -9089,7 +9096,7 @@ function parseSyncArgs(argv) {
9089
9096
  // package.json
9090
9097
  var package_default = {
9091
9098
  name: "claude-nomad",
9092
- version: "0.62.3",
9099
+ version: "0.62.4",
9093
9100
  type: "module",
9094
9101
  description: "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
9095
9102
  keywords: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-nomad",
3
- "version": "0.62.3",
3
+ "version": "0.62.4",
4
4
  "type": "module",
5
5
  "description": "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
6
6
  "keywords": [