claude-nomad 0.67.1 → 0.67.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/README.md +7 -1
- package/dist/nomad.mjs +685 -498
- package/package.json +4 -3
package/dist/nomad.mjs
CHANGED
|
@@ -806,12 +806,12 @@ import {
|
|
|
806
806
|
cpSync as cpSync3,
|
|
807
807
|
existsSync as existsSync3,
|
|
808
808
|
fsyncSync,
|
|
809
|
-
lstatSync as
|
|
809
|
+
lstatSync as lstatSync5,
|
|
810
810
|
mkdirSync,
|
|
811
811
|
openSync,
|
|
812
812
|
renameSync,
|
|
813
813
|
rmdirSync,
|
|
814
|
-
statSync,
|
|
814
|
+
statSync as statSync2,
|
|
815
815
|
symlinkSync,
|
|
816
816
|
writeFileSync
|
|
817
817
|
} from "node:fs";
|
|
@@ -840,7 +840,7 @@ function renameAtomicRetry(tmp, dst, renameFn = renameSync) {
|
|
|
840
840
|
}
|
|
841
841
|
}
|
|
842
842
|
function writeJsonAtomic(path, data) {
|
|
843
|
-
const mode = existsSync3(path) ?
|
|
843
|
+
const mode = existsSync3(path) ? statSync2(path).mode & 511 : 384;
|
|
844
844
|
const tmp = `${path}.tmp.${process.pid}`;
|
|
845
845
|
const fd = openSync(tmp, "w", mode);
|
|
846
846
|
try {
|
|
@@ -879,7 +879,7 @@ function discardEmptyBackupDir(backupRoot) {
|
|
|
879
879
|
}
|
|
880
880
|
function ensureSymlink(linkPath, target) {
|
|
881
881
|
if (existsSync3(linkPath)) {
|
|
882
|
-
if (
|
|
882
|
+
if (lstatSync5(linkPath).isSymbolicLink()) return;
|
|
883
883
|
die(`${linkPath} exists and is not a symlink. Move it aside first.`);
|
|
884
884
|
}
|
|
885
885
|
mkdirSync(dirname(linkPath), { recursive: true });
|
|
@@ -918,12 +918,12 @@ var init_utils_fs = __esm({
|
|
|
918
918
|
|
|
919
919
|
// src/commands.pull.wedge.ts
|
|
920
920
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
921
|
-
import { existsSync as
|
|
922
|
-
import { join as
|
|
921
|
+
import { existsSync as existsSync15 } from "node:fs";
|
|
922
|
+
import { join as join21 } from "node:path";
|
|
923
923
|
function detectWedge(repo) {
|
|
924
|
-
const g =
|
|
925
|
-
if (
|
|
926
|
-
if (
|
|
924
|
+
const g = join21(repo, ".git");
|
|
925
|
+
if (existsSync15(join21(g, "rebase-merge")) || existsSync15(join21(g, "rebase-apply"))) return "rebase";
|
|
926
|
+
if (existsSync15(join21(g, "MERGE_HEAD"))) return "merge";
|
|
927
927
|
return null;
|
|
928
928
|
}
|
|
929
929
|
function probeUnmergedIndex(repo) {
|
|
@@ -993,15 +993,15 @@ var init_commands_pull_wedge = __esm({
|
|
|
993
993
|
});
|
|
994
994
|
|
|
995
995
|
// src/push-gitleaks.config.ts
|
|
996
|
-
import { existsSync as
|
|
996
|
+
import { existsSync as existsSync16, mkdtempSync, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
|
|
997
997
|
import { tmpdir } from "node:os";
|
|
998
|
-
import { join as
|
|
998
|
+
import { join as join22 } from "node:path";
|
|
999
999
|
import { fileURLToPath } from "node:url";
|
|
1000
1000
|
function resolveTomlPath(repo = repoHome()) {
|
|
1001
|
-
const repoToml =
|
|
1002
|
-
if (
|
|
1001
|
+
const repoToml = join22(repo, ".gitleaks.toml");
|
|
1002
|
+
if (existsSync16(repoToml)) return repoToml;
|
|
1003
1003
|
const bundled = fileURLToPath(new URL("../.gitleaks.toml", import.meta.url));
|
|
1004
|
-
return
|
|
1004
|
+
return existsSync16(bundled) ? bundled : null;
|
|
1005
1005
|
}
|
|
1006
1006
|
function assertOverlayAllowlistsScoped(overlayBody) {
|
|
1007
1007
|
if (OVERLAY_INLINE_ALLOWLIST_RE.test(overlayBody)) {
|
|
@@ -1039,17 +1039,17 @@ function buildOverlayTempConfig(overlayBody, bundled) {
|
|
|
1039
1039
|
path = ${JSON.stringify(bundled)}
|
|
1040
1040
|
|
|
1041
1041
|
${overlayBody}`;
|
|
1042
|
-
const tempPath = mkdtempSync(
|
|
1043
|
-
const configPath =
|
|
1042
|
+
const tempPath = mkdtempSync(join22(tmpdir(), "nomad-gitleaks-cfg-"));
|
|
1043
|
+
const configPath = join22(tempPath, "config.toml");
|
|
1044
1044
|
writeFileSync3(configPath, tempBody, { mode: 384, flag: "wx" });
|
|
1045
1045
|
return { configPath, tempPath };
|
|
1046
1046
|
}
|
|
1047
1047
|
function resolveTomlConfig() {
|
|
1048
1048
|
const repo = repoHome();
|
|
1049
|
-
const overlayPath =
|
|
1050
|
-
const repoToml =
|
|
1049
|
+
const overlayPath = join22(repo, ".gitleaks.overlay.toml");
|
|
1050
|
+
const repoToml = join22(repo, ".gitleaks.toml");
|
|
1051
1051
|
const bundled = resolveTomlPath(repo);
|
|
1052
|
-
if (!
|
|
1052
|
+
if (!existsSync16(overlayPath)) {
|
|
1053
1053
|
return { path: bundled, tempPath: null };
|
|
1054
1054
|
}
|
|
1055
1055
|
if (bundled === repoToml) {
|
|
@@ -1137,7 +1137,7 @@ var init_autostash_guard = __esm({
|
|
|
1137
1137
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
1138
1138
|
import { readdirSync as readdirSync8, rmSync as rmSync7 } from "node:fs";
|
|
1139
1139
|
import { homedir as homedir2, platform } from "node:os";
|
|
1140
|
-
import { delimiter, join as
|
|
1140
|
+
import { delimiter, join as join23 } from "node:path";
|
|
1141
1141
|
function gitleaksInstallHint() {
|
|
1142
1142
|
const head = "gitleaks not on PATH (required for nomad push). Install:";
|
|
1143
1143
|
const plat = platform();
|
|
@@ -1185,7 +1185,7 @@ function findGitlinks(dir) {
|
|
|
1185
1185
|
return;
|
|
1186
1186
|
}
|
|
1187
1187
|
for (const e of entries) {
|
|
1188
|
-
const p =
|
|
1188
|
+
const p = join23(current, e.name);
|
|
1189
1189
|
if (e.name === ".git") {
|
|
1190
1190
|
hits.push(p);
|
|
1191
1191
|
continue;
|
|
@@ -1249,7 +1249,7 @@ var init_push_checks = __esm({
|
|
|
1249
1249
|
import { execFileSync as execFileSync8 } from "node:child_process";
|
|
1250
1250
|
import { mkdirSync as mkdirSync3, mkdtempSync as mkdtempSync2, readFileSync as readFileSync6, rmSync as rmSync8 } from "node:fs";
|
|
1251
1251
|
import { homedir as homedir3 } from "node:os";
|
|
1252
|
-
import { join as
|
|
1252
|
+
import { join as join27 } from "node:path";
|
|
1253
1253
|
function toFinding(entry) {
|
|
1254
1254
|
if (typeof entry !== "object" || entry === null) return null;
|
|
1255
1255
|
const e = entry;
|
|
@@ -1297,9 +1297,9 @@ function resolveScanFailure(reportPath, e, forwardStreams) {
|
|
|
1297
1297
|
return report;
|
|
1298
1298
|
}
|
|
1299
1299
|
function scanStagedTree(repoDir, forwardStreams = false) {
|
|
1300
|
-
const cacheDir =
|
|
1300
|
+
const cacheDir = join27(homedir3(), ".cache", "claude-nomad");
|
|
1301
1301
|
mkdirSync3(cacheDir, { recursive: true });
|
|
1302
|
-
const reportPath =
|
|
1302
|
+
const reportPath = join27(cacheDir, `gitleaks-${nowTimestamp()}-${process.pid}.json`);
|
|
1303
1303
|
const { path: toml, tempPath } = resolveTomlConfig();
|
|
1304
1304
|
const args = [
|
|
1305
1305
|
"protect",
|
|
@@ -1327,14 +1327,14 @@ function scanStagedTree(repoDir, forwardStreams = false) {
|
|
|
1327
1327
|
}
|
|
1328
1328
|
}
|
|
1329
1329
|
function scanFile(filePath, forwardStreams = false, timeoutMs = GITLEAKS_SCAN_TIMEOUT_MS) {
|
|
1330
|
-
const cacheDir =
|
|
1330
|
+
const cacheDir = join27(homedir3(), ".cache", "claude-nomad");
|
|
1331
1331
|
mkdirSync3(cacheDir, { recursive: true });
|
|
1332
1332
|
const { path: toml, tempPath } = resolveTomlConfig();
|
|
1333
1333
|
let reportDir;
|
|
1334
1334
|
let reportPath;
|
|
1335
1335
|
try {
|
|
1336
|
-
reportDir = mkdtempSync2(
|
|
1337
|
-
reportPath =
|
|
1336
|
+
reportDir = mkdtempSync2(join27(cacheDir, `gitleaks-file-${nowTimestamp()}-${process.pid}-`));
|
|
1337
|
+
reportPath = join27(reportDir, "report.json");
|
|
1338
1338
|
const args = [
|
|
1339
1339
|
"detect",
|
|
1340
1340
|
"--no-git",
|
|
@@ -1562,7 +1562,7 @@ import { existsSync as existsSync5, lstatSync as lstatSync7 } from "node:fs";
|
|
|
1562
1562
|
import { join as join9 } from "node:path";
|
|
1563
1563
|
|
|
1564
1564
|
// src/commands.adopt.recover.ts
|
|
1565
|
-
import {
|
|
1565
|
+
import { rmSync as rmSync4 } from "node:fs";
|
|
1566
1566
|
import { dirname as dirname2, join as join7, resolve as resolve2 } from "node:path";
|
|
1567
1567
|
|
|
1568
1568
|
// src/commands.adopt.scan.ts
|
|
@@ -1872,9 +1872,34 @@ function copyExtrasFilteredPreserving(src, dst, blockSet) {
|
|
|
1872
1872
|
});
|
|
1873
1873
|
}
|
|
1874
1874
|
|
|
1875
|
+
// src/fs-presence.ts
|
|
1876
|
+
import { lstatSync as lstatSync4, statSync } from "node:fs";
|
|
1877
|
+
var ABSENT_CODES = /* @__PURE__ */ new Set(["ENOENT", "ENOTDIR", "ENAMETOOLONG"]);
|
|
1878
|
+
function classifyPresence(p) {
|
|
1879
|
+
let stat;
|
|
1880
|
+
try {
|
|
1881
|
+
stat = lstatSync4(p, { throwIfNoEntry: false });
|
|
1882
|
+
} catch (err) {
|
|
1883
|
+
return ABSENT_CODES.has(String(err.code)) ? "absent" : "unknown";
|
|
1884
|
+
}
|
|
1885
|
+
if (stat === void 0) return "absent";
|
|
1886
|
+
try {
|
|
1887
|
+
statSync(p);
|
|
1888
|
+
} catch (err) {
|
|
1889
|
+
return ABSENT_CODES.has(String(err.code)) ? "dangling" : "unknown";
|
|
1890
|
+
}
|
|
1891
|
+
return "resolves";
|
|
1892
|
+
}
|
|
1893
|
+
function isUnusableTarget(state) {
|
|
1894
|
+
return state === "dangling" || state === "unknown";
|
|
1895
|
+
}
|
|
1896
|
+
function lexists(p) {
|
|
1897
|
+
return classifyPresence(p) !== "absent";
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1875
1900
|
// src/links.ts
|
|
1876
1901
|
init_config();
|
|
1877
|
-
import { existsSync as existsSync4, lstatSync as
|
|
1902
|
+
import { existsSync as existsSync4, lstatSync as lstatSync6, rmSync as rmSync3 } from "node:fs";
|
|
1878
1903
|
import { join as join6 } from "node:path";
|
|
1879
1904
|
|
|
1880
1905
|
// src/hooks-filter.ts
|
|
@@ -2190,14 +2215,14 @@ function emitCopy(onPreview, from, to) {
|
|
|
2190
2215
|
}
|
|
2191
2216
|
}
|
|
2192
2217
|
function isAlreadySymlink(linkPath) {
|
|
2193
|
-
return existsSync4(linkPath) &&
|
|
2218
|
+
return existsSync4(linkPath) && lstatSync6(linkPath).isSymbolicLink();
|
|
2194
2219
|
}
|
|
2195
2220
|
function runAutoMovePasses(linkNames, claude, repo, ts, dryRun, onPreview) {
|
|
2196
2221
|
for (const name of linkNames) {
|
|
2197
2222
|
const linkPath = join6(claude, name);
|
|
2198
2223
|
const target = join6(repo, "shared", name);
|
|
2199
2224
|
if (!existsSync4(linkPath)) continue;
|
|
2200
|
-
if (
|
|
2225
|
+
if (lstatSync6(linkPath).isSymbolicLink()) continue;
|
|
2201
2226
|
if (!existsSync4(target)) continue;
|
|
2202
2227
|
if (dryRun) {
|
|
2203
2228
|
emitAutoMove(onPreview, linkPath, ts, name);
|
|
@@ -2212,7 +2237,7 @@ function copySharedLinkPull(src, dst) {
|
|
|
2212
2237
|
}
|
|
2213
2238
|
function stillOccupied(abs) {
|
|
2214
2239
|
try {
|
|
2215
|
-
return
|
|
2240
|
+
return lstatSync6(abs, { throwIfNoEntry: false }) !== void 0;
|
|
2216
2241
|
} catch {
|
|
2217
2242
|
return true;
|
|
2218
2243
|
}
|
|
@@ -2237,7 +2262,7 @@ function warnWin32ApplyFailed(linkPath, ts, err, snapshotted) {
|
|
|
2237
2262
|
function applyOneSharedLinkWin32(target, linkPath, ts) {
|
|
2238
2263
|
let snapshotted = false;
|
|
2239
2264
|
try {
|
|
2240
|
-
const stat =
|
|
2265
|
+
const stat = lstatSync6(linkPath, { throwIfNoEntry: false });
|
|
2241
2266
|
if (stat !== void 0) {
|
|
2242
2267
|
const snapshot = snapshotBeforeWin32Copy(linkPath, ts);
|
|
2243
2268
|
if (snapshot === "failed") return;
|
|
@@ -2352,14 +2377,6 @@ function regenerateSettings(ts, opts = {}) {
|
|
|
2352
2377
|
|
|
2353
2378
|
// src/commands.adopt.recover.ts
|
|
2354
2379
|
init_utils();
|
|
2355
|
-
function lexists(p) {
|
|
2356
|
-
try {
|
|
2357
|
-
lstatSync6(p);
|
|
2358
|
-
return true;
|
|
2359
|
-
} catch {
|
|
2360
|
-
return false;
|
|
2361
|
-
}
|
|
2362
|
-
}
|
|
2363
2380
|
function isDirectChildOf(root, abs) {
|
|
2364
2381
|
return dirname2(resolve2(abs)) === resolve2(root);
|
|
2365
2382
|
}
|
|
@@ -2604,11 +2621,56 @@ function isValidAdoptName(name) {
|
|
|
2604
2621
|
if (SHARED_LINKS.includes(name)) return true;
|
|
2605
2622
|
return isValidSharedDir(name);
|
|
2606
2623
|
}
|
|
2624
|
+
function refuseUnusableSharedTarget(name) {
|
|
2625
|
+
throw new NomadFatal(
|
|
2626
|
+
`${name}: shared/${name} does not resolve to anything usable, so adopting would publish nothing. Remove shared/${name} from the repo, or restore what it points at, then re-run adopt.`,
|
|
2627
|
+
{ code: EXIT.GENERIC_FAILURE }
|
|
2628
|
+
);
|
|
2629
|
+
}
|
|
2630
|
+
function clobberMessage(name, state) {
|
|
2631
|
+
if (state === "dangling") {
|
|
2632
|
+
return `${name}: shared/${name} is a pointer that does not resolve; would clobber. Remove it, or restore what it points at, first.`;
|
|
2633
|
+
}
|
|
2634
|
+
if (state === "unknown") {
|
|
2635
|
+
return `${name}: shared/${name} could not be read, so adopt cannot tell whether it would clobber existing content. Check its permissions in the sync repo.`;
|
|
2636
|
+
}
|
|
2637
|
+
return `${name}: shared/${name} already exists; would clobber. Remove it first.`;
|
|
2638
|
+
}
|
|
2639
|
+
function refuseClobber(name, sharedTarget) {
|
|
2640
|
+
const state = classifyPresence(sharedTarget);
|
|
2641
|
+
if (state === "absent") return;
|
|
2642
|
+
throw new NomadFatal(clobberMessage(name, state), { code: EXIT.GENERIC_FAILURE });
|
|
2643
|
+
}
|
|
2607
2644
|
function reportWin32AlreadyAdopted(name, sharedTarget) {
|
|
2608
|
-
if (process.platform !== "win32"
|
|
2645
|
+
if (process.platform !== "win32") return false;
|
|
2646
|
+
const state = classifyPresence(sharedTarget);
|
|
2647
|
+
if (state === "absent") return false;
|
|
2648
|
+
if (isUnusableTarget(state)) refuseUnusableSharedTarget(name);
|
|
2609
2649
|
log(`${name}: already adopted (win32 copy-sync); run \`nomad pull\` to refresh the local copy`);
|
|
2610
2650
|
return true;
|
|
2611
2651
|
}
|
|
2652
|
+
function alreadySymlinkMessage(name, sharedTarget) {
|
|
2653
|
+
const state = classifyPresence(sharedTarget);
|
|
2654
|
+
if (state === "dangling") {
|
|
2655
|
+
return {
|
|
2656
|
+
text: `${name}: already a symlink, but shared/${name} does not resolve, so the link is broken. Restore shared/${name} in the repo, or remove ~/.claude/${name} and re-run adopt`,
|
|
2657
|
+
healthy: false
|
|
2658
|
+
};
|
|
2659
|
+
}
|
|
2660
|
+
if (state === "unknown") {
|
|
2661
|
+
return {
|
|
2662
|
+
text: `${name}: already a symlink, but shared/${name} could not be read, so whether the link works could not be determined. Check its permissions in the sync repo`,
|
|
2663
|
+
healthy: false
|
|
2664
|
+
};
|
|
2665
|
+
}
|
|
2666
|
+
if (state === "absent") {
|
|
2667
|
+
return {
|
|
2668
|
+
text: `${name}: already a symlink, but there is no shared/${name} in the repo, so it is not adopted. Remove ~/.claude/${name} and re-run adopt, or run \`nomad pull\` if another host shares it`,
|
|
2669
|
+
healthy: false
|
|
2670
|
+
};
|
|
2671
|
+
}
|
|
2672
|
+
return { text: `${name}: already adopted (already a symlink)`, healthy: true };
|
|
2673
|
+
}
|
|
2612
2674
|
function adoptStopsEarly(name, linkPath, sharedTarget) {
|
|
2613
2675
|
if (!existsSync5(linkPath)) {
|
|
2614
2676
|
if (reportWin32AlreadyAdopted(name, sharedTarget)) return true;
|
|
@@ -2616,14 +2678,13 @@ function adoptStopsEarly(name, linkPath, sharedTarget) {
|
|
|
2616
2678
|
return true;
|
|
2617
2679
|
}
|
|
2618
2680
|
if (lstatSync7(linkPath).isSymbolicLink()) {
|
|
2619
|
-
|
|
2681
|
+
const { text, healthy } = alreadySymlinkMessage(name, sharedTarget);
|
|
2682
|
+
if (healthy) log(text);
|
|
2683
|
+
else warn(text);
|
|
2620
2684
|
return true;
|
|
2621
2685
|
}
|
|
2622
2686
|
if (reportWin32AlreadyAdopted(name, sharedTarget)) return true;
|
|
2623
|
-
|
|
2624
|
-
fail(`${name}: shared/${name} already exists; would clobber. Remove it first.`);
|
|
2625
|
-
process.exit(1);
|
|
2626
|
-
}
|
|
2687
|
+
refuseClobber(name, sharedTarget);
|
|
2627
2688
|
return false;
|
|
2628
2689
|
}
|
|
2629
2690
|
function performAdoptMove(name, linkPath, sharedTarget, repo, backup) {
|
|
@@ -2656,18 +2717,19 @@ function cmdAdopt(name, opts = {}) {
|
|
|
2656
2717
|
);
|
|
2657
2718
|
}
|
|
2658
2719
|
if (!isValidAdoptName(name)) {
|
|
2659
|
-
|
|
2660
|
-
|
|
2720
|
+
throw new NomadFatal(`invalid name: ${JSON.stringify(name)}`, {
|
|
2721
|
+
code: EXIT.GENERIC_FAILURE
|
|
2722
|
+
});
|
|
2661
2723
|
}
|
|
2662
2724
|
const repo = repoHome();
|
|
2663
2725
|
const claude = claudeHome();
|
|
2664
2726
|
const backup = backupBase();
|
|
2665
2727
|
const map = readMapIfPresent(repo);
|
|
2666
2728
|
if (!isConfiguredTarget(name, map)) {
|
|
2667
|
-
|
|
2668
|
-
`${name}: not a configured shared target. Add it to sharedDirs in path-map.json first, then re-run adopt
|
|
2729
|
+
throw new NomadFatal(
|
|
2730
|
+
`${name}: not a configured shared target. Add it to sharedDirs in path-map.json first, then re-run adopt.`,
|
|
2731
|
+
{ code: EXIT.GENERIC_FAILURE }
|
|
2669
2732
|
);
|
|
2670
|
-
process.exit(1);
|
|
2671
2733
|
}
|
|
2672
2734
|
const linkPath = join9(claude, name);
|
|
2673
2735
|
const sharedTarget = join9(repo, "shared", name);
|
|
@@ -2889,7 +2951,7 @@ async function cmdCaptureSettings(opts) {
|
|
|
2889
2951
|
init_config();
|
|
2890
2952
|
init_exit_codes();
|
|
2891
2953
|
init_utils();
|
|
2892
|
-
import { existsSync as existsSync8, lstatSync as lstatSync8, readdirSync as readdirSync4, rmSync as rmSync5, statSync as
|
|
2954
|
+
import { existsSync as existsSync8, lstatSync as lstatSync8, readdirSync as readdirSync4, rmSync as rmSync5, statSync as statSync3 } from "node:fs";
|
|
2893
2955
|
import { join as join12 } from "node:path";
|
|
2894
2956
|
var TS_SHAPE = /^\d{8}-\d{6}(-\d+)?$/;
|
|
2895
2957
|
var DURATION_RE = /^(\d+)([dhm])$/;
|
|
@@ -2908,7 +2970,7 @@ function parseDuration(s) {
|
|
|
2908
2970
|
}
|
|
2909
2971
|
function listBackupDirs(backupBase2) {
|
|
2910
2972
|
if (!existsSync8(backupBase2)) return [];
|
|
2911
|
-
return readdirSync4(backupBase2).filter(isTsDir).map((name) => ({ name, mtimeMs:
|
|
2973
|
+
return readdirSync4(backupBase2).filter(isTsDir).map((name) => ({ name, mtimeMs: statSync3(join12(backupBase2, name)).mtimeMs })).sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
2912
2974
|
}
|
|
2913
2975
|
function holdsNoContent(dir) {
|
|
2914
2976
|
let entries;
|
|
@@ -3189,14 +3251,14 @@ function cmdEject(opts = {}, roots = defaultEjectRoots()) {
|
|
|
3189
3251
|
}
|
|
3190
3252
|
|
|
3191
3253
|
// src/commands.doctor.ts
|
|
3192
|
-
import { existsSync as
|
|
3193
|
-
import { join as
|
|
3254
|
+
import { existsSync as existsSync35 } from "node:fs";
|
|
3255
|
+
import { join as join49 } from "node:path";
|
|
3194
3256
|
|
|
3195
3257
|
// src/commands.doctor.checks.repo.ts
|
|
3196
3258
|
init_color();
|
|
3197
3259
|
init_config();
|
|
3198
|
-
import { existsSync as
|
|
3199
|
-
import { join as
|
|
3260
|
+
import { existsSync as existsSync11, lstatSync as lstatSync10, readdirSync as readdirSync5, statSync as statSync4 } from "node:fs";
|
|
3261
|
+
import { join as join17 } from "node:path";
|
|
3200
3262
|
|
|
3201
3263
|
// src/commands.doctor.format.ts
|
|
3202
3264
|
init_color();
|
|
@@ -3274,7 +3336,6 @@ function readJsonSafe(path, label, section2) {
|
|
|
3274
3336
|
// src/commands.doctor.checks.repo.win32.ts
|
|
3275
3337
|
init_color();
|
|
3276
3338
|
init_config();
|
|
3277
|
-
import { existsSync as existsSync10 } from "node:fs";
|
|
3278
3339
|
import { join as join14, win32 as win32Path } from "node:path";
|
|
3279
3340
|
|
|
3280
3341
|
// src/extras-sync.diff.ts
|
|
@@ -3343,6 +3404,18 @@ function win32CopyUnpublishedRow(name) {
|
|
|
3343
3404
|
fail: false
|
|
3344
3405
|
};
|
|
3345
3406
|
}
|
|
3407
|
+
function win32CopyUnusableRow(name, state) {
|
|
3408
|
+
if (state === "dangling") {
|
|
3409
|
+
return {
|
|
3410
|
+
line: `${yellow(warnGlyph)} ${name}: real local copy, but shared/${name} does not resolve in the repo (remove shared/${name}, or restore what it points at)`,
|
|
3411
|
+
fail: false
|
|
3412
|
+
};
|
|
3413
|
+
}
|
|
3414
|
+
return {
|
|
3415
|
+
line: `${yellow(warnGlyph)} ${name}: real local copy, but shared/${name} could not be read in the repo, so whether it is usable could not be determined (check its permissions in the sync repo)`,
|
|
3416
|
+
fail: false
|
|
3417
|
+
};
|
|
3418
|
+
}
|
|
3346
3419
|
function relativeUnder(root, path) {
|
|
3347
3420
|
const rel = win32Path.relative(root, path);
|
|
3348
3421
|
const segments = rel.split(win32Path.sep);
|
|
@@ -3356,7 +3429,9 @@ function divergingRepoPath(line, name, local, shared) {
|
|
|
3356
3429
|
}
|
|
3357
3430
|
function classifyWin32Copy(name, p) {
|
|
3358
3431
|
const sharedPath = join14(repoHome(), "shared", name);
|
|
3359
|
-
|
|
3432
|
+
const sharedState = classifyPresence(sharedPath);
|
|
3433
|
+
if (sharedState === "absent") return win32CopyUnpublishedRow(name);
|
|
3434
|
+
if (isUnusableTarget(sharedState)) return win32CopyUnusableRow(name, sharedState);
|
|
3360
3435
|
const compared = listDivergingFiles(p, sharedPath);
|
|
3361
3436
|
const diverging = compared.filter(
|
|
3362
3437
|
(line) => deniedSegmentFor(divergingRepoPath(line, name, p, sharedPath)) === null
|
|
@@ -3370,18 +3445,69 @@ function classifyWin32Copy(name, p) {
|
|
|
3370
3445
|
};
|
|
3371
3446
|
}
|
|
3372
3447
|
|
|
3448
|
+
// src/commands.doctor.checks.repo.source.ts
|
|
3449
|
+
init_color();
|
|
3450
|
+
init_config();
|
|
3451
|
+
import { join as join15 } from "node:path";
|
|
3452
|
+
function repoSourceState(name) {
|
|
3453
|
+
return classifyPresence(join15(repoHome(), "shared", name));
|
|
3454
|
+
}
|
|
3455
|
+
function adoptRefusalClause(name, state) {
|
|
3456
|
+
if (state === "dangling") {
|
|
3457
|
+
return `shared/${name} does not resolve, so \`nomad adopt ${name}\` would refuse (remove shared/${name}, or restore what it points at, first)`;
|
|
3458
|
+
}
|
|
3459
|
+
return `shared/${name} could not be read, so \`nomad adopt ${name}\` would refuse (check its permissions in the sync repo first)`;
|
|
3460
|
+
}
|
|
3461
|
+
function adoptWouldClobberRow(name) {
|
|
3462
|
+
return {
|
|
3463
|
+
line: `${red(failGlyph)} ${name}: NOT a symlink (blocks sync), and shared/${name} already holds content, so \`nomad adopt ${name}\` would refuse rather than choose between them (compare the two, then either remove ~/.claude/${name} and run \`nomad pull\`, or remove shared/${name} and run \`nomad adopt ${name}\`)`,
|
|
3464
|
+
fail: true
|
|
3465
|
+
};
|
|
3466
|
+
}
|
|
3467
|
+
function posixNonSymlinkRow(name) {
|
|
3468
|
+
const state = repoSourceState(name);
|
|
3469
|
+
if (isUnusableTarget(state)) {
|
|
3470
|
+
return {
|
|
3471
|
+
line: `${red(failGlyph)} ${name}: NOT a symlink (blocks sync), and ${adoptRefusalClause(name, state)}`,
|
|
3472
|
+
fail: true
|
|
3473
|
+
};
|
|
3474
|
+
}
|
|
3475
|
+
if (state === "resolves") return adoptWouldClobberRow(name);
|
|
3476
|
+
return {
|
|
3477
|
+
line: `${red(failGlyph)} ${name}: NOT a symlink (blocks sync); run \`nomad adopt ${name}\` to fix`,
|
|
3478
|
+
fail: true
|
|
3479
|
+
};
|
|
3480
|
+
}
|
|
3481
|
+
function repoSourceUnusableRow(name, state) {
|
|
3482
|
+
if (state === "dangling") {
|
|
3483
|
+
return {
|
|
3484
|
+
line: `${yellow(warnGlyph)} ${name}: shared/${name} in the repo does not resolve, so there is nothing to restore from (remove shared/${name}, or restore what it points at)`,
|
|
3485
|
+
fail: false
|
|
3486
|
+
};
|
|
3487
|
+
}
|
|
3488
|
+
return {
|
|
3489
|
+
line: `${yellow(warnGlyph)} ${name}: shared/${name} in the repo could not be read, so whether there is anything to restore from could not be determined (check its permissions in the sync repo)`,
|
|
3490
|
+
fail: false
|
|
3491
|
+
};
|
|
3492
|
+
}
|
|
3493
|
+
function repoSourceRow(name, present, absent) {
|
|
3494
|
+
const state = repoSourceState(name);
|
|
3495
|
+
if (isUnusableTarget(state)) return repoSourceUnusableRow(name, state);
|
|
3496
|
+
return state === "resolves" ? present : absent;
|
|
3497
|
+
}
|
|
3498
|
+
|
|
3373
3499
|
// src/init.classify.ts
|
|
3374
3500
|
init_config();
|
|
3375
3501
|
init_utils_json();
|
|
3376
|
-
import { existsSync as
|
|
3377
|
-
import { join as
|
|
3502
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
3503
|
+
import { join as join16 } from "node:path";
|
|
3378
3504
|
function classifyRepoState(repoHome2, host) {
|
|
3379
|
-
const basePath =
|
|
3380
|
-
const mapPath =
|
|
3381
|
-
const hostPath =
|
|
3382
|
-
const hasBase =
|
|
3383
|
-
const hasMap =
|
|
3384
|
-
const hasHost =
|
|
3505
|
+
const basePath = join16(repoHome2, "shared", "settings.base.json");
|
|
3506
|
+
const mapPath = join16(repoHome2, "path-map.json");
|
|
3507
|
+
const hostPath = join16(repoHome2, "hosts", `${host}.json`);
|
|
3508
|
+
const hasBase = existsSync10(basePath);
|
|
3509
|
+
const hasMap = existsSync10(mapPath);
|
|
3510
|
+
const hasHost = existsSync10(hostPath);
|
|
3385
3511
|
let mapEntryCount = 0;
|
|
3386
3512
|
if (hasMap) {
|
|
3387
3513
|
try {
|
|
@@ -3396,11 +3522,11 @@ function classifyRepoState(repoHome2, host) {
|
|
|
3396
3522
|
return "partial";
|
|
3397
3523
|
}
|
|
3398
3524
|
function reasonForPartial(repoHome2, host) {
|
|
3399
|
-
const basePath =
|
|
3400
|
-
const mapPath =
|
|
3401
|
-
const hostPath =
|
|
3402
|
-
if (!
|
|
3403
|
-
if (!
|
|
3525
|
+
const basePath = join16(repoHome2, "shared", "settings.base.json");
|
|
3526
|
+
const mapPath = join16(repoHome2, "path-map.json");
|
|
3527
|
+
const hostPath = join16(repoHome2, "hosts", `${host}.json`);
|
|
3528
|
+
if (!existsSync10(basePath)) return "- shared/settings.base.json missing";
|
|
3529
|
+
if (!existsSync10(mapPath)) return "- path-map.json missing";
|
|
3404
3530
|
let mapEntryCount;
|
|
3405
3531
|
try {
|
|
3406
3532
|
const map = readJson(mapPath);
|
|
@@ -3409,7 +3535,7 @@ function reasonForPartial(repoHome2, host) {
|
|
|
3409
3535
|
mapEntryCount = 0;
|
|
3410
3536
|
}
|
|
3411
3537
|
if (mapEntryCount === 0) return "- path-map.json.projects has no entries";
|
|
3412
|
-
if (!
|
|
3538
|
+
if (!existsSync10(hostPath)) return `- hosts/${host}.json missing`;
|
|
3413
3539
|
return "- partial state (unknown gap)";
|
|
3414
3540
|
}
|
|
3415
3541
|
|
|
@@ -3426,15 +3552,15 @@ function reportHostAndPaths(section2) {
|
|
|
3426
3552
|
if (isOverrideActive()) {
|
|
3427
3553
|
addItem(section2, `${dim(infoGlyph)} NOMAD_REPO: ${blue(repo)}`);
|
|
3428
3554
|
}
|
|
3429
|
-
addItem(section2, `${
|
|
3555
|
+
addItem(section2, `${existsSync11(repo) ? green(okGlyph) : yellow(warnGlyph)} repo: ${blue(repo)}`);
|
|
3430
3556
|
addItem(
|
|
3431
3557
|
section2,
|
|
3432
|
-
`${
|
|
3558
|
+
`${existsSync11(claude) ? green(okGlyph) : yellow(warnGlyph)} claude home: ${blue(claude)}`
|
|
3433
3559
|
);
|
|
3434
3560
|
}
|
|
3435
3561
|
function pathMapHostKeys() {
|
|
3436
|
-
const mapPath =
|
|
3437
|
-
if (!
|
|
3562
|
+
const mapPath = join17(repoHome(), "path-map.json");
|
|
3563
|
+
if (!existsSync11(mapPath)) return /* @__PURE__ */ new Set();
|
|
3438
3564
|
let raw;
|
|
3439
3565
|
try {
|
|
3440
3566
|
raw = readJson(mapPath);
|
|
@@ -3451,7 +3577,7 @@ function pathMapHostKeys() {
|
|
|
3451
3577
|
function hostOverrideLabels() {
|
|
3452
3578
|
let entries;
|
|
3453
3579
|
try {
|
|
3454
|
-
entries = readdirSync5(
|
|
3580
|
+
entries = readdirSync5(join17(repoHome(), "hosts"));
|
|
3455
3581
|
} catch {
|
|
3456
3582
|
return /* @__PURE__ */ new Set();
|
|
3457
3583
|
}
|
|
@@ -3491,9 +3617,6 @@ function reportRepoState(section2) {
|
|
|
3491
3617
|
process.exitCode = 1;
|
|
3492
3618
|
}
|
|
3493
3619
|
}
|
|
3494
|
-
function repoHasSharedSource(name) {
|
|
3495
|
-
return existsSync12(join16(repoHome(), "shared", name));
|
|
3496
|
-
}
|
|
3497
3620
|
function classifySharedLink(name, p) {
|
|
3498
3621
|
let stat;
|
|
3499
3622
|
try {
|
|
@@ -3501,10 +3624,14 @@ function classifySharedLink(name, p) {
|
|
|
3501
3624
|
} catch (err) {
|
|
3502
3625
|
const code = err.code;
|
|
3503
3626
|
if (code === "ENOENT") {
|
|
3504
|
-
return
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3627
|
+
return repoSourceRow(
|
|
3628
|
+
name,
|
|
3629
|
+
{
|
|
3630
|
+
line: `${yellow(warnGlyph)} ${name}: missing (run \`nomad pull\` to restore)`,
|
|
3631
|
+
fail: false
|
|
3632
|
+
},
|
|
3633
|
+
{ line: `${dim(infoGlyph)} ${name}: not synced (nothing in shared/)`, fail: false }
|
|
3634
|
+
);
|
|
3508
3635
|
}
|
|
3509
3636
|
return { line: `${red(failGlyph)} ${name}: could not stat (${String(code)})`, fail: true };
|
|
3510
3637
|
}
|
|
@@ -3512,27 +3639,28 @@ function classifySharedLink(name, p) {
|
|
|
3512
3639
|
if (process.platform === "win32") {
|
|
3513
3640
|
return classifyWin32Copy(name, p);
|
|
3514
3641
|
}
|
|
3515
|
-
return
|
|
3516
|
-
line: `${red(failGlyph)} ${name}: NOT a symlink (blocks sync); run \`nomad adopt ${name}\` to fix`,
|
|
3517
|
-
fail: true
|
|
3518
|
-
};
|
|
3642
|
+
return posixNonSymlinkRow(name);
|
|
3519
3643
|
}
|
|
3520
3644
|
return classifySymlinkTarget(name, p);
|
|
3521
3645
|
}
|
|
3522
3646
|
function classifySymlinkTarget(name, p) {
|
|
3523
3647
|
try {
|
|
3524
|
-
|
|
3648
|
+
statSync4(p);
|
|
3525
3649
|
return { line: `${green(okGlyph)} ${name}: symlink`, fail: false };
|
|
3526
3650
|
} catch (err) {
|
|
3527
3651
|
const code = err.code;
|
|
3528
3652
|
if (code === "ENOENT") {
|
|
3529
|
-
return
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3653
|
+
return repoSourceRow(
|
|
3654
|
+
name,
|
|
3655
|
+
{
|
|
3656
|
+
line: `${yellow(warnGlyph)} ${name}: broken symlink (target missing, run \`nomad pull\`)`,
|
|
3657
|
+
fail: false
|
|
3658
|
+
},
|
|
3659
|
+
{
|
|
3660
|
+
line: `${dim(infoGlyph)} ${name}: stale symlink (no longer in shared/, safe to remove)`,
|
|
3661
|
+
fail: false
|
|
3662
|
+
}
|
|
3663
|
+
);
|
|
3536
3664
|
}
|
|
3537
3665
|
return {
|
|
3538
3666
|
line: `${yellow(warnGlyph)} ${name}: symlink target unreadable (${String(code)})`,
|
|
@@ -3543,7 +3671,7 @@ function classifySymlinkTarget(name, p) {
|
|
|
3543
3671
|
function reportSharedLinks(section2, map) {
|
|
3544
3672
|
const claude = claudeHome();
|
|
3545
3673
|
for (const name of allSharedLinks(map, { quiet: true })) {
|
|
3546
|
-
const p =
|
|
3674
|
+
const p = join17(claude, name);
|
|
3547
3675
|
const { line, fail: fail2, children } = classifySharedLink(name, p);
|
|
3548
3676
|
addItem(section2, line);
|
|
3549
3677
|
if (fail2) process.exitCode = 1;
|
|
@@ -3555,7 +3683,7 @@ function reportSharedLinks(section2, map) {
|
|
|
3555
3683
|
function reportDroppedNamesMigration(section2) {
|
|
3556
3684
|
const claude = claudeHome();
|
|
3557
3685
|
for (const name of GSD_DROPPED_NAMES) {
|
|
3558
|
-
const p =
|
|
3686
|
+
const p = join17(claude, name);
|
|
3559
3687
|
let stat;
|
|
3560
3688
|
try {
|
|
3561
3689
|
stat = lstatSync10(p);
|
|
@@ -3573,11 +3701,11 @@ function reportDroppedNamesMigration(section2) {
|
|
|
3573
3701
|
// src/commands.doctor.checks.settings.ts
|
|
3574
3702
|
init_color();
|
|
3575
3703
|
init_config();
|
|
3576
|
-
import { existsSync as
|
|
3577
|
-
import { join as
|
|
3704
|
+
import { existsSync as existsSync12, readdirSync as readdirSync6 } from "node:fs";
|
|
3705
|
+
import { join as join18 } from "node:path";
|
|
3578
3706
|
function loadBaseSettings(section2) {
|
|
3579
|
-
const basePath =
|
|
3580
|
-
if (!
|
|
3707
|
+
const basePath = join18(repoHome(), "shared", "settings.base.json");
|
|
3708
|
+
if (!existsSync12(basePath)) {
|
|
3581
3709
|
addItem(section2, `${red(failGlyph)} shared/settings.base.json missing at ${blue(basePath)}`);
|
|
3582
3710
|
process.exitCode = 1;
|
|
3583
3711
|
return null;
|
|
@@ -3585,8 +3713,8 @@ function loadBaseSettings(section2) {
|
|
|
3585
3713
|
return readJsonSafe(basePath, basePath, section2);
|
|
3586
3714
|
}
|
|
3587
3715
|
function loadAndReportSettings(section2) {
|
|
3588
|
-
const settingsPath =
|
|
3589
|
-
if (!
|
|
3716
|
+
const settingsPath = join18(claudeHome(), "settings.json");
|
|
3717
|
+
if (!existsSync12(settingsPath)) return null;
|
|
3590
3718
|
const settings = readJsonSafe(settingsPath, settingsPath, section2);
|
|
3591
3719
|
if (settings === null) return null;
|
|
3592
3720
|
const unknownKeys = Object.keys(settings).filter((k) => !KNOWN_SETTINGS_KEYS.has(k));
|
|
@@ -3602,13 +3730,13 @@ function loadAndReportSettings(section2) {
|
|
|
3602
3730
|
}
|
|
3603
3731
|
function reportHostOverrides(section2, base, settings) {
|
|
3604
3732
|
const repo = repoHome();
|
|
3605
|
-
const hostFile =
|
|
3733
|
+
const hostFile = join18(repo, "hosts", `${HOST}.json`);
|
|
3606
3734
|
let drift = [];
|
|
3607
3735
|
if (base !== null && settings !== null) {
|
|
3608
3736
|
const baseKeys = new Set(Object.keys(base));
|
|
3609
3737
|
drift = Object.keys(settings).filter((k) => !baseKeys.has(k));
|
|
3610
3738
|
}
|
|
3611
|
-
if (
|
|
3739
|
+
if (existsSync12(hostFile)) {
|
|
3612
3740
|
if (readJsonSafe(hostFile, hostFile, section2) !== null) {
|
|
3613
3741
|
addItem(section2, `${green(okGlyph)} host overrides: ${blue(hostFile)}`);
|
|
3614
3742
|
}
|
|
@@ -3617,8 +3745,8 @@ function reportHostOverrides(section2, base, settings) {
|
|
|
3617
3745
|
section2,
|
|
3618
3746
|
`${red(failGlyph)} no hosts/${HOST}.json AND settings.json has unbased keys ${JSON.stringify(drift)}`
|
|
3619
3747
|
);
|
|
3620
|
-
const hostsDir =
|
|
3621
|
-
if (
|
|
3748
|
+
const hostsDir = join18(repo, "hosts");
|
|
3749
|
+
if (existsSync12(hostsDir)) {
|
|
3622
3750
|
const cands = readdirSync6(hostsDir).filter((f) => f.endsWith(".json"));
|
|
3623
3751
|
if (cands.length > 0) addItem(section2, `${dim(infoGlyph)} candidates: ${cands.join(", ")}`);
|
|
3624
3752
|
}
|
|
@@ -3635,8 +3763,8 @@ function reportHostOverrides(section2, base, settings) {
|
|
|
3635
3763
|
init_color();
|
|
3636
3764
|
init_config();
|
|
3637
3765
|
init_config_sharedDirs_guard();
|
|
3638
|
-
import { existsSync as
|
|
3639
|
-
import { join as
|
|
3766
|
+
import { existsSync as existsSync13, lstatSync as lstatSync11, readdirSync as readdirSync7, realpathSync as realpathSync2 } from "node:fs";
|
|
3767
|
+
import { join as join19, sep as sep4 } from "node:path";
|
|
3640
3768
|
init_utils_json();
|
|
3641
3769
|
function reportMappedProjects(section2, map) {
|
|
3642
3770
|
const mapped = Object.entries(map.projects).filter(([, hosts]) => hosts[HOST]);
|
|
@@ -3646,8 +3774,8 @@ function reportMappedProjects(section2, map) {
|
|
|
3646
3774
|
}
|
|
3647
3775
|
}
|
|
3648
3776
|
function reportUnmappedProjects(section2, map) {
|
|
3649
|
-
const localProjects =
|
|
3650
|
-
if (!
|
|
3777
|
+
const localProjects = join19(claudeHome(), "projects");
|
|
3778
|
+
if (!existsSync13(localProjects)) return;
|
|
3651
3779
|
let localDirs;
|
|
3652
3780
|
try {
|
|
3653
3781
|
localDirs = readdirSync7(localProjects);
|
|
@@ -3668,7 +3796,7 @@ function reportCurrentHostPathsMissing(section2, map) {
|
|
|
3668
3796
|
for (const [name, hosts] of Object.entries(map.projects)) {
|
|
3669
3797
|
const abspath = hosts[HOST];
|
|
3670
3798
|
if (!abspath || abspath === "TBD") continue;
|
|
3671
|
-
if (!
|
|
3799
|
+
if (!existsSync13(abspath)) {
|
|
3672
3800
|
addItem(
|
|
3673
3801
|
section2,
|
|
3674
3802
|
`${yellow(warnGlyph)} path-map: ${name} local path missing on ${HOST}: ${blue(abspath)}`
|
|
@@ -3678,7 +3806,7 @@ function reportCurrentHostPathsMissing(section2, map) {
|
|
|
3678
3806
|
}
|
|
3679
3807
|
function hasSharedLeftover(entry) {
|
|
3680
3808
|
try {
|
|
3681
|
-
return lstatSync11(
|
|
3809
|
+
return lstatSync11(join19(repoHome(), "shared", entry), { throwIfNoEntry: false }) !== void 0;
|
|
3682
3810
|
} catch {
|
|
3683
3811
|
return false;
|
|
3684
3812
|
}
|
|
@@ -3688,7 +3816,7 @@ var PROBEABLE_REASONS = /* @__PURE__ */ new Set([
|
|
|
3688
3816
|
"secret-shaped"
|
|
3689
3817
|
]);
|
|
3690
3818
|
function classifyLocalLink(entry) {
|
|
3691
|
-
const linkPath =
|
|
3819
|
+
const linkPath = join19(claudeHome(), entry);
|
|
3692
3820
|
let stat;
|
|
3693
3821
|
try {
|
|
3694
3822
|
stat = lstatSync11(linkPath, { throwIfNoEntry: false });
|
|
@@ -3704,7 +3832,7 @@ function classifyLocalLink(entry) {
|
|
|
3704
3832
|
}
|
|
3705
3833
|
let root;
|
|
3706
3834
|
try {
|
|
3707
|
-
root = realpathSync2(
|
|
3835
|
+
root = realpathSync2(join19(repoHome(), "shared"));
|
|
3708
3836
|
} catch {
|
|
3709
3837
|
return "foreign";
|
|
3710
3838
|
}
|
|
@@ -3788,8 +3916,8 @@ function reportPathCollisions(section2, map) {
|
|
|
3788
3916
|
else addItem(section2, `${green(okGlyph)} path-encoding: no collisions`);
|
|
3789
3917
|
}
|
|
3790
3918
|
function reportPathMap(section2) {
|
|
3791
|
-
const mapPath =
|
|
3792
|
-
if (!
|
|
3919
|
+
const mapPath = join19(repoHome(), "path-map.json");
|
|
3920
|
+
if (!existsSync13(mapPath)) {
|
|
3793
3921
|
addItem(section2, `${red(failGlyph)} path-map.json missing at ${blue(mapPath)}`);
|
|
3794
3922
|
process.exitCode = 1;
|
|
3795
3923
|
return;
|
|
@@ -3819,8 +3947,8 @@ function reportNeverSync(section2) {
|
|
|
3819
3947
|
|
|
3820
3948
|
// src/commands.doctor.checks.skills.ts
|
|
3821
3949
|
init_color();
|
|
3822
|
-
import { existsSync as
|
|
3823
|
-
import { join as
|
|
3950
|
+
import { existsSync as existsSync14 } from "node:fs";
|
|
3951
|
+
import { join as join20 } from "node:path";
|
|
3824
3952
|
init_config();
|
|
3825
3953
|
function stripSideIndicator(line) {
|
|
3826
3954
|
if (line.endsWith(" (local only)")) return line.slice(0, -" (local only)".length);
|
|
@@ -3840,13 +3968,13 @@ function isGsdDiffLine(line, localBase, sharedBase) {
|
|
|
3840
3968
|
return relative12.split("/")[0].startsWith(GSD_PREFIX);
|
|
3841
3969
|
}
|
|
3842
3970
|
function reportSkillsDivergence(section2) {
|
|
3843
|
-
const sharedSkills =
|
|
3844
|
-
const localSkills =
|
|
3845
|
-
if (!
|
|
3971
|
+
const sharedSkills = join20(repoHome(), "shared", "skills");
|
|
3972
|
+
const localSkills = join20(claudeHome(), "skills");
|
|
3973
|
+
if (!existsSync14(sharedSkills)) {
|
|
3846
3974
|
addItem(section2, `${dim(infoGlyph)} skills: no shared/skills/ to compare`);
|
|
3847
3975
|
return;
|
|
3848
3976
|
}
|
|
3849
|
-
if (!
|
|
3977
|
+
if (!existsSync14(localSkills)) {
|
|
3850
3978
|
addItem(section2, `${dim(infoGlyph)} skills: no local skills/ to compare`);
|
|
3851
3979
|
return;
|
|
3852
3980
|
}
|
|
@@ -3869,8 +3997,8 @@ function reportSkillsDivergence(section2) {
|
|
|
3869
3997
|
init_color();
|
|
3870
3998
|
init_config();
|
|
3871
3999
|
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
3872
|
-
import { existsSync as
|
|
3873
|
-
import { join as
|
|
4000
|
+
import { existsSync as existsSync17 } from "node:fs";
|
|
4001
|
+
import { join as join24, relative as relative5 } from "node:path";
|
|
3874
4002
|
init_commands_pull_wedge();
|
|
3875
4003
|
init_push_checks();
|
|
3876
4004
|
init_utils();
|
|
@@ -3893,8 +4021,8 @@ function reportGitleaksProbe(section2) {
|
|
|
3893
4021
|
}
|
|
3894
4022
|
function reportGitlinks(section2) {
|
|
3895
4023
|
const repo = repoHome();
|
|
3896
|
-
const sharedDir =
|
|
3897
|
-
if (
|
|
4024
|
+
const sharedDir = join24(repo, "shared");
|
|
4025
|
+
if (existsSync17(sharedDir)) {
|
|
3898
4026
|
const gitlinks = findGitlinks(sharedDir);
|
|
3899
4027
|
for (const p of gitlinks) {
|
|
3900
4028
|
const rel = relative5(repo, p).replaceAll("\\", "/");
|
|
@@ -3992,8 +4120,8 @@ function reportGitIdentity(section2) {
|
|
|
3992
4120
|
|
|
3993
4121
|
// src/commands.doctor.checks.backups.ts
|
|
3994
4122
|
init_color();
|
|
3995
|
-
import { existsSync as
|
|
3996
|
-
import { join as
|
|
4123
|
+
import { existsSync as existsSync18, lstatSync as lstatSync12, readdirSync as readdirSync9 } from "node:fs";
|
|
4124
|
+
import { join as join25 } from "node:path";
|
|
3997
4125
|
init_config();
|
|
3998
4126
|
var TS_SHAPE2 = /^\d{8}-\d{6}(-\d+)?$/;
|
|
3999
4127
|
function safeReaddir(dir) {
|
|
@@ -4009,7 +4137,7 @@ var BYTES_PER_MB = 1024 * 1024;
|
|
|
4009
4137
|
function dirSizeBytes(dir) {
|
|
4010
4138
|
let bytes = 0;
|
|
4011
4139
|
for (const entry of safeReaddir(dir)) {
|
|
4012
|
-
const full =
|
|
4140
|
+
const full = join25(dir, entry);
|
|
4013
4141
|
const st = lstatSync12(full, { throwIfNoEntry: false });
|
|
4014
4142
|
if (!st) continue;
|
|
4015
4143
|
if (st.isSymbolicLink()) continue;
|
|
@@ -4020,11 +4148,11 @@ function dirSizeBytes(dir) {
|
|
|
4020
4148
|
}
|
|
4021
4149
|
function totalSizeMb(backupBase2, dirs) {
|
|
4022
4150
|
let bytes = 0;
|
|
4023
|
-
for (const name of dirs) bytes += dirSizeBytes(
|
|
4151
|
+
for (const name of dirs) bytes += dirSizeBytes(join25(backupBase2, name));
|
|
4024
4152
|
return bytes / BYTES_PER_MB;
|
|
4025
4153
|
}
|
|
4026
4154
|
function reportBackupsCheck(section2, backupBase2 = backupBase()) {
|
|
4027
|
-
if (!
|
|
4155
|
+
if (!existsSync18(backupBase2)) return;
|
|
4028
4156
|
const dirs = safeReaddir(backupBase2).filter((n) => TS_SHAPE2.test(n));
|
|
4029
4157
|
const count = dirs.length;
|
|
4030
4158
|
const sizeMb = totalSizeMb(backupBase2, dirs);
|
|
@@ -4105,8 +4233,8 @@ function reportCheckRemote(section2) {
|
|
|
4105
4233
|
|
|
4106
4234
|
// src/commands.doctor.check-schema.ts
|
|
4107
4235
|
init_color();
|
|
4108
|
-
import { existsSync as
|
|
4109
|
-
import { join as
|
|
4236
|
+
import { existsSync as existsSync19 } from "node:fs";
|
|
4237
|
+
import { join as join26 } from "node:path";
|
|
4110
4238
|
init_config();
|
|
4111
4239
|
|
|
4112
4240
|
// src/http-fetch.ts
|
|
@@ -4143,8 +4271,8 @@ function fetchSchemaKeys() {
|
|
|
4143
4271
|
}
|
|
4144
4272
|
}
|
|
4145
4273
|
function reportCheckSchema(section2) {
|
|
4146
|
-
const settingsPath =
|
|
4147
|
-
if (!
|
|
4274
|
+
const settingsPath = join26(claudeHome(), "settings.json");
|
|
4275
|
+
if (!existsSync19(settingsPath)) {
|
|
4148
4276
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json to check`);
|
|
4149
4277
|
return;
|
|
4150
4278
|
}
|
|
@@ -4174,9 +4302,9 @@ function reportCheckSchema(section2) {
|
|
|
4174
4302
|
init_color();
|
|
4175
4303
|
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
4176
4304
|
import { execFileSync as execFileSync11 } from "node:child_process";
|
|
4177
|
-
import { existsSync as
|
|
4305
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync8, readdirSync as readdirSync12, rmSync as rmSync12 } from "node:fs";
|
|
4178
4306
|
import { homedir as homedir6 } from "node:os";
|
|
4179
|
-
import { join as
|
|
4307
|
+
import { join as join33 } from "node:path";
|
|
4180
4308
|
|
|
4181
4309
|
// src/commands.doctor.check-shared.memory.ts
|
|
4182
4310
|
init_color();
|
|
@@ -4184,7 +4312,7 @@ import { execFileSync as execFileSync9 } from "node:child_process";
|
|
|
4184
4312
|
import { randomBytes } from "node:crypto";
|
|
4185
4313
|
import { mkdirSync as mkdirSync4, rmSync as rmSync9, writeFileSync as writeFileSync4 } from "node:fs";
|
|
4186
4314
|
import { homedir as homedir4 } from "node:os";
|
|
4187
|
-
import { dirname as dirname4, join as
|
|
4315
|
+
import { dirname as dirname4, join as join28 } from "node:path";
|
|
4188
4316
|
init_config();
|
|
4189
4317
|
init_push_gitleaks();
|
|
4190
4318
|
init_utils_fs();
|
|
@@ -4217,7 +4345,7 @@ function buildMemoryScanTree(tmpRoot) {
|
|
|
4217
4345
|
incomplete = true;
|
|
4218
4346
|
continue;
|
|
4219
4347
|
}
|
|
4220
|
-
const dest =
|
|
4348
|
+
const dest = join28(tmpRoot, rel);
|
|
4221
4349
|
mkdirSync4(dirname4(dest), { recursive: true });
|
|
4222
4350
|
writeFileSync4(dest, blob);
|
|
4223
4351
|
logicals.add(m[1]);
|
|
@@ -4236,10 +4364,10 @@ function reportMemoryFindings(section2, findings) {
|
|
|
4236
4364
|
function reportCommittedMemory(section2) {
|
|
4237
4365
|
let tmpRoot;
|
|
4238
4366
|
try {
|
|
4239
|
-
const cacheDir =
|
|
4367
|
+
const cacheDir = join28(homedir4(), ".cache", "claude-nomad");
|
|
4240
4368
|
mkdirSync4(cacheDir, { recursive: true });
|
|
4241
4369
|
const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes(4).toString("hex")}`;
|
|
4242
|
-
tmpRoot =
|
|
4370
|
+
tmpRoot = join28(cacheDir, `check-shared-memory-tree-${stamp}`);
|
|
4243
4371
|
mkdirSync4(tmpRoot, { recursive: true, mode: 448 });
|
|
4244
4372
|
const { staged, incomplete } = buildMemoryScanTree(tmpRoot);
|
|
4245
4373
|
if (incomplete) {
|
|
@@ -4286,12 +4414,12 @@ function reportCommittedMemory(section2) {
|
|
|
4286
4414
|
|
|
4287
4415
|
// src/commands.doctor.check-shared.scan.ts
|
|
4288
4416
|
init_color();
|
|
4289
|
-
import { join as
|
|
4417
|
+
import { join as join29 } from "node:path";
|
|
4290
4418
|
init_config();
|
|
4291
4419
|
init_push_gitleaks();
|
|
4292
4420
|
function scrubPath(logical, sid, logicalToEncoded) {
|
|
4293
4421
|
const encoded = logicalToEncoded.get(logical) ?? logical;
|
|
4294
|
-
return
|
|
4422
|
+
return join29(claudeHome(), "projects", encoded, `${sid}.jsonl`);
|
|
4295
4423
|
}
|
|
4296
4424
|
function reportSessionFindings(section2, bySession) {
|
|
4297
4425
|
for (const [sid, counts] of bySession) {
|
|
@@ -4380,7 +4508,7 @@ import { execFileSync as execFileSync10 } from "node:child_process";
|
|
|
4380
4508
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
4381
4509
|
import { mkdirSync as mkdirSync5, rmSync as rmSync10, writeFileSync as writeFileSync5 } from "node:fs";
|
|
4382
4510
|
import { homedir as homedir5 } from "node:os";
|
|
4383
|
-
import { dirname as dirname5, join as
|
|
4511
|
+
import { dirname as dirname5, join as join30 } from "node:path";
|
|
4384
4512
|
init_config();
|
|
4385
4513
|
init_push_gitleaks();
|
|
4386
4514
|
init_utils_fs();
|
|
@@ -4413,7 +4541,7 @@ function buildSkillScanTree(tmpRoot) {
|
|
|
4413
4541
|
incomplete = true;
|
|
4414
4542
|
continue;
|
|
4415
4543
|
}
|
|
4416
|
-
const dest =
|
|
4544
|
+
const dest = join30(tmpRoot, rel);
|
|
4417
4545
|
mkdirSync5(dirname5(dest), { recursive: true });
|
|
4418
4546
|
writeFileSync5(dest, blob);
|
|
4419
4547
|
names.add(m[1]);
|
|
@@ -4436,10 +4564,10 @@ function reportSkillFindings(section2, findings) {
|
|
|
4436
4564
|
function reportCommittedSkills(section2) {
|
|
4437
4565
|
let tmpRoot;
|
|
4438
4566
|
try {
|
|
4439
|
-
const cacheDir =
|
|
4567
|
+
const cacheDir = join30(homedir5(), ".cache", "claude-nomad");
|
|
4440
4568
|
mkdirSync5(cacheDir, { recursive: true });
|
|
4441
4569
|
const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes2(4).toString("hex")}`;
|
|
4442
|
-
tmpRoot =
|
|
4570
|
+
tmpRoot = join30(cacheDir, `check-shared-skills-tree-${stamp}`);
|
|
4443
4571
|
mkdirSync5(tmpRoot, { recursive: true, mode: 448 });
|
|
4444
4572
|
const { staged, incomplete } = buildSkillScanTree(tmpRoot);
|
|
4445
4573
|
if (incomplete) {
|
|
@@ -4489,8 +4617,8 @@ init_config();
|
|
|
4489
4617
|
|
|
4490
4618
|
// src/remap.ts
|
|
4491
4619
|
init_config_sharedDirs_guard();
|
|
4492
|
-
import { cpSync as cpSync5, existsSync as
|
|
4493
|
-
import { dirname as dirname7, join as
|
|
4620
|
+
import { cpSync as cpSync5, existsSync as existsSync21, lstatSync as lstatSync13, mkdirSync as mkdirSync7, readdirSync as readdirSync11, rmSync as rmSync11, statSync as statSync6 } from "node:fs";
|
|
4621
|
+
import { dirname as dirname7, join as join32, relative as relative6, sep as sep5 } from "node:path";
|
|
4494
4622
|
init_config();
|
|
4495
4623
|
|
|
4496
4624
|
// src/push-manifest.ts
|
|
@@ -4498,8 +4626,8 @@ init_config();
|
|
|
4498
4626
|
init_push_gitleaks_config();
|
|
4499
4627
|
init_utils_fs();
|
|
4500
4628
|
import { createHash } from "node:crypto";
|
|
4501
|
-
import { existsSync as
|
|
4502
|
-
import { dirname as dirname6, join as
|
|
4629
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync6, readdirSync as readdirSync10, readFileSync as readFileSync7, statSync as statSync5 } from "node:fs";
|
|
4630
|
+
import { dirname as dirname6, join as join31 } from "node:path";
|
|
4503
4631
|
function isChanged(prev, cur, hash) {
|
|
4504
4632
|
if (prev === void 0) return true;
|
|
4505
4633
|
if (prev.size !== cur.size) return true;
|
|
@@ -4540,8 +4668,8 @@ function hashFile(absPath) {
|
|
|
4540
4668
|
}
|
|
4541
4669
|
function enumerateDir(dir, results) {
|
|
4542
4670
|
for (const entry of readdirSync10(dir)) {
|
|
4543
|
-
const fullPath =
|
|
4544
|
-
const st =
|
|
4671
|
+
const fullPath = join31(dir, entry);
|
|
4672
|
+
const st = statSync5(fullPath);
|
|
4545
4673
|
if (st.isDirectory()) {
|
|
4546
4674
|
enumerateDir(fullPath, results);
|
|
4547
4675
|
} else {
|
|
@@ -4552,8 +4680,8 @@ function enumerateDir(dir, results) {
|
|
|
4552
4680
|
function enumerateSourceFiles(projectDir) {
|
|
4553
4681
|
const results = [];
|
|
4554
4682
|
for (const entry of readdirSync10(projectDir)) {
|
|
4555
|
-
const fullPath =
|
|
4556
|
-
const st =
|
|
4683
|
+
const fullPath = join31(projectDir, entry);
|
|
4684
|
+
const st = statSync5(fullPath);
|
|
4557
4685
|
if (st.isDirectory()) {
|
|
4558
4686
|
enumerateDir(fullPath, results);
|
|
4559
4687
|
} else if (fullPath.endsWith(".jsonl")) {
|
|
@@ -4564,15 +4692,15 @@ function enumerateSourceFiles(projectDir) {
|
|
|
4564
4692
|
}
|
|
4565
4693
|
function fileIdentity(p) {
|
|
4566
4694
|
if (p === null) return "none::absent";
|
|
4567
|
-
if (!
|
|
4695
|
+
if (!existsSync20(p)) return `${p}::absent`;
|
|
4568
4696
|
return `${p}::${createHash("sha256").update(readFileSync7(p)).digest("hex")}`;
|
|
4569
4697
|
}
|
|
4570
4698
|
function computeConfigHash() {
|
|
4571
4699
|
const repo = repoHome();
|
|
4572
4700
|
const parts = [
|
|
4573
4701
|
fileIdentity(resolveTomlPath(repo)),
|
|
4574
|
-
fileIdentity(
|
|
4575
|
-
fileIdentity(
|
|
4702
|
+
fileIdentity(join31(repo, ".gitleaks.overlay.toml")),
|
|
4703
|
+
fileIdentity(join31(repo, ".gitleaksignore"))
|
|
4576
4704
|
];
|
|
4577
4705
|
return createHash("sha256").update(parts.join("\n")).digest("hex");
|
|
4578
4706
|
}
|
|
@@ -4634,12 +4762,12 @@ function applySelective(sel, localDir, repoDst) {
|
|
|
4634
4762
|
const prefix = `${localDir}${sep5}`;
|
|
4635
4763
|
for (const src of sel.changed) {
|
|
4636
4764
|
if (!src.startsWith(prefix)) continue;
|
|
4637
|
-
copyFileAtomic(src,
|
|
4765
|
+
copyFileAtomic(src, join32(repoDst, relative6(localDir, src)));
|
|
4638
4766
|
}
|
|
4639
4767
|
for (const src of sel.deleted) {
|
|
4640
4768
|
if (!src.startsWith(prefix)) continue;
|
|
4641
|
-
const dst =
|
|
4642
|
-
if (
|
|
4769
|
+
const dst = join32(repoDst, relative6(localDir, src));
|
|
4770
|
+
if (existsSync21(dst)) rmSync11(dst);
|
|
4643
4771
|
}
|
|
4644
4772
|
}
|
|
4645
4773
|
function copyDirJsonlOnly(src, dst) {
|
|
@@ -4650,7 +4778,7 @@ function copyDirJsonlOnly(src, dst) {
|
|
|
4650
4778
|
const rel = relative6(src, srcPath);
|
|
4651
4779
|
if (rel === "") return true;
|
|
4652
4780
|
if (rel.split(sep5).length > 1) return true;
|
|
4653
|
-
if (
|
|
4781
|
+
if (statSync6(srcPath).isDirectory()) return true;
|
|
4654
4782
|
if (srcPath.endsWith(".jsonl")) return true;
|
|
4655
4783
|
item(`skip ${rel}: extension not in allowlist`);
|
|
4656
4784
|
return false;
|
|
@@ -4668,15 +4796,15 @@ function remapPull(ts, opts = {}) {
|
|
|
4668
4796
|
const wouldPull = [];
|
|
4669
4797
|
const repo = repoHome();
|
|
4670
4798
|
const claude = claudeHome();
|
|
4671
|
-
const mapPath =
|
|
4672
|
-
const repoProjects =
|
|
4673
|
-
if (!
|
|
4799
|
+
const mapPath = join32(repo, "path-map.json");
|
|
4800
|
+
const repoProjects = join32(repo, "shared", "projects");
|
|
4801
|
+
if (!existsSync21(mapPath) || !existsSync21(repoProjects)) {
|
|
4674
4802
|
const text = "no path-map or repo projects dir; skipping session remap";
|
|
4675
4803
|
emitPreview(opts.onPreview, { kind: "note", text }, text);
|
|
4676
4804
|
return { unmapped: 0, pulled, wouldPull };
|
|
4677
4805
|
}
|
|
4678
4806
|
const map = readPathMap(mapPath);
|
|
4679
|
-
const localProjects =
|
|
4807
|
+
const localProjects = join32(claude, "projects");
|
|
4680
4808
|
if (!dryRun) mkdirSync7(localProjects, { recursive: true });
|
|
4681
4809
|
for (const [logical, hosts] of Object.entries(map.projects)) {
|
|
4682
4810
|
assertSafeLogical(logical);
|
|
@@ -4686,9 +4814,9 @@ function remapPull(ts, opts = {}) {
|
|
|
4686
4814
|
continue;
|
|
4687
4815
|
}
|
|
4688
4816
|
assertSafeLocalRoot(localPath, logical);
|
|
4689
|
-
const src =
|
|
4690
|
-
if (!
|
|
4691
|
-
const dst =
|
|
4817
|
+
const src = join32(repoProjects, logical);
|
|
4818
|
+
if (!existsSync21(src)) continue;
|
|
4819
|
+
const dst = join32(localProjects, encodePath(localPath));
|
|
4692
4820
|
if (dryRun) {
|
|
4693
4821
|
wouldPull.push(logical);
|
|
4694
4822
|
emitPreview(
|
|
@@ -4707,8 +4835,8 @@ function remapPull(ts, opts = {}) {
|
|
|
4707
4835
|
function countLocalOnly(src, dst) {
|
|
4708
4836
|
let count = 0;
|
|
4709
4837
|
for (const name of readdirSync11(dst)) {
|
|
4710
|
-
const dstPath =
|
|
4711
|
-
const srcPath =
|
|
4838
|
+
const dstPath = join32(dst, name);
|
|
4839
|
+
const srcPath = join32(src, name);
|
|
4712
4840
|
if (lstatSync13(dstPath).isDirectory()) {
|
|
4713
4841
|
count += countLocalOnly(srcPath, dstPath);
|
|
4714
4842
|
} else if (lstatSync13(srcPath, { throwIfNoEntry: false }) === void 0) {
|
|
@@ -4720,20 +4848,20 @@ function countLocalOnly(src, dst) {
|
|
|
4720
4848
|
function scanLocalOnly() {
|
|
4721
4849
|
const repo = repoHome();
|
|
4722
4850
|
const claude = claudeHome();
|
|
4723
|
-
const mapPath =
|
|
4724
|
-
const repoProjects =
|
|
4725
|
-
if (!
|
|
4851
|
+
const mapPath = join32(repo, "path-map.json");
|
|
4852
|
+
const repoProjects = join32(repo, "shared", "projects");
|
|
4853
|
+
if (!existsSync21(mapPath) || !existsSync21(repoProjects)) return 0;
|
|
4726
4854
|
const map = readPathMap(mapPath);
|
|
4727
|
-
const localProjects =
|
|
4855
|
+
const localProjects = join32(claude, "projects");
|
|
4728
4856
|
let count = 0;
|
|
4729
4857
|
for (const [logical, hosts] of Object.entries(map.projects)) {
|
|
4730
4858
|
assertSafeLogical(logical);
|
|
4731
4859
|
const localPath = hosts[HOST];
|
|
4732
4860
|
if (!localPath || localPath === "TBD") continue;
|
|
4733
4861
|
assertSafeLocalRoot(localPath, logical);
|
|
4734
|
-
const dst =
|
|
4735
|
-
if (!
|
|
4736
|
-
count += countLocalOnly(
|
|
4862
|
+
const dst = join32(localProjects, encodePath(localPath));
|
|
4863
|
+
if (!existsSync21(dst)) continue;
|
|
4864
|
+
count += countLocalOnly(join32(repoProjects, logical), dst);
|
|
4737
4865
|
}
|
|
4738
4866
|
return count;
|
|
4739
4867
|
}
|
|
@@ -4768,16 +4896,16 @@ function remapPush(ts, opts = {}) {
|
|
|
4768
4896
|
const wouldPush = [];
|
|
4769
4897
|
const repo = repoHome();
|
|
4770
4898
|
const claude = claudeHome();
|
|
4771
|
-
const mapPath =
|
|
4772
|
-
if (!
|
|
4899
|
+
const mapPath = join32(repo, "path-map.json");
|
|
4900
|
+
if (!existsSync21(mapPath)) {
|
|
4773
4901
|
log("no path-map.json; skipping session export");
|
|
4774
4902
|
return { unmapped: 0, collisions: 0, pushed, wouldPush };
|
|
4775
4903
|
}
|
|
4776
4904
|
const map = readPathMap(mapPath);
|
|
4777
|
-
const localProjects =
|
|
4778
|
-
const repoProjects =
|
|
4905
|
+
const localProjects = join32(claude, "projects");
|
|
4906
|
+
const repoProjects = join32(repo, "shared", "projects");
|
|
4779
4907
|
const reverse = buildReverseMap(map);
|
|
4780
|
-
if (!
|
|
4908
|
+
if (!existsSync21(localProjects)) return { unmapped, collisions: 0, pushed, wouldPush };
|
|
4781
4909
|
if (!dryRun) mkdirSync7(repoProjects, { recursive: true });
|
|
4782
4910
|
for (const dir of readdirSync11(localProjects)) {
|
|
4783
4911
|
if (dir.endsWith(TMP_SUFFIX)) continue;
|
|
@@ -4786,8 +4914,8 @@ function remapPush(ts, opts = {}) {
|
|
|
4786
4914
|
unmapped++;
|
|
4787
4915
|
continue;
|
|
4788
4916
|
}
|
|
4789
|
-
const localDir =
|
|
4790
|
-
const repoDst =
|
|
4917
|
+
const localDir = join32(localProjects, dir);
|
|
4918
|
+
const repoDst = join32(repoProjects, logical);
|
|
4791
4919
|
if (skipForSelection(opts.selection, localDir)) continue;
|
|
4792
4920
|
if (dryRun) {
|
|
4793
4921
|
wouldPush.push(logical);
|
|
@@ -4811,8 +4939,8 @@ function buildScanTree(tmpRoot) {
|
|
|
4811
4939
|
const logicalToEncoded = /* @__PURE__ */ new Map();
|
|
4812
4940
|
let staged = 0;
|
|
4813
4941
|
const repo = repoHome();
|
|
4814
|
-
const mapPath =
|
|
4815
|
-
if (!
|
|
4942
|
+
const mapPath = join33(repo, "path-map.json");
|
|
4943
|
+
if (!existsSync22(mapPath)) return { logicalToEncoded, staged, malformed: false };
|
|
4816
4944
|
let map;
|
|
4817
4945
|
try {
|
|
4818
4946
|
map = readJson(mapPath);
|
|
@@ -4829,12 +4957,12 @@ function buildScanTree(tmpRoot) {
|
|
|
4829
4957
|
if (!p || p === "TBD") continue;
|
|
4830
4958
|
reverse.set(encodePath(p), logical);
|
|
4831
4959
|
}
|
|
4832
|
-
const localProjects =
|
|
4833
|
-
if (!
|
|
4960
|
+
const localProjects = join33(claudeHome(), "projects");
|
|
4961
|
+
if (!existsSync22(localProjects)) return { logicalToEncoded, staged, malformed: false };
|
|
4834
4962
|
for (const dir of readdirSync12(localProjects)) {
|
|
4835
4963
|
const logical = reverse.get(dir);
|
|
4836
4964
|
if (!logical) continue;
|
|
4837
|
-
copyDirJsonlOnly(
|
|
4965
|
+
copyDirJsonlOnly(join33(localProjects, dir), join33(tmpRoot, "shared", "projects", logical));
|
|
4838
4966
|
logicalToEncoded.set(logical, dir);
|
|
4839
4967
|
staged++;
|
|
4840
4968
|
}
|
|
@@ -4864,11 +4992,11 @@ function ensureGitleaksReady(section2, gitleaksReady) {
|
|
|
4864
4992
|
return true;
|
|
4865
4993
|
}
|
|
4866
4994
|
function runLocalPreviewScan(section2) {
|
|
4867
|
-
const cacheDir =
|
|
4995
|
+
const cacheDir = join33(homedir6(), ".cache", "claude-nomad");
|
|
4868
4996
|
mkdirSync8(cacheDir, { recursive: true });
|
|
4869
4997
|
const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes3(4).toString("hex")}`;
|
|
4870
|
-
const reportPath =
|
|
4871
|
-
const tmpRoot =
|
|
4998
|
+
const reportPath = join33(cacheDir, `check-shared-${stamp}.json`);
|
|
4999
|
+
const tmpRoot = join33(cacheDir, `check-shared-tree-${stamp}`);
|
|
4872
5000
|
try {
|
|
4873
5001
|
const { logicalToEncoded, staged, malformed } = buildScanTree(tmpRoot);
|
|
4874
5002
|
if (malformed) {
|
|
@@ -4901,8 +5029,8 @@ function reportCheckShared(section2, gitleaksReady) {
|
|
|
4901
5029
|
|
|
4902
5030
|
// src/commands.doctor.checks.hooks.scope.ts
|
|
4903
5031
|
init_color();
|
|
4904
|
-
import { existsSync as
|
|
4905
|
-
import { dirname as dirname8, extname, join as
|
|
5032
|
+
import { existsSync as existsSync23, readFileSync as readFileSync8, readdirSync as readdirSync13, realpathSync as realpathSync3 } from "node:fs";
|
|
5033
|
+
import { dirname as dirname8, extname, join as join34 } from "node:path";
|
|
4906
5034
|
init_config();
|
|
4907
5035
|
function typeFromPackageJson(pkgPath) {
|
|
4908
5036
|
try {
|
|
@@ -4924,8 +5052,8 @@ function effectiveType(hookPath) {
|
|
|
4924
5052
|
}
|
|
4925
5053
|
let dir = dirname8(real);
|
|
4926
5054
|
for (; ; ) {
|
|
4927
|
-
const pkg =
|
|
4928
|
-
if (
|
|
5055
|
+
const pkg = join34(dir, "package.json");
|
|
5056
|
+
if (existsSync23(pkg)) return typeFromPackageJson(pkg);
|
|
4929
5057
|
const parent = dirname8(dir);
|
|
4930
5058
|
if (parent === dir) return "cjs";
|
|
4931
5059
|
dir = parent;
|
|
@@ -4967,15 +5095,15 @@ function safeRead(path) {
|
|
|
4967
5095
|
}
|
|
4968
5096
|
}
|
|
4969
5097
|
function reportHookScopeCheck(section2) {
|
|
4970
|
-
const hooksDir =
|
|
4971
|
-
if (!
|
|
5098
|
+
const hooksDir = join34(claudeHome(), "hooks");
|
|
5099
|
+
if (!existsSync23(hooksDir)) {
|
|
4972
5100
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/hooks; skipping module-scope check`);
|
|
4973
5101
|
return;
|
|
4974
5102
|
}
|
|
4975
5103
|
let anyWarn = false;
|
|
4976
5104
|
for (const name of safeReaddir2(hooksDir)) {
|
|
4977
5105
|
if (extname(name) !== ".js") continue;
|
|
4978
|
-
const abs =
|
|
5106
|
+
const abs = join34(hooksDir, name);
|
|
4979
5107
|
const eff = effectiveType(abs);
|
|
4980
5108
|
if (eff === null) continue;
|
|
4981
5109
|
const src = safeRead(abs);
|
|
@@ -4995,8 +5123,8 @@ function reportHookScopeCheck(section2) {
|
|
|
4995
5123
|
|
|
4996
5124
|
// src/commands.doctor.checks.hooks.ts
|
|
4997
5125
|
init_color();
|
|
4998
|
-
import { existsSync as
|
|
4999
|
-
import { join as
|
|
5126
|
+
import { existsSync as existsSync24 } from "node:fs";
|
|
5127
|
+
import { join as join35 } from "node:path";
|
|
5000
5128
|
init_config();
|
|
5001
5129
|
function expandHome(token) {
|
|
5002
5130
|
const h2 = home();
|
|
@@ -5039,7 +5167,7 @@ function checkEventGroups(section2, event, groups) {
|
|
|
5039
5167
|
for (const group of groups) {
|
|
5040
5168
|
for (const cmd of commandsFromGroup(group)) {
|
|
5041
5169
|
for (const resolved of claudePathsIn(cmd)) {
|
|
5042
|
-
if (
|
|
5170
|
+
if (existsSync24(resolved)) continue;
|
|
5043
5171
|
addItem(
|
|
5044
5172
|
section2,
|
|
5045
5173
|
`${red(failGlyph)} hooks/${event}: command target missing: ${resolved} (run nomad pull)`
|
|
@@ -5052,8 +5180,8 @@ function checkEventGroups(section2, event, groups) {
|
|
|
5052
5180
|
return anyFail;
|
|
5053
5181
|
}
|
|
5054
5182
|
function reportHooksTargetCheck(section2) {
|
|
5055
|
-
const settingsPath =
|
|
5056
|
-
if (!
|
|
5183
|
+
const settingsPath = join35(claudeHome(), "settings.json");
|
|
5184
|
+
if (!existsSync24(settingsPath)) {
|
|
5057
5185
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json; skipping hook target check`);
|
|
5058
5186
|
return;
|
|
5059
5187
|
}
|
|
@@ -5076,12 +5204,12 @@ function reportHooksTargetCheck(section2) {
|
|
|
5076
5204
|
|
|
5077
5205
|
// src/commands.doctor.checks.hooks.preserve-symlinks.ts
|
|
5078
5206
|
init_color();
|
|
5079
|
-
import { existsSync as
|
|
5080
|
-
import { join as
|
|
5207
|
+
import { existsSync as existsSync26, readFileSync as readFileSync9 } from "node:fs";
|
|
5208
|
+
import { join as join37 } from "node:path";
|
|
5081
5209
|
|
|
5082
5210
|
// src/commands.doctor.checks.hooks.preserve-symlinks.probe.ts
|
|
5083
|
-
import { closeSync as closeSync3, existsSync as
|
|
5084
|
-
import { dirname as dirname9, join as
|
|
5211
|
+
import { closeSync as closeSync3, existsSync as existsSync25, openSync as openSync3, readSync, realpathSync as realpathSync4 } from "node:fs";
|
|
5212
|
+
import { dirname as dirname9, join as join36, resolve as resolve3 } from "node:path";
|
|
5085
5213
|
function suppressedRanges(src) {
|
|
5086
5214
|
const ranges = [];
|
|
5087
5215
|
const re = /\/\*[\s\S]*?\*\/|\/\/[^\n]*|'[^']*'|"[^"]*"|`[^`]*`/g;
|
|
@@ -5113,12 +5241,12 @@ function topRelativeSpecifiers(src) {
|
|
|
5113
5241
|
}
|
|
5114
5242
|
function specifierIsMissing(specifier, baseDir) {
|
|
5115
5243
|
const base = resolve3(baseDir, specifier);
|
|
5116
|
-
if (
|
|
5244
|
+
if (existsSync25(base)) return false;
|
|
5117
5245
|
for (const ext of [".js", ".cjs", ".mjs"]) {
|
|
5118
|
-
if (
|
|
5246
|
+
if (existsSync25(base + ext)) return false;
|
|
5119
5247
|
}
|
|
5120
5248
|
for (const idx of ["index.js", "index.cjs", "index.mjs"]) {
|
|
5121
|
-
if (
|
|
5249
|
+
if (existsSync25(join36(base, idx))) return false;
|
|
5122
5250
|
}
|
|
5123
5251
|
return true;
|
|
5124
5252
|
}
|
|
@@ -5173,8 +5301,8 @@ function commandTokens(command) {
|
|
|
5173
5301
|
return tokens;
|
|
5174
5302
|
}
|
|
5175
5303
|
function readPathMapSafe() {
|
|
5176
|
-
const mapPath =
|
|
5177
|
-
if (!
|
|
5304
|
+
const mapPath = join37(repoHome(), "path-map.json");
|
|
5305
|
+
if (!existsSync26(mapPath)) return { projects: {} };
|
|
5178
5306
|
try {
|
|
5179
5307
|
return JSON.parse(readFileSync9(mapPath, "utf8"));
|
|
5180
5308
|
} catch {
|
|
@@ -5248,8 +5376,8 @@ function checkEventForPreserveSymlinks(section2, event, groups, sharedLinkNames)
|
|
|
5248
5376
|
return anyWarn;
|
|
5249
5377
|
}
|
|
5250
5378
|
function reportPreserveSymlinksCheck(section2) {
|
|
5251
|
-
const settingsPath =
|
|
5252
|
-
if (!
|
|
5379
|
+
const settingsPath = join37(claudeHome(), "settings.json");
|
|
5380
|
+
if (!existsSync26(settingsPath)) {
|
|
5253
5381
|
addItem(
|
|
5254
5382
|
section2,
|
|
5255
5383
|
`${dim(infoGlyph)} no ~/.claude/settings.json; skipping preserve-symlinks-main check`
|
|
@@ -5282,8 +5410,8 @@ function reportPreserveSymlinksCheck(section2) {
|
|
|
5282
5410
|
|
|
5283
5411
|
// src/commands.doctor.checks.settings-drift.ts
|
|
5284
5412
|
init_color();
|
|
5285
|
-
import { existsSync as
|
|
5286
|
-
import { join as
|
|
5413
|
+
import { existsSync as existsSync27, readFileSync as readFileSync10 } from "node:fs";
|
|
5414
|
+
import { join as join38 } from "node:path";
|
|
5287
5415
|
init_config();
|
|
5288
5416
|
init_utils_json();
|
|
5289
5417
|
function diffMergedSettings(merged, settings) {
|
|
@@ -5301,7 +5429,7 @@ function tryReadJson(filePath) {
|
|
|
5301
5429
|
}
|
|
5302
5430
|
}
|
|
5303
5431
|
function reportHooksBaseSelfCleanNote(section2) {
|
|
5304
|
-
const basePath =
|
|
5432
|
+
const basePath = join38(repoHome(), "shared", "settings.base.json");
|
|
5305
5433
|
const base = tryReadJson(basePath);
|
|
5306
5434
|
if (base === null) return;
|
|
5307
5435
|
if (!baseHasGsdHookEntries(base)) return;
|
|
@@ -5314,14 +5442,14 @@ function reportSettingsDriftCheck(section2) {
|
|
|
5314
5442
|
const claude = claudeHome();
|
|
5315
5443
|
const repo = repoHome();
|
|
5316
5444
|
const host = HOST;
|
|
5317
|
-
const settingsPath =
|
|
5318
|
-
const basePath =
|
|
5319
|
-
const hostPath =
|
|
5320
|
-
if (!
|
|
5445
|
+
const settingsPath = join38(claude, "settings.json");
|
|
5446
|
+
const basePath = join38(repo, "shared", "settings.base.json");
|
|
5447
|
+
const hostPath = join38(repo, "hosts", `${host}.json`);
|
|
5448
|
+
if (!existsSync27(settingsPath)) {
|
|
5321
5449
|
addItem(section2, `${dim(infoGlyph)} no ~/.claude/settings.json; skipping merge-drift check`);
|
|
5322
5450
|
return;
|
|
5323
5451
|
}
|
|
5324
|
-
if (!
|
|
5452
|
+
if (!existsSync27(basePath)) {
|
|
5325
5453
|
addItem(
|
|
5326
5454
|
section2,
|
|
5327
5455
|
`${dim(infoGlyph)} shared/settings.base.json missing; skipping merge-drift check`
|
|
@@ -5340,7 +5468,7 @@ function reportSettingsDriftCheck(section2) {
|
|
|
5340
5468
|
if (settings === null) {
|
|
5341
5469
|
return;
|
|
5342
5470
|
}
|
|
5343
|
-
const hostExists =
|
|
5471
|
+
const hostExists = existsSync27(hostPath);
|
|
5344
5472
|
const hostObj = hostExists ? tryReadJson(hostPath) : null;
|
|
5345
5473
|
if (hostExists && hostObj === null) {
|
|
5346
5474
|
addItem(
|
|
@@ -5557,15 +5685,15 @@ function reportSyncModality(section2) {
|
|
|
5557
5685
|
// src/commands.doctor.checks.crlf.ts
|
|
5558
5686
|
init_color();
|
|
5559
5687
|
import { execFileSync as execFileSync13 } from "node:child_process";
|
|
5560
|
-
import { existsSync as
|
|
5561
|
-
import { join as
|
|
5688
|
+
import { existsSync as existsSync28, readFileSync as readFileSync13 } from "node:fs";
|
|
5689
|
+
import { join as join39 } from "node:path";
|
|
5562
5690
|
init_config();
|
|
5563
5691
|
var PROBE_TIMEOUT_MS2 = 3e3;
|
|
5564
5692
|
var GUARD_LINE = /^\*\s+-text\b/;
|
|
5565
5693
|
function hasGitattributesGuard(repo) {
|
|
5566
5694
|
try {
|
|
5567
|
-
const path =
|
|
5568
|
-
if (!
|
|
5695
|
+
const path = join39(repo, ".gitattributes");
|
|
5696
|
+
if (!existsSync28(path)) return false;
|
|
5569
5697
|
const content = readFileSync13(path, "utf8");
|
|
5570
5698
|
return content.split("\n").map((line) => line.trim()).some((line) => line !== "" && !line.startsWith("#") && GUARD_LINE.test(line));
|
|
5571
5699
|
} catch {
|
|
@@ -5606,14 +5734,14 @@ function reportCrlfGuardCheck(section2, run = execFileSync13) {
|
|
|
5606
5734
|
|
|
5607
5735
|
// src/spinner.ts
|
|
5608
5736
|
init_color();
|
|
5609
|
-
import { existsSync as
|
|
5737
|
+
import { existsSync as existsSync33 } from "node:fs";
|
|
5610
5738
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
5611
5739
|
import { Worker } from "node:worker_threads";
|
|
5612
5740
|
|
|
5613
5741
|
// src/commands.push.recovery.ts
|
|
5614
5742
|
init_config();
|
|
5615
5743
|
import { readFileSync as readFileSync18, rmSync as rmSync15, writeFileSync as writeFileSync9 } from "node:fs";
|
|
5616
|
-
import { join as
|
|
5744
|
+
import { join as join47 } from "node:path";
|
|
5617
5745
|
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
5618
5746
|
|
|
5619
5747
|
// src/commands.push.recovery.actions.ts
|
|
@@ -5630,10 +5758,10 @@ import {
|
|
|
5630
5758
|
mkdirSync as mkdirSync9,
|
|
5631
5759
|
readFileSync as readFileSync14,
|
|
5632
5760
|
realpathSync as realpathSync5,
|
|
5633
|
-
statSync as
|
|
5761
|
+
statSync as statSync7,
|
|
5634
5762
|
writeFileSync as writeFileSync6
|
|
5635
5763
|
} from "node:fs";
|
|
5636
|
-
import { dirname as dirname10, join as
|
|
5764
|
+
import { dirname as dirname10, join as join40, sep as sep6 } from "node:path";
|
|
5637
5765
|
init_push_gitleaks_scan();
|
|
5638
5766
|
|
|
5639
5767
|
// src/rel-path-guard.ts
|
|
@@ -5662,15 +5790,15 @@ function resolveMemoryLocalPath(logical, relPath, map) {
|
|
|
5662
5790
|
if (!isSafeRelPath(relPath)) return null;
|
|
5663
5791
|
const abs = map.projects[logical]?.[HOST];
|
|
5664
5792
|
if (abs === void 0) return null;
|
|
5665
|
-
const memoryRoot =
|
|
5666
|
-
const localPath =
|
|
5793
|
+
const memoryRoot = join40(claudeHome(), "projects", encodePath(abs), "memory");
|
|
5794
|
+
const localPath = join40(memoryRoot, ...relPath.split("/"));
|
|
5667
5795
|
if (localPath !== memoryRoot && !localPath.startsWith(memoryRoot + sep6)) return null;
|
|
5668
5796
|
try {
|
|
5669
5797
|
if (lstatSync14(localPath).isSymbolicLink()) return null;
|
|
5670
5798
|
const realLocal = realpathSync5(localPath);
|
|
5671
5799
|
const realRoot = realpathSync5(memoryRoot);
|
|
5672
5800
|
if (!realLocal.startsWith(realRoot + sep6)) return null;
|
|
5673
|
-
if (!
|
|
5801
|
+
if (!statSync7(realLocal).isFile()) return null;
|
|
5674
5802
|
} catch {
|
|
5675
5803
|
return null;
|
|
5676
5804
|
}
|
|
@@ -5717,7 +5845,7 @@ function applyMemoryRedact(f, ts, map, scan = scanFile) {
|
|
|
5717
5845
|
);
|
|
5718
5846
|
}
|
|
5719
5847
|
writeFileSync6(localPath, after, "utf8");
|
|
5720
|
-
const dest =
|
|
5848
|
+
const dest = join40(repoHome(), "shared", "projects", logical, "memory", ...relPath.split("/"));
|
|
5721
5849
|
mkdirSync9(dirname10(dest), { recursive: true });
|
|
5722
5850
|
cpSync6(localPath, dest, { force: true });
|
|
5723
5851
|
return true;
|
|
@@ -5731,15 +5859,15 @@ import {
|
|
|
5731
5859
|
mkdirSync as mkdirSync11,
|
|
5732
5860
|
readFileSync as readFileSync15,
|
|
5733
5861
|
realpathSync as realpathSync6,
|
|
5734
|
-
statSync as
|
|
5862
|
+
statSync as statSync8,
|
|
5735
5863
|
writeFileSync as writeFileSync7
|
|
5736
5864
|
} from "node:fs";
|
|
5737
|
-
import { dirname as dirname11, join as
|
|
5865
|
+
import { dirname as dirname11, join as join42, sep as sep7 } from "node:path";
|
|
5738
5866
|
|
|
5739
5867
|
// src/skills-sync.ts
|
|
5740
5868
|
init_config();
|
|
5741
|
-
import { existsSync as
|
|
5742
|
-
import { join as
|
|
5869
|
+
import { existsSync as existsSync29, lstatSync as lstatSync15, mkdirSync as mkdirSync10, readdirSync as readdirSync14, rmSync as rmSync13 } from "node:fs";
|
|
5870
|
+
import { join as join41 } from "node:path";
|
|
5743
5871
|
|
|
5744
5872
|
// src/skills-sync.tracked.ts
|
|
5745
5873
|
init_utils();
|
|
@@ -5774,9 +5902,9 @@ function copySkillsPull(src, dst, isRootPreserved) {
|
|
|
5774
5902
|
copyExtrasFilteredPreservingBy(src, dst, isSkillExcluded, isRootPreserved);
|
|
5775
5903
|
}
|
|
5776
5904
|
function syncSkillsPull(ts, prePostHeads) {
|
|
5777
|
-
const sharedSkills =
|
|
5778
|
-
if (!
|
|
5779
|
-
const localSkills =
|
|
5905
|
+
const sharedSkills = join41(repoHome(), "shared", "skills");
|
|
5906
|
+
if (!existsSync29(sharedSkills)) return;
|
|
5907
|
+
const localSkills = join41(claudeHome(), "skills");
|
|
5780
5908
|
backupBeforeWrite(localSkills, ts);
|
|
5781
5909
|
const dstStat = lstatSync15(localSkills, { throwIfNoEntry: false });
|
|
5782
5910
|
if (dstStat?.isSymbolicLink() === true) {
|
|
@@ -5787,11 +5915,11 @@ function syncSkillsPull(ts, prePostHeads) {
|
|
|
5787
5915
|
copySkillsPull(sharedSkills, localSkills, (name) => !tracked.has(name));
|
|
5788
5916
|
}
|
|
5789
5917
|
function syncSkillsPush() {
|
|
5790
|
-
const localSkills =
|
|
5918
|
+
const localSkills = join41(claudeHome(), "skills");
|
|
5791
5919
|
const stat = lstatSync15(localSkills, { throwIfNoEntry: false });
|
|
5792
5920
|
if (stat === void 0) return;
|
|
5793
5921
|
if (stat.isSymbolicLink()) return;
|
|
5794
|
-
const sharedSkills =
|
|
5922
|
+
const sharedSkills = join41(repoHome(), "shared", "skills");
|
|
5795
5923
|
copySkillsPush(localSkills, sharedSkills);
|
|
5796
5924
|
}
|
|
5797
5925
|
|
|
@@ -5814,16 +5942,16 @@ function resolveSkillLocalPath(name, relPath) {
|
|
|
5814
5942
|
if (!SAFE_SKILL_NAME.test(name) || name === "." || name === "..") return null;
|
|
5815
5943
|
if (isGsdOwned(name)) return null;
|
|
5816
5944
|
if (!isSafeRelPath(relPath)) return null;
|
|
5817
|
-
const skillsRoot =
|
|
5818
|
-
const skillRoot =
|
|
5819
|
-
const localPath =
|
|
5945
|
+
const skillsRoot = join42(claudeHome(), "skills");
|
|
5946
|
+
const skillRoot = join42(skillsRoot, name);
|
|
5947
|
+
const localPath = join42(skillRoot, ...relPath.split("/"));
|
|
5820
5948
|
if (localPath !== skillRoot && !localPath.startsWith(skillRoot + sep7)) return null;
|
|
5821
5949
|
try {
|
|
5822
5950
|
if (lstatSync16(localPath).isSymbolicLink()) return null;
|
|
5823
5951
|
const realLocal = realpathSync6(localPath);
|
|
5824
5952
|
const realRoot = realpathSync6(skillsRoot);
|
|
5825
5953
|
if (!realLocal.startsWith(realRoot + sep7)) return null;
|
|
5826
|
-
if (!
|
|
5954
|
+
if (!statSync8(realLocal).isFile()) return null;
|
|
5827
5955
|
} catch {
|
|
5828
5956
|
return null;
|
|
5829
5957
|
}
|
|
@@ -5868,7 +5996,7 @@ function applySkillRedact(f, ts, scan = scanFile) {
|
|
|
5868
5996
|
);
|
|
5869
5997
|
}
|
|
5870
5998
|
writeFileSync7(localPath, after, "utf8");
|
|
5871
|
-
const dest =
|
|
5999
|
+
const dest = join42(repoHome(), "shared", "skills", name, ...relPath.split("/"));
|
|
5872
6000
|
mkdirSync11(dirname11(dest), { recursive: true });
|
|
5873
6001
|
cpSync7(localPath, dest, { force: true });
|
|
5874
6002
|
return true;
|
|
@@ -5946,25 +6074,25 @@ function applyDeferredAllows(findings, state, repo) {
|
|
|
5946
6074
|
// src/commands.push.recovery.redact.ts
|
|
5947
6075
|
init_config();
|
|
5948
6076
|
init_config_sharedDirs_guard();
|
|
5949
|
-
import { cpSync as cpSync8, existsSync as
|
|
5950
|
-
import { dirname as dirname13, join as
|
|
6077
|
+
import { cpSync as cpSync8, existsSync as existsSync32, mkdirSync as mkdirSync12 } from "node:fs";
|
|
6078
|
+
import { dirname as dirname13, join as join45, sep as sep8 } from "node:path";
|
|
5951
6079
|
|
|
5952
6080
|
// src/commands.redact.ts
|
|
5953
6081
|
init_config();
|
|
5954
|
-
import { existsSync as
|
|
5955
|
-
import { dirname as dirname12, join as
|
|
6082
|
+
import { existsSync as existsSync31 } from "node:fs";
|
|
6083
|
+
import { dirname as dirname12, join as join44 } from "node:path";
|
|
5956
6084
|
|
|
5957
6085
|
// src/commands.redact.subtree.ts
|
|
5958
|
-
import { existsSync as
|
|
5959
|
-
import { join as
|
|
6086
|
+
import { existsSync as existsSync30, lstatSync as lstatSync17, readFileSync as readFileSync16, readdirSync as readdirSync15, statSync as statSync9, writeFileSync as writeFileSync8 } from "node:fs";
|
|
6087
|
+
import { join as join43 } from "node:path";
|
|
5960
6088
|
init_utils_fs();
|
|
5961
6089
|
init_utils();
|
|
5962
6090
|
function collectFiles(dir, out) {
|
|
5963
|
-
if (!
|
|
6091
|
+
if (!existsSync30(dir)) return;
|
|
5964
6092
|
const st = lstatSync17(dir);
|
|
5965
6093
|
if (!st.isDirectory()) return;
|
|
5966
6094
|
for (const entry of readdirSync15(dir)) {
|
|
5967
|
-
const abs =
|
|
6095
|
+
const abs = join43(dir, entry);
|
|
5968
6096
|
const lst = lstatSync17(abs);
|
|
5969
6097
|
if (lst.isSymbolicLink()) continue;
|
|
5970
6098
|
if (lst.isDirectory()) {
|
|
@@ -5979,7 +6107,7 @@ function listSubtreeFiles(sessionDir) {
|
|
|
5979
6107
|
collectFiles(sessionDir, out);
|
|
5980
6108
|
return out.sort((a, b) => a.localeCompare(b));
|
|
5981
6109
|
}
|
|
5982
|
-
function newestSubtreeMtimeMs(mainPath, subtreeFiles, statMtime = (p) =>
|
|
6110
|
+
function newestSubtreeMtimeMs(mainPath, subtreeFiles, statMtime = (p) => statSync9(p).mtimeMs) {
|
|
5983
6111
|
let newest = statMtime(mainPath);
|
|
5984
6112
|
for (const filePath of subtreeFiles) {
|
|
5985
6113
|
const t = statMtime(filePath);
|
|
@@ -5988,7 +6116,7 @@ function newestSubtreeMtimeMs(mainPath, subtreeFiles, statMtime = (p) => statSyn
|
|
|
5988
6116
|
return newest;
|
|
5989
6117
|
}
|
|
5990
6118
|
function isSubtreeActive(mainPath, subtreeFiles, now) {
|
|
5991
|
-
const subtreeMtime = newestSubtreeMtimeMs(mainPath, subtreeFiles, (p) =>
|
|
6119
|
+
const subtreeMtime = newestSubtreeMtimeMs(mainPath, subtreeFiles, (p) => statSync9(p).mtimeMs);
|
|
5992
6120
|
return isRecentlyModified(subtreeMtime, now);
|
|
5993
6121
|
}
|
|
5994
6122
|
function applySubtreeRedactions(mainPath, mainFindings, subtreeFiles, rule, ts, scan, dryRun) {
|
|
@@ -6074,15 +6202,15 @@ init_utils_json();
|
|
|
6074
6202
|
init_utils();
|
|
6075
6203
|
function resolveLiveTranscript(id) {
|
|
6076
6204
|
try {
|
|
6077
|
-
const mapPath =
|
|
6078
|
-
if (!
|
|
6205
|
+
const mapPath = join44(repoHome(), "path-map.json");
|
|
6206
|
+
if (!existsSync31(mapPath)) return null;
|
|
6079
6207
|
const projects = readJson(mapPath).projects;
|
|
6080
6208
|
const claude = claudeHome();
|
|
6081
6209
|
for (const hostMap of Object.values(projects)) {
|
|
6082
6210
|
const abs = hostMap[HOST];
|
|
6083
6211
|
if (abs === void 0) continue;
|
|
6084
|
-
const live =
|
|
6085
|
-
if (
|
|
6212
|
+
const live = join44(claude, "projects", encodePath(abs), `${id}.jsonl`);
|
|
6213
|
+
if (existsSync31(live)) return live;
|
|
6086
6214
|
}
|
|
6087
6215
|
return null;
|
|
6088
6216
|
} catch {
|
|
@@ -6102,17 +6230,17 @@ function cmdRedact(opts, nowMs = Date.now, scan = scanFile) {
|
|
|
6102
6230
|
}
|
|
6103
6231
|
const repo = repoHome();
|
|
6104
6232
|
const backup = backupBase();
|
|
6105
|
-
if (!
|
|
6233
|
+
if (!existsSync31(repo)) die(`repo not cloned at ${repo}`);
|
|
6106
6234
|
const handle = acquireLock("redact");
|
|
6107
6235
|
if (handle === null) process.exit(0);
|
|
6108
6236
|
try {
|
|
6109
6237
|
const localPath = resolveLiveTranscript(id);
|
|
6110
|
-
if (localPath === null || !
|
|
6238
|
+
if (localPath === null || !existsSync31(localPath)) {
|
|
6111
6239
|
fail(`could not resolve local transcript for session ${id} on this host`);
|
|
6112
6240
|
process.exitCode = 1;
|
|
6113
6241
|
return;
|
|
6114
6242
|
}
|
|
6115
|
-
const sessionDir =
|
|
6243
|
+
const sessionDir = join44(dirname12(localPath), id);
|
|
6116
6244
|
const subtreeFiles = listSubtreeFiles(sessionDir);
|
|
6117
6245
|
if (isSubtreeActive(localPath, subtreeFiles, nowMs())) {
|
|
6118
6246
|
log(
|
|
@@ -6173,8 +6301,8 @@ function resolveStagedDir(localPath, map, claude, repo) {
|
|
|
6173
6301
|
assertSafeLogical(logical);
|
|
6174
6302
|
const abs = hostMap[HOST];
|
|
6175
6303
|
if (abs === void 0) continue;
|
|
6176
|
-
if (localPath.startsWith(
|
|
6177
|
-
return
|
|
6304
|
+
if (localPath.startsWith(join45(claude, "projects", encodePath(abs)) + sep8)) {
|
|
6305
|
+
return join45(repo, "shared", "projects", logical);
|
|
6178
6306
|
}
|
|
6179
6307
|
}
|
|
6180
6308
|
return null;
|
|
@@ -6184,7 +6312,7 @@ function preflightRedactable(f, map, nowMs) {
|
|
|
6184
6312
|
if (sid === null) return "a finding has no resolvable session id (not a session transcript)";
|
|
6185
6313
|
const localPath = resolveLiveTranscript(sid);
|
|
6186
6314
|
if (localPath === null) return `session ${sid}: local transcript not found`;
|
|
6187
|
-
const sessionDir =
|
|
6315
|
+
const sessionDir = join45(dirname13(localPath), sid);
|
|
6188
6316
|
const subtreeFiles = listSubtreeFiles(sessionDir);
|
|
6189
6317
|
if (isSubtreeActive(localPath, subtreeFiles, nowMs())) {
|
|
6190
6318
|
return `session ${sid}: looks active (modified within the last 5 minutes)`;
|
|
@@ -6213,7 +6341,7 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
|
|
|
6213
6341
|
`could not locate the local transcript for session ${sid}; choose Skip or Drop session.`
|
|
6214
6342
|
);
|
|
6215
6343
|
}
|
|
6216
|
-
const sessionDir =
|
|
6344
|
+
const sessionDir = join45(dirname13(localPath), sid);
|
|
6217
6345
|
const subtreeFiles = listSubtreeFiles(sessionDir);
|
|
6218
6346
|
if (isSubtreeActive(localPath, subtreeFiles, nowMs())) {
|
|
6219
6347
|
return refuse(
|
|
@@ -6246,9 +6374,9 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
|
|
|
6246
6374
|
);
|
|
6247
6375
|
}
|
|
6248
6376
|
mkdirSync12(stagedProjectDir, { recursive: true });
|
|
6249
|
-
cpSync8(localPath,
|
|
6250
|
-
if (
|
|
6251
|
-
cpSync8(sessionDir,
|
|
6377
|
+
cpSync8(localPath, join45(stagedProjectDir, `${sid}.jsonl`), { force: true });
|
|
6378
|
+
if (existsSync32(sessionDir)) {
|
|
6379
|
+
cpSync8(sessionDir, join45(stagedProjectDir, sid), { force: true, recursive: true });
|
|
6252
6380
|
}
|
|
6253
6381
|
return true;
|
|
6254
6382
|
}
|
|
@@ -6256,14 +6384,14 @@ function applyRedact(f, ts, map, nowMs, scan = scanFile) {
|
|
|
6256
6384
|
// src/commands.push.recovery.drop.ts
|
|
6257
6385
|
init_config();
|
|
6258
6386
|
import { rmSync as rmSync14 } from "node:fs";
|
|
6259
|
-
import { join as
|
|
6387
|
+
import { join as join46 } from "node:path";
|
|
6260
6388
|
function dropSessionFromStaged(sid, map) {
|
|
6261
6389
|
const logicals = Object.keys(map.projects);
|
|
6262
6390
|
if (logicals.length === 0) return false;
|
|
6263
6391
|
const repo = repoHome();
|
|
6264
6392
|
for (const logical of logicals) {
|
|
6265
|
-
const jsonl =
|
|
6266
|
-
const dir =
|
|
6393
|
+
const jsonl = join46(repo, "shared", "projects", logical, `${sid}.jsonl`);
|
|
6394
|
+
const dir = join46(repo, "shared", "projects", logical, sid);
|
|
6267
6395
|
rmSync14(jsonl, { force: true });
|
|
6268
6396
|
rmSync14(dir, { recursive: true, force: true });
|
|
6269
6397
|
}
|
|
@@ -6748,7 +6876,7 @@ function applyThenRescan(scanVerdict, repoHome2) {
|
|
|
6748
6876
|
return next;
|
|
6749
6877
|
}
|
|
6750
6878
|
function allowThenRescan(append, scanVerdict, repoHome2) {
|
|
6751
|
-
const ignPath =
|
|
6879
|
+
const ignPath = join47(repoHome2, ".gitleaksignore");
|
|
6752
6880
|
let before;
|
|
6753
6881
|
try {
|
|
6754
6882
|
before = readFileSync18(ignPath, "utf8");
|
|
@@ -6854,7 +6982,7 @@ function writeAnimatedDone(out, label, ms, useTTY) {
|
|
|
6854
6982
|
`);
|
|
6855
6983
|
}
|
|
6856
6984
|
function resolveWorkerPath(deps = {}) {
|
|
6857
|
-
const check = deps.existsSyncFn ??
|
|
6985
|
+
const check = deps.existsSyncFn ?? existsSync33;
|
|
6858
6986
|
const base = deps.baseUrl ?? import.meta.url;
|
|
6859
6987
|
const mjs = fileURLToPath4(new URL("./nomad.worker.mjs", base));
|
|
6860
6988
|
if (check(mjs)) return mjs;
|
|
@@ -6921,8 +7049,8 @@ function withSpinner(label, fn, deps) {
|
|
|
6921
7049
|
// src/commands.doctor.gitleaks-version.ts
|
|
6922
7050
|
init_color();
|
|
6923
7051
|
import { execFileSync as execFileSync15 } from "node:child_process";
|
|
6924
|
-
import { existsSync as
|
|
6925
|
-
import { join as
|
|
7052
|
+
import { existsSync as existsSync34 } from "node:fs";
|
|
7053
|
+
import { join as join48 } from "node:path";
|
|
6926
7054
|
init_config();
|
|
6927
7055
|
var SEMVER_MAJOR_MINOR = /^(\d+)\.(\d+)\.\d+$/;
|
|
6928
7056
|
var GITLEAKS_TIMEOUT_MS = 5e3;
|
|
@@ -6931,7 +7059,7 @@ function majorMinorOf(value) {
|
|
|
6931
7059
|
return m === null ? null : [m[1], m[2]];
|
|
6932
7060
|
}
|
|
6933
7061
|
function readGitleaksVersion(run, tomlExists) {
|
|
6934
|
-
const tomlPath =
|
|
7062
|
+
const tomlPath = join48(repoHome(), ".gitleaks.toml");
|
|
6935
7063
|
const args = ["version"];
|
|
6936
7064
|
if (tomlExists(tomlPath)) args.push("--config", tomlPath);
|
|
6937
7065
|
try {
|
|
@@ -6943,7 +7071,7 @@ function readGitleaksVersion(run, tomlExists) {
|
|
|
6943
7071
|
return null;
|
|
6944
7072
|
}
|
|
6945
7073
|
}
|
|
6946
|
-
function reportGitleaksVersionCheck(section2, run = execFileSync15, tomlExists =
|
|
7074
|
+
function reportGitleaksVersionCheck(section2, run = execFileSync15, tomlExists = existsSync34) {
|
|
6947
7075
|
const raw = readGitleaksVersion(run, tomlExists);
|
|
6948
7076
|
if (raw === null) return;
|
|
6949
7077
|
const local = majorMinorOf(raw);
|
|
@@ -7200,8 +7328,8 @@ function gatherDoctorSections(opts) {
|
|
|
7200
7328
|
reportLongPathsCheck(host);
|
|
7201
7329
|
reportCrlfGuardCheck(host);
|
|
7202
7330
|
const links = section("Shared links");
|
|
7203
|
-
const mapPath =
|
|
7204
|
-
const rawMap =
|
|
7331
|
+
const mapPath = join49(repoHome(), "path-map.json");
|
|
7332
|
+
const rawMap = existsSync35(mapPath) ? readJsonSafe(mapPath, mapPath, links) : null;
|
|
7205
7333
|
const map = rawMap ?? { projects: {} };
|
|
7206
7334
|
reportSharedLinks(links, map);
|
|
7207
7335
|
reportDroppedNamesMigration(links);
|
|
@@ -7299,8 +7427,8 @@ function parseDoctorArgs(args) {
|
|
|
7299
7427
|
// src/commands.drop-session.ts
|
|
7300
7428
|
init_config();
|
|
7301
7429
|
import { execFileSync as execFileSync20 } from "node:child_process";
|
|
7302
|
-
import { existsSync as
|
|
7303
|
-
import { join as
|
|
7430
|
+
import { existsSync as existsSync37, readdirSync as readdirSync16, statSync as statSync10 } from "node:fs";
|
|
7431
|
+
import { join as join51, relative as relative7 } from "node:path";
|
|
7304
7432
|
|
|
7305
7433
|
// src/commands.drop-session.git.ts
|
|
7306
7434
|
import { execFileSync as execFileSync19 } from "node:child_process";
|
|
@@ -7343,8 +7471,8 @@ function isInIndex(rel, repo) {
|
|
|
7343
7471
|
init_config();
|
|
7344
7472
|
init_utils();
|
|
7345
7473
|
init_utils_json();
|
|
7346
|
-
import { existsSync as
|
|
7347
|
-
import { join as
|
|
7474
|
+
import { existsSync as existsSync36 } from "node:fs";
|
|
7475
|
+
import { join as join50 } from "node:path";
|
|
7348
7476
|
var SHARED_PROJECT_LOGICAL = /^shared\/projects\/([^/]+)\//;
|
|
7349
7477
|
function reportScrubHint(id, matches) {
|
|
7350
7478
|
const live = resolveLiveTranscript2(id, matches);
|
|
@@ -7360,8 +7488,8 @@ function reportScrubHint(id, matches) {
|
|
|
7360
7488
|
}
|
|
7361
7489
|
function resolveLiveTranscript2(id, matches) {
|
|
7362
7490
|
try {
|
|
7363
|
-
const mapPath =
|
|
7364
|
-
if (!
|
|
7491
|
+
const mapPath = join50(repoHome(), "path-map.json");
|
|
7492
|
+
if (!existsSync36(mapPath)) return null;
|
|
7365
7493
|
const projects = readJson(mapPath).projects;
|
|
7366
7494
|
const claude = claudeHome();
|
|
7367
7495
|
for (const rel of matches) {
|
|
@@ -7369,8 +7497,8 @@ function resolveLiveTranscript2(id, matches) {
|
|
|
7369
7497
|
if (logical === void 0) continue;
|
|
7370
7498
|
const abs = projects[logical]?.[HOST];
|
|
7371
7499
|
if (abs === void 0) continue;
|
|
7372
|
-
const live =
|
|
7373
|
-
if (
|
|
7500
|
+
const live = join50(claude, "projects", encodePath(abs), `${id}.jsonl`);
|
|
7501
|
+
if (existsSync36(live)) return live;
|
|
7374
7502
|
}
|
|
7375
7503
|
return null;
|
|
7376
7504
|
} catch {
|
|
@@ -7386,12 +7514,12 @@ function cmdDropSession(id) {
|
|
|
7386
7514
|
process.exit(1);
|
|
7387
7515
|
}
|
|
7388
7516
|
const repo = repoHome();
|
|
7389
|
-
if (!
|
|
7517
|
+
if (!existsSync37(repo)) die(`repo not cloned at ${repo}`);
|
|
7390
7518
|
const handle = acquireLock("drop-session");
|
|
7391
7519
|
if (handle === null) process.exit(0);
|
|
7392
7520
|
try {
|
|
7393
|
-
const repoProjects =
|
|
7394
|
-
if (!
|
|
7521
|
+
const repoProjects = join51(repo, "shared", "projects");
|
|
7522
|
+
if (!existsSync37(repoProjects)) {
|
|
7395
7523
|
throw new NomadFatal(`no staged session matches ${id}`);
|
|
7396
7524
|
}
|
|
7397
7525
|
const matches = collectMatches(repoProjects, id, repo);
|
|
@@ -7414,12 +7542,12 @@ function cmdDropSession(id) {
|
|
|
7414
7542
|
function collectMatches(repoProjects, id, repo) {
|
|
7415
7543
|
const matches = [];
|
|
7416
7544
|
for (const logical of readdirSync16(repoProjects)) {
|
|
7417
|
-
const candidate =
|
|
7418
|
-
if (
|
|
7545
|
+
const candidate = join51(repoProjects, logical, `${id}.jsonl`);
|
|
7546
|
+
if (existsSync37(candidate)) {
|
|
7419
7547
|
matches.push(relative7(repo, candidate));
|
|
7420
7548
|
}
|
|
7421
|
-
const dir =
|
|
7422
|
-
if (
|
|
7549
|
+
const dir = join51(repoProjects, logical, id);
|
|
7550
|
+
if (existsSync37(dir) && statSync10(dir).isDirectory()) {
|
|
7423
7551
|
const dirRel = relative7(repo, dir);
|
|
7424
7552
|
const staged = expandStagedDir(dirRel, repo);
|
|
7425
7553
|
if (staged.length > 0) matches.push(...staged);
|
|
@@ -7455,8 +7583,8 @@ function unstageOne(rel, repo) {
|
|
|
7455
7583
|
|
|
7456
7584
|
// src/commands.pull.ts
|
|
7457
7585
|
init_autostash_guard();
|
|
7458
|
-
import { existsSync as
|
|
7459
|
-
import { join as
|
|
7586
|
+
import { existsSync as existsSync46, mkdirSync as mkdirSync14 } from "node:fs";
|
|
7587
|
+
import { join as join62 } from "node:path";
|
|
7460
7588
|
|
|
7461
7589
|
// src/commands.push.sections.ts
|
|
7462
7590
|
init_color();
|
|
@@ -7564,18 +7692,18 @@ init_config();
|
|
|
7564
7692
|
|
|
7565
7693
|
// src/extras-sync.ts
|
|
7566
7694
|
init_config();
|
|
7567
|
-
import { existsSync as
|
|
7568
|
-
import { join as
|
|
7695
|
+
import { existsSync as existsSync39, statSync as statSync11 } from "node:fs";
|
|
7696
|
+
import { join as join54 } from "node:path";
|
|
7569
7697
|
init_utils();
|
|
7570
7698
|
init_utils_json();
|
|
7571
7699
|
|
|
7572
7700
|
// src/extras-sync.remap.ts
|
|
7573
7701
|
init_config();
|
|
7574
|
-
import { existsSync as
|
|
7575
|
-
import { dirname as dirname14, join as
|
|
7702
|
+
import { existsSync as existsSync38, mkdirSync as mkdirSync13, readdirSync as readdirSync17, readFileSync as readFileSync19, realpathSync as realpathSync7, rmSync as rmSync16 } from "node:fs";
|
|
7703
|
+
import { dirname as dirname14, join as join53, sep as sep11 } from "node:path";
|
|
7576
7704
|
|
|
7577
7705
|
// src/extras-sync.planning-diff.ts
|
|
7578
|
-
import { join as
|
|
7706
|
+
import { join as join52, normalize as normalize2, sep as sep10 } from "node:path";
|
|
7579
7707
|
init_utils();
|
|
7580
7708
|
function processRecord(fields, i, changed, deleted) {
|
|
7581
7709
|
const status = fields[i];
|
|
@@ -7627,7 +7755,7 @@ function planningDeleteTargets(opts) {
|
|
|
7627
7755
|
const { deleted } = parsePlanningDiff(raw);
|
|
7628
7756
|
const logicalPrefix = "shared/extras/" + logical + "/";
|
|
7629
7757
|
const prefix = logicalPrefix + ".planning/";
|
|
7630
|
-
const planningRoot =
|
|
7758
|
+
const planningRoot = join52(localRoot, ".planning");
|
|
7631
7759
|
const planningRootBoundary = planningRoot + sep10;
|
|
7632
7760
|
const targets = [];
|
|
7633
7761
|
for (const repoPath of deleted) {
|
|
@@ -7635,7 +7763,7 @@ function planningDeleteTargets(opts) {
|
|
|
7635
7763
|
continue;
|
|
7636
7764
|
}
|
|
7637
7765
|
const remainder = repoPath.slice(logicalPrefix.length);
|
|
7638
|
-
const candidate =
|
|
7766
|
+
const candidate = join52(localRoot, remainder);
|
|
7639
7767
|
const resolved = normalize2(candidate);
|
|
7640
7768
|
if (!resolved.startsWith(planningRootBoundary)) {
|
|
7641
7769
|
throw new NomadFatal(
|
|
@@ -7656,7 +7784,7 @@ function runExtrasOp(v, dryRun, paths, backup, copy) {
|
|
|
7656
7784
|
const would = [];
|
|
7657
7785
|
for (const t of eachExtrasTarget(v, counts)) {
|
|
7658
7786
|
const { src, dst } = paths(t);
|
|
7659
|
-
if (!
|
|
7787
|
+
if (!existsSync38(src)) continue;
|
|
7660
7788
|
const item2 = `${t.logical}/${t.dirname}`;
|
|
7661
7789
|
if (dryRun) {
|
|
7662
7790
|
would.push(item2);
|
|
@@ -7691,7 +7819,7 @@ function isInsidePlanningRoot(parentReal, rootReal) {
|
|
|
7691
7819
|
return parentReal === rootReal || parentReal.startsWith(rootReal + sep11);
|
|
7692
7820
|
}
|
|
7693
7821
|
function deletePlanningTarget(target, planningRoot, repoCounterpart) {
|
|
7694
|
-
if (
|
|
7822
|
+
if (existsSync38(repoCounterpart)) return;
|
|
7695
7823
|
const parentReal = tryRealpath(dirname14(target));
|
|
7696
7824
|
if (parentReal === void 0) return;
|
|
7697
7825
|
const rootReal = tryRealpath(planningRoot);
|
|
@@ -7701,7 +7829,7 @@ function deletePlanningTarget(target, planningRoot, repoCounterpart) {
|
|
|
7701
7829
|
pruneEmptyAncestors(target, planningRoot);
|
|
7702
7830
|
}
|
|
7703
7831
|
function localDivergesFromPreDelete(target, pre, repoRel, repo) {
|
|
7704
|
-
if (!
|
|
7832
|
+
if (!existsSync38(target)) return false;
|
|
7705
7833
|
try {
|
|
7706
7834
|
const preBlob = gitCaptureBuffer(["show", `${pre}:${repoRel}`], repo);
|
|
7707
7835
|
return !readFileSync19(target).equals(preBlob);
|
|
@@ -7746,7 +7874,7 @@ function keptDeletePreview(v, prePostHeads, repo) {
|
|
|
7746
7874
|
return kept;
|
|
7747
7875
|
}
|
|
7748
7876
|
function propagatePlanningDeletes(v, ts, prePostHeads, repo) {
|
|
7749
|
-
const repoExtras =
|
|
7877
|
+
const repoExtras = join53(repo, "shared", "extras");
|
|
7750
7878
|
for (const t of eachExtrasTarget(v, { unmapped: 0, skipped: 0 })) {
|
|
7751
7879
|
if (t.dirname !== ".planning") continue;
|
|
7752
7880
|
let raw;
|
|
@@ -7761,14 +7889,14 @@ function propagatePlanningDeletes(v, ts, prePostHeads, repo) {
|
|
|
7761
7889
|
}
|
|
7762
7890
|
const pairs = deletePairsFor(t, raw);
|
|
7763
7891
|
if (pairs.length === 0) continue;
|
|
7764
|
-
backupExtrasWrite(
|
|
7765
|
-
const planningRoot =
|
|
7892
|
+
backupExtrasWrite(join53(t.localRoot, t.dirname), ts, t.localRoot);
|
|
7893
|
+
const planningRoot = join53(t.localRoot, ".planning");
|
|
7766
7894
|
for (const { target, relToLocal, repoRel } of pairs) {
|
|
7767
7895
|
if (localDivergesFromPreDelete(target, prePostHeads.pre, repoRel, repo)) {
|
|
7768
7896
|
warn(keptDeleteWarnLine(t.logical, relToLocal));
|
|
7769
7897
|
continue;
|
|
7770
7898
|
}
|
|
7771
|
-
deletePlanningTarget(target, planningRoot,
|
|
7899
|
+
deletePlanningTarget(target, planningRoot, join53(repoExtras, t.logical, relToLocal));
|
|
7772
7900
|
}
|
|
7773
7901
|
}
|
|
7774
7902
|
}
|
|
@@ -7777,14 +7905,14 @@ function remapExtrasPush(ts, opts = {}) {
|
|
|
7777
7905
|
const v = loadValidatedExtras({ missingMsg: "no path-map.json; skipping extras push" });
|
|
7778
7906
|
if (v === null) return { unmapped: 0, skipped: 0, pushed: [], wouldPush: [] };
|
|
7779
7907
|
const repo = repoHome();
|
|
7780
|
-
const repoExtras =
|
|
7908
|
+
const repoExtras = join53(repo, "shared", "extras");
|
|
7781
7909
|
if (!dryRun) mkdirSync13(repoExtras, { recursive: true });
|
|
7782
7910
|
const { unmapped, skipped, done, would } = runExtrasOp(
|
|
7783
7911
|
v,
|
|
7784
7912
|
dryRun,
|
|
7785
7913
|
({ localRoot, logical, dirname: dirname15 }) => ({
|
|
7786
|
-
src:
|
|
7787
|
-
dst:
|
|
7914
|
+
src: join53(localRoot, dirname15),
|
|
7915
|
+
dst: join53(repoExtras, logical, dirname15)
|
|
7788
7916
|
}),
|
|
7789
7917
|
(dst) => backupRepoWrite(dst, ts, repo),
|
|
7790
7918
|
// Push copy routing per extra type:
|
|
@@ -7814,8 +7942,8 @@ function remapExtrasPull(ts, opts = {}) {
|
|
|
7814
7942
|
v,
|
|
7815
7943
|
dryRun,
|
|
7816
7944
|
({ localRoot, logical, dirname: dirname15 }) => ({
|
|
7817
|
-
src:
|
|
7818
|
-
dst:
|
|
7945
|
+
src: join53(repo, "shared", "extras", logical, dirname15),
|
|
7946
|
+
dst: join53(localRoot, dirname15)
|
|
7819
7947
|
}),
|
|
7820
7948
|
// Snapshot the host-side dst BEFORE the copy step touches it. Anchor on
|
|
7821
7949
|
// localRoot so the backup tree mirrors the project layout.
|
|
@@ -7862,22 +7990,22 @@ function divergenceCheckExtras(ts, prePostHeads) {
|
|
|
7862
7990
|
const v = loadValidatedExtras({});
|
|
7863
7991
|
if (v === null) return 0;
|
|
7864
7992
|
const counts = { unmapped: 0, skipped: 0 };
|
|
7865
|
-
const backupRoot =
|
|
7993
|
+
const backupRoot = join54(backupBase(), ts, "extras");
|
|
7866
7994
|
const repo = repoHome();
|
|
7867
7995
|
let divergedCount = 0;
|
|
7868
7996
|
for (const { logical, localRoot, dirname: dirname15 } of eachExtrasTarget(v, counts)) {
|
|
7869
|
-
const local =
|
|
7870
|
-
const repoEntry =
|
|
7871
|
-
if (!
|
|
7997
|
+
const local = join54(localRoot, dirname15);
|
|
7998
|
+
const repoEntry = join54(repo, "shared", "extras", logical, dirname15);
|
|
7999
|
+
if (!existsSync39(local) || !existsSync39(repoEntry)) continue;
|
|
7872
8000
|
const modified = listDivergingModified(local, repoEntry);
|
|
7873
8001
|
if (modified.length === 0) continue;
|
|
7874
8002
|
divergedCount += modified.length;
|
|
7875
|
-
const projectBackupRoot =
|
|
8003
|
+
const projectBackupRoot = join54(backupRoot, encodePath(localRoot));
|
|
7876
8004
|
warn(
|
|
7877
8005
|
divergenceWarnLine({
|
|
7878
8006
|
dirname: dirname15,
|
|
7879
8007
|
logical,
|
|
7880
|
-
isDir:
|
|
8008
|
+
isDir: statSync11(local).isDirectory(),
|
|
7881
8009
|
count: modified.length,
|
|
7882
8010
|
projectBackupRoot
|
|
7883
8011
|
})
|
|
@@ -7895,7 +8023,7 @@ function divergenceCheckExtras(ts, prePostHeads) {
|
|
|
7895
8023
|
// src/links.baseline.ts
|
|
7896
8024
|
init_config();
|
|
7897
8025
|
import { lstatSync as lstatSync18, readdirSync as readdirSync18 } from "node:fs";
|
|
7898
|
-
import { join as
|
|
8026
|
+
import { join as join55, relative as relative8, sep as sep12 } from "node:path";
|
|
7899
8027
|
init_utils();
|
|
7900
8028
|
var SHARED_BASELINE_KIND = "shared-links-baseline/1";
|
|
7901
8029
|
var SHARED_BASELINE_CONFIG_HASH = "not-applicable";
|
|
@@ -7937,7 +8065,7 @@ function collectSharedFiles(dir, claude, scan) {
|
|
|
7937
8065
|
return;
|
|
7938
8066
|
}
|
|
7939
8067
|
for (const entry of entries) {
|
|
7940
|
-
const abs =
|
|
8068
|
+
const abs = join55(dir, entry);
|
|
7941
8069
|
if (isDeniedName(ALWAYS_NEVER_SYNC, entry)) {
|
|
7942
8070
|
scan.declined.push(baselineKey(claude, abs));
|
|
7943
8071
|
continue;
|
|
@@ -7953,7 +8081,7 @@ function enumerateLocalSharedScan(map, opts = {}) {
|
|
|
7953
8081
|
scan.declined.push(name);
|
|
7954
8082
|
continue;
|
|
7955
8083
|
}
|
|
7956
|
-
addLocalPath(
|
|
8084
|
+
addLocalPath(join55(claude, name), claude, scan);
|
|
7957
8085
|
}
|
|
7958
8086
|
return scan;
|
|
7959
8087
|
}
|
|
@@ -7965,7 +8093,7 @@ function buildSharedBaseline(map, opts = {}) {
|
|
|
7965
8093
|
const files = {};
|
|
7966
8094
|
for (const [key, st] of Object.entries(enumerateLocalSharedFiles(map, opts))) {
|
|
7967
8095
|
try {
|
|
7968
|
-
files[key] = { size: st.size, mtime: st.mtime, hash: hashFile(
|
|
8096
|
+
files[key] = { size: st.size, mtime: st.mtime, hash: hashFile(join55(claude, key)) };
|
|
7969
8097
|
} catch {
|
|
7970
8098
|
}
|
|
7971
8099
|
}
|
|
@@ -7982,8 +8110,8 @@ function writeSharedBaseline(map, opts = {}) {
|
|
|
7982
8110
|
|
|
7983
8111
|
// src/commands.pull.win32.ts
|
|
7984
8112
|
init_color();
|
|
7985
|
-
import { existsSync as
|
|
7986
|
-
import { join as
|
|
8113
|
+
import { existsSync as existsSync44 } from "node:fs";
|
|
8114
|
+
import { join as join60 } from "node:path";
|
|
7987
8115
|
|
|
7988
8116
|
// src/commands.pull.recovery.git.ts
|
|
7989
8117
|
init_utils();
|
|
@@ -8046,8 +8174,8 @@ function gitProbe(args, repo) {
|
|
|
8046
8174
|
|
|
8047
8175
|
// src/links.deletions.ts
|
|
8048
8176
|
init_config();
|
|
8049
|
-
import { existsSync as
|
|
8050
|
-
import { join as
|
|
8177
|
+
import { existsSync as existsSync40, lstatSync as lstatSync19, rmSync as rmSync17 } from "node:fs";
|
|
8178
|
+
import { join as join56, relative as relative9, resolve as resolve5, sep as sep13 } from "node:path";
|
|
8051
8179
|
init_utils();
|
|
8052
8180
|
init_utils_fs();
|
|
8053
8181
|
function isUnknown(declined, key) {
|
|
@@ -8057,15 +8185,15 @@ function deletionFor(key, names, claude, sharedRoot) {
|
|
|
8057
8185
|
const segments = key.split("/");
|
|
8058
8186
|
const name = segments[0];
|
|
8059
8187
|
if (!names.has(name)) return null;
|
|
8060
|
-
if (!
|
|
8188
|
+
if (!existsSync40(join56(claude, name))) return null;
|
|
8061
8189
|
if (segments.some((segment) => isDeniedName(ALWAYS_NEVER_SYNC, segment))) return null;
|
|
8062
8190
|
const repoPath = resolve5(sharedRoot, key);
|
|
8063
|
-
const rel = relative9(
|
|
8191
|
+
const rel = relative9(join56(sharedRoot, name), repoPath);
|
|
8064
8192
|
if (rel === "" || rel === ".." || rel.startsWith(`..${sep13}`)) return null;
|
|
8065
8193
|
if (segments.some((segment) => segment === "" || segment === "." || segment === ".."))
|
|
8066
8194
|
return null;
|
|
8067
|
-
if (!
|
|
8068
|
-
return { kind: "deletion", name, localPath:
|
|
8195
|
+
if (!existsSync40(repoPath)) return null;
|
|
8196
|
+
return { kind: "deletion", name, localPath: join56(claude, key), repoPath };
|
|
8069
8197
|
}
|
|
8070
8198
|
function planSharedLinkDeletions(map, opts = {}) {
|
|
8071
8199
|
if (process.platform !== "win32") return [];
|
|
@@ -8073,7 +8201,7 @@ function planSharedLinkDeletions(map, opts = {}) {
|
|
|
8073
8201
|
const baseline = readSharedBaseline();
|
|
8074
8202
|
if (baseline === null) return [];
|
|
8075
8203
|
const claude = claudeHome();
|
|
8076
|
-
const sharedRoot =
|
|
8204
|
+
const sharedRoot = join56(repoHome(), "shared");
|
|
8077
8205
|
const linkNames = opts.linkNames ?? allSharedLinks(map);
|
|
8078
8206
|
const scan = enumerateLocalSharedScan(map, { linkNames });
|
|
8079
8207
|
const present = new Set(Object.keys(scan.files).map((key) => key.toLowerCase()));
|
|
@@ -8107,8 +8235,8 @@ function applySharedLinkDeletions(map, ts, opts = {}) {
|
|
|
8107
8235
|
|
|
8108
8236
|
// src/links.mirror.ts
|
|
8109
8237
|
init_config();
|
|
8110
|
-
import { existsSync as
|
|
8111
|
-
import { join as
|
|
8238
|
+
import { existsSync as existsSync41, lstatSync as lstatSync20, rmSync as rmSync18, statSync as statSync12 } from "node:fs";
|
|
8239
|
+
import { join as join57 } from "node:path";
|
|
8112
8240
|
init_utils();
|
|
8113
8241
|
init_utils_fs();
|
|
8114
8242
|
function emitMirror(onPreview, name, localPath, repoPath) {
|
|
@@ -8122,29 +8250,50 @@ function emitMirrorWet(onPreview, name, localPath, repoPath) {
|
|
|
8122
8250
|
if (onPreview) onPreview({ kind: "mirror", name, localPath, repoPath });
|
|
8123
8251
|
}
|
|
8124
8252
|
function notShared(target) {
|
|
8125
|
-
return !
|
|
8253
|
+
return !existsSync41(target);
|
|
8254
|
+
}
|
|
8255
|
+
function warnUnusableSharedTarget(name, dryRun, state) {
|
|
8256
|
+
const reason = state === "dangling" ? `shared/${name} does not resolve to anything usable in the sync repo. Remove it, or restore what it points at` : `shared/${name} could not be read in the sync repo, so whether it is usable could not be determined. Check its permissions there`;
|
|
8257
|
+
if (dryRun) {
|
|
8258
|
+
warn(`${name} would not be captured into shared/: ${reason}`);
|
|
8259
|
+
return;
|
|
8260
|
+
}
|
|
8261
|
+
warn(`${name} was not captured into shared/ this run: ${reason}, then run \`nomad push\` again`);
|
|
8262
|
+
}
|
|
8263
|
+
function warnUnreadableLocalName(name, target, err, dryRun) {
|
|
8264
|
+
const compoundState = classifyPresence(target);
|
|
8265
|
+
if (isUnusableTarget(compoundState)) {
|
|
8266
|
+
warnUnusableSharedTarget(name, dryRun, compoundState);
|
|
8267
|
+
return;
|
|
8268
|
+
}
|
|
8269
|
+
if (notShared(target)) return;
|
|
8270
|
+
if (dryRun) {
|
|
8271
|
+
warn(
|
|
8272
|
+
`${name} could not be read (${errorText(err)}), so nothing was captured for it and nothing was written. A pull that captures shared edits would skip it too. Check its permissions, or whether another program has it open`
|
|
8273
|
+
);
|
|
8274
|
+
return;
|
|
8275
|
+
}
|
|
8276
|
+
warn(
|
|
8277
|
+
`${name} could not be read (${errorText(err)}), so it was left out of shared/ this run. Check its permissions, or whether another program has it open`
|
|
8278
|
+
);
|
|
8126
8279
|
}
|
|
8127
8280
|
function mirrorOneSharedName(name, claude, repo, policy, opts) {
|
|
8128
|
-
const localPath =
|
|
8129
|
-
const target =
|
|
8281
|
+
const localPath = join57(claude, name);
|
|
8282
|
+
const target = join57(repo, "shared", name);
|
|
8130
8283
|
let stat;
|
|
8131
8284
|
try {
|
|
8132
8285
|
stat = lstatSync20(localPath, { throwIfNoEntry: false });
|
|
8133
8286
|
} catch (err) {
|
|
8134
|
-
|
|
8135
|
-
if (opts.dryRun === true) {
|
|
8136
|
-
warn(
|
|
8137
|
-
`${name} could not be read (${errorText(err)}), so nothing was captured for it and nothing was written. A pull that captures shared edits would skip it too. Check its permissions, or whether another program has it open`
|
|
8138
|
-
);
|
|
8139
|
-
return;
|
|
8140
|
-
}
|
|
8141
|
-
warn(
|
|
8142
|
-
`${name} could not be read (${errorText(err)}), so it was left out of shared/ this run. Check its permissions, or whether another program has it open`
|
|
8143
|
-
);
|
|
8287
|
+
warnUnreadableLocalName(name, target, err, opts.dryRun === true);
|
|
8144
8288
|
return;
|
|
8145
8289
|
}
|
|
8146
8290
|
if (stat === void 0) return;
|
|
8147
8291
|
if (stat.isSymbolicLink()) return;
|
|
8292
|
+
const targetState = classifyPresence(target);
|
|
8293
|
+
if (isUnusableTarget(targetState)) {
|
|
8294
|
+
warnUnusableSharedTarget(name, opts.dryRun === true, targetState);
|
|
8295
|
+
return;
|
|
8296
|
+
}
|
|
8148
8297
|
if (notShared(target)) return;
|
|
8149
8298
|
if (opts.dryRun === true) {
|
|
8150
8299
|
emitMirror(opts.onPreview, name, localPath, target);
|
|
@@ -8158,11 +8307,48 @@ function mirrorOneSharedName(name, claude, repo, policy, opts) {
|
|
|
8158
8307
|
}
|
|
8159
8308
|
emitMirrorWet(opts.onPreview, name, localPath, target);
|
|
8160
8309
|
}
|
|
8310
|
+
function warnUnusableSharedDir(dryRun, state) {
|
|
8311
|
+
const reason = state === "dangling" ? "shared/ does not resolve to anything usable in the sync repo. Remove it, or restore what it points at" : "shared/ could not be read in the sync repo, so whether it is usable could not be determined. Check its permissions there";
|
|
8312
|
+
if (dryRun) {
|
|
8313
|
+
warn(`no shared name would be captured into shared/: ${reason}`);
|
|
8314
|
+
return;
|
|
8315
|
+
}
|
|
8316
|
+
warn(
|
|
8317
|
+
`no shared name was captured into shared/ this run: ${reason}, then run \`nomad push\` again`
|
|
8318
|
+
);
|
|
8319
|
+
}
|
|
8320
|
+
function resolvesToDirectory(p) {
|
|
8321
|
+
try {
|
|
8322
|
+
return statSync12(p).isDirectory();
|
|
8323
|
+
} catch {
|
|
8324
|
+
return false;
|
|
8325
|
+
}
|
|
8326
|
+
}
|
|
8327
|
+
function warnSharedNotADirectory(dryRun) {
|
|
8328
|
+
const reason = "shared/ in the sync repo is not a directory, so there is nowhere to capture names into. Replace it with the shared/ directory the repo expects";
|
|
8329
|
+
if (dryRun) {
|
|
8330
|
+
warn(`no shared name would be captured into shared/: ${reason}`);
|
|
8331
|
+
return;
|
|
8332
|
+
}
|
|
8333
|
+
warn(
|
|
8334
|
+
`no shared name was captured into shared/ this run: ${reason}, then run \`nomad push\` again`
|
|
8335
|
+
);
|
|
8336
|
+
}
|
|
8161
8337
|
function mirrorSharedNames(map, policy, opts = {}) {
|
|
8162
8338
|
if (process.platform !== "win32") return;
|
|
8163
8339
|
if (map === null) return;
|
|
8164
8340
|
const claude = claudeHome();
|
|
8165
8341
|
const repo = repoHome();
|
|
8342
|
+
const sharedPath = join57(repo, "shared");
|
|
8343
|
+
const sharedState = classifyPresence(sharedPath);
|
|
8344
|
+
if (isUnusableTarget(sharedState)) {
|
|
8345
|
+
warnUnusableSharedDir(opts.dryRun === true, sharedState);
|
|
8346
|
+
return;
|
|
8347
|
+
}
|
|
8348
|
+
if (sharedState === "resolves" && !resolvesToDirectory(sharedPath)) {
|
|
8349
|
+
warnSharedNotADirectory(opts.dryRun === true);
|
|
8350
|
+
return;
|
|
8351
|
+
}
|
|
8166
8352
|
const linkNames = opts.linkNames ?? allSharedLinks(map);
|
|
8167
8353
|
for (const name of linkNames) {
|
|
8168
8354
|
mirrorOneSharedName(name, claude, repo, policy, opts);
|
|
@@ -8182,7 +8368,7 @@ function presentAt(abs) {
|
|
|
8182
8368
|
}
|
|
8183
8369
|
}
|
|
8184
8370
|
function removeUntrackedDenied(repo, path, segment, ts) {
|
|
8185
|
-
const abs =
|
|
8371
|
+
const abs = join57(repo, path);
|
|
8186
8372
|
const denied = `the path segment "${segment}" is on the never-sync list`;
|
|
8187
8373
|
if (!presentAt(abs)) {
|
|
8188
8374
|
warn(
|
|
@@ -8190,7 +8376,7 @@ function removeUntrackedDenied(repo, path, segment, ts) {
|
|
|
8190
8376
|
);
|
|
8191
8377
|
return;
|
|
8192
8378
|
}
|
|
8193
|
-
const snapshotted =
|
|
8379
|
+
const snapshotted = existsSync41(abs);
|
|
8194
8380
|
try {
|
|
8195
8381
|
backupRepoWrite(abs, ts, repo);
|
|
8196
8382
|
} catch (err) {
|
|
@@ -8226,7 +8412,7 @@ function reportTrackedDenied(repo, path, segment) {
|
|
|
8226
8412
|
);
|
|
8227
8413
|
return;
|
|
8228
8414
|
}
|
|
8229
|
-
if (!
|
|
8415
|
+
if (!existsSync41(join57(repo, path))) return;
|
|
8230
8416
|
warn(
|
|
8231
8417
|
`${path} is tracked and has changes against HEAD: ${denied}. Nothing was changed. Run "git checkout HEAD -- ${path}" to put the committed content back, or move the file outside shared/ if you want to keep it. Neither of those takes the committed copy out of the repo: "git rm -- ${path}" and a commit does that going forward, and if it holds a real secret, rotate it and rewrite history, because nomad only changes your local worktree and index and cannot scrub what a previous push already sent to the remote`
|
|
8232
8418
|
);
|
|
@@ -8244,8 +8430,8 @@ function revertDeniedMirrorPaths(repo, status, ts) {
|
|
|
8244
8430
|
|
|
8245
8431
|
// src/preview.ts
|
|
8246
8432
|
init_config();
|
|
8247
|
-
import { existsSync as
|
|
8248
|
-
import { join as
|
|
8433
|
+
import { existsSync as existsSync43 } from "node:fs";
|
|
8434
|
+
import { join as join59 } from "node:path";
|
|
8249
8435
|
|
|
8250
8436
|
// node_modules/diff/libesm/diff/base.js
|
|
8251
8437
|
var Diff = class {
|
|
@@ -8520,16 +8706,16 @@ function diffLinesToUnified(oldStr, newStr) {
|
|
|
8520
8706
|
|
|
8521
8707
|
// src/preview.skills.ts
|
|
8522
8708
|
init_config();
|
|
8523
|
-
import { existsSync as
|
|
8524
|
-
import { join as
|
|
8709
|
+
import { existsSync as existsSync42, readdirSync as readdirSync19 } from "node:fs";
|
|
8710
|
+
import { join as join58 } from "node:path";
|
|
8525
8711
|
function buildSkillsPreviewSection() {
|
|
8526
8712
|
const s = section("Skills");
|
|
8527
|
-
const sharedSkills =
|
|
8528
|
-
if (!
|
|
8713
|
+
const sharedSkills = join58(repoHome(), "shared", "skills");
|
|
8714
|
+
if (!existsSync42(sharedSkills)) return s;
|
|
8529
8715
|
const sharedNames = readdirSync19(sharedSkills, { encoding: "utf8" }).filter((name) => !isSkillExcluded(name)).sort((a, b) => a.localeCompare(b, "en"));
|
|
8530
8716
|
for (const name of sharedNames) addItem(s, name);
|
|
8531
|
-
const localSkills =
|
|
8532
|
-
const localOnly =
|
|
8717
|
+
const localSkills = join58(claudeHome(), "skills");
|
|
8718
|
+
const localOnly = existsSync42(localSkills) ? readdirSync19(localSkills, { encoding: "utf8" }).filter(
|
|
8533
8719
|
(name) => !isSkillExcluded(name) && !sharedNames.includes(name)
|
|
8534
8720
|
).length : 0;
|
|
8535
8721
|
if (localOnly > 0) {
|
|
@@ -8551,7 +8737,7 @@ function diffJsonStrings(currentJsonText, newJsonText) {
|
|
|
8551
8737
|
return lines.join("\n");
|
|
8552
8738
|
}
|
|
8553
8739
|
function readJsonOrNull(path) {
|
|
8554
|
-
if (!
|
|
8740
|
+
if (!existsSync43(path)) return null;
|
|
8555
8741
|
try {
|
|
8556
8742
|
return readJson(path);
|
|
8557
8743
|
} catch {
|
|
@@ -8565,12 +8751,12 @@ function previewSettings(basePath, hostPath, settingsPath) {
|
|
|
8565
8751
|
}
|
|
8566
8752
|
const notes = [];
|
|
8567
8753
|
const hostOverrides = readJsonOrNull(hostPath);
|
|
8568
|
-
if (hostOverrides === null &&
|
|
8754
|
+
if (hostOverrides === null && existsSync43(hostPath)) {
|
|
8569
8755
|
notes.push(`malformed hosts/${HOST}.json; ignoring overrides`);
|
|
8570
8756
|
}
|
|
8571
8757
|
const merged = stripGsdHookEntries(deepMerge(base, hostOverrides ?? {}));
|
|
8572
8758
|
const current = readJsonOrNull(settingsPath);
|
|
8573
|
-
if (current === null &&
|
|
8759
|
+
if (current === null && existsSync43(settingsPath)) {
|
|
8574
8760
|
return { diff: "", notes: [...notes, "malformed; skipping diff"] };
|
|
8575
8761
|
}
|
|
8576
8762
|
const strippedCurrent = stripGsdHookEntries(current ?? {});
|
|
@@ -8637,9 +8823,9 @@ function computePreview(ts, map, verb = "pull", plans) {
|
|
|
8637
8823
|
onPreview: (e) => addItem(links, formatLinkRow(e))
|
|
8638
8824
|
});
|
|
8639
8825
|
const settingsResult = previewSettings(
|
|
8640
|
-
|
|
8641
|
-
|
|
8642
|
-
|
|
8826
|
+
join59(repo, "shared", "settings.base.json"),
|
|
8827
|
+
join59(repo, "hosts", `${HOST}.json`),
|
|
8828
|
+
join59(claude, "settings.json")
|
|
8643
8829
|
);
|
|
8644
8830
|
const settingsSection = buildSettingsSectionForPreview(settingsResult);
|
|
8645
8831
|
const sessions = section("Sessions");
|
|
@@ -8655,7 +8841,7 @@ function computePreview(ts, map, verb = "pull", plans) {
|
|
|
8655
8841
|
const extras = section("Extras");
|
|
8656
8842
|
let extrasSkipped = 0;
|
|
8657
8843
|
let extrasUnmapped = 0;
|
|
8658
|
-
if (
|
|
8844
|
+
if (existsSync43(join59(repo, "path-map.json")) && existsSync43(join59(repo, "shared", "extras"))) {
|
|
8659
8845
|
const extrasResult = remapExtrasPull(ts, { dryRun: true });
|
|
8660
8846
|
for (const entry of extrasResult.wouldPull) {
|
|
8661
8847
|
addItem(extras, entry);
|
|
@@ -8676,7 +8862,7 @@ function computePreview(ts, map, verb = "pull", plans) {
|
|
|
8676
8862
|
init_utils();
|
|
8677
8863
|
init_utils_json();
|
|
8678
8864
|
function readMapForMirror(mapPath) {
|
|
8679
|
-
if (!
|
|
8865
|
+
if (!existsSync44(mapPath)) return { projects: {} };
|
|
8680
8866
|
try {
|
|
8681
8867
|
return readPathMap(mapPath);
|
|
8682
8868
|
} catch {
|
|
@@ -8723,7 +8909,7 @@ function reconcileSharedLinksBeforePull(repo, ts) {
|
|
|
8723
8909
|
let namesDerived = false;
|
|
8724
8910
|
let derivedSharedDirs;
|
|
8725
8911
|
try {
|
|
8726
|
-
const map = readMapForMirror(
|
|
8912
|
+
const map = readMapForMirror(join60(repo, "path-map.json"));
|
|
8727
8913
|
if (map !== null) {
|
|
8728
8914
|
linkNames = allSharedLinks(map);
|
|
8729
8915
|
namesDerived = true;
|
|
@@ -8762,7 +8948,7 @@ function buildMirrorSection(events, discard) {
|
|
|
8762
8948
|
function describeSkippedMirrorDiscard(repo, ts) {
|
|
8763
8949
|
if (process.platform !== "win32") return null;
|
|
8764
8950
|
try {
|
|
8765
|
-
const map = readMapForMirror(
|
|
8951
|
+
const map = readMapForMirror(join60(repo, "path-map.json"));
|
|
8766
8952
|
const linkNames = map !== null ? allSharedLinks(map, { quiet: true }) : void 0;
|
|
8767
8953
|
const captures = [];
|
|
8768
8954
|
stageLocalSharedEdits(map, ts, {
|
|
@@ -8771,13 +8957,13 @@ function describeSkippedMirrorDiscard(repo, ts) {
|
|
|
8771
8957
|
linkNames
|
|
8772
8958
|
});
|
|
8773
8959
|
if (captures.length === 0) return null;
|
|
8774
|
-
return { count: captures.length, backupPath:
|
|
8960
|
+
return { count: captures.length, backupPath: join60(backupBase(), ts) };
|
|
8775
8961
|
} catch {
|
|
8776
8962
|
return null;
|
|
8777
8963
|
}
|
|
8778
8964
|
}
|
|
8779
8965
|
function planSharedReconcileBeforePull(repo, ts) {
|
|
8780
|
-
const map = readMapForMirror(
|
|
8966
|
+
const map = readMapForMirror(join60(repo, "path-map.json"));
|
|
8781
8967
|
const captures = [];
|
|
8782
8968
|
const linkNames = process.platform === "win32" && map !== null ? allSharedLinks(map) : void 0;
|
|
8783
8969
|
stageLocalSharedEdits(map, ts, { dryRun: true, onPreview: (e) => captures.push(e), linkNames });
|
|
@@ -8792,8 +8978,8 @@ function planSharedReconcileBeforePull(repo, ts) {
|
|
|
8792
8978
|
// src/commands.pull.collision.ts
|
|
8793
8979
|
init_config();
|
|
8794
8980
|
init_exit_codes();
|
|
8795
|
-
import { existsSync as
|
|
8796
|
-
import { join as
|
|
8981
|
+
import { existsSync as existsSync45, lstatSync as lstatSync21, readFileSync as readFileSync20, rmSync as rmSync19 } from "node:fs";
|
|
8982
|
+
import { join as join61 } from "node:path";
|
|
8797
8983
|
init_utils();
|
|
8798
8984
|
var SHARED_PREFIX = /^shared\//;
|
|
8799
8985
|
var EXTENSION = /(\.[^.]+)?$/;
|
|
@@ -8849,15 +9035,15 @@ function isContainedMirrorPath(rel) {
|
|
|
8849
9035
|
}
|
|
8850
9036
|
function matchesLocalOriginal(repo, rel) {
|
|
8851
9037
|
try {
|
|
8852
|
-
const local =
|
|
8853
|
-
return readFileSync20(
|
|
9038
|
+
const local = join61(claudeHome(), rel.replace(SHARED_PREFIX, ""));
|
|
9039
|
+
return readFileSync20(join61(repo, rel)).equals(readFileSync20(local));
|
|
8854
9040
|
} catch {
|
|
8855
9041
|
return false;
|
|
8856
9042
|
}
|
|
8857
9043
|
}
|
|
8858
9044
|
function removeMirroredCopies(repo, repoRelPaths) {
|
|
8859
9045
|
for (const rel of repoRelPaths.filter(isContainedMirrorPath)) {
|
|
8860
|
-
const abs =
|
|
9046
|
+
const abs = join61(repo, rel);
|
|
8861
9047
|
try {
|
|
8862
9048
|
if (lstatSync21(abs, { throwIfNoEntry: false })?.isFile() !== true) continue;
|
|
8863
9049
|
if (!matchesLocalOriginal(repo, rel)) continue;
|
|
@@ -8866,7 +9052,7 @@ function removeMirroredCopies(repo, repoRelPaths) {
|
|
|
8866
9052
|
warn(`could not remove nomad's copy at ${abs}: ${err.message}`);
|
|
8867
9053
|
}
|
|
8868
9054
|
}
|
|
8869
|
-
return repoRelPaths.every((rel) => !
|
|
9055
|
+
return repoRelPaths.every((rel) => !existsSync45(join61(repo, rel)));
|
|
8870
9056
|
}
|
|
8871
9057
|
function pullWithCollisionRunbook(repo, mirrored) {
|
|
8872
9058
|
try {
|
|
@@ -9073,7 +9259,7 @@ function runPullCore(opts = {}) {
|
|
|
9073
9259
|
try {
|
|
9074
9260
|
return runPullWithBackupTs(ts, opts);
|
|
9075
9261
|
} finally {
|
|
9076
|
-
discardEmptyBackupDir(
|
|
9262
|
+
discardEmptyBackupDir(join62(backup, ts));
|
|
9077
9263
|
}
|
|
9078
9264
|
}
|
|
9079
9265
|
function runPullWithBackupTs(ts, opts) {
|
|
@@ -9084,7 +9270,7 @@ function runPullWithBackupTs(ts, opts) {
|
|
|
9084
9270
|
const backup = backupBase();
|
|
9085
9271
|
const recovered = handleWedge(repo, forceRemote);
|
|
9086
9272
|
if (!dryRun) {
|
|
9087
|
-
const backupRoot =
|
|
9273
|
+
const backupRoot = join62(backup, ts);
|
|
9088
9274
|
try {
|
|
9089
9275
|
mkdirSync14(backupRoot, { recursive: true });
|
|
9090
9276
|
} catch (err) {
|
|
@@ -9107,8 +9293,8 @@ function runPullWithBackupTs(ts, opts) {
|
|
|
9107
9293
|
pullWithCollisionRunbook(repo, mirrored);
|
|
9108
9294
|
});
|
|
9109
9295
|
assertNoAutostashConflict(repo, "nomad pull");
|
|
9110
|
-
const mapPath =
|
|
9111
|
-
const map =
|
|
9296
|
+
const mapPath = join62(repo, "path-map.json");
|
|
9297
|
+
const map = existsSync46(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
9112
9298
|
const divergedKeptLocal = divergenceCheckExtras(ts, dryRun ? prePostHeads : void 0);
|
|
9113
9299
|
if (dryRun) {
|
|
9114
9300
|
computePreview(ts, map, "pull", plansAgainst(sharedPlans, map));
|
|
@@ -9135,8 +9321,8 @@ function runPullWithBackupTs(ts, opts) {
|
|
|
9135
9321
|
}
|
|
9136
9322
|
function cmdPull(opts = {}) {
|
|
9137
9323
|
const repo = repoHome();
|
|
9138
|
-
if (!
|
|
9139
|
-
if (!
|
|
9324
|
+
if (!existsSync46(repo)) die(`repo not cloned at ${repo}`);
|
|
9325
|
+
if (!existsSync46(join62(repo, "shared", "settings.base.json"))) {
|
|
9140
9326
|
die("repo not initialized; run 'nomad init' to scaffold");
|
|
9141
9327
|
}
|
|
9142
9328
|
const handle = acquireLock("pull");
|
|
@@ -9159,13 +9345,13 @@ function cmdPull(opts = {}) {
|
|
|
9159
9345
|
|
|
9160
9346
|
// src/commands.push.ts
|
|
9161
9347
|
init_config();
|
|
9162
|
-
import { existsSync as
|
|
9163
|
-
import { join as
|
|
9348
|
+
import { existsSync as existsSync50 } from "node:fs";
|
|
9349
|
+
import { join as join67 } from "node:path";
|
|
9164
9350
|
|
|
9165
9351
|
// src/commands.push.selection.ts
|
|
9166
9352
|
init_config();
|
|
9167
|
-
import { existsSync as
|
|
9168
|
-
import { join as
|
|
9353
|
+
import { existsSync as existsSync47, statSync as statSync13 } from "node:fs";
|
|
9354
|
+
import { join as join63 } from "node:path";
|
|
9169
9355
|
init_utils_json();
|
|
9170
9356
|
function buildCurrentMap(map) {
|
|
9171
9357
|
const current = {};
|
|
@@ -9174,10 +9360,10 @@ function buildCurrentMap(map) {
|
|
|
9174
9360
|
for (const [, hostMap] of Object.entries(map.projects)) {
|
|
9175
9361
|
const localPath = hostMap[HOST];
|
|
9176
9362
|
if (!localPath) continue;
|
|
9177
|
-
const localDir =
|
|
9178
|
-
if (!
|
|
9363
|
+
const localDir = join63(claude, "projects", encodePath(localPath));
|
|
9364
|
+
if (!existsSync47(localDir)) continue;
|
|
9179
9365
|
for (const f of enumerateSourceFiles(localDir)) {
|
|
9180
|
-
const st =
|
|
9366
|
+
const st = statSync13(f);
|
|
9181
9367
|
current[f] = { size: st.size, mtime: st.mtimeMs };
|
|
9182
9368
|
}
|
|
9183
9369
|
}
|
|
@@ -9206,7 +9392,7 @@ function computePushSelection(map, old, scannerVersion, configHash, fullScan) {
|
|
|
9206
9392
|
};
|
|
9207
9393
|
}
|
|
9208
9394
|
function loadSelectionForPush(mapPath, old, scannerVersion, configHash, fullScan) {
|
|
9209
|
-
const map =
|
|
9395
|
+
const map = existsSync47(mapPath) ? readPathMap(mapPath) : null;
|
|
9210
9396
|
const { selection, newManifest } = computePushSelection(
|
|
9211
9397
|
map,
|
|
9212
9398
|
old,
|
|
@@ -9299,14 +9485,14 @@ function enforceAllowList(statusPorcelain, map) {
|
|
|
9299
9485
|
|
|
9300
9486
|
// src/commands.push.settings.ts
|
|
9301
9487
|
init_config();
|
|
9302
|
-
import { existsSync as
|
|
9303
|
-
import { join as
|
|
9488
|
+
import { existsSync as existsSync48 } from "node:fs";
|
|
9489
|
+
import { join as join64 } from "node:path";
|
|
9304
9490
|
init_utils();
|
|
9305
9491
|
init_utils_fs();
|
|
9306
9492
|
init_utils_json();
|
|
9307
9493
|
function stripGsdHooksFromBase(repo, backup) {
|
|
9308
|
-
const basePath =
|
|
9309
|
-
if (!
|
|
9494
|
+
const basePath = join64(repo, "shared", "settings.base.json");
|
|
9495
|
+
if (!existsSync48(basePath)) return;
|
|
9310
9496
|
let base;
|
|
9311
9497
|
try {
|
|
9312
9498
|
base = readJson(basePath);
|
|
@@ -9320,14 +9506,14 @@ function stripGsdHooksFromBase(repo, backup) {
|
|
|
9320
9506
|
writeJsonAtomic(basePath, stripped);
|
|
9321
9507
|
}
|
|
9322
9508
|
function reportSettingsAheadDrift(repo) {
|
|
9323
|
-
const basePath =
|
|
9324
|
-
if (!
|
|
9325
|
-
const settingsPath =
|
|
9326
|
-
if (!
|
|
9509
|
+
const basePath = join64(repo, "shared", "settings.base.json");
|
|
9510
|
+
if (!existsSync48(basePath)) return;
|
|
9511
|
+
const settingsPath = join64(claudeHome(), "settings.json");
|
|
9512
|
+
if (!existsSync48(settingsPath)) return;
|
|
9327
9513
|
try {
|
|
9328
9514
|
const base = readJson(basePath);
|
|
9329
|
-
const hostPath =
|
|
9330
|
-
const overrides =
|
|
9515
|
+
const hostPath = join64(repo, "hosts", `${HOST}.json`);
|
|
9516
|
+
const overrides = existsSync48(hostPath) ? readJson(hostPath) : {};
|
|
9331
9517
|
const merged = deepMerge(base, overrides);
|
|
9332
9518
|
const settings = readJson(settingsPath);
|
|
9333
9519
|
const { ahead } = classifySettingsDrift(merged, settings);
|
|
@@ -9344,9 +9530,9 @@ function reportSettingsAheadDrift(repo) {
|
|
|
9344
9530
|
// src/commands.push.guards.ts
|
|
9345
9531
|
init_push_checks();
|
|
9346
9532
|
init_utils();
|
|
9347
|
-
import { join as
|
|
9533
|
+
import { join as join65, relative as relative10 } from "node:path";
|
|
9348
9534
|
function guardGitlinks(repo) {
|
|
9349
|
-
const gitlinks = findGitlinks(
|
|
9535
|
+
const gitlinks = findGitlinks(join65(repo, "shared"));
|
|
9350
9536
|
if (gitlinks.length === 0) return;
|
|
9351
9537
|
for (const p of gitlinks) {
|
|
9352
9538
|
const rel = relative10(repo, p).replaceAll("\\", "/");
|
|
@@ -9456,9 +9642,9 @@ init_color();
|
|
|
9456
9642
|
init_config();
|
|
9457
9643
|
init_config_sharedDirs_guard();
|
|
9458
9644
|
import { randomBytes as randomBytes4 } from "node:crypto";
|
|
9459
|
-
import { copyFileSync, existsSync as
|
|
9645
|
+
import { copyFileSync, existsSync as existsSync49, lstatSync as lstatSync22, mkdirSync as mkdirSync15, readdirSync as readdirSync20, rmSync as rmSync20 } from "node:fs";
|
|
9460
9646
|
import { homedir as homedir7 } from "node:os";
|
|
9461
|
-
import { join as
|
|
9647
|
+
import { join as join66, relative as relative11, sep as sep14 } from "node:path";
|
|
9462
9648
|
init_push_leak_verdict();
|
|
9463
9649
|
init_push_gitleaks();
|
|
9464
9650
|
init_utils_fs();
|
|
@@ -9470,7 +9656,7 @@ function stageSessionDir(localDir, dstDir, changed) {
|
|
|
9470
9656
|
const matching = [...changed].filter((p) => p.startsWith(prefix));
|
|
9471
9657
|
if (matching.length === 0) return false;
|
|
9472
9658
|
for (const src of matching) {
|
|
9473
|
-
copyFileAtomic(src,
|
|
9659
|
+
copyFileAtomic(src, join66(dstDir, relative11(localDir, src)));
|
|
9474
9660
|
}
|
|
9475
9661
|
return true;
|
|
9476
9662
|
}
|
|
@@ -9486,14 +9672,14 @@ function stageSessions(tmpRoot, map, changed) {
|
|
|
9486
9672
|
if (!p || p === "TBD") continue;
|
|
9487
9673
|
reverse.set(encodePath(p), logical);
|
|
9488
9674
|
}
|
|
9489
|
-
const localProjects =
|
|
9490
|
-
if (!
|
|
9675
|
+
const localProjects = join66(claudeHome(), "projects");
|
|
9676
|
+
if (!existsSync49(localProjects)) return 0;
|
|
9491
9677
|
let staged = 0;
|
|
9492
9678
|
for (const dir of readdirSync20(localProjects)) {
|
|
9493
9679
|
const logical = reverse.get(dir);
|
|
9494
9680
|
if (!logical) continue;
|
|
9495
|
-
const localDir =
|
|
9496
|
-
const dstDir =
|
|
9681
|
+
const localDir = join66(localProjects, dir);
|
|
9682
|
+
const dstDir = join66(tmpRoot, "shared", "projects", logical);
|
|
9497
9683
|
if (stageSessionDir(localDir, dstDir, changed)) staged++;
|
|
9498
9684
|
}
|
|
9499
9685
|
return staged;
|
|
@@ -9509,9 +9695,9 @@ function stageExtras(tmpRoot, map) {
|
|
|
9509
9695
|
if (!localRoot || localRoot === "TBD") continue;
|
|
9510
9696
|
for (const dirname15 of dirnames) {
|
|
9511
9697
|
if (!whitelist.includes(dirname15)) continue;
|
|
9512
|
-
const src =
|
|
9513
|
-
if (!
|
|
9514
|
-
const dst =
|
|
9698
|
+
const src = join66(localRoot, dirname15);
|
|
9699
|
+
if (!existsSync49(src)) continue;
|
|
9700
|
+
const dst = join66(tmpRoot, "shared", "extras", logical, dirname15);
|
|
9515
9701
|
copyExtras(src, dst);
|
|
9516
9702
|
staged++;
|
|
9517
9703
|
}
|
|
@@ -9519,19 +9705,19 @@ function stageExtras(tmpRoot, map) {
|
|
|
9519
9705
|
return staged;
|
|
9520
9706
|
}
|
|
9521
9707
|
function stageSkills(tmpRoot) {
|
|
9522
|
-
const localSkills =
|
|
9708
|
+
const localSkills = join66(claudeHome(), "skills");
|
|
9523
9709
|
const stat = lstatSync22(localSkills, { throwIfNoEntry: false });
|
|
9524
9710
|
if (stat === void 0 || stat.isSymbolicLink()) return 0;
|
|
9525
9711
|
const names = readdirSync20(localSkills, { encoding: "utf8" }).filter((n) => !isSkillExcluded(n));
|
|
9526
9712
|
if (names.length === 0) return 0;
|
|
9527
|
-
copySkillsPush(localSkills,
|
|
9713
|
+
copySkillsPush(localSkills, join66(tmpRoot, "shared", "skills"));
|
|
9528
9714
|
return names.length;
|
|
9529
9715
|
}
|
|
9530
9716
|
function previewPushLeaks(map, opts = {}) {
|
|
9531
|
-
const cacheDir =
|
|
9717
|
+
const cacheDir = join66(homedir7(), ".cache", "claude-nomad");
|
|
9532
9718
|
mkdirSync15(cacheDir, { recursive: true });
|
|
9533
9719
|
const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes4(4).toString("hex")}`;
|
|
9534
|
-
const tmpRoot =
|
|
9720
|
+
const tmpRoot = join66(cacheDir, `push-preview-tree-${stamp}`);
|
|
9535
9721
|
try {
|
|
9536
9722
|
const sessionCount = stageSessions(tmpRoot, map, opts.selection?.changed);
|
|
9537
9723
|
const extrasCount = stageExtras(tmpRoot, map);
|
|
@@ -9539,9 +9725,9 @@ function previewPushLeaks(map, opts = {}) {
|
|
|
9539
9725
|
if (sessionCount + extrasCount + skillCount === 0) {
|
|
9540
9726
|
return { leak: false, verdictRow: NOTHING_TO_SCAN_ROW, recovery: null, findings: [] };
|
|
9541
9727
|
}
|
|
9542
|
-
const ignoreFile =
|
|
9543
|
-
if (
|
|
9544
|
-
copyFileSync(ignoreFile,
|
|
9728
|
+
const ignoreFile = join66(repoHome(), ".gitleaksignore");
|
|
9729
|
+
if (existsSync49(ignoreFile)) {
|
|
9730
|
+
copyFileSync(ignoreFile, join66(tmpRoot, ".gitleaksignore"));
|
|
9545
9731
|
}
|
|
9546
9732
|
let findings;
|
|
9547
9733
|
try {
|
|
@@ -9669,7 +9855,7 @@ async function runPushCore(opts = {}) {
|
|
|
9669
9855
|
const scannerVersion = probeGitleaks();
|
|
9670
9856
|
const configHash = computeConfigHash();
|
|
9671
9857
|
const old = readManifest(manifestPath());
|
|
9672
|
-
const mapPath =
|
|
9858
|
+
const mapPath = join67(repo, "path-map.json");
|
|
9673
9859
|
const { map, selection, newManifest } = loadSelectionForPush(
|
|
9674
9860
|
mapPath,
|
|
9675
9861
|
old,
|
|
@@ -9722,7 +9908,7 @@ async function cmdPush(opts = {}) {
|
|
|
9722
9908
|
opts.allowRule
|
|
9723
9909
|
);
|
|
9724
9910
|
const repo = repoHome();
|
|
9725
|
-
if (!
|
|
9911
|
+
if (!existsSync50(repo)) die(`repo not cloned at ${repo}`);
|
|
9726
9912
|
const handle = acquireLock("push");
|
|
9727
9913
|
if (handle === null) process.exit(0);
|
|
9728
9914
|
try {
|
|
@@ -9740,8 +9926,8 @@ async function cmdPush(opts = {}) {
|
|
|
9740
9926
|
}
|
|
9741
9927
|
|
|
9742
9928
|
// src/commands.sync.ts
|
|
9743
|
-
import { existsSync as
|
|
9744
|
-
import { join as
|
|
9929
|
+
import { existsSync as existsSync51 } from "node:fs";
|
|
9930
|
+
import { join as join68 } from "node:path";
|
|
9745
9931
|
init_config();
|
|
9746
9932
|
init_color();
|
|
9747
9933
|
init_utils();
|
|
@@ -9882,8 +10068,8 @@ async function cmdSync(opts = {}) {
|
|
|
9882
10068
|
const dryRun = opts.dryRun === true;
|
|
9883
10069
|
const verbose = opts.verbose === true;
|
|
9884
10070
|
const repo = repoHome();
|
|
9885
|
-
if (!
|
|
9886
|
-
if (!
|
|
10071
|
+
if (!existsSync51(repo)) die(`repo not cloned at ${repo}`);
|
|
10072
|
+
if (!existsSync51(join68(repo, "shared", "settings.base.json"))) {
|
|
9887
10073
|
die("repo not initialized; run 'nomad init' to scaffold");
|
|
9888
10074
|
}
|
|
9889
10075
|
const handle = acquireLock("sync");
|
|
@@ -9959,8 +10145,8 @@ init_config();
|
|
|
9959
10145
|
// src/crash-report.write.ts
|
|
9960
10146
|
init_config();
|
|
9961
10147
|
import { randomBytes as randomBytes5 } from "node:crypto";
|
|
9962
|
-
import { mkdirSync as mkdirSync16, readdirSync as readdirSync21, statSync as
|
|
9963
|
-
import { join as
|
|
10148
|
+
import { mkdirSync as mkdirSync16, readdirSync as readdirSync21, statSync as statSync14, unlinkSync as unlinkSync2, writeFileSync as writeFileSync11 } from "node:fs";
|
|
10149
|
+
import { join as join70 } from "node:path";
|
|
9964
10150
|
|
|
9965
10151
|
// src/crash-report.ts
|
|
9966
10152
|
var CRASH_MAX_STACK_LINES = 50;
|
|
@@ -10045,15 +10231,15 @@ function buildCrashReport(input) {
|
|
|
10045
10231
|
// src/crash-report.redact.ts
|
|
10046
10232
|
import { mkdtempSync as mkdtempSync3, rmSync as rmSync21, writeFileSync as writeFileSync10 } from "node:fs";
|
|
10047
10233
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
10048
|
-
import { join as
|
|
10234
|
+
import { join as join69 } from "node:path";
|
|
10049
10235
|
init_push_gitleaks_scan();
|
|
10050
10236
|
var CRASH_SCAN_TIMEOUT_MS = 3e3;
|
|
10051
10237
|
var SCAN_UNAVAILABLE_ADVISORY = "\n\n[gitleaks value-based scan unavailable; only structural redaction applied. Review before sharing this file publicly.]\n";
|
|
10052
10238
|
function redactWithGitleaks(text, scan = scanFile) {
|
|
10053
10239
|
let dir;
|
|
10054
10240
|
try {
|
|
10055
|
-
dir = mkdtempSync3(
|
|
10056
|
-
const tmp =
|
|
10241
|
+
dir = mkdtempSync3(join69(tmpdir2(), "nomad-crash-scan-"));
|
|
10242
|
+
const tmp = join69(dir, "crash.txt");
|
|
10057
10243
|
writeFileSync10(tmp, text, { mode: 384 });
|
|
10058
10244
|
const findings = scan(tmp, false, CRASH_SCAN_TIMEOUT_MS);
|
|
10059
10245
|
if (findings === null) return text + SCAN_UNAVAILABLE_ADVISORY;
|
|
@@ -10078,7 +10264,7 @@ function listCrashFiles(dir = crashDir()) {
|
|
|
10078
10264
|
try {
|
|
10079
10265
|
return readdirSync21(dir).flatMap((name) => {
|
|
10080
10266
|
try {
|
|
10081
|
-
return [{ name, mtimeMs:
|
|
10267
|
+
return [{ name, mtimeMs: statSync14(join70(dir, name)).mtimeMs }];
|
|
10082
10268
|
} catch {
|
|
10083
10269
|
return [];
|
|
10084
10270
|
}
|
|
@@ -10092,7 +10278,7 @@ function pruneCrashDir(dir, keep = CRASH_RETENTION_KEEP) {
|
|
|
10092
10278
|
const targets = prunableByCount(files, keep);
|
|
10093
10279
|
for (const name of targets) {
|
|
10094
10280
|
try {
|
|
10095
|
-
unlinkSync2(
|
|
10281
|
+
unlinkSync2(join70(dir, name));
|
|
10096
10282
|
} catch {
|
|
10097
10283
|
}
|
|
10098
10284
|
}
|
|
@@ -10100,7 +10286,7 @@ function pruneCrashDir(dir, keep = CRASH_RETENTION_KEEP) {
|
|
|
10100
10286
|
function writeCrashReport(text, dir = crashDir()) {
|
|
10101
10287
|
mkdirSync16(dir, { recursive: true, mode: 448 });
|
|
10102
10288
|
const stamp = `${nowTimestamp()}-${process.pid}-${randomBytes5(4).toString("hex")}`;
|
|
10103
|
-
const path =
|
|
10289
|
+
const path = join70(dir, `crash-${stamp}.txt`);
|
|
10104
10290
|
writeFileSync11(path, text, { mode: 384 });
|
|
10105
10291
|
pruneCrashDir(dir);
|
|
10106
10292
|
return path;
|
|
@@ -10132,18 +10318,18 @@ function handleCrash(err, argv, opts) {
|
|
|
10132
10318
|
|
|
10133
10319
|
// src/diff.ts
|
|
10134
10320
|
init_config();
|
|
10135
|
-
import { existsSync as
|
|
10136
|
-
import { join as
|
|
10321
|
+
import { existsSync as existsSync52 } from "node:fs";
|
|
10322
|
+
import { join as join71 } from "node:path";
|
|
10137
10323
|
init_utils();
|
|
10138
10324
|
init_utils_fs();
|
|
10139
10325
|
init_utils_json();
|
|
10140
10326
|
function cmdDiff() {
|
|
10141
10327
|
try {
|
|
10142
10328
|
const repo = repoHome();
|
|
10143
|
-
if (!
|
|
10329
|
+
if (!existsSync52(repo)) die(`repo not cloned at ${repo}`);
|
|
10144
10330
|
const ts = freshBackupTs(backupBase());
|
|
10145
|
-
const mapPath =
|
|
10146
|
-
const map =
|
|
10331
|
+
const mapPath = join71(repo, "path-map.json");
|
|
10332
|
+
const map = existsSync52(mapPath) ? readPathMap(mapPath) : { projects: {} };
|
|
10147
10333
|
divergenceCheckExtras(ts);
|
|
10148
10334
|
computePreview(ts, map, "diff");
|
|
10149
10335
|
} catch (err) {
|
|
@@ -10158,8 +10344,8 @@ function cmdDiff() {
|
|
|
10158
10344
|
|
|
10159
10345
|
// src/init.ts
|
|
10160
10346
|
init_config();
|
|
10161
|
-
import { existsSync as
|
|
10162
|
-
import { join as
|
|
10347
|
+
import { existsSync as existsSync54, mkdirSync as mkdirSync17, writeFileSync as writeFileSync12 } from "node:fs";
|
|
10348
|
+
import { join as join73 } from "node:path";
|
|
10163
10349
|
|
|
10164
10350
|
// src/init.gh-onboard.ts
|
|
10165
10351
|
init_config();
|
|
@@ -10241,33 +10427,33 @@ init_config();
|
|
|
10241
10427
|
init_utils();
|
|
10242
10428
|
init_utils_fs();
|
|
10243
10429
|
init_utils_json();
|
|
10244
|
-
import { copyFileSync as copyFileSync2, cpSync as cpSync9, existsSync as
|
|
10245
|
-
import { join as
|
|
10430
|
+
import { copyFileSync as copyFileSync2, cpSync as cpSync9, existsSync as existsSync53, rmSync as rmSync22, statSync as statSync15 } from "node:fs";
|
|
10431
|
+
import { join as join72 } from "node:path";
|
|
10246
10432
|
function snapshotIntoShared(map) {
|
|
10247
10433
|
const repo = repoHome();
|
|
10248
10434
|
const claude = claudeHome();
|
|
10249
10435
|
for (const name of allSharedLinks(map)) {
|
|
10250
|
-
const src =
|
|
10251
|
-
if (!
|
|
10252
|
-
const dst =
|
|
10253
|
-
if (
|
|
10254
|
-
const gk =
|
|
10255
|
-
if (
|
|
10436
|
+
const src = join72(claude, name);
|
|
10437
|
+
if (!existsSync53(src)) continue;
|
|
10438
|
+
const dst = join72(repo, "shared", name);
|
|
10439
|
+
if (statSync15(src).isDirectory()) {
|
|
10440
|
+
const gk = join72(dst, ".gitkeep");
|
|
10441
|
+
if (existsSync53(gk)) rmSync22(gk);
|
|
10256
10442
|
cpSync9(src, dst, { recursive: true, force: false, errorOnExist: true });
|
|
10257
10443
|
} else {
|
|
10258
10444
|
copyFileSync2(src, dst);
|
|
10259
10445
|
}
|
|
10260
10446
|
log(`snapshotted shared/${name} from ${src}`);
|
|
10261
10447
|
}
|
|
10262
|
-
const userSettings =
|
|
10263
|
-
if (
|
|
10448
|
+
const userSettings = join72(claude, "settings.json");
|
|
10449
|
+
if (existsSync53(userSettings)) {
|
|
10264
10450
|
let parsed;
|
|
10265
10451
|
try {
|
|
10266
10452
|
parsed = readJson(userSettings);
|
|
10267
10453
|
} catch (err) {
|
|
10268
10454
|
return die(`malformed ${userSettings}: ${err.message}`);
|
|
10269
10455
|
}
|
|
10270
|
-
const hostFile =
|
|
10456
|
+
const hostFile = join72(repo, "hosts", `${HOST}.json`);
|
|
10271
10457
|
writeJsonAtomic(hostFile, parsed);
|
|
10272
10458
|
log(`snapshotted hosts/${HOST}.json from ${userSettings}`);
|
|
10273
10459
|
}
|
|
@@ -10281,14 +10467,14 @@ var GITATTRIBUTES = "# nomad: sync content is byte-managed, disable all line-end
|
|
|
10281
10467
|
var SHARED_KEEP_DIRS = ["agents", "skills", "commands", "rules", "hooks"];
|
|
10282
10468
|
function preflightConflict(repoHome2) {
|
|
10283
10469
|
const candidates = [
|
|
10284
|
-
|
|
10285
|
-
|
|
10286
|
-
|
|
10287
|
-
|
|
10288
|
-
|
|
10470
|
+
join73(repoHome2, "shared", "settings.base.json"),
|
|
10471
|
+
join73(repoHome2, "shared", "CLAUDE.md"),
|
|
10472
|
+
join73(repoHome2, "path-map.json"),
|
|
10473
|
+
join73(repoHome2, "hosts"),
|
|
10474
|
+
join73(repoHome2, "shared")
|
|
10289
10475
|
];
|
|
10290
10476
|
for (const c of candidates) {
|
|
10291
|
-
if (
|
|
10477
|
+
if (existsSync54(c)) return c;
|
|
10292
10478
|
}
|
|
10293
10479
|
return null;
|
|
10294
10480
|
}
|
|
@@ -10306,27 +10492,27 @@ function cmdInit(opts = {}) {
|
|
|
10306
10492
|
die(`already initialized; refusing to clobber ${conflict}`);
|
|
10307
10493
|
}
|
|
10308
10494
|
ensureOriginRepo(opts.repoName ?? DEFAULT_REPO_NAME, opts.run);
|
|
10309
|
-
mkdirSync17(
|
|
10310
|
-
mkdirSync17(
|
|
10495
|
+
mkdirSync17(join73(repo, "shared"), { recursive: true });
|
|
10496
|
+
mkdirSync17(join73(repo, "hosts"), { recursive: true });
|
|
10311
10497
|
for (const name of SHARED_KEEP_DIRS) {
|
|
10312
|
-
mkdirSync17(
|
|
10498
|
+
mkdirSync17(join73(repo, "shared", name), { recursive: true });
|
|
10313
10499
|
}
|
|
10314
|
-
const userClaudeMd =
|
|
10315
|
-
if (!snapshot || !
|
|
10316
|
-
writeFileSync12(
|
|
10500
|
+
const userClaudeMd = join73(claude, "CLAUDE.md");
|
|
10501
|
+
if (!snapshot || !existsSync54(userClaudeMd)) {
|
|
10502
|
+
writeFileSync12(join73(repo, "shared", "CLAUDE.md"), SHARED_CLAUDE_MD);
|
|
10317
10503
|
item("created shared/CLAUDE.md");
|
|
10318
10504
|
}
|
|
10319
10505
|
for (const name of SHARED_KEEP_DIRS) {
|
|
10320
|
-
writeFileSync12(
|
|
10506
|
+
writeFileSync12(join73(repo, "shared", name, ".gitkeep"), "");
|
|
10321
10507
|
item(`created shared/${name}/.gitkeep`);
|
|
10322
10508
|
}
|
|
10323
|
-
writeFileSync12(
|
|
10509
|
+
writeFileSync12(join73(repo, "hosts", ".gitkeep"), "");
|
|
10324
10510
|
item("created hosts/.gitkeep");
|
|
10325
|
-
writeJsonAtomic(
|
|
10511
|
+
writeJsonAtomic(join73(repo, "shared", "settings.base.json"), {});
|
|
10326
10512
|
item("created shared/settings.base.json");
|
|
10327
|
-
writeJsonAtomic(
|
|
10513
|
+
writeJsonAtomic(join73(repo, "path-map.json"), { projects: {} });
|
|
10328
10514
|
item("created path-map.json");
|
|
10329
|
-
writeFileSync12(
|
|
10515
|
+
writeFileSync12(join73(repo, ".gitattributes"), GITATTRIBUTES);
|
|
10330
10516
|
item("created .gitattributes");
|
|
10331
10517
|
if (snapshot) {
|
|
10332
10518
|
snapshotIntoShared({ projects: {} });
|
|
@@ -10392,21 +10578,21 @@ function maybeDisableRepoActions(repoHome2, run) {
|
|
|
10392
10578
|
// src/init.prompt.ts
|
|
10393
10579
|
init_config();
|
|
10394
10580
|
init_utils();
|
|
10395
|
-
import { existsSync as
|
|
10396
|
-
import { join as
|
|
10581
|
+
import { existsSync as existsSync55, readdirSync as readdirSync22, statSync as statSync16 } from "node:fs";
|
|
10582
|
+
import { join as join74 } from "node:path";
|
|
10397
10583
|
import { createInterface as createInterface3 } from "node:readline/promises";
|
|
10398
10584
|
function nonEmptyExists(path) {
|
|
10399
|
-
if (!
|
|
10585
|
+
if (!existsSync55(path)) return false;
|
|
10400
10586
|
try {
|
|
10401
|
-
if (
|
|
10587
|
+
if (statSync16(path).isDirectory()) return readdirSync22(path).length > 0;
|
|
10402
10588
|
return true;
|
|
10403
10589
|
} catch {
|
|
10404
10590
|
return false;
|
|
10405
10591
|
}
|
|
10406
10592
|
}
|
|
10407
10593
|
function hasExistingClaudeConfig(claudeHome2) {
|
|
10408
|
-
if (
|
|
10409
|
-
return SHARED_LINKS.some((name) => nonEmptyExists(
|
|
10594
|
+
if (existsSync55(join74(claudeHome2, "settings.json"))) return true;
|
|
10595
|
+
return SHARED_LINKS.some((name) => nonEmptyExists(join74(claudeHome2, name)));
|
|
10410
10596
|
}
|
|
10411
10597
|
async function confirmSnapshotDefault(claudeHome2) {
|
|
10412
10598
|
if (process.stdin.isTTY !== true || process.stdout.isTTY !== true) {
|
|
@@ -10713,7 +10899,7 @@ function parseSyncArgs(argv) {
|
|
|
10713
10899
|
// package.json
|
|
10714
10900
|
var package_default = {
|
|
10715
10901
|
name: "claude-nomad",
|
|
10716
|
-
version: "0.67.
|
|
10902
|
+
version: "0.67.2",
|
|
10717
10903
|
type: "module",
|
|
10718
10904
|
description: "Sync Claude Code config (~/.claude/) across machines via a private Git repo, with path remapping and per-host settings overrides.",
|
|
10719
10905
|
keywords: [
|
|
@@ -10786,10 +10972,11 @@ var package_default = {
|
|
|
10786
10972
|
"@commitlint/cli": "^21.0.1",
|
|
10787
10973
|
"@commitlint/config-conventional": "^21.0.1",
|
|
10788
10974
|
"@eslint/js": "^10.0.1",
|
|
10789
|
-
"@stryker-mutator/core": "
|
|
10790
|
-
"@stryker-mutator/vitest-runner": "
|
|
10975
|
+
"@stryker-mutator/core": "10.0.0",
|
|
10976
|
+
"@stryker-mutator/vitest-runner": "10.0.0",
|
|
10791
10977
|
"@types/node": "^22.0.0",
|
|
10792
10978
|
"@vitest/coverage-v8": "^4.1.6",
|
|
10979
|
+
"conventional-changelog-conventionalcommits": "^10.2.1",
|
|
10793
10980
|
diff: "^9.0.0",
|
|
10794
10981
|
esbuild: "^0.28.0",
|
|
10795
10982
|
eslint: "^10.4.0",
|
|
@@ -10960,15 +11147,15 @@ var DEFAULT_HELP = [
|
|
|
10960
11147
|
init_config();
|
|
10961
11148
|
init_utils();
|
|
10962
11149
|
init_utils_json();
|
|
10963
|
-
import { existsSync as
|
|
10964
|
-
import { join as
|
|
11150
|
+
import { existsSync as existsSync56, readFileSync as readFileSync21, readdirSync as readdirSync23 } from "node:fs";
|
|
11151
|
+
import { join as join75 } from "node:path";
|
|
10965
11152
|
function resumeCmd(sessionId) {
|
|
10966
11153
|
if (!/^[A-Za-z0-9_-]+$/.test(sessionId) || sessionId.length > 128) {
|
|
10967
11154
|
fail(`invalid session id: ${sessionId}`);
|
|
10968
11155
|
process.exit(1);
|
|
10969
11156
|
}
|
|
10970
|
-
const projectsRoot =
|
|
10971
|
-
if (!
|
|
11157
|
+
const projectsRoot = join75(claudeHome(), "projects");
|
|
11158
|
+
if (!existsSync56(projectsRoot)) {
|
|
10972
11159
|
fail(`${projectsRoot} does not exist`);
|
|
10973
11160
|
process.exit(1);
|
|
10974
11161
|
}
|
|
@@ -10982,8 +11169,8 @@ function resumeCmd(sessionId) {
|
|
|
10982
11169
|
fail(`no cwd field found in ${jsonlPath}`);
|
|
10983
11170
|
process.exit(1);
|
|
10984
11171
|
}
|
|
10985
|
-
const mapPath =
|
|
10986
|
-
if (!
|
|
11172
|
+
const mapPath = join75(repoHome(), "path-map.json");
|
|
11173
|
+
if (!existsSync56(mapPath)) {
|
|
10987
11174
|
fail("path-map.json missing");
|
|
10988
11175
|
process.exit(1);
|
|
10989
11176
|
}
|
|
@@ -11006,8 +11193,8 @@ function resumeCmd(sessionId) {
|
|
|
11006
11193
|
}
|
|
11007
11194
|
function findTranscriptPath(projectsRoot, sessionId) {
|
|
11008
11195
|
for (const dir of readdirSync23(projectsRoot)) {
|
|
11009
|
-
const candidate =
|
|
11010
|
-
if (
|
|
11196
|
+
const candidate = join75(projectsRoot, dir, `${sessionId}.jsonl`);
|
|
11197
|
+
if (existsSync56(candidate)) return candidate;
|
|
11011
11198
|
}
|
|
11012
11199
|
return null;
|
|
11013
11200
|
}
|