claude-nomad 0.47.0 → 0.47.1

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,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.47.1](https://github.com/funkadelic/claude-nomad/compare/v0.47.0...v0.47.1) (2026-06-09)
4
+
5
+
6
+ ### Fixed
7
+
8
+ * **extras:** preserve host-local deny-set files on .claude pull ([#276](https://github.com/funkadelic/claude-nomad/issues/276)) ([3742c3e](https://github.com/funkadelic/claude-nomad/commit/3742c3e4067c1c986e7f182011581d4c867847b0))
9
+
3
10
  ## [0.47.0](https://github.com/funkadelic/claude-nomad/compare/v0.46.0...v0.47.0) (2026-06-09)
4
11
 
5
12
 
package/dist/nomad.mjs CHANGED
@@ -4240,7 +4240,7 @@ function listDivergingFiles(a, b) {
4240
4240
 
4241
4241
  // src/extras-sync.core.ts
4242
4242
  init_config();
4243
- import { cpSync as cpSync6, existsSync as existsSync30, rmSync as rmSync10 } from "node:fs";
4243
+ import { cpSync as cpSync6, existsSync as existsSync30, lstatSync as lstatSync8, readdirSync as readdirSync11, rmSync as rmSync10 } from "node:fs";
4244
4244
  import { basename, join as join36 } from "node:path";
4245
4245
 
4246
4246
  // src/extras-sync.guards.ts
@@ -4314,6 +4314,36 @@ function copyExtrasFiltered(src, dst, blockSet) {
4314
4314
  filter: (srcEntry) => srcEntry === src || !blockSet.has(basename(srcEntry))
4315
4315
  });
4316
4316
  }
4317
+ function prunePreservingDenied(src, dst, blockSet) {
4318
+ for (const name of readdirSync11(dst)) {
4319
+ if (blockSet.has(name)) continue;
4320
+ const dstPath = join36(dst, name);
4321
+ const srcStat = lstatSync8(join36(src, name), { throwIfNoEntry: false });
4322
+ if (srcStat === void 0) {
4323
+ rmSync10(dstPath, { recursive: true, force: true });
4324
+ continue;
4325
+ }
4326
+ const dstStat = lstatSync8(dstPath);
4327
+ if (srcStat.isDirectory() && dstStat.isDirectory()) {
4328
+ prunePreservingDenied(join36(src, name), dstPath, blockSet);
4329
+ } else if (srcStat.isDirectory() !== dstStat.isDirectory()) {
4330
+ rmSync10(dstPath, { recursive: true, force: true });
4331
+ }
4332
+ }
4333
+ }
4334
+ function copyExtrasFilteredPreserving(src, dst, blockSet) {
4335
+ const dstStat = lstatSync8(dst, { throwIfNoEntry: false });
4336
+ if (dstStat !== void 0) {
4337
+ if (dstStat.isDirectory()) prunePreservingDenied(src, dst, blockSet);
4338
+ else rmSync10(dst, { recursive: true, force: true });
4339
+ }
4340
+ cpSync6(src, dst, {
4341
+ recursive: true,
4342
+ force: true,
4343
+ verbatimSymlinks: true,
4344
+ filter: (srcEntry) => srcEntry === src || !blockSet.has(basename(srcEntry))
4345
+ });
4346
+ }
4317
4347
 
4318
4348
  // src/extras-sync.ts
4319
4349
  init_utils();
@@ -4381,12 +4411,16 @@ function remapExtrasPull(ts, opts = {}) {
4381
4411
  // Snapshot the host-side dst BEFORE copyExtras clobbers it. Anchor on
4382
4412
  // localRoot so the backup tree mirrors the project layout.
4383
4413
  (dst, localRoot) => backupExtrasWrite(dst, ts, localRoot),
4384
- // Pull filters `.claude` against its NEVER_SYNC boundary so a repo poisoned
4385
- // out-of-band cannot restore a blocked per-host file (e.g. settings.local.json)
4386
- // onto this host. Other extras use the exact-mirror copyExtras: the repo is
4387
- // clean once push filters, and exact mirror is the documented restore for
4388
- // `.planning`.
4389
- (src, dst, dirname7) => dirname7 === ".claude" ? copyExtrasFiltered(src, dst, extrasDenySet(dirname7)) : copyExtras(src, dst)
4414
+ // Pull routes `.claude` through copyExtrasFilteredPreserving so host-local
4415
+ // deny-set files already on disk (e.g. settings.local.json) are preserved
4416
+ // instead of being wiped by a blanket rmSync. The same deny-set filter still
4417
+ // strips blocked basenames from the src copy (defense-in-depth: a repo
4418
+ // poisoned out-of-band cannot restore a blocked per-host file). Synced
4419
+ // non-deny files that are absent from src are still mirror-pruned. This
4420
+ // preservation is `.claude`-only; `.planning` and `CLAUDE.md` use the
4421
+ // exact-mirror copyExtras (documented restore semantics; they rarely carry
4422
+ // host-local files, so the exact mirror is the correct default).
4423
+ (src, dst, dirname7) => dirname7 === ".claude" ? copyExtrasFilteredPreserving(src, dst, extrasDenySet(dirname7)) : copyExtras(src, dst)
4390
4424
  );
4391
4425
  return { unmapped, skipped, pulled: done, wouldPull: would };
4392
4426
  }
@@ -4417,7 +4451,7 @@ init_config();
4417
4451
  init_utils();
4418
4452
  init_utils_fs();
4419
4453
  init_utils_json();
4420
- import { existsSync as existsSync33, lstatSync as lstatSync8, rmSync as rmSync11 } from "node:fs";
4454
+ import { existsSync as existsSync33, lstatSync as lstatSync9, rmSync as rmSync11 } from "node:fs";
4421
4455
  import { join as join39 } from "node:path";
4422
4456
  function emitAutoMove(onPreview, linkPath, ts, name) {
4423
4457
  if (onPreview) {
@@ -4434,14 +4468,14 @@ function emitCreate(onPreview, from, to) {
4434
4468
  }
4435
4469
  }
4436
4470
  function isAlreadySymlink(linkPath) {
4437
- return existsSync33(linkPath) && lstatSync8(linkPath).isSymbolicLink();
4471
+ return existsSync33(linkPath) && lstatSync9(linkPath).isSymbolicLink();
4438
4472
  }
4439
4473
  function runAutoMovePasses(linkNames, claude, repo, ts, dryRun, onPreview) {
4440
4474
  for (const name of linkNames) {
4441
4475
  const linkPath = join39(claude, name);
4442
4476
  const target = join39(repo, "shared", name);
4443
4477
  if (!existsSync33(linkPath)) continue;
4444
- if (lstatSync8(linkPath).isSymbolicLink()) continue;
4478
+ if (lstatSync9(linkPath).isSymbolicLink()) continue;
4445
4479
  if (!existsSync33(target)) continue;
4446
4480
  if (dryRun) {
4447
4481
  emitAutoMove(onPreview, linkPath, ts, name);
@@ -5146,7 +5180,7 @@ init_color();
5146
5180
  init_config();
5147
5181
  init_config_sharedDirs_guard();
5148
5182
  import { randomBytes as randomBytes2 } from "node:crypto";
5149
- import { copyFileSync, existsSync as existsSync36, mkdirSync as mkdirSync9, readdirSync as readdirSync11, rmSync as rmSync12 } from "node:fs";
5183
+ import { copyFileSync, existsSync as existsSync36, mkdirSync as mkdirSync9, readdirSync as readdirSync12, rmSync as rmSync12 } from "node:fs";
5150
5184
  import { homedir as homedir5 } from "node:os";
5151
5185
  import { join as join42 } from "node:path";
5152
5186
  init_push_leak_verdict();
@@ -5166,7 +5200,7 @@ function stageSessions(tmpRoot, map) {
5166
5200
  const localProjects = join42(claudeHome(), "projects");
5167
5201
  if (!existsSync36(localProjects)) return 0;
5168
5202
  let staged = 0;
5169
- for (const dir of readdirSync11(localProjects)) {
5203
+ for (const dir of readdirSync12(localProjects)) {
5170
5204
  const logical = reverse.get(dir);
5171
5205
  if (!logical) continue;
5172
5206
  copyDirJsonlOnly(join42(localProjects, dir), join42(tmpRoot, "shared", "projects", logical));
@@ -5830,7 +5864,7 @@ function parsePushArgs(argv) {
5830
5864
  // package.json
5831
5865
  var package_default = {
5832
5866
  name: "claude-nomad",
5833
- version: "0.47.0",
5867
+ version: "0.47.1",
5834
5868
  type: "module",
5835
5869
  description: "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
5836
5870
  keywords: [
@@ -6031,7 +6065,7 @@ var DEFAULT_HELP = [
6031
6065
  init_config();
6032
6066
  init_utils();
6033
6067
  init_utils_json();
6034
- import { existsSync as existsSync41, readFileSync as readFileSync14, readdirSync as readdirSync12 } from "node:fs";
6068
+ import { existsSync as existsSync41, readFileSync as readFileSync14, readdirSync as readdirSync13 } from "node:fs";
6035
6069
  import { join as join47 } from "node:path";
6036
6070
  function resumeCmd(sessionId) {
6037
6071
  if (!/^[A-Za-z0-9_-]+$/.test(sessionId) || sessionId.length > 128) {
@@ -6076,7 +6110,7 @@ function resumeCmd(sessionId) {
6076
6110
  console.log(`cd ${shQuote(hit.localPath)} && claude --resume ${shQuote(sessionId)}`);
6077
6111
  }
6078
6112
  function findTranscriptPath(projectsRoot, sessionId) {
6079
- for (const dir of readdirSync12(projectsRoot)) {
6113
+ for (const dir of readdirSync13(projectsRoot)) {
6080
6114
  const candidate = join47(projectsRoot, dir, `${sessionId}.jsonl`);
6081
6115
  if (existsSync41(candidate)) return candidate;
6082
6116
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-nomad",
3
- "version": "0.47.0",
3
+ "version": "0.47.1",
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": [