claude-nomad 0.56.0 → 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 +28 -0
- package/dist/nomad.mjs +589 -410
- 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"] });
|
|
@@ -1041,22 +1048,38 @@ function otherFindingHint(f) {
|
|
|
1041
1048
|
}
|
|
1042
1049
|
return " Review with: git diff --cached, then unstage manually.";
|
|
1043
1050
|
}
|
|
1044
|
-
function
|
|
1045
|
-
if (bySession.size === 0) return LEGACY_FATAL;
|
|
1051
|
+
function renderOtherFindings(other) {
|
|
1046
1052
|
const lines = [];
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
"nomad drop-session also clears each session's sibling subagent transcript directory."
|
|
1050
|
-
);
|
|
1051
|
-
for (const [sid, counts] of bySession) {
|
|
1052
|
-
const summary = [...counts.entries()].map(([rule, n]) => `${rule} (${n})`).join(", ");
|
|
1053
|
-
lines.push("", `Session ${sid}:`, ` ${summary}`, ` Recover with: nomad drop-session ${sid}`);
|
|
1053
|
+
for (const f of other) {
|
|
1054
|
+
lines.push(formatOtherFinding(f), otherFindingHint(f));
|
|
1054
1055
|
}
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1056
|
+
return lines;
|
|
1057
|
+
}
|
|
1058
|
+
function buildSessionAwareFatal(bySession, other) {
|
|
1059
|
+
if (bySession.size === 0 && other.length === 0) return LEGACY_FATAL;
|
|
1060
|
+
const lines = [];
|
|
1061
|
+
if (bySession.size > 0) {
|
|
1062
|
+
lines.push(
|
|
1063
|
+
`gitleaks detected secrets in ${bySession.size} session transcript(s).`,
|
|
1064
|
+
"nomad drop-session also clears each session's sibling subagent transcript directory."
|
|
1065
|
+
);
|
|
1066
|
+
for (const [sid, counts] of bySession) {
|
|
1067
|
+
const summary = [...counts.entries()].map(([rule, n]) => `${rule} (${n})`).join(", ");
|
|
1068
|
+
lines.push(
|
|
1069
|
+
"",
|
|
1070
|
+
`Session ${sid}:`,
|
|
1071
|
+
` ${summary}`,
|
|
1072
|
+
` Recover with: nomad drop-session ${sid}`
|
|
1073
|
+
);
|
|
1074
|
+
}
|
|
1075
|
+
if (other.length > 0) {
|
|
1076
|
+
lines.push("", "Also found:", ...renderOtherFindings(other));
|
|
1059
1077
|
}
|
|
1078
|
+
} else {
|
|
1079
|
+
lines.push(
|
|
1080
|
+
`gitleaks detected secrets in ${other.length} location(s):`,
|
|
1081
|
+
...renderOtherFindings(other)
|
|
1082
|
+
);
|
|
1060
1083
|
}
|
|
1061
1084
|
lines.push("", "After recovery, re-run nomad push.");
|
|
1062
1085
|
return lines.join("\n");
|
|
@@ -1086,9 +1109,11 @@ __export(push_leak_verdict_exports, {
|
|
|
1086
1109
|
verdictScanError: () => verdictScanError
|
|
1087
1110
|
});
|
|
1088
1111
|
function leakVerdictRow(findings) {
|
|
1089
|
-
const { bySession } = partitionFindings(findings);
|
|
1090
|
-
|
|
1091
|
-
|
|
1112
|
+
const { bySession, other } = partitionFindings(findings);
|
|
1113
|
+
if (bySession.size > 0) {
|
|
1114
|
+
return failRow(`gitleaks detected secrets in ${bySession.size} session transcript(s)`);
|
|
1115
|
+
}
|
|
1116
|
+
return failRow(`gitleaks detected secrets in ${other.length} location(s)`);
|
|
1092
1117
|
}
|
|
1093
1118
|
function leakFound(findings) {
|
|
1094
1119
|
const { bySession, other } = partitionFindings(findings);
|
|
@@ -2092,8 +2117,8 @@ function cmdEject(opts = {}, roots = defaultEjectRoots()) {
|
|
|
2092
2117
|
}
|
|
2093
2118
|
|
|
2094
2119
|
// src/commands.doctor.ts
|
|
2095
|
-
import { existsSync as
|
|
2096
|
-
import { join as
|
|
2120
|
+
import { existsSync as existsSync32 } from "node:fs";
|
|
2121
|
+
import { join as join38 } from "node:path";
|
|
2097
2122
|
|
|
2098
2123
|
// src/commands.doctor.checks.repo.ts
|
|
2099
2124
|
init_color();
|
|
@@ -2533,28 +2558,42 @@ init_config();
|
|
|
2533
2558
|
// src/extras-sync.diff.ts
|
|
2534
2559
|
init_utils();
|
|
2535
2560
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
const
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
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;
|
|
2544
2576
|
}
|
|
2545
2577
|
function parseDiffOutput(stdout) {
|
|
2546
|
-
return stdout
|
|
2578
|
+
return parseNameStatus(stdout).map(labelEntry);
|
|
2547
2579
|
}
|
|
2548
|
-
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) {
|
|
2549
2584
|
try {
|
|
2550
|
-
const stdout = execFileSync2(
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
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);
|
|
2554
2593
|
} catch (err) {
|
|
2555
2594
|
const e = err;
|
|
2556
2595
|
if (e.status === 1 && e.stdout !== void 0) {
|
|
2557
|
-
return
|
|
2596
|
+
return parse(e.stdout.toString());
|
|
2558
2597
|
}
|
|
2559
2598
|
if (e.code === "ENOENT") {
|
|
2560
2599
|
warn(`git not on PATH; divergence check skipped for ${a}`);
|
|
@@ -2564,6 +2603,12 @@ function listDivergingFiles(a, b) {
|
|
|
2564
2603
|
return [];
|
|
2565
2604
|
}
|
|
2566
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
|
+
}
|
|
2567
2612
|
|
|
2568
2613
|
// src/commands.doctor.checks.skills.ts
|
|
2569
2614
|
function stripSideIndicator(line) {
|
|
@@ -2573,15 +2618,15 @@ function stripSideIndicator(line) {
|
|
|
2573
2618
|
}
|
|
2574
2619
|
function isGsdDiffLine(line, localBase, sharedBase) {
|
|
2575
2620
|
const bare = stripSideIndicator(line);
|
|
2576
|
-
let
|
|
2621
|
+
let relative9;
|
|
2577
2622
|
if (bare.startsWith(localBase + "/")) {
|
|
2578
|
-
|
|
2623
|
+
relative9 = bare.slice(localBase.length + 1);
|
|
2579
2624
|
} else if (bare.startsWith(sharedBase + "/")) {
|
|
2580
|
-
|
|
2625
|
+
relative9 = bare.slice(sharedBase.length + 1);
|
|
2581
2626
|
} else {
|
|
2582
|
-
|
|
2627
|
+
relative9 = bare;
|
|
2583
2628
|
}
|
|
2584
|
-
return
|
|
2629
|
+
return relative9.split("/")[0].startsWith(GSD_PREFIX);
|
|
2585
2630
|
}
|
|
2586
2631
|
function reportSkillsDivergence(section2) {
|
|
2587
2632
|
const sharedSkills = join14(repoHome(), "shared", "skills");
|
|
@@ -2614,7 +2659,7 @@ init_color();
|
|
|
2614
2659
|
init_config();
|
|
2615
2660
|
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
2616
2661
|
import { existsSync as existsSync15 } from "node:fs";
|
|
2617
|
-
import { join as join18, relative as
|
|
2662
|
+
import { join as join18, relative as relative3 } from "node:path";
|
|
2618
2663
|
init_commands_pull_wedge();
|
|
2619
2664
|
init_push_checks();
|
|
2620
2665
|
init_utils();
|
|
@@ -2641,7 +2686,7 @@ function reportGitlinks(section2) {
|
|
|
2641
2686
|
if (existsSync15(sharedDir)) {
|
|
2642
2687
|
const gitlinks = findGitlinks(sharedDir);
|
|
2643
2688
|
for (const p of gitlinks) {
|
|
2644
|
-
const rel =
|
|
2689
|
+
const rel = relative3(repo, p);
|
|
2645
2690
|
addItem(
|
|
2646
2691
|
section2,
|
|
2647
2692
|
`${red(failGlyph)} gitlink: ${blue(rel)} would push as submodule (run: rm -rf ${rel} or remove the nested repo)`
|
|
@@ -2918,9 +2963,9 @@ function reportCheckSchema(section2) {
|
|
|
2918
2963
|
init_color();
|
|
2919
2964
|
import { randomBytes } from "node:crypto";
|
|
2920
2965
|
import { execFileSync as execFileSync9 } from "node:child_process";
|
|
2921
|
-
import { existsSync as
|
|
2966
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync6, readdirSync as readdirSync10, rmSync as rmSync9 } from "node:fs";
|
|
2922
2967
|
import { homedir as homedir4 } from "node:os";
|
|
2923
|
-
import { join as
|
|
2968
|
+
import { join as join26 } from "node:path";
|
|
2924
2969
|
|
|
2925
2970
|
// src/commands.doctor.check-shared.scan.ts
|
|
2926
2971
|
init_color();
|
|
@@ -3017,8 +3062,22 @@ init_config();
|
|
|
3017
3062
|
|
|
3018
3063
|
// src/remap.ts
|
|
3019
3064
|
init_config_sharedDirs_guard();
|
|
3020
|
-
import {
|
|
3021
|
-
|
|
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";
|
|
3022
3081
|
|
|
3023
3082
|
// src/extras-sync.guards.ts
|
|
3024
3083
|
init_utils();
|
|
@@ -3037,6 +3096,180 @@ function assertSafeLocalRoot(localRoot, logical) {
|
|
|
3037
3096
|
}
|
|
3038
3097
|
}
|
|
3039
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
|
+
|
|
3040
3273
|
// src/remap.ts
|
|
3041
3274
|
init_config();
|
|
3042
3275
|
|
|
@@ -3045,8 +3278,8 @@ init_config();
|
|
|
3045
3278
|
init_push_gitleaks_config();
|
|
3046
3279
|
init_utils_fs();
|
|
3047
3280
|
import { createHash } from "node:crypto";
|
|
3048
|
-
import { existsSync as
|
|
3049
|
-
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";
|
|
3050
3283
|
function isChanged(prev, cur, hash) {
|
|
3051
3284
|
if (prev === void 0) return true;
|
|
3052
3285
|
if (prev.size !== cur.size) return true;
|
|
@@ -3083,11 +3316,11 @@ function shouldFullRescan(old, scannerVersion, configHash, forceFlag) {
|
|
|
3083
3316
|
return false;
|
|
3084
3317
|
}
|
|
3085
3318
|
function hashFile(absPath) {
|
|
3086
|
-
return createHash("sha256").update(
|
|
3319
|
+
return createHash("sha256").update(readFileSync7(absPath)).digest("hex");
|
|
3087
3320
|
}
|
|
3088
3321
|
function enumerateDir(dir, results) {
|
|
3089
|
-
for (const entry of
|
|
3090
|
-
const fullPath =
|
|
3322
|
+
for (const entry of readdirSync8(dir)) {
|
|
3323
|
+
const fullPath = join24(dir, entry);
|
|
3091
3324
|
const st = statSync4(fullPath);
|
|
3092
3325
|
if (st.isDirectory()) {
|
|
3093
3326
|
enumerateDir(fullPath, results);
|
|
@@ -3098,8 +3331,8 @@ function enumerateDir(dir, results) {
|
|
|
3098
3331
|
}
|
|
3099
3332
|
function enumerateSourceFiles(projectDir) {
|
|
3100
3333
|
const results = [];
|
|
3101
|
-
for (const entry of
|
|
3102
|
-
const fullPath =
|
|
3334
|
+
for (const entry of readdirSync8(projectDir)) {
|
|
3335
|
+
const fullPath = join24(projectDir, entry);
|
|
3103
3336
|
const st = statSync4(fullPath);
|
|
3104
3337
|
if (st.isDirectory()) {
|
|
3105
3338
|
enumerateDir(fullPath, results);
|
|
@@ -3111,15 +3344,15 @@ function enumerateSourceFiles(projectDir) {
|
|
|
3111
3344
|
}
|
|
3112
3345
|
function fileIdentity(p) {
|
|
3113
3346
|
if (p === null) return "none::absent";
|
|
3114
|
-
if (!
|
|
3115
|
-
return `${p}::${createHash("sha256").update(
|
|
3347
|
+
if (!existsSync19(p)) return `${p}::absent`;
|
|
3348
|
+
return `${p}::${createHash("sha256").update(readFileSync7(p)).digest("hex")}`;
|
|
3116
3349
|
}
|
|
3117
3350
|
function computeConfigHash() {
|
|
3118
3351
|
const repo = repoHome();
|
|
3119
3352
|
const parts = [
|
|
3120
3353
|
fileIdentity(resolveTomlPath(repo)),
|
|
3121
|
-
fileIdentity(
|
|
3122
|
-
fileIdentity(
|
|
3354
|
+
fileIdentity(join24(repo, ".gitleaks.overlay.toml")),
|
|
3355
|
+
fileIdentity(join24(repo, ".gitleaksignore"))
|
|
3123
3356
|
];
|
|
3124
3357
|
return createHash("sha256").update(parts.join("\n")).digest("hex");
|
|
3125
3358
|
}
|
|
@@ -3130,7 +3363,7 @@ function isManifestShape(raw) {
|
|
|
3130
3363
|
}
|
|
3131
3364
|
function readManifest(path) {
|
|
3132
3365
|
try {
|
|
3133
|
-
const raw =
|
|
3366
|
+
const raw = readFileSync7(path, "utf8");
|
|
3134
3367
|
const parsed = JSON.parse(raw);
|
|
3135
3368
|
if (!isManifestShape(parsed)) return null;
|
|
3136
3369
|
return parsed;
|
|
@@ -3153,18 +3386,19 @@ init_utils_json();
|
|
|
3153
3386
|
var TMP_SUFFIX = ".nomad-tmp";
|
|
3154
3387
|
function atomicMirror(src, dst, options) {
|
|
3155
3388
|
const tmp = `${dst}${TMP_SUFFIX}`;
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3389
|
+
rmSync8(tmp, { recursive: true, force: true });
|
|
3390
|
+
cpSync5(src, tmp, options);
|
|
3391
|
+
rmSync8(dst, { recursive: true, force: true });
|
|
3159
3392
|
renameSync3(tmp, dst);
|
|
3160
3393
|
}
|
|
3161
|
-
function
|
|
3162
|
-
|
|
3394
|
+
function overlaySessionDir(src, dst) {
|
|
3395
|
+
stripCollidingDstSymlinks(src, dst, () => false);
|
|
3396
|
+
cpSyncGuarded(src, dst, void 0, "overlaySessionDir");
|
|
3163
3397
|
}
|
|
3164
3398
|
function copyFileAtomic(src, dst) {
|
|
3165
3399
|
mkdirSync5(dirname4(dst), { recursive: true });
|
|
3166
3400
|
const tmp = `${dst}.tmp.${process.pid}`;
|
|
3167
|
-
|
|
3401
|
+
cpSync5(src, tmp, { force: true, preserveTimestamps: true });
|
|
3168
3402
|
renameSync3(tmp, dst);
|
|
3169
3403
|
}
|
|
3170
3404
|
function hasDeltaForDir(sel, localDir) {
|
|
@@ -3180,12 +3414,12 @@ function applySelective(sel, localDir, repoDst) {
|
|
|
3180
3414
|
const prefix = `${localDir}${sep3}`;
|
|
3181
3415
|
for (const src of sel.changed) {
|
|
3182
3416
|
if (!src.startsWith(prefix)) continue;
|
|
3183
|
-
copyFileAtomic(src,
|
|
3417
|
+
copyFileAtomic(src, join25(repoDst, relative5(localDir, src)));
|
|
3184
3418
|
}
|
|
3185
3419
|
for (const src of sel.deleted) {
|
|
3186
3420
|
if (!src.startsWith(prefix)) continue;
|
|
3187
|
-
const dst =
|
|
3188
|
-
if (
|
|
3421
|
+
const dst = join25(repoDst, relative5(localDir, src));
|
|
3422
|
+
if (existsSync20(dst)) rmSync8(dst);
|
|
3189
3423
|
}
|
|
3190
3424
|
}
|
|
3191
3425
|
function copyDirJsonlOnly(src, dst) {
|
|
@@ -3193,7 +3427,7 @@ function copyDirJsonlOnly(src, dst) {
|
|
|
3193
3427
|
recursive: true,
|
|
3194
3428
|
force: true,
|
|
3195
3429
|
filter: (srcPath) => {
|
|
3196
|
-
const rel =
|
|
3430
|
+
const rel = relative5(src, srcPath);
|
|
3197
3431
|
if (rel === "") return true;
|
|
3198
3432
|
if (rel.split(sep3).length > 1) return true;
|
|
3199
3433
|
if (statSync5(srcPath).isDirectory()) return true;
|
|
@@ -3214,15 +3448,15 @@ function remapPull(ts, opts = {}) {
|
|
|
3214
3448
|
const wouldPull = [];
|
|
3215
3449
|
const repo = repoHome();
|
|
3216
3450
|
const claude = claudeHome();
|
|
3217
|
-
const mapPath =
|
|
3218
|
-
const repoProjects =
|
|
3219
|
-
if (!
|
|
3451
|
+
const mapPath = join25(repo, "path-map.json");
|
|
3452
|
+
const repoProjects = join25(repo, "shared", "projects");
|
|
3453
|
+
if (!existsSync20(mapPath) || !existsSync20(repoProjects)) {
|
|
3220
3454
|
const text = "no path-map or repo projects dir; skipping session remap";
|
|
3221
3455
|
emitPreview(opts.onPreview, { kind: "note", text }, text);
|
|
3222
3456
|
return { unmapped: 0, pulled, wouldPull };
|
|
3223
3457
|
}
|
|
3224
3458
|
const map = readPathMap(mapPath);
|
|
3225
|
-
const localProjects =
|
|
3459
|
+
const localProjects = join25(claude, "projects");
|
|
3226
3460
|
if (!dryRun) mkdirSync5(localProjects, { recursive: true });
|
|
3227
3461
|
for (const [logical, hosts] of Object.entries(map.projects)) {
|
|
3228
3462
|
assertSafeLogical(logical);
|
|
@@ -3232,9 +3466,9 @@ function remapPull(ts, opts = {}) {
|
|
|
3232
3466
|
continue;
|
|
3233
3467
|
}
|
|
3234
3468
|
assertSafeLocalRoot(localPath, logical);
|
|
3235
|
-
const src =
|
|
3236
|
-
if (!
|
|
3237
|
-
const dst =
|
|
3469
|
+
const src = join25(repoProjects, logical);
|
|
3470
|
+
if (!existsSync20(src)) continue;
|
|
3471
|
+
const dst = join25(localProjects, encodePath(localPath));
|
|
3238
3472
|
if (dryRun) {
|
|
3239
3473
|
wouldPull.push(logical);
|
|
3240
3474
|
emitPreview(
|
|
@@ -3245,11 +3479,44 @@ function remapPull(ts, opts = {}) {
|
|
|
3245
3479
|
continue;
|
|
3246
3480
|
}
|
|
3247
3481
|
backupBeforeWrite(dst, ts);
|
|
3248
|
-
|
|
3482
|
+
overlaySessionDir(src, dst);
|
|
3249
3483
|
pulled.push(logical);
|
|
3250
3484
|
}
|
|
3251
3485
|
return { unmapped, pulled, wouldPull };
|
|
3252
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
|
+
}
|
|
3253
3520
|
function buildReverseMap(map) {
|
|
3254
3521
|
const reverse = /* @__PURE__ */ new Map();
|
|
3255
3522
|
const encodedPaths = /* @__PURE__ */ new Map();
|
|
@@ -3281,26 +3548,26 @@ function remapPush(ts, opts = {}) {
|
|
|
3281
3548
|
const wouldPush = [];
|
|
3282
3549
|
const repo = repoHome();
|
|
3283
3550
|
const claude = claudeHome();
|
|
3284
|
-
const mapPath =
|
|
3285
|
-
if (!
|
|
3551
|
+
const mapPath = join25(repo, "path-map.json");
|
|
3552
|
+
if (!existsSync20(mapPath)) {
|
|
3286
3553
|
log("no path-map.json; skipping session export");
|
|
3287
3554
|
return { unmapped: 0, collisions: 0, pushed, wouldPush };
|
|
3288
3555
|
}
|
|
3289
3556
|
const map = readPathMap(mapPath);
|
|
3290
|
-
const localProjects =
|
|
3291
|
-
const repoProjects =
|
|
3557
|
+
const localProjects = join25(claude, "projects");
|
|
3558
|
+
const repoProjects = join25(repo, "shared", "projects");
|
|
3292
3559
|
const reverse = buildReverseMap(map);
|
|
3293
|
-
if (!
|
|
3560
|
+
if (!existsSync20(localProjects)) return { unmapped, collisions: 0, pushed, wouldPush };
|
|
3294
3561
|
if (!dryRun) mkdirSync5(repoProjects, { recursive: true });
|
|
3295
|
-
for (const dir of
|
|
3562
|
+
for (const dir of readdirSync9(localProjects)) {
|
|
3296
3563
|
if (dir.endsWith(TMP_SUFFIX)) continue;
|
|
3297
3564
|
const logical = reverse.get(dir);
|
|
3298
3565
|
if (!logical) {
|
|
3299
3566
|
unmapped++;
|
|
3300
3567
|
continue;
|
|
3301
3568
|
}
|
|
3302
|
-
const localDir =
|
|
3303
|
-
const repoDst =
|
|
3569
|
+
const localDir = join25(localProjects, dir);
|
|
3570
|
+
const repoDst = join25(repoProjects, logical);
|
|
3304
3571
|
if (skipForSelection(opts.selection, localDir)) continue;
|
|
3305
3572
|
if (dryRun) {
|
|
3306
3573
|
wouldPush.push(logical);
|
|
@@ -3324,8 +3591,8 @@ function buildScanTree(tmpRoot) {
|
|
|
3324
3591
|
const logicalToEncoded = /* @__PURE__ */ new Map();
|
|
3325
3592
|
let staged = 0;
|
|
3326
3593
|
const repo = repoHome();
|
|
3327
|
-
const mapPath =
|
|
3328
|
-
if (!
|
|
3594
|
+
const mapPath = join26(repo, "path-map.json");
|
|
3595
|
+
if (!existsSync21(mapPath)) return { logicalToEncoded, staged, malformed: false };
|
|
3329
3596
|
let map;
|
|
3330
3597
|
try {
|
|
3331
3598
|
map = readJson(mapPath);
|
|
@@ -3342,12 +3609,12 @@ function buildScanTree(tmpRoot) {
|
|
|
3342
3609
|
if (!p || p === "TBD") continue;
|
|
3343
3610
|
reverse.set(encodePath(p), logical);
|
|
3344
3611
|
}
|
|
3345
|
-
const localProjects =
|
|
3346
|
-
if (!
|
|
3347
|
-
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)) {
|
|
3348
3615
|
const logical = reverse.get(dir);
|
|
3349
3616
|
if (!logical) continue;
|
|
3350
|
-
copyDirJsonlOnly(
|
|
3617
|
+
copyDirJsonlOnly(join26(localProjects, dir), join26(tmpRoot, "shared", "projects", logical));
|
|
3351
3618
|
logicalToEncoded.set(logical, dir);
|
|
3352
3619
|
staged++;
|
|
3353
3620
|
}
|
|
@@ -3378,11 +3645,11 @@ function ensureGitleaksReady(section2, gitleaksReady) {
|
|
|
3378
3645
|
}
|
|
3379
3646
|
function reportCheckShared(section2, gitleaksReady) {
|
|
3380
3647
|
if (!ensureGitleaksReady(section2, gitleaksReady)) return;
|
|
3381
|
-
const cacheDir =
|
|
3648
|
+
const cacheDir = join26(homedir4(), ".cache", "claude-nomad");
|
|
3382
3649
|
mkdirSync6(cacheDir, { recursive: true });
|
|
3383
3650
|
const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes(4).toString("hex")}`;
|
|
3384
|
-
const reportPath =
|
|
3385
|
-
const tmpRoot =
|
|
3651
|
+
const reportPath = join26(cacheDir, `check-shared-${stamp}.json`);
|
|
3652
|
+
const tmpRoot = join26(cacheDir, `check-shared-tree-${stamp}`);
|
|
3386
3653
|
try {
|
|
3387
3654
|
const { logicalToEncoded, staged, malformed } = buildScanTree(tmpRoot);
|
|
3388
3655
|
if (malformed) {
|
|
@@ -3396,19 +3663,19 @@ function reportCheckShared(section2, gitleaksReady) {
|
|
|
3396
3663
|
}
|
|
3397
3664
|
scanAndReport(section2, tmpRoot, staged, logicalToEncoded);
|
|
3398
3665
|
} finally {
|
|
3399
|
-
|
|
3400
|
-
|
|
3666
|
+
rmSync9(reportPath, { force: true });
|
|
3667
|
+
rmSync9(tmpRoot, { recursive: true, force: true });
|
|
3401
3668
|
}
|
|
3402
3669
|
}
|
|
3403
3670
|
|
|
3404
3671
|
// src/commands.doctor.checks.hooks.scope.ts
|
|
3405
3672
|
init_color();
|
|
3406
|
-
import { existsSync as
|
|
3407
|
-
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";
|
|
3408
3675
|
init_config();
|
|
3409
3676
|
function typeFromPackageJson(pkgPath) {
|
|
3410
3677
|
try {
|
|
3411
|
-
const parsed = JSON.parse(
|
|
3678
|
+
const parsed = JSON.parse(readFileSync8(pkgPath, "utf8"));
|
|
3412
3679
|
return parsed.type === "module" ? "esm" : "cjs";
|
|
3413
3680
|
} catch {
|
|
3414
3681
|
return "cjs";
|
|
@@ -3426,8 +3693,8 @@ function effectiveType(hookPath) {
|
|
|
3426
3693
|
}
|
|
3427
3694
|
let dir = dirname5(real);
|
|
3428
3695
|
for (; ; ) {
|
|
3429
|
-
const pkg =
|
|
3430
|
-
if (
|
|
3696
|
+
const pkg = join27(dir, "package.json");
|
|
3697
|
+
if (existsSync22(pkg)) return typeFromPackageJson(pkg);
|
|
3431
3698
|
const parent = dirname5(dir);
|
|
3432
3699
|
if (parent === dir) return "cjs";
|
|
3433
3700
|
dir = parent;
|
|
@@ -3456,28 +3723,28 @@ function remedy(family) {
|
|
|
3456
3723
|
}
|
|
3457
3724
|
function safeReaddir2(dir) {
|
|
3458
3725
|
try {
|
|
3459
|
-
return
|
|
3726
|
+
return readdirSync11(dir);
|
|
3460
3727
|
} catch {
|
|
3461
3728
|
return [];
|
|
3462
3729
|
}
|
|
3463
3730
|
}
|
|
3464
3731
|
function safeRead(path) {
|
|
3465
3732
|
try {
|
|
3466
|
-
return
|
|
3733
|
+
return readFileSync8(path, "utf8");
|
|
3467
3734
|
} catch {
|
|
3468
3735
|
return null;
|
|
3469
3736
|
}
|
|
3470
3737
|
}
|
|
3471
3738
|
function reportHookScopeCheck(section2) {
|
|
3472
|
-
const hooksDir =
|
|
3473
|
-
if (!
|
|
3739
|
+
const hooksDir = join27(claudeHome(), "hooks");
|
|
3740
|
+
if (!existsSync22(hooksDir)) {
|
|
3474
3741
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/hooks; skipping module-scope check`);
|
|
3475
3742
|
return;
|
|
3476
3743
|
}
|
|
3477
3744
|
let anyWarn = false;
|
|
3478
3745
|
for (const name of safeReaddir2(hooksDir)) {
|
|
3479
3746
|
if (extname(name) !== ".js") continue;
|
|
3480
|
-
const abs =
|
|
3747
|
+
const abs = join27(hooksDir, name);
|
|
3481
3748
|
const eff = effectiveType(abs);
|
|
3482
3749
|
if (eff === null) continue;
|
|
3483
3750
|
const src = safeRead(abs);
|
|
@@ -3497,8 +3764,8 @@ function reportHookScopeCheck(section2) {
|
|
|
3497
3764
|
|
|
3498
3765
|
// src/commands.doctor.checks.hooks.ts
|
|
3499
3766
|
init_color();
|
|
3500
|
-
import { existsSync as
|
|
3501
|
-
import { join as
|
|
3767
|
+
import { existsSync as existsSync23 } from "node:fs";
|
|
3768
|
+
import { join as join28 } from "node:path";
|
|
3502
3769
|
init_config();
|
|
3503
3770
|
function expandHome(token) {
|
|
3504
3771
|
const h2 = home();
|
|
@@ -3541,7 +3808,7 @@ function checkEventGroups(section2, event, groups) {
|
|
|
3541
3808
|
for (const group of groups) {
|
|
3542
3809
|
for (const cmd of commandsFromGroup(group)) {
|
|
3543
3810
|
for (const resolved of claudePathsIn(cmd)) {
|
|
3544
|
-
if (
|
|
3811
|
+
if (existsSync23(resolved)) continue;
|
|
3545
3812
|
addItem(
|
|
3546
3813
|
section2,
|
|
3547
3814
|
`${red(failGlyph)} hooks/${event}: command target missing: ${resolved} (run nomad pull)`
|
|
@@ -3554,8 +3821,8 @@ function checkEventGroups(section2, event, groups) {
|
|
|
3554
3821
|
return anyFail;
|
|
3555
3822
|
}
|
|
3556
3823
|
function reportHooksTargetCheck(section2) {
|
|
3557
|
-
const settingsPath =
|
|
3558
|
-
if (!
|
|
3824
|
+
const settingsPath = join28(claudeHome(), "settings.json");
|
|
3825
|
+
if (!existsSync23(settingsPath)) {
|
|
3559
3826
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json; skipping hook target check`);
|
|
3560
3827
|
return;
|
|
3561
3828
|
}
|
|
@@ -3578,12 +3845,12 @@ function reportHooksTargetCheck(section2) {
|
|
|
3578
3845
|
|
|
3579
3846
|
// src/commands.doctor.checks.hooks.preserve-symlinks.ts
|
|
3580
3847
|
init_color();
|
|
3581
|
-
import { existsSync as
|
|
3582
|
-
import { join as
|
|
3848
|
+
import { existsSync as existsSync25, readFileSync as readFileSync9 } from "node:fs";
|
|
3849
|
+
import { join as join30 } from "node:path";
|
|
3583
3850
|
|
|
3584
3851
|
// src/commands.doctor.checks.hooks.preserve-symlinks.probe.ts
|
|
3585
|
-
import { closeSync as closeSync3, existsSync as
|
|
3586
|
-
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";
|
|
3587
3854
|
function suppressedRanges(src) {
|
|
3588
3855
|
const ranges = [];
|
|
3589
3856
|
const re = /\/\*[\s\S]*?\*\/|\/\/[^\n]*|'[^']*'|"[^"]*"|`[^`]*`/g;
|
|
@@ -3615,12 +3882,12 @@ function topRelativeSpecifiers(src) {
|
|
|
3615
3882
|
}
|
|
3616
3883
|
function specifierIsMissing(specifier, baseDir) {
|
|
3617
3884
|
const base = resolve2(baseDir, specifier);
|
|
3618
|
-
if (
|
|
3885
|
+
if (existsSync24(base)) return false;
|
|
3619
3886
|
for (const ext of [".js", ".cjs", ".mjs"]) {
|
|
3620
|
-
if (
|
|
3887
|
+
if (existsSync24(base + ext)) return false;
|
|
3621
3888
|
}
|
|
3622
3889
|
for (const idx of ["index.js", "index.cjs", "index.mjs"]) {
|
|
3623
|
-
if (
|
|
3890
|
+
if (existsSync24(join29(base, idx))) return false;
|
|
3624
3891
|
}
|
|
3625
3892
|
return true;
|
|
3626
3893
|
}
|
|
@@ -3675,10 +3942,10 @@ function commandTokens(command) {
|
|
|
3675
3942
|
return tokens;
|
|
3676
3943
|
}
|
|
3677
3944
|
function readPathMapSafe() {
|
|
3678
|
-
const mapPath =
|
|
3679
|
-
if (!
|
|
3945
|
+
const mapPath = join30(repoHome(), "path-map.json");
|
|
3946
|
+
if (!existsSync25(mapPath)) return { projects: {} };
|
|
3680
3947
|
try {
|
|
3681
|
-
return JSON.parse(
|
|
3948
|
+
return JSON.parse(readFileSync9(mapPath, "utf8"));
|
|
3682
3949
|
} catch {
|
|
3683
3950
|
return { projects: {} };
|
|
3684
3951
|
}
|
|
@@ -3749,8 +4016,8 @@ function checkEventForPreserveSymlinks(section2, event, groups, sharedLinkNames)
|
|
|
3749
4016
|
return anyWarn;
|
|
3750
4017
|
}
|
|
3751
4018
|
function reportPreserveSymlinksCheck(section2) {
|
|
3752
|
-
const settingsPath =
|
|
3753
|
-
if (!
|
|
4019
|
+
const settingsPath = join30(claudeHome(), "settings.json");
|
|
4020
|
+
if (!existsSync25(settingsPath)) {
|
|
3754
4021
|
addItem(
|
|
3755
4022
|
section2,
|
|
3756
4023
|
`${dim(infoGlyph)} no ~/.claude/settings.json; skipping preserve-symlinks-main check`
|
|
@@ -3759,7 +4026,7 @@ function reportPreserveSymlinksCheck(section2) {
|
|
|
3759
4026
|
}
|
|
3760
4027
|
let settings;
|
|
3761
4028
|
try {
|
|
3762
|
-
settings = JSON.parse(
|
|
4029
|
+
settings = JSON.parse(readFileSync9(settingsPath, "utf8"));
|
|
3763
4030
|
} catch {
|
|
3764
4031
|
return;
|
|
3765
4032
|
}
|
|
@@ -3783,8 +4050,8 @@ function reportPreserveSymlinksCheck(section2) {
|
|
|
3783
4050
|
|
|
3784
4051
|
// src/commands.doctor.checks.settings-drift.ts
|
|
3785
4052
|
init_color();
|
|
3786
|
-
import { existsSync as
|
|
3787
|
-
import { join as
|
|
4053
|
+
import { existsSync as existsSync26, readFileSync as readFileSync10 } from "node:fs";
|
|
4054
|
+
import { join as join31 } from "node:path";
|
|
3788
4055
|
init_config();
|
|
3789
4056
|
init_utils_json();
|
|
3790
4057
|
function diffMergedSettings(merged, settings) {
|
|
@@ -3793,7 +4060,7 @@ function diffMergedSettings(merged, settings) {
|
|
|
3793
4060
|
}
|
|
3794
4061
|
function tryReadJson(filePath) {
|
|
3795
4062
|
try {
|
|
3796
|
-
const raw =
|
|
4063
|
+
const raw = readFileSync10(filePath, "utf8");
|
|
3797
4064
|
const parsed = JSON.parse(raw);
|
|
3798
4065
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
3799
4066
|
return parsed;
|
|
@@ -3802,7 +4069,7 @@ function tryReadJson(filePath) {
|
|
|
3802
4069
|
}
|
|
3803
4070
|
}
|
|
3804
4071
|
function reportHooksBaseSelfCleanNote(section2) {
|
|
3805
|
-
const basePath =
|
|
4072
|
+
const basePath = join31(repoHome(), "shared", "settings.base.json");
|
|
3806
4073
|
const base = tryReadJson(basePath);
|
|
3807
4074
|
if (base === null) return;
|
|
3808
4075
|
if (!baseHasGsdHookEntries(base)) return;
|
|
@@ -3815,14 +4082,14 @@ function reportSettingsDriftCheck(section2) {
|
|
|
3815
4082
|
const claude = claudeHome();
|
|
3816
4083
|
const repo = repoHome();
|
|
3817
4084
|
const host = HOST;
|
|
3818
|
-
const settingsPath =
|
|
3819
|
-
const basePath =
|
|
3820
|
-
const hostPath =
|
|
3821
|
-
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)) {
|
|
3822
4089
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json; skipping merge-drift check`);
|
|
3823
4090
|
return;
|
|
3824
4091
|
}
|
|
3825
|
-
if (!
|
|
4092
|
+
if (!existsSync26(basePath)) {
|
|
3826
4093
|
addItem(
|
|
3827
4094
|
section2,
|
|
3828
4095
|
`${dim(infoGlyph)} shared/settings.base.json missing; skipping merge-drift check`
|
|
@@ -3841,7 +4108,7 @@ function reportSettingsDriftCheck(section2) {
|
|
|
3841
4108
|
if (settings === null) {
|
|
3842
4109
|
return;
|
|
3843
4110
|
}
|
|
3844
|
-
const hostExists =
|
|
4111
|
+
const hostExists = existsSync26(hostPath);
|
|
3845
4112
|
const hostObj = hostExists ? tryReadJson(hostPath) : null;
|
|
3846
4113
|
if (hostExists && hostObj === null) {
|
|
3847
4114
|
addItem(
|
|
@@ -3890,12 +4157,12 @@ init_config();
|
|
|
3890
4157
|
|
|
3891
4158
|
// src/commands.doctor.engine.ts
|
|
3892
4159
|
init_color();
|
|
3893
|
-
import { readFileSync as
|
|
4160
|
+
import { readFileSync as readFileSync12 } from "node:fs";
|
|
3894
4161
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
3895
4162
|
|
|
3896
4163
|
// src/commands.doctor.version.ts
|
|
3897
4164
|
init_color();
|
|
3898
|
-
import { readFileSync as
|
|
4165
|
+
import { readFileSync as readFileSync11 } from "node:fs";
|
|
3899
4166
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
3900
4167
|
init_config();
|
|
3901
4168
|
var STRICT_SEMVER = /^\d+\.\d+\.\d+$/;
|
|
@@ -3912,7 +4179,7 @@ function compareSemver(a, b) {
|
|
|
3912
4179
|
function readLocalVersion() {
|
|
3913
4180
|
try {
|
|
3914
4181
|
const pkgPath = fileURLToPath2(new URL("../package.json", import.meta.url));
|
|
3915
|
-
const parsed = JSON.parse(
|
|
4182
|
+
const parsed = JSON.parse(readFileSync11(pkgPath, "utf8"));
|
|
3916
4183
|
if (typeof parsed.version === "string" && parsed.version.length > 0) {
|
|
3917
4184
|
return parsed.version;
|
|
3918
4185
|
}
|
|
@@ -3971,7 +4238,7 @@ function parseMinVersion(spec) {
|
|
|
3971
4238
|
function readEnginesNode() {
|
|
3972
4239
|
try {
|
|
3973
4240
|
const pkgPath = fileURLToPath3(new URL("../package.json", import.meta.url));
|
|
3974
|
-
const parsed = JSON.parse(
|
|
4241
|
+
const parsed = JSON.parse(readFileSync12(pkgPath, "utf8"));
|
|
3975
4242
|
const node = parsed.engines?.node;
|
|
3976
4243
|
if (typeof node === "string" && node.length > 0) return node;
|
|
3977
4244
|
return null;
|
|
@@ -3999,44 +4266,44 @@ function reportNodeEngineCheck(section2) {
|
|
|
3999
4266
|
|
|
4000
4267
|
// src/spinner.ts
|
|
4001
4268
|
init_color();
|
|
4002
|
-
import { existsSync as
|
|
4269
|
+
import { existsSync as existsSync30 } from "node:fs";
|
|
4003
4270
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
4004
4271
|
import { Worker } from "node:worker_threads";
|
|
4005
4272
|
|
|
4006
4273
|
// src/commands.push.recovery.ts
|
|
4007
4274
|
init_config();
|
|
4008
|
-
import { readFileSync as
|
|
4009
|
-
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";
|
|
4010
4277
|
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
4011
4278
|
|
|
4012
4279
|
// src/commands.push.recovery.actions.ts
|
|
4013
4280
|
init_config();
|
|
4014
|
-
import { readFileSync as
|
|
4281
|
+
import { readFileSync as readFileSync14 } from "node:fs";
|
|
4015
4282
|
import { isAbsolute as isAbsolute2, resolve as resolve3, sep as sep5 } from "node:path";
|
|
4016
4283
|
|
|
4017
4284
|
// src/commands.push.recovery.redact.ts
|
|
4018
4285
|
init_config();
|
|
4019
4286
|
init_config_sharedDirs_guard();
|
|
4020
|
-
import { cpSync as
|
|
4021
|
-
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";
|
|
4022
4289
|
|
|
4023
4290
|
// src/commands.redact.ts
|
|
4024
4291
|
init_config();
|
|
4025
|
-
import { existsSync as
|
|
4026
|
-
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";
|
|
4027
4294
|
|
|
4028
4295
|
// src/commands.redact.subtree.ts
|
|
4029
|
-
import { existsSync as
|
|
4030
|
-
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";
|
|
4031
4298
|
init_utils_fs();
|
|
4032
4299
|
init_utils();
|
|
4033
4300
|
function collectFiles(dir, out) {
|
|
4034
|
-
if (!
|
|
4035
|
-
const st =
|
|
4301
|
+
if (!existsSync27(dir)) return;
|
|
4302
|
+
const st = lstatSync10(dir);
|
|
4036
4303
|
if (!st.isDirectory()) return;
|
|
4037
|
-
for (const entry of
|
|
4038
|
-
const abs =
|
|
4039
|
-
const lst =
|
|
4304
|
+
for (const entry of readdirSync12(dir)) {
|
|
4305
|
+
const abs = join32(dir, entry);
|
|
4306
|
+
const lst = lstatSync10(abs);
|
|
4040
4307
|
if (lst.isSymbolicLink()) continue;
|
|
4041
4308
|
if (lst.isDirectory()) {
|
|
4042
4309
|
collectFiles(abs, out);
|
|
@@ -4072,7 +4339,7 @@ function applySubtreeRedactions(mainPath, mainFindings, subtreeFiles, rule, ts,
|
|
|
4072
4339
|
if (!dryRun && total > 0) {
|
|
4073
4340
|
for (const { path: filePath, findings } of dirty) {
|
|
4074
4341
|
backupBeforeWrite(filePath, ts);
|
|
4075
|
-
const before =
|
|
4342
|
+
const before = readFileSync13(filePath, "utf8");
|
|
4076
4343
|
const after = applyRedactions(before, findings);
|
|
4077
4344
|
if (after === before) {
|
|
4078
4345
|
log(
|
|
@@ -4140,15 +4407,15 @@ init_utils_json();
|
|
|
4140
4407
|
init_utils();
|
|
4141
4408
|
function resolveLiveTranscript(id) {
|
|
4142
4409
|
try {
|
|
4143
|
-
const mapPath =
|
|
4144
|
-
if (!
|
|
4410
|
+
const mapPath = join33(repoHome(), "path-map.json");
|
|
4411
|
+
if (!existsSync28(mapPath)) return null;
|
|
4145
4412
|
const projects = readJson(mapPath).projects;
|
|
4146
4413
|
const claude = claudeHome();
|
|
4147
4414
|
for (const hostMap of Object.values(projects)) {
|
|
4148
4415
|
const abs = hostMap[HOST];
|
|
4149
4416
|
if (abs === void 0) continue;
|
|
4150
|
-
const live =
|
|
4151
|
-
if (
|
|
4417
|
+
const live = join33(claude, "projects", encodePath(abs), `${id}.jsonl`);
|
|
4418
|
+
if (existsSync28(live)) return live;
|
|
4152
4419
|
}
|
|
4153
4420
|
return null;
|
|
4154
4421
|
} catch {
|
|
@@ -4168,17 +4435,17 @@ function cmdRedact(opts, nowMs = Date.now, scan = scanFile) {
|
|
|
4168
4435
|
}
|
|
4169
4436
|
const repo = repoHome();
|
|
4170
4437
|
const backup = backupBase();
|
|
4171
|
-
if (!
|
|
4438
|
+
if (!existsSync28(repo)) die(`repo not cloned at ${repo}`);
|
|
4172
4439
|
const handle = acquireLock("redact");
|
|
4173
4440
|
if (handle === null) process.exit(0);
|
|
4174
4441
|
try {
|
|
4175
4442
|
const localPath = resolveLiveTranscript(id);
|
|
4176
|
-
if (localPath === null || !
|
|
4443
|
+
if (localPath === null || !existsSync28(localPath)) {
|
|
4177
4444
|
fail(`could not resolve local transcript for session ${id} on this host`);
|
|
4178
4445
|
process.exitCode = 1;
|
|
4179
4446
|
return;
|
|
4180
4447
|
}
|
|
4181
|
-
const sessionDir =
|
|
4448
|
+
const sessionDir = join33(dirname7(localPath), id);
|
|
4182
4449
|
const subtreeFiles = listSubtreeFiles(sessionDir);
|
|
4183
4450
|
const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync7(p).mtimeMs);
|
|
4184
4451
|
if (isRecentlyModified(subtreeMtime, nowMs())) {
|
|
@@ -4294,8 +4561,8 @@ function resolveStagedDir(localPath, map, claude, repo) {
|
|
|
4294
4561
|
assertSafeLogical(logical);
|
|
4295
4562
|
const abs = hostMap[HOST];
|
|
4296
4563
|
if (abs === void 0) continue;
|
|
4297
|
-
if (localPath.startsWith(
|
|
4298
|
-
return
|
|
4564
|
+
if (localPath.startsWith(join34(claude, "projects", encodePath(abs)) + sep4)) {
|
|
4565
|
+
return join34(repo, "shared", "projects", logical);
|
|
4299
4566
|
}
|
|
4300
4567
|
}
|
|
4301
4568
|
return null;
|
|
@@ -4305,7 +4572,7 @@ function preflightRedactable(f, map, nowMs) {
|
|
|
4305
4572
|
if (sid === null) return "a finding has no resolvable session id (not a session transcript)";
|
|
4306
4573
|
const localPath = resolveLiveTranscript(sid);
|
|
4307
4574
|
if (localPath === null) return `session ${sid}: local transcript not found`;
|
|
4308
|
-
const sessionDir =
|
|
4575
|
+
const sessionDir = join34(dirname8(localPath), sid);
|
|
4309
4576
|
const subtreeFiles = listSubtreeFiles(sessionDir);
|
|
4310
4577
|
const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync8(p).mtimeMs);
|
|
4311
4578
|
if (isRecentlyModified(subtreeMtime, nowMs())) {
|
|
@@ -4335,7 +4602,7 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
|
|
|
4335
4602
|
`could not locate the local transcript for session ${sid}; choose Skip or Drop session.`
|
|
4336
4603
|
);
|
|
4337
4604
|
}
|
|
4338
|
-
const sessionDir =
|
|
4605
|
+
const sessionDir = join34(dirname8(localPath), sid);
|
|
4339
4606
|
const subtreeFiles = listSubtreeFiles(sessionDir);
|
|
4340
4607
|
const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync8(p).mtimeMs);
|
|
4341
4608
|
if (isRecentlyModified(subtreeMtime, nowMs())) {
|
|
@@ -4369,26 +4636,26 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
|
|
|
4369
4636
|
);
|
|
4370
4637
|
}
|
|
4371
4638
|
mkdirSync7(stagedProjectDir, { recursive: true });
|
|
4372
|
-
|
|
4373
|
-
if (
|
|
4374
|
-
|
|
4639
|
+
cpSync6(localPath, join34(stagedProjectDir, `${sid}.jsonl`), { force: true });
|
|
4640
|
+
if (existsSync29(sessionDir)) {
|
|
4641
|
+
cpSync6(sessionDir, join34(stagedProjectDir, sid), { force: true, recursive: true });
|
|
4375
4642
|
}
|
|
4376
4643
|
return true;
|
|
4377
4644
|
}
|
|
4378
4645
|
|
|
4379
4646
|
// src/commands.push.recovery.drop.ts
|
|
4380
4647
|
init_config();
|
|
4381
|
-
import { rmSync as
|
|
4382
|
-
import { join as
|
|
4648
|
+
import { rmSync as rmSync10 } from "node:fs";
|
|
4649
|
+
import { join as join35 } from "node:path";
|
|
4383
4650
|
function dropSessionFromStaged(sid, map) {
|
|
4384
4651
|
const logicals = Object.keys(map.projects);
|
|
4385
4652
|
if (logicals.length === 0) return false;
|
|
4386
4653
|
const repo = repoHome();
|
|
4387
4654
|
for (const logical of logicals) {
|
|
4388
|
-
const jsonl =
|
|
4389
|
-
const dir =
|
|
4390
|
-
|
|
4391
|
-
|
|
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 });
|
|
4392
4659
|
}
|
|
4393
4660
|
return true;
|
|
4394
4661
|
}
|
|
@@ -4422,7 +4689,7 @@ function makeDefaultReadLine(repo) {
|
|
|
4422
4689
|
if (isAbsolute2(file) || target !== repoRoot && !target.startsWith(repoRoot + sep5)) {
|
|
4423
4690
|
return null;
|
|
4424
4691
|
}
|
|
4425
|
-
const content =
|
|
4692
|
+
const content = readFileSync14(target, "utf8");
|
|
4426
4693
|
const lines = content.split(/\r?\n/);
|
|
4427
4694
|
const idx = line - 1;
|
|
4428
4695
|
if (idx < 0 || idx >= lines.length) return null;
|
|
@@ -4543,10 +4810,10 @@ function applyThenRescan(scanVerdict, repoHome2) {
|
|
|
4543
4810
|
return next;
|
|
4544
4811
|
}
|
|
4545
4812
|
function allowThenRescan(append, scanVerdict, repoHome2) {
|
|
4546
|
-
const ignPath =
|
|
4813
|
+
const ignPath = join36(repoHome2, ".gitleaksignore");
|
|
4547
4814
|
let before;
|
|
4548
4815
|
try {
|
|
4549
|
-
before =
|
|
4816
|
+
before = readFileSync15(ignPath, "utf8");
|
|
4550
4817
|
} catch {
|
|
4551
4818
|
before = null;
|
|
4552
4819
|
}
|
|
@@ -4554,7 +4821,7 @@ function allowThenRescan(append, scanVerdict, repoHome2) {
|
|
|
4554
4821
|
try {
|
|
4555
4822
|
return applyThenRescan(scanVerdict, repoHome2);
|
|
4556
4823
|
} catch (err) {
|
|
4557
|
-
if (before === null)
|
|
4824
|
+
if (before === null) rmSync11(ignPath, { force: true });
|
|
4558
4825
|
else writeFileSync5(ignPath, before, "utf8");
|
|
4559
4826
|
throw err;
|
|
4560
4827
|
}
|
|
@@ -4642,7 +4909,7 @@ function writeAnimatedDone(out, label, ms, useTTY) {
|
|
|
4642
4909
|
`);
|
|
4643
4910
|
}
|
|
4644
4911
|
function resolveWorkerPath(deps = {}) {
|
|
4645
|
-
const check = deps.existsSyncFn ??
|
|
4912
|
+
const check = deps.existsSyncFn ?? existsSync30;
|
|
4646
4913
|
const base = deps.baseUrl ?? import.meta.url;
|
|
4647
4914
|
const mjs = fileURLToPath4(new URL("./nomad.worker.mjs", base));
|
|
4648
4915
|
if (check(mjs)) return mjs;
|
|
@@ -4709,8 +4976,8 @@ function withSpinner(label, fn, deps) {
|
|
|
4709
4976
|
// src/commands.doctor.gitleaks-version.ts
|
|
4710
4977
|
init_color();
|
|
4711
4978
|
import { execFileSync as execFileSync11 } from "node:child_process";
|
|
4712
|
-
import { existsSync as
|
|
4713
|
-
import { join as
|
|
4979
|
+
import { existsSync as existsSync31 } from "node:fs";
|
|
4980
|
+
import { join as join37 } from "node:path";
|
|
4714
4981
|
init_config();
|
|
4715
4982
|
var SEMVER_MAJOR_MINOR = /^(\d+)\.(\d+)\.\d+$/;
|
|
4716
4983
|
var GITLEAKS_TIMEOUT_MS = 5e3;
|
|
@@ -4719,7 +4986,7 @@ function majorMinorOf(value) {
|
|
|
4719
4986
|
return m === null ? null : [m[1], m[2]];
|
|
4720
4987
|
}
|
|
4721
4988
|
function readGitleaksVersion(run, tomlExists) {
|
|
4722
|
-
const tomlPath =
|
|
4989
|
+
const tomlPath = join37(repoHome(), ".gitleaks.toml");
|
|
4723
4990
|
const args = ["version"];
|
|
4724
4991
|
if (tomlExists(tomlPath)) args.push("--config", tomlPath);
|
|
4725
4992
|
try {
|
|
@@ -4731,7 +4998,7 @@ function readGitleaksVersion(run, tomlExists) {
|
|
|
4731
4998
|
return null;
|
|
4732
4999
|
}
|
|
4733
5000
|
}
|
|
4734
|
-
function reportGitleaksVersionCheck(section2, run = execFileSync11, tomlExists =
|
|
5001
|
+
function reportGitleaksVersionCheck(section2, run = execFileSync11, tomlExists = existsSync31) {
|
|
4735
5002
|
const raw = readGitleaksVersion(run, tomlExists);
|
|
4736
5003
|
if (raw === null) return;
|
|
4737
5004
|
const local = majorMinorOf(raw);
|
|
@@ -4954,8 +5221,8 @@ function gatherDoctorSections(opts) {
|
|
|
4954
5221
|
reportHostKeyAlignment(host);
|
|
4955
5222
|
reportRepoState(host);
|
|
4956
5223
|
const links = section("Shared links");
|
|
4957
|
-
const mapPath =
|
|
4958
|
-
const rawMap =
|
|
5224
|
+
const mapPath = join38(repoHome(), "path-map.json");
|
|
5225
|
+
const rawMap = existsSync32(mapPath) ? readJsonSafe(mapPath, mapPath, links) : null;
|
|
4959
5226
|
const map = rawMap ?? { projects: {} };
|
|
4960
5227
|
reportSharedLinks(links, map);
|
|
4961
5228
|
reportDroppedNamesMigration(links);
|
|
@@ -5052,8 +5319,8 @@ function parseDoctorArgs(args) {
|
|
|
5052
5319
|
// src/commands.drop-session.ts
|
|
5053
5320
|
init_config();
|
|
5054
5321
|
import { execFileSync as execFileSync16 } from "node:child_process";
|
|
5055
|
-
import { existsSync as
|
|
5056
|
-
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";
|
|
5057
5324
|
|
|
5058
5325
|
// src/commands.drop-session.git.ts
|
|
5059
5326
|
import { execFileSync as execFileSync15 } from "node:child_process";
|
|
@@ -5095,8 +5362,8 @@ function isInIndex(rel, repo) {
|
|
|
5095
5362
|
init_config();
|
|
5096
5363
|
init_utils();
|
|
5097
5364
|
init_utils_json();
|
|
5098
|
-
import { existsSync as
|
|
5099
|
-
import { join as
|
|
5365
|
+
import { existsSync as existsSync33 } from "node:fs";
|
|
5366
|
+
import { join as join39 } from "node:path";
|
|
5100
5367
|
var SHARED_PROJECT_LOGICAL = /^shared\/projects\/([^/]+)\//;
|
|
5101
5368
|
function reportScrubHint(id, matches) {
|
|
5102
5369
|
const live = resolveLiveTranscript2(id, matches);
|
|
@@ -5112,8 +5379,8 @@ function reportScrubHint(id, matches) {
|
|
|
5112
5379
|
}
|
|
5113
5380
|
function resolveLiveTranscript2(id, matches) {
|
|
5114
5381
|
try {
|
|
5115
|
-
const mapPath =
|
|
5116
|
-
if (!
|
|
5382
|
+
const mapPath = join39(repoHome(), "path-map.json");
|
|
5383
|
+
if (!existsSync33(mapPath)) return null;
|
|
5117
5384
|
const projects = readJson(mapPath).projects;
|
|
5118
5385
|
const claude = claudeHome();
|
|
5119
5386
|
for (const rel of matches) {
|
|
@@ -5121,8 +5388,8 @@ function resolveLiveTranscript2(id, matches) {
|
|
|
5121
5388
|
if (logical === void 0) continue;
|
|
5122
5389
|
const abs = projects[logical]?.[HOST];
|
|
5123
5390
|
if (abs === void 0) continue;
|
|
5124
|
-
const live =
|
|
5125
|
-
if (
|
|
5391
|
+
const live = join39(claude, "projects", encodePath(abs), `${id}.jsonl`);
|
|
5392
|
+
if (existsSync33(live)) return live;
|
|
5126
5393
|
}
|
|
5127
5394
|
return null;
|
|
5128
5395
|
} catch {
|
|
@@ -5138,12 +5405,12 @@ function cmdDropSession(id) {
|
|
|
5138
5405
|
process.exit(1);
|
|
5139
5406
|
}
|
|
5140
5407
|
const repo = repoHome();
|
|
5141
|
-
if (!
|
|
5408
|
+
if (!existsSync34(repo)) die(`repo not cloned at ${repo}`);
|
|
5142
5409
|
const handle = acquireLock("drop-session");
|
|
5143
5410
|
if (handle === null) process.exit(0);
|
|
5144
5411
|
try {
|
|
5145
|
-
const repoProjects =
|
|
5146
|
-
if (!
|
|
5412
|
+
const repoProjects = join40(repo, "shared", "projects");
|
|
5413
|
+
if (!existsSync34(repoProjects)) {
|
|
5147
5414
|
throw new NomadFatal(`no staged session matches ${id}`);
|
|
5148
5415
|
}
|
|
5149
5416
|
const matches = collectMatches(repoProjects, id, repo);
|
|
@@ -5165,14 +5432,14 @@ function cmdDropSession(id) {
|
|
|
5165
5432
|
}
|
|
5166
5433
|
function collectMatches(repoProjects, id, repo) {
|
|
5167
5434
|
const matches = [];
|
|
5168
|
-
for (const logical of
|
|
5169
|
-
const candidate =
|
|
5170
|
-
if (
|
|
5171
|
-
matches.push(
|
|
5172
|
-
}
|
|
5173
|
-
const dir =
|
|
5174
|
-
if (
|
|
5175
|
-
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);
|
|
5176
5443
|
const staged = expandStagedDir(dirRel, repo);
|
|
5177
5444
|
if (staged.length > 0) matches.push(...staged);
|
|
5178
5445
|
else matches.push(dirRel);
|
|
@@ -5214,7 +5481,7 @@ init_color();
|
|
|
5214
5481
|
|
|
5215
5482
|
// src/summary.ts
|
|
5216
5483
|
init_utils();
|
|
5217
|
-
function summaryText(verb, unmapped, collisions = 0, extrasSkipped = 0) {
|
|
5484
|
+
function summaryText(verb, unmapped, collisions = 0, extrasSkipped = 0, localOnly = 0) {
|
|
5218
5485
|
const extras = extrasSkipped > 0 ? `, ${extrasSkipped} extras skipped` : "";
|
|
5219
5486
|
if (verb === "push") {
|
|
5220
5487
|
if (unmapped === 0 && collisions === 0 && extrasSkipped === 0) {
|
|
@@ -5223,16 +5490,17 @@ function summaryText(verb, unmapped, collisions = 0, extrasSkipped = 0) {
|
|
|
5223
5490
|
const base = `summary: ${unmapped} unmapped on push, ${collisions} collisions`;
|
|
5224
5491
|
return { text: `${base}${extras} (run nomad doctor to list)`, clean: false };
|
|
5225
5492
|
}
|
|
5226
|
-
if (unmapped === 0 && extrasSkipped === 0) {
|
|
5493
|
+
if (unmapped === 0 && extrasSkipped === 0 && localOnly === 0) {
|
|
5227
5494
|
return { text: "summary: clean", clean: true };
|
|
5228
5495
|
}
|
|
5496
|
+
const localOnlyPhrase = localOnly > 0 ? `, ${localOnly} local-only present (push to reconcile)` : "";
|
|
5229
5497
|
return {
|
|
5230
|
-
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}`,
|
|
5231
5499
|
clean: false
|
|
5232
5500
|
};
|
|
5233
5501
|
}
|
|
5234
|
-
function summaryRow(verb, unmapped, collisions = 0, extrasSkipped = 0) {
|
|
5235
|
-
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);
|
|
5236
5504
|
return text.replace(/^summary: /, "");
|
|
5237
5505
|
}
|
|
5238
5506
|
|
|
@@ -5246,11 +5514,17 @@ function buildSettingsSection(label) {
|
|
|
5246
5514
|
addItem(s, `${green(okGlyph)} settings.json (base + ${label})`);
|
|
5247
5515
|
return s;
|
|
5248
5516
|
}
|
|
5249
|
-
function buildSessionsSection(items, unmapped) {
|
|
5517
|
+
function buildSessionsSection(items, unmapped, localOnly = 0) {
|
|
5250
5518
|
const s = section("Sessions");
|
|
5251
5519
|
for (const logical of items) addItem(s, `${green(okGlyph)} ${logical}`);
|
|
5252
5520
|
const skip = collapsedSkipRow(unmapped, "not in path-map (run nomad doctor to list)");
|
|
5253
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
|
+
}
|
|
5254
5528
|
return s;
|
|
5255
5529
|
}
|
|
5256
5530
|
function buildExtrasSection(items, extrasSkipped) {
|
|
@@ -5304,167 +5578,12 @@ init_config();
|
|
|
5304
5578
|
init_config();
|
|
5305
5579
|
import { existsSync as existsSync36, statSync as statSync10 } from "node:fs";
|
|
5306
5580
|
import { join as join43 } from "node:path";
|
|
5307
|
-
|
|
5308
|
-
// src/extras-sync.core.ts
|
|
5309
|
-
init_config();
|
|
5310
|
-
import { cpSync as cpSync6, existsSync as existsSync34, lstatSync as lstatSync9, readdirSync as readdirSync13, rmSync as rmSync11 } from "node:fs";
|
|
5311
|
-
import { basename, join as join40 } from "node:path";
|
|
5312
|
-
init_utils();
|
|
5313
|
-
init_utils_json();
|
|
5314
|
-
function loadValidatedExtras(opts) {
|
|
5315
|
-
const repo = repoHome();
|
|
5316
|
-
const mapPath = join40(repo, "path-map.json");
|
|
5317
|
-
const repoExtras = join40(repo, "shared", "extras");
|
|
5318
|
-
if (!existsSync34(mapPath) || opts.requireRepoExtras === true && !existsSync34(repoExtras)) {
|
|
5319
|
-
if (opts.missingMsg !== void 0) log(opts.missingMsg);
|
|
5320
|
-
return null;
|
|
5321
|
-
}
|
|
5322
|
-
const map = readPathMap(mapPath);
|
|
5323
|
-
const extrasMap = map.extras ?? {};
|
|
5324
|
-
if (Object.keys(extrasMap).length === 0) return null;
|
|
5325
|
-
for (const logical of Object.keys(extrasMap)) {
|
|
5326
|
-
assertSafeLogical(logical);
|
|
5327
|
-
const localRoot = map.projects[logical]?.[HOST];
|
|
5328
|
-
if (localRoot && localRoot !== "TBD") assertSafeLocalRoot(localRoot, logical);
|
|
5329
|
-
}
|
|
5330
|
-
return { map, extrasMap };
|
|
5331
|
-
}
|
|
5332
|
-
function* eachExtrasTarget(v, counts) {
|
|
5333
|
-
const whitelist = SUPPORTED_EXTRAS;
|
|
5334
|
-
for (const [logical, dirnames] of Object.entries(v.extrasMap)) {
|
|
5335
|
-
const localRoot = v.map.projects[logical]?.[HOST];
|
|
5336
|
-
if (!localRoot || localRoot === "TBD") {
|
|
5337
|
-
counts.unmapped++;
|
|
5338
|
-
continue;
|
|
5339
|
-
}
|
|
5340
|
-
for (const dirname10 of dirnames) {
|
|
5341
|
-
if (!whitelist.includes(dirname10)) {
|
|
5342
|
-
counts.skipped++;
|
|
5343
|
-
continue;
|
|
5344
|
-
}
|
|
5345
|
-
yield { logical, localRoot, dirname: dirname10 };
|
|
5346
|
-
}
|
|
5347
|
-
}
|
|
5348
|
-
}
|
|
5349
|
-
function stripCollidingDstSymlinks(src, dst, isExcluded) {
|
|
5350
|
-
if (!existsSync34(dst)) return;
|
|
5351
|
-
for (const name of readdirSync13(src)) {
|
|
5352
|
-
if (isExcluded(name)) continue;
|
|
5353
|
-
const dstPath = join40(dst, name);
|
|
5354
|
-
const dstStat = lstatSync9(dstPath, { throwIfNoEntry: false });
|
|
5355
|
-
if (dstStat === void 0) continue;
|
|
5356
|
-
if (dstStat.isSymbolicLink()) {
|
|
5357
|
-
rmSync11(dstPath, { recursive: true, force: true });
|
|
5358
|
-
} else if (dstStat.isDirectory() && lstatSync9(join40(src, name)).isDirectory()) {
|
|
5359
|
-
stripCollidingDstSymlinks(join40(src, name), dstPath, isExcluded);
|
|
5360
|
-
}
|
|
5361
|
-
}
|
|
5362
|
-
}
|
|
5363
|
-
function copyExtrasOverlayFiltered(src, dst, blockSet) {
|
|
5364
|
-
stripCollidingDstSymlinks(src, dst, (name) => isDeniedName(blockSet, name));
|
|
5365
|
-
try {
|
|
5366
|
-
cpSync6(src, dst, {
|
|
5367
|
-
recursive: true,
|
|
5368
|
-
force: true,
|
|
5369
|
-
verbatimSymlinks: true,
|
|
5370
|
-
filter: (srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename(srcEntry))
|
|
5371
|
-
});
|
|
5372
|
-
} catch (err) {
|
|
5373
|
-
const e = err;
|
|
5374
|
-
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") {
|
|
5375
|
-
throw new NomadFatal(
|
|
5376
|
-
`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`
|
|
5377
|
-
);
|
|
5378
|
-
}
|
|
5379
|
-
throw err;
|
|
5380
|
-
}
|
|
5381
|
-
}
|
|
5382
|
-
function copyExtras(src, dst) {
|
|
5383
|
-
rmSync11(dst, { recursive: true, force: true });
|
|
5384
|
-
cpSync6(src, dst, { recursive: true, force: true, verbatimSymlinks: true });
|
|
5385
|
-
}
|
|
5386
|
-
function extrasDenySet(dirname10) {
|
|
5387
|
-
return dirname10 === ".claude" ? CLAUDE_EXTRA_NEVER_SYNC : ALWAYS_NEVER_SYNC;
|
|
5388
|
-
}
|
|
5389
|
-
function copyExtrasFiltered(src, dst, blockSet) {
|
|
5390
|
-
rmSync11(dst, { recursive: true, force: true });
|
|
5391
|
-
cpSync6(src, dst, {
|
|
5392
|
-
recursive: true,
|
|
5393
|
-
force: true,
|
|
5394
|
-
verbatimSymlinks: true,
|
|
5395
|
-
filter: (srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename(srcEntry))
|
|
5396
|
-
});
|
|
5397
|
-
}
|
|
5398
|
-
function prunePreservingDenied(src, dst, blockSet) {
|
|
5399
|
-
for (const name of readdirSync13(dst)) {
|
|
5400
|
-
if (isDeniedName(blockSet, name)) continue;
|
|
5401
|
-
const dstPath = join40(dst, name);
|
|
5402
|
-
const srcStat = lstatSync9(join40(src, name), { throwIfNoEntry: false });
|
|
5403
|
-
if (srcStat === void 0) {
|
|
5404
|
-
rmSync11(dstPath, { recursive: true, force: true });
|
|
5405
|
-
continue;
|
|
5406
|
-
}
|
|
5407
|
-
const dstStat = lstatSync9(dstPath);
|
|
5408
|
-
if (srcStat.isDirectory() && dstStat.isDirectory()) {
|
|
5409
|
-
prunePreservingDenied(join40(src, name), dstPath, blockSet);
|
|
5410
|
-
} else if (srcStat.isDirectory() !== dstStat.isDirectory()) {
|
|
5411
|
-
rmSync11(dstPath, { recursive: true, force: true });
|
|
5412
|
-
}
|
|
5413
|
-
}
|
|
5414
|
-
}
|
|
5415
|
-
function copyExtrasFilteredPreserving(src, dst, blockSet) {
|
|
5416
|
-
const dstStat = lstatSync9(dst, { throwIfNoEntry: false });
|
|
5417
|
-
if (dstStat !== void 0) {
|
|
5418
|
-
if (dstStat.isDirectory()) prunePreservingDenied(src, dst, blockSet);
|
|
5419
|
-
else rmSync11(dst, { recursive: true, force: true });
|
|
5420
|
-
}
|
|
5421
|
-
stripCollidingDstSymlinks(src, dst, (name) => isDeniedName(blockSet, name));
|
|
5422
|
-
cpSync6(src, dst, {
|
|
5423
|
-
recursive: true,
|
|
5424
|
-
force: true,
|
|
5425
|
-
verbatimSymlinks: true,
|
|
5426
|
-
filter: (srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename(srcEntry))
|
|
5427
|
-
});
|
|
5428
|
-
}
|
|
5429
|
-
function prunePreservingBy(src, dst, isPreserved) {
|
|
5430
|
-
for (const name of readdirSync13(dst)) {
|
|
5431
|
-
if (isPreserved(name)) continue;
|
|
5432
|
-
const dstPath = join40(dst, name);
|
|
5433
|
-
const srcStat = lstatSync9(join40(src, name), { throwIfNoEntry: false });
|
|
5434
|
-
if (srcStat === void 0) {
|
|
5435
|
-
rmSync11(dstPath, { recursive: true, force: true });
|
|
5436
|
-
continue;
|
|
5437
|
-
}
|
|
5438
|
-
const dstStat = lstatSync9(dstPath);
|
|
5439
|
-
if (srcStat.isDirectory() && dstStat.isDirectory()) {
|
|
5440
|
-
prunePreservingBy(join40(src, name), dstPath, isPreserved);
|
|
5441
|
-
} else if (srcStat.isDirectory() !== dstStat.isDirectory()) {
|
|
5442
|
-
rmSync11(dstPath, { recursive: true, force: true });
|
|
5443
|
-
}
|
|
5444
|
-
}
|
|
5445
|
-
}
|
|
5446
|
-
function copyExtrasFilteredPreservingBy(src, dst, isPreserved) {
|
|
5447
|
-
const dstStat = lstatSync9(dst, { throwIfNoEntry: false });
|
|
5448
|
-
if (dstStat !== void 0) {
|
|
5449
|
-
if (dstStat.isDirectory()) prunePreservingBy(src, dst, isPreserved);
|
|
5450
|
-
else rmSync11(dst, { recursive: true, force: true });
|
|
5451
|
-
}
|
|
5452
|
-
stripCollidingDstSymlinks(src, dst, isPreserved);
|
|
5453
|
-
cpSync6(src, dst, {
|
|
5454
|
-
recursive: true,
|
|
5455
|
-
force: true,
|
|
5456
|
-
verbatimSymlinks: true,
|
|
5457
|
-
filter: (srcEntry) => srcEntry === src || !isPreserved(basename(srcEntry))
|
|
5458
|
-
});
|
|
5459
|
-
}
|
|
5460
|
-
|
|
5461
|
-
// src/extras-sync.ts
|
|
5462
5581
|
init_utils();
|
|
5463
5582
|
init_utils_json();
|
|
5464
5583
|
|
|
5465
5584
|
// src/extras-sync.remap.ts
|
|
5466
5585
|
init_config();
|
|
5467
|
-
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";
|
|
5468
5587
|
import { dirname as dirname9, join as join42, sep as sep7 } from "node:path";
|
|
5469
5588
|
|
|
5470
5589
|
// src/extras-sync.planning-diff.ts
|
|
@@ -5593,24 +5712,58 @@ function deletePlanningTarget(target, planningRoot, repoCounterpart) {
|
|
|
5593
5712
|
rmSync12(target, { recursive: true, force: true });
|
|
5594
5713
|
pruneEmptyAncestors(target, planningRoot);
|
|
5595
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
|
+
}
|
|
5596
5760
|
function propagatePlanningDeletes(v, ts, prePostHeads, repo) {
|
|
5597
5761
|
const repoExtras = join42(repo, "shared", "extras");
|
|
5598
5762
|
for (const t of eachExtrasTarget(v, { unmapped: 0, skipped: 0 })) {
|
|
5599
5763
|
if (t.dirname !== ".planning") continue;
|
|
5600
5764
|
let raw;
|
|
5601
5765
|
try {
|
|
5602
|
-
raw = gitCaptureRaw(
|
|
5603
|
-
[
|
|
5604
|
-
"diff",
|
|
5605
|
-
"--name-status",
|
|
5606
|
-
"-z",
|
|
5607
|
-
prePostHeads.pre,
|
|
5608
|
-
prePostHeads.post,
|
|
5609
|
-
"--",
|
|
5610
|
-
`shared/extras/${t.logical}/.planning/`
|
|
5611
|
-
],
|
|
5612
|
-
repo
|
|
5613
|
-
);
|
|
5766
|
+
raw = gitCaptureRaw(planningDiffArgs(prePostHeads.pre, prePostHeads.post, t.logical), repo);
|
|
5614
5767
|
} catch (err) {
|
|
5615
5768
|
const e = err;
|
|
5616
5769
|
if (e.stderr) process.stderr.write(e.stderr);
|
|
@@ -5618,12 +5771,15 @@ function propagatePlanningDeletes(v, ts, prePostHeads, repo) {
|
|
|
5618
5771
|
`git diff failed while propagating .planning deletes for ${t.logical}; run nomad pull --force-remote to recover`
|
|
5619
5772
|
);
|
|
5620
5773
|
}
|
|
5621
|
-
const
|
|
5622
|
-
if (
|
|
5774
|
+
const pairs = deletePairsFor(t, raw);
|
|
5775
|
+
if (pairs.length === 0) continue;
|
|
5623
5776
|
backupExtrasWrite(join42(t.localRoot, t.dirname), ts, t.localRoot);
|
|
5624
5777
|
const planningRoot = join42(t.localRoot, ".planning");
|
|
5625
|
-
for (const target of
|
|
5626
|
-
|
|
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
|
+
}
|
|
5627
5783
|
deletePlanningTarget(target, planningRoot, join42(repoExtras, t.logical, relToLocal));
|
|
5628
5784
|
}
|
|
5629
5785
|
}
|
|
@@ -5672,23 +5828,30 @@ function remapExtrasPull(ts, opts = {}) {
|
|
|
5672
5828
|
src: join42(repo, "shared", "extras", logical, dirname10),
|
|
5673
5829
|
dst: join42(localRoot, dirname10)
|
|
5674
5830
|
}),
|
|
5675
|
-
// Snapshot the host-side dst BEFORE
|
|
5831
|
+
// Snapshot the host-side dst BEFORE the copy step touches it. Anchor on
|
|
5676
5832
|
// localRoot so the backup tree mirrors the project layout.
|
|
5677
5833
|
(dst, localRoot) => backupExtrasWrite(dst, ts, localRoot),
|
|
5678
5834
|
// Pull routing per extra type:
|
|
5679
5835
|
// `.claude`: copyExtrasFilteredPreserving preserves host-local deny-set
|
|
5680
5836
|
// files (e.g. settings.local.json) while mirror-pruning synced entries.
|
|
5681
|
-
// `.planning`:
|
|
5682
|
-
// keeps local-only files
|
|
5683
|
-
//
|
|
5684
|
-
//
|
|
5685
|
-
//
|
|
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`.
|
|
5686
5847
|
(src, dst, dirname10) => {
|
|
5687
5848
|
if (dirname10 === ".claude")
|
|
5688
5849
|
return copyExtrasFilteredPreserving(src, dst, extrasDenySet(dirname10));
|
|
5689
|
-
if (dirname10 === ".planning")
|
|
5690
|
-
|
|
5691
|
-
|
|
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);
|
|
5692
5855
|
}
|
|
5693
5856
|
);
|
|
5694
5857
|
if (!dryRun && prePostHeads !== void 0) {
|
|
@@ -5703,11 +5866,10 @@ function divergenceWarnLine(o) {
|
|
|
5703
5866
|
const name = o.isDir ? `${o.dirname}/` : o.dirname;
|
|
5704
5867
|
const one = o.count === 1;
|
|
5705
5868
|
const fileCount = one ? "1 file" : `${o.count} files`;
|
|
5706
|
-
const them = one ? "it" : "them";
|
|
5707
5869
|
const yours = one ? "your current file is" : "your current files are";
|
|
5708
|
-
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}/)`;
|
|
5709
5871
|
}
|
|
5710
|
-
function divergenceCheckExtras(ts) {
|
|
5872
|
+
function divergenceCheckExtras(ts, prePostHeads) {
|
|
5711
5873
|
const v = loadValidatedExtras({});
|
|
5712
5874
|
if (v === null) return;
|
|
5713
5875
|
const counts = { unmapped: 0, skipped: 0 };
|
|
@@ -5717,25 +5879,30 @@ function divergenceCheckExtras(ts) {
|
|
|
5717
5879
|
const local = join43(localRoot, dirname10);
|
|
5718
5880
|
const repoEntry = join43(repo, "shared", "extras", logical, dirname10);
|
|
5719
5881
|
if (!existsSync36(local) || !existsSync36(repoEntry)) continue;
|
|
5720
|
-
const
|
|
5721
|
-
if (
|
|
5882
|
+
const modified = listDivergingModified(local, repoEntry);
|
|
5883
|
+
if (modified.length === 0) continue;
|
|
5722
5884
|
const projectBackupRoot = join43(backupRoot, encodePath(localRoot));
|
|
5723
5885
|
warn(
|
|
5724
5886
|
divergenceWarnLine({
|
|
5725
5887
|
dirname: dirname10,
|
|
5726
5888
|
logical,
|
|
5727
5889
|
isDir: statSync10(local).isDirectory(),
|
|
5728
|
-
count:
|
|
5890
|
+
count: modified.length,
|
|
5729
5891
|
projectBackupRoot
|
|
5730
5892
|
})
|
|
5731
5893
|
);
|
|
5732
|
-
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
|
+
}
|
|
5733
5900
|
}
|
|
5734
5901
|
}
|
|
5735
5902
|
|
|
5736
5903
|
// src/skills-sync.ts
|
|
5737
5904
|
init_config();
|
|
5738
|
-
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";
|
|
5739
5906
|
import { join as join44 } from "node:path";
|
|
5740
5907
|
init_utils_fs();
|
|
5741
5908
|
function isGsdOwned(name) {
|
|
@@ -5759,7 +5926,7 @@ function syncSkillsPull(ts) {
|
|
|
5759
5926
|
const sharedSkills = join44(repoHome(), "shared", "skills");
|
|
5760
5927
|
if (!existsSync37(sharedSkills)) return;
|
|
5761
5928
|
const localSkills = join44(claudeHome(), "skills");
|
|
5762
|
-
const dstStat =
|
|
5929
|
+
const dstStat = lstatSync11(localSkills, { throwIfNoEntry: false });
|
|
5763
5930
|
if (dstStat?.isSymbolicLink() === true) {
|
|
5764
5931
|
backupBeforeWrite(localSkills, ts);
|
|
5765
5932
|
rmSync13(localSkills, { recursive: true, force: true });
|
|
@@ -5769,7 +5936,7 @@ function syncSkillsPull(ts) {
|
|
|
5769
5936
|
}
|
|
5770
5937
|
function syncSkillsPush() {
|
|
5771
5938
|
const localSkills = join44(claudeHome(), "skills");
|
|
5772
|
-
const stat =
|
|
5939
|
+
const stat = lstatSync11(localSkills, { throwIfNoEntry: false });
|
|
5773
5940
|
if (stat === void 0) return;
|
|
5774
5941
|
if (stat.isSymbolicLink()) return;
|
|
5775
5942
|
const sharedSkills = join44(repoHome(), "shared", "skills");
|
|
@@ -6135,10 +6302,14 @@ function computePreview(ts, map, verb = "pull") {
|
|
|
6135
6302
|
dryRun: true,
|
|
6136
6303
|
onPreview: (e) => addItem(sessions, formatSessionRow(e))
|
|
6137
6304
|
});
|
|
6305
|
+
const localOnly = scanLocalOnly();
|
|
6306
|
+
if (localOnly > 0) {
|
|
6307
|
+
addItem(sessions, `${localOnly} local-only present, not in repo (push to reconcile)`);
|
|
6308
|
+
}
|
|
6138
6309
|
const summary = section("Summary");
|
|
6139
|
-
addItem(summary, summaryRow(verb, remapResult.unmapped));
|
|
6310
|
+
addItem(summary, summaryRow(verb, remapResult.unmapped, 0, 0, localOnly));
|
|
6140
6311
|
renderTree([links, settingsSection, sessions, summary]);
|
|
6141
|
-
return { unmapped: remapResult.unmapped, collisions: 0 };
|
|
6312
|
+
return { unmapped: remapResult.unmapped, collisions: 0, localOnly };
|
|
6142
6313
|
}
|
|
6143
6314
|
|
|
6144
6315
|
// src/commands.pull.ts
|
|
@@ -6293,14 +6464,21 @@ function applyWetPull(ts, map, prePostHeads) {
|
|
|
6293
6464
|
syncSkillsPull(ts);
|
|
6294
6465
|
const remapResult = withSpinner("Syncing sessions", () => remapPull(ts));
|
|
6295
6466
|
const extrasResult = remapExtrasPull(ts, { prePostHeads });
|
|
6467
|
+
const localOnly = scanLocalOnly();
|
|
6296
6468
|
const summary = section("Summary");
|
|
6297
6469
|
addItem(
|
|
6298
6470
|
summary,
|
|
6299
|
-
summaryRow(
|
|
6471
|
+
summaryRow(
|
|
6472
|
+
"pull",
|
|
6473
|
+
remapResult.unmapped + extrasResult.unmapped,
|
|
6474
|
+
0,
|
|
6475
|
+
extrasResult.skipped,
|
|
6476
|
+
localOnly
|
|
6477
|
+
)
|
|
6300
6478
|
);
|
|
6301
6479
|
renderTree([
|
|
6302
6480
|
buildSettingsSection(label),
|
|
6303
|
-
buildSessionsSection(remapResult.pulled, remapResult.unmapped),
|
|
6481
|
+
buildSessionsSection(remapResult.pulled, remapResult.unmapped, localOnly),
|
|
6304
6482
|
buildExtrasSection(extrasResult.pulled, extrasResult.skipped),
|
|
6305
6483
|
summary
|
|
6306
6484
|
]);
|
|
@@ -6353,7 +6531,7 @@ function cmdPull(opts = {}) {
|
|
|
6353
6531
|
});
|
|
6354
6532
|
const mapPath = join46(repo, "path-map.json");
|
|
6355
6533
|
const map = existsSync39(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
6356
|
-
divergenceCheckExtras(ts);
|
|
6534
|
+
divergenceCheckExtras(ts, dryRun ? prePostHeads : void 0);
|
|
6357
6535
|
if (dryRun) {
|
|
6358
6536
|
computePreview(ts, map, "pull");
|
|
6359
6537
|
log("dry-run complete; no mutation");
|
|
@@ -6568,12 +6746,12 @@ function reportSettingsAheadDrift(repo) {
|
|
|
6568
6746
|
// src/commands.push.guards.ts
|
|
6569
6747
|
init_push_checks();
|
|
6570
6748
|
init_utils();
|
|
6571
|
-
import { join as join49, relative as
|
|
6749
|
+
import { join as join49, relative as relative7 } from "node:path";
|
|
6572
6750
|
function guardGitlinks(repo) {
|
|
6573
6751
|
const gitlinks = findGitlinks(join49(repo, "shared"));
|
|
6574
6752
|
if (gitlinks.length === 0) return;
|
|
6575
6753
|
for (const p of gitlinks) {
|
|
6576
|
-
const rel =
|
|
6754
|
+
const rel = relative7(repo, p);
|
|
6577
6755
|
fail(`gitlink: ${rel} would push as submodule (run: rm -rf ${rel} or remove the nested repo)`);
|
|
6578
6756
|
}
|
|
6579
6757
|
const noun = gitlinks.length === 1 ? "entry" : "entries";
|
|
@@ -6682,7 +6860,7 @@ init_config_sharedDirs_guard();
|
|
|
6682
6860
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
6683
6861
|
import { copyFileSync, existsSync as existsSync42, mkdirSync as mkdirSync11, readdirSync as readdirSync16, rmSync as rmSync14 } from "node:fs";
|
|
6684
6862
|
import { homedir as homedir5 } from "node:os";
|
|
6685
|
-
import { join as join50, relative as
|
|
6863
|
+
import { join as join50, relative as relative8, sep as sep8 } from "node:path";
|
|
6686
6864
|
init_push_leak_verdict();
|
|
6687
6865
|
init_push_gitleaks();
|
|
6688
6866
|
init_utils_fs();
|
|
@@ -6694,7 +6872,7 @@ function stageSessionDir(localDir, dstDir, changed) {
|
|
|
6694
6872
|
const matching = [...changed].filter((p) => p.startsWith(prefix));
|
|
6695
6873
|
if (matching.length === 0) return false;
|
|
6696
6874
|
for (const src of matching) {
|
|
6697
|
-
copyFileAtomic(src, join50(dstDir,
|
|
6875
|
+
copyFileAtomic(src, join50(dstDir, relative8(localDir, src)));
|
|
6698
6876
|
}
|
|
6699
6877
|
return true;
|
|
6700
6878
|
}
|
|
@@ -6933,6 +7111,7 @@ function cmdDiff() {
|
|
|
6933
7111
|
const ts = freshBackupTs(backupBase());
|
|
6934
7112
|
const mapPath = join52(repo, "path-map.json");
|
|
6935
7113
|
const map = existsSync44(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
7114
|
+
divergenceCheckExtras(ts);
|
|
6936
7115
|
computePreview(ts, map, "diff");
|
|
6937
7116
|
} catch (err) {
|
|
6938
7117
|
if (err instanceof NomadFatal) {
|
|
@@ -7478,7 +7657,7 @@ function parsePushArgs(argv) {
|
|
|
7478
7657
|
// package.json
|
|
7479
7658
|
var package_default = {
|
|
7480
7659
|
name: "claude-nomad",
|
|
7481
|
-
version: "0.56.
|
|
7660
|
+
version: "0.56.2",
|
|
7482
7661
|
type: "module",
|
|
7483
7662
|
description: "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
|
|
7484
7663
|
keywords: [
|
|
@@ -7703,7 +7882,7 @@ var DEFAULT_HELP = [
|
|
|
7703
7882
|
init_config();
|
|
7704
7883
|
init_utils();
|
|
7705
7884
|
init_utils_json();
|
|
7706
|
-
import { existsSync as existsSync48, readFileSync as
|
|
7885
|
+
import { existsSync as existsSync48, readFileSync as readFileSync17, readdirSync as readdirSync18 } from "node:fs";
|
|
7707
7886
|
import { join as join56 } from "node:path";
|
|
7708
7887
|
function resumeCmd(sessionId) {
|
|
7709
7888
|
if (!/^[A-Za-z0-9_-]+$/.test(sessionId) || sessionId.length > 128) {
|
|
@@ -7755,7 +7934,7 @@ function findTranscriptPath(projectsRoot, sessionId) {
|
|
|
7755
7934
|
return null;
|
|
7756
7935
|
}
|
|
7757
7936
|
function extractRecordedCwd(jsonlPath) {
|
|
7758
|
-
for (const line of
|
|
7937
|
+
for (const line of readFileSync17(jsonlPath, "utf8").split("\n")) {
|
|
7759
7938
|
if (!line.trim()) continue;
|
|
7760
7939
|
try {
|
|
7761
7940
|
const obj = JSON.parse(line);
|