claude-nomad 0.56.1 → 0.56.2
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 +16 -0
- package/dist/nomad.mjs +555 -394
- package/package.json +1 -1
package/dist/nomad.mjs
CHANGED
|
@@ -198,6 +198,13 @@ function gitCaptureRaw(args, cwd) {
|
|
|
198
198
|
maxBuffer: 64 * 1024 * 1024
|
|
199
199
|
}).toString();
|
|
200
200
|
}
|
|
201
|
+
function gitCaptureBuffer(args, cwd) {
|
|
202
|
+
return execFileSync("git", args, {
|
|
203
|
+
cwd,
|
|
204
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
205
|
+
maxBuffer: 64 * 1024 * 1024
|
|
206
|
+
});
|
|
207
|
+
}
|
|
201
208
|
function gitOrFatal(args, context, cwd) {
|
|
202
209
|
try {
|
|
203
210
|
execFileSync("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
@@ -2110,8 +2117,8 @@ function cmdEject(opts = {}, roots = defaultEjectRoots()) {
|
|
|
2110
2117
|
}
|
|
2111
2118
|
|
|
2112
2119
|
// src/commands.doctor.ts
|
|
2113
|
-
import { existsSync as
|
|
2114
|
-
import { join as
|
|
2120
|
+
import { existsSync as existsSync32 } from "node:fs";
|
|
2121
|
+
import { join as join38 } from "node:path";
|
|
2115
2122
|
|
|
2116
2123
|
// src/commands.doctor.checks.repo.ts
|
|
2117
2124
|
init_color();
|
|
@@ -2551,28 +2558,42 @@ init_config();
|
|
|
2551
2558
|
// src/extras-sync.diff.ts
|
|
2552
2559
|
init_utils();
|
|
2553
2560
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
const
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2561
|
+
import { relative as relative2 } from "node:path";
|
|
2562
|
+
function parseNameStatus(stdout) {
|
|
2563
|
+
const entries = [];
|
|
2564
|
+
for (const line of stdout.split("\n")) {
|
|
2565
|
+
if (line.length === 0) continue;
|
|
2566
|
+
const tab = line.indexOf(" ");
|
|
2567
|
+
if (tab === -1) continue;
|
|
2568
|
+
entries.push({ status: line.slice(0, tab), path: line.slice(tab + 1) });
|
|
2569
|
+
}
|
|
2570
|
+
return entries;
|
|
2571
|
+
}
|
|
2572
|
+
function labelEntry(entry) {
|
|
2573
|
+
if (entry.status === "D") return `${entry.path} (local only)`;
|
|
2574
|
+
if (entry.status === "A") return `${entry.path} (repo only)`;
|
|
2575
|
+
return entry.path;
|
|
2562
2576
|
}
|
|
2563
2577
|
function parseDiffOutput(stdout) {
|
|
2564
|
-
return stdout
|
|
2578
|
+
return parseNameStatus(stdout).map(labelEntry);
|
|
2565
2579
|
}
|
|
2566
|
-
function
|
|
2580
|
+
function parseModifiedPaths(stdout, a) {
|
|
2581
|
+
return parseNameStatus(stdout).filter((entry) => entry.status === "M").map((entry) => relative2(a, entry.path));
|
|
2582
|
+
}
|
|
2583
|
+
function runNameStatusDiff(a, b, parse) {
|
|
2567
2584
|
try {
|
|
2568
|
-
const stdout = execFileSync2(
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2585
|
+
const stdout = execFileSync2(
|
|
2586
|
+
"git",
|
|
2587
|
+
["diff", "--no-index", "--no-renames", "--name-status", a, b],
|
|
2588
|
+
{
|
|
2589
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
2590
|
+
}
|
|
2591
|
+
).toString();
|
|
2592
|
+
return parse(stdout);
|
|
2572
2593
|
} catch (err) {
|
|
2573
2594
|
const e = err;
|
|
2574
2595
|
if (e.status === 1 && e.stdout !== void 0) {
|
|
2575
|
-
return
|
|
2596
|
+
return parse(e.stdout.toString());
|
|
2576
2597
|
}
|
|
2577
2598
|
if (e.code === "ENOENT") {
|
|
2578
2599
|
warn(`git not on PATH; divergence check skipped for ${a}`);
|
|
@@ -2582,6 +2603,12 @@ function listDivergingFiles(a, b) {
|
|
|
2582
2603
|
return [];
|
|
2583
2604
|
}
|
|
2584
2605
|
}
|
|
2606
|
+
function listDivergingFiles(a, b) {
|
|
2607
|
+
return runNameStatusDiff(a, b, parseDiffOutput);
|
|
2608
|
+
}
|
|
2609
|
+
function listDivergingModified(a, b) {
|
|
2610
|
+
return runNameStatusDiff(a, b, (stdout) => parseModifiedPaths(stdout, a));
|
|
2611
|
+
}
|
|
2585
2612
|
|
|
2586
2613
|
// src/commands.doctor.checks.skills.ts
|
|
2587
2614
|
function stripSideIndicator(line) {
|
|
@@ -2591,15 +2618,15 @@ function stripSideIndicator(line) {
|
|
|
2591
2618
|
}
|
|
2592
2619
|
function isGsdDiffLine(line, localBase, sharedBase) {
|
|
2593
2620
|
const bare = stripSideIndicator(line);
|
|
2594
|
-
let
|
|
2621
|
+
let relative9;
|
|
2595
2622
|
if (bare.startsWith(localBase + "/")) {
|
|
2596
|
-
|
|
2623
|
+
relative9 = bare.slice(localBase.length + 1);
|
|
2597
2624
|
} else if (bare.startsWith(sharedBase + "/")) {
|
|
2598
|
-
|
|
2625
|
+
relative9 = bare.slice(sharedBase.length + 1);
|
|
2599
2626
|
} else {
|
|
2600
|
-
|
|
2627
|
+
relative9 = bare;
|
|
2601
2628
|
}
|
|
2602
|
-
return
|
|
2629
|
+
return relative9.split("/")[0].startsWith(GSD_PREFIX);
|
|
2603
2630
|
}
|
|
2604
2631
|
function reportSkillsDivergence(section2) {
|
|
2605
2632
|
const sharedSkills = join14(repoHome(), "shared", "skills");
|
|
@@ -2632,7 +2659,7 @@ init_color();
|
|
|
2632
2659
|
init_config();
|
|
2633
2660
|
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
2634
2661
|
import { existsSync as existsSync15 } from "node:fs";
|
|
2635
|
-
import { join as join18, relative as
|
|
2662
|
+
import { join as join18, relative as relative3 } from "node:path";
|
|
2636
2663
|
init_commands_pull_wedge();
|
|
2637
2664
|
init_push_checks();
|
|
2638
2665
|
init_utils();
|
|
@@ -2659,7 +2686,7 @@ function reportGitlinks(section2) {
|
|
|
2659
2686
|
if (existsSync15(sharedDir)) {
|
|
2660
2687
|
const gitlinks = findGitlinks(sharedDir);
|
|
2661
2688
|
for (const p of gitlinks) {
|
|
2662
|
-
const rel =
|
|
2689
|
+
const rel = relative3(repo, p);
|
|
2663
2690
|
addItem(
|
|
2664
2691
|
section2,
|
|
2665
2692
|
`${red(failGlyph)} gitlink: ${blue(rel)} would push as submodule (run: rm -rf ${rel} or remove the nested repo)`
|
|
@@ -2936,9 +2963,9 @@ function reportCheckSchema(section2) {
|
|
|
2936
2963
|
init_color();
|
|
2937
2964
|
import { randomBytes } from "node:crypto";
|
|
2938
2965
|
import { execFileSync as execFileSync9 } from "node:child_process";
|
|
2939
|
-
import { existsSync as
|
|
2966
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync6, readdirSync as readdirSync10, rmSync as rmSync9 } from "node:fs";
|
|
2940
2967
|
import { homedir as homedir4 } from "node:os";
|
|
2941
|
-
import { join as
|
|
2968
|
+
import { join as join26 } from "node:path";
|
|
2942
2969
|
|
|
2943
2970
|
// src/commands.doctor.check-shared.scan.ts
|
|
2944
2971
|
init_color();
|
|
@@ -3035,8 +3062,22 @@ init_config();
|
|
|
3035
3062
|
|
|
3036
3063
|
// src/remap.ts
|
|
3037
3064
|
init_config_sharedDirs_guard();
|
|
3038
|
-
import {
|
|
3039
|
-
|
|
3065
|
+
import {
|
|
3066
|
+
cpSync as cpSync5,
|
|
3067
|
+
existsSync as existsSync20,
|
|
3068
|
+
lstatSync as lstatSync9,
|
|
3069
|
+
mkdirSync as mkdirSync5,
|
|
3070
|
+
readdirSync as readdirSync9,
|
|
3071
|
+
renameSync as renameSync3,
|
|
3072
|
+
rmSync as rmSync8,
|
|
3073
|
+
statSync as statSync5
|
|
3074
|
+
} from "node:fs";
|
|
3075
|
+
import { dirname as dirname4, join as join25, relative as relative5, sep as sep3 } from "node:path";
|
|
3076
|
+
|
|
3077
|
+
// src/extras-sync.core.ts
|
|
3078
|
+
init_config();
|
|
3079
|
+
import { cpSync as cpSync4, existsSync as existsSync18, lstatSync as lstatSync8, readdirSync as readdirSync7, readFileSync as readFileSync6, rmSync as rmSync7 } from "node:fs";
|
|
3080
|
+
import { basename, join as join23, relative as relative4 } from "node:path";
|
|
3040
3081
|
|
|
3041
3082
|
// src/extras-sync.guards.ts
|
|
3042
3083
|
init_utils();
|
|
@@ -3055,6 +3096,180 @@ function assertSafeLocalRoot(localRoot, logical) {
|
|
|
3055
3096
|
}
|
|
3056
3097
|
}
|
|
3057
3098
|
|
|
3099
|
+
// src/extras-sync.core.ts
|
|
3100
|
+
init_utils();
|
|
3101
|
+
init_utils_json();
|
|
3102
|
+
function loadValidatedExtras(opts) {
|
|
3103
|
+
const repo = repoHome();
|
|
3104
|
+
const mapPath = join23(repo, "path-map.json");
|
|
3105
|
+
const repoExtras = join23(repo, "shared", "extras");
|
|
3106
|
+
if (!existsSync18(mapPath) || opts.requireRepoExtras === true && !existsSync18(repoExtras)) {
|
|
3107
|
+
if (opts.missingMsg !== void 0) log(opts.missingMsg);
|
|
3108
|
+
return null;
|
|
3109
|
+
}
|
|
3110
|
+
const map = readPathMap(mapPath);
|
|
3111
|
+
const extrasMap = map.extras ?? {};
|
|
3112
|
+
if (Object.keys(extrasMap).length === 0) return null;
|
|
3113
|
+
for (const logical of Object.keys(extrasMap)) {
|
|
3114
|
+
assertSafeLogical(logical);
|
|
3115
|
+
const localRoot = map.projects[logical]?.[HOST];
|
|
3116
|
+
if (localRoot && localRoot !== "TBD") assertSafeLocalRoot(localRoot, logical);
|
|
3117
|
+
}
|
|
3118
|
+
return { map, extrasMap };
|
|
3119
|
+
}
|
|
3120
|
+
function* eachExtrasTarget(v, counts) {
|
|
3121
|
+
const whitelist = SUPPORTED_EXTRAS;
|
|
3122
|
+
for (const [logical, dirnames] of Object.entries(v.extrasMap)) {
|
|
3123
|
+
const localRoot = v.map.projects[logical]?.[HOST];
|
|
3124
|
+
if (!localRoot || localRoot === "TBD") {
|
|
3125
|
+
counts.unmapped++;
|
|
3126
|
+
continue;
|
|
3127
|
+
}
|
|
3128
|
+
for (const dirname10 of dirnames) {
|
|
3129
|
+
if (!whitelist.includes(dirname10)) {
|
|
3130
|
+
counts.skipped++;
|
|
3131
|
+
continue;
|
|
3132
|
+
}
|
|
3133
|
+
yield { logical, localRoot, dirname: dirname10 };
|
|
3134
|
+
}
|
|
3135
|
+
}
|
|
3136
|
+
}
|
|
3137
|
+
function stripCollidingDstSymlinks(src, dst, isExcluded) {
|
|
3138
|
+
if (!existsSync18(dst)) return;
|
|
3139
|
+
for (const name of readdirSync7(src)) {
|
|
3140
|
+
if (isExcluded(name)) continue;
|
|
3141
|
+
const dstPath = join23(dst, name);
|
|
3142
|
+
const dstStat = lstatSync8(dstPath, { throwIfNoEntry: false });
|
|
3143
|
+
if (dstStat === void 0) continue;
|
|
3144
|
+
if (dstStat.isSymbolicLink()) {
|
|
3145
|
+
rmSync7(dstPath, { recursive: true, force: true });
|
|
3146
|
+
} else if (dstStat.isDirectory() && lstatSync8(join23(src, name)).isDirectory()) {
|
|
3147
|
+
stripCollidingDstSymlinks(join23(src, name), dstPath, isExcluded);
|
|
3148
|
+
}
|
|
3149
|
+
}
|
|
3150
|
+
}
|
|
3151
|
+
function cpSyncGuarded(src, dst, filter, label) {
|
|
3152
|
+
try {
|
|
3153
|
+
cpSync4(src, dst, { recursive: true, force: true, verbatimSymlinks: true, filter });
|
|
3154
|
+
} catch (err) {
|
|
3155
|
+
const e = err;
|
|
3156
|
+
if (e.code === "EINVAL" || e.code === "ENOTEMPTY" || e.code === "ERR_FS_CP_NON_DIR_TO_DIR" || e.code === "ERR_FS_CP_DIR_TO_NON_DIR") {
|
|
3157
|
+
throw new NomadFatal(
|
|
3158
|
+
`${label}: type collision copying ${JSON.stringify(src)} -> ${JSON.stringify(dst)} (${e.path ?? "unknown path"}): a file/directory type changed upstream; run nomad pull --force-remote to recover`
|
|
3159
|
+
);
|
|
3160
|
+
}
|
|
3161
|
+
throw err;
|
|
3162
|
+
}
|
|
3163
|
+
}
|
|
3164
|
+
function copyExtrasOverlayFiltered(src, dst, blockSet) {
|
|
3165
|
+
stripCollidingDstSymlinks(src, dst, (name) => isDeniedName(blockSet, name));
|
|
3166
|
+
cpSyncGuarded(
|
|
3167
|
+
src,
|
|
3168
|
+
dst,
|
|
3169
|
+
(srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename(srcEntry)),
|
|
3170
|
+
"copyExtrasOverlayFiltered"
|
|
3171
|
+
);
|
|
3172
|
+
}
|
|
3173
|
+
function copyExtrasOverlaySkipDiverged(src, dst, blockSet, divergedSet) {
|
|
3174
|
+
stripCollidingDstSymlinks(src, dst, (name) => isDeniedName(blockSet, name));
|
|
3175
|
+
cpSyncGuarded(
|
|
3176
|
+
src,
|
|
3177
|
+
dst,
|
|
3178
|
+
(srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename(srcEntry)) && !divergedSet.has(relative4(src, srcEntry)),
|
|
3179
|
+
"copyExtrasOverlaySkipDiverged"
|
|
3180
|
+
);
|
|
3181
|
+
}
|
|
3182
|
+
function copyExtras(src, dst) {
|
|
3183
|
+
rmSync7(dst, { recursive: true, force: true });
|
|
3184
|
+
cpSync4(src, dst, { recursive: true, force: true, verbatimSymlinks: true });
|
|
3185
|
+
}
|
|
3186
|
+
function copyExtrasFileSkipDiverged(src, dst) {
|
|
3187
|
+
if (existsSync18(dst)) {
|
|
3188
|
+
let dstBuf;
|
|
3189
|
+
try {
|
|
3190
|
+
dstBuf = readFileSync6(dst);
|
|
3191
|
+
} catch {
|
|
3192
|
+
return;
|
|
3193
|
+
}
|
|
3194
|
+
if (!readFileSync6(src).equals(dstBuf)) return;
|
|
3195
|
+
}
|
|
3196
|
+
copyExtras(src, dst);
|
|
3197
|
+
}
|
|
3198
|
+
function extrasDenySet(dirname10) {
|
|
3199
|
+
return dirname10 === ".claude" ? CLAUDE_EXTRA_NEVER_SYNC : ALWAYS_NEVER_SYNC;
|
|
3200
|
+
}
|
|
3201
|
+
function copyExtrasFiltered(src, dst, blockSet) {
|
|
3202
|
+
rmSync7(dst, { recursive: true, force: true });
|
|
3203
|
+
cpSync4(src, dst, {
|
|
3204
|
+
recursive: true,
|
|
3205
|
+
force: true,
|
|
3206
|
+
verbatimSymlinks: true,
|
|
3207
|
+
filter: (srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename(srcEntry))
|
|
3208
|
+
});
|
|
3209
|
+
}
|
|
3210
|
+
function prunePreservingDenied(src, dst, blockSet) {
|
|
3211
|
+
for (const name of readdirSync7(dst)) {
|
|
3212
|
+
if (isDeniedName(blockSet, name)) continue;
|
|
3213
|
+
const dstPath = join23(dst, name);
|
|
3214
|
+
const srcStat = lstatSync8(join23(src, name), { throwIfNoEntry: false });
|
|
3215
|
+
if (srcStat === void 0) {
|
|
3216
|
+
rmSync7(dstPath, { recursive: true, force: true });
|
|
3217
|
+
continue;
|
|
3218
|
+
}
|
|
3219
|
+
const dstStat = lstatSync8(dstPath);
|
|
3220
|
+
if (srcStat.isDirectory() && dstStat.isDirectory()) {
|
|
3221
|
+
prunePreservingDenied(join23(src, name), dstPath, blockSet);
|
|
3222
|
+
} else if (srcStat.isDirectory() !== dstStat.isDirectory()) {
|
|
3223
|
+
rmSync7(dstPath, { recursive: true, force: true });
|
|
3224
|
+
}
|
|
3225
|
+
}
|
|
3226
|
+
}
|
|
3227
|
+
function copyExtrasFilteredPreserving(src, dst, blockSet) {
|
|
3228
|
+
const dstStat = lstatSync8(dst, { throwIfNoEntry: false });
|
|
3229
|
+
if (dstStat !== void 0) {
|
|
3230
|
+
if (dstStat.isDirectory()) prunePreservingDenied(src, dst, blockSet);
|
|
3231
|
+
else rmSync7(dst, { recursive: true, force: true });
|
|
3232
|
+
}
|
|
3233
|
+
stripCollidingDstSymlinks(src, dst, (name) => isDeniedName(blockSet, name));
|
|
3234
|
+
cpSync4(src, dst, {
|
|
3235
|
+
recursive: true,
|
|
3236
|
+
force: true,
|
|
3237
|
+
verbatimSymlinks: true,
|
|
3238
|
+
filter: (srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename(srcEntry))
|
|
3239
|
+
});
|
|
3240
|
+
}
|
|
3241
|
+
function prunePreservingBy(src, dst, isPreserved) {
|
|
3242
|
+
for (const name of readdirSync7(dst)) {
|
|
3243
|
+
if (isPreserved(name)) continue;
|
|
3244
|
+
const dstPath = join23(dst, name);
|
|
3245
|
+
const srcStat = lstatSync8(join23(src, name), { throwIfNoEntry: false });
|
|
3246
|
+
if (srcStat === void 0) {
|
|
3247
|
+
rmSync7(dstPath, { recursive: true, force: true });
|
|
3248
|
+
continue;
|
|
3249
|
+
}
|
|
3250
|
+
const dstStat = lstatSync8(dstPath);
|
|
3251
|
+
if (srcStat.isDirectory() && dstStat.isDirectory()) {
|
|
3252
|
+
prunePreservingBy(join23(src, name), dstPath, isPreserved);
|
|
3253
|
+
} else if (srcStat.isDirectory() !== dstStat.isDirectory()) {
|
|
3254
|
+
rmSync7(dstPath, { recursive: true, force: true });
|
|
3255
|
+
}
|
|
3256
|
+
}
|
|
3257
|
+
}
|
|
3258
|
+
function copyExtrasFilteredPreservingBy(src, dst, isPreserved) {
|
|
3259
|
+
const dstStat = lstatSync8(dst, { throwIfNoEntry: false });
|
|
3260
|
+
if (dstStat !== void 0) {
|
|
3261
|
+
if (dstStat.isDirectory()) prunePreservingBy(src, dst, isPreserved);
|
|
3262
|
+
else rmSync7(dst, { recursive: true, force: true });
|
|
3263
|
+
}
|
|
3264
|
+
stripCollidingDstSymlinks(src, dst, isPreserved);
|
|
3265
|
+
cpSync4(src, dst, {
|
|
3266
|
+
recursive: true,
|
|
3267
|
+
force: true,
|
|
3268
|
+
verbatimSymlinks: true,
|
|
3269
|
+
filter: (srcEntry) => srcEntry === src || !isPreserved(basename(srcEntry))
|
|
3270
|
+
});
|
|
3271
|
+
}
|
|
3272
|
+
|
|
3058
3273
|
// src/remap.ts
|
|
3059
3274
|
init_config();
|
|
3060
3275
|
|
|
@@ -3063,8 +3278,8 @@ init_config();
|
|
|
3063
3278
|
init_push_gitleaks_config();
|
|
3064
3279
|
init_utils_fs();
|
|
3065
3280
|
import { createHash } from "node:crypto";
|
|
3066
|
-
import { existsSync as
|
|
3067
|
-
import { dirname as dirname3, join as
|
|
3281
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync4, readdirSync as readdirSync8, readFileSync as readFileSync7, statSync as statSync4 } from "node:fs";
|
|
3282
|
+
import { dirname as dirname3, join as join24 } from "node:path";
|
|
3068
3283
|
function isChanged(prev, cur, hash) {
|
|
3069
3284
|
if (prev === void 0) return true;
|
|
3070
3285
|
if (prev.size !== cur.size) return true;
|
|
@@ -3101,11 +3316,11 @@ function shouldFullRescan(old, scannerVersion, configHash, forceFlag) {
|
|
|
3101
3316
|
return false;
|
|
3102
3317
|
}
|
|
3103
3318
|
function hashFile(absPath) {
|
|
3104
|
-
return createHash("sha256").update(
|
|
3319
|
+
return createHash("sha256").update(readFileSync7(absPath)).digest("hex");
|
|
3105
3320
|
}
|
|
3106
3321
|
function enumerateDir(dir, results) {
|
|
3107
|
-
for (const entry of
|
|
3108
|
-
const fullPath =
|
|
3322
|
+
for (const entry of readdirSync8(dir)) {
|
|
3323
|
+
const fullPath = join24(dir, entry);
|
|
3109
3324
|
const st = statSync4(fullPath);
|
|
3110
3325
|
if (st.isDirectory()) {
|
|
3111
3326
|
enumerateDir(fullPath, results);
|
|
@@ -3116,8 +3331,8 @@ function enumerateDir(dir, results) {
|
|
|
3116
3331
|
}
|
|
3117
3332
|
function enumerateSourceFiles(projectDir) {
|
|
3118
3333
|
const results = [];
|
|
3119
|
-
for (const entry of
|
|
3120
|
-
const fullPath =
|
|
3334
|
+
for (const entry of readdirSync8(projectDir)) {
|
|
3335
|
+
const fullPath = join24(projectDir, entry);
|
|
3121
3336
|
const st = statSync4(fullPath);
|
|
3122
3337
|
if (st.isDirectory()) {
|
|
3123
3338
|
enumerateDir(fullPath, results);
|
|
@@ -3129,15 +3344,15 @@ function enumerateSourceFiles(projectDir) {
|
|
|
3129
3344
|
}
|
|
3130
3345
|
function fileIdentity(p) {
|
|
3131
3346
|
if (p === null) return "none::absent";
|
|
3132
|
-
if (!
|
|
3133
|
-
return `${p}::${createHash("sha256").update(
|
|
3347
|
+
if (!existsSync19(p)) return `${p}::absent`;
|
|
3348
|
+
return `${p}::${createHash("sha256").update(readFileSync7(p)).digest("hex")}`;
|
|
3134
3349
|
}
|
|
3135
3350
|
function computeConfigHash() {
|
|
3136
3351
|
const repo = repoHome();
|
|
3137
3352
|
const parts = [
|
|
3138
3353
|
fileIdentity(resolveTomlPath(repo)),
|
|
3139
|
-
fileIdentity(
|
|
3140
|
-
fileIdentity(
|
|
3354
|
+
fileIdentity(join24(repo, ".gitleaks.overlay.toml")),
|
|
3355
|
+
fileIdentity(join24(repo, ".gitleaksignore"))
|
|
3141
3356
|
];
|
|
3142
3357
|
return createHash("sha256").update(parts.join("\n")).digest("hex");
|
|
3143
3358
|
}
|
|
@@ -3148,7 +3363,7 @@ function isManifestShape(raw) {
|
|
|
3148
3363
|
}
|
|
3149
3364
|
function readManifest(path) {
|
|
3150
3365
|
try {
|
|
3151
|
-
const raw =
|
|
3366
|
+
const raw = readFileSync7(path, "utf8");
|
|
3152
3367
|
const parsed = JSON.parse(raw);
|
|
3153
3368
|
if (!isManifestShape(parsed)) return null;
|
|
3154
3369
|
return parsed;
|
|
@@ -3171,18 +3386,19 @@ init_utils_json();
|
|
|
3171
3386
|
var TMP_SUFFIX = ".nomad-tmp";
|
|
3172
3387
|
function atomicMirror(src, dst, options) {
|
|
3173
3388
|
const tmp = `${dst}${TMP_SUFFIX}`;
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3389
|
+
rmSync8(tmp, { recursive: true, force: true });
|
|
3390
|
+
cpSync5(src, tmp, options);
|
|
3391
|
+
rmSync8(dst, { recursive: true, force: true });
|
|
3177
3392
|
renameSync3(tmp, dst);
|
|
3178
3393
|
}
|
|
3179
|
-
function
|
|
3180
|
-
|
|
3394
|
+
function overlaySessionDir(src, dst) {
|
|
3395
|
+
stripCollidingDstSymlinks(src, dst, () => false);
|
|
3396
|
+
cpSyncGuarded(src, dst, void 0, "overlaySessionDir");
|
|
3181
3397
|
}
|
|
3182
3398
|
function copyFileAtomic(src, dst) {
|
|
3183
3399
|
mkdirSync5(dirname4(dst), { recursive: true });
|
|
3184
3400
|
const tmp = `${dst}.tmp.${process.pid}`;
|
|
3185
|
-
|
|
3401
|
+
cpSync5(src, tmp, { force: true, preserveTimestamps: true });
|
|
3186
3402
|
renameSync3(tmp, dst);
|
|
3187
3403
|
}
|
|
3188
3404
|
function hasDeltaForDir(sel, localDir) {
|
|
@@ -3198,12 +3414,12 @@ function applySelective(sel, localDir, repoDst) {
|
|
|
3198
3414
|
const prefix = `${localDir}${sep3}`;
|
|
3199
3415
|
for (const src of sel.changed) {
|
|
3200
3416
|
if (!src.startsWith(prefix)) continue;
|
|
3201
|
-
copyFileAtomic(src,
|
|
3417
|
+
copyFileAtomic(src, join25(repoDst, relative5(localDir, src)));
|
|
3202
3418
|
}
|
|
3203
3419
|
for (const src of sel.deleted) {
|
|
3204
3420
|
if (!src.startsWith(prefix)) continue;
|
|
3205
|
-
const dst =
|
|
3206
|
-
if (
|
|
3421
|
+
const dst = join25(repoDst, relative5(localDir, src));
|
|
3422
|
+
if (existsSync20(dst)) rmSync8(dst);
|
|
3207
3423
|
}
|
|
3208
3424
|
}
|
|
3209
3425
|
function copyDirJsonlOnly(src, dst) {
|
|
@@ -3211,7 +3427,7 @@ function copyDirJsonlOnly(src, dst) {
|
|
|
3211
3427
|
recursive: true,
|
|
3212
3428
|
force: true,
|
|
3213
3429
|
filter: (srcPath) => {
|
|
3214
|
-
const rel =
|
|
3430
|
+
const rel = relative5(src, srcPath);
|
|
3215
3431
|
if (rel === "") return true;
|
|
3216
3432
|
if (rel.split(sep3).length > 1) return true;
|
|
3217
3433
|
if (statSync5(srcPath).isDirectory()) return true;
|
|
@@ -3232,15 +3448,15 @@ function remapPull(ts, opts = {}) {
|
|
|
3232
3448
|
const wouldPull = [];
|
|
3233
3449
|
const repo = repoHome();
|
|
3234
3450
|
const claude = claudeHome();
|
|
3235
|
-
const mapPath =
|
|
3236
|
-
const repoProjects =
|
|
3237
|
-
if (!
|
|
3451
|
+
const mapPath = join25(repo, "path-map.json");
|
|
3452
|
+
const repoProjects = join25(repo, "shared", "projects");
|
|
3453
|
+
if (!existsSync20(mapPath) || !existsSync20(repoProjects)) {
|
|
3238
3454
|
const text = "no path-map or repo projects dir; skipping session remap";
|
|
3239
3455
|
emitPreview(opts.onPreview, { kind: "note", text }, text);
|
|
3240
3456
|
return { unmapped: 0, pulled, wouldPull };
|
|
3241
3457
|
}
|
|
3242
3458
|
const map = readPathMap(mapPath);
|
|
3243
|
-
const localProjects =
|
|
3459
|
+
const localProjects = join25(claude, "projects");
|
|
3244
3460
|
if (!dryRun) mkdirSync5(localProjects, { recursive: true });
|
|
3245
3461
|
for (const [logical, hosts] of Object.entries(map.projects)) {
|
|
3246
3462
|
assertSafeLogical(logical);
|
|
@@ -3250,9 +3466,9 @@ function remapPull(ts, opts = {}) {
|
|
|
3250
3466
|
continue;
|
|
3251
3467
|
}
|
|
3252
3468
|
assertSafeLocalRoot(localPath, logical);
|
|
3253
|
-
const src =
|
|
3254
|
-
if (!
|
|
3255
|
-
const dst =
|
|
3469
|
+
const src = join25(repoProjects, logical);
|
|
3470
|
+
if (!existsSync20(src)) continue;
|
|
3471
|
+
const dst = join25(localProjects, encodePath(localPath));
|
|
3256
3472
|
if (dryRun) {
|
|
3257
3473
|
wouldPull.push(logical);
|
|
3258
3474
|
emitPreview(
|
|
@@ -3263,11 +3479,44 @@ function remapPull(ts, opts = {}) {
|
|
|
3263
3479
|
continue;
|
|
3264
3480
|
}
|
|
3265
3481
|
backupBeforeWrite(dst, ts);
|
|
3266
|
-
|
|
3482
|
+
overlaySessionDir(src, dst);
|
|
3267
3483
|
pulled.push(logical);
|
|
3268
3484
|
}
|
|
3269
3485
|
return { unmapped, pulled, wouldPull };
|
|
3270
3486
|
}
|
|
3487
|
+
function countLocalOnly(src, dst) {
|
|
3488
|
+
let count = 0;
|
|
3489
|
+
for (const name of readdirSync9(dst)) {
|
|
3490
|
+
const dstPath = join25(dst, name);
|
|
3491
|
+
const srcPath = join25(src, name);
|
|
3492
|
+
if (lstatSync9(dstPath).isDirectory()) {
|
|
3493
|
+
count += countLocalOnly(srcPath, dstPath);
|
|
3494
|
+
} else if (lstatSync9(srcPath, { throwIfNoEntry: false }) === void 0) {
|
|
3495
|
+
count++;
|
|
3496
|
+
}
|
|
3497
|
+
}
|
|
3498
|
+
return count;
|
|
3499
|
+
}
|
|
3500
|
+
function scanLocalOnly() {
|
|
3501
|
+
const repo = repoHome();
|
|
3502
|
+
const claude = claudeHome();
|
|
3503
|
+
const mapPath = join25(repo, "path-map.json");
|
|
3504
|
+
const repoProjects = join25(repo, "shared", "projects");
|
|
3505
|
+
if (!existsSync20(mapPath) || !existsSync20(repoProjects)) return 0;
|
|
3506
|
+
const map = readPathMap(mapPath);
|
|
3507
|
+
const localProjects = join25(claude, "projects");
|
|
3508
|
+
let count = 0;
|
|
3509
|
+
for (const [logical, hosts] of Object.entries(map.projects)) {
|
|
3510
|
+
assertSafeLogical(logical);
|
|
3511
|
+
const localPath = hosts[HOST];
|
|
3512
|
+
if (!localPath || localPath === "TBD") continue;
|
|
3513
|
+
assertSafeLocalRoot(localPath, logical);
|
|
3514
|
+
const dst = join25(localProjects, encodePath(localPath));
|
|
3515
|
+
if (!existsSync20(dst)) continue;
|
|
3516
|
+
count += countLocalOnly(join25(repoProjects, logical), dst);
|
|
3517
|
+
}
|
|
3518
|
+
return count;
|
|
3519
|
+
}
|
|
3271
3520
|
function buildReverseMap(map) {
|
|
3272
3521
|
const reverse = /* @__PURE__ */ new Map();
|
|
3273
3522
|
const encodedPaths = /* @__PURE__ */ new Map();
|
|
@@ -3299,26 +3548,26 @@ function remapPush(ts, opts = {}) {
|
|
|
3299
3548
|
const wouldPush = [];
|
|
3300
3549
|
const repo = repoHome();
|
|
3301
3550
|
const claude = claudeHome();
|
|
3302
|
-
const mapPath =
|
|
3303
|
-
if (!
|
|
3551
|
+
const mapPath = join25(repo, "path-map.json");
|
|
3552
|
+
if (!existsSync20(mapPath)) {
|
|
3304
3553
|
log("no path-map.json; skipping session export");
|
|
3305
3554
|
return { unmapped: 0, collisions: 0, pushed, wouldPush };
|
|
3306
3555
|
}
|
|
3307
3556
|
const map = readPathMap(mapPath);
|
|
3308
|
-
const localProjects =
|
|
3309
|
-
const repoProjects =
|
|
3557
|
+
const localProjects = join25(claude, "projects");
|
|
3558
|
+
const repoProjects = join25(repo, "shared", "projects");
|
|
3310
3559
|
const reverse = buildReverseMap(map);
|
|
3311
|
-
if (!
|
|
3560
|
+
if (!existsSync20(localProjects)) return { unmapped, collisions: 0, pushed, wouldPush };
|
|
3312
3561
|
if (!dryRun) mkdirSync5(repoProjects, { recursive: true });
|
|
3313
|
-
for (const dir of
|
|
3562
|
+
for (const dir of readdirSync9(localProjects)) {
|
|
3314
3563
|
if (dir.endsWith(TMP_SUFFIX)) continue;
|
|
3315
3564
|
const logical = reverse.get(dir);
|
|
3316
3565
|
if (!logical) {
|
|
3317
3566
|
unmapped++;
|
|
3318
3567
|
continue;
|
|
3319
3568
|
}
|
|
3320
|
-
const localDir =
|
|
3321
|
-
const repoDst =
|
|
3569
|
+
const localDir = join25(localProjects, dir);
|
|
3570
|
+
const repoDst = join25(repoProjects, logical);
|
|
3322
3571
|
if (skipForSelection(opts.selection, localDir)) continue;
|
|
3323
3572
|
if (dryRun) {
|
|
3324
3573
|
wouldPush.push(logical);
|
|
@@ -3342,8 +3591,8 @@ function buildScanTree(tmpRoot) {
|
|
|
3342
3591
|
const logicalToEncoded = /* @__PURE__ */ new Map();
|
|
3343
3592
|
let staged = 0;
|
|
3344
3593
|
const repo = repoHome();
|
|
3345
|
-
const mapPath =
|
|
3346
|
-
if (!
|
|
3594
|
+
const mapPath = join26(repo, "path-map.json");
|
|
3595
|
+
if (!existsSync21(mapPath)) return { logicalToEncoded, staged, malformed: false };
|
|
3347
3596
|
let map;
|
|
3348
3597
|
try {
|
|
3349
3598
|
map = readJson(mapPath);
|
|
@@ -3360,12 +3609,12 @@ function buildScanTree(tmpRoot) {
|
|
|
3360
3609
|
if (!p || p === "TBD") continue;
|
|
3361
3610
|
reverse.set(encodePath(p), logical);
|
|
3362
3611
|
}
|
|
3363
|
-
const localProjects =
|
|
3364
|
-
if (!
|
|
3365
|
-
for (const dir of
|
|
3612
|
+
const localProjects = join26(claudeHome(), "projects");
|
|
3613
|
+
if (!existsSync21(localProjects)) return { logicalToEncoded, staged, malformed: false };
|
|
3614
|
+
for (const dir of readdirSync10(localProjects)) {
|
|
3366
3615
|
const logical = reverse.get(dir);
|
|
3367
3616
|
if (!logical) continue;
|
|
3368
|
-
copyDirJsonlOnly(
|
|
3617
|
+
copyDirJsonlOnly(join26(localProjects, dir), join26(tmpRoot, "shared", "projects", logical));
|
|
3369
3618
|
logicalToEncoded.set(logical, dir);
|
|
3370
3619
|
staged++;
|
|
3371
3620
|
}
|
|
@@ -3396,11 +3645,11 @@ function ensureGitleaksReady(section2, gitleaksReady) {
|
|
|
3396
3645
|
}
|
|
3397
3646
|
function reportCheckShared(section2, gitleaksReady) {
|
|
3398
3647
|
if (!ensureGitleaksReady(section2, gitleaksReady)) return;
|
|
3399
|
-
const cacheDir =
|
|
3648
|
+
const cacheDir = join26(homedir4(), ".cache", "claude-nomad");
|
|
3400
3649
|
mkdirSync6(cacheDir, { recursive: true });
|
|
3401
3650
|
const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes(4).toString("hex")}`;
|
|
3402
|
-
const reportPath =
|
|
3403
|
-
const tmpRoot =
|
|
3651
|
+
const reportPath = join26(cacheDir, `check-shared-${stamp}.json`);
|
|
3652
|
+
const tmpRoot = join26(cacheDir, `check-shared-tree-${stamp}`);
|
|
3404
3653
|
try {
|
|
3405
3654
|
const { logicalToEncoded, staged, malformed } = buildScanTree(tmpRoot);
|
|
3406
3655
|
if (malformed) {
|
|
@@ -3414,19 +3663,19 @@ function reportCheckShared(section2, gitleaksReady) {
|
|
|
3414
3663
|
}
|
|
3415
3664
|
scanAndReport(section2, tmpRoot, staged, logicalToEncoded);
|
|
3416
3665
|
} finally {
|
|
3417
|
-
|
|
3418
|
-
|
|
3666
|
+
rmSync9(reportPath, { force: true });
|
|
3667
|
+
rmSync9(tmpRoot, { recursive: true, force: true });
|
|
3419
3668
|
}
|
|
3420
3669
|
}
|
|
3421
3670
|
|
|
3422
3671
|
// src/commands.doctor.checks.hooks.scope.ts
|
|
3423
3672
|
init_color();
|
|
3424
|
-
import { existsSync as
|
|
3425
|
-
import { dirname as dirname5, extname, join as
|
|
3673
|
+
import { existsSync as existsSync22, readFileSync as readFileSync8, readdirSync as readdirSync11, realpathSync as realpathSync2 } from "node:fs";
|
|
3674
|
+
import { dirname as dirname5, extname, join as join27 } from "node:path";
|
|
3426
3675
|
init_config();
|
|
3427
3676
|
function typeFromPackageJson(pkgPath) {
|
|
3428
3677
|
try {
|
|
3429
|
-
const parsed = JSON.parse(
|
|
3678
|
+
const parsed = JSON.parse(readFileSync8(pkgPath, "utf8"));
|
|
3430
3679
|
return parsed.type === "module" ? "esm" : "cjs";
|
|
3431
3680
|
} catch {
|
|
3432
3681
|
return "cjs";
|
|
@@ -3444,8 +3693,8 @@ function effectiveType(hookPath) {
|
|
|
3444
3693
|
}
|
|
3445
3694
|
let dir = dirname5(real);
|
|
3446
3695
|
for (; ; ) {
|
|
3447
|
-
const pkg =
|
|
3448
|
-
if (
|
|
3696
|
+
const pkg = join27(dir, "package.json");
|
|
3697
|
+
if (existsSync22(pkg)) return typeFromPackageJson(pkg);
|
|
3449
3698
|
const parent = dirname5(dir);
|
|
3450
3699
|
if (parent === dir) return "cjs";
|
|
3451
3700
|
dir = parent;
|
|
@@ -3474,28 +3723,28 @@ function remedy(family) {
|
|
|
3474
3723
|
}
|
|
3475
3724
|
function safeReaddir2(dir) {
|
|
3476
3725
|
try {
|
|
3477
|
-
return
|
|
3726
|
+
return readdirSync11(dir);
|
|
3478
3727
|
} catch {
|
|
3479
3728
|
return [];
|
|
3480
3729
|
}
|
|
3481
3730
|
}
|
|
3482
3731
|
function safeRead(path) {
|
|
3483
3732
|
try {
|
|
3484
|
-
return
|
|
3733
|
+
return readFileSync8(path, "utf8");
|
|
3485
3734
|
} catch {
|
|
3486
3735
|
return null;
|
|
3487
3736
|
}
|
|
3488
3737
|
}
|
|
3489
3738
|
function reportHookScopeCheck(section2) {
|
|
3490
|
-
const hooksDir =
|
|
3491
|
-
if (!
|
|
3739
|
+
const hooksDir = join27(claudeHome(), "hooks");
|
|
3740
|
+
if (!existsSync22(hooksDir)) {
|
|
3492
3741
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/hooks; skipping module-scope check`);
|
|
3493
3742
|
return;
|
|
3494
3743
|
}
|
|
3495
3744
|
let anyWarn = false;
|
|
3496
3745
|
for (const name of safeReaddir2(hooksDir)) {
|
|
3497
3746
|
if (extname(name) !== ".js") continue;
|
|
3498
|
-
const abs =
|
|
3747
|
+
const abs = join27(hooksDir, name);
|
|
3499
3748
|
const eff = effectiveType(abs);
|
|
3500
3749
|
if (eff === null) continue;
|
|
3501
3750
|
const src = safeRead(abs);
|
|
@@ -3515,8 +3764,8 @@ function reportHookScopeCheck(section2) {
|
|
|
3515
3764
|
|
|
3516
3765
|
// src/commands.doctor.checks.hooks.ts
|
|
3517
3766
|
init_color();
|
|
3518
|
-
import { existsSync as
|
|
3519
|
-
import { join as
|
|
3767
|
+
import { existsSync as existsSync23 } from "node:fs";
|
|
3768
|
+
import { join as join28 } from "node:path";
|
|
3520
3769
|
init_config();
|
|
3521
3770
|
function expandHome(token) {
|
|
3522
3771
|
const h2 = home();
|
|
@@ -3559,7 +3808,7 @@ function checkEventGroups(section2, event, groups) {
|
|
|
3559
3808
|
for (const group of groups) {
|
|
3560
3809
|
for (const cmd of commandsFromGroup(group)) {
|
|
3561
3810
|
for (const resolved of claudePathsIn(cmd)) {
|
|
3562
|
-
if (
|
|
3811
|
+
if (existsSync23(resolved)) continue;
|
|
3563
3812
|
addItem(
|
|
3564
3813
|
section2,
|
|
3565
3814
|
`${red(failGlyph)} hooks/${event}: command target missing: ${resolved} (run nomad pull)`
|
|
@@ -3572,8 +3821,8 @@ function checkEventGroups(section2, event, groups) {
|
|
|
3572
3821
|
return anyFail;
|
|
3573
3822
|
}
|
|
3574
3823
|
function reportHooksTargetCheck(section2) {
|
|
3575
|
-
const settingsPath =
|
|
3576
|
-
if (!
|
|
3824
|
+
const settingsPath = join28(claudeHome(), "settings.json");
|
|
3825
|
+
if (!existsSync23(settingsPath)) {
|
|
3577
3826
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json; skipping hook target check`);
|
|
3578
3827
|
return;
|
|
3579
3828
|
}
|
|
@@ -3596,12 +3845,12 @@ function reportHooksTargetCheck(section2) {
|
|
|
3596
3845
|
|
|
3597
3846
|
// src/commands.doctor.checks.hooks.preserve-symlinks.ts
|
|
3598
3847
|
init_color();
|
|
3599
|
-
import { existsSync as
|
|
3600
|
-
import { join as
|
|
3848
|
+
import { existsSync as existsSync25, readFileSync as readFileSync9 } from "node:fs";
|
|
3849
|
+
import { join as join30 } from "node:path";
|
|
3601
3850
|
|
|
3602
3851
|
// src/commands.doctor.checks.hooks.preserve-symlinks.probe.ts
|
|
3603
|
-
import { closeSync as closeSync3, existsSync as
|
|
3604
|
-
import { dirname as dirname6, join as
|
|
3852
|
+
import { closeSync as closeSync3, existsSync as existsSync24, openSync as openSync3, readSync, realpathSync as realpathSync3 } from "node:fs";
|
|
3853
|
+
import { dirname as dirname6, join as join29, resolve as resolve2 } from "node:path";
|
|
3605
3854
|
function suppressedRanges(src) {
|
|
3606
3855
|
const ranges = [];
|
|
3607
3856
|
const re = /\/\*[\s\S]*?\*\/|\/\/[^\n]*|'[^']*'|"[^"]*"|`[^`]*`/g;
|
|
@@ -3633,12 +3882,12 @@ function topRelativeSpecifiers(src) {
|
|
|
3633
3882
|
}
|
|
3634
3883
|
function specifierIsMissing(specifier, baseDir) {
|
|
3635
3884
|
const base = resolve2(baseDir, specifier);
|
|
3636
|
-
if (
|
|
3885
|
+
if (existsSync24(base)) return false;
|
|
3637
3886
|
for (const ext of [".js", ".cjs", ".mjs"]) {
|
|
3638
|
-
if (
|
|
3887
|
+
if (existsSync24(base + ext)) return false;
|
|
3639
3888
|
}
|
|
3640
3889
|
for (const idx of ["index.js", "index.cjs", "index.mjs"]) {
|
|
3641
|
-
if (
|
|
3890
|
+
if (existsSync24(join29(base, idx))) return false;
|
|
3642
3891
|
}
|
|
3643
3892
|
return true;
|
|
3644
3893
|
}
|
|
@@ -3693,10 +3942,10 @@ function commandTokens(command) {
|
|
|
3693
3942
|
return tokens;
|
|
3694
3943
|
}
|
|
3695
3944
|
function readPathMapSafe() {
|
|
3696
|
-
const mapPath =
|
|
3697
|
-
if (!
|
|
3945
|
+
const mapPath = join30(repoHome(), "path-map.json");
|
|
3946
|
+
if (!existsSync25(mapPath)) return { projects: {} };
|
|
3698
3947
|
try {
|
|
3699
|
-
return JSON.parse(
|
|
3948
|
+
return JSON.parse(readFileSync9(mapPath, "utf8"));
|
|
3700
3949
|
} catch {
|
|
3701
3950
|
return { projects: {} };
|
|
3702
3951
|
}
|
|
@@ -3767,8 +4016,8 @@ function checkEventForPreserveSymlinks(section2, event, groups, sharedLinkNames)
|
|
|
3767
4016
|
return anyWarn;
|
|
3768
4017
|
}
|
|
3769
4018
|
function reportPreserveSymlinksCheck(section2) {
|
|
3770
|
-
const settingsPath =
|
|
3771
|
-
if (!
|
|
4019
|
+
const settingsPath = join30(claudeHome(), "settings.json");
|
|
4020
|
+
if (!existsSync25(settingsPath)) {
|
|
3772
4021
|
addItem(
|
|
3773
4022
|
section2,
|
|
3774
4023
|
`${dim(infoGlyph)} no ~/.claude/settings.json; skipping preserve-symlinks-main check`
|
|
@@ -3777,7 +4026,7 @@ function reportPreserveSymlinksCheck(section2) {
|
|
|
3777
4026
|
}
|
|
3778
4027
|
let settings;
|
|
3779
4028
|
try {
|
|
3780
|
-
settings = JSON.parse(
|
|
4029
|
+
settings = JSON.parse(readFileSync9(settingsPath, "utf8"));
|
|
3781
4030
|
} catch {
|
|
3782
4031
|
return;
|
|
3783
4032
|
}
|
|
@@ -3801,8 +4050,8 @@ function reportPreserveSymlinksCheck(section2) {
|
|
|
3801
4050
|
|
|
3802
4051
|
// src/commands.doctor.checks.settings-drift.ts
|
|
3803
4052
|
init_color();
|
|
3804
|
-
import { existsSync as
|
|
3805
|
-
import { join as
|
|
4053
|
+
import { existsSync as existsSync26, readFileSync as readFileSync10 } from "node:fs";
|
|
4054
|
+
import { join as join31 } from "node:path";
|
|
3806
4055
|
init_config();
|
|
3807
4056
|
init_utils_json();
|
|
3808
4057
|
function diffMergedSettings(merged, settings) {
|
|
@@ -3811,7 +4060,7 @@ function diffMergedSettings(merged, settings) {
|
|
|
3811
4060
|
}
|
|
3812
4061
|
function tryReadJson(filePath) {
|
|
3813
4062
|
try {
|
|
3814
|
-
const raw =
|
|
4063
|
+
const raw = readFileSync10(filePath, "utf8");
|
|
3815
4064
|
const parsed = JSON.parse(raw);
|
|
3816
4065
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
3817
4066
|
return parsed;
|
|
@@ -3820,7 +4069,7 @@ function tryReadJson(filePath) {
|
|
|
3820
4069
|
}
|
|
3821
4070
|
}
|
|
3822
4071
|
function reportHooksBaseSelfCleanNote(section2) {
|
|
3823
|
-
const basePath =
|
|
4072
|
+
const basePath = join31(repoHome(), "shared", "settings.base.json");
|
|
3824
4073
|
const base = tryReadJson(basePath);
|
|
3825
4074
|
if (base === null) return;
|
|
3826
4075
|
if (!baseHasGsdHookEntries(base)) return;
|
|
@@ -3833,14 +4082,14 @@ function reportSettingsDriftCheck(section2) {
|
|
|
3833
4082
|
const claude = claudeHome();
|
|
3834
4083
|
const repo = repoHome();
|
|
3835
4084
|
const host = HOST;
|
|
3836
|
-
const settingsPath =
|
|
3837
|
-
const basePath =
|
|
3838
|
-
const hostPath =
|
|
3839
|
-
if (!
|
|
4085
|
+
const settingsPath = join31(claude, "settings.json");
|
|
4086
|
+
const basePath = join31(repo, "shared", "settings.base.json");
|
|
4087
|
+
const hostPath = join31(repo, "hosts", `${host}.json`);
|
|
4088
|
+
if (!existsSync26(settingsPath)) {
|
|
3840
4089
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json; skipping merge-drift check`);
|
|
3841
4090
|
return;
|
|
3842
4091
|
}
|
|
3843
|
-
if (!
|
|
4092
|
+
if (!existsSync26(basePath)) {
|
|
3844
4093
|
addItem(
|
|
3845
4094
|
section2,
|
|
3846
4095
|
`${dim(infoGlyph)} shared/settings.base.json missing; skipping merge-drift check`
|
|
@@ -3859,7 +4108,7 @@ function reportSettingsDriftCheck(section2) {
|
|
|
3859
4108
|
if (settings === null) {
|
|
3860
4109
|
return;
|
|
3861
4110
|
}
|
|
3862
|
-
const hostExists =
|
|
4111
|
+
const hostExists = existsSync26(hostPath);
|
|
3863
4112
|
const hostObj = hostExists ? tryReadJson(hostPath) : null;
|
|
3864
4113
|
if (hostExists && hostObj === null) {
|
|
3865
4114
|
addItem(
|
|
@@ -3908,12 +4157,12 @@ init_config();
|
|
|
3908
4157
|
|
|
3909
4158
|
// src/commands.doctor.engine.ts
|
|
3910
4159
|
init_color();
|
|
3911
|
-
import { readFileSync as
|
|
4160
|
+
import { readFileSync as readFileSync12 } from "node:fs";
|
|
3912
4161
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
3913
4162
|
|
|
3914
4163
|
// src/commands.doctor.version.ts
|
|
3915
4164
|
init_color();
|
|
3916
|
-
import { readFileSync as
|
|
4165
|
+
import { readFileSync as readFileSync11 } from "node:fs";
|
|
3917
4166
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
3918
4167
|
init_config();
|
|
3919
4168
|
var STRICT_SEMVER = /^\d+\.\d+\.\d+$/;
|
|
@@ -3930,7 +4179,7 @@ function compareSemver(a, b) {
|
|
|
3930
4179
|
function readLocalVersion() {
|
|
3931
4180
|
try {
|
|
3932
4181
|
const pkgPath = fileURLToPath2(new URL("../package.json", import.meta.url));
|
|
3933
|
-
const parsed = JSON.parse(
|
|
4182
|
+
const parsed = JSON.parse(readFileSync11(pkgPath, "utf8"));
|
|
3934
4183
|
if (typeof parsed.version === "string" && parsed.version.length > 0) {
|
|
3935
4184
|
return parsed.version;
|
|
3936
4185
|
}
|
|
@@ -3989,7 +4238,7 @@ function parseMinVersion(spec) {
|
|
|
3989
4238
|
function readEnginesNode() {
|
|
3990
4239
|
try {
|
|
3991
4240
|
const pkgPath = fileURLToPath3(new URL("../package.json", import.meta.url));
|
|
3992
|
-
const parsed = JSON.parse(
|
|
4241
|
+
const parsed = JSON.parse(readFileSync12(pkgPath, "utf8"));
|
|
3993
4242
|
const node = parsed.engines?.node;
|
|
3994
4243
|
if (typeof node === "string" && node.length > 0) return node;
|
|
3995
4244
|
return null;
|
|
@@ -4017,44 +4266,44 @@ function reportNodeEngineCheck(section2) {
|
|
|
4017
4266
|
|
|
4018
4267
|
// src/spinner.ts
|
|
4019
4268
|
init_color();
|
|
4020
|
-
import { existsSync as
|
|
4269
|
+
import { existsSync as existsSync30 } from "node:fs";
|
|
4021
4270
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
4022
4271
|
import { Worker } from "node:worker_threads";
|
|
4023
4272
|
|
|
4024
4273
|
// src/commands.push.recovery.ts
|
|
4025
4274
|
init_config();
|
|
4026
|
-
import { readFileSync as
|
|
4027
|
-
import { join as
|
|
4275
|
+
import { readFileSync as readFileSync15, rmSync as rmSync11, writeFileSync as writeFileSync5 } from "node:fs";
|
|
4276
|
+
import { join as join36 } from "node:path";
|
|
4028
4277
|
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
4029
4278
|
|
|
4030
4279
|
// src/commands.push.recovery.actions.ts
|
|
4031
4280
|
init_config();
|
|
4032
|
-
import { readFileSync as
|
|
4281
|
+
import { readFileSync as readFileSync14 } from "node:fs";
|
|
4033
4282
|
import { isAbsolute as isAbsolute2, resolve as resolve3, sep as sep5 } from "node:path";
|
|
4034
4283
|
|
|
4035
4284
|
// src/commands.push.recovery.redact.ts
|
|
4036
4285
|
init_config();
|
|
4037
4286
|
init_config_sharedDirs_guard();
|
|
4038
|
-
import { cpSync as
|
|
4039
|
-
import { dirname as dirname8, join as
|
|
4287
|
+
import { cpSync as cpSync6, existsSync as existsSync29, mkdirSync as mkdirSync7, statSync as statSync8 } from "node:fs";
|
|
4288
|
+
import { dirname as dirname8, join as join34, sep as sep4 } from "node:path";
|
|
4040
4289
|
|
|
4041
4290
|
// src/commands.redact.ts
|
|
4042
4291
|
init_config();
|
|
4043
|
-
import { existsSync as
|
|
4044
|
-
import { dirname as dirname7, join as
|
|
4292
|
+
import { existsSync as existsSync28, statSync as statSync7 } from "node:fs";
|
|
4293
|
+
import { dirname as dirname7, join as join33 } from "node:path";
|
|
4045
4294
|
|
|
4046
4295
|
// src/commands.redact.subtree.ts
|
|
4047
|
-
import { existsSync as
|
|
4048
|
-
import { join as
|
|
4296
|
+
import { existsSync as existsSync27, lstatSync as lstatSync10, readFileSync as readFileSync13, readdirSync as readdirSync12, statSync as statSync6, writeFileSync as writeFileSync4 } from "node:fs";
|
|
4297
|
+
import { join as join32 } from "node:path";
|
|
4049
4298
|
init_utils_fs();
|
|
4050
4299
|
init_utils();
|
|
4051
4300
|
function collectFiles(dir, out) {
|
|
4052
|
-
if (!
|
|
4053
|
-
const st =
|
|
4301
|
+
if (!existsSync27(dir)) return;
|
|
4302
|
+
const st = lstatSync10(dir);
|
|
4054
4303
|
if (!st.isDirectory()) return;
|
|
4055
|
-
for (const entry of
|
|
4056
|
-
const abs =
|
|
4057
|
-
const lst =
|
|
4304
|
+
for (const entry of readdirSync12(dir)) {
|
|
4305
|
+
const abs = join32(dir, entry);
|
|
4306
|
+
const lst = lstatSync10(abs);
|
|
4058
4307
|
if (lst.isSymbolicLink()) continue;
|
|
4059
4308
|
if (lst.isDirectory()) {
|
|
4060
4309
|
collectFiles(abs, out);
|
|
@@ -4090,7 +4339,7 @@ function applySubtreeRedactions(mainPath, mainFindings, subtreeFiles, rule, ts,
|
|
|
4090
4339
|
if (!dryRun && total > 0) {
|
|
4091
4340
|
for (const { path: filePath, findings } of dirty) {
|
|
4092
4341
|
backupBeforeWrite(filePath, ts);
|
|
4093
|
-
const before =
|
|
4342
|
+
const before = readFileSync13(filePath, "utf8");
|
|
4094
4343
|
const after = applyRedactions(before, findings);
|
|
4095
4344
|
if (after === before) {
|
|
4096
4345
|
log(
|
|
@@ -4158,15 +4407,15 @@ init_utils_json();
|
|
|
4158
4407
|
init_utils();
|
|
4159
4408
|
function resolveLiveTranscript(id) {
|
|
4160
4409
|
try {
|
|
4161
|
-
const mapPath =
|
|
4162
|
-
if (!
|
|
4410
|
+
const mapPath = join33(repoHome(), "path-map.json");
|
|
4411
|
+
if (!existsSync28(mapPath)) return null;
|
|
4163
4412
|
const projects = readJson(mapPath).projects;
|
|
4164
4413
|
const claude = claudeHome();
|
|
4165
4414
|
for (const hostMap of Object.values(projects)) {
|
|
4166
4415
|
const abs = hostMap[HOST];
|
|
4167
4416
|
if (abs === void 0) continue;
|
|
4168
|
-
const live =
|
|
4169
|
-
if (
|
|
4417
|
+
const live = join33(claude, "projects", encodePath(abs), `${id}.jsonl`);
|
|
4418
|
+
if (existsSync28(live)) return live;
|
|
4170
4419
|
}
|
|
4171
4420
|
return null;
|
|
4172
4421
|
} catch {
|
|
@@ -4186,17 +4435,17 @@ function cmdRedact(opts, nowMs = Date.now, scan = scanFile) {
|
|
|
4186
4435
|
}
|
|
4187
4436
|
const repo = repoHome();
|
|
4188
4437
|
const backup = backupBase();
|
|
4189
|
-
if (!
|
|
4438
|
+
if (!existsSync28(repo)) die(`repo not cloned at ${repo}`);
|
|
4190
4439
|
const handle = acquireLock("redact");
|
|
4191
4440
|
if (handle === null) process.exit(0);
|
|
4192
4441
|
try {
|
|
4193
4442
|
const localPath = resolveLiveTranscript(id);
|
|
4194
|
-
if (localPath === null || !
|
|
4443
|
+
if (localPath === null || !existsSync28(localPath)) {
|
|
4195
4444
|
fail(`could not resolve local transcript for session ${id} on this host`);
|
|
4196
4445
|
process.exitCode = 1;
|
|
4197
4446
|
return;
|
|
4198
4447
|
}
|
|
4199
|
-
const sessionDir =
|
|
4448
|
+
const sessionDir = join33(dirname7(localPath), id);
|
|
4200
4449
|
const subtreeFiles = listSubtreeFiles(sessionDir);
|
|
4201
4450
|
const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync7(p).mtimeMs);
|
|
4202
4451
|
if (isRecentlyModified(subtreeMtime, nowMs())) {
|
|
@@ -4312,8 +4561,8 @@ function resolveStagedDir(localPath, map, claude, repo) {
|
|
|
4312
4561
|
assertSafeLogical(logical);
|
|
4313
4562
|
const abs = hostMap[HOST];
|
|
4314
4563
|
if (abs === void 0) continue;
|
|
4315
|
-
if (localPath.startsWith(
|
|
4316
|
-
return
|
|
4564
|
+
if (localPath.startsWith(join34(claude, "projects", encodePath(abs)) + sep4)) {
|
|
4565
|
+
return join34(repo, "shared", "projects", logical);
|
|
4317
4566
|
}
|
|
4318
4567
|
}
|
|
4319
4568
|
return null;
|
|
@@ -4323,7 +4572,7 @@ function preflightRedactable(f, map, nowMs) {
|
|
|
4323
4572
|
if (sid === null) return "a finding has no resolvable session id (not a session transcript)";
|
|
4324
4573
|
const localPath = resolveLiveTranscript(sid);
|
|
4325
4574
|
if (localPath === null) return `session ${sid}: local transcript not found`;
|
|
4326
|
-
const sessionDir =
|
|
4575
|
+
const sessionDir = join34(dirname8(localPath), sid);
|
|
4327
4576
|
const subtreeFiles = listSubtreeFiles(sessionDir);
|
|
4328
4577
|
const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync8(p).mtimeMs);
|
|
4329
4578
|
if (isRecentlyModified(subtreeMtime, nowMs())) {
|
|
@@ -4353,7 +4602,7 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
|
|
|
4353
4602
|
`could not locate the local transcript for session ${sid}; choose Skip or Drop session.`
|
|
4354
4603
|
);
|
|
4355
4604
|
}
|
|
4356
|
-
const sessionDir =
|
|
4605
|
+
const sessionDir = join34(dirname8(localPath), sid);
|
|
4357
4606
|
const subtreeFiles = listSubtreeFiles(sessionDir);
|
|
4358
4607
|
const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync8(p).mtimeMs);
|
|
4359
4608
|
if (isRecentlyModified(subtreeMtime, nowMs())) {
|
|
@@ -4387,26 +4636,26 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
|
|
|
4387
4636
|
);
|
|
4388
4637
|
}
|
|
4389
4638
|
mkdirSync7(stagedProjectDir, { recursive: true });
|
|
4390
|
-
|
|
4391
|
-
if (
|
|
4392
|
-
|
|
4639
|
+
cpSync6(localPath, join34(stagedProjectDir, `${sid}.jsonl`), { force: true });
|
|
4640
|
+
if (existsSync29(sessionDir)) {
|
|
4641
|
+
cpSync6(sessionDir, join34(stagedProjectDir, sid), { force: true, recursive: true });
|
|
4393
4642
|
}
|
|
4394
4643
|
return true;
|
|
4395
4644
|
}
|
|
4396
4645
|
|
|
4397
4646
|
// src/commands.push.recovery.drop.ts
|
|
4398
4647
|
init_config();
|
|
4399
|
-
import { rmSync as
|
|
4400
|
-
import { join as
|
|
4648
|
+
import { rmSync as rmSync10 } from "node:fs";
|
|
4649
|
+
import { join as join35 } from "node:path";
|
|
4401
4650
|
function dropSessionFromStaged(sid, map) {
|
|
4402
4651
|
const logicals = Object.keys(map.projects);
|
|
4403
4652
|
if (logicals.length === 0) return false;
|
|
4404
4653
|
const repo = repoHome();
|
|
4405
4654
|
for (const logical of logicals) {
|
|
4406
|
-
const jsonl =
|
|
4407
|
-
const dir =
|
|
4408
|
-
|
|
4409
|
-
|
|
4655
|
+
const jsonl = join35(repo, "shared", "projects", logical, `${sid}.jsonl`);
|
|
4656
|
+
const dir = join35(repo, "shared", "projects", logical, sid);
|
|
4657
|
+
rmSync10(jsonl, { force: true });
|
|
4658
|
+
rmSync10(dir, { recursive: true, force: true });
|
|
4410
4659
|
}
|
|
4411
4660
|
return true;
|
|
4412
4661
|
}
|
|
@@ -4440,7 +4689,7 @@ function makeDefaultReadLine(repo) {
|
|
|
4440
4689
|
if (isAbsolute2(file) || target !== repoRoot && !target.startsWith(repoRoot + sep5)) {
|
|
4441
4690
|
return null;
|
|
4442
4691
|
}
|
|
4443
|
-
const content =
|
|
4692
|
+
const content = readFileSync14(target, "utf8");
|
|
4444
4693
|
const lines = content.split(/\r?\n/);
|
|
4445
4694
|
const idx = line - 1;
|
|
4446
4695
|
if (idx < 0 || idx >= lines.length) return null;
|
|
@@ -4561,10 +4810,10 @@ function applyThenRescan(scanVerdict, repoHome2) {
|
|
|
4561
4810
|
return next;
|
|
4562
4811
|
}
|
|
4563
4812
|
function allowThenRescan(append, scanVerdict, repoHome2) {
|
|
4564
|
-
const ignPath =
|
|
4813
|
+
const ignPath = join36(repoHome2, ".gitleaksignore");
|
|
4565
4814
|
let before;
|
|
4566
4815
|
try {
|
|
4567
|
-
before =
|
|
4816
|
+
before = readFileSync15(ignPath, "utf8");
|
|
4568
4817
|
} catch {
|
|
4569
4818
|
before = null;
|
|
4570
4819
|
}
|
|
@@ -4572,7 +4821,7 @@ function allowThenRescan(append, scanVerdict, repoHome2) {
|
|
|
4572
4821
|
try {
|
|
4573
4822
|
return applyThenRescan(scanVerdict, repoHome2);
|
|
4574
4823
|
} catch (err) {
|
|
4575
|
-
if (before === null)
|
|
4824
|
+
if (before === null) rmSync11(ignPath, { force: true });
|
|
4576
4825
|
else writeFileSync5(ignPath, before, "utf8");
|
|
4577
4826
|
throw err;
|
|
4578
4827
|
}
|
|
@@ -4660,7 +4909,7 @@ function writeAnimatedDone(out, label, ms, useTTY) {
|
|
|
4660
4909
|
`);
|
|
4661
4910
|
}
|
|
4662
4911
|
function resolveWorkerPath(deps = {}) {
|
|
4663
|
-
const check = deps.existsSyncFn ??
|
|
4912
|
+
const check = deps.existsSyncFn ?? existsSync30;
|
|
4664
4913
|
const base = deps.baseUrl ?? import.meta.url;
|
|
4665
4914
|
const mjs = fileURLToPath4(new URL("./nomad.worker.mjs", base));
|
|
4666
4915
|
if (check(mjs)) return mjs;
|
|
@@ -4727,8 +4976,8 @@ function withSpinner(label, fn, deps) {
|
|
|
4727
4976
|
// src/commands.doctor.gitleaks-version.ts
|
|
4728
4977
|
init_color();
|
|
4729
4978
|
import { execFileSync as execFileSync11 } from "node:child_process";
|
|
4730
|
-
import { existsSync as
|
|
4731
|
-
import { join as
|
|
4979
|
+
import { existsSync as existsSync31 } from "node:fs";
|
|
4980
|
+
import { join as join37 } from "node:path";
|
|
4732
4981
|
init_config();
|
|
4733
4982
|
var SEMVER_MAJOR_MINOR = /^(\d+)\.(\d+)\.\d+$/;
|
|
4734
4983
|
var GITLEAKS_TIMEOUT_MS = 5e3;
|
|
@@ -4737,7 +4986,7 @@ function majorMinorOf(value) {
|
|
|
4737
4986
|
return m === null ? null : [m[1], m[2]];
|
|
4738
4987
|
}
|
|
4739
4988
|
function readGitleaksVersion(run, tomlExists) {
|
|
4740
|
-
const tomlPath =
|
|
4989
|
+
const tomlPath = join37(repoHome(), ".gitleaks.toml");
|
|
4741
4990
|
const args = ["version"];
|
|
4742
4991
|
if (tomlExists(tomlPath)) args.push("--config", tomlPath);
|
|
4743
4992
|
try {
|
|
@@ -4749,7 +4998,7 @@ function readGitleaksVersion(run, tomlExists) {
|
|
|
4749
4998
|
return null;
|
|
4750
4999
|
}
|
|
4751
5000
|
}
|
|
4752
|
-
function reportGitleaksVersionCheck(section2, run = execFileSync11, tomlExists =
|
|
5001
|
+
function reportGitleaksVersionCheck(section2, run = execFileSync11, tomlExists = existsSync31) {
|
|
4753
5002
|
const raw = readGitleaksVersion(run, tomlExists);
|
|
4754
5003
|
if (raw === null) return;
|
|
4755
5004
|
const local = majorMinorOf(raw);
|
|
@@ -4972,8 +5221,8 @@ function gatherDoctorSections(opts) {
|
|
|
4972
5221
|
reportHostKeyAlignment(host);
|
|
4973
5222
|
reportRepoState(host);
|
|
4974
5223
|
const links = section("Shared links");
|
|
4975
|
-
const mapPath =
|
|
4976
|
-
const rawMap =
|
|
5224
|
+
const mapPath = join38(repoHome(), "path-map.json");
|
|
5225
|
+
const rawMap = existsSync32(mapPath) ? readJsonSafe(mapPath, mapPath, links) : null;
|
|
4977
5226
|
const map = rawMap ?? { projects: {} };
|
|
4978
5227
|
reportSharedLinks(links, map);
|
|
4979
5228
|
reportDroppedNamesMigration(links);
|
|
@@ -5070,8 +5319,8 @@ function parseDoctorArgs(args) {
|
|
|
5070
5319
|
// src/commands.drop-session.ts
|
|
5071
5320
|
init_config();
|
|
5072
5321
|
import { execFileSync as execFileSync16 } from "node:child_process";
|
|
5073
|
-
import { existsSync as
|
|
5074
|
-
import { join as
|
|
5322
|
+
import { existsSync as existsSync34, readdirSync as readdirSync13, statSync as statSync9 } from "node:fs";
|
|
5323
|
+
import { join as join40, relative as relative6 } from "node:path";
|
|
5075
5324
|
|
|
5076
5325
|
// src/commands.drop-session.git.ts
|
|
5077
5326
|
import { execFileSync as execFileSync15 } from "node:child_process";
|
|
@@ -5113,8 +5362,8 @@ function isInIndex(rel, repo) {
|
|
|
5113
5362
|
init_config();
|
|
5114
5363
|
init_utils();
|
|
5115
5364
|
init_utils_json();
|
|
5116
|
-
import { existsSync as
|
|
5117
|
-
import { join as
|
|
5365
|
+
import { existsSync as existsSync33 } from "node:fs";
|
|
5366
|
+
import { join as join39 } from "node:path";
|
|
5118
5367
|
var SHARED_PROJECT_LOGICAL = /^shared\/projects\/([^/]+)\//;
|
|
5119
5368
|
function reportScrubHint(id, matches) {
|
|
5120
5369
|
const live = resolveLiveTranscript2(id, matches);
|
|
@@ -5130,8 +5379,8 @@ function reportScrubHint(id, matches) {
|
|
|
5130
5379
|
}
|
|
5131
5380
|
function resolveLiveTranscript2(id, matches) {
|
|
5132
5381
|
try {
|
|
5133
|
-
const mapPath =
|
|
5134
|
-
if (!
|
|
5382
|
+
const mapPath = join39(repoHome(), "path-map.json");
|
|
5383
|
+
if (!existsSync33(mapPath)) return null;
|
|
5135
5384
|
const projects = readJson(mapPath).projects;
|
|
5136
5385
|
const claude = claudeHome();
|
|
5137
5386
|
for (const rel of matches) {
|
|
@@ -5139,8 +5388,8 @@ function resolveLiveTranscript2(id, matches) {
|
|
|
5139
5388
|
if (logical === void 0) continue;
|
|
5140
5389
|
const abs = projects[logical]?.[HOST];
|
|
5141
5390
|
if (abs === void 0) continue;
|
|
5142
|
-
const live =
|
|
5143
|
-
if (
|
|
5391
|
+
const live = join39(claude, "projects", encodePath(abs), `${id}.jsonl`);
|
|
5392
|
+
if (existsSync33(live)) return live;
|
|
5144
5393
|
}
|
|
5145
5394
|
return null;
|
|
5146
5395
|
} catch {
|
|
@@ -5156,12 +5405,12 @@ function cmdDropSession(id) {
|
|
|
5156
5405
|
process.exit(1);
|
|
5157
5406
|
}
|
|
5158
5407
|
const repo = repoHome();
|
|
5159
|
-
if (!
|
|
5408
|
+
if (!existsSync34(repo)) die(`repo not cloned at ${repo}`);
|
|
5160
5409
|
const handle = acquireLock("drop-session");
|
|
5161
5410
|
if (handle === null) process.exit(0);
|
|
5162
5411
|
try {
|
|
5163
|
-
const repoProjects =
|
|
5164
|
-
if (!
|
|
5412
|
+
const repoProjects = join40(repo, "shared", "projects");
|
|
5413
|
+
if (!existsSync34(repoProjects)) {
|
|
5165
5414
|
throw new NomadFatal(`no staged session matches ${id}`);
|
|
5166
5415
|
}
|
|
5167
5416
|
const matches = collectMatches(repoProjects, id, repo);
|
|
@@ -5183,14 +5432,14 @@ function cmdDropSession(id) {
|
|
|
5183
5432
|
}
|
|
5184
5433
|
function collectMatches(repoProjects, id, repo) {
|
|
5185
5434
|
const matches = [];
|
|
5186
|
-
for (const logical of
|
|
5187
|
-
const candidate =
|
|
5188
|
-
if (
|
|
5189
|
-
matches.push(
|
|
5190
|
-
}
|
|
5191
|
-
const dir =
|
|
5192
|
-
if (
|
|
5193
|
-
const dirRel =
|
|
5435
|
+
for (const logical of readdirSync13(repoProjects)) {
|
|
5436
|
+
const candidate = join40(repoProjects, logical, `${id}.jsonl`);
|
|
5437
|
+
if (existsSync34(candidate)) {
|
|
5438
|
+
matches.push(relative6(repo, candidate));
|
|
5439
|
+
}
|
|
5440
|
+
const dir = join40(repoProjects, logical, id);
|
|
5441
|
+
if (existsSync34(dir) && statSync9(dir).isDirectory()) {
|
|
5442
|
+
const dirRel = relative6(repo, dir);
|
|
5194
5443
|
const staged = expandStagedDir(dirRel, repo);
|
|
5195
5444
|
if (staged.length > 0) matches.push(...staged);
|
|
5196
5445
|
else matches.push(dirRel);
|
|
@@ -5232,7 +5481,7 @@ init_color();
|
|
|
5232
5481
|
|
|
5233
5482
|
// src/summary.ts
|
|
5234
5483
|
init_utils();
|
|
5235
|
-
function summaryText(verb, unmapped, collisions = 0, extrasSkipped = 0) {
|
|
5484
|
+
function summaryText(verb, unmapped, collisions = 0, extrasSkipped = 0, localOnly = 0) {
|
|
5236
5485
|
const extras = extrasSkipped > 0 ? `, ${extrasSkipped} extras skipped` : "";
|
|
5237
5486
|
if (verb === "push") {
|
|
5238
5487
|
if (unmapped === 0 && collisions === 0 && extrasSkipped === 0) {
|
|
@@ -5241,16 +5490,17 @@ function summaryText(verb, unmapped, collisions = 0, extrasSkipped = 0) {
|
|
|
5241
5490
|
const base = `summary: ${unmapped} unmapped on push, ${collisions} collisions`;
|
|
5242
5491
|
return { text: `${base}${extras} (run nomad doctor to list)`, clean: false };
|
|
5243
5492
|
}
|
|
5244
|
-
if (unmapped === 0 && extrasSkipped === 0) {
|
|
5493
|
+
if (unmapped === 0 && extrasSkipped === 0 && localOnly === 0) {
|
|
5245
5494
|
return { text: "summary: clean", clean: true };
|
|
5246
5495
|
}
|
|
5496
|
+
const localOnlyPhrase = localOnly > 0 ? `, ${localOnly} local-only present (push to reconcile)` : "";
|
|
5247
5497
|
return {
|
|
5248
|
-
text: `summary: ${unmapped} unmapped on ${verb}${extras} (run nomad doctor to list)`,
|
|
5498
|
+
text: `summary: ${unmapped} unmapped on ${verb}${extras} (run nomad doctor to list)${localOnlyPhrase}`,
|
|
5249
5499
|
clean: false
|
|
5250
5500
|
};
|
|
5251
5501
|
}
|
|
5252
|
-
function summaryRow(verb, unmapped, collisions = 0, extrasSkipped = 0) {
|
|
5253
|
-
const { text } = summaryText(verb, unmapped, collisions, extrasSkipped);
|
|
5502
|
+
function summaryRow(verb, unmapped, collisions = 0, extrasSkipped = 0, localOnly = 0) {
|
|
5503
|
+
const { text } = summaryText(verb, unmapped, collisions, extrasSkipped, localOnly);
|
|
5254
5504
|
return text.replace(/^summary: /, "");
|
|
5255
5505
|
}
|
|
5256
5506
|
|
|
@@ -5264,11 +5514,17 @@ function buildSettingsSection(label) {
|
|
|
5264
5514
|
addItem(s, `${green(okGlyph)} settings.json (base + ${label})`);
|
|
5265
5515
|
return s;
|
|
5266
5516
|
}
|
|
5267
|
-
function buildSessionsSection(items, unmapped) {
|
|
5517
|
+
function buildSessionsSection(items, unmapped, localOnly = 0) {
|
|
5268
5518
|
const s = section("Sessions");
|
|
5269
5519
|
for (const logical of items) addItem(s, `${green(okGlyph)} ${logical}`);
|
|
5270
5520
|
const skip = collapsedSkipRow(unmapped, "not in path-map (run nomad doctor to list)");
|
|
5271
5521
|
if (skip !== null) addItem(s, skip);
|
|
5522
|
+
if (localOnly > 0) {
|
|
5523
|
+
addItem(
|
|
5524
|
+
s,
|
|
5525
|
+
`${yellow(warnGlyph)} ${localOnly} local-only present, not in repo (push to reconcile)`
|
|
5526
|
+
);
|
|
5527
|
+
}
|
|
5272
5528
|
return s;
|
|
5273
5529
|
}
|
|
5274
5530
|
function buildExtrasSection(items, extrasSkipped) {
|
|
@@ -5322,167 +5578,12 @@ init_config();
|
|
|
5322
5578
|
init_config();
|
|
5323
5579
|
import { existsSync as existsSync36, statSync as statSync10 } from "node:fs";
|
|
5324
5580
|
import { join as join43 } from "node:path";
|
|
5325
|
-
|
|
5326
|
-
// src/extras-sync.core.ts
|
|
5327
|
-
init_config();
|
|
5328
|
-
import { cpSync as cpSync6, existsSync as existsSync34, lstatSync as lstatSync9, readdirSync as readdirSync13, rmSync as rmSync11 } from "node:fs";
|
|
5329
|
-
import { basename, join as join40 } from "node:path";
|
|
5330
|
-
init_utils();
|
|
5331
|
-
init_utils_json();
|
|
5332
|
-
function loadValidatedExtras(opts) {
|
|
5333
|
-
const repo = repoHome();
|
|
5334
|
-
const mapPath = join40(repo, "path-map.json");
|
|
5335
|
-
const repoExtras = join40(repo, "shared", "extras");
|
|
5336
|
-
if (!existsSync34(mapPath) || opts.requireRepoExtras === true && !existsSync34(repoExtras)) {
|
|
5337
|
-
if (opts.missingMsg !== void 0) log(opts.missingMsg);
|
|
5338
|
-
return null;
|
|
5339
|
-
}
|
|
5340
|
-
const map = readPathMap(mapPath);
|
|
5341
|
-
const extrasMap = map.extras ?? {};
|
|
5342
|
-
if (Object.keys(extrasMap).length === 0) return null;
|
|
5343
|
-
for (const logical of Object.keys(extrasMap)) {
|
|
5344
|
-
assertSafeLogical(logical);
|
|
5345
|
-
const localRoot = map.projects[logical]?.[HOST];
|
|
5346
|
-
if (localRoot && localRoot !== "TBD") assertSafeLocalRoot(localRoot, logical);
|
|
5347
|
-
}
|
|
5348
|
-
return { map, extrasMap };
|
|
5349
|
-
}
|
|
5350
|
-
function* eachExtrasTarget(v, counts) {
|
|
5351
|
-
const whitelist = SUPPORTED_EXTRAS;
|
|
5352
|
-
for (const [logical, dirnames] of Object.entries(v.extrasMap)) {
|
|
5353
|
-
const localRoot = v.map.projects[logical]?.[HOST];
|
|
5354
|
-
if (!localRoot || localRoot === "TBD") {
|
|
5355
|
-
counts.unmapped++;
|
|
5356
|
-
continue;
|
|
5357
|
-
}
|
|
5358
|
-
for (const dirname10 of dirnames) {
|
|
5359
|
-
if (!whitelist.includes(dirname10)) {
|
|
5360
|
-
counts.skipped++;
|
|
5361
|
-
continue;
|
|
5362
|
-
}
|
|
5363
|
-
yield { logical, localRoot, dirname: dirname10 };
|
|
5364
|
-
}
|
|
5365
|
-
}
|
|
5366
|
-
}
|
|
5367
|
-
function stripCollidingDstSymlinks(src, dst, isExcluded) {
|
|
5368
|
-
if (!existsSync34(dst)) return;
|
|
5369
|
-
for (const name of readdirSync13(src)) {
|
|
5370
|
-
if (isExcluded(name)) continue;
|
|
5371
|
-
const dstPath = join40(dst, name);
|
|
5372
|
-
const dstStat = lstatSync9(dstPath, { throwIfNoEntry: false });
|
|
5373
|
-
if (dstStat === void 0) continue;
|
|
5374
|
-
if (dstStat.isSymbolicLink()) {
|
|
5375
|
-
rmSync11(dstPath, { recursive: true, force: true });
|
|
5376
|
-
} else if (dstStat.isDirectory() && lstatSync9(join40(src, name)).isDirectory()) {
|
|
5377
|
-
stripCollidingDstSymlinks(join40(src, name), dstPath, isExcluded);
|
|
5378
|
-
}
|
|
5379
|
-
}
|
|
5380
|
-
}
|
|
5381
|
-
function copyExtrasOverlayFiltered(src, dst, blockSet) {
|
|
5382
|
-
stripCollidingDstSymlinks(src, dst, (name) => isDeniedName(blockSet, name));
|
|
5383
|
-
try {
|
|
5384
|
-
cpSync6(src, dst, {
|
|
5385
|
-
recursive: true,
|
|
5386
|
-
force: true,
|
|
5387
|
-
verbatimSymlinks: true,
|
|
5388
|
-
filter: (srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename(srcEntry))
|
|
5389
|
-
});
|
|
5390
|
-
} catch (err) {
|
|
5391
|
-
const e = err;
|
|
5392
|
-
if (e.code === "EINVAL" || e.code === "ENOTEMPTY" || e.code === "ERR_FS_CP_NON_DIR_TO_DIR" || e.code === "ERR_FS_CP_DIR_TO_NON_DIR") {
|
|
5393
|
-
throw new NomadFatal(
|
|
5394
|
-
`copyExtrasOverlayFiltered: type collision copying ${JSON.stringify(src)} -> ${JSON.stringify(dst)} (${e.path ?? "unknown path"}): a file/directory type changed upstream; run nomad pull --force-remote to recover`
|
|
5395
|
-
);
|
|
5396
|
-
}
|
|
5397
|
-
throw err;
|
|
5398
|
-
}
|
|
5399
|
-
}
|
|
5400
|
-
function copyExtras(src, dst) {
|
|
5401
|
-
rmSync11(dst, { recursive: true, force: true });
|
|
5402
|
-
cpSync6(src, dst, { recursive: true, force: true, verbatimSymlinks: true });
|
|
5403
|
-
}
|
|
5404
|
-
function extrasDenySet(dirname10) {
|
|
5405
|
-
return dirname10 === ".claude" ? CLAUDE_EXTRA_NEVER_SYNC : ALWAYS_NEVER_SYNC;
|
|
5406
|
-
}
|
|
5407
|
-
function copyExtrasFiltered(src, dst, blockSet) {
|
|
5408
|
-
rmSync11(dst, { recursive: true, force: true });
|
|
5409
|
-
cpSync6(src, dst, {
|
|
5410
|
-
recursive: true,
|
|
5411
|
-
force: true,
|
|
5412
|
-
verbatimSymlinks: true,
|
|
5413
|
-
filter: (srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename(srcEntry))
|
|
5414
|
-
});
|
|
5415
|
-
}
|
|
5416
|
-
function prunePreservingDenied(src, dst, blockSet) {
|
|
5417
|
-
for (const name of readdirSync13(dst)) {
|
|
5418
|
-
if (isDeniedName(blockSet, name)) continue;
|
|
5419
|
-
const dstPath = join40(dst, name);
|
|
5420
|
-
const srcStat = lstatSync9(join40(src, name), { throwIfNoEntry: false });
|
|
5421
|
-
if (srcStat === void 0) {
|
|
5422
|
-
rmSync11(dstPath, { recursive: true, force: true });
|
|
5423
|
-
continue;
|
|
5424
|
-
}
|
|
5425
|
-
const dstStat = lstatSync9(dstPath);
|
|
5426
|
-
if (srcStat.isDirectory() && dstStat.isDirectory()) {
|
|
5427
|
-
prunePreservingDenied(join40(src, name), dstPath, blockSet);
|
|
5428
|
-
} else if (srcStat.isDirectory() !== dstStat.isDirectory()) {
|
|
5429
|
-
rmSync11(dstPath, { recursive: true, force: true });
|
|
5430
|
-
}
|
|
5431
|
-
}
|
|
5432
|
-
}
|
|
5433
|
-
function copyExtrasFilteredPreserving(src, dst, blockSet) {
|
|
5434
|
-
const dstStat = lstatSync9(dst, { throwIfNoEntry: false });
|
|
5435
|
-
if (dstStat !== void 0) {
|
|
5436
|
-
if (dstStat.isDirectory()) prunePreservingDenied(src, dst, blockSet);
|
|
5437
|
-
else rmSync11(dst, { recursive: true, force: true });
|
|
5438
|
-
}
|
|
5439
|
-
stripCollidingDstSymlinks(src, dst, (name) => isDeniedName(blockSet, name));
|
|
5440
|
-
cpSync6(src, dst, {
|
|
5441
|
-
recursive: true,
|
|
5442
|
-
force: true,
|
|
5443
|
-
verbatimSymlinks: true,
|
|
5444
|
-
filter: (srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename(srcEntry))
|
|
5445
|
-
});
|
|
5446
|
-
}
|
|
5447
|
-
function prunePreservingBy(src, dst, isPreserved) {
|
|
5448
|
-
for (const name of readdirSync13(dst)) {
|
|
5449
|
-
if (isPreserved(name)) continue;
|
|
5450
|
-
const dstPath = join40(dst, name);
|
|
5451
|
-
const srcStat = lstatSync9(join40(src, name), { throwIfNoEntry: false });
|
|
5452
|
-
if (srcStat === void 0) {
|
|
5453
|
-
rmSync11(dstPath, { recursive: true, force: true });
|
|
5454
|
-
continue;
|
|
5455
|
-
}
|
|
5456
|
-
const dstStat = lstatSync9(dstPath);
|
|
5457
|
-
if (srcStat.isDirectory() && dstStat.isDirectory()) {
|
|
5458
|
-
prunePreservingBy(join40(src, name), dstPath, isPreserved);
|
|
5459
|
-
} else if (srcStat.isDirectory() !== dstStat.isDirectory()) {
|
|
5460
|
-
rmSync11(dstPath, { recursive: true, force: true });
|
|
5461
|
-
}
|
|
5462
|
-
}
|
|
5463
|
-
}
|
|
5464
|
-
function copyExtrasFilteredPreservingBy(src, dst, isPreserved) {
|
|
5465
|
-
const dstStat = lstatSync9(dst, { throwIfNoEntry: false });
|
|
5466
|
-
if (dstStat !== void 0) {
|
|
5467
|
-
if (dstStat.isDirectory()) prunePreservingBy(src, dst, isPreserved);
|
|
5468
|
-
else rmSync11(dst, { recursive: true, force: true });
|
|
5469
|
-
}
|
|
5470
|
-
stripCollidingDstSymlinks(src, dst, isPreserved);
|
|
5471
|
-
cpSync6(src, dst, {
|
|
5472
|
-
recursive: true,
|
|
5473
|
-
force: true,
|
|
5474
|
-
verbatimSymlinks: true,
|
|
5475
|
-
filter: (srcEntry) => srcEntry === src || !isPreserved(basename(srcEntry))
|
|
5476
|
-
});
|
|
5477
|
-
}
|
|
5478
|
-
|
|
5479
|
-
// src/extras-sync.ts
|
|
5480
5581
|
init_utils();
|
|
5481
5582
|
init_utils_json();
|
|
5482
5583
|
|
|
5483
5584
|
// src/extras-sync.remap.ts
|
|
5484
5585
|
init_config();
|
|
5485
|
-
import { existsSync as existsSync35, mkdirSync as mkdirSync8, readdirSync as readdirSync14, realpathSync as realpathSync4, rmSync as rmSync12 } from "node:fs";
|
|
5586
|
+
import { existsSync as existsSync35, mkdirSync as mkdirSync8, readdirSync as readdirSync14, readFileSync as readFileSync16, realpathSync as realpathSync4, rmSync as rmSync12 } from "node:fs";
|
|
5486
5587
|
import { dirname as dirname9, join as join42, sep as sep7 } from "node:path";
|
|
5487
5588
|
|
|
5488
5589
|
// src/extras-sync.planning-diff.ts
|
|
@@ -5611,24 +5712,58 @@ function deletePlanningTarget(target, planningRoot, repoCounterpart) {
|
|
|
5611
5712
|
rmSync12(target, { recursive: true, force: true });
|
|
5612
5713
|
pruneEmptyAncestors(target, planningRoot);
|
|
5613
5714
|
}
|
|
5715
|
+
function localDivergesFromPreDelete(target, pre, repoRel, repo) {
|
|
5716
|
+
if (!existsSync35(target)) return false;
|
|
5717
|
+
try {
|
|
5718
|
+
const preBlob = gitCaptureBuffer(["show", `${pre}:${repoRel}`], repo);
|
|
5719
|
+
return !readFileSync16(target).equals(preBlob);
|
|
5720
|
+
} catch {
|
|
5721
|
+
return true;
|
|
5722
|
+
}
|
|
5723
|
+
}
|
|
5724
|
+
function planningDiffArgs(pre, post, logical) {
|
|
5725
|
+
return ["diff", "--name-status", "-z", pre, post, "--", `shared/extras/${logical}/.planning/`];
|
|
5726
|
+
}
|
|
5727
|
+
function deletePairsFor(t, raw) {
|
|
5728
|
+
return planningDeleteTargets({ raw, logical: t.logical, localRoot: t.localRoot }).map(
|
|
5729
|
+
(target) => {
|
|
5730
|
+
const relToLocal = target.slice(t.localRoot.length + sep7.length);
|
|
5731
|
+
return {
|
|
5732
|
+
target,
|
|
5733
|
+
relToLocal,
|
|
5734
|
+
repoRel: `shared/extras/${t.logical}/${relToLocal.split(sep7).join("/")}`
|
|
5735
|
+
};
|
|
5736
|
+
}
|
|
5737
|
+
);
|
|
5738
|
+
}
|
|
5739
|
+
function keptDeleteWarnLine(logical, relToLocal) {
|
|
5740
|
+
return `keeping locally-edited ${relToLocal} in ${logical}: deleted upstream but changed locally (push to reconcile; your copy is backed up)`;
|
|
5741
|
+
}
|
|
5742
|
+
function keptDeletePreview(v, prePostHeads, repo) {
|
|
5743
|
+
const kept = [];
|
|
5744
|
+
for (const t of eachExtrasTarget(v, { unmapped: 0, skipped: 0 })) {
|
|
5745
|
+
if (t.dirname !== ".planning") continue;
|
|
5746
|
+
let raw;
|
|
5747
|
+
try {
|
|
5748
|
+
raw = gitCaptureRaw(planningDiffArgs(prePostHeads.pre, prePostHeads.post, t.logical), repo);
|
|
5749
|
+
} catch {
|
|
5750
|
+
continue;
|
|
5751
|
+
}
|
|
5752
|
+
for (const { target, relToLocal, repoRel } of deletePairsFor(t, raw)) {
|
|
5753
|
+
if (localDivergesFromPreDelete(target, prePostHeads.pre, repoRel, repo)) {
|
|
5754
|
+
kept.push({ logical: t.logical, relToLocal });
|
|
5755
|
+
}
|
|
5756
|
+
}
|
|
5757
|
+
}
|
|
5758
|
+
return kept;
|
|
5759
|
+
}
|
|
5614
5760
|
function propagatePlanningDeletes(v, ts, prePostHeads, repo) {
|
|
5615
5761
|
const repoExtras = join42(repo, "shared", "extras");
|
|
5616
5762
|
for (const t of eachExtrasTarget(v, { unmapped: 0, skipped: 0 })) {
|
|
5617
5763
|
if (t.dirname !== ".planning") continue;
|
|
5618
5764
|
let raw;
|
|
5619
5765
|
try {
|
|
5620
|
-
raw = gitCaptureRaw(
|
|
5621
|
-
[
|
|
5622
|
-
"diff",
|
|
5623
|
-
"--name-status",
|
|
5624
|
-
"-z",
|
|
5625
|
-
prePostHeads.pre,
|
|
5626
|
-
prePostHeads.post,
|
|
5627
|
-
"--",
|
|
5628
|
-
`shared/extras/${t.logical}/.planning/`
|
|
5629
|
-
],
|
|
5630
|
-
repo
|
|
5631
|
-
);
|
|
5766
|
+
raw = gitCaptureRaw(planningDiffArgs(prePostHeads.pre, prePostHeads.post, t.logical), repo);
|
|
5632
5767
|
} catch (err) {
|
|
5633
5768
|
const e = err;
|
|
5634
5769
|
if (e.stderr) process.stderr.write(e.stderr);
|
|
@@ -5636,12 +5771,15 @@ function propagatePlanningDeletes(v, ts, prePostHeads, repo) {
|
|
|
5636
5771
|
`git diff failed while propagating .planning deletes for ${t.logical}; run nomad pull --force-remote to recover`
|
|
5637
5772
|
);
|
|
5638
5773
|
}
|
|
5639
|
-
const
|
|
5640
|
-
if (
|
|
5774
|
+
const pairs = deletePairsFor(t, raw);
|
|
5775
|
+
if (pairs.length === 0) continue;
|
|
5641
5776
|
backupExtrasWrite(join42(t.localRoot, t.dirname), ts, t.localRoot);
|
|
5642
5777
|
const planningRoot = join42(t.localRoot, ".planning");
|
|
5643
|
-
for (const target of
|
|
5644
|
-
|
|
5778
|
+
for (const { target, relToLocal, repoRel } of pairs) {
|
|
5779
|
+
if (localDivergesFromPreDelete(target, prePostHeads.pre, repoRel, repo)) {
|
|
5780
|
+
warn(keptDeleteWarnLine(t.logical, relToLocal));
|
|
5781
|
+
continue;
|
|
5782
|
+
}
|
|
5645
5783
|
deletePlanningTarget(target, planningRoot, join42(repoExtras, t.logical, relToLocal));
|
|
5646
5784
|
}
|
|
5647
5785
|
}
|
|
@@ -5690,23 +5828,30 @@ function remapExtrasPull(ts, opts = {}) {
|
|
|
5690
5828
|
src: join42(repo, "shared", "extras", logical, dirname10),
|
|
5691
5829
|
dst: join42(localRoot, dirname10)
|
|
5692
5830
|
}),
|
|
5693
|
-
// Snapshot the host-side dst BEFORE
|
|
5831
|
+
// Snapshot the host-side dst BEFORE the copy step touches it. Anchor on
|
|
5694
5832
|
// localRoot so the backup tree mirrors the project layout.
|
|
5695
5833
|
(dst, localRoot) => backupExtrasWrite(dst, ts, localRoot),
|
|
5696
5834
|
// Pull routing per extra type:
|
|
5697
5835
|
// `.claude`: copyExtrasFilteredPreserving preserves host-local deny-set
|
|
5698
5836
|
// files (e.g. settings.local.json) while mirror-pruning synced entries.
|
|
5699
|
-
// `.planning`:
|
|
5700
|
-
// keeps local-only files
|
|
5701
|
-
//
|
|
5702
|
-
//
|
|
5703
|
-
//
|
|
5837
|
+
// `.planning`: copyExtrasOverlaySkipDiverged (no rmSync; deny-set filtered)
|
|
5838
|
+
// keeps local-only files AND skips any file whose local copy diverges
|
|
5839
|
+
// from the repo copy (content hash differs), so a local hand-edit wins
|
|
5840
|
+
// on conflict; the delete pass below still propagates
|
|
5841
|
+
// upstream removals via the git-diff D set. The filter is defense-in-
|
|
5842
|
+
// depth against a repo poisoned out-of-band.
|
|
5843
|
+
// All others (a single root-level file, e.g. CLAUDE.md):
|
|
5844
|
+
// copyExtrasFileSkipDiverged keeps a locally-edited file rather than
|
|
5845
|
+
// clobbering it, so the divergence WARN's keep-local promise holds for
|
|
5846
|
+
// every extra, not just `.planning`.
|
|
5704
5847
|
(src, dst, dirname10) => {
|
|
5705
5848
|
if (dirname10 === ".claude")
|
|
5706
5849
|
return copyExtrasFilteredPreserving(src, dst, extrasDenySet(dirname10));
|
|
5707
|
-
if (dirname10 === ".planning")
|
|
5708
|
-
|
|
5709
|
-
|
|
5850
|
+
if (dirname10 === ".planning") {
|
|
5851
|
+
const divergedSet = new Set(listDivergingModified(dst, src));
|
|
5852
|
+
return copyExtrasOverlaySkipDiverged(src, dst, extrasDenySet(dirname10), divergedSet);
|
|
5853
|
+
}
|
|
5854
|
+
return copyExtrasFileSkipDiverged(src, dst);
|
|
5710
5855
|
}
|
|
5711
5856
|
);
|
|
5712
5857
|
if (!dryRun && prePostHeads !== void 0) {
|
|
@@ -5721,11 +5866,10 @@ function divergenceWarnLine(o) {
|
|
|
5721
5866
|
const name = o.isDir ? `${o.dirname}/` : o.dirname;
|
|
5722
5867
|
const one = o.count === 1;
|
|
5723
5868
|
const fileCount = one ? "1 file" : `${o.count} files`;
|
|
5724
|
-
const them = one ? "it" : "them";
|
|
5725
5869
|
const yours = one ? "your current file is" : "your current files are";
|
|
5726
|
-
return `local ${kind} ${name} in repo ${o.logical} differs from the synced copy in ${fileCount}; the next pull step will
|
|
5870
|
+
return `local ${kind} ${name} in repo ${o.logical} differs from the synced copy in ${fileCount}; the next pull step will keep your local copy (push to reconcile; ${yours} backed up to ${o.projectBackupRoot}/)`;
|
|
5727
5871
|
}
|
|
5728
|
-
function divergenceCheckExtras(ts) {
|
|
5872
|
+
function divergenceCheckExtras(ts, prePostHeads) {
|
|
5729
5873
|
const v = loadValidatedExtras({});
|
|
5730
5874
|
if (v === null) return;
|
|
5731
5875
|
const counts = { unmapped: 0, skipped: 0 };
|
|
@@ -5735,25 +5879,30 @@ function divergenceCheckExtras(ts) {
|
|
|
5735
5879
|
const local = join43(localRoot, dirname10);
|
|
5736
5880
|
const repoEntry = join43(repo, "shared", "extras", logical, dirname10);
|
|
5737
5881
|
if (!existsSync36(local) || !existsSync36(repoEntry)) continue;
|
|
5738
|
-
const
|
|
5739
|
-
if (
|
|
5882
|
+
const modified = listDivergingModified(local, repoEntry);
|
|
5883
|
+
if (modified.length === 0) continue;
|
|
5740
5884
|
const projectBackupRoot = join43(backupRoot, encodePath(localRoot));
|
|
5741
5885
|
warn(
|
|
5742
5886
|
divergenceWarnLine({
|
|
5743
5887
|
dirname: dirname10,
|
|
5744
5888
|
logical,
|
|
5745
5889
|
isDir: statSync10(local).isDirectory(),
|
|
5746
|
-
count:
|
|
5890
|
+
count: modified.length,
|
|
5747
5891
|
projectBackupRoot
|
|
5748
5892
|
})
|
|
5749
5893
|
);
|
|
5750
|
-
for (const f of
|
|
5894
|
+
for (const f of modified) warn(` ${f}`);
|
|
5895
|
+
}
|
|
5896
|
+
if (prePostHeads !== void 0) {
|
|
5897
|
+
for (const { logical, relToLocal } of keptDeletePreview(v, prePostHeads, repo)) {
|
|
5898
|
+
warn(keptDeleteWarnLine(logical, relToLocal));
|
|
5899
|
+
}
|
|
5751
5900
|
}
|
|
5752
5901
|
}
|
|
5753
5902
|
|
|
5754
5903
|
// src/skills-sync.ts
|
|
5755
5904
|
init_config();
|
|
5756
|
-
import { existsSync as existsSync37, lstatSync as
|
|
5905
|
+
import { existsSync as existsSync37, lstatSync as lstatSync11, mkdirSync as mkdirSync9, readdirSync as readdirSync15, rmSync as rmSync13 } from "node:fs";
|
|
5757
5906
|
import { join as join44 } from "node:path";
|
|
5758
5907
|
init_utils_fs();
|
|
5759
5908
|
function isGsdOwned(name) {
|
|
@@ -5777,7 +5926,7 @@ function syncSkillsPull(ts) {
|
|
|
5777
5926
|
const sharedSkills = join44(repoHome(), "shared", "skills");
|
|
5778
5927
|
if (!existsSync37(sharedSkills)) return;
|
|
5779
5928
|
const localSkills = join44(claudeHome(), "skills");
|
|
5780
|
-
const dstStat =
|
|
5929
|
+
const dstStat = lstatSync11(localSkills, { throwIfNoEntry: false });
|
|
5781
5930
|
if (dstStat?.isSymbolicLink() === true) {
|
|
5782
5931
|
backupBeforeWrite(localSkills, ts);
|
|
5783
5932
|
rmSync13(localSkills, { recursive: true, force: true });
|
|
@@ -5787,7 +5936,7 @@ function syncSkillsPull(ts) {
|
|
|
5787
5936
|
}
|
|
5788
5937
|
function syncSkillsPush() {
|
|
5789
5938
|
const localSkills = join44(claudeHome(), "skills");
|
|
5790
|
-
const stat =
|
|
5939
|
+
const stat = lstatSync11(localSkills, { throwIfNoEntry: false });
|
|
5791
5940
|
if (stat === void 0) return;
|
|
5792
5941
|
if (stat.isSymbolicLink()) return;
|
|
5793
5942
|
const sharedSkills = join44(repoHome(), "shared", "skills");
|
|
@@ -6153,10 +6302,14 @@ function computePreview(ts, map, verb = "pull") {
|
|
|
6153
6302
|
dryRun: true,
|
|
6154
6303
|
onPreview: (e) => addItem(sessions, formatSessionRow(e))
|
|
6155
6304
|
});
|
|
6305
|
+
const localOnly = scanLocalOnly();
|
|
6306
|
+
if (localOnly > 0) {
|
|
6307
|
+
addItem(sessions, `${localOnly} local-only present, not in repo (push to reconcile)`);
|
|
6308
|
+
}
|
|
6156
6309
|
const summary = section("Summary");
|
|
6157
|
-
addItem(summary, summaryRow(verb, remapResult.unmapped));
|
|
6310
|
+
addItem(summary, summaryRow(verb, remapResult.unmapped, 0, 0, localOnly));
|
|
6158
6311
|
renderTree([links, settingsSection, sessions, summary]);
|
|
6159
|
-
return { unmapped: remapResult.unmapped, collisions: 0 };
|
|
6312
|
+
return { unmapped: remapResult.unmapped, collisions: 0, localOnly };
|
|
6160
6313
|
}
|
|
6161
6314
|
|
|
6162
6315
|
// src/commands.pull.ts
|
|
@@ -6311,14 +6464,21 @@ function applyWetPull(ts, map, prePostHeads) {
|
|
|
6311
6464
|
syncSkillsPull(ts);
|
|
6312
6465
|
const remapResult = withSpinner("Syncing sessions", () => remapPull(ts));
|
|
6313
6466
|
const extrasResult = remapExtrasPull(ts, { prePostHeads });
|
|
6467
|
+
const localOnly = scanLocalOnly();
|
|
6314
6468
|
const summary = section("Summary");
|
|
6315
6469
|
addItem(
|
|
6316
6470
|
summary,
|
|
6317
|
-
summaryRow(
|
|
6471
|
+
summaryRow(
|
|
6472
|
+
"pull",
|
|
6473
|
+
remapResult.unmapped + extrasResult.unmapped,
|
|
6474
|
+
0,
|
|
6475
|
+
extrasResult.skipped,
|
|
6476
|
+
localOnly
|
|
6477
|
+
)
|
|
6318
6478
|
);
|
|
6319
6479
|
renderTree([
|
|
6320
6480
|
buildSettingsSection(label),
|
|
6321
|
-
buildSessionsSection(remapResult.pulled, remapResult.unmapped),
|
|
6481
|
+
buildSessionsSection(remapResult.pulled, remapResult.unmapped, localOnly),
|
|
6322
6482
|
buildExtrasSection(extrasResult.pulled, extrasResult.skipped),
|
|
6323
6483
|
summary
|
|
6324
6484
|
]);
|
|
@@ -6371,7 +6531,7 @@ function cmdPull(opts = {}) {
|
|
|
6371
6531
|
});
|
|
6372
6532
|
const mapPath = join46(repo, "path-map.json");
|
|
6373
6533
|
const map = existsSync39(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
6374
|
-
divergenceCheckExtras(ts);
|
|
6534
|
+
divergenceCheckExtras(ts, dryRun ? prePostHeads : void 0);
|
|
6375
6535
|
if (dryRun) {
|
|
6376
6536
|
computePreview(ts, map, "pull");
|
|
6377
6537
|
log("dry-run complete; no mutation");
|
|
@@ -6586,12 +6746,12 @@ function reportSettingsAheadDrift(repo) {
|
|
|
6586
6746
|
// src/commands.push.guards.ts
|
|
6587
6747
|
init_push_checks();
|
|
6588
6748
|
init_utils();
|
|
6589
|
-
import { join as join49, relative as
|
|
6749
|
+
import { join as join49, relative as relative7 } from "node:path";
|
|
6590
6750
|
function guardGitlinks(repo) {
|
|
6591
6751
|
const gitlinks = findGitlinks(join49(repo, "shared"));
|
|
6592
6752
|
if (gitlinks.length === 0) return;
|
|
6593
6753
|
for (const p of gitlinks) {
|
|
6594
|
-
const rel =
|
|
6754
|
+
const rel = relative7(repo, p);
|
|
6595
6755
|
fail(`gitlink: ${rel} would push as submodule (run: rm -rf ${rel} or remove the nested repo)`);
|
|
6596
6756
|
}
|
|
6597
6757
|
const noun = gitlinks.length === 1 ? "entry" : "entries";
|
|
@@ -6700,7 +6860,7 @@ init_config_sharedDirs_guard();
|
|
|
6700
6860
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
6701
6861
|
import { copyFileSync, existsSync as existsSync42, mkdirSync as mkdirSync11, readdirSync as readdirSync16, rmSync as rmSync14 } from "node:fs";
|
|
6702
6862
|
import { homedir as homedir5 } from "node:os";
|
|
6703
|
-
import { join as join50, relative as
|
|
6863
|
+
import { join as join50, relative as relative8, sep as sep8 } from "node:path";
|
|
6704
6864
|
init_push_leak_verdict();
|
|
6705
6865
|
init_push_gitleaks();
|
|
6706
6866
|
init_utils_fs();
|
|
@@ -6712,7 +6872,7 @@ function stageSessionDir(localDir, dstDir, changed) {
|
|
|
6712
6872
|
const matching = [...changed].filter((p) => p.startsWith(prefix));
|
|
6713
6873
|
if (matching.length === 0) return false;
|
|
6714
6874
|
for (const src of matching) {
|
|
6715
|
-
copyFileAtomic(src, join50(dstDir,
|
|
6875
|
+
copyFileAtomic(src, join50(dstDir, relative8(localDir, src)));
|
|
6716
6876
|
}
|
|
6717
6877
|
return true;
|
|
6718
6878
|
}
|
|
@@ -6951,6 +7111,7 @@ function cmdDiff() {
|
|
|
6951
7111
|
const ts = freshBackupTs(backupBase());
|
|
6952
7112
|
const mapPath = join52(repo, "path-map.json");
|
|
6953
7113
|
const map = existsSync44(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
7114
|
+
divergenceCheckExtras(ts);
|
|
6954
7115
|
computePreview(ts, map, "diff");
|
|
6955
7116
|
} catch (err) {
|
|
6956
7117
|
if (err instanceof NomadFatal) {
|
|
@@ -7496,7 +7657,7 @@ function parsePushArgs(argv) {
|
|
|
7496
7657
|
// package.json
|
|
7497
7658
|
var package_default = {
|
|
7498
7659
|
name: "claude-nomad",
|
|
7499
|
-
version: "0.56.
|
|
7660
|
+
version: "0.56.2",
|
|
7500
7661
|
type: "module",
|
|
7501
7662
|
description: "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
|
|
7502
7663
|
keywords: [
|
|
@@ -7721,7 +7882,7 @@ var DEFAULT_HELP = [
|
|
|
7721
7882
|
init_config();
|
|
7722
7883
|
init_utils();
|
|
7723
7884
|
init_utils_json();
|
|
7724
|
-
import { existsSync as existsSync48, readFileSync as
|
|
7885
|
+
import { existsSync as existsSync48, readFileSync as readFileSync17, readdirSync as readdirSync18 } from "node:fs";
|
|
7725
7886
|
import { join as join56 } from "node:path";
|
|
7726
7887
|
function resumeCmd(sessionId) {
|
|
7727
7888
|
if (!/^[A-Za-z0-9_-]+$/.test(sessionId) || sessionId.length > 128) {
|
|
@@ -7773,7 +7934,7 @@ function findTranscriptPath(projectsRoot, sessionId) {
|
|
|
7773
7934
|
return null;
|
|
7774
7935
|
}
|
|
7775
7936
|
function extractRecordedCwd(jsonlPath) {
|
|
7776
|
-
for (const line of
|
|
7937
|
+
for (const line of readFileSync17(jsonlPath, "utf8").split("\n")) {
|
|
7777
7938
|
if (!line.trim()) continue;
|
|
7778
7939
|
try {
|
|
7779
7940
|
const obj = JSON.parse(line);
|