claude-nomad 0.56.1 → 0.57.0
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 +35 -0
- package/README.md +18 -12
- package/dist/nomad.mjs +844 -511
- 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"] });
|
|
@@ -207,12 +214,13 @@ function gitOrFatal(args, context, cwd) {
|
|
|
207
214
|
throw new NomadFatal(`${context} failed`);
|
|
208
215
|
}
|
|
209
216
|
}
|
|
210
|
-
var log, warn, fail, item, NomadFatal, die, gitStatusPorcelainZ;
|
|
217
|
+
var log, ok, warn, fail, item, NomadFatal, die, gitStatusPorcelainZ;
|
|
211
218
|
var init_utils = __esm({
|
|
212
219
|
"src/utils.ts"() {
|
|
213
220
|
"use strict";
|
|
214
221
|
init_color();
|
|
215
222
|
log = (msg) => console.log(`${dim(infoGlyph)} ${msg}`);
|
|
223
|
+
ok = (msg) => console.log(`${green(okGlyph)} ${msg}`);
|
|
216
224
|
warn = (msg) => {
|
|
217
225
|
console.error(`${yellow(warnGlyph)} ${msg}`);
|
|
218
226
|
};
|
|
@@ -461,7 +469,7 @@ var init_config = __esm({
|
|
|
461
469
|
"hosts/",
|
|
462
470
|
"path-map.json",
|
|
463
471
|
".gitleaksignore",
|
|
464
|
-
// written by nomad push Allow action
|
|
472
|
+
// written by nomad push Allow action
|
|
465
473
|
".gitleaks.overlay.toml"
|
|
466
474
|
// user-owned gitleaks allowlist overlay layered on the bundled base
|
|
467
475
|
];
|
|
@@ -2110,8 +2118,8 @@ function cmdEject(opts = {}, roots = defaultEjectRoots()) {
|
|
|
2110
2118
|
}
|
|
2111
2119
|
|
|
2112
2120
|
// src/commands.doctor.ts
|
|
2113
|
-
import { existsSync as
|
|
2114
|
-
import { join as
|
|
2121
|
+
import { existsSync as existsSync32 } from "node:fs";
|
|
2122
|
+
import { join as join38 } from "node:path";
|
|
2115
2123
|
|
|
2116
2124
|
// src/commands.doctor.checks.repo.ts
|
|
2117
2125
|
init_color();
|
|
@@ -2551,28 +2559,42 @@ init_config();
|
|
|
2551
2559
|
// src/extras-sync.diff.ts
|
|
2552
2560
|
init_utils();
|
|
2553
2561
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
const
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
+
import { relative as relative2 } from "node:path";
|
|
2563
|
+
function parseNameStatus(stdout) {
|
|
2564
|
+
const entries = [];
|
|
2565
|
+
for (const line of stdout.split("\n")) {
|
|
2566
|
+
if (line.length === 0) continue;
|
|
2567
|
+
const tab = line.indexOf(" ");
|
|
2568
|
+
if (tab === -1) continue;
|
|
2569
|
+
entries.push({ status: line.slice(0, tab), path: line.slice(tab + 1) });
|
|
2570
|
+
}
|
|
2571
|
+
return entries;
|
|
2572
|
+
}
|
|
2573
|
+
function labelEntry(entry) {
|
|
2574
|
+
if (entry.status === "D") return `${entry.path} (local only)`;
|
|
2575
|
+
if (entry.status === "A") return `${entry.path} (repo only)`;
|
|
2576
|
+
return entry.path;
|
|
2562
2577
|
}
|
|
2563
2578
|
function parseDiffOutput(stdout) {
|
|
2564
|
-
return stdout
|
|
2579
|
+
return parseNameStatus(stdout).map(labelEntry);
|
|
2565
2580
|
}
|
|
2566
|
-
function
|
|
2581
|
+
function parseModifiedPaths(stdout, a) {
|
|
2582
|
+
return parseNameStatus(stdout).filter((entry) => entry.status === "M").map((entry) => relative2(a, entry.path));
|
|
2583
|
+
}
|
|
2584
|
+
function runNameStatusDiff(a, b, parse) {
|
|
2567
2585
|
try {
|
|
2568
|
-
const stdout = execFileSync2(
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2586
|
+
const stdout = execFileSync2(
|
|
2587
|
+
"git",
|
|
2588
|
+
["diff", "--no-index", "--no-renames", "--name-status", a, b],
|
|
2589
|
+
{
|
|
2590
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
2591
|
+
}
|
|
2592
|
+
).toString();
|
|
2593
|
+
return parse(stdout);
|
|
2572
2594
|
} catch (err) {
|
|
2573
2595
|
const e = err;
|
|
2574
2596
|
if (e.status === 1 && e.stdout !== void 0) {
|
|
2575
|
-
return
|
|
2597
|
+
return parse(e.stdout.toString());
|
|
2576
2598
|
}
|
|
2577
2599
|
if (e.code === "ENOENT") {
|
|
2578
2600
|
warn(`git not on PATH; divergence check skipped for ${a}`);
|
|
@@ -2582,6 +2604,12 @@ function listDivergingFiles(a, b) {
|
|
|
2582
2604
|
return [];
|
|
2583
2605
|
}
|
|
2584
2606
|
}
|
|
2607
|
+
function listDivergingFiles(a, b) {
|
|
2608
|
+
return runNameStatusDiff(a, b, parseDiffOutput);
|
|
2609
|
+
}
|
|
2610
|
+
function listDivergingModified(a, b) {
|
|
2611
|
+
return runNameStatusDiff(a, b, (stdout) => parseModifiedPaths(stdout, a));
|
|
2612
|
+
}
|
|
2585
2613
|
|
|
2586
2614
|
// src/commands.doctor.checks.skills.ts
|
|
2587
2615
|
function stripSideIndicator(line) {
|
|
@@ -2591,15 +2619,15 @@ function stripSideIndicator(line) {
|
|
|
2591
2619
|
}
|
|
2592
2620
|
function isGsdDiffLine(line, localBase, sharedBase) {
|
|
2593
2621
|
const bare = stripSideIndicator(line);
|
|
2594
|
-
let
|
|
2622
|
+
let relative9;
|
|
2595
2623
|
if (bare.startsWith(localBase + "/")) {
|
|
2596
|
-
|
|
2624
|
+
relative9 = bare.slice(localBase.length + 1);
|
|
2597
2625
|
} else if (bare.startsWith(sharedBase + "/")) {
|
|
2598
|
-
|
|
2626
|
+
relative9 = bare.slice(sharedBase.length + 1);
|
|
2599
2627
|
} else {
|
|
2600
|
-
|
|
2628
|
+
relative9 = bare;
|
|
2601
2629
|
}
|
|
2602
|
-
return
|
|
2630
|
+
return relative9.split("/")[0].startsWith(GSD_PREFIX);
|
|
2603
2631
|
}
|
|
2604
2632
|
function reportSkillsDivergence(section2) {
|
|
2605
2633
|
const sharedSkills = join14(repoHome(), "shared", "skills");
|
|
@@ -2632,7 +2660,7 @@ init_color();
|
|
|
2632
2660
|
init_config();
|
|
2633
2661
|
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
2634
2662
|
import { existsSync as existsSync15 } from "node:fs";
|
|
2635
|
-
import { join as join18, relative as
|
|
2663
|
+
import { join as join18, relative as relative3 } from "node:path";
|
|
2636
2664
|
init_commands_pull_wedge();
|
|
2637
2665
|
init_push_checks();
|
|
2638
2666
|
init_utils();
|
|
@@ -2659,7 +2687,7 @@ function reportGitlinks(section2) {
|
|
|
2659
2687
|
if (existsSync15(sharedDir)) {
|
|
2660
2688
|
const gitlinks = findGitlinks(sharedDir);
|
|
2661
2689
|
for (const p of gitlinks) {
|
|
2662
|
-
const rel =
|
|
2690
|
+
const rel = relative3(repo, p);
|
|
2663
2691
|
addItem(
|
|
2664
2692
|
section2,
|
|
2665
2693
|
`${red(failGlyph)} gitlink: ${blue(rel)} would push as submodule (run: rm -rf ${rel} or remove the nested repo)`
|
|
@@ -2936,9 +2964,9 @@ function reportCheckSchema(section2) {
|
|
|
2936
2964
|
init_color();
|
|
2937
2965
|
import { randomBytes } from "node:crypto";
|
|
2938
2966
|
import { execFileSync as execFileSync9 } from "node:child_process";
|
|
2939
|
-
import { existsSync as
|
|
2967
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync6, readdirSync as readdirSync10, rmSync as rmSync9 } from "node:fs";
|
|
2940
2968
|
import { homedir as homedir4 } from "node:os";
|
|
2941
|
-
import { join as
|
|
2969
|
+
import { join as join26 } from "node:path";
|
|
2942
2970
|
|
|
2943
2971
|
// src/commands.doctor.check-shared.scan.ts
|
|
2944
2972
|
init_color();
|
|
@@ -3035,8 +3063,22 @@ init_config();
|
|
|
3035
3063
|
|
|
3036
3064
|
// src/remap.ts
|
|
3037
3065
|
init_config_sharedDirs_guard();
|
|
3038
|
-
import {
|
|
3039
|
-
|
|
3066
|
+
import {
|
|
3067
|
+
cpSync as cpSync5,
|
|
3068
|
+
existsSync as existsSync20,
|
|
3069
|
+
lstatSync as lstatSync9,
|
|
3070
|
+
mkdirSync as mkdirSync5,
|
|
3071
|
+
readdirSync as readdirSync9,
|
|
3072
|
+
renameSync as renameSync3,
|
|
3073
|
+
rmSync as rmSync8,
|
|
3074
|
+
statSync as statSync5
|
|
3075
|
+
} from "node:fs";
|
|
3076
|
+
import { dirname as dirname4, join as join25, relative as relative5, sep as sep3 } from "node:path";
|
|
3077
|
+
|
|
3078
|
+
// src/extras-sync.core.ts
|
|
3079
|
+
init_config();
|
|
3080
|
+
import { cpSync as cpSync4, existsSync as existsSync18, lstatSync as lstatSync8, readdirSync as readdirSync7, readFileSync as readFileSync6, rmSync as rmSync7 } from "node:fs";
|
|
3081
|
+
import { basename, join as join23, relative as relative4 } from "node:path";
|
|
3040
3082
|
|
|
3041
3083
|
// src/extras-sync.guards.ts
|
|
3042
3084
|
init_utils();
|
|
@@ -3055,6 +3097,180 @@ function assertSafeLocalRoot(localRoot, logical) {
|
|
|
3055
3097
|
}
|
|
3056
3098
|
}
|
|
3057
3099
|
|
|
3100
|
+
// src/extras-sync.core.ts
|
|
3101
|
+
init_utils();
|
|
3102
|
+
init_utils_json();
|
|
3103
|
+
function loadValidatedExtras(opts) {
|
|
3104
|
+
const repo = repoHome();
|
|
3105
|
+
const mapPath = join23(repo, "path-map.json");
|
|
3106
|
+
const repoExtras = join23(repo, "shared", "extras");
|
|
3107
|
+
if (!existsSync18(mapPath) || opts.requireRepoExtras === true && !existsSync18(repoExtras)) {
|
|
3108
|
+
if (opts.missingMsg !== void 0) log(opts.missingMsg);
|
|
3109
|
+
return null;
|
|
3110
|
+
}
|
|
3111
|
+
const map = readPathMap(mapPath);
|
|
3112
|
+
const extrasMap = map.extras ?? {};
|
|
3113
|
+
if (Object.keys(extrasMap).length === 0) return null;
|
|
3114
|
+
for (const logical of Object.keys(extrasMap)) {
|
|
3115
|
+
assertSafeLogical(logical);
|
|
3116
|
+
const localRoot = map.projects[logical]?.[HOST];
|
|
3117
|
+
if (localRoot && localRoot !== "TBD") assertSafeLocalRoot(localRoot, logical);
|
|
3118
|
+
}
|
|
3119
|
+
return { map, extrasMap };
|
|
3120
|
+
}
|
|
3121
|
+
function* eachExtrasTarget(v, counts) {
|
|
3122
|
+
const whitelist = SUPPORTED_EXTRAS;
|
|
3123
|
+
for (const [logical, dirnames] of Object.entries(v.extrasMap)) {
|
|
3124
|
+
const localRoot = v.map.projects[logical]?.[HOST];
|
|
3125
|
+
if (!localRoot || localRoot === "TBD") {
|
|
3126
|
+
counts.unmapped++;
|
|
3127
|
+
continue;
|
|
3128
|
+
}
|
|
3129
|
+
for (const dirname10 of dirnames) {
|
|
3130
|
+
if (!whitelist.includes(dirname10)) {
|
|
3131
|
+
counts.skipped++;
|
|
3132
|
+
continue;
|
|
3133
|
+
}
|
|
3134
|
+
yield { logical, localRoot, dirname: dirname10 };
|
|
3135
|
+
}
|
|
3136
|
+
}
|
|
3137
|
+
}
|
|
3138
|
+
function stripCollidingDstSymlinks(src, dst, isExcluded) {
|
|
3139
|
+
if (!existsSync18(dst)) return;
|
|
3140
|
+
for (const name of readdirSync7(src)) {
|
|
3141
|
+
if (isExcluded(name)) continue;
|
|
3142
|
+
const dstPath = join23(dst, name);
|
|
3143
|
+
const dstStat = lstatSync8(dstPath, { throwIfNoEntry: false });
|
|
3144
|
+
if (dstStat === void 0) continue;
|
|
3145
|
+
if (dstStat.isSymbolicLink()) {
|
|
3146
|
+
rmSync7(dstPath, { recursive: true, force: true });
|
|
3147
|
+
} else if (dstStat.isDirectory() && lstatSync8(join23(src, name)).isDirectory()) {
|
|
3148
|
+
stripCollidingDstSymlinks(join23(src, name), dstPath, isExcluded);
|
|
3149
|
+
}
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
function cpSyncGuarded(src, dst, filter, label) {
|
|
3153
|
+
try {
|
|
3154
|
+
cpSync4(src, dst, { recursive: true, force: true, verbatimSymlinks: true, filter });
|
|
3155
|
+
} catch (err) {
|
|
3156
|
+
const e = err;
|
|
3157
|
+
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") {
|
|
3158
|
+
throw new NomadFatal(
|
|
3159
|
+
`${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`
|
|
3160
|
+
);
|
|
3161
|
+
}
|
|
3162
|
+
throw err;
|
|
3163
|
+
}
|
|
3164
|
+
}
|
|
3165
|
+
function copyExtrasOverlayFiltered(src, dst, blockSet) {
|
|
3166
|
+
stripCollidingDstSymlinks(src, dst, (name) => isDeniedName(blockSet, name));
|
|
3167
|
+
cpSyncGuarded(
|
|
3168
|
+
src,
|
|
3169
|
+
dst,
|
|
3170
|
+
(srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename(srcEntry)),
|
|
3171
|
+
"copyExtrasOverlayFiltered"
|
|
3172
|
+
);
|
|
3173
|
+
}
|
|
3174
|
+
function copyExtrasOverlaySkipDiverged(src, dst, blockSet, divergedSet) {
|
|
3175
|
+
stripCollidingDstSymlinks(src, dst, (name) => isDeniedName(blockSet, name));
|
|
3176
|
+
cpSyncGuarded(
|
|
3177
|
+
src,
|
|
3178
|
+
dst,
|
|
3179
|
+
(srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename(srcEntry)) && !divergedSet.has(relative4(src, srcEntry)),
|
|
3180
|
+
"copyExtrasOverlaySkipDiverged"
|
|
3181
|
+
);
|
|
3182
|
+
}
|
|
3183
|
+
function copyExtras(src, dst) {
|
|
3184
|
+
rmSync7(dst, { recursive: true, force: true });
|
|
3185
|
+
cpSync4(src, dst, { recursive: true, force: true, verbatimSymlinks: true });
|
|
3186
|
+
}
|
|
3187
|
+
function copyExtrasFileSkipDiverged(src, dst) {
|
|
3188
|
+
if (existsSync18(dst)) {
|
|
3189
|
+
let dstBuf;
|
|
3190
|
+
try {
|
|
3191
|
+
dstBuf = readFileSync6(dst);
|
|
3192
|
+
} catch {
|
|
3193
|
+
return;
|
|
3194
|
+
}
|
|
3195
|
+
if (!readFileSync6(src).equals(dstBuf)) return;
|
|
3196
|
+
}
|
|
3197
|
+
copyExtras(src, dst);
|
|
3198
|
+
}
|
|
3199
|
+
function extrasDenySet(dirname10) {
|
|
3200
|
+
return dirname10 === ".claude" ? CLAUDE_EXTRA_NEVER_SYNC : ALWAYS_NEVER_SYNC;
|
|
3201
|
+
}
|
|
3202
|
+
function copyExtrasFiltered(src, dst, blockSet) {
|
|
3203
|
+
rmSync7(dst, { recursive: true, force: true });
|
|
3204
|
+
cpSync4(src, dst, {
|
|
3205
|
+
recursive: true,
|
|
3206
|
+
force: true,
|
|
3207
|
+
verbatimSymlinks: true,
|
|
3208
|
+
filter: (srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename(srcEntry))
|
|
3209
|
+
});
|
|
3210
|
+
}
|
|
3211
|
+
function prunePreservingDenied(src, dst, blockSet) {
|
|
3212
|
+
for (const name of readdirSync7(dst)) {
|
|
3213
|
+
if (isDeniedName(blockSet, name)) continue;
|
|
3214
|
+
const dstPath = join23(dst, name);
|
|
3215
|
+
const srcStat = lstatSync8(join23(src, name), { throwIfNoEntry: false });
|
|
3216
|
+
if (srcStat === void 0) {
|
|
3217
|
+
rmSync7(dstPath, { recursive: true, force: true });
|
|
3218
|
+
continue;
|
|
3219
|
+
}
|
|
3220
|
+
const dstStat = lstatSync8(dstPath);
|
|
3221
|
+
if (srcStat.isDirectory() && dstStat.isDirectory()) {
|
|
3222
|
+
prunePreservingDenied(join23(src, name), dstPath, blockSet);
|
|
3223
|
+
} else if (srcStat.isDirectory() !== dstStat.isDirectory()) {
|
|
3224
|
+
rmSync7(dstPath, { recursive: true, force: true });
|
|
3225
|
+
}
|
|
3226
|
+
}
|
|
3227
|
+
}
|
|
3228
|
+
function copyExtrasFilteredPreserving(src, dst, blockSet) {
|
|
3229
|
+
const dstStat = lstatSync8(dst, { throwIfNoEntry: false });
|
|
3230
|
+
if (dstStat !== void 0) {
|
|
3231
|
+
if (dstStat.isDirectory()) prunePreservingDenied(src, dst, blockSet);
|
|
3232
|
+
else rmSync7(dst, { recursive: true, force: true });
|
|
3233
|
+
}
|
|
3234
|
+
stripCollidingDstSymlinks(src, dst, (name) => isDeniedName(blockSet, name));
|
|
3235
|
+
cpSync4(src, dst, {
|
|
3236
|
+
recursive: true,
|
|
3237
|
+
force: true,
|
|
3238
|
+
verbatimSymlinks: true,
|
|
3239
|
+
filter: (srcEntry) => srcEntry === src || !isDeniedName(blockSet, basename(srcEntry))
|
|
3240
|
+
});
|
|
3241
|
+
}
|
|
3242
|
+
function prunePreservingBy(src, dst, isPreserved) {
|
|
3243
|
+
for (const name of readdirSync7(dst)) {
|
|
3244
|
+
if (isPreserved(name)) continue;
|
|
3245
|
+
const dstPath = join23(dst, name);
|
|
3246
|
+
const srcStat = lstatSync8(join23(src, name), { throwIfNoEntry: false });
|
|
3247
|
+
if (srcStat === void 0) {
|
|
3248
|
+
rmSync7(dstPath, { recursive: true, force: true });
|
|
3249
|
+
continue;
|
|
3250
|
+
}
|
|
3251
|
+
const dstStat = lstatSync8(dstPath);
|
|
3252
|
+
if (srcStat.isDirectory() && dstStat.isDirectory()) {
|
|
3253
|
+
prunePreservingBy(join23(src, name), dstPath, isPreserved);
|
|
3254
|
+
} else if (srcStat.isDirectory() !== dstStat.isDirectory()) {
|
|
3255
|
+
rmSync7(dstPath, { recursive: true, force: true });
|
|
3256
|
+
}
|
|
3257
|
+
}
|
|
3258
|
+
}
|
|
3259
|
+
function copyExtrasFilteredPreservingBy(src, dst, isPreserved) {
|
|
3260
|
+
const dstStat = lstatSync8(dst, { throwIfNoEntry: false });
|
|
3261
|
+
if (dstStat !== void 0) {
|
|
3262
|
+
if (dstStat.isDirectory()) prunePreservingBy(src, dst, isPreserved);
|
|
3263
|
+
else rmSync7(dst, { recursive: true, force: true });
|
|
3264
|
+
}
|
|
3265
|
+
stripCollidingDstSymlinks(src, dst, isPreserved);
|
|
3266
|
+
cpSync4(src, dst, {
|
|
3267
|
+
recursive: true,
|
|
3268
|
+
force: true,
|
|
3269
|
+
verbatimSymlinks: true,
|
|
3270
|
+
filter: (srcEntry) => srcEntry === src || !isPreserved(basename(srcEntry))
|
|
3271
|
+
});
|
|
3272
|
+
}
|
|
3273
|
+
|
|
3058
3274
|
// src/remap.ts
|
|
3059
3275
|
init_config();
|
|
3060
3276
|
|
|
@@ -3063,8 +3279,8 @@ init_config();
|
|
|
3063
3279
|
init_push_gitleaks_config();
|
|
3064
3280
|
init_utils_fs();
|
|
3065
3281
|
import { createHash } from "node:crypto";
|
|
3066
|
-
import { existsSync as
|
|
3067
|
-
import { dirname as dirname3, join as
|
|
3282
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync4, readdirSync as readdirSync8, readFileSync as readFileSync7, statSync as statSync4 } from "node:fs";
|
|
3283
|
+
import { dirname as dirname3, join as join24 } from "node:path";
|
|
3068
3284
|
function isChanged(prev, cur, hash) {
|
|
3069
3285
|
if (prev === void 0) return true;
|
|
3070
3286
|
if (prev.size !== cur.size) return true;
|
|
@@ -3101,11 +3317,11 @@ function shouldFullRescan(old, scannerVersion, configHash, forceFlag) {
|
|
|
3101
3317
|
return false;
|
|
3102
3318
|
}
|
|
3103
3319
|
function hashFile(absPath) {
|
|
3104
|
-
return createHash("sha256").update(
|
|
3320
|
+
return createHash("sha256").update(readFileSync7(absPath)).digest("hex");
|
|
3105
3321
|
}
|
|
3106
3322
|
function enumerateDir(dir, results) {
|
|
3107
|
-
for (const entry of
|
|
3108
|
-
const fullPath =
|
|
3323
|
+
for (const entry of readdirSync8(dir)) {
|
|
3324
|
+
const fullPath = join24(dir, entry);
|
|
3109
3325
|
const st = statSync4(fullPath);
|
|
3110
3326
|
if (st.isDirectory()) {
|
|
3111
3327
|
enumerateDir(fullPath, results);
|
|
@@ -3116,8 +3332,8 @@ function enumerateDir(dir, results) {
|
|
|
3116
3332
|
}
|
|
3117
3333
|
function enumerateSourceFiles(projectDir) {
|
|
3118
3334
|
const results = [];
|
|
3119
|
-
for (const entry of
|
|
3120
|
-
const fullPath =
|
|
3335
|
+
for (const entry of readdirSync8(projectDir)) {
|
|
3336
|
+
const fullPath = join24(projectDir, entry);
|
|
3121
3337
|
const st = statSync4(fullPath);
|
|
3122
3338
|
if (st.isDirectory()) {
|
|
3123
3339
|
enumerateDir(fullPath, results);
|
|
@@ -3129,15 +3345,15 @@ function enumerateSourceFiles(projectDir) {
|
|
|
3129
3345
|
}
|
|
3130
3346
|
function fileIdentity(p) {
|
|
3131
3347
|
if (p === null) return "none::absent";
|
|
3132
|
-
if (!
|
|
3133
|
-
return `${p}::${createHash("sha256").update(
|
|
3348
|
+
if (!existsSync19(p)) return `${p}::absent`;
|
|
3349
|
+
return `${p}::${createHash("sha256").update(readFileSync7(p)).digest("hex")}`;
|
|
3134
3350
|
}
|
|
3135
3351
|
function computeConfigHash() {
|
|
3136
3352
|
const repo = repoHome();
|
|
3137
3353
|
const parts = [
|
|
3138
3354
|
fileIdentity(resolveTomlPath(repo)),
|
|
3139
|
-
fileIdentity(
|
|
3140
|
-
fileIdentity(
|
|
3355
|
+
fileIdentity(join24(repo, ".gitleaks.overlay.toml")),
|
|
3356
|
+
fileIdentity(join24(repo, ".gitleaksignore"))
|
|
3141
3357
|
];
|
|
3142
3358
|
return createHash("sha256").update(parts.join("\n")).digest("hex");
|
|
3143
3359
|
}
|
|
@@ -3148,7 +3364,7 @@ function isManifestShape(raw) {
|
|
|
3148
3364
|
}
|
|
3149
3365
|
function readManifest(path) {
|
|
3150
3366
|
try {
|
|
3151
|
-
const raw =
|
|
3367
|
+
const raw = readFileSync7(path, "utf8");
|
|
3152
3368
|
const parsed = JSON.parse(raw);
|
|
3153
3369
|
if (!isManifestShape(parsed)) return null;
|
|
3154
3370
|
return parsed;
|
|
@@ -3171,18 +3387,19 @@ init_utils_json();
|
|
|
3171
3387
|
var TMP_SUFFIX = ".nomad-tmp";
|
|
3172
3388
|
function atomicMirror(src, dst, options) {
|
|
3173
3389
|
const tmp = `${dst}${TMP_SUFFIX}`;
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3390
|
+
rmSync8(tmp, { recursive: true, force: true });
|
|
3391
|
+
cpSync5(src, tmp, options);
|
|
3392
|
+
rmSync8(dst, { recursive: true, force: true });
|
|
3177
3393
|
renameSync3(tmp, dst);
|
|
3178
3394
|
}
|
|
3179
|
-
function
|
|
3180
|
-
|
|
3395
|
+
function overlaySessionDir(src, dst) {
|
|
3396
|
+
stripCollidingDstSymlinks(src, dst, () => false);
|
|
3397
|
+
cpSyncGuarded(src, dst, void 0, "overlaySessionDir");
|
|
3181
3398
|
}
|
|
3182
3399
|
function copyFileAtomic(src, dst) {
|
|
3183
3400
|
mkdirSync5(dirname4(dst), { recursive: true });
|
|
3184
3401
|
const tmp = `${dst}.tmp.${process.pid}`;
|
|
3185
|
-
|
|
3402
|
+
cpSync5(src, tmp, { force: true, preserveTimestamps: true });
|
|
3186
3403
|
renameSync3(tmp, dst);
|
|
3187
3404
|
}
|
|
3188
3405
|
function hasDeltaForDir(sel, localDir) {
|
|
@@ -3198,12 +3415,12 @@ function applySelective(sel, localDir, repoDst) {
|
|
|
3198
3415
|
const prefix = `${localDir}${sep3}`;
|
|
3199
3416
|
for (const src of sel.changed) {
|
|
3200
3417
|
if (!src.startsWith(prefix)) continue;
|
|
3201
|
-
copyFileAtomic(src,
|
|
3418
|
+
copyFileAtomic(src, join25(repoDst, relative5(localDir, src)));
|
|
3202
3419
|
}
|
|
3203
3420
|
for (const src of sel.deleted) {
|
|
3204
3421
|
if (!src.startsWith(prefix)) continue;
|
|
3205
|
-
const dst =
|
|
3206
|
-
if (
|
|
3422
|
+
const dst = join25(repoDst, relative5(localDir, src));
|
|
3423
|
+
if (existsSync20(dst)) rmSync8(dst);
|
|
3207
3424
|
}
|
|
3208
3425
|
}
|
|
3209
3426
|
function copyDirJsonlOnly(src, dst) {
|
|
@@ -3211,7 +3428,7 @@ function copyDirJsonlOnly(src, dst) {
|
|
|
3211
3428
|
recursive: true,
|
|
3212
3429
|
force: true,
|
|
3213
3430
|
filter: (srcPath) => {
|
|
3214
|
-
const rel =
|
|
3431
|
+
const rel = relative5(src, srcPath);
|
|
3215
3432
|
if (rel === "") return true;
|
|
3216
3433
|
if (rel.split(sep3).length > 1) return true;
|
|
3217
3434
|
if (statSync5(srcPath).isDirectory()) return true;
|
|
@@ -3232,15 +3449,15 @@ function remapPull(ts, opts = {}) {
|
|
|
3232
3449
|
const wouldPull = [];
|
|
3233
3450
|
const repo = repoHome();
|
|
3234
3451
|
const claude = claudeHome();
|
|
3235
|
-
const mapPath =
|
|
3236
|
-
const repoProjects =
|
|
3237
|
-
if (!
|
|
3452
|
+
const mapPath = join25(repo, "path-map.json");
|
|
3453
|
+
const repoProjects = join25(repo, "shared", "projects");
|
|
3454
|
+
if (!existsSync20(mapPath) || !existsSync20(repoProjects)) {
|
|
3238
3455
|
const text = "no path-map or repo projects dir; skipping session remap";
|
|
3239
3456
|
emitPreview(opts.onPreview, { kind: "note", text }, text);
|
|
3240
3457
|
return { unmapped: 0, pulled, wouldPull };
|
|
3241
3458
|
}
|
|
3242
3459
|
const map = readPathMap(mapPath);
|
|
3243
|
-
const localProjects =
|
|
3460
|
+
const localProjects = join25(claude, "projects");
|
|
3244
3461
|
if (!dryRun) mkdirSync5(localProjects, { recursive: true });
|
|
3245
3462
|
for (const [logical, hosts] of Object.entries(map.projects)) {
|
|
3246
3463
|
assertSafeLogical(logical);
|
|
@@ -3250,9 +3467,9 @@ function remapPull(ts, opts = {}) {
|
|
|
3250
3467
|
continue;
|
|
3251
3468
|
}
|
|
3252
3469
|
assertSafeLocalRoot(localPath, logical);
|
|
3253
|
-
const src =
|
|
3254
|
-
if (!
|
|
3255
|
-
const dst =
|
|
3470
|
+
const src = join25(repoProjects, logical);
|
|
3471
|
+
if (!existsSync20(src)) continue;
|
|
3472
|
+
const dst = join25(localProjects, encodePath(localPath));
|
|
3256
3473
|
if (dryRun) {
|
|
3257
3474
|
wouldPull.push(logical);
|
|
3258
3475
|
emitPreview(
|
|
@@ -3263,11 +3480,44 @@ function remapPull(ts, opts = {}) {
|
|
|
3263
3480
|
continue;
|
|
3264
3481
|
}
|
|
3265
3482
|
backupBeforeWrite(dst, ts);
|
|
3266
|
-
|
|
3483
|
+
overlaySessionDir(src, dst);
|
|
3267
3484
|
pulled.push(logical);
|
|
3268
3485
|
}
|
|
3269
3486
|
return { unmapped, pulled, wouldPull };
|
|
3270
3487
|
}
|
|
3488
|
+
function countLocalOnly(src, dst) {
|
|
3489
|
+
let count = 0;
|
|
3490
|
+
for (const name of readdirSync9(dst)) {
|
|
3491
|
+
const dstPath = join25(dst, name);
|
|
3492
|
+
const srcPath = join25(src, name);
|
|
3493
|
+
if (lstatSync9(dstPath).isDirectory()) {
|
|
3494
|
+
count += countLocalOnly(srcPath, dstPath);
|
|
3495
|
+
} else if (lstatSync9(srcPath, { throwIfNoEntry: false }) === void 0) {
|
|
3496
|
+
count++;
|
|
3497
|
+
}
|
|
3498
|
+
}
|
|
3499
|
+
return count;
|
|
3500
|
+
}
|
|
3501
|
+
function scanLocalOnly() {
|
|
3502
|
+
const repo = repoHome();
|
|
3503
|
+
const claude = claudeHome();
|
|
3504
|
+
const mapPath = join25(repo, "path-map.json");
|
|
3505
|
+
const repoProjects = join25(repo, "shared", "projects");
|
|
3506
|
+
if (!existsSync20(mapPath) || !existsSync20(repoProjects)) return 0;
|
|
3507
|
+
const map = readPathMap(mapPath);
|
|
3508
|
+
const localProjects = join25(claude, "projects");
|
|
3509
|
+
let count = 0;
|
|
3510
|
+
for (const [logical, hosts] of Object.entries(map.projects)) {
|
|
3511
|
+
assertSafeLogical(logical);
|
|
3512
|
+
const localPath = hosts[HOST];
|
|
3513
|
+
if (!localPath || localPath === "TBD") continue;
|
|
3514
|
+
assertSafeLocalRoot(localPath, logical);
|
|
3515
|
+
const dst = join25(localProjects, encodePath(localPath));
|
|
3516
|
+
if (!existsSync20(dst)) continue;
|
|
3517
|
+
count += countLocalOnly(join25(repoProjects, logical), dst);
|
|
3518
|
+
}
|
|
3519
|
+
return count;
|
|
3520
|
+
}
|
|
3271
3521
|
function buildReverseMap(map) {
|
|
3272
3522
|
const reverse = /* @__PURE__ */ new Map();
|
|
3273
3523
|
const encodedPaths = /* @__PURE__ */ new Map();
|
|
@@ -3299,26 +3549,26 @@ function remapPush(ts, opts = {}) {
|
|
|
3299
3549
|
const wouldPush = [];
|
|
3300
3550
|
const repo = repoHome();
|
|
3301
3551
|
const claude = claudeHome();
|
|
3302
|
-
const mapPath =
|
|
3303
|
-
if (!
|
|
3552
|
+
const mapPath = join25(repo, "path-map.json");
|
|
3553
|
+
if (!existsSync20(mapPath)) {
|
|
3304
3554
|
log("no path-map.json; skipping session export");
|
|
3305
3555
|
return { unmapped: 0, collisions: 0, pushed, wouldPush };
|
|
3306
3556
|
}
|
|
3307
3557
|
const map = readPathMap(mapPath);
|
|
3308
|
-
const localProjects =
|
|
3309
|
-
const repoProjects =
|
|
3558
|
+
const localProjects = join25(claude, "projects");
|
|
3559
|
+
const repoProjects = join25(repo, "shared", "projects");
|
|
3310
3560
|
const reverse = buildReverseMap(map);
|
|
3311
|
-
if (!
|
|
3561
|
+
if (!existsSync20(localProjects)) return { unmapped, collisions: 0, pushed, wouldPush };
|
|
3312
3562
|
if (!dryRun) mkdirSync5(repoProjects, { recursive: true });
|
|
3313
|
-
for (const dir of
|
|
3563
|
+
for (const dir of readdirSync9(localProjects)) {
|
|
3314
3564
|
if (dir.endsWith(TMP_SUFFIX)) continue;
|
|
3315
3565
|
const logical = reverse.get(dir);
|
|
3316
3566
|
if (!logical) {
|
|
3317
3567
|
unmapped++;
|
|
3318
3568
|
continue;
|
|
3319
3569
|
}
|
|
3320
|
-
const localDir =
|
|
3321
|
-
const repoDst =
|
|
3570
|
+
const localDir = join25(localProjects, dir);
|
|
3571
|
+
const repoDst = join25(repoProjects, logical);
|
|
3322
3572
|
if (skipForSelection(opts.selection, localDir)) continue;
|
|
3323
3573
|
if (dryRun) {
|
|
3324
3574
|
wouldPush.push(logical);
|
|
@@ -3342,8 +3592,8 @@ function buildScanTree(tmpRoot) {
|
|
|
3342
3592
|
const logicalToEncoded = /* @__PURE__ */ new Map();
|
|
3343
3593
|
let staged = 0;
|
|
3344
3594
|
const repo = repoHome();
|
|
3345
|
-
const mapPath =
|
|
3346
|
-
if (!
|
|
3595
|
+
const mapPath = join26(repo, "path-map.json");
|
|
3596
|
+
if (!existsSync21(mapPath)) return { logicalToEncoded, staged, malformed: false };
|
|
3347
3597
|
let map;
|
|
3348
3598
|
try {
|
|
3349
3599
|
map = readJson(mapPath);
|
|
@@ -3360,12 +3610,12 @@ function buildScanTree(tmpRoot) {
|
|
|
3360
3610
|
if (!p || p === "TBD") continue;
|
|
3361
3611
|
reverse.set(encodePath(p), logical);
|
|
3362
3612
|
}
|
|
3363
|
-
const localProjects =
|
|
3364
|
-
if (!
|
|
3365
|
-
for (const dir of
|
|
3613
|
+
const localProjects = join26(claudeHome(), "projects");
|
|
3614
|
+
if (!existsSync21(localProjects)) return { logicalToEncoded, staged, malformed: false };
|
|
3615
|
+
for (const dir of readdirSync10(localProjects)) {
|
|
3366
3616
|
const logical = reverse.get(dir);
|
|
3367
3617
|
if (!logical) continue;
|
|
3368
|
-
copyDirJsonlOnly(
|
|
3618
|
+
copyDirJsonlOnly(join26(localProjects, dir), join26(tmpRoot, "shared", "projects", logical));
|
|
3369
3619
|
logicalToEncoded.set(logical, dir);
|
|
3370
3620
|
staged++;
|
|
3371
3621
|
}
|
|
@@ -3396,11 +3646,11 @@ function ensureGitleaksReady(section2, gitleaksReady) {
|
|
|
3396
3646
|
}
|
|
3397
3647
|
function reportCheckShared(section2, gitleaksReady) {
|
|
3398
3648
|
if (!ensureGitleaksReady(section2, gitleaksReady)) return;
|
|
3399
|
-
const cacheDir =
|
|
3649
|
+
const cacheDir = join26(homedir4(), ".cache", "claude-nomad");
|
|
3400
3650
|
mkdirSync6(cacheDir, { recursive: true });
|
|
3401
3651
|
const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes(4).toString("hex")}`;
|
|
3402
|
-
const reportPath =
|
|
3403
|
-
const tmpRoot =
|
|
3652
|
+
const reportPath = join26(cacheDir, `check-shared-${stamp}.json`);
|
|
3653
|
+
const tmpRoot = join26(cacheDir, `check-shared-tree-${stamp}`);
|
|
3404
3654
|
try {
|
|
3405
3655
|
const { logicalToEncoded, staged, malformed } = buildScanTree(tmpRoot);
|
|
3406
3656
|
if (malformed) {
|
|
@@ -3414,19 +3664,19 @@ function reportCheckShared(section2, gitleaksReady) {
|
|
|
3414
3664
|
}
|
|
3415
3665
|
scanAndReport(section2, tmpRoot, staged, logicalToEncoded);
|
|
3416
3666
|
} finally {
|
|
3417
|
-
|
|
3418
|
-
|
|
3667
|
+
rmSync9(reportPath, { force: true });
|
|
3668
|
+
rmSync9(tmpRoot, { recursive: true, force: true });
|
|
3419
3669
|
}
|
|
3420
3670
|
}
|
|
3421
3671
|
|
|
3422
3672
|
// src/commands.doctor.checks.hooks.scope.ts
|
|
3423
3673
|
init_color();
|
|
3424
|
-
import { existsSync as
|
|
3425
|
-
import { dirname as dirname5, extname, join as
|
|
3674
|
+
import { existsSync as existsSync22, readFileSync as readFileSync8, readdirSync as readdirSync11, realpathSync as realpathSync2 } from "node:fs";
|
|
3675
|
+
import { dirname as dirname5, extname, join as join27 } from "node:path";
|
|
3426
3676
|
init_config();
|
|
3427
3677
|
function typeFromPackageJson(pkgPath) {
|
|
3428
3678
|
try {
|
|
3429
|
-
const parsed = JSON.parse(
|
|
3679
|
+
const parsed = JSON.parse(readFileSync8(pkgPath, "utf8"));
|
|
3430
3680
|
return parsed.type === "module" ? "esm" : "cjs";
|
|
3431
3681
|
} catch {
|
|
3432
3682
|
return "cjs";
|
|
@@ -3444,8 +3694,8 @@ function effectiveType(hookPath) {
|
|
|
3444
3694
|
}
|
|
3445
3695
|
let dir = dirname5(real);
|
|
3446
3696
|
for (; ; ) {
|
|
3447
|
-
const pkg =
|
|
3448
|
-
if (
|
|
3697
|
+
const pkg = join27(dir, "package.json");
|
|
3698
|
+
if (existsSync22(pkg)) return typeFromPackageJson(pkg);
|
|
3449
3699
|
const parent = dirname5(dir);
|
|
3450
3700
|
if (parent === dir) return "cjs";
|
|
3451
3701
|
dir = parent;
|
|
@@ -3474,28 +3724,28 @@ function remedy(family) {
|
|
|
3474
3724
|
}
|
|
3475
3725
|
function safeReaddir2(dir) {
|
|
3476
3726
|
try {
|
|
3477
|
-
return
|
|
3727
|
+
return readdirSync11(dir);
|
|
3478
3728
|
} catch {
|
|
3479
3729
|
return [];
|
|
3480
3730
|
}
|
|
3481
3731
|
}
|
|
3482
3732
|
function safeRead(path) {
|
|
3483
3733
|
try {
|
|
3484
|
-
return
|
|
3734
|
+
return readFileSync8(path, "utf8");
|
|
3485
3735
|
} catch {
|
|
3486
3736
|
return null;
|
|
3487
3737
|
}
|
|
3488
3738
|
}
|
|
3489
3739
|
function reportHookScopeCheck(section2) {
|
|
3490
|
-
const hooksDir =
|
|
3491
|
-
if (!
|
|
3740
|
+
const hooksDir = join27(claudeHome(), "hooks");
|
|
3741
|
+
if (!existsSync22(hooksDir)) {
|
|
3492
3742
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/hooks; skipping module-scope check`);
|
|
3493
3743
|
return;
|
|
3494
3744
|
}
|
|
3495
3745
|
let anyWarn = false;
|
|
3496
3746
|
for (const name of safeReaddir2(hooksDir)) {
|
|
3497
3747
|
if (extname(name) !== ".js") continue;
|
|
3498
|
-
const abs =
|
|
3748
|
+
const abs = join27(hooksDir, name);
|
|
3499
3749
|
const eff = effectiveType(abs);
|
|
3500
3750
|
if (eff === null) continue;
|
|
3501
3751
|
const src = safeRead(abs);
|
|
@@ -3515,8 +3765,8 @@ function reportHookScopeCheck(section2) {
|
|
|
3515
3765
|
|
|
3516
3766
|
// src/commands.doctor.checks.hooks.ts
|
|
3517
3767
|
init_color();
|
|
3518
|
-
import { existsSync as
|
|
3519
|
-
import { join as
|
|
3768
|
+
import { existsSync as existsSync23 } from "node:fs";
|
|
3769
|
+
import { join as join28 } from "node:path";
|
|
3520
3770
|
init_config();
|
|
3521
3771
|
function expandHome(token) {
|
|
3522
3772
|
const h2 = home();
|
|
@@ -3559,7 +3809,7 @@ function checkEventGroups(section2, event, groups) {
|
|
|
3559
3809
|
for (const group of groups) {
|
|
3560
3810
|
for (const cmd of commandsFromGroup(group)) {
|
|
3561
3811
|
for (const resolved of claudePathsIn(cmd)) {
|
|
3562
|
-
if (
|
|
3812
|
+
if (existsSync23(resolved)) continue;
|
|
3563
3813
|
addItem(
|
|
3564
3814
|
section2,
|
|
3565
3815
|
`${red(failGlyph)} hooks/${event}: command target missing: ${resolved} (run nomad pull)`
|
|
@@ -3572,8 +3822,8 @@ function checkEventGroups(section2, event, groups) {
|
|
|
3572
3822
|
return anyFail;
|
|
3573
3823
|
}
|
|
3574
3824
|
function reportHooksTargetCheck(section2) {
|
|
3575
|
-
const settingsPath =
|
|
3576
|
-
if (!
|
|
3825
|
+
const settingsPath = join28(claudeHome(), "settings.json");
|
|
3826
|
+
if (!existsSync23(settingsPath)) {
|
|
3577
3827
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json; skipping hook target check`);
|
|
3578
3828
|
return;
|
|
3579
3829
|
}
|
|
@@ -3596,12 +3846,12 @@ function reportHooksTargetCheck(section2) {
|
|
|
3596
3846
|
|
|
3597
3847
|
// src/commands.doctor.checks.hooks.preserve-symlinks.ts
|
|
3598
3848
|
init_color();
|
|
3599
|
-
import { existsSync as
|
|
3600
|
-
import { join as
|
|
3849
|
+
import { existsSync as existsSync25, readFileSync as readFileSync9 } from "node:fs";
|
|
3850
|
+
import { join as join30 } from "node:path";
|
|
3601
3851
|
|
|
3602
3852
|
// src/commands.doctor.checks.hooks.preserve-symlinks.probe.ts
|
|
3603
|
-
import { closeSync as closeSync3, existsSync as
|
|
3604
|
-
import { dirname as dirname6, join as
|
|
3853
|
+
import { closeSync as closeSync3, existsSync as existsSync24, openSync as openSync3, readSync, realpathSync as realpathSync3 } from "node:fs";
|
|
3854
|
+
import { dirname as dirname6, join as join29, resolve as resolve2 } from "node:path";
|
|
3605
3855
|
function suppressedRanges(src) {
|
|
3606
3856
|
const ranges = [];
|
|
3607
3857
|
const re = /\/\*[\s\S]*?\*\/|\/\/[^\n]*|'[^']*'|"[^"]*"|`[^`]*`/g;
|
|
@@ -3633,12 +3883,12 @@ function topRelativeSpecifiers(src) {
|
|
|
3633
3883
|
}
|
|
3634
3884
|
function specifierIsMissing(specifier, baseDir) {
|
|
3635
3885
|
const base = resolve2(baseDir, specifier);
|
|
3636
|
-
if (
|
|
3886
|
+
if (existsSync24(base)) return false;
|
|
3637
3887
|
for (const ext of [".js", ".cjs", ".mjs"]) {
|
|
3638
|
-
if (
|
|
3888
|
+
if (existsSync24(base + ext)) return false;
|
|
3639
3889
|
}
|
|
3640
3890
|
for (const idx of ["index.js", "index.cjs", "index.mjs"]) {
|
|
3641
|
-
if (
|
|
3891
|
+
if (existsSync24(join29(base, idx))) return false;
|
|
3642
3892
|
}
|
|
3643
3893
|
return true;
|
|
3644
3894
|
}
|
|
@@ -3693,10 +3943,10 @@ function commandTokens(command) {
|
|
|
3693
3943
|
return tokens;
|
|
3694
3944
|
}
|
|
3695
3945
|
function readPathMapSafe() {
|
|
3696
|
-
const mapPath =
|
|
3697
|
-
if (!
|
|
3946
|
+
const mapPath = join30(repoHome(), "path-map.json");
|
|
3947
|
+
if (!existsSync25(mapPath)) return { projects: {} };
|
|
3698
3948
|
try {
|
|
3699
|
-
return JSON.parse(
|
|
3949
|
+
return JSON.parse(readFileSync9(mapPath, "utf8"));
|
|
3700
3950
|
} catch {
|
|
3701
3951
|
return { projects: {} };
|
|
3702
3952
|
}
|
|
@@ -3767,8 +4017,8 @@ function checkEventForPreserveSymlinks(section2, event, groups, sharedLinkNames)
|
|
|
3767
4017
|
return anyWarn;
|
|
3768
4018
|
}
|
|
3769
4019
|
function reportPreserveSymlinksCheck(section2) {
|
|
3770
|
-
const settingsPath =
|
|
3771
|
-
if (!
|
|
4020
|
+
const settingsPath = join30(claudeHome(), "settings.json");
|
|
4021
|
+
if (!existsSync25(settingsPath)) {
|
|
3772
4022
|
addItem(
|
|
3773
4023
|
section2,
|
|
3774
4024
|
`${dim(infoGlyph)} no ~/.claude/settings.json; skipping preserve-symlinks-main check`
|
|
@@ -3777,7 +4027,7 @@ function reportPreserveSymlinksCheck(section2) {
|
|
|
3777
4027
|
}
|
|
3778
4028
|
let settings;
|
|
3779
4029
|
try {
|
|
3780
|
-
settings = JSON.parse(
|
|
4030
|
+
settings = JSON.parse(readFileSync9(settingsPath, "utf8"));
|
|
3781
4031
|
} catch {
|
|
3782
4032
|
return;
|
|
3783
4033
|
}
|
|
@@ -3801,8 +4051,8 @@ function reportPreserveSymlinksCheck(section2) {
|
|
|
3801
4051
|
|
|
3802
4052
|
// src/commands.doctor.checks.settings-drift.ts
|
|
3803
4053
|
init_color();
|
|
3804
|
-
import { existsSync as
|
|
3805
|
-
import { join as
|
|
4054
|
+
import { existsSync as existsSync26, readFileSync as readFileSync10 } from "node:fs";
|
|
4055
|
+
import { join as join31 } from "node:path";
|
|
3806
4056
|
init_config();
|
|
3807
4057
|
init_utils_json();
|
|
3808
4058
|
function diffMergedSettings(merged, settings) {
|
|
@@ -3811,7 +4061,7 @@ function diffMergedSettings(merged, settings) {
|
|
|
3811
4061
|
}
|
|
3812
4062
|
function tryReadJson(filePath) {
|
|
3813
4063
|
try {
|
|
3814
|
-
const raw =
|
|
4064
|
+
const raw = readFileSync10(filePath, "utf8");
|
|
3815
4065
|
const parsed = JSON.parse(raw);
|
|
3816
4066
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
3817
4067
|
return parsed;
|
|
@@ -3820,7 +4070,7 @@ function tryReadJson(filePath) {
|
|
|
3820
4070
|
}
|
|
3821
4071
|
}
|
|
3822
4072
|
function reportHooksBaseSelfCleanNote(section2) {
|
|
3823
|
-
const basePath =
|
|
4073
|
+
const basePath = join31(repoHome(), "shared", "settings.base.json");
|
|
3824
4074
|
const base = tryReadJson(basePath);
|
|
3825
4075
|
if (base === null) return;
|
|
3826
4076
|
if (!baseHasGsdHookEntries(base)) return;
|
|
@@ -3833,14 +4083,14 @@ function reportSettingsDriftCheck(section2) {
|
|
|
3833
4083
|
const claude = claudeHome();
|
|
3834
4084
|
const repo = repoHome();
|
|
3835
4085
|
const host = HOST;
|
|
3836
|
-
const settingsPath =
|
|
3837
|
-
const basePath =
|
|
3838
|
-
const hostPath =
|
|
3839
|
-
if (!
|
|
4086
|
+
const settingsPath = join31(claude, "settings.json");
|
|
4087
|
+
const basePath = join31(repo, "shared", "settings.base.json");
|
|
4088
|
+
const hostPath = join31(repo, "hosts", `${host}.json`);
|
|
4089
|
+
if (!existsSync26(settingsPath)) {
|
|
3840
4090
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json; skipping merge-drift check`);
|
|
3841
4091
|
return;
|
|
3842
4092
|
}
|
|
3843
|
-
if (!
|
|
4093
|
+
if (!existsSync26(basePath)) {
|
|
3844
4094
|
addItem(
|
|
3845
4095
|
section2,
|
|
3846
4096
|
`${dim(infoGlyph)} shared/settings.base.json missing; skipping merge-drift check`
|
|
@@ -3859,7 +4109,7 @@ function reportSettingsDriftCheck(section2) {
|
|
|
3859
4109
|
if (settings === null) {
|
|
3860
4110
|
return;
|
|
3861
4111
|
}
|
|
3862
|
-
const hostExists =
|
|
4112
|
+
const hostExists = existsSync26(hostPath);
|
|
3863
4113
|
const hostObj = hostExists ? tryReadJson(hostPath) : null;
|
|
3864
4114
|
if (hostExists && hostObj === null) {
|
|
3865
4115
|
addItem(
|
|
@@ -3908,12 +4158,12 @@ init_config();
|
|
|
3908
4158
|
|
|
3909
4159
|
// src/commands.doctor.engine.ts
|
|
3910
4160
|
init_color();
|
|
3911
|
-
import { readFileSync as
|
|
4161
|
+
import { readFileSync as readFileSync12 } from "node:fs";
|
|
3912
4162
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
3913
4163
|
|
|
3914
4164
|
// src/commands.doctor.version.ts
|
|
3915
4165
|
init_color();
|
|
3916
|
-
import { readFileSync as
|
|
4166
|
+
import { readFileSync as readFileSync11 } from "node:fs";
|
|
3917
4167
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
3918
4168
|
init_config();
|
|
3919
4169
|
var STRICT_SEMVER = /^\d+\.\d+\.\d+$/;
|
|
@@ -3930,7 +4180,7 @@ function compareSemver(a, b) {
|
|
|
3930
4180
|
function readLocalVersion() {
|
|
3931
4181
|
try {
|
|
3932
4182
|
const pkgPath = fileURLToPath2(new URL("../package.json", import.meta.url));
|
|
3933
|
-
const parsed = JSON.parse(
|
|
4183
|
+
const parsed = JSON.parse(readFileSync11(pkgPath, "utf8"));
|
|
3934
4184
|
if (typeof parsed.version === "string" && parsed.version.length > 0) {
|
|
3935
4185
|
return parsed.version;
|
|
3936
4186
|
}
|
|
@@ -3989,7 +4239,7 @@ function parseMinVersion(spec) {
|
|
|
3989
4239
|
function readEnginesNode() {
|
|
3990
4240
|
try {
|
|
3991
4241
|
const pkgPath = fileURLToPath3(new URL("../package.json", import.meta.url));
|
|
3992
|
-
const parsed = JSON.parse(
|
|
4242
|
+
const parsed = JSON.parse(readFileSync12(pkgPath, "utf8"));
|
|
3993
4243
|
const node = parsed.engines?.node;
|
|
3994
4244
|
if (typeof node === "string" && node.length > 0) return node;
|
|
3995
4245
|
return null;
|
|
@@ -4017,44 +4267,44 @@ function reportNodeEngineCheck(section2) {
|
|
|
4017
4267
|
|
|
4018
4268
|
// src/spinner.ts
|
|
4019
4269
|
init_color();
|
|
4020
|
-
import { existsSync as
|
|
4270
|
+
import { existsSync as existsSync30 } from "node:fs";
|
|
4021
4271
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
4022
4272
|
import { Worker } from "node:worker_threads";
|
|
4023
4273
|
|
|
4024
4274
|
// src/commands.push.recovery.ts
|
|
4025
4275
|
init_config();
|
|
4026
|
-
import { readFileSync as
|
|
4027
|
-
import { join as
|
|
4276
|
+
import { readFileSync as readFileSync15, rmSync as rmSync11, writeFileSync as writeFileSync5 } from "node:fs";
|
|
4277
|
+
import { join as join36 } from "node:path";
|
|
4028
4278
|
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
4029
4279
|
|
|
4030
4280
|
// src/commands.push.recovery.actions.ts
|
|
4031
4281
|
init_config();
|
|
4032
|
-
import { readFileSync as
|
|
4282
|
+
import { readFileSync as readFileSync14 } from "node:fs";
|
|
4033
4283
|
import { isAbsolute as isAbsolute2, resolve as resolve3, sep as sep5 } from "node:path";
|
|
4034
4284
|
|
|
4035
4285
|
// src/commands.push.recovery.redact.ts
|
|
4036
4286
|
init_config();
|
|
4037
4287
|
init_config_sharedDirs_guard();
|
|
4038
|
-
import { cpSync as
|
|
4039
|
-
import { dirname as dirname8, join as
|
|
4288
|
+
import { cpSync as cpSync6, existsSync as existsSync29, mkdirSync as mkdirSync7, statSync as statSync8 } from "node:fs";
|
|
4289
|
+
import { dirname as dirname8, join as join34, sep as sep4 } from "node:path";
|
|
4040
4290
|
|
|
4041
4291
|
// src/commands.redact.ts
|
|
4042
4292
|
init_config();
|
|
4043
|
-
import { existsSync as
|
|
4044
|
-
import { dirname as dirname7, join as
|
|
4293
|
+
import { existsSync as existsSync28, statSync as statSync7 } from "node:fs";
|
|
4294
|
+
import { dirname as dirname7, join as join33 } from "node:path";
|
|
4045
4295
|
|
|
4046
4296
|
// src/commands.redact.subtree.ts
|
|
4047
|
-
import { existsSync as
|
|
4048
|
-
import { join as
|
|
4297
|
+
import { existsSync as existsSync27, lstatSync as lstatSync10, readFileSync as readFileSync13, readdirSync as readdirSync12, statSync as statSync6, writeFileSync as writeFileSync4 } from "node:fs";
|
|
4298
|
+
import { join as join32 } from "node:path";
|
|
4049
4299
|
init_utils_fs();
|
|
4050
4300
|
init_utils();
|
|
4051
4301
|
function collectFiles(dir, out) {
|
|
4052
|
-
if (!
|
|
4053
|
-
const st =
|
|
4302
|
+
if (!existsSync27(dir)) return;
|
|
4303
|
+
const st = lstatSync10(dir);
|
|
4054
4304
|
if (!st.isDirectory()) return;
|
|
4055
|
-
for (const entry of
|
|
4056
|
-
const abs =
|
|
4057
|
-
const lst =
|
|
4305
|
+
for (const entry of readdirSync12(dir)) {
|
|
4306
|
+
const abs = join32(dir, entry);
|
|
4307
|
+
const lst = lstatSync10(abs);
|
|
4058
4308
|
if (lst.isSymbolicLink()) continue;
|
|
4059
4309
|
if (lst.isDirectory()) {
|
|
4060
4310
|
collectFiles(abs, out);
|
|
@@ -4090,7 +4340,7 @@ function applySubtreeRedactions(mainPath, mainFindings, subtreeFiles, rule, ts,
|
|
|
4090
4340
|
if (!dryRun && total > 0) {
|
|
4091
4341
|
for (const { path: filePath, findings } of dirty) {
|
|
4092
4342
|
backupBeforeWrite(filePath, ts);
|
|
4093
|
-
const before =
|
|
4343
|
+
const before = readFileSync13(filePath, "utf8");
|
|
4094
4344
|
const after = applyRedactions(before, findings);
|
|
4095
4345
|
if (after === before) {
|
|
4096
4346
|
log(
|
|
@@ -4158,15 +4408,15 @@ init_utils_json();
|
|
|
4158
4408
|
init_utils();
|
|
4159
4409
|
function resolveLiveTranscript(id) {
|
|
4160
4410
|
try {
|
|
4161
|
-
const mapPath =
|
|
4162
|
-
if (!
|
|
4411
|
+
const mapPath = join33(repoHome(), "path-map.json");
|
|
4412
|
+
if (!existsSync28(mapPath)) return null;
|
|
4163
4413
|
const projects = readJson(mapPath).projects;
|
|
4164
4414
|
const claude = claudeHome();
|
|
4165
4415
|
for (const hostMap of Object.values(projects)) {
|
|
4166
4416
|
const abs = hostMap[HOST];
|
|
4167
4417
|
if (abs === void 0) continue;
|
|
4168
|
-
const live =
|
|
4169
|
-
if (
|
|
4418
|
+
const live = join33(claude, "projects", encodePath(abs), `${id}.jsonl`);
|
|
4419
|
+
if (existsSync28(live)) return live;
|
|
4170
4420
|
}
|
|
4171
4421
|
return null;
|
|
4172
4422
|
} catch {
|
|
@@ -4186,17 +4436,17 @@ function cmdRedact(opts, nowMs = Date.now, scan = scanFile) {
|
|
|
4186
4436
|
}
|
|
4187
4437
|
const repo = repoHome();
|
|
4188
4438
|
const backup = backupBase();
|
|
4189
|
-
if (!
|
|
4439
|
+
if (!existsSync28(repo)) die(`repo not cloned at ${repo}`);
|
|
4190
4440
|
const handle = acquireLock("redact");
|
|
4191
4441
|
if (handle === null) process.exit(0);
|
|
4192
4442
|
try {
|
|
4193
4443
|
const localPath = resolveLiveTranscript(id);
|
|
4194
|
-
if (localPath === null || !
|
|
4444
|
+
if (localPath === null || !existsSync28(localPath)) {
|
|
4195
4445
|
fail(`could not resolve local transcript for session ${id} on this host`);
|
|
4196
4446
|
process.exitCode = 1;
|
|
4197
4447
|
return;
|
|
4198
4448
|
}
|
|
4199
|
-
const sessionDir =
|
|
4449
|
+
const sessionDir = join33(dirname7(localPath), id);
|
|
4200
4450
|
const subtreeFiles = listSubtreeFiles(sessionDir);
|
|
4201
4451
|
const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync7(p).mtimeMs);
|
|
4202
4452
|
if (isRecentlyModified(subtreeMtime, nowMs())) {
|
|
@@ -4312,8 +4562,8 @@ function resolveStagedDir(localPath, map, claude, repo) {
|
|
|
4312
4562
|
assertSafeLogical(logical);
|
|
4313
4563
|
const abs = hostMap[HOST];
|
|
4314
4564
|
if (abs === void 0) continue;
|
|
4315
|
-
if (localPath.startsWith(
|
|
4316
|
-
return
|
|
4565
|
+
if (localPath.startsWith(join34(claude, "projects", encodePath(abs)) + sep4)) {
|
|
4566
|
+
return join34(repo, "shared", "projects", logical);
|
|
4317
4567
|
}
|
|
4318
4568
|
}
|
|
4319
4569
|
return null;
|
|
@@ -4323,7 +4573,7 @@ function preflightRedactable(f, map, nowMs) {
|
|
|
4323
4573
|
if (sid === null) return "a finding has no resolvable session id (not a session transcript)";
|
|
4324
4574
|
const localPath = resolveLiveTranscript(sid);
|
|
4325
4575
|
if (localPath === null) return `session ${sid}: local transcript not found`;
|
|
4326
|
-
const sessionDir =
|
|
4576
|
+
const sessionDir = join34(dirname8(localPath), sid);
|
|
4327
4577
|
const subtreeFiles = listSubtreeFiles(sessionDir);
|
|
4328
4578
|
const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync8(p).mtimeMs);
|
|
4329
4579
|
if (isRecentlyModified(subtreeMtime, nowMs())) {
|
|
@@ -4353,7 +4603,7 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
|
|
|
4353
4603
|
`could not locate the local transcript for session ${sid}; choose Skip or Drop session.`
|
|
4354
4604
|
);
|
|
4355
4605
|
}
|
|
4356
|
-
const sessionDir =
|
|
4606
|
+
const sessionDir = join34(dirname8(localPath), sid);
|
|
4357
4607
|
const subtreeFiles = listSubtreeFiles(sessionDir);
|
|
4358
4608
|
const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync8(p).mtimeMs);
|
|
4359
4609
|
if (isRecentlyModified(subtreeMtime, nowMs())) {
|
|
@@ -4387,26 +4637,26 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
|
|
|
4387
4637
|
);
|
|
4388
4638
|
}
|
|
4389
4639
|
mkdirSync7(stagedProjectDir, { recursive: true });
|
|
4390
|
-
|
|
4391
|
-
if (
|
|
4392
|
-
|
|
4640
|
+
cpSync6(localPath, join34(stagedProjectDir, `${sid}.jsonl`), { force: true });
|
|
4641
|
+
if (existsSync29(sessionDir)) {
|
|
4642
|
+
cpSync6(sessionDir, join34(stagedProjectDir, sid), { force: true, recursive: true });
|
|
4393
4643
|
}
|
|
4394
4644
|
return true;
|
|
4395
4645
|
}
|
|
4396
4646
|
|
|
4397
4647
|
// src/commands.push.recovery.drop.ts
|
|
4398
4648
|
init_config();
|
|
4399
|
-
import { rmSync as
|
|
4400
|
-
import { join as
|
|
4649
|
+
import { rmSync as rmSync10 } from "node:fs";
|
|
4650
|
+
import { join as join35 } from "node:path";
|
|
4401
4651
|
function dropSessionFromStaged(sid, map) {
|
|
4402
4652
|
const logicals = Object.keys(map.projects);
|
|
4403
4653
|
if (logicals.length === 0) return false;
|
|
4404
4654
|
const repo = repoHome();
|
|
4405
4655
|
for (const logical of logicals) {
|
|
4406
|
-
const jsonl =
|
|
4407
|
-
const dir =
|
|
4408
|
-
|
|
4409
|
-
|
|
4656
|
+
const jsonl = join35(repo, "shared", "projects", logical, `${sid}.jsonl`);
|
|
4657
|
+
const dir = join35(repo, "shared", "projects", logical, sid);
|
|
4658
|
+
rmSync10(jsonl, { force: true });
|
|
4659
|
+
rmSync10(dir, { recursive: true, force: true });
|
|
4410
4660
|
}
|
|
4411
4661
|
return true;
|
|
4412
4662
|
}
|
|
@@ -4440,7 +4690,7 @@ function makeDefaultReadLine(repo) {
|
|
|
4440
4690
|
if (isAbsolute2(file) || target !== repoRoot && !target.startsWith(repoRoot + sep5)) {
|
|
4441
4691
|
return null;
|
|
4442
4692
|
}
|
|
4443
|
-
const content =
|
|
4693
|
+
const content = readFileSync14(target, "utf8");
|
|
4444
4694
|
const lines = content.split(/\r?\n/);
|
|
4445
4695
|
const idx = line - 1;
|
|
4446
4696
|
if (idx < 0 || idx >= lines.length) return null;
|
|
@@ -4561,10 +4811,10 @@ function applyThenRescan(scanVerdict, repoHome2) {
|
|
|
4561
4811
|
return next;
|
|
4562
4812
|
}
|
|
4563
4813
|
function allowThenRescan(append, scanVerdict, repoHome2) {
|
|
4564
|
-
const ignPath =
|
|
4814
|
+
const ignPath = join36(repoHome2, ".gitleaksignore");
|
|
4565
4815
|
let before;
|
|
4566
4816
|
try {
|
|
4567
|
-
before =
|
|
4817
|
+
before = readFileSync15(ignPath, "utf8");
|
|
4568
4818
|
} catch {
|
|
4569
4819
|
before = null;
|
|
4570
4820
|
}
|
|
@@ -4572,7 +4822,7 @@ function allowThenRescan(append, scanVerdict, repoHome2) {
|
|
|
4572
4822
|
try {
|
|
4573
4823
|
return applyThenRescan(scanVerdict, repoHome2);
|
|
4574
4824
|
} catch (err) {
|
|
4575
|
-
if (before === null)
|
|
4825
|
+
if (before === null) rmSync11(ignPath, { force: true });
|
|
4576
4826
|
else writeFileSync5(ignPath, before, "utf8");
|
|
4577
4827
|
throw err;
|
|
4578
4828
|
}
|
|
@@ -4660,7 +4910,7 @@ function writeAnimatedDone(out, label, ms, useTTY) {
|
|
|
4660
4910
|
`);
|
|
4661
4911
|
}
|
|
4662
4912
|
function resolveWorkerPath(deps = {}) {
|
|
4663
|
-
const check = deps.existsSyncFn ??
|
|
4913
|
+
const check = deps.existsSyncFn ?? existsSync30;
|
|
4664
4914
|
const base = deps.baseUrl ?? import.meta.url;
|
|
4665
4915
|
const mjs = fileURLToPath4(new URL("./nomad.worker.mjs", base));
|
|
4666
4916
|
if (check(mjs)) return mjs;
|
|
@@ -4727,8 +4977,8 @@ function withSpinner(label, fn, deps) {
|
|
|
4727
4977
|
// src/commands.doctor.gitleaks-version.ts
|
|
4728
4978
|
init_color();
|
|
4729
4979
|
import { execFileSync as execFileSync11 } from "node:child_process";
|
|
4730
|
-
import { existsSync as
|
|
4731
|
-
import { join as
|
|
4980
|
+
import { existsSync as existsSync31 } from "node:fs";
|
|
4981
|
+
import { join as join37 } from "node:path";
|
|
4732
4982
|
init_config();
|
|
4733
4983
|
var SEMVER_MAJOR_MINOR = /^(\d+)\.(\d+)\.\d+$/;
|
|
4734
4984
|
var GITLEAKS_TIMEOUT_MS = 5e3;
|
|
@@ -4737,7 +4987,7 @@ function majorMinorOf(value) {
|
|
|
4737
4987
|
return m === null ? null : [m[1], m[2]];
|
|
4738
4988
|
}
|
|
4739
4989
|
function readGitleaksVersion(run, tomlExists) {
|
|
4740
|
-
const tomlPath =
|
|
4990
|
+
const tomlPath = join37(repoHome(), ".gitleaks.toml");
|
|
4741
4991
|
const args = ["version"];
|
|
4742
4992
|
if (tomlExists(tomlPath)) args.push("--config", tomlPath);
|
|
4743
4993
|
try {
|
|
@@ -4749,7 +4999,7 @@ function readGitleaksVersion(run, tomlExists) {
|
|
|
4749
4999
|
return null;
|
|
4750
5000
|
}
|
|
4751
5001
|
}
|
|
4752
|
-
function reportGitleaksVersionCheck(section2, run = execFileSync11, tomlExists =
|
|
5002
|
+
function reportGitleaksVersionCheck(section2, run = execFileSync11, tomlExists = existsSync31) {
|
|
4753
5003
|
const raw = readGitleaksVersion(run, tomlExists);
|
|
4754
5004
|
if (raw === null) return;
|
|
4755
5005
|
const local = majorMinorOf(raw);
|
|
@@ -4972,8 +5222,8 @@ function gatherDoctorSections(opts) {
|
|
|
4972
5222
|
reportHostKeyAlignment(host);
|
|
4973
5223
|
reportRepoState(host);
|
|
4974
5224
|
const links = section("Shared links");
|
|
4975
|
-
const mapPath =
|
|
4976
|
-
const rawMap =
|
|
5225
|
+
const mapPath = join38(repoHome(), "path-map.json");
|
|
5226
|
+
const rawMap = existsSync32(mapPath) ? readJsonSafe(mapPath, mapPath, links) : null;
|
|
4977
5227
|
const map = rawMap ?? { projects: {} };
|
|
4978
5228
|
reportSharedLinks(links, map);
|
|
4979
5229
|
reportDroppedNamesMigration(links);
|
|
@@ -5070,8 +5320,8 @@ function parseDoctorArgs(args) {
|
|
|
5070
5320
|
// src/commands.drop-session.ts
|
|
5071
5321
|
init_config();
|
|
5072
5322
|
import { execFileSync as execFileSync16 } from "node:child_process";
|
|
5073
|
-
import { existsSync as
|
|
5074
|
-
import { join as
|
|
5323
|
+
import { existsSync as existsSync34, readdirSync as readdirSync13, statSync as statSync9 } from "node:fs";
|
|
5324
|
+
import { join as join40, relative as relative6 } from "node:path";
|
|
5075
5325
|
|
|
5076
5326
|
// src/commands.drop-session.git.ts
|
|
5077
5327
|
import { execFileSync as execFileSync15 } from "node:child_process";
|
|
@@ -5113,8 +5363,8 @@ function isInIndex(rel, repo) {
|
|
|
5113
5363
|
init_config();
|
|
5114
5364
|
init_utils();
|
|
5115
5365
|
init_utils_json();
|
|
5116
|
-
import { existsSync as
|
|
5117
|
-
import { join as
|
|
5366
|
+
import { existsSync as existsSync33 } from "node:fs";
|
|
5367
|
+
import { join as join39 } from "node:path";
|
|
5118
5368
|
var SHARED_PROJECT_LOGICAL = /^shared\/projects\/([^/]+)\//;
|
|
5119
5369
|
function reportScrubHint(id, matches) {
|
|
5120
5370
|
const live = resolveLiveTranscript2(id, matches);
|
|
@@ -5130,8 +5380,8 @@ function reportScrubHint(id, matches) {
|
|
|
5130
5380
|
}
|
|
5131
5381
|
function resolveLiveTranscript2(id, matches) {
|
|
5132
5382
|
try {
|
|
5133
|
-
const mapPath =
|
|
5134
|
-
if (!
|
|
5383
|
+
const mapPath = join39(repoHome(), "path-map.json");
|
|
5384
|
+
if (!existsSync33(mapPath)) return null;
|
|
5135
5385
|
const projects = readJson(mapPath).projects;
|
|
5136
5386
|
const claude = claudeHome();
|
|
5137
5387
|
for (const rel of matches) {
|
|
@@ -5139,8 +5389,8 @@ function resolveLiveTranscript2(id, matches) {
|
|
|
5139
5389
|
if (logical === void 0) continue;
|
|
5140
5390
|
const abs = projects[logical]?.[HOST];
|
|
5141
5391
|
if (abs === void 0) continue;
|
|
5142
|
-
const live =
|
|
5143
|
-
if (
|
|
5392
|
+
const live = join39(claude, "projects", encodePath(abs), `${id}.jsonl`);
|
|
5393
|
+
if (existsSync33(live)) return live;
|
|
5144
5394
|
}
|
|
5145
5395
|
return null;
|
|
5146
5396
|
} catch {
|
|
@@ -5156,12 +5406,12 @@ function cmdDropSession(id) {
|
|
|
5156
5406
|
process.exit(1);
|
|
5157
5407
|
}
|
|
5158
5408
|
const repo = repoHome();
|
|
5159
|
-
if (!
|
|
5409
|
+
if (!existsSync34(repo)) die(`repo not cloned at ${repo}`);
|
|
5160
5410
|
const handle = acquireLock("drop-session");
|
|
5161
5411
|
if (handle === null) process.exit(0);
|
|
5162
5412
|
try {
|
|
5163
|
-
const repoProjects =
|
|
5164
|
-
if (!
|
|
5413
|
+
const repoProjects = join40(repo, "shared", "projects");
|
|
5414
|
+
if (!existsSync34(repoProjects)) {
|
|
5165
5415
|
throw new NomadFatal(`no staged session matches ${id}`);
|
|
5166
5416
|
}
|
|
5167
5417
|
const matches = collectMatches(repoProjects, id, repo);
|
|
@@ -5183,14 +5433,14 @@ function cmdDropSession(id) {
|
|
|
5183
5433
|
}
|
|
5184
5434
|
function collectMatches(repoProjects, id, repo) {
|
|
5185
5435
|
const matches = [];
|
|
5186
|
-
for (const logical of
|
|
5187
|
-
const candidate =
|
|
5188
|
-
if (
|
|
5189
|
-
matches.push(
|
|
5190
|
-
}
|
|
5191
|
-
const dir =
|
|
5192
|
-
if (
|
|
5193
|
-
const dirRel =
|
|
5436
|
+
for (const logical of readdirSync13(repoProjects)) {
|
|
5437
|
+
const candidate = join40(repoProjects, logical, `${id}.jsonl`);
|
|
5438
|
+
if (existsSync34(candidate)) {
|
|
5439
|
+
matches.push(relative6(repo, candidate));
|
|
5440
|
+
}
|
|
5441
|
+
const dir = join40(repoProjects, logical, id);
|
|
5442
|
+
if (existsSync34(dir) && statSync9(dir).isDirectory()) {
|
|
5443
|
+
const dirRel = relative6(repo, dir);
|
|
5194
5444
|
const staged = expandStagedDir(dirRel, repo);
|
|
5195
5445
|
if (staged.length > 0) matches.push(...staged);
|
|
5196
5446
|
else matches.push(dirRel);
|
|
@@ -5232,7 +5482,7 @@ init_color();
|
|
|
5232
5482
|
|
|
5233
5483
|
// src/summary.ts
|
|
5234
5484
|
init_utils();
|
|
5235
|
-
function summaryText(verb, unmapped, collisions = 0, extrasSkipped = 0) {
|
|
5485
|
+
function summaryText(verb, unmapped, collisions = 0, extrasSkipped = 0, localOnly = 0) {
|
|
5236
5486
|
const extras = extrasSkipped > 0 ? `, ${extrasSkipped} extras skipped` : "";
|
|
5237
5487
|
if (verb === "push") {
|
|
5238
5488
|
if (unmapped === 0 && collisions === 0 && extrasSkipped === 0) {
|
|
@@ -5241,16 +5491,17 @@ function summaryText(verb, unmapped, collisions = 0, extrasSkipped = 0) {
|
|
|
5241
5491
|
const base = `summary: ${unmapped} unmapped on push, ${collisions} collisions`;
|
|
5242
5492
|
return { text: `${base}${extras} (run nomad doctor to list)`, clean: false };
|
|
5243
5493
|
}
|
|
5244
|
-
if (unmapped === 0 && extrasSkipped === 0) {
|
|
5494
|
+
if (unmapped === 0 && extrasSkipped === 0 && localOnly === 0) {
|
|
5245
5495
|
return { text: "summary: clean", clean: true };
|
|
5246
5496
|
}
|
|
5497
|
+
const localOnlyPhrase = localOnly > 0 ? `, ${localOnly} local-only present (push to reconcile)` : "";
|
|
5247
5498
|
return {
|
|
5248
|
-
text: `summary: ${unmapped} unmapped on ${verb}${extras} (run nomad doctor to list)`,
|
|
5499
|
+
text: `summary: ${unmapped} unmapped on ${verb}${extras} (run nomad doctor to list)${localOnlyPhrase}`,
|
|
5249
5500
|
clean: false
|
|
5250
5501
|
};
|
|
5251
5502
|
}
|
|
5252
|
-
function summaryRow(verb, unmapped, collisions = 0, extrasSkipped = 0) {
|
|
5253
|
-
const { text } = summaryText(verb, unmapped, collisions, extrasSkipped);
|
|
5503
|
+
function summaryRow(verb, unmapped, collisions = 0, extrasSkipped = 0, localOnly = 0) {
|
|
5504
|
+
const { text } = summaryText(verb, unmapped, collisions, extrasSkipped, localOnly);
|
|
5254
5505
|
return text.replace(/^summary: /, "");
|
|
5255
5506
|
}
|
|
5256
5507
|
|
|
@@ -5264,11 +5515,17 @@ function buildSettingsSection(label) {
|
|
|
5264
5515
|
addItem(s, `${green(okGlyph)} settings.json (base + ${label})`);
|
|
5265
5516
|
return s;
|
|
5266
5517
|
}
|
|
5267
|
-
function buildSessionsSection(items, unmapped) {
|
|
5518
|
+
function buildSessionsSection(items, unmapped, localOnly = 0) {
|
|
5268
5519
|
const s = section("Sessions");
|
|
5269
5520
|
for (const logical of items) addItem(s, `${green(okGlyph)} ${logical}`);
|
|
5270
5521
|
const skip = collapsedSkipRow(unmapped, "not in path-map (run nomad doctor to list)");
|
|
5271
5522
|
if (skip !== null) addItem(s, skip);
|
|
5523
|
+
if (localOnly > 0) {
|
|
5524
|
+
addItem(
|
|
5525
|
+
s,
|
|
5526
|
+
`${yellow(warnGlyph)} ${localOnly} local-only present, not in repo (push to reconcile)`
|
|
5527
|
+
);
|
|
5528
|
+
}
|
|
5272
5529
|
return s;
|
|
5273
5530
|
}
|
|
5274
5531
|
function buildExtrasSection(items, extrasSkipped) {
|
|
@@ -5322,167 +5579,12 @@ init_config();
|
|
|
5322
5579
|
init_config();
|
|
5323
5580
|
import { existsSync as existsSync36, statSync as statSync10 } from "node:fs";
|
|
5324
5581
|
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
5582
|
init_utils();
|
|
5481
5583
|
init_utils_json();
|
|
5482
5584
|
|
|
5483
5585
|
// src/extras-sync.remap.ts
|
|
5484
5586
|
init_config();
|
|
5485
|
-
import { existsSync as existsSync35, mkdirSync as mkdirSync8, readdirSync as readdirSync14, realpathSync as realpathSync4, rmSync as rmSync12 } from "node:fs";
|
|
5587
|
+
import { existsSync as existsSync35, mkdirSync as mkdirSync8, readdirSync as readdirSync14, readFileSync as readFileSync16, realpathSync as realpathSync4, rmSync as rmSync12 } from "node:fs";
|
|
5486
5588
|
import { dirname as dirname9, join as join42, sep as sep7 } from "node:path";
|
|
5487
5589
|
|
|
5488
5590
|
// src/extras-sync.planning-diff.ts
|
|
@@ -5611,24 +5713,58 @@ function deletePlanningTarget(target, planningRoot, repoCounterpart) {
|
|
|
5611
5713
|
rmSync12(target, { recursive: true, force: true });
|
|
5612
5714
|
pruneEmptyAncestors(target, planningRoot);
|
|
5613
5715
|
}
|
|
5716
|
+
function localDivergesFromPreDelete(target, pre, repoRel, repo) {
|
|
5717
|
+
if (!existsSync35(target)) return false;
|
|
5718
|
+
try {
|
|
5719
|
+
const preBlob = gitCaptureBuffer(["show", `${pre}:${repoRel}`], repo);
|
|
5720
|
+
return !readFileSync16(target).equals(preBlob);
|
|
5721
|
+
} catch {
|
|
5722
|
+
return true;
|
|
5723
|
+
}
|
|
5724
|
+
}
|
|
5725
|
+
function planningDiffArgs(pre, post, logical) {
|
|
5726
|
+
return ["diff", "--name-status", "-z", pre, post, "--", `shared/extras/${logical}/.planning/`];
|
|
5727
|
+
}
|
|
5728
|
+
function deletePairsFor(t, raw) {
|
|
5729
|
+
return planningDeleteTargets({ raw, logical: t.logical, localRoot: t.localRoot }).map(
|
|
5730
|
+
(target) => {
|
|
5731
|
+
const relToLocal = target.slice(t.localRoot.length + sep7.length);
|
|
5732
|
+
return {
|
|
5733
|
+
target,
|
|
5734
|
+
relToLocal,
|
|
5735
|
+
repoRel: `shared/extras/${t.logical}/${relToLocal.split(sep7).join("/")}`
|
|
5736
|
+
};
|
|
5737
|
+
}
|
|
5738
|
+
);
|
|
5739
|
+
}
|
|
5740
|
+
function keptDeleteWarnLine(logical, relToLocal) {
|
|
5741
|
+
return `keeping locally-edited ${relToLocal} in ${logical}: deleted upstream but changed locally (push to reconcile; your copy is backed up)`;
|
|
5742
|
+
}
|
|
5743
|
+
function keptDeletePreview(v, prePostHeads, repo) {
|
|
5744
|
+
const kept = [];
|
|
5745
|
+
for (const t of eachExtrasTarget(v, { unmapped: 0, skipped: 0 })) {
|
|
5746
|
+
if (t.dirname !== ".planning") continue;
|
|
5747
|
+
let raw;
|
|
5748
|
+
try {
|
|
5749
|
+
raw = gitCaptureRaw(planningDiffArgs(prePostHeads.pre, prePostHeads.post, t.logical), repo);
|
|
5750
|
+
} catch {
|
|
5751
|
+
continue;
|
|
5752
|
+
}
|
|
5753
|
+
for (const { target, relToLocal, repoRel } of deletePairsFor(t, raw)) {
|
|
5754
|
+
if (localDivergesFromPreDelete(target, prePostHeads.pre, repoRel, repo)) {
|
|
5755
|
+
kept.push({ logical: t.logical, relToLocal });
|
|
5756
|
+
}
|
|
5757
|
+
}
|
|
5758
|
+
}
|
|
5759
|
+
return kept;
|
|
5760
|
+
}
|
|
5614
5761
|
function propagatePlanningDeletes(v, ts, prePostHeads, repo) {
|
|
5615
5762
|
const repoExtras = join42(repo, "shared", "extras");
|
|
5616
5763
|
for (const t of eachExtrasTarget(v, { unmapped: 0, skipped: 0 })) {
|
|
5617
5764
|
if (t.dirname !== ".planning") continue;
|
|
5618
5765
|
let raw;
|
|
5619
5766
|
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
|
-
);
|
|
5767
|
+
raw = gitCaptureRaw(planningDiffArgs(prePostHeads.pre, prePostHeads.post, t.logical), repo);
|
|
5632
5768
|
} catch (err) {
|
|
5633
5769
|
const e = err;
|
|
5634
5770
|
if (e.stderr) process.stderr.write(e.stderr);
|
|
@@ -5636,12 +5772,15 @@ function propagatePlanningDeletes(v, ts, prePostHeads, repo) {
|
|
|
5636
5772
|
`git diff failed while propagating .planning deletes for ${t.logical}; run nomad pull --force-remote to recover`
|
|
5637
5773
|
);
|
|
5638
5774
|
}
|
|
5639
|
-
const
|
|
5640
|
-
if (
|
|
5775
|
+
const pairs = deletePairsFor(t, raw);
|
|
5776
|
+
if (pairs.length === 0) continue;
|
|
5641
5777
|
backupExtrasWrite(join42(t.localRoot, t.dirname), ts, t.localRoot);
|
|
5642
5778
|
const planningRoot = join42(t.localRoot, ".planning");
|
|
5643
|
-
for (const target of
|
|
5644
|
-
|
|
5779
|
+
for (const { target, relToLocal, repoRel } of pairs) {
|
|
5780
|
+
if (localDivergesFromPreDelete(target, prePostHeads.pre, repoRel, repo)) {
|
|
5781
|
+
warn(keptDeleteWarnLine(t.logical, relToLocal));
|
|
5782
|
+
continue;
|
|
5783
|
+
}
|
|
5645
5784
|
deletePlanningTarget(target, planningRoot, join42(repoExtras, t.logical, relToLocal));
|
|
5646
5785
|
}
|
|
5647
5786
|
}
|
|
@@ -5666,7 +5805,7 @@ function remapExtrasPush(ts, opts = {}) {
|
|
|
5666
5805
|
// Repo-only files survive; local edits propagate (overlay overwrites).
|
|
5667
5806
|
// The filter prevents ALWAYS_NEVER_SYNC files from landing in the repo
|
|
5668
5807
|
// working tree before the allow-list gate fires, eliminating the
|
|
5669
|
-
// "residue wedges repeat push" regression
|
|
5808
|
+
// "residue wedges repeat push" regression. The allow-list gate
|
|
5670
5809
|
// (enforceAllowList / blockSetFor in commands.push.allowlist.ts)
|
|
5671
5810
|
// remains the hard security boundary.
|
|
5672
5811
|
// All others: copyExtrasFiltered with per-extra denylist.
|
|
@@ -5690,23 +5829,30 @@ function remapExtrasPull(ts, opts = {}) {
|
|
|
5690
5829
|
src: join42(repo, "shared", "extras", logical, dirname10),
|
|
5691
5830
|
dst: join42(localRoot, dirname10)
|
|
5692
5831
|
}),
|
|
5693
|
-
// Snapshot the host-side dst BEFORE
|
|
5832
|
+
// Snapshot the host-side dst BEFORE the copy step touches it. Anchor on
|
|
5694
5833
|
// localRoot so the backup tree mirrors the project layout.
|
|
5695
5834
|
(dst, localRoot) => backupExtrasWrite(dst, ts, localRoot),
|
|
5696
5835
|
// Pull routing per extra type:
|
|
5697
5836
|
// `.claude`: copyExtrasFilteredPreserving preserves host-local deny-set
|
|
5698
5837
|
// files (e.g. settings.local.json) while mirror-pruning synced entries.
|
|
5699
|
-
// `.planning`:
|
|
5700
|
-
// keeps local-only files
|
|
5701
|
-
//
|
|
5702
|
-
//
|
|
5703
|
-
//
|
|
5838
|
+
// `.planning`: copyExtrasOverlaySkipDiverged (no rmSync; deny-set filtered)
|
|
5839
|
+
// keeps local-only files AND skips any file whose local copy diverges
|
|
5840
|
+
// from the repo copy (content hash differs), so a local hand-edit wins
|
|
5841
|
+
// on conflict; the delete pass below still propagates
|
|
5842
|
+
// upstream removals via the git-diff D set. The filter is defense-in-
|
|
5843
|
+
// depth against a repo poisoned out-of-band.
|
|
5844
|
+
// All others (a single root-level file, e.g. CLAUDE.md):
|
|
5845
|
+
// copyExtrasFileSkipDiverged keeps a locally-edited file rather than
|
|
5846
|
+
// clobbering it, so the divergence WARN's keep-local promise holds for
|
|
5847
|
+
// every extra, not just `.planning`.
|
|
5704
5848
|
(src, dst, dirname10) => {
|
|
5705
5849
|
if (dirname10 === ".claude")
|
|
5706
5850
|
return copyExtrasFilteredPreserving(src, dst, extrasDenySet(dirname10));
|
|
5707
|
-
if (dirname10 === ".planning")
|
|
5708
|
-
|
|
5709
|
-
|
|
5851
|
+
if (dirname10 === ".planning") {
|
|
5852
|
+
const divergedSet = new Set(listDivergingModified(dst, src));
|
|
5853
|
+
return copyExtrasOverlaySkipDiverged(src, dst, extrasDenySet(dirname10), divergedSet);
|
|
5854
|
+
}
|
|
5855
|
+
return copyExtrasFileSkipDiverged(src, dst);
|
|
5710
5856
|
}
|
|
5711
5857
|
);
|
|
5712
5858
|
if (!dryRun && prePostHeads !== void 0) {
|
|
@@ -5721,39 +5867,46 @@ function divergenceWarnLine(o) {
|
|
|
5721
5867
|
const name = o.isDir ? `${o.dirname}/` : o.dirname;
|
|
5722
5868
|
const one = o.count === 1;
|
|
5723
5869
|
const fileCount = one ? "1 file" : `${o.count} files`;
|
|
5724
|
-
const them = one ? "it" : "them";
|
|
5725
5870
|
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
|
|
5871
|
+
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
5872
|
}
|
|
5728
|
-
function divergenceCheckExtras(ts) {
|
|
5873
|
+
function divergenceCheckExtras(ts, prePostHeads) {
|
|
5729
5874
|
const v = loadValidatedExtras({});
|
|
5730
|
-
if (v === null) return;
|
|
5875
|
+
if (v === null) return 0;
|
|
5731
5876
|
const counts = { unmapped: 0, skipped: 0 };
|
|
5732
5877
|
const backupRoot = join43(backupBase(), ts, "extras");
|
|
5733
5878
|
const repo = repoHome();
|
|
5879
|
+
let divergedCount = 0;
|
|
5734
5880
|
for (const { logical, localRoot, dirname: dirname10 } of eachExtrasTarget(v, counts)) {
|
|
5735
5881
|
const local = join43(localRoot, dirname10);
|
|
5736
5882
|
const repoEntry = join43(repo, "shared", "extras", logical, dirname10);
|
|
5737
5883
|
if (!existsSync36(local) || !existsSync36(repoEntry)) continue;
|
|
5738
|
-
const
|
|
5739
|
-
if (
|
|
5884
|
+
const modified = listDivergingModified(local, repoEntry);
|
|
5885
|
+
if (modified.length === 0) continue;
|
|
5886
|
+
divergedCount += modified.length;
|
|
5740
5887
|
const projectBackupRoot = join43(backupRoot, encodePath(localRoot));
|
|
5741
5888
|
warn(
|
|
5742
5889
|
divergenceWarnLine({
|
|
5743
5890
|
dirname: dirname10,
|
|
5744
5891
|
logical,
|
|
5745
5892
|
isDir: statSync10(local).isDirectory(),
|
|
5746
|
-
count:
|
|
5893
|
+
count: modified.length,
|
|
5747
5894
|
projectBackupRoot
|
|
5748
5895
|
})
|
|
5749
5896
|
);
|
|
5750
|
-
for (const f of
|
|
5897
|
+
for (const f of modified) warn(` ${f}`);
|
|
5751
5898
|
}
|
|
5899
|
+
if (prePostHeads !== void 0) {
|
|
5900
|
+
for (const { logical, relToLocal } of keptDeletePreview(v, prePostHeads, repo)) {
|
|
5901
|
+
warn(keptDeleteWarnLine(logical, relToLocal));
|
|
5902
|
+
}
|
|
5903
|
+
}
|
|
5904
|
+
return divergedCount;
|
|
5752
5905
|
}
|
|
5753
5906
|
|
|
5754
5907
|
// src/skills-sync.ts
|
|
5755
5908
|
init_config();
|
|
5756
|
-
import { existsSync as existsSync37, lstatSync as
|
|
5909
|
+
import { existsSync as existsSync37, lstatSync as lstatSync11, mkdirSync as mkdirSync9, readdirSync as readdirSync15, rmSync as rmSync13 } from "node:fs";
|
|
5757
5910
|
import { join as join44 } from "node:path";
|
|
5758
5911
|
init_utils_fs();
|
|
5759
5912
|
function isGsdOwned(name) {
|
|
@@ -5777,7 +5930,7 @@ function syncSkillsPull(ts) {
|
|
|
5777
5930
|
const sharedSkills = join44(repoHome(), "shared", "skills");
|
|
5778
5931
|
if (!existsSync37(sharedSkills)) return;
|
|
5779
5932
|
const localSkills = join44(claudeHome(), "skills");
|
|
5780
|
-
const dstStat =
|
|
5933
|
+
const dstStat = lstatSync11(localSkills, { throwIfNoEntry: false });
|
|
5781
5934
|
if (dstStat?.isSymbolicLink() === true) {
|
|
5782
5935
|
backupBeforeWrite(localSkills, ts);
|
|
5783
5936
|
rmSync13(localSkills, { recursive: true, force: true });
|
|
@@ -5787,7 +5940,7 @@ function syncSkillsPull(ts) {
|
|
|
5787
5940
|
}
|
|
5788
5941
|
function syncSkillsPush() {
|
|
5789
5942
|
const localSkills = join44(claudeHome(), "skills");
|
|
5790
|
-
const stat =
|
|
5943
|
+
const stat = lstatSync11(localSkills, { throwIfNoEntry: false });
|
|
5791
5944
|
if (stat === void 0) return;
|
|
5792
5945
|
if (stat.isSymbolicLink()) return;
|
|
5793
5946
|
const sharedSkills = join44(repoHome(), "shared", "skills");
|
|
@@ -6153,10 +6306,14 @@ function computePreview(ts, map, verb = "pull") {
|
|
|
6153
6306
|
dryRun: true,
|
|
6154
6307
|
onPreview: (e) => addItem(sessions, formatSessionRow(e))
|
|
6155
6308
|
});
|
|
6309
|
+
const localOnly = scanLocalOnly();
|
|
6310
|
+
if (localOnly > 0) {
|
|
6311
|
+
addItem(sessions, `${localOnly} local-only present, not in repo (push to reconcile)`);
|
|
6312
|
+
}
|
|
6156
6313
|
const summary = section("Summary");
|
|
6157
|
-
addItem(summary, summaryRow(verb, remapResult.unmapped));
|
|
6314
|
+
addItem(summary, summaryRow(verb, remapResult.unmapped, 0, 0, localOnly));
|
|
6158
6315
|
renderTree([links, settingsSection, sessions, summary]);
|
|
6159
|
-
return { unmapped: remapResult.unmapped, collisions: 0 };
|
|
6316
|
+
return { unmapped: remapResult.unmapped, collisions: 0, localOnly };
|
|
6160
6317
|
}
|
|
6161
6318
|
|
|
6162
6319
|
// src/commands.pull.ts
|
|
@@ -6305,23 +6462,33 @@ function capturePrePostHeads(repo, rebase) {
|
|
|
6305
6462
|
if (pre === void 0 || post === void 0) return void 0;
|
|
6306
6463
|
return { pre, post };
|
|
6307
6464
|
}
|
|
6308
|
-
function
|
|
6465
|
+
function buildWetPullSections(ts, map, prePostHeads) {
|
|
6309
6466
|
applySharedLinks(ts, map);
|
|
6310
6467
|
const { label } = regenerateSettings(ts);
|
|
6311
6468
|
syncSkillsPull(ts);
|
|
6312
6469
|
const remapResult = withSpinner("Syncing sessions", () => remapPull(ts));
|
|
6313
6470
|
const extrasResult = remapExtrasPull(ts, { prePostHeads });
|
|
6471
|
+
const localOnly = scanLocalOnly();
|
|
6314
6472
|
const summary = section("Summary");
|
|
6315
6473
|
addItem(
|
|
6316
6474
|
summary,
|
|
6317
|
-
summaryRow(
|
|
6475
|
+
summaryRow(
|
|
6476
|
+
"pull",
|
|
6477
|
+
remapResult.unmapped + extrasResult.unmapped,
|
|
6478
|
+
0,
|
|
6479
|
+
extrasResult.skipped,
|
|
6480
|
+
localOnly
|
|
6481
|
+
)
|
|
6318
6482
|
);
|
|
6319
|
-
|
|
6320
|
-
|
|
6321
|
-
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
|
|
6483
|
+
return {
|
|
6484
|
+
sections: [
|
|
6485
|
+
buildSettingsSection(label),
|
|
6486
|
+
buildSessionsSection(remapResult.pulled, remapResult.unmapped, localOnly),
|
|
6487
|
+
buildExtrasSection(extrasResult.pulled, extrasResult.skipped),
|
|
6488
|
+
summary
|
|
6489
|
+
],
|
|
6490
|
+
localOnly
|
|
6491
|
+
};
|
|
6325
6492
|
}
|
|
6326
6493
|
function handleWedge(repo, forceRemote) {
|
|
6327
6494
|
const wedge = classifyWedge(repo);
|
|
@@ -6341,11 +6508,40 @@ function handleWedge(repo, forceRemote) {
|
|
|
6341
6508
|
const state = wedge === "rebase" ? "mid-rebase" : "mid-merge";
|
|
6342
6509
|
die(wedgeMarkerRunbookText(state));
|
|
6343
6510
|
}
|
|
6344
|
-
function
|
|
6511
|
+
function runPullCore(opts = {}) {
|
|
6345
6512
|
const dryRun = opts.dryRun === true;
|
|
6346
6513
|
const forceRemote = opts.forceRemote === true;
|
|
6347
6514
|
const repo = repoHome();
|
|
6348
6515
|
const backup = backupBase();
|
|
6516
|
+
const ts = freshBackupTs(backup);
|
|
6517
|
+
handleWedge(repo, forceRemote);
|
|
6518
|
+
if (!dryRun) {
|
|
6519
|
+
const backupRoot = join46(backup, ts);
|
|
6520
|
+
try {
|
|
6521
|
+
mkdirSync10(backupRoot, { recursive: true });
|
|
6522
|
+
} catch (err) {
|
|
6523
|
+
die(`could not create backup dir: ${err.message}`);
|
|
6524
|
+
}
|
|
6525
|
+
}
|
|
6526
|
+
log(
|
|
6527
|
+
dryRun ? `pulling on host=${HOST} (backup=${ts}; dry-run)` : `pull on host=${HOST} (backup=${ts})`
|
|
6528
|
+
);
|
|
6529
|
+
const prePostHeads = capturePrePostHeads(repo, () => {
|
|
6530
|
+
gitOrFatal(["pull", "--rebase", "--autostash"], "git pull --rebase", repo);
|
|
6531
|
+
});
|
|
6532
|
+
const mapPath = join46(repo, "path-map.json");
|
|
6533
|
+
const map = existsSync39(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
6534
|
+
const divergedKeptLocal = divergenceCheckExtras(ts, dryRun ? prePostHeads : void 0);
|
|
6535
|
+
if (dryRun) {
|
|
6536
|
+
computePreview(ts, map, "pull");
|
|
6537
|
+
log("dry-run complete; no mutation");
|
|
6538
|
+
return { tag: "dry" };
|
|
6539
|
+
}
|
|
6540
|
+
const { sections, localOnly } = buildWetPullSections(ts, map, prePostHeads);
|
|
6541
|
+
return { tag: "wet", sections, localOnly, divergedKeptLocal };
|
|
6542
|
+
}
|
|
6543
|
+
function cmdPull(opts = {}) {
|
|
6544
|
+
const repo = repoHome();
|
|
6349
6545
|
if (!existsSync39(repo)) die(`repo not cloned at ${repo}`);
|
|
6350
6546
|
if (!existsSync39(join46(repo, "shared", "settings.base.json"))) {
|
|
6351
6547
|
die("repo not initialized; run 'nomad init' to scaffold");
|
|
@@ -6353,31 +6549,8 @@ function cmdPull(opts = {}) {
|
|
|
6353
6549
|
const handle = acquireLock("pull");
|
|
6354
6550
|
if (handle === null) process.exit(0);
|
|
6355
6551
|
try {
|
|
6356
|
-
const
|
|
6357
|
-
|
|
6358
|
-
if (!dryRun) {
|
|
6359
|
-
const backupRoot = join46(backup, ts);
|
|
6360
|
-
try {
|
|
6361
|
-
mkdirSync10(backupRoot, { recursive: true });
|
|
6362
|
-
} catch (err) {
|
|
6363
|
-
die(`could not create backup dir: ${err.message}`);
|
|
6364
|
-
}
|
|
6365
|
-
}
|
|
6366
|
-
log(
|
|
6367
|
-
dryRun ? `pulling on host=${HOST} (backup=${ts}; dry-run)` : `pull on host=${HOST} (backup=${ts})`
|
|
6368
|
-
);
|
|
6369
|
-
const prePostHeads = capturePrePostHeads(repo, () => {
|
|
6370
|
-
gitOrFatal(["pull", "--rebase", "--autostash"], "git pull --rebase", repo);
|
|
6371
|
-
});
|
|
6372
|
-
const mapPath = join46(repo, "path-map.json");
|
|
6373
|
-
const map = existsSync39(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
6374
|
-
divergenceCheckExtras(ts);
|
|
6375
|
-
if (dryRun) {
|
|
6376
|
-
computePreview(ts, map, "pull");
|
|
6377
|
-
log("dry-run complete; no mutation");
|
|
6378
|
-
} else {
|
|
6379
|
-
applyWetPull(ts, map, prePostHeads);
|
|
6380
|
-
}
|
|
6552
|
+
const result = runPullCore(opts);
|
|
6553
|
+
if (result.tag === "wet") renderTree(result.sections);
|
|
6381
6554
|
} catch (err) {
|
|
6382
6555
|
if (err instanceof NomadFatal) {
|
|
6383
6556
|
fail(err.message);
|
|
@@ -6586,12 +6759,12 @@ function reportSettingsAheadDrift(repo) {
|
|
|
6586
6759
|
// src/commands.push.guards.ts
|
|
6587
6760
|
init_push_checks();
|
|
6588
6761
|
init_utils();
|
|
6589
|
-
import { join as join49, relative as
|
|
6762
|
+
import { join as join49, relative as relative7 } from "node:path";
|
|
6590
6763
|
function guardGitlinks(repo) {
|
|
6591
6764
|
const gitlinks = findGitlinks(join49(repo, "shared"));
|
|
6592
6765
|
if (gitlinks.length === 0) return;
|
|
6593
6766
|
for (const p of gitlinks) {
|
|
6594
|
-
const rel =
|
|
6767
|
+
const rel = relative7(repo, p);
|
|
6595
6768
|
fail(`gitlink: ${rel} would push as submodule (run: rm -rf ${rel} or remove the nested repo)`);
|
|
6596
6769
|
}
|
|
6597
6770
|
const noun = gitlinks.length === 1 ? "entry" : "entries";
|
|
@@ -6700,7 +6873,7 @@ init_config_sharedDirs_guard();
|
|
|
6700
6873
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
6701
6874
|
import { copyFileSync, existsSync as existsSync42, mkdirSync as mkdirSync11, readdirSync as readdirSync16, rmSync as rmSync14 } from "node:fs";
|
|
6702
6875
|
import { homedir as homedir5 } from "node:os";
|
|
6703
|
-
import { join as join50, relative as
|
|
6876
|
+
import { join as join50, relative as relative8, sep as sep8 } from "node:path";
|
|
6704
6877
|
init_push_leak_verdict();
|
|
6705
6878
|
init_push_gitleaks();
|
|
6706
6879
|
init_utils_fs();
|
|
@@ -6712,7 +6885,7 @@ function stageSessionDir(localDir, dstDir, changed) {
|
|
|
6712
6885
|
const matching = [...changed].filter((p) => p.startsWith(prefix));
|
|
6713
6886
|
if (matching.length === 0) return false;
|
|
6714
6887
|
for (const src of matching) {
|
|
6715
|
-
copyFileAtomic(src, join50(dstDir,
|
|
6888
|
+
copyFileAtomic(src, join50(dstDir, relative8(localDir, src)));
|
|
6716
6889
|
}
|
|
6717
6890
|
return true;
|
|
6718
6891
|
}
|
|
@@ -6802,7 +6975,7 @@ async function commitAndPush(st, ts, map, resolution, repo, newManifest) {
|
|
|
6802
6975
|
if (staged.length === toDrop.length) {
|
|
6803
6976
|
log("nothing to commit");
|
|
6804
6977
|
renderNoScanTree(st);
|
|
6805
|
-
return;
|
|
6978
|
+
return "nothing";
|
|
6806
6979
|
}
|
|
6807
6980
|
st.globalConfig = collectGlobalConfigChanges(repo, HOST, { staged: true });
|
|
6808
6981
|
let verdict = withSpinner("Scanning for secrets", () => scanPushVerdict(repo));
|
|
@@ -6818,6 +6991,7 @@ async function commitAndPush(st, ts, map, resolution, repo, newManifest) {
|
|
|
6818
6991
|
warn(`could not write push manifest (next push will full-rescan): ${String(err)}`);
|
|
6819
6992
|
}
|
|
6820
6993
|
renderPushTree(st, verdict);
|
|
6994
|
+
return "pushed";
|
|
6821
6995
|
}
|
|
6822
6996
|
function runDryRunPreview(st, map, repo, selection) {
|
|
6823
6997
|
st.globalConfig = collectGlobalConfigChanges(repo, HOST, { staged: false });
|
|
@@ -6834,55 +7008,181 @@ function runDryRunPreview(st, map, repo, selection) {
|
|
|
6834
7008
|
init_push_checks();
|
|
6835
7009
|
init_utils();
|
|
6836
7010
|
init_utils_fs();
|
|
6837
|
-
async function
|
|
7011
|
+
async function runPushCore(opts = {}) {
|
|
6838
7012
|
const dryRun = opts.dryRun === true;
|
|
6839
7013
|
const redactAll = opts.redactAll === true;
|
|
6840
7014
|
const allowAll = opts.allowAll === true;
|
|
6841
7015
|
const allowRule = opts.allowRule;
|
|
6842
7016
|
const fullScan = opts.fullScan === true;
|
|
6843
|
-
guardResolutionModeConflicts(dryRun, redactAll, allowAll, allowRule);
|
|
6844
7017
|
const repo = repoHome();
|
|
6845
7018
|
const backup = backupBase();
|
|
7019
|
+
console.log(dryRun ? `push on host=${HOST} (dry-run)` : `push on host=${HOST}`);
|
|
7020
|
+
reportSettingsAheadDrift(repo);
|
|
7021
|
+
const scannerVersion = probeGitleaks();
|
|
7022
|
+
const configHash = computeConfigHash();
|
|
7023
|
+
const old = readManifest(manifestPath());
|
|
7024
|
+
const mapPath = join51(repo, "path-map.json");
|
|
7025
|
+
const { map, selection, newManifest } = loadSelectionForPush(
|
|
7026
|
+
mapPath,
|
|
7027
|
+
old,
|
|
7028
|
+
scannerVersion,
|
|
7029
|
+
configHash,
|
|
7030
|
+
fullScan
|
|
7031
|
+
);
|
|
7032
|
+
withSpinner("Rebasing onto origin", () => rebaseBeforePush(repo));
|
|
7033
|
+
const ts = freshBackupTs(backup);
|
|
7034
|
+
const remap = withSpinner("Syncing sessions", () => remapPush(ts, { dryRun, selection }));
|
|
7035
|
+
const extras = withSpinner("Syncing extras", () => remapExtrasPush(ts, { dryRun }));
|
|
7036
|
+
if (!dryRun) {
|
|
7037
|
+
syncSkillsPush();
|
|
7038
|
+
stripGsdHooksFromBase(repo, backup);
|
|
7039
|
+
}
|
|
7040
|
+
const st = { dryRun, remap, extras, globalConfig: [] };
|
|
7041
|
+
guardGitlinks(repo);
|
|
7042
|
+
const status = gitStatusPorcelainZ(repo, { untrackedAll: true });
|
|
7043
|
+
if (!dryRun && !status) {
|
|
7044
|
+
log("nothing to commit");
|
|
7045
|
+
renderNoScanTree(st);
|
|
7046
|
+
return { tag: "nothing" };
|
|
7047
|
+
}
|
|
7048
|
+
if (map === null) {
|
|
7049
|
+
if (dryRun) {
|
|
7050
|
+
runDryRunPreview(st, null, repo, selection);
|
|
7051
|
+
return { tag: "dry" };
|
|
7052
|
+
}
|
|
7053
|
+
return die("path-map.json missing, cannot enforce push allow-list");
|
|
7054
|
+
}
|
|
7055
|
+
if (status) enforceAllowList(status, map);
|
|
7056
|
+
if (dryRun) {
|
|
7057
|
+
runDryRunPreview(st, map, repo, selection);
|
|
7058
|
+
return { tag: "dry" };
|
|
7059
|
+
}
|
|
7060
|
+
const outcome = await commitAndPush(
|
|
7061
|
+
st,
|
|
7062
|
+
ts,
|
|
7063
|
+
map,
|
|
7064
|
+
{ redactAll, allowAll, allowRule },
|
|
7065
|
+
repo,
|
|
7066
|
+
newManifest
|
|
7067
|
+
);
|
|
7068
|
+
return outcome === "nothing" ? { tag: "nothing" } : { tag: "pushed" };
|
|
7069
|
+
}
|
|
7070
|
+
async function cmdPush(opts = {}) {
|
|
7071
|
+
const dryRun = opts.dryRun === true;
|
|
7072
|
+
const redactAll = opts.redactAll === true;
|
|
7073
|
+
const allowAll = opts.allowAll === true;
|
|
7074
|
+
const allowRule = opts.allowRule;
|
|
7075
|
+
guardResolutionModeConflicts(dryRun, redactAll, allowAll, allowRule);
|
|
7076
|
+
const repo = repoHome();
|
|
6846
7077
|
if (!existsSync43(repo)) die(`repo not cloned at ${repo}`);
|
|
6847
7078
|
const handle = acquireLock("push");
|
|
6848
7079
|
if (handle === null) process.exit(0);
|
|
6849
7080
|
try {
|
|
6850
|
-
|
|
6851
|
-
|
|
6852
|
-
|
|
6853
|
-
|
|
6854
|
-
|
|
6855
|
-
|
|
6856
|
-
|
|
6857
|
-
|
|
6858
|
-
|
|
6859
|
-
|
|
6860
|
-
|
|
6861
|
-
|
|
6862
|
-
|
|
6863
|
-
|
|
6864
|
-
|
|
6865
|
-
|
|
6866
|
-
|
|
6867
|
-
|
|
6868
|
-
|
|
6869
|
-
|
|
6870
|
-
|
|
6871
|
-
|
|
6872
|
-
|
|
6873
|
-
|
|
6874
|
-
|
|
6875
|
-
|
|
6876
|
-
|
|
6877
|
-
|
|
7081
|
+
await runPushCore(opts);
|
|
7082
|
+
} catch (err) {
|
|
7083
|
+
if (err instanceof NomadFatal) {
|
|
7084
|
+
fail(err.message);
|
|
7085
|
+
process.exitCode = 1;
|
|
7086
|
+
} else {
|
|
7087
|
+
throw err;
|
|
7088
|
+
}
|
|
7089
|
+
} finally {
|
|
7090
|
+
releaseLock(handle);
|
|
7091
|
+
}
|
|
7092
|
+
}
|
|
7093
|
+
|
|
7094
|
+
// src/commands.sync.ts
|
|
7095
|
+
import { existsSync as existsSync44 } from "node:fs";
|
|
7096
|
+
import { join as join52 } from "node:path";
|
|
7097
|
+
init_config();
|
|
7098
|
+
init_utils();
|
|
7099
|
+
init_utils_fs();
|
|
7100
|
+
init_utils_json();
|
|
7101
|
+
function pullHasNoSyncedItems(sections) {
|
|
7102
|
+
return sections.every(
|
|
7103
|
+
(s) => s.header === "Settings" || s.header === "Summary" || s.items.length === 0
|
|
7104
|
+
);
|
|
7105
|
+
}
|
|
7106
|
+
function isNoopSync(pull, pushOutcome) {
|
|
7107
|
+
if (!pushOutcome.ok) return false;
|
|
7108
|
+
if (pull.localOnly !== 0 || pull.divergedKeptLocal !== 0) return false;
|
|
7109
|
+
if (pushOutcome.result.tag !== "nothing") return false;
|
|
7110
|
+
return pullHasNoSyncedItems(pull.sections);
|
|
7111
|
+
}
|
|
7112
|
+
function pullPhrase(pull) {
|
|
7113
|
+
const summary = pull.sections.find((s) => s.header === "Summary");
|
|
7114
|
+
return summary?.items[0] ?? "applied";
|
|
7115
|
+
}
|
|
7116
|
+
function reconciledNotes(pull) {
|
|
7117
|
+
const notes = [];
|
|
7118
|
+
if (pull.divergedKeptLocal > 0) {
|
|
7119
|
+
notes.push(`${pull.divergedKeptLocal} diverged files kept local and pushed`);
|
|
7120
|
+
}
|
|
7121
|
+
if (pull.localOnly > 0) {
|
|
7122
|
+
notes.push(`${pull.localOnly} local-only sessions pushed`);
|
|
7123
|
+
}
|
|
7124
|
+
return notes;
|
|
7125
|
+
}
|
|
7126
|
+
function buildSyncSummarySection(pull, pushOutcome) {
|
|
7127
|
+
const s = section("Summary");
|
|
7128
|
+
if (!pushOutcome.ok) {
|
|
7129
|
+
addItem(s, `pull: applied, push: failed (${pushOutcome.message})`);
|
|
7130
|
+
return s;
|
|
7131
|
+
}
|
|
7132
|
+
addItem(s, `pull: ${pullPhrase(pull)}`);
|
|
7133
|
+
addItem(s, `push: ${pushOutcome.result.tag === "pushed" ? "pushed" : "nothing to push"}`);
|
|
7134
|
+
for (const note of reconciledNotes(pull)) addItem(s, note);
|
|
7135
|
+
return s;
|
|
7136
|
+
}
|
|
7137
|
+
function renderWetSync(pull, pushOutcome) {
|
|
7138
|
+
if (isNoopSync(pull, pushOutcome)) {
|
|
7139
|
+
ok("already in sync");
|
|
7140
|
+
return;
|
|
7141
|
+
}
|
|
7142
|
+
renderTree(pull.sections);
|
|
7143
|
+
renderTree([buildSyncSummarySection(pull, pushOutcome)]);
|
|
7144
|
+
}
|
|
7145
|
+
async function runSyncPushHalf() {
|
|
7146
|
+
try {
|
|
7147
|
+
const result = await runPushCore();
|
|
7148
|
+
return { ok: true, result };
|
|
7149
|
+
} catch (err) {
|
|
7150
|
+
if (err instanceof NomadFatal) {
|
|
7151
|
+
process.exitCode = 1;
|
|
7152
|
+
return { ok: false, message: err.message };
|
|
6878
7153
|
}
|
|
6879
|
-
|
|
6880
|
-
|
|
6881
|
-
|
|
7154
|
+
throw err;
|
|
7155
|
+
}
|
|
7156
|
+
}
|
|
7157
|
+
async function runSyncWet() {
|
|
7158
|
+
const pull = runPullCore();
|
|
7159
|
+
const pushOutcome = await runSyncPushHalf();
|
|
7160
|
+
renderWetSync(pull, pushOutcome);
|
|
7161
|
+
}
|
|
7162
|
+
async function runSyncDryRun(repo, backup) {
|
|
7163
|
+
const ts = freshBackupTs(backup);
|
|
7164
|
+
const mapPath = join52(repo, "path-map.json");
|
|
7165
|
+
const map = existsSync44(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
7166
|
+
computePreview(ts, map, "pull");
|
|
7167
|
+
log("push preview below is computed against pre-pull state (a real sync pushes after pull)");
|
|
7168
|
+
await runPushCore({ dryRun: true });
|
|
7169
|
+
}
|
|
7170
|
+
async function cmdSync(opts = {}) {
|
|
7171
|
+
const dryRun = opts.dryRun === true;
|
|
7172
|
+
const repo = repoHome();
|
|
7173
|
+
const backup = backupBase();
|
|
7174
|
+
if (!existsSync44(repo)) die(`repo not cloned at ${repo}`);
|
|
7175
|
+
if (!existsSync44(join52(repo, "shared", "settings.base.json"))) {
|
|
7176
|
+
die("repo not initialized; run 'nomad init' to scaffold");
|
|
7177
|
+
}
|
|
7178
|
+
const handle = acquireLock("sync");
|
|
7179
|
+
if (handle === null) process.exit(0);
|
|
7180
|
+
try {
|
|
7181
|
+
if (dryRun) {
|
|
7182
|
+
await runSyncDryRun(repo, backup);
|
|
7183
|
+
} else {
|
|
7184
|
+
await runSyncWet();
|
|
6882
7185
|
}
|
|
6883
|
-
if (status) enforceAllowList(status, map);
|
|
6884
|
-
if (dryRun) return runDryRunPreview(st, map, repo, selection);
|
|
6885
|
-
await commitAndPush(st, ts, map, { redactAll, allowAll, allowRule }, repo, newManifest);
|
|
6886
7186
|
} catch (err) {
|
|
6887
7187
|
if (err instanceof NomadFatal) {
|
|
6888
7188
|
fail(err.message);
|
|
@@ -6939,18 +7239,19 @@ init_config();
|
|
|
6939
7239
|
|
|
6940
7240
|
// src/diff.ts
|
|
6941
7241
|
init_config();
|
|
6942
|
-
import { existsSync as
|
|
6943
|
-
import { join as
|
|
7242
|
+
import { existsSync as existsSync45 } from "node:fs";
|
|
7243
|
+
import { join as join53 } from "node:path";
|
|
6944
7244
|
init_utils();
|
|
6945
7245
|
init_utils_fs();
|
|
6946
7246
|
init_utils_json();
|
|
6947
7247
|
function cmdDiff() {
|
|
6948
7248
|
try {
|
|
6949
7249
|
const repo = repoHome();
|
|
6950
|
-
if (!
|
|
7250
|
+
if (!existsSync45(repo)) die(`repo not cloned at ${repo}`);
|
|
6951
7251
|
const ts = freshBackupTs(backupBase());
|
|
6952
|
-
const mapPath =
|
|
6953
|
-
const map =
|
|
7252
|
+
const mapPath = join53(repo, "path-map.json");
|
|
7253
|
+
const map = existsSync45(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
7254
|
+
divergenceCheckExtras(ts);
|
|
6954
7255
|
computePreview(ts, map, "diff");
|
|
6955
7256
|
} catch (err) {
|
|
6956
7257
|
if (err instanceof NomadFatal) {
|
|
@@ -6964,8 +7265,8 @@ function cmdDiff() {
|
|
|
6964
7265
|
|
|
6965
7266
|
// src/init.ts
|
|
6966
7267
|
init_config();
|
|
6967
|
-
import { existsSync as
|
|
6968
|
-
import { join as
|
|
7268
|
+
import { existsSync as existsSync47, mkdirSync as mkdirSync12, writeFileSync as writeFileSync6 } from "node:fs";
|
|
7269
|
+
import { join as join55 } from "node:path";
|
|
6969
7270
|
|
|
6970
7271
|
// src/init.gh-onboard.ts
|
|
6971
7272
|
init_config();
|
|
@@ -7047,33 +7348,33 @@ init_config();
|
|
|
7047
7348
|
init_utils();
|
|
7048
7349
|
init_utils_fs();
|
|
7049
7350
|
init_utils_json();
|
|
7050
|
-
import { copyFileSync as copyFileSync2, cpSync as cpSync7, existsSync as
|
|
7051
|
-
import { join as
|
|
7351
|
+
import { copyFileSync as copyFileSync2, cpSync as cpSync7, existsSync as existsSync46, rmSync as rmSync15, statSync as statSync12 } from "node:fs";
|
|
7352
|
+
import { join as join54 } from "node:path";
|
|
7052
7353
|
function snapshotIntoShared(map) {
|
|
7053
7354
|
const repo = repoHome();
|
|
7054
7355
|
const claude = claudeHome();
|
|
7055
7356
|
for (const name of allSharedLinks(map)) {
|
|
7056
|
-
const src =
|
|
7057
|
-
if (!
|
|
7058
|
-
const dst =
|
|
7357
|
+
const src = join54(claude, name);
|
|
7358
|
+
if (!existsSync46(src)) continue;
|
|
7359
|
+
const dst = join54(repo, "shared", name);
|
|
7059
7360
|
if (statSync12(src).isDirectory()) {
|
|
7060
|
-
const gk =
|
|
7061
|
-
if (
|
|
7361
|
+
const gk = join54(dst, ".gitkeep");
|
|
7362
|
+
if (existsSync46(gk)) rmSync15(gk);
|
|
7062
7363
|
cpSync7(src, dst, { recursive: true, force: false, errorOnExist: true });
|
|
7063
7364
|
} else {
|
|
7064
7365
|
copyFileSync2(src, dst);
|
|
7065
7366
|
}
|
|
7066
7367
|
log(`snapshotted shared/${name} from ${src}`);
|
|
7067
7368
|
}
|
|
7068
|
-
const userSettings =
|
|
7069
|
-
if (
|
|
7369
|
+
const userSettings = join54(claude, "settings.json");
|
|
7370
|
+
if (existsSync46(userSettings)) {
|
|
7070
7371
|
let parsed;
|
|
7071
7372
|
try {
|
|
7072
7373
|
parsed = readJson(userSettings);
|
|
7073
7374
|
} catch (err) {
|
|
7074
7375
|
return die(`malformed ${userSettings}: ${err.message}`);
|
|
7075
7376
|
}
|
|
7076
|
-
const hostFile =
|
|
7377
|
+
const hostFile = join54(repo, "hosts", `${HOST}.json`);
|
|
7077
7378
|
writeJsonAtomic(hostFile, parsed);
|
|
7078
7379
|
log(`snapshotted hosts/${HOST}.json from ${userSettings}`);
|
|
7079
7380
|
}
|
|
@@ -7086,14 +7387,14 @@ var SHARED_CLAUDE_MD = "<!-- claude-nomad shared CLAUDE.md; symlinked into ~/.cl
|
|
|
7086
7387
|
var SHARED_KEEP_DIRS = ["agents", "skills", "commands", "rules", "hooks"];
|
|
7087
7388
|
function preflightConflict(repoHome2) {
|
|
7088
7389
|
const candidates = [
|
|
7089
|
-
|
|
7090
|
-
|
|
7091
|
-
|
|
7092
|
-
|
|
7093
|
-
|
|
7390
|
+
join55(repoHome2, "shared", "settings.base.json"),
|
|
7391
|
+
join55(repoHome2, "shared", "CLAUDE.md"),
|
|
7392
|
+
join55(repoHome2, "path-map.json"),
|
|
7393
|
+
join55(repoHome2, "hosts"),
|
|
7394
|
+
join55(repoHome2, "shared")
|
|
7094
7395
|
];
|
|
7095
7396
|
for (const c of candidates) {
|
|
7096
|
-
if (
|
|
7397
|
+
if (existsSync47(c)) return c;
|
|
7097
7398
|
}
|
|
7098
7399
|
return null;
|
|
7099
7400
|
}
|
|
@@ -7111,25 +7412,25 @@ function cmdInit(opts = {}) {
|
|
|
7111
7412
|
die(`already initialized; refusing to clobber ${conflict}`);
|
|
7112
7413
|
}
|
|
7113
7414
|
ensureOriginRepo(opts.repoName ?? DEFAULT_REPO_NAME, opts.run);
|
|
7114
|
-
mkdirSync12(
|
|
7115
|
-
mkdirSync12(
|
|
7415
|
+
mkdirSync12(join55(repo, "shared"), { recursive: true });
|
|
7416
|
+
mkdirSync12(join55(repo, "hosts"), { recursive: true });
|
|
7116
7417
|
for (const name of SHARED_KEEP_DIRS) {
|
|
7117
|
-
mkdirSync12(
|
|
7418
|
+
mkdirSync12(join55(repo, "shared", name), { recursive: true });
|
|
7118
7419
|
}
|
|
7119
|
-
const userClaudeMd =
|
|
7120
|
-
if (!snapshot || !
|
|
7121
|
-
writeFileSync6(
|
|
7420
|
+
const userClaudeMd = join55(claude, "CLAUDE.md");
|
|
7421
|
+
if (!snapshot || !existsSync47(userClaudeMd)) {
|
|
7422
|
+
writeFileSync6(join55(repo, "shared", "CLAUDE.md"), SHARED_CLAUDE_MD);
|
|
7122
7423
|
item("created shared/CLAUDE.md");
|
|
7123
7424
|
}
|
|
7124
7425
|
for (const name of SHARED_KEEP_DIRS) {
|
|
7125
|
-
writeFileSync6(
|
|
7426
|
+
writeFileSync6(join55(repo, "shared", name, ".gitkeep"), "");
|
|
7126
7427
|
item(`created shared/${name}/.gitkeep`);
|
|
7127
7428
|
}
|
|
7128
|
-
writeFileSync6(
|
|
7429
|
+
writeFileSync6(join55(repo, "hosts", ".gitkeep"), "");
|
|
7129
7430
|
item("created hosts/.gitkeep");
|
|
7130
|
-
writeJsonAtomic(
|
|
7431
|
+
writeJsonAtomic(join55(repo, "shared", "settings.base.json"), {});
|
|
7131
7432
|
item("created shared/settings.base.json");
|
|
7132
|
-
writeJsonAtomic(
|
|
7433
|
+
writeJsonAtomic(join55(repo, "path-map.json"), { projects: {} });
|
|
7133
7434
|
item("created path-map.json");
|
|
7134
7435
|
if (snapshot) {
|
|
7135
7436
|
snapshotIntoShared({ projects: {} });
|
|
@@ -7195,11 +7496,11 @@ function maybeDisableRepoActions(repoHome2, run) {
|
|
|
7195
7496
|
// src/init.prompt.ts
|
|
7196
7497
|
init_config();
|
|
7197
7498
|
init_utils();
|
|
7198
|
-
import { existsSync as
|
|
7199
|
-
import { join as
|
|
7499
|
+
import { existsSync as existsSync48, readdirSync as readdirSync17, statSync as statSync13 } from "node:fs";
|
|
7500
|
+
import { join as join56 } from "node:path";
|
|
7200
7501
|
import { createInterface as createInterface3 } from "node:readline/promises";
|
|
7201
7502
|
function nonEmptyExists(path) {
|
|
7202
|
-
if (!
|
|
7503
|
+
if (!existsSync48(path)) return false;
|
|
7203
7504
|
try {
|
|
7204
7505
|
if (statSync13(path).isDirectory()) return readdirSync17(path).length > 0;
|
|
7205
7506
|
return true;
|
|
@@ -7208,8 +7509,8 @@ function nonEmptyExists(path) {
|
|
|
7208
7509
|
}
|
|
7209
7510
|
}
|
|
7210
7511
|
function hasExistingClaudeConfig(claudeHome2) {
|
|
7211
|
-
if (
|
|
7212
|
-
return SHARED_LINKS.some((name) => nonEmptyExists(
|
|
7512
|
+
if (existsSync48(join56(claudeHome2, "settings.json"))) return true;
|
|
7513
|
+
return SHARED_LINKS.some((name) => nonEmptyExists(join56(claudeHome2, name)));
|
|
7213
7514
|
}
|
|
7214
7515
|
async function confirmSnapshotDefault(claudeHome2) {
|
|
7215
7516
|
if (process.stdin.isTTY !== true || process.stdout.isTTY !== true) {
|
|
@@ -7493,10 +7794,27 @@ function parsePushArgs(argv) {
|
|
|
7493
7794
|
};
|
|
7494
7795
|
}
|
|
7495
7796
|
|
|
7797
|
+
// src/nomad.dispatch.sync.ts
|
|
7798
|
+
function parseSyncArgs(argv) {
|
|
7799
|
+
let dryRun = false;
|
|
7800
|
+
let i = 3;
|
|
7801
|
+
while (i < argv.length) {
|
|
7802
|
+
const token = argv[i];
|
|
7803
|
+
if (token === "--dry-run") {
|
|
7804
|
+
if (dryRun) return null;
|
|
7805
|
+
dryRun = true;
|
|
7806
|
+
} else {
|
|
7807
|
+
return null;
|
|
7808
|
+
}
|
|
7809
|
+
i++;
|
|
7810
|
+
}
|
|
7811
|
+
return { dryRun };
|
|
7812
|
+
}
|
|
7813
|
+
|
|
7496
7814
|
// package.json
|
|
7497
7815
|
var package_default = {
|
|
7498
7816
|
name: "claude-nomad",
|
|
7499
|
-
version: "0.
|
|
7817
|
+
version: "0.57.0",
|
|
7500
7818
|
type: "module",
|
|
7501
7819
|
description: "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
|
|
7502
7820
|
keywords: [
|
|
@@ -7625,6 +7943,12 @@ var DEFAULT_HELP = [
|
|
|
7625
7943
|
cont("when no finding survives. No TTY required. Never skips the scan."),
|
|
7626
7944
|
cont("Mutually exclusive with --redact-all/--allow. Cannot combine with --dry-run."),
|
|
7627
7945
|
"",
|
|
7946
|
+
row(" sync", "Pull (retain-merge), then push, under one lock. One-shot; hides ordering."),
|
|
7947
|
+
row(" --dry-run", "Stack the pull preview then the push preview; mutates nothing."),
|
|
7948
|
+
cont("Fails fast on a pull-half error; a wedged repo hints at nomad pull --force-remote"),
|
|
7949
|
+
cont("(sync itself takes no --force-remote). Mid-push leak recovery behaves exactly"),
|
|
7950
|
+
cont("like nomad push."),
|
|
7951
|
+
"",
|
|
7628
7952
|
row(" diff", "Offline preview of what `pull` would change against local repo state."),
|
|
7629
7953
|
cont("No git pull, no lock acquired."),
|
|
7630
7954
|
"",
|
|
@@ -7721,15 +8045,15 @@ var DEFAULT_HELP = [
|
|
|
7721
8045
|
init_config();
|
|
7722
8046
|
init_utils();
|
|
7723
8047
|
init_utils_json();
|
|
7724
|
-
import { existsSync as
|
|
7725
|
-
import { join as
|
|
8048
|
+
import { existsSync as existsSync49, readFileSync as readFileSync17, readdirSync as readdirSync18 } from "node:fs";
|
|
8049
|
+
import { join as join57 } from "node:path";
|
|
7726
8050
|
function resumeCmd(sessionId) {
|
|
7727
8051
|
if (!/^[A-Za-z0-9_-]+$/.test(sessionId) || sessionId.length > 128) {
|
|
7728
8052
|
fail(`invalid session id: ${sessionId}`);
|
|
7729
8053
|
process.exit(1);
|
|
7730
8054
|
}
|
|
7731
|
-
const projectsRoot =
|
|
7732
|
-
if (!
|
|
8055
|
+
const projectsRoot = join57(claudeHome(), "projects");
|
|
8056
|
+
if (!existsSync49(projectsRoot)) {
|
|
7733
8057
|
fail(`${projectsRoot} does not exist`);
|
|
7734
8058
|
process.exit(1);
|
|
7735
8059
|
}
|
|
@@ -7743,8 +8067,8 @@ function resumeCmd(sessionId) {
|
|
|
7743
8067
|
fail(`no cwd field found in ${jsonlPath}`);
|
|
7744
8068
|
process.exit(1);
|
|
7745
8069
|
}
|
|
7746
|
-
const mapPath =
|
|
7747
|
-
if (!
|
|
8070
|
+
const mapPath = join57(repoHome(), "path-map.json");
|
|
8071
|
+
if (!existsSync49(mapPath)) {
|
|
7748
8072
|
fail("path-map.json missing");
|
|
7749
8073
|
process.exit(1);
|
|
7750
8074
|
}
|
|
@@ -7767,13 +8091,13 @@ function resumeCmd(sessionId) {
|
|
|
7767
8091
|
}
|
|
7768
8092
|
function findTranscriptPath(projectsRoot, sessionId) {
|
|
7769
8093
|
for (const dir of readdirSync18(projectsRoot)) {
|
|
7770
|
-
const candidate =
|
|
7771
|
-
if (
|
|
8094
|
+
const candidate = join57(projectsRoot, dir, `${sessionId}.jsonl`);
|
|
8095
|
+
if (existsSync49(candidate)) return candidate;
|
|
7772
8096
|
}
|
|
7773
8097
|
return null;
|
|
7774
8098
|
}
|
|
7775
8099
|
function extractRecordedCwd(jsonlPath) {
|
|
7776
|
-
for (const line of
|
|
8100
|
+
for (const line of readFileSync17(jsonlPath, "utf8").split("\n")) {
|
|
7777
8101
|
if (!line.trim()) continue;
|
|
7778
8102
|
try {
|
|
7779
8103
|
const obj = JSON.parse(line);
|
|
@@ -7846,6 +8170,15 @@ try {
|
|
|
7846
8170
|
});
|
|
7847
8171
|
break;
|
|
7848
8172
|
}
|
|
8173
|
+
case "sync": {
|
|
8174
|
+
const syncArgs = parseSyncArgs(process.argv);
|
|
8175
|
+
if (syncArgs === null) {
|
|
8176
|
+
console.error("usage: nomad sync [--dry-run]");
|
|
8177
|
+
process.exit(1);
|
|
8178
|
+
}
|
|
8179
|
+
await cmdSync({ dryRun: syncArgs.dryRun });
|
|
8180
|
+
break;
|
|
8181
|
+
}
|
|
7849
8182
|
case "init": {
|
|
7850
8183
|
const initArgs = parseInitArgs(process.argv);
|
|
7851
8184
|
if (initArgs === null) {
|