claude-nomad 0.55.0 → 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 +43 -0
- package/README.md +7 -1
- package/dist/nomad.mjs +676 -402
- 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 ?? []) {
|
|
@@ -423,7 +426,7 @@ function allSharedLinks(map) {
|
|
|
423
426
|
}
|
|
424
427
|
return [...SHARED_LINKS, ...extras];
|
|
425
428
|
}
|
|
426
|
-
var SETTINGS_SCHEMA_URL, NPM_REGISTRY_LATEST_URL, GITLEAKS_PINNED_VERSION, HOST, SHARED_LINKS, GSD_PREFIX, GSD_DROPPED_NAMES, SUPPORTED_EXTRAS, ALWAYS_NEVER_SYNC, PUSH_ALLOWED_STATIC;
|
|
429
|
+
var SETTINGS_SCHEMA_URL, NPM_REGISTRY_LATEST_URL, GITLEAKS_PINNED_VERSION, GITLEAKS_SCAN_TIMEOUT_MS, HOST, SHARED_LINKS, GSD_PREFIX, GSD_DROPPED_NAMES, SUPPORTED_EXTRAS, ALWAYS_NEVER_SYNC, PUSH_ALLOWED_STATIC;
|
|
427
430
|
var init_config = __esm({
|
|
428
431
|
"src/config.ts"() {
|
|
429
432
|
"use strict";
|
|
@@ -434,6 +437,7 @@ var init_config = __esm({
|
|
|
434
437
|
SETTINGS_SCHEMA_URL = "https://json.schemastore.org/claude-code-settings.json";
|
|
435
438
|
NPM_REGISTRY_LATEST_URL = "https://registry.npmjs.org/claude-nomad/latest";
|
|
436
439
|
GITLEAKS_PINNED_VERSION = "8.30.1";
|
|
440
|
+
GITLEAKS_SCAN_TIMEOUT_MS = 9e5;
|
|
437
441
|
HOST = (process.env.NOMAD_HOST || hostname()).toLowerCase();
|
|
438
442
|
SHARED_LINKS = ["CLAUDE.md", "commands", "rules", "my-statusline.cjs"];
|
|
439
443
|
GSD_PREFIX = "gsd-";
|
|
@@ -523,13 +527,23 @@ function sortKeysDeep(value) {
|
|
|
523
527
|
}
|
|
524
528
|
return value;
|
|
525
529
|
}
|
|
526
|
-
var encodePath;
|
|
530
|
+
var ENCODE_MAX, hashPath, encodePath;
|
|
527
531
|
var init_utils_json = __esm({
|
|
528
532
|
"src/utils.json.ts"() {
|
|
529
533
|
"use strict";
|
|
530
534
|
init_config();
|
|
531
535
|
init_utils();
|
|
532
|
-
|
|
536
|
+
ENCODE_MAX = 200;
|
|
537
|
+
hashPath = (s) => {
|
|
538
|
+
let h2 = 0;
|
|
539
|
+
for (let i = 0; i < s.length; i++) h2 = (h2 << 5) - h2 + s.charCodeAt(i) | 0;
|
|
540
|
+
return h2;
|
|
541
|
+
};
|
|
542
|
+
encodePath = (absPath) => {
|
|
543
|
+
const enc = absPath.replace(/[^a-zA-Z0-9]/g, "-");
|
|
544
|
+
if (enc.length <= ENCODE_MAX) return enc;
|
|
545
|
+
return `${enc.slice(0, ENCODE_MAX)}-${Math.abs(hashPath(absPath)).toString(36)}`;
|
|
546
|
+
};
|
|
533
547
|
}
|
|
534
548
|
});
|
|
535
549
|
|
|
@@ -769,7 +783,7 @@ var init_push_gitleaks_config = __esm({
|
|
|
769
783
|
"use strict";
|
|
770
784
|
init_config();
|
|
771
785
|
init_utils();
|
|
772
|
-
OVERLAY_EXTEND_RE =
|
|
786
|
+
OVERLAY_EXTEND_RE = /^[ \t]*(?:\[[ \t]*extend[ \t]*\]|extend[ \t]*[.=])/m;
|
|
773
787
|
TABLE_HEADER_RE = /^\s*\[/;
|
|
774
788
|
OVERLAY_ALLOWLIST_HEADER_RE = /^\s*\[\[?\s*allowlists?\s*\]\]?/;
|
|
775
789
|
OVERLAY_INLINE_ALLOWLIST_RE = /^[ \t]*allowlists?[ \t]*[.=]/m;
|
|
@@ -912,10 +926,11 @@ function scanStagedTree(repoDir, forwardStreams = false) {
|
|
|
912
926
|
];
|
|
913
927
|
if (toml !== null) args.push("--config", toml);
|
|
914
928
|
const opts = { cwd: repoDir, stdio: ["ignore", "pipe", "pipe"] };
|
|
929
|
+
const gitleaksOpts = { ...opts, timeout: GITLEAKS_SCAN_TIMEOUT_MS };
|
|
915
930
|
try {
|
|
916
931
|
execFileSync8("git", ["init", "-q"], opts);
|
|
917
932
|
execFileSync8("git", ["add", "-A"], opts);
|
|
918
|
-
execFileSync8("gitleaks", args,
|
|
933
|
+
execFileSync8("gitleaks", args, gitleaksOpts);
|
|
919
934
|
return [];
|
|
920
935
|
} catch (err) {
|
|
921
936
|
const e = err;
|
|
@@ -945,7 +960,10 @@ function scanFile(filePath, forwardStreams = false) {
|
|
|
945
960
|
`--report-path=${reportPath}`
|
|
946
961
|
];
|
|
947
962
|
if (toml !== null) args.push("--config", toml);
|
|
948
|
-
const opts = {
|
|
963
|
+
const opts = {
|
|
964
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
965
|
+
timeout: GITLEAKS_SCAN_TIMEOUT_MS
|
|
966
|
+
};
|
|
949
967
|
try {
|
|
950
968
|
execFileSync8("gitleaks", args, opts);
|
|
951
969
|
return [];
|
|
@@ -966,6 +984,7 @@ function scanFile(filePath, forwardStreams = false) {
|
|
|
966
984
|
var init_push_gitleaks_scan = __esm({
|
|
967
985
|
"src/push-gitleaks.scan.ts"() {
|
|
968
986
|
"use strict";
|
|
987
|
+
init_config();
|
|
969
988
|
init_push_gitleaks_config();
|
|
970
989
|
init_utils_fs();
|
|
971
990
|
}
|
|
@@ -2073,8 +2092,8 @@ function cmdEject(opts = {}, roots = defaultEjectRoots()) {
|
|
|
2073
2092
|
}
|
|
2074
2093
|
|
|
2075
2094
|
// src/commands.doctor.ts
|
|
2076
|
-
import { existsSync as
|
|
2077
|
-
import { join as
|
|
2095
|
+
import { existsSync as existsSync31 } from "node:fs";
|
|
2096
|
+
import { join as join37 } from "node:path";
|
|
2078
2097
|
|
|
2079
2098
|
// src/commands.doctor.checks.repo.ts
|
|
2080
2099
|
init_color();
|
|
@@ -2554,15 +2573,15 @@ function stripSideIndicator(line) {
|
|
|
2554
2573
|
}
|
|
2555
2574
|
function isGsdDiffLine(line, localBase, sharedBase) {
|
|
2556
2575
|
const bare = stripSideIndicator(line);
|
|
2557
|
-
let
|
|
2576
|
+
let relative7;
|
|
2558
2577
|
if (bare.startsWith(localBase + "/")) {
|
|
2559
|
-
|
|
2578
|
+
relative7 = bare.slice(localBase.length + 1);
|
|
2560
2579
|
} else if (bare.startsWith(sharedBase + "/")) {
|
|
2561
|
-
|
|
2580
|
+
relative7 = bare.slice(sharedBase.length + 1);
|
|
2562
2581
|
} else {
|
|
2563
|
-
|
|
2582
|
+
relative7 = bare;
|
|
2564
2583
|
}
|
|
2565
|
-
return
|
|
2584
|
+
return relative7.split("/")[0].startsWith(GSD_PREFIX);
|
|
2566
2585
|
}
|
|
2567
2586
|
function reportSkillsDivergence(section2) {
|
|
2568
2587
|
const sharedSkills = join14(repoHome(), "shared", "skills");
|
|
@@ -2899,9 +2918,9 @@ function reportCheckSchema(section2) {
|
|
|
2899
2918
|
init_color();
|
|
2900
2919
|
import { randomBytes } from "node:crypto";
|
|
2901
2920
|
import { execFileSync as execFileSync9 } from "node:child_process";
|
|
2902
|
-
import { existsSync as
|
|
2921
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync6, readdirSync as readdirSync9, rmSync as rmSync8 } from "node:fs";
|
|
2903
2922
|
import { homedir as homedir4 } from "node:os";
|
|
2904
|
-
import { join as
|
|
2923
|
+
import { join as join25 } from "node:path";
|
|
2905
2924
|
|
|
2906
2925
|
// src/commands.doctor.check-shared.scan.ts
|
|
2907
2926
|
init_color();
|
|
@@ -2998,8 +3017,8 @@ init_config();
|
|
|
2998
3017
|
|
|
2999
3018
|
// src/remap.ts
|
|
3000
3019
|
init_config_sharedDirs_guard();
|
|
3001
|
-
import { cpSync as cpSync4, existsSync as
|
|
3002
|
-
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";
|
|
3003
3022
|
|
|
3004
3023
|
// src/extras-sync.guards.ts
|
|
3005
3024
|
init_utils();
|
|
@@ -3020,6 +3039,114 @@ function assertSafeLocalRoot(localRoot, logical) {
|
|
|
3020
3039
|
|
|
3021
3040
|
// src/remap.ts
|
|
3022
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
|
|
3023
3150
|
init_utils();
|
|
3024
3151
|
init_utils_fs();
|
|
3025
3152
|
init_utils_json();
|
|
@@ -3034,6 +3161,33 @@ function atomicMirror(src, dst, options) {
|
|
|
3034
3161
|
function copyDir(src, dst) {
|
|
3035
3162
|
atomicMirror(src, dst, { recursive: true, force: true });
|
|
3036
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
|
+
}
|
|
3037
3191
|
function copyDirJsonlOnly(src, dst) {
|
|
3038
3192
|
atomicMirror(src, dst, {
|
|
3039
3193
|
recursive: true,
|
|
@@ -3042,7 +3196,7 @@ function copyDirJsonlOnly(src, dst) {
|
|
|
3042
3196
|
const rel = relative3(src, srcPath);
|
|
3043
3197
|
if (rel === "") return true;
|
|
3044
3198
|
if (rel.split(sep3).length > 1) return true;
|
|
3045
|
-
if (
|
|
3199
|
+
if (statSync5(srcPath).isDirectory()) return true;
|
|
3046
3200
|
if (srcPath.endsWith(".jsonl")) return true;
|
|
3047
3201
|
item(`skip ${rel}: extension not in allowlist`);
|
|
3048
3202
|
return false;
|
|
@@ -3060,16 +3214,16 @@ function remapPull(ts, opts = {}) {
|
|
|
3060
3214
|
const wouldPull = [];
|
|
3061
3215
|
const repo = repoHome();
|
|
3062
3216
|
const claude = claudeHome();
|
|
3063
|
-
const mapPath =
|
|
3064
|
-
const repoProjects =
|
|
3065
|
-
if (!
|
|
3217
|
+
const mapPath = join24(repo, "path-map.json");
|
|
3218
|
+
const repoProjects = join24(repo, "shared", "projects");
|
|
3219
|
+
if (!existsSync19(mapPath) || !existsSync19(repoProjects)) {
|
|
3066
3220
|
const text = "no path-map or repo projects dir; skipping session remap";
|
|
3067
3221
|
emitPreview(opts.onPreview, { kind: "note", text }, text);
|
|
3068
3222
|
return { unmapped: 0, pulled, wouldPull };
|
|
3069
3223
|
}
|
|
3070
3224
|
const map = readPathMap(mapPath);
|
|
3071
|
-
const localProjects =
|
|
3072
|
-
if (!dryRun)
|
|
3225
|
+
const localProjects = join24(claude, "projects");
|
|
3226
|
+
if (!dryRun) mkdirSync5(localProjects, { recursive: true });
|
|
3073
3227
|
for (const [logical, hosts] of Object.entries(map.projects)) {
|
|
3074
3228
|
assertSafeLogical(logical);
|
|
3075
3229
|
const localPath = hosts[HOST];
|
|
@@ -3078,9 +3232,9 @@ function remapPull(ts, opts = {}) {
|
|
|
3078
3232
|
continue;
|
|
3079
3233
|
}
|
|
3080
3234
|
assertSafeLocalRoot(localPath, logical);
|
|
3081
|
-
const src =
|
|
3082
|
-
if (!
|
|
3083
|
-
const dst =
|
|
3235
|
+
const src = join24(repoProjects, logical);
|
|
3236
|
+
if (!existsSync19(src)) continue;
|
|
3237
|
+
const dst = join24(localProjects, encodePath(localPath));
|
|
3084
3238
|
if (dryRun) {
|
|
3085
3239
|
wouldPull.push(logical);
|
|
3086
3240
|
emitPreview(
|
|
@@ -3127,31 +3281,37 @@ function remapPush(ts, opts = {}) {
|
|
|
3127
3281
|
const wouldPush = [];
|
|
3128
3282
|
const repo = repoHome();
|
|
3129
3283
|
const claude = claudeHome();
|
|
3130
|
-
const mapPath =
|
|
3131
|
-
if (!
|
|
3284
|
+
const mapPath = join24(repo, "path-map.json");
|
|
3285
|
+
if (!existsSync19(mapPath)) {
|
|
3132
3286
|
log("no path-map.json; skipping session export");
|
|
3133
3287
|
return { unmapped: 0, collisions: 0, pushed, wouldPush };
|
|
3134
3288
|
}
|
|
3135
3289
|
const map = readPathMap(mapPath);
|
|
3136
|
-
const localProjects =
|
|
3137
|
-
const repoProjects =
|
|
3290
|
+
const localProjects = join24(claude, "projects");
|
|
3291
|
+
const repoProjects = join24(repo, "shared", "projects");
|
|
3138
3292
|
const reverse = buildReverseMap(map);
|
|
3139
|
-
if (!
|
|
3140
|
-
if (!dryRun)
|
|
3141
|
-
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)) {
|
|
3142
3296
|
if (dir.endsWith(TMP_SUFFIX)) continue;
|
|
3143
3297
|
const logical = reverse.get(dir);
|
|
3144
3298
|
if (!logical) {
|
|
3145
3299
|
unmapped++;
|
|
3146
3300
|
continue;
|
|
3147
3301
|
}
|
|
3148
|
-
const
|
|
3302
|
+
const localDir = join24(localProjects, dir);
|
|
3303
|
+
const repoDst = join24(repoProjects, logical);
|
|
3304
|
+
if (skipForSelection(opts.selection, localDir)) continue;
|
|
3149
3305
|
if (dryRun) {
|
|
3150
3306
|
wouldPush.push(logical);
|
|
3151
3307
|
continue;
|
|
3152
3308
|
}
|
|
3153
3309
|
backupRepoWrite(repoDst, ts, repo);
|
|
3154
|
-
|
|
3310
|
+
if (opts.selection) {
|
|
3311
|
+
applySelective(opts.selection, localDir, repoDst);
|
|
3312
|
+
} else {
|
|
3313
|
+
copyDirJsonlOnly(localDir, repoDst);
|
|
3314
|
+
}
|
|
3155
3315
|
pushed.push(logical);
|
|
3156
3316
|
}
|
|
3157
3317
|
return { unmapped, collisions: 0, pushed, wouldPush };
|
|
@@ -3164,8 +3324,8 @@ function buildScanTree(tmpRoot) {
|
|
|
3164
3324
|
const logicalToEncoded = /* @__PURE__ */ new Map();
|
|
3165
3325
|
let staged = 0;
|
|
3166
3326
|
const repo = repoHome();
|
|
3167
|
-
const mapPath =
|
|
3168
|
-
if (!
|
|
3327
|
+
const mapPath = join25(repo, "path-map.json");
|
|
3328
|
+
if (!existsSync20(mapPath)) return { logicalToEncoded, staged, malformed: false };
|
|
3169
3329
|
let map;
|
|
3170
3330
|
try {
|
|
3171
3331
|
map = readJson(mapPath);
|
|
@@ -3182,12 +3342,12 @@ function buildScanTree(tmpRoot) {
|
|
|
3182
3342
|
if (!p || p === "TBD") continue;
|
|
3183
3343
|
reverse.set(encodePath(p), logical);
|
|
3184
3344
|
}
|
|
3185
|
-
const localProjects =
|
|
3186
|
-
if (!
|
|
3187
|
-
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)) {
|
|
3188
3348
|
const logical = reverse.get(dir);
|
|
3189
3349
|
if (!logical) continue;
|
|
3190
|
-
copyDirJsonlOnly(
|
|
3350
|
+
copyDirJsonlOnly(join25(localProjects, dir), join25(tmpRoot, "shared", "projects", logical));
|
|
3191
3351
|
logicalToEncoded.set(logical, dir);
|
|
3192
3352
|
staged++;
|
|
3193
3353
|
}
|
|
@@ -3218,11 +3378,11 @@ function ensureGitleaksReady(section2, gitleaksReady) {
|
|
|
3218
3378
|
}
|
|
3219
3379
|
function reportCheckShared(section2, gitleaksReady) {
|
|
3220
3380
|
if (!ensureGitleaksReady(section2, gitleaksReady)) return;
|
|
3221
|
-
const cacheDir =
|
|
3222
|
-
|
|
3381
|
+
const cacheDir = join25(homedir4(), ".cache", "claude-nomad");
|
|
3382
|
+
mkdirSync6(cacheDir, { recursive: true });
|
|
3223
3383
|
const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes(4).toString("hex")}`;
|
|
3224
|
-
const reportPath =
|
|
3225
|
-
const tmpRoot =
|
|
3384
|
+
const reportPath = join25(cacheDir, `check-shared-${stamp}.json`);
|
|
3385
|
+
const tmpRoot = join25(cacheDir, `check-shared-tree-${stamp}`);
|
|
3226
3386
|
try {
|
|
3227
3387
|
const { logicalToEncoded, staged, malformed } = buildScanTree(tmpRoot);
|
|
3228
3388
|
if (malformed) {
|
|
@@ -3243,12 +3403,12 @@ function reportCheckShared(section2, gitleaksReady) {
|
|
|
3243
3403
|
|
|
3244
3404
|
// src/commands.doctor.checks.hooks.scope.ts
|
|
3245
3405
|
init_color();
|
|
3246
|
-
import { existsSync as
|
|
3247
|
-
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";
|
|
3248
3408
|
init_config();
|
|
3249
3409
|
function typeFromPackageJson(pkgPath) {
|
|
3250
3410
|
try {
|
|
3251
|
-
const parsed = JSON.parse(
|
|
3411
|
+
const parsed = JSON.parse(readFileSync7(pkgPath, "utf8"));
|
|
3252
3412
|
return parsed.type === "module" ? "esm" : "cjs";
|
|
3253
3413
|
} catch {
|
|
3254
3414
|
return "cjs";
|
|
@@ -3264,11 +3424,11 @@ function effectiveType(hookPath) {
|
|
|
3264
3424
|
} catch {
|
|
3265
3425
|
return null;
|
|
3266
3426
|
}
|
|
3267
|
-
let dir =
|
|
3427
|
+
let dir = dirname5(real);
|
|
3268
3428
|
for (; ; ) {
|
|
3269
|
-
const pkg =
|
|
3270
|
-
if (
|
|
3271
|
-
const parent =
|
|
3429
|
+
const pkg = join26(dir, "package.json");
|
|
3430
|
+
if (existsSync21(pkg)) return typeFromPackageJson(pkg);
|
|
3431
|
+
const parent = dirname5(dir);
|
|
3272
3432
|
if (parent === dir) return "cjs";
|
|
3273
3433
|
dir = parent;
|
|
3274
3434
|
}
|
|
@@ -3285,7 +3445,7 @@ function stripCommentsAndStrings(src) {
|
|
|
3285
3445
|
function classifySource(src) {
|
|
3286
3446
|
const code = stripCommentsAndStrings(src);
|
|
3287
3447
|
const cjs = /\brequire\s*\(/.test(code) || /\bmodule\.exports\b/.test(code) || /\bexports\.\w/.test(code);
|
|
3288
|
-
const esm = /^[
|
|
3448
|
+
const esm = /^[ \t]*import\s/m.test(code) || /^[ \t]*export\s/m.test(code) || /\bimport\.meta\b/.test(code);
|
|
3289
3449
|
if (cjs && !esm) return "cjs";
|
|
3290
3450
|
if (esm && !cjs) return "esm";
|
|
3291
3451
|
if (cjs && esm) return "cjs";
|
|
@@ -3296,28 +3456,28 @@ function remedy(family) {
|
|
|
3296
3456
|
}
|
|
3297
3457
|
function safeReaddir2(dir) {
|
|
3298
3458
|
try {
|
|
3299
|
-
return
|
|
3459
|
+
return readdirSync10(dir);
|
|
3300
3460
|
} catch {
|
|
3301
3461
|
return [];
|
|
3302
3462
|
}
|
|
3303
3463
|
}
|
|
3304
3464
|
function safeRead(path) {
|
|
3305
3465
|
try {
|
|
3306
|
-
return
|
|
3466
|
+
return readFileSync7(path, "utf8");
|
|
3307
3467
|
} catch {
|
|
3308
3468
|
return null;
|
|
3309
3469
|
}
|
|
3310
3470
|
}
|
|
3311
3471
|
function reportHookScopeCheck(section2) {
|
|
3312
|
-
const hooksDir =
|
|
3313
|
-
if (!
|
|
3472
|
+
const hooksDir = join26(claudeHome(), "hooks");
|
|
3473
|
+
if (!existsSync21(hooksDir)) {
|
|
3314
3474
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/hooks; skipping module-scope check`);
|
|
3315
3475
|
return;
|
|
3316
3476
|
}
|
|
3317
3477
|
let anyWarn = false;
|
|
3318
3478
|
for (const name of safeReaddir2(hooksDir)) {
|
|
3319
3479
|
if (extname(name) !== ".js") continue;
|
|
3320
|
-
const abs =
|
|
3480
|
+
const abs = join26(hooksDir, name);
|
|
3321
3481
|
const eff = effectiveType(abs);
|
|
3322
3482
|
if (eff === null) continue;
|
|
3323
3483
|
const src = safeRead(abs);
|
|
@@ -3337,15 +3497,19 @@ function reportHookScopeCheck(section2) {
|
|
|
3337
3497
|
|
|
3338
3498
|
// src/commands.doctor.checks.hooks.ts
|
|
3339
3499
|
init_color();
|
|
3340
|
-
import { existsSync as
|
|
3341
|
-
import { join as
|
|
3500
|
+
import { existsSync as existsSync22 } from "node:fs";
|
|
3501
|
+
import { join as join27 } from "node:path";
|
|
3342
3502
|
init_config();
|
|
3343
3503
|
function expandHome(token) {
|
|
3344
3504
|
const h2 = home();
|
|
3345
3505
|
return token.replace(/^\$\{HOME\}/, h2).replace(/^\$HOME/, h2).replace(/^~/, h2);
|
|
3346
3506
|
}
|
|
3507
|
+
var TRAILING_SHELL_PUNCT = /* @__PURE__ */ new Set(["'", '"', "`", ";", ")", "|", "&", ">"]);
|
|
3347
3508
|
function stripShellPunctuation(token) {
|
|
3348
|
-
|
|
3509
|
+
const stripped = token.replace(/^['"]+/, "");
|
|
3510
|
+
let end = stripped.length;
|
|
3511
|
+
while (end > 0 && TRAILING_SHELL_PUNCT.has(stripped[end - 1])) end--;
|
|
3512
|
+
return stripped.slice(0, end);
|
|
3349
3513
|
}
|
|
3350
3514
|
function* claudePathsIn(command) {
|
|
3351
3515
|
const claudePrefix = `${claudeHome()}/`;
|
|
@@ -3377,7 +3541,7 @@ function checkEventGroups(section2, event, groups) {
|
|
|
3377
3541
|
for (const group of groups) {
|
|
3378
3542
|
for (const cmd of commandsFromGroup(group)) {
|
|
3379
3543
|
for (const resolved of claudePathsIn(cmd)) {
|
|
3380
|
-
if (
|
|
3544
|
+
if (existsSync22(resolved)) continue;
|
|
3381
3545
|
addItem(
|
|
3382
3546
|
section2,
|
|
3383
3547
|
`${red(failGlyph)} hooks/${event}: command target missing: ${resolved} (run nomad pull)`
|
|
@@ -3390,8 +3554,8 @@ function checkEventGroups(section2, event, groups) {
|
|
|
3390
3554
|
return anyFail;
|
|
3391
3555
|
}
|
|
3392
3556
|
function reportHooksTargetCheck(section2) {
|
|
3393
|
-
const settingsPath =
|
|
3394
|
-
if (!
|
|
3557
|
+
const settingsPath = join27(claudeHome(), "settings.json");
|
|
3558
|
+
if (!existsSync22(settingsPath)) {
|
|
3395
3559
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json; skipping hook target check`);
|
|
3396
3560
|
return;
|
|
3397
3561
|
}
|
|
@@ -3414,12 +3578,12 @@ function reportHooksTargetCheck(section2) {
|
|
|
3414
3578
|
|
|
3415
3579
|
// src/commands.doctor.checks.hooks.preserve-symlinks.ts
|
|
3416
3580
|
init_color();
|
|
3417
|
-
import { existsSync as
|
|
3418
|
-
import { join as
|
|
3581
|
+
import { existsSync as existsSync24, readFileSync as readFileSync8 } from "node:fs";
|
|
3582
|
+
import { join as join29 } from "node:path";
|
|
3419
3583
|
|
|
3420
3584
|
// src/commands.doctor.checks.hooks.preserve-symlinks.probe.ts
|
|
3421
|
-
import { closeSync as closeSync3, existsSync as
|
|
3422
|
-
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";
|
|
3423
3587
|
function suppressedRanges(src) {
|
|
3424
3588
|
const ranges = [];
|
|
3425
3589
|
const re = /\/\*[\s\S]*?\*\/|\/\/[^\n]*|'[^']*'|"[^"]*"|`[^`]*`/g;
|
|
@@ -3451,12 +3615,12 @@ function topRelativeSpecifiers(src) {
|
|
|
3451
3615
|
}
|
|
3452
3616
|
function specifierIsMissing(specifier, baseDir) {
|
|
3453
3617
|
const base = resolve2(baseDir, specifier);
|
|
3454
|
-
if (
|
|
3618
|
+
if (existsSync23(base)) return false;
|
|
3455
3619
|
for (const ext of [".js", ".cjs", ".mjs"]) {
|
|
3456
|
-
if (
|
|
3620
|
+
if (existsSync23(base + ext)) return false;
|
|
3457
3621
|
}
|
|
3458
3622
|
for (const idx of ["index.js", "index.cjs", "index.mjs"]) {
|
|
3459
|
-
if (
|
|
3623
|
+
if (existsSync23(join28(base, idx))) return false;
|
|
3460
3624
|
}
|
|
3461
3625
|
return true;
|
|
3462
3626
|
}
|
|
@@ -3482,7 +3646,7 @@ function relativeRequireTargetsBroken(scriptPath) {
|
|
|
3482
3646
|
}
|
|
3483
3647
|
const specifiers = topRelativeSpecifiers(raw);
|
|
3484
3648
|
if (specifiers.length === 0) return false;
|
|
3485
|
-
const baseDir =
|
|
3649
|
+
const baseDir = dirname6(realPath);
|
|
3486
3650
|
for (const spec of specifiers) {
|
|
3487
3651
|
if (specifierIsMissing(spec, baseDir)) return true;
|
|
3488
3652
|
}
|
|
@@ -3511,10 +3675,10 @@ function commandTokens(command) {
|
|
|
3511
3675
|
return tokens;
|
|
3512
3676
|
}
|
|
3513
3677
|
function readPathMapSafe() {
|
|
3514
|
-
const mapPath =
|
|
3515
|
-
if (!
|
|
3678
|
+
const mapPath = join29(repoHome(), "path-map.json");
|
|
3679
|
+
if (!existsSync24(mapPath)) return { projects: {} };
|
|
3516
3680
|
try {
|
|
3517
|
-
return JSON.parse(
|
|
3681
|
+
return JSON.parse(readFileSync8(mapPath, "utf8"));
|
|
3518
3682
|
} catch {
|
|
3519
3683
|
return { projects: {} };
|
|
3520
3684
|
}
|
|
@@ -3585,8 +3749,8 @@ function checkEventForPreserveSymlinks(section2, event, groups, sharedLinkNames)
|
|
|
3585
3749
|
return anyWarn;
|
|
3586
3750
|
}
|
|
3587
3751
|
function reportPreserveSymlinksCheck(section2) {
|
|
3588
|
-
const settingsPath =
|
|
3589
|
-
if (!
|
|
3752
|
+
const settingsPath = join29(claudeHome(), "settings.json");
|
|
3753
|
+
if (!existsSync24(settingsPath)) {
|
|
3590
3754
|
addItem(
|
|
3591
3755
|
section2,
|
|
3592
3756
|
`${dim(infoGlyph)} no ~/.claude/settings.json; skipping preserve-symlinks-main check`
|
|
@@ -3595,7 +3759,7 @@ function reportPreserveSymlinksCheck(section2) {
|
|
|
3595
3759
|
}
|
|
3596
3760
|
let settings;
|
|
3597
3761
|
try {
|
|
3598
|
-
settings = JSON.parse(
|
|
3762
|
+
settings = JSON.parse(readFileSync8(settingsPath, "utf8"));
|
|
3599
3763
|
} catch {
|
|
3600
3764
|
return;
|
|
3601
3765
|
}
|
|
@@ -3619,8 +3783,8 @@ function reportPreserveSymlinksCheck(section2) {
|
|
|
3619
3783
|
|
|
3620
3784
|
// src/commands.doctor.checks.settings-drift.ts
|
|
3621
3785
|
init_color();
|
|
3622
|
-
import { existsSync as
|
|
3623
|
-
import { join as
|
|
3786
|
+
import { existsSync as existsSync25, readFileSync as readFileSync9 } from "node:fs";
|
|
3787
|
+
import { join as join30 } from "node:path";
|
|
3624
3788
|
init_config();
|
|
3625
3789
|
init_utils_json();
|
|
3626
3790
|
function diffMergedSettings(merged, settings) {
|
|
@@ -3629,7 +3793,7 @@ function diffMergedSettings(merged, settings) {
|
|
|
3629
3793
|
}
|
|
3630
3794
|
function tryReadJson(filePath) {
|
|
3631
3795
|
try {
|
|
3632
|
-
const raw =
|
|
3796
|
+
const raw = readFileSync9(filePath, "utf8");
|
|
3633
3797
|
const parsed = JSON.parse(raw);
|
|
3634
3798
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
3635
3799
|
return parsed;
|
|
@@ -3638,7 +3802,7 @@ function tryReadJson(filePath) {
|
|
|
3638
3802
|
}
|
|
3639
3803
|
}
|
|
3640
3804
|
function reportHooksBaseSelfCleanNote(section2) {
|
|
3641
|
-
const basePath =
|
|
3805
|
+
const basePath = join30(repoHome(), "shared", "settings.base.json");
|
|
3642
3806
|
const base = tryReadJson(basePath);
|
|
3643
3807
|
if (base === null) return;
|
|
3644
3808
|
if (!baseHasGsdHookEntries(base)) return;
|
|
@@ -3651,14 +3815,14 @@ function reportSettingsDriftCheck(section2) {
|
|
|
3651
3815
|
const claude = claudeHome();
|
|
3652
3816
|
const repo = repoHome();
|
|
3653
3817
|
const host = HOST;
|
|
3654
|
-
const settingsPath =
|
|
3655
|
-
const basePath =
|
|
3656
|
-
const hostPath =
|
|
3657
|
-
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)) {
|
|
3658
3822
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json; skipping merge-drift check`);
|
|
3659
3823
|
return;
|
|
3660
3824
|
}
|
|
3661
|
-
if (!
|
|
3825
|
+
if (!existsSync25(basePath)) {
|
|
3662
3826
|
addItem(
|
|
3663
3827
|
section2,
|
|
3664
3828
|
`${dim(infoGlyph)} shared/settings.base.json missing; skipping merge-drift check`
|
|
@@ -3677,7 +3841,7 @@ function reportSettingsDriftCheck(section2) {
|
|
|
3677
3841
|
if (settings === null) {
|
|
3678
3842
|
return;
|
|
3679
3843
|
}
|
|
3680
|
-
const hostExists =
|
|
3844
|
+
const hostExists = existsSync25(hostPath);
|
|
3681
3845
|
const hostObj = hostExists ? tryReadJson(hostPath) : null;
|
|
3682
3846
|
if (hostExists && hostObj === null) {
|
|
3683
3847
|
addItem(
|
|
@@ -3726,12 +3890,12 @@ init_config();
|
|
|
3726
3890
|
|
|
3727
3891
|
// src/commands.doctor.engine.ts
|
|
3728
3892
|
init_color();
|
|
3729
|
-
import { readFileSync as
|
|
3893
|
+
import { readFileSync as readFileSync11 } from "node:fs";
|
|
3730
3894
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
3731
3895
|
|
|
3732
3896
|
// src/commands.doctor.version.ts
|
|
3733
3897
|
init_color();
|
|
3734
|
-
import { readFileSync as
|
|
3898
|
+
import { readFileSync as readFileSync10 } from "node:fs";
|
|
3735
3899
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
3736
3900
|
init_config();
|
|
3737
3901
|
var STRICT_SEMVER = /^\d+\.\d+\.\d+$/;
|
|
@@ -3748,7 +3912,7 @@ function compareSemver(a, b) {
|
|
|
3748
3912
|
function readLocalVersion() {
|
|
3749
3913
|
try {
|
|
3750
3914
|
const pkgPath = fileURLToPath2(new URL("../package.json", import.meta.url));
|
|
3751
|
-
const parsed = JSON.parse(
|
|
3915
|
+
const parsed = JSON.parse(readFileSync10(pkgPath, "utf8"));
|
|
3752
3916
|
if (typeof parsed.version === "string" && parsed.version.length > 0) {
|
|
3753
3917
|
return parsed.version;
|
|
3754
3918
|
}
|
|
@@ -3807,7 +3971,7 @@ function parseMinVersion(spec) {
|
|
|
3807
3971
|
function readEnginesNode() {
|
|
3808
3972
|
try {
|
|
3809
3973
|
const pkgPath = fileURLToPath3(new URL("../package.json", import.meta.url));
|
|
3810
|
-
const parsed = JSON.parse(
|
|
3974
|
+
const parsed = JSON.parse(readFileSync11(pkgPath, "utf8"));
|
|
3811
3975
|
const node = parsed.engines?.node;
|
|
3812
3976
|
if (typeof node === "string" && node.length > 0) return node;
|
|
3813
3977
|
return null;
|
|
@@ -3835,43 +3999,43 @@ function reportNodeEngineCheck(section2) {
|
|
|
3835
3999
|
|
|
3836
4000
|
// src/spinner.ts
|
|
3837
4001
|
init_color();
|
|
3838
|
-
import { existsSync as
|
|
4002
|
+
import { existsSync as existsSync29 } from "node:fs";
|
|
3839
4003
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
3840
4004
|
import { Worker } from "node:worker_threads";
|
|
3841
4005
|
|
|
3842
4006
|
// src/commands.push.recovery.ts
|
|
3843
4007
|
init_config();
|
|
3844
|
-
import { readFileSync as
|
|
3845
|
-
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";
|
|
3846
4010
|
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
3847
4011
|
|
|
3848
4012
|
// src/commands.push.recovery.actions.ts
|
|
3849
4013
|
init_config();
|
|
3850
|
-
import { readFileSync as
|
|
4014
|
+
import { readFileSync as readFileSync13 } from "node:fs";
|
|
3851
4015
|
import { isAbsolute as isAbsolute2, resolve as resolve3, sep as sep5 } from "node:path";
|
|
3852
4016
|
|
|
3853
4017
|
// src/commands.push.recovery.redact.ts
|
|
3854
4018
|
init_config();
|
|
3855
4019
|
init_config_sharedDirs_guard();
|
|
3856
|
-
import { cpSync as cpSync5, existsSync as
|
|
3857
|
-
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";
|
|
3858
4022
|
|
|
3859
4023
|
// src/commands.redact.ts
|
|
3860
4024
|
init_config();
|
|
3861
|
-
import { existsSync as
|
|
3862
|
-
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";
|
|
3863
4027
|
|
|
3864
4028
|
// src/commands.redact.subtree.ts
|
|
3865
|
-
import { existsSync as
|
|
3866
|
-
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";
|
|
3867
4031
|
init_utils_fs();
|
|
3868
4032
|
init_utils();
|
|
3869
4033
|
function collectFiles(dir, out) {
|
|
3870
|
-
if (!
|
|
4034
|
+
if (!existsSync26(dir)) return;
|
|
3871
4035
|
const st = lstatSync8(dir);
|
|
3872
4036
|
if (!st.isDirectory()) return;
|
|
3873
|
-
for (const entry of
|
|
3874
|
-
const abs =
|
|
4037
|
+
for (const entry of readdirSync11(dir)) {
|
|
4038
|
+
const abs = join31(dir, entry);
|
|
3875
4039
|
const lst = lstatSync8(abs);
|
|
3876
4040
|
if (lst.isSymbolicLink()) continue;
|
|
3877
4041
|
if (lst.isDirectory()) {
|
|
@@ -3886,7 +4050,7 @@ function listSubtreeFiles(sessionDir) {
|
|
|
3886
4050
|
collectFiles(sessionDir, out);
|
|
3887
4051
|
return out.sort((a, b) => a.localeCompare(b));
|
|
3888
4052
|
}
|
|
3889
|
-
function newestSubtreeMtimeMs(mainPath, subtreeFiles, statMtime = (p) =>
|
|
4053
|
+
function newestSubtreeMtimeMs(mainPath, subtreeFiles, statMtime = (p) => statSync6(p).mtimeMs) {
|
|
3890
4054
|
let newest = statMtime(mainPath);
|
|
3891
4055
|
for (const filePath of subtreeFiles) {
|
|
3892
4056
|
const t = statMtime(filePath);
|
|
@@ -3908,7 +4072,7 @@ function applySubtreeRedactions(mainPath, mainFindings, subtreeFiles, rule, ts,
|
|
|
3908
4072
|
if (!dryRun && total > 0) {
|
|
3909
4073
|
for (const { path: filePath, findings } of dirty) {
|
|
3910
4074
|
backupBeforeWrite(filePath, ts);
|
|
3911
|
-
const before =
|
|
4075
|
+
const before = readFileSync12(filePath, "utf8");
|
|
3912
4076
|
const after = applyRedactions(before, findings);
|
|
3913
4077
|
if (after === before) {
|
|
3914
4078
|
log(
|
|
@@ -3976,15 +4140,15 @@ init_utils_json();
|
|
|
3976
4140
|
init_utils();
|
|
3977
4141
|
function resolveLiveTranscript(id) {
|
|
3978
4142
|
try {
|
|
3979
|
-
const mapPath =
|
|
3980
|
-
if (!
|
|
4143
|
+
const mapPath = join32(repoHome(), "path-map.json");
|
|
4144
|
+
if (!existsSync27(mapPath)) return null;
|
|
3981
4145
|
const projects = readJson(mapPath).projects;
|
|
3982
4146
|
const claude = claudeHome();
|
|
3983
4147
|
for (const hostMap of Object.values(projects)) {
|
|
3984
4148
|
const abs = hostMap[HOST];
|
|
3985
4149
|
if (abs === void 0) continue;
|
|
3986
|
-
const live =
|
|
3987
|
-
if (
|
|
4150
|
+
const live = join32(claude, "projects", encodePath(abs), `${id}.jsonl`);
|
|
4151
|
+
if (existsSync27(live)) return live;
|
|
3988
4152
|
}
|
|
3989
4153
|
return null;
|
|
3990
4154
|
} catch {
|
|
@@ -4004,19 +4168,19 @@ function cmdRedact(opts, nowMs = Date.now, scan = scanFile) {
|
|
|
4004
4168
|
}
|
|
4005
4169
|
const repo = repoHome();
|
|
4006
4170
|
const backup = backupBase();
|
|
4007
|
-
if (!
|
|
4171
|
+
if (!existsSync27(repo)) die(`repo not cloned at ${repo}`);
|
|
4008
4172
|
const handle = acquireLock("redact");
|
|
4009
4173
|
if (handle === null) process.exit(0);
|
|
4010
4174
|
try {
|
|
4011
4175
|
const localPath = resolveLiveTranscript(id);
|
|
4012
|
-
if (localPath === null || !
|
|
4176
|
+
if (localPath === null || !existsSync27(localPath)) {
|
|
4013
4177
|
fail(`could not resolve local transcript for session ${id} on this host`);
|
|
4014
4178
|
process.exitCode = 1;
|
|
4015
4179
|
return;
|
|
4016
4180
|
}
|
|
4017
|
-
const sessionDir =
|
|
4181
|
+
const sessionDir = join32(dirname7(localPath), id);
|
|
4018
4182
|
const subtreeFiles = listSubtreeFiles(sessionDir);
|
|
4019
|
-
const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) =>
|
|
4183
|
+
const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync7(p).mtimeMs);
|
|
4020
4184
|
if (isRecentlyModified(subtreeMtime, nowMs())) {
|
|
4021
4185
|
log(
|
|
4022
4186
|
`session ${id} was modified recently and may be active.
|
|
@@ -4130,8 +4294,8 @@ function resolveStagedDir(localPath, map, claude, repo) {
|
|
|
4130
4294
|
assertSafeLogical(logical);
|
|
4131
4295
|
const abs = hostMap[HOST];
|
|
4132
4296
|
if (abs === void 0) continue;
|
|
4133
|
-
if (localPath.startsWith(
|
|
4134
|
-
return
|
|
4297
|
+
if (localPath.startsWith(join33(claude, "projects", encodePath(abs)) + sep4)) {
|
|
4298
|
+
return join33(repo, "shared", "projects", logical);
|
|
4135
4299
|
}
|
|
4136
4300
|
}
|
|
4137
4301
|
return null;
|
|
@@ -4141,9 +4305,9 @@ function preflightRedactable(f, map, nowMs) {
|
|
|
4141
4305
|
if (sid === null) return "a finding has no resolvable session id (not a session transcript)";
|
|
4142
4306
|
const localPath = resolveLiveTranscript(sid);
|
|
4143
4307
|
if (localPath === null) return `session ${sid}: local transcript not found`;
|
|
4144
|
-
const sessionDir =
|
|
4308
|
+
const sessionDir = join33(dirname8(localPath), sid);
|
|
4145
4309
|
const subtreeFiles = listSubtreeFiles(sessionDir);
|
|
4146
|
-
const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) =>
|
|
4310
|
+
const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync8(p).mtimeMs);
|
|
4147
4311
|
if (isRecentlyModified(subtreeMtime, nowMs())) {
|
|
4148
4312
|
return `session ${sid}: looks active (modified within the last 5 minutes)`;
|
|
4149
4313
|
}
|
|
@@ -4171,9 +4335,9 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
|
|
|
4171
4335
|
`could not locate the local transcript for session ${sid}; choose Skip or Drop session.`
|
|
4172
4336
|
);
|
|
4173
4337
|
}
|
|
4174
|
-
const sessionDir =
|
|
4338
|
+
const sessionDir = join33(dirname8(localPath), sid);
|
|
4175
4339
|
const subtreeFiles = listSubtreeFiles(sessionDir);
|
|
4176
|
-
const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) =>
|
|
4340
|
+
const subtreeMtime = newestSubtreeMtimeMs(localPath, subtreeFiles, (p) => statSync8(p).mtimeMs);
|
|
4177
4341
|
if (isRecentlyModified(subtreeMtime, nowMs())) {
|
|
4178
4342
|
return refuse(
|
|
4179
4343
|
`session ${sid} looks active (modified within the last 5 minutes); refusing to redact, no changes made.
|
|
@@ -4204,10 +4368,10 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
|
|
|
4204
4368
|
`nothing to redact in the local transcript for session ${sid}; choose Skip or Drop session.`
|
|
4205
4369
|
);
|
|
4206
4370
|
}
|
|
4207
|
-
|
|
4208
|
-
cpSync5(localPath,
|
|
4209
|
-
if (
|
|
4210
|
-
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 });
|
|
4211
4375
|
}
|
|
4212
4376
|
return true;
|
|
4213
4377
|
}
|
|
@@ -4215,14 +4379,14 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
|
|
|
4215
4379
|
// src/commands.push.recovery.drop.ts
|
|
4216
4380
|
init_config();
|
|
4217
4381
|
import { rmSync as rmSync9 } from "node:fs";
|
|
4218
|
-
import { join as
|
|
4382
|
+
import { join as join34 } from "node:path";
|
|
4219
4383
|
function dropSessionFromStaged(sid, map) {
|
|
4220
4384
|
const logicals = Object.keys(map.projects);
|
|
4221
4385
|
if (logicals.length === 0) return false;
|
|
4222
4386
|
const repo = repoHome();
|
|
4223
4387
|
for (const logical of logicals) {
|
|
4224
|
-
const jsonl =
|
|
4225
|
-
const dir =
|
|
4388
|
+
const jsonl = join34(repo, "shared", "projects", logical, `${sid}.jsonl`);
|
|
4389
|
+
const dir = join34(repo, "shared", "projects", logical, sid);
|
|
4226
4390
|
rmSync9(jsonl, { force: true });
|
|
4227
4391
|
rmSync9(dir, { recursive: true, force: true });
|
|
4228
4392
|
}
|
|
@@ -4258,7 +4422,7 @@ function makeDefaultReadLine(repo) {
|
|
|
4258
4422
|
if (isAbsolute2(file) || target !== repoRoot && !target.startsWith(repoRoot + sep5)) {
|
|
4259
4423
|
return null;
|
|
4260
4424
|
}
|
|
4261
|
-
const content =
|
|
4425
|
+
const content = readFileSync13(target, "utf8");
|
|
4262
4426
|
const lines = content.split(/\r?\n/);
|
|
4263
4427
|
const idx = line - 1;
|
|
4264
4428
|
if (idx < 0 || idx >= lines.length) return null;
|
|
@@ -4379,10 +4543,10 @@ function applyThenRescan(scanVerdict, repoHome2) {
|
|
|
4379
4543
|
return next;
|
|
4380
4544
|
}
|
|
4381
4545
|
function allowThenRescan(append, scanVerdict, repoHome2) {
|
|
4382
|
-
const ignPath =
|
|
4546
|
+
const ignPath = join35(repoHome2, ".gitleaksignore");
|
|
4383
4547
|
let before;
|
|
4384
4548
|
try {
|
|
4385
|
-
before =
|
|
4549
|
+
before = readFileSync14(ignPath, "utf8");
|
|
4386
4550
|
} catch {
|
|
4387
4551
|
before = null;
|
|
4388
4552
|
}
|
|
@@ -4478,7 +4642,7 @@ function writeAnimatedDone(out, label, ms, useTTY) {
|
|
|
4478
4642
|
`);
|
|
4479
4643
|
}
|
|
4480
4644
|
function resolveWorkerPath(deps = {}) {
|
|
4481
|
-
const check = deps.existsSyncFn ??
|
|
4645
|
+
const check = deps.existsSyncFn ?? existsSync29;
|
|
4482
4646
|
const base = deps.baseUrl ?? import.meta.url;
|
|
4483
4647
|
const mjs = fileURLToPath4(new URL("./nomad.worker.mjs", base));
|
|
4484
4648
|
if (check(mjs)) return mjs;
|
|
@@ -4545,8 +4709,8 @@ function withSpinner(label, fn, deps) {
|
|
|
4545
4709
|
// src/commands.doctor.gitleaks-version.ts
|
|
4546
4710
|
init_color();
|
|
4547
4711
|
import { execFileSync as execFileSync11 } from "node:child_process";
|
|
4548
|
-
import { existsSync as
|
|
4549
|
-
import { join as
|
|
4712
|
+
import { existsSync as existsSync30 } from "node:fs";
|
|
4713
|
+
import { join as join36 } from "node:path";
|
|
4550
4714
|
init_config();
|
|
4551
4715
|
var SEMVER_MAJOR_MINOR = /^(\d+)\.(\d+)\.\d+$/;
|
|
4552
4716
|
var GITLEAKS_TIMEOUT_MS = 5e3;
|
|
@@ -4555,7 +4719,7 @@ function majorMinorOf(value) {
|
|
|
4555
4719
|
return m === null ? null : [m[1], m[2]];
|
|
4556
4720
|
}
|
|
4557
4721
|
function readGitleaksVersion(run, tomlExists) {
|
|
4558
|
-
const tomlPath =
|
|
4722
|
+
const tomlPath = join36(repoHome(), ".gitleaks.toml");
|
|
4559
4723
|
const args = ["version"];
|
|
4560
4724
|
if (tomlExists(tomlPath)) args.push("--config", tomlPath);
|
|
4561
4725
|
try {
|
|
@@ -4567,7 +4731,7 @@ function readGitleaksVersion(run, tomlExists) {
|
|
|
4567
4731
|
return null;
|
|
4568
4732
|
}
|
|
4569
4733
|
}
|
|
4570
|
-
function reportGitleaksVersionCheck(section2, run = execFileSync11, tomlExists =
|
|
4734
|
+
function reportGitleaksVersionCheck(section2, run = execFileSync11, tomlExists = existsSync30) {
|
|
4571
4735
|
const raw = readGitleaksVersion(run, tomlExists);
|
|
4572
4736
|
if (raw === null) return;
|
|
4573
4737
|
const local = majorMinorOf(raw);
|
|
@@ -4790,8 +4954,8 @@ function gatherDoctorSections(opts) {
|
|
|
4790
4954
|
reportHostKeyAlignment(host);
|
|
4791
4955
|
reportRepoState(host);
|
|
4792
4956
|
const links = section("Shared links");
|
|
4793
|
-
const mapPath =
|
|
4794
|
-
const rawMap =
|
|
4957
|
+
const mapPath = join37(repoHome(), "path-map.json");
|
|
4958
|
+
const rawMap = existsSync31(mapPath) ? readJsonSafe(mapPath, mapPath, links) : null;
|
|
4795
4959
|
const map = rawMap ?? { projects: {} };
|
|
4796
4960
|
reportSharedLinks(links, map);
|
|
4797
4961
|
reportDroppedNamesMigration(links);
|
|
@@ -4888,8 +5052,8 @@ function parseDoctorArgs(args) {
|
|
|
4888
5052
|
// src/commands.drop-session.ts
|
|
4889
5053
|
init_config();
|
|
4890
5054
|
import { execFileSync as execFileSync16 } from "node:child_process";
|
|
4891
|
-
import { existsSync as
|
|
4892
|
-
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";
|
|
4893
5057
|
|
|
4894
5058
|
// src/commands.drop-session.git.ts
|
|
4895
5059
|
import { execFileSync as execFileSync15 } from "node:child_process";
|
|
@@ -4931,8 +5095,8 @@ function isInIndex(rel, repo) {
|
|
|
4931
5095
|
init_config();
|
|
4932
5096
|
init_utils();
|
|
4933
5097
|
init_utils_json();
|
|
4934
|
-
import { existsSync as
|
|
4935
|
-
import { join as
|
|
5098
|
+
import { existsSync as existsSync32 } from "node:fs";
|
|
5099
|
+
import { join as join38 } from "node:path";
|
|
4936
5100
|
var SHARED_PROJECT_LOGICAL = /^shared\/projects\/([^/]+)\//;
|
|
4937
5101
|
function reportScrubHint(id, matches) {
|
|
4938
5102
|
const live = resolveLiveTranscript2(id, matches);
|
|
@@ -4948,8 +5112,8 @@ function reportScrubHint(id, matches) {
|
|
|
4948
5112
|
}
|
|
4949
5113
|
function resolveLiveTranscript2(id, matches) {
|
|
4950
5114
|
try {
|
|
4951
|
-
const mapPath =
|
|
4952
|
-
if (!
|
|
5115
|
+
const mapPath = join38(repoHome(), "path-map.json");
|
|
5116
|
+
if (!existsSync32(mapPath)) return null;
|
|
4953
5117
|
const projects = readJson(mapPath).projects;
|
|
4954
5118
|
const claude = claudeHome();
|
|
4955
5119
|
for (const rel of matches) {
|
|
@@ -4957,8 +5121,8 @@ function resolveLiveTranscript2(id, matches) {
|
|
|
4957
5121
|
if (logical === void 0) continue;
|
|
4958
5122
|
const abs = projects[logical]?.[HOST];
|
|
4959
5123
|
if (abs === void 0) continue;
|
|
4960
|
-
const live =
|
|
4961
|
-
if (
|
|
5124
|
+
const live = join38(claude, "projects", encodePath(abs), `${id}.jsonl`);
|
|
5125
|
+
if (existsSync32(live)) return live;
|
|
4962
5126
|
}
|
|
4963
5127
|
return null;
|
|
4964
5128
|
} catch {
|
|
@@ -4974,12 +5138,12 @@ function cmdDropSession(id) {
|
|
|
4974
5138
|
process.exit(1);
|
|
4975
5139
|
}
|
|
4976
5140
|
const repo = repoHome();
|
|
4977
|
-
if (!
|
|
5141
|
+
if (!existsSync33(repo)) die(`repo not cloned at ${repo}`);
|
|
4978
5142
|
const handle = acquireLock("drop-session");
|
|
4979
5143
|
if (handle === null) process.exit(0);
|
|
4980
5144
|
try {
|
|
4981
|
-
const repoProjects =
|
|
4982
|
-
if (!
|
|
5145
|
+
const repoProjects = join39(repo, "shared", "projects");
|
|
5146
|
+
if (!existsSync33(repoProjects)) {
|
|
4983
5147
|
throw new NomadFatal(`no staged session matches ${id}`);
|
|
4984
5148
|
}
|
|
4985
5149
|
const matches = collectMatches(repoProjects, id, repo);
|
|
@@ -5001,13 +5165,13 @@ function cmdDropSession(id) {
|
|
|
5001
5165
|
}
|
|
5002
5166
|
function collectMatches(repoProjects, id, repo) {
|
|
5003
5167
|
const matches = [];
|
|
5004
|
-
for (const logical of
|
|
5005
|
-
const candidate =
|
|
5006
|
-
if (
|
|
5168
|
+
for (const logical of readdirSync12(repoProjects)) {
|
|
5169
|
+
const candidate = join39(repoProjects, logical, `${id}.jsonl`);
|
|
5170
|
+
if (existsSync33(candidate)) {
|
|
5007
5171
|
matches.push(relative4(repo, candidate));
|
|
5008
5172
|
}
|
|
5009
|
-
const dir =
|
|
5010
|
-
if (
|
|
5173
|
+
const dir = join39(repoProjects, logical, id);
|
|
5174
|
+
if (existsSync33(dir) && statSync9(dir).isDirectory()) {
|
|
5011
5175
|
const dirRel = relative4(repo, dir);
|
|
5012
5176
|
const staged = expandStagedDir(dirRel, repo);
|
|
5013
5177
|
if (staged.length > 0) matches.push(...staged);
|
|
@@ -5042,8 +5206,8 @@ function unstageOne(rel, repo) {
|
|
|
5042
5206
|
}
|
|
5043
5207
|
|
|
5044
5208
|
// src/commands.pull.ts
|
|
5045
|
-
import { existsSync as
|
|
5046
|
-
import { join as
|
|
5209
|
+
import { existsSync as existsSync39, mkdirSync as mkdirSync10 } from "node:fs";
|
|
5210
|
+
import { join as join46 } from "node:path";
|
|
5047
5211
|
|
|
5048
5212
|
// src/commands.push.sections.ts
|
|
5049
5213
|
init_color();
|
|
@@ -5138,20 +5302,20 @@ init_config();
|
|
|
5138
5302
|
|
|
5139
5303
|
// src/extras-sync.ts
|
|
5140
5304
|
init_config();
|
|
5141
|
-
import { existsSync as
|
|
5142
|
-
import { join as
|
|
5305
|
+
import { existsSync as existsSync36, statSync as statSync10 } from "node:fs";
|
|
5306
|
+
import { join as join43 } from "node:path";
|
|
5143
5307
|
|
|
5144
5308
|
// src/extras-sync.core.ts
|
|
5145
5309
|
init_config();
|
|
5146
|
-
import { cpSync as cpSync6, existsSync as
|
|
5147
|
-
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";
|
|
5148
5312
|
init_utils();
|
|
5149
5313
|
init_utils_json();
|
|
5150
5314
|
function loadValidatedExtras(opts) {
|
|
5151
5315
|
const repo = repoHome();
|
|
5152
|
-
const mapPath =
|
|
5153
|
-
const repoExtras =
|
|
5154
|
-
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)) {
|
|
5155
5319
|
if (opts.missingMsg !== void 0) log(opts.missingMsg);
|
|
5156
5320
|
return null;
|
|
5157
5321
|
}
|
|
@@ -5173,26 +5337,26 @@ function* eachExtrasTarget(v, counts) {
|
|
|
5173
5337
|
counts.unmapped++;
|
|
5174
5338
|
continue;
|
|
5175
5339
|
}
|
|
5176
|
-
for (const
|
|
5177
|
-
if (!whitelist.includes(
|
|
5340
|
+
for (const dirname10 of dirnames) {
|
|
5341
|
+
if (!whitelist.includes(dirname10)) {
|
|
5178
5342
|
counts.skipped++;
|
|
5179
5343
|
continue;
|
|
5180
5344
|
}
|
|
5181
|
-
yield { logical, localRoot, dirname:
|
|
5345
|
+
yield { logical, localRoot, dirname: dirname10 };
|
|
5182
5346
|
}
|
|
5183
5347
|
}
|
|
5184
5348
|
}
|
|
5185
5349
|
function stripCollidingDstSymlinks(src, dst, isExcluded) {
|
|
5186
|
-
if (!
|
|
5187
|
-
for (const name of
|
|
5350
|
+
if (!existsSync34(dst)) return;
|
|
5351
|
+
for (const name of readdirSync13(src)) {
|
|
5188
5352
|
if (isExcluded(name)) continue;
|
|
5189
|
-
const dstPath =
|
|
5353
|
+
const dstPath = join40(dst, name);
|
|
5190
5354
|
const dstStat = lstatSync9(dstPath, { throwIfNoEntry: false });
|
|
5191
5355
|
if (dstStat === void 0) continue;
|
|
5192
5356
|
if (dstStat.isSymbolicLink()) {
|
|
5193
5357
|
rmSync11(dstPath, { recursive: true, force: true });
|
|
5194
|
-
} else if (dstStat.isDirectory() && lstatSync9(
|
|
5195
|
-
stripCollidingDstSymlinks(
|
|
5358
|
+
} else if (dstStat.isDirectory() && lstatSync9(join40(src, name)).isDirectory()) {
|
|
5359
|
+
stripCollidingDstSymlinks(join40(src, name), dstPath, isExcluded);
|
|
5196
5360
|
}
|
|
5197
5361
|
}
|
|
5198
5362
|
}
|
|
@@ -5219,8 +5383,8 @@ function copyExtras(src, dst) {
|
|
|
5219
5383
|
rmSync11(dst, { recursive: true, force: true });
|
|
5220
5384
|
cpSync6(src, dst, { recursive: true, force: true, verbatimSymlinks: true });
|
|
5221
5385
|
}
|
|
5222
|
-
function extrasDenySet(
|
|
5223
|
-
return
|
|
5386
|
+
function extrasDenySet(dirname10) {
|
|
5387
|
+
return dirname10 === ".claude" ? CLAUDE_EXTRA_NEVER_SYNC : ALWAYS_NEVER_SYNC;
|
|
5224
5388
|
}
|
|
5225
5389
|
function copyExtrasFiltered(src, dst, blockSet) {
|
|
5226
5390
|
rmSync11(dst, { recursive: true, force: true });
|
|
@@ -5232,17 +5396,17 @@ function copyExtrasFiltered(src, dst, blockSet) {
|
|
|
5232
5396
|
});
|
|
5233
5397
|
}
|
|
5234
5398
|
function prunePreservingDenied(src, dst, blockSet) {
|
|
5235
|
-
for (const name of
|
|
5399
|
+
for (const name of readdirSync13(dst)) {
|
|
5236
5400
|
if (isDeniedName(blockSet, name)) continue;
|
|
5237
|
-
const dstPath =
|
|
5238
|
-
const srcStat = lstatSync9(
|
|
5401
|
+
const dstPath = join40(dst, name);
|
|
5402
|
+
const srcStat = lstatSync9(join40(src, name), { throwIfNoEntry: false });
|
|
5239
5403
|
if (srcStat === void 0) {
|
|
5240
5404
|
rmSync11(dstPath, { recursive: true, force: true });
|
|
5241
5405
|
continue;
|
|
5242
5406
|
}
|
|
5243
5407
|
const dstStat = lstatSync9(dstPath);
|
|
5244
5408
|
if (srcStat.isDirectory() && dstStat.isDirectory()) {
|
|
5245
|
-
prunePreservingDenied(
|
|
5409
|
+
prunePreservingDenied(join40(src, name), dstPath, blockSet);
|
|
5246
5410
|
} else if (srcStat.isDirectory() !== dstStat.isDirectory()) {
|
|
5247
5411
|
rmSync11(dstPath, { recursive: true, force: true });
|
|
5248
5412
|
}
|
|
@@ -5263,17 +5427,17 @@ function copyExtrasFilteredPreserving(src, dst, blockSet) {
|
|
|
5263
5427
|
});
|
|
5264
5428
|
}
|
|
5265
5429
|
function prunePreservingBy(src, dst, isPreserved) {
|
|
5266
|
-
for (const name of
|
|
5430
|
+
for (const name of readdirSync13(dst)) {
|
|
5267
5431
|
if (isPreserved(name)) continue;
|
|
5268
|
-
const dstPath =
|
|
5269
|
-
const srcStat = lstatSync9(
|
|
5432
|
+
const dstPath = join40(dst, name);
|
|
5433
|
+
const srcStat = lstatSync9(join40(src, name), { throwIfNoEntry: false });
|
|
5270
5434
|
if (srcStat === void 0) {
|
|
5271
5435
|
rmSync11(dstPath, { recursive: true, force: true });
|
|
5272
5436
|
continue;
|
|
5273
5437
|
}
|
|
5274
5438
|
const dstStat = lstatSync9(dstPath);
|
|
5275
5439
|
if (srcStat.isDirectory() && dstStat.isDirectory()) {
|
|
5276
|
-
prunePreservingBy(
|
|
5440
|
+
prunePreservingBy(join40(src, name), dstPath, isPreserved);
|
|
5277
5441
|
} else if (srcStat.isDirectory() !== dstStat.isDirectory()) {
|
|
5278
5442
|
rmSync11(dstPath, { recursive: true, force: true });
|
|
5279
5443
|
}
|
|
@@ -5300,11 +5464,11 @@ init_utils_json();
|
|
|
5300
5464
|
|
|
5301
5465
|
// src/extras-sync.remap.ts
|
|
5302
5466
|
init_config();
|
|
5303
|
-
import { existsSync as
|
|
5304
|
-
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";
|
|
5305
5469
|
|
|
5306
5470
|
// src/extras-sync.planning-diff.ts
|
|
5307
|
-
import { join as
|
|
5471
|
+
import { join as join41, normalize as normalize2, sep as sep6 } from "node:path";
|
|
5308
5472
|
init_utils();
|
|
5309
5473
|
function processRecord(fields, i, changed, deleted) {
|
|
5310
5474
|
const status = fields[i];
|
|
@@ -5356,7 +5520,7 @@ function planningDeleteTargets(opts) {
|
|
|
5356
5520
|
const { deleted } = parsePlanningDiff(raw);
|
|
5357
5521
|
const logicalPrefix = "shared/extras/" + logical + "/";
|
|
5358
5522
|
const prefix = logicalPrefix + ".planning/";
|
|
5359
|
-
const planningRoot =
|
|
5523
|
+
const planningRoot = join41(localRoot, ".planning");
|
|
5360
5524
|
const planningRootBoundary = planningRoot + sep6;
|
|
5361
5525
|
const targets = [];
|
|
5362
5526
|
for (const repoPath of deleted) {
|
|
@@ -5364,7 +5528,7 @@ function planningDeleteTargets(opts) {
|
|
|
5364
5528
|
continue;
|
|
5365
5529
|
}
|
|
5366
5530
|
const remainder = repoPath.slice(logicalPrefix.length);
|
|
5367
|
-
const candidate =
|
|
5531
|
+
const candidate = join41(localRoot, remainder);
|
|
5368
5532
|
const resolved = normalize2(candidate);
|
|
5369
5533
|
if (!resolved.startsWith(planningRootBoundary)) {
|
|
5370
5534
|
throw new NomadFatal(
|
|
@@ -5385,7 +5549,7 @@ function runExtrasOp(v, dryRun, paths, backup, copy) {
|
|
|
5385
5549
|
const would = [];
|
|
5386
5550
|
for (const t of eachExtrasTarget(v, counts)) {
|
|
5387
5551
|
const { src, dst } = paths(t);
|
|
5388
|
-
if (!
|
|
5552
|
+
if (!existsSync35(src)) continue;
|
|
5389
5553
|
const item2 = `${t.logical}/${t.dirname}`;
|
|
5390
5554
|
if (dryRun) {
|
|
5391
5555
|
would.push(item2);
|
|
@@ -5398,15 +5562,15 @@ function runExtrasOp(v, dryRun, paths, backup, copy) {
|
|
|
5398
5562
|
return { ...counts, done, would };
|
|
5399
5563
|
}
|
|
5400
5564
|
function pruneEmptyAncestors(target, planningRoot) {
|
|
5401
|
-
let dir =
|
|
5565
|
+
let dir = dirname9(target);
|
|
5402
5566
|
while (dir !== planningRoot && dir.startsWith(planningRoot + sep7)) {
|
|
5403
5567
|
try {
|
|
5404
|
-
if (
|
|
5568
|
+
if (readdirSync14(dir).length > 0) break;
|
|
5405
5569
|
rmSync12(dir, { recursive: true, force: true });
|
|
5406
5570
|
} catch {
|
|
5407
5571
|
break;
|
|
5408
5572
|
}
|
|
5409
|
-
dir =
|
|
5573
|
+
dir = dirname9(dir);
|
|
5410
5574
|
}
|
|
5411
5575
|
}
|
|
5412
5576
|
function tryRealpath(dir) {
|
|
@@ -5420,8 +5584,8 @@ function isInsidePlanningRoot(parentReal, rootReal) {
|
|
|
5420
5584
|
return parentReal === rootReal || parentReal.startsWith(rootReal + sep7);
|
|
5421
5585
|
}
|
|
5422
5586
|
function deletePlanningTarget(target, planningRoot, repoCounterpart) {
|
|
5423
|
-
if (
|
|
5424
|
-
const parentReal = tryRealpath(
|
|
5587
|
+
if (existsSync35(repoCounterpart)) return;
|
|
5588
|
+
const parentReal = tryRealpath(dirname9(target));
|
|
5425
5589
|
if (parentReal === void 0) return;
|
|
5426
5590
|
const rootReal = tryRealpath(planningRoot);
|
|
5427
5591
|
if (rootReal === void 0) return;
|
|
@@ -5430,7 +5594,7 @@ function deletePlanningTarget(target, planningRoot, repoCounterpart) {
|
|
|
5430
5594
|
pruneEmptyAncestors(target, planningRoot);
|
|
5431
5595
|
}
|
|
5432
5596
|
function propagatePlanningDeletes(v, ts, prePostHeads, repo) {
|
|
5433
|
-
const repoExtras =
|
|
5597
|
+
const repoExtras = join42(repo, "shared", "extras");
|
|
5434
5598
|
for (const t of eachExtrasTarget(v, { unmapped: 0, skipped: 0 })) {
|
|
5435
5599
|
if (t.dirname !== ".planning") continue;
|
|
5436
5600
|
let raw;
|
|
@@ -5456,11 +5620,11 @@ function propagatePlanningDeletes(v, ts, prePostHeads, repo) {
|
|
|
5456
5620
|
}
|
|
5457
5621
|
const targets = planningDeleteTargets({ raw, logical: t.logical, localRoot: t.localRoot });
|
|
5458
5622
|
if (targets.length === 0) continue;
|
|
5459
|
-
backupExtrasWrite(
|
|
5460
|
-
const planningRoot =
|
|
5623
|
+
backupExtrasWrite(join42(t.localRoot, t.dirname), ts, t.localRoot);
|
|
5624
|
+
const planningRoot = join42(t.localRoot, ".planning");
|
|
5461
5625
|
for (const target of targets) {
|
|
5462
5626
|
const relToLocal = target.slice(t.localRoot.length + sep7.length);
|
|
5463
|
-
deletePlanningTarget(target, planningRoot,
|
|
5627
|
+
deletePlanningTarget(target, planningRoot, join42(repoExtras, t.logical, relToLocal));
|
|
5464
5628
|
}
|
|
5465
5629
|
}
|
|
5466
5630
|
}
|
|
@@ -5469,14 +5633,14 @@ function remapExtrasPush(ts, opts = {}) {
|
|
|
5469
5633
|
const v = loadValidatedExtras({ missingMsg: "no path-map.json; skipping extras push" });
|
|
5470
5634
|
if (v === null) return { unmapped: 0, skipped: 0, pushed: [], wouldPush: [] };
|
|
5471
5635
|
const repo = repoHome();
|
|
5472
|
-
const repoExtras =
|
|
5473
|
-
if (!dryRun)
|
|
5636
|
+
const repoExtras = join42(repo, "shared", "extras");
|
|
5637
|
+
if (!dryRun) mkdirSync8(repoExtras, { recursive: true });
|
|
5474
5638
|
const { unmapped, skipped, done, would } = runExtrasOp(
|
|
5475
5639
|
v,
|
|
5476
5640
|
dryRun,
|
|
5477
|
-
({ localRoot, logical, dirname:
|
|
5478
|
-
src:
|
|
5479
|
-
dst:
|
|
5641
|
+
({ localRoot, logical, dirname: dirname10 }) => ({
|
|
5642
|
+
src: join42(localRoot, dirname10),
|
|
5643
|
+
dst: join42(repoExtras, logical, dirname10)
|
|
5480
5644
|
}),
|
|
5481
5645
|
(dst) => backupRepoWrite(dst, ts, repo),
|
|
5482
5646
|
// Push copy routing per extra type:
|
|
@@ -5488,7 +5652,7 @@ function remapExtrasPush(ts, opts = {}) {
|
|
|
5488
5652
|
// (enforceAllowList / blockSetFor in commands.push.allowlist.ts)
|
|
5489
5653
|
// remains the hard security boundary.
|
|
5490
5654
|
// All others: copyExtrasFiltered with per-extra denylist.
|
|
5491
|
-
(src, dst,
|
|
5655
|
+
(src, dst, dirname10) => dirname10 === ".planning" ? copyExtrasOverlayFiltered(src, dst, extrasDenySet(dirname10)) : copyExtrasFiltered(src, dst, extrasDenySet(dirname10))
|
|
5492
5656
|
);
|
|
5493
5657
|
return { unmapped, skipped, pushed: done, wouldPush: would };
|
|
5494
5658
|
}
|
|
@@ -5504,9 +5668,9 @@ function remapExtrasPull(ts, opts = {}) {
|
|
|
5504
5668
|
const { unmapped, skipped, done, would } = runExtrasOp(
|
|
5505
5669
|
v,
|
|
5506
5670
|
dryRun,
|
|
5507
|
-
({ localRoot, logical, dirname:
|
|
5508
|
-
src:
|
|
5509
|
-
dst:
|
|
5671
|
+
({ localRoot, logical, dirname: dirname10 }) => ({
|
|
5672
|
+
src: join42(repo, "shared", "extras", logical, dirname10),
|
|
5673
|
+
dst: join42(localRoot, dirname10)
|
|
5510
5674
|
}),
|
|
5511
5675
|
// Snapshot the host-side dst BEFORE copyExtras clobbers it. Anchor on
|
|
5512
5676
|
// localRoot so the backup tree mirrors the project layout.
|
|
@@ -5519,11 +5683,11 @@ function remapExtrasPull(ts, opts = {}) {
|
|
|
5519
5683
|
// removals via the git-diff D set. The filter is defense-in-depth
|
|
5520
5684
|
// against a repo poisoned out-of-band.
|
|
5521
5685
|
// All others: copyExtras (exact mirror; rarely carry host-local files).
|
|
5522
|
-
(src, dst,
|
|
5523
|
-
if (
|
|
5524
|
-
return copyExtrasFilteredPreserving(src, dst, extrasDenySet(
|
|
5525
|
-
if (
|
|
5526
|
-
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));
|
|
5527
5691
|
return copyExtras(src, dst);
|
|
5528
5692
|
}
|
|
5529
5693
|
);
|
|
@@ -5547,20 +5711,20 @@ function divergenceCheckExtras(ts) {
|
|
|
5547
5711
|
const v = loadValidatedExtras({});
|
|
5548
5712
|
if (v === null) return;
|
|
5549
5713
|
const counts = { unmapped: 0, skipped: 0 };
|
|
5550
|
-
const backupRoot =
|
|
5714
|
+
const backupRoot = join43(backupBase(), ts, "extras");
|
|
5551
5715
|
const repo = repoHome();
|
|
5552
|
-
for (const { logical, localRoot, dirname:
|
|
5553
|
-
const local =
|
|
5554
|
-
const repoEntry =
|
|
5555
|
-
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;
|
|
5556
5720
|
const diff = listDivergingFiles(local, repoEntry);
|
|
5557
5721
|
if (diff.length === 0) continue;
|
|
5558
|
-
const projectBackupRoot =
|
|
5722
|
+
const projectBackupRoot = join43(backupRoot, encodePath(localRoot));
|
|
5559
5723
|
warn(
|
|
5560
5724
|
divergenceWarnLine({
|
|
5561
|
-
dirname:
|
|
5725
|
+
dirname: dirname10,
|
|
5562
5726
|
logical,
|
|
5563
|
-
isDir:
|
|
5727
|
+
isDir: statSync10(local).isDirectory(),
|
|
5564
5728
|
count: diff.length,
|
|
5565
5729
|
projectBackupRoot
|
|
5566
5730
|
})
|
|
@@ -5571,8 +5735,8 @@ function divergenceCheckExtras(ts) {
|
|
|
5571
5735
|
|
|
5572
5736
|
// src/skills-sync.ts
|
|
5573
5737
|
init_config();
|
|
5574
|
-
import { existsSync as
|
|
5575
|
-
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";
|
|
5576
5740
|
init_utils_fs();
|
|
5577
5741
|
function isGsdOwned(name) {
|
|
5578
5742
|
return name.startsWith(GSD_PREFIX);
|
|
@@ -5581,7 +5745,7 @@ function isSkillExcluded(name) {
|
|
|
5581
5745
|
return isGsdOwned(name) || isDeniedName(ALWAYS_NEVER_SYNC, name);
|
|
5582
5746
|
}
|
|
5583
5747
|
function copySkillsPush(src, dst) {
|
|
5584
|
-
const srcNames =
|
|
5748
|
+
const srcNames = readdirSync15(src, { encoding: "utf8" });
|
|
5585
5749
|
const blockSet = /* @__PURE__ */ new Set([
|
|
5586
5750
|
...srcNames.filter((n) => isGsdOwned(n)),
|
|
5587
5751
|
...ALWAYS_NEVER_SYNC
|
|
@@ -5592,30 +5756,30 @@ function copySkillsPull(src, dst) {
|
|
|
5592
5756
|
copyExtrasFilteredPreservingBy(src, dst, isSkillExcluded);
|
|
5593
5757
|
}
|
|
5594
5758
|
function syncSkillsPull(ts) {
|
|
5595
|
-
const sharedSkills =
|
|
5596
|
-
if (!
|
|
5597
|
-
const localSkills =
|
|
5759
|
+
const sharedSkills = join44(repoHome(), "shared", "skills");
|
|
5760
|
+
if (!existsSync37(sharedSkills)) return;
|
|
5761
|
+
const localSkills = join44(claudeHome(), "skills");
|
|
5598
5762
|
const dstStat = lstatSync10(localSkills, { throwIfNoEntry: false });
|
|
5599
5763
|
if (dstStat?.isSymbolicLink() === true) {
|
|
5600
5764
|
backupBeforeWrite(localSkills, ts);
|
|
5601
5765
|
rmSync13(localSkills, { recursive: true, force: true });
|
|
5602
|
-
|
|
5766
|
+
mkdirSync9(localSkills, { recursive: true });
|
|
5603
5767
|
}
|
|
5604
5768
|
copySkillsPull(sharedSkills, localSkills);
|
|
5605
5769
|
}
|
|
5606
5770
|
function syncSkillsPush() {
|
|
5607
|
-
const localSkills =
|
|
5771
|
+
const localSkills = join44(claudeHome(), "skills");
|
|
5608
5772
|
const stat = lstatSync10(localSkills, { throwIfNoEntry: false });
|
|
5609
5773
|
if (stat === void 0) return;
|
|
5610
5774
|
if (stat.isSymbolicLink()) return;
|
|
5611
|
-
const sharedSkills =
|
|
5775
|
+
const sharedSkills = join44(repoHome(), "shared", "skills");
|
|
5612
5776
|
copySkillsPush(localSkills, sharedSkills);
|
|
5613
5777
|
}
|
|
5614
5778
|
|
|
5615
5779
|
// src/preview.ts
|
|
5616
5780
|
init_config();
|
|
5617
|
-
import { existsSync as
|
|
5618
|
-
import { join as
|
|
5781
|
+
import { existsSync as existsSync38 } from "node:fs";
|
|
5782
|
+
import { join as join45 } from "node:path";
|
|
5619
5783
|
|
|
5620
5784
|
// node_modules/diff/libesm/diff/base.js
|
|
5621
5785
|
var Diff = class {
|
|
@@ -5901,7 +6065,7 @@ function diffJsonStrings(currentJsonText, newJsonText) {
|
|
|
5901
6065
|
return lines.join("\n");
|
|
5902
6066
|
}
|
|
5903
6067
|
function readJsonOrNull(path) {
|
|
5904
|
-
if (!
|
|
6068
|
+
if (!existsSync38(path)) return null;
|
|
5905
6069
|
try {
|
|
5906
6070
|
return readJson(path);
|
|
5907
6071
|
} catch {
|
|
@@ -5915,12 +6079,12 @@ function previewSettings(basePath, hostPath, settingsPath) {
|
|
|
5915
6079
|
}
|
|
5916
6080
|
const notes = [];
|
|
5917
6081
|
const hostOverrides = readJsonOrNull(hostPath);
|
|
5918
|
-
if (hostOverrides === null &&
|
|
6082
|
+
if (hostOverrides === null && existsSync38(hostPath)) {
|
|
5919
6083
|
notes.push(`malformed hosts/${HOST}.json; ignoring overrides`);
|
|
5920
6084
|
}
|
|
5921
6085
|
const merged = stripGsdHookEntries(deepMerge(base, hostOverrides ?? {}));
|
|
5922
6086
|
const current = readJsonOrNull(settingsPath);
|
|
5923
|
-
if (current === null &&
|
|
6087
|
+
if (current === null && existsSync38(settingsPath)) {
|
|
5924
6088
|
return { diff: "", notes: [...notes, "malformed; skipping diff"] };
|
|
5925
6089
|
}
|
|
5926
6090
|
const strippedCurrent = stripGsdHookEntries(current ?? {});
|
|
@@ -5961,9 +6125,9 @@ function computePreview(ts, map, verb = "pull") {
|
|
|
5961
6125
|
onPreview: (e) => addItem(links, formatLinkRow(e))
|
|
5962
6126
|
});
|
|
5963
6127
|
const settingsResult = previewSettings(
|
|
5964
|
-
|
|
5965
|
-
|
|
5966
|
-
|
|
6128
|
+
join45(repo, "shared", "settings.base.json"),
|
|
6129
|
+
join45(repo, "hosts", `${HOST}.json`),
|
|
6130
|
+
join45(claude, "settings.json")
|
|
5967
6131
|
);
|
|
5968
6132
|
const settingsSection = buildSettingsSectionForPreview(settingsResult);
|
|
5969
6133
|
const sessions = section("Sessions");
|
|
@@ -6164,8 +6328,8 @@ function cmdPull(opts = {}) {
|
|
|
6164
6328
|
const forceRemote = opts.forceRemote === true;
|
|
6165
6329
|
const repo = repoHome();
|
|
6166
6330
|
const backup = backupBase();
|
|
6167
|
-
if (!
|
|
6168
|
-
if (!
|
|
6331
|
+
if (!existsSync39(repo)) die(`repo not cloned at ${repo}`);
|
|
6332
|
+
if (!existsSync39(join46(repo, "shared", "settings.base.json"))) {
|
|
6169
6333
|
die("repo not initialized; run 'nomad init' to scaffold");
|
|
6170
6334
|
}
|
|
6171
6335
|
const handle = acquireLock("pull");
|
|
@@ -6174,9 +6338,9 @@ function cmdPull(opts = {}) {
|
|
|
6174
6338
|
const ts = freshBackupTs(backup);
|
|
6175
6339
|
handleWedge(repo, forceRemote);
|
|
6176
6340
|
if (!dryRun) {
|
|
6177
|
-
const backupRoot =
|
|
6341
|
+
const backupRoot = join46(backup, ts);
|
|
6178
6342
|
try {
|
|
6179
|
-
|
|
6343
|
+
mkdirSync10(backupRoot, { recursive: true });
|
|
6180
6344
|
} catch (err) {
|
|
6181
6345
|
die(`could not create backup dir: ${err.message}`);
|
|
6182
6346
|
}
|
|
@@ -6187,8 +6351,8 @@ function cmdPull(opts = {}) {
|
|
|
6187
6351
|
const prePostHeads = capturePrePostHeads(repo, () => {
|
|
6188
6352
|
gitOrFatal(["pull", "--rebase", "--autostash"], "git pull --rebase", repo);
|
|
6189
6353
|
});
|
|
6190
|
-
const mapPath =
|
|
6191
|
-
const map =
|
|
6354
|
+
const mapPath = join46(repo, "path-map.json");
|
|
6355
|
+
const map = existsSync39(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
6192
6356
|
divergenceCheckExtras(ts);
|
|
6193
6357
|
if (dryRun) {
|
|
6194
6358
|
computePreview(ts, map, "pull");
|
|
@@ -6210,8 +6374,63 @@ function cmdPull(opts = {}) {
|
|
|
6210
6374
|
|
|
6211
6375
|
// src/commands.push.ts
|
|
6212
6376
|
init_config();
|
|
6213
|
-
import { existsSync as
|
|
6214
|
-
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
|
+
}
|
|
6215
6434
|
|
|
6216
6435
|
// src/commands.push.allowlist.ts
|
|
6217
6436
|
init_config();
|
|
@@ -6302,6 +6521,85 @@ function enforceAllowList(statusPorcelain, map) {
|
|
|
6302
6521
|
throw new NomadFatal("push allow-list violations");
|
|
6303
6522
|
}
|
|
6304
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
|
+
|
|
6305
6603
|
// src/push-global-config.ts
|
|
6306
6604
|
init_config();
|
|
6307
6605
|
import { execFileSync as execFileSync18 } from "node:child_process";
|
|
@@ -6374,24 +6672,36 @@ function collectGlobalConfigChanges(repoHome2, hostname2, opts) {
|
|
|
6374
6672
|
return changes;
|
|
6375
6673
|
}
|
|
6376
6674
|
|
|
6377
|
-
// src/commands.push.ts
|
|
6675
|
+
// src/commands.push.steps.ts
|
|
6378
6676
|
init_push_leak_verdict();
|
|
6379
|
-
init_push_checks();
|
|
6380
6677
|
|
|
6381
6678
|
// src/push-preview.ts
|
|
6382
6679
|
init_color();
|
|
6383
6680
|
init_config();
|
|
6384
6681
|
init_config_sharedDirs_guard();
|
|
6385
6682
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
6386
|
-
import { copyFileSync, existsSync as
|
|
6683
|
+
import { copyFileSync, existsSync as existsSync42, mkdirSync as mkdirSync11, readdirSync as readdirSync16, rmSync as rmSync14 } from "node:fs";
|
|
6387
6684
|
import { homedir as homedir5 } from "node:os";
|
|
6388
|
-
import { join as
|
|
6685
|
+
import { join as join50, relative as relative6, sep as sep8 } from "node:path";
|
|
6389
6686
|
init_push_leak_verdict();
|
|
6390
6687
|
init_push_gitleaks();
|
|
6391
6688
|
init_utils_fs();
|
|
6392
6689
|
init_utils_json();
|
|
6393
6690
|
var NOTHING_TO_SCAN_ROW = `${dim(infoGlyph)} nothing to scan, no leaks`;
|
|
6394
|
-
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) {
|
|
6395
6705
|
if (typeof map.projects !== "object" || map.projects === null) return 0;
|
|
6396
6706
|
const reverse = /* @__PURE__ */ new Map();
|
|
6397
6707
|
for (const [logical, hosts] of Object.entries(map.projects)) {
|
|
@@ -6400,14 +6710,15 @@ function stageSessions(tmpRoot, map) {
|
|
|
6400
6710
|
if (!p || p === "TBD") continue;
|
|
6401
6711
|
reverse.set(encodePath(p), logical);
|
|
6402
6712
|
}
|
|
6403
|
-
const localProjects =
|
|
6404
|
-
if (!
|
|
6713
|
+
const localProjects = join50(claudeHome(), "projects");
|
|
6714
|
+
if (!existsSync42(localProjects)) return 0;
|
|
6405
6715
|
let staged = 0;
|
|
6406
|
-
for (const dir of
|
|
6716
|
+
for (const dir of readdirSync16(localProjects)) {
|
|
6407
6717
|
const logical = reverse.get(dir);
|
|
6408
6718
|
if (!logical) continue;
|
|
6409
|
-
|
|
6410
|
-
|
|
6719
|
+
const localDir = join50(localProjects, dir);
|
|
6720
|
+
const dstDir = join50(tmpRoot, "shared", "projects", logical);
|
|
6721
|
+
if (stageSessionDir(localDir, dstDir, changed)) staged++;
|
|
6411
6722
|
}
|
|
6412
6723
|
return staged;
|
|
6413
6724
|
}
|
|
@@ -6420,31 +6731,31 @@ function stageExtras(tmpRoot, map) {
|
|
|
6420
6731
|
assertSafeLogical(logical);
|
|
6421
6732
|
const localRoot = map.projects[logical]?.[HOST];
|
|
6422
6733
|
if (!localRoot || localRoot === "TBD") continue;
|
|
6423
|
-
for (const
|
|
6424
|
-
if (!whitelist.includes(
|
|
6425
|
-
const src =
|
|
6426
|
-
if (!
|
|
6427
|
-
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);
|
|
6428
6739
|
copyExtras(src, dst);
|
|
6429
6740
|
staged++;
|
|
6430
6741
|
}
|
|
6431
6742
|
}
|
|
6432
6743
|
return staged;
|
|
6433
6744
|
}
|
|
6434
|
-
function previewPushLeaks(map) {
|
|
6435
|
-
const cacheDir =
|
|
6436
|
-
|
|
6745
|
+
function previewPushLeaks(map, opts = {}) {
|
|
6746
|
+
const cacheDir = join50(homedir5(), ".cache", "claude-nomad");
|
|
6747
|
+
mkdirSync11(cacheDir, { recursive: true });
|
|
6437
6748
|
const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes2(4).toString("hex")}`;
|
|
6438
|
-
const tmpRoot =
|
|
6749
|
+
const tmpRoot = join50(cacheDir, `push-preview-tree-${stamp}`);
|
|
6439
6750
|
try {
|
|
6440
|
-
const sessionCount = stageSessions(tmpRoot, map);
|
|
6751
|
+
const sessionCount = stageSessions(tmpRoot, map, opts.selection?.changed);
|
|
6441
6752
|
const extrasCount = stageExtras(tmpRoot, map);
|
|
6442
6753
|
if (sessionCount + extrasCount === 0) {
|
|
6443
6754
|
return { leak: false, verdictRow: NOTHING_TO_SCAN_ROW, recovery: null, findings: [] };
|
|
6444
6755
|
}
|
|
6445
|
-
const ignoreFile =
|
|
6446
|
-
if (
|
|
6447
|
-
copyFileSync(ignoreFile,
|
|
6756
|
+
const ignoreFile = join50(repoHome(), ".gitleaksignore");
|
|
6757
|
+
if (existsSync42(ignoreFile)) {
|
|
6758
|
+
copyFileSync(ignoreFile, join50(tmpRoot, ".gitleaksignore"));
|
|
6448
6759
|
}
|
|
6449
6760
|
let findings;
|
|
6450
6761
|
try {
|
|
@@ -6461,59 +6772,9 @@ function previewPushLeaks(map) {
|
|
|
6461
6772
|
}
|
|
6462
6773
|
}
|
|
6463
6774
|
|
|
6464
|
-
// src/commands.push.ts
|
|
6775
|
+
// src/commands.push.steps.ts
|
|
6465
6776
|
init_utils();
|
|
6466
|
-
|
|
6467
|
-
init_utils_json();
|
|
6468
|
-
function stripGsdHooksFromBase(repo, backup) {
|
|
6469
|
-
const basePath = join47(repo, "shared", "settings.base.json");
|
|
6470
|
-
if (!existsSync40(basePath)) return;
|
|
6471
|
-
let base;
|
|
6472
|
-
try {
|
|
6473
|
-
base = readJson(basePath);
|
|
6474
|
-
} catch {
|
|
6475
|
-
return;
|
|
6476
|
-
}
|
|
6477
|
-
if (!baseHasGsdHookEntries(base)) return;
|
|
6478
|
-
const stripped = stripGsdHookEntries(base);
|
|
6479
|
-
const ts = freshBackupTs(backup);
|
|
6480
|
-
backupRepoWrite(basePath, ts, repo);
|
|
6481
|
-
writeJsonAtomic(basePath, stripped);
|
|
6482
|
-
}
|
|
6483
|
-
function reportSettingsAheadDrift(repo) {
|
|
6484
|
-
const basePath = join47(repo, "shared", "settings.base.json");
|
|
6485
|
-
if (!existsSync40(basePath)) return;
|
|
6486
|
-
const settingsPath = join47(claudeHome(), "settings.json");
|
|
6487
|
-
if (!existsSync40(settingsPath)) return;
|
|
6488
|
-
try {
|
|
6489
|
-
const base = readJson(basePath);
|
|
6490
|
-
const hostPath = join47(repo, "hosts", `${HOST}.json`);
|
|
6491
|
-
const overrides = existsSync40(hostPath) ? readJson(hostPath) : {};
|
|
6492
|
-
const merged = deepMerge(base, overrides);
|
|
6493
|
-
const settings = readJson(settingsPath);
|
|
6494
|
-
const { ahead } = classifySettingsDrift(merged, settings);
|
|
6495
|
-
const { promotable } = partitionByCaptureExclusion(ahead);
|
|
6496
|
-
if (promotable.length === 0) return;
|
|
6497
|
-
const { phrase, pronoun, verb } = describeSettings(promotable);
|
|
6498
|
-
warn(
|
|
6499
|
-
`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).`
|
|
6500
|
-
);
|
|
6501
|
-
} catch {
|
|
6502
|
-
}
|
|
6503
|
-
}
|
|
6504
|
-
function guardGitlinks(repo) {
|
|
6505
|
-
const gitlinks = findGitlinks(join47(repo, "shared"));
|
|
6506
|
-
if (gitlinks.length === 0) return;
|
|
6507
|
-
for (const p of gitlinks) {
|
|
6508
|
-
const rel = relative5(repo, p);
|
|
6509
|
-
fail(`gitlink: ${rel} would push as submodule (run: rm -rf ${rel} or remove the nested repo)`);
|
|
6510
|
-
}
|
|
6511
|
-
const noun = gitlinks.length === 1 ? "entry" : "entries";
|
|
6512
|
-
throw new NomadFatal(
|
|
6513
|
-
`gitlink trap: ${gitlinks.length} nested .git ${noun} in shared/; remove before retry`
|
|
6514
|
-
);
|
|
6515
|
-
}
|
|
6516
|
-
async function commitAndPush(st, ts, map, redactAll, allowAll, allowRule, repo) {
|
|
6777
|
+
async function commitAndPush(st, ts, map, resolution, repo, newManifest) {
|
|
6517
6778
|
gitOrFatal(["add", "-A"], "git add", repo);
|
|
6518
6779
|
const staged = parsePorcelainZ2(gitStatusPorcelainZ(repo));
|
|
6519
6780
|
const toDrop = staged.filter((p) => isGsdDropped(p));
|
|
@@ -6529,55 +6790,61 @@ async function commitAndPush(st, ts, map, redactAll, allowAll, allowRule, repo)
|
|
|
6529
6790
|
let verdict = withSpinner("Scanning for secrets", () => scanPushVerdict(repo));
|
|
6530
6791
|
if (verdict.leak) {
|
|
6531
6792
|
renderPushTree(st, verdict);
|
|
6532
|
-
verdict = await resolveLeakFindings(verdict, ts, map,
|
|
6793
|
+
verdict = await resolveLeakFindings(verdict, ts, map, resolution);
|
|
6533
6794
|
}
|
|
6534
6795
|
gitOrFatal(["commit", "-m", `chore: sync from ${HOST}`], "git commit", repo);
|
|
6535
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
|
+
}
|
|
6536
6802
|
renderPushTree(st, verdict);
|
|
6537
6803
|
}
|
|
6538
|
-
function runDryRunPreview(st, map, repo) {
|
|
6804
|
+
function runDryRunPreview(st, map, repo, selection) {
|
|
6539
6805
|
st.globalConfig = collectGlobalConfigChanges(repo, HOST, { staged: false });
|
|
6540
6806
|
if (map === null) {
|
|
6541
6807
|
renderNoScanTree(st, { noMapHint: true });
|
|
6542
6808
|
return;
|
|
6543
6809
|
}
|
|
6544
|
-
const verdict = previewPushLeaks(map);
|
|
6810
|
+
const verdict = withSpinner("Scanning for secrets", () => previewPushLeaks(map, { selection }));
|
|
6545
6811
|
renderPushTree(st, verdict);
|
|
6546
6812
|
if (verdict.recovery !== null) fail(verdict.recovery);
|
|
6547
6813
|
}
|
|
6548
|
-
|
|
6549
|
-
|
|
6550
|
-
|
|
6551
|
-
|
|
6552
|
-
|
|
6553
|
-
}
|
|
6554
|
-
if (allowAll && allowRule !== void 0) {
|
|
6555
|
-
die("--redact-all, --allow-all, and --allow are mutually exclusive resolution modes");
|
|
6556
|
-
}
|
|
6557
|
-
if (dryRun && wantsResolution) {
|
|
6558
|
-
die(
|
|
6559
|
-
"--redact-all, --allow-all, and --allow cannot be combined with --dry-run (dry-run resolves nothing)"
|
|
6560
|
-
);
|
|
6561
|
-
}
|
|
6562
|
-
}
|
|
6814
|
+
|
|
6815
|
+
// src/commands.push.ts
|
|
6816
|
+
init_push_checks();
|
|
6817
|
+
init_utils();
|
|
6818
|
+
init_utils_fs();
|
|
6563
6819
|
async function cmdPush(opts = {}) {
|
|
6564
6820
|
const dryRun = opts.dryRun === true;
|
|
6565
6821
|
const redactAll = opts.redactAll === true;
|
|
6566
6822
|
const allowAll = opts.allowAll === true;
|
|
6567
6823
|
const allowRule = opts.allowRule;
|
|
6824
|
+
const fullScan = opts.fullScan === true;
|
|
6568
6825
|
guardResolutionModeConflicts(dryRun, redactAll, allowAll, allowRule);
|
|
6569
6826
|
const repo = repoHome();
|
|
6570
6827
|
const backup = backupBase();
|
|
6571
|
-
if (!
|
|
6828
|
+
if (!existsSync43(repo)) die(`repo not cloned at ${repo}`);
|
|
6572
6829
|
const handle = acquireLock("push");
|
|
6573
6830
|
if (handle === null) process.exit(0);
|
|
6574
6831
|
try {
|
|
6575
6832
|
console.log(dryRun ? `push on host=${HOST} (dry-run)` : `push on host=${HOST}`);
|
|
6576
6833
|
reportSettingsAheadDrift(repo);
|
|
6577
|
-
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
|
+
);
|
|
6578
6845
|
withSpinner("Rebasing onto origin", () => rebaseBeforePush(repo));
|
|
6579
6846
|
const ts = freshBackupTs(backup);
|
|
6580
|
-
const remap = withSpinner("Syncing sessions", () => remapPush(ts, { dryRun }));
|
|
6847
|
+
const remap = withSpinner("Syncing sessions", () => remapPush(ts, { dryRun, selection }));
|
|
6581
6848
|
const extras = withSpinner("Syncing extras", () => remapExtrasPush(ts, { dryRun }));
|
|
6582
6849
|
if (!dryRun) {
|
|
6583
6850
|
syncSkillsPush();
|
|
@@ -6591,15 +6858,13 @@ async function cmdPush(opts = {}) {
|
|
|
6591
6858
|
renderNoScanTree(st);
|
|
6592
6859
|
return;
|
|
6593
6860
|
}
|
|
6594
|
-
|
|
6595
|
-
|
|
6596
|
-
|
|
6597
|
-
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");
|
|
6598
6864
|
}
|
|
6599
|
-
const map = readPathMap(mapPath);
|
|
6600
6865
|
if (status) enforceAllowList(status, map);
|
|
6601
|
-
if (dryRun) return runDryRunPreview(st, map, repo);
|
|
6602
|
-
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);
|
|
6603
6868
|
} catch (err) {
|
|
6604
6869
|
if (err instanceof NomadFatal) {
|
|
6605
6870
|
fail(err.message);
|
|
@@ -6656,18 +6921,18 @@ init_config();
|
|
|
6656
6921
|
|
|
6657
6922
|
// src/diff.ts
|
|
6658
6923
|
init_config();
|
|
6659
|
-
import { existsSync as
|
|
6660
|
-
import { join as
|
|
6924
|
+
import { existsSync as existsSync44 } from "node:fs";
|
|
6925
|
+
import { join as join52 } from "node:path";
|
|
6661
6926
|
init_utils();
|
|
6662
6927
|
init_utils_fs();
|
|
6663
6928
|
init_utils_json();
|
|
6664
6929
|
function cmdDiff() {
|
|
6665
6930
|
try {
|
|
6666
6931
|
const repo = repoHome();
|
|
6667
|
-
if (!
|
|
6932
|
+
if (!existsSync44(repo)) die(`repo not cloned at ${repo}`);
|
|
6668
6933
|
const ts = freshBackupTs(backupBase());
|
|
6669
|
-
const mapPath =
|
|
6670
|
-
const map =
|
|
6934
|
+
const mapPath = join52(repo, "path-map.json");
|
|
6935
|
+
const map = existsSync44(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
6671
6936
|
computePreview(ts, map, "diff");
|
|
6672
6937
|
} catch (err) {
|
|
6673
6938
|
if (err instanceof NomadFatal) {
|
|
@@ -6681,8 +6946,8 @@ function cmdDiff() {
|
|
|
6681
6946
|
|
|
6682
6947
|
// src/init.ts
|
|
6683
6948
|
init_config();
|
|
6684
|
-
import { existsSync as
|
|
6685
|
-
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";
|
|
6686
6951
|
|
|
6687
6952
|
// src/init.gh-onboard.ts
|
|
6688
6953
|
init_config();
|
|
@@ -6764,33 +7029,33 @@ init_config();
|
|
|
6764
7029
|
init_utils();
|
|
6765
7030
|
init_utils_fs();
|
|
6766
7031
|
init_utils_json();
|
|
6767
|
-
import { copyFileSync as copyFileSync2, cpSync as cpSync7, existsSync as
|
|
6768
|
-
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";
|
|
6769
7034
|
function snapshotIntoShared(map) {
|
|
6770
7035
|
const repo = repoHome();
|
|
6771
7036
|
const claude = claudeHome();
|
|
6772
7037
|
for (const name of allSharedLinks(map)) {
|
|
6773
|
-
const src =
|
|
6774
|
-
if (!
|
|
6775
|
-
const dst =
|
|
6776
|
-
if (
|
|
6777
|
-
const gk =
|
|
6778
|
-
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);
|
|
6779
7044
|
cpSync7(src, dst, { recursive: true, force: false, errorOnExist: true });
|
|
6780
7045
|
} else {
|
|
6781
7046
|
copyFileSync2(src, dst);
|
|
6782
7047
|
}
|
|
6783
7048
|
log(`snapshotted shared/${name} from ${src}`);
|
|
6784
7049
|
}
|
|
6785
|
-
const userSettings =
|
|
6786
|
-
if (
|
|
7050
|
+
const userSettings = join53(claude, "settings.json");
|
|
7051
|
+
if (existsSync45(userSettings)) {
|
|
6787
7052
|
let parsed;
|
|
6788
7053
|
try {
|
|
6789
7054
|
parsed = readJson(userSettings);
|
|
6790
7055
|
} catch (err) {
|
|
6791
7056
|
return die(`malformed ${userSettings}: ${err.message}`);
|
|
6792
7057
|
}
|
|
6793
|
-
const hostFile =
|
|
7058
|
+
const hostFile = join53(repo, "hosts", `${HOST}.json`);
|
|
6794
7059
|
writeJsonAtomic(hostFile, parsed);
|
|
6795
7060
|
log(`snapshotted hosts/${HOST}.json from ${userSettings}`);
|
|
6796
7061
|
}
|
|
@@ -6803,14 +7068,14 @@ var SHARED_CLAUDE_MD = "<!-- claude-nomad shared CLAUDE.md; symlinked into ~/.cl
|
|
|
6803
7068
|
var SHARED_KEEP_DIRS = ["agents", "skills", "commands", "rules", "hooks"];
|
|
6804
7069
|
function preflightConflict(repoHome2) {
|
|
6805
7070
|
const candidates = [
|
|
6806
|
-
|
|
6807
|
-
|
|
6808
|
-
|
|
6809
|
-
|
|
6810
|
-
|
|
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")
|
|
6811
7076
|
];
|
|
6812
7077
|
for (const c of candidates) {
|
|
6813
|
-
if (
|
|
7078
|
+
if (existsSync46(c)) return c;
|
|
6814
7079
|
}
|
|
6815
7080
|
return null;
|
|
6816
7081
|
}
|
|
@@ -6822,31 +7087,31 @@ function cmdInit(opts = {}) {
|
|
|
6822
7087
|
const keepActions = opts.keepActions === true;
|
|
6823
7088
|
const repo = repoHome();
|
|
6824
7089
|
const claude = claudeHome();
|
|
6825
|
-
|
|
7090
|
+
mkdirSync12(repo, { recursive: true });
|
|
6826
7091
|
const conflict = preflightConflict(repo);
|
|
6827
7092
|
if (conflict !== null) {
|
|
6828
7093
|
die(`already initialized; refusing to clobber ${conflict}`);
|
|
6829
7094
|
}
|
|
6830
7095
|
ensureOriginRepo(opts.repoName ?? DEFAULT_REPO_NAME, opts.run);
|
|
6831
|
-
|
|
6832
|
-
|
|
7096
|
+
mkdirSync12(join54(repo, "shared"), { recursive: true });
|
|
7097
|
+
mkdirSync12(join54(repo, "hosts"), { recursive: true });
|
|
6833
7098
|
for (const name of SHARED_KEEP_DIRS) {
|
|
6834
|
-
|
|
7099
|
+
mkdirSync12(join54(repo, "shared", name), { recursive: true });
|
|
6835
7100
|
}
|
|
6836
|
-
const userClaudeMd =
|
|
6837
|
-
if (!snapshot || !
|
|
6838
|
-
writeFileSync6(
|
|
7101
|
+
const userClaudeMd = join54(claude, "CLAUDE.md");
|
|
7102
|
+
if (!snapshot || !existsSync46(userClaudeMd)) {
|
|
7103
|
+
writeFileSync6(join54(repo, "shared", "CLAUDE.md"), SHARED_CLAUDE_MD);
|
|
6839
7104
|
item("created shared/CLAUDE.md");
|
|
6840
7105
|
}
|
|
6841
7106
|
for (const name of SHARED_KEEP_DIRS) {
|
|
6842
|
-
writeFileSync6(
|
|
7107
|
+
writeFileSync6(join54(repo, "shared", name, ".gitkeep"), "");
|
|
6843
7108
|
item(`created shared/${name}/.gitkeep`);
|
|
6844
7109
|
}
|
|
6845
|
-
writeFileSync6(
|
|
7110
|
+
writeFileSync6(join54(repo, "hosts", ".gitkeep"), "");
|
|
6846
7111
|
item("created hosts/.gitkeep");
|
|
6847
|
-
writeJsonAtomic(
|
|
7112
|
+
writeJsonAtomic(join54(repo, "shared", "settings.base.json"), {});
|
|
6848
7113
|
item("created shared/settings.base.json");
|
|
6849
|
-
writeJsonAtomic(
|
|
7114
|
+
writeJsonAtomic(join54(repo, "path-map.json"), { projects: {} });
|
|
6850
7115
|
item("created path-map.json");
|
|
6851
7116
|
if (snapshot) {
|
|
6852
7117
|
snapshotIntoShared({ projects: {} });
|
|
@@ -6912,21 +7177,21 @@ function maybeDisableRepoActions(repoHome2, run) {
|
|
|
6912
7177
|
// src/init.prompt.ts
|
|
6913
7178
|
init_config();
|
|
6914
7179
|
init_utils();
|
|
6915
|
-
import { existsSync as
|
|
6916
|
-
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";
|
|
6917
7182
|
import { createInterface as createInterface3 } from "node:readline/promises";
|
|
6918
7183
|
function nonEmptyExists(path) {
|
|
6919
|
-
if (!
|
|
7184
|
+
if (!existsSync47(path)) return false;
|
|
6920
7185
|
try {
|
|
6921
|
-
if (
|
|
7186
|
+
if (statSync13(path).isDirectory()) return readdirSync17(path).length > 0;
|
|
6922
7187
|
return true;
|
|
6923
7188
|
} catch {
|
|
6924
7189
|
return false;
|
|
6925
7190
|
}
|
|
6926
7191
|
}
|
|
6927
7192
|
function hasExistingClaudeConfig(claudeHome2) {
|
|
6928
|
-
if (
|
|
6929
|
-
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)));
|
|
6930
7195
|
}
|
|
6931
7196
|
async function confirmSnapshotDefault(claudeHome2) {
|
|
6932
7197
|
if (process.stdin.isTTY !== true || process.stdout.isTTY !== true) {
|
|
@@ -7176,6 +7441,8 @@ function applyPushToken(argv, i, st) {
|
|
|
7176
7441
|
return applyBool(st.allowAll, () => st.allowAll = true);
|
|
7177
7442
|
case "--allow":
|
|
7178
7443
|
return applyAllow2(argv, i, st);
|
|
7444
|
+
case "--full-scan":
|
|
7445
|
+
return applyBool(st.fullScan, () => st.fullScan = true);
|
|
7179
7446
|
default:
|
|
7180
7447
|
return REJECT;
|
|
7181
7448
|
}
|
|
@@ -7185,7 +7452,8 @@ function parsePushArgs(argv) {
|
|
|
7185
7452
|
dryRun: false,
|
|
7186
7453
|
redactAll: false,
|
|
7187
7454
|
allowAll: false,
|
|
7188
|
-
allowRule: void 0
|
|
7455
|
+
allowRule: void 0,
|
|
7456
|
+
fullScan: false
|
|
7189
7457
|
};
|
|
7190
7458
|
let i = 3;
|
|
7191
7459
|
while (i < argv.length) {
|
|
@@ -7202,14 +7470,15 @@ function parsePushArgs(argv) {
|
|
|
7202
7470
|
dryRun: st.dryRun,
|
|
7203
7471
|
redactAll: st.redactAll,
|
|
7204
7472
|
allowAll: st.allowAll,
|
|
7205
|
-
allowRule: st.allowRule
|
|
7473
|
+
allowRule: st.allowRule,
|
|
7474
|
+
fullScan: st.fullScan
|
|
7206
7475
|
};
|
|
7207
7476
|
}
|
|
7208
7477
|
|
|
7209
7478
|
// package.json
|
|
7210
7479
|
var package_default = {
|
|
7211
7480
|
name: "claude-nomad",
|
|
7212
|
-
version: "0.
|
|
7481
|
+
version: "0.56.0",
|
|
7213
7482
|
type: "module",
|
|
7214
7483
|
description: "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
|
|
7215
7484
|
keywords: [
|
|
@@ -7317,6 +7586,10 @@ var DEFAULT_HELP = [
|
|
|
7317
7586
|
row(" push", "Rebase, run safety checks (gitleaks, gitlinks, allow-list), commit, push."),
|
|
7318
7587
|
row(" --dry-run", "Run pre-checks (rebase, gitleaks probe, gitlink scan) and preview"),
|
|
7319
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."),
|
|
7320
7593
|
row(" --redact-all", "Redact all findings non-interactively (backup, no prompt); no TTY"),
|
|
7321
7594
|
cont("required. Does not auto-Allow. Mutually exclusive with --allow*."),
|
|
7322
7595
|
cont("Cannot combine with --dry-run."),
|
|
@@ -7430,15 +7703,15 @@ var DEFAULT_HELP = [
|
|
|
7430
7703
|
init_config();
|
|
7431
7704
|
init_utils();
|
|
7432
7705
|
init_utils_json();
|
|
7433
|
-
import { existsSync as
|
|
7434
|
-
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";
|
|
7435
7708
|
function resumeCmd(sessionId) {
|
|
7436
7709
|
if (!/^[A-Za-z0-9_-]+$/.test(sessionId) || sessionId.length > 128) {
|
|
7437
7710
|
fail(`invalid session id: ${sessionId}`);
|
|
7438
7711
|
process.exit(1);
|
|
7439
7712
|
}
|
|
7440
|
-
const projectsRoot =
|
|
7441
|
-
if (!
|
|
7713
|
+
const projectsRoot = join56(claudeHome(), "projects");
|
|
7714
|
+
if (!existsSync48(projectsRoot)) {
|
|
7442
7715
|
fail(`${projectsRoot} does not exist`);
|
|
7443
7716
|
process.exit(1);
|
|
7444
7717
|
}
|
|
@@ -7452,8 +7725,8 @@ function resumeCmd(sessionId) {
|
|
|
7452
7725
|
fail(`no cwd field found in ${jsonlPath}`);
|
|
7453
7726
|
process.exit(1);
|
|
7454
7727
|
}
|
|
7455
|
-
const mapPath =
|
|
7456
|
-
if (!
|
|
7728
|
+
const mapPath = join56(repoHome(), "path-map.json");
|
|
7729
|
+
if (!existsSync48(mapPath)) {
|
|
7457
7730
|
fail("path-map.json missing");
|
|
7458
7731
|
process.exit(1);
|
|
7459
7732
|
}
|
|
@@ -7475,14 +7748,14 @@ function resumeCmd(sessionId) {
|
|
|
7475
7748
|
console.log(`cd ${shQuote(hit.localPath)} && claude --resume ${shQuote(sessionId)}`);
|
|
7476
7749
|
}
|
|
7477
7750
|
function findTranscriptPath(projectsRoot, sessionId) {
|
|
7478
|
-
for (const dir of
|
|
7479
|
-
const candidate =
|
|
7480
|
-
if (
|
|
7751
|
+
for (const dir of readdirSync18(projectsRoot)) {
|
|
7752
|
+
const candidate = join56(projectsRoot, dir, `${sessionId}.jsonl`);
|
|
7753
|
+
if (existsSync48(candidate)) return candidate;
|
|
7481
7754
|
}
|
|
7482
7755
|
return null;
|
|
7483
7756
|
}
|
|
7484
7757
|
function extractRecordedCwd(jsonlPath) {
|
|
7485
|
-
for (const line of
|
|
7758
|
+
for (const line of readFileSync15(jsonlPath, "utf8").split("\n")) {
|
|
7486
7759
|
if (!line.trim()) continue;
|
|
7487
7760
|
try {
|
|
7488
7761
|
const obj = JSON.parse(line);
|
|
@@ -7542,7 +7815,7 @@ try {
|
|
|
7542
7815
|
const pushArgs = parsePushArgs(process.argv);
|
|
7543
7816
|
if (pushArgs === null) {
|
|
7544
7817
|
console.error(
|
|
7545
|
-
"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]"
|
|
7546
7819
|
);
|
|
7547
7820
|
process.exit(1);
|
|
7548
7821
|
}
|
|
@@ -7550,7 +7823,8 @@ try {
|
|
|
7550
7823
|
dryRun: pushArgs.dryRun,
|
|
7551
7824
|
redactAll: pushArgs.redactAll,
|
|
7552
7825
|
allowAll: pushArgs.allowAll,
|
|
7553
|
-
allowRule: pushArgs.allowRule
|
|
7826
|
+
allowRule: pushArgs.allowRule,
|
|
7827
|
+
fullScan: pushArgs.fullScan
|
|
7554
7828
|
});
|
|
7555
7829
|
break;
|
|
7556
7830
|
}
|