claude-nomad 0.55.1 → 0.56.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 +18 -0
- package/README.md +7 -1
- package/dist/nomad.mjs +648 -394
- package/package.json +1 -1
package/dist/nomad.mjs
CHANGED
|
@@ -410,6 +410,9 @@ function repoHome() {
|
|
|
410
410
|
function backupBase() {
|
|
411
411
|
return join(home(), ".cache", "claude-nomad", "backup");
|
|
412
412
|
}
|
|
413
|
+
function manifestPath() {
|
|
414
|
+
return join(home(), ".cache", "claude-nomad", `push-manifest-${encodeURIComponent(HOST)}.json`);
|
|
415
|
+
}
|
|
413
416
|
function allSharedLinks(map) {
|
|
414
417
|
const extras = [];
|
|
415
418
|
for (const entry of map.sharedDirs ?? []) {
|
|
@@ -2089,8 +2092,8 @@ function cmdEject(opts = {}, roots = defaultEjectRoots()) {
|
|
|
2089
2092
|
}
|
|
2090
2093
|
|
|
2091
2094
|
// src/commands.doctor.ts
|
|
2092
|
-
import { existsSync as
|
|
2093
|
-
import { join as
|
|
2095
|
+
import { existsSync as existsSync31 } from "node:fs";
|
|
2096
|
+
import { join as join37 } from "node:path";
|
|
2094
2097
|
|
|
2095
2098
|
// src/commands.doctor.checks.repo.ts
|
|
2096
2099
|
init_color();
|
|
@@ -2570,15 +2573,15 @@ function stripSideIndicator(line) {
|
|
|
2570
2573
|
}
|
|
2571
2574
|
function isGsdDiffLine(line, localBase, sharedBase) {
|
|
2572
2575
|
const bare = stripSideIndicator(line);
|
|
2573
|
-
let
|
|
2576
|
+
let relative7;
|
|
2574
2577
|
if (bare.startsWith(localBase + "/")) {
|
|
2575
|
-
|
|
2578
|
+
relative7 = bare.slice(localBase.length + 1);
|
|
2576
2579
|
} else if (bare.startsWith(sharedBase + "/")) {
|
|
2577
|
-
|
|
2580
|
+
relative7 = bare.slice(sharedBase.length + 1);
|
|
2578
2581
|
} else {
|
|
2579
|
-
|
|
2582
|
+
relative7 = bare;
|
|
2580
2583
|
}
|
|
2581
|
-
return
|
|
2584
|
+
return relative7.split("/")[0].startsWith(GSD_PREFIX);
|
|
2582
2585
|
}
|
|
2583
2586
|
function reportSkillsDivergence(section2) {
|
|
2584
2587
|
const sharedSkills = join14(repoHome(), "shared", "skills");
|
|
@@ -2915,9 +2918,9 @@ function reportCheckSchema(section2) {
|
|
|
2915
2918
|
init_color();
|
|
2916
2919
|
import { randomBytes } from "node:crypto";
|
|
2917
2920
|
import { execFileSync as execFileSync9 } from "node:child_process";
|
|
2918
|
-
import { existsSync as
|
|
2921
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync6, readdirSync as readdirSync9, rmSync as rmSync8 } from "node:fs";
|
|
2919
2922
|
import { homedir as homedir4 } from "node:os";
|
|
2920
|
-
import { join as
|
|
2923
|
+
import { join as join25 } from "node:path";
|
|
2921
2924
|
|
|
2922
2925
|
// src/commands.doctor.check-shared.scan.ts
|
|
2923
2926
|
init_color();
|
|
@@ -3014,8 +3017,8 @@ init_config();
|
|
|
3014
3017
|
|
|
3015
3018
|
// src/remap.ts
|
|
3016
3019
|
init_config_sharedDirs_guard();
|
|
3017
|
-
import { cpSync as cpSync4, existsSync as
|
|
3018
|
-
import { join as
|
|
3020
|
+
import { cpSync as cpSync4, existsSync as existsSync19, mkdirSync as mkdirSync5, readdirSync as readdirSync8, renameSync as renameSync3, rmSync as rmSync7, statSync as statSync5 } from "node:fs";
|
|
3021
|
+
import { dirname as dirname4, join as join24, relative as relative3, sep as sep3 } from "node:path";
|
|
3019
3022
|
|
|
3020
3023
|
// src/extras-sync.guards.ts
|
|
3021
3024
|
init_utils();
|
|
@@ -3036,6 +3039,114 @@ function assertSafeLocalRoot(localRoot, logical) {
|
|
|
3036
3039
|
|
|
3037
3040
|
// src/remap.ts
|
|
3038
3041
|
init_config();
|
|
3042
|
+
|
|
3043
|
+
// src/push-manifest.ts
|
|
3044
|
+
init_config();
|
|
3045
|
+
init_push_gitleaks_config();
|
|
3046
|
+
init_utils_fs();
|
|
3047
|
+
import { createHash } from "node:crypto";
|
|
3048
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync4, readdirSync as readdirSync7, readFileSync as readFileSync6, statSync as statSync4 } from "node:fs";
|
|
3049
|
+
import { dirname as dirname3, join as join23 } from "node:path";
|
|
3050
|
+
function isChanged(prev, cur, hash) {
|
|
3051
|
+
if (prev === void 0) return true;
|
|
3052
|
+
if (prev.size !== cur.size) return true;
|
|
3053
|
+
if (prev.mtime === cur.mtime) return false;
|
|
3054
|
+
return prev.hash !== hash();
|
|
3055
|
+
}
|
|
3056
|
+
function diffManifest(old, current, hashFor) {
|
|
3057
|
+
const changed = /* @__PURE__ */ new Set();
|
|
3058
|
+
const deleted = [];
|
|
3059
|
+
if (old === null) {
|
|
3060
|
+
for (const key of Object.keys(current)) {
|
|
3061
|
+
changed.add(key);
|
|
3062
|
+
}
|
|
3063
|
+
return { changed, deleted };
|
|
3064
|
+
}
|
|
3065
|
+
for (const [key, cur] of Object.entries(current)) {
|
|
3066
|
+
const prev = old.files[key];
|
|
3067
|
+
if (isChanged(prev, cur, () => hashFor(key))) {
|
|
3068
|
+
changed.add(key);
|
|
3069
|
+
}
|
|
3070
|
+
}
|
|
3071
|
+
for (const key of Object.keys(old.files)) {
|
|
3072
|
+
if (!(key in current)) {
|
|
3073
|
+
deleted.push(key);
|
|
3074
|
+
}
|
|
3075
|
+
}
|
|
3076
|
+
return { changed, deleted };
|
|
3077
|
+
}
|
|
3078
|
+
function shouldFullRescan(old, scannerVersion, configHash, forceFlag) {
|
|
3079
|
+
if (forceFlag) return true;
|
|
3080
|
+
if (old === null) return true;
|
|
3081
|
+
if (old.scannerVersion !== scannerVersion) return true;
|
|
3082
|
+
if (old.configHash !== configHash) return true;
|
|
3083
|
+
return false;
|
|
3084
|
+
}
|
|
3085
|
+
function hashFile(absPath) {
|
|
3086
|
+
return createHash("sha256").update(readFileSync6(absPath)).digest("hex");
|
|
3087
|
+
}
|
|
3088
|
+
function enumerateDir(dir, results) {
|
|
3089
|
+
for (const entry of readdirSync7(dir)) {
|
|
3090
|
+
const fullPath = join23(dir, entry);
|
|
3091
|
+
const st = statSync4(fullPath);
|
|
3092
|
+
if (st.isDirectory()) {
|
|
3093
|
+
enumerateDir(fullPath, results);
|
|
3094
|
+
} else {
|
|
3095
|
+
results.push(fullPath);
|
|
3096
|
+
}
|
|
3097
|
+
}
|
|
3098
|
+
}
|
|
3099
|
+
function enumerateSourceFiles(projectDir) {
|
|
3100
|
+
const results = [];
|
|
3101
|
+
for (const entry of readdirSync7(projectDir)) {
|
|
3102
|
+
const fullPath = join23(projectDir, entry);
|
|
3103
|
+
const st = statSync4(fullPath);
|
|
3104
|
+
if (st.isDirectory()) {
|
|
3105
|
+
enumerateDir(fullPath, results);
|
|
3106
|
+
} else if (fullPath.endsWith(".jsonl")) {
|
|
3107
|
+
results.push(fullPath);
|
|
3108
|
+
}
|
|
3109
|
+
}
|
|
3110
|
+
return results;
|
|
3111
|
+
}
|
|
3112
|
+
function fileIdentity(p) {
|
|
3113
|
+
if (p === null) return "none::absent";
|
|
3114
|
+
if (!existsSync18(p)) return `${p}::absent`;
|
|
3115
|
+
return `${p}::${createHash("sha256").update(readFileSync6(p)).digest("hex")}`;
|
|
3116
|
+
}
|
|
3117
|
+
function computeConfigHash() {
|
|
3118
|
+
const repo = repoHome();
|
|
3119
|
+
const parts = [
|
|
3120
|
+
fileIdentity(resolveTomlPath(repo)),
|
|
3121
|
+
fileIdentity(join23(repo, ".gitleaks.overlay.toml")),
|
|
3122
|
+
fileIdentity(join23(repo, ".gitleaksignore"))
|
|
3123
|
+
];
|
|
3124
|
+
return createHash("sha256").update(parts.join("\n")).digest("hex");
|
|
3125
|
+
}
|
|
3126
|
+
function isManifestShape(raw) {
|
|
3127
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return false;
|
|
3128
|
+
const m = raw;
|
|
3129
|
+
return m.schema === 1 && typeof m.scannerVersion === "string" && typeof m.configHash === "string" && m.files !== null && typeof m.files === "object" && !Array.isArray(m.files);
|
|
3130
|
+
}
|
|
3131
|
+
function readManifest(path) {
|
|
3132
|
+
try {
|
|
3133
|
+
const raw = readFileSync6(path, "utf8");
|
|
3134
|
+
const parsed = JSON.parse(raw);
|
|
3135
|
+
if (!isManifestShape(parsed)) return null;
|
|
3136
|
+
return parsed;
|
|
3137
|
+
} catch {
|
|
3138
|
+
return null;
|
|
3139
|
+
}
|
|
3140
|
+
}
|
|
3141
|
+
function writeManifest(path, manifest) {
|
|
3142
|
+
mkdirSync4(dirname3(path), { recursive: true });
|
|
3143
|
+
writeJsonAtomic(path, manifest);
|
|
3144
|
+
}
|
|
3145
|
+
function buildManifest(files, scannerVersion, configHash) {
|
|
3146
|
+
return { schema: 1, scannerVersion, configHash, files };
|
|
3147
|
+
}
|
|
3148
|
+
|
|
3149
|
+
// src/remap.ts
|
|
3039
3150
|
init_utils();
|
|
3040
3151
|
init_utils_fs();
|
|
3041
3152
|
init_utils_json();
|
|
@@ -3050,6 +3161,33 @@ function atomicMirror(src, dst, options) {
|
|
|
3050
3161
|
function copyDir(src, dst) {
|
|
3051
3162
|
atomicMirror(src, dst, { recursive: true, force: true });
|
|
3052
3163
|
}
|
|
3164
|
+
function copyFileAtomic(src, dst) {
|
|
3165
|
+
mkdirSync5(dirname4(dst), { recursive: true });
|
|
3166
|
+
const tmp = `${dst}.tmp.${process.pid}`;
|
|
3167
|
+
cpSync4(src, tmp, { force: true, preserveTimestamps: true });
|
|
3168
|
+
renameSync3(tmp, dst);
|
|
3169
|
+
}
|
|
3170
|
+
function hasDeltaForDir(sel, localDir) {
|
|
3171
|
+
const prefix = `${localDir}${sep3}`;
|
|
3172
|
+
for (const p of sel.changed) if (p.startsWith(prefix)) return true;
|
|
3173
|
+
for (const p of sel.deleted) if (p.startsWith(prefix)) return true;
|
|
3174
|
+
return false;
|
|
3175
|
+
}
|
|
3176
|
+
function skipForSelection(sel, localDir) {
|
|
3177
|
+
return sel !== void 0 && !hasDeltaForDir(sel, localDir);
|
|
3178
|
+
}
|
|
3179
|
+
function applySelective(sel, localDir, repoDst) {
|
|
3180
|
+
const prefix = `${localDir}${sep3}`;
|
|
3181
|
+
for (const src of sel.changed) {
|
|
3182
|
+
if (!src.startsWith(prefix)) continue;
|
|
3183
|
+
copyFileAtomic(src, join24(repoDst, relative3(localDir, src)));
|
|
3184
|
+
}
|
|
3185
|
+
for (const src of sel.deleted) {
|
|
3186
|
+
if (!src.startsWith(prefix)) continue;
|
|
3187
|
+
const dst = join24(repoDst, relative3(localDir, src));
|
|
3188
|
+
if (existsSync19(dst)) rmSync7(dst);
|
|
3189
|
+
}
|
|
3190
|
+
}
|
|
3053
3191
|
function copyDirJsonlOnly(src, dst) {
|
|
3054
3192
|
atomicMirror(src, dst, {
|
|
3055
3193
|
recursive: true,
|
|
@@ -3058,7 +3196,7 @@ function copyDirJsonlOnly(src, dst) {
|
|
|
3058
3196
|
const rel = relative3(src, srcPath);
|
|
3059
3197
|
if (rel === "") return true;
|
|
3060
3198
|
if (rel.split(sep3).length > 1) return true;
|
|
3061
|
-
if (
|
|
3199
|
+
if (statSync5(srcPath).isDirectory()) return true;
|
|
3062
3200
|
if (srcPath.endsWith(".jsonl")) return true;
|
|
3063
3201
|
item(`skip ${rel}: extension not in allowlist`);
|
|
3064
3202
|
return false;
|
|
@@ -3076,16 +3214,16 @@ function remapPull(ts, opts = {}) {
|
|
|
3076
3214
|
const wouldPull = [];
|
|
3077
3215
|
const repo = repoHome();
|
|
3078
3216
|
const claude = claudeHome();
|
|
3079
|
-
const mapPath =
|
|
3080
|
-
const repoProjects =
|
|
3081
|
-
if (!
|
|
3217
|
+
const mapPath = join24(repo, "path-map.json");
|
|
3218
|
+
const repoProjects = join24(repo, "shared", "projects");
|
|
3219
|
+
if (!existsSync19(mapPath) || !existsSync19(repoProjects)) {
|
|
3082
3220
|
const text = "no path-map or repo projects dir; skipping session remap";
|
|
3083
3221
|
emitPreview(opts.onPreview, { kind: "note", text }, text);
|
|
3084
3222
|
return { unmapped: 0, pulled, wouldPull };
|
|
3085
3223
|
}
|
|
3086
3224
|
const map = readPathMap(mapPath);
|
|
3087
|
-
const localProjects =
|
|
3088
|
-
if (!dryRun)
|
|
3225
|
+
const localProjects = join24(claude, "projects");
|
|
3226
|
+
if (!dryRun) mkdirSync5(localProjects, { recursive: true });
|
|
3089
3227
|
for (const [logical, hosts] of Object.entries(map.projects)) {
|
|
3090
3228
|
assertSafeLogical(logical);
|
|
3091
3229
|
const localPath = hosts[HOST];
|
|
@@ -3094,9 +3232,9 @@ function remapPull(ts, opts = {}) {
|
|
|
3094
3232
|
continue;
|
|
3095
3233
|
}
|
|
3096
3234
|
assertSafeLocalRoot(localPath, logical);
|
|
3097
|
-
const src =
|
|
3098
|
-
if (!
|
|
3099
|
-
const dst =
|
|
3235
|
+
const src = join24(repoProjects, logical);
|
|
3236
|
+
if (!existsSync19(src)) continue;
|
|
3237
|
+
const dst = join24(localProjects, encodePath(localPath));
|
|
3100
3238
|
if (dryRun) {
|
|
3101
3239
|
wouldPull.push(logical);
|
|
3102
3240
|
emitPreview(
|
|
@@ -3143,31 +3281,37 @@ function remapPush(ts, opts = {}) {
|
|
|
3143
3281
|
const wouldPush = [];
|
|
3144
3282
|
const repo = repoHome();
|
|
3145
3283
|
const claude = claudeHome();
|
|
3146
|
-
const mapPath =
|
|
3147
|
-
if (!
|
|
3284
|
+
const mapPath = join24(repo, "path-map.json");
|
|
3285
|
+
if (!existsSync19(mapPath)) {
|
|
3148
3286
|
log("no path-map.json; skipping session export");
|
|
3149
3287
|
return { unmapped: 0, collisions: 0, pushed, wouldPush };
|
|
3150
3288
|
}
|
|
3151
3289
|
const map = readPathMap(mapPath);
|
|
3152
|
-
const localProjects =
|
|
3153
|
-
const repoProjects =
|
|
3290
|
+
const localProjects = join24(claude, "projects");
|
|
3291
|
+
const repoProjects = join24(repo, "shared", "projects");
|
|
3154
3292
|
const reverse = buildReverseMap(map);
|
|
3155
|
-
if (!
|
|
3156
|
-
if (!dryRun)
|
|
3157
|
-
for (const dir of
|
|
3293
|
+
if (!existsSync19(localProjects)) return { unmapped, collisions: 0, pushed, wouldPush };
|
|
3294
|
+
if (!dryRun) mkdirSync5(repoProjects, { recursive: true });
|
|
3295
|
+
for (const dir of readdirSync8(localProjects)) {
|
|
3158
3296
|
if (dir.endsWith(TMP_SUFFIX)) continue;
|
|
3159
3297
|
const logical = reverse.get(dir);
|
|
3160
3298
|
if (!logical) {
|
|
3161
3299
|
unmapped++;
|
|
3162
3300
|
continue;
|
|
3163
3301
|
}
|
|
3164
|
-
const
|
|
3302
|
+
const localDir = join24(localProjects, dir);
|
|
3303
|
+
const repoDst = join24(repoProjects, logical);
|
|
3304
|
+
if (skipForSelection(opts.selection, localDir)) continue;
|
|
3165
3305
|
if (dryRun) {
|
|
3166
3306
|
wouldPush.push(logical);
|
|
3167
3307
|
continue;
|
|
3168
3308
|
}
|
|
3169
3309
|
backupRepoWrite(repoDst, ts, repo);
|
|
3170
|
-
|
|
3310
|
+
if (opts.selection) {
|
|
3311
|
+
applySelective(opts.selection, localDir, repoDst);
|
|
3312
|
+
} else {
|
|
3313
|
+
copyDirJsonlOnly(localDir, repoDst);
|
|
3314
|
+
}
|
|
3171
3315
|
pushed.push(logical);
|
|
3172
3316
|
}
|
|
3173
3317
|
return { unmapped, collisions: 0, pushed, wouldPush };
|
|
@@ -3180,8 +3324,8 @@ function buildScanTree(tmpRoot) {
|
|
|
3180
3324
|
const logicalToEncoded = /* @__PURE__ */ new Map();
|
|
3181
3325
|
let staged = 0;
|
|
3182
3326
|
const repo = repoHome();
|
|
3183
|
-
const mapPath =
|
|
3184
|
-
if (!
|
|
3327
|
+
const mapPath = join25(repo, "path-map.json");
|
|
3328
|
+
if (!existsSync20(mapPath)) return { logicalToEncoded, staged, malformed: false };
|
|
3185
3329
|
let map;
|
|
3186
3330
|
try {
|
|
3187
3331
|
map = readJson(mapPath);
|
|
@@ -3198,12 +3342,12 @@ function buildScanTree(tmpRoot) {
|
|
|
3198
3342
|
if (!p || p === "TBD") continue;
|
|
3199
3343
|
reverse.set(encodePath(p), logical);
|
|
3200
3344
|
}
|
|
3201
|
-
const localProjects =
|
|
3202
|
-
if (!
|
|
3203
|
-
for (const dir of
|
|
3345
|
+
const localProjects = join25(claudeHome(), "projects");
|
|
3346
|
+
if (!existsSync20(localProjects)) return { logicalToEncoded, staged, malformed: false };
|
|
3347
|
+
for (const dir of readdirSync9(localProjects)) {
|
|
3204
3348
|
const logical = reverse.get(dir);
|
|
3205
3349
|
if (!logical) continue;
|
|
3206
|
-
copyDirJsonlOnly(
|
|
3350
|
+
copyDirJsonlOnly(join25(localProjects, dir), join25(tmpRoot, "shared", "projects", logical));
|
|
3207
3351
|
logicalToEncoded.set(logical, dir);
|
|
3208
3352
|
staged++;
|
|
3209
3353
|
}
|
|
@@ -3234,11 +3378,11 @@ function ensureGitleaksReady(section2, gitleaksReady) {
|
|
|
3234
3378
|
}
|
|
3235
3379
|
function reportCheckShared(section2, gitleaksReady) {
|
|
3236
3380
|
if (!ensureGitleaksReady(section2, gitleaksReady)) return;
|
|
3237
|
-
const cacheDir =
|
|
3238
|
-
|
|
3381
|
+
const cacheDir = join25(homedir4(), ".cache", "claude-nomad");
|
|
3382
|
+
mkdirSync6(cacheDir, { recursive: true });
|
|
3239
3383
|
const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes(4).toString("hex")}`;
|
|
3240
|
-
const reportPath =
|
|
3241
|
-
const tmpRoot =
|
|
3384
|
+
const reportPath = join25(cacheDir, `check-shared-${stamp}.json`);
|
|
3385
|
+
const tmpRoot = join25(cacheDir, `check-shared-tree-${stamp}`);
|
|
3242
3386
|
try {
|
|
3243
3387
|
const { logicalToEncoded, staged, malformed } = buildScanTree(tmpRoot);
|
|
3244
3388
|
if (malformed) {
|
|
@@ -3259,12 +3403,12 @@ function reportCheckShared(section2, gitleaksReady) {
|
|
|
3259
3403
|
|
|
3260
3404
|
// src/commands.doctor.checks.hooks.scope.ts
|
|
3261
3405
|
init_color();
|
|
3262
|
-
import { existsSync as
|
|
3263
|
-
import { dirname as
|
|
3406
|
+
import { existsSync as existsSync21, readFileSync as readFileSync7, readdirSync as readdirSync10, realpathSync as realpathSync2 } from "node:fs";
|
|
3407
|
+
import { dirname as dirname5, extname, join as join26 } from "node:path";
|
|
3264
3408
|
init_config();
|
|
3265
3409
|
function typeFromPackageJson(pkgPath) {
|
|
3266
3410
|
try {
|
|
3267
|
-
const parsed = JSON.parse(
|
|
3411
|
+
const parsed = JSON.parse(readFileSync7(pkgPath, "utf8"));
|
|
3268
3412
|
return parsed.type === "module" ? "esm" : "cjs";
|
|
3269
3413
|
} catch {
|
|
3270
3414
|
return "cjs";
|
|
@@ -3280,11 +3424,11 @@ function effectiveType(hookPath) {
|
|
|
3280
3424
|
} catch {
|
|
3281
3425
|
return null;
|
|
3282
3426
|
}
|
|
3283
|
-
let dir =
|
|
3427
|
+
let dir = dirname5(real);
|
|
3284
3428
|
for (; ; ) {
|
|
3285
|
-
const pkg =
|
|
3286
|
-
if (
|
|
3287
|
-
const parent =
|
|
3429
|
+
const pkg = join26(dir, "package.json");
|
|
3430
|
+
if (existsSync21(pkg)) return typeFromPackageJson(pkg);
|
|
3431
|
+
const parent = dirname5(dir);
|
|
3288
3432
|
if (parent === dir) return "cjs";
|
|
3289
3433
|
dir = parent;
|
|
3290
3434
|
}
|
|
@@ -3312,28 +3456,28 @@ function remedy(family) {
|
|
|
3312
3456
|
}
|
|
3313
3457
|
function safeReaddir2(dir) {
|
|
3314
3458
|
try {
|
|
3315
|
-
return
|
|
3459
|
+
return readdirSync10(dir);
|
|
3316
3460
|
} catch {
|
|
3317
3461
|
return [];
|
|
3318
3462
|
}
|
|
3319
3463
|
}
|
|
3320
3464
|
function safeRead(path) {
|
|
3321
3465
|
try {
|
|
3322
|
-
return
|
|
3466
|
+
return readFileSync7(path, "utf8");
|
|
3323
3467
|
} catch {
|
|
3324
3468
|
return null;
|
|
3325
3469
|
}
|
|
3326
3470
|
}
|
|
3327
3471
|
function reportHookScopeCheck(section2) {
|
|
3328
|
-
const hooksDir =
|
|
3329
|
-
if (!
|
|
3472
|
+
const hooksDir = join26(claudeHome(), "hooks");
|
|
3473
|
+
if (!existsSync21(hooksDir)) {
|
|
3330
3474
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/hooks; skipping module-scope check`);
|
|
3331
3475
|
return;
|
|
3332
3476
|
}
|
|
3333
3477
|
let anyWarn = false;
|
|
3334
3478
|
for (const name of safeReaddir2(hooksDir)) {
|
|
3335
3479
|
if (extname(name) !== ".js") continue;
|
|
3336
|
-
const abs =
|
|
3480
|
+
const abs = join26(hooksDir, name);
|
|
3337
3481
|
const eff = effectiveType(abs);
|
|
3338
3482
|
if (eff === null) continue;
|
|
3339
3483
|
const src = safeRead(abs);
|
|
@@ -3353,8 +3497,8 @@ function reportHookScopeCheck(section2) {
|
|
|
3353
3497
|
|
|
3354
3498
|
// src/commands.doctor.checks.hooks.ts
|
|
3355
3499
|
init_color();
|
|
3356
|
-
import { existsSync as
|
|
3357
|
-
import { join as
|
|
3500
|
+
import { existsSync as existsSync22 } from "node:fs";
|
|
3501
|
+
import { join as join27 } from "node:path";
|
|
3358
3502
|
init_config();
|
|
3359
3503
|
function expandHome(token) {
|
|
3360
3504
|
const h2 = home();
|
|
@@ -3397,7 +3541,7 @@ function checkEventGroups(section2, event, groups) {
|
|
|
3397
3541
|
for (const group of groups) {
|
|
3398
3542
|
for (const cmd of commandsFromGroup(group)) {
|
|
3399
3543
|
for (const resolved of claudePathsIn(cmd)) {
|
|
3400
|
-
if (
|
|
3544
|
+
if (existsSync22(resolved)) continue;
|
|
3401
3545
|
addItem(
|
|
3402
3546
|
section2,
|
|
3403
3547
|
`${red(failGlyph)} hooks/${event}: command target missing: ${resolved} (run nomad pull)`
|
|
@@ -3410,8 +3554,8 @@ function checkEventGroups(section2, event, groups) {
|
|
|
3410
3554
|
return anyFail;
|
|
3411
3555
|
}
|
|
3412
3556
|
function reportHooksTargetCheck(section2) {
|
|
3413
|
-
const settingsPath =
|
|
3414
|
-
if (!
|
|
3557
|
+
const settingsPath = join27(claudeHome(), "settings.json");
|
|
3558
|
+
if (!existsSync22(settingsPath)) {
|
|
3415
3559
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json; skipping hook target check`);
|
|
3416
3560
|
return;
|
|
3417
3561
|
}
|
|
@@ -3434,12 +3578,12 @@ function reportHooksTargetCheck(section2) {
|
|
|
3434
3578
|
|
|
3435
3579
|
// src/commands.doctor.checks.hooks.preserve-symlinks.ts
|
|
3436
3580
|
init_color();
|
|
3437
|
-
import { existsSync as
|
|
3438
|
-
import { join as
|
|
3581
|
+
import { existsSync as existsSync24, readFileSync as readFileSync8 } from "node:fs";
|
|
3582
|
+
import { join as join29 } from "node:path";
|
|
3439
3583
|
|
|
3440
3584
|
// src/commands.doctor.checks.hooks.preserve-symlinks.probe.ts
|
|
3441
|
-
import { closeSync as closeSync3, existsSync as
|
|
3442
|
-
import { dirname as
|
|
3585
|
+
import { closeSync as closeSync3, existsSync as existsSync23, openSync as openSync3, readSync, realpathSync as realpathSync3 } from "node:fs";
|
|
3586
|
+
import { dirname as dirname6, join as join28, resolve as resolve2 } from "node:path";
|
|
3443
3587
|
function suppressedRanges(src) {
|
|
3444
3588
|
const ranges = [];
|
|
3445
3589
|
const re = /\/\*[\s\S]*?\*\/|\/\/[^\n]*|'[^']*'|"[^"]*"|`[^`]*`/g;
|
|
@@ -3471,12 +3615,12 @@ function topRelativeSpecifiers(src) {
|
|
|
3471
3615
|
}
|
|
3472
3616
|
function specifierIsMissing(specifier, baseDir) {
|
|
3473
3617
|
const base = resolve2(baseDir, specifier);
|
|
3474
|
-
if (
|
|
3618
|
+
if (existsSync23(base)) return false;
|
|
3475
3619
|
for (const ext of [".js", ".cjs", ".mjs"]) {
|
|
3476
|
-
if (
|
|
3620
|
+
if (existsSync23(base + ext)) return false;
|
|
3477
3621
|
}
|
|
3478
3622
|
for (const idx of ["index.js", "index.cjs", "index.mjs"]) {
|
|
3479
|
-
if (
|
|
3623
|
+
if (existsSync23(join28(base, idx))) return false;
|
|
3480
3624
|
}
|
|
3481
3625
|
return true;
|
|
3482
3626
|
}
|
|
@@ -3502,7 +3646,7 @@ function relativeRequireTargetsBroken(scriptPath) {
|
|
|
3502
3646
|
}
|
|
3503
3647
|
const specifiers = topRelativeSpecifiers(raw);
|
|
3504
3648
|
if (specifiers.length === 0) return false;
|
|
3505
|
-
const baseDir =
|
|
3649
|
+
const baseDir = dirname6(realPath);
|
|
3506
3650
|
for (const spec of specifiers) {
|
|
3507
3651
|
if (specifierIsMissing(spec, baseDir)) return true;
|
|
3508
3652
|
}
|
|
@@ -3531,10 +3675,10 @@ function commandTokens(command) {
|
|
|
3531
3675
|
return tokens;
|
|
3532
3676
|
}
|
|
3533
3677
|
function readPathMapSafe() {
|
|
3534
|
-
const mapPath =
|
|
3535
|
-
if (!
|
|
3678
|
+
const mapPath = join29(repoHome(), "path-map.json");
|
|
3679
|
+
if (!existsSync24(mapPath)) return { projects: {} };
|
|
3536
3680
|
try {
|
|
3537
|
-
return JSON.parse(
|
|
3681
|
+
return JSON.parse(readFileSync8(mapPath, "utf8"));
|
|
3538
3682
|
} catch {
|
|
3539
3683
|
return { projects: {} };
|
|
3540
3684
|
}
|
|
@@ -3605,8 +3749,8 @@ function checkEventForPreserveSymlinks(section2, event, groups, sharedLinkNames)
|
|
|
3605
3749
|
return anyWarn;
|
|
3606
3750
|
}
|
|
3607
3751
|
function reportPreserveSymlinksCheck(section2) {
|
|
3608
|
-
const settingsPath =
|
|
3609
|
-
if (!
|
|
3752
|
+
const settingsPath = join29(claudeHome(), "settings.json");
|
|
3753
|
+
if (!existsSync24(settingsPath)) {
|
|
3610
3754
|
addItem(
|
|
3611
3755
|
section2,
|
|
3612
3756
|
`${dim(infoGlyph)} no ~/.claude/settings.json; skipping preserve-symlinks-main check`
|
|
@@ -3615,7 +3759,7 @@ function reportPreserveSymlinksCheck(section2) {
|
|
|
3615
3759
|
}
|
|
3616
3760
|
let settings;
|
|
3617
3761
|
try {
|
|
3618
|
-
settings = JSON.parse(
|
|
3762
|
+
settings = JSON.parse(readFileSync8(settingsPath, "utf8"));
|
|
3619
3763
|
} catch {
|
|
3620
3764
|
return;
|
|
3621
3765
|
}
|
|
@@ -3639,8 +3783,8 @@ function reportPreserveSymlinksCheck(section2) {
|
|
|
3639
3783
|
|
|
3640
3784
|
// src/commands.doctor.checks.settings-drift.ts
|
|
3641
3785
|
init_color();
|
|
3642
|
-
import { existsSync as
|
|
3643
|
-
import { join as
|
|
3786
|
+
import { existsSync as existsSync25, readFileSync as readFileSync9 } from "node:fs";
|
|
3787
|
+
import { join as join30 } from "node:path";
|
|
3644
3788
|
init_config();
|
|
3645
3789
|
init_utils_json();
|
|
3646
3790
|
function diffMergedSettings(merged, settings) {
|
|
@@ -3649,7 +3793,7 @@ function diffMergedSettings(merged, settings) {
|
|
|
3649
3793
|
}
|
|
3650
3794
|
function tryReadJson(filePath) {
|
|
3651
3795
|
try {
|
|
3652
|
-
const raw =
|
|
3796
|
+
const raw = readFileSync9(filePath, "utf8");
|
|
3653
3797
|
const parsed = JSON.parse(raw);
|
|
3654
3798
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
3655
3799
|
return parsed;
|
|
@@ -3658,7 +3802,7 @@ function tryReadJson(filePath) {
|
|
|
3658
3802
|
}
|
|
3659
3803
|
}
|
|
3660
3804
|
function reportHooksBaseSelfCleanNote(section2) {
|
|
3661
|
-
const basePath =
|
|
3805
|
+
const basePath = join30(repoHome(), "shared", "settings.base.json");
|
|
3662
3806
|
const base = tryReadJson(basePath);
|
|
3663
3807
|
if (base === null) return;
|
|
3664
3808
|
if (!baseHasGsdHookEntries(base)) return;
|
|
@@ -3671,14 +3815,14 @@ function reportSettingsDriftCheck(section2) {
|
|
|
3671
3815
|
const claude = claudeHome();
|
|
3672
3816
|
const repo = repoHome();
|
|
3673
3817
|
const host = HOST;
|
|
3674
|
-
const settingsPath =
|
|
3675
|
-
const basePath =
|
|
3676
|
-
const hostPath =
|
|
3677
|
-
if (!
|
|
3818
|
+
const settingsPath = join30(claude, "settings.json");
|
|
3819
|
+
const basePath = join30(repo, "shared", "settings.base.json");
|
|
3820
|
+
const hostPath = join30(repo, "hosts", `${host}.json`);
|
|
3821
|
+
if (!existsSync25(settingsPath)) {
|
|
3678
3822
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json; skipping merge-drift check`);
|
|
3679
3823
|
return;
|
|
3680
3824
|
}
|
|
3681
|
-
if (!
|
|
3825
|
+
if (!existsSync25(basePath)) {
|
|
3682
3826
|
addItem(
|
|
3683
3827
|
section2,
|
|
3684
3828
|
`${dim(infoGlyph)} shared/settings.base.json missing; skipping merge-drift check`
|
|
@@ -3697,7 +3841,7 @@ function reportSettingsDriftCheck(section2) {
|
|
|
3697
3841
|
if (settings === null) {
|
|
3698
3842
|
return;
|
|
3699
3843
|
}
|
|
3700
|
-
const hostExists =
|
|
3844
|
+
const hostExists = existsSync25(hostPath);
|
|
3701
3845
|
const hostObj = hostExists ? tryReadJson(hostPath) : null;
|
|
3702
3846
|
if (hostExists && hostObj === null) {
|
|
3703
3847
|
addItem(
|
|
@@ -3746,12 +3890,12 @@ init_config();
|
|
|
3746
3890
|
|
|
3747
3891
|
// src/commands.doctor.engine.ts
|
|
3748
3892
|
init_color();
|
|
3749
|
-
import { readFileSync as
|
|
3893
|
+
import { readFileSync as readFileSync11 } from "node:fs";
|
|
3750
3894
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
3751
3895
|
|
|
3752
3896
|
// src/commands.doctor.version.ts
|
|
3753
3897
|
init_color();
|
|
3754
|
-
import { readFileSync as
|
|
3898
|
+
import { readFileSync as readFileSync10 } from "node:fs";
|
|
3755
3899
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
3756
3900
|
init_config();
|
|
3757
3901
|
var STRICT_SEMVER = /^\d+\.\d+\.\d+$/;
|
|
@@ -3768,7 +3912,7 @@ function compareSemver(a, b) {
|
|
|
3768
3912
|
function readLocalVersion() {
|
|
3769
3913
|
try {
|
|
3770
3914
|
const pkgPath = fileURLToPath2(new URL("../package.json", import.meta.url));
|
|
3771
|
-
const parsed = JSON.parse(
|
|
3915
|
+
const parsed = JSON.parse(readFileSync10(pkgPath, "utf8"));
|
|
3772
3916
|
if (typeof parsed.version === "string" && parsed.version.length > 0) {
|
|
3773
3917
|
return parsed.version;
|
|
3774
3918
|
}
|
|
@@ -3827,7 +3971,7 @@ function parseMinVersion(spec) {
|
|
|
3827
3971
|
function readEnginesNode() {
|
|
3828
3972
|
try {
|
|
3829
3973
|
const pkgPath = fileURLToPath3(new URL("../package.json", import.meta.url));
|
|
3830
|
-
const parsed = JSON.parse(
|
|
3974
|
+
const parsed = JSON.parse(readFileSync11(pkgPath, "utf8"));
|
|
3831
3975
|
const node = parsed.engines?.node;
|
|
3832
3976
|
if (typeof node === "string" && node.length > 0) return node;
|
|
3833
3977
|
return null;
|
|
@@ -3855,43 +3999,43 @@ function reportNodeEngineCheck(section2) {
|
|
|
3855
3999
|
|
|
3856
4000
|
// src/spinner.ts
|
|
3857
4001
|
init_color();
|
|
3858
|
-
import { existsSync as
|
|
4002
|
+
import { existsSync as existsSync29 } from "node:fs";
|
|
3859
4003
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
3860
4004
|
import { Worker } from "node:worker_threads";
|
|
3861
4005
|
|
|
3862
4006
|
// src/commands.push.recovery.ts
|
|
3863
4007
|
init_config();
|
|
3864
|
-
import { readFileSync as
|
|
3865
|
-
import { join as
|
|
4008
|
+
import { readFileSync as readFileSync14, rmSync as rmSync10, writeFileSync as writeFileSync5 } from "node:fs";
|
|
4009
|
+
import { join as join35 } from "node:path";
|
|
3866
4010
|
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
3867
4011
|
|
|
3868
4012
|
// src/commands.push.recovery.actions.ts
|
|
3869
4013
|
init_config();
|
|
3870
|
-
import { readFileSync as
|
|
4014
|
+
import { readFileSync as readFileSync13 } from "node:fs";
|
|
3871
4015
|
import { isAbsolute as isAbsolute2, resolve as resolve3, sep as sep5 } from "node:path";
|
|
3872
4016
|
|
|
3873
4017
|
// src/commands.push.recovery.redact.ts
|
|
3874
4018
|
init_config();
|
|
3875
4019
|
init_config_sharedDirs_guard();
|
|
3876
|
-
import { cpSync as cpSync5, existsSync as
|
|
3877
|
-
import { dirname as
|
|
4020
|
+
import { cpSync as cpSync5, existsSync as existsSync28, mkdirSync as mkdirSync7, statSync as statSync8 } from "node:fs";
|
|
4021
|
+
import { dirname as dirname8, join as join33, sep as sep4 } from "node:path";
|
|
3878
4022
|
|
|
3879
4023
|
// src/commands.redact.ts
|
|
3880
4024
|
init_config();
|
|
3881
|
-
import { existsSync as
|
|
3882
|
-
import { dirname as
|
|
4025
|
+
import { existsSync as existsSync27, statSync as statSync7 } from "node:fs";
|
|
4026
|
+
import { dirname as dirname7, join as join32 } from "node:path";
|
|
3883
4027
|
|
|
3884
4028
|
// src/commands.redact.subtree.ts
|
|
3885
|
-
import { existsSync as
|
|
3886
|
-
import { join as
|
|
4029
|
+
import { existsSync as existsSync26, lstatSync as lstatSync8, readFileSync as readFileSync12, readdirSync as readdirSync11, statSync as statSync6, writeFileSync as writeFileSync4 } from "node:fs";
|
|
4030
|
+
import { join as join31 } from "node:path";
|
|
3887
4031
|
init_utils_fs();
|
|
3888
4032
|
init_utils();
|
|
3889
4033
|
function collectFiles(dir, out) {
|
|
3890
|
-
if (!
|
|
4034
|
+
if (!existsSync26(dir)) return;
|
|
3891
4035
|
const st = lstatSync8(dir);
|
|
3892
4036
|
if (!st.isDirectory()) return;
|
|
3893
|
-
for (const entry of
|
|
3894
|
-
const abs =
|
|
4037
|
+
for (const entry of readdirSync11(dir)) {
|
|
4038
|
+
const abs = join31(dir, entry);
|
|
3895
4039
|
const lst = lstatSync8(abs);
|
|
3896
4040
|
if (lst.isSymbolicLink()) continue;
|
|
3897
4041
|
if (lst.isDirectory()) {
|
|
@@ -3906,7 +4050,7 @@ function listSubtreeFiles(sessionDir) {
|
|
|
3906
4050
|
collectFiles(sessionDir, out);
|
|
3907
4051
|
return out.sort((a, b) => a.localeCompare(b));
|
|
3908
4052
|
}
|
|
3909
|
-
function newestSubtreeMtimeMs(mainPath, subtreeFiles, statMtime = (p) =>
|
|
4053
|
+
function newestSubtreeMtimeMs(mainPath, subtreeFiles, statMtime = (p) => statSync6(p).mtimeMs) {
|
|
3910
4054
|
let newest = statMtime(mainPath);
|
|
3911
4055
|
for (const filePath of subtreeFiles) {
|
|
3912
4056
|
const t = statMtime(filePath);
|
|
@@ -3928,7 +4072,7 @@ function applySubtreeRedactions(mainPath, mainFindings, subtreeFiles, rule, ts,
|
|
|
3928
4072
|
if (!dryRun && total > 0) {
|
|
3929
4073
|
for (const { path: filePath, findings } of dirty) {
|
|
3930
4074
|
backupBeforeWrite(filePath, ts);
|
|
3931
|
-
const before =
|
|
4075
|
+
const before = readFileSync12(filePath, "utf8");
|
|
3932
4076
|
const after = applyRedactions(before, findings);
|
|
3933
4077
|
if (after === before) {
|
|
3934
4078
|
log(
|
|
@@ -3996,15 +4140,15 @@ init_utils_json();
|
|
|
3996
4140
|
init_utils();
|
|
3997
4141
|
function resolveLiveTranscript(id) {
|
|
3998
4142
|
try {
|
|
3999
|
-
const mapPath =
|
|
4000
|
-
if (!
|
|
4143
|
+
const mapPath = join32(repoHome(), "path-map.json");
|
|
4144
|
+
if (!existsSync27(mapPath)) return null;
|
|
4001
4145
|
const projects = readJson(mapPath).projects;
|
|
4002
4146
|
const claude = claudeHome();
|
|
4003
4147
|
for (const hostMap of Object.values(projects)) {
|
|
4004
4148
|
const abs = hostMap[HOST];
|
|
4005
4149
|
if (abs === void 0) continue;
|
|
4006
|
-
const live =
|
|
4007
|
-
if (
|
|
4150
|
+
const live = join32(claude, "projects", encodePath(abs), `${id}.jsonl`);
|
|
4151
|
+
if (existsSync27(live)) return live;
|
|
4008
4152
|
}
|
|
4009
4153
|
return null;
|
|
4010
4154
|
} catch {
|
|
@@ -4024,19 +4168,19 @@ function cmdRedact(opts, nowMs = Date.now, scan = scanFile) {
|
|
|
4024
4168
|
}
|
|
4025
4169
|
const repo = repoHome();
|
|
4026
4170
|
const backup = backupBase();
|
|
4027
|
-
if (!
|
|
4171
|
+
if (!existsSync27(repo)) die(`repo not cloned at ${repo}`);
|
|
4028
4172
|
const handle = acquireLock("redact");
|
|
4029
4173
|
if (handle === null) process.exit(0);
|
|
4030
4174
|
try {
|
|
4031
4175
|
const localPath = resolveLiveTranscript(id);
|
|
4032
|
-
if (localPath === null || !
|
|
4176
|
+
if (localPath === null || !existsSync27(localPath)) {
|
|
4033
4177
|
fail(`could not resolve local transcript for session ${id} on this host`);
|
|
4034
4178
|
process.exitCode = 1;
|
|
4035
4179
|
return;
|
|
4036
4180
|
}
|
|
4037
|
-
const sessionDir =
|
|
4181
|
+
const sessionDir = join32(dirname7(localPath), id);
|
|
4038
4182
|
const subtreeFiles = listSubtreeFiles(sessionDir);
|
|
4039
|
-
const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) =>
|
|
4183
|
+
const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync7(p).mtimeMs);
|
|
4040
4184
|
if (isRecentlyModified(subtreeMtime, nowMs())) {
|
|
4041
4185
|
log(
|
|
4042
4186
|
`session ${id} was modified recently and may be active.
|
|
@@ -4150,8 +4294,8 @@ function resolveStagedDir(localPath, map, claude, repo) {
|
|
|
4150
4294
|
assertSafeLogical(logical);
|
|
4151
4295
|
const abs = hostMap[HOST];
|
|
4152
4296
|
if (abs === void 0) continue;
|
|
4153
|
-
if (localPath.startsWith(
|
|
4154
|
-
return
|
|
4297
|
+
if (localPath.startsWith(join33(claude, "projects", encodePath(abs)) + sep4)) {
|
|
4298
|
+
return join33(repo, "shared", "projects", logical);
|
|
4155
4299
|
}
|
|
4156
4300
|
}
|
|
4157
4301
|
return null;
|
|
@@ -4161,9 +4305,9 @@ function preflightRedactable(f, map, nowMs) {
|
|
|
4161
4305
|
if (sid === null) return "a finding has no resolvable session id (not a session transcript)";
|
|
4162
4306
|
const localPath = resolveLiveTranscript(sid);
|
|
4163
4307
|
if (localPath === null) return `session ${sid}: local transcript not found`;
|
|
4164
|
-
const sessionDir =
|
|
4308
|
+
const sessionDir = join33(dirname8(localPath), sid);
|
|
4165
4309
|
const subtreeFiles = listSubtreeFiles(sessionDir);
|
|
4166
|
-
const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) =>
|
|
4310
|
+
const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync8(p).mtimeMs);
|
|
4167
4311
|
if (isRecentlyModified(subtreeMtime, nowMs())) {
|
|
4168
4312
|
return `session ${sid}: looks active (modified within the last 5 minutes)`;
|
|
4169
4313
|
}
|
|
@@ -4191,9 +4335,9 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
|
|
|
4191
4335
|
`could not locate the local transcript for session ${sid}; choose Skip or Drop session.`
|
|
4192
4336
|
);
|
|
4193
4337
|
}
|
|
4194
|
-
const sessionDir =
|
|
4338
|
+
const sessionDir = join33(dirname8(localPath), sid);
|
|
4195
4339
|
const subtreeFiles = listSubtreeFiles(sessionDir);
|
|
4196
|
-
const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) =>
|
|
4340
|
+
const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync8(p).mtimeMs);
|
|
4197
4341
|
if (isRecentlyModified(subtreeMtime, nowMs())) {
|
|
4198
4342
|
return refuse(
|
|
4199
4343
|
`session ${sid} looks active (modified within the last 5 minutes); refusing to redact, no changes made.
|
|
@@ -4224,10 +4368,10 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
|
|
|
4224
4368
|
`nothing to redact in the local transcript for session ${sid}; choose Skip or Drop session.`
|
|
4225
4369
|
);
|
|
4226
4370
|
}
|
|
4227
|
-
|
|
4228
|
-
cpSync5(localPath,
|
|
4229
|
-
if (
|
|
4230
|
-
cpSync5(sessionDir,
|
|
4371
|
+
mkdirSync7(stagedProjectDir, { recursive: true });
|
|
4372
|
+
cpSync5(localPath, join33(stagedProjectDir, `${sid}.jsonl`), { force: true });
|
|
4373
|
+
if (existsSync28(sessionDir)) {
|
|
4374
|
+
cpSync5(sessionDir, join33(stagedProjectDir, sid), { force: true, recursive: true });
|
|
4231
4375
|
}
|
|
4232
4376
|
return true;
|
|
4233
4377
|
}
|
|
@@ -4235,14 +4379,14 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
|
|
|
4235
4379
|
// src/commands.push.recovery.drop.ts
|
|
4236
4380
|
init_config();
|
|
4237
4381
|
import { rmSync as rmSync9 } from "node:fs";
|
|
4238
|
-
import { join as
|
|
4382
|
+
import { join as join34 } from "node:path";
|
|
4239
4383
|
function dropSessionFromStaged(sid, map) {
|
|
4240
4384
|
const logicals = Object.keys(map.projects);
|
|
4241
4385
|
if (logicals.length === 0) return false;
|
|
4242
4386
|
const repo = repoHome();
|
|
4243
4387
|
for (const logical of logicals) {
|
|
4244
|
-
const jsonl =
|
|
4245
|
-
const dir =
|
|
4388
|
+
const jsonl = join34(repo, "shared", "projects", logical, `${sid}.jsonl`);
|
|
4389
|
+
const dir = join34(repo, "shared", "projects", logical, sid);
|
|
4246
4390
|
rmSync9(jsonl, { force: true });
|
|
4247
4391
|
rmSync9(dir, { recursive: true, force: true });
|
|
4248
4392
|
}
|
|
@@ -4278,7 +4422,7 @@ function makeDefaultReadLine(repo) {
|
|
|
4278
4422
|
if (isAbsolute2(file) || target !== repoRoot && !target.startsWith(repoRoot + sep5)) {
|
|
4279
4423
|
return null;
|
|
4280
4424
|
}
|
|
4281
|
-
const content =
|
|
4425
|
+
const content = readFileSync13(target, "utf8");
|
|
4282
4426
|
const lines = content.split(/\r?\n/);
|
|
4283
4427
|
const idx = line - 1;
|
|
4284
4428
|
if (idx < 0 || idx >= lines.length) return null;
|
|
@@ -4399,10 +4543,10 @@ function applyThenRescan(scanVerdict, repoHome2) {
|
|
|
4399
4543
|
return next;
|
|
4400
4544
|
}
|
|
4401
4545
|
function allowThenRescan(append, scanVerdict, repoHome2) {
|
|
4402
|
-
const ignPath =
|
|
4546
|
+
const ignPath = join35(repoHome2, ".gitleaksignore");
|
|
4403
4547
|
let before;
|
|
4404
4548
|
try {
|
|
4405
|
-
before =
|
|
4549
|
+
before = readFileSync14(ignPath, "utf8");
|
|
4406
4550
|
} catch {
|
|
4407
4551
|
before = null;
|
|
4408
4552
|
}
|
|
@@ -4498,7 +4642,7 @@ function writeAnimatedDone(out, label, ms, useTTY) {
|
|
|
4498
4642
|
`);
|
|
4499
4643
|
}
|
|
4500
4644
|
function resolveWorkerPath(deps = {}) {
|
|
4501
|
-
const check = deps.existsSyncFn ??
|
|
4645
|
+
const check = deps.existsSyncFn ?? existsSync29;
|
|
4502
4646
|
const base = deps.baseUrl ?? import.meta.url;
|
|
4503
4647
|
const mjs = fileURLToPath4(new URL("./nomad.worker.mjs", base));
|
|
4504
4648
|
if (check(mjs)) return mjs;
|
|
@@ -4565,8 +4709,8 @@ function withSpinner(label, fn, deps) {
|
|
|
4565
4709
|
// src/commands.doctor.gitleaks-version.ts
|
|
4566
4710
|
init_color();
|
|
4567
4711
|
import { execFileSync as execFileSync11 } from "node:child_process";
|
|
4568
|
-
import { existsSync as
|
|
4569
|
-
import { join as
|
|
4712
|
+
import { existsSync as existsSync30 } from "node:fs";
|
|
4713
|
+
import { join as join36 } from "node:path";
|
|
4570
4714
|
init_config();
|
|
4571
4715
|
var SEMVER_MAJOR_MINOR = /^(\d+)\.(\d+)\.\d+$/;
|
|
4572
4716
|
var GITLEAKS_TIMEOUT_MS = 5e3;
|
|
@@ -4575,7 +4719,7 @@ function majorMinorOf(value) {
|
|
|
4575
4719
|
return m === null ? null : [m[1], m[2]];
|
|
4576
4720
|
}
|
|
4577
4721
|
function readGitleaksVersion(run, tomlExists) {
|
|
4578
|
-
const tomlPath =
|
|
4722
|
+
const tomlPath = join36(repoHome(), ".gitleaks.toml");
|
|
4579
4723
|
const args = ["version"];
|
|
4580
4724
|
if (tomlExists(tomlPath)) args.push("--config", tomlPath);
|
|
4581
4725
|
try {
|
|
@@ -4587,7 +4731,7 @@ function readGitleaksVersion(run, tomlExists) {
|
|
|
4587
4731
|
return null;
|
|
4588
4732
|
}
|
|
4589
4733
|
}
|
|
4590
|
-
function reportGitleaksVersionCheck(section2, run = execFileSync11, tomlExists =
|
|
4734
|
+
function reportGitleaksVersionCheck(section2, run = execFileSync11, tomlExists = existsSync30) {
|
|
4591
4735
|
const raw = readGitleaksVersion(run, tomlExists);
|
|
4592
4736
|
if (raw === null) return;
|
|
4593
4737
|
const local = majorMinorOf(raw);
|
|
@@ -4810,8 +4954,8 @@ function gatherDoctorSections(opts) {
|
|
|
4810
4954
|
reportHostKeyAlignment(host);
|
|
4811
4955
|
reportRepoState(host);
|
|
4812
4956
|
const links = section("Shared links");
|
|
4813
|
-
const mapPath =
|
|
4814
|
-
const rawMap =
|
|
4957
|
+
const mapPath = join37(repoHome(), "path-map.json");
|
|
4958
|
+
const rawMap = existsSync31(mapPath) ? readJsonSafe(mapPath, mapPath, links) : null;
|
|
4815
4959
|
const map = rawMap ?? { projects: {} };
|
|
4816
4960
|
reportSharedLinks(links, map);
|
|
4817
4961
|
reportDroppedNamesMigration(links);
|
|
@@ -4908,8 +5052,8 @@ function parseDoctorArgs(args) {
|
|
|
4908
5052
|
// src/commands.drop-session.ts
|
|
4909
5053
|
init_config();
|
|
4910
5054
|
import { execFileSync as execFileSync16 } from "node:child_process";
|
|
4911
|
-
import { existsSync as
|
|
4912
|
-
import { join as
|
|
5055
|
+
import { existsSync as existsSync33, readdirSync as readdirSync12, statSync as statSync9 } from "node:fs";
|
|
5056
|
+
import { join as join39, relative as relative4 } from "node:path";
|
|
4913
5057
|
|
|
4914
5058
|
// src/commands.drop-session.git.ts
|
|
4915
5059
|
import { execFileSync as execFileSync15 } from "node:child_process";
|
|
@@ -4951,8 +5095,8 @@ function isInIndex(rel, repo) {
|
|
|
4951
5095
|
init_config();
|
|
4952
5096
|
init_utils();
|
|
4953
5097
|
init_utils_json();
|
|
4954
|
-
import { existsSync as
|
|
4955
|
-
import { join as
|
|
5098
|
+
import { existsSync as existsSync32 } from "node:fs";
|
|
5099
|
+
import { join as join38 } from "node:path";
|
|
4956
5100
|
var SHARED_PROJECT_LOGICAL = /^shared\/projects\/([^/]+)\//;
|
|
4957
5101
|
function reportScrubHint(id, matches) {
|
|
4958
5102
|
const live = resolveLiveTranscript2(id, matches);
|
|
@@ -4968,8 +5112,8 @@ function reportScrubHint(id, matches) {
|
|
|
4968
5112
|
}
|
|
4969
5113
|
function resolveLiveTranscript2(id, matches) {
|
|
4970
5114
|
try {
|
|
4971
|
-
const mapPath =
|
|
4972
|
-
if (!
|
|
5115
|
+
const mapPath = join38(repoHome(), "path-map.json");
|
|
5116
|
+
if (!existsSync32(mapPath)) return null;
|
|
4973
5117
|
const projects = readJson(mapPath).projects;
|
|
4974
5118
|
const claude = claudeHome();
|
|
4975
5119
|
for (const rel of matches) {
|
|
@@ -4977,8 +5121,8 @@ function resolveLiveTranscript2(id, matches) {
|
|
|
4977
5121
|
if (logical === void 0) continue;
|
|
4978
5122
|
const abs = projects[logical]?.[HOST];
|
|
4979
5123
|
if (abs === void 0) continue;
|
|
4980
|
-
const live =
|
|
4981
|
-
if (
|
|
5124
|
+
const live = join38(claude, "projects", encodePath(abs), `${id}.jsonl`);
|
|
5125
|
+
if (existsSync32(live)) return live;
|
|
4982
5126
|
}
|
|
4983
5127
|
return null;
|
|
4984
5128
|
} catch {
|
|
@@ -4994,12 +5138,12 @@ function cmdDropSession(id) {
|
|
|
4994
5138
|
process.exit(1);
|
|
4995
5139
|
}
|
|
4996
5140
|
const repo = repoHome();
|
|
4997
|
-
if (!
|
|
5141
|
+
if (!existsSync33(repo)) die(`repo not cloned at ${repo}`);
|
|
4998
5142
|
const handle = acquireLock("drop-session");
|
|
4999
5143
|
if (handle === null) process.exit(0);
|
|
5000
5144
|
try {
|
|
5001
|
-
const repoProjects =
|
|
5002
|
-
if (!
|
|
5145
|
+
const repoProjects = join39(repo, "shared", "projects");
|
|
5146
|
+
if (!existsSync33(repoProjects)) {
|
|
5003
5147
|
throw new NomadFatal(`no staged session matches ${id}`);
|
|
5004
5148
|
}
|
|
5005
5149
|
const matches = collectMatches(repoProjects, id, repo);
|
|
@@ -5021,13 +5165,13 @@ function cmdDropSession(id) {
|
|
|
5021
5165
|
}
|
|
5022
5166
|
function collectMatches(repoProjects, id, repo) {
|
|
5023
5167
|
const matches = [];
|
|
5024
|
-
for (const logical of
|
|
5025
|
-
const candidate =
|
|
5026
|
-
if (
|
|
5168
|
+
for (const logical of readdirSync12(repoProjects)) {
|
|
5169
|
+
const candidate = join39(repoProjects, logical, `${id}.jsonl`);
|
|
5170
|
+
if (existsSync33(candidate)) {
|
|
5027
5171
|
matches.push(relative4(repo, candidate));
|
|
5028
5172
|
}
|
|
5029
|
-
const dir =
|
|
5030
|
-
if (
|
|
5173
|
+
const dir = join39(repoProjects, logical, id);
|
|
5174
|
+
if (existsSync33(dir) && statSync9(dir).isDirectory()) {
|
|
5031
5175
|
const dirRel = relative4(repo, dir);
|
|
5032
5176
|
const staged = expandStagedDir(dirRel, repo);
|
|
5033
5177
|
if (staged.length > 0) matches.push(...staged);
|
|
@@ -5062,8 +5206,8 @@ function unstageOne(rel, repo) {
|
|
|
5062
5206
|
}
|
|
5063
5207
|
|
|
5064
5208
|
// src/commands.pull.ts
|
|
5065
|
-
import { existsSync as
|
|
5066
|
-
import { join as
|
|
5209
|
+
import { existsSync as existsSync39, mkdirSync as mkdirSync10 } from "node:fs";
|
|
5210
|
+
import { join as join46 } from "node:path";
|
|
5067
5211
|
|
|
5068
5212
|
// src/commands.push.sections.ts
|
|
5069
5213
|
init_color();
|
|
@@ -5158,20 +5302,20 @@ init_config();
|
|
|
5158
5302
|
|
|
5159
5303
|
// src/extras-sync.ts
|
|
5160
5304
|
init_config();
|
|
5161
|
-
import { existsSync as
|
|
5162
|
-
import { join as
|
|
5305
|
+
import { existsSync as existsSync36, statSync as statSync10 } from "node:fs";
|
|
5306
|
+
import { join as join43 } from "node:path";
|
|
5163
5307
|
|
|
5164
5308
|
// src/extras-sync.core.ts
|
|
5165
5309
|
init_config();
|
|
5166
|
-
import { cpSync as cpSync6, existsSync as
|
|
5167
|
-
import { basename, join as
|
|
5310
|
+
import { cpSync as cpSync6, existsSync as existsSync34, lstatSync as lstatSync9, readdirSync as readdirSync13, rmSync as rmSync11 } from "node:fs";
|
|
5311
|
+
import { basename, join as join40 } from "node:path";
|
|
5168
5312
|
init_utils();
|
|
5169
5313
|
init_utils_json();
|
|
5170
5314
|
function loadValidatedExtras(opts) {
|
|
5171
5315
|
const repo = repoHome();
|
|
5172
|
-
const mapPath =
|
|
5173
|
-
const repoExtras =
|
|
5174
|
-
if (!
|
|
5316
|
+
const mapPath = join40(repo, "path-map.json");
|
|
5317
|
+
const repoExtras = join40(repo, "shared", "extras");
|
|
5318
|
+
if (!existsSync34(mapPath) || opts.requireRepoExtras === true && !existsSync34(repoExtras)) {
|
|
5175
5319
|
if (opts.missingMsg !== void 0) log(opts.missingMsg);
|
|
5176
5320
|
return null;
|
|
5177
5321
|
}
|
|
@@ -5193,26 +5337,26 @@ function* eachExtrasTarget(v, counts) {
|
|
|
5193
5337
|
counts.unmapped++;
|
|
5194
5338
|
continue;
|
|
5195
5339
|
}
|
|
5196
|
-
for (const
|
|
5197
|
-
if (!whitelist.includes(
|
|
5340
|
+
for (const dirname10 of dirnames) {
|
|
5341
|
+
if (!whitelist.includes(dirname10)) {
|
|
5198
5342
|
counts.skipped++;
|
|
5199
5343
|
continue;
|
|
5200
5344
|
}
|
|
5201
|
-
yield { logical, localRoot, dirname:
|
|
5345
|
+
yield { logical, localRoot, dirname: dirname10 };
|
|
5202
5346
|
}
|
|
5203
5347
|
}
|
|
5204
5348
|
}
|
|
5205
5349
|
function stripCollidingDstSymlinks(src, dst, isExcluded) {
|
|
5206
|
-
if (!
|
|
5207
|
-
for (const name of
|
|
5350
|
+
if (!existsSync34(dst)) return;
|
|
5351
|
+
for (const name of readdirSync13(src)) {
|
|
5208
5352
|
if (isExcluded(name)) continue;
|
|
5209
|
-
const dstPath =
|
|
5353
|
+
const dstPath = join40(dst, name);
|
|
5210
5354
|
const dstStat = lstatSync9(dstPath, { throwIfNoEntry: false });
|
|
5211
5355
|
if (dstStat === void 0) continue;
|
|
5212
5356
|
if (dstStat.isSymbolicLink()) {
|
|
5213
5357
|
rmSync11(dstPath, { recursive: true, force: true });
|
|
5214
|
-
} else if (dstStat.isDirectory() && lstatSync9(
|
|
5215
|
-
stripCollidingDstSymlinks(
|
|
5358
|
+
} else if (dstStat.isDirectory() && lstatSync9(join40(src, name)).isDirectory()) {
|
|
5359
|
+
stripCollidingDstSymlinks(join40(src, name), dstPath, isExcluded);
|
|
5216
5360
|
}
|
|
5217
5361
|
}
|
|
5218
5362
|
}
|
|
@@ -5239,8 +5383,8 @@ function copyExtras(src, dst) {
|
|
|
5239
5383
|
rmSync11(dst, { recursive: true, force: true });
|
|
5240
5384
|
cpSync6(src, dst, { recursive: true, force: true, verbatimSymlinks: true });
|
|
5241
5385
|
}
|
|
5242
|
-
function extrasDenySet(
|
|
5243
|
-
return
|
|
5386
|
+
function extrasDenySet(dirname10) {
|
|
5387
|
+
return dirname10 === ".claude" ? CLAUDE_EXTRA_NEVER_SYNC : ALWAYS_NEVER_SYNC;
|
|
5244
5388
|
}
|
|
5245
5389
|
function copyExtrasFiltered(src, dst, blockSet) {
|
|
5246
5390
|
rmSync11(dst, { recursive: true, force: true });
|
|
@@ -5252,17 +5396,17 @@ function copyExtrasFiltered(src, dst, blockSet) {
|
|
|
5252
5396
|
});
|
|
5253
5397
|
}
|
|
5254
5398
|
function prunePreservingDenied(src, dst, blockSet) {
|
|
5255
|
-
for (const name of
|
|
5399
|
+
for (const name of readdirSync13(dst)) {
|
|
5256
5400
|
if (isDeniedName(blockSet, name)) continue;
|
|
5257
|
-
const dstPath =
|
|
5258
|
-
const srcStat = lstatSync9(
|
|
5401
|
+
const dstPath = join40(dst, name);
|
|
5402
|
+
const srcStat = lstatSync9(join40(src, name), { throwIfNoEntry: false });
|
|
5259
5403
|
if (srcStat === void 0) {
|
|
5260
5404
|
rmSync11(dstPath, { recursive: true, force: true });
|
|
5261
5405
|
continue;
|
|
5262
5406
|
}
|
|
5263
5407
|
const dstStat = lstatSync9(dstPath);
|
|
5264
5408
|
if (srcStat.isDirectory() && dstStat.isDirectory()) {
|
|
5265
|
-
prunePreservingDenied(
|
|
5409
|
+
prunePreservingDenied(join40(src, name), dstPath, blockSet);
|
|
5266
5410
|
} else if (srcStat.isDirectory() !== dstStat.isDirectory()) {
|
|
5267
5411
|
rmSync11(dstPath, { recursive: true, force: true });
|
|
5268
5412
|
}
|
|
@@ -5283,17 +5427,17 @@ function copyExtrasFilteredPreserving(src, dst, blockSet) {
|
|
|
5283
5427
|
});
|
|
5284
5428
|
}
|
|
5285
5429
|
function prunePreservingBy(src, dst, isPreserved) {
|
|
5286
|
-
for (const name of
|
|
5430
|
+
for (const name of readdirSync13(dst)) {
|
|
5287
5431
|
if (isPreserved(name)) continue;
|
|
5288
|
-
const dstPath =
|
|
5289
|
-
const srcStat = lstatSync9(
|
|
5432
|
+
const dstPath = join40(dst, name);
|
|
5433
|
+
const srcStat = lstatSync9(join40(src, name), { throwIfNoEntry: false });
|
|
5290
5434
|
if (srcStat === void 0) {
|
|
5291
5435
|
rmSync11(dstPath, { recursive: true, force: true });
|
|
5292
5436
|
continue;
|
|
5293
5437
|
}
|
|
5294
5438
|
const dstStat = lstatSync9(dstPath);
|
|
5295
5439
|
if (srcStat.isDirectory() && dstStat.isDirectory()) {
|
|
5296
|
-
prunePreservingBy(
|
|
5440
|
+
prunePreservingBy(join40(src, name), dstPath, isPreserved);
|
|
5297
5441
|
} else if (srcStat.isDirectory() !== dstStat.isDirectory()) {
|
|
5298
5442
|
rmSync11(dstPath, { recursive: true, force: true });
|
|
5299
5443
|
}
|
|
@@ -5320,11 +5464,11 @@ init_utils_json();
|
|
|
5320
5464
|
|
|
5321
5465
|
// src/extras-sync.remap.ts
|
|
5322
5466
|
init_config();
|
|
5323
|
-
import { existsSync as
|
|
5324
|
-
import { dirname as
|
|
5467
|
+
import { existsSync as existsSync35, mkdirSync as mkdirSync8, readdirSync as readdirSync14, realpathSync as realpathSync4, rmSync as rmSync12 } from "node:fs";
|
|
5468
|
+
import { dirname as dirname9, join as join42, sep as sep7 } from "node:path";
|
|
5325
5469
|
|
|
5326
5470
|
// src/extras-sync.planning-diff.ts
|
|
5327
|
-
import { join as
|
|
5471
|
+
import { join as join41, normalize as normalize2, sep as sep6 } from "node:path";
|
|
5328
5472
|
init_utils();
|
|
5329
5473
|
function processRecord(fields, i, changed, deleted) {
|
|
5330
5474
|
const status = fields[i];
|
|
@@ -5376,7 +5520,7 @@ function planningDeleteTargets(opts) {
|
|
|
5376
5520
|
const { deleted } = parsePlanningDiff(raw);
|
|
5377
5521
|
const logicalPrefix = "shared/extras/" + logical + "/";
|
|
5378
5522
|
const prefix = logicalPrefix + ".planning/";
|
|
5379
|
-
const planningRoot =
|
|
5523
|
+
const planningRoot = join41(localRoot, ".planning");
|
|
5380
5524
|
const planningRootBoundary = planningRoot + sep6;
|
|
5381
5525
|
const targets = [];
|
|
5382
5526
|
for (const repoPath of deleted) {
|
|
@@ -5384,7 +5528,7 @@ function planningDeleteTargets(opts) {
|
|
|
5384
5528
|
continue;
|
|
5385
5529
|
}
|
|
5386
5530
|
const remainder = repoPath.slice(logicalPrefix.length);
|
|
5387
|
-
const candidate =
|
|
5531
|
+
const candidate = join41(localRoot, remainder);
|
|
5388
5532
|
const resolved = normalize2(candidate);
|
|
5389
5533
|
if (!resolved.startsWith(planningRootBoundary)) {
|
|
5390
5534
|
throw new NomadFatal(
|
|
@@ -5405,7 +5549,7 @@ function runExtrasOp(v, dryRun, paths, backup, copy) {
|
|
|
5405
5549
|
const would = [];
|
|
5406
5550
|
for (const t of eachExtrasTarget(v, counts)) {
|
|
5407
5551
|
const { src, dst } = paths(t);
|
|
5408
|
-
if (!
|
|
5552
|
+
if (!existsSync35(src)) continue;
|
|
5409
5553
|
const item2 = `${t.logical}/${t.dirname}`;
|
|
5410
5554
|
if (dryRun) {
|
|
5411
5555
|
would.push(item2);
|
|
@@ -5418,15 +5562,15 @@ function runExtrasOp(v, dryRun, paths, backup, copy) {
|
|
|
5418
5562
|
return { ...counts, done, would };
|
|
5419
5563
|
}
|
|
5420
5564
|
function pruneEmptyAncestors(target, planningRoot) {
|
|
5421
|
-
let dir =
|
|
5565
|
+
let dir = dirname9(target);
|
|
5422
5566
|
while (dir !== planningRoot && dir.startsWith(planningRoot + sep7)) {
|
|
5423
5567
|
try {
|
|
5424
|
-
if (
|
|
5568
|
+
if (readdirSync14(dir).length > 0) break;
|
|
5425
5569
|
rmSync12(dir, { recursive: true, force: true });
|
|
5426
5570
|
} catch {
|
|
5427
5571
|
break;
|
|
5428
5572
|
}
|
|
5429
|
-
dir =
|
|
5573
|
+
dir = dirname9(dir);
|
|
5430
5574
|
}
|
|
5431
5575
|
}
|
|
5432
5576
|
function tryRealpath(dir) {
|
|
@@ -5440,8 +5584,8 @@ function isInsidePlanningRoot(parentReal, rootReal) {
|
|
|
5440
5584
|
return parentReal === rootReal || parentReal.startsWith(rootReal + sep7);
|
|
5441
5585
|
}
|
|
5442
5586
|
function deletePlanningTarget(target, planningRoot, repoCounterpart) {
|
|
5443
|
-
if (
|
|
5444
|
-
const parentReal = tryRealpath(
|
|
5587
|
+
if (existsSync35(repoCounterpart)) return;
|
|
5588
|
+
const parentReal = tryRealpath(dirname9(target));
|
|
5445
5589
|
if (parentReal === void 0) return;
|
|
5446
5590
|
const rootReal = tryRealpath(planningRoot);
|
|
5447
5591
|
if (rootReal === void 0) return;
|
|
@@ -5450,7 +5594,7 @@ function deletePlanningTarget(target, planningRoot, repoCounterpart) {
|
|
|
5450
5594
|
pruneEmptyAncestors(target, planningRoot);
|
|
5451
5595
|
}
|
|
5452
5596
|
function propagatePlanningDeletes(v, ts, prePostHeads, repo) {
|
|
5453
|
-
const repoExtras =
|
|
5597
|
+
const repoExtras = join42(repo, "shared", "extras");
|
|
5454
5598
|
for (const t of eachExtrasTarget(v, { unmapped: 0, skipped: 0 })) {
|
|
5455
5599
|
if (t.dirname !== ".planning") continue;
|
|
5456
5600
|
let raw;
|
|
@@ -5476,11 +5620,11 @@ function propagatePlanningDeletes(v, ts, prePostHeads, repo) {
|
|
|
5476
5620
|
}
|
|
5477
5621
|
const targets = planningDeleteTargets({ raw, logical: t.logical, localRoot: t.localRoot });
|
|
5478
5622
|
if (targets.length === 0) continue;
|
|
5479
|
-
backupExtrasWrite(
|
|
5480
|
-
const planningRoot =
|
|
5623
|
+
backupExtrasWrite(join42(t.localRoot, t.dirname), ts, t.localRoot);
|
|
5624
|
+
const planningRoot = join42(t.localRoot, ".planning");
|
|
5481
5625
|
for (const target of targets) {
|
|
5482
5626
|
const relToLocal = target.slice(t.localRoot.length + sep7.length);
|
|
5483
|
-
deletePlanningTarget(target, planningRoot,
|
|
5627
|
+
deletePlanningTarget(target, planningRoot, join42(repoExtras, t.logical, relToLocal));
|
|
5484
5628
|
}
|
|
5485
5629
|
}
|
|
5486
5630
|
}
|
|
@@ -5489,14 +5633,14 @@ function remapExtrasPush(ts, opts = {}) {
|
|
|
5489
5633
|
const v = loadValidatedExtras({ missingMsg: "no path-map.json; skipping extras push" });
|
|
5490
5634
|
if (v === null) return { unmapped: 0, skipped: 0, pushed: [], wouldPush: [] };
|
|
5491
5635
|
const repo = repoHome();
|
|
5492
|
-
const repoExtras =
|
|
5493
|
-
if (!dryRun)
|
|
5636
|
+
const repoExtras = join42(repo, "shared", "extras");
|
|
5637
|
+
if (!dryRun) mkdirSync8(repoExtras, { recursive: true });
|
|
5494
5638
|
const { unmapped, skipped, done, would } = runExtrasOp(
|
|
5495
5639
|
v,
|
|
5496
5640
|
dryRun,
|
|
5497
|
-
({ localRoot, logical, dirname:
|
|
5498
|
-
src:
|
|
5499
|
-
dst:
|
|
5641
|
+
({ localRoot, logical, dirname: dirname10 }) => ({
|
|
5642
|
+
src: join42(localRoot, dirname10),
|
|
5643
|
+
dst: join42(repoExtras, logical, dirname10)
|
|
5500
5644
|
}),
|
|
5501
5645
|
(dst) => backupRepoWrite(dst, ts, repo),
|
|
5502
5646
|
// Push copy routing per extra type:
|
|
@@ -5508,7 +5652,7 @@ function remapExtrasPush(ts, opts = {}) {
|
|
|
5508
5652
|
// (enforceAllowList / blockSetFor in commands.push.allowlist.ts)
|
|
5509
5653
|
// remains the hard security boundary.
|
|
5510
5654
|
// All others: copyExtrasFiltered with per-extra denylist.
|
|
5511
|
-
(src, dst,
|
|
5655
|
+
(src, dst, dirname10) => dirname10 === ".planning" ? copyExtrasOverlayFiltered(src, dst, extrasDenySet(dirname10)) : copyExtrasFiltered(src, dst, extrasDenySet(dirname10))
|
|
5512
5656
|
);
|
|
5513
5657
|
return { unmapped, skipped, pushed: done, wouldPush: would };
|
|
5514
5658
|
}
|
|
@@ -5524,9 +5668,9 @@ function remapExtrasPull(ts, opts = {}) {
|
|
|
5524
5668
|
const { unmapped, skipped, done, would } = runExtrasOp(
|
|
5525
5669
|
v,
|
|
5526
5670
|
dryRun,
|
|
5527
|
-
({ localRoot, logical, dirname:
|
|
5528
|
-
src:
|
|
5529
|
-
dst:
|
|
5671
|
+
({ localRoot, logical, dirname: dirname10 }) => ({
|
|
5672
|
+
src: join42(repo, "shared", "extras", logical, dirname10),
|
|
5673
|
+
dst: join42(localRoot, dirname10)
|
|
5530
5674
|
}),
|
|
5531
5675
|
// Snapshot the host-side dst BEFORE copyExtras clobbers it. Anchor on
|
|
5532
5676
|
// localRoot so the backup tree mirrors the project layout.
|
|
@@ -5539,11 +5683,11 @@ function remapExtrasPull(ts, opts = {}) {
|
|
|
5539
5683
|
// removals via the git-diff D set. The filter is defense-in-depth
|
|
5540
5684
|
// against a repo poisoned out-of-band.
|
|
5541
5685
|
// All others: copyExtras (exact mirror; rarely carry host-local files).
|
|
5542
|
-
(src, dst,
|
|
5543
|
-
if (
|
|
5544
|
-
return copyExtrasFilteredPreserving(src, dst, extrasDenySet(
|
|
5545
|
-
if (
|
|
5546
|
-
return copyExtrasOverlayFiltered(src, dst, extrasDenySet(
|
|
5686
|
+
(src, dst, dirname10) => {
|
|
5687
|
+
if (dirname10 === ".claude")
|
|
5688
|
+
return copyExtrasFilteredPreserving(src, dst, extrasDenySet(dirname10));
|
|
5689
|
+
if (dirname10 === ".planning")
|
|
5690
|
+
return copyExtrasOverlayFiltered(src, dst, extrasDenySet(dirname10));
|
|
5547
5691
|
return copyExtras(src, dst);
|
|
5548
5692
|
}
|
|
5549
5693
|
);
|
|
@@ -5567,20 +5711,20 @@ function divergenceCheckExtras(ts) {
|
|
|
5567
5711
|
const v = loadValidatedExtras({});
|
|
5568
5712
|
if (v === null) return;
|
|
5569
5713
|
const counts = { unmapped: 0, skipped: 0 };
|
|
5570
|
-
const backupRoot =
|
|
5714
|
+
const backupRoot = join43(backupBase(), ts, "extras");
|
|
5571
5715
|
const repo = repoHome();
|
|
5572
|
-
for (const { logical, localRoot, dirname:
|
|
5573
|
-
const local =
|
|
5574
|
-
const repoEntry =
|
|
5575
|
-
if (!
|
|
5716
|
+
for (const { logical, localRoot, dirname: dirname10 } of eachExtrasTarget(v, counts)) {
|
|
5717
|
+
const local = join43(localRoot, dirname10);
|
|
5718
|
+
const repoEntry = join43(repo, "shared", "extras", logical, dirname10);
|
|
5719
|
+
if (!existsSync36(local) || !existsSync36(repoEntry)) continue;
|
|
5576
5720
|
const diff = listDivergingFiles(local, repoEntry);
|
|
5577
5721
|
if (diff.length === 0) continue;
|
|
5578
|
-
const projectBackupRoot =
|
|
5722
|
+
const projectBackupRoot = join43(backupRoot, encodePath(localRoot));
|
|
5579
5723
|
warn(
|
|
5580
5724
|
divergenceWarnLine({
|
|
5581
|
-
dirname:
|
|
5725
|
+
dirname: dirname10,
|
|
5582
5726
|
logical,
|
|
5583
|
-
isDir:
|
|
5727
|
+
isDir: statSync10(local).isDirectory(),
|
|
5584
5728
|
count: diff.length,
|
|
5585
5729
|
projectBackupRoot
|
|
5586
5730
|
})
|
|
@@ -5591,8 +5735,8 @@ function divergenceCheckExtras(ts) {
|
|
|
5591
5735
|
|
|
5592
5736
|
// src/skills-sync.ts
|
|
5593
5737
|
init_config();
|
|
5594
|
-
import { existsSync as
|
|
5595
|
-
import { join as
|
|
5738
|
+
import { existsSync as existsSync37, lstatSync as lstatSync10, mkdirSync as mkdirSync9, readdirSync as readdirSync15, rmSync as rmSync13 } from "node:fs";
|
|
5739
|
+
import { join as join44 } from "node:path";
|
|
5596
5740
|
init_utils_fs();
|
|
5597
5741
|
function isGsdOwned(name) {
|
|
5598
5742
|
return name.startsWith(GSD_PREFIX);
|
|
@@ -5601,7 +5745,7 @@ function isSkillExcluded(name) {
|
|
|
5601
5745
|
return isGsdOwned(name) || isDeniedName(ALWAYS_NEVER_SYNC, name);
|
|
5602
5746
|
}
|
|
5603
5747
|
function copySkillsPush(src, dst) {
|
|
5604
|
-
const srcNames =
|
|
5748
|
+
const srcNames = readdirSync15(src, { encoding: "utf8" });
|
|
5605
5749
|
const blockSet = /* @__PURE__ */ new Set([
|
|
5606
5750
|
...srcNames.filter((n) => isGsdOwned(n)),
|
|
5607
5751
|
...ALWAYS_NEVER_SYNC
|
|
@@ -5612,30 +5756,30 @@ function copySkillsPull(src, dst) {
|
|
|
5612
5756
|
copyExtrasFilteredPreservingBy(src, dst, isSkillExcluded);
|
|
5613
5757
|
}
|
|
5614
5758
|
function syncSkillsPull(ts) {
|
|
5615
|
-
const sharedSkills =
|
|
5616
|
-
if (!
|
|
5617
|
-
const localSkills =
|
|
5759
|
+
const sharedSkills = join44(repoHome(), "shared", "skills");
|
|
5760
|
+
if (!existsSync37(sharedSkills)) return;
|
|
5761
|
+
const localSkills = join44(claudeHome(), "skills");
|
|
5618
5762
|
const dstStat = lstatSync10(localSkills, { throwIfNoEntry: false });
|
|
5619
5763
|
if (dstStat?.isSymbolicLink() === true) {
|
|
5620
5764
|
backupBeforeWrite(localSkills, ts);
|
|
5621
5765
|
rmSync13(localSkills, { recursive: true, force: true });
|
|
5622
|
-
|
|
5766
|
+
mkdirSync9(localSkills, { recursive: true });
|
|
5623
5767
|
}
|
|
5624
5768
|
copySkillsPull(sharedSkills, localSkills);
|
|
5625
5769
|
}
|
|
5626
5770
|
function syncSkillsPush() {
|
|
5627
|
-
const localSkills =
|
|
5771
|
+
const localSkills = join44(claudeHome(), "skills");
|
|
5628
5772
|
const stat = lstatSync10(localSkills, { throwIfNoEntry: false });
|
|
5629
5773
|
if (stat === void 0) return;
|
|
5630
5774
|
if (stat.isSymbolicLink()) return;
|
|
5631
|
-
const sharedSkills =
|
|
5775
|
+
const sharedSkills = join44(repoHome(), "shared", "skills");
|
|
5632
5776
|
copySkillsPush(localSkills, sharedSkills);
|
|
5633
5777
|
}
|
|
5634
5778
|
|
|
5635
5779
|
// src/preview.ts
|
|
5636
5780
|
init_config();
|
|
5637
|
-
import { existsSync as
|
|
5638
|
-
import { join as
|
|
5781
|
+
import { existsSync as existsSync38 } from "node:fs";
|
|
5782
|
+
import { join as join45 } from "node:path";
|
|
5639
5783
|
|
|
5640
5784
|
// node_modules/diff/libesm/diff/base.js
|
|
5641
5785
|
var Diff = class {
|
|
@@ -5921,7 +6065,7 @@ function diffJsonStrings(currentJsonText, newJsonText) {
|
|
|
5921
6065
|
return lines.join("\n");
|
|
5922
6066
|
}
|
|
5923
6067
|
function readJsonOrNull(path) {
|
|
5924
|
-
if (!
|
|
6068
|
+
if (!existsSync38(path)) return null;
|
|
5925
6069
|
try {
|
|
5926
6070
|
return readJson(path);
|
|
5927
6071
|
} catch {
|
|
@@ -5935,12 +6079,12 @@ function previewSettings(basePath, hostPath, settingsPath) {
|
|
|
5935
6079
|
}
|
|
5936
6080
|
const notes = [];
|
|
5937
6081
|
const hostOverrides = readJsonOrNull(hostPath);
|
|
5938
|
-
if (hostOverrides === null &&
|
|
6082
|
+
if (hostOverrides === null && existsSync38(hostPath)) {
|
|
5939
6083
|
notes.push(`malformed hosts/${HOST}.json; ignoring overrides`);
|
|
5940
6084
|
}
|
|
5941
6085
|
const merged = stripGsdHookEntries(deepMerge(base, hostOverrides ?? {}));
|
|
5942
6086
|
const current = readJsonOrNull(settingsPath);
|
|
5943
|
-
if (current === null &&
|
|
6087
|
+
if (current === null && existsSync38(settingsPath)) {
|
|
5944
6088
|
return { diff: "", notes: [...notes, "malformed; skipping diff"] };
|
|
5945
6089
|
}
|
|
5946
6090
|
const strippedCurrent = stripGsdHookEntries(current ?? {});
|
|
@@ -5981,9 +6125,9 @@ function computePreview(ts, map, verb = "pull") {
|
|
|
5981
6125
|
onPreview: (e) => addItem(links, formatLinkRow(e))
|
|
5982
6126
|
});
|
|
5983
6127
|
const settingsResult = previewSettings(
|
|
5984
|
-
|
|
5985
|
-
|
|
5986
|
-
|
|
6128
|
+
join45(repo, "shared", "settings.base.json"),
|
|
6129
|
+
join45(repo, "hosts", `${HOST}.json`),
|
|
6130
|
+
join45(claude, "settings.json")
|
|
5987
6131
|
);
|
|
5988
6132
|
const settingsSection = buildSettingsSectionForPreview(settingsResult);
|
|
5989
6133
|
const sessions = section("Sessions");
|
|
@@ -6184,8 +6328,8 @@ function cmdPull(opts = {}) {
|
|
|
6184
6328
|
const forceRemote = opts.forceRemote === true;
|
|
6185
6329
|
const repo = repoHome();
|
|
6186
6330
|
const backup = backupBase();
|
|
6187
|
-
if (!
|
|
6188
|
-
if (!
|
|
6331
|
+
if (!existsSync39(repo)) die(`repo not cloned at ${repo}`);
|
|
6332
|
+
if (!existsSync39(join46(repo, "shared", "settings.base.json"))) {
|
|
6189
6333
|
die("repo not initialized; run 'nomad init' to scaffold");
|
|
6190
6334
|
}
|
|
6191
6335
|
const handle = acquireLock("pull");
|
|
@@ -6194,9 +6338,9 @@ function cmdPull(opts = {}) {
|
|
|
6194
6338
|
const ts = freshBackupTs(backup);
|
|
6195
6339
|
handleWedge(repo, forceRemote);
|
|
6196
6340
|
if (!dryRun) {
|
|
6197
|
-
const backupRoot =
|
|
6341
|
+
const backupRoot = join46(backup, ts);
|
|
6198
6342
|
try {
|
|
6199
|
-
|
|
6343
|
+
mkdirSync10(backupRoot, { recursive: true });
|
|
6200
6344
|
} catch (err) {
|
|
6201
6345
|
die(`could not create backup dir: ${err.message}`);
|
|
6202
6346
|
}
|
|
@@ -6207,8 +6351,8 @@ function cmdPull(opts = {}) {
|
|
|
6207
6351
|
const prePostHeads = capturePrePostHeads(repo, () => {
|
|
6208
6352
|
gitOrFatal(["pull", "--rebase", "--autostash"], "git pull --rebase", repo);
|
|
6209
6353
|
});
|
|
6210
|
-
const mapPath =
|
|
6211
|
-
const map =
|
|
6354
|
+
const mapPath = join46(repo, "path-map.json");
|
|
6355
|
+
const map = existsSync39(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
6212
6356
|
divergenceCheckExtras(ts);
|
|
6213
6357
|
if (dryRun) {
|
|
6214
6358
|
computePreview(ts, map, "pull");
|
|
@@ -6230,8 +6374,63 @@ function cmdPull(opts = {}) {
|
|
|
6230
6374
|
|
|
6231
6375
|
// src/commands.push.ts
|
|
6232
6376
|
init_config();
|
|
6233
|
-
import { existsSync as
|
|
6234
|
-
import { join as
|
|
6377
|
+
import { existsSync as existsSync43 } from "node:fs";
|
|
6378
|
+
import { join as join51 } from "node:path";
|
|
6379
|
+
|
|
6380
|
+
// src/commands.push.selection.ts
|
|
6381
|
+
init_config();
|
|
6382
|
+
import { existsSync as existsSync40, statSync as statSync11 } from "node:fs";
|
|
6383
|
+
import { join as join47 } from "node:path";
|
|
6384
|
+
init_utils_json();
|
|
6385
|
+
function buildCurrentMap(map) {
|
|
6386
|
+
const current = {};
|
|
6387
|
+
if (map === null) return current;
|
|
6388
|
+
const claude = claudeHome();
|
|
6389
|
+
for (const [, hostMap] of Object.entries(map.projects)) {
|
|
6390
|
+
const localPath = hostMap[HOST];
|
|
6391
|
+
if (!localPath) continue;
|
|
6392
|
+
const localDir = join47(claude, "projects", encodePath(localPath));
|
|
6393
|
+
if (!existsSync40(localDir)) continue;
|
|
6394
|
+
for (const f of enumerateSourceFiles(localDir)) {
|
|
6395
|
+
const st = statSync11(f);
|
|
6396
|
+
current[f] = { size: st.size, mtime: st.mtimeMs };
|
|
6397
|
+
}
|
|
6398
|
+
}
|
|
6399
|
+
return current;
|
|
6400
|
+
}
|
|
6401
|
+
function computePushSelection(map, old, scannerVersion, configHash, fullScan) {
|
|
6402
|
+
const current = buildCurrentMap(map);
|
|
6403
|
+
const fullRescan = shouldFullRescan(old, scannerVersion, configHash, fullScan);
|
|
6404
|
+
const hashCache = /* @__PURE__ */ new Map();
|
|
6405
|
+
const cachedHash = (p) => {
|
|
6406
|
+
const hit = hashCache.get(p);
|
|
6407
|
+
if (hit !== void 0) return hit;
|
|
6408
|
+
const h2 = hashFile(p);
|
|
6409
|
+
hashCache.set(p, h2);
|
|
6410
|
+
return h2;
|
|
6411
|
+
};
|
|
6412
|
+
const delta = fullRescan ? { changed: new Set(Object.keys(current)), deleted: [] } : diffManifest(old, current, cachedHash);
|
|
6413
|
+
const files = {};
|
|
6414
|
+
for (const [key, meta] of Object.entries(current)) {
|
|
6415
|
+
const hash = delta.changed.has(key) ? cachedHash(key) : old.files[key].hash;
|
|
6416
|
+
files[key] = { size: meta.size, mtime: meta.mtime, hash };
|
|
6417
|
+
}
|
|
6418
|
+
return {
|
|
6419
|
+
selection: fullRescan ? void 0 : delta,
|
|
6420
|
+
newManifest: buildManifest(files, scannerVersion, configHash)
|
|
6421
|
+
};
|
|
6422
|
+
}
|
|
6423
|
+
function loadSelectionForPush(mapPath, old, scannerVersion, configHash, fullScan) {
|
|
6424
|
+
const map = existsSync40(mapPath) ? readPathMap(mapPath) : null;
|
|
6425
|
+
const { selection, newManifest } = computePushSelection(
|
|
6426
|
+
map,
|
|
6427
|
+
old,
|
|
6428
|
+
scannerVersion,
|
|
6429
|
+
configHash,
|
|
6430
|
+
fullScan
|
|
6431
|
+
);
|
|
6432
|
+
return { map, selection, newManifest };
|
|
6433
|
+
}
|
|
6235
6434
|
|
|
6236
6435
|
// src/commands.push.allowlist.ts
|
|
6237
6436
|
init_config();
|
|
@@ -6322,6 +6521,85 @@ function enforceAllowList(statusPorcelain, map) {
|
|
|
6322
6521
|
throw new NomadFatal("push allow-list violations");
|
|
6323
6522
|
}
|
|
6324
6523
|
|
|
6524
|
+
// src/commands.push.settings.ts
|
|
6525
|
+
init_config();
|
|
6526
|
+
import { existsSync as existsSync41 } from "node:fs";
|
|
6527
|
+
import { join as join48 } from "node:path";
|
|
6528
|
+
init_utils();
|
|
6529
|
+
init_utils_fs();
|
|
6530
|
+
init_utils_json();
|
|
6531
|
+
function stripGsdHooksFromBase(repo, backup) {
|
|
6532
|
+
const basePath = join48(repo, "shared", "settings.base.json");
|
|
6533
|
+
if (!existsSync41(basePath)) return;
|
|
6534
|
+
let base;
|
|
6535
|
+
try {
|
|
6536
|
+
base = readJson(basePath);
|
|
6537
|
+
} catch {
|
|
6538
|
+
return;
|
|
6539
|
+
}
|
|
6540
|
+
if (!baseHasGsdHookEntries(base)) return;
|
|
6541
|
+
const stripped = stripGsdHookEntries(base);
|
|
6542
|
+
const ts = freshBackupTs(backup);
|
|
6543
|
+
backupRepoWrite(basePath, ts, repo);
|
|
6544
|
+
writeJsonAtomic(basePath, stripped);
|
|
6545
|
+
}
|
|
6546
|
+
function reportSettingsAheadDrift(repo) {
|
|
6547
|
+
const basePath = join48(repo, "shared", "settings.base.json");
|
|
6548
|
+
if (!existsSync41(basePath)) return;
|
|
6549
|
+
const settingsPath = join48(claudeHome(), "settings.json");
|
|
6550
|
+
if (!existsSync41(settingsPath)) return;
|
|
6551
|
+
try {
|
|
6552
|
+
const base = readJson(basePath);
|
|
6553
|
+
const hostPath = join48(repo, "hosts", `${HOST}.json`);
|
|
6554
|
+
const overrides = existsSync41(hostPath) ? readJson(hostPath) : {};
|
|
6555
|
+
const merged = deepMerge(base, overrides);
|
|
6556
|
+
const settings = readJson(settingsPath);
|
|
6557
|
+
const { ahead } = classifySettingsDrift(merged, settings);
|
|
6558
|
+
const { promotable } = partitionByCaptureExclusion(ahead);
|
|
6559
|
+
if (promotable.length === 0) return;
|
|
6560
|
+
const { phrase, pronoun, verb } = describeSettings(promotable);
|
|
6561
|
+
warn(
|
|
6562
|
+
`your settings.json has ${phrase} that ${verb} not yet in the repo; run 'nomad capture-settings' to save ${pronoun} (or 'nomad capture-settings --host' for host-specific values).`
|
|
6563
|
+
);
|
|
6564
|
+
} catch {
|
|
6565
|
+
}
|
|
6566
|
+
}
|
|
6567
|
+
|
|
6568
|
+
// src/commands.push.guards.ts
|
|
6569
|
+
init_push_checks();
|
|
6570
|
+
init_utils();
|
|
6571
|
+
import { join as join49, relative as relative5 } from "node:path";
|
|
6572
|
+
function guardGitlinks(repo) {
|
|
6573
|
+
const gitlinks = findGitlinks(join49(repo, "shared"));
|
|
6574
|
+
if (gitlinks.length === 0) return;
|
|
6575
|
+
for (const p of gitlinks) {
|
|
6576
|
+
const rel = relative5(repo, p);
|
|
6577
|
+
fail(`gitlink: ${rel} would push as submodule (run: rm -rf ${rel} or remove the nested repo)`);
|
|
6578
|
+
}
|
|
6579
|
+
const noun = gitlinks.length === 1 ? "entry" : "entries";
|
|
6580
|
+
throw new NomadFatal(
|
|
6581
|
+
`gitlink trap: ${gitlinks.length} nested .git ${noun} in shared/; remove before retry`
|
|
6582
|
+
);
|
|
6583
|
+
}
|
|
6584
|
+
function guardResolutionModeConflicts(dryRun, redactAll, allowAll, allowRule) {
|
|
6585
|
+
const hasAllow = allowAll || allowRule !== void 0;
|
|
6586
|
+
const wantsResolution = redactAll || hasAllow;
|
|
6587
|
+
if (redactAll && hasAllow) {
|
|
6588
|
+
die("--redact-all, --allow-all, and --allow are mutually exclusive resolution modes");
|
|
6589
|
+
}
|
|
6590
|
+
if (allowAll && allowRule !== void 0) {
|
|
6591
|
+
die("--redact-all, --allow-all, and --allow are mutually exclusive resolution modes");
|
|
6592
|
+
}
|
|
6593
|
+
if (dryRun && wantsResolution) {
|
|
6594
|
+
die(
|
|
6595
|
+
"--redact-all, --allow-all, and --allow cannot be combined with --dry-run (dry-run resolves nothing)"
|
|
6596
|
+
);
|
|
6597
|
+
}
|
|
6598
|
+
}
|
|
6599
|
+
|
|
6600
|
+
// src/commands.push.steps.ts
|
|
6601
|
+
init_config();
|
|
6602
|
+
|
|
6325
6603
|
// src/push-global-config.ts
|
|
6326
6604
|
init_config();
|
|
6327
6605
|
import { execFileSync as execFileSync18 } from "node:child_process";
|
|
@@ -6394,24 +6672,36 @@ function collectGlobalConfigChanges(repoHome2, hostname2, opts) {
|
|
|
6394
6672
|
return changes;
|
|
6395
6673
|
}
|
|
6396
6674
|
|
|
6397
|
-
// src/commands.push.ts
|
|
6675
|
+
// src/commands.push.steps.ts
|
|
6398
6676
|
init_push_leak_verdict();
|
|
6399
|
-
init_push_checks();
|
|
6400
6677
|
|
|
6401
6678
|
// src/push-preview.ts
|
|
6402
6679
|
init_color();
|
|
6403
6680
|
init_config();
|
|
6404
6681
|
init_config_sharedDirs_guard();
|
|
6405
6682
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
6406
|
-
import { copyFileSync, existsSync as
|
|
6683
|
+
import { copyFileSync, existsSync as existsSync42, mkdirSync as mkdirSync11, readdirSync as readdirSync16, rmSync as rmSync14 } from "node:fs";
|
|
6407
6684
|
import { homedir as homedir5 } from "node:os";
|
|
6408
|
-
import { join as
|
|
6685
|
+
import { join as join50, relative as relative6, sep as sep8 } from "node:path";
|
|
6409
6686
|
init_push_leak_verdict();
|
|
6410
6687
|
init_push_gitleaks();
|
|
6411
6688
|
init_utils_fs();
|
|
6412
6689
|
init_utils_json();
|
|
6413
6690
|
var NOTHING_TO_SCAN_ROW = `${dim(infoGlyph)} nothing to scan, no leaks`;
|
|
6414
|
-
function
|
|
6691
|
+
function stageSessionDir(localDir, dstDir, changed) {
|
|
6692
|
+
if (changed !== void 0) {
|
|
6693
|
+
const prefix = `${localDir}${sep8}`;
|
|
6694
|
+
const matching = [...changed].filter((p) => p.startsWith(prefix));
|
|
6695
|
+
if (matching.length === 0) return false;
|
|
6696
|
+
for (const src of matching) {
|
|
6697
|
+
copyFileAtomic(src, join50(dstDir, relative6(localDir, src)));
|
|
6698
|
+
}
|
|
6699
|
+
return true;
|
|
6700
|
+
}
|
|
6701
|
+
copyDirJsonlOnly(localDir, dstDir);
|
|
6702
|
+
return true;
|
|
6703
|
+
}
|
|
6704
|
+
function stageSessions(tmpRoot, map, changed) {
|
|
6415
6705
|
if (typeof map.projects !== "object" || map.projects === null) return 0;
|
|
6416
6706
|
const reverse = /* @__PURE__ */ new Map();
|
|
6417
6707
|
for (const [logical, hosts] of Object.entries(map.projects)) {
|
|
@@ -6420,14 +6710,15 @@ function stageSessions(tmpRoot, map) {
|
|
|
6420
6710
|
if (!p || p === "TBD") continue;
|
|
6421
6711
|
reverse.set(encodePath(p), logical);
|
|
6422
6712
|
}
|
|
6423
|
-
const localProjects =
|
|
6424
|
-
if (!
|
|
6713
|
+
const localProjects = join50(claudeHome(), "projects");
|
|
6714
|
+
if (!existsSync42(localProjects)) return 0;
|
|
6425
6715
|
let staged = 0;
|
|
6426
|
-
for (const dir of
|
|
6716
|
+
for (const dir of readdirSync16(localProjects)) {
|
|
6427
6717
|
const logical = reverse.get(dir);
|
|
6428
6718
|
if (!logical) continue;
|
|
6429
|
-
|
|
6430
|
-
|
|
6719
|
+
const localDir = join50(localProjects, dir);
|
|
6720
|
+
const dstDir = join50(tmpRoot, "shared", "projects", logical);
|
|
6721
|
+
if (stageSessionDir(localDir, dstDir, changed)) staged++;
|
|
6431
6722
|
}
|
|
6432
6723
|
return staged;
|
|
6433
6724
|
}
|
|
@@ -6440,31 +6731,31 @@ function stageExtras(tmpRoot, map) {
|
|
|
6440
6731
|
assertSafeLogical(logical);
|
|
6441
6732
|
const localRoot = map.projects[logical]?.[HOST];
|
|
6442
6733
|
if (!localRoot || localRoot === "TBD") continue;
|
|
6443
|
-
for (const
|
|
6444
|
-
if (!whitelist.includes(
|
|
6445
|
-
const src =
|
|
6446
|
-
if (!
|
|
6447
|
-
const dst =
|
|
6734
|
+
for (const dirname10 of dirnames) {
|
|
6735
|
+
if (!whitelist.includes(dirname10)) continue;
|
|
6736
|
+
const src = join50(localRoot, dirname10);
|
|
6737
|
+
if (!existsSync42(src)) continue;
|
|
6738
|
+
const dst = join50(tmpRoot, "shared", "extras", logical, dirname10);
|
|
6448
6739
|
copyExtras(src, dst);
|
|
6449
6740
|
staged++;
|
|
6450
6741
|
}
|
|
6451
6742
|
}
|
|
6452
6743
|
return staged;
|
|
6453
6744
|
}
|
|
6454
|
-
function previewPushLeaks(map) {
|
|
6455
|
-
const cacheDir =
|
|
6456
|
-
|
|
6745
|
+
function previewPushLeaks(map, opts = {}) {
|
|
6746
|
+
const cacheDir = join50(homedir5(), ".cache", "claude-nomad");
|
|
6747
|
+
mkdirSync11(cacheDir, { recursive: true });
|
|
6457
6748
|
const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes2(4).toString("hex")}`;
|
|
6458
|
-
const tmpRoot =
|
|
6749
|
+
const tmpRoot = join50(cacheDir, `push-preview-tree-${stamp}`);
|
|
6459
6750
|
try {
|
|
6460
|
-
const sessionCount = stageSessions(tmpRoot, map);
|
|
6751
|
+
const sessionCount = stageSessions(tmpRoot, map, opts.selection?.changed);
|
|
6461
6752
|
const extrasCount = stageExtras(tmpRoot, map);
|
|
6462
6753
|
if (sessionCount + extrasCount === 0) {
|
|
6463
6754
|
return { leak: false, verdictRow: NOTHING_TO_SCAN_ROW, recovery: null, findings: [] };
|
|
6464
6755
|
}
|
|
6465
|
-
const ignoreFile =
|
|
6466
|
-
if (
|
|
6467
|
-
copyFileSync(ignoreFile,
|
|
6756
|
+
const ignoreFile = join50(repoHome(), ".gitleaksignore");
|
|
6757
|
+
if (existsSync42(ignoreFile)) {
|
|
6758
|
+
copyFileSync(ignoreFile, join50(tmpRoot, ".gitleaksignore"));
|
|
6468
6759
|
}
|
|
6469
6760
|
let findings;
|
|
6470
6761
|
try {
|
|
@@ -6481,59 +6772,9 @@ function previewPushLeaks(map) {
|
|
|
6481
6772
|
}
|
|
6482
6773
|
}
|
|
6483
6774
|
|
|
6484
|
-
// src/commands.push.ts
|
|
6775
|
+
// src/commands.push.steps.ts
|
|
6485
6776
|
init_utils();
|
|
6486
|
-
|
|
6487
|
-
init_utils_json();
|
|
6488
|
-
function stripGsdHooksFromBase(repo, backup) {
|
|
6489
|
-
const basePath = join47(repo, "shared", "settings.base.json");
|
|
6490
|
-
if (!existsSync40(basePath)) return;
|
|
6491
|
-
let base;
|
|
6492
|
-
try {
|
|
6493
|
-
base = readJson(basePath);
|
|
6494
|
-
} catch {
|
|
6495
|
-
return;
|
|
6496
|
-
}
|
|
6497
|
-
if (!baseHasGsdHookEntries(base)) return;
|
|
6498
|
-
const stripped = stripGsdHookEntries(base);
|
|
6499
|
-
const ts = freshBackupTs(backup);
|
|
6500
|
-
backupRepoWrite(basePath, ts, repo);
|
|
6501
|
-
writeJsonAtomic(basePath, stripped);
|
|
6502
|
-
}
|
|
6503
|
-
function reportSettingsAheadDrift(repo) {
|
|
6504
|
-
const basePath = join47(repo, "shared", "settings.base.json");
|
|
6505
|
-
if (!existsSync40(basePath)) return;
|
|
6506
|
-
const settingsPath = join47(claudeHome(), "settings.json");
|
|
6507
|
-
if (!existsSync40(settingsPath)) return;
|
|
6508
|
-
try {
|
|
6509
|
-
const base = readJson(basePath);
|
|
6510
|
-
const hostPath = join47(repo, "hosts", `${HOST}.json`);
|
|
6511
|
-
const overrides = existsSync40(hostPath) ? readJson(hostPath) : {};
|
|
6512
|
-
const merged = deepMerge(base, overrides);
|
|
6513
|
-
const settings = readJson(settingsPath);
|
|
6514
|
-
const { ahead } = classifySettingsDrift(merged, settings);
|
|
6515
|
-
const { promotable } = partitionByCaptureExclusion(ahead);
|
|
6516
|
-
if (promotable.length === 0) return;
|
|
6517
|
-
const { phrase, pronoun, verb } = describeSettings(promotable);
|
|
6518
|
-
warn(
|
|
6519
|
-
`your settings.json has ${phrase} that ${verb} not yet in the repo; run 'nomad capture-settings' to save ${pronoun} (or 'nomad capture-settings --host' for host-specific values).`
|
|
6520
|
-
);
|
|
6521
|
-
} catch {
|
|
6522
|
-
}
|
|
6523
|
-
}
|
|
6524
|
-
function guardGitlinks(repo) {
|
|
6525
|
-
const gitlinks = findGitlinks(join47(repo, "shared"));
|
|
6526
|
-
if (gitlinks.length === 0) return;
|
|
6527
|
-
for (const p of gitlinks) {
|
|
6528
|
-
const rel = relative5(repo, p);
|
|
6529
|
-
fail(`gitlink: ${rel} would push as submodule (run: rm -rf ${rel} or remove the nested repo)`);
|
|
6530
|
-
}
|
|
6531
|
-
const noun = gitlinks.length === 1 ? "entry" : "entries";
|
|
6532
|
-
throw new NomadFatal(
|
|
6533
|
-
`gitlink trap: ${gitlinks.length} nested .git ${noun} in shared/; remove before retry`
|
|
6534
|
-
);
|
|
6535
|
-
}
|
|
6536
|
-
async function commitAndPush(st, ts, map, redactAll, allowAll, allowRule, repo) {
|
|
6777
|
+
async function commitAndPush(st, ts, map, resolution, repo, newManifest) {
|
|
6537
6778
|
gitOrFatal(["add", "-A"], "git add", repo);
|
|
6538
6779
|
const staged = parsePorcelainZ2(gitStatusPorcelainZ(repo));
|
|
6539
6780
|
const toDrop = staged.filter((p) => isGsdDropped(p));
|
|
@@ -6549,55 +6790,61 @@ async function commitAndPush(st, ts, map, redactAll, allowAll, allowRule, repo)
|
|
|
6549
6790
|
let verdict = withSpinner("Scanning for secrets", () => scanPushVerdict(repo));
|
|
6550
6791
|
if (verdict.leak) {
|
|
6551
6792
|
renderPushTree(st, verdict);
|
|
6552
|
-
verdict = await resolveLeakFindings(verdict, ts, map,
|
|
6793
|
+
verdict = await resolveLeakFindings(verdict, ts, map, resolution);
|
|
6553
6794
|
}
|
|
6554
6795
|
gitOrFatal(["commit", "-m", `chore: sync from ${HOST}`], "git commit", repo);
|
|
6555
6796
|
withSpinner("Pushing", () => gitOrFatal(["push"], "git push", repo));
|
|
6797
|
+
try {
|
|
6798
|
+
writeManifest(manifestPath(), newManifest);
|
|
6799
|
+
} catch (err) {
|
|
6800
|
+
warn(`could not write push manifest (next push will full-rescan): ${String(err)}`);
|
|
6801
|
+
}
|
|
6556
6802
|
renderPushTree(st, verdict);
|
|
6557
6803
|
}
|
|
6558
|
-
function runDryRunPreview(st, map, repo) {
|
|
6804
|
+
function runDryRunPreview(st, map, repo, selection) {
|
|
6559
6805
|
st.globalConfig = collectGlobalConfigChanges(repo, HOST, { staged: false });
|
|
6560
6806
|
if (map === null) {
|
|
6561
6807
|
renderNoScanTree(st, { noMapHint: true });
|
|
6562
6808
|
return;
|
|
6563
6809
|
}
|
|
6564
|
-
const verdict = withSpinner("Scanning for secrets", () => previewPushLeaks(map));
|
|
6810
|
+
const verdict = withSpinner("Scanning for secrets", () => previewPushLeaks(map, { selection }));
|
|
6565
6811
|
renderPushTree(st, verdict);
|
|
6566
6812
|
if (verdict.recovery !== null) fail(verdict.recovery);
|
|
6567
6813
|
}
|
|
6568
|
-
|
|
6569
|
-
|
|
6570
|
-
|
|
6571
|
-
|
|
6572
|
-
|
|
6573
|
-
}
|
|
6574
|
-
if (allowAll && allowRule !== void 0) {
|
|
6575
|
-
die("--redact-all, --allow-all, and --allow are mutually exclusive resolution modes");
|
|
6576
|
-
}
|
|
6577
|
-
if (dryRun && wantsResolution) {
|
|
6578
|
-
die(
|
|
6579
|
-
"--redact-all, --allow-all, and --allow cannot be combined with --dry-run (dry-run resolves nothing)"
|
|
6580
|
-
);
|
|
6581
|
-
}
|
|
6582
|
-
}
|
|
6814
|
+
|
|
6815
|
+
// src/commands.push.ts
|
|
6816
|
+
init_push_checks();
|
|
6817
|
+
init_utils();
|
|
6818
|
+
init_utils_fs();
|
|
6583
6819
|
async function cmdPush(opts = {}) {
|
|
6584
6820
|
const dryRun = opts.dryRun === true;
|
|
6585
6821
|
const redactAll = opts.redactAll === true;
|
|
6586
6822
|
const allowAll = opts.allowAll === true;
|
|
6587
6823
|
const allowRule = opts.allowRule;
|
|
6824
|
+
const fullScan = opts.fullScan === true;
|
|
6588
6825
|
guardResolutionModeConflicts(dryRun, redactAll, allowAll, allowRule);
|
|
6589
6826
|
const repo = repoHome();
|
|
6590
6827
|
const backup = backupBase();
|
|
6591
|
-
if (!
|
|
6828
|
+
if (!existsSync43(repo)) die(`repo not cloned at ${repo}`);
|
|
6592
6829
|
const handle = acquireLock("push");
|
|
6593
6830
|
if (handle === null) process.exit(0);
|
|
6594
6831
|
try {
|
|
6595
6832
|
console.log(dryRun ? `push on host=${HOST} (dry-run)` : `push on host=${HOST}`);
|
|
6596
6833
|
reportSettingsAheadDrift(repo);
|
|
6597
|
-
probeGitleaks();
|
|
6834
|
+
const scannerVersion = probeGitleaks();
|
|
6835
|
+
const configHash = computeConfigHash();
|
|
6836
|
+
const old = readManifest(manifestPath());
|
|
6837
|
+
const mapPath = join51(repo, "path-map.json");
|
|
6838
|
+
const { map, selection, newManifest } = loadSelectionForPush(
|
|
6839
|
+
mapPath,
|
|
6840
|
+
old,
|
|
6841
|
+
scannerVersion,
|
|
6842
|
+
configHash,
|
|
6843
|
+
fullScan
|
|
6844
|
+
);
|
|
6598
6845
|
withSpinner("Rebasing onto origin", () => rebaseBeforePush(repo));
|
|
6599
6846
|
const ts = freshBackupTs(backup);
|
|
6600
|
-
const remap = withSpinner("Syncing sessions", () => remapPush(ts, { dryRun }));
|
|
6847
|
+
const remap = withSpinner("Syncing sessions", () => remapPush(ts, { dryRun, selection }));
|
|
6601
6848
|
const extras = withSpinner("Syncing extras", () => remapExtrasPush(ts, { dryRun }));
|
|
6602
6849
|
if (!dryRun) {
|
|
6603
6850
|
syncSkillsPush();
|
|
@@ -6611,15 +6858,13 @@ async function cmdPush(opts = {}) {
|
|
|
6611
6858
|
renderNoScanTree(st);
|
|
6612
6859
|
return;
|
|
6613
6860
|
}
|
|
6614
|
-
|
|
6615
|
-
|
|
6616
|
-
|
|
6617
|
-
die("path-map.json missing, cannot enforce push allow-list");
|
|
6861
|
+
if (map === null) {
|
|
6862
|
+
if (dryRun) return runDryRunPreview(st, null, repo, selection);
|
|
6863
|
+
return die("path-map.json missing, cannot enforce push allow-list");
|
|
6618
6864
|
}
|
|
6619
|
-
const map = readPathMap(mapPath);
|
|
6620
6865
|
if (status) enforceAllowList(status, map);
|
|
6621
|
-
if (dryRun) return runDryRunPreview(st, map, repo);
|
|
6622
|
-
await commitAndPush(st, ts, map, redactAll, allowAll, allowRule, repo);
|
|
6866
|
+
if (dryRun) return runDryRunPreview(st, map, repo, selection);
|
|
6867
|
+
await commitAndPush(st, ts, map, { redactAll, allowAll, allowRule }, repo, newManifest);
|
|
6623
6868
|
} catch (err) {
|
|
6624
6869
|
if (err instanceof NomadFatal) {
|
|
6625
6870
|
fail(err.message);
|
|
@@ -6676,18 +6921,18 @@ init_config();
|
|
|
6676
6921
|
|
|
6677
6922
|
// src/diff.ts
|
|
6678
6923
|
init_config();
|
|
6679
|
-
import { existsSync as
|
|
6680
|
-
import { join as
|
|
6924
|
+
import { existsSync as existsSync44 } from "node:fs";
|
|
6925
|
+
import { join as join52 } from "node:path";
|
|
6681
6926
|
init_utils();
|
|
6682
6927
|
init_utils_fs();
|
|
6683
6928
|
init_utils_json();
|
|
6684
6929
|
function cmdDiff() {
|
|
6685
6930
|
try {
|
|
6686
6931
|
const repo = repoHome();
|
|
6687
|
-
if (!
|
|
6932
|
+
if (!existsSync44(repo)) die(`repo not cloned at ${repo}`);
|
|
6688
6933
|
const ts = freshBackupTs(backupBase());
|
|
6689
|
-
const mapPath =
|
|
6690
|
-
const map =
|
|
6934
|
+
const mapPath = join52(repo, "path-map.json");
|
|
6935
|
+
const map = existsSync44(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
6691
6936
|
computePreview(ts, map, "diff");
|
|
6692
6937
|
} catch (err) {
|
|
6693
6938
|
if (err instanceof NomadFatal) {
|
|
@@ -6701,8 +6946,8 @@ function cmdDiff() {
|
|
|
6701
6946
|
|
|
6702
6947
|
// src/init.ts
|
|
6703
6948
|
init_config();
|
|
6704
|
-
import { existsSync as
|
|
6705
|
-
import { join as
|
|
6949
|
+
import { existsSync as existsSync46, mkdirSync as mkdirSync12, writeFileSync as writeFileSync6 } from "node:fs";
|
|
6950
|
+
import { join as join54 } from "node:path";
|
|
6706
6951
|
|
|
6707
6952
|
// src/init.gh-onboard.ts
|
|
6708
6953
|
init_config();
|
|
@@ -6784,33 +7029,33 @@ init_config();
|
|
|
6784
7029
|
init_utils();
|
|
6785
7030
|
init_utils_fs();
|
|
6786
7031
|
init_utils_json();
|
|
6787
|
-
import { copyFileSync as copyFileSync2, cpSync as cpSync7, existsSync as
|
|
6788
|
-
import { join as
|
|
7032
|
+
import { copyFileSync as copyFileSync2, cpSync as cpSync7, existsSync as existsSync45, rmSync as rmSync15, statSync as statSync12 } from "node:fs";
|
|
7033
|
+
import { join as join53 } from "node:path";
|
|
6789
7034
|
function snapshotIntoShared(map) {
|
|
6790
7035
|
const repo = repoHome();
|
|
6791
7036
|
const claude = claudeHome();
|
|
6792
7037
|
for (const name of allSharedLinks(map)) {
|
|
6793
|
-
const src =
|
|
6794
|
-
if (!
|
|
6795
|
-
const dst =
|
|
6796
|
-
if (
|
|
6797
|
-
const gk =
|
|
6798
|
-
if (
|
|
7038
|
+
const src = join53(claude, name);
|
|
7039
|
+
if (!existsSync45(src)) continue;
|
|
7040
|
+
const dst = join53(repo, "shared", name);
|
|
7041
|
+
if (statSync12(src).isDirectory()) {
|
|
7042
|
+
const gk = join53(dst, ".gitkeep");
|
|
7043
|
+
if (existsSync45(gk)) rmSync15(gk);
|
|
6799
7044
|
cpSync7(src, dst, { recursive: true, force: false, errorOnExist: true });
|
|
6800
7045
|
} else {
|
|
6801
7046
|
copyFileSync2(src, dst);
|
|
6802
7047
|
}
|
|
6803
7048
|
log(`snapshotted shared/${name} from ${src}`);
|
|
6804
7049
|
}
|
|
6805
|
-
const userSettings =
|
|
6806
|
-
if (
|
|
7050
|
+
const userSettings = join53(claude, "settings.json");
|
|
7051
|
+
if (existsSync45(userSettings)) {
|
|
6807
7052
|
let parsed;
|
|
6808
7053
|
try {
|
|
6809
7054
|
parsed = readJson(userSettings);
|
|
6810
7055
|
} catch (err) {
|
|
6811
7056
|
return die(`malformed ${userSettings}: ${err.message}`);
|
|
6812
7057
|
}
|
|
6813
|
-
const hostFile =
|
|
7058
|
+
const hostFile = join53(repo, "hosts", `${HOST}.json`);
|
|
6814
7059
|
writeJsonAtomic(hostFile, parsed);
|
|
6815
7060
|
log(`snapshotted hosts/${HOST}.json from ${userSettings}`);
|
|
6816
7061
|
}
|
|
@@ -6823,14 +7068,14 @@ var SHARED_CLAUDE_MD = "<!-- claude-nomad shared CLAUDE.md; symlinked into ~/.cl
|
|
|
6823
7068
|
var SHARED_KEEP_DIRS = ["agents", "skills", "commands", "rules", "hooks"];
|
|
6824
7069
|
function preflightConflict(repoHome2) {
|
|
6825
7070
|
const candidates = [
|
|
6826
|
-
|
|
6827
|
-
|
|
6828
|
-
|
|
6829
|
-
|
|
6830
|
-
|
|
7071
|
+
join54(repoHome2, "shared", "settings.base.json"),
|
|
7072
|
+
join54(repoHome2, "shared", "CLAUDE.md"),
|
|
7073
|
+
join54(repoHome2, "path-map.json"),
|
|
7074
|
+
join54(repoHome2, "hosts"),
|
|
7075
|
+
join54(repoHome2, "shared")
|
|
6831
7076
|
];
|
|
6832
7077
|
for (const c of candidates) {
|
|
6833
|
-
if (
|
|
7078
|
+
if (existsSync46(c)) return c;
|
|
6834
7079
|
}
|
|
6835
7080
|
return null;
|
|
6836
7081
|
}
|
|
@@ -6842,31 +7087,31 @@ function cmdInit(opts = {}) {
|
|
|
6842
7087
|
const keepActions = opts.keepActions === true;
|
|
6843
7088
|
const repo = repoHome();
|
|
6844
7089
|
const claude = claudeHome();
|
|
6845
|
-
|
|
7090
|
+
mkdirSync12(repo, { recursive: true });
|
|
6846
7091
|
const conflict = preflightConflict(repo);
|
|
6847
7092
|
if (conflict !== null) {
|
|
6848
7093
|
die(`already initialized; refusing to clobber ${conflict}`);
|
|
6849
7094
|
}
|
|
6850
7095
|
ensureOriginRepo(opts.repoName ?? DEFAULT_REPO_NAME, opts.run);
|
|
6851
|
-
|
|
6852
|
-
|
|
7096
|
+
mkdirSync12(join54(repo, "shared"), { recursive: true });
|
|
7097
|
+
mkdirSync12(join54(repo, "hosts"), { recursive: true });
|
|
6853
7098
|
for (const name of SHARED_KEEP_DIRS) {
|
|
6854
|
-
|
|
7099
|
+
mkdirSync12(join54(repo, "shared", name), { recursive: true });
|
|
6855
7100
|
}
|
|
6856
|
-
const userClaudeMd =
|
|
6857
|
-
if (!snapshot || !
|
|
6858
|
-
writeFileSync6(
|
|
7101
|
+
const userClaudeMd = join54(claude, "CLAUDE.md");
|
|
7102
|
+
if (!snapshot || !existsSync46(userClaudeMd)) {
|
|
7103
|
+
writeFileSync6(join54(repo, "shared", "CLAUDE.md"), SHARED_CLAUDE_MD);
|
|
6859
7104
|
item("created shared/CLAUDE.md");
|
|
6860
7105
|
}
|
|
6861
7106
|
for (const name of SHARED_KEEP_DIRS) {
|
|
6862
|
-
writeFileSync6(
|
|
7107
|
+
writeFileSync6(join54(repo, "shared", name, ".gitkeep"), "");
|
|
6863
7108
|
item(`created shared/${name}/.gitkeep`);
|
|
6864
7109
|
}
|
|
6865
|
-
writeFileSync6(
|
|
7110
|
+
writeFileSync6(join54(repo, "hosts", ".gitkeep"), "");
|
|
6866
7111
|
item("created hosts/.gitkeep");
|
|
6867
|
-
writeJsonAtomic(
|
|
7112
|
+
writeJsonAtomic(join54(repo, "shared", "settings.base.json"), {});
|
|
6868
7113
|
item("created shared/settings.base.json");
|
|
6869
|
-
writeJsonAtomic(
|
|
7114
|
+
writeJsonAtomic(join54(repo, "path-map.json"), { projects: {} });
|
|
6870
7115
|
item("created path-map.json");
|
|
6871
7116
|
if (snapshot) {
|
|
6872
7117
|
snapshotIntoShared({ projects: {} });
|
|
@@ -6932,21 +7177,21 @@ function maybeDisableRepoActions(repoHome2, run) {
|
|
|
6932
7177
|
// src/init.prompt.ts
|
|
6933
7178
|
init_config();
|
|
6934
7179
|
init_utils();
|
|
6935
|
-
import { existsSync as
|
|
6936
|
-
import { join as
|
|
7180
|
+
import { existsSync as existsSync47, readdirSync as readdirSync17, statSync as statSync13 } from "node:fs";
|
|
7181
|
+
import { join as join55 } from "node:path";
|
|
6937
7182
|
import { createInterface as createInterface3 } from "node:readline/promises";
|
|
6938
7183
|
function nonEmptyExists(path) {
|
|
6939
|
-
if (!
|
|
7184
|
+
if (!existsSync47(path)) return false;
|
|
6940
7185
|
try {
|
|
6941
|
-
if (
|
|
7186
|
+
if (statSync13(path).isDirectory()) return readdirSync17(path).length > 0;
|
|
6942
7187
|
return true;
|
|
6943
7188
|
} catch {
|
|
6944
7189
|
return false;
|
|
6945
7190
|
}
|
|
6946
7191
|
}
|
|
6947
7192
|
function hasExistingClaudeConfig(claudeHome2) {
|
|
6948
|
-
if (
|
|
6949
|
-
return SHARED_LINKS.some((name) => nonEmptyExists(
|
|
7193
|
+
if (existsSync47(join55(claudeHome2, "settings.json"))) return true;
|
|
7194
|
+
return SHARED_LINKS.some((name) => nonEmptyExists(join55(claudeHome2, name)));
|
|
6950
7195
|
}
|
|
6951
7196
|
async function confirmSnapshotDefault(claudeHome2) {
|
|
6952
7197
|
if (process.stdin.isTTY !== true || process.stdout.isTTY !== true) {
|
|
@@ -7196,6 +7441,8 @@ function applyPushToken(argv, i, st) {
|
|
|
7196
7441
|
return applyBool(st.allowAll, () => st.allowAll = true);
|
|
7197
7442
|
case "--allow":
|
|
7198
7443
|
return applyAllow2(argv, i, st);
|
|
7444
|
+
case "--full-scan":
|
|
7445
|
+
return applyBool(st.fullScan, () => st.fullScan = true);
|
|
7199
7446
|
default:
|
|
7200
7447
|
return REJECT;
|
|
7201
7448
|
}
|
|
@@ -7205,7 +7452,8 @@ function parsePushArgs(argv) {
|
|
|
7205
7452
|
dryRun: false,
|
|
7206
7453
|
redactAll: false,
|
|
7207
7454
|
allowAll: false,
|
|
7208
|
-
allowRule: void 0
|
|
7455
|
+
allowRule: void 0,
|
|
7456
|
+
fullScan: false
|
|
7209
7457
|
};
|
|
7210
7458
|
let i = 3;
|
|
7211
7459
|
while (i < argv.length) {
|
|
@@ -7222,14 +7470,15 @@ function parsePushArgs(argv) {
|
|
|
7222
7470
|
dryRun: st.dryRun,
|
|
7223
7471
|
redactAll: st.redactAll,
|
|
7224
7472
|
allowAll: st.allowAll,
|
|
7225
|
-
allowRule: st.allowRule
|
|
7473
|
+
allowRule: st.allowRule,
|
|
7474
|
+
fullScan: st.fullScan
|
|
7226
7475
|
};
|
|
7227
7476
|
}
|
|
7228
7477
|
|
|
7229
7478
|
// package.json
|
|
7230
7479
|
var package_default = {
|
|
7231
7480
|
name: "claude-nomad",
|
|
7232
|
-
version: "0.
|
|
7481
|
+
version: "0.56.0",
|
|
7233
7482
|
type: "module",
|
|
7234
7483
|
description: "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
|
|
7235
7484
|
keywords: [
|
|
@@ -7337,6 +7586,10 @@ var DEFAULT_HELP = [
|
|
|
7337
7586
|
row(" push", "Rebase, run safety checks (gitleaks, gitlinks, allow-list), commit, push."),
|
|
7338
7587
|
row(" --dry-run", "Run pre-checks (rebase, gitleaks probe, gitlink scan) and preview"),
|
|
7339
7588
|
cont("remap, without staging or pushing."),
|
|
7589
|
+
row(" --full-scan", "Ignore the per-host push manifest and rescan all transcripts."),
|
|
7590
|
+
cont("On a successful non-dry-run push, rewrite the manifest from that full"),
|
|
7591
|
+
cont("rescan. Use after a gitleaks upgrade or when in doubt. Composes freely"),
|
|
7592
|
+
cont("with --dry-run and all resolution modes."),
|
|
7340
7593
|
row(" --redact-all", "Redact all findings non-interactively (backup, no prompt); no TTY"),
|
|
7341
7594
|
cont("required. Does not auto-Allow. Mutually exclusive with --allow*."),
|
|
7342
7595
|
cont("Cannot combine with --dry-run."),
|
|
@@ -7450,15 +7703,15 @@ var DEFAULT_HELP = [
|
|
|
7450
7703
|
init_config();
|
|
7451
7704
|
init_utils();
|
|
7452
7705
|
init_utils_json();
|
|
7453
|
-
import { existsSync as
|
|
7454
|
-
import { join as
|
|
7706
|
+
import { existsSync as existsSync48, readFileSync as readFileSync15, readdirSync as readdirSync18 } from "node:fs";
|
|
7707
|
+
import { join as join56 } from "node:path";
|
|
7455
7708
|
function resumeCmd(sessionId) {
|
|
7456
7709
|
if (!/^[A-Za-z0-9_-]+$/.test(sessionId) || sessionId.length > 128) {
|
|
7457
7710
|
fail(`invalid session id: ${sessionId}`);
|
|
7458
7711
|
process.exit(1);
|
|
7459
7712
|
}
|
|
7460
|
-
const projectsRoot =
|
|
7461
|
-
if (!
|
|
7713
|
+
const projectsRoot = join56(claudeHome(), "projects");
|
|
7714
|
+
if (!existsSync48(projectsRoot)) {
|
|
7462
7715
|
fail(`${projectsRoot} does not exist`);
|
|
7463
7716
|
process.exit(1);
|
|
7464
7717
|
}
|
|
@@ -7472,8 +7725,8 @@ function resumeCmd(sessionId) {
|
|
|
7472
7725
|
fail(`no cwd field found in ${jsonlPath}`);
|
|
7473
7726
|
process.exit(1);
|
|
7474
7727
|
}
|
|
7475
|
-
const mapPath =
|
|
7476
|
-
if (!
|
|
7728
|
+
const mapPath = join56(repoHome(), "path-map.json");
|
|
7729
|
+
if (!existsSync48(mapPath)) {
|
|
7477
7730
|
fail("path-map.json missing");
|
|
7478
7731
|
process.exit(1);
|
|
7479
7732
|
}
|
|
@@ -7495,14 +7748,14 @@ function resumeCmd(sessionId) {
|
|
|
7495
7748
|
console.log(`cd ${shQuote(hit.localPath)} && claude --resume ${shQuote(sessionId)}`);
|
|
7496
7749
|
}
|
|
7497
7750
|
function findTranscriptPath(projectsRoot, sessionId) {
|
|
7498
|
-
for (const dir of
|
|
7499
|
-
const candidate =
|
|
7500
|
-
if (
|
|
7751
|
+
for (const dir of readdirSync18(projectsRoot)) {
|
|
7752
|
+
const candidate = join56(projectsRoot, dir, `${sessionId}.jsonl`);
|
|
7753
|
+
if (existsSync48(candidate)) return candidate;
|
|
7501
7754
|
}
|
|
7502
7755
|
return null;
|
|
7503
7756
|
}
|
|
7504
7757
|
function extractRecordedCwd(jsonlPath) {
|
|
7505
|
-
for (const line of
|
|
7758
|
+
for (const line of readFileSync15(jsonlPath, "utf8").split("\n")) {
|
|
7506
7759
|
if (!line.trim()) continue;
|
|
7507
7760
|
try {
|
|
7508
7761
|
const obj = JSON.parse(line);
|
|
@@ -7562,7 +7815,7 @@ try {
|
|
|
7562
7815
|
const pushArgs = parsePushArgs(process.argv);
|
|
7563
7816
|
if (pushArgs === null) {
|
|
7564
7817
|
console.error(
|
|
7565
|
-
"usage: nomad push [--dry-run] [--redact-all] [--allow <rule>] [--allow-all]"
|
|
7818
|
+
"usage: nomad push [--dry-run] [--full-scan] [--redact-all] [--allow <rule>] [--allow-all]"
|
|
7566
7819
|
);
|
|
7567
7820
|
process.exit(1);
|
|
7568
7821
|
}
|
|
@@ -7570,7 +7823,8 @@ try {
|
|
|
7570
7823
|
dryRun: pushArgs.dryRun,
|
|
7571
7824
|
redactAll: pushArgs.redactAll,
|
|
7572
7825
|
allowAll: pushArgs.allowAll,
|
|
7573
|
-
allowRule: pushArgs.allowRule
|
|
7826
|
+
allowRule: pushArgs.allowRule,
|
|
7827
|
+
fullScan: pushArgs.fullScan
|
|
7574
7828
|
});
|
|
7575
7829
|
break;
|
|
7576
7830
|
}
|