claude-nomad 0.64.0 → 0.64.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 +33 -0
- package/dist/nomad.mjs +307 -90
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.64.1](https://github.com/funkadelic/claude-nomad/compare/v0.64.0...v0.64.1) (2026-08-07)
|
|
4
|
+
|
|
5
|
+
### What's new
|
|
6
|
+
|
|
7
|
+
- **A stray dot or space at the end of a file name no longer hides a secret.** nomad never copies
|
|
8
|
+
password and key files (things like `.env`, `id_rsa`, or `server.pem`) out of the project folders
|
|
9
|
+
you sync. It spots them by name, and a name with an extra dot or space on the end, such as
|
|
10
|
+
`.env.`, did not match, so a file like that could end up in your sync repo. It matches now, and so
|
|
11
|
+
do the other files nomad always keeps out, such as `settings.local.json`. Nothing changes in how
|
|
12
|
+
you use nomad. It simply catches more ways of writing the same names it already blocked.
|
|
13
|
+
- **A `.claude` folder is recognized however it is capitalized.** If a project had a folder named
|
|
14
|
+
`.Claude` instead of `.claude`, nomad checked what was inside it less carefully than it should
|
|
15
|
+
have. Both are treated the same now.
|
|
16
|
+
- **A folder name nomad will never sync is turned down as soon as you add it.** You can ask nomad to
|
|
17
|
+
sync extra folders by listing their names in `path-map.json`. If a name you list is one nomad
|
|
18
|
+
refuses to sync, because it looks like a password file, or because it is a name that causes
|
|
19
|
+
trouble on one of the machines you sync to, you are told straight away and told why. Before,
|
|
20
|
+
nothing was said until a later `nomad push` stopped without explaining itself. `nomad doctor` also
|
|
21
|
+
lists any names in this situation and what to do about them.
|
|
22
|
+
|
|
23
|
+
All of this applies on macOS, Linux, WSL2, and native Windows.
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
* **config:** close the trailing-dot bypass in the secret-name guard ([#495](https://github.com/funkadelic/claude-nomad/issues/495)) ([9aaf5d9](https://github.com/funkadelic/claude-nomad/commit/9aaf5d958027a3557974c1ab32254c6171aea631))
|
|
29
|
+
* reject a secret-shaped name as a shared dir ([#493](https://github.com/funkadelic/claude-nomad/issues/493)) ([3054464](https://github.com/funkadelic/claude-nomad/commit/3054464c91c974a7abdad24b2067bab8386e1dfd))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
|
|
34
|
+
* **release:** publish the release body from CHANGELOG.md ([#496](https://github.com/funkadelic/claude-nomad/issues/496)) ([e7bff07](https://github.com/funkadelic/claude-nomad/commit/e7bff07504935e563936733eae1146dc6eeaca83))
|
|
35
|
+
|
|
3
36
|
## [0.64.0](https://github.com/funkadelic/claude-nomad/compare/v0.63.2...v0.64.0) (2026-08-05)
|
|
4
37
|
|
|
5
38
|
### What's new
|
package/dist/nomad.mjs
CHANGED
|
@@ -42,11 +42,21 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
42
42
|
));
|
|
43
43
|
|
|
44
44
|
// src/config.never-sync.ts
|
|
45
|
+
function stripTrailingDotsAndWhitespace(name) {
|
|
46
|
+
let end = name.length;
|
|
47
|
+
while (end > 0 && (name[end - 1] === "." || /\s/.test(name[end - 1]))) end -= 1;
|
|
48
|
+
return name.slice(0, end);
|
|
49
|
+
}
|
|
45
50
|
function isSecretFileName(name) {
|
|
46
|
-
|
|
51
|
+
const stripped = stripTrailingDotsAndWhitespace(name);
|
|
52
|
+
return SECRET_FILE_PATTERNS.some((re) => re.test(stripped));
|
|
47
53
|
}
|
|
48
54
|
function isDeniedName(blockSet, name) {
|
|
49
|
-
|
|
55
|
+
const stripped = stripTrailingDotsAndWhitespace(name);
|
|
56
|
+
return blockSet.has(name) || blockSet.has(name.toLowerCase()) || blockSet.has(stripped) || blockSet.has(stripped.toLowerCase()) || isSecretFileName(name);
|
|
57
|
+
}
|
|
58
|
+
function isClaudeExtraName(name) {
|
|
59
|
+
return stripTrailingDotsAndWhitespace(name).toLowerCase() === ".claude";
|
|
50
60
|
}
|
|
51
61
|
var NEVER_SYNC, CLAUDE_EXTRA_NEVER_SYNC, SECRET_FILE_PATTERNS;
|
|
52
62
|
var init_config_never_sync = __esm({
|
|
@@ -302,14 +312,67 @@ function assertSafeLogical(logical) {
|
|
|
302
312
|
);
|
|
303
313
|
}
|
|
304
314
|
}
|
|
315
|
+
function validateSharedDirEntry(entry) {
|
|
316
|
+
if (typeof entry !== "string") {
|
|
317
|
+
return { reason: "not-a-string", message: "not a string" };
|
|
318
|
+
}
|
|
319
|
+
if (!SAFE_SEGMENT.test(entry) || entry === "." || entry === "..") {
|
|
320
|
+
return {
|
|
321
|
+
reason: "not-a-segment",
|
|
322
|
+
message: 'not a single path segment (contains a path separator, an unsupported character, "." or "..")'
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
const addressed = stripTrailingDots(entry);
|
|
326
|
+
const named = classifyDeniedName(addressed);
|
|
327
|
+
if (named !== null) {
|
|
328
|
+
return addressed === entry ? named : {
|
|
329
|
+
reason: named.reason,
|
|
330
|
+
message: `a trailing-dot name shadowing ${named.message}`
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
if (entry.endsWith(".")) {
|
|
334
|
+
return {
|
|
335
|
+
reason: "win32-alias",
|
|
336
|
+
message: 'a trailing-dot name (git for Windows refuses to create or check out a path ending in ".")'
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
function stripTrailingDots(name) {
|
|
342
|
+
let end = name.length;
|
|
343
|
+
while (end > 0 && name[end - 1] === ".") end -= 1;
|
|
344
|
+
return name.slice(0, end);
|
|
345
|
+
}
|
|
346
|
+
function classifyDeniedName(name) {
|
|
347
|
+
const folded = name.toLowerCase();
|
|
348
|
+
if (NEVER_SYNC.has(name) || NEVER_SYNC.has(folded)) {
|
|
349
|
+
return { reason: "never-sync", message: "a never-sync name" };
|
|
350
|
+
}
|
|
351
|
+
if (RESERVED_SHARED.has(name) || RESERVED_SHARED_FOLDED.has(folded)) {
|
|
352
|
+
return { reason: "reserved", message: "a reserved shared/ name" };
|
|
353
|
+
}
|
|
354
|
+
const dot = folded.indexOf(".");
|
|
355
|
+
const stem = dot === -1 ? folded : folded.slice(0, dot);
|
|
356
|
+
if (WIN32_DEVICE_NAMES.has(stem)) {
|
|
357
|
+
return { reason: "reserved", message: "a reserved Windows device name" };
|
|
358
|
+
}
|
|
359
|
+
if (isSecretFileName(name)) {
|
|
360
|
+
return {
|
|
361
|
+
reason: "secret-shaped",
|
|
362
|
+
message: "a credential-shaped filename (.env, id_rsa, credentials, *.pem, *.key and similar)"
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
function mayJoinRefusedEntry(entry, reason, remediable) {
|
|
368
|
+
if (UNJOINABLE_REASONS.has(reason)) return false;
|
|
369
|
+
if (entry.endsWith(".")) return true;
|
|
370
|
+
return remediable.has(reason);
|
|
371
|
+
}
|
|
305
372
|
function isValidSharedDir(entry) {
|
|
306
|
-
|
|
307
|
-
if (!SAFE_SEGMENT.test(entry) || entry === "." || entry === "..") return false;
|
|
308
|
-
if (NEVER_SYNC.has(entry)) return false;
|
|
309
|
-
if (RESERVED_SHARED.has(entry)) return false;
|
|
310
|
-
return true;
|
|
373
|
+
return validateSharedDirEntry(entry) === null;
|
|
311
374
|
}
|
|
312
|
-
var SAFE_LOGICAL, SAFE_SEGMENT, RESERVED_SHARED;
|
|
375
|
+
var SAFE_LOGICAL, SAFE_SEGMENT, RESERVED_SHARED, RESERVED_SHARED_FOLDED, WIN32_DEVICE_NAMES, UNJOINABLE_REASONS;
|
|
313
376
|
var init_config_sharedDirs_guard = __esm({
|
|
314
377
|
"src/config.sharedDirs.guard.ts"() {
|
|
315
378
|
"use strict";
|
|
@@ -331,6 +394,19 @@ var init_config_sharedDirs_guard = __esm({
|
|
|
331
394
|
"extras",
|
|
332
395
|
"projects"
|
|
333
396
|
]);
|
|
397
|
+
RESERVED_SHARED_FOLDED = new Set([...RESERVED_SHARED].map((name) => name.toLowerCase()));
|
|
398
|
+
WIN32_DEVICE_NAMES = /* @__PURE__ */ new Set([
|
|
399
|
+
"con",
|
|
400
|
+
"prn",
|
|
401
|
+
"aux",
|
|
402
|
+
"nul",
|
|
403
|
+
...Array.from({ length: 9 }, (_unused, i) => `com${i + 1}`),
|
|
404
|
+
...Array.from({ length: 9 }, (_unused, i) => `lpt${i + 1}`)
|
|
405
|
+
]);
|
|
406
|
+
UNJOINABLE_REASONS = /* @__PURE__ */ new Set([
|
|
407
|
+
"not-a-string",
|
|
408
|
+
"not-a-segment"
|
|
409
|
+
]);
|
|
334
410
|
}
|
|
335
411
|
});
|
|
336
412
|
|
|
@@ -541,19 +617,28 @@ function manifestPath() {
|
|
|
541
617
|
function sharedBaselinePath() {
|
|
542
618
|
return join(home(), ".cache", "claude-nomad", `shared-baseline-${encodeURIComponent(HOST)}.json`);
|
|
543
619
|
}
|
|
544
|
-
function allSharedLinks(map) {
|
|
620
|
+
function allSharedLinks(map, opts = {}) {
|
|
621
|
+
const emit = opts.quiet === true ? () => void 0 : warn;
|
|
622
|
+
const raw = map.sharedDirs;
|
|
623
|
+
if (raw !== void 0 && !Array.isArray(raw)) {
|
|
624
|
+
emit("sharedDirs in path-map.json is not an array; ignoring the whole field");
|
|
625
|
+
return [...SHARED_LINKS];
|
|
626
|
+
}
|
|
545
627
|
const extras = [];
|
|
546
|
-
for (const entry of
|
|
547
|
-
|
|
628
|
+
for (const entry of raw ?? []) {
|
|
629
|
+
const rejection = validateSharedDirEntry(entry);
|
|
630
|
+
if (rejection === null) {
|
|
548
631
|
extras.push(entry);
|
|
549
632
|
} else {
|
|
550
|
-
|
|
551
|
-
`sharedDirs entry ${JSON.stringify(entry)} is invalid (path separator, reserved name, or NEVER_SYNC); skipping`
|
|
552
|
-
);
|
|
633
|
+
emit(`sharedDirs entry ${JSON.stringify(entry)} rejected: ${rejection.message}; skipping`);
|
|
553
634
|
}
|
|
554
635
|
}
|
|
555
636
|
return [...SHARED_LINKS, ...extras];
|
|
556
637
|
}
|
|
638
|
+
function sharedDirEntries(map) {
|
|
639
|
+
const raw = map.sharedDirs;
|
|
640
|
+
return Array.isArray(raw) ? raw : [];
|
|
641
|
+
}
|
|
557
642
|
var SETTINGS_SCHEMA_URL, NPM_REGISTRY_LATEST_URL, GITLEAKS_PINNED_VERSION, GITLEAKS_SCAN_TIMEOUT_MS, HOST, SHARED_LINKS, GSD_PREFIX, GSD_DROPPED_NAMES, SUPPORTED_EXTRAS, ALWAYS_NEVER_SYNC, PUSH_ALLOWED_STATIC;
|
|
558
643
|
var init_config = __esm({
|
|
559
644
|
"src/config.ts"() {
|
|
@@ -1422,6 +1507,7 @@ var init_push_leak_verdict = __esm({
|
|
|
1422
1507
|
// src/commands.adopt.ts
|
|
1423
1508
|
init_config();
|
|
1424
1509
|
init_config_sharedDirs_guard();
|
|
1510
|
+
init_exit_codes();
|
|
1425
1511
|
import { cpSync as cpSync4, existsSync as existsSync5, lstatSync as lstatSync5, rmSync as rmSync4 } from "node:fs";
|
|
1426
1512
|
import { join as join6 } from "node:path";
|
|
1427
1513
|
|
|
@@ -1892,7 +1978,7 @@ function copyExtrasFileSkipDiverged(src, dst) {
|
|
|
1892
1978
|
copyExtras(src, dst);
|
|
1893
1979
|
}
|
|
1894
1980
|
function extrasDenySet(dirname14) {
|
|
1895
|
-
return dirname14
|
|
1981
|
+
return isClaudeExtraName(dirname14) ? CLAUDE_EXTRA_NEVER_SYNC : ALWAYS_NEVER_SYNC;
|
|
1896
1982
|
}
|
|
1897
1983
|
function copyExtrasFiltered(src, dst, blockSet) {
|
|
1898
1984
|
rmSync2(dst, { recursive: true, force: true });
|
|
@@ -2133,7 +2219,7 @@ function readMapIfPresent(repoHome2) {
|
|
|
2133
2219
|
return existsSync5(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
2134
2220
|
}
|
|
2135
2221
|
function isConfiguredTarget(name, map) {
|
|
2136
|
-
return SHARED_LINKS.includes(name) || (map.
|
|
2222
|
+
return SHARED_LINKS.includes(name) || sharedDirEntries(map).includes(name);
|
|
2137
2223
|
}
|
|
2138
2224
|
function isValidAdoptName(name) {
|
|
2139
2225
|
if (SHARED_LINKS.includes(name)) return true;
|
|
@@ -2155,6 +2241,13 @@ function performAdoptMove(name, linkPath, sharedTarget, repo, backup) {
|
|
|
2155
2241
|
}
|
|
2156
2242
|
function cmdAdopt(name, opts = {}) {
|
|
2157
2243
|
const dryRun = opts.dryRun === true;
|
|
2244
|
+
const rejection = validateSharedDirEntry(name);
|
|
2245
|
+
if (rejection !== null && rejection.reason === "secret-shaped") {
|
|
2246
|
+
throw new NomadFatal(
|
|
2247
|
+
`cannot adopt ${JSON.stringify(name)}: ${rejection.message}. If it is listed in sharedDirs in path-map.json, remove it there too.`,
|
|
2248
|
+
{ code: EXIT.GENERIC_FAILURE }
|
|
2249
|
+
);
|
|
2250
|
+
}
|
|
2158
2251
|
if (!isValidAdoptName(name)) {
|
|
2159
2252
|
fail(`invalid name: ${JSON.stringify(name)}`);
|
|
2160
2253
|
process.exit(1);
|
|
@@ -2568,6 +2661,7 @@ function cmdClean(opts, backupBase2 = backupBase()) {
|
|
|
2568
2661
|
|
|
2569
2662
|
// src/commands.eject.ts
|
|
2570
2663
|
init_config();
|
|
2664
|
+
init_config_sharedDirs_guard();
|
|
2571
2665
|
init_utils();
|
|
2572
2666
|
init_utils_fs();
|
|
2573
2667
|
init_utils_json();
|
|
@@ -2705,6 +2799,19 @@ function materializeOneOrDie(name, linkPath, sharedRoot, done) {
|
|
|
2705
2799
|
);
|
|
2706
2800
|
}
|
|
2707
2801
|
}
|
|
2802
|
+
var WIDENED_REASONS = /* @__PURE__ */ new Set([
|
|
2803
|
+
"never-sync",
|
|
2804
|
+
"reserved",
|
|
2805
|
+
"secret-shaped"
|
|
2806
|
+
]);
|
|
2807
|
+
function ejectNames(map) {
|
|
2808
|
+
const alreadyMaterialized = sharedDirEntries(map).filter((entry) => {
|
|
2809
|
+
if (typeof entry !== "string") return false;
|
|
2810
|
+
const rejection = validateSharedDirEntry(entry);
|
|
2811
|
+
return rejection === null || mayJoinRefusedEntry(entry, rejection.reason, WIDENED_REASONS);
|
|
2812
|
+
});
|
|
2813
|
+
return [.../* @__PURE__ */ new Set([...allSharedLinks(map), ...alreadyMaterialized])];
|
|
2814
|
+
}
|
|
2708
2815
|
function defaultEjectRoots() {
|
|
2709
2816
|
return { claudeHome: claudeHome(), repoHome: repoHome() };
|
|
2710
2817
|
}
|
|
@@ -2712,16 +2819,31 @@ function cmdEject(opts = {}, roots = defaultEjectRoots()) {
|
|
|
2712
2819
|
const dryRun = opts.dryRun === true;
|
|
2713
2820
|
const { claudeHome: claudeHome2, repoHome: repoHome2 } = roots;
|
|
2714
2821
|
const map = readMapIfPresent2(repoHome2);
|
|
2715
|
-
const names =
|
|
2822
|
+
const names = ejectNames(map);
|
|
2716
2823
|
const classifications = /* @__PURE__ */ new Map();
|
|
2717
2824
|
for (const name of names) {
|
|
2718
2825
|
classifications.set(name, classifyName(join11(claudeHome2, name)));
|
|
2719
2826
|
}
|
|
2827
|
+
const base = new Set(allSharedLinks(map, { quiet: true }));
|
|
2828
|
+
for (const name of names) {
|
|
2829
|
+
if (base.has(name)) continue;
|
|
2830
|
+
if (classifications.get(name) !== "materialize") continue;
|
|
2831
|
+
item(`processing rejected entry already present on this host: ${name}`);
|
|
2832
|
+
}
|
|
2720
2833
|
const dangling = names.filter((n) => classifications.get(n) === "dangling");
|
|
2721
|
-
|
|
2834
|
+
const danglingBase = dangling.filter((n) => base.has(n));
|
|
2835
|
+
const danglingRefused = dangling.filter((n) => !base.has(n));
|
|
2836
|
+
if (danglingBase.length > 0) {
|
|
2837
|
+
fail(
|
|
2838
|
+
`dangling symlink(s): ${danglingBase.join(", ")}. run \`nomad pull\` first to restore the missing target, then re-run \`nomad eject\``
|
|
2839
|
+
);
|
|
2840
|
+
}
|
|
2841
|
+
if (danglingRefused.length > 0) {
|
|
2722
2842
|
fail(
|
|
2723
|
-
`dangling symlink(s): ${
|
|
2843
|
+
`dangling symlink(s) for a refused name nomad cannot restore: ${danglingRefused.join(", ")}. recover the content by hand if you need it, then remove the dead link and re-run \`nomad eject\``
|
|
2724
2844
|
);
|
|
2845
|
+
}
|
|
2846
|
+
if (dangling.length > 0) {
|
|
2725
2847
|
process.exit(1);
|
|
2726
2848
|
}
|
|
2727
2849
|
const sharedRoot = resolveSharedRoot(repoHome2);
|
|
@@ -3066,7 +3188,7 @@ function classifySymlinkTarget(name, p) {
|
|
|
3066
3188
|
}
|
|
3067
3189
|
function reportSharedLinks(section2, map) {
|
|
3068
3190
|
const claude = claudeHome();
|
|
3069
|
-
for (const name of allSharedLinks(map)) {
|
|
3191
|
+
for (const name of allSharedLinks(map, { quiet: true })) {
|
|
3070
3192
|
const p = join13(claude, name);
|
|
3071
3193
|
const { line, fail: fail2, children } = classifySharedLink(name, p);
|
|
3072
3194
|
addItem(section2, line);
|
|
@@ -3158,8 +3280,9 @@ function reportHostOverrides(section2, base, settings) {
|
|
|
3158
3280
|
// src/commands.doctor.checks.pathmap.ts
|
|
3159
3281
|
init_color();
|
|
3160
3282
|
init_config();
|
|
3161
|
-
|
|
3162
|
-
import {
|
|
3283
|
+
init_config_sharedDirs_guard();
|
|
3284
|
+
import { existsSync as existsSync13, lstatSync as lstatSync9, readdirSync as readdirSync6, realpathSync as realpathSync2 } from "node:fs";
|
|
3285
|
+
import { join as join15, sep as sep4 } from "node:path";
|
|
3163
3286
|
init_utils_json();
|
|
3164
3287
|
function reportMappedProjects(section2, map) {
|
|
3165
3288
|
const mapped = Object.entries(map.projects).filter(([, hosts]) => hosts[HOST]);
|
|
@@ -3199,6 +3322,95 @@ function reportCurrentHostPathsMissing(section2, map) {
|
|
|
3199
3322
|
}
|
|
3200
3323
|
}
|
|
3201
3324
|
}
|
|
3325
|
+
function hasSharedLeftover(entry) {
|
|
3326
|
+
try {
|
|
3327
|
+
return lstatSync9(join15(repoHome(), "shared", entry), { throwIfNoEntry: false }) !== void 0;
|
|
3328
|
+
} catch {
|
|
3329
|
+
return false;
|
|
3330
|
+
}
|
|
3331
|
+
}
|
|
3332
|
+
var PROBEABLE_REASONS = /* @__PURE__ */ new Set([
|
|
3333
|
+
"never-sync",
|
|
3334
|
+
"secret-shaped"
|
|
3335
|
+
]);
|
|
3336
|
+
function classifyLocalLink(entry) {
|
|
3337
|
+
const linkPath = join15(claudeHome(), entry);
|
|
3338
|
+
let stat;
|
|
3339
|
+
try {
|
|
3340
|
+
stat = lstatSync9(linkPath, { throwIfNoEntry: false });
|
|
3341
|
+
} catch {
|
|
3342
|
+
return "absent";
|
|
3343
|
+
}
|
|
3344
|
+
if (stat?.isSymbolicLink() !== true) return "absent";
|
|
3345
|
+
let target;
|
|
3346
|
+
try {
|
|
3347
|
+
target = realpathSync2(linkPath);
|
|
3348
|
+
} catch {
|
|
3349
|
+
return "dangling";
|
|
3350
|
+
}
|
|
3351
|
+
let root;
|
|
3352
|
+
try {
|
|
3353
|
+
root = realpathSync2(join15(repoHome(), "shared"));
|
|
3354
|
+
} catch {
|
|
3355
|
+
return "foreign";
|
|
3356
|
+
}
|
|
3357
|
+
return target.startsWith(root + sep4) ? "managed" : "foreign";
|
|
3358
|
+
}
|
|
3359
|
+
function reportRejectedSharedDirs(section2, map) {
|
|
3360
|
+
const raw = map.sharedDirs;
|
|
3361
|
+
if (raw !== void 0 && !Array.isArray(raw)) {
|
|
3362
|
+
addItem(
|
|
3363
|
+
section2,
|
|
3364
|
+
`${yellow(warnGlyph)} path-map: sharedDirs is not an array (${typeof raw}); the whole field is ignored`
|
|
3365
|
+
);
|
|
3366
|
+
return;
|
|
3367
|
+
}
|
|
3368
|
+
const entries = Array.isArray(raw) ? raw : [];
|
|
3369
|
+
const probable = [];
|
|
3370
|
+
for (const entry of entries) {
|
|
3371
|
+
const rejection = validateSharedDirEntry(entry);
|
|
3372
|
+
if (rejection === null) continue;
|
|
3373
|
+
if (typeof entry === "string" && mayJoinRefusedEntry(entry, rejection.reason, PROBEABLE_REASONS)) {
|
|
3374
|
+
probable.push(entry);
|
|
3375
|
+
}
|
|
3376
|
+
addItem(
|
|
3377
|
+
section2,
|
|
3378
|
+
`${yellow(warnGlyph)} path-map: sharedDirs entry ${JSON.stringify(entry)} rejected: ${rejection.message}; skipping`
|
|
3379
|
+
);
|
|
3380
|
+
}
|
|
3381
|
+
reportRejectedLeftovers(section2, probable);
|
|
3382
|
+
}
|
|
3383
|
+
function reportRejectedLeftovers(section2, probable) {
|
|
3384
|
+
for (const entry of probable) {
|
|
3385
|
+
const link = classifyLocalLink(entry);
|
|
3386
|
+
if (link === "managed") {
|
|
3387
|
+
addItem(
|
|
3388
|
+
section2,
|
|
3389
|
+
`${yellow(warnGlyph)} path-map: entry ${JSON.stringify(entry)} is a symlink under ~/.claude/ pointing into shared/; copy the content out first (cp -RL), then remove both. nomad will not do it for you`
|
|
3390
|
+
);
|
|
3391
|
+
continue;
|
|
3392
|
+
}
|
|
3393
|
+
if (link === "dangling") {
|
|
3394
|
+
addItem(
|
|
3395
|
+
section2,
|
|
3396
|
+
`${yellow(warnGlyph)} path-map: entry ${JSON.stringify(entry)} is a DANGLING symlink under ~/.claude/ (its target does not resolve); do not delete anything under shared/ yet, since a copy there may be the only one left. nomad cannot restore this name, because the entry is refused: recover the content by hand if you need it, then remove the dead link`
|
|
3397
|
+
);
|
|
3398
|
+
continue;
|
|
3399
|
+
}
|
|
3400
|
+
if (link === "foreign") {
|
|
3401
|
+
addItem(
|
|
3402
|
+
section2,
|
|
3403
|
+
`${yellow(warnGlyph)} path-map: entry ${JSON.stringify(entry)} is a symlink under ~/.claude/ pointing OUTSIDE shared/; nomad does not manage it, so leave it alone and just drop the entry from sharedDirs`
|
|
3404
|
+
);
|
|
3405
|
+
}
|
|
3406
|
+
if (hasSharedLeftover(entry)) {
|
|
3407
|
+
addItem(
|
|
3408
|
+
section2,
|
|
3409
|
+
`${yellow(warnGlyph)} path-map: shared/ entry ${JSON.stringify(entry)} exists in the repo working tree; remove it by hand, nomad will not delete it`
|
|
3410
|
+
);
|
|
3411
|
+
}
|
|
3412
|
+
}
|
|
3413
|
+
}
|
|
3202
3414
|
function reportPathCollisions(section2, map) {
|
|
3203
3415
|
const seen = /* @__PURE__ */ new Map();
|
|
3204
3416
|
let collisionCount = 0;
|
|
@@ -3230,6 +3442,7 @@ function reportPathMap(section2) {
|
|
|
3230
3442
|
}
|
|
3231
3443
|
const map = readJsonSafe(mapPath, mapPath, section2);
|
|
3232
3444
|
if (map === null) return;
|
|
3445
|
+
reportRejectedSharedDirs(section2, map);
|
|
3233
3446
|
const shapeError = validatePathMapShape(map);
|
|
3234
3447
|
if (shapeError !== null) {
|
|
3235
3448
|
addItem(section2, `${red(failGlyph)} ${shapeError}`);
|
|
@@ -3425,7 +3638,7 @@ function reportGitIdentity(section2) {
|
|
|
3425
3638
|
|
|
3426
3639
|
// src/commands.doctor.checks.backups.ts
|
|
3427
3640
|
init_color();
|
|
3428
|
-
import { existsSync as existsSync18, lstatSync as
|
|
3641
|
+
import { existsSync as existsSync18, lstatSync as lstatSync10, readdirSync as readdirSync8 } from "node:fs";
|
|
3429
3642
|
import { join as join21 } from "node:path";
|
|
3430
3643
|
init_config();
|
|
3431
3644
|
var TS_SHAPE2 = /^\d{8}-\d{6}(-\d+)?$/;
|
|
@@ -3443,7 +3656,7 @@ function dirSizeBytes(dir) {
|
|
|
3443
3656
|
let bytes = 0;
|
|
3444
3657
|
for (const entry of safeReaddir(dir)) {
|
|
3445
3658
|
const full = join21(dir, entry);
|
|
3446
|
-
const st =
|
|
3659
|
+
const st = lstatSync10(full, { throwIfNoEntry: false });
|
|
3447
3660
|
if (!st) continue;
|
|
3448
3661
|
if (st.isSymbolicLink()) continue;
|
|
3449
3662
|
if (st.isDirectory()) bytes += dirSizeBytes(full);
|
|
@@ -3922,8 +4135,8 @@ init_config();
|
|
|
3922
4135
|
|
|
3923
4136
|
// src/remap.ts
|
|
3924
4137
|
init_config_sharedDirs_guard();
|
|
3925
|
-
import { cpSync as cpSync6, existsSync as existsSync21, lstatSync as
|
|
3926
|
-
import { dirname as dirname6, join as join28, relative as relative5, sep as
|
|
4138
|
+
import { cpSync as cpSync6, existsSync as existsSync21, lstatSync as lstatSync11, mkdirSync as mkdirSync7, readdirSync as readdirSync10, rmSync as rmSync11, statSync as statSync5 } from "node:fs";
|
|
4139
|
+
import { dirname as dirname6, join as join28, relative as relative5, sep as sep5 } from "node:path";
|
|
3927
4140
|
init_config();
|
|
3928
4141
|
|
|
3929
4142
|
// src/push-manifest.ts
|
|
@@ -4055,7 +4268,7 @@ function copyFileAtomic(src, dst) {
|
|
|
4055
4268
|
renameAtomicRetry(tmp, dst);
|
|
4056
4269
|
}
|
|
4057
4270
|
function hasDeltaForDir(sel, localDir) {
|
|
4058
|
-
const prefix = `${localDir}${
|
|
4271
|
+
const prefix = `${localDir}${sep5}`;
|
|
4059
4272
|
for (const p of sel.changed) if (p.startsWith(prefix)) return true;
|
|
4060
4273
|
for (const p of sel.deleted) if (p.startsWith(prefix)) return true;
|
|
4061
4274
|
return false;
|
|
@@ -4064,7 +4277,7 @@ function skipForSelection(sel, localDir) {
|
|
|
4064
4277
|
return sel !== void 0 && !hasDeltaForDir(sel, localDir);
|
|
4065
4278
|
}
|
|
4066
4279
|
function applySelective(sel, localDir, repoDst) {
|
|
4067
|
-
const prefix = `${localDir}${
|
|
4280
|
+
const prefix = `${localDir}${sep5}`;
|
|
4068
4281
|
for (const src of sel.changed) {
|
|
4069
4282
|
if (!src.startsWith(prefix)) continue;
|
|
4070
4283
|
copyFileAtomic(src, join28(repoDst, relative5(localDir, src)));
|
|
@@ -4082,7 +4295,7 @@ function copyDirJsonlOnly(src, dst) {
|
|
|
4082
4295
|
filter: (srcPath) => {
|
|
4083
4296
|
const rel = relative5(src, srcPath);
|
|
4084
4297
|
if (rel === "") return true;
|
|
4085
|
-
if (rel.split(
|
|
4298
|
+
if (rel.split(sep5).length > 1) return true;
|
|
4086
4299
|
if (statSync5(srcPath).isDirectory()) return true;
|
|
4087
4300
|
if (srcPath.endsWith(".jsonl")) return true;
|
|
4088
4301
|
item(`skip ${rel}: extension not in allowlist`);
|
|
@@ -4142,9 +4355,9 @@ function countLocalOnly(src, dst) {
|
|
|
4142
4355
|
for (const name of readdirSync10(dst)) {
|
|
4143
4356
|
const dstPath = join28(dst, name);
|
|
4144
4357
|
const srcPath = join28(src, name);
|
|
4145
|
-
if (
|
|
4358
|
+
if (lstatSync11(dstPath).isDirectory()) {
|
|
4146
4359
|
count += countLocalOnly(srcPath, dstPath);
|
|
4147
|
-
} else if (
|
|
4360
|
+
} else if (lstatSync11(srcPath, { throwIfNoEntry: false }) === void 0) {
|
|
4148
4361
|
count++;
|
|
4149
4362
|
}
|
|
4150
4363
|
}
|
|
@@ -4334,7 +4547,7 @@ function reportCheckShared(section2, gitleaksReady) {
|
|
|
4334
4547
|
|
|
4335
4548
|
// src/commands.doctor.checks.hooks.scope.ts
|
|
4336
4549
|
init_color();
|
|
4337
|
-
import { existsSync as existsSync23, readFileSync as readFileSync8, readdirSync as readdirSync12, realpathSync as
|
|
4550
|
+
import { existsSync as existsSync23, readFileSync as readFileSync8, readdirSync as readdirSync12, realpathSync as realpathSync3 } from "node:fs";
|
|
4338
4551
|
import { dirname as dirname7, extname, join as join30 } from "node:path";
|
|
4339
4552
|
init_config();
|
|
4340
4553
|
function typeFromPackageJson(pkgPath) {
|
|
@@ -4351,7 +4564,7 @@ function effectiveType(hookPath) {
|
|
|
4351
4564
|
if (ext === ".cjs") return "cjs";
|
|
4352
4565
|
let real;
|
|
4353
4566
|
try {
|
|
4354
|
-
real =
|
|
4567
|
+
real = realpathSync3(hookPath);
|
|
4355
4568
|
} catch {
|
|
4356
4569
|
return null;
|
|
4357
4570
|
}
|
|
@@ -4513,7 +4726,7 @@ import { existsSync as existsSync26, readFileSync as readFileSync9 } from "node:
|
|
|
4513
4726
|
import { join as join33 } from "node:path";
|
|
4514
4727
|
|
|
4515
4728
|
// src/commands.doctor.checks.hooks.preserve-symlinks.probe.ts
|
|
4516
|
-
import { closeSync as closeSync3, existsSync as existsSync25, openSync as openSync3, readSync, realpathSync as
|
|
4729
|
+
import { closeSync as closeSync3, existsSync as existsSync25, openSync as openSync3, readSync, realpathSync as realpathSync4 } from "node:fs";
|
|
4517
4730
|
import { dirname as dirname8, join as join32, resolve as resolve2 } from "node:path";
|
|
4518
4731
|
function suppressedRanges(src) {
|
|
4519
4732
|
const ranges = [];
|
|
@@ -4558,7 +4771,7 @@ function specifierIsMissing(specifier, baseDir) {
|
|
|
4558
4771
|
function relativeRequireTargetsBroken(scriptPath) {
|
|
4559
4772
|
let realPath;
|
|
4560
4773
|
try {
|
|
4561
|
-
realPath =
|
|
4774
|
+
realPath = realpathSync4(scriptPath);
|
|
4562
4775
|
} catch {
|
|
4563
4776
|
return false;
|
|
4564
4777
|
}
|
|
@@ -4702,7 +4915,7 @@ function reportPreserveSymlinksCheck(section2) {
|
|
|
4702
4915
|
return;
|
|
4703
4916
|
}
|
|
4704
4917
|
const map = readPathMapSafe();
|
|
4705
|
-
const sharedLinkNames = allSharedLinks(map);
|
|
4918
|
+
const sharedLinkNames = allSharedLinks(map, { quiet: true });
|
|
4706
4919
|
let anyWarn = false;
|
|
4707
4920
|
for (const [event, groups] of Object.entries(hooks)) {
|
|
4708
4921
|
if (!Array.isArray(groups)) continue;
|
|
@@ -5052,21 +5265,21 @@ import { createInterface as createInterface2 } from "node:readline/promises";
|
|
|
5052
5265
|
// src/commands.push.recovery.actions.ts
|
|
5053
5266
|
init_config();
|
|
5054
5267
|
import { readFileSync as readFileSync17 } from "node:fs";
|
|
5055
|
-
import { isAbsolute as isAbsolute2, resolve as resolve3, sep as
|
|
5268
|
+
import { isAbsolute as isAbsolute2, resolve as resolve3, sep as sep9 } from "node:path";
|
|
5056
5269
|
|
|
5057
5270
|
// src/commands.push.recovery.memory.ts
|
|
5058
5271
|
init_config();
|
|
5059
5272
|
init_config_sharedDirs_guard();
|
|
5060
5273
|
import {
|
|
5061
5274
|
cpSync as cpSync7,
|
|
5062
|
-
lstatSync as
|
|
5275
|
+
lstatSync as lstatSync12,
|
|
5063
5276
|
mkdirSync as mkdirSync9,
|
|
5064
5277
|
readFileSync as readFileSync14,
|
|
5065
|
-
realpathSync as
|
|
5278
|
+
realpathSync as realpathSync5,
|
|
5066
5279
|
statSync as statSync6,
|
|
5067
5280
|
writeFileSync as writeFileSync6
|
|
5068
5281
|
} from "node:fs";
|
|
5069
|
-
import { dirname as dirname9, join as join36, sep as
|
|
5282
|
+
import { dirname as dirname9, join as join36, sep as sep6 } from "node:path";
|
|
5070
5283
|
init_push_gitleaks_scan();
|
|
5071
5284
|
|
|
5072
5285
|
// src/rel-path-guard.ts
|
|
@@ -5097,12 +5310,12 @@ function resolveMemoryLocalPath(logical, relPath, map) {
|
|
|
5097
5310
|
if (abs === void 0) return null;
|
|
5098
5311
|
const memoryRoot = join36(claudeHome(), "projects", encodePath(abs), "memory");
|
|
5099
5312
|
const localPath = join36(memoryRoot, ...relPath.split("/"));
|
|
5100
|
-
if (localPath !== memoryRoot && !localPath.startsWith(memoryRoot +
|
|
5313
|
+
if (localPath !== memoryRoot && !localPath.startsWith(memoryRoot + sep6)) return null;
|
|
5101
5314
|
try {
|
|
5102
|
-
if (
|
|
5103
|
-
const realLocal =
|
|
5104
|
-
const realRoot =
|
|
5105
|
-
if (!realLocal.startsWith(realRoot +
|
|
5315
|
+
if (lstatSync12(localPath).isSymbolicLink()) return null;
|
|
5316
|
+
const realLocal = realpathSync5(localPath);
|
|
5317
|
+
const realRoot = realpathSync5(memoryRoot);
|
|
5318
|
+
if (!realLocal.startsWith(realRoot + sep6)) return null;
|
|
5106
5319
|
if (!statSync6(realLocal).isFile()) return null;
|
|
5107
5320
|
} catch {
|
|
5108
5321
|
return null;
|
|
@@ -5160,18 +5373,18 @@ function applyMemoryRedact(f, ts, map, scan = scanFile) {
|
|
|
5160
5373
|
init_config();
|
|
5161
5374
|
import {
|
|
5162
5375
|
cpSync as cpSync8,
|
|
5163
|
-
lstatSync as
|
|
5376
|
+
lstatSync as lstatSync14,
|
|
5164
5377
|
mkdirSync as mkdirSync11,
|
|
5165
5378
|
readFileSync as readFileSync15,
|
|
5166
|
-
realpathSync as
|
|
5379
|
+
realpathSync as realpathSync6,
|
|
5167
5380
|
statSync as statSync7,
|
|
5168
5381
|
writeFileSync as writeFileSync7
|
|
5169
5382
|
} from "node:fs";
|
|
5170
|
-
import { dirname as dirname10, join as join38, sep as
|
|
5383
|
+
import { dirname as dirname10, join as join38, sep as sep7 } from "node:path";
|
|
5171
5384
|
|
|
5172
5385
|
// src/skills-sync.ts
|
|
5173
5386
|
init_config();
|
|
5174
|
-
import { existsSync as existsSync29, lstatSync as
|
|
5387
|
+
import { existsSync as existsSync29, lstatSync as lstatSync13, mkdirSync as mkdirSync10, readdirSync as readdirSync13, rmSync as rmSync13 } from "node:fs";
|
|
5175
5388
|
import { join as join37 } from "node:path";
|
|
5176
5389
|
|
|
5177
5390
|
// src/skills-sync.tracked.ts
|
|
@@ -5211,7 +5424,7 @@ function syncSkillsPull(ts, prePostHeads) {
|
|
|
5211
5424
|
if (!existsSync29(sharedSkills)) return;
|
|
5212
5425
|
const localSkills = join37(claudeHome(), "skills");
|
|
5213
5426
|
backupBeforeWrite(localSkills, ts);
|
|
5214
|
-
const dstStat =
|
|
5427
|
+
const dstStat = lstatSync13(localSkills, { throwIfNoEntry: false });
|
|
5215
5428
|
if (dstStat?.isSymbolicLink() === true) {
|
|
5216
5429
|
rmSync13(localSkills, { recursive: true, force: true });
|
|
5217
5430
|
mkdirSync10(localSkills, { recursive: true });
|
|
@@ -5221,7 +5434,7 @@ function syncSkillsPull(ts, prePostHeads) {
|
|
|
5221
5434
|
}
|
|
5222
5435
|
function syncSkillsPush() {
|
|
5223
5436
|
const localSkills = join37(claudeHome(), "skills");
|
|
5224
|
-
const stat =
|
|
5437
|
+
const stat = lstatSync13(localSkills, { throwIfNoEntry: false });
|
|
5225
5438
|
if (stat === void 0) return;
|
|
5226
5439
|
if (stat.isSymbolicLink()) return;
|
|
5227
5440
|
const sharedSkills = join37(repoHome(), "shared", "skills");
|
|
@@ -5250,12 +5463,12 @@ function resolveSkillLocalPath(name, relPath) {
|
|
|
5250
5463
|
const skillsRoot = join38(claudeHome(), "skills");
|
|
5251
5464
|
const skillRoot = join38(skillsRoot, name);
|
|
5252
5465
|
const localPath = join38(skillRoot, ...relPath.split("/"));
|
|
5253
|
-
if (localPath !== skillRoot && !localPath.startsWith(skillRoot +
|
|
5466
|
+
if (localPath !== skillRoot && !localPath.startsWith(skillRoot + sep7)) return null;
|
|
5254
5467
|
try {
|
|
5255
|
-
if (
|
|
5256
|
-
const realLocal =
|
|
5257
|
-
const realRoot =
|
|
5258
|
-
if (!realLocal.startsWith(realRoot +
|
|
5468
|
+
if (lstatSync14(localPath).isSymbolicLink()) return null;
|
|
5469
|
+
const realLocal = realpathSync6(localPath);
|
|
5470
|
+
const realRoot = realpathSync6(skillsRoot);
|
|
5471
|
+
if (!realLocal.startsWith(realRoot + sep7)) return null;
|
|
5259
5472
|
if (!statSync7(realLocal).isFile()) return null;
|
|
5260
5473
|
} catch {
|
|
5261
5474
|
return null;
|
|
@@ -5380,7 +5593,7 @@ function applyDeferredAllows(findings, state, repo) {
|
|
|
5380
5593
|
init_config();
|
|
5381
5594
|
init_config_sharedDirs_guard();
|
|
5382
5595
|
import { cpSync as cpSync9, existsSync as existsSync32, mkdirSync as mkdirSync12 } from "node:fs";
|
|
5383
|
-
import { dirname as dirname12, join as join41, sep as
|
|
5596
|
+
import { dirname as dirname12, join as join41, sep as sep8 } from "node:path";
|
|
5384
5597
|
|
|
5385
5598
|
// src/commands.redact.ts
|
|
5386
5599
|
init_config();
|
|
@@ -5388,17 +5601,17 @@ import { existsSync as existsSync31 } from "node:fs";
|
|
|
5388
5601
|
import { dirname as dirname11, join as join40 } from "node:path";
|
|
5389
5602
|
|
|
5390
5603
|
// src/commands.redact.subtree.ts
|
|
5391
|
-
import { existsSync as existsSync30, lstatSync as
|
|
5604
|
+
import { existsSync as existsSync30, lstatSync as lstatSync15, readFileSync as readFileSync16, readdirSync as readdirSync14, statSync as statSync8, writeFileSync as writeFileSync8 } from "node:fs";
|
|
5392
5605
|
import { join as join39 } from "node:path";
|
|
5393
5606
|
init_utils_fs();
|
|
5394
5607
|
init_utils();
|
|
5395
5608
|
function collectFiles(dir, out) {
|
|
5396
5609
|
if (!existsSync30(dir)) return;
|
|
5397
|
-
const st =
|
|
5610
|
+
const st = lstatSync15(dir);
|
|
5398
5611
|
if (!st.isDirectory()) return;
|
|
5399
5612
|
for (const entry of readdirSync14(dir)) {
|
|
5400
5613
|
const abs = join39(dir, entry);
|
|
5401
|
-
const lst =
|
|
5614
|
+
const lst = lstatSync15(abs);
|
|
5402
5615
|
if (lst.isSymbolicLink()) continue;
|
|
5403
5616
|
if (lst.isDirectory()) {
|
|
5404
5617
|
collectFiles(abs, out);
|
|
@@ -5606,7 +5819,7 @@ function resolveStagedDir(localPath, map, claude, repo) {
|
|
|
5606
5819
|
assertSafeLogical(logical);
|
|
5607
5820
|
const abs = hostMap[HOST];
|
|
5608
5821
|
if (abs === void 0) continue;
|
|
5609
|
-
if (localPath.startsWith(join41(claude, "projects", encodePath(abs)) +
|
|
5822
|
+
if (localPath.startsWith(join41(claude, "projects", encodePath(abs)) + sep8)) {
|
|
5610
5823
|
return join41(repo, "shared", "projects", logical);
|
|
5611
5824
|
}
|
|
5612
5825
|
}
|
|
@@ -5970,7 +6183,7 @@ function makeDefaultReadLine(repo) {
|
|
|
5970
6183
|
try {
|
|
5971
6184
|
const repoRoot = resolve3(repo);
|
|
5972
6185
|
const target = resolve3(repoRoot, file);
|
|
5973
|
-
if (isAbsolute2(file) || target !== repoRoot && !target.startsWith(repoRoot +
|
|
6186
|
+
if (isAbsolute2(file) || target !== repoRoot && !target.startsWith(repoRoot + sep9)) {
|
|
5974
6187
|
return null;
|
|
5975
6188
|
}
|
|
5976
6189
|
let lines = cache.get(target);
|
|
@@ -6980,11 +7193,11 @@ init_utils_json();
|
|
|
6980
7193
|
|
|
6981
7194
|
// src/extras-sync.remap.ts
|
|
6982
7195
|
init_config();
|
|
6983
|
-
import { existsSync as existsSync38, mkdirSync as mkdirSync13, readdirSync as readdirSync16, readFileSync as readFileSync19, realpathSync as
|
|
6984
|
-
import { dirname as dirname13, join as join49, sep as
|
|
7196
|
+
import { existsSync as existsSync38, mkdirSync as mkdirSync13, readdirSync as readdirSync16, readFileSync as readFileSync19, realpathSync as realpathSync7, rmSync as rmSync16 } from "node:fs";
|
|
7197
|
+
import { dirname as dirname13, join as join49, sep as sep11 } from "node:path";
|
|
6985
7198
|
|
|
6986
7199
|
// src/extras-sync.planning-diff.ts
|
|
6987
|
-
import { join as join48, normalize as normalize2, sep as
|
|
7200
|
+
import { join as join48, normalize as normalize2, sep as sep10 } from "node:path";
|
|
6988
7201
|
init_utils();
|
|
6989
7202
|
function processRecord(fields, i, changed, deleted) {
|
|
6990
7203
|
const status = fields[i];
|
|
@@ -7037,7 +7250,7 @@ function planningDeleteTargets(opts) {
|
|
|
7037
7250
|
const logicalPrefix = "shared/extras/" + logical + "/";
|
|
7038
7251
|
const prefix = logicalPrefix + ".planning/";
|
|
7039
7252
|
const planningRoot = join48(localRoot, ".planning");
|
|
7040
|
-
const planningRootBoundary = planningRoot +
|
|
7253
|
+
const planningRootBoundary = planningRoot + sep10;
|
|
7041
7254
|
const targets = [];
|
|
7042
7255
|
for (const repoPath of deleted) {
|
|
7043
7256
|
if (!repoPath.startsWith(prefix)) {
|
|
@@ -7079,7 +7292,7 @@ function runExtrasOp(v, dryRun, paths, backup, copy) {
|
|
|
7079
7292
|
}
|
|
7080
7293
|
function pruneEmptyAncestors(target, planningRoot) {
|
|
7081
7294
|
let dir = dirname13(target);
|
|
7082
|
-
while (dir !== planningRoot && dir.startsWith(planningRoot +
|
|
7295
|
+
while (dir !== planningRoot && dir.startsWith(planningRoot + sep11)) {
|
|
7083
7296
|
try {
|
|
7084
7297
|
if (readdirSync16(dir).length > 0) break;
|
|
7085
7298
|
rmSync16(dir, { recursive: true, force: true });
|
|
@@ -7091,13 +7304,13 @@ function pruneEmptyAncestors(target, planningRoot) {
|
|
|
7091
7304
|
}
|
|
7092
7305
|
function tryRealpath(dir) {
|
|
7093
7306
|
try {
|
|
7094
|
-
return
|
|
7307
|
+
return realpathSync7(dir);
|
|
7095
7308
|
} catch {
|
|
7096
7309
|
return void 0;
|
|
7097
7310
|
}
|
|
7098
7311
|
}
|
|
7099
7312
|
function isInsidePlanningRoot(parentReal, rootReal) {
|
|
7100
|
-
return parentReal === rootReal || parentReal.startsWith(rootReal +
|
|
7313
|
+
return parentReal === rootReal || parentReal.startsWith(rootReal + sep11);
|
|
7101
7314
|
}
|
|
7102
7315
|
function deletePlanningTarget(target, planningRoot, repoCounterpart) {
|
|
7103
7316
|
if (existsSync38(repoCounterpart)) return;
|
|
@@ -7124,11 +7337,11 @@ function planningDiffArgs(pre, post, logical) {
|
|
|
7124
7337
|
function deletePairsFor(t, raw) {
|
|
7125
7338
|
return planningDeleteTargets({ raw, logical: t.logical, localRoot: t.localRoot }).map(
|
|
7126
7339
|
(target) => {
|
|
7127
|
-
const relToLocal = target.slice(t.localRoot.length +
|
|
7340
|
+
const relToLocal = target.slice(t.localRoot.length + sep11.length);
|
|
7128
7341
|
return {
|
|
7129
7342
|
target,
|
|
7130
7343
|
relToLocal,
|
|
7131
|
-
repoRel: `shared/extras/${t.logical}/${relToLocal.split(
|
|
7344
|
+
repoRel: `shared/extras/${t.logical}/${relToLocal.split(sep11).join("/")}`
|
|
7132
7345
|
};
|
|
7133
7346
|
}
|
|
7134
7347
|
);
|
|
@@ -7302,8 +7515,8 @@ function divergenceCheckExtras(ts, prePostHeads) {
|
|
|
7302
7515
|
|
|
7303
7516
|
// src/links.baseline.ts
|
|
7304
7517
|
init_config();
|
|
7305
|
-
import { lstatSync as
|
|
7306
|
-
import { join as join51, relative as relative7, sep as
|
|
7518
|
+
import { lstatSync as lstatSync16, readdirSync as readdirSync17 } from "node:fs";
|
|
7519
|
+
import { join as join51, relative as relative7, sep as sep12 } from "node:path";
|
|
7307
7520
|
init_utils();
|
|
7308
7521
|
var SHARED_BASELINE_KIND = "shared-links-baseline/1";
|
|
7309
7522
|
var SHARED_BASELINE_CONFIG_HASH = "not-applicable";
|
|
@@ -7314,13 +7527,13 @@ function readSharedBaseline() {
|
|
|
7314
7527
|
return parsed;
|
|
7315
7528
|
}
|
|
7316
7529
|
function baselineKey(claude, abs) {
|
|
7317
|
-
return relative7(claude, abs).split(
|
|
7530
|
+
return relative7(claude, abs).split(sep12).join("/");
|
|
7318
7531
|
}
|
|
7319
7532
|
function addLocalPath(abs, claude, scan) {
|
|
7320
7533
|
const key = baselineKey(claude, abs);
|
|
7321
7534
|
let st;
|
|
7322
7535
|
try {
|
|
7323
|
-
st =
|
|
7536
|
+
st = lstatSync16(abs, { throwIfNoEntry: false });
|
|
7324
7537
|
} catch {
|
|
7325
7538
|
scan.declined.push(key);
|
|
7326
7539
|
return;
|
|
@@ -7412,7 +7625,7 @@ function gitProbe(args, repo) {
|
|
|
7412
7625
|
|
|
7413
7626
|
// src/links.captures.ts
|
|
7414
7627
|
init_config();
|
|
7415
|
-
import { existsSync as existsSync40, lstatSync as
|
|
7628
|
+
import { existsSync as existsSync40, lstatSync as lstatSync17 } from "node:fs";
|
|
7416
7629
|
import { join as join52 } from "node:path";
|
|
7417
7630
|
function planSharedLinkCaptures(map) {
|
|
7418
7631
|
if (process.platform !== "win32") return [];
|
|
@@ -7424,7 +7637,7 @@ function planSharedLinkCaptures(map) {
|
|
|
7424
7637
|
const localPath = join52(claude, name);
|
|
7425
7638
|
let stat;
|
|
7426
7639
|
try {
|
|
7427
|
-
stat =
|
|
7640
|
+
stat = lstatSync17(localPath, { throwIfNoEntry: false });
|
|
7428
7641
|
} catch {
|
|
7429
7642
|
continue;
|
|
7430
7643
|
}
|
|
@@ -7439,8 +7652,8 @@ function planSharedLinkCaptures(map) {
|
|
|
7439
7652
|
|
|
7440
7653
|
// src/links.deletions.ts
|
|
7441
7654
|
init_config();
|
|
7442
|
-
import { existsSync as existsSync41, lstatSync as
|
|
7443
|
-
import { join as join53, relative as relative8, resolve as resolve4, sep as
|
|
7655
|
+
import { existsSync as existsSync41, lstatSync as lstatSync18, rmSync as rmSync17 } from "node:fs";
|
|
7656
|
+
import { join as join53, relative as relative8, resolve as resolve4, sep as sep13 } from "node:path";
|
|
7444
7657
|
init_utils();
|
|
7445
7658
|
init_utils_fs();
|
|
7446
7659
|
function isUnknown(declined, key) {
|
|
@@ -7454,7 +7667,7 @@ function deletionFor(key, names, claude, sharedRoot) {
|
|
|
7454
7667
|
if (segments.some((segment) => isDeniedName(ALWAYS_NEVER_SYNC, segment))) return null;
|
|
7455
7668
|
const repoPath = resolve4(sharedRoot, key);
|
|
7456
7669
|
const rel = relative8(join53(sharedRoot, name), repoPath);
|
|
7457
|
-
if (rel === "" || rel === ".." || rel.startsWith(`..${
|
|
7670
|
+
if (rel === "" || rel === ".." || rel.startsWith(`..${sep13}`)) return null;
|
|
7458
7671
|
if (segments.some((segment) => segment === "" || segment === "." || segment === ".."))
|
|
7459
7672
|
return null;
|
|
7460
7673
|
if (!existsSync41(repoPath)) return null;
|
|
@@ -7485,7 +7698,7 @@ function applySharedLinkDeletions(map, ts) {
|
|
|
7485
7698
|
const repo = repoHome();
|
|
7486
7699
|
for (const entry of planSharedLinkDeletions(map)) {
|
|
7487
7700
|
try {
|
|
7488
|
-
if (!
|
|
7701
|
+
if (!lstatSync18(entry.repoPath).isFile()) continue;
|
|
7489
7702
|
backupRepoWrite(entry.repoPath, ts, repo);
|
|
7490
7703
|
rmSync17(entry.repoPath, { force: true });
|
|
7491
7704
|
} catch (err) {
|
|
@@ -7955,7 +8168,7 @@ function planSharedReconcileBeforePull(repo) {
|
|
|
7955
8168
|
// src/commands.pull.collision.ts
|
|
7956
8169
|
init_config();
|
|
7957
8170
|
init_exit_codes();
|
|
7958
|
-
import { existsSync as existsSync45, lstatSync as
|
|
8171
|
+
import { existsSync as existsSync45, lstatSync as lstatSync19, readFileSync as readFileSync20, rmSync as rmSync18 } from "node:fs";
|
|
7959
8172
|
import { join as join57 } from "node:path";
|
|
7960
8173
|
init_utils();
|
|
7961
8174
|
var SHARED_PREFIX = /^shared\//;
|
|
@@ -8022,7 +8235,7 @@ function removeMirroredCopies(repo, repoRelPaths) {
|
|
|
8022
8235
|
for (const rel of repoRelPaths.filter(isContainedMirrorPath)) {
|
|
8023
8236
|
const abs = join57(repo, rel);
|
|
8024
8237
|
try {
|
|
8025
|
-
if (
|
|
8238
|
+
if (lstatSync19(abs, { throwIfNoEntry: false })?.isFile() !== true) continue;
|
|
8026
8239
|
if (!matchesLocalOriginal(repo, rel)) continue;
|
|
8027
8240
|
rmSync18(abs, { force: true });
|
|
8028
8241
|
} catch (err) {
|
|
@@ -8401,7 +8614,7 @@ function isAllowed(path, allowed) {
|
|
|
8401
8614
|
}
|
|
8402
8615
|
function blockSetFor(segments) {
|
|
8403
8616
|
if (segments[0] !== "shared" || segments[1] !== "extras") return NEVER_SYNC;
|
|
8404
|
-
return segments[3]
|
|
8617
|
+
return isClaudeExtraName(segments[3] ?? "") ? CLAUDE_EXTRA_NEVER_SYNC : ALWAYS_NEVER_SYNC;
|
|
8405
8618
|
}
|
|
8406
8619
|
function isNeverSync(path) {
|
|
8407
8620
|
const segments = path.split("/");
|
|
@@ -8451,7 +8664,11 @@ function enforceAllowList(statusPorcelain, map) {
|
|
|
8451
8664
|
...Object.entries(map.extras ?? {}).flatMap(
|
|
8452
8665
|
([l, names]) => names.filter((n) => extrasWhitelist.includes(n)).flatMap((n) => [`shared/extras/${l}/${n}`, `shared/extras/${l}/${n}/`])
|
|
8453
8666
|
),
|
|
8454
|
-
|
|
8667
|
+
// Predicate passed by reference, not wrapped in an arrow: `isValidSharedDir`
|
|
8668
|
+
// is a type guard, and an arrow returning its result is just a boolean, so
|
|
8669
|
+
// wrapping it would leave `d` as `unknown` and let a non-string reach the
|
|
8670
|
+
// template literal as "[object Object]".
|
|
8671
|
+
...sharedDirEntries(map).filter(isValidSharedDir).map((d) => `shared/${d}/`)
|
|
8455
8672
|
];
|
|
8456
8673
|
const neverSyncHits = [];
|
|
8457
8674
|
const violations = [];
|
|
@@ -8632,9 +8849,9 @@ init_color();
|
|
|
8632
8849
|
init_config();
|
|
8633
8850
|
init_config_sharedDirs_guard();
|
|
8634
8851
|
import { randomBytes as randomBytes4 } from "node:crypto";
|
|
8635
|
-
import { copyFileSync, existsSync as existsSync49, lstatSync as
|
|
8852
|
+
import { copyFileSync, existsSync as existsSync49, lstatSync as lstatSync20, mkdirSync as mkdirSync15, readdirSync as readdirSync19, rmSync as rmSync19 } from "node:fs";
|
|
8636
8853
|
import { homedir as homedir7 } from "node:os";
|
|
8637
|
-
import { join as join62, relative as relative10, sep as
|
|
8854
|
+
import { join as join62, relative as relative10, sep as sep14 } from "node:path";
|
|
8638
8855
|
init_push_leak_verdict();
|
|
8639
8856
|
init_push_gitleaks();
|
|
8640
8857
|
init_utils_fs();
|
|
@@ -8642,7 +8859,7 @@ init_utils_json();
|
|
|
8642
8859
|
var NOTHING_TO_SCAN_ROW = `${dim(infoGlyph)} nothing to scan, no leaks`;
|
|
8643
8860
|
function stageSessionDir(localDir, dstDir, changed) {
|
|
8644
8861
|
if (changed !== void 0) {
|
|
8645
|
-
const prefix = `${localDir}${
|
|
8862
|
+
const prefix = `${localDir}${sep14}`;
|
|
8646
8863
|
const matching = [...changed].filter((p) => p.startsWith(prefix));
|
|
8647
8864
|
if (matching.length === 0) return false;
|
|
8648
8865
|
for (const src of matching) {
|
|
@@ -8696,7 +8913,7 @@ function stageExtras(tmpRoot, map) {
|
|
|
8696
8913
|
}
|
|
8697
8914
|
function stageSkills(tmpRoot) {
|
|
8698
8915
|
const localSkills = join62(claudeHome(), "skills");
|
|
8699
|
-
const stat =
|
|
8916
|
+
const stat = lstatSync20(localSkills, { throwIfNoEntry: false });
|
|
8700
8917
|
if (stat === void 0 || stat.isSymbolicLink()) return 0;
|
|
8701
8918
|
const names = readdirSync19(localSkills, { encoding: "utf8" }).filter((n) => !isSkillExcluded(n));
|
|
8702
8919
|
if (names.length === 0) return 0;
|
|
@@ -9881,7 +10098,7 @@ function parseSyncArgs(argv) {
|
|
|
9881
10098
|
// package.json
|
|
9882
10099
|
var package_default = {
|
|
9883
10100
|
name: "claude-nomad",
|
|
9884
|
-
version: "0.64.
|
|
10101
|
+
version: "0.64.1",
|
|
9885
10102
|
type: "module",
|
|
9886
10103
|
description: "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
|
|
9887
10104
|
keywords: [
|
package/package.json
CHANGED